diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..496ee2ca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 000000000..205df726c --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + + + + + 404 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/CA/part1.webp b/CA/part1.webp new file mode 100644 index 000000000..540dd356e Binary files /dev/null and b/CA/part1.webp differ diff --git a/CA/part2.webp b/CA/part2.webp new file mode 100644 index 000000000..11af1fcf6 Binary files /dev/null and b/CA/part2.webp differ diff --git a/CA/part3.webp b/CA/part3.webp new file mode 100644 index 000000000..fb81ed633 Binary files /dev/null and b/CA/part3.webp differ diff --git a/CA/part5.webp b/CA/part5.webp new file mode 100644 index 000000000..593437c14 Binary files /dev/null and b/CA/part5.webp differ diff --git a/CA/part7.webp b/CA/part7.webp new file mode 100644 index 000000000..c79b3bfba Binary files /dev/null and b/CA/part7.webp differ diff --git a/README.md b/README.md deleted file mode 100644 index 713d5759e..000000000 --- a/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# NpgsqlRest Documentation Site - -This repository contains the built static files for the NpgsqlRest documentation site. - -The source files are maintained in [npgsqlrest-docs](https://github.com/NpgsqlRest/npgsqlrest-docs). - -## Deployment - -This site is automatically deployed to GitHub Pages at https://npgsqlrest.github.io diff --git a/about.html b/about.html new file mode 100644 index 000000000..736dc33b4 --- /dev/null +++ b/about.html @@ -0,0 +1,36 @@ + + + + + + About This Website | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

About This Website

This website is built and maintained with substantial help from AI tools — primarily Claude. Pages carry an "AI-assisted · verified against source" badge to make that explicit. A few blog posts written entirely by hand carry a "Human Written" badge instead. The badges link here.

How These Docs Are Made

The docs are maintained the same way NpgsqlRest itself proposes you build software: AI does the writing, machines verify the facts. Configuration keys, defaults, and annotation behavior are checked against the source code of the documented version — when the code and the docs disagree, the code wins and the docs get fixed. Accuracy is the contract; authorship is just tooling.

The project itself is a different story. The C# library, parser, code generator, and runtime are hand-written — more than two years of evenings and weekends — and covered by 2,200+ integration tests running against real PostgreSQL. The AI-assisted part is the website you're reading; the thing it documents is not.

We think that's the honest division of labor, and we'd rather label it than fake it. If the approach sounds familiar, it's because it is the product's whole thesis: declare the intent, let machines verify the result.

Look, I'm just a guy who likes SQL, that's all.

About the Author

NpgsqlRest is built and maintained by Vedran Bilopavlović — battle-tested in production, MIT-licensed, no paid tier, no telemetry.

Feedback

Found a mistake or an inaccuracy? Comments are open at the bottom of every page and go straight to the maintainer — that feedback loop is how AI-assisted docs stay accurate. Bug reports and feature requests live on GitHub; security issues have a private reporting channel.

+ + + + \ No newline at end of file diff --git a/annotations/allow-anonymous.html b/annotations/allow-anonymous.html new file mode 100644 index 000000000..327b94dca --- /dev/null +++ b/annotations/allow-anonymous.html @@ -0,0 +1,58 @@ + + + + + + ALLOW_ANONYMOUS Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

ALLOW_ANONYMOUS

Also known as

anonymous, allow_anon, anon (with or without @ prefix)

Allow unauthenticated access to the endpoint, overriding the global RequiresAuthorization setting.

Syntax

code
@allow_anonymous

Examples

Public Endpoint

sql
sql
create function get_public_info()
+returns json
+language sql
+begin atomic;
+select '{"version": "1.0"}'::json;
+end;
+
+comment on function get_public_info() is
+'HTTP GET
+@allow_anonymous';

Equivalent as a SQL file endpoint (sql/get-public-info.sql):

sql
sql
-- HTTP GET
+-- @allow_anonymous
+select '{"version": "1.0"}'::json;

Short Form

sql
sql
comment on function health_check() is
+'HTTP GET
+@anon';

Public Read, Protected Write Pattern

sql
sql
-- Anyone can read
+comment on function get_products() is
+'HTTP GET
+@allow_anonymous';
+
+-- Only authenticated users can create
+comment on function create_product(text, numeric) is
+'HTTP POST
+@authorize';

Behavior

  • Overrides the global RequiresAuthorization: true setting
  • Allows requests without authentication tokens
  • Useful for public APIs, health checks, and login endpoints

Comments

+ + + + \ No newline at end of file diff --git a/annotations/authorize.html b/annotations/authorize.html new file mode 100644 index 000000000..f60a68918 --- /dev/null +++ b/annotations/authorize.html @@ -0,0 +1,100 @@ + + + + + + AUTHORIZE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

AUTHORIZE

Also known as

authorized, requires_authorization (with or without @ prefix)

Require authentication for the endpoint. Optionally restrict access by roles, user names, or user IDs.

Syntax

code
@authorize
+@authorize <value1>, <value2>, <value3>, ...

Values can be role names, user names, or user IDs. Space-separated lists are also valid: @authorize admin editor john

Examples

Require Any Authenticated User

sql
sql
create function get_my_profile()
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where u.id = current_user_id();
+end;
+
+comment on function get_my_profile() is
+'HTTP GET
+@authorize';

Equivalent as a SQL file endpoint (sql/get-my-profile.sql):

sql
sql
-- HTTP GET
+-- @authorize
+select row_to_json(u) from users u where u.id = current_user_id();

Unauthenticated requests receive 401 Unauthorized.

Alternative Keywords

sql
sql
-- All of these are equivalent
+comment on function func1() is 'HTTP
+@authorize';
+
+comment on function func2() is 'HTTP
+@authorized';
+
+comment on function func3() is 'HTTP
+@requires_authorization';

Require Specific Role

sql
sql
create function delete_user(_id int)
+returns void
+language sql
+begin atomic;
+delete from users where id = _id;
+end;
+
+comment on function delete_user(int) is
+'HTTP DELETE
+@authorize admin';

Only users with the admin role can access this endpoint.

Authorize by User Name

Available since version 3.11.1

sql
sql
create function get_my_profile()
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where u.id = current_user_id();
+end;
+
+comment on function get_my_profile() is
+'HTTP GET
+@authorize john';

Only the user with user name john can access this endpoint. Matches against the DefaultNameClaimType claim.

Authorize by User ID

Available since version 3.11.1

sql
sql
create function get_account()
+returns json
+language sql
+begin atomic;
+select row_to_json(a) from accounts a where a.user_id = current_user_id();
+end;
+
+comment on function get_account() is
+'HTTP GET
+@authorize user123';

Only the user with user ID user123 can access this endpoint. Matches against the DefaultUserIdClaimType claim.

Multiple Roles

sql
sql
create function manage_content(_action text, _id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function manage_content(text, int) is
+'HTTP POST
+@authorize admin, editor, moderator';

Users must have at least one of the specified roles.

Mix of Roles and User Identifiers

Available since version 3.11.1

sql
sql
comment on function get_data() is
+'HTTP GET
+@authorize admin, user123, jane';

Access is granted if the user matches any of the specified values — whether it's a role name, user name, or user ID. Each value is checked against all three claim types (DefaultRoleClaimType, DefaultNameClaimType, DefaultUserIdClaimType).

Authorize Before HTTP

The order of annotations doesn't matter:

sql
sql
comment on function protected_func() is
+'@authorize admin
+HTTP GET';

Authorize on Separate Line

sql
sql
comment on function another_protected() is
+'HTTP
+
+@authorize';

Behavior

  • Returns 401 Unauthorized for unauthenticated requests
  • Returns 403 Forbidden when values are specified and user lacks a matching role, user name, or user ID
  • Works with all configured authentication providers (JWT, Cookie, Basic, etc.)
  • ALLOW_ANONYMOUS - Override to allow unauthenticated access
  • LOGIN - Mark as authentication endpoint
  • LOGOUT - Mark as sign-out endpoint

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/basic-auth-command.html b/annotations/basic-auth-command.html new file mode 100644 index 000000000..cc4ca3057 --- /dev/null +++ b/annotations/basic-auth-command.html @@ -0,0 +1,181 @@ + + + + + + BASIC_AUTH_COMMAND Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

BASIC_AUTH_COMMAND

Also known as

basic_authentication_command, challenge_command (with or without @ prefix)

Set the PostgreSQL command used to validate Basic Authentication credentials and return user claims.

Syntax

code
@challenge_command = <sql-command>
+@basic_auth_command = <sql-command>
  • sql-command: A SQL SELECT statement that validates credentials and returns user claims.

Command Parameters

The challenge command receives up to 5 parameters in the following order:

ParameterTypeDescription
$1textUsername from the Authorization header
$2textPassword from the Authorization header (plain text)
$3booleanPre-validation result: true if password matched annotation credentials, false if not, null if no credentials were configured in the annotation
$4textRealm name (from annotation or default NpgsqlRest)
$5textRequest path (e.g., /api/my-endpoint)

Return Value

The challenge command result set is interpreted exactly the same as the LOGIN endpoint. This includes support for special columns and claim mapping.

Special Columns

Four special column names control authentication behavior (configurable in AuthenticationOptions):

ColumnDefault NameTypePurpose
Statusstatusboolean or intControls success/failure. true or 200 = success, false or other status code = failure
SchemeschemetextAuthentication scheme name for sign-in
BodybodytextResponse body message (for schemes that don't write body)
HashhashtextPassword hash for verification by NpgsqlRest

Authentication Success

Return a row with claim columns. Column names become claim types, values become claim values:

sql
sql
-- Successful authentication returns claims
+select
+    1 as name_identifier,        -- becomes ClaimTypes.NameIdentifier
+    'john_doe' as name,          -- becomes ClaimTypes.Name
+    'admin' as role;             -- becomes ClaimTypes.Role

Authentication Failure

Return either:

  • No rows (empty result set), OR
  • A row with a status column set to false (boolean) or a non-200 status code (integer)
sql
sql
-- Method 1: Return no rows
+select * from users where false;
+
+-- Method 2: Return status = false (boolean)
+select false as status, null as name;
+
+-- Method 3: Return status code (integer)
+select 401 as status, 'Invalid credentials' as body;

For complete details on result set interpretation, see LOGIN - Special Columns.

Examples

Basic Challenge Command

sql
sql
-- Validation function that checks credentials in the database
+create function auth_challenge_command(
+    _user text,
+    _password text,
+    _valid boolean,
+    _realm text,
+    _path text
+)
+returns table (
+    name_identifier int,
+    name text,
+    role text
+)
+language sql
+begin atomic;
+select
+    u.id,
+    u.username,
+    u.role
+from users u
+where u.username = _user
+  and u.password_hash = crypt(_password, u.password_hash);
+end;
+
+-- Endpoint using the challenge command
+create function protected_resource(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function protected_resource(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+';

Equivalent as a SQL file endpoint (sql/protected-resource.sql):

sql
sql
/*
+HTTP GET
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+@param $1 user_claims
+*/
+select $1;

Challenge Command with Pre-Validated Password

When basic_auth includes credentials, the $3 parameter indicates if the password already matched:

sql
sql
create function auth_with_preval(
+    _user text,
+    _password text,
+    _valid boolean,     -- true if password matched annotation credentials
+    _realm text,
+    _path text
+)
+returns table (
+    name_identifier int,
+    name text,
+    password text,
+    valid boolean,
+    realm text,
+    path text
+)
+language sql
+begin atomic;
+select 1, _user, _password, _valid, _realm, _path;
+end;
+
+-- Generate hash: ./npgsqlrest --hash my_password
+-- Output: Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+
+create function get_basic_auth_challenge_command_pass(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function get_basic_auth_challenge_command_pass(json) is '
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@challenge_command = select * from auth_with_preval($1, $2, $3, $4, $5)
+@user_params
+';

Test with:

bash
bash
# Generate header: ./npgsqlrest --basic_auth my_name my_password
+curl -H "Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==" \
+     http://localhost:5000/api/get-basic-auth-challenge-command-pass
+
+# Returns: {"name_identifier":"1","name":"my_name","password":"my_password","valid":"True","realm":"NpgsqlRest","path":"/api/get-basic-auth-challenge-command-pass"}

Challenge Command That Denies Access

sql
sql
create function auth_challenge_command_failed(
+    _user text,
+    _password text,
+    _valid boolean,
+    _realm text,
+    _path text
+)
+returns table (
+    status boolean,          -- First column named 'status' controls auth
+    name_identifier int,
+    name text
+)
+language sql
+begin atomic;
+select false, 1, _user;      -- status = false means denied
+end;
+
+create function denied_endpoint(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function denied_endpoint(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command_failed($1, $2, $3, $4, $5)
+@user_params
+';

This will always return 401 Unauthorized because the status column is false.

Challenge Command Without Annotation Credentials

When basic_auth is used without credentials, $3 will be null:

sql
sql
create function get_basic_auth_challenge_command(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function get_basic_auth_challenge_command(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+';

Test with:

bash
bash
# Any username/password combination will be passed to the challenge command
+curl -H "Authorization: Basic eHh4Onl5eQ==" \  # xxx:yyy
+     http://localhost:5000/api/get-basic-auth-challenge-command
+
+# Returns: {"name_identifier":"1","name":"xxx","password":"yyy","valid":null,"realm":"NpgsqlRest","path":"/api/get-basic-auth-challenge-command"}

Behavior

  • The challenge command is executed for every request to the protected endpoint.
  • If both annotation credentials and a challenge command are configured, the password is first verified against annotation credentials, and the result is passed as $3.
  • The challenge command can implement custom logic such as:
    • Database-backed user authentication
    • Rate limiting based on failed attempts
    • IP-based access control using the path parameter
    • Audit logging of authentication attempts
    • Multi-factor authentication flows
  • If the challenge command returns a row (without status = false), the column names become claim types and values become claim values for the authenticated user.
  • Claims are accessible in the endpoint function via the user_params annotation.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/basic-auth-realm.html b/annotations/basic-auth-realm.html new file mode 100644 index 000000000..3c3d872b1 --- /dev/null +++ b/annotations/basic-auth-realm.html @@ -0,0 +1,72 @@ + + + + + + BASIC_AUTH_REALM Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

BASIC_AUTH_REALM

Also known as

basic_authentication_realm, realm (with or without @ prefix)

Set the HTTP Basic Authentication realm name.

Syntax

code
@realm = <realm-name>
+@basic_auth_realm = <realm-name>
  • realm-name: The realm name displayed in the browser's authentication dialog and included in the WWW-Authenticate response header.

Default Value

If not specified, the default realm is NpgsqlRest.

Examples

Set Realm Name

sql
sql
create function protected_api()
+returns json
+language sql
+begin atomic;
+select '{"data": "secret"}'::json;
+end;
+
+comment on function protected_api() is '
+HTTP GET
+@basic_auth admin_user hashed_password_here
+@realm = MyApplication
+';

Equivalent as a SQL file endpoint (sql/protected-api.sql):

sql
sql
/*
+HTTP GET
+@basic_auth admin_user hashed_password_here
+@realm = MyApplication
+*/
+select '{"data": "secret"}'::json;

When authentication fails, the response header will be:

code
WWW-Authenticate: Basic realm="MyApplication"

Alternative Keyword

sql
sql
comment on function admin_area() is '
+HTTP GET
+@basic_auth admin hashed_password_here
+@basic_auth_realm = Admin Area
+';

With Challenge Command

sql
sql
create function secure_endpoint(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function secure_endpoint(json) is '
+@basic_auth
+@challenge_command = select * from validate_user($1, $2, $3, $4, $5)
+@realm = SecureZone
+@user_params
+';

The realm value (SecureZone) is passed as the 4th parameter ($4) to the challenge command.

Behavior

  • Sets the realm parameter in the WWW-Authenticate response header when authentication fails.
  • The realm helps browsers identify which set of credentials to use for a given protected resource.
  • Different realms can have different sets of valid credentials.
  • The realm name is displayed in browser authentication dialogs, helping users identify which credentials to enter.
  • When using challenge_command, the realm value is passed as the 4th parameter to the validation function.

Realm Resolution Order

The realm is determined in the following order:

  1. Endpoint-specific realm annotation (highest priority)
  2. Global BasicAuth.Realm configuration option
  3. Default value: NpgsqlRest

Comments

+ + + + \ No newline at end of file diff --git a/annotations/basic-auth.html b/annotations/basic-auth.html new file mode 100644 index 000000000..2c296baac --- /dev/null +++ b/annotations/basic-auth.html @@ -0,0 +1,107 @@ + + + + + + BASIC_AUTH Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

BASIC_AUTH

Also known as

basic_authentication (with or without @ prefix)

Enable HTTP Basic Authentication for the endpoint.

Syntax

code
@basic_auth
+@basic_auth <username> <password_hash>
  • username: The expected username for authentication.
  • password_hash: The hashed password generated using the NpgsqlRest CLI --hash command.

Generating Password Hashes

Use the NpgsqlRest CLI to generate password hashes:

bash
bash
# Generate a hash for a password
+./npgsqlrest --hash my_password
+
+# Output example:
+# Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t

Generating Authorization Headers

Use the NpgsqlRest CLI to generate Base64-encoded Basic Auth headers for testing:

bash
bash
# Generate Authorization header value
+./npgsqlrest --basic_auth my_name my_password
+
+# Output example:
+# Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==

Examples

Basic Auth Without Credentials (Requires Challenge Command)

When basic_auth is used without credentials, a challenge_command must be configured to validate the user:

sql
sql
create function get_basic_auth_no_creds(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+comment on function get_basic_auth_no_creds(text) is '
+@basic_auth
+@user_params
+';

Note: Without credentials and without a challenge_command, all requests will return 401 Unauthorized.

Basic Auth With Credentials

sql
sql
create function get_basic_auth_user(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+-- Generate hash: ./npgsqlrest --hash my_password
+-- Output: Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+
+comment on function get_basic_auth_user(text) is '
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@user_params
+';

Equivalent as a SQL file endpoint (sql/get-basic-auth-user.sql):

sql
sql
/*
+HTTP GET
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@user_params
+@param $1 user_name
+*/
+select $1;

Test with:

bash
bash
# Generate header: ./npgsqlrest --basic_auth my_name my_password
+curl -H "Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==" \
+     http://localhost:5000/api/get-basic-auth-user
+# Returns: my_name

Multiple Users

You can define multiple users by adding multiple basic_auth annotations:

sql
sql
create function get_basic_auth_multiple_users(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+-- Generate hashes:
+-- ./npgsqlrest --hash pass1  =>  um4K594nL6pBQx2el0lcbKKLADof1k9atRYKy+G14f6BQPtSCkwO6qz1wJ1d9Tx/
+-- ./npgsqlrest --hash pass2  =>  TIDVxenk9gSqApyI82XDuqUaigQ5OdBIecfRtq7wFWtHT3Ffx2s+noIjvFCAw90z
+
+comment on function get_basic_auth_multiple_users(text) is '
+@basic_auth user1 um4K594nL6pBQx2el0lcbKKLADof1k9atRYKy+G14f6BQPtSCkwO6qz1wJ1d9Tx/
+@basic_auth user2 TIDVxenk9gSqApyI82XDuqUaigQ5OdBIecfRtq7wFWtHT3Ffx2s+noIjvFCAw90z
+@user_params
+';

Test with:

bash
bash
# ./npgsqlrest --basic_auth user1 pass1
+curl -H "Authorization: Basic dXNlcjE6cGFzczE=" \
+     http://localhost:5000/api/get-basic-auth-multiple-users
+# Returns: user1
+
+# ./npgsqlrest --basic_auth user2 pass2
+curl -H "Authorization: Basic dXNlcjI6cGFzczI=" \
+     http://localhost:5000/api/get-basic-auth-multiple-users
+# Returns: user2

Behavior

  • Requires HTTP Basic Authentication header in the format Authorization: Basic <base64(username:password)>.
  • Returns 401 Unauthorized with WWW-Authenticate: Basic realm="..." header if:
    • No Authorization header is provided.
    • The header is malformed or cannot be decoded.
    • Username or password is missing.
    • Username is not found in configured users.
    • Password verification fails.
  • When credentials are specified in the annotation, passwords are verified using the configured password hasher.
  • When no credentials are specified, a challenge_command must be configured to handle authentication.
  • The authenticated username is available via the user_params annotation as the name claim.

SSL Requirements

Basic Authentication transmits credentials encoded (not encrypted). The behavior when SSL is disabled is controlled by the SslRequirement configuration:

  • Required: Rejects all non-SSL requests with 401 Unauthorized.
  • Warning: Allows requests but logs a warning.
  • Ignore: Allows requests with only a debug-level log.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/body-parameter-name.html b/annotations/body-parameter-name.html new file mode 100644 index 000000000..31c72d3e7 --- /dev/null +++ b/annotations/body-parameter-name.html @@ -0,0 +1,63 @@ + + + + + + BODY_PARAMETER_NAME Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

BODY_PARAMETER_NAME

Also known as

body_param_name (with or without @ prefix)

Specify which parameter receives the raw request body.

Syntax

code
@body_parameter_name <param-name>

Examples

Custom Body Parameter

sql
sql
create function process_payload(_metadata json, _payload text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_payload(json, text) is
+'HTTP POST
+@body_parameter_name _payload';

Equivalent as a SQL file endpoint (sql/process-payload.sql):

sql
sql
/*
+HTTP POST
+@body_parameter_name payload
+@param $1 metadata json
+@param $2 payload text
+*/
+select process_payload($1, $2);

JSON Body Parameter

sql
sql
create function handle_webhook(_body json)
+returns void
+language sql
+begin atomic;
+...;
+end;
+
+comment on function handle_webhook(json) is
+'HTTP POST
+@body_parameter_name _body';

Behavior

  • Directs the raw request body to the specified parameter
  • Useful when you need access to the complete body content
  • Parameter type should match expected content (text, json, bytea)

Matching Rules

The parameter name is matched case-insensitively and accepts any of the parameter's names:

  • the converted (API) name — e.g. responseBody (camelCase),
  • the actual SQL name — e.g. _response_body,
  • for a field expanded out of an HTTP Custom Type composite parameter, the expanded signature name (_response_body) and the base composite name (_response, shared by all expanded fields — resolves to the first one).

New in 3.18.2

Before 3.18.2 the value was force-lowercased and compared case-sensitively, so the camelCase converted name never matched, and the expanded signature name of an HTTP Custom Type field matched nothing at all. From 3.18.2 the same matching rule is applied consistently by request handling and every code generator (TypeScript client, HTTP file, OpenAPI), so they no longer disagree about which parameter carries the body.

Redirecting an HTTP Custom Type field into a proxy body

A common use is forwarding a large field — such as an HTTP Custom Type's responseBody — into a @proxy upstream request body instead of the query string (where an oversized value would be rejected, see MaxForwardedQueryParamLength). Target the field by its converted name (responseBody), its expanded signature name (_response_body), or the composite base (_response), and use a body-carrying method:

sql
sql
comment on function scrape_and_forward(...) is 'HTTP POST
+@proxy https://upstream.example.com/ingest
+@body_parameter_name responseBody';

The remaining small fields still travel on the proxy query string.

  • REQUEST_PARAM_TYPE - Control parameter source
  • HTTP_TYPE - HTTP Custom Type whose expanded fields can be targeted as the body
  • PROXY - Forward the body field into an upstream request body

Comments

+ + + + \ No newline at end of file diff --git a/annotations/buffer-rows.html b/annotations/buffer-rows.html new file mode 100644 index 000000000..1fcf79a21 --- /dev/null +++ b/annotations/buffer-rows.html @@ -0,0 +1,47 @@ + + + + + + BUFFER_ROWS Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

BUFFER_ROWS

Also known as

buffer (with or without @ prefix)

Set the number of rows to buffer in the string builder before sending the response.

Syntax

code
@buffer_rows <count>
+@buffer <count>

Or using custom parameter syntax:

code
@buffer_rows = <count>
+@buffer = <count>

Default Value

The default value is 25 rows.

Special Values

ValueBehavior
0Disable buffering - write response for each row
1Buffer the entire array (all rows)
25Default - buffer 25 rows before writing
> 1Buffer specified number of rows before writing

Examples

Disable Buffering

Write each row immediately to the response stream:

sql
sql
comment on function stream_live_data() is
+'HTTP GET
+@buffer_rows 0';

Buffer Entire Response

Wait for all rows before sending response:

sql
sql
comment on function get_small_dataset() is
+'HTTP GET
+@buffer 1';

Large Buffer for Throughput

sql
sql
comment on function export_all_data() is
+'HTTP GET
+@buffer_rows 5000';

Small Buffer for Memory Efficiency

sql
sql
comment on function stream_data() is
+'HTTP GET
+@buffer 100';

Behavior

  • Controls how many rows are buffered in the string builder before writing to the response stream.
  • Applies to rows in JSON object arrays when returning records from the database.
  • Buffering is more efficient than writing to the response stream for each row.
  • Disabling buffering (0) can have a slight negative impact on performance.
  • Higher values can have a negative impact on memory usage, especially with large datasets.

Performance Considerations

  • Low values (0-10): Lower memory usage, more response stream writes, slight performance overhead.
  • Default (25): Balanced trade-off between memory and performance.
  • High values (1000+): Better throughput, higher memory usage per request.
  • Value of 1: Entire result buffered before sending - best for small datasets where you want atomic responses.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/cache-expires-in.html b/annotations/cache-expires-in.html new file mode 100644 index 000000000..ec3c1409d --- /dev/null +++ b/annotations/cache-expires-in.html @@ -0,0 +1,49 @@ + + + + + + CACHE_EXPIRES_IN Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

CACHE_EXPIRES_IN

Also known as

cache_expires (with or without @ prefix)

Set cache expiration time for cached endpoints.

Syntax

code
@cache_expires_in <interval>

Uses interval format. Common examples:

FormatMeaning
10s10 seconds
5m5 minutes
1h1 hour
1d1 day
1w1 week

Examples

Short Cache (10 seconds)

sql
sql
comment on function get_live_data() is
+'HTTP GET
+@cached
+@cache_expires_in 10s';

Medium Cache (5 minutes)

sql
sql
comment on function get_dashboard_stats() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';

Long Cache (1 hour)

sql
sql
comment on function get_static_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1h';

Daily Cache

sql
sql
comment on function get_daily_report() is
+'HTTP GET
+@cached
+@cache_expires_in 1d';

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/cache-profile.html b/annotations/cache-profile.html new file mode 100644 index 000000000..32027e327 --- /dev/null +++ b/annotations/cache-profile.html @@ -0,0 +1,83 @@ + + + + + + CACHE_PROFILE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

CACHE_PROFILE

Select a named cache profile for an endpoint.

A cache profile bundles together a cache backend (Memory / Redis / Hybrid), a default expiration, the cache-key parameter list, and per-parameter conditional rules ("when X is null, bypass cache" or "when status='draft', cache 30 seconds"). Profiles are defined once in Cache Options configuration and selected per endpoint via this annotation.

@cache_profile implies caching — you don't also need @cached. Both @cached and @cache_expires annotations remain valid; when present they override the profile's defaults.

Syntax

code
@cache_profile <name>

The annotation accepts exactly one profile name. The name must match a profile defined in CacheOptions.Profiles and registered with "Enabled": true. Unknown names cause startup to fail with a single error listing every unresolved name and the offending endpoints.

Examples

Basic usage

sql
sql
create function get_dashboard()
+returns json
+language sql
+begin atomic;
+select dashboard_data();
+end;
+
+comment on function get_dashboard() is
+'HTTP GET
+@cache_profile fast_memory';

Equivalent as a SQL file endpoint (sql/get-dashboard.sql):

sql
sql
-- HTTP GET
+-- @cache_profile fast_memory
+select dashboard_data();

The fast_memory profile (defined in CacheOptions.Profiles) supplies the backend, expiration, and any conditional rules.

Combined with cached / cache_expires

sql
sql
comment on function get_user_report(user_id int, year int) is
+'HTTP GET
+@cache_profile shared_redis
+@cached user_id, year
+@cache_expires 1 hour';

The annotations override the profile's defaults:

  • @cached user_id, year → cache key uses these params (overrides profile's Parameters list).
  • @cache_expires 1 hour → entry TTL is 1 hour (overrides profile's Expiration).

The profile still supplies the cache backend (Redis in this case) and any When rules.

Multi-tenant search_path pattern

A profile that bypasses the cache when no end date is supplied — the request asks for "until now" data, which changes constantly:

jsonc
jsonc
// in appsettings.json
+"CacheOptions": {
+  "Enabled": true,
+  "Profiles": {
+    "timeseries": {
+      "Enabled": true,
+      "Type": "Memory",
+      "Expiration": "1 hour",
+      "Parameters": ["from", "to"],
+      "When": [
+        { "Parameter": "to", "Value": null, "Then": "5 minutes" }
+      ]
+    }
+  }
+}
sql
sql
comment on function compute_timeseries(from text, to text default null) is
+'HTTP GET
+@cache_profile timeseries';

Behavior per request:

  • Both from and to present → 1-hour cache (historical query, safe to cache long).
  • to is null → 5-minute cache (open-ended query; data may update at the matching cadence).

Tiered TTL by user role

jsonc
jsonc
"CacheOptions": {
+  "Profiles": {
+    "tier_aware": {
+      "Enabled": true,
+      "Type": "Hybrid",
+      "Parameters": ["tier"],
+      "When": [
+        { "Parameter": "tier", "Value": "free",  "Then": "5 minutes" },
+        { "Parameter": "tier", "Value": "pro",   "Then": "1 hour" },
+        { "Parameter": "tier", "Value": "admin", "Then": "skip" }
+      ]
+    }
+  }
+}
sql
sql
comment on function get_account_data(tier text) is
+'HTTP GET
+@cache_profile tier_aware';
  • Free tier → cached 5 minutes.
  • Pro tier → cached 1 hour.
  • Admin tier → never cached (always fresh).

Behavior

  • @cache_profile implies @cached — explicit @cached is unnecessary.
  • The profile's Cache (backend instance) is used instead of the root DefaultRoutineCache.
  • The profile's Expiration is used unless overridden by @cache_expires.
  • The profile's Parameters list is used as the default cache-key set unless overridden by @cached <list>.
  • The profile's When rules are evaluated at request time; first match wins. Rules can "skip" (bypass cache) or override TTL with a PostgreSQL interval.
  • Cache entries written under a profile are prefixed with the profile name, so two profiles sharing the same backend (e.g., two Memory profiles) cannot collide.
  • The cache invalidation endpoint (when InvalidateCacheSuffix is configured) routes through the same profile backend.

Validation

Misconfiguration is caught at startup:

ProblemResult
Unknown profile name referenced by @cache_profileStartup fails with single error listing every unresolved name + offending endpoints
Profile registered but no endpoint references itInformation-level log warning
When rule references a parameter that's not in the cache-key listRule dropped at startup with Warning (other rules still apply)
Multiple @cache_profile arguments (e.g. @cache_profile a b)Annotation ignored with Warning; one name only

See Also

  • Cache Options — top-level cache backend and profile configuration

Comments

+ + + + \ No newline at end of file diff --git a/annotations/cached.html b/annotations/cached.html new file mode 100644 index 000000000..9f55ff997 --- /dev/null +++ b/annotations/cached.html @@ -0,0 +1,91 @@ + + + + + + CACHED Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

CACHED

Enable server-side response caching for routine results.

Syntax

code
@cached
+@cached <param1>, <param2>, <param3>, ...

Space-separated lists are also valid: @cached _year _department

Parameters specified become part of the cache key.

Examples

Simple Caching

sql
sql
create function get_app_settings()
+returns json
+language sql
+begin atomic;
+select settings from app_config where id = 1;
+end;
+
+comment on function get_app_settings() is
+'HTTP GET
+@cached';

Equivalent as a SQL file endpoint (sql/get-app-settings.sql):

sql
sql
-- HTTP GET
+-- @cached
+select settings from app_config where id = 1;

Cache Key by Parameter

sql
sql
create function get_user_profile(_user_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where id = _user_id;
+end;
+
+comment on function get_user_profile(int) is
+'HTTP GET
+@cached _user_id';

Different _user_id values create separate cache entries.

Multiple Cache Key Parameters

sql
sql
create function get_report(_year int, _department text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_report(int, text) is
+'HTTP GET
+@cached _year, _department';

With Cache Expiration

Cache expiration uses interval format:

sql
sql
comment on function get_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1h';

Caching Set-Returning Functions

Caching works for set-returning functions and record types. When a cached function returns multiple rows, the entire result set is cached:

sql
sql
create function get_all_users()
+returns table(id int, name text)
+language sql
+begin atomic;
+select id, name from users;
+end;
+
+comment on function get_all_users() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';

Use MaxCacheableRows in Cache Options to limit the maximum number of rows that can be cached. Result sets exceeding this limit are returned but not cached.

Behavior

  • Caches the response for subsequent identical requests
  • Works with scalar results, set-returning functions, and record types
  • Cache key is based on specified parameters
  • Use with cache_expires_in to set expiration time

Cache Configuration

The cached annotation requires cache to be enabled in Cache Options configuration.

Two cache types are available:

TypeDescriptionUse Case
MemoryIn-memory cache on the application serverSingle instance deployments, development
RedisDistributed cache using RedisMulti-instance deployments, production

Example configuration:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Memory"
+  }
+}

For Redis:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Redis",
+    "RedisConfiguration": "localhost:6379"
+  }
+}

See Cache Options for complete configuration reference.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/column-names.html b/annotations/column-names.html new file mode 100644 index 000000000..c6a289b21 --- /dev/null +++ b/annotations/column-names.html @@ -0,0 +1,65 @@ + + + + + + COLUMN_NAMES Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

COLUMN_NAMES

Also known as

columns, names (with or without @ prefix)

Include column names as the first row in raw output mode.

Syntax

code
@columns

Examples

CSV with Headers

sql
sql
create function export_users()
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_users() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \n
+@columns
+Content-Type: text/csv';

Equivalent as a SQL file endpoint (sql/export-users.sql):

sql
sql
/*
+HTTP GET
+@raw
+@separator ,
+@new_line \n
+@columns
+Content-Type: text/csv
+*/
+select id, name, email from users;

Response:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

TSV with Headers

sql
sql
comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \t
+@new_line \n
+@column_names';

Comments

+ + + + \ No newline at end of file diff --git a/annotations/command-timeout.html b/annotations/command-timeout.html new file mode 100644 index 000000000..964feeeb8 --- /dev/null +++ b/annotations/command-timeout.html @@ -0,0 +1,74 @@ + + + + + + COMMAND_TIMEOUT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

COMMAND_TIMEOUT

Also known as

timeout (with or without @ prefix)

Set the query execution timeout for the endpoint.

Syntax

code
@timeout <interval>
+@command_timeout <interval>

Or using custom parameter syntax:

code
@timeout = <interval>
+@command_timeout = <interval>

The value uses the interval format. Common examples:

UnitExamples
Microseconds1000us, 1000usec, 1000microseconds
Milliseconds500ms, 500msec, 500milliseconds
Seconds30, 30s, 30sec, 30seconds
Minutes5m, 5min, 5minutes
Hours1h, 1hour, 1hours
Days1d, 1day, 1days
Weeks1w, 1week, 1weeks

Single Token Requirement

The @timeout annotation reads only the first token after the keyword. Use formats without spaces to avoid parsing issues. Numbers without a unit default to seconds.

Default Value

The default timeout is configured via NpgsqlRest.CommandTimeout in configuration. If not set, defaults to 30 seconds.

Examples

Short Timeout

sql
sql
create function quick_lookup(_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(t) from table t where id = _id;
+end;
+
+comment on function quick_lookup(int) is
+'HTTP GET
+@timeout 5s';

Equivalent as a SQL file endpoint (sql/quick-lookup.sql):

sql
sql
/*
+HTTP GET
+@timeout 5s
+@param $1 id
+*/
+select row_to_json(t) from items t where t.id = $1;

Long Running Query

sql
sql
create function generate_report(_year int)
+returns json
+language sql
+begin atomic;
+...complex aggregation...;
+end;
+
+comment on function generate_report(int) is
+'HTTP GET
+@timeout 2min
+@authorize';

Using Seconds Format

sql
sql
comment on function slow_process() is
+'HTTP POST
+@command_timeout 90s';

Behavior

  • Overrides the global NpgsqlRest.CommandTimeout configuration for this endpoint.
  • Query is cancelled if it exceeds the timeout.
  • On timeout, returns the response configured in ErrorHandlingOptions.TimeoutErrorMapping.

Timeout Response

When a command times out, the response is determined by the TimeoutErrorMapping configuration:

json
json
{
+  "ErrorHandlingOptions": {
+    "TimeoutErrorMapping": {
+      "StatusCode": 504,
+      "Title": "Command execution timed out",
+      "Details": null,
+      "Type": null
+    }
+  }
+}

Default timeout response: HTTP 504 Gateway Timeout

See Error Handling for customizing timeout responses.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/connection.html b/annotations/connection.html new file mode 100644 index 000000000..6ddf3c815 --- /dev/null +++ b/annotations/connection.html @@ -0,0 +1,44 @@ + + + + + + CONNECTION Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

CONNECTION

Also known as

connection_name (with or without @ prefix)

Specify a named database connection for the endpoint.

Syntax

code
@connection <connection-name>
+@connection_name <connection-name>

Examples

Use Named Connection

sql
sql
comment on function get_analytics() is
+'HTTP GET
+@connection analytics_db';

Reporting Database

sql
sql
comment on function generate_report() is
+'HTTP GET
+@connection_name reporting';

Read Replica

sql
sql
comment on function read_heavy_query() is
+'HTTP GET
+@connection read_replica';

Behavior

  • References a connection string defined in ConnectionStrings configuration
  • Allows different endpoints to use different databases
  • Requires UseMultipleConnections: true in NpgsqlRest options
  • See Connection Settings configuration

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/custom-parameters.html b/annotations/custom-parameters.html new file mode 100644 index 000000000..3d2c73dbc --- /dev/null +++ b/annotations/custom-parameters.html @@ -0,0 +1,56 @@ + + + + + + Custom Parameters Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Custom Parameters

Set custom key-value configuration for the endpoint.

Syntax

code
@<key> = <value>

The @ prefix is optional - both @key = value and key = value work identically. Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

Dynamic Parameter Values

Some parameters support dynamic values using the {param_name} format, where param_name references a function parameter. The value is resolved at runtime from the actual parameter value passed to the endpoint. The matching and substitution rules are shared across annotations — see Parameter Value Substitution.

Example

sql
sql
create function upload_file(_path text, _file text)
+returns void
+language sql
+begin atomic;
+  -- function body
+end;
+
+comment on function upload_file(text, text) is '
+@upload for file_system
+@file_system_path = {_path}
+@file_system_file = {_file}
+';

Equivalent as a SQL file endpoint (sql/upload-file.sql):

sql
sql
/*
+HTTP POST
+@upload for file_system
+@file_system_path = {path}
+@file_system_file = {file}
+@param $1 path
+@param $2 file
+*/
+select;

When called with {"_path": "/uploads/images", "_file": "photo.jpg"}, the file will be saved to /uploads/images/photo.jpg.

Built-in Parameters

Many annotations support the @key = value syntax. The following sections link to where each parameter group is documented.

General

These parameters are predefined annotations that also support the key = value syntax:

Upload

Upload handlers accept custom parameters to control file processing behavior per-endpoint:

Table Format

Per-endpoint control of HTML table and Excel spreadsheet rendering:

Server-Sent Events

SSE annotations that also support the key = value syntax:

TypeScript Client

Per-endpoint control of generated TypeScript client code:

Comments

+ + + + \ No newline at end of file diff --git a/annotations/define-param.html b/annotations/define-param.html new file mode 100644 index 000000000..c0d88a33d --- /dev/null +++ b/annotations/define-param.html @@ -0,0 +1,52 @@ + + + + + + DEFINE_PARAM Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

DEFINE_PARAM

Also known as

define_param (with or without @ prefix)

Define HTTP parameters that are not bound to the PostgreSQL command. These virtual parameters exist in the HTTP request (query string or JSON body) but do not correspond to any $N positional parameter in the SQL query.

This is useful for SQL file endpoints where you need HTTP parameters for:

  • Custom parameter placeholders — parameters that feed into annotation placeholders like {format} without being part of the SQL
  • Claim mapping — auto-filling parameters from authenticated user claims without referencing them in the query
  • HTTP request matching — parameters that affect endpoint behavior without participating in the database query

Syntax

code
@define_param name
+@define_param name type
  • name — the HTTP parameter name
  • type — optional PostgreSQL type (default: text)

Custom Parameter Placeholders

Pass HTTP parameters that control endpoint behavior without referencing them in SQL:

sql
sql
-- sql/users_report.sql
+-- @define_param format text
+-- @table_format = {format}
+-- @param $1 department_id
+select id, name, email from users where department_id = $1;

GET /api/users-report?department_id=5&format=html_table

The format parameter feeds into the @table_format annotation via the {format} placeholder, selecting the output format (JSON, HTML table, Excel, etc.) without being part of the SQL query. Without @define_param, there would be no format parameter in the endpoint — the {format} placeholder would have nothing to resolve.

Claim Mapping

Auto-fill a parameter from the authenticated user's claims without including it in the SQL query:

sql
sql
-- sql/user_dashboard.sql
+-- @authorize
+-- @user_parameters
+-- @define_param _user_id
+select * from user_dashboard_data;

Here _user_id is created as a virtual parameter that maps to the name_identifier claim (via standard User Parameters claim mapping). The authenticated user's ID is injected automatically — but unlike @param, this parameter doesn't correspond to any $N in the SQL. The query itself doesn't filter by user — the virtual parameter exists solely for the claim mapping mechanism.

This is different from using @param with @user_parameters:

sql
sql
-- This uses @param — $1 IS in the SQL query
+-- @authorize
+-- @user_parameters
+-- @param $1 _user_id
+select * from orders where user_id = $1;

Use @define_param when the parameter shouldn't appear in the SQL at all. Use @param when you need the value both as a claim-mapped parameter and as a query parameter.

Default Type

If no type is specified, the parameter defaults to text:

sql
sql
-- These are equivalent:
+-- @define_param _user_id
+-- @define_param _user_id text

Specify a type when needed:

sql
sql
-- @define_param _user_id integer

Comments

+ + + + \ No newline at end of file diff --git a/annotations/disabled.html b/annotations/disabled.html new file mode 100644 index 000000000..e8599a641 --- /dev/null +++ b/annotations/disabled.html @@ -0,0 +1,42 @@ + + + + + + DISABLED Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

DISABLED

Hide a routine from being exposed as an HTTP endpoint without dropping or modifying it.

Keywords

@disabled, disabled

Syntax

code
@disabled

The endpoint will not be created. The function or procedure remains in the database, callable directly via SQL — only the HTTP exposure is suppressed.

Example

sql
sql
comment on function deprecated_func() is '
+HTTP
+@disabled';

deprecated_func is not registered as an HTTP endpoint at startup. Useful for:

  • Temporarily hiding an endpoint without removing the function
  • Keeping a routine that's called internally from other functions but should not be reachable over HTTP
  • Disabling old endpoints during a deprecation cycle while leaving the function around for rollback

Tag-conditional form

code
@disabled <tag1>, <tag2>, ...

Disables the endpoint only when the routine matches at least one of the listed tags. The available auto-tags assigned by RoutineSource are:

TagMatches
functionPostgreSQL functions
procedurePostgreSQL procedures
volatileFunctions declared VOLATILE (the default)
stableFunctions declared STABLE
immutableFunctions declared IMMUTABLE
otherProcedures (volatility doesn't apply)
sql
sql
-- Disable only if the function is volatile (e.g., to enforce read-only API surface)
+comment on function get_data() is '
+HTTP GET
+@disabled volatile';

Custom tags are not supported — only the auto-tags above are available. SQL file endpoints have no auto-tags.

Most projects don't need the tag form

The unconditional @disabled is the form you'll reach for in practice. The tag form is a leftover from earlier versions where the CRUD source assigned per-operation tags (select, insert, etc.).

  • ENABLED — re-enable inside a tag-scoped block
  • TAGS — apply annotations conditionally by routine tag
  • INTERNAL — alternative for marking a routine as internal-only

Comments

+ + + + \ No newline at end of file diff --git a/annotations/enabled.html b/annotations/enabled.html new file mode 100644 index 000000000..2ad236988 --- /dev/null +++ b/annotations/enabled.html @@ -0,0 +1,42 @@ + + + + + + ENABLED Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

ENABLED

Re-enable an endpoint that an earlier @disabled would otherwise hide.

Rarely needed

Endpoints are enabled by default. You only need @enabled to undo a @disabled on a tag-conditional basis. If you've never reached for @disabled, you don't need @enabled either.

Keywords

@enabled, enabled

Syntax

code
@enabled
+@enabled <tag1>, <tag2>, ...
  • Without tags: enables the endpoint unconditionally.
  • With tags: enables only when the routine matches at least one of the listed tags.

The available auto-tags assigned by RoutineSource are function, procedure, volatile, stable, immutable, other. SQL file endpoints have no auto-tags.

Example: disable-by-default, enable for immutable only

sql
sql
comment on function calculate_total(_items json) is '
+HTTP GET
+@disabled
+@enabled immutable
+@cached';

The endpoint is disabled by default, but re-enabled when the function is declared IMMUTABLE. If you later mark the function STABLE or VOLATILE, the endpoint disappears without further changes.

  • DISABLED — hide an endpoint
  • TAGS — apply annotations conditionally by routine tag

Comments

+ + + + \ No newline at end of file diff --git a/annotations/encrypt-decrypt.html b/annotations/encrypt-decrypt.html new file mode 100644 index 000000000..cc10288e6 --- /dev/null +++ b/annotations/encrypt-decrypt.html @@ -0,0 +1,82 @@ + + + + + + ENCRYPT / DECRYPT Annotations | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

ENCRYPT / DECRYPT

Also known as

encrypt: encrypted, protect, protecteddecrypt: decrypted, unprotect, unprotected

Transparent application-level column encryption using ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client. The database stores ciphertext; the API consumer sees plaintext. No pgcrypto or client-side encryption required.

Prerequisite: The DataProtection section must be enabled in appsettings.json (it is by default). See Data Protection Configuration.

Encrypt Parameters

Syntax

code
encrypt [parameter_name, ...]

Mark specific parameters to encrypt before they are sent to PostgreSQL:

sql
sql
create function store_patient_ssn(_patient_id int, _ssn text)
+returns void
+language plpgsql as $$
+begin
+    insert into patients (id, ssn) values (_patient_id, _ssn)
+    on conflict (id) do update set ssn = excluded.ssn;
+end;
+$$;
+comment on function store_patient_ssn(int, text) is '
+HTTP POST
+encrypt _ssn
+';

Equivalent as a SQL file endpoint (sql/store-patient-ssn.sql):

sql
sql
/*
+HTTP POST
+@encrypt ssn
+@param $1 patient_id
+@param $2 ssn
+*/
+insert into patients (id, ssn) values ($1, $2)
+on conflict (id) do update set ssn = excluded.ssn;

The client calls POST /api/store-patient-ssn/ with {"patientId": 1, "ssn": "123-45-6789"}. The server encrypts _ssn using Data Protection before executing the SQL — the database stores ciphertext like CfDJ8N..., never the plaintext SSN.

Use encrypt without arguments to encrypt all text parameters:

sql
sql
comment on function store_all_secrets(text, text) is '
+HTTP POST
+encrypt
+';

Decrypt Result Columns

Syntax

code
decrypt [column_name, ...]

Mark specific result columns to decrypt before returning to the client:

sql
sql
create function get_patient(_patient_id int)
+returns table(id int, ssn text, name text)
+language plpgsql as $$
+begin
+    return query select p.id, p.ssn, p.name from patients p where p.id = _patient_id;
+end;
+$$;
+comment on function get_patient(int) is '
+decrypt ssn
+';

The client calls GET /api/get-patient/?patientId=1. The ssn column is decrypted from ciphertext back to "123-45-6789" before being included in the JSON response. The id and name columns are returned as-is.

Use decrypt without arguments to decrypt all result columns:

sql
sql
comment on function get_all_secrets(text) is '
+decrypt
+';

Decrypt also works on scalar (single-value) return types:

sql
sql
create function get_secret(_id int) returns text ...
+comment on function get_secret(int) is 'decrypt';

Full Roundtrip Example

sql
sql
-- Store with encryption
+create function store_secret(_key text, _value text) returns void ...
+comment on function store_secret(text, text) is '
+HTTP POST
+encrypt _value
+';
+
+-- Retrieve with decryption
+create function get_secret(_key text) returns table(key text, value text) ...
+comment on function get_secret(text) is '
+decrypt value
+';
code
POST /api/store-secret/  {"key": "api-key", "value": "sk-abc123"}
+GET  /api/get-secret/?key=api-key  →  {"key": "api-key", "value": "sk-abc123"}

The value is stored encrypted in PostgreSQL and decrypted transparently on read.

Behavior

  • NULL values: NULL parameters are not encrypted (passed as DBNull). NULL columns are not decrypted (returned as JSON null).
  • Non-text types: Only string parameter values are encrypted. Integer, boolean, and other types are unaffected even when encrypt is used without arguments.
  • Decryption failures: If a column value cannot be decrypted (e.g., it was not encrypted, or keys were rotated/lost), the raw value is returned as-is — no error is thrown.
  • Key rotation: ASP.NET Data Protection maintains a key ring. Old keys still decrypt old ciphertext. Keys rotate based on DefaultKeyLifetimeDays (default: 90 days).
  • Encrypted columns are opaque to PostgreSQL: The database cannot filter, join, sort, or index on encrypted values. Use encryption only for columns that are written and read back, never queried by content.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/error-code-policy.html b/annotations/error-code-policy.html new file mode 100644 index 000000000..4f09736dc --- /dev/null +++ b/annotations/error-code-policy.html @@ -0,0 +1,42 @@ + + + + + + ERROR_CODE_POLICY Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

ERROR_CODE_POLICY

Also known as

error_code_policy, error_code (with or without @ prefix)

Associate an error handling policy with the endpoint.

Syntax

code
@error_code_policy <policy-name>
+@error_code <policy-name>

Examples

Named Policy

sql
sql
comment on function risky_operation() is
+'HTTP POST
+@error_code_policy strict_errors';

Short Form

sql
sql
comment on function api_endpoint() is
+'HTTP GET
+@error_code default_policy';

Behavior

  • References an error policy defined in the ErrorCodePolicies configuration
  • Controls how PostgreSQL errors are mapped to HTTP status codes
  • Defines error response format

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/http-type.html b/annotations/http-type.html new file mode 100644 index 000000000..055a70018 --- /dev/null +++ b/annotations/http-type.html @@ -0,0 +1,218 @@ + + + + + + HTTP Types Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

HTTP CUSTOM TYPES

Define HTTP request on a composite type to enable PostgreSQL functions to make HTTP requests to external APIs.

Overview

HTTP Types are PostgreSQL composite types with a special comment that defines an HTTP request. When a function uses an HTTP Type as a parameter, NpgsqlRest automatically makes the HTTP request and populates the type fields with the response before executing the function.

Syntax

The HTTP definition is added as a comment on the composite type:

code
[@timeout directive]
+[@retry_delay directive]
+[@cache directive]
+METHOD URL [HTTP/version]
+Header-Name: Header-Value
+...
+[@timeout directive]
+[@retry_delay directive]
+[@cache directive]
+
+[request body]

Directives (@timeout, @retry_delay, @cache) may appear either before the request line or after the headers — both placements are equivalent.

Supported Methods

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE

Examples

Basic GET Request

sql
sql
-- Create response type
+create type simple_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+-- Define HTTP request
+comment on type simple_api is 'GET https://api.example.com/data';
+
+-- Use in function
+create function fetch_data(_response simple_api)
+returns text
+language sql
+begin atomic;
+select (_response).body;
+end;

Equivalent as a SQL file endpoint (sql/fetch-data.sql):

The HTTP Type itself must be defined in DDL (it's a composite type), but the consuming endpoint can be a SQL file. Assuming simple_api is already defined as above:

sql
sql
/*
+HTTP GET
+@param $1 response simple_api
+*/
+select ($1::simple_api).body;

GET with Headers and Placeholders

sql
sql
create type weather_api as (
+    body text,
+    status_code int,
+    headers json,
+    content_type text,
+    success boolean,
+    error_message text
+);
+
+comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
+Authorization: Bearer {_api_key}
+Accept: application/json
+@timeout 30s';
+
+create function get_weather(
+    _city text,
+    _api_key text,
+    _req weather_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    if (_req).success then
+        return (_req).body::json;
+    else
+        return json_build_object('error', (_req).error_message);
+    end if;
+end;
+$$;

POST with Request Body

sql
sql
create type create_user_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type create_user_api is 'POST https://api.example.com/users
+Content-Type: application/json
+Authorization: Bearer {_token}
+@timeout 10s
+
+{"name": "{_name}", "email": "{_email}"}';
+
+create function create_user(
+    _name text,
+    _email text,
+    _token text,
+    _response create_user_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    if (_response).success then
+        return (_response).body::json;
+    else
+        raise exception 'Failed to create user: %', (_response).error_message;
+    end if;
+end;
+$$;

Multiple API Calls

A function can have multiple HTTP Type parameters for chained API calls:

sql
sql
create type auth_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+create type data_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type auth_api is 'POST https://auth.example.com/token
+Content-Type: application/x-www-form-urlencoded
+
+client_id={_client_id}&client_secret={_client_secret}';
+
+comment on type data_api is 'GET https://api.example.com/data
+Authorization: Bearer {_token}';
+
+create function fetch_with_auth(
+    _client_id text,
+    _client_secret text,
+    _auth auth_api,
+    _token text,
+    _data data_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    -- Note: _token would need to be extracted from _auth.body in practice
+    if not (_auth).success then
+        return json_build_object('error', 'Authentication failed');
+    end if;
+
+    if (_data).success then
+        return (_data).body::json;
+    else
+        return json_build_object('error', (_data).error_message);
+    end if;
+end;
+$$;

Response Fields

The composite type fields are populated based on their names:

Field NameTypeDescription
bodytextResponse body content
status_codeint or textHTTP status code (e.g., 200, 404)
headersjsonResponse headers as JSON object
content_typetextContent-Type header value
successbooleanTrue for 2xx status codes
error_messagetextError message if request failed

Field names are configurable via HTTP Client Options.

Timeout Directives

Timeout uses interval format:

FormatExample
Seconds (integer)@timeout 30
Seconds with suffix@timeout 30s
TimeSpan format@timeout 00:00:30
Minutes@timeout 2min
Without @ prefixtimeout 30s

Timeout can appear before the request line or after headers:

sql
sql
-- Before request line
+comment on type api_type is 'timeout 30
+GET https://api.example.com/data';
+
+-- After headers
+comment on type api_type is 'GET https://api.example.com/data
+Authorization: Bearer {_token}
+@timeout 30s';

Placeholder Substitution

Placeholders in the format {name} are replaced via the shared Parameter Value Substitution mechanism (case-insensitive name matching, NULL → empty, unknown → left literal). For HTTP types the type's own field names are also valid placeholders. A {name} can be supplied by any of three sources:

  • a request/function parameter (shown below);
  • an allowlisted environment variable — ideal for a static API key, without routing it through a parameter (e.g. Authorization: Bearer {WEATHER_API_KEY});
  • a resolved parameter expression — a value computed server-side from SQL (e.g. a token read from a table), never supplied by the client.
sql
sql
comment on type api_type is 'GET https://api.example.com/users/{_user_id}/posts?limit={_limit}
+Authorization: Bearer {_token}';
+
+create function get_user_posts(
+    _user_id int,       -- Substitutes {_user_id}
+    _limit int,         -- Substitutes {_limit}
+    _token text,        -- Substitutes {_token}
+    _response api_type  -- Receives HTTP response
+)
+...

Placeholders work in:

  • URL path and query string
  • Header values
  • Request body

Retry Logic

The @retry_delay directive adds automatic retries with configurable delays for transient failures:

sql
sql
-- Retry on any failure:
+comment on type my_api_type is '@retry_delay 1s, 2s, 5s
+GET https://api.example.com/data';
+
+-- Retry only on specific HTTP status codes:
+comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
+GET https://api.example.com/data';
+
+-- Combined with timeout:
+comment on type my_api_type is '@timeout 10s
+@retry_delay 1s, 2s, 5s on 429, 503
+GET https://api.example.com/data';

The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1s, 2s, and 5s delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

  • Without on filter: Retries on any non-success HTTP response, timeout, or network error.
  • With on filter: Retries only when the status code matches a listed code. Timeouts and network errors always trigger retry.
  • Retry exhaustion: If all retries fail, the last error is passed to the function.

Response Caching

The @cache directive caches the outbound HTTP response and reuses it for matching requests within a time window, instead of calling the upstream on every request:

sql
sql
comment on type books_api is '@cache 5m
+GET https://books.toscrape.com/';

A cached type fires one outbound call for a given request shape; subsequent matching requests are served from an in-memory cache until the TTL elapses. For a type with no per-request placeholders (a constant URL, headers, and body), that means a single shared upstream call per TTL window across the whole application — rather than one call per inbound request.

sql
sql
-- TTL accepts the same interval formats as @timeout:
+comment on type t is '@cache 30s
+GET https://api.example.com/data';
+
+comment on type t is '@cache 5m
+GET https://api.example.com/data';
+
+comment on type t is '@cache 00:05:00
+GET https://api.example.com/data';
+
+-- Combined with other directives (order and placement are flexible):
+comment on type t is '@timeout 10s
+@retry_delay 1s, 2s on 429, 503
+@cache 5m
+GET https://api.example.com/data';

Behavior and rules:

  • Opt-in, per type. Caching happens only when @cache is present. Without it, every request fires a fresh call (the previous behavior).
  • GET only. A @cache directive on any non-GET method is ignored with a startup warning — caching a mutating request is almost always a mistake.
  • TTL. @cache <interval> uses the interval format (30s, 5m, 1h, 00:05:00, or a bare number of seconds). A bare @cache (no interval) caches with no expiration — until the process restarts — and logs a warning.
  • Successful responses only. Only 2xx responses are cached, so a transient upstream failure is never pinned for the whole TTL; the next request re-fetches.
  • Stampede protection. A burst of concurrent requests for the same cache key coalesces into a single outbound call; the rest await the in-flight result.
  • Cache key. The key is the HTTP method + resolved URL + resolved content-type + resolved headers + resolved body. Placeholders are resolved first, so per-request values vary the key naturally — each distinct resolved request is cached separately.

Caching is configured globally under HttpClientOptions (CacheEnabled kill switch, MaxCacheEntries, CachePruneIntervalSeconds).

Resolved Parameter Expressions

A common need with HTTP Types is a value computed server-side — an API token read from a table, a secret derived from the user's claims — injected into a {name} placeholder without the client ever supplying it. A resolved parameter expression does this: a param = <sql> annotation on the function runs that SQL per request and binds the result to the parameter, which then substitutes into the URL/headers/body.

sql
sql
comment on type my_api_response is 'GET https://api.example.com/data
+Authorization: Bearer {_token}';
+
+comment on function get_secure_data(_user_id int, _req my_api_response, _token text) is '
+_token = select api_token from user_tokens where user_id = {_user_id}
+';

The server resolves _token from the database, substitutes it into the Authorization header, and makes the call — the token never leaves the server or appears in client input.

See Resolved Parameters for the full reference (behavior, security, multiple expressions, table/refresh-token patterns).

Behavior

  • HTTP Types require HttpClientOptions.Enabled = true in configuration
  • The HTTP request is made before the PostgreSQL function executes
  • All function parameters (except the HTTP Type itself) are available for placeholder substitution
  • Multiple HTTP Type parameters in one function result in multiple HTTP requests
  • Errors are captured in error_message field rather than raising exceptions

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/http.html b/annotations/http.html new file mode 100644 index 000000000..da646d9ac --- /dev/null +++ b/annotations/http.html @@ -0,0 +1,101 @@ + + + + + + HTTP Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

HTTP

Expose a PostgreSQL function, procedure, or SQL file as an HTTP endpoint.

Keywords

http

Syntax

code
HTTP
+HTTP <method>
+HTTP <path>
+HTTP <method> <path>

method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS

path: Custom URL path (must start with / or be a relative path)

CommentsMode Requirement

The HTTP annotation behavior depends on the CommentsMode configuration setting:

ModeHTTP Annotation Behavior
OnlyWithHttpTagRequired - Endpoints are only created for routines with HTTP in their comment (default).
ParseAllOptional - All routines become endpoints; HTTP can customize method/path.
IgnoreIgnored - All routines become endpoints; comments are not parsed.

With the default OnlyWithHttpTag mode, a function without the HTTP annotation will not be exposed as an endpoint.

Default Behavior

When method is not specified:

  • GET for non-volatile functions, or names starting with get_, containing _get_, or ending with _get
  • POST for all other functions

When path is not specified, it's generated from the function name using the configured URL prefix and naming conventions.

Examples

Basic Endpoint

sql
sql
create function get_status()
+returns text
+language sql
+begin atomic;
+select 'OK';
+end;
+
+comment on function get_status() is 'HTTP';

Equivalent as a SQL file endpoint (sql/get-status.sql):

sql
sql
-- HTTP
+select 'OK';

Creates: GET /api/get-status

Explicit HTTP Method

sql
sql
create function create_user(_name text)
+returns int
+language sql
+begin atomic;
+insert into users(name) values(_name) returning id;
+end;
+
+comment on function create_user(text) is 'HTTP POST';

Creates: POST /api/create-user

Custom Path

sql
sql
create function get_all_users()
+returns setof users
+language sql
+begin atomic;
+select * from users;
+end;
+
+comment on function get_all_users() is 'HTTP GET /users';

Creates: GET /users

Method and Custom Path

sql
sql
create function search_products(_query text)
+returns setof products
+language sql
+begin atomic;
+select * from products where name ilike '%' || _query || '%';
+end;
+
+comment on function search_products(text) is 'HTTP GET /products/search';

Creates: GET /products/search

Multi-line with Documentation

sql
sql
comment on function get_user_profile(int) is
+'Returns the complete user profile including preferences.
+Used by the frontend dashboard.
+
+HTTP GET /users/profile';

The documentation text is ignored; only the HTTP line is parsed.

Unrecognized Method Becomes Path

sql
sql
comment on function my_endpoint() is 'HTTP custom-endpoint';

Since custom-endpoint is not a valid HTTP method, it's treated as a path:

Creates: POST /custom-endpoint

Path Parameters

You can define RESTful path parameters using the {param} syntax in URL paths. Parameter values are extracted directly from the URL path instead of query strings or request body.

Single Path Parameter

sql
sql
create function get_product(p_id int)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_product(int) is 'HTTP GET /products/{p_id}';

Call: GET /products/123p_id = 123

Multiple Path Parameters

sql
sql
create function get_review(p_id int, review_id int)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_review(int, int) is 'HTTP GET /products/{p_id}/reviews/{review_id}';

Call: GET /products/5/reviews/10p_id = 5, review_id = 10

Path Parameters with Query String

sql
sql
create function get_product_details(p_id int, include_reviews boolean default false)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_product_details(int, boolean) is 'HTTP GET /products/{p_id}/details';

Call: GET /products/42/details?includeReviews=truep_id = 42, include_reviews = true

Path Parameters with JSON Body

sql
sql
create function update_product(p_id int, new_name text)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function update_product(int, text) is 'HTTP POST /products/{p_id}';

Call: POST /products/7 with body {"newName": "New Name"}p_id = 7, new_name = "New Name"

Path Parameter Key Features

  • Parameter names in {param} can use either the PostgreSQL name ({p_id}) or the converted camelCase name ({pId}), matching is case-insensitive
  • Works with all HTTP methods (GET, POST, PUT, DELETE)
  • Can be combined with query string parameters (GET/DELETE) or JSON body parameters (POST/PUT)
  • Supports all parameter types (int, text, uuid, bigint, etc.)
  • Zero performance impact on endpoints without path parameters

Comments

+ + + + \ No newline at end of file diff --git a/annotations/index.html b/annotations/index.html new file mode 100644 index 000000000..a733b002a --- /dev/null +++ b/annotations/index.html @@ -0,0 +1,37 @@ + + + + + + Annotations Reference | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Annotations Reference

Complete reference for all NpgsqlRest comment annotations. For an introduction to how annotations work, see the Comment Annotations Guide.

INFO

All annotations work in both PostgreSQL function/procedure comments (COMMENT ON FUNCTION ...) and SQL file endpoints (-- and /* */ comments in .sql files). The "SQL File Annotations" section below lists annotations that are specific to SQL files.

How to Use This Reference

Each annotation has its own page with:

  • Recognized keywords
  • Syntax explanation
  • Working examples from the test suite
  • Related annotations

Annotation Categories

HTTP & Routing

  • HTTP - Expose function as HTTP endpoint
  • PATH - Set custom endpoint path
  • PROXY - Mark endpoint as reverse proxy
  • PROXY_OUT - Execute function first, then forward result to upstream
  • ENABLED - Enable endpoint for specific tags
  • DISABLED - Disable endpoint for specific tags
  • TAGS - Filter annotations by tags
  • OPENAPI - Hide from the OpenAPI document or override the section tag
  • MCP - Expose a routine as a Model Context Protocol (MCP) tool for AI agents
  • HTTP CUSTOM TYPES - Define HTTP request on composite type for external API calls
  • INTERNAL - Mark endpoint as internal-only (accessible via proxy/HTTP client types, not public HTTP)

Authorization

Basic Authentication

Request Configuration

Response Configuration

  • Response Headers - Set custom response headers
  • RESPONSE_NULL_HANDLING - NULL handling in responses
  • NESTED - Serialize composite type columns as nested JSON objects
  • SINGLE - Return a single record as a JSON object instead of an array
  • VOID - Force endpoint to return 204 No Content

Table Format Output

  • Custom Parameters - table_format, excel_file_name, excel_sheet for HTML table and Excel rendering

Raw Output Mode

Caching

Performance

Format References

Server-Sent Events

Upload

Policies

Context & Security

Parameter Annotations

  • PARAM - Rename, retype, set defaults, and configure parameters
  • PARAMETER_HASH - Hash one parameter using another
  • ENCRYPT - Encrypt parameter values before sending to PostgreSQL
  • DECRYPT - Decrypt result column values before returning to client

SQL File Annotations

  • DEFINE_PARAM - Define virtual HTTP parameters not bound to SQL
  • RESULT_NAME - Rename result keys in multi-command SQL file endpoints
  • SKIP - Exclude commands from multi-command results
  • RETURNS - Skip Describe step and resolve return columns from a composite type (for runtime-created temp tables)

Test File Annotations

These apply only to test files run by the SQL test runner (npgsqlrest --test) — not to endpoint SQL files or routine comments:

  • TEST @setup - Run named steps before an individual test file
  • TEST @teardown - Run named steps after an individual test file, always
  • TEST @connection - Run a test file on a different named connection
  • TEST @tag - Tag a test file for Tag/ExcludeTag filtering
  • TEST @claim - Set the acting principal for an in-process endpoint call (HTTP block directive)
  • TEST @response - Name the captured response temp table (HTTP block directive)

Custom

Comments

+ + + + \ No newline at end of file diff --git a/annotations/internal.html b/annotations/internal.html new file mode 100644 index 000000000..a8fe44f1b --- /dev/null +++ b/annotations/internal.html @@ -0,0 +1,75 @@ + + + + + + INTERNAL Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

INTERNAL

Also known as

internal, internal_only (with or without @ prefix)

Mark an endpoint as internal-only — accessible via self-referencing calls (proxy annotations and HTTP client types with relative paths) but not exposed as a public HTTP route.

Direct HTTP calls to an internal endpoint return 404. Internal calls via proxy or HTTP client types work normally.

Syntax

code
@internal
+@internal_only
+internal
+internal_only

All forms are equivalent.

Example: Internal Helper with Proxy

sql
sql
-- Internal helper: returns data but is NOT callable from outside
+create function get_cached_rates()
+returns json language sql as $$
+    select rates from exchange_rates order by fetched_at desc limit 1
+$$;
+comment on function get_cached_rates() is 'HTTP GET
+@internal';
+
+-- Public endpoint that proxies the internal one
+create function convert_currency(_amount numeric, _from text, _to text)
+returns json language plpgsql as $$
+...
+$$;
+comment on function convert_currency(numeric, text, text) is 'HTTP GET
+proxy GET /api/get-cached-rates';
  • GET /api/get-cached-rates404 Not Found
  • GET /api/convert-currency?amount=100&from=USD&to=EUR → works (proxies internally)

Example: Internal Helper with HTTP Client Types

sql
sql
-- Internal data source
+create function get_users()
+returns json language sql as $$
+    select json_agg(row_to_json(u)) from users u
+$$;
+comment on function get_users() is 'HTTP GET
+@internal';
+
+-- HTTP client type pointing to internal endpoint
+create type api_users as (body text);
+comment on type api_users is 'GET /api/get-users';
+
+-- Public endpoint composing internal calls
+create function get_dashboard(_users api_users)
+returns json language plpgsql as $$
+begin
+    return json_build_object('users', (_users).body::json);
+end;
+$$;

SQL File Endpoints

Works on all endpoint sources — functions, procedures, and SQL files:

sql
sql
-- sql/internal_helper.sql
+-- HTTP GET
+-- @internal
+select * from cached_data;

Comments

+ + + + \ No newline at end of file diff --git a/annotations/interval-format.html b/annotations/interval-format.html new file mode 100644 index 000000000..8baf579b4 --- /dev/null +++ b/annotations/interval-format.html @@ -0,0 +1,95 @@ + + + + + + Interval Format Reference | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Interval Format Reference

Several NpgsqlRest annotations accept time or duration values. This page documents the supported interval format used throughout the system.

Syntax

code
<number>[unit]
+<number> [unit]
  • number: Integer or decimal value (e.g., 30, 1.5, 0.25)
  • unit: Optional time unit suffix (defaults to seconds if omitted)
  • space: Optional space between number and unit

Supported Units

UnitShortLong Forms
Microsecondsususec, microsecond, microseconds
Millisecondsmsmsec, millisecond, milliseconds
Secondsssec, second, seconds
Minutesmmin, minute, minutes
Hourshhour, hours
Daysdday, days
Weekswweek, weeks

All unit names are case-insensitive: 5s, 5S, 5sec, 5SEC, 5Seconds are all equivalent.

Examples

code
30s          -- 30 seconds
+5m           -- 5 minutes
+1h           -- 1 hour
+1d           -- 1 day
+2w           -- 2 weeks
+500ms        -- 500 milliseconds
+1000us       -- 1000 microseconds

Long Form

code
30seconds    -- 30 seconds
+5minutes     -- 5 minutes
+1hour        -- 1 hour
+1day         -- 1 day
+2weeks       -- 2 weeks

With Space

code
30 s         -- 30 seconds
+5 minutes    -- 5 minutes
+1 hour       -- 1 hour
+1 d          -- 1 day

Decimal Values

code
1.5h         -- 1 hour 30 minutes
+0.5d         -- 12 hours
+2.5m         -- 2 minutes 30 seconds
+500.5ms      -- 500.5 milliseconds

No Unit (Defaults to Seconds)

code
30           -- 30 seconds
+120          -- 120 seconds (2 minutes)
+3600         -- 3600 seconds (1 hour)
+1.5          -- 1.5 seconds

Usage in Annotations

@timeout / @command_timeout

Sets query execution timeout:

sql
sql
comment on function quick_lookup() is
+'HTTP GET
+@timeout 5s';
+
+comment on function slow_report() is
+'HTTP GET
+@timeout 2min';
+
+comment on function very_long_process() is
+'HTTP GET
+@timeout 1h';

Single Token for @timeout

The @timeout annotation reads only the first token after the keyword. Use formats without spaces or use the short forms to avoid parsing issues.

@cache_expires_in

Sets cache expiration time:

sql
sql
comment on function get_live_data() is
+'HTTP GET
+@cached
+@cache_expires_in 10s';
+
+comment on function get_dashboard() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';
+
+comment on function get_static_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1d';

Configuration Values

The same interval format is used in JSON configuration files:

json
json
{
+  "NpgsqlRest": {
+    "CommandTimeout": "30s"
+  },
+  "CacheOptions": {
+    "DefaultExpiration": "5m",
+    "LocalCacheExpiration": "1m"
+  },
+  "Auth": {
+    "JwtClockSkew": "5m"
+  }
+}

Invalid Formats

The following formats are not supported:

code
5.5.5h       -- Multiple decimal points
+h5           -- Unit before number
+5 m m        -- Multiple units
+5months      -- Unsupported unit
+1year        -- Unsupported unit (use days or weeks)

Comments

+ + + + \ No newline at end of file diff --git a/annotations/login.html b/annotations/login.html new file mode 100644 index 000000000..78d4474cd --- /dev/null +++ b/annotations/login.html @@ -0,0 +1,255 @@ + + + + + + LOGIN Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Comments

+ + + + \ No newline at end of file diff --git a/annotations/logout.html b/annotations/logout.html new file mode 100644 index 000000000..253cf0a0d --- /dev/null +++ b/annotations/logout.html @@ -0,0 +1,98 @@ + + + + + + LOGOUT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

LOGOUT

Also known as

signout (with or without @ prefix)

Mark endpoint as a sign-out endpoint.

Syntax

code
@logout

Logout Endpoint Behavior

When an endpoint is marked with logout, NpgsqlRest executes the sign-out operation after running the function.

Void Functions

If the function returns void, NpgsqlRest simply:

  1. Executes the function
  2. Calls sign-out on all authentication schemes
  3. Completes the response

Functions with Return Values

If the function returns values, all returned values are interpreted as authentication scheme names to sign out from. This allows selective logout from specific schemes.

  • Single values are added as scheme names
  • Arrays are expanded - each element becomes a scheme name
  • NULL values are ignored
  • If no schemes are returned (empty result), signs out from all schemes

This is useful when using multiple authentication schemes (e.g., Cookie and Bearer Token) and you want to sign out from only specific ones.

Examples

Basic Logout (Void)

sql
sql
create function signout()
+returns void
+language sql
+begin atomic;
+  -- Optionally perform cleanup
+  delete from sessions where user_id = current_user_id();
+end;
+
+comment on function signout() is
+'HTTP POST
+@logout
+@authorize';

Equivalent as a SQL file endpoint (sql/signout.sql):

sql
sql
-- HTTP POST
+-- @logout
+-- @authorize
+delete from sessions where user_id = current_user_id();

Signs out from all authentication schemes.

Logout from Specific Scheme

sql
sql
create function logout_cookie()
+returns text
+language sql
+begin atomic;
+  select 'Cookies'::text;
+end;
+
+comment on function logout_cookie() is
+'HTTP POST /auth/logout/cookie
+@logout
+@authorize';

Signs out only from the "Cookies" authentication scheme.

Logout from Multiple Schemes

sql
sql
create function logout_web()
+returns text[]
+language sql
+begin atomic;
+  select array['Cookies', 'Bearer']::text[];
+end;
+
+comment on function logout_web() is
+'HTTP POST /auth/logout/web
+@logout
+@authorize';

Signs out from both "Cookies" and "Bearer" schemes.

Conditional Scheme Logout

sql
sql
create function smart_logout(_scheme text default null)
+returns text
+language sql
+begin atomic;
+  select _scheme;  -- Returns NULL to logout from all, or specific scheme
+end;
+
+comment on function smart_logout(text) is
+'HTTP POST /auth/logout
+@logout
+@authorize';
  • POST /auth/logout → Signs out from all schemes
  • POST /auth/logout?_scheme=Cookies → Signs out only from Cookies

Logout with Cleanup

sql
sql
create function full_logout()
+returns void
+language plpgsql
+as $$
+begin
+  -- Revoke all refresh tokens for this user
+  delete from refresh_tokens where user_id = current_user_id();
+
+  -- Log the logout event
+  insert into audit_log(user_id, action)
+  values (current_user_id(), 'logout');
+end;
+$$;
+
+comment on function full_logout() is
+'HTTP POST
+@logout
+@authorize';

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/mcp.html b/annotations/mcp.html new file mode 100644 index 000000000..137a113e4 --- /dev/null +++ b/annotations/mcp.html @@ -0,0 +1,65 @@ + + + + + + MCP Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

MCP

New in 3.17.0

The @mcp annotation and the NpgsqlRest.Mcp plugin were added in version 3.17.0. It implements the Model Context Protocol specification 2025-11-25.

Opt a routine in as an MCP tool so an AI agent can discover it (tools/list) and execute it (tools/call) over the MCP server endpoint.

Exposure is never automatic — a routine becomes a tool only when its comment carries @mcp. When the MCP plugin is not loaded (or McpOptions.Enabled is false), the annotation is a no-op — safe to leave on a routine regardless of how the host is configured.

Syntax

code
@mcp                       # expose as a tool; description from the comment prose
+@mcp <text>                # expose; <text> is an inline (explicit) tool description
+@mcp_description <text>    # expose; explicit, authoritative description (alias: @mcp_desc)
+@mcp_name <name>           # override the tool name (default: the routine name)

Description precedence

The tool's description uses a fixed priority — the highest-priority source that is present wins, regardless of the order the lines appear in the comment — and an explicit description suppresses the comment-prose fallback (so unrelated comment lines never leak into it):

  1. @mcp_description <text> — explicit and authoritative. Always wins when present, even if it appears after an @mcp <text> line.
  2. inline @mcp <text> — explicit.
  3. comment prose — the routine's free-text comment lines (those that aren't annotations). Used only when no explicit description is given.
  4. the routine name — last resort (a warning is logged).

So if you give any explicit description, the rest of your comment is just a comment. (Order only matters when you repeat the same annotation — the last occurrence wins.) Provide a description explicitly (preferably @mcp_description) whenever your comment also contains notes you don't want an agent to see; let the prose fallback do the work when your comment is the description.

MCP-only tools (no HTTP route)

The HTTP tag controls the REST route; @mcp controls the tool — independently. A bare @mcp with no HTTP tag exposes the routine only as an MCP tool, with no public REST endpoint:

sql
sql
comment on function summarize_account(_account_id int) is '
+@mcp Summarize an account for the agent, including balance and recent activity.
+';

The routine is callable via tools/call but has no HTTP route — an endpoint that exists only because @mcp requested it is internal-only by default, so opting into MCP never silently widens your HTTP surface. (Requires the comment-gated modes — OnlyAnnotated, the client default, or OnlyWithHttpTag. A debug log notes the defaulting at startup.)

The full matrix:

Comment carriesResult
HTTP GET + @mcpREST endpoint and MCP tool
@mcp onlyMCP-only (no REST route)
HTTP GET onlyREST-only (no tool)
HTTP GET + @mcp + @internalMCP-only (@internal hides the declared route)

This works identically for SQL file endpoints: a .sql file whose comment carries @mcp but no HTTP tag becomes an MCP-only tool (without @mcp such a file is skipped as a non-endpoint script, as before).

All other annotations apply equally — most usefully @authorize: when a tool runs, the caller's authenticated identity is forwarded, so role checks are enforced exactly as they would be for the HTTP endpoint.

Examples

Expose a routine as a tool (HTTP and MCP)

sql
sql
create function get_weather(_city text)
+returns text
+language sql as $$
+  select format('Weather for %s: sunny, 22C', _city);
+$$;
+
+comment on function get_weather(_city text) is '
+HTTP GET /api/weather
+@mcp Get the current weather for a city.
+';

The routine is reachable at GET /api/weather and advertised as the get_weather MCP tool with the description "Get the current weather for a city." and an input schema derived from its parameters ({ "city": { "type": "string" } }).

Description from comment prose

sql
sql
comment on function list_open_tickets() is '
+HTTP GET /api/tickets/open
+List all currently open support tickets for triage.
+@mcp
+';

With a bare @mcp, the description is taken from the prose line — "List all currently open support tickets for triage."

Explicit description, with a private note that stays out of it

sql
sql
comment on function rebuild_search_index() is '
+HTTP POST
+@mcp Rebuild the product search index. Safe to call; runs in the background.
+@mcp_description Rebuild the product search index. Returns immediately.
+TODO: revisit batch size — internal note, must NOT reach the agent.
+';

Because @mcp_description is present, it is the description verbatim — the inline @mcp text and the TODO: prose line are both ignored. (For SQL-file endpoints, also see SqlFileSource.CommentScope, which controls which comments are parsed at all.)

Override the tool name

sql
sql
comment on function fn_q1_report() is '
+@mcp Quarterly revenue report.
+@mcp_name quarterly_report
+';

The tool is published as quarterly_report rather than fn_q1_report — and since there is no HTTP tag, it is MCP-only (no REST route).

As a SQL file endpoint (sql/quarterly-report.sql):

sql
sql
-- @mcp Quarterly revenue report.
+-- @mcp_name quarterly_report
+select * from generate_quarterly_report();

Recognized keywords

FormAction
@mcpExpose as a tool; description from comment prose
@mcp <text>Expose as a tool; <text> is an inline (explicit) description
@mcp_description <text>Expose as a tool; explicit, authoritative description (alias @mcp_desc) — suppresses comment prose
@mcp_name <name>Override the tool name

Comments

+ + + + \ No newline at end of file diff --git a/annotations/nested.html b/annotations/nested.html new file mode 100644 index 000000000..31dbd41a4 --- /dev/null +++ b/annotations/nested.html @@ -0,0 +1,114 @@ + + + + + + NESTED Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

NESTED

Also known as

nested_json, nested_composite (with or without @ prefix)

Serialize composite type columns as nested JSON objects instead of expanding their fields into separate columns.

Syntax

code
@nested

Default Behavior vs Nested

When a function returns a composite type column, by default the composite type fields are expanded into separate columns (for backward compatibility).

With the nested annotation, composite type columns are serialized as nested JSON objects.

Examples

Basic Usage

sql
sql
create type address_type as (
+    street text,
+    city text,
+    zip_code text
+);
+
+create function get_user_with_address()
+returns table(
+    user_id int,
+    user_name text,
+    address address_type
+)
+language sql
+begin atomic;
+select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
+end;
+
+comment on function get_user_with_address() is 'HTTP GET
+@nested';

Default behavior (without @nested):

json
json
[{"userId":1,"userName":"Alice","street":"123 Main St","city":"New York","zipCode":"10001"}]

With @nested annotation:

json
json
[{"userId":1,"userName":"Alice","address":{"street":"123 Main St","city":"New York","zipCode":"10001"}}]

Multiple Composite Columns

sql
sql
create type contact_info as (
+    email text,
+    phone text
+);
+
+create type location as (
+    lat numeric,
+    lng numeric
+);
+
+create function get_business()
+returns table(
+    id int,
+    name text,
+    contact contact_info,
+    coords location
+)
+language sql
+begin atomic;
+select 1, 'Acme Corp',
+       row('info@acme.com', '555-1234')::contact_info,
+       row(40.7128, -74.0060)::location;
+end;
+
+comment on function get_business() is 'HTTP GET
+@nested';

Response:

json
json
[{
+    "id": 1,
+    "name": "Acme Corp",
+    "contact": {"email": "info@acme.com", "phone": "555-1234"},
+    "coords": {"lat": 40.7128, "lng": -74.0060}
+}]

Deep Nested Composite Types

When composite types contain other composite types (or arrays of composites), the inner composites are also serialized as proper JSON objects by default:

sql
sql
create type inner_type as (id int, name text);
+create type outer_type as (label text, inner_val inner_type);
+
+create function get_nested_data()
+returns table(data outer_type)
+language sql
+begin atomic;
+select row('outer', row(1, 'inner')::inner_type)::outer_type;
+end;
+
+comment on function get_nested_data() is 'HTTP GET
+@nested';

Response:

json
json
[{"data": {"label": "outer", "innerVal": {"id": 1, "name": "inner"}}}]

This works to any nesting depth. Deep resolution is controlled by the ResolveNestedCompositeTypes option (default: true). See Routine Options for details and when you might want to disable it.

Arrays of Composite Types

Arrays of composite types are automatically serialized as JSON arrays of objects — this happens regardless of the @nested annotation:

sql
sql
create type book_item as (book_id int, title text);
+
+create function get_books()
+returns table(author text, books book_item[])
+language sql
+begin atomic;
+select 'Orwell', array[row(1, '1984')::book_item, row(2, 'Animal Farm')::book_item];
+end;
+
+comment on function get_books() is 'HTTP GET';

Response:

json
json
[{"author": "Orwell", "books": [{"bookId": 1, "title": "1984"}, {"bookId": 2, "title": "Animal Farm"}]}]

The @nested annotation specifically controls whether single composite type columns are serialized as nested objects or expanded into flat fields.

Global Configuration

Instead of adding the annotation to each endpoint, you can enable nested JSON globally via configuration. Each endpoint source has its own independent setting:

json
json
{
+  "NpgsqlRest": {
+    "RoutineOptions": {
+      "NestedJsonForCompositeTypes": true
+    },
+    "SqlFileSource": {
+      "NestedJsonForCompositeTypes": true
+    }
+  }
+}

When enabled globally, all composite type columns from the respective endpoint source will be serialized as nested JSON objects by default, without requiring the annotation.

Behavior

  • Only affects composite type columns in the result set — controls whether their fields are expanded flat or kept as nested JSON objects
  • Arrays of composite types are always automatically serialized as JSON arrays of objects (independent of this annotation)
  • Works with custom composite types (CREATE TYPE) and table types
  • NULL composite values are serialized as null in JSON
  • Deep nesting (composites inside composites) is resolved to any depth by default via ResolveNestedCompositeTypes

Comments

+ + + + \ No newline at end of file diff --git a/annotations/new-line.html b/annotations/new-line.html new file mode 100644 index 000000000..252ce9ca5 --- /dev/null +++ b/annotations/new-line.html @@ -0,0 +1,48 @@ + + + + + + NEW_LINE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

NEW_LINE

Also known as

raw_new_line (with or without @ prefix)

Set the row separator for raw output mode.

Syntax

code
@new_line <string>

Supports escape sequences: \n (newline), \r\n (Windows newline), \\ (backslash)

Examples

Unix Line Endings

sql
sql
comment on function export_unix() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \n';

Windows Line Endings

sql
sql
comment on function export_windows() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \r\n';

Custom Row Separator

sql
sql
comment on function export_custom() is
+'HTTP GET
+@raw
+@new_line |||';

Comments

+ + + + \ No newline at end of file diff --git a/annotations/openapi.html b/annotations/openapi.html new file mode 100644 index 000000000..ef0548603 --- /dev/null +++ b/annotations/openapi.html @@ -0,0 +1,82 @@ + + + + + + OPENAPI Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

OPENAPI

New in 3.15.0

The @openapi annotation was added in version 3.15.0.

Per-routine override for OpenAPI document inclusion and section grouping. Two sub-commands: hide an endpoint from the document entirely, or replace its default schema-name tag with one or more custom tags.

The HTTP endpoint itself is unaffected — @openapi hide only suppresses the spec entry. The endpoint is still reachable, still respects @authorize, still runs the same SQL.

When the OpenAPI plugin is not loaded, both sub-commands are no-ops — safe to leave on a routine regardless of how the host is configured.

Syntax

code
@openapi                  # hide from document (default action)
+@openapi hide             # hide from document
+@openapi hidden           # alias for hide
+@openapi ignore           # alias for hide
+
+@openapi tag <name>             # replace default schema tag with <name>
+@openapi tags <a>, <b>, <c>     # replace default tag with multiple tags

Tag values preserve their original casing — @openapi tag Partner API produces a Partner API tag, not partner api.

How it composes with config-level filters

@openapi is the first filter applied — it wins over IncludeSchemas, ExcludeSchemas, NameSimilarTo, NameNotSimilarTo, and RequiresAuthorizationOnly. See Filter order in the OpenAPI config reference.

This means @openapi hide reliably keeps a routine out of the document even when broad config filters would otherwise include it (e.g. when IncludeSchemas allows the schema).

Examples

Hide an internal maintenance routine

sql
sql
create function refresh_materialized_views()
+returns void
+language sql security definer as $$
+  refresh materialized view concurrently revenue_summary;
+  refresh materialized view concurrently user_activity;
+$$;
+
+comment on function refresh_materialized_views() is '
+HTTP POST
+@authorize admin
+@openapi hide
+';

The endpoint stays reachable at POST /api/refresh-materialized-views for admin callers — it just isn't advertised in the generated openapi.json.

As a SQL file endpoint (sql/refresh-materialized-views.sql):

sql
sql
-- HTTP POST
+-- @authorize admin
+-- @openapi hide
+refresh materialized view concurrently revenue_summary;

Group routines under a custom tag

By default, endpoints are tagged with their schema name — every routine in public lands in a public section in Swagger UI / ReDoc. @openapi tag overrides that.

sql
sql
comment on function partner_get_orders(_partner_id text) is '
+HTTP GET /api/partner/orders
+@authorize partner
+@openapi tag Partner API
+';
+
+comment on function partner_create_order(_partner_id text, _order_json text) is '
+HTTP POST /api/partner/orders
+@authorize partner
+@openapi tag Partner API
+';

Both endpoints group under a single Partner API section in Swagger UI instead of the default public tag.

Multiple tags

sql
sql
comment on function get_dashboard_summary() is '
+HTTP GET /api/dashboard
+@authorize
+@openapi tags Dashboard, Reports
+';

The endpoint appears in both the Dashboard and Reports sections.

Hide alongside a config filter

@openapi hide is checked before IncludeSchemas etc., so even when broad filters would include the routine, @openapi hide keeps it out:

json
json
{
+  "NpgsqlRest": {
+    "OpenApiOptions": {
+      "IncludeSchemas": ["partner"]
+    }
+  }
+}
sql
sql
-- Lives in partner schema, but marked hidden — won't appear in the partner document.
+comment on function partner.diagnostic_check() is '
+HTTP GET /api/partner/_diagnostic
+@authorize partner
+@openapi hide
+';

Recognized keywords

FormAction
@openapiHide from document
@openapi hideHide from document
@openapi hiddenHide from document
@openapi ignoreHide from document
@openapi tag <name>Replace default tag with <name>
@openapi tags <a>, <b>Replace default tag with multiple tags

Comments

+ + + + \ No newline at end of file diff --git a/annotations/param.html b/annotations/param.html new file mode 100644 index 000000000..e4ae6ec4b --- /dev/null +++ b/annotations/param.html @@ -0,0 +1,118 @@ + + + + + + PARAM Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

PARAM

Also known as

param, parameter (with or without @ prefix)

Rename and optionally retype individual endpoint parameters. This provides better API ergonomics by replacing positional parameter names ($1, $2) or internal parameter names (_old_name) with cleaner, user-facing names.

Works on all endpoint types — functions, procedures, and SQL file endpoints.

TIP

The @param keyword is shared with the PARAMETER_HASH annotation (@param X is hash of Y). Both forms coexist without ambiguity — the parser distinguishes them by the presence of hash of in the annotation.

Syntax

code
@param <old_name> <new_name>
+@param <old_name> <new_name> <type>
+@param <old_name> is <new_name>
+@param <old_name> is <new_name> <type>
+@param <old_name> default <value>
+@param <old_name> <new_name> default <value>
+@param <old_name> <new_name> <type> default <value>
+@param <old_name> is <new_name> default <value>
+@param <old_name> is <new_name> <type> default <value>
+
+# `=` can be used instead of `default` in all forms above:
+@param <old_name> = <value>
+@param <old_name> <new_name> = <value>
+@param <old_name> <new_name> <type> = <value>
+@param <old_name> is <new_name> = <value>
+@param <old_name> is <new_name> <type> = <value>
  • old_name: The original parameter name (e.g., $1, $2, or _old_name)
  • new_name: The new parameter name for the HTTP API. Used as-is — no name conversion is applied. If you write @param $1 authorId, the HTTP parameter name is exactly authorId, not author_id or author-id.
  • type: Optional PostgreSQL type override (e.g., integer, text, boolean)

Both @param and @parameter (long form) are supported.

Examples

Rename Positional Parameters (SQL Files)

SQL files use PostgreSQL positional parameters ($1, $2, ...) which aren't user-friendly as HTTP parameter names. Use @param to give them meaningful names:

sql
sql
-- sql/get_reports.sql
+-- HTTP GET
+-- @param $1 from_date
+-- @param $2 to_date
+select id, title, created_at
+from reports
+where created_at between $1 and $2;

Without rename: GET /api/get-reports?$1=2024-01-01&$2=2024-12-31

With rename: GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31

Rename with Type Override

When parameter types can't be inferred correctly, or when you need to override the inferred type:

sql
sql
-- @param $1 user_id integer
+-- @param $2 active boolean
+select * from users where id = $1 and active = $2;

Rename Function Parameters

Works on function and procedure parameters too — useful when internal naming conventions (like _ prefixes) shouldn't leak into the API:

sql
sql
create function get_user_profile(_user_id int, _include_stats boolean)
+returns json
+language sql
+begin atomic;
+select json_build_object('id', id, 'name', name) from users where id = _user_id;
+end;
+
+comment on function get_user_profile(int, boolean) is '
+HTTP GET
+@param _user_id user_id
+@param _include_stats include_stats
+';

Without rename: GET /api/get-user-profile?_user_id=1&_include_stats=true

With rename: GET /api/get-user-profile?user_id=1&include_stats=true

"is" Style Syntax

The is keyword is optional and provides consistency with the existing @param X is hash of Y style:

sql
sql
-- These are equivalent:
+-- @param $1 user_id
+-- @param $1 is user_id
+
+-- With type override:
+-- @param $1 user_id integer
+-- @param $1 is user_id integer

Claim Mapping with Renamed Parameters

Renamed parameters work with user_parameters claim mapping. When you rename a positional parameter to a claim-mapped name (like _user_id or _user_name), the parameter is automatically filled from the authenticated user's claims — just like it would be for a native function parameter.

sql
sql
-- sql/get_my_profile.sql
+-- @authorize
+-- @user_parameters
+-- @param $1 _user_id
+-- @param $2 _user_name
+select $1 as user_id, $2 as user_name;

GET /api/get-my-profile (authenticated as user123) → [{"userId": "user123", "userName": "user"}]

The parameters are auto-filled from claims — the client doesn't need to send them. This is especially useful for SQL file endpoints where positional parameters ($1, $2) have no inherent name for claim matching.

Default Values (SQL File Parameters)

SQL file parameters can have default values via @param. When a parameter with a default is not provided in the request, the default value is bound instead of returning 404.

This is essential for SQL files because positional parameters ($1, $2) must always be bound — unlike PostgreSQL functions where the engine applies its own defaults.

Syntax

sql
sql
-- Separate annotations (rename first, then set default):
+-- @param $1 user_id
+-- @param user_id default null
+
+-- Combined rename + default on a single line:
+-- @param $1 user_id default null
+
+-- Default without rename:
+-- @param $1 default 'fallback'
+
+-- Various value types:
+-- @param $1 status default 'active'     -- text (single-quoted)
+-- @param $1 amount default 42           -- number
+-- @param $1 enabled default true        -- boolean
+-- @param $1 filter default null         -- SQL NULL (unquoted)
+-- @param $1 tag default 'null'          -- literal text "null" (quoted)
+-- @param $1 val default                 -- no value = NULL

Inline comments after the value are ignored. The parser consumes only the value token (or quoted string) and stops.

sql
sql

+-- `=` can be used instead of `default` in all forms:
+-- @param $1 user_id = null
+-- @param $1 user_id integer = 42
+-- @param $1 is greeting = 'hey'
+-- @param my_name = 'hello'

Value Parsing Rules (SQL Conventions)

  • Unquoted null (case-insensitive) → DBNull.Value
  • Single-quoted 'text value' → string literal (supports multi-word)
  • Unquoted value → raw string (Npgsql handles type conversion via NpgsqlDbType)

Example

User identity endpoint with claim-filled parameters that fall back to NULL:

sql
sql
/* HTTP GET
+@authorize
+@user_parameters
+@param $1 _user_id default null
+@param $2 _username default null
+@param $3 _email default null
+*/
+select $1 as user_id, $2 as username, $3 as email;

When authenticated, claims fill the parameters automatically. The defaults ensure the parameters are always bindable.

Effects on Generated Output

  • TsClient: Parameters with defaults get ? suffix in TypeScript interfaces (optional)
  • OpenAPI: Parameters with defaults are marked required: false

Rename Validation

Parameter names are validated when renaming. Invalid renames are rejected with a warning log instead of silently creating broken endpoints.

Rules:

  • Must be a valid PostgreSQL identifier: starts with letter or _, followed by letters, digits, _, or $
  • Positional parameters ($1, $2) are allowed
sql
sql
-- Valid:
+-- @param $1 user_id        ✓
+-- @param $1 _val$1         ✓
+
+-- Rejected (with warning log):
+-- @param $1 1bad           ✗ starts with digit
+-- @param $1 my-param       ✗ invalid character (hyphen)

Composite Type Parameters (SQL Files)

When a parameter type is a known composite type, the parameter is treated as a single text value. The SQL is never rewritten — it stays exactly as written.

HTTP custom types (auto-filled from HTTP calls):

sql
sql
-- @param $1 _response example_9.exchange_rate_api
+select ($1::example_9.exchange_rate_api).body;

The framework makes the HTTP call and passes the response as a composite text value automatically.

Client-sent composite types:

sql
sql
-- @param $1 data my_composite_type
+select ($1::my_composite_type).field1, ($1::my_composite_type).field2;

The client sends the value as PostgreSQL composite text format: ?data=("val1","val2").

If the type in @param is not a recognized PostgreSQL type or composite type, a warning is logged and the parameter keeps its original type from Describe.

Behavior

  • The rename applies to HTTP parameter names only — the SQL still uses the original parameter name or positional reference
  • Type overrides affect how the HTTP parameter value is parsed and converted before being sent to PostgreSQL
  • For multi-command SQL files, parameter types are merged across all statements — use type override to resolve conflicts
  • The parameter must exist in the endpoint's parameter list; otherwise a warning is logged and the rename is skipped
  • Renamed parameters participate in claim mapping — renaming $1 to _user_id enables automatic user_parameters filling from the name_identifier claim

Comments

+ + + + \ No newline at end of file diff --git a/annotations/parameter-hash.html b/annotations/parameter-hash.html new file mode 100644 index 000000000..bd834163c --- /dev/null +++ b/annotations/parameter-hash.html @@ -0,0 +1,92 @@ + + + + + + PARAMETER_HASH Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

PARAMETER_HASH

Also known as

param (with or without @ prefix)

Hash one parameter value using another parameter as the hash input. This annotation is commonly used to create user registration endpoints that securely store hashed passwords in the database.

Syntax

code
@param <target_param> is hash of <source_param>
+@parameter <target_param> is hash of <source_param>
  • target_param: The parameter that will receive the hashed value.
  • source_param: The parameter whose value will be hashed.

Examples

Simple User Registration

sql
sql
create function register(_email text, _password text, _hash text)
+returns int
+language sql
+begin atomic;
+insert into users (email, password_hash) values (_email, _hash) returning id;
+end;
+
+comment on function register(text, text, text) is '
+@param _hash is hash of _password
+';

Equivalent as a SQL file endpoint (sql/register.sql):

sql
sql
/*
+HTTP POST
+@param $1 email
+@param $2 password
+@param $3 hash is hash of password
+*/
+insert into users (email, password_hash) values ($1, $3) returning id;

User Registration with Response

sql
sql
create function create_user(
+    _username text,
+    _password text,
+    _password_hash text
+)
+returns json
+language sql
+begin atomic;
+insert into users (username, password_hash)
+values (_username, _password_hash)
+returning json_build_object('id', id, 'username', username);
+end;
+
+comment on function create_user(text, text, text) is '
+HTTP POST
+@param _password_hash is hash of _password
+';

When called with {"username": "john", "password": "secret123"}:

  • _password receives the plain text "secret123"
  • _password_hash receives the hashed value of "secret123"

Behavior

  • The hash is computed using the built-in password hasher.
  • The source parameter value remains unchanged and can still be used in the function.
  • The target parameter receives the hashed value before the function is executed.
  • Both parameters must exist in the function signature.
  • This is typically used for securely storing passwords without exposing them in plain text in the database.

Built-in Password Hasher

The default password hasher uses PBKDF2 (Password-Based Key Derivation Function 2) with:

  • SHA-256 algorithm
  • 128-bit salt
  • 600,000 iterations (OWASP-recommended as of 2025)

This provides secure password hashing out of the box. A custom IPasswordHasher implementation can be injected in source code if needed.

Complete Registration and Login Flow

The param is hash of annotation works together with the LOGIN annotation to provide a complete authentication flow using the same built-in password hasher:

  1. Registration: Use param <target> is hash of <source> to hash passwords before storing them
  2. Login: Return the stored hash in a hash column and NpgsqlRest verifies it automatically

Registration Function

sql
sql
create function register(_email text, _password text, _hash text)
+returns int
+language sql
+begin atomic;
+insert into users (email, password_hash) values (_email, _hash) returning id;
+end;
+
+comment on function register(text, text, text) is '
+HTTP POST /auth/register
+@param _hash is hash of _password
+@sensitive
+';

Login Function

sql
sql
create function login(_email text, _password text)
+returns table(hash text, id int, name text, email text)
+language sql
+begin atomic;
+select u.password_hash as hash, u.id, u.name, u.email
+from users u where u.email = _email;
+end;
+
+comment on function login(text, text) is '
+HTTP POST /auth/login
+@login
+@sensitive
+';

Both functions use the same PBKDF2 hasher, ensuring passwords hashed during registration can be verified during login.

  • LOGIN - Authentication endpoint that verifies hashed passwords
  • BASIC_AUTH - Basic authentication with hashed passwords
  • SECURITY_SENSITIVE - Obfuscate parameter values in logs

Comments

+ + + + \ No newline at end of file diff --git a/annotations/parameter-substitution.html b/annotations/parameter-substitution.html new file mode 100644 index 000000000..379b7f4de --- /dev/null +++ b/annotations/parameter-substitution.html @@ -0,0 +1,63 @@ + + + + + + Parameter Value Substitution | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Parameter Value Substitution

Several comment annotations accept a {name} placeholder in their value. At request time, {name} is replaced with the value of the routine parameter name taken from that request. This lets a single endpoint produce a response header, file name, upload path, or outbound HTTP call that depends on what the caller sent.

This is one shared mechanism reused by a few annotations — this page documents it once; each annotation page links here.

sql
sql
create function export_report(_type text, _file text)
+returns text language sql as $$ select '...report...' $$;
+
+comment on function export_report(text, text) is '
+HTTP GET
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}
+';

A request GET /api/export-report?type=text/csv&file=q1.csv responds with Content-Type: text/csv and Content-Disposition: attachment; filename=q1.csv.

Where it works

{name} substitution is applied to these annotation values:

AnnotationWhat is substitutedPage
Response headers (Header-Name: value), including Content-Typethe header valueResponse Headers
Custom parameters (@key = value) — e.g. upload paths/filenamesthe value after =Custom Parameters
HTTP custom types — request URL, query string, headers, and bodythose parts of the outbound callHTTP Custom Types

Other annotations do not perform this substitution. (Braces in unrelated annotations — e.g. a URL {segment} in PATH — are a different feature; see Not to be confused with.)

How a placeholder is resolved

For each request, NpgsqlRest builds a lookup from the bound parameters (plus any allowlisted environment variables) and replaces every {name} it finds:

  • The name is matched case-insensitively. {userId}, {USERID}, and {userid} all resolve the same parameter. (Consistent with how PostgreSQL folds unquoted identifiers, and with resolved parameter expressions.)
  • Both names work. A placeholder matches either the original PostgreSQL parameter name (e.g. {_user_id}) or its converted (camelCase) name (e.g. {userId}). For HTTP custom types, the type field name also matches.
  • NULL or a missing value → empty string. If the parameter is SQL NULL (or not supplied), {name} becomes `` (nothing).
  • An unknown name is left untouched — and warned about. If name matches no parameter, the literal text {name} is kept verbatim in the output, and NpgsqlRest logs a build-time warning naming the placeholder, so typos (e.g. {_fil} for {_file}) surface at startup instead of silently shipping literal text. (The warning only fires for response headers and custom parameters, and only when the placeholder looks like an identifier — {0} or JSON-like {"a":1} are never treated as placeholders.)
  • Substitution is per-request, evaluated against the actual values bound for that call — not fixed when the endpoint is created.
  • Zero overhead when unused. A value is only scanned when it actually contains braces, so endpoints without placeholders pay nothing.

Brace handling

  • There is no escape sequence for a literal brace. A {...} whose inner text doesn't match a parameter is simply passed through unchanged (so {not_a_param} survives literally), but you cannot force a literal {userId} when userId is a parameter.
  • A stray } with no opening {, and an unclosed {, are passed through as-is.

Environment variables

A {name} can also resolve to an environment variable — useful for outbound API keys (HTTP custom types) or per-deployment values like a server/environment name in a response header — without routing them through request parameters.

This is opt-in via an allowlist: only environment variables you name in NpgsqlRest:AvailableEnvVars can be referenced. Any other {NAME} is never read from the environment (it stays literal, like an unknown parameter). The allowlist is the security boundary — there is no way to substitute an arbitrary env var.

jsonc
jsonc
"NpgsqlRest": {
+  // array form — a missing variable resolves to an empty string
+  "AvailableEnvVars": [ "WEATHER_API_KEY", "SERVER_NAME" ]
+
+  // …or object form — name → default used when the variable is absent
+  // "AvailableEnvVars": { "SERVER_NAME": "local" }
+}
sql
sql
comment on type weather_api is '
+GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {WEATHER_API_KEY}
+';

Here {_city} comes from a request parameter and {WEATHER_API_KEY} from the allowlisted environment variable — the API key never has to be passed by the caller.

Rules specific to env vars:

  • Resolved once at startup. The process environment is read when the app starts; changing a variable requires a restart (e.g. a new pod).
  • Case-insensitive, same as parameters ({server_name} resolves SERVER_NAME).
  • A routine parameter of the same name wins. If a request parameter and an allowlisted env var share a name, the parameter value is used.
  • Allowlisted names don't trigger the typo warning — they're recognized placeholders.

Response headers are client-visible

A value substituted into a response header is sent to the caller. That's exactly what you want for a per-pod Server: {SERVER_NAME} header, but it means you must not put a secret env var in a response header. Reserve secrets (API keys, tokens) for outbound HTTP custom type calls and custom parameters, which stay server-side.

Examples

Dynamic file download

sql
sql
comment on function get_invoice(_id int, _filename text) is '
+HTTP GET
+Content-Type: application/pdf
+Content-Disposition: attachment; filename={_filename}
+';

Upload destination from a parameter

See Custom Parameters and Upload. The upload handler's path/filename keys accept placeholders:

sql
sql
comment on function upload_avatar(_user_id int, _path text) is '
+@upload for file_system
+@file_system_path = /var/uploads/{_user_id}
+';

Outbound HTTP call shaped by parameters

See HTTP Custom Types. The URL, headers, and body of the proxied call accept placeholders — mix request parameters with an allowlisted environment variable so the API key never has to be passed by the caller:

sql
sql
comment on type weather_api is '
+GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {WEATHER_API_KEY}
+';

Not to be confused with

NpgsqlRest uses {...} syntax in a few unrelated places. They are different features with different rules:

FeatureLooks likeWhen/whereRules
Parameter value substitution (this page){name} in an annotation valuerequest time, into headers / custom params / HTTP-type callscase-insensitive; unknown → literal (+ build-time warning); NULL → empty
Environment-variable config placeholders{NAME} / {!NAME} in appsettings.jsonstartup, into config valuesresolved from env vars; {!NAME} errors if unset
URL path segments{segment} in a PATH routeroutingmaps a URL path segment to a parameter
Resolved parameter expressionsparam = <sql> (may itself contain {name})request time, server-side in SQLthe parameter is computed by running SQL; its result then substitutes here. {name} inside that SQL is matched case-insensitively (same as this page)

Comments

+ + + + \ No newline at end of file diff --git a/annotations/path.html b/annotations/path.html new file mode 100644 index 000000000..abe199c8f --- /dev/null +++ b/annotations/path.html @@ -0,0 +1,89 @@ + + + + + + PATH Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

PATH

Set a custom endpoint path. Alternative to specifying the path in the HTTP annotation.

Keywords

@path, path

Syntax

code
@path <url-path>

Examples

Custom Path

sql
sql
create function get_user_data()
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user_data() is
+'HTTP GET
+@path /users/data';

Equivalent as a SQL file endpoint (sql/get-user-data.sql):

sql
sql
-- HTTP GET
+-- @path /users/data
+select row_to_json(u) from users u where id = current_user_id();

Creates: GET /users/data

Path with HTTP Method

sql
sql
comment on function my_function() is
+'HTTP GET
+@path /custom/endpoint';

Creates: GET /custom/endpoint

Versioned API

sql
sql
comment on function get_users_v2() is
+'HTTP GET
+@path /api/v2/users';

Path Parameters

Paths can include parameter placeholders using the {param} syntax. Parameter values are extracted directly from the URL path.

Basic Path Parameter

sql
sql
create function get_user(user_id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user(int) is
+'HTTP GET
+@path /users/{user_id}';

Call: GET /users/42user_id = 42

Nested Path Parameters

sql
sql
create function get_user_order(user_id int, order_id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user_order(int, int) is
+'HTTP GET
+@path /users/{user_id}/orders/{order_id}';

Call: GET /users/42/orders/123user_id = 42, order_id = 123

Parameter Name Matching

Parameter names in {param} can use either:

  • PostgreSQL snake_case name: {user_id}
  • Converted camelCase name: {userId}

Matching is case-insensitive.

Optional Path Parameters

New in 3.8.0

Optional path parameters were added in version 3.8.0.

Path parameters support the ASP.NET Core optional parameter syntax {param?}. When a path parameter is marked as optional and the corresponding PostgreSQL function parameter has a default value, omitting the URL segment will use the PostgreSQL default:

sql
sql
create function get_item(p_id int default 42)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id?}
+';
  • GET /items/5 → uses the provided value 5
  • GET /items/ → uses the PostgreSQL default 42

This also works with query_string_null_handling null_literal to pass NULL via the literal string "null" in the path for any parameter type:

sql
sql
create function get_item(p_id int default null)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id}
+query_string_null_handling null_literal
+';
  • GET /items/null → passes SQL NULL to the function

Behavior

  • Overrides the auto-generated path
  • Path should start with / for absolute paths
  • Can be used alongside HTTP annotation
  • Path parameters can be combined with query string or body parameters
  • Optional path parameters ({param?}) use the PostgreSQL default when the URL segment is omitted
  • HTTP - Define endpoint (can also set path)

Comments

+ + + + \ No newline at end of file diff --git a/annotations/proxy-out.html b/annotations/proxy-out.html new file mode 100644 index 000000000..ea678dc8c --- /dev/null +++ b/annotations/proxy-out.html @@ -0,0 +1,166 @@ + + + + + + PROXY_OUT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

PROXY_OUT

Also known as

forward_proxy (with or without @ prefix)

Available since version 3.11.0

Execute the PostgreSQL function first, then forward its result body to an upstream service. The upstream response is returned to the client.

Syntax

code
@proxy_out
+@proxy_out [ host_url ]
+@proxy_out [ http_method ]
+@proxy_out [ http_method ] [ host_url ]

Description

The proxy_out annotation reverses the flow of the existing proxy annotation. Instead of forwarding the incoming request to upstream, proxy_out forwards the outgoing function result.

This enables a common pattern where business logic in PostgreSQL prepares a payload, and an external service performs processing that PostgreSQL cannot do — PDF rendering, image processing, ML inference, email sending, etc.

code
Client Request → NpgsqlRest
+  → Execute PostgreSQL function
+  → Forward function result as request body to upstream service
+  → Append the original request path and query string to the upstream host
+  → Return upstream response to client

The client-facing HTTP method and the upstream HTTP method are independent — the client can send a GET while the upstream receives a POST.

The upstream URL is built the same way as for proxy — the incoming request path and query string are appended to the host:

code
target URL = host + incoming request path + incoming query string

The difference from proxy is only the direction of the body: proxy_out sends the function's result as the request body to the upstream, whereas proxy sends the incoming request body.

Basic Usage

sql
sql
create function generate_report(report_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object(
+        'title', 'Monthly Report',
+        'data', (select json_agg(row_to_json(t)) from sales t where month = report_id)
+    );
+end;
+$$;
+
+comment on function generate_report(int) is 'HTTP GET
+@proxy_out POST https://render-service.internal/render';

Equivalent as a SQL file endpoint (sql/generate-report.sql):

sql
sql
/*
+HTTP GET
+@proxy_out POST https://render-service.internal/render
+@param $1 report_id
+*/
+select json_build_object(
+    'title', 'Monthly Report',
+    'data', (select json_agg(row_to_json(t)) from sales t where month = $1)
+);

The client calls GET /api/generate-report/?reportId=3. The server:

  1. Executes generate_report(3) in PostgreSQL.
  2. Takes the returned JSON and POSTs it to https://render-service.internal/render/api/generate-report/?reportId=3 (original query string forwarded).
  3. Returns the upstream response (e.g., a rendered PDF) directly to the client with the upstream's content-type and status code.

Proxy Annotations

Basic Proxy Out with Default Host

Uses the host from ProxyOptions.Host configuration:

sql
sql
-- function form
+comment on function my_func() is '@proxy_out';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy_out
+select my_func();

Proxy Out with Custom Host

Override the default host:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST https://my-other-service.internal';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST https://my-other-service.internal
+*/
+select my_func();

Proxy Out with HTTP Method Override

Specify which HTTP method to use for the upstream request (uses default host from configuration):

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out PUT';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out PUT
+*/
+select my_func();

The client sends GET, but the upstream receives PUT with the function's result as the body.

Combined Method and Host

Specify both HTTP method and host:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST https://render-service.internal/render';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST https://render-service.internal/render
+*/
+select my_func();

Self-Referencing Proxy Out (Relative Path)

Use a relative path starting with / to forward the function result to another endpoint on the same server:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST /api/internal-processor';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST /api/internal-processor
+*/
+select my_func();

Self-referencing calls bypass the HTTP stack entirely — the target endpoint handler is invoked directly in-process with zero network overhead.

URL Resolution

The target URL follows the same resolution rules as proxy:

  • Annotation URL takes priority — if provided (absolute or relative), the global ProxyOptions.Host is ignored.
  • Global ProxyOptions.Host is used only when the annotation has no URL.
  • A relative path (starting with /) always creates an internal self-call, regardless of ProxyOptions.Host.

Path and Query String Forwarding

The original client request path and query string are both appended to the upstream host as-is (host + path + query). This lets the upstream receive the same path and parameters that were used to invoke the function:

sql
sql
create function generate_report(p_format text, p_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object('id', p_id, 'data', 'report');
+end;
+$$;
+
+comment on function generate_report(text, int) is 'HTTP GET
+@proxy_out POST';

With ProxyOptions.Host = "https://api.example.com", calling GET /api/generate-report/?pFormat=pdf&pId=123 executes the function, then POSTs the result body to https://api.example.com/api/generate-report/?pFormat=pdf&pId=123 — both the path and query string are appended.

To send the result to a fixed upstream path instead, put it in the annotation host (e.g. @proxy_out POST https://api.example.com/render, which forwards to https://api.example.com/render/api/generate-report/?...), or change the endpoint path with HTTP <method> <path>.

Self-calls are the exception

For a relative self-call (host starting with /, e.g. @proxy_out POST /api/processor), the annotation path is the full target and the incoming request path is not appended.

Error Handling

  • If the function fails (database error, exception), the error is returned directly to the client — the proxy call is never made.
  • If the upstream fails (5xx, timeout, connection error), the upstream's error status and body are forwarded to the client (502 for connection errors, 504 for timeouts).

Examples

PDF Rendering Pipeline

Prepare data in PostgreSQL and render it as a PDF via an external service:

sql
sql
create function invoice_pdf(invoice_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object(
+        'invoice_number', invoice_id,
+        'items', (select json_agg(row_to_json(i)) from invoice_items i where i.invoice_id = invoice_pdf.invoice_id),
+        'total', (select sum(amount) from invoice_items where invoice_items.invoice_id = invoice_pdf.invoice_id)
+    );
+end;
+$$;
+
+comment on function invoice_pdf(int) is 'HTTP GET
+@proxy_out POST https://pdf-service.internal/render';

ML Inference

Send prepared feature data to an ML service:

sql
sql
create function predict_churn(customer_id int)
+returns json
+language plpgsql as $$
+begin
+    return (
+        select json_build_object(
+            'features', json_build_object(
+                'total_orders', count(*),
+                'last_order_days', extract(day from now() - max(order_date)),
+                'avg_order_value', avg(total)
+            )
+        )
+        from orders
+        where orders.customer_id = predict_churn.customer_id
+    );
+end;
+$$;
+
+comment on function predict_churn(int) is 'HTTP GET
+@proxy_out POST https://ml-service.internal/predict/churn';

Email Sending

Prepare email content in PostgreSQL and send via an email service:

sql
sql
create function send_welcome_email(user_id int)
+returns json
+language plpgsql as $$
+declare
+    u record;
+begin
+    select * into u from users where id = user_id;
+    return json_build_object(
+        'to', u.email,
+        'subject', 'Welcome to Our Platform',
+        'body', format('Hello %s, welcome!', u.display_name)
+    );
+end;
+$$;
+
+comment on function send_welcome_email(int) is 'HTTP POST
+@proxy_out POST https://email-service.internal/send';

TypeScript Client

The TypeScript client generator (NpgsqlRest.TsClient) recognizes proxy_out endpoints and generates functions that return the raw Response object. Since the actual response comes from the upstream proxy service (not from the PostgreSQL function's return type), the generated function returns Promise<Response>:

typescript
typescript
// Generated for a proxy_out endpoint
+export async function generateReport() : Promise<Response> {
+    const response = await fetch(baseUrl + "/api/generate-report", {
+        method: "GET",
+    });
+    return response;
+}

This allows the caller to handle the response appropriately (.json(), .blob(), .text(), etc.).

Configuration

Uses the same ProxyOptions configuration as the existing proxy annotation. ProxyOptions.Enabled must be true:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "DefaultTimeout": "30 seconds"
+    }
+  }
+}

See Proxy Options for complete configuration reference.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/proxy.html b/annotations/proxy.html new file mode 100644 index 000000000..68bcaa2c9 --- /dev/null +++ b/annotations/proxy.html @@ -0,0 +1,210 @@ + + + + + + PROXY Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

PROXY

Also known as

reverse_proxy (with or without @ prefix)

Mark endpoint as a reverse proxy that forwards requests to an upstream service.

Syntax

code
@proxy
+@proxy [ host_url ]
+@proxy [ http_method ]
+@proxy [ http_method ] [ host_url ]

Description

The proxy annotation marks an endpoint as a reverse proxy. When a request arrives, NpgsqlRest forwards it to an upstream service and either returns the response directly (passthrough mode) or passes it to your PostgreSQL function for processing (transform mode).

How the target URL is built

This is the most important thing to understand about @proxy. The function still becomes a normal NpgsqlRest endpoint with its usual URL (auto-generated from the function name, or whatever you set with HTTP <method> <path>). When a request hits that endpoint, NpgsqlRest builds the upstream URL by appending the incoming request path and query string to the host:

code
target URL = host + incoming request path + incoming query string

The host is the value from the annotation (@proxy https://...) if present, otherwise the global ProxyOptions.Host. The path is not the function name directly — it is the actual path the client used to reach the endpoint (which, by default, is derived from the function name).

Walkthrough: what does the basic example call?

sql
sql
create function get_external_data()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function get_external_data() is 'HTTP GET
+@proxy';

With the default configuration below:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com"
+    }
+  }
+}
  1. The function is exposed at its default endpoint: GET /api/get-external-data/.

  2. A client calls GET /api/get-external-data/?id=42 on your NpgsqlRest server.

  3. NpgsqlRest forwards it to the host with the same path and query appended:

    GET https://api.example.com/api/get-external-data/?id=42

  4. The upstream response is streamed straight back to the client (passthrough — no database connection is opened).

So @proxy alone is a mirror: it forwards each request to the same path on a different host. To forward to a different path, either change the endpoint path (HTTP GET /v1/data, which then forwards to https://api.example.com/v1/data) or use an absolute/relative URL in the annotation (see URL Resolution below).

Host is required

If neither the annotation nor ProxyOptions.Host provides a host, the endpoint responds with 500 and "Proxy host is not configured." The bare @proxy form only works when ProxyOptions.Host is set.

Automatic parameters are forwarded too

On top of the verbatim path and query, any server-filled parameters — user claims, IP address, HTTP Custom Type fields, and resolved-parameter expressions — are forwarded to the upstream in the endpoint's native shape (query string or JSON body, per RequestParamType). See Automatic Parameter Forwarding.

Basic Usage

Passthrough Mode

For simple proxy forwarding without database processing:

sql
sql
create function get_external_data()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function get_external_data() is 'HTTP GET
+@proxy';

Equivalent as a SQL file endpoint (sql/get-external-data.sql):

sql
sql
-- HTTP GET
+-- @proxy
+select;

When the function has no proxy response parameters, the upstream response is returned directly to the client without opening a database connection. The function body itself (select;) is never executed — it exists only to declare the endpoint and its annotations.

Transform Mode

To process the upstream response in PostgreSQL, add one or more proxy response parameters to the function. Their presence is what switches the endpoint from passthrough into transform mode:

sql
sql
create function get_and_transform(
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_headers json default null,
+    _proxy_content_type text default null,
+    _proxy_success boolean default null,
+    _proxy_error_message text default null
+)
+returns json
+language plpgsql as $$
+begin
+    if not _proxy_success then
+        return json_build_object('error', _proxy_error_message);
+    end if;
+    return json_build_object(
+        'status', _proxy_status_code,
+        'data', _proxy_body::json
+    );
+end;
+$$;
+
+comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
+@proxy';

In transform mode the order of operations is: forward the request to the upstream → collect the response → execute the PostgreSQL function with the response values bound to its proxy parameters → return the function's result to the client. The function output (not the raw upstream response) is what the client receives.

Proxy Annotations

Basic Proxy with Default Host

Uses the host from ProxyOptions.Host configuration:

sql
sql
-- function form
+comment on function my_func() is '@proxy';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy
+select;

Proxy with Custom Host

Override the default host:

sql
sql
-- function form
+comment on function my_func() is '@proxy https://api.example.com';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy https://api.example.com
+select;

Proxy with Custom HTTP Method

Override the upstream HTTP method (uses default host from configuration):

sql
sql
-- function form
+comment on function my_func() is '@proxy POST';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST
+select;

Combined Method and Host

Specify both HTTP method and host:

sql
sql
-- function form
+comment on function my_func() is '@proxy POST https://api.example.com';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST https://api.example.com
+select;

Self-Referencing Proxy (Relative Path)

Use a relative path starting with / to proxy to another endpoint on the same server:

sql
sql
-- function form
+comment on function my_func() is '@proxy POST /api/data-source';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST /api/data-source
+select;

Self-referencing calls bypass the HTTP stack entirely — the target endpoint handler is invoked directly in-process with zero network overhead.

URL Resolution

The proxy target host is resolved with the following priority:

  1. Annotation URL — if the annotation includes a URL (absolute or relative), it is used. The global ProxyOptions.Host is ignored.
  2. Global ProxyOptions.Host — used only when the annotation has no URL (e.g., @proxy or @proxy POST).

In every case except a relative self-call, the incoming request path and query string are then appended to the resolved host (host + request path + query). For relative self-calls (host starting with /), the annotation path is the full target and the incoming path is not appended.

AnnotationProxyOptions.HostResolved TargetSelf-Call?
@proxyhttps://api.example.comhttps://api.example.com + request pathNo
@proxy POSThttps://api.example.comhttps://api.example.com + request pathNo
@proxy https://other.comhttps://api.example.comhttps://other.com + request pathNo
@proxy POST /api/datahttps://api.example.com/api/data (internal)Yes
@proxy /api/datahttps://api.example.com/api/data (internal)Yes
@proxy /api/datanull/api/data (internal)Yes

Important

A relative path in the annotation (starting with /) always creates a self-referencing internal call, regardless of the ProxyOptions.Host setting. The global host is never prepended to relative paths.

Response Parameters

When the PostgreSQL function has parameters whose names match the configured proxy parameter names, the upstream response data is bound to them after the request returns:

Parameter NameTypeDescription
_proxy_status_codeint or textHTTP status code from upstream (e.g., 200, 404). Bound as text if the parameter is declared text/varchar, otherwise as an integer.
_proxy_bodytextResponse body content. null if empty.
_proxy_headersjsonResponse headers as a JSON object.
_proxy_content_typetextContent-Type header value.
_proxy_successbooleantrue for 2xx status codes.
_proxy_error_messagetextError message if the request failed (timeout, connection error, etc.); null otherwise.

How parameters are mapped

  • Matched by name, not position. Each parameter is identified by its name (case-insensitive), so order and placement in the signature are irrelevant. You can mix proxy parameters freely with regular parameters.
  • Declare only the ones you need. None of the six are required — include just the parameters your function uses. The presence of any one of them is what puts the endpoint in transform mode.
  • Not read from the request. Proxy response parameters are never supplied by the caller — NpgsqlRest sets a placeholder before the upstream call and overwrites it with the real value afterwards, then passes it to the function. Declaring them with default null (as in the examples) is the recommended convention: it documents intent and keeps the function directly callable from SQL.
  • Regular parameters work as usual. Any non-proxy parameter (e.g. city, report_id) is bound from the request (query string, body, route) exactly like a normal endpoint, and is available to the function. For @proxy, those request values are also forwarded to the upstream as part of the forwarded path/query/body.
  • The names are configurable. Override them under ProxyOptions (ResponseStatusCodeParameter, ResponseBodyParameter, ResponseHeadersParameter, ResponseContentTypeParameter, ResponseSuccessParameter, ResponseErrorMessageParameter) if the defaults clash with your own parameter names.

Custom parameter names

For example, to drop the _proxy_ prefix, configure the names you want:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "ResponseStatusCodeParameter": "status",
+      "ResponseBodyParameter": "body",
+      "ResponseSuccessParameter": "ok"
+    }
+  }
+}

The function then uses those names instead of the defaults:

sql
sql
create function get_and_transform(
+    status int default null,
+    body text default null,
+    ok boolean default null
+)
+returns json
+language plpgsql as $$
+begin
+    if not ok then
+        return json_build_object('error', 'upstream failed');
+    end if;
+    return json_build_object('status', status, 'data', body::json);
+end;
+$$;
+
+comment on function get_and_transform(int, text, boolean) is 'HTTP GET
+@proxy';

Examples

API Gateway Pattern

Forward requests to different microservices:

sql
sql
-- Users service
+create function users_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function users_api() is 'HTTP GET /api/users
+@proxy https://users-service.internal:8080';
+
+-- Orders service
+create function orders_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function orders_api() is 'HTTP GET /api/orders
+@proxy https://orders-service.internal:8080';

Data Enrichment

Fetch external data and enrich it with local data:

sql
sql
create function get_enriched_weather(
+    city text,
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null
+)
+returns json
+language plpgsql as $$
+declare
+    local_data json;
+begin
+    -- Get local city preferences
+    select json_build_object('favorite', is_favorite, 'notes', notes)
+    into local_data
+    from user_city_preferences
+    where city_name = city;
+
+    if not _proxy_success then
+        return json_build_object('error', 'Weather API unavailable');
+    end if;
+
+    return json_build_object(
+        'weather', _proxy_body::json,
+        'local', coalesce(local_data, '{}'::json)
+    );
+end;
+$$;
+
+comment on function get_enriched_weather(text, int, text, boolean) is 'HTTP GET /v1/current
+@proxy https://api.weather.com';

A client request to GET /v1/current?city=London is forwarded to https://api.weather.com/v1/current?city=London — the endpoint path and the incoming query string are appended to the host. The city value also populates the city parameter so it is available to the function for the local lookup.

No URL templating

The annotation host is used literally — there is no {city}-style substitution. Dynamic values reach the upstream only through the forwarded request path and query string (and, optionally, user_parameters). Do not put placeholders like ?city={city} in the host; they are forwarded verbatim.

Authenticated Proxy with User Context

Use NpgsqlRest as an authenticating gateway: it verifies the caller, then forwards the request to a protected upstream service along with the caller's identity as HTTP headers. This is a passthrough proxy — the function does no work, so it needs no body and no proxy parameters:

sql
sql
create function secure_api_call()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function secure_api_call() is 'HTTP GET
+@authorize
+@user_context
+@proxy https://secure-api.internal/data';

On a proxy endpoint, @user_context adds the caller's identity to the upstream request as HTTP headers: the claims JSON, the client IP, and one header per entry in ContextKeyClaimsMapping (e.g. request.user_id, request.user_name, request.user_roles). The upstream can trust these headers because the request was authenticated by the gateway, so it never re-authenticates.

In this passthrough example the function never runs, so header forwarding is the only effect. In transform mode the function does run, and there @user_context additionally sets the usual PostgreSQL session context for it — so the function can read the caller's identity while the upstream still receives the headers.

Proxy with User Parameters

Forward user claims to the upstream as query string parameters:

sql
sql
create function proxy_with_user(
+    _user_id text default null,    -- filled from the caller's user-id claim by @user_params,
+                                   -- then forwarded to the upstream as ?userId=...
+    _proxy_body text default null
+)
+returns json language plpgsql as $$
+begin
+    -- _user_id is sent to the upstream automatically; the function reads only the response here.
+    return _proxy_body::json;
+end;
+$$;
+
+comment on function proxy_with_user(text, text) is 'HTTP GET
+@authorize
+@user_params
+@proxy https://api.internal/user-data';

With @user_params, _user_id is populated from the authenticated user's claim (not from the request) and appended to the upstream URL using the camelCase form of the parameter name. A call to GET /api/proxy-with-user/ is forwarded to:

GET https://api.internal/user-data/api/proxy-with-user/?userId=<claim value>

The function may also read _user_id directly if it needs the value — but it doesn't have to for the value to reach the upstream.

Configuration

Enable proxy functionality in your configuration:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "DefaultTimeout": "30 seconds"
+    }
+  }
+}

See Proxy Options for complete configuration reference.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/query-string-null-handling.html b/annotations/query-string-null-handling.html new file mode 100644 index 000000000..9bdec289e --- /dev/null +++ b/annotations/query-string-null-handling.html @@ -0,0 +1,91 @@ + + + + + + QUERY_STRING_NULL_HANDLING Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

QUERY_STRING_NULL_HANDLING

Also known as

query_null_handling, query_string_null, query_null (with or without @ prefix)

Controls how clients can pass NULL values to PostgreSQL function parameters via query string.

Since query strings can only contain text values, there's no native way to represent SQL NULL. This annotation defines what query string value should be interpreted as NULL.

Syntax

code
@query_null <mode>
+@query_string_null_handling <mode>

Values

ValueAliasResult
emptyempty_stringEmpty query string value (?param=) is interpreted as NULL
nullnull_literalLiteral string "null" (?param=null) is interpreted as NULL
ignoreNo special NULL handling - values are passed as-is (default)

Behavior Explained

Ignore Mode (Default)

By default (ignore), no special NULL handling is applied. Query string values are passed as-is to the function:

code
GET /api/func/?t=         →   _t = '' (empty string)
+GET /api/func/?t=null     →   _t = 'null' (literal string "null")
+GET /api/func/?t=hello    →   _t = 'hello'

If the parameter is not provided at all, the function receives NULL:

code
GET /api/func/            →   _t = NULL (parameter not provided)

EmptyString Mode

When set to empty_string (or empty), an empty query string value is interpreted as SQL NULL:

code
GET /api/func/?t=         →   _t = NULL
+GET /api/func/?t=null     →   _t = 'null' (literal string)
+GET /api/func/?t=hello    →   _t = 'hello'

This allows clients to explicitly pass NULL by providing an empty value.

NullLiteral Mode

When set to null_literal (or null), the literal string "null" (case-insensitive) is interpreted as SQL NULL:

code
GET /api/func/?t=null     →   _t = NULL
+GET /api/func/?t=NULL     →   _t = NULL
+GET /api/func/?t=         →   _t = '' (empty string)
+GET /api/func/?t=hello    →   _t = 'hello'

This allows clients to explicitly pass NULL by providing the string "null".

Examples

Using Empty String for NULL

sql
sql
create function get_nullable_param(_t text)
+returns text
+language sql
+begin atomic;
+select _t;
+end;
+
+comment on function get_nullable_param(text) is '
+@query_string_null_handling empty_string
+';

Equivalent as a SQL file endpoint (sql/get-nullable-param.sql):

sql
sql
/*
+HTTP GET
+@query_string_null_handling empty_string
+@param $1 t
+*/
+select $1;
RequestParameter Value
GET /api/get-nullable-param/?t=_t = NULL
GET /api/get-nullable-param/?t=hello_t = 'hello'

Using "null" String for NULL

sql
sql
create function get_data(_filter text)
+returns text
+language sql
+begin atomic;
+select _filter;
+end;
+
+comment on function get_data(text) is '
+@query_string_null_handling null_literal
+';
RequestParameter Value
GET /api/get-data/?filter=null_filter = NULL
GET /api/get-data/?filter=_filter = '' (empty string)
GET /api/get-data/?filter=active_filter = 'active'

Default Behavior (No Special Handling)

sql
sql
create function search(_query text)
+returns text
+language sql
+begin atomic;
+select _query;
+end;
+
+comment on function search(text) is '
+@query_string_null_handling ignore
+';
RequestParameter Value
GET /api/search/?query=_query = '' (empty string)
GET /api/search/?query=null_query = 'null' (literal string)
GET /api/search/_query = NULL (parameter omitted)

Path Parameters

New in 3.8.0

Path parameter interaction with null_literal mode was added in version 3.8.0.

The null_literal mode also works with path parameters. When combined with optional path parameters, you can pass NULL via the literal string "null" in the URL path:

sql
sql
create function get_item(p_id int default null)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id}
+query_string_null_handling null_literal
+';
  • GET /items/5p_id = 5
  • GET /items/nullp_id = NULL

Configuration Default

You can set the default behavior for all endpoints in appsettings.json:

json
json
{
+  "NpgsqlRest": {
+    "QueryStringNullHandling": "EmptyString"
+  }
+}

Available values: Ignore (default), EmptyString, NullLiteral.

This sets the default for all endpoints, which can then be overridden per-endpoint using comment annotations.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/rate-limiter-policy.html b/annotations/rate-limiter-policy.html new file mode 100644 index 000000000..be4835d97 --- /dev/null +++ b/annotations/rate-limiter-policy.html @@ -0,0 +1,104 @@ + + + + + + RATE_LIMITER_POLICY Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RATE_LIMITER_POLICY

Also known as

rate_limiter_policy, rate_limiter (with or without @ prefix)

Apply a rate limiting policy to the endpoint. The policy name must match a policy configured in the Rate Limiter configuration.

Syntax

code
@rate_limiter_policy <policy-name>
+@rate_limiter <policy-name>

Examples

Fixed Window Policy

Apply a fixed window rate limiter to an API endpoint:

sql
sql
comment on function public_api() is
+'HTTP GET
+@rate_limiter_policy fixed';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "fixed": {
+        "Type": "FixedWindow",
+        "Enabled": true,
+        "PermitLimit": 100,
+        "WindowSeconds": 60
+      }
+    }
+  }
+}

Token Bucket Policy

Apply a token bucket rate limiter to an expensive operation:

sql
sql
comment on function expensive_operation() is
+'HTTP POST
+@rate_limiter bucket';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "bucket": {
+        "Type": "TokenBucket",
+        "Enabled": true,
+        "TokenLimit": 10,
+        "ReplenishmentPeriodSeconds": 60
+      }
+    }
+  }
+}

Combined with Authorization

Apply rate limiting to an authenticated endpoint:

sql
sql
comment on function protected_resource() is
+'HTTP GET
+@authorize
+@rate_limiter authenticated_limit';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "authenticated_limit": {
+        "Type": "SlidingWindow",
+        "Enabled": true,
+        "PermitLimit": 1000,
+        "WindowSeconds": 60,
+        "SegmentsPerWindow": 6
+      }
+    }
+  }
+}

Per-User Rate Limiting

Apply per-user rate limiting using a partitioned policy:

sql
sql
comment on function user_dashboard() is
+'HTTP GET
+@authorize
+@rate_limiter per_user';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "per_user": {
+        "Type": "FixedWindow",
+        "Enabled": true,
+        "PermitLimit": 100,
+        "WindowSeconds": 60,
+        "Partition": {
+          "Sources": [
+            { "Type": "Claim", "Name": "name_identifier" },
+            { "Type": "IpAddress" },
+            { "Type": "Static", "Value": "anonymous" }
+          ]
+        }
+      }
+    }
+  }
+}

Each authenticated user gets their own quota instead of all users sharing one global bucket.

Behavior

  • The policy name must match a key in the Policies dictionary defined in the Rate Limiter configuration
  • If the policy name doesn't match any configured policy, rate limiting won't be applied
  • Returns 429 Too Many Requests when limit exceeded (status code and message are configurable)
  • Policy defines requests per time window based on the policy type (FixedWindow, SlidingWindow, TokenBucket, or Concurrency)
  • Policies with a Partition block bucket requests per-user / per-IP / per-header instead of using a single global bucket
  • The policy applies to HTTP requests hitting this endpoint's route. It is not consulted when the endpoint is invoked in-process — via HTTP client type self-calls, proxy self-calls, or MCP tools/call (use McpOptions.RateLimiterPolicy for agent traffic). See Rate Limiting Scope

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/raw.html b/annotations/raw.html new file mode 100644 index 000000000..a3a41d32e --- /dev/null +++ b/annotations/raw.html @@ -0,0 +1,124 @@ + + + + + + RAW Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RAW

Also known as

raw_mode, raw_results (with or without @ prefix)

Return raw text output instead of JSON formatting.

Syntax

code
@raw

Examples

Basic Raw Output

sql
sql
create function get_plain_text()
+returns text
+language sql
+begin atomic;
+select 'Hello, World!';
+end;
+
+comment on function get_plain_text() is
+'HTTP GET
+@raw';

Equivalent as a SQL file endpoint (sql/get-plain-text.sql):

sql
sql
-- HTTP GET
+-- @raw
+select 'Hello, World!';

Response: Hello, World! (plain text, no JSON wrapping)

Raw with Multiple Columns

sql
sql
create function get_user_info()
+returns table(name text, email text)
+language sql
+begin atomic;
+select name, email from users limit 1;
+end;
+
+comment on function get_user_info() is
+'HTTP GET
+@raw';

Response: JohnDoe john@example.com (values concatenated)

CSV Export

sql
sql
create function export_users_csv()
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_users_csv() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \n
+@columns
+Content-Type: text/csv';

Response:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

Tab-Separated Values

sql
sql
create function export_tsv()
+returns table(col1 text, col2 text, col3 text)
+language sql
+begin atomic;
+select * from my_table;
+end;
+
+comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \t
+@new_line \n
+Content-Type: text/tab-separated-values';

Pipe-Delimited Format

sql
sql
create function export_data()
+returns table(a text, b text, c text)
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_data() is
+'HTTP GET
+@raw
+@separator |
+@new_line \n';

Response:

code
value1|value2|value3
+value4|value5|value6

Download as File

sql
sql
create function download_report()
+returns table(data text)
+language sql
+begin atomic;
+...;
+end;
+
+comment on function download_report() is
+'HTTP GET
+@raw
+Content-Type: text/csv
+Content-Disposition: attachment; filename="report.csv"';

Browser will download the response as a file.

Dynamic CSV Download

Use {param_name} template syntax in headers for dynamic content type and filename:

sql
sql
create function export_data(_type text, _file text)
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_data(text, text) is
+'HTTP GET
+@raw
+@separator ,
+@new_line \n
+@columns
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}';

Request: GET /api/export-data?_type=text/csv&_file=users.csv

Response headers:

code
Content-Type: text/csv
+Content-Disposition: attachment; filename=users.csv

Response body:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

Behavior

  • Returns content as plain text instead of JSON
  • Multiple columns are concatenated (use separator to delimit)
  • Multiple rows are concatenated (use new_line to delimit)
  • Use with Content-Type header to set appropriate media type

Comments

+ + + + \ No newline at end of file diff --git a/annotations/request-headers-mode.html b/annotations/request-headers-mode.html new file mode 100644 index 000000000..9e624324f --- /dev/null +++ b/annotations/request-headers-mode.html @@ -0,0 +1,57 @@ + + + + + + REQUEST_HEADERS_MODE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

REQUEST_HEADERS_MODE

Also known as

request_headers (with or without @ prefix)

Control how HTTP request headers are passed to the PostgreSQL function.

Syntax

code
@request_headers_mode <mode>
+@request_headers <mode>

Values

ValueDescription
ignoreDon't pass request headers to the function
contextSet headers as PostgreSQL context variable via set_config()
parameterPass headers to a function parameter as JSON

Examples

Ignore Headers

sql
sql
comment on function simple_func() is
+'HTTP GET
+@request_headers_mode ignore';

Pass as Context Variable

sql
sql
comment on function context_aware_func() is
+'HTTP GET
+@request_headers_mode context';

Headers accessible via: current_setting('request.headers', true)

Pass as Parameter

sql
sql
create function with_headers(_data text, _headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function with_headers(text, json) is
+'HTTP POST
+@request_headers_mode parameter';

Equivalent as a SQL file endpoint (sql/with-headers.sql):

sql
sql
/*
+HTTP POST
+@request_headers_mode parameter
+@param $1 data
+@param $2 headers json
+*/
+select json_build_object('data', $1, 'headers', $2);

Behavior

  • Default mode is configured in NpgsqlRest.RequestHeadersMode
  • context mode uses the key from RequestHeadersContextKey setting
  • parameter mode uses the parameter name from RequestHeadersParameterName setting

Comments

+ + + + \ No newline at end of file diff --git a/annotations/request-headers-parameter-name.html b/annotations/request-headers-parameter-name.html new file mode 100644 index 000000000..73403254b --- /dev/null +++ b/annotations/request-headers-parameter-name.html @@ -0,0 +1,63 @@ + + + + + + REQUEST_HEADERS_PARAMETER_NAME Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

REQUEST_HEADERS_PARAMETER_NAME

Also known as

request_headers_param_name (with or without @ prefix)

Set the parameter name that receives request headers when using parameter mode.

Syntax

code
@request_headers_parameter_name <param-name>

Examples

Custom Parameter Name

sql
sql
create function process_request(_data text, _req_headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_request(text, json) is
+'HTTP POST
+@request_headers_mode parameter
+@request_headers_parameter_name _req_headers';

Equivalent as a SQL file endpoint (sql/process-request.sql):

sql
sql
/*
+HTTP POST
+@request_headers_mode parameter
+@request_headers_parameter_name req_headers
+@param $1 data
+@param $2 req_headers json
+*/
+select json_build_object('data', $1, 'headers', $2);

Default Parameter Name

By default, uses _headers as the parameter name:

sql
sql
create function my_func(_input text, _headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function my_func(text, json) is
+'HTTP POST
+@request_headers_mode parameter';

Behavior

  • Only applies when request_headers_mode is parameter
  • Parameter must have a default value (typically null)
  • Parameter type should be json or text
  • Headers are passed as JSON object

Comments

+ + + + \ No newline at end of file diff --git a/annotations/request-param-type.html b/annotations/request-param-type.html new file mode 100644 index 000000000..4e27ea922 --- /dev/null +++ b/annotations/request-param-type.html @@ -0,0 +1,82 @@ + + + + + + REQUEST_PARAM_TYPE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

REQUEST_PARAM_TYPE

Also known as

param_type (with or without @ prefix)

The bare value keywords also work as standalone annotations: query_string / query (same as @request_param_type query_string) and body_json / body (same as @request_param_type body_json).

Control how parameters are transmitted to the endpoint - via query string or request body.

Syntax

code
@request_param_type <type>
+@param_type <type>

type: query_string, query, body, body_json

Values

ValueDescription
query_stringParameters from URL query string
querySame as query_string
body_jsonParameters from JSON request body
bodySame as body_json

Default Behavior

When not specified:

  • GET and DELETE methods use query string
  • All other methods use JSON body

Examples

Force Query String Parameters

sql
sql
create function search_users(_name text, _active bool)
+returns setof users
+language sql
+begin atomic;
+select * from users where name ilike '%' || _name || '%' and active = _active;
+end;
+
+comment on function search_users(text, bool) is
+'HTTP GET
+@request_param_type query_string';

Equivalent as a SQL file endpoint (sql/search-users.sql):

sql
sql
/*
+HTTP GET
+@request_param_type query_string
+@param $1 name
+@param $2 active boolean
+*/
+select * from users where name ilike '%' || $1 || '%' and active = $2;

Request: GET /api/search-users?_name=john&_active=true

Force JSON Body Parameters

sql
sql
create function get_filtered_data(_filters text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_filtered_data(text) is
+'HTTP GET
+@request_param_type body_json';

Request:

http
http
GET /api/get-filtered-data
+Content-Type: application/json
+
+{"_filters": "status=active"}

Short Form Keywords

sql
sql
-- Using '@param_type' instead of '@request_param_type'
+comment on function func1(text) is
+'HTTP
+@param_type query';
+
+-- Using 'BODY' (case-insensitive)
+comment on function func2(text) is
+'HTTP
+@param_type BODY';

POST with Query String

Override the default body behavior for POST:

sql
sql
create function quick_action(_id int)
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function quick_action(int) is
+'HTTP POST
+@param_type query_string';

Request: POST /api/quick-action?_id=123

Behavior

When parameter type doesn't match the request format, the endpoint returns 404 Not Found:

  • Endpoint configured for query_string but receives JSON body → 404
  • Endpoint configured for body_json but receives query parameters → 404

Comments

+ + + + \ No newline at end of file diff --git a/annotations/resolved-parameters.html b/annotations/resolved-parameters.html new file mode 100644 index 000000000..875fa9230 --- /dev/null +++ b/annotations/resolved-parameters.html @@ -0,0 +1,51 @@ + + + + + + Resolved Parameters | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Resolved Parameters

A resolved parameter has its value computed server-side from a SQL expression at request time, instead of being supplied by the client. You declare it with a param_name = <sql> comment annotation where param_name matches a real routine parameter.

This is how you inject a value the caller must not provide or see — a DB-stored API token, a secret, or anything derived in SQL — into the routine and into {name} placeholder substitution (response headers, custom parameters, and HTTP custom type URL/headers/body).

sql
sql
comment on function get_secure_data(_user_id int, _req my_api_type, _token text) is '
+HTTP GET
+_token = select api_token from user_tokens where user_id = {_user_id}
+';

The client calls GET /api/get-secure-data/?user_id=42; the server runs select api_token from user_tokens where user_id = 42, binds the result to _token, and uses it wherever {_token} appears. The token never leaves the server, and a client &token=hacked is ignored.

Syntax

code
<parameter_name> = <sql expression>
  • parameter_name must match an actual routine parameter (by its PostgreSQL name). If the key doesn't match a parameter, it's treated as a custom parameter instead.
  • <sql expression> is any scalar SQL expression — a column read, a subquery, a function call, concatenation, coalesce, etc. It is run with ExecuteScalar, so it must return a single value.
  • It may contain {name} placeholders referencing other parameters (resolved case-insensitively, by actual or converted name), which are passed as safe $N parameters — never string-concatenated.

Behavior

  • Server-side only. The resolved value cannot be overridden by client input. Even if the client sends &token=hacked, the SQL-resolved value wins.
  • Runs before the call. Resolved expressions execute before the outbound HTTP-type request and before placeholder substitution, so the resolved value is available everywhere the parameter is referenced.
  • NULL handling. If the expression returns no rows or NULL, the parameter becomes SQL NULL (an empty string in placeholder substitution).
  • SQL-injection safe. {name} placeholders inside the expression are converted to positional $N parameters.
  • Sequential. Multiple resolved expressions run one-by-one on the same connection, in annotation order. A later expression can reference an earlier resolved parameter.
  • Works with claims. Expressions can reference parameters auto-filled from JWT claims via user parameters, enabling fully zero-input authenticated calls.
  • Hidden from callers. A resolved parameter is excluded from the client input surface and from MCP tools/list input schemas — an agent or caller can neither see nor set it.

Examples

Inject a DB-stored API token into an outbound call

sql
sql
comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {_api_key}';
+
+comment on function get_weather(_city text, _api weather_api, _api_key text) is '
+HTTP GET
+_api_key = select token from weather_tokens order by fetched_at desc limit 1
+';

The caller supplies only _city. _api_key is read from weather_tokens per request, so it always reflects the latest token — useful when a separate refresh/login routine periodically writes a new token into that table. (Pair it with pg_cron or a refresh routine to keep the row current; a tight-expiry variant: … where expires_at > now() order by fetched_at desc limit 1.)

Multiple resolved parameters

sql
sql
comment on function my_func(_name text, _req my_type, _token text, _api_key text) is '
+_token   = select api_token from tokens where user_name = {_name}
+_api_key = select ''static-key-'' || api_token from tokens where user_name = {_name}
+';

Resolved value in URL, header, and body

A resolved value participates in every placeholder location of an HTTP custom type:

code
-- URL:    GET https://api.example.com/resource/{_secret_path}
+-- Header: Authorization: Bearer {_token}
+-- Body:   {"token": "{_token}", "data": "{_payload}"}

How it compares to the other {name} sources

A {name} placeholder can be filled three ways — pick by where the value comes from:

SourceAnnotationUse when
Request parameter(the parameter itself)the caller provides the value
Environment variableNpgsqlRest:AvailableEnvVars allowlista static, per-deployment value (API key set at deploy, server name)
Resolved parameter (this page)param = <sql>a value computed/looked-up server-side per request (DB-stored token, claim-derived secret) — must not come from the client

Comments

+ + + + \ No newline at end of file diff --git a/annotations/response-headers.html b/annotations/response-headers.html new file mode 100644 index 000000000..9d048712d --- /dev/null +++ b/annotations/response-headers.html @@ -0,0 +1,117 @@ + + + + + + Response Headers Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

Response Headers

Set custom HTTP response headers for the endpoint.

Syntax

code
<Header-Name>: <value>

Response headers use standard HTTP header format with a colon separator. Header names are case-insensitive.

Examples

Set Content-Type

sql
sql
create function get_html_page()
+returns text
+language sql
+begin atomic;
+select '<html><body><h1>Hello</h1></body></html>';
+end;
+
+comment on function get_html_page() is
+'HTTP GET
+Content-Type: text/html';

Equivalent as a SQL file endpoint (sql/get-html-page.sql):

sql
sql
/*
+HTTP GET
+Content-Type: text/html
+*/
+select '<html><body><h1>Hello</h1></body></html>';

Response includes: Content-Type: text/html

Multiple Headers

sql
sql
create function get_api_data()
+returns json
+language sql
+begin atomic;
+select '{"status": "ok"}'::json;
+end;
+
+comment on function get_api_data() is
+'HTTP GET
+Content-Type: application/json
+Cache-Control: no-store
+X-Custom-Header: custom-value';

Response includes all three headers.

Multi-Value Headers

Headers with the same name are combined:

sql
sql
create function set_cookies()
+returns text
+language sql
+begin atomic;
+select 'OK';
+end;
+
+comment on function set_cookies() is
+'HTTP GET
+Set-Cookie: session=abc123
+Set-Cookie: theme=dark
+Set-Cookie: lang=en';

Response includes: Set-Cookie: session=abc123, theme=dark, lang=en

Cache Control

sql
sql
create function get_static_config()
+returns json
+language sql
+begin atomic;
+select config from app_config where id = 1;
+end;
+
+comment on function get_static_config() is
+'HTTP GET
+Cache-Control: public, max-age=3600';

Clients can cache the response for 1 hour.

Combined with Other Annotations

sql
sql
create function export_report()
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_report() is
+'HTTP GET
+@authorize manager
+Content-Type: text/csv
+Content-Disposition: attachment; filename="report.csv"
+Cache-Control: no-cache';

Dynamic Headers from Parameters

Header values can include parameter values using the {param_name} template syntax. The matching and substitution rules (case-sensitivity, NULL handling, etc.) are shared across annotations — see Parameter Value Substitution.

sql
sql
create function export_report(_type text, _file text)
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_report(text, text) is
+'HTTP GET
+@authorize manager
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}
+Cache-Control: no-cache';

Request: GET /api/export-report?_type=text/csv&_file=report.csv

Response headers:

code
Content-Type: text/csv
+Content-Disposition: attachment; filename=report.csv

CORS Headers

sql
sql
create function cors_endpoint()
+returns json
+language sql
+begin atomic;
+select '{}'::json;
+end;
+
+comment on function cors_endpoint() is
+'HTTP GET
+Access-Control-Allow-Origin: *
+Access-Control-Allow-Methods: GET, POST
+Access-Control-Allow-Headers: Content-Type';

Note: To configure CORS centrally (origins, methods, credentials, preflight), use the CORS configuration instead.

Common Headers

HeaderPurpose
Content-TypeResponse media type
Cache-ControlCaching directives
Content-DispositionDownload filename
X-*Custom application headers
Set-CookieSet cookies
  • RAW - Return raw text output
  • CACHED - Server-side caching

Comments

+ + + + \ No newline at end of file diff --git a/annotations/response-null-handling.html b/annotations/response-null-handling.html new file mode 100644 index 000000000..bd8985f58 --- /dev/null +++ b/annotations/response-null-handling.html @@ -0,0 +1,49 @@ + + + + + + RESPONSE_NULL_HANDLING Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RESPONSE_NULL_HANDLING

Also known as

response_null, text_response_null_handling (with or without @ prefix)

Control how NULL results are returned in plain text responses when the execution returns NULL from the database.

Syntax

code
@response_null <mode>
+@response_null_handling <mode>
+@text_response_null_handling <mode>

Values

ValueDescription
empty_stringReturns an empty string response with status code 200 OK (default)
null_literalReturns a string literal "NULL" with status code 200 OK
no_content or 204_no_contentReturns status code 204 NO CONTENT

Examples

Return Empty String for NULL

sql
sql
comment on function get_value(_id int) is
+'HTTP GET
+@response_null empty_string';

If result is NULL → Response body: ""

Return 204 for NULL

sql
sql
comment on function find_record(_id int) is
+'HTTP GET
+@response_null 204_no_content';

If result is NULL → HTTP 204 with no body

Return JSON null

sql
sql
comment on function get_optional(_key text) is
+'HTTP GET
+@response_null null_literal';

If result is NULL → Response body: null

Configuration Default

You can set the default behavior for all endpoints in appsettings.json:

json
json
{
+  "NpgsqlRest": {
+    "TextResponseNullHandling": "NoContent"
+  }
+}

Available values: EmptyString (default), NullLiteral, NoContent.

This sets the default for all endpoints, which can then be overridden per-endpoint using comment annotations.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/result-name.html b/annotations/result-name.html new file mode 100644 index 000000000..ba8a6a058 --- /dev/null +++ b/annotations/result-name.html @@ -0,0 +1,73 @@ + + + + + + RESULT_NAME Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RESULT_NAME

Rename the default result keys (result1, result2, ...) in multi-command SQL file endpoints. This makes the response JSON more descriptive and easier to consume.

This annotation only applies to multi-command SQL file endpoints (files with multiple SQL statements separated by ;).

Available since version 3.12.0.

Syntax

code
@result <name>
+@result is <name>

The @result annotation is positional. It can be placed in two ways:

  1. Before the statement (on a separate line) — applies to the next statement below it
  2. Inline after the semicolon (on the same line) — applies to the statement on that line

This same placement rule applies to all positional annotations: @result, @single, and @skip.

Examples

Before Statement (Separate Line)

Place @result name on a line before the statement it applies to:

sql
sql
-- sql/dashboard.sql
+-- HTTP GET
+-- @result users
+SELECT id, name FROM users;
+-- @result orders
+SELECT id, total FROM orders;

Response:

json
json
{
+  "users": [{"id": 1, "name": "Alice"}, ...],
+  "orders": [{"id": 1, "total": 99.99}, ...]
+}

Inline After Semicolon (Same Line)

Place @result name after the semicolon on the same line as the statement:

sql
sql
-- sql/dashboard.sql
+-- HTTP GET
+SELECT id, name FROM users; -- @result users
+SELECT id, total FROM orders; -- @result orders

Produces the same result as the previous example.

"is" Style Syntax

The is keyword is optional:

sql
sql
-- These are equivalent:
+-- @result validate
+-- @result is validate

Naming Some Results

Commands without a @result annotation keep their default auto-generated key:

sql
sql
-- sql/process_order.sql
+-- HTTP POST
+-- @param $1 order_id
+-- @result validate
+select count(*) from orders where id = $1;
+update orders set status = 'processing' where id = $1;
+-- @result confirm
+select id, status from orders where id = $1;

POST /api/process-order with {"order_id": 42} returns:

json
json
{
+  "validate": [1],
+  "result2": 1,
+  "confirm": [{"id": 42, "status": "processing"}]
+}
  • First command renamed to validate
  • Second command keeps its default name result2 (no annotation)
  • Third command renamed to confirm

Naming All Results

sql
sql
-- sql/dashboard_data.sql
+-- HTTP GET
+-- @result users
+select count(*) from users;
+-- @result orders
+select count(*) from orders where created_at > now() - interval '24 hours';
+-- @result revenue
+select sum(total) from orders where created_at > now() - interval '24 hours';

Response:

json
json
{
+  "users": [{"count": 150}],
+  "orders": [{"count": 42}],
+  "revenue": [{"sum": 12500.00}]
+}

Behavior

  • Default result keys use the format result1, result2, result3, etc.
  • The prefix (result) is configurable via the ResultPrefix setting in SqlFileSource configuration
  • Commands returning rows produce a JSON array of row objects
  • Void commands (INSERT/UPDATE/DELETE without RETURNING) produce an integer (rows affected count)
  • Only results with a @result annotation are renamed; others keep their default key
  • This annotation has no effect on single-command SQL file endpoints (they return a plain array, not a keyed object)

Comments

+ + + + \ No newline at end of file diff --git a/annotations/retry-strategy.html b/annotations/retry-strategy.html new file mode 100644 index 000000000..8f2310df4 --- /dev/null +++ b/annotations/retry-strategy.html @@ -0,0 +1,73 @@ + + + + + + RETRY_STRATEGY Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RETRY_STRATEGY

Also known as

retry_strategy, retry (with or without @ prefix)

Assign a named retry strategy for handling transient database failures.

Syntax

code
@retry_strategy <strategy-name>
+@retry <strategy-name>

Or using custom parameter syntax:

code
@retry_strategy = <strategy-name>
+@retry = <strategy-name>

The strategy-name must match a strategy defined in CommandRetryOptions configuration.

Examples

Use Default Strategy

sql
sql
comment on function critical_operation() is
+'HTTP POST
+@retry_strategy default';

Use Named Strategy

sql
sql
comment on function important_query() is
+'HTTP GET
+@retry aggressive';

Combined with Timeout

sql
sql
comment on function long_running_task() is
+'HTTP POST
+@timeout 2min
+@retry_strategy default';

Behavior

  • References a retry strategy defined in CommandRetryOptions.Strategies configuration.
  • Automatically retries on transient failures when PostgreSQL returns error codes defined in the strategy.
  • Strategy defines:
    • Retry count: Number of elements in RetrySequenceSeconds array
    • Retry delays: Wait time between retries in seconds
    • Error codes: PostgreSQL error codes that trigger retries

Common Retry Scenarios

Error TypePostgreSQL CodesDescription
Serialization40001, 40P01Transaction conflicts, deadlocks
Connection08000, 08003, 08006Connection issues
Resources53300Too many connections
System57P03Cannot connect now

Configuration Example

Define strategies in configuration:

json
json
{
+  "CommandRetryOptions": {
+    "Enabled": true,
+    "DefaultStrategy": "default",
+    "Strategies": {
+      "default": {
+        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
+      },
+      "aggressive": {
+        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
+      },
+      "minimal": {
+        "RetrySequenceSeconds": [0, 1],
+        "ErrorCodes": ["40001", "40P01"]
+      }
+    }
+  }
+}

Then use in annotations:

sql
sql
-- Use aggressive retry for critical operations
+comment on function process_payment() is
+'HTTP POST
+@retry aggressive';
+
+-- Use minimal retry for fast queries
+comment on function quick_lookup() is
+'HTTP GET
+@retry minimal';

See Command Retry for complete configuration reference.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/returns.html b/annotations/returns.html new file mode 100644 index 000000000..ebff6fcf3 --- /dev/null +++ b/annotations/returns.html @@ -0,0 +1,70 @@ + + + + + + RETURNS Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

RETURNS

Skip the PostgreSQL Describe step for a statement and resolve return columns from a composite type instead. This is a positional annotation — it applies to the next statement below it.

Available since version 3.12.0.

Syntax

code
@returns <composite_type_name>
+@returns <scalar_type>
+@returns void

Supported values:

  • Composite type name — schema-qualified (e.g., public.my_type) or unqualified (e.g., my_type). Columns resolved from the type definition.
  • Scalar type — any built-in PostgreSQL type (e.g., integer, text, boolean, jsonb). Declares a single-column result. Only the first column from the query is used at runtime.
  • void — no columns, no results.

This annotation skips the PostgreSQL Describe step entirely for the annotated statement. The statement's SQL is never sent to PostgreSQL during startup.

When to Use

  • Composite types: when the statement references objects that don't exist at startup (e.g., temp tables created inside DO blocks)
  • Scalar types: when you want to declare a single typed return value and ignore extra columns
  • void: when the statement returns no results (e.g., INSERT, CREATE TEMP TABLE, set_config)

Example

sql
sql
-- HTTP GET
+-- @param $1 val1 text
+-- @param $2 val2 integer
+begin;
+select set_config('app.val1', $1, true); -- @skip
+select set_config('app.val2', $2::text, true); -- @skip
+do $$ begin
+    create temp table _result on commit drop as
+    select current_setting('app.val1') as val1,
+           current_setting('app.val2')::int as val2,
+           true as active;
+end; $$;
+-- @returns my_result_type
+-- @result data
+-- @single
+select * from _result;
+end;

Where my_result_type is defined as:

sql
sql
create type my_result_type as (
+    val1 text,
+    val2 integer,
+    active boolean
+);

Without @returns, the select * from _result statement fails at startup because the temp table doesn't exist yet. With @returns my_result_type, the columns are resolved from the composite type definition in pg_catalog.

Scalar Type

Declare a single typed column. Only the first column from the query is used at runtime — extra columns are ignored:

sql
sql
-- HTTP GET
+-- @returns integer
+select count(*) from users;

Returns: [42]

With @single, returns a bare scalar value:

sql
sql
-- HTTP GET
+-- @returns integer
+-- @single
+select count(*) from users;

Returns: 42

Supported scalar types: integer, text, boolean, jsonb, json, bigint, numeric, real, double precision, date, timestamp, timestamptz, uuid, bytea, and all other built-in PostgreSQL types.

Void Statements

Use @returns void to skip Describe for statements that return no results:

sql
sql
-- HTTP POST
+-- @param $1 key text
+-- @param $2 value text
+-- @returns void
+select set_config($1, $2, false);
+-- @result data
+select current_setting($1, true) as result;

The first statement's Describe is skipped entirely. In multi-command files, it produces a rows-affected count in the response. For single-command files, it makes the endpoint void (returns 204 No Content).

Behavior

  • The Describe step is skipped entirely for annotated statements — the SQL is never sent to PostgreSQL during startup
  • For composite types: the type must exist in the database at startup. If not found, an error is logged and the file is skipped or exits (depending on ErrorMode)
  • For void: the statement is treated as returning no columns (zero-column result)
  • No parameter type inference happens for the skipped statement — other statements in the same multi-command file provide parameter types
  • At runtime, the actual query result must match the declared type's column structure — mismatches may produce incorrect output
  • Can be combined with other positional annotations like @result, @single, @skip

@returns void vs @void

For single-command SQL files, @returns void has the same runtime effect as @void — both return 204 No Content. The difference: @returns void skips the Describe step (the SQL is never sent to PostgreSQL at startup), while @void still runs Describe and only changes the runtime response. Use @returns void when the statement would fail Describe (e.g., references a temp table). Use @void when Describe succeeds but you don't want any response.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/security-sensitive.html b/annotations/security-sensitive.html new file mode 100644 index 000000000..fee04eddd --- /dev/null +++ b/annotations/security-sensitive.html @@ -0,0 +1,78 @@ + + + + + + SECURITY_SENSITIVE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SECURITY_SENSITIVE

Also known as

sensitive, security (with or without @ prefix)

Mark endpoint as security-sensitive to obfuscate parameter values in logs.

Syntax

code
@sensitive

Examples

Password Change Endpoint

sql
sql
create function change_password(_old_password text, _new_password text)
+returns boolean
+language sql
+begin atomic;
+...;
+end;
+
+comment on function change_password(text, text) is
+'HTTP POST
+@authorize
+@sensitive';

Equivalent as a SQL file endpoint (sql/change-password.sql):

sql
sql
/*
+HTTP POST
+@authorize
+@sensitive
+@param $1 old_password
+@param $2 new_password
+*/
+update users
+set password_hash = crypt($2, gen_salt('bf'))
+where id = current_user_id()
+  and password_hash = crypt($1, password_hash)
+returning true;

Login Endpoint

sql
sql
create function authenticate(_username text, _password text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function authenticate(text, text) is
+'HTTP POST
+@login
+@sensitive';

Payment Processing

sql
sql
create function process_payment(_card_number text, _cvv text, _amount numeric)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_payment(text, text, numeric) is
+'HTTP POST
+@authorize
+@security_sensitive';

Behavior

  • Parameter values are replaced with *** in logs
  • Helps prevent sensitive data from appearing in log files
  • Applies to all parameters of the endpoint

Comments

+ + + + \ No newline at end of file diff --git a/annotations/separator.html b/annotations/separator.html new file mode 100644 index 000000000..b6a44ea52 --- /dev/null +++ b/annotations/separator.html @@ -0,0 +1,49 @@ + + + + + + SEPARATOR Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SEPARATOR

Also known as

raw_separator (with or without @ prefix)

Set the column separator for raw output mode.

Syntax

code
@separator <string>

Supports escape sequences: \t (tab), \n (newline), \\ (backslash)

Examples

Comma Separator (CSV)

sql
sql
comment on function export_csv() is
+'HTTP GET
+@raw
+@separator ,';

Tab Separator (TSV)

sql
sql
comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \t';

Pipe Separator

sql
sql
comment on function export_pipe() is
+'HTTP GET
+@raw
+@separator |';

Custom Separator

sql
sql
comment on function export_custom() is
+'HTTP GET
+@raw
+@separator ::';

Comments

+ + + + \ No newline at end of file diff --git a/annotations/single.html b/annotations/single.html new file mode 100644 index 000000000..136c7d3e8 --- /dev/null +++ b/annotations/single.html @@ -0,0 +1,64 @@ + + + + + + SINGLE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SINGLE

Also known as

single_record, single_result (with or without @ prefix)

Return a single record as a JSON object instead of a JSON array.

Syntax

code
@single

Default Behavior vs Single

By default, all endpoints return results as a JSON array, even when only one row is returned. With the @single annotation, the result is returned as a plain JSON object.

Without @single:

json
json
[{"id": 1, "name": "Alice"}]

With @single:

json
json
{"id": 1, "name": "Alice"}

Examples

PostgreSQL Function

sql
sql
create function get_user(_id int)
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users where id = _id;
+end;
+
+comment on function get_user(int) is 'HTTP GET /users/{_id}
+@single';

GET /users/1

json
json
{"id": 1, "name": "Alice", "email": "alice@example.com"}

SQL File

sql
sql
-- sql/get_user.sql
+-- HTTP GET
+-- @single
+-- @param $1 user_id
+SELECT id, name, email FROM users WHERE id = $1;

GET /api/get-user?user_id=1

json
json
{"id": 1, "name": "Alice", "email": "alice@example.com"}

Single Unnamed Column

When the result has a single unnamed column, the bare JSON value is returned:

sql
sql
-- sql/get_username.sql
+-- HTTP GET
+-- @single
+-- @param $1 user_id
+SELECT name FROM users WHERE id = $1;

GET /api/get-username?user_id=1"Alice"

Multi-Command Files (Positional)

In multi-command SQL files, @single is positional — it applies to the next statement below it:

sql
sql
-- sql/process_user.sql
+-- HTTP POST
+-- @param $1 id
+-- @single
+SELECT id, name FROM users WHERE id = $1;
+UPDATE orders SET status = 'done' WHERE id = $1;
+-- @single
+SELECT id, status FROM orders WHERE id = $1;

Result:

json
json
{
+  "result1": {"id": 1, "name": "alice"},
+  "result2": 1,
+  "result3": {"id": 1, "status": "done"}
+}
  • First and third commands return objects (@single above them)
  • Second command returns rows-affected count (void, unaffected)
  • Empty per-command @single results render as null

Behavior

  • Multi-column results return a JSON object (no array wrapping)
  • Single unnamed column results return a bare JSON value (e.g., "hello", 42)
  • If the query returns multiple rows, only the first row is returned
  • Works across all endpoint sources: functions, procedures, and SQL files
  • In multi-command files, @single is positional — applies to the next statement below
  • TypeScript client generates Promise<IResponse> instead of Promise<IResponse[]>

Empty Results

When the query returns no rows, the behavior depends on the @response_null annotation:

SettingResponse
empty_string (default)Empty response body
null_literalnull
no_contentHTTP 204 No Content

In multi-command files, empty per-command @single results render as null.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/skip.html b/annotations/skip.html new file mode 100644 index 000000000..570ad6d65 --- /dev/null +++ b/annotations/skip.html @@ -0,0 +1,67 @@ + + + + + + SKIP Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SKIP

Also known as

skip_result, no_result (with or without @ prefix)

Mark a command in a multi-command SQL file to be executed but excluded from the JSON response. The statement runs against the database, but its result is not included in the response object and it does not consume a result number.

Available since version 3.12.0.

Syntax

code
@skip

The @skip annotation is positional. It can be placed in two ways:

  1. Before the statement (on a separate line) — applies to the next statement below it
  2. Inline after the semicolon (on the same line) — applies to the statement on that line

This same placement rule applies to all positional annotations: @result, @single, and @skip.

Examples

Skipping a DO Block

sql
sql
-- sql/process_and_notify.sql
+-- HTTP POST
+-- @param $1 user_id
+-- @skip
+do $$ begin perform pg_notify('user_updated', 'event'); end; $$;
+-- @result data
+SELECT id, name FROM users WHERE id = $1;

Result: {"data": [{"id": 1, "name": "Alice"}]}

The DO block executes (sending the notification) but does not appear in the response.

Skipping Transaction Control

sql
sql
-- sql/transfer.sql
+-- HTTP POST
+-- @param $1 from_id
+-- @param $2 to_id
+-- @param $3 amount
+-- @skip
+BEGIN;
+UPDATE accounts SET balance = balance - $3 WHERE id = $1;
+UPDATE accounts SET balance = balance + $3 WHERE id = $2;
+-- @skip
+COMMIT;
+-- @result from_account
+SELECT id, balance FROM accounts WHERE id = $1;
+-- @result to_account
+SELECT id, balance FROM accounts WHERE id = $2;

Result:

json
json
{
+  "result1": 1,
+  "result2": 1,
+  "from_account": [{"id": 1, "balance": 900}],
+  "to_account": [{"id": 2, "balance": 1100}]
+}

The BEGIN and COMMIT statements are executed but excluded from the response. The two UPDATE results show rows-affected counts.

Inline Placement

sql
sql
-- sql/cleanup.sql
+-- HTTP POST
+-- @param $1 user_id
+DELETE FROM sessions WHERE user_id = $1; -- @skip
+-- @result user
+SELECT id, name FROM users WHERE id = $1;

Result: {"user": [{"id": 1, "name": "Alice"}]}

SkipNonQueryCommands Setting

The SkipNonQueryCommands setting (default: true) in SqlFileSource configuration automatically excludes non-query commands from the response. This covers transaction control (BEGIN, COMMIT, ROLLBACK, etc.), session commands (SET, RESET), DO blocks, and other non-query statements.

With SkipNonQueryCommands enabled (the default), you typically do not need @skip for these common cases. The @skip annotation is useful for:

  • Explicitly skipping DML commands (INSERT, UPDATE, DELETE) whose rows-affected count you do not want in the response
  • Skipping statements when SkipNonQueryCommands is set to false
  • Making skip intent explicit in the SQL file for documentation purposes

Behavior

  • The skipped statement is still executed against the database
  • Skipped commands do not consume a result number — subsequent commands are numbered as if the skipped command did not exist
  • Works with any statement type: SELECT, INSERT, UPDATE, DELETE, DO, transaction control, etc.
  • Only applies to multi-command SQL file endpoints

Comments

+ + + + \ No newline at end of file diff --git a/annotations/sse-events-level.html b/annotations/sse-events-level.html new file mode 100644 index 000000000..ff6a68ce0 --- /dev/null +++ b/annotations/sse-events-level.html @@ -0,0 +1,47 @@ + + + + + + SSE_EVENTS_LEVEL Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SSE_EVENTS_LEVEL

Also known as

sse_level (with or without @ prefix)

Set the minimum PostgreSQL notice level for Server-Sent Events.

Syntax

code
@sse_level <level>
+@sse_events_level <level>

Values

ValuePostgreSQL Level
infoINFO (default)
noticeNOTICE
warningWARNING

Examples

Info Level (All Messages)

sql
sql
comment on function verbose_process() is
+'HTTP POST
+@sse /events
+@sse_level info';

Receives: RAISE INFO, RAISE NOTICE, RAISE WARNING

Notice Level

sql
sql
comment on function standard_process() is
+'HTTP POST
+@sse /events
+@sse_level notice';

Receives: RAISE NOTICE, RAISE WARNING

Warning Level Only

sql
sql
comment on function quiet_process() is
+'HTTP POST
+@sse /events
+@sse_level warning';

Receives: RAISE WARNING only

Comments

+ + + + \ No newline at end of file diff --git a/annotations/sse-events-scope.html b/annotations/sse-events-scope.html new file mode 100644 index 000000000..c6c0694c0 --- /dev/null +++ b/annotations/sse-events-scope.html @@ -0,0 +1,83 @@ + + + + + + SSE_EVENTS_SCOPE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SSE_EVENTS_SCOPE

Also known as

sse_scope (with or without @ prefix)

Control who receives Server-Sent Events from this endpoint.

Why scope matters

Every event flows through the single global broadcaster, and every connected EventSource reads from the same stream. Scope is the per-event filter that decides which subscribers actually have the event written to their response. Without scope (or with all), every subscriber sees every event from this endpoint.

Syntax

code
@sse_scope <scope>
+@sse_scope authorize <value1>, <value2>, ...

Space-separated lists are also valid: @sse_scope authorize admin manager supervisor

Or using custom parameter syntax:

code
@sse_scope = <scope>
+@sse_events_scope = <scope>

Values

ValueDescription
matchingClients with matching security context receive events (checks roles, user names, and user IDs)
authorizeOnly authorized clients receive events. Optionally filter by role names, user names, or user IDs
allAll connected clients receive events

Request Correlation

Events are filtered by execution ID when both conditions are met:

  • The request includes an execution ID header (configured via ExecutionIdHeaderName)
  • The SSE event source includes the same execution ID as a query parameter

When execution IDs are provided but don't match, the event is skipped regardless of scope.

Examples

Matching Scope

sql
sql
comment on function team_task() is
+'HTTP POST
+@sse /team-events
+@sse_scope matching';

Equivalent as a SQL file endpoint (sql/team-task.sql):

sql
sql
/*
+HTTP POST
+@sse /team-events
+@sse_scope matching
+*/
+do $$ begin
+    raise info 'team task progress...';
+end $$;

Events are sent to clients with matching security context:

  • If the endpoint requires authorization, all authorized sessions receive events
  • If the endpoint requires specific roles, user names, or user IDs, only sessions matching those values receive events (checks DefaultRoleClaimType, DefaultNameClaimType, and DefaultUserIdClaimType)

Authorize Scope with Roles

sql
sql
comment on function admin_broadcast() is
+'HTTP POST
+@sse /admin-events
+@sse_scope authorize admin';

Only clients with admin role receive events.

Authorize with User Names or IDs

sql
sql
comment on function specific_users_notification() is
+'HTTP POST
+@sse /user-events
+@sse_scope authorize john.doe, jane.smith, user123';

Events are sent to clients matching any of the specified role names, user names, or user IDs.

Multiple Values

sql
sql
comment on function staff_notification() is
+'HTTP POST
+@sse /staff-events
+@sse_scope authorize admin, manager, supervisor';

Clients matching any of the specified values receive events.

Broadcast to All

sql
sql
comment on function system_announcement() is
+'HTTP POST
+@sse /announcements
+@sse_scope all';

All connected SSE clients receive events regardless of security context.

Dynamic Scope via RAISE HINT

The scope can also be set dynamically at runtime using the HINT parameter of PostgreSQL RAISE statements. This allows different events within the same function to have different scopes:

sql
sql
create function process_with_notifications()
+returns void
+language plpgsql
+as $$
+begin
+  -- This event goes to all clients
+  raise notice 'System maintenance starting...' using hint = 'all';
+
+  -- This event only goes to admins
+  raise notice 'Admin: detailed system stats...' using hint = 'authorize admin';
+
+  -- This event goes to specific users
+  raise notice 'Your task is complete' using hint = 'authorize john.doe, jane.smith';
+
+  -- This event uses the default scope from annotation
+  raise notice 'General progress update...';
+end;
+$$;
+
+comment on function process_with_notifications() is
+'HTTP POST
+@sse /process-events
+@sse_scope matching';

The HINT value is parsed as: <scope> [value1] [value2] ...

When a hint is provided, it overrides the annotation scope for that specific event. When no hint is provided, the annotation scope is used.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/sse.html b/annotations/sse.html new file mode 100644 index 000000000..a06c49aac --- /dev/null +++ b/annotations/sse.html @@ -0,0 +1,146 @@ + + + + + + SSE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

SSE

Also known as

sse_events_path, sse_path (with or without @ prefix)

Enable Server-Sent Events (SSE) streaming for the endpoint.

How events flow

@sse is the only SSE annotation that affects runtime behavior on its own, and it does two independent things:

  1. Registers a connection URL at <endpoint-path>/<level> — clients open an EventSource against it to listen.
  2. Enables broadcasting from this procedureRAISE statements inside this procedure's body forward their notices to the SSE broadcaster.

A procedure without @sse can RAISE whatever it wants — those notices never reach SSE subscribers.

mermaid
flowchart LR
+    A["Procedure A<br/>(@sse)"] -->|RAISE| BC[("Global Broadcaster<br/>(process-wide)")]
+    B["Procedure B<br/>(@sse)"] -->|RAISE| BC
+    X["Procedure X<br/>(no @sse)"] -. RAISE not broadcast .-> Drop((("✗")))
+    BC --> S1["Subscriber on<br/>/api/a/info"]
+    BC --> S2["Subscriber on<br/>/api/b/info"]
+    BC --> S3["Subscriber on<br/>/api/c/info"]

There is one process-wide broadcaster. Every connected EventSource reads from the same stream regardless of which /info URL it opened — the URL is just an entry point, not a topic name. Once a connection is established, the path it came in through is no longer used for routing.

Per-event filtering decides which subscribers actually receive each event:

  • The originating endpoint's scope (matching / authorize / all).
  • An optional RAISE ... USING HINT override, parsed as <scope> [value1] [value2] ....
  • Optional execution-ID correlation via the X-NpgsqlRest-ID header.

Common pitfall

The URL is not a topic. Subscribers on /api/foo/info and /api/bar/info do not see different streams — they see the same stream. If you want events from procedure B to reach clients connected to procedure A's URL, both procedures must have @sse: A so clients can connect, B so its RAISEs broadcast. See the cross-procedure pattern below.

Syntax

code
@sse
+@sse <path>
+@sse <path> on <level>

level: info, notice, warning

SSE Path Construction

The SSE endpoint path is constructed by appending the SSE path segment to the original endpoint path.

When Path is Omitted

When the path is omitted (@sse without arguments), the SSE path segment defaults to the notice level name in lowercase:

LevelSSE Path Segment
INFO (default)info
NOTICEnotice
WARNINGwarning

Example: If your endpoint path is /api/my-function and you use @sse without arguments, the SSE endpoint will be at /api/my-function/info (since INFO is the default level).

When Custom Path is Specified

When you specify a custom path (@sse my_events), that path segment is appended to the endpoint path.

Example: If your endpoint path is /api/my-function and you use @sse my_events, the SSE endpoint will be at /api/my-function/my_events.

Default Level

The default notice level is INFO. This can be changed globally via the DefaultServerSentEventsEventNoticeLevel configuration setting.

Level Filtering

Important

SSE events are sent only for the exact level specified, not for "this level and above".

When you set the level to NOTICE, only RAISE NOTICE statements will generate SSE events. RAISE INFO and RAISE WARNING statements will not generate SSE events for that endpoint.

Configured LevelRAISE INFORAISE NOTICERAISE WARNING
INFOSentNot sentNot sent
NOTICENot sentSentNot sent
WARNINGNot sentNot sentSent

If you need events from multiple levels, create separate SSE endpoints for each level.

Examples

Basic SSE Endpoint (function)

sql
sql
create function long_running_process(_id int)
+returns void
+language plpgsql
+as $$
+begin
+  raise info 'Starting process...';
+  -- do work
+  raise info 'Progress: 50%%';
+  -- more work
+  raise info 'Complete!';
+end;
+$$;
+
+comment on function long_running_process(int) is
+'HTTP POST
+@sse events';

If the endpoint is at /api/long-running-process, the SSE endpoint will be at /api/long-running-process/events. It receives RAISE INFO messages (the default level).

Basic SSE Endpoint (SQL file)

The same behavior, expressed as a SQL file:

sql
sql
/*
+HTTP POST
+@sse events
+@param $1 _id int
+@void
+*/
+do $$
+begin
+    raise info 'Starting process...';
+    -- do work
+    raise info 'Progress: 50%%';
+    -- more work
+    raise info 'Complete!';
+end;
+$$;

For files placed under the configured Path with the default CommentsMode, the leading comment block carries the same annotations as a function comment.

With Notice Level

sql
sql
comment on function background_task() is
+'HTTP POST
+@sse updates on notice';

If the endpoint is at /api/background-task, the SSE endpoint will be at /api/background-task/updates. It receives only RAISE NOTICE messages.

Warning Level Only

sql
sql
comment on function critical_job() is
+'HTTP POST
+@sse alerts on warning';

If the endpoint is at /api/critical-job, the SSE endpoint will be at /api/critical-job/alerts. It receives only RAISE WARNING messages.

Using Default Path (Level Name)

sql
sql
comment on function my_process() is
+'HTTP POST
+@sse';

If the endpoint is at /api/my-process, the SSE endpoint will be at /api/my-process/info (default path segment from the default INFO level).

sql
sql
comment on function my_process() is
+'HTTP POST
+@sse_events_level notice
+@sse';

If the endpoint is at /api/my-process, the SSE endpoint will be at /api/my-process/notice (path segment derived from the configured NOTICE level).

Cross-procedure pattern

The single-procedure case (one procedure both broadcasts and exposes the URL) is straightforward. But sometimes the procedure that triggers an event isn't the one that should be the client subscription URL. Common reasons:

  • The trigger has restrictive authorization (e.g. manager only) but listeners are regular users.
  • Multiple triggers feed one logical stream — having a stable subscribe URL keeps the client code simple as new triggers are added.
  • The semantic name of the trigger (update_user_roles) reads wrong as a client-facing URL.

The pattern: split publish from subscribe across two procedures, each with @sse. Annotate the trigger so its RAISEs broadcast; annotate a no-op procedure so its URL is the stable client entry point. Use RAISE ... USING HINT inside the trigger to scope events per user.

mermaid
flowchart LR
+    Client["Browser<br/>EventSource"] -->|opens connection| SubURL["/api/user-events-subscribe/info"]
+    SubURL -.->|registers via @sse| SubProc["user_events_subscribe<br/>(no-op, @sse)"]
+    Manager["Manager<br/>browser"] -->|POST| EmitURL["/api/update-user-roles"]
+    EmitURL --> EmitProc["update_user_roles<br/>(@sse)"]
+    EmitProc -->|RAISE INFO<br/>using hint| BC[("Global<br/>Broadcaster")]
+    BC -->|filtered by hint| Client

Function form

sql
sql
-- Subscribe URL: a no-op procedure whose @sse only registers the URL.
+-- Annotated 'authorize' so any authenticated user can connect.
+create procedure user_events_subscribe()
+language plpgsql as $$ begin perform 1; end; $$;
+
+comment on procedure user_events_subscribe() is '
+HTTP GET
+@authorize
+@sse
+@sse_scope authorize';
+
+-- Emitter: the procedure that actually causes events. @sse is required
+-- here too — without it, the RAISE never reaches the broadcaster.
+create procedure update_user_roles(_target_user_id int, _roles text[])
+language plpgsql as $$
+begin
+    -- ... do the role update ...
+    raise info 'roles updated'
+        using hint = format('authorize %s', _target_user_id);
+end;
+$$;
+
+comment on procedure update_user_roles(int, text[]) is '
+HTTP POST
+@authorize manager
+@sse
+@sse_scope authorize';

SQL file form

The same shape, expressed as two files. @sse and @sse_scope work identically; the no-op subscribe file is just a body that does nothing.

sql
sql
-- file: sql/user-events-subscribe.sql
+/*
+HTTP GET
+@authorize
+@sse
+@sse_scope authorize
+@void
+*/
+select 1;
sql
sql
-- file: sql/update-user-roles.sql
+/*
+HTTP POST
+@authorize manager
+@sse
+@sse_scope authorize
+@param $1 _target_user_id int
+@param $2 _roles text[]
+@void
+*/
+do $$
+declare
+    _target_user_id int = $1;
+    _roles text[]    = $2;
+begin
+    -- ... do the role update ...
+    raise info 'roles updated'
+        using hint = format('authorize %s', _target_user_id);
+end;
+$$;

What the client does

ts
ts
const eventSource = new EventSource('/api/user-events-subscribe/info');
+
+eventSource.onmessage = () => {
+    // ... handle the event ...
+};

Clients open EventSource once against /api/user-events-subscribe/info. When update_user_roles runs, its RAISE flows through the global broadcaster, every subscriber receives it, and the per-event hint (authorize <target_user_id>) ensures only the affected user's connection writes the data line. The fact that the event came from a different URL than the one the client subscribed to is invisible to both sides — they share the broadcaster.

The @sse on update_user_roles is what enables broadcasting; the @sse on user_events_subscribe is what gives clients a stable, semantically meaningful URL to open. They serve different purposes despite using the same annotation.

Behavior

@sse affects the procedure on two sides — its execution and its URL. Each side is independent, even though one annotation enables both.

On the publisher side

What @sse does to the procedure's execution:

  • Subscribes to PostgreSQL notices on the connection during the procedure's execution.
  • Filters by the configured level (only RAISE statements matching that level are forwarded).
  • Pushes matching notices to the global broadcaster, tagged with the originating endpoint's metadata and the optional X-NpgsqlRest-ID header for execution-ID correlation.
  • Procedures without @sse skip this entire path — their notices are not visible to any subscriber.

On the subscriber side

What @sse does to the procedure's URL:

  • Registers <endpoint-path>/<level> as an SSE connection URL (or the custom path you specified).
  • Connections to this URL are pure listeners — they never invoke the procedure body.
  • Each connection iterates the broadcaster's stream and decides per-event whether to write to the response, based on the originating endpoint's scope and any HINT override.

Because the URL is a connection point and not a topic, subscribers on different @sse URLs see the same stream. Use the URL primarily to give clients a stable, meaningful connection address and to scope which roles can subscribe (via the procedure's regular authorization).

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/table-format.html b/annotations/table-format.html new file mode 100644 index 000000000..e0676d0e2 --- /dev/null +++ b/annotations/table-format.html @@ -0,0 +1,79 @@ + + + + + + TABLE_FORMAT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TABLE_FORMAT

Control how function results (from routines returning SETOF or TABLE) are rendered. Instead of JSON, results can be rendered as HTML tables or Excel spreadsheet downloads.

Applies to Set-Returning Functions Only

Table format rendering only applies to routines that return SETOF or TABLE results. Scalar-returning functions are not affected.

Requires Configuration

Table format rendering must be enabled in the Table Format Options configuration (TableFormatOptions.Enabled = true).

Syntax

code
@table_format = <format>
+@excel_file_name = <filename>
+@excel_sheet = <sheet_name>

All parameters support dynamic placeholders using the {param_name} format.

Parameters

ParameterDescription
table_formatSets the table format renderer for the endpoint. Values: html (render as HTML table), excel (render as .xlsx download). If the value is not a recognized format, a warning is logged and the endpoint falls back to the default JSON response.
excel_file_nameSets the download filename for Excel table format output. Only applies when table_format is excel. If omitted, defaults to the routine name.
excel_sheetSets the worksheet name for Excel table format output. Only applies when table_format is excel. If omitted, defaults to the routine name (max 31 characters).

Examples

Static HTML Table

sql
sql
create function get_report()
+returns table (id int, name text, amount numeric)
+language sql
+begin atomic;
+  select * from reports;
+end;
+
+comment on function get_report() is '
+HTTP GET
+@table_format = html
+';

Equivalent as a SQL file endpoint (sql/get-report.sql):

sql
sql
/*
+HTTP GET
+@table_format = html
+*/
+select id, name, amount from reports;

Static Excel Download

sql
sql
comment on function get_report() is '
+HTTP GET
+@table_format = excel
+@excel_file_name = monthly_report.xlsx
+@excel_sheet = Report Data
+';

Dynamic Format Selection

Use function parameters as dynamic placeholders to let the caller choose the output format:

sql
sql
create function get_data(
+    _format text,
+    _excel_file_name text = null,
+    _excel_sheet text = null
+)
+returns table (
+    int_val int,
+    text_val text,
+    date_val date
+)
+language sql
+begin atomic;
+  select * from data;
+end;
+
+comment on function get_data(text, text, text) is '
+HTTP GET
+@table_format = {_format}
+@excel_file_name = {_excel_file_name}
+@excel_sheet = {_excel_sheet}
+@tsclient_url_only = true
+';

When called with ?format=html, renders an HTML table. When called with ?format=excel&excelFileName=report.xlsx, returns an Excel download.

Use with tsclient_url_only

Table format endpoints are typically consumed via browser navigation (opening a URL directly), not via fetch. Use @tsclient_url_only = true to generate only the URL builder in the TypeScript client.

  • TSCLIENT - Control TypeScript client generation (use tsclient_url_only for table format endpoints)

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/tags.html b/annotations/tags.html new file mode 100644 index 000000000..24a269b32 --- /dev/null +++ b/annotations/tags.html @@ -0,0 +1,40 @@ + + + + + + TAGS Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TAGS

Also known as

for, tags, tag (no @ prefix needed; @for and @tags also work)

Apply subsequent annotations only when the routine matches a specific volatility or routine-type tag.

Rarely needed

Most projects never use this. Reach for it only when a single comment needs to behave differently depending on the function's volatility — and even then, putting the annotations directly on the specific function is usually clearer.

Available tags

RoutineSource assigns these auto-tags to each function or procedure:

TagMatches
functionPostgreSQL functions
procedurePostgreSQL procedures
volatileFunctions declared VOLATILE (default)
stableFunctions declared STABLE
immutableFunctions declared IMMUTABLE
otherProcedures (volatility doesn't apply)

That's the complete list. Custom tags are not supported, and SQL file endpoints have no auto-tags — for has no effect on them.

Syntax

code
for <tag1>, <tag2>, ...

Annotations following a for line apply only when the routine matches at least one of the listed tags. The scope ends at the next for line or at the end of the comment.

Example: cache only when immutable

sql
sql
comment on function calculate_hash(_data text) is '
+HTTP GET
+for immutable
+@cached';

If the function is later changed to STABLE or VOLATILE, the @cached annotation no longer applies — no other comment changes needed. This is the one pattern where for carries its weight.

Behavior

  • Multiple for blocks can appear in the same comment; each scopes the annotations that follow it.
  • Tag matching is case-insensitive.
  • Comma- and space-separated lists are equivalent: for stable, immutablefor stable immutable.
  • Annotations before any for line apply unconditionally.
  • DISABLED — hide an endpoint, optionally scoped by tag
  • ENABLED — re-enable an endpoint, optionally scoped by tag

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-claim.html b/annotations/test-claim.html new file mode 100644 index 000000000..dba44fc1e --- /dev/null +++ b/annotations/test-claim.html @@ -0,0 +1,62 @@ + + + + + + TEST @claim Directive | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @claim

Test files only

This directive applies only inside HTTP blocks of test files run by the SQL test runner (npgsqlrest --test).

Add a claim to the acting principal of an in-process endpoint call. Placed inside an HTTP block, after the request line and before the body:

sql
sql
/*
+GET /api/get-users
+# @claim user_id=42
+# @claim roles=admin
+# @claim roles=auditor
+*/
+select status = 200, 'authorized call succeeds' from _response;

Semantics

  • Repeatable — including the same claim type twice (two roles claims above), exactly like a real multi-valued principal.
  • Any # @claim makes the request authenticated; a block with no # @claim is anonymous — an @authorize endpoint returns 401.
  • Role checks (@authorize roles ...) and claim-to-parameter bindings (@user_parameters, ParameterNameClaimsMapping) run exactly as in production — the directive injects the principal, everything downstream is the real authorization path.
  • // @claim is accepted as an alternative to # @claim.

Why not call the login endpoint?

@login/@logout endpoints are rejected in test mode (they manipulate real authentication schemes that don't exist in-process). Inject the principal directly instead — it is both faster and lets a test act as any user:

sql
sql
/*
+POST /api/admin/delete-user
+Content-Type: application/json
+# @claim user_id=1
+# @claim roles=admin
+
+{"id": 42}
+*/
+select status = 200, 'admin can delete' from _response;
+
+/*
+POST /api/admin/delete-user
+Content-Type: application/json
+# @claim user_id=2
+# @claim roles=viewer
+
+{"id": 42}
+*/
+-- second block → second response table (_response_1, _response_2)
+select status = 403, 'viewer cannot delete' from _response_2;

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-connection.html b/annotations/test-connection.html new file mode 100644 index 000000000..debc97270 --- /dev/null +++ b/annotations/test-connection.html @@ -0,0 +1,55 @@ + + + + + + TEST @connection Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @connection

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It is distinct from the endpoint CONNECTION annotation, which selects a connection for a routine endpoint.

Run this test file on a named ConnectionStrings entry instead of the test runner's default connection (TestRunner.ConnectionName or the app's main connection).

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @connection Name

The whole file — its SQL statements and the endpoints invoked by its HTTP blocks — runs on a non-pooled connection built from that entry. This is the key to perfect per-test isolation: point the file at a database that its own @setup step just created.

Example

Config:

json
json
{
+  "ConnectionStrings": {
+    "Admin": "Host=localhost;Database=postgres;...",
+    "Test": "Host=localhost;Database=app_test_{rnd5};...",
+    "Isolated": "Host=localhost;Database=app_iso_{rnd5_1};..."
+  }
+}

Test file — gets its own clone, invisible to every other test:

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+/*
+POST /api/create-user
+Content-Type: application/json
+
+{"name": "Ada", "email": "ada@example.com"}
+*/
+select body::jsonb ->> 'id' = '4',
+       'sequence ids are deterministic in a fresh clone'
+from _response;

Sequences are the classic motivation: nextval() is non-transactional (it sticks even through rollback), so on a shared test database a generated id depends on which other tests ran first. In a private clone the id is deterministic.

Notes

  • The endpoint pipeline still type-checks (Describe) against the run-level test connection at startup; @connection switches the execution connection for this file. The databases must therefore be structurally compatible — which they are by construction when both are created from the same template or migrations.
  • {rnd} tokens in the connection string resolve once per run; use indexed tokens ({rnd5_1}, {rnd5_2}) when several files each need their own database name.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-response.html b/annotations/test-response.html new file mode 100644 index 000000000..7c894761a --- /dev/null +++ b/annotations/test-response.html @@ -0,0 +1,45 @@ + + + + + + TEST @response Directive | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @response

Test files only

This directive applies only inside HTTP blocks of test files run by the SQL test runner (npgsqlrest --test).

Capture this HTTP block's response into a temp table with a custom name, instead of the default.

Default naming

Without the directive, the response table name comes from TestRunner.ResponseTempTable:

  • a file with one HTTP block → _response
  • a file with two or more blocks → _response_1, _response_2, … in block order

Includes participate in the numbering: HTTP blocks spliced in by \i/\ir count as if pasted.

Syntax

sql
sql
/*
+POST /api/login
+Content-Type: application/json
+# @response login_result
+
+{"email": "ada@example.com", "password": "secret"}
+*/
+select (select status from login_result) = 200, 'login succeeds';
+select (select body::jsonb ->> 'role' from login_result) = 'admin', 'role returned';

The named table has the same columns as the default (status int, body text, content_type text, headers jsonb, is_success boolean — configurable).

Notes

  • Each block's table is created fresh (no IF NOT EXISTS): reusing a name — two blocks both saying # @response x, or a name colliding with the default — fails the test loudly rather than silently overwriting.
  • Named tables make multi-call tests readable: login_result, created, after_delete beat _response_1..3.
  • // @response is accepted as an alternative to # @response.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-setup.html b/annotations/test-setup.html new file mode 100644 index 000000000..be30284ce --- /dev/null +++ b/annotations/test-setup.html @@ -0,0 +1,60 @@ + + + + + + TEST @setup Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @setup

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It has no meaning in endpoint SQL files or routine comments.

Run one or more named steps (from the TestRunner.Steps registry) before this test file executes.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @setup StepName [StepName ...]
  • Names may be whitespace- or comma-separated: -- @setup CreateDb SeedData and -- @setup CreateDb, SeedData are equivalent.
  • The annotation is repeatable; all listed steps run in the order written.
  • Every name must exist in the TestRunner.Steps registry — an unknown name is a loud error, not a silent skip.
  • A step with "Enabled": false is the one sanctioned skip: it is ignored wherever referenced (logged at debug level) — the default configuration ships disabled example steps to flip on instead of typing.

Example

Config:

json
json
{
+  "TestRunner": {
+    "Steps": {
+      "CreateIsolatedDb": {
+        "Sql": "create database app_iso_{rnd5_1} template app_template_{rnd5}",
+        "ConnectionName": "Admin"
+      },
+      "DropIsolatedDb": {
+        "Sql": "drop database if exists app_iso_{rnd5_1} with (force)",
+        "ConnectionName": "Admin"
+      }
+    }
+  }
+}

Test file:

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+/*
+POST /api/create-user
+Content-Type: application/json
+
+{"name": "Ada"}
+*/
+select status = 200, 'user created in the isolated clone' from _response;

The step runs once, immediately before this file (after the run-level Setup). Combined with -- @teardown and -- @connection, this gives a single test file its own private database.

Header semantics

The header ends at the first SQL statement or HTTP block. An included file (\i/\ir) that contains only comments (an annotation profile) continues the header — its annotations count as if written in-place — so a shared profile can carry @setup/@teardown/@connection/@tag for many test files.

Watch the prose

Everything after the step names on the line is treated as more step names. Write explanatory text on its own comment line, not after the names.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-tag.html b/annotations/test-tag.html new file mode 100644 index 000000000..76be2caa2 --- /dev/null +++ b/annotations/test-tag.html @@ -0,0 +1,57 @@ + + + + + + TEST @tag Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @tag

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It is distinct from the endpoint TAGS annotation, which scopes routine annotations by volatility.

Declare tags on a test file, so runs can be narrowed with TestRunner.Tag / ExcludeTag.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @tag name [name ...]
  • Names may be whitespace- or comma-separated: -- @tag smoke auth and -- @tag smoke, auth are equivalent.
  • The annotation is repeatable; tags accumulate. Matching is case-insensitive.

Example

sql
sql
-- @tag auth, smoke
+-- Test: GET /api/get-users requires authentication.
+
+/*
+GET /api/get-users
+*/
+select status = 401, 'anonymous request is rejected' from _response;

Selective runs:

sh
sh
# only the smoke suite
+npgsqlrest ./config.json --test --testrunner:tag=smoke
+
+# everything except slow tests
+npgsqlrest ./config.json --test --testrunner:excludetag=slow
+
+# smoke AND auth files, but never slow ones (exclude wins)
+npgsqlrest ./config.json --test --testrunner:tag=smoke,auth --testrunner:excludetag=slow

Tags via a shared profile

Tags declared in an included annotation profile count as if written in the file — a shared \ir include can tag a whole family of tests at once:

sql
sql
-- tests/shared/isolated_database.sql (an annotation profile: comments only)
+-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+-- @tag isolation, slow
sql
sql
-- a test file attaching the profile
+\ir shared/isolated_database.sql
+
+select 1 = 1, 'runs isolated, tagged isolation+slow via the profile';

Comments

+ + + + \ No newline at end of file diff --git a/annotations/test-teardown.html b/annotations/test-teardown.html new file mode 100644 index 000000000..3f02a1688 --- /dev/null +++ b/annotations/test-teardown.html @@ -0,0 +1,44 @@ + + + + + + TEST @teardown Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TEST @teardown

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It has no meaning in endpoint SQL files or routine comments.

Run one or more named steps (from the TestRunner.Steps registry) after this test filealways, best-effort, even when the file failed or errored.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @teardown StepName [StepName ...]
  • Names may be whitespace- or comma-separated; the annotation is repeatable; steps run in the order written.
  • Every name must exist in the TestRunner.Steps registry.
  • Runs after the file's own connection is disposed, so it can safely drop database ... with (force) on an admin connection.

Example

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+begin;
+insert into users (name) values ('fixture');
+select count(*) = 1, 'fixture inserted in the private clone' from users;
+rollback;

Even if the assertion fails — or the file errors halfway — DropIsolatedDb still runs, so the per-file database never leaks.

Ordering

For one test file the lifecycle is:

  1. per-file @setup steps (in written order)
  2. the file body, on its own non-pooled connection
  3. connection disposed
  4. per-file @teardown steps (in written order, always)

The run-level TestRunner.Setup/Teardown wrap the whole run outside of this.

Comments

+ + + + \ No newline at end of file diff --git a/annotations/tsclient.html b/annotations/tsclient.html new file mode 100644 index 000000000..25f472c30 --- /dev/null +++ b/annotations/tsclient.html @@ -0,0 +1,91 @@ + + + + + + TSCLIENT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

TSCLIENT

Control TypeScript client code generation for individual endpoints using custom parameter annotations.

Requires Configuration

TypeScript client generation must be enabled in the Code Generation configuration (ClientCodeGen.Enabled = true).

Syntax

code
@tsclient = <true|false>
+@tsclient_module = <module_name>
+@tsclient_events = <true|false>
+@tsclient_parse_url = <true|false>
+@tsclient_parse_request = <true|false>
+@tsclient_status_code = <true|false>
+@tsclient_export_url = <true|false>
+@tsclient_url_only = <true|false>

Parameters

ParameterDescription
tsclientSet to false, off, disabled, disable, or 0 to disable TypeScript client code generation for the endpoint.
tsclient_moduleSets a different module name for the generated TypeScript client file. Endpoints with the same module name are grouped into the same file.
tsclient_eventsEnable or disable SSE events parameter for endpoints with SSE events enabled.
tsclient_parse_urlEnable or disable parseUrl parameter in the generated function.
tsclient_parse_requestEnable or disable parseRequest parameter in the generated function.
tsclient_status_codeEnable or disable status code in the return value.
tsclient_export_urlWhen true, exports a URL constant for this endpoint regardless of the global ExportUrls setting.
tsclient_url_onlyWhen true, only the URL constant and request interface are exported — the fetch function and response type are skipped. Implies tsclient_export_url = true. Useful for endpoints consumed via browser navigation (e.g., table format downloads).

Examples

Disable Generation

Use @tsclient = false to skip client generation for endpoints that return binary data or are not useful in the TypeScript client:

sql
sql
create function get_image(_id int)
+returns bytea
+language sql
+begin atomic;
+  select data from images where id = _id;
+end;
+
+comment on function get_image(int) is '
+HTTP GET
+@tsclient = false
+';

Equivalent as a SQL file endpoint (sql/get-image.sql):

sql
sql
/*
+HTTP GET
+@tsclient = false
+@param $1 id
+*/
+select data from images where id = $1;

URL-Only Export

Use @tsclient_url_only = true for endpoints consumed via browser navigation rather than fetch — such as table format downloads or file exports:

sql
sql
create function get_data(
+    _format text,
+    _excel_file_name text = null,
+    _excel_sheet text = null
+)
+returns table (int_val int, text_val text, date_val date)
+language sql
+begin atomic;
+  select * from data;
+end;
+
+comment on function get_data(text, text, text) is '
+HTTP GET
+@table_format = {_format}
+@excel_file_name = {_excel_file_name}
+@excel_sheet = {_excel_sheet}
+@tsclient_url_only = true
+';

This generates only the URL builder and request interface:

typescript
typescript
export const getDataUrl = (request: IGetDataRequest) =>
+    baseUrl + "/api/get-data" + parseQuery(request);
+
+interface IGetDataRequest {
+    format: string | null;
+    excelFileName?: string | null;
+    excelSheet?: string | null;
+}

Custom Module

Use @tsclient_module to group endpoints from different schemas into the same generated file:

sql
sql
comment on function public.get_users() is '
+HTTP GET
+@tsclient_module = admin
+';
+
+comment on function auth.get_roles() is '
+HTTP GET
+@tsclient_module = admin
+';

Both endpoints will be generated in the admin module file.

  • TABLE_FORMAT - Table format rendering (commonly used with tsclient_url_only)
  • SSE - Server-Sent Events (use tsclient_events to control SSE parameter generation)

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/upload.html b/annotations/upload.html new file mode 100644 index 000000000..8f8e3c030 --- /dev/null +++ b/annotations/upload.html @@ -0,0 +1,363 @@ + + + + + + UPLOAD Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

UPLOAD

Mark endpoint as a file upload handler.

Keywords

@upload, upload

Syntax

code
@upload
+@upload for <handler_type>

If no handler is specified (only upload annotation without for), then the default handler will be used. The default handler is large_object unless configured otherwise via DefaultUploadHandler setting.

Handler Types

There are 4 handler types available:

HandlerKeyDescription
Large Objectlarge_objectStores files using PostgreSQL Large Objects API (default)
File Systemfile_systemStores files on the server file system
CSVcsvParses CSV files and processes rows via PostgreSQL command
ExcelexcelParses Excel files and processes rows via PostgreSQL command

Shared Annotation Options

These options are available for all handler types:

OptionTypeDefaultDescription
stop_after_first_successboolfalseStop upload after first successful upload when multiple handlers are used. Subsequent files will have status Ignored.
included_mime_typesstringnullCSV string of MIME type patterns to include. Set to null to allow all.
excluded_mime_typesstringnullCSV string of MIME type patterns to exclude. Set to null to exclude none.
buffer_sizeintnullBuffer size in bytes for raw content uploads (large_object and file_system).
check_textboolfalseValidate file is a text file (not binary). Set to true to accept only text files.
check_imagebool/stringfalseValidate file is an image. Set to true to accept only images, or CSV of allowed types: jpg, png, gif, bmp, tiff, webp.
test_buffer_sizeint4096Buffer size in bytes when checking text files.
non_printable_thresholdint5Maximum non-printable characters allowed in test buffer to consider a valid text file.
check_formatboolfalseValidate the file format before processing. When true and validation fails, the fallback_handler is used if configured.
fallback_handlerstringnullHandler name to delegate to if format validation fails (e.g., large_object, file_system, csv, excel). When a handler's format validation fails and a fallback_handler is configured, processing is automatically delegated to the named handler.

Upload Metadata

All handlers return upload metadata as JSON with the following common properties:

PropertyTypeDescription
typestringHandler type used (large_object, file_system, csv, excel)
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the uploaded file
sizeintFile size in bytes
successboolWhether the upload succeeded
statusstringStatus message (e.g., Ok, InvalidMimeType)

Handler-specific properties:

HandlerPropertyTypeDescription
large_objectoidintPostgreSQL Large Object OID
file_systemfilePathstringPath where file was saved

Large Object Handler

Default handler that stores files using PostgreSQL Large Objects.

Basic Example

sql
sql
create function lo_simple_upload(
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function lo_simple_upload(json) is '
+@upload
+@param _meta is upload metadata
+';

Equivalent as a SQL file endpoint (sql/lo-simple-upload.sql):

sql
sql
/*
+HTTP POST
+@upload
+@param $1 meta is upload metadata
+*/
+select $1;

With Custom OID Parameter

You can specify a custom OID for the large object:

sql
sql
create function lo_custom_parameter_upload(
+    _oid bigint,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function lo_custom_parameter_upload(bigint, json) is '
+@upload for large_object
+@param _meta is upload metadata
+@oid = {_oid}
+';

Context Metadata

Upload metadata is also available via PostgreSQL context setting:

sql
sql
create function lo_simple_upload_context_metadata()
+returns json
+language plpgsql
+as
+$$
+begin
+    return current_setting('request.upload_metadata', true)::text;
+end;
+$$;
+
+comment on function lo_simple_upload_context_metadata() is '@upload';

Large Object Annotation Options

All shared options plus:

OptionDescription
oidCustom OID for the large object
large_object_included_mime_typesHandler-specific MIME types to include
large_object_excluded_mime_typesHandler-specific MIME types to exclude
large_object_buffer_sizeHandler-specific buffer size
large_object_oidHandler-specific OID (alias for oid)
large_object_check_textHandler-specific text check
large_object_check_imageHandler-specific image check
large_object_test_buffer_sizeHandler-specific test buffer size
large_object_non_printable_thresholdHandler-specific non-printable threshold

File System Handler

Stores files on the server file system.

Basic Example

sql
sql
create function fs_simple_upload(
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function fs_simple_upload(json) is '
+@upload for file_system
+@param _meta is upload metadata
+';

With Custom Parameters

Control the file path, name, and behavior:

sql
sql
create function fs_custom_parameter_upload(
+    _path text,
+    _file text,
+    _unique_name boolean,
+    _create_path boolean,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function fs_custom_parameter_upload(text, text, boolean, boolean, json) is '
+@upload for file_system
+@param _meta is upload metadata
+@path = {_path}
+@file = {_file}
+@unique_name = {_unique_name}
+@create_path = {_create_path}
+';

File System Annotation Options

All shared options plus:

OptionDescription
pathDirectory path for uploaded file
fileFile name to use
unique_nameGenerate unique file name (bool)
create_pathCreate directory if not exists (bool)
file_system_included_mime_typesHandler-specific MIME types to include
file_system_excluded_mime_typesHandler-specific MIME types to exclude
file_system_buffer_sizeHandler-specific buffer size
file_system_pathHandler-specific path (alias for path)
file_system_fileHandler-specific file name (alias for file)
file_system_unique_nameHandler-specific unique name setting
file_system_create_pathHandler-specific create path setting
file_system_check_textHandler-specific text check
file_system_check_imageHandler-specific image check
file_system_test_buffer_sizeHandler-specific test buffer size
file_system_non_printable_thresholdHandler-specific non-printable threshold

MIME Type Filtering

sql
sql
comment on function fs_upload_include_mime_type(json) is '
+@upload for file_system
+@param _meta is upload metadata
+@path = ./test
+@file = mime_type.csv
+@included_mime_types = image/*, application/*
+';

CSV Handler

Parses CSV files and processes each row via a PostgreSQL command.

Row Command Function Signature

The row command function receives up to 4 parameters:

sql
sql
create function my_csv_row_processor(
+    _index int,           -- $1: Row index (1-based)
+    _row text[],          -- $2: Parsed row values as text array
+    _prev_result any,     -- $3: Result of previous row command (for chaining)
+    _meta json            -- $4: Row metadata JSON
+)
+returns any               -- Return value passed to next row as $3

Row Command Parameters

ParameterTypeDescription
$1intRow index (1-based, includes header row)
$2text[]Parsed row values as text array (e.g., _row[1], _row[2], etc.)
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values).

Row Metadata Structure ($4)

The metadata JSON passed to each row command contains:

json
json
{
+  "type": "csv",
+  "fileName": "data.csv",
+  "contentType": "text/csv",
+  "size": 1234,
+  "claims": {                    // Only if RowCommandUserClaimsKey is set (default: "claims")
+    "user_id": "1",
+    "user_name": "alice",
+    "name_identifier": "1"
+  }
+}
PropertyTypeDescription
typestringHandler type ("csv")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Unlike Excel, CSV row metadata does NOT include rowIndex. Use the $1 parameter for the row index.

Upload Function Metadata (_meta parameter)

The main upload function receives metadata as a JSON array with one element per uploaded file:

json
json
[
+  {
+    "type": "csv",
+    "fileName": "data.csv",
+    "contentType": "text/csv",
+    "size": 1234,
+    "success": true,
+    "status": "Ok",
+    "lastResult": 100
+  }
+]
PropertyTypeDescription
lastResultanyFinal return value from the last row command execution

Basic Example

sql
sql
-- Table for uploads
+create table csv_uploads (
+    id int primary key generated always as identity,
+    file_name text not null,
+    row_index int not null,
+    row_data text[] not null
+);
+
+-- Row command to process each CSV row
+create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (file_name, row_index, row_data)
+    values (_meta->>'fileName', _index, _row);
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;
+
+-- HTTP POST endpoint
+create function csv_upload(_meta json = null)
+returns json
+language sql
+begin atomic;
+    select _meta;
+end;
+
+comment on function csv_upload(json) is '
+@upload for csv
+@param _meta is upload metadata
+@row_command = select csv_upload_row($1,$2,$3,$4)
+';

Accessing User Claims in Row Command

With RowCommandUserClaimsKey configured (default: "claims"), user claims are available in the row metadata:

sql
sql
create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (user_id, file_name, row_index, row_data)
+    values (
+        (_meta->'claims'->>'user_id')::int,  -- Access user_id from claims
+        _meta->>'fileName',
+        _index,
+        _row
+    );
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

Using User Context Variables

With UseUserContext: true, user context variables are set before upload and accessible via current_setting():

sql
sql
create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (user_id, file_name, row_index, row_data)
+    values (
+        current_setting('request.user_id')::int,  -- Access from context
+        _meta->>'fileName',
+        _index,
+        _row
+    );
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

Custom Delimiters

Support multiple delimiter characters:

sql
sql
comment on function csv_upload(json) is '
+@upload for csv
+@param _meta is upload metadata
+@delimiters = ,;
+@row_command = select csv_upload_row($1,$2,$3,$4)
+';

This will use comma (,) and semicolon (;) as delimiters. Use \t for tab.

CSV Annotation Options

All shared options (except buffer_size, check_text, check_image) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
delimitersstring,Delimiter character(s)
check_formatboolfalseValidate file is text before processing
has_fields_enclosed_in_quotesbooltrueFields may be enclosed in quotes
set_white_space_to_nullbooltrueConvert whitespace-only values to NULL

Handler-specific prefixed aliases are also available (e.g., csv_row_command, csv_delimiters).

Excel Handler

Parses Excel files (.xlsx, .xls) and processes each row via a PostgreSQL command.

Row Command Function Signature

The row command function receives up to 4 parameters:

sql
sql
create function my_excel_row_processor(
+    _index int,           -- $1: Row index (1-based, non-empty rows only)
+    _row text[],          -- $2: Row values as text array (or json if row_is_json = true)
+    _prev_result any,     -- $3: Result of previous row command
+    _meta json            -- $4: Row metadata JSON (includes sheet name)
+)
+returns any               -- Return value passed to next row as $3

Row Command Parameters

ParameterTypeDescription
$1intRow index (1-based, only counts non-empty rows)
$2text[] or jsonRow values as text array, or JSON if row_is_json = true
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object (includes sheet info)

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values). Note: When processing multiple sheets (all_sheets = true), $3 resets to NULL at the start of each sheet.

Row Metadata Structure ($4)

The metadata JSON passed to each row command contains:

json
json
{
+  "type": "excel",
+  "fileName": "data.xlsx",
+  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+  "size": 5678,
+  "sheet": "Sheet1",
+  "rowIndex": 5,
+  "claims": {                    // Only if RowCommandUserClaimsKey is set
+    "user_id": "1",
+    "user_name": "alice"
+  }
+}
PropertyTypeDescription
typestringHandler type ("excel")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
sheetstringCurrent sheet name being processed
rowIndexintExcel row index (1-based, includes empty rows)
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Excel row metadata includes rowIndex (actual Excel row number) and sheet name. The $1 parameter is a sequential counter for non-empty rows only, while rowIndex reflects the actual Excel row position.

Upload Function Metadata (_meta parameter)

The main upload function receives metadata as a JSON array. When all_sheets = true, there's one element per sheet:

json
json
[
+  {
+    "type": "excel",
+    "fileName": "data.xlsx",
+    "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+    "size": 5678,
+    "sheet": "Sheet1",
+    "success": true,
+    "rows": 99,
+    "result": 100
+  },
+  {
+    "type": "excel",
+    "fileName": "data.xlsx",
+    "contentType": "...",
+    "size": 5678,
+    "sheet": "Sheet2",
+    "success": true,
+    "rows": 50,
+    "result": 50
+  }
+]
PropertyTypeDescription
sheetstringSheet name
rowsintNumber of non-empty rows processed
resultanyFinal return value from the last row command for this sheet

Basic Example

sql
sql
-- Table for uploads
+create table excel_uploads (
+    id int primary key generated always as identity,
+    file_name text not null,
+    sheet_name text,
+    row_index int not null,
+    row_data text[] not null
+);
+
+-- Row command to process each Excel row
+create function excel_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into excel_uploads (file_name, sheet_name, row_index, row_data)
+    values (
+        _meta->>'fileName',
+        _meta->>'sheet',
+        _index,
+        coalesce(_row, '{}')
+    );
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;
+
+-- HTTP POST endpoint
+create function excel_upload(_meta json = null)
+returns json
+language sql
+begin atomic;
+    select _meta;
+end;
+
+comment on function excel_upload(json) is '
+@upload for excel
+@param _meta is upload metadata
+@all_sheets = true
+@row_command = select excel_upload_row($1,$2,$3,$4)
+';

Row Data as JSON

When row_is_json = true, row data is passed as JSON with Excel cell references as keys:

sql
sql
comment on function excel_upload(json) is '
+@upload for excel
+@param _meta is upload metadata
+@row_is_json = true
+@row_command = select excel_upload_row($1,$2,$3,$4)
+';

The $2 parameter becomes JSON like:

json
json
{"A1": "Name", "B1": "Value", "C1": 123}

Excel Annotation Options

All shared options (except buffer_size, check_text, check_image, test_buffer_size, non_printable_threshold) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
sheet_namestringnullSpecific sheet name to process (first sheet if null)
all_sheetsboolfalseProcess all sheets in the workbook
time_formatstringHH:mm:ssFormat for time values
date_formatstringyyyy-MM-ddFormat for date values
datetime_formatstringyyyy-MM-dd HH:mm:ssFormat for datetime values
row_is_jsonboolfalsePass row data as JSON instead of text array

Handler-specific prefixed aliases are also available (e.g., excel_row_command, excel_all_sheets).

Error Handling and Rollback

All upload handlers support automatic rollback on error. If the handler function raises an exception, any uploaded data is rolled back:

sql
sql
create function lo_upload_raise_exception(
+    _oid bigint,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    raise exception 'failed upload';
+    return _meta;
+end;
+$$;
+
+comment on function lo_upload_raise_exception(bigint, json) is '
+@upload for large_object
+@param _meta is upload metadata
+@oid = {_oid}
+';

If an exception is raised:

  • Large Object: The large object is deleted
  • File System: The uploaded file is deleted
  • CSV/Excel: All database changes are rolled back

Multiple File Uploads

Upload endpoints support multiple files in a single request. The metadata will be returned as a JSON array with one entry per file.

Behavior

  • Enables multipart/form-data file uploads
  • Handlers process the uploaded file (storage, validation, parsing)
  • Metadata parameter receives file information (name, size, type)
  • All uploads are transactional - errors trigger rollback
  • See Upload Options for configuration

Custom Parameters

Upload handlers accept custom parameters using the @key = value syntax to control file processing behavior per-endpoint.

Shared Parameters

ParameterDescription
stop_after_first_successWhen true, stops processing after the first successful upload handler.
included_mime_typesComma-separated list of MIME type patterns to include for upload processing.
excluded_mime_typesComma-separated list of MIME type patterns to exclude from upload processing.
check_formatWhen true, validates the file format before processing. If validation fails, fallback_handler is used.
fallback_handlerHandler name to delegate to if format validation fails (e.g., csv, large_object). Available on all upload handlers.

Large Object Upload Handler

ParameterDescription
buffer_size, large_object_buffer_sizeSize of the buffer used for reading/writing large object data.
check_text, large_object_check_textWhen true, checks if the uploaded content is text format.
check_image, large_object_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, large_object_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, large_object_non_printable_thresholdThreshold for determining if content contains non-printable characters.
oid, large_object_oidPostgreSQL large object OID to use for storage.
large_object_included_mime_typesMIME type patterns to include for large object upload processing.
large_object_excluded_mime_typesMIME type patterns to exclude from large object upload processing.

Example

sql
sql
comment on function upload_to_large_object(text, json) is '
+HTTP POST
+@upload for large_object
+@param _meta is upload metadata
+@check_image = true';

File System Upload Handler

ParameterDescription
buffer_size, file_system_buffer_sizeSize of the buffer used for reading/writing file system data.
check_text, file_system_check_textWhen true, checks if the uploaded content is text format.
check_image, file_system_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, file_system_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, file_system_non_printable_thresholdThreshold for determining if content contains non-printable characters.
path, file_system_pathFile system path where uploaded files will be stored. Supports dynamic placeholders.
file, file_system_fileSpecific file name to use for the uploaded content. Supports dynamic placeholders.
unique_name, file_system_unique_nameWhen true, generates unique file names to avoid conflicts.
create_path, file_system_create_pathWhen true, creates the directory path if it doesn't exist.
file_system_included_mime_typesMIME type patterns to include for file system upload processing.
file_system_excluded_mime_typesMIME type patterns to exclude from file system upload processing.

Example

sql
sql
comment on function upload_to_file_system(text, json) is '
+HTTP POST
+@upload for file_system
+@param _meta is upload metadata
+@check_image = true
+@path = ./public/uploads
+@unique_name = true
+@create_path = true';

CSV Upload Handler

ParameterDescription
test_buffer_size, csv_test_buffer_sizeSize of the buffer used for testing CSV content type.
non_printable_threshold, csv_non_printable_thresholdThreshold for determining if content contains non-printable characters.
check_format, csv_check_formatWhen true, validates the CSV format before processing.
delimiters, csv_delimitersCharacters used as field delimiters in CSV files (e.g., comma, semicolon).
has_fields_enclosed_in_quotes, csv_has_fields_enclosed_in_quotesWhen true, expects CSV fields to be enclosed in quotes.
set_white_space_to_null, csv_set_white_space_to_nullWhen true, converts whitespace-only fields to NULL values.
row_command, csv_row_commandSQL command to execute for each CSV row during processing.
csv_included_mime_typesMIME type patterns to include for CSV upload processing.
csv_excluded_mime_typesMIME type patterns to exclude from CSV upload processing.

Example

sql
sql
comment on function csv_upload(json) is '
+HTTP POST
+@upload for csv
+@param _meta is upload metadata
+@delimiters = ,;
+@row_command = select csv_upload_row($1,$2,$3,$4)';

Excel Upload Handler

ParameterDescription
sheet_name, excel_sheet_nameName of the specific Excel worksheet to process.
all_sheets, excel_all_sheetsWhen true, processes all worksheets in the Excel file.
time_format, excel_time_formatFormat string for parsing time values from Excel cells.
date_format, excel_date_formatFormat string for parsing date values from Excel cells.
datetime_format, excel_datetime_formatFormat string for parsing datetime values from Excel cells.
row_is_json, excel_row_is_jsonWhen true, treats each Excel row as JSON data.
row_command, excel_row_commandSQL command to execute for each Excel row during processing.
excel_included_mime_typesMIME type patterns to include for Excel upload processing.
excel_excluded_mime_typesMIME type patterns to exclude from Excel upload processing.

Example

sql
sql
comment on function excel_upload(json) is '
+HTTP POST
+@upload for excel
+@param _meta is upload metadata
+@all_sheets = true
+@row_command = select excel_upload_row($1,$2,$3,$4)';

Blog Posts

  • AUTHORIZE - Protect upload endpoint
  • TSCLIENT - Control TypeScript client generation for upload endpoints

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/user-context.html b/annotations/user-context.html new file mode 100644 index 000000000..71155a687 --- /dev/null +++ b/annotations/user-context.html @@ -0,0 +1,103 @@ + + + + + + USER_CONTEXT Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

USER_CONTEXT

Enable setting user claims into PostgreSQL session context variables for the endpoint.

Keywords

@user_context, user_context

Syntax

code
@user_context

Examples

Enable User Context

sql
sql
comment on function personalized_data() is
+'HTTP GET
+@authorize
+@user_context';

Access User Claims in Function

sql
sql
create function get_user_context()
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    current_setting('request.user_id', true)::int,
+    current_setting('request.user_name', true)::text,
+    (current_setting('request.user_roles', true))::text[];
+end;
+
+comment on function get_user_context() is '
+@authorize
+@user_context
+';

Equivalent as a SQL file endpoint (sql/get-user-context.sql):

sql
sql
/*
+HTTP GET
+@authorize
+@user_context
+*/
+select
+    current_setting('request.user_id', true)::int as user_id,
+    current_setting('request.user_name', true)::text as user_name,
+    (current_setting('request.user_roles', true))::text[] as user_roles;

Access All Claims as JSON

When ClaimsJsonContextKey is configured (e.g., "request.user_claims"):

sql
sql
create function get_full_claims()
+returns table (claims text)
+language sql
+begin atomic;
+select current_setting('request.user_claims', true)::text;
+end;
+
+comment on function get_full_claims() is '
+@authorize
+@user_context
+';

Access Client IP Address

sql
sql
create function get_client_info()
+returns table (ip_address text)
+language sql
+begin atomic;
+select current_setting('request.ip_address', true)::text;
+end;
+
+comment on function get_client_info() is '
+@authorize
+@user_context
+';

Combined with Request Headers

sql
sql
create function get_user_context_and_headers()
+returns table (
+    user_id int,
+    user_name text,
+    headers jsonb
+)
+language sql
+begin atomic;
+select
+    current_setting('request.user_id', true)::int,
+    current_setting('request.user_name', true)::text,
+    current_setting('request.headers', true)::jsonb;
+end;
+
+comment on function get_user_context_and_headers() is '
+@authorize
+@user_context
+@request_headers context
+';

Behavior

  • Sets authenticated user claims into PostgreSQL session context variables before executing the function
  • Claims are accessible via current_setting('context_key', true) in PostgreSQL
  • The second parameter true prevents errors when the setting doesn't exist
  • Default behavior for all endpoints can be configured via UseUserContext
  • Claim-to-context key mapping is configured via ContextKeyClaimsMapping

Default Context Keys

Context KeyClaimDescription
request.user_iduser_idUser identifier
request.user_nameuser_nameUsername
request.user_rolesuser_rolesUser roles (array)
request.ip_address-Client IP address

Additional Context Keys (when configured)

Context KeyConfig OptionDescription
(configurable)ClaimsJsonContextKeyAll claims serialized as JSON

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/user-parameters.html b/annotations/user-parameters.html new file mode 100644 index 000000000..afb8640b7 --- /dev/null +++ b/annotations/user-parameters.html @@ -0,0 +1,109 @@ + + + + + + USER_PARAMETERS Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

USER_PARAMETERS

Also known as

user_params (with or without @ prefix)

Enable passing user claims as function parameters for the endpoint.

Syntax

code
@user_parameters
+@user_params

Examples

Basic User Parameters

sql
sql
create function get_user_params(
+    _user_id text,
+    _user_name text,
+    _user_roles text[]
+)
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    _user_id::int,
+    _user_name,
+    _user_roles;
+end;
+
+comment on function get_user_params(text, text, text[]) is '
+@authorize
+@user_params
+';

Equivalent as a SQL file endpoint (sql/get-user-params.sql):

sql
sql
/*
+HTTP GET
+@authorize
+@user_params
+@param $1 user_id text
+@param $2 user_name text
+@param $3 user_roles text[]
+*/
+select $1::int as user_id, $2 as user_name, $3 as user_roles;

With Default Values (for unauthenticated access)

sql
sql
create function get_user_params_optional(
+    _user_id text = null,
+    _user_name text = 'anonymous',
+    _user_roles text[] = array[]::text[]
+)
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    _user_id::int,
+    _user_name,
+    _user_roles;
+end;
+
+comment on function get_user_params_optional(text, text, text[]) is '
+@user_params
+';

Access All Claims as JSON

sql
sql
create function get_user_ip_and_full_claims(
+    _ip_address text,
+    _user_claims json
+)
+returns table (
+    ip_address text,
+    user_claims json
+)
+language sql
+begin atomic;
+select
+    _ip_address,
+    _user_claims;
+end;
+
+comment on function get_user_ip_and_full_claims(text, json) is '
+@authorize
+@user_params
+';

Combined with User Context

sql
sql
comment on function user_profile() is
+'HTTP GET
+@authorize
+@user_context
+@user_parameters';

Behavior

  • Automatically injects user claim values into matching function parameters before execution
  • Parameters are matched by name according to ParameterNameClaimsMapping configuration
  • Default behavior for all endpoints can be configured via UseUserParameters
  • Parameters with default values work without authentication; claim values override defaults when authenticated
  • Parameters not found in claims use their default values or null
  • Claim values are always passed as text type. For multi-value claims (like roles), values are passed as text[]. PostgreSQL handles type coercion to your parameter types.

Default Parameter Mapping

Parameter NameClaimDescription
_user_iduser_idUser identifier
_user_nameuser_nameUsername
_user_rolesuser_rolesUser roles (array)
_ip_address-Client IP address
_user_claims-All claims serialized as JSON

Differences from USER_CONTEXT

FeatureUSER_PARAMETERSUSER_CONTEXT
Access methodFunction parameterscurrent_setting()
Works without authYes (with defaults)Yes (returns empty)
Type safetyPostgreSQL enforcedManual casting required
PerformanceSlightly fasterSlightly slower
  • USER_CONTEXT - Access user claims via PostgreSQL session context variables
  • AUTHORIZE - Require authentication

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/validate.html b/annotations/validate.html new file mode 100644 index 000000000..f3a890558 --- /dev/null +++ b/annotations/validate.html @@ -0,0 +1,136 @@ + + + + + + VALIDATE Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

VALIDATE

Validate endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

Keywords

@validate, validate

Syntax

code
@validate <parameter_name> using <rule_name>
+@validate <parameter_name> using <rule1>, <rule2>, <rule3>, ...
  • parameter_name - The parameter to validate. Can use either the original PostgreSQL name (_email) or the converted camelCase name (email). Matching is case-insensitive.
  • rule_name - The name of a validation rule defined in ValidationOptions configuration.

Multiple rules can be specified as comma-separated values or on separate lines.

Examples

Single Rule Validation

sql
sql
create function get_user(_user_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where id = _user_id;
+end;
+
+comment on function get_user(int) is '
+HTTP GET
+@validate _user_id using not_null
+';

Equivalent as a SQL file endpoint (sql/get-user.sql):

sql
sql
/*
+HTTP GET
+@validate user_id using not_null
+@param $1 user_id int
+*/
+select row_to_json(u) from users u where id = $1;

Multiple Rules on One Parameter

sql
sql
create function update_email(_user_id int, _email text)
+returns json
+language plpgsql
+as $$
+begin
+    update users set email = _email where id = _user_id;
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function update_email(int, text) is '
+HTTP PUT
+@validate _user_id using not_null
+@validate _email using required, email
+';

The _email parameter must pass both required (not null and not empty) and email (regex pattern) validation.

Multiple Parameters

sql
sql
create function register_user(_email text, _password text, _name text)
+returns json
+language plpgsql
+as $$
+begin
+    insert into users (email, password_hash, name)
+    values (_email, crypt(_password, gen_salt('bf')), _name);
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function register_user(text, text, text) is '
+HTTP POST
+@validate _email using required, email
+@validate _password using required
+@validate _name using not_empty
+';

Using Converted Parameter Names

Parameter names can use the converted camelCase format:

sql
sql
create function create_product(_product_name text, _unit_price numeric)
+returns json
+language plpgsql
+as $$
+begin
+    insert into products (name, price) values (_product_name, _unit_price);
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function create_product(text, numeric) is '
+HTTP POST
+@validate productName using required
+@validate unitPrice using not_null
+';

Both productName and _product_name refer to the same parameter.

With Authorization

Validation works alongside other annotations:

sql
sql
create function update_profile(_user_id int, _bio text, _website text)
+returns json
+language plpgsql
+as $$
+begin
+    update profiles set bio = _bio, website = _website where user_id = _user_id;
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function update_profile(int, text, text) is '
+HTTP PUT
+@authorize
+@user_params
+@validate _bio using not_empty
+';

Default Rules

Four validation rules are available by default without additional configuration:

Rule NameTypeDescription
not_nullNotNullValue cannot be null
not_emptyNotEmptyValue cannot be empty string (nulls pass)
requiredRequiredValue cannot be null or empty
emailRegexValue must match email pattern

Custom Rules

Custom validation rules are defined in ValidationOptions configuration:

json
json
{
+  "ValidationOptions": {
+    "Rules": {
+      "phone": {
+        "Type": "Regex",
+        "Pattern": "^\\+?[1-9]\\d{1,14}$",
+        "Message": "Parameter '{0}' must be a valid phone number",
+        "StatusCode": 400
+      },
+      "password_min": {
+        "Type": "MinLength",
+        "MinLength": 8,
+        "Message": "Password must be at least 8 characters"
+      }
+    }
+  }
+}

Then use in annotations:

sql
sql
comment on function create_user(text, text, text) is '
+HTTP POST
+@validate _phone using phone
+@validate _password using required, password_min
+';

Behavior

  • Validation runs before database connections are opened
  • Rules are evaluated in the order specified
  • Validation stops on first failure
  • Failed validation returns the configured HTTP status code and error message
  • Parameter names are matched case-insensitively
  • Original PostgreSQL names (_email) and converted names (email) both work

Error Response

When validation fails, the endpoint returns an error response:

json
json
{
+  "title": "Parameter '_email' must be a valid email address",
+  "status": 400,
+  "detail": null
+}

The HTTP status code and message are configured per rule in ValidationOptions.

See Also

Comments

+ + + + \ No newline at end of file diff --git a/annotations/void.html b/annotations/void.html new file mode 100644 index 000000000..82b12829e --- /dev/null +++ b/annotations/void.html @@ -0,0 +1,55 @@ + + + + + + VOID Annotation | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
Skip to content
AI-assisted, verified against source

VOID

Also known as

void_result (with or without @ prefix)

Force an endpoint to return 204 No Content instead of a JSON response. All statements are executed for side effects only.

Available since version 3.12.0.

Syntax

code
@void
+@void_result

Examples

Multi-Command Side Effects

Useful when all statements are side-effect-only (e.g., set_config calls followed by a DO block):

sql
sql
/* HTTP POST
+@void
+@param $1 message_text text
+@param $2 _user_id text = null
+*/
+select set_config('app.message', $1, true);
+select set_config('app.user_id', $2, true);
+do $$ begin
+    insert into messages (user_id, text)
+    values (current_setting('app.user_id')::int, current_setting('app.message'));
+end; $$;

Without @void, this returns {"result1":"...","result2":"...","result3":-1}. With @void, it returns 204 No Content.

This eliminates the need to add @skip to every individual statement.

Single-Command Void

Also works on single-command endpoints:

sql
sql
-- HTTP POST
+-- @void
+-- @param $1 key text
+-- @param $2 value text
+select set_config($1, $2, true);

Function Endpoints

Works on function and procedure endpoints too:

sql
sql
comment on function process_data(int) is '
+HTTP POST
+void
+';

Behavior

  • All statements are executed normally via ExecuteNonQuery
  • The response status is 204 No Content with an empty body
  • No JSON wrapping, no result keys, no rows-affected counts
  • Works on all endpoint types: functions, procedures, and SQL file endpoints
  • For multi-command SQL files, all statements execute sequentially — if any fails, the request fails
  • The Describe step still runs at startup — use @returns void instead if the statement would fail Describe

Comments

+ + + + \ No newline at end of file diff --git a/assets/about.md.tQJT3Mf7.js b/assets/about.md.tQJT3Mf7.js new file mode 100644 index 000000000..5328024c2 --- /dev/null +++ b/assets/about.md.tQJT3Mf7.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"About This Website","titleTemplate":"NpgsqlRest","description":"On AI tools, the people behind NpgsqlRest, and how this documentation was built.","frontmatter":{"layout":"doc","outline":false,"title":"About This Website","titleTemplate":"NpgsqlRest","description":"On AI tools, the people behind NpgsqlRest, and how this documentation was built."},"headers":[],"relativePath":"about.md","filePath":"about.md"}'),i={name:"about.md"};function r(n,e,h,d,l,u){return s(),a("div",null,e[0]||(e[0]=[o('

About This Website

This website is built and maintained with substantial help from AI tools — primarily Claude. Pages carry an "AI-assisted · verified against source" badge to make that explicit. A few blog posts written entirely by hand carry a "Human Written" badge instead. The badges link here.

How These Docs Are Made

The docs are maintained the same way NpgsqlRest itself proposes you build software: AI does the writing, machines verify the facts. Configuration keys, defaults, and annotation behavior are checked against the source code of the documented version — when the code and the docs disagree, the code wins and the docs get fixed. Accuracy is the contract; authorship is just tooling.

The project itself is a different story. The C# library, parser, code generator, and runtime are hand-written — more than two years of evenings and weekends — and covered by 2,200+ integration tests running against real PostgreSQL. The AI-assisted part is the website you're reading; the thing it documents is not.

We think that's the honest division of labor, and we'd rather label it than fake it. If the approach sounds familiar, it's because it is the product's whole thesis: declare the intent, let machines verify the result.

Look, I'm just a guy who likes SQL, that's all.

About the Author

NpgsqlRest is built and maintained by Vedran Bilopavlović — battle-tested in production, MIT-licensed, no paid tier, no telemetry.

Feedback

Found a mistake or an inaccuracy? Comments are open at the bottom of every page and go straight to the maintainer — that feedback loop is how AI-assisted docs stay accurate. Bug reports and feature requests live on GitHub; security issues have a private reporting channel.

',11)]))}const b=t(i,[["render",r]]);export{p as __pageData,b as default}; diff --git a/assets/about.md.tQJT3Mf7.lean.js b/assets/about.md.tQJT3Mf7.lean.js new file mode 100644 index 000000000..917066820 --- /dev/null +++ b/assets/about.md.tQJT3Mf7.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"About This Website","titleTemplate":"NpgsqlRest","description":"On AI tools, the people behind NpgsqlRest, and how this documentation was built.","frontmatter":{"layout":"doc","outline":false,"title":"About This Website","titleTemplate":"NpgsqlRest","description":"On AI tools, the people behind NpgsqlRest, and how this documentation was built."},"headers":[],"relativePath":"about.md","filePath":"about.md"}'),i={name:"about.md"};function r(n,e,h,d,l,u){return s(),a("div",null,e[0]||(e[0]=[o("",11)]))}const b=t(i,[["render",r]]);export{p as __pageData,b as default}; diff --git a/assets/annotations_allow-anonymous.md.B0UuFGg2.js b/assets/annotations_allow-anonymous.md.B0UuFGg2.js new file mode 100644 index 000000000..99c55bab2 --- /dev/null +++ b/assets/annotations_allow-anonymous.md.B0UuFGg2.js @@ -0,0 +1,22 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"ALLOW_ANONYMOUS Annotation","titleTemplate":"NpgsqlRest","description":"Allow unauthenticated access to specific PostgreSQL REST API endpoints. Override global authorization requirements.","frontmatter":{"outline":[2,3],"title":"ALLOW_ANONYMOUS Annotation","titleTemplate":"NpgsqlRest","description":"Allow unauthenticated access to specific PostgreSQL REST API endpoints. Override global authorization requirements.","head":[["meta",{"name":"keywords","content":"npgsqlrest allow anonymous, public endpoint, unauthenticated access, anonymous api, public api endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest ALLOW_ANONYMOUS Annotation"}],["meta",{"property":"og:description","content":"Allow unauthenticated access to specific endpoints, overriding global authorization."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/allow-anonymous.md","filePath":"annotations/allow-anonymous.md"}'),t={name:"annotations/allow-anonymous.md"};function l(o,s,p,r,c,h){return n(),i("div",null,s[0]||(s[0]=[e(`

ALLOW_ANONYMOUS

Also known as

anonymous, allow_anon, anon (with or without @ prefix)

Allow unauthenticated access to the endpoint, overriding the global RequiresAuthorization setting.

Syntax

code
@allow_anonymous

Examples

Public Endpoint

sql
sql
create function get_public_info()
+returns json
+language sql
+begin atomic;
+select '{"version": "1.0"}'::json;
+end;
+
+comment on function get_public_info() is
+'HTTP GET
+@allow_anonymous';

Equivalent as a SQL file endpoint (sql/get-public-info.sql):

sql
sql
-- HTTP GET
+-- @allow_anonymous
+select '{"version": "1.0"}'::json;

Short Form

sql
sql
comment on function health_check() is
+'HTTP GET
+@anon';

Public Read, Protected Write Pattern

sql
sql
-- Anyone can read
+comment on function get_products() is
+'HTTP GET
+@allow_anonymous';
+
+-- Only authenticated users can create
+comment on function create_product(text, numeric) is
+'HTTP POST
+@authorize';

Behavior

`,20)]))}const k=a(t,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_allow-anonymous.md.B0UuFGg2.lean.js b/assets/annotations_allow-anonymous.md.B0UuFGg2.lean.js new file mode 100644 index 000000000..8d8598a4f --- /dev/null +++ b/assets/annotations_allow-anonymous.md.B0UuFGg2.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"ALLOW_ANONYMOUS Annotation","titleTemplate":"NpgsqlRest","description":"Allow unauthenticated access to specific PostgreSQL REST API endpoints. Override global authorization requirements.","frontmatter":{"outline":[2,3],"title":"ALLOW_ANONYMOUS Annotation","titleTemplate":"NpgsqlRest","description":"Allow unauthenticated access to specific PostgreSQL REST API endpoints. Override global authorization requirements.","head":[["meta",{"name":"keywords","content":"npgsqlrest allow anonymous, public endpoint, unauthenticated access, anonymous api, public api endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest ALLOW_ANONYMOUS Annotation"}],["meta",{"property":"og:description","content":"Allow unauthenticated access to specific endpoints, overriding global authorization."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/allow-anonymous.md","filePath":"annotations/allow-anonymous.md"}'),t={name:"annotations/allow-anonymous.md"};function l(o,s,p,r,c,h){return n(),i("div",null,s[0]||(s[0]=[e("",20)]))}const k=a(t,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_authorize.md.CK7v-SQu.js b/assets/annotations_authorize.md.CK7v-SQu.js new file mode 100644 index 000000000..a87be45ba --- /dev/null +++ b/assets/annotations_authorize.md.CK7v-SQu.js @@ -0,0 +1,64 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"AUTHORIZE Annotation","titleTemplate":"NpgsqlRest","description":"Require authentication for PostgreSQL REST API endpoints. Configure role-based access control with single or multiple required roles.","frontmatter":{"outline":[2,3],"title":"AUTHORIZE Annotation","titleTemplate":"NpgsqlRest","description":"Require authentication for PostgreSQL REST API endpoints. Configure role-based access control with single or multiple required roles.","head":[["meta",{"name":"keywords","content":"npgsqlrest authorize, postgresql api authentication, role based access, require authentication, api authorization"}],["meta",{"property":"og:title","content":"NpgsqlRest AUTHORIZE Annotation"}],["meta",{"property":"og:description","content":"Require authentication and configure role-based access for PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/authorize.md","filePath":"annotations/authorize.md"}'),l={name:"annotations/authorize.md"};function t(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e(`

AUTHORIZE

Also known as

authorized, requires_authorization (with or without @ prefix)

Require authentication for the endpoint. Optionally restrict access by roles, user names, or user IDs.

Syntax

code
@authorize
+@authorize <value1>, <value2>, <value3>, ...

Values can be role names, user names, or user IDs. Space-separated lists are also valid: @authorize admin editor john

Examples

Require Any Authenticated User

sql
sql
create function get_my_profile()
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where u.id = current_user_id();
+end;
+
+comment on function get_my_profile() is
+'HTTP GET
+@authorize';

Equivalent as a SQL file endpoint (sql/get-my-profile.sql):

sql
sql
-- HTTP GET
+-- @authorize
+select row_to_json(u) from users u where u.id = current_user_id();

Unauthenticated requests receive 401 Unauthorized.

Alternative Keywords

sql
sql
-- All of these are equivalent
+comment on function func1() is 'HTTP
+@authorize';
+
+comment on function func2() is 'HTTP
+@authorized';
+
+comment on function func3() is 'HTTP
+@requires_authorization';

Require Specific Role

sql
sql
create function delete_user(_id int)
+returns void
+language sql
+begin atomic;
+delete from users where id = _id;
+end;
+
+comment on function delete_user(int) is
+'HTTP DELETE
+@authorize admin';

Only users with the admin role can access this endpoint.

Authorize by User Name

Available since version 3.11.1

sql
sql
create function get_my_profile()
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where u.id = current_user_id();
+end;
+
+comment on function get_my_profile() is
+'HTTP GET
+@authorize john';

Only the user with user name john can access this endpoint. Matches against the DefaultNameClaimType claim.

Authorize by User ID

Available since version 3.11.1

sql
sql
create function get_account()
+returns json
+language sql
+begin atomic;
+select row_to_json(a) from accounts a where a.user_id = current_user_id();
+end;
+
+comment on function get_account() is
+'HTTP GET
+@authorize user123';

Only the user with user ID user123 can access this endpoint. Matches against the DefaultUserIdClaimType claim.

Multiple Roles

sql
sql
create function manage_content(_action text, _id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function manage_content(text, int) is
+'HTTP POST
+@authorize admin, editor, moderator';

Users must have at least one of the specified roles.

Mix of Roles and User Identifiers

Available since version 3.11.1

sql
sql
comment on function get_data() is
+'HTTP GET
+@authorize admin, user123, jane';

Access is granted if the user matches any of the specified values — whether it's a role name, user name, or user ID. Each value is checked against all three claim types (DefaultRoleClaimType, DefaultNameClaimType, DefaultUserIdClaimType).

Authorize Before HTTP

The order of annotations doesn't matter:

sql
sql
comment on function protected_func() is
+'@authorize admin
+HTTP GET';

Authorize on Separate Line

sql
sql
comment on function another_protected() is
+'HTTP
+
+@authorize';

Behavior

See Also

`,45)]))}const u=a(l,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_authorize.md.CK7v-SQu.lean.js b/assets/annotations_authorize.md.CK7v-SQu.lean.js new file mode 100644 index 000000000..efcf606de --- /dev/null +++ b/assets/annotations_authorize.md.CK7v-SQu.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"AUTHORIZE Annotation","titleTemplate":"NpgsqlRest","description":"Require authentication for PostgreSQL REST API endpoints. Configure role-based access control with single or multiple required roles.","frontmatter":{"outline":[2,3],"title":"AUTHORIZE Annotation","titleTemplate":"NpgsqlRest","description":"Require authentication for PostgreSQL REST API endpoints. Configure role-based access control with single or multiple required roles.","head":[["meta",{"name":"keywords","content":"npgsqlrest authorize, postgresql api authentication, role based access, require authentication, api authorization"}],["meta",{"property":"og:title","content":"NpgsqlRest AUTHORIZE Annotation"}],["meta",{"property":"og:description","content":"Require authentication and configure role-based access for PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/authorize.md","filePath":"annotations/authorize.md"}'),l={name:"annotations/authorize.md"};function t(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e("",45)]))}const u=a(l,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_basic-auth-command.md.Csb6mha2.js b/assets/annotations_basic-auth-command.md.Csb6mha2.js new file mode 100644 index 000000000..ba2d6c376 --- /dev/null +++ b/assets/annotations_basic-auth-command.md.Csb6mha2.js @@ -0,0 +1,145 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"BASIC_AUTH_COMMAND Annotation","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL command for Basic Authentication credential validation. Custom user lookup and claim generation.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH_COMMAND Annotation","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL command for Basic Authentication credential validation. Custom user lookup and claim generation.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth command, credential validation, user lookup postgresql, authentication command"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH_COMMAND Annotation"}],["meta",{"property":"og:description","content":"Configure PostgreSQL command for Basic Authentication credential validation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth-command.md","filePath":"annotations/basic-auth-command.md"}'),l={name:"annotations/basic-auth-command.md"};function t(p,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

BASIC_AUTH_COMMAND

Also known as

basic_authentication_command, challenge_command (with or without @ prefix)

Set the PostgreSQL command used to validate Basic Authentication credentials and return user claims.

Syntax

code
@challenge_command = <sql-command>
+@basic_auth_command = <sql-command>

Command Parameters

The challenge command receives up to 5 parameters in the following order:

ParameterTypeDescription
$1textUsername from the Authorization header
$2textPassword from the Authorization header (plain text)
$3booleanPre-validation result: true if password matched annotation credentials, false if not, null if no credentials were configured in the annotation
$4textRealm name (from annotation or default NpgsqlRest)
$5textRequest path (e.g., /api/my-endpoint)

Return Value

The challenge command result set is interpreted exactly the same as the LOGIN endpoint. This includes support for special columns and claim mapping.

Special Columns

Four special column names control authentication behavior (configurable in AuthenticationOptions):

ColumnDefault NameTypePurpose
Statusstatusboolean or intControls success/failure. true or 200 = success, false or other status code = failure
SchemeschemetextAuthentication scheme name for sign-in
BodybodytextResponse body message (for schemes that don't write body)
HashhashtextPassword hash for verification by NpgsqlRest

Authentication Success

Return a row with claim columns. Column names become claim types, values become claim values:

sql
sql
-- Successful authentication returns claims
+select
+    1 as name_identifier,        -- becomes ClaimTypes.NameIdentifier
+    'john_doe' as name,          -- becomes ClaimTypes.Name
+    'admin' as role;             -- becomes ClaimTypes.Role

Authentication Failure

Return either:

sql
sql
-- Method 1: Return no rows
+select * from users where false;
+
+-- Method 2: Return status = false (boolean)
+select false as status, null as name;
+
+-- Method 3: Return status code (integer)
+select 401 as status, 'Invalid credentials' as body;

For complete details on result set interpretation, see LOGIN - Special Columns.

Examples

Basic Challenge Command

sql
sql
-- Validation function that checks credentials in the database
+create function auth_challenge_command(
+    _user text,
+    _password text,
+    _valid boolean,
+    _realm text,
+    _path text
+)
+returns table (
+    name_identifier int,
+    name text,
+    role text
+)
+language sql
+begin atomic;
+select
+    u.id,
+    u.username,
+    u.role
+from users u
+where u.username = _user
+  and u.password_hash = crypt(_password, u.password_hash);
+end;
+
+-- Endpoint using the challenge command
+create function protected_resource(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function protected_resource(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+';

Equivalent as a SQL file endpoint (sql/protected-resource.sql):

sql
sql
/*
+HTTP GET
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+@param $1 user_claims
+*/
+select $1;

Challenge Command with Pre-Validated Password

When basic_auth includes credentials, the $3 parameter indicates if the password already matched:

sql
sql
create function auth_with_preval(
+    _user text,
+    _password text,
+    _valid boolean,     -- true if password matched annotation credentials
+    _realm text,
+    _path text
+)
+returns table (
+    name_identifier int,
+    name text,
+    password text,
+    valid boolean,
+    realm text,
+    path text
+)
+language sql
+begin atomic;
+select 1, _user, _password, _valid, _realm, _path;
+end;
+
+-- Generate hash: ./npgsqlrest --hash my_password
+-- Output: Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+
+create function get_basic_auth_challenge_command_pass(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function get_basic_auth_challenge_command_pass(json) is '
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@challenge_command = select * from auth_with_preval($1, $2, $3, $4, $5)
+@user_params
+';

Test with:

bash
bash
# Generate header: ./npgsqlrest --basic_auth my_name my_password
+curl -H "Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==" \\
+     http://localhost:5000/api/get-basic-auth-challenge-command-pass
+
+# Returns: {"name_identifier":"1","name":"my_name","password":"my_password","valid":"True","realm":"NpgsqlRest","path":"/api/get-basic-auth-challenge-command-pass"}

Challenge Command That Denies Access

sql
sql
create function auth_challenge_command_failed(
+    _user text,
+    _password text,
+    _valid boolean,
+    _realm text,
+    _path text
+)
+returns table (
+    status boolean,          -- First column named 'status' controls auth
+    name_identifier int,
+    name text
+)
+language sql
+begin atomic;
+select false, 1, _user;      -- status = false means denied
+end;
+
+create function denied_endpoint(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function denied_endpoint(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command_failed($1, $2, $3, $4, $5)
+@user_params
+';

This will always return 401 Unauthorized because the status column is false.

Challenge Command Without Annotation Credentials

When basic_auth is used without credentials, $3 will be null:

sql
sql
create function get_basic_auth_challenge_command(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function get_basic_auth_challenge_command(json) is '
+@basic_auth
+@challenge_command = select * from auth_challenge_command($1, $2, $3, $4, $5)
+@user_params
+';

Test with:

bash
bash
# Any username/password combination will be passed to the challenge command
+curl -H "Authorization: Basic eHh4Onl5eQ==" \\  # xxx:yyy
+     http://localhost:5000/api/get-basic-auth-challenge-command
+
+# Returns: {"name_identifier":"1","name":"xxx","password":"yyy","valid":null,"realm":"NpgsqlRest","path":"/api/get-basic-auth-challenge-command"}

Behavior

`,46)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_basic-auth-command.md.Csb6mha2.lean.js b/assets/annotations_basic-auth-command.md.Csb6mha2.lean.js new file mode 100644 index 000000000..4762e1023 --- /dev/null +++ b/assets/annotations_basic-auth-command.md.Csb6mha2.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"BASIC_AUTH_COMMAND Annotation","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL command for Basic Authentication credential validation. Custom user lookup and claim generation.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH_COMMAND Annotation","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL command for Basic Authentication credential validation. Custom user lookup and claim generation.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth command, credential validation, user lookup postgresql, authentication command"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH_COMMAND Annotation"}],["meta",{"property":"og:description","content":"Configure PostgreSQL command for Basic Authentication credential validation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth-command.md","filePath":"annotations/basic-auth-command.md"}'),l={name:"annotations/basic-auth-command.md"};function t(p,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",46)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_basic-auth-realm.md.DsMX0SDT.js b/assets/annotations_basic-auth-realm.md.DsMX0SDT.js new file mode 100644 index 000000000..f42f27505 --- /dev/null +++ b/assets/annotations_basic-auth-realm.md.DsMX0SDT.js @@ -0,0 +1,36 @@ +import{_ as s,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"BASIC_AUTH_REALM Annotation","titleTemplate":"NpgsqlRest","description":"Set HTTP Basic Authentication realm name for PostgreSQL REST API endpoints. Customize the authentication prompt.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH_REALM Annotation","titleTemplate":"NpgsqlRest","description":"Set HTTP Basic Authentication realm name for PostgreSQL REST API endpoints. Customize the authentication prompt.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth realm, authentication realm, www-authenticate header, basic auth prompt"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH_REALM Annotation"}],["meta",{"property":"og:description","content":"Set HTTP Basic Authentication realm name for the authentication prompt."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth-realm.md","filePath":"annotations/basic-auth-realm.md"}'),l={name:"annotations/basic-auth-realm.md"};function t(p,a,r,h,c,o){return e(),i("div",null,a[0]||(a[0]=[n(`

BASIC_AUTH_REALM

Also known as

basic_authentication_realm, realm (with or without @ prefix)

Set the HTTP Basic Authentication realm name.

Syntax

code
@realm = <realm-name>
+@basic_auth_realm = <realm-name>

Default Value

If not specified, the default realm is NpgsqlRest.

Examples

Set Realm Name

sql
sql
create function protected_api()
+returns json
+language sql
+begin atomic;
+select '{"data": "secret"}'::json;
+end;
+
+comment on function protected_api() is '
+HTTP GET
+@basic_auth admin_user hashed_password_here
+@realm = MyApplication
+';

Equivalent as a SQL file endpoint (sql/protected-api.sql):

sql
sql
/*
+HTTP GET
+@basic_auth admin_user hashed_password_here
+@realm = MyApplication
+*/
+select '{"data": "secret"}'::json;

When authentication fails, the response header will be:

code
WWW-Authenticate: Basic realm="MyApplication"

Alternative Keyword

sql
sql
comment on function admin_area() is '
+HTTP GET
+@basic_auth admin hashed_password_here
+@basic_auth_realm = Admin Area
+';

With Challenge Command

sql
sql
create function secure_endpoint(
+    _user_claims json
+)
+returns text
+language sql
+begin atomic;
+select _user_claims;
+end;
+
+comment on function secure_endpoint(json) is '
+@basic_auth
+@challenge_command = select * from validate_user($1, $2, $3, $4, $5)
+@realm = SecureZone
+@user_params
+';

The realm value (SecureZone) is passed as the 4th parameter ($4) to the challenge command.

Behavior

Realm Resolution Order

The realm is determined in the following order:

  1. Endpoint-specific realm annotation (highest priority)
  2. Global BasicAuth.Realm configuration option
  3. Default value: NpgsqlRest
`,29)]))}const u=s(l,[["render",t]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_basic-auth-realm.md.DsMX0SDT.lean.js b/assets/annotations_basic-auth-realm.md.DsMX0SDT.lean.js new file mode 100644 index 000000000..843ac730a --- /dev/null +++ b/assets/annotations_basic-auth-realm.md.DsMX0SDT.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"BASIC_AUTH_REALM Annotation","titleTemplate":"NpgsqlRest","description":"Set HTTP Basic Authentication realm name for PostgreSQL REST API endpoints. Customize the authentication prompt.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH_REALM Annotation","titleTemplate":"NpgsqlRest","description":"Set HTTP Basic Authentication realm name for PostgreSQL REST API endpoints. Customize the authentication prompt.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth realm, authentication realm, www-authenticate header, basic auth prompt"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH_REALM Annotation"}],["meta",{"property":"og:description","content":"Set HTTP Basic Authentication realm name for the authentication prompt."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth-realm.md","filePath":"annotations/basic-auth-realm.md"}'),l={name:"annotations/basic-auth-realm.md"};function t(p,a,r,h,c,o){return e(),i("div",null,a[0]||(a[0]=[n("",29)]))}const u=s(l,[["render",t]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_basic-auth.md.ClrT2h43.js b/assets/annotations_basic-auth.md.ClrT2h43.js new file mode 100644 index 000000000..28dd3300a --- /dev/null +++ b/assets/annotations_basic-auth.md.ClrT2h43.js @@ -0,0 +1,71 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"BASIC_AUTH Annotation","titleTemplate":"NpgsqlRest","description":"Enable HTTP Basic Authentication for PostgreSQL REST API endpoints. Require username and password in Authorization header.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH Annotation","titleTemplate":"NpgsqlRest","description":"Enable HTTP Basic Authentication for PostgreSQL REST API endpoints. Require username and password in Authorization header.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth, http basic authentication, authorization header, password authentication api"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH Annotation"}],["meta",{"property":"og:description","content":"Enable HTTP Basic Authentication requiring username and password."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth.md","filePath":"annotations/basic-auth.md"}'),l={name:"annotations/basic-auth.md"};function t(p,s,h,r,c,o){return n(),i("div",null,s[0]||(s[0]=[e(`

BASIC_AUTH

Also known as

basic_authentication (with or without @ prefix)

Enable HTTP Basic Authentication for the endpoint.

Syntax

code
@basic_auth
+@basic_auth <username> <password_hash>

Generating Password Hashes

Use the NpgsqlRest CLI to generate password hashes:

bash
bash
# Generate a hash for a password
+./npgsqlrest --hash my_password
+
+# Output example:
+# Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t

Generating Authorization Headers

Use the NpgsqlRest CLI to generate Base64-encoded Basic Auth headers for testing:

bash
bash
# Generate Authorization header value
+./npgsqlrest --basic_auth my_name my_password
+
+# Output example:
+# Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==

Examples

Basic Auth Without Credentials (Requires Challenge Command)

When basic_auth is used without credentials, a challenge_command must be configured to validate the user:

sql
sql
create function get_basic_auth_no_creds(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+comment on function get_basic_auth_no_creds(text) is '
+@basic_auth
+@user_params
+';

Note: Without credentials and without a challenge_command, all requests will return 401 Unauthorized.

Basic Auth With Credentials

sql
sql
create function get_basic_auth_user(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+-- Generate hash: ./npgsqlrest --hash my_password
+-- Output: Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+
+comment on function get_basic_auth_user(text) is '
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@user_params
+';

Equivalent as a SQL file endpoint (sql/get-basic-auth-user.sql):

sql
sql
/*
+HTTP GET
+@basic_auth my_name Myb55+6lW6iiUOI3opLkysOaS8J0NNIuQ+qE2SGaKs3r62ngDJROrhX75+zmLC7t
+@user_params
+@param $1 user_name
+*/
+select $1;

Test with:

bash
bash
# Generate header: ./npgsqlrest --basic_auth my_name my_password
+curl -H "Authorization: Basic bXlfbmFtZTpteV9wYXNzd29yZA==" \\
+     http://localhost:5000/api/get-basic-auth-user
+# Returns: my_name

Multiple Users

You can define multiple users by adding multiple basic_auth annotations:

sql
sql
create function get_basic_auth_multiple_users(
+    _user_name text = null -- mapped to name claim
+)
+returns text
+language sql
+begin atomic;
+select _user_name;
+end;
+
+-- Generate hashes:
+-- ./npgsqlrest --hash pass1  =>  um4K594nL6pBQx2el0lcbKKLADof1k9atRYKy+G14f6BQPtSCkwO6qz1wJ1d9Tx/
+-- ./npgsqlrest --hash pass2  =>  TIDVxenk9gSqApyI82XDuqUaigQ5OdBIecfRtq7wFWtHT3Ffx2s+noIjvFCAw90z
+
+comment on function get_basic_auth_multiple_users(text) is '
+@basic_auth user1 um4K594nL6pBQx2el0lcbKKLADof1k9atRYKy+G14f6BQPtSCkwO6qz1wJ1d9Tx/
+@basic_auth user2 TIDVxenk9gSqApyI82XDuqUaigQ5OdBIecfRtq7wFWtHT3Ffx2s+noIjvFCAw90z
+@user_params
+';

Test with:

bash
bash
# ./npgsqlrest --basic_auth user1 pass1
+curl -H "Authorization: Basic dXNlcjE6cGFzczE=" \\
+     http://localhost:5000/api/get-basic-auth-multiple-users
+# Returns: user1
+
+# ./npgsqlrest --basic_auth user2 pass2
+curl -H "Authorization: Basic dXNlcjI6cGFzczI=" \\
+     http://localhost:5000/api/get-basic-auth-multiple-users
+# Returns: user2

Behavior

SSL Requirements

Basic Authentication transmits credentials encoded (not encrypted). The behavior when SSL is disabled is controlled by the SslRequirement configuration:

See Also

`,39)]))}const u=a(l,[["render",t]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_basic-auth.md.ClrT2h43.lean.js b/assets/annotations_basic-auth.md.ClrT2h43.lean.js new file mode 100644 index 000000000..6fd293749 --- /dev/null +++ b/assets/annotations_basic-auth.md.ClrT2h43.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"BASIC_AUTH Annotation","titleTemplate":"NpgsqlRest","description":"Enable HTTP Basic Authentication for PostgreSQL REST API endpoints. Require username and password in Authorization header.","frontmatter":{"outline":[2,3],"title":"BASIC_AUTH Annotation","titleTemplate":"NpgsqlRest","description":"Enable HTTP Basic Authentication for PostgreSQL REST API endpoints. Require username and password in Authorization header.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth, http basic authentication, authorization header, password authentication api"}],["meta",{"property":"og:title","content":"NpgsqlRest BASIC_AUTH Annotation"}],["meta",{"property":"og:description","content":"Enable HTTP Basic Authentication requiring username and password."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/basic-auth.md","filePath":"annotations/basic-auth.md"}'),l={name:"annotations/basic-auth.md"};function t(p,s,h,r,c,o){return n(),i("div",null,s[0]||(s[0]=[e("",39)]))}const u=a(l,[["render",t]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_body-parameter-name.md.BfhRegXI.js b/assets/annotations_body-parameter-name.md.BfhRegXI.js new file mode 100644 index 000000000..e4719f078 --- /dev/null +++ b/assets/annotations_body-parameter-name.md.BfhRegXI.js @@ -0,0 +1,27 @@ +import{_ as s,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"BODY_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Specify which PostgreSQL function parameter receives the raw HTTP request body. Handle JSON payloads in SQL functions.","frontmatter":{"outline":[2,3],"title":"BODY_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Specify which PostgreSQL function parameter receives the raw HTTP request body. Handle JSON payloads in SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest body parameter, request body parameter, json body postgresql, raw body function, http payload sql"}],["meta",{"property":"og:title","content":"NpgsqlRest BODY_PARAMETER_NAME Annotation"}],["meta",{"property":"og:description","content":"Specify which function parameter receives the raw HTTP request body."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/body-parameter-name.md","filePath":"annotations/body-parameter-name.md"}'),t={name:"annotations/body-parameter-name.md"};function l(p,a,r,o,h,d){return i(),e("div",null,a[0]||(a[0]=[n(`

BODY_PARAMETER_NAME

Also known as

body_param_name (with or without @ prefix)

Specify which parameter receives the raw request body.

Syntax

code
@body_parameter_name <param-name>

Examples

Custom Body Parameter

sql
sql
create function process_payload(_metadata json, _payload text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_payload(json, text) is
+'HTTP POST
+@body_parameter_name _payload';

Equivalent as a SQL file endpoint (sql/process-payload.sql):

sql
sql
/*
+HTTP POST
+@body_parameter_name payload
+@param $1 metadata json
+@param $2 payload text
+*/
+select process_payload($1, $2);

JSON Body Parameter

sql
sql
create function handle_webhook(_body json)
+returns void
+language sql
+begin atomic;
+...;
+end;
+
+comment on function handle_webhook(json) is
+'HTTP POST
+@body_parameter_name _body';

Behavior

Matching Rules

The parameter name is matched case-insensitively and accepts any of the parameter's names:

New in 3.18.2

Before 3.18.2 the value was force-lowercased and compared case-sensitively, so the camelCase converted name never matched, and the expanded signature name of an HTTP Custom Type field matched nothing at all. From 3.18.2 the same matching rule is applied consistently by request handling and every code generator (TypeScript client, HTTP file, OpenAPI), so they no longer disagree about which parameter carries the body.

Redirecting an HTTP Custom Type field into a proxy body

A common use is forwarding a large field — such as an HTTP Custom Type's responseBody — into a @proxy upstream request body instead of the query string (where an oversized value would be rejected, see MaxForwardedQueryParamLength). Target the field by its converted name (responseBody), its expanded signature name (_response_body), or the composite base (_response), and use a body-carrying method:

sql
sql
comment on function scrape_and_forward(...) is 'HTTP POST
+@proxy https://upstream.example.com/ingest
+@body_parameter_name responseBody';

The remaining small fields still travel on the proxy query string.

`,26)]))}const k=s(t,[["render",l]]);export{m as __pageData,k as default}; diff --git a/assets/annotations_body-parameter-name.md.BfhRegXI.lean.js b/assets/annotations_body-parameter-name.md.BfhRegXI.lean.js new file mode 100644 index 000000000..41007aabb --- /dev/null +++ b/assets/annotations_body-parameter-name.md.BfhRegXI.lean.js @@ -0,0 +1 @@ +import{_ as s,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"BODY_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Specify which PostgreSQL function parameter receives the raw HTTP request body. Handle JSON payloads in SQL functions.","frontmatter":{"outline":[2,3],"title":"BODY_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Specify which PostgreSQL function parameter receives the raw HTTP request body. Handle JSON payloads in SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest body parameter, request body parameter, json body postgresql, raw body function, http payload sql"}],["meta",{"property":"og:title","content":"NpgsqlRest BODY_PARAMETER_NAME Annotation"}],["meta",{"property":"og:description","content":"Specify which function parameter receives the raw HTTP request body."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/body-parameter-name.md","filePath":"annotations/body-parameter-name.md"}'),t={name:"annotations/body-parameter-name.md"};function l(p,a,r,o,h,d){return i(),e("div",null,a[0]||(a[0]=[n("",26)]))}const k=s(t,[["render",l]]);export{m as __pageData,k as default}; diff --git a/assets/annotations_buffer-rows.md.BrfXbHRV.js b/assets/annotations_buffer-rows.md.BrfXbHRV.js new file mode 100644 index 000000000..44c006eb4 --- /dev/null +++ b/assets/annotations_buffer-rows.md.BrfXbHRV.js @@ -0,0 +1,11 @@ +import{_ as a,c as s,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"BUFFER_ROWS Annotation","titleTemplate":"NpgsqlRest","description":"Configure row buffering for PostgreSQL REST API responses. Control memory usage and streaming behavior for large result sets.","frontmatter":{"outline":[2,3],"title":"BUFFER_ROWS Annotation","titleTemplate":"NpgsqlRest","description":"Configure row buffering for PostgreSQL REST API responses. Control memory usage and streaming behavior for large result sets.","head":[["meta",{"name":"keywords","content":"npgsqlrest buffer rows, response buffering, streaming results, large result sets, memory optimization"}],["meta",{"property":"og:title","content":"NpgsqlRest BUFFER_ROWS Annotation"}],["meta",{"property":"og:description","content":"Configure row buffering for controlling memory usage and streaming."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/buffer-rows.md","filePath":"annotations/buffer-rows.md"}'),i={name:"annotations/buffer-rows.md"};function r(l,e,o,p,c,d){return n(),s("div",null,e[0]||(e[0]=[t(`

BUFFER_ROWS

Also known as

buffer (with or without @ prefix)

Set the number of rows to buffer in the string builder before sending the response.

Syntax

code
@buffer_rows <count>
+@buffer <count>

Or using custom parameter syntax:

code
@buffer_rows = <count>
+@buffer = <count>

Default Value

The default value is 25 rows.

Special Values

ValueBehavior
0Disable buffering - write response for each row
1Buffer the entire array (all rows)
25Default - buffer 25 rows before writing
> 1Buffer specified number of rows before writing

Examples

Disable Buffering

Write each row immediately to the response stream:

sql
sql
comment on function stream_live_data() is
+'HTTP GET
+@buffer_rows 0';

Buffer Entire Response

Wait for all rows before sending response:

sql
sql
comment on function get_small_dataset() is
+'HTTP GET
+@buffer 1';

Large Buffer for Throughput

sql
sql
comment on function export_all_data() is
+'HTTP GET
+@buffer_rows 5000';

Small Buffer for Memory Efficiency

sql
sql
comment on function stream_data() is
+'HTTP GET
+@buffer 100';

Behavior

Performance Considerations

`,30)]))}const m=a(i,[["render",r]]);export{h as __pageData,m as default}; diff --git a/assets/annotations_buffer-rows.md.BrfXbHRV.lean.js b/assets/annotations_buffer-rows.md.BrfXbHRV.lean.js new file mode 100644 index 000000000..bb2f26832 --- /dev/null +++ b/assets/annotations_buffer-rows.md.BrfXbHRV.lean.js @@ -0,0 +1 @@ +import{_ as a,c as s,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"BUFFER_ROWS Annotation","titleTemplate":"NpgsqlRest","description":"Configure row buffering for PostgreSQL REST API responses. Control memory usage and streaming behavior for large result sets.","frontmatter":{"outline":[2,3],"title":"BUFFER_ROWS Annotation","titleTemplate":"NpgsqlRest","description":"Configure row buffering for PostgreSQL REST API responses. Control memory usage and streaming behavior for large result sets.","head":[["meta",{"name":"keywords","content":"npgsqlrest buffer rows, response buffering, streaming results, large result sets, memory optimization"}],["meta",{"property":"og:title","content":"NpgsqlRest BUFFER_ROWS Annotation"}],["meta",{"property":"og:description","content":"Configure row buffering for controlling memory usage and streaming."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/buffer-rows.md","filePath":"annotations/buffer-rows.md"}'),i={name:"annotations/buffer-rows.md"};function r(l,e,o,p,c,d){return n(),s("div",null,e[0]||(e[0]=[t("",30)]))}const m=a(i,[["render",r]]);export{h as __pageData,m as default}; diff --git a/assets/annotations_cache-expires-in.md.BRoExkyw.js b/assets/annotations_cache-expires-in.md.BRoExkyw.js new file mode 100644 index 000000000..df1943018 --- /dev/null +++ b/assets/annotations_cache-expires-in.md.BRoExkyw.js @@ -0,0 +1,13 @@ +import{_ as e,c as s,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"CACHE_EXPIRES_IN Annotation","titleTemplate":"NpgsqlRest","description":"Set cache expiration time for PostgreSQL REST API endpoints. Configure TTL for cached responses.","frontmatter":{"outline":[2,3],"title":"CACHE_EXPIRES_IN Annotation","titleTemplate":"NpgsqlRest","description":"Set cache expiration time for PostgreSQL REST API endpoints. Configure TTL for cached responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache expires, cache ttl, cache expiration, api cache duration, response cache timeout"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHE_EXPIRES_IN Annotation"}],["meta",{"property":"og:description","content":"Set cache expiration time for cached PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cache-expires-in.md","filePath":"annotations/cache-expires-in.md"}'),t={name:"annotations/cache-expires-in.md"};function l(c,a,o,r,p,d){return n(),s("div",null,a[0]||(a[0]=[i(`

CACHE_EXPIRES_IN

Also known as

cache_expires (with or without @ prefix)

Set cache expiration time for cached endpoints.

Syntax

code
@cache_expires_in <interval>

Uses interval format. Common examples:

FormatMeaning
10s10 seconds
5m5 minutes
1h1 hour
1d1 day
1w1 week

Examples

Short Cache (10 seconds)

sql
sql
comment on function get_live_data() is
+'HTTP GET
+@cached
+@cache_expires_in 10s';

Medium Cache (5 minutes)

sql
sql
comment on function get_dashboard_stats() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';

Long Cache (1 hour)

sql
sql
comment on function get_static_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1h';

Daily Cache

sql
sql
comment on function get_daily_report() is
+'HTTP GET
+@cached
+@cache_expires_in 1d';

See Also

`,22)]))}const u=e(t,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_cache-expires-in.md.BRoExkyw.lean.js b/assets/annotations_cache-expires-in.md.BRoExkyw.lean.js new file mode 100644 index 000000000..71e9c72d9 --- /dev/null +++ b/assets/annotations_cache-expires-in.md.BRoExkyw.lean.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"CACHE_EXPIRES_IN Annotation","titleTemplate":"NpgsqlRest","description":"Set cache expiration time for PostgreSQL REST API endpoints. Configure TTL for cached responses.","frontmatter":{"outline":[2,3],"title":"CACHE_EXPIRES_IN Annotation","titleTemplate":"NpgsqlRest","description":"Set cache expiration time for PostgreSQL REST API endpoints. Configure TTL for cached responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache expires, cache ttl, cache expiration, api cache duration, response cache timeout"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHE_EXPIRES_IN Annotation"}],["meta",{"property":"og:description","content":"Set cache expiration time for cached PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cache-expires-in.md","filePath":"annotations/cache-expires-in.md"}'),t={name:"annotations/cache-expires-in.md"};function l(c,a,o,r,p,d){return n(),s("div",null,a[0]||(a[0]=[i("",22)]))}const u=e(t,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_cache-profile.md.YD0xPH6k.js b/assets/annotations_cache-profile.md.YD0xPH6k.js new file mode 100644 index 000000000..1a09adff0 --- /dev/null +++ b/assets/annotations_cache-profile.md.YD0xPH6k.js @@ -0,0 +1,47 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"CACHE_PROFILE Annotation","titleTemplate":"NpgsqlRest","description":"Select a named cache profile defined in CacheOptions. Profiles let you mix multiple cache backends, dynamic TTLs, and conditional skip rules in one application.","frontmatter":{"outline":[2,3],"title":"CACHE_PROFILE Annotation","titleTemplate":"NpgsqlRest","description":"Select a named cache profile defined in CacheOptions. Profiles let you mix multiple cache backends, dynamic TTLs, and conditional skip rules in one application.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache_profile, cache profiles, multiple cache backends, dynamic ttl, conditional caching, postgresql api cache profiles"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHE_PROFILE Annotation"}],["meta",{"property":"og:description","content":"Select a named cache profile to apply per-endpoint cache backend, expiration, and conditional skip rules."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cache-profile.md","filePath":"annotations/cache-profile.md"}'),t={name:"annotations/cache-profile.md"};function l(p,s,h,r,k,d){return e(),a("div",null,s[0]||(s[0]=[n(`

CACHE_PROFILE

Select a named cache profile for an endpoint.

A cache profile bundles together a cache backend (Memory / Redis / Hybrid), a default expiration, the cache-key parameter list, and per-parameter conditional rules ("when X is null, bypass cache" or "when status='draft', cache 30 seconds"). Profiles are defined once in Cache Options configuration and selected per endpoint via this annotation.

@cache_profile implies caching — you don't also need @cached. Both @cached and @cache_expires annotations remain valid; when present they override the profile's defaults.

Syntax

code
@cache_profile <name>

The annotation accepts exactly one profile name. The name must match a profile defined in CacheOptions.Profiles and registered with "Enabled": true. Unknown names cause startup to fail with a single error listing every unresolved name and the offending endpoints.

Examples

Basic usage

sql
sql
create function get_dashboard()
+returns json
+language sql
+begin atomic;
+select dashboard_data();
+end;
+
+comment on function get_dashboard() is
+'HTTP GET
+@cache_profile fast_memory';

Equivalent as a SQL file endpoint (sql/get-dashboard.sql):

sql
sql
-- HTTP GET
+-- @cache_profile fast_memory
+select dashboard_data();

The fast_memory profile (defined in CacheOptions.Profiles) supplies the backend, expiration, and any conditional rules.

Combined with cached / cache_expires

sql
sql
comment on function get_user_report(user_id int, year int) is
+'HTTP GET
+@cache_profile shared_redis
+@cached user_id, year
+@cache_expires 1 hour';

The annotations override the profile's defaults:

The profile still supplies the cache backend (Redis in this case) and any When rules.

Multi-tenant search_path pattern

A profile that bypasses the cache when no end date is supplied — the request asks for "until now" data, which changes constantly:

jsonc
jsonc
// in appsettings.json
+"CacheOptions": {
+  "Enabled": true,
+  "Profiles": {
+    "timeseries": {
+      "Enabled": true,
+      "Type": "Memory",
+      "Expiration": "1 hour",
+      "Parameters": ["from", "to"],
+      "When": [
+        { "Parameter": "to", "Value": null, "Then": "5 minutes" }
+      ]
+    }
+  }
+}
sql
sql
comment on function compute_timeseries(from text, to text default null) is
+'HTTP GET
+@cache_profile timeseries';

Behavior per request:

Tiered TTL by user role

jsonc
jsonc
"CacheOptions": {
+  "Profiles": {
+    "tier_aware": {
+      "Enabled": true,
+      "Type": "Hybrid",
+      "Parameters": ["tier"],
+      "When": [
+        { "Parameter": "tier", "Value": "free",  "Then": "5 minutes" },
+        { "Parameter": "tier", "Value": "pro",   "Then": "1 hour" },
+        { "Parameter": "tier", "Value": "admin", "Then": "skip" }
+      ]
+    }
+  }
+}
sql
sql
comment on function get_account_data(tier text) is
+'HTTP GET
+@cache_profile tier_aware';

Behavior

Validation

Misconfiguration is caught at startup:

ProblemResult
Unknown profile name referenced by @cache_profileStartup fails with single error listing every unresolved name + offending endpoints
Profile registered but no endpoint references itInformation-level log warning
When rule references a parameter that's not in the cache-key listRule dropped at startup with Warning (other rules still apply)
Multiple @cache_profile arguments (e.g. @cache_profile a b)Annotation ignored with Warning; one name only

See Also

`,37)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_cache-profile.md.YD0xPH6k.lean.js b/assets/annotations_cache-profile.md.YD0xPH6k.lean.js new file mode 100644 index 000000000..abe377105 --- /dev/null +++ b/assets/annotations_cache-profile.md.YD0xPH6k.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"CACHE_PROFILE Annotation","titleTemplate":"NpgsqlRest","description":"Select a named cache profile defined in CacheOptions. Profiles let you mix multiple cache backends, dynamic TTLs, and conditional skip rules in one application.","frontmatter":{"outline":[2,3],"title":"CACHE_PROFILE Annotation","titleTemplate":"NpgsqlRest","description":"Select a named cache profile defined in CacheOptions. Profiles let you mix multiple cache backends, dynamic TTLs, and conditional skip rules in one application.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache_profile, cache profiles, multiple cache backends, dynamic ttl, conditional caching, postgresql api cache profiles"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHE_PROFILE Annotation"}],["meta",{"property":"og:description","content":"Select a named cache profile to apply per-endpoint cache backend, expiration, and conditional skip rules."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cache-profile.md","filePath":"annotations/cache-profile.md"}'),t={name:"annotations/cache-profile.md"};function l(p,s,h,r,k,d){return e(),a("div",null,s[0]||(s[0]=[n("",37)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_cached.md.Z17IaMQ8.js b/assets/annotations_cached.md.Z17IaMQ8.js new file mode 100644 index 000000000..1c65858ec --- /dev/null +++ b/assets/annotations_cached.md.Z17IaMQ8.js @@ -0,0 +1,55 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"CACHED Annotation","titleTemplate":"NpgsqlRest","description":"Enable server-side response caching for PostgreSQL function results. Configure cache keys and expiration for improved performance.","frontmatter":{"outline":[2,3],"title":"CACHED Annotation","titleTemplate":"NpgsqlRest","description":"Enable server-side response caching for PostgreSQL function results. Configure cache keys and expiration for improved performance.","head":[["meta",{"name":"keywords","content":"npgsqlrest cached, response caching, api cache, postgresql cache, server side cache"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHED Annotation"}],["meta",{"property":"og:description","content":"Enable server-side response caching for PostgreSQL function results."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cached.md","filePath":"annotations/cached.md"}'),l={name:"annotations/cached.md"};function t(p,s,h,r,c,k){return n(),i("div",null,s[0]||(s[0]=[e(`

CACHED

Enable server-side response caching for routine results.

Syntax

code
@cached
+@cached <param1>, <param2>, <param3>, ...

Space-separated lists are also valid: @cached _year _department

Parameters specified become part of the cache key.

Examples

Simple Caching

sql
sql
create function get_app_settings()
+returns json
+language sql
+begin atomic;
+select settings from app_config where id = 1;
+end;
+
+comment on function get_app_settings() is
+'HTTP GET
+@cached';

Equivalent as a SQL file endpoint (sql/get-app-settings.sql):

sql
sql
-- HTTP GET
+-- @cached
+select settings from app_config where id = 1;

Cache Key by Parameter

sql
sql
create function get_user_profile(_user_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where id = _user_id;
+end;
+
+comment on function get_user_profile(int) is
+'HTTP GET
+@cached _user_id';

Different _user_id values create separate cache entries.

Multiple Cache Key Parameters

sql
sql
create function get_report(_year int, _department text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_report(int, text) is
+'HTTP GET
+@cached _year, _department';

With Cache Expiration

Cache expiration uses interval format:

sql
sql
comment on function get_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1h';

Caching Set-Returning Functions

Caching works for set-returning functions and record types. When a cached function returns multiple rows, the entire result set is cached:

sql
sql
create function get_all_users()
+returns table(id int, name text)
+language sql
+begin atomic;
+select id, name from users;
+end;
+
+comment on function get_all_users() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';

Use MaxCacheableRows in Cache Options to limit the maximum number of rows that can be cached. Result sets exceeding this limit are returned but not cached.

Behavior

Cache Configuration

The cached annotation requires cache to be enabled in Cache Options configuration.

Two cache types are available:

TypeDescriptionUse Case
MemoryIn-memory cache on the application serverSingle instance deployments, development
RedisDistributed cache using RedisMulti-instance deployments, production

Example configuration:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Memory"
+  }
+}

For Redis:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Redis",
+    "RedisConfiguration": "localhost:6379"
+  }
+}

See Cache Options for complete configuration reference.

See Also

`,40)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_cached.md.Z17IaMQ8.lean.js b/assets/annotations_cached.md.Z17IaMQ8.lean.js new file mode 100644 index 000000000..ab63bdf4d --- /dev/null +++ b/assets/annotations_cached.md.Z17IaMQ8.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"CACHED Annotation","titleTemplate":"NpgsqlRest","description":"Enable server-side response caching for PostgreSQL function results. Configure cache keys and expiration for improved performance.","frontmatter":{"outline":[2,3],"title":"CACHED Annotation","titleTemplate":"NpgsqlRest","description":"Enable server-side response caching for PostgreSQL function results. Configure cache keys and expiration for improved performance.","head":[["meta",{"name":"keywords","content":"npgsqlrest cached, response caching, api cache, postgresql cache, server side cache"}],["meta",{"property":"og:title","content":"NpgsqlRest CACHED Annotation"}],["meta",{"property":"og:description","content":"Enable server-side response caching for PostgreSQL function results."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/cached.md","filePath":"annotations/cached.md"}'),l={name:"annotations/cached.md"};function t(p,s,h,r,c,k){return n(),i("div",null,s[0]||(s[0]=[e("",40)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_column-names.md.BgCUEwzC.js b/assets/annotations_column-names.md.BgCUEwzC.js new file mode 100644 index 000000000..b99cc7d2f --- /dev/null +++ b/assets/annotations_column-names.md.BgCUEwzC.js @@ -0,0 +1,29 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"COLUMN_NAMES Annotation","titleTemplate":"NpgsqlRest","description":"Include column names as header row in raw output mode. Add CSV headers to PostgreSQL REST API responses.","frontmatter":{"outline":[2,3],"title":"COLUMN_NAMES Annotation","titleTemplate":"NpgsqlRest","description":"Include column names as header row in raw output mode. Add CSV headers to PostgreSQL REST API responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest column names, csv header row, include headers, raw output headers, column headers"}],["meta",{"property":"og:title","content":"NpgsqlRest COLUMN_NAMES Annotation"}],["meta",{"property":"og:description","content":"Include column names as header row in raw output mode."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/column-names.md","filePath":"annotations/column-names.md"}'),l={name:"annotations/column-names.md"};function t(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e(`

COLUMN_NAMES

Also known as

columns, names (with or without @ prefix)

Include column names as the first row in raw output mode.

Syntax

code
@columns

Examples

CSV with Headers

sql
sql
create function export_users()
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_users() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: text/csv';

Equivalent as a SQL file endpoint (sql/export-users.sql):

sql
sql
/*
+HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: text/csv
+*/
+select id, name, email from users;

Response:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

TSV with Headers

sql
sql
comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \\t
+@new_line \\n
+@column_names';
`,18)]))}const m=a(l,[["render",t]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_column-names.md.BgCUEwzC.lean.js b/assets/annotations_column-names.md.BgCUEwzC.lean.js new file mode 100644 index 000000000..ff57649aa --- /dev/null +++ b/assets/annotations_column-names.md.BgCUEwzC.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"COLUMN_NAMES Annotation","titleTemplate":"NpgsqlRest","description":"Include column names as header row in raw output mode. Add CSV headers to PostgreSQL REST API responses.","frontmatter":{"outline":[2,3],"title":"COLUMN_NAMES Annotation","titleTemplate":"NpgsqlRest","description":"Include column names as header row in raw output mode. Add CSV headers to PostgreSQL REST API responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest column names, csv header row, include headers, raw output headers, column headers"}],["meta",{"property":"og:title","content":"NpgsqlRest COLUMN_NAMES Annotation"}],["meta",{"property":"og:description","content":"Include column names as header row in raw output mode."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/column-names.md","filePath":"annotations/column-names.md"}'),l={name:"annotations/column-names.md"};function t(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e("",18)]))}const m=a(l,[["render",t]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_command-timeout.md.DGZAbOuK.js b/assets/annotations_command-timeout.md.DGZAbOuK.js new file mode 100644 index 000000000..0d7959457 --- /dev/null +++ b/assets/annotations_command-timeout.md.DGZAbOuK.js @@ -0,0 +1,38 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"COMMAND_TIMEOUT Annotation","titleTemplate":"NpgsqlRest","description":"Set query execution timeout for PostgreSQL REST API endpoints. Configure per-endpoint database command timeouts.","frontmatter":{"outline":[2,3],"title":"COMMAND_TIMEOUT Annotation","titleTemplate":"NpgsqlRest","description":"Set query execution timeout for PostgreSQL REST API endpoints. Configure per-endpoint database command timeouts.","head":[["meta",{"name":"keywords","content":"npgsqlrest timeout, query timeout, command timeout, postgresql timeout, api request timeout"}],["meta",{"property":"og:title","content":"NpgsqlRest COMMAND_TIMEOUT Annotation"}],["meta",{"property":"og:description","content":"Set query execution timeout for individual PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/command-timeout.md","filePath":"annotations/command-timeout.md"}'),t={name:"annotations/command-timeout.md"};function l(p,s,o,r,h,d){return n(),i("div",null,s[0]||(s[0]=[e(`

COMMAND_TIMEOUT

Also known as

timeout (with or without @ prefix)

Set the query execution timeout for the endpoint.

Syntax

code
@timeout <interval>
+@command_timeout <interval>

Or using custom parameter syntax:

code
@timeout = <interval>
+@command_timeout = <interval>

The value uses the interval format. Common examples:

UnitExamples
Microseconds1000us, 1000usec, 1000microseconds
Milliseconds500ms, 500msec, 500milliseconds
Seconds30, 30s, 30sec, 30seconds
Minutes5m, 5min, 5minutes
Hours1h, 1hour, 1hours
Days1d, 1day, 1days
Weeks1w, 1week, 1weeks

Single Token Requirement

The @timeout annotation reads only the first token after the keyword. Use formats without spaces to avoid parsing issues. Numbers without a unit default to seconds.

Default Value

The default timeout is configured via NpgsqlRest.CommandTimeout in configuration. If not set, defaults to 30 seconds.

Examples

Short Timeout

sql
sql
create function quick_lookup(_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(t) from table t where id = _id;
+end;
+
+comment on function quick_lookup(int) is
+'HTTP GET
+@timeout 5s';

Equivalent as a SQL file endpoint (sql/quick-lookup.sql):

sql
sql
/*
+HTTP GET
+@timeout 5s
+@param $1 id
+*/
+select row_to_json(t) from items t where t.id = $1;

Long Running Query

sql
sql
create function generate_report(_year int)
+returns json
+language sql
+begin atomic;
+...complex aggregation...;
+end;
+
+comment on function generate_report(int) is
+'HTTP GET
+@timeout 2min
+@authorize';

Using Seconds Format

sql
sql
comment on function slow_process() is
+'HTTP POST
+@command_timeout 90s';

Behavior

Timeout Response

When a command times out, the response is determined by the TimeoutErrorMapping configuration:

json
json
{
+  "ErrorHandlingOptions": {
+    "TimeoutErrorMapping": {
+      "StatusCode": 504,
+      "Title": "Command execution timed out",
+      "Details": null,
+      "Type": null
+    }
+  }
+}

Default timeout response: HTTP 504 Gateway Timeout

See Error Handling for customizing timeout responses.

See Also

`,34)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_command-timeout.md.DGZAbOuK.lean.js b/assets/annotations_command-timeout.md.DGZAbOuK.lean.js new file mode 100644 index 000000000..2eda9b0c1 --- /dev/null +++ b/assets/annotations_command-timeout.md.DGZAbOuK.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"COMMAND_TIMEOUT Annotation","titleTemplate":"NpgsqlRest","description":"Set query execution timeout for PostgreSQL REST API endpoints. Configure per-endpoint database command timeouts.","frontmatter":{"outline":[2,3],"title":"COMMAND_TIMEOUT Annotation","titleTemplate":"NpgsqlRest","description":"Set query execution timeout for PostgreSQL REST API endpoints. Configure per-endpoint database command timeouts.","head":[["meta",{"name":"keywords","content":"npgsqlrest timeout, query timeout, command timeout, postgresql timeout, api request timeout"}],["meta",{"property":"og:title","content":"NpgsqlRest COMMAND_TIMEOUT Annotation"}],["meta",{"property":"og:description","content":"Set query execution timeout for individual PostgreSQL REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/command-timeout.md","filePath":"annotations/command-timeout.md"}'),t={name:"annotations/command-timeout.md"};function l(p,s,o,r,h,d){return n(),i("div",null,s[0]||(s[0]=[e("",34)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_connection.md.kVRk6UjZ.js b/assets/annotations_connection.md.kVRk6UjZ.js new file mode 100644 index 000000000..25d15468c --- /dev/null +++ b/assets/annotations_connection.md.kVRk6UjZ.js @@ -0,0 +1,8 @@ +import{_ as e,c as n,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"CONNECTION Annotation","titleTemplate":"NpgsqlRest","description":"Route PostgreSQL REST API endpoints to specific database connections. Use different databases per endpoint.","frontmatter":{"outline":[2,3],"title":"CONNECTION Annotation","titleTemplate":"NpgsqlRest","description":"Route PostgreSQL REST API endpoints to specific database connections. Use different databases per endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest connection, named connection, multiple databases, database routing, connection per endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest CONNECTION Annotation"}],["meta",{"property":"og:description","content":"Route endpoints to specific database connections for multi-database setups."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/connection.md","filePath":"annotations/connection.md"}'),t={name:"annotations/connection.md"};function o(l,a,c,r,p,d){return s(),n("div",null,a[0]||(a[0]=[i(`

CONNECTION

Also known as

connection_name (with or without @ prefix)

Specify a named database connection for the endpoint.

Syntax

code
@connection <connection-name>
+@connection_name <connection-name>

Examples

Use Named Connection

sql
sql
comment on function get_analytics() is
+'HTTP GET
+@connection analytics_db';

Reporting Database

sql
sql
comment on function generate_report() is
+'HTTP GET
+@connection_name reporting';

Read Replica

sql
sql
comment on function read_heavy_query() is
+'HTTP GET
+@connection read_replica';

Behavior

See Also

`,20)]))}const m=e(t,[["render",o]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_connection.md.kVRk6UjZ.lean.js b/assets/annotations_connection.md.kVRk6UjZ.lean.js new file mode 100644 index 000000000..a8e6667d3 --- /dev/null +++ b/assets/annotations_connection.md.kVRk6UjZ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as n,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"CONNECTION Annotation","titleTemplate":"NpgsqlRest","description":"Route PostgreSQL REST API endpoints to specific database connections. Use different databases per endpoint.","frontmatter":{"outline":[2,3],"title":"CONNECTION Annotation","titleTemplate":"NpgsqlRest","description":"Route PostgreSQL REST API endpoints to specific database connections. Use different databases per endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest connection, named connection, multiple databases, database routing, connection per endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest CONNECTION Annotation"}],["meta",{"property":"og:description","content":"Route endpoints to specific database connections for multi-database setups."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/connection.md","filePath":"annotations/connection.md"}'),t={name:"annotations/connection.md"};function o(l,a,c,r,p,d){return s(),n("div",null,a[0]||(a[0]=[i("",20)]))}const m=e(t,[["render",o]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_custom-parameters.md.C_JoJInz.js b/assets/annotations_custom-parameters.md.C_JoJInz.js new file mode 100644 index 000000000..6c3dfbd94 --- /dev/null +++ b/assets/annotations_custom-parameters.md.C_JoJInz.js @@ -0,0 +1,20 @@ +import{_ as e,c as s,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Custom Parameters Annotation","titleTemplate":"NpgsqlRest","description":"Set custom key-value configuration for PostgreSQL REST API endpoints. Add metadata and custom settings to endpoints.","frontmatter":{"outline":[2,3],"title":"Custom Parameters Annotation","titleTemplate":"NpgsqlRest","description":"Set custom key-value configuration for PostgreSQL REST API endpoints. Add metadata and custom settings to endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest custom parameters, endpoint metadata, key value config, custom endpoint settings, api custom config"}],["meta",{"property":"og:title","content":"NpgsqlRest Custom Parameters Annotation"}],["meta",{"property":"og:description","content":"Set custom key-value configuration and metadata for endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/custom-parameters.md","filePath":"annotations/custom-parameters.md"}'),n={name:"annotations/custom-parameters.md"};function l(o,a,r,p,c,d){return i(),s("div",null,a[0]||(a[0]=[t(`

Custom Parameters

Set custom key-value configuration for the endpoint.

Syntax

code
@<key> = <value>

The @ prefix is optional - both @key = value and key = value work identically. Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

Dynamic Parameter Values

Some parameters support dynamic values using the {param_name} format, where param_name references a function parameter. The value is resolved at runtime from the actual parameter value passed to the endpoint. The matching and substitution rules are shared across annotations — see Parameter Value Substitution.

Example

sql
sql
create function upload_file(_path text, _file text)
+returns void
+language sql
+begin atomic;
+  -- function body
+end;
+
+comment on function upload_file(text, text) is '
+@upload for file_system
+@file_system_path = {_path}
+@file_system_file = {_file}
+';

Equivalent as a SQL file endpoint (sql/upload-file.sql):

sql
sql
/*
+HTTP POST
+@upload for file_system
+@file_system_path = {path}
+@file_system_file = {file}
+@param $1 path
+@param $2 file
+*/
+select;

When called with {"_path": "/uploads/images", "_file": "photo.jpg"}, the file will be saved to /uploads/images/photo.jpg.

Built-in Parameters

Many annotations support the @key = value syntax. The following sections link to where each parameter group is documented.

General

These parameters are predefined annotations that also support the key = value syntax:

Upload

Upload handlers accept custom parameters to control file processing behavior per-endpoint:

Table Format

Per-endpoint control of HTML table and Excel spreadsheet rendering:

Server-Sent Events

SSE annotations that also support the key = value syntax:

TypeScript Client

Per-endpoint control of generated TypeScript client code:

`,31)]))}const u=e(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_custom-parameters.md.C_JoJInz.lean.js b/assets/annotations_custom-parameters.md.C_JoJInz.lean.js new file mode 100644 index 000000000..8894158b1 --- /dev/null +++ b/assets/annotations_custom-parameters.md.C_JoJInz.lean.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Custom Parameters Annotation","titleTemplate":"NpgsqlRest","description":"Set custom key-value configuration for PostgreSQL REST API endpoints. Add metadata and custom settings to endpoints.","frontmatter":{"outline":[2,3],"title":"Custom Parameters Annotation","titleTemplate":"NpgsqlRest","description":"Set custom key-value configuration for PostgreSQL REST API endpoints. Add metadata and custom settings to endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest custom parameters, endpoint metadata, key value config, custom endpoint settings, api custom config"}],["meta",{"property":"og:title","content":"NpgsqlRest Custom Parameters Annotation"}],["meta",{"property":"og:description","content":"Set custom key-value configuration and metadata for endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/custom-parameters.md","filePath":"annotations/custom-parameters.md"}'),n={name:"annotations/custom-parameters.md"};function l(o,a,r,p,c,d){return i(),s("div",null,a[0]||(a[0]=[t("",31)]))}const u=e(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_define-param.md.BEpTOvZh.js b/assets/annotations_define-param.md.BEpTOvZh.js new file mode 100644 index 000000000..09de563cf --- /dev/null +++ b/assets/annotations_define-param.md.BEpTOvZh.js @@ -0,0 +1,16 @@ +import{_ as e,c as s,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"DEFINE_PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Define virtual HTTP parameters that are not bound to the PostgreSQL command. Used for custom parameter placeholders, claim mapping, and HTTP request matching in SQL file endpoints.","frontmatter":{"outline":[2,3],"title":"DEFINE_PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Define virtual HTTP parameters that are not bound to the PostgreSQL command. Used for custom parameter placeholders, claim mapping, and HTTP request matching in SQL file endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest define_param, virtual parameter, sql file parameter, claim mapping parameter, custom parameter placeholder"}],["meta",{"property":"og:title","content":"NpgsqlRest DEFINE_PARAM Annotation"}],["meta",{"property":"og:description","content":"Define virtual HTTP parameters that are not bound to the PostgreSQL command."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/define-param.md","filePath":"annotations/define-param.md"}'),n={name:"annotations/define-param.md"};function l(r,a,p,o,d,h){return i(),s("div",null,a[0]||(a[0]=[t(`

DEFINE_PARAM

Also known as

define_param (with or without @ prefix)

Define HTTP parameters that are not bound to the PostgreSQL command. These virtual parameters exist in the HTTP request (query string or JSON body) but do not correspond to any $N positional parameter in the SQL query.

This is useful for SQL file endpoints where you need HTTP parameters for:

Syntax

code
@define_param name
+@define_param name type

Custom Parameter Placeholders

Pass HTTP parameters that control endpoint behavior without referencing them in SQL:

sql
sql
-- sql/users_report.sql
+-- @define_param format text
+-- @table_format = {format}
+-- @param $1 department_id
+select id, name, email from users where department_id = $1;

GET /api/users-report?department_id=5&format=html_table

The format parameter feeds into the @table_format annotation via the {format} placeholder, selecting the output format (JSON, HTML table, Excel, etc.) without being part of the SQL query. Without @define_param, there would be no format parameter in the endpoint — the {format} placeholder would have nothing to resolve.

Claim Mapping

Auto-fill a parameter from the authenticated user's claims without including it in the SQL query:

sql
sql
-- sql/user_dashboard.sql
+-- @authorize
+-- @user_parameters
+-- @define_param _user_id
+select * from user_dashboard_data;

Here _user_id is created as a virtual parameter that maps to the name_identifier claim (via standard User Parameters claim mapping). The authenticated user's ID is injected automatically — but unlike @param, this parameter doesn't correspond to any $N in the SQL. The query itself doesn't filter by user — the virtual parameter exists solely for the claim mapping mechanism.

This is different from using @param with @user_parameters:

sql
sql
-- This uses @param — $1 IS in the SQL query
+-- @authorize
+-- @user_parameters
+-- @param $1 _user_id
+select * from orders where user_id = $1;

Use @define_param when the parameter shouldn't appear in the SQL at all. Use @param when you need the value both as a claim-mapped parameter and as a query parameter.

Default Type

If no type is specified, the parameter defaults to text:

sql
sql
-- These are equivalent:
+-- @define_param _user_id
+-- @define_param _user_id text

Specify a type when needed:

sql
sql
-- @define_param _user_id integer
`,27)]))}const u=e(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_define-param.md.BEpTOvZh.lean.js b/assets/annotations_define-param.md.BEpTOvZh.lean.js new file mode 100644 index 000000000..78e965946 --- /dev/null +++ b/assets/annotations_define-param.md.BEpTOvZh.lean.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"DEFINE_PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Define virtual HTTP parameters that are not bound to the PostgreSQL command. Used for custom parameter placeholders, claim mapping, and HTTP request matching in SQL file endpoints.","frontmatter":{"outline":[2,3],"title":"DEFINE_PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Define virtual HTTP parameters that are not bound to the PostgreSQL command. Used for custom parameter placeholders, claim mapping, and HTTP request matching in SQL file endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest define_param, virtual parameter, sql file parameter, claim mapping parameter, custom parameter placeholder"}],["meta",{"property":"og:title","content":"NpgsqlRest DEFINE_PARAM Annotation"}],["meta",{"property":"og:description","content":"Define virtual HTTP parameters that are not bound to the PostgreSQL command."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/define-param.md","filePath":"annotations/define-param.md"}'),n={name:"annotations/define-param.md"};function l(r,a,p,o,d,h){return i(),s("div",null,a[0]||(a[0]=[t("",27)]))}const u=e(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_disabled.md.DLiSD84Q.js b/assets/annotations_disabled.md.DLiSD84Q.js new file mode 100644 index 000000000..101c041c9 --- /dev/null +++ b/assets/annotations_disabled.md.DLiSD84Q.js @@ -0,0 +1,6 @@ +import{_ as a,c as t,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"DISABLED Annotation","titleTemplate":"NpgsqlRest","description":"Disable a PostgreSQL function or procedure as an HTTP endpoint without dropping or modifying the routine itself.","frontmatter":{"outline":[2,3],"title":"DISABLED Annotation","titleTemplate":"NpgsqlRest","description":"Disable a PostgreSQL function or procedure as an HTTP endpoint without dropping or modifying the routine itself.","head":[["meta",{"name":"keywords","content":"npgsqlrest disabled, disable endpoint, hide api endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest DISABLED Annotation"}],["meta",{"property":"og:description","content":"Disable a function or procedure from being exposed as an HTTP endpoint."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/disabled.md","filePath":"annotations/disabled.md"}'),n={name:"annotations/disabled.md"};function o(l,e,d,r,c,p){return s(),t("div",null,e[0]||(e[0]=[i(`

DISABLED

Hide a routine from being exposed as an HTTP endpoint without dropping or modifying it.

Keywords

@disabled, disabled

Syntax

code
@disabled

The endpoint will not be created. The function or procedure remains in the database, callable directly via SQL — only the HTTP exposure is suppressed.

Example

sql
sql
comment on function deprecated_func() is '
+HTTP
+@disabled';

deprecated_func is not registered as an HTTP endpoint at startup. Useful for:

Tag-conditional form

code
@disabled <tag1>, <tag2>, ...

Disables the endpoint only when the routine matches at least one of the listed tags. The available auto-tags assigned by RoutineSource are:

TagMatches
functionPostgreSQL functions
procedurePostgreSQL procedures
volatileFunctions declared VOLATILE (the default)
stableFunctions declared STABLE
immutableFunctions declared IMMUTABLE
otherProcedures (volatility doesn't apply)
sql
sql
-- Disable only if the function is volatile (e.g., to enforce read-only API surface)
+comment on function get_data() is '
+HTTP GET
+@disabled volatile';

Custom tags are not supported — only the auto-tags above are available. SQL file endpoints have no auto-tags.

Most projects don't need the tag form

The unconditional @disabled is the form you'll reach for in practice. The tag form is a leftover from earlier versions where the CRUD source assigned per-operation tags (select, insert, etc.).

`,20)]))}const m=a(n,[["render",o]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_disabled.md.DLiSD84Q.lean.js b/assets/annotations_disabled.md.DLiSD84Q.lean.js new file mode 100644 index 000000000..abfc35f1d --- /dev/null +++ b/assets/annotations_disabled.md.DLiSD84Q.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"DISABLED Annotation","titleTemplate":"NpgsqlRest","description":"Disable a PostgreSQL function or procedure as an HTTP endpoint without dropping or modifying the routine itself.","frontmatter":{"outline":[2,3],"title":"DISABLED Annotation","titleTemplate":"NpgsqlRest","description":"Disable a PostgreSQL function or procedure as an HTTP endpoint without dropping or modifying the routine itself.","head":[["meta",{"name":"keywords","content":"npgsqlrest disabled, disable endpoint, hide api endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest DISABLED Annotation"}],["meta",{"property":"og:description","content":"Disable a function or procedure from being exposed as an HTTP endpoint."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/disabled.md","filePath":"annotations/disabled.md"}'),n={name:"annotations/disabled.md"};function o(l,e,d,r,c,p){return s(),t("div",null,e[0]||(e[0]=[i("",20)]))}const m=a(n,[["render",o]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_enabled.md.w_f0uh0_.js b/assets/annotations_enabled.md.w_f0uh0_.js new file mode 100644 index 000000000..f9b5585a6 --- /dev/null +++ b/assets/annotations_enabled.md.w_f0uh0_.js @@ -0,0 +1,6 @@ +import{_ as a,c as n,o as t,a5 as s}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"ENABLED Annotation","titleTemplate":"NpgsqlRest","description":"Re-enable a PostgreSQL endpoint after a prior @disabled, optionally scoped by routine tag.","frontmatter":{"outline":[2,3],"title":"ENABLED Annotation","titleTemplate":"NpgsqlRest","description":"Re-enable a PostgreSQL endpoint after a prior @disabled, optionally scoped by routine tag.","head":[["meta",{"name":"keywords","content":"npgsqlrest enabled, enable endpoint, conditional enable"}],["meta",{"property":"og:title","content":"NpgsqlRest ENABLED Annotation"}],["meta",{"property":"og:description","content":"Re-enable an endpoint after @disabled, optionally scoped by routine tag."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/enabled.md","filePath":"annotations/enabled.md"}'),l={name:"annotations/enabled.md"};function i(o,e,d,p,c,r){return t(),n("div",null,e[0]||(e[0]=[s(`

ENABLED

Re-enable an endpoint that an earlier @disabled would otherwise hide.

Rarely needed

Endpoints are enabled by default. You only need @enabled to undo a @disabled on a tag-conditional basis. If you've never reached for @disabled, you don't need @enabled either.

Keywords

@enabled, enabled

Syntax

code
@enabled
+@enabled <tag1>, <tag2>, ...

The available auto-tags assigned by RoutineSource are function, procedure, volatile, stable, immutable, other. SQL file endpoints have no auto-tags.

Example: disable-by-default, enable for immutable only

sql
sql
comment on function calculate_total(_items json) is '
+HTTP GET
+@disabled
+@enabled immutable
+@cached';

The endpoint is disabled by default, but re-enabled when the function is declared IMMUTABLE. If you later mark the function STABLE or VOLATILE, the endpoint disappears without further changes.

`,14)]))}const u=a(l,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/annotations_enabled.md.w_f0uh0_.lean.js b/assets/annotations_enabled.md.w_f0uh0_.lean.js new file mode 100644 index 000000000..f9ade70d0 --- /dev/null +++ b/assets/annotations_enabled.md.w_f0uh0_.lean.js @@ -0,0 +1 @@ +import{_ as a,c as n,o as t,a5 as s}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"ENABLED Annotation","titleTemplate":"NpgsqlRest","description":"Re-enable a PostgreSQL endpoint after a prior @disabled, optionally scoped by routine tag.","frontmatter":{"outline":[2,3],"title":"ENABLED Annotation","titleTemplate":"NpgsqlRest","description":"Re-enable a PostgreSQL endpoint after a prior @disabled, optionally scoped by routine tag.","head":[["meta",{"name":"keywords","content":"npgsqlrest enabled, enable endpoint, conditional enable"}],["meta",{"property":"og:title","content":"NpgsqlRest ENABLED Annotation"}],["meta",{"property":"og:description","content":"Re-enable an endpoint after @disabled, optionally scoped by routine tag."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/enabled.md","filePath":"annotations/enabled.md"}'),l={name:"annotations/enabled.md"};function i(o,e,d,p,c,r){return t(),n("div",null,e[0]||(e[0]=[s("",14)]))}const u=a(l,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/annotations_encrypt-decrypt.md.BVL3DPHk.js b/assets/annotations_encrypt-decrypt.md.BVL3DPHk.js new file mode 100644 index 000000000..b2a7ccd27 --- /dev/null +++ b/assets/annotations_encrypt-decrypt.md.BVL3DPHk.js @@ -0,0 +1,46 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"ENCRYPT / DECRYPT Annotations","titleTemplate":"NpgsqlRest","description":"Transparent application-level column encryption using ASP.NET Data Protection. Encrypt parameter values before PostgreSQL, decrypt result columns before returning to client.","frontmatter":{"outline":[2,3],"title":"ENCRYPT / DECRYPT Annotations","titleTemplate":"NpgsqlRest","description":"Transparent application-level column encryption using ASP.NET Data Protection. Encrypt parameter values before PostgreSQL, decrypt result columns before returning to client.","head":[["meta",{"name":"keywords","content":"npgsqlrest encrypt, npgsqlrest decrypt, data protection encryption, column encryption postgresql, encrypt parameters, decrypt results"}],["meta",{"property":"og:title","content":"NpgsqlRest ENCRYPT / DECRYPT Annotations"}],["meta",{"property":"og:description","content":"Transparent column encryption and decryption using ASP.NET Data Protection."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/encrypt-decrypt.md","filePath":"annotations/encrypt-decrypt.md"}'),t={name:"annotations/encrypt-decrypt.md"};function l(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e(`

ENCRYPT / DECRYPT

Also known as

encrypt: encrypted, protect, protecteddecrypt: decrypted, unprotect, unprotected

Transparent application-level column encryption using ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client. The database stores ciphertext; the API consumer sees plaintext. No pgcrypto or client-side encryption required.

Prerequisite: The DataProtection section must be enabled in appsettings.json (it is by default). See Data Protection Configuration.

Encrypt Parameters

Syntax

code
encrypt [parameter_name, ...]

Mark specific parameters to encrypt before they are sent to PostgreSQL:

sql
sql
create function store_patient_ssn(_patient_id int, _ssn text)
+returns void
+language plpgsql as $$
+begin
+    insert into patients (id, ssn) values (_patient_id, _ssn)
+    on conflict (id) do update set ssn = excluded.ssn;
+end;
+$$;
+comment on function store_patient_ssn(int, text) is '
+HTTP POST
+encrypt _ssn
+';

Equivalent as a SQL file endpoint (sql/store-patient-ssn.sql):

sql
sql
/*
+HTTP POST
+@encrypt ssn
+@param $1 patient_id
+@param $2 ssn
+*/
+insert into patients (id, ssn) values ($1, $2)
+on conflict (id) do update set ssn = excluded.ssn;

The client calls POST /api/store-patient-ssn/ with {"patientId": 1, "ssn": "123-45-6789"}. The server encrypts _ssn using Data Protection before executing the SQL — the database stores ciphertext like CfDJ8N..., never the plaintext SSN.

Use encrypt without arguments to encrypt all text parameters:

sql
sql
comment on function store_all_secrets(text, text) is '
+HTTP POST
+encrypt
+';

Decrypt Result Columns

Syntax

code
decrypt [column_name, ...]

Mark specific result columns to decrypt before returning to the client:

sql
sql
create function get_patient(_patient_id int)
+returns table(id int, ssn text, name text)
+language plpgsql as $$
+begin
+    return query select p.id, p.ssn, p.name from patients p where p.id = _patient_id;
+end;
+$$;
+comment on function get_patient(int) is '
+decrypt ssn
+';

The client calls GET /api/get-patient/?patientId=1. The ssn column is decrypted from ciphertext back to "123-45-6789" before being included in the JSON response. The id and name columns are returned as-is.

Use decrypt without arguments to decrypt all result columns:

sql
sql
comment on function get_all_secrets(text) is '
+decrypt
+';

Decrypt also works on scalar (single-value) return types:

sql
sql
create function get_secret(_id int) returns text ...
+comment on function get_secret(int) is 'decrypt';

Full Roundtrip Example

sql
sql
-- Store with encryption
+create function store_secret(_key text, _value text) returns void ...
+comment on function store_secret(text, text) is '
+HTTP POST
+encrypt _value
+';
+
+-- Retrieve with decryption
+create function get_secret(_key text) returns table(key text, value text) ...
+comment on function get_secret(text) is '
+decrypt value
+';
code
POST /api/store-secret/  {"key": "api-key", "value": "sk-abc123"}
+GET  /api/get-secret/?key=api-key  →  {"key": "api-key", "value": "sk-abc123"}

The value is stored encrypted in PostgreSQL and decrypted transparently on read.

Behavior

See Also

`,36)]))}const u=a(t,[["render",l]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_encrypt-decrypt.md.BVL3DPHk.lean.js b/assets/annotations_encrypt-decrypt.md.BVL3DPHk.lean.js new file mode 100644 index 000000000..6c8f33007 --- /dev/null +++ b/assets/annotations_encrypt-decrypt.md.BVL3DPHk.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"ENCRYPT / DECRYPT Annotations","titleTemplate":"NpgsqlRest","description":"Transparent application-level column encryption using ASP.NET Data Protection. Encrypt parameter values before PostgreSQL, decrypt result columns before returning to client.","frontmatter":{"outline":[2,3],"title":"ENCRYPT / DECRYPT Annotations","titleTemplate":"NpgsqlRest","description":"Transparent application-level column encryption using ASP.NET Data Protection. Encrypt parameter values before PostgreSQL, decrypt result columns before returning to client.","head":[["meta",{"name":"keywords","content":"npgsqlrest encrypt, npgsqlrest decrypt, data protection encryption, column encryption postgresql, encrypt parameters, decrypt results"}],["meta",{"property":"og:title","content":"NpgsqlRest ENCRYPT / DECRYPT Annotations"}],["meta",{"property":"og:description","content":"Transparent column encryption and decryption using ASP.NET Data Protection."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/encrypt-decrypt.md","filePath":"annotations/encrypt-decrypt.md"}'),t={name:"annotations/encrypt-decrypt.md"};function l(p,s,r,h,c,o){return n(),i("div",null,s[0]||(s[0]=[e("",36)]))}const u=a(t,[["render",l]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_error-code-policy.md.CcDC1Uqp.js b/assets/annotations_error-code-policy.md.CcDC1Uqp.js new file mode 100644 index 000000000..702e92f17 --- /dev/null +++ b/assets/annotations_error-code-policy.md.CcDC1Uqp.js @@ -0,0 +1,6 @@ +import{_ as a,c as s,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"ERROR_CODE_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Associate error handling policies with PostgreSQL REST API endpoints. Map PostgreSQL errors to custom HTTP responses.","frontmatter":{"outline":[2,3],"title":"ERROR_CODE_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Associate error handling policies with PostgreSQL REST API endpoints. Map PostgreSQL errors to custom HTTP responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest error policy, postgresql error handling, custom error responses, error code mapping"}],["meta",{"property":"og:title","content":"NpgsqlRest ERROR_CODE_POLICY Annotation"}],["meta",{"property":"og:description","content":"Associate error handling policies for custom PostgreSQL error responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/error-code-policy.md","filePath":"annotations/error-code-policy.md"}'),n={name:"annotations/error-code-policy.md"};function r(t,e,l,c,p,d){return o(),s("div",null,e[0]||(e[0]=[i(`

ERROR_CODE_POLICY

Also known as

error_code_policy, error_code (with or without @ prefix)

Associate an error handling policy with the endpoint.

Syntax

code
@error_code_policy <policy-name>
+@error_code <policy-name>

Examples

Named Policy

sql
sql
comment on function risky_operation() is
+'HTTP POST
+@error_code_policy strict_errors';

Short Form

sql
sql
comment on function api_endpoint() is
+'HTTP GET
+@error_code default_policy';

Behavior

See Also

`,18)]))}const u=a(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_error-code-policy.md.CcDC1Uqp.lean.js b/assets/annotations_error-code-policy.md.CcDC1Uqp.lean.js new file mode 100644 index 000000000..0ba7826e8 --- /dev/null +++ b/assets/annotations_error-code-policy.md.CcDC1Uqp.lean.js @@ -0,0 +1 @@ +import{_ as a,c as s,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"ERROR_CODE_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Associate error handling policies with PostgreSQL REST API endpoints. Map PostgreSQL errors to custom HTTP responses.","frontmatter":{"outline":[2,3],"title":"ERROR_CODE_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Associate error handling policies with PostgreSQL REST API endpoints. Map PostgreSQL errors to custom HTTP responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest error policy, postgresql error handling, custom error responses, error code mapping"}],["meta",{"property":"og:title","content":"NpgsqlRest ERROR_CODE_POLICY Annotation"}],["meta",{"property":"og:description","content":"Associate error handling policies for custom PostgreSQL error responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/error-code-policy.md","filePath":"annotations/error-code-policy.md"}'),n={name:"annotations/error-code-policy.md"};function r(t,e,l,c,p,d){return o(),s("div",null,e[0]||(e[0]=[i("",18)]))}const u=a(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_http-type.md.Yj3KYiqW.js b/assets/annotations_http-type.md.Yj3KYiqW.js new file mode 100644 index 000000000..ee7ac25b7 --- /dev/null +++ b/assets/annotations_http-type.md.Yj3KYiqW.js @@ -0,0 +1,182 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"HTTP Types Annotation","titleTemplate":"NpgsqlRest","description":"Define HTTP requests on PostgreSQL composite types. Enable SQL functions to call external APIs with automatic request/response handling.","frontmatter":{"outline":[2,3],"title":"HTTP Types Annotation","titleTemplate":"NpgsqlRest","description":"Define HTTP requests on PostgreSQL composite types. Enable SQL functions to call external APIs with automatic request/response handling.","head":[["meta",{"name":"keywords","content":"npgsqlrest http types, postgresql http request, call external api sql, http composite type, sql to http"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Types Annotation"}],["meta",{"property":"og:description","content":"Define HTTP requests on composite types for calling external APIs from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/http-type.md","filePath":"annotations/http-type.md"}'),t={name:"annotations/http-type.md"};function l(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

HTTP CUSTOM TYPES

Define HTTP request on a composite type to enable PostgreSQL functions to make HTTP requests to external APIs.

Overview

HTTP Types are PostgreSQL composite types with a special comment that defines an HTTP request. When a function uses an HTTP Type as a parameter, NpgsqlRest automatically makes the HTTP request and populates the type fields with the response before executing the function.

Syntax

The HTTP definition is added as a comment on the composite type:

code
[@timeout directive]
+[@retry_delay directive]
+[@cache directive]
+METHOD URL [HTTP/version]
+Header-Name: Header-Value
+...
+[@timeout directive]
+[@retry_delay directive]
+[@cache directive]
+
+[request body]

Directives (@timeout, @retry_delay, @cache) may appear either before the request line or after the headers — both placements are equivalent.

Supported Methods

Examples

Basic GET Request

sql
sql
-- Create response type
+create type simple_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+-- Define HTTP request
+comment on type simple_api is 'GET https://api.example.com/data';
+
+-- Use in function
+create function fetch_data(_response simple_api)
+returns text
+language sql
+begin atomic;
+select (_response).body;
+end;

Equivalent as a SQL file endpoint (sql/fetch-data.sql):

The HTTP Type itself must be defined in DDL (it's a composite type), but the consuming endpoint can be a SQL file. Assuming simple_api is already defined as above:

sql
sql
/*
+HTTP GET
+@param $1 response simple_api
+*/
+select ($1::simple_api).body;

GET with Headers and Placeholders

sql
sql
create type weather_api as (
+    body text,
+    status_code int,
+    headers json,
+    content_type text,
+    success boolean,
+    error_message text
+);
+
+comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
+Authorization: Bearer {_api_key}
+Accept: application/json
+@timeout 30s';
+
+create function get_weather(
+    _city text,
+    _api_key text,
+    _req weather_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    if (_req).success then
+        return (_req).body::json;
+    else
+        return json_build_object('error', (_req).error_message);
+    end if;
+end;
+$$;

POST with Request Body

sql
sql
create type create_user_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type create_user_api is 'POST https://api.example.com/users
+Content-Type: application/json
+Authorization: Bearer {_token}
+@timeout 10s
+
+{"name": "{_name}", "email": "{_email}"}';
+
+create function create_user(
+    _name text,
+    _email text,
+    _token text,
+    _response create_user_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    if (_response).success then
+        return (_response).body::json;
+    else
+        raise exception 'Failed to create user: %', (_response).error_message;
+    end if;
+end;
+$$;

Multiple API Calls

A function can have multiple HTTP Type parameters for chained API calls:

sql
sql
create type auth_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+create type data_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type auth_api is 'POST https://auth.example.com/token
+Content-Type: application/x-www-form-urlencoded
+
+client_id={_client_id}&client_secret={_client_secret}';
+
+comment on type data_api is 'GET https://api.example.com/data
+Authorization: Bearer {_token}';
+
+create function fetch_with_auth(
+    _client_id text,
+    _client_secret text,
+    _auth auth_api,
+    _token text,
+    _data data_api
+)
+returns json
+language plpgsql
+as $$
+begin
+    -- Note: _token would need to be extracted from _auth.body in practice
+    if not (_auth).success then
+        return json_build_object('error', 'Authentication failed');
+    end if;
+
+    if (_data).success then
+        return (_data).body::json;
+    else
+        return json_build_object('error', (_data).error_message);
+    end if;
+end;
+$$;

Response Fields

The composite type fields are populated based on their names:

Field NameTypeDescription
bodytextResponse body content
status_codeint or textHTTP status code (e.g., 200, 404)
headersjsonResponse headers as JSON object
content_typetextContent-Type header value
successbooleanTrue for 2xx status codes
error_messagetextError message if request failed

Field names are configurable via HTTP Client Options.

Timeout Directives

Timeout uses interval format:

FormatExample
Seconds (integer)@timeout 30
Seconds with suffix@timeout 30s
TimeSpan format@timeout 00:00:30
Minutes@timeout 2min
Without @ prefixtimeout 30s

Timeout can appear before the request line or after headers:

sql
sql
-- Before request line
+comment on type api_type is 'timeout 30
+GET https://api.example.com/data';
+
+-- After headers
+comment on type api_type is 'GET https://api.example.com/data
+Authorization: Bearer {_token}
+@timeout 30s';

Placeholder Substitution

Placeholders in the format {name} are replaced via the shared Parameter Value Substitution mechanism (case-insensitive name matching, NULL → empty, unknown → left literal). For HTTP types the type's own field names are also valid placeholders. A {name} can be supplied by any of three sources:

sql
sql
comment on type api_type is 'GET https://api.example.com/users/{_user_id}/posts?limit={_limit}
+Authorization: Bearer {_token}';
+
+create function get_user_posts(
+    _user_id int,       -- Substitutes {_user_id}
+    _limit int,         -- Substitutes {_limit}
+    _token text,        -- Substitutes {_token}
+    _response api_type  -- Receives HTTP response
+)
+...

Placeholders work in:

Retry Logic

The @retry_delay directive adds automatic retries with configurable delays for transient failures:

sql
sql
-- Retry on any failure:
+comment on type my_api_type is '@retry_delay 1s, 2s, 5s
+GET https://api.example.com/data';
+
+-- Retry only on specific HTTP status codes:
+comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
+GET https://api.example.com/data';
+
+-- Combined with timeout:
+comment on type my_api_type is '@timeout 10s
+@retry_delay 1s, 2s, 5s on 429, 503
+GET https://api.example.com/data';

The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1s, 2s, and 5s delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

Response Caching

The @cache directive caches the outbound HTTP response and reuses it for matching requests within a time window, instead of calling the upstream on every request:

sql
sql
comment on type books_api is '@cache 5m
+GET https://books.toscrape.com/';

A cached type fires one outbound call for a given request shape; subsequent matching requests are served from an in-memory cache until the TTL elapses. For a type with no per-request placeholders (a constant URL, headers, and body), that means a single shared upstream call per TTL window across the whole application — rather than one call per inbound request.

sql
sql
-- TTL accepts the same interval formats as @timeout:
+comment on type t is '@cache 30s
+GET https://api.example.com/data';
+
+comment on type t is '@cache 5m
+GET https://api.example.com/data';
+
+comment on type t is '@cache 00:05:00
+GET https://api.example.com/data';
+
+-- Combined with other directives (order and placement are flexible):
+comment on type t is '@timeout 10s
+@retry_delay 1s, 2s on 429, 503
+@cache 5m
+GET https://api.example.com/data';

Behavior and rules:

Caching is configured globally under HttpClientOptions (CacheEnabled kill switch, MaxCacheEntries, CachePruneIntervalSeconds).

Resolved Parameter Expressions

A common need with HTTP Types is a value computed server-side — an API token read from a table, a secret derived from the user's claims — injected into a {name} placeholder without the client ever supplying it. A resolved parameter expression does this: a param = <sql> annotation on the function runs that SQL per request and binds the result to the parameter, which then substitutes into the URL/headers/body.

sql
sql
comment on type my_api_response is 'GET https://api.example.com/data
+Authorization: Bearer {_token}';
+
+comment on function get_secure_data(_user_id int, _req my_api_response, _token text) is '
+_token = select api_token from user_tokens where user_id = {_user_id}
+';

The server resolves _token from the database, substitutes it into the Authorization header, and makes the call — the token never leaves the server or appears in client input.

See Resolved Parameters for the full reference (behavior, security, multiple expressions, table/refresh-token patterns).

Behavior

See Also

`,64)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_http-type.md.Yj3KYiqW.lean.js b/assets/annotations_http-type.md.Yj3KYiqW.lean.js new file mode 100644 index 000000000..f39302e3e --- /dev/null +++ b/assets/annotations_http-type.md.Yj3KYiqW.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"HTTP Types Annotation","titleTemplate":"NpgsqlRest","description":"Define HTTP requests on PostgreSQL composite types. Enable SQL functions to call external APIs with automatic request/response handling.","frontmatter":{"outline":[2,3],"title":"HTTP Types Annotation","titleTemplate":"NpgsqlRest","description":"Define HTTP requests on PostgreSQL composite types. Enable SQL functions to call external APIs with automatic request/response handling.","head":[["meta",{"name":"keywords","content":"npgsqlrest http types, postgresql http request, call external api sql, http composite type, sql to http"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Types Annotation"}],["meta",{"property":"og:description","content":"Define HTTP requests on composite types for calling external APIs from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/http-type.md","filePath":"annotations/http-type.md"}'),t={name:"annotations/http-type.md"};function l(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",64)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_http.md.DPCDKDD2.js b/assets/annotations_http.md.DPCDKDD2.js new file mode 100644 index 000000000..707e7fde3 --- /dev/null +++ b/assets/annotations_http.md.DPCDKDD2.js @@ -0,0 +1,65 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"HTTP Annotation","titleTemplate":"NpgsqlRest","description":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints. Configure HTTP methods (GET, POST, PUT, DELETE) and custom URL paths.","frontmatter":{"outline":[2,3],"title":"HTTP Annotation","titleTemplate":"NpgsqlRest","description":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints. Configure HTTP methods (GET, POST, PUT, DELETE) and custom URL paths.","head":[["meta",{"name":"keywords","content":"npgsqlrest http annotation, postgresql http endpoint, rest api function, http method postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Annotation"}],["meta",{"property":"og:description","content":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints with configurable methods and paths."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/http.md","filePath":"annotations/http.md"}'),t={name:"annotations/http.md"};function l(p,s,h,r,d,o){return n(),i("div",null,s[0]||(s[0]=[e(`

HTTP

Expose a PostgreSQL function, procedure, or SQL file as an HTTP endpoint.

Keywords

http

Syntax

code
HTTP
+HTTP <method>
+HTTP <path>
+HTTP <method> <path>

method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS

path: Custom URL path (must start with / or be a relative path)

CommentsMode Requirement

The HTTP annotation behavior depends on the CommentsMode configuration setting:

ModeHTTP Annotation Behavior
OnlyWithHttpTagRequired - Endpoints are only created for routines with HTTP in their comment (default).
ParseAllOptional - All routines become endpoints; HTTP can customize method/path.
IgnoreIgnored - All routines become endpoints; comments are not parsed.

With the default OnlyWithHttpTag mode, a function without the HTTP annotation will not be exposed as an endpoint.

Default Behavior

When method is not specified:

When path is not specified, it's generated from the function name using the configured URL prefix and naming conventions.

Examples

Basic Endpoint

sql
sql
create function get_status()
+returns text
+language sql
+begin atomic;
+select 'OK';
+end;
+
+comment on function get_status() is 'HTTP';

Equivalent as a SQL file endpoint (sql/get-status.sql):

sql
sql
-- HTTP
+select 'OK';

Creates: GET /api/get-status

Explicit HTTP Method

sql
sql
create function create_user(_name text)
+returns int
+language sql
+begin atomic;
+insert into users(name) values(_name) returning id;
+end;
+
+comment on function create_user(text) is 'HTTP POST';

Creates: POST /api/create-user

Custom Path

sql
sql
create function get_all_users()
+returns setof users
+language sql
+begin atomic;
+select * from users;
+end;
+
+comment on function get_all_users() is 'HTTP GET /users';

Creates: GET /users

Method and Custom Path

sql
sql
create function search_products(_query text)
+returns setof products
+language sql
+begin atomic;
+select * from products where name ilike '%' || _query || '%';
+end;
+
+comment on function search_products(text) is 'HTTP GET /products/search';

Creates: GET /products/search

Multi-line with Documentation

sql
sql
comment on function get_user_profile(int) is
+'Returns the complete user profile including preferences.
+Used by the frontend dashboard.
+
+HTTP GET /users/profile';

The documentation text is ignored; only the HTTP line is parsed.

Unrecognized Method Becomes Path

sql
sql
comment on function my_endpoint() is 'HTTP custom-endpoint';

Since custom-endpoint is not a valid HTTP method, it's treated as a path:

Creates: POST /custom-endpoint

Path Parameters

You can define RESTful path parameters using the {param} syntax in URL paths. Parameter values are extracted directly from the URL path instead of query strings or request body.

Single Path Parameter

sql
sql
create function get_product(p_id int)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_product(int) is 'HTTP GET /products/{p_id}';

Call: GET /products/123p_id = 123

Multiple Path Parameters

sql
sql
create function get_review(p_id int, review_id int)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_review(int, int) is 'HTTP GET /products/{p_id}/reviews/{review_id}';

Call: GET /products/5/reviews/10p_id = 5, review_id = 10

Path Parameters with Query String

sql
sql
create function get_product_details(p_id int, include_reviews boolean default false)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function get_product_details(int, boolean) is 'HTTP GET /products/{p_id}/details';

Call: GET /products/42/details?includeReviews=truep_id = 42, include_reviews = true

Path Parameters with JSON Body

sql
sql
create function update_product(p_id int, new_name text)
+returns text
+language sql
+begin atomic;
+select ...;
+end;
+
+comment on function update_product(int, text) is 'HTTP POST /products/{p_id}';

Call: POST /products/7 with body {"newName": "New Name"}p_id = 7, new_name = "New Name"

Path Parameter Key Features

`,58)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_http.md.DPCDKDD2.lean.js b/assets/annotations_http.md.DPCDKDD2.lean.js new file mode 100644 index 000000000..71c5ef485 --- /dev/null +++ b/assets/annotations_http.md.DPCDKDD2.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"HTTP Annotation","titleTemplate":"NpgsqlRest","description":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints. Configure HTTP methods (GET, POST, PUT, DELETE) and custom URL paths.","frontmatter":{"outline":[2,3],"title":"HTTP Annotation","titleTemplate":"NpgsqlRest","description":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints. Configure HTTP methods (GET, POST, PUT, DELETE) and custom URL paths.","head":[["meta",{"name":"keywords","content":"npgsqlrest http annotation, postgresql http endpoint, rest api function, http method postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Annotation"}],["meta",{"property":"og:description","content":"Expose PostgreSQL functions, procedures, and SQL files as HTTP endpoints with configurable methods and paths."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/http.md","filePath":"annotations/http.md"}'),t={name:"annotations/http.md"};function l(p,s,h,r,d,o){return n(),i("div",null,s[0]||(s[0]=[e("",58)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_index.md.DSU3OO0a.js b/assets/annotations_index.md.DSU3OO0a.js new file mode 100644 index 000000000..e4d25729f --- /dev/null +++ b/assets/annotations_index.md.DSU3OO0a.js @@ -0,0 +1 @@ +import{_ as a,c as t,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Annotations Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for all NpgsqlRest comment annotations. HTTP methods, authorization, caching, rate limiting, and more for PostgreSQL REST APIs.","frontmatter":{"outline":[2,3],"title":"Annotations Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for all NpgsqlRest comment annotations. HTTP methods, authorization, caching, rate limiting, and more for PostgreSQL REST APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest annotations, postgresql comment annotations, rest api annotations, http endpoint configuration, sql api annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest Annotations Reference"}],["meta",{"property":"og:description","content":"Complete reference for all NpgsqlRest comment annotations for PostgreSQL REST APIs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/index.md","filePath":"annotations/index.md"}'),o={name:"annotations/index.md"};function r(l,e,s,h,c,u){return i(),t("div",null,e[0]||(e[0]=[n('

Annotations Reference

Complete reference for all NpgsqlRest comment annotations. For an introduction to how annotations work, see the Comment Annotations Guide.

INFO

All annotations work in both PostgreSQL function/procedure comments (COMMENT ON FUNCTION ...) and SQL file endpoints (-- and /* */ comments in .sql files). The "SQL File Annotations" section below lists annotations that are specific to SQL files.

How to Use This Reference

Each annotation has its own page with:

Annotation Categories

HTTP & Routing

Authorization

Basic Authentication

Request Configuration

Response Configuration

Table Format Output

Raw Output Mode

Caching

Performance

Format References

Server-Sent Events

Upload

Policies

Context & Security

Parameter Annotations

SQL File Annotations

Test File Annotations

These apply only to test files run by the SQL test runner (npgsqlrest --test) — not to endpoint SQL files or routine comments:

Custom

',44)]))}const f=a(o,[["render",r]]);export{d as __pageData,f as default}; diff --git a/assets/annotations_index.md.DSU3OO0a.lean.js b/assets/annotations_index.md.DSU3OO0a.lean.js new file mode 100644 index 000000000..69b78173f --- /dev/null +++ b/assets/annotations_index.md.DSU3OO0a.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Annotations Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for all NpgsqlRest comment annotations. HTTP methods, authorization, caching, rate limiting, and more for PostgreSQL REST APIs.","frontmatter":{"outline":[2,3],"title":"Annotations Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for all NpgsqlRest comment annotations. HTTP methods, authorization, caching, rate limiting, and more for PostgreSQL REST APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest annotations, postgresql comment annotations, rest api annotations, http endpoint configuration, sql api annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest Annotations Reference"}],["meta",{"property":"og:description","content":"Complete reference for all NpgsqlRest comment annotations for PostgreSQL REST APIs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/index.md","filePath":"annotations/index.md"}'),o={name:"annotations/index.md"};function r(l,e,s,h,c,u){return i(),t("div",null,e[0]||(e[0]=[n("",44)]))}const f=a(o,[["render",r]]);export{d as __pageData,f as default}; diff --git a/assets/annotations_internal.md.yWqX_Gop.js b/assets/annotations_internal.md.yWqX_Gop.js new file mode 100644 index 000000000..afa23c19b --- /dev/null +++ b/assets/annotations_internal.md.yWqX_Gop.js @@ -0,0 +1,39 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"INTERNAL Annotation","titleTemplate":"NpgsqlRest","description":"Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but not exposed as a public HTTP route.","frontmatter":{"outline":[2,3],"title":"INTERNAL Annotation","titleTemplate":"NpgsqlRest","description":"Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but not exposed as a public HTTP route.","head":[["meta",{"name":"keywords","content":"npgsqlrest internal annotation, internal endpoint, internal only, self-referencing call, parallel query composition"}],["meta",{"property":"og:title","content":"NpgsqlRest INTERNAL Annotation"}],["meta",{"property":"og:description","content":"Mark an endpoint as internal-only — accessible via self-referencing calls but not exposed as a public HTTP route."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/internal.md","filePath":"annotations/internal.md"}'),l={name:"annotations/internal.md"};function t(p,s,r,h,k,c){return n(),a("div",null,s[0]||(s[0]=[e(`

INTERNAL

Also known as

internal, internal_only (with or without @ prefix)

Mark an endpoint as internal-only — accessible via self-referencing calls (proxy annotations and HTTP client types with relative paths) but not exposed as a public HTTP route.

Direct HTTP calls to an internal endpoint return 404. Internal calls via proxy or HTTP client types work normally.

Syntax

code
@internal
+@internal_only
+internal
+internal_only

All forms are equivalent.

Example: Internal Helper with Proxy

sql
sql
-- Internal helper: returns data but is NOT callable from outside
+create function get_cached_rates()
+returns json language sql as $$
+    select rates from exchange_rates order by fetched_at desc limit 1
+$$;
+comment on function get_cached_rates() is 'HTTP GET
+@internal';
+
+-- Public endpoint that proxies the internal one
+create function convert_currency(_amount numeric, _from text, _to text)
+returns json language plpgsql as $$
+...
+$$;
+comment on function convert_currency(numeric, text, text) is 'HTTP GET
+proxy GET /api/get-cached-rates';

Example: Internal Helper with HTTP Client Types

sql
sql
-- Internal data source
+create function get_users()
+returns json language sql as $$
+    select json_agg(row_to_json(u)) from users u
+$$;
+comment on function get_users() is 'HTTP GET
+@internal';
+
+-- HTTP client type pointing to internal endpoint
+create type api_users as (body text);
+comment on type api_users is 'GET /api/get-users';
+
+-- Public endpoint composing internal calls
+create function get_dashboard(_users api_users)
+returns json language plpgsql as $$
+begin
+    return json_build_object('users', (_users).body::json);
+end;
+$$;

SQL File Endpoints

Works on all endpoint sources — functions, procedures, and SQL files:

sql
sql
-- sql/internal_helper.sql
+-- HTTP GET
+-- @internal
+select * from cached_data;
`,17)]))}const y=i(l,[["render",t]]);export{d as __pageData,y as default}; diff --git a/assets/annotations_internal.md.yWqX_Gop.lean.js b/assets/annotations_internal.md.yWqX_Gop.lean.js new file mode 100644 index 000000000..bdd5a14af --- /dev/null +++ b/assets/annotations_internal.md.yWqX_Gop.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"INTERNAL Annotation","titleTemplate":"NpgsqlRest","description":"Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but not exposed as a public HTTP route.","frontmatter":{"outline":[2,3],"title":"INTERNAL Annotation","titleTemplate":"NpgsqlRest","description":"Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but not exposed as a public HTTP route.","head":[["meta",{"name":"keywords","content":"npgsqlrest internal annotation, internal endpoint, internal only, self-referencing call, parallel query composition"}],["meta",{"property":"og:title","content":"NpgsqlRest INTERNAL Annotation"}],["meta",{"property":"og:description","content":"Mark an endpoint as internal-only — accessible via self-referencing calls but not exposed as a public HTTP route."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/internal.md","filePath":"annotations/internal.md"}'),l={name:"annotations/internal.md"};function t(p,s,r,h,k,c){return n(),a("div",null,s[0]||(s[0]=[e("",17)]))}const y=i(l,[["render",t]]);export{d as __pageData,y as default}; diff --git a/assets/annotations_interval-format.md.qinH4egR.js b/assets/annotations_interval-format.md.qinH4egR.js new file mode 100644 index 000000000..6a766643d --- /dev/null +++ b/assets/annotations_interval-format.md.qinH4egR.js @@ -0,0 +1,59 @@ +import{_ as a,c as n,o as e,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Interval Format Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for time and duration formats used in NpgsqlRest annotations. Supported units, syntax variations, and examples.","frontmatter":{"outline":[2,3],"title":"Interval Format Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for time and duration formats used in NpgsqlRest annotations. Supported units, syntax variations, and examples.","head":[["meta",{"name":"keywords","content":"npgsqlrest interval, time format, duration format, timeout format, cache expiration format, postgresql interval"}],["meta",{"property":"og:title","content":"NpgsqlRest Interval Format Reference"}],["meta",{"property":"og:description","content":"Complete reference for time and duration formats used in NpgsqlRest annotations."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/interval-format.md","filePath":"annotations/interval-format.md"}'),t={name:"annotations/interval-format.md"};function l(p,s,r,o,c,d){return e(),n("div",null,s[0]||(s[0]=[i(`

Interval Format Reference

Several NpgsqlRest annotations accept time or duration values. This page documents the supported interval format used throughout the system.

Syntax

code
<number>[unit]
+<number> [unit]

Supported Units

UnitShortLong Forms
Microsecondsususec, microsecond, microseconds
Millisecondsmsmsec, millisecond, milliseconds
Secondsssec, second, seconds
Minutesmmin, minute, minutes
Hourshhour, hours
Daysdday, days
Weekswweek, weeks

All unit names are case-insensitive: 5s, 5S, 5sec, 5SEC, 5Seconds are all equivalent.

Examples

code
30s          -- 30 seconds
+5m           -- 5 minutes
+1h           -- 1 hour
+1d           -- 1 day
+2w           -- 2 weeks
+500ms        -- 500 milliseconds
+1000us       -- 1000 microseconds

Long Form

code
30seconds    -- 30 seconds
+5minutes     -- 5 minutes
+1hour        -- 1 hour
+1day         -- 1 day
+2weeks       -- 2 weeks

With Space

code
30 s         -- 30 seconds
+5 minutes    -- 5 minutes
+1 hour       -- 1 hour
+1 d          -- 1 day

Decimal Values

code
1.5h         -- 1 hour 30 minutes
+0.5d         -- 12 hours
+2.5m         -- 2 minutes 30 seconds
+500.5ms      -- 500.5 milliseconds

No Unit (Defaults to Seconds)

code
30           -- 30 seconds
+120          -- 120 seconds (2 minutes)
+3600         -- 3600 seconds (1 hour)
+1.5          -- 1.5 seconds

Usage in Annotations

@timeout / @command_timeout

Sets query execution timeout:

sql
sql
comment on function quick_lookup() is
+'HTTP GET
+@timeout 5s';
+
+comment on function slow_report() is
+'HTTP GET
+@timeout 2min';
+
+comment on function very_long_process() is
+'HTTP GET
+@timeout 1h';

Single Token for @timeout

The @timeout annotation reads only the first token after the keyword. Use formats without spaces or use the short forms to avoid parsing issues.

@cache_expires_in

Sets cache expiration time:

sql
sql
comment on function get_live_data() is
+'HTTP GET
+@cached
+@cache_expires_in 10s';
+
+comment on function get_dashboard() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';
+
+comment on function get_static_config() is
+'HTTP GET
+@cached
+@cache_expires_in 1d';

Configuration Values

The same interval format is used in JSON configuration files:

json
json
{
+  "NpgsqlRest": {
+    "CommandTimeout": "30s"
+  },
+  "CacheOptions": {
+    "DefaultExpiration": "5m",
+    "LocalCacheExpiration": "1m"
+  },
+  "Auth": {
+    "JwtClockSkew": "5m"
+  }
+}

Invalid Formats

The following formats are not supported:

code
5.5.5h       -- Multiple decimal points
+h5           -- Unit before number
+5 m m        -- Multiple units
+5months      -- Unsupported unit
+1year        -- Unsupported unit (use days or weeks)
`,35)]))}const m=a(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_interval-format.md.qinH4egR.lean.js b/assets/annotations_interval-format.md.qinH4egR.lean.js new file mode 100644 index 000000000..873a68cd6 --- /dev/null +++ b/assets/annotations_interval-format.md.qinH4egR.lean.js @@ -0,0 +1 @@ +import{_ as a,c as n,o as e,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Interval Format Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for time and duration formats used in NpgsqlRest annotations. Supported units, syntax variations, and examples.","frontmatter":{"outline":[2,3],"title":"Interval Format Reference","titleTemplate":"NpgsqlRest","description":"Complete reference for time and duration formats used in NpgsqlRest annotations. Supported units, syntax variations, and examples.","head":[["meta",{"name":"keywords","content":"npgsqlrest interval, time format, duration format, timeout format, cache expiration format, postgresql interval"}],["meta",{"property":"og:title","content":"NpgsqlRest Interval Format Reference"}],["meta",{"property":"og:description","content":"Complete reference for time and duration formats used in NpgsqlRest annotations."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/interval-format.md","filePath":"annotations/interval-format.md"}'),t={name:"annotations/interval-format.md"};function l(p,s,r,o,c,d){return e(),n("div",null,s[0]||(s[0]=[i("",35)]))}const m=a(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_login.md.CNtMBAwP.js b/assets/annotations_login.md.CNtMBAwP.js new file mode 100644 index 000000000..6f713e5d5 --- /dev/null +++ b/assets/annotations_login.md.CNtMBAwP.js @@ -0,0 +1,219 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"LOGIN Annotation","titleTemplate":"NpgsqlRest","description":"Create authentication endpoints for PostgreSQL REST APIs. Handle user sign-in with cookies, JWT tokens, or bearer tokens — and turn returned columns into user claims.","frontmatter":{"outline":[2,3],"title":"LOGIN Annotation","titleTemplate":"NpgsqlRest","description":"Create authentication endpoints for PostgreSQL REST APIs. Handle user sign-in with cookies, JWT tokens, or bearer tokens — and turn returned columns into user claims.","head":[["meta",{"name":"keywords","content":"npgsqlrest login, authentication endpoint, signin api, user login postgresql, jwt login endpoint, claims mapping"}],["meta",{"property":"og:title","content":"NpgsqlRest LOGIN Annotation"}],["meta",{"property":"og:description","content":"Create authentication endpoints for user sign-in with various token types."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/login.md","filePath":"annotations/login.md"}'),t={name:"annotations/login.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

LOGIN

Also known as

signin (with or without @ prefix)

Mark a routine (function/procedure) or SQL file endpoint as a sign-in endpoint.

code
@login

Looking for the bigger picture?

This page is the reference for the @login annotation. For an end-to-end walkthrough — configuring an auth scheme, how claims flow through the system, and reading claims back in your other endpoints — see the Authentication guide.

How a login endpoint works

A login endpoint is an ordinary endpoint that returns one row. NpgsqlRest treats that row specially:

  1. The client POSTs credentials (e.g. username + password) to the endpoint.
  2. Your SQL runs and returns at most one record.
  3. NpgsqlRest reads a few special columns (status, scheme, body, hash) for control flow.
  4. Every other column becomes a user claim — the column name is the claim name, the column value is the claim value.
  5. NpgsqlRest signs the user in by issuing the cookie or token for the active scheme.
mermaid
flowchart TD
+    C["Client
+    POST /login (username, password)"]
+    F["Your login function / .sql
+    returns one row"]
+    R["Returned row
+    user_id=1, username=alice, email=a@x.com"]
+    N["NpgsqlRest
+    reads special columns (status, scheme, body, hash)
+    turns every other column into a claim
+    issues cookie / token"]
+    O["Signed in
+    Set-Cookie or Bearer token
+    claims: user_id=1, username=alice, email=a@x.com"]
+
+    C --> F --> R --> N --> O

There is no status column required — if the row is returned the login succeeds; if no row is returned the result is 401 Unauthorized. (Add a status column only when you need explicit HTTP status control.)

Minimal example

The simplest login: verify the password inside SQL, return the user's claims on success, return nothing on failure.

sql
sql
create function login(_username text, _password text)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    email text
+)
+language sql
+security definer
+as $$
+select
+    'cookies' as scheme,   -- special column: which auth scheme to sign in
+    u.user_id,             -- every non-special column
+    u.username,            -- becomes a claim
+    u.email
+from users u
+where u.username = _username
+  and verify_password(_password, u.password_hash);  -- your own check
+$$;
+
+comment on function login(text, text) is '
+HTTP POST
+@login
+@anonymous
+@security_sensitive';

Equivalent as a SQL file endpoint (sql/login.sql):

sql
sql
/*
+HTTP POST
+@login
+@anonymous
+@security_sensitive
+@param $1 username
+@param $2 password
+*/
+select
+    'cookies' as scheme,   -- special column: which auth scheme to sign in
+    u.user_id,             -- every non-special column
+    u.username,            -- becomes a claim
+    u.email
+from users u
+where u.username = $1
+  and verify_password($2, u.password_hash);

This is the pattern used in the Security & Auth example. NpgsqlRest never sees the password hash — you verify it yourself. For the alternative where NpgsqlRest verifies the hash for you, see Password verification.

The return record

RuleResult
Must return a named record (table)A void, a scalar (int/text), or an unnamed record → 401 Unauthorized
Returns a row (and no failing status/hash)Login succeeds, claims are created
Returns no row (empty result)401 Unauthorized
Returns multiple rowsOnly the first row is read; the rest is discarded

Column names are matched against the configured special-column names and claim mappings using either the original PostgreSQL column name or the converted name (camelCase by default).

Special columns

Four column names are consumed by NpgsqlRest for control flow and are not turned into claims. Their names are configurable in AuthenticationOptions.

ColumnConfig optionDefaultPurpose
StatusStatusColumnNamestatusControls login success/failure and HTTP status
SchemeSchemeColumnNameschemeWhich authentication scheme to sign in
BodyBodyColumnNamebodyResponse body message
HashHashColumnNamehashPassword hash for built-in verification

Status column

Optional. When present, it controls success/failure explicitly:

Boolean:

Numeric (int/smallint/bigint):

If the column is neither boolean nor numeric, the endpoint returns 500 Internal Server Error.

Omit the status column entirely to use the default rule: row returned = success, no row = 401.

Scheme column

Sets the authentication scheme used for this sign-in. Use it when more than one scheme is configured (e.g. cookie and bearer token and JWT) so a single login function can issue any of them — typically driven by a request parameter:

sql
sql
-- the client asks for 'cookies', 'token', or 'jwt'
+select _scheme as scheme, u.user_id, u.username, u.roles
+from users u
+where u.username = _username;

A scheme value that isn't configured is rejected (404). See the Multiple Auth Schemes example.

Body column

A text message returned in the response body — but only when the active scheme doesn't already write the body itself:

Claims: how columns become the user

This is the core of the login contract. Every returned column that isn't a special column becomes a claim, where:

So a login function returning user_id, username, email, roles produces exactly those four claims, plus whatever else you select. No transformation, no mapping config is needed to create claims — you simply select the columns you want.

Identity claims

Among all the claims, three are designated as the canonical identity. NpgsqlRest uses them for the signed-in principal, for role-based @authorize, and as $2/$3 in the verification callbacks. They are configured in AuthenticationOptions:

Config optionDefault claim nameUsed for
DefaultUserIdClaimTypeuser_idThe user identifier
DefaultNameClaimTypeuser_nameThe display name
DefaultRoleClaimTypeuser_rolesRoles for @authorize role1, role2

Make sure your login routine returns a column matching each of these names (or change the config to match your column names). For example, the Multiple Auth Schemes example returns a roles column and configures:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "DefaultUserIdClaimType": "user_id",
+      "DefaultNameClaimType": "username",
+      "DefaultRoleClaimType": "roles"
+    }
+  }
+}

Now @authorize admin works because NpgsqlRest knows the roles claim holds the role list.

Using claims in your other endpoints

Once a user is signed in, the claims travel with every request. To read them inside a protected endpoint, NpgsqlRest can inject them two ways:

MechanismHow you read the claimMapping configAnnotation
As parametersa function/$N parameterParameterNameClaimsMapping (param name → claim name)@user_parameters
As context variablescurrent_setting('request.user_id', true)ContextKeyClaimsMapping (context key → claim name)@user_context

The thread that ties it all together is the claim name — the same string appears as the login column name and as the value in the mapping object:

mermaid
flowchart LR
+    COL["login column
+    user_id"]
+    CLAIM["claim name
+    user_id"]
+    P["parameter
+    _user_id
+    read with @user_parameters"]
+    CTX["context variable
+    request.user_id
+    read with current_setting()"]
+
+    COL --> CLAIM
+    CLAIM -->|"ParameterNameClaimsMapping"| P
+    CLAIM -->|"ContextKeyClaimsMapping"| CTX

Both mechanisms are covered in depth — with worked examples — in the Authentication guide → Accessing claims. See also @user_parameters, @user_context, and Claims Mapping configuration.

Password verification

There are two ways to verify the password. Pick one.

Which one should I use?

Option B (the built-in hasher) is the more secure default and is recommended for production. Two reasons:

Option A (verify in SQL) is simpler and keeps everything in the database — fine for small or low-traffic apps, or when you want full control over the hashing scheme.

Option A — verify in SQL (no hash column)

You verify the password yourself (as in the minimal example) and simply don't return a matching row when it fails. NpgsqlRest stays out of it — this is the simplest approach and gives you full control over hashing.

You don't need anything external: PostgreSQL's built-in pgcrypto extension already provides crypt(), gen_salt(), and digest():

sql
sql
create extension if not exists pgcrypto;

Recommended hashing — pre-hash the password with SHA-256 and base64-encode it before bcrypt. Bcrypt silently truncates its input at 72 bytes; the SHA-256 + base64 step produces a fixed 44-character digest that always fits, so passwords of any length (and any byte content) are hashed safely:

sql
sql
-- hash (on registration / password change)
+crypt(encode(digest(_password, 'sha256'), 'base64'), gen_salt('bf', 12))
+
+-- verify (on login) — compare the recomputed hash against the stored one
+crypt(encode(digest(_password, 'sha256'), 'base64'), _password_hash) = _password_hash

Wrap them as reusable helpers — verify_password() is the function used in the minimal example above:

sql
sql
create function hash_password(_password text)
+returns text
+language sql
+as $$
+  select crypt(encode(digest(_password, 'sha256'), 'base64'), gen_salt('bf', 12));
+$$;
+
+create function verify_password(_password text, _password_hash text)
+returns boolean
+language sql
+as $$
+  select crypt(encode(digest(_password, 'sha256'), 'base64'), _password_hash) = _password_hash;
+$$;

Store the hash when registering a user with the same hash_password():

sql
sql
insert into users (username, email, password_hash)
+values (_username, _email, hash_password(_password));

Work factor

The second argument to gen_salt('bf', …) is the bcrypt work factor (cost). 12 is a sensible default in 2025 — raise it for stronger (but slower) hashing.

Option B — built-in hasher (return a hash column)

Return the stored password hash in a column named hash (configurable via HashColumnName) and let NpgsqlRest verify it against the submitted password using its built-in hasher.

When a hash column is present, NpgsqlRest:

  1. Reads the hash value from that column.
  2. Identifies the password parameter — the first parameter whose name contains PasswordParameterNameContains (default pass).
  3. Verifies the submitted password against the hash.
  4. On failure, returns 404 Not Found and the row's claims are discarded.

The hash column name and the password-parameter substring are set in AuthenticationOptions — these are the defaults:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "HashColumnName": "hash",
+      "PasswordParameterNameContains": "pass"
+    }
+  }
+}

Change them to match your own naming. The example below uses these defaults:

sql
sql
create function login(_username text, _password text)
+returns table (hash text, user_id int, username text, email text, roles text[])
+language sql
+as $$
+  select
+    u.password_hash as hash,  -- NpgsqlRest verifies _password against this
+    u.user_id,
+    u.username,
+    u.email,
+    u.roles
+  from users u
+  where u.username = _username;
+$$;
+
+comment on function login(text, text) is '
+HTTP POST
+@login
+@anonymous
+@security_sensitive';

The built-in hasher uses PBKDF2 with SHA-256, a 128-bit salt, and 600,000 iterations (OWASP-recommended as of 2025). Use the matching @parameter_hash annotation when registering users so the stored hash is compatible. A custom IPasswordHasher can be injected in source code if needed.

Verification callbacks

With Option B, you can run a command on success or failure of the built-in verification — the only way to react to the outcome, since the verification itself happens inside NpgsqlRest.

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "PasswordVerificationFailedCommand": "call password_verification_failed($1, $2, $3)",
+      "PasswordVerificationSucceededCommand": "call password_verification_succeeded($1, $2, $3)"
+    }
+  }
+}

Both commands receive up to three positional parameters — all optional (define your procedure with 0, 1, 2, or 3):

PositionTypeDescription
$1textAuthentication scheme used for the login
$2textUser ID (the DefaultUserIdClaimType claim)
$3textUsername (the DefaultNameClaimType claim)

Typical use — lock the account after repeated failures, reset the counter and log on success:

sql
sql
create procedure password_verification_failed(_scheme text, _user_id text, _user_name text)
+language plpgsql as $$
+declare _attempts int;
+begin
+    update users set password_attempts = password_attempts + 1
+    where user_id = _user_id::int
+    returning password_attempts into _attempts;
+
+    if _attempts >= 5 then
+        update users set locked_until = now() + interval '15 minutes'
+        where user_id = _user_id::int;
+    end if;
+end;
+$$;
+
+create procedure password_verification_succeeded(_scheme text, _user_id text, _user_name text)
+language plpgsql as $$
+begin
+    update users set password_attempts = 0
+    where user_id = _user_id::int and password_attempts > 0;
+
+    insert into login_history (user_id, logged_in_at) values (_user_id::int, now());
+end;
+$$;

See the Multiple Auth Schemes example for both callbacks wired up end-to-end.

More examples

Multiple schemes from one login

A single login function that can sign the user into cookie, bearer-token, or JWT depending on the requested scheme, using the built-in hasher (hash column):

sql
sql
create function login(_scheme text, _username text, _password text)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    roles text[],
+    email text,
+    hash text
+)
+language sql
+as $$
+select
+    _scheme,            -- 'cookies', 'token' or 'jwt'
+    u.user_id,
+    u.username,
+    u.roles,
+    u.email,
+    u.password_hash as hash  -- built-in verification
+from users u
+where u.username = _username;
+$$;
+
+comment on function login(text, text, text) is '
+HTTP POST
+@login
+@anonymous
+@security_sensitive';

Equivalent as a SQL file endpoint (sql/login.sql):

sql
sql
/*
+HTTP POST
+@login
+@anonymous
+@security_sensitive
+@param $1 scheme
+@param $2 username
+@param $3 password
+*/
+select
+    $1 as scheme,            -- 'cookies', 'token' or 'jwt'
+    u.user_id,
+    u.username,
+    u.roles,
+    u.email,
+    u.password_hash as hash  -- built-in verification
+from users u
+where u.username = $2;

Explicit status code and message

Use a status + body column to return a specific HTTP status with a message (cookie scheme, where body is honored):

sql
sql
create function login(_email text, _password text)
+returns table (status int, body text, user_id int, username text)
+language plpgsql
+as $$
+begin
+  if exists (
+    select 1 from users
+    where email = _email and verify_password(_password, password_hash)
+  ) then
+    return query
+      select 200, 'Welcome back'::text, u.user_id, u.username
+      from users u where u.email = _email;
+  else
+    return query select 401, 'Invalid credentials'::text, null::int, null::text;
+  end if;
+end;
+$$;
+
+comment on function login(text, text) is '
+HTTP POST /auth/login
+@login
+@anonymous
+@security_sensitive';

Role-protected endpoint after login

Once roles is the role claim, protect endpoints with @authorize:

sql
sql
comment on function get_users() is '
+HTTP GET
+@authorize admin';   -- only users whose roles claim contains "admin"
`,101)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_login.md.CNtMBAwP.lean.js b/assets/annotations_login.md.CNtMBAwP.lean.js new file mode 100644 index 000000000..bc111c22d --- /dev/null +++ b/assets/annotations_login.md.CNtMBAwP.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"LOGIN Annotation","titleTemplate":"NpgsqlRest","description":"Create authentication endpoints for PostgreSQL REST APIs. Handle user sign-in with cookies, JWT tokens, or bearer tokens — and turn returned columns into user claims.","frontmatter":{"outline":[2,3],"title":"LOGIN Annotation","titleTemplate":"NpgsqlRest","description":"Create authentication endpoints for PostgreSQL REST APIs. Handle user sign-in with cookies, JWT tokens, or bearer tokens — and turn returned columns into user claims.","head":[["meta",{"name":"keywords","content":"npgsqlrest login, authentication endpoint, signin api, user login postgresql, jwt login endpoint, claims mapping"}],["meta",{"property":"og:title","content":"NpgsqlRest LOGIN Annotation"}],["meta",{"property":"og:description","content":"Create authentication endpoints for user sign-in with various token types."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/login.md","filePath":"annotations/login.md"}'),t={name:"annotations/login.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e("",101)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_logout.md.Cle7WE1K.js b/assets/annotations_logout.md.Cle7WE1K.js new file mode 100644 index 000000000..2a6c19a33 --- /dev/null +++ b/assets/annotations_logout.md.Cle7WE1K.js @@ -0,0 +1,62 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"LOGOUT Annotation","titleTemplate":"NpgsqlRest","description":"Create sign-out endpoints for PostgreSQL REST APIs. Handle user logout by clearing cookies and invalidating sessions.","frontmatter":{"outline":[2,3],"title":"LOGOUT Annotation","titleTemplate":"NpgsqlRest","description":"Create sign-out endpoints for PostgreSQL REST APIs. Handle user logout by clearing cookies and invalidating sessions.","head":[["meta",{"name":"keywords","content":"npgsqlrest logout, signout endpoint, user logout api, session invalidation, clear authentication"}],["meta",{"property":"og:title","content":"NpgsqlRest LOGOUT Annotation"}],["meta",{"property":"og:description","content":"Create sign-out endpoints that clear cookies and invalidate sessions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/logout.md","filePath":"annotations/logout.md"}'),l={name:"annotations/logout.md"};function t(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[e(`

LOGOUT

Also known as

signout (with or without @ prefix)

Mark endpoint as a sign-out endpoint.

Syntax

code
@logout

Logout Endpoint Behavior

When an endpoint is marked with logout, NpgsqlRest executes the sign-out operation after running the function.

Void Functions

If the function returns void, NpgsqlRest simply:

  1. Executes the function
  2. Calls sign-out on all authentication schemes
  3. Completes the response

Functions with Return Values

If the function returns values, all returned values are interpreted as authentication scheme names to sign out from. This allows selective logout from specific schemes.

This is useful when using multiple authentication schemes (e.g., Cookie and Bearer Token) and you want to sign out from only specific ones.

Examples

Basic Logout (Void)

sql
sql
create function signout()
+returns void
+language sql
+begin atomic;
+  -- Optionally perform cleanup
+  delete from sessions where user_id = current_user_id();
+end;
+
+comment on function signout() is
+'HTTP POST
+@logout
+@authorize';

Equivalent as a SQL file endpoint (sql/signout.sql):

sql
sql
-- HTTP POST
+-- @logout
+-- @authorize
+delete from sessions where user_id = current_user_id();

Signs out from all authentication schemes.

Logout from Specific Scheme

sql
sql
create function logout_cookie()
+returns text
+language sql
+begin atomic;
+  select 'Cookies'::text;
+end;
+
+comment on function logout_cookie() is
+'HTTP POST /auth/logout/cookie
+@logout
+@authorize';

Signs out only from the "Cookies" authentication scheme.

Logout from Multiple Schemes

sql
sql
create function logout_web()
+returns text[]
+language sql
+begin atomic;
+  select array['Cookies', 'Bearer']::text[];
+end;
+
+comment on function logout_web() is
+'HTTP POST /auth/logout/web
+@logout
+@authorize';

Signs out from both "Cookies" and "Bearer" schemes.

Conditional Scheme Logout

sql
sql
create function smart_logout(_scheme text default null)
+returns text
+language sql
+begin atomic;
+  select _scheme;  -- Returns NULL to logout from all, or specific scheme
+end;
+
+comment on function smart_logout(text) is
+'HTTP POST /auth/logout
+@logout
+@authorize';

Logout with Cleanup

sql
sql
create function full_logout()
+returns void
+language plpgsql
+as $$
+begin
+  -- Revoke all refresh tokens for this user
+  delete from refresh_tokens where user_id = current_user_id();
+
+  -- Log the logout event
+  insert into audit_log(user_id, action)
+  values (current_user_id(), 'logout');
+end;
+$$;
+
+comment on function full_logout() is
+'HTTP POST
+@logout
+@authorize';

See Also

`,37)]))}const u=i(l,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_logout.md.Cle7WE1K.lean.js b/assets/annotations_logout.md.Cle7WE1K.lean.js new file mode 100644 index 000000000..de76eaab5 --- /dev/null +++ b/assets/annotations_logout.md.Cle7WE1K.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"LOGOUT Annotation","titleTemplate":"NpgsqlRest","description":"Create sign-out endpoints for PostgreSQL REST APIs. Handle user logout by clearing cookies and invalidating sessions.","frontmatter":{"outline":[2,3],"title":"LOGOUT Annotation","titleTemplate":"NpgsqlRest","description":"Create sign-out endpoints for PostgreSQL REST APIs. Handle user logout by clearing cookies and invalidating sessions.","head":[["meta",{"name":"keywords","content":"npgsqlrest logout, signout endpoint, user logout api, session invalidation, clear authentication"}],["meta",{"property":"og:title","content":"NpgsqlRest LOGOUT Annotation"}],["meta",{"property":"og:description","content":"Create sign-out endpoints that clear cookies and invalidate sessions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/logout.md","filePath":"annotations/logout.md"}'),l={name:"annotations/logout.md"};function t(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[e("",37)]))}const u=i(l,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_mcp.md.CUGE4v1n.js b/assets/annotations_mcp.md.CUGE4v1n.js new file mode 100644 index 000000000..c4a97c357 --- /dev/null +++ b/assets/annotations_mcp.md.CUGE4v1n.js @@ -0,0 +1,29 @@ +import{_ as s,c as t,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"MCP Annotation","titleTemplate":"NpgsqlRest","description":"Opt a PostgreSQL routine in as a Model Context Protocol (MCP) tool. Expose functions to AI agents for discovery and execution, with an optional MCP-only (no HTTP route) mode.","frontmatter":{"outline":[2,3],"title":"MCP Annotation","titleTemplate":"NpgsqlRest","description":"Opt a PostgreSQL routine in as a Model Context Protocol (MCP) tool. Expose functions to AI agents for discovery and execution, with an optional MCP-only (no HTTP route) mode.","head":[["meta",{"name":"keywords","content":"npgsqlrest mcp annotation, model context protocol postgresql, expose function as mcp tool, ai agent tools, postgresql mcp server, mcp tool name"}],["meta",{"property":"og:title","content":"NpgsqlRest MCP Annotation"}],["meta",{"property":"og:description","content":"Opt a PostgreSQL routine in as an MCP tool for AI agents in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/mcp.md","filePath":"annotations/mcp.md"}'),i={name:"annotations/mcp.md"};function o(l,e,r,p,c,d){return a(),t("div",null,e[0]||(e[0]=[n(`

MCP

New in 3.17.0

The @mcp annotation and the NpgsqlRest.Mcp plugin were added in version 3.17.0. It implements the Model Context Protocol specification 2025-11-25.

Opt a routine in as an MCP tool so an AI agent can discover it (tools/list) and execute it (tools/call) over the MCP server endpoint.

Exposure is never automatic — a routine becomes a tool only when its comment carries @mcp. When the MCP plugin is not loaded (or McpOptions.Enabled is false), the annotation is a no-op — safe to leave on a routine regardless of how the host is configured.

Syntax

code
@mcp                       # expose as a tool; description from the comment prose
+@mcp <text>                # expose; <text> is an inline (explicit) tool description
+@mcp_description <text>    # expose; explicit, authoritative description (alias: @mcp_desc)
+@mcp_name <name>           # override the tool name (default: the routine name)

Description precedence

The tool's description uses a fixed priority — the highest-priority source that is present wins, regardless of the order the lines appear in the comment — and an explicit description suppresses the comment-prose fallback (so unrelated comment lines never leak into it):

  1. @mcp_description <text> — explicit and authoritative. Always wins when present, even if it appears after an @mcp <text> line.
  2. inline @mcp <text> — explicit.
  3. comment prose — the routine's free-text comment lines (those that aren't annotations). Used only when no explicit description is given.
  4. the routine name — last resort (a warning is logged).

So if you give any explicit description, the rest of your comment is just a comment. (Order only matters when you repeat the same annotation — the last occurrence wins.) Provide a description explicitly (preferably @mcp_description) whenever your comment also contains notes you don't want an agent to see; let the prose fallback do the work when your comment is the description.

MCP-only tools (no HTTP route)

The HTTP tag controls the REST route; @mcp controls the tool — independently. A bare @mcp with no HTTP tag exposes the routine only as an MCP tool, with no public REST endpoint:

sql
sql
comment on function summarize_account(_account_id int) is '
+@mcp Summarize an account for the agent, including balance and recent activity.
+';

The routine is callable via tools/call but has no HTTP route — an endpoint that exists only because @mcp requested it is internal-only by default, so opting into MCP never silently widens your HTTP surface. (Requires the comment-gated modes — OnlyAnnotated, the client default, or OnlyWithHttpTag. A debug log notes the defaulting at startup.)

The full matrix:

Comment carriesResult
HTTP GET + @mcpREST endpoint and MCP tool
@mcp onlyMCP-only (no REST route)
HTTP GET onlyREST-only (no tool)
HTTP GET + @mcp + @internalMCP-only (@internal hides the declared route)

This works identically for SQL file endpoints: a .sql file whose comment carries @mcp but no HTTP tag becomes an MCP-only tool (without @mcp such a file is skipped as a non-endpoint script, as before).

All other annotations apply equally — most usefully @authorize: when a tool runs, the caller's authenticated identity is forwarded, so role checks are enforced exactly as they would be for the HTTP endpoint.

Examples

Expose a routine as a tool (HTTP and MCP)

sql
sql
create function get_weather(_city text)
+returns text
+language sql as $$
+  select format('Weather for %s: sunny, 22C', _city);
+$$;
+
+comment on function get_weather(_city text) is '
+HTTP GET /api/weather
+@mcp Get the current weather for a city.
+';

The routine is reachable at GET /api/weather and advertised as the get_weather MCP tool with the description "Get the current weather for a city." and an input schema derived from its parameters ({ "city": { "type": "string" } }).

Description from comment prose

sql
sql
comment on function list_open_tickets() is '
+HTTP GET /api/tickets/open
+List all currently open support tickets for triage.
+@mcp
+';

With a bare @mcp, the description is taken from the prose line — "List all currently open support tickets for triage."

Explicit description, with a private note that stays out of it

sql
sql
comment on function rebuild_search_index() is '
+HTTP POST
+@mcp Rebuild the product search index. Safe to call; runs in the background.
+@mcp_description Rebuild the product search index. Returns immediately.
+TODO: revisit batch size — internal note, must NOT reach the agent.
+';

Because @mcp_description is present, it is the description verbatim — the inline @mcp text and the TODO: prose line are both ignored. (For SQL-file endpoints, also see SqlFileSource.CommentScope, which controls which comments are parsed at all.)

Override the tool name

sql
sql
comment on function fn_q1_report() is '
+@mcp Quarterly revenue report.
+@mcp_name quarterly_report
+';

The tool is published as quarterly_report rather than fn_q1_report — and since there is no HTTP tag, it is MCP-only (no REST route).

As a SQL file endpoint (sql/quarterly-report.sql):

sql
sql
-- @mcp Quarterly revenue report.
+-- @mcp_name quarterly_report
+select * from generate_quarterly_report();

Recognized keywords

FormAction
@mcpExpose as a tool; description from comment prose
@mcp <text>Expose as a tool; <text> is an inline (explicit) description
@mcp_description <text>Expose as a tool; explicit, authoritative description (alias @mcp_desc) — suppresses comment prose
@mcp_name <name>Override the tool name
`,37)]))}const u=s(i,[["render",o]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_mcp.md.CUGE4v1n.lean.js b/assets/annotations_mcp.md.CUGE4v1n.lean.js new file mode 100644 index 000000000..0e42c03a9 --- /dev/null +++ b/assets/annotations_mcp.md.CUGE4v1n.lean.js @@ -0,0 +1 @@ +import{_ as s,c as t,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"MCP Annotation","titleTemplate":"NpgsqlRest","description":"Opt a PostgreSQL routine in as a Model Context Protocol (MCP) tool. Expose functions to AI agents for discovery and execution, with an optional MCP-only (no HTTP route) mode.","frontmatter":{"outline":[2,3],"title":"MCP Annotation","titleTemplate":"NpgsqlRest","description":"Opt a PostgreSQL routine in as a Model Context Protocol (MCP) tool. Expose functions to AI agents for discovery and execution, with an optional MCP-only (no HTTP route) mode.","head":[["meta",{"name":"keywords","content":"npgsqlrest mcp annotation, model context protocol postgresql, expose function as mcp tool, ai agent tools, postgresql mcp server, mcp tool name"}],["meta",{"property":"og:title","content":"NpgsqlRest MCP Annotation"}],["meta",{"property":"og:description","content":"Opt a PostgreSQL routine in as an MCP tool for AI agents in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/mcp.md","filePath":"annotations/mcp.md"}'),i={name:"annotations/mcp.md"};function o(l,e,r,p,c,d){return a(),t("div",null,e[0]||(e[0]=[n("",37)]))}const u=s(i,[["render",o]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_nested.md.G3R18jm5.js b/assets/annotations_nested.md.G3R18jm5.js new file mode 100644 index 000000000..34a2caa7c --- /dev/null +++ b/assets/annotations_nested.md.G3R18jm5.js @@ -0,0 +1,78 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"NESTED Annotation","titleTemplate":"NpgsqlRest","description":"Serialize composite type columns as nested JSON objects instead of expanding fields. Control JSON structure for PostgreSQL composite types.","frontmatter":{"outline":[2,3],"title":"NESTED Annotation","titleTemplate":"NpgsqlRest","description":"Serialize composite type columns as nested JSON objects instead of expanding fields. Control JSON structure for PostgreSQL composite types.","head":[["meta",{"name":"keywords","content":"npgsqlrest nested json, composite type json, postgresql nested object, json serialization, composite column"}],["meta",{"property":"og:title","content":"NpgsqlRest NESTED Annotation"}],["meta",{"property":"og:description","content":"Serialize composite type columns as nested JSON objects instead of expanding fields."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/nested.md","filePath":"annotations/nested.md"}'),e={name:"annotations/nested.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

NESTED

Also known as

nested_json, nested_composite (with or without @ prefix)

Serialize composite type columns as nested JSON objects instead of expanding their fields into separate columns.

Syntax

code
@nested

Default Behavior vs Nested

When a function returns a composite type column, by default the composite type fields are expanded into separate columns (for backward compatibility).

With the nested annotation, composite type columns are serialized as nested JSON objects.

Examples

Basic Usage

sql
sql
create type address_type as (
+    street text,
+    city text,
+    zip_code text
+);
+
+create function get_user_with_address()
+returns table(
+    user_id int,
+    user_name text,
+    address address_type
+)
+language sql
+begin atomic;
+select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
+end;
+
+comment on function get_user_with_address() is 'HTTP GET
+@nested';

Default behavior (without @nested):

json
json
[{"userId":1,"userName":"Alice","street":"123 Main St","city":"New York","zipCode":"10001"}]

With @nested annotation:

json
json
[{"userId":1,"userName":"Alice","address":{"street":"123 Main St","city":"New York","zipCode":"10001"}}]

Multiple Composite Columns

sql
sql
create type contact_info as (
+    email text,
+    phone text
+);
+
+create type location as (
+    lat numeric,
+    lng numeric
+);
+
+create function get_business()
+returns table(
+    id int,
+    name text,
+    contact contact_info,
+    coords location
+)
+language sql
+begin atomic;
+select 1, 'Acme Corp',
+       row('info@acme.com', '555-1234')::contact_info,
+       row(40.7128, -74.0060)::location;
+end;
+
+comment on function get_business() is 'HTTP GET
+@nested';

Response:

json
json
[{
+    "id": 1,
+    "name": "Acme Corp",
+    "contact": {"email": "info@acme.com", "phone": "555-1234"},
+    "coords": {"lat": 40.7128, "lng": -74.0060}
+}]

Deep Nested Composite Types

When composite types contain other composite types (or arrays of composites), the inner composites are also serialized as proper JSON objects by default:

sql
sql
create type inner_type as (id int, name text);
+create type outer_type as (label text, inner_val inner_type);
+
+create function get_nested_data()
+returns table(data outer_type)
+language sql
+begin atomic;
+select row('outer', row(1, 'inner')::inner_type)::outer_type;
+end;
+
+comment on function get_nested_data() is 'HTTP GET
+@nested';

Response:

json
json
[{"data": {"label": "outer", "innerVal": {"id": 1, "name": "inner"}}}]

This works to any nesting depth. Deep resolution is controlled by the ResolveNestedCompositeTypes option (default: true). See Routine Options for details and when you might want to disable it.

Arrays of Composite Types

Arrays of composite types are automatically serialized as JSON arrays of objects — this happens regardless of the @nested annotation:

sql
sql
create type book_item as (book_id int, title text);
+
+create function get_books()
+returns table(author text, books book_item[])
+language sql
+begin atomic;
+select 'Orwell', array[row(1, '1984')::book_item, row(2, 'Animal Farm')::book_item];
+end;
+
+comment on function get_books() is 'HTTP GET';

Response:

json
json
[{"author": "Orwell", "books": [{"bookId": 1, "title": "1984"}, {"bookId": 2, "title": "Animal Farm"}]}]

The @nested annotation specifically controls whether single composite type columns are serialized as nested objects or expanded into flat fields.

Global Configuration

Instead of adding the annotation to each endpoint, you can enable nested JSON globally via configuration. Each endpoint source has its own independent setting:

json
json
{
+  "NpgsqlRest": {
+    "RoutineOptions": {
+      "NestedJsonForCompositeTypes": true
+    },
+    "SqlFileSource": {
+      "NestedJsonForCompositeTypes": true
+    }
+  }
+}

When enabled globally, all composite type columns from the respective endpoint source will be serialized as nested JSON objects by default, without requiring the annotation.

Behavior

`,39)]))}const F=i(e,[["render",l]]);export{c as __pageData,F as default}; diff --git a/assets/annotations_nested.md.G3R18jm5.lean.js b/assets/annotations_nested.md.G3R18jm5.lean.js new file mode 100644 index 000000000..6537b5fd0 --- /dev/null +++ b/assets/annotations_nested.md.G3R18jm5.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"NESTED Annotation","titleTemplate":"NpgsqlRest","description":"Serialize composite type columns as nested JSON objects instead of expanding fields. Control JSON structure for PostgreSQL composite types.","frontmatter":{"outline":[2,3],"title":"NESTED Annotation","titleTemplate":"NpgsqlRest","description":"Serialize composite type columns as nested JSON objects instead of expanding fields. Control JSON structure for PostgreSQL composite types.","head":[["meta",{"name":"keywords","content":"npgsqlrest nested json, composite type json, postgresql nested object, json serialization, composite column"}],["meta",{"property":"og:title","content":"NpgsqlRest NESTED Annotation"}],["meta",{"property":"og:description","content":"Serialize composite type columns as nested JSON objects instead of expanding fields."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/nested.md","filePath":"annotations/nested.md"}'),e={name:"annotations/nested.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",39)]))}const F=i(e,[["render",l]]);export{c as __pageData,F as default}; diff --git a/assets/annotations_new-line.md.9HsK47GA.js b/assets/annotations_new-line.md.9HsK47GA.js new file mode 100644 index 000000000..3067d1794 --- /dev/null +++ b/assets/annotations_new-line.md.9HsK47GA.js @@ -0,0 +1,12 @@ +import{_ as s,c as n,o as e,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"NEW_LINE Annotation","titleTemplate":"NpgsqlRest","description":"Set row separator for raw output mode in PostgreSQL REST APIs. Configure line endings for CSV and text output.","frontmatter":{"outline":[2,3],"title":"NEW_LINE Annotation","titleTemplate":"NpgsqlRest","description":"Set row separator for raw output mode in PostgreSQL REST APIs. Configure line endings for CSV and text output.","head":[["meta",{"name":"keywords","content":"npgsqlrest new line, row separator, line ending, csv line break, raw output newline"}],["meta",{"property":"og:title","content":"NpgsqlRest NEW_LINE Annotation"}],["meta",{"property":"og:description","content":"Set row separator for raw output mode and CSV line endings."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/new-line.md","filePath":"annotations/new-line.md"}'),t={name:"annotations/new-line.md"};function l(o,a,r,p,d,c){return e(),n("div",null,a[0]||(a[0]=[i(`

NEW_LINE

Also known as

raw_new_line (with or without @ prefix)

Set the row separator for raw output mode.

Syntax

code
@new_line <string>

Supports escape sequences: \\n (newline), \\r\\n (Windows newline), \\\\ (backslash)

Examples

Unix Line Endings

sql
sql
comment on function export_unix() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \\n';

Windows Line Endings

sql
sql
comment on function export_windows() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \\r\\n';

Custom Row Separator

sql
sql
comment on function export_custom() is
+'HTTP GET
+@raw
+@new_line |||';
`,17)]))}const m=s(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_new-line.md.9HsK47GA.lean.js b/assets/annotations_new-line.md.9HsK47GA.lean.js new file mode 100644 index 000000000..306e2c938 --- /dev/null +++ b/assets/annotations_new-line.md.9HsK47GA.lean.js @@ -0,0 +1 @@ +import{_ as s,c as n,o as e,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"NEW_LINE Annotation","titleTemplate":"NpgsqlRest","description":"Set row separator for raw output mode in PostgreSQL REST APIs. Configure line endings for CSV and text output.","frontmatter":{"outline":[2,3],"title":"NEW_LINE Annotation","titleTemplate":"NpgsqlRest","description":"Set row separator for raw output mode in PostgreSQL REST APIs. Configure line endings for CSV and text output.","head":[["meta",{"name":"keywords","content":"npgsqlrest new line, row separator, line ending, csv line break, raw output newline"}],["meta",{"property":"og:title","content":"NpgsqlRest NEW_LINE Annotation"}],["meta",{"property":"og:description","content":"Set row separator for raw output mode and CSV line endings."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/new-line.md","filePath":"annotations/new-line.md"}'),t={name:"annotations/new-line.md"};function l(o,a,r,p,d,c){return e(),n("div",null,a[0]||(a[0]=[i("",17)]))}const m=s(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_openapi.md.CYi2hImP.js b/assets/annotations_openapi.md.CYi2hImP.js new file mode 100644 index 000000000..c58255a1b --- /dev/null +++ b/assets/annotations_openapi.md.CYi2hImP.js @@ -0,0 +1,46 @@ +import{_ as a,c as e,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"OPENAPI Annotation","titleTemplate":"NpgsqlRest","description":"Per-routine control over OpenAPI document inclusion and grouping. Hide an endpoint from the spec or override its default schema tag.","frontmatter":{"outline":[2,3],"title":"OPENAPI Annotation","titleTemplate":"NpgsqlRest","description":"Per-routine control over OpenAPI document inclusion and grouping. Hide an endpoint from the spec or override its default schema tag.","head":[["meta",{"name":"keywords","content":"npgsqlrest openapi annotation, hide endpoint from openapi, openapi tag, swagger ui grouping, partner facing openapi, postgresql openapi"}],["meta",{"property":"og:title","content":"NpgsqlRest OPENAPI Annotation"}],["meta",{"property":"og:description","content":"Per-routine override for OpenAPI document inclusion and section grouping in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/openapi.md","filePath":"annotations/openapi.md"}'),t={name:"annotations/openapi.md"};function l(p,s,r,o,d,c){return n(),e("div",null,s[0]||(s[0]=[i(`

OPENAPI

New in 3.15.0

The @openapi annotation was added in version 3.15.0.

Per-routine override for OpenAPI document inclusion and section grouping. Two sub-commands: hide an endpoint from the document entirely, or replace its default schema-name tag with one or more custom tags.

The HTTP endpoint itself is unaffected — @openapi hide only suppresses the spec entry. The endpoint is still reachable, still respects @authorize, still runs the same SQL.

When the OpenAPI plugin is not loaded, both sub-commands are no-ops — safe to leave on a routine regardless of how the host is configured.

Syntax

code
@openapi                  # hide from document (default action)
+@openapi hide             # hide from document
+@openapi hidden           # alias for hide
+@openapi ignore           # alias for hide
+
+@openapi tag <name>             # replace default schema tag with <name>
+@openapi tags <a>, <b>, <c>     # replace default tag with multiple tags

Tag values preserve their original casing — @openapi tag Partner API produces a Partner API tag, not partner api.

How it composes with config-level filters

@openapi is the first filter applied — it wins over IncludeSchemas, ExcludeSchemas, NameSimilarTo, NameNotSimilarTo, and RequiresAuthorizationOnly. See Filter order in the OpenAPI config reference.

This means @openapi hide reliably keeps a routine out of the document even when broad config filters would otherwise include it (e.g. when IncludeSchemas allows the schema).

Examples

Hide an internal maintenance routine

sql
sql
create function refresh_materialized_views()
+returns void
+language sql security definer as $$
+  refresh materialized view concurrently revenue_summary;
+  refresh materialized view concurrently user_activity;
+$$;
+
+comment on function refresh_materialized_views() is '
+HTTP POST
+@authorize admin
+@openapi hide
+';

The endpoint stays reachable at POST /api/refresh-materialized-views for admin callers — it just isn't advertised in the generated openapi.json.

As a SQL file endpoint (sql/refresh-materialized-views.sql):

sql
sql
-- HTTP POST
+-- @authorize admin
+-- @openapi hide
+refresh materialized view concurrently revenue_summary;

Group routines under a custom tag

By default, endpoints are tagged with their schema name — every routine in public lands in a public section in Swagger UI / ReDoc. @openapi tag overrides that.

sql
sql
comment on function partner_get_orders(_partner_id text) is '
+HTTP GET /api/partner/orders
+@authorize partner
+@openapi tag Partner API
+';
+
+comment on function partner_create_order(_partner_id text, _order_json text) is '
+HTTP POST /api/partner/orders
+@authorize partner
+@openapi tag Partner API
+';

Both endpoints group under a single Partner API section in Swagger UI instead of the default public tag.

Multiple tags

sql
sql
comment on function get_dashboard_summary() is '
+HTTP GET /api/dashboard
+@authorize
+@openapi tags Dashboard, Reports
+';

The endpoint appears in both the Dashboard and Reports sections.

Hide alongside a config filter

@openapi hide is checked before IncludeSchemas etc., so even when broad filters would include the routine, @openapi hide keeps it out:

json
json
{
+  "NpgsqlRest": {
+    "OpenApiOptions": {
+      "IncludeSchemas": ["partner"]
+    }
+  }
+}
sql
sql
-- Lives in partner schema, but marked hidden — won't appear in the partner document.
+comment on function partner.diagnostic_check() is '
+HTTP GET /api/partner/_diagnostic
+@authorize partner
+@openapi hide
+';

Recognized keywords

FormAction
@openapiHide from document
@openapi hideHide from document
@openapi hiddenHide from document
@openapi ignoreHide from document
@openapi tag <name>Replace default tag with <name>
@openapi tags <a>, <b>Replace default tag with multiple tags
`,32)]))}const k=a(t,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_openapi.md.CYi2hImP.lean.js b/assets/annotations_openapi.md.CYi2hImP.lean.js new file mode 100644 index 000000000..abbfa06a6 --- /dev/null +++ b/assets/annotations_openapi.md.CYi2hImP.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"OPENAPI Annotation","titleTemplate":"NpgsqlRest","description":"Per-routine control over OpenAPI document inclusion and grouping. Hide an endpoint from the spec or override its default schema tag.","frontmatter":{"outline":[2,3],"title":"OPENAPI Annotation","titleTemplate":"NpgsqlRest","description":"Per-routine control over OpenAPI document inclusion and grouping. Hide an endpoint from the spec or override its default schema tag.","head":[["meta",{"name":"keywords","content":"npgsqlrest openapi annotation, hide endpoint from openapi, openapi tag, swagger ui grouping, partner facing openapi, postgresql openapi"}],["meta",{"property":"og:title","content":"NpgsqlRest OPENAPI Annotation"}],["meta",{"property":"og:description","content":"Per-routine override for OpenAPI document inclusion and section grouping in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/openapi.md","filePath":"annotations/openapi.md"}'),t={name:"annotations/openapi.md"};function l(p,s,r,o,d,c){return n(),e("div",null,s[0]||(s[0]=[i("",32)]))}const k=a(t,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_param.md.DCLc58O5.js b/assets/annotations_param.md.DCLc58O5.js new file mode 100644 index 000000000..ce861eb0b --- /dev/null +++ b/assets/annotations_param.md.DCLc58O5.js @@ -0,0 +1,82 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Rename, retype, set defaults, and configure composite type parameters. Works on all endpoint types — functions, procedures, and SQL file endpoints.","frontmatter":{"outline":[2,3],"title":"PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Rename, retype, set defaults, and configure composite type parameters. Works on all endpoint types — functions, procedures, and SQL file endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest param annotation, rename parameter, retype parameter, default value, composite type parameter, sql file parameters, api parameter names"}],["meta",{"property":"og:title","content":"NpgsqlRest PARAM Annotation"}],["meta",{"property":"og:description","content":"Rename, retype, set defaults, and configure composite type parameters."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/param.md","filePath":"annotations/param.md"}'),t={name:"annotations/param.md"};function l(p,s,r,h,o,c){return e(),i("div",null,s[0]||(s[0]=[n(`

PARAM

Also known as

param, parameter (with or without @ prefix)

Rename and optionally retype individual endpoint parameters. This provides better API ergonomics by replacing positional parameter names ($1, $2) or internal parameter names (_old_name) with cleaner, user-facing names.

Works on all endpoint types — functions, procedures, and SQL file endpoints.

TIP

The @param keyword is shared with the PARAMETER_HASH annotation (@param X is hash of Y). Both forms coexist without ambiguity — the parser distinguishes them by the presence of hash of in the annotation.

Syntax

code
@param <old_name> <new_name>
+@param <old_name> <new_name> <type>
+@param <old_name> is <new_name>
+@param <old_name> is <new_name> <type>
+@param <old_name> default <value>
+@param <old_name> <new_name> default <value>
+@param <old_name> <new_name> <type> default <value>
+@param <old_name> is <new_name> default <value>
+@param <old_name> is <new_name> <type> default <value>
+
+# \`=\` can be used instead of \`default\` in all forms above:
+@param <old_name> = <value>
+@param <old_name> <new_name> = <value>
+@param <old_name> <new_name> <type> = <value>
+@param <old_name> is <new_name> = <value>
+@param <old_name> is <new_name> <type> = <value>

Both @param and @parameter (long form) are supported.

Examples

Rename Positional Parameters (SQL Files)

SQL files use PostgreSQL positional parameters ($1, $2, ...) which aren't user-friendly as HTTP parameter names. Use @param to give them meaningful names:

sql
sql
-- sql/get_reports.sql
+-- HTTP GET
+-- @param $1 from_date
+-- @param $2 to_date
+select id, title, created_at
+from reports
+where created_at between $1 and $2;

Without rename: GET /api/get-reports?$1=2024-01-01&$2=2024-12-31

With rename: GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31

Rename with Type Override

When parameter types can't be inferred correctly, or when you need to override the inferred type:

sql
sql
-- @param $1 user_id integer
+-- @param $2 active boolean
+select * from users where id = $1 and active = $2;

Rename Function Parameters

Works on function and procedure parameters too — useful when internal naming conventions (like _ prefixes) shouldn't leak into the API:

sql
sql
create function get_user_profile(_user_id int, _include_stats boolean)
+returns json
+language sql
+begin atomic;
+select json_build_object('id', id, 'name', name) from users where id = _user_id;
+end;
+
+comment on function get_user_profile(int, boolean) is '
+HTTP GET
+@param _user_id user_id
+@param _include_stats include_stats
+';

Without rename: GET /api/get-user-profile?_user_id=1&_include_stats=true

With rename: GET /api/get-user-profile?user_id=1&include_stats=true

"is" Style Syntax

The is keyword is optional and provides consistency with the existing @param X is hash of Y style:

sql
sql
-- These are equivalent:
+-- @param $1 user_id
+-- @param $1 is user_id
+
+-- With type override:
+-- @param $1 user_id integer
+-- @param $1 is user_id integer

Claim Mapping with Renamed Parameters

Renamed parameters work with user_parameters claim mapping. When you rename a positional parameter to a claim-mapped name (like _user_id or _user_name), the parameter is automatically filled from the authenticated user's claims — just like it would be for a native function parameter.

sql
sql
-- sql/get_my_profile.sql
+-- @authorize
+-- @user_parameters
+-- @param $1 _user_id
+-- @param $2 _user_name
+select $1 as user_id, $2 as user_name;

GET /api/get-my-profile (authenticated as user123) → [{"userId": "user123", "userName": "user"}]

The parameters are auto-filled from claims — the client doesn't need to send them. This is especially useful for SQL file endpoints where positional parameters ($1, $2) have no inherent name for claim matching.

Default Values (SQL File Parameters)

SQL file parameters can have default values via @param. When a parameter with a default is not provided in the request, the default value is bound instead of returning 404.

This is essential for SQL files because positional parameters ($1, $2) must always be bound — unlike PostgreSQL functions where the engine applies its own defaults.

Syntax

sql
sql
-- Separate annotations (rename first, then set default):
+-- @param $1 user_id
+-- @param user_id default null
+
+-- Combined rename + default on a single line:
+-- @param $1 user_id default null
+
+-- Default without rename:
+-- @param $1 default 'fallback'
+
+-- Various value types:
+-- @param $1 status default 'active'     -- text (single-quoted)
+-- @param $1 amount default 42           -- number
+-- @param $1 enabled default true        -- boolean
+-- @param $1 filter default null         -- SQL NULL (unquoted)
+-- @param $1 tag default 'null'          -- literal text "null" (quoted)
+-- @param $1 val default                 -- no value = NULL

Inline comments after the value are ignored. The parser consumes only the value token (or quoted string) and stops.

sql
sql

+-- \`=\` can be used instead of \`default\` in all forms:
+-- @param $1 user_id = null
+-- @param $1 user_id integer = 42
+-- @param $1 is greeting = 'hey'
+-- @param my_name = 'hello'

Value Parsing Rules (SQL Conventions)

Example

User identity endpoint with claim-filled parameters that fall back to NULL:

sql
sql
/* HTTP GET
+@authorize
+@user_parameters
+@param $1 _user_id default null
+@param $2 _username default null
+@param $3 _email default null
+*/
+select $1 as user_id, $2 as username, $3 as email;

When authenticated, claims fill the parameters automatically. The defaults ensure the parameters are always bindable.

Effects on Generated Output

Rename Validation

Parameter names are validated when renaming. Invalid renames are rejected with a warning log instead of silently creating broken endpoints.

Rules:

sql
sql
-- Valid:
+-- @param $1 user_id        ✓
+-- @param $1 _val$1         ✓
+
+-- Rejected (with warning log):
+-- @param $1 1bad           ✗ starts with digit
+-- @param $1 my-param       ✗ invalid character (hyphen)

Composite Type Parameters (SQL Files)

When a parameter type is a known composite type, the parameter is treated as a single text value. The SQL is never rewritten — it stays exactly as written.

HTTP custom types (auto-filled from HTTP calls):

sql
sql
-- @param $1 _response example_9.exchange_rate_api
+select ($1::example_9.exchange_rate_api).body;

The framework makes the HTTP call and passes the response as a composite text value automatically.

Client-sent composite types:

sql
sql
-- @param $1 data my_composite_type
+select ($1::my_composite_type).field1, ($1::my_composite_type).field2;

The client sends the value as PostgreSQL composite text format: ?data=("val1","val2").

If the type in @param is not a recognized PostgreSQL type or composite type, a warning is logged and the parameter keeps its original type from Describe.

Behavior

`,64)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_param.md.DCLc58O5.lean.js b/assets/annotations_param.md.DCLc58O5.lean.js new file mode 100644 index 000000000..b65f20f31 --- /dev/null +++ b/assets/annotations_param.md.DCLc58O5.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Rename, retype, set defaults, and configure composite type parameters. Works on all endpoint types — functions, procedures, and SQL file endpoints.","frontmatter":{"outline":[2,3],"title":"PARAM Annotation","titleTemplate":"NpgsqlRest","description":"Rename, retype, set defaults, and configure composite type parameters. Works on all endpoint types — functions, procedures, and SQL file endpoints.","head":[["meta",{"name":"keywords","content":"npgsqlrest param annotation, rename parameter, retype parameter, default value, composite type parameter, sql file parameters, api parameter names"}],["meta",{"property":"og:title","content":"NpgsqlRest PARAM Annotation"}],["meta",{"property":"og:description","content":"Rename, retype, set defaults, and configure composite type parameters."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/param.md","filePath":"annotations/param.md"}'),t={name:"annotations/param.md"};function l(p,s,r,h,o,c){return e(),i("div",null,s[0]||(s[0]=[n("",64)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_parameter-hash.md.DAN4SbBB.js b/assets/annotations_parameter-hash.md.DAN4SbBB.js new file mode 100644 index 000000000..f068a256a --- /dev/null +++ b/assets/annotations_parameter-hash.md.DAN4SbBB.js @@ -0,0 +1,56 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PARAMETER_HASH Annotation","titleTemplate":"NpgsqlRest","description":"Hash password parameters for secure user registration. Automatically hash passwords before storing in PostgreSQL database.","frontmatter":{"outline":[2,3],"title":"PARAMETER_HASH Annotation","titleTemplate":"NpgsqlRest","description":"Hash password parameters for secure user registration. Automatically hash passwords before storing in PostgreSQL database.","head":[["meta",{"name":"keywords","content":"npgsqlrest parameter hash, password hashing, secure registration, hash password api, user registration postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest PARAMETER_HASH Annotation"}],["meta",{"property":"og:description","content":"Hash password parameters for secure user registration endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/parameter-hash.md","filePath":"annotations/parameter-hash.md"}'),t={name:"annotations/parameter-hash.md"};function l(p,s,h,r,k,o){return n(),i("div",null,s[0]||(s[0]=[e(`

PARAMETER_HASH

Also known as

param (with or without @ prefix)

Hash one parameter value using another parameter as the hash input. This annotation is commonly used to create user registration endpoints that securely store hashed passwords in the database.

Syntax

code
@param <target_param> is hash of <source_param>
+@parameter <target_param> is hash of <source_param>

Examples

Simple User Registration

sql
sql
create function register(_email text, _password text, _hash text)
+returns int
+language sql
+begin atomic;
+insert into users (email, password_hash) values (_email, _hash) returning id;
+end;
+
+comment on function register(text, text, text) is '
+@param _hash is hash of _password
+';

Equivalent as a SQL file endpoint (sql/register.sql):

sql
sql
/*
+HTTP POST
+@param $1 email
+@param $2 password
+@param $3 hash is hash of password
+*/
+insert into users (email, password_hash) values ($1, $3) returning id;

User Registration with Response

sql
sql
create function create_user(
+    _username text,
+    _password text,
+    _password_hash text
+)
+returns json
+language sql
+begin atomic;
+insert into users (username, password_hash)
+values (_username, _password_hash)
+returning json_build_object('id', id, 'username', username);
+end;
+
+comment on function create_user(text, text, text) is '
+HTTP POST
+@param _password_hash is hash of _password
+';

When called with {"username": "john", "password": "secret123"}:

Behavior

Built-in Password Hasher

The default password hasher uses PBKDF2 (Password-Based Key Derivation Function 2) with:

This provides secure password hashing out of the box. A custom IPasswordHasher implementation can be injected in source code if needed.

Complete Registration and Login Flow

The param is hash of annotation works together with the LOGIN annotation to provide a complete authentication flow using the same built-in password hasher:

  1. Registration: Use param <target> is hash of <source> to hash passwords before storing them
  2. Login: Return the stored hash in a hash column and NpgsqlRest verifies it automatically

Registration Function

sql
sql
create function register(_email text, _password text, _hash text)
+returns int
+language sql
+begin atomic;
+insert into users (email, password_hash) values (_email, _hash) returning id;
+end;
+
+comment on function register(text, text, text) is '
+HTTP POST /auth/register
+@param _hash is hash of _password
+@sensitive
+';

Login Function

sql
sql
create function login(_email text, _password text)
+returns table(hash text, id int, name text, email text)
+language sql
+begin atomic;
+select u.password_hash as hash, u.id, u.name, u.email
+from users u where u.email = _email;
+end;
+
+comment on function login(text, text) is '
+HTTP POST /auth/login
+@login
+@sensitive
+';

Both functions use the same PBKDF2 hasher, ensuring passwords hashed during registration can be verified during login.

`,33)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_parameter-hash.md.DAN4SbBB.lean.js b/assets/annotations_parameter-hash.md.DAN4SbBB.lean.js new file mode 100644 index 000000000..5f999d1c4 --- /dev/null +++ b/assets/annotations_parameter-hash.md.DAN4SbBB.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PARAMETER_HASH Annotation","titleTemplate":"NpgsqlRest","description":"Hash password parameters for secure user registration. Automatically hash passwords before storing in PostgreSQL database.","frontmatter":{"outline":[2,3],"title":"PARAMETER_HASH Annotation","titleTemplate":"NpgsqlRest","description":"Hash password parameters for secure user registration. Automatically hash passwords before storing in PostgreSQL database.","head":[["meta",{"name":"keywords","content":"npgsqlrest parameter hash, password hashing, secure registration, hash password api, user registration postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest PARAMETER_HASH Annotation"}],["meta",{"property":"og:description","content":"Hash password parameters for secure user registration endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/parameter-hash.md","filePath":"annotations/parameter-hash.md"}'),t={name:"annotations/parameter-hash.md"};function l(p,s,h,r,k,o){return n(),i("div",null,s[0]||(s[0]=[e("",33)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_parameter-substitution.md.BAnY-Etv.js b/assets/annotations_parameter-substitution.md.BAnY-Etv.js new file mode 100644 index 000000000..6f4a21c08 --- /dev/null +++ b/assets/annotations_parameter-substitution.md.BAnY-Etv.js @@ -0,0 +1,27 @@ +import{_ as a,c as s,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse(`{"title":"Parameter Value Substitution","titleTemplate":"NpgsqlRest","description":"Use {name} placeholders in NpgsqlRest comment annotations to inject a request's parameter values into response headers, custom parameters, and HTTP custom type calls at request time.","frontmatter":{"outline":[2,3],"title":"Parameter Value Substitution","titleTemplate":"NpgsqlRest","description":"Use {name} placeholders in NpgsqlRest comment annotations to inject a request's parameter values into response headers, custom parameters, and HTTP custom type calls at request time.","head":[["meta",{"name":"keywords","content":"npgsqlrest parameter substitution, annotation placeholder, dynamic response header, dynamic content-type, parameter value placeholder, npgsqlrest {name}"}],["meta",{"property":"og:title","content":"NpgsqlRest Parameter Value Substitution"}],["meta",{"property":"og:description","content":"Inject request parameter values into annotation values with {name} placeholders."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/parameter-substitution.md","filePath":"annotations/parameter-substitution.md"}`),i={name:"annotations/parameter-substitution.md"};function r(l,e,o,p,d,h){return t(),s("div",null,e[0]||(e[0]=[n(`

Parameter Value Substitution

Several comment annotations accept a {name} placeholder in their value. At request time, {name} is replaced with the value of the routine parameter name taken from that request. This lets a single endpoint produce a response header, file name, upload path, or outbound HTTP call that depends on what the caller sent.

This is one shared mechanism reused by a few annotations — this page documents it once; each annotation page links here.

sql
sql
create function export_report(_type text, _file text)
+returns text language sql as $$ select '...report...' $$;
+
+comment on function export_report(text, text) is '
+HTTP GET
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}
+';

A request GET /api/export-report?type=text/csv&file=q1.csv responds with Content-Type: text/csv and Content-Disposition: attachment; filename=q1.csv.

Where it works

{name} substitution is applied to these annotation values:

AnnotationWhat is substitutedPage
Response headers (Header-Name: value), including Content-Typethe header valueResponse Headers
Custom parameters (@key = value) — e.g. upload paths/filenamesthe value after =Custom Parameters
HTTP custom types — request URL, query string, headers, and bodythose parts of the outbound callHTTP Custom Types

Other annotations do not perform this substitution. (Braces in unrelated annotations — e.g. a URL {segment} in PATH — are a different feature; see Not to be confused with.)

How a placeholder is resolved

For each request, NpgsqlRest builds a lookup from the bound parameters (plus any allowlisted environment variables) and replaces every {name} it finds:

Brace handling

Environment variables

A {name} can also resolve to an environment variable — useful for outbound API keys (HTTP custom types) or per-deployment values like a server/environment name in a response header — without routing them through request parameters.

This is opt-in via an allowlist: only environment variables you name in NpgsqlRest:AvailableEnvVars can be referenced. Any other {NAME} is never read from the environment (it stays literal, like an unknown parameter). The allowlist is the security boundary — there is no way to substitute an arbitrary env var.

jsonc
jsonc
"NpgsqlRest": {
+  // array form — a missing variable resolves to an empty string
+  "AvailableEnvVars": [ "WEATHER_API_KEY", "SERVER_NAME" ]
+
+  // …or object form — name → default used when the variable is absent
+  // "AvailableEnvVars": { "SERVER_NAME": "local" }
+}
sql
sql
comment on type weather_api is '
+GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {WEATHER_API_KEY}
+';

Here {_city} comes from a request parameter and {WEATHER_API_KEY} from the allowlisted environment variable — the API key never has to be passed by the caller.

Rules specific to env vars:

Response headers are client-visible

A value substituted into a response header is sent to the caller. That's exactly what you want for a per-pod Server: {SERVER_NAME} header, but it means you must not put a secret env var in a response header. Reserve secrets (API keys, tokens) for outbound HTTP custom type calls and custom parameters, which stay server-side.

Examples

Dynamic file download

sql
sql
comment on function get_invoice(_id int, _filename text) is '
+HTTP GET
+Content-Type: application/pdf
+Content-Disposition: attachment; filename={_filename}
+';

Upload destination from a parameter

See Custom Parameters and Upload. The upload handler's path/filename keys accept placeholders:

sql
sql
comment on function upload_avatar(_user_id int, _path text) is '
+@upload for file_system
+@file_system_path = /var/uploads/{_user_id}
+';

Outbound HTTP call shaped by parameters

See HTTP Custom Types. The URL, headers, and body of the proxied call accept placeholders — mix request parameters with an allowlisted environment variable so the API key never has to be passed by the caller:

sql
sql
comment on type weather_api is '
+GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {WEATHER_API_KEY}
+';

Not to be confused with

NpgsqlRest uses {...} syntax in a few unrelated places. They are different features with different rules:

FeatureLooks likeWhen/whereRules
Parameter value substitution (this page){name} in an annotation valuerequest time, into headers / custom params / HTTP-type callscase-insensitive; unknown → literal (+ build-time warning); NULL → empty
Environment-variable config placeholders{NAME} / {!NAME} in appsettings.jsonstartup, into config valuesresolved from env vars; {!NAME} errors if unset
URL path segments{segment} in a PATH routeroutingmaps a URL path segment to a parameter
Resolved parameter expressionsparam = <sql> (may itself contain {name})request time, server-side in SQLthe parameter is computed by running SQL; its result then substitutes here. {name} inside that SQL is matched case-insensitively (same as this page)
`,37)]))}const m=a(i,[["render",r]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_parameter-substitution.md.BAnY-Etv.lean.js b/assets/annotations_parameter-substitution.md.BAnY-Etv.lean.js new file mode 100644 index 000000000..d0f7fdc4a --- /dev/null +++ b/assets/annotations_parameter-substitution.md.BAnY-Etv.lean.js @@ -0,0 +1 @@ +import{_ as a,c as s,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse(`{"title":"Parameter Value Substitution","titleTemplate":"NpgsqlRest","description":"Use {name} placeholders in NpgsqlRest comment annotations to inject a request's parameter values into response headers, custom parameters, and HTTP custom type calls at request time.","frontmatter":{"outline":[2,3],"title":"Parameter Value Substitution","titleTemplate":"NpgsqlRest","description":"Use {name} placeholders in NpgsqlRest comment annotations to inject a request's parameter values into response headers, custom parameters, and HTTP custom type calls at request time.","head":[["meta",{"name":"keywords","content":"npgsqlrest parameter substitution, annotation placeholder, dynamic response header, dynamic content-type, parameter value placeholder, npgsqlrest {name}"}],["meta",{"property":"og:title","content":"NpgsqlRest Parameter Value Substitution"}],["meta",{"property":"og:description","content":"Inject request parameter values into annotation values with {name} placeholders."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/parameter-substitution.md","filePath":"annotations/parameter-substitution.md"}`),i={name:"annotations/parameter-substitution.md"};function r(l,e,o,p,d,h){return t(),s("div",null,e[0]||(e[0]=[n("",37)]))}const m=a(i,[["render",r]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_path.md.4EcrWuEH.js b/assets/annotations_path.md.4EcrWuEH.js new file mode 100644 index 000000000..9591bdee6 --- /dev/null +++ b/assets/annotations_path.md.4EcrWuEH.js @@ -0,0 +1,53 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"PATH Annotation","titleTemplate":"NpgsqlRest","description":"Set custom URL paths for PostgreSQL REST API endpoints. Override default naming conventions with custom routes.","frontmatter":{"outline":[2,3],"title":"PATH Annotation","titleTemplate":"NpgsqlRest","description":"Set custom URL paths for PostgreSQL REST API endpoints. Override default naming conventions with custom routes.","head":[["meta",{"name":"keywords","content":"npgsqlrest path, custom url path, api routing, endpoint path, custom route postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest PATH Annotation"}],["meta",{"property":"og:description","content":"Set custom URL paths for PostgreSQL REST API endpoints with custom routes."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/path.md","filePath":"annotations/path.md"}'),t={name:"annotations/path.md"};function l(p,s,r,h,d,c){return n(),i("div",null,s[0]||(s[0]=[e(`

PATH

Set a custom endpoint path. Alternative to specifying the path in the HTTP annotation.

Keywords

@path, path

Syntax

code
@path <url-path>

Examples

Custom Path

sql
sql
create function get_user_data()
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user_data() is
+'HTTP GET
+@path /users/data';

Equivalent as a SQL file endpoint (sql/get-user-data.sql):

sql
sql
-- HTTP GET
+-- @path /users/data
+select row_to_json(u) from users u where id = current_user_id();

Creates: GET /users/data

Path with HTTP Method

sql
sql
comment on function my_function() is
+'HTTP GET
+@path /custom/endpoint';

Creates: GET /custom/endpoint

Versioned API

sql
sql
comment on function get_users_v2() is
+'HTTP GET
+@path /api/v2/users';

Path Parameters

Paths can include parameter placeholders using the {param} syntax. Parameter values are extracted directly from the URL path.

Basic Path Parameter

sql
sql
create function get_user(user_id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user(int) is
+'HTTP GET
+@path /users/{user_id}';

Call: GET /users/42user_id = 42

Nested Path Parameters

sql
sql
create function get_user_order(user_id int, order_id int)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_user_order(int, int) is
+'HTTP GET
+@path /users/{user_id}/orders/{order_id}';

Call: GET /users/42/orders/123user_id = 42, order_id = 123

Parameter Name Matching

Parameter names in {param} can use either:

Matching is case-insensitive.

Optional Path Parameters

New in 3.8.0

Optional path parameters were added in version 3.8.0.

Path parameters support the ASP.NET Core optional parameter syntax {param?}. When a path parameter is marked as optional and the corresponding PostgreSQL function parameter has a default value, omitting the URL segment will use the PostgreSQL default:

sql
sql
create function get_item(p_id int default 42)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id?}
+';

This also works with query_string_null_handling null_literal to pass NULL via the literal string "null" in the path for any parameter type:

sql
sql
create function get_item(p_id int default null)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id}
+query_string_null_handling null_literal
+';

Behavior

`,43)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_path.md.4EcrWuEH.lean.js b/assets/annotations_path.md.4EcrWuEH.lean.js new file mode 100644 index 000000000..57a95e3df --- /dev/null +++ b/assets/annotations_path.md.4EcrWuEH.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"PATH Annotation","titleTemplate":"NpgsqlRest","description":"Set custom URL paths for PostgreSQL REST API endpoints. Override default naming conventions with custom routes.","frontmatter":{"outline":[2,3],"title":"PATH Annotation","titleTemplate":"NpgsqlRest","description":"Set custom URL paths for PostgreSQL REST API endpoints. Override default naming conventions with custom routes.","head":[["meta",{"name":"keywords","content":"npgsqlrest path, custom url path, api routing, endpoint path, custom route postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest PATH Annotation"}],["meta",{"property":"og:description","content":"Set custom URL paths for PostgreSQL REST API endpoints with custom routes."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/path.md","filePath":"annotations/path.md"}'),t={name:"annotations/path.md"};function l(p,s,r,h,d,c){return n(),i("div",null,s[0]||(s[0]=[e("",43)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_proxy-out.md.C6XnOMaD.js b/assets/annotations_proxy-out.md.C6XnOMaD.js new file mode 100644 index 000000000..474ab88fa --- /dev/null +++ b/assets/annotations_proxy-out.md.C6XnOMaD.js @@ -0,0 +1,130 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"PROXY_OUT Annotation","titleTemplate":"NpgsqlRest","description":"Execute PostgreSQL function first, then forward the result to an upstream service. Build post-processing pipelines with PDF rendering, ML inference, and more.","frontmatter":{"outline":[2,3],"title":"PROXY_OUT Annotation","titleTemplate":"NpgsqlRest","description":"Execute PostgreSQL function first, then forward the result to an upstream service. Build post-processing pipelines with PDF rendering, ML inference, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy_out, forward proxy postgresql, post-execution proxy, upstream service, pdf rendering, ml inference"}],["meta",{"property":"og:title","content":"NpgsqlRest PROXY_OUT Annotation"}],["meta",{"property":"og:description","content":"Execute PostgreSQL function first, then forward the result to an upstream service."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/proxy-out.md","filePath":"annotations/proxy-out.md"}'),t={name:"annotations/proxy-out.md"};function l(p,s,r,h,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

PROXY_OUT

Also known as

forward_proxy (with or without @ prefix)

Available since version 3.11.0

Execute the PostgreSQL function first, then forward its result body to an upstream service. The upstream response is returned to the client.

Syntax

code
@proxy_out
+@proxy_out [ host_url ]
+@proxy_out [ http_method ]
+@proxy_out [ http_method ] [ host_url ]

Description

The proxy_out annotation reverses the flow of the existing proxy annotation. Instead of forwarding the incoming request to upstream, proxy_out forwards the outgoing function result.

This enables a common pattern where business logic in PostgreSQL prepares a payload, and an external service performs processing that PostgreSQL cannot do — PDF rendering, image processing, ML inference, email sending, etc.

code
Client Request → NpgsqlRest
+  → Execute PostgreSQL function
+  → Forward function result as request body to upstream service
+  → Append the original request path and query string to the upstream host
+  → Return upstream response to client

The client-facing HTTP method and the upstream HTTP method are independent — the client can send a GET while the upstream receives a POST.

The upstream URL is built the same way as for proxy — the incoming request path and query string are appended to the host:

code
target URL = host + incoming request path + incoming query string

The difference from proxy is only the direction of the body: proxy_out sends the function's result as the request body to the upstream, whereas proxy sends the incoming request body.

Basic Usage

sql
sql
create function generate_report(report_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object(
+        'title', 'Monthly Report',
+        'data', (select json_agg(row_to_json(t)) from sales t where month = report_id)
+    );
+end;
+$$;
+
+comment on function generate_report(int) is 'HTTP GET
+@proxy_out POST https://render-service.internal/render';

Equivalent as a SQL file endpoint (sql/generate-report.sql):

sql
sql
/*
+HTTP GET
+@proxy_out POST https://render-service.internal/render
+@param $1 report_id
+*/
+select json_build_object(
+    'title', 'Monthly Report',
+    'data', (select json_agg(row_to_json(t)) from sales t where month = $1)
+);

The client calls GET /api/generate-report/?reportId=3. The server:

  1. Executes generate_report(3) in PostgreSQL.
  2. Takes the returned JSON and POSTs it to https://render-service.internal/render/api/generate-report/?reportId=3 (original query string forwarded).
  3. Returns the upstream response (e.g., a rendered PDF) directly to the client with the upstream's content-type and status code.

Proxy Annotations

Basic Proxy Out with Default Host

Uses the host from ProxyOptions.Host configuration:

sql
sql
-- function form
+comment on function my_func() is '@proxy_out';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy_out
+select my_func();

Proxy Out with Custom Host

Override the default host:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST https://my-other-service.internal';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST https://my-other-service.internal
+*/
+select my_func();

Proxy Out with HTTP Method Override

Specify which HTTP method to use for the upstream request (uses default host from configuration):

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out PUT';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out PUT
+*/
+select my_func();

The client sends GET, but the upstream receives PUT with the function's result as the body.

Combined Method and Host

Specify both HTTP method and host:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST https://render-service.internal/render';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST https://render-service.internal/render
+*/
+select my_func();

Self-Referencing Proxy Out (Relative Path)

Use a relative path starting with / to forward the function result to another endpoint on the same server:

sql
sql
-- function form
+comment on function my_func() is 'HTTP GET
+@proxy_out POST /api/internal-processor';
sql
sql
-- sql/my-func.sql (SQL file form)
+/*
+HTTP GET
+@proxy_out POST /api/internal-processor
+*/
+select my_func();

Self-referencing calls bypass the HTTP stack entirely — the target endpoint handler is invoked directly in-process with zero network overhead.

URL Resolution

The target URL follows the same resolution rules as proxy:

Path and Query String Forwarding

The original client request path and query string are both appended to the upstream host as-is (host + path + query). This lets the upstream receive the same path and parameters that were used to invoke the function:

sql
sql
create function generate_report(p_format text, p_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object('id', p_id, 'data', 'report');
+end;
+$$;
+
+comment on function generate_report(text, int) is 'HTTP GET
+@proxy_out POST';

With ProxyOptions.Host = "https://api.example.com", calling GET /api/generate-report/?pFormat=pdf&pId=123 executes the function, then POSTs the result body to https://api.example.com/api/generate-report/?pFormat=pdf&pId=123 — both the path and query string are appended.

To send the result to a fixed upstream path instead, put it in the annotation host (e.g. @proxy_out POST https://api.example.com/render, which forwards to https://api.example.com/render/api/generate-report/?...), or change the endpoint path with HTTP <method> <path>.

Self-calls are the exception

For a relative self-call (host starting with /, e.g. @proxy_out POST /api/processor), the annotation path is the full target and the incoming request path is not appended.

Error Handling

Examples

PDF Rendering Pipeline

Prepare data in PostgreSQL and render it as a PDF via an external service:

sql
sql
create function invoice_pdf(invoice_id int)
+returns json
+language plpgsql as $$
+begin
+    return json_build_object(
+        'invoice_number', invoice_id,
+        'items', (select json_agg(row_to_json(i)) from invoice_items i where i.invoice_id = invoice_pdf.invoice_id),
+        'total', (select sum(amount) from invoice_items where invoice_items.invoice_id = invoice_pdf.invoice_id)
+    );
+end;
+$$;
+
+comment on function invoice_pdf(int) is 'HTTP GET
+@proxy_out POST https://pdf-service.internal/render';

ML Inference

Send prepared feature data to an ML service:

sql
sql
create function predict_churn(customer_id int)
+returns json
+language plpgsql as $$
+begin
+    return (
+        select json_build_object(
+            'features', json_build_object(
+                'total_orders', count(*),
+                'last_order_days', extract(day from now() - max(order_date)),
+                'avg_order_value', avg(total)
+            )
+        )
+        from orders
+        where orders.customer_id = predict_churn.customer_id
+    );
+end;
+$$;
+
+comment on function predict_churn(int) is 'HTTP GET
+@proxy_out POST https://ml-service.internal/predict/churn';

Email Sending

Prepare email content in PostgreSQL and send via an email service:

sql
sql
create function send_welcome_email(user_id int)
+returns json
+language plpgsql as $$
+declare
+    u record;
+begin
+    select * into u from users where id = user_id;
+    return json_build_object(
+        'to', u.email,
+        'subject', 'Welcome to Our Platform',
+        'body', format('Hello %s, welcome!', u.display_name)
+    );
+end;
+$$;
+
+comment on function send_welcome_email(int) is 'HTTP POST
+@proxy_out POST https://email-service.internal/send';

TypeScript Client

The TypeScript client generator (NpgsqlRest.TsClient) recognizes proxy_out endpoints and generates functions that return the raw Response object. Since the actual response comes from the upstream proxy service (not from the PostgreSQL function's return type), the generated function returns Promise<Response>:

typescript
typescript
// Generated for a proxy_out endpoint
+export async function generateReport() : Promise<Response> {
+    const response = await fetch(baseUrl + "/api/generate-report", {
+        method: "GET",
+    });
+    return response;
+}

This allows the caller to handle the response appropriately (.json(), .blob(), .text(), etc.).

Configuration

Uses the same ProxyOptions configuration as the existing proxy annotation. ProxyOptions.Enabled must be true:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "DefaultTimeout": "30 seconds"
+    }
+  }
+}

See Proxy Options for complete configuration reference.

See Also

`,76)]))}const u=i(t,[["render",l]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_proxy-out.md.C6XnOMaD.lean.js b/assets/annotations_proxy-out.md.C6XnOMaD.lean.js new file mode 100644 index 000000000..fa5a1d748 --- /dev/null +++ b/assets/annotations_proxy-out.md.C6XnOMaD.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"PROXY_OUT Annotation","titleTemplate":"NpgsqlRest","description":"Execute PostgreSQL function first, then forward the result to an upstream service. Build post-processing pipelines with PDF rendering, ML inference, and more.","frontmatter":{"outline":[2,3],"title":"PROXY_OUT Annotation","titleTemplate":"NpgsqlRest","description":"Execute PostgreSQL function first, then forward the result to an upstream service. Build post-processing pipelines with PDF rendering, ML inference, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy_out, forward proxy postgresql, post-execution proxy, upstream service, pdf rendering, ml inference"}],["meta",{"property":"og:title","content":"NpgsqlRest PROXY_OUT Annotation"}],["meta",{"property":"og:description","content":"Execute PostgreSQL function first, then forward the result to an upstream service."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/proxy-out.md","filePath":"annotations/proxy-out.md"}'),t={name:"annotations/proxy-out.md"};function l(p,s,r,h,k,o){return n(),a("div",null,s[0]||(s[0]=[e("",76)]))}const u=i(t,[["render",l]]);export{d as __pageData,u as default}; diff --git a/assets/annotations_proxy.md.C1XKqZ1u.js b/assets/annotations_proxy.md.C1XKqZ1u.js new file mode 100644 index 000000000..2a5778d1b --- /dev/null +++ b/assets/annotations_proxy.md.C1XKqZ1u.js @@ -0,0 +1,174 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PROXY Annotation","titleTemplate":"NpgsqlRest","description":"Create reverse proxy endpoints that forward requests to upstream services. Transform responses with PostgreSQL functions.","frontmatter":{"outline":[2,3],"title":"PROXY Annotation","titleTemplate":"NpgsqlRest","description":"Create reverse proxy endpoints that forward requests to upstream services. Transform responses with PostgreSQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, reverse proxy postgresql, upstream service, api gateway, proxy annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest PROXY Annotation"}],["meta",{"property":"og:description","content":"Create reverse proxy endpoints that forward requests to upstream services."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/proxy.md","filePath":"annotations/proxy.md"}'),t={name:"annotations/proxy.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e(`

PROXY

Also known as

reverse_proxy (with or without @ prefix)

Mark endpoint as a reverse proxy that forwards requests to an upstream service.

Syntax

code
@proxy
+@proxy [ host_url ]
+@proxy [ http_method ]
+@proxy [ http_method ] [ host_url ]

Description

The proxy annotation marks an endpoint as a reverse proxy. When a request arrives, NpgsqlRest forwards it to an upstream service and either returns the response directly (passthrough mode) or passes it to your PostgreSQL function for processing (transform mode).

How the target URL is built

This is the most important thing to understand about @proxy. The function still becomes a normal NpgsqlRest endpoint with its usual URL (auto-generated from the function name, or whatever you set with HTTP <method> <path>). When a request hits that endpoint, NpgsqlRest builds the upstream URL by appending the incoming request path and query string to the host:

code
target URL = host + incoming request path + incoming query string

The host is the value from the annotation (@proxy https://...) if present, otherwise the global ProxyOptions.Host. The path is not the function name directly — it is the actual path the client used to reach the endpoint (which, by default, is derived from the function name).

Walkthrough: what does the basic example call?

sql
sql
create function get_external_data()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function get_external_data() is 'HTTP GET
+@proxy';

With the default configuration below:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com"
+    }
+  }
+}
  1. The function is exposed at its default endpoint: GET /api/get-external-data/.

  2. A client calls GET /api/get-external-data/?id=42 on your NpgsqlRest server.

  3. NpgsqlRest forwards it to the host with the same path and query appended:

    GET https://api.example.com/api/get-external-data/?id=42

  4. The upstream response is streamed straight back to the client (passthrough — no database connection is opened).

So @proxy alone is a mirror: it forwards each request to the same path on a different host. To forward to a different path, either change the endpoint path (HTTP GET /v1/data, which then forwards to https://api.example.com/v1/data) or use an absolute/relative URL in the annotation (see URL Resolution below).

Host is required

If neither the annotation nor ProxyOptions.Host provides a host, the endpoint responds with 500 and "Proxy host is not configured." The bare @proxy form only works when ProxyOptions.Host is set.

Automatic parameters are forwarded too

On top of the verbatim path and query, any server-filled parameters — user claims, IP address, HTTP Custom Type fields, and resolved-parameter expressions — are forwarded to the upstream in the endpoint's native shape (query string or JSON body, per RequestParamType). See Automatic Parameter Forwarding.

Basic Usage

Passthrough Mode

For simple proxy forwarding without database processing:

sql
sql
create function get_external_data()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function get_external_data() is 'HTTP GET
+@proxy';

Equivalent as a SQL file endpoint (sql/get-external-data.sql):

sql
sql
-- HTTP GET
+-- @proxy
+select;

When the function has no proxy response parameters, the upstream response is returned directly to the client without opening a database connection. The function body itself (select;) is never executed — it exists only to declare the endpoint and its annotations.

Transform Mode

To process the upstream response in PostgreSQL, add one or more proxy response parameters to the function. Their presence is what switches the endpoint from passthrough into transform mode:

sql
sql
create function get_and_transform(
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_headers json default null,
+    _proxy_content_type text default null,
+    _proxy_success boolean default null,
+    _proxy_error_message text default null
+)
+returns json
+language plpgsql as $$
+begin
+    if not _proxy_success then
+        return json_build_object('error', _proxy_error_message);
+    end if;
+    return json_build_object(
+        'status', _proxy_status_code,
+        'data', _proxy_body::json
+    );
+end;
+$$;
+
+comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
+@proxy';

In transform mode the order of operations is: forward the request to the upstream → collect the response → execute the PostgreSQL function with the response values bound to its proxy parameters → return the function's result to the client. The function output (not the raw upstream response) is what the client receives.

Proxy Annotations

Basic Proxy with Default Host

Uses the host from ProxyOptions.Host configuration:

sql
sql
-- function form
+comment on function my_func() is '@proxy';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy
+select;

Proxy with Custom Host

Override the default host:

sql
sql
-- function form
+comment on function my_func() is '@proxy https://api.example.com';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy https://api.example.com
+select;

Proxy with Custom HTTP Method

Override the upstream HTTP method (uses default host from configuration):

sql
sql
-- function form
+comment on function my_func() is '@proxy POST';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST
+select;

Combined Method and Host

Specify both HTTP method and host:

sql
sql
-- function form
+comment on function my_func() is '@proxy POST https://api.example.com';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST https://api.example.com
+select;

Self-Referencing Proxy (Relative Path)

Use a relative path starting with / to proxy to another endpoint on the same server:

sql
sql
-- function form
+comment on function my_func() is '@proxy POST /api/data-source';
sql
sql
-- sql/my-func.sql (SQL file form)
+-- @proxy POST /api/data-source
+select;

Self-referencing calls bypass the HTTP stack entirely — the target endpoint handler is invoked directly in-process with zero network overhead.

URL Resolution

The proxy target host is resolved with the following priority:

  1. Annotation URL — if the annotation includes a URL (absolute or relative), it is used. The global ProxyOptions.Host is ignored.
  2. Global ProxyOptions.Host — used only when the annotation has no URL (e.g., @proxy or @proxy POST).

In every case except a relative self-call, the incoming request path and query string are then appended to the resolved host (host + request path + query). For relative self-calls (host starting with /), the annotation path is the full target and the incoming path is not appended.

AnnotationProxyOptions.HostResolved TargetSelf-Call?
@proxyhttps://api.example.comhttps://api.example.com + request pathNo
@proxy POSThttps://api.example.comhttps://api.example.com + request pathNo
@proxy https://other.comhttps://api.example.comhttps://other.com + request pathNo
@proxy POST /api/datahttps://api.example.com/api/data (internal)Yes
@proxy /api/datahttps://api.example.com/api/data (internal)Yes
@proxy /api/datanull/api/data (internal)Yes

Important

A relative path in the annotation (starting with /) always creates a self-referencing internal call, regardless of the ProxyOptions.Host setting. The global host is never prepended to relative paths.

Response Parameters

When the PostgreSQL function has parameters whose names match the configured proxy parameter names, the upstream response data is bound to them after the request returns:

Parameter NameTypeDescription
_proxy_status_codeint or textHTTP status code from upstream (e.g., 200, 404). Bound as text if the parameter is declared text/varchar, otherwise as an integer.
_proxy_bodytextResponse body content. null if empty.
_proxy_headersjsonResponse headers as a JSON object.
_proxy_content_typetextContent-Type header value.
_proxy_successbooleantrue for 2xx status codes.
_proxy_error_messagetextError message if the request failed (timeout, connection error, etc.); null otherwise.

How parameters are mapped

Custom parameter names

For example, to drop the _proxy_ prefix, configure the names you want:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "ResponseStatusCodeParameter": "status",
+      "ResponseBodyParameter": "body",
+      "ResponseSuccessParameter": "ok"
+    }
+  }
+}

The function then uses those names instead of the defaults:

sql
sql
create function get_and_transform(
+    status int default null,
+    body text default null,
+    ok boolean default null
+)
+returns json
+language plpgsql as $$
+begin
+    if not ok then
+        return json_build_object('error', 'upstream failed');
+    end if;
+    return json_build_object('status', status, 'data', body::json);
+end;
+$$;
+
+comment on function get_and_transform(int, text, boolean) is 'HTTP GET
+@proxy';

Examples

API Gateway Pattern

Forward requests to different microservices:

sql
sql
-- Users service
+create function users_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function users_api() is 'HTTP GET /api/users
+@proxy https://users-service.internal:8080';
+
+-- Orders service
+create function orders_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function orders_api() is 'HTTP GET /api/orders
+@proxy https://orders-service.internal:8080';

Data Enrichment

Fetch external data and enrich it with local data:

sql
sql
create function get_enriched_weather(
+    city text,
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null
+)
+returns json
+language plpgsql as $$
+declare
+    local_data json;
+begin
+    -- Get local city preferences
+    select json_build_object('favorite', is_favorite, 'notes', notes)
+    into local_data
+    from user_city_preferences
+    where city_name = city;
+
+    if not _proxy_success then
+        return json_build_object('error', 'Weather API unavailable');
+    end if;
+
+    return json_build_object(
+        'weather', _proxy_body::json,
+        'local', coalesce(local_data, '{}'::json)
+    );
+end;
+$$;
+
+comment on function get_enriched_weather(text, int, text, boolean) is 'HTTP GET /v1/current
+@proxy https://api.weather.com';

A client request to GET /v1/current?city=London is forwarded to https://api.weather.com/v1/current?city=London — the endpoint path and the incoming query string are appended to the host. The city value also populates the city parameter so it is available to the function for the local lookup.

No URL templating

The annotation host is used literally — there is no {city}-style substitution. Dynamic values reach the upstream only through the forwarded request path and query string (and, optionally, user_parameters). Do not put placeholders like ?city={city} in the host; they are forwarded verbatim.

Authenticated Proxy with User Context

Use NpgsqlRest as an authenticating gateway: it verifies the caller, then forwards the request to a protected upstream service along with the caller's identity as HTTP headers. This is a passthrough proxy — the function does no work, so it needs no body and no proxy parameters:

sql
sql
create function secure_api_call()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function secure_api_call() is 'HTTP GET
+@authorize
+@user_context
+@proxy https://secure-api.internal/data';

On a proxy endpoint, @user_context adds the caller's identity to the upstream request as HTTP headers: the claims JSON, the client IP, and one header per entry in ContextKeyClaimsMapping (e.g. request.user_id, request.user_name, request.user_roles). The upstream can trust these headers because the request was authenticated by the gateway, so it never re-authenticates.

In this passthrough example the function never runs, so header forwarding is the only effect. In transform mode the function does run, and there @user_context additionally sets the usual PostgreSQL session context for it — so the function can read the caller's identity while the upstream still receives the headers.

Proxy with User Parameters

Forward user claims to the upstream as query string parameters:

sql
sql
create function proxy_with_user(
+    _user_id text default null,    -- filled from the caller's user-id claim by @user_params,
+                                   -- then forwarded to the upstream as ?userId=...
+    _proxy_body text default null
+)
+returns json language plpgsql as $$
+begin
+    -- _user_id is sent to the upstream automatically; the function reads only the response here.
+    return _proxy_body::json;
+end;
+$$;
+
+comment on function proxy_with_user(text, text) is 'HTTP GET
+@authorize
+@user_params
+@proxy https://api.internal/user-data';

With @user_params, _user_id is populated from the authenticated user's claim (not from the request) and appended to the upstream URL using the camelCase form of the parameter name. A call to GET /api/proxy-with-user/ is forwarded to:

GET https://api.internal/user-data/api/proxy-with-user/?userId=<claim value>

The function may also read _user_id directly if it needs the value — but it doesn't have to for the value to reach the upstream.

Configuration

Enable proxy functionality in your configuration:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "https://api.example.com",
+      "DefaultTimeout": "30 seconds"
+    }
+  }
+}

See Proxy Options for complete configuration reference.

See Also

`,91)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_proxy.md.C1XKqZ1u.lean.js b/assets/annotations_proxy.md.C1XKqZ1u.lean.js new file mode 100644 index 000000000..08b18fa8f --- /dev/null +++ b/assets/annotations_proxy.md.C1XKqZ1u.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PROXY Annotation","titleTemplate":"NpgsqlRest","description":"Create reverse proxy endpoints that forward requests to upstream services. Transform responses with PostgreSQL functions.","frontmatter":{"outline":[2,3],"title":"PROXY Annotation","titleTemplate":"NpgsqlRest","description":"Create reverse proxy endpoints that forward requests to upstream services. Transform responses with PostgreSQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, reverse proxy postgresql, upstream service, api gateway, proxy annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest PROXY Annotation"}],["meta",{"property":"og:description","content":"Create reverse proxy endpoints that forward requests to upstream services."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/proxy.md","filePath":"annotations/proxy.md"}'),t={name:"annotations/proxy.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e("",91)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_query-string-null-handling.md.bDnQmkU1.js b/assets/annotations_query-string-null-handling.md.bDnQmkU1.js new file mode 100644 index 000000000..48e20c20a --- /dev/null +++ b/assets/annotations_query-string-null-handling.md.bDnQmkU1.js @@ -0,0 +1,55 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"QUERY_STRING_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control NULL value interpretation in query string parameters. Configure empty string vs NULL handling for PostgreSQL APIs.","frontmatter":{"outline":[2,3],"title":"QUERY_STRING_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control NULL value interpretation in query string parameters. Configure empty string vs NULL handling for PostgreSQL APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest null handling, query string null, empty string null, parameter null handling, api null values"}],["meta",{"property":"og:title","content":"NpgsqlRest QUERY_STRING_NULL_HANDLING Annotation"}],["meta",{"property":"og:description","content":"Control how NULL values are interpreted in query string parameters."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/query-string-null-handling.md","filePath":"annotations/query-string-null-handling.md"}'),l={name:"annotations/query-string-null-handling.md"};function t(p,s,r,d,h,c){return n(),i("div",null,s[0]||(s[0]=[e(`

QUERY_STRING_NULL_HANDLING

Also known as

query_null_handling, query_string_null, query_null (with or without @ prefix)

Controls how clients can pass NULL values to PostgreSQL function parameters via query string.

Since query strings can only contain text values, there's no native way to represent SQL NULL. This annotation defines what query string value should be interpreted as NULL.

Syntax

code
@query_null <mode>
+@query_string_null_handling <mode>

Values

ValueAliasResult
emptyempty_stringEmpty query string value (?param=) is interpreted as NULL
nullnull_literalLiteral string "null" (?param=null) is interpreted as NULL
ignoreNo special NULL handling - values are passed as-is (default)

Behavior Explained

Ignore Mode (Default)

By default (ignore), no special NULL handling is applied. Query string values are passed as-is to the function:

code
GET /api/func/?t=         →   _t = '' (empty string)
+GET /api/func/?t=null     →   _t = 'null' (literal string "null")
+GET /api/func/?t=hello    →   _t = 'hello'

If the parameter is not provided at all, the function receives NULL:

code
GET /api/func/            →   _t = NULL (parameter not provided)

EmptyString Mode

When set to empty_string (or empty), an empty query string value is interpreted as SQL NULL:

code
GET /api/func/?t=         →   _t = NULL
+GET /api/func/?t=null     →   _t = 'null' (literal string)
+GET /api/func/?t=hello    →   _t = 'hello'

This allows clients to explicitly pass NULL by providing an empty value.

NullLiteral Mode

When set to null_literal (or null), the literal string "null" (case-insensitive) is interpreted as SQL NULL:

code
GET /api/func/?t=null     →   _t = NULL
+GET /api/func/?t=NULL     →   _t = NULL
+GET /api/func/?t=         →   _t = '' (empty string)
+GET /api/func/?t=hello    →   _t = 'hello'

This allows clients to explicitly pass NULL by providing the string "null".

Examples

Using Empty String for NULL

sql
sql
create function get_nullable_param(_t text)
+returns text
+language sql
+begin atomic;
+select _t;
+end;
+
+comment on function get_nullable_param(text) is '
+@query_string_null_handling empty_string
+';

Equivalent as a SQL file endpoint (sql/get-nullable-param.sql):

sql
sql
/*
+HTTP GET
+@query_string_null_handling empty_string
+@param $1 t
+*/
+select $1;
RequestParameter Value
GET /api/get-nullable-param/?t=_t = NULL
GET /api/get-nullable-param/?t=hello_t = 'hello'

Using "null" String for NULL

sql
sql
create function get_data(_filter text)
+returns text
+language sql
+begin atomic;
+select _filter;
+end;
+
+comment on function get_data(text) is '
+@query_string_null_handling null_literal
+';
RequestParameter Value
GET /api/get-data/?filter=null_filter = NULL
GET /api/get-data/?filter=_filter = '' (empty string)
GET /api/get-data/?filter=active_filter = 'active'

Default Behavior (No Special Handling)

sql
sql
create function search(_query text)
+returns text
+language sql
+begin atomic;
+select _query;
+end;
+
+comment on function search(text) is '
+@query_string_null_handling ignore
+';
RequestParameter Value
GET /api/search/?query=_query = '' (empty string)
GET /api/search/?query=null_query = 'null' (literal string)
GET /api/search/_query = NULL (parameter omitted)

Path Parameters

New in 3.8.0

Path parameter interaction with null_literal mode was added in version 3.8.0.

The null_literal mode also works with path parameters. When combined with optional path parameters, you can pass NULL via the literal string "null" in the URL path:

sql
sql
create function get_item(p_id int default null)
+returns text
+language sql
+begin atomic;
+select p_id::text;
+end;
+
+comment on function get_item(int) is '
+HTTP GET /items/{p_id}
+query_string_null_handling null_literal
+';

Configuration Default

You can set the default behavior for all endpoints in appsettings.json:

json
json
{
+  "NpgsqlRest": {
+    "QueryStringNullHandling": "EmptyString"
+  }
+}

Available values: Ignore (default), EmptyString, NullLiteral.

This sets the default for all endpoints, which can then be overridden per-endpoint using comment annotations.

`,48)]))}const k=a(l,[["render",t]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_query-string-null-handling.md.bDnQmkU1.lean.js b/assets/annotations_query-string-null-handling.md.bDnQmkU1.lean.js new file mode 100644 index 000000000..2c1344c3a --- /dev/null +++ b/assets/annotations_query-string-null-handling.md.bDnQmkU1.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"QUERY_STRING_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control NULL value interpretation in query string parameters. Configure empty string vs NULL handling for PostgreSQL APIs.","frontmatter":{"outline":[2,3],"title":"QUERY_STRING_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control NULL value interpretation in query string parameters. Configure empty string vs NULL handling for PostgreSQL APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest null handling, query string null, empty string null, parameter null handling, api null values"}],["meta",{"property":"og:title","content":"NpgsqlRest QUERY_STRING_NULL_HANDLING Annotation"}],["meta",{"property":"og:description","content":"Control how NULL values are interpreted in query string parameters."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/query-string-null-handling.md","filePath":"annotations/query-string-null-handling.md"}'),l={name:"annotations/query-string-null-handling.md"};function t(p,s,r,d,h,c){return n(),i("div",null,s[0]||(s[0]=[e("",48)]))}const k=a(l,[["render",t]]);export{u as __pageData,k as default}; diff --git a/assets/annotations_rate-limiter-policy.md.C6rNxcxq.js b/assets/annotations_rate-limiter-policy.md.C6rNxcxq.js new file mode 100644 index 000000000..127b6f756 --- /dev/null +++ b/assets/annotations_rate-limiter-policy.md.C6rNxcxq.js @@ -0,0 +1,68 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RATE_LIMITER_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Apply rate limiting policies to PostgreSQL REST API endpoints. Control request rates per endpoint with configured policies.","frontmatter":{"outline":[2,3],"title":"RATE_LIMITER_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Apply rate limiting policies to PostgreSQL REST API endpoints. Control request rates per endpoint with configured policies.","head":[["meta",{"name":"keywords","content":"npgsqlrest rate limiter, api throttling, request rate limit, endpoint throttle, rate limit policy"}],["meta",{"property":"og:title","content":"NpgsqlRest RATE_LIMITER_POLICY Annotation"}],["meta",{"property":"og:description","content":"Apply rate limiting policies to control request rates per endpoint."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/rate-limiter-policy.md","filePath":"annotations/rate-limiter-policy.md"}'),l={name:"annotations/rate-limiter-policy.md"};function t(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e(`

RATE_LIMITER_POLICY

Also known as

rate_limiter_policy, rate_limiter (with or without @ prefix)

Apply a rate limiting policy to the endpoint. The policy name must match a policy configured in the Rate Limiter configuration.

Syntax

code
@rate_limiter_policy <policy-name>
+@rate_limiter <policy-name>

Examples

Fixed Window Policy

Apply a fixed window rate limiter to an API endpoint:

sql
sql
comment on function public_api() is
+'HTTP GET
+@rate_limiter_policy fixed';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "fixed": {
+        "Type": "FixedWindow",
+        "Enabled": true,
+        "PermitLimit": 100,
+        "WindowSeconds": 60
+      }
+    }
+  }
+}

Token Bucket Policy

Apply a token bucket rate limiter to an expensive operation:

sql
sql
comment on function expensive_operation() is
+'HTTP POST
+@rate_limiter bucket';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "bucket": {
+        "Type": "TokenBucket",
+        "Enabled": true,
+        "TokenLimit": 10,
+        "ReplenishmentPeriodSeconds": 60
+      }
+    }
+  }
+}

Combined with Authorization

Apply rate limiting to an authenticated endpoint:

sql
sql
comment on function protected_resource() is
+'HTTP GET
+@authorize
+@rate_limiter authenticated_limit';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "authenticated_limit": {
+        "Type": "SlidingWindow",
+        "Enabled": true,
+        "PermitLimit": 1000,
+        "WindowSeconds": 60,
+        "SegmentsPerWindow": 6
+      }
+    }
+  }
+}

Per-User Rate Limiting

Apply per-user rate limiting using a partitioned policy:

sql
sql
comment on function user_dashboard() is
+'HTTP GET
+@authorize
+@rate_limiter per_user';

With configuration:

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "Policies": {
+      "per_user": {
+        "Type": "FixedWindow",
+        "Enabled": true,
+        "PermitLimit": 100,
+        "WindowSeconds": 60,
+        "Partition": {
+          "Sources": [
+            { "Type": "Claim", "Name": "name_identifier" },
+            { "Type": "IpAddress" },
+            { "Type": "Static", "Value": "anonymous" }
+          ]
+        }
+      }
+    }
+  }
+}

Each authenticated user gets their own quota instead of all users sharing one global bucket.

Behavior

See Also

`,35)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_rate-limiter-policy.md.C6rNxcxq.lean.js b/assets/annotations_rate-limiter-policy.md.C6rNxcxq.lean.js new file mode 100644 index 000000000..1e135f0b1 --- /dev/null +++ b/assets/annotations_rate-limiter-policy.md.C6rNxcxq.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RATE_LIMITER_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Apply rate limiting policies to PostgreSQL REST API endpoints. Control request rates per endpoint with configured policies.","frontmatter":{"outline":[2,3],"title":"RATE_LIMITER_POLICY Annotation","titleTemplate":"NpgsqlRest","description":"Apply rate limiting policies to PostgreSQL REST API endpoints. Control request rates per endpoint with configured policies.","head":[["meta",{"name":"keywords","content":"npgsqlrest rate limiter, api throttling, request rate limit, endpoint throttle, rate limit policy"}],["meta",{"property":"og:title","content":"NpgsqlRest RATE_LIMITER_POLICY Annotation"}],["meta",{"property":"og:description","content":"Apply rate limiting policies to control request rates per endpoint."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/rate-limiter-policy.md","filePath":"annotations/rate-limiter-policy.md"}'),l={name:"annotations/rate-limiter-policy.md"};function t(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e("",35)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_raw.md.mVR52W_-.js b/assets/annotations_raw.md.mVR52W_-.js new file mode 100644 index 000000000..1812a6aed --- /dev/null +++ b/assets/annotations_raw.md.mVR52W_-.js @@ -0,0 +1,88 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"RAW Annotation","titleTemplate":"NpgsqlRest","description":"Return raw text output instead of JSON from PostgreSQL REST API endpoints. Output CSV, plain text, or custom formats.","frontmatter":{"outline":[2,3],"title":"RAW Annotation","titleTemplate":"NpgsqlRest","description":"Return raw text output instead of JSON from PostgreSQL REST API endpoints. Output CSV, plain text, or custom formats.","head":[["meta",{"name":"keywords","content":"npgsqlrest raw output, text response api, csv output postgresql, non-json response, plain text api"}],["meta",{"property":"og:title","content":"NpgsqlRest RAW Annotation"}],["meta",{"property":"og:description","content":"Return raw text output instead of JSON formatting from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/raw.md","filePath":"annotations/raw.md"}'),l={name:"annotations/raw.md"};function p(t,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

RAW

Also known as

raw_mode, raw_results (with or without @ prefix)

Return raw text output instead of JSON formatting.

Syntax

code
@raw

Examples

Basic Raw Output

sql
sql
create function get_plain_text()
+returns text
+language sql
+begin atomic;
+select 'Hello, World!';
+end;
+
+comment on function get_plain_text() is
+'HTTP GET
+@raw';

Equivalent as a SQL file endpoint (sql/get-plain-text.sql):

sql
sql
-- HTTP GET
+-- @raw
+select 'Hello, World!';

Response: Hello, World! (plain text, no JSON wrapping)

Raw with Multiple Columns

sql
sql
create function get_user_info()
+returns table(name text, email text)
+language sql
+begin atomic;
+select name, email from users limit 1;
+end;
+
+comment on function get_user_info() is
+'HTTP GET
+@raw';

Response: JohnDoe john@example.com (values concatenated)

CSV Export

sql
sql
create function export_users_csv()
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_users_csv() is
+'HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: text/csv';

Response:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

Tab-Separated Values

sql
sql
create function export_tsv()
+returns table(col1 text, col2 text, col3 text)
+language sql
+begin atomic;
+select * from my_table;
+end;
+
+comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \\t
+@new_line \\n
+Content-Type: text/tab-separated-values';

Pipe-Delimited Format

sql
sql
create function export_data()
+returns table(a text, b text, c text)
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_data() is
+'HTTP GET
+@raw
+@separator |
+@new_line \\n';

Response:

code
value1|value2|value3
+value4|value5|value6

Download as File

sql
sql
create function download_report()
+returns table(data text)
+language sql
+begin atomic;
+...;
+end;
+
+comment on function download_report() is
+'HTTP GET
+@raw
+Content-Type: text/csv
+Content-Disposition: attachment; filename="report.csv"';

Browser will download the response as a file.

Dynamic CSV Download

Use {param_name} template syntax in headers for dynamic content type and filename:

sql
sql
create function export_data(_type text, _file text)
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users;
+end;
+
+comment on function export_data(text, text) is
+'HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}';

Request: GET /api/export-data?_type=text/csv&_file=users.csv

Response headers:

code
Content-Type: text/csv
+Content-Disposition: attachment; filename=users.csv

Response body:

code
id,name,email
+1,John Doe,john@example.com
+2,Jane Smith,jane@example.com

Behavior

`,41)]))}const u=a(l,[["render",p]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_raw.md.mVR52W_-.lean.js b/assets/annotations_raw.md.mVR52W_-.lean.js new file mode 100644 index 000000000..dd7b08954 --- /dev/null +++ b/assets/annotations_raw.md.mVR52W_-.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"RAW Annotation","titleTemplate":"NpgsqlRest","description":"Return raw text output instead of JSON from PostgreSQL REST API endpoints. Output CSV, plain text, or custom formats.","frontmatter":{"outline":[2,3],"title":"RAW Annotation","titleTemplate":"NpgsqlRest","description":"Return raw text output instead of JSON from PostgreSQL REST API endpoints. Output CSV, plain text, or custom formats.","head":[["meta",{"name":"keywords","content":"npgsqlrest raw output, text response api, csv output postgresql, non-json response, plain text api"}],["meta",{"property":"og:title","content":"NpgsqlRest RAW Annotation"}],["meta",{"property":"og:description","content":"Return raw text output instead of JSON formatting from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/raw.md","filePath":"annotations/raw.md"}'),l={name:"annotations/raw.md"};function p(t,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",41)]))}const u=a(l,[["render",p]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_request-headers-mode.md.CiBM2WBe.js b/assets/annotations_request-headers-mode.md.CiBM2WBe.js new file mode 100644 index 000000000..1ce580a3e --- /dev/null +++ b/assets/annotations_request-headers-mode.md.CiBM2WBe.js @@ -0,0 +1,21 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"REQUEST_HEADERS_MODE Annotation","titleTemplate":"NpgsqlRest","description":"Control how HTTP request headers are passed to PostgreSQL functions. Access headers via context or parameters.","frontmatter":{"outline":[2,3],"title":"REQUEST_HEADERS_MODE Annotation","titleTemplate":"NpgsqlRest","description":"Control how HTTP request headers are passed to PostgreSQL functions. Access headers via context or parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest request headers, http headers postgresql, access request headers, headers to function, header mode"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_HEADERS_MODE Annotation"}],["meta",{"property":"og:description","content":"Control how HTTP request headers are passed to PostgreSQL functions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-headers-mode.md","filePath":"annotations/request-headers-mode.md"}'),t={name:"annotations/request-headers-mode.md"};function l(r,s,p,o,d,h){return i(),e("div",null,s[0]||(s[0]=[n(`

REQUEST_HEADERS_MODE

Also known as

request_headers (with or without @ prefix)

Control how HTTP request headers are passed to the PostgreSQL function.

Syntax

code
@request_headers_mode <mode>
+@request_headers <mode>

Values

ValueDescription
ignoreDon't pass request headers to the function
contextSet headers as PostgreSQL context variable via set_config()
parameterPass headers to a function parameter as JSON

Examples

Ignore Headers

sql
sql
comment on function simple_func() is
+'HTTP GET
+@request_headers_mode ignore';

Pass as Context Variable

sql
sql
comment on function context_aware_func() is
+'HTTP GET
+@request_headers_mode context';

Headers accessible via: current_setting('request.headers', true)

Pass as Parameter

sql
sql
create function with_headers(_data text, _headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function with_headers(text, json) is
+'HTTP POST
+@request_headers_mode parameter';

Equivalent as a SQL file endpoint (sql/with-headers.sql):

sql
sql
/*
+HTTP POST
+@request_headers_mode parameter
+@param $1 data
+@param $2 headers json
+*/
+select json_build_object('data', $1, 'headers', $2);

Behavior

`,23)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_request-headers-mode.md.CiBM2WBe.lean.js b/assets/annotations_request-headers-mode.md.CiBM2WBe.lean.js new file mode 100644 index 000000000..11c1ceaa3 --- /dev/null +++ b/assets/annotations_request-headers-mode.md.CiBM2WBe.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"REQUEST_HEADERS_MODE Annotation","titleTemplate":"NpgsqlRest","description":"Control how HTTP request headers are passed to PostgreSQL functions. Access headers via context or parameters.","frontmatter":{"outline":[2,3],"title":"REQUEST_HEADERS_MODE Annotation","titleTemplate":"NpgsqlRest","description":"Control how HTTP request headers are passed to PostgreSQL functions. Access headers via context or parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest request headers, http headers postgresql, access request headers, headers to function, header mode"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_HEADERS_MODE Annotation"}],["meta",{"property":"og:description","content":"Control how HTTP request headers are passed to PostgreSQL functions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-headers-mode.md","filePath":"annotations/request-headers-mode.md"}'),t={name:"annotations/request-headers-mode.md"};function l(r,s,p,o,d,h){return i(),e("div",null,s[0]||(s[0]=[n("",23)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_request-headers-parameter-name.md.B1w65j2m.js b/assets/annotations_request-headers-parameter-name.md.B1w65j2m.js new file mode 100644 index 000000000..f143d28ac --- /dev/null +++ b/assets/annotations_request-headers-parameter-name.md.B1w65j2m.js @@ -0,0 +1,27 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"REQUEST_HEADERS_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Set parameter name for receiving HTTP request headers in PostgreSQL functions. Customize header parameter naming.","frontmatter":{"outline":[2,3],"title":"REQUEST_HEADERS_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Set parameter name for receiving HTTP request headers in PostgreSQL functions. Customize header parameter naming.","head":[["meta",{"name":"keywords","content":"npgsqlrest headers parameter, request headers param, http headers function, header parameter name"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_HEADERS_PARAMETER_NAME Annotation"}],["meta",{"property":"og:description","content":"Set the parameter name that receives HTTP request headers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-headers-parameter-name.md","filePath":"annotations/request-headers-parameter-name.md"}'),t={name:"annotations/request-headers-parameter-name.md"};function l(r,s,p,h,d,o){return i(),e("div",null,s[0]||(s[0]=[n(`

REQUEST_HEADERS_PARAMETER_NAME

Also known as

request_headers_param_name (with or without @ prefix)

Set the parameter name that receives request headers when using parameter mode.

Syntax

code
@request_headers_parameter_name <param-name>

Examples

Custom Parameter Name

sql
sql
create function process_request(_data text, _req_headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_request(text, json) is
+'HTTP POST
+@request_headers_mode parameter
+@request_headers_parameter_name _req_headers';

Equivalent as a SQL file endpoint (sql/process-request.sql):

sql
sql
/*
+HTTP POST
+@request_headers_mode parameter
+@request_headers_parameter_name req_headers
+@param $1 data
+@param $2 req_headers json
+*/
+select json_build_object('data', $1, 'headers', $2);

Default Parameter Name

By default, uses _headers as the parameter name:

sql
sql
create function my_func(_input text, _headers json default null)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function my_func(text, json) is
+'HTTP POST
+@request_headers_mode parameter';

Behavior

`,19)]))}const m=a(t,[["render",l]]);export{c as __pageData,m as default}; diff --git a/assets/annotations_request-headers-parameter-name.md.B1w65j2m.lean.js b/assets/annotations_request-headers-parameter-name.md.B1w65j2m.lean.js new file mode 100644 index 000000000..f195262b1 --- /dev/null +++ b/assets/annotations_request-headers-parameter-name.md.B1w65j2m.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"REQUEST_HEADERS_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Set parameter name for receiving HTTP request headers in PostgreSQL functions. Customize header parameter naming.","frontmatter":{"outline":[2,3],"title":"REQUEST_HEADERS_PARAMETER_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Set parameter name for receiving HTTP request headers in PostgreSQL functions. Customize header parameter naming.","head":[["meta",{"name":"keywords","content":"npgsqlrest headers parameter, request headers param, http headers function, header parameter name"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_HEADERS_PARAMETER_NAME Annotation"}],["meta",{"property":"og:description","content":"Set the parameter name that receives HTTP request headers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-headers-parameter-name.md","filePath":"annotations/request-headers-parameter-name.md"}'),t={name:"annotations/request-headers-parameter-name.md"};function l(r,s,p,h,d,o){return i(),e("div",null,s[0]||(s[0]=[n("",19)]))}const m=a(t,[["render",l]]);export{c as __pageData,m as default}; diff --git a/assets/annotations_request-param-type.md.D2ADbicS.js b/assets/annotations_request-param-type.md.D2ADbicS.js new file mode 100644 index 000000000..81557763c --- /dev/null +++ b/assets/annotations_request-param-type.md.D2ADbicS.js @@ -0,0 +1,46 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"REQUEST_PARAM_TYPE Annotation","titleTemplate":"NpgsqlRest","description":"Control parameter transmission method for PostgreSQL REST API endpoints. Choose between query string and request body parameters.","frontmatter":{"outline":[2,3],"title":"REQUEST_PARAM_TYPE Annotation","titleTemplate":"NpgsqlRest","description":"Control parameter transmission method for PostgreSQL REST API endpoints. Choose between query string and request body parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest param type, query string parameters, request body params, parameter transmission, api input method"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_PARAM_TYPE Annotation"}],["meta",{"property":"og:description","content":"Control parameter transmission via query string or request body."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-param-type.md","filePath":"annotations/request-param-type.md"}'),t={name:"annotations/request-param-type.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e(`

REQUEST_PARAM_TYPE

Also known as

param_type (with or without @ prefix)

The bare value keywords also work as standalone annotations: query_string / query (same as @request_param_type query_string) and body_json / body (same as @request_param_type body_json).

Control how parameters are transmitted to the endpoint - via query string or request body.

Syntax

code
@request_param_type <type>
+@param_type <type>

type: query_string, query, body, body_json

Values

ValueDescription
query_stringParameters from URL query string
querySame as query_string
body_jsonParameters from JSON request body
bodySame as body_json

Default Behavior

When not specified:

Examples

Force Query String Parameters

sql
sql
create function search_users(_name text, _active bool)
+returns setof users
+language sql
+begin atomic;
+select * from users where name ilike '%' || _name || '%' and active = _active;
+end;
+
+comment on function search_users(text, bool) is
+'HTTP GET
+@request_param_type query_string';

Equivalent as a SQL file endpoint (sql/search-users.sql):

sql
sql
/*
+HTTP GET
+@request_param_type query_string
+@param $1 name
+@param $2 active boolean
+*/
+select * from users where name ilike '%' || $1 || '%' and active = $2;

Request: GET /api/search-users?_name=john&_active=true

Force JSON Body Parameters

sql
sql
create function get_filtered_data(_filters text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function get_filtered_data(text) is
+'HTTP GET
+@request_param_type body_json';

Request:

http
http
GET /api/get-filtered-data
+Content-Type: application/json
+
+{"_filters": "status=active"}

Short Form Keywords

sql
sql
-- Using '@param_type' instead of '@request_param_type'
+comment on function func1(text) is
+'HTTP
+@param_type query';
+
+-- Using 'BODY' (case-insensitive)
+comment on function func2(text) is
+'HTTP
+@param_type BODY';

POST with Query String

Override the default body behavior for POST:

sql
sql
create function quick_action(_id int)
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function quick_action(int) is
+'HTTP POST
+@param_type query_string';

Request: POST /api/quick-action?_id=123

Behavior

When parameter type doesn't match the request format, the endpoint returns 404 Not Found:

`,34)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_request-param-type.md.D2ADbicS.lean.js b/assets/annotations_request-param-type.md.D2ADbicS.lean.js new file mode 100644 index 000000000..4f02d5e0c --- /dev/null +++ b/assets/annotations_request-param-type.md.D2ADbicS.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"REQUEST_PARAM_TYPE Annotation","titleTemplate":"NpgsqlRest","description":"Control parameter transmission method for PostgreSQL REST API endpoints. Choose between query string and request body parameters.","frontmatter":{"outline":[2,3],"title":"REQUEST_PARAM_TYPE Annotation","titleTemplate":"NpgsqlRest","description":"Control parameter transmission method for PostgreSQL REST API endpoints. Choose between query string and request body parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest param type, query string parameters, request body params, parameter transmission, api input method"}],["meta",{"property":"og:title","content":"NpgsqlRest REQUEST_PARAM_TYPE Annotation"}],["meta",{"property":"og:description","content":"Control parameter transmission via query string or request body."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/request-param-type.md","filePath":"annotations/request-param-type.md"}'),t={name:"annotations/request-param-type.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e("",34)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_resolved-parameters.md.CtIRul_U.js b/assets/annotations_resolved-parameters.md.CtIRul_U.js new file mode 100644 index 000000000..96e817581 --- /dev/null +++ b/assets/annotations_resolved-parameters.md.CtIRul_U.js @@ -0,0 +1,15 @@ +import{_ as a,c as s,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse(`{"title":"Resolved Parameters","titleTemplate":"NpgsqlRest","description":"Compute a routine parameter's value server-side from a SQL expression at request time, instead of taking it from the client — for injecting DB-stored API tokens, secrets, or derived values into outbound HTTP calls, headers, and parameters.","frontmatter":{"outline":[2,3],"title":"Resolved Parameters","titleTemplate":"NpgsqlRest","description":"Compute a routine parameter's value server-side from a SQL expression at request time, instead of taking it from the client — for injecting DB-stored API tokens, secrets, or derived values into outbound HTTP calls, headers, and parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest resolved parameter, server-side parameter, sql expression parameter, inject api token postgresql, secret in authorization header, resolved parameter expression"}],["meta",{"property":"og:title","content":"NpgsqlRest Resolved Parameters"}],["meta",{"property":"og:description","content":"Resolve a parameter's value server-side from SQL at request time, never from the client."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/resolved-parameters.md","filePath":"annotations/resolved-parameters.md"}`),r={name:"annotations/resolved-parameters.md"};function i(o,e,l,p,d,c){return t(),s("div",null,e[0]||(e[0]=[n(`

Resolved Parameters

A resolved parameter has its value computed server-side from a SQL expression at request time, instead of being supplied by the client. You declare it with a param_name = <sql> comment annotation where param_name matches a real routine parameter.

This is how you inject a value the caller must not provide or see — a DB-stored API token, a secret, or anything derived in SQL — into the routine and into {name} placeholder substitution (response headers, custom parameters, and HTTP custom type URL/headers/body).

sql
sql
comment on function get_secure_data(_user_id int, _req my_api_type, _token text) is '
+HTTP GET
+_token = select api_token from user_tokens where user_id = {_user_id}
+';

The client calls GET /api/get-secure-data/?user_id=42; the server runs select api_token from user_tokens where user_id = 42, binds the result to _token, and uses it wherever {_token} appears. The token never leaves the server, and a client &token=hacked is ignored.

Syntax

code
<parameter_name> = <sql expression>

Behavior

Examples

Inject a DB-stored API token into an outbound call

sql
sql
comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}
+Authorization: Bearer {_api_key}';
+
+comment on function get_weather(_city text, _api weather_api, _api_key text) is '
+HTTP GET
+_api_key = select token from weather_tokens order by fetched_at desc limit 1
+';

The caller supplies only _city. _api_key is read from weather_tokens per request, so it always reflects the latest token — useful when a separate refresh/login routine periodically writes a new token into that table. (Pair it with pg_cron or a refresh routine to keep the row current; a tight-expiry variant: … where expires_at > now() order by fetched_at desc limit 1.)

Multiple resolved parameters

sql
sql
comment on function my_func(_name text, _req my_type, _token text, _api_key text) is '
+_token   = select api_token from tokens where user_name = {_name}
+_api_key = select ''static-key-'' || api_token from tokens where user_name = {_name}
+';

Resolved value in URL, header, and body

A resolved value participates in every placeholder location of an HTTP custom type:

code
-- URL:    GET https://api.example.com/resource/{_secret_path}
+-- Header: Authorization: Bearer {_token}
+-- Body:   {"token": "{_token}", "data": "{_payload}"}

How it compares to the other {name} sources

A {name} placeholder can be filled three ways — pick by where the value comes from:

SourceAnnotationUse when
Request parameter(the parameter itself)the caller provides the value
Environment variableNpgsqlRest:AvailableEnvVars allowlista static, per-deployment value (API key set at deploy, server name)
Resolved parameter (this page)param = <sql>a value computed/looked-up server-side per request (DB-stored token, claim-derived secret) — must not come from the client
`,24)]))}const u=a(r,[["render",i]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_resolved-parameters.md.CtIRul_U.lean.js b/assets/annotations_resolved-parameters.md.CtIRul_U.lean.js new file mode 100644 index 000000000..8b24b5f8d --- /dev/null +++ b/assets/annotations_resolved-parameters.md.CtIRul_U.lean.js @@ -0,0 +1 @@ +import{_ as a,c as s,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse(`{"title":"Resolved Parameters","titleTemplate":"NpgsqlRest","description":"Compute a routine parameter's value server-side from a SQL expression at request time, instead of taking it from the client — for injecting DB-stored API tokens, secrets, or derived values into outbound HTTP calls, headers, and parameters.","frontmatter":{"outline":[2,3],"title":"Resolved Parameters","titleTemplate":"NpgsqlRest","description":"Compute a routine parameter's value server-side from a SQL expression at request time, instead of taking it from the client — for injecting DB-stored API tokens, secrets, or derived values into outbound HTTP calls, headers, and parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest resolved parameter, server-side parameter, sql expression parameter, inject api token postgresql, secret in authorization header, resolved parameter expression"}],["meta",{"property":"og:title","content":"NpgsqlRest Resolved Parameters"}],["meta",{"property":"og:description","content":"Resolve a parameter's value server-side from SQL at request time, never from the client."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/resolved-parameters.md","filePath":"annotations/resolved-parameters.md"}`),r={name:"annotations/resolved-parameters.md"};function i(o,e,l,p,d,c){return t(),s("div",null,e[0]||(e[0]=[n("",24)]))}const u=a(r,[["render",i]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_response-headers.md.BjZbt4Z0.js b/assets/annotations_response-headers.md.BjZbt4Z0.js new file mode 100644 index 000000000..55448a980 --- /dev/null +++ b/assets/annotations_response-headers.md.BjZbt4Z0.js @@ -0,0 +1,81 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Response Headers Annotation","titleTemplate":"NpgsqlRest","description":"Set custom HTTP response headers for PostgreSQL REST API endpoints. Configure Cache-Control, Content-Type, and custom headers.","frontmatter":{"outline":[2,3],"title":"Response Headers Annotation","titleTemplate":"NpgsqlRest","description":"Set custom HTTP response headers for PostgreSQL REST API endpoints. Configure Cache-Control, Content-Type, and custom headers.","head":[["meta",{"name":"keywords","content":"npgsqlrest response headers, custom http headers, cache control header, content type header, api response headers"}],["meta",{"property":"og:title","content":"NpgsqlRest Response Headers Annotation"}],["meta",{"property":"og:description","content":"Set custom HTTP response headers like Cache-Control and Content-Type."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/response-headers.md","filePath":"annotations/response-headers.md"}'),l={name:"annotations/response-headers.md"};function t(p,s,r,h,c,k){return i(),n("div",null,s[0]||(s[0]=[e(`

Response Headers

Set custom HTTP response headers for the endpoint.

Syntax

code
<Header-Name>: <value>

Response headers use standard HTTP header format with a colon separator. Header names are case-insensitive.

Examples

Set Content-Type

sql
sql
create function get_html_page()
+returns text
+language sql
+begin atomic;
+select '<html><body><h1>Hello</h1></body></html>';
+end;
+
+comment on function get_html_page() is
+'HTTP GET
+Content-Type: text/html';

Equivalent as a SQL file endpoint (sql/get-html-page.sql):

sql
sql
/*
+HTTP GET
+Content-Type: text/html
+*/
+select '<html><body><h1>Hello</h1></body></html>';

Response includes: Content-Type: text/html

Multiple Headers

sql
sql
create function get_api_data()
+returns json
+language sql
+begin atomic;
+select '{"status": "ok"}'::json;
+end;
+
+comment on function get_api_data() is
+'HTTP GET
+Content-Type: application/json
+Cache-Control: no-store
+X-Custom-Header: custom-value';

Response includes all three headers.

Multi-Value Headers

Headers with the same name are combined:

sql
sql
create function set_cookies()
+returns text
+language sql
+begin atomic;
+select 'OK';
+end;
+
+comment on function set_cookies() is
+'HTTP GET
+Set-Cookie: session=abc123
+Set-Cookie: theme=dark
+Set-Cookie: lang=en';

Response includes: Set-Cookie: session=abc123, theme=dark, lang=en

Cache Control

sql
sql
create function get_static_config()
+returns json
+language sql
+begin atomic;
+select config from app_config where id = 1;
+end;
+
+comment on function get_static_config() is
+'HTTP GET
+Cache-Control: public, max-age=3600';

Clients can cache the response for 1 hour.

Combined with Other Annotations

sql
sql
create function export_report()
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_report() is
+'HTTP GET
+@authorize manager
+Content-Type: text/csv
+Content-Disposition: attachment; filename="report.csv"
+Cache-Control: no-cache';

Dynamic Headers from Parameters

Header values can include parameter values using the {param_name} template syntax. The matching and substitution rules (case-sensitivity, NULL handling, etc.) are shared across annotations — see Parameter Value Substitution.

sql
sql
create function export_report(_type text, _file text)
+returns text
+language sql
+begin atomic;
+...;
+end;
+
+comment on function export_report(text, text) is
+'HTTP GET
+@authorize manager
+Content-Type: {_type}
+Content-Disposition: attachment; filename={_file}
+Cache-Control: no-cache';

Request: GET /api/export-report?_type=text/csv&_file=report.csv

Response headers:

code
Content-Type: text/csv
+Content-Disposition: attachment; filename=report.csv

CORS Headers

sql
sql
create function cors_endpoint()
+returns json
+language sql
+begin atomic;
+select '{}'::json;
+end;
+
+comment on function cors_endpoint() is
+'HTTP GET
+Access-Control-Allow-Origin: *
+Access-Control-Allow-Methods: GET, POST
+Access-Control-Allow-Headers: Content-Type';

Note: To configure CORS centrally (origins, methods, credentials, preflight), use the CORS configuration instead.

Common Headers

HeaderPurpose
Content-TypeResponse media type
Cache-ControlCaching directives
Content-DispositionDownload filename
X-*Custom application headers
Set-CookieSet cookies
`,38)]))}const m=a(l,[["render",t]]);export{d as __pageData,m as default}; diff --git a/assets/annotations_response-headers.md.BjZbt4Z0.lean.js b/assets/annotations_response-headers.md.BjZbt4Z0.lean.js new file mode 100644 index 000000000..e50b7057b --- /dev/null +++ b/assets/annotations_response-headers.md.BjZbt4Z0.lean.js @@ -0,0 +1 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Response Headers Annotation","titleTemplate":"NpgsqlRest","description":"Set custom HTTP response headers for PostgreSQL REST API endpoints. Configure Cache-Control, Content-Type, and custom headers.","frontmatter":{"outline":[2,3],"title":"Response Headers Annotation","titleTemplate":"NpgsqlRest","description":"Set custom HTTP response headers for PostgreSQL REST API endpoints. Configure Cache-Control, Content-Type, and custom headers.","head":[["meta",{"name":"keywords","content":"npgsqlrest response headers, custom http headers, cache control header, content type header, api response headers"}],["meta",{"property":"og:title","content":"NpgsqlRest Response Headers Annotation"}],["meta",{"property":"og:description","content":"Set custom HTTP response headers like Cache-Control and Content-Type."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/response-headers.md","filePath":"annotations/response-headers.md"}'),l={name:"annotations/response-headers.md"};function t(p,s,r,h,c,k){return i(),n("div",null,s[0]||(s[0]=[e("",38)]))}const m=a(l,[["render",t]]);export{d as __pageData,m as default}; diff --git a/assets/annotations_response-null-handling.md.BXujxmf9.js b/assets/annotations_response-null-handling.md.BXujxmf9.js new file mode 100644 index 000000000..66c2717eb --- /dev/null +++ b/assets/annotations_response-null-handling.md.BXujxmf9.js @@ -0,0 +1,13 @@ +import{_ as n,c as e,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"RESPONSE_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control how NULL results are returned in PostgreSQL REST API responses. Configure JSON null handling and empty responses.","frontmatter":{"outline":[2,3],"title":"RESPONSE_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control how NULL results are returned in PostgreSQL REST API responses. Configure JSON null handling and empty responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest response null, null json response, empty response handling, postgresql null output, api null response"}],["meta",{"property":"og:title","content":"NpgsqlRest RESPONSE_NULL_HANDLING Annotation"}],["meta",{"property":"og:description","content":"Control how NULL results are returned in API responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/response-null-handling.md","filePath":"annotations/response-null-handling.md"}'),i={name:"annotations/response-null-handling.md"};function l(o,s,r,p,d,c){return a(),e("div",null,s[0]||(s[0]=[t(`

RESPONSE_NULL_HANDLING

Also known as

response_null, text_response_null_handling (with or without @ prefix)

Control how NULL results are returned in plain text responses when the execution returns NULL from the database.

Syntax

code
@response_null <mode>
+@response_null_handling <mode>
+@text_response_null_handling <mode>

Values

ValueDescription
empty_stringReturns an empty string response with status code 200 OK (default)
null_literalReturns a string literal "NULL" with status code 200 OK
no_content or 204_no_contentReturns status code 204 NO CONTENT

Examples

Return Empty String for NULL

sql
sql
comment on function get_value(_id int) is
+'HTTP GET
+@response_null empty_string';

If result is NULL → Response body: ""

Return 204 for NULL

sql
sql
comment on function find_record(_id int) is
+'HTTP GET
+@response_null 204_no_content';

If result is NULL → HTTP 204 with no body

Return JSON null

sql
sql
comment on function get_optional(_key text) is
+'HTTP GET
+@response_null null_literal';

If result is NULL → Response body: null

Configuration Default

You can set the default behavior for all endpoints in appsettings.json:

json
json
{
+  "NpgsqlRest": {
+    "TextResponseNullHandling": "NoContent"
+  }
+}

Available values: EmptyString (default), NullLiteral, NoContent.

This sets the default for all endpoints, which can then be overridden per-endpoint using comment annotations.

`,26)]))}const m=n(i,[["render",l]]);export{h as __pageData,m as default}; diff --git a/assets/annotations_response-null-handling.md.BXujxmf9.lean.js b/assets/annotations_response-null-handling.md.BXujxmf9.lean.js new file mode 100644 index 000000000..d7fe713e6 --- /dev/null +++ b/assets/annotations_response-null-handling.md.BXujxmf9.lean.js @@ -0,0 +1 @@ +import{_ as n,c as e,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"RESPONSE_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control how NULL results are returned in PostgreSQL REST API responses. Configure JSON null handling and empty responses.","frontmatter":{"outline":[2,3],"title":"RESPONSE_NULL_HANDLING Annotation","titleTemplate":"NpgsqlRest","description":"Control how NULL results are returned in PostgreSQL REST API responses. Configure JSON null handling and empty responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest response null, null json response, empty response handling, postgresql null output, api null response"}],["meta",{"property":"og:title","content":"NpgsqlRest RESPONSE_NULL_HANDLING Annotation"}],["meta",{"property":"og:description","content":"Control how NULL results are returned in API responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/response-null-handling.md","filePath":"annotations/response-null-handling.md"}'),i={name:"annotations/response-null-handling.md"};function l(o,s,r,p,d,c){return a(),e("div",null,s[0]||(s[0]=[t("",26)]))}const m=n(i,[["render",l]]);export{h as __pageData,m as default}; diff --git a/assets/annotations_result-name.md.oSFavaHs.js b/assets/annotations_result-name.md.oSFavaHs.js new file mode 100644 index 000000000..b98b0bd2b --- /dev/null +++ b/assets/annotations_result-name.md.oSFavaHs.js @@ -0,0 +1,37 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RESULT_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Rename result keys in multi-command SQL file endpoints. Replace default result1, result2 keys with meaningful names.","frontmatter":{"outline":[2,3],"title":"RESULT_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Rename result keys in multi-command SQL file endpoints. Replace default result1, result2 keys with meaningful names.","head":[["meta",{"name":"keywords","content":"npgsqlrest result name, multi-command result, sql file result, rename result key, batch sql endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest RESULT_NAME Annotation"}],["meta",{"property":"og:description","content":"Rename result keys in multi-command SQL file endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/result-name.md","filePath":"annotations/result-name.md"}'),t={name:"annotations/result-name.md"};function l(h,s,p,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e(`

RESULT_NAME

Rename the default result keys (result1, result2, ...) in multi-command SQL file endpoints. This makes the response JSON more descriptive and easier to consume.

This annotation only applies to multi-command SQL file endpoints (files with multiple SQL statements separated by ;).

Available since version 3.12.0.

Syntax

code
@result <name>
+@result is <name>

The @result annotation is positional. It can be placed in two ways:

  1. Before the statement (on a separate line) — applies to the next statement below it
  2. Inline after the semicolon (on the same line) — applies to the statement on that line

This same placement rule applies to all positional annotations: @result, @single, and @skip.

Examples

Before Statement (Separate Line)

Place @result name on a line before the statement it applies to:

sql
sql
-- sql/dashboard.sql
+-- HTTP GET
+-- @result users
+SELECT id, name FROM users;
+-- @result orders
+SELECT id, total FROM orders;

Response:

json
json
{
+  "users": [{"id": 1, "name": "Alice"}, ...],
+  "orders": [{"id": 1, "total": 99.99}, ...]
+}

Inline After Semicolon (Same Line)

Place @result name after the semicolon on the same line as the statement:

sql
sql
-- sql/dashboard.sql
+-- HTTP GET
+SELECT id, name FROM users; -- @result users
+SELECT id, total FROM orders; -- @result orders

Produces the same result as the previous example.

"is" Style Syntax

The is keyword is optional:

sql
sql
-- These are equivalent:
+-- @result validate
+-- @result is validate

Naming Some Results

Commands without a @result annotation keep their default auto-generated key:

sql
sql
-- sql/process_order.sql
+-- HTTP POST
+-- @param $1 order_id
+-- @result validate
+select count(*) from orders where id = $1;
+update orders set status = 'processing' where id = $1;
+-- @result confirm
+select id, status from orders where id = $1;

POST /api/process-order with {"order_id": 42} returns:

json
json
{
+  "validate": [1],
+  "result2": 1,
+  "confirm": [{"id": 42, "status": "processing"}]
+}

Naming All Results

sql
sql
-- sql/dashboard_data.sql
+-- HTTP GET
+-- @result users
+select count(*) from users;
+-- @result orders
+select count(*) from orders where created_at > now() - interval '24 hours';
+-- @result revenue
+select sum(total) from orders where created_at > now() - interval '24 hours';

Response:

json
json
{
+  "users": [{"count": 150}],
+  "orders": [{"count": 42}],
+  "revenue": [{"sum": 12500.00}]
+}

Behavior

`,36)]))}const y=i(t,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/annotations_result-name.md.oSFavaHs.lean.js b/assets/annotations_result-name.md.oSFavaHs.lean.js new file mode 100644 index 000000000..ea6755ee6 --- /dev/null +++ b/assets/annotations_result-name.md.oSFavaHs.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RESULT_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Rename result keys in multi-command SQL file endpoints. Replace default result1, result2 keys with meaningful names.","frontmatter":{"outline":[2,3],"title":"RESULT_NAME Annotation","titleTemplate":"NpgsqlRest","description":"Rename result keys in multi-command SQL file endpoints. Replace default result1, result2 keys with meaningful names.","head":[["meta",{"name":"keywords","content":"npgsqlrest result name, multi-command result, sql file result, rename result key, batch sql endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest RESULT_NAME Annotation"}],["meta",{"property":"og:description","content":"Rename result keys in multi-command SQL file endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/result-name.md","filePath":"annotations/result-name.md"}'),t={name:"annotations/result-name.md"};function l(h,s,p,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e("",36)]))}const y=i(t,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/annotations_retry-strategy.md.DJkjr3eT.js b/assets/annotations_retry-strategy.md.DJkjr3eT.js new file mode 100644 index 000000000..6f82c27e7 --- /dev/null +++ b/assets/annotations_retry-strategy.md.DJkjr3eT.js @@ -0,0 +1,37 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RETRY_STRATEGY Annotation","titleTemplate":"NpgsqlRest","description":"Assign retry strategies for handling transient PostgreSQL database failures. Configure automatic retries per endpoint.","frontmatter":{"outline":[2,3],"title":"RETRY_STRATEGY Annotation","titleTemplate":"NpgsqlRest","description":"Assign retry strategies for handling transient PostgreSQL database failures. Configure automatic retries per endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest retry strategy, transient error retry, database failure handling, automatic retry api"}],["meta",{"property":"og:title","content":"NpgsqlRest RETRY_STRATEGY Annotation"}],["meta",{"property":"og:description","content":"Assign retry strategies for handling transient database failures."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/retry-strategy.md","filePath":"annotations/retry-strategy.md"}'),t={name:"annotations/retry-strategy.md"};function l(r,s,p,h,k,o){return n(),i("div",null,s[0]||(s[0]=[e(`

RETRY_STRATEGY

Also known as

retry_strategy, retry (with or without @ prefix)

Assign a named retry strategy for handling transient database failures.

Syntax

code
@retry_strategy <strategy-name>
+@retry <strategy-name>

Or using custom parameter syntax:

code
@retry_strategy = <strategy-name>
+@retry = <strategy-name>

The strategy-name must match a strategy defined in CommandRetryOptions configuration.

Examples

Use Default Strategy

sql
sql
comment on function critical_operation() is
+'HTTP POST
+@retry_strategy default';

Use Named Strategy

sql
sql
comment on function important_query() is
+'HTTP GET
+@retry aggressive';

Combined with Timeout

sql
sql
comment on function long_running_task() is
+'HTTP POST
+@timeout 2min
+@retry_strategy default';

Behavior

Common Retry Scenarios

Error TypePostgreSQL CodesDescription
Serialization40001, 40P01Transaction conflicts, deadlocks
Connection08000, 08003, 08006Connection issues
Resources53300Too many connections
System57P03Cannot connect now

Configuration Example

Define strategies in configuration:

json
json
{
+  "CommandRetryOptions": {
+    "Enabled": true,
+    "DefaultStrategy": "default",
+    "Strategies": {
+      "default": {
+        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
+      },
+      "aggressive": {
+        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
+      },
+      "minimal": {
+        "RetrySequenceSeconds": [0, 1],
+        "ErrorCodes": ["40001", "40P01"]
+      }
+    }
+  }
+}

Then use in annotations:

sql
sql
-- Use aggressive retry for critical operations
+comment on function process_payment() is
+'HTTP POST
+@retry aggressive';
+
+-- Use minimal retry for fast queries
+comment on function quick_lookup() is
+'HTTP GET
+@retry minimal';

See Command Retry for complete configuration reference.

See Also

`,31)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_retry-strategy.md.DJkjr3eT.lean.js b/assets/annotations_retry-strategy.md.DJkjr3eT.lean.js new file mode 100644 index 000000000..c1b925766 --- /dev/null +++ b/assets/annotations_retry-strategy.md.DJkjr3eT.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"RETRY_STRATEGY Annotation","titleTemplate":"NpgsqlRest","description":"Assign retry strategies for handling transient PostgreSQL database failures. Configure automatic retries per endpoint.","frontmatter":{"outline":[2,3],"title":"RETRY_STRATEGY Annotation","titleTemplate":"NpgsqlRest","description":"Assign retry strategies for handling transient PostgreSQL database failures. Configure automatic retries per endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest retry strategy, transient error retry, database failure handling, automatic retry api"}],["meta",{"property":"og:title","content":"NpgsqlRest RETRY_STRATEGY Annotation"}],["meta",{"property":"og:description","content":"Assign retry strategies for handling transient database failures."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/retry-strategy.md","filePath":"annotations/retry-strategy.md"}'),t={name:"annotations/retry-strategy.md"};function l(r,s,p,h,k,o){return n(),i("div",null,s[0]||(s[0]=[e("",31)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_returns.md.BXcLtPWm.js b/assets/annotations_returns.md.BXcLtPWm.js new file mode 100644 index 000000000..38d479e92 --- /dev/null +++ b/assets/annotations_returns.md.BXcLtPWm.js @@ -0,0 +1,34 @@ +import{_ as i,c as e,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"RETURNS Annotation","titleTemplate":"NpgsqlRest","description":"Skip the PostgreSQL Describe step and resolve return columns from a composite type. Enables SQL files that use runtime-created temp tables.","frontmatter":{"outline":[2,3],"title":"RETURNS Annotation","titleTemplate":"NpgsqlRest","description":"Skip the PostgreSQL Describe step and resolve return columns from a composite type. Enables SQL files that use runtime-created temp tables.","head":[["meta",{"name":"keywords","content":"npgsqlrest returns annotation, composite type override, skip describe, temp table sql file, do block result"}],["meta",{"property":"og:title","content":"NpgsqlRest RETURNS Annotation"}],["meta",{"property":"og:description","content":"Skip the PostgreSQL Describe step and resolve return columns from a composite type."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/returns.md","filePath":"annotations/returns.md"}'),n={name:"annotations/returns.md"};function l(p,s,r,h,o,c){return a(),e("div",null,s[0]||(s[0]=[t(`

RETURNS

Skip the PostgreSQL Describe step for a statement and resolve return columns from a composite type instead. This is a positional annotation — it applies to the next statement below it.

Available since version 3.12.0.

Syntax

code
@returns <composite_type_name>
+@returns <scalar_type>
+@returns void

Supported values:

This annotation skips the PostgreSQL Describe step entirely for the annotated statement. The statement's SQL is never sent to PostgreSQL during startup.

When to Use

Example

sql
sql
-- HTTP GET
+-- @param $1 val1 text
+-- @param $2 val2 integer
+begin;
+select set_config('app.val1', $1, true); -- @skip
+select set_config('app.val2', $2::text, true); -- @skip
+do $$ begin
+    create temp table _result on commit drop as
+    select current_setting('app.val1') as val1,
+           current_setting('app.val2')::int as val2,
+           true as active;
+end; $$;
+-- @returns my_result_type
+-- @result data
+-- @single
+select * from _result;
+end;

Where my_result_type is defined as:

sql
sql
create type my_result_type as (
+    val1 text,
+    val2 integer,
+    active boolean
+);

Without @returns, the select * from _result statement fails at startup because the temp table doesn't exist yet. With @returns my_result_type, the columns are resolved from the composite type definition in pg_catalog.

Scalar Type

Declare a single typed column. Only the first column from the query is used at runtime — extra columns are ignored:

sql
sql
-- HTTP GET
+-- @returns integer
+select count(*) from users;

Returns: [42]

With @single, returns a bare scalar value:

sql
sql
-- HTTP GET
+-- @returns integer
+-- @single
+select count(*) from users;

Returns: 42

Supported scalar types: integer, text, boolean, jsonb, json, bigint, numeric, real, double precision, date, timestamp, timestamptz, uuid, bytea, and all other built-in PostgreSQL types.

Void Statements

Use @returns void to skip Describe for statements that return no results:

sql
sql
-- HTTP POST
+-- @param $1 key text
+-- @param $2 value text
+-- @returns void
+select set_config($1, $2, false);
+-- @result data
+select current_setting($1, true) as result;

The first statement's Describe is skipped entirely. In multi-command files, it produces a rows-affected count in the response. For single-command files, it makes the endpoint void (returns 204 No Content).

Behavior

@returns void vs @void

For single-command SQL files, @returns void has the same runtime effect as @void — both return 204 No Content. The difference: @returns void skips the Describe step (the SQL is never sent to PostgreSQL at startup), while @void still runs Describe and only changes the runtime response. Use @returns void when the statement would fail Describe (e.g., references a temp table). Use @void when Describe succeeds but you don't want any response.

`,32)]))}const u=i(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_returns.md.BXcLtPWm.lean.js b/assets/annotations_returns.md.BXcLtPWm.lean.js new file mode 100644 index 000000000..251ff956e --- /dev/null +++ b/assets/annotations_returns.md.BXcLtPWm.lean.js @@ -0,0 +1 @@ +import{_ as i,c as e,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"RETURNS Annotation","titleTemplate":"NpgsqlRest","description":"Skip the PostgreSQL Describe step and resolve return columns from a composite type. Enables SQL files that use runtime-created temp tables.","frontmatter":{"outline":[2,3],"title":"RETURNS Annotation","titleTemplate":"NpgsqlRest","description":"Skip the PostgreSQL Describe step and resolve return columns from a composite type. Enables SQL files that use runtime-created temp tables.","head":[["meta",{"name":"keywords","content":"npgsqlrest returns annotation, composite type override, skip describe, temp table sql file, do block result"}],["meta",{"property":"og:title","content":"NpgsqlRest RETURNS Annotation"}],["meta",{"property":"og:description","content":"Skip the PostgreSQL Describe step and resolve return columns from a composite type."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/returns.md","filePath":"annotations/returns.md"}'),n={name:"annotations/returns.md"};function l(p,s,r,h,o,c){return a(),e("div",null,s[0]||(s[0]=[t("",32)]))}const u=i(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_security-sensitive.md.iGNlRYXu.js b/assets/annotations_security-sensitive.md.iGNlRYXu.js new file mode 100644 index 000000000..2251a6762 --- /dev/null +++ b/assets/annotations_security-sensitive.md.iGNlRYXu.js @@ -0,0 +1,42 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"SECURITY_SENSITIVE Annotation","titleTemplate":"NpgsqlRest","description":"Mark PostgreSQL REST API endpoints as security-sensitive. Obfuscate passwords and sensitive data in logs.","frontmatter":{"outline":[2,3],"title":"SECURITY_SENSITIVE Annotation","titleTemplate":"NpgsqlRest","description":"Mark PostgreSQL REST API endpoints as security-sensitive. Obfuscate passwords and sensitive data in logs.","head":[["meta",{"name":"keywords","content":"npgsqlrest security sensitive, obfuscate logs, password logging, sensitive data protection, secure logging"}],["meta",{"property":"og:title","content":"NpgsqlRest SECURITY_SENSITIVE Annotation"}],["meta",{"property":"og:description","content":"Mark endpoints as security-sensitive to obfuscate parameter values in logs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/security-sensitive.md","filePath":"annotations/security-sensitive.md"}'),l={name:"annotations/security-sensitive.md"};function t(p,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

SECURITY_SENSITIVE

Also known as

sensitive, security (with or without @ prefix)

Mark endpoint as security-sensitive to obfuscate parameter values in logs.

Syntax

code
@sensitive

Examples

Password Change Endpoint

sql
sql
create function change_password(_old_password text, _new_password text)
+returns boolean
+language sql
+begin atomic;
+...;
+end;
+
+comment on function change_password(text, text) is
+'HTTP POST
+@authorize
+@sensitive';

Equivalent as a SQL file endpoint (sql/change-password.sql):

sql
sql
/*
+HTTP POST
+@authorize
+@sensitive
+@param $1 old_password
+@param $2 new_password
+*/
+update users
+set password_hash = crypt($2, gen_salt('bf'))
+where id = current_user_id()
+  and password_hash = crypt($1, password_hash)
+returning true;

Login Endpoint

sql
sql
create function authenticate(_username text, _password text)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function authenticate(text, text) is
+'HTTP POST
+@login
+@sensitive';

Payment Processing

sql
sql
create function process_payment(_card_number text, _cvv text, _amount numeric)
+returns json
+language sql
+begin atomic;
+...;
+end;
+
+comment on function process_payment(text, text, numeric) is
+'HTTP POST
+@authorize
+@security_sensitive';

Behavior

`,20)]))}const g=a(l,[["render",t]]);export{d as __pageData,g as default}; diff --git a/assets/annotations_security-sensitive.md.iGNlRYXu.lean.js b/assets/annotations_security-sensitive.md.iGNlRYXu.lean.js new file mode 100644 index 000000000..40b9a64b1 --- /dev/null +++ b/assets/annotations_security-sensitive.md.iGNlRYXu.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"SECURITY_SENSITIVE Annotation","titleTemplate":"NpgsqlRest","description":"Mark PostgreSQL REST API endpoints as security-sensitive. Obfuscate passwords and sensitive data in logs.","frontmatter":{"outline":[2,3],"title":"SECURITY_SENSITIVE Annotation","titleTemplate":"NpgsqlRest","description":"Mark PostgreSQL REST API endpoints as security-sensitive. Obfuscate passwords and sensitive data in logs.","head":[["meta",{"name":"keywords","content":"npgsqlrest security sensitive, obfuscate logs, password logging, sensitive data protection, secure logging"}],["meta",{"property":"og:title","content":"NpgsqlRest SECURITY_SENSITIVE Annotation"}],["meta",{"property":"og:description","content":"Mark endpoints as security-sensitive to obfuscate parameter values in logs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/security-sensitive.md","filePath":"annotations/security-sensitive.md"}'),l={name:"annotations/security-sensitive.md"};function t(p,s,h,r,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",20)]))}const g=a(l,[["render",t]]);export{d as __pageData,g as default}; diff --git a/assets/annotations_separator.md.Bh3F0ZIY.js b/assets/annotations_separator.md.Bh3F0ZIY.js new file mode 100644 index 000000000..526c82fbc --- /dev/null +++ b/assets/annotations_separator.md.Bh3F0ZIY.js @@ -0,0 +1,13 @@ +import{_ as s,c as e,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"SEPARATOR Annotation","titleTemplate":"NpgsqlRest","description":"Set column separator for raw output mode in PostgreSQL REST APIs. Configure CSV delimiters and custom separators.","frontmatter":{"outline":[2,3],"title":"SEPARATOR Annotation","titleTemplate":"NpgsqlRest","description":"Set column separator for raw output mode in PostgreSQL REST APIs. Configure CSV delimiters and custom separators.","head":[["meta",{"name":"keywords","content":"npgsqlrest separator, csv delimiter, column separator, raw output format, custom delimiter"}],["meta",{"property":"og:title","content":"NpgsqlRest SEPARATOR Annotation"}],["meta",{"property":"og:description","content":"Set column separator for raw output mode and CSV generation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/separator.md","filePath":"annotations/separator.md"}'),i={name:"annotations/separator.md"};function l(r,a,p,o,c,d){return n(),e("div",null,a[0]||(a[0]=[t(`

SEPARATOR

Also known as

raw_separator (with or without @ prefix)

Set the column separator for raw output mode.

Syntax

code
@separator <string>

Supports escape sequences: \\t (tab), \\n (newline), \\\\ (backslash)

Examples

Comma Separator (CSV)

sql
sql
comment on function export_csv() is
+'HTTP GET
+@raw
+@separator ,';

Tab Separator (TSV)

sql
sql
comment on function export_tsv() is
+'HTTP GET
+@raw
+@separator \\t';

Pipe Separator

sql
sql
comment on function export_pipe() is
+'HTTP GET
+@raw
+@separator |';

Custom Separator

sql
sql
comment on function export_custom() is
+'HTTP GET
+@raw
+@separator ::';
`,19)]))}const u=s(i,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_separator.md.Bh3F0ZIY.lean.js b/assets/annotations_separator.md.Bh3F0ZIY.lean.js new file mode 100644 index 000000000..7923a086a --- /dev/null +++ b/assets/annotations_separator.md.Bh3F0ZIY.lean.js @@ -0,0 +1 @@ +import{_ as s,c as e,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"SEPARATOR Annotation","titleTemplate":"NpgsqlRest","description":"Set column separator for raw output mode in PostgreSQL REST APIs. Configure CSV delimiters and custom separators.","frontmatter":{"outline":[2,3],"title":"SEPARATOR Annotation","titleTemplate":"NpgsqlRest","description":"Set column separator for raw output mode in PostgreSQL REST APIs. Configure CSV delimiters and custom separators.","head":[["meta",{"name":"keywords","content":"npgsqlrest separator, csv delimiter, column separator, raw output format, custom delimiter"}],["meta",{"property":"og:title","content":"NpgsqlRest SEPARATOR Annotation"}],["meta",{"property":"og:description","content":"Set column separator for raw output mode and CSV generation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/separator.md","filePath":"annotations/separator.md"}'),i={name:"annotations/separator.md"};function l(r,a,p,o,c,d){return n(),e("div",null,a[0]||(a[0]=[t("",19)]))}const u=s(i,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_single.md.6tuGsQhF.js b/assets/annotations_single.md.6tuGsQhF.js new file mode 100644 index 000000000..99c89ba60 --- /dev/null +++ b/assets/annotations_single.md.6tuGsQhF.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SINGLE Annotation","titleTemplate":"NpgsqlRest","description":"Return a single record as a JSON object instead of a JSON array. Unwrap single-row results for cleaner API responses.","frontmatter":{"outline":[2,3],"title":"SINGLE Annotation","titleTemplate":"NpgsqlRest","description":"Return a single record as a JSON object instead of a JSON array. Unwrap single-row results for cleaner API responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest single record, json object response, single result api, unwrap json array, postgresql single row"}],["meta",{"property":"og:title","content":"NpgsqlRest SINGLE Annotation"}],["meta",{"property":"og:description","content":"Return a single record as a JSON object instead of a JSON array."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/single.md","filePath":"annotations/single.md"}'),l={name:"annotations/single.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e(`

SINGLE

Also known as

single_record, single_result (with or without @ prefix)

Return a single record as a JSON object instead of a JSON array.

Syntax

code
@single

Default Behavior vs Single

By default, all endpoints return results as a JSON array, even when only one row is returned. With the @single annotation, the result is returned as a plain JSON object.

Without @single:

json
json
[{"id": 1, "name": "Alice"}]

With @single:

json
json
{"id": 1, "name": "Alice"}

Examples

PostgreSQL Function

sql
sql
create function get_user(_id int)
+returns table(id int, name text, email text)
+language sql
+begin atomic;
+select id, name, email from users where id = _id;
+end;
+
+comment on function get_user(int) is 'HTTP GET /users/{_id}
+@single';

GET /users/1

json
json
{"id": 1, "name": "Alice", "email": "alice@example.com"}

SQL File

sql
sql
-- sql/get_user.sql
+-- HTTP GET
+-- @single
+-- @param $1 user_id
+SELECT id, name, email FROM users WHERE id = $1;

GET /api/get-user?user_id=1

json
json
{"id": 1, "name": "Alice", "email": "alice@example.com"}

Single Unnamed Column

When the result has a single unnamed column, the bare JSON value is returned:

sql
sql
-- sql/get_username.sql
+-- HTTP GET
+-- @single
+-- @param $1 user_id
+SELECT name FROM users WHERE id = $1;

GET /api/get-username?user_id=1"Alice"

Multi-Command Files (Positional)

In multi-command SQL files, @single is positional — it applies to the next statement below it:

sql
sql
-- sql/process_user.sql
+-- HTTP POST
+-- @param $1 id
+-- @single
+SELECT id, name FROM users WHERE id = $1;
+UPDATE orders SET status = 'done' WHERE id = $1;
+-- @single
+SELECT id, status FROM orders WHERE id = $1;

Result:

json
json
{
+  "result1": {"id": 1, "name": "alice"},
+  "result2": 1,
+  "result3": {"id": 1, "status": "done"}
+}

Behavior

Empty Results

When the query returns no rows, the behavior depends on the @response_null annotation:

SettingResponse
empty_string (default)Empty response body
null_literalnull
no_contentHTTP 204 No Content

In multi-command files, empty per-command @single results render as null.

`,38)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_single.md.6tuGsQhF.lean.js b/assets/annotations_single.md.6tuGsQhF.lean.js new file mode 100644 index 000000000..840a54493 --- /dev/null +++ b/assets/annotations_single.md.6tuGsQhF.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SINGLE Annotation","titleTemplate":"NpgsqlRest","description":"Return a single record as a JSON object instead of a JSON array. Unwrap single-row results for cleaner API responses.","frontmatter":{"outline":[2,3],"title":"SINGLE Annotation","titleTemplate":"NpgsqlRest","description":"Return a single record as a JSON object instead of a JSON array. Unwrap single-row results for cleaner API responses.","head":[["meta",{"name":"keywords","content":"npgsqlrest single record, json object response, single result api, unwrap json array, postgresql single row"}],["meta",{"property":"og:title","content":"NpgsqlRest SINGLE Annotation"}],["meta",{"property":"og:description","content":"Return a single record as a JSON object instead of a JSON array."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/single.md","filePath":"annotations/single.md"}'),l={name:"annotations/single.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e("",38)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_skip.md.Dh_dVMzK.js b/assets/annotations_skip.md.Dh_dVMzK.js new file mode 100644 index 000000000..1d620ac26 --- /dev/null +++ b/assets/annotations_skip.md.Dh_dVMzK.js @@ -0,0 +1,31 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SKIP Annotation","titleTemplate":"NpgsqlRest","description":"Exclude commands from multi-command SQL file JSON responses while still executing them. Control which statements appear in the result.","frontmatter":{"outline":[2,3],"title":"SKIP Annotation","titleTemplate":"NpgsqlRest","description":"Exclude commands from multi-command SQL file JSON responses while still executing them. Control which statements appear in the result.","head":[["meta",{"name":"keywords","content":"npgsqlrest skip result, skip command, multi-command skip, sql file skip result, exclude result"}],["meta",{"property":"og:title","content":"NpgsqlRest SKIP Annotation"}],["meta",{"property":"og:description","content":"Exclude commands from multi-command SQL file JSON responses while still executing them."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/skip.md","filePath":"annotations/skip.md"}'),t={name:"annotations/skip.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e(`

SKIP

Also known as

skip_result, no_result (with or without @ prefix)

Mark a command in a multi-command SQL file to be executed but excluded from the JSON response. The statement runs against the database, but its result is not included in the response object and it does not consume a result number.

Available since version 3.12.0.

Syntax

code
@skip

The @skip annotation is positional. It can be placed in two ways:

  1. Before the statement (on a separate line) — applies to the next statement below it
  2. Inline after the semicolon (on the same line) — applies to the statement on that line

This same placement rule applies to all positional annotations: @result, @single, and @skip.

Examples

Skipping a DO Block

sql
sql
-- sql/process_and_notify.sql
+-- HTTP POST
+-- @param $1 user_id
+-- @skip
+do $$ begin perform pg_notify('user_updated', 'event'); end; $$;
+-- @result data
+SELECT id, name FROM users WHERE id = $1;

Result: {"data": [{"id": 1, "name": "Alice"}]}

The DO block executes (sending the notification) but does not appear in the response.

Skipping Transaction Control

sql
sql
-- sql/transfer.sql
+-- HTTP POST
+-- @param $1 from_id
+-- @param $2 to_id
+-- @param $3 amount
+-- @skip
+BEGIN;
+UPDATE accounts SET balance = balance - $3 WHERE id = $1;
+UPDATE accounts SET balance = balance + $3 WHERE id = $2;
+-- @skip
+COMMIT;
+-- @result from_account
+SELECT id, balance FROM accounts WHERE id = $1;
+-- @result to_account
+SELECT id, balance FROM accounts WHERE id = $2;

Result:

json
json
{
+  "result1": 1,
+  "result2": 1,
+  "from_account": [{"id": 1, "balance": 900}],
+  "to_account": [{"id": 2, "balance": 1100}]
+}

The BEGIN and COMMIT statements are executed but excluded from the response. The two UPDATE results show rows-affected counts.

Inline Placement

sql
sql
-- sql/cleanup.sql
+-- HTTP POST
+-- @param $1 user_id
+DELETE FROM sessions WHERE user_id = $1; -- @skip
+-- @result user
+SELECT id, name FROM users WHERE id = $1;

Result: {"user": [{"id": 1, "name": "Alice"}]}

SkipNonQueryCommands Setting

The SkipNonQueryCommands setting (default: true) in SqlFileSource configuration automatically excludes non-query commands from the response. This covers transaction control (BEGIN, COMMIT, ROLLBACK, etc.), session commands (SET, RESET), DO blocks, and other non-query statements.

With SkipNonQueryCommands enabled (the default), you typically do not need @skip for these common cases. The @skip annotation is useful for:

Behavior

`,30)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_skip.md.Dh_dVMzK.lean.js b/assets/annotations_skip.md.Dh_dVMzK.lean.js new file mode 100644 index 000000000..95a6033d3 --- /dev/null +++ b/assets/annotations_skip.md.Dh_dVMzK.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SKIP Annotation","titleTemplate":"NpgsqlRest","description":"Exclude commands from multi-command SQL file JSON responses while still executing them. Control which statements appear in the result.","frontmatter":{"outline":[2,3],"title":"SKIP Annotation","titleTemplate":"NpgsqlRest","description":"Exclude commands from multi-command SQL file JSON responses while still executing them. Control which statements appear in the result.","head":[["meta",{"name":"keywords","content":"npgsqlrest skip result, skip command, multi-command skip, sql file skip result, exclude result"}],["meta",{"property":"og:title","content":"NpgsqlRest SKIP Annotation"}],["meta",{"property":"og:description","content":"Exclude commands from multi-command SQL file JSON responses while still executing them."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/skip.md","filePath":"annotations/skip.md"}'),t={name:"annotations/skip.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e("",30)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_sse-events-level.md.BzejI_6s.js b/assets/annotations_sse-events-level.md.BzejI_6s.js new file mode 100644 index 000000000..145ec061b --- /dev/null +++ b/assets/annotations_sse-events-level.md.BzejI_6s.js @@ -0,0 +1,11 @@ +import{_ as s,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"SSE_EVENTS_LEVEL Annotation","titleTemplate":"NpgsqlRest","description":"Set minimum PostgreSQL notice level for Server-Sent Events. Filter SSE messages by severity (INFO, WARNING, ERROR).","frontmatter":{"outline":[2,3],"title":"SSE_EVENTS_LEVEL Annotation","titleTemplate":"NpgsqlRest","description":"Set minimum PostgreSQL notice level for Server-Sent Events. Filter SSE messages by severity (INFO, WARNING, ERROR).","head":[["meta",{"name":"keywords","content":"npgsqlrest sse level, server sent events filter, notice level sse, postgresql notice events, sse severity filter"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE_EVENTS_LEVEL Annotation"}],["meta",{"property":"og:description","content":"Set minimum PostgreSQL notice level for Server-Sent Events filtering."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse-events-level.md","filePath":"annotations/sse-events-level.md"}'),l={name:"annotations/sse-events-level.md"};function i(o,e,r,p,c,d){return n(),a("div",null,e[0]||(e[0]=[t(`

SSE_EVENTS_LEVEL

Also known as

sse_level (with or without @ prefix)

Set the minimum PostgreSQL notice level for Server-Sent Events.

Syntax

code
@sse_level <level>
+@sse_events_level <level>

Values

ValuePostgreSQL Level
infoINFO (default)
noticeNOTICE
warningWARNING

Examples

Info Level (All Messages)

sql
sql
comment on function verbose_process() is
+'HTTP POST
+@sse /events
+@sse_level info';

Receives: RAISE INFO, RAISE NOTICE, RAISE WARNING

Notice Level

sql
sql
comment on function standard_process() is
+'HTTP POST
+@sse /events
+@sse_level notice';

Receives: RAISE NOTICE, RAISE WARNING

Warning Level Only

sql
sql
comment on function quiet_process() is
+'HTTP POST
+@sse /events
+@sse_level warning';

Receives: RAISE WARNING only

`,21)]))}const u=s(l,[["render",i]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_sse-events-level.md.BzejI_6s.lean.js b/assets/annotations_sse-events-level.md.BzejI_6s.lean.js new file mode 100644 index 000000000..cc260b4c8 --- /dev/null +++ b/assets/annotations_sse-events-level.md.BzejI_6s.lean.js @@ -0,0 +1 @@ +import{_ as s,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"SSE_EVENTS_LEVEL Annotation","titleTemplate":"NpgsqlRest","description":"Set minimum PostgreSQL notice level for Server-Sent Events. Filter SSE messages by severity (INFO, WARNING, ERROR).","frontmatter":{"outline":[2,3],"title":"SSE_EVENTS_LEVEL Annotation","titleTemplate":"NpgsqlRest","description":"Set minimum PostgreSQL notice level for Server-Sent Events. Filter SSE messages by severity (INFO, WARNING, ERROR).","head":[["meta",{"name":"keywords","content":"npgsqlrest sse level, server sent events filter, notice level sse, postgresql notice events, sse severity filter"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE_EVENTS_LEVEL Annotation"}],["meta",{"property":"og:description","content":"Set minimum PostgreSQL notice level for Server-Sent Events filtering."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse-events-level.md","filePath":"annotations/sse-events-level.md"}'),l={name:"annotations/sse-events-level.md"};function i(o,e,r,p,c,d){return n(),a("div",null,e[0]||(e[0]=[t("",21)]))}const u=s(l,[["render",i]]);export{m as __pageData,u as default}; diff --git a/assets/annotations_sse-events-scope.md.DSBEdrmZ.js b/assets/annotations_sse-events-scope.md.DSBEdrmZ.js new file mode 100644 index 000000000..66add6bb2 --- /dev/null +++ b/assets/annotations_sse-events-scope.md.DSBEdrmZ.js @@ -0,0 +1,47 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SSE_EVENTS_SCOPE Annotation","titleTemplate":"NpgsqlRest","description":"Control Server-Sent Events broadcast scope. Configure SSE message delivery to individual connections or all listeners.","frontmatter":{"outline":[2,3],"title":"SSE_EVENTS_SCOPE Annotation","titleTemplate":"NpgsqlRest","description":"Control Server-Sent Events broadcast scope. Configure SSE message delivery to individual connections or all listeners.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse scope, server sent events broadcast, sse message scope, event broadcasting, real-time scope"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE_EVENTS_SCOPE Annotation"}],["meta",{"property":"og:description","content":"Control Server-Sent Events broadcast scope for message delivery."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse-events-scope.md","filePath":"annotations/sse-events-scope.md"}'),t={name:"annotations/sse-events-scope.md"};function l(p,s,r,c,o,h){return i(),e("div",null,s[0]||(s[0]=[n(`

SSE_EVENTS_SCOPE

Also known as

sse_scope (with or without @ prefix)

Control who receives Server-Sent Events from this endpoint.

Why scope matters

Every event flows through the single global broadcaster, and every connected EventSource reads from the same stream. Scope is the per-event filter that decides which subscribers actually have the event written to their response. Without scope (or with all), every subscriber sees every event from this endpoint.

Syntax

code
@sse_scope <scope>
+@sse_scope authorize <value1>, <value2>, ...

Space-separated lists are also valid: @sse_scope authorize admin manager supervisor

Or using custom parameter syntax:

code
@sse_scope = <scope>
+@sse_events_scope = <scope>

Values

ValueDescription
matchingClients with matching security context receive events (checks roles, user names, and user IDs)
authorizeOnly authorized clients receive events. Optionally filter by role names, user names, or user IDs
allAll connected clients receive events

Request Correlation

Events are filtered by execution ID when both conditions are met:

When execution IDs are provided but don't match, the event is skipped regardless of scope.

Examples

Matching Scope

sql
sql
comment on function team_task() is
+'HTTP POST
+@sse /team-events
+@sse_scope matching';

Equivalent as a SQL file endpoint (sql/team-task.sql):

sql
sql
/*
+HTTP POST
+@sse /team-events
+@sse_scope matching
+*/
+do $$ begin
+    raise info 'team task progress...';
+end $$;

Events are sent to clients with matching security context:

Authorize Scope with Roles

sql
sql
comment on function admin_broadcast() is
+'HTTP POST
+@sse /admin-events
+@sse_scope authorize admin';

Only clients with admin role receive events.

Authorize with User Names or IDs

sql
sql
comment on function specific_users_notification() is
+'HTTP POST
+@sse /user-events
+@sse_scope authorize john.doe, jane.smith, user123';

Events are sent to clients matching any of the specified role names, user names, or user IDs.

Multiple Values

sql
sql
comment on function staff_notification() is
+'HTTP POST
+@sse /staff-events
+@sse_scope authorize admin, manager, supervisor';

Clients matching any of the specified values receive events.

Broadcast to All

sql
sql
comment on function system_announcement() is
+'HTTP POST
+@sse /announcements
+@sse_scope all';

All connected SSE clients receive events regardless of security context.

Dynamic Scope via RAISE HINT

The scope can also be set dynamically at runtime using the HINT parameter of PostgreSQL RAISE statements. This allows different events within the same function to have different scopes:

sql
sql
create function process_with_notifications()
+returns void
+language plpgsql
+as $$
+begin
+  -- This event goes to all clients
+  raise notice 'System maintenance starting...' using hint = 'all';
+
+  -- This event only goes to admins
+  raise notice 'Admin: detailed system stats...' using hint = 'authorize admin';
+
+  -- This event goes to specific users
+  raise notice 'Your task is complete' using hint = 'authorize john.doe, jane.smith';
+
+  -- This event uses the default scope from annotation
+  raise notice 'General progress update...';
+end;
+$$;
+
+comment on function process_with_notifications() is
+'HTTP POST
+@sse /process-events
+@sse_scope matching';

The HINT value is parsed as: <scope> [value1] [value2] ...

When a hint is provided, it overrides the annotation scope for that specific event. When no hint is provided, the annotation scope is used.

`,43)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_sse-events-scope.md.DSBEdrmZ.lean.js b/assets/annotations_sse-events-scope.md.DSBEdrmZ.lean.js new file mode 100644 index 000000000..c70408251 --- /dev/null +++ b/assets/annotations_sse-events-scope.md.DSBEdrmZ.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SSE_EVENTS_SCOPE Annotation","titleTemplate":"NpgsqlRest","description":"Control Server-Sent Events broadcast scope. Configure SSE message delivery to individual connections or all listeners.","frontmatter":{"outline":[2,3],"title":"SSE_EVENTS_SCOPE Annotation","titleTemplate":"NpgsqlRest","description":"Control Server-Sent Events broadcast scope. Configure SSE message delivery to individual connections or all listeners.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse scope, server sent events broadcast, sse message scope, event broadcasting, real-time scope"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE_EVENTS_SCOPE Annotation"}],["meta",{"property":"og:description","content":"Control Server-Sent Events broadcast scope for message delivery."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse-events-scope.md","filePath":"annotations/sse-events-scope.md"}'),t={name:"annotations/sse-events-scope.md"};function l(p,s,r,c,o,h){return i(),e("div",null,s[0]||(s[0]=[n("",43)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_sse.md.VtYFy1W4.js b/assets/annotations_sse.md.VtYFy1W4.js new file mode 100644 index 000000000..ef75ba84f --- /dev/null +++ b/assets/annotations_sse.md.VtYFy1W4.js @@ -0,0 +1,110 @@ +import{_ as i,c as e,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SSE Annotation","titleTemplate":"NpgsqlRest","description":"Enable Server-Sent Events streaming for PostgreSQL REST APIs. Create real-time endpoints with SSE for live data updates.","frontmatter":{"outline":[2,3],"title":"SSE Annotation","titleTemplate":"NpgsqlRest","description":"Enable Server-Sent Events streaming for PostgreSQL REST APIs. Create real-time endpoints with SSE for live data updates.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse, server sent events, real-time api, streaming endpoint, postgresql streaming, live updates"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE Annotation"}],["meta",{"property":"og:description","content":"Enable Server-Sent Events streaming for real-time data updates from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse.md","filePath":"annotations/sse.md"}'),t={name:"annotations/sse.md"};function l(p,s,r,h,o,c){return a(),e("div",null,s[0]||(s[0]=[n(`

SSE

Also known as

sse_events_path, sse_path (with or without @ prefix)

Enable Server-Sent Events (SSE) streaming for the endpoint.

How events flow

@sse is the only SSE annotation that affects runtime behavior on its own, and it does two independent things:

  1. Registers a connection URL at <endpoint-path>/<level> — clients open an EventSource against it to listen.
  2. Enables broadcasting from this procedureRAISE statements inside this procedure's body forward their notices to the SSE broadcaster.

A procedure without @sse can RAISE whatever it wants — those notices never reach SSE subscribers.

mermaid
flowchart LR
+    A["Procedure A<br/>(@sse)"] -->|RAISE| BC[("Global Broadcaster<br/>(process-wide)")]
+    B["Procedure B<br/>(@sse)"] -->|RAISE| BC
+    X["Procedure X<br/>(no @sse)"] -. RAISE not broadcast .-> Drop((("✗")))
+    BC --> S1["Subscriber on<br/>/api/a/info"]
+    BC --> S2["Subscriber on<br/>/api/b/info"]
+    BC --> S3["Subscriber on<br/>/api/c/info"]

There is one process-wide broadcaster. Every connected EventSource reads from the same stream regardless of which /info URL it opened — the URL is just an entry point, not a topic name. Once a connection is established, the path it came in through is no longer used for routing.

Per-event filtering decides which subscribers actually receive each event:

Common pitfall

The URL is not a topic. Subscribers on /api/foo/info and /api/bar/info do not see different streams — they see the same stream. If you want events from procedure B to reach clients connected to procedure A's URL, both procedures must have @sse: A so clients can connect, B so its RAISEs broadcast. See the cross-procedure pattern below.

Syntax

code
@sse
+@sse <path>
+@sse <path> on <level>

level: info, notice, warning

SSE Path Construction

The SSE endpoint path is constructed by appending the SSE path segment to the original endpoint path.

When Path is Omitted

When the path is omitted (@sse without arguments), the SSE path segment defaults to the notice level name in lowercase:

LevelSSE Path Segment
INFO (default)info
NOTICEnotice
WARNINGwarning

Example: If your endpoint path is /api/my-function and you use @sse without arguments, the SSE endpoint will be at /api/my-function/info (since INFO is the default level).

When Custom Path is Specified

When you specify a custom path (@sse my_events), that path segment is appended to the endpoint path.

Example: If your endpoint path is /api/my-function and you use @sse my_events, the SSE endpoint will be at /api/my-function/my_events.

Default Level

The default notice level is INFO. This can be changed globally via the DefaultServerSentEventsEventNoticeLevel configuration setting.

Level Filtering

Important

SSE events are sent only for the exact level specified, not for "this level and above".

When you set the level to NOTICE, only RAISE NOTICE statements will generate SSE events. RAISE INFO and RAISE WARNING statements will not generate SSE events for that endpoint.

Configured LevelRAISE INFORAISE NOTICERAISE WARNING
INFOSentNot sentNot sent
NOTICENot sentSentNot sent
WARNINGNot sentNot sentSent

If you need events from multiple levels, create separate SSE endpoints for each level.

Examples

Basic SSE Endpoint (function)

sql
sql
create function long_running_process(_id int)
+returns void
+language plpgsql
+as $$
+begin
+  raise info 'Starting process...';
+  -- do work
+  raise info 'Progress: 50%%';
+  -- more work
+  raise info 'Complete!';
+end;
+$$;
+
+comment on function long_running_process(int) is
+'HTTP POST
+@sse events';

If the endpoint is at /api/long-running-process, the SSE endpoint will be at /api/long-running-process/events. It receives RAISE INFO messages (the default level).

Basic SSE Endpoint (SQL file)

The same behavior, expressed as a SQL file:

sql
sql
/*
+HTTP POST
+@sse events
+@param $1 _id int
+@void
+*/
+do $$
+begin
+    raise info 'Starting process...';
+    -- do work
+    raise info 'Progress: 50%%';
+    -- more work
+    raise info 'Complete!';
+end;
+$$;

For files placed under the configured Path with the default CommentsMode, the leading comment block carries the same annotations as a function comment.

With Notice Level

sql
sql
comment on function background_task() is
+'HTTP POST
+@sse updates on notice';

If the endpoint is at /api/background-task, the SSE endpoint will be at /api/background-task/updates. It receives only RAISE NOTICE messages.

Warning Level Only

sql
sql
comment on function critical_job() is
+'HTTP POST
+@sse alerts on warning';

If the endpoint is at /api/critical-job, the SSE endpoint will be at /api/critical-job/alerts. It receives only RAISE WARNING messages.

Using Default Path (Level Name)

sql
sql
comment on function my_process() is
+'HTTP POST
+@sse';

If the endpoint is at /api/my-process, the SSE endpoint will be at /api/my-process/info (default path segment from the default INFO level).

sql
sql
comment on function my_process() is
+'HTTP POST
+@sse_events_level notice
+@sse';

If the endpoint is at /api/my-process, the SSE endpoint will be at /api/my-process/notice (path segment derived from the configured NOTICE level).

Cross-procedure pattern

The single-procedure case (one procedure both broadcasts and exposes the URL) is straightforward. But sometimes the procedure that triggers an event isn't the one that should be the client subscription URL. Common reasons:

The pattern: split publish from subscribe across two procedures, each with @sse. Annotate the trigger so its RAISEs broadcast; annotate a no-op procedure so its URL is the stable client entry point. Use RAISE ... USING HINT inside the trigger to scope events per user.

mermaid
flowchart LR
+    Client["Browser<br/>EventSource"] -->|opens connection| SubURL["/api/user-events-subscribe/info"]
+    SubURL -.->|registers via @sse| SubProc["user_events_subscribe<br/>(no-op, @sse)"]
+    Manager["Manager<br/>browser"] -->|POST| EmitURL["/api/update-user-roles"]
+    EmitURL --> EmitProc["update_user_roles<br/>(@sse)"]
+    EmitProc -->|RAISE INFO<br/>using hint| BC[("Global<br/>Broadcaster")]
+    BC -->|filtered by hint| Client

Function form

sql
sql
-- Subscribe URL: a no-op procedure whose @sse only registers the URL.
+-- Annotated 'authorize' so any authenticated user can connect.
+create procedure user_events_subscribe()
+language plpgsql as $$ begin perform 1; end; $$;
+
+comment on procedure user_events_subscribe() is '
+HTTP GET
+@authorize
+@sse
+@sse_scope authorize';
+
+-- Emitter: the procedure that actually causes events. @sse is required
+-- here too — without it, the RAISE never reaches the broadcaster.
+create procedure update_user_roles(_target_user_id int, _roles text[])
+language plpgsql as $$
+begin
+    -- ... do the role update ...
+    raise info 'roles updated'
+        using hint = format('authorize %s', _target_user_id);
+end;
+$$;
+
+comment on procedure update_user_roles(int, text[]) is '
+HTTP POST
+@authorize manager
+@sse
+@sse_scope authorize';

SQL file form

The same shape, expressed as two files. @sse and @sse_scope work identically; the no-op subscribe file is just a body that does nothing.

sql
sql
-- file: sql/user-events-subscribe.sql
+/*
+HTTP GET
+@authorize
+@sse
+@sse_scope authorize
+@void
+*/
+select 1;
sql
sql
-- file: sql/update-user-roles.sql
+/*
+HTTP POST
+@authorize manager
+@sse
+@sse_scope authorize
+@param $1 _target_user_id int
+@param $2 _roles text[]
+@void
+*/
+do $$
+declare
+    _target_user_id int = $1;
+    _roles text[]    = $2;
+begin
+    -- ... do the role update ...
+    raise info 'roles updated'
+        using hint = format('authorize %s', _target_user_id);
+end;
+$$;

What the client does

ts
ts
const eventSource = new EventSource('/api/user-events-subscribe/info');
+
+eventSource.onmessage = () => {
+    // ... handle the event ...
+};

Clients open EventSource once against /api/user-events-subscribe/info. When update_user_roles runs, its RAISE flows through the global broadcaster, every subscriber receives it, and the per-event hint (authorize <target_user_id>) ensures only the affected user's connection writes the data line. The fact that the event came from a different URL than the one the client subscribed to is invisible to both sides — they share the broadcaster.

The @sse on update_user_roles is what enables broadcasting; the @sse on user_events_subscribe is what gives clients a stable, semantically meaningful URL to open. They serve different purposes despite using the same annotation.

Behavior

@sse affects the procedure on two sides — its execution and its URL. Each side is independent, even though one annotation enables both.

On the publisher side

What @sse does to the procedure's execution:

On the subscriber side

What @sse does to the procedure's URL:

Because the URL is a connection point and not a topic, subscribers on different @sse URLs see the same stream. Use the URL primarily to give clients a stable, meaningful connection address and to scope which roles can subscribe (via the procedure's regular authorization).

See Also

`,80)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_sse.md.VtYFy1W4.lean.js b/assets/annotations_sse.md.VtYFy1W4.lean.js new file mode 100644 index 000000000..e66810d1d --- /dev/null +++ b/assets/annotations_sse.md.VtYFy1W4.lean.js @@ -0,0 +1 @@ +import{_ as i,c as e,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SSE Annotation","titleTemplate":"NpgsqlRest","description":"Enable Server-Sent Events streaming for PostgreSQL REST APIs. Create real-time endpoints with SSE for live data updates.","frontmatter":{"outline":[2,3],"title":"SSE Annotation","titleTemplate":"NpgsqlRest","description":"Enable Server-Sent Events streaming for PostgreSQL REST APIs. Create real-time endpoints with SSE for live data updates.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse, server sent events, real-time api, streaming endpoint, postgresql streaming, live updates"}],["meta",{"property":"og:title","content":"NpgsqlRest SSE Annotation"}],["meta",{"property":"og:description","content":"Enable Server-Sent Events streaming for real-time data updates from PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/sse.md","filePath":"annotations/sse.md"}'),t={name:"annotations/sse.md"};function l(p,s,r,h,o,c){return a(),e("div",null,s[0]||(s[0]=[n("",80)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_table-format.md.BKXSdBmi.js b/assets/annotations_table-format.md.BKXSdBmi.js new file mode 100644 index 000000000..2fa961c4c --- /dev/null +++ b/assets/annotations_table-format.md.BKXSdBmi.js @@ -0,0 +1,43 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TABLE_FORMAT Annotation","titleTemplate":"NpgsqlRest","description":"Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON. Per-endpoint table format control with dynamic placeholders.","frontmatter":{"outline":[2,3],"title":"TABLE_FORMAT Annotation","titleTemplate":"NpgsqlRest","description":"Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON. Per-endpoint table format control with dynamic placeholders.","head":[["meta",{"name":"keywords","content":"npgsqlrest table format, html table rendering, excel download api, spreadsheet export, table_format annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest TABLE_FORMAT Annotation"}],["meta",{"property":"og:description","content":"Render function results as HTML tables or Excel downloads with per-endpoint annotation control."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/table-format.md","filePath":"annotations/table-format.md"}'),t={name:"annotations/table-format.md"};function l(p,s,r,h,o,c){return i(),e("div",null,s[0]||(s[0]=[n(`

TABLE_FORMAT

Control how function results (from routines returning SETOF or TABLE) are rendered. Instead of JSON, results can be rendered as HTML tables or Excel spreadsheet downloads.

Applies to Set-Returning Functions Only

Table format rendering only applies to routines that return SETOF or TABLE results. Scalar-returning functions are not affected.

Requires Configuration

Table format rendering must be enabled in the Table Format Options configuration (TableFormatOptions.Enabled = true).

Syntax

code
@table_format = <format>
+@excel_file_name = <filename>
+@excel_sheet = <sheet_name>

All parameters support dynamic placeholders using the {param_name} format.

Parameters

ParameterDescription
table_formatSets the table format renderer for the endpoint. Values: html (render as HTML table), excel (render as .xlsx download). If the value is not a recognized format, a warning is logged and the endpoint falls back to the default JSON response.
excel_file_nameSets the download filename for Excel table format output. Only applies when table_format is excel. If omitted, defaults to the routine name.
excel_sheetSets the worksheet name for Excel table format output. Only applies when table_format is excel. If omitted, defaults to the routine name (max 31 characters).

Examples

Static HTML Table

sql
sql
create function get_report()
+returns table (id int, name text, amount numeric)
+language sql
+begin atomic;
+  select * from reports;
+end;
+
+comment on function get_report() is '
+HTTP GET
+@table_format = html
+';

Equivalent as a SQL file endpoint (sql/get-report.sql):

sql
sql
/*
+HTTP GET
+@table_format = html
+*/
+select id, name, amount from reports;

Static Excel Download

sql
sql
comment on function get_report() is '
+HTTP GET
+@table_format = excel
+@excel_file_name = monthly_report.xlsx
+@excel_sheet = Report Data
+';

Dynamic Format Selection

Use function parameters as dynamic placeholders to let the caller choose the output format:

sql
sql
create function get_data(
+    _format text,
+    _excel_file_name text = null,
+    _excel_sheet text = null
+)
+returns table (
+    int_val int,
+    text_val text,
+    date_val date
+)
+language sql
+begin atomic;
+  select * from data;
+end;
+
+comment on function get_data(text, text, text) is '
+HTTP GET
+@table_format = {_format}
+@excel_file_name = {_excel_file_name}
+@excel_sheet = {_excel_sheet}
+@tsclient_url_only = true
+';

When called with ?format=html, renders an HTML table. When called with ?format=excel&excelFileName=report.xlsx, returns an Excel download.

Use with tsclient_url_only

Table format endpoints are typically consumed via browser navigation (opening a URL directly), not via fetch. Use @tsclient_url_only = true to generate only the URL builder in the TypeScript client.

See Also

`,27)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_table-format.md.BKXSdBmi.lean.js b/assets/annotations_table-format.md.BKXSdBmi.lean.js new file mode 100644 index 000000000..a1a31cdae --- /dev/null +++ b/assets/annotations_table-format.md.BKXSdBmi.lean.js @@ -0,0 +1 @@ +import{_ as a,c as e,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TABLE_FORMAT Annotation","titleTemplate":"NpgsqlRest","description":"Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON. Per-endpoint table format control with dynamic placeholders.","frontmatter":{"outline":[2,3],"title":"TABLE_FORMAT Annotation","titleTemplate":"NpgsqlRest","description":"Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON. Per-endpoint table format control with dynamic placeholders.","head":[["meta",{"name":"keywords","content":"npgsqlrest table format, html table rendering, excel download api, spreadsheet export, table_format annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest TABLE_FORMAT Annotation"}],["meta",{"property":"og:description","content":"Render function results as HTML tables or Excel downloads with per-endpoint annotation control."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/table-format.md","filePath":"annotations/table-format.md"}'),t={name:"annotations/table-format.md"};function l(p,s,r,h,o,c){return i(),e("div",null,s[0]||(s[0]=[n("",27)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/annotations_tags.md.DmTy6McN.js b/assets/annotations_tags.md.DmTy6McN.js new file mode 100644 index 000000000..181c482b0 --- /dev/null +++ b/assets/annotations_tags.md.DmTy6McN.js @@ -0,0 +1,4 @@ +import{_ as t,c as a,o,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"TAGS Annotation","titleTemplate":"NpgsqlRest","description":"Apply annotations conditionally based on routine volatility tags.","frontmatter":{"outline":[2,3],"title":"TAGS Annotation","titleTemplate":"NpgsqlRest","description":"Apply annotations conditionally based on routine volatility tags.","head":[["meta",{"name":"keywords","content":"npgsqlrest tags, conditional annotations, for keyword, volatility scoping"}],["meta",{"property":"og:title","content":"NpgsqlRest TAGS Annotation"}],["meta",{"property":"og:description","content":"Apply annotations conditionally based on routine volatility tags."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/tags.md","filePath":"annotations/tags.md"}'),s={name:"annotations/tags.md"};function i(l,e,d,c,r,p){return o(),a("div",null,e[0]||(e[0]=[n(`

TAGS

Also known as

for, tags, tag (no @ prefix needed; @for and @tags also work)

Apply subsequent annotations only when the routine matches a specific volatility or routine-type tag.

Rarely needed

Most projects never use this. Reach for it only when a single comment needs to behave differently depending on the function's volatility — and even then, putting the annotations directly on the specific function is usually clearer.

Available tags

RoutineSource assigns these auto-tags to each function or procedure:

TagMatches
functionPostgreSQL functions
procedurePostgreSQL procedures
volatileFunctions declared VOLATILE (default)
stableFunctions declared STABLE
immutableFunctions declared IMMUTABLE
otherProcedures (volatility doesn't apply)

That's the complete list. Custom tags are not supported, and SQL file endpoints have no auto-tags — for has no effect on them.

Syntax

code
for <tag1>, <tag2>, ...

Annotations following a for line apply only when the routine matches at least one of the listed tags. The scope ends at the next for line or at the end of the comment.

Example: cache only when immutable

sql
sql
comment on function calculate_hash(_data text) is '
+HTTP GET
+for immutable
+@cached';

If the function is later changed to STABLE or VOLATILE, the @cached annotation no longer applies — no other comment changes needed. This is the one pattern where for carries its weight.

Behavior

`,18)]))}const m=t(s,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_tags.md.DmTy6McN.lean.js b/assets/annotations_tags.md.DmTy6McN.lean.js new file mode 100644 index 000000000..46361977f --- /dev/null +++ b/assets/annotations_tags.md.DmTy6McN.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"TAGS Annotation","titleTemplate":"NpgsqlRest","description":"Apply annotations conditionally based on routine volatility tags.","frontmatter":{"outline":[2,3],"title":"TAGS Annotation","titleTemplate":"NpgsqlRest","description":"Apply annotations conditionally based on routine volatility tags.","head":[["meta",{"name":"keywords","content":"npgsqlrest tags, conditional annotations, for keyword, volatility scoping"}],["meta",{"property":"og:title","content":"NpgsqlRest TAGS Annotation"}],["meta",{"property":"og:description","content":"Apply annotations conditionally based on routine volatility tags."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/tags.md","filePath":"annotations/tags.md"}'),s={name:"annotations/tags.md"};function i(l,e,d,c,r,p){return o(),a("div",null,e[0]||(e[0]=[n("",18)]))}const m=t(s,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/annotations_test-claim.md.B8Axvirh.js b/assets/annotations_test-claim.md.B8Axvirh.js new file mode 100644 index 000000000..d6060eed5 --- /dev/null +++ b/assets/annotations_test-claim.md.B8Axvirh.js @@ -0,0 +1,26 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse(`{"title":"TEST @claim Directive","titleTemplate":"NpgsqlRest","description":"Set the acting principal's claims for an in-process endpoint call inside a SQL test file HTTP block. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @claim Directive","titleTemplate":"NpgsqlRest","description":"Set the acting principal's claims for an in-process endpoint call inside a SQL test file HTTP block. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test claim, test authentication, claims principal, authorized endpoint testing"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @claim Directive"}],["meta",{"property":"og:description","content":"Set the acting principal's claims for an in-process endpoint call in a SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-claim.md","filePath":"annotations/test-claim.md"}`),t={name:"annotations/test-claim.md"};function l(p,s,r,c,h,o){return e(),a("div",null,s[0]||(s[0]=[n(`

TEST @claim

Test files only

This directive applies only inside HTTP blocks of test files run by the SQL test runner (npgsqlrest --test).

Add a claim to the acting principal of an in-process endpoint call. Placed inside an HTTP block, after the request line and before the body:

sql
sql
/*
+GET /api/get-users
+# @claim user_id=42
+# @claim roles=admin
+# @claim roles=auditor
+*/
+select status = 200, 'authorized call succeeds' from _response;

Semantics

Why not call the login endpoint?

@login/@logout endpoints are rejected in test mode (they manipulate real authentication schemes that don't exist in-process). Inject the principal directly instead — it is both faster and lets a test act as any user:

sql
sql
/*
+POST /api/admin/delete-user
+Content-Type: application/json
+# @claim user_id=1
+# @claim roles=admin
+
+{"id": 42}
+*/
+select status = 200, 'admin can delete' from _response;
+
+/*
+POST /api/admin/delete-user
+Content-Type: application/json
+# @claim user_id=2
+# @claim roles=viewer
+
+{"id": 42}
+*/
+-- second block → second response table (_response_1, _response_2)
+select status = 403, 'viewer cannot delete' from _response_2;
`,11)]))}const m=i(t,[["render",l]]);export{d as __pageData,m as default}; diff --git a/assets/annotations_test-claim.md.B8Axvirh.lean.js b/assets/annotations_test-claim.md.B8Axvirh.lean.js new file mode 100644 index 000000000..e56461b72 --- /dev/null +++ b/assets/annotations_test-claim.md.B8Axvirh.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse(`{"title":"TEST @claim Directive","titleTemplate":"NpgsqlRest","description":"Set the acting principal's claims for an in-process endpoint call inside a SQL test file HTTP block. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @claim Directive","titleTemplate":"NpgsqlRest","description":"Set the acting principal's claims for an in-process endpoint call inside a SQL test file HTTP block. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test claim, test authentication, claims principal, authorized endpoint testing"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @claim Directive"}],["meta",{"property":"og:description","content":"Set the acting principal's claims for an in-process endpoint call in a SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-claim.md","filePath":"annotations/test-claim.md"}`),t={name:"annotations/test-claim.md"};function l(p,s,r,c,h,o){return e(),a("div",null,s[0]||(s[0]=[n("",11)]))}const m=i(t,[["render",l]]);export{d as __pageData,m as default}; diff --git a/assets/annotations_test-connection.md.CBXd7WXg.js b/assets/annotations_test-connection.md.CBXd7WXg.js new file mode 100644 index 000000000..6c2a950db --- /dev/null +++ b/assets/annotations_test-connection.md.CBXd7WXg.js @@ -0,0 +1,19 @@ +import{_ as n,c as e,o as t,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @connection Annotation","titleTemplate":"NpgsqlRest","description":"Run an individual SQL test file on a different named connection. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @connection Annotation","titleTemplate":"NpgsqlRest","description":"Run an individual SQL test file on a different named connection. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test connection, per-file connection, test isolation, isolated test database"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @connection Annotation"}],["meta",{"property":"og:description","content":"Run an individual SQL test file on a different named connection."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-connection.md","filePath":"annotations/test-connection.md"}'),a={name:"annotations/test-connection.md"};function l(o,s,p,r,c,h){return t(),e("div",null,s[0]||(s[0]=[i(`

TEST @connection

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It is distinct from the endpoint CONNECTION annotation, which selects a connection for a routine endpoint.

Run this test file on a named ConnectionStrings entry instead of the test runner's default connection (TestRunner.ConnectionName or the app's main connection).

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @connection Name

The whole file — its SQL statements and the endpoints invoked by its HTTP blocks — runs on a non-pooled connection built from that entry. This is the key to perfect per-test isolation: point the file at a database that its own @setup step just created.

Example

Config:

json
json
{
+  "ConnectionStrings": {
+    "Admin": "Host=localhost;Database=postgres;...",
+    "Test": "Host=localhost;Database=app_test_{rnd5};...",
+    "Isolated": "Host=localhost;Database=app_iso_{rnd5_1};..."
+  }
+}

Test file — gets its own clone, invisible to every other test:

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+/*
+POST /api/create-user
+Content-Type: application/json
+
+{"name": "Ada", "email": "ada@example.com"}
+*/
+select body::jsonb ->> 'id' = '4',
+       'sequence ids are deterministic in a fresh clone'
+from _response;

Sequences are the classic motivation: nextval() is non-transactional (it sticks even through rollback), so on a shared test database a generated id depends on which other tests ran first. In a private clone the id is deterministic.

Notes

`,17)]))}const u=n(a,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-connection.md.CBXd7WXg.lean.js b/assets/annotations_test-connection.md.CBXd7WXg.lean.js new file mode 100644 index 000000000..11d89ea4b --- /dev/null +++ b/assets/annotations_test-connection.md.CBXd7WXg.lean.js @@ -0,0 +1 @@ +import{_ as n,c as e,o as t,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @connection Annotation","titleTemplate":"NpgsqlRest","description":"Run an individual SQL test file on a different named connection. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @connection Annotation","titleTemplate":"NpgsqlRest","description":"Run an individual SQL test file on a different named connection. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test connection, per-file connection, test isolation, isolated test database"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @connection Annotation"}],["meta",{"property":"og:description","content":"Run an individual SQL test file on a different named connection."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-connection.md","filePath":"annotations/test-connection.md"}'),a={name:"annotations/test-connection.md"};function l(o,s,p,r,c,h){return t(),e("div",null,s[0]||(s[0]=[i("",17)]))}const u=n(a,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-response.md.BIQYW_7j.js b/assets/annotations_test-response.md.BIQYW_7j.js new file mode 100644 index 000000000..37abc2bff --- /dev/null +++ b/assets/annotations_test-response.md.BIQYW_7j.js @@ -0,0 +1,9 @@ +import{_ as e,c as t,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse(`{"title":"TEST @response Directive","titleTemplate":"NpgsqlRest","description":"Name the temp table that captures an HTTP block's response in a SQL test file. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @response Directive","titleTemplate":"NpgsqlRest","description":"Name the temp table that captures an HTTP block's response in a SQL test file. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test response, response temp table, response capture, sql test assertions"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @response Directive"}],["meta",{"property":"og:description","content":"Name the temp table that captures an HTTP block's response in a SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-response.md","filePath":"annotations/test-response.md"}`),n={name:"annotations/test-response.md"};function l(o,s,r,p,h,c){return a(),t("div",null,s[0]||(s[0]=[i(`

TEST @response

Test files only

This directive applies only inside HTTP blocks of test files run by the SQL test runner (npgsqlrest --test).

Capture this HTTP block's response into a temp table with a custom name, instead of the default.

Default naming

Without the directive, the response table name comes from TestRunner.ResponseTempTable:

Includes participate in the numbering: HTTP blocks spliced in by \\i/\\ir count as if pasted.

Syntax

sql
sql
/*
+POST /api/login
+Content-Type: application/json
+# @response login_result
+
+{"email": "ada@example.com", "password": "secret"}
+*/
+select (select status from login_result) = 200, 'login succeeds';
+select (select body::jsonb ->> 'role' from login_result) = 'admin', 'role returned';

The named table has the same columns as the default (status int, body text, content_type text, headers jsonb, is_success boolean — configurable).

Notes

`,14)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-response.md.BIQYW_7j.lean.js b/assets/annotations_test-response.md.BIQYW_7j.lean.js new file mode 100644 index 000000000..dc924c783 --- /dev/null +++ b/assets/annotations_test-response.md.BIQYW_7j.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse(`{"title":"TEST @response Directive","titleTemplate":"NpgsqlRest","description":"Name the temp table that captures an HTTP block's response in a SQL test file. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @response Directive","titleTemplate":"NpgsqlRest","description":"Name the temp table that captures an HTTP block's response in a SQL test file. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test response, response temp table, response capture, sql test assertions"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @response Directive"}],["meta",{"property":"og:description","content":"Name the temp table that captures an HTTP block's response in a SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-response.md","filePath":"annotations/test-response.md"}`),n={name:"annotations/test-response.md"};function l(o,s,r,p,h,c){return a(),t("div",null,s[0]||(s[0]=[i("",14)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-setup.md.CEjyi1f2.js b/assets/annotations_test-setup.md.CEjyi1f2.js new file mode 100644 index 000000000..2a8479b3f --- /dev/null +++ b/assets/annotations_test-setup.md.CEjyi1f2.js @@ -0,0 +1,24 @@ +import{_ as i,c as e,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @setup Annotation","titleTemplate":"NpgsqlRest","description":"Run named setup steps before an individual SQL test file. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @setup Annotation","titleTemplate":"NpgsqlRest","description":"Run named setup steps before an individual SQL test file. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test setup, per-file setup, test fixtures, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @setup Annotation"}],["meta",{"property":"og:description","content":"Run named setup steps before an individual SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-setup.md","filePath":"annotations/test-setup.md"}'),t={name:"annotations/test-setup.md"};function l(p,s,r,o,h,d){return a(),e("div",null,s[0]||(s[0]=[n(`

TEST @setup

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It has no meaning in endpoint SQL files or routine comments.

Run one or more named steps (from the TestRunner.Steps registry) before this test file executes.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @setup StepName [StepName ...]

Example

Config:

json
json
{
+  "TestRunner": {
+    "Steps": {
+      "CreateIsolatedDb": {
+        "Sql": "create database app_iso_{rnd5_1} template app_template_{rnd5}",
+        "ConnectionName": "Admin"
+      },
+      "DropIsolatedDb": {
+        "Sql": "drop database if exists app_iso_{rnd5_1} with (force)",
+        "ConnectionName": "Admin"
+      }
+    }
+  }
+}

Test file:

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+/*
+POST /api/create-user
+Content-Type: application/json
+
+{"name": "Ada"}
+*/
+select status = 200, 'user created in the isolated clone' from _response;

The step runs once, immediately before this file (after the run-level Setup). Combined with -- @teardown and -- @connection, this gives a single test file its own private database.

Header semantics

The header ends at the first SQL statement or HTTP block. An included file (\\i/\\ir) that contains only comments (an annotation profile) continues the header — its annotations count as if written in-place — so a shared profile can carry @setup/@teardown/@connection/@tag for many test files.

Watch the prose

Everything after the step names on the line is treated as more step names. Write explanatory text on its own comment line, not after the names.

`,18)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-setup.md.CEjyi1f2.lean.js b/assets/annotations_test-setup.md.CEjyi1f2.lean.js new file mode 100644 index 000000000..5204fd93c --- /dev/null +++ b/assets/annotations_test-setup.md.CEjyi1f2.lean.js @@ -0,0 +1 @@ +import{_ as i,c as e,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @setup Annotation","titleTemplate":"NpgsqlRest","description":"Run named setup steps before an individual SQL test file. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @setup Annotation","titleTemplate":"NpgsqlRest","description":"Run named setup steps before an individual SQL test file. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test setup, per-file setup, test fixtures, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @setup Annotation"}],["meta",{"property":"og:description","content":"Run named setup steps before an individual SQL test file."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-setup.md","filePath":"annotations/test-setup.md"}'),t={name:"annotations/test-setup.md"};function l(p,s,r,o,h,d){return a(),e("div",null,s[0]||(s[0]=[n("",18)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-tag.md.CzXuyAz7.js b/assets/annotations_test-tag.md.CzXuyAz7.js new file mode 100644 index 000000000..84f23f028 --- /dev/null +++ b/assets/annotations_test-tag.md.CzXuyAz7.js @@ -0,0 +1,21 @@ +import{_ as a,c as i,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @tag Annotation","titleTemplate":"NpgsqlRest","description":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @tag Annotation","titleTemplate":"NpgsqlRest","description":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test tag, tag filtering, smoke tests, test suites, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @tag Annotation"}],["meta",{"property":"og:description","content":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-tag.md","filePath":"annotations/test-tag.md"}'),n={name:"annotations/test-tag.md"};function l(p,s,r,h,o,c){return e(),i("div",null,s[0]||(s[0]=[t(`

TEST @tag

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It is distinct from the endpoint TAGS annotation, which scopes routine annotations by volatility.

Declare tags on a test file, so runs can be narrowed with TestRunner.Tag / ExcludeTag.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @tag name [name ...]

Example

sql
sql
-- @tag auth, smoke
+-- Test: GET /api/get-users requires authentication.
+
+/*
+GET /api/get-users
+*/
+select status = 401, 'anonymous request is rejected' from _response;

Selective runs:

sh
sh
# only the smoke suite
+npgsqlrest ./config.json --test --testrunner:tag=smoke
+
+# everything except slow tests
+npgsqlrest ./config.json --test --testrunner:excludetag=slow
+
+# smoke AND auth files, but never slow ones (exclude wins)
+npgsqlrest ./config.json --test --testrunner:tag=smoke,auth --testrunner:excludetag=slow

Tags via a shared profile

Tags declared in an included annotation profile count as if written in the file — a shared \\ir include can tag a whole family of tests at once:

sql
sql
-- tests/shared/isolated_database.sql (an annotation profile: comments only)
+-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+-- @tag isolation, slow
sql
sql
-- a test file attaching the profile
+\\ir shared/isolated_database.sql
+
+select 1 = 1, 'runs isolated, tagged isolation+slow via the profile';
`,17)]))}const g=a(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/annotations_test-tag.md.CzXuyAz7.lean.js b/assets/annotations_test-tag.md.CzXuyAz7.lean.js new file mode 100644 index 000000000..807caa045 --- /dev/null +++ b/assets/annotations_test-tag.md.CzXuyAz7.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @tag Annotation","titleTemplate":"NpgsqlRest","description":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @tag Annotation","titleTemplate":"NpgsqlRest","description":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test tag, tag filtering, smoke tests, test suites, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @tag Annotation"}],["meta",{"property":"og:description","content":"Tag SQL test files for selective runs with Tag/ExcludeTag filtering."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-tag.md","filePath":"annotations/test-tag.md"}'),n={name:"annotations/test-tag.md"};function l(p,s,r,h,o,c){return e(),i("div",null,s[0]||(s[0]=[t("",17)]))}const g=a(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/annotations_test-teardown.md.DrTOzo2w.js b/assets/annotations_test-teardown.md.DrTOzo2w.js new file mode 100644 index 000000000..68a33a98c --- /dev/null +++ b/assets/annotations_test-teardown.md.DrTOzo2w.js @@ -0,0 +1,8 @@ +import{_ as s,c as t,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @teardown Annotation","titleTemplate":"NpgsqlRest","description":"Run named teardown steps after an individual SQL test file, always. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @teardown Annotation","titleTemplate":"NpgsqlRest","description":"Run named teardown steps after an individual SQL test file, always. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test teardown, per-file teardown, test cleanup, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @teardown Annotation"}],["meta",{"property":"og:description","content":"Run named teardown steps after an individual SQL test file, always."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-teardown.md","filePath":"annotations/test-teardown.md"}'),n={name:"annotations/test-teardown.md"};function l(r,e,o,p,d,h){return a(),t("div",null,e[0]||(e[0]=[i(`

TEST @teardown

Test files only

This annotation applies only to test files run by the SQL test runner (npgsqlrest --test). It has no meaning in endpoint SQL files or routine comments.

Run one or more named steps (from the TestRunner.Steps registry) after this test filealways, best-effort, even when the file failed or errored.

Syntax

Placed in the file's header — the leading -- line comments before the first SQL statement or HTTP block:

sql
sql
-- @teardown StepName [StepName ...]

Example

sql
sql
-- @setup CreateIsolatedDb
+-- @teardown DropIsolatedDb
+-- @connection Isolated
+
+begin;
+insert into users (name) values ('fixture');
+select count(*) = 1, 'fixture inserted in the private clone' from users;
+rollback;

Even if the assertion fails — or the file errors halfway — DropIsolatedDb still runs, so the per-file database never leaks.

Ordering

For one test file the lifecycle is:

  1. per-file @setup steps (in written order)
  2. the file body, on its own non-pooled connection
  3. connection disposed
  4. per-file @teardown steps (in written order, always)

The run-level TestRunner.Setup/Teardown wrap the whole run outside of this.

`,16)]))}const u=s(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_test-teardown.md.DrTOzo2w.lean.js b/assets/annotations_test-teardown.md.DrTOzo2w.lean.js new file mode 100644 index 000000000..e791d3d5e --- /dev/null +++ b/assets/annotations_test-teardown.md.DrTOzo2w.lean.js @@ -0,0 +1 @@ +import{_ as s,c as t,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TEST @teardown Annotation","titleTemplate":"NpgsqlRest","description":"Run named teardown steps after an individual SQL test file, always. Test-runner (--test) test files only.","frontmatter":{"outline":[2,3],"title":"TEST @teardown Annotation","titleTemplate":"NpgsqlRest","description":"Run named teardown steps after an individual SQL test file, always. Test-runner (--test) test files only.","head":[["meta",{"name":"keywords","content":"npgsqlrest test teardown, per-file teardown, test cleanup, sql test runner annotations"}],["meta",{"property":"og:title","content":"NpgsqlRest TEST @teardown Annotation"}],["meta",{"property":"og:description","content":"Run named teardown steps after an individual SQL test file, always."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/test-teardown.md","filePath":"annotations/test-teardown.md"}'),n={name:"annotations/test-teardown.md"};function l(r,e,o,p,d,h){return a(),t("div",null,e[0]||(e[0]=[i("",16)]))}const u=s(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_tsclient.md.BNgVXSoY.js b/assets/annotations_tsclient.md.BNgVXSoY.js new file mode 100644 index 000000000..704dbf926 --- /dev/null +++ b/assets/annotations_tsclient.md.BNgVXSoY.js @@ -0,0 +1,55 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TSCLIENT Annotation","titleTemplate":"NpgsqlRest","description":"Control TypeScript client code generation per-endpoint. Disable generation, set module names, configure URL exports and response options.","frontmatter":{"outline":[2,3],"title":"TSCLIENT Annotation","titleTemplate":"NpgsqlRest","description":"Control TypeScript client code generation per-endpoint. Disable generation, set module names, configure URL exports and response options.","head":[["meta",{"name":"keywords","content":"npgsqlrest tsclient, typescript client annotation, codegen control, api client generation, per-endpoint typescript"}],["meta",{"property":"og:title","content":"NpgsqlRest TSCLIENT Annotation"}],["meta",{"property":"og:description","content":"Control TypeScript client code generation per-endpoint with custom parameter annotations."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/tsclient.md","filePath":"annotations/tsclient.md"}'),t={name:"annotations/tsclient.md"};function l(p,s,r,h,d,c){return n(),i("div",null,s[0]||(s[0]=[e(`

TSCLIENT

Control TypeScript client code generation for individual endpoints using custom parameter annotations.

Requires Configuration

TypeScript client generation must be enabled in the Code Generation configuration (ClientCodeGen.Enabled = true).

Syntax

code
@tsclient = <true|false>
+@tsclient_module = <module_name>
+@tsclient_events = <true|false>
+@tsclient_parse_url = <true|false>
+@tsclient_parse_request = <true|false>
+@tsclient_status_code = <true|false>
+@tsclient_export_url = <true|false>
+@tsclient_url_only = <true|false>

Parameters

ParameterDescription
tsclientSet to false, off, disabled, disable, or 0 to disable TypeScript client code generation for the endpoint.
tsclient_moduleSets a different module name for the generated TypeScript client file. Endpoints with the same module name are grouped into the same file.
tsclient_eventsEnable or disable SSE events parameter for endpoints with SSE events enabled.
tsclient_parse_urlEnable or disable parseUrl parameter in the generated function.
tsclient_parse_requestEnable or disable parseRequest parameter in the generated function.
tsclient_status_codeEnable or disable status code in the return value.
tsclient_export_urlWhen true, exports a URL constant for this endpoint regardless of the global ExportUrls setting.
tsclient_url_onlyWhen true, only the URL constant and request interface are exported — the fetch function and response type are skipped. Implies tsclient_export_url = true. Useful for endpoints consumed via browser navigation (e.g., table format downloads).

Examples

Disable Generation

Use @tsclient = false to skip client generation for endpoints that return binary data or are not useful in the TypeScript client:

sql
sql
create function get_image(_id int)
+returns bytea
+language sql
+begin atomic;
+  select data from images where id = _id;
+end;
+
+comment on function get_image(int) is '
+HTTP GET
+@tsclient = false
+';

Equivalent as a SQL file endpoint (sql/get-image.sql):

sql
sql
/*
+HTTP GET
+@tsclient = false
+@param $1 id
+*/
+select data from images where id = $1;

URL-Only Export

Use @tsclient_url_only = true for endpoints consumed via browser navigation rather than fetch — such as table format downloads or file exports:

sql
sql
create function get_data(
+    _format text,
+    _excel_file_name text = null,
+    _excel_sheet text = null
+)
+returns table (int_val int, text_val text, date_val date)
+language sql
+begin atomic;
+  select * from data;
+end;
+
+comment on function get_data(text, text, text) is '
+HTTP GET
+@table_format = {_format}
+@excel_file_name = {_excel_file_name}
+@excel_sheet = {_excel_sheet}
+@tsclient_url_only = true
+';

This generates only the URL builder and request interface:

typescript
typescript
export const getDataUrl = (request: IGetDataRequest) =>
+    baseUrl + "/api/get-data" + parseQuery(request);
+
+interface IGetDataRequest {
+    format: string | null;
+    excelFileName?: string | null;
+    excelSheet?: string | null;
+}

Custom Module

Use @tsclient_module to group endpoints from different schemas into the same generated file:

sql
sql
comment on function public.get_users() is '
+HTTP GET
+@tsclient_module = admin
+';
+
+comment on function auth.get_roles() is '
+HTTP GET
+@tsclient_module = admin
+';

Both endpoints will be generated in the admin module file.

See Also

`,28)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_tsclient.md.BNgVXSoY.lean.js b/assets/annotations_tsclient.md.BNgVXSoY.lean.js new file mode 100644 index 000000000..6919d0cae --- /dev/null +++ b/assets/annotations_tsclient.md.BNgVXSoY.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"TSCLIENT Annotation","titleTemplate":"NpgsqlRest","description":"Control TypeScript client code generation per-endpoint. Disable generation, set module names, configure URL exports and response options.","frontmatter":{"outline":[2,3],"title":"TSCLIENT Annotation","titleTemplate":"NpgsqlRest","description":"Control TypeScript client code generation per-endpoint. Disable generation, set module names, configure URL exports and response options.","head":[["meta",{"name":"keywords","content":"npgsqlrest tsclient, typescript client annotation, codegen control, api client generation, per-endpoint typescript"}],["meta",{"property":"og:title","content":"NpgsqlRest TSCLIENT Annotation"}],["meta",{"property":"og:description","content":"Control TypeScript client code generation per-endpoint with custom parameter annotations."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/tsclient.md","filePath":"annotations/tsclient.md"}'),t={name:"annotations/tsclient.md"};function l(p,s,r,h,d,c){return n(),i("div",null,s[0]||(s[0]=[e("",28)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_upload.md.BJPkIDDG.js b/assets/annotations_upload.md.BJPkIDDG.js new file mode 100644 index 000000000..6b58445ba --- /dev/null +++ b/assets/annotations_upload.md.BJPkIDDG.js @@ -0,0 +1,327 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"UPLOAD Annotation","titleTemplate":"NpgsqlRest","description":"Create file upload endpoints for PostgreSQL REST APIs. Handle multipart uploads with Large Objects, file system, CSV, or Excel handlers.","frontmatter":{"outline":[2,3],"title":"UPLOAD Annotation","titleTemplate":"NpgsqlRest","description":"Create file upload endpoints for PostgreSQL REST APIs. Handle multipart uploads with Large Objects, file system, CSV, or Excel handlers.","head":[["meta",{"name":"keywords","content":"npgsqlrest upload, file upload api, multipart upload, postgresql large objects, csv upload excel upload"}],["meta",{"property":"og:title","content":"NpgsqlRest UPLOAD Annotation"}],["meta",{"property":"og:description","content":"Create file upload endpoints with Large Objects, file system, CSV, or Excel handlers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/upload.md","filePath":"annotations/upload.md"}'),t={name:"annotations/upload.md"};function l(p,s,r,d,h,k){return n(),i("div",null,s[0]||(s[0]=[e(`

UPLOAD

Mark endpoint as a file upload handler.

Keywords

@upload, upload

Syntax

code
@upload
+@upload for <handler_type>

If no handler is specified (only upload annotation without for), then the default handler will be used. The default handler is large_object unless configured otherwise via DefaultUploadHandler setting.

Handler Types

There are 4 handler types available:

HandlerKeyDescription
Large Objectlarge_objectStores files using PostgreSQL Large Objects API (default)
File Systemfile_systemStores files on the server file system
CSVcsvParses CSV files and processes rows via PostgreSQL command
ExcelexcelParses Excel files and processes rows via PostgreSQL command

Shared Annotation Options

These options are available for all handler types:

OptionTypeDefaultDescription
stop_after_first_successboolfalseStop upload after first successful upload when multiple handlers are used. Subsequent files will have status Ignored.
included_mime_typesstringnullCSV string of MIME type patterns to include. Set to null to allow all.
excluded_mime_typesstringnullCSV string of MIME type patterns to exclude. Set to null to exclude none.
buffer_sizeintnullBuffer size in bytes for raw content uploads (large_object and file_system).
check_textboolfalseValidate file is a text file (not binary). Set to true to accept only text files.
check_imagebool/stringfalseValidate file is an image. Set to true to accept only images, or CSV of allowed types: jpg, png, gif, bmp, tiff, webp.
test_buffer_sizeint4096Buffer size in bytes when checking text files.
non_printable_thresholdint5Maximum non-printable characters allowed in test buffer to consider a valid text file.
check_formatboolfalseValidate the file format before processing. When true and validation fails, the fallback_handler is used if configured.
fallback_handlerstringnullHandler name to delegate to if format validation fails (e.g., large_object, file_system, csv, excel). When a handler's format validation fails and a fallback_handler is configured, processing is automatically delegated to the named handler.

Upload Metadata

All handlers return upload metadata as JSON with the following common properties:

PropertyTypeDescription
typestringHandler type used (large_object, file_system, csv, excel)
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the uploaded file
sizeintFile size in bytes
successboolWhether the upload succeeded
statusstringStatus message (e.g., Ok, InvalidMimeType)

Handler-specific properties:

HandlerPropertyTypeDescription
large_objectoidintPostgreSQL Large Object OID
file_systemfilePathstringPath where file was saved

Large Object Handler

Default handler that stores files using PostgreSQL Large Objects.

Basic Example

sql
sql
create function lo_simple_upload(
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function lo_simple_upload(json) is '
+@upload
+@param _meta is upload metadata
+';

Equivalent as a SQL file endpoint (sql/lo-simple-upload.sql):

sql
sql
/*
+HTTP POST
+@upload
+@param $1 meta is upload metadata
+*/
+select $1;

With Custom OID Parameter

You can specify a custom OID for the large object:

sql
sql
create function lo_custom_parameter_upload(
+    _oid bigint,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function lo_custom_parameter_upload(bigint, json) is '
+@upload for large_object
+@param _meta is upload metadata
+@oid = {_oid}
+';

Context Metadata

Upload metadata is also available via PostgreSQL context setting:

sql
sql
create function lo_simple_upload_context_metadata()
+returns json
+language plpgsql
+as
+$$
+begin
+    return current_setting('request.upload_metadata', true)::text;
+end;
+$$;
+
+comment on function lo_simple_upload_context_metadata() is '@upload';

Large Object Annotation Options

All shared options plus:

OptionDescription
oidCustom OID for the large object
large_object_included_mime_typesHandler-specific MIME types to include
large_object_excluded_mime_typesHandler-specific MIME types to exclude
large_object_buffer_sizeHandler-specific buffer size
large_object_oidHandler-specific OID (alias for oid)
large_object_check_textHandler-specific text check
large_object_check_imageHandler-specific image check
large_object_test_buffer_sizeHandler-specific test buffer size
large_object_non_printable_thresholdHandler-specific non-printable threshold

File System Handler

Stores files on the server file system.

Basic Example

sql
sql
create function fs_simple_upload(
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function fs_simple_upload(json) is '
+@upload for file_system
+@param _meta is upload metadata
+';

With Custom Parameters

Control the file path, name, and behavior:

sql
sql
create function fs_custom_parameter_upload(
+    _path text,
+    _file text,
+    _unique_name boolean,
+    _create_path boolean,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    return _meta;
+end;
+$$;
+
+comment on function fs_custom_parameter_upload(text, text, boolean, boolean, json) is '
+@upload for file_system
+@param _meta is upload metadata
+@path = {_path}
+@file = {_file}
+@unique_name = {_unique_name}
+@create_path = {_create_path}
+';

File System Annotation Options

All shared options plus:

OptionDescription
pathDirectory path for uploaded file
fileFile name to use
unique_nameGenerate unique file name (bool)
create_pathCreate directory if not exists (bool)
file_system_included_mime_typesHandler-specific MIME types to include
file_system_excluded_mime_typesHandler-specific MIME types to exclude
file_system_buffer_sizeHandler-specific buffer size
file_system_pathHandler-specific path (alias for path)
file_system_fileHandler-specific file name (alias for file)
file_system_unique_nameHandler-specific unique name setting
file_system_create_pathHandler-specific create path setting
file_system_check_textHandler-specific text check
file_system_check_imageHandler-specific image check
file_system_test_buffer_sizeHandler-specific test buffer size
file_system_non_printable_thresholdHandler-specific non-printable threshold

MIME Type Filtering

sql
sql
comment on function fs_upload_include_mime_type(json) is '
+@upload for file_system
+@param _meta is upload metadata
+@path = ./test
+@file = mime_type.csv
+@included_mime_types = image/*, application/*
+';

CSV Handler

Parses CSV files and processes each row via a PostgreSQL command.

Row Command Function Signature

The row command function receives up to 4 parameters:

sql
sql
create function my_csv_row_processor(
+    _index int,           -- $1: Row index (1-based)
+    _row text[],          -- $2: Parsed row values as text array
+    _prev_result any,     -- $3: Result of previous row command (for chaining)
+    _meta json            -- $4: Row metadata JSON
+)
+returns any               -- Return value passed to next row as $3

Row Command Parameters

ParameterTypeDescription
$1intRow index (1-based, includes header row)
$2text[]Parsed row values as text array (e.g., _row[1], _row[2], etc.)
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values).

Row Metadata Structure ($4)

The metadata JSON passed to each row command contains:

json
json
{
+  "type": "csv",
+  "fileName": "data.csv",
+  "contentType": "text/csv",
+  "size": 1234,
+  "claims": {                    // Only if RowCommandUserClaimsKey is set (default: "claims")
+    "user_id": "1",
+    "user_name": "alice",
+    "name_identifier": "1"
+  }
+}
PropertyTypeDescription
typestringHandler type ("csv")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Unlike Excel, CSV row metadata does NOT include rowIndex. Use the $1 parameter for the row index.

Upload Function Metadata (_meta parameter)

The main upload function receives metadata as a JSON array with one element per uploaded file:

json
json
[
+  {
+    "type": "csv",
+    "fileName": "data.csv",
+    "contentType": "text/csv",
+    "size": 1234,
+    "success": true,
+    "status": "Ok",
+    "lastResult": 100
+  }
+]
PropertyTypeDescription
lastResultanyFinal return value from the last row command execution

Basic Example

sql
sql
-- Table for uploads
+create table csv_uploads (
+    id int primary key generated always as identity,
+    file_name text not null,
+    row_index int not null,
+    row_data text[] not null
+);
+
+-- Row command to process each CSV row
+create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (file_name, row_index, row_data)
+    values (_meta->>'fileName', _index, _row);
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;
+
+-- HTTP POST endpoint
+create function csv_upload(_meta json = null)
+returns json
+language sql
+begin atomic;
+    select _meta;
+end;
+
+comment on function csv_upload(json) is '
+@upload for csv
+@param _meta is upload metadata
+@row_command = select csv_upload_row($1,$2,$3,$4)
+';

Accessing User Claims in Row Command

With RowCommandUserClaimsKey configured (default: "claims"), user claims are available in the row metadata:

sql
sql
create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (user_id, file_name, row_index, row_data)
+    values (
+        (_meta->'claims'->>'user_id')::int,  -- Access user_id from claims
+        _meta->>'fileName',
+        _index,
+        _row
+    );
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

Using User Context Variables

With UseUserContext: true, user context variables are set before upload and accessible via current_setting():

sql
sql
create function csv_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into csv_uploads (user_id, file_name, row_index, row_data)
+    values (
+        current_setting('request.user_id')::int,  -- Access from context
+        _meta->>'fileName',
+        _index,
+        _row
+    );
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

Custom Delimiters

Support multiple delimiter characters:

sql
sql
comment on function csv_upload(json) is '
+@upload for csv
+@param _meta is upload metadata
+@delimiters = ,;
+@row_command = select csv_upload_row($1,$2,$3,$4)
+';

This will use comma (,) and semicolon (;) as delimiters. Use \\t for tab.

CSV Annotation Options

All shared options (except buffer_size, check_text, check_image) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
delimitersstring,Delimiter character(s)
check_formatboolfalseValidate file is text before processing
has_fields_enclosed_in_quotesbooltrueFields may be enclosed in quotes
set_white_space_to_nullbooltrueConvert whitespace-only values to NULL

Handler-specific prefixed aliases are also available (e.g., csv_row_command, csv_delimiters).

Excel Handler

Parses Excel files (.xlsx, .xls) and processes each row via a PostgreSQL command.

Row Command Function Signature

The row command function receives up to 4 parameters:

sql
sql
create function my_excel_row_processor(
+    _index int,           -- $1: Row index (1-based, non-empty rows only)
+    _row text[],          -- $2: Row values as text array (or json if row_is_json = true)
+    _prev_result any,     -- $3: Result of previous row command
+    _meta json            -- $4: Row metadata JSON (includes sheet name)
+)
+returns any               -- Return value passed to next row as $3

Row Command Parameters

ParameterTypeDescription
$1intRow index (1-based, only counts non-empty rows)
$2text[] or jsonRow values as text array, or JSON if row_is_json = true
$3anyResult of previous row command execution (see below)
$4jsonRow metadata JSON object (includes sheet info)

Row chaining with $3: The return value from each row command is passed to the next row as $3. For the first row, $3 is NULL. If the row command returns void (no return value), $3 will be NULL for the next row. This enables accumulating values across rows (e.g., counting rows, summing values). Note: When processing multiple sheets (all_sheets = true), $3 resets to NULL at the start of each sheet.

Row Metadata Structure ($4)

The metadata JSON passed to each row command contains:

json
json
{
+  "type": "excel",
+  "fileName": "data.xlsx",
+  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+  "size": 5678,
+  "sheet": "Sheet1",
+  "rowIndex": 5,
+  "claims": {                    // Only if RowCommandUserClaimsKey is set
+    "user_id": "1",
+    "user_name": "alice"
+  }
+}
PropertyTypeDescription
typestringHandler type ("excel")
fileNamestringOriginal uploaded file name
contentTypestringMIME type of the file
sizeintFile size in bytes
sheetstringCurrent sheet name being processed
rowIndexintExcel row index (1-based, includes empty rows)
claimsobjectUser claims (when RowCommandUserClaimsKey is configured)

Note: Excel row metadata includes rowIndex (actual Excel row number) and sheet name. The $1 parameter is a sequential counter for non-empty rows only, while rowIndex reflects the actual Excel row position.

Upload Function Metadata (_meta parameter)

The main upload function receives metadata as a JSON array. When all_sheets = true, there's one element per sheet:

json
json
[
+  {
+    "type": "excel",
+    "fileName": "data.xlsx",
+    "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+    "size": 5678,
+    "sheet": "Sheet1",
+    "success": true,
+    "rows": 99,
+    "result": 100
+  },
+  {
+    "type": "excel",
+    "fileName": "data.xlsx",
+    "contentType": "...",
+    "size": 5678,
+    "sheet": "Sheet2",
+    "success": true,
+    "rows": 50,
+    "result": 50
+  }
+]
PropertyTypeDescription
sheetstringSheet name
rowsintNumber of non-empty rows processed
resultanyFinal return value from the last row command for this sheet

Basic Example

sql
sql
-- Table for uploads
+create table excel_uploads (
+    id int primary key generated always as identity,
+    file_name text not null,
+    sheet_name text,
+    row_index int not null,
+    row_data text[] not null
+);
+
+-- Row command to process each Excel row
+create function excel_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into excel_uploads (file_name, sheet_name, row_index, row_data)
+    values (
+        _meta->>'fileName',
+        _meta->>'sheet',
+        _index,
+        coalesce(_row, '{}')
+    );
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;
+
+-- HTTP POST endpoint
+create function excel_upload(_meta json = null)
+returns json
+language sql
+begin atomic;
+    select _meta;
+end;
+
+comment on function excel_upload(json) is '
+@upload for excel
+@param _meta is upload metadata
+@all_sheets = true
+@row_command = select excel_upload_row($1,$2,$3,$4)
+';

Row Data as JSON

When row_is_json = true, row data is passed as JSON with Excel cell references as keys:

sql
sql
comment on function excel_upload(json) is '
+@upload for excel
+@param _meta is upload metadata
+@row_is_json = true
+@row_command = select excel_upload_row($1,$2,$3,$4)
+';

The $2 parameter becomes JSON like:

json
json
{"A1": "Name", "B1": "Value", "C1": 123}

Excel Annotation Options

All shared options (except buffer_size, check_text, check_image, test_buffer_size, non_printable_threshold) plus:

OptionTypeDefaultDescription
row_commandstring-PostgreSQL command to process each row (required)
sheet_namestringnullSpecific sheet name to process (first sheet if null)
all_sheetsboolfalseProcess all sheets in the workbook
time_formatstringHH:mm:ssFormat for time values
date_formatstringyyyy-MM-ddFormat for date values
datetime_formatstringyyyy-MM-dd HH:mm:ssFormat for datetime values
row_is_jsonboolfalsePass row data as JSON instead of text array

Handler-specific prefixed aliases are also available (e.g., excel_row_command, excel_all_sheets).

Error Handling and Rollback

All upload handlers support automatic rollback on error. If the handler function raises an exception, any uploaded data is rolled back:

sql
sql
create function lo_upload_raise_exception(
+    _oid bigint,
+    _meta json = null
+)
+returns json
+language plpgsql
+as
+$$
+begin
+    raise exception 'failed upload';
+    return _meta;
+end;
+$$;
+
+comment on function lo_upload_raise_exception(bigint, json) is '
+@upload for large_object
+@param _meta is upload metadata
+@oid = {_oid}
+';

If an exception is raised:

Multiple File Uploads

Upload endpoints support multiple files in a single request. The metadata will be returned as a JSON array with one entry per file.

Behavior

Custom Parameters

Upload handlers accept custom parameters using the @key = value syntax to control file processing behavior per-endpoint.

Shared Parameters

ParameterDescription
stop_after_first_successWhen true, stops processing after the first successful upload handler.
included_mime_typesComma-separated list of MIME type patterns to include for upload processing.
excluded_mime_typesComma-separated list of MIME type patterns to exclude from upload processing.
check_formatWhen true, validates the file format before processing. If validation fails, fallback_handler is used.
fallback_handlerHandler name to delegate to if format validation fails (e.g., csv, large_object). Available on all upload handlers.

Large Object Upload Handler

ParameterDescription
buffer_size, large_object_buffer_sizeSize of the buffer used for reading/writing large object data.
check_text, large_object_check_textWhen true, checks if the uploaded content is text format.
check_image, large_object_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, large_object_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, large_object_non_printable_thresholdThreshold for determining if content contains non-printable characters.
oid, large_object_oidPostgreSQL large object OID to use for storage.
large_object_included_mime_typesMIME type patterns to include for large object upload processing.
large_object_excluded_mime_typesMIME type patterns to exclude from large object upload processing.

Example

sql
sql
comment on function upload_to_large_object(text, json) is '
+HTTP POST
+@upload for large_object
+@param _meta is upload metadata
+@check_image = true';

File System Upload Handler

ParameterDescription
buffer_size, file_system_buffer_sizeSize of the buffer used for reading/writing file system data.
check_text, file_system_check_textWhen true, checks if the uploaded content is text format.
check_image, file_system_check_imageWhen true, checks if the uploaded content is an image format.
test_buffer_size, file_system_test_buffer_sizeSize of the buffer used for testing file content type.
non_printable_threshold, file_system_non_printable_thresholdThreshold for determining if content contains non-printable characters.
path, file_system_pathFile system path where uploaded files will be stored. Supports dynamic placeholders.
file, file_system_fileSpecific file name to use for the uploaded content. Supports dynamic placeholders.
unique_name, file_system_unique_nameWhen true, generates unique file names to avoid conflicts.
create_path, file_system_create_pathWhen true, creates the directory path if it doesn't exist.
file_system_included_mime_typesMIME type patterns to include for file system upload processing.
file_system_excluded_mime_typesMIME type patterns to exclude from file system upload processing.

Example

sql
sql
comment on function upload_to_file_system(text, json) is '
+HTTP POST
+@upload for file_system
+@param _meta is upload metadata
+@check_image = true
+@path = ./public/uploads
+@unique_name = true
+@create_path = true';

CSV Upload Handler

ParameterDescription
test_buffer_size, csv_test_buffer_sizeSize of the buffer used for testing CSV content type.
non_printable_threshold, csv_non_printable_thresholdThreshold for determining if content contains non-printable characters.
check_format, csv_check_formatWhen true, validates the CSV format before processing.
delimiters, csv_delimitersCharacters used as field delimiters in CSV files (e.g., comma, semicolon).
has_fields_enclosed_in_quotes, csv_has_fields_enclosed_in_quotesWhen true, expects CSV fields to be enclosed in quotes.
set_white_space_to_null, csv_set_white_space_to_nullWhen true, converts whitespace-only fields to NULL values.
row_command, csv_row_commandSQL command to execute for each CSV row during processing.
csv_included_mime_typesMIME type patterns to include for CSV upload processing.
csv_excluded_mime_typesMIME type patterns to exclude from CSV upload processing.

Example

sql
sql
comment on function csv_upload(json) is '
+HTTP POST
+@upload for csv
+@param _meta is upload metadata
+@delimiters = ,;
+@row_command = select csv_upload_row($1,$2,$3,$4)';

Excel Upload Handler

ParameterDescription
sheet_name, excel_sheet_nameName of the specific Excel worksheet to process.
all_sheets, excel_all_sheetsWhen true, processes all worksheets in the Excel file.
time_format, excel_time_formatFormat string for parsing time values from Excel cells.
date_format, excel_date_formatFormat string for parsing date values from Excel cells.
datetime_format, excel_datetime_formatFormat string for parsing datetime values from Excel cells.
row_is_json, excel_row_is_jsonWhen true, treats each Excel row as JSON data.
row_command, excel_row_commandSQL command to execute for each Excel row during processing.
excel_included_mime_typesMIME type patterns to include for Excel upload processing.
excel_excluded_mime_typesMIME type patterns to exclude from Excel upload processing.

Example

sql
sql
comment on function excel_upload(json) is '
+HTTP POST
+@upload for excel
+@param _meta is upload metadata
+@all_sheets = true
+@row_command = select excel_upload_row($1,$2,$3,$4)';

Blog Posts

See Also

`,143)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_upload.md.BJPkIDDG.lean.js b/assets/annotations_upload.md.BJPkIDDG.lean.js new file mode 100644 index 000000000..6d8ffad76 --- /dev/null +++ b/assets/annotations_upload.md.BJPkIDDG.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"UPLOAD Annotation","titleTemplate":"NpgsqlRest","description":"Create file upload endpoints for PostgreSQL REST APIs. Handle multipart uploads with Large Objects, file system, CSV, or Excel handlers.","frontmatter":{"outline":[2,3],"title":"UPLOAD Annotation","titleTemplate":"NpgsqlRest","description":"Create file upload endpoints for PostgreSQL REST APIs. Handle multipart uploads with Large Objects, file system, CSV, or Excel handlers.","head":[["meta",{"name":"keywords","content":"npgsqlrest upload, file upload api, multipart upload, postgresql large objects, csv upload excel upload"}],["meta",{"property":"og:title","content":"NpgsqlRest UPLOAD Annotation"}],["meta",{"property":"og:description","content":"Create file upload endpoints with Large Objects, file system, CSV, or Excel handlers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/upload.md","filePath":"annotations/upload.md"}'),t={name:"annotations/upload.md"};function l(p,s,r,d,h,k){return n(),i("div",null,s[0]||(s[0]=[e("",143)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_user-context.md.D9DNdqIU.js b/assets/annotations_user-context.md.D9DNdqIU.js new file mode 100644 index 000000000..30d507737 --- /dev/null +++ b/assets/annotations_user-context.md.D9DNdqIU.js @@ -0,0 +1,67 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"USER_CONTEXT Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims to PostgreSQL session context variables. Access user info in SQL functions via current_setting().","frontmatter":{"outline":[2,3],"title":"USER_CONTEXT Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims to PostgreSQL session context variables. Access user info in SQL functions via current_setting().","head":[["meta",{"name":"keywords","content":"npgsqlrest user context, postgresql session context, user claims sql, current_setting user, session variables"}],["meta",{"property":"og:title","content":"NpgsqlRest USER_CONTEXT Annotation"}],["meta",{"property":"og:description","content":"Pass user claims to PostgreSQL session context variables for SQL access."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/user-context.md","filePath":"annotations/user-context.md"}'),t={name:"annotations/user-context.md"};function l(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

USER_CONTEXT

Enable setting user claims into PostgreSQL session context variables for the endpoint.

Keywords

@user_context, user_context

Syntax

code
@user_context

Examples

Enable User Context

sql
sql
comment on function personalized_data() is
+'HTTP GET
+@authorize
+@user_context';

Access User Claims in Function

sql
sql
create function get_user_context()
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    current_setting('request.user_id', true)::int,
+    current_setting('request.user_name', true)::text,
+    (current_setting('request.user_roles', true))::text[];
+end;
+
+comment on function get_user_context() is '
+@authorize
+@user_context
+';

Equivalent as a SQL file endpoint (sql/get-user-context.sql):

sql
sql
/*
+HTTP GET
+@authorize
+@user_context
+*/
+select
+    current_setting('request.user_id', true)::int as user_id,
+    current_setting('request.user_name', true)::text as user_name,
+    (current_setting('request.user_roles', true))::text[] as user_roles;

Access All Claims as JSON

When ClaimsJsonContextKey is configured (e.g., "request.user_claims"):

sql
sql
create function get_full_claims()
+returns table (claims text)
+language sql
+begin atomic;
+select current_setting('request.user_claims', true)::text;
+end;
+
+comment on function get_full_claims() is '
+@authorize
+@user_context
+';

Access Client IP Address

sql
sql
create function get_client_info()
+returns table (ip_address text)
+language sql
+begin atomic;
+select current_setting('request.ip_address', true)::text;
+end;
+
+comment on function get_client_info() is '
+@authorize
+@user_context
+';

Combined with Request Headers

sql
sql
create function get_user_context_and_headers()
+returns table (
+    user_id int,
+    user_name text,
+    headers jsonb
+)
+language sql
+begin atomic;
+select
+    current_setting('request.user_id', true)::int,
+    current_setting('request.user_name', true)::text,
+    current_setting('request.headers', true)::jsonb;
+end;
+
+comment on function get_user_context_and_headers() is '
+@authorize
+@user_context
+@request_headers context
+';

Behavior

Default Context Keys

Context KeyClaimDescription
request.user_iduser_idUser identifier
request.user_nameuser_nameUsername
request.user_rolesuser_rolesUser roles (array)
request.ip_address-Client IP address

Additional Context Keys (when configured)

Context KeyConfig OptionDescription
(configurable)ClaimsJsonContextKeyAll claims serialized as JSON

See Also

`,32)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_user-context.md.D9DNdqIU.lean.js b/assets/annotations_user-context.md.D9DNdqIU.lean.js new file mode 100644 index 000000000..e4166c9b2 --- /dev/null +++ b/assets/annotations_user-context.md.D9DNdqIU.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"USER_CONTEXT Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims to PostgreSQL session context variables. Access user info in SQL functions via current_setting().","frontmatter":{"outline":[2,3],"title":"USER_CONTEXT Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims to PostgreSQL session context variables. Access user info in SQL functions via current_setting().","head":[["meta",{"name":"keywords","content":"npgsqlrest user context, postgresql session context, user claims sql, current_setting user, session variables"}],["meta",{"property":"og:title","content":"NpgsqlRest USER_CONTEXT Annotation"}],["meta",{"property":"og:description","content":"Pass user claims to PostgreSQL session context variables for SQL access."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/user-context.md","filePath":"annotations/user-context.md"}'),t={name:"annotations/user-context.md"};function l(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",32)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_user-parameters.md.RuNgxxKs.js b/assets/annotations_user-parameters.md.RuNgxxKs.js new file mode 100644 index 000000000..549aad694 --- /dev/null +++ b/assets/annotations_user-parameters.md.RuNgxxKs.js @@ -0,0 +1,73 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"USER_PARAMETERS Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims as PostgreSQL function parameters. Inject user ID, roles, and custom claims into SQL functions.","frontmatter":{"outline":[2,3],"title":"USER_PARAMETERS Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims as PostgreSQL function parameters. Inject user ID, roles, and custom claims into SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest user parameters, user claims parameters, inject user id, function user parameter, authenticated user sql"}],["meta",{"property":"og:title","content":"NpgsqlRest USER_PARAMETERS Annotation"}],["meta",{"property":"og:description","content":"Pass user claims as function parameters for authenticated endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/user-parameters.md","filePath":"annotations/user-parameters.md"}'),l={name:"annotations/user-parameters.md"};function t(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e(`

USER_PARAMETERS

Also known as

user_params (with or without @ prefix)

Enable passing user claims as function parameters for the endpoint.

Syntax

code
@user_parameters
+@user_params

Examples

Basic User Parameters

sql
sql
create function get_user_params(
+    _user_id text,
+    _user_name text,
+    _user_roles text[]
+)
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    _user_id::int,
+    _user_name,
+    _user_roles;
+end;
+
+comment on function get_user_params(text, text, text[]) is '
+@authorize
+@user_params
+';

Equivalent as a SQL file endpoint (sql/get-user-params.sql):

sql
sql
/*
+HTTP GET
+@authorize
+@user_params
+@param $1 user_id text
+@param $2 user_name text
+@param $3 user_roles text[]
+*/
+select $1::int as user_id, $2 as user_name, $3 as user_roles;

With Default Values (for unauthenticated access)

sql
sql
create function get_user_params_optional(
+    _user_id text = null,
+    _user_name text = 'anonymous',
+    _user_roles text[] = array[]::text[]
+)
+returns table (
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language sql
+begin atomic;
+select
+    _user_id::int,
+    _user_name,
+    _user_roles;
+end;
+
+comment on function get_user_params_optional(text, text, text[]) is '
+@user_params
+';

Access All Claims as JSON

sql
sql
create function get_user_ip_and_full_claims(
+    _ip_address text,
+    _user_claims json
+)
+returns table (
+    ip_address text,
+    user_claims json
+)
+language sql
+begin atomic;
+select
+    _ip_address,
+    _user_claims;
+end;
+
+comment on function get_user_ip_and_full_claims(text, json) is '
+@authorize
+@user_params
+';

Combined with User Context

sql
sql
comment on function user_profile() is
+'HTTP GET
+@authorize
+@user_context
+@user_parameters';

Behavior

Default Parameter Mapping

Parameter NameClaimDescription
_user_iduser_idUser identifier
_user_nameuser_nameUsername
_user_rolesuser_rolesUser roles (array)
_ip_address-Client IP address
_user_claims-All claims serialized as JSON

Differences from USER_CONTEXT

FeatureUSER_PARAMETERSUSER_CONTEXT
Access methodFunction parameterscurrent_setting()
Works without authYes (with defaults)Yes (returns empty)
Type safetyPostgreSQL enforcedManual casting required
PerformanceSlightly fasterSlightly slower

See Also

`,28)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_user-parameters.md.RuNgxxKs.lean.js b/assets/annotations_user-parameters.md.RuNgxxKs.lean.js new file mode 100644 index 000000000..35d05b422 --- /dev/null +++ b/assets/annotations_user-parameters.md.RuNgxxKs.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"USER_PARAMETERS Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims as PostgreSQL function parameters. Inject user ID, roles, and custom claims into SQL functions.","frontmatter":{"outline":[2,3],"title":"USER_PARAMETERS Annotation","titleTemplate":"NpgsqlRest","description":"Pass authenticated user claims as PostgreSQL function parameters. Inject user ID, roles, and custom claims into SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest user parameters, user claims parameters, inject user id, function user parameter, authenticated user sql"}],["meta",{"property":"og:title","content":"NpgsqlRest USER_PARAMETERS Annotation"}],["meta",{"property":"og:description","content":"Pass user claims as function parameters for authenticated endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/user-parameters.md","filePath":"annotations/user-parameters.md"}'),l={name:"annotations/user-parameters.md"};function t(p,s,r,h,k,c){return n(),i("div",null,s[0]||(s[0]=[e("",28)]))}const u=a(l,[["render",t]]);export{o as __pageData,u as default}; diff --git a/assets/annotations_validate.md.BYjsje_G.js b/assets/annotations_validate.md.BYjsje_G.js new file mode 100644 index 000000000..efea03174 --- /dev/null +++ b/assets/annotations_validate.md.BYjsje_G.js @@ -0,0 +1,100 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"VALIDATE Annotation","titleTemplate":"NpgsqlRest","description":"Validate endpoint parameters before database execution. Apply validation rules like NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength to PostgreSQL function parameters.","frontmatter":{"outline":[2,3],"title":"VALIDATE Annotation","titleTemplate":"NpgsqlRest","description":"Validate endpoint parameters before database execution. Apply validation rules like NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength to PostgreSQL function parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest validate, parameter validation, api input validation, postgresql validation, request validation annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest VALIDATE Annotation"}],["meta",{"property":"og:description","content":"Validate endpoint parameters before database execution using predefined validation rules."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/validate.md","filePath":"annotations/validate.md"}'),l={name:"annotations/validate.md"};function t(p,s,h,r,k,d){return n(),i("div",null,s[0]||(s[0]=[e(`

VALIDATE

Validate endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

Keywords

@validate, validate

Syntax

code
@validate <parameter_name> using <rule_name>
+@validate <parameter_name> using <rule1>, <rule2>, <rule3>, ...

Multiple rules can be specified as comma-separated values or on separate lines.

Examples

Single Rule Validation

sql
sql
create function get_user(_user_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where id = _user_id;
+end;
+
+comment on function get_user(int) is '
+HTTP GET
+@validate _user_id using not_null
+';

Equivalent as a SQL file endpoint (sql/get-user.sql):

sql
sql
/*
+HTTP GET
+@validate user_id using not_null
+@param $1 user_id int
+*/
+select row_to_json(u) from users u where id = $1;

Multiple Rules on One Parameter

sql
sql
create function update_email(_user_id int, _email text)
+returns json
+language plpgsql
+as $$
+begin
+    update users set email = _email where id = _user_id;
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function update_email(int, text) is '
+HTTP PUT
+@validate _user_id using not_null
+@validate _email using required, email
+';

The _email parameter must pass both required (not null and not empty) and email (regex pattern) validation.

Multiple Parameters

sql
sql
create function register_user(_email text, _password text, _name text)
+returns json
+language plpgsql
+as $$
+begin
+    insert into users (email, password_hash, name)
+    values (_email, crypt(_password, gen_salt('bf')), _name);
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function register_user(text, text, text) is '
+HTTP POST
+@validate _email using required, email
+@validate _password using required
+@validate _name using not_empty
+';

Using Converted Parameter Names

Parameter names can use the converted camelCase format:

sql
sql
create function create_product(_product_name text, _unit_price numeric)
+returns json
+language plpgsql
+as $$
+begin
+    insert into products (name, price) values (_product_name, _unit_price);
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function create_product(text, numeric) is '
+HTTP POST
+@validate productName using required
+@validate unitPrice using not_null
+';

Both productName and _product_name refer to the same parameter.

With Authorization

Validation works alongside other annotations:

sql
sql
create function update_profile(_user_id int, _bio text, _website text)
+returns json
+language plpgsql
+as $$
+begin
+    update profiles set bio = _bio, website = _website where user_id = _user_id;
+    return json_build_object('success', true);
+end;
+$$;
+
+comment on function update_profile(int, text, text) is '
+HTTP PUT
+@authorize
+@user_params
+@validate _bio using not_empty
+';

Default Rules

Four validation rules are available by default without additional configuration:

Rule NameTypeDescription
not_nullNotNullValue cannot be null
not_emptyNotEmptyValue cannot be empty string (nulls pass)
requiredRequiredValue cannot be null or empty
emailRegexValue must match email pattern

Custom Rules

Custom validation rules are defined in ValidationOptions configuration:

json
json
{
+  "ValidationOptions": {
+    "Rules": {
+      "phone": {
+        "Type": "Regex",
+        "Pattern": "^\\\\+?[1-9]\\\\d{1,14}$",
+        "Message": "Parameter '{0}' must be a valid phone number",
+        "StatusCode": 400
+      },
+      "password_min": {
+        "Type": "MinLength",
+        "MinLength": 8,
+        "Message": "Password must be at least 8 characters"
+      }
+    }
+  }
+}

Then use in annotations:

sql
sql
comment on function create_user(text, text, text) is '
+HTTP POST
+@validate _phone using phone
+@validate _password using required, password_min
+';

Behavior

Error Response

When validation fails, the endpoint returns an error response:

json
json
{
+  "title": "Parameter '_email' must be a valid email address",
+  "status": 400,
+  "detail": null
+}

The HTTP status code and message are configured per rule in ValidationOptions.

See Also

`,45)]))}const u=a(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_validate.md.BYjsje_G.lean.js b/assets/annotations_validate.md.BYjsje_G.lean.js new file mode 100644 index 000000000..7a8e1173d --- /dev/null +++ b/assets/annotations_validate.md.BYjsje_G.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"VALIDATE Annotation","titleTemplate":"NpgsqlRest","description":"Validate endpoint parameters before database execution. Apply validation rules like NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength to PostgreSQL function parameters.","frontmatter":{"outline":[2,3],"title":"VALIDATE Annotation","titleTemplate":"NpgsqlRest","description":"Validate endpoint parameters before database execution. Apply validation rules like NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength to PostgreSQL function parameters.","head":[["meta",{"name":"keywords","content":"npgsqlrest validate, parameter validation, api input validation, postgresql validation, request validation annotation"}],["meta",{"property":"og:title","content":"NpgsqlRest VALIDATE Annotation"}],["meta",{"property":"og:description","content":"Validate endpoint parameters before database execution using predefined validation rules."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/validate.md","filePath":"annotations/validate.md"}'),l={name:"annotations/validate.md"};function t(p,s,h,r,k,d){return n(),i("div",null,s[0]||(s[0]=[e("",45)]))}const u=a(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/annotations_void.md.CdJ8Ec9f.js b/assets/annotations_void.md.CdJ8Ec9f.js new file mode 100644 index 000000000..9dd828564 --- /dev/null +++ b/assets/annotations_void.md.CdJ8Ec9f.js @@ -0,0 +1,19 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"VOID Annotation","titleTemplate":"NpgsqlRest","description":"Force an endpoint to return 204 No Content. Execute all statements for side effects without returning a JSON response.","frontmatter":{"outline":[2,3],"title":"VOID Annotation","titleTemplate":"NpgsqlRest","description":"Force an endpoint to return 204 No Content. Execute all statements for side effects without returning a JSON response.","head":[["meta",{"name":"keywords","content":"npgsqlrest void annotation, no content response, 204 response, side effect endpoint, void endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest VOID Annotation"}],["meta",{"property":"og:description","content":"Force an endpoint to return 204 No Content."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/void.md","filePath":"annotations/void.md"}'),t={name:"annotations/void.md"};function l(p,s,o,r,d,h){return e(),a("div",null,s[0]||(s[0]=[n(`

VOID

Also known as

void_result (with or without @ prefix)

Force an endpoint to return 204 No Content instead of a JSON response. All statements are executed for side effects only.

Available since version 3.12.0.

Syntax

code
@void
+@void_result

Examples

Multi-Command Side Effects

Useful when all statements are side-effect-only (e.g., set_config calls followed by a DO block):

sql
sql
/* HTTP POST
+@void
+@param $1 message_text text
+@param $2 _user_id text = null
+*/
+select set_config('app.message', $1, true);
+select set_config('app.user_id', $2, true);
+do $$ begin
+    insert into messages (user_id, text)
+    values (current_setting('app.user_id')::int, current_setting('app.message'));
+end; $$;

Without @void, this returns {"result1":"...","result2":"...","result3":-1}. With @void, it returns 204 No Content.

This eliminates the need to add @skip to every individual statement.

Single-Command Void

Also works on single-command endpoints:

sql
sql
-- HTTP POST
+-- @void
+-- @param $1 key text
+-- @param $2 value text
+select set_config($1, $2, true);

Function Endpoints

Works on function and procedure endpoints too:

sql
sql
comment on function process_data(int) is '
+HTTP POST
+void
+';

Behavior

`,22)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/annotations_void.md.CdJ8Ec9f.lean.js b/assets/annotations_void.md.CdJ8Ec9f.lean.js new file mode 100644 index 000000000..2409120ca --- /dev/null +++ b/assets/annotations_void.md.CdJ8Ec9f.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"VOID Annotation","titleTemplate":"NpgsqlRest","description":"Force an endpoint to return 204 No Content. Execute all statements for side effects without returning a JSON response.","frontmatter":{"outline":[2,3],"title":"VOID Annotation","titleTemplate":"NpgsqlRest","description":"Force an endpoint to return 204 No Content. Execute all statements for side effects without returning a JSON response.","head":[["meta",{"name":"keywords","content":"npgsqlrest void annotation, no content response, 204 response, side effect endpoint, void endpoint"}],["meta",{"property":"og:title","content":"NpgsqlRest VOID Annotation"}],["meta",{"property":"og:description","content":"Force an endpoint to return 204 No Content."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"annotations/void.md","filePath":"annotations/void.md"}'),t={name:"annotations/void.md"};function l(p,s,o,r,d,h){return e(),a("div",null,s[0]||(s[0]=[n("",22)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/app.Ds8YbgIC.js b/assets/app.Ds8YbgIC.js new file mode 100644 index 000000000..c4e9d7f5e --- /dev/null +++ b/assets/app.Ds8YbgIC.js @@ -0,0 +1 @@ +import{R as p}from"./chunks/theme.kqgpP4eL.js";import{R as s,a6 as i,a7 as u,a8 as c,a9 as l,aa as f,ab as d,ac as m,ad as h,ae as g,af as A,d as v,u as R,v as w,s as y,ag as C,ah as P,ai as b,a2 as E}from"./chunks/framework.CgT1UzWm.js";function r(e){if(e.extends){const a=r(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const n=r(p),S=v({name:"VitePressApp",setup(){const{site:e,lang:a,dir:t}=R();return w(()=>{y(()=>{document.documentElement.lang=a.value,document.documentElement.dir=t.value})}),e.value.router.prefetchLinks&&C(),P(),b(),n.setup&&n.setup(),()=>E(n.Layout)}});async function T(){globalThis.__VITEPRESS__=!0;const e=_(),a=D();a.provide(u,e);const t=c(e.route);return a.provide(l,t),a.component("Content",f),a.component("ClientOnly",d),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),n.enhanceApp&&await n.enhanceApp({app:a,router:e,siteData:m}),{app:a,router:e,data:t}}function D(){return A(S)}function _(){let e=s;return h(a=>{let t=g(a),o=null;return t&&(e&&(t=t.replace(/\.js$/,".lean.js")),o=import(t)),s&&(e=!1),o},n.NotFound)}s&&T().then(({app:e,router:a,data:t})=>{a.go().then(()=>{i(a.route,t.site),e.mount("#app")})});export{T as createApp}; diff --git a/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.js b/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.js new file mode 100644 index 000000000..aa1ce9f09 --- /dev/null +++ b/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.js @@ -0,0 +1,33 @@ +import{_ as t,c as a,o as i,a5 as s}from"./chunks/framework.CgT1UzWm.js";const n="/ddd-agggrate-transparent.webp",o="/vietnam/evans.png",r="/vietnam/vernon.png",l="/vietnam/codd.png",b=JSON.parse('{"title":"DRAFT: 20th Anniversary of The Vietnam of Computer Science","titleTemplate":"NpgsqlRest","description":"DRAFT — A compilation of a decade of arguments about DDD, Clean Architecture, the wrong abstractions modern business software is built on, and the case for putting the database back where it belongs.","frontmatter":{"layout":"doc","outline":[2,3],"title":"DRAFT: 20th Anniversary of The Vietnam of Computer Science","titleTemplate":"NpgsqlRest","description":"DRAFT — A compilation of a decade of arguments about DDD, Clean Architecture, the wrong abstractions modern business software is built on, and the case for putting the database back where it belongs.","badge":"human","head":[["meta",{"name":"robots","content":"noindex, nofollow"}],["meta",{"name":"keywords","content":"npgsqlrest postgresql clean architecture ddd database-first state abstraction sql platform business rules"}]]},"headers":[],"relativePath":"blog/DRAFT-anniversary-vietnam-of-computer-science.md","filePath":"blog/DRAFT-anniversary-vietnam-of-computer-science.md"}'),h={name:"blog/DRAFT-anniversary-vietnam-of-computer-science.md"};function d(c,e,p,m,u,g){return i(),a("div",null,e[0]||(e[0]=[s('

DRAFT: 20th Anniversary of The Vietnam of Computer Science

DRAFT — TODO: date · NpgsqlRestPostgreSQLArchitectureDDDClean ArchitectureOpinion

Introduction

It has been twenty years since Ted Neward published "The Vietnam of Computer Science". Twenty years.

Did we win? Did we even leave? Are we stuck in a quagmire?

Since then, the industry has marched relentlessly through a never-ending parade of patterns, architectures, and methodologies: ORM tools of every flavor, Repository and Unit of Work patterns, Domain-Driven Design, CQRS, Event Sourcing (currently all the rage), Hexagonal Architecture, Clean Architecture, Onion Architecture, Service-Oriented Architecture (now obsolete and so last year), Microservices, and on and on.

The quagmire Neward described is, of course, Object-Relational Impedance Mismatch. So to speak, a shotgun marriage between Object and Relational worlds. One lives in your application memory and works over data structures, and the other, well, in a relational database.

And none of these patterns above made the problem go away. We need both, and somehow the majority of the effort goes into dealing with persistence in one way or another. It's like unsuccessful couples therapy for that shotgun marriage. Maybe we need divorce papers?

Let's dig in deep.

What Is The Object–Relational Impedance Mismatch

According to Wikipedia (link: https://en.wikipedia.org/wiki/Object–relational_impedance_mismatch):

Object–relational impedance mismatch is a set of difficulties going between data in relational data stores and data in domain-driven object models.

Ok, got it. A set of difficulties. Difficulties that refuse to go away, it seems, but fine.

It's worth noting that Object–Relational isn't the only mismatch on the menu. OO isn't the only way we work over application memory — functional programming is on the rise too, so there's a Functional–Relational mismatch as well. To be fair, FP gets along with relational better than OO does: SQL is already declarative, set-based, and value-oriented, right up FP's alley. But it still works over application memory, so it still has to bridge to the relational database like everyone else.

Anyway...

Relational Database Management Systems (RDBMS) is the standard method for storing data in a dedicated database, while object-oriented (OO) programming is the default method for business-centric design in programming languages.

RDBMS is the standard method for storing data in a dedicated database, but are they typically doing only data storage? I mean, files are also doing that, are they not? Are we missing something here?

And this second claim that object-oriented (OO) programming is the default method for business-centric design, that might be true, but SQL is the default method for anything business data-related, like the way organizations store, manage, protect, and analyze their data.

We can see the tension already here, but let's move on.

The problem lies in neither relational databases nor OO programming, but in the conceptual difficulty mapping between the two logic models. Both logical models are differently implementable using database servers, programming languages, design patterns, or other technologies.

Now, why would they say that we have two logical models?

Take a Customer. In your business, a customer is one thing — one concept. DDD even has a name for this: within a bounded context there is one ubiquitous language, so there is one shared notion of what a Customer is. That single concept becomes one logical model — what a Customer is, which attributes it has, how it relates to orders — with no implementation details attached. That's why it's called a logical model.

Then you build it. And here is where it splits: that one logical model gets implemented physically more than once. Once as a table — customer, with a varchar(255) name column. Once as a class — Customer, with a string Name property. Same concept, same logical model, two physical incarnations.

So in modern software design we are not talking about two different logical models, as Wikipedia claims. We have one logical model and two — sometimes more — physical models. Modern overengineering knows no bounds.

The reason why they say that we have two logical models is probably because OO and Relational are seen as totally different paradigms so the logical models must be genuinely different. For example OO does have behavior, but the problem is that we are talking strictly about data here, not behavior. Behavior is a different problem axis - we'll come to it when we get to algorithms.

In any case, impedance mismatch is not about two different logical models, but about the mismatch between those two physical models. Your system has to map them and make them work together because, logically, we still have one logical model and the system needs to reflect that. That’s the real problem and source of tension.

Issues range from application to enterprise scale, whenever stored relational data is used in domain-driven object models, and vice versa. Object-oriented data stores can trade this problem for other implementation difficulties.

This last sentence just confirms what I just said. Whenever stored relational data (one physical model) is used in domain-driven object models (another physical model), we have this problem or mismatch. Maybe the belief that we are talking about two different logical models is the reason why these difficulties exist and persist and are not being resolved. If it was just a simple mapping, let's say from varchar(255) to a string, that would be easy to solve a long time ago. Or something a bit more complex, let's say many-to-many on a logical model level. In a relational model, that requires a junction table, but in an object model that can be just a collection of references. Still, a little bit more complex mapping, but still solvable and indeed already solved.

My sincere belief is that we are talking here about a series of misconceptions and misunderstandings about the nature of abstractions themselves. More specifically, about abstractions the RDBMS already provides. And if RDBMS already provides them, well, that means that the application layer goes on and re-implements them anyway. No wonder we have difficulties. Let's look at these misconceptions one by one and discuss in detail:

  1. State Data Abstraction Misconception

  2. Storage Devices Abstraction Misconception

  3. Data Structures Abstraction Misconception

  4. Abstraction Over Algorithms

  5. Abstraction Over Concurrency and Integrity

1) State Data Abstraction Misconception

The Claim

Object-oriented programming has one of its core tenets called encapsulation. Encapsulation is supposed to protect internal data — the state. The object is the authoritative custodian of its state. Nobody else has it. Period. Without encapsulation, we don't really have OOP anymore.

On the other hand, functional programming has its own version of that — state immutability. A function takes values in and returns new values out, leaving the originals untouched — so there's no shared mutable state to corrupt in the first place. FP also enforces valid state through invariants, often by encoding them directly into the type system. Same goal, fewer bugs from uncontrolled state, arguably reached more elegantly. Fine. State status - protected, bugs - reduced. Great, beautiful, love it.

This, in fact, is very reasonable and well-thought-out. State data is data shared between different parts of the system and even different users. If every part of the system can poke at it without other parts knowing about it, then you have a lot of bugs. So, naturally, over time people came up with these concepts of encapsulation and immutability to protect that state and make sure it is only changed and fiddled with in a controlled way. Because we don't want to have bugs. Bugs are bad, okay.

No objection here. This is perfectly fine, and it makes a lot of sense. Let's say we have something complex. A game scene. Compiler syntax tree. Whatever. Protecting state in memory of such systems is invaluable, to say at least.

In his book Domain-Driven Design (2004), when describing the Domain Layer in Chapter 4, "Isolating the Domain," Eric Evans writes:

State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure.

So he treats the in-memory object as the custodian of state - state that is "controlled and used here," where "here" is the in-memory Domain Layer — while the RDBMS is merely "delegated to the infrastructure." More on that in the next chapter. What matters here is the claim itself: the state is in memory. No doubt about it.

The Reality

But what about applications backed by relational databases, business or otherwise?

In RDBMS-backed applications, the state lives in that RDBMS itself - not in memory. The authoritative custodian of state is the database table row, not the in-memory object.

Take any business application backed by a relational database. How do you check the current state of some entity? Do you peek at the object in memory? No, you query the database for that. Anyone who has worked 5 seconds in industry knows this, of course.

I know what DDD people will say now: object memory (or functional state memory) is the real state data, and RDBMS is just where that data is persisted (presumably when the user clicks on a "Save" icon).

And, if you still believe that objects/memory are the real state and not RDBMS, then riddle me this:

What if we have multiple instances of the application running behind a load balancer? And then maybe some background work as well, and some other services too. Maybe reporting replica as well, so we have multiple processes accessing the same state data. Who is the sole custodian of that state data in that case? The in-memory object of one process, or the database row? The obvious answer is, of course, NONE of the in-memory copies. The best we can do is to have each process hold a copy, while the real state is in the RDBMS itself.

Some may say now, but databases have multiplicity too, right? We have multiple replicas, and they are all copies of the same data. And there are also multi-master setups as well with multiple writers for high availability scenarios. Yeah, but the big difference is that the database solves it, while in-memory objects don't even try to be honest. They just deny the reality. For multiple replicas, there is never any ambiguity about which one is the source of truth, since we are talking about read-only copies, and for multi-master setups we have either different consensus protocols that ensure a single source of truth or so-called eventual-consistency for the source of truth. In-memory objects have no such machinery, so they just pretend that they are the single custodian of the state data, which is, of course, a big, fat lie.

If the object is the custodian of state, what if we kill and restart the application? Oh my, the object's custodianship has evaporated.

Also, what, for example, if we have two concurrent user writers? One loads an invoice as 'pending', but another writer marks it 'paid' while the first one holds it. Now the first one is lying about the state of that invoice. The database is right - it is the source of truth - the first one is stale. We will talk more about concurrency and integrity later - this is just to prove my point:

RDBMS is the authoritative custodian of state, not in-memory objects. The state lives in the database, not in memory.

The Cost

Take a look at this example of a DDD-style domain model below:

Source credit: https://www.reddit.com/r/DomainDrivenDesign/comments/1ttzr19/create_complex_and_deep_aggregate/

This is a random example from Reddit, but it is indicative. Virtually every domain model is more or less like that - strip away the method, adjust types a bit, and it is basically an Entity-Relationship (ER) diagram, that's it. There is no structural difference between the domain model and the database model. It is the same logical model implemented twice.

And that method isValid() - the only behavior in the whole model - is just a data integrity check. Every rule it enforces, the database enforces too: dateBegin <= dateEnd is a one-line CHECK, and the harder ones —- price periods that must not overlap, quantity ranges that must stay contiguous — are exactly what database constraints exist for (more on the how in the integrity misconception). Same logical model implemented twice, same integrity rules implemented twice.

Because modern software design orthodoxy refuses to acknowledge that the state lives in the RDBMS, it forces us to implement the same model at least twice - once in the database (relational model implementation), once in memory (object model implementation, mapped usually with an O/R tool or a library).

I say at least, because there are examples where we have even more. Some "architects" will consider that O/R mapped model a persistence model because it doesn't have any behavior, and then they will add a separate domain model on top of that, which is the one with the behavior. So we have three implementations of the same logical model - database model, persistence model, and domain model.

And then, since we now have at least two physical models, we also have two type systems and two sets of constraints to protect the state. And we must keep them in sync at all times, and we must maintain the correct mapping. To be fair, this is mostly automated in modern systems (not completely), but we still have to do it and it is still there, automated or not. And even automated, it is still a cost and still a source of bugs and still a source of complexity. And even that can't bridge the semantic gap between the two type systems fully. For example, in PostgreSQL we can have a NOT NULL constraint on a column, but the corresponding C# property will be a nullable string.

All because we believe that there is some important state data we need to protect with our objects. In the best case, there are just some transient and disposable chunks of data copies, and in any case there is a lot of plumbing to manage: connections, transactions, commands, queries, etc. You know, the actual infrastructure.

Personally, I see a lot of irony in this. Modern orthodoxy calls RDBMS infrastructure, just to end up with code bases implementing, well, actual infrastructure.

2) Storage Devices Abstraction Misconception

The Claim

A modern software engineering approach makes the assumption that an RDBMS is a storage device. A device used to store data. Therefore, good engineering practice is to abstract that storage device.

We can see that in the quote from Eric Evans above, where he says that:

... the technical details of storing it are delegated to the infrastructure.

Evans doesn't dwell on this point much - he just delegates storage to the infrastructure and moves on. On the other hand, Robert C. Martin is much more vocal about it. In Clean Architecture, Martin dedicates an entire chapter to "The Database Is a Detail" argument. He is very blunt about it, and he repeats it several times, for example:

It's just a mechanism we use to move the data back and forth between the surface of the disk and the RAM.

Or this:

The database is really nothing more than a big bucket of bits where we store our data on a long-term basis.

This is not some fringe blog post. These are two of the most-cited and influential authors in the field - arriving at one and the same conclusion - just at different volumes.

Evans states it just once and quietly, like he doesn't want to talk about it too much, and then delegates it away (perhaps he'd rather not have you examine it too closely, I don't know). Martin states it over and over, bluntly, and goes as far as to insist we should not even acknowledge that the disk exists.

Different approaches - identical claims: the database is a storage device. Storage is mechanical, it sits beneath the business — no different from a file system — and the architect's job is to wrap it up tight and forget it is there.

The Reality

Reality is that RDBMS uses a storage device or devices, and that means that it already does this abstraction for you. It already abstracts storage.

If we go back to the beginning, when Edgar Codd introduced the relational model in 1970, the pitch was data independence — the whole point was to insulate the logical shape of your data from how it physically sits on a device. In fact, that is the core concept of the relational model. It was codified as the numbered Rule 8 fifteen years later in Edgar Codd's twelve rules: Rule 8: Physical data independence:

Application programs and terminal activities remain logically unimpaired whenever any changes are made in either storage representations or access methods.

The ANSI/SPARC Architecture from that era formalized it and walled off internal storage as well. So, the relational model was designed, from day one, to hide the disk.

Back to modern times and modern RDBMS implementations.

Riddle me this: We can write a SELECT, a JOIN, a WHERE, and even an INSERT, UPDATE, or DELETE, and in most cases it will be executed with the same results on different RDBMS engines from different vendors. That is called ANSI/ISO standardized SQL, which goes to show the real separation between the model and the physical implementation. Virtually every RDBMS implements the same ANSI/ISO SQL standard, but with dialect differences at the edges. Those differences can sometimes be significant, but the core of the language is the same, and that is what matters here. The same SELECT statement can run on MySQL, PostgreSQL, SQL Server, Oracle, and so on, given that you have the same logical model implemented in each of them.

But the abstraction doesn't stop at the SQL language. The relational model and the RDBMS implementations built on top of it are designed to be agnostic to the underlying storage device. For example:

Martin writes: "To mitigate the time delay imposed by disks, you need indexes...". Then why are in-memory databases full of indexes? Why are they (in-memory databases) a thing at all? Because an index was never about the medium - it is about not scanning every row when you want one, be it on disk or in RAM.

As we can see, different devices, different formats, even machines and clusters, or even no machines at all — the same logical model, the same SQL, the same results. The RDBMS already abstracts storage for you. It is designed to do exactly that. Thank you very much, but you are wrong.

The Cost

Cost is never ending persistence-ceremony. We are asked to carefully construct our data models in memory (to maintain the illusion of encapsulation), protect it from illogical and unwanted changes, and then save it to the database (persist it) when the time is right.

It is hard to overstate how big this persistence-ceremony thing really is. The number of specialized patterns, libraries, countless tutorials, videos, entire philosophies, strategies, etc. But take a look at this trivial example:

This is a simple SQL statement to update a transfer approval - two people must sign off, they must be different people, and the status moves from pending to partly to fully approved. In a traditional codebase this would be a considerable chunk of code spanning multiple files and doing several database calls, just to maintain the illusion.

sql
sql
update transfer_approvals
+set
+  status = case
+    when status = 'pending' then 'partly_approved'
+    when status = 'partly_approved'
+         and approver1 is distinct from $1 then 'fully_approved'
+    else status
+  end,
+  approver1 = case when status = 'pending' then $1 else approver1 end,
+  approver2 = case
+    when status = 'partly_approved'
+         and approver1 is distinct from $1 then $1
+    else approver2
+  end
+where 
+  transfer_id = $2
+  and status in ('pending', 'partly_approved')
+returning status;

We don't know where those transfer approvals are. Is it stored on a disk? What disk is it on? Is it in memory, what format it's in, or even what machine it is on, and we don't care. That is not the point. This is a DECLARATION. We have just declared business rules for how to update transfer approvals.

It has nothing to do with storage as far as the application is concerned. And since our RDBMS engine hides and abstracts all the storage details and does all the persistence work for us, what are we left with then? That's right: the business logic and business rules. Just a declaration of how our transfer approvals should be updated in this case. Nothing else. No ceremony, no plumbing, no persistence layer, no storage, no nothing.

The entire pile of code which is now gone with this move is the following:

Every one of those exists for one reason only: to carry state from memory to storage and back. Remove that silly belief, and every one of those big code blocks removes itself.

3) Data Structures Abstraction Misconception

The Claim

If we already have established two previous claims — that the state lives in memory and that the database is a storage device — then data itself must be an in-memory data structure as well. And, if we are going to perform operations on it, protect the state, mutate, and so on, then we need to have it in memory, and only suitable in-memory data structures will do, period.

Direct quote from Eric Evans, Domain-Driven Design (2003), Part II "Building Blocks of a Model-Driven Design," Chapter 6 "The Lifecycle of a Domain Object," in the section on Repositories (p. 108), he writes:

For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type.

Screenshot from Eric Evans, Domain-Driven Design (2003), p. 108, showing the Repository definition
Eric Evans, Domain-Driven Design (2003), p. 108.

The word Evans uses: illusion. Not a real collection, oh no, just the illusion of an in-memory structure. This should tell you everything now: You should only build an elaborate pattern to simulate an in-memory collection if the real data collection was never in memory to begin with.

The Repository exists precisely because the objects live in the database, and its entire job is to make them look like they are sitting in memory. Fake it until you make it, except you will never make it.

The repository pattern is an admission in structural form: relational data, dressed up to pass as an in-memory data structure (so we can do OOP on it).

This is not a coincidence nor an isolated quote. For example, in the book Patterns of Enterprise Application Architecture (2002), Martin Fowler writes (source: https://martinfowler.com/eaaCatalog/repository.html):

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

Acting like an in-memory domain object collection? Why do we want to force relational data into an in-memory collection? Just for good measure, let's check out Microsoft's recommendation in their Software Architecture e-book, in a part that teaches us how to "Design the infrastructure persistence layer." Microsoft echoes Fowler almost word for word, putting it as a set of domain objects in memory.

A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory.

And then they continue:

Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. Once the entities are in memory, they can be changed and then persisted back to the database through transactions.

Again, just goes to prove the point above - real state is in the database, we are simply mandated to load temporary chunks into memory - in order to maintain the illusion of encapsulation and illusion of in-memory data structure.

Perhaps we might end up with an illusion of the entire software solution?

In any case, more than a decade after Evans and Fowler had laid out this machinery that simulates in-memory collections, the mismatch still had not been solved. We know this because in 2014, the field's leading DDD experts from around the world gathered at the DDD eXchange conference in NYC to figure out how to do DDD better and, finally, solve this Object-Relational Impedance Mismatch. Because you do not hold a summit to solve a problem you have already solved.

One of the speakers, renowned DDD expert Vaughn Vernon, gave a talk called "The Ideal Domain-Driven Design Aggregate Store?" where he proposed a final solution to the O/R Impedance Mismatch problem:

During the park bench discussion I promoted the idea of serializing Aggregates as JSON and storing them in that object notation in a document store. A JSON-based store would enable you to query the object’s fields. Central to the discussion, there would be no need to use an ORM. This would help to keep the Domain Model pure and save days or weeks of time generally spent fiddling with mapping details. Even more, your objects could be designed in just the way your Ubiquitous Language is developed, and without any object-relational impedance mismatch whatsoever. Anyone who has used ORM with DDD knows that the limitations of mapping options regularly impede your modeling efforts.

Screenshot from Vaughn Vernon, 'The Ideal Domain-Driven Design Aggregate Store?', proposing JSON-serialized Aggregates in a document store
Vaughn Vernon, "The Ideal Domain-Driven Design Aggregate Store?"

Framing in this case is that the O/R mapping tools and libraries are the main source of impedance mismatch, and if we could just get rid of them, then we would have solved the problem. No O/R mapping, no O/R mapping at all, and no impedance mismatch. That's the idea. In essence, the proposed data design is this:

That's it. That is the "ideal aggregate store" solution to the impedance mismatch problem.

Now, to be fair, Vaughn Vernon is not saying that RDBMS = file system. He does propose using Postgres for ACID and JSON querying. He does want to keep the relational engine. Just not the relational model.

And that is the whole trick. We have solved the Object-Relational impedance mismatch by removing the relational part entirely — and keeping the Object part, obviously. No relations, no foreign keys, no joins, no set operations, no nothing. Just a flat collection of objects, serialized, frozen to disk, and fetched by id. The illusion of an in-memory collection, made real at last.

So, yeah, the solution to the O/R impedance mismatch is to remove the R (relational part) entirely, and just have a key-value store with JSON blobs.

That was proposed ten years ago. Does anyone use that today? Is that how the industry builds? I don't think so. Twenty years after Evans and Fowler, the mismatch sits exactly where it started. We never solved it.

The Reality

Let's get one thing out of the way immediately, because I don't want to win this argument by cheating.

A table is a set. That is not a metaphor — it is the mathematical definition. Codd's 1970 paper defines a relation as a subset of the Cartesian product of domains: a set of tuples.

Screenshot from E.F. Codd, 'A Relational Model of Data for Large Shared Data Banks,' defining a relation as a subset of the Cartesian product of domains
Codd, E.F., "A Relational Model of Data for Large Shared Data Banks," CACM 13(6), June 1970, pp. 377–387, §1.3.

And SQL is an algebra over those sets — selection, projection, JOIN, UNION, INTERSECT, EXCEPT — a closed algebra, so every operation over sets returns another set you can keep operating on.

All true. And here is the problem with building the argument on that: an in-memory collection can do all of it too. LINQ in C# ships Join, GroupJoin, Union, Intersect, Except, Distinct, GroupBy. Those are not arbitrary method names — those are Codd's operators, reimplemented over IEnumerable. And every other ecosystem rebuilt some version of the same algebra over its collections; LINQ just did it most completely.

So if the impedance mismatch were about operations — about what you can do with the data — it would have been solved around 2007, when the operators finished porting. Case closed, everybody go home.

The mismatch is still here. Which means it was never about the operations.

Here is what the table has that no collection in your process can have. Not what it does — what it is:

  1. It is the record. As established in the first misconception: the row is not a representation of the state, it is the state. Your collection is a copy of it, taken at load time.

  2. It is shared. Every process, every writer, every background job operates on the same table, under the engine's arbitration. Your collection is private to one process. The other writers don't know it exists, and they are not waiting for it.

  3. It is durable and live. The table existed before your process started, will exist after it dies, and keeps changing under other writers the whole time. Your collection is a photograph. The table is the thing being photographed — and it kept moving after the shutter clicked.

  4. It is guarded. CHECK, UNIQUE, NOT NULL, foreign keys — enforced transactionally, against every writer, no exceptions. Your in-memory validation binds exactly one thing: your copy. The next writer does not inherit your discipline.

And now the key observation, the one this whole chapter hangs on: not one of these four is an operation.

LINQ could port Join because join is a function — values in, values out. Pure computation travels; you can implement it anywhere. Being the shared, durable, guarded record is not a function. There is no method you can add to List<T> that makes it be the authoritative state. You can port an operator. You cannot port a status.

Notice where the line falls between what made the trip into memory and what didn't. Everything that is algebra — join, filter, group, project — ported over just fine. Everything that is state — the transaction, the arbitration, the constraints, the durability — never left the database. It can't. The line between them is exactly the line we drew in the first misconception: computation versus state.

And if you want this confirmed by the industry itself: the most serious application of LINQ's relational operators is LINQ-to-Entities, which takes your C# expression tree and compiles it back into SQL, to send to the database. We rebuilt the algebra in memory, and its main job turned out to be translating itself back — because the algebra made the trip, and the data never did.

Which puts the domain object model in a completely new light. It is not an alternative data structure for your data. Look at the machinery again, piece by piece:

The object model buildsTo simulate
Repositorythe table
Identity mapthe primary key
Navigation propertiesforeign keys and joins
Unit of workthe transaction
Change trackerwhat UPDATE ... SET already knew
In-memory validationCHECK, UNIQUE, and foreign key constraints

That is not a different model. That is the same model — the database — re-implemented in RAM, minus the four properties that made it meaningful. The domain model is a simulation of the database, running inside your process. Evans told us himself, remember: the illusion of an in-memory collection. And the real thing sits two feet away the entire time, doing all of it correctly, under ACID, for every process at once. We call the copy "the domain" and the authority "a detail."

To be precise about what I am not claiming, before somebody builds a strawman out of it:

In-memory data structures are not the problem. Pure computation over values you were genuinely given is exactly what memory is for — take inputs, compute, return outputs. The game scene and the compiler syntax tree from the first misconception live in memory legitimately, because they are the state of those systems. LINQ over data you rightfully hold is wonderful.

Caching is not the problem either — honest caching. A cache knows it is a copy. It has a TTL, an invalidation story, and it never claims to be the truth.

The sin is narrow and specific: a copy that claims to be the authority and has no answer for the moment it goes stale. That is not a cache, and it is not a data-structure choice. That is a simulation impersonating the thing it copied.

Hold on to that word — impersonating — because it explains something two decades of framework engineering could not fix. Every famous ORM pathology you have ever fought is not a separate bug with a separate fix. It is one failure with many faces: a simulation forced to behave like the authority it impersonates. Let's count the faces.

The Cost

What follows are not four independent grievances. It is one impossibility wearing four costumes.

Copy, not record → staleness and write amplification

The object must be hydrated before it can be mutated — that is the whole contract. So to change one column, the ORM does a SELECT followed by an UPDATE: load the row (usually the whole aggregate — scroll back to that Reddit picture and count the objects), let the object "decide" in memory, write it back. Remember the transfer approval from the previous chapter: one UPDATE statement, zero preliminary round trips, the rule declared right where the state lives. The simulated version is load → check in memory → mutate in memory → save. And between load and save, the row is free to change under you.

Hence lost updates. Hence the "optimistic concurrency token" — a rowversion or xmin column added to the schema not because the business needs it, but to detect that your copy lied to you between load and save. The token is a confession written in DDL: the copy is not the record, and we know it.

Private, not shared → arbitration lives in the database anyway

Two requests load the same invoice. Each holds a private snapshot; each decides from it; both decisions are "valid" in memory, and one of them is wrong in reality. The decision your domain model makes is provisional — the only binding decision happens in the database, under a lock, inside a transaction, where arbitration has lived all along. The version-token "rescue" concedes exactly this: the object proposes, but the WHERE clause of the write disposes. Now add a background job, a second service, a nightly import — and the in-memory "state" stops being state at all. It is one process's guess about what the state was, some number of milliseconds ago. (Much more on this in the concurrency and integrity misconception.)

Graph walk, not set operation → the access-pattern tax

We conceded that collections have the operators. But the object model's shape pushes you away from them: objects navigate. customer.Orders, order.Lines — walking references one object at a time, each step a query you didn't see, fired from behind a property getter. One JOIN becomes a hundred SELECTs, and the call site looks innocent. This is N+1, the most documented performance pathology of the last two decades. And the fixes bill you separately: eager-load with Include and over-fetch half the database, or project into DTOs — at which point you are writing relational queries again, in C#, so that a library can compile them back into the SQL you were abstracting away. The abstraction gets abandoned at exactly the moment it gets tested.

Simulation, not engine → the capability ceiling

Window functions. Recursive CTEs — foreign keys form a graph, and the engine will walk arbitrary-depth hierarchies for you, declaratively. GROUPING SETS, lateral joins, partial and expression indexes, set-based bulk updates. And underneath all of it: a cost-based planner with live statistics about your actual data, choosing between a hash join, a merge join, and an index scan — per query, per data distribution. The simulation has none of this and cannot grow it. This one isn't even a defect — it is simply what a private copy in RAM lacks next to a database engine.

The standard reply — "but EF has raw SQL escape hatches" — is my argument wearing a different hat. If the object model were the system of record, there would be nothing to escape to. The moment you drop to SQL for the hard 20%, you have admitted where the real system was all along. The escape hatch is not a counterexample. It is the proof.


Now step back and look at the four together. None of them is an implementation defect. Hibernate is twenty-five years old; Entity Framework is eighteen. Some of the best engineers in the industry have been sanding these edges for two decades, and every one of these problems is still here — because they are not bugs in the simulation. They are the simulation working correctly: behaving exactly like what it is — a private, transient copy — instead of what it plays: the shared, durable record. That gap does not close with effort, because it is not made of code. It is made of what the two things are.

And that is Neward's quagmire, stated mechanically. An escalating investment that cannot win — not because the enemy is strong, but because a copy cannot out-invest its way into being the original.

4) Abstraction Over Algorithms

The Claim

Back in the Wikipedia section, I promised that behavior is a different problem axis and that we would come to it when we get to algorithms. Here we are.

The first three misconceptions were about data. This one is about behavior. The claim goes like this: fine, the data may sit in the database — but the algorithms, the logic, the behavior of the system, those belong in application code. SQL fetches; code computes. The database is where data sleeps, and the application is where it wakes up.

Martin Fowler wrote an article about this exact question back in February 2003, called "Domain Logic and SQL". It opens with an honest description of the mainstream attitude:

Many application developers, particularly strong OO developers like myself, tend to treat relational databases as a storage mechanism that is best hidden away.

There it is again — the storage device from the second misconception — but this time the subject is logic. To his credit, Fowler takes the SQL option far more seriously than most of his readers ever did. And still, the verdict:

Personally I don't think performance should be the first question. My philosophy is that most of the time you should focus on writing maintainable code.

With a warning label attached:

I would suggest that if you go the route of putting a lot of logic in SQL, don't expect to be portable — use all of your vendors extensions and cheerfully bind yourself to their technology.

And a concession that defines SQL's proper place in this worldview:

If you use an in-memory approach and have hot-spots that can be solved by more powerful queries, then do that.

So there is the claim, in its most reasonable and balanced form, from its most reasonable and balanced proponent: logic in memory is the default — maintainable, portable, testable. Logic in SQL is the exception — a performance hot-fix, to be applied reluctantly, hot-spot by hot-spot. Twenty-three years later, this is still the mainstream position, and most codebases you will open this week are built on it.

The Reality

SQL is a programming language. A declarative one, but a programming language.

Look back at the transfer approval statement from the second misconception. Two approvers, they must be different people, the status walks from pending to partly to fully approved. That is not "fetching data." That is behavior — an algorithm, expressed as a declaration, executed next to the data, atomically.

Now ask what the algorithms of a business system actually are. Strip away the ceremony and it is overwhelmingly this: filter, join, group, aggregate, rank, deduplicate, walk a hierarchy, compute something over ordered data. Which is, item for item, exactly what SQL was designed to express. A running balance, for example:

sql
sql
select
+  customer_id,
+  transaction_date,
+  amount,
+  sum(amount) over (
+    partition by customer_id
+    order by transaction_date, transaction_id
+  ) as running_balance
+from transactions;

The in-memory version of this algorithm: fetch every transaction over the wire, group by customer in a dictionary, sort each group, loop and accumulate — plus the memory footprint, plus deciding what happens on the day the table stops fitting in RAM. The declarative version is the window function above. Ranking, top-N-per-group, gaps in sequences, running totals, year-over-year — window functions. Org charts, bills of materials, category trees — recursive CTEs: declare the traversal, and the engine walks the graph. With recursive CTEs, SQL is Turing-complete — not that you should compute Fibonacci in it, but "SQL can't express my logic" stopped being true decades ago.

But here is the part that actually settles the argument, and it is not expressiveness. When you write the loop, you are writing one algorithm, frozen at commit time. When you write the declaration, the engine writes the algorithm — at runtime, with a cost-based planner and live statistics about your actual data. Hash join, merge join, nested loop; index scan or sequential scan; parallel workers or not — chosen per query, per data distribution, and re-chosen as the data grows. Your hand-written loop was a perfectly good plan at ten thousand rows. At ten million it is a catastrophe, and it will not adapt, because it is code — someone has to notice it, profile it, and rewrite it. The declaration just quietly gets a new plan.

Nobody would hand-write three join algorithms plus a statistics-driven optimizer to choose between them in the service layer. That machinery already exists. It sits directly under the data — and the claim instructs us not to use it.

Which leaves the claim's justifications, so let's take them in order. Maintainability: is the eight-line window function really less maintainable than the same algorithm spread across a repository, a service method, and a mapping profile? "Maintainable" is not a synonym for "written in my favorite language." Portability: the second misconception already dealt with that — the core of SQL is an ANSI/ISO standard that runs on every engine, while your domain layer is portable to exactly nothing; nobody in recorded history has swapped C# + EF for Java + Hibernate because the code was so nicely decoupled. Testability: SQL is testable — pgTAP exists, and the oldest trick in the book still works: open a transaction, run the test, roll back. Besides, when you mock the database out of a test of data logic, look at what is left standing: you are testing the simulation from the previous misconception, not the system.

The Cost

Every other corner of the industry has a name for this cost: moving data to compute instead of moving compute to data. The entire big-data field was built on the lesson that you ship the algorithm to where the data lives, because the other direction does not scale. Business software orthodoxy teaches the other direction as a best practice.

So we pay, in four installments:

And the punchline is already inside the claim itself. SQL is admitted as the exception, for hot-spots — and then, over the life of the system, every part that matters turns out to be a hot-spot. One by one, the pieces that count get rewritten in SQL anyway, by tired people, during incident reviews. It is the same concession we saw with the raw-SQL escape hatch in the previous misconception: the exception clause ends up doing all the load-bearing work. At some point, the honest question is why the exception is not the architecture.

5) Abstraction Over Concurrency and Integrity

The Claim

DDD's answer to data integrity is the aggregate. Eric Evans, Domain-Driven Design, Chapter 6:

An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.

The aggregate root guards the boundary and enforces the invariants — the business rules that must never be broken. That Reddit picture from the first misconception is exactly this: Article at the root, guarding its packages, price periods, and quantity ranges, with isValid() standing watch.

Vaughn Vernon — the same Vaughn Vernon from the aggregate store — codified the discipline in Implementing Domain-Driven Design (2013) and the "Effective Aggregate Design" essays it grew from. He is admirably precise about it:

An invariant is a business rule that must always be consistent.

A properly designed Aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction.

Thus, Aggregate is synonymous with transactional consistency boundary.

Along with the rule that turns it into a discipline:

A properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases.

So the claim: invariants — the rules that must always hold — are enforced by the domain model, in memory, one aggregate at a time. The community even has a slogan for it: the always-valid domain model.

But read Vernon's third sentence again, slowly, because something remarkable is happening in it. "Aggregate is synonymous with transactional consistency boundary." The pattern defines itself as a transaction. Hold that thought.

The Reality

First things first: the goal is completely right. Invariants must hold — that was never in dispute; it is the same reasonable instinct we already conceded in the first misconception. The question was never whether to enforce invariants. The question is where enforcement actually binds.

An invariant enforced in memory binds exactly one process — property four of the data structures misconception: the next writer does not inherit your discipline. And it binds at exactly one moment — validation time. Between the check and the write, the world keeps moving.

The canonical example, the one every team eventually learns in production: usernames must be unique. The domain model checks — no such username, valid — and inserts. Two concurrent registrations both check, both pass, both insert. The "always-valid" model just produced invalid data, twice, without a single line of it misbehaving. Check-then-act on a private snapshot is a race by construction — TOCTOU, time-of-check to time-of-use, a bug class old enough to have its own acronym. And notice what every team actually does about it: they add a UNIQUE constraint. The engine catches what the model cannot.

It is worth being precise about why the engine can do what the model cannot: it is the only party that sees every writer. Which makes it the only place where invariant machinery means anything: NOT NULL, CHECK, UNIQUE, foreign keys, EXCLUDE — wrapped in transactions, with isolation levels up to SERIALIZABLE, where concurrent transactions are guaranteed to behave as if they had run one at a time. Enforced against every writer: your application, the second instance behind the load balancer, the background job, the DBA at 2 a.m. No exceptions, and no discipline required.

Time to pay the debt from the first misconception. The hardest invariant in that Reddit aggregate: price periods must not overlap. isValid() can inspect its own copy — while another process commits an overlapping period it has never heard of. Here is the entire invariant, declared:

sql
sql
create extension if not exists btree_gist;
+
+alter table price_periods
+add constraint price_periods_no_overlap
+exclude using gist (
+  package_id with =,
+  daterange(date_begin, date_end, '[]') with &&
+);

Two periods for the same package with overlapping date ranges can now not exist. Not "will be caught, provided the request comes in through the domain layer" — cannot exist. Under any concurrency, from any writer, forever. One declaration. That is what enforcing an invariant actually means.

And now unhold that thought from the claim. Aggregate is synonymous with transactional consistency boundary. The transaction is a database concept. The pattern's own definition concedes that invariant enforcement is transaction work — it just redraws the transaction as an object graph, in one process's private memory, where it can see no other writer and therefore enforce nothing. The aggregate is a hand-drawn picture of a transaction. The database has the real ones — and the real ones can span whatever rows and tables the invariant actually needs, not just one object cluster.

The Cost

Everything is enforced twice — or worse, once, in the wrong place. The same rules live in C# validation and in database constraints, drifting apart release by release — the behavioral edition of the duplicated models from the first misconception, same bill, new line item. And the team that takes "always-valid" at its word and skips the constraints has it worse: their invariants are now enforced nowhere. They hold only in the absence of concurrency — which is to say, they are not enforced. They are observed, until further notice.

The races ship. Check-then-act bugs pass every unit test, because in the test the domain model really is alone — the race needs a second writer, and the test suite proudly mocks that out. The mock removes the exact enemy the invariant exists to fight. So the bug appears only under production load, intermittently, and is discovered as corrupted data weeks later: the duplicate payment, the double-booked slot, the negative stock. Every experienced engineer has one of these stories, and in every single one of them, the domain model passed all of its tests.

The aggregate-boundary industry. Vernon's rule — one aggregate instance per transaction — voluntarily outlaws the multi-row, multi-table atomicity the engine offers natively. So what happens when a real invariant spans two aggregates? A whole discipline unfolds: redesign the boundaries, or accept eventual consistency between aggregates, coordinated through domain events, process managers, sagas, compensating actions. An entire detect-and-compensate machinery, invented to route around BEGIN ... COMMIT. The database would have held both rows in one transaction — the real kind — and gone to lunch.

And with that, the five misconceptions close into a single picture. The state lives in the database (1), which already abstracts its own storage (2). Its tables cannot be replaced by in-memory structures — only impersonated by them (3). Its language already expresses the algorithms (4), and its transactions and constraints are the only invariant enforcement that actually binds (5). Every axis the modern application layer re-implements is an axis the engine already owns. The impedance mismatch was never a mapping problem between two equal worlds. It is the ongoing cost of running a simulation of one world inside the other — and calling the original "a detail."

`,213)]))}const f=t(h,[["render",d]]);export{b as __pageData,f as default}; diff --git a/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.lean.js b/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.lean.js new file mode 100644 index 000000000..808a53100 --- /dev/null +++ b/assets/blog_DRAFT-anniversary-vietnam-of-computer-science.md.D7rY_yx-.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as i,a5 as s}from"./chunks/framework.CgT1UzWm.js";const n="/ddd-agggrate-transparent.webp",o="/vietnam/evans.png",r="/vietnam/vernon.png",l="/vietnam/codd.png",b=JSON.parse('{"title":"DRAFT: 20th Anniversary of The Vietnam of Computer Science","titleTemplate":"NpgsqlRest","description":"DRAFT — A compilation of a decade of arguments about DDD, Clean Architecture, the wrong abstractions modern business software is built on, and the case for putting the database back where it belongs.","frontmatter":{"layout":"doc","outline":[2,3],"title":"DRAFT: 20th Anniversary of The Vietnam of Computer Science","titleTemplate":"NpgsqlRest","description":"DRAFT — A compilation of a decade of arguments about DDD, Clean Architecture, the wrong abstractions modern business software is built on, and the case for putting the database back where it belongs.","badge":"human","head":[["meta",{"name":"robots","content":"noindex, nofollow"}],["meta",{"name":"keywords","content":"npgsqlrest postgresql clean architecture ddd database-first state abstraction sql platform business rules"}]]},"headers":[],"relativePath":"blog/DRAFT-anniversary-vietnam-of-computer-science.md","filePath":"blog/DRAFT-anniversary-vietnam-of-computer-science.md"}'),h={name:"blog/DRAFT-anniversary-vietnam-of-computer-science.md"};function d(c,e,p,m,u,g){return i(),a("div",null,e[0]||(e[0]=[s("",213)]))}const f=t(h,[["render",d]]);export{b as __pageData,f as default}; diff --git a/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.js b/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.js new file mode 100644 index 000000000..27a875d09 --- /dev/null +++ b/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.js @@ -0,0 +1,36 @@ +import{_ as t,C as a,c as i,o as n,a5 as l,G as r}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse(`{"title":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers","titleTemplate":"NpgsqlRest","description":"NpgsqlRest and SQLPage are both SQL-first tools that connect straight to your database — but one builds REST APIs and the other builds web UIs. An honest comparison of where each fits, and why they're often complementary.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers","titleTemplate":"NpgsqlRest","description":"NpgsqlRest and SQLPage are both SQL-first tools that connect straight to your database — but one builds REST APIs and the other builds web UIs. An honest comparison of where each fits, and why they're often complementary.","head":[["meta",{"name":"keywords","content":"npgsqlrest vs sqlpage, sqlpage alternative, sql web framework, sql rest api, sqlpage rest api, build app with sql, sql-first tools, postgresql sql ui"}],["meta",{"property":"og:title","content":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"}],["meta",{"property":"og:description","content":"Both are SQL-first and connect straight to the database — but SQLPage renders UIs and NpgsqlRest serves APIs. Where each fits, and why they're complementary."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"}],["meta",{"name":"twitter:description","content":"SQLPage renders web UIs from SQL; NpgsqlRest serves REST APIs from SQL. Different layers of the stack — and often complementary."}]]},"headers":[],"relativePath":"blog/DRAFT-npgsqlrest-vs-sqlpage.md","filePath":"blog/DRAFT-npgsqlrest-vs-sqlpage.md"}`),p={name:"blog/DRAFT-npgsqlrest-vs-sqlpage.md"};function o(h,s,d,c,g,k){const e=a("BlogNav");return n(),i("div",null,[s[0]||(s[0]=l(`

NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers

Comparison · SQLPage · SQL-First · June 2026


Most comparisons on this site put NpgsqlRest next to other tools that do the same job — turning PostgreSQL into a REST API. PostgREST and Supabase are direct competitors in that sense: all three answer the same question.

SQLPage is different. It shares NpgsqlRest's core idea — write SQL, not application code — but it spends that idea on the other end of the stack. SQLPage renders web UIs from SQL. NpgsqlRest serves REST APIs from SQL. So this isn't a "which one wins" piece. It's a map of where each one fits — and they fit together more often than they compete.

The shared idea

Both tools belong to the same family: logic and data live in the database, and a single self-contained binary turns SQL into something a browser can use — no ORM, no hand-written backend, no separate runtime.

Shared foundationNpgsqlRestSQLPage
SQL-first (.sql files are the unit of work)
Single self-contained binary✅ (.NET)✅ (Rust)
Connects directly to the database
Built-in authentication & sessions
File uploads
CSV export
Open source✅ (MIT)
Trivial deployment (copy binary, run)

If you like the SQL-first philosophy, you'll feel at home in either. The difference is what comes out of the binary.

The fork in the road

mermaid
flowchart LR
+    subgraph SP [SQLPage]
+        direction LR
+        S1[".sql file"] --> S2["UI components"] --> S3["HTML page"]
+    end
+    subgraph NR [NpgsqlRest]
+        direction LR
+        N1[".sql file / function"] --> N2["REST endpoint"] --> N3["JSON + typed client"]
+    end

SQLPage maps SQL results to UI components and streams back an HTML page. Each .sql file is a page. You pick a component (list, card, chart, form, table, map…) and feed it rows. There is no separate frontend — SQLPage is the frontend.

NpgsqlRest maps SQL files and functions to REST endpoints and returns JSON. Each .sql file or routine is an API endpoint. There is no UI — you consume the API from your own frontend (often with a generated TypeScript client), a mobile app, or another service.

That single difference — HTML out vs JSON out — is what everything else follows from.

Where SQLPage is stronger: the UI

This is SQLPage's whole identity, and it's genuinely strong. Building a page is a couple of SELECTs:

sql
sql
-- users.sql — a complete, working web page
+SELECT 'list' AS component, 'Users' AS title;
+SELECT name AS title, email AS description, '/user.sql?id=' || id AS link
+FROM users
+ORDER BY name;

Want a bar chart instead? Change the component name:

sql
sql
SELECT 'chart' AS component, 'Monthly sales' AS title, 'bar' AS type;
+SELECT month AS x, total AS y FROM sales ORDER BY month;

SQLPage ships 50+ components — tables, cards, forms, charts, maps (with PostGIS/Spatialite), timelines, carousels, steps — plus password auth, form handling, and even multi-database support (PostgreSQL, MySQL/MariaDB, SQLite, SQL Server, and ODBC sources like DuckDB, ClickHouse, Snowflake, and BigQuery). For an internal dashboard, an admin panel, or a data-entry tool, you can have a working interface in minutes with zero frontend code.

NpgsqlRest does not do this. It does not render SQL results into UI components. What it offers on the frontend side is the plumbing to build your own UI:

That's useful, but it's not a UI builder. If "render a UI from SQL" is the goal, SQLPage wins decisively.

Where NpgsqlRest is stronger: the API

SQLPage can emit JSON — it has a json component, and with sqlpage.request_method() you can branch on GET/POST/PUT/DELETE and hand-roll an endpoint:

sql
sql
-- A JSON endpoint in SQLPage — manual, one file at a time
+SELECT 'json' AS component, 'array' AS type;
+SELECT id, name, email FROM users ORDER BY name;

That's fine for a simple read endpoint. SQLPage routes it automatically — like NpgsqlRest, dropping a .sql file gives you a URL with no route registration. But the file is an imperative script that happens to output JSON, not a declared endpoint. There's no inferred contract — no typed parameters, no OpenAPI, no client codegen, no per-endpoint policy. Every method check, status code, and header is something you write by hand in SQL.

NpgsqlRest is built around the API. The same kind of endpoint is generated automatically, and configured declaratively in a SQL comment:

sql
sql
create function api.get_user(p_id int)
+returns setof user_info
+language sql
+begin atomic;
+select id, name, email, role from users where id = p_id;
+end;
+
+comment on function api.get_user(int) is '
+HTTP GET /users/{p_id}
+@authorize admin, user
+@cached
+@cache_expires_in 300
+@rate_limiter_policy standard
+';

And it brings the rest of the API platform with it:

API capabilityNpgsqlRestSQLPage
File-based routing (drop a .sql file, get a URL)
Expose existing functions/procedures as endpoints (no file)
Inferred HTTP contract (typed params, method, response shape)❌ (imperative script per file)
Path parameters (/users/{id}), method routing, function overloading
OpenAPI / Swagger generation
TypeScript client codegen + .http test files
Per-endpoint caching (memory/Redis/hybrid)
Per-endpoint rate limiting (partitioned per user/IP)
Declarative auth schemes (JWT, encrypted Bearer/Cookie, OAuth, Passkey)⚠️ basic
Reverse proxy & HTTP client types (call external APIs from SQL)
Server-Sent Events streaming
Error policies (RFC 7807, per-endpoint status mapping)
Security headers, health checks, OpenTelemetry

⚠️ SQLPage has password/session auth aimed at protecting pages, not the multi-scheme token model an API needs.

If "serve a typed, policy-rich API to a frontend, mobile app, or other service" is the goal, NpgsqlRest wins decisively.

They're complementary, not rivals

Here's the part most "X vs Y" posts miss: because they target different layers, you can run both against the same PostgreSQL database — and it's a good architecture.

mermaid
flowchart TD
+    DB[("PostgreSQL")]
+    SP["SQLPage<br/>internal dashboards,<br/>admin tools, reports"]
+    NR["NpgsqlRest<br/>customer-facing REST API<br/>+ typed TS client"]
+    DB <--> SP
+    DB <--> NR
+    SP --> Ops[Internal users / analysts]
+    NR --> App[Public app / mobile / partners]

A common split:

Both read the same tables and call the same functions. Your business logic stays in one place — the database — which is the whole point of the SQL-first / business-rules-in-the-database approach both tools share.

When to choose each

Reach for SQLPage when:

Reach for NpgsqlRest when:

Reach for both when you have an internal side and an external side — which most real products do.

Conclusion

SQLPage and NpgsqlRest answer different questions with the same philosophy. SQLPage asks "how do I get a UI out of SQL?" and answers it beautifully. NpgsqlRest asks "how do I get a production API out of SQL?" and answers that. Neither is a worse version of the other — they sit one layer apart.

If you've already adopted the SQL-first mindset, the honest takeaway isn't "switch." It's: use SQLPage where you need a screen, use NpgsqlRest where you need an endpoint, and let both lean on the same PostgreSQL so your logic never gets duplicated.


`,49)),r(e,{"get-started":[{text:"Installation Guide",href:"/guide/installation"},{text:"Quick Start",href:"/guide/quick-start"},{text:"SQL File Source",href:"/config/sql-file-source"},{text:"TypeScript Code Generation",href:"/config/codegen"}]})])}const m=t(p,[["render",o]]);export{y as __pageData,m as default}; diff --git a/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.lean.js b/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.lean.js new file mode 100644 index 000000000..99be845f7 --- /dev/null +++ b/assets/blog_DRAFT-npgsqlrest-vs-sqlpage.md.DbSL1Z8X.lean.js @@ -0,0 +1 @@ +import{_ as t,C as a,c as i,o as n,a5 as l,G as r}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse(`{"title":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers","titleTemplate":"NpgsqlRest","description":"NpgsqlRest and SQLPage are both SQL-first tools that connect straight to your database — but one builds REST APIs and the other builds web UIs. An honest comparison of where each fits, and why they're often complementary.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers","titleTemplate":"NpgsqlRest","description":"NpgsqlRest and SQLPage are both SQL-first tools that connect straight to your database — but one builds REST APIs and the other builds web UIs. An honest comparison of where each fits, and why they're often complementary.","head":[["meta",{"name":"keywords","content":"npgsqlrest vs sqlpage, sqlpage alternative, sql web framework, sql rest api, sqlpage rest api, build app with sql, sql-first tools, postgresql sql ui"}],["meta",{"property":"og:title","content":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"}],["meta",{"property":"og:description","content":"Both are SQL-first and connect straight to the database — but SQLPage renders UIs and NpgsqlRest serves APIs. Where each fits, and why they're complementary."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"}],["meta",{"name":"twitter:description","content":"SQLPage renders web UIs from SQL; NpgsqlRest serves REST APIs from SQL. Different layers of the stack — and often complementary."}]]},"headers":[],"relativePath":"blog/DRAFT-npgsqlrest-vs-sqlpage.md","filePath":"blog/DRAFT-npgsqlrest-vs-sqlpage.md"}`),p={name:"blog/DRAFT-npgsqlrest-vs-sqlpage.md"};function o(h,s,d,c,g,k){const e=a("BlogNav");return n(),i("div",null,[s[0]||(s[0]=l("",49)),r(e,{"get-started":[{text:"Installation Guide",href:"/guide/installation"},{text:"Quick Start",href:"/guide/quick-start"},{text:"SQL File Source",href:"/config/sql-file-source"},{text:"TypeScript Code Generation",href:"/config/codegen"}]})])}const m=t(p,[["render",o]]);export{y as __pageData,m as default}; diff --git a/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.js b/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.js new file mode 100644 index 000000000..9c07579fc --- /dev/null +++ b/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.js @@ -0,0 +1 @@ +import{_ as o,C as n,c as a,o as i,a5 as s,G as r}from"./chunks/framework.CgT1UzWm.js";const f=JSON.parse('{"title":"Case Study: 74 Endpoints, Zero Backend Code — A Production App Built Entirely on NpgsqlRest","titleTemplate":"NpgsqlRest","description":"What it actually looks like to ship a production application without a controller layer. Real numbers from a finance/visualization app: ~74 HTTP endpoints, 12K LOC of SQL, zero lines of C# or Python, and an estimated 3,500–7,300 LOC of host-language boilerplate eliminated versus an equivalent ASP.NET Core build.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Case Study: 74 Endpoints, Zero Backend Code — A Production App Built Entirely on NpgsqlRest","titleTemplate":"NpgsqlRest","description":"What it actually looks like to ship a production application without a controller layer. Real numbers from a finance/visualization app: ~74 HTTP endpoints, 12K LOC of SQL, zero lines of C# or Python, and an estimated 3,500–7,300 LOC of host-language boilerplate eliminated versus an equivalent ASP.NET Core build.","head":[["meta",{"name":"keywords","content":"npgsqlrest case study, postgresql first development, no backend code, sql first architecture, asp.net core comparison, fastapi comparison, production npgsqlrest, webauthn sql, lines of code saved"}],["meta",{"property":"og:title","content":"Case Study: A Production App with Zero Backend Code"}],["meta",{"property":"og:description","content":"74 endpoints, 12K LOC of SQL, zero C# or Python. Real numbers from the first production-grade NpgsqlRest deployment we know of."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Case Study: 74 Endpoints, Zero Backend Code"}],["meta",{"name":"twitter:description","content":"What it actually looks like to ship a production application without a controller layer. Real numbers, honest tradeoffs."}]]},"headers":[],"relativePath":"blog/case-study-zero-backend-code.md","filePath":"blog/case-study-zero-backend-code.md"}'),d={name:"blog/case-study-zero-backend-code.md"};function c(l,e,h,p,u,g){const t=n("BlogNav");return i(),a("div",null,[e[0]||(e[0]=s('

Case Study: 74 Endpoints, Zero Backend Code

May 2026 · Case StudyArchitectureNpgsqlRest


Most of the blog posts on this site argue why you might want to build an application with NpgsqlRest. This one is different: it reports what happened when a team actually did, end-to-end, on a real product. The application is anonymized — it's a finance/visualization platform with time-series charts, expression-based metric computation, and per-user dashboards — but the numbers are unmodified, taken directly from the repository on the day this post was written.

It is, as far as we know, the first production-grade application built entirely on NpgsqlRest. No C#. No Python. No Node backend. The HTTP layer is the binary. Everything else lives in PostgreSQL.

What's in the repository

LayerFilesLines of code
Public API SQL (auto-exposed as HTTP)804,889
System / migrations / helpers SQL53~2,000
pgTAP-style SQL tests1104,756
Auto-generated TypeScript API client245,679
Hand-written frontend TypeScript~36~1,700
Svelte components4314,419
Hand-written backend host code (C#/Python/Node)00
Backend host config (appsettings.json)1217

Approximately 74 HTTP endpoints are exposed. There are no controllers, no DTO classes, no repository layer, no hand-written API client. The single appsettings.json replaces what would otherwise be a Program.cs plus DI registration plus route mapping plus auth pipeline configuration.

The application has shipped to production at build number 2.9.1517 — fifteen hundred production builds with no backend host code at any point in the project's history.

What NpgsqlRest is doing for them

Read through the repository and the workload NpgsqlRest absorbs turns out broader than most users probably realize from the docs:

Equally telling is what's available without writing host code and either already configured or queued for near-term addition. Two features being added next to the compute path illustrate how additions to this stack actually work:

The same applies to several other features the team has not yet needed but could enable with one config block and one annotation each: parameter validation rules (@validate), security headers (CSP / X-Frame-Options / Permissions-Policy via SecurityHeaders config), Kubernetes health probes (HealthChecks config), automatic OpenAPI/Swagger generation (OpenApiOptions), transparent column encryption (@encrypt / @decrypt) using ASP.NET Data Protection, parameter hashing for passwords (@parameter_hash), security-sensitive log obfuscation (@security_sensitive), antiforgery, response compression, command retry, CORS, external OAuth (Google / GitHub / Microsoft / LinkedIn / Facebook), and Excel/HTML table-format response rendering.

To be precise about what "configuration" means here: ASP.NET Core and FastAPI applications also configure these features — they configure them in startup code (Program.cs, DI registration, middleware pipeline ordering). NpgsqlRest configures them in layered JSON. The binary ships with a documented defaults baseline of roughly 2,900 lines covering every available knob; this project overrides it with appsettings.json (217 lines, production) and appsettings.development.json (88 lines, dev override that layers cleanly on top of prod — diagnostic logs turned up, codegen and HTTP-file generation enabled, WebAuthn relying-party pointed at localhost). Combined application-specific configuration: 305 lines of JSON. Combined application-specific startup code: zero lines.

The savings aren't that the features arrive magically. They're that they arrive pre-wired with sensible defaults, the override surface is uniform JSON rather than framework-specific DI ceremony, and prod-vs-dev layering is first-class. Every cross-cutting concern in the table below — caching, rate limiting, validation, security headers, health probes, OpenAPI — is enabled or tuned by adding keys to those 305 lines, not by modifying a startup pipeline.

The blast radius of "stop using NpgsqlRest" is therefore much wider than just losing the routing layer. It would mean replacing roughly a dozen distinct, individually load-bearing features and re-expressing 305 lines of declarative JSON as several hundred lines of imperative startup code.

The comparison: equivalent build in ASP.NET Core

The realistic alternative for a .NET-shop building this application would be ASP.NET Core with Minimal APIs or controllers, plus Dapper or Npgsql for data access (an ORM doesn't fit the workload — most of the endpoints return composite records or aggregated time-series data). A typical per-endpoint cost in that stack:

Conservatively: 40–80 lines of C# per endpoint, spread across 3–5 files. For 74 endpoints, that is ~3,000–6,000 LOC before any application-specific concern is addressed.

On top of the per-endpoint cost, the cross-cutting concerns NpgsqlRest already absorbs (or will absorb the moment a config block is added). Most of these features exist in classic ASP.NET Core too — built into the framework, exposed as attributes, or provided as official NuGet packages — so the LOC estimate assumes a disciplined .NET team using those built-ins, not reinventing them from scratch:

ConcernNpgsqlRestHand-rolled ASP.NET Core (honest LOC est.)
Program.cs / DI / routingconfig50–150 (Minimal APIs are dense)
Cookie authconfig10–30 (AddAuthentication().AddCookie())
WebAuthn / passkey ceremonies9 named SQL commands300–600 (Fido2.NET-Core plumbing, not built-in)
Data-protection keys → PostgreSQL2 SQL commands0–50 (filesystem default works for many cases)
TypeScript client generation + drift managementregenerated, free0–ongoing (NSwag CLI if you want it; otherwise hand-write the frontend client)
Stats / activity / index admin endpointsbuilt-in0 (optional; most apps don't have them)
SSE streaming endpoint(s)@sse annotation50–100 (Results.Stream, you own the protocol)
Parameter validation pipeline@validate annotation + rules in config20–50 ([Required], [Range], [StringLength] attributes are free)
Rate limiting (compute endpoint)config + @rate_limiter_policy annotation20–50 (.NET 7+ built-in AddRateLimiter())
Response caching (multi-backend, profiles, invalidation)config + @cache_profile annotation30–100 (ResponseCacheAttribute + IDistributedCache; Redis adds more if needed)
Health checks (Kubernetes probes)config10–30 (AddHealthChecks() built-in)
Security headers middleware (CSP, X-Frame, etc.)config10–30 (NuGet package + a few lines)
OpenAPI / Swagger documentationconfig5–20 (Swashbuckle is nearly free)
Retry, forwarded headers, antiforgery, compression, CORSconfig30–80 (mostly built-in middleware)
Cross-cutting subtotal~550–1,300

Adding the per-endpoint plumbing (~3,000–6,000 LOC for 74 composite/aggregated endpoints) to the honest cross-cutting subtotal gives a realistic ASP.NET Core equivalent total of ~3,500–7,300 lines of C# — plus a .csproj, a layered project structure, and a CI step to keep the generated TypeScript client in sync with the deployed API. None of this exists in the case-study repository.

A FastAPI / Python equivalent comes out leaner still — Pydantic is denser than C# DTOs, and FastAPI ships validation, OpenAPI, and dependency injection in the framework itself. WebAuthn plumbing (py_webauthn), rate limiting (slowapi), response caching (fastapi-cache + Redis), and SSE streaming still cost real lines, but the per-endpoint cost compresses substantially. Honest estimate: ~2,500–5,000 LOC of Python.

The earlier framing of this case study quoted higher figures (7,000–13,000 for .NET, 5,000–9,000 for Python). Those overcounted the cross-cutting layer because they assumed hand-rolled implementations of features that classic .NET / FastAPI actually ship with — built-in rate limiting, attribute-based validation, framework-managed health checks, Swashbuckle, and so on. The revised numbers above credit those frameworks honestly. The savings are still significant — 3,500–7,300 LOC is a meaningful slice of a full-stack codebase — but the LOC headline is smaller than it first appeared, and the real edge has to be argued qualitatively, not by raw line count alone.

How it scores on the four dimensions that matter

Productivity

The headline number isn't lines of code — it's that impedance mismatch is gone entirely. Both kinds.

The classic object–relational impedance mismatch — objects on one side, rows on the other, an ORM negotiating between them — doesn't exist here because there's no object layer. PostgreSQL composite types and JSON are the data model, end to end. The frontend receives the same shapes the database returns.

The less-discussed function impedance mismatch — the chain of translation layers between an HTTP request and the SQL that ultimately runs (controller → service → repository → ORM → SQL) — doesn't exist either. The SQL function is the endpoint. The generated TypeScript wrapper calls it directly. There are no intermediate functions whose signatures can drift from the layer below them, because there are no intermediate functions.

In practice, adding a column in a typical ASP.NET Core + EF Core path means: write a migration, update the entity, update the DTO, update the mapping, update the endpoint, regenerate the TypeScript client, fix any drift the regeneration surfaces. In this project: edit the SQL function, restart the dev server. The TypeScript client rewrites itself; the type checker fails the build on every line of frontend code that no longer matches.

Adding a new endpoint is: write one annotated SQL function. Restart. Done. There is no parallel set of files in the host language to keep in sync, because there is no host language.

The empirical signal that this works at scale is build number 2.9.1517. A development loop that is genuinely faster is the only thing that gets a small team to that build count.

One productivity dimension worth naming explicitly in the LLM-assisted era: this architecture is far more token-efficient. Adding a feature in a conventional N-layer stack requires loading the DTO, controller, service, repository, ORM mapping, frontend type definition, and frontend API client into the model's context (eight to ten files with strict cross-file consistency requirements), and asking the model to produce the change requires it to write the new code in lockstep across all of them. In this architecture, the same change is one SQL function plus one frontend component. Two files, no inter-layer consistency burden (the generator handles it), and far less boilerplate for the model to reproduce. An "add an endpoint" task that runs roughly 5,000 tokens in a typical ASP.NET Core or FastAPI codebase runs closer to 1,000–1,500 here — a 3–5× reduction that compounds across a year of AI-assisted feature work, in both inference cost and developer wait time. The same simplification that The Power of Simplicity post frames at the architectural level as raw energy savings shows up at the per-task level as token savings.

Time saved, quantified

The arguments above are structural. The same effect can be measured in hours and iteration-cycle time on the same repository — and the picture that emerges is stronger than the LOC table alone suggests.

One-time scaffolding avoided. In a hand-written stack, each endpoint costs roughly 20–35 minutes of pure typing: request interface (3–5 min), response interface (5–10 min), fetch wrapper with URL builder and serialiser (10–15 min), and an .http line for testing (3–5 min). Across the 74 endpoints exposed by this application, that is 25–45 hours of mechanical work the team never spent. The raw artifact size corroborates the estimate — the 5,679-line autogenerated TypeScript client plus an autogenerated .http test file is roughly the volume an experienced developer produces in that many hours on boilerplate (~125 LOC/hour).

The phrase "one-time" understates the saving in one important direction. The line counts in the table are the current snapshot of an API surface that has been reshaped many times as requirements — and the team's understanding of them — evolved. In a hand-written stack, every rename, every restructured return type, every endpoint that was later split, merged, or dropped would have required rewriting the corresponding client and interface code at the time of the change. NpgsqlRest regenerates the entire client on the next db-up, in full, regardless of how many iterations happened. The integral of typing avoided over the project's history is therefore meaningfully larger than the snapshot — earlier versions of these clients, including the ones that no longer exist, would all have been hand-written and rewritten in a conventional stack.

Ongoing maintenance avoided. Every backend signature change in a conventional stack means edits to the SQL, the request interface, the response interface, the fetch wrapper, and any callers — five files minimum, plus the inevitable type-drift bug when one of them is missed. Each such change costs roughly 5–15 minutes of cross-file editing, and the misses cost 30+ minutes of runtime debugging when an interface quietly lies about the shape the API actually returns. The NpgsqlRest cycle is edit SQL → restart → done; the regenerated client either matches or fails the type checker on every consumer at once. Across the lifetime of this codebase — through several significant refactors and feature additions spanning the auth surface, the visualization pipeline, and the user-management subsystem — there have been hundreds of signature-level changes. Even at a conservative 10 minutes saved per change × 200 changes, that is another ~30 hours of mechanical edits avoided, with the type-drift bug tail entirely absent.

Iteration-speed multiplier. This is the qualitative win, and it is the largest of the three:

The bottom line. Conservatively, 55–100 hours of typing saved across the lifetime of this project — and that "conservatively" matters: the figure is anchored to the current 74 endpoints, not to the larger set of endpoints that existed and were rewritten along the way as the product's requirements evolved. On top of that, an iteration loop that is roughly 5× faster on signature changes and 2–3× faster on end-to-end feature additions. None of those numbers were measured with a stopwatch — they are honest estimates from per-task timing and the actual change history — but they line up with the LOC totals and with the build cadence (1,500+ production builds, small team) the project has actually sustained. Combined with the token-efficiency multiplier described above, this is the kind of compounding gain that determines whether a small team can ship a feature surface this wide at all.

Lines of code saved

Net of the SQL the team would have written anyway (the business logic has to live somewhere), our honest estimate is ~3,500–7,300 lines of host-language code eliminated — roughly 3,000–6,000 of per-endpoint plumbing (DTOs, controllers, repositories, services, DI registrations across 220–370 files) plus another 550–1,300 of cross-cutting code that is configuration here and a mix of built-in middleware, attributes, and small custom wiring elsewhere. On top of that, the 5,679-line generated TypeScript client is a purely free byproduct.

The dominant slice of the saving — and the most defensible one — is the per-endpoint plumbing layer, not the cross-cutting infrastructure. Classic ASP.NET Core and FastAPI both ship with strong cross-cutting stories (built-in rate limiting, attribute-driven validation, framework-managed health checks, Swashbuckle, response caching). The compression NpgsqlRest provides on those concerns is real but modest. Where it dominates is on the architectural layer that classic stacks cannot compress: every endpoint still needs a request DTO, a response DTO, a controller, a repository, and a service in a hand-written stack, and those add up to thousands of lines that the SQL-as-endpoint model simply doesn't have.

One caveat: SQL functions tend to be denser than C# or Python, so a one-to-one LOC swap understates the effective savings. The eliminated code is overwhelmingly the low-value boilerplate — DTOs, mappers, route attributes, repository methods that are one query each — not the parts of an application where careful design pays off.

Where the real edge actually is — qualitatively. The LOC argument above tells part of the story. The structural wins, which are harder to put a number on but compound across the project's lifetime, are:

  1. No DTO layer at all. The largest concrete saving (~3,000–6,000 LOC). Attributes don't help here; you still need the DTOs in classic .NET.
  2. Per-endpoint annotations sit next to the SQL. @cache_profile, @rate_limiter_policy, @authorize on the function itself — same mechanism as [ResponseCache], far better locality than a controller four files away from the query.
  3. WebAuthn shipped as SQL functions. ~300–600 LOC of C# that genuinely doesn't exist in this project, because Fido2.NET-Core wiring is not built-in to the framework.
  4. Single source of truth. The schema is the contract. No DTO can lie about the database shape, no interface can drift from the response. This is a bug class that does not exist by construction — and the time-cost of those bugs is invisible in any LOC count.
  5. TypeScript client + types regenerated from the live schema. A free byproduct; in classic .NET you either run NSwag/Kiota (works, but a separate toolchain to maintain) or hand-write the client on the frontend.
  6. Layered prod-vs-dev JSON is first-class. Classic .NET has appsettings.{Environment}.json too, but in practice some of the environment-specific behaviour ends up in if (env.IsDevelopment()) branches in Program.cs. NpgsqlRest's overlay model puts every override in one place.

Performance

Strong, structurally. One database round-trip per request. No ORM materialization, no entity-graph allocation in the API process, no DTO mapping pass. Stored functions are precompiled in PostgreSQL. The transport layer is Kestrel + Npgsql, which is already at the top tier of TechEmpower-style benchmarks for the underlying primitives.

The performance-tuning work concentrates where it actually pays off: in the database. A recent optimization in this project rewrote one of the heaviest functions from PL/pgSQL with temp tables to plain SQL with CTEs and produced a measured 5.6× speedup (1.96 ms → 0.35 ms over 500 calls × 20 mixed inputs). That improvement is visible directly in pg_stat_statements. In a conventional stack, the same query is hidden behind ORM-generated SQL and the per-request cost is split across object materialization, JSON serialization, and HTTP middleware — all of which obscure where the actual time goes.

That kind of rewrite — a full implementation swap, a different language paradigm inside the function, and two unrelated correctness fixes folded in along the way — is exactly the change that goes badly wrong without a safety net. It didn't here, because the safety net already existed: the 110 SQL test files and 4,756 lines of assertions covered later in this post. The optimization shipped in the same commit as new test coverage for the edge cases the rewrite exposed. The tests run against the real PostgreSQL engine, against the real function being optimized, and finish in seconds. There is no faster correctness gate than that, and without it a 5.6× refactor is the sort of thing teams quietly decide isn't worth the risk. Performance work and test coverage are not two separate concerns in this architecture; they're the same loop.

Overall quality

End-to-end type safety is genuinely better than most hand-rolled stacks. PostgreSQL types map directly to TypeScript types via the generator. There is no DTO layer in the middle that can disagree with either the database or the frontend. Schema drift is impossible by construction: if a column is renamed, the generated client changes shape on the next dev restart, and every consumer fails to compile.

The schema is the single source of truth. There is no ORM mapping that can lie about it.

The test suite — 110 SQL test files totaling 4,756 lines — runs against the real PostgreSQL engine, not a mock or in-memory fake. Every assertion exercises the same code path that production uses. This is harder to do well than a conventional unit-test suite, but the tests that exist are categorically more truthful.

Honest tradeoffs

A case study that doesn't acknowledge tradeoffs is a sales pitch. After working through the obvious objections, two stand up:

That's the honest list. A few other objections look like tradeoffs at first but don't survive scrutiny:

What this case study is, and isn't

It isn't a benchmark. It isn't a controlled study. It's a single production application, observed honestly, with the numbers reported as they are.

What it does establish is that the architecture works — not as a demo, not as a "how-to," but as the substrate of a real product that has shipped 1,500+ builds. The combination most people assume can't be done without host code (WebAuthn, data-protection keys, real-time SSE streaming, an admin/observability surface, a generated frontend client, structured tests, and — when the team gets to them — rate limiting and response caching) is all here, in SQL and configuration, in production.

If you've been wondering whether a database-first architecture scales past CRUD demos, this is the existence proof.

',67)),r(t,{"get-started":[{text:"TypeScript Code Generation Walkthrough",href:"/blog/typescript-codegen-walkthrough"},{text:"Implementing WebAuthn Passkeys with Pure SQL",href:"/blog/passkey-sql-auth"},{text:"The Power of Simplicity",href:"/blog/the-power-of-simplicity"},{text:"PostgreSQL REST API Benchmark 2026",href:"/blog/postgresql-rest-api-benchmark-2026"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const y=o(d,[["render",c]]);export{f as __pageData,y as default}; diff --git a/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.lean.js b/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.lean.js new file mode 100644 index 000000000..f90100842 --- /dev/null +++ b/assets/blog_case-study-zero-backend-code.md.BnEeD2xb.lean.js @@ -0,0 +1 @@ +import{_ as o,C as n,c as a,o as i,a5 as s,G as r}from"./chunks/framework.CgT1UzWm.js";const f=JSON.parse('{"title":"Case Study: 74 Endpoints, Zero Backend Code — A Production App Built Entirely on NpgsqlRest","titleTemplate":"NpgsqlRest","description":"What it actually looks like to ship a production application without a controller layer. Real numbers from a finance/visualization app: ~74 HTTP endpoints, 12K LOC of SQL, zero lines of C# or Python, and an estimated 3,500–7,300 LOC of host-language boilerplate eliminated versus an equivalent ASP.NET Core build.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Case Study: 74 Endpoints, Zero Backend Code — A Production App Built Entirely on NpgsqlRest","titleTemplate":"NpgsqlRest","description":"What it actually looks like to ship a production application without a controller layer. Real numbers from a finance/visualization app: ~74 HTTP endpoints, 12K LOC of SQL, zero lines of C# or Python, and an estimated 3,500–7,300 LOC of host-language boilerplate eliminated versus an equivalent ASP.NET Core build.","head":[["meta",{"name":"keywords","content":"npgsqlrest case study, postgresql first development, no backend code, sql first architecture, asp.net core comparison, fastapi comparison, production npgsqlrest, webauthn sql, lines of code saved"}],["meta",{"property":"og:title","content":"Case Study: A Production App with Zero Backend Code"}],["meta",{"property":"og:description","content":"74 endpoints, 12K LOC of SQL, zero C# or Python. Real numbers from the first production-grade NpgsqlRest deployment we know of."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Case Study: 74 Endpoints, Zero Backend Code"}],["meta",{"name":"twitter:description","content":"What it actually looks like to ship a production application without a controller layer. Real numbers, honest tradeoffs."}]]},"headers":[],"relativePath":"blog/case-study-zero-backend-code.md","filePath":"blog/case-study-zero-backend-code.md"}'),d={name:"blog/case-study-zero-backend-code.md"};function c(l,e,h,p,u,g){const t=n("BlogNav");return i(),a("div",null,[e[0]||(e[0]=s("",67)),r(t,{"get-started":[{text:"TypeScript Code Generation Walkthrough",href:"/blog/typescript-codegen-walkthrough"},{text:"Implementing WebAuthn Passkeys with Pure SQL",href:"/blog/passkey-sql-auth"},{text:"The Power of Simplicity",href:"/blog/the-power-of-simplicity"},{text:"PostgreSQL REST API Benchmark 2026",href:"/blog/postgresql-rest-api-benchmark-2026"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const y=o(d,[["render",c]]);export{f as __pageData,y as default}; diff --git a/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.js b/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.js new file mode 100644 index 000000000..5a7318340 --- /dev/null +++ b/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.js @@ -0,0 +1,335 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing","titleTemplate":"NpgsqlRest","description":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing, automatic TypeScript client, progress tracking - all in SQL.","frontmatter":{"layout":"doc","outline":[2,3],"title":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing","titleTemplate":"NpgsqlRest","description":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing, automatic TypeScript client, progress tracking - all in SQL.","head":[["meta",{"name":"keywords","content":"postgresql csv import, excel to postgresql, csv upload api, postgresql data ingestion, npgsqlrest csv, bulk import postgresql, excel file upload postgresql"}],["meta",{"property":"og:title","content":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing"}],["meta",{"property":"og:description","content":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing in SQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"CSV and Excel Ingestion: PostgreSQL Row Processing"}],["meta",{"name":"twitter:description","content":"Import CSV and Excel into PostgreSQL with dynamic schema handling. Row-by-row processing."}]]},"headers":[],"relativePath":"blog/csv-excel-ingestion-postgresql-npgsqlrest.md","filePath":"blog/csv-excel-ingestion-postgresql-npgsqlrest.md"}'),h={name:"blog/csv-excel-ingestion-postgresql-npgsqlrest.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e(`

CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest

January 2026 · CSVExcelPostgreSQLData ImportNpgsqlRest

Most CSV and Excel import code makes you know the file structure in advance, hardcode it into your application, and redeploy on every change.

NpgsqlRest's CSV and Excel upload handlers move that structure into a SQL function instead: adding a column becomes a CREATE OR REPLACE, not a deployment. This tutorial walks through the row-by-row processing pipeline, with the auto-generated TypeScript client and progress tracking included.

Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/7_csv_excel_uploads

The Traditional Approach: Rigid and Brittle

With traditional CSV/Excel import implementations, you must:

  1. Know the exact structure before writing code
  2. Hardcode column mappings into your application
  3. Redeploy the application whenever the file format changes
  4. Maintain separate code paths for different file types

And that's just the structure problem. You also need to:

The result: a simple change like adding a column requires code changes, testing, and redeployment, on top of the hundreds of lines of boilerplate you wrote just to get the basics working.

The NpgsqlRest Approach: Dynamic and Flexible

All of that boilerplate comes out of the box:

Your row function receives a text[] array containing whatever is in the row. That's it. No hardcoded structure, no column mappings in application code, no redeployment when formats change.

Simply put:

When the file structure changes, you just ALTER or CREATE OR REPLACE your row function. No application restart required.

How It Works

mermaid
flowchart TB
+    HTTP["HTTP POST
+    multipart/form-data + file"]
+
+    HTTP --> NR["NpgsqlRest Handler
+    • Parses CSV/Excel
+    • Manages transaction"]
+
+    NR --> R1["Row 1: $1=1, $2=data, $3=prev, $4=meta"]
+    NR --> R2["Row 2: $1=2, $2=data, $3=prev, $4=meta"]
+    NR --> RN["Row N: $1=N, $2=data, $3=prev, $4=meta"]
+
+    R1 & R2 & RN --> FN["Your Row Function (SQL)
+    INSERT INTO table VALUES (...)"]

NpgsqlRest:

  1. Receives the upload via HTTP multipart/form-data
  2. Parses the file using optimized C# libraries (CsvHelper for CSV, ExcelDataReader for Excel)
  3. Calls your SQL function for each row, passing the data as a text array
  4. Manages the transaction - all rows succeed or all are rolled back
  5. Returns metadata about the upload to your main function

The Row Function: Four Parameters, Infinite Flexibility

ParameterTypeDescription
$1intRow index (1-based)
$2text[]Row values as a text array - whatever is in the row
$3anyResult from previous row (for chaining/accumulation)
$4jsonMetadata (file name, MIME type, user claims, etc.)

CSV Row Function Example

sql
sql
create or replace function example_7.csv_upload_row(
+    _index int,           -- Row number (1-based)
+    _row text[],          -- Row data: _row[1], _row[2], etc. - dynamic!
+    _prev_result int,     -- Return value from previous row call
+    _meta json            -- Metadata: fileName, contentType, size, claims
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into example_7.csv_uploads (user_id, file_name, row_index, row_data)
+    values (
+        (_meta->'claims'->>'user_id')::int,  -- User ID from auth claims
+        _meta->>'fileName',                   -- Original file name
+        _index,                               -- Row number
+        coalesce(_row, '{}')                  -- Row data as array
+    );
+
+    -- Return count for chaining - passed to next row as $3
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

Excel Row Function Example

Excel is nearly identical, but includes sheet name and actual Excel row index in metadata:

sql
sql
create or replace function example_7.excel_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into example_7.excel_uploads (user_id, file_name, sheet_name, row_index, row_data)
+    values (
+        (_meta->'claims'->>'user_id')::int,
+        _meta->>'fileName',
+        _meta->>'sheet',        -- Sheet name (Excel only)
+        _index,
+        coalesce(_row, '{}')
+    );
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

The same row function works for both CSV and Excel with minor metadata differences. You can even reuse the same function for both if you don't need sheet-specific handling.

Row Chaining: The Power of $3

The $3 parameter (_prev_result) receives whatever the previous row's function call returned. For the first row, it's NULL. This enables accumulation patterns:

Counting rows:

sql
sql
return coalesce(_prev_result, 0) + 1;  -- Returns 1, 2, 3, ... N

Returning the last inserted ID:

sql
sql
-- Insert and return the new ID
+insert into orders (...) values (...) returning id into _new_id;
+return _new_id;  -- Next row receives this as _prev_result

Summing a column:

sql
sql
return coalesce(_prev_result, 0) + (_row[3])::numeric;  -- Sum column 3

Building a running list:

sql
sql
return coalesce(_prev_result, '[]'::json) || json_build_array(_row[1]);

The final return value is included in the upload metadata as lastResult (CSV) or result (Excel).

The Upload Endpoint Function

The main upload function receives metadata about all processed files:

sql
sql
create or replace function example_7.csv_upload(
+    _meta json = null
+)
+returns json
+language sql
+begin atomic;
+select _meta;
+end;
+
+comment on function example_7.csv_upload(json) is '
+HTTP POST
+@upload for csv
+@param _meta is upload metadata
+@delimiters = ,;
+@row_command = select example_7.csv_upload_row($1,$2,$3,$4)';

That's it. This annotation:

Upload Metadata Structure

The _meta parameter receives a JSON array with one element per uploaded file:

json
json
[
+  {
+    "type": "csv",
+    "fileName": "sales_data.csv",
+    "contentType": "text/csv",
+    "size": 45678,
+    "success": true,
+    "status": "Ok",
+    "lastResult": 1247
+  }
+]

For Excel with multiple sheets (all_sheets = true), you get one entry per sheet:

json
json
[
+  {
+    "type": "excel",
+    "fileName": "report.xlsx",
+    "sheet": "January",
+    "success": true,
+    "rows": 450,
+    "result": 450
+  },
+  {
+    "type": "excel",
+    "fileName": "report.xlsx",
+    "sheet": "February",
+    "success": true,
+    "rows": 380,
+    "result": 380
+  }
+]

Dynamic Structure: No Hardcoding, No Redeployment

The text[] approach is the key to flexibility:

Store raw, process later:

sql
sql
-- Accept ANY file structure without schema changes
+insert into raw_imports (source, row_index, data, imported_at)
+values (
+    _meta->>'fileName',
+    _index,
+    _row,  -- Store the entire text[] as-is
+    now()
+);

Transform when you know the structure:

sql
sql
-- File structure: name, email, signup_date
+create or replace function process_user_import(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    -- Skip header row
+    if _index = 1 then
+        return 0;
+    end if;
+
+    insert into users (name, email, signup_date, imported_by)
+    values (
+        _row[1],                              -- Name (text)
+        lower(trim(_row[2])),                 -- Email (normalized)
+        _row[3]::date,                        -- Signup date (cast to date)
+        (_meta->'claims'->>'user_id')::int    -- Importing user
+    );
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

When the structure changes, just update the function:

sql
sql
-- New file structure: name, email, phone, signup_date
+create or replace function process_user_import(...)
+...
+    insert into users (name, email, phone, signup_date, imported_by)
+    values (
+        _row[1],
+        lower(trim(_row[2])),
+        _row[3],           -- New phone column
+        _row[4]::date,     -- signup_date moved to column 4
+        (_meta->'claims'->>'user_id')::int
+    );
+...

No application restart. No redeployment. Instant effect.

Configuration

Enable CSV and Excel handlers in config.json:

json
json
{
+  "NpgsqlRest": {
+    "UploadOptions": {
+      "Enabled": true,
+
+      "UploadHandlers": {
+        "CsvUploadEnabled": true,
+        "CsvUploadKey": "csv",
+        "CsvUploadDelimiterChars": ",;",
+        "CsvUploadHasFieldsEnclosedInQuotes": true,
+        "CsvUploadSetWhiteSpaceToNull": true,
+
+        "ExcelUploadEnabled": true,
+        "ExcelUploadKey": "excel",
+        "ExcelAllSheets": true,
+        "ExcelDateFormat": "yyyy-MM-dd",
+        "ExcelTimeFormat": "HH:mm:ss",
+        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss"
+      }
+    }
+  }
+}

Annotation Options

CSV Handler Options

OptionDefaultDescription
row_command(required)SQL command to process each row
delimiters,Delimiter character(s) for parsing
has_fields_enclosed_in_quotestrueFields may be enclosed in quotes
set_white_space_to_nulltrueConvert whitespace-only values to NULL

Excel Handler Options

OptionDefaultDescription
row_command(required)SQL command to process each row
sheet_namenullSpecific sheet to process (first sheet if null)
all_sheetsfalseProcess all sheets in workbook
time_formatHH:mm:ssFormat for time values
date_formatyyyy-MM-ddFormat for date values
datetime_formatyyyy-MM-dd HH:mm:ssFormat for datetime values
row_is_jsonfalsePass row as JSON instead of text array
fallback_handlernullHandler name to delegate to if format validation fails (e.g., csv). Available on all upload handlers since 3.8.0 (previously Excel-only).

Row Metadata Differences: CSV vs Excel

CSV row metadata ($4):

json
json
{
+  "type": "csv",
+  "fileName": "data.csv",
+  "contentType": "text/csv",
+  "size": 12345,
+  "claims": { "user_id": "1", "username": "alice" }
+}

Excel row metadata ($4):

json
json
{
+  "type": "excel",
+  "fileName": "data.xlsx",
+  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+  "size": 67890,
+  "sheet": "Sheet1",
+  "rowIndex": 5,
+  "claims": { "user_id": "1", "username": "alice" }
+}

Key difference: Excel includes sheet (current sheet name) and rowIndex (actual Excel row number including empty rows). CSV does not have these - use $1 for the row index.

Generated TypeScript Client

NpgsqlRest automatically generates a TypeScript client with progress tracking:

typescript
typescript
// Auto-generated - you write ZERO of this code
+
+interface ICsvUploadResponse {
+    type: string;
+    fileName: string;
+    contentType: string;
+    size: number;
+    success: boolean;
+    status: string;
+    [key: string]: string | number | boolean;
+}
+
+export async function csvUpload(
+    files: FileList | null,
+    request: ICsvUploadRequest,
+    progress?: (loaded: number, total: number) => void,
+): Promise<{
+    status: number,
+    response: ICsvUploadResponse[],
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    return new Promise((resolve, reject) => {
+        if (!files || files.length === 0) {
+            reject(new Error("No files to upload"));
+            return;
+        }
+        var xhr = new XMLHttpRequest();
+        if (progress) {
+            xhr.upload.addEventListener("progress", (event) => {
+                if (event.lengthComputable && progress) {
+                    progress(event.loaded, event.total);
+                }
+            }, false);
+        }
+        xhr.onload = function () {
+            if (this.status >= 200 && this.status < 300) {
+                resolve({status: this.status, response: JSON.parse(this.responseText), error: undefined});
+            } else {
+                resolve({status: this.status, response: [], error: JSON.parse(this.responseText)});
+            }
+        };
+        xhr.onerror = function () {
+            reject({xhr: this, status: this.status, statusText: this.statusText});
+        };
+        xhr.open("POST", baseUrl + "/api/example-7/csv-upload" + parseQuery(request));
+        const formData = new FormData();
+        for(let i = 0; i < files.length; i++) {
+            formData.append("file", files[i], files[i].name);
+        }
+        xhr.send(formData);
+    });
+}

Using it in your frontend:

typescript
typescript
import { csvUpload } from "./example7Api.ts";
+
+const response = await csvUpload(
+    fileInput.files,
+    {},
+    (loaded, total) => {
+        const percent = Math.round((loaded / total) * 100);
+        progressBar.style.width = \`\${percent}%\`;
+    }
+);
+
+if (response.status === 200) {
+    console.log(\`Imported \${response.response[0].lastResult} rows\`);
+}

Advanced Patterns

Skipping Header Rows

sql
sql
if _index = 1 then
+    return null;  -- Skip header, don't increment counter
+end if;

Validation and Rejection

sql
sql
-- Validate required fields
+if _row[1] is null or _row[2] is null then
+    raise exception 'Row % missing required fields', _index;
+end if;
+
+-- Validate format
+if _row[3] !~ '^\\d{4}-\\d{2}-\\d{2}$' then
+    raise exception 'Row % has invalid date format: %', _index, _row[3];
+end if;

Upsert (Insert or Update)

sql
sql
insert into products (sku, name, price)
+values (_row[1], _row[2], _row[3]::numeric)
+on conflict (sku) do update set
+    name = excluded.name,
+    price = excluded.price,
+    updated_at = now();

Processing Only Specific Sheets

sql
sql
comment on function excel_upload(json) is '
+HTTP POST
+@upload for excel
+@sheet_name = Transactions
+@row_command = select process_transaction_row($1,$2,$3,$4)';

JSON Row Format for Complex Data

sql
sql
comment on function excel_upload(json) is '
+HTTP POST
+@upload for excel
+@row_is_json = true
+@row_command = select process_json_row($1,$2,$3,$4)';

With row_is_json = true, $2 becomes JSON with Excel cell references as keys:

json
json
{"A1": "Name", "B1": "Amount", "C1": 123.45}

Transaction Safety

All row commands execute within a single transaction. If any row fails:

This is automatic - you don't write transaction handling code.

Combining Handlers: Process AND Store the Original File

You can combine CSV/Excel handlers with file_system or large_object handlers to both process the rows AND store the original file. This is useful when you need to:

sql
sql
comment on function example_7.csv_upload(json) is '
+HTTP POST
+@upload for csv, large_object
+@param _meta is upload metadata
+@row_command = select example_7.csv_upload_row($1,$2,$3,$4)';

The handler list is comma-separated. With this configuration:

  1. The CSV handler processes each row via row_command
  2. The Large Object handler stores the original file in PostgreSQL
  3. Both operate within the same transaction

Combined Handler Metadata

When using multiple handlers, your upload function receives a JSON array with one entry per handler:

json
json
[
+  {
+    "type": "csv",
+    "fileName": "data.csv",
+    "contentType": "text/csv",
+    "size": 12345,
+    "success": true,
+    "status": "Ok",
+    "lastResult": 500
+  },
+  {
+    "type": "large_object",
+    "fileName": "data.csv",
+    "contentType": "text/csv",
+    "size": 12345,
+    "success": true,
+    "status": "Ok",
+    "oid": 16456
+  }
+]

The CSV entry contains lastResult (final row function return value), while the Large Object entry contains oid (PostgreSQL Large Object identifier for retrieving the file later).

You can store both in your database:

sql
sql
insert into csv_imports (file_name, rows_processed, original_file_oid)
+select
+    m->>'fileName',
+    (m->>'lastResult')::int,
+    (select (x->>'oid')::bigint from json_array_elements(_meta) x where x->>'type' = 'large_object')
+from json_array_elements(_meta) m
+where m->>'type' = 'csv' and (m->>'success')::boolean = true;

If any row command fails or raises an exception, the entire transaction rolls back - including the Large Object storage. The original file won't be saved if processing fails.

You get structured data extraction and original-file preservation in a single transaction.

For more details on Large Object and File System handlers, see Secure Image Uploads with PostgreSQL.

Fallback Handler: One Endpoint for Both Excel and CSV

Updated in 3.8.0

The fallback_handler parameter is now available on all upload handlers (previously Excel-only). This enables scenarios like CSV format check fails on a binary file → fall back to large_object or file_system to save the raw file.

Sometimes users don't know (or care) whether their file is .xlsx or .csv - they just want to upload it. With the fallback_handler parameter, you can create a single upload endpoint that tries Excel first and automatically falls back to CSV if the file isn't a valid Excel format:

sql
sql
create or replace function example_7.combined_upload(
+    _meta json = null
+)
+returns json
+language sql
+begin atomic;
+select _meta;
+end;
+
+comment on function example_7.combined_upload(json) is '
+HTTP POST
+@authorize
+@upload for excel
+@param _meta is upload metadata
+@all_sheets = true
+@fallback_handler = csv
+@row_command = select example_7.combined_upload_row($1,$2,$3,$4)';

When @fallback_handler = csv is set:

  1. The Excel handler (ExcelDataReader) tries to parse the uploaded file first
  2. If it fails (invalid Excel format), the handler automatically delegates to the CSV handler
  3. The same @row_command is used for both - your row function receives text[] either way

This is simpler than the combined handler approach when you just want to accept either format. No need for separate endpoints - one function handles both:

sql
sql
create or replace function example_7.combined_upload_row(
+    _index int,
+    _row text[],
+    _prev_result int,
+    _meta json
+)
+returns int
+language plpgsql
+as $$
+begin
+    insert into example_7.combined_uploads (user_id, file_name, sheet_name, row_index, row_data)
+    values (
+        (_meta->'claims'->>'user_id')::int,
+        _meta->>'fileName',
+        _meta->>'sheet',    -- NULL for CSV, sheet name for Excel
+        _index,
+        coalesce(_row, '{}')
+    );
+
+    return coalesce(_prev_result, 0) + 1;
+end;
+$$;

You can check the type field in the metadata ($4) to know which parser handled the file: "excel" or "csv".

Authentication Integration

With RowCommandUserClaimsKey configured (default: "claims"), authenticated user information is available in every row's metadata:

sql
sql
-- Access user_id from claims
+(_meta->'claims'->>'user_id')::int
+
+-- Access username
+_meta->'claims'->>'username'

This enables per-user import tracking, row-level authorization, and audit trails.

Comparison with Other Tools

COPY Command

PostgreSQL's COPY command is fast for bulk loading, but:

NpgsqlRest's approach gives you per-row control while maintaining transaction safety.

ETL Tools (Talend, Pentaho, etc.)

Enterprise ETL tools handle far more than imports, but:

Python/pandas

pandas is excellent for data analysis but:

NpgsqlRest streams rows to PostgreSQL, using database-native transactions.

Conclusion: What You Don't Have to Write

Implementing this the traditional way means everything on the checklist at the top of this post: parsing library, upload endpoint, validation, transactions, error handling, auth integration, hand-written frontend types - and a redeploy whenever the structure changes.

With NpgsqlRest, you write:

Parsing, validation, transactions, user claims, and the TypeScript client all come from the handler and the annotations - and a structure change is a CREATE OR REPLACE, not a redeploy.

The Numbers

MetricTraditional ApproachNpgsqlRest
Backend code150-300 lines~30 lines (SQL only)
Frontend code50-100 lines~15 lines (using generated client)
Libraries to learn/configure3-50
Files to create/maintain5-102-3 (SQL files)
Time to implement1-3 days30 minutes
Redeployment on structure changeYesNo

Estimated savings: 80-90% less code, 90%+ time reduction.

The text[] approach means your row function receives whatever data is in the file. When you know the structure, cast and transform inline. When you don't, store raw and process later. When the structure changes, update the function - no application restart required.

One caveat: if you control the file format, need no per-row logic, and are loading very large files, plain COPY will still be faster. For everything user-facing, this approach covers it.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,147)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/7_csv_excel_uploads","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"}]})])}const u=a(h,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.lean.js b/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.lean.js new file mode 100644 index 000000000..2d23c39b3 --- /dev/null +++ b/assets/blog_csv-excel-ingestion-postgresql-npgsqlrest.md.Rjqy9NyI.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing","titleTemplate":"NpgsqlRest","description":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing, automatic TypeScript client, progress tracking - all in SQL.","frontmatter":{"layout":"doc","outline":[2,3],"title":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing","titleTemplate":"NpgsqlRest","description":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing, automatic TypeScript client, progress tracking - all in SQL.","head":[["meta",{"name":"keywords","content":"postgresql csv import, excel to postgresql, csv upload api, postgresql data ingestion, npgsqlrest csv, bulk import postgresql, excel file upload postgresql"}],["meta",{"property":"og:title","content":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing"}],["meta",{"property":"og:description","content":"Import CSV and Excel files into PostgreSQL with dynamic schema handling. Row-by-row processing in SQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"CSV and Excel Ingestion: PostgreSQL Row Processing"}],["meta",{"name":"twitter:description","content":"Import CSV and Excel into PostgreSQL with dynamic schema handling. Row-by-row processing."}]]},"headers":[],"relativePath":"blog/csv-excel-ingestion-postgresql-npgsqlrest.md","filePath":"blog/csv-excel-ingestion-postgresql-npgsqlrest.md"}'),h={name:"blog/csv-excel-ingestion-postgresql-npgsqlrest.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e("",147)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/7_csv_excel_uploads","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"}]})])}const u=a(h,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.js b/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.js new file mode 100644 index 000000000..73284dcc6 --- /dev/null +++ b/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.js @@ -0,0 +1,548 @@ +import{_ as a,C as n,c as l,o as t,a5 as p,G as h}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","titleTemplate":"NpgsqlRest","description":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures, use composite types as parameters, and build hierarchical responses with NpgsqlRest.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","titleTemplate":"NpgsqlRest","description":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures, use composite types as parameters, and build hierarchical responses with NpgsqlRest.","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql custom types, composite types rest api, nested json postgresql, table types api, npgsqlrest nested, postgresql array types, hierarchical json response, postgresql type system"}],["meta",{"property":"og:title","content":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"}],["meta",{"property":"og:description","content":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures with NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"}],["meta",{"name":"twitter:description","content":"Use PostgreSQL custom types to build hierarchical REST API responses with automatic TypeScript generation."}]]},"headers":[],"relativePath":"blog/custom-types-multiset-rest-api.md","filePath":"blog/custom-types-multiset-rest-api.md"}'),e={name:"blog/custom-types-multiset-rest-api.md"};function k(r,s,d,F,y,o){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=p(`

Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs

PostgreSQL · Custom Types · Multiset · Nested JSON · January 2026


There has been some exciting new features added to NpgsqlRest lately, and I wanted to share them personally, so this post will be human-written.

Those features revolve mainly around usage of custom types in PostgreSQL, including composite types and table types and their support in TypeScript code generation.

Custom types were supported from the beginning, but now, this support has been greatly expanded.

So lets do a quick overview of custom types and how they can help us building better APIs.

Example Setup

Source Code For This Example: github.com/NpgsqlRest/npgsqlrest-docs/examples/7_csv_excel_uploads

Databa schema for this example is fairly simple, we have authors and books tables with a one-to-many relationship (one author can have many books). There is also a reviews table that holds reviews for books. Source code (schema ommitted here for brevity) can be found in the example folder:

sql
sql
create table authors (
+    author_id int not null generated always as identity primary key,
+    first_name text,
+    last_name text
+);
+
+create table books (
+    book_id int not null generated always as identity primary key,
+    title text not null,
+    author_id int references authors(author_id)
+);
+
+create table reviews (
+    review_id int not null generated always as identity primary key,
+    book_id int references books(book_id),
+    reviewer_name text,
+    rating int check (rating between 1 and 5),
+    review_text text,
+    created_at timestamp default now()
+);
+
+-- Test data for authors
+insert into authors (first_name, last_name) values
+    ('George', 'Orwell'),
+    ('Jane', 'Austen'),
+    ('Ernest', 'Hemingway'),
+    ('Virginia', 'Woolf'),
+    ('Franz', 'Kafka');
+
+-- Test data for books
+insert into books (title, author_id) values
+    ('1984', 1),
+    ('Animal Farm', 1),
+    ('Pride and Prejudice', 2),
+    ('Sense and Sensibility', 2),
+    ('The Old Man and the Sea', 3),
+    ('A Farewell to Arms', 3),
+    ('Mrs Dalloway', 4),
+    ('To the Lighthouse', 4),
+    ('The Metamorphosis', 5),
+    ('The Trial', 5);
+
+-- Test data for reviews
+insert into reviews (book_id, reviewer_name, rating, review_text) values
+    (1, 'Alice Johnson', 5, 'A chilling and prophetic masterpiece.'),
+    (1, 'Bob Smith', 4, 'Thought-provoking but bleak.'),
+    (1, 'Carol White', 5, 'Essential reading for everyone.'),
+    (2, 'David Brown', 5, 'Brilliant political allegory.'),
+    (2, 'Eve Davis', 4, 'Simple yet profound.'),
+    (3, 'Frank Miller', 5, 'The perfect romance novel.'),
+    (3, 'Grace Lee', 5, 'Witty and timeless.'),
+    (4, 'Henry Wilson', 4, 'Austen at her finest.'),
+    (5, 'Ivy Chen', 5, 'Beautiful and moving.'),
+    (5, 'Jack Taylor', 4, 'A short but powerful read.'),
+    (6, 'Karen Adams', 4, 'Hemingway''s prose shines.'),
+    (7, 'Leo Garcia', 5, 'Stream of consciousness done right.'),
+    (8, 'Mia Robinson', 4, 'Poetic and haunting.'),
+    (9, 'Noah Martinez', 5, 'Surreal and unforgettable.'),
+    (9, 'Olivia Clark', 3, 'Disturbing but brilliant.'),
+    (10, 'Paul Wright', 4, 'Kafka at his most absurd.');

Returning Single Object

Common usage of custom types is to return a single object with multiple fields.

sql
sql
/*
+* Get author by ID or all authors if ID is null and return author object from table type
+*/
+create function get_author(
+    _author_id int
+)
+returns authors
+language sql
+begin atomic;
+select author_id, first_name, last_name
+from authors
+where author_id = _author_id;
+end;
+comment on function get_author(int) is 'HTTP GET';

The entire client code for .http file and TypeScript with generated types is automatically generated when we run NpgsqlRest with development configuration (check the example on GitHub).

And when we call /api/example-12/get-author?authorId=1 we get the following response:

json
json
{
+  "authorId": 1,
+  "firstName": "George",
+  "lastName": "Orwell"
+}

As we can see, the function returns a single authors type object. Of course, that is a table type, we can also define our own composite types if needed. For example:

sql
sql
create type author_info as (
+    first_name text,
+    last_name text,
+    books int
+);
+
+/*
+* Get author info by ID and return custom type with additional info
+*/
+create function get_author_info(
+    _author_id int
+)
+returns author_info
+language sql
+begin atomic;
+select first_name, last_name, count(b.*)
+from authors a
+left join books b using (author_id)
+where author_id = _author_id
+group by author_id, first_name, last_name;
+end;
+comment on function .get_author_info(int) is 'HTTP GET';

Calling /api/example-12/get-author-info?authorId=1 now returns:

json
json
{
+  "firstName": "George",
+  "lastName": "Orwell",
+  "books": 2
+}

This shows how we can use custom composite types to return simple JSON objects with specific fields. These types can also be reused accross multiple functions and even as parameters as we will see next.

Using Custom Types as Parameters

For example, we can reuse entire authors table type as a parameter to insert a new author:

sql
sql
/*
+* Create author by passing author object as parameter to function.
+* Demonstrates passing custom type as parameter to function. It can be user-defined type or table type.
+* Type fields are unnested into individual parameters: authorFirstName, authorLastName
+*/
+create function create_author(
+    _author authors
+)
+returns authors
+language sql
+begin atomic;
+insert into authors (first_name, last_name)
+values (_author.first_name,  _author.last_name)
+returning author_id, first_name, last_name;
+end;
+comment on function create_author(authors) is 'HTTP POST';

We now have generated .http test call (along with TypeScript client code and types) that looks like this:

http
http
// function create_author(
+//     _author_author_id integer,
+//     _author_first_name text,
+//     _author_last_name text
+// )
+// returns record
+//
+// comment on function create_author is 'HTTP POST
+POST {host}/api/example-12/create-author
+content-type: application/json
+
+{
+    "authorAuthorId": 1,
+    "authorFirstName": "XYZ",
+    "authorLastName": "IJK"
+}

And when we call this endpoint with the above body, we get the following response:

json
json
{
+  "authorId": 6,
+  "firstName": "XYZ",
+  "lastName": "IJK"
+}

You may notice that parameter names are prefixed with the type name (authorAuthorId, authorFirstName, etc). This is because custom type fields are expanded and merged with normal parameters.

The prefix avoids name collisions when multiple custom types are used as parameters in the same function. The separator between the type name and field name can be customized using the CustomTypeParameterSeparator setting in Routine Options.

If, for example, we would add two more parameters to the above function:

sql
sql
create function create_author(
+    _param1 text,
+    _author authors,
+    _param2 int
+)
+...

The generated parameter names would be: param1, authorAuthorId, authorFirstName, authorLastName and param2.

This is simple and effective way to share same types across multiple functions easily, either as return types or parameter types.

Returning Sets of Objects

The most common usage of custom types is to return sets of objects, for example, returning all books for an author:

sql
sql
/*
+* Get author by ID or all authors if ID is null and return author object from table type
+*/
+create function get_authors(
+    _author_id int
+)
+returns setof authors
+language sql
+begin atomic;
+select author_id, first_name, last_name
+from authors
+where
+    _author_id is null or author_id = _author_id;
+end;
+comment on function get_authors(int) is 'HTTP GET';

Calling /api/example-12/get-authors now returns as expected a list of authors:

json
json
[
+  {
+    "authorId": 1,
+    "firstName": "George",
+    "lastName": "Orwell"
+  },
+  {
+    "authorId": 2,
+    "firstName": "Jane",
+    "lastName": "Austen"
+  },
+  ...
+]

That is all fine, but in real-world scenarios, we rarely want to return the exact table structure. Those are our entities, meant to be part of the data model, we want to return something more tailored to the API consumer needs.

For example, we may want to return authors along with, for example, number of books they have written, then we may do something like this:

sql
sql
create function get_authors_with_details(
+    _author_id int
+)
+returns table(
+    author authors,
+    books int
+)
+language sql
+begin atomic;
+select
+    row(a.author_id, first_name, last_name),
+    count(b.*)
+from authors a join books b using (author_id)
+where
+    _author_id is null or author_id = _author_id
+group by
+    a.author_id, first_name, last_name;
+end;
+comment on function get_authors_with_details(int) is 'HTTP GET';

When we return a table type with multiple columns, NpgsqlRest will merge all columns, including custom types, into a single JSON object per row. The response for /api/example-12/get-authors-with-details now looks like this:

json
json
[
+  {
+    "authorId": 1,
+    "firstName": "George",
+    "lastName": "Orwell",
+    "books": 2
+  },
+  {
+    "authorId": 2,
+    "firstName": "Jane",
+    "lastName": "Austen",
+    "books": 2
+  },
+  ...
+]

As we can see, the author custom type fields are merged into the main object, resulting in a flat structure that is easy to consume.

But we are not limited to table types, we can comibine multiple custom types, including user defined types as well:

sql
sql
-- Define a custom type to hold book statistics
+create type books_info as (
+    books int,
+    active_reviews int,
+    avg_rating numeric
+);
+
+create function get_authors_with_details_type(
+    _author_id int
+)
+returns table(
+    author authors,
+    books_info books_info
+)
+language sql
+begin atomic;
+select
+    row(a.author_id, a.first_name, a.last_name),
+    (count(distinct b.book_id), count(r.review_id), avg(r.rating))
+from
+    authors a
+    left join books b using (author_id)
+    left join reviews r on b.book_id = r.book_id
+where
+    _author_id is null or author_id = _author_id
+group by
+    a.author_id, first_name, last_name;
+end;
+comment on function get_authors_with_details_type(int) is '
+HTTP GET
+';

As we can see, we are returning two custom types now, table type authors and custom type books_info. Note on implementation and casting types:

Resulting JSON for /api/example-12/get-authors-with-details-type now looks like this:

json
json
[
+  {
+    "authorId": 1,
+    "firstName": "George",
+    "lastName": "Orwell",
+    "books": 2,
+    "activeReviews": 5,
+    "avgRating": 4.6000000000000000
+  },
+  {
+    "authorId": 2,
+    "firstName": "Jane",
+    "lastName": "Austen",
+    "books": 2,
+    "activeReviews": 3,
+    "avgRating": 4.6666666666666667
+  },
+  ...
+]

So, this a powerful way to build tailored API responses using custom types, while keeping the database functions clean and reusable. There is one more piece.

NEW: Nested JSON Objects

Starting from NpgsqlRest 3.4.0, we can now nest custom types within the JSON response, instead of merging all fields into a flat structure.

This is opt-in behavior to keep backward compatibility, and we can enable it either globally for all routines, or per-routine basis.

json
json
{
+  "NpgsqlRest": {
+      "RoutineOptions": {
+        "NestedJsonForCompositeTypes": true
+    }
+  }
+}
sql
sql
comment on function get_authors_with_details_type(int) is '
+HTTP GET
+@nested
+';

And when we enable this feauture, the response for /api/example-12/get-authors-with-details now looks like this:

json
json
[
+  {
+    "author": {
+      "authorId": 1,
+      "firstName": "George",
+      "lastName": "Orwell"
+    },
+    "books": 2
+  },
+  {
+    "author": {
+      "authorId": 2,
+      "firstName": "Jane",
+      "lastName": "Austen"
+    },
+    "books": 2
+  },
+  ...
+]

And for /api/example-12/get-authors-with-details-type:

json
json
[
+  {
+    "author": {
+      "authorId": 1,
+      "firstName": "George",
+      "lastName": "Orwell"
+    },
+    "booksInfo": {
+      "books": 2,
+      "activeReviews": 5,
+      "avgRating": 4.6000000000000000
+    }
+  },
+  {
+    "author": {
+      "authorId": 2,
+      "firstName": "Jane",
+      "lastName": "Austen"
+    },
+    "booksInfo": {
+      "books": 2,
+      "activeReviews": 3,
+      "avgRating": 4.6666666666666667
+    }
+  },
+  ...
+]

As we can see, the custom types are now nested within their own JSON objects, preserving the structure defined in the database function. And the best part is that TypeScript types are also generated accordingly, so we get proper type safety when consuming these APIs.

But, wait, there is even more!

NEW: Nested JSON with Multiset

Starting from NpgsqlRest 3.4.0, we can now return nested JSON arrays (multisets) using custom types.

But, first, what is the Multiset?

In scenmarios when we are returning joined datasets with one-to-many relationships, such as authors and their books for example, typical SQL join query would return a flat result set with repeated author information for each book.

For example, consider the following simple query:

sql
sql
select * from authors join books using (author_id)
author_idfirst_namelast_namebook_idtitle
1GeorgeOrwell11984
1GeorgeOrwell2Animal Farm
2JaneAusten3Pride and Prejudice
2JaneAusten4Sense and Sensibility

As we can see, author information is repeated for each book. Now imageine this query for thousands of authors and books, the result set would be huge and inefficient to transfer over the network.

And if we would join, for example, author addresses as well to form multiple one-to-many relationships, the result would something called the Cartesian Explosion, which a very bad thing, obviously we don't want that in our API.

To solve this problems, database vendors came up with the concept of Multiset, which allows us to return hierarchical data structures directly from the database.

Unfortunately, PostgreSQL does not have built-in support and in general, multisets are not widely supported in SQL databases. This is the support matrix from my AI research:

DatabaseNative MULTISETWorkaround
Oracle✅ Full-
Informix✅ Full-
PostgreSQLARRAY, JSON_AGG
EDB PostgresOracle compat mode
SQL ServerFOR JSON/XML
MySQLJSON_ARRAYAGG
TeradataPartialSET/MULTISET tables

As we can see, we can use array workarounds in PostgreSQL to achieve similar results. And from NpgsqlRest 3.4.0, we can now use this to return nested JSON arrays using custom types. Let's see how that works.

sql
sql
create function get_authors_and_books(
+    _author_id int
+)
+returns table(
+    author authors,
+    books books[]
+)
+language sql
+begin atomic;
+select
+    row(
+        a.author_id, first_name, last_name
+    ),
+    array_agg(
+        row(b.book_id, b.title, b.author_id)::books
+    )
+from
+    authors a
+    left join books b using (author_id)
+where
+    _author_id is null or author_id = _author_id
+group by
+    a.author_id, first_name, last_name;
+end;
+comment on function get_authors_and_books(int) is '
+HTTP GET
+@nested
+';

This function returns authors along with an array of their books. Note the usage of array_agg(...) to aggregate books into an array.

When we call /api/example-12/get-authors-and-books, we get the following nested JSON response:

json
json
[
+  {
+    "author": {
+      "authorId": 1,
+      "firstName": "George",
+      "lastName": "Orwell"
+    },
+    "books": [
+      {
+        "bookId": 1,
+        "title": "1984",
+        "authorId": 1
+      },
+      {
+        "bookId": 2,
+        "title": "Animal Farm",
+        "authorId": 1
+      }
+    ]
+  },
+  {
+    "author": {
+      "authorId": 2,
+      "firstName": "Jane",
+      "lastName": "Austen"
+    },
+    "books": [
+      {
+        "bookId": 3,
+        "title": "Pride and Prejudice",
+        "authorId": 2
+      },
+      {
+        "bookId": 4,
+        "title": "Sense and Sensibility",
+        "authorId": 2
+      }
+    ]
+  },
+  ...
+]

So we now have a proper hierarchical JSON structure with authors and their books nested within just by returning arrays of custom types from our database function. As always, these can be any table or user defined type and TypeScript types are generated accordingly for type-safe consumption.

And this is even simpler then, for example, it would be in Oracle, where we would need to define MULTISET types explicitly. According to AI research, OraOracle/Informix/SQL Standard syntax would look something like this:

sql
sql
SELECT author_id, author_name,
+       MULTISET(SELECT book_id, title FROM books 
+                WHERE books.author_id = authors.id) AS books
+FROM authors;

This may be more declarative, but it certainly doesn't have the automatic REST API and TypeScript generation like NpgsqlRest provides.

Limitations

There are some limitations to be aware of when using nested JSON with multiset:

  1. Nesting is limited to one level deep per function.

For example, if we wanted to return authors, together with their books and with reviews for each book, we would ran into limitations:

First of all, PostgreSQL does not support aggregation in aggregation, so we cannot do array_agg(...) inside another array_agg(...) in a single query:

code
ERROR:
+aggregate function calls cannot be nested
+LINE 11: array_agg(

This limitation can be worked around by using subqueries, temp tables or CTEs, but it gets complicated quickly. Example is in source code on GitHub (see link above).

But even if we could do that, prior to NpgsqlRest 3.4.4, only one level of nesting was supported per function. Any additional levels of nesting were rendered as PostgreSQL tuple strings in JSON.

Deep Nesting Support (v3.4.4+)

Since NpgsqlRest 3.4.4, the ResolveNestedCompositeTypes option (enabled by default) resolves nested composite types to any depth, serializing inner composites as proper JSON objects/arrays instead of PostgreSQL tuple strings. The limitation below only applies when ResolveNestedCompositeTypes is set to false.

For example, with ResolveNestedCompositeTypes: false, a single object in the JSON array would look like this:

json
json
[
+  {
+    "author": {
+      "authorId": 1,
+      "firstName": "George",
+      "lastName": "Orwell"
+    },
+    "books": [
+      {
+        "bookId": 1,
+        "title": "1984",
+        "reviews": [
+          "(1,1,\\"Alice Johnson\\",5,\\"A chilling and prophetic masterpiece.\\",\\"2026-01-16 08:45:55.560972\\")",
+          "(2,1,\\"Bob Smith\\",4,\\"Thought-provoking but bleak.\\",\\"2026-01-16 08:45:55.560972\\")",
+          "(3,1,\\"Carol White\\",5,\\"Essential reading for everyone.\\",\\"2026-01-16 08:45:55.560972\\")"
+        ]
+      },
+      {
+        "bookId": 2,
+        "title": "Animal Farm",
+        "reviews": [
+          "(4,2,\\"David Brown\\",5,\\"Brilliant political allegory.\\",\\"2026-01-16 08:45:55.560972\\")",
+          "(5,2,\\"Eve Davis\\",4,\\"Simple yet profound.\\",\\"2026-01-16 08:45:55.560972\\")"
+        ]
+      }
+    ]
+  },
+  ...
+]

With the default ResolveNestedCompositeTypes: true, the reviews array is properly serialized as JSON objects:

json
json
[
+  {
+    "author": {
+      "authorId": 1,
+      "firstName": "George",
+      "lastName": "Orwell"
+    },
+    "books": [
+      {
+        "bookId": 1,
+        "title": "1984",
+        "reviews": [
+          {"reviewId": 1, "bookId": 1, "reviewerName": "Alice Johnson", "rating": 5, "reviewText": "A chilling and prophetic masterpiece.", "createdAt": "2026-01-16T08:45:55.560972"},
+          {"reviewId": 2, "bookId": 1, "reviewerName": "Bob Smith", "rating": 4, "reviewText": "Thought-provoking but bleak.", "createdAt": "2026-01-16T08:45:55.560972"},
+          {"reviewId": 3, "bookId": 1, "reviewerName": "Carol White", "rating": 5, "reviewText": "Essential reading for everyone.", "createdAt": "2026-01-16T08:45:55.560972"}
+        ]
+      },
+      {
+        "bookId": 2,
+        "title": "Animal Farm",
+        "reviews": [
+          {"reviewId": 4, "bookId": 2, "reviewerName": "David Brown", "rating": 5, "reviewText": "Brilliant political allegory.", "createdAt": "2026-01-16T08:45:55.560972"},
+          {"reviewId": 5, "bookId": 2, "reviewerName": "Eve Davis", "rating": 4, "reviewText": "Simple yet profound.", "createdAt": "2026-01-16T08:45:55.560972"}
+        ]
+      }
+    ]
+  },
+  ...
+]
  1. Working memory pressure on the database server.

Aggegations in PostgreSQL are accumulated in working memory and by default PostgreSQL allocates 4MB per aggregation operation (see the work_mem parameter). Those allocations are per operation and even a single query can have multiple aggregations, let alone multiple concurrent queries. When memory limit is exceeded, PostgreSQL spills to disk to complete the operation, which can severely impact performance.

This is a consideriation when using aggregations excessively, especially with large datasets on a busy server. Always monitor memory usage and tune work_mem accordingly.

Conclusion And Workaround

Using custom types and multiset patterns in PostgreSQL with NpgsqlRest allows us to build expressive and efficient REST APIs with hierarchical JSON responses but as we can see, there are some limitations to be aware of.

There is a good workaround taht I use in my projects. More like different pattern. Simply, run multiple queries, preferably in parallel, instead of a single complex query with multiple levels of nesting. This pattern gives us:

Speaking of which, that might be just the best part of using NpgsqlRest with PostgreSQL. The amount of code that I dont have to write myself is incredible. You can see it in the example source code on GitHub (see link above), but just to illustrate, here is the copy and paste here just for the interfaces part, the entire file is much longer:

typescript
typescript
interface IAuthor {
+    authorId: number | null;
+    firstName: string | null;
+    lastName: string | null;
+}
+
+interface IBooks {
+    bookId: number | null;
+    title: string | null;
+    authorId: number | null;
+}
+
+interface IBooks {
+    bookId: number | null;
+    title: string | null;
+    reviews: string[] | null;
+}
+
+interface IBooksInfo {
+    books: number | null;
+    activeReviews: number | null;
+    avgRating: number | null;
+}
+
+interface ICreateAuthorRequest {
+    authorAuthorId?: number | null;
+    authorFirstName?: string | null;
+    authorLastName?: string | null;
+}
+
+interface ICreateAuthorResponse {
+    authorId: number | null;
+    firstName: string | null;
+    lastName: string | null;
+}
+
+interface IGetAuthorRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorResponse {
+    authorId: number | null;
+    firstName: string | null;
+    lastName: string | null;
+}
+
+interface IGetAuthorInfoRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorInfoResponse {
+    firstName: string | null;
+    lastName: string | null;
+    books: number | null;
+}
+
+interface IGetAuthorsRequest {
+    authorAuthorId?: number | null;
+    authorFirstName?: string | null;
+    authorLastName?: string | null;
+}
+
+interface IGetAuthorsResponse {
+    authorId: number | null;
+    firstName: string | null;
+    lastName: string | null;
+}
+
+interface IGetAuthorsAndBooksRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsAndBooksResponse {
+    author: IAuthor | null;
+    books: IBooks[] | null;
+}
+
+interface IGetAuthorsAndBooksAndReviewsRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsAndBooksAndReviewsResponse {
+    author: IAuthor | null;
+    books: IBooks[] | null;
+}
+
+interface IGetAuthorsWithDetailsRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsWithDetailsResponse {
+    author: IAuthor | null;
+    books: number | null;
+}
+
+interface IGetAuthorsWithDetailsNestedRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsWithDetailsNestedResponse {
+    author: IAuthor | null;
+    books: number | null;
+}
+
+interface IGetAuthorsWithDetailsTypeRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsWithDetailsTypeResponse {
+    author: IAuthor | null;
+    booksInfo: IBooksInfo | null;
+}
+
+interface IGetAuthorsWithDetailsTypeNestedRequest {
+    authorId: number | null;
+}
+
+interface IGetAuthorsWithDetailsTypeNestedResponse {
+    author: IAuthor | null;
+    booksInfo: IBooksInfo | null;
+}

That is a lot of code that I did not have to write myself. Plus, there is also the .http test file with all the calls, which is also generated automatically.

I built this project to save me time and effort, and it does exactly that. I hope you find it useful as well. If you do, give it a star on GitHub!

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,112)),h(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/12_custom_types","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Routine Options",href:"/config/routine-options"},{text:"Code Generation",href:"/config/codegen"}]})])}const u=a(e,[["render",k]]);export{c as __pageData,u as default}; diff --git a/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.lean.js b/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.lean.js new file mode 100644 index 000000000..f8b0a8817 --- /dev/null +++ b/assets/blog_custom-types-multiset-rest-api.md.BEa032jW.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as t,a5 as p,G as h}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","titleTemplate":"NpgsqlRest","description":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures, use composite types as parameters, and build hierarchical responses with NpgsqlRest.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","titleTemplate":"NpgsqlRest","description":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures, use composite types as parameters, and build hierarchical responses with NpgsqlRest.","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql custom types, composite types rest api, nested json postgresql, table types api, npgsqlrest nested, postgresql array types, hierarchical json response, postgresql type system"}],["meta",{"property":"og:title","content":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"}],["meta",{"property":"og:description","content":"Learn how to use PostgreSQL custom types and table types in REST APIs. Return nested JSON structures with NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"}],["meta",{"name":"twitter:description","content":"Use PostgreSQL custom types to build hierarchical REST API responses with automatic TypeScript generation."}]]},"headers":[],"relativePath":"blog/custom-types-multiset-rest-api.md","filePath":"blog/custom-types-multiset-rest-api.md"}'),e={name:"blog/custom-types-multiset-rest-api.md"};function k(r,s,d,F,y,o){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=p("",112)),h(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/12_custom_types","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Routine Options",href:"/config/routine-options"},{text:"Code Generation",href:"/config/codegen"}]})])}const u=a(e,[["render",k]]);export{c as __pageData,u as default}; diff --git a/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.js b/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.js new file mode 100644 index 000000000..48af8f67e --- /dev/null +++ b/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.js @@ -0,0 +1,326 @@ +import{_ as a,C as n,c as l,o as e,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Database-Level Security: Building Secure Authentication with PostgreSQL","titleTemplate":"NpgsqlRest","description":"Implement Principle of Least Privilege at the database level. Build secure authentication with PostgreSQL SECURITY DEFINER, search path protection, and advanced password hashing.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Database-Level Security: Building Secure Authentication with PostgreSQL","titleTemplate":"NpgsqlRest","description":"Implement Principle of Least Privilege at the database level. Build secure authentication with PostgreSQL SECURITY DEFINER, search path protection, and advanced password hashing.","head":[["meta",{"name":"keywords","content":"postgresql security, database authentication, postgresql security definer, principle of least privilege postgresql, postgresql password hashing, secure api postgresql, npgsqlrest auth"}],["meta",{"property":"og:title","content":"Database-Level Security: Secure Authentication with PostgreSQL"}],["meta",{"property":"og:description","content":"Implement Principle of Least Privilege at the database level. Secure authentication with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Database-Level Security with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Implement Principle of Least Privilege. Build secure auth with PostgreSQL."}]]},"headers":[],"relativePath":"blog/database-level-security-postgresql-authentication.md","filePath":"blog/database-level-security-postgresql-authentication.md"}'),h={name:"blog/database-level-security-postgresql-authentication.md"};function k(r,s,c,d,o,g){const i=n("BlogNav");return e(),l("div",null,[s[0]||(s[0]=t(`

Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest

January 2026 · SecurityPostgreSQLAuthenticationNpgsqlRest

Most web applications treat databases as dumb storage - a place to persist data that the application logic protects. This is backwards. Your database is the last line of defense, and it should be the strongest.

This post demonstrates how to build an authentication system that uses PostgreSQL as the security boundary: the Principle of Least Privilege enforced at the database level, plus a password hashing scheme that sidesteps bcrypt's 72-byte limit.

Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/3_security_and_auth

The Principle of Least Privilege (PoLP)

The Principle of Least Privilege states that any user, program, or process should have only the minimum privileges necessary to perform its function. In traditional web applications, the database connection often has full access to all tables - a disaster waiting to happen.

With NpgsqlRest, we implement PoLP at the database level:

mermaid
flowchart TB
+    APP["Application
+    (app_user role)"]
+
+    APP --> PUB
+
+    subgraph PUB["example_3_public schema — ONLY schema app_user can access"]
+        L["login()
+        SECURITY DEFINER"]
+        LO["logout()
+        SECURITY DEFINER"]
+        W["who_am_i()"]
+    end
+
+    L & LO --> PROT
+
+    subgraph PROT["example_3 schema — app_user has NO ACCESS here"]
+        UT["users table
+        (passwords)"]
+        HP["hash_password()
+        verify_password()"]
+    end

The whole design rests on one rule: the application role cannot access tables directly. It can only call functions in a public schema, and those functions use SECURITY DEFINER to access protected data.

Schema Architecture

The Protected Schema

The example_3 schema contains sensitive data and internal functions. This is defined in V1__example_3_schema.sql - a versioned migration. The V1__ prefix means this is version 1, and versioned migrations only run once (unlike repeatable R__ migrations that run on every change).

sql
sql
-- V1__example_3_schema.sql (versioned migration - runs only once)
+
+-- Create the protected schema
+drop schema if exists example_3 cascade;
+create schema example_3;
+
+-- Enable pgcrypto for password hashing
+create extension if not exists pgcrypto with schema example_3;
+
+-- Users table with password hashes
+create table example_3.users (
+    user_id int primary key generated always as identity,
+    username text not null,
+    email text not null,
+    password_hash text[] not null  -- Array of hashes (explained below)
+);

The Public API Schema

The same versioned migration also creates the public schema for API endpoints:

sql
sql
-- Create public schema for API endpoints
+drop schema if exists example_3_public cascade;
+create schema example_3_public;

The Restricted Application Role

Here's where PoLP becomes concrete. We create a role with minimal permissions:

sql
sql
-- Create application role with minimal privileges
+create role \${APP_USER} with
+    login
+    nosuperuser
+    nocreatedb
+    nocreaterole
+    noinherit
+    noreplication
+    connection limit -1
+    password '\${APP_PASSWORD}';
+
+-- Grant ONLY usage on the public schema
+-- This is the ONLY grant this role needs
+grant usage on schema example_3_public to \${APP_USER};

The \${APP_USER} and \${APP_PASSWORD} placeholders are environment variables that get replaced when running migrations. The pgmigrations tool supports this \${VAR} syntax for variable substitution.

Note on credentials: In this example, APP_USER and APP_PASSWORD are stored in the .env file alongside the superuser credentials used to run migrations. This is for demonstration purposes only. In production, these credentials should be stored separately - the superuser credentials belong to whoever administers the database and runs migrations (DevOps, DBA, etc.), while the application credentials should be managed through your secrets management system.

Note what's not granted:

The application can only execute functions in example_3_public. Period.

Bypassing Bcrypt's 72-Byte Limit

Bcrypt is the gold standard for password hashing, but it has a critical limitation: it silently ignores everything after byte 72. This means these two passwords hash identically:

code
"a]2@[Z]f)!BqC6:g%/$wrz7-Cz<B9!@z]j{9,X]3xaM'uqQW*l7:zK"s:-xt*2Pd$e7Emore_stuff_here"
+"a]2@[Z]f)!BqC6:g%/$wrz7-Cz<B9!@z]j{9,X]3xaM'uqQW*l7:zK"s:-xt*2Pd$e7E"

An attacker who discovers this limit could exploit it. Our solution: segment the password and hash each segment separately.

The Hash Function

sql
sql
-- R__1_example_3_hash_password.sql
+
+--
+-- Bcrypt has a 72-byte input limit - any characters beyond that are silently ignored.
+-- This function overcomes that limitation by splitting passwords into 72-char segments,
+-- hashing each segment separately, and returning an array of hashes.
+-- This allows secure hashing of passwords with unlimited length.
+--
+create or replace function example_3.hash_password(
+    _input text
+)
+returns text[]
+parallel safe
+language plpgsql
+as
+$$
+declare
+    _segment text;
+    _result text[] = '{}';
+    _alg text = 'bf';  -- Blowfish (bcrypt)
+    _i int;
+    _max_len constant int = 72;
+begin
+    for _i in 0..ceil(length(_input) / _max_len) + 1 loop
+        _segment = substring(_input from _i * _max_len + 1 for _max_len);
+        if length(_segment) > 0 then
+            _result = array_append(_result, example_3.crypt(_segment, example_3.gen_salt(_alg)));
+        end if;
+    end loop;
+
+    return _result;
+end;
+$$;

The Verify Function

sql
sql
-- R__2_example_3_verify_password.sql
+
+create or replace function example_3.verify_password(
+    _input text,
+    _array text[]
+)
+returns boolean
+language plpgsql
+as
+$$
+declare
+    _segment text;
+    _max_len constant int = 72;
+    _expected_segments int;
+    _segment_count int = 0;
+begin
+    for _i in 0..ceil(length(_input) / _max_len) + 1 loop
+        _segment = substring(_input from _i * _max_len + 1 for _max_len);
+        if length(_segment) > 0 then
+            _segment_count = _segment_count + 1;
+            if example_3.crypt(_segment, _array[_i+1]) <> _array[_i+1] then
+                return false;
+            end if;
+        end if;
+    end loop;
+
+    -- Ensure the hash array has exactly the expected number of segments
+    if coalesce(array_length(_array, 1), 0) <> _segment_count then
+        return false;
+    end if;
+
+    return true;
+end;
+$$;

Testing the Password Functions

The tests run on every migration, ensuring the functions work correctly:

sql
sql
do
+$$
+declare
+    _hash text[];
+    _long_password text;
+begin
+    -- Test 1: Simple password hash and verify
+    _hash = example_3.hash_password('mypassword123');
+    assert example_3.verify_password('mypassword123', _hash),
+        'Test 1 failed: correct password should verify';
+
+    -- Test 2: Wrong password should not verify
+    assert not example_3.verify_password('wrongpassword', _hash),
+        'Test 2 failed: wrong password should not verify';
+
+    -- Test 3: Empty string vs actual password
+    assert not example_3.verify_password('', _hash),
+        'Test 3 failed: empty string should not verify against non-empty hash';
+
+    -- Test 4: Empty password hash and verify
+    _hash = example_3.hash_password('');
+    assert example_3.verify_password('', _hash),
+        'Test 4 failed: empty password should verify against its own hash';
+
+    -- Test 5: Long password (> 72 chars to test segmentation)
+    _long_password = repeat('a', 100);
+    _hash = example_3.hash_password(_long_password);
+    assert array_length(_hash, 1) > 1,
+        'Test 5a failed: long password should produce multiple hash segments';
+    assert example_3.verify_password(_long_password, _hash),
+        'Test 5b failed: long password should verify correctly';
+
+    -- Test 6: Very long password (> 144 chars for 3 segments)
+    _long_password = repeat('x', 200);
+    _hash = example_3.hash_password(_long_password);
+    assert array_length(_hash, 1) >= 3,
+        'Test 6a failed: very long password should produce 3+ hash segments';
+    assert example_3.verify_password(_long_password, _hash),
+        'Test 6b failed: very long password should verify correctly';
+    assert not example_3.verify_password(repeat('y', 200), _hash),
+        'Test 6c failed: different long password should not verify';
+
+    -- Test 7: Special characters
+    _hash = example_3.hash_password('p@!?w0rd!#%&*()');
+    assert example_3.verify_password('p@!?w0rd!#%&*()', _hash),
+        'Test 7 failed: special characters should hash and verify correctly';
+
+    -- Test 8: Unicode characters
+    _hash = example_3.hash_password('пароль密码🔐');
+    assert example_3.verify_password('пароль密码🔐', _hash),
+        'Test 8 failed: unicode characters should hash and verify correctly';
+
+    raise notice 'All password hash/verify tests passed!';
+end;
+$$;

The Authentication Functions

Understanding SECURITY DEFINER

First, what SECURITY DEFINER actually does:

Since migrations are run by a superuser (or at least a privileged role), functions marked SECURITY DEFINER will execute with those elevated privileges - even when called by the restricted application role.

This is the key mechanism that makes PoLP work:

  1. The example_3_public schema contains only the functions that the application needs to call - nothing else
  2. The application role has USAGE on example_3_public schema, so it can discover and call those functions
  3. Those functions are SECURITY DEFINER, so they run as the superuser who created them
  4. Inside the function, we can access example_3.users table - something the application role cannot do directly

Protecting Against Search Path Attacks

SECURITY DEFINER functions have a well-known vulnerability: search path injection. When a function calls other functions or operators without schema-qualifying them, PostgreSQL uses the search_path to resolve them. An attacker can manipulate this path to substitute malicious functions that execute with elevated privileges.

For example, if a SECURITY DEFINER function uses the + operator without qualification, an attacker could:

  1. Create a schema they control
  2. Define a malicious + operator in that schema
  3. Manipulate search_path to prioritize their schema
  4. When the function runs as superuser, the malicious operator executes with superuser privileges

The fix is simple: always set search_path explicitly on SECURITY DEFINER functions:

sql
sql
set search_path = pg_catalog, pg_temp

This ensures only trusted system catalogs are searched. Even if a function doesn't call other functions, it's good practice to include this on all SECURITY DEFINER functions as defense in depth.

For more details on this vulnerability, see Abusing SECURITY DEFINER functions and CVE-2018-1058.

Login Function

sql
sql
-- R__example_3_public_login.sql
+
+create or replace function example_3_public.login(
+    _username text,
+    _password text
+)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    email text
+)
+language sql
+set search_path = pg_catalog, pg_temp  -- Protect against search path attacks
+security definer  -- Runs as migration user (superuser), not app_user
+begin atomic;
+select
+    'cookies' as scheme,  -- Tells NpgsqlRest to use cookie authentication
+    user_id,
+    username,
+    email
+from example_3.users  -- app_user can't access this table directly!
+where
+    username = _username
+    and example_3.verify_password(_password, password_hash);
+end;
+
+comment on function example_3_public.login(text, text) is '
+HTTP POST
+@login
+@anonymous';  -- Allow unauthenticated access to login

The login annotation marks this as an authentication endpoint. Here's how it works:

  1. The function must return a named record (table) - returning void, simple values, or an empty result triggers 401 Unauthorized
  2. The scheme column specifies which configured authentication scheme to use (in this case 'cookies', but it could be any scheme you've configured - Bearer tokens, JWT, etc.)
  3. All other columns (user_id, username, email) become security claims stored in the authentication cookie
  4. The anonymous annotation allows unauthenticated access - otherwise users couldn't log in!

On successful login, NpgsqlRest signs in the user with the specified scheme and the returned claims become available in subsequent requests.

Logout Function

sql
sql
-- R__example_3_public_logout.sql
+
+create or replace function example_3_public.logout()
+returns text
+set search_path = pg_catalog, pg_temp
+language sql
+security definer
+begin atomic;
+select 'cookies';  -- Return the scheme to clear
+end;
+
+comment on function example_3_public.logout() is
+'HTTP POST
+@logout
+@authorize';  -- Requires authentication

The logout annotation tells NpgsqlRest to clear the authentication cookie.

Who Am I Function

This function demonstrates user parameters - claims from the authentication cookie are automatically injected:

sql
sql
-- R__example_3_public_who_am_i.sql
+
+create or replace function example_3_public.who_am_i(
+    _user_id text = null,
+    _username text = null,
+    _email text = null
+)
+returns table (
+    user_id text,
+    username text,
+    email text
+)
+set search_path = pg_catalog, pg_temp  -- Good practice even without SECURITY DEFINER
+language sql
+begin atomic;
+select
+    _user_id,
+    _username,
+    _email;
+end;
+
+comment on function example_3_public.who_am_i(text, text, text) is '
+HTTP GET
+@authorize';

The parameters _user_id, _username, and _email are filled in automatically by NpgsqlRest from the authenticated user's claims - the client never sends them.

NpgsqlRest Configuration

The configuration ties everything together:

json
json
{
+  "ApplicationName": "3_security_and_auth",
+
+  "ConnectionStrings": {
+    // Use the restricted application role
+    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={APP_USER};Password={APP_PASSWORD}"
+  },
+
+  "StaticFiles": {
+    "RootPath": "./3_security_and_auth/public"
+  },
+
+  // Cookie authentication settings
+  "Auth": {
+    "CookieAuth": true,
+    "CookieAuthScheme": "cookies",
+    "CookieValidDays": 1,
+    "CookieName": "example_3_auth"
+  },
+
+  "NpgsqlRest": {
+    // ONLY expose the public schema
+    "IncludeSchemas": [ "example_3_public" ],
+    "RequiresAuthorization": true,
+
+    "AuthenticationOptions": {
+      "DefaultAuthenticationType": "example_3",
+      // Map claims to function parameters
+      "UseUserParameters": true,
+      "ParameterNameClaimsMapping": {
+        "_user_id": "user_id",
+        "_username": "username",
+        "_email": "email"
+      }
+    },
+
+    "ClientCodeGen": {
+      "FilePath": "./3_security_and_auth/src/{0}Api.ts"
+    }
+  }
+}

Key configuration points:

  1. Connection uses the restricted role - The application connects as APP_USER, not the superuser
  2. Only public schema is exposed - IncludeSchemas: ["example_3_public"] ensures internal functions are never exposed
  3. Cookie authentication enabled - CookieAuth: true with scheme matching what login returns
  4. User parameters mapped - Claims from cookies are automatically injected into function parameters

The Demo Application

The example includes a simple web interface demonstrating the authentication flow:

html
html
<!-- public/index.html -->
+<div id="login-form">
+    <h2>Login</h2>
+    <input type="text" id="username" placeholder="Username" value="alice" />
+    <input type="password" id="password" placeholder="Password" value="password123" />
+    <button id="login-btn">Login</button>
+</div>
+
+<div id="actions">
+    <button id="whoami-btn">Who Am I?</button>
+    <button id="logout-btn">Logout</button>
+</div>

The TypeScript client is automatically generated:

typescript
typescript
// Auto-generated by NpgsqlRest
+
+interface ILoginRequest {
+    username: string | null;
+    password: string | null;
+}
+
+interface IWhoAmIResponse {
+    userId: string | null;
+    username: string | null;
+    email: string | null;
+}
+
+export async function login(request: ILoginRequest) : Promise<{
+    status: number,
+    response: string,
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    const response = await fetch(baseUrl + "/api/example-3-public/login", {
+        method: "POST",
+        body: JSON.stringify(request)
+    });
+    // ...
+}
+
+export async function whoAmI(request: IWhoAmIRequest) : Promise<{
+    status: number,
+    response: IWhoAmIResponse[],
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    // ...
+}

Test users are created during migration:

sql
sql
-- R__3_example_3_test_data.sql
+insert into example_3.users (username, email, password_hash) values
+('alice', 'alice@example.com', example_3.hash_password('password123')),
+('bob', 'bob@example.com', example_3.hash_password('password456'));

Why This Architecture is More Secure

1. Defense in Depth

Even if an attacker compromises the application, they cannot:

The database itself enforces security boundaries.

2. SQL Injection Becomes Less Dangerous

Traditional SQL injection exploits often rely on the application having broad database permissions. With PoLP:

sql
sql
-- Attacker tries: ' OR '1'='1
+-- Even if injection succeeds, app_user can only call functions in example_3_public
+-- No direct table access means no data exfiltration

3. No Secrets in Application Code

Password hashing and verification happen entirely in PostgreSQL. The application never sees raw passwords or hashes - it just passes them to functions.

4. Auditable Security Boundary

The security model is visible in the database schema:

This makes security audits straightforward.

5. Bcrypt Limit Protection

The segmented password hashing ensures long passwords remain secure. An attacker who knows about bcrypt's 72-byte limit gains no advantage.

Comparison with Traditional Approaches

Traditional StackThis Approach
App has full DB accessApp has minimal permissions
ORM manages all tablesApp can only call functions
Password hashing in app codePassword hashing in PostgreSQL
SQL injection = full compromiseSQL injection = limited scope
Security logic scatteredSecurity logic centralized in DB
Audit requires code reviewAudit visible in schema

Conclusion

Security should be built into the architecture, not bolted on. A restricted application role, a separate public schema, SECURITY DEFINER functions with a pinned search_path, and segmented bcrypt hashing inside the database add up to an application that stays secure by default - even when the layer above it is compromised.

Combined with NpgsqlRest's end-to-end type safety and superior performance, this approach delivers applications that are not only faster and more maintainable, but fundamentally more secure.

The database is your most trusted component - treat it that way.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,98)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/3_security_and_auth","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Cookie Authentication",href:"/config/auth#cookie-authentication"},{text:"Authentication Options",href:"/config/authentication-options"}]})])}const u=a(h,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.lean.js b/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.lean.js new file mode 100644 index 000000000..945284114 --- /dev/null +++ b/assets/blog_database-level-security-postgresql-authentication.md.CI9eBklt.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as e,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Database-Level Security: Building Secure Authentication with PostgreSQL","titleTemplate":"NpgsqlRest","description":"Implement Principle of Least Privilege at the database level. Build secure authentication with PostgreSQL SECURITY DEFINER, search path protection, and advanced password hashing.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Database-Level Security: Building Secure Authentication with PostgreSQL","titleTemplate":"NpgsqlRest","description":"Implement Principle of Least Privilege at the database level. Build secure authentication with PostgreSQL SECURITY DEFINER, search path protection, and advanced password hashing.","head":[["meta",{"name":"keywords","content":"postgresql security, database authentication, postgresql security definer, principle of least privilege postgresql, postgresql password hashing, secure api postgresql, npgsqlrest auth"}],["meta",{"property":"og:title","content":"Database-Level Security: Secure Authentication with PostgreSQL"}],["meta",{"property":"og:description","content":"Implement Principle of Least Privilege at the database level. Secure authentication with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Database-Level Security with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Implement Principle of Least Privilege. Build secure auth with PostgreSQL."}]]},"headers":[],"relativePath":"blog/database-level-security-postgresql-authentication.md","filePath":"blog/database-level-security-postgresql-authentication.md"}'),h={name:"blog/database-level-security-postgresql-authentication.md"};function k(r,s,c,d,o,g){const i=n("BlogNav");return e(),l("div",null,[s[0]||(s[0]=t("",98)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/3_security_and_auth","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Cookie Authentication",href:"/config/auth#cookie-authentication"},{text:"Authentication Options",href:"/config/authentication-options"}]})])}const u=a(h,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.js b/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.js new file mode 100644 index 000000000..1e25d6b9b --- /dev/null +++ b/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.js @@ -0,0 +1,497 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"End-to-End Static Type Checking: PostgreSQL to TypeScript","titleTemplate":"NpgsqlRest","description":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime. Full type safety from database to frontend.","frontmatter":{"layout":"doc","outline":[2,3],"title":"End-to-End Static Type Checking: PostgreSQL to TypeScript","titleTemplate":"NpgsqlRest","description":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime. Full type safety from database to frontend.","head":[["meta",{"name":"keywords","content":"postgresql typescript types, auto generate typescript from sql, type-safe postgresql api, npgsqlrest codegen, postgresql to typescript, database schema typescript, sql function typescript"}],["meta",{"property":"og:title","content":"End-to-End Static Type Checking: PostgreSQL to TypeScript"}],["meta",{"property":"og:description","content":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"End-to-End Static Type Checking: PostgreSQL to TypeScript"}],["meta",{"name":"twitter:description","content":"Auto-generate TypeScript types from PostgreSQL. Catch schema changes at compile time."}]]},"headers":[],"relativePath":"blog/end-to-end-static-type-checking-postgresql-typescript.md","filePath":"blog/end-to-end-static-type-checking-postgresql-typescript.md"}'),h={name:"blog/end-to-end-static-type-checking-postgresql-typescript.md"};function k(r,s,d,c,o,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l(`

End-to-End Static Type Checking: PostgreSQL to TypeScript

TypeScript · Type Safety · Developer Experience · December 2025


With NpgsqlRest's automatic type generation, a database schema change breaks your TypeScript build before it ever reaches production. This post walks through a complete example of end-to-end static type checking, from PostgreSQL functions to TypeScript client code.

The Problem with Traditional API Development

In typical REST API workflows, there's a dangerous gap between your database schema and your client code:

  1. You change a column name in PostgreSQL
  2. Your API continues to work (returning the new column name)
  3. Your frontend code silently breaks at runtime
  4. Users discover the bug, not your build system

This gap exists because types are defined in multiple places - database schemas, API layer, and client code - with no automatic synchronization.

Why PostgreSQL Functions?

PostgreSQL functions let you encapsulate business logic in the database. When you keep logic there:

But perhaps the most underappreciated benefit is static type checking. PostgreSQL functions have explicit return types that the database enforces. This creates a natural contract that can be propagated all the way to your client code.

The Solution: Single Source of Truth

NpgsqlRest solves this by making PostgreSQL the single source of truth:

code
PostgreSQL Function → NpgsqlRest → Generated TypeScript API Client → Your Application

Any change to the PostgreSQL function signature automatically regenerates the TypeScript interfaces. If your application code references a property that no longer exists, the TypeScript compiler fails the build.

SQL Files Also Generate TypeScript

Starting with v3.12.0, SQL file endpoints also generate TypeScript clients automatically. However, SQL files don't provide the same level of database-enforced type checking that functions do — there's no returns table(...) contract that PostgreSQL validates at creation time. For maximum type safety, PostgreSQL functions remain the strongest approach and are the focus of this post. SQL files are better suited for simpler queries where the convenience of skipping CREATE FUNCTION outweighs the stricter type checking.

Project Structure

Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/2_static_type_checking

The example follows this structure:

code
2_static_type_checking/
+├── sql/
+│   ├── R__example_2_tables.sql       # Schema and tables (repeatable)
+│   ├── A__example_2_get_users.sql    # get_users() function (always run)
+│   └── A__example_2_get_posts.sql    # get_posts() function (always run)
+├── src/
+│   ├── example2Api.ts                # Auto-generated by NpgsqlRest
+│   └── app.ts                        # Hand-written application code
+├── public/
+│   └── index.html                    # HTML entry point
+└── config.json                       # NpgsqlRest configuration

The file naming convention carries the weight:

The Database Schema

The schema creates two simple tables: users and posts.

sql
sql
-- R__example_2_tables.sql
+
+-- recreate entire schema example_2
+drop schema if exists example_2 cascade;
+create schema example_2;
+
+create table example_2.users (
+    user_id int primary key generated always as identity,
+    username text not null,
+    email text not null,
+    active boolean not null default true
+);
+
+insert into example_2.users (username, email, active) values
+('alice', 'alice@example.com', true),
+('bob', 'bob@example.com', true),
+('charlie', 'charlie@example.com', true);
+
+create table example_2.posts (
+    post_id int primary key generated always as identity,
+    user_id int references example_2.users(user_id) deferrable,
+    content text not null,
+    created_at timestamp not null default now()
+);
+
+insert into example_2.posts (user_id, content, created_at) values
+(1, 'Hello world! This is my first post.', '2024-01-15 10:30:00'),
+(1, 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'),
+(2, 'Just joined this platform.', '2024-01-17 09:00:00'),
+(3, 'Anyone here interested in databases?', '2024-01-18 11:45:00'),
+(2, 'Working on a new project today.', '2024-01-19 16:30:00');

Functions That Define the API Contract

The function definitions are where the contract lives. They are recreated on every build, which means:

  1. The function signature is always authoritative
  2. NpgsqlRest regenerates TypeScript types from the current function definition
  3. Any mismatch between the function and client code is caught at compile time

get_users()

sql
sql
-- A__example_2_get_users.sql
+
+create or replace function example_2.get_users()
+returns setof example_2.users
+language sql
+begin atomic;
+select user_id, username, email, active from example_2.users;
+end;
+
+comment on function example_2.get_users() is 'HTTP GET';
+
+do
+$$
+begin
+    assert (
+        select count(*) = 3
+        from example_2.get_users()
+        where (user_id, username, email, active) in (
+            (1, 'alice', 'alice@example.com', true),
+            (2, 'bob', 'bob@example.com', true),
+            (3, 'charlie', 'charlie@example.com', true)
+        )
+    ), 'get_users() does not contain expected data';
+end;
+$$;

Notice:

get_posts()

sql
sql
-- A__example_2_get_posts.sql
+
+create or replace function example_2.get_posts()
+returns table(
+    username text,
+    content text,
+    created_at timestamp
+)
+language sql
+begin atomic;
+select u.username, p.content, p.created_at
+from example_2.posts p join example_2.users u using(user_id)
+where u.active = true;
+end;
+
+comment on function example_2.get_posts() is 'HTTP GET';
+
+do
+$$
+begin
+    assert (
+        select count(*) = 5
+        from example_2.get_posts()
+        where (username, content, created_at) in (
+            ('alice', 'Hello world! This is my first post.', '2024-01-15 10:30:00'::timestamp),
+            ('alice', 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'::timestamp),
+            ('bob', 'Just joined this platform.', '2024-01-17 09:00:00'::timestamp),
+            ('charlie', 'Anyone here interested in databases?', '2024-01-18 11:45:00'::timestamp),
+            ('bob', 'Working on a new project today.', '2024-01-19 16:30:00'::timestamp)
+        )
+    ), 'get_posts() does not contain expected data';
+end;
+$$;

This function uses an explicit returns table(...) definition, specifying exactly which columns are returned. The function joins users and posts, filtering only active users.

Static Type Checking at the SQL Level

Before we even get to TypeScript, PostgreSQL itself performs static type checking on function definitions. That's the first line of defense.

How PostgreSQL Enforces Return Types

When you define a function with returns table(...) or returns setof, PostgreSQL validates that your function body actually returns the declared types. If there's a mismatch, the function creation fails.

Consider this scenario - you have a function returning username text:

sql
sql
create or replace function example_2.get_posts()
+returns table(
+    username text,
+    content text,
+    created_at timestamp
+)
+language sql
+begin atomic;
+select u.username, p.content, p.created_at
+from example_2.posts p join example_2.users u using(user_id)
+where u.active = true;
+end;

Now imagine someone changes the username column in the users table to jsonb:

sql
sql
alter table example_2.users alter column username type jsonb using to_jsonb(username);

The next time you run your migration (which recreates the function), PostgreSQL will fail:

code
ERROR:  return type mismatch in function declared to return record
+DETAIL:  Final statement returns jsonb instead of text at column 1.

The database itself caught the type error. This happens at migration time, before any application code runs, before any TypeScript is compiled.

The Return Type Contract

PostgreSQL function return types create an explicit contract:

Return Type DeclarationContract
returns setof usersMust return all columns of users table, with matching types
returns table(username text, ...)Must return exactly these columns with these types
returns intMust return a single integer value
returns voidMust not return a value

This contract is enforced when:

  1. The function is created or replaced
  2. The function is called

If the underlying table structure changes in a way that breaks the contract, you discover it immediately when recreating the function - not when a user triggers the code path in production.

Type Changes Propagate Naturally

The workflow becomes:

  1. Change a column type in PostgreSQL
  2. Function recreation fails during migration (if return type doesn't match)
  3. Update the function to handle the new type
  4. NpgsqlRest regenerates TypeScript interfaces
  5. TypeScript build fails if client code uses the old type
  6. Update client code to match

Every layer validates types. Errors surface at the earliest possible moment.

Why Functions Are Recreated on Every Build

The A__ prefix ensures these SQL files run on every database migration. This is the key to enforcing type checking:

Scenario: You decide to rename content to body in get_posts().

  1. You update the SQL function to return body instead of content
  2. Database migration runs, recreating the function with the new signature
  3. NpgsqlRest regenerates example2Api.ts with the new interface
  4. TypeScript build fails: Property 'content' does not exist on type 'IGetPostsResponse'
  5. You fix app.ts to use body instead of content
  6. Build succeeds

Without the function recreation on each build, the old TypeScript types would persist, and the error would only surface at runtime.

Built-in Testing with SQL Assertions

Update — since NpgsqlRest 3.19

The pattern described here — SQL assertions with deferrable constraints and rollback isolation — is now a first-class feature: the SQL test runner (npgsqlrest --test) runs .sql test files against the real HTTP endpoints in-process, on each test's own transaction, with parallel isolated connections, watch mode, and endpoint-coverage reporting. Everything below still applies; the runner gives it a harness, a report, and CI integration.

Each function file includes an assert block that validates the function returns expected data. These assertions run during migration, providing immediate feedback:

sql
sql
do
+$$
+begin
+    assert (
+        select count(*) = 3
+        from example_2.get_users()
+        where (user_id, username, email, active) in (
+            (1, 'alice', 'alice@example.com', true),
+            (2, 'bob', 'bob@example.com', true),
+            (3, 'charlie', 'charlie@example.com', true)
+        )
+    ), 'get_users() does not contain expected data';
+end;
+$$;

If the assertion fails, the migration fails, and you know immediately that something is wrong. This creates a safety net ensuring:

  1. The function executes without errors
  2. The function returns the expected structure
  3. Test data is present and correct

This is database-level unit testing that runs on every deployment.

Unit Testing PostgreSQL Functions: Beyond Fixed Data

The example above uses fixed test data that's inserted during migration. This approach is simple and effective, but what about testing with dynamic data or edge cases? Here are sturdier testing patterns.

Co-located Tests: Function and Test in the Same File

The most practical approach is to place tests directly in the same file as the function being tested. Since these files run on every build (the A__ prefix), your tests execute automatically with every migration.

Here's the complete pattern for A__example_2_get_users.sql:

sql
sql
-- A__example_2_get_users.sql
+
+create or replace function example_2.get_users()
+returns setof example_2.users
+language sql
+begin atomic;
+select user_id, username, email, active from example_2.users;
+end;
+
+comment on function example_2.get_users() is 'HTTP GET';
+
+-- Test: Verify function returns expected fixed data
+do
+$$
+begin
+    assert (
+        select count(*) = 3
+        from example_2.get_users()
+        where (user_id, username, email, active) in (
+            (1, 'alice', 'alice@example.com', true),
+            (2, 'bob', 'bob@example.com', true),
+            (3, 'charlie', 'charlie@example.com', true)
+        )
+    ), 'get_users() does not contain expected data';
+end;
+$$;

The function and its test live together. When the function changes, the test is right there to update.

Test Isolation with Rollback

Unit tests must not interfere with each other. The solution: end your test block with rollback; to undo any data modifications.

sql
sql
-- Test: get_users() returns newly inserted users
+do
+$$
+begin
+    -- Insert test data
+    insert into example_2.users (username, email, active)
+    values ('test_user', 'test@example.com', true);
+
+    -- Verify the function returns our test user
+    assert (
+        select count(*) = 1
+        from example_2.get_users()
+        where username = 'test_user'
+    ), 'get_users() should return the inserted test user';
+
+    -- Rollback to undo the insert
+    rollback;
+end;
+$$;

The rollback; at the end ensures the test data never persists. Each test starts with a clean slate.

Testing Multiple Scenarios

You can have multiple test blocks in the same file, each testing a different scenario:

sql
sql
-- A__example_2_get_posts.sql
+
+create or replace function example_2.get_posts()
+returns table(
+    username text,
+    content text,
+    created_at timestamp
+)
+language sql
+begin atomic;
+select u.username, p.content, p.created_at
+from example_2.posts p join example_2.users u using(user_id)
+where u.active = true;
+end;
+
+comment on function example_2.get_posts() is 'HTTP GET';
+
+-- Test 1: Verify function returns expected fixed data
+do
+$$
+begin
+    assert (
+        select count(*) = 5
+        from example_2.get_posts()
+        where (username, content, created_at) in (
+            ('alice', 'Hello world! This is my first post.', '2024-01-15 10:30:00'::timestamp),
+            ('alice', 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'::timestamp),
+            ('bob', 'Just joined this platform.', '2024-01-17 09:00:00'::timestamp),
+            ('charlie', 'Anyone here interested in databases?', '2024-01-18 11:45:00'::timestamp),
+            ('bob', 'Working on a new project today.', '2024-01-19 16:30:00'::timestamp)
+        )
+    ), 'get_posts() does not contain expected data';
+end;
+$$;
+
+-- Test 2: Verify inactive users' posts are excluded
+do
+$$
+declare
+    _count int;
+begin
+    -- Make charlie inactive
+    update example_2.users set active = false where username = 'charlie';
+
+    -- Count posts from charlie (should be 0 since he's now inactive)
+    select count(*) into _count
+    from example_2.get_posts()
+    where username = 'charlie';
+
+    assert _count = 0,
+        'get_posts() should not return posts from inactive users';
+
+    rollback;
+end;
+$$;
+
+-- Test 3: New posts from active users appear in results
+do
+$$
+declare
+    _result record;
+begin
+    -- Insert a new post
+    insert into example_2.posts (user_id, content, created_at)
+    values (1, 'Brand new post!', now());
+
+    -- Verify it appears in results
+    select * into _result
+    from example_2.get_posts()
+    where content = 'Brand new post!';
+
+    assert _result.username = 'alice',
+        'New post should appear with correct username';
+
+    rollback;
+end;
+$$;

Testing Against Empty Tables

To test how a function behaves with no data:

sql
sql
-- Test: Function handles empty tables gracefully
+do
+$$
+declare
+    _count int;
+begin
+    -- Delete all data
+    delete from example_2.posts;
+    delete from example_2.users;
+
+    -- Test that function returns 0 rows (not an error)
+    select count(*) into _count from example_2.get_users();
+    assert _count = 0,
+        'get_users() should return 0 rows when table is empty';
+
+    select count(*) into _count from example_2.get_posts();
+    assert _count = 0,
+        'get_posts() should return 0 rows when table is empty';
+
+    rollback;
+end;
+$$;

Deferrable Constraints: The Key to Test Data

Notice in our schema definition:

sql
sql
user_id int references example_2.users(user_id) deferrable

The deferrable keyword is crucial for testing. By default, foreign key constraints are checked immediately when you insert a row. With deferrable constraints, checks can be deferred until the end of the transaction.

Since test transactions are rolled back anyway, deferrable constraints let you insert test data without first populating a dozen related tables. That removes most of the test setup work.

sql
sql
-- Test: Posts with deferred constraints
+do
+$$
+begin
+    -- Defer all constraint checks until transaction end
+    set constraints all deferred;
+
+    -- Insert post with non-existent user_id (constraint check deferred)
+    insert into example_2.posts (user_id, content, created_at)
+    values (999, 'Orphan post', now());
+
+    -- We can test our function here
+    -- The FK violation won't be checked because we'll rollback
+
+    rollback;
+end;
+$$;

Why Database Testing is Fast

A common misconception is that database testing is slow. In reality, PostgreSQL testing can be faster than application-level testing because:

  1. No network overhead: Tests run inside the database
  2. Transaction rollback is instant: No need to truncate tables or restore backups
  3. Parallel execution: Tests in separate transactions can run concurrently
  4. No ORM overhead: Direct SQL execution

The key is proper isolation through transactions, not through recreating entire databases for each test.

Addressing Common Myths

"Testing in a database is impossible"

This is simply not true for PostgreSQL. While testing stored procedures might be difficult in some database systems, PostgreSQL provides all the tools you need: anonymous blocks, assertions, and transaction control.

"Testing in a database is slow"

It's actually faster than most alternatives. When your test ends with rollback;, there's no cleanup needed - no truncating tables, no restoring backups. The rollback is nearly instantaneous. Compare this to spinning up Docker containers or re-seeding test databases.

"You can't do TDD with SQL"

You absolutely can. Write your test first in the function file:

sql
sql
-- A__example_2_create_user.sql
+
+-- Test first (this will fail until we implement create_user)
+do
+$$
+begin
+    perform example_2.create_user('newuser', 'new@example.com');
+
+    assert exists (
+        select 1 from example_2.users where username = 'newuser'
+    ), 'User should be created';
+
+    rollback;
+end;
+$$;
+
+-- Now implement the function to make the test pass
+create or replace function example_2.create_user(_username text, _email text)
+returns void
+language sql
+begin atomic;
+insert into example_2.users (username, email, active)
+values (_username, _email, true);
+end;

Red-green-refactor works just as well in SQL as anywhere else. The test runs on every build, ensuring the function continues to work as expected.

The Generated TypeScript Client

NpgsqlRest automatically generates example2Api.ts based on the PostgreSQL function signatures:

typescript
typescript
// autogenerated at 2025-12-31T12:06:45.2201980+01:00
+
+const baseUrl = "http://127.0.0.1:8080";
+
+interface IGetPostsResponse {
+    username: string | null;
+    content: string | null;
+    createdAt: string | null;
+}
+
+interface IGetUsersResponse {
+    userId: number | null;
+    username: string | null;
+    email: string | null;
+    active: boolean | null;
+}
+
+/**
+* function example_2.get_posts()
+* returns table(
+*     username text,
+*     content text,
+*     created_at timestamp without time zone
+* )
+*/
+export async function getPosts() : Promise<{
+    status: number,
+    response: IGetPostsResponse[],
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    const response = await fetch(baseUrl + "/api/example-2/get-posts", {
+        method: "GET",
+        headers: {
+            "Content-Type": "application/json"
+        },
+    });
+    return {
+        status: response.status,
+        response: response.ok ? await response.json() as IGetPostsResponse[] : undefined!,
+        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as {status: number; title: string; detail?: string | null} : undefined
+    };
+}
+
+/**
+* function example_2.get_users()
+* returns table(
+*     user_id integer,
+*     username text,
+*     email text,
+*     active boolean
+* )
+*/
+export async function getUsers() : Promise<{
+    status: number,
+    response: IGetUsersResponse[],
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    const response = await fetch(baseUrl + "/api/example-2/get-users", {
+        method: "GET",
+        headers: {
+            "Content-Type": "application/json"
+        },
+    });
+    return {
+        status: response.status,
+        response: response.ok ? await response.json() as IGetUsersResponse[] : undefined!,
+        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as {status: number; title: string; detail?: string | null} : undefined
+    };
+}

Key features of the generated code:

  1. Interfaces match the PostgreSQL return types exactly - Column names are converted from snake_case to camelCase
  2. Nullable fields use | null - Reflecting PostgreSQL's nullable columns
  3. JSDoc comments include the original function signature - Making it easy to trace back to the source
  4. Typed error handling - Errors have a consistent structure with status, title, and optional detail

The Application Code

The hand-written application code in app.ts demonstrates how type safety flows through to the UI layer:

typescript
typescript
import { getPosts, getUsers } from "./example2Api.ts";
+
+const app = document.getElementById("app")!;
+
+// Render a single user row - uses IGetUsersResponse properties
+function renderUserRow(user: {
+    userId: number | null;
+    username: string | null;
+    email: string | null;
+    active: boolean | null
+}) {
+    const row = document.createElement("tr");
+    row.innerHTML = \`
+        <td>\${user.userId}</td>
+        <td>\${user.username ?? "Anonymous"}</td>
+        <td>\${user.email ?? "N/A"}</td>
+        <td>\${user.active ? "✓" : "✗"}</td>
+    \`;
+    return row;
+}
+
+// Render a single post - uses IGetPostsResponse properties
+function renderPost(post: {
+    username: string | null;
+    content: string | null;
+    createdAt: string | null
+}) {
+    const article = document.createElement("article");
+    article.className = "post";
+    article.innerHTML = \`
+        <header><strong>\${post.username ?? "Anonymous"}</strong></header>
+        <p>\${post.content ?? ""}</p>
+        <footer><small>\${
+            post.createdAt
+                ? new Date(post.createdAt).toLocaleString()
+                : "Unknown date"
+        }</small></footer>
+    \`;
+    return article;
+}
+
+// Load and display users
+async function loadUsers() {
+    const { status, response: users, error } = await getUsers();
+
+    if (status !== 200) {
+        app.innerHTML = \`<p>Error loading users: \${error?.title}</p>\`;
+        return;
+    }
+
+    const table = document.createElement("table");
+    table.innerHTML = \`
+        <thead>
+            <tr>
+                <th>ID</th>
+                <th>Username</th>
+                <th>Email</th>
+                <th>Active</th>
+            </tr>
+        </thead>
+    \`;
+    const tbody = document.createElement("tbody");
+
+    // Static type checking happens here!
+    // If IGetUsersResponse changes (e.g., "username" renamed to "name"),
+    // TypeScript will fail the build with: Property 'username' does not exist
+    for (const user of users) {
+        tbody.appendChild(renderUserRow(user));
+    }
+
+    table.appendChild(tbody);
+    app.appendChild(table);
+}
+
+// Load and display posts
+async function loadPosts() {
+    const { status, response: posts, error } = await getPosts();
+
+    if (status !== 200) {
+        app.innerHTML += \`<p>Error loading posts: \${error?.title}</p>\`;
+        return;
+    }
+
+    const postsSection = document.createElement("section");
+    postsSection.innerHTML = "<h2>Posts</h2>";
+
+    // Static type checking happens here!
+    // If IGetPostsResponse changes (e.g., "content" renamed to "body"),
+    // TypeScript will fail the build with: Property 'content' does not exist
+    for (const post of posts) {
+        postsSection.appendChild(renderPost(post));
+    }
+
+    app.appendChild(postsSection);
+}
+
+// Initialize the app
+async function init() {
+    app.innerHTML = "<h1>Users & Posts</h1>";
+    await loadUsers();
+    await loadPosts();
+}
+
+init();

The critical type-checking points are in the loop bodies where we access properties:

If any of these properties are renamed or removed in the PostgreSQL function, the TypeScript build fails immediately.

The Complete Type-Safe Workflow

Here's what happens during development:

bash
bash
# 1. Run database migrations (recreates functions every time)
+bun run db:up
+
+# 2. Start NpgsqlRest (regenerates example2Api.ts from current functions)
+bun run dev
+
+# 3. Build TypeScript (catches any type mismatches)
+bun run build

If you change a column name in a PostgreSQL function:

  1. db:up recreates the function with the new column
  2. NpgsqlRest detects the schema change and regenerates example2Api.ts
  3. bun run build fails because app.ts references the old column name
  4. You update app.ts to use the new name
  5. Build succeeds

The error is caught at build time, not runtime. No more mysterious undefined values in production.

Configuration

The NpgsqlRest configuration enables type generation:

json
json
{
+  "ApplicationName": "static_type_checking",
+  "StaticFiles": {
+    "RootPath": "./2_static_type_checking/public"
+  },
+  "NpgsqlRest": {
+    "IncludeSchemas": [ "example_2" ],
+    "RequiresAuthorization": false,
+    "ClientCodeGen": {
+      "FilePath": "./2_static_type_checking/src/{0}Api.ts"
+    }
+  }
+}

The {0} placeholder in FilePath is replaced with the schema name, so example_2 becomes example2Api.ts.

Benefits of This Approach

1. Single Source of Truth

PostgreSQL functions define both the API contract and the TypeScript types. No manual synchronization required.

2. Compile-Time Safety

Schema changes break the build, not production. You discover problems during development, not from user reports.

3. Automatic Documentation

The generated code includes JSDoc comments with the original PostgreSQL function signature. Your IDE shows exactly what the database returns.

4. Database-Level Testing

SQL assertions validate your functions return the expected data structure. Tests run on every migration.

5. No Runtime Type Checking Overhead

Types are enforced at compile time. The generated JavaScript has zero overhead compared to hand-written fetch calls.

Conclusion

Make PostgreSQL the single source of truth, regenerate types on every build, and schema mismatches get caught before they reach production.

The key ingredients:

  1. PostgreSQL function return types that create explicit contracts enforced by the database
  2. Always-run migrations (A__ prefix) that recreate functions and run tests on every build
  3. Co-located tests in the same file as the function, using simple do $$ ... rollback; end; $$; blocks
  4. Deferrable constraints that enable isolated, fast unit tests
  5. NpgsqlRest's type generation that creates TypeScript interfaces from PostgreSQL signatures
  6. TypeScript compilation that catches property mismatches

The result is a multi-layered type safety system:

code
PostgreSQL Schema
+      ↓ (enforced by DB)
+PostgreSQL Functions + Tests
+      ↓ (generated by NpgsqlRest)
+TypeScript Interfaces
+      ↓ (enforced by tsc)
+Application Code

Schema changes propagate through the entire stack, with the database and the compilers acting as your safety net.

And unit testing in PostgreSQL isn't slow or difficult. Anonymous blocks, transaction rollback, and deferrable constraints support the same TDD workflows you'd use in any other language - against your actual database logic, not a mock of it.

Why This Stack is Superior

Compare this approach to traditional stacks:

Traditional StackThis Stack
Database schemaDatabase schema
ORM models
Repository layer
Service layerPostgreSQL functions (or SQL files for simpler queries)
Controller layer
API documentation
TypeScript types (manual)TypeScript types (generated)
Integration testsCo-located SQL tests

You eliminate entire layers. No ORM mappings to maintain. No repository pattern boilerplate. No controller classes. No manual API documentation. No hand-written TypeScript interfaces that drift out of sync with reality.

The code you don't write has no bugs.

Performance That Scales

The architecture also pays off at runtime, because NpgsqlRest skips the overhead that traditional frameworks accumulate:

In benchmarks, it achieves 4,588 requests per second at 100 concurrent users - alongside Swoole PHP and ahead of Bun, Go, Fastify, and Spring Boot.

Maximum Type Safety, Minimum Code

Traditional approaches require you to define types in multiple places and hope they stay synchronized:

code
Database → ORM → Service → Controller → OpenAPI → Client SDK → Frontend

Each arrow is a potential desynchronization point. Each layer requires manual maintenance.

With NpgsqlRest:

code
Database → Generated Client → Frontend

One source of truth. Zero manual synchronization. Types flow automatically from database to browser.

The Bottom Line

This stack delivers:

When you combine database-enforced types, automated type generation, compile-time checking, and co-located SQL tests, you get a development experience where "it works on my machine" actually means "it will work in production."

`,165)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking","get-started":[{text:"TypeScript Code Generation Walkthrough",href:"/blog/typescript-codegen-walkthrough"},{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Code Generation Config",href:"/config/codegen"}]})])}const C=a(h,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.lean.js b/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.lean.js new file mode 100644 index 000000000..05e28d219 --- /dev/null +++ b/assets/blog_end-to-end-static-type-checking-postgresql-typescript.md.B_IRfXH-.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"End-to-End Static Type Checking: PostgreSQL to TypeScript","titleTemplate":"NpgsqlRest","description":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime. Full type safety from database to frontend.","frontmatter":{"layout":"doc","outline":[2,3],"title":"End-to-End Static Type Checking: PostgreSQL to TypeScript","titleTemplate":"NpgsqlRest","description":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime. Full type safety from database to frontend.","head":[["meta",{"name":"keywords","content":"postgresql typescript types, auto generate typescript from sql, type-safe postgresql api, npgsqlrest codegen, postgresql to typescript, database schema typescript, sql function typescript"}],["meta",{"property":"og:title","content":"End-to-End Static Type Checking: PostgreSQL to TypeScript"}],["meta",{"property":"og:description","content":"Automatically generate TypeScript types from PostgreSQL functions. Catch database schema changes at compile time, not runtime."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"End-to-End Static Type Checking: PostgreSQL to TypeScript"}],["meta",{"name":"twitter:description","content":"Auto-generate TypeScript types from PostgreSQL. Catch schema changes at compile time."}]]},"headers":[],"relativePath":"blog/end-to-end-static-type-checking-postgresql-typescript.md","filePath":"blog/end-to-end-static-type-checking-postgresql-typescript.md"}'),h={name:"blog/end-to-end-static-type-checking-postgresql-typescript.md"};function k(r,s,d,c,o,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l("",165)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking","get-started":[{text:"TypeScript Code Generation Walkthrough",href:"/blog/typescript-codegen-walkthrough"},{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Code Generation Config",href:"/config/codegen"}]})])}const C=a(h,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.js b/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.js new file mode 100644 index 000000000..10ce534d3 --- /dev/null +++ b/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.js @@ -0,0 +1,154 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","titleTemplate":"NpgsqlRest","description":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. Zero allocations, native type mapping, and one SQL annotation. No more server-crashing export code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","titleTemplate":"NpgsqlRest","description":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. Zero allocations, native type mapping, and one SQL annotation. No more server-crashing export code.","head":[["meta",{"name":"keywords","content":"postgresql excel export, xlsx streaming export, spreadcheetah postgresql, npgsqlrest excel, zero allocation excel, postgresql report download, table format rendering, postgresql stats endpoints"}],["meta",{"property":"og:title","content":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"}],["meta",{"property":"og:description","content":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. No more server-crashing export code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"}],["meta",{"name":"twitter:description","content":"Stream .xlsx Excel exports from PostgreSQL with constant memory. Zero allocations, native types, one annotation."}]]},"headers":[],"relativePath":"blog/excel-export-table-format-postgresql-npgsqlrest.md","filePath":"blog/excel-export-table-format-postgresql-npgsqlrest.md"}'),h={name:"blog/excel-export-table-format-postgresql-npgsqlrest.md"};function r(k,s,o,d,c,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l(`

Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL

February 2026 · ExcelExportPostgreSQLPerformanceNpgsqlRest

After 20 years of maintaining codebases, I can confidently say: Excel export code is the worst part of any system. It eats all the memory. It crashes servers. It brings down production. I've seen teams isolate export services behind separate infrastructure just so a report download doesn't take the rest of the application with it.

NpgsqlRest 3.7.0 removes the problem at the source. One SQL annotation turns any PostgreSQL function into a streaming .xlsx download with constant memory usage, zero allocations per cell, and native Excel type mapping - all without writing a single line of application code.

Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/14_table_format

Why Excel Exports Are Terrible

The traditional approach to Excel exports goes something like this:

  1. Execute a query and load the entire result set into memory
  2. Create an in-memory workbook object (another copy of all the data)
  3. Write cells one by one (allocating strings for each cell value)
  4. Serialize the workbook to a byte array (yet another copy)
  5. Send the byte array to the client

For a 1 million row report, you're looking at 3-4x the data size in memory simultaneously.

And that's the best case. In practice, export code often introduces N+1 query patterns that make things catastrophically worse. The export iterates over rows, and for each row it fires off additional queries to fetch related data - customer names, product details, category labels, audit info. A 100,000-row export becomes 100,001 database queries. The database connection pool fills up, response times spike across the entire application, and now your export isn't just crashing the export service - it's dragging down every other request in the system.

This is why export endpoints are the number one cause of OutOfMemoryException in enterprise systems. It's why teams build separate "report servers" and queue-based export systems. It's why a simple "Download to Excel" button requires an entire architecture discussion.

The NpgsqlRest Approach: Pure Streaming

NpgsqlRest's table format rendering never builds a workbook at all:

mermaid
flowchart LR
+    PG["PostgreSQL
+    NpgsqlDataReader
+    (one row at a time)"]
+
+    PG -- "row by row" --> SC["SpreadCheetah
+    (forward-only writer)"]
+
+    SC -- "streaming" --> PW["PipeWriter
+    (HTTP response stream)"]
+
+    PW -- ".xlsx download" --> BR["Browser"]

Rows flow directly from PostgreSQL to the browser. The full result set is never materialized in memory. There are no intermediate collections. No buffering the entire dataset.

All you need is one annotation:

sql
sql
comment on function get_report() is '
+HTTP GET
+@table_format = excel
+';

That's it. Your function's result set streams straight to the user's browser as an .xlsx download.

What Makes This Special

Zero-Allocation Cell Writing

The implementation uses SpreadCheetah - a library designed specifically for forward-only spreadsheet generation with minimal allocations.

SpreadCheetah writes cells as value types (DataCell structs), avoiding heap allocations per cell. Combined with a pre-allocated cell array that gets reused for every row, memory pressure stays flat regardless of dataset size.

Native Type Mapping

Integers, decimals, booleans, and DateTimes are written as native Excel types directly from the PostgreSQL reader - not stringified and re-parsed. Your numbers stay as numbers. Your dates stay as dates. Excel formulas work immediately without "Convert to Number" warnings.

PostgreSQL TypeExcel Type
int, bigintNumber
numeric, floatNumber (with format)
booleanBoolean
date, timestampDateTime
text, varcharString
json, jsonbString

Constant Memory Usage

Since Excel is compressed XML under the hood, some buffering is needed for the compression layer. But that buffering doesn't exceed ~80KB during export operations, regardless of whether you're exporting 100 rows or 10 million rows.

AOT/Trim Compatible

No reflection, no expression trees, no runtime code generation. Works with .NET's PublishAot and full trimming - important for the single-binary deployment that NpgsqlRest uses.

Building an Excel Export Endpoint

Step 1: Write Your Function

Your PostgreSQL function defines the report. The return columns become Excel columns:

sql
sql
create or replace function example_14.get_data(
+    _format text,
+    _excel_file_name text = null,
+    _excel_sheet text = null
+)
+returns table (
+    int_val int,
+    bigint_val bigint,
+    numeric_val numeric(10,4),
+    float_val double precision,
+    bool_val bool,
+    text_val text,
+    date_val date,
+    timestamp_val timestamp,
+    time_val time,
+    json_val json,
+    null_text text,
+    null_int int
+)
+language sql
+begin atomic;
+select * from (values
+    (42,        9999999999::bigint, 3.1415::numeric(10,4), 2.71828::float8, true,  'hello world',      '2025-06-15'::date, '2025-06-15 14:30:00'::timestamp, '09:45:30'::time, '{"key":"value"}'::json, null::text, null::int),
+    (-1,        0::bigint,          0.0001::numeric(10,4), -99.99::float8,  false, 'special <chars> &', '2000-01-01'::date, '2000-01-01 00:00:00'::timestamp, '23:59:59'::time, '[1,2,3]'::json,        'not null', 7),
+    (2147483647, -1::bigint,        99999.9999::numeric(10,4), 0::float8,   true,  '',                  '1999-12-31'::date, '1999-12-31 23:59:59'::timestamp, '00:00:00'::time, 'null'::json,           null::text, null::int)
+);
+end;

In a real application, this would be your reporting query - joins across tables, aggregations, window functions, whatever your report needs. The point is: the report logic lives in PostgreSQL, not in application code.

Step 2: Add the Annotation

The function comment controls everything:

sql
sql
comment on function example_14.get_data(text,text,text) is '
+HTTP GET
+@authorize
+@table_format = {_format}
+@excel_file_name = {_excel_file_name}
+@excel_sheet = {_excel_sheet}
+@tsclient_url_only = true
+';
AnnotationEffect
@authorizeRequire authentication
@table_format = {_format}Dynamic format from _format parameter (html or excel)
@excel_file_name = {_excel_file_name}Custom download filename from parameter
@excel_sheet = {_excel_sheet}Custom worksheet name from parameter
@tsclient_url_only = trueGenerate only URL constant in TypeScript (not a fetch function)

The {_format} placeholder resolves the table format from a function parameter, so the same endpoint serves both HTML table views and Excel downloads depending on what the client requests.

Step 3: Configure Table Format

Enable the feature in your config.json:

json
json
{
+  "NpgsqlRest": {
+    "TableFormatOptions": {
+      "Enabled": true,
+      "HtmlEnabled": true,
+      "ExcelEnabled": true,
+      "ExcelKey": "excel"
+    }
+  }
+}

That's the entire backend. No libraries to install. No export service to build. No memory tuning to configure.

Two Formats, One Endpoint

The dynamic @table_format = {_format} pattern means the same function serves both HTML and Excel:

HTML view (for browser preview):

code
GET /api/example-14/get-data?format=html

Renders a styled HTML table in the browser - perfect for quick previews and copy-paste into Excel.

Excel download (for file export):

code
GET /api/example-14/get-data?format=excel

Streams a properly-typed .xlsx file as a download.

Static Format Annotation

If you don't need dynamic format switching, you can hardcode the format:

sql
sql
-- Always Excel download
+comment on function monthly_report() is '
+HTTP GET
+@table_format = excel
+@excel_file_name = monthly_report.xlsx
+@excel_sheet = Report Data
+';
sql
sql
-- Always HTML table
+comment on function dashboard_data() is '
+HTTP GET
+@table_format = html
+';

The TypeScript Client: URL-Only Generation

For table format endpoints, the traditional fetch-based client doesn't make sense. You don't fetch() an Excel file - you navigate to it. That's what @tsclient_url_only = true is for.

NpgsqlRest generates only the URL constant and request interface:

typescript
typescript
// Auto-generated - no fetch function, just the URL builder
+export const getDataUrl = (request: IGetDataRequest) =>
+    baseUrl + "/api/example-14/get-data" + parseQuery(request);
+
+interface IGetDataRequest {
+    format: string | null;
+    excelFileName?: string | null;
+    excelSheet?: string | null;
+}

Using it in your frontend:

typescript
typescript
import { getDataUrl } from "./example14Api.ts";
+
+// HTML preview - open in browser
+htmlLink.href = getDataUrl({ format: "html" });
+
+// Excel download - navigate to trigger download
+excelLink.addEventListener("click", (e) => {
+    e.preventDefault();
+    const dateStr = new Date().toISOString().slice(0, 19).replace(/[-:]/g, "");
+    const fileName = \`data-\${dateStr}.xlsx\`;
+    const sheetName = \`data-\${dateStr}\`;
+    document.location.href = getDataUrl({
+        format: "excel",
+        excelFileName: fileName,
+        excelSheet: sheetName
+    });
+});

Notice how the Excel filename includes a timestamp - each download gets a unique name. This is resolved on the server from the {_excel_file_name} placeholder.

Or for the simplest case, just a plain HTML link:

html
html
<a href="/api/example-14/get-data?format=html">View as HTML Table</a>
+<a href="/api/example-14/get-data?format=excel">Download Excel</a>

No JavaScript required for basic usage.

Excel Format Configuration

DateTime and Numeric Formats

Control how dates and numbers appear in Excel cells:

json
json
{
+  "NpgsqlRest": {
+    "TableFormatOptions": {
+      "Enabled": true,
+      "ExcelEnabled": true,
+      "ExcelDateTimeFormat": "yyyy-mm-dd hh:mm",
+      "ExcelNumericFormat": "#,##0.00"
+    }
+  }
+}
OptionDefaultExamples
ExcelDateTimeFormatyyyy-MM-dd HH:mm:ssyyyy-mm-dd, dd/mm/yyyy hh:mm
ExcelNumericFormatGeneral#,##0.00, 0.00, #,##0

These are Excel Format Codes, not .NET format strings. They control how Excel displays the values in cells.

Worksheet and File Names

Set defaults globally, override per-endpoint:

json
json
{
+  "NpgsqlRest": {
+    "TableFormatOptions": {
+      "ExcelSheetName": "Data"
+    }
+  }
+}

Per-endpoint overrides via annotations:

sql
sql
comment on function quarterly_report(_quarter int, _year int) is '
+HTTP GET
+@table_format = excel
+@excel_file_name = Q{_quarter}_{_year}_report.xlsx
+@excel_sheet = Q{_quarter} {_year}
+';

Calling GET /api/quarterly-report?quarter=2&year=2026 downloads Q2_2026_report.xlsx with a worksheet named Q2 2026.

HTML Table Format

The HTML format renders results as a styled table suitable for browser viewing:

sql
sql
comment on function get_report() is '
+HTTP GET
+@table_format = html
+';

The HTML output is designed for copy-paste into Excel. Select the table in your browser, paste into Excel, and the data transfers cleanly with proper column alignment.

You can customize the HTML wrapper:

json
json
{
+  "NpgsqlRest": {
+    "TableFormatOptions": {
+      "HtmlEnabled": true,
+      "HtmlHeader": "<!DOCTYPE html><html><head><style>table { border-collapse: collapse; } th, td { border: 1px solid #ddd; padding: 8px; }</style></head><body>",
+      "HtmlFooter": "</body></html>"
+    }
+  }
+}

Bonus: Built-In Statistics Endpoints

Version 3.7.0 also introduced PostgreSQL statistics endpoints - built-in HTTP endpoints for monitoring your database performance without writing any SQL:

json
json
{
+  "Stats": {
+    "Enabled": true,
+    "OutputFormat": "html",
+    "SchemaSimilarTo": "example_14"
+  }
+}

This gives you four monitoring endpoints out of the box:

EndpointSourceWhat It Shows
/stats/routinespg_stat_user_functionsFunction call counts, execution times
/stats/tablespg_stat_user_tablesTuple counts, table sizes, scan counts, vacuum info
/stats/indexespg_stat_user_indexesIndex scan counts, index definitions
/stats/activitypg_stat_activityActive sessions, running queries, wait events

The default html output format renders as an HTML table you can view in the browser or copy-paste into Excel - the same HTML table format used by the table format rendering system.

Stats Configuration Options

json
json
{
+  "Stats": {
+    "Enabled": true,
+    "OutputFormat": "html",
+    "CacheDuration": "5 seconds",
+    "RequireAuthorization": true,
+    "AuthorizedRoles": ["admin"],
+    "RateLimiterPolicy": "fixed",
+    "SchemaSimilarTo": "my_schema"
+  }
+}
OptionDescription
OutputFormathtml (default) or json
CacheDurationCache responses to avoid hitting pg_stat views on every request
RequireAuthorizationLock down stats endpoints (recommended for production)
AuthorizedRolesRestrict access to specific roles
SchemaSimilarToFilter stats to a specific schema pattern
ConnectionNameQuery stats from a different connection (e.g., read replica)

For routine statistics to work, make sure track_functions is enabled in PostgreSQL:

sql
sql
alter system set track_functions = 'all';
+select pg_reload_conf();

The Traditional Way vs. This Way

Here's what Excel export typically looks like in a traditional codebase:

Traditional Excel ExportNpgsqlRest
Install EPPlus/ClosedXML/NPOINothing to install
Write query execution codeWrite your SQL function
Build in-memory workbookStreaming - no workbook object
Handle type conversions manuallyNative type mapping
Manage memory for large exportsConstant ~80KB buffer
Write download response handlingAutomatic
Separate export service for safetyNot needed
TypeScript types for parametersAuto-generated
200-500 lines of codeOne SQL annotation

Memory Profile Comparison

For a 1 million row export with 10 columns:

MetricTraditional (EPPlus/ClosedXML)NpgsqlRest + SpreadCheetah
Peak memory500MB - 2GB+~80KB buffer
Allocation rateMillions of objectsZero per-cell allocations
Time to first byteAfter entire workbook builtImmediate (streaming)
Risk of OOM crashHighNone

Running the Example

bash
bash
# Clone the repository
+git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
+cd npgsqlrest-docs/examples
+
+# Install dependencies
+bun install
+
+# Navigate to the example
+cd 14_table_format
+
+# Apply database migrations
+bun run db:up
+
+# Start the server
+bun run dev

Open http://localhost:8080, log in with alice / password123, and try both the HTML view and Excel download links.

Conclusion

Write the report as a PostgreSQL function, add @table_format = excel, and rows stream to the browser with native Excel types and a constant ~80KB buffer. No export library, no separate report server, no server crash at 3 AM because someone exported a large report.

One caveat: this produces raw tabular .xlsx - if you need charts, merged cells, or conditional formatting, you still want a workbook library. For plain data exports of any size, this is the simpler and safer option.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,107)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/14_table_format",documentation:[{text:"Table Format Configuration",href:"/config/table-format"},{text:"Statistics Configuration",href:"/config/stats"},{text:"Annotations Reference",href:"/annotations/"}]})])}const F=a(h,[["render",r]]);export{u as __pageData,F as default}; diff --git a/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.lean.js b/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.lean.js new file mode 100644 index 000000000..25cf669bc --- /dev/null +++ b/assets/blog_excel-export-table-format-postgresql-npgsqlrest.md.BuWwG6CQ.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","titleTemplate":"NpgsqlRest","description":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. Zero allocations, native type mapping, and one SQL annotation. No more server-crashing export code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","titleTemplate":"NpgsqlRest","description":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. Zero allocations, native type mapping, and one SQL annotation. No more server-crashing export code.","head":[["meta",{"name":"keywords","content":"postgresql excel export, xlsx streaming export, spreadcheetah postgresql, npgsqlrest excel, zero allocation excel, postgresql report download, table format rendering, postgresql stats endpoints"}],["meta",{"property":"og:title","content":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"}],["meta",{"property":"og:description","content":"Stream .xlsx Excel exports directly from PostgreSQL with constant memory usage. No more server-crashing export code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"}],["meta",{"name":"twitter:description","content":"Stream .xlsx Excel exports from PostgreSQL with constant memory. Zero allocations, native types, one annotation."}]]},"headers":[],"relativePath":"blog/excel-export-table-format-postgresql-npgsqlrest.md","filePath":"blog/excel-export-table-format-postgresql-npgsqlrest.md"}'),h={name:"blog/excel-export-table-format-postgresql-npgsqlrest.md"};function r(k,s,o,d,c,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l("",107)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/14_table_format",documentation:[{text:"Table Format Configuration",href:"/config/table-format"},{text:"Statistics Configuration",href:"/config/stats"},{text:"Annotations Reference",href:"/annotations/"}]})])}const F=a(h,[["render",r]]);export{u as __pageData,F as default}; diff --git a/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.js b/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.js new file mode 100644 index 000000000..35051836e --- /dev/null +++ b/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.js @@ -0,0 +1,377 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Call external REST APIs directly from PostgreSQL functions using .http file syntax in type comments. No HTTP extensions, no middleware, just SQL.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Call external REST APIs directly from PostgreSQL functions using .http file syntax in type comments. No HTTP extensions, no middleware, just SQL.","head":[["meta",{"name":"keywords","content":"postgresql call external api, postgresql http request, sql call rest api, npgsqlrest http types, postgresql webhook, postgresql api integration, database api calls"}],["meta",{"property":"og:title","content":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"}],["meta",{"property":"og:description","content":"Call external REST APIs directly from PostgreSQL functions. No HTTP extensions, no middleware, just SQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Call External APIs from PostgreSQL: HTTP Types"}],["meta",{"name":"twitter:description","content":"Call external REST APIs directly from PostgreSQL. No extensions, no middleware, just SQL."}]]},"headers":[],"relativePath":"blog/external-api-calls-postgresql-http-types.md","filePath":"blog/external-api-calls-postgresql-http-types.md"}'),h={name:"blog/external-api-calls-postgresql-http-types.md"};function k(r,s,d,c,g,y){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e(`

Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest

HTTP · External APIs · Data Aggregation · January 2026


Calling an external REST API from a PostgreSQL function takes one type comment - no HTTP client libraries, no middleware services, no API gateway configuration, no PostgreSQL HTTP extensions to install. Just SQL.

NpgsqlRest's HTTP Types feature lets you define external API calls using the familiar .http file syntax right in your database. The HTTP request definition lives in a type comment, with function parameters automatically substituted into URLs, headers, and request bodies.

This tutorial builds a Financial Dashboard that aggregates data from two public APIs - currency exchange rates and cryptocurrency prices - combining them into a single response. All with about 50 lines of SQL.

Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/9_http_calls

The Problem: Backend-for-Frontend API Aggregation

A financial dashboard typically combines several external feeds before anything reaches the user:

The traditional approach requires:

  1. HTTP Client Library - Axios, fetch, HttpClient, etc.
  2. API Service Layer - Classes to manage each external API
  3. Error Handling - Retry logic, timeout handling, circuit breakers
  4. Response Transformation - Map external responses to internal DTOs
  5. Caching Layer - Reduce API calls and improve performance
  6. API Gateway - Route and aggregate external calls

This creates a substantial codebase just to proxy external data.

Why Not Use PostgreSQL HTTP Extensions?

PostgreSQL has extensions like http and pgsql-http that allow making HTTP requests directly from SQL. They work, but the drawbacks add up:

Installation and Distribution Overhead

HTTP extensions must be:

With managed database services (AWS RDS, Azure Database, Google Cloud SQL), you're often limited to a predefined list of extensions - and HTTP extensions may not be available.

Network and Performance Issues

Making HTTP calls directly from the database has architectural problems:

The NpgsqlRest Advantage

With NpgsqlRest HTTP Types, the HTTP calls are made from the NpgsqlRest server, not from PostgreSQL:

code
Client → NpgsqlRest (makes HTTP calls) → PostgreSQL (receives populated data)

This architecture provides:

The NpgsqlRest Solution: HTTP Types

HTTP Types turn PostgreSQL composite types into HTTP request definitions. When a function parameter uses an HTTP Type, NpgsqlRest automatically:

  1. Parses the HTTP definition from the type comment
  2. Substitutes placeholders with function parameter values
  3. Executes the HTTP request before calling the function
  4. Populates the type fields with the response (body, status, headers)
  5. Executes the PostgreSQL function with the populated parameter

The result: external API calls declared in SQL, executed automatically by NpgsqlRest.

The HTTP Type Syntax: Just Like .http Files

The HTTP definition comment uses the same syntax as .http files (RFC 7230 format):

code
METHOD URL [HTTP/version]
+Header-Name: Header-Value
+...
+[timeout directive]
+
+[request body]

With one addition that does the heavy lifting: placeholders. Any {parameter_name} in the URL, headers, or body is replaced with the corresponding function parameter value.

sql
sql
comment on type my_api is 'GET https://api.example.com/users/{_user_id}
+Authorization: Bearer {_token}
+Accept: application/json
+@timeout 10s';

When called with _user_id = 123 and _token = 'abc', NpgsqlRest makes:

code
GET https://api.example.com/users/123
+Authorization: Bearer abc
+Accept: application/json

Building the Financial Dashboard

The dashboard fetches real data from two free, public APIs:

  1. Exchange Rate API (open.er-api.com) - Fiat currency rates
  2. CoinGecko API (api.coingecko.com) - Cryptocurrency prices

Step 1: Define HTTP Types

First, create composite types to receive the API responses:

sql
sql
-- HTTP Type for Exchange Rate API
+create type example_9.exchange_rate_api as (
+    body jsonb,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type example_9.exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
+Accept: application/json
+@timeout 10s';

The comment defines:

sql
sql
-- HTTP Type for CoinGecko API
+create type example_9.crypto_price_api as (
+    body jsonb,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type example_9.crypto_price_api is 'GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
+Accept: application/json
+@timeout 10s';

This API requires query parameters for cryptocurrency IDs and target currencies.

Step 2: Define the Return Type

Create a strongly-typed return structure:

sql
sql
create type example_9.financial_dashboard_result as (
+    -- Fiat exchange rates
+    fiat_base_currency text,
+    fiat_rates jsonb,
+    fiat_last_updated text,
+    fiat_success boolean,
+    fiat_error text,
+    -- Cryptocurrency prices
+    crypto_prices jsonb,
+    crypto_success boolean,
+    crypto_error text
+);

This return type becomes a TypeScript interface in the generated client, so the type contract extends all the way to the frontend.

Step 3: Create the Aggregation Function

sql
sql
create function example_9.get_financial_dashboard(
+    _base_currency text,
+    _target_currencies_csv text,
+    _crypto_ids_csv text,
+    _vs_currencies_csv text,
+    _exchange_rate_response example_9.exchange_rate_api,
+    _crypto_response example_9.crypto_price_api
+)
+returns example_9.financial_dashboard_result
+language plpgsql
+as $$
+declare
+    _result example_9.financial_dashboard_result;
+    _filtered_rates jsonb = '{}'::jsonb;
+    _rate_data jsonb;
+    _currency text;
+    _target_arr text[];
+begin
+    -- Process exchange rate response
+    if (_exchange_rate_response).success then
+        _rate_data = (_exchange_rate_response).body;
+        _target_arr = string_to_array(_target_currencies_csv, ',');
+
+        -- Filter only requested target currencies
+        foreach _currency in array _target_arr loop
+            _currency = upper(trim(_currency));
+            if _rate_data->'rates' ? _currency then
+                _filtered_rates = _filtered_rates ||
+                    jsonb_build_object(_currency, _rate_data->'rates'->_currency);
+            end if;
+        end loop;
+
+        _result.fiat_base_currency = upper(_base_currency);
+        _result.fiat_rates = _filtered_rates;
+        _result.fiat_last_updated = _rate_data->>'time_last_update_utc';
+        _result.fiat_success = true;
+    else
+        _result.fiat_base_currency = upper(_base_currency);
+        _result.fiat_success = false;
+        _result.fiat_error = coalesce(
+            (_exchange_rate_response).error_message,
+            'Failed to fetch exchange rates (status: ' || (_exchange_rate_response).status_code || ')'
+        );
+    end if;
+
+    -- Process crypto price response
+    if (_crypto_response).success then
+        _result.crypto_prices = (_crypto_response).body;
+        _result.crypto_success = true;
+    else
+        _result.crypto_success = false;
+        _result.crypto_error = coalesce(
+            (_crypto_response).error_message,
+            'Failed to fetch crypto prices (status: ' || (_crypto_response).status_code || ')'
+        );
+    end if;
+
+    return _result;
+end;
+$$;
+
+comment on function example_9.get_financial_dashboard is '
+HTTP GET /financial-dashboard
+@authorize';

That's it. The entire backend for fetching, aggregating, and returning data from two external APIs is ~80 lines of SQL.

Step 4: Configuration

Enable HTTP Types in your configuration:

json
json
{
+  "NpgsqlRest": {
+    "HttpClientOptions": {
+      "Enabled": true
+    }
+  }
+}

What Happens at Runtime

When a client calls:

code
GET /financial-dashboard?baseCurrency=USD&targetCurrenciesCsv=EUR,GBP,JPY&cryptoIdsCsv=bitcoin,ethereum&vsCurrenciesCsv=usd,eur

NpgsqlRest:

  1. Parses function parameters from the query string
  2. Identifies HTTP Type parameters (_exchange_rate_response, _crypto_response)
  3. Substitutes placeholders in each HTTP Type's definition:
  4. Executes both HTTP requests (can be parallel)
  5. Populates the HTTP Type fields with responses
  6. Calls the PostgreSQL function with populated parameters
  7. Returns the function result as JSON

The response:

json
json
{
+  "fiatBaseCurrency": "USD",
+  "fiatRates": {
+    "EUR": 0.854542,
+    "GBP": 0.740946,
+    "JPY": 156.619455
+  },
+  "fiatLastUpdated": "Tue, 06 Jan 2026 00:02:31 +0000",
+  "fiatSuccess": true,
+  "fiatError": null,
+  "cryptoPrices": {
+    "bitcoin": { "usd": 93561, "eur": 79851 },
+    "ethereum": { "usd": 3226.93, "eur": 2754.07 }
+  },
+  "cryptoSuccess": true,
+  "cryptoError": null
+}

The Generated TypeScript Client

NpgsqlRest automatically generates a typed client:

typescript
typescript
interface IGetFinancialDashboardRequest {
+    baseCurrency: string | null;
+    targetCurrenciesCsv: string | null;
+    cryptoIdsCsv: string | null;
+    vsCurrenciesCsv: string | null;
+}
+
+interface IGetFinancialDashboardResponse {
+    fiatBaseCurrency: string | null;
+    fiatRates: any; // JSON
+    fiatLastUpdated: string | null;
+    fiatSuccess: boolean | null;
+    fiatError: string | null;
+    cryptoPrices: any; // JSON
+    cryptoSuccess: boolean | null;
+    cryptoError: string | null;
+}
+
+export async function getFinancialDashboard(
+    request: IGetFinancialDashboardRequest
+): Promise<{
+    status: number,
+    response: IGetFinancialDashboardResponse,
+    error: {...} | undefined
+}> {
+    // ... auto-generated fetch implementation
+}

The frontend code is straightforward:

typescript
typescript
const response = await getFinancialDashboard({
+    baseCurrency: "USD",
+    targetCurrenciesCsv: "EUR,GBP,JPY,CHF",
+    cryptoIdsCsv: "bitcoin,ethereum",
+    vsCurrenciesCsv: "usd,eur"
+});
+
+if (response.response.fiatSuccess) {
+    // Display exchange rates
+    for (const [currency, rate] of Object.entries(response.response.fiatRates)) {
+        console.log(\`1 USD = \${rate} \${currency}\`);
+    }
+}
+
+if (response.response.cryptoSuccess) {
+    // Display crypto prices
+    for (const [crypto, prices] of Object.entries(response.response.cryptoPrices)) {
+        console.log(\`\${crypto}: $\${prices.usd}\`);
+    }
+}

Traditional Approach: What It Would Take

The equivalent Node.js/Express implementation:

Traditional Backend (Node.js)

javascript
javascript
// services/exchangeRateService.js
+const axios = require('axios');
+
+class ExchangeRateService {
+    constructor() {
+        this.baseUrl = 'https://open.er-api.com/v6/latest';
+        this.timeout = 10000;
+    }
+
+    async getRates(baseCurrency) {
+        try {
+            const response = await axios.get(\`\${this.baseUrl}/\${baseCurrency}\`, {
+                timeout: this.timeout,
+                headers: { 'Accept': 'application/json' }
+            });
+            return {
+                success: true,
+                data: response.data,
+                statusCode: response.status
+            };
+        } catch (error) {
+            return {
+                success: false,
+                error: error.message,
+                statusCode: error.response?.status || 500
+            };
+        }
+    }
+}
+
+// services/cryptoPriceService.js
+class CryptoPriceService {
+    constructor() {
+        this.baseUrl = 'https://api.coingecko.com/api/v3/simple/price';
+        this.timeout = 10000;
+    }
+
+    async getPrices(cryptoIds, vsCurrencies) {
+        try {
+            const response = await axios.get(this.baseUrl, {
+                params: {
+                    ids: cryptoIds.join(','),
+                    vs_currencies: vsCurrencies.join(',')
+                },
+                timeout: this.timeout,
+                headers: { 'Accept': 'application/json' }
+            });
+            return {
+                success: true,
+                data: response.data,
+                statusCode: response.status
+            };
+        } catch (error) {
+            return {
+                success: false,
+                error: error.message,
+                statusCode: error.response?.status || 500
+            };
+        }
+    }
+}
+
+// controllers/dashboardController.js
+const { body, query, validationResult } = require('express-validator');
+
+const validateDashboardRequest = [
+    query('baseCurrency')
+        .isLength({ min: 3, max: 3 })
+        .withMessage('baseCurrency must be a 3-letter code'),
+    query('targetCurrencies')
+        .notEmpty()
+        .withMessage('targetCurrencies is required'),
+    query('cryptoIds')
+        .notEmpty()
+        .withMessage('cryptoIds is required'),
+    query('vsCurrencies')
+        .notEmpty()
+        .withMessage('vsCurrencies is required')
+];
+
+async function getFinancialDashboard(req, res) {
+    const errors = validationResult(req);
+    if (!errors.isEmpty()) {
+        return res.status(400).json({ errors: errors.array() });
+    }
+
+    const { baseCurrency, targetCurrencies, cryptoIds, vsCurrencies } = req.query;
+
+    const exchangeService = new ExchangeRateService();
+    const cryptoService = new CryptoPriceService();
+
+    // Fetch both APIs in parallel
+    const [exchangeResult, cryptoResult] = await Promise.all([
+        exchangeService.getRates(baseCurrency),
+        cryptoService.getPrices(
+            cryptoIds.split(','),
+            vsCurrencies.split(',')
+        )
+    ]);
+
+    // Filter exchange rates to requested currencies
+    let filteredRates = {};
+    if (exchangeResult.success) {
+        const targetArray = targetCurrencies.split(',').map(c => c.trim().toUpperCase());
+        for (const currency of targetArray) {
+            if (exchangeResult.data.rates[currency]) {
+                filteredRates[currency] = exchangeResult.data.rates[currency];
+            }
+        }
+    }
+
+    // Build response
+    const response = {
+        fiatBaseCurrency: baseCurrency.toUpperCase(),
+        fiatRates: exchangeResult.success ? filteredRates : null,
+        fiatLastUpdated: exchangeResult.success ? exchangeResult.data.time_last_update_utc : null,
+        fiatSuccess: exchangeResult.success,
+        fiatError: exchangeResult.success ? null : exchangeResult.error,
+        cryptoPrices: cryptoResult.success ? cryptoResult.data : null,
+        cryptoSuccess: cryptoResult.success,
+        cryptoError: cryptoResult.success ? null : cryptoResult.error
+    };
+
+    res.json(response);
+}
+
+// routes/dashboard.js
+const express = require('express');
+const router = express.Router();
+const { authenticateToken } = require('../middleware/auth');
+
+router.get(
+    '/financial-dashboard',
+    authenticateToken,
+    validateDashboardRequest,
+    getFinancialDashboard
+);
+
+module.exports = router;
+
+// types/dashboard.ts (if using TypeScript)
+interface FinancialDashboardRequest {
+    baseCurrency: string;
+    targetCurrencies: string;
+    cryptoIds: string;
+    vsCurrencies: string;
+}
+
+interface FinancialDashboardResponse {
+    fiatBaseCurrency: string;
+    fiatRates: Record<string, number> | null;
+    fiatLastUpdated: string | null;
+    fiatSuccess: boolean;
+    fiatError: string | null;
+    cryptoPrices: Record<string, Record<string, number>> | null;
+    cryptoSuccess: boolean;
+    cryptoError: string | null;
+}

Plus you need:

The Numbers

ComponentTraditional (Node.js)NpgsqlRest
Service classes2 files, ~60 lines each0
Controller1 file, ~80 lines0
Routes1 file, ~15 lines0 (annotation)
Type definitions1 file, ~20 linesGenerated automatically
HTTP clientaxios + configurationBuilt-in
Total backend code~250 lines + dependencies~80 lines SQL
TypeScript clientManual or OpenAPI generationAuto-generated
Dependenciesaxios, express-validator, etc.None additional

Estimated savings: 70% less code, zero additional dependencies, auto-generated client.

Advanced Features

Multiple API Calls

A single function can use multiple HTTP Types for sequential or parallel API calls:

sql
sql
create function fetch_with_enrichment(
+    _user_id text,
+    _user_api user_api_type,        -- First API call
+    _preferences_api prefs_api_type  -- Second API call
+)
+returns json
+language plpgsql
+as $$
+begin
+    -- Both APIs are called before this function executes
+    -- Results are available in _user_api and _preferences_api
+    return json_build_object(
+        'user', (_user_api).body::json,
+        'preferences', (_preferences_api).body::json
+    );
+end;
+$$;

POST Requests with Bodies

HTTP Types support all methods including POST with request bodies:

sql
sql
comment on type webhook_api is 'POST https://hooks.example.com/notify
+Content-Type: application/json
+Authorization: Bearer {_webhook_token}
+@timeout 5s
+
+{"event": "{_event_type}", "data": {_payload}}';

Response Field Customization

Configure field names in your composite type:

Field NameTypeDescription
bodytext or jsonbResponse body content
status_codeintHTTP status code
headersjsonResponse headers
content_typetextContent-Type header
successbooleanTrue for 2xx status codes
error_messagetextError description if failed

Using jsonb for the body field (as we did) avoids explicit casting in your function.

Retry Logic

External APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. The @retry_delay directive adds automatic retries:

sql
sql
-- Retry 3 times with increasing delays, only on 429 and 503:
+comment on type exchange_rate_api is '@retry_delay 1s, 2s, 5s on 429, 503
+GET https://open.er-api.com/v6/latest/{_base_currency}
+Accept: application/json
+@timeout 10s';

The delay list defines both the number of retries and the delay before each. Without the on filter, retries happen on any failure. See HTTP Client Options for full details.

Resolved Parameter Expressions

Sensitive values like API tokens can be resolved server-side via SQL, keeping secrets out of client requests entirely:

sql
sql
comment on type paid_api is 'GET https://api.example.com/premium/{_query}
+Authorization: Bearer {_token}
+@timeout 10s';
+
+create function search_premium(
+    _query text,
+    _user_id int,
+    _req paid_api,
+    _token text default null
+) returns json ...
+
+comment on function search_premium(text, int, paid_api, text) is '
+_token = select api_token from user_tokens where user_id = {_user_id}
+';

The client calls GET /api/search-premium/?query=test&user_id=42. The server resolves _token from the database and substitutes it into the Authorization header — the token never leaves the server. See HTTP Client Options for full details.

Timeout Configuration

Multiple timeout formats are supported:

sql
sql
-- Seconds (integer)
+comment on type api is '@timeout 30
+GET https://api.example.com/data';
+
+-- With suffix
+comment on type api is 'GET https://api.example.com/data
+@timeout 30s';
+
+-- TimeSpan format
+comment on type api is 'GET https://api.example.com/data
+@timeout 00:00:30';

When to Use HTTP Types

HTTP Types are ideal for:

Consider alternatives for:

Conclusion

HTTP Types reduce external API integration to a type comment: no client libraries, no PostgreSQL extensions, no service classes, no manual type definitions. The Financial Dashboard aggregates two external APIs in about 80 lines of SQL where the traditional approach needs 250+ lines across multiple files, plus dependencies, plus manual TypeScript types. They're the wrong tool for high-frequency polling or streaming - use background services or SSE for those - but for request-scoped API aggregation, the code you don't write has no bugs.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,109)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/9_http_calls",documentation:[{text:"HTTP Type Annotation",href:"/annotations/http-type"},{text:"HTTP Client Options",href:"/config/http-client"}],"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration",href:"/guide/configuration"}]})])}const C=a(h,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.lean.js b/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.lean.js new file mode 100644 index 000000000..76b7dbcc5 --- /dev/null +++ b/assets/blog_external-api-calls-postgresql-http-types.md.C9zYiqiX.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Call external REST APIs directly from PostgreSQL functions using .http file syntax in type comments. No HTTP extensions, no middleware, just SQL.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Call external REST APIs directly from PostgreSQL functions using .http file syntax in type comments. No HTTP extensions, no middleware, just SQL.","head":[["meta",{"name":"keywords","content":"postgresql call external api, postgresql http request, sql call rest api, npgsqlrest http types, postgresql webhook, postgresql api integration, database api calls"}],["meta",{"property":"og:title","content":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"}],["meta",{"property":"og:description","content":"Call external REST APIs directly from PostgreSQL functions. No HTTP extensions, no middleware, just SQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Call External APIs from PostgreSQL: HTTP Types"}],["meta",{"name":"twitter:description","content":"Call external REST APIs directly from PostgreSQL. No extensions, no middleware, just SQL."}]]},"headers":[],"relativePath":"blog/external-api-calls-postgresql-http-types.md","filePath":"blog/external-api-calls-postgresql-http-types.md"}'),h={name:"blog/external-api-calls-postgresql-http-types.md"};function k(r,s,d,c,g,y){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e("",109)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/9_http_calls",documentation:[{text:"HTTP Type Annotation",href:"/annotations/http-type"},{text:"HTTP Client Options",href:"/config/http-client"}],"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration",href:"/guide/configuration"}]})])}const C=a(h,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_index.md.CTEJ27sE.js b/assets/blog_index.md.CTEJ27sE.js new file mode 100644 index 000000000..1b5d69bad --- /dev/null +++ b/assets/blog_index.md.CTEJ27sE.js @@ -0,0 +1 @@ +import{_ as t,c as s,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"Blog","description":"","frontmatter":{"title":"Blog"},"headers":[],"relativePath":"blog/index.md","filePath":"blog/index.md"}'),n={name:"blog/index.md"};function r(i,e,l,p,g,c){return a(),s("div",null,e[0]||(e[0]=[o('

Blog Posts & Tutorials

',3)]))}const u=t(n,[["render",r]]);export{h as __pageData,u as default}; diff --git a/assets/blog_index.md.CTEJ27sE.lean.js b/assets/blog_index.md.CTEJ27sE.lean.js new file mode 100644 index 000000000..ec885ad08 --- /dev/null +++ b/assets/blog_index.md.CTEJ27sE.lean.js @@ -0,0 +1 @@ +import{_ as t,c as s,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"Blog","description":"","frontmatter":{"title":"Blog"},"headers":[],"relativePath":"blog/index.md","filePath":"blog/index.md"}'),n={name:"blog/index.md"};function r(i,e,l,p,g,c){return a(),s("div",null,e[0]||(e[0]=[o("",3)]))}const u=t(n,[["render",r]]);export{h as __pageData,u as default}; diff --git a/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.js b/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.js new file mode 100644 index 000000000..c444568a7 --- /dev/null +++ b/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.js @@ -0,0 +1,43 @@ +import{_ as e,c as a,o as t,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Turn PostgreSQL into MCP Tools an AI Agent Can Call","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a PostgreSQL function or .sql file with @mcp and it becomes a tool an AI agent can discover and call — one source, two interfaces (REST + MCP), no glue code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Turn PostgreSQL into MCP Tools an AI Agent Can Call","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a PostgreSQL function or .sql file with @mcp and it becomes a tool an AI agent can discover and call — one source, two interfaces (REST + MCP), no glue code.","head":[["meta",{"name":"keywords","content":"mcp server postgresql, model context protocol postgresql, ai agent tools postgresql, npgsqlrest mcp, expose sql as mcp tools, postgresql ai tools, mcp tools from sql, claude tools postgresql, structured tool output mcp"}],["meta",{"property":"og:title","content":"Turn PostgreSQL into MCP Tools an AI Agent Can Call"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a function or .sql file with @mcp and it becomes a tool an AI agent can discover and call."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Turn PostgreSQL into MCP Tools an AI Agent Can Call"}],["meta",{"name":"twitter:description","content":"Annotate a PostgreSQL function or .sql file with @mcp and an AI agent can discover and call it. One source, two interfaces."}]]},"headers":[],"relativePath":"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md","filePath":"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md"}'),n={name:"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md"};function l(o,s,r,p,c,h){return t(),a("div",null,s[0]||(s[0]=[i(`

Turn PostgreSQL into MCP Tools an AI Agent Can Call

MCP · AI Agents · Model Context Protocol · PostgreSQL · v3.17.0 · June 2026


You already write the operations your application needs as PostgreSQL functions or .sql files. NpgsqlRest turns them into a typed REST API for your frontend. As of 3.17.0, the same routines can also be MCP tools — so an AI agent can discover them and call them directly, with no separate tool layer to write and keep in sync.

One annotation does it:

sql
sql
/*
+HTTP GET
+@param $1 query text default null
+@param $2 maxPrice numeric default null
+@mcp Search the product catalog by name or category and/or a maximum price.
+*/
+select id, name, category, price, stock
+from products
+where ($1 is null or name ilike '%' || $1 || '%' or category ilike '%' || $1 || '%')
+  and ($2 is null or price <= $2)
+order by price;

HTTP GET makes it a REST endpoint; @mcp makes it a tool. The two are independent, so this file is one PostgreSQL routine exposed through two interfaces — a typed /api/search-products call for your UI, and a search_products tool for an agent — from a single source of truth.

This post walks through example 15, an "Acme Store" built entirely from .sql files, and ends with a real Claude agent driving the store autonomously.

What MCP is, in one paragraph

The Model Context Protocol (spec 2025-11-25) is how an AI agent talks to external capabilities. The agent asks a server "what can you do?" (tools/list), gets back a list of tools — each with a JSON-Schema describing its arguments (inputSchema) — and then calls one (tools/call) with arguments it fills in itself. It's a thin JSON-RPC contract. NpgsqlRest implements the server side over a single Streamable-HTTP endpoint (/mcp), and core stays protocol-agnostic — the whole MCP layer lives in a separate NpgsqlRest.Mcp plugin.

Opt-in, never automatic

A routine becomes a tool only when its comment carries @mcp. Nothing is exposed by accident:

AnnotationEffect
@mcpExpose as a tool; description comes from the comment prose
@mcp <text>Expose, with <text> as the description
@mcp_description <text>Explicit, authoritative description (suppresses comment prose)
@mcp_name <name>Override the tool name (default: the routine name)

The argument schema is derived from the routine's parameters@param $2 maxPrice numeric default null becomes an optional maxPrice: number in the tool's inputSchema, camel-cased, nullable-aware. The return columns become an outputSchema. You don't hand-write either.

Results are structured

tools/call runs the routine through the same pipeline as the HTTP endpoint and returns structuredContent — always a JSON object, shaped to the result:

Business failures (an over-order tripping a CHECK constraint) come back as isError: true in the result; structural problems (unknown tool, malformed request) are JSON-RPC errors. Agents can tell the difference.

MCP-only tools: a tool with no REST route

Because the HTTP tag and @mcp are independent, a bare @mcp with no HTTP tag is an MCP-only tool — it exists for agents but has no public REST route at all. The declaration can be this small:

sql
sql
/*
+@mcp
+
+Low-stock report (3 or fewer in stock); staff/agent-only.
+*/
+select name, category, stock from products where stock <= 3 order by stock;

The @mcp line opts the routine in; the plain prose beneath it becomes the tool's description — the text the model reads in tools/list to decide when to reach for this tool (the keyword is also case-insensitive and the leading @ is optional, so mcp or MCP work too). That's the whole declaration. tools/list includes inventory_report, but GET /api/inventory-report returns 404. Opting a routine into MCP never silently widens your HTTP surface, and the generated TypeScript client and OpenAPI document correctly leave MCP-only routines out.

One source, two interfaces — made visible

Example 15's web page drives the same .sql files two ways, side by side:

place_order.sql is a typed REST function on the left and an MCP tool on the right. One routine, two consumers, zero duplicated code.

The real test: an AI agent driving the store

A browser form is one thing; a model choosing tools on its own is the actual validation. The example ships a small, dependency-free agent (agent.ts) that hands the MCP tools to Claude and executes whatever it decides to call:

bash
bash
export ANTHROPIC_API_KEY=sk-ant-...
+bun run agent "find peripherals under \\$100, order one of the cheapest, then show me what's low on stock"
text
text
── connected to Acme Store v1.0.0 · 8 tools ──
+🧑 find peripherals under $100, order one of the cheapest, then show me what's low on stock
+
+🔧 search_products({"query":"Peripherals","maxPrice":100})
+   → {"items":[{"id":5,"name":"Ergonomic Mouse","category":"Peripherals","price":59.90,"stock":25}]}
+
+🔧 place_order({"productId":5,"quantity":1})
+   → {"orderId":1,"product":"Ergonomic Mouse","quantity":1,"total":59.90,"status":"placed"}
+
+🔧 inventory_report({})
+   → {"items":[{"name":"Wireless Charger Pad","stock":0},{"name":"1080p Webcam","stock":2}, …]}
+
+🤖 Done — I ordered 1 Ergonomic Mouse ($59.90, order #1). Low stock: the Wireless Charger Pad
+   is out of stock, and the 1080p Webcam (2) is running low.
+── done ──

The whole pipeline ran with a real model in the loop: your @mcp SQL files → inputSchema → Claude's tool selection → tools/call → PostgreSQL → result → Claude. The server's configured Instructions become the agent's system prompt, so you steer behavior from config, not code.

Authorization, without locking down the server

Tools run through the same pipeline as HTTP endpoints, so the routine's own @authorize check applies on tools/call — and you can gate a single privileged tool without forcing everyone else to authenticate. Example 15 makes only restock_product manager-only:

sql
sql
/*
+HTTP POST
+@param $1 productId int
+@param $2 newStock int
+@single
+@authorize manager
+@mcp Set a product's stock to an exact value. Manager-only — a privileged, mutating operation.
+*/
+update products set stock = $2 where id = $1
+returning id, name, category, stock;

With JWT enabled but RequiresAuthorization left off, the store stays anonymous to browse and the agent keeps working — only this one tool refuses:

…and identically over REST (POST /api/restock-product), because it's the same routine. The challenge is enforced exactly as the MCP authorization model prescribes: NpgsqlRest acts as an OAuth 2.1 Resource Server (it validates tokens and serves Protected Resource Metadata so a client can discover your Authorization Server) — bring your own IdP (Keycloak, Auth0, Entra…) or use NpgsqlRest's own JWT login, as the example does.

Why this approach holds up

Try it

The complete, runnable example — .sql tools, the dual-panel web page, the Claude agent, and the authorization demo — is example 15 on GitHub:

bash
bash
cd examples/15_mcp_server
+bun run db:up    # schema + seed data
+bun run build    # bundle the web client
+bun run dev      # start the server

Then point MCP Inspector (npx @modelcontextprotocol/inspector, transport Streamable HTTP, URL http://127.0.0.1:8080/mcp) or Claude Desktop at the endpoint — or just run the agent.

Further reading: the MCP configuration reference and the @mcp annotation docs.

`,44)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.lean.js b/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.lean.js new file mode 100644 index 000000000..887f8d0d4 --- /dev/null +++ b/assets/blog_mcp-server-postgresql-ai-tools-npgsqlrest.md.DvISemRM.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as t,a5 as i}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Turn PostgreSQL into MCP Tools an AI Agent Can Call","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a PostgreSQL function or .sql file with @mcp and it becomes a tool an AI agent can discover and call — one source, two interfaces (REST + MCP), no glue code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Turn PostgreSQL into MCP Tools an AI Agent Can Call","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a PostgreSQL function or .sql file with @mcp and it becomes a tool an AI agent can discover and call — one source, two interfaces (REST + MCP), no glue code.","head":[["meta",{"name":"keywords","content":"mcp server postgresql, model context protocol postgresql, ai agent tools postgresql, npgsqlrest mcp, expose sql as mcp tools, postgresql ai tools, mcp tools from sql, claude tools postgresql, structured tool output mcp"}],["meta",{"property":"og:title","content":"Turn PostgreSQL into MCP Tools an AI Agent Can Call"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.17.0 adds Model Context Protocol support. Annotate a function or .sql file with @mcp and it becomes a tool an AI agent can discover and call."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Turn PostgreSQL into MCP Tools an AI Agent Can Call"}],["meta",{"name":"twitter:description","content":"Annotate a PostgreSQL function or .sql file with @mcp and an AI agent can discover and call it. One source, two interfaces."}]]},"headers":[],"relativePath":"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md","filePath":"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md"}'),n={name:"blog/mcp-server-postgresql-ai-tools-npgsqlrest.md"};function l(o,s,r,p,c,h){return t(),a("div",null,s[0]||(s[0]=[i("",44)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.js b/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.js new file mode 100644 index 000000000..37d7d2f7c --- /dev/null +++ b/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.js @@ -0,0 +1,358 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Multiple Auth Schemes, RBAC, and External OAuth Providers","titleTemplate":"NpgsqlRest","description":"Implement multiple authentication schemes, role-based access control, Google/GitHub OAuth integration, and JWT tokens with PostgreSQL. Complete auth system guide.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Multiple Auth Schemes, RBAC, and External OAuth Providers","titleTemplate":"NpgsqlRest","description":"Implement multiple authentication schemes, role-based access control, Google/GitHub OAuth integration, and JWT tokens with PostgreSQL. Complete auth system guide.","head":[["meta",{"name":"keywords","content":"postgresql oauth, postgresql rbac, multiple auth schemes, google oauth postgresql, github oauth api, jwt postgresql, role based access control api, npgsqlrest authentication"}],["meta",{"property":"og:title","content":"Multiple Auth Schemes, RBAC, and External OAuth Providers"}],["meta",{"property":"og:description","content":"Implement multiple auth schemes, RBAC, and Google/GitHub OAuth with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Multiple Auth Schemes, RBAC & OAuth with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Multiple auth schemes, RBAC, Google/GitHub OAuth integration with PostgreSQL."}]]},"headers":[],"relativePath":"blog/multiple-auth-schemes-rbac-external-providers.md","filePath":"blog/multiple-auth-schemes-rbac-external-providers.md"}'),h={name:"blog/multiple-auth-schemes-rbac-external-providers.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e(`

Multiple Authentication Schemes, Role-Based Access Control, and External Providers

January 2026 · SecurityAuthenticationOAuthNpgsqlRest

In the previous post, we built a secure authentication system using PostgreSQL's pgcrypto extension for password hashing. That approach works well, but NpgsqlRest can go further: a built-in password verification system, multiple authentication schemes, role-based access control, and integration with external OAuth providers.

This post walks through an authentication example that combines four pieces:

  1. Built-in Password Hasher - NpgsqlRest's pluggable password verification with verification callbacks
  2. Multiple Authentication Schemes - Cookies, Bearer tokens, and JWT all working together
  3. Role-Based Access Control (RBAC) - Restricting endpoints to specific roles
  4. External OAuth Providers - Google login with zero password management

Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/4_passwords_tokens_roles

Why NpgsqlRest's Built-in Password Hasher?

The pgcrypto approach from the previous post hashes passwords in SQL, on the database server. The built-in hasher moves that work into NpgsqlRest, and the differences add up:

pgcrypto ApproachNpgsqlRest Built-in Hasher
Hashing logic in SQLHashing handled by NpgsqlRest
Requires pgcrypto extensionNo extension needed
Must return hash for comparisonReturns hash, NpgsqlRest verifies automatically
Custom verification requires manual implementationBuilt-in verification callbacks via config
bcrypt with 72-byte limit (needs workaround)PBKDF2-SHA256 with no length limit
bcrypt onlyPluggable - can use Argon2 or any .NET algorithm
Runs on database serverRuns on application server
Generate hashes with SQL functionsGenerate hashes with CLI or auto-hash parameters

Why process passwords on the application server? When your database and application servers are separate (as they should be in production), every CPU cycle on the database server counts. Password hashing is intentionally CPU-intensive - offloading it to the application server keeps your database responsive for queries.

Pluggable algorithms: The default is PBKDF2, but since NpgsqlRest runs on .NET, you can plug in any password hasher available in the .NET ecosystem - including Argon2, scrypt, or custom implementations. This requires a custom build but gives you flexibility as security standards evolve.

The default built-in hasher uses PBKDF2 (Password-Based Key Derivation Function 2) with:

Schema Design

The schema is straightforward - a users table with optional password hash:

sql
sql
-- V1__example_4_schema.sql
+
+create table example_4.users (
+    user_id int primary key generated always as identity,
+    username text not null,
+    email text not null,
+    roles text[] not null,
+    password_hash text null,  -- null when using external auth only
+    last_login timestamp with time zone null,
+    last_login_provider text null
+);

Note that password_hash is nullable - users authenticating only via external providers (like Google) don't need a password.

Generating Password Hashes

Use the NpgsqlRest CLI to generate hashes:

bash
bash
 npgsqlrest --hash password123
+RfpqB6nKcoT2lL/w4ItB24mvxg8R9rC906C0/+7DAI62PQayBWjqihU96XPzmzYu
+
+ npgsqlrest --hash password456
+X+e/OsZkNL4j/9a7WIy/2bkQDk4rHHwlFwLXx7MNpclUUPdtQlI1JiDqyqMnJbgu

Insert users with pre-generated hashes:

sql
sql
insert into example_4.users (username, email, roles, password_hash) values
+-- alice is a normal user
+('alice', 'alice@example.com', array['user'],
+    'RfpqB6nKcoT2lL/w4ItB24mvxg8R9rC906C0/+7DAI62PQayBWjqihU96XPzmzYu'),
+-- bob is an admin
+('bob', 'bob@example.com', array['user', 'admin'],
+    'X+e/OsZkNL4j/9a7WIy/2bkQDk4rHHwlFwLXx7MNpclUUPdtQlI1JiDqyqMnJbgu'),
+-- carol has no roles
+('carol', 'carol@example.com', array[]::text[],
+    '3XBVW23Yn6j8b8sRQMoerOvSYlFosXuRrY0G/nkquuquDNdSnbn8bacvCQlCQKhs');

Automatic Parameter Hashing for Registration

For user registration endpoints, NpgsqlRest can automatically hash password parameters before they reach your function. Configure PasswordParameterNameContains to specify which parameters should be hashed:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "PasswordParameterNameContains": "password"
+    }
+  }
+}

Any parameter containing "password" in its name will be automatically hashed using the same built-in hasher. Your registration function receives the hash directly - no hashing logic needed in SQL.

Multiple Authentication Schemes

NpgsqlRest supports multiple authentication schemes simultaneously. This example configures three:

json
json
{
+  "Auth": {
+    // Scheme 1: Cookie-based authentication
+    "CookieAuth": true,
+    "CookieAuthScheme": "cookies",
+    "CookieValidDays": 1,
+    "CookieName": "example_4_auth",
+
+    // Scheme 2: Microsoft Bearer Token
+    "BearerTokenAuth": true,
+    "BearerTokenAuthScheme": "token",
+    "BearerTokenExpireHours": 1,
+    "BearerTokenRefreshPath": "/api/token/refresh",
+
+    // Scheme 3: JWT
+    "JwtAuth": true,
+    "JwtAuthScheme": "jwt",
+    "JwtSecret": "your-secret-key-at-least-32-characters-long",
+    "JwtIssuer": "example_4",
+    "JwtAudience": "example_4",
+    "JwtExpireMinutes": 60,
+    "JwtRefreshExpireDays": 7,
+    "JwtRefreshPath": "/api/jwt/refresh"
+  }
+}

Each scheme has a unique name (cookies, token, jwt) that the login function returns to indicate which scheme to use.

A Note on Data Protection and Encryption

Both Cookies and Microsoft Bearer Tokens are encrypted by default using ASP.NET Core's Data Protection system. Unlike JWT (which is signed but readable), both cookies and Bearer tokens are fully encrypted - the client cannot inspect their contents.

The Microsoft Bearer Token is a proprietary format, while the cookie encryption uses standard Data Protection mechanisms. Both rely on the same key management infrastructure. For production deployments, you should store these keys in the database so they persist across application restarts:

json
json
{
+  "DataProtection": {
+    "Enabled": true,
+    "DefaultKeyLifetimeDays": 90,
+    "Storage": "Database",
+    "GetAllElementsCommand": "select example_4.get_data_protection_keys()",
+    "StoreElementCommand": "call example_4.store_data_protection_keys($1,$2)"
+  }
+}

With supporting SQL:

sql
sql
create table example_4.auth_data_protection_keys (
+    name text not null primary key,
+    data text not null
+);
+
+create function example_4.get_data_protection_keys()
+returns setof text
+language sql
+begin atomic;
+select data from example_4.auth_data_protection_keys;
+end;
+
+create procedure example_4.store_data_protection_keys(
+    _name text,
+    _data text
+)
+language sql
+begin atomic;
+insert into example_4.auth_data_protection_keys (name, data)
+values (_name, _data)
+on conflict (name) do update set data = excluded.data;
+end;

Without database storage, keys are stored in memory and lost on restart - invalidating all existing cookies and tokens, forcing users to log in again. With database storage, encrypted authentication keeps working across application restarts.

For full details on authentication configuration, see:

The Login Function with Built-in Password Verification

The login function returns the password_hash column - NpgsqlRest automatically verifies it:

sql
sql
-- R__example_4_login.sql
+
+create or replace function example_4.login(
+    _scheme text,
+    _username text,
+    _password text
+)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    roles text[],
+    email text,
+    password_hash text  -- NpgsqlRest verifies this automatically
+)
+language sql
+set search_path = pg_catalog, pg_temp
+begin atomic;
+select
+    _scheme,  -- Can be 'cookies', 'token', or 'jwt'
+    user_id,
+    username,
+    roles,
+    email,
+    password_hash
+from example_4.users
+where
+    username = _username;
+end;
+
+comment on function example_4.login(text, text, text) is '
+HTTP POST
+@login
+@anonymous';

Key points:

  1. The _scheme parameter lets clients choose which authentication method to use
  2. The function returns password_hash - NpgsqlRest verifies it against _password
  3. If verification fails, NpgsqlRest returns 404 Not Found (not 401, to avoid leaking whether users exist)

See the login annotation documentation for full details on the built-in password hasher.

Password Verification Callbacks

Configure callbacks for successful and failed password verification:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "HashColumnName": "password_hash",
+      "PasswordVerificationFailedCommand": "call example_4.password_verification_failed($1, $2, $3)",
+      "PasswordVerificationSucceededCommand": "call example_4.password_verification_succeeded($1, $2, $3)"
+    }
+  }
+}

The callbacks receive the scheme, user ID, and username:

sql
sql
-- R__example_4_password_verification_succeeded.sql
+
+create or replace procedure example_4.password_verification_succeeded(
+    _scheme text,
+    _user_id text,
+    _user_name text
+)
+language plpgsql
+set search_path = pg_catalog, pg_temp
+as
+$$
+begin
+    -- Update last login timestamp
+    update example_4.users
+    set
+        last_login = now(),
+        last_login_provider = _scheme
+    where user_id = _user_id::int;
+
+    raise notice 'Password verification succeeded for user % (ID: %) using scheme %',
+        _user_name, _user_id, _scheme;
+end;
+$$;
sql
sql
-- R__example_4_password_verification_failed.sql
+
+create or replace procedure example_4.password_verification_failed(
+    _scheme text,
+    _user_id text,
+    _user_name text
+)
+language plpgsql
+set search_path = pg_catalog, pg_temp
+as
+$$
+begin
+    -- Log failed attempt, increment counters, implement lockout, etc.
+    raise warning 'Password verification failed for user % (ID: %) using scheme %',
+        _user_name, _user_id, _scheme;
+end;
+$$;

These callbacks are the only way to know whether NpgsqlRest's built-in password verification succeeded or failed - use them for account lockout, audit logging, or failed-attempt counting.

Role-Based Access Control

RBAC is implemented through the authorize annotation with role names:

sql
sql
-- R__example_4_get_users.sql
+
+create or replace function example_4.get_users()
+returns table (
+    users example_4.users,
+    is_this_me boolean
+)
+set search_path = pg_catalog, pg_temp
+language sql
+begin atomic;
+select
+    (u.*)::example_4.users,
+    (u.user_id = nullif(pg_catalog.current_setting('request.user_id', true), '')::int) is true
+from example_4.users u;
+end;
+
+comment on function example_4.get_users() is '
+HTTP GET
+@authorize admin';  -- Only admin role can access

The authorize admin annotation restricts this endpoint to users with the admin role. Users without this role receive 403 Forbidden.

Compare these authorization levels:

User Context with current_setting

Instead of user parameters, this example uses PostgreSQL's current_setting to access user claims:

sql
sql
create function example_4.who_am_i()
+returns example_4.who_am_i_response
+set search_path = pg_catalog, pg_temp
+language sql
+begin atomic;
+select
+    nullif(pg_catalog.current_setting('request.user_id', true), '')::int as user_id,
+    nullif(pg_catalog.current_setting('request.username', true), '') as username,
+    nullif(pg_catalog.current_setting('request.email', true), '') as email,
+    nullif(pg_catalog.current_setting('request.roles', true), '')::text[] as roles,
+    last_login,
+    last_login_provider
+from example_4.users
+where user_id = nullif(pg_catalog.current_setting('request.user_id', true), '')::int;
+end;

Configuration maps claims to settings:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "UseUserContext": true,
+      "ContextKeyClaimsMapping": {
+        "request.user_id": "user_id",
+        "request.username": "username",
+        "request.email": "email",
+        "request.roles": "roles"
+      }
+    }
+  }
+}

See User Context Settings for details on this approach vs. user parameters.

External OAuth Providers

Who needs passwords at all? An external OAuth provider takes a dozen lines of config:

json
json
{
+  "Auth": {
+    "External": {
+      "Enabled": true,
+      "SigninUrl": "/signin-{0}",
+      "LoginCommand": "select * from example_4.external_login($1,$2,$3,$4,$5)",
+      "Google": {
+        "Enabled": true,
+        "ClientId": "{GOOGLE_CLIENT_ID}",
+        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
+      }
+    }
+  }
+}

That's it. Users can now visit /signin-google to authenticate via Google.

The External Login Function

When OAuth completes, NpgsqlRest calls your login command with the provider info:

sql
sql
-- R__example_4_external_login.sql
+
+create or replace function example_4.external_login(
+    _provider text,      -- e.g., "google"
+    _email text,         -- User's email from provider
+    _name text,          -- User's display name
+    _provider_data json, -- Raw data from OAuth provider
+    _analytics_data json -- Browser analytics (screen size, timezone, etc.)
+)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    roles text[],
+    email text
+)
+language plpgsql
+set search_path = public, pg_catalog
+as
+$$
+declare
+    _user_id int;
+begin
+    return query
+    select
+        'cookies' as scheme,  -- External logins use cookies by default
+        u.user_id,
+        u.username,
+        u.roles,
+        u.email
+    from example_4.users u
+    where u.username = _email;  -- Match by email
+
+    if not found then
+        raise warning 'Could not find user with email % for provider %',
+            _email, _provider;
+    else
+        _user_id = (
+            select u.user_id
+            from example_4.users u
+            where u.username = _email
+        );
+
+        update example_4.users
+        set
+            last_login = now(),
+            last_login_provider = _provider
+        where example_4.users.user_id = _user_id;
+    end if;
+end
+$$;

The function uses the same conventions as the regular login annotation - return a named record with scheme and claim columns.

For full details on external authentication, see the External OAuth Authentication documentation. NpgsqlRest supports Google, GitHub, LinkedIn, Microsoft, Facebook, and custom OAuth providers.

The Demo Application

The example includes a web interface demonstrating all authentication methods:

html
html
<div id="login-form">
+    <h2>Login</h2>
+    <input type="text" id="username" placeholder="Username" />
+    <input type="password" id="password" placeholder="Password" />
+
+    <div style="margin: 8px 0;">
+        <label><strong>Auth Scheme:</strong></label>
+        <label><input type="radio" name="scheme" value="cookies" checked /> Cookies</label>
+        <label><input type="radio" name="scheme" value="token" /> Bearer Token</label>
+        <label><input type="radio" name="scheme" value="jwt" /> JWT</label>
+    </div>
+
+    <button id="login-btn">Login</button>
+
+    <a href="/signin-google">Login with Google (Cookies)</a>
+</div>
+
+<div id="actions">
+    <button id="whoami-btn">Who Am I?</button>
+    <button id="getusers-btn">Get Users (Admin)</button>
+    <button id="logout-btn">Logout</button>
+</div>

Test users:

Try logging in as alice and clicking "Get Users (Admin)" - you'll get 403 Forbidden. Log in as bob and it works.

Configuration Summary

The complete configuration enables all features:

json
json
{
+  "ApplicationName": "4_passwords_tokens_roles",
+
+  "Auth": {
+    "CookieAuth": true,
+    "CookieAuthScheme": "cookies",
+    "CookieValidDays": 1,
+
+    "BearerTokenAuth": true,
+    "BearerTokenAuthScheme": "token",
+    "BearerTokenExpireHours": 1,
+    "BearerTokenRefreshPath": "/api/token/refresh",
+
+    "JwtAuth": true,
+    "JwtAuthScheme": "jwt",
+    "JwtSecret": "your-secret-key-at-least-32-characters-long",
+    "JwtExpireMinutes": 60,
+    "JwtRefreshPath": "/api/jwt/refresh",
+
+    "External": {
+      "Enabled": true,
+      "LoginCommand": "select * from example_4.external_login($1,$2,$3,$4,$5)",
+      "Google": {
+        "Enabled": true,
+        "ClientId": "{GOOGLE_CLIENT_ID}",
+        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
+      }
+    }
+  },
+
+  "NpgsqlRest": {
+    "IncludeSchemas": [ "example_4" ],
+    "RequiresAuthorization": true,
+
+    "AuthenticationOptions": {
+      "DefaultUserIdClaimType": "user_id",
+      "DefaultNameClaimType": "username",
+      "DefaultRoleClaimType": "roles",
+
+      "HashColumnName": "password_hash",
+      "PasswordVerificationFailedCommand": "call example_4.password_verification_failed($1, $2, $3)",
+      "PasswordVerificationSucceededCommand": "call example_4.password_verification_succeeded($1, $2, $3)",
+
+      "UseUserContext": true,
+      "ContextKeyClaimsMapping": {
+        "request.user_id": "user_id",
+        "request.username": "username",
+        "request.email": "email",
+        "request.roles": "roles"
+      }
+    },
+
+    "ClientCodeGen": {
+      "FilePath": "./4_passwords_tokens_roles/src/{0}Api.ts",
+      "IncludeParseRequestParam": true
+    }
+  }
+}

Generated Client with Token Support

The IncludeParseRequestParam: true option generates API functions that accept an optional parseRequest callback. This allows you to inject authorization headers for Bearer token or JWT authentication:

typescript
typescript
// Add Authorization header for token-based auth
+function parseRequest(request: RequestInit): RequestInit {
+    if (!authToken) return request;
+
+    const headers = new Headers(request.headers);
+    headers.set("Authorization", \`Bearer \${authToken}\`);
+    return { ...request, headers };
+}
+
+// Pass the callback to any API call
+const response = await whoAmI(parseRequest);
+const users = await getUsers(parseRequest);

For cookie authentication, no callback is needed - cookies are sent automatically by the browser. But for Bearer tokens or JWT, you need to add the Authorization header manually, and IncludeParseRequestParam makes this trivial.

Conclusion: Enterprise Auth Made Simple

Modern authentication and authorization is notoriously complex. A production-ready system typically requires:

Implementing all of this traditionally takes multiple libraries and deep security expertise - and getting it wrong means vulnerabilities.

With NpgsqlRest, all of this is configuration and a few SQL functions.

What this example actually required:

ComponentLines of Code
Schema + users table~15 lines SQL
Login function~15 lines SQL
Logout function~8 lines SQL
Who Am I function~15 lines SQL
Role-restricted endpoint~10 lines SQL
External login function~25 lines SQL
Verification callbacks~20 lines SQL
Configuration~50 lines JSON
Total~160 lines

That's it. Under 200 lines for a complete authentication system with:

Compare this to a typical Node.js/Express or Spring Boot implementation - you'd be looking at thousands of lines of code, multiple dependencies, and weeks of development time.

This Blog Post is Your Recipe

Use this example as a template:

  1. Copy the schema - Adapt the users table to your needs
  2. Copy the configuration - Enable the schemes you need, add your OAuth credentials
  3. Write your login function - Return the scheme and claims you want
  4. Add role annotations - authorize admin on endpoints that need it
  5. Done - You have production-ready authentication

The code you don't write has no bugs, and authentication is exactly where you want fewer of them.

Combined with database-level security (Principle of Least Privilege, SECURITY DEFINER, search path protection) and end-to-end type safety, you get a complete, secure, performant application stack that would take months to build from scratch.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,102)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/4_passwords_tokens_roles","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Authentication Config",href:"/config/auth"},{text:"External OAuth",href:"/config/external-auth"},{text:"Login Annotation",href:"/annotations/login"}]})])}const u=a(h,[["render",k]]);export{y as __pageData,u as default}; diff --git a/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.lean.js b/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.lean.js new file mode 100644 index 000000000..25670347d --- /dev/null +++ b/assets/blog_multiple-auth-schemes-rbac-external-providers.md.PAYr3KYB.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as t,a5 as e,G as p}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Multiple Auth Schemes, RBAC, and External OAuth Providers","titleTemplate":"NpgsqlRest","description":"Implement multiple authentication schemes, role-based access control, Google/GitHub OAuth integration, and JWT tokens with PostgreSQL. Complete auth system guide.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Multiple Auth Schemes, RBAC, and External OAuth Providers","titleTemplate":"NpgsqlRest","description":"Implement multiple authentication schemes, role-based access control, Google/GitHub OAuth integration, and JWT tokens with PostgreSQL. Complete auth system guide.","head":[["meta",{"name":"keywords","content":"postgresql oauth, postgresql rbac, multiple auth schemes, google oauth postgresql, github oauth api, jwt postgresql, role based access control api, npgsqlrest authentication"}],["meta",{"property":"og:title","content":"Multiple Auth Schemes, RBAC, and External OAuth Providers"}],["meta",{"property":"og:description","content":"Implement multiple auth schemes, RBAC, and Google/GitHub OAuth with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Multiple Auth Schemes, RBAC & OAuth with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Multiple auth schemes, RBAC, Google/GitHub OAuth integration with PostgreSQL."}]]},"headers":[],"relativePath":"blog/multiple-auth-schemes-rbac-external-providers.md","filePath":"blog/multiple-auth-schemes-rbac-external-providers.md"}'),h={name:"blog/multiple-auth-schemes-rbac-external-providers.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=e("",102)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/4_passwords_tokens_roles","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Authentication Config",href:"/config/auth"},{text:"External OAuth",href:"/config/external-auth"},{text:"Login Annotation",href:"/annotations/login"}]})])}const u=a(h,[["render",k]]);export{y as __pageData,u as default}; diff --git a/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.js b/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.js new file mode 100644 index 000000000..f24305111 --- /dev/null +++ b/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.js @@ -0,0 +1,74 @@ +import{_ as a,C as e,c as n,o as t,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.13.0 release notes with real-world examples: conditional caching with When rules, short-lived sensitive sessions, per-user rate limiting, and multi-tenant search_path with pgBouncer.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.13.0 release notes with real-world examples: conditional caching with When rules, short-lived sensitive sessions, per-user rate limiting, and multi-tenant search_path with pgBouncer.","head":[["meta",{"name":"keywords","content":"npgsqlrest 3.13, cache profiles, auth schemes, rate limiter partition, pgbouncer multi-tenant, before routine commands, wrap in transaction, postgresql connection pooler"}],["meta",{"property":"og:title","content":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.13.0 — conditional caching, short-lived sessions, per-user rate limiting, and pgBouncer transaction-mode compatibility."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest 3.13.0: Production Patterns"}],["meta",{"name":"twitter:description","content":"Cache profiles, auth schemes, partitioned rate limiting, and pgBouncer multi-tenant support in NpgsqlRest 3.13.0."}]]},"headers":[],"relativePath":"blog/npgsqlrest-3.13-production-patterns.md","filePath":"blog/npgsqlrest-3.13-production-patterns.md"}'),p={name:"blog/npgsqlrest-3.13-production-patterns.md"};function r(o,s,k,d,c,u){const i=e("BlogNav");return t(),n("div",null,[s[0]||(s[0]=l(`

NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility

April 2026 · v3.13.0CachingAuthRate LimitingMulti-Tenancy

NpgsqlRest 3.13.0 gives first-class declarative support to four production scenarios that previously required custom middleware, external services, or hacky workarounds:

  1. Caching that adapts to query inputs — historical data cached for hours, "open-ended" data cached briefly, real-time queries bypassing the cache entirely.
  2. Short-lived sensitive sessions alongside a normal long-lived session, e.g., for recovery-code or admin flows.
  3. Per-user rate limits instead of global buckets shared by all users.
  4. pgBouncer / RDS Proxy / Supabase Pooler compatibility with multi-tenant search_path driven by JWT claims.

All four patterns ship as configuration, not code. Walkthroughs below; full reference and migration notes in the v3.13.0 changelog.

1. Conditional Caching: Historical vs Current vs Live

A common analytics endpoint has parameters like from, to, and live. The right caching policy depends on which combination the client sends:

Before 3.13, this either meant three different endpoints or imperative cache logic in SQL. Now it's a single profile with When rules. Here's the full CacheOptions block backed by Redis (so cache state survives restarts and is shared across multiple NpgsqlRest instances behind a load balancer):

jsonc
jsonc
"CacheOptions": {
+  "Enabled": true,
+  "Type": "Redis",
+  "RedisConfiguration": "redis-server:6379,password={REDIS_PASSWORD},ssl=true,abortConnect=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
+  "MaxCacheableRows": 1000,
+  "UseHashedCacheKeys": true,
+  "HashKeyThreshold": 256,
+  "InvalidateCacheSuffix": "invalidate",
+  "Profiles": {
+    "timeseries_compute": {
+      "Enabled": true,
+      "Type": "Redis",
+      "Expiration": "1 hour",
+      "Parameters": ["from", "to", "live"],
+      "When": [
+        { "Parameter": "live", "Value": true,  "Then": "skip" },
+        { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
+      ]
+    }
+  }
+}

Notes on the top-level fields:

Profiles share the root backend pool — both root and timeseries_compute use a single Redis connection, not two. The profile-name prefix on cache keys keeps entries isolated even when multiple profiles share a backend.

sql
sql
comment on function compute_timeseries(
+  from text,
+  to text default null,
+  live boolean default false
+) is 'HTTP GET
+@cache_profile timeseries_compute';

How rules evaluate (first match wins):

RequestMatchesResult
?from=2025-01-01&to=2025-12-31noneprofile default → cached 1 hour
?from=2025-01-01 (to omitted)to=nullcached 5 minutes
?from=2025-01-01&live=truelive=truebypass cache entirely

The Then field accepts the literal "skip" (bypass cache) or any PostgreSQL interval string (override TTL). Because Parameters is ["from", "to", "live"], both rule-matched paths and the default-TTL path get separate cache entries — live=true requests never poison the historical cache.

See Cache Options → Cache Profiles for the full rule semantics, validation rules, and backend pooling notes.

2. Short-Lived Sensitive Session Alongside the Normal Session

A user is signed in with a normal 14-day cookie. Now they want to view recovery codes or change their password. You don't want the recovery-code page accessible for 14 days from any device that ever signed in — you want a fresh, short-lived authentication that expires when the browser closes.

Before 3.13, this meant either re-prompting for the password and just trusting it, or building a separate "step-up" auth service. Now you register an additional cookie scheme:

jsonc
jsonc
"Auth": {
+  "CookieAuth": true,
+  "CookieValid": "14 days",
+  "Schemes": {
+    "short_session": {
+      "Type": "Cookies",
+      "Enabled": true,
+      "CookieValid": "1 hour",
+      "CookieMultiSessions": false
+    }
+  }
+}

Any field not overridden inherits from the root Auth section, so the override block stays small.

The recovery-code step has its own login function that returns 'short_session' in the scheme column:

sql
sql
create function recovery_step_up(_user_id int, _recovery_code text)
+returns table (scheme text, name_identifier text, name text)
+language sql security definer as $$
+  select 'short_session' as scheme, user_id::text, username
+  from users
+  where user_id = _user_id
+    and verify_recovery_code(recovery_code_hash, _recovery_code);
+$$;

The endpoint that displays the recovery codes requires this specific scheme:

sql
sql
comment on function show_recovery_codes() is 'HTTP GET
+@authorize short_session';

CookieMultiSessions: false makes the cookie session-only (no Max-Age), so closing the browser invalidates it. The 1-hour CookieValid server-side cap means even a left-open tab stops working after an hour. The user's normal 14-day session is unaffected.

Same pattern works for admin areas, payment flows, or any per-scope restriction. JWT and BearerToken types support the same overrides — see the per-type override fields table.

3. Per-User Rate Limits

Pre-3.13, a rate limiter policy was a single global bucket. PermitLimit: 100, WindowSeconds: 60 meant 100 requests per minute across all users combined, which is rarely what you want — one heavy user could exhaust the quota for everyone else.

The new Partition block resolves a partition key per request and gives each key its own bucket:

jsonc
jsonc
"RateLimiterOptions": {
+  "Enabled": true,
+  "Policies": {
+    "per_user": {
+      "Type": "FixedWindow",
+      "Enabled": true,
+      "PermitLimit": 100,
+      "WindowSeconds": 60,
+      "Partition": {
+        "Sources": [
+          { "Type": "Claim", "Name": "name_identifier" },
+          { "Type": "IpAddress" },
+          { "Type": "Static", "Value": "anonymous" }
+        ]
+      }
+    }
+  }
+}
sql
sql
comment on function user_dashboard() is 'HTTP GET
+@authorize
+@rate_limiter per_user';

Sources are walked top-to-bottom; the first one that returns a non-empty value wins:

RequestResolved keyBucket
Authenticated user 42name_identifier=42per-user bucket
Anonymous request from 203.0.113.5IpAddress=203.0.113.5per-IP bucket
Anonymous, no IP visibleStatic=anonymousshared "anonymous" bucket

BypassAuthenticated: true is also available — useful for "throttle anonymous only" patterns where signed-in users skip the limiter entirely.

Behavior is unchanged for policies without a Partition block. See Per-User Rate Limiting for source types and validation rules.

Breaking change

RateLimiterOptions:Policies is now an object keyed by policy name, no longer an array of objects with "Name" properties. Migration is mechanical — see the changelog.

4. Multi-Tenant search_path with pgBouncer (or RDS Proxy, or Supabase Pooler)

A multi-tenant deployment where each tenant's data lives in its own schema, and the JWT carries a tenant_id claim. Each request needs search_path set so the routine sees the right tenant's tables.

Without a connection pooler, set_config('search_path', tenant, false) works fine — the GUC sticks for the session and Npgsql's pool issues DISCARD ALL when the connection returns. With a transaction-mode pooler (pgBouncer transaction-pool, AWS RDS Proxy in transaction mode, Supabase Pooler), the same backend is reused for unrelated requests without session reset, so a search_path set in one request can leak into the next.

3.13 introduces two cooperating options:

jsonc
jsonc
{
+  "NpgsqlRest": {
+    "WrapInTransaction": true,
+    "BeforeRoutineCommands": [
+      {
+        "Sql": "select set_config('search_path', $1, true)",
+        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
+      }
+    ]
+  }
+}

WrapInTransaction: true wraps every request in BEGIN ... COMMIT and switches all set_config calls to is_local=true (transaction-scoped). On COMMIT, the GUC is discarded — the next request on the same backend gets a clean slate.

BeforeRoutineCommands runs declared SQL after any context is set but before the main routine call, in the same NpgsqlBatch (no extra round-trip). Parameters are bound from HttpContext at request time — Source: "Claim" reads User.FindFirst("tenant_id"), and the value is passed as a SQL parameter (no string interpolation, no injection risk).

Per-request execution order:

  1. BEGIN
  2. set_config('search_path', $1, true) with $1 bound to the claim value
  3. The routine call
  4. COMMIT

Steps 1–3 share a single network round-trip. The routine sees the right search_path; nothing leaks between requests.

BeforeRoutineCommands also accepts raw strings (no parameters) and supports Source: "RequestHeader" and Source: "IpAddress" for non-claim cases. See Connection Pooler Compatibility for the full schema.

Other Notable Changes

For the complete release notes including NuGet upgrades and minor fixes, see the v3.13.0 changelog.

`,53)),h(i,{"get-started":[{text:"Changelog v3.13.0",href:"/guide/changelog/v3.13.0"},{text:"Cache Profiles Configuration",href:"/config/cache-options#cache-profiles"},{text:"Authentication Schemes",href:"/config/auth#additional-authentication-schemes"},{text:"Rate Limiter Partition",href:"/config/rate-limiter#per-user-rate-limiting-partition"},{text:"Connection Pooler Compatibility",href:"/config/npgsqlrest#connection-pooler-compatibility"}]})])}const F=a(p,[["render",r]]);export{y as __pageData,F as default}; diff --git a/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.lean.js b/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.lean.js new file mode 100644 index 000000000..014d9a9a2 --- /dev/null +++ b/assets/blog_npgsqlrest-3.13-production-patterns.md.jTlztVH-.lean.js @@ -0,0 +1 @@ +import{_ as a,C as e,c as n,o as t,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.13.0 release notes with real-world examples: conditional caching with When rules, short-lived sensitive sessions, per-user rate limiting, and multi-tenant search_path with pgBouncer.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.13.0 release notes with real-world examples: conditional caching with When rules, short-lived sensitive sessions, per-user rate limiting, and multi-tenant search_path with pgBouncer.","head":[["meta",{"name":"keywords","content":"npgsqlrest 3.13, cache profiles, auth schemes, rate limiter partition, pgbouncer multi-tenant, before routine commands, wrap in transaction, postgresql connection pooler"}],["meta",{"property":"og:title","content":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.13.0 — conditional caching, short-lived sessions, per-user rate limiting, and pgBouncer transaction-mode compatibility."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest 3.13.0: Production Patterns"}],["meta",{"name":"twitter:description","content":"Cache profiles, auth schemes, partitioned rate limiting, and pgBouncer multi-tenant support in NpgsqlRest 3.13.0."}]]},"headers":[],"relativePath":"blog/npgsqlrest-3.13-production-patterns.md","filePath":"blog/npgsqlrest-3.13-production-patterns.md"}'),p={name:"blog/npgsqlrest-3.13-production-patterns.md"};function r(o,s,k,d,c,u){const i=e("BlogNav");return t(),n("div",null,[s[0]||(s[0]=l("",53)),h(i,{"get-started":[{text:"Changelog v3.13.0",href:"/guide/changelog/v3.13.0"},{text:"Cache Profiles Configuration",href:"/config/cache-options#cache-profiles"},{text:"Authentication Schemes",href:"/config/auth#additional-authentication-schemes"},{text:"Rate Limiter Partition",href:"/config/rate-limiter#per-user-rate-limiting-partition"},{text:"Connection Pooler Compatibility",href:"/config/npgsqlrest#connection-pooler-compatibility"}]})])}const F=a(p,[["render",r]]);export{y as __pageData,F as default}; diff --git a/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.js b/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.js new file mode 100644 index 000000000..2e017c63a --- /dev/null +++ b/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.js @@ -0,0 +1,79 @@ +import{_ as e,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const n="/watch.gif",g=JSON.parse('{"title":"Tests Are SQL Files Too","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Tests Are SQL Files Too","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database.","badge":"none","head":[["meta",{"name":"keywords","content":"npgsqlrest postgresql sql testing test runner watch mode deferrable constraints database unit testing tdd"}],["meta",{"property":"og:title","content":"Tests Are SQL Files Too"}],["meta",{"property":"og:description","content":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Tests Are SQL Files Too"}],["meta",{"name":"twitter:description","content":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never impossible — you were just using the wrong database."}]]},"headers":[],"relativePath":"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md","filePath":"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md"}'),l={name:"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md"};function r(p,s,h,o,d,c){return i(),a("div",null,s[0]||(s[0]=[t(`

Tests Are SQL Files Too

July 2026 · NpgsqlRestPostgreSQLStoryTestingWatch Modev3.19.0

Introduction

Features in this last version 3.19.0 got me excited in a way I haven't been since 3.12.0 and I wanted to write about it personally. So let's do a quick recap:

Major shift is that I don't have to deploy anything or run a migration to add a new endpoint. Just write a simple SQL file, like a script and that is it, NpgsqlRest happily creates the endpoint for you from that file. Nothing deployed, nothing migrated. You probably already have a bunch of SQL scripts sitting around. You just need to add a comment with the HTTP method and path, and NpgsqlRest will serve it as an endpoint:

sql
sql
/*
+HTTP GET
+@param $1 from_date
+@param $2 to_date
+@authorize admin
+*/
+select id, title, created_at
+from reports
+where created_at between $1 and $2;

That is it. Simple as it gets.

1) Named Parameters in SQL Files

Example above can now be written like this:

sql
sql
/*
+HTTP GET
+@authorize admin
+*/
+select id, title, created_at
+from reports
+where created_at between :from_date and :to_date;

Small but sweet improvement.

2) Watch Mode

Initially developed for the test runner, it was expanded during the development for all modes, including SQL file endpoints as well as function/procedure endpoints.

It is particularly useful for SQL file endpoints, SQL files from the beginning included native parser validation against real database schema on startup which serves as a type checker for your SQL files. Now with watch mode, you can get that validation on every save, just as you type. This is big in terms of development experience.

3) Native Test Runner

And finally, the big one: native test runner. This implementation shares the same approach as SQL file endpoints, meaning you can write a test in a SQL file, and HTTP blocks in comments will be executed against the real endpoint, in-process, inside the test's own transaction. The fixture rolls back. Nothing is left behind. The full walkthrough is in the Testing Guide.

Let's go into more details.

TL;DR Test Runner

Quick example of a test file:

sql
sql
-- optional setup to ensure isolation
+begin;
+
+-- ARRANGE: insert a fixture user into the database
+
+insert into users (email) 
+values ('fixture@example.com');
+
+-- ACT: call the real endpoint, in-process, inside the test's own transaction and simulate an authenticated request with a claim user_id=1
+
+/*
+GET /api/get-users
+# @claim user_id=1
+*/
+
+-- ASSERT: assert on the response
+
+select status = 200, 'authenticated caller gets 200' 
+from _response;
+
+select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture is listed' 
+from _response;
+
+-- cleanup: rollback the transaction to clean up the fixture and keep isolation for the next test
+rollback;
console
console
$ npgsqlrest ./config.json --test
+
+PASS  tests/get_users.test.sql  (2 assertions, 52ms)
+19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
+endpoint coverage: 2/2 (100%)

That's the real endpoint — routing, authorization, parameter binding, JSON serialization — invoked in-process, inside the test's own transaction. The fixture rolls back. Nothing is left behind. The full walkthrough is in the Testing Guide.

And here is something worth saying up front: you don't need NpgsqlRest endpoints to use this. The HTTP blocks are optional. Boolean-SELECT assertions and DO-block asserts against your functions, views, and schema work on their own — so even if you never expose a single REST endpoint, you now have a fast, isolated, zero-framework unit test runner for plain PostgreSQL scripts. Point it at a directory of .sql tests and go.

Database Testing Is Impossible (They Said)

There is a widespread belief in this industry that testing against a real database is somewhere between painful and impossible. That belief is why we have in-memory database fakes that never work, endless mocks, and entire testing philosophies built around not touching the database — which is a strange way to test software whose primary job is to talk to a database.

And here is the thing: the belief is not entirely wrong. But it may be database-specific where some databases make it harder than others. PostgreSQL is one of the databases that makes it easy.

For example, testing against SQL Server without containers is hard. Even with containers, it is still hard.

No deferrable constraints — every foreign key is checked immediately, always, so a test fixture must satisfy the entire dependency graph before it can insert one interesting row. Creating a throwaway database per test run is a heavyweight operation. The standard answer was tSQLt — a framework you install into the database, with CLR dependencies and its own way of faking tables. People tried it, got burned, and concluded that database testing doesn't work.

Then they carried that conclusion over to PostgreSQL, where it was never true.

PostgreSQL has transactional DDL. It has deferrable constraints. It has cheap database creation and template databases. It has assert in every DO block. The primitives for elegant database testing have been sitting there for decades — they just needed a harness.

The Pattern I Have Used for Years

Long before 3.19, I tested my functions with a pattern so simple it barely deserves the name: the function and its assertions live in the same file, and the assertions run right below the definition.

sql
sql
-- recreate the entire function every time
+-- this is the first line of tests, sql functions are checked against schema on replace
+create or replace function get_posts()
+returns table (...)
+language sql as
+$$
+select ...
+$$;
+
+-- anonymous function (DO block) asserts against the function's output, and fails loudly if it doesn't match expectations
+do 
+$$
+begin
+    -- ARRANGE and ACT
+    create temp table _result on commit drop as
+    select * from get_posts();
+
+    -- ASSERT: assert on the result set
+    assert (select count(*) = 5 from _result where ...), 'get_posts() does not return expected data';
+    assert (select name from _result where id = ...) = 'expected_name', 'get_posts() returns wrong name for id ...';
+
+    -- do the optional cleanup if data was mutated
+    -- rollback;
+end 
+$$;

Open the file in the editor, hit execute, and the whole thing runs on one connection: drop, create, assert. If the assert fails, the script fails, loudly. Fix, execute again. RED — GREEN, right there in the editor.

And here is the part people don't believe until they try it: this loop is faster than traditional TDD. There is no build step. There is no test framework to boot, no runner to discover tests, no DI container to spin up. The unit of execution is one SQL script on one already-open connection — the round trip is measured in tens of milliseconds. Executing on a save-keystroke is faster than any dotnet test or pytest will ever warm up. I have shipped entire projects this way, and I will absolutely keep using this pattern — 3.19 doesn't replace it.

It is hard to explain how powerful this is until you try it.

But It Has Limits

The pattern has one requirement that is also its ceiling: you need a callable unit. A function or a procedure — something select-able that the DO block can assert against.

Which means:

Testing the function but not the endpoint is testing the engine but not the car.

Tests Are SQL Files Too

The fix, in hindsight, was obvious — it is the same move 3.12 made for endpoints. If SQL files can be endpoints, then SQL files can test endpoints.

And the syntax was already waiting: NpgsqlRest has embedded HTTP semantics in SQL comments from day one, so putting an HTTP request in a comment is the most natural thing in the world:

sql
sql
/*
+POST /api/login
+Content-Type: application/json
+
+{"email": "ada@example.com", "password": "correct horse battery staple"}
+*/

The runner invokes the real endpoint pipeline in-process — no server, no network — and captures the response into a temp table on the test's connection. And because this is PostgreSQL, with arguably the best JSON support of any relational database, asserting on that response is just... SQL:

sql
sql
select status = 200, 'login succeeds' from _response;
+select body::jsonb ->> 'email' = 'ada@example.com', 'the right user comes back' from _response;
+select body::jsonb -> 'roles' @> '["admin"]', 'role is present' from _response;

@>, ->>, jsonb_path_query — the assertion language for JSON responses was already built into the database. No fluent assertion library will ever compete with that.

The critical design decision — and the one that took the most engineering — is connection affinity: the endpoint call runs on the test's own connection, inside the test's own transaction. Insert a fixture, don't commit it, call the endpoint — the endpoint sees your uncommitted fixture. Assert, roll back, and it never existed. No .http file replay, no test HTTP client, no docker-compose test stack can reproduce that, because the state you tested against never existed outside your transaction.

The Old Demons: Isolation and Fixtures

Two problems have haunted database testing forever, and this is where PostgreSQL gets to show off.

Isolation. Every test file runs on its own non-pooled connection, in parallel — a fresh physical session, no temp tables or GUCs leaking between tests. Transactions handle the rest. And when transaction isolation isn't enough — sequences are non-transactional, nextval() survives rollback — you go one level up: Setup steps create a throwaway database (create database app_test_{rnd5} — a random token stable for the run), or even per-test clones from a template database, which PostgreSQL creates in well under a second. Perfect isolation is a config block, not a framework.

Fixtures. The reason fixtures are miserable everywhere else: to insert one row you need its foreign keys, and their foreign keys, and suddenly your test spends thirty lines building a company and a country and a currency to test one post. PostgreSQL's answer is deferrable constraints:

sql
sql
begin;
+set constraints all deferred;
+
+-- one post by a user that is never inserted — legal, because the FK check
+-- runs at COMMIT, and this transaction never commits
+insert into posts (id, user_id, content) values (1, 999, 'fixture post');

Deferred checks run at COMMIT. A test that ends in rollback never commits. Therefore the checks never run. Insert exactly the rows the test is about, in any order, referencing rows that don't exist — and let the endpoint's LEFT JOINs treat the missing references as what they are: nulls. (Try that on SQL Server. You can't — deferrable constraints don't exist there. This is what I mean by "the belief was database-specific.")

And because DDL is transactional too, a shared include (\\ir fixtures/relax_users.sql) can even drop NOT NULL from columns your test doesn't care about — and the rollback restores the schema. The Testing Guide covers both tricks.

And Then There Is Watch Mode

Here is where it stops being a testing feature and becomes a development environment.

console
console
$ npgsqlrest ./config.json --test --watch

Save a test — it re-runs alone, in milliseconds. Save an endpoint file — the endpoints rebuild in-process and everything re-runs, with a delta report: break a file and you see - GET /api/get-users (endpoint dropped — check its SQL file for errors) plus the failing tests; fix it and the next save brings it back. The RED–GREEN loop I described earlier, except now it covers the whole HTTP surface.

And without --test:

console
console
$ npgsqlrest ./config.json --watch

...it watches the running server. Every save re-parses and re-validates your SQL against the real schema — the native PostgreSQL parser checks every statement the moment you write it, which means your queries are validated against the live database as you type. A typo'd column name is an error on screen one second after you save, not a runtime surprise. Configuration files are watched. Even the database itself is watched — the discovery query is polled, so create or replace a function in psql and the endpoint is live about two seconds later, annotations included. The TypeScript client regenerates on every cycle, so the frontend's types follow your SQL as you type it.

Watch mode in action: a save triggers a restart, an error appears and disappears

This is the part I did not fully anticipate: watch mode is not a testing accessory. It changed how I write endpoints. The database schema became my type checker, running continuously.

Because nothing beats local development. Nothing.

AI TDD

And if you happen to enjoy AI-assisted development, this unlocks what has quietly become my favorite way of working: AI TDD.

You write a failing test assertion. Or — better — you instruct the AI agent to write it for you, and you just review it like a manager. Then you give your favorite AI coding agent one instruction: don't stop until all tests are green (or you run out of tokens, whichever comes first).

The reason this works so well here is everything this post has been about: the tests are fast (milliseconds, in-process, no build), isolated (transactions, throwaway databases — the agent cannot corrupt anything), and complete (the real endpoint, real auth, real JSON — not a mock the agent can game). The agent gets a tight, truthful feedback loop, and tight truthful feedback loops are the one thing agents actually need. Watch mode even runs the loop for it.

Write the assertion, state the goal, go get a coffee. That's my favorite method.

Where to Start


Looking back, 3.12 and 3.19 are the same idea, asked twice. 3.12 asked: what if the SQL file is the endpoint? 3.19 asks: what if the SQL file is the test? Both times the answer was already in PostgreSQL — transactional DDL, deferrable constraints, template databases, assert in every DO block. The primitives were there all along; the tooling just had to get out of the way.

That was the idea from day one: your SQL is the API. As of 3.19, it is the test suite too.

Happy testing.

',79)]))}const u=e(l,[["render",r]]);export{g as __pageData,u as default}; diff --git a/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.lean.js b/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.lean.js new file mode 100644 index 000000000..5bfea8cac --- /dev/null +++ b/assets/blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md.CeVn9yDN.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const n="/watch.gif",g=JSON.parse('{"title":"Tests Are SQL Files Too","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Tests Are SQL Files Too","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database.","badge":"none","head":[["meta",{"name":"keywords","content":"npgsqlrest postgresql sql testing test runner watch mode deferrable constraints database unit testing tdd"}],["meta",{"property":"og:title","content":"Tests Are SQL Files Too"}],["meta",{"property":"og:description","content":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never actually impossible — you were just using the wrong database."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Tests Are SQL Files Too"}],["meta",{"name":"twitter:description","content":"The story behind NpgsqlRest 3.19.0: the SQL test runner, watch mode, and why database testing was never impossible — you were just using the wrong database."}]]},"headers":[],"relativePath":"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md","filePath":"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md"}'),l={name:"blog/npgsqlrest-3.19-sql-test-runner-watch-mode.md"};function r(p,s,h,o,d,c){return i(),a("div",null,s[0]||(s[0]=[t("",79)]))}const u=e(l,[["render",r]]);export{g as __pageData,u as default}; diff --git a/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.js b/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.js new file mode 100644 index 000000000..8d88f960f --- /dev/null +++ b/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.js @@ -0,0 +1,163 @@ +import{_ as s,C as i,c as a,o as n,a5 as r,G as l}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","titleTemplate":"NpgsqlRest","description":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, authentication, file handling, and deployment options compared.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","titleTemplate":"NpgsqlRest","description":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, authentication, file handling, and deployment options compared.","head":[["meta",{"name":"keywords","content":"npgsqlrest vs postgrest, npgsqlrest vs supabase, postgresql rest api comparison, postgrest alternative, supabase alternative, postgresql api performance, rest api framework comparison"}],["meta",{"property":"og:title","content":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"}],["meta",{"property":"og:description","content":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, and deployment options compared."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"}],["meta",{"name":"twitter:description","content":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase for PostgreSQL REST APIs."}]]},"headers":[],"relativePath":"blog/npgsqlrest-vs-postgrest-supabase-comparison.md","filePath":"blog/npgsqlrest-vs-postgrest-supabase-comparison.md"}'),o={name:"blog/npgsqlrest-vs-postgrest-supabase-comparison.md"};function d(p,t,c,h,g,k){const e=i("BlogNav");return n(),a("div",null,[t[0]||(t[0]=r(`

NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison

Comparison · PostgREST · Supabase · January 2026


Three tools can expose a PostgreSQL database as a REST API without a hand-written backend: NpgsqlRest, PostgREST, and Supabase. They differ in architecture, performance, and how much of the surrounding platform (auth, files, caching, deployment) they cover. This comparison works through each area in turn.

Executive Summary

AspectNpgsqlRestPostgRESTSupabase
What It IsComplete platform in a single binaryStandalone executable/DockerBackend-as-a-Service platform
Core FocusSQL files + functions as REST endpointsTable/View-centric REST APIComplete backend platform
Best ForSQL-first APIs, self-hosted full-stackFlexible client-side queriesManaged hosting with dashboard
Performance4,588 req/s (100 VU)¹1,749 req/s (100 VU)¹Uses PostgREST internally
DeploymentSingle binary (~30MB), any cloudSingle binary (~20MB)Managed cloud or complex self-host
Self-HostingSimple (single binary)SimpleComplex (7+ services)
Learning CurveLow (SQL comments)Medium (RLS policies)Medium (platform concepts)

¹ Benchmarked on a single PostgreSQL function returning a simple result set. See the full benchmark methodology for details. Performance will vary with query complexity and workload type.

Architecture Comparison

NpgsqlRest

mermaid
flowchart LR
+    A[Client] <--> B["NpgsqlRest
+    (30MB AOT)
+    Single executable"]
+    B <--> C
+    subgraph C [PostgreSQL]
+        D["Comment Annotations
+        (API config)"]
+    end

NpgsqlRest is a complete platform in a single, self-contained executable that connects directly to PostgreSQL and serves REST APIs. Starting with v3.12.0, the primary way to create endpoints is SQL files — write a .sql file, add a comment annotation, and it becomes a REST endpoint. No database deployment needed, no functions to create. For more complex logic, PostgreSQL functions and procedures are still fully supported and give you true static type checking end-to-end.

API configuration lives in SQL comments — the same annotation syntax works in both SQL files and database object comments. Custom paths, caching, rate limiting, authentication, and more, all version-controlled with your code.

Beyond API generation, NpgsqlRest includes everything needed for full-stack development:

No additional infrastructure required. Download, configure connection string, run. It deploys on any cloud server instance—AWS EC2, DigitalOcean, Hetzner—or your own hardware.

PostgREST

mermaid
flowchart LR
+    A[Client] <--> B["PostgREST
+    (Haskell)
+    Single executable"] <--> C[PostgreSQL]

PostgREST follows a similar single-binary model. Both are lightweight and straightforward to deploy.

Supabase

mermaid
flowchart LR
+    A[Client] <--> B
+    subgraph B [Supabase]
+        direction TB
+        subgraph row1 [" "]
+            direction LR
+            C[PostgREST]
+            D[GoTrue]
+            E[Realtime]
+        end
+        subgraph row2 [" "]
+            direction LR
+            F[Storage]
+            G[Kong]
+            H[Studio]
+        end
+        I[(PostgreSQL)]
+    end

Supabase is a platform composed of multiple services. PostgREST handles REST API generation, GoTrue manages authentication, Kong is the API gateway, and further services handle storage, realtime, and the admin dashboard. Self-hosting means orchestrating all of these.

Key difference: Both NpgsqlRest and Supabase are platforms, but NpgsqlRest packages everything into a single binary (~30MB) while Supabase requires 7+ separate services. Supabase offers a managed cloud service with a visual dashboard; NpgsqlRest gives you full control with simpler self-hosting on any cloud provider.

Performance Benchmarks

Benchmark results from PostgreSQL REST API Benchmark 2026, testing 14 frameworks under identical conditions:

Requests Per Second (100 Concurrent Users, 1 Record)

FrameworkRequests/secLatencyScaling Factor
NpgsqlRest JIT4,58810.88ms9.5x
NpgsqlRest AOT4,52711.02ms9.7x
Swoole PHP4,42311.29ms9.4x
Rust (Actix)3,94012.67ms7.8x
PostgREST1,74928.58ms6.5x

Key findings:

Larger Payloads (500 Records, 100 VU)

FrameworkRequests/secLatency
Swoole PHP106.88468ms
NpgsqlRest JIT82.37607ms
PostgREST78.59636ms

With larger payloads where database I/O dominates, the performance gap narrows significantly. Swoole PHP leads in data-heavy scenarios, while NpgsqlRest remains competitive with PostgREST.

PostgreSQL Type Handling

TypeNpgsqlRestPostgRESTSupabase
JSON/JSONB
Arrays (int[], text[])
Composite types
Date/Time types
Boolean
Variadic parameters
OUT parameters
Default parameters

All three frameworks handle PostgreSQL types correctly.

Feature Comparison Matrix

Platform Features

FeatureNpgsqlRestPostgRESTSupabase
Static file serving
Static file authorization
Template parsing (claim substitution)
TypeScript/JavaScript code generation
HTTP test file generation
Built-in SQL test runner
Watch mode (dev reload)⚠️
Built-in authentication
File uploads
Visual dashboard
Managed cloud hosting
Single-binary deployment
Deploy on any cloud/server⚠️

⚠️ = Complex self-hosting (7+ services); Supabase local dev reloads via the CLI, not the platform itself

NpgsqlRest as a complete platform: Unlike PostgREST (API-only), NpgsqlRest covers the rest of the stack:

Supabase offers different platform strengths: managed cloud hosting, a visual Studio dashboard for database management, and real-time WebSocket subscriptions. Choose Supabase if you prefer managed infrastructure and a visual interface; choose NpgsqlRest if you want full control with simpler self-hosting.

Core API Generation

FeatureNpgsqlRestPostgRESTSupabase
SQL files as endpoints
Multi-command SQL batch execution
Functions as endpoints
Procedures as endpoints
Tables as endpoints
Views as endpoints
Function overloading
Custom URL paths
Path parameters (/users/{id})
OpenAPI/Swagger generation
TypeScript client generation
HTTP test file generation

SQL file endpoints are unique to NpgsqlRest. Neither PostgREST nor Supabase can turn a .sql file on disk into a REST endpoint. Both require you to create database objects first (functions, views, or tables) before anything is exposed as an API. With NpgsqlRest, you write a SQL file, annotate it with a comment, and the endpoint exists — no database deployment step, no CREATE FUNCTION. Multi-command files execute as a batch and return a JSON object with named result sets.

Table and View Query Features

FeatureNpgsqlRestPostgRESTSupabase
Auto-generated CRUD endpoints over tables/views
Client-side filtering operators✅ (28+ operators)
Client-side resource embedding
Client-side aggregates
Client-side column selection
Client-side ordering/pagination

NpgsqlRest deliberately does not auto-generate CRUD endpoints over tables and views — endpoints come from explicit SQL files or PostgreSQL routines. PostgREST and Supabase allow clients to compose queries against tables:

NpgsqlRest takes a different approach: write the SQL yourself — either as a SQL file or a PostgreSQL function — and expose exactly the query you intend. These same capabilities (joins, full-text search, aggregates, filtering, pagination) are all available because you're writing actual SQL:

Author's note: Earlier versions of NpgsqlRest shipped a CrudSource plugin that auto-generated CRUD endpoints over tables and views. I removed it from the standalone client in v3.14.0 — exposing raw tables directly is a questionable engineering practice. SQL files and functions provide a proper API contract, encapsulate business logic, and give you full control over what data is exposed and how. The plugin still ships as a NuGet package for embedded library use, but it is no longer wired into the default client. — Vedran Bilopavlović, NpgsqlRest author

Choose PostgREST if you want clients to compose their own queries against tables. Choose NpgsqlRest if you prefer well-defined server-side endpoints with explicit query logic.

Custom Types and Nested JSON

FeatureNpgsqlRestPostgRESTSupabase
Return composite types as JSON
Return SETOF composite types
Nested composite types in response
Arrays of composite types (multiset)
Deep nesting (3+ levels)
Flat/merged composite mode
Composite type as parameter
Parameter field unnesting
TypeScript types for nested structures⚠️

✅ = Full support ⚠️ = Partial support or workarounds needed ❌ = Not supported

All three frameworks support PostgreSQL composite types and can return nested JSON structures, including arrays of composite types (multiset pattern). Key differences:

See Custom Types and Multiset for Nested JSON for detailed examples.

Authentication

FeatureNpgsqlRestPostgRESTSupabase
Token Schemes
Standard JWT Bearer token
Encrypted Bearer token (Microsoft Data Protection)
Encrypted Cookie
HTTP Basic authentication
Authentication Sources
OAuth providers (Google, GitHub, etc.)
Password authentication (custom)
Passkey/WebAuthn (biometrics)⚠️
Password Hashing
PostgreSQL native (pgcrypto)
Built-in PBKDF2
Other Features
Custom login/logout functions
Multiple auth schemes simultaneously
Named additional auth schemes (different TTL/scope per scheme)
Role-based access control
Claims to PostgreSQL context
Claims to PostgreSQL parameters
Auth time fields in interval format ("14 days", "5 minutes")

⚠️ = Requires third-party integration

Token schemes: NpgsqlRest supports three distinct token formats:

Authentication sources: Users can authenticate via:

Passkey/WebAuthn support: NpgsqlRest includes built-in passkey authentication for passwordless login using device biometrics or PINs. NpgsqlRest handles the WebAuthn protocol (CBOR parsing, signature verification) while your PostgreSQL functions control user management and credential storage. PostgREST has no passkey support. Supabase does not offer native passkey support—users must integrate third-party services like Corbado or Descope to add passkeys to their Supabase applications.

NpgsqlRest also offers claims-to-parameters mapping - user claims (user ID, roles, custom claims) are automatically injected as function parameters by configurable name matching, with type safety enforced by PostgreSQL. PostgREST and Supabase only support accessing claims via current_setting() or auth.jwt() context functions, requiring manual casting within your SQL code.

PostgREST only supports JWT - you need an external auth server like GoTrue (which is what Supabase uses).

Architectural difference: PostgREST and Supabase rely on Row Level Security (RLS) as their primary authorization mechanism — you write DDL policies on tables that filter rows based on the authenticated user's JWT claims. These policies must be created on the server via migrations and are evaluated on every query. NpgsqlRest takes a different approach: authorization is declared via comment annotations (@authorize, @allow_anonymous, @basic_auth, etc.) directly on each endpoint — no RLS needed, no DDL, and each endpoint is individually configurable. For a deeper look at NpgsqlRest's authentication approach, see Database-Level Security: PostgreSQL Authentication and Multiple Auth Schemes, RBAC & External Providers.

Named additional auth schemes (NpgsqlRest 3.13.0): NpgsqlRest can register multiple named authentication schemes alongside the main one — e.g., a normal 14-day cookie session for general use plus a 1-hour single-session cookie for sensitive operations (recovery codes, admin areas, payment flows). Each scheme has its own Type (Cookies, BearerToken, or Jwt), expiration, and (for JWT) signing secret. Login functions select the scheme by returning its name in the scheme column. PostgREST supports only a single JWT scheme with one secret. Supabase Auth uses a single GoTrue session model with one cookie configuration — Supabase docs explicitly note that shortening the cookie's Max-Age "results in a degraded user experience," so step-up authentication for sensitive operations must be implemented at the application layer.

File Handling

FeatureNpgsqlRestPostgRESTSupabase
File uploads
Image uploads with validation
PostgreSQL Large Objects
File system storage
CSV file ingestion
Excel file ingestion
CSV export
Excel (.xlsx) export (streaming)
HTML table rendering
Row-by-row processing
Upload metadata to functions

NpgsqlRest handles the full file lifecycle:

PostgREST has no file upload support. Supabase requires a separate Storage service.

Security and Infrastructure

FeatureNpgsqlRestPostgRESTSupabase
Application-level column encryption
Security headers middleware
Forwarded headers (reverse proxy)⚠️
Health check endpoints⚠️
PostgreSQL statistics endpoints

Application-level column encryption: NpgsqlRest provides transparent encrypt/decrypt annotations powered by ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result columns are decrypted before returning to the client — the database stores ciphertext while the API consumer sees plaintext. Useful for PII (SSN, medical records) without requiring pgcrypto or client-side encryption. PostgREST has no built-in encryption. Supabase provides Vault for managing secrets but not annotation-level column encryption.

Security headers: NpgsqlRest includes built-in middleware for HTTP security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Content-Security-Policy, Permissions-Policy, COOP, COEP, CORP). PostgREST and Supabase require reverse proxy configuration or application-level implementation.

Forwarded headers: NpgsqlRest has built-in middleware for processing X-Forwarded-For/Proto/Host headers with configurable trusted proxies and networks. PostgREST can read these headers via SQL but has no configuration for trusted proxy validation. Supabase handles this at their API gateway level.

Health check endpoints: NpgsqlRest provides /health, /health/ready (with PostgreSQL connectivity check), and /health/live endpoints for Kubernetes/Docker orchestration. PostgREST has /live and /ready via its Admin Server. Supabase has service-specific health checks but no unified application health endpoint.

PostgreSQL statistics: NpgsqlRest exposes /stats/routines, /stats/tables, /stats/indexes, and /stats/activity for monitoring PostgreSQL performance. PostgREST has no built-in stats endpoints (requires custom views). Supabase provides a Metrics API with ~200 PostgreSQL metrics (cloud only, not self-hosted).

Performance Features

FeatureNpgsqlRestPostgRESTSupabase
Response caching (memory)
Response caching (Redis)
Hybrid cache with stampede protection
Cache invalidation endpoints
Named cache profiles (per-endpoint)
Conditional caching (When rules: dynamic TTL, skip-on-condition)
Rate limiting⚠️
Multiple rate limit algorithms
Partitioned rate limiting (per-user/IP/header)
Connection pooling
Transaction-mode pooler compatibility (PgBouncer/RDS Proxy/Supavisor)
Command retry with backoff
Connection retry with configurable delays
Multi-host failover
Request compression
Response compression (Gzip/Brotli)

⚠️ Supabase has rate limiting at the managed cloud edge layer, but it is not configurable per-endpoint or as a policy.

NpgsqlRest ships performance features the other two delegate to external infrastructure:

Caching architectural difference: PostgREST has no built-in response caching (active issue #4460 proposing a SIEVE-based in-memory cache), so caching must be handled by an upstream reverse proxy or CDN. Supabase has CDN caching for storage assets only — API response caching requires manual implementation (Next.js cache helpers, revalidation webhooks, or external Redis). NpgsqlRest is the only one with first-class response caching including conditional rules: a single endpoint can serve historical queries cached for 1 hour, "until-now" queries cached for 5 minutes, and live=true queries that bypass the cache entirely — all declared in JSON config, no SQL changes.

Rate limiting architectural difference: PostgREST has no built-in rate limiting (issue #785 — the official position is that rate limiting belongs at the gateway level, not in PostgREST). Supabase has edge-level rate limiting in their managed cloud, but it is not configurable per-endpoint or as a partitioned policy. NpgsqlRest's Partition block resolves a key per request from claims/IP/headers, so each authenticated user gets their own bucket — without writing custom middleware or fronting the service with Nginx/Kong.

Connection Pooler Compatibility & Multi-Tenancy

FeatureNpgsqlRestPostgRESTSupabase
Per-request transaction wrapping (opt-in)
Pre-routine SQL execution hook
Multiple pre-routine commands per request
Parameterized pre-routine SQL with claim/header/IP binding
No extra round-trip for pre-routine commands
Multi-tenant search_path from JWT claim (declarative)⚠️⚠️

⚠️ Possible via a custom function called by db-pre-request, but not declarative — requires writing a SQL function that reads current_setting('request.jwt.claims', true)::json and calls set_config() manually.

All three platforms support transaction-mode connection poolers (PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, Supabase's Supavisor) — but the ergonomics differ.

PostgREST has db-pre-request, a configuration option that names a single SQL function to call after authentication and before the main query. The function can read JWT claims via current_setting('request.jwt.claims', true)::json and apply settings like search_path. This is functionally similar to NpgsqlRest's hook, but is a single function call with no parameter binding — you write SQL that reads context strings, parses JSON, and calls set_config() itself. PostgREST also wraps every request in a transaction with role/jwt-claims set per-transaction by default, so pooler compatibility is built in without an opt-in flag.

Supabase uses PostgREST internally, so it inherits db-pre-request. Supavisor handles transaction-mode pooling.

NpgsqlRest 3.13.0 introduces two cooperating options:

The practical difference: NpgsqlRest's multi-tenant search_path is 4 lines of JSON config:

jsonc
jsonc
"BeforeRoutineCommands": [
+  {
+    "Sql": "select set_config('search_path', $1, true)",
+    "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
+  }
+]

The PostgREST equivalent requires creating a SQL function that reads request.jwt.claims, parses the JSON, extracts tenant_id, and calls set_config() — plus the db-pre-request = '<function_name>' config entry. Functionally equivalent, but the NpgsqlRest version is declarative with type-safe parameter binding rather than string-parsing JWT claims in SQL.

For details on the NpgsqlRest options, see Connection Pooler Compatibility.

Real-Time Capabilities

FeatureNpgsqlRestPostgRESTSupabase
Server-Sent Events (SSE)
WebSockets
PostgreSQL RAISE streaming
PostgreSQL LISTEN/NOTIFY
Broadcast to subscribed clients
Event scoping (authorize/matching/all)

NpgsqlRest uses Server-Sent Events for real-time streaming, which is simpler than WebSockets and works through standard HTTP. PostgreSQL's RAISE INFO/NOTICE/WARNING statements stream directly to connected clients - no message brokers or LISTEN/NOTIFY infrastructure needed. See the Real-Time Chat example.

External Service Integration and Custom Code Execution

A critical question for any PostgreSQL REST framework: how do you execute custom logic that isn't in the database? — calling external APIs, rendering PDFs, sending emails, running ML inference, or integrating with third-party services.

The three take very different approaches:

CapabilityNpgsqlRestPostgRESTSupabase
Proxy / External Service Calls
Forward request to upstream (passthrough)
Transform upstream response in PG
Forward PG result to upstream (proxy_out)
Declarative proxy via SQL annotations
Proxy response caching
Custom Code Runtimes
Edge Functions (Deno/TypeScript)
Serverless function runtime
HTTP Client Types (app-layer HTTP calls)
Typed HTTP calls via composite types
Parallel execution of multiple HTTP calls
Placeholder substitution (URL, headers, body)
All HTTP methods (GET/POST/PUT/PATCH/DELETE)
Per-type timeout and retry with backoff
Retry on specific status codes (429, 503, etc.)
Server-side resolved parameters (secrets)
Database-level HTTP extensions
pg_net (async, JSON POST only, 200 req/s)
http extension (sync, blocks DB connection)⚠️
Event-Driven
Database Webhooks (trigger → HTTP)
Pre-request hook

⚠️ = Available via third-party PostgreSQL extension (not a PostgREST feature)

NpgsqlRest: Declarative Proxy in SQL

NpgsqlRest has three proxy modes, all configured via SQL comment annotations on PostgreSQL functions, with no additional infrastructure:

1. Passthrough proxy — forward the client request to an upstream service, return the upstream response directly. The PostgreSQL function is never executed (no database connection opened):

sql
sql
create function get_weather_forecast()
+returns void language sql as 'select';
+
+comment on function get_weather_forecast() is 'HTTP GET
+@proxy https://weather-api.example.com';

2. Transform proxy — forward the request to upstream, then pass the upstream response into the PostgreSQL function for processing. The function receives the upstream status code, body, headers, content type, success flag, and error message as parameters:

sql
sql
create function enrich_user_data(
+    _user_id int,
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null
+)
+returns json language plpgsql as $$
+begin
+    if not _proxy_success then
+        return json_build_object('error', 'upstream failed');
+    end if;
+    -- Combine upstream data with local data
+    return json_build_object(
+        'external', _proxy_body::json,
+        'local', (select row_to_json(u) from users u where u.id = _user_id)
+    );
+end; $$;
+
+comment on function enrich_user_data(int, int, text, boolean) is 'HTTP GET
+@proxy https://external-api.example.com';

3. Proxy out (post-execution proxy) — execute the PostgreSQL function first, then forward its result as the request body to an upstream service. The upstream response is returned to the client. Ideal for scenarios where PostgreSQL prepares a payload and an external service does heavy processing (PDF rendering, ML inference, image processing):

sql
sql
create function generate_invoice(order_id int)
+returns json language plpgsql as $$
+begin
+    return (select json_build_object(
+        'customer', c.name, 'items', json_agg(row_to_json(i))
+    ) from orders o
+    join customers c on c.id = o.customer_id
+    join order_items i on i.order_id = o.id
+    where o.id = order_id
+    group by c.name);
+end; $$;
+
+comment on function generate_invoice(int) is 'HTTP GET
+@proxy_out POST https://pdf-renderer.internal/render';

All three modes support: custom upstream host per endpoint, HTTP method override, authenticated user claim/context forwarding to upstream, upload content forwarding, configurable timeouts, and response caching. Proxy configuration shares the same ProxyOptions section and integrates with NpgsqlRest's existing caching, rate limiting, and authentication systems.

4. HTTP Client Types — a completely different mechanism from the proxy modes. You define a PostgreSQL composite type whose comment contains an HTTP request definition (method, URL, headers, body), and NpgsqlRest makes the HTTP call automatically when a function parameter uses that type. The response fields (body, status_code, headers, content_type, success, error_message) are populated and passed to the PostgreSQL function as a regular parameter:

sql
sql
-- Define an HTTP client type via comment on a composite type
+create type openai_api as (body json, status_code int, success boolean, error_message text);
+comment on type openai_api is 'POST https://api.openai.com/v1/chat/completions
+Authorization: Bearer {_api_key}
+Content-Type: application/json
+
+{"model": "gpt-4", "messages": [{"role": "user", "content": "{_prompt}"}]}';
+
+-- Use it in a function — NpgsqlRest makes the HTTP call, PG processes the result
+create function ask_ai(_prompt text, _api_key text, _response openai_api)
+returns json language plpgsql as $$
+begin
+    if not (_response).success then
+        return json_build_object('error', (_response).error_message);
+    end if;
+    return (_response).body;
+end; $$;

Key capabilities of HTTP Client Types:

This is fundamentally different from Supabase's pg_net (async-only, JSON POST only, 200 req/s limit, no placeholder substitution, responses in a queue table) and the http extension (blocks the database connection). NpgsqlRest's HTTP Client Types run in the application layer, execute in parallel, support all methods, have built-in retry logic, and integrate directly into the function's parameter pipeline.

PostgREST: No Custom Code Execution

PostgREST is intentionally narrow in scope — it maps PostgreSQL to a REST API and nothing else. It has no built-in proxy, no webhook support, no custom code runtime, and no outbound HTTP capability.

The only hook is db-pre-request: a configurable pre-request function that runs inside the database transaction before the main query. It can inspect headers and JWT claims, and can block requests by raising exceptions — but it cannot make external HTTP calls or modify the response.

PostgREST users who need external service integration must add separate infrastructure:

Supabase: Edge Functions (Separate Deno Runtime)

Supabase addresses custom code execution through Edge Functions — serverless TypeScript/JavaScript functions running on a Deno-based runtime in V8 isolates, deployed as a separate service alongside PostgREST.

Each Edge Function gets its own HTTP endpoint (https://<project>.supabase.co/functions/v1/<name>) and must be written in TypeScript, deployed via CLI or dashboard, and managed independently from your PostgreSQL schema:

typescript
typescript
// supabase/functions/generate-pdf/index.ts
+import { serve } from "https://deno.land/std/http/server.ts"
+
+serve(async (req) => {
+  const { orderId } = await req.json()
+
+  // Must manually connect to database
+  const supabase = createClient(
+    Deno.env.get('SUPABASE_URL')!,
+    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
+  )
+  const { data } = await supabase
+    .from('orders')
+    .select('*, customer(*), items(*)')
+    .eq('id', orderId)
+    .single()
+
+  // Must manually call external service
+  const pdf = await fetch('https://pdf-renderer.internal/render', {
+    method: 'POST',
+    body: JSON.stringify(data),
+  })
+
+  return new Response(await pdf.arrayBuffer(), {
+    headers: { 'Content-Type': 'application/pdf' },
+  })
+})

Edge Functions have notable constraints: 2-second CPU time limit per request, 256 MB memory, 20 MB bundle size, and cold starts. They are available in self-hosted Supabase but run as a single instance (no multi-region distribution).

Supabase also offers Database Webhooks — PostgreSQL triggers that use the pg_net extension to send HTTP requests on INSERT/UPDATE/DELETE events. These are fire-and-forget (async, responses stored in a queue table for 6 hours) with a 200 requests/second limit and JSON POST only (no PUT/PATCH).

For direct HTTP calls from PostgreSQL, Supabase provides the pg_net extension (async, non-blocking, JSON POST only) and the http extension (synchronous, blocks the database connection). Neither can control what gets returned to the original HTTP client — they are purely outbound.

Architectural Comparison

The fundamental difference is where orchestration lives:

AspectNpgsqlRestPostgRESTSupabase
Where logic is definedSQL files + comments on PG functions/typesN/ATypeScript in separate runtime
Additional services requiredNoneCustom API server or middlewareEdge Runtime (Deno container)
Database involvementPG function controls the entire flowPG has no role in external callsPG can trigger webhooks only
Response controlPG function decides what client receivesN/AEdge Function decides
Parallel external callsBuilt-in (HTTP Client Types)N/AManual Promise.all() in TS
Deployment complexityZero — same binaryRequires additional infrastructureRequires separate Deno service
Caching of external callsBuilt-in (@cached annotation)N/AMust implement manually
Secret managementServer-side resolved parametersN/Asupabase secrets set CLI
Retry logicBuilt-in (@retry_delay annotation)N/AMust implement manually

NpgsqlRest keeps external service orchestration inside PostgreSQL with declarative annotations — no additional runtimes, no separate deployment pipelines, no TypeScript code to maintain. Supabase requires a separate Deno runtime and imperative TypeScript code. PostgREST requires users to build their own solution entirely.

Advanced Features

FeatureNpgsqlRestPostgRESTSupabase
Per-endpoint configuration in SQL comments
Server-side resolved parameters
Custom response headers
Request header forwarding
Per-endpoint timeouts
Per-endpoint caching policies
Per-endpoint rate limiting
Per-endpoint retry strategies
Parameter validation
Raw (non-JSON) responses
Error code mapping⚠️⚠️
Named error code policies
Per-endpoint error policies
Configurable timeout error mapping
RFC 7807 Problem Details format
TraceId in error responses
CORS configuration
HTTPS/TLS

Observability

FeatureNpgsqlRestPostgRESTSupabase
Structured logging
Log to file
Log to PostgreSQL
OpenTelemetry
Request tracing
Execution ID tracking
Sensitive parameter obfuscation

NpgsqlRest uses Serilog with multiple output targets: console, file (with rotation), PostgreSQL table, and OpenTelemetry for distributed tracing.

Error Handling

FeatureNpgsqlRestPostgRESTSupabase
Configurable error code mapping
Named error code policies
Per-endpoint error policies
Configurable timeout error mapping
RFC 7807 Problem Details format
TraceId in error responses
Custom HTTP status from SQL⚠️⚠️

⚠️ = Limited workaround available

NpgsqlRest's error handling is policy-based and configurable end to end — a clear gap over PostgREST and Supabase, which hardcode their mappings.

Named error code policies: Define multiple named policies that map PostgreSQL error codes to specific HTTP status codes, titles, and details. For example, map 23505 (unique violation) to HTTP 409 Conflict, or 23503 (foreign key violation) to HTTP 400 with a custom message—all in configuration, not in SQL:

json
json
{
+  "ErrorCodePolicies": [
+    {
+      "Name": "strict_errors",
+      "ErrorCodes": {
+        "23505": {"StatusCode": 409, "Title": "Conflict", "Details": "A record with this key already exists."},
+        "23503": {"StatusCode": 400, "Title": "Invalid Reference", "Details": "Referenced record does not exist."},
+        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege"}
+      }
+    }
+  ]
+}

Per-endpoint error policies: Each endpoint can use a different error policy via the @error_code_policy annotation:

sql
sql
comment on function risky_operation() is '
+HTTP POST
+@error_code_policy strict_errors';

Configurable timeout error mapping: Custom HTTP response for command timeouts with configurable status code, title, and details—not a generic 500.

RFC 7807 Problem Details: Error responses follow the RFC 7807 standard ("Problem Details for HTTP APIs"), including a type URI reference to RFC documentation, title, status, and detail fields. This is the IETF-recommended format for API error responses, enabling interoperability with standard API tooling and middleware.

TraceId correlation: Error responses include a traceId field for direct correlation between what the client receives and server-side logs—essential for debugging in production.

PostgREST uses a hardcoded mapping table (e.g., 23505 → 409, 42501 → 401/403). The only workaround is the PT### SQLSTATE mechanism—RAISE sqlstate 'PT404' forces HTTP 404—which couples your SQL to HTTP semantics. Error responses use a custom JSON format (code, message, details, hint), not RFC 7807. No per-endpoint configuration, no named policies, no configurable timeout handling, no trace IDs.

Supabase inherits PostgREST's error handling wholesale since it embeds PostgREST as its REST layer. The client SDKs wrap errors into {data, error} objects but add no additional error mapping configurability.

Configuration Approach

NpgsqlRest: SQL Comments

sql
sql
-- Define endpoint with custom path and authentication
+create function api.get_user(p_id int)
+returns setof user_info
+language sql
+begin atomic;
+select id, name, email, role from users where id = p_id;
+end;
+
+comment on function api.get_user(int) is '
+HTTP GET /users/{p_id}
+@authorize admin, user
+@cached
+@cache_expires_in 300
+@rate_limiter_policy standard
+';

Everything is configured through SQL comments directly on your functions, procedures, or .sql files. This keeps API configuration close to the implementation and version-controlled with your schema.

Every endpoint can be individually configured with its own caching policy, rate limiting, authentication requirements, timeout, and retry strategy—declared in SQL comments, either in your SQL files or on database objects. PostgREST and Supabase apply configuration globally or require external tools for per-endpoint customization.

PostgREST: External Configuration + RLS

sql
sql
-- PostgREST relies on Row Level Security
+create policy "Users can view own data"
+  on users for select
+  using (auth.uid() = id);

PostgREST uses a combination of configuration files and PostgreSQL Row Level Security policies. Custom paths and advanced routing require an API gateway like Kong.

Supabase: Dashboard + RLS + Edge Functions

Supabase uses a web dashboard for most configuration, RLS for authorization, and Edge Functions (Deno-based TypeScript runtime) for custom logic that cannot live in the database. Edge Functions run as a separate service, deployed independently via CLI, with their own endpoint URLs, secrets management, and resource limits. More moving parts, in exchange for a visual interface and a managed cloud option.

Deployment Comparison

NpgsqlRest

bash
bash
# Option 1: Direct download (30MB)
+wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux64
+chmod +x npgsqlrest-linux64
+./npgsqlrest-linux64 --connection "Host=localhost;Database=mydb;Username=api"
+
+# Option 2: Docker
+docker run -p 8080:8080 vbilopav/npgsqlrest:latest \\
+  --connection "Host=host.docker.internal;Database=mydb;Username=api"
+
+# Option 3: NPM
+npm install -g npgsqlrest
+npx npgsqlrest --connection "..."

Single binary, zero dependencies. Works on Windows, Linux (x64/ARM64), and macOS.

PostgREST

bash
bash
# Download and run
+wget https://github.com/PostgREST/postgrest/releases/latest/download/postgrest-linux-static-x64.tar.xz
+tar xf postgrest-linux-static-x64.tar.xz
+./postgrest postgrest.conf
+
+# Docker
+docker run -p 3000:3000 postgrest/postgrest

Similar simplicity, but requires external services for authentication and file handling.

Supabase Self-Hosted

bash
bash
# Clone the Docker setup
+git clone https://github.com/supabase/supabase
+cd supabase/docker
+cp .env.example .env
+docker compose up -d

Supabase self-hosting requires Docker Compose with 7+ containers: PostgreSQL, PostgREST, GoTrue, Realtime, Storage, Kong, Studio, and more — correspondingly harder to maintain and scale.

When to Choose Each

Choose NpgsqlRest When:

Choose PostgREST When:

Choose Supabase When:

Migration Considerations

From PostgREST to NpgsqlRest

  1. Functions work identically — both expose PostgreSQL functions as endpoints
  2. Replace direct table/view access with SQL files or functions — NpgsqlRest does not auto-generate CRUD endpoints over tables. Endpoints that relied on PostgREST's ?eq./?order. style query composition need to be expressed as SQL files (often the simplest path) or as PostgreSQL functions
  3. Move simple RPC functions to SQL files — many /rpc/ endpoints can become plain .sql files, no CREATE FUNCTION needed
  4. Add SQL comments for configuration — replace external config with inline annotations
  5. Replace RLS with function-level auth — or keep RLS and add @authorize annotations
  6. Gain features — caching, rate limiting, file uploads, multi-command batch scripts

From Supabase to NpgsqlRest

  1. Keep your PostgreSQL schema - It's still just PostgreSQL
  2. Replace direct table/view access with SQL files or functions — same caveat as the PostgREST migration: Supabase's auto-generated table API has no direct counterpart, so write the queries as SQL files or routines
  3. Replace GoTrue with built-in auth - JWT, encrypted Bearer/Cookie, OAuth, Passkey/WebAuthn
  4. Replace Storage with NpgsqlRest uploads - File system or Large Objects
  5. Replace Realtime with SSE - Simpler protocol, works through standard HTTP
  6. Replace Edge Functions with SQL files or proxy annotations — simple Edge Functions become SQL files. Those that call external APIs become functions with @proxy, @proxy_out, or HTTP Client Type annotations. No separate Deno runtime needed
  7. Replace pg_net/http extensions with HTTP Client Types - Typed, synchronous HTTP calls with retry logic and server-side secret resolution, integrated into the function execution pipeline

Conclusion

CriteriaWinner
SQL File EndpointsNpgsqlRest (only native support)
Raw PerformanceNpgsqlRest (2.6x faster)
Self-Hosted PlatformNpgsqlRest (single binary)
Table/View Query FlexibilityPostgREST / Supabase
Function-Based APIsNpgsqlRest
Per-Endpoint ConfigurationNpgsqlRest
Static Files + Template ParsingNpgsqlRest
Frontend Code GenerationNpgsqlRest / Supabase
Deployment SimplicityNpgsqlRest / PostgREST (tie)
Authentication OptionsNpgsqlRest
Passkey/WebAuthnNpgsqlRest (only native support)
File HandlingNpgsqlRest
Excel Export (native .xlsx)NpgsqlRest (only native support)
Enterprise Features (caching, rate limiting)NpgsqlRest
Conditional Caching (When rules)NpgsqlRest (only native support)
Per-User Rate Limiting (Partition)NpgsqlRest (only declarative)
Multi-Tier Auth Schemes (per-scope sessions)NpgsqlRest (only declarative)
Multi-Tenant search_path from JWTNpgsqlRest (declarative)
Column Encryption (encrypt/decrypt)NpgsqlRest (only native support)
Error Handling (policies, RFC 7807)NpgsqlRest (only configurable)
Security HeadersNpgsqlRest (only built-in)
Health Checks (Kubernetes/Docker)NpgsqlRest / PostgREST
PostgreSQL Statistics EndpointsNpgsqlRest / Supabase
Custom Types / Nested JSONAll three (different strengths)
External Service Integration (proxy)NpgsqlRest (declarative, zero infrastructure)
Custom Code RuntimeSupabase (Edge Functions)
Real-TimeSupabase (WebSockets) / NpgsqlRest (SSE)
Managed HostingSupabase
Visual DashboardSupabase
Maturity/CommunityPostgREST / Supabase

NpgsqlRest is a complete self-hosted platform where the fastest way to create an endpoint is to write a SQL file. No CREATE FUNCTION, no database deployment — just a .sql file with a comment annotation. For complex logic, PostgreSQL functions are fully supported with true end-to-end type checking. Beyond API generation, it serves static files with template parsing, generates TypeScript clients, and includes built-in authentication — all in a single ~30MB binary, with per-endpoint caching, rate limiting, timeout, and auth rules version-controlled in SQL comments.

PostgREST shines for flexible client-side queries with its GraphQL-like resource embedding, 28+ filtering operators, aggregates, and pagination. It's the right choice when your API consumers need to compose their own queries against tables and views. PostgREST is API-only—you'll need additional services for auth, static files, and file uploads.

Supabase is ideal when you want managed cloud hosting with a visual dashboard—especially for teams that prefer not to manage infrastructure. While both NpgsqlRest and Supabase are platforms, Supabase requires 7+ services for self-hosting whereas NpgsqlRest is a single binary.


`,195)),l(e,{"get-started":[{text:"Installation Guide",href:"/guide/installation"},{text:"Quick Start",href:"/guide/quick-start"},{text:"Authentication Configuration",href:"/config/auth"},{text:"File Upload Configuration",href:"/config/uploads"}]})])}const m=s(o,[["render",d]]);export{y as __pageData,m as default}; diff --git a/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.lean.js b/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.lean.js new file mode 100644 index 000000000..aa11bb983 --- /dev/null +++ b/assets/blog_npgsqlrest-vs-postgrest-supabase-comparison.md.00RFbAF5.lean.js @@ -0,0 +1 @@ +import{_ as s,C as i,c as a,o as n,a5 as r,G as l}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","titleTemplate":"NpgsqlRest","description":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, authentication, file handling, and deployment options compared.","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","titleTemplate":"NpgsqlRest","description":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, authentication, file handling, and deployment options compared.","head":[["meta",{"name":"keywords","content":"npgsqlrest vs postgrest, npgsqlrest vs supabase, postgresql rest api comparison, postgrest alternative, supabase alternative, postgresql api performance, rest api framework comparison"}],["meta",{"property":"og:title","content":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"}],["meta",{"property":"og:description","content":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase. Performance benchmarks, features, and deployment options compared."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"}],["meta",{"name":"twitter:description","content":"Side-by-side comparison of NpgsqlRest, PostgREST, and Supabase for PostgreSQL REST APIs."}]]},"headers":[],"relativePath":"blog/npgsqlrest-vs-postgrest-supabase-comparison.md","filePath":"blog/npgsqlrest-vs-postgrest-supabase-comparison.md"}'),o={name:"blog/npgsqlrest-vs-postgrest-supabase-comparison.md"};function d(p,t,c,h,g,k){const e=i("BlogNav");return n(),a("div",null,[t[0]||(t[0]=r("",195)),l(e,{"get-started":[{text:"Installation Guide",href:"/guide/installation"},{text:"Quick Start",href:"/guide/quick-start"},{text:"Authentication Configuration",href:"/config/auth"},{text:"File Upload Configuration",href:"/config/uploads"}]})])}const m=s(o,[["render",d]]);export{y as __pageData,m as default}; diff --git a/assets/blog_optimization-labels-101.md.0ZBmELgS.js b/assets/blog_optimization-labels-101.md.0ZBmELgS.js new file mode 100644 index 000000000..16da88e79 --- /dev/null +++ b/assets/blog_optimization-labels-101.md.0ZBmELgS.js @@ -0,0 +1,4 @@ +import{_ as a,C as n,c as o,o as s,a5 as i,G as l}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"NpgsqlRest Story Told Without AI","titleTemplate":"NpgsqlRest","description":"PostgreSQL Optimization Labels 101","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest Story Told Without AI","titleTemplate":"NpgsqlRest","description":"PostgreSQL Optimization Labels 101","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql optimization volatile stable immutable"}],["meta",{"property":"og:title","content":"NpgsqlRest Story told without AI"}],["meta",{"property":"og:description","content":"PostgreSQL Optimization Labels 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL Optimization Labels 101"}]]},"headers":[],"relativePath":"blog/optimization-labels-101.md","filePath":"blog/optimization-labels-101.md"}'),r={name:"blog/optimization-labels-101.md"};function c(p,e,d,h,u,m){const t=n("BlogNav");return s(),o("div",null,[e[0]||(e[0]=i(`

PostgreSQL Optimization Labels 101

January 2026 · PostgreSQLOptimizationFunctionVOLATILESTABLEIMMUTABLE

Originally published on Medium

When creating a PostgreSQL function or procedure, you can declare many different labels that affect different aspects of your function. Some of them are optimization-oriented. Here is my 101 breakdown, because I keep forgetting them, and the best way to learn for me is to write.

VOLATILE / STABLE / IMMUTABLE

These are mutually exclusive.

Important

If a function marked IMMUTABLE calls a STABLE function, it will revert to STABLE. If STABLE calls VOLATILE, it will revert to VOLATILE automatically, regardless of what is set during creation — so check the metadata.

PARALLEL UNSAFE / RESTRICTED / SAFE

Sometimes the query planner will opportunistically decide that some operation will run in parallel. This won't happen on a small set, only when the planner estimates a performance benefit. In that case you may see something like this in the planner:

code
Finalize Aggregate
+    -> Gather
+         Workers Planned: 2
+         Workers Launched: 2

COST / ROWS

COST (default: 100)

The planner needs to know how much operations cost in terms of execution performance, and functions are indeed operations. PostgreSQL uses its own arbitrary measurement unit.

TIP

Tackling this setting should be done if the query is indeed slow.

ROWS (default: 1000)

The number of rows returned is another piece of information for the planner that can be used to optimize calls and plan execution better. Therefore, this can only be used in functions that return sets (SETOF or TABLE).

A planner cannot possibly know the number of rows returned, and it will assume that it is 1000. But you can, and if you do, you can set this parameter.

The practical implications of this optimization label depend on which joining strategy the planner chooses, based on the row estimate:

In any case, this parameter only makes sense if you tend to join a function that returns sets.

CALLED ON NULL INPUT / RETURNS NULL ON NULL INPUT / STRICT

If you have a function that is supposed to return NULL if one of the parameters is NULL, you can just say so by labeling it RETURNS NULL ON NULL INPUT or shorter STRICT. This tells the engine to avoid calling the function in that case — simply replace it with a NULL value.

Default is CALLED ON NULL INPUT, which is normal behavior, and it doesn't need to be stated explicitly.


This concludes my short series on PostgreSQL optimization labels (some might call them hints).

`,28)),l(t,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"}]})])}const b=a(r,[["render",c]]);export{g as __pageData,b as default}; diff --git a/assets/blog_optimization-labels-101.md.0ZBmELgS.lean.js b/assets/blog_optimization-labels-101.md.0ZBmELgS.lean.js new file mode 100644 index 000000000..5ad92d788 --- /dev/null +++ b/assets/blog_optimization-labels-101.md.0ZBmELgS.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as o,o as s,a5 as i,G as l}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"NpgsqlRest Story Told Without AI","titleTemplate":"NpgsqlRest","description":"PostgreSQL Optimization Labels 101","frontmatter":{"layout":"doc","outline":[2,3],"title":"NpgsqlRest Story Told Without AI","titleTemplate":"NpgsqlRest","description":"PostgreSQL Optimization Labels 101","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql optimization volatile stable immutable"}],["meta",{"property":"og:title","content":"NpgsqlRest Story told without AI"}],["meta",{"property":"og:description","content":"PostgreSQL Optimization Labels 101"}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL Optimization Labels 101"}]]},"headers":[],"relativePath":"blog/optimization-labels-101.md","filePath":"blog/optimization-labels-101.md"}'),r={name:"blog/optimization-labels-101.md"};function c(p,e,d,h,u,m){const t=n("BlogNav");return s(),o("div",null,[e[0]||(e[0]=i("",28)),l(t,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"}]})])}const b=a(r,[["render",c]]);export{g as __pageData,b as default}; diff --git a/assets/blog_passkey-sql-auth.md.BRkr8TRo.js b/assets/blog_passkey-sql-auth.md.BRkr8TRo.js new file mode 100644 index 000000000..f1e65c6e0 --- /dev/null +++ b/assets/blog_passkey-sql-auth.md.BRkr8TRo.js @@ -0,0 +1,611 @@ +import{_ as a,C as n,c as e,o as l,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest. Full SQL-based passkey registration, login, and credential management.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest. Full SQL-based passkey registration, login, and credential management.","head":[["meta",{"name":"keywords","content":"webauthn passkey postgresql, passwordless authentication sql, passkey implementation postgres, npgsqlrest webauthn, sql authentication passkey, fido2 postgresql"}],["meta",{"property":"og:title","content":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"}],["meta",{"property":"og:description","content":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"}],["meta",{"name":"twitter:description","content":"Passwordless WebAuthn passkey authentication implemented entirely in PostgreSQL with NpgsqlRest."}]]},"headers":[],"relativePath":"blog/passkey-sql-auth.md","filePath":"blog/passkey-sql-auth.md"}'),h={name:"blog/passkey-sql-auth.md"};function r(k,s,d,c,o,F){const i=n("BlogNav");return l(),e("div",null,[s[0]||(s[0]=t(`

Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest

WebAuthn · Authentication · January 2026


WebAuthn passkeys replace the weakest part of most systems - the password - with public-key cryptography tied to biometric or PIN verification on the user's device. There is no shared secret to phish, reuse across sites, or leak in a database breach.

NpgsqlRest now supports built-in passkey authentication that lets you implement passwordless login with your authentication logic written entirely in PostgreSQL functions. No external authentication libraries, no third-party services—just your database, a few SQL functions, and a client-side script to call the browser's WebAuthn API (see the passkey.ts example you can use as a starting point).

What Gets Stored (And What Doesn't)

A common misconception about passkeys is that they store biometric data. They don't. Here's what actually happens:

  1. During registration, the user's device generates a public/private key pair. The private key never leaves the device and is protected by biometrics or a PIN
  2. Your database stores only the public key, a credential ID, and metadata like the signature counter
  3. During login, the device signs a challenge with the private key, and your server verifies it with the stored public key

No fingerprints, no facial recognition data, no personal biometric information ever touches your server. You're storing cryptographic keys, not sensitive personal data.

Architecture Overview

NpgsqlRest's passkey implementation follows the WebAuthn specification with a SQL-first approach:

mermaid
flowchart LR
+    A["Browser
+    (Client Script)"] <--> B["NpgsqlRest
+    (Endpoints + CBOR)"] <--> C["PostgreSQL
+    (SQL Functions)"]

The division of labor: NpgsqlRest does the cryptographic work (parsing CBOR attestation objects, verifying signatures with EC and RSA keys) while your SQL functions define the business logic.

Complete Example Walkthrough

The full source for the example below is in the examples/13_passkey directory.

1. Database Schema

First, create the tables to store users, passkeys, and challenges:

sql
sql
-- Users table (can integrate with existing users)
+create table users (
+    user_id serial primary key,
+    username text not null,
+    email text,
+    password text null,  -- null for passkey-only users
+    created_at timestamptz default now()
+);
+create unique index on users(username);
+
+-- Passkeys table - stores the public keys
+create table passkeys (
+    credential_id bytea primary key,
+    user_id int not null references users(user_id) on delete cascade,
+    user_handle bytea unique not null,
+    public_key bytea not null,
+    public_key_algorithm int not null,
+    sign_count bigint not null default 0,
+    transports text[],
+    backup_eligible boolean default false,
+    device_name text,
+    created_at timestamptz default now(),
+    last_used_at timestamptz
+);
+
+-- Challenge storage for replay protection
+create table passkey_challenges (
+    id bigint not null generated always as identity primary key,
+    challenge bytea not null,
+    user_id int,  -- null for authentication, set for registration
+    operation text not null check (operation in ('registration', 'authentication')),
+    expires_at timestamptz not null,
+    created_at timestamptz default now()
+);

2. Challenge Functions

The WebAuthn flow requires generating random challenges that are verified later. Here's the registration challenge function:

sql
sql
create or replace function passkey_challenge_registration(_body json)
+returns table (
+    status int,
+    message text,
+    challenge text,
+    challenge_id bigint,
+    user_handle text,
+    user_name text,
+    user_display_name text,
+    exclude_credentials text,
+    user_context json
+)
+security definer
+language plpgsql
+as $$
+declare
+    _user_name text = _body->>'userName';
+    _user_handle bytea;
+    _challenge bytea;
+    _challenge_id bigint;
+begin
+    assert _user_name is not null and _user_name <> '';
+
+    -- Generate new user handle (random 32 bytes)
+    _user_handle = gen_random_bytes(32);
+
+    -- Generate challenge (random 32 bytes)
+    _challenge = gen_random_bytes(32);
+
+    -- Store challenge for verification
+    insert into passkey_challenges
+        (challenge, user_id, operation, expires_at)
+    values
+        (_challenge, null, 'registration', now() + interval '5 minutes')
+    returning id into _challenge_id;
+
+    return query select
+        200,
+        null::text,
+        encode(_challenge, 'base64'),
+        _challenge_id,
+        encode(_user_handle, 'base64'),
+        _user_name,
+        coalesce(_body->>'displayName', _user_name),
+        '[]'::text,
+        json_build_object(
+            'userName', _user_name,
+            'email', _body->>'email',
+            'deviceName', _body->>'deviceName'
+        );
+end;
+$$;

The function returns exactly the columns NpgsqlRest expects:

3. Completion Functions

After the browser creates the credential, NpgsqlRest verifies the attestation and calls your completion function:

sql
sql
create or replace function passkey_complete_registration(
+    _credential_id bytea,
+    _user_handle bytea,
+    _public_key bytea,
+    _public_key_algorithm int,
+    _sign_count bigint,
+    _backup_eligible boolean,
+    _transports text[],
+    _user_context json
+)
+returns table (status int, message text, user_context json)
+security definer
+language plpgsql
+as $$
+declare
+    _user_id int;
+    _user_name text = _user_context->>'userName';
+begin
+    -- Create the user
+    insert into users (username, email)
+    values (_user_name, _user_context->>'email')
+    returning user_id into _user_id;
+
+    -- Store the passkey
+    insert into passkeys (
+        credential_id, user_id, user_handle, public_key,
+        public_key_algorithm, sign_count, transports,
+        backup_eligible, device_name
+    )
+    values (
+        _credential_id, _user_id, _user_handle, _public_key,
+        _public_key_algorithm, _sign_count, _transports,
+        _backup_eligible, _user_context->>'deviceName'
+    );
+
+    return query select 200, null::text,
+        json_build_object('userId', _user_id);
+end;
+$$;

NpgsqlRest extracts the public key and algorithm from the CBOR attestation object before calling your function. You just store them.

4. Authentication Function

For login, the completion function verifies the user and returns claims for cookie/JWT authentication:

sql
sql
create or replace function passkey_complete_authenticate(
+    _credential_id bytea,
+    _new_sign_count bigint,
+    _user_context json,
+    _analytics_data json default null
+)
+returns table (
+    scheme text,
+    user_id int,
+    username text,
+    email text,
+    message jsonb
+)
+security definer
+language plpgsql
+as $$
+declare
+    _user_id int = (_user_context->>'id')::int;
+begin
+    -- Update sign count and last used timestamp
+    update passkeys
+    set sign_count = _new_sign_count, last_used_at = now()
+    where credential_id = _credential_id;
+
+    -- Return user claims for authentication
+    return query
+    select
+        'cookies' as scheme,
+        u.user_id,
+        u.username,
+        u.email,
+        jsonb_build_object(
+            'userId', u.user_id,
+            'username', u.username,
+            'email', u.email
+        )
+    from users u
+    where u.user_id = _user_id;
+end;
+$$;

The scheme column tells NpgsqlRest which authentication scheme to use (cookies, JWT bearer, etc.).

5. Configuration

Enable passkey authentication in your appsettings.json. Minimal configuration:

json
json
{
+  "Auth": {
+    "PasskeyAuth": {
+      "Enabled": true,
+      "EnableRegister": true
+    }
+  }
+}

For custom SQL commands, specify the command settings:

json
json
{
+  "Auth": {
+    "PasskeyAuth": {
+      "Enabled": true,
+      "EnableRegister": true,
+      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
+      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
+      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
+      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
+      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)"
+    }
+  }
+}

For the complete configuration reference, see Passkey Authentication Configuration.

Key configuration options:

OptionDescription
UserVerificationRequirement"required" = must use biometric/PIN; "preferred" = use if available
ResidentKeyRequirement"required" = true passwordless (no username field); "preferred" = user enters username first
AttestationConveyance"none" for most apps; "direct" to verify authenticator hardware
RateLimiterPolicyName of a configured rate limiter policy for brute-force protection
ConnectionNameOptional named connection for multi-database setups
CommandRetryStrategyRetry strategy for transient database errors (default: "default")

6. Client-Side Implementation

The passkey.ts script in the example provides a complete TypeScript implementation you can use as a template:

typescript
typescript
// Registration
+const result = await register({
+    userName: 'alice',
+    displayName: 'Alice',
+    deviceName: 'MacBook Pro'
+});
+
+if (result.success) {
+    console.log('Registered with credential:', result.credentialId);
+}
+
+// Login
+const loginResult = await login({
+    userName: 'alice'  // Optional for discoverable credentials
+});
+
+if (loginResult.success) {
+    const user = JSON.parse(loginResult.response);
+    console.log('Logged in as:', user.username);
+}

The script handles:

Three Authentication Flows

NpgsqlRest supports three distinct passkey flows. Each endpoint internally executes a configured SQL command (typically a PostgreSQL function) that you define.

1. Registration (New User with Passkey)

For new users signing up with a passkey. Creates both the user account and passkey.

mermaid
flowchart LR
+    subgraph Step1["Step 1: Get Challenge"]
+        direction TB
+        R1["POST /api/passkey/register/options
+        ────────────────────────────
+        Body:
+        {
+          userName: string ✓
+          displayName?: string
+          email?: string
+        }"]
+
+        F1["passkey_challenge_registration($1)
+        ────────────────────────────
+        $1 = JSON body
+
+        Returns:
+        • status, challenge, challenge_id
+        • user_handle, user_name
+        • exclude_credentials, user_context"]
+
+        RES1["Response:
+        {
+          challenge, challengeId
+          rp: {id, name}
+          user: {id, name, displayName}
+          pubKeyCredParams, timeout
+          excludeCredentials, userContext
+        }"]
+
+        R1 --> F1 --> RES1
+    end
+
+    subgraph Browser["Browser WebAuthn"]
+        WA["navigator.credentials.create()
+        ────────────────────────────
+        User creates passkey
+        (biometric/PIN)"]
+    end
+
+    subgraph Step2["Step 2: Complete Registration"]
+        direction TB
+        R2["POST /api/passkey/register
+        ────────────────────────────
+        Body:
+        {
+          challengeId, credentialId ✓
+          attestationObject ✓
+          clientDataJSON ✓
+          transports?, userContext ✓
+        }"]
+
+        F2["① passkey_verify_challenge($1, $2)
+           $1=challengeId, $2='registration'
+           Returns: challenge bytea
+
+        ② passkey_complete_registration(...)
+           $1=credentialId, $2=userHandle
+           $3=publicKey, $4=algorithm
+           $5=transports, $6=backupEligible
+           $7=userContext
+           Returns: status, message"]
+
+        RES2["Response:
+        {
+          success: true
+          credentialId: base64url
+        }"]
+
+        R2 --> F2 --> RES2
+    end
+
+    Step1 --> Browser --> Step2
EndpointExecutes SQL Command
POST /api/passkey/register/optionsChallengeRegistrationCommand
POST /api/passkey/registerCompleteRegistrationCommand

Registration is Disabled by Default

The standalone registration flow (EnableRegister: false by default) allows anyone to create an account with just a passkey. In most production systems, you'll want additional verification before creating accounts—email confirmation, admin approval, invitation codes, or CAPTCHA validation.

For this reason, the recommended approach is:

  1. Create user accounts through your existing registration flow (with whatever verification you need)
  2. Let users add passkeys to their verified accounts using the Add Passkey flow

The standalone registration endpoints are available when you need them (set EnableRegister: true), and the complete example demonstrates both approaches.

2. Add Passkey (Existing User)

For users who already have an account (maybe they logged in with password) and want to add a passkey. These endpoints require authentication.

mermaid
flowchart LR
+    subgraph Step1["Step 1: Get Challenge"]
+        direction TB
+        R1["POST /api/passkey/add/options
+        🔐 Requires JWT
+        ────────────────────────────
+        Headers: Authorization: Bearer
+        Body (optional):
+        {
+          deviceName?: string
+        }"]
+
+        F1["passkey_challenge_add_existing($1, $2)
+        ────────────────────────────
+        $1 = JWT claims (JSON)
+        $2 = body JSON
+
+        Returns:
+        • status, challenge, challenge_id
+        • user_handle, user_name
+        • exclude_credentials, user_context"]
+
+        RES1["Response:
+        {
+          challenge, challengeId
+          user: {id, name, displayName}
+          excludeCredentials: [...existing]
+          userContext
+        }"]
+
+        R1 --> F1 --> RES1
+    end
+
+    subgraph Browser["Browser WebAuthn"]
+        WA["navigator.credentials.create()
+        ────────────────────────────
+        User creates passkey
+        (biometric/PIN)"]
+    end
+
+    subgraph Step2["Step 2: Complete Add"]
+        direction TB
+        R2["POST /api/passkey/add
+        🔐 Requires JWT
+        ────────────────────────────
+        Body:
+        {
+          challengeId, credentialId ✓
+          attestationObject ✓
+          clientDataJSON ✓
+          transports?, userContext ✓
+        }"]
+
+        F2["① passkey_verify_challenge($1, $2)
+           $1=challengeId, $2='registration'
+           Returns: challenge bytea
+
+        ② passkey_complete_add_existing(...)
+           $1=credentialId, $2=userHandle
+           $3=publicKey, $4=algorithm
+           $5=transports, $6=backupEligible
+           $7=userContext
+           Returns: status, message"]
+
+        RES2["Response:
+        {
+          success: true
+          credentialId: base64url
+        }"]
+
+        R2 --> F2 --> RES2
+    end
+
+    Step1 --> Browser --> Step2
Endpoint (requires auth)Executes SQL Command
POST /api/passkey/add/optionsChallengeAddExistingUserCommand
POST /api/passkey/addCompleteAddExistingUserCommand

3. Login

For authenticating with an existing passkey.

mermaid
flowchart LR
+    subgraph Step1["Step 1: Get Challenge"]
+        direction TB
+        R1["POST /api/passkey/login/options
+        ────────────────────────────
+        Body (optional):
+        {
+          userName?: string
+        }
+        Empty = discoverable credentials"]
+
+        F1["passkey_challenge_authentication($1, $2)
+        ────────────────────────────
+        $1 = userName (nullable)
+        $2 = body JSON
+
+        Returns:
+        • status, challenge, challenge_id
+        • allow_credentials"]
+
+        RES1["Response:
+        {
+          challenge, challengeId
+          rpId, timeout
+          userVerification
+          allowCredentials?
+        }"]
+
+        R1 --> F1 --> RES1
+    end
+
+    subgraph Browser["Browser WebAuthn"]
+        WA["navigator.credentials.get()
+        ────────────────────────────
+        User authenticates
+        (biometric/PIN)"]
+    end
+
+    subgraph Step2["Step 2: Complete Login"]
+        direction TB
+        R2["POST /api/passkey/login
+        ────────────────────────────
+        Body:
+        {
+          challengeId, credentialId ✓
+          authenticatorData ✓
+          clientDataJSON ✓
+          signature ✓
+          userHandle?
+        }"]
+
+        F2["① passkey_verify_challenge($1, $2)
+           $1=challengeId, $2='authentication'
+           Returns: challenge bytea
+
+        ② passkey_authenticate_data($1)
+           $1=credentialId
+           Returns: public_key, sign_count
+                    user_context
+
+        ③ passkey_complete_authenticate(...)
+           $1=credentialId, $2=newSignCount
+           $3=userContext
+           Returns: scheme, user_id
+                    username, email"]
+
+        RES2["Response:
+        {
+          accessToken: JWT
+          refreshToken?: JWT
+          expiresIn: number
+        }
+        OR Set-Cookie"]
+
+        R2 --> F2 --> RES2
+    end
+
+    Step1 --> Browser --> Step2
EndpointExecutes SQL Command
POST /api/passkey/login/optionsChallengeAuthenticationCommand
POST /api/passkey/loginCompleteAuthenticateCommand

Complete Configuration Reference

This section provides a detailed reference for all PasskeyAuth configuration options and SQL commands.

General Settings

SettingDefaultDescription
EnabledfalseMaster switch to enable passkey authentication
EnableRegisterfalseEnable standalone registration (new users can sign up with passkey only)
RateLimiterPolicynullName of a configured rate limiter policy to protect against brute-force
ConnectionNamenullNamed connection for multi-database setups; uses default if null
CommandRetryStrategy"default"Retry strategy for transient database errors; set to null to disable

Relying Party Settings

The Relying Party (RP) identifies your application to the authenticator.

SettingDefaultDescription
RelyingPartyIdnullDomain name (e.g., "example.com"). Auto-detected from request if null. Note: IP addresses are not permitted—use "localhost" for development
RelyingPartyNamenullHuman-readable name shown during registration. Uses ApplicationName if null
RelyingPartyOrigins[]Allowed origins for validation (e.g., ["https://example.com"]). Auto-detected if empty

Endpoint Paths

All paths are POST endpoints. Set to null to disable an endpoint.

SettingDefaultDescription
AddPasskeyOptionsPath"/api/passkey/add/options"Get options for adding passkey to existing user (requires auth)
AddPasskeyPath"/api/passkey/add"Complete adding passkey to existing user (requires auth)
RegistrationOptionsPath"/api/passkey/register/options"Get options for new user registration (no auth required)
RegistrationPath"/api/passkey/register"Complete new user registration (no auth required)
LoginOptionsPath"/api/passkey/login/options"Get login challenge (no auth required)
LoginPath"/api/passkey/login"Complete authentication (no auth required)

WebAuthn Settings

SettingDefaultDescription
ChallengeTimeoutMinutes5How long challenges remain valid before expiring
ValidateSignCounttrueValidate signature counter to detect cloned authenticators
UserVerificationRequirement"required"See below
ResidentKeyRequirement"required"See below
AttestationConveyance"none"See below

UserVerificationRequirement

Controls whether biometric/PIN verification is required:

ValueBehaviorUse Case
"required"User MUST verify with biometric or PINBanking, healthcare, any sensitive data
"preferred"Request verification if available, proceed without if notMost consumer apps
"discouraged"Don't request verification (proves device possession only)Low-security scenarios

ResidentKeyRequirement

Controls discoverable credentials (true passwordless):

ValueBehaviorUse Case
"required"Credential stored on authenticator; browser shows account pickerTrue passwordless (no username field)
"preferred"Request discoverable if supportedGradual migration to passwordless
"discouraged"Server must provide credential IDUsername-first flows

AttestationConveyance

Controls whether to verify authenticator hardware:

ValueBehaviorUse Case
"none"Accept any authenticatorMost apps (recommended)
"indirect"Allow anonymized attestationRarely useful
"direct"Request full attestation chainVerify specific hardware models
"enterprise"Enterprise-managed attestationCorporate device policies

SQL Commands Reference

NpgsqlRest calls your SQL functions at specific points in each flow. Here's when each command is executed and what it should return.

ChallengeAddExistingUserCommand

When executed: User clicks "Add Passkey" in their account settings (they're already logged in)

Endpoint: POST /api/passkey/add/options

Parameters:

Expected return columns:

ColumnTypeDescription
statusintHTTP status code. Return 200 to proceed, any other aborts
messagetextError message when status ≠ 200
challengetextBase64-encoded random bytes (32 bytes recommended)
challenge_idbigint/uuid/textServer-side identifier to verify later
user_handletextBase64-encoded random bytes for WebAuthn user.id
user_nametextUsername shown in authenticator UI
user_display_nametextDisplay name shown in authenticator UI
exclude_credentialstextJSON array of existing credential IDs to prevent re-registration
user_contextjsonPassed through to completion command (should contain user ID)

Example:

sql
sql
create or replace function passkey_challenge_add_existing(
+    _claims json,
+    _body json
+)
+returns table (
+    status int, message text, challenge text, challenge_id bigint,
+    user_handle text, user_name text, user_display_name text,
+    exclude_credentials text, user_context json
+)
+language plpgsql as $$
+declare
+    _user_id int = (_claims->>'user_id')::int;
+    _challenge bytea = gen_random_bytes(32);
+    _challenge_id bigint;
+    _existing_handle bytea;
+begin
+    -- Get existing user handle (or create new one)
+    select user_handle into _existing_handle
+    from passkeys where user_id = _user_id limit 1;
+
+    if _existing_handle is null then
+        _existing_handle = gen_random_bytes(32);
+    end if;
+
+    -- Store challenge
+    insert into passkey_challenges (challenge, user_id, operation, expires_at)
+    values (_challenge, _user_id, 'registration', now() + interval '5 minutes')
+    returning id into _challenge_id;
+
+    return query
+    select 200, null::text,
+        encode(_challenge, 'base64'),
+        _challenge_id,
+        encode(_existing_handle, 'base64'),
+        _claims->>'username',
+        _claims->>'username',
+        (select coalesce(jsonb_agg(jsonb_build_object(
+            'type', 'public-key',
+            'id', encode(credential_id, 'base64')
+        )), '[]'::jsonb)::text from passkeys where user_id = _user_id),
+        json_build_object('id', _user_id, 'deviceName', _body->>'deviceName');
+end;
+$$;

ChallengeRegistrationCommand

When executed: New user starts passkey-only registration (no existing account)

Endpoint: POST /api/passkey/register/options

Parameters:

Expected return columns: Same as ChallengeAddExistingUserCommand

Key difference: The user_context should NOT contain an id field—this tells the completion command to create a new user.


ChallengeAuthenticationCommand

When executed: User initiates passkey login

Endpoint: POST /api/passkey/login/options

Parameters:

Expected return columns:

ColumnTypeDescription
statusintHTTP status code (200 to proceed)
messagetextError message when status ≠ 200
challengetextBase64-encoded random challenge
challenge_idbigint/uuid/textServer-side identifier
allow_credentialstextJSON array of credential IDs for this user (empty for discoverable)

Example:

sql
sql
create or replace function passkey_challenge_authentication(
+    _user_name text,
+    _body json
+)
+returns table (
+    status int, message text, challenge text,
+    challenge_id bigint, allow_credentials text
+)
+language plpgsql as $$
+declare
+    _challenge bytea = gen_random_bytes(32);
+    _challenge_id bigint;
+    _user_id int;
+begin
+    -- If username provided, look up user
+    if _user_name is not null and _user_name <> '' then
+        select user_id into _user_id from users where username = _user_name;
+        if _user_id is null then
+            return query select 400, 'Bad request'::text,
+                null::text, null::bigint, null::text;
+            return;
+        end if;
+    end if;
+
+    -- Store challenge
+    insert into passkey_challenges (challenge, user_id, operation, expires_at)
+    values (_challenge, _user_id, 'authentication', now() + interval '5 minutes')
+    returning id into _challenge_id;
+
+    return query
+    select 200, null::text,
+        encode(_challenge, 'base64'),
+        _challenge_id,
+        coalesce((
+            select jsonb_agg(jsonb_build_object(
+                'type', 'public-key',
+                'id', encode(credential_id, 'base64'),
+                'transports', transports
+            ))::text from passkeys where user_id = _user_id
+        ), '[]');
+end;
+$$;

VerifyChallengeCommand

When executed: After browser returns credential, before cryptographic verification

Used by: ALL flows (add passkey, registration, and login)

Parameters:

Expected return: Single column challenge (bytea) containing the original challenge bytes, or NULL if not found/expired

Example:

sql
sql
create or replace function passkey_verify_challenge(
+    _challenge_id bigint,
+    _operation text
+)
+returns bytea
+language plpgsql as $$
+declare
+    _challenge bytea;
+begin
+    -- Delete and return the challenge (one-time use)
+    delete from passkey_challenges
+    where id = _challenge_id
+      and operation = _operation
+      and expires_at > now()
+    returning challenge into _challenge;
+
+    return _challenge;
+end;
+$$;

AuthenticateDataCommand

When executed: During login, after challenge verification but before signature verification

Endpoint: POST /api/passkey/login

Parameters:

Expected return columns:

ColumnTypeDescription
statusintHTTP status code (200 to proceed)
messagetextError message when status ≠ 200
public_keybyteaThe stored public key for signature verification
public_key_algorithmintCOSE algorithm ID (-7 for ES256, -257 for RS256)
sign_countbigintCurrent signature counter
user_contextjsonPassed to CompleteAuthenticateCommand (typically contains user ID)

Example:

sql
sql
create or replace function passkey_authenticate_data(_credential_id bytea)
+returns table (
+    status int, message text, public_key bytea,
+    public_key_algorithm int, sign_count bigint, user_context json
+)
+language plpgsql as $$
+begin
+    return query
+    select 200, null::text,
+        p.public_key,
+        p.public_key_algorithm,
+        p.sign_count,
+        json_build_object('id', p.user_id)
+    from passkeys p
+    where p.credential_id = _credential_id;
+
+    if not found then
+        return query select 400, 'Bad request'::text,
+            null::bytea, null::int, null::bigint, null::json;
+    end if;
+end;
+$$;

CompleteAddExistingUserCommand

When executed: After successful attestation verification when adding passkey to existing user

Endpoint: POST /api/passkey/add

Parameters:

ParameterTypeDescription
$1byteacredential_id - Unique credential identifier
$2byteauser_handle - WebAuthn user.id
$3byteapublic_key - Public key in COSE format
$4intalgorithm - COSE algorithm (-7 = ES256, -257 = RS256)
$5text[]transports - Transport hints (e.g., ["internal", "hybrid"])
$6booleanbackup_eligible - Whether credential can be synced
$7jsonuser_context - From ChallengeAddExistingUserCommand
$8jsonanalytics_data - Optional client analytics with server-added IP

Expected return columns:

ColumnTypeDescription
statusintHTTP status code (200 = success)
messagetextError message when status ≠ 200

CompleteRegistrationCommand

When executed: After successful attestation verification for new user registration

Endpoint: POST /api/passkey/register

Parameters: Same as CompleteAddExistingUserCommand

Expected return columns: Same as CompleteAddExistingUserCommand

Key difference: This command should CREATE a new user since user_context doesn't contain an existing user ID.


CompleteAuthenticateCommand

When executed: After successful signature verification during login

Endpoint: POST /api/passkey/login

Parameters:

ParameterTypeDescription
$1byteacredential_id - The credential that was used
$2bigintnew_sign_count - Updated signature counter
$3jsonuser_context - From AuthenticateDataCommand
$4jsonanalytics_data - Optional client analytics

Expected return columns:

The return columns depend on your authentication scheme. For cookie authentication:

ColumnTypeDescription
schemetextAuthentication scheme (e.g., "cookies")
Any claim columnsvariousColumns become claims (e.g., user_id, username, email)
messagejsonbOptional JSON returned in response body

Example:

sql
sql
create or replace function passkey_complete_authenticate(
+    _credential_id bytea,
+    _new_sign_count bigint,
+    _user_context json,
+    _analytics_data json default null
+)
+returns table (
+    scheme text, user_id int, username text, email text, message jsonb
+)
+language plpgsql as $$
+declare
+    _user_id int = (_user_context->>'id')::int;
+begin
+    -- Update sign count
+    update passkeys
+    set sign_count = _new_sign_count, last_used_at = now()
+    where credential_id = _credential_id;
+
+    -- Optional: Log authentication
+    if _analytics_data is not null then
+        insert into auth_audit_log (user_id, event_type, analytics_data, ip_address)
+        values (_user_id, 'passkey_login', _analytics_data, _analytics_data->>'ip');
+    end if;
+
+    -- Return claims for authentication
+    return query
+    select 'cookies', u.user_id, u.username, u.email,
+        jsonb_build_object('userId', u.user_id, 'username', u.username)
+    from users u where u.user_id = _user_id;
+end;
+$$;

Column Name Configuration

If your SQL functions use different column names, you can configure the mappings:

json
json
{
+  "PasskeyAuth": {
+    "StatusColumnName": "status",
+    "MessageColumnName": "message",
+    "ChallengeColumnName": "challenge",
+    "ChallengeIdColumnName": "challenge_id",
+    "UserNameColumnName": "user_name",
+    "UserDisplayNameColumnName": "user_display_name",
+    "UserHandleColumnName": "user_handle",
+    "ExcludeCredentialsColumnName": "exclude_credentials",
+    "AllowCredentialsColumnName": "allow_credentials",
+    "PublicKeyColumnName": "public_key",
+    "PublicKeyAlgorithmColumnName": "public_key_algorithm",
+    "SignCountColumnName": "sign_count",
+    "UserContextColumnName": "user_context"
+  }
+}

Analytics Data

You can collect client-side analytics by passing analyticsData in completion requests. NpgsqlRest automatically adds the client's IP address:

json
json
{
+  "PasskeyAuth": {
+    "ClientAnalyticsIpKey": "ip"
+  }
+}

Set to null or empty string to disable IP collection.

Security Considerations

What NpgsqlRest Validates

NpgsqlRest performs all the cryptographic verification required by WebAuthn:

What You Control

Your SQL functions control the business logic:

Rate Limiting

Always enable rate limiting on passkey endpoints:

json
json
{
+  "PasskeyAuth": {
+    "RateLimiterPolicy": "passkey-limit"
+  },
+  "RateLimiting": {
+    "Policies": {
+      "passkey-limit": {
+        "Type": "SlidingWindow",
+        "PermitLimit": 10,
+        "WindowSeconds": 60
+      }
+    }
+  }
+}

Advantages of This Approach

1. SQL-First Logic

Your authentication flow is defined in PostgreSQL functions. This means:

2. No External Dependencies

No FIDO2 libraries, no external authentication services. NpgsqlRest includes its own CBOR parser and cryptographic verification.

3. Complete Control

You decide:

4. Built-in Resilience

The passkey endpoints support:

5. Privacy by Design

Since you only store public keys:

Getting Started

  1. Create the schema: Set up your users, passkeys, and challenges tables
  2. Implement the SQL functions: Challenge creation, verification, completion
  3. Configure PasskeyAuth: Enable it and point to your functions
  4. Add client code: Use the provided passkey.ts as a starting point
  5. Enable rate limiting: Protect against brute-force attacks

The complete example includes everything you need: schema, SQL functions, configuration, and client code. For all configuration options, see the Passkey Authentication Configuration reference.

Conclusion

If you take one recommendation from this post: keep EnableRegister off, let users add passkeys to accounts created through your existing verified registration flow, and put a rate limiter on the endpoints. The rest—challenge storage, claims, audit logging—is ordinary SQL, version-controlled next to your schema.

`,192)),p(i,{"get-started":[{text:"Passkey Configuration Reference",href:"/config/passkey-auth"},{text:"Complete Passkey Example",href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/13_passkey"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const C=a(h,[["render",r]]);export{u as __pageData,C as default}; diff --git a/assets/blog_passkey-sql-auth.md.BRkr8TRo.lean.js b/assets/blog_passkey-sql-auth.md.BRkr8TRo.lean.js new file mode 100644 index 000000000..6af6e04af --- /dev/null +++ b/assets/blog_passkey-sql-auth.md.BRkr8TRo.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as e,o as l,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest. Full SQL-based passkey registration, login, and credential management.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest. Full SQL-based passkey registration, login, and credential management.","head":[["meta",{"name":"keywords","content":"webauthn passkey postgresql, passwordless authentication sql, passkey implementation postgres, npgsqlrest webauthn, sql authentication passkey, fido2 postgresql"}],["meta",{"property":"og:title","content":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"}],["meta",{"property":"og:description","content":"Learn how to implement passwordless WebAuthn passkey authentication entirely in PostgreSQL using NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"}],["meta",{"name":"twitter:description","content":"Passwordless WebAuthn passkey authentication implemented entirely in PostgreSQL with NpgsqlRest."}]]},"headers":[],"relativePath":"blog/passkey-sql-auth.md","filePath":"blog/passkey-sql-auth.md"}'),h={name:"blog/passkey-sql-auth.md"};function r(k,s,d,c,o,F){const i=n("BlogNav");return l(),e("div",null,[s[0]||(s[0]=t("",192)),p(i,{"get-started":[{text:"Passkey Configuration Reference",href:"/config/passkey-auth"},{text:"Complete Passkey Example",href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/13_passkey"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const C=a(h,[["render",r]]);export{u as __pageData,C as default}; diff --git a/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.js b/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.js new file mode 100644 index 000000000..907d0984d --- /dev/null +++ b/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.js @@ -0,0 +1,496 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Performance, Scalability, and High Availability with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Production-ready API configuration: response caching, retry logic, rate limiting, PostgreSQL multi-host failover, and load balancing. Complete guide with examples.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Performance, Scalability, and High Availability with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Production-ready API configuration: response caching, retry logic, rate limiting, PostgreSQL multi-host failover, and load balancing. Complete guide with examples.","head":[["meta",{"name":"keywords","content":"postgresql api caching, api rate limiting, postgresql high availability, postgresql load balancing, npgsql failover, api retry logic, postgresql connection pooling, production api"}],["meta",{"property":"og:title","content":"Performance, Scalability, and High Availability with NpgsqlRest"}],["meta",{"property":"og:description","content":"Production-ready API config: caching, retry logic, rate limiting, PostgreSQL multi-host failover."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Performance & High Availability with NpgsqlRest"}],["meta",{"name":"twitter:description","content":"Production-ready API: caching, rate limiting, PostgreSQL failover and load balancing."}]]},"headers":[],"relativePath":"blog/performance-scalability-high-availability-npgsqlrest.md","filePath":"blog/performance-scalability-high-availability-npgsqlrest.md"}'),h={name:"blog/performance-scalability-high-availability-npgsqlrest.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l(`

Performance, Scalability, and High Availability with NpgsqlRest

Performance · Scalability · High Availability · January 2026


A functionally correct API is the easy part. Production adds the rest: caching to reduce load, retry logic to handle transient failures, rate limiting to protect your infrastructure, and high availability configuration to survive a server going down.

NpgsqlRest has built-in support for all of these. This guide walks through each feature with practical examples.

Functions or SQL files — same annotations, different placement

Every annotation in this post works identically with PostgreSQL functions (via comment on function ... is '...') and with SQL files (via -- @annotation header comments in a .sql file). Same semantics, same defaults, same ordering rules — only the comment placement differs. Examples below use both forms; pick whichever fits your codebase.

The same endpoint as a function and as a SQL file:

sql
sql
-- As a function
+create function get_settings() returns json language sql
+begin atomic; select settings from app_config where id = 1; end;
+
+comment on function get_settings() is 'HTTP GET
+@cached
+@cache_expires_in 1 hour';
sql
sql
-- As a SQL file: sql/get-settings.sql
+-- HTTP GET
+-- @cached
+-- @cache_expires_in 1 hour
+select settings from app_config where id = 1;

Both produce GET /api/get-settings with a one-hour cached response. SQL files need the SqlFileSource plugin enabled in config ("NpgsqlRest": { "SqlFileSource": { "Enabled": true, "FilePattern": "sql/**/*.sql" } }) — see SQL File Source for the full reference.

Caching Strategies

Caching is your first line of defense against unnecessary database load. NpgsqlRest supports two complementary caching layers: HTTP caching (browser/CDN level) and server-side caching (application level).

HTTP Cache Headers: The Fastest Cache

The fastest request is the one that never reaches your server. HTTP caching via Cache-Control headers lets browsers and CDNs serve responses without touching your infrastructure at all.

When a browser or CDN has a cached response that hasn't expired, your server receives zero requests. This is fundamentally different from server-side caching—with HTTP caching, there's no network round-trip, no connection pool usage, nothing.

Setting Cache Headers in Annotations

NpgsqlRest can set any HTTP response header directly from comment annotations using the Header-Name: value format:

sql
sql
create function get_product_catalog()
+returns json
+language sql
+begin atomic;
+select json_agg(p) from products p where active;
+end;
+
+comment on function get_product_catalog() is
+'HTTP GET
+Cache-Control: public, max-age=3600';

Or as a SQL file:

sql
sql
-- sql/get-product-catalog.sql
+-- HTTP GET
+-- Cache-Control: public, max-age=3600
+select json_agg(p) from products p where active;

This tells browsers and CDNs to cache the response for 1 hour (3600 seconds). You can combine multiple headers:

sql
sql
comment on function get_static_config() is
+'HTTP GET
+Cache-Control: public, max-age=86400
+ETag: "v1.0"
+Vary: Accept-Encoding';

Common Cache-Control directives:

DirectiveMeaning
publicCan be cached by browsers and CDNs
privateOnly browser can cache, not CDNs
max-age=NCache for N seconds
no-cacheMust revalidate before using cached copy
no-storeNever cache

For authenticated endpoints, use private to prevent CDNs from serving one user's data to another:

sql
sql
comment on function get_user_dashboard() is
+'HTTP GET
+@authorize
+Cache-Control: private, max-age=300';

Cache Busting Technique

The challenge with aggressive HTTP caching is invalidation. How do you force clients to fetch fresh data when the underlying data changes?

The standard technique is cache busting via URL parameters. Add a version or timestamp parameter that changes when data is updated:

code
GET /api/products?v=1
+GET /api/products?v=2    # After data update - treated as new URL

The parameter doesn't need to do anything server-side—it simply makes the URL unique, causing browsers and CDNs to treat it as a completely different resource. Your function can ignore it entirely:

sql
sql
create function get_products(_v text default null)
+returns json
+language sql
+begin atomic;
+    select json_agg(p) from products p;
+end;
+
+comment on function get_products(text) is
+'HTTP GET
+Cache-Control: public, max-age=31536000';

The _v parameter exists only to differentiate cache keys. When you update your products, change v=1 to v=2 in your client code, and every user gets fresh data. With this pattern, you can set very long cache times (the example uses 1 year) because you control invalidation through URL changes.

This technique pays off most when combined with CDNs like Cloudflare or CloudFront, which cache at edge locations globally.

Server-Side Caching

When HTTP caching isn't sufficient—perhaps you need more control over invalidation, or you're dealing with authenticated endpoints that can't be cached by CDNs—NpgsqlRest provides built-in server-side caching.

Enabling Server Cache

Enable caching in your configuration:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Memory"
+  }
+}

Then annotate specific endpoints:

sql
sql
create function get_app_settings()
+returns json
+language sql
+begin atomic;
+select settings from app_config where id = 1;
+end;
+
+comment on function get_app_settings() is
+'HTTP GET
+@cached';

Or as a SQL file:

sql
sql
-- sql/get-app-settings.sql
+-- HTTP GET
+-- @cached
+select settings from app_config where id = 1;

When a cached endpoint is hit and the cache is warm, no database connection is opened. This is critical for high-traffic endpoints—you're not just saving database CPU cycles, you're preserving your connection pool for requests that actually need it.

Cache Keys by Parameter

For endpoints with parameters, specify which parameters form the cache key:

sql
sql
create function get_user_profile(_user_id int)
+returns json
+language sql
+begin atomic;
+select row_to_json(u) from users u where id = _user_id;
+end;
+
+comment on function get_user_profile(int) is
+'HTTP GET
+@cached _user_id';

Or as a SQL file — note that positional parameters need @param to get a readable name:

sql
sql
-- sql/get-user-profile.sql
+-- HTTP GET
+-- @param $1 user_id int
+-- @cached user_id
+select row_to_json(u) from users u where id = $1;

Different _user_id values create separate cache entries. Requests for user 1 and user 2 are cached independently.

For multiple parameters:

sql
sql
comment on function get_report(int, text) is
+'HTTP GET
+@cached _year, _department';

Cache Expiration

Control how long entries stay cached:

sql
sql
comment on function get_dashboard_stats() is
+'HTTP GET
+@cached
+@cache_expires_in 5m';

Supported formats: 10s (seconds), 5m (minutes), 1h (hour), 1d (day), 1w (week).

Cache Types

NpgsqlRest supports three cache backends, each suited for different deployment scenarios.

Memory Cache

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Memory",
+    "MemoryCachePruneIntervalSeconds": 60
+  }
+}

Best for:

Limitation: Each application instance maintains its own cache. If you run multiple instances, they won't share cached data.

Redis Cache

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Redis",
+    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false"
+  }
+}

Best for:

Hybrid Cache

The most sophisticated option, using Microsoft's HybridCache:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Hybrid",
+    "HybridCacheUseRedisBackend": true,
+    "RedisConfiguration": "localhost:6379,abortConnect=false",
+    "HybridCacheDefaultExpiration": "5 minutes",
+    "HybridCacheLocalCacheExpiration": "1 minute"
+  }
+}

Hybrid cache provides:

Without stampede protection, when a popular cached entry expires, every concurrent request tries to refresh it simultaneously—potentially overwhelming your database. Hybrid cache ensures only one request fetches fresh data while others wait.

You can use Hybrid cache without Redis for stampede protection alone:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Hybrid",
+    "HybridCacheUseRedisBackend": false
+  }
+}

Cache Invalidation Endpoints

NpgsqlRest can automatically create invalidation endpoints for programmatic cache clearing:

json
json
{
+  "CacheOptions": {
+    "Enabled": true,
+    "InvalidateCacheSuffix": "invalidate"
+  }
+}

Usage:

code
GET /api/get-user/?id=123           -> Returns cached user data
+GET /api/get-user/invalidate?id=123 -> Clears cache entry
+GET /api/get-user/?id=123           -> Fresh data from database

The invalidation endpoint:

Use it to invalidate cache right after data modifications instead of waiting for expiration.

Caching Set-Returning Functions

Server-side caching works for functions returning multiple rows:

sql
sql
create function get_all_users()
+returns table(id int, name text)
+language sql
+begin atomic;
+select id, name from users;
+end;
+
+comment on function get_all_users() is
+'HTTP GET
+@cached
+@cache_expires_in 5 minutes';

Protect against caching excessively large result sets:

json
json
{
+  "CacheOptions": {
+    "MaxCacheableRows": 1000
+  }
+}

Results exceeding this limit are returned but not cached—preventing memory issues from unexpectedly large queries.

Cache Profiles

A single root cache type isn't always enough. You might want fast per-user data in Memory, shared session data in Redis, and historical analytics with a long TTL on a third backend — all in one application. Cache profiles let you register multiple named caching policies, each with its own backend, default expiration, key shape, and conditional rules, and let endpoints opt into them with the @cache_profile annotation.

jsonc
jsonc
{
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Memory",
+    "Profiles": {
+      "fast_memory": {
+        "Enabled": true,
+        "Type": "Memory",
+        "Expiration": "1 minute",
+        "Parameters": ["user_id"]
+      },
+      "shared_redis": {
+        "Enabled": true,
+        "Type": "Redis",
+        "Expiration": "1 hour"
+      },
+      "timeseries": {
+        "Enabled": true,
+        "Type": "Memory",
+        "Expiration": "1 hour",
+        "Parameters": ["from", "to", "live"],
+        "When": [
+          { "Parameter": "live", "Value": true, "Then": "skip" },
+          { "Parameter": "to",   "Value": null, "Then": "5 minutes" }
+        ]
+      }
+    }
+  }
+}
sql
sql
-- Per-user 1-minute cache via the fast Memory profile
+comment on function get_my_dashboard(_user_id int) is
+'HTTP GET
+@authorize
+@cache_profile fast_memory';
+
+-- 1-hour distributed cache via Redis
+comment on function get_global_metrics() is
+'HTTP GET
+@cache_profile shared_redis';
+
+-- Long cache for historical queries; short cache for "until-now";
+-- bypass entirely when ?live=true
+comment on function compute_timeseries(_from text, _to text default null, _live boolean default false) is
+'HTTP GET
+@cache_profile timeseries';

The same timeseries profile applied to a SQL file — the profile's When rules don't care whether the endpoint is a function or a file, they just inspect the resolved parameter values at request time:

sql
sql
-- sql/compute-timeseries.sql
+-- HTTP GET
+-- @param $1 from text
+-- @param $2 to   text    default null
+-- @param $3 live boolean default false
+-- @cache_profile timeseries
+select * from timeseries_data($1, $2)
+where ($3 = false or now() - interval '1 minute' < $2::timestamp);

Three things to know:

Endpoints without @cache_profile continue to use the root cache, so profiles are purely additive. See Cache Profiles for the full reference.

Retry Strategies

Transient failures are inevitable in distributed systems. Database connections drop, servers restart, deadlocks occur. NpgsqlRest provides two levels of retry handling: connection retries and command retries.

Connection Retries

Connection retry handles failures when establishing a database connection:

json
json
{
+  "ConnectionSettings": {
+    "RetryOptions": {
+      "Enabled": true,
+      "RetrySequenceSeconds": [1, 3, 6, 12],
+      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
+    }
+  }
+}

The RetrySequenceSeconds array defines delays between attempts:

Default error codes cover common transient scenarios:

CodeDescription
08000General connection error
08003Connection lost
08006Connection failed
53300Too many connections
57P03Server starting up
40001Serialization failure

For high-availability deployments where brief connection issues are expected during failovers:

json
json
{
+  "ConnectionSettings": {
+    "RetryOptions": {
+      "Enabled": true,
+      "RetrySequenceSeconds": [0.5, 1, 2, 4, 8, 16, 32],
+      "ErrorCodes": ["08000", "08003", "08006", "57P03"]
+    }
+  }
+}

Command Retries

Command retry handles failures during query execution—after the connection is established:

json
json
{
+  "CommandRetryOptions": {
+    "Enabled": true,
+    "DefaultStrategy": "default",
+    "Strategies": {
+      "default": {
+        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
+        "ErrorCodes": [
+          "40001", "40P01",
+          "08000", "08003", "08006", "08001", "08004",
+          "53000", "53100", "53200", "53300", "53400",
+          "57P01", "57P02", "57P03",
+          "55P03", "55006", "55000"
+        ]
+      }
+    }
+  }
+}

Note the first retry is 0 (immediate)—for serialization failures and deadlocks, immediate retry often succeeds because the conflict is resolved.

Multiple Retry Strategies

Define different strategies for different workloads:

json
json
{
+  "CommandRetryOptions": {
+    "Enabled": true,
+    "DefaultStrategy": "default",
+    "Strategies": {
+      "default": {
+        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
+      },
+      "aggressive": {
+        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
+      },
+      "minimal": {
+        "RetrySequenceSeconds": [0, 1],
+        "ErrorCodes": ["40001", "40P01"]
+      }
+    }
+  }
+}

Assign strategies per endpoint:

sql
sql
-- Critical payment processing - aggressive retries
+comment on function process_payment() is
+'HTTP POST
+@retry_strategy aggressive';
+
+-- Fast lookup - minimal retries to fail fast
+comment on function quick_lookup() is
+'HTTP GET
+@retry_strategy minimal';

The same as SQL files:

sql
sql
-- sql/process-payment.sql
+-- HTTP POST
+-- @retry_strategy aggressive
+call process_payment_tx($1, $2);
sql
sql
-- sql/quick-lookup.sql
+-- HTTP GET
+-- @param $1 id int
+-- @retry_strategy minimal
+select * from lookup_table where id = $1;

PostgreSQL Error Code Classes

Understanding error codes helps you configure appropriate retry behavior:

ClassCodesDescription
4040001, 40P01Serialization failures, deadlocks—always retry
0808000-08P01Connection issues—retry with backoff
5353000-53400Resource constraints (connections, memory, disk)
5757P01-57P03Operator intervention (shutdown, restart)
5555P03, 55006Lock contention

The full list is available in the PostgreSQL Error Codes documentation.

Rate Limiting

Rate limiting protects your API from abuse and keeps one client from starving the rest. NpgsqlRest integrates ASP.NET Core's rate limiting middleware with four policy types.

Enabling Rate Limiting

json
json
{
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "StatusCode": 429,
+    "StatusMessage": "Too many requests. Please try again later.",
+    "DefaultPolicy": null,
+    "Policies": {}
+  }
+}

Breaking change in 3.13.0

RateLimiterOptions:Policies is now an object keyed by policy name, not an array of objects with a "Name" field. Earlier versions of this post showed the array form — if you copied from there, migrate to the keyed-object form below. Old configs fail at startup with a clear InvalidOperationException.

Fixed Window

Limits requests within fixed time intervals:

json
json
{
+  "Policies": {
+    "fixed": {
+      "Type": "FixedWindow",
+      "Enabled": true,
+      "PermitLimit": 100,
+      "WindowSeconds": 60,
+      "QueueLimit": 10
+    }
+  }
+}

100 requests allowed per 60-second window. When the limit is reached, up to 10 additional requests queue and wait for the next window.

Apply to endpoints:

sql
sql
comment on function public_api() is
+'HTTP GET
+@rate_limiter_policy fixed';

Sliding Window

Smoother rate limiting using overlapping segments:

json
json
{
+  "Policies": {
+    "sliding": {
+      "Type": "SlidingWindow",
+      "Enabled": true,
+      "PermitLimit": 100,
+      "WindowSeconds": 60,
+      "SegmentsPerWindow": 6
+    }
+  }
+}

The window is divided into 6 segments (10 seconds each). As time passes, old segments expire gradually rather than all at once—preventing burst traffic at window boundaries.

Token Bucket

Allows controlled bursting while maintaining overall rate:

json
json
{
+  "Policies": {
+    "bucket": {
+      "Type": "TokenBucket",
+      "Enabled": true,
+      "TokenLimit": 100,
+      "TokensPerPeriod": 10,
+      "ReplenishmentPeriodSeconds": 10
+    }
+  }
+}

The bucket holds up to 100 tokens. Every 10 seconds, 10 tokens are added. A burst of 100 requests is allowed, but sustained rate is limited to 1 request per second (10 tokens per 10 seconds).

Ideal for APIs where occasional bursts are acceptable but you want to prevent sustained abuse.

Concurrency Limiting

Limits simultaneous requests rather than rate:

json
json
{
+  "Policies": {
+    "concurrency": {
+      "Type": "Concurrency",
+      "Enabled": true,
+      "PermitLimit": 10,
+      "QueueLimit": 5,
+      "OldestFirst": true
+    }
+  }
+}

Only 10 requests can execute concurrently. Additional requests queue (up to 5) until a slot opens.

Use it for expensive operations where you want to cap database load regardless of request rate:

sql
sql
comment on function generate_large_report() is
+'HTTP POST
+@rate_limiter_policy concurrency';

Same thing as a SQL file:

sql
sql
-- sql/generate-large-report.sql
+-- HTTP POST
+-- @rate_limiter_policy concurrency
+select build_report_payload();

Per-User Rate Limiting (Partitions)

Out of the box, every request under a given policy shares a single global bucket — 100 requests per minute means 100 across all users, combined. That's rarely what you want for authenticated APIs, where one heavy user can starve everyone else.

A Partition block makes each request resolve its own bucket based on something from HttpContext: a claim, the client IP, a header, or a static fallback. The first source that resolves to a non-empty value wins.

jsonc
jsonc
"RateLimiterOptions": {
+  "Enabled": true,
+  "Policies": {
+    "per_user": {
+      "Type": "FixedWindow",
+      "Enabled": true,
+      "PermitLimit": 100,
+      "WindowSeconds": 60,
+      "Partition": {
+        "Sources": [
+          { "Type": "Claim", "Name": "name_identifier" },
+          { "Type": "IpAddress" },
+          { "Type": "Static", "Value": "anonymous" }
+        ]
+      }
+    },
+    "throttle_anon_only": {
+      "Type": "FixedWindow",
+      "Enabled": true,
+      "PermitLimit": 10,
+      "WindowSeconds": 60,
+      "Partition": {
+        "BypassAuthenticated": true,
+        "Sources": [{ "Type": "IpAddress" }]
+      }
+    }
+  }
+}

per_user gives each authenticated user their own 100-per-minute bucket, falls back to per-IP for anonymous requests, and lumps anything else into a shared anonymous bucket. throttle_anon_only waves signed-in users through entirely (BypassAuthenticated: true) and applies a stricter 10-per-minute limit per IP for everyone else — a common pattern for protecting unauthenticated endpoints from scraping.

Policies without a Partition block still use a single global bucket, so partitioning only kicks in where you ask for it.

Combining Policies

Different endpoints can use different policies:

sql
sql
-- Public API: strict rate limiting
+comment on function public_search() is
+'HTTP GET
+@rate_limiter_policy fixed';
+
+-- Authenticated users: more generous limits
+comment on function user_dashboard() is
+'HTTP GET
+@authorize
+@rate_limiter_policy sliding';
+
+-- Expensive operations: concurrency limited
+comment on function export_data() is
+'HTTP POST
+@authorize
+@rate_limiter_policy concurrency';

Thread Pool Optimization

Under high load, the .NET thread pool becomes a critical factor in API performance. By default, the thread pool starts with a small number of threads and grows slowly—adding only one thread every 500 milliseconds when all threads are busy. For high-throughput APIs handling thousands of concurrent requests, this gradual growth creates latency spikes during traffic bursts.

The Thread Injection Problem

When your API receives a burst of requests, here's what happens:

  1. The thread pool has its minimum number of threads (typically equal to CPU cores)
  2. All threads become busy handling requests
  3. New requests arrive but no threads are available
  4. The thread pool waits 500ms before creating a new thread
  5. This repeats for each additional thread needed

If you have 8 CPU cores and suddenly receive 100 concurrent requests, it could take 46 seconds ((100-8) × 0.5s) for the thread pool to grow large enough—during which time requests queue and latency degrades.

Configuring Minimum Threads

NpgsqlRest exposes thread pool settings so you can eliminate this cold-start penalty:

json
json
{
+  "ThreadPool": {
+    "MinWorkerThreads": 100,
+    "MinCompletionPortThreads": 100
+  }
+}

With MinWorkerThreads set to 100, the thread pool immediately has 100 threads available. New requests don't wait for thread injection—they execute immediately on pre-allocated threads.

Worker Threads vs Completion Port Threads

The thread pool manages two types of threads:

TypePurposeWhen to Increase
Worker ThreadsCPU-bound work, synchronous operationsHigh CPU utilization, synchronous code paths
Completion Port ThreadsAsync I/O operations (database queries, HTTP)Many concurrent async operations

For database APIs like NpgsqlRest, both matter:

High-Throughput Configuration

For APIs expecting thousands of concurrent requests:

json
json
{
+  "ThreadPool": {
+    "MinWorkerThreads": 200,
+    "MinCompletionPortThreads": 200,
+    "MaxWorkerThreads": 1000,
+    "MaxCompletionPortThreads": 1000
+  }
+}

This configuration:

Sizing Guidelines

There's no universal formula, but here are starting points:

Expected Concurrent RequestsMinWorkerThreadsMinCompletionPortThreads
Up to 505050
50-200100100
200-500200200
500-1000300300
1000+400-500400-500

Key considerations:

When NOT to Increase Thread Pool Size

Don't blindly increase thread counts. The defaults work well when:

Over-provisioning threads wastes memory and can hurt performance through excessive context switching. Always benchmark with realistic load before and after changes.

Example: Burst Traffic Handling

For an API that normally handles 50 concurrent requests but experiences bursts of 500:

json
json
{
+  "ThreadPool": {
+    "MinWorkerThreads": 100,
+    "MinCompletionPortThreads": 100,
+    "MaxWorkerThreads": 600,
+    "MaxCompletionPortThreads": 600
+  }
+}

This configuration:

Combined with the retry and caching strategies above, your API remains responsive even under unexpected load spikes.

High Availability

For production deployments, single-server databases are a single point of failure. NpgsqlRest uses Npgsql's multi-host connection support for failover and load balancing across PostgreSQL clusters.

Multi-Host Connections

Specify multiple hosts in your connection string:

json
json
{
+  "ConnectionStrings": {
+    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
+  }
+}

Npgsql tries hosts in order. If the primary fails, it automatically connects to the next available host.

Target Session Attributes

Control which server type handles connections:

json
json
{
+  "ConnectionSettings": {
+    "MultiHostConnectionTargets": {
+      "Default": "Any",
+      "ByConnectionName": {
+        "ReadOnly": "Standby",
+        "Primary": "Primary"
+      }
+    }
+  }
+}

Available targets:

TargetBehavior
AnyAny available server (default)
PrimaryOnly non-standby servers (for writes)
StandbyOnly hot standby servers (for reads)
PreferPrimaryPrimary if available, otherwise any
PreferStandbyStandby if available, otherwise any
ReadWriteMust accept read-write transactions
ReadOnlyMust not accept read-write transactions

Npgsql detects server role by querying pg_is_in_recovery(), which adds a small overhead to each connection. You can avoid this overhead by using separate named connections and specifying the connection directly in function annotations (covered below in Read Replica Routing).

Load Balancing

For distributing load across multiple servers of the same type, enable load balancing:

code
Host=replica1,replica2,replica3;Load Balance Hosts=true;Target Session Attributes=prefer-standby

With Load Balance Hosts=true, Npgsql rotates through the host list round-robin style—each new connection starts at a different position, distributing load evenly.

Read Replica Routing

A common pattern: write to primary, read from replicas. Instead of relying on Target Session Attributes (which queries pg_is_in_recovery() on each connection), you can define separate named connections pointing directly to your servers:

Configure multiple connection strings:

json
json
{
+  "ConnectionStrings": {
+    "Default": "Host=primary.db.com;Database=mydb;Username=app;Password=secret",
+    "ReadReplica": "Host=replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Load Balance Hosts=true"
+  },
+  "NpgsqlRest": {
+    "UseMultipleConnections": true
+  },
+  "ConnectionSettings": {
+    "MultiHostConnectionTargets": {
+      "Default": "Primary",
+      "ByConnectionName": {
+        "ReadReplica": "PreferStandby"
+      }
+    }
+  }
+}

Route read-heavy queries to replicas:

sql
sql
comment on function get_analytics_data() is
+'HTTP GET
+@connection ReadReplica';
+
+comment on function heavy_report() is
+'HTTP GET
+@connection_name ReadReplica';

Or as SQL files — @connection works the same way regardless of endpoint source:

sql
sql
-- sql/get-analytics-data.sql
+-- HTTP GET
+-- @connection ReadReplica
+select * from analytics_summary;

The connection annotation references the connection string name. The endpoint uses that connection instead of the default.

This approach is more efficient than multi-host connections with Target Session Attributes because:

Production High-Availability Configuration

A complete HA setup with failover, load balancing, caching, and retries:

json
json
{
+  "ConnectionStrings": {
+    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Pooling=true;Maximum Pool Size=100",
+    "ReadReplica": "Host=replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Load Balance Hosts=true;Pooling=true;Maximum Pool Size=50"
+  },
+  "ConnectionSettings": {
+    "TestConnectionStrings": true,
+    "RetryOptions": {
+      "Enabled": true,
+      "RetrySequenceSeconds": [0.5, 1, 2, 5, 10]
+    },
+    "MultiHostConnectionTargets": {
+      "Default": "PreferPrimary",
+      "ByConnectionName": {
+        "ReadReplica": "PreferStandby"
+      }
+    }
+  },
+  "NpgsqlRest": {
+    "UseMultipleConnections": true
+  },
+  "CommandRetryOptions": {
+    "Enabled": true,
+    "DefaultStrategy": "default",
+    "Strategies": {
+      "default": {
+        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5],
+        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "57P03"]
+      }
+    }
+  },
+  "CacheOptions": {
+    "Enabled": true,
+    "Type": "Hybrid",
+    "HybridCacheUseRedisBackend": true,
+    "RedisConfiguration": "redis-cluster:6379,abortConnect=false",
+    "HybridCacheDefaultExpiration": "5 minutes",
+    "InvalidateCacheSuffix": "invalidate",
+    "MaxCacheableRows": 1000
+  },
+  "RateLimiterOptions": {
+    "Enabled": true,
+    "DefaultPolicy": "standard",
+    "Policies": {
+      "standard": {
+        "Type": "SlidingWindow",
+        "Enabled": true,
+        "PermitLimit": 1000,
+        "WindowSeconds": 60,
+        "SegmentsPerWindow": 6
+      }
+    }
+  }
+}

This configuration:

Same Schema Requirement

When using multiple connections, ensure all databases share the same schema. NpgsqlRest builds endpoints from database metadata at startup—the function signatures must match across all connections.

This is naturally true for primary-replica setups (replicas are copies of the primary) but requires attention if using separate databases.

Putting It All Together

Here's how these features work together for a production API — mixing functions and SQL files in the same codebase, since both speak the same annotation language:

sql
sql
-- Function — frequently accessed, rarely changes, aggressive caching
+create function get_product_catalog()
+returns json
+language sql
+begin atomic;
+select json_agg(p) from products p where active;
+end;
+
+comment on function get_product_catalog() is
+'HTTP GET
+@cached
+@cache_expires_in 1h
+@connection ReadReplica';
sql
sql
-- SQL file — user-specific, moderate caching
+-- sql/get-user-orders.sql
+-- HTTP GET
+-- @param $1 user_id int
+-- @authorize
+-- @cached user_id
+-- @cache_expires_in 5m
+-- @connection ReadReplica
+select json_agg(o) from orders o where user_id = $1;
sql
sql
-- Function — critical write operation needs plpgsql, retries, rate limiting
+create function process_order(_order json)
+returns json
+language plpgsql security definer
+as $$
+begin
+  -- Order processing logic
+  return '{"success": true}'::json;
+end;
+$$;
+
+comment on function process_order(json) is
+'HTTP POST
+@authorize
+@retry_strategy aggressive
+@rate_limiter_policy order_limit';
sql
sql
-- SQL file — expensive report, concurrency limited, long cache
+-- sql/generate-sales-report.sql
+-- HTTP GET
+-- @param $1 start_date date
+-- @param $2 end_date   date
+-- @authorize roles admin,analyst
+-- @cached start_date, end_date
+-- @cache_expires_in 1d
+-- @rate_limiter_policy concurrency
+-- @connection ReadReplica
+select json_build_object(
+  'period', json_build_object('start', $1, 'end', $2),
+  'data',   (select json_agg(r) from sales_summary r where date between $1 and $2)
+);

Summary

What each feature buys you:

FeatureBenefit
HTTP CachingZero server load for cached responses
Server CachingNo database connections for cache hits
Hybrid CacheStampede protection + distributed storage
Connection RetriesHandles failover transparently
Command RetriesRecovers from transient query failures
Rate LimitingProtects infrastructure from abuse
Thread Pool TuningEliminates latency spikes during traffic bursts
Multi-Host ConnectionsAutomatic failover between servers
Load BalancingDistributes read load across replicas

These features work together: cache misses that hit the database benefit from retry logic. Rate limiting prevents cache stampedes before they happen. Load balancing distributes the requests that make it past the cache.

The result is an API that's not just fast under normal conditions, but resilient under adverse ones.

Development Time Saved

For comparison, here is what each feature costs to build by hand in a traditional backend:

FeatureManual ImplementationNpgsqlRest
HTTP Cache HeadersMiddleware + per-endpoint logic (~50-100 LOC)1 line annotation
Server-Side CachingCache service + key generation + invalidation logic (~200-400 LOC)cached annotation + JSON config
Redis/Hybrid CacheRedis client setup + serialization + stampede protection (~300-500 LOC)JSON config only
Cache Invalidation EndpointsAdditional controller actions + cache key matching (~100-200 LOC)InvalidateCacheSuffix config
Connection RetriesPolly policies + error handling + backoff logic (~150-300 LOC)JSON config only
Command RetriesPer-command retry wrapper + error classification (~200-400 LOC)JSON config + optional annotation
Rate LimitingMiddleware + policy configuration + storage (~200-400 LOC)JSON config + annotation
Multi-Host FailoverConnection management + health checks + failover logic (~300-500 LOC)Connection string only
Read Replica RoutingConnection factory + routing logic + context propagation (~200-400 LOC)connection annotation
Thread Pool TuningStartup configuration + monitoring (~50-100 LOC)JSON config only

Conservative estimates:

Beyond line count, consider what you're not dealing with:

The declarative approach means you describe what you want, not how to implement it. The infrastructure is implemented once, in NpgsqlRest, and reused by every endpoint.

SQL File Source

All performance features in this post — caching, retry strategies, rate limiting, timeouts — also work with SQL file endpoints.

`,254)),p(i,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Cache Options",href:"/config/cache-options"},{text:"Connection Settings",href:"/config/connection"},{text:"Rate Limiter",href:"/config/rate-limiter"}]})])}const F=a(h,[["render",k]]);export{u as __pageData,F as default}; diff --git a/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.lean.js b/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.lean.js new file mode 100644 index 000000000..ab7d4e4d8 --- /dev/null +++ b/assets/blog_performance-scalability-high-availability-npgsqlrest.md.73MKg7Tu.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Performance, Scalability, and High Availability with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Production-ready API configuration: response caching, retry logic, rate limiting, PostgreSQL multi-host failover, and load balancing. Complete guide with examples.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Performance, Scalability, and High Availability with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Production-ready API configuration: response caching, retry logic, rate limiting, PostgreSQL multi-host failover, and load balancing. Complete guide with examples.","head":[["meta",{"name":"keywords","content":"postgresql api caching, api rate limiting, postgresql high availability, postgresql load balancing, npgsql failover, api retry logic, postgresql connection pooling, production api"}],["meta",{"property":"og:title","content":"Performance, Scalability, and High Availability with NpgsqlRest"}],["meta",{"property":"og:description","content":"Production-ready API config: caching, retry logic, rate limiting, PostgreSQL multi-host failover."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Performance & High Availability with NpgsqlRest"}],["meta",{"name":"twitter:description","content":"Production-ready API: caching, rate limiting, PostgreSQL failover and load balancing."}]]},"headers":[],"relativePath":"blog/performance-scalability-high-availability-npgsqlrest.md","filePath":"blog/performance-scalability-high-availability-npgsqlrest.md"}'),h={name:"blog/performance-scalability-high-availability-npgsqlrest.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l("",254)),p(i,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Cache Options",href:"/config/cache-options"},{text:"Connection Settings",href:"/config/connection"},{text:"Rate Limiter",href:"/config/rate-limiter"}]})])}const F=a(h,[["render",k]]);export{u as __pageData,F as default}; diff --git a/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.js b/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.js new file mode 100644 index 000000000..c78ad25f5 --- /dev/null +++ b/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.js @@ -0,0 +1,272 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration","titleTemplate":"NpgsqlRest","description":"Serve CSV reports directly from PostgreSQL to Excel with Basic Auth. Build a complete BI data delivery system without application code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration","titleTemplate":"NpgsqlRest","description":"Serve CSV reports directly from PostgreSQL to Excel with Basic Auth. Build a complete BI data delivery system without application code.","head":[["meta",{"name":"keywords","content":"postgresql bi server, postgresql excel integration, csv export postgresql, power query postgresql, basic auth postgresql api, npgsqlrest csv, postgresql reporting"}],["meta",{"property":"og:title","content":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration"}],["meta",{"property":"og:description","content":"Serve CSV reports from PostgreSQL to Excel with Basic Auth. Build BI data delivery without code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL as a BI Server: Excel & CSV Integration"}],["meta",{"name":"twitter:description","content":"Serve CSV reports from PostgreSQL directly to Excel with authentication."}]]},"headers":[],"relativePath":"blog/postgresql-bi-server-excel-csv-basic-auth.md","filePath":"blog/postgresql-bi-server-excel-csv-basic-auth.md"}'),h={name:"blog/postgresql-bi-server-excel-csv-basic-auth.md"};function r(k,s,c,d,o,u){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l(`

Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration

January 2026 · PostgreSQLBIExcelCSVNpgsqlRest

A PostgreSQL function with a CSV annotation is a report endpoint that Excel can consume directly over Basic Auth - with no application code in between.

This post builds that into a working BI data delivery setup with NpgsqlRest: secured CSV endpoints, an audit trail, SSL, and Power Query on the consuming end. Change the function, and every connected Excel workbook picks up the change on its next refresh.

Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/5_csv_basic_auth

The Architecture

mermaid
flowchart TB
+    EX["Excel / Power BI
+    (Power Query with Basic Auth)"]
+
+    EX -- "HTTPS + Basic Auth" --> NR["NpgsqlRest
+    (CSV endpoint layer)"]
+
+    NR --> PG
+
+    subgraph PG["PostgreSQL"]
+        SR["sales_report()
+        Basic Auth + Audit
+        SECURITY DEFINER"]
+        SRP["sales_report_public()
+        Public Access
+        SECURITY DEFINER"]
+        SR --> ST["sales table
+        (protected data)"]
+    end

Your reporting logic lives in PostgreSQL functions, not application code. Business analysts connect Excel directly to these endpoints, and you control everything from one central place - the database.

Defense in Depth: The Principle of Least Privilege

This example implements the Principle of Least Privilege (PoLP) at the database level - a security architecture explained in detail in Database-Level Security: Building Secure Authentication with PostgreSQL.

Even if a Basic Auth password is compromised, the damage is limited:

mermaid
flowchart TB
+    AR["Application Role (app_user)
+    Can ONLY access public schema"]
+
+    AR -- "Can only call functions" --> PUB
+
+    subgraph PUB["example_5_public schema (exposed)"]
+        SR["sales_report() — SECURITY DEFINER
+        Can access protected data, but ONLY this specific data"]
+    end
+
+    PUB -- "SECURITY DEFINER elevates" --> PROT
+
+    subgraph PROT["example_5 schema (protected — NO direct access)"]
+        ST["sales table — app_user CANNOT access directly"]
+    end

Why this matters:

  1. No direct table access - The application role (app_user) cannot query tables directly. Even with a stolen password, an attacker can only call the exposed functions.

  2. Controlled data exposure - Each SECURITY DEFINER function exposes exactly what it's designed to expose - nothing more. The sales_report() function returns sales data; it cannot be tricked into returning user passwords or other sensitive data.

  3. Search path protection - The set search_path = pg_catalog, pg_temp prevents search path injection attacks that could otherwise exploit SECURITY DEFINER functions.

  4. Audit trail built-in - The _user_name parameter records who accessed the data, providing accountability even for legitimate access.

This is defense in depth: SSL protects credentials in transit, Basic Auth controls who can access endpoints, and PoLP limits what authenticated users can actually do. A compromised password gives access to the sales report - not the entire database.

Creating CSV Endpoints

Define a Reusable Type

First, create a composite type that defines your report structure:

sql
sql
create type example_5_public.sales_report_record as (
+    exported_by text,
+    order_id int,
+    customer_name text,
+    product text,
+    quantity int,
+    unit_price numeric(10,2),
+    total numeric(10,2),
+    order_date date
+);

This type can be reused across multiple functions and enables type composition (more on this later).

The Secured Report Function

sql
sql
create function example_5_public.sales_report(
+    _user_name text default null  -- mapped from basic auth name claim
+)
+returns setof example_5_public.sales_report_record
+language sql
+set search_path = pg_catalog, pg_temp  -- Protect against search path attacks
+security definer  -- Runs as migration user, not app_user
+begin atomic;
+select
+    _user_name as exported_by,  -- Shows who exported the report
+    order_id,
+    customer_name,
+    product,
+    quantity,
+    unit_price,
+    total,
+    order_date
+from example_5.sales
+order by order_date;
+end;

The _user_name parameter is automatically populated from the authenticated user's claims - providing a built-in audit trail of who accessed the data.

CSV Annotations

Everything HTTP-related is declared in the function comment:

sql
sql
comment on function example_5_public.sales_report(text) is '
+HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: text/csv
+Content-Disposition: attachment; filename="sales_report.csv"
+@basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp
+@user_params';
AnnotationEffect
@rawReturn plain text instead of JSON
@separator ,Use comma as column delimiter
@new_line \\nUse newline as row delimiter
@columnsInclude column header row
Content-Type: text/csvSet proper MIME type
Content-Disposition: attachmentMake browser download as file
@basic_auth admin <hash>Require Basic Authentication
@user_paramsMap authenticated user to _user_name parameter

The result:

csv
csv
"exported_by","order_id","customer_name","product","quantity","unit_price","total","order_date"
+"admin",1,"Acme Corp","Widget Pro",50,29.99,1499.50,"2024-01-15"
+"admin",2,"TechStart Inc","Widget Basic",100,19.99,1999.00,"2024-01-16"

Type Reuse and Composition

NpgsqlRest supports composite type expansion. When a function returns a table that includes a composite type alongside other columns, NpgsqlRest automatically expands the type's fields and merges them with the additional columns into a flat structure.

This is DRY at the database level: define the record shape once and reuse it across endpoints, so consumers see consistent structures everywhere.

The Problem: Schema Duplication

In traditional approaches, each endpoint defines its own return structure:

sql
sql
-- Endpoint 1: Sales report
+create function sales_report() returns table (
+    order_id int, customer_name text, product text, quantity int, ...
+);
+
+-- Endpoint 2: Sales with audit info
+create function sales_audit() returns table (
+    order_id int, customer_name text, product text, quantity int, ...  -- duplicated!
+    audited_by text, audit_date timestamp
+);
+
+-- Endpoint 3: Sales summary
+create function sales_summary() returns table (
+    order_id int, customer_name text, product text, quantity int, ...  -- duplicated again!
+    category text, region text
+);

When you need to add a field to sales data, you must update every function. Miss one, and your application has inconsistent data structures.

The Solution: Composite Type Expansion

Define the structure once as a composite type:

sql
sql
create type example_5_public.sales_report_record as (
+    exported_by text,
+    order_id int,
+    customer_name text,
+    product text,
+    quantity int,
+    unit_price numeric(10,2),
+    total numeric(10,2),
+    order_date date
+);

Then compose new return types by combining this type with additional columns:

sql
sql
create function example_5_public.sales_report_public()
+returns table (
+    record example_5_public.sales_report_record,  -- reuse the type
+    message text                                   -- add context-specific column
+)
+language sql
+set search_path = pg_catalog, pg_temp
+security definer
+begin atomic;
+select
+    (report.*)::example_5_public.sales_report_record,
+    'WARNING: This is a public endpoint without authentication.' as message
+from example_5_public.sales_report('public_user') report;
+end;

NpgsqlRest automatically expands this into a flat CSV structure:

csv
csv
"exported_by","order_id","customer_name","product","quantity","unit_price","total","order_date","message"
+"public_user",1,"Acme Corp","Widget Pro",50,29.99,1499.50,"2024-01-15","WARNING: This is a public endpoint..."

The composite type's 8 fields are expanded inline, followed by the message column - all as a single flat row.

Benefits for Applications

1. Single Source of Truth

Change the type definition once, and every function using it inherits the change:

sql
sql
-- Add a new field to the type
+alter type example_5_public.sales_report_record add attribute discount_applied boolean;

Every endpoint returning sales_report_record now includes discount_applied. No hunting through code to update multiple functions.

2. Consistent API Contracts

When multiple endpoints share the same base type, applications can rely on consistent field names and types:

typescript
typescript
// TypeScript clients can define a shared interface
+interface SalesReportBase {
+    exportedBy: string;
+    orderId: number;
+    customerName: string;
+    // ... always the same structure
+}
+
+// Extended interfaces compose naturally
+interface SalesReportPublic extends SalesReportBase {
+    message: string;
+}
+
+interface SalesReportAudit extends SalesReportBase {
+    auditedBy: string;
+    auditTimestamp: Date;
+}

The database types map directly to application types.

3. Function Chaining and Layered Access

Composite types enable building layered APIs where higher-level functions wrap lower-level ones:

sql
sql
-- Base function: secured, audited
+create function sales_report(_user_name text)
+returns setof sales_report_record ...
+
+-- Public wrapper: calls base function, adds warning
+create function sales_report_public()
+returns table (record sales_report_record, message text)
+as $$ select *, 'Public access' from sales_report('public_user') $$;
+
+-- Admin wrapper: calls base function, adds admin metadata
+create function sales_report_admin(_user_name text)
+returns table (record sales_report_record, access_level text, can_edit boolean)
+as $$ select *, 'admin', true from sales_report(_user_name) $$;

Each layer adds its own context without duplicating the core data structure.

4. Evolutionary Schema Design

As your application grows, you can evolve types without breaking existing consumers:

sql
sql
-- Week 1: Basic type
+create type order_summary as (id int, total numeric);
+
+-- Week 4: Add field - existing functions still work
+alter type order_summary add attribute status text;

5. Cross-Module Consistency

In larger applications with multiple schemas, composite types ensure consistency across different modules:

sql
sql
-- Shared type in a common schema
+create schema shared_types;
+create type shared_types.customer_info as (
+    customer_id int,
+    customer_name text,
+    email text,
+    tier text
+);
+
+-- Module A: Orders uses the shared type
+create function orders.get_order_details(_order_id int)
+returns table (
+    customer shared_types.customer_info,  -- reused type
+    order_id int,
+    order_total numeric,
+    order_date date
+);
+
+-- Module B: Support tickets uses the same type
+create function support.get_ticket_details(_ticket_id int)
+returns table (
+    customer shared_types.customer_info,  -- same type, consistent structure
+    ticket_id int,
+    issue text,
+    status text
+);

Both modules produce consistent customer information structures. When customer fields change (e.g., adding phone), update the type once and both modules inherit the change.

Securing with Basic Authentication

Password Hashing

Never store plain-text passwords in annotations. Generate a hash:

bash
bash
npgsqlrest --hash secret123
+# Output: lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp

Use the hash in your annotation:

sql
sql
basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp

Multiple Users via Configuration

For multiple users, define them in your configuration file instead of annotations:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "BasicAuth": {
+        "Enabled": true,
+        "UseDefaultPasswordHasher": true,
+        "Users": {
+          "admin": "lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp",
+          "analyst": "another_hashed_password_here",
+          "finance": "yet_another_hash"
+        }
+      }
+    }
+  }
+}

Then use basic_auth without credentials in the annotation - it will validate against the configuration:

sql
sql
comment on function example_5_public.sales_report(text) is '
+HTTP GET
+@raw
+@separator ,
+@columns
+@basic_auth
+@user_params';

Database-Driven Authentication with ChallengeCommand

For more elaborate authentication schemes, use ChallengeCommand to delegate authentication to a PostgreSQL function:

json
json
{
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "BasicAuth": {
+        "Enabled": true,
+        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
+      }
+    }
+  }
+}

The challenge function receives:

sql
sql
create function basic_auth_login(
+    _username text,
+    _password text,
+    _validated bool
+)
+returns table (
+    status bool,
+    user_id int,
+    user_name text,
+    user_roles text[]
+)
+language plpgsql as $$
+begin
+    -- Validate against your users table
+    return query
+    select
+        u.password_hash = crypt(_password, u.password_hash),
+        u.id,
+        u.username,
+        array_agg(r.role_name)
+    from users u
+    left join user_roles r on r.user_id = u.id
+    where u.username = _username
+    group by u.id, u.username, u.password_hash;
+end;
+$$;

See Basic Auth Configuration for full documentation.

SSL Configuration

Basic Authentication transmits credentials in Base64 encoding - this is NOT encryption. SSL/TLS is mandatory for production use.

Setup Steps

  1. Export a development certificate:
bash
bash
dotnet dev-certs https --export-path ./5_csv_basic_auth/localhost.pfx --password dev123
  1. Trust the certificate (optional, avoids browser warnings):
bash
bash
dotnet dev-certs https --trust
  1. Configure Kestrel in your config file:
json
json
{
+  "Urls": "https://localhost:8080",
+
+  "Ssl": {
+    "Enabled": true,
+    "UseHttpsRedirection": false,
+    "UseHsts": false
+  },
+
+  "Kestrel": {
+    "Endpoints": {
+      "Https": {
+        "Url": "https://localhost:8080",
+        "Certificate": {
+          "Path": "./5_csv_basic_auth/localhost.pfx",
+          "Password": "dev123"
+        }
+      }
+    }
+  },
+
+  "NpgsqlRest": {
+    "AuthenticationOptions": {
+      "BasicAuth": {
+        "SslRequirement": "Required"
+      }
+    }
+  }
+}

The SslRequirement setting controls SSL enforcement:

ValueBehavior
RequiredReject Basic Auth over plain HTTP
WarningAllow HTTP but log a warning
IgnoreAllow HTTP silently (debug only)

No Code Generation Required

Unlike traditional approaches that require generated API clients, CSV endpoints work with just a URL. The index.html in this example is one link:

html
html
<a href="/api/example-5-public/sales-report">Download Sales Report (CSV)</a>

Click the link, enter credentials when prompted, and the browser downloads the CSV file. No JavaScript, no build process, no dependencies.

Excel Power Query Integration

Connecting Excel to Your Endpoint

In Excel, use Power Query to connect:

powerquery
powerquery
let
+    Source = Web.Contents("https://localhost:8080/api/example-5-public/sales-report"),
+    ImportedCSV = Csv.Document(Source, [Delimiter=",", Encoding=TextEncoding.Utf8, QuoteStyle=QuoteStyle.Csv]),
+    PromotedHeaders = Table.PromoteHeaders(ImportedCSV, [PromoteAllScalars=true])
+in
+    PromotedHeaders

When you run this query, Excel prompts for Basic Auth credentials. Enter your username and password, and the data flows directly into your spreadsheet.

The Power of Central Control

When you modify the PostgreSQL function:

sql
sql
-- Add a new column to the report
+create or replace function example_5_public.sales_report(...)
+returns table (
+    -- existing columns...
+    profit_margin numeric(5,2)  -- NEW!
+)

Every Excel workbook that connects to this endpoint will see the new column on the next refresh. You control the data structure from one place - the database - and every consumer automatically gets the update.

This is the opposite of traditional BI systems where:

With this approach:

Production Considerations

For production deployment within a corporate network:

  1. Use proper SSL certificates - Get certificates from your internal CA or a trusted provider
  2. Deploy on trusted network - These endpoints should only be accessible from your internal network
  3. Use strong passwords - Consider integrating with your corporate identity system via ChallengeCommand
  4. Audit access - The _user_name parameter in the secured endpoint creates an audit trail

When You Still Need ETL

This approach works well for many scenarios, but you may still need traditional ETL when:

For these cases, consider replicating data to a dedicated reporting database (using PostgreSQL logical replication, for example) and pointing NpgsqlRest at that replica. You get the same simple CSV endpoints without impacting production.

The Cost Comparison

Consider what you'd typically need for a BI system:

Traditional BIThis Approach
PostgreSQL (or paid database)PostgreSQL (free)
ETL tool licensesOften not needed*
Data warehouse licensesOften not needed*
BI tool licenses (Tableau, etc.)Not needed
Development time for integrationMinutes
Ongoing maintenanceSchema changes only

*For simple reporting from a single database, ETL and data warehouses add unnecessary complexity. For high-load production systems or cross-database reporting, consider a read replica.

With PostgreSQL (free) and NpgsqlRest (free), you have a complete BI data delivery system in minutes. Users get their data in Excel - the tool they already know - and can create any charts or analysis they want.

Complete Example

The full working example includes:

bash
bash
# Clone the repository
+git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
+
+# Navigate to example
+cd npgsqlrest-docs/examples/5_csv_basic_auth
+
+# Generate certificate
+dotnet dev-certs https --export-path ./localhost.pfx --password dev123
+
+# Run migrations and start
+npgsqlrest --config config.json

Then open https://localhost:8080 in your browser or connect Excel to https://localhost:8080/api/example-5-public/sales-report.

Conclusion

PostgreSQL functions define the reports, annotations turn them into authenticated CSV endpoints, and Excel consumes them directly - structure changes reach every workbook on the next refresh. The caveats above still apply: point heavy reporting at a replica, and treat Basic Auth as internal-network-only, always behind SSL.

Within those bounds, no BI licenses, no ETL pipelines, no data warehouse - just PostgreSQL functions serving CSV to Excel. That's what "database as the application" means in practice.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,131)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/5_csv_basic_auth",documentation:[{text:"Basic Auth Configuration",href:"/config/basic-auth-config"},{text:"CSV Annotations",href:"/annotations/raw"},{text:"Authentication Options",href:"/config/authentication-options"}]})])}const F=a(h,[["render",r]]);export{y as __pageData,F as default}; diff --git a/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.lean.js b/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.lean.js new file mode 100644 index 000000000..d211958f0 --- /dev/null +++ b/assets/blog_postgresql-bi-server-excel-csv-basic-auth.md.C9mEJphA.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration","titleTemplate":"NpgsqlRest","description":"Serve CSV reports directly from PostgreSQL to Excel with Basic Auth. Build a complete BI data delivery system without application code.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration","titleTemplate":"NpgsqlRest","description":"Serve CSV reports directly from PostgreSQL to Excel with Basic Auth. Build a complete BI data delivery system without application code.","head":[["meta",{"name":"keywords","content":"postgresql bi server, postgresql excel integration, csv export postgresql, power query postgresql, basic auth postgresql api, npgsqlrest csv, postgresql reporting"}],["meta",{"property":"og:title","content":"Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration"}],["meta",{"property":"og:description","content":"Serve CSV reports from PostgreSQL to Excel with Basic Auth. Build BI data delivery without code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL as a BI Server: Excel & CSV Integration"}],["meta",{"name":"twitter:description","content":"Serve CSV reports from PostgreSQL directly to Excel with authentication."}]]},"headers":[],"relativePath":"blog/postgresql-bi-server-excel-csv-basic-auth.md","filePath":"blog/postgresql-bi-server-excel-csv-basic-auth.md"}'),h={name:"blog/postgresql-bi-server-excel-csv-basic-auth.md"};function r(k,s,c,d,o,u){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l("",131)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/5_csv_basic_auth",documentation:[{text:"Basic Auth Configuration",href:"/config/basic-auth-config"},{text:"CSV Annotations",href:"/annotations/raw"},{text:"Authentication Options",href:"/config/authentication-options"}]})])}const F=a(h,[["render",r]]);export{y as __pageData,F as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.js b/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.js new file mode 100644 index 000000000..ec4918c0d --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.js @@ -0,0 +1 @@ +import{v as s,aj as o,c as l,o as n,j as t,a as r}from"./chunks/framework.CgT1UzWm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"page"},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2024.md","filePath":"blog/postgresql-rest-api-benchmark-2024.md"}'),p={name:"blog/postgresql-rest-api-benchmark-2024.md"},m=Object.assign(p,{setup(c){return s(()=>{o().go("/blog/postgresql-rest-api-benchmark-2026")}),(a,e)=>(n(),l("div",null,e[0]||(e[0]=[t("p",null,[r("Redirecting to "),t("a",{href:"/blog/postgresql-rest-api-benchmark-2026.html"},"PostgreSQL REST API Benchmark 2026"),r("...")],-1)])))}});export{i as __pageData,m as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.lean.js b/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.lean.js new file mode 100644 index 000000000..ec4918c0d --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2024.md.DwEQmIoA.lean.js @@ -0,0 +1 @@ +import{v as s,aj as o,c as l,o as n,j as t,a as r}from"./chunks/framework.CgT1UzWm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"page"},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2024.md","filePath":"blog/postgresql-rest-api-benchmark-2024.md"}'),p={name:"blog/postgresql-rest-api-benchmark-2024.md"},m=Object.assign(p,{setup(c){return s(()=>{o().go("/blog/postgresql-rest-api-benchmark-2026")}),(a,e)=>(n(),l("div",null,e[0]||(e[0]=[t("p",null,[r("Redirecting to "),t("a",{href:"/blog/postgresql-rest-api-benchmark-2026.html"},"PostgreSQL REST API Benchmark 2026"),r("...")],-1)])))}});export{i as __pageData,m as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.js b/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.js new file mode 100644 index 000000000..91595774d --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.js @@ -0,0 +1 @@ +import{v as s,aj as o,c as l,o as n,j as t,a as r}from"./chunks/framework.CgT1UzWm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"page"},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2025.md","filePath":"blog/postgresql-rest-api-benchmark-2025.md"}'),p={name:"blog/postgresql-rest-api-benchmark-2025.md"},m=Object.assign(p,{setup(c){return s(()=>{o().go("/blog/postgresql-rest-api-benchmark-2026")}),(a,e)=>(n(),l("div",null,e[0]||(e[0]=[t("p",null,[r("Redirecting to "),t("a",{href:"/blog/postgresql-rest-api-benchmark-2026.html"},"PostgreSQL REST API Benchmark 2026"),r("...")],-1)])))}});export{i as __pageData,m as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.lean.js b/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.lean.js new file mode 100644 index 000000000..91595774d --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2025.md.Tbw6Blag.lean.js @@ -0,0 +1 @@ +import{v as s,aj as o,c as l,o as n,j as t,a as r}from"./chunks/framework.CgT1UzWm.js";const i=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"page"},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2025.md","filePath":"blog/postgresql-rest-api-benchmark-2025.md"}'),p={name:"blog/postgresql-rest-api-benchmark-2025.md"},m=Object.assign(p,{setup(c){return s(()=>{o().go("/blog/postgresql-rest-api-benchmark-2026")}),(a,e)=>(n(),l("div",null,e[0]||(e[0]=[t("p",null,[r("Redirecting to "),t("a",{href:"/blog/postgresql-rest-api-benchmark-2026.html"},"PostgreSQL REST API Benchmark 2026"),r("...")],-1)])))}});export{i as __pageData,m as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.js b/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.js new file mode 100644 index 000000000..7b83ee386 --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.js @@ -0,0 +1 @@ +import{_ as r,C as s,c as a,o as l,a5 as d,G as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","titleTemplate":"NpgsqlRest","description":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. NpgsqlRest, PostgREST, Swoole PHP, Go, Rust, Express, FastAPI, Spring Boot and more tested under load.","frontmatter":{"layout":"doc","outline":[2,3],"title":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","titleTemplate":"NpgsqlRest","description":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. NpgsqlRest, PostgREST, Swoole PHP, Go, Rust, Express, FastAPI, Spring Boot and more tested under load.","head":[["meta",{"name":"keywords","content":"postgresql rest api benchmark, api performance comparison, npgsqlrest vs postgrest, fastapi vs express postgresql, spring boot postgresql performance, swoole php benchmark, rest api framework comparison 2026"}],["meta",{"property":"og:title","content":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"}],["meta",{"property":"og:description","content":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. See how NpgsqlRest, PostgREST, Swoole PHP, and others perform under load."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"}],["meta",{"name":"twitter:description","content":"Performance comparison of 14 REST API frameworks serving PostgreSQL data under various load conditions."}]]},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2026.md","filePath":"blog/postgresql-rest-api-benchmark-2026.md"}'),i={name:"blog/postgresql-rest-api-benchmark-2026.md"};function o(g,t,h,p,_,c){const e=s("BlogNav");return l(),a("div",null,[t[0]||(t[0]=d('

PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared

Benchmark · Performance · January 2026


INFO

I wanted to repeat the benchmark from last year for several big reasons:

Other than that, during the developement and explaration of the test project, I found some issues. For example:

Other then that, I wanted to make the benchmarks as transparent, fair, realistic and accurate as possible. Everyone can check the source code used for tests, and run run themself if desired with their own setup. Also, anyone can suggest improvements or make PRs with new frameworks, optimizations or new test scenarios, and I would very much welcome that. As development of NpgsqlRest continues, I plan to repeat this benchmark from time to time to keep track of performance changes.

What follows is the detailed AI-generated analysis and report of the benchmark results. But you can also skip to summary results table or to conclusion sections directly.

Enjoy!

Following our 2025 benchmark, this year's run uses the latest framework versions and adds higher concurrency levels (up to 200 VUs), pure HTTP overhead tests (Minimal Baseline), and POST body parsing benchmarks.

What We Tested

All frameworks executed the same PostgreSQL functions. This isolates the framework overhead from database performance - every framework runs identical queries against the same PostgreSQL instance. All functions use generate_series() for constant, predictable database response times with no table I/O.

Test Scenarios:

ScenarioEndpointFunctionDescription
Data Type SerializationGET /api/perf-testperf_testComprehensive test with 23 data types (text, int, numeric, bool, date/time, UUID, JSON, arrays). Returns 1, 10, 100, or 500 records.
Minimal Baseline (new)GET /api/perf-minimalperf_minimalPure HTTP routing overhead. Returns {"status": "ok", "ts": ...} - no parameters, minimal response. Tested at 100, 200, and 500 VUs.
POST Body Parsing (new)POST /api/perf-postperf_postTests JSON request body parsing. Sends nested JSON payload, echoes it back with computed values. Tested at 50 VUs with 10 and 100 records.
Nested JSON (new)GET /api/perf-nestedperf_nestedTests nested object serialization at configurable depth levels.
Large Payload (new)GET /api/perf-large-payloadperf_large_payloadTests chunked transfer and buffer handling with configurable KB-sized responses.
Many Parameters (new)GET /api/perf-many-paramsperf_many_paramsTests query string parsing with 20 parameters of mixed types.

Test Configuration:

Frameworks Tested:

FrameworkLanguageVersion
NpgsqlRest (JIT).NET3.4.7
NpgsqlRest (AOT).NET3.4.7
PostgRESTHaskell14.3
Go (net/http + pgx)Go1.25
Rust (Actix + tokio-postgres)Rust1.91.1
Spring BootJava 244.0.1
.NET Minimal API + Dapper.NET 10-
.NET Minimal API + EF Core.NET 9/10-
FastifyNode.js5.7.1
BunBun1.3.3
SwoolePHP 8.46.0 (extension)
FastAPIPython0.128.0
DjangoPython6.0.1

What's New in This Benchmark

This benchmark introduces several improvements over the previous 2025 benchmark:

Version Updates

FrameworkPrevious VersionNew VersionChanged
NpgsqlRest3.2.23.4.7Yes
PostgREST12.2.814.3Yes
Go1.241.25Yes
Rust1.83.01.91.1Yes
Bun1.1.421.3.3Yes
Fastify5.6.25.7.1Yes
FastAPI0.127.10.128.0Yes
Django6.06.0.1Yes
SwoolePHP 8.4 + Swoole 6.0PHP 8.4 + Swoole 6.0No
Spring BootJava 24 + 4.0.1Java 24 + 4.0.1No
.NET Dapper.NET 10 Preview.NET 10 PreviewNo
.NET EF Core.NET 9 / .NET 10 Preview.NET 9 / .NET 10 PreviewNo

New Test Scenarios

We added five new benchmark scenarios (marked "(new)" in the scenario table above):

We also extended concurrency testing to better expose scaling limits:

Infrastructure Changes

Comparing With Previous Results

For detailed comparison with the 2025 benchmark:

Key Findings

Swoole PHP Dominates Large Payload Scenarios

A major shift from 2025: Swoole PHP 6.0 now leads in most scenarios involving larger payloads. At 100 VU with 100 records, Swoole achieves 469.58 req/s - outperforming all competitors. With 500 records, Swoole maintains its lead at 106.88 req/s.

NpgsqlRest Leads High-Concurrency Low-Payload Scenarios

At 100 concurrent users with minimal payload (1 record), NpgsqlRest JIT achieves 4,588 req/s, maintaining its lead in low-latency scenarios. The gap between JIT and AOT versions has narrowed significantly in v3.4.7.

The Top Performers by Scenario

ScenarioLeaderPerformance
1 VU, 1 RecordBun539.91 req/s
100 VU, 1 RecordNpgsqlRest JIT4,588 req/s
100 VU, 100 RecordsSwoole PHP469.58 req/s
100 VU, 500 RecordsSwoole PHP106.88 req/s
Minimal Baseline (pure HTTP)Go20,104 req/s
POST Body ParsingGo9,628 req/s

Performance Tiers at 100 VU, 1 Record

TierFrameworksPerformance Range
EliteNpgsqlRest JIT/AOT, Swoole PHP4,400-4,600 req/s
TopBun, Go, Fastify, Spring Boot4,100-4,400 req/s
High.NET Dapper, Rust3,900-4,100 req/s
Mid.NET EF Core3,400-3,500 req/s
LowerFastAPI, PostgREST, Django1,600-1,850 req/s

What Changed From 2025

Several notable performance shifts:

  1. Swoole PHP improved dramatically - Jumping from mid-tier to top performer in data-heavy scenarios
  2. Bun emerged as single-threaded champion - Best performance at 1 VU across all payload sizes
  3. NpgsqlRest JIT/AOT gap narrowed - AOT now performs nearly identically to JIT in most scenarios
  4. PostgREST improved - Better scaling under high concurrency compared to 2025
  5. Go remains pure HTTP champion - Unmatched at 20,000+ req/s in minimal baseline tests

Scaling Behavior

Framework scaling patterns at increasing concurrency:

Framework1 VU50 VU100 VU200 VUScaling Factor
NpgsqlRest JIT4804,3804,5884,5639.5x
Swoole PHP4714,1604,4234,4859.5x
Bun5404,4214,3774,4198.2x
PostgREST2711,8181,7491,6636.5x

Large Payloads Level the Playing Field

With 500 records at 100 VU, database I/O dominates and the performance gap narrows:

Framework500 Records @ 100 VULatency
Swoole PHP106.88 req/s468ms
Go90.93 req/s550ms
Rust85.79 req/s583ms
NpgsqlRest JIT82.37 req/s607ms
FastAPI25.36 req/s1,977ms

Pure HTTP Overhead (Minimal Baseline)

Testing pure HTTP handling without database access reveals framework overhead:

Framework100 VU200 VU500 VU
Go20,104 req/s20,807 req/s20,573 req/s
NpgsqlRest JIT16,065 req/s17,105 req/s17,015 req/s
.NET Dapper14,764 req/s15,401 req/s15,705 req/s
Spring Boot14,138 req/s14,593 req/s14,435 req/s
Swoole PHP12,042 req/s12,309 req/s12,297 req/s
PostgREST5,410 req/s4,974 req/s5,324 req/s

Go's lightweight HTTP server achieves 20,000+ req/s - nearly 4x faster than PostgREST's pure HTTP overhead.

POST Body Parsing Performance

Testing JSON body parsing adds another dimension:

Framework50 VU, 10 Records50 VU, 100 Records
Go9,629 req/s2,697 req/s
Swoole PHP7,470 req/s2,445 req/s
Spring Boot7,133 req/s1,758 req/s
NpgsqlRest JIT6,101 req/s1,226 req/s
Bun4,028 req/s1,832 req/s
PostgREST3,479 req/s1,156 req/s

Go's JSON parsing keeps it in front, while Bun loses less throughput than most as the payload grows.

Python Frameworks Continue to Struggle

Both FastAPI and Django remain at the bottom in most scenarios:

JIT vs AOT in 2026

NpgsqlRest's JIT and AOT versions now perform nearly identically:

ScenarioJITAOTDifference
100 VU, 1 Record4,588 req/s4,527 req/s1.3%
100 VU, 100 Records377 req/s375 req/s0.5%
Minimal Baseline16,065 req/s15,624 req/s2.8%

For most workloads, the choice between JIT and AOT can now be based on deployment requirements (image size, cold start) rather than performance.

Why Certain Frameworks Excel

Swoole PHP's Rise

Swoole 6.0's gains come from:

Go's HTTP Dominance

Go's minimal baseline performance (20K+ req/s) demonstrates:

NpgsqlRest's Architecture

NpgsqlRest's numbers come from eliminating layers:

  1. No ORM overhead - Direct PostgreSQL protocol via Npgsql
  2. No routing framework - Endpoints derived from database metadata
  3. No serialization layer - PostgreSQL handles JSON serialization
  4. Efficient connection pooling - Npgsql's built-in pooling

Resource Usage

New in this benchmark: per-service memory and CPU monitoring during test execution.

ServicePeak MemoryAvg MemoryAvg CPU
Go75.94 MB21.35 MB4.84%
Swoole PHP78.99 MB50.92 MB4.49%
Rust168.20 MB43.03 MB3.79%
Bun168.00 MB59.31 MB4.29%
.NET Dapper192.70 MB96.55 MB4.19%
FastAPI198.10 MB133.73 MB4.29%
NpgsqlRest AOT237.80 MB59.84 MB4.30%
.NET 10 EF256.50 MB143.59 MB6.16%
.NET 9 EF276.60 MB133.80 MB6.36%
NpgsqlRest JIT321.30 MB111.41 MB4.42%
Fastify411.40 MB58.96 MB3.68%
Django824.00 MB386.77 MB11.84%
Spring Boot1,010.00 MB806.79 MB5.38%

Key Observations

The full resource monitoring data is available in the stats directory.

Important Note: JSON and Array Type Handling

One caveat when interpreting these results: not all frameworks return identical JSON responses. The differences lie in how each handles PostgreSQL's JSON, JSONB, and array types.

Frameworkjsonjsonbint[]text[]
NpgsqlRest (JIT/AOT)
.NET EF Core / Dapper
Rust
Fastify
Django
Go
FastAPI
PostgREST⚠️⚠️
Bun⚠️⚠️
Spring Boot⚠️⚠️
Swoole PHP

✅ = Properly parsed as native JSON/array ❌ = Returns raw PostgreSQL text format (string) ⚠️ = Unusual format (wrapped in metadata or array)

Swoole PHP's impressive numbers come with a caveat: JSON/JSONB and array fields require additional client-side parsing.

Conclusion

Where each framework fits, based on the 2026 numbers:

The "database as API" approach continues to hold the top tier: NpgsqlRest requires zero application code - just configuration - while delivering top-tier performance and correct PostgreSQL type handling.

Lines of Code Comparison

Performance isn't everything — development time, maintainability, and code complexity matter too. How much code each framework needs to implement the same API endpoints:

FrameworkLines of Code
PostgREST14 (config only)
NpgsqlRest21 (config only)
Fastify100
.NET EF116
Bun133
FastAPI136
Spring Boot139
.NET Dapper140
Django203
Swoole PHP216
Rust291
Go347

Go may deliver 20,000+ req/s in pure HTTP tests, but that comes at the cost of writing 347 lines of boilerplate code for basic CRUD operations. Every line of code is a potential bug, a maintenance burden, and development time spent.

NpgsqlRest and PostgREST take a different approach: define your API in the database, configure once, and let the framework handle the rest. You get top-tier performance (4,500+ req/s) with just 14-21 lines of configuration - no serialization code, no routing logic, no connection pool management.

The benchmark source code and detailed results are available in the pg_function_load_tests repository.


Full Benchmark Results

Summary Tables

All results in requests per second (req/s). Sorted by 100/1 performance.

Data Type Serialization

Function: perf_test

Framework1/11/10100/1100/100100/500
NpgsqlRest JIT480265🥇 4,58837782
NpgsqlRest AOT466262🥈 4,52737582
Swoole PHP471🥇 292🥉 4,423🥇 470🥇 107
Bun🥇 5402434,37735379
Go🥉 484🥈 2894,362🥈 406🥈 91
Fastify4822604,17235173
Spring Boot4642334,14728261
.NET Dapper4252444,10133172
Rust🥈 507🥉 2853,940🥉 388🥉 86
.NET 10 EF3772203,51533172
.NET 9 EF3622233,42533072
FastAPI4782201,84311125
PostgREST2711811,74934279
Django2711781,69130771

Column headers = VU/Records (e.g., 100/1 = 100 VU, 1 record). See detailed results.

New Scenarios

FrameworkMinPOSTNestLargeParams
Go🥇 20,104🥇 9,629🥇 3,756🥇 1,618🥇 16,100
NpgsqlRest JIT🥈 16,0656,1013,0611,096🥈 11,504
NpgsqlRest AOT🥉 15,6246,065🥉 3,073919🥉 11,221
.NET Dapper14,7646,0892,9891,39710,702
Spring Boot14,138🥉 7,1332,230🥈 1,51510,324
Swoole PHP12,042🥈 7,470🥈 3,426🥉 1,5009,458
Rust11,7615,4002,8241,5099,231
.NET 10 EF9,3854,8792,6151,3147,452
.NET 9 EF8,9894,7102,5581,3267,182
Fastify8,8994,6312,9211,2487,711
Bun7,8034,0282,2871,2353,116
PostgREST5,4103,4791,9129083,960
FastAPI4,0902,4879711,4922,080
Django2,5352,2661,6771,0712,222

Results are grouped by concurrency level and payload size, sorted by requests per second (highest first).

Data Type Serialization Tests

1 Virtual User, 1 Record

Function: perf_test

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
bun-app-v1.3.3539.91/s1.84ms32,395summarysource
rust-app-v1.91.1506.53/s1.96ms30,392summarysource
go-app-v1.25483.79/s2.05ms29,028summarysource
fastify-app-v5.7.1481.94/s2.06ms28,917summarysource
npgsqlrest-jit-v3.4.7480.28/s2.07ms28,817summarysource
fastapi-app-v0.128.0478.18/s2.08ms28,692summarysource
swoole-php-app-v6.0471.18/s2.11ms28,272summarysource
npgsqlrest-aot-v3.4.7466.06/s2.13ms27,964summarysource
java24-spring-boot-v4.0.1464.36/s2.14ms27,863summarysource
net10-minapi-dapper-jit425.45/s2.34ms25,527summarysource
net10-minapi-ef-jit376.73/s2.64ms22,604summarysource
net9-minapi-ef-jit361.52/s2.75ms21,692summarysource
postgrest-v14.3271.38/s3.67ms16,284summarysource
django-app-v6.0.1270.87/s3.68ms16,254summarysource

1 Virtual User, 10 Records

Function: perf_test

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v6.0291.51/s3.42ms17,491summarysource
go-app-v1.25288.60/s3.45ms17,317summarysource
rust-app-v1.91.1284.94/s3.50ms17,098summarysource
npgsqlrest-jit-v3.4.7264.71/s3.76ms15,883summarysource
npgsqlrest-aot-v3.4.7262.37/s3.80ms15,743summarysource
fastify-app-v5.7.1259.77/s3.84ms15,587summarysource
net10-minapi-dapper-jit244.13/s4.08ms14,649summarysource
bun-app-v1.3.3243.23/s4.10ms14,594summarysource
java24-spring-boot-v4.0.1232.71/s4.28ms13,964summarysource
net9-minapi-ef-jit223.13/s4.47ms13,389summarysource
fastapi-app-v0.128.0220.40/s4.52ms13,225summarysource
net10-minapi-ef-jit219.98/s4.53ms13,200summarysource
postgrest-v14.3180.58/s5.52ms10,835summarysource
django-app-v6.0.1178.29/s5.59ms10,698summarysource

100 Virtual Users, 1 Record

Function: perf_test

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
npgsqlrest-jit-v3.4.74,588.02/s10.88ms275,381summarysource
npgsqlrest-aot-v3.4.74,526.64/s11.02ms271,720summarysource
swoole-php-app-v6.04,423.22/s11.29ms265,603summarysource
bun-app-v1.3.34,377.29/s11.41ms262,711summarysource
go-app-v1.254,362.06/s11.44ms261,787summarysource
fastify-app-v5.7.14,171.93/s11.97ms250,370summarysource
java24-spring-boot-v4.0.14,146.96/s12.03ms248,882summarysource
net10-minapi-dapper-jit4,100.54/s12.17ms246,098summarysource
rust-app-v1.91.13,939.83/s12.67ms236,565summarysource
net10-minapi-ef-jit3,515.31/s14.20ms210,977summarysource
net9-minapi-ef-jit3,424.67/s14.60ms205,781summarysource
fastapi-app-v0.128.01,842.81/s27.12ms110,610summarysource
postgrest-v14.31,749.07/s28.58ms105,038summarysource
django-app-v6.0.11,690.72/s29.56ms101,545summarysource

100 Virtual Users, 100 Records

Function: perf_test

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v6.0469.58/s106.44ms28,257summarysource
go-app-v1.25405.52/s123.21ms24,378summarysource
rust-app-v1.91.1387.70/s128.79ms23,313summarysource
npgsqlrest-jit-v3.4.7377.42/s132.43ms22,691summarysource
npgsqlrest-aot-v3.4.7374.57/s133.40ms22,519summarysource
bun-app-v1.3.3352.79/s141.70ms21,217summarysource
fastify-app-v5.7.1351.07/s142.62ms21,212summarysource
postgrest-v14.3342.26/s145.93ms20,578summarysource
net10-minapi-ef-jit331.31/s151.40ms19,994summarysource
net10-minapi-dapper-jit331.16/s150.92ms19,914summarysource
net9-minapi-ef-jit329.64/s151.67ms19,836summarysource
django-app-v6.0.1307.22/s162.61ms18,480summarysource
java24-spring-boot-v4.0.1281.56/s177.62ms16,949summarysource
fastapi-app-v0.128.0111.17/s449.98ms6,774summarysource

100 Virtual Users, 500 Records

Function: perf_test

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
swoole-php-app-v6.0106.88/s468.22ms6,459summarysource
go-app-v1.2590.93/s550.42ms5,511summarysource
rust-app-v1.91.185.79/s583.48ms5,192summarysource
npgsqlrest-jit-v3.4.782.37/s606.96ms4,991summarysource
npgsqlrest-aot-v3.4.781.89/s610.99ms4,956summarysource
postgrest-v14.378.59/s636.29ms4,753summarysource
bun-app-v1.3.378.55/s637.59ms4,772summarysource
fastify-app-v5.7.173.00/s685.34ms4,463summarysource
net10-minapi-dapper-jit72.42/s691.80ms4,391summarysource
net9-minapi-ef-jit72.00/s694.45ms4,355summarysource
net10-minapi-ef-jit71.98/s694.12ms4,363summarysource
django-app-v6.0.171.16/s702.86ms4,302summarysource
java24-spring-boot-v4.0.160.75/s822.37ms3,683summarysource
fastapi-app-v0.128.025.36/s1,977.30ms1,613summarysource

Minimal Baseline (Pure HTTP Overhead)

100 Virtual Users

Function: perf_minimal

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
go-app-v1.2520,104.17/s2.47ms603,258summarysource
npgsqlrest-jit-v3.4.716,064.93/s3.10ms481,990summarysource
npgsqlrest-aot-v3.4.715,623.97/s3.19ms468,869summarysource
net10-minapi-dapper-jit14,763.56/s3.37ms442,970summarysource
java24-spring-boot-v4.0.114,137.76/s3.52ms424,203summarysource
swoole-php-app-v6.012,042.03/s4.13ms361,335summarysource
rust-app-v1.91.111,760.56/s4.22ms352,878summarysource
net10-minapi-ef-jit9,384.73/s5.31ms281,613summarysource
net9-minapi-ef-jit8,988.95/s5.55ms269,715summarysource
fastify-app-v5.7.18,899.19/s5.61ms267,058summarysource
bun-app-v1.3.37,803.09/s6.40ms234,250summarysource
postgrest-v14.35,410.48/s9.23ms162,402summarysource
fastapi-app-v0.128.04,089.65/s12.21ms122,737summarysource
django-app-v6.0.12,535.28/s19.71ms76,114summarysource

POST Body Parsing

50 Virtual Users, 10 Records

Function: perf_post

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
go-app-v1.259,628.69/s2.58ms577,788summarysource
swoole-php-app-v6.07,470.15/s3.33ms448,231summarysource
java24-spring-boot-v4.0.17,132.74/s3.49ms427,988summarysource
npgsqlrest-jit-v3.4.76,100.82/s4.08ms366,070summarysource
net10-minapi-dapper-jit6,088.58/s4.09ms365,437summarysource
npgsqlrest-aot-v3.4.76,065.30/s4.11ms363,949summarysource
rust-app-v1.91.15,399.96/s4.61ms324,130summarysource
net10-minapi-ef-jit4,879.06/s5.11ms292,761summarysource
net9-minapi-ef-jit4,710.24/s5.29ms282,651summarysource
fastify-app-v5.7.14,631.21/s5.39ms277,906summarysource
bun-app-v1.3.34,027.52/s6.20ms241,687summarysource
postgrest-v14.33,478.73/s7.17ms208,759summarysource
fastapi-app-v0.128.02,487.28/s10.04ms149,259summarysource
django-app-v6.0.12,266.37/s11.02ms136,044summarysource

Nested JSON Serialization

50 Virtual Users, Depth 1

Function: perf_nested

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
go-app-v1.253,756.41/s6.64ms225,426summarysource
swoole-php-app-v6.03,426.34/s7.27ms205,620summarysource
npgsqlrest-aot-v3.4.73,073.40/s8.12ms184,428summarysource
npgsqlrest-jit-v3.4.73,060.64/s8.15ms183,675summarysource
net10-minapi-dapper-jit2,988.63/s8.35ms179,345summarysource
fastify-app-v5.7.12,921.44/s8.54ms175,319summarysource
rust-app-v1.91.12,824.19/s8.83ms169,476summarysource
net10-minapi-ef-jit2,615.08/s9.54ms156,947summarysource
net9-minapi-ef-jit2,557.86/s9.76ms153,501summarysource
bun-app-v1.3.32,286.95/s10.92ms137,261summarysource
java24-spring-boot-v4.0.12,229.81/s11.19ms133,816summarysource
postgrest-v14.31,912.23/s13.06ms114,792summarysource
django-app-v6.0.11,676.67/s14.90ms100,661summarysource
fastapi-app-v0.128.0970.57/s25.74ms58,262summarysource

Large Payload

25 Virtual Users, 100KB Payload

Function: perf_large_payload

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
go-app-v1.251,618.26/s7.70ms97,107summarysource
java24-spring-boot-v4.0.11,514.97/s8.23ms90,930summarysource
rust-app-v1.91.11,508.80/s8.27ms90,539summarysource
swoole-php-app-v6.01,500.22/s8.31ms90,027summarysource
fastapi-app-v0.128.01,491.75/s8.36ms89,518summarysource
net10-minapi-dapper-jit1,396.93/s8.93ms83,826summarysource
net9-minapi-ef-jit1,326.18/s9.41ms79,583summarysource
net10-minapi-ef-jit1,313.73/s9.50ms78,847summarysource
fastify-app-v5.7.11,248.43/s10.00ms74,941summarysource
bun-app-v1.3.31,234.55/s10.11ms74,093summarysource
npgsqlrest-jit-v3.4.71,095.93/s11.39ms65,765summarysource
django-app-v6.0.11,070.77/s11.66ms64,270summarysource
npgsqlrest-aot-v3.4.7919.29/s13.58ms55,175summarysource
postgrest-v14.3907.83/s13.75ms54,499summarysource

Many Parameters (20 params)

50 Virtual Users

Function: perf_many_params

FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
go-app-v1.2516,100.46/s1.54ms966,176summarysource
npgsqlrest-jit-v3.4.711,503.53/s2.16ms690,265summarysource
npgsqlrest-aot-v3.4.711,220.54/s2.22ms673,270summarysource
net10-minapi-dapper-jit10,701.77/s2.33ms642,149summarysource
java24-spring-boot-v4.0.110,323.63/s2.41ms619,498summarysource
swoole-php-app-v6.09,457.64/s2.63ms567,509summarysource
rust-app-v1.91.19,230.96/s2.69ms553,984summarysource
fastify-app-v5.7.17,710.72/s3.23ms462,679summarysource
net10-minapi-ef-jit7,452.38/s3.34ms447,165summarysource
net9-minapi-ef-jit7,181.52/s3.47ms430,916summarysource
postgrest-v14.33,960.49/s6.30ms237,672summarysource
bun-app-v1.3.33,116.21/s8.01ms187,044summarysource
django-app-v6.0.12,222.19/s11.24ms133,359summarysource
fastapi-app-v0.128.02,079.91/s12.01ms124,813summarysource
',145)),n(e,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration Guide",href:"/guide/configuration"}]})])}const b=r(i,[["render",o]]);export{m as __pageData,b as default}; diff --git a/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.lean.js b/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.lean.js new file mode 100644 index 000000000..c05852952 --- /dev/null +++ b/assets/blog_postgresql-rest-api-benchmark-2026.md.CJcg5B7z.lean.js @@ -0,0 +1 @@ +import{_ as r,C as s,c as a,o as l,a5 as d,G as n}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","titleTemplate":"NpgsqlRest","description":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. NpgsqlRest, PostgREST, Swoole PHP, Go, Rust, Express, FastAPI, Spring Boot and more tested under load.","frontmatter":{"layout":"doc","outline":[2,3],"title":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","titleTemplate":"NpgsqlRest","description":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. NpgsqlRest, PostgREST, Swoole PHP, Go, Rust, Express, FastAPI, Spring Boot and more tested under load.","head":[["meta",{"name":"keywords","content":"postgresql rest api benchmark, api performance comparison, npgsqlrest vs postgrest, fastapi vs express postgresql, spring boot postgresql performance, swoole php benchmark, rest api framework comparison 2026"}],["meta",{"property":"og:title","content":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"}],["meta",{"property":"og:description","content":"Performance comparison of 14 REST API frameworks serving PostgreSQL data. See how NpgsqlRest, PostgREST, Swoole PHP, and others perform under load."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"}],["meta",{"name":"twitter:description","content":"Performance comparison of 14 REST API frameworks serving PostgreSQL data under various load conditions."}]]},"headers":[],"relativePath":"blog/postgresql-rest-api-benchmark-2026.md","filePath":"blog/postgresql-rest-api-benchmark-2026.md"}'),i={name:"blog/postgresql-rest-api-benchmark-2026.md"};function o(g,t,h,p,_,c){const e=s("BlogNav");return l(),a("div",null,[t[0]||(t[0]=d("",145)),n(e,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration Guide",href:"/guide/configuration"}]})])}const b=r(i,[["render",o]]);export{m as __pageData,b as default}; diff --git a/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.js b/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.js new file mode 100644 index 000000000..61ecf01ef --- /dev/null +++ b/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.js @@ -0,0 +1,299 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","titleTemplate":"NpgsqlRest","description":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and Server-Sent Events. No WebSocket servers, no message brokers - just SQL procedures and TypeScript.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","titleTemplate":"NpgsqlRest","description":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and Server-Sent Events. No WebSocket servers, no message brokers - just SQL procedures and TypeScript.","head":[["meta",{"name":"keywords","content":"postgresql real-time chat, server-sent events postgresql, sse postgresql, postgresql listen notify, real-time notifications postgresql, npgsqlrest sse, postgresql websocket alternative"}],["meta",{"property":"og:title","content":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"}],["meta",{"property":"og:description","content":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and SSE. No WebSocket servers, no message brokers."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Real-Time Chat with PostgreSQL and Server-Sent Events"}],["meta",{"name":"twitter:description","content":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and SSE. No WebSocket servers needed."}]]},"headers":[],"relativePath":"blog/real-time-chat-postgresql-sse-npgsqlrest.md","filePath":"blog/real-time-chat-postgresql-sse-npgsqlrest.md"}'),p={name:"blog/real-time-chat-postgresql-sse-npgsqlrest.md"};function k(r,s,d,c,o,g){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l(`

Build a Real-Time Chat App with PostgreSQL and Server-Sent Events

January 2026 · SSEReal-TimePostgreSQLChatNpgsqlRest

Adding chat to an application usually means adding a WebSocket server, a message broker, pub/sub plumbing, and hundreds of lines of code spread across multiple services.

What if you could build a fully functional, secure real-time chat with just a single SQL procedure and a few lines of TypeScript?

This tutorial shows how NpgsqlRest's Server-Sent Events (SSE) support turns PostgreSQL RAISE statements into real-time events.

Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/8_simple_chat_client

The Traditional Approach: Complex Infrastructure

Building real-time chat the traditional way requires:

  1. WebSocket Server - Separate service to manage persistent connections
  2. Message Broker - Redis Pub/Sub, RabbitMQ, or similar for message distribution
  3. Connection Management - Track connected users, handle reconnections
  4. Authentication Integration - Validate tokens on WebSocket handshake
  5. Scaling Strategy - Sticky sessions or shared state for horizontal scaling
  6. Frontend WebSocket Client - Handle connection lifecycle, reconnection logic
  7. Backend API - REST endpoints for message history, user management
  8. Database Integration - Separate persistence layer for messages

That's eight moving parts, each one a failure point, a deployment concern, and development time.

The NpgsqlRest Approach: PostgreSQL IS Your Real-Time Server

NpgsqlRest inverts this. Instead of adding infrastructure, it uses what you already have:

The mechanism is mundane: PostgreSQL already supports sending messages during query execution via RAISE. NpgsqlRest captures these messages and streams them to connected clients via Server-Sent Events.

How SSE Works in NpgsqlRest

mermaid
flowchart TB
+    A["Alice's Browser"] & B["Bob's Browser"]
+    A -- "EventSource('/api/send-message/info?TEST_CHANNEL')" --> N
+    B -- "SSE Connection" --> N
+
+    N["NpgsqlRest
+    Maintains SSE connections for authorized clients
+    Filters events by scope (authorize, matching, all)"]
+
+    N -- "RAISE INFO JSON payload" --> P
+
+    P["PostgreSQL
+    PROCEDURE send_message() executes RAISE INFO
+    NpgsqlRest captures the notice and broadcasts"]

When an @sse-annotated PostgreSQL function or procedure executes RAISE INFO, RAISE NOTICE, or RAISE WARNING, NpgsqlRest captures the matching messages and streams them to connected SSE clients based on the configured scope. Procedures without @sse can RAISE all they want — those notices are never broadcast.

Internally, every @sse-annotated procedure publishes into a single process-wide broadcaster, and every connected EventSource reads from that same stream — the URL it opened is just an entry point. Filtering happens per event (scope, hint, optional execution ID), not per URL. This becomes important once you have more than one source of events; see Cross-procedure pattern in the SSE annotation reference.

Building the Chat: Step by Step

Step 1: Schema Setup

sql
sql
-- Users table with secure password hashing
+create table example_8.users (
+    user_id int primary key generated always as identity,
+    username text not null unique,
+    password_hash text not null
+);
+
+-- Insert test users (alice/password123, bob/password456)
+insert into example_8.users (username, password_hash) values
+    ('alice', crypt('password123', gen_salt('bf'))),
+    ('bob', crypt('password456', gen_salt('bf')));
+
+-- Messages table for chat history
+create table example_8.messages (
+    message_id int primary key generated always as identity,
+    user_id int not null references example_8.users(user_id),
+    username text not null,
+    message_text text not null,
+    created_at timestamptz not null default now()
+);

Step 2: Login Function

sql
sql
create function example_8.login(_username text, _password text)
+returns table (scheme text, user_id int, user_name text)
+language sql
+security definer
+begin atomic;
+    select 'cookies', u.user_id, u.username
+    from example_8.users u
+    where u.username = _username
+      and u.password_hash = crypt(_password, u.password_hash);
+end;
+
+comment on function example_8.login(text, text) is '
+HTTP POST
+@login
+@anonymous';

The login annotation creates a cookie-based session automatically.

Step 3: The Magic - Send Message with SSE

Here's the entire backend for real-time messaging:

sql
sql
create procedure example_8.send_message(
+    _message_text text,
+    _user_id text = null,
+    _user_name text = null
+)
+language plpgsql
+as $$
+declare
+    _message_id int;
+    _created_at timestamptz;
+begin
+    -- Store the message
+    insert into example_8.messages (user_id, username, message_text)
+    values (_user_id::int, _user_name, _message_text)
+    returning message_id, created_at into _message_id, _created_at;
+
+    -- Broadcast to all connected authorized clients via SSE
+    raise info '%', json_build_object(
+        'message_id', _message_id,
+        'user_id', _user_id::int,
+        'username', _user_name,
+        'message_text', _message_text,
+        'created_at', _created_at
+    );
+end;
+$$;
+
+comment on procedure example_8.send_message(text, text, text) is '
+HTTP POST
+@authorize
+@sse
+@sse_scope authorize';

That's it. The entire real-time messaging backend is 25 lines of SQL.

Three annotations do the work:

AnnotationPurpose
authorizeOnly authenticated users can send messages
sseTwo effects: (1) registers /info as an SSE connection URL, (2) makes this procedure's RAISEs feed the SSE broadcaster
sse_scope authorizePer-event filter: only authenticated subscribers receive events from this endpoint

The RAISE INFO statement with JSON payload becomes the SSE event data. NpgsqlRest automatically:

Step 4: Message History

sql
sql
create function example_8.get_messages()
+returns table (
+    message_id int,
+    user_id int,
+    username text,
+    message_text text,
+    created_at timestamptz
+)
+language sql
+begin atomic;
+    select message_id, user_id, username, message_text, created_at
+    from example_8.messages
+    order by created_at asc;
+end;
+
+comment on function example_8.get_messages() is '
+HTTP GET
+@authorize';

Understanding SSE Scopes

The sse_scope annotation controls who receives events:

sse_scope authorize

Only authenticated clients receive events. Perfect for private chat rooms.

sql
sql
comment on procedure private_broadcast() is '
+@sse
+@sse_scope authorize';

sse_scope authorize <roles/users>

Target specific roles or users:

sql
sql
-- Only admins receive these events
+comment on procedure admin_notification() is '
+@sse
+@sse_scope authorize admin';
+
+-- Specific users receive events
+comment on procedure user_alert() is '
+@sse
+@sse_scope authorize alice, bob';

sse_scope matching

Clients with matching security context receive events:

sql
sql
comment on procedure team_update() is '
+@sse
+@sse_scope matching';

sse_scope all

Broadcast to everyone (use carefully):

sql
sql
comment on procedure public_announcement() is '
+@sse
+@sse_scope all';

Dynamic Scopes with RAISE HINT

Override scope per-event at runtime:

sql
sql
-- This event goes to admins only
+raise notice 'Admin alert: server load high' using hint = 'authorize admin';
+
+-- This event goes to everyone
+raise notice 'System maintenance in 5 minutes' using hint = 'all';
+
+-- This uses the default scope from annotation
+raise notice 'Regular update...';

The Auto-Generated TypeScript Client

NpgsqlRest generates a complete TypeScript client including SSE support:

typescript
typescript
// Auto-generated EventSource factory
+export const createSendMessageEventSource = (id: string = "") =>
+    new EventSource(baseUrl + "/api/example-8/send-message/info?" + id);
+
+// Auto-generated send function with SSE support
+export async function sendMessage(
+    request: ISendMessageRequest,
+    onMessage?: (message: string) => void,
+    id: string | undefined = undefined,
+    closeAfterMs = 1000,
+    awaitConnectionMs: number | undefined = 0
+): Promise<{status: number, error: ... }> {
+    const executionId = id ? id : window.crypto.randomUUID();
+    let eventSource: EventSource;
+
+    if (onMessage) {
+        eventSource = createSendMessageEventSource(executionId);
+        eventSource.onmessage = (event: MessageEvent) => {
+            onMessage(event.data);
+        };
+        // ... connection handling
+    }
+
+    // ... fetch call with X-NpgsqlRest-ID header
+}

The Frontend: Minimal Code Required

Using the generated client, the frontend is straightforward:

typescript
typescript
import { login, logout, sendMessage, createSendMessageEventSource, getMessages }
+    from "./example8Api.ts";
+
+const channelName = "TEST_CHANNEL";
+let eventSource: EventSource | null = null;
+
+// Connect to SSE when user logs in
+function connectEventSource() {
+    eventSource = createSendMessageEventSource(channelName);
+
+    eventSource.onmessage = (event: MessageEvent) => {
+        const msg = JSON.parse(event.data);
+        appendMessage(msg);  // Display in UI
+    };
+}
+
+// Send a message - it will be broadcast to all connected clients
+async function sendChatMessage() {
+    const messageText = messageInput.value.trim();
+    if (!messageText) return;
+
+    messageInput.value = "";
+
+    await sendMessage(
+        { messageText },
+        undefined,      // Skip local onMessage (we're already connected)
+        channelName     // Channel identifier
+    );
+}
+
+// Disconnect when logging out
+function disconnectEventSource() {
+    if (eventSource) {
+        eventSource.close();
+        eventSource = null;
+    }
+}

Code Comparison: Traditional vs NpgsqlRest

Traditional Real-Time Chat Architecture

Backend (Node.js + Socket.IO + Redis):

javascript
javascript
// server.js - WebSocket server
+const io = require('socket.io')(server);
+const redis = require('redis');
+const pub = redis.createClient();
+const sub = redis.createClient();
+
+// Authentication middleware
+io.use(async (socket, next) => {
+    const token = socket.handshake.auth.token;
+    try {
+        const user = await verifyToken(token);
+        socket.user = user;
+        next();
+    } catch (err) {
+        next(new Error('Authentication failed'));
+    }
+});
+
+// Connection handling
+io.on('connection', (socket) => {
+    const userId = socket.user.id;
+
+    // Join user's room
+    socket.join(\`user:\${userId}\`);
+
+    // Handle chat messages
+    socket.on('chat:message', async (data) => {
+        // Save to database
+        const message = await db.messages.create({
+            userId: socket.user.id,
+            username: socket.user.username,
+            text: data.text,
+            createdAt: new Date()
+        });
+
+        // Broadcast via Redis pub/sub
+        pub.publish('chat:messages', JSON.stringify(message));
+    });
+
+    // Handle disconnection
+    socket.on('disconnect', () => {
+        console.log(\`User \${userId} disconnected\`);
+    });
+});
+
+// Redis subscription for horizontal scaling
+sub.subscribe('chat:messages');
+sub.on('message', (channel, message) => {
+    const msg = JSON.parse(message);
+    io.emit('chat:message', msg);
+});

Plus you need:

Frontend (Socket.IO client):

javascript
javascript
import { io } from 'socket.io-client';
+
+const socket = io('http://localhost:3000', {
+    auth: { token: getAuthToken() },
+    reconnection: true,
+    reconnectionAttempts: 5,
+    reconnectionDelay: 1000
+});
+
+socket.on('connect', () => {
+    console.log('Connected');
+    loadMessageHistory();
+});
+
+socket.on('chat:message', (msg) => {
+    appendMessage(msg);
+});
+
+socket.on('disconnect', () => {
+    showDisconnected();
+});
+
+socket.on('connect_error', (err) => {
+    handleConnectionError(err);
+});
+
+function sendMessage(text) {
+    socket.emit('chat:message', { text });
+}

NpgsqlRest Approach

Backend (SQL only):

sql
sql
create procedure example_8.send_message(
+    _message_text text,
+    _user_id text = null,
+    _user_name text = null
+)
+language plpgsql
+as $$
+declare
+    _message_id int;
+    _created_at timestamptz;
+begin
+    insert into example_8.messages (user_id, username, message_text)
+    values (_user_id::int, _user_name, _message_text)
+    returning message_id, created_at into _message_id, _created_at;
+
+    raise info '%', json_build_object(
+        'message_id', _message_id,
+        'user_id', _user_id::int,
+        'username', _user_name,
+        'message_text', _message_text,
+        'created_at', _created_at
+    );
+end;
+$$;
+
+comment on procedure example_8.send_message(text, text, text) is '
+HTTP POST
+@authorize
+@sse
+@sse_scope authorize';

Frontend (using generated client):

typescript
typescript
import { sendMessage, createSendMessageEventSource } from "./example8Api.ts";
+
+const eventSource = createSendMessageEventSource(channelName);
+
+eventSource.onmessage = (event) => {
+    appendMessage(JSON.parse(event.data));
+};
+
+async function send(text: string) {
+    await sendMessage({ messageText: text }, undefined, channelName);
+}

The Numbers

ComponentTraditionalNpgsqlRest
Backend code100-200 lines25 lines (SQL)
Frontend code50-80 lines20 lines
InfrastructureWebSocket server + RedisNone (PostgreSQL only)
Dependenciessocket.io, redis, jwt, etc.None additional
Services to deploy3+ (API, WebSocket, Redis)1 (NpgsqlRest)
TypeScript typesManualAuto-generated
Auth integrationCustom middlewareBuilt-in (cookies)
Horizontal scalingRedis pub/sub requiredWorks out of the box
Time to implement1-3 days30 minutes

Estimated savings: 85-90% less code, single deployment, zero additional infrastructure.

When to Use SSE vs WebSockets

SSE (Server-Sent Events) is ideal for:

WebSockets are better for:

For most real-time features (chat, notifications, dashboards), SSE is simpler and sufficient.

Why Not PostgreSQL LISTEN/NOTIFY?

A common question: why use RAISE INFO instead of PostgreSQL's built-in LISTEN/NOTIFY for real-time events?

LISTEN/NOTIFY has a critical scalability problem. When you execute NOTIFY during a transaction, PostgreSQL acquires a global lock on the entire database during the commit phase. This serializes all commits across your system.

As documented by Recall.ai, this creates severe issues under high concurrency:

Their load testing showed that removing NOTIFY allowed full CPU utilization and rapid recovery from load spikes, while NOTIFY caused the database to grind to a halt.

How NpgsqlRest Avoids This Problem

NpgsqlRest's SSE implementation uses RAISE INFO/NOTICE/WARNING instead of NOTIFY:

AspectLISTEN/NOTIFYRAISE + SSE
LockingGlobal database lock on commitNo additional locking
ScalabilitySerializes all commitsScales with connections
DeliveryRequires dedicated listener connectionHTTP streaming (standard)
PersistenceFire-and-forget (can lose messages)Immediate streaming
Connection modelLong-lived DB connectionsStandard HTTP connections
Client implementationCustom pg_notify clientStandard EventSource API

RAISE statements are connection-local - they emit notices to the current connection's notice handler without any global coordination. NpgsqlRest captures these notices during query execution and streams them to SSE clients. No locks, no contention, no scalability ceiling.

This is why NpgsqlRest uses RAISE instead of NOTIFY for real-time events.

Advanced: Execution-ID correlation as soft channels

The X-NpgsqlRest-ID header was designed for request correlation — letting the client receive only the events fired during its own POST. When both the connection's query string and the request's header carry the same ID, NpgsqlRest filters out events whose IDs don't match.

You can lean on that mechanism to build channel-like behavior, as long as every emitter sets the header and every listener sets the query string:

typescript
typescript
// Listeners — each connection only receives events tagged with its own ID
+const generalChat = createSendMessageEventSource("general");
+const teamChat    = createSendMessageEventSource("team-123");
+const notifications = createSendMessageEventSource("notifications");
+
+// Sender — pass the same ID; the generated client puts it in X-NpgsqlRest-ID
+await sendMessage({ messageText: "Hello team!" }, undefined, "team-123");

Caveat

This is a soft filter. If the listener has no execution ID, or the emitter doesn't set the header, the filter is bypassed and the listener will see the event regardless. For truly isolated streams you need scope/hint filtering (per-user, per-role, etc.) rather than execution IDs alone.

Conclusion

NpgsqlRest's SSE support turns PostgreSQL's notice system into real-time messaging: RAISE INFO emits the events, annotations control who receives them, and the same cookie auth covers both your REST API and the event stream. For chat, notifications, and live dashboards, that means one deployment and zero extra infrastructure. Reserve WebSockets for the cases that genuinely need them - bidirectional high-frequency traffic or binary data - and use SSE for everything else.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,98)),h(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/8_simple_chat_client","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"SSE Annotation",href:"/annotations/sse"},{text:"SSE Scope",href:"/annotations/sse-events-scope"},{text:"SSE Level",href:"/annotations/sse-events-level"},{text:"Code Generation",href:"/config/codegen"}]})])}const C=a(p,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.lean.js b/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.lean.js new file mode 100644 index 000000000..dec6225df --- /dev/null +++ b/assets/blog_real-time-chat-postgresql-sse-npgsqlrest.md.Fd5M97qI.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","titleTemplate":"NpgsqlRest","description":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and Server-Sent Events. No WebSocket servers, no message brokers - just SQL procedures and TypeScript.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","titleTemplate":"NpgsqlRest","description":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and Server-Sent Events. No WebSocket servers, no message brokers - just SQL procedures and TypeScript.","head":[["meta",{"name":"keywords","content":"postgresql real-time chat, server-sent events postgresql, sse postgresql, postgresql listen notify, real-time notifications postgresql, npgsqlrest sse, postgresql websocket alternative"}],["meta",{"property":"og:title","content":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"}],["meta",{"property":"og:description","content":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and SSE. No WebSocket servers, no message brokers."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Real-Time Chat with PostgreSQL and Server-Sent Events"}],["meta",{"name":"twitter:description","content":"Build real-time chat with PostgreSQL LISTEN/NOTIFY and SSE. No WebSocket servers needed."}]]},"headers":[],"relativePath":"blog/real-time-chat-postgresql-sse-npgsqlrest.md","filePath":"blog/real-time-chat-postgresql-sse-npgsqlrest.md"}'),p={name:"blog/real-time-chat-postgresql-sse-npgsqlrest.md"};function k(r,s,d,c,o,g){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l("",98)),h(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/8_simple_chat_client","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"SSE Annotation",href:"/annotations/sse"},{text:"SSE Scope",href:"/annotations/sse-events-scope"},{text:"SSE Level",href:"/annotations/sse-events-level"},{text:"Code Generation",href:"/config/codegen"}]})])}const C=a(p,[["render",k]]);export{F as __pageData,C as default}; diff --git a/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.js b/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.js new file mode 100644 index 000000000..84f65232c --- /dev/null +++ b/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.js @@ -0,0 +1,444 @@ +import{_ as a,C as n,c as l,o as e,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data, avoid connection pool exhaustion. No middleware required.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data, avoid connection pool exhaustion. No middleware required.","head":[["meta",{"name":"keywords","content":"postgresql reverse proxy, api gateway postgresql, cache ai responses postgresql, microservices aggregation, npgsqlrest proxy, postgresql external api, connection pool optimization"}],["meta",{"property":"og:title","content":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"}],["meta",{"property":"og:description","content":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data without middleware."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Reverse Proxy in PostgreSQL: Gateway to External Services"}],["meta",{"name":"twitter:description","content":"Proxy external services through PostgreSQL. Cache AI responses, aggregate microservices data."}]]},"headers":[],"relativePath":"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md","filePath":"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md"}'),h={name:"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md"};function k(r,s,d,c,y,F){const i=n("BlogNav");return e(),l("div",null,[s[0]||(s[0]=t(`

Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest

Proxy · API Gateway · Microservices · Caching · January 2026


Not all API endpoints need a database connection. Health checks, static responses, proxied requests to microservices - these operations consume connection pool slots without ever touching PostgreSQL.

NpgsqlRest's Reverse Proxy feature addresses this: define endpoints in SQL that forward requests to upstream services, transform the responses in PostgreSQL when needed, and skip the database connection entirely when you don't - same annotation-driven workflow throughout. That covers everything from pass-through health checks to caching layers in front of expensive AI service calls.

This tutorial builds an AI Text Analysis Service that combines a local AI processing server with PostgreSQL-backed caching and enrichment - without spending connection pool slots where they aren't needed.

Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/10_proxy_ai_service

The Problem: Connection Pool Exhaustion

Every NpgsqlRest endpoint normally opens a database connection. That's fine for data operations, but not every endpoint is one:

Each of these consumes a connection from the pool, even when no database operation is needed. Under high load:

The NpgsqlRest Solution: Proxy Mode

The Reverse Proxy feature offers two modes:

Passthrough Mode: Zero Database Connections

When a proxy endpoint function has no proxy response parameters, NpgsqlRest:

sql
sql
-- This function body is NEVER executed
+-- The proxy annotation handles everything
+create function ai_health()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function ai_health is '
+HTTP GET /ai/health
+@proxy';

When a client calls /ai/health, NpgsqlRest forwards to the configured upstream host, receives the response, and returns it - no PostgreSQL involved.

Transform Mode: Process Before Returning

When a proxy endpoint function has proxy response parameters (_proxy_body, _proxy_status_code, etc.), NpgsqlRest:

sql
sql
create function ai_summarize(
+    _text text,
+    _max_length int default 150,
+    -- Proxy response parameters - these trigger transform mode
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null,
+    _proxy_error_message text default null
+)
+returns json
+language plpgsql
+as $$
+declare
+    _result json;
+begin
+    -- Check cache first
+    select cached_response into _result
+    from analysis_cache
+    where text_hash = md5(_text);
+
+    if _result is not null then
+        return _result;  -- Cache hit - no upstream call was made
+    end if;
+
+    -- Upstream response is in _proxy_body
+    if not _proxy_success then
+        return json_build_object('error', _proxy_error_message);
+    end if;
+
+    -- Cache and return
+    insert into analysis_cache (text_hash, cached_response)
+    values (md5(_text), _proxy_body::json);
+
+    return _proxy_body::json;
+end;
+$$;
+
+comment on function ai_summarize is '
+HTTP POST /ai/summarize
+@authorize
+@proxy POST';

Architecture: NpgsqlRest as API Gateway

mermaid
flowchart TB
+    C["Client Request"] --> N["NpgsqlRest Server
+    (Reverse Proxy Layer)"]
+
+    N --> PT["Passthrough Proxy
+    No DB Conn · Health, etc"]
+    N --> TR["Transform Proxy
+    DB + Cache · AI, APIs"]
+    N --> RE["Regular Endpoint
+    DB Only · SQL/Functions"]
+
+    PT --> U1["Upstream Service
+    (AI Server)"]
+    TR --> U2["Upstream Service
+    (AI Server)"]
+
+    U2 --> PG["PostgreSQL
+    (Cache)"]

Building the AI Text Analysis Service

The example API proxies to a local AI text processing service (running on Bun), with PostgreSQL as the cache.

The Upstream AI Service

First, we need a service to proxy to. This Bun server provides text analysis:

typescript
typescript
// upstream/server.ts
+const PORT = 3001;
+
+const server = Bun.serve({
+    port: PORT,
+    async fetch(req) {
+        const url = new URL(req.url);
+        const path = url.pathname;
+
+        // Health check
+        if (path === '/ai/health' && req.method === 'GET') {
+            return Response.json({
+                status: 'healthy',
+                service: 'ai-text-service',
+                version: '1.0.0'
+            });
+        }
+
+        // Summarization
+        if (path === '/ai/summarize' && req.method === 'POST') {
+            const { text, max_length = 150 } = await req.json();
+            const summary = summarizeText(text, max_length);
+            return Response.json({
+                summary,
+                original_length: text.length,
+                summary_length: summary.length,
+                model: 'simple-extractive-v1'
+            });
+        }
+
+        // Sentiment analysis
+        if (path === '/ai/sentiment' && req.method === 'POST') {
+            const { text } = await req.json();
+            const result = analyzeSentiment(text);
+            return Response.json({
+                ...result,
+                model: 'simple-lexicon-v1'
+            });
+        }
+
+        // Full analysis
+        if (path === '/ai/analyze' && req.method === 'POST') {
+            const { text, max_length = 150, max_keywords = 5 } = await req.json();
+            return Response.json({
+                summary: { text: summarizeText(text, max_length) },
+                sentiment: analyzeSentiment(text),
+                keywords: { words: extractKeywords(text, max_keywords) },
+                model: 'combined-analysis-v1',
+                processed_at: new Date().toISOString()
+            });
+        }
+
+        return Response.json({ error: 'Not found' }, { status: 404 });
+    }
+});

This simulates what a real AI/ML service would provide - in production, this could be:

PostgreSQL Schema: Caching Layer

sql
sql
create table example_10.analysis_cache (
+    id serial primary key,
+    text_hash text not null,
+    text_preview text not null,
+    summary text,
+    sentiment text,
+    sentiment_score numeric(4,2),
+    sentiment_confidence numeric(4,2),
+    keywords text[],
+    model_version text,
+    created_at timestamptz default now(),
+    accessed_count int default 1,
+    last_accessed_at timestamptz default now()
+);
+
+create unique index idx_analysis_cache_hash
+    on example_10.analysis_cache(text_hash);

Passthrough Proxy: Health Check

For health checks, we don't need PostgreSQL at all:

sql
sql
create function example_10.ai_health()
+returns void
+language sql
+begin atomic;
+select;
+end;
+
+comment on function example_10.ai_health is '
+HTTP GET /ai/health
+@proxy';

The trigger: the function returns void and has no proxy parameters. NpgsqlRest detects this and uses passthrough mode - no database connection is opened.

When the load balancer pings /ai/health every 5 seconds, it gets the upstream response directly without touching the connection pool.

Transform Proxy: Summarization with Caching

For summarization, we want to cache results to avoid repeated AI calls:

sql
sql
create function example_10.ai_summarize(
+    _text text,
+    _max_length int default 150,
+    -- Proxy response parameters trigger transform mode
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null,
+    _proxy_error_message text default null
+)
+returns json
+language plpgsql
+as $$
+declare
+    _text_hash text;
+    _cached json;
+    _result json;
+begin
+    -- Generate cache key
+    _text_hash := md5(_text || '::' || _max_length::text);
+
+    -- Check cache first
+    select json_build_object(
+        'summary', ac.summary,
+        'original_length', length(_text),
+        'summary_length', length(ac.summary),
+        'cached', true,
+        'cache_hits', ac.accessed_count
+    )
+    into _cached
+    from example_10.analysis_cache ac
+    where ac.text_hash = _text_hash;
+
+    if _cached is not null then
+        -- Update cache stats
+        update example_10.analysis_cache
+        set accessed_count = accessed_count + 1,
+            last_accessed_at = now()
+        where text_hash = _text_hash;
+
+        return _cached;  -- Return cached result
+    end if;
+
+    -- Handle proxy errors
+    if not _proxy_success then
+        return json_build_object(
+            'error', coalesce(_proxy_error_message, 'AI service unavailable'),
+            'status_code', _proxy_status_code
+        );
+    end if;
+
+    -- Parse and cache the response
+    _result := _proxy_body::json;
+
+    insert into example_10.analysis_cache
+        (text_hash, text_preview, summary, model_version)
+    values (
+        _text_hash,
+        left(_text, 100),
+        _result->>'summary',
+        _result->>'model'
+    );
+
+    -- Return enriched response
+    return json_build_object(
+        'summary', _result->>'summary',
+        'original_length', (_result->>'original_length')::int,
+        'summary_length', (_result->>'summary_length')::int,
+        'model', _result->>'model',
+        'cached', false
+    );
+end;
+$$;
+
+comment on function example_10.ai_summarize is '
+HTTP POST /ai/summarize
+@authorize
+@proxy POST';

What happens:

  1. Client calls POST /ai/summarize with {"text": "..."}
  2. NpgsqlRest forwards to upstream: POST http://localhost:3001/ai/summarize
  3. Upstream returns the AI analysis
  4. NpgsqlRest passes the response to our function via _proxy_body
  5. Our function checks cache, stores if new, returns result
  6. Client receives the response

On cache hit:

Full Analysis: Complete Transform Example

sql
sql
create function example_10.ai_analyze(
+    _text text,
+    _max_length int default 150,
+    _max_keywords int default 5,
+    _proxy_status_code int default null,
+    _proxy_body text default null,
+    _proxy_success boolean default null,
+    _proxy_error_message text default null
+)
+returns json
+language plpgsql
+as $$
+declare
+    _text_hash text;
+    _cached record;
+    _result json;
+begin
+    -- Cache key includes all parameters
+    _text_hash := md5(_text || '::full::' || _max_length || '::' || _max_keywords);
+
+    -- Check for complete cached analysis
+    select summary, sentiment, sentiment_score,
+           sentiment_confidence, keywords, accessed_count
+    into _cached
+    from example_10.analysis_cache
+    where text_hash = _text_hash
+      and summary is not null
+      and sentiment is not null
+      and keywords is not null;
+
+    if found then
+        -- Update stats and return cached
+        update example_10.analysis_cache
+        set accessed_count = accessed_count + 1,
+            last_accessed_at = now()
+        where text_hash = _text_hash;
+
+        return json_build_object(
+            'summary', json_build_object('text', _cached.summary),
+            'sentiment', json_build_object(
+                'sentiment', _cached.sentiment,
+                'score', _cached.sentiment_score,
+                'confidence', _cached.sentiment_confidence
+            ),
+            'keywords', json_build_object(
+                'words', to_json(_cached.keywords),
+                'count', array_length(_cached.keywords, 1)
+            ),
+            'cached', true,
+            'cache_hits', _cached.accessed_count
+        );
+    end if;
+
+    -- Handle errors
+    if not _proxy_success then
+        return json_build_object(
+            'error', coalesce(_proxy_error_message, 'AI service unavailable'),
+            'status_code', _proxy_status_code
+        );
+    end if;
+
+    -- Parse and cache
+    _result := _proxy_body::json;
+
+    insert into example_10.analysis_cache (
+        text_hash, text_preview, summary, sentiment,
+        sentiment_score, sentiment_confidence, keywords, model_version
+    ) values (
+        _text_hash,
+        left(_text, 100),
+        _result->'summary'->>'text',
+        _result->'sentiment'->>'sentiment',
+        (_result->'sentiment'->>'score')::numeric,
+        (_result->'sentiment'->>'confidence')::numeric,
+        array(select jsonb_array_elements_text(
+            (_result->'keywords'->'words')::jsonb)),
+        _result->>'model'
+    )
+    on conflict (text_hash) do update set
+        summary = excluded.summary,
+        sentiment = excluded.sentiment,
+        sentiment_score = excluded.sentiment_score,
+        sentiment_confidence = excluded.sentiment_confidence,
+        keywords = excluded.keywords,
+        accessed_count = example_10.analysis_cache.accessed_count + 1,
+        last_accessed_at = now();
+
+    return json_build_object(
+        'summary', _result->'summary',
+        'sentiment', _result->'sentiment',
+        'keywords', _result->'keywords',
+        'model', _result->>'model',
+        'processed_at', _result->>'processed_at',
+        'cached', false
+    );
+end;
+$$;
+
+comment on function example_10.ai_analyze is '
+HTTP POST /ai/analyze
+@authorize
+@proxy POST';

Configuration

Enable proxy in your configuration:

json
json
{
+  "NpgsqlRest": {
+    "ProxyOptions": {
+      "Enabled": true,
+      "Host": "http://localhost:3001",
+      "DefaultTimeout": "00:00:30",
+      "ForwardHeaders": true,
+      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
+      "ForwardResponseHeaders": true
+    }
+  }
+}
OptionDescription
HostDefault upstream URL - can be overridden per endpoint
DefaultTimeoutRequest timeout for proxy calls
ForwardHeadersPass client headers to upstream
ExcludeHeadersHeaders to strip from forwarded requests
ForwardResponseHeadersPass upstream headers to client

Proxy Response Parameters

When your function includes these parameters, it enters transform mode:

ParameterTypeDescription
_proxy_status_codeintHTTP status from upstream (200, 404, etc.)
_proxy_bodytextResponse body content
_proxy_headersjsonResponse headers as JSON object
_proxy_content_typetextContent-Type header value
_proxy_successbooleanTrue for 2xx status codes
_proxy_error_messagetextError description if request failed

Parameter names are configurable in ProxyOptions.

The Generated TypeScript Client

NpgsqlRest auto-generates a typed client:

typescript
typescript
// Auto-generated
+interface IAiAnalyzeRequest {
+    text: string | null;
+    maxLength?: number | null;
+    maxKeywords?: number | null;
+}
+
+export async function aiAnalyze(
+    request: IAiAnalyzeRequest
+): Promise<{
+    status: number,
+    response: any,
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    const response = await fetch(baseUrl + "/ai/analyze", {
+        method: "POST",
+        body: JSON.stringify(request)
+    });
+    return {
+        status: response.status,
+        response: response.ok ? await response.json() : undefined,
+        error: !response.ok ? await response.json() : undefined
+    };
+}

Frontend usage:

typescript
typescript
import { aiAnalyze, aiHealth } from "./example10Api.ts";
+
+// Check service health (passthrough - no DB connection)
+const health = await aiHealth();
+if (health.status === 200) {
+    console.log("AI service is online");
+}
+
+// Analyze text (transform - with caching)
+const result = await aiAnalyze({
+    text: "PostgreSQL is an excellent database...",
+    maxLength: 150,
+    maxKeywords: 5
+});
+
+if (result.response.cached) {
+    console.log(\`Cache hit! \${result.response.cache_hits} previous accesses\`);
+} else {
+    console.log("Fresh analysis from AI service");
+}

Docker: Bun Runtime Image

For deployments where the upstream service runs in the same container, NpgsqlRest provides a Docker image with pre-installed Bun:

bash
bash
docker pull vbilopav/npgsqlrest:latest-bun
+
+docker run --name npgsqlrest-bun -it \\
+    -p 8080:8080 \\
+    -v ./config.json:/app/config.json \\
+    -v ./upstream:/app/upstream \\
+    vbilopav/npgsqlrest:latest-bun

This image includes the Bun JavaScript runtime, so proxy endpoints can execute Bun scripts within the same container. Useful for:

Use Cases for Reverse Proxy

API Gateway Pattern

Route requests to different microservices:

sql
sql
-- Users service
+create function users_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+comment on function users_api is '
+HTTP GET /api/users
+@proxy https://users-service.internal:8080';
+
+-- Orders service
+create function orders_api()
+returns void
+language sql
+begin atomic;
+select;
+end;
+comment on function orders_api is '
+HTTP GET /api/orders
+@proxy https://orders-service.internal:8080';

Caching Expensive Operations

Cache AI, ML, or expensive API calls:

sql
sql
-- First call: fetch from upstream, cache in PostgreSQL
+-- Subsequent calls: return cached result instantly

Data Enrichment

Combine external data with local database data:

sql
sql
create function get_enriched_weather(
+    city text,
+    _proxy_body text default null,
+    _proxy_success boolean default null
+)
+returns json
+language plpgsql as $$
+declare
+    local_prefs json;
+begin
+    -- Get user's city preferences from database
+    select json_build_object('favorite', is_favorite, 'notes', notes)
+    into local_prefs
+    from user_city_preferences
+    where city_name = city;
+
+    -- Combine with weather data from proxy
+    return json_build_object(
+        'weather', _proxy_body::json,
+        'local', coalesce(local_prefs, '{}'::json)
+    );
+end;
+$$;
+
+comment on function get_enriched_weather is '
+HTTP GET /weather/{city}
+@proxy https://api.weather.com/v1/current';

Authentication Context Forwarding

Forward authenticated user claims to upstream:

sql
sql
create function secure_api_call(
+    _proxy_body text default null
+)
+returns json language plpgsql as $$
+begin
+    return _proxy_body::json;
+end;
+$$;
+
+comment on function secure_api_call is '
+HTTP GET /secure-data
+@authorize
+@user_context
+@proxy https://internal-api.example.com/data';

With user_context, NpgsqlRest forwards user claims as HTTP headers to the upstream service.

The Numbers

MetricWithout ProxyWith Passthrough Proxy
Health check DB connections1 per request0
Connection pool usage100%Reduced by passthrough %
Response latency (cached)~5-10msSame (transform mode)
Response latency (passthrough)~5-10ms~2-3ms (no DB)

For a service with 50% health check traffic:

Code Comparison

ComponentTraditional Node.jsNpgsqlRest Proxy
Proxy middleware~50 lines0
Caching logic~40 linesIn SQL function
HTTP client setup~30 linesConfig only
Error handling~30 linesBuilt-in
Type definitions~20 linesAuto-generated
Total~170 lines~80 lines SQL

Plus the upstream service remains identical - NpgsqlRest adds the gateway layer without changing your services.

Summary: When to Use Proxy Mode

Use Passthrough Mode for:

Use Transform Mode for:

Proxy mode is NOT for:

The Reverse Proxy feature extends NpgsqlRest beyond a database-to-API bridge into a full API gateway - preserving the annotation-driven, SQL-first workflow while adding routing, transformation, and caching on top.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,96)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/10_proxy_ai_service",documentation:[{text:"Proxy Annotation",href:"/annotations/proxy"},{text:"Proxy Configuration",href:"/config/proxy"},{text:"Docker Bun Image",href:"/guide/installation#bun-runtime-image"}],"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration",href:"/guide/configuration"}]})])}const C=a(h,[["render",k]]);export{o as __pageData,C as default}; diff --git a/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.lean.js b/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.lean.js new file mode 100644 index 000000000..6af4a0c81 --- /dev/null +++ b/assets/blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md.BtaYnMBE.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as e,a5 as t,G as p}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data, avoid connection pool exhaustion. No middleware required.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","titleTemplate":"NpgsqlRest","description":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data, avoid connection pool exhaustion. No middleware required.","head":[["meta",{"name":"keywords","content":"postgresql reverse proxy, api gateway postgresql, cache ai responses postgresql, microservices aggregation, npgsqlrest proxy, postgresql external api, connection pool optimization"}],["meta",{"property":"og:title","content":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"}],["meta",{"property":"og:description","content":"Proxy external services through PostgreSQL functions. Cache AI responses, aggregate microservices data without middleware."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Reverse Proxy in PostgreSQL: Gateway to External Services"}],["meta",{"name":"twitter:description","content":"Proxy external services through PostgreSQL. Cache AI responses, aggregate microservices data."}]]},"headers":[],"relativePath":"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md","filePath":"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md"}'),h={name:"blog/reverse-proxy-postgresql-ai-service-npgsqlrest.md"};function k(r,s,d,c,y,F){const i=n("BlogNav");return e(),l("div",null,[s[0]||(s[0]=t("",96)),p(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/10_proxy_ai_service",documentation:[{text:"Proxy Annotation",href:"/annotations/proxy"},{text:"Proxy Configuration",href:"/config/proxy"},{text:"Docker Bun Image",href:"/guide/installation#bun-runtime-image"}],"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Installation",href:"/guide/installation"},{text:"Configuration",href:"/guide/configuration"}]})])}const C=a(h,[["render",k]]);export{o as __pageData,C as default}; diff --git a/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.js b/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.js new file mode 100644 index 000000000..32eed367c --- /dev/null +++ b/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.js @@ -0,0 +1,295 @@ +import{_ as a,C as n,c as l,o as t,a5 as h,G as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Secure Image Uploads with PostgreSQL: File System and Large Objects","titleTemplate":"NpgsqlRest","description":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects, with automatic TypeScript client and progress tracking.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Secure Image Uploads with PostgreSQL: File System and Large Objects","titleTemplate":"NpgsqlRest","description":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects, with automatic TypeScript client and progress tracking.","head":[["meta",{"name":"keywords","content":"postgresql image upload, file upload postgresql, postgresql large objects, secure file upload api, npgsqlrest uploads, postgresql blob storage, typescript file upload"}],["meta",{"property":"og:title","content":"Secure Image Uploads with PostgreSQL: File System and Large Objects"}],["meta",{"property":"og:description","content":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Secure Image Uploads with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Build image uploads with PostgreSQL. File system or Large Objects storage."}]]},"headers":[],"relativePath":"blog/secure-image-uploads-postgresql-typescript.md","filePath":"blog/secure-image-uploads-postgresql-typescript.md"}'),p={name:"blog/secure-image-uploads-postgresql-typescript.md"};function k(r,s,d,g,y,F){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=h(`

Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript

January 2026 · UploadsPostgreSQLTypeScriptNpgsqlRest

Image uploads in NpgsqlRest come down to one SQL function and one annotation: the annotation picks the storage - file system, PostgreSQL Large Objects, or both - and the function records the metadata. The TypeScript client, progress tracking included, is generated for you.

Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/6_image_uploads

Storage Options

StrategyUse WhenStored In
File SystemFast CDN delivery neededDisk files
Large ObjectDatabase backup requiredPostgreSQL pg_largeobject
CombinedNeed both speed and backupBoth locations

When to Use Each Strategy

File System - Use when:

Large Object - Use when:

Combined - Use when:

Step 1: Create the Schema

Create a table to track uploads and a response type for type-safe TypeScript generation:

sql
sql
-- Unified uploads table
+create table example_6.uploads (
+    id int primary key generated always as identity,
+    user_id int not null,
+    file_name text not null,
+    content_type text not null,
+    file_size bigint not null,
+    oid bigint,           -- Large Object identifier (when using LO handler)
+    file_path text,       -- File path (when using FS handler)
+    uploaded_at timestamptz not null default now()
+);
+
+-- Response type for generated TypeScript interfaces
+create type example_6.upload_response as (
+    success boolean,
+    status text,
+    file_name text,
+    content_type text,
+    file_size bigint,
+    oid bigint,
+    file_path text
+);

The oid and file_path columns are nullable - each upload populates one or both depending on which handler is used.

Step 2: Configure Upload Handlers

In config.json, enable the upload handlers:

json
json
{
+  "NpgsqlRest": {
+    "UploadOptions": {
+      "Enabled": true,
+      "UseDefaultUploadMetadataParameter": true,
+      "DefaultUploadMetadataParameterName": "_meta",
+
+      "UploadHandlers": {
+        "StopAfterFirstSuccess": false,
+        "BufferSize": 16384,
+
+        "LargeObjectEnabled": true,
+        "LargeObjectKey": "large_object",
+        "LargeObjectCheckImage": true,
+
+        "FileSystemEnabled": true,
+        "FileSystemKey": "file_system",
+        "FileSystemPath": "./uploads",
+        "FileSystemUseUniqueFileName": true,
+        "FileSystemCreatePathIfNotExists": true,
+        "FileSystemCheckImage": true
+      }
+    }
+  }
+}

See upload configuration for all options.

Step 3: Create the Upload Function

All three upload functions use identical code - only the annotation changes.

sql
sql
create or replace function example_6.upload_to_file_system(
+    _user_id text = null,
+    _meta json = null
+)
+returns setof example_6.upload_response
+language sql
+begin atomic;
+    -- Insert successful uploads into the database
+    with inserted as (
+        insert into example_6.uploads (user_id, file_name, content_type, file_size, oid, file_path)
+        select
+            _user_id::int,
+            m->>'fileName',
+            m->>'contentType',
+            (m->>'size')::bigint,
+            (m->>'oid')::bigint,
+            m->>'filePath'
+        from json_array_elements(_meta) as m
+        where (m->>'success')::boolean = true
+        returning *
+    )
+    -- Return all upload results to the client
+    select
+        (m->>'success')::boolean,
+        m->>'status',
+        m->>'fileName',
+        m->>'contentType',
+        (m->>'size')::bigint,
+        (m->>'oid')::bigint,
+        m->>'filePath'
+    from json_array_elements(_meta) as m;
+end;

The function:

  1. Receives upload metadata in _meta parameter (injected by NpgsqlRest)
  2. Inserts successful uploads into the database
  3. Returns all results to the client

Step 4: Add the Upload Annotation

The annotation in the function comment controls which handler processes uploads:

sql
sql
-- File System storage
+comment on function example_6.upload_to_file_system(text, json) is '
+HTTP POST
+@upload for file_system
+@param _meta is upload metadata
+@check_image = true
+@path = ./uploads
+@unique_name = true';
+
+-- Large Object storage
+comment on function example_6.upload_to_large_object(text, json) is '
+HTTP POST
+@upload for large_object
+@param _meta is upload metadata
+@check_image = true';
+
+-- Combined (both handlers)
+comment on function example_6.upload_to_combined(text, json) is '
+HTTP POST
+@upload for large_object, file_system
+@param _meta is upload metadata
+@check_image = true
+@path = ./uploads
+@unique_name = true';

Key annotations:

See upload annotations for all options.

How the Metadata Works

When you upload a file, NpgsqlRest processes it through the handler and passes metadata to your function:

json
json
[
+  {
+    "type": "file_system",
+    "fileName": "photo.jpg",
+    "contentType": "image/jpeg",
+    "size": 245678,
+    "filePath": "./uploads/abc123-photo.jpg",
+    "success": true,
+    "status": "Ok"
+  }
+]

Which fields are populated depends on the handler:

When Uploads Fail

Failed uploads still appear in the metadata with success: false and a status code:

json
json
{
+  "type": "file_system",
+  "fileName": "document.pdf",
+  "contentType": "application/pdf",
+  "size": 102400,
+  "filePath": null,
+  "success": false,
+  "status": "InvalidImage"
+}

Status values:

Step 5: Use the Generated Client

NpgsqlRest generates a TypeScript client with progress tracking:

typescript
typescript
import { uploadToFileSystem } from "./example6Api.ts";
+
+const fileInput = document.getElementById("file-input") as HTMLInputElement;
+
+const response = await uploadToFileSystem(
+    fileInput.files,
+    { },
+    (loaded, total) => {
+        progressBar.style.width = \`\${Math.round((loaded / total) * 100)}%\`;
+    }
+);
+
+if (response.status === 200) {
+    console.log("Uploaded:", response.response);
+}

See code generation for configuration options.

Step 6: Serve Images from Large Objects

For images stored in Large Objects, create a function to retrieve them:

sql
sql
create or replace function example_6.get_image(
+    _oid bigint,
+    _mime_type text
+)
+returns bytea
+language sql
+begin atomic;
+    select lo_get(_oid);
+end;
+
+comment on function example_6.get_image(bigint, text) is '
+HTTP GET
+@raw
+content_type: {_mime_type}
+Cache-Control: public, max-age=31536000, immutable';

Annotations:

Performance: Large Objects vs File System

Unlike file system storage where the web server serves static files directly, every Large Object request requires:

  1. Opening a database connection from the pool
  2. Executing the lo_get() function
  3. Streaming binary data through the application
  4. Returning the connection to the pool

This is more expensive than serving a static file. For high-traffic images, use Cache-Control headers so browsers and CDNs cache the response:

After the first request, subsequent requests are served from cache without hitting your database.

Displaying Images

Serve images based on storage type:

typescript
typescript
if (upload.filePath) {
+    // File system: static file URL
+    imgUrl = upload.filePath.replace('./uploads', '/uploads');
+} else if (upload.oid) {
+    // Large Object: API endpoint
+    imgUrl = \`/api/get-image?oid=\${upload.oid}&mimeType=\${encodeURIComponent(upload.contentType)}\`;
+}

Backup Advantage

Large Objects are included in pg_dump automatically. With combined storage, you get:

Traditional Approach Comparison

For comparison, here is the same feature in three common stacks:

ASP.NET Core (C#):

csharp
csharp
[HttpPost("upload")]
+[Authorize]
+public async Task<IActionResult> Upload(IFormFile file)
+{
+    if (file == null || file.Length == 0)
+        return BadRequest("No file");
+
+    // Validate image
+    using var image = Image.Load(file.OpenReadStream());
+    if (image == null)
+        return BadRequest("Invalid image");
+
+    // Generate unique filename
+    var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
+    var filePath = Path.Combine(_uploadPath, fileName);
+
+    // Save to file system
+    using var stream = new FileStream(filePath, FileMode.Create);
+    await file.CopyToAsync(stream);
+
+    // Save metadata to database
+    var upload = new Upload {
+        UserId = User.GetUserId(),
+        FileName = file.FileName,
+        ContentType = file.ContentType,
+        FileSize = file.Length,
+        FilePath = filePath
+    };
+    _context.Uploads.Add(upload);
+    await _context.SaveChangesAsync();
+
+    return Ok(new { upload.Id, filePath });
+}

Spring Boot (Java):

java
java
@PostMapping("/upload")
+@PreAuthorize("isAuthenticated()")
+public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) {
+    if (file.isEmpty()) {
+        return ResponseEntity.badRequest().body("No file");
+    }
+
+    // Validate image
+    try {
+        BufferedImage img = ImageIO.read(file.getInputStream());
+        if (img == null) throw new IOException("Invalid image");
+    } catch (IOException e) {
+        return ResponseEntity.badRequest().body("Invalid image");
+    }
+
+    // Generate unique filename and save
+    String fileName = UUID.randomUUID() + getExtension(file.getOriginalFilename());
+    Path filePath = Paths.get(uploadPath, fileName);
+    Files.copy(file.getInputStream(), filePath);
+
+    // Save to database
+    Upload upload = new Upload();
+    upload.setUserId(getCurrentUserId());
+    upload.setFileName(file.getOriginalFilename());
+    upload.setContentType(file.getContentType());
+    upload.setFileSize(file.getSize());
+    upload.setFilePath(filePath.toString());
+    uploadRepository.save(upload);
+
+    return ResponseEntity.ok(Map.of("id", upload.getId(), "path", filePath));
+}

FastAPI (Python):

python
python
@app.post("/upload")
+async def upload(file: UploadFile, user: User = Depends(get_current_user)):
+    if not file:
+        raise HTTPException(400, "No file")
+
+    # Validate image
+    contents = await file.read()
+    try:
+        Image.open(io.BytesIO(contents)).verify()
+    except:
+        raise HTTPException(400, "Invalid image")
+
+    # Generate unique filename and save
+    file_name = f"{uuid.uuid4()}{Path(file.filename).suffix}"
+    file_path = UPLOAD_PATH / file_name
+    async with aiofiles.open(file_path, 'wb') as f:
+        await f.write(contents)
+
+    # Save to database
+    upload = Upload(
+        user_id=user.id,
+        file_name=file.filename,
+        content_type=file.content_type,
+        file_size=len(contents),
+        file_path=str(file_path)
+    )
+    db.add(upload)
+    await db.commit()
+
+    return {"id": upload.id, "path": str(file_path)}

And this is just the backend. You still need:

Meanwhile, NpgsqlRest handles the backend automatically based on your SQL function and annotations, and generates this TypeScript client for your frontend:

typescript
typescript
// Auto-generated - you write ZERO of this code
+
+interface IUploadToFileSystemResponse {
+    type: string;
+    fileName: string;
+    contentType: string;
+    size: number;
+    success: boolean;
+    status: string;
+    filePath?: string;
+    oid?: number;
+}
+
+export async function uploadToFileSystem(
+    files: FileList | null,
+    request: IUploadToFileSystemRequest,
+    progress?: (loaded: number, total: number) => void,
+): Promise<{
+    status: number,
+    response: IUploadToFileSystemResponse[],
+    error: {status: number; title: string; detail?: string | null} | undefined
+}> {
+    return new Promise((resolve, reject) => {
+        if (!files || files.length === 0) {
+            reject(new Error("No files to upload"));
+            return;
+        }
+        var xhr = new XMLHttpRequest();
+        if (progress) {
+            xhr.upload.addEventListener("progress", (event) => {
+                if (event.lengthComputable && progress) {
+                    progress(event.loaded, event.total);
+                }
+            }, false);
+        }
+        xhr.onload = function () {
+            if (this.status >= 200 && this.status < 300) {
+                resolve({status: this.status, response: JSON.parse(this.responseText), error: undefined});
+            } else {
+                resolve({status: this.status, response: [], error: JSON.parse(this.responseText)});
+            }
+        };
+        xhr.onerror = function () {
+            reject({xhr: this, status: this.status, statusText: this.statusText});
+        };
+        xhr.open("POST", baseUrl + "/api/example-6/upload-to-file-system" + parseQuery(request));
+        const formData = new FormData();
+        for(let i = 0; i < files.length; i++) {
+            formData.append("file", files[i], files[i].name);
+        }
+        xhr.send(formData);
+    });
+}

Typed interfaces, FormData handling, progress callbacks, error handling - all generated from your SQL function signature.

Line count comparison for a complete upload feature:

ComponentTraditionalNpgsqlRest
Backend endpoint30-500
Entity/Model class15-250
Repository10-200
DTO classes10-200
Database migration10-1510-15
SQL function020
Annotation05
TypeScript types15-30 (manual)0 (generated)
Frontend upload30-5010 (generated)
Total~120-210~35-50

That's 70-80% less code - and the NpgsqlRest code is mostly just your SQL function doing exactly what you want, with TypeScript client generated automatically.

Summary

To add image uploads to your NpgsqlRest application:

  1. Create an uploads table with oid and file_path columns
  2. Enable upload handlers in config.json
  3. Write a single upload function that inserts from _meta JSON
  4. Add upload for <handler> annotation to choose storage
  5. Use the generated TypeScript client with progress callbacks

The same function code works for all three storage strategies - only the annotation changes.

SQL File Source

Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

`,84)),e(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/6_image_uploads","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"},{text:"Response Headers",href:"/annotations/response-headers"},{text:"Raw Output",href:"/annotations/raw"}]})])}const C=a(p,[["render",k]]);export{o as __pageData,C as default}; diff --git a/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.lean.js b/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.lean.js new file mode 100644 index 000000000..e52bac391 --- /dev/null +++ b/assets/blog_secure-image-uploads-postgresql-typescript.md.BCA4okme.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as l,o as t,a5 as h,G as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Secure Image Uploads with PostgreSQL: File System and Large Objects","titleTemplate":"NpgsqlRest","description":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects, with automatic TypeScript client and progress tracking.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Secure Image Uploads with PostgreSQL: File System and Large Objects","titleTemplate":"NpgsqlRest","description":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects, with automatic TypeScript client and progress tracking.","head":[["meta",{"name":"keywords","content":"postgresql image upload, file upload postgresql, postgresql large objects, secure file upload api, npgsqlrest uploads, postgresql blob storage, typescript file upload"}],["meta",{"property":"og:title","content":"Secure Image Uploads with PostgreSQL: File System and Large Objects"}],["meta",{"property":"og:description","content":"Build a complete image upload system with PostgreSQL. Store files on disk or in Large Objects."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Secure Image Uploads with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Build image uploads with PostgreSQL. File system or Large Objects storage."}]]},"headers":[],"relativePath":"blog/secure-image-uploads-postgresql-typescript.md","filePath":"blog/secure-image-uploads-postgresql-typescript.md"}'),p={name:"blog/secure-image-uploads-postgresql-typescript.md"};function k(r,s,d,g,y,F){const i=n("BlogNav");return t(),l("div",null,[s[0]||(s[0]=h("",84)),e(i,{"source-code":"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/6_image_uploads","get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Upload Annotations",href:"/annotations/upload"},{text:"Upload Configuration",href:"/config/uploads"},{text:"Code Generation",href:"/config/codegen"},{text:"Response Headers",href:"/annotations/response-headers"},{text:"Raw Output",href:"/annotations/raw"}]})])}const C=a(p,[["render",k]]);export{o as __pageData,C as default}; diff --git a/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.js b/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.js new file mode 100644 index 000000000..0be0fec4e --- /dev/null +++ b/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.js @@ -0,0 +1,216 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"SQL File Source: REST Endpoints from Plain .sql Files","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.12.0 introduces SQL File Source — build REST APIs from plain .sql files. See real examples: auth, real-time chat, CSV exports, external API calls, and file uploads.","frontmatter":{"layout":"doc","outline":[2,3],"title":"SQL File Source: REST Endpoints from Plain .sql Files","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.12.0 introduces SQL File Source — build REST APIs from plain .sql files. See real examples: auth, real-time chat, CSV exports, external API calls, and file uploads.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql to rest api, postgresql sql file endpoint, plain sql rest api, sql file api generator, npgsqlrest 3.12, multi-command sql endpoint"}],["meta",{"property":"og:title","content":"SQL File Source: REST Endpoints from Plain .sql Files"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.12.0 introduces SQL File Source. See real examples: auth, chat, CSV exports, API calls, uploads."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"SQL File Source: REST Endpoints from Plain .sql Files"}],["meta",{"name":"twitter:description","content":"NpgsqlRest 3.12.0 introduces SQL File Source — REST APIs from plain .sql files."}]]},"headers":[],"relativePath":"blog/sql-file-source-rest-api-from-plain-sql.md","filePath":"blog/sql-file-source-rest-api-from-plain-sql.md"}'),e={name:"blog/sql-file-source-rest-api-from-plain-sql.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[l(`

SQL File Source: REST Endpoints from Plain .sql Files

March 2026 · SQL Filesv3.12.0PostgreSQLNpgsqlRest

NpgsqlRest 3.12.0 introduces SQL File Source. This is a REST endpoint:

sql
sql
-- HTTP GET
+-- @param $1 active
+select user_id, username, email, active from users where active = $1;

Drop this file as sql/get-users.sql, and GET /api/get-users?active=true is live. TypeScript client generated. Static type checking against your database schema at startup. No DDL, no migration, no CREATE FUNCTION.

For comparison, here's the same endpoint as a PostgreSQL function — the way it worked before:

sql
sql
create or replace function get_users(_active boolean)
+returns table (user_id int, username text, email text, active boolean)
+language sql
+begin atomic;
+  select user_id, username, email, active from users where active = _active;
+end;
+
+comment on function get_users(boolean) is 'HTTP GET';

Eight lines of DDL wrapping a 1-line query. Requires a migration. Lives in the database, not your file system. Changing the return columns means DROP and CREATE (or CREATE OR REPLACE with matching signatures).

The SQL file version: 3 lines. Edit the file, restart the server. All existing NpgsqlRest features — auth, caching, SSE, uploads, proxy, exports — work unchanged.

For the full technical reference, see the SQL File Endpoints Guide. This post shows the feature in practice.

All code in this post is taken verbatim from the examples repository.

The Simplest Endpoint

From 1_my_first_function_sql_file:

sql
sql
-- HTTP GET
+select user_id, username, email, active from example_2.users;

That's a complete endpoint. GET /api/get-users returns:

json
json
[{"userId": 1, "username": "alice", "email": "alice@example.com", "active": true}, ...]

No function definition. No migration DDL. The file is the endpoint.

Multi-Command: Multiple Queries in One Request

From the same example — a single file, two statements, one HTTP request:

sql
sql
-- HTTP GET
+
+-- @result first
+select * from (
+    values ('Hello, World!'), 
+    ('This is my first SQL endpoint.'), 
+    ('Enjoy coding in SQL!')
+) as t(text);
+
+-- @result second
+-- @single
+select current_query() as query_text, current_user as user, current_timestamp as timestamp;

Returns a JSON object with named result keys:

json
json
{
+  "first": ["Hello, World!", "This is my first SQL endpoint.", "Enjoy coding in SQL!"],
+  "second": {"queryText": "...", "user": "postgres", "timestamp": "..."}
+}

@result names the keys. @single returns one row as an object instead of an array. Single-column results become flat arrays automatically. All executed in a single database round-trip via NpgsqlBatch.

Authentication: Login, Logout, Who Am I

From 3_security_and_auth_sql_file — three files, complete cookie auth:

sql/login.sql:

sql
sql
/*
+HTTP POST
+@login
+@allow_anonymous
+@param $1 username
+@param $2 password
+*/
+select
+    'cookies' as scheme,
+    u.user_id::text as user_id,
+    u.username,
+    u.email
+from example_3.users u
+where
+    u.username = $1
+    and example_3.verify_password($2, u.password_hash);

sql/who-am-i.sql:

sql
sql
/*
+HTTP GET
+@authorize
+@user_parameters
+@param $1 _user_id default null
+@param $2 _username default null
+@param $3 _email default null
+*/
+select $1 as user_id, $2 as username, $3 as email;

sql/logout.sql:

sql
sql
-- HTTP POST
+-- @logout
+-- @authorize
+select 'cookies'

The annotations (@login, @logout, @authorize, @user_parameters) work exactly the same as in function endpoints. The default null on who-am-i.sql parameters ensures they're always bindable — @user_parameters fills them from the authenticated user's claims.

Real-Time Chat with SSE

From 8_simple_chat_client_sql_file — a real-time chat message sender in one file:

sql
sql
/*
+HTTP POST
+@authorize
+@sse
+@sse_scope authorize
+@user_parameters
+@param $1 messageText text
+@param $2 _user_id text = null
+@param $3 _user_name text = null
+@void
+*/
+begin;
+
+select set_config('example_8.message_text', $1, true);
+select set_config('example_8.current_user_id', $2, true); 
+select set_config('example_8.current_user_name', $3, true);
+
+do
+$$
+declare
+    _message_text text = current_setting('example_8.message_text')::text;
+    _user_id int = current_setting('example_8.current_user_id')::int;
+    _user_name text = current_setting('example_8.current_user_name')::text;
+    
+    _message_id int;
+    _created_at timestamptz;
+begin
+    insert into example_8.messages (user_id, username, message_text)
+    values (_user_id, _user_name, _message_text)
+    returning message_id, created_at into _message_id, _created_at;
+
+    raise info '%', json_build_object(
+        'message_id', _message_id,
+        'user_id', _user_id,
+        'username', _user_name,
+        'message_text', _message_text,
+        'created_at', _created_at
+    );
+end;
+$$;
+
+end;

Several patterns work together here:

No WebSockets, no message brokers — just SQL and SSE.

CSV Export with Basic Auth

From 5_csv_basic_auth_sql_file:

sql
sql
/*
+HTTP GET
+@raw
+@separator ,
+@new_line \\n
+@columns
+Content-Type: text/csv
+Content-Disposition: attachment; filename="sales_report.csv"
+@basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp
+@user_parameters
+@param $1 _user_name text default null
+*/
+select
+    $1 as exported_by, 
+    order_id,
+    customer_name,
+    product,
+    quantity,
+    unit_price,
+    total,
+    order_date
+from example_5.sales
+order by order_date;

@raw outputs plain text instead of JSON. @separator , and @columns produce a proper CSV with headers. The custom Content-Type and Content-Disposition headers make the browser download it as a file. @basic_auth means Excel's "Get Data from Web" can connect directly — turning this into a live data feed.

Dynamic Excel Output

From 14_table_format_sql_file:

sql
sql
/*
+HTTP GET
+@authorize
+@define_param format text
+@define_param excelFileName text
+@define_param excelSheet text
+@table_format = {format}
+@excel_file_name = {excelFileName}
+@excel_sheet = {excelSheet}
+@tsclient_url_only = true
+*/
+select * from (values
+    (42,        9999999999::bigint, 3.1415::numeric(10,4), 2.71828::float8, true,  'hello world',      '2025-06-15'::date, '2025-06-15 14:30:00'::timestamp, '09:45:30'::time, '{"key":"value"}'::json, null::text, null::int),
+    (-1,        0::bigint,          0.0001::numeric(10,4), -99.99::float8,  false, 'special <chars> &', '2000-01-01'::date, '2000-01-01 00:00:00'::timestamp, '23:59:59'::time, '[1,2,3]'::json,        'not null', 7),
+    (2147483647, -1::bigint,        99999.9999::numeric(10,4), 0::float8,   true,  '',                  '1999-12-31'::date, '1999-12-31 23:59:59'::timestamp, '00:00:00'::time, 'null'::json,           null::text, null::int)
+) as t(int_val, bigint_val, numeric_val, float_val, bool_val, text_val, date_val, timestamp_val, time_val, json_val, null_text, null_int);

@define_param creates HTTP parameters that feed into annotation placeholders without appearing in the SQL. GET /api/get-data?format=excel&excelFileName=report.xlsx&excelSheet=Data streams a native .xlsx file. Change format to html_table for HTML, or omit it for JSON. Same SQL, different output.

Nested Custom Types

From 12_custom_types_sql_file:

sql
sql
/*
+HTTP GET
+@nested
+@param $1 authorId int default null
+*/
+select
+    row(a.author_id, first_name, last_name)::example_12.authors as author,
+    count(b.*) as books
+from example_12.authors a
+left join example_12.books b using (author_id)
+where
+    $1 is null or author_id = $1
+group by
+    a.author_id, first_name, last_name;

@nested wraps composite type columns as nested JSON objects instead of flattening them inline:

json
json
[{"author": {"authorId": 1, "firstName": "Alice", "lastName": "Smith"}, "books": 3}, ...]

External API Calls

From 9_http_calls_sql_file — a financial dashboard that calls two external APIs in parallel and processes the results in SQL:

sql
sql
/*
+HTTP GET /financial-dashboard
+@authorize
+
+@param $1 _base_currency text
+@param $2 _target_currencies_csv text
+@param $3 _crypto_ids_csv text
+@param $4 _vs_currencies_csv text
+
+@param $5 _exchange_rate_response example_9.exchange_rate_api
+@param $6 _crypto_response example_9.crypto_price_api
+*/
+
+begin;
+
+-- @skip
+create temp table _var on commit drop as
+select 
+    $1::text as base_currency,
+    $2::text as target_currencies_csv,
+    $3::text as crypto_ids_csv,
+    $4::text as vs_currencies_csv,
+    $5::example_9.exchange_rate_api as exchange_rate_response,
+    $6::example_9.crypto_price_api as crypto_response;
+
+do
+$$
+declare
+    _base_currency text = (select base_currency from _var);
+    _target_currencies_csv text = (select target_currencies_csv from _var);
+    _exchange_rate_response example_9.exchange_rate_api = (select exchange_rate_response from _var);
+    _crypto_response example_9.crypto_price_api = (select crypto_response from _var);
+
+    _result example_9.financial_dashboard_result;
+    _filtered_rates jsonb = '{}'::jsonb;
+    _rate_data jsonb;
+    _currency text;
+    _target_arr text[];
+begin
+    if (_exchange_rate_response).success then
+        _rate_data = (_exchange_rate_response).body;
+        _target_arr = string_to_array(_target_currencies_csv, ',');
+        foreach _currency in array _target_arr loop
+            _currency = upper(trim(_currency));
+            if _rate_data->'rates' ? _currency then
+                _filtered_rates = _filtered_rates ||
+                    jsonb_build_object(_currency, _rate_data->'rates'->_currency);
+            end if;
+        end loop;
+        _result.fiat_base_currency = upper(_base_currency);
+        _result.fiat_rates = _filtered_rates::json;
+        _result.fiat_last_updated = _rate_data->>'time_last_update_utc';
+        _result.fiat_success = true;
+    end if;
+
+    if (_crypto_response).success then
+        _result.crypto_prices = (_crypto_response).body;
+        _result.crypto_success = true;
+    end if;
+
+    create temp table _result_out on commit drop as
+    select (_result).*;
+end;
+$$;
+
+-- @result dashboard
+-- @single
+-- @returns example_9.financial_dashboard_result
+select * from _result_out;
+
+end;

Parameters $5 and $6 are HTTP custom types — NpgsqlRest makes the external API calls in parallel (Task.WhenAll) and passes the responses as composite values. The DO block processes both responses and builds a combined dashboard result. @returns declares the return type because the temp table doesn't exist at startup. @skip hides the temp table setup from the response.

This is the most advanced SQL file pattern: temp table bridge, @returns, @skip, @single, @result, HTTP custom types, and transaction wrapping — all in one file.

The Important Part

Every feature shown above — authentication, SSE, CSV export, Excel streaming, file uploads, HTTP custom types, proxy, composite types, caching — existed before SQL File Source. They were designed for function endpoints. SQL File Source makes them available to plain SQL files with zero changes.

If you're already using NpgsqlRest with functions, you can migrate endpoints one by one. If you're new, you can start with SQL files and add functions when you need procedural logic, testing, or optimization hints.

SQL Files vs Routines: When to Use Which

SQL files win on simplicity and flexibility. Here's the short version:

SQL files advantages:

Routine advantages:

The rule of thumb: start with SQL files. Move to functions when you need unit-testable procedural logic or when DO block workarounds become too clumsy.

What Came After This Post

Updates since 3.12

SQL files kept growing up: named parameters (where email = :email — the placeholder is the API parameter, since 3.19) replace the positional $1 + @param dance shown above; the SQL test runner (--test) runs plain .sql tests against these endpoints in-process; and watch mode (--watch) restarts the server on file, config, and database changes with the TypeScript client regenerated every cycle.

Get Started

bash
bash
git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
+cd npgsqlrest-docs/examples && bun install
+cd 1_my_first_function_sql_file
+bun run db:up && bun run dev
+# Visit http://127.0.0.1:8080
`,71)]))}const g=i(e,[["render",t]]);export{o as __pageData,g as default}; diff --git a/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.lean.js b/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.lean.js new file mode 100644 index 000000000..00c2d2aaf --- /dev/null +++ b/assets/blog_sql-file-source-rest-api-from-plain-sql.md.czCJfcgj.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"SQL File Source: REST Endpoints from Plain .sql Files","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.12.0 introduces SQL File Source — build REST APIs from plain .sql files. See real examples: auth, real-time chat, CSV exports, external API calls, and file uploads.","frontmatter":{"layout":"doc","outline":[2,3],"title":"SQL File Source: REST Endpoints from Plain .sql Files","titleTemplate":"NpgsqlRest","description":"NpgsqlRest 3.12.0 introduces SQL File Source — build REST APIs from plain .sql files. See real examples: auth, real-time chat, CSV exports, external API calls, and file uploads.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql to rest api, postgresql sql file endpoint, plain sql rest api, sql file api generator, npgsqlrest 3.12, multi-command sql endpoint"}],["meta",{"property":"og:title","content":"SQL File Source: REST Endpoints from Plain .sql Files"}],["meta",{"property":"og:description","content":"NpgsqlRest 3.12.0 introduces SQL File Source. See real examples: auth, chat, CSV exports, API calls, uploads."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"SQL File Source: REST Endpoints from Plain .sql Files"}],["meta",{"name":"twitter:description","content":"NpgsqlRest 3.12.0 introduces SQL File Source — REST APIs from plain .sql files."}]]},"headers":[],"relativePath":"blog/sql-file-source-rest-api-from-plain-sql.md","filePath":"blog/sql-file-source-rest-api-from-plain-sql.md"}'),e={name:"blog/sql-file-source-rest-api-from-plain-sql.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[l("",71)]))}const g=i(e,[["render",t]]);export{o as __pageData,g as default}; diff --git a/assets/blog_sql-rest-api.md.Dv5ncwRQ.js b/assets/blog_sql-rest-api.md.Dv5ncwRQ.js new file mode 100644 index 000000000..4d5c93891 --- /dev/null +++ b/assets/blog_sql-rest-api.md.Dv5ncwRQ.js @@ -0,0 +1,354 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const h="/clean.png",r="/proto.jpeg",F=JSON.parse('{"title":"SQL REST API","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong.","frontmatter":{"layout":"doc","outline":[2,4],"title":"SQL REST API","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong.","badge":"human","head":[["meta",{"name":"keywords","content":"npgsqlrest postgresql sql rest api sql files database-first clean architecture ddd ai tools"}],["meta",{"property":"og:title","content":"SQL REST API"}],["meta",{"property":"og:description","content":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"SQL REST API"}],["meta",{"name":"twitter:description","content":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, database-first philosophy, AI tools, and why Clean Architecture got it wrong."}]]},"headers":[],"relativePath":"blog/sql-rest-api.md","filePath":"blog/sql-rest-api.md"}'),o={name:"blog/sql-rest-api.md"};function k(d,s,c,g,u,y){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l(`

SQL REST API

April 2026 · NpgsqlRestPostgreSQLStorySQL Filesv3.12.0

Version 3.12.0 was such a massive and important release that I really, really really wanted to take some time to write personally about it. Ok, so this time and in this blog post - no AI, no nothing. Let's do this...

Close the Claude Code, turn it off, shut it down. Claude Code you shut up you now. Fine.

Start typing "This" ...

This blog post is unique because it is written entirely without the assistance of any AI tools. In an era where AI-generated content is becoming increasingly prevalent, I wanted to take a moment to share my personal journey with NpgsqlRest and how it has impacted my development workflow.

Damn it, shut up Copilot you. How do I turn this thing off? Ask Claude Code how to do it, turn it on again. And now he sees me writing a new blog post, damn it, who told you, is that you Copilot again? What's going on here?

Oh I see, there is actually a mute button, let's start finally...

Introduction

A couple of weeks ago I was asked if NpgsqlRest is an in-place replacement for PostgrREST/Supabase.

Except for the fact that it is way better, faster, more secure, more flexible, more powerful, more feature-rich, more stable, more reliable, more scalable, more maintainable, more extensible, more customizable, more user-friendly, more developer-friendly, more community-friendly, and more open-source than PostgrREST/Supabase - no! And hell no! (That list was generated by AI, I admit).

But seriously, no. NpgsqlRest is NOT a replacement for PostgrREST/Supabase simply because we don't share the same philosophy and the same approach. Let me explain...

Besides the fact that NpgsqlRest doesn't use or need RLS to implement auth and authorization, which is a huge difference in itself, and the fact that we can configure each endpoint individually, the biggest difference between NpgsqlRest and PostgrREST/Supabase is in the way they generate REST API endpoints.

PostgrREST/Supabase uses direct Table/View access as primary source of generated REST API endpoints. And to do that, it offers a rich set of query filters and operators (e.g., eq, gt, like, in, order, limit, select) to cover a wide range of use cases. I believe this is the wrong approach for many reasons. And, as I have worked on more than one ambitious low-code project over decades, I have seen this approach fail, well, almost every time.

First of all, most real-world endpoints aren't CRUDs on a single table at all. We have data models, we have various joins of various complexity depending on the use case and model itself, is it normalized, denormalized, various aggregations, conditional logic, etc. - the kind of stuff that SQL was made for.

But instead of letting you write your own SQL declarations, this approach forces you to express your logic through query filters and operators - essentially, configuration completely bypassing the expressiveness of SQL. It is basically swapping power and expressiveness of SQL for a lousy configuration. At that point you are not doing SQL, you are doing configuration. No thanks. I mean, what if your use case is not supported by the configuration and to be perfectly honest - it usually isn't.

Given those facts, my approach was always from the start - let the user write their own SQL and have endpoints generated from that unchanged in any shape or form, just as they are. And when I say user I mean me, because it was always a tool for me, myself and I.

Both tools, however, do support generated REST API endpoints from PostgreSQL functions and procedures (short: routines). But the difference is that in PostgrREST/Supabase, routines are just an alternative to direct Table/View access, while in NpgsqlRest, they are the primary source of generated REST API endpoints. In addition, with NpgsqlRest you can pick and choose exactly which functions and procedures you want to generate endpoints from, and you can configure each endpoint individually, with PostgrREST/Supabase, all functions in the exposed schema are automatically available as endpoints — you control access at the schema level with grants, not per individual function.

I also have designed NpgsqlRest to be extendable with system of plugins. A set of plugins to generate code from endpoint source (e.g., TypeScript/JavaScript modules and types, HTTP files for testing, OpenAPI spec, etc.) and then another set of plugins to act as endpoint source.

Default source has always been so-called Routine Source for Functions and Procedures. There used to be a CRUD Source for direct Table/View access too. Basically, I was nagged. Like, look, PostgrREST has direct Table/View access, boo-hoo, why don't you have it? So I added it. Then I removed it from the standalone client in 3.14.0. It was an abomination that shouldn't have existed. It broke any sane design principle from separation of concerns to encapsulation and abstraction, and served no real purpose except to achieve quick results with big issues down the road.

In any case, I have used functions and procedures as primary source for auto generated REST API endpoints for more than two years now, and it was a great success. I have completed successfully couple of challenging projects using this tool and I am more than happy. Velocity and productivity were through the roof, and I was able to focus on my UI/UX masterpiece instead of dealing with all the boilerplate and infrastructure.

I even built a simple and lightweight migration tool to help me with managing migration scripts that works similarly to Flyway community (with repeatable and versioned scripts, etc.) but much more lightweight and simpler and with few additional features that I needed for my projects (test runner comes to mind). But even with that, I still had to deal with the fact that I need to create a function on the server for every endpoint and that meant running DDL scripts in repeatable scripts.

Little did I know that there is a much simpler way. If you have running PostgreSQL connection, you can actually use native PostgreSQL parser to parse any SQL command to generate metadata and that can also be used to generate REST API endpoints. And that is exactly what we did with SQL script files as REST API endpoints feature in NpgsqlRest 3.12.0.

SQL Script Files as REST API Endpoints

The idea is simple: a directory with SQL script files/scripts, and then NpgsqlRest will use that on startup to parse all configured files with the native PostgreSQL parser and generate REST API endpoints from your script files. That's it.

Here is technical guide with details, here is a complete configuration guide and finally, here is a list of available examples.

NpgsqlRest will first match files in the configured directory with the configured glob pattern (e.g., ./scripts/**/*.sql for example) - and then it will parse those files to check if they have at least one HTTP comment in them. That is the default behavior by the way, it is configurable, but anyway, if they do have HTTP in comments - it will proceed to use the native PostgreSQL parser to parse each command separately and generate REST API endpoints from them.

Let's checkout a simple example. Let's say we have a file get_users.sql with the following content:

sql
sql
-- HTTP
+select user_id, username, email, active 
+from example.users;

This file will automatically generate a GET /api/get-users endpoint that might have a response like this (just compact, this is example):

json
json
[
+  {
+    "userId": 123,
+    "username": "john_doe",
+    "email": "john_doe@example.com",
+    "active": true
+  }, 
+  {
+    "userId": 124,
+    "username": "jane_doe",
+    "email": "jane_doe@example.com",
+    "active": false
+  }
+]

What will happen in this case is on startup, NpgsqlRest will first do the initial simple parsing to separate commands from comments, and then if the file satisfies the condition, it will proceed to use the native PostgreSQL parser to parse each command separately.

This native PostgreSQL parser is the one from your configured and connected database that simply executes EXPLAIN on the command that doesn't do anything with the database and doesn't fetch any data, but it will do two things for us:

  1. It will validate the command and make sure it is valid SQL that can be executed on the database.
  2. It will generate necessary metadata for us to generate REST API endpoint such as column names and types.

If the command fails, for example column or type doesn't even exist, we will see the error in the logs something like this:

console
console
SqlFileSource: /sql-path/get-users.sql:
+error 42703: column user_id does not exist
+  at line 2, column 15
+  select user_id, username, email, active 
+                ^

And startup will fail, which is good, because you want to know about these errors before you start serving requests (but this is also configurable, you can continue with just a warning and disabled endpoint).

But if it doesn't fail, and you also happen to have a TypeScript plugin configured, you will also get this module generated for you:

typescript
typescript
type ApiError = {status: number; title: string; detail?: string | null};
+type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};
+
+interface IGetUsersResponse {
+    userId: number | null;
+    username: string | null;
+    email: string | null;
+    active: boolean | null;
+}
+
+/**
+* SQL file: /sql-path/get-users.sql
+* 
+* @remarks
+*  HTTP
+* 
+* @returns {ApiResult<IGetUsersResponse[]>}
+*/
+export async function getUsers() : Promise<ApiResult<IGetUsersResponse[]>> {
+    const response = await fetch(baseUrl + "/api/get-users", {
+        method: "GET",
+        headers: {
+            "Content-Type": "application/json"
+        },
+    });
+    return {
+        status: response.status,
+        response: response.ok ? await response.json() as IGetUsersResponse[] : undefined!,
+        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as ApiError : undefined
+    };
+}

Note: this can also be a plain JavaScript module if you are a TypeScript hater, that is fine, you can configure differently. And then for a good measure, you can also get an HTTP file for testing with HTTP files plugin:

http
http
@host=http://127.0.0.1:8080
+
+// SQL file: /sql-path/get-users.sql
+//
+//  HTTP GET
+GET {{host}}/api/get-users
+
+###

As we can see, we achieved two important things here:

  1. Static type checking and type safety end-to-end, from database to your UI code.
  2. Easy, out-of-the-box observability during development time.

This gives us a very tight and fast feedback loop during development, there is no room for guessing anymore, you will know immediately if your SQL is wrong, which in turn means extreme PRODUCTIVITY AND HAPPINESS during development. Yaaaarr!

Now, obviously, there is a lot more to this feature.

For instance, let's talk about parameters. PostgreSQL supports only positional parameters in SQL scripts ($N format, $1, $2, etc.), and that means that we now have to set parameter names in comments, for example:

sql
sql
-- HTTP
+-- @param $1 userId
+select user_id, username, email, active 
+from example.users
+where user_id = $1;

This gives us the following endpoint GET /api/get-users?userId=123. Without @param $1 userId comment - we would have something like this GET /api/get-users?$1=123.

And then there is also support for multiple commands in a single file, for example:

sql
sql
/*
+HTTP
+@param $1 userId
+*/
+select user_id, username, email, active 
+from example.users
+where user_id = $1;
+
+select count(*) as userCount 
+from example.invoices 
+where user_id = $1;

This endpoint might return something like this:

json
json
{
+  "result1": [
+    {
+      "userId": 123,
+      "username": "john_doe",
+      "email": "john_doe@example.com",
+      "active": true
+    }
+  ],
+  "result2": [
+    {
+      "userCount": 5
+    }
+  ]
+}

We can customize this response with comments even further. As we can see multi-command files will generate an object with properties for each command that we can customize with new @result comments like this. And then we can label each result that it returns a single record with @single because by default data is always returned as an array of records, etc, etc, so our multi-command file can look like this:

sql
sql
/*
+HTTP
+@param $1 userId
+
+return a single user record in user property
+@single
+@result user
+*/
+select user_id, username, email, active 
+from example.users
+where user_id = $1;
+
+/*
+return a single invoice count in invoiceCount property
+
+@single
+@result invoiceCount
+*/
+select count(*) 
+from example.invoices 
+where user_id = $1;

Response will now look like this:

json
json
{
+  "user": {
+    "userId": 123,
+    "username": "john_doe",
+    "email": "john_doe@example.com",
+    "active": true
+  },
+  "invoiceCount": 5
+}

You can notice that these comment annotations @result and @single are positional labels, they apply to the command immediately following them (or in same line). Also, regular comments are simply ignored they don't have any special meaning, so you can write whatever.

There is more to this, but I suggest you check out the technical guide with details and other documentation and examples for more details. It is written with AI tool, but nevertheless, it is accurate and I really hope you get the idea by now.

Also, worth noting, that this is just another endpoint source you can configure. Which means that ALL other features of NpgsqlRest developed over the years are available and working (tested and confirmed): Auth, CORS, OpenAPI spec, HTTP files for testing, TypeScript/JavaScript modules and types generation, caching, rate limiting, logging, monitoring, exports, imports, uploads, notifications, etc. - all of that works with SQL files as well. See the examples for more advanced use cases — there are already examples for image uploads, CSV/Excel ingestion, Excel exports, real-time notifications, etc.

And, if you ask me, the best thing about this feature - we all know SQL, we all write a bunch of SQL scripts for various reasons and purposes, and now we can simply reuse those scripts as REST API endpoints without any changes to them. Just, you know, use what you already got and focus on your UI/UX masterpiece. As we can see these comment annotations are just labels, declarations really. SQL is already declarative, why not just declare some infrastructure for your application in SQL as well, and then have it generated for you. Easy as that.

So, with introduction of SQL files as REST API endpoints, we have cut through the boilerplate even further. We don't need to create functions on the server anymore. That doesn't mean that it will replace routines as endpoint source completely. There are advantages and disadvantages of both approaches. Let's talk about that now.

SQL Files vs Routines

Let's compare these two approaches with a simple example. Simple SQL file:

sql
sql
-- HTTP
+-- @param $1 userId
+select user_id, username, email, active 
+from example.users
+where user_id = $1;

Equivalent routine function:

sql
sql
create or replace function get_user(
+  _user_id int
+)
+language sql
+returns table (
+  user_id int, 
+  username text, 
+  email text, 
+  active boolean
+)
+as $$
+select user_id, username, email, active 
+from example.users
+where user_id = _user_id;
+$$;
+
+comment on function get_user(int) is 'HTTP';

Here are important observations and differences between these two approaches:

1) No Migrations

A routine function is basically a DDL script that creates (or replaces) a function.

Function needs to exist in the database for NpgsqlRest to be able to generate an endpoint from the function metadata stored in database. That means we need to execute this DDL in order to create this function either manually executing script or through some migration tool that can track changes.

SQL files don't need any of that.

On the other hand, we need to trust that those files do match our existing schema, but as we have seen earlier, parser will warn us with a nice error if they don't.

All in all, not having to create a function on the server is a big win for SQL files.

2) No Comment On Statements

This is a small nitpick, but still worth mentioning. With routines, we need to have COMMENT ON FUNCTION statements to declare that this function is an endpoint source. With SQL files, we just need to have a comment with HTTP in it, and that is it. No extra statements, no extra boilerplate, just a simple comment.

A small win for SQL files here as well.

3) Mapping by Position vs No Mapping at All

As we can see in the example above, when using PostgreSQL functions to return result set, we need to match the expected result.

That matching is done by position, so first column in the result set needs to be user_id of type int, second column needs to be username of type text, etc.

Getting this wrong will result in creation-time error, which is good because we have another layer of safety and validation, but it is still a boilerplate that we need to deal with. Mapping by position is not intuitive, and it can be tedious to maintain. We can simplify this with custom types like this:

sql
sql
create or replace function get_user(
+  _user_id int
+)
+language sql
+returns setof example.user_info
+as $$
+select user_id, username, email, active 
+from example.users
+where user_id = _user_id;
+$$;
+
+comment on function get_user(int) is 'HTTP';

But that is still a boilerplate and still mapping by position.

On the other hand, with SQL files, there is no mapping at all. Result set is returned as it is, and column names and types are generated from the result set itself. This is a much more natural way of working with SQL, and it is also much more flexible. We can return any result set we want, and we don't need to worry about matching it to some predefined structure.

Another big win for SQL files.

4) Multiple Result Sets

This is big. Unlike, for example MSSQL, PostgreSQL can't return multiple result sets from a single function at all. We can use either JSON or temporary tables wrapped in transactions to achieve that, but it is still a boilerplate and it's clumsy. And with JSON result, we are losing type safety.

With SQL files, we can have multiple commands in a single file, and each command can return its own result set. Modified example from above:

sql
sql
-- HTTP
+-- @param $1 userId
+
+-- @single
+-- @result user
+select user_id, username, email, active 
+from example.users
+where user_id = $1;
+
+-- @result invoices
+select invoice_id, amount, due_date
+from example.invoices 
+where user_id = $1;

This will give us the following response:

json
json
{
+  "user": {
+    "userId": 123,
+    "username": "john_doe",
+    "email": "john_doe@example.com",
+    "active": true
+  },
+  "invoices": [
+    {
+      "invoiceId": 1,
+      "amount": 100.00,
+      "dueDate": "2024-05-01"
+    },
+    {
+      "invoiceId": 2,
+      "amount": 200.00,
+      "dueDate": "2024-06-01"
+    }
+  ]
+}

And also, proper TypeScript types will be generated for this as well (if you are into that sort of thing, nothing wrong with that).

So, that is it, we have two result sets in a single response, and we didn't have to do any boilerplate to achieve that. A huge win for SQL files.

5) Named Parameters

Now let's talk about some routine advantages over SQL files. Because there are some. With routines, we can have named parameters, which is a nice to have. With SQL files, we only have positional parameters and we are forced to use @param comments to give them names as we have seen in examples above.

This is a small win for routines, but it is still a win.

6) Testability

This is a big one. The fact is that routines are single callable units that can be easily tested in isolation. Example:

sql
sql
create or replace function get_user(
+  _user_id int
+)
+language sql
+returns setof example.user_info
+as $$
+select user_id, username, email, active 
+from example.users
+where user_id = _user_id;
+$$;
+
+comment on function get_user(int) is 'HTTP';
+
+/*
+Manual test:
+select * from get_user(123);
+*/
+
+-- Automated test:
+do
+$$
+declare
+    _result record;
+begin
+  -- arrange test data
+  insert into example.users (user_id, username, email, active) 
+  values (123, 'john_doe', 'john_doe@example.com', true);
+
+  -- act by calling the function directly
+  select * into _result from get_user(123);
+    
+  -- assert results
+  assert _result.user_id is not null, 'User ID should not be null';
+  assert _result.username = 'john_doe', 'Username should be john_doe';
+
+  rollback; -- cleanup
+end;
+$$;

This is a very neat and practical pattern and testing practice. Testing loop is insanely fast, almost immediate and you don't have to have any additional tools. And I can even get fancy with this, and add this test block before the actual function - and then proclaim myself as TDD guru on LinkedIn and Twitter and start spamming people.

But, as a matter of fact, you can't do that with SQL files. At all. I mean, you can manually execute to see the results and that is it, you can't have that automated test. So, you either test them manually or have some external testing framework that can test endpoints through HTTP, and that just complicates things a lot. So, if testability is important to you, then routines are the way to go.

A win for routines here.

7) Complex Logic

Complex logic flows are usually handled with procedural language constructs such as variables, loops, branching, error handling, etc. That is the standard stuff in all programming languages of 3rd generation. SQL language per-se, as higher level declarative data language, doesn't have these constructs, it doesn't support them out of the box.

Normal PostgreSQL script files are just a bunch of SQL commands, executed sequentially, one by one, with no real connection between them. If we wanted to have some variables between those commands, we could use temporary tables or even PostgreSQL's custom config settings maybe, but that is a hacky workaround (but it works). There are no loops (we work with sets and set algebra instead), there is no branching (we can use CASE statements, or conditional expressions), etc, but again, that is still a bit of a hacky workaround.

Now, to solve this gap, relational systems provide language extensions, that can extend standard SQL with procedural constructs as language super-sets (same way that TypeScript extends JavaScript with types for example). In PostgreSQL, that language extension is PL/pgSQL, and it is supported in routines by default. We can use them also in SQL script files as well, in so-called anonymous code blocks, or DO blocks, but there are some limitations and caveats to be aware of.

Let's see an example of a DO block in a SQL file:

sql
sql
-- HTTP
+do
+$$
+declare
+    _user_id int = 123;
+    _username text;
+begin
+    select username 
+    into _username 
+    from example.users where user_id = _user_id; 
+
+    if _username is null then
+        raise exception 'User not found';
+    end if;
+end;
+$$;

As we can see, this is the anonymous code block (a DO block) containing procedural logic that executes immediately, and it is perfectly valid SQL that can be executed on the PostgreSQL database. Those DO block in PostgreSQL are by default PL/pgSQL language type (PostgreSQL's procedural language), but you can also specify other languages (it needs to be trusted language, the JavaScript plv8 extension comes to mind).

We also see an example of procedural constructs such as variable declaration, branching with IF statement, and error handling with RAISE EXCEPTION. Those are not valid SQL commands, but they are valid PL/pgSQL commands and they live happily together in the same block as PL/pgSQL is a super-set of SQL.

NpgsqlRest will parse this file successfully and generate a POST endpoint from this - no problems. However, there are some hard limitations with this approach:

1) Parameters are not supported in DO blocks.

By design, DO blocks don't support parameters at all, so we can't use $1, $2, etc. I mean, we can, but they will simply be ignored and probably raise an error.

One workaround for this is to use custom settings for example:

sql
sql
-- HTTP
+-- @param $1 userId text
+
+begin;
+
+select set_config('example.user_id', $1, true); 
+
+do
+$$
+declare
+    _user_id int = current_setting('example.user_id')::int;
+    _username text;
+begin
+    select username 
+    into _username 
+    from example.users where user_id = _user_id; 
+
+    if _username is null then
+        raise exception 'User not found';
+    end if;
+end;
+$$;
+
+end;

First, our parameter has to be text or a string, because custom settings only support text values. And then we set config to scope current transaction and wrap up everything in a transaction block. And finally we declare variable from that custom setting but we need to cast it to the type we want (in this case int). This is a bit of a big hack, but it works.

Second workaround is to simply use temporary tables, for example:

sql
sql
-- HTTP
+-- @param $1 userId text
+
+begin;
+
+create temp table _var on commit drop as select $1::int as user_id;
+
+do
+$$
+declare
+    _user_id int = (select user_id from _var);
+begin
+    /* ... use _user_id as parameter ... */
+end;
+$$;
+
+end;

As you can see, we create a temporary table with our parameter value, and then we select that value into our variable. This is also a bit of a hack, but it works as well.

2) DO blocks can't return result sets.

No they can't. DO blocks are designed for procedural logic that doesn't return any result sets at all, this is by design.

Of course, there are workarounds for this as well and you might have probably already guessed it - we can use temporary tables again. For example:

sql
sql
-- HTTP
+-- @param $1 userId text
+
+begin;
+
+create temp table _var on commit drop as select $1::int as user_id;
+
+do
+$$
+declare
+    _user_id int = (select user_id from _var);
+begin
+    /* ... use _user_id as parameter ... */
+
+    create temp table _result_out on commit drop as
+    select user_id, username, email, active
+    from example.users
+    where user_id = _user_id;
+end;
+$$;
+
+-- @returns result_type
+select * from _result_out;
+
+end;

In this example, we create a temporary table _result_out inside our DO block and then we select from that table to return the result set. We must use the @returns positional comment annotation to tell NpgsqlRest what exact type is our temporary table because that temporary table only exists during the execution of this file and it doesn't exist at the time of parsing, so we need to explicitly tell NpgsqlRest what type it is. Again, this is a bit of a hack, but it works.

As we can see, it is possible to use anonymous DO blocks to implement procedural logic in our SQL file endpoints to receive parameters and return result sets, but at this point, why not simply create a routine function or stored procedure and no need for hacks?

It might also be worth mentioning that complex procedural logic can also be implemented with built-in NpgsqlRest proxy features. @proxy forwards an incoming HTTP request to an upstream service and optionally passes the response into your SQL for processing. @proxy_out does the reverse — executes your SQL first, then forwards the result to an upstream service (useful for things like PDF rendering, email sending, or ML inference). There's also a passthrough mode where the upstream response goes directly to the client without touching the database at all. See the proxy configuration guide for details. I use this feature with BUN server running together with NpgsqlRest AOT server and it works great!

All in all, when it comes to complex logic, a gigantic win for routines here.

That is it, those are the main differences between SQL files and routines as endpoint sources. Both approaches have their advantages and disadvantages, and both are valid and useful in different scenarios.

Other Features in v3.12.0

Let's touch now on some other features that we have added in v3.12.0 that are not directly related to SQL files but are still worth mentioning. Because they are cool and exciting, that's why.

Self-Referencing Endpoints

NpgsqlRest has always had this feature called HTTP Client Types. It provides me with a simple way to call external APIs from my SQL by simply defining a composite type with an HTTP definition in a comment. For example:

sql
sql
create type example.crypto_price_api as (
+    body jsonb,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type example.crypto_price_api is '
+GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
+Accept: application/json
+@timeout 10s';

That comment is basically a slightly modified version of the RFC 2616 HTTP request format, the one used in HTTP files (as is the case with all comment annotations in NpgsqlRest) in addition to {_param_name} curly bracket syntax for parameter placeholders, so I can pass some parameters to this HTTP client type and then use it in my SQL like this:

sql
sql
/*
+@param $1 _crypto_ids_csv text
+@param $2 _vs_currencies_csv text
+@param $3 _crypto example.crypto_price_api default null
+*/
+
+/* 
+  parameter $3 will now have full response from the API call, 
+  including body, status code, success flag and error message
+  
+  ... the rest of the SQL file ...
+*/

Or, as routine function parameter:

sql
sql
create or replace function get_crypto_price(
+    _crypto_ids_csv text,
+    _vs_currencies_csv text,
+    _crypto example.crypto_price_api default null
+)
+/* ... the rest of the function ... */

A neat and simple way to call external APIs and pass them some parameters from SQL. And no, you can't pass any value to this _crypto example.crypto_price_api parameter — it's automatically populated by NpgsqlRest. HTTP call is resolved before SQL command invocation. And in case of multiple HTTP client type parameters, calls are made in parallel with Task.WhenAll and then all responses are available for SQL command.

So that works, fine. But starting with v3.12.0, these HTTP client types now support relative paths as well. That means we can point them back to the same NpgsqlRest server and call our own endpoints from SQL. And the most important thing: there is zero HTTP overhead because relative paths bypass the HTTP stack entirely and invoke the endpoint handler directly in-process. This reveals a pattern that makes parallel query composition straightforward. Let's see how that works.

sql/get-user-profile.sql:

sql
sql
-- HTTP GET
+-- @param $1 userId
+select user_id, username, email 
+from example.users 
+where user_id = $1;

sql/get-user-orders.sql:

sql
sql
-- HTTP GET
+-- @param $1 userId
+select order_id, amount, order_date 
+from example.orders 
+where user_id = $1;

sql/get-user-stats.sql:

sql
sql
-- HTTP GET
+-- @param $1 userId
+-- @single
+select count(*) as total_orders, sum(amount) as total_spent 
+from example.stats 
+where user_id = $1;

Now we define three HTTP client types that point back to our own server using relative paths:

sql
sql
create type user_profile_api as (body text, success boolean);
+comment on type user_profile_api is 'GET /api/get-user-profile?userId={_user_id}';
+
+create type user_orders_api as (body text, success boolean);
+comment on type user_orders_api is 'GET /api/get-user-orders?userId={_user_id}';
+
+create type user_stats_api as (body text, success boolean);
+comment on type user_stats_api is 'GET /api/get-user-stats?userId={_user_id}';

And then a dashboard endpoint that combines all three ...

sql/get-user-dashboard.sql:

sql
sql
/*
+HTTP GET
+@param $1 _user_id int
+@param $2 _profile user_profile_api
+@param $3 _orders user_orders_api
+@param $4 _stats user_stats_api
+@single
+*/
+select
+    ($2).body::json as profile,
+    ($3).body::json as orders,
+    ($4).body::json as stats;

All three queries from get-user-profile.sql, get-user-orders.sql and get-user-stats.sql will be executed in parallel, at the same time with Task.WhenAll and then their results will be available in the dashboard SQL file as parameters. We can then combine those results in any way we want and return a single response for the dashboard endpoint. Furthermore, we can mark them as @internal to prevent them from being exposed as public endpoints, and they will still be available for internal calls.

This pattern goes a long way for query composition and code reuse as well as internal optimization since queries are executed in parallel mode. Each query will skip HTTP pipeline and will get dedicated database connection from the pool and execute immediately. This is a great way to compose complex queries from simpler ones and reuse them across different endpoints, but you also need to have connection pooler configured to get the best performance out of this pattern.

Future Improvements

The one thing that is still missing with these self-referencing calls is that we still map to JSON and with standard HTTP client types (body, status code, etc.) - instead of mapping directly to the expected columns.

That means that we still have overhead of JSON serialization and deserialization which is completely unnecessary in this case.

The feature I am working on right now will look something like this (this is just a draft, not final API design yet). If we have an endpoint like the one from get-user-profile.sql example above, and that endpoint returns user_id, username and email columns, then we can define our HTTP client type like this:

sql
sql
create type user_profile_result_type as (
+  user_id int, 
+  username text, 
+  email text, 
+  success boolean
+);
+comment on type user_profile_result_type is 'CALL /api/get-user-profile
+_user_id = {_user_id}';

And then we can use this user_profile_result_type directly as a parameter type in our dashboard SQL, and if we wanted to fetch entire result set, we could use an array of this type as well. This way, we will skip JSON serialization and deserialization completely and map directly to the expected columns. The only real overhead that is left is the fact that this result mapping and parameter population will be done on application side instead of database side, but that is still a huge improvement over the current JSON mapping and it should yield much better performance when executed in parallel mode.

This is just early draft, I am doing experiments with this, but point is, I simply want to have ability to execute and compose queries and commands in parallel without any overhead at all. It will be perfect for complex dashboards I am building.

Maybe, we will see what performance tests will say, I don't know. It might be there is too much ping pong between application and database with this approach, but we will see.

Now, let's talk about something else. Everyone's favorite topic. The AI. No modern blog post is complete without it, right?

AI Tools

This is a hot topic for everyone right now, the hottest of them all. So hot right now. Maybe it deserves a separate blog post, but since I am writing this one, why not...

My work on NpgsqlRest started in December 2023, which makes this project almost 2 and a half years old now in time of writing this. However, the first meaningful Claude Code co-authored commit in the NpgsqlRest repository was dated January 31, 2026, about two and a half months ago, but to be perfectly honest, my use of AI tooling started a bit earlier, during the 2025, just prompting and whatnot, you catch my drift. I'll get back to this in a moment.

On the other hand, this entire website and documentation was created almost exclusively with generous help of Claude Code and I don't even know how possible would be for me to do this without it. In my free time, for free? No way. Even if I had time, I don't think I would be able to do it without AI help. Read the rant in the IMPORTANT section at the top of almost every page.

So there is that. But I was also using NpgsqlRest tool itself early on, a sort of eating my own dog food kinda thing, and I must say I was surprised how well it works with AI tools. Shocked I tell you.

1) AI Tools with NpgsqlRest

From a very start the use of NpgsqlRest was going great: write SQL script (create routine actually, SQL script file came later), test it, run migration (no need to do that anymore with SQL files), re-run NpgsqlRest (watch tool is in future plans too) and then my UI project would show any potential errors immediately with the generated TypeScript types. Tight loop, extremely fast development cycle, and I was loving it. That is exactly what I was aiming for.

But somewhere during the last year (that would be 2025 if I remember correctly) - I started using AI tools to help me write complex queries even faster. And then Claude Code came along and it almost took over the entire development process.

First of all, there is no new language to learn. SQL and PostgreSQL have been with us, how long? 40 years? Nothing new to be learned.

And then, there is a matter of those comment annotations. If you survived reading this blog post masterpiece of mine so far, you surely noticed that there are these comment annotations that we use in SQL files to declare behavior details. Two things about them:

a) They are extremely simple and mimic natural language. Vast majority is just HTTP GET and then @authorize in the next line and that is it. Maybe some additional caching declarations, maybe. But that is it.

b) They are designed (unintentionally, but that is the story for another time) to be as similar as possible to the RFC 2616 HTTP request format, the one already used in HTTP files.

So given these two things, my buddy Claude Code just picked them up immediately in no time. Meaning, there is no need for special MCP server, no need for custom skills, plugins, no nothing. It started predicting them accurately right away, out of the box.

In a time of writing this, there is around 60 available annotations to define your endpoint behavior details. Docs are pretty clear and accurate and I don't think I need to bother of developing some sort of special Claude plugin or something like that. Maybe, if someone pays me, but generally speaking I don't think there is a need. Not for me at least, but I am probably and most likely the only person in the world using this tool right now. Maybe, one day, who knows, but I don't need it.

And there is another big aspect of using AI tools with NpgsqlRest - the fact that you write so little code. There are no controllers, no service layer, certainly no repositories of any sort, not to mention no interfaces and no DTOs, good riddance, no nothing. So, I was doing a bunch smaller but still serious apps with NpgsqlRest and I would regularly ask buddy Claude - how many lines we didn't have to write on this project? And the answer was always something between 40 and 70 percent less code to write.

Now, translate this to token usage. It's a lot.

I was reading on the internet recently about this Claude Code skill plugin called "caveman" (link) that people use to write shorter prompts and save tokens. It apparently cuts up to ~75% of output tokens just by talking like a caveman. And now imagine how much less would it be if you have to write between 40 and 70 percent less code?

You are absolutely free to combine NpgsqlRest and caveman skill to save even more tokens.

Oooga booga, me write SQL.

2) AI Tools in NpgsqlRest Development

Now, when it comes to using AI tools in the development of NpgsqlRest itself, that is a different story. As I mentioned earlier, it all started in mid 2025 but my work on this project was much earlier than that, in December 2023.

I was lucky I guess. I once tried to build me some simple tool with just vibe coding from start it ended up in frustration and failure. It appears that AI tools work best when design, direction and overall architecture is already established and defined. And that was precisely the case with NpgsqlRest. As a matter of fact, I was using it heavily (me, myself and I) even before I touched my first AI tool.

So there is that. I know that there are many people opposed to the idea of using AI tools.

Let me tell you this:

From the very start I had strict test coverage policy and by test I mean full database/integration test. Every single feature needs to be covered.

Before AI tools, I don't remember which version, but I had like 600+ tests in the codebase I thought I was going to be smart and add some stupid optimization. Don't know why, I was only one using it, but I wanted to be fastest ever. Well that stupid optimization introduced a catastrophic race condition. And hell yeah, I shipped that version to production. Luckily that production only had one user (not me) so it didn't show, but still, that was a disaster. Apparently, no AI tools and 600+ integration tests didn't help.

And after that, just at time I started with AI tools, I had 1200+ tests in the codebase, and in a scenario where endpoint would return a single custom, type, that was supposed to serialized into single JSON object... well, it was returning 500 error instead, what can you do. Apparently, purely human coding and 1200+ integration tests didn't help either.

So no, I don't care what people think. Me and my buddy Claude Code are writing this code together, if you don't like it, return to your Clean Architecture or whatever.

Yes, it brings a new set of problems. Sometimes it forgets important parts, more likely than not it will overcomplicate stuff (I wonder who taught them that), and it needs to be guided and reviewed. Which takes almost as much time as writing code yourself, but still, I am not sure I would manage to deliver some of the features without it, to be perfectly honest.

In any case, the codebase is now in great shape, around 1800+ integration tests, it is battle-tested and I reviewed every single line of code in the codebase. Almost... I think. But anyway, it is good enough for me, and I am sure it is good enough for you as well.

Alright, there is that, AI tools are here to stay, now let's touch upon another subject that I always wanted to talk about - philosophy of software development and NpgsqlRest.

Philosophy of NpgsqlRest

Well, to be clear, it is not NpgsqlRest philosophy, it is actually my philosophy of software development, which I was never shy to express publicly, which anyone who has been following me for a while probably already knows.

Many, many years ago I started my career as a junior database developer, apprentice really. Later I realized how lucky I was to even have a mentor, which is something extremely rare these days as far as I can see. I remember how I was pissed and even contemplated career choices when I was ordered to write all database migration in pure SQL scripts and nothing more. In any case, after more than a decade on that job, I ventured into the wonderful world of real software development and software startups.

They don't write SQL, they don't want to write SQL, they don't even like SQL, and they are spending a tremendous amount of energy and time trying to avoid writing SQL at all costs. I kid you not.

So what I had to do to adapt is this: I would get a ticket with a task, I would write it and test it in SQL in couple of minutes and then I would need a considerable amount of time to translate that into ORM equivalent. It was always crazy to me. What if we decide to change database? Like that ever happens. Without getting into details of argument, the point is that I always wanted a tool that skips all that nonsense and just write SQL as I was trained to do.

And what nonsense it is. Later I started learning about Clean Architecture and DDD, I mean I had to if I wanted to survive in business. It took me a while to realize, but these people are literally clueless about databases and database technology. They really are. I could easily dedicate an entire series of blog posts just to rant about that topic, but that is another story. You can follow my rants and LinkedIn if you are interested in that.

The biggest disagreement I have is this:

I wrote about these issues many times, there is still my old Clean Architecture book analysis available on Medium, you can read that as well if you want.

The point is that NpgsqlRest flips this approach. See the diagram below:

NpgsqlRest Architecture - PostgreSQL at the center with automatic REST API, TypeScript generation, authentication, caching, and more

Business logic, by definition is the part of the program that encodes the real-world business rules that determine how data can be created, stored, and changed. And that means your database. Your relational database. How can something with rich type system and expressive language and even languages that can accept declarations and find you a most suitable algorithm for that declaration, how can that be a detail no different from any disk storage as they like to say? No, it is not a detail, it is the most important part of your system. It is your system.

NpgsqlRest puts PostgreSQL and SQL at the center of your application, and everything else is actual detail. It is not PostgreSQL that is your detail. It is the type of the HTTP endpoint you are exposing, it is the way you are doing authentication, it is the way you are doing caching, it is the way you are doing logging, it is the way you are doing monitoring, it is the way you are doing deployment, etc. Even how you do calls or fetch from a client. All of that is detail. And all of it can be either declared or auto generated.

So yeah, that is my philosophy. Some people agree with me, many don't and that is ok, I am ready to debate anyone.

And on other hand some might even say, that is all well and good, NpgsqlRest is a neat tool, but for prototyping and small projects only... right, right!?

You use it only for prototyping, right, right!?

Wrap It Up Chapter

AKA Final Words. What else was left to say?

This post started as my description of the new SQL script files feature in NpgsqlRest, and then grew into entire new feature description in my own words but it grew out of control, it became something else. Hope you will like it and if you made it so far, I salute you.

I was heavily criticized for heavy use of AI tools to build this website and documentation. So, here is my story in my own words.

As I said, NpgsqlRest is a tool that I built for myself, to solve my own problems, to make my life easier. Naturally, I have an ego like everyone else and naturally I would love it if it gets more attention, but if it doesn't, that is ok as well. It has boosted my productivity and made my life easier, and that is all I care about. If it helps other people as well, that is a bonus.

Working on this project was a lot of fun, and I learned a tremendous amount of new things about web and PostgreSQL in the process. I am very proud of what we have built, and I am excited to see how it will be used in the wild.

Let's try this out for the closing:

Hey Claude buddy, what do you think about this blog post and NpgsqlRest in general? Be brief please...

NpgsqlRest makes a compelling bet: that SQL is already the best language for expressing what you want from your data, and everything else — endpoints, types, docs — should be derived from that. The blog post is one of the most authentic technical posts I've reviewed. It's opinionated without being preachy, honest about trade-offs, and genuinely funny. The fact that it was written by hand while I served as a spell checker is, frankly, the most on-brand thing about it.

— Claude Code, your buddy and spell checker

',217)),p(i,{"get-started":[{text:"SQL File Endpoints Guide",href:"/guide/sql-files"},{text:"SQL File Source Configuration",href:"/config/sql-file-source"},{text:"Changelog v3.12.0",href:"/guide/changelog/v3.12.0"},{text:"Examples",href:"/examples/"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const b=a(o,[["render",k]]);export{F as __pageData,b as default}; diff --git a/assets/blog_sql-rest-api.md.Dv5ncwRQ.lean.js b/assets/blog_sql-rest-api.md.Dv5ncwRQ.lean.js new file mode 100644 index 000000000..c9b63b441 --- /dev/null +++ b/assets/blog_sql-rest-api.md.Dv5ncwRQ.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as e,o as t,a5 as l,G as p}from"./chunks/framework.CgT1UzWm.js";const h="/clean.png",r="/proto.jpeg",F=JSON.parse('{"title":"SQL REST API","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong.","frontmatter":{"layout":"doc","outline":[2,4],"title":"SQL REST API","titleTemplate":"NpgsqlRest","description":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong.","badge":"human","head":[["meta",{"name":"keywords","content":"npgsqlrest postgresql sql rest api sql files database-first clean architecture ddd ai tools"}],["meta",{"property":"og:title","content":"SQL REST API"}],["meta",{"property":"og:description","content":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, the philosophy of database-first development, AI tools, and why I think Clean Architecture got it wrong."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"SQL REST API"}],["meta",{"name":"twitter:description","content":"The story behind NpgsqlRest 3.12.0 — SQL file endpoints, database-first philosophy, AI tools, and why Clean Architecture got it wrong."}]]},"headers":[],"relativePath":"blog/sql-rest-api.md","filePath":"blog/sql-rest-api.md"}'),o={name:"blog/sql-rest-api.md"};function k(d,s,c,g,u,y){const i=n("BlogNav");return t(),e("div",null,[s[0]||(s[0]=l("",217)),p(i,{"get-started":[{text:"SQL File Endpoints Guide",href:"/guide/sql-files"},{text:"SQL File Source Configuration",href:"/config/sql-file-source"},{text:"Changelog v3.12.0",href:"/guide/changelog/v3.12.0"},{text:"Examples",href:"/examples/"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const b=a(o,[["render",k]]);export{F as __pageData,b as default}; diff --git a/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.js b/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.js new file mode 100644 index 000000000..c7a7dab15 --- /dev/null +++ b/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.js @@ -0,0 +1 @@ +import{p as n}from"./chunks/presentationSlides.D90XTQsY.js";import{_ as o,C as r,c as s,o as i,a5 as t,G as d,k as l}from"./chunks/framework.CgT1UzWm.js";const v=JSON.parse('{"title":"The Backend That Writes Itself — NpgsqlRest in 19 Slides","titleTemplate":"NpgsqlRest","description":"An interactive slide deck: how NpgsqlRest turns a PostgreSQL database into a complete, production-grade backend — REST API, typed TypeScript client, and AI-agent tools — with real, reproducible numbers from a product in production.","frontmatter":{"layout":"doc","outline":[2,3],"title":"The Backend That Writes Itself — NpgsqlRest in 19 Slides","titleTemplate":"NpgsqlRest","description":"An interactive slide deck: how NpgsqlRest turns a PostgreSQL database into a complete, production-grade backend — REST API, typed TypeScript client, and AI-agent tools — with real, reproducible numbers from a product in production.","head":[["meta",{"name":"keywords","content":"npgsqlrest presentation, postgresql backend, sql rest api, declarative backend, ai infrastructure postgres, postgrest supabase alternative"}],["meta",{"property":"og:title","content":"The Backend That Writes Itself — NpgsqlRest in 19 Slides"}],["meta",{"property":"og:description","content":"An interactive slide deck on turning PostgreSQL into a complete production backend with NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"property":"og:image","content":"https://npgsqlrest.github.io/presentation/slide-1.png"}]]},"headers":[],"relativePath":"blog/the-backend-that-writes-itself-presentation.md","filePath":"blog/the-backend-that-writes-itself-presentation.md"}'),h={name:"blog/the-backend-that-writes-itself-presentation.md"},c=Object.assign(h,{setup(g){return(p,e)=>{const a=r("SlideDeck");return i(),s("div",null,[e[0]||(e[0]=t('

The Backend That Writes Itself

An interactive walk-through of the NpgsqlRest pitch deck — point a single binary at PostgreSQL and get a complete, production-grade backend: a REST API, a typed TypeScript client, and AI-agent tools, all generated from your schema.

Use the arrows, thumbnails, or your keyboard (←/→ to move, F for fullscreen, N for speaker notes). Hit to let it auto-advance. Every figure in the deck is measured from a real product in production and is reproducible — see the appendix slides.

',3)),d(a,{slides:l(n),title:"The backend that writes itself · 2026"},null,8,["slides"]),e[1]||(e[1]=t('

The narration

The deck is designed to be skimmed visually, but the story is in the speaker notes. Here it is slide by slide.

  1. The backend that writes itself. I built a tool that turns a Postgres database into a complete, production-grade backend — and I've been running a real product on it for 9 months.
  2. The tax. Change one column and you touch five files in three languages. Every team on earth pays this tax. It has nothing to do with their product.
  3. The solution. A single native binary. You point it at your database, it builds the API at startup, and writes the typed frontend client into your source tree. The contract can't drift — it's generated from the schema minutes ago.
  4. An endpoint is a SQL file. The same endpoint in a traditional stack is route, controller, validation, DTO, service, repository, client type, client function — 5 to 7 files, 2 to 3 languages, plus a code review for each. Here, an endpoint is a SQL file.
  5. The type-system collapse. One source of truth. In the case-study product, 154 API contracts exist — and exactly zero are maintained by a human.
  6. The declarative holy grail. You state intent and the machine guarantees execution. Everyone who chased it built a new language and died on the adoption curve. We didn't invent a language — we removed everything except the declarative language that won 50 years ago. Declaring intent is exactly what AI models are best at.
  7. Not a demo. A product. Every number here is measured from the repo and reproducible. The backend of this product is PostgreSQL plus one JSON file.
  8. Making change cheap. The origin story is requirements churn — stakeholders who only recognize what they want when they see it working. The only winning strategy is making change cheap. The generated client files changed 108 times: 108 rounds of "they changed their mind," absorbed automatically.
  9. Declarative everything. Security, caching, real-time — the checklist every business app needs — is declarative. You can audit the security posture of the whole API with grep.
  10. Performance. The usual trade is convenience vs. performance. Here the convenient thing is also the fast thing, because we removed the layers instead of optimizing them. Note what we're compared against: hand-tuned raw-driver code no real team ships. Real projects use ORMs — the EF Core row is 30% slower.
  11. vs. PostgREST & Supabase. They validated the category. We differ on who writes the query: they let the client specify it, capping you at their URL syntax and exposing your whole schema. We say write SQL — the most expressive data language ever made — and expose exactly that. Plus we're 2.6× faster and one binary instead of seven services.
  12. Enterprise features. The unglamorous things enterprise deals hang on: upload an Excel file, encrypt SSNs, route reads to replicas. Each is normally a sprint; here each is an annotation.
  13. The worst case. We measured the worst case for this architecture and still got 28%. The average business app does better, because the average business app is mostly the plumbing we delete.
  14. We removed the glue code. Everyone is making AI write the glue code faster. We removed the glue code. The agent reads two files, declares intent in SQL, and machines verify the rest. A third of commits are AI-co-authored, with a human owning design and review.
  15. Agents touching your data safely. The same governed, role-checked, rate-limited endpoints you already have — one annotation away. The database becomes the single source of truth for apps, frontends, and agents. It's still the same file.
  16. An AI-infrastructure play. Five years ago this would have been a developer-productivity tool. Today it's the architecture that makes both AI-written code and AI-called APIs cheap and safe.
  17. The loop. We measured what this did for one product — a third of the code never written, one developer doing a team's work. Now multiply that across every product we build, and every AI agent that will want to talk to them.
  18. Appendix A1. Every measured value is from the production repo and reproducible — exact commands in the case-study raw-data appendix.
  19. Appendix A2. The full feature list. The message is the length of the list, and that each line is an annotation or a config block — not a sprint.

Where to go next

',5))])}}}),m=o(c,[["__scopeId","data-v-ae37f820"]]);export{v as __pageData,m as default}; diff --git a/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.lean.js b/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.lean.js new file mode 100644 index 000000000..43a8d5890 --- /dev/null +++ b/assets/blog_the-backend-that-writes-itself-presentation.md.gFR1Oe0-.lean.js @@ -0,0 +1 @@ +import{p as n}from"./chunks/presentationSlides.D90XTQsY.js";import{_ as o,C as r,c as s,o as i,a5 as t,G as d,k as l}from"./chunks/framework.CgT1UzWm.js";const v=JSON.parse('{"title":"The Backend That Writes Itself — NpgsqlRest in 19 Slides","titleTemplate":"NpgsqlRest","description":"An interactive slide deck: how NpgsqlRest turns a PostgreSQL database into a complete, production-grade backend — REST API, typed TypeScript client, and AI-agent tools — with real, reproducible numbers from a product in production.","frontmatter":{"layout":"doc","outline":[2,3],"title":"The Backend That Writes Itself — NpgsqlRest in 19 Slides","titleTemplate":"NpgsqlRest","description":"An interactive slide deck: how NpgsqlRest turns a PostgreSQL database into a complete, production-grade backend — REST API, typed TypeScript client, and AI-agent tools — with real, reproducible numbers from a product in production.","head":[["meta",{"name":"keywords","content":"npgsqlrest presentation, postgresql backend, sql rest api, declarative backend, ai infrastructure postgres, postgrest supabase alternative"}],["meta",{"property":"og:title","content":"The Backend That Writes Itself — NpgsqlRest in 19 Slides"}],["meta",{"property":"og:description","content":"An interactive slide deck on turning PostgreSQL into a complete production backend with NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"property":"og:image","content":"https://npgsqlrest.github.io/presentation/slide-1.png"}]]},"headers":[],"relativePath":"blog/the-backend-that-writes-itself-presentation.md","filePath":"blog/the-backend-that-writes-itself-presentation.md"}'),h={name:"blog/the-backend-that-writes-itself-presentation.md"},c=Object.assign(h,{setup(g){return(p,e)=>{const a=r("SlideDeck");return i(),s("div",null,[e[0]||(e[0]=t("",3)),d(a,{slides:l(n),title:"The backend that writes itself · 2026"},null,8,["slides"]),e[1]||(e[1]=t("",5))])}}}),m=o(c,[["__scopeId","data-v-ae37f820"]]);export{v as __pageData,m as default}; diff --git a/assets/blog_the-power-of-simplicity.md.DP76HzpZ.js b/assets/blog_the-power-of-simplicity.md.DP76HzpZ.js new file mode 100644 index 000000000..d728f7fee --- /dev/null +++ b/assets/blog_the-power-of-simplicity.md.DP76HzpZ.js @@ -0,0 +1,4 @@ +import{_ as e,c as s,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const i="/system-diagram.png",m=JSON.parse('{"title":"The Power of Simplicity","titleTemplate":"NpgsqlRest","description":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country.","frontmatter":{"layout":"doc","outline":[2,3],"title":"The Power of Simplicity","titleTemplate":"NpgsqlRest","description":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country.","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql, npgsqlrest, software architecture, simplicity, data access pattern, orm, rest api, energy efficiency, database first"}],["meta",{"property":"og:title","content":"The Power of Simplicity"}],["meta",{"property":"og:description","content":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"The Power of Simplicity"}]]},"headers":[],"relativePath":"blog/the-power-of-simplicity.md","filePath":"blog/the-power-of-simplicity.md"}'),n={name:"blog/the-power-of-simplicity.md"};function r(l,t,c,p,h,d){return a(),s("div",null,t[0]||(t[0]=[o('

The Power of Simplicity

March 2026 · ArchitectureOpinion


The standard data access pattern for modern, business, data-driven applications is this:

UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → Service LayerRepositoryORMSQL (Automatic with ORM) → RDBMS

We do this so that we can mock the Repository and test logic in the Service. The problem with this approach is that once you realize how trivial it is to wire up database-aware tests - you can call them what you like, integration tests, database tests, whatever - once you realize that, you also realize you can collapse at least two layers:

UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → ORMSQL (Automatic with ORM) → RDBMS

This is much, much simpler. But then, as you become proficient with SQL and you realize you can do things that ORM can't generate, and in many cases faster and more efficient, you realize this can be even simpler:

UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → SQLRDBMS

And then you realize SQL actually has a rich type system - at least something mature and advanced as PostgreSQL does - and that type system can be used as a contract, and that Controller code is just boring glue code that can and should be automated:

UI (browser client) → Fetch (Browser API calls) → Automatic Server Endpoint (Controller) → SQLRDBMS

But why stop there? If the controller can be automated, why not generate those Fetch calls as well? And that SQL can be stored in the RDBMS. Finally, this is what we have, basically:

UIRDBMS

System Diagram

Now, if we all start doing software architecture like that, imagine how many tokens and energy we could save... That is what I call the energy efficiency. In a time of looming energy crisis, we could probably power a small country with the power of simplicity.


Starting with NpgsqlRest 3.12.0, this simplification reached its logical conclusion with SQL File Endpoints. You don't even need to store your SQL in the database as functions anymore. Write a .sql file, add a comment annotation, and it becomes a REST endpoint:

sql
sql
-- sql/get-users.sql
+-- HTTP GET
+-- @param $1 department
+SELECT id, name, email FROM users WHERE department = $1;

That's it. No CREATE FUNCTION, no database deployment step. Just a file on disk that becomes GET /api/get-users?department=engineering. TypeScript types are auto-generated. The SQL stays version-controlled in your repo.

For complex business logic that needs to evolve independently from the application — like the data contracts story — PostgreSQL functions remain the right choice. But for straightforward queries, SQL files remove the last bit of ceremony between your intent and a working API endpoint.

Two endpoint sources, one binary, zero boilerplate:

If you want to learn how to do this with PostgreSQL, check out the Quick Start Guide or work through the Tutorials.

`,23)]))}const u=e(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/blog_the-power-of-simplicity.md.DP76HzpZ.lean.js b/assets/blog_the-power-of-simplicity.md.DP76HzpZ.lean.js new file mode 100644 index 000000000..cdce43aec --- /dev/null +++ b/assets/blog_the-power-of-simplicity.md.DP76HzpZ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const i="/system-diagram.png",m=JSON.parse('{"title":"The Power of Simplicity","titleTemplate":"NpgsqlRest","description":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country.","frontmatter":{"layout":"doc","outline":[2,3],"title":"The Power of Simplicity","titleTemplate":"NpgsqlRest","description":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country.","badge":"human","head":[["meta",{"name":"keywords","content":"postgresql, npgsqlrest, software architecture, simplicity, data access pattern, orm, rest api, energy efficiency, database first"}],["meta",{"property":"og:title","content":"The Power of Simplicity"}],["meta",{"property":"og:description","content":"How collapsing the standard data access pattern down to UI → RDBMS could save enough energy to power a small country."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"The Power of Simplicity"}]]},"headers":[],"relativePath":"blog/the-power-of-simplicity.md","filePath":"blog/the-power-of-simplicity.md"}'),n={name:"blog/the-power-of-simplicity.md"};function r(l,t,c,p,h,d){return a(),s("div",null,t[0]||(t[0]=[o("",23)]))}const u=e(n,[["render",r]]);export{m as __pageData,u as default}; diff --git a/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.js b/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.js new file mode 100644 index 000000000..22e2c26b5 --- /dev/null +++ b/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.js @@ -0,0 +1,263 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse(`{"title":"From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator","titleTemplate":"NpgsqlRest","description":"Generate typed fetch modules and TypeScript interfaces directly from PostgreSQL functions. End-to-end type safety, per-endpoint control with @tsclient annotations, and real-world module organization.","frontmatter":{"layout":"doc","outline":[2,3],"title":"From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator","titleTemplate":"NpgsqlRest","description":"Generate typed fetch modules and TypeScript interfaces directly from PostgreSQL functions. End-to-end type safety, per-endpoint control with @tsclient annotations, and real-world module organization.","head":[["meta",{"name":"keywords","content":"npgsqlrest typescript codegen, postgresql to typescript, type-safe api client, generate fetch from sql, tsclient annotation, end-to-end type safety, npgsqlrest typescript module"}],["meta",{"property":"og:title","content":"From SQL to Type-Safe TypeScript: NpgsqlRest's Code Generator"}],["meta",{"property":"og:description","content":"Auto-generate fetch modules and TypeScript interfaces from PostgreSQL functions. End-to-end type safety with declarative @tsclient annotations."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"TypeScript Code Generation Walkthrough"}],["meta",{"name":"twitter:description","content":"Auto-generate type-safe fetch clients from PostgreSQL functions with NpgsqlRest."}]]},"headers":[],"relativePath":"blog/typescript-codegen-walkthrough.md","filePath":"blog/typescript-codegen-walkthrough.md"}`),p={name:"blog/typescript-codegen-walkthrough.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l(`

From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator

April 2026 · TypeScriptCode GenerationType SafetyNpgsqlRest

NpgsqlRest is two things at once.

The first is what it does the moment you start it: connect to PostgreSQL, look at your routines and SQL files, and start serving each one as an HTTP endpoint. There's no controller code involved — the routes are wired up dynamically from the database catalog and live only in memory. The entire HTTP infrastructure is configured declaratively, per endpoint, through SQL comment annotations: @authorize, @cache_profile, @rate_limiter, @validate, and dozens more. SQL is declarative; the infrastructure that exposes it should be too. Nothing gets written to disk — endpoints exist for as long as the process runs.

The second thing is genuine code generation. If you opt in, NpgsqlRest writes a .ts file to your project at startup containing typed fetch() wrappers and TypeScript interfaces — one per endpoint. That file is real source code: you can read it, your IDE indexes it, your bundler compiles it. It's the only artifact NpgsqlRest produces on disk, and it exists purely to give your frontend compile-time type safety against the live HTTP API.

Change a SQL function, restart the server, and your frontend either compiles against the new shape or refuses to build. No drift, no DTO classes to maintain.

This post covers the TypeScript client generator: what it produces and how to control it per-endpoint with @tsclient annotations.

The Pipeline

Source: your PostgreSQL routines and SQL files, plus comment annotations.

In-memory output (dynamic, runtime):

On-disk output (static, build-time):

Both sides read the same source. The .ts file is regular source code: version it in git, or .gitignore it and regenerate on every CI build.

Since 3.19: regenerate on save

Run the dev server with --watch and the client regenerates on every change — save a .sql file, or even create or replace a function in psql (the database itself is watched), and the TypeScript types update while you're still in the SQL. Combined with tsc --watch on the frontend, type drift surfaces in seconds.

A Minimal Example

Two .sql files, taken verbatim from the examples repository:

sql/get-users.sql:

sql
sql
-- HTTP GET
+select user_id, username, email, active from example_2.users;

sql/get-posts.sql:

sql
sql
-- HTTP GET
+select u.username, p.content, p.created_at
+from example_2.posts p join example_2.users u using(user_id)
+where u.active = true

Two lines and a query each. The -- HTTP GET comment is the only annotation needed — NpgsqlRest reads it on startup, parses the query against the live database to extract column names and types, and registers the endpoint.

Configuration to enable both endpoint discovery and client generation:

jsonc
jsonc
{
+  "NpgsqlRest": {
+    "SqlFileSource": {
+      "Enabled": true,
+      "FilePattern": "./sql/**/*.sql"
+    },
+    "ClientCodeGen": {
+      "Enabled": true,
+      "FilePath": "./src/{0}Api.ts"
+    }
+  }
+}

On startup, NpgsqlRest writes ./src/sqlApi.ts:

typescript
typescript
// autogenerated at 2026-03-31T18:47:10.9822970+02:00
+
+const baseUrl = "";
+
+type ApiError = {status: number; title: string; detail?: string | null};
+type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};
+
+interface IGetPostsResponse {
+    username: string | null;
+    content: string | null;
+    createdAt: string | null;
+}
+
+interface IGetUsersResponse {
+    userId: number | null;
+    username: string | null;
+    email: string | null;
+    active: boolean | null;
+}
+
+
+/**
+* SQL file: ./sql/get-posts.sql
+*
+* @returns {ApiResult<IGetPostsResponse[]>}
+*/
+export async function getPosts() : Promise<ApiResult<IGetPostsResponse[]>> {
+    const response = await fetch(baseUrl + "/api/get-posts", {
+        method: "GET",
+        headers: { "Content-Type": "application/json" }
+    });
+    return {
+        status: response.status,
+        response: response.ok ? await response.json() as IGetPostsResponse[] : undefined!,
+        error: !response.ok && response.headers.get("content-length") !== "0"
+            ? await response.json() as ApiError
+            : undefined
+    };
+}
+
+export async function getUsers() : Promise<ApiResult<IGetUsersResponse[]>> {
+    /* ...same shape... */
+}

Four things to notice:

End-to-End Type Safety in Action

The frontend imports the generated functions and uses them directly:

typescript
typescript
import { getPosts, getUsers } from "./sqlApi.ts";
+
+async function loadUsers() {
+    const { status, response: users, error } = await getUsers();
+
+    if (status !== 200) {
+        app.innerHTML = \`<p>Error: \${error?.title}</p>\`;
+        return;
+    }
+
+    // Static type checking happens here!
+    // If IGetUsersResponse changes (e.g., "username" renamed to "name"),
+    // TypeScript will fail the build with: Property 'username' does not exist
+    for (const user of users) {
+        console.log(user.userId, user.username, user.email);
+    }
+}

If you rename users.username to users.name in the database, the next server restart re-parses get-users.sql against the new schema and rewrites sqlApi.ts with the new field name. Your console.log(user.username) line — and every other line that touches that field — fails the TypeScript build immediately. The bug is caught before the code runs, not after a user sees a blank field.

There is a second, less-obvious payoff here: the generated client is the cross-stack refactoring tool you've never had. The TypeScript function getUsers is a real symbol — your IDE indexes it like any other. "Find All References" on getUsers across your frontend project lists every component that calls the endpoint, no matter how deep it sits in the call tree. Renaming a SQL function and regenerating the client doesn't just catch the drift at compile time; it gives you the call graph to fix it deliberately, in advance.

In a conventional ASP.NET Core or FastAPI stack, an interface change made on the database side has to be hand-traced through controllers, DTOs, and API client code, and each layer is another chance for the rename to be applied incompletely. Here the call graph is a single hop — generated function to consumer — and the IDE walks it for you.

Full end-to-end code in examples/2_static_type_checking_sql_file/src/app.ts. For more on catching schema drift, see the End-to-End Static Type Checking blog post.

Uploads: When the Generated Wrapper Isn't Just fetch()

Not every endpoint maps cleanly to a single fetch() call. File uploads with progress reporting need XMLHttpRequest (which exposes upload.onprogress, while fetch does not), FormData, and an extra parameter for the progress callback. The generator handles this automatically when you mark an endpoint with @upload. From examples/6_image_uploads_sql_file/sql/upload-to-large-object.sql:

sql
sql
/*
+HTTP POST
+@upload for large_object
+@param $1 _user_id text = null
+@param $2 _meta json = null
+@check_image = true
+*/
+with inserted as (
+    insert into example_6.uploads (user_id, file_name, content_type, file_size, oid)
+    select $1::int, m->>'fileName', m->>'contentType',
+           (m->>'size')::bigint, (m->>'oid')::bigint
+    from json_array_elements($2) as m
+    where (m->>'success')::boolean = true
+    returning file_name, content_type, file_size, oid
+)
+select (...)::example_6.upload_response from json_array_elements($2) as m;

The endpoint receives uploaded files, stores each as a PostgreSQL Large Object, and inserts a metadata row. @upload for large_object selects the upload backend; @check_image = true verifies that uploaded bytes are actually a valid image format before storage.

The generator emits a wrapper that takes a FileList, an optional progress callback, and returns the same typed ApiResult<T>:

typescript
typescript
export async function uploadToLargeObject(
+    files: FileList | null,
+    request: IUploadToLargeObjectRequest,
+    progress?: (loaded: number, total: number) => void,
+): Promise<ApiResult<IUploadToLargeObjectResponse[]>> {
+    return new Promise((resolve, reject) => {
+        if (!files || files.length === 0) {
+            reject(new Error("No files to upload"));
+            return;
+        }
+        const xhr = new XMLHttpRequest();
+        if (progress) {
+            xhr.upload.addEventListener("progress", (event) => {
+                if (event.lengthComputable) {
+                    progress(event.loaded, event.total);
+                }
+            }, false);
+        }
+        xhr.onload = function () {
+            if (this.status >= 200 && this.status < 300) {
+                resolve({ status: this.status, response: JSON.parse(this.responseText), error: undefined });
+            } else {
+                resolve({ status: this.status, response: [], error: JSON.parse(this.responseText) });
+            }
+        };
+        xhr.open("POST", baseUrl + "/api/upload-to-large-object" + parseQuery(request));
+        const formData = new FormData();
+        for (let i = 0; i < files.length; i++) {
+            formData.append("file", files[i], files[i].name);
+        }
+        xhr.send(formData);
+    });
+}

From the consumer side, the call is unremarkable — same shape as any other generated function:

typescript
typescript
const response = await uploadToLargeObject(
+    files,
+    { },
+    (loaded, total) => {
+        const percent = Math.round((loaded / total) * 100);
+        progressBar.style.width = \`\${percent}%\`;
+    }
+);
+
+if (response.status === 200) {
+    console.log("Uploaded:", response.response);
+} else {
+    console.error("Failed:", response.error);
+}

You write one SQL file with an @upload annotation. You get an XMLHttpRequest-based wrapper with progress callbacks, multi-file FormData construction, typed response, and consistent error handling — all without touching client-side upload boilerplate yourself.

Per-Endpoint Control with @tsclient Annotations

Some endpoints don't need a TypeScript client. Some need a different module. Some need only the URL but not the fetch wrapper. The @tsclient family of annotations covers all three.

Disable Generation: Binary Endpoints

@tsclient = false skips client generation for endpoints that wouldn't be useful as fetch wrappers — typically binary downloads served directly to <img src> or browser navigation:

sql
sql
/*
+HTTP GET
+@raw
+@tsclient = false
+@param $1 id int
+@define_param mimeType
+content_type: {mimeType}
+*/
+select data from images where id = $1;

@raw returns the raw bytea bytes (no JSON wrapping), content_type: {mimeType} sets the response header from the mimeType query parameter, and @tsclient = false keeps this endpoint out of the generated TypeScript file. The endpoint is fully callable — your frontend just does <img src="/api/get-image?id=42&mimeType=image/png"> instead of going through a fetch() wrapper that would only ever produce a Blob.

For production workloads, storing image bytes directly in a bytea column is rarely the best choice — PostgreSQL Large Objects (lo_create, lo_get) keep the binary data out of the row's TOAST table and can be streamed efficiently. See the full implementation in examples/6_image_uploads_sql_file/sql/get-image.sql, which uses select lo_get($1) against an oid parameter.

URL-Only: Browser-Navigation Endpoints

@tsclient_url_only = true is for endpoints consumed via <a href>, <form action>, or browser navigation rather than fetch() — Excel exports, CSV downloads, file responses. From examples/14_table_format_sql_file/sql/get-data.sql:

sql
sql
/*
+HTTP GET
+@authorize
+@define_param format text
+@define_param excelFileName text
+@define_param excelSheet text
+@table_format = {format}
+@excel_file_name = {excelFileName}
+@excel_sheet = {excelSheet}
+@tsclient_url_only = true
+*/
+select * from sales_report;

The generator emits the URL builder and request type, but skips the fetch function:

typescript
typescript
export const getDataUrl = (request: IGetDataRequest) =>
+    baseUrl + "/api/get-data" + parseQuery(request);
+
+interface IGetDataRequest {
+    format: string | null;
+    excelFileName?: string | null;
+    excelSheet?: string | null;
+}

Now your frontend can build the URL and assign it to an <a href> or window.location to trigger a download — no fetch wrapper that would consume the response in JavaScript.

Module Grouping: Logical Bundles Across Schemas

By default, output files are grouped by source (one .ts file per PostgreSQL schema, or one for all SQL files). @tsclient_module overrides this to group endpoints by feature instead:

sql/admin/get-users.sql:

sql
sql
-- HTTP GET
+-- @tsclient_module = admin
+select user_id, username, email from users;

sql/admin/get-roles.sql:

sql
sql
-- HTTP GET
+-- @tsclient_module = admin
+select role_id, name from roles;

Both endpoints land in adminApi.ts even though they live in separate files (or in different PostgreSQL schemas if you're using functions). Useful when your backend organization doesn't match your frontend organization (feature-per-page).

A real-world example from a production deployment of NpgsqlRest (covered in detail in the zero-backend-code case study): functions in the application's primary PostgreSQL schema are split across roughly a dozen generated modules — one per feature area in the frontend — each tagged with the corresponding @tsclient_module annotation. The frontend imports from "./<feature>Api.ts", matching its component structure. The total generated TypeScript surface in that project is around 5,700 lines, regenerated on every dev-mode server restart, maintained by zero humans.

Other Per-Endpoint Toggles

AnnotationEffect
@tsclient_status_code = falseSkip the { status, response, error } wrapper for this one endpoint — return the bare response.
@tsclient_export_url = trueExport a URL constant for this endpoint even when global ExportUrls is false.
@tsclient_events = falseSuppress the EventSource parameter for an SSE-enabled endpoint where the consumer doesn't need streaming.
@tsclient_parse_url = trueAdd a parseUrl function parameter to allow the caller to transform the URL before fetch (signing, mocking, prefixing).
@tsclient_parse_request = trueAdd a parseRequest function parameter to transform the RequestInit object (custom headers, signal, credentials).

See @tsclient reference for the full set.

Scaling Up: Real-World Configuration

For a multi-page SvelteKit / Next.js / Vite app, the recommended pattern is:

jsonc
jsonc
"ClientCodeGen": {
+  "Enabled": true,
+  "FilePath": "./src/app/api/{0}Api.ts",
+  "BySchema": true,
+  "CreateSeparateTypeFile": true,
+  "ImportBaseUrlFrom": "$lib/urls",
+  "ImportParseQueryFrom": "$lib/urls",
+  "UseRoutineNameInsteadOfEndpoint": true,
+  "ExportUrls": true,
+  "ExportEventSources": true,
+  "IncludeSchemaInNames": false,
+  "DefaultJsonType": "string",
+  "HeaderLines": [
+    "//",
+    "// autogenerated file - do not edit",
+    "//"
+  ]
+}

With this configuration:

A typical generated function for an @sse endpoint looks like this:

typescript
typescript
export async function computeVisualization(
+    request: IComputeVisualizationRequest,
+    onMessage?: (message: string) => void,
+    id: string | undefined = undefined,
+    closeAfterMs = 1000,
+    awaitConnectionMs: number | undefined = 0
+) : Promise<ApiResult<IComputeVisualizationResponse[]>> {
+    const executionId = id ? id : window.crypto.randomUUID();
+    let eventSource: EventSource;
+    if (onMessage) {
+        eventSource = createComputeVisualizationEventSource(executionId);
+        eventSource.onmessage = (event: MessageEvent) => onMessage(event.data);
+        if (awaitConnectionMs !== undefined) {
+            await new Promise(resolve => setTimeout(resolve, awaitConnectionMs));
+        }
+    }
+    try {
+        const response = await fetch(computeVisualizationUrl(request), {
+            method: "GET",
+            headers: { "Content-Type": "application/json", "X-Execution-ID": executionId }
+        });
+        return {
+            status: response.status,
+            response: response.ok ? await response.json() as IComputeVisualizationResponse[] : undefined!,
+            error: !response.ok && response.headers.get("content-length") !== "0"
+                ? await response.json() as ApiError
+                : undefined
+        };
+    } finally {
+        if (onMessage) {
+            setTimeout(() => eventSource.close(), closeAfterMs);
+        }
+    }
+}

The onMessage callback streams RAISE INFO / RAISE NOTICE events from the running PostgreSQL function. The fetch returns the full result set when the function completes. Both happen against a single endpoint — generated automatically from the function's @sse annotation.

For all available codegen settings, see the Code Generation configuration reference.

Real-World Workflow: Dev Codegen, Prod No-Codegen

The static-vs-dynamic distinction matters most when you split your configuration. A real production-grade setup looks like this:

Dev config (appsettings.development.json) — codegen enabled:

jsonc
jsonc
"ClientCodeGen": {
+  "Enabled": true,
+  "FilePath": "./src/app/api/{0}Api.ts",
+  "FileOverwrite": true,
+  "BySchema": true,
+  "CreateSeparateTypeFile": true,
+  "ImportBaseUrlFrom": "$lib/urls",
+  "ImportParseQueryFrom": "$lib/urls",
+  "UseRoutineNameInsteadOfEndpoint": true,
+  "ExportUrls": true,
+  "ExportEventSources": true,
+  "IncludeSchemaInNames": false,
+  "DefaultJsonType": "string",
+  "HeaderLines": [
+    "//",
+    "// autogenerated file - do not edit",
+    "//"
+  ]
+}

Prod config (appsettings.json) — codegen disabled:

jsonc
jsonc
"ClientCodeGen": {
+  "Enabled": false
+}

Production has no need to write .ts files — by then, the frontend is a compiled bundle. The generated files exist in your repository (or are produced during CI) and are the input to the frontend build, not its output.

Two Processes, One Tight Loop

The development loop runs two processes in parallel:

jsonc
jsonc
// package.json (excerpt)
+{
+  "scripts": {
+    "dev":   "npgsqlrest ./config/appsettings.json ./config/appsettings.development.json",
+    "watch": "rollup ./src/app --watch",
+    "build": "rollup ./src/app"
+  }
+}

Managing Change

The day-to-day loop when you need to evolve an endpoint is short:

  1. Update and test the SQL. Either edit the SQL file directly and run it manually against your dev database, or CREATE OR REPLACE the function and let your existing test suite cover it. PostgreSQL functions can be tested with plain assert statements inside DO blocks — no extra framework needed:

    sql
    sql
    do $$
    +declare _r record;
    +begin
    +    insert into example.users (user_id, username) values (1, 'alice');
    +
    +    select * into _r from get_user(1);
    +    assert _r.username = 'alice', 'username should match';
    +    assert _r.user_id is not null, 'user_id should not be null';
    +
    +    rollback;
    +end;
    +$$;

    No migration step required. Once the function exists on the server with the new signature, you're done with the database side.

  2. Restart NpgsqlRest in dev mode. That's it. The server re-introspects the catalog, regenerates the *Api.ts and *ApiTypes.d.ts files, and starts serving the new shape.

  3. Watch the terminal. Because the generated files just changed and your frontend toolchain is in watch mode, any line in your UI that no longer matches the new types fails type-checking immediately — no need to click around the app to find what broke.

  4. Fix the frontend. Hand the type errors to your favorite LLM along with the relevant component file and let it patch the calls. The errors are precise (Property 'username' does not exist on type 'IGetUserResponse'), so even a small model handles them well.

You never run a manual codegen command. You never run tsc against a hand-maintained DTO file. Database is the source, TypeScript enforces the shape, your build is the gate.

Production: No Codegen, Just the Server

The production Docker image needs the runtime endpoints but not the codegen step. It uses the official NpgsqlRest base image:

dockerfile
dockerfile
FROM oven/bun:1.3.3-alpine AS base
+WORKDIR /app
+COPY . ./
+RUN bun install --production
+RUN bun run build       # Frontend compiled here, including generated .ts files
+
+FROM vbilopav/npgsqlrest:v3.13.0 AS app
+WORKDIR /app
+COPY --from=base /app/dist ./dist
+COPY ./config/appsettings.json ./
+EXPOSE 8080
+ENTRYPOINT [ "npgsqlrest", "./appsettings.json" ]

The production image:

Deploy to Kubernetes via Helm. Codegen never runs in the production cluster — it's a build-time concern, fully separated from the runtime concerns of serving requests.

What This Means in Practice

The author of the project this configuration comes from — profiled in the zero-backend-code case study, which puts the LOC numbers next to an equivalent ASP.NET Core build — describes the experience like this:

"I am free to focus on my database that it works correctly."

That's the loop NpgsqlRest is designed to enable. The database is the artifact you actually care about. The HTTP layer is generated. The TypeScript client is generated. Both come from the same source. When the database is right, everything downstream is right too — and the build catches it the moment it isn't.

Workflow Summary

  1. Write a PostgreSQL function (or SQL file) with a comment annotation describing the endpoint.
  2. Restart NpgsqlRest. Two things happen on the same startup:
  3. Import the generated function in your frontend code. Your build pipeline (Vite, Next.js, tsc, esbuild) compiles it like any other source file.
  4. Change the database schema. Restart. The next run rewrites the .ts file. Your frontend's next compile either matches the new shape (build passes) or doesn't (build fails with a precise error).

There is no DTO file to maintain, no OpenAPI spec to keep in sync, no runtime validation library configured at three layers. The database is the source of truth, the endpoint generator translates it to HTTP at runtime, and the client generator translates it to TypeScript at build time. Both translations come from the same catalog read.

`,105)),h(i,{"get-started":[{text:"Code Generation Configuration",href:"/config/codegen"},{text:"@tsclient Annotation Reference",href:"/annotations/tsclient"},{text:"Static Type Checking Example (SQL files)",href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking_sql_file"},{text:"End-to-End Type Checking Blog",href:"/blog/end-to-end-static-type-checking-postgresql-typescript"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const u=a(p,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.lean.js b/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.lean.js new file mode 100644 index 000000000..785ebac5e --- /dev/null +++ b/assets/blog_typescript-codegen-walkthrough.md.DU5mkobw.lean.js @@ -0,0 +1 @@ +import{_ as a,C as n,c as t,o as e,a5 as l,G as h}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse(`{"title":"From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator","titleTemplate":"NpgsqlRest","description":"Generate typed fetch modules and TypeScript interfaces directly from PostgreSQL functions. End-to-end type safety, per-endpoint control with @tsclient annotations, and real-world module organization.","frontmatter":{"layout":"doc","outline":[2,3],"title":"From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator","titleTemplate":"NpgsqlRest","description":"Generate typed fetch modules and TypeScript interfaces directly from PostgreSQL functions. End-to-end type safety, per-endpoint control with @tsclient annotations, and real-world module organization.","head":[["meta",{"name":"keywords","content":"npgsqlrest typescript codegen, postgresql to typescript, type-safe api client, generate fetch from sql, tsclient annotation, end-to-end type safety, npgsqlrest typescript module"}],["meta",{"property":"og:title","content":"From SQL to Type-Safe TypeScript: NpgsqlRest's Code Generator"}],["meta",{"property":"og:description","content":"Auto-generate fetch modules and TypeScript interfaces from PostgreSQL functions. End-to-end type safety with declarative @tsclient annotations."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"TypeScript Code Generation Walkthrough"}],["meta",{"name":"twitter:description","content":"Auto-generate type-safe fetch clients from PostgreSQL functions with NpgsqlRest."}]]},"headers":[],"relativePath":"blog/typescript-codegen-walkthrough.md","filePath":"blog/typescript-codegen-walkthrough.md"}`),p={name:"blog/typescript-codegen-walkthrough.md"};function k(r,s,d,o,c,g){const i=n("BlogNav");return e(),t("div",null,[s[0]||(s[0]=l("",105)),h(i,{"get-started":[{text:"Code Generation Configuration",href:"/config/codegen"},{text:"@tsclient Annotation Reference",href:"/annotations/tsclient"},{text:"Static Type Checking Example (SQL files)",href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking_sql_file"},{text:"End-to-End Type Checking Blog",href:"/blog/end-to-end-static-type-checking-postgresql-typescript"},{text:"Quick Start Guide",href:"/guide/quick-start"}]})])}const u=a(p,[["render",k]]);export{F as __pageData,u as default}; diff --git a/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.js b/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.js new file mode 100644 index 000000000..6b31f1297 --- /dev/null +++ b/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.js @@ -0,0 +1,131 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse(`{"title":"Web Scraping with PostgreSQL: HTTP Types + XML Functions","titleTemplate":"NpgsqlRest","description":"Scrape a web page entirely in SQL: fetch the HTML with an HTTP Custom Type, parse it with PostgreSQL's built-in XPath functions, and serve the result as JSON. No Python, no scraping service.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Web Scraping with PostgreSQL: HTTP Types + XML Functions","titleTemplate":"NpgsqlRest","description":"Scrape a web page entirely in SQL: fetch the HTML with an HTTP Custom Type, parse it with PostgreSQL's built-in XPath functions, and serve the result as JSON. No Python, no scraping service.","head":[["meta",{"name":"keywords","content":"postgresql web scraping, sql web scraper, postgresql xpath, postgresql xml functions, scrape html sql, npgsqlrest http types, postgresql parse html"}],["meta",{"property":"og:title","content":"Web Scraping with PostgreSQL: HTTP Types + XML Functions"}],["meta",{"property":"og:description","content":"Fetch a page with an HTTP Custom Type, parse the HTML with PostgreSQL XPath, serve JSON. A web scraper with no application code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Web Scraping with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Fetch a page, parse the HTML with XPath, serve JSON — all in SQL."}]]},"headers":[],"relativePath":"blog/web-scraping-postgresql-http-types-xml.md","filePath":"blog/web-scraping-postgresql-http-types-xml.md"}`),t={name:"blog/web-scraping-postgresql-http-types-xml.md"};function l(p,s,h,r,k,c){return n(),a("div",null,s[0]||(s[0]=[e(`

Web Scraping with PostgreSQL: HTTP Types + XML Functions

HTTP · Web Scraping · XML / XPath · June 2026


A web scraper is two things: something that fetches a page, and something that parses the HTML it gets back. The usual stack reaches for Python plus requests plus BeautifulSoup, or a hosted scraping API.

PostgreSQL already has both halves. NpgsqlRest's HTTP Custom Types do the fetch — the request lives in a type comment and is performed automatically before your function runs. And PostgreSQL's built-in xpath() / xmlparse() functions do the parse. Put them together and a scraper endpoint is just a SQL function — no application code, no extra services.

This post builds two of them:

Both are public test sites built for exactly this.

The recipe

Every scraper here follows the same four steps:

  1. Fetch the page with an HTTP Custom Type.
  2. Isolate the repeating blocks (a product card, a book article) with a regex.
  3. Clean the HTML into well-formed XML — drop void tags like <img> that never close.
  4. Parse with xpath() and compute the answer in plain SQL.

Example 17: average book price

Fetch — the HTTP Custom Type

An HTTP Custom Type is a composite type whose comment defines an HTTP request. When a function takes the type as a parameter, NpgsqlRest makes the call and fills in the fields before the function body executes. body is text, not jsonb, because we're getting raw HTML back:

sql
sql
create type example_17.books_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type example_17.books_api is 'GET https://books.toscrape.com/
+Accept: text/html
+@timeout 30s';

Parse — regex to isolate, XPath to read

sql
sql
create function example_17.average_book_price(
+    _response example_17.books_api default null
+)
+returns table (
+    avg_price numeric
+)
+language plpgsql
+as
+$$
+declare
+    _articles text[];
+    _cleaned  text;
+    _doc      xml;
+begin
+    -- Bail out if the external request failed.
+    if not (_response).success then
+        raise exception 'Failed to fetch books page: %',
+            coalesce((_response).error_message, 'HTTP status ' || (_response).status_code);
+    end if;
+
+    -- 1. Isolate each book from the HTML. Each book is an <article>.
+    select array_agg(m[1])
+      into _articles
+    from regexp_matches(
+        (_response).body,
+        '<article class="product_pod">.*?</article>',
+        'gs'
+    ) m;
+
+    if _articles is null then
+        raise exception 'No books found in the response';
+    end if;
+
+    -- 2. Clean the HTML into well-formed XML: drop the void <img> elements.
+    _cleaned := array_to_string(_articles, '');
+    _cleaned := regexp_replace(_cleaned, '<img[^>]*>', '', 'g');
+
+    _doc := xmlparse(document '<books>' || _cleaned || '</books>');
+
+    -- 3. Read every price with XPath and average them.
+    --    The price is the text of <p class="price_color">, e.g. "£51.77".
+    return query
+    select round(avg(substring(p::text from '([0-9]+(?:\\.[0-9]+)?)')::numeric), 2)
+    from unnest(xpath('//p[@class="price_color"]/text()', _doc)) as p;
+end;
+$$;
+
+comment on function example_17.average_book_price(example_17.books_api) is 'HTTP GET /average-book-price
+@allow_anonymous
+@single';

The @single annotation makes the endpoint return one JSON object instead of an array — { "avgPrice": 35.07 } — and that's the whole backend. GET /average-book-price fetches the page, parses it, and answers.

Why regex and XPath?

PostgreSQL's xmlparse(document ...) wants well-formed XML, and real HTML isn't — <img>, <meta>, and bare boolean attributes have no XML equivalent. The two-step approach sidesteps that: a permissive regex carves out just the repeating blocks we care about, then a couple of regexp_replace calls strip the tags that would break the parser. Once the fragment is clean, xpath() does the precise, structured reading — far more robust than trying to pull every field out with regex alone.

Example 16: best-value laptop

Same recipe, one step further — instead of averaging a single column, it reads three fields per product and ranks them. The card is isolated, the void tags and a bare itemscope attribute are normalized, and then XPath pulls title, price, and rating:

sql
sql
with raw as (
+    select
+        (xpath('.//a[@class="title"]/@title', node))[1]::text as p_title,
+        replace(substring(
+            (xpath('.//h4[contains(@class,"price")]//span[@itemprop="price"]/text()', node))[1]::text
+            from '(\\$[0-9]+(?:\\.[0-9]+)?)'), '$', '')::numeric as p_price,
+        (xpath('.//p[@data-rating]/@data-rating', node))[1]::text::numeric as p_rating
+    from unnest(xpath('/products/div', _doc)) as node
+),
+bounds as (
+    select min(p_price) as min_price, max(p_price) as max_price from raw
+)
+select
+    r.p_title,
+    r.p_price,
+    r.p_rating,
+    round(
+        0.7 * (1 - case when b.max_price = b.min_price then 0
+                        else (r.p_price - b.min_price) / (b.max_price - b.min_price) end)
+        + 0.3 * (r.p_rating / 5),
+    6)
+from raw r cross join bounds b
+order by 4 desc
+limit 1;

The score is 0.7 * (1 - normalizedPrice) + 0.3 * (rating / 5) — cheaper (70%) and higher-rated (30%) wins. The point isn't the formula; it's that once the data is in relational form, ranking, weighting, and aggregating are just SQL. No mapping HTML into objects in another language first.

Be a good citizen: cache the page

Scraped pages change slowly, but a naive endpoint hits the upstream on every request. As of NpgsqlRest 3.18.0, the @cache directive caches the outbound response and reuses it for matching requests within a TTL:

sql
sql
comment on type example_17.books_api is '@cache 5m
+GET https://books.toscrape.com/
+Accept: text/html
+@timeout 30s';

Now a burst of traffic to /average-book-price collapses to one upstream fetch every 5 minutes (with stampede protection coalescing concurrent requests into a single call), instead of one fetch per visitor. Caching is opt-in, GET-only, and stores only successful responses — exactly the right default for scraping.

A different split: fetch in SQL, parse in a service

Parsing in SQL is the point of this post, but it isn't the only shape. Sometimes the parser already exists — a service that turns HTML into the numbers you need — and you only want PostgreSQL to do the fetch. NpgsqlRest can do exactly that: let the HTTP Custom Type fetch the page, then @proxy the scraped HTML straight to that upstream service. The function body stays empty — it's a passthrough.

The catch is where the HTML goes. Automatic (server-filled) parameters are forwarded to the proxy, but an entire HTML page is far too large for the query string — it produces a request line the upstream rejects (HTTP 414/431). The fix is to route that one field into the proxy request body with @body_parameter_name, and use a body-carrying method (POST):

sql
sql
create type example_18.books_api as (
+    body text,
+    status_code int,
+    success boolean,
+    error_message text
+);
+
+comment on type example_18.books_api is 'GET https://books.toscrape.com/
+Accept: text/html
+@timeout 30s';
+
+create function example_18.average_book_price(
+    _response example_18.books_api default null
+)
+returns table (
+    avg_price numeric
+)
+language plpgsql
+as
+$$
+begin
+-- empty, proxy passthrough: no DB called at all
+end;
+$$;
+
+comment on function example_18.average_book_price(example_18.books_api) is '
+HTTP POST /average-book-price
+@body_parameter_name _response_body
+@allow_anonymous
+@single
+@proxy
+';

@body_parameter_name _response_body targets the expanded body field of the HTTP Custom Type — the scraped HTML — and sends it as the raw POST body to the upstream. The small fields (status_code, success, …) ride along on the query string. The upstream then does the parsing it already knows how to do and answers with the average:

ts
ts
// upstream/server.ts — receives the scraped HTML in the POST body
+const html = await req.text();
+const prices = [...html.matchAll(/class="price_color">\\s*£([\\d.]+)/g)]
+    .map(m => Number(m[1]))
+    .filter(n => !Number.isNaN(n));
+const avgPrice = prices.length
+    ? prices.reduce((a, b) => a + b, 0) / prices.length
+    : null;
+return Response.json({ avgPrice });

Two NpgsqlRest 3.18.2 options make this clean: ProxyOptions.MaxForwardedQueryParamLength is the guard that would have skipped the oversized body from the query string in the first place, and OmitAutomaticParameters drops the server-filled fields from the generated client so the call is a bare averageBookPrice() with no arguments.

When this works (and when it doesn't)

This approach shines on server-rendered, reasonably structured HTML — product listings, catalogs, tables, RSS-like pages. It's a few lines of SQL and it deploys with the rest of your database.

It is not a headless browser. Pages that build their content with client-side JavaScript return an empty shell to an HTTP fetch — there's no DOM to render and nothing for XPath to read. For those you still need a real browser engine. But for the large class of pages that ship their data in the HTML, PostgreSQL plus an HTTP Custom Type is all the scraper you need.

Try it

Both examples are runnable end to end:

Source: examples/16_scrap_demo · examples/17_scrap_demo_2 · examples/18_scrap_proxy_demo

bash
bash
cd examples/17_scrap_demo_2
+bun run db:up
+bun run dev
+# open http://127.0.0.1:8080

The proxy variant (example 18) runs the same way, plus its upstream service in a second terminal:

bash
bash
cd examples/18_scrap_proxy_demo
+bun run db:up
+bun run upstream   # starts the parsing service on :3001
+bun run dev        # in another terminal
+# open http://127.0.0.1:8080
`,46)]))}const g=i(t,[["render",l]]);export{d as __pageData,g as default}; diff --git a/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.lean.js b/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.lean.js new file mode 100644 index 000000000..64a92e679 --- /dev/null +++ b/assets/blog_web-scraping-postgresql-http-types-xml.md.CCovU_63.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse(`{"title":"Web Scraping with PostgreSQL: HTTP Types + XML Functions","titleTemplate":"NpgsqlRest","description":"Scrape a web page entirely in SQL: fetch the HTML with an HTTP Custom Type, parse it with PostgreSQL's built-in XPath functions, and serve the result as JSON. No Python, no scraping service.","frontmatter":{"layout":"doc","outline":[2,3],"title":"Web Scraping with PostgreSQL: HTTP Types + XML Functions","titleTemplate":"NpgsqlRest","description":"Scrape a web page entirely in SQL: fetch the HTML with an HTTP Custom Type, parse it with PostgreSQL's built-in XPath functions, and serve the result as JSON. No Python, no scraping service.","head":[["meta",{"name":"keywords","content":"postgresql web scraping, sql web scraper, postgresql xpath, postgresql xml functions, scrape html sql, npgsqlrest http types, postgresql parse html"}],["meta",{"property":"og:title","content":"Web Scraping with PostgreSQL: HTTP Types + XML Functions"}],["meta",{"property":"og:description","content":"Fetch a page with an HTTP Custom Type, parse the HTML with PostgreSQL XPath, serve JSON. A web scraper with no application code."}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"Web Scraping with PostgreSQL"}],["meta",{"name":"twitter:description","content":"Fetch a page, parse the HTML with XPath, serve JSON — all in SQL."}]]},"headers":[],"relativePath":"blog/web-scraping-postgresql-http-types-xml.md","filePath":"blog/web-scraping-postgresql-http-types-xml.md"}`),t={name:"blog/web-scraping-postgresql-http-types-xml.md"};function l(p,s,h,r,k,c){return n(),a("div",null,s[0]||(s[0]=[e("",46)]))}const g=i(t,[["render",l]]);export{d as __pageData,g as default}; diff --git a/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.js b/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.js new file mode 100644 index 000000000..df42d9682 --- /dev/null +++ b/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.js @@ -0,0 +1,12 @@ +import{_ as t,C as a,c as i,o as n,a5 as o,G as r}from"./chunks/framework.CgT1UzWm.js";const p="/sp1.jpeg",l="/polp/code1.png",d="/polp/code2.png",h="/polp/code3.png",c="/polp/code5.png",u="/polp/code4.png",k="/sp2.jpeg",m="/sp5.jpeg",C=JSON.parse('{"title":"What Have PostgreSQL Functions Ever Done for Us?","titleTemplate":"NpgsqlRest","description":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?","frontmatter":{"layout":"doc","outline":[2,3],"title":"What Have PostgreSQL Functions Ever Done for Us?","titleTemplate":"NpgsqlRest","description":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?","head":[["meta",{"name":"keywords","content":"postgresql stored procedures, postgresql functions, npgsqlrest, database api, sql injection prevention, database security, api development, stored procedure benefits, data contracts"}],["meta",{"property":"og:title","content":"What Have PostgreSQL Functions Ever Done for Us?"}],["meta",{"property":"og:description","content":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?"}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"What Have PostgreSQL Functions Ever Done for Us?"}]]},"headers":[],"relativePath":"blog/what-have-stored-procedures-ever-done-for-us.md","filePath":"blog/what-have-stored-procedures-ever-done-for-us.md"}'),y={name:"blog/what-have-stored-procedures-ever-done-for-us.md"};function g(f,e,b,v,F,D){const s=a("BlogNav");return n(),i("div",null,[e[0]||(e[0]=o('

What Have PostgreSQL Functions Ever Done for Us?

January 2026 · PostgreSQLArchitectureOpinion


Hey, DDD developers. What's stopping you from crafting beautiful, efficient domain models with these? With these obsolete, dusty old PostgreSQL functions and stored procedures?

What are we, stuck in the 90s or something?

What have the stored procedures ever done for us?

Besides — what have user-defined functions and stored procedures ever really done for us?

Let me actually answer that. Not with hand-waving, but with a tiny bookstore I can fit on your screen.

Here's the schema. Three tables: books, orders, a little price history. Standard stuff.

Bookstore schema

These tables? Private parts. Nobody outside the database touches them directly. Ever. Instead, every command and query the application is allowed to run lives in a function, in its own schema:

Application user with minimal privileges and an API schema

That app_user can login. That's pretty much it. nosuperuser, nocreatedb, nocreaterole, noinherit. It gets usage on the bookstore_api schema and nothing else. No tables. No data schema. Just the functions I decide to expose.

To borrow some OOP vocabulary: those functions are my data contracts. Parameters are the input contract, the result set is the output contract. The tables behind them can do whatever they want. The contract doesn't budge unless the application does too.

And if you strap NpgsqlRest on top, every one of those functions becomes a REST endpoint automatically — no controllers, no DTOs, no mappers. Your SQL is the API.

OK. But besides being a tidy mental model — what have they ever done for us?

Type Safety?

Here's the search API. One function, list_books:

Type-safe search function with a test block

create or replace on every build re-validates the whole signature: every column name, every type, every table it touches. Rename a column in books, change price from numeric to int, drop something — and the build screams. No type or name change slips through unnoticed.

And as a bonus: strap NpgsqlRest on top and those same types propagate straight into your frontend as generated TypeScript. You're type-protected on every front, from the table to the React component.

OK — besides Type Safety — what have user-defined functions and stored procedures ever done for us?

Real Encapsulation?

The tables and types are invisible to the application. It can't see them. It can't SELECT * FROM bookstore.books. All it knows is list_books(_search).

Which means I can do whatever I want behind the curtain — split a table, denormalize it, archive half of it to cold storage, add a price_history audit on every update like in update_price here:

Update procedure that writes an audit row, with a test

The application never finds out. The contract didn't move an inch.

OK — besides Type Safety and Real Encapsulation — what have user-defined functions and stored procedures ever done for us?

Zero Downtime?

Same deal. You split, denormalize, archive, rewrite the guts of a function — and not only does the API not move, you do it with zero downtime. create or replace function is atomic. No deploy. No container restart. No rolling anything. The next call just runs the new body.

OK — besides Type Safety, Real Encapsulation, and Zero Downtime — what have user-defined functions and stored procedures ever done for us?

Performance?

Look at place_order:

Order function doing stock check, update, and insert in one trip

Check the stock, validate it, decrement it, insert the order — all of it in one single round trip to the database and back. From the application's point of view it's one call. The unnecessary network chatter — the read, then the read, then the write, then the other write — is reduced to zero.

OK — besides Type Safety, Real Encapsulation, Zero Downtime, and Performance — what have user-defined functions and stored procedures ever done for us?

Race Conditions Minimized?

Same place_order again. Fewer round trips means less time spent on the wire, which means latency drops, which means the window where two orders can fight over the last copy of Ficciones shrinks dramatically. Add the select ... for update and that window basically closes. Try doing that cleanly across four separate ORM calls.

OK — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, and Race Conditions Minimized — what have user-defined functions and stored procedures ever done for us?

Security?

Apart from security, what have stored procedures ever done for us?

Your ORM is still connecting with a God connection — a role that can do anything and everything to every table. We don't. We use the principle of least privilege, the same way the army does. They call it "need-to-know." If you're captured, you can't give up what you were never told.

That's exactly what app_user is. It can call functions in one schema. It cannot read a table, cannot drop one, cannot escalate. So even if those credentials leak — out of the codebase, out of the infra, out of some forgotten .env in a public repo — the attacker gets to call the same endpoints your frontend already calls. They don't get to dump your data. The God connection credentials never went anywhere a thief could reach.

Alright, alright — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, Race Conditions Minimized, and Security — what have user-defined functions and stored procedures ever done for us?

A Short Test Loop?

Look at the bottom of every one of those screenshots. See that do block?

sql
sql
do
+$$
+begin
+    insert into bookstore.books (title, author, price) values
+        ('Ficciones', 'Jorge Luis Borges', 12.99),
+        ('The Aleph', 'Jorge Luis Borges', 14.50);
+    assert (select count(*) from bookstore_api.list_books()) = 2;
+    assert (select count(*) from bookstore_api.list_books(_search => 'borges')) = 2;
+    raise notice 'list_books: ok';
+    rollback;
+end;
+$$;

Seed. Assert. Rollback. It lives at the end of the same SQL file as the function. I re-run the file in psql — the function gets create or replace'd, the test runs, I get list_books: ok, and the transaction rolls back so the database is exactly as it was.

Milliseconds. No build. No test runner. No mocked database. No spinning up half of Docker to find out my WHERE clause was wrong.

Oh God, oh man — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, Race Conditions Minimized, Security, and a Short Test Loop — what have user-defined functions and stored procedures ever done for us?

Apart from security, performance, maintainability, and availability, what have stored procedures ever done for us?

...

Yeah. I really don't know either.

So, DDD developers

If you actually want to learn these ancient, obsolete, stuck-in-the-90s techniques — type-safe contracts, real encapsulation, least-privilege security, a test loop measured in milliseconds — I'll be running free workshops (and consulting) for NpgsqlRest users after this summer.

Drop a comment if you're interested.

And if you'd rather just start: your SQL is already the API, you just haven't pointed NpgsqlRest at it yet.

',57)),r(s,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Benchmark Results",href:"/blog/postgresql-rest-api-benchmark-2026"},{text:"Security Guide",href:"/guide/security"},{text:"Function Annotations",href:"/annotations/"}]})])}const A=t(y,[["render",g]]);export{C as __pageData,A as default}; diff --git a/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.lean.js b/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.lean.js new file mode 100644 index 000000000..3b1bd84ec --- /dev/null +++ b/assets/blog_what-have-stored-procedures-ever-done-for-us.md.DbQNwRp9.lean.js @@ -0,0 +1 @@ +import{_ as t,C as a,c as i,o as n,a5 as o,G as r}from"./chunks/framework.CgT1UzWm.js";const p="/sp1.jpeg",l="/polp/code1.png",d="/polp/code2.png",h="/polp/code3.png",c="/polp/code5.png",u="/polp/code4.png",k="/sp2.jpeg",m="/sp5.jpeg",C=JSON.parse('{"title":"What Have PostgreSQL Functions Ever Done for Us?","titleTemplate":"NpgsqlRest","description":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?","frontmatter":{"layout":"doc","outline":[2,3],"title":"What Have PostgreSQL Functions Ever Done for Us?","titleTemplate":"NpgsqlRest","description":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?","head":[["meta",{"name":"keywords","content":"postgresql stored procedures, postgresql functions, npgsqlrest, database api, sql injection prevention, database security, api development, stored procedure benefits, data contracts"}],["meta",{"property":"og:title","content":"What Have PostgreSQL Functions Ever Done for Us?"}],["meta",{"property":"og:description","content":"Apart from security, performance, maintainability, testability, and zero boilerplate code, what have PostgreSQL stored procedures and functions ever really done for us?"}],["meta",{"property":"og:type","content":"article"}],["meta",{"name":"twitter:card","content":"summary_large_image"}],["meta",{"name":"twitter:title","content":"What Have PostgreSQL Functions Ever Done for Us?"}]]},"headers":[],"relativePath":"blog/what-have-stored-procedures-ever-done-for-us.md","filePath":"blog/what-have-stored-procedures-ever-done-for-us.md"}'),y={name:"blog/what-have-stored-procedures-ever-done-for-us.md"};function g(f,e,b,v,F,D){const s=a("BlogNav");return n(),i("div",null,[e[0]||(e[0]=o("",57)),r(s,{"get-started":[{text:"Quick Start Guide",href:"/guide/quick-start"},{text:"Benchmark Results",href:"/blog/postgresql-rest-api-benchmark-2026"},{text:"Security Guide",href:"/guide/security"},{text:"Function Annotations",href:"/annotations/"}]})])}const A=t(y,[["render",g]]);export{C as __pageData,A as default}; diff --git a/assets/chunks/@localSearchIndexroot.CFuD74JD.js b/assets/chunks/@localSearchIndexroot.CFuD74JD.js new file mode 100644 index 000000000..ceee093a9 --- /dev/null +++ b/assets/chunks/@localSearchIndexroot.CFuD74JD.js @@ -0,0 +1 @@ +const e='{"documentCount":2883,"nextId":2883,"documentIds":{"0":"/about.html#about-this-website","1":"/about.html#how-these-docs-are-made","2":"/about.html#about-the-author","3":"/about.html#feedback","4":"/annotations/allow-anonymous.html#allow-anonymous","5":"/annotations/allow-anonymous.html#syntax","6":"/annotations/allow-anonymous.html#examples","7":"/annotations/allow-anonymous.html#public-endpoint","8":"/annotations/allow-anonymous.html#short-form","9":"/annotations/allow-anonymous.html#public-read-protected-write-pattern","10":"/annotations/allow-anonymous.html#behavior","11":"/annotations/allow-anonymous.html#related","12":"/annotations/allow-anonymous.html#related-annotations","13":"/annotations/authorize.html#authorize","14":"/annotations/authorize.html#syntax","15":"/annotations/authorize.html#examples","16":"/annotations/authorize.html#require-any-authenticated-user","17":"/annotations/authorize.html#alternative-keywords","18":"/annotations/authorize.html#require-specific-role","19":"/annotations/authorize.html#authorize-by-user-name","20":"/annotations/authorize.html#authorize-by-user-id","21":"/annotations/authorize.html#multiple-roles","22":"/annotations/authorize.html#mix-of-roles-and-user-identifiers","23":"/annotations/authorize.html#authorize-before-http","24":"/annotations/authorize.html#authorize-on-separate-line","25":"/annotations/authorize.html#behavior","26":"/annotations/authorize.html#related","27":"/annotations/authorize.html#related-annotations","28":"/annotations/authorize.html#see-also","29":"/annotations/basic-auth-command.html#basic-auth-command","30":"/annotations/basic-auth-command.html#syntax","31":"/annotations/basic-auth-command.html#command-parameters","32":"/annotations/basic-auth-command.html#return-value","33":"/annotations/basic-auth-command.html#special-columns","34":"/annotations/basic-auth-command.html#authentication-success","35":"/annotations/basic-auth-command.html#authentication-failure","36":"/annotations/basic-auth-command.html#examples","37":"/annotations/basic-auth-command.html#basic-challenge-command","38":"/annotations/basic-auth-command.html#challenge-command-with-pre-validated-password","39":"/annotations/basic-auth-command.html#challenge-command-that-denies-access","40":"/annotations/basic-auth-command.html#challenge-command-without-annotation-credentials","41":"/annotations/basic-auth-command.html#behavior","42":"/annotations/basic-auth-command.html#related","43":"/annotations/basic-auth-command.html#related-annotations","44":"/annotations/basic-auth-realm.html#basic-auth-realm","45":"/annotations/basic-auth-realm.html#syntax","46":"/annotations/basic-auth-realm.html#default-value","47":"/annotations/basic-auth-realm.html#examples","48":"/annotations/basic-auth-realm.html#set-realm-name","49":"/annotations/basic-auth-realm.html#alternative-keyword","50":"/annotations/basic-auth-realm.html#with-challenge-command","51":"/annotations/basic-auth-realm.html#behavior","52":"/annotations/basic-auth-realm.html#realm-resolution-order","53":"/annotations/basic-auth-realm.html#related","54":"/annotations/basic-auth-realm.html#related-annotations","55":"/annotations/basic-auth.html#basic-auth","56":"/annotations/basic-auth.html#syntax","57":"/annotations/basic-auth.html#generating-password-hashes","58":"/annotations/basic-auth.html#generating-authorization-headers","59":"/annotations/basic-auth.html#examples","60":"/annotations/basic-auth.html#basic-auth-without-credentials-requires-challenge-command","61":"/annotations/basic-auth.html#basic-auth-with-credentials","62":"/annotations/basic-auth.html#multiple-users","63":"/annotations/basic-auth.html#behavior","64":"/annotations/basic-auth.html#ssl-requirements","65":"/annotations/basic-auth.html#related","66":"/annotations/basic-auth.html#related-annotations","67":"/annotations/basic-auth.html#see-also","68":"/annotations/body-parameter-name.html#body-parameter-name","69":"/annotations/body-parameter-name.html#syntax","70":"/annotations/body-parameter-name.html#examples","71":"/annotations/body-parameter-name.html#custom-body-parameter","72":"/annotations/body-parameter-name.html#json-body-parameter","73":"/annotations/body-parameter-name.html#behavior","74":"/annotations/body-parameter-name.html#matching-rules","75":"/annotations/body-parameter-name.html#redirecting-an-http-custom-type-field-into-a-proxy-body","76":"/annotations/body-parameter-name.html#related","77":"/annotations/body-parameter-name.html#related-annotations","78":"/annotations/buffer-rows.html#buffer-rows","79":"/annotations/buffer-rows.html#syntax","80":"/annotations/buffer-rows.html#default-value","81":"/annotations/buffer-rows.html#special-values","82":"/annotations/buffer-rows.html#examples","83":"/annotations/buffer-rows.html#disable-buffering","84":"/annotations/buffer-rows.html#buffer-entire-response","85":"/annotations/buffer-rows.html#large-buffer-for-throughput","86":"/annotations/buffer-rows.html#small-buffer-for-memory-efficiency","87":"/annotations/buffer-rows.html#behavior","88":"/annotations/buffer-rows.html#performance-considerations","89":"/annotations/buffer-rows.html#related","90":"/annotations/buffer-rows.html#related-annotations","91":"/annotations/cache-expires-in.html#cache-expires-in","92":"/annotations/cache-expires-in.html#syntax","93":"/annotations/cache-expires-in.html#examples","94":"/annotations/cache-expires-in.html#short-cache-10-seconds","95":"/annotations/cache-expires-in.html#medium-cache-5-minutes","96":"/annotations/cache-expires-in.html#long-cache-1-hour","97":"/annotations/cache-expires-in.html#daily-cache","98":"/annotations/cache-expires-in.html#related","99":"/annotations/cache-expires-in.html#related-annotations","100":"/annotations/cache-expires-in.html#see-also","101":"/annotations/cache-profile.html#cache-profile","102":"/annotations/cache-profile.html#syntax","103":"/annotations/cache-profile.html#examples","104":"/annotations/cache-profile.html#basic-usage","105":"/annotations/cache-profile.html#combined-with-cached-cache-expires","106":"/annotations/cache-profile.html#multi-tenant-search-path-pattern","107":"/annotations/cache-profile.html#tiered-ttl-by-user-role","108":"/annotations/cache-profile.html#behavior","109":"/annotations/cache-profile.html#validation","110":"/annotations/cache-profile.html#related","111":"/annotations/cache-profile.html#see-also","112":"/annotations/cached.html#cached","113":"/annotations/cached.html#syntax","114":"/annotations/cached.html#examples","115":"/annotations/cached.html#simple-caching","116":"/annotations/cached.html#cache-key-by-parameter","117":"/annotations/cached.html#multiple-cache-key-parameters","118":"/annotations/cached.html#with-cache-expiration","119":"/annotations/cached.html#caching-set-returning-functions","120":"/annotations/cached.html#behavior","121":"/annotations/cached.html#cache-configuration","122":"/annotations/cached.html#related","123":"/annotations/cached.html#related-annotations","124":"/annotations/cached.html#see-also","125":"/annotations/column-names.html#column-names","126":"/annotations/column-names.html#syntax","127":"/annotations/column-names.html#examples","128":"/annotations/column-names.html#csv-with-headers","129":"/annotations/column-names.html#tsv-with-headers","130":"/annotations/column-names.html#related","131":"/annotations/column-names.html#related-annotations","132":"/annotations/command-timeout.html#command-timeout","133":"/annotations/command-timeout.html#syntax","134":"/annotations/command-timeout.html#default-value","135":"/annotations/command-timeout.html#examples","136":"/annotations/command-timeout.html#short-timeout","137":"/annotations/command-timeout.html#long-running-query","138":"/annotations/command-timeout.html#using-seconds-format","139":"/annotations/command-timeout.html#behavior","140":"/annotations/command-timeout.html#timeout-response","141":"/annotations/command-timeout.html#related","142":"/annotations/command-timeout.html#related-annotations","143":"/annotations/command-timeout.html#see-also","144":"/annotations/connection.html#connection","145":"/annotations/connection.html#syntax","146":"/annotations/connection.html#examples","147":"/annotations/connection.html#use-named-connection","148":"/annotations/connection.html#reporting-database","149":"/annotations/connection.html#read-replica","150":"/annotations/connection.html#behavior","151":"/annotations/connection.html#related","152":"/annotations/connection.html#related-annotations","153":"/annotations/connection.html#see-also","154":"/annotations/custom-parameters.html#custom-parameters","155":"/annotations/custom-parameters.html#syntax","156":"/annotations/custom-parameters.html#dynamic-parameter-values","157":"/annotations/custom-parameters.html#example","158":"/annotations/custom-parameters.html#built-in-parameters","159":"/annotations/custom-parameters.html#general","160":"/annotations/custom-parameters.html#upload","161":"/annotations/custom-parameters.html#table-format","162":"/annotations/custom-parameters.html#server-sent-events","163":"/annotations/custom-parameters.html#typescript-client","164":"/annotations/custom-parameters.html#related","165":"/annotations/define-param.html#define-param","166":"/annotations/define-param.html#syntax","167":"/annotations/define-param.html#custom-parameter-placeholders","168":"/annotations/define-param.html#claim-mapping","169":"/annotations/define-param.html#default-type","170":"/annotations/define-param.html#related","171":"/annotations/disabled.html#disabled","172":"/annotations/disabled.html#keywords","173":"/annotations/disabled.html#syntax","174":"/annotations/disabled.html#example","175":"/annotations/disabled.html#tag-conditional-form","176":"/annotations/disabled.html#related","177":"/annotations/enabled.html#enabled","178":"/annotations/enabled.html#keywords","179":"/annotations/enabled.html#syntax","180":"/annotations/enabled.html#example-disable-by-default-enable-for-immutable-only","181":"/annotations/enabled.html#related","182":"/annotations/encrypt-decrypt.html#encrypt-decrypt","183":"/annotations/encrypt-decrypt.html#encrypt-parameters","184":"/annotations/encrypt-decrypt.html#syntax","185":"/annotations/encrypt-decrypt.html#decrypt-result-columns","186":"/annotations/encrypt-decrypt.html#syntax-1","187":"/annotations/encrypt-decrypt.html#full-roundtrip-example","188":"/annotations/encrypt-decrypt.html#behavior","189":"/annotations/encrypt-decrypt.html#related","190":"/annotations/encrypt-decrypt.html#related-annotations","191":"/annotations/encrypt-decrypt.html#see-also","192":"/annotations/error-code-policy.html#error-code-policy","193":"/annotations/error-code-policy.html#syntax","194":"/annotations/error-code-policy.html#examples","195":"/annotations/error-code-policy.html#named-policy","196":"/annotations/error-code-policy.html#short-form","197":"/annotations/error-code-policy.html#behavior","198":"/annotations/error-code-policy.html#related","199":"/annotations/error-code-policy.html#related-annotations","200":"/annotations/error-code-policy.html#see-also","201":"/annotations/http-type.html#http-custom-types","202":"/annotations/http-type.html#overview","203":"/annotations/http-type.html#syntax","204":"/annotations/http-type.html#supported-methods","205":"/annotations/http-type.html#examples","206":"/annotations/http-type.html#basic-get-request","207":"/annotations/http-type.html#get-with-headers-and-placeholders","208":"/annotations/http-type.html#post-with-request-body","209":"/annotations/http-type.html#multiple-api-calls","210":"/annotations/http-type.html#response-fields","211":"/annotations/http-type.html#timeout-directives","212":"/annotations/http-type.html#placeholder-substitution","213":"/annotations/http-type.html#retry-logic","214":"/annotations/http-type.html#response-caching","215":"/annotations/http-type.html#resolved-parameter-expressions","216":"/annotations/http-type.html#behavior","217":"/annotations/http-type.html#related","218":"/annotations/http-type.html#related-annotations","219":"/annotations/http-type.html#see-also","220":"/annotations/#annotations-reference","221":"/annotations/#how-to-use-this-reference","222":"/annotations/#annotation-categories","223":"/annotations/#http-routing","224":"/annotations/#authorization","225":"/annotations/#basic-authentication","226":"/annotations/#request-configuration","227":"/annotations/#response-configuration","228":"/annotations/#table-format-output","229":"/annotations/#raw-output-mode","230":"/annotations/#caching","231":"/annotations/#performance","232":"/annotations/#format-references","233":"/annotations/#server-sent-events","234":"/annotations/#upload","235":"/annotations/#policies","236":"/annotations/#context-security","237":"/annotations/#parameter-annotations","238":"/annotations/#sql-file-annotations","239":"/annotations/#test-file-annotations","240":"/annotations/#custom","241":"/annotations/http.html#http","242":"/annotations/http.html#keywords","243":"/annotations/http.html#syntax","244":"/annotations/http.html#commentsmode-requirement","245":"/annotations/http.html#default-behavior","246":"/annotations/http.html#examples","247":"/annotations/http.html#basic-endpoint","248":"/annotations/http.html#explicit-http-method","249":"/annotations/http.html#custom-path","250":"/annotations/http.html#method-and-custom-path","251":"/annotations/http.html#multi-line-with-documentation","252":"/annotations/http.html#unrecognized-method-becomes-path","253":"/annotations/http.html#path-parameters","254":"/annotations/http.html#single-path-parameter","255":"/annotations/http.html#multiple-path-parameters","256":"/annotations/http.html#path-parameters-with-query-string","257":"/annotations/http.html#path-parameters-with-json-body","258":"/annotations/http.html#path-parameter-key-features","259":"/annotations/http.html#related","260":"/annotations/http.html#related-annotations","261":"/annotations/internal.html#internal","262":"/annotations/internal.html#syntax","263":"/annotations/internal.html#example-internal-helper-with-proxy","264":"/annotations/internal.html#example-internal-helper-with-http-client-types","265":"/annotations/internal.html#sql-file-endpoints","266":"/annotations/internal.html#related","267":"/annotations/interval-format.html#interval-format-reference","268":"/annotations/interval-format.html#syntax","269":"/annotations/interval-format.html#supported-units","270":"/annotations/interval-format.html#examples","271":"/annotations/interval-format.html#short-form-recommended","272":"/annotations/interval-format.html#long-form","273":"/annotations/interval-format.html#with-space","274":"/annotations/interval-format.html#decimal-values","275":"/annotations/interval-format.html#no-unit-defaults-to-seconds","276":"/annotations/interval-format.html#usage-in-annotations","277":"/annotations/interval-format.html#timeout-command-timeout","278":"/annotations/interval-format.html#cache-expires-in","279":"/annotations/interval-format.html#configuration-values","280":"/annotations/interval-format.html#invalid-formats","281":"/annotations/interval-format.html#related","282":"/annotations/logout.html#logout","283":"/annotations/logout.html#syntax","284":"/annotations/logout.html#logout-endpoint-behavior","285":"/annotations/logout.html#void-functions","286":"/annotations/logout.html#functions-with-return-values","287":"/annotations/logout.html#examples","288":"/annotations/logout.html#basic-logout-void","289":"/annotations/logout.html#logout-from-specific-scheme","290":"/annotations/logout.html#logout-from-multiple-schemes","291":"/annotations/logout.html#conditional-scheme-logout","292":"/annotations/logout.html#logout-with-cleanup","293":"/annotations/logout.html#related","294":"/annotations/logout.html#related-annotations","295":"/annotations/logout.html#see-also","296":"/annotations/login.html#login","297":"/annotations/login.html#how-a-login-endpoint-works","298":"/annotations/login.html#minimal-example","299":"/annotations/login.html#the-return-record","300":"/annotations/login.html#special-columns","301":"/annotations/login.html#status-column","302":"/annotations/login.html#scheme-column","303":"/annotations/login.html#body-column","304":"/annotations/login.html#claims-how-columns-become-the-user","305":"/annotations/login.html#identity-claims","306":"/annotations/login.html#using-claims-in-your-other-endpoints","307":"/annotations/login.html#password-verification","308":"/annotations/login.html#option-a-—-verify-in-sql-no-hash-column","309":"/annotations/login.html#option-b-—-built-in-hasher-return-a-hash-column","310":"/annotations/login.html#verification-callbacks","311":"/annotations/login.html#more-examples","312":"/annotations/login.html#multiple-schemes-from-one-login","313":"/annotations/login.html#explicit-status-code-and-message","314":"/annotations/login.html#role-protected-endpoint-after-login","315":"/annotations/login.html#related","316":"/annotations/login.html#related-annotations","317":"/annotations/mcp.html#mcp","318":"/annotations/mcp.html#syntax","319":"/annotations/mcp.html#description-precedence","320":"/annotations/mcp.html#mcp-only-tools-no-http-route","321":"/annotations/mcp.html#examples","322":"/annotations/mcp.html#expose-a-routine-as-a-tool-http-and-mcp","323":"/annotations/mcp.html#description-from-comment-prose","324":"/annotations/mcp.html#explicit-description-with-a-private-note-that-stays-out-of-it","325":"/annotations/mcp.html#override-the-tool-name","326":"/annotations/mcp.html#recognized-keywords","327":"/annotations/mcp.html#related","328":"/annotations/nested.html#nested","329":"/annotations/nested.html#syntax","330":"/annotations/nested.html#default-behavior-vs-nested","331":"/annotations/nested.html#examples","332":"/annotations/nested.html#basic-usage","333":"/annotations/nested.html#multiple-composite-columns","334":"/annotations/nested.html#deep-nested-composite-types","335":"/annotations/nested.html#arrays-of-composite-types","336":"/annotations/nested.html#global-configuration","337":"/annotations/nested.html#behavior","338":"/annotations/nested.html#related","339":"/annotations/new-line.html#new-line","340":"/annotations/new-line.html#syntax","341":"/annotations/new-line.html#examples","342":"/annotations/new-line.html#unix-line-endings","343":"/annotations/new-line.html#windows-line-endings","344":"/annotations/new-line.html#custom-row-separator","345":"/annotations/new-line.html#related","346":"/annotations/new-line.html#related-annotations","347":"/annotations/openapi.html#openapi","348":"/annotations/openapi.html#syntax","349":"/annotations/openapi.html#how-it-composes-with-config-level-filters","350":"/annotations/openapi.html#examples","351":"/annotations/openapi.html#hide-an-internal-maintenance-routine","352":"/annotations/openapi.html#group-routines-under-a-custom-tag","353":"/annotations/openapi.html#multiple-tags","354":"/annotations/openapi.html#hide-alongside-a-config-filter","355":"/annotations/openapi.html#recognized-keywords","356":"/annotations/openapi.html#related","357":"/annotations/parameter-hash.html#parameter-hash","358":"/annotations/parameter-hash.html#syntax","359":"/annotations/parameter-hash.html#examples","360":"/annotations/parameter-hash.html#simple-user-registration","361":"/annotations/parameter-hash.html#user-registration-with-response","362":"/annotations/parameter-hash.html#behavior","363":"/annotations/parameter-hash.html#built-in-password-hasher","364":"/annotations/parameter-hash.html#complete-registration-and-login-flow","365":"/annotations/parameter-hash.html#registration-function","366":"/annotations/parameter-hash.html#login-function","367":"/annotations/parameter-hash.html#related","368":"/annotations/parameter-hash.html#related-annotations","369":"/annotations/param.html#param","370":"/annotations/param.html#syntax","371":"/annotations/param.html#examples","372":"/annotations/param.html#rename-positional-parameters-sql-files","373":"/annotations/param.html#rename-with-type-override","374":"/annotations/param.html#rename-function-parameters","375":"/annotations/param.html#is-style-syntax","376":"/annotations/param.html#claim-mapping-with-renamed-parameters","377":"/annotations/param.html#default-values-sql-file-parameters","378":"/annotations/param.html#syntax-1","379":"/annotations/param.html#value-parsing-rules-sql-conventions","380":"/annotations/param.html#example","381":"/annotations/param.html#effects-on-generated-output","382":"/annotations/param.html#rename-validation","383":"/annotations/param.html#composite-type-parameters-sql-files","384":"/annotations/param.html#behavior","385":"/annotations/param.html#related","386":"/annotations/parameter-substitution.html#parameter-value-substitution","387":"/annotations/parameter-substitution.html#where-it-works","388":"/annotations/parameter-substitution.html#how-a-placeholder-is-resolved","389":"/annotations/parameter-substitution.html#brace-handling","390":"/annotations/parameter-substitution.html#environment-variables","391":"/annotations/parameter-substitution.html#examples","392":"/annotations/parameter-substitution.html#dynamic-file-download","393":"/annotations/parameter-substitution.html#upload-destination-from-a-parameter","394":"/annotations/parameter-substitution.html#outbound-http-call-shaped-by-parameters","395":"/annotations/parameter-substitution.html#not-to-be-confused-with","396":"/annotations/parameter-substitution.html#related","397":"/annotations/path.html#path","398":"/annotations/path.html#keywords","399":"/annotations/path.html#syntax","400":"/annotations/path.html#examples","401":"/annotations/path.html#custom-path","402":"/annotations/path.html#path-with-http-method","403":"/annotations/path.html#versioned-api","404":"/annotations/path.html#path-parameters","405":"/annotations/path.html#basic-path-parameter","406":"/annotations/path.html#nested-path-parameters","407":"/annotations/path.html#parameter-name-matching","408":"/annotations/path.html#optional-path-parameters","409":"/annotations/path.html#behavior","410":"/annotations/path.html#related","411":"/annotations/path.html#related-annotations","412":"/annotations/proxy-out.html#proxy-out","413":"/annotations/proxy-out.html#syntax","414":"/annotations/proxy-out.html#description","415":"/annotations/proxy-out.html#basic-usage","416":"/annotations/proxy-out.html#proxy-annotations","417":"/annotations/proxy-out.html#basic-proxy-out-with-default-host","418":"/annotations/proxy-out.html#proxy-out-with-custom-host","419":"/annotations/proxy-out.html#proxy-out-with-http-method-override","420":"/annotations/proxy-out.html#combined-method-and-host","421":"/annotations/proxy-out.html#self-referencing-proxy-out-relative-path","422":"/annotations/proxy-out.html#url-resolution","423":"/annotations/proxy-out.html#path-and-query-string-forwarding","424":"/annotations/proxy-out.html#error-handling","425":"/annotations/proxy-out.html#examples","426":"/annotations/proxy-out.html#pdf-rendering-pipeline","427":"/annotations/proxy-out.html#ml-inference","428":"/annotations/proxy-out.html#email-sending","429":"/annotations/proxy-out.html#typescript-client","430":"/annotations/proxy-out.html#configuration","431":"/annotations/proxy-out.html#related","432":"/annotations/proxy-out.html#see-also","433":"/annotations/proxy.html#proxy","434":"/annotations/proxy.html#syntax","435":"/annotations/proxy.html#description","436":"/annotations/proxy.html#how-the-target-url-is-built","437":"/annotations/proxy.html#basic-usage","438":"/annotations/proxy.html#passthrough-mode","439":"/annotations/proxy.html#transform-mode","440":"/annotations/proxy.html#proxy-annotations","441":"/annotations/proxy.html#basic-proxy-with-default-host","442":"/annotations/proxy.html#proxy-with-custom-host","443":"/annotations/proxy.html#proxy-with-custom-http-method","444":"/annotations/proxy.html#combined-method-and-host","445":"/annotations/proxy.html#self-referencing-proxy-relative-path","446":"/annotations/proxy.html#url-resolution","447":"/annotations/proxy.html#response-parameters","448":"/annotations/proxy.html#how-parameters-are-mapped","449":"/annotations/proxy.html#custom-parameter-names","450":"/annotations/proxy.html#examples","451":"/annotations/proxy.html#api-gateway-pattern","452":"/annotations/proxy.html#data-enrichment","453":"/annotations/proxy.html#authenticated-proxy-with-user-context","454":"/annotations/proxy.html#proxy-with-user-parameters","455":"/annotations/proxy.html#configuration","456":"/annotations/proxy.html#related","457":"/annotations/proxy.html#see-also","458":"/annotations/query-string-null-handling.html#query-string-null-handling","459":"/annotations/query-string-null-handling.html#syntax","460":"/annotations/query-string-null-handling.html#values","461":"/annotations/query-string-null-handling.html#behavior-explained","462":"/annotations/query-string-null-handling.html#ignore-mode-default","463":"/annotations/query-string-null-handling.html#emptystring-mode","464":"/annotations/query-string-null-handling.html#nullliteral-mode","465":"/annotations/query-string-null-handling.html#examples","466":"/annotations/query-string-null-handling.html#using-empty-string-for-null","467":"/annotations/query-string-null-handling.html#using-null-string-for-null","468":"/annotations/query-string-null-handling.html#default-behavior-no-special-handling","469":"/annotations/query-string-null-handling.html#path-parameters","470":"/annotations/query-string-null-handling.html#configuration-default","471":"/annotations/query-string-null-handling.html#related","472":"/annotations/query-string-null-handling.html#related-annotations","473":"/annotations/rate-limiter-policy.html#rate-limiter-policy","474":"/annotations/rate-limiter-policy.html#syntax","475":"/annotations/rate-limiter-policy.html#examples","476":"/annotations/rate-limiter-policy.html#fixed-window-policy","477":"/annotations/rate-limiter-policy.html#token-bucket-policy","478":"/annotations/rate-limiter-policy.html#combined-with-authorization","479":"/annotations/rate-limiter-policy.html#per-user-rate-limiting","480":"/annotations/rate-limiter-policy.html#behavior","481":"/annotations/rate-limiter-policy.html#related","482":"/annotations/rate-limiter-policy.html#related-annotations","483":"/annotations/rate-limiter-policy.html#see-also","484":"/annotations/raw.html#raw","485":"/annotations/raw.html#syntax","486":"/annotations/raw.html#examples","487":"/annotations/raw.html#basic-raw-output","488":"/annotations/raw.html#raw-with-multiple-columns","489":"/annotations/raw.html#csv-export","490":"/annotations/raw.html#tab-separated-values","491":"/annotations/raw.html#pipe-delimited-format","492":"/annotations/raw.html#download-as-file","493":"/annotations/raw.html#dynamic-csv-download","494":"/annotations/raw.html#behavior","495":"/annotations/raw.html#related","496":"/annotations/raw.html#related-annotations","497":"/annotations/request-headers-mode.html#request-headers-mode","498":"/annotations/request-headers-mode.html#syntax","499":"/annotations/request-headers-mode.html#values","500":"/annotations/request-headers-mode.html#examples","501":"/annotations/request-headers-mode.html#ignore-headers","502":"/annotations/request-headers-mode.html#pass-as-context-variable","503":"/annotations/request-headers-mode.html#pass-as-parameter","504":"/annotations/request-headers-mode.html#behavior","505":"/annotations/request-headers-mode.html#related","506":"/annotations/request-headers-mode.html#related-annotations","507":"/annotations/request-headers-parameter-name.html#request-headers-parameter-name","508":"/annotations/request-headers-parameter-name.html#syntax","509":"/annotations/request-headers-parameter-name.html#examples","510":"/annotations/request-headers-parameter-name.html#custom-parameter-name","511":"/annotations/request-headers-parameter-name.html#default-parameter-name","512":"/annotations/request-headers-parameter-name.html#behavior","513":"/annotations/request-headers-parameter-name.html#related","514":"/annotations/request-headers-parameter-name.html#related-annotations","515":"/annotations/request-param-type.html#request-param-type","516":"/annotations/request-param-type.html#syntax","517":"/annotations/request-param-type.html#values","518":"/annotations/request-param-type.html#default-behavior","519":"/annotations/request-param-type.html#examples","520":"/annotations/request-param-type.html#force-query-string-parameters","521":"/annotations/request-param-type.html#force-json-body-parameters","522":"/annotations/request-param-type.html#short-form-keywords","523":"/annotations/request-param-type.html#post-with-query-string","524":"/annotations/request-param-type.html#behavior","525":"/annotations/request-param-type.html#related","526":"/annotations/request-param-type.html#related-annotations","527":"/annotations/resolved-parameters.html#resolved-parameters","528":"/annotations/resolved-parameters.html#syntax","529":"/annotations/resolved-parameters.html#behavior","530":"/annotations/resolved-parameters.html#examples","531":"/annotations/resolved-parameters.html#inject-a-db-stored-api-token-into-an-outbound-call","532":"/annotations/resolved-parameters.html#multiple-resolved-parameters","533":"/annotations/resolved-parameters.html#resolved-value-in-url-header-and-body","534":"/annotations/resolved-parameters.html#how-it-compares-to-the-other-name-sources","535":"/annotations/resolved-parameters.html#related","536":"/annotations/response-headers.html#response-headers","537":"/annotations/response-headers.html#syntax","538":"/annotations/response-headers.html#examples","539":"/annotations/response-headers.html#set-content-type","540":"/annotations/response-headers.html#multiple-headers","541":"/annotations/response-headers.html#multi-value-headers","542":"/annotations/response-headers.html#cache-control","543":"/annotations/response-headers.html#combined-with-other-annotations","544":"/annotations/response-headers.html#dynamic-headers-from-parameters","545":"/annotations/response-headers.html#cors-headers","546":"/annotations/response-headers.html#common-headers","547":"/annotations/response-headers.html#related","548":"/annotations/response-headers.html#related-annotations","549":"/annotations/response-null-handling.html#response-null-handling","550":"/annotations/response-null-handling.html#syntax","551":"/annotations/response-null-handling.html#values","552":"/annotations/response-null-handling.html#examples","553":"/annotations/response-null-handling.html#return-empty-string-for-null","554":"/annotations/response-null-handling.html#return-204-for-null","555":"/annotations/response-null-handling.html#return-json-null","556":"/annotations/response-null-handling.html#configuration-default","557":"/annotations/response-null-handling.html#related","558":"/annotations/response-null-handling.html#related-annotations","559":"/annotations/result-name.html#result-name","560":"/annotations/result-name.html#syntax","561":"/annotations/result-name.html#examples","562":"/annotations/result-name.html#before-statement-separate-line","563":"/annotations/result-name.html#inline-after-semicolon-same-line","564":"/annotations/result-name.html#is-style-syntax","565":"/annotations/result-name.html#naming-some-results","566":"/annotations/result-name.html#naming-all-results","567":"/annotations/result-name.html#behavior","568":"/annotations/result-name.html#related","569":"/annotations/retry-strategy.html#retry-strategy","570":"/annotations/retry-strategy.html#syntax","571":"/annotations/retry-strategy.html#examples","572":"/annotations/retry-strategy.html#use-default-strategy","573":"/annotations/retry-strategy.html#use-named-strategy","574":"/annotations/retry-strategy.html#combined-with-timeout","575":"/annotations/retry-strategy.html#behavior","576":"/annotations/retry-strategy.html#common-retry-scenarios","577":"/annotations/retry-strategy.html#configuration-example","578":"/annotations/retry-strategy.html#related","579":"/annotations/retry-strategy.html#related-annotations","580":"/annotations/retry-strategy.html#see-also","581":"/annotations/returns.html#returns","582":"/annotations/returns.html#syntax","583":"/annotations/returns.html#when-to-use","584":"/annotations/returns.html#example","585":"/annotations/returns.html#scalar-type","586":"/annotations/returns.html#void-statements","587":"/annotations/returns.html#behavior","588":"/annotations/returns.html#related","589":"/annotations/security-sensitive.html#security-sensitive","590":"/annotations/security-sensitive.html#syntax","591":"/annotations/security-sensitive.html#examples","592":"/annotations/security-sensitive.html#password-change-endpoint","593":"/annotations/security-sensitive.html#login-endpoint","594":"/annotations/security-sensitive.html#payment-processing","595":"/annotations/security-sensitive.html#behavior","596":"/annotations/security-sensitive.html#related","597":"/annotations/security-sensitive.html#related-annotations","598":"/annotations/separator.html#separator","599":"/annotations/separator.html#syntax","600":"/annotations/separator.html#examples","601":"/annotations/separator.html#comma-separator-csv","602":"/annotations/separator.html#tab-separator-tsv","603":"/annotations/separator.html#pipe-separator","604":"/annotations/separator.html#custom-separator","605":"/annotations/separator.html#related","606":"/annotations/separator.html#related-annotations","607":"/annotations/single.html#single","608":"/annotations/single.html#syntax","609":"/annotations/single.html#default-behavior-vs-single","610":"/annotations/single.html#examples","611":"/annotations/single.html#postgresql-function","612":"/annotations/single.html#sql-file","613":"/annotations/single.html#single-unnamed-column","614":"/annotations/single.html#multi-command-files-positional","615":"/annotations/single.html#behavior","616":"/annotations/single.html#empty-results","617":"/annotations/single.html#related","618":"/annotations/skip.html#skip","619":"/annotations/skip.html#syntax","620":"/annotations/skip.html#examples","621":"/annotations/skip.html#skipping-a-do-block","622":"/annotations/skip.html#skipping-transaction-control","623":"/annotations/skip.html#inline-placement","624":"/annotations/skip.html#skipnonquerycommands-setting","625":"/annotations/skip.html#behavior","626":"/annotations/skip.html#related","627":"/annotations/sse-events-level.html#sse-events-level","628":"/annotations/sse-events-level.html#syntax","629":"/annotations/sse-events-level.html#values","630":"/annotations/sse-events-level.html#examples","631":"/annotations/sse-events-level.html#info-level-all-messages","632":"/annotations/sse-events-level.html#notice-level","633":"/annotations/sse-events-level.html#warning-level-only","634":"/annotations/sse-events-level.html#related","635":"/annotations/sse-events-level.html#related-annotations","636":"/annotations/sse-events-scope.html#sse-events-scope","637":"/annotations/sse-events-scope.html#syntax","638":"/annotations/sse-events-scope.html#values","639":"/annotations/sse-events-scope.html#request-correlation","640":"/annotations/sse-events-scope.html#examples","641":"/annotations/sse-events-scope.html#matching-scope","642":"/annotations/sse-events-scope.html#authorize-scope-with-roles","643":"/annotations/sse-events-scope.html#authorize-with-user-names-or-ids","644":"/annotations/sse-events-scope.html#multiple-values","645":"/annotations/sse-events-scope.html#broadcast-to-all","646":"/annotations/sse-events-scope.html#dynamic-scope-via-raise-hint","647":"/annotations/sse-events-scope.html#related","648":"/annotations/sse-events-scope.html#related-annotations","649":"/annotations/sse.html#sse","650":"/annotations/sse.html#how-events-flow","651":"/annotations/sse.html#syntax","652":"/annotations/sse.html#sse-path-construction","653":"/annotations/sse.html#when-path-is-omitted","654":"/annotations/sse.html#when-custom-path-is-specified","655":"/annotations/sse.html#default-level","656":"/annotations/sse.html#level-filtering","657":"/annotations/sse.html#examples","658":"/annotations/sse.html#basic-sse-endpoint-function","659":"/annotations/sse.html#basic-sse-endpoint-sql-file","660":"/annotations/sse.html#with-notice-level","661":"/annotations/sse.html#warning-level-only","662":"/annotations/sse.html#using-default-path-level-name","663":"/annotations/sse.html#cross-procedure-pattern","664":"/annotations/sse.html#function-form","665":"/annotations/sse.html#sql-file-form","666":"/annotations/sse.html#what-the-client-does","667":"/annotations/sse.html#behavior","668":"/annotations/sse.html#on-the-publisher-side","669":"/annotations/sse.html#on-the-subscriber-side","670":"/annotations/sse.html#related","671":"/annotations/sse.html#related-annotations","672":"/annotations/sse.html#see-also","673":"/annotations/table-format.html#table-format","674":"/annotations/table-format.html#syntax","675":"/annotations/table-format.html#parameters","676":"/annotations/table-format.html#examples","677":"/annotations/table-format.html#static-html-table","678":"/annotations/table-format.html#static-excel-download","679":"/annotations/table-format.html#dynamic-format-selection","680":"/annotations/table-format.html#related","681":"/annotations/table-format.html#related-annotations","682":"/annotations/table-format.html#see-also","683":"/annotations/tags.html#tags","684":"/annotations/tags.html#available-tags","685":"/annotations/tags.html#syntax","686":"/annotations/tags.html#example-cache-only-when-immutable","687":"/annotations/tags.html#behavior","688":"/annotations/tags.html#related","689":"/annotations/test-claim.html#test-claim","690":"/annotations/test-claim.html#semantics","691":"/annotations/test-claim.html#why-not-call-the-login-endpoint","692":"/annotations/test-claim.html#related","693":"/annotations/test-connection.html#test-connection","694":"/annotations/test-connection.html#syntax","695":"/annotations/test-connection.html#example","696":"/annotations/test-connection.html#notes","697":"/annotations/test-connection.html#related","698":"/annotations/test-response.html#test-response","699":"/annotations/test-response.html#default-naming","700":"/annotations/test-response.html#syntax","701":"/annotations/test-response.html#notes","702":"/annotations/test-response.html#related","703":"/annotations/test-setup.html#test-setup","704":"/annotations/test-setup.html#syntax","705":"/annotations/test-setup.html#example","706":"/annotations/test-setup.html#header-semantics","707":"/annotations/test-setup.html#related","708":"/annotations/test-tag.html#test-tag","709":"/annotations/test-tag.html#syntax","710":"/annotations/test-tag.html#example","711":"/annotations/test-tag.html#tags-via-a-shared-profile","712":"/annotations/test-tag.html#related","713":"/annotations/test-teardown.html#test-teardown","714":"/annotations/test-teardown.html#syntax","715":"/annotations/test-teardown.html#example","716":"/annotations/test-teardown.html#ordering","717":"/annotations/test-teardown.html#related","718":"/annotations/tsclient.html#tsclient","719":"/annotations/tsclient.html#syntax","720":"/annotations/tsclient.html#parameters","721":"/annotations/tsclient.html#examples","722":"/annotations/tsclient.html#disable-generation","723":"/annotations/tsclient.html#url-only-export","724":"/annotations/tsclient.html#custom-module","725":"/annotations/tsclient.html#related","726":"/annotations/tsclient.html#related-annotations","727":"/annotations/tsclient.html#see-also","728":"/annotations/user-context.html#user-context","729":"/annotations/user-context.html#keywords","730":"/annotations/user-context.html#syntax","731":"/annotations/user-context.html#examples","732":"/annotations/user-context.html#enable-user-context","733":"/annotations/user-context.html#access-user-claims-in-function","734":"/annotations/user-context.html#access-all-claims-as-json","735":"/annotations/user-context.html#access-client-ip-address","736":"/annotations/user-context.html#combined-with-request-headers","737":"/annotations/user-context.html#behavior","738":"/annotations/user-context.html#default-context-keys","739":"/annotations/user-context.html#additional-context-keys-when-configured","740":"/annotations/user-context.html#related","741":"/annotations/user-context.html#related-annotations","742":"/annotations/user-context.html#see-also","743":"/annotations/upload.html#upload","744":"/annotations/upload.html#keywords","745":"/annotations/upload.html#syntax","746":"/annotations/upload.html#handler-types","747":"/annotations/upload.html#shared-annotation-options","748":"/annotations/upload.html#upload-metadata","749":"/annotations/upload.html#large-object-handler","750":"/annotations/upload.html#basic-example","751":"/annotations/upload.html#with-custom-oid-parameter","752":"/annotations/upload.html#context-metadata","753":"/annotations/upload.html#large-object-annotation-options","754":"/annotations/upload.html#file-system-handler","755":"/annotations/upload.html#basic-example-1","756":"/annotations/upload.html#with-custom-parameters","757":"/annotations/upload.html#file-system-annotation-options","758":"/annotations/upload.html#mime-type-filtering","759":"/annotations/upload.html#csv-handler","760":"/annotations/upload.html#row-command-function-signature","761":"/annotations/upload.html#row-command-parameters","762":"/annotations/upload.html#row-metadata-structure-4","763":"/annotations/upload.html#upload-function-metadata-meta-parameter","764":"/annotations/upload.html#basic-example-2","765":"/annotations/upload.html#accessing-user-claims-in-row-command","766":"/annotations/upload.html#using-user-context-variables","767":"/annotations/upload.html#custom-delimiters","768":"/annotations/upload.html#csv-annotation-options","769":"/annotations/upload.html#excel-handler","770":"/annotations/upload.html#row-command-function-signature-1","771":"/annotations/upload.html#row-command-parameters-1","772":"/annotations/upload.html#row-metadata-structure-4-1","773":"/annotations/upload.html#upload-function-metadata-meta-parameter-1","774":"/annotations/upload.html#basic-example-3","775":"/annotations/upload.html#row-data-as-json","776":"/annotations/upload.html#excel-annotation-options","777":"/annotations/upload.html#error-handling-and-rollback","778":"/annotations/upload.html#multiple-file-uploads","779":"/annotations/upload.html#behavior","780":"/annotations/upload.html#custom-parameters","781":"/annotations/upload.html#shared-parameters","782":"/annotations/upload.html#large-object-upload-handler","783":"/annotations/upload.html#example","784":"/annotations/upload.html#file-system-upload-handler","785":"/annotations/upload.html#example-1","786":"/annotations/upload.html#csv-upload-handler","787":"/annotations/upload.html#example-2","788":"/annotations/upload.html#excel-upload-handler","789":"/annotations/upload.html#example-3","790":"/annotations/upload.html#related","791":"/annotations/upload.html#blog-posts","792":"/annotations/upload.html#related-annotations","793":"/annotations/upload.html#see-also","794":"/annotations/user-parameters.html#user-parameters","795":"/annotations/user-parameters.html#syntax","796":"/annotations/user-parameters.html#examples","797":"/annotations/user-parameters.html#basic-user-parameters","798":"/annotations/user-parameters.html#with-default-values-for-unauthenticated-access","799":"/annotations/user-parameters.html#access-all-claims-as-json","800":"/annotations/user-parameters.html#combined-with-user-context","801":"/annotations/user-parameters.html#behavior","802":"/annotations/user-parameters.html#default-parameter-mapping","803":"/annotations/user-parameters.html#differences-from-user-context","804":"/annotations/user-parameters.html#related","805":"/annotations/user-parameters.html#related-annotations","806":"/annotations/user-parameters.html#see-also","807":"/annotations/validate.html#validate","808":"/annotations/validate.html#keywords","809":"/annotations/validate.html#syntax","810":"/annotations/validate.html#examples","811":"/annotations/validate.html#single-rule-validation","812":"/annotations/validate.html#multiple-rules-on-one-parameter","813":"/annotations/validate.html#multiple-parameters","814":"/annotations/validate.html#using-converted-parameter-names","815":"/annotations/validate.html#with-authorization","816":"/annotations/validate.html#default-rules","817":"/annotations/validate.html#custom-rules","818":"/annotations/validate.html#behavior","819":"/annotations/validate.html#error-response","820":"/annotations/validate.html#related","821":"/annotations/validate.html#related-annotations","822":"/annotations/validate.html#see-also","823":"/annotations/void.html#void","824":"/annotations/void.html#syntax","825":"/annotations/void.html#examples","826":"/annotations/void.html#multi-command-side-effects","827":"/annotations/void.html#single-command-void","828":"/annotations/void.html#function-endpoints","829":"/annotations/void.html#behavior","830":"/annotations/void.html#related","831":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#npgsqlrest-vs-sqlpage-two-sql-first-tools-two-different-layers","832":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#the-shared-idea","833":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#the-fork-in-the-road","834":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#where-sqlpage-is-stronger-the-ui","835":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#where-npgsqlrest-is-stronger-the-api","836":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#they-re-complementary-not-rivals","837":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#when-to-choose-each","838":"/blog/DRAFT-npgsqlrest-vs-sqlpage.html#conclusion","839":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#draft-20th-anniversary-of-the-vietnam-of-computer-science","840":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#introduction","841":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#what-is-the-object–relational-impedance-mismatch","842":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#_1-state-data-abstraction-misconception","843":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-claim","844":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-reality","845":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-cost","846":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#_2-storage-devices-abstraction-misconception","847":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-claim-1","848":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-reality-1","849":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-cost-1","850":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#_3-data-structures-abstraction-misconception","851":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-claim-2","852":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-reality-2","853":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-cost-2","854":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#copy-not-record-→-staleness-and-write-amplification","855":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#private-not-shared-→-arbitration-lives-in-the-database-anyway","856":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#graph-walk-not-set-operation-→-the-access-pattern-tax","857":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#simulation-not-engine-→-the-capability-ceiling","858":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#_4-abstraction-over-algorithms","859":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-claim-3","860":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-reality-3","861":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-cost-3","862":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#_5-abstraction-over-concurrency-and-integrity","863":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-claim-4","864":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-reality-4","865":"/blog/DRAFT-anniversary-vietnam-of-computer-science.html#the-cost-4","866":"/blog/case-study-zero-backend-code.html#case-study-74-endpoints-zero-backend-code","867":"/blog/case-study-zero-backend-code.html#what-s-in-the-repository","868":"/blog/case-study-zero-backend-code.html#what-npgsqlrest-is-doing-for-them","869":"/blog/case-study-zero-backend-code.html#the-comparison-equivalent-build-in-asp-net-core","870":"/blog/case-study-zero-backend-code.html#how-it-scores-on-the-four-dimensions-that-matter","871":"/blog/case-study-zero-backend-code.html#productivity","872":"/blog/case-study-zero-backend-code.html#time-saved-quantified","873":"/blog/case-study-zero-backend-code.html#lines-of-code-saved","874":"/blog/case-study-zero-backend-code.html#performance","875":"/blog/case-study-zero-backend-code.html#overall-quality","876":"/blog/case-study-zero-backend-code.html#honest-tradeoffs","877":"/blog/case-study-zero-backend-code.html#what-this-case-study-is-and-isn-t","878":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#csv-and-excel-ingestion-made-easy-postgresql-row-processing-with-npgsqlrest","879":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#the-traditional-approach-rigid-and-brittle","880":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#the-npgsqlrest-approach-dynamic-and-flexible","881":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#how-it-works","882":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#the-row-function-four-parameters-infinite-flexibility","883":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#csv-row-function-example","884":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#excel-row-function-example","885":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#row-chaining-the-power-of-3","886":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#the-upload-endpoint-function","887":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#upload-metadata-structure","888":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#dynamic-structure-no-hardcoding-no-redeployment","889":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#configuration","890":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#annotation-options","891":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#csv-handler-options","892":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#excel-handler-options","893":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#row-metadata-differences-csv-vs-excel","894":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#generated-typescript-client","895":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#advanced-patterns","896":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#skipping-header-rows","897":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#validation-and-rejection","898":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#upsert-insert-or-update","899":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#processing-only-specific-sheets","900":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#json-row-format-for-complex-data","901":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#transaction-safety","902":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#combining-handlers-process-and-store-the-original-file","903":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#combined-handler-metadata","904":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#fallback-handler-one-endpoint-for-both-excel-and-csv","905":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#authentication-integration","906":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#comparison-with-other-tools","907":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#copy-command","908":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#etl-tools-talend-pentaho-etc","909":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#python-pandas","910":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#conclusion-what-you-don-t-have-to-write","911":"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html#the-numbers","912":"/blog/custom-types-multiset-rest-api.html#custom-types-and-multiset-for-nested-json-in-postgresql-rest-apis","913":"/blog/custom-types-multiset-rest-api.html#example-setup","914":"/blog/custom-types-multiset-rest-api.html#returning-single-object","915":"/blog/custom-types-multiset-rest-api.html#using-custom-types-as-parameters","916":"/blog/custom-types-multiset-rest-api.html#returning-sets-of-objects","917":"/blog/custom-types-multiset-rest-api.html#new-nested-json-objects","918":"/blog/custom-types-multiset-rest-api.html#new-nested-json-with-multiset","919":"/blog/custom-types-multiset-rest-api.html#limitations","920":"/blog/custom-types-multiset-rest-api.html#conclusion-and-workaround","921":"/blog/database-level-security-postgresql-authentication.html#database-level-security-building-secure-authentication-with-postgresql-and-npgsqlrest","922":"/blog/database-level-security-postgresql-authentication.html#the-principle-of-least-privilege-polp","923":"/blog/database-level-security-postgresql-authentication.html#schema-architecture","924":"/blog/database-level-security-postgresql-authentication.html#the-protected-schema","925":"/blog/database-level-security-postgresql-authentication.html#the-public-api-schema","926":"/blog/database-level-security-postgresql-authentication.html#the-restricted-application-role","927":"/blog/database-level-security-postgresql-authentication.html#bypassing-bcrypt-s-72-byte-limit","928":"/blog/database-level-security-postgresql-authentication.html#the-hash-function","929":"/blog/database-level-security-postgresql-authentication.html#the-verify-function","930":"/blog/database-level-security-postgresql-authentication.html#testing-the-password-functions","931":"/blog/database-level-security-postgresql-authentication.html#the-authentication-functions","932":"/blog/database-level-security-postgresql-authentication.html#understanding-security-definer","933":"/blog/database-level-security-postgresql-authentication.html#protecting-against-search-path-attacks","934":"/blog/database-level-security-postgresql-authentication.html#login-function","935":"/blog/database-level-security-postgresql-authentication.html#logout-function","936":"/blog/database-level-security-postgresql-authentication.html#who-am-i-function","937":"/blog/database-level-security-postgresql-authentication.html#npgsqlrest-configuration","938":"/blog/database-level-security-postgresql-authentication.html#the-demo-application","939":"/blog/database-level-security-postgresql-authentication.html#why-this-architecture-is-more-secure","940":"/blog/database-level-security-postgresql-authentication.html#_1-defense-in-depth","941":"/blog/database-level-security-postgresql-authentication.html#_2-sql-injection-becomes-less-dangerous","942":"/blog/database-level-security-postgresql-authentication.html#_3-no-secrets-in-application-code","943":"/blog/database-level-security-postgresql-authentication.html#_4-auditable-security-boundary","944":"/blog/database-level-security-postgresql-authentication.html#_5-bcrypt-limit-protection","945":"/blog/database-level-security-postgresql-authentication.html#comparison-with-traditional-approaches","946":"/blog/database-level-security-postgresql-authentication.html#conclusion","947":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#excel-exports-done-right-zero-allocation-streaming-from-postgresql","948":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#why-excel-exports-are-terrible","949":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#the-npgsqlrest-approach-pure-streaming","950":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#what-makes-this-special","951":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#zero-allocation-cell-writing","952":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#native-type-mapping","953":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#constant-memory-usage","954":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#aot-trim-compatible","955":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#building-an-excel-export-endpoint","956":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#step-1-write-your-function","957":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#step-2-add-the-annotation","958":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#step-3-configure-table-format","959":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#two-formats-one-endpoint","960":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#static-format-annotation","961":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#the-typescript-client-url-only-generation","962":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#excel-format-configuration","963":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#datetime-and-numeric-formats","964":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#worksheet-and-file-names","965":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#html-table-format","966":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#bonus-built-in-statistics-endpoints","967":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#stats-configuration-options","968":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#the-traditional-way-vs-this-way","969":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#memory-profile-comparison","970":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#running-the-example","971":"/blog/excel-export-table-format-postgresql-npgsqlrest.html#conclusion","972":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#end-to-end-static-type-checking-postgresql-to-typescript","973":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-problem-with-traditional-api-development","974":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#why-postgresql-functions","975":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-solution-single-source-of-truth","976":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#project-structure","977":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-database-schema","978":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#functions-that-define-the-api-contract","979":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#get-users","980":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#get-posts","981":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#static-type-checking-at-the-sql-level","982":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#how-postgresql-enforces-return-types","983":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-return-type-contract","984":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#type-changes-propagate-naturally","985":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#why-functions-are-recreated-on-every-build","986":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#built-in-testing-with-sql-assertions","987":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#unit-testing-postgresql-functions-beyond-fixed-data","988":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#co-located-tests-function-and-test-in-the-same-file","989":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#test-isolation-with-rollback","990":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#testing-multiple-scenarios","991":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#testing-against-empty-tables","992":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#deferrable-constraints-the-key-to-test-data","993":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#why-database-testing-is-fast","994":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#addressing-common-myths","995":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-generated-typescript-client","996":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-application-code","997":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-complete-type-safe-workflow","998":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#configuration","999":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#benefits-of-this-approach","1000":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#_1-single-source-of-truth","1001":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#_2-compile-time-safety","1002":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#_3-automatic-documentation","1003":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#_4-database-level-testing","1004":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#_5-no-runtime-type-checking-overhead","1005":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#conclusion","1006":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#why-this-stack-is-superior","1007":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#performance-that-scales","1008":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#maximum-type-safety-minimum-code","1009":"/blog/end-to-end-static-type-checking-postgresql-typescript.html#the-bottom-line","1010":"/blog/external-api-calls-postgresql-http-types.html#call-external-apis-from-postgresql-http-types-in-npgsqlrest","1011":"/blog/external-api-calls-postgresql-http-types.html#the-problem-backend-for-frontend-api-aggregation","1012":"/blog/external-api-calls-postgresql-http-types.html#why-not-use-postgresql-http-extensions","1013":"/blog/external-api-calls-postgresql-http-types.html#installation-and-distribution-overhead","1014":"/blog/external-api-calls-postgresql-http-types.html#network-and-performance-issues","1015":"/blog/external-api-calls-postgresql-http-types.html#the-npgsqlrest-advantage","1016":"/blog/external-api-calls-postgresql-http-types.html#the-npgsqlrest-solution-http-types","1017":"/blog/external-api-calls-postgresql-http-types.html#the-http-type-syntax-just-like-http-files","1018":"/blog/external-api-calls-postgresql-http-types.html#building-the-financial-dashboard","1019":"/blog/external-api-calls-postgresql-http-types.html#step-1-define-http-types","1020":"/blog/external-api-calls-postgresql-http-types.html#step-2-define-the-return-type","1021":"/blog/external-api-calls-postgresql-http-types.html#step-3-create-the-aggregation-function","1022":"/blog/external-api-calls-postgresql-http-types.html#step-4-configuration","1023":"/blog/external-api-calls-postgresql-http-types.html#what-happens-at-runtime","1024":"/blog/external-api-calls-postgresql-http-types.html#the-generated-typescript-client","1025":"/blog/external-api-calls-postgresql-http-types.html#traditional-approach-what-it-would-take","1026":"/blog/external-api-calls-postgresql-http-types.html#traditional-backend-node-js","1027":"/blog/external-api-calls-postgresql-http-types.html#the-numbers","1028":"/blog/external-api-calls-postgresql-http-types.html#advanced-features","1029":"/blog/external-api-calls-postgresql-http-types.html#multiple-api-calls","1030":"/blog/external-api-calls-postgresql-http-types.html#post-requests-with-bodies","1031":"/blog/external-api-calls-postgresql-http-types.html#response-field-customization","1032":"/blog/external-api-calls-postgresql-http-types.html#retry-logic","1033":"/blog/external-api-calls-postgresql-http-types.html#resolved-parameter-expressions","1034":"/blog/external-api-calls-postgresql-http-types.html#timeout-configuration","1035":"/blog/external-api-calls-postgresql-http-types.html#when-to-use-http-types","1036":"/blog/external-api-calls-postgresql-http-types.html#conclusion","1037":"/blog/#blog-posts-tutorials","1038":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#turn-postgresql-into-mcp-tools-an-ai-agent-can-call","1039":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#what-mcp-is-in-one-paragraph","1040":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#opt-in-never-automatic","1041":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#results-are-structured","1042":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#mcp-only-tools-a-tool-with-no-rest-route","1043":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#one-source-two-interfaces-—-made-visible","1044":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#the-real-test-an-ai-agent-driving-the-store","1045":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#authorization-without-locking-down-the-server","1046":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#why-this-approach-holds-up","1047":"/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html#try-it","1048":"/blog/multiple-auth-schemes-rbac-external-providers.html#multiple-authentication-schemes-role-based-access-control-and-external-providers","1049":"/blog/multiple-auth-schemes-rbac-external-providers.html#why-npgsqlrest-s-built-in-password-hasher","1050":"/blog/multiple-auth-schemes-rbac-external-providers.html#schema-design","1051":"/blog/multiple-auth-schemes-rbac-external-providers.html#generating-password-hashes","1052":"/blog/multiple-auth-schemes-rbac-external-providers.html#automatic-parameter-hashing-for-registration","1053":"/blog/multiple-auth-schemes-rbac-external-providers.html#multiple-authentication-schemes","1054":"/blog/multiple-auth-schemes-rbac-external-providers.html#a-note-on-data-protection-and-encryption","1055":"/blog/multiple-auth-schemes-rbac-external-providers.html#the-login-function-with-built-in-password-verification","1056":"/blog/multiple-auth-schemes-rbac-external-providers.html#password-verification-callbacks","1057":"/blog/multiple-auth-schemes-rbac-external-providers.html#role-based-access-control","1058":"/blog/multiple-auth-schemes-rbac-external-providers.html#user-context-with-current-setting","1059":"/blog/multiple-auth-schemes-rbac-external-providers.html#external-oauth-providers","1060":"/blog/multiple-auth-schemes-rbac-external-providers.html#the-external-login-function","1061":"/blog/multiple-auth-schemes-rbac-external-providers.html#the-demo-application","1062":"/blog/multiple-auth-schemes-rbac-external-providers.html#configuration-summary","1063":"/blog/multiple-auth-schemes-rbac-external-providers.html#generated-client-with-token-support","1064":"/blog/multiple-auth-schemes-rbac-external-providers.html#conclusion-enterprise-auth-made-simple","1065":"/blog/multiple-auth-schemes-rbac-external-providers.html#this-blog-post-is-your-recipe","1066":"/blog/npgsqlrest-3.13-production-patterns.html#npgsqlrest-3-13-0-cache-profiles-auth-schemes-per-user-rate-limits-and-pgbouncer-compatibility","1067":"/blog/npgsqlrest-3.13-production-patterns.html#_1-conditional-caching-historical-vs-current-vs-live","1068":"/blog/npgsqlrest-3.13-production-patterns.html#_2-short-lived-sensitive-session-alongside-the-normal-session","1069":"/blog/npgsqlrest-3.13-production-patterns.html#_3-per-user-rate-limits","1070":"/blog/npgsqlrest-3.13-production-patterns.html#_4-multi-tenant-search-path-with-pgbouncer-or-rds-proxy-or-supabase-pooler","1071":"/blog/npgsqlrest-3.13-production-patterns.html#other-notable-changes","1072":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#tests-are-sql-files-too","1073":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#introduction","1074":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#tl-dr-test-runner","1075":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#database-testing-is-impossible-they-said","1076":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#the-pattern-i-have-used-for-years","1077":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#but-it-has-limits","1078":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#tests-are-sql-files-too-1","1079":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#the-old-demons-isolation-and-fixtures","1080":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#and-then-there-is-watch-mode","1081":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#ai-tdd","1082":"/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html#where-to-start","1083":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#npgsqlrest-vs-postgrest-vs-supabase-complete-feature-comparison","1084":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#executive-summary","1085":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#architecture-comparison","1086":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#npgsqlrest","1087":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#postgrest","1088":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#supabase","1089":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#performance-benchmarks","1090":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#requests-per-second-100-concurrent-users-1-record","1091":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#larger-payloads-500-records-100-vu","1092":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#postgresql-type-handling","1093":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#feature-comparison-matrix","1094":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#platform-features","1095":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#core-api-generation","1096":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#table-and-view-query-features","1097":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#custom-types-and-nested-json","1098":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#authentication","1099":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#file-handling","1100":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#security-and-infrastructure","1101":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#performance-features","1102":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#connection-pooler-compatibility-multi-tenancy","1103":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#real-time-capabilities","1104":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#external-service-integration-and-custom-code-execution","1105":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#npgsqlrest-declarative-proxy-in-sql","1106":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#postgrest-no-custom-code-execution","1107":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#supabase-edge-functions-separate-deno-runtime","1108":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#architectural-comparison","1109":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#advanced-features","1110":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#observability","1111":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#error-handling","1112":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#configuration-approach","1113":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#npgsqlrest-sql-comments","1114":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#postgrest-external-configuration-rls","1115":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#supabase-dashboard-rls-edge-functions","1116":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#deployment-comparison","1117":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#npgsqlrest-1","1118":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#postgrest-1","1119":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#supabase-self-hosted","1120":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#when-to-choose-each","1121":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#choose-npgsqlrest-when","1122":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#choose-postgrest-when","1123":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#choose-supabase-when","1124":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#migration-considerations","1125":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#from-postgrest-to-npgsqlrest","1126":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#from-supabase-to-npgsqlrest","1127":"/blog/npgsqlrest-vs-postgrest-supabase-comparison.html#conclusion","1128":"/blog/optimization-labels-101.html#postgresql-optimization-labels-101","1129":"/blog/optimization-labels-101.html#volatile-stable-immutable","1130":"/blog/optimization-labels-101.html#parallel-unsafe-restricted-safe","1131":"/blog/optimization-labels-101.html#cost-rows","1132":"/blog/optimization-labels-101.html#cost-default-100","1133":"/blog/optimization-labels-101.html#rows-default-1000","1134":"/blog/optimization-labels-101.html#called-on-null-input-returns-null-on-null-input-strict","1135":"/blog/performance-scalability-high-availability-npgsqlrest.html#performance-scalability-and-high-availability-with-npgsqlrest","1136":"/blog/performance-scalability-high-availability-npgsqlrest.html#caching-strategies","1137":"/blog/performance-scalability-high-availability-npgsqlrest.html#http-cache-headers-the-fastest-cache","1138":"/blog/performance-scalability-high-availability-npgsqlrest.html#setting-cache-headers-in-annotations","1139":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-busting-technique","1140":"/blog/performance-scalability-high-availability-npgsqlrest.html#server-side-caching","1141":"/blog/performance-scalability-high-availability-npgsqlrest.html#enabling-server-cache","1142":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-keys-by-parameter","1143":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-expiration","1144":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-types","1145":"/blog/performance-scalability-high-availability-npgsqlrest.html#memory-cache","1146":"/blog/performance-scalability-high-availability-npgsqlrest.html#redis-cache","1147":"/blog/performance-scalability-high-availability-npgsqlrest.html#hybrid-cache","1148":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-invalidation-endpoints","1149":"/blog/performance-scalability-high-availability-npgsqlrest.html#caching-set-returning-functions","1150":"/blog/performance-scalability-high-availability-npgsqlrest.html#cache-profiles","1151":"/blog/performance-scalability-high-availability-npgsqlrest.html#retry-strategies","1152":"/blog/performance-scalability-high-availability-npgsqlrest.html#connection-retries","1153":"/blog/performance-scalability-high-availability-npgsqlrest.html#command-retries","1154":"/blog/performance-scalability-high-availability-npgsqlrest.html#multiple-retry-strategies","1155":"/blog/performance-scalability-high-availability-npgsqlrest.html#postgresql-error-code-classes","1156":"/blog/performance-scalability-high-availability-npgsqlrest.html#rate-limiting","1157":"/blog/performance-scalability-high-availability-npgsqlrest.html#enabling-rate-limiting","1158":"/blog/performance-scalability-high-availability-npgsqlrest.html#fixed-window","1159":"/blog/performance-scalability-high-availability-npgsqlrest.html#sliding-window","1160":"/blog/performance-scalability-high-availability-npgsqlrest.html#token-bucket","1161":"/blog/performance-scalability-high-availability-npgsqlrest.html#concurrency-limiting","1162":"/blog/performance-scalability-high-availability-npgsqlrest.html#per-user-rate-limiting-partitions","1163":"/blog/performance-scalability-high-availability-npgsqlrest.html#combining-policies","1164":"/blog/performance-scalability-high-availability-npgsqlrest.html#thread-pool-optimization","1165":"/blog/performance-scalability-high-availability-npgsqlrest.html#the-thread-injection-problem","1166":"/blog/performance-scalability-high-availability-npgsqlrest.html#configuring-minimum-threads","1167":"/blog/performance-scalability-high-availability-npgsqlrest.html#worker-threads-vs-completion-port-threads","1168":"/blog/performance-scalability-high-availability-npgsqlrest.html#high-throughput-configuration","1169":"/blog/performance-scalability-high-availability-npgsqlrest.html#sizing-guidelines","1170":"/blog/performance-scalability-high-availability-npgsqlrest.html#when-not-to-increase-thread-pool-size","1171":"/blog/performance-scalability-high-availability-npgsqlrest.html#example-burst-traffic-handling","1172":"/blog/performance-scalability-high-availability-npgsqlrest.html#high-availability","1173":"/blog/performance-scalability-high-availability-npgsqlrest.html#multi-host-connections","1174":"/blog/performance-scalability-high-availability-npgsqlrest.html#target-session-attributes","1175":"/blog/performance-scalability-high-availability-npgsqlrest.html#load-balancing","1176":"/blog/performance-scalability-high-availability-npgsqlrest.html#read-replica-routing","1177":"/blog/performance-scalability-high-availability-npgsqlrest.html#production-high-availability-configuration","1178":"/blog/performance-scalability-high-availability-npgsqlrest.html#same-schema-requirement","1179":"/blog/performance-scalability-high-availability-npgsqlrest.html#putting-it-all-together","1180":"/blog/performance-scalability-high-availability-npgsqlrest.html#summary","1181":"/blog/performance-scalability-high-availability-npgsqlrest.html#development-time-saved","1182":"/blog/performance-scalability-high-availability-npgsqlrest.html#related-documentation","1183":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#turn-postgresql-into-a-bi-server-csv-exports-basic-auth-excel-integration","1184":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-architecture","1185":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#defense-in-depth-the-principle-of-least-privilege","1186":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#creating-csv-endpoints","1187":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#define-a-reusable-type","1188":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-secured-report-function","1189":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#csv-annotations","1190":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#type-reuse-and-composition","1191":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-problem-schema-duplication","1192":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-solution-composite-type-expansion","1193":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#benefits-for-applications","1194":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#securing-with-basic-authentication","1195":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#password-hashing","1196":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#multiple-users-via-configuration","1197":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#database-driven-authentication-with-challengecommand","1198":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#ssl-configuration","1199":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#setup-steps","1200":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#no-code-generation-required","1201":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#excel-power-query-integration","1202":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#connecting-excel-to-your-endpoint","1203":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-power-of-central-control","1204":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#production-considerations","1205":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#when-you-still-need-etl","1206":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#the-cost-comparison","1207":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#complete-example","1208":"/blog/postgresql-bi-server-excel-csv-basic-auth.html#conclusion","1209":"/blog/passkey-sql-auth.html#implementing-webauthn-passkeys-with-pure-sql-and-npgsqlrest","1210":"/blog/passkey-sql-auth.html#what-gets-stored-and-what-doesn-t","1211":"/blog/passkey-sql-auth.html#architecture-overview","1212":"/blog/passkey-sql-auth.html#complete-example-walkthrough","1213":"/blog/passkey-sql-auth.html#_1-database-schema","1214":"/blog/passkey-sql-auth.html#_2-challenge-functions","1215":"/blog/passkey-sql-auth.html#_3-completion-functions","1216":"/blog/passkey-sql-auth.html#_4-authentication-function","1217":"/blog/passkey-sql-auth.html#_5-configuration","1218":"/blog/passkey-sql-auth.html#_6-client-side-implementation","1219":"/blog/passkey-sql-auth.html#three-authentication-flows","1220":"/blog/passkey-sql-auth.html#_1-registration-new-user-with-passkey","1221":"/blog/passkey-sql-auth.html#_2-add-passkey-existing-user","1222":"/blog/passkey-sql-auth.html#_3-login","1223":"/blog/passkey-sql-auth.html#complete-configuration-reference","1224":"/blog/passkey-sql-auth.html#general-settings","1225":"/blog/passkey-sql-auth.html#relying-party-settings","1226":"/blog/passkey-sql-auth.html#endpoint-paths","1227":"/blog/passkey-sql-auth.html#webauthn-settings","1228":"/blog/passkey-sql-auth.html#userverificationrequirement","1229":"/blog/passkey-sql-auth.html#residentkeyrequirement","1230":"/blog/passkey-sql-auth.html#attestationconveyance","1231":"/blog/passkey-sql-auth.html#sql-commands-reference","1232":"/blog/passkey-sql-auth.html#challengeaddexistingusercommand","1233":"/blog/passkey-sql-auth.html#challengeregistrationcommand","1234":"/blog/passkey-sql-auth.html#challengeauthenticationcommand","1235":"/blog/passkey-sql-auth.html#verifychallengecommand","1236":"/blog/passkey-sql-auth.html#authenticatedatacommand","1237":"/blog/passkey-sql-auth.html#completeaddexistingusercommand","1238":"/blog/passkey-sql-auth.html#completeregistrationcommand","1239":"/blog/passkey-sql-auth.html#completeauthenticatecommand","1240":"/blog/passkey-sql-auth.html#column-name-configuration","1241":"/blog/passkey-sql-auth.html#analytics-data","1242":"/blog/passkey-sql-auth.html#security-considerations","1243":"/blog/passkey-sql-auth.html#what-npgsqlrest-validates","1244":"/blog/passkey-sql-auth.html#what-you-control","1245":"/blog/passkey-sql-auth.html#rate-limiting","1246":"/blog/passkey-sql-auth.html#advantages-of-this-approach","1247":"/blog/passkey-sql-auth.html#_1-sql-first-logic","1248":"/blog/passkey-sql-auth.html#_2-no-external-dependencies","1249":"/blog/passkey-sql-auth.html#_3-complete-control","1250":"/blog/passkey-sql-auth.html#_4-built-in-resilience","1251":"/blog/passkey-sql-auth.html#_5-privacy-by-design","1252":"/blog/passkey-sql-auth.html#getting-started","1253":"/blog/passkey-sql-auth.html#conclusion","1254":"/blog/postgresql-rest-api-benchmark-2026.html#postgresql-rest-api-benchmark-2026-14-frameworks-compared","1255":"/blog/postgresql-rest-api-benchmark-2026.html#what-we-tested","1256":"/blog/postgresql-rest-api-benchmark-2026.html#what-s-new-in-this-benchmark","1257":"/blog/postgresql-rest-api-benchmark-2026.html#version-updates","1258":"/blog/postgresql-rest-api-benchmark-2026.html#new-test-scenarios","1259":"/blog/postgresql-rest-api-benchmark-2026.html#infrastructure-changes","1260":"/blog/postgresql-rest-api-benchmark-2026.html#comparing-with-previous-results","1261":"/blog/postgresql-rest-api-benchmark-2026.html#key-findings","1262":"/blog/postgresql-rest-api-benchmark-2026.html#swoole-php-dominates-large-payload-scenarios","1263":"/blog/postgresql-rest-api-benchmark-2026.html#npgsqlrest-leads-high-concurrency-low-payload-scenarios","1264":"/blog/postgresql-rest-api-benchmark-2026.html#the-top-performers-by-scenario","1265":"/blog/postgresql-rest-api-benchmark-2026.html#performance-tiers-at-100-vu-1-record","1266":"/blog/postgresql-rest-api-benchmark-2026.html#what-changed-from-2025","1267":"/blog/postgresql-rest-api-benchmark-2026.html#scaling-behavior","1268":"/blog/postgresql-rest-api-benchmark-2026.html#large-payloads-level-the-playing-field","1269":"/blog/postgresql-rest-api-benchmark-2026.html#pure-http-overhead-minimal-baseline","1270":"/blog/postgresql-rest-api-benchmark-2026.html#post-body-parsing-performance","1271":"/blog/postgresql-rest-api-benchmark-2026.html#python-frameworks-continue-to-struggle","1272":"/blog/postgresql-rest-api-benchmark-2026.html#jit-vs-aot-in-2026","1273":"/blog/postgresql-rest-api-benchmark-2026.html#why-certain-frameworks-excel","1274":"/blog/postgresql-rest-api-benchmark-2026.html#swoole-php-s-rise","1275":"/blog/postgresql-rest-api-benchmark-2026.html#go-s-http-dominance","1276":"/blog/postgresql-rest-api-benchmark-2026.html#npgsqlrest-s-architecture","1277":"/blog/postgresql-rest-api-benchmark-2026.html#resource-usage","1278":"/blog/postgresql-rest-api-benchmark-2026.html#key-observations","1279":"/blog/postgresql-rest-api-benchmark-2026.html#important-note-json-and-array-type-handling","1280":"/blog/postgresql-rest-api-benchmark-2026.html#conclusion","1281":"/blog/postgresql-rest-api-benchmark-2026.html#lines-of-code-comparison","1282":"/blog/postgresql-rest-api-benchmark-2026.html#full-benchmark-results","1283":"/blog/postgresql-rest-api-benchmark-2026.html#summary-tables","1284":"/blog/postgresql-rest-api-benchmark-2026.html#data-type-serialization","1285":"/blog/postgresql-rest-api-benchmark-2026.html#new-scenarios","1286":"/blog/postgresql-rest-api-benchmark-2026.html#data-type-serialization-tests","1287":"/blog/postgresql-rest-api-benchmark-2026.html#_1-virtual-user-1-record","1288":"/blog/postgresql-rest-api-benchmark-2026.html#_1-virtual-user-10-records","1289":"/blog/postgresql-rest-api-benchmark-2026.html#_100-virtual-users-1-record","1290":"/blog/postgresql-rest-api-benchmark-2026.html#_100-virtual-users-100-records","1291":"/blog/postgresql-rest-api-benchmark-2026.html#_100-virtual-users-500-records","1292":"/blog/postgresql-rest-api-benchmark-2026.html#minimal-baseline-pure-http-overhead","1293":"/blog/postgresql-rest-api-benchmark-2026.html#_100-virtual-users","1294":"/blog/postgresql-rest-api-benchmark-2026.html#post-body-parsing","1295":"/blog/postgresql-rest-api-benchmark-2026.html#_50-virtual-users-10-records","1296":"/blog/postgresql-rest-api-benchmark-2026.html#nested-json-serialization","1297":"/blog/postgresql-rest-api-benchmark-2026.html#_50-virtual-users-depth-1","1298":"/blog/postgresql-rest-api-benchmark-2026.html#large-payload","1299":"/blog/postgresql-rest-api-benchmark-2026.html#_25-virtual-users-100kb-payload","1300":"/blog/postgresql-rest-api-benchmark-2026.html#many-parameters-20-params","1301":"/blog/postgresql-rest-api-benchmark-2026.html#_50-virtual-users","1302":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#build-a-real-time-chat-app-with-postgresql-and-server-sent-events","1303":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#the-traditional-approach-complex-infrastructure","1304":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#the-npgsqlrest-approach-postgresql-is-your-real-time-server","1305":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#how-sse-works-in-npgsqlrest","1306":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#building-the-chat-step-by-step","1307":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#step-1-schema-setup","1308":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#step-2-login-function","1309":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#step-3-the-magic-send-message-with-sse","1310":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#step-4-message-history","1311":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#understanding-sse-scopes","1312":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#sse-scope-authorize","1313":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#sse-scope-authorize-roles-users","1314":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#sse-scope-matching","1315":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#sse-scope-all","1316":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#dynamic-scopes-with-raise-hint","1317":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#the-auto-generated-typescript-client","1318":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#the-frontend-minimal-code-required","1319":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#code-comparison-traditional-vs-npgsqlrest","1320":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#traditional-real-time-chat-architecture","1321":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#npgsqlrest-approach","1322":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#the-numbers","1323":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#when-to-use-sse-vs-websockets","1324":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#why-not-postgresql-listen-notify","1325":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#how-npgsqlrest-avoids-this-problem","1326":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#advanced-execution-id-correlation-as-soft-channels","1327":"/blog/real-time-chat-postgresql-sse-npgsqlrest.html#conclusion","1328":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#reverse-proxy-in-postgresql-gateway-to-external-services-with-npgsqlrest","1329":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#the-problem-connection-pool-exhaustion","1330":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#the-npgsqlrest-solution-proxy-mode","1331":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#passthrough-mode-zero-database-connections","1332":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#transform-mode-process-before-returning","1333":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#architecture-npgsqlrest-as-api-gateway","1334":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#building-the-ai-text-analysis-service","1335":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#the-upstream-ai-service","1336":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#postgresql-schema-caching-layer","1337":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#passthrough-proxy-health-check","1338":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#transform-proxy-summarization-with-caching","1339":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#full-analysis-complete-transform-example","1340":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#configuration","1341":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#proxy-response-parameters","1342":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#the-generated-typescript-client","1343":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#docker-bun-runtime-image","1344":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#use-cases-for-reverse-proxy","1345":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#api-gateway-pattern","1346":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#caching-expensive-operations","1347":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#data-enrichment","1348":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#authentication-context-forwarding","1349":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#the-numbers","1350":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#code-comparison","1351":"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html#summary-when-to-use-proxy-mode","1352":"/blog/secure-image-uploads-postgresql-typescript.html#secure-image-uploads-with-postgresql-file-system-large-objects-and-type-safe-typescript","1353":"/blog/secure-image-uploads-postgresql-typescript.html#storage-options","1354":"/blog/secure-image-uploads-postgresql-typescript.html#when-to-use-each-strategy","1355":"/blog/secure-image-uploads-postgresql-typescript.html#step-1-create-the-schema","1356":"/blog/secure-image-uploads-postgresql-typescript.html#step-2-configure-upload-handlers","1357":"/blog/secure-image-uploads-postgresql-typescript.html#step-3-create-the-upload-function","1358":"/blog/secure-image-uploads-postgresql-typescript.html#step-4-add-the-upload-annotation","1359":"/blog/secure-image-uploads-postgresql-typescript.html#how-the-metadata-works","1360":"/blog/secure-image-uploads-postgresql-typescript.html#when-uploads-fail","1361":"/blog/secure-image-uploads-postgresql-typescript.html#step-5-use-the-generated-client","1362":"/blog/secure-image-uploads-postgresql-typescript.html#step-6-serve-images-from-large-objects","1363":"/blog/secure-image-uploads-postgresql-typescript.html#performance-large-objects-vs-file-system","1364":"/blog/secure-image-uploads-postgresql-typescript.html#displaying-images","1365":"/blog/secure-image-uploads-postgresql-typescript.html#backup-advantage","1366":"/blog/secure-image-uploads-postgresql-typescript.html#traditional-approach-comparison","1367":"/blog/secure-image-uploads-postgresql-typescript.html#summary","1368":"/blog/sql-file-source-rest-api-from-plain-sql.html#sql-file-source-rest-endpoints-from-plain-sql-files","1369":"/blog/sql-file-source-rest-api-from-plain-sql.html#the-simplest-endpoint","1370":"/blog/sql-file-source-rest-api-from-plain-sql.html#multi-command-multiple-queries-in-one-request","1371":"/blog/sql-file-source-rest-api-from-plain-sql.html#authentication-login-logout-who-am-i","1372":"/blog/sql-file-source-rest-api-from-plain-sql.html#real-time-chat-with-sse","1373":"/blog/sql-file-source-rest-api-from-plain-sql.html#csv-export-with-basic-auth","1374":"/blog/sql-file-source-rest-api-from-plain-sql.html#dynamic-excel-output","1375":"/blog/sql-file-source-rest-api-from-plain-sql.html#nested-custom-types","1376":"/blog/sql-file-source-rest-api-from-plain-sql.html#external-api-calls","1377":"/blog/sql-file-source-rest-api-from-plain-sql.html#the-important-part","1378":"/blog/sql-file-source-rest-api-from-plain-sql.html#sql-files-vs-routines-when-to-use-which","1379":"/blog/sql-file-source-rest-api-from-plain-sql.html#what-came-after-this-post","1380":"/blog/sql-file-source-rest-api-from-plain-sql.html#get-started","1381":"/blog/the-backend-that-writes-itself-presentation.html#the-backend-that-writes-itself","1382":"/blog/the-backend-that-writes-itself-presentation.html#the-narration","1383":"/blog/the-backend-that-writes-itself-presentation.html#where-to-go-next","1384":"/blog/sql-rest-api.html#sql-rest-api","1385":"/blog/sql-rest-api.html#introduction","1386":"/blog/sql-rest-api.html#sql-script-files-as-rest-api-endpoints","1387":"/blog/sql-rest-api.html#sql-files-vs-routines","1388":"/blog/sql-rest-api.html#_1-no-migrations","1389":"/blog/sql-rest-api.html#_2-no-comment-on-statements","1390":"/blog/sql-rest-api.html#_3-mapping-by-position-vs-no-mapping-at-all","1391":"/blog/sql-rest-api.html#_4-multiple-result-sets","1392":"/blog/sql-rest-api.html#_5-named-parameters","1393":"/blog/sql-rest-api.html#_6-testability","1394":"/blog/sql-rest-api.html#_7-complex-logic","1395":"/blog/sql-rest-api.html#_1-parameters-are-not-supported-in-do-blocks","1396":"/blog/sql-rest-api.html#_2-do-blocks-can-t-return-result-sets","1397":"/blog/sql-rest-api.html#other-features-in-v3-12-0","1398":"/blog/sql-rest-api.html#self-referencing-endpoints","1399":"/blog/sql-rest-api.html#future-improvements","1400":"/blog/sql-rest-api.html#ai-tools","1401":"/blog/sql-rest-api.html#_1-ai-tools-with-npgsqlrest","1402":"/blog/sql-rest-api.html#_2-ai-tools-in-npgsqlrest-development","1403":"/blog/sql-rest-api.html#philosophy-of-npgsqlrest","1404":"/blog/sql-rest-api.html#wrap-it-up-chapter","1405":"/blog/the-power-of-simplicity.html#the-power-of-simplicity","1406":"/blog/typescript-codegen-walkthrough.html#from-sql-to-type-safe-typescript-a-walkthrough-of-npgsqlrest-s-code-generator","1407":"/blog/typescript-codegen-walkthrough.html#the-pipeline","1408":"/blog/typescript-codegen-walkthrough.html#a-minimal-example","1409":"/blog/typescript-codegen-walkthrough.html#end-to-end-type-safety-in-action","1410":"/blog/typescript-codegen-walkthrough.html#uploads-when-the-generated-wrapper-isn-t-just-fetch","1411":"/blog/typescript-codegen-walkthrough.html#per-endpoint-control-with-tsclient-annotations","1412":"/blog/typescript-codegen-walkthrough.html#disable-generation-binary-endpoints","1413":"/blog/typescript-codegen-walkthrough.html#url-only-browser-navigation-endpoints","1414":"/blog/typescript-codegen-walkthrough.html#module-grouping-logical-bundles-across-schemas","1415":"/blog/typescript-codegen-walkthrough.html#other-per-endpoint-toggles","1416":"/blog/typescript-codegen-walkthrough.html#scaling-up-real-world-configuration","1417":"/blog/typescript-codegen-walkthrough.html#real-world-workflow-dev-codegen-prod-no-codegen","1418":"/blog/typescript-codegen-walkthrough.html#two-processes-one-tight-loop","1419":"/blog/typescript-codegen-walkthrough.html#managing-change","1420":"/blog/typescript-codegen-walkthrough.html#production-no-codegen-just-the-server","1421":"/blog/typescript-codegen-walkthrough.html#what-this-means-in-practice","1422":"/blog/typescript-codegen-walkthrough.html#workflow-summary","1423":"/blog/web-scraping-postgresql-http-types-xml.html#web-scraping-with-postgresql-http-types-xml-functions","1424":"/blog/web-scraping-postgresql-http-types-xml.html#the-recipe","1425":"/blog/web-scraping-postgresql-http-types-xml.html#example-17-average-book-price","1426":"/blog/web-scraping-postgresql-http-types-xml.html#fetch-—-the-http-custom-type","1427":"/blog/web-scraping-postgresql-http-types-xml.html#parse-—-regex-to-isolate-xpath-to-read","1428":"/blog/web-scraping-postgresql-http-types-xml.html#why-regex-and-xpath","1429":"/blog/web-scraping-postgresql-http-types-xml.html#example-16-best-value-laptop","1430":"/blog/web-scraping-postgresql-http-types-xml.html#be-a-good-citizen-cache-the-page","1431":"/blog/web-scraping-postgresql-http-types-xml.html#a-different-split-fetch-in-sql-parse-in-a-service","1432":"/blog/web-scraping-postgresql-http-types-xml.html#when-this-works-and-when-it-doesn-t","1433":"/blog/web-scraping-postgresql-http-types-xml.html#try-it","1434":"/blog/web-scraping-postgresql-http-types-xml.html#related","1435":"/blog/what-have-stored-procedures-ever-done-for-us.html#what-have-postgresql-functions-ever-done-for-us","1436":"/blog/what-have-stored-procedures-ever-done-for-us.html#type-safety","1437":"/blog/what-have-stored-procedures-ever-done-for-us.html#real-encapsulation","1438":"/blog/what-have-stored-procedures-ever-done-for-us.html#zero-downtime","1439":"/blog/what-have-stored-procedures-ever-done-for-us.html#performance","1440":"/blog/what-have-stored-procedures-ever-done-for-us.html#race-conditions-minimized","1441":"/blog/what-have-stored-procedures-ever-done-for-us.html#security","1442":"/blog/what-have-stored-procedures-ever-done-for-us.html#a-short-test-loop","1443":"/blog/what-have-stored-procedures-ever-done-for-us.html#so-ddd-developers","1444":"/config/auth.html#authentication","1445":"/config/auth.html#overview","1446":"/config/auth.html#cookie-authentication","1447":"/config/auth.html#cookie-settings-reference","1448":"/config/auth.html#cookie-security","1449":"/config/auth.html#cross-origin-cookies-new-in-3-15-0","1450":"/config/auth.html#microsoft-bearer-token-authentication","1451":"/config/auth.html#bearer-token-settings-reference","1452":"/config/auth.html#token-refresh","1453":"/config/auth.html#jwt-authentication","1454":"/config/auth.html#jwt-settings-reference","1455":"/config/auth.html#login-response","1456":"/config/auth.html#token-refresh-1","1457":"/config/auth.html#jwt-vs-microsoft-bearer-token","1458":"/config/auth.html#additional-authentication-schemes","1459":"/config/auth.html#per-type-override-fields","1460":"/config/auth.html#validation-at-startup-fail-fast","1461":"/config/auth.html#complete-examples","1462":"/config/auth.html#cookie-authentication-1","1463":"/config/auth.html#jwt-authentication-1","1464":"/config/auth.html#combined-authentication","1465":"/config/auth.html#related","1466":"/config/auth.html#next-steps","1467":"/config/auth.html#see-also","1468":"/config/authentication-options.html#authentication-options","1469":"/config/authentication-options.html#overview","1470":"/config/authentication-options.html#general-settings","1471":"/config/authentication-options.html#login-response-columns","1472":"/config/authentication-options.html#password-handling","1473":"/config/authentication-options.html#password-verification-command-parameters","1474":"/config/authentication-options.html#default-claim-types","1475":"/config/authentication-options.html#user-context-settings","1476":"/config/authentication-options.html#default-contextkeyclaimsmapping","1477":"/config/authentication-options.html#user-parameters-settings","1478":"/config/authentication-options.html#default-parameternameclaimsmapping","1479":"/config/authentication-options.html#login-and-logout-paths","1480":"/config/authentication-options.html#login-command-convention","1481":"/config/authentication-options.html#logout-command-convention","1482":"/config/authentication-options.html#basic-authentication","1483":"/config/authentication-options.html#complete-example","1484":"/config/authentication-options.html#related","1485":"/config/authentication-options.html#next-steps","1486":"/config/authentication-options.html#see-also","1487":"/config/antiforgery.html#antiforgery","1488":"/config/antiforgery.html#overview","1489":"/config/antiforgery.html#settings-reference","1490":"/config/antiforgery.html#token-submission","1491":"/config/antiforgery.html#form-field","1492":"/config/antiforgery.html#http-header","1493":"/config/antiforgery.html#x-frame-options-header","1494":"/config/antiforgery.html#example-configuration","1495":"/config/antiforgery.html#related","1496":"/config/antiforgery.html#next-steps","1497":"/config/basic-auth-config.html#basic-auth-configuration","1498":"/config/basic-auth-config.html#overview","1499":"/config/basic-auth-config.html#settings","1500":"/config/basic-auth-config.html#ssl-requirement-values","1501":"/config/basic-auth-config.html#challenge-command-parameters","1502":"/config/basic-auth-config.html#static-users-example","1503":"/config/basic-auth-config.html#database-authentication-example","1504":"/config/basic-auth-config.html#challenge-function-example","1505":"/config/basic-auth-config.html#complete-example","1506":"/config/basic-auth-config.html#related","1507":"/config/basic-auth-config.html#next-steps","1508":"/config/basic-auth-config.html#see-also","1509":"/config/cache-options.html#cache-options","1510":"/config/cache-options.html#overview","1511":"/config/cache-options.html#settings-reference","1512":"/config/cache-options.html#cache-types","1513":"/config/cache-options.html#memory-cache","1514":"/config/cache-options.html#redis-cache","1515":"/config/cache-options.html#hybrid-cache","1516":"/config/cache-options.html#cache-key-hashing","1517":"/config/cache-options.html#caching-set-returning-functions","1518":"/config/cache-options.html#cache-invalidation-endpoints","1519":"/config/cache-options.html#cache-profiles","1520":"/config/cache-options.html#overview-1","1521":"/config/cache-options.html#profile-fields","1522":"/config/cache-options.html#backend-pooling","1523":"/config/cache-options.html#when-rules","1524":"/config/cache-options.html#pattern-skip-on-condition","1525":"/config/cache-options.html#pattern-dynamic-ttl","1526":"/config/cache-options.html#pattern-array-of-values","1527":"/config/cache-options.html#validation","1528":"/config/cache-options.html#connection-pooler-note","1529":"/config/cache-options.html#complete-example","1530":"/config/cache-options.html#routine-annotations","1531":"/config/cache-options.html#cached","1532":"/config/cache-options.html#cache-expires-cache-expires-in","1533":"/config/cache-options.html#cache-profile","1534":"/config/cache-options.html#example-configuration","1535":"/config/cache-options.html#related","1536":"/config/cache-options.html#next-steps","1537":"/config/cache-options.html#see-also","1538":"/config/claims-mapping.html#claims-mapping","1539":"/config/claims-mapping.html#overview","1540":"/config/claims-mapping.html#user-context-postgresql-context-variables","1541":"/config/claims-mapping.html#default-context-mapping","1542":"/config/claims-mapping.html#custom-context-mapping-example","1543":"/config/claims-mapping.html#access-in-postgresql","1544":"/config/claims-mapping.html#user-parameters","1545":"/config/claims-mapping.html#default-parameter-mapping","1546":"/config/claims-mapping.html#custom-parameter-mapping-example","1547":"/config/claims-mapping.html#example-function-using-parameters","1548":"/config/claims-mapping.html#complete-example","1549":"/config/claims-mapping.html#related","1550":"/config/claims-mapping.html#next-steps","1551":"/config/claims-mapping.html#see-also","1552":"/config/codegen.html#code-generation","1553":"/config/codegen.html#overview","1554":"/config/codegen.html#general-settings","1555":"/config/codegen.html#host-configuration","1556":"/config/codegen.html#comment-headers","1557":"/config/codegen.html#comment-header-styles","1558":"/config/codegen.html#response-options","1559":"/config/codegen.html#type-generation","1560":"/config/codegen.html#import-configuration","1561":"/config/codegen.html#function-parameters","1562":"/config/codegen.html#skip-options","1563":"/config/codegen.html#export-options","1564":"/config/codegen.html#headers-and-security","1565":"/config/codegen.html#file-headers","1566":"/config/codegen.html#what-gets-generated","1567":"/config/codegen.html#default-function-shape","1568":"/config/codegen.html#includestatuscode-false-—-direct-response","1569":"/config/codegen.html#omitautomaticparameters-true","1570":"/config/codegen.html#createseparatetypefile-true-—-type-only-files","1571":"/config/codegen.html#exporttypes-true-—-importable-interfaces","1572":"/config/codegen.html#exporturls-true-—-url-constants","1573":"/config/codegen.html#exporteventsources-true-—-sse-helpers","1574":"/config/codegen.html#importbaseurlfrom-importparsequeryfrom","1575":"/config/codegen.html#path-parameters","1576":"/config/codegen.html#useroutinenameinsteadofendpoint-true","1577":"/config/codegen.html#byschema-true-—-one-file-per-schema","1578":"/config/codegen.html#example-configurations","1579":"/config/codegen.html#minimal-examples-repo-style","1580":"/config/codegen.html#single-javascript-file-no-types","1581":"/config/codegen.html#production-sveltekit-vite-setup","1582":"/config/codegen.html#with-custom-headers-and-imports","1583":"/config/codegen.html#related","1584":"/config/codegen.html#next-steps","1585":"/config/codegen.html#see-also","1586":"/config/command-retry.html#command-retry","1587":"/config/command-retry.html#overview","1588":"/config/command-retry.html#settings-reference","1589":"/config/command-retry.html#strategy-settings","1590":"/config/command-retry.html#retry-sequence","1591":"/config/command-retry.html#default-error-codes","1592":"/config/command-retry.html#serialization-failures","1593":"/config/command-retry.html#connection-issues-class-08","1594":"/config/command-retry.html#resource-constraints-class-53","1595":"/config/command-retry.html#system-errors-class-57-58","1596":"/config/command-retry.html#lock-acquisition-issues-class-55","1597":"/config/command-retry.html#multiple-strategies","1598":"/config/command-retry.html#example-configuration","1599":"/config/command-retry.html#using-strategies-in-annotations","1600":"/config/command-retry.html#related","1601":"/config/command-retry.html#next-steps","1602":"/config/command-retry.html#see-also","1603":"/config/config-section.html#config-section","1604":"/config/config-section.html#settings-reference","1605":"/config/config-section.html#placeholder-forms-optional-and-required-3-17-0","1606":"/config/config-section.html#environment-variable-override","1607":"/config/config-section.html#environment-variable-parsing","1608":"/config/config-section.html#loading-from-env-file","1609":"/config/config-section.html#configuration-key-validation","1610":"/config/config-section.html#related","1611":"/config/config-section.html#next-steps","1612":"/config/connection.html#connection-settings","1613":"/config/connection.html#connection-strings","1614":"/config/connection.html#multiple-connections","1615":"/config/connection.html#using-environment-variables","1616":"/config/connection.html#connection-string-parameters","1617":"/config/connection.html#connection-settings-1","1618":"/config/connection.html#settings-reference","1619":"/config/connection.html#application-name-in-connection","1620":"/config/connection.html#json-application-name","1621":"/config/connection.html#connection-testing","1622":"/config/connection.html#retry-options","1623":"/config/connection.html#retry-settings-reference","1624":"/config/connection.html#default-error-codes","1625":"/config/connection.html#custom-retry-configuration","1626":"/config/connection.html#multi-host-connection-support","1627":"/config/connection.html#multi-host-connection-strings","1628":"/config/connection.html#target-session-attributes","1629":"/config/connection.html#multi-host-example","1630":"/config/connection.html#npgsqlrest-connection-options","1631":"/config/connection.html#npgsqlrest-connection-settings-reference","1632":"/config/connection.html#using-multiple-connections","1633":"/config/connection.html#complete-example","1634":"/config/connection.html#related","1635":"/config/connection.html#next-steps","1636":"/config/connection.html#see-also","1637":"/config/cors.html#cors","1638":"/config/cors.html#overview","1639":"/config/cors.html#settings-reference","1640":"/config/cors.html#allowed-origins","1641":"/config/cors.html#allow-all-origins","1642":"/config/cors.html#allowed-methods","1643":"/config/cors.html#allowed-headers","1644":"/config/cors.html#credentials","1645":"/config/cors.html#preflight-caching","1646":"/config/cors.html#example-configuration","1647":"/config/cors.html#related","1648":"/config/cors.html#next-steps","1649":"/config/data-protection.html#data-protection","1650":"/config/data-protection.html#overview","1651":"/config/data-protection.html#settings-reference","1652":"/config/data-protection.html#storage-options","1653":"/config/data-protection.html#default-storage","1654":"/config/data-protection.html#file-system-storage","1655":"/config/data-protection.html#database-storage","1656":"/config/data-protection.html#encryption-algorithms","1657":"/config/data-protection.html#validation-algorithms","1658":"/config/data-protection.html#application-name-scope","1659":"/config/data-protection.html#key-encryption-options","1660":"/config/data-protection.html#no-encryption-default","1661":"/config/data-protection.html#certificate-encryption","1662":"/config/data-protection.html#dpapi-encryption-windows-only","1663":"/config/data-protection.html#complete-example","1664":"/config/data-protection.html#column-encryption-with-annotations","1665":"/config/data-protection.html#related","1666":"/config/data-protection.html#next-steps","1667":"/config/data-protection.html#see-also","1668":"/config/error-handling.html#error-handling","1669":"/config/error-handling.html#overview","1670":"/config/error-handling.html#settings-reference","1671":"/config/error-handling.html#error-mapping-object","1672":"/config/error-handling.html#timeout-error-mapping","1673":"/config/error-handling.html#error-code-policies","1674":"/config/error-handling.html#default-error-code-mappings","1675":"/config/error-handling.html#response-fields","1676":"/config/error-handling.html#type-url","1677":"/config/error-handling.html#traceid","1678":"/config/error-handling.html#example-configuration","1679":"/config/error-handling.html#related","1680":"/config/error-handling.html#next-steps","1681":"/config/error-handling.html#see-also","1682":"/config/external-auth.html#external-oauth-authentication","1683":"/config/external-auth.html#overview","1684":"/config/external-auth.html#settings-reference","1685":"/config/external-auth.html#signinhtmltemplate","1686":"/config/external-auth.html#login-command","1687":"/config/external-auth.html#parameters","1688":"/config/external-auth.html#result-set-conventions","1689":"/config/external-auth.html#example-login-command-function","1690":"/config/external-auth.html#oauth-providers","1691":"/config/external-auth.html#google","1692":"/config/external-auth.html#linkedin","1693":"/config/external-auth.html#github","1694":"/config/external-auth.html#microsoft","1695":"/config/external-auth.html#facebook","1696":"/config/external-auth.html#provider-settings-reference","1697":"/config/external-auth.html#custom-providers","1698":"/config/external-auth.html#complete-example","1699":"/config/external-auth.html#related","1700":"/config/external-auth.html#next-steps","1701":"/config/forwarded-headers.html#forwarded-headers","1702":"/config/forwarded-headers.html#overview","1703":"/config/forwarded-headers.html#settings-reference","1704":"/config/forwarded-headers.html#why-forwarded-headers-matter","1705":"/config/forwarded-headers.html#processed-headers","1706":"/config/forwarded-headers.html#forward-limit","1707":"/config/forwarded-headers.html#known-proxies","1708":"/config/forwarded-headers.html#known-networks","1709":"/config/forwarded-headers.html#allowed-hosts","1710":"/config/forwarded-headers.html#example-configurations","1711":"/config/forwarded-headers.html#behind-nginx","1712":"/config/forwarded-headers.html#aws-alb-elb","1713":"/config/forwarded-headers.html#azure-app-service","1714":"/config/forwarded-headers.html#cloudflare-origin-server","1715":"/config/forwarded-headers.html#docker-kubernetes-with-internal-load-balancer","1716":"/config/forwarded-headers.html#development-trust-all","1717":"/config/forwarded-headers.html#security-considerations","1718":"/config/forwarded-headers.html#related","1719":"/config/forwarded-headers.html#next-steps","1720":"/config/http-client.html#http-client-options","1721":"/config/http-client.html#overview","1722":"/config/http-client.html#settings-reference","1723":"/config/http-client.html#how-http-types-work","1724":"/config/http-client.html#creating-an-http-type","1725":"/config/http-client.html#step-1-create-a-composite-type","1726":"/config/http-client.html#step-2-add-http-definition-comment","1727":"/config/http-client.html#step-3-use-in-a-function","1728":"/config/http-client.html#http-definition-format","1729":"/config/http-client.html#supported-methods","1730":"/config/http-client.html#example-definitions","1731":"/config/http-client.html#timeout-directives","1732":"/config/http-client.html#response-fields","1733":"/config/http-client.html#placeholder-substitution","1734":"/config/http-client.html#complete-example","1735":"/config/http-client.html#configuration","1736":"/config/http-client.html#sql-setup","1737":"/config/http-client.html#usage","1738":"/config/http-client.html#resolved-parameter-expressions","1739":"/config/http-client.html#retry-logic","1740":"/config/http-client.html#syntax","1741":"/config/http-client.html#behavior","1742":"/config/http-client.html#example","1743":"/config/http-client.html#response-caching","1744":"/config/http-client.html#self-referencing-calls-relative-paths","1745":"/config/http-client.html#parallel-query-composition","1746":"/config/http-client.html#zero-http-overhead","1747":"/config/http-client.html#internal-only-endpoints","1748":"/config/http-client.html#related","1749":"/config/http-client.html#next-steps","1750":"/config/http-client.html#see-also","1751":"/config/http-files.html#http-file-options","1752":"/config/http-files.html#overview","1753":"/config/http-files.html#settings-reference","1754":"/config/http-files.html#generation-options","1755":"/config/http-files.html#comment-header-styles","1756":"/config/http-files.html#file-mode","1757":"/config/http-files.html#http-files","1758":"/config/http-files.html#example-configuration","1759":"/config/http-files.html#omitting-automatic-parameters","1760":"/config/http-files.html#related","1761":"/config/http-files.html#next-steps","1762":"/config/health-checks.html#health-checks","1763":"/config/health-checks.html#overview","1764":"/config/health-checks.html#settings-reference","1765":"/config/health-checks.html#health-check-types","1766":"/config/health-checks.html#main-health-health","1767":"/config/health-checks.html#readiness-probe-health-ready","1768":"/config/health-checks.html#liveness-probe-health-live","1769":"/config/health-checks.html#cache-duration","1770":"/config/health-checks.html#database-health-check","1771":"/config/health-checks.html#using-a-different-connection","1772":"/config/health-checks.html#kubernetes-integration","1773":"/config/health-checks.html#deployment-configuration","1774":"/config/health-checks.html#probe-behavior","1775":"/config/health-checks.html#docker-compose-health-check","1776":"/config/health-checks.html#custom-paths","1777":"/config/health-checks.html#example-configurations","1778":"/config/health-checks.html#basic-configuration","1779":"/config/health-checks.html#production-with-caching","1780":"/config/health-checks.html#api-gateway-integration","1781":"/config/health-checks.html#without-database-check","1782":"/config/health-checks.html#response-format","1783":"/config/health-checks.html#related","1784":"/config/health-checks.html#next-steps","1785":"/config/#configuration-reference","1786":"/config/#reference-sections","1787":"/config/#core-settings","1788":"/config/#security","1789":"/config/#features","1790":"/config/#performance","1791":"/config/#infrastructure","1792":"/config/latest.html#latest-default-configuration-reference","1793":"/config/latest.html#related","1794":"/config/latest.html#core-settings","1795":"/config/latest.html#security","1796":"/config/latest.html#features","1797":"/config/latest.html#performance","1798":"/config/latest.html#infrastructure","1799":"/config/logging.html#logging","1800":"/config/logging.html#overview","1801":"/config/logging.html#log-levels","1802":"/config/logging.html#minimal-levels","1803":"/config/logging.html#console-output","1804":"/config/logging.html#file-output","1805":"/config/logging.html#postgresql-output","1806":"/config/logging.html#postgresql-command-parameters","1807":"/config/logging.html#opentelemetry-output","1808":"/config/logging.html#resource-attributes","1809":"/config/logging.html#output-template","1810":"/config/logging.html#complete-example","1811":"/config/logging.html#related","1812":"/config/logging.html#next-steps","1813":"/config/mcp.html#mcp-options","1814":"/config/mcp.html#overview","1815":"/config/mcp.html#options","1816":"/config/mcp.html#enabled","1817":"/config/mcp.html#urlpath","1818":"/config/mcp.html#servername","1819":"/config/mcp.html#serverversion","1820":"/config/mcp.html#instructions","1821":"/config/mcp.html#tooldescriptionsuffix","1822":"/config/mcp.html#ratelimiterpolicy","1823":"/config/mcp.html#allowedorigins","1824":"/config/mcp.html#how-it-works","1825":"/config/mcp.html#authentication-—-oauth-2-1-resource-server","1826":"/config/mcp.html#authorization-options","1827":"/config/mcp.html#requireauthorization","1828":"/config/mcp.html#authorizationservers","1829":"/config/mcp.html#scopessupported","1830":"/config/mcp.html#audience","1831":"/config/mcp.html#protectedresourcemetadatapath","1832":"/config/mcp.html#filtertoolsbyrole","1833":"/config/mcp.html#protected-resource-metadata-rfc-9728","1834":"/config/mcp.html#related","1835":"/config/npgsqlrest.html#npgsqlrest-options","1836":"/config/npgsqlrest.html#overview","1837":"/config/npgsqlrest.html#connection-settings","1838":"/config/npgsqlrest.html#schema-and-name-filtering","1839":"/config/npgsqlrest.html#filtering-examples","1840":"/config/npgsqlrest.html#comments-mode","1841":"/config/npgsqlrest.html#url-and-naming","1842":"/config/npgsqlrest.html#url-examples","1843":"/config/npgsqlrest.html#authorization","1844":"/config/npgsqlrest.html#logging","1845":"/config/npgsqlrest.html#notice-event-modes","1846":"/config/npgsqlrest.html#http-method-and-parameters","1847":"/config/npgsqlrest.html#default-behavior","1848":"/config/npgsqlrest.html#request-headers","1849":"/config/npgsqlrest.html#request-headers-modes","1850":"/config/npgsqlrest.html#connection-pooler-compatibility","1851":"/config/npgsqlrest.html#wrapintransaction","1852":"/config/npgsqlrest.html#beforeroutinecommands","1853":"/config/npgsqlrest.html#null-handling","1854":"/config/npgsqlrest.html#querystringnullhandling-values","1855":"/config/npgsqlrest.html#textresponsenullhandling-values","1856":"/config/npgsqlrest.html#json-timestamp-handling","1857":"/config/npgsqlrest.html#server-sent-events","1858":"/config/npgsqlrest.html#notice-level-behavior","1859":"/config/npgsqlrest.html#example-configuration","1860":"/config/npgsqlrest.html#related","1861":"/config/npgsqlrest.html#unbound-raise-warning","1862":"/config/npgsqlrest.html#environment-variables-in-annotation-values","1863":"/config/npgsqlrest.html#complete-example","1864":"/config/npgsqlrest.html#related-1","1865":"/config/npgsqlrest.html#next-steps","1866":"/config/passkey-auth.html#passkey-authentication","1867":"/config/passkey-auth.html#overview","1868":"/config/passkey-auth.html#how-it-works","1869":"/config/passkey-auth.html#three-authentication-flows","1870":"/config/passkey-auth.html#_1-registration-new-user-with-passkey","1871":"/config/passkey-auth.html#_2-add-passkey-existing-user","1872":"/config/passkey-auth.html#_3-login","1873":"/config/passkey-auth.html#settings-reference","1874":"/config/passkey-auth.html#general-settings","1875":"/config/passkey-auth.html#relying-party-settings","1876":"/config/passkey-auth.html#endpoint-paths","1877":"/config/passkey-auth.html#webauthn-settings","1878":"/config/passkey-auth.html#userverificationrequirement","1879":"/config/passkey-auth.html#residentkeyrequirement","1880":"/config/passkey-auth.html#attestationconveyance","1881":"/config/passkey-auth.html#sql-commands-reference","1882":"/config/passkey-auth.html#challengeaddexistingusercommand","1883":"/config/passkey-auth.html#challengeregistrationcommand","1884":"/config/passkey-auth.html#challengeauthenticationcommand","1885":"/config/passkey-auth.html#verifychallengecommand","1886":"/config/passkey-auth.html#authenticatedatacommand","1887":"/config/passkey-auth.html#completeaddexistingusercommand-completeregistrationcommand","1888":"/config/passkey-auth.html#completeauthenticatecommand","1889":"/config/passkey-auth.html#column-name-configuration","1890":"/config/passkey-auth.html#analytics-data","1891":"/config/passkey-auth.html#complete-example","1892":"/config/passkey-auth.html#minimal-configuration","1893":"/config/passkey-auth.html#full-configuration","1894":"/config/passkey-auth.html#related","1895":"/config/passkey-auth.html#next-steps","1896":"/config/openapi.html#openapi-options","1897":"/config/openapi.html#overview","1898":"/config/openapi.html#settings-reference","1899":"/config/openapi.html#document-info","1900":"/config/openapi.html#servers","1901":"/config/openapi.html#security-schemes","1902":"/config/openapi.html#bearer-token-authentication","1903":"/config/openapi.html#basic-authentication","1904":"/config/openapi.html#cookie-authentication","1905":"/config/openapi.html#api-key-in-header","1906":"/config/openapi.html#security-scheme-settings","1907":"/config/openapi.html#complete-example","1908":"/config/openapi.html#filters-new-in-3-15-0","1909":"/config/openapi.html#schema-and-name-filters","1910":"/config/openapi.html#filter-order","1911":"/config/openapi.html#partner-facing-document-example","1912":"/config/openapi.html#omitting-automatic-parameters","1913":"/config/openapi.html#related","1914":"/config/openapi.html#next-steps","1915":"/config/proxy.html#proxy-options","1916":"/config/proxy.html#overview","1917":"/config/proxy.html#settings-reference","1918":"/config/proxy.html#response-parameter-names","1919":"/config/proxy.html#proxy-modes","1920":"/config/proxy.html#passthrough-mode","1921":"/config/proxy.html#transform-mode","1922":"/config/proxy.html#response-parameters","1923":"/config/proxy.html#automatic-parameter-forwarding","1924":"/config/proxy.html#placement-follows-the-endpoint-shape-not-the-http-verb","1925":"/config/proxy.html#query-string-length-guard","1926":"/config/proxy.html#http-headers-user-context","1927":"/config/proxy.html#upload-forwarding","1928":"/config/proxy.html#key-features","1929":"/config/proxy.html#self-referencing-calls-relative-paths","1930":"/config/proxy.html#internal-only-endpoints","1931":"/config/proxy.html#complete-example","1932":"/config/proxy.html#related","1933":"/config/proxy.html#next-steps","1934":"/config/proxy.html#see-also","1935":"/config/response-compression.html#response-compression","1936":"/config/response-compression.html#overview","1937":"/config/response-compression.html#settings-reference","1938":"/config/response-compression.html#compression-levels","1939":"/config/response-compression.html#compression-algorithms","1940":"/config/response-compression.html#brotli","1941":"/config/response-compression.html#gzip-fallback","1942":"/config/response-compression.html#https-compression","1943":"/config/response-compression.html#default-mime-types","1944":"/config/response-compression.html#example-configuration","1945":"/config/response-compression.html#related","1946":"/config/response-compression.html#next-steps","1947":"/config/rate-limiter.html#rate-limiter","1948":"/config/rate-limiter.html#overview","1949":"/config/rate-limiter.html#settings-reference","1950":"/config/rate-limiter.html#policy-types","1951":"/config/rate-limiter.html#fixed-window-policy","1952":"/config/rate-limiter.html#sliding-window-policy","1953":"/config/rate-limiter.html#token-bucket-policy","1954":"/config/rate-limiter.html#concurrency-policy","1955":"/config/rate-limiter.html#per-user-rate-limiting-partition","1956":"/config/rate-limiter.html#partition-fields","1957":"/config/rate-limiter.html#source-types","1958":"/config/rate-limiter.html#per-policy-status-code-and-message","1959":"/config/rate-limiter.html#ready-to-use-login-throttle-policy","1960":"/config/rate-limiter.html#complete-example","1961":"/config/rate-limiter.html#rate-limiting-scope","1962":"/config/rate-limiter.html#related","1963":"/config/rate-limiter.html#next-steps","1964":"/config/rate-limiter.html#see-also","1965":"/config/routine-options.html#routine-options","1966":"/config/routine-options.html#overview","1967":"/config/routine-options.html#settings","1968":"/config/routine-options.html#custom-type-parameter-separator","1969":"/config/routine-options.html#language-filtering","1970":"/config/routine-options.html#include-specific-languages","1971":"/config/routine-options.html#exclude-additional-languages","1972":"/config/routine-options.html#common-postgresql-languages","1973":"/config/routine-options.html#nested-json-for-composite-types","1974":"/config/routine-options.html#resolve-nested-composite-types","1975":"/config/routine-options.html#complete-example","1976":"/config/routine-options.html#related","1977":"/config/routine-options.html#next-steps","1978":"/config/server.html#server-ssl-settings","1979":"/config/server.html#ssl-configuration","1980":"/config/server.html#settings-reference","1981":"/config/server.html#enabling-https","1982":"/config/server.html#https-redirection","1983":"/config/server.html#http-strict-transport-security-hsts","1984":"/config/server.html#kestrel-configuration","1985":"/config/server.html#certificate-configuration","1986":"/config/server.html#pfx-file","1987":"/config/server.html#pem-crt-with-key-file","1988":"/config/server.html#certificate-store-windows","1989":"/config/server.html#default-certificate","1990":"/config/server.html#connection-limits","1991":"/config/server.html#limits-reference","1992":"/config/server.html#http-2-settings","1993":"/config/server.html#http-3-settings","1994":"/config/server.html#additional-kestrel-options","1995":"/config/server.html#complete-example","1996":"/config/server.html#related","1997":"/config/server.html#next-steps","1998":"/config/sql-file-source.html#sql-file-source","1999":"/config/sql-file-source.html#overview","2000":"/config/sql-file-source.html#settings","2001":"/config/sql-file-source.html#enabled","2002":"/config/sql-file-source.html#filepattern","2003":"/config/sql-file-source.html#examples","2004":"/config/sql-file-source.html#commentsmode","2005":"/config/sql-file-source.html#commentscope","2006":"/config/sql-file-source.html#example","2007":"/config/sql-file-source.html#errormode","2008":"/config/sql-file-source.html#resultprefix","2009":"/config/sql-file-source.html#unnamedsinglecolumnset","2010":"/config/sql-file-source.html#nestedjsonforcompositetypes","2011":"/config/sql-file-source.html#logcommandtext","2012":"/config/sql-file-source.html#quick-start-example","2013":"/config/sql-file-source.html#related","2014":"/config/security-headers.html#security-headers","2015":"/config/security-headers.html#overview","2016":"/config/security-headers.html#settings-reference","2017":"/config/security-headers.html#x-content-type-options","2018":"/config/security-headers.html#x-frame-options","2019":"/config/security-headers.html#referrer-policy","2020":"/config/security-headers.html#content-security-policy","2021":"/config/security-headers.html#permissions-policy","2022":"/config/security-headers.html#cross-origin-policies","2023":"/config/security-headers.html#cross-origin-opener-policy","2024":"/config/security-headers.html#cross-origin-embedder-policy","2025":"/config/security-headers.html#cross-origin-resource-policy","2026":"/config/security-headers.html#example-configurations","2027":"/config/security-headers.html#basic-security-recommended-starting-point","2028":"/config/security-headers.html#api-only-application","2029":"/config/security-headers.html#full-protection-with-csp","2030":"/config/security-headers.html#related","2031":"/config/security-headers.html#next-steps","2032":"/config/static-files.html#static-files","2033":"/config/static-files.html#overview","2034":"/config/static-files.html#settings-reference","2035":"/config/static-files.html#authorization","2036":"/config/static-files.html#path-patterns","2037":"/config/static-files.html#content-parsing","2038":"/config/static-files.html#parse-content-settings-reference","2039":"/config/static-files.html#tag-replacement","2040":"/config/static-files.html#environment-variable-injection","2041":"/config/static-files.html#default-headers","2042":"/config/static-files.html#example-configuration","2043":"/config/static-files.html#related","2044":"/config/static-files.html#next-steps","2045":"/config/stats.html#postgresql-stats","2046":"/config/stats.html#overview","2047":"/config/stats.html#settings-reference","2048":"/config/stats.html#available-endpoints","2049":"/config/stats.html#routines-stats-stats-routines","2050":"/config/stats.html#tables-stats-stats-tables","2051":"/config/stats.html#indexes-stats-stats-indexes","2052":"/config/stats.html#activity-stats-activity","2053":"/config/stats.html#output-formats","2054":"/config/stats.html#html-format-default","2055":"/config/stats.html#json-format","2056":"/config/stats.html#per-request-format-override","2057":"/config/stats.html#security","2058":"/config/stats.html#require-authentication","2059":"/config/stats.html#role-based-access","2060":"/config/stats.html#caching","2061":"/config/stats.html#rate-limiting","2062":"/config/stats.html#schema-filtering","2063":"/config/stats.html#using-a-different-connection","2064":"/config/stats.html#custom-paths","2065":"/config/stats.html#example-configurations","2066":"/config/stats.html#development-open-access","2067":"/config/stats.html#production-secured","2068":"/config/stats.html#monitoring-integration","2069":"/config/stats.html#limited-schema-access","2070":"/config/stats.html#related","2071":"/config/stats.html#next-steps","2072":"/config/table-format.html#table-format-options","2073":"/config/table-format.html#overview","2074":"/config/table-format.html#general-settings","2075":"/config/table-format.html#html-table-handler","2076":"/config/table-format.html#example","2077":"/config/table-format.html#excel-table-handler","2078":"/config/table-format.html#example-1","2079":"/config/table-format.html#per-endpoint-overrides","2080":"/config/table-format.html#complete-example","2081":"/config/table-format.html#related","2082":"/config/table-format.html#next-steps","2083":"/config/table-format.html#see-also","2084":"/config/thread-pool.html#thread-pool","2085":"/config/thread-pool.html#overview","2086":"/config/thread-pool.html#settings-reference","2087":"/config/thread-pool.html#worker-threads-vs-completion-port-threads","2088":"/config/thread-pool.html#when-to-configure","2089":"/config/thread-pool.html#example-configuration","2090":"/config/thread-pool.html#related","2091":"/config/thread-pool.html#next-steps","2092":"/config/test-runner.html#test-runner","2093":"/config/test-runner.html#overview","2094":"/config/test-runner.html#settings","2095":"/config/test-runner.html#filepattern","2096":"/config/test-runner.html#filter","2097":"/config/test-runner.html#tag-and-excludetag","2098":"/config/test-runner.html#connectionname","2099":"/config/test-runner.html#maxparallelism","2100":"/config/test-runner.html#failfast","2101":"/config/test-runner.html#pertesttimeout","2102":"/config/test-runner.html#junitoutput","2103":"/config/test-runner.html#keep","2104":"/config/test-runner.html#detailedreport","2105":"/config/test-runner.html#allowempty","2106":"/config/test-runner.html#watch-mode","2107":"/config/test-runner.html#coverage-and-coveragethreshold","2108":"/config/test-runner.html#loggername","2109":"/config/test-runner.html#responsetemptable","2110":"/config/test-runner.html#debugtable-—-inspect-responses-after-the-run","2111":"/config/test-runner.html#steps","2112":"/config/test-runner.html#setup-and-teardown","2113":"/config/test-runner.html#exit-codes","2114":"/config/test-runner.html#related","2115":"/config/top-level.html#top-level-settings","2116":"/config/top-level.html#application-settings","2117":"/config/top-level.html#settings-reference","2118":"/config/top-level.html#urls-configuration","2119":"/config/top-level.html#startup-message-placeholders","2120":"/config/top-level.html#related","2121":"/config/top-level.html#next-steps","2122":"/config/uploads.html#upload-options","2123":"/config/uploads.html#overview","2124":"/config/uploads.html#general-settings","2125":"/config/uploads.html#upload-handlers-common-settings","2126":"/config/uploads.html#large-object-handler","2127":"/config/uploads.html#file-system-handler","2128":"/config/uploads.html#csv-upload-handler","2129":"/config/uploads.html#csv-row-command-parameters","2130":"/config/uploads.html#excel-upload-handler","2131":"/config/uploads.html#excel-row-command-parameters","2132":"/config/uploads.html#complete-example","2133":"/config/uploads.html#related","2134":"/config/uploads.html#blog-posts","2135":"/config/uploads.html#next-steps","2136":"/config/uploads.html#see-also","2137":"/config/validation.html#validation-options","2138":"/config/validation.html#overview","2139":"/config/validation.html#settings-reference","2140":"/config/validation.html#validation-types","2141":"/config/validation.html#rule-properties","2142":"/config/validation.html#default-rules","2143":"/config/validation.html#adding-custom-rules","2144":"/config/validation.html#regex-pattern-rule","2145":"/config/validation.html#length-validation-rules","2146":"/config/validation.html#complete-example","2147":"/config/validation.html#usage-with-annotations","2148":"/config/validation.html#programmatic-configuration","2149":"/config/validation.html#behavior","2150":"/config/validation.html#related","2151":"/config/validation.html#next-steps","2152":"/config/validation.html#see-also","2153":"/config/watch.html#watch-mode","2154":"/config/watch.html#overview","2155":"/config/watch.html#enabled","2156":"/config/watch.html#databasepollinginterval","2157":"/config/watch.html#server-watch-behavior","2158":"/config/watch.html#test-watch-behavior","2159":"/config/watch.html#related","2160":"/examples/#examples","2161":"/examples/#prerequisites","2162":"/examples/#getting-started","2163":"/examples/#available-examples","2164":"/examples/#function-based-examples-routinesource","2165":"/examples/#sql-file-examples-sqlfilesource","2166":"/examples/#mcp-server-sqlfilesource","2167":"/examples/#sql-test-runner","2168":"/examples/#available-commands","2169":"/examples/#next-steps","2170":"/guide/authentication.html#authentication","2171":"/guide/authentication.html#the-big-picture","2172":"/guide/authentication.html#step-1-configure-an-authentication-scheme","2173":"/guide/authentication.html#cookie-the-simplest","2174":"/guide/authentication.html#bearer-token","2175":"/guide/authentication.html#jwt","2176":"/guide/authentication.html#step-2-write-a-login-endpoint","2177":"/guide/authentication.html#verifying-the-password","2178":"/guide/authentication.html#choosing-a-scheme","2179":"/guide/authentication.html#how-claims-work","2180":"/guide/authentication.html#claims-are-just-the-login-columns","2181":"/guide/authentication.html#identity-claims","2182":"/guide/authentication.html#accessing-claims-in-your-endpoints","2183":"/guide/authentication.html#as-function-parameters","2184":"/guide/authentication.html#as-postgresql-context-variables","2185":"/guide/authentication.html#as-template-placeholders","2186":"/guide/authentication.html#logging-out","2187":"/guide/authentication.html#a-complete-worked-example","2188":"/guide/authentication.html#see-it-in-the-examples","2189":"/guide/authentication.html#related","2190":"/guide/annotations.html#comment-annotations-guide","2191":"/guide/annotations.html#how-annotations-work","2192":"/guide/annotations.html#basic-rules","2193":"/guide/annotations.html#optional-prefix","2194":"/guide/annotations.html#simple-example","2195":"/guide/annotations.html#the-http-annotation","2196":"/guide/annotations.html#syntax-variations","2197":"/guide/annotations.html#default-behavior","2198":"/guide/annotations.html#authorization-annotations","2199":"/guide/annotations.html#require-authentication","2200":"/guide/annotations.html#role-list-syntax","2201":"/guide/annotations.html#allow-anonymous-access","2202":"/guide/annotations.html#response-headers","2203":"/guide/annotations.html#request-parameter-configuration","2204":"/guide/annotations.html#query-string-vs-body","2205":"/guide/annotations.html#caching","2206":"/guide/annotations.html#raw-output-mode","2207":"/guide/annotations.html#combining-annotations","2208":"/guide/annotations.html#debugging-annotations","2209":"/guide/annotations.html#comments-mode","2210":"/guide/annotations.html#time-duration-formats","2211":"/guide/annotations.html#quick-reference","2212":"/guide/annotations.html#examples","2213":"/guide/annotations.html#common-patterns","2214":"/guide/annotations.html#public-read-protected-write","2215":"/guide/annotations.html#api-versioning-with-custom-paths","2216":"/guide/annotations.html#secure-sensitive-operations","2217":"/guide/annotations.html#nested-json-for-composite-types","2218":"/guide/annotations.html#rate-limiting","2219":"/guide/annotations.html#next-steps","2220":"/guide/changelog/#changelog","2221":"/guide/changelog/#version-3-19-latest","2222":"/guide/changelog/#version-3-18","2223":"/guide/changelog/#version-3-17","2224":"/guide/changelog/#version-3-16","2225":"/guide/changelog/#version-3-15","2226":"/guide/changelog/#version-3-14","2227":"/guide/changelog/#version-3-13","2228":"/guide/changelog/#version-3-12","2229":"/guide/changelog/#version-3-11","2230":"/guide/changelog/#version-3-10","2231":"/guide/changelog/#version-3-9","2232":"/guide/changelog/#version-3-8","2233":"/guide/changelog/#version-3-7","2234":"/guide/changelog/#version-3-6","2235":"/guide/changelog/#version-3-5","2236":"/guide/changelog/#version-3-4","2237":"/guide/changelog/#version-3-3","2238":"/guide/changelog/#version-3-2","2239":"/guide/changelog/#version-3-1","2240":"/guide/changelog/#version-3-0","2241":"/guide/changelog/v3.0.1.html#changelog-v3-0-1-2025-11-28","2242":"/guide/changelog/v3.0.1.html#version-3-0-1-2025-11-28","2243":"/guide/changelog/v3.0.0.html#changelog-v3-0-0-2025-11-27","2244":"/guide/changelog/v3.0.0.html#version-3-0-0-2025-11-27","2245":"/guide/changelog/v3.0.0.html#docker-jit-version","2246":"/guide/changelog/v3.0.0.html#net-10-target-framework","2247":"/guide/changelog/v3.0.0.html#tsclient-code-generation-improvements","2248":"/guide/changelog/v3.0.0.html#info-events-streaming-changes-server-sent-events","2249":"/guide/changelog/v3.0.0.html#name-refactor-changed-all-info-events-related-names-to-sse-to-better-reflect-their-purpose","2250":"/guide/changelog/v3.0.0.html#removed-self-scope-level","2251":"/guide/changelog/v3.0.0.html#new-feature-support-for-custom-notice-level","2252":"/guide/changelog/v3.0.0.html#other-comment-annotations-changes","2253":"/guide/changelog/v3.0.0.html#timeout-handling","2254":"/guide/changelog/v3.0.0.html#openapi-3-0-support","2255":"/guide/changelog/v3.0.0.html#error-handling-improvements","2256":"/guide/changelog/v3.0.0.html#metadata-query-improvements","2257":"/guide/changelog/v3.0.0.html#rate-limiter","2258":"/guide/changelog/v3.0.0.html#other-changes-and-fixes","2259":"/guide/changelog/v3.0.0.html#login-endpoint-changes","2260":"/guide/changelog/v3.1.1.html#changelog-v3-1-1-2025-12-15","2261":"/guide/changelog/v3.1.1.html#version-3-1-1-2025-12-15","2262":"/guide/changelog/v3.1.0.html#changelog-v3-1-0-2025-12-13","2263":"/guide/changelog/v3.1.0.html#version-3-1-0-2025-12-13","2264":"/guide/changelog/v3.1.0.html#http-types","2265":"/guide/changelog/v3.1.0.html#routine-caching-improvements","2266":"/guide/changelog/v3.1.0.html#multi-host-connection-support","2267":"/guide/changelog/v3.1.0.html#other-changes-and-fixes","2268":"/guide/changelog/v3.1.2.html#changelog-v3-1-2-2025-12-20","2269":"/guide/changelog/v3.1.2.html#version-3-1-2-2025-12-20","2270":"/guide/changelog/v3.1.2.html#performance-simd-accelerated-string-processing","2271":"/guide/changelog/v3.1.2.html#consistent-json-error-responses","2272":"/guide/changelog/v3.1.2.html#envfile-configuration-option","2273":"/guide/changelog/v3.1.2.html#tsclient-configurable-error-expression-and-type","2274":"/guide/changelog/v3.1.2.html#hybridcache-support","2275":"/guide/changelog/v3.1.3.html#changelog-v3-1-3-2025-12-21","2276":"/guide/changelog/v3.1.3.html#version-3-1-3-2025-12-21","2277":"/guide/changelog/v3.1.3.html#path-parameters-support","2278":"/guide/changelog/v3.1.3.html#tsclient-improvements","2279":"/guide/changelog/v3.1.3.html#hybridcache-configuration-keys-renamed","2280":"/guide/changelog/v3.10.0.html#changelog-v3-10-0-2026-02-25","2281":"/guide/changelog/v3.10.0.html#version-3-10-0-2026-02-25","2282":"/guide/changelog/v3.10.0.html#new-feature-resolved-parameter-expressions","2283":"/guide/changelog/v3.10.0.html#how-it-works","2284":"/guide/changelog/v3.10.0.html#behavior","2285":"/guide/changelog/v3.10.0.html#multiple-resolved-parameters","2286":"/guide/changelog/v3.10.0.html#resolved-parameters-in-url-headers-and-body","2287":"/guide/changelog/v3.10.0.html#new-feature-http-client-type-retry-logic","2288":"/guide/changelog/v3.10.0.html#syntax","2289":"/guide/changelog/v3.10.0.html#behavior-1","2290":"/guide/changelog/v3.10.0.html#example","2291":"/guide/changelog/v3.10.0.html#new-feature-data-protection-encrypt-decrypt-annotations","2292":"/guide/changelog/v3.10.0.html#encrypt-parameters","2293":"/guide/changelog/v3.10.0.html#decrypt-result-columns","2294":"/guide/changelog/v3.10.0.html#full-roundtrip-example","2295":"/guide/changelog/v3.10.0.html#annotation-aliases","2296":"/guide/changelog/v3.10.0.html#behavior-notes","2297":"/guide/changelog/v3.10.0.html#key-management","2298":"/guide/changelog/v3.11.0.html#changelog-v3-11-0-2026-03-10","2299":"/guide/changelog/v3.11.0.html#version-3-11-0-2026-03-10","2300":"/guide/changelog/v3.11.0.html#new-feature-proxy-out-annotation-post-execution-proxy","2301":"/guide/changelog/v3.11.0.html#syntax","2302":"/guide/changelog/v3.11.0.html#how-it-works","2303":"/guide/changelog/v3.11.0.html#basic-usage","2304":"/guide/changelog/v3.11.0.html#query-string-forwarding","2305":"/guide/changelog/v3.11.0.html#http-method-override","2306":"/guide/changelog/v3.11.0.html#custom-host","2307":"/guide/changelog/v3.11.0.html#error-handling","2308":"/guide/changelog/v3.11.0.html#configuration","2309":"/guide/changelog/v3.11.0.html#performance","2310":"/guide/changelog/v3.11.0.html#tsclient-proxy-out-endpoint-support","2311":"/guide/changelog/v3.11.1.html#changelog-v3-11-1-2026-03-13","2312":"/guide/changelog/v3.11.1.html#version-3-11-1-2026-03-13","2313":"/guide/changelog/v3.11.1.html#tsclient-proxy-passthrough-endpoint-support","2314":"/guide/changelog/v3.11.1.html#authorize-annotation-now-matches-user-id-and-user-name-claims","2315":"/guide/changelog/v3.12.0.html#changelog-v3-12-0-2026-03-23","2316":"/guide/changelog/v3.12.0.html#version-3-12-0-2026-03-23","2317":"/guide/changelog/v3.12.0.html#new-endpoint-source-plugin-npgsqlrest-sqlfilesource","2318":"/guide/changelog/v3.12.0.html#how-it-works","2319":"/guide/changelog/v3.12.0.html#single-command-files","2320":"/guide/changelog/v3.12.0.html#multi-command-files","2321":"/guide/changelog/v3.12.0.html#parameters","2322":"/guide/changelog/v3.12.0.html#virtual-parameters","2323":"/guide/changelog/v3.12.0.html#comments-and-annotations","2324":"/guide/changelog/v3.12.0.html#wire-protocol-introspection","2325":"/guide/changelog/v3.12.0.html#custom-composite-type-support","2326":"/guide/changelog/v3.12.0.html#unnamed-and-duplicate-columns","2327":"/guide/changelog/v3.12.0.html#url-path-derivation","2328":"/guide/changelog/v3.12.0.html#error-handling","2329":"/guide/changelog/v3.12.0.html#feature-parity","2330":"/guide/changelog/v3.12.0.html#configuration-reference","2331":"/guide/changelog/v3.12.0.html#new-annotations","2332":"/guide/changelog/v3.12.0.html#new-core-annotation-param-parameter-—-rename-and-retype-parameters","2333":"/guide/changelog/v3.12.0.html#param-default-values-for-sql-file-parameters","2334":"/guide/changelog/v3.12.0.html#param-rename-validation","2335":"/guide/changelog/v3.12.0.html#param-default-value-alias-for-default","2336":"/guide/changelog/v3.12.0.html#param-type-hints-for-sql-file-describe","2337":"/guide/changelog/v3.12.0.html#new-positional-annotation-returns-—-skip-describe-and-declare-return-type","2338":"/guide/changelog/v3.12.0.html#new-annotation-void-—-force-void-response","2339":"/guide/changelog/v3.12.0.html#new-comment-annotation-single","2340":"/guide/changelog/v3.12.0.html#positional-result-annotation-for-multi-command-files","2341":"/guide/changelog/v3.12.0.html#skipnonquerycommands-setting-and-skip-annotation","2342":"/guide/changelog/v3.12.0.html#skipnonquerycommands-default-true","2343":"/guide/changelog/v3.12.0.html#skip-annotation-aliases-skip-result-no-result","2344":"/guide/changelog/v3.12.0.html#new-core-annotation-internal-internal-only","2345":"/guide/changelog/v3.12.0.html#http-custom-types-self-referencing-calls","2346":"/guide/changelog/v3.12.0.html#self-referencing-calls-relative-path-support-for-proxy-and-http-client-types","2347":"/guide/changelog/v3.12.0.html#internal-self-call-optimization-zero-http-overhead","2348":"/guide/changelog/v3.12.0.html#composite-type-parameters-in-sql-files-—-no-sql-rewriting","2349":"/guide/changelog/v3.12.0.html#configuration-changes","2350":"/guide/changelog/v3.12.0.html#routinesource-enabled-configuration-option","2351":"/guide/changelog/v3.12.0.html#crudsource-disabled-by-default","2352":"/guide/changelog/v3.12.0.html#crudsource-no-longer-blocks-sqlfilesource","2353":"/guide/changelog/v3.12.0.html#dataprotection-disabled-by-default","2354":"/guide/changelog/v3.12.0.html#sqlfilesource-logcommandtext-setting","2355":"/guide/changelog/v3.12.0.html#tsclient-improvements","2356":"/guide/changelog/v3.12.0.html#tsclient-composite-type-support-for-sql-files","2357":"/guide/changelog/v3.12.0.html#tsclient-multi-command-sql-file-support","2358":"/guide/changelog/v3.12.0.html#tsclient-sql-file-comment-headers","2359":"/guide/changelog/v3.12.0.html#tsclient-type-alias-extraction-for-error-and-result-types","2360":"/guide/changelog/v3.12.0.html#tsclient-fix-skiptypes-generating-invalid-javascript","2361":"/guide/changelog/v3.12.0.html#bug-fixes-log-improvements","2362":"/guide/changelog/v3.12.0.html#graceful-shutdown-with-active-sse-connections","2363":"/guide/changelog/v3.12.0.html#downgrade-basic-auth-missing-header-log-to-debug","2364":"/guide/changelog/v3.12.0.html#improved-log-level-classification","2365":"/guide/changelog/v3.12.0.html#fix-separator-and-new-line-annotations-not-working-with-prefix","2366":"/guide/changelog/v3.12.0.html#aggregated-comment-annotation-logging","2367":"/guide/changelog/v3.12.0.html#fix-onlywithhttptag-mode-skips-files-before-describe","2368":"/guide/changelog/v3.12.0.html#internal-breaking-changes","2369":"/guide/changelog/v3.12.0.html#interface-refactoring-iendpointsource-iroutinesource","2370":"/guide/changelog/v3.12.0.html#composite-type-cache-public-api","2371":"/guide/changelog/v3.12.0.html#glob-pattern-enhancement-recursive-matching","2372":"/guide/changelog/v3.12.0.html#internal-changes","2373":"/guide/changelog/v3.13.0.html#changelog-v3-13-0-2026-04-24","2374":"/guide/changelog/v3.13.0.html#version-3-13-0-2026-04-24","2375":"/guide/changelog/v3.13.0.html#new-auth-schemes-named-additional-authentication-schemes","2376":"/guide/changelog/v3.13.0.html#breaking-legacy-auth-time-integer-fields-removed","2377":"/guide/changelog/v3.13.0.html#new-interval-notation-for-auth-time-fields","2378":"/guide/changelog/v3.13.0.html#breaking-ratelimiteroptions-policies-is-now-a-dict-not-an-array","2379":"/guide/changelog/v3.13.0.html#new-per-user-rate-limiting-partition-on-a-policy","2380":"/guide/changelog/v3.13.0.html#new-caching-profiles-cacheoptions-profiles-cache-profile-annotation","2381":"/guide/changelog/v3.13.0.html#never-expiring-infinite-cache-entries","2382":"/guide/changelog/v3.13.0.html#new-wrapintransaction-option-connection-pooler-compatibility","2383":"/guide/changelog/v3.13.0.html#new-beforeroutinecommands-option","2384":"/guide/changelog/v3.13.0.html#fix-400-bad-request-responses-are-no-longer-silent-in-logs","2385":"/guide/changelog/v3.13.0.html#docker-images-ubuntu-26-04-lts-base","2386":"/guide/changelog/v3.13.0.html#nuget-package-upgrades","2387":"/guide/changelog/v3.14.0.html#changelog-v3-14-0-2026-05-09","2388":"/guide/changelog/v3.14.0.html#version-3-14-0-2026-05-09","2389":"/guide/changelog/v3.14.0.html#removed-auto-crud-endpoint-generation-from-the-standalone-client","2390":"/guide/changelog/v3.14.0.html#what-s-new","2391":"/guide/changelog/v3.14.0.html#two-new-sse-annotations-sse-publish-and-sse-subscribe","2392":"/guide/changelog/v3.14.0.html#warning-when-a-raise-looks-like-a-missed-sse-publish","2393":"/guide/changelog/v3.14.0.html#reliable-sse-connection-handshake","2394":"/guide/changelog/v3.14.0.html#startup-error-when-claim-mapped-parameters-use-a-non-text-type","2395":"/guide/changelog/v3.14.0.html#warning-when-a-request-value-is-overridden-by-claim-auto-bind","2396":"/guide/changelog/v3.14.0.html#performance","2397":"/guide/changelog/v3.14.0.html#lower-allocation-json-conversion-for-arrays-and-composites","2398":"/guide/changelog/v3.14.0.html#estimated-impact-on-the-postgresql-rest-api-benchmark-2026-workloads","2399":"/guide/changelog/v3.14.0.html#utf-8-literals-for-json-markup-constants","2400":"/guide/changelog/v3.14.0.html#tighter-pipewriter-writes","2401":"/guide/changelog/v3.14.0.html#hardening-silent-failure-fixes","2402":"/guide/changelog/v3.14.0.html#arraypool-rent-now-in-try-finally","2403":"/guide/changelog/v3.14.0.html#multi-command-stringbuilder-rentals-always-released","2404":"/guide/changelog/v3.14.0.html#proxy-out-buffer-released-on-exception-path","2405":"/guide/changelog/v3.14.0.html#column-decryption-failures-now-logged-at-trace","2406":"/guide/changelog/v3.14.0.html#configuration","2407":"/guide/changelog/v3.14.0.html#test-suite","2408":"/guide/changelog/v3.15.1.html#changelog-v3-15-1-2026-05-11","2409":"/guide/changelog/v3.15.1.html#version-3-15-1-2026-05-11","2410":"/guide/changelog/v3.15.1.html#fix-named-auth-schemes-are-validated-by-type-not-by-name","2411":"/guide/changelog/v3.15.1.html#root-cause","2412":"/guide/changelog/v3.15.1.html#what-changed","2413":"/guide/changelog/v3.15.1.html#behavior-after-the-fix","2414":"/guide/changelog/v3.15.1.html#fix-config-and-validate-cli-commands-now-honor-validateconfigkeys-mode","2415":"/guide/changelog/v3.15.1.html#what-changed-1","2416":"/guide/changelog/v3.15.1.html#behavior-after-the-fix-1","2417":"/guide/changelog/v3.15.1.html#tests","2418":"/guide/changelog/v3.15.0.html#changelog-v3-15-0","2419":"/guide/changelog/v3.15.0.html#version-3-15-0-2026-05-11","2420":"/guide/changelog/v3.15.0.html#fix-named-cookie-schemes-now-actually-authenticate-requests","2421":"/guide/changelog/v3.15.0.html#root-cause","2422":"/guide/changelog/v3.15.0.html#what-changed","2423":"/guide/changelog/v3.15.0.html#behavior-after-the-fix","2424":"/guide/changelog/v3.15.0.html#cookie-precedence-order-when-both-are-present","2425":"/guide/changelog/v3.15.0.html#feature-cookiesamesite-and-cookiesecure-config","2426":"/guide/changelog/v3.15.0.html#root-level-main-cookie-scheme","2427":"/guide/changelog/v3.15.0.html#per-scheme-override-under-auth-schemes","2428":"/guide/changelog/v3.15.0.html#validation-and-warnings","2429":"/guide/changelog/v3.15.0.html#cross-origin-checklist-for-an-external-web-api-setup","2430":"/guide/changelog/v3.15.0.html#feature-openapi-filtering-for-partner-facing-documents","2431":"/guide/changelog/v3.15.0.html#config-level-filters","2432":"/guide/changelog/v3.15.0.html#per-routine-openapi-comment-annotation","2433":"/guide/changelog/v3.15.0.html#filter-order-and-composition","2434":"/guide/changelog/v3.15.0.html#partner-facing-config-example","2435":"/guide/changelog/v3.15.0.html#tests","2436":"/guide/changelog/v3.15.0.html#configuration-summary","2437":"/guide/changelog/v3.15.0.html#out-of-scope","2438":"/guide/changelog/v3.15.0.html#partner-system-integration-readiness-—-what-s-still-missing","2439":"/guide/changelog/v3.15.2.html#changelog-v3-15-2-2026-05-11","2440":"/guide/changelog/v3.15.2.html#version-3-15-2-2026-05-11","2441":"/guide/changelog/v3.15.2.html#fix-ratelimiteroptions-policies-validates-by-type-not-by-name","2442":"/guide/changelog/v3.15.2.html#root-cause","2443":"/guide/changelog/v3.15.2.html#what-changed","2444":"/guide/changelog/v3.15.2.html#behavior-after-the-fix","2445":"/guide/changelog/v3.15.2.html#fix-cacheoptions-profiles-validates-by-shape","2446":"/guide/changelog/v3.15.2.html#improvement-validationoptions-rules-now-validates-rule-bodies","2447":"/guide/changelog/v3.15.2.html#tests","2448":"/guide/changelog/v3.15.2.html#files-touched","2449":"/guide/changelog/v3.16.0.html#changelog-v3-16-0-2026-05-20","2450":"/guide/changelog/v3.16.0.html#version-3-16-0-2026-05-20","2451":"/guide/changelog/v3.16.0.html#fix-datetime-parsers-are-now-host-tz-independent","2452":"/guide/changelog/v3.16.0.html#why-this-was-hidden-so-long","2453":"/guide/changelog/v3.16.0.html#tryparsedate-left-alone","2454":"/guide/changelog/v3.16.0.html#breaking-change","2455":"/guide/changelog/v3.16.0.html#opt-out-npgsqlrestoptions-jsontimestampsareutc","2456":"/guide/changelog/v3.16.0.html#tests","2457":"/guide/changelog/v3.16.0.html#files-touched","2458":"/guide/changelog/v3.16.1.html#changelog-v3-16-1-2026-06-01","2459":"/guide/changelog/v3.16.1.html#version-3-16-1-2026-06-01","2460":"/guide/changelog/v3.16.1.html#what-changed","2461":"/guide/changelog/v3.16.1.html#iroutinecache-gains-getorcreateasync-additive","2462":"/guide/changelog/v3.16.1.html#stampede-protection-per-backend","2463":"/guide/changelog/v3.16.1.html#middleware-paths","2464":"/guide/changelog/v3.16.1.html#effect","2465":"/guide/changelog/v3.16.1.html#test-coverage-read-this-honestly","2466":"/guide/changelog/v3.16.1.html#known-limitations","2467":"/guide/changelog/v3.16.2.html#changelog-v3-16-2-2026-06-02","2468":"/guide/changelog/v3.16.2.html#version-3-16-2-2026-06-02","2469":"/guide/changelog/v3.16.2.html#what-changed","2470":"/guide/changelog/v3.16.2.html#per-policy-statuscode-statusmessage-overrides","2471":"/guide/changelog/v3.16.2.html#new-ready-to-use-login-throttle-default-policy","2472":"/guide/changelog/v3.16.2.html#test-coverage","2473":"/guide/changelog/v3.16.3.html#changelog-v3-16-3-2026-06-03","2474":"/guide/changelog/v3.16.3.html#version-3-16-3-2026-06-03","2475":"/guide/changelog/v3.16.3.html#what-changed","2476":"/guide/changelog/v3.16.3.html#availableenvvars-under-staticfiles-parsecontentoptions","2477":"/guide/changelog/v3.16.3.html#security-note","2478":"/guide/changelog/v3.17.0.html#changelog-v3-17-0","2479":"/guide/changelog/v3.17.0.html#version-3-17-0-2026-06-13","2480":"/guide/changelog/v3.17.0.html#new-features","2481":"/guide/changelog/v3.17.0.html#mcp-model-context-protocol-server-—-new-npgsqlrest-mcp-plugin","2482":"/guide/changelog/v3.17.0.html#plugin-extension-points-on-routineendpoint","2483":"/guide/changelog/v3.17.0.html#name-annotation-substitution-can-resolve-allowlisted-environment-variables","2484":"/guide/changelog/v3.17.0.html#tsclient-exporttypes-—-emit-request-response-interfaces-with-the-export-keyword","2485":"/guide/changelog/v3.17.0.html#breaking-changes","2486":"/guide/changelog/v3.17.0.html#⚠️-safer-configuration-defaults-cors-credentials-passkey-requirements-connection-testing","2487":"/guide/changelog/v3.17.0.html#⚠️-openapi-annotation-handling-moved-out-of-core-c-api-only","2488":"/guide/changelog/v3.17.0.html#fixes","2489":"/guide/changelog/v3.17.0.html#internal-only-endpoints-are-excluded-from-generated-client-artifacts-and-api-docs","2490":"/guide/changelog/v3.17.0.html#🔴-security-sse-scope-hints-were-not-enforced-—-hint-scoped-events-were-delivered-to-every-subscriber","2491":"/guide/changelog/v3.17.0.html#malformed-json-request-body-now-returns-400-bad-request-was-404-not-found","2492":"/guide/changelog/v3.17.0.html#passkey-webauthn-diagnostics-cbor-decode-failures-are-no-longer-silent","2493":"/guide/changelog/v3.17.0.html#name-parameter-value-placeholders-case-insensitive-matching-typo-warning","2494":"/guide/changelog/v3.17.0.html#bare-cached-no-parameter-list-used-only-the-routine-name-as-the-cache-key","2495":"/guide/changelog/v3.17.0.html#hybridcache-cache-key-contains-invalid-content-on-nullable-cached-params","2496":"/guide/changelog/v3.17.0.html#json-command-parameters-accept-json-jsonb-or-text","2497":"/guide/changelog/v3.17.0.html#optional-name-and-required-name-environment-variable-placeholders","2498":"/guide/changelog/v3.17.0.html#tests","2499":"/guide/changelog/v3.18.0.html#changelog-v3-18-0","2500":"/guide/changelog/v3.18.0.html#version-3-18-0-2026-06-23","2501":"/guide/changelog/v3.18.0.html#new-features","2502":"/guide/changelog/v3.18.0.html#http-custom-type-response-caching-—-cache-directive","2503":"/guide/changelog/v3.18.0.html#fixes","2504":"/guide/changelog/v3.18.0.html#http-custom-type-request-fired-once-per-composite-field-on-database-function-endpoints","2505":"/guide/changelog/v3.18.0.html#http-type-directives-after-the-headers-were-silently-ignored","2506":"/guide/changelog/v3.18.0.html#tests","2507":"/guide/changelog/v3.18.1.html#changelog-v3-18-1","2508":"/guide/changelog/v3.18.1.html#version-3-18-1-2026-06-23","2509":"/guide/changelog/v3.18.1.html#what-changed","2510":"/guide/changelog/v3.18.1.html#why","2511":"/guide/changelog/v3.18.1.html#behavior-change-to-note","2512":"/guide/changelog/v3.18.1.html#notes","2513":"/guide/changelog/v3.18.1.html#tests","2514":"/guide/changelog/v3.18.2.html#changelog-v3-18-2","2515":"/guide/changelog/v3.18.2.html#version-3-18-2-2026-06-26","2516":"/guide/changelog/v3.18.2.html#what-changed","2517":"/guide/changelog/v3.18.2.html#_1-large-auto-filled-values-no-longer-break-the-proxy-query-string","2518":"/guide/changelog/v3.18.2.html#_2-body-parameter-name-reliably-matches-http-custom-type-fields","2519":"/guide/changelog/v3.18.2.html#_3-typescript-client-generation-for-body-parameter-name","2520":"/guide/changelog/v3.18.2.html#_4-opt-in-omit-automatic-server-filled-parameters-from-generated-request-shapes","2521":"/guide/changelog/v3.18.2.html#why-these-go-together","2522":"/guide/changelog/v3.18.2.html#notes","2523":"/guide/changelog/v3.18.2.html#tests","2524":"/guide/changelog/v3.19.0.html#changelog-v3-19-0","2525":"/guide/changelog/v3.19.0.html#version-3-19-0-2026-07-03","2526":"/guide/changelog/v3.19.0.html#_1-sql-test-runner-test","2527":"/guide/changelog/v3.19.0.html#how-it-works","2528":"/guide/changelog/v3.19.0.html#test-file-anatomy","2529":"/guide/changelog/v3.19.0.html#http-blocks-—-invoking-endpoints","2530":"/guide/changelog/v3.19.0.html#the-response-temp-table","2531":"/guide/changelog/v3.19.0.html#reusing-scripts-i-and-ir-includes","2532":"/guide/changelog/v3.19.0.html#setup-and-teardown-and-named-steps","2533":"/guide/changelog/v3.19.0.html#per-file-setup-teardown-and-connection-header-annotations","2534":"/guide/changelog/v3.19.0.html#a-dedicated-test-database","2535":"/guide/changelog/v3.19.0.html#reporting","2536":"/guide/changelog/v3.19.0.html#logging","2537":"/guide/changelog/v3.19.0.html#configuration-reference-testrunner-section","2538":"/guide/changelog/v3.19.0.html#project-layout","2539":"/guide/changelog/v3.19.0.html#_2-sqlfilesource-skippattern-—-exclude-files-from-endpoint-discovery","2540":"/guide/changelog/v3.19.0.html#_3-named-parameters-in-sql-files-name","2541":"/guide/changelog/v3.19.0.html#_4-watch-mode-watch","2542":"/guide/changelog/v3.19.0.html#watching-the-routine-source-—-database-polling","2543":"/guide/changelog/v3.19.0.html#server-watch","2544":"/guide/changelog/v3.19.0.html#_5-mute-an-individual-logger-with-off-in-log-minimallevels","2545":"/guide/changelog/v3.19.0.html#notes","2546":"/guide/changelog/v3.19.0.html#tests","2547":"/guide/changelog/v3.2.0.html#changelog-v3-2-0-2025-12-22","2548":"/guide/changelog/v3.2.0.html#version-3-2-0-2025-12-22","2549":"/guide/changelog/v3.2.0.html#reverse-proxy-feature","2550":"/guide/changelog/v3.2.0.html#docker-image-with-bun-runtime","2551":"/guide/changelog/v3.2.0.html#configuration-default-fixes","2552":"/guide/changelog/v3.2.1.html#changelog-v3-2-1-2025-12-23","2553":"/guide/changelog/v3.2.1.html#version-3-2-1-2025-12-23","2554":"/guide/changelog/v3.2.1.html#jwt-json-web-token-authentication-support","2555":"/guide/changelog/v3.2.1.html#path-parameters-support-for-httpfiles-and-openapi-plugins","2556":"/guide/changelog/v3.2.2.html#changelog-v3-2-2-2025-12-24","2557":"/guide/changelog/v3.2.2.html#version-3-2-2-2025-12-24","2558":"/guide/changelog/v3.2.2.html#bug-fixes","2559":"/guide/changelog/v3.2.2.html#performance-improvements","2560":"/guide/changelog/v3.2.3.html#changelog-v3-2-3-2025-12-30","2561":"/guide/changelog/v3.2.3.html#version-3-2-3-2025-12-30","2562":"/guide/changelog/v3.2.3.html#tsclient-plugin","2563":"/guide/changelog/v3.2.4.html#changelog-v3-2-4-2025-01-03","2564":"/guide/changelog/v3.2.4.html#version-3-2-4-2025-01-03","2565":"/guide/changelog/v3.2.4.html#dataprotection-key-encryption-options","2566":"/guide/changelog/v3.2.4.html#tsclient-plugin","2567":"/guide/changelog/v3.2.4.html#npgsqlrestclient","2568":"/guide/changelog/v3.2.6.html#changelog-v3-2-6-2025-01-04","2569":"/guide/changelog/v3.2.6.html#version-3-2-6-2025-01-04","2570":"/guide/changelog/v3.2.7.html#changelog-v3-2-7-2025-01-05","2571":"/guide/changelog/v3.2.7.html#version-3-2-7-2025-01-05","2572":"/guide/changelog/v3.2.7.html#upload-handlers-user-context-and-claims-support","2573":"/guide/changelog/v3.3.0.html#changelog-v3-3-0-2025-01-08","2574":"/guide/changelog/v3.3.0.html#version-3-3-0-2025-01-08","2575":"/guide/changelog/v3.3.0.html#parameter-validation","2576":"/guide/changelog/v3.3.0.html#linux-arm64-build-and-docker-image","2577":"/guide/changelog/v3.3.0.html#config-command-shows-default-values","2578":"/guide/changelog/v3.3.1.html#changelog-v3-3-1-2025-01-14","2579":"/guide/changelog/v3.3.1.html#version-3-3-1-2025-01-14","2580":"/guide/changelog/v3.3.1.html#proxy-response-caching","2581":"/guide/changelog/v3.3.1.html#optional-prefix-for-comment-annotations","2582":"/guide/changelog/v3.3.1.html#added-a-logo-on-client-app-commands","2583":"/guide/changelog/v3.4.0.html#changelog-v3-4-0-2025-01-16","2584":"/guide/changelog/v3.4.0.html#version-3-4-0-2025-01-16","2585":"/guide/changelog/v3.4.0.html#composite-type-support","2586":"/guide/changelog/v3.4.0.html#_1-arrays-of-composite-types","2587":"/guide/changelog/v3.4.0.html#_2-nested-json-for-composite-type-columns-opt-in","2588":"/guide/changelog/v3.4.0.html#multidimensional-array-support","2589":"/guide/changelog/v3.4.0.html#json-escaping-fix-for-arrays-and-tuple-strings","2590":"/guide/changelog/v3.4.0.html#tsclient-plugin-composite-type-interface-generation","2591":"/guide/changelog/v3.4.0.html#optional-prefix-extended-to-annotation-parameters","2592":"/guide/changelog/v3.4.1.html#changelog-v3-4-1-2025-01-15","2593":"/guide/changelog/v3.4.1.html#version-3-4-1-2025-01-15","2594":"/guide/changelog/v3.4.1.html#configuration-options-for-null-handling","2595":"/guide/changelog/v3.4.1.html#querystringnullhandling","2596":"/guide/changelog/v3.4.1.html#textresponsenullhandling","2597":"/guide/changelog/v3.4.1.html#bug-fixes","2598":"/guide/changelog/v3.4.2.html#changelog-v3-4-2-2025-01-15","2599":"/guide/changelog/v3.4.2.html#version-3-4-2-2025-01-15","2600":"/guide/changelog/v3.4.2.html#bug-fixes","2601":"/guide/changelog/v3.4.3.html#changelog-v3-4-3-2025-01-16","2602":"/guide/changelog/v3.4.3.html#version-3-4-3-2025-01-16","2603":"/guide/changelog/v3.4.3.html#bug-fixes","2604":"/guide/changelog/v3.4.3.html#performance-improvements","2605":"/guide/changelog/v3.4.4.html#changelog-v3-4-4-2025-01-17","2606":"/guide/changelog/v3.4.4.html#version-3-4-4-2025-01-17","2607":"/guide/changelog/v3.4.4.html#deep-nested-composite-type-resolution-resolvenestedcompositetypes","2608":"/guide/changelog/v3.4.4.html#bug-fixes","2609":"/guide/changelog/v3.4.5.html#changelog-v3-4-5-2025-01-19","2610":"/guide/changelog/v3.4.5.html#version-3-4-5-2025-01-19","2611":"/guide/changelog/v3.4.5.html#npgsqlrest-tsclient-deep-nested-composite-type-support","2612":"/guide/changelog/v3.4.6.html#changelog-v3-4-6-2025-01-21","2613":"/guide/changelog/v3.4.6.html#version-3-4-6-2025-01-21","2614":"/guide/changelog/v3.4.6.html#endpoint-execution-performance-optimizations","2615":"/guide/changelog/v3.4.6.html#comprehensive-cancellationtoken-propagation","2616":"/guide/changelog/v3.4.8.html#changelog-v3-4-8-2025-01-26","2617":"/guide/changelog/v3.4.8.html#version-3-4-8-2025-01-26","2618":"/guide/changelog/v3.4.8.html#fix-single-field-composite-type-returns","2619":"/guide/changelog/v3.4.7.html#changelog-v3-4-7-2025-01-21","2620":"/guide/changelog/v3.4.7.html#version-3-4-7-2025-01-21","2621":"/guide/changelog/v3.4.7.html#type-category-lookup-optimization","2622":"/guide/changelog/v3.4.7.html#additional-allocation-optimizations","2623":"/guide/changelog/v3.5.0.html#changelog-v3-5-0-2025-01-28","2624":"/guide/changelog/v3.5.0.html#version-3-5-0-2025-01-28","2625":"/guide/changelog/v3.5.0.html#new-feature-passkeyauth-webauthn-fido2","2626":"/guide/changelog/v3.5.0.html#bugfix-response-compression-for-static-files","2627":"/guide/changelog/v3.5.0.html#added-client-integration-tests","2628":"/guide/changelog/v3.5.0.html#separate-core-and-client-logging","2629":"/guide/changelog/v3.5.0.html#debug-log-filtering-options","2630":"/guide/changelog/v3.6.0.html#changelog-v3-6-0-2025-02-01","2631":"/guide/changelog/v3.6.0.html#version-3-6-0-2025-02-01","2632":"/guide/changelog/v3.6.0.html#new-feature-security-headers-middleware","2633":"/guide/changelog/v3.6.0.html#new-feature-forwarded-headers-middleware","2634":"/guide/changelog/v3.6.0.html#new-feature-health-check-endpoints","2635":"/guide/changelog/v3.6.0.html#new-feature-postgresql-statistics-endpoints","2636":"/guide/changelog/v3.6.1.html#changelog-v3-6-1-2025-02-02","2637":"/guide/changelog/v3.6.1.html#version-3-6-1-2025-02-02","2638":"/guide/changelog/v3.6.1.html#fixes","2639":"/guide/changelog/v3.6.2.html#changelog-v3-6-2-2025-02-02","2640":"/guide/changelog/v3.6.2.html#version-3-6-2-2025-02-02","2641":"/guide/changelog/v3.6.2.html#fixes","2642":"/guide/changelog/v3.6.2.html#breaking-changes","2643":"/guide/changelog/v3.6.3.html#changelog-v3-6-3-2025-02-03","2644":"/guide/changelog/v3.6.3.html#version-3-6-3-2025-02-03","2645":"/guide/changelog/v3.6.3.html#fixes","2646":"/guide/changelog/v3.7.0.html#changelog-v3-7-0-2025-02-07","2647":"/guide/changelog/v3.7.0.html#version-3-7-0-2025-02-07","2648":"/guide/changelog/v3.7.0.html#fixes","2649":"/guide/changelog/v3.7.0.html#new-features","2650":"/guide/changelog/v3.7.0.html#new-feature-pluggable-table-format-renderers","2651":"/guide/changelog/v3.7.0.html#html-table-format","2652":"/guide/changelog/v3.7.0.html#excel-table-format","2653":"/guide/changelog/v3.7.0.html#per-endpoint-custom-parameters","2654":"/guide/changelog/v3.7.0.html#tsclient-per-endpoint-url-export-control","2655":"/guide/changelog/v3.7.0.html#tsclient-export-url","2656":"/guide/changelog/v3.7.0.html#tsclient-url-only","2657":"/guide/changelog/v3.8.0.html#changelog-v3-8-0-2025-02-11","2658":"/guide/changelog/v3.8.0.html#version-3-8-0-2025-02-11","2659":"/guide/changelog/v3.8.0.html#new-feature-configuration-key-validation","2660":"/guide/changelog/v3.8.0.html#removed","2661":"/guide/changelog/v3.8.0.html#kestrel-configuration-validation","2662":"/guide/changelog/v3.8.0.html#syntax-highlighted-config-output","2663":"/guide/changelog/v3.8.0.html#improved-cli-error-handling","2664":"/guide/changelog/v3.8.0.html#universal-fallback-handler-for-all-upload-handlers","2665":"/guide/changelog/v3.8.0.html#optional-path-parameters","2666":"/guide/changelog/v3.8.0.html#fixes","2667":"/guide/changelog/v3.8.0.html#machine-readable-cli-commands-for-tool-integration","2668":"/guide/changelog/v3.8.0.html#version-json","2669":"/guide/changelog/v3.8.0.html#validate-json","2670":"/guide/changelog/v3.8.0.html#config-schema","2671":"/guide/changelog/v3.8.0.html#annotations","2672":"/guide/changelog/v3.8.0.html#endpoints","2673":"/guide/changelog/v3.8.0.html#config-updated","2674":"/guide/changelog/v3.8.0.html#stats-endpoints-format-query-string-override","2675":"/guide/changelog/v3.9.0.html#changelog-v3-9-0-2026-02-23","2676":"/guide/changelog/v3.9.0.html#version-3-9-0-2026-02-23","2677":"/guide/changelog/v3.9.0.html#commented-configuration-output-config","2678":"/guide/changelog/v3.9.0.html#configuration-search-and-filter-config-filter","2679":"/guide/changelog/v3.9.0.html#cli-improvements","2680":"/guide/configuration.html#configuration-guide","2681":"/guide/configuration.html#configuration-sources","2682":"/guide/configuration.html#default-values","2683":"/guide/configuration.html#configuration-files","2684":"/guide/configuration.html#default-configuration-files","2685":"/guide/configuration.html#optional-configuration-files","2686":"/guide/configuration.html#configuration-file-format","2687":"/guide/configuration.html#environment-variables","2688":"/guide/configuration.html#optional-and-required-placeholders","2689":"/guide/configuration.html#enabling-environment-variable-binding","2690":"/guide/configuration.html#environment-variable-naming-rules","2691":"/guide/configuration.html#command-line-arguments","2692":"/guide/configuration.html#command-line-syntax-rules","2693":"/guide/configuration.html#exploring-configuration","2694":"/guide/configuration.html#generating-a-default-configuration-file","2695":"/guide/configuration.html#searching-for-settings","2696":"/guide/configuration.html#configuration-validation","2697":"/guide/configuration.html#configuration-precedence-example","2698":"/guide/configuration.html#quick-reference","2699":"/guide/configuration.html#common-command-line-overrides","2700":"/guide/configuration.html#exploring-configuration-1","2701":"/guide/configuration.html#configuration-structure-overview","2702":"/guide/configuration.html#top-level-settings","2703":"/guide/configuration.html#urls-configuration","2704":"/guide/configuration.html#startup-message","2705":"/guide/configuration.html#config-section-options","2706":"/guide/configuration.html#next-steps","2707":"/guide/faq.html#faq-troubleshooting","2708":"/guide/faq.html#general","2709":"/guide/faq.html#what-is-npgsqlrest","2710":"/guide/faq.html#what-postgresql-versions-are-supported","2711":"/guide/faq.html#what-net-version-is-required","2712":"/guide/faq.html#is-it-safe-from-sql-injection","2713":"/guide/faq.html#how-does-npgsqlrest-compare-to-postgrest-or-supabase","2714":"/guide/faq.html#can-i-use-it-inside-an-existing-asp-net-core-application","2715":"/guide/faq.html#installation-setup","2716":"/guide/faq.html#how-do-i-install-npgsqlrest","2717":"/guide/faq.html#how-do-i-run-it-in-docker","2718":"/guide/faq.html#how-do-i-connect-to-my-database","2719":"/guide/faq.html#can-i-use-environment-variables-for-configuration","2720":"/guide/faq.html#endpoints","2721":"/guide/faq.html#my-function-doesn-t-appear-as-an-endpoint","2722":"/guide/faq.html#my-sql-file-doesn-t-appear-as-an-endpoint","2723":"/guide/faq.html#an-endpoint-exists-but-i-get-404-—-why","2724":"/guide/faq.html#why-are-parameter-and-column-names-camelcased-how-do-i-turn-that-off","2725":"/guide/faq.html#my-query-returns-one-row-—-why-do-i-get-an-array","2726":"/guide/faq.html#how-do-i-return-plain-text-html-or-csv-instead-of-json","2727":"/guide/faq.html#how-do-i-customize-the-endpoint-url-path","2728":"/guide/faq.html#how-do-i-restrict-access-to-an-endpoint","2729":"/guide/faq.html#can-i-expose-tables-and-views-directly-without-writing-any-sql","2730":"/guide/faq.html#parameters","2731":"/guide/faq.html#named-or-positional-parameters-in-sql-files-—-which-should-i-use","2732":"/guide/faq.html#how-do-i-make-a-parameter-optional","2733":"/guide/faq.html#how-do-i-get-the-authenticated-user-s-id-into-a-query","2734":"/guide/faq.html#error-could-not-determine-data-type-of-parameter","2735":"/guide/faq.html#authentication","2736":"/guide/faq.html#what-authentication-methods-are-supported","2737":"/guide/faq.html#how-do-i-set-up-jwt-authentication","2738":"/guide/faq.html#testing","2739":"/guide/faq.html#how-do-i-test-my-endpoints","2740":"/guide/faq.html#can-tests-run-against-a-temporary-database-instead-of-my-real-one","2741":"/guide/faq.html#my-test-fixtures-need-half-the-database-inserted-first-—-is-there-a-better-way","2742":"/guide/faq.html#is-there-a-watch-mode","2743":"/guide/faq.html#performance","2744":"/guide/faq.html#how-fast-is-it","2745":"/guide/faq.html#how-do-i-enable-caching","2746":"/guide/faq.html#how-do-i-enable-response-compression","2747":"/guide/faq.html#how-do-i-set-up-rate-limiting","2748":"/guide/faq.html#debugging-logging","2749":"/guide/faq.html#how-do-i-see-which-endpoints-are-created-and-what-options-they-have","2750":"/guide/faq.html#how-do-i-log-the-sql-each-endpoint-executes-at-runtime","2751":"/guide/faq.html#how-do-i-see-the-metadata-queries-npgsqlrest-runs-at-startup","2752":"/guide/faq.html#how-do-i-completely-silence-a-logger","2753":"/guide/faq.html#troubleshooting","2754":"/guide/faq.html#startup-warning-unknown-configuration-key","2755":"/guide/faq.html#error-permission-denied-for-schema","2756":"/guide/faq.html#timeout-errors-504-gateway-timeout","2757":"/guide/faq.html#encrypted-data-is-unreadable-after-restart","2758":"/guide/faq.html#leftover-abcde-test-databases","2759":"/guide/http-types.html#http-custom-types","2760":"/guide/http-types.html#how-it-works","2761":"/guide/http-types.html#enabling-the-http-client","2762":"/guide/http-types.html#defining-and-using-a-type","2763":"/guide/http-types.html#reading-the-response","2764":"/guide/http-types.html#dynamic-requests-with-placeholders","2765":"/guide/http-types.html#timeouts-retries-and-caching","2766":"/guide/http-types.html#multiple-calls-in-parallel","2767":"/guide/http-types.html#self-calls-composing-your-own-endpoints","2768":"/guide/http-types.html#secrets-and-server-side-values","2769":"/guide/http-types.html#configuration","2770":"/guide/http-types.html#see-it-in-the-examples","2771":"/guide/http-types.html#related","2772":"/guide/#overview","2773":"/guide/#declarative-approach","2774":"/guide/#plain-sql-files","2775":"/guide/#postgresql-routines-functions-and-procedures","2776":"/guide/#technology-distribution","2777":"/guide/installation.html#npgsqlrest-installation-guide","2778":"/guide/installation.html#download-executable","2779":"/guide/installation.html#manual-installation","2780":"/guide/installation.html#command-line-download","2781":"/guide/installation.html#windows-x64","2782":"/guide/installation.html#linux-x64","2783":"/guide/installation.html#linux-arm64","2784":"/guide/installation.html#macos-arm64","2785":"/guide/installation.html#command-line-basic-commands","2786":"/guide/installation.html#npm-installation","2787":"/guide/installation.html#docker-installation","2788":"/guide/installation.html#standard-image-aot","2789":"/guide/installation.html#jit-image","2790":"/guide/installation.html#arm64-image","2791":"/guide/installation.html#bun-runtime-image","2792":"/guide/installation.html#building-from-source","2793":"/guide/installation.html#next-steps","2794":"/guide/logging.html#logging","2795":"/guide/logging.html#the-channel-map","2796":"/guide/logging.html#recipes","2797":"/guide/logging.html#see-which-endpoints-exist-and-why","2798":"/guide/logging.html#see-every-sql-command-endpoints-execute","2799":"/guide/logging.html#debug-discovery-why-isn-t-my-function-file-picked-up","2800":"/guide/logging.html#watch-the-test-runner-mute-everything-else","2801":"/guide/logging.html#silence-a-channel-completely","2802":"/guide/logging.html#postgresql-messages-in-your-logs","2803":"/guide/logging.html#logging-into-postgresql","2804":"/guide/logging.html#files-opentelemetry-and-production","2805":"/guide/logging.html#related","2806":"/guide/proxy.html#proxy-endpoints","2807":"/guide/proxy.html#how-it-works","2808":"/guide/proxy.html#enabling-the-proxy","2809":"/guide/proxy.html#passthrough-mode","2810":"/guide/proxy.html#transform-mode","2811":"/guide/proxy.html#where-the-request-goes-target-url","2812":"/guide/proxy.html#forwarding-headers-claims-and-ip","2813":"/guide/proxy.html#forward-proxy-send-a-function-result-upstream","2814":"/guide/proxy.html#configuration","2815":"/guide/proxy.html#a-complete-example-cached-ai-gateway","2816":"/guide/proxy.html#see-it-in-the-examples","2817":"/guide/proxy.html#related","2818":"/guide/quick-start.html#quick-start","2819":"/guide/quick-start.html#prerequisites","2820":"/guide/quick-start.html#step-1-create-your-first-endpoint","2821":"/guide/quick-start.html#option-a-sql-file-recommended","2822":"/guide/quick-start.html#option-b-postgresql-function","2823":"/guide/quick-start.html#step-2-run-npgsqlrest","2824":"/guide/quick-start.html#step-3-anonymous-endpoint-and-verbose-logging","2825":"/guide/quick-start.html#step-4-create-configuration-file","2826":"/guide/quick-start.html#next-steps","2827":"/guide/sse.html#server-sent-events-sse","2828":"/guide/sse.html#how-sse-works","2829":"/guide/sse.html#creating-a-publisher-endpoint","2830":"/guide/sse.html#subscribing-from-the-browser","2831":"/guide/sse.html#who-receives-events-scope","2832":"/guide/sse.html#which-raise-level-fires-event-level","2833":"/guide/sse.html#targeting-specific-recipients","2834":"/guide/sse.html#splitting-publish-from-subscribe","2835":"/guide/sse.html#configuration","2836":"/guide/sse.html#a-complete-example-real-time-chat","2837":"/guide/sse.html#see-it-in-the-examples","2838":"/guide/sse.html#related","2839":"/guide/sql-files.html#sql-file-endpoints","2840":"/guide/sql-files.html#how-it-works","2841":"/guide/sql-files.html#configuration","2842":"/guide/sql-files.html#single-command-files","2843":"/guide/sql-files.html#http-verb-detection","2844":"/guide/sql-files.html#parameters","2845":"/guide/sql-files.html#named-parameters-name","2846":"/guide/sql-files.html#positional-parameters-n","2847":"/guide/sql-files.html#type-hints","2848":"/guide/sql-files.html#default-values","2849":"/guide/sql-files.html#virtual-parameters-define-param","2850":"/guide/sql-files.html#multi-command-files","2851":"/guide/sql-files.html#result-rules","2852":"/guide/sql-files.html#positional-annotations","2853":"/guide/sql-files.html#void","2854":"/guide/sql-files.html#returns-—-skip-describe","2855":"/guide/sql-files.html#do-blocks-and-limitations","2856":"/guide/sql-files.html#existing-features-work-unchanged","2857":"/guide/sql-files.html#the-dev-loop-watch-mode","2858":"/guide/sql-files.html#sql-files-vs-functions","2859":"/guide/sql-files.html#related","2860":"/guide/testing.html#testing","2861":"/guide/testing.html#quick-start","2862":"/guide/testing.html#how-it-works","2863":"/guide/testing.html#test-file-anatomy","2864":"/guide/testing.html#assertions","2865":"/guide/testing.html#http-blocks-invoking-endpoints","2866":"/guide/testing.html#the-response-table","2867":"/guide/testing.html#transactions-when-to-begin-rollback","2868":"/guide/testing.html#fixtures-without-inserting-the-whole-database-deferrable-constraints","2869":"/guide/testing.html#reusing-sql-includes","2870":"/guide/testing.html#per-file-annotations","2871":"/guide/testing.html#setup-teardown-and-named-steps","2872":"/guide/testing.html#scenario-dedicated-test-database-per-run","2873":"/guide/testing.html#scenario-template-database-and-per-test-isolation","2874":"/guide/testing.html#scenario-external-migration-runners","2875":"/guide/testing.html#scenario-docker","2876":"/guide/testing.html#scenario-testing-least-privilege-polp-setups","2877":"/guide/testing.html#filtering-and-tags","2878":"/guide/testing.html#watch-mode","2879":"/guide/testing.html#endpoint-coverage","2880":"/guide/testing.html#reporting-logging-ci","2881":"/guide/testing.html#troubleshooting","2882":"/guide/testing.html#reference"},"fieldIds":{"title":0,"titles":1,"text":2},"fieldLength":{"0":[3,1,44],"1":[5,3,111],"2":[3,3,19],"3":[1,3,44],"4":[2,1,21],"5":[1,2,5],"6":[1,2,1],"7":[2,3,32],"8":[2,3,11],"9":[5,3,25],"10":[1,2,21],"11":[1,2,24],"12":[2,2,10],"13":[1,1,24],"14":[1,1,24],"15":[1,1,1],"16":[4,2,42],"17":[2,2,20],"18":[3,2,33],"19":[4,2,49],"20":[4,2,48],"21":[2,2,35],"22":[6,2,48],"23":[3,2,19],"24":[4,2,10],"25":[1,1,32],"26":[1,1,22],"27":[2,1,16],"28":[2,1,7],"29":[3,1,25],"30":[1,3,22],"31":[2,3,56],"32":[2,3,23],"33":[2,5,52],"34":[2,5,32],"35":[2,5,50],"36":[1,3,1],"37":[3,4,69],"38":[6,4,77],"39":[5,4,58],"40":[5,4,67],"41":[1,3,75],"42":[1,3,16],"43":[2,3,27],"44":[3,1,17],"45":[1,3,24],"46":[2,3,9],"47":[1,3,1],"48":[3,4,50],"49":[2,4,19],"50":[3,4,42],"51":[1,3,53],"52":[3,3,22],"53":[1,3,17],"54":[2,3,14],"55":[2,1,17],"56":[1,2,22],"57":[3,2,20],"58":[3,2,29],"59":[1,2,1],"60":[8,3,49],"61":[4,3,58],"62":[2,3,59],"63":[1,2,67],"64":[2,2,38],"65":[1,2,22],"66":[2,2,25],"67":[2,2,6],"68":[3,1,18],"69":[1,3,9],"70":[1,3,1],"71":[3,4,34],"72":[3,4,22],"73":[1,3,24],"74":[2,3,83],"75":[10,5,66],"76":[1,3,16],"77":[2,3,26],"78":[2,1,21],"79":[1,2,13],"80":[2,2,7],"81":[2,2,27],"82":[1,2,1],"83":[2,3,21],"84":[3,3,20],"85":[4,3,14],"86":[5,3,12],"87":[1,2,52],"88":[2,2,46],"89":[1,2,16],"90":[2,2,11],"91":[3,1,16],"92":[1,3,27],"93":[1,3,1],"94":[5,4,16],"95":[5,4,16],"96":[5,4,16],"97":[2,4,16],"98":[1,3,21],"99":[2,3,4],"100":[2,3,8],"101":[2,1,67],"102":[1,2,40],"103":[1,2,1],"104":[2,3,43],"105":[5,3,50],"106":[5,3,73],"107":[5,3,44],"108":[1,2,76],"109":[1,2,63],"110":[1,2,29],"111":[2,2,10],"112":[1,1,9],"113":[1,1,23],"114":[1,1,1],"115":[2,2,34],"116":[4,2,35],"117":[4,2,23],"118":[3,2,20],"119":[4,2,65],"120":[1,1,32],"121":[2,1,50],"122":[1,1,24],"123":[2,1,20],"124":[2,1,8],"125":[2,1,19],"126":[1,2,4],"127":[1,2,1],"128":[3,3,53],"129":[3,3,18],"130":[1,2,10],"131":[2,2,13],"132":[2,1,15],"133":[1,2,75],"134":[2,2,18],"135":[1,2,1],"136":[2,3,40],"137":[3,3,25],"138":[3,3,13],"139":[1,2,24],"140":[2,2,35],"141":[1,2,27],"142":[2,2,11],"143":[2,2,6],"144":[1,1,17],"145":[1,1,7],"146":[1,1,1],"147":[3,2,12],"148":[2,2,13],"149":[2,2,13],"150":[1,1,23],"151":[1,1,18],"152":[2,1,6],"153":[2,1,6],"154":[2,1,9],"155":[1,2,28],"156":[3,2,39],"157":[1,5,46],"158":[3,2,20],"159":[1,4,36],"160":[1,4,16],"161":[2,4,13],"162":[3,4,19],"163":[2,4,10],"164":[1,2,28],"165":[2,1,73],"166":[1,2,14],"167":[3,2,63],"168":[2,2,85],"169":[2,2,25],"170":[1,2,40],"171":[1,1,16],"172":[1,1,2],"173":[1,1,26],"174":[1,1,49],"175":[3,1,100],"176":[1,1,22],"177":[1,1,37],"178":[1,1,2],"179":[1,1,41],"180":[8,1,36],"181":[1,1,12],"182":[2,1,63],"183":[2,2,1],"184":[1,4,89],"185":[3,2,1],"186":[1,5,77],"187":[3,2,41],"188":[1,2,99],"189":[1,2,16],"190":[2,2,11],"191":[2,2,8],"192":[3,1,16],"193":[1,3,7],"194":[1,3,1],"195":[2,4,15],"196":[2,4,14],"197":[1,3,23],"198":[1,3,16],"199":[2,3,6],"200":[2,3,7],"201":[3,1,16],"202":[1,3,32],"203":[1,3,40],"204":[2,3,6],"205":[1,3,1],"206":[3,4,65],"207":[5,4,52],"208":[4,4,52],"209":[3,4,80],"210":[2,3,52],"211":[2,3,46],"212":[2,3,113],"213":[2,3,94],"214":[2,3,180],"215":[3,3,108],"216":[1,3,43],"217":[1,3,27],"218":[2,3,12],"219":[2,3,7],"220":[2,1,45],"221":[5,2,20],"222":[2,2,1],"223":[3,4,71],"224":[1,4,17],"225":[2,4,14],"226":[2,4,35],"227":[2,4,40],"228":[3,4,14],"229":[3,4,21],"230":[1,4,24],"231":[1,4,14],"232":[2,4,11],"233":[3,4,14],"234":[1,4,5],"235":[1,4,11],"236":[3,4,17],"237":[2,4,29],"238":[3,4,42],"239":[3,4,54],"240":[1,4,6],"241":[1,1,13],"242":[1,1,2],"243":[1,1,24],"244":[2,1,52],"245":[2,1,36],"246":[1,1,1],"247":[2,2,28],"248":[3,2,27],"249":[2,2,21],"250":[4,2,26],"251":[4,2,28],"252":[4,2,21],"253":[2,1,27],"254":[3,3,26],"255":[3,3,28],"256":[5,3,34],"257":[5,3,34],"258":[4,3,53],"259":[1,1,17],"260":[2,1,17],"261":[1,1,39],"262":[1,1,8],"263":[5,1,62],"264":[7,1,45],"265":[3,1,22],"266":[1,1,23],"267":[3,1,19],"268":[1,3,29],"269":[2,3,50],"270":[1,3,1],"271":[4,4,22],"272":[2,4,16],"273":[2,4,12],"274":[2,4,19],"275":[6,4,12],"276":[3,3,1],"277":[3,6,46],"278":[4,6,25],"279":[2,3,23],"280":[2,3,28],"281":[1,3,17],"282":[1,1,14],"283":[1,1,4],"284":[3,1,17],"285":[2,4,18],"286":[4,4,54],"287":[1,1,1],"288":[4,2,41],"289":[4,2,29],"290":[4,2,30],"291":[3,2,38],"292":[3,2,42],"293":[1,1,23],"294":[2,1,10],"295":[2,1,7],"296":[1,1,53],"297":[5,1,103],"298":[2,1,116],"299":[3,1,61],"300":[2,1,51],"301":[2,3,59],"302":[2,3,69],"303":[2,3,33],"304":[6,1,52],"305":[2,7,80],"306":[6,7,89],"307":[2,1,101],"308":[9,3,144],"309":[10,3,130],"310":[2,3,129],"311":[2,1,1],"312":[5,3,67],"313":[5,3,65],"314":[5,3,23],"315":[1,1,29],"316":[2,1,45],"317":[1,1,74],"318":[1,1,27],"319":[2,2,91],"320":[7,1,124],"321":[1,1,1],"322":[9,2,50],"323":[4,2,28],"324":[11,2,59],"325":[4,2,41],"326":[2,1,26],"327":[1,1,61],"328":[1,1,23],"329":[1,1,4],"330":[4,1,27],"331":[1,1,1],"332":[2,2,50],"333":[3,2,50],"334":[4,2,77],"335":[4,2,66],"336":[2,1,47],"337":[1,1,56],"338":[1,1,36],"339":[2,1,18],"340":[1,2,14],"341":[1,2,1],"342":[3,3,15],"343":[3,3,16],"344":[3,3,14],"345":[1,2,10],"346":[2,2,15],"347":[1,1,76],"348":[1,1,36],"349":[7,1,44],"350":[1,1,1],"351":[5,2,52],"352":[6,2,51],"353":[2,2,24],"354":[5,2,45],"355":[2,1,21],"356":[1,1,29],"357":[2,1,35],"358":[1,2,21],"359":[1,2,1],"360":[3,3,39],"361":[4,3,42],"362":[1,2,44],"363":[4,2,47],"364":[5,2,43],"365":[2,7,31],"366":[2,7,47],"367":[1,2,15],"368":[2,2,20],"369":[1,1,65],"370":[1,1,63],"371":[1,1,1],"372":[6,2,50],"373":[4,2,33],"374":[3,2,57],"375":[5,2,29],"376":[5,1,73],"377":[6,1,46],"378":[1,7,78],"379":[6,7,24],"380":[1,7,41],"381":[4,7,16],"382":[2,1,55],"383":[6,1,72],"384":[1,1,71],"385":[1,1,32],"386":[3,1,85],"387":[3,3,70],"388":[5,3,148],"389":[2,8,41],"390":[2,3,174],"391":[1,3,1],"392":[3,4,21],"393":[5,4,31],"394":[6,4,53],"395":[5,3,103],"396":[1,3,40],"397":[1,1,13],"398":[1,1,2],"399":[1,1,7],"400":[1,1,1],"401":[2,2,37],"402":[4,2,12],"403":[2,2,13],"404":[2,1,18],"405":[3,3,25],"406":[3,3,28],"407":[3,3,20],"408":[3,3,84],"409":[1,1,36],"410":[1,1,17],"411":[2,1,8],"412":[2,1,34],"413":[1,2,9],"414":[1,2,96],"415":[2,2,89],"416":[2,2,1],"417":[6,4,24],"418":[5,4,28],"419":[6,4,43],"420":[4,4,29],"421":[7,4,51],"422":[2,2,43],"423":[5,2,107],"424":[2,2,32],"425":[1,2,1],"426":[3,3,55],"427":[2,3,57],"428":[2,3,58],"429":[2,2,68],"430":[1,2,30],"431":[1,2,23],"432":[2,2,8],"433":[1,1,20],"434":[1,1,8],"435":[1,1,35],"436":[6,1,185],"437":[2,1,1],"438":[2,3,58],"439":[2,3,83],"440":[2,1,1],"441":[5,3,23],"442":[4,3,24],"443":[5,3,27],"444":[4,3,27],"445":[6,3,47],"446":[2,1,82],"447":[2,1,73],"448":[4,3,132],"449":[3,3,67],"450":[1,1,1],"451":[3,2,33],"452":[2,2,116],"453":[5,2,109],"454":[4,2,87],"455":[1,1,26],"456":[1,1,35],"457":[2,1,8],"458":[4,1,44],"459":[1,4,9],"460":[1,4,27],"461":[2,4,1],"462":[4,6,41],"463":[2,6,35],"464":[2,6,37],"465":[1,4,1],"466":[5,5,43],"467":[6,5,35],"468":[6,5,34],"469":[2,4,61],"470":[2,4,32],"471":[1,4,16],"472":[2,4,12],"473":[3,1,26],"474":[1,3,8],"475":[1,3,1],"476":[3,4,34],"477":[3,4,31],"478":[3,4,35],"479":[4,4,56],"480":[1,3,97],"481":[1,3,16],"482":[2,3,4],"483":[2,3,7],"484":[1,1,18],"485":[1,1,4],"486":[1,1,1],"487":[3,2,32],"488":[4,2,34],"489":[2,2,46],"490":[3,2,36],"491":[3,2,31],"492":[3,2,33],"493":[3,2,72],"494":[1,1,29],"495":[1,1,10],"496":[2,1,16],"497":[3,1,19],"498":[1,3,8],"499":[1,3,24],"500":[1,3,1],"501":[2,4,14],"502":[4,4,19],"503":[3,4,38],"504":[1,3,18],"505":[1,3,17],"506":[2,3,8],"507":[4,1,20],"508":[1,4,10],"509":[1,4,1],"510":[3,5,40],"511":[3,5,31],"512":[1,4,29],"513":[1,4,16],"514":[2,4,8],"515":[3,1,34],"516":[1,3,12],"517":[1,3,14],"518":[2,3,15],"519":[1,3,1],"520":[4,4,48],"521":[4,4,32],"522":[3,4,21],"523":[4,4,32],"524":[1,3,27],"525":[1,3,16],"526":[2,3,16],"527":[2,1,104],"528":[1,2,72],"529":[1,2,108],"530":[1,2,1],"531":[10,3,76],"532":[3,3,23],"533":[7,3,32],"534":[8,2,63],"535":[1,2,40],"536":[2,1,9],"537":[1,2,21],"538":[1,2,1],"539":[3,3,35],"540":[2,3,38],"541":[3,3,33],"542":[2,3,37],"543":[4,3,30],"544":[4,3,70],"545":[2,3,40],"546":[2,2,22],"547":[1,2,14],"548":[2,2,10],"549":[3,1,27],"550":[1,3,9],"551":[1,3,25],"552":[1,3,1],"553":[5,4,22],"554":[4,4,22],"555":[3,4,20],"556":[2,3,32],"557":[1,3,16],"558":[2,3,8],"559":[2,1,39],"560":[1,2,41],"561":[1,2,1],"562":[5,3,30],"563":[6,3,26],"564":[5,3,12],"565":[3,3,53],"566":[3,3,31],"567":[1,2,69],"568":[1,2,33],"569":[2,1,18],"570":[1,2,21],"571":[1,2,1],"572":[3,3,13],"573":[3,3,12],"574":[3,3,16],"575":[1,2,38],"576":[3,2,27],"577":[2,2,56],"578":[1,2,15],"579":[2,2,13],"580":[2,2,6],"581":[1,1,32],"582":[1,1,68],"583":[3,1,46],"584":[1,1,64],"585":[2,2,56],"586":[2,2,59],"587":[1,1,115],"588":[1,1,32],"589":[2,1,18],"590":[1,2,4],"591":[1,2,1],"592":[3,3,49],"593":[2,3,22],"594":[2,3,27],"595":[1,2,23],"596":[1,2,18],"597":[2,2,10],"598":[1,1,16],"599":[1,1,13],"600":[1,1,1],"601":[4,2,12],"602":[4,2,13],"603":[2,2,13],"604":[2,2,12],"605":[1,1,10],"606":[2,1,15],"607":[1,1,18],"608":[1,1,4],"609":[4,1,32],"610":[1,1,1],"611":[2,2,36],"612":[2,2,28],"613":[3,2,36],"614":[5,2,65],"615":[1,1,60],"616":[2,2,35],"617":[1,1,29],"618":[1,1,48],"619":[1,1,39],"620":[1,1,1],"621":[4,2,46],"622":[3,2,52],"623":[2,2,24],"624":[2,1,71],"625":[1,1,45],"626":[1,1,31],"627":[3,1,19],"628":[1,3,8],"629":[1,3,11],"630":[1,3,1],"631":[5,4,18],"632":[2,4,17],"633":[3,4,17],"634":[1,3,21],"635":[2,3,11],"636":[3,1,54],"637":[1,3,26],"638":[1,3,28],"639":[2,3,40],"640":[1,3,1],"641":[2,4,58],"642":[4,4,18],"643":[6,4,34],"644":[2,4,24],"645":[3,4,23],"646":[5,3,98],"647":[1,3,21],"648":[2,3,14],"649":[1,1,20],"650":[3,1,166],"651":[1,1,11],"652":[3,1,13],"653":[4,3,41],"654":[5,3,28],"655":[2,1,17],"656":[2,1,43],"657":[1,1,1],"658":[5,2,47],"659":[6,2,48],"660":[3,2,26],"661":[3,2,26],"662":[6,2,31],"663":[3,1,125],"664":[2,4,70],"665":[3,4,62],"666":[4,4,86],"667":[1,1,22],"668":[4,2,53],"669":[4,2,75],"670":[1,1,21],"671":[2,1,10],"672":[2,1,6],"673":[2,1,54],"674":[1,2,21],"675":[1,2,49],"676":[1,2,1],"677":[3,3,37],"678":[3,3,21],"679":[3,3,78],"680":[1,2,25],"681":[2,2,14],"682":[2,2,9],"683":[1,1,51],"684":[2,1,51],"685":[1,1,29],"686":[5,1,37],"687":[1,1,37],"688":[1,1,12],"689":[2,1,55],"690":[1,2,54],"691":[7,2,68],"692":[1,2,21],"693":[2,1,43],"694":[1,2,54],"695":[1,2,85],"696":[1,2,60],"697":[1,2,22],"698":[2,1,32],"699":[2,2,37],"700":[1,2,47],"701":[1,2,52],"702":[1,2,17],"703":[2,1,37],"704":[1,2,84],"705":[1,2,69],"706":[2,2,63],"707":[1,2,20],"708":[2,1,40],"709":[1,2,41],"710":[1,2,47],"711":[5,2,51],"712":[1,2,18],"713":[2,1,43],"714":[1,2,57],"715":[1,2,41],"716":[1,2,35],"717":[1,2,17],"718":[1,1,27],"719":[1,1,16],"720":[1,1,80],"721":[1,1,1],"722":[2,2,54],"723":[3,2,71],"724":[2,2,33],"725":[1,1,23],"726":[2,1,22],"727":[2,1,7],"728":[2,1,13],"729":[1,2,3],"730":[1,2,5],"731":[1,2,1],"732":[3,3,13],"733":[5,3,37],"734":[5,3,32],"735":[4,3,28],"736":[4,3,30],"737":[1,2,46],"738":[3,3,18],"739":[6,3,13],"740":[1,2,16],"741":[2,2,23],"742":[2,2,7],"743":[1,1,8],"744":[1,1,2],"745":[1,1,29],"746":[2,1,38],"747":[3,1,115],"748":[2,1,60],"749":[3,1,10],"750":[2,4,35],"751":[4,4,36],"752":[2,4,31],"753":[4,4,34],"754":[3,1,8],"755":[2,4,27],"756":[3,4,38],"757":[4,4,46],"758":[3,4,26],"759":[2,1,12],"760":[4,3,46],"761":[3,3,66],"762":[5,3,66],"763":[6,3,46],"764":[2,3,69],"765":[6,3,51],"766":[4,3,53],"767":[2,3,35],"768":[3,3,63],"769":[2,1,14],"770":[4,3,55],"771":[3,3,81],"772":[5,3,84],"773":[6,3,68],"774":[2,3,73],"775":[4,3,43],"776":[3,3,73],"777":[4,1,60],"778":[3,1,23],"779":[1,1,32],"780":[2,1,19],"781":[2,3,55],"782":[4,3,55],"783":[1,7,22],"784":[4,3,74],"785":[1,7,28],"786":[3,3,73],"787":[1,6,24],"788":[3,3,56],"789":[1,6,26],"790":[1,1,22],"791":[2,1,22],"792":[2,1,12],"793":[2,1,8],"794":[2,1,18],"795":[1,2,6],"796":[1,2,1],"797":[3,3,37],"798":[7,3,28],"799":[5,3,26],"800":[4,3,13],"801":[1,2,65],"802":[3,3,22],"803":[4,3,30],"804":[1,2,16],"805":[2,2,14],"806":[2,2,7],"807":[1,1,22],"808":[1,1,2],"809":[1,1,48],"810":[1,1,1],"811":[3,2,41],"812":[5,2,46],"813":[2,2,42],"814":[4,2,53],"815":[2,2,45],"816":[2,1,34],"817":[2,1,56],"818":[1,1,39],"819":[2,1,34],"820":[1,1,14],"821":[2,1,11],"822":[2,1,6],"823":[1,1,37],"824":[1,1,5],"825":[1,1,1],"826":[4,2,69],"827":[3,2,22],"828":[2,2,17],"829":[1,1,62],"830":[1,1,20],"831":[9,1,83],"832":[3,9,80],"833":[5,9,88],"834":[6,9,171],"835":[6,9,225],"836":[5,9,123],"837":[4,9,105],"838":[1,9,69],"839":[8,1,10],"840":[1,8,133],"841":[7,8,358],"842":[5,8,1],"843":[2,13,211],"844":[2,13,220],"845":[2,13,248],"846":[5,8,1],"847":[2,13,176],"848":[2,13,375],"849":[2,13,222],"850":[5,8,1],"851":[2,13,425],"852":[2,13,431],"853":[2,13,14],"854":[8,14,99],"855":[10,14,93],"856":[10,14,102],"857":[7,14,194],"858":[4,8,1],"859":[2,12,197],"860":[2,12,310],"861":[2,12,194],"862":[6,8,1],"863":[2,14,147],"864":[2,14,278],"865":[2,14,251],"866":[7,1,93],"867":[5,7,109],"868":[6,7,447],"869":[8,7,367],"870":[9,7,1],"871":[1,16,250],"872":[3,17,397],"873":[4,16,300],"874":[1,16,201],"875":[2,16,107],"876":[2,7,340],"877":[8,7,100],"878":[11,1,74],"879":[6,11,107],"880":[6,11,100],"881":[3,11,75],"882":[7,11,39],"883":[4,17,69],"884":[4,17,76],"885":[6,11,76],"886":[4,11,65],"887":[3,15,50],"888":[5,11,102],"889":[1,11,35],"890":[2,11,1],"891":[3,13,35],"892":[3,13,65],"893":[6,11,54],"894":[3,11,132],"895":[2,11,1],"896":[3,13,17],"897":[3,13,30],"898":[5,13,24],"899":[4,13,25],"900":[6,13,39],"901":[2,11,34],"902":[8,11,84],"903":[3,19,116],"904":[9,11,163],"905":[2,11,36],"906":[4,11,1],"907":[2,14,46],"908":[6,14,23],"909":[2,14,35],"910":[8,11,67],"911":[2,19,125],"912":[11,1,73],"913":[2,11,204],"914":[3,11,129],"915":[5,11,151],"916":[4,11,207],"917":[4,11,127],"918":[5,11,267],"919":[1,13,243],"920":[3,13,206],"921":[10,1,79],"922":[7,10,105],"923":[2,10,1],"924":[3,12,77],"925":[4,12,23],"926":[4,12,115],"927":[6,10,66],"928":[3,16,90],"929":[3,16,64],"930":[4,16,97],"931":[3,10,1],"932":[3,12,84],"933":[5,12,100],"934":[2,12,132],"935":[2,12,47],"936":[4,12,69],"937":[2,10,101],"938":[3,10,110],"939":[6,10,1],"940":[4,15,33],"941":[6,15,44],"942":[6,15,23],"943":[4,15,27],"944":[4,15,23],"945":[4,10,41],"946":[1,10,90],"947":[9,1,99],"948":[5,9,148],"949":[5,9,81],"950":[4,9,1],"951":[4,13,45],"952":[3,13,51],"953":[3,13,35],"954":[3,13,26],"955":[5,9,1],"956":[5,13,115],"957":[5,13,67],"958":[5,13,31],"959":[4,9,46],"960":[3,13,39],"961":[6,9,140],"962":[3,9,1],"963":[4,11,45],"964":[4,11,48],"965":[3,9,71],"966":[5,9,94],"967":[3,14,79],"968":[5,9,69],"969":[3,14,47],"970":[3,9,51],"971":[1,9,84],"972":[7,1,42],"973":[6,7,56],"974":[4,7,106],"975":[6,7,101],"976":[2,7,77],"977":[3,7,98],"978":[6,7,37],"979":[3,13,74],"980":[3,13,109],"981":[7,7,23],"982":[5,11,105],"983":[4,11,62],"984":[4,11,45],"985":[7,7,72],"986":[6,7,132],"987":[7,7,32],"988":[10,13,94],"989":[4,13,67],"990":[3,13,145],"991":[4,13,51],"992":[7,13,101],"993":[5,13,57],"994":[3,13,145],"995":[4,7,136],"996":[3,7,159],"997":[5,7,75],"998":[1,7,37],"999":[4,7,1],"1000":[5,11,15],"1001":[4,11,16],"1002":[3,11,20],"1003":[4,11,16],"1004":[6,11,19],"1005":[1,7,128],"1006":[5,7,67],"1007":[3,12,69],"1008":[5,12,52],"1009":[3,12,70],"1010":[9,1,96],"1011":[7,9,75],"1012":[7,9,23],"1013":[4,14,61],"1014":[4,14,65],"1015":[3,14,70],"1016":[5,9,49],"1017":[8,9,73],"1018":[4,9,25],"1019":[5,13,79],"1020":[6,13,46],"1021":[6,13,101],"1022":[3,13,13],"1023":[4,9,110],"1024":[4,9,84],"1025":[6,9,7],"1026":[5,15,178],"1027":[2,9,58],"1028":[2,9,1],"1029":[3,11,53],"1030":[4,11,35],"1031":[3,11,47],"1032":[2,11,75],"1033":[3,11,77],"1034":[2,11,27],"1035":[5,9,58],"1036":[1,9,86],"1037":[4,1,463],"1038":[10,1,134],"1039":[6,10,74],"1040":[4,10,66],"1041":[3,14,62],"1042":[9,10,107],"1043":[6,10,86],"1044":[9,10,142],"1045":[6,10,133],"1046":[5,10,79],"1047":[2,10,65],"1048":[10,1,99],"1049":[8,10,147],"1050":[2,10,55],"1051":[3,12,51],"1052":[5,12,47],"1053":[3,10,72],"1054":[7,10,143],"1055":[8,10,90],"1056":[3,18,92],"1057":[4,10,74],"1058":[5,10,69],"1059":[3,10,53],"1060":[4,11,118],"1061":[3,10,100],"1062":[2,10,93],"1063":[5,12,76],"1064":[5,10,145],"1065":[6,15,102],"1066":[15,1,108],"1067":[7,15,264],"1068":[8,15,174],"1069":[5,15,150],"1070":[13,15,167],"1071":[3,15,104],"1072":[5,1,12],"1073":[1,5,211],"1074":[4,5,158],"1075":[7,5,152],"1076":[7,5,179],"1077":[4,5,103],"1078":[5,5,166],"1079":[6,5,200],"1080":[6,5,163],"1081":[2,5,117],"1082":[3,5,106],"1083":[7,1,46],"1084":[2,7,105],"1085":[2,7,1],"1086":[1,8,178],"1087":[1,8,28],"1088":[1,8,90],"1089":[2,7,14],"1090":[9,9,74],"1091":[7,9,41],"1092":[3,9,26],"1093":[3,7,1],"1094":[2,8,193],"1095":[3,8,88],"1096":[5,8,190],"1097":[5,8,148],"1098":[1,8,314],"1099":[2,8,98],"1100":[3,8,150],"1101":[2,8,228],"1102":[6,8,219],"1103":[3,8,59],"1104":[7,8,153],"1105":[5,15,303],"1106":[5,15,140],"1107":[7,15,199],"1108":[2,15,125],"1109":[2,8,53],"1110":[1,8,34],"1111":[2,7,203],"1112":[2,7,1],"1113":[3,9,97],"1114":[5,9,43],"1115":[6,9,52],"1116":[2,7,1],"1117":[1,8,49],"1118":[1,8,35],"1119":[3,8,42],"1120":[4,7,1],"1121":[4,11,153],"1122":[4,11,65],"1123":[4,11,59],"1124":[2,7,1],"1125":[4,9,87],"1126":[4,9,101],"1127":[1,7,237],"1128":[4,1,53],"1129":[3,4,82],"1130":[4,4,81],"1131":[2,4,1],"1132":[4,6,65],"1133":[4,6,97],"1134":[6,4,66],"1135":[7,1,146],"1136":[2,7,26],"1137":[5,9,59],"1138":[5,14,112],"1139":[3,14,137],"1140":[3,9,32],"1141":[3,11,77],"1142":[4,11,72],"1143":[2,11,31],"1144":[2,9,12],"1145":[2,11,42],"1146":[2,11,28],"1147":[2,11,88],"1148":[3,9,59],"1149":[4,9,62],"1150":[2,9,212],"1151":[2,7,26],"1152":[2,9,83],"1153":[2,9,57],"1154":[3,11,74],"1155":[4,9,57],"1156":[2,7,27],"1157":[3,9,63],"1158":[2,9,50],"1159":[2,9,43],"1160":[2,9,53],"1161":[2,9,67],"1162":[6,9,127],"1163":[2,9,41],"1164":[3,7,52],"1165":[4,10,74],"1166":[3,10,39],"1167":[5,10,49],"1168":[3,10,38],"1169":[2,10,76],"1170":[7,10,54],"1171":[4,10,60],"1172":[2,7,28],"1173":[3,7,33],"1174":[3,7,76],"1175":[2,7,41],"1176":[3,7,116],"1177":[4,7,122],"1178":[3,7,42],"1179":[4,7,127],"1180":[1,7,105],"1181":[3,8,171],"1182":[2,7,54],"1183":[13,1,75],"1184":[2,13,63],"1185":[8,13,158],"1186":[3,13,1],"1187":[4,15,49],"1188":[4,15,75],"1189":[2,15,108],"1190":[4,13,49],"1191":[4,17,62],"1192":[5,17,105],"1193":[3,17,214],"1194":[4,13,1],"1195":[2,16,23],"1196":[4,16,61],"1197":[5,16,87],"1198":[2,13,18],"1199":[2,15,75],"1200":[4,13,53],"1201":[4,13,1],"1202":[5,15,55],"1203":[5,15,88],"1204":[2,15,51],"1205":[5,15,86],"1206":[3,13,79],"1207":[2,13,84],"1208":[1,13,83],"1209":[8,1,84],"1210":[7,8,75],"1211":[2,8,81],"1212":[3,8,14],"1213":[3,11,82],"1214":[3,11,112],"1215":[3,11,85],"1216":[3,11,83],"1217":[2,11,107],"1218":[4,11,79],"1219":[3,8,21],"1220":[7,11,170],"1221":[6,11,124],"1222":[2,11,111],"1223":[3,8,15],"1224":[2,11,54],"1225":[3,11,52],"1226":[2,11,42],"1227":[2,11,30],"1228":[1,12,44],"1229":[1,12,40],"1230":[1,12,37],"1231":[3,11,23],"1232":[1,12,162],"1233":[1,12,58],"1234":[1,12,129],"1235":[1,12,77],"1236":[1,12,97],"1237":[1,12,89],"1238":[1,12,38],"1239":[1,12,130],"1240":[3,11,47],"1241":[2,11,32],"1242":[2,8,1],"1243":[3,10,45],"1244":[3,10,32],"1245":[2,10,21],"1246":[4,8,1],"1247":[4,12,38],"1248":[4,12,17],"1249":[3,12,31],"1250":[4,12,24],"1251":[4,12,30],"1252":[2,8,58],"1253":[1,8,47],"1254":[8,1,200],"1255":[3,8,217],"1256":[6,8,10],"1257":[2,13,47],"1258":[3,13,78],"1259":[2,13,43],"1260":[4,13,17],"1261":[2,8,1],"1262":[6,10,39],"1263":[7,10,35],"1264":[5,10,37],"1265":[7,10,39],"1266":[4,10,64],"1267":[2,10,42],"1268":[6,10,44],"1269":[6,10,62],"1270":[4,10,58],"1271":[5,10,29],"1272":[5,10,54],"1273":[4,8,1],"1274":[4,11,25],"1275":[4,11,24],"1276":[3,11,31],"1277":[2,8,93],"1278":[2,10,52],"1279":[7,8,77],"1280":[1,8,75],"1281":[4,9,127],"1282":[3,8,1],"1283":[2,10,14],"1284":[3,12,102],"1285":[2,12,149],"1286":[4,10,1],"1287":[4,14,111],"1288":[5,14,108],"1289":[5,14,126],"1290":[4,14,123],"1291":[5,14,116],"1292":[6,10,1],"1293":[3,16,129],"1294":[3,10,1],"1295":[5,13,125],"1296":[3,10,1],"1297":[5,13,126],"1298":[2,10,1],"1299":[5,12,119],"1300":[5,10,1],"1301":[3,15,127],"1302":[12,1,75],"1303":[5,12,81],"1304":[9,12,75],"1305":[5,12,121],"1306":[5,12,1],"1307":[4,16,53],"1308":[4,16,47],"1309":[8,16,122],"1310":[4,16,35],"1311":[3,12,9],"1312":[3,15,20],"1313":[8,15,27],"1314":[3,15,17],"1315":[3,15,16],"1316":[5,15,40],"1317":[5,12,70],"1318":[5,12,81],"1319":[5,12,1],"1320":[5,17,124],"1321":[2,17,73],"1322":[2,12,73],"1323":[6,12,70],"1324":[6,12,109],"1325":[5,17,96],"1326":[7,12,115],"1327":[1,12,81],"1328":[10,1,103],"1329":[5,10,74],"1330":[5,10,8],"1331":[5,13,59],"1332":[5,13,96],"1333":[5,10,39],"1334":[6,10,20],"1335":[4,16,109],"1336":[4,16,42],"1337":[4,16,61],"1338":[5,16,146],"1339":[5,16,114],"1340":[1,16,49],"1341":[3,10,55],"1342":[4,10,95],"1343":[4,10,61],"1344":[5,10,1],"1345":[3,13,33],"1346":[3,13,23],"1347":[2,13,57],"1348":[3,13,44],"1349":[2,10,48],"1350":[2,12,48],"1351":[6,10,110],"1352":[13,1,47],"1353":[2,13,29],"1354":[5,15,52],"1355":[5,13,72],"1356":[5,13,40],"1357":[6,13,82],"1358":[6,13,114],"1359":[4,13,54],"1360":[3,17,69],"1361":[6,13,50],"1362":[7,13,61],"1363":[6,18,83],"1364":[2,13,36],"1365":[2,13,25],"1366":[3,13,326],"1367":[1,13,72],"1368":[9,1,140],"1369":[3,9,42],"1370":[7,9,69],"1371":[6,9,72],"1372":[5,9,110],"1373":[5,9,85],"1374":[3,9,117],"1375":[3,9,60],"1376":[3,9,138],"1377":[3,9,62],"1378":[8,9,159],"1379":[5,9,56],"1380":[2,9,51],"1381":[5,1,67],"1382":[2,5,370],"1383":[4,5,49],"1384":[3,1,118],"1385":[1,3,392],"1386":[7,3,465],"1387":[4,3,55],"1388":[3,6,86],"1389":[5,6,47],"1390":[8,6,134],"1391":[4,6,130],"1392":[3,6,49],"1393":[2,6,158],"1394":[3,6,210],"1395":[9,9,124],"1396":[9,9,216],"1397":[6,3,34],"1398":[3,9,288],"1399":[2,12,186],"1400":[2,3,150],"1401":[5,5,304],"1402":[6,5,258],"1403":[3,3,326],"1404":[4,3,185],"1405":[4,1,262],"1406":[13,1,153],"1407":[2,13,109],"1408":[3,13,192],"1409":[6,13,206],"1410":[10,13,222],"1411":[6,13,26],"1412":[4,19,130],"1413":[5,19,99],"1414":[6,19,132],"1415":[4,19,69],"1416":[5,13,214],"1417":[7,13,90],"1418":[5,20,75],"1419":[2,20,175],"1420":[6,20,106],"1421":[5,20,75],"1422":[2,13,107],"1423":[9,1,110],"1424":[2,9,49],"1425":[5,9,1],"1426":[5,14,62],"1427":[6,14,146],"1428":[5,14,73],"1429":[5,9,121],"1430":[7,9,85],"1431":[9,9,209],"1432":[9,9,78],"1433":[2,9,48],"1434":[1,9,36],"1435":[9,1,171],"1436":[3,9,79],"1437":[3,9,77],"1438":[3,9,62],"1439":[2,9,60],"1440":[4,9,69],"1441":[2,9,115],"1442":[5,9,137],"1443":[3,9,63],"1444":[1,1,13],"1445":[1,1,52],"1446":[2,1,26],"1447":[3,2,104],"1448":[2,2,33],"1449":[9,2,67],"1450":[4,1,36],"1451":[4,4,56],"1452":[2,4,20],"1453":[2,1,69],"1454":[3,2,94],"1455":[2,2,26],"1456":[2,2,28],"1457":[5,2,49],"1458":[3,1,180],"1459":[4,3,93],"1460":[6,3,116],"1461":[2,1,1],"1462":[2,3,12],"1463":[2,3,22],"1464":[2,3,99],"1465":[1,1,44],"1466":[2,1,28],"1467":[2,1,14],"1468":[2,1,14],"1469":[1,2,55],"1470":[2,2,55],"1471":[3,2,49],"1472":[2,2,52],"1473":[4,4,21],"1474":[3,2,19],"1475":[3,2,72],"1476":[2,5,8],"1477":[3,2,80],"1478":[2,5,7],"1479":[4,2,16],"1480":[3,6,57],"1481":[3,6,15],"1482":[2,2,37],"1483":[2,2,47],"1484":[1,2,34],"1485":[2,2,23],"1486":[2,2,18],"1487":[1,1,24],"1488":[1,1,13],"1489":[2,1,58],"1490":[2,1,9],"1491":[2,3,27],"1492":[2,3,21],"1493":[4,1,36],"1494":[2,1,17],"1495":[1,1,10],"1496":[2,1,9],"1497":[3,1,11],"1498":[1,3,16],"1499":[1,3,50],"1500":[3,3,22],"1501":[3,3,29],"1502":[3,3,36],"1503":[3,3,29],"1504":[3,6,92],"1505":[2,3,27],"1506":[1,3,35],"1507":[2,3,21],"1508":[2,3,14],"1509":[2,1,6],"1510":[1,2,33],"1511":[2,2,186],"1512":[2,2,1],"1513":[2,3,26],"1514":[2,3,25],"1515":[2,3,98],"1516":[3,2,71],"1517":[4,2,50],"1518":[3,2,73],"1519":[2,2,74],"1520":[1,3,53],"1521":[2,3,90],"1522":[2,3,75],"1523":[2,3,97],"1524":[4,5,33],"1525":[3,5,52],"1526":[4,5,20],"1527":[1,3,93],"1528":[3,3,32],"1529":[2,3,79],"1530":[2,2,9],"1531":[1,4,34],"1532":[3,4,28],"1533":[2,4,40],"1534":[2,2,26],"1535":[1,2,33],"1536":[2,2,9],"1537":[2,2,13],"1538":[2,1,15],"1539":[1,2,23],"1540":[5,2,64],"1541":[3,7,9],"1542":[4,7,19],"1543":[3,7,38],"1544":[2,2,59],"1545":[3,4,8],"1546":[4,4,21],"1547":[4,4,60],"1548":[2,2,27],"1549":[1,2,28],"1550":[2,2,18],"1551":[2,2,11],"1552":[2,1,10],"1553":[1,2,56],"1554":[2,2,45],"1555":[2,2,20],"1556":[2,2,22],"1557":[3,4,20],"1558":[2,2,55],"1559":[2,2,87],"1560":[2,2,23],"1561":[2,2,19],"1562":[2,2,21],"1563":[2,2,27],"1564":[3,2,28],"1565":[2,2,18],"1566":[3,2,28],"1567":[3,5,131],"1568":[4,5,56],"1569":[2,5,132],"1570":[5,5,52],"1571":[4,5,108],"1572":[4,5,52],"1573":[4,5,40],"1574":[3,5,97],"1575":[2,5,57],"1576":[2,5,50],"1577":[6,5,43],"1578":[2,2,1],"1579":[5,4,31],"1580":[6,4,40],"1581":[4,4,121],"1582":[5,4,78],"1583":[1,2,17],"1584":[2,2,12],"1585":[2,2,7],"1586":[2,1,11],"1587":[1,2,38],"1588":[2,2,41],"1589":[2,2,35],"1590":[2,4,34],"1591":[3,2,10],"1592":[2,5,19],"1593":[5,5,30],"1594":[5,5,21],"1595":[6,5,17],"1596":[6,5,22],"1597":[2,2,33],"1598":[2,2,31],"1599":[4,2,36],"1600":[1,2,17],"1601":[2,2,15],"1602":[2,2,8],"1603":[2,1,22],"1604":[2,2,52],"1605":[9,2,75],"1606":[3,2,32],"1607":[3,2,41],"1608":[4,2,65],"1609":[3,2,126],"1610":[1,2,10],"1611":[2,2,13],"1612":[2,1,17],"1613":[2,2,26],"1614":[2,3,31],"1615":[3,3,39],"1616":[3,3,75],"1617":[2,2,43],"1618":[2,2,86],"1619":[4,2,23],"1620":[3,2,81],"1621":[2,2,39],"1622":[2,2,33],"1623":[3,4,41],"1624":[3,4,68],"1625":[3,4,31],"1626":[4,2,17],"1627":[4,5,26],"1628":[3,5,57],"1629":[3,5,27],"1630":[3,2,23],"1631":[4,4,27],"1632":[3,4,68],"1633":[2,2,62],"1634":[1,2,17],"1635":[2,2,15],"1636":[2,2,7],"1637":[1,1,13],"1638":[1,1,12],"1639":[2,1,65],"1640":[2,1,34],"1641":[3,3,33],"1642":[2,1,19],"1643":[2,1,21],"1644":[1,1,51],"1645":[2,1,25],"1646":[2,1,33],"1647":[1,1,10],"1648":[2,1,13],"1649":[2,1,21],"1650":[1,2,31],"1651":[2,2,127],"1652":[2,2,1],"1653":[2,4,38],"1654":[3,4,27],"1655":[2,4,95],"1656":[2,2,26],"1657":[2,2,24],"1658":[3,2,29],"1659":[3,2,19],"1660":[4,5,13],"1661":[2,5,51],"1662":[5,5,47],"1663":[2,2,41],"1664":[4,2,142],"1665":[1,2,16],"1666":[2,2,13],"1667":[2,2,7],"1668":[2,1,11],"1669":[1,2,34],"1670":[2,2,62],"1671":[3,2,44],"1672":[3,2,42],"1673":[3,2,35],"1674":[4,4,49],"1675":[2,2,1],"1676":[2,4,34],"1677":[1,4,34],"1678":[2,2,67],"1679":[1,2,24],"1680":[2,2,12],"1681":[2,2,10],"1682":[3,1,16],"1683":[1,3,21],"1684":[2,3,90],"1685":[1,5,65],"1686":[2,3,52],"1687":[1,5,45],"1688":[3,5,48],"1689":[4,5,88],"1690":[2,3,65],"1691":[1,4,53],"1692":[1,4,54],"1693":[1,4,47],"1694":[1,4,57],"1695":[1,4,56],"1696":[3,3,59],"1697":[2,6,62],"1698":[2,3,36],"1699":[1,3,32],"1700":[2,3,17],"1701":[2,1,42],"1702":[1,2,11],"1703":[2,2,59],"1704":[4,2,66],"1705":[2,2,21],"1706":[2,2,53],"1707":[2,2,25],"1708":[2,2,56],"1709":[2,2,26],"1710":[2,2,1],"1711":[2,4,37],"1712":[3,4,26],"1713":[3,4,23],"1714":[4,4,65],"1715":[6,4,16],"1716":[4,4,27],"1717":[2,2,44],"1718":[1,2,30],"1719":[2,2,13],"1720":[3,1,16],"1721":[1,3,28],"1722":[2,3,103],"1723":[4,3,37],"1724":[4,3,1],"1725":[6,6,29],"1726":[6,6,31],"1727":[6,6,64],"1728":[3,3,26],"1729":[2,5,6],"1730":[2,5,32],"1731":[2,3,52],"1732":[2,3,59],"1733":[2,3,80],"1734":[2,3,1],"1735":[1,5,7],"1736":[2,5,58],"1737":[1,5,13],"1738":[3,3,127],"1739":[2,3,33],"1740":[1,5,68],"1741":[1,5,74],"1742":[1,5,65],"1743":[2,3,141],"1744":[6,3,34],"1745":[3,9,52],"1746":[3,9,52],"1747":[3,9,38],"1748":[1,3,27],"1749":[2,3,10],"1750":[2,3,7],"1751":[3,1,18],"1752":[1,3,22],"1753":[2,3,72],"1754":[2,3,20],"1755":[3,3,22],"1756":[2,3,15],"1757":[2,3,29],"1758":[2,3,43],"1759":[3,3,91],"1760":[1,3,10],"1761":[2,3,11],"1762":[2,1,31],"1763":[1,2,19],"1764":[2,2,61],"1765":[3,2,9],"1766":[4,4,22],"1767":[5,4,41],"1768":[5,4,33],"1769":[2,2,44],"1770":[3,2,33],"1771":[4,4,37],"1772":[2,2,1],"1773":[2,4,34],"1774":[2,4,39],"1775":[4,2,25],"1776":[2,2,17],"1777":[2,2,1],"1778":[2,4,16],"1779":[3,4,10],"1780":[3,4,18],"1781":[3,4,23],"1782":[2,2,25],"1783":[1,2,16],"1784":[2,2,11],"1785":[2,1,38],"1786":[2,2,1],"1787":[2,3,47],"1788":[1,3,66],"1789":[1,3,84],"1790":[1,3,23],"1791":[1,3,26],"1792":[4,1,3360],"1793":[1,4,1],"1794":[2,5,47],"1795":[1,5,34],"1796":[1,5,27],"1797":[1,5,23],"1798":[1,5,14],"1799":[1,1,31],"1800":[1,1,65],"1801":[2,1,62],"1802":[2,1,105],"1803":[2,1,24],"1804":[2,1,55],"1805":[2,1,34],"1806":[3,3,38],"1807":[2,1,57],"1808":[2,3,22],"1809":[2,1,34],"1810":[2,1,59],"1811":[1,1,27],"1812":[2,1,14],"1813":[2,1,68],"1814":[1,2,25],"1815":[1,2,1],"1816":[1,2,21],"1817":[1,2,22],"1818":[1,2,36],"1819":[1,2,24],"1820":[1,2,32],"1821":[1,2,24],"1822":[1,2,102],"1823":[1,2,123],"1824":[3,2,213],"1825":[6,2,172],"1826":[2,8,6],"1827":[1,10,63],"1828":[1,10,22],"1829":[1,10,13],"1830":[1,10,64],"1831":[1,10,30],"1832":[1,10,61],"1833":[6,8,76],"1834":[1,2,54],"1835":[2,1,12],"1836":[1,2,73],"1837":[2,2,49],"1838":[4,2,41],"1839":[2,6,24],"1840":[2,2,105],"1841":[3,2,29],"1842":[2,5,16],"1843":[1,2,22],"1844":[1,2,62],"1845":[3,3,17],"1846":[4,2,25],"1847":[2,6,30],"1848":[2,2,52],"1849":[3,4,33],"1850":[3,2,65],"1851":[1,5,84],"1852":[1,5,101],"1853":[2,2,27],"1854":[2,4,30],"1855":[2,4,47],"1856":[3,2,140],"1857":[3,2,38],"1858":[3,5,50],"1859":[2,5,26],"1860":[1,5,17],"1861":[3,5,48],"1862":[5,2,86],"1863":[2,2,42],"1864":[1,2,48],"1865":[2,2,19],"1866":[2,1,27],"1867":[1,2,43],"1868":[3,2,75],"1869":[3,2,21],"1870":[7,4,71],"1871":[6,4,27],"1872":[2,4,17],"1873":[2,2,1],"1874":[2,4,54],"1875":[3,4,69],"1876":[2,4,40],"1877":[2,4,32],"1878":[1,5,39],"1879":[1,5,40],"1880":[1,5,37],"1881":[3,2,12],"1882":[1,5,83],"1883":[1,5,23],"1884":[1,5,58],"1885":[1,5,43],"1886":[1,5,58],"1887":[2,5,78],"1888":[1,5,52],"1889":[3,2,41],"1890":[2,2,31],"1891":[2,2,1],"1892":[2,4,8],"1893":[2,4,61],"1894":[1,2,48],"1895":[2,2,19],"1896":[2,1,11],"1897":[1,2,29],"1898":[2,2,122],"1899":[2,2,23],"1900":[1,2,24],"1901":[2,2,21],"1902":[3,4,15],"1903":[2,4,13],"1904":[2,4,16],"1905":[4,4,18],"1906":[3,4,40],"1907":[2,2,58],"1908":[7,2,38],"1909":[4,9,50],"1910":[2,9,34],"1911":[4,9,85],"1912":[3,2,92],"1913":[1,2,26],"1914":[2,2,10],"1915":[2,1,39],"1916":[1,2,40],"1917":[2,2,116],"1918":[3,2,49],"1919":[2,2,1],"1920":[2,3,48],"1921":[2,3,60],"1922":[2,2,99],"1923":[3,2,57],"1924":[8,5,144],"1925":[4,5,125],"1926":[5,5,54],"1927":[2,2,43],"1928":[2,2,61],"1929":[6,2,76],"1930":[3,8,48],"1931":[2,2,34],"1932":[1,2,25],"1933":[2,2,19],"1934":[2,2,10],"1935":[2,1,14],"1936":[1,2,27],"1937":[2,2,51],"1938":[2,2,21],"1939":[2,2,1],"1940":[1,3,30],"1941":[2,3,27],"1942":[2,2,32],"1943":[3,2,30],"1944":[2,2,21],"1945":[1,2,10],"1946":[2,2,13],"1947":[2,1,20],"1948":[1,2,77],"1949":[2,2,59],"1950":[2,2,21],"1951":[3,2,97],"1952":[3,2,96],"1953":[3,2,95],"1954":[2,2,81],"1955":[6,2,87],"1956":[2,7,59],"1957":[2,7,97],"1958":[6,2,112],"1959":[6,8,68],"1960":[2,2,57],"1961":[3,2,114],"1962":[1,2,19],"1963":[2,2,15],"1964":[2,2,9],"1965":[2,1,9],"1966":[1,2,14],"1967":[1,2,80],"1968":[4,2,43],"1969":[2,2,21],"1970":[3,4,16],"1971":[3,4,16],"1972":[3,4,26],"1973":[5,2,93],"1974":[4,2,185],"1975":[2,2,15],"1976":[1,2,10],"1977":[2,2,10],"1978":[4,1,15],"1979":[2,4,18],"1980":[2,5,29],"1981":[2,5,21],"1982":[2,5,21],"1983":[6,5,46],"1984":[2,4,41],"1985":[2,6,9],"1986":[2,7,19],"1987":[5,7,22],"1988":[4,7,18],"1989":[2,7,30],"1990":[2,6,33],"1991":[2,8,61],"1992":[3,6,29],"1993":[3,6,14],"1994":[3,6,47],"1995":[2,4,49],"1996":[1,4,10],"1997":[2,4,11],"1998":[3,1,10],"1999":[1,3,19],"2000":[1,3,88],"2001":[1,3,22],"2002":[1,3,38],"2003":[1,4,29],"2004":[1,3,55],"2005":[1,3,30],"2006":[1,4,37],"2007":[1,3,86],"2008":[1,3,34],"2009":[1,3,50],"2010":[1,3,95],"2011":[1,3,63],"2012":[3,3,43],"2013":[1,3,38],"2014":[2,1,26],"2015":[1,2,21],"2016":[2,2,107],"2017":[4,2,31],"2018":[3,2,61],"2019":[2,2,49],"2020":[3,2,77],"2021":[2,2,44],"2022":[3,2,1],"2023":[4,5,29],"2024":[4,5,50],"2025":[4,5,24],"2026":[2,2,1],"2027":[6,4,15],"2028":[3,4,19],"2029":[4,4,37],"2030":[1,2,22],"2031":[2,2,10],"2032":[2,1,11],"2033":[1,2,35],"2034":[2,2,47],"2035":[1,2,23],"2036":[2,3,30],"2037":[2,2,40],"2038":[4,4,102],"2039":[2,4,54],"2040":[3,4,212],"2041":[2,4,20],"2042":[2,2,38],"2043":[1,2,10],"2044":[2,2,8],"2045":[2,1,30],"2046":[1,2,26],"2047":[2,2,110],"2048":[2,2,1],"2049":[5,4,39],"2050":[5,4,26],"2051":[5,4,18],"2052":[4,4,39],"2053":[2,2,1],"2054":[4,4,25],"2055":[2,4,20],"2056":[4,4,54],"2057":[1,2,1],"2058":[2,3,13],"2059":[3,3,38],"2060":[1,2,44],"2061":[2,2,24],"2062":[2,2,35],"2063":[4,2,34],"2064":[2,2,16],"2065":[2,2,1],"2066":[4,4,8],"2067":[3,4,14],"2068":[2,4,15],"2069":[3,4,11],"2070":[1,2,16],"2071":[2,2,10],"2072":[3,1,46],"2073":[1,3,46],"2074":[2,3,16],"2075":[3,3,97],"2076":[1,5,45],"2077":[3,3,90],"2078":[1,5,37],"2079":[3,5,58],"2080":[2,3,58],"2081":[1,3,34],"2082":[2,3,10],"2083":[2,3,9],"2084":[2,1,9],"2085":[1,2,9],"2086":[2,2,27],"2087":[5,2,20],"2088":[3,2,28],"2089":[2,2,33],"2090":[1,2,10],"2091":[2,2,14],"2092":[2,1,81],"2093":[1,2,45],"2094":[1,2,183],"2095":[1,2,51],"2096":[1,2,41],"2097":[3,2,70],"2098":[1,2,90],"2099":[1,2,41],"2100":[1,2,18],"2101":[1,2,32],"2102":[1,2,44],"2103":[1,2,31],"2104":[1,2,53],"2105":[1,2,24],"2106":[2,2,153],"2107":[3,2,121],"2108":[1,2,36],"2109":[1,2,95],"2110":[6,3,123],"2111":[1,2,159],"2112":[3,2,93],"2113":[2,2,43],"2114":[1,2,41],"2115":[3,1,12],"2116":[2,3,19],"2117":[2,4,52],"2118":[2,4,23],"2119":[3,4,33],"2120":[1,3,15],"2121":[2,3,13],"2122":[2,1,17],"2123":[1,2,86],"2124":[2,2,46],"2125":[4,2,106],"2126":[3,2,44],"2127":[3,2,61],"2128":[3,2,67],"2129":[4,4,39],"2130":[3,2,81],"2131":[4,4,44],"2132":[2,2,55],"2133":[1,2,16],"2134":[2,2,22],"2135":[2,2,11],"2136":[2,2,7],"2137":[2,1,26],"2138":[1,2,18],"2139":[2,2,23],"2140":[2,2,42],"2141":[2,2,62],"2142":[2,2,41],"2143":[3,2,20],"2144":[3,5,43],"2145":[3,5,26],"2146":[2,2,67],"2147":[3,2,73],"2148":[2,2,56],"2149":[1,2,49],"2150":[1,2,18],"2151":[2,2,11],"2152":[2,2,7],"2153":[2,1,68],"2154":[1,2,50],"2155":[1,2,49],"2156":[1,2,163],"2157":[3,2,153],"2158":[3,2,37],"2159":[1,2,30],"2160":[1,1,47],"2161":[1,1,26],"2162":[2,1,60],"2163":[2,1,1],"2164":[5,2,235],"2165":[5,2,135],"2166":[4,2,63],"2167":[3,2,119],"2168":[2,1,36],"2169":[2,1,24],"2170":[1,1,72],"2171":[3,1,116],"2172":[6,1,25],"2173":[4,7,23],"2174":[2,7,29],"2175":[1,7,100],"2176":[6,1,97],"2177":[3,7,170],"2178":[3,7,49],"2179":[3,1,32],"2180":[6,4,52],"2181":[2,4,84],"2182":[5,1,32],"2183":[3,6,107],"2184":[4,6,144],"2185":[3,6,88],"2186":[2,1,43],"2187":[4,1,117],"2188":[5,1,40],"2189":[1,1,43],"2190":[3,1,50],"2191":[3,3,30],"2192":[2,5,43],"2193":[2,5,118],"2194":[2,5,30],"2195":[3,3,49],"2196":[2,6,23],"2197":[2,6,36],"2198":[2,3,8],"2199":[2,4,27],"2200":[3,4,48],"2201":[3,4,20],"2202":[2,3,46],"2203":[3,3,9],"2204":[4,6,26],"2205":[1,3,44],"2206":[3,3,36],"2207":[2,3,57],"2208":[2,3,38],"2209":[2,3,32],"2210":[3,3,26],"2211":[2,6,27],"2212":[1,6,54],"2213":[2,3,1],"2214":[4,5,22],"2215":[5,5,13],"2216":[3,5,23],"2217":[5,5,34],"2218":[2,5,24],"2219":[2,3,20],"2220":[1,1,24],"2221":[5,1,169],"2222":[3,1,175],"2223":[3,1,189],"2224":[3,1,137],"2225":[3,1,92],"2226":[3,1,77],"2227":[3,1,27],"2228":[3,1,60],"2229":[3,1,28],"2230":[3,1,28],"2231":[3,1,21],"2232":[3,1,28],"2233":[3,1,24],"2234":[3,1,22],"2235":[3,1,23],"2236":[3,1,40],"2237":[2,1,27],"2238":[3,1,33],"2239":[3,1,35],"2240":[3,1,33],"2241":[8,1,1],"2242":[1,8,47],"2243":[7,1,1],"2244":[1,7,3],"2245":[3,7,53],"2246":[5,7,12],"2247":[4,7,174],"2248":[7,7,1],"2249":[16,14,18],"2250":[4,14,38],"2251":[7,14,37],"2252":[4,14,73],"2253":[2,7,64],"2254":[4,7,146],"2255":[3,7,216],"2256":[3,7,92],"2257":[2,7,143],"2258":[4,7,156],"2259":[3,11,37],"2260":[7,1,1],"2261":[1,7,39],"2262":[8,1,1],"2263":[1,8,3],"2264":[2,8,222],"2265":[3,8,276],"2266":[4,8,147],"2267":[4,8,137],"2268":[8,1,1],"2269":[1,8,3],"2270":[5,8,139],"2271":[4,8,52],"2272":[3,8,69],"2273":[6,8,64],"2274":[2,8,105],"2275":[8,1,1],"2276":[1,8,3],"2277":[3,8,130],"2278":[2,8,44],"2279":[4,8,53],"2280":[8,1,1],"2281":[1,8,3],"2282":[5,8,79],"2283":[3,13,102],"2284":[1,13,96],"2285":[3,13,31],"2286":[7,13,35],"2287":[7,8,55],"2288":[1,15,68],"2289":[1,15,94],"2290":[1,15,65],"2291":[7,8,98],"2292":[2,15,77],"2293":[3,15,76],"2294":[3,15,41],"2295":[2,15,11],"2296":[2,15,105],"2297":[2,15,158],"2298":[8,1,1],"2299":[1,8,3],"2300":[9,8,53],"2301":[1,17,15],"2302":[3,17,49],"2303":[2,17,81],"2304":[3,17,61],"2305":[3,17,31],"2306":[2,17,24],"2307":[2,17,32],"2308":[1,17,25],"2309":[1,17,49],"2310":[5,8,73],"2311":[8,1,1],"2312":[1,8,3],"2313":[5,8,81],"2314":[9,8,80],"2315":[8,1,1],"2316":[1,8,3],"2317":[6,8,39],"2318":[3,14,56],"2319":[3,14,110],"2320":[3,14,147],"2321":[1,14,88],"2322":[2,14,87],"2323":[3,14,97],"2324":[3,14,63],"2325":[4,14,33],"2326":[4,14,31],"2327":[3,14,33],"2328":[2,14,103],"2329":[2,14,92],"2330":[2,14,125],"2331":[2,8,1],"2332":[9,10,81],"2333":[8,10,154],"2334":[4,10,57],"2335":[8,10,43],"2336":[8,10,57],"2337":[10,10,149],"2338":[6,10,105],"2339":[4,10,143],"2340":[7,10,71],"2341":[5,10,1],"2342":[4,15,98],"2343":[6,15,38],"2344":[5,10,88],"2345":[7,8,1],"2346":[12,15,133],"2347":[7,15,114],"2348":[8,15,84],"2349":[2,8,1],"2350":[4,10,40],"2351":[4,10,37],"2352":[5,10,28],"2353":[4,10,69],"2354":[3,10,63],"2355":[2,8,1],"2356":[7,10,34],"2357":[6,10,74],"2358":[5,10,43],"2359":[9,10,98],"2360":[6,10,57],"2361":[5,8,1],"2362":[6,13,48],"2363":[8,13,44],"2364":[4,13,67],"2365":[10,13,46],"2366":[4,13,64],"2367":[7,13,46],"2368":[4,8,1],"2369":[4,12,54],"2370":[5,12,40],"2371":[5,12,57],"2372":[2,12,202],"2373":[8,1,1],"2374":[1,8,3],"2375":[7,8,291],"2376":[7,8,102],"2377":[7,8,80],"2378":[10,8,110],"2379":[10,8,187],"2380":[9,8,299],"2381":[5,17,102],"2382":[7,8,96],"2383":[3,8,121],"2384":[11,8,98],"2385":[7,8,74],"2386":[3,8,34],"2387":[8,1,1],"2388":[1,8,51],"2389":[9,8,209],"2390":[3,8,1],"2391":[8,10,136],"2392":[9,10,88],"2393":[4,10,70],"2394":[11,10,127],"2395":[11,10,104],"2396":[1,8,13],"2397":[8,8,129],"2398":[4,16,209],"2399":[7,8,60],"2400":[3,8,47],"2401":[5,8,19],"2402":[6,12,55],"2403":[6,12,30],"2404":[7,12,31],"2405":[7,12,65],"2406":[1,8,49],"2407":[2,8,68],"2408":[8,1,1],"2409":[1,8,36],"2410":[10,8,66],"2411":[2,17,58],"2412":[2,17,78],"2413":[4,17,69],"2414":[10,8,86],"2415":[2,17,96],"2416":[4,17,57],"2417":[1,8,44],"2418":[4,1,1],"2419":[1,4,132],"2420":[8,4,55],"2421":[2,12,93],"2422":[2,12,117],"2423":[4,12,81],"2424":[7,12,62],"2425":[5,4,67],"2426":[6,9,35],"2427":[6,9,78],"2428":[3,9,86],"2429":[9,9,84],"2430":[7,4,82],"2431":[3,11,89],"2432":[5,11,99],"2433":[4,11,53],"2434":[4,11,94],"2435":[1,4,175],"2436":[2,4,88],"2437":[3,4,70],"2438":[8,4,292],"2439":[8,1,1],"2440":[1,8,62],"2441":[8,8,44],"2442":[2,15,67],"2443":[2,15,58],"2444":[4,15,49],"2445":[6,8,76],"2446":[7,8,66],"2447":[1,8,40],"2448":[2,8,36],"2449":[8,1,1],"2450":[1,8,103],"2451":[8,8,123],"2452":[6,8,110],"2453":[3,8,82],"2454":[2,8,51],"2455":[4,8,103],"2456":[1,8,100],"2457":[2,8,34],"2458":[8,1,1],"2459":[1,8,87],"2460":[2,8,1],"2461":[5,9,91],"2462":[4,9,44],"2463":[2,9,99],"2464":[1,8,45],"2465":[6,8,93],"2466":[2,8,191],"2467":[8,1,1],"2468":[1,8,61],"2469":[2,8,1],"2470":[5,9,111],"2471":[8,9,69],"2472":[2,8,56],"2473":[8,1,1],"2474":[1,8,57],"2475":[2,8,1],"2476":[4,9,152],"2477":[2,9,68],"2478":[4,1,1],"2479":[1,4,82],"2480":[2,4,1],"2481":[9,6,410],"2482":[5,6,161],"2483":[9,6,121],"2484":[10,6,90],"2485":[2,4,1],"2486":[10,6,100],"2487":[12,6,61],"2488":[1,4,1],"2489":[12,5,63],"2490":[15,5,112],"2491":[14,5,51],"2492":[10,5,79],"2493":[11,5,106],"2494":[13,5,73],"2495":[10,5,118],"2496":[8,5,93],"2497":[7,5,95],"2498":[1,4,169],"2499":[4,1,1],"2500":[1,4,47],"2501":[2,4,1],"2502":[7,6,188],"2503":[1,4,1],"2504":[13,5,107],"2505":[9,5,57],"2506":[1,4,60],"2507":[4,1,1],"2508":[1,4,24],"2509":[2,4,104],"2510":[1,6,49],"2511":[4,6,54],"2512":[1,6,36],"2513":[1,4,58],"2514":[4,1,1],"2515":[1,4,44],"2516":[2,4,1],"2517":[12,6,81],"2518":[10,6,109],"2519":[8,6,74],"2520":[12,6,107],"2521":[4,6,43],"2522":[1,4,44],"2523":[1,4,85],"2524":[4,1,1],"2525":[1,4,63],"2526":[5,4,94],"2527":[3,9,146],"2528":[3,9,157],"2529":[4,9,210],"2530":[4,9,180],"2531":[6,9,206],"2532":[5,9,277],"2533":[9,9,232],"2534":[4,9,188],"2535":[1,9,199],"2536":[1,9,84],"2537":[5,9,498],"2538":[2,9,43],"2539":[8,4,96],"2540":[7,4,230],"2541":[4,4,74],"2542":[6,8,183],"2543":[2,8,227],"2544":[11,4,84],"2545":[1,4,151],"2546":[1,4,211],"2547":[8,1,1],"2548":[1,8,3],"2549":[3,8,232],"2550":[5,8,49],"2551":[3,8,113],"2552":[8,1,1],"2553":[1,8,3],"2554":[6,8,156],"2555":[8,8,73],"2556":[7,1,1],"2557":[1,7,3],"2558":[2,7,72],"2559":[2,7,41],"2560":[8,1,1],"2561":[1,8,3],"2562":[2,8,21],"2563":[8,1,1],"2564":[1,8,3],"2565":[4,8,75],"2566":[2,8,35],"2567":[1,8,17],"2568":[8,1,1],"2569":[1,8,21],"2570":[8,1,1],"2571":[1,8,19],"2572":[7,8,105],"2573":[8,1,1],"2574":[1,8,3],"2575":[2,8,203],"2576":[6,8,76],"2577":[5,8,58],"2578":[8,1,1],"2579":[1,8,3],"2580":[3,8,111],"2581":[5,8,78],"2582":[7,8,1],"2583":[8,1,1],"2584":[1,8,3],"2585":[3,8,13],"2586":[5,11,149],"2587":[10,11,87],"2588":[3,8,111],"2589":[8,8,96],"2590":[6,8,110],"2591":[6,8,63],"2592":[8,1,1],"2593":[1,8,3],"2594":[5,8,12],"2595":[1,13,38],"2596":[1,13,60],"2597":[2,8,83],"2598":[8,1,1],"2599":[1,8,3],"2600":[2,8,39],"2601":[8,1,1],"2602":[1,8,3],"2603":[2,8,51],"2604":[2,8,27],"2605":[7,1,1],"2606":[1,7,3],"2607":[7,7,224],"2608":[2,7,65],"2609":[8,1,1],"2610":[1,8,3],"2611":[7,8,105],"2612":[8,1,1],"2613":[1,8,3],"2614":[4,8,126],"2615":[3,8,138],"2616":[8,1,1],"2617":[1,8,3],"2618":[6,8,14],"2619":[8,1,1],"2620":[1,8,3],"2621":[4,8,142],"2622":[3,8,68],"2623":[8,1,1],"2624":[1,8,3],"2625":[6,8,42],"2626":[6,8,40],"2627":[4,8,41],"2628":[5,8,44],"2629":[4,8,41],"2630":[8,1,1],"2631":[1,8,3],"2632":[5,8,216],"2633":[5,8,167],"2634":[5,8,213],"2635":[5,8,228],"2636":[7,1,1],"2637":[1,7,3],"2638":[1,7,21],"2639":[7,1,1],"2640":[1,7,3],"2641":[1,7,55],"2642":[2,7,14],"2643":[8,1,1],"2644":[1,8,3],"2645":[1,8,53],"2646":[8,1,1],"2647":[1,8,3],"2648":[1,8,70],"2649":[2,8,56],"2650":[6,8,30],"2651":[3,14,36],"2652":[3,14,55],"2653":[4,14,44],"2654":[6,8,15],"2655":[3,14,42],"2656":[3,14,50],"2657":[8,1,1],"2658":[1,8,3],"2659":[5,8,68],"2660":[1,8,14],"2661":[3,8,39],"2662":[4,8,44],"2663":[4,8,21],"2664":[7,8,64],"2665":[3,8,71],"2666":[1,8,57],"2667":[7,8,30],"2668":[3,15,19],"2669":[3,15,30],"2670":[3,15,37],"2671":[2,15,22],"2672":[2,15,32],"2673":[3,15,26],"2674":[6,8,34],"2675":[8,1,1],"2676":[1,8,3],"2677":[5,8,40],"2678":[7,8,72],"2679":[2,8,83],"2680":[2,1,27],"2681":[2,2,43],"2682":[2,2,49],"2683":[2,2,1],"2684":[3,3,58],"2685":[3,3,33],"2686":[3,3,31],"2687":[2,2,64],"2688":[4,4,80],"2689":[4,4,59],"2690":[4,4,13],"2691":[3,2,41],"2692":[4,5,46],"2693":[2,2,34],"2694":[5,3,54],"2695":[3,3,66],"2696":[2,3,30],"2697":[3,2,36],"2698":[2,2,1],"2699":[4,4,31],"2700":[2,4,31],"2701":[3,2,127],"2702":[3,2,58],"2703":[2,5,23],"2704":[2,5,29],"2705":[3,2,41],"2706":[2,2,38],"2707":[3,1,1],"2708":[1,3,1],"2709":[4,4,32],"2710":[6,4,16],"2711":[6,4,23],"2712":[7,4,52],"2713":[9,4,33],"2714":[12,4,32],"2715":[3,3,1],"2716":[6,5,20],"2717":[8,5,34],"2718":[8,5,22],"2719":[8,5,50],"2720":[1,3,1],"2721":[8,4,84],"2722":[9,4,57],"2723":[9,4,84],"2724":[14,4,27],"2725":[12,4,40],"2726":[13,4,49],"2727":[9,4,20],"2728":[9,4,30],"2729":[12,4,51],"2730":[1,3,1],"2731":[12,4,64],"2732":[8,4,32],"2733":[13,4,53],"2734":[10,4,34],"2735":[1,3,1],"2736":[6,4,30],"2737":[8,4,32],"2738":[1,3,1],"2739":[7,4,81],"2740":[13,4,45],"2741":[15,4,58],"2742":[6,4,66],"2743":[1,3,1],"2744":[5,4,42],"2745":[6,4,27],"2746":[7,4,8],"2747":[8,4,16],"2748":[3,3,1],"2749":[14,5,31],"2750":[12,5,46],"2751":[12,5,39],"2752":[8,5,36],"2753":[1,3,1],"2754":[7,3,30],"2755":[7,3,36],"2756":[5,3,14],"2757":[6,3,23],"2758":[4,3,54],"2759":[3,1,94],"2760":[3,3,86],"2761":[4,3,17],"2762":[5,3,143],"2763":[3,3,87],"2764":[4,3,102],"2765":[4,3,112],"2766":[4,3,107],"2767":[6,3,79],"2768":[5,3,91],"2769":[1,3,87],"2770":[5,3,36],"2771":[1,3,47],"2772":[1,1,71],"2773":[2,1,25],"2774":[3,3,94],"2775":[6,3,79],"2776":[3,1,66],"2777":[3,1,1],"2778":[2,3,1],"2779":[2,5,60],"2780":[3,5,1],"2781":[3,7,28],"2782":[3,7,31],"2783":[3,7,39],"2784":[3,7,33],"2785":[4,5,75],"2786":[2,3,42],"2787":[2,3,1],"2788":[4,4,44],"2789":[2,4,78],"2790":[2,4,59],"2791":[3,4,64],"2792":[3,3,103],"2793":[2,3,26],"2794":[1,1,83],"2795":[3,1,161],"2796":[1,1,1],"2797":[6,2,48],"2798":[6,2,51],"2799":[12,2,33],"2800":[7,2,46],"2801":[4,2,31],"2802":[5,1,122],"2803":[3,1,93],"2804":[4,1,115],"2805":[1,1,36],"2806":[2,1,91],"2807":[3,2,86],"2808":[3,2,25],"2809":[2,2,100],"2810":[2,2,107],"2811":[6,2,76],"2812":[5,2,123],"2813":[7,2,96],"2814":[1,2,102],"2815":[6,2,139],"2816":[5,2,44],"2817":[1,2,40],"2818":[2,1,28],"2819":[1,2,21],"2820":[6,2,29],"2821":[6,8,51],"2822":[4,8,49],"2823":[4,2,118],"2824":[7,2,198],"2825":[5,2,155],"2826":[2,2,59],"2827":[5,1,84],"2828":[3,5,113],"2829":[4,5,124],"2830":[4,5,120],"2831":[4,5,64],"2832":[5,5,60],"2833":[3,5,109],"2834":[4,5,111],"2835":[1,5,114],"2836":[6,5,154],"2837":[5,5,24],"2838":[1,5,42],"2839":[3,1,40],"2840":[3,3,130],"2841":[1,3,136],"2842":[3,3,63],"2843":[3,6,31],"2844":[1,3,20],"2845":[4,4,158],"2846":[4,4,44],"2847":[2,4,40],"2848":[2,4,67],"2849":[5,4,38],"2850":[3,3,59],"2851":[2,6,50],"2852":[2,6,56],"2853":[2,6,30],"2854":[4,3,74],"2855":[4,3,95],"2856":[4,3,59],"2857":[5,3,74],"2858":[4,3,96],"2859":[1,3,55],"2860":[1,1,137],"2861":[2,1,83],"2862":[3,1,129],"2863":[3,1,46],"2864":[1,4,120],"2865":[4,4,150],"2866":[3,4,80],"2867":[5,4,70],"2868":[8,4,185],"2869":[3,1,206],"2870":[3,1,58],"2871":[5,1,166],"2872":[6,1,98],"2873":[7,1,161],"2874":[4,1,78],"2875":[2,1,67],"2876":[6,1,77],"2877":[3,1,37],"2878":[2,1,155],"2879":[2,1,99],"2880":[3,1,120],"2881":[1,1,114],"2882":[1,1,27]},"averageFieldLength":[3.2365591397849456,5.156434269857778,56.49462365591402],"storedFields":{"0":{"title":"About This Website","titles":[]},"1":{"title":"How These Docs Are Made","titles":["About This Website"]},"2":{"title":"About the Author","titles":["About This Website"]},"3":{"title":"Feedback","titles":["About This Website"]},"4":{"title":"ALLOW_ANONYMOUS","titles":[]},"5":{"title":"Syntax","titles":["ALLOW_ANONYMOUS"]},"6":{"title":"Examples","titles":["ALLOW_ANONYMOUS"]},"7":{"title":"Public Endpoint","titles":["ALLOW_ANONYMOUS","Examples"]},"8":{"title":"Short Form","titles":["ALLOW_ANONYMOUS","Examples"]},"9":{"title":"Public Read, Protected Write Pattern","titles":["ALLOW_ANONYMOUS","Examples"]},"10":{"title":"Behavior","titles":["ALLOW_ANONYMOUS"]},"11":{"title":"Related","titles":["ALLOW_ANONYMOUS"]},"12":{"title":"Related Annotations","titles":["ALLOW_ANONYMOUS"]},"13":{"title":"AUTHORIZE","titles":[]},"14":{"title":"Syntax","titles":["AUTHORIZE"]},"15":{"title":"Examples","titles":["AUTHORIZE"]},"16":{"title":"Require Any Authenticated User","titles":["AUTHORIZE","Examples"]},"17":{"title":"Alternative Keywords","titles":["AUTHORIZE","Examples"]},"18":{"title":"Require Specific Role","titles":["AUTHORIZE","Examples"]},"19":{"title":"Authorize by User Name","titles":["AUTHORIZE","Examples"]},"20":{"title":"Authorize by User ID","titles":["AUTHORIZE","Examples"]},"21":{"title":"Multiple Roles","titles":["AUTHORIZE","Examples"]},"22":{"title":"Mix of Roles and User Identifiers","titles":["AUTHORIZE","Examples"]},"23":{"title":"Authorize Before HTTP","titles":["AUTHORIZE","Examples"]},"24":{"title":"Authorize on Separate Line","titles":["AUTHORIZE","Examples"]},"25":{"title":"Behavior","titles":["AUTHORIZE"]},"26":{"title":"Related","titles":["AUTHORIZE"]},"27":{"title":"Related Annotations","titles":["AUTHORIZE"]},"28":{"title":"See Also","titles":["AUTHORIZE"]},"29":{"title":"BASIC_AUTH_COMMAND","titles":[]},"30":{"title":"Syntax","titles":["BASIC_AUTH_COMMAND"]},"31":{"title":"Command Parameters","titles":["BASIC_AUTH_COMMAND"]},"32":{"title":"Return Value","titles":["BASIC_AUTH_COMMAND"]},"33":{"title":"Special Columns","titles":["BASIC_AUTH_COMMAND","Return Value"]},"34":{"title":"Authentication Success","titles":["BASIC_AUTH_COMMAND","Return Value"]},"35":{"title":"Authentication Failure","titles":["BASIC_AUTH_COMMAND","Return Value"]},"36":{"title":"Examples","titles":["BASIC_AUTH_COMMAND"]},"37":{"title":"Basic Challenge Command","titles":["BASIC_AUTH_COMMAND","Examples"]},"38":{"title":"Challenge Command with Pre-Validated Password","titles":["BASIC_AUTH_COMMAND","Examples"]},"39":{"title":"Challenge Command That Denies Access","titles":["BASIC_AUTH_COMMAND","Examples"]},"40":{"title":"Challenge Command Without Annotation Credentials","titles":["BASIC_AUTH_COMMAND","Examples"]},"41":{"title":"Behavior","titles":["BASIC_AUTH_COMMAND"]},"42":{"title":"Related","titles":["BASIC_AUTH_COMMAND"]},"43":{"title":"Related Annotations","titles":["BASIC_AUTH_COMMAND"]},"44":{"title":"BASIC_AUTH_REALM","titles":[]},"45":{"title":"Syntax","titles":["BASIC_AUTH_REALM"]},"46":{"title":"Default Value","titles":["BASIC_AUTH_REALM"]},"47":{"title":"Examples","titles":["BASIC_AUTH_REALM"]},"48":{"title":"Set Realm Name","titles":["BASIC_AUTH_REALM","Examples"]},"49":{"title":"Alternative Keyword","titles":["BASIC_AUTH_REALM","Examples"]},"50":{"title":"With Challenge Command","titles":["BASIC_AUTH_REALM","Examples"]},"51":{"title":"Behavior","titles":["BASIC_AUTH_REALM"]},"52":{"title":"Realm Resolution Order","titles":["BASIC_AUTH_REALM"]},"53":{"title":"Related","titles":["BASIC_AUTH_REALM"]},"54":{"title":"Related Annotations","titles":["BASIC_AUTH_REALM"]},"55":{"title":"BASIC_AUTH","titles":[]},"56":{"title":"Syntax","titles":["BASIC_AUTH"]},"57":{"title":"Generating Password Hashes","titles":["BASIC_AUTH"]},"58":{"title":"Generating Authorization Headers","titles":["BASIC_AUTH"]},"59":{"title":"Examples","titles":["BASIC_AUTH"]},"60":{"title":"Basic Auth Without Credentials (Requires Challenge Command)","titles":["BASIC_AUTH","Examples"]},"61":{"title":"Basic Auth With Credentials","titles":["BASIC_AUTH","Examples"]},"62":{"title":"Multiple Users","titles":["BASIC_AUTH","Examples"]},"63":{"title":"Behavior","titles":["BASIC_AUTH"]},"64":{"title":"SSL Requirements","titles":["BASIC_AUTH"]},"65":{"title":"Related","titles":["BASIC_AUTH"]},"66":{"title":"Related Annotations","titles":["BASIC_AUTH"]},"67":{"title":"See Also","titles":["BASIC_AUTH"]},"68":{"title":"BODY_PARAMETER_NAME","titles":[]},"69":{"title":"Syntax","titles":["BODY_PARAMETER_NAME"]},"70":{"title":"Examples","titles":["BODY_PARAMETER_NAME"]},"71":{"title":"Custom Body Parameter","titles":["BODY_PARAMETER_NAME","Examples"]},"72":{"title":"JSON Body Parameter","titles":["BODY_PARAMETER_NAME","Examples"]},"73":{"title":"Behavior","titles":["BODY_PARAMETER_NAME"]},"74":{"title":"Matching Rules","titles":["BODY_PARAMETER_NAME"]},"75":{"title":"Redirecting an HTTP Custom Type field into a proxy body","titles":["BODY_PARAMETER_NAME","Matching Rules"]},"76":{"title":"Related","titles":["BODY_PARAMETER_NAME"]},"77":{"title":"Related Annotations","titles":["BODY_PARAMETER_NAME"]},"78":{"title":"BUFFER_ROWS","titles":[]},"79":{"title":"Syntax","titles":["BUFFER_ROWS"]},"80":{"title":"Default Value","titles":["BUFFER_ROWS"]},"81":{"title":"Special Values","titles":["BUFFER_ROWS"]},"82":{"title":"Examples","titles":["BUFFER_ROWS"]},"83":{"title":"Disable Buffering","titles":["BUFFER_ROWS","Examples"]},"84":{"title":"Buffer Entire Response","titles":["BUFFER_ROWS","Examples"]},"85":{"title":"Large Buffer for Throughput","titles":["BUFFER_ROWS","Examples"]},"86":{"title":"Small Buffer for Memory Efficiency","titles":["BUFFER_ROWS","Examples"]},"87":{"title":"Behavior","titles":["BUFFER_ROWS"]},"88":{"title":"Performance Considerations","titles":["BUFFER_ROWS"]},"89":{"title":"Related","titles":["BUFFER_ROWS"]},"90":{"title":"Related Annotations","titles":["BUFFER_ROWS"]},"91":{"title":"CACHE_EXPIRES_IN","titles":[]},"92":{"title":"Syntax","titles":["CACHE_EXPIRES_IN"]},"93":{"title":"Examples","titles":["CACHE_EXPIRES_IN"]},"94":{"title":"Short Cache (10 seconds)","titles":["CACHE_EXPIRES_IN","Examples"]},"95":{"title":"Medium Cache (5 minutes)","titles":["CACHE_EXPIRES_IN","Examples"]},"96":{"title":"Long Cache (1 hour)","titles":["CACHE_EXPIRES_IN","Examples"]},"97":{"title":"Daily Cache","titles":["CACHE_EXPIRES_IN","Examples"]},"98":{"title":"Related","titles":["CACHE_EXPIRES_IN"]},"99":{"title":"Related Annotations","titles":["CACHE_EXPIRES_IN"]},"100":{"title":"See Also","titles":["CACHE_EXPIRES_IN"]},"101":{"title":"CACHE_PROFILE","titles":[]},"102":{"title":"Syntax","titles":["CACHE_PROFILE"]},"103":{"title":"Examples","titles":["CACHE_PROFILE"]},"104":{"title":"Basic usage","titles":["CACHE_PROFILE","Examples"]},"105":{"title":"Combined with cached / cache_expires","titles":["CACHE_PROFILE","Examples"]},"106":{"title":"Multi-tenant search_path pattern","titles":["CACHE_PROFILE","Examples"]},"107":{"title":"Tiered TTL by user role","titles":["CACHE_PROFILE","Examples"]},"108":{"title":"Behavior","titles":["CACHE_PROFILE"]},"109":{"title":"Validation","titles":["CACHE_PROFILE"]},"110":{"title":"Related","titles":["CACHE_PROFILE"]},"111":{"title":"See Also","titles":["CACHE_PROFILE"]},"112":{"title":"CACHED","titles":[]},"113":{"title":"Syntax","titles":["CACHED"]},"114":{"title":"Examples","titles":["CACHED"]},"115":{"title":"Simple Caching","titles":["CACHED","Examples"]},"116":{"title":"Cache Key by Parameter","titles":["CACHED","Examples"]},"117":{"title":"Multiple Cache Key Parameters","titles":["CACHED","Examples"]},"118":{"title":"With Cache Expiration","titles":["CACHED","Examples"]},"119":{"title":"Caching Set-Returning Functions","titles":["CACHED","Examples"]},"120":{"title":"Behavior","titles":["CACHED"]},"121":{"title":"Cache Configuration","titles":["CACHED"]},"122":{"title":"Related","titles":["CACHED"]},"123":{"title":"Related Annotations","titles":["CACHED"]},"124":{"title":"See Also","titles":["CACHED"]},"125":{"title":"COLUMN_NAMES","titles":[]},"126":{"title":"Syntax","titles":["COLUMN_NAMES"]},"127":{"title":"Examples","titles":["COLUMN_NAMES"]},"128":{"title":"CSV with Headers","titles":["COLUMN_NAMES","Examples"]},"129":{"title":"TSV with Headers","titles":["COLUMN_NAMES","Examples"]},"130":{"title":"Related","titles":["COLUMN_NAMES"]},"131":{"title":"Related Annotations","titles":["COLUMN_NAMES"]},"132":{"title":"COMMAND_TIMEOUT","titles":[]},"133":{"title":"Syntax","titles":["COMMAND_TIMEOUT"]},"134":{"title":"Default Value","titles":["COMMAND_TIMEOUT"]},"135":{"title":"Examples","titles":["COMMAND_TIMEOUT"]},"136":{"title":"Short Timeout","titles":["COMMAND_TIMEOUT","Examples"]},"137":{"title":"Long Running Query","titles":["COMMAND_TIMEOUT","Examples"]},"138":{"title":"Using Seconds Format","titles":["COMMAND_TIMEOUT","Examples"]},"139":{"title":"Behavior","titles":["COMMAND_TIMEOUT"]},"140":{"title":"Timeout Response","titles":["COMMAND_TIMEOUT"]},"141":{"title":"Related","titles":["COMMAND_TIMEOUT"]},"142":{"title":"Related Annotations","titles":["COMMAND_TIMEOUT"]},"143":{"title":"See Also","titles":["COMMAND_TIMEOUT"]},"144":{"title":"CONNECTION","titles":[]},"145":{"title":"Syntax","titles":["CONNECTION"]},"146":{"title":"Examples","titles":["CONNECTION"]},"147":{"title":"Use Named Connection","titles":["CONNECTION","Examples"]},"148":{"title":"Reporting Database","titles":["CONNECTION","Examples"]},"149":{"title":"Read Replica","titles":["CONNECTION","Examples"]},"150":{"title":"Behavior","titles":["CONNECTION"]},"151":{"title":"Related","titles":["CONNECTION"]},"152":{"title":"Related Annotations","titles":["CONNECTION"]},"153":{"title":"See Also","titles":["CONNECTION"]},"154":{"title":"Custom Parameters","titles":[]},"155":{"title":"Syntax","titles":["Custom Parameters"]},"156":{"title":"Dynamic Parameter Values","titles":["Custom Parameters"]},"157":{"title":"Example","titles":["Custom Parameters","Dynamic Parameter Values"]},"158":{"title":"Built-in Parameters","titles":["Custom Parameters"]},"159":{"title":"General","titles":["Custom Parameters","Built-in Parameters"]},"160":{"title":"Upload","titles":["Custom Parameters","Built-in Parameters"]},"161":{"title":"Table Format","titles":["Custom Parameters","Built-in Parameters"]},"162":{"title":"Server-Sent Events","titles":["Custom Parameters","Built-in Parameters"]},"163":{"title":"TypeScript Client","titles":["Custom Parameters","Built-in Parameters"]},"164":{"title":"Related","titles":["Custom Parameters"]},"165":{"title":"DEFINE_PARAM","titles":[]},"166":{"title":"Syntax","titles":["DEFINE_PARAM"]},"167":{"title":"Custom Parameter Placeholders","titles":["DEFINE_PARAM"]},"168":{"title":"Claim Mapping","titles":["DEFINE_PARAM"]},"169":{"title":"Default Type","titles":["DEFINE_PARAM"]},"170":{"title":"Related","titles":["DEFINE_PARAM"]},"171":{"title":"DISABLED","titles":[]},"172":{"title":"Keywords","titles":["DISABLED"]},"173":{"title":"Syntax","titles":["DISABLED"]},"174":{"title":"Example","titles":["DISABLED"]},"175":{"title":"Tag-conditional form","titles":["DISABLED"]},"176":{"title":"Related","titles":["DISABLED"]},"177":{"title":"ENABLED","titles":[]},"178":{"title":"Keywords","titles":["ENABLED"]},"179":{"title":"Syntax","titles":["ENABLED"]},"180":{"title":"Example: disable-by-default, enable for immutable only","titles":["ENABLED"]},"181":{"title":"Related","titles":["ENABLED"]},"182":{"title":"ENCRYPT / DECRYPT","titles":[]},"183":{"title":"Encrypt Parameters","titles":["ENCRYPT / DECRYPT"]},"184":{"title":"Syntax","titles":["ENCRYPT / DECRYPT","Encrypt Parameters"]},"185":{"title":"Decrypt Result Columns","titles":["ENCRYPT / DECRYPT"]},"186":{"title":"Syntax","titles":["ENCRYPT / DECRYPT","Decrypt Result Columns"]},"187":{"title":"Full Roundtrip Example","titles":["ENCRYPT / DECRYPT"]},"188":{"title":"Behavior","titles":["ENCRYPT / DECRYPT"]},"189":{"title":"Related","titles":["ENCRYPT / DECRYPT"]},"190":{"title":"Related Annotations","titles":["ENCRYPT / DECRYPT"]},"191":{"title":"See Also","titles":["ENCRYPT / DECRYPT"]},"192":{"title":"ERROR_CODE_POLICY","titles":[]},"193":{"title":"Syntax","titles":["ERROR_CODE_POLICY"]},"194":{"title":"Examples","titles":["ERROR_CODE_POLICY"]},"195":{"title":"Named Policy","titles":["ERROR_CODE_POLICY","Examples"]},"196":{"title":"Short Form","titles":["ERROR_CODE_POLICY","Examples"]},"197":{"title":"Behavior","titles":["ERROR_CODE_POLICY"]},"198":{"title":"Related","titles":["ERROR_CODE_POLICY"]},"199":{"title":"Related Annotations","titles":["ERROR_CODE_POLICY"]},"200":{"title":"See Also","titles":["ERROR_CODE_POLICY"]},"201":{"title":"HTTP CUSTOM TYPES","titles":[]},"202":{"title":"Overview","titles":["HTTP CUSTOM TYPES"]},"203":{"title":"Syntax","titles":["HTTP CUSTOM TYPES"]},"204":{"title":"Supported Methods","titles":["HTTP CUSTOM TYPES"]},"205":{"title":"Examples","titles":["HTTP CUSTOM TYPES"]},"206":{"title":"Basic GET Request","titles":["HTTP CUSTOM TYPES","Examples"]},"207":{"title":"GET with Headers and Placeholders","titles":["HTTP CUSTOM TYPES","Examples"]},"208":{"title":"POST with Request Body","titles":["HTTP CUSTOM TYPES","Examples"]},"209":{"title":"Multiple API Calls","titles":["HTTP CUSTOM TYPES","Examples"]},"210":{"title":"Response Fields","titles":["HTTP CUSTOM TYPES"]},"211":{"title":"Timeout Directives","titles":["HTTP CUSTOM TYPES"]},"212":{"title":"Placeholder Substitution","titles":["HTTP CUSTOM TYPES"]},"213":{"title":"Retry Logic","titles":["HTTP CUSTOM TYPES"]},"214":{"title":"Response Caching","titles":["HTTP CUSTOM TYPES"]},"215":{"title":"Resolved Parameter Expressions","titles":["HTTP CUSTOM TYPES"]},"216":{"title":"Behavior","titles":["HTTP CUSTOM TYPES"]},"217":{"title":"Related","titles":["HTTP CUSTOM TYPES"]},"218":{"title":"Related Annotations","titles":["HTTP CUSTOM TYPES"]},"219":{"title":"See Also","titles":["HTTP CUSTOM TYPES"]},"220":{"title":"Annotations Reference","titles":[]},"221":{"title":"How to Use This Reference","titles":["Annotations Reference"]},"222":{"title":"Annotation Categories","titles":["Annotations Reference"]},"223":{"title":"HTTP & Routing","titles":["Annotations Reference","Annotation Categories"]},"224":{"title":"Authorization","titles":["Annotations Reference","Annotation Categories"]},"225":{"title":"Basic Authentication","titles":["Annotations Reference","Annotation Categories"]},"226":{"title":"Request Configuration","titles":["Annotations Reference","Annotation Categories"]},"227":{"title":"Response Configuration","titles":["Annotations Reference","Annotation Categories"]},"228":{"title":"Table Format Output","titles":["Annotations Reference","Annotation Categories"]},"229":{"title":"Raw Output Mode","titles":["Annotations Reference","Annotation Categories"]},"230":{"title":"Caching","titles":["Annotations Reference","Annotation Categories"]},"231":{"title":"Performance","titles":["Annotations Reference","Annotation Categories"]},"232":{"title":"Format References","titles":["Annotations Reference","Annotation Categories"]},"233":{"title":"Server-Sent Events","titles":["Annotations Reference","Annotation Categories"]},"234":{"title":"Upload","titles":["Annotations Reference","Annotation Categories"]},"235":{"title":"Policies","titles":["Annotations Reference","Annotation Categories"]},"236":{"title":"Context & Security","titles":["Annotations Reference","Annotation Categories"]},"237":{"title":"Parameter Annotations","titles":["Annotations Reference","Annotation Categories"]},"238":{"title":"SQL File Annotations","titles":["Annotations Reference","Annotation Categories"]},"239":{"title":"Test File Annotations","titles":["Annotations Reference","Annotation Categories"]},"240":{"title":"Custom","titles":["Annotations Reference","Annotation Categories"]},"241":{"title":"HTTP","titles":[]},"242":{"title":"Keywords","titles":["HTTP"]},"243":{"title":"Syntax","titles":["HTTP"]},"244":{"title":"CommentsMode Requirement","titles":["HTTP"]},"245":{"title":"Default Behavior","titles":["HTTP"]},"246":{"title":"Examples","titles":["HTTP"]},"247":{"title":"Basic Endpoint","titles":["HTTP","Examples"]},"248":{"title":"Explicit HTTP Method","titles":["HTTP","Examples"]},"249":{"title":"Custom Path","titles":["HTTP","Examples"]},"250":{"title":"Method and Custom Path","titles":["HTTP","Examples"]},"251":{"title":"Multi-line with Documentation","titles":["HTTP","Examples"]},"252":{"title":"Unrecognized Method Becomes Path","titles":["HTTP","Examples"]},"253":{"title":"Path Parameters","titles":["HTTP"]},"254":{"title":"Single Path Parameter","titles":["HTTP","Path Parameters"]},"255":{"title":"Multiple Path Parameters","titles":["HTTP","Path Parameters"]},"256":{"title":"Path Parameters with Query String","titles":["HTTP","Path Parameters"]},"257":{"title":"Path Parameters with JSON Body","titles":["HTTP","Path Parameters"]},"258":{"title":"Path Parameter Key Features","titles":["HTTP","Path Parameters"]},"259":{"title":"Related","titles":["HTTP"]},"260":{"title":"Related Annotations","titles":["HTTP"]},"261":{"title":"INTERNAL","titles":[]},"262":{"title":"Syntax","titles":["INTERNAL"]},"263":{"title":"Example: Internal Helper with Proxy","titles":["INTERNAL"]},"264":{"title":"Example: Internal Helper with HTTP Client Types","titles":["INTERNAL"]},"265":{"title":"SQL File Endpoints","titles":["INTERNAL"]},"266":{"title":"Related","titles":["INTERNAL"]},"267":{"title":"Interval Format Reference","titles":[]},"268":{"title":"Syntax","titles":["Interval Format Reference"]},"269":{"title":"Supported Units","titles":["Interval Format Reference"]},"270":{"title":"Examples","titles":["Interval Format Reference"]},"271":{"title":"Short Form (Recommended)","titles":["Interval Format Reference","Examples"]},"272":{"title":"Long Form","titles":["Interval Format Reference","Examples"]},"273":{"title":"With Space","titles":["Interval Format Reference","Examples"]},"274":{"title":"Decimal Values","titles":["Interval Format Reference","Examples"]},"275":{"title":"No Unit (Defaults to Seconds)","titles":["Interval Format Reference","Examples"]},"276":{"title":"Usage in Annotations","titles":["Interval Format Reference"]},"277":{"title":"@timeout / @command_timeout","titles":["Interval Format Reference","Usage in Annotations"]},"278":{"title":"@cache_expires_in","titles":["Interval Format Reference","Usage in Annotations"]},"279":{"title":"Configuration Values","titles":["Interval Format Reference"]},"280":{"title":"Invalid Formats","titles":["Interval Format Reference"]},"281":{"title":"Related","titles":["Interval Format Reference"]},"282":{"title":"LOGOUT","titles":[]},"283":{"title":"Syntax","titles":["LOGOUT"]},"284":{"title":"Logout Endpoint Behavior","titles":["LOGOUT"]},"285":{"title":"Void Functions","titles":["LOGOUT","Logout Endpoint Behavior"]},"286":{"title":"Functions with Return Values","titles":["LOGOUT","Logout Endpoint Behavior"]},"287":{"title":"Examples","titles":["LOGOUT"]},"288":{"title":"Basic Logout (Void)","titles":["LOGOUT","Examples"]},"289":{"title":"Logout from Specific Scheme","titles":["LOGOUT","Examples"]},"290":{"title":"Logout from Multiple Schemes","titles":["LOGOUT","Examples"]},"291":{"title":"Conditional Scheme Logout","titles":["LOGOUT","Examples"]},"292":{"title":"Logout with Cleanup","titles":["LOGOUT","Examples"]},"293":{"title":"Related","titles":["LOGOUT"]},"294":{"title":"Related Annotations","titles":["LOGOUT"]},"295":{"title":"See Also","titles":["LOGOUT"]},"296":{"title":"LOGIN","titles":[]},"297":{"title":"How a login endpoint works","titles":["LOGIN"]},"298":{"title":"Minimal example","titles":["LOGIN"]},"299":{"title":"The return record","titles":["LOGIN"]},"300":{"title":"Special columns","titles":["LOGIN"]},"301":{"title":"Status column","titles":["LOGIN","Special columns"]},"302":{"title":"Scheme column","titles":["LOGIN","Special columns"]},"303":{"title":"Body column","titles":["LOGIN","Special columns"]},"304":{"title":"Claims: how columns become the user","titles":["LOGIN"]},"305":{"title":"Identity claims","titles":["LOGIN","Claims: how columns become the user"]},"306":{"title":"Using claims in your other endpoints","titles":["LOGIN","Claims: how columns become the user"]},"307":{"title":"Password verification","titles":["LOGIN"]},"308":{"title":"Option A — verify in SQL (no hash column)","titles":["LOGIN","Password verification"]},"309":{"title":"Option B — built-in hasher (return a hash column)","titles":["LOGIN","Password verification"]},"310":{"title":"Verification callbacks","titles":["LOGIN","Password verification"]},"311":{"title":"More examples","titles":["LOGIN"]},"312":{"title":"Multiple schemes from one login","titles":["LOGIN","More examples"]},"313":{"title":"Explicit status code and message","titles":["LOGIN","More examples"]},"314":{"title":"Role-protected endpoint after login","titles":["LOGIN","More examples"]},"315":{"title":"Related","titles":["LOGIN"]},"316":{"title":"Related Annotations","titles":["LOGIN"]},"317":{"title":"MCP","titles":[]},"318":{"title":"Syntax","titles":["MCP"]},"319":{"title":"Description precedence","titles":["MCP","Syntax"]},"320":{"title":"MCP-only tools (no HTTP route)","titles":["MCP"]},"321":{"title":"Examples","titles":["MCP"]},"322":{"title":"Expose a routine as a tool (HTTP and MCP)","titles":["MCP","Examples"]},"323":{"title":"Description from comment prose","titles":["MCP","Examples"]},"324":{"title":"Explicit description, with a private note that stays out of it","titles":["MCP","Examples"]},"325":{"title":"Override the tool name","titles":["MCP","Examples"]},"326":{"title":"Recognized keywords","titles":["MCP"]},"327":{"title":"Related","titles":["MCP"]},"328":{"title":"NESTED","titles":[]},"329":{"title":"Syntax","titles":["NESTED"]},"330":{"title":"Default Behavior vs Nested","titles":["NESTED"]},"331":{"title":"Examples","titles":["NESTED"]},"332":{"title":"Basic Usage","titles":["NESTED","Examples"]},"333":{"title":"Multiple Composite Columns","titles":["NESTED","Examples"]},"334":{"title":"Deep Nested Composite Types","titles":["NESTED","Examples"]},"335":{"title":"Arrays of Composite Types","titles":["NESTED","Examples"]},"336":{"title":"Global Configuration","titles":["NESTED"]},"337":{"title":"Behavior","titles":["NESTED"]},"338":{"title":"Related","titles":["NESTED"]},"339":{"title":"NEW_LINE","titles":[]},"340":{"title":"Syntax","titles":["NEW_LINE"]},"341":{"title":"Examples","titles":["NEW_LINE"]},"342":{"title":"Unix Line Endings","titles":["NEW_LINE","Examples"]},"343":{"title":"Windows Line Endings","titles":["NEW_LINE","Examples"]},"344":{"title":"Custom Row Separator","titles":["NEW_LINE","Examples"]},"345":{"title":"Related","titles":["NEW_LINE"]},"346":{"title":"Related Annotations","titles":["NEW_LINE"]},"347":{"title":"OPENAPI","titles":[]},"348":{"title":"Syntax","titles":["OPENAPI"]},"349":{"title":"How it composes with config-level filters","titles":["OPENAPI"]},"350":{"title":"Examples","titles":["OPENAPI"]},"351":{"title":"Hide an internal maintenance routine","titles":["OPENAPI","Examples"]},"352":{"title":"Group routines under a custom tag","titles":["OPENAPI","Examples"]},"353":{"title":"Multiple tags","titles":["OPENAPI","Examples"]},"354":{"title":"Hide alongside a config filter","titles":["OPENAPI","Examples"]},"355":{"title":"Recognized keywords","titles":["OPENAPI"]},"356":{"title":"Related","titles":["OPENAPI"]},"357":{"title":"PARAMETER_HASH","titles":[]},"358":{"title":"Syntax","titles":["PARAMETER_HASH"]},"359":{"title":"Examples","titles":["PARAMETER_HASH"]},"360":{"title":"Simple User Registration","titles":["PARAMETER_HASH","Examples"]},"361":{"title":"User Registration with Response","titles":["PARAMETER_HASH","Examples"]},"362":{"title":"Behavior","titles":["PARAMETER_HASH"]},"363":{"title":"Built-in Password Hasher","titles":["PARAMETER_HASH"]},"364":{"title":"Complete Registration and Login Flow","titles":["PARAMETER_HASH"]},"365":{"title":"Registration Function","titles":["PARAMETER_HASH","Complete Registration and Login Flow"]},"366":{"title":"Login Function","titles":["PARAMETER_HASH","Complete Registration and Login Flow"]},"367":{"title":"Related","titles":["PARAMETER_HASH"]},"368":{"title":"Related Annotations","titles":["PARAMETER_HASH"]},"369":{"title":"PARAM","titles":[]},"370":{"title":"Syntax","titles":["PARAM"]},"371":{"title":"Examples","titles":["PARAM"]},"372":{"title":"Rename Positional Parameters (SQL Files)","titles":["PARAM","Examples"]},"373":{"title":"Rename with Type Override","titles":["PARAM","Examples"]},"374":{"title":"Rename Function Parameters","titles":["PARAM","Examples"]},"375":{"title":""is" Style Syntax","titles":["PARAM","Examples"]},"376":{"title":"Claim Mapping with Renamed Parameters","titles":["PARAM"]},"377":{"title":"Default Values (SQL File Parameters)","titles":["PARAM"]},"378":{"title":"Syntax","titles":["PARAM","Default Values (SQL File Parameters)"]},"379":{"title":"Value Parsing Rules (SQL Conventions)","titles":["PARAM","Default Values (SQL File Parameters)"]},"380":{"title":"Example","titles":["PARAM","Default Values (SQL File Parameters)"]},"381":{"title":"Effects on Generated Output","titles":["PARAM","Default Values (SQL File Parameters)"]},"382":{"title":"Rename Validation","titles":["PARAM"]},"383":{"title":"Composite Type Parameters (SQL Files)","titles":["PARAM"]},"384":{"title":"Behavior","titles":["PARAM"]},"385":{"title":"Related","titles":["PARAM"]},"386":{"title":"Parameter Value Substitution","titles":[]},"387":{"title":"Where it works","titles":["Parameter Value Substitution"]},"388":{"title":"How a placeholder is resolved","titles":["Parameter Value Substitution"]},"389":{"title":"Brace handling","titles":["Parameter Value Substitution","How a placeholder is resolved"]},"390":{"title":"Environment variables","titles":["Parameter Value Substitution"]},"391":{"title":"Examples","titles":["Parameter Value Substitution"]},"392":{"title":"Dynamic file download","titles":["Parameter Value Substitution","Examples"]},"393":{"title":"Upload destination from a parameter","titles":["Parameter Value Substitution","Examples"]},"394":{"title":"Outbound HTTP call shaped by parameters","titles":["Parameter Value Substitution","Examples"]},"395":{"title":"Not to be confused with","titles":["Parameter Value Substitution"]},"396":{"title":"Related","titles":["Parameter Value Substitution"]},"397":{"title":"PATH","titles":[]},"398":{"title":"Keywords","titles":["PATH"]},"399":{"title":"Syntax","titles":["PATH"]},"400":{"title":"Examples","titles":["PATH"]},"401":{"title":"Custom Path","titles":["PATH","Examples"]},"402":{"title":"Path with HTTP Method","titles":["PATH","Examples"]},"403":{"title":"Versioned API","titles":["PATH","Examples"]},"404":{"title":"Path Parameters","titles":["PATH"]},"405":{"title":"Basic Path Parameter","titles":["PATH","Path Parameters"]},"406":{"title":"Nested Path Parameters","titles":["PATH","Path Parameters"]},"407":{"title":"Parameter Name Matching","titles":["PATH","Path Parameters"]},"408":{"title":"Optional Path Parameters","titles":["PATH","Path Parameters"]},"409":{"title":"Behavior","titles":["PATH"]},"410":{"title":"Related","titles":["PATH"]},"411":{"title":"Related Annotations","titles":["PATH"]},"412":{"title":"PROXY_OUT","titles":[]},"413":{"title":"Syntax","titles":["PROXY_OUT"]},"414":{"title":"Description","titles":["PROXY_OUT"]},"415":{"title":"Basic Usage","titles":["PROXY_OUT"]},"416":{"title":"Proxy Annotations","titles":["PROXY_OUT"]},"417":{"title":"Basic Proxy Out with Default Host","titles":["PROXY_OUT","Proxy Annotations"]},"418":{"title":"Proxy Out with Custom Host","titles":["PROXY_OUT","Proxy Annotations"]},"419":{"title":"Proxy Out with HTTP Method Override","titles":["PROXY_OUT","Proxy Annotations"]},"420":{"title":"Combined Method and Host","titles":["PROXY_OUT","Proxy Annotations"]},"421":{"title":"Self-Referencing Proxy Out (Relative Path)","titles":["PROXY_OUT","Proxy Annotations"]},"422":{"title":"URL Resolution","titles":["PROXY_OUT"]},"423":{"title":"Path and Query String Forwarding","titles":["PROXY_OUT"]},"424":{"title":"Error Handling","titles":["PROXY_OUT"]},"425":{"title":"Examples","titles":["PROXY_OUT"]},"426":{"title":"PDF Rendering Pipeline","titles":["PROXY_OUT","Examples"]},"427":{"title":"ML Inference","titles":["PROXY_OUT","Examples"]},"428":{"title":"Email Sending","titles":["PROXY_OUT","Examples"]},"429":{"title":"TypeScript Client","titles":["PROXY_OUT"]},"430":{"title":"Configuration","titles":["PROXY_OUT"]},"431":{"title":"Related","titles":["PROXY_OUT"]},"432":{"title":"See Also","titles":["PROXY_OUT"]},"433":{"title":"PROXY","titles":[]},"434":{"title":"Syntax","titles":["PROXY"]},"435":{"title":"Description","titles":["PROXY"]},"436":{"title":"How the target URL is built","titles":["PROXY"]},"437":{"title":"Basic Usage","titles":["PROXY"]},"438":{"title":"Passthrough Mode","titles":["PROXY","Basic Usage"]},"439":{"title":"Transform Mode","titles":["PROXY","Basic Usage"]},"440":{"title":"Proxy Annotations","titles":["PROXY"]},"441":{"title":"Basic Proxy with Default Host","titles":["PROXY","Proxy Annotations"]},"442":{"title":"Proxy with Custom Host","titles":["PROXY","Proxy Annotations"]},"443":{"title":"Proxy with Custom HTTP Method","titles":["PROXY","Proxy Annotations"]},"444":{"title":"Combined Method and Host","titles":["PROXY","Proxy Annotations"]},"445":{"title":"Self-Referencing Proxy (Relative Path)","titles":["PROXY","Proxy Annotations"]},"446":{"title":"URL Resolution","titles":["PROXY"]},"447":{"title":"Response Parameters","titles":["PROXY"]},"448":{"title":"How parameters are mapped","titles":["PROXY","Response Parameters"]},"449":{"title":"Custom parameter names","titles":["PROXY","Response Parameters"]},"450":{"title":"Examples","titles":["PROXY"]},"451":{"title":"API Gateway Pattern","titles":["PROXY","Examples"]},"452":{"title":"Data Enrichment","titles":["PROXY","Examples"]},"453":{"title":"Authenticated Proxy with User Context","titles":["PROXY","Examples"]},"454":{"title":"Proxy with User Parameters","titles":["PROXY","Examples"]},"455":{"title":"Configuration","titles":["PROXY"]},"456":{"title":"Related","titles":["PROXY"]},"457":{"title":"See Also","titles":["PROXY"]},"458":{"title":"QUERY_STRING_NULL_HANDLING","titles":[]},"459":{"title":"Syntax","titles":["QUERY_STRING_NULL_HANDLING"]},"460":{"title":"Values","titles":["QUERY_STRING_NULL_HANDLING"]},"461":{"title":"Behavior Explained","titles":["QUERY_STRING_NULL_HANDLING"]},"462":{"title":"Ignore Mode (Default)","titles":["QUERY_STRING_NULL_HANDLING","Behavior Explained"]},"463":{"title":"EmptyString Mode","titles":["QUERY_STRING_NULL_HANDLING","Behavior Explained"]},"464":{"title":"NullLiteral Mode","titles":["QUERY_STRING_NULL_HANDLING","Behavior Explained"]},"465":{"title":"Examples","titles":["QUERY_STRING_NULL_HANDLING"]},"466":{"title":"Using Empty String for NULL","titles":["QUERY_STRING_NULL_HANDLING","Examples"]},"467":{"title":"Using "null" String for NULL","titles":["QUERY_STRING_NULL_HANDLING","Examples"]},"468":{"title":"Default Behavior (No Special Handling)","titles":["QUERY_STRING_NULL_HANDLING","Examples"]},"469":{"title":"Path Parameters","titles":["QUERY_STRING_NULL_HANDLING"]},"470":{"title":"Configuration Default","titles":["QUERY_STRING_NULL_HANDLING"]},"471":{"title":"Related","titles":["QUERY_STRING_NULL_HANDLING"]},"472":{"title":"Related Annotations","titles":["QUERY_STRING_NULL_HANDLING"]},"473":{"title":"RATE_LIMITER_POLICY","titles":[]},"474":{"title":"Syntax","titles":["RATE_LIMITER_POLICY"]},"475":{"title":"Examples","titles":["RATE_LIMITER_POLICY"]},"476":{"title":"Fixed Window Policy","titles":["RATE_LIMITER_POLICY","Examples"]},"477":{"title":"Token Bucket Policy","titles":["RATE_LIMITER_POLICY","Examples"]},"478":{"title":"Combined with Authorization","titles":["RATE_LIMITER_POLICY","Examples"]},"479":{"title":"Per-User Rate Limiting","titles":["RATE_LIMITER_POLICY","Examples"]},"480":{"title":"Behavior","titles":["RATE_LIMITER_POLICY"]},"481":{"title":"Related","titles":["RATE_LIMITER_POLICY"]},"482":{"title":"Related Annotations","titles":["RATE_LIMITER_POLICY"]},"483":{"title":"See Also","titles":["RATE_LIMITER_POLICY"]},"484":{"title":"RAW","titles":[]},"485":{"title":"Syntax","titles":["RAW"]},"486":{"title":"Examples","titles":["RAW"]},"487":{"title":"Basic Raw Output","titles":["RAW","Examples"]},"488":{"title":"Raw with Multiple Columns","titles":["RAW","Examples"]},"489":{"title":"CSV Export","titles":["RAW","Examples"]},"490":{"title":"Tab-Separated Values","titles":["RAW","Examples"]},"491":{"title":"Pipe-Delimited Format","titles":["RAW","Examples"]},"492":{"title":"Download as File","titles":["RAW","Examples"]},"493":{"title":"Dynamic CSV Download","titles":["RAW","Examples"]},"494":{"title":"Behavior","titles":["RAW"]},"495":{"title":"Related","titles":["RAW"]},"496":{"title":"Related Annotations","titles":["RAW"]},"497":{"title":"REQUEST_HEADERS_MODE","titles":[]},"498":{"title":"Syntax","titles":["REQUEST_HEADERS_MODE"]},"499":{"title":"Values","titles":["REQUEST_HEADERS_MODE"]},"500":{"title":"Examples","titles":["REQUEST_HEADERS_MODE"]},"501":{"title":"Ignore Headers","titles":["REQUEST_HEADERS_MODE","Examples"]},"502":{"title":"Pass as Context Variable","titles":["REQUEST_HEADERS_MODE","Examples"]},"503":{"title":"Pass as Parameter","titles":["REQUEST_HEADERS_MODE","Examples"]},"504":{"title":"Behavior","titles":["REQUEST_HEADERS_MODE"]},"505":{"title":"Related","titles":["REQUEST_HEADERS_MODE"]},"506":{"title":"Related Annotations","titles":["REQUEST_HEADERS_MODE"]},"507":{"title":"REQUEST_HEADERS_PARAMETER_NAME","titles":[]},"508":{"title":"Syntax","titles":["REQUEST_HEADERS_PARAMETER_NAME"]},"509":{"title":"Examples","titles":["REQUEST_HEADERS_PARAMETER_NAME"]},"510":{"title":"Custom Parameter Name","titles":["REQUEST_HEADERS_PARAMETER_NAME","Examples"]},"511":{"title":"Default Parameter Name","titles":["REQUEST_HEADERS_PARAMETER_NAME","Examples"]},"512":{"title":"Behavior","titles":["REQUEST_HEADERS_PARAMETER_NAME"]},"513":{"title":"Related","titles":["REQUEST_HEADERS_PARAMETER_NAME"]},"514":{"title":"Related Annotations","titles":["REQUEST_HEADERS_PARAMETER_NAME"]},"515":{"title":"REQUEST_PARAM_TYPE","titles":[]},"516":{"title":"Syntax","titles":["REQUEST_PARAM_TYPE"]},"517":{"title":"Values","titles":["REQUEST_PARAM_TYPE"]},"518":{"title":"Default Behavior","titles":["REQUEST_PARAM_TYPE"]},"519":{"title":"Examples","titles":["REQUEST_PARAM_TYPE"]},"520":{"title":"Force Query String Parameters","titles":["REQUEST_PARAM_TYPE","Examples"]},"521":{"title":"Force JSON Body Parameters","titles":["REQUEST_PARAM_TYPE","Examples"]},"522":{"title":"Short Form Keywords","titles":["REQUEST_PARAM_TYPE","Examples"]},"523":{"title":"POST with Query String","titles":["REQUEST_PARAM_TYPE","Examples"]},"524":{"title":"Behavior","titles":["REQUEST_PARAM_TYPE"]},"525":{"title":"Related","titles":["REQUEST_PARAM_TYPE"]},"526":{"title":"Related Annotations","titles":["REQUEST_PARAM_TYPE"]},"527":{"title":"Resolved Parameters","titles":[]},"528":{"title":"Syntax","titles":["Resolved Parameters"]},"529":{"title":"Behavior","titles":["Resolved Parameters"]},"530":{"title":"Examples","titles":["Resolved Parameters"]},"531":{"title":"Inject a DB-stored API token into an outbound call","titles":["Resolved Parameters","Examples"]},"532":{"title":"Multiple resolved parameters","titles":["Resolved Parameters","Examples"]},"533":{"title":"Resolved value in URL, header, and body","titles":["Resolved Parameters","Examples"]},"534":{"title":"How it compares to the other {name} sources","titles":["Resolved Parameters"]},"535":{"title":"Related","titles":["Resolved Parameters"]},"536":{"title":"Response Headers","titles":[]},"537":{"title":"Syntax","titles":["Response Headers"]},"538":{"title":"Examples","titles":["Response Headers"]},"539":{"title":"Set Content-Type","titles":["Response Headers","Examples"]},"540":{"title":"Multiple Headers","titles":["Response Headers","Examples"]},"541":{"title":"Multi-Value Headers","titles":["Response Headers","Examples"]},"542":{"title":"Cache Control","titles":["Response Headers","Examples"]},"543":{"title":"Combined with Other Annotations","titles":["Response Headers","Examples"]},"544":{"title":"Dynamic Headers from Parameters","titles":["Response Headers","Examples"]},"545":{"title":"CORS Headers","titles":["Response Headers","Examples"]},"546":{"title":"Common Headers","titles":["Response Headers"]},"547":{"title":"Related","titles":["Response Headers"]},"548":{"title":"Related Annotations","titles":["Response Headers"]},"549":{"title":"RESPONSE_NULL_HANDLING","titles":[]},"550":{"title":"Syntax","titles":["RESPONSE_NULL_HANDLING"]},"551":{"title":"Values","titles":["RESPONSE_NULL_HANDLING"]},"552":{"title":"Examples","titles":["RESPONSE_NULL_HANDLING"]},"553":{"title":"Return Empty String for NULL","titles":["RESPONSE_NULL_HANDLING","Examples"]},"554":{"title":"Return 204 for NULL","titles":["RESPONSE_NULL_HANDLING","Examples"]},"555":{"title":"Return JSON null","titles":["RESPONSE_NULL_HANDLING","Examples"]},"556":{"title":"Configuration Default","titles":["RESPONSE_NULL_HANDLING"]},"557":{"title":"Related","titles":["RESPONSE_NULL_HANDLING"]},"558":{"title":"Related Annotations","titles":["RESPONSE_NULL_HANDLING"]},"559":{"title":"RESULT_NAME","titles":[]},"560":{"title":"Syntax","titles":["RESULT_NAME"]},"561":{"title":"Examples","titles":["RESULT_NAME"]},"562":{"title":"Before Statement (Separate Line)","titles":["RESULT_NAME","Examples"]},"563":{"title":"Inline After Semicolon (Same Line)","titles":["RESULT_NAME","Examples"]},"564":{"title":""is" Style Syntax","titles":["RESULT_NAME","Examples"]},"565":{"title":"Naming Some Results","titles":["RESULT_NAME","Examples"]},"566":{"title":"Naming All Results","titles":["RESULT_NAME","Examples"]},"567":{"title":"Behavior","titles":["RESULT_NAME"]},"568":{"title":"Related","titles":["RESULT_NAME"]},"569":{"title":"RETRY_STRATEGY","titles":[]},"570":{"title":"Syntax","titles":["RETRY_STRATEGY"]},"571":{"title":"Examples","titles":["RETRY_STRATEGY"]},"572":{"title":"Use Default Strategy","titles":["RETRY_STRATEGY","Examples"]},"573":{"title":"Use Named Strategy","titles":["RETRY_STRATEGY","Examples"]},"574":{"title":"Combined with Timeout","titles":["RETRY_STRATEGY","Examples"]},"575":{"title":"Behavior","titles":["RETRY_STRATEGY"]},"576":{"title":"Common Retry Scenarios","titles":["RETRY_STRATEGY"]},"577":{"title":"Configuration Example","titles":["RETRY_STRATEGY"]},"578":{"title":"Related","titles":["RETRY_STRATEGY"]},"579":{"title":"Related Annotations","titles":["RETRY_STRATEGY"]},"580":{"title":"See Also","titles":["RETRY_STRATEGY"]},"581":{"title":"RETURNS","titles":[]},"582":{"title":"Syntax","titles":["RETURNS"]},"583":{"title":"When to Use","titles":["RETURNS"]},"584":{"title":"Example","titles":["RETURNS"]},"585":{"title":"Scalar Type","titles":["RETURNS","Example"]},"586":{"title":"Void Statements","titles":["RETURNS","Example"]},"587":{"title":"Behavior","titles":["RETURNS"]},"588":{"title":"Related","titles":["RETURNS"]},"589":{"title":"SECURITY_SENSITIVE","titles":[]},"590":{"title":"Syntax","titles":["SECURITY_SENSITIVE"]},"591":{"title":"Examples","titles":["SECURITY_SENSITIVE"]},"592":{"title":"Password Change Endpoint","titles":["SECURITY_SENSITIVE","Examples"]},"593":{"title":"Login Endpoint","titles":["SECURITY_SENSITIVE","Examples"]},"594":{"title":"Payment Processing","titles":["SECURITY_SENSITIVE","Examples"]},"595":{"title":"Behavior","titles":["SECURITY_SENSITIVE"]},"596":{"title":"Related","titles":["SECURITY_SENSITIVE"]},"597":{"title":"Related Annotations","titles":["SECURITY_SENSITIVE"]},"598":{"title":"SEPARATOR","titles":[]},"599":{"title":"Syntax","titles":["SEPARATOR"]},"600":{"title":"Examples","titles":["SEPARATOR"]},"601":{"title":"Comma Separator (CSV)","titles":["SEPARATOR","Examples"]},"602":{"title":"Tab Separator (TSV)","titles":["SEPARATOR","Examples"]},"603":{"title":"Pipe Separator","titles":["SEPARATOR","Examples"]},"604":{"title":"Custom Separator","titles":["SEPARATOR","Examples"]},"605":{"title":"Related","titles":["SEPARATOR"]},"606":{"title":"Related Annotations","titles":["SEPARATOR"]},"607":{"title":"SINGLE","titles":[]},"608":{"title":"Syntax","titles":["SINGLE"]},"609":{"title":"Default Behavior vs Single","titles":["SINGLE"]},"610":{"title":"Examples","titles":["SINGLE"]},"611":{"title":"PostgreSQL Function","titles":["SINGLE","Examples"]},"612":{"title":"SQL File","titles":["SINGLE","Examples"]},"613":{"title":"Single Unnamed Column","titles":["SINGLE","Examples"]},"614":{"title":"Multi-Command Files (Positional)","titles":["SINGLE","Examples"]},"615":{"title":"Behavior","titles":["SINGLE"]},"616":{"title":"Empty Results","titles":["SINGLE","Behavior"]},"617":{"title":"Related","titles":["SINGLE"]},"618":{"title":"SKIP","titles":[]},"619":{"title":"Syntax","titles":["SKIP"]},"620":{"title":"Examples","titles":["SKIP"]},"621":{"title":"Skipping a DO Block","titles":["SKIP","Examples"]},"622":{"title":"Skipping Transaction Control","titles":["SKIP","Examples"]},"623":{"title":"Inline Placement","titles":["SKIP","Examples"]},"624":{"title":"SkipNonQueryCommands Setting","titles":["SKIP"]},"625":{"title":"Behavior","titles":["SKIP"]},"626":{"title":"Related","titles":["SKIP"]},"627":{"title":"SSE_EVENTS_LEVEL","titles":[]},"628":{"title":"Syntax","titles":["SSE_EVENTS_LEVEL"]},"629":{"title":"Values","titles":["SSE_EVENTS_LEVEL"]},"630":{"title":"Examples","titles":["SSE_EVENTS_LEVEL"]},"631":{"title":"Info Level (All Messages)","titles":["SSE_EVENTS_LEVEL","Examples"]},"632":{"title":"Notice Level","titles":["SSE_EVENTS_LEVEL","Examples"]},"633":{"title":"Warning Level Only","titles":["SSE_EVENTS_LEVEL","Examples"]},"634":{"title":"Related","titles":["SSE_EVENTS_LEVEL"]},"635":{"title":"Related Annotations","titles":["SSE_EVENTS_LEVEL"]},"636":{"title":"SSE_EVENTS_SCOPE","titles":[]},"637":{"title":"Syntax","titles":["SSE_EVENTS_SCOPE"]},"638":{"title":"Values","titles":["SSE_EVENTS_SCOPE"]},"639":{"title":"Request Correlation","titles":["SSE_EVENTS_SCOPE"]},"640":{"title":"Examples","titles":["SSE_EVENTS_SCOPE"]},"641":{"title":"Matching Scope","titles":["SSE_EVENTS_SCOPE","Examples"]},"642":{"title":"Authorize Scope with Roles","titles":["SSE_EVENTS_SCOPE","Examples"]},"643":{"title":"Authorize with User Names or IDs","titles":["SSE_EVENTS_SCOPE","Examples"]},"644":{"title":"Multiple Values","titles":["SSE_EVENTS_SCOPE","Examples"]},"645":{"title":"Broadcast to All","titles":["SSE_EVENTS_SCOPE","Examples"]},"646":{"title":"Dynamic Scope via RAISE HINT","titles":["SSE_EVENTS_SCOPE"]},"647":{"title":"Related","titles":["SSE_EVENTS_SCOPE"]},"648":{"title":"Related Annotations","titles":["SSE_EVENTS_SCOPE"]},"649":{"title":"SSE","titles":[]},"650":{"title":"How events flow","titles":["SSE"]},"651":{"title":"Syntax","titles":["SSE"]},"652":{"title":"SSE Path Construction","titles":["SSE"]},"653":{"title":"When Path is Omitted","titles":["SSE","SSE Path Construction"]},"654":{"title":"When Custom Path is Specified","titles":["SSE","SSE Path Construction"]},"655":{"title":"Default Level","titles":["SSE"]},"656":{"title":"Level Filtering","titles":["SSE"]},"657":{"title":"Examples","titles":["SSE"]},"658":{"title":"Basic SSE Endpoint (function)","titles":["SSE","Examples"]},"659":{"title":"Basic SSE Endpoint (SQL file)","titles":["SSE","Examples"]},"660":{"title":"With Notice Level","titles":["SSE","Examples"]},"661":{"title":"Warning Level Only","titles":["SSE","Examples"]},"662":{"title":"Using Default Path (Level Name)","titles":["SSE","Examples"]},"663":{"title":"Cross-procedure pattern","titles":["SSE"]},"664":{"title":"Function form","titles":["SSE","Cross-procedure pattern"]},"665":{"title":"SQL file form","titles":["SSE","Cross-procedure pattern"]},"666":{"title":"What the client does","titles":["SSE","Cross-procedure pattern"]},"667":{"title":"Behavior","titles":["SSE"]},"668":{"title":"On the publisher side","titles":["SSE","Behavior"]},"669":{"title":"On the subscriber side","titles":["SSE","Behavior"]},"670":{"title":"Related","titles":["SSE"]},"671":{"title":"Related Annotations","titles":["SSE"]},"672":{"title":"See Also","titles":["SSE"]},"673":{"title":"TABLE_FORMAT","titles":[]},"674":{"title":"Syntax","titles":["TABLE_FORMAT"]},"675":{"title":"Parameters","titles":["TABLE_FORMAT"]},"676":{"title":"Examples","titles":["TABLE_FORMAT"]},"677":{"title":"Static HTML Table","titles":["TABLE_FORMAT","Examples"]},"678":{"title":"Static Excel Download","titles":["TABLE_FORMAT","Examples"]},"679":{"title":"Dynamic Format Selection","titles":["TABLE_FORMAT","Examples"]},"680":{"title":"Related","titles":["TABLE_FORMAT"]},"681":{"title":"Related Annotations","titles":["TABLE_FORMAT"]},"682":{"title":"See Also","titles":["TABLE_FORMAT"]},"683":{"title":"TAGS","titles":[]},"684":{"title":"Available tags","titles":["TAGS"]},"685":{"title":"Syntax","titles":["TAGS"]},"686":{"title":"Example: cache only when immutable","titles":["TAGS"]},"687":{"title":"Behavior","titles":["TAGS"]},"688":{"title":"Related","titles":["TAGS"]},"689":{"title":"TEST @claim","titles":[]},"690":{"title":"Semantics","titles":["TEST @claim"]},"691":{"title":"Why not call the login endpoint?","titles":["TEST @claim"]},"692":{"title":"Related","titles":["TEST @claim"]},"693":{"title":"TEST @connection","titles":[]},"694":{"title":"Syntax","titles":["TEST @connection"]},"695":{"title":"Example","titles":["TEST @connection"]},"696":{"title":"Notes","titles":["TEST @connection"]},"697":{"title":"Related","titles":["TEST @connection"]},"698":{"title":"TEST @response","titles":[]},"699":{"title":"Default naming","titles":["TEST @response"]},"700":{"title":"Syntax","titles":["TEST @response"]},"701":{"title":"Notes","titles":["TEST @response"]},"702":{"title":"Related","titles":["TEST @response"]},"703":{"title":"TEST @setup","titles":[]},"704":{"title":"Syntax","titles":["TEST @setup"]},"705":{"title":"Example","titles":["TEST @setup"]},"706":{"title":"Header semantics","titles":["TEST @setup"]},"707":{"title":"Related","titles":["TEST @setup"]},"708":{"title":"TEST @tag","titles":[]},"709":{"title":"Syntax","titles":["TEST @tag"]},"710":{"title":"Example","titles":["TEST @tag"]},"711":{"title":"Tags via a shared profile","titles":["TEST @tag"]},"712":{"title":"Related","titles":["TEST @tag"]},"713":{"title":"TEST @teardown","titles":[]},"714":{"title":"Syntax","titles":["TEST @teardown"]},"715":{"title":"Example","titles":["TEST @teardown"]},"716":{"title":"Ordering","titles":["TEST @teardown"]},"717":{"title":"Related","titles":["TEST @teardown"]},"718":{"title":"TSCLIENT","titles":[]},"719":{"title":"Syntax","titles":["TSCLIENT"]},"720":{"title":"Parameters","titles":["TSCLIENT"]},"721":{"title":"Examples","titles":["TSCLIENT"]},"722":{"title":"Disable Generation","titles":["TSCLIENT","Examples"]},"723":{"title":"URL-Only Export","titles":["TSCLIENT","Examples"]},"724":{"title":"Custom Module","titles":["TSCLIENT","Examples"]},"725":{"title":"Related","titles":["TSCLIENT"]},"726":{"title":"Related Annotations","titles":["TSCLIENT"]},"727":{"title":"See Also","titles":["TSCLIENT"]},"728":{"title":"USER_CONTEXT","titles":[]},"729":{"title":"Keywords","titles":["USER_CONTEXT"]},"730":{"title":"Syntax","titles":["USER_CONTEXT"]},"731":{"title":"Examples","titles":["USER_CONTEXT"]},"732":{"title":"Enable User Context","titles":["USER_CONTEXT","Examples"]},"733":{"title":"Access User Claims in Function","titles":["USER_CONTEXT","Examples"]},"734":{"title":"Access All Claims as JSON","titles":["USER_CONTEXT","Examples"]},"735":{"title":"Access Client IP Address","titles":["USER_CONTEXT","Examples"]},"736":{"title":"Combined with Request Headers","titles":["USER_CONTEXT","Examples"]},"737":{"title":"Behavior","titles":["USER_CONTEXT"]},"738":{"title":"Default Context Keys","titles":["USER_CONTEXT","Behavior"]},"739":{"title":"Additional Context Keys (when configured)","titles":["USER_CONTEXT","Behavior"]},"740":{"title":"Related","titles":["USER_CONTEXT"]},"741":{"title":"Related Annotations","titles":["USER_CONTEXT"]},"742":{"title":"See Also","titles":["USER_CONTEXT"]},"743":{"title":"UPLOAD","titles":[]},"744":{"title":"Keywords","titles":["UPLOAD"]},"745":{"title":"Syntax","titles":["UPLOAD"]},"746":{"title":"Handler Types","titles":["UPLOAD"]},"747":{"title":"Shared Annotation Options","titles":["UPLOAD"]},"748":{"title":"Upload Metadata","titles":["UPLOAD"]},"749":{"title":"Large Object Handler","titles":["UPLOAD"]},"750":{"title":"Basic Example","titles":["UPLOAD","Large Object Handler"]},"751":{"title":"With Custom OID Parameter","titles":["UPLOAD","Large Object Handler"]},"752":{"title":"Context Metadata","titles":["UPLOAD","Large Object Handler"]},"753":{"title":"Large Object Annotation Options","titles":["UPLOAD","Large Object Handler"]},"754":{"title":"File System Handler","titles":["UPLOAD"]},"755":{"title":"Basic Example","titles":["UPLOAD","File System Handler"]},"756":{"title":"With Custom Parameters","titles":["UPLOAD","File System Handler"]},"757":{"title":"File System Annotation Options","titles":["UPLOAD","File System Handler"]},"758":{"title":"MIME Type Filtering","titles":["UPLOAD","File System Handler"]},"759":{"title":"CSV Handler","titles":["UPLOAD"]},"760":{"title":"Row Command Function Signature","titles":["UPLOAD","CSV Handler"]},"761":{"title":"Row Command Parameters","titles":["UPLOAD","CSV Handler"]},"762":{"title":"Row Metadata Structure ($4)","titles":["UPLOAD","CSV Handler"]},"763":{"title":"Upload Function Metadata (_meta parameter)","titles":["UPLOAD","CSV Handler"]},"764":{"title":"Basic Example","titles":["UPLOAD","CSV Handler"]},"765":{"title":"Accessing User Claims in Row Command","titles":["UPLOAD","CSV Handler"]},"766":{"title":"Using User Context Variables","titles":["UPLOAD","CSV Handler"]},"767":{"title":"Custom Delimiters","titles":["UPLOAD","CSV Handler"]},"768":{"title":"CSV Annotation Options","titles":["UPLOAD","CSV Handler"]},"769":{"title":"Excel Handler","titles":["UPLOAD"]},"770":{"title":"Row Command Function Signature","titles":["UPLOAD","Excel Handler"]},"771":{"title":"Row Command Parameters","titles":["UPLOAD","Excel Handler"]},"772":{"title":"Row Metadata Structure ($4)","titles":["UPLOAD","Excel Handler"]},"773":{"title":"Upload Function Metadata (_meta parameter)","titles":["UPLOAD","Excel Handler"]},"774":{"title":"Basic Example","titles":["UPLOAD","Excel Handler"]},"775":{"title":"Row Data as JSON","titles":["UPLOAD","Excel Handler"]},"776":{"title":"Excel Annotation Options","titles":["UPLOAD","Excel Handler"]},"777":{"title":"Error Handling and Rollback","titles":["UPLOAD"]},"778":{"title":"Multiple File Uploads","titles":["UPLOAD"]},"779":{"title":"Behavior","titles":["UPLOAD"]},"780":{"title":"Custom Parameters","titles":["UPLOAD"]},"781":{"title":"Shared Parameters","titles":["UPLOAD","Custom Parameters"]},"782":{"title":"Large Object Upload Handler","titles":["UPLOAD","Custom Parameters"]},"783":{"title":"Example","titles":["UPLOAD","Custom Parameters","Large Object Upload Handler"]},"784":{"title":"File System Upload Handler","titles":["UPLOAD","Custom Parameters"]},"785":{"title":"Example","titles":["UPLOAD","Custom Parameters","File System Upload Handler"]},"786":{"title":"CSV Upload Handler","titles":["UPLOAD","Custom Parameters"]},"787":{"title":"Example","titles":["UPLOAD","Custom Parameters","CSV Upload Handler"]},"788":{"title":"Excel Upload Handler","titles":["UPLOAD","Custom Parameters"]},"789":{"title":"Example","titles":["UPLOAD","Custom Parameters","Excel Upload Handler"]},"790":{"title":"Related","titles":["UPLOAD"]},"791":{"title":"Blog Posts","titles":["UPLOAD"]},"792":{"title":"Related Annotations","titles":["UPLOAD"]},"793":{"title":"See Also","titles":["UPLOAD"]},"794":{"title":"USER_PARAMETERS","titles":[]},"795":{"title":"Syntax","titles":["USER_PARAMETERS"]},"796":{"title":"Examples","titles":["USER_PARAMETERS"]},"797":{"title":"Basic User Parameters","titles":["USER_PARAMETERS","Examples"]},"798":{"title":"With Default Values (for unauthenticated access)","titles":["USER_PARAMETERS","Examples"]},"799":{"title":"Access All Claims as JSON","titles":["USER_PARAMETERS","Examples"]},"800":{"title":"Combined with User Context","titles":["USER_PARAMETERS","Examples"]},"801":{"title":"Behavior","titles":["USER_PARAMETERS"]},"802":{"title":"Default Parameter Mapping","titles":["USER_PARAMETERS","Behavior"]},"803":{"title":"Differences from USER_CONTEXT","titles":["USER_PARAMETERS","Behavior"]},"804":{"title":"Related","titles":["USER_PARAMETERS"]},"805":{"title":"Related Annotations","titles":["USER_PARAMETERS"]},"806":{"title":"See Also","titles":["USER_PARAMETERS"]},"807":{"title":"VALIDATE","titles":[]},"808":{"title":"Keywords","titles":["VALIDATE"]},"809":{"title":"Syntax","titles":["VALIDATE"]},"810":{"title":"Examples","titles":["VALIDATE"]},"811":{"title":"Single Rule Validation","titles":["VALIDATE","Examples"]},"812":{"title":"Multiple Rules on One Parameter","titles":["VALIDATE","Examples"]},"813":{"title":"Multiple Parameters","titles":["VALIDATE","Examples"]},"814":{"title":"Using Converted Parameter Names","titles":["VALIDATE","Examples"]},"815":{"title":"With Authorization","titles":["VALIDATE","Examples"]},"816":{"title":"Default Rules","titles":["VALIDATE"]},"817":{"title":"Custom Rules","titles":["VALIDATE"]},"818":{"title":"Behavior","titles":["VALIDATE"]},"819":{"title":"Error Response","titles":["VALIDATE"]},"820":{"title":"Related","titles":["VALIDATE"]},"821":{"title":"Related Annotations","titles":["VALIDATE"]},"822":{"title":"See Also","titles":["VALIDATE"]},"823":{"title":"VOID","titles":[]},"824":{"title":"Syntax","titles":["VOID"]},"825":{"title":"Examples","titles":["VOID"]},"826":{"title":"Multi-Command Side Effects","titles":["VOID","Examples"]},"827":{"title":"Single-Command Void","titles":["VOID","Examples"]},"828":{"title":"Function Endpoints","titles":["VOID","Examples"]},"829":{"title":"Behavior","titles":["VOID"]},"830":{"title":"Related","titles":["VOID"]},"831":{"title":"NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers","titles":[]},"832":{"title":"The shared idea","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"833":{"title":"The fork in the road","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"834":{"title":"Where SQLPage is stronger: the UI","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"835":{"title":"Where NpgsqlRest is stronger: the API","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"836":{"title":"They\'re complementary, not rivals","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"837":{"title":"When to choose each","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"838":{"title":"Conclusion","titles":["NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers"]},"839":{"title":"DRAFT: 20th Anniversary of The Vietnam of Computer Science","titles":[]},"840":{"title":"Introduction","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"841":{"title":"What Is The Object–Relational Impedance Mismatch","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"842":{"title":"1) State Data Abstraction Misconception","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"843":{"title":"The Claim","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","1) State Data Abstraction Misconception"]},"844":{"title":"The Reality","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","1) State Data Abstraction Misconception"]},"845":{"title":"The Cost","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","1) State Data Abstraction Misconception"]},"846":{"title":"2) Storage Devices Abstraction Misconception","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"847":{"title":"The Claim","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","2) Storage Devices Abstraction Misconception"]},"848":{"title":"The Reality","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","2) Storage Devices Abstraction Misconception"]},"849":{"title":"The Cost","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","2) Storage Devices Abstraction Misconception"]},"850":{"title":"3) Data Structures Abstraction Misconception","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"851":{"title":"The Claim","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception"]},"852":{"title":"The Reality","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception"]},"853":{"title":"The Cost","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception"]},"854":{"title":"Copy, not record → staleness and write amplification","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception","The Cost"]},"855":{"title":"Private, not shared → arbitration lives in the database anyway","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception","The Cost"]},"856":{"title":"Graph walk, not set operation → the access-pattern tax","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception","The Cost"]},"857":{"title":"Simulation, not engine → the capability ceiling","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","3) Data Structures Abstraction Misconception","The Cost"]},"858":{"title":"4) Abstraction Over Algorithms","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"859":{"title":"The Claim","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","4) Abstraction Over Algorithms"]},"860":{"title":"The Reality","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","4) Abstraction Over Algorithms"]},"861":{"title":"The Cost","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","4) Abstraction Over Algorithms"]},"862":{"title":"5) Abstraction Over Concurrency and Integrity","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science"]},"863":{"title":"The Claim","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","5) Abstraction Over Concurrency and Integrity"]},"864":{"title":"The Reality","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","5) Abstraction Over Concurrency and Integrity"]},"865":{"title":"The Cost","titles":["DRAFT: 20th Anniversary of The Vietnam of Computer Science","5) Abstraction Over Concurrency and Integrity"]},"866":{"title":"Case Study: 74 Endpoints, Zero Backend Code","titles":[]},"867":{"title":"What\'s in the repository","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"868":{"title":"What NpgsqlRest is doing for them","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"869":{"title":"The comparison: equivalent build in ASP.NET Core","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"870":{"title":"How it scores on the four dimensions that matter","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"871":{"title":"Productivity","titles":["Case Study: 74 Endpoints, Zero Backend Code","How it scores on the four dimensions that matter"]},"872":{"title":"Time saved, quantified","titles":["Case Study: 74 Endpoints, Zero Backend Code","How it scores on the four dimensions that matter","Productivity"]},"873":{"title":"Lines of code saved","titles":["Case Study: 74 Endpoints, Zero Backend Code","How it scores on the four dimensions that matter"]},"874":{"title":"Performance","titles":["Case Study: 74 Endpoints, Zero Backend Code","How it scores on the four dimensions that matter"]},"875":{"title":"Overall quality","titles":["Case Study: 74 Endpoints, Zero Backend Code","How it scores on the four dimensions that matter"]},"876":{"title":"Honest tradeoffs","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"877":{"title":"What this case study is, and isn\'t","titles":["Case Study: 74 Endpoints, Zero Backend Code"]},"878":{"title":"CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","titles":[]},"879":{"title":"The Traditional Approach: Rigid and Brittle","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"880":{"title":"The NpgsqlRest Approach: Dynamic and Flexible","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"881":{"title":"How It Works","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"882":{"title":"The Row Function: Four Parameters, Infinite Flexibility","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"883":{"title":"CSV Row Function Example","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","The Row Function: Four Parameters, Infinite Flexibility"]},"884":{"title":"Excel Row Function Example","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","The Row Function: Four Parameters, Infinite Flexibility"]},"885":{"title":"Row Chaining: The Power of $3","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"886":{"title":"The Upload Endpoint Function","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"887":{"title":"Upload Metadata Structure","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","The Upload Endpoint Function"]},"888":{"title":"Dynamic Structure: No Hardcoding, No Redeployment","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"889":{"title":"Configuration","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"890":{"title":"Annotation Options","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"891":{"title":"CSV Handler Options","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Annotation Options"]},"892":{"title":"Excel Handler Options","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Annotation Options"]},"893":{"title":"Row Metadata Differences: CSV vs Excel","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"894":{"title":"Generated TypeScript Client","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"895":{"title":"Advanced Patterns","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"896":{"title":"Skipping Header Rows","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Advanced Patterns"]},"897":{"title":"Validation and Rejection","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Advanced Patterns"]},"898":{"title":"Upsert (Insert or Update)","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Advanced Patterns"]},"899":{"title":"Processing Only Specific Sheets","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Advanced Patterns"]},"900":{"title":"JSON Row Format for Complex Data","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Advanced Patterns"]},"901":{"title":"Transaction Safety","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"902":{"title":"Combining Handlers: Process AND Store the Original File","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"903":{"title":"Combined Handler Metadata","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Combining Handlers: Process AND Store the Original File"]},"904":{"title":"Fallback Handler: One Endpoint for Both Excel and CSV","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"905":{"title":"Authentication Integration","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"906":{"title":"Comparison with Other Tools","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"907":{"title":"COPY Command","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Comparison with Other Tools"]},"908":{"title":"ETL Tools (Talend, Pentaho, etc.)","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Comparison with Other Tools"]},"909":{"title":"Python/pandas","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Comparison with Other Tools"]},"910":{"title":"Conclusion: What You Don\'t Have to Write","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest"]},"911":{"title":"The Numbers","titles":["CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest","Conclusion: What You Don\'t Have to Write"]},"912":{"title":"Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","titles":[]},"913":{"title":"Example Setup","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"914":{"title":"Returning Single Object","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"915":{"title":"Using Custom Types as Parameters","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"916":{"title":"Returning Sets of Objects","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"917":{"title":"NEW: Nested JSON Objects","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"918":{"title":"NEW: Nested JSON with Multiset","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs"]},"919":{"title":"Limitations","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","NEW: Nested JSON with Multiset"]},"920":{"title":"Conclusion And Workaround","titles":["Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs","NEW: Nested JSON with Multiset"]},"921":{"title":"Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","titles":[]},"922":{"title":"The Principle of Least Privilege (PoLP)","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"923":{"title":"Schema Architecture","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"924":{"title":"The Protected Schema","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Schema Architecture"]},"925":{"title":"The Public API Schema","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Schema Architecture"]},"926":{"title":"The Restricted Application Role","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Schema Architecture"]},"927":{"title":"Bypassing Bcrypt\'s 72-Byte Limit","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"928":{"title":"The Hash Function","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Bypassing Bcrypt\'s 72-Byte Limit"]},"929":{"title":"The Verify Function","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Bypassing Bcrypt\'s 72-Byte Limit"]},"930":{"title":"Testing the Password Functions","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Bypassing Bcrypt\'s 72-Byte Limit"]},"931":{"title":"The Authentication Functions","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"932":{"title":"Understanding SECURITY DEFINER","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","The Authentication Functions"]},"933":{"title":"Protecting Against Search Path Attacks","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","The Authentication Functions"]},"934":{"title":"Login Function","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","The Authentication Functions"]},"935":{"title":"Logout Function","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","The Authentication Functions"]},"936":{"title":"Who Am I Function","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","The Authentication Functions"]},"937":{"title":"NpgsqlRest Configuration","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"938":{"title":"The Demo Application","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"939":{"title":"Why This Architecture is More Secure","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"940":{"title":"1. Defense in Depth","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Why This Architecture is More Secure"]},"941":{"title":"2. SQL Injection Becomes Less Dangerous","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Why This Architecture is More Secure"]},"942":{"title":"3. No Secrets in Application Code","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Why This Architecture is More Secure"]},"943":{"title":"4. Auditable Security Boundary","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Why This Architecture is More Secure"]},"944":{"title":"5. Bcrypt Limit Protection","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest","Why This Architecture is More Secure"]},"945":{"title":"Comparison with Traditional Approaches","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"946":{"title":"Conclusion","titles":["Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest"]},"947":{"title":"Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","titles":[]},"948":{"title":"Why Excel Exports Are Terrible","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"949":{"title":"The NpgsqlRest Approach: Pure Streaming","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"950":{"title":"What Makes This Special","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"951":{"title":"Zero-Allocation Cell Writing","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","What Makes This Special"]},"952":{"title":"Native Type Mapping","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","What Makes This Special"]},"953":{"title":"Constant Memory Usage","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","What Makes This Special"]},"954":{"title":"AOT/Trim Compatible","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","What Makes This Special"]},"955":{"title":"Building an Excel Export Endpoint","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"956":{"title":"Step 1: Write Your Function","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Building an Excel Export Endpoint"]},"957":{"title":"Step 2: Add the Annotation","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Building an Excel Export Endpoint"]},"958":{"title":"Step 3: Configure Table Format","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Building an Excel Export Endpoint"]},"959":{"title":"Two Formats, One Endpoint","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"960":{"title":"Static Format Annotation","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Two Formats, One Endpoint"]},"961":{"title":"The TypeScript Client: URL-Only Generation","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"962":{"title":"Excel Format Configuration","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"963":{"title":"DateTime and Numeric Formats","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Excel Format Configuration"]},"964":{"title":"Worksheet and File Names","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Excel Format Configuration"]},"965":{"title":"HTML Table Format","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"966":{"title":"Bonus: Built-In Statistics Endpoints","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"967":{"title":"Stats Configuration Options","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","Bonus: Built-In Statistics Endpoints"]},"968":{"title":"The Traditional Way vs. This Way","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"969":{"title":"Memory Profile Comparison","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL","The Traditional Way vs. This Way"]},"970":{"title":"Running the Example","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"971":{"title":"Conclusion","titles":["Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL"]},"972":{"title":"End-to-End Static Type Checking: PostgreSQL to TypeScript","titles":[]},"973":{"title":"The Problem with Traditional API Development","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"974":{"title":"Why PostgreSQL Functions?","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"975":{"title":"The Solution: Single Source of Truth","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"976":{"title":"Project Structure","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"977":{"title":"The Database Schema","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"978":{"title":"Functions That Define the API Contract","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"979":{"title":"get_users()","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Functions That Define the API Contract"]},"980":{"title":"get_posts()","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Functions That Define the API Contract"]},"981":{"title":"Static Type Checking at the SQL Level","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"982":{"title":"How PostgreSQL Enforces Return Types","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Static Type Checking at the SQL Level"]},"983":{"title":"The Return Type Contract","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Static Type Checking at the SQL Level"]},"984":{"title":"Type Changes Propagate Naturally","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Static Type Checking at the SQL Level"]},"985":{"title":"Why Functions Are Recreated on Every Build","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"986":{"title":"Built-in Testing with SQL Assertions","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"987":{"title":"Unit Testing PostgreSQL Functions: Beyond Fixed Data","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"988":{"title":"Co-located Tests: Function and Test in the Same File","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"989":{"title":"Test Isolation with Rollback","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"990":{"title":"Testing Multiple Scenarios","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"991":{"title":"Testing Against Empty Tables","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"992":{"title":"Deferrable Constraints: The Key to Test Data","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"993":{"title":"Why Database Testing is Fast","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"994":{"title":"Addressing Common Myths","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Unit Testing PostgreSQL Functions: Beyond Fixed Data"]},"995":{"title":"The Generated TypeScript Client","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"996":{"title":"The Application Code","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"997":{"title":"The Complete Type-Safe Workflow","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"998":{"title":"Configuration","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"999":{"title":"Benefits of This Approach","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"1000":{"title":"1. Single Source of Truth","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Benefits of This Approach"]},"1001":{"title":"2. Compile-Time Safety","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Benefits of This Approach"]},"1002":{"title":"3. Automatic Documentation","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Benefits of This Approach"]},"1003":{"title":"4. Database-Level Testing","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Benefits of This Approach"]},"1004":{"title":"5. No Runtime Type Checking Overhead","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Benefits of This Approach"]},"1005":{"title":"Conclusion","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"1006":{"title":"Why This Stack is Superior","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript"]},"1007":{"title":"Performance That Scales","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Why This Stack is Superior"]},"1008":{"title":"Maximum Type Safety, Minimum Code","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Why This Stack is Superior"]},"1009":{"title":"The Bottom Line","titles":["End-to-End Static Type Checking: PostgreSQL to TypeScript","Why This Stack is Superior"]},"1010":{"title":"Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","titles":[]},"1011":{"title":"The Problem: Backend-for-Frontend API Aggregation","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1012":{"title":"Why Not Use PostgreSQL HTTP Extensions?","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1013":{"title":"Installation and Distribution Overhead","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Why Not Use PostgreSQL HTTP Extensions?"]},"1014":{"title":"Network and Performance Issues","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Why Not Use PostgreSQL HTTP Extensions?"]},"1015":{"title":"The NpgsqlRest Advantage","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Why Not Use PostgreSQL HTTP Extensions?"]},"1016":{"title":"The NpgsqlRest Solution: HTTP Types","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1017":{"title":"The HTTP Type Syntax: Just Like .http Files","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1018":{"title":"Building the Financial Dashboard","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1019":{"title":"Step 1: Define HTTP Types","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Building the Financial Dashboard"]},"1020":{"title":"Step 2: Define the Return Type","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Building the Financial Dashboard"]},"1021":{"title":"Step 3: Create the Aggregation Function","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Building the Financial Dashboard"]},"1022":{"title":"Step 4: Configuration","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Building the Financial Dashboard"]},"1023":{"title":"What Happens at Runtime","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1024":{"title":"The Generated TypeScript Client","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1025":{"title":"Traditional Approach: What It Would Take","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1026":{"title":"Traditional Backend (Node.js)","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Traditional Approach: What It Would Take"]},"1027":{"title":"The Numbers","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1028":{"title":"Advanced Features","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1029":{"title":"Multiple API Calls","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1030":{"title":"POST Requests with Bodies","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1031":{"title":"Response Field Customization","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1032":{"title":"Retry Logic","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1033":{"title":"Resolved Parameter Expressions","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1034":{"title":"Timeout Configuration","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest","Advanced Features"]},"1035":{"title":"When to Use HTTP Types","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1036":{"title":"Conclusion","titles":["Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest"]},"1037":{"title":"Blog Posts & Tutorials","titles":[]},"1038":{"title":"Turn PostgreSQL into MCP Tools an AI Agent Can Call","titles":[]},"1039":{"title":"What MCP is, in one paragraph","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1040":{"title":"Opt-in, never automatic","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1041":{"title":"Results are structured","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call","Opt-in, never automatic"]},"1042":{"title":"MCP-only tools: a tool with no REST route","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1043":{"title":"One source, two interfaces — made visible","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1044":{"title":"The real test: an AI agent driving the store","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1045":{"title":"Authorization, without locking down the server","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1046":{"title":"Why this approach holds up","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1047":{"title":"Try it","titles":["Turn PostgreSQL into MCP Tools an AI Agent Can Call"]},"1048":{"title":"Multiple Authentication Schemes, Role-Based Access Control, and External Providers","titles":[]},"1049":{"title":"Why NpgsqlRest\'s Built-in Password Hasher?","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1050":{"title":"Schema Design","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1051":{"title":"Generating Password Hashes","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Schema Design"]},"1052":{"title":"Automatic Parameter Hashing for Registration","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Schema Design"]},"1053":{"title":"Multiple Authentication Schemes","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1054":{"title":"A Note on Data Protection and Encryption","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Multiple Authentication Schemes"]},"1055":{"title":"The Login Function with Built-in Password Verification","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1056":{"title":"Password Verification Callbacks","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","The Login Function with Built-in Password Verification"]},"1057":{"title":"Role-Based Access Control","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1058":{"title":"User Context with current_setting","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Role-Based Access Control"]},"1059":{"title":"External OAuth Providers","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1060":{"title":"The External Login Function","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","External OAuth Providers"]},"1061":{"title":"The Demo Application","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1062":{"title":"Configuration Summary","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1063":{"title":"Generated Client with Token Support","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Configuration Summary"]},"1064":{"title":"Conclusion: Enterprise Auth Made Simple","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers"]},"1065":{"title":"This Blog Post is Your Recipe","titles":["Multiple Authentication Schemes, Role-Based Access Control, and External Providers","Conclusion: Enterprise Auth Made Simple"]},"1066":{"title":"NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility","titles":[]},"1067":{"title":"1. Conditional Caching: Historical vs Current vs Live","titles":["NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"]},"1068":{"title":"2. Short-Lived Sensitive Session Alongside the Normal Session","titles":["NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"]},"1069":{"title":"3. Per-User Rate Limits","titles":["NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"]},"1070":{"title":"4. Multi-Tenant search_path with pgBouncer (or RDS Proxy, or Supabase Pooler)","titles":["NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"]},"1071":{"title":"Other Notable Changes","titles":["NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility"]},"1072":{"title":"Tests Are SQL Files Too","titles":[]},"1073":{"title":"Introduction","titles":["Tests Are SQL Files Too"]},"1074":{"title":"TL;DR Test Runner","titles":["Tests Are SQL Files Too"]},"1075":{"title":"Database Testing Is Impossible (They Said)","titles":["Tests Are SQL Files Too"]},"1076":{"title":"The Pattern I Have Used for Years","titles":["Tests Are SQL Files Too"]},"1077":{"title":"But It Has Limits","titles":["Tests Are SQL Files Too"]},"1078":{"title":"Tests Are SQL Files Too","titles":["Tests Are SQL Files Too"]},"1079":{"title":"The Old Demons: Isolation and Fixtures","titles":["Tests Are SQL Files Too"]},"1080":{"title":"And Then There Is Watch Mode","titles":["Tests Are SQL Files Too"]},"1081":{"title":"AI TDD","titles":["Tests Are SQL Files Too"]},"1082":{"title":"Where to Start","titles":["Tests Are SQL Files Too"]},"1083":{"title":"NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","titles":[]},"1084":{"title":"Executive Summary","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1085":{"title":"Architecture Comparison","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1086":{"title":"NpgsqlRest","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Architecture Comparison"]},"1087":{"title":"PostgREST","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Architecture Comparison"]},"1088":{"title":"Supabase","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Architecture Comparison"]},"1089":{"title":"Performance Benchmarks","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1090":{"title":"Requests Per Second (100 Concurrent Users, 1 Record)","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Performance Benchmarks"]},"1091":{"title":"Larger Payloads (500 Records, 100 VU)","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Performance Benchmarks"]},"1092":{"title":"PostgreSQL Type Handling","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Performance Benchmarks"]},"1093":{"title":"Feature Comparison Matrix","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1094":{"title":"Platform Features","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1095":{"title":"Core API Generation","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1096":{"title":"Table and View Query Features","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1097":{"title":"Custom Types and Nested JSON","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1098":{"title":"Authentication","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1099":{"title":"File Handling","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1100":{"title":"Security and Infrastructure","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1101":{"title":"Performance Features","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1102":{"title":"Connection Pooler Compatibility & Multi-Tenancy","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1103":{"title":"Real-Time Capabilities","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1104":{"title":"External Service Integration and Custom Code Execution","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1105":{"title":"NpgsqlRest: Declarative Proxy in SQL","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix","External Service Integration and Custom Code Execution"]},"1106":{"title":"PostgREST: No Custom Code Execution","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix","External Service Integration and Custom Code Execution"]},"1107":{"title":"Supabase: Edge Functions (Separate Deno Runtime)","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix","External Service Integration and Custom Code Execution"]},"1108":{"title":"Architectural Comparison","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix","External Service Integration and Custom Code Execution"]},"1109":{"title":"Advanced Features","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1110":{"title":"Observability","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Feature Comparison Matrix"]},"1111":{"title":"Error Handling","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1112":{"title":"Configuration Approach","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1113":{"title":"NpgsqlRest: SQL Comments","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Configuration Approach"]},"1114":{"title":"PostgREST: External Configuration + RLS","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Configuration Approach"]},"1115":{"title":"Supabase: Dashboard + RLS + Edge Functions","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Configuration Approach"]},"1116":{"title":"Deployment Comparison","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1117":{"title":"NpgsqlRest","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Deployment Comparison"]},"1118":{"title":"PostgREST","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Deployment Comparison"]},"1119":{"title":"Supabase Self-Hosted","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Deployment Comparison"]},"1120":{"title":"When to Choose Each","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1121":{"title":"Choose NpgsqlRest When:","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","When to Choose Each"]},"1122":{"title":"Choose PostgREST When:","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","When to Choose Each"]},"1123":{"title":"Choose Supabase When:","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","When to Choose Each"]},"1124":{"title":"Migration Considerations","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1125":{"title":"From PostgREST to NpgsqlRest","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Migration Considerations"]},"1126":{"title":"From Supabase to NpgsqlRest","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison","Migration Considerations"]},"1127":{"title":"Conclusion","titles":["NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison"]},"1128":{"title":"PostgreSQL Optimization Labels 101","titles":[]},"1129":{"title":"VOLATILE / STABLE / IMMUTABLE","titles":["PostgreSQL Optimization Labels 101"]},"1130":{"title":"PARALLEL UNSAFE / RESTRICTED / SAFE","titles":["PostgreSQL Optimization Labels 101"]},"1131":{"title":"COST / ROWS","titles":["PostgreSQL Optimization Labels 101"]},"1132":{"title":"COST (default: 100)","titles":["PostgreSQL Optimization Labels 101","COST / ROWS"]},"1133":{"title":"ROWS (default: 1000)","titles":["PostgreSQL Optimization Labels 101","COST / ROWS"]},"1134":{"title":"CALLED ON NULL INPUT / RETURNS NULL ON NULL INPUT / STRICT","titles":["PostgreSQL Optimization Labels 101"]},"1135":{"title":"Performance, Scalability, and High Availability with NpgsqlRest","titles":[]},"1136":{"title":"Caching Strategies","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1137":{"title":"HTTP Cache Headers: The Fastest Cache","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1138":{"title":"Setting Cache Headers in Annotations","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","HTTP Cache Headers: The Fastest Cache"]},"1139":{"title":"Cache Busting Technique","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","HTTP Cache Headers: The Fastest Cache"]},"1140":{"title":"Server-Side Caching","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1141":{"title":"Enabling Server Cache","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Server-Side Caching"]},"1142":{"title":"Cache Keys by Parameter","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Server-Side Caching"]},"1143":{"title":"Cache Expiration","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Server-Side Caching"]},"1144":{"title":"Cache Types","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1145":{"title":"Memory Cache","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Cache Types"]},"1146":{"title":"Redis Cache","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Cache Types"]},"1147":{"title":"Hybrid Cache","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies","Cache Types"]},"1148":{"title":"Cache Invalidation Endpoints","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1149":{"title":"Caching Set-Returning Functions","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1150":{"title":"Cache Profiles","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Caching Strategies"]},"1151":{"title":"Retry Strategies","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1152":{"title":"Connection Retries","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Retry Strategies"]},"1153":{"title":"Command Retries","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Retry Strategies"]},"1154":{"title":"Multiple Retry Strategies","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Retry Strategies","Command Retries"]},"1155":{"title":"PostgreSQL Error Code Classes","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Retry Strategies"]},"1156":{"title":"Rate Limiting","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1157":{"title":"Enabling Rate Limiting","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1158":{"title":"Fixed Window","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1159":{"title":"Sliding Window","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1160":{"title":"Token Bucket","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1161":{"title":"Concurrency Limiting","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1162":{"title":"Per-User Rate Limiting (Partitions)","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1163":{"title":"Combining Policies","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Rate Limiting"]},"1164":{"title":"Thread Pool Optimization","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1165":{"title":"The Thread Injection Problem","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1166":{"title":"Configuring Minimum Threads","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1167":{"title":"Worker Threads vs Completion Port Threads","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1168":{"title":"High-Throughput Configuration","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1169":{"title":"Sizing Guidelines","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1170":{"title":"When NOT to Increase Thread Pool Size","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1171":{"title":"Example: Burst Traffic Handling","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Thread Pool Optimization"]},"1172":{"title":"High Availability","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1173":{"title":"Multi-Host Connections","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1174":{"title":"Target Session Attributes","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1175":{"title":"Load Balancing","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1176":{"title":"Read Replica Routing","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1177":{"title":"Production High-Availability Configuration","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1178":{"title":"Same Schema Requirement","titles":["Performance, Scalability, and High Availability with NpgsqlRest","High Availability"]},"1179":{"title":"Putting It All Together","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1180":{"title":"Summary","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1181":{"title":"Development Time Saved","titles":["Performance, Scalability, and High Availability with NpgsqlRest","Summary"]},"1182":{"title":"Related Documentation","titles":["Performance, Scalability, and High Availability with NpgsqlRest"]},"1183":{"title":"Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","titles":[]},"1184":{"title":"The Architecture","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1185":{"title":"Defense in Depth: The Principle of Least Privilege","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1186":{"title":"Creating CSV Endpoints","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1187":{"title":"Define a Reusable Type","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Creating CSV Endpoints"]},"1188":{"title":"The Secured Report Function","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Creating CSV Endpoints"]},"1189":{"title":"CSV Annotations","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Creating CSV Endpoints"]},"1190":{"title":"Type Reuse and Composition","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1191":{"title":"The Problem: Schema Duplication","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Type Reuse and Composition"]},"1192":{"title":"The Solution: Composite Type Expansion","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Type Reuse and Composition"]},"1193":{"title":"Benefits for Applications","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Type Reuse and Composition"]},"1194":{"title":"Securing with Basic Authentication","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1195":{"title":"Password Hashing","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Securing with Basic Authentication"]},"1196":{"title":"Multiple Users via Configuration","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Securing with Basic Authentication"]},"1197":{"title":"Database-Driven Authentication with ChallengeCommand","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Securing with Basic Authentication"]},"1198":{"title":"SSL Configuration","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1199":{"title":"Setup Steps","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","SSL Configuration"]},"1200":{"title":"No Code Generation Required","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1201":{"title":"Excel Power Query Integration","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1202":{"title":"Connecting Excel to Your Endpoint","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Excel Power Query Integration"]},"1203":{"title":"The Power of Central Control","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Excel Power Query Integration"]},"1204":{"title":"Production Considerations","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Excel Power Query Integration"]},"1205":{"title":"When You Still Need ETL","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration","Excel Power Query Integration"]},"1206":{"title":"The Cost Comparison","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1207":{"title":"Complete Example","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1208":{"title":"Conclusion","titles":["Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration"]},"1209":{"title":"Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","titles":[]},"1210":{"title":"What Gets Stored (And What Doesn\'t)","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1211":{"title":"Architecture Overview","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1212":{"title":"Complete Example Walkthrough","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1213":{"title":"1. Database Schema","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1214":{"title":"2. Challenge Functions","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1215":{"title":"3. Completion Functions","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1216":{"title":"4. Authentication Function","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1217":{"title":"5. Configuration","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1218":{"title":"6. Client-Side Implementation","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Example Walkthrough"]},"1219":{"title":"Three Authentication Flows","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1220":{"title":"1. Registration (New User with Passkey)","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Three Authentication Flows"]},"1221":{"title":"2. Add Passkey (Existing User)","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Three Authentication Flows"]},"1222":{"title":"3. Login","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Three Authentication Flows"]},"1223":{"title":"Complete Configuration Reference","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1224":{"title":"General Settings","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1225":{"title":"Relying Party Settings","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1226":{"title":"Endpoint Paths","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1227":{"title":"WebAuthn Settings","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1228":{"title":"UserVerificationRequirement","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","WebAuthn Settings"]},"1229":{"title":"ResidentKeyRequirement","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","WebAuthn Settings"]},"1230":{"title":"AttestationConveyance","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","WebAuthn Settings"]},"1231":{"title":"SQL Commands Reference","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1232":{"title":"ChallengeAddExistingUserCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1233":{"title":"ChallengeRegistrationCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1234":{"title":"ChallengeAuthenticationCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1235":{"title":"VerifyChallengeCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1236":{"title":"AuthenticateDataCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1237":{"title":"CompleteAddExistingUserCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1238":{"title":"CompleteRegistrationCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1239":{"title":"CompleteAuthenticateCommand","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference","SQL Commands Reference"]},"1240":{"title":"Column Name Configuration","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1241":{"title":"Analytics Data","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Complete Configuration Reference"]},"1242":{"title":"Security Considerations","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1243":{"title":"What NpgsqlRest Validates","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Security Considerations"]},"1244":{"title":"What You Control","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Security Considerations"]},"1245":{"title":"Rate Limiting","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Security Considerations"]},"1246":{"title":"Advantages of This Approach","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1247":{"title":"1. SQL-First Logic","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Advantages of This Approach"]},"1248":{"title":"2. No External Dependencies","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Advantages of This Approach"]},"1249":{"title":"3. Complete Control","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Advantages of This Approach"]},"1250":{"title":"4. Built-in Resilience","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Advantages of This Approach"]},"1251":{"title":"5. Privacy by Design","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest","Advantages of This Approach"]},"1252":{"title":"Getting Started","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1253":{"title":"Conclusion","titles":["Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest"]},"1254":{"title":"PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","titles":[]},"1255":{"title":"What We Tested","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1256":{"title":"What\'s New in This Benchmark","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1257":{"title":"Version Updates","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","What\'s New in This Benchmark"]},"1258":{"title":"New Test Scenarios","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","What\'s New in This Benchmark"]},"1259":{"title":"Infrastructure Changes","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","What\'s New in This Benchmark"]},"1260":{"title":"Comparing With Previous Results","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","What\'s New in This Benchmark"]},"1261":{"title":"Key Findings","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1262":{"title":"Swoole PHP Dominates Large Payload Scenarios","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1263":{"title":"NpgsqlRest Leads High-Concurrency Low-Payload Scenarios","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1264":{"title":"The Top Performers by Scenario","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1265":{"title":"Performance Tiers at 100 VU, 1 Record","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1266":{"title":"What Changed From 2025","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1267":{"title":"Scaling Behavior","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1268":{"title":"Large Payloads Level the Playing Field","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1269":{"title":"Pure HTTP Overhead (Minimal Baseline)","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1270":{"title":"POST Body Parsing Performance","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1271":{"title":"Python Frameworks Continue to Struggle","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1272":{"title":"JIT vs AOT in 2026","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Key Findings"]},"1273":{"title":"Why Certain Frameworks Excel","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1274":{"title":"Swoole PHP\'s Rise","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Why Certain Frameworks Excel"]},"1275":{"title":"Go\'s HTTP Dominance","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Why Certain Frameworks Excel"]},"1276":{"title":"NpgsqlRest\'s Architecture","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Why Certain Frameworks Excel"]},"1277":{"title":"Resource Usage","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1278":{"title":"Key Observations","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Resource Usage"]},"1279":{"title":"Important Note: JSON and Array Type Handling","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1280":{"title":"Conclusion","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1281":{"title":"Lines of Code Comparison","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Conclusion"]},"1282":{"title":"Full Benchmark Results","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared"]},"1283":{"title":"Summary Tables","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1284":{"title":"Data Type Serialization","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Summary Tables"]},"1285":{"title":"New Scenarios","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Summary Tables"]},"1286":{"title":"Data Type Serialization Tests","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1287":{"title":"1 Virtual User, 1 Record","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Data Type Serialization Tests"]},"1288":{"title":"1 Virtual User, 10 Records","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Data Type Serialization Tests"]},"1289":{"title":"100 Virtual Users, 1 Record","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Data Type Serialization Tests"]},"1290":{"title":"100 Virtual Users, 100 Records","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Data Type Serialization Tests"]},"1291":{"title":"100 Virtual Users, 500 Records","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Data Type Serialization Tests"]},"1292":{"title":"Minimal Baseline (Pure HTTP Overhead)","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1293":{"title":"100 Virtual Users","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Minimal Baseline (Pure HTTP Overhead)"]},"1294":{"title":"POST Body Parsing","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1295":{"title":"50 Virtual Users, 10 Records","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","POST Body Parsing"]},"1296":{"title":"Nested JSON Serialization","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1297":{"title":"50 Virtual Users, Depth 1","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Nested JSON Serialization"]},"1298":{"title":"Large Payload","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1299":{"title":"25 Virtual Users, 100KB Payload","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Large Payload"]},"1300":{"title":"Many Parameters (20 params)","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results"]},"1301":{"title":"50 Virtual Users","titles":["PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared","Full Benchmark Results","Many Parameters (20 params)"]},"1302":{"title":"Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","titles":[]},"1303":{"title":"The Traditional Approach: Complex Infrastructure","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1304":{"title":"The NpgsqlRest Approach: PostgreSQL IS Your Real-Time Server","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1305":{"title":"How SSE Works in NpgsqlRest","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1306":{"title":"Building the Chat: Step by Step","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1307":{"title":"Step 1: Schema Setup","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Building the Chat: Step by Step"]},"1308":{"title":"Step 2: Login Function","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Building the Chat: Step by Step"]},"1309":{"title":"Step 3: The Magic - Send Message with SSE","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Building the Chat: Step by Step"]},"1310":{"title":"Step 4: Message History","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Building the Chat: Step by Step"]},"1311":{"title":"Understanding SSE Scopes","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1312":{"title":"sse_scope authorize","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Understanding SSE Scopes"]},"1313":{"title":"sse_scope authorize <roles/users>","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Understanding SSE Scopes"]},"1314":{"title":"sse_scope matching","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Understanding SSE Scopes"]},"1315":{"title":"sse_scope all","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Understanding SSE Scopes"]},"1316":{"title":"Dynamic Scopes with RAISE HINT","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Understanding SSE Scopes"]},"1317":{"title":"The Auto-Generated TypeScript Client","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1318":{"title":"The Frontend: Minimal Code Required","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1319":{"title":"Code Comparison: Traditional vs NpgsqlRest","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1320":{"title":"Traditional Real-Time Chat Architecture","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Code Comparison: Traditional vs NpgsqlRest"]},"1321":{"title":"NpgsqlRest Approach","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Code Comparison: Traditional vs NpgsqlRest"]},"1322":{"title":"The Numbers","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1323":{"title":"When to Use SSE vs WebSockets","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1324":{"title":"Why Not PostgreSQL LISTEN/NOTIFY?","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1325":{"title":"How NpgsqlRest Avoids This Problem","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events","Why Not PostgreSQL LISTEN/NOTIFY?"]},"1326":{"title":"Advanced: Execution-ID correlation as soft channels","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1327":{"title":"Conclusion","titles":["Build a Real-Time Chat App with PostgreSQL and Server-Sent Events"]},"1328":{"title":"Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","titles":[]},"1329":{"title":"The Problem: Connection Pool Exhaustion","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1330":{"title":"The NpgsqlRest Solution: Proxy Mode","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1331":{"title":"Passthrough Mode: Zero Database Connections","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","The NpgsqlRest Solution: Proxy Mode"]},"1332":{"title":"Transform Mode: Process Before Returning","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","The NpgsqlRest Solution: Proxy Mode"]},"1333":{"title":"Architecture: NpgsqlRest as API Gateway","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1334":{"title":"Building the AI Text Analysis Service","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1335":{"title":"The Upstream AI Service","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1336":{"title":"PostgreSQL Schema: Caching Layer","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1337":{"title":"Passthrough Proxy: Health Check","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1338":{"title":"Transform Proxy: Summarization with Caching","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1339":{"title":"Full Analysis: Complete Transform Example","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1340":{"title":"Configuration","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Building the AI Text Analysis Service"]},"1341":{"title":"Proxy Response Parameters","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1342":{"title":"The Generated TypeScript Client","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1343":{"title":"Docker: Bun Runtime Image","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1344":{"title":"Use Cases for Reverse Proxy","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1345":{"title":"API Gateway Pattern","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Use Cases for Reverse Proxy"]},"1346":{"title":"Caching Expensive Operations","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Use Cases for Reverse Proxy"]},"1347":{"title":"Data Enrichment","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Use Cases for Reverse Proxy"]},"1348":{"title":"Authentication Context Forwarding","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","Use Cases for Reverse Proxy"]},"1349":{"title":"The Numbers","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1350":{"title":"Code Comparison","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest","The Numbers"]},"1351":{"title":"Summary: When to Use Proxy Mode","titles":["Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest"]},"1352":{"title":"Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript","titles":[]},"1353":{"title":"Storage Options","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1354":{"title":"When to Use Each Strategy","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript","Storage Options"]},"1355":{"title":"Step 1: Create the Schema","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1356":{"title":"Step 2: Configure Upload Handlers","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1357":{"title":"Step 3: Create the Upload Function","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1358":{"title":"Step 4: Add the Upload Annotation","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1359":{"title":"How the Metadata Works","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1360":{"title":"When Uploads Fail","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript","How the Metadata Works"]},"1361":{"title":"Step 5: Use the Generated Client","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1362":{"title":"Step 6: Serve Images from Large Objects","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1363":{"title":"Performance: Large Objects vs File System","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript","Step 6: Serve Images from Large Objects"]},"1364":{"title":"Displaying Images","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1365":{"title":"Backup Advantage","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1366":{"title":"Traditional Approach Comparison","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1367":{"title":"Summary","titles":["Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript"]},"1368":{"title":"SQL File Source: REST Endpoints from Plain .sql Files","titles":[]},"1369":{"title":"The Simplest Endpoint","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1370":{"title":"Multi-Command: Multiple Queries in One Request","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1371":{"title":"Authentication: Login, Logout, Who Am I","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1372":{"title":"Real-Time Chat with SSE","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1373":{"title":"CSV Export with Basic Auth","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1374":{"title":"Dynamic Excel Output","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1375":{"title":"Nested Custom Types","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1376":{"title":"External API Calls","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1377":{"title":"The Important Part","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1378":{"title":"SQL Files vs Routines: When to Use Which","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1379":{"title":"What Came After This Post","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1380":{"title":"Get Started","titles":["SQL File Source: REST Endpoints from Plain .sql Files"]},"1381":{"title":"The Backend That Writes Itself","titles":[]},"1382":{"title":"The narration","titles":["The Backend That Writes Itself"]},"1383":{"title":"Where to go next","titles":["The Backend That Writes Itself"]},"1384":{"title":"SQL REST API","titles":[]},"1385":{"title":"Introduction","titles":["SQL REST API"]},"1386":{"title":"SQL Script Files as REST API Endpoints","titles":["SQL REST API"]},"1387":{"title":"SQL Files vs Routines","titles":["SQL REST API"]},"1388":{"title":"1) No Migrations","titles":["SQL REST API","SQL Files vs Routines"]},"1389":{"title":"2) No Comment On Statements","titles":["SQL REST API","SQL Files vs Routines"]},"1390":{"title":"3) Mapping by Position vs No Mapping at All","titles":["SQL REST API","SQL Files vs Routines"]},"1391":{"title":"4) Multiple Result Sets","titles":["SQL REST API","SQL Files vs Routines"]},"1392":{"title":"5) Named Parameters","titles":["SQL REST API","SQL Files vs Routines"]},"1393":{"title":"6) Testability","titles":["SQL REST API","SQL Files vs Routines"]},"1394":{"title":"7) Complex Logic","titles":["SQL REST API","SQL Files vs Routines"]},"1395":{"title":"1) Parameters are not supported in DO blocks.","titles":["SQL REST API","SQL Files vs Routines","7) Complex Logic"]},"1396":{"title":"2) DO blocks can\'t return result sets.","titles":["SQL REST API","SQL Files vs Routines","7) Complex Logic"]},"1397":{"title":"Other Features in v3.12.0","titles":["SQL REST API"]},"1398":{"title":"Self-Referencing Endpoints","titles":["SQL REST API","Other Features in v3.12.0"]},"1399":{"title":"Future Improvements","titles":["SQL REST API","Other Features in v3.12.0","Self-Referencing Endpoints"]},"1400":{"title":"AI Tools","titles":["SQL REST API"]},"1401":{"title":"1) AI Tools with NpgsqlRest","titles":["SQL REST API","AI Tools"]},"1402":{"title":"2) AI Tools in NpgsqlRest Development","titles":["SQL REST API","AI Tools"]},"1403":{"title":"Philosophy of NpgsqlRest","titles":["SQL REST API"]},"1404":{"title":"Wrap It Up Chapter","titles":["SQL REST API"]},"1405":{"title":"The Power of Simplicity","titles":[]},"1406":{"title":"From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","titles":[]},"1407":{"title":"The Pipeline","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1408":{"title":"A Minimal Example","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1409":{"title":"End-to-End Type Safety in Action","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1410":{"title":"Uploads: When the Generated Wrapper Isn\'t Just fetch()","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1411":{"title":"Per-Endpoint Control with @tsclient Annotations","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1412":{"title":"Disable Generation: Binary Endpoints","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Per-Endpoint Control with @tsclient Annotations"]},"1413":{"title":"URL-Only: Browser-Navigation Endpoints","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Per-Endpoint Control with @tsclient Annotations"]},"1414":{"title":"Module Grouping: Logical Bundles Across Schemas","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Per-Endpoint Control with @tsclient Annotations"]},"1415":{"title":"Other Per-Endpoint Toggles","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Per-Endpoint Control with @tsclient Annotations"]},"1416":{"title":"Scaling Up: Real-World Configuration","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1417":{"title":"Real-World Workflow: Dev Codegen, Prod No-Codegen","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1418":{"title":"Two Processes, One Tight Loop","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Real-World Workflow: Dev Codegen, Prod No-Codegen"]},"1419":{"title":"Managing Change","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Real-World Workflow: Dev Codegen, Prod No-Codegen"]},"1420":{"title":"Production: No Codegen, Just the Server","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Real-World Workflow: Dev Codegen, Prod No-Codegen"]},"1421":{"title":"What This Means in Practice","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator","Real-World Workflow: Dev Codegen, Prod No-Codegen"]},"1422":{"title":"Workflow Summary","titles":["From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest\'s Code Generator"]},"1423":{"title":"Web Scraping with PostgreSQL: HTTP Types + XML Functions","titles":[]},"1424":{"title":"The recipe","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1425":{"title":"Example 17: average book price","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1426":{"title":"Fetch — the HTTP Custom Type","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions","Example 17: average book price"]},"1427":{"title":"Parse — regex to isolate, XPath to read","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions","Example 17: average book price"]},"1428":{"title":"Why regex and XPath?","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions","Example 17: average book price"]},"1429":{"title":"Example 16: best-value laptop","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1430":{"title":"Be a good citizen: cache the page","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1431":{"title":"A different split: fetch in SQL, parse in a service","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1432":{"title":"When this works (and when it doesn\'t)","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1433":{"title":"Try it","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1434":{"title":"Related","titles":["Web Scraping with PostgreSQL: HTTP Types + XML Functions"]},"1435":{"title":"What Have PostgreSQL Functions Ever Done for Us?","titles":[]},"1436":{"title":"Type Safety?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1437":{"title":"Real Encapsulation?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1438":{"title":"Zero Downtime?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1439":{"title":"Performance?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1440":{"title":"Race Conditions Minimized?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1441":{"title":"Security?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1442":{"title":"A Short Test Loop?","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1443":{"title":"So, DDD developers","titles":["What Have PostgreSQL Functions Ever Done for Us?"]},"1444":{"title":"Authentication","titles":[]},"1445":{"title":"Overview","titles":["Authentication"]},"1446":{"title":"Cookie Authentication","titles":["Authentication"]},"1447":{"title":"Cookie Settings Reference","titles":["Authentication","Cookie Authentication"]},"1448":{"title":"Cookie Security","titles":["Authentication","Cookie Authentication"]},"1449":{"title":"Cross-Origin Cookies (New in 3.15.0)","titles":["Authentication","Cookie Authentication"]},"1450":{"title":"Microsoft Bearer Token Authentication","titles":["Authentication"]},"1451":{"title":"Bearer Token Settings Reference","titles":["Authentication","Microsoft Bearer Token Authentication"]},"1452":{"title":"Token Refresh","titles":["Authentication","Microsoft Bearer Token Authentication"]},"1453":{"title":"JWT Authentication","titles":["Authentication"]},"1454":{"title":"JWT Settings Reference","titles":["Authentication","JWT Authentication"]},"1455":{"title":"Login Response","titles":["Authentication","JWT Authentication"]},"1456":{"title":"Token Refresh","titles":["Authentication","JWT Authentication"]},"1457":{"title":"JWT vs Microsoft Bearer Token","titles":["Authentication","JWT Authentication"]},"1458":{"title":"Additional Authentication Schemes","titles":["Authentication"]},"1459":{"title":"Per-Type Override Fields","titles":["Authentication","Additional Authentication Schemes"]},"1460":{"title":"Validation at Startup (Fail-Fast)","titles":["Authentication","Additional Authentication Schemes"]},"1461":{"title":"Complete Examples","titles":["Authentication"]},"1462":{"title":"Cookie Authentication","titles":["Authentication","Complete Examples"]},"1463":{"title":"JWT Authentication","titles":["Authentication","Complete Examples"]},"1464":{"title":"Combined Authentication","titles":["Authentication","Complete Examples"]},"1465":{"title":"Related","titles":["Authentication"]},"1466":{"title":"Next Steps","titles":["Authentication"]},"1467":{"title":"See Also","titles":["Authentication"]},"1468":{"title":"Authentication Options","titles":[]},"1469":{"title":"Overview","titles":["Authentication Options"]},"1470":{"title":"General Settings","titles":["Authentication Options"]},"1471":{"title":"Login Response Columns","titles":["Authentication Options"]},"1472":{"title":"Password Handling","titles":["Authentication Options"]},"1473":{"title":"Password Verification Command Parameters","titles":["Authentication Options","Password Handling"]},"1474":{"title":"Default Claim Types","titles":["Authentication Options"]},"1475":{"title":"User Context Settings","titles":["Authentication Options"]},"1476":{"title":"Default ContextKeyClaimsMapping","titles":["Authentication Options","User Context Settings"]},"1477":{"title":"User Parameters Settings","titles":["Authentication Options"]},"1478":{"title":"Default ParameterNameClaimsMapping","titles":["Authentication Options","User Parameters Settings"]},"1479":{"title":"Login and Logout Paths","titles":["Authentication Options"]},"1480":{"title":"Login Command Convention","titles":["Authentication Options","Login and Logout Paths"]},"1481":{"title":"Logout Command Convention","titles":["Authentication Options","Login and Logout Paths"]},"1482":{"title":"Basic Authentication","titles":["Authentication Options"]},"1483":{"title":"Complete Example","titles":["Authentication Options"]},"1484":{"title":"Related","titles":["Authentication Options"]},"1485":{"title":"Next Steps","titles":["Authentication Options"]},"1486":{"title":"See Also","titles":["Authentication Options"]},"1487":{"title":"Antiforgery","titles":[]},"1488":{"title":"Overview","titles":["Antiforgery"]},"1489":{"title":"Settings Reference","titles":["Antiforgery"]},"1490":{"title":"Token Submission","titles":["Antiforgery"]},"1491":{"title":"Form Field","titles":["Antiforgery","Token Submission"]},"1492":{"title":"HTTP Header","titles":["Antiforgery","Token Submission"]},"1493":{"title":"X-Frame-Options Header","titles":["Antiforgery"]},"1494":{"title":"Example Configuration","titles":["Antiforgery"]},"1495":{"title":"Related","titles":["Antiforgery"]},"1496":{"title":"Next Steps","titles":["Antiforgery"]},"1497":{"title":"Basic Auth Configuration","titles":[]},"1498":{"title":"Overview","titles":["Basic Auth Configuration"]},"1499":{"title":"Settings","titles":["Basic Auth Configuration"]},"1500":{"title":"SSL Requirement Values","titles":["Basic Auth Configuration"]},"1501":{"title":"Challenge Command Parameters","titles":["Basic Auth Configuration"]},"1502":{"title":"Static Users Example","titles":["Basic Auth Configuration"]},"1503":{"title":"Database Authentication Example","titles":["Basic Auth Configuration"]},"1504":{"title":"Challenge Function Example","titles":["Basic Auth Configuration","Database Authentication Example"]},"1505":{"title":"Complete Example","titles":["Basic Auth Configuration"]},"1506":{"title":"Related","titles":["Basic Auth Configuration"]},"1507":{"title":"Next Steps","titles":["Basic Auth Configuration"]},"1508":{"title":"See Also","titles":["Basic Auth Configuration"]},"1509":{"title":"Cache Options","titles":[]},"1510":{"title":"Overview","titles":["Cache Options"]},"1511":{"title":"Settings Reference","titles":["Cache Options"]},"1512":{"title":"Cache Types","titles":["Cache Options"]},"1513":{"title":"Memory Cache","titles":["Cache Options","Cache Types"]},"1514":{"title":"Redis Cache","titles":["Cache Options","Cache Types"]},"1515":{"title":"Hybrid Cache","titles":["Cache Options","Cache Types"]},"1516":{"title":"Cache Key Hashing","titles":["Cache Options"]},"1517":{"title":"Caching Set-Returning Functions","titles":["Cache Options"]},"1518":{"title":"Cache Invalidation Endpoints","titles":["Cache Options"]},"1519":{"title":"Cache Profiles","titles":["Cache Options"]},"1520":{"title":"Overview","titles":["Cache Options","Cache Profiles"]},"1521":{"title":"Profile fields","titles":["Cache Options","Cache Profiles"]},"1522":{"title":"Backend pooling","titles":["Cache Options","Cache Profiles"]},"1523":{"title":"When rules","titles":["Cache Options","Cache Profiles"]},"1524":{"title":"Pattern: skip-on-condition","titles":["Cache Options","Cache Profiles","When rules"]},"1525":{"title":"Pattern: dynamic TTL","titles":["Cache Options","Cache Profiles","When rules"]},"1526":{"title":"Pattern: array-of-values","titles":["Cache Options","Cache Profiles","When rules"]},"1527":{"title":"Validation","titles":["Cache Options","Cache Profiles"]},"1528":{"title":"Connection pooler note","titles":["Cache Options","Cache Profiles"]},"1529":{"title":"Complete example","titles":["Cache Options","Cache Profiles"]},"1530":{"title":"Routine Annotations","titles":["Cache Options"]},"1531":{"title":"cached","titles":["Cache Options","Routine Annotations"]},"1532":{"title":"cache_expires / cache_expires_in","titles":["Cache Options","Routine Annotations"]},"1533":{"title":"cache_profile","titles":["Cache Options","Routine Annotations"]},"1534":{"title":"Example Configuration","titles":["Cache Options"]},"1535":{"title":"Related","titles":["Cache Options"]},"1536":{"title":"Next Steps","titles":["Cache Options"]},"1537":{"title":"See Also","titles":["Cache Options"]},"1538":{"title":"Claims Mapping","titles":[]},"1539":{"title":"Overview","titles":["Claims Mapping"]},"1540":{"title":"User Context (PostgreSQL Context Variables)","titles":["Claims Mapping"]},"1541":{"title":"Default Context Mapping","titles":["Claims Mapping","User Context (PostgreSQL Context Variables)"]},"1542":{"title":"Custom Context Mapping Example","titles":["Claims Mapping","User Context (PostgreSQL Context Variables)"]},"1543":{"title":"Access in PostgreSQL","titles":["Claims Mapping","User Context (PostgreSQL Context Variables)"]},"1544":{"title":"User Parameters","titles":["Claims Mapping"]},"1545":{"title":"Default Parameter Mapping","titles":["Claims Mapping","User Parameters"]},"1546":{"title":"Custom Parameter Mapping Example","titles":["Claims Mapping","User Parameters"]},"1547":{"title":"Example Function Using Parameters","titles":["Claims Mapping","User Parameters"]},"1548":{"title":"Complete Example","titles":["Claims Mapping"]},"1549":{"title":"Related","titles":["Claims Mapping"]},"1550":{"title":"Next Steps","titles":["Claims Mapping"]},"1551":{"title":"See Also","titles":["Claims Mapping"]},"1552":{"title":"Code Generation","titles":[]},"1553":{"title":"Overview","titles":["Code Generation"]},"1554":{"title":"General Settings","titles":["Code Generation"]},"1555":{"title":"Host Configuration","titles":["Code Generation"]},"1556":{"title":"Comment Headers","titles":["Code Generation"]},"1557":{"title":"Comment Header Styles","titles":["Code Generation","Comment Headers"]},"1558":{"title":"Response Options","titles":["Code Generation"]},"1559":{"title":"Type Generation","titles":["Code Generation"]},"1560":{"title":"Import Configuration","titles":["Code Generation"]},"1561":{"title":"Function Parameters","titles":["Code Generation"]},"1562":{"title":"Skip Options","titles":["Code Generation"]},"1563":{"title":"Export Options","titles":["Code Generation"]},"1564":{"title":"Headers and Security","titles":["Code Generation"]},"1565":{"title":"File Headers","titles":["Code Generation"]},"1566":{"title":"What Gets Generated","titles":["Code Generation"]},"1567":{"title":"Default Function Shape","titles":["Code Generation","What Gets Generated"]},"1568":{"title":"IncludeStatusCode: false — Direct Response","titles":["Code Generation","What Gets Generated"]},"1569":{"title":"OmitAutomaticParameters: true","titles":["Code Generation","What Gets Generated"]},"1570":{"title":"CreateSeparateTypeFile: true — Type-Only Files","titles":["Code Generation","What Gets Generated"]},"1571":{"title":"ExportTypes: true — Importable Interfaces","titles":["Code Generation","What Gets Generated"]},"1572":{"title":"ExportUrls: true — URL Constants","titles":["Code Generation","What Gets Generated"]},"1573":{"title":"ExportEventSources: true — SSE Helpers","titles":["Code Generation","What Gets Generated"]},"1574":{"title":"ImportBaseUrlFrom & ImportParseQueryFrom","titles":["Code Generation","What Gets Generated"]},"1575":{"title":"Path Parameters","titles":["Code Generation","What Gets Generated"]},"1576":{"title":"UseRoutineNameInsteadOfEndpoint: true","titles":["Code Generation","What Gets Generated"]},"1577":{"title":"BySchema: true — One File Per Schema","titles":["Code Generation","What Gets Generated"]},"1578":{"title":"Example Configurations","titles":["Code Generation"]},"1579":{"title":"Minimal (Examples Repo Style)","titles":["Code Generation","Example Configurations"]},"1580":{"title":"Single JavaScript File (No Types)","titles":["Code Generation","Example Configurations"]},"1581":{"title":"Production SvelteKit / Vite Setup","titles":["Code Generation","Example Configurations"]},"1582":{"title":"With Custom Headers and Imports","titles":["Code Generation","Example Configurations"]},"1583":{"title":"Related","titles":["Code Generation"]},"1584":{"title":"Next Steps","titles":["Code Generation"]},"1585":{"title":"See Also","titles":["Code Generation"]},"1586":{"title":"Command Retry","titles":[]},"1587":{"title":"Overview","titles":["Command Retry"]},"1588":{"title":"Settings Reference","titles":["Command Retry"]},"1589":{"title":"Strategy Settings","titles":["Command Retry"]},"1590":{"title":"Retry Sequence","titles":["Command Retry","Strategy Settings"]},"1591":{"title":"Default Error Codes","titles":["Command Retry"]},"1592":{"title":"Serialization Failures","titles":["Command Retry","Default Error Codes"]},"1593":{"title":"Connection Issues (Class 08)","titles":["Command Retry","Default Error Codes"]},"1594":{"title":"Resource Constraints (Class 53)","titles":["Command Retry","Default Error Codes"]},"1595":{"title":"System Errors (Class 57/58)","titles":["Command Retry","Default Error Codes"]},"1596":{"title":"Lock Acquisition Issues (Class 55)","titles":["Command Retry","Default Error Codes"]},"1597":{"title":"Multiple Strategies","titles":["Command Retry"]},"1598":{"title":"Example Configuration","titles":["Command Retry"]},"1599":{"title":"Using Strategies in Annotations","titles":["Command Retry"]},"1600":{"title":"Related","titles":["Command Retry"]},"1601":{"title":"Next Steps","titles":["Command Retry"]},"1602":{"title":"See Also","titles":["Command Retry"]},"1603":{"title":"Config Section","titles":[]},"1604":{"title":"Settings Reference","titles":["Config Section"]},"1605":{"title":"Placeholder Forms: Optional and Required (3.17.0+)","titles":["Config Section"]},"1606":{"title":"Environment Variable Override","titles":["Config Section"]},"1607":{"title":"Environment Variable Parsing","titles":["Config Section"]},"1608":{"title":"Loading from .env File","titles":["Config Section"]},"1609":{"title":"Configuration Key Validation","titles":["Config Section"]},"1610":{"title":"Related","titles":["Config Section"]},"1611":{"title":"Next Steps","titles":["Config Section"]},"1612":{"title":"Connection Settings","titles":[]},"1613":{"title":"Connection Strings","titles":["Connection Settings"]},"1614":{"title":"Multiple Connections","titles":["Connection Settings","Connection Strings"]},"1615":{"title":"Using Environment Variables","titles":["Connection Settings","Connection Strings"]},"1616":{"title":"Connection String Parameters","titles":["Connection Settings","Connection Strings"]},"1617":{"title":"Connection Settings","titles":["Connection Settings"]},"1618":{"title":"Settings Reference","titles":["Connection Settings","Connection Settings"]},"1619":{"title":"Application Name in Connection","titles":["Connection Settings","Connection Settings"]},"1620":{"title":"JSON Application Name","titles":["Connection Settings","Connection Settings"]},"1621":{"title":"Connection Testing","titles":["Connection Settings","Connection Settings"]},"1622":{"title":"Retry Options","titles":["Connection Settings"]},"1623":{"title":"Retry Settings Reference","titles":["Connection Settings","Retry Options"]},"1624":{"title":"Default Error Codes","titles":["Connection Settings","Retry Options"]},"1625":{"title":"Custom Retry Configuration","titles":["Connection Settings","Retry Options"]},"1626":{"title":"Multi-Host Connection Support","titles":["Connection Settings"]},"1627":{"title":"Multi-Host Connection Strings","titles":["Connection Settings","Multi-Host Connection Support"]},"1628":{"title":"Target Session Attributes","titles":["Connection Settings","Multi-Host Connection Support"]},"1629":{"title":"Multi-Host Example","titles":["Connection Settings","Multi-Host Connection Support"]},"1630":{"title":"NpgsqlRest Connection Options","titles":["Connection Settings"]},"1631":{"title":"NpgsqlRest Connection Settings Reference","titles":["Connection Settings","NpgsqlRest Connection Options"]},"1632":{"title":"Using Multiple Connections","titles":["Connection Settings","NpgsqlRest Connection Options"]},"1633":{"title":"Complete Example","titles":["Connection Settings"]},"1634":{"title":"Related","titles":["Connection Settings"]},"1635":{"title":"Next Steps","titles":["Connection Settings"]},"1636":{"title":"See Also","titles":["Connection Settings"]},"1637":{"title":"CORS","titles":[]},"1638":{"title":"Overview","titles":["CORS"]},"1639":{"title":"Settings Reference","titles":["CORS"]},"1640":{"title":"Allowed Origins","titles":["CORS"]},"1641":{"title":"Allow All Origins","titles":["CORS","Allowed Origins"]},"1642":{"title":"Allowed Methods","titles":["CORS"]},"1643":{"title":"Allowed Headers","titles":["CORS"]},"1644":{"title":"Credentials","titles":["CORS"]},"1645":{"title":"Preflight Caching","titles":["CORS"]},"1646":{"title":"Example Configuration","titles":["CORS"]},"1647":{"title":"Related","titles":["CORS"]},"1648":{"title":"Next Steps","titles":["CORS"]},"1649":{"title":"Data Protection","titles":[]},"1650":{"title":"Overview","titles":["Data Protection"]},"1651":{"title":"Settings Reference","titles":["Data Protection"]},"1652":{"title":"Storage Options","titles":["Data Protection"]},"1653":{"title":"Default Storage","titles":["Data Protection","Storage Options"]},"1654":{"title":"File System Storage","titles":["Data Protection","Storage Options"]},"1655":{"title":"Database Storage","titles":["Data Protection","Storage Options"]},"1656":{"title":"Encryption Algorithms","titles":["Data Protection"]},"1657":{"title":"Validation Algorithms","titles":["Data Protection"]},"1658":{"title":"Application Name Scope","titles":["Data Protection"]},"1659":{"title":"Key Encryption Options","titles":["Data Protection"]},"1660":{"title":"No Encryption (Default)","titles":["Data Protection","Key Encryption Options"]},"1661":{"title":"Certificate Encryption","titles":["Data Protection","Key Encryption Options"]},"1662":{"title":"DPAPI Encryption (Windows Only)","titles":["Data Protection","Key Encryption Options"]},"1663":{"title":"Complete Example","titles":["Data Protection"]},"1664":{"title":"Column Encryption with Annotations","titles":["Data Protection"]},"1665":{"title":"Related","titles":["Data Protection"]},"1666":{"title":"Next Steps","titles":["Data Protection"]},"1667":{"title":"See Also","titles":["Data Protection"]},"1668":{"title":"Error Handling","titles":[]},"1669":{"title":"Overview","titles":["Error Handling"]},"1670":{"title":"Settings Reference","titles":["Error Handling"]},"1671":{"title":"Error Mapping Object","titles":["Error Handling"]},"1672":{"title":"Timeout Error Mapping","titles":["Error Handling"]},"1673":{"title":"Error Code Policies","titles":["Error Handling"]},"1674":{"title":"Default Error Code Mappings","titles":["Error Handling","Error Code Policies"]},"1675":{"title":"Response Fields","titles":["Error Handling"]},"1676":{"title":"Type URL","titles":["Error Handling","Response Fields"]},"1677":{"title":"TraceId","titles":["Error Handling","Response Fields"]},"1678":{"title":"Example Configuration","titles":["Error Handling"]},"1679":{"title":"Related","titles":["Error Handling"]},"1680":{"title":"Next Steps","titles":["Error Handling"]},"1681":{"title":"See Also","titles":["Error Handling"]},"1682":{"title":"External OAuth Authentication","titles":[]},"1683":{"title":"Overview","titles":["External OAuth Authentication"]},"1684":{"title":"Settings Reference","titles":["External OAuth Authentication"]},"1685":{"title":"SignInHtmlTemplate","titles":["External OAuth Authentication","Settings Reference"]},"1686":{"title":"Login Command","titles":["External OAuth Authentication"]},"1687":{"title":"Parameters","titles":["External OAuth Authentication","Login Command"]},"1688":{"title":"Result Set Conventions","titles":["External OAuth Authentication","Login Command"]},"1689":{"title":"Example Login Command Function","titles":["External OAuth Authentication","Login Command"]},"1690":{"title":"OAuth Providers","titles":["External OAuth Authentication"]},"1691":{"title":"Google","titles":["External OAuth Authentication","OAuth Providers"]},"1692":{"title":"LinkedIn","titles":["External OAuth Authentication","OAuth Providers"]},"1693":{"title":"GitHub","titles":["External OAuth Authentication","OAuth Providers"]},"1694":{"title":"Microsoft","titles":["External OAuth Authentication","OAuth Providers"]},"1695":{"title":"Facebook","titles":["External OAuth Authentication","OAuth Providers"]},"1696":{"title":"Provider Settings Reference","titles":["External OAuth Authentication"]},"1697":{"title":"Custom Providers","titles":["External OAuth Authentication","Provider Settings Reference"]},"1698":{"title":"Complete Example","titles":["External OAuth Authentication"]},"1699":{"title":"Related","titles":["External OAuth Authentication"]},"1700":{"title":"Next Steps","titles":["External OAuth Authentication"]},"1701":{"title":"Forwarded Headers","titles":[]},"1702":{"title":"Overview","titles":["Forwarded Headers"]},"1703":{"title":"Settings Reference","titles":["Forwarded Headers"]},"1704":{"title":"Why Forwarded Headers Matter","titles":["Forwarded Headers"]},"1705":{"title":"Processed Headers","titles":["Forwarded Headers"]},"1706":{"title":"Forward Limit","titles":["Forwarded Headers"]},"1707":{"title":"Known Proxies","titles":["Forwarded Headers"]},"1708":{"title":"Known Networks","titles":["Forwarded Headers"]},"1709":{"title":"Allowed Hosts","titles":["Forwarded Headers"]},"1710":{"title":"Example Configurations","titles":["Forwarded Headers"]},"1711":{"title":"Behind nginx","titles":["Forwarded Headers","Example Configurations"]},"1712":{"title":"AWS ALB / ELB","titles":["Forwarded Headers","Example Configurations"]},"1713":{"title":"Azure App Service","titles":["Forwarded Headers","Example Configurations"]},"1714":{"title":"Cloudflare + Origin Server","titles":["Forwarded Headers","Example Configurations"]},"1715":{"title":"Docker/Kubernetes with Internal Load Balancer","titles":["Forwarded Headers","Example Configurations"]},"1716":{"title":"Development (Trust All)","titles":["Forwarded Headers","Example Configurations"]},"1717":{"title":"Security Considerations","titles":["Forwarded Headers"]},"1718":{"title":"Related","titles":["Forwarded Headers"]},"1719":{"title":"Next Steps","titles":["Forwarded Headers"]},"1720":{"title":"HTTP Client Options","titles":[]},"1721":{"title":"Overview","titles":["HTTP Client Options"]},"1722":{"title":"Settings Reference","titles":["HTTP Client Options"]},"1723":{"title":"How HTTP Types Work","titles":["HTTP Client Options"]},"1724":{"title":"Creating an HTTP Type","titles":["HTTP Client Options"]},"1725":{"title":"Step 1: Create a Composite Type","titles":["HTTP Client Options","Creating an HTTP Type"]},"1726":{"title":"Step 2: Add HTTP Definition Comment","titles":["HTTP Client Options","Creating an HTTP Type"]},"1727":{"title":"Step 3: Use in a Function","titles":["HTTP Client Options","Creating an HTTP Type"]},"1728":{"title":"HTTP Definition Format","titles":["HTTP Client Options"]},"1729":{"title":"Supported Methods","titles":["HTTP Client Options","HTTP Definition Format"]},"1730":{"title":"Example Definitions","titles":["HTTP Client Options","HTTP Definition Format"]},"1731":{"title":"Timeout Directives","titles":["HTTP Client Options"]},"1732":{"title":"Response Fields","titles":["HTTP Client Options"]},"1733":{"title":"Placeholder Substitution","titles":["HTTP Client Options"]},"1734":{"title":"Complete Example","titles":["HTTP Client Options"]},"1735":{"title":"Configuration","titles":["HTTP Client Options","Complete Example"]},"1736":{"title":"SQL Setup","titles":["HTTP Client Options","Complete Example"]},"1737":{"title":"Usage","titles":["HTTP Client Options","Complete Example"]},"1738":{"title":"Resolved Parameter Expressions","titles":["HTTP Client Options"]},"1739":{"title":"Retry Logic","titles":["HTTP Client Options"]},"1740":{"title":"Syntax","titles":["HTTP Client Options","Retry Logic"]},"1741":{"title":"Behavior","titles":["HTTP Client Options","Retry Logic"]},"1742":{"title":"Example","titles":["HTTP Client Options","Retry Logic"]},"1743":{"title":"Response Caching","titles":["HTTP Client Options"]},"1744":{"title":"Self-Referencing Calls (Relative Paths)","titles":["HTTP Client Options"]},"1745":{"title":"Parallel Query Composition","titles":["HTTP Client Options","Self-Referencing Calls (Relative Paths)"]},"1746":{"title":"Zero HTTP Overhead","titles":["HTTP Client Options","Self-Referencing Calls (Relative Paths)"]},"1747":{"title":"Internal-Only Endpoints","titles":["HTTP Client Options","Self-Referencing Calls (Relative Paths)"]},"1748":{"title":"Related","titles":["HTTP Client Options"]},"1749":{"title":"Next Steps","titles":["HTTP Client Options"]},"1750":{"title":"See Also","titles":["HTTP Client Options"]},"1751":{"title":"HTTP File Options","titles":[]},"1752":{"title":"Overview","titles":["HTTP File Options"]},"1753":{"title":"Settings Reference","titles":["HTTP File Options"]},"1754":{"title":"Generation Options","titles":["HTTP File Options"]},"1755":{"title":"Comment Header Styles","titles":["HTTP File Options"]},"1756":{"title":"File Mode","titles":["HTTP File Options"]},"1757":{"title":"HTTP Files","titles":["HTTP File Options"]},"1758":{"title":"Example Configuration","titles":["HTTP File Options"]},"1759":{"title":"Omitting Automatic Parameters","titles":["HTTP File Options"]},"1760":{"title":"Related","titles":["HTTP File Options"]},"1761":{"title":"Next Steps","titles":["HTTP File Options"]},"1762":{"title":"Health Checks","titles":[]},"1763":{"title":"Overview","titles":["Health Checks"]},"1764":{"title":"Settings Reference","titles":["Health Checks"]},"1765":{"title":"Health Check Types","titles":["Health Checks"]},"1766":{"title":"Main Health (/health)","titles":["Health Checks","Health Check Types"]},"1767":{"title":"Readiness Probe (/health/ready)","titles":["Health Checks","Health Check Types"]},"1768":{"title":"Liveness Probe (/health/live)","titles":["Health Checks","Health Check Types"]},"1769":{"title":"Cache Duration","titles":["Health Checks"]},"1770":{"title":"Database Health Check","titles":["Health Checks"]},"1771":{"title":"Using a Different Connection","titles":["Health Checks","Database Health Check"]},"1772":{"title":"Kubernetes Integration","titles":["Health Checks"]},"1773":{"title":"Deployment Configuration","titles":["Health Checks","Kubernetes Integration"]},"1774":{"title":"Probe Behavior","titles":["Health Checks","Kubernetes Integration"]},"1775":{"title":"Docker Compose Health Check","titles":["Health Checks"]},"1776":{"title":"Custom Paths","titles":["Health Checks"]},"1777":{"title":"Example Configurations","titles":["Health Checks"]},"1778":{"title":"Basic Configuration","titles":["Health Checks","Example Configurations"]},"1779":{"title":"Production with Caching","titles":["Health Checks","Example Configurations"]},"1780":{"title":"API Gateway Integration","titles":["Health Checks","Example Configurations"]},"1781":{"title":"Without Database Check","titles":["Health Checks","Example Configurations"]},"1782":{"title":"Response Format","titles":["Health Checks"]},"1783":{"title":"Related","titles":["Health Checks"]},"1784":{"title":"Next Steps","titles":["Health Checks"]},"1785":{"title":"Configuration Reference","titles":[]},"1786":{"title":"Reference Sections","titles":["Configuration Reference"]},"1787":{"title":"Core Settings","titles":["Configuration Reference","Reference Sections"]},"1788":{"title":"Security","titles":["Configuration Reference","Reference Sections"]},"1789":{"title":"Features","titles":["Configuration Reference","Reference Sections"]},"1790":{"title":"Performance","titles":["Configuration Reference","Reference Sections"]},"1791":{"title":"Infrastructure","titles":["Configuration Reference","Reference Sections"]},"1792":{"title":"Latest Default Configuration Reference","titles":[]},"1793":{"title":"Related","titles":["Latest Default Configuration Reference"]},"1794":{"title":"Core Settings","titles":["Latest Default Configuration Reference","Related"]},"1795":{"title":"Security","titles":["Latest Default Configuration Reference","Related"]},"1796":{"title":"Features","titles":["Latest Default Configuration Reference","Related"]},"1797":{"title":"Performance","titles":["Latest Default Configuration Reference","Related"]},"1798":{"title":"Infrastructure","titles":["Latest Default Configuration Reference","Related"]},"1799":{"title":"Logging","titles":[]},"1800":{"title":"Overview","titles":["Logging"]},"1801":{"title":"Log Levels","titles":["Logging"]},"1802":{"title":"Minimal Levels","titles":["Logging"]},"1803":{"title":"Console Output","titles":["Logging"]},"1804":{"title":"File Output","titles":["Logging"]},"1805":{"title":"PostgreSQL Output","titles":["Logging"]},"1806":{"title":"PostgreSQL Command Parameters","titles":["Logging","PostgreSQL Output"]},"1807":{"title":"OpenTelemetry Output","titles":["Logging"]},"1808":{"title":"Resource Attributes","titles":["Logging","OpenTelemetry Output"]},"1809":{"title":"Output Template","titles":["Logging"]},"1810":{"title":"Complete Example","titles":["Logging"]},"1811":{"title":"Related","titles":["Logging"]},"1812":{"title":"Next Steps","titles":["Logging"]},"1813":{"title":"MCP Options","titles":[]},"1814":{"title":"Overview","titles":["MCP Options"]},"1815":{"title":"Options","titles":["MCP Options"]},"1816":{"title":"Enabled","titles":["MCP Options","Options"]},"1817":{"title":"UrlPath","titles":["MCP Options","Options"]},"1818":{"title":"ServerName","titles":["MCP Options","Options"]},"1819":{"title":"ServerVersion","titles":["MCP Options","Options"]},"1820":{"title":"Instructions","titles":["MCP Options","Options"]},"1821":{"title":"ToolDescriptionSuffix","titles":["MCP Options","Options"]},"1822":{"title":"RateLimiterPolicy","titles":["MCP Options","Options"]},"1823":{"title":"AllowedOrigins","titles":["MCP Options","Options"]},"1824":{"title":"How it works","titles":["MCP Options"]},"1825":{"title":"Authentication — OAuth 2.1 Resource Server","titles":["MCP Options"]},"1826":{"title":"Authorization options","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server"]},"1827":{"title":"RequireAuthorization","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1828":{"title":"AuthorizationServers","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1829":{"title":"ScopesSupported","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1830":{"title":"Audience","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1831":{"title":"ProtectedResourceMetadataPath","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1832":{"title":"FilterToolsByRole","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server","Authorization options"]},"1833":{"title":"Protected Resource Metadata (RFC 9728)","titles":["MCP Options","Authentication — OAuth 2.1 Resource Server"]},"1834":{"title":"Related","titles":["MCP Options"]},"1835":{"title":"NpgsqlRest Options","titles":[]},"1836":{"title":"Overview","titles":["NpgsqlRest Options"]},"1837":{"title":"Connection Settings","titles":["NpgsqlRest Options"]},"1838":{"title":"Schema and Name Filtering","titles":["NpgsqlRest Options"]},"1839":{"title":"Filtering Examples","titles":["NpgsqlRest Options","Schema and Name Filtering"]},"1840":{"title":"Comments Mode","titles":["NpgsqlRest Options"]},"1841":{"title":"URL and Naming","titles":["NpgsqlRest Options"]},"1842":{"title":"URL Examples","titles":["NpgsqlRest Options","URL and Naming"]},"1843":{"title":"Authorization","titles":["NpgsqlRest Options"]},"1844":{"title":"Logging","titles":["NpgsqlRest Options"]},"1845":{"title":"Notice Event Modes","titles":["NpgsqlRest Options","Logging"]},"1846":{"title":"HTTP Method and Parameters","titles":["NpgsqlRest Options"]},"1847":{"title":"Default Behavior","titles":["NpgsqlRest Options","HTTP Method and Parameters"]},"1848":{"title":"Request Headers","titles":["NpgsqlRest Options"]},"1849":{"title":"Request Headers Modes","titles":["NpgsqlRest Options","Request Headers"]},"1850":{"title":"Connection Pooler Compatibility","titles":["NpgsqlRest Options"]},"1851":{"title":"WrapInTransaction","titles":["NpgsqlRest Options","Connection Pooler Compatibility"]},"1852":{"title":"BeforeRoutineCommands","titles":["NpgsqlRest Options","Connection Pooler Compatibility"]},"1853":{"title":"NULL Handling","titles":["NpgsqlRest Options"]},"1854":{"title":"QueryStringNullHandling Values","titles":["NpgsqlRest Options","NULL Handling"]},"1855":{"title":"TextResponseNullHandling Values","titles":["NpgsqlRest Options","NULL Handling"]},"1856":{"title":"JSON Timestamp Handling","titles":["NpgsqlRest Options"]},"1857":{"title":"Server-Sent Events","titles":["NpgsqlRest Options"]},"1858":{"title":"Notice Level Behavior","titles":["NpgsqlRest Options","Server-Sent Events"]},"1859":{"title":"Example Configuration","titles":["NpgsqlRest Options","Server-Sent Events"]},"1860":{"title":"Related","titles":["NpgsqlRest Options","Server-Sent Events"]},"1861":{"title":"Unbound RAISE warning","titles":["NpgsqlRest Options","Server-Sent Events"]},"1862":{"title":"Environment Variables in Annotation Values","titles":["NpgsqlRest Options"]},"1863":{"title":"Complete Example","titles":["NpgsqlRest Options"]},"1864":{"title":"Related","titles":["NpgsqlRest Options"]},"1865":{"title":"Next Steps","titles":["NpgsqlRest Options"]},"1866":{"title":"Passkey Authentication","titles":[]},"1867":{"title":"Overview","titles":["Passkey Authentication"]},"1868":{"title":"How It Works","titles":["Passkey Authentication"]},"1869":{"title":"Three Authentication Flows","titles":["Passkey Authentication"]},"1870":{"title":"1. Registration (New User with Passkey)","titles":["Passkey Authentication","Three Authentication Flows"]},"1871":{"title":"2. Add Passkey (Existing User)","titles":["Passkey Authentication","Three Authentication Flows"]},"1872":{"title":"3. Login","titles":["Passkey Authentication","Three Authentication Flows"]},"1873":{"title":"Settings Reference","titles":["Passkey Authentication"]},"1874":{"title":"General Settings","titles":["Passkey Authentication","Settings Reference"]},"1875":{"title":"Relying Party Settings","titles":["Passkey Authentication","Settings Reference"]},"1876":{"title":"Endpoint Paths","titles":["Passkey Authentication","Settings Reference"]},"1877":{"title":"WebAuthn Settings","titles":["Passkey Authentication","Settings Reference"]},"1878":{"title":"UserVerificationRequirement","titles":["Passkey Authentication","Settings Reference","WebAuthn Settings"]},"1879":{"title":"ResidentKeyRequirement","titles":["Passkey Authentication","Settings Reference","WebAuthn Settings"]},"1880":{"title":"AttestationConveyance","titles":["Passkey Authentication","Settings Reference","WebAuthn Settings"]},"1881":{"title":"SQL Commands Reference","titles":["Passkey Authentication"]},"1882":{"title":"ChallengeAddExistingUserCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1883":{"title":"ChallengeRegistrationCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1884":{"title":"ChallengeAuthenticationCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1885":{"title":"VerifyChallengeCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1886":{"title":"AuthenticateDataCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1887":{"title":"CompleteAddExistingUserCommand / CompleteRegistrationCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1888":{"title":"CompleteAuthenticateCommand","titles":["Passkey Authentication","SQL Commands Reference"]},"1889":{"title":"Column Name Configuration","titles":["Passkey Authentication"]},"1890":{"title":"Analytics Data","titles":["Passkey Authentication"]},"1891":{"title":"Complete Example","titles":["Passkey Authentication"]},"1892":{"title":"Minimal Configuration","titles":["Passkey Authentication","Complete Example"]},"1893":{"title":"Full Configuration","titles":["Passkey Authentication","Complete Example"]},"1894":{"title":"Related","titles":["Passkey Authentication"]},"1895":{"title":"Next Steps","titles":["Passkey Authentication"]},"1896":{"title":"OpenAPI Options","titles":[]},"1897":{"title":"Overview","titles":["OpenAPI Options"]},"1898":{"title":"Settings Reference","titles":["OpenAPI Options"]},"1899":{"title":"Document Info","titles":["OpenAPI Options"]},"1900":{"title":"Servers","titles":["OpenAPI Options"]},"1901":{"title":"Security Schemes","titles":["OpenAPI Options"]},"1902":{"title":"Bearer Token Authentication","titles":["OpenAPI Options","Security Schemes"]},"1903":{"title":"Basic Authentication","titles":["OpenAPI Options","Security Schemes"]},"1904":{"title":"Cookie Authentication","titles":["OpenAPI Options","Security Schemes"]},"1905":{"title":"API Key in Header","titles":["OpenAPI Options","Security Schemes"]},"1906":{"title":"Security Scheme Settings","titles":["OpenAPI Options","Security Schemes"]},"1907":{"title":"Complete Example","titles":["OpenAPI Options"]},"1908":{"title":"Filters (New in 3.15.0)","titles":["OpenAPI Options"]},"1909":{"title":"Schema and name filters","titles":["OpenAPI Options","Filters (New in 3.15.0)"]},"1910":{"title":"Filter order","titles":["OpenAPI Options","Filters (New in 3.15.0)"]},"1911":{"title":"Partner-facing document example","titles":["OpenAPI Options","Filters (New in 3.15.0)"]},"1912":{"title":"Omitting Automatic Parameters","titles":["OpenAPI Options"]},"1913":{"title":"Related","titles":["OpenAPI Options"]},"1914":{"title":"Next Steps","titles":["OpenAPI Options"]},"1915":{"title":"Proxy Options","titles":[]},"1916":{"title":"Overview","titles":["Proxy Options"]},"1917":{"title":"Settings Reference","titles":["Proxy Options"]},"1918":{"title":"Response Parameter Names","titles":["Proxy Options"]},"1919":{"title":"Proxy Modes","titles":["Proxy Options"]},"1920":{"title":"Passthrough Mode","titles":["Proxy Options","Proxy Modes"]},"1921":{"title":"Transform Mode","titles":["Proxy Options","Proxy Modes"]},"1922":{"title":"Response Parameters","titles":["Proxy Options"]},"1923":{"title":"Automatic Parameter Forwarding","titles":["Proxy Options"]},"1924":{"title":"Placement follows the endpoint shape, not the HTTP verb","titles":["Proxy Options","Automatic Parameter Forwarding"]},"1925":{"title":"Query-string length guard","titles":["Proxy Options","Automatic Parameter Forwarding"]},"1926":{"title":"HTTP Headers (user_context)","titles":["Proxy Options","Automatic Parameter Forwarding"]},"1927":{"title":"Upload Forwarding","titles":["Proxy Options"]},"1928":{"title":"Key Features","titles":["Proxy Options"]},"1929":{"title":"Self-Referencing Calls (Relative Paths)","titles":["Proxy Options"]},"1930":{"title":"Internal-Only Endpoints","titles":["Proxy Options","Self-Referencing Calls (Relative Paths)"]},"1931":{"title":"Complete Example","titles":["Proxy Options"]},"1932":{"title":"Related","titles":["Proxy Options"]},"1933":{"title":"Next Steps","titles":["Proxy Options"]},"1934":{"title":"See Also","titles":["Proxy Options"]},"1935":{"title":"Response Compression","titles":[]},"1936":{"title":"Overview","titles":["Response Compression"]},"1937":{"title":"Settings Reference","titles":["Response Compression"]},"1938":{"title":"Compression Levels","titles":["Response Compression"]},"1939":{"title":"Compression Algorithms","titles":["Response Compression"]},"1940":{"title":"Brotli","titles":["Response Compression","Compression Algorithms"]},"1941":{"title":"Gzip Fallback","titles":["Response Compression","Compression Algorithms"]},"1942":{"title":"HTTPS Compression","titles":["Response Compression"]},"1943":{"title":"Default MIME Types","titles":["Response Compression"]},"1944":{"title":"Example Configuration","titles":["Response Compression"]},"1945":{"title":"Related","titles":["Response Compression"]},"1946":{"title":"Next Steps","titles":["Response Compression"]},"1947":{"title":"Rate Limiter","titles":[]},"1948":{"title":"Overview","titles":["Rate Limiter"]},"1949":{"title":"Settings Reference","titles":["Rate Limiter"]},"1950":{"title":"Policy Types","titles":["Rate Limiter"]},"1951":{"title":"Fixed Window Policy","titles":["Rate Limiter"]},"1952":{"title":"Sliding Window Policy","titles":["Rate Limiter"]},"1953":{"title":"Token Bucket Policy","titles":["Rate Limiter"]},"1954":{"title":"Concurrency Policy","titles":["Rate Limiter"]},"1955":{"title":"Per-User Rate Limiting (Partition)","titles":["Rate Limiter"]},"1956":{"title":"Partition Fields","titles":["Rate Limiter","Per-User Rate Limiting (Partition)"]},"1957":{"title":"Source Types","titles":["Rate Limiter","Per-User Rate Limiting (Partition)"]},"1958":{"title":"Per-Policy Status Code and Message","titles":["Rate Limiter"]},"1959":{"title":"Ready-to-use login_throttle policy","titles":["Rate Limiter","Per-Policy Status Code and Message"]},"1960":{"title":"Complete Example","titles":["Rate Limiter"]},"1961":{"title":"Rate Limiting Scope","titles":["Rate Limiter"]},"1962":{"title":"Related","titles":["Rate Limiter"]},"1963":{"title":"Next Steps","titles":["Rate Limiter"]},"1964":{"title":"See Also","titles":["Rate Limiter"]},"1965":{"title":"Routine Options","titles":[]},"1966":{"title":"Overview","titles":["Routine Options"]},"1967":{"title":"Settings","titles":["Routine Options"]},"1968":{"title":"Custom Type Parameter Separator","titles":["Routine Options"]},"1969":{"title":"Language Filtering","titles":["Routine Options"]},"1970":{"title":"Include Specific Languages","titles":["Routine Options","Language Filtering"]},"1971":{"title":"Exclude Additional Languages","titles":["Routine Options","Language Filtering"]},"1972":{"title":"Common PostgreSQL Languages","titles":["Routine Options","Language Filtering"]},"1973":{"title":"Nested JSON for Composite Types","titles":["Routine Options"]},"1974":{"title":"Resolve Nested Composite Types","titles":["Routine Options"]},"1975":{"title":"Complete Example","titles":["Routine Options"]},"1976":{"title":"Related","titles":["Routine Options"]},"1977":{"title":"Next Steps","titles":["Routine Options"]},"1978":{"title":"Server & SSL Settings","titles":[]},"1979":{"title":"SSL Configuration","titles":["Server & SSL Settings"]},"1980":{"title":"Settings Reference","titles":["Server & SSL Settings","SSL Configuration"]},"1981":{"title":"Enabling HTTPS","titles":["Server & SSL Settings","SSL Configuration"]},"1982":{"title":"HTTPS Redirection","titles":["Server & SSL Settings","SSL Configuration"]},"1983":{"title":"HTTP Strict Transport Security (HSTS)","titles":["Server & SSL Settings","SSL Configuration"]},"1984":{"title":"Kestrel Configuration","titles":["Server & SSL Settings"]},"1985":{"title":"Certificate Configuration","titles":["Server & SSL Settings","Kestrel Configuration"]},"1986":{"title":"PFX File","titles":["Server & SSL Settings","Kestrel Configuration","Certificate Configuration"]},"1987":{"title":"PEM/CRT with Key File","titles":["Server & SSL Settings","Kestrel Configuration","Certificate Configuration"]},"1988":{"title":"Certificate Store (Windows)","titles":["Server & SSL Settings","Kestrel Configuration","Certificate Configuration"]},"1989":{"title":"Default Certificate","titles":["Server & SSL Settings","Kestrel Configuration","Certificate Configuration"]},"1990":{"title":"Connection Limits","titles":["Server & SSL Settings","Kestrel Configuration"]},"1991":{"title":"Limits Reference","titles":["Server & SSL Settings","Kestrel Configuration","Connection Limits"]},"1992":{"title":"HTTP/2 Settings","titles":["Server & SSL Settings","Kestrel Configuration"]},"1993":{"title":"HTTP/3 Settings","titles":["Server & SSL Settings","Kestrel Configuration"]},"1994":{"title":"Additional Kestrel Options","titles":["Server & SSL Settings","Kestrel Configuration"]},"1995":{"title":"Complete Example","titles":["Server & SSL Settings"]},"1996":{"title":"Related","titles":["Server & SSL Settings"]},"1997":{"title":"Next Steps","titles":["Server & SSL Settings"]},"1998":{"title":"SQL File Source","titles":[]},"1999":{"title":"Overview","titles":["SQL File Source"]},"2000":{"title":"Settings","titles":["SQL File Source"]},"2001":{"title":"Enabled","titles":["SQL File Source"]},"2002":{"title":"FilePattern","titles":["SQL File Source"]},"2003":{"title":"Examples","titles":["SQL File Source","FilePattern"]},"2004":{"title":"CommentsMode","titles":["SQL File Source"]},"2005":{"title":"CommentScope","titles":["SQL File Source"]},"2006":{"title":"Example","titles":["SQL File Source","CommentScope"]},"2007":{"title":"ErrorMode","titles":["SQL File Source"]},"2008":{"title":"ResultPrefix","titles":["SQL File Source"]},"2009":{"title":"UnnamedSingleColumnSet","titles":["SQL File Source"]},"2010":{"title":"NestedJsonForCompositeTypes","titles":["SQL File Source"]},"2011":{"title":"LogCommandText","titles":["SQL File Source"]},"2012":{"title":"Quick Start Example","titles":["SQL File Source"]},"2013":{"title":"Related","titles":["SQL File Source"]},"2014":{"title":"Security Headers","titles":[]},"2015":{"title":"Overview","titles":["Security Headers"]},"2016":{"title":"Settings Reference","titles":["Security Headers"]},"2017":{"title":"X-Content-Type-Options","titles":["Security Headers"]},"2018":{"title":"X-Frame-Options","titles":["Security Headers"]},"2019":{"title":"Referrer-Policy","titles":["Security Headers"]},"2020":{"title":"Content-Security-Policy","titles":["Security Headers"]},"2021":{"title":"Permissions-Policy","titles":["Security Headers"]},"2022":{"title":"Cross-Origin Policies","titles":["Security Headers"]},"2023":{"title":"Cross-Origin-Opener-Policy","titles":["Security Headers","Cross-Origin Policies"]},"2024":{"title":"Cross-Origin-Embedder-Policy","titles":["Security Headers","Cross-Origin Policies"]},"2025":{"title":"Cross-Origin-Resource-Policy","titles":["Security Headers","Cross-Origin Policies"]},"2026":{"title":"Example Configurations","titles":["Security Headers"]},"2027":{"title":"Basic Security (Recommended Starting Point)","titles":["Security Headers","Example Configurations"]},"2028":{"title":"API-Only Application","titles":["Security Headers","Example Configurations"]},"2029":{"title":"Full Protection with CSP","titles":["Security Headers","Example Configurations"]},"2030":{"title":"Related","titles":["Security Headers"]},"2031":{"title":"Next Steps","titles":["Security Headers"]},"2032":{"title":"Static Files","titles":[]},"2033":{"title":"Overview","titles":["Static Files"]},"2034":{"title":"Settings Reference","titles":["Static Files"]},"2035":{"title":"Authorization","titles":["Static Files"]},"2036":{"title":"Path Patterns","titles":["Static Files","Authorization"]},"2037":{"title":"Content Parsing","titles":["Static Files"]},"2038":{"title":"Parse Content Settings Reference","titles":["Static Files","Content Parsing"]},"2039":{"title":"Tag Replacement","titles":["Static Files","Content Parsing"]},"2040":{"title":"Environment Variable Injection","titles":["Static Files","Content Parsing"]},"2041":{"title":"Default Headers","titles":["Static Files","Content Parsing"]},"2042":{"title":"Example Configuration","titles":["Static Files"]},"2043":{"title":"Related","titles":["Static Files"]},"2044":{"title":"Next Steps","titles":["Static Files"]},"2045":{"title":"PostgreSQL Stats","titles":[]},"2046":{"title":"Overview","titles":["PostgreSQL Stats"]},"2047":{"title":"Settings Reference","titles":["PostgreSQL Stats"]},"2048":{"title":"Available Endpoints","titles":["PostgreSQL Stats"]},"2049":{"title":"Routines Stats (/stats/routines)","titles":["PostgreSQL Stats","Available Endpoints"]},"2050":{"title":"Tables Stats (/stats/tables)","titles":["PostgreSQL Stats","Available Endpoints"]},"2051":{"title":"Indexes Stats (/stats/indexes)","titles":["PostgreSQL Stats","Available Endpoints"]},"2052":{"title":"Activity (/stats/activity)","titles":["PostgreSQL Stats","Available Endpoints"]},"2053":{"title":"Output Formats","titles":["PostgreSQL Stats"]},"2054":{"title":"HTML Format (Default)","titles":["PostgreSQL Stats","Output Formats"]},"2055":{"title":"JSON Format","titles":["PostgreSQL Stats","Output Formats"]},"2056":{"title":"Per-Request Format Override","titles":["PostgreSQL Stats","Output Formats"]},"2057":{"title":"Security","titles":["PostgreSQL Stats"]},"2058":{"title":"Require Authentication","titles":["PostgreSQL Stats","Security"]},"2059":{"title":"Role-Based Access","titles":["PostgreSQL Stats","Security"]},"2060":{"title":"Caching","titles":["PostgreSQL Stats"]},"2061":{"title":"Rate Limiting","titles":["PostgreSQL Stats"]},"2062":{"title":"Schema Filtering","titles":["PostgreSQL Stats"]},"2063":{"title":"Using a Different Connection","titles":["PostgreSQL Stats"]},"2064":{"title":"Custom Paths","titles":["PostgreSQL Stats"]},"2065":{"title":"Example Configurations","titles":["PostgreSQL Stats"]},"2066":{"title":"Development (Open Access)","titles":["PostgreSQL Stats","Example Configurations"]},"2067":{"title":"Production (Secured)","titles":["PostgreSQL Stats","Example Configurations"]},"2068":{"title":"Monitoring Integration","titles":["PostgreSQL Stats","Example Configurations"]},"2069":{"title":"Limited Schema Access","titles":["PostgreSQL Stats","Example Configurations"]},"2070":{"title":"Related","titles":["PostgreSQL Stats"]},"2071":{"title":"Next Steps","titles":["PostgreSQL Stats"]},"2072":{"title":"Table Format Options","titles":[]},"2073":{"title":"Overview","titles":["Table Format Options"]},"2074":{"title":"General Settings","titles":["Table Format Options"]},"2075":{"title":"HTML Table Handler","titles":["Table Format Options"]},"2076":{"title":"Example","titles":["Table Format Options","HTML Table Handler"]},"2077":{"title":"Excel Table Handler","titles":["Table Format Options"]},"2078":{"title":"Example","titles":["Table Format Options","Excel Table Handler"]},"2079":{"title":"Per-Endpoint Overrides","titles":["Table Format Options","Excel Table Handler"]},"2080":{"title":"Complete Example","titles":["Table Format Options"]},"2081":{"title":"Related","titles":["Table Format Options"]},"2082":{"title":"Next Steps","titles":["Table Format Options"]},"2083":{"title":"See Also","titles":["Table Format Options"]},"2084":{"title":"Thread Pool","titles":[]},"2085":{"title":"Overview","titles":["Thread Pool"]},"2086":{"title":"Settings Reference","titles":["Thread Pool"]},"2087":{"title":"Worker Threads vs Completion Port Threads","titles":["Thread Pool"]},"2088":{"title":"When to Configure","titles":["Thread Pool"]},"2089":{"title":"Example Configuration","titles":["Thread Pool"]},"2090":{"title":"Related","titles":["Thread Pool"]},"2091":{"title":"Next Steps","titles":["Thread Pool"]},"2092":{"title":"Test Runner","titles":[]},"2093":{"title":"Overview","titles":["Test Runner"]},"2094":{"title":"Settings","titles":["Test Runner"]},"2095":{"title":"FilePattern","titles":["Test Runner"]},"2096":{"title":"Filter","titles":["Test Runner"]},"2097":{"title":"Tag and ExcludeTag","titles":["Test Runner"]},"2098":{"title":"ConnectionName","titles":["Test Runner"]},"2099":{"title":"MaxParallelism","titles":["Test Runner"]},"2100":{"title":"FailFast","titles":["Test Runner"]},"2101":{"title":"PerTestTimeout","titles":["Test Runner"]},"2102":{"title":"JUnitOutput","titles":["Test Runner"]},"2103":{"title":"Keep","titles":["Test Runner"]},"2104":{"title":"DetailedReport","titles":["Test Runner"]},"2105":{"title":"AllowEmpty","titles":["Test Runner"]},"2106":{"title":"Watch mode","titles":["Test Runner"]},"2107":{"title":"Coverage and CoverageThreshold","titles":["Test Runner"]},"2108":{"title":"LoggerName","titles":["Test Runner"]},"2109":{"title":"ResponseTempTable","titles":["Test Runner"]},"2110":{"title":"DebugTable — inspect responses after the run","titles":["Test Runner","ResponseTempTable"]},"2111":{"title":"Steps","titles":["Test Runner"]},"2112":{"title":"Setup and Teardown","titles":["Test Runner"]},"2113":{"title":"Exit codes","titles":["Test Runner"]},"2114":{"title":"Related","titles":["Test Runner"]},"2115":{"title":"Top-Level Settings","titles":[]},"2116":{"title":"Application Settings","titles":["Top-Level Settings"]},"2117":{"title":"Settings Reference","titles":["Top-Level Settings","Application Settings"]},"2118":{"title":"Urls Configuration","titles":["Top-Level Settings","Application Settings"]},"2119":{"title":"Startup Message Placeholders","titles":["Top-Level Settings","Application Settings"]},"2120":{"title":"Related","titles":["Top-Level Settings"]},"2121":{"title":"Next Steps","titles":["Top-Level Settings"]},"2122":{"title":"Upload Options","titles":[]},"2123":{"title":"Overview","titles":["Upload Options"]},"2124":{"title":"General Settings","titles":["Upload Options"]},"2125":{"title":"Upload Handlers Common Settings","titles":["Upload Options"]},"2126":{"title":"Large Object Handler","titles":["Upload Options"]},"2127":{"title":"File System Handler","titles":["Upload Options"]},"2128":{"title":"CSV Upload Handler","titles":["Upload Options"]},"2129":{"title":"CSV Row Command Parameters","titles":["Upload Options","CSV Upload Handler"]},"2130":{"title":"Excel Upload Handler","titles":["Upload Options"]},"2131":{"title":"Excel Row Command Parameters","titles":["Upload Options","Excel Upload Handler"]},"2132":{"title":"Complete Example","titles":["Upload Options"]},"2133":{"title":"Related","titles":["Upload Options"]},"2134":{"title":"Blog Posts","titles":["Upload Options"]},"2135":{"title":"Next Steps","titles":["Upload Options"]},"2136":{"title":"See Also","titles":["Upload Options"]},"2137":{"title":"Validation Options","titles":[]},"2138":{"title":"Overview","titles":["Validation Options"]},"2139":{"title":"Settings Reference","titles":["Validation Options"]},"2140":{"title":"Validation Types","titles":["Validation Options"]},"2141":{"title":"Rule Properties","titles":["Validation Options"]},"2142":{"title":"Default Rules","titles":["Validation Options"]},"2143":{"title":"Adding Custom Rules","titles":["Validation Options"]},"2144":{"title":"Regex Pattern Rule","titles":["Validation Options","Adding Custom Rules"]},"2145":{"title":"Length Validation Rules","titles":["Validation Options","Adding Custom Rules"]},"2146":{"title":"Complete Example","titles":["Validation Options"]},"2147":{"title":"Usage with Annotations","titles":["Validation Options"]},"2148":{"title":"Programmatic Configuration","titles":["Validation Options"]},"2149":{"title":"Behavior","titles":["Validation Options"]},"2150":{"title":"Related","titles":["Validation Options"]},"2151":{"title":"Next Steps","titles":["Validation Options"]},"2152":{"title":"See Also","titles":["Validation Options"]},"2153":{"title":"Watch Mode","titles":[]},"2154":{"title":"Overview","titles":["Watch Mode"]},"2155":{"title":"Enabled","titles":["Watch Mode"]},"2156":{"title":"DatabasePollingInterval","titles":["Watch Mode"]},"2157":{"title":"Server watch behavior","titles":["Watch Mode"]},"2158":{"title":"Test watch behavior","titles":["Watch Mode"]},"2159":{"title":"Related","titles":["Watch Mode"]},"2160":{"title":"Examples","titles":[]},"2161":{"title":"Prerequisites","titles":["Examples"]},"2162":{"title":"Getting Started","titles":["Examples"]},"2163":{"title":"Available Examples","titles":["Examples"]},"2164":{"title":"Function-Based Examples (RoutineSource)","titles":["Examples","Available Examples"]},"2165":{"title":"SQL File Examples (SqlFileSource)","titles":["Examples","Available Examples"]},"2166":{"title":"MCP Server (SqlFileSource)","titles":["Examples","Available Examples"]},"2167":{"title":"SQL Test Runner","titles":["Examples","Available Examples"]},"2168":{"title":"Available Commands","titles":["Examples"]},"2169":{"title":"Next Steps","titles":["Examples"]},"2170":{"title":"Authentication","titles":[]},"2171":{"title":"The big picture","titles":["Authentication"]},"2172":{"title":"Step 1: Configure an authentication scheme","titles":["Authentication"]},"2173":{"title":"Cookie (the simplest)","titles":["Authentication","Step 1: Configure an authentication scheme"]},"2174":{"title":"Bearer token","titles":["Authentication","Step 1: Configure an authentication scheme"]},"2175":{"title":"JWT","titles":["Authentication","Step 1: Configure an authentication scheme"]},"2176":{"title":"Step 2: Write a login endpoint","titles":["Authentication"]},"2177":{"title":"Verifying the password","titles":["Authentication","Step 2: Write a login endpoint"]},"2178":{"title":"Choosing a scheme","titles":["Authentication","Step 2: Write a login endpoint"]},"2179":{"title":"How claims work","titles":["Authentication"]},"2180":{"title":"Claims are just the login columns","titles":["Authentication","How claims work"]},"2181":{"title":"Identity claims","titles":["Authentication","How claims work"]},"2182":{"title":"Accessing claims in your endpoints","titles":["Authentication"]},"2183":{"title":"As function parameters","titles":["Authentication","Accessing claims in your endpoints"]},"2184":{"title":"As PostgreSQL context variables","titles":["Authentication","Accessing claims in your endpoints"]},"2185":{"title":"As template placeholders","titles":["Authentication","Accessing claims in your endpoints"]},"2186":{"title":"Logging out","titles":["Authentication"]},"2187":{"title":"A complete worked example","titles":["Authentication"]},"2188":{"title":"See it in the examples","titles":["Authentication"]},"2189":{"title":"Related","titles":["Authentication"]},"2190":{"title":"Comment Annotations Guide","titles":[]},"2191":{"title":"How Annotations Work","titles":["Comment Annotations Guide"]},"2192":{"title":"Basic Rules","titles":["Comment Annotations Guide","How Annotations Work"]},"2193":{"title":"Optional @ Prefix","titles":["Comment Annotations Guide","How Annotations Work"]},"2194":{"title":"Simple Example","titles":["Comment Annotations Guide","How Annotations Work"]},"2195":{"title":"The HTTP Annotation","titles":["Comment Annotations Guide"]},"2196":{"title":"Syntax Variations","titles":["Comment Annotations Guide","The HTTP Annotation"]},"2197":{"title":"Default Behavior","titles":["Comment Annotations Guide","The HTTP Annotation"]},"2198":{"title":"Authorization Annotations","titles":["Comment Annotations Guide"]},"2199":{"title":"Require Authentication","titles":["Comment Annotations Guide","Authorization Annotations"]},"2200":{"title":"Role List Syntax","titles":["Comment Annotations Guide","Authorization Annotations"]},"2201":{"title":"Allow Anonymous Access","titles":["Comment Annotations Guide","Authorization Annotations"]},"2202":{"title":"Response Headers","titles":["Comment Annotations Guide"]},"2203":{"title":"Request Parameter Configuration","titles":["Comment Annotations Guide"]},"2204":{"title":"Query String vs Body","titles":["Comment Annotations Guide","Request Parameter Configuration"]},"2205":{"title":"Caching","titles":["Comment Annotations Guide"]},"2206":{"title":"Raw Output Mode","titles":["Comment Annotations Guide"]},"2207":{"title":"Combining Annotations","titles":["Comment Annotations Guide"]},"2208":{"title":"Debugging Annotations","titles":["Comment Annotations Guide"]},"2209":{"title":"Comments Mode","titles":["Comment Annotations Guide"]},"2210":{"title":"Time/Duration Formats","titles":["Comment Annotations Guide"]},"2211":{"title":"Quick Reference","titles":["Comment Annotations Guide","Time/Duration Formats"]},"2212":{"title":"Examples","titles":["Comment Annotations Guide","Time/Duration Formats"]},"2213":{"title":"Common Patterns","titles":["Comment Annotations Guide"]},"2214":{"title":"Public Read, Protected Write","titles":["Comment Annotations Guide","Common Patterns"]},"2215":{"title":"API Versioning with Custom Paths","titles":["Comment Annotations Guide","Common Patterns"]},"2216":{"title":"Secure Sensitive Operations","titles":["Comment Annotations Guide","Common Patterns"]},"2217":{"title":"Nested JSON for Composite Types","titles":["Comment Annotations Guide","Common Patterns"]},"2218":{"title":"Rate Limiting","titles":["Comment Annotations Guide","Common Patterns"]},"2219":{"title":"Next Steps","titles":["Comment Annotations Guide"]},"2220":{"title":"Changelog","titles":[]},"2221":{"title":"Version 3.19 (Latest)","titles":["Changelog"]},"2222":{"title":"Version 3.18","titles":["Changelog"]},"2223":{"title":"Version 3.17","titles":["Changelog"]},"2224":{"title":"Version 3.16","titles":["Changelog"]},"2225":{"title":"Version 3.15","titles":["Changelog"]},"2226":{"title":"Version 3.14","titles":["Changelog"]},"2227":{"title":"Version 3.13","titles":["Changelog"]},"2228":{"title":"Version 3.12","titles":["Changelog"]},"2229":{"title":"Version 3.11","titles":["Changelog"]},"2230":{"title":"Version 3.10","titles":["Changelog"]},"2231":{"title":"Version 3.9","titles":["Changelog"]},"2232":{"title":"Version 3.8","titles":["Changelog"]},"2233":{"title":"Version 3.7","titles":["Changelog"]},"2234":{"title":"Version 3.6","titles":["Changelog"]},"2235":{"title":"Version 3.5","titles":["Changelog"]},"2236":{"title":"Version 3.4","titles":["Changelog"]},"2237":{"title":"Version 3.3","titles":["Changelog"]},"2238":{"title":"Version 3.2","titles":["Changelog"]},"2239":{"title":"Version 3.1","titles":["Changelog"]},"2240":{"title":"Version 3.0","titles":["Changelog"]},"2241":{"title":"Changelog v3.0.1 (2025-11-28)","titles":[]},"2242":{"title":"Version","titles":["Changelog v3.0.1 (2025-11-28)"]},"2243":{"title":"Changelog v3.0.0 (2025-11-27)","titles":[]},"2244":{"title":"Version","titles":["Changelog v3.0.0 (2025-11-27)"]},"2245":{"title":"Docker JIT Version","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2246":{"title":".NET 10 Target Framework","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2247":{"title":"TsClient (Code Generation) Improvements","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2248":{"title":"Info Events Streaming Changes (Server-Sent Events)","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2249":{"title":"Name refactor: changed all "Info Events" related names to "SSE" to better reflect their purpose.","titles":["Changelog v3.0.0 (2025-11-27)","Version","Info Events Streaming Changes (Server-Sent Events)"]},"2250":{"title":"Removed Self scope level","titles":["Changelog v3.0.0 (2025-11-27)","Version","Info Events Streaming Changes (Server-Sent Events)"]},"2251":{"title":"New Feature: Support for custom notice level","titles":["Changelog v3.0.0 (2025-11-27)","Version","Info Events Streaming Changes (Server-Sent Events)"]},"2252":{"title":"Other Comment Annotations Changes","titles":["Changelog v3.0.0 (2025-11-27)","Version","Info Events Streaming Changes (Server-Sent Events)"]},"2253":{"title":"Timeout Handling","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2254":{"title":"OpenAPI 3.0 Support","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2255":{"title":"Error Handling Improvements","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2256":{"title":"Metadata Query Improvements","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2257":{"title":"Rate Limiter","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2258":{"title":"Other Changes and Fixes","titles":["Changelog v3.0.0 (2025-11-27)","Version"]},"2259":{"title":"Login Endpoint Changes","titles":["Changelog v3.0.0 (2025-11-27)","Version","Other Changes and Fixes"]},"2260":{"title":"Changelog v3.1.1 (2025-12-15)","titles":[]},"2261":{"title":"Version","titles":["Changelog v3.1.1 (2025-12-15)"]},"2262":{"title":"Changelog v3.1.0 (2025-12-13)","titles":[]},"2263":{"title":"Version","titles":["Changelog v3.1.0 (2025-12-13)"]},"2264":{"title":"Http Types","titles":["Changelog v3.1.0 (2025-12-13)","Version"]},"2265":{"title":"Routine Caching Improvements","titles":["Changelog v3.1.0 (2025-12-13)","Version"]},"2266":{"title":"Multi-Host Connection Support","titles":["Changelog v3.1.0 (2025-12-13)","Version"]},"2267":{"title":"Other Changes and Fixes","titles":["Changelog v3.1.0 (2025-12-13)","Version"]},"2268":{"title":"Changelog v3.1.2 (2025-12-20)","titles":[]},"2269":{"title":"Version","titles":["Changelog v3.1.2 (2025-12-20)"]},"2270":{"title":"Performance: SIMD-Accelerated String Processing","titles":["Changelog v3.1.2 (2025-12-20)","Version"]},"2271":{"title":"Consistent JSON Error Responses","titles":["Changelog v3.1.2 (2025-12-20)","Version"]},"2272":{"title":"EnvFile Configuration Option","titles":["Changelog v3.1.2 (2025-12-20)","Version"]},"2273":{"title":"TsClient: Configurable Error Expression and Type","titles":["Changelog v3.1.2 (2025-12-20)","Version"]},"2274":{"title":"HybridCache Support","titles":["Changelog v3.1.2 (2025-12-20)","Version"]},"2275":{"title":"Changelog v3.1.3 (2025-12-21)","titles":[]},"2276":{"title":"Version","titles":["Changelog v3.1.3 (2025-12-21)"]},"2277":{"title":"Path Parameters Support","titles":["Changelog v3.1.3 (2025-12-21)","Version"]},"2278":{"title":"TsClient Improvements","titles":["Changelog v3.1.3 (2025-12-21)","Version"]},"2279":{"title":"HybridCache Configuration Keys Renamed","titles":["Changelog v3.1.3 (2025-12-21)","Version"]},"2280":{"title":"Changelog v3.10.0 (2026-02-25)","titles":[]},"2281":{"title":"Version","titles":["Changelog v3.10.0 (2026-02-25)"]},"2282":{"title":"New Feature: Resolved Parameter Expressions","titles":["Changelog v3.10.0 (2026-02-25)","Version"]},"2283":{"title":"How It Works","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Resolved Parameter Expressions"]},"2284":{"title":"Behavior","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Resolved Parameter Expressions"]},"2285":{"title":"Multiple Resolved Parameters","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Resolved Parameter Expressions"]},"2286":{"title":"Resolved Parameters in URL, Headers, and Body","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Resolved Parameter Expressions"]},"2287":{"title":"New Feature: HTTP Client Type Retry Logic","titles":["Changelog v3.10.0 (2026-02-25)","Version"]},"2288":{"title":"Syntax","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: HTTP Client Type Retry Logic"]},"2289":{"title":"Behavior","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: HTTP Client Type Retry Logic"]},"2290":{"title":"Example","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: HTTP Client Type Retry Logic"]},"2291":{"title":"New Feature: Data Protection Encrypt/Decrypt Annotations","titles":["Changelog v3.10.0 (2026-02-25)","Version"]},"2292":{"title":"Encrypt Parameters","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2293":{"title":"Decrypt Result Columns","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2294":{"title":"Full Roundtrip Example","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2295":{"title":"Annotation Aliases","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2296":{"title":"Behavior Notes","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2297":{"title":"Key Management","titles":["Changelog v3.10.0 (2026-02-25)","Version","New Feature: Data Protection Encrypt/Decrypt Annotations"]},"2298":{"title":"Changelog v3.11.0 (2026-03-10)","titles":[]},"2299":{"title":"Version","titles":["Changelog v3.11.0 (2026-03-10)"]},"2300":{"title":"New Feature: proxy_out Annotation (Post-Execution Proxy)","titles":["Changelog v3.11.0 (2026-03-10)","Version"]},"2301":{"title":"Syntax","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2302":{"title":"How It Works","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2303":{"title":"Basic Usage","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2304":{"title":"Query String Forwarding","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2305":{"title":"HTTP Method Override","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2306":{"title":"Custom Host","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2307":{"title":"Error Handling","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2308":{"title":"Configuration","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2309":{"title":"Performance","titles":["Changelog v3.11.0 (2026-03-10)","Version","New Feature: proxy_out Annotation (Post-Execution Proxy)"]},"2310":{"title":"TsClient: proxy_out Endpoint Support","titles":["Changelog v3.11.0 (2026-03-10)","Version"]},"2311":{"title":"Changelog v3.11.1 (2026-03-13)","titles":[]},"2312":{"title":"Version","titles":["Changelog v3.11.1 (2026-03-13)"]},"2313":{"title":"TsClient: proxy Passthrough Endpoint Support","titles":["Changelog v3.11.1 (2026-03-13)","Version"]},"2314":{"title":"authorize Annotation Now Matches User ID and User Name Claims","titles":["Changelog v3.11.1 (2026-03-13)","Version"]},"2315":{"title":"Changelog v3.12.0 (2026-03-23)","titles":[]},"2316":{"title":"Version","titles":["Changelog v3.12.0 (2026-03-23)"]},"2317":{"title":"New Endpoint Source Plugin: NpgsqlRest.SqlFileSource","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2318":{"title":"How It Works","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2319":{"title":"Single-Command Files","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2320":{"title":"Multi-Command Files","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2321":{"title":"Parameters","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2322":{"title":"Virtual Parameters","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2323":{"title":"Comments and Annotations","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2324":{"title":"Wire Protocol Introspection","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2325":{"title":"Custom / Composite Type Support","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2326":{"title":"Unnamed and Duplicate Columns","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2327":{"title":"URL Path Derivation","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2328":{"title":"Error Handling","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2329":{"title":"Feature Parity","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2330":{"title":"Configuration Reference","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Endpoint Source Plugin: NpgsqlRest.SqlFileSource"]},"2331":{"title":"New Annotations","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2332":{"title":"New Core Annotation: @param / @parameter — Rename and Retype Parameters","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2333":{"title":"@param Default Values for SQL File Parameters","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2334":{"title":"@param Rename Validation","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2335":{"title":"@param Default Value: = Alias for default","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2336":{"title":"@param Type Hints for SQL File Describe","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2337":{"title":"New Positional Annotation: @returns — Skip Describe and Declare Return Type","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2338":{"title":"New Annotation: @void — Force Void Response","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2339":{"title":"New Comment Annotation: @single","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2340":{"title":"Positional @result Annotation for Multi-Command Files","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2341":{"title":"SkipNonQueryCommands Setting and @skip Annotation","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2342":{"title":"SkipNonQueryCommands (default: true)","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations","SkipNonQueryCommands Setting and @skip Annotation"]},"2343":{"title":"@skip Annotation (aliases: @skip_result, @no_result)","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations","SkipNonQueryCommands Setting and @skip Annotation"]},"2344":{"title":"New Core Annotation: @internal / @internal_only","titles":["Changelog v3.12.0 (2026-03-23)","Version","New Annotations"]},"2345":{"title":"HTTP Custom Types & Self-Referencing Calls","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2346":{"title":"Self-Referencing Calls: Relative Path Support for Proxy and HTTP Client Types","titles":["Changelog v3.12.0 (2026-03-23)","Version","HTTP Custom Types & Self-Referencing Calls"]},"2347":{"title":"Internal Self-Call Optimization: Zero HTTP Overhead","titles":["Changelog v3.12.0 (2026-03-23)","Version","HTTP Custom Types & Self-Referencing Calls"]},"2348":{"title":"Composite Type Parameters in SQL Files — No SQL Rewriting","titles":["Changelog v3.12.0 (2026-03-23)","Version","HTTP Custom Types & Self-Referencing Calls"]},"2349":{"title":"Configuration Changes","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2350":{"title":"RoutineSource: Enabled Configuration Option","titles":["Changelog v3.12.0 (2026-03-23)","Version","Configuration Changes"]},"2351":{"title":"CrudSource Disabled by Default","titles":["Changelog v3.12.0 (2026-03-23)","Version","Configuration Changes"]},"2352":{"title":"CrudSource No Longer Blocks SqlFileSource","titles":["Changelog v3.12.0 (2026-03-23)","Version","Configuration Changes"]},"2353":{"title":"DataProtection Disabled by Default","titles":["Changelog v3.12.0 (2026-03-23)","Version","Configuration Changes"]},"2354":{"title":"SqlFileSource:LogCommandText Setting","titles":["Changelog v3.12.0 (2026-03-23)","Version","Configuration Changes"]},"2355":{"title":"TsClient Improvements","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2356":{"title":"TsClient: Composite Type Support for SQL Files","titles":["Changelog v3.12.0 (2026-03-23)","Version","TsClient Improvements"]},"2357":{"title":"TsClient: Multi-Command SQL File Support","titles":["Changelog v3.12.0 (2026-03-23)","Version","TsClient Improvements"]},"2358":{"title":"TsClient: SQL File Comment Headers","titles":["Changelog v3.12.0 (2026-03-23)","Version","TsClient Improvements"]},"2359":{"title":"TsClient: Type Alias Extraction for Error and Result Types","titles":["Changelog v3.12.0 (2026-03-23)","Version","TsClient Improvements"]},"2360":{"title":"TsClient: Fix SkipTypes Generating Invalid JavaScript","titles":["Changelog v3.12.0 (2026-03-23)","Version","TsClient Improvements"]},"2361":{"title":"Bug Fixes & Log Improvements","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2362":{"title":"Graceful Shutdown with Active SSE Connections","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2363":{"title":"Downgrade Basic Auth Missing Header Log to Debug","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2364":{"title":"Improved Log Level Classification","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2365":{"title":"Fix @separator and @new_line Annotations Not Working with @ Prefix","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2366":{"title":"Aggregated Comment Annotation Logging","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2367":{"title":"Fix: OnlyWithHttpTag Mode Skips Files Before Describe","titles":["Changelog v3.12.0 (2026-03-23)","Version","Bug Fixes & Log Improvements"]},"2368":{"title":"Internal & Breaking Changes","titles":["Changelog v3.12.0 (2026-03-23)","Version"]},"2369":{"title":"Interface Refactoring: IEndpointSource / IRoutineSource","titles":["Changelog v3.12.0 (2026-03-23)","Version","Internal & Breaking Changes"]},"2370":{"title":"Composite Type Cache: Public API","titles":["Changelog v3.12.0 (2026-03-23)","Version","Internal & Breaking Changes"]},"2371":{"title":"Glob Pattern Enhancement: ** Recursive Matching","titles":["Changelog v3.12.0 (2026-03-23)","Version","Internal & Breaking Changes"]},"2372":{"title":"Internal Changes","titles":["Changelog v3.12.0 (2026-03-23)","Version","Internal & Breaking Changes"]},"2373":{"title":"Changelog v3.13.0 (2026-04-24)","titles":[]},"2374":{"title":"Version","titles":["Changelog v3.13.0 (2026-04-24)"]},"2375":{"title":"New: Auth Schemes (Named Additional Authentication Schemes)","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2376":{"title":"Breaking: legacy auth time-integer fields removed","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2377":{"title":"New: interval notation for auth time fields","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2378":{"title":"Breaking: RateLimiterOptions:Policies is now a dict, not an array","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2379":{"title":"New: Per-User Rate Limiting (Partition on a policy)","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2380":{"title":"New: Caching Profiles (CacheOptions.Profiles + @cache_profile annotation)","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2381":{"title":"Never-expiring (infinite) cache entries","titles":["Changelog v3.13.0 (2026-04-24)","Version","New: Caching Profiles (CacheOptions.Profiles + @cache_profile annotation)"]},"2382":{"title":"New: WrapInTransaction Option (Connection Pooler Compatibility)","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2383":{"title":"New: BeforeRoutineCommands Option","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2384":{"title":"Fix: 400 Bad Request responses are no longer silent in logs","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2385":{"title":"Docker Images: Ubuntu 26.04 LTS Base","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2386":{"title":"NuGet Package Upgrades","titles":["Changelog v3.13.0 (2026-04-24)","Version"]},"2387":{"title":"Changelog v3.14.0 (2026-05-09)","titles":[]},"2388":{"title":"Version","titles":["Changelog v3.14.0 (2026-05-09)"]},"2389":{"title":"Removed: auto-CRUD endpoint generation from the standalone client","titles":["Changelog v3.14.0 (2026-05-09)"]},"2390":{"title":"What\'s new","titles":["Changelog v3.14.0 (2026-05-09)"]},"2391":{"title":"Two new SSE annotations: @sse_publish and @sse_subscribe","titles":["Changelog v3.14.0 (2026-05-09)","What\'s new"]},"2392":{"title":"Warning when a RAISE looks like a missed @sse_publish","titles":["Changelog v3.14.0 (2026-05-09)","What\'s new"]},"2393":{"title":"Reliable SSE connection handshake","titles":["Changelog v3.14.0 (2026-05-09)","What\'s new"]},"2394":{"title":"Startup error when claim-mapped parameters use a non-text type","titles":["Changelog v3.14.0 (2026-05-09)","What\'s new"]},"2395":{"title":"Warning when a request value is overridden by claim auto-bind","titles":["Changelog v3.14.0 (2026-05-09)","What\'s new"]},"2396":{"title":"Performance","titles":["Changelog v3.14.0 (2026-05-09)"]},"2397":{"title":"Lower-allocation JSON conversion for arrays and composites","titles":["Changelog v3.14.0 (2026-05-09)","Performance"]},"2398":{"title":"Estimated impact on the","titles":["Changelog v3.14.0 (2026-05-09)","Performance","Lower-allocation JSON conversion for arrays and composites"]},"2399":{"title":"UTF-8 literals for JSON markup constants","titles":["Changelog v3.14.0 (2026-05-09)","Performance"]},"2400":{"title":"Tighter PipeWriter writes","titles":["Changelog v3.14.0 (2026-05-09)","Performance"]},"2401":{"title":"Hardening (silent-failure fixes)","titles":["Changelog v3.14.0 (2026-05-09)"]},"2402":{"title":"ArrayPool rent now in try/finally","titles":["Changelog v3.14.0 (2026-05-09)","Hardening (silent-failure fixes)"]},"2403":{"title":"Multi-command StringBuilder rentals always released","titles":["Changelog v3.14.0 (2026-05-09)","Hardening (silent-failure fixes)"]},"2404":{"title":"proxy_out buffer released on exception path","titles":["Changelog v3.14.0 (2026-05-09)","Hardening (silent-failure fixes)"]},"2405":{"title":"Column-decryption failures now logged at Trace","titles":["Changelog v3.14.0 (2026-05-09)","Hardening (silent-failure fixes)"]},"2406":{"title":"Configuration","titles":["Changelog v3.14.0 (2026-05-09)"]},"2407":{"title":"Test suite","titles":["Changelog v3.14.0 (2026-05-09)"]},"2408":{"title":"Changelog v3.15.1 (2026-05-11)","titles":[]},"2409":{"title":"Version","titles":["Changelog v3.15.1 (2026-05-11)"]},"2410":{"title":"Fix: named auth schemes are validated by Type, not by name","titles":["Changelog v3.15.1 (2026-05-11)"]},"2411":{"title":"Root cause","titles":["Changelog v3.15.1 (2026-05-11)","Fix: named auth schemes are validated by Type, not by name"]},"2412":{"title":"What changed","titles":["Changelog v3.15.1 (2026-05-11)","Fix: named auth schemes are validated by Type, not by name"]},"2413":{"title":"Behavior after the fix","titles":["Changelog v3.15.1 (2026-05-11)","Fix: named auth schemes are validated by Type, not by name"]},"2414":{"title":"Fix: --config and --validate CLI commands now honor ValidateConfigKeys mode","titles":["Changelog v3.15.1 (2026-05-11)"]},"2415":{"title":"What changed","titles":["Changelog v3.15.1 (2026-05-11)","Fix: --config and --validate CLI commands now honor ValidateConfigKeys mode"]},"2416":{"title":"Behavior after the fix","titles":["Changelog v3.15.1 (2026-05-11)","Fix: --config and --validate CLI commands now honor ValidateConfigKeys mode"]},"2417":{"title":"Tests","titles":["Changelog v3.15.1 (2026-05-11)"]},"2418":{"title":"Changelog v3.15.0","titles":[]},"2419":{"title":"Version","titles":["Changelog v3.15.0"]},"2420":{"title":"Fix: named cookie schemes now actually authenticate requests","titles":["Changelog v3.15.0"]},"2421":{"title":"Root cause","titles":["Changelog v3.15.0","Fix: named cookie schemes now actually authenticate requests"]},"2422":{"title":"What changed","titles":["Changelog v3.15.0","Fix: named cookie schemes now actually authenticate requests"]},"2423":{"title":"Behavior after the fix","titles":["Changelog v3.15.0","Fix: named cookie schemes now actually authenticate requests"]},"2424":{"title":"Cookie-precedence order when both are present","titles":["Changelog v3.15.0","Fix: named cookie schemes now actually authenticate requests"]},"2425":{"title":"Feature: CookieSameSite and CookieSecure config","titles":["Changelog v3.15.0"]},"2426":{"title":"Root-level (main cookie scheme)","titles":["Changelog v3.15.0","Feature: CookieSameSite and CookieSecure config"]},"2427":{"title":"Per-scheme override under Auth:Schemes","titles":["Changelog v3.15.0","Feature: CookieSameSite and CookieSecure config"]},"2428":{"title":"Validation and warnings","titles":["Changelog v3.15.0","Feature: CookieSameSite and CookieSecure config"]},"2429":{"title":"Cross-origin checklist for an external Web API setup","titles":["Changelog v3.15.0","Feature: CookieSameSite and CookieSecure config"]},"2430":{"title":"Feature: OpenAPI filtering for partner-facing documents","titles":["Changelog v3.15.0"]},"2431":{"title":"Config-level filters","titles":["Changelog v3.15.0","Feature: OpenAPI filtering for partner-facing documents"]},"2432":{"title":"Per-routine @openapi comment annotation","titles":["Changelog v3.15.0","Feature: OpenAPI filtering for partner-facing documents"]},"2433":{"title":"Filter order and composition","titles":["Changelog v3.15.0","Feature: OpenAPI filtering for partner-facing documents"]},"2434":{"title":"Partner-facing config example","titles":["Changelog v3.15.0","Feature: OpenAPI filtering for partner-facing documents"]},"2435":{"title":"Tests","titles":["Changelog v3.15.0"]},"2436":{"title":"Configuration summary","titles":["Changelog v3.15.0"]},"2437":{"title":"Out of scope","titles":["Changelog v3.15.0"]},"2438":{"title":"Partner-system integration readiness — what\'s still missing","titles":["Changelog v3.15.0"]},"2439":{"title":"Changelog v3.15.2 (2026-05-11)","titles":[]},"2440":{"title":"Version","titles":["Changelog v3.15.2 (2026-05-11)"]},"2441":{"title":"Fix: RateLimiterOptions:Policies validates by Type, not by name","titles":["Changelog v3.15.2 (2026-05-11)"]},"2442":{"title":"Root cause","titles":["Changelog v3.15.2 (2026-05-11)","Fix: RateLimiterOptions:Policies validates by Type, not by name"]},"2443":{"title":"What changed","titles":["Changelog v3.15.2 (2026-05-11)","Fix: RateLimiterOptions:Policies validates by Type, not by name"]},"2444":{"title":"Behavior after the fix","titles":["Changelog v3.15.2 (2026-05-11)","Fix: RateLimiterOptions:Policies validates by Type, not by name"]},"2445":{"title":"Fix: CacheOptions:Profiles validates by shape","titles":["Changelog v3.15.2 (2026-05-11)"]},"2446":{"title":"Improvement: ValidationOptions:Rules now validates rule bodies","titles":["Changelog v3.15.2 (2026-05-11)"]},"2447":{"title":"Tests","titles":["Changelog v3.15.2 (2026-05-11)"]},"2448":{"title":"Files touched","titles":["Changelog v3.15.2 (2026-05-11)"]},"2449":{"title":"Changelog v3.16.0 (2026-05-20)","titles":[]},"2450":{"title":"Version","titles":["Changelog v3.16.0 (2026-05-20)"]},"2451":{"title":"Fix: datetime parsers are now host-TZ-independent","titles":["Changelog v3.16.0 (2026-05-20)"]},"2452":{"title":"Why this was hidden so long","titles":["Changelog v3.16.0 (2026-05-20)"]},"2453":{"title":"TryParseDate left alone","titles":["Changelog v3.16.0 (2026-05-20)"]},"2454":{"title":"Breaking change","titles":["Changelog v3.16.0 (2026-05-20)"]},"2455":{"title":"Opt-out: NpgsqlRestOptions.JsonTimestampsAreUtc","titles":["Changelog v3.16.0 (2026-05-20)"]},"2456":{"title":"Tests","titles":["Changelog v3.16.0 (2026-05-20)"]},"2457":{"title":"Files touched","titles":["Changelog v3.16.0 (2026-05-20)"]},"2458":{"title":"Changelog v3.16.1 (2026-06-01)","titles":[]},"2459":{"title":"Version","titles":["Changelog v3.16.1 (2026-06-01)"]},"2460":{"title":"What changed","titles":["Changelog v3.16.1 (2026-06-01)"]},"2461":{"title":"IRoutineCache gains GetOrCreateAsync (additive)","titles":["Changelog v3.16.1 (2026-06-01)","What changed"]},"2462":{"title":"Stampede protection per backend","titles":["Changelog v3.16.1 (2026-06-01)","What changed"]},"2463":{"title":"Middleware paths","titles":["Changelog v3.16.1 (2026-06-01)","What changed"]},"2464":{"title":"Effect","titles":["Changelog v3.16.1 (2026-06-01)"]},"2465":{"title":"Test coverage (read this honestly)","titles":["Changelog v3.16.1 (2026-06-01)"]},"2466":{"title":"Known limitations","titles":["Changelog v3.16.1 (2026-06-01)"]},"2467":{"title":"Changelog v3.16.2 (2026-06-02)","titles":[]},"2468":{"title":"Version","titles":["Changelog v3.16.2 (2026-06-02)"]},"2469":{"title":"What changed","titles":["Changelog v3.16.2 (2026-06-02)"]},"2470":{"title":"Per-policy StatusCode / StatusMessage overrides","titles":["Changelog v3.16.2 (2026-06-02)","What changed"]},"2471":{"title":"New ready-to-use login_throttle default policy","titles":["Changelog v3.16.2 (2026-06-02)","What changed"]},"2472":{"title":"Test coverage","titles":["Changelog v3.16.2 (2026-06-02)"]},"2473":{"title":"Changelog v3.16.3 (2026-06-03)","titles":[]},"2474":{"title":"Version","titles":["Changelog v3.16.3 (2026-06-03)"]},"2475":{"title":"What changed","titles":["Changelog v3.16.3 (2026-06-03)"]},"2476":{"title":"AvailableEnvVars under StaticFiles:ParseContentOptions","titles":["Changelog v3.16.3 (2026-06-03)","What changed"]},"2477":{"title":"Security note","titles":["Changelog v3.16.3 (2026-06-03)","What changed"]},"2478":{"title":"Changelog v3.17.0","titles":[]},"2479":{"title":"Version","titles":["Changelog v3.17.0"]},"2480":{"title":"New Features","titles":["Changelog v3.17.0"]},"2481":{"title":"MCP (Model Context Protocol) server — new NpgsqlRest.Mcp plugin","titles":["Changelog v3.17.0","New Features"]},"2482":{"title":"Plugin extension points on RoutineEndpoint","titles":["Changelog v3.17.0","New Features"]},"2483":{"title":"{name} annotation substitution can resolve allowlisted environment variables","titles":["Changelog v3.17.0","New Features"]},"2484":{"title":"TsClient: ExportTypes — emit request/response interfaces with the export keyword","titles":["Changelog v3.17.0","New Features"]},"2485":{"title":"Breaking Changes","titles":["Changelog v3.17.0"]},"2486":{"title":"⚠️ Safer configuration defaults: CORS credentials, passkey requirements, connection testing","titles":["Changelog v3.17.0","Breaking Changes"]},"2487":{"title":"⚠️ OpenAPI annotation handling moved out of core (C# API only)","titles":["Changelog v3.17.0","Breaking Changes"]},"2488":{"title":"Fixes","titles":["Changelog v3.17.0"]},"2489":{"title":"Internal-only endpoints are excluded from generated client artifacts and API docs","titles":["Changelog v3.17.0","Fixes"]},"2490":{"title":"🔴 Security: SSE scope hints were not enforced — hint-scoped events were delivered to every subscriber","titles":["Changelog v3.17.0","Fixes"]},"2491":{"title":"Malformed JSON request body now returns 400 Bad Request (was 404 Not Found)","titles":["Changelog v3.17.0","Fixes"]},"2492":{"title":"Passkey/WebAuthn diagnostics: CBOR decode failures are no longer silent","titles":["Changelog v3.17.0","Fixes"]},"2493":{"title":"{name} parameter-value placeholders: case-insensitive matching + typo warning","titles":["Changelog v3.17.0","Fixes"]},"2494":{"title":"Bare @cached (no parameter list) used only the routine name as the cache key","titles":["Changelog v3.17.0","Fixes"]},"2495":{"title":"HybridCache Cache key contains invalid content on nullable cached params","titles":["Changelog v3.17.0","Fixes"]},"2496":{"title":"JSON command parameters accept json, jsonb, or text","titles":["Changelog v3.17.0","Fixes"]},"2497":{"title":"Optional {NAME} and required {!NAME} environment-variable placeholders","titles":["Changelog v3.17.0","Fixes"]},"2498":{"title":"Tests","titles":["Changelog v3.17.0"]},"2499":{"title":"Changelog v3.18.0","titles":[]},"2500":{"title":"Version","titles":["Changelog v3.18.0"]},"2501":{"title":"New Features","titles":["Changelog v3.18.0"]},"2502":{"title":"HTTP Custom Type response caching — @cache directive","titles":["Changelog v3.18.0","New Features"]},"2503":{"title":"Fixes","titles":["Changelog v3.18.0"]},"2504":{"title":"HTTP Custom Type request fired once per composite field on database-function endpoints","titles":["Changelog v3.18.0","Fixes"]},"2505":{"title":"HTTP type directives after the headers were silently ignored","titles":["Changelog v3.18.0","Fixes"]},"2506":{"title":"Tests","titles":["Changelog v3.18.0"]},"2507":{"title":"Changelog v3.18.1","titles":[]},"2508":{"title":"Version","titles":["Changelog v3.18.1"]},"2509":{"title":"What changed","titles":["Changelog v3.18.1"]},"2510":{"title":"Why","titles":["Changelog v3.18.1","What changed"]},"2511":{"title":"Behavior change to note","titles":["Changelog v3.18.1","What changed"]},"2512":{"title":"Notes","titles":["Changelog v3.18.1","What changed"]},"2513":{"title":"Tests","titles":["Changelog v3.18.1"]},"2514":{"title":"Changelog v3.18.2","titles":[]},"2515":{"title":"Version","titles":["Changelog v3.18.2"]},"2516":{"title":"What changed","titles":["Changelog v3.18.2"]},"2517":{"title":"1. Large auto-filled values no longer break the proxy query string","titles":["Changelog v3.18.2","What changed"]},"2518":{"title":"2. @body_parameter_name reliably matches HTTP Custom Type fields","titles":["Changelog v3.18.2","What changed"]},"2519":{"title":"3. TypeScript client generation for @body_parameter_name","titles":["Changelog v3.18.2","What changed"]},"2520":{"title":"4. Opt-in: omit automatic (server-filled) parameters from generated request shapes","titles":["Changelog v3.18.2","What changed"]},"2521":{"title":"Why these go together","titles":["Changelog v3.18.2","What changed"]},"2522":{"title":"Notes","titles":["Changelog v3.18.2"]},"2523":{"title":"Tests","titles":["Changelog v3.18.2"]},"2524":{"title":"Changelog v3.19.0","titles":[]},"2525":{"title":"Version","titles":["Changelog v3.19.0"]},"2526":{"title":"1. SQL test runner (--test)","titles":["Changelog v3.19.0"]},"2527":{"title":"How it works","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2528":{"title":"Test file anatomy","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2529":{"title":"HTTP blocks — invoking endpoints","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2530":{"title":"The response temp table","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2531":{"title":"Reusing scripts: \\\\i and \\\\ir includes","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2532":{"title":"Setup and Teardown, and named steps","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2533":{"title":"Per-file setup, teardown, and connection (header annotations)","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2534":{"title":"A dedicated test database","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2535":{"title":"Reporting","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2536":{"title":"Logging","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2537":{"title":"Configuration reference (TestRunner section)","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2538":{"title":"Project layout","titles":["Changelog v3.19.0","1. SQL test runner (--test)"]},"2539":{"title":"2. SqlFileSource.SkipPattern — exclude files from endpoint discovery","titles":["Changelog v3.19.0"]},"2540":{"title":"3. Named parameters in SQL files: :name","titles":["Changelog v3.19.0"]},"2541":{"title":"4. Watch mode: --watch","titles":["Changelog v3.19.0"]},"2542":{"title":"Watching the routine source — database polling","titles":["Changelog v3.19.0","4. Watch mode: --watch"]},"2543":{"title":"Server watch","titles":["Changelog v3.19.0","4. Watch mode: --watch"]},"2544":{"title":"5. Mute an individual logger with "Off" in Log:MinimalLevels","titles":["Changelog v3.19.0"]},"2545":{"title":"Notes","titles":["Changelog v3.19.0"]},"2546":{"title":"Tests","titles":["Changelog v3.19.0"]},"2547":{"title":"Changelog v3.2.0 (2025-12-22)","titles":[]},"2548":{"title":"Version","titles":["Changelog v3.2.0 (2025-12-22)"]},"2549":{"title":"Reverse Proxy Feature","titles":["Changelog v3.2.0 (2025-12-22)","Version"]},"2550":{"title":"Docker Image with Bun Runtime","titles":["Changelog v3.2.0 (2025-12-22)","Version"]},"2551":{"title":"Configuration Default Fixes","titles":["Changelog v3.2.0 (2025-12-22)","Version"]},"2552":{"title":"Changelog v3.2.1 (2025-12-23)","titles":[]},"2553":{"title":"Version","titles":["Changelog v3.2.1 (2025-12-23)"]},"2554":{"title":"JWT (JSON Web Token) Authentication Support","titles":["Changelog v3.2.1 (2025-12-23)","Version"]},"2555":{"title":"Path Parameters Support for HttpFiles and OpenApi Plugins","titles":["Changelog v3.2.1 (2025-12-23)","Version"]},"2556":{"title":"Changelog v3.2.2 (2025-12-24)","titles":[]},"2557":{"title":"Version","titles":["Changelog v3.2.2 (2025-12-24)"]},"2558":{"title":"Bug Fixes","titles":["Changelog v3.2.2 (2025-12-24)","Version"]},"2559":{"title":"Performance Improvements","titles":["Changelog v3.2.2 (2025-12-24)","Version"]},"2560":{"title":"Changelog v3.2.3 (2025-12-30)","titles":[]},"2561":{"title":"Version","titles":["Changelog v3.2.3 (2025-12-30)"]},"2562":{"title":"TsClient Plugin","titles":["Changelog v3.2.3 (2025-12-30)","Version"]},"2563":{"title":"Changelog v3.2.4 (2025-01-03)","titles":[]},"2564":{"title":"Version","titles":["Changelog v3.2.4 (2025-01-03)"]},"2565":{"title":"DataProtection Key Encryption Options","titles":["Changelog v3.2.4 (2025-01-03)","Version"]},"2566":{"title":"TsClient Plugin","titles":["Changelog v3.2.4 (2025-01-03)","Version"]},"2567":{"title":"NpgsqlRestClient","titles":["Changelog v3.2.4 (2025-01-03)","Version"]},"2568":{"title":"Changelog v3.2.6 (2025-01-04)","titles":[]},"2569":{"title":"Version","titles":["Changelog v3.2.6 (2025-01-04)"]},"2570":{"title":"Changelog v3.2.7 (2025-01-05)","titles":[]},"2571":{"title":"Version","titles":["Changelog v3.2.7 (2025-01-05)"]},"2572":{"title":"Upload Handlers: User Context and Claims Support","titles":["Changelog v3.2.7 (2025-01-05)","Version"]},"2573":{"title":"Changelog v3.3.0 (2025-01-08)","titles":[]},"2574":{"title":"Version","titles":["Changelog v3.3.0 (2025-01-08)"]},"2575":{"title":"Parameter Validation","titles":["Changelog v3.3.0 (2025-01-08)","Version"]},"2576":{"title":"Linux ARM64 Build and Docker Image","titles":["Changelog v3.3.0 (2025-01-08)","Version"]},"2577":{"title":"Config Command Shows Default Values","titles":["Changelog v3.3.0 (2025-01-08)","Version"]},"2578":{"title":"Changelog v3.3.1 (2025-01-14)","titles":[]},"2579":{"title":"Version","titles":["Changelog v3.3.1 (2025-01-14)"]},"2580":{"title":"Proxy Response Caching","titles":["Changelog v3.3.1 (2025-01-14)","Version"]},"2581":{"title":"Optional @ Prefix for Comment Annotations","titles":["Changelog v3.3.1 (2025-01-14)","Version"]},"2582":{"title":"Added a logo on client app commands","titles":["Changelog v3.3.1 (2025-01-14)","Version"]},"2583":{"title":"Changelog v3.4.0 (2025-01-16)","titles":[]},"2584":{"title":"Version","titles":["Changelog v3.4.0 (2025-01-16)"]},"2585":{"title":"Composite Type Support","titles":["Changelog v3.4.0 (2025-01-16)","Version"]},"2586":{"title":"1. Arrays of Composite Types","titles":["Changelog v3.4.0 (2025-01-16)","Version","Composite Type Support"]},"2587":{"title":"2. Nested JSON for Composite Type Columns (Opt-in)","titles":["Changelog v3.4.0 (2025-01-16)","Version","Composite Type Support"]},"2588":{"title":"Multidimensional Array Support","titles":["Changelog v3.4.0 (2025-01-16)","Version"]},"2589":{"title":"JSON Escaping Fix for Arrays and Tuple Strings","titles":["Changelog v3.4.0 (2025-01-16)","Version"]},"2590":{"title":"TsClient Plugin: Composite Type Interface Generation","titles":["Changelog v3.4.0 (2025-01-16)","Version"]},"2591":{"title":"Optional @ Prefix Extended to Annotation Parameters","titles":["Changelog v3.4.0 (2025-01-16)","Version"]},"2592":{"title":"Changelog v3.4.1 (2025-01-15)","titles":[]},"2593":{"title":"Version","titles":["Changelog v3.4.1 (2025-01-15)"]},"2594":{"title":"Configuration Options for Null Handling","titles":["Changelog v3.4.1 (2025-01-15)","Version"]},"2595":{"title":"QueryStringNullHandling","titles":["Changelog v3.4.1 (2025-01-15)","Version","Configuration Options for Null Handling"]},"2596":{"title":"TextResponseNullHandling","titles":["Changelog v3.4.1 (2025-01-15)","Version","Configuration Options for Null Handling"]},"2597":{"title":"Bug Fixes","titles":["Changelog v3.4.1 (2025-01-15)","Version"]},"2598":{"title":"Changelog v3.4.2 (2025-01-15)","titles":[]},"2599":{"title":"Version","titles":["Changelog v3.4.2 (2025-01-15)"]},"2600":{"title":"Bug Fixes","titles":["Changelog v3.4.2 (2025-01-15)","Version"]},"2601":{"title":"Changelog v3.4.3 (2025-01-16)","titles":[]},"2602":{"title":"Version","titles":["Changelog v3.4.3 (2025-01-16)"]},"2603":{"title":"Bug Fixes","titles":["Changelog v3.4.3 (2025-01-16)","Version"]},"2604":{"title":"Performance Improvements","titles":["Changelog v3.4.3 (2025-01-16)","Version"]},"2605":{"title":"Changelog v3.4.4 (2025-01-17)","titles":[]},"2606":{"title":"Version","titles":["Changelog v3.4.4 (2025-01-17)"]},"2607":{"title":"Deep Nested Composite Type Resolution (ResolveNestedCompositeTypes)","titles":["Changelog v3.4.4 (2025-01-17)","Version"]},"2608":{"title":"Bug Fixes","titles":["Changelog v3.4.4 (2025-01-17)","Version"]},"2609":{"title":"Changelog v3.4.5 (2025-01-19)","titles":[]},"2610":{"title":"Version","titles":["Changelog v3.4.5 (2025-01-19)"]},"2611":{"title":"NpgsqlRest.TsClient: Deep Nested Composite Type Support","titles":["Changelog v3.4.5 (2025-01-19)","Version"]},"2612":{"title":"Changelog v3.4.6 (2025-01-21)","titles":[]},"2613":{"title":"Version","titles":["Changelog v3.4.6 (2025-01-21)"]},"2614":{"title":"Endpoint Execution Performance Optimizations","titles":["Changelog v3.4.6 (2025-01-21)","Version"]},"2615":{"title":"Comprehensive CancellationToken Propagation","titles":["Changelog v3.4.6 (2025-01-21)","Version"]},"2616":{"title":"Changelog v3.4.8 (2025-01-26)","titles":[]},"2617":{"title":"Version","titles":["Changelog v3.4.8 (2025-01-26)"]},"2618":{"title":"Fix: Single-Field Composite Type Returns","titles":["Changelog v3.4.8 (2025-01-26)","Version"]},"2619":{"title":"Changelog v3.4.7 (2025-01-21)","titles":[]},"2620":{"title":"Version","titles":["Changelog v3.4.7 (2025-01-21)"]},"2621":{"title":"Type Category Lookup Optimization","titles":["Changelog v3.4.7 (2025-01-21)","Version"]},"2622":{"title":"Additional Allocation Optimizations","titles":["Changelog v3.4.7 (2025-01-21)","Version"]},"2623":{"title":"Changelog v3.5.0 (2025-01-28)","titles":[]},"2624":{"title":"Version","titles":["Changelog v3.5.0 (2025-01-28)"]},"2625":{"title":"New Feature: PasskeyAuth (WebAuthn/FIDO2)","titles":["Changelog v3.5.0 (2025-01-28)","Version"]},"2626":{"title":"Bugfix: Response Compression for Static Files","titles":["Changelog v3.5.0 (2025-01-28)","Version"]},"2627":{"title":"Added Client Integration Tests","titles":["Changelog v3.5.0 (2025-01-28)","Version"]},"2628":{"title":"Separate Core and Client Logging","titles":["Changelog v3.5.0 (2025-01-28)","Version"]},"2629":{"title":"Debug Log Filtering Options","titles":["Changelog v3.5.0 (2025-01-28)","Version"]},"2630":{"title":"Changelog v3.6.0 (2025-02-01)","titles":[]},"2631":{"title":"Version","titles":["Changelog v3.6.0 (2025-02-01)"]},"2632":{"title":"New Feature: Security Headers Middleware","titles":["Changelog v3.6.0 (2025-02-01)","Version"]},"2633":{"title":"New Feature: Forwarded Headers Middleware","titles":["Changelog v3.6.0 (2025-02-01)","Version"]},"2634":{"title":"New Feature: Health Check Endpoints","titles":["Changelog v3.6.0 (2025-02-01)","Version"]},"2635":{"title":"New Feature: PostgreSQL Statistics Endpoints","titles":["Changelog v3.6.0 (2025-02-01)","Version"]},"2636":{"title":"Changelog v3.6.1 (2025-02-02)","titles":[]},"2637":{"title":"Version","titles":["Changelog v3.6.1 (2025-02-02)"]},"2638":{"title":"Fixes","titles":["Changelog v3.6.1 (2025-02-02)","Version"]},"2639":{"title":"Changelog v3.6.2 (2025-02-02)","titles":[]},"2640":{"title":"Version","titles":["Changelog v3.6.2 (2025-02-02)"]},"2641":{"title":"Fixes","titles":["Changelog v3.6.2 (2025-02-02)","Version"]},"2642":{"title":"Breaking Changes","titles":["Changelog v3.6.2 (2025-02-02)","Version"]},"2643":{"title":"Changelog v3.6.3 (2025-02-03)","titles":[]},"2644":{"title":"Version","titles":["Changelog v3.6.3 (2025-02-03)"]},"2645":{"title":"Fixes","titles":["Changelog v3.6.3 (2025-02-03)","Version"]},"2646":{"title":"Changelog v3.7.0 (2025-02-07)","titles":[]},"2647":{"title":"Version","titles":["Changelog v3.7.0 (2025-02-07)"]},"2648":{"title":"Fixes","titles":["Changelog v3.7.0 (2025-02-07)","Version"]},"2649":{"title":"New Features","titles":["Changelog v3.7.0 (2025-02-07)","Version"]},"2650":{"title":"New Feature: Pluggable Table Format Renderers","titles":["Changelog v3.7.0 (2025-02-07)","Version"]},"2651":{"title":"HTML Table Format","titles":["Changelog v3.7.0 (2025-02-07)","Version","New Feature: Pluggable Table Format Renderers"]},"2652":{"title":"Excel Table Format","titles":["Changelog v3.7.0 (2025-02-07)","Version","New Feature: Pluggable Table Format Renderers"]},"2653":{"title":"Per-Endpoint Custom Parameters","titles":["Changelog v3.7.0 (2025-02-07)","Version","New Feature: Pluggable Table Format Renderers"]},"2654":{"title":"TsClient: Per-Endpoint URL Export Control","titles":["Changelog v3.7.0 (2025-02-07)","Version"]},"2655":{"title":"tsclient_export_url","titles":["Changelog v3.7.0 (2025-02-07)","Version","TsClient: Per-Endpoint URL Export Control"]},"2656":{"title":"tsclient_url_only","titles":["Changelog v3.7.0 (2025-02-07)","Version","TsClient: Per-Endpoint URL Export Control"]},"2657":{"title":"Changelog v3.8.0 (2025-02-11)","titles":[]},"2658":{"title":"Version","titles":["Changelog v3.8.0 (2025-02-11)"]},"2659":{"title":"New Feature: Configuration Key Validation","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2660":{"title":"Removed","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2661":{"title":"Kestrel Configuration Validation","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2662":{"title":"Syntax Highlighted --config Output","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2663":{"title":"Improved CLI Error Handling","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2664":{"title":"Universal fallback_handler for All Upload Handlers","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2665":{"title":"Optional Path Parameters","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2666":{"title":"Fixes","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2667":{"title":"Machine-Readable CLI Commands for Tool Integration","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2668":{"title":"--version --json","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2669":{"title":"--validate [--json]","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2670":{"title":"--config-schema","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2671":{"title":"--annotations","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2672":{"title":"--endpoints","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2673":{"title":"--config (updated)","titles":["Changelog v3.8.0 (2025-02-11)","Version","Machine-Readable CLI Commands for Tool Integration"]},"2674":{"title":"Stats Endpoints: format Query String Override","titles":["Changelog v3.8.0 (2025-02-11)","Version"]},"2675":{"title":"Changelog v3.9.0 (2026-02-23)","titles":[]},"2676":{"title":"Version","titles":["Changelog v3.9.0 (2026-02-23)"]},"2677":{"title":"Commented Configuration Output (--config)","titles":["Changelog v3.9.0 (2026-02-23)","Version"]},"2678":{"title":"Configuration Search and Filter (--config [filter])","titles":["Changelog v3.9.0 (2026-02-23)","Version"]},"2679":{"title":"CLI Improvements","titles":["Changelog v3.9.0 (2026-02-23)","Version"]},"2680":{"title":"Configuration Guide","titles":[]},"2681":{"title":"Configuration Sources","titles":["Configuration Guide"]},"2682":{"title":"Default Values","titles":["Configuration Guide"]},"2683":{"title":"Configuration Files","titles":["Configuration Guide"]},"2684":{"title":"Default Configuration Files","titles":["Configuration Guide","Configuration Files"]},"2685":{"title":"Optional Configuration Files","titles":["Configuration Guide","Configuration Files"]},"2686":{"title":"Configuration File Format","titles":["Configuration Guide","Configuration Files"]},"2687":{"title":"Environment Variables","titles":["Configuration Guide"]},"2688":{"title":"Optional and Required Placeholders","titles":["Configuration Guide","Environment Variables"]},"2689":{"title":"Enabling Environment Variable Binding","titles":["Configuration Guide","Environment Variables"]},"2690":{"title":"Environment Variable Naming Rules","titles":["Configuration Guide","Environment Variables"]},"2691":{"title":"Command-Line Arguments","titles":["Configuration Guide"]},"2692":{"title":"Command-Line Syntax Rules","titles":["Configuration Guide","Command-Line Arguments"]},"2693":{"title":"Exploring Configuration","titles":["Configuration Guide"]},"2694":{"title":"Generating a Default Configuration File","titles":["Configuration Guide","Exploring Configuration"]},"2695":{"title":"Searching for Settings","titles":["Configuration Guide","Exploring Configuration"]},"2696":{"title":"Configuration Validation","titles":["Configuration Guide","Exploring Configuration"]},"2697":{"title":"Configuration Precedence Example","titles":["Configuration Guide"]},"2698":{"title":"Quick Reference","titles":["Configuration Guide"]},"2699":{"title":"Common Command-Line Overrides","titles":["Configuration Guide","Quick Reference"]},"2700":{"title":"Exploring Configuration","titles":["Configuration Guide","Quick Reference"]},"2701":{"title":"Configuration Structure Overview","titles":["Configuration Guide"]},"2702":{"title":"Top-Level Settings","titles":["Configuration Guide"]},"2703":{"title":"Urls Configuration","titles":["Configuration Guide","Top-Level Settings"]},"2704":{"title":"Startup Message","titles":["Configuration Guide","Top-Level Settings"]},"2705":{"title":"Config Section Options","titles":["Configuration Guide"]},"2706":{"title":"Next Steps","titles":["Configuration Guide"]},"2707":{"title":"FAQ & Troubleshooting","titles":[]},"2708":{"title":"General","titles":["FAQ & Troubleshooting"]},"2709":{"title":"What is NpgsqlRest?","titles":["FAQ & Troubleshooting","General"]},"2710":{"title":"What PostgreSQL versions are supported?","titles":["FAQ & Troubleshooting","General"]},"2711":{"title":"What .NET version is required?","titles":["FAQ & Troubleshooting","General"]},"2712":{"title":"Is it safe from SQL injection?","titles":["FAQ & Troubleshooting","General"]},"2713":{"title":"How does NpgsqlRest compare to PostgREST or Supabase?","titles":["FAQ & Troubleshooting","General"]},"2714":{"title":"Can I use it inside an existing ASP.NET Core application?","titles":["FAQ & Troubleshooting","General"]},"2715":{"title":"Installation & Setup","titles":["FAQ & Troubleshooting"]},"2716":{"title":"How do I install NpgsqlRest?","titles":["FAQ & Troubleshooting","Installation & Setup"]},"2717":{"title":"How do I run it in Docker?","titles":["FAQ & Troubleshooting","Installation & Setup"]},"2718":{"title":"How do I connect to my database?","titles":["FAQ & Troubleshooting","Installation & Setup"]},"2719":{"title":"Can I use environment variables for configuration?","titles":["FAQ & Troubleshooting","Installation & Setup"]},"2720":{"title":"Endpoints","titles":["FAQ & Troubleshooting"]},"2721":{"title":"My function doesn\'t appear as an endpoint","titles":["FAQ & Troubleshooting","Endpoints"]},"2722":{"title":"My SQL file doesn\'t appear as an endpoint","titles":["FAQ & Troubleshooting","Endpoints"]},"2723":{"title":"An endpoint exists but I get 404 — why?","titles":["FAQ & Troubleshooting","Endpoints"]},"2724":{"title":"Why are parameter and column names camelCased? How do I turn that off?","titles":["FAQ & Troubleshooting","Endpoints"]},"2725":{"title":"My query returns one row — why do I get an array?","titles":["FAQ & Troubleshooting","Endpoints"]},"2726":{"title":"How do I return plain text, HTML, or CSV instead of JSON?","titles":["FAQ & Troubleshooting","Endpoints"]},"2727":{"title":"How do I customize the endpoint URL path?","titles":["FAQ & Troubleshooting","Endpoints"]},"2728":{"title":"How do I restrict access to an endpoint?","titles":["FAQ & Troubleshooting","Endpoints"]},"2729":{"title":"Can I expose tables and views directly, without writing any SQL?","titles":["FAQ & Troubleshooting","Endpoints"]},"2730":{"title":"Parameters","titles":["FAQ & Troubleshooting"]},"2731":{"title":"Named or positional parameters in SQL files — which should I use?","titles":["FAQ & Troubleshooting","Parameters"]},"2732":{"title":"How do I make a parameter optional?","titles":["FAQ & Troubleshooting","Parameters"]},"2733":{"title":"How do I get the authenticated user\'s ID into a query?","titles":["FAQ & Troubleshooting","Parameters"]},"2734":{"title":"Error: "could not determine data type of parameter"","titles":["FAQ & Troubleshooting","Parameters"]},"2735":{"title":"Authentication","titles":["FAQ & Troubleshooting"]},"2736":{"title":"What authentication methods are supported?","titles":["FAQ & Troubleshooting","Authentication"]},"2737":{"title":"How do I set up JWT authentication?","titles":["FAQ & Troubleshooting","Authentication"]},"2738":{"title":"Testing","titles":["FAQ & Troubleshooting"]},"2739":{"title":"How do I test my endpoints?","titles":["FAQ & Troubleshooting","Testing"]},"2740":{"title":"Can tests run against a temporary database instead of my real one?","titles":["FAQ & Troubleshooting","Testing"]},"2741":{"title":"My test fixtures need half the database inserted first — is there a better way?","titles":["FAQ & Troubleshooting","Testing"]},"2742":{"title":"Is there a watch mode?","titles":["FAQ & Troubleshooting","Testing"]},"2743":{"title":"Performance","titles":["FAQ & Troubleshooting"]},"2744":{"title":"How fast is it?","titles":["FAQ & Troubleshooting","Performance"]},"2745":{"title":"How do I enable caching?","titles":["FAQ & Troubleshooting","Performance"]},"2746":{"title":"How do I enable response compression?","titles":["FAQ & Troubleshooting","Performance"]},"2747":{"title":"How do I set up rate limiting?","titles":["FAQ & Troubleshooting","Performance"]},"2748":{"title":"Debugging & Logging","titles":["FAQ & Troubleshooting"]},"2749":{"title":"How do I see which endpoints are created and what options they have?","titles":["FAQ & Troubleshooting","Debugging & Logging"]},"2750":{"title":"How do I log the SQL each endpoint executes at runtime?","titles":["FAQ & Troubleshooting","Debugging & Logging"]},"2751":{"title":"How do I see the metadata queries NpgsqlRest runs at startup?","titles":["FAQ & Troubleshooting","Debugging & Logging"]},"2752":{"title":"How do I completely silence a logger?","titles":["FAQ & Troubleshooting","Debugging & Logging"]},"2753":{"title":"Troubleshooting","titles":["FAQ & Troubleshooting"]},"2754":{"title":"Startup warning: "Unknown configuration key"","titles":["FAQ & Troubleshooting","Troubleshooting"]},"2755":{"title":"Error: "permission denied for schema"","titles":["FAQ & Troubleshooting","Troubleshooting"]},"2756":{"title":"Timeout errors (504 Gateway Timeout)","titles":["FAQ & Troubleshooting","Troubleshooting"]},"2757":{"title":"Encrypted data is unreadable after restart","titles":["FAQ & Troubleshooting","Troubleshooting"]},"2758":{"title":"Leftover *_abcde test databases","titles":["FAQ & Troubleshooting","Troubleshooting"]},"2759":{"title":"HTTP Custom Types","titles":[]},"2760":{"title":"How it works","titles":["HTTP Custom Types"]},"2761":{"title":"Enabling the HTTP client","titles":["HTTP Custom Types"]},"2762":{"title":"Defining and using a type","titles":["HTTP Custom Types"]},"2763":{"title":"Reading the response","titles":["HTTP Custom Types"]},"2764":{"title":"Dynamic requests with placeholders","titles":["HTTP Custom Types"]},"2765":{"title":"Timeouts, retries, and caching","titles":["HTTP Custom Types"]},"2766":{"title":"Multiple calls in parallel","titles":["HTTP Custom Types"]},"2767":{"title":"Self-calls: composing your own endpoints","titles":["HTTP Custom Types"]},"2768":{"title":"Secrets and server-side values","titles":["HTTP Custom Types"]},"2769":{"title":"Configuration","titles":["HTTP Custom Types"]},"2770":{"title":"See it in the examples","titles":["HTTP Custom Types"]},"2771":{"title":"Related","titles":["HTTP Custom Types"]},"2772":{"title":"Overview","titles":[]},"2773":{"title":"Declarative Approach","titles":["Overview"]},"2774":{"title":"Plain SQL Files","titles":["Overview","Declarative Approach"]},"2775":{"title":"PostgreSQL Routines (Functions and Procedures)","titles":["Overview","Declarative Approach"]},"2776":{"title":"Technology & Distribution","titles":["Overview"]},"2777":{"title":"NpgsqlRest Installation Guide","titles":[]},"2778":{"title":"Download Executable","titles":["NpgsqlRest Installation Guide"]},"2779":{"title":"Manual Installation","titles":["NpgsqlRest Installation Guide","Download Executable"]},"2780":{"title":"Command Line Download","titles":["NpgsqlRest Installation Guide","Download Executable"]},"2781":{"title":"Windows (x64)","titles":["NpgsqlRest Installation Guide","Download Executable","Command Line Download"]},"2782":{"title":"Linux (x64)","titles":["NpgsqlRest Installation Guide","Download Executable","Command Line Download"]},"2783":{"title":"Linux (ARM64)","titles":["NpgsqlRest Installation Guide","Download Executable","Command Line Download"]},"2784":{"title":"macOS (ARM64)","titles":["NpgsqlRest Installation Guide","Download Executable","Command Line Download"]},"2785":{"title":"Command Line Basic Commands","titles":["NpgsqlRest Installation Guide","Download Executable"]},"2786":{"title":"NPM Installation","titles":["NpgsqlRest Installation Guide"]},"2787":{"title":"Docker Installation","titles":["NpgsqlRest Installation Guide"]},"2788":{"title":"Standard Image (AOT)","titles":["NpgsqlRest Installation Guide","Docker Installation"]},"2789":{"title":"JIT Image","titles":["NpgsqlRest Installation Guide","Docker Installation"]},"2790":{"title":"ARM64 Image","titles":["NpgsqlRest Installation Guide","Docker Installation"]},"2791":{"title":"Bun Runtime Image","titles":["NpgsqlRest Installation Guide","Docker Installation"]},"2792":{"title":"Building From Source","titles":["NpgsqlRest Installation Guide"]},"2793":{"title":"Next Steps","titles":["NpgsqlRest Installation Guide"]},"2794":{"title":"Logging","titles":[]},"2795":{"title":"The channel map","titles":["Logging"]},"2796":{"title":"Recipes","titles":["Logging"]},"2797":{"title":"See which endpoints exist and why","titles":["Logging","Recipes"]},"2798":{"title":"See every SQL command endpoints execute","titles":["Logging","Recipes"]},"2799":{"title":"Debug discovery: "why isn\'t my function/file picked up?"","titles":["Logging","Recipes"]},"2800":{"title":"Watch the test runner, mute everything else","titles":["Logging","Recipes"]},"2801":{"title":"Silence a channel completely","titles":["Logging","Recipes"]},"2802":{"title":"PostgreSQL messages in your logs","titles":["Logging"]},"2803":{"title":"Logging into PostgreSQL","titles":["Logging"]},"2804":{"title":"Files, OpenTelemetry, and production","titles":["Logging"]},"2805":{"title":"Related","titles":["Logging"]},"2806":{"title":"Proxy Endpoints","titles":[]},"2807":{"title":"How it works","titles":["Proxy Endpoints"]},"2808":{"title":"Enabling the proxy","titles":["Proxy Endpoints"]},"2809":{"title":"Passthrough mode","titles":["Proxy Endpoints"]},"2810":{"title":"Transform mode","titles":["Proxy Endpoints"]},"2811":{"title":"Where the request goes: target URL","titles":["Proxy Endpoints"]},"2812":{"title":"Forwarding headers, claims, and IP","titles":["Proxy Endpoints"]},"2813":{"title":"Forward proxy: send a function result upstream","titles":["Proxy Endpoints"]},"2814":{"title":"Configuration","titles":["Proxy Endpoints"]},"2815":{"title":"A complete example: cached AI gateway","titles":["Proxy Endpoints"]},"2816":{"title":"See it in the examples","titles":["Proxy Endpoints"]},"2817":{"title":"Related","titles":["Proxy Endpoints"]},"2818":{"title":"Quick Start","titles":[]},"2819":{"title":"Prerequisites","titles":["Quick Start"]},"2820":{"title":"Step 1: Create Your First Endpoint","titles":["Quick Start"]},"2821":{"title":"Option A: SQL File (Recommended)","titles":["Quick Start","Step 1: Create Your First Endpoint"]},"2822":{"title":"Option B: PostgreSQL Function","titles":["Quick Start","Step 1: Create Your First Endpoint"]},"2823":{"title":"Step 2: Run NpgsqlRest","titles":["Quick Start"]},"2824":{"title":"Step 3: Anonymous Endpoint And Verbose Logging","titles":["Quick Start"]},"2825":{"title":"Step 4: Create Configuration File","titles":["Quick Start"]},"2826":{"title":"Next Steps","titles":["Quick Start"]},"2827":{"title":"Server-Sent Events (SSE)","titles":[]},"2828":{"title":"How SSE works","titles":["Server-Sent Events (SSE)"]},"2829":{"title":"Creating a publisher endpoint","titles":["Server-Sent Events (SSE)"]},"2830":{"title":"Subscribing from the browser","titles":["Server-Sent Events (SSE)"]},"2831":{"title":"Who receives events: scope","titles":["Server-Sent Events (SSE)"]},"2832":{"title":"Which RAISE level fires: event level","titles":["Server-Sent Events (SSE)"]},"2833":{"title":"Targeting specific recipients","titles":["Server-Sent Events (SSE)"]},"2834":{"title":"Splitting publish from subscribe","titles":["Server-Sent Events (SSE)"]},"2835":{"title":"Configuration","titles":["Server-Sent Events (SSE)"]},"2836":{"title":"A complete example: real-time chat","titles":["Server-Sent Events (SSE)"]},"2837":{"title":"See it in the examples","titles":["Server-Sent Events (SSE)"]},"2838":{"title":"Related","titles":["Server-Sent Events (SSE)"]},"2839":{"title":"SQL File Endpoints","titles":[]},"2840":{"title":"How It Works","titles":["SQL File Endpoints"]},"2841":{"title":"Configuration","titles":["SQL File Endpoints"]},"2842":{"title":"Single-Command Files","titles":["SQL File Endpoints"]},"2843":{"title":"HTTP Verb Detection","titles":["SQL File Endpoints","Single-Command Files"]},"2844":{"title":"Parameters","titles":["SQL File Endpoints"]},"2845":{"title":"Named Parameters (:name)","titles":["SQL File Endpoints","Parameters"]},"2846":{"title":"Positional Parameters ($N)","titles":["SQL File Endpoints","Parameters"]},"2847":{"title":"Type Hints","titles":["SQL File Endpoints","Parameters"]},"2848":{"title":"Default Values","titles":["SQL File Endpoints","Parameters"]},"2849":{"title":"Virtual Parameters (@define_param)","titles":["SQL File Endpoints","Parameters"]},"2850":{"title":"Multi-Command Files","titles":["SQL File Endpoints"]},"2851":{"title":"Result Rules","titles":["SQL File Endpoints","Multi-Command Files"]},"2852":{"title":"Positional Annotations","titles":["SQL File Endpoints","Multi-Command Files"]},"2853":{"title":"@void","titles":["SQL File Endpoints","Multi-Command Files"]},"2854":{"title":"@returns — Skip Describe","titles":["SQL File Endpoints"]},"2855":{"title":"DO Blocks and Limitations","titles":["SQL File Endpoints"]},"2856":{"title":"Existing Features Work Unchanged","titles":["SQL File Endpoints"]},"2857":{"title":"The Dev Loop: Watch Mode","titles":["SQL File Endpoints"]},"2858":{"title":"SQL Files vs Functions","titles":["SQL File Endpoints"]},"2859":{"title":"Related","titles":["SQL File Endpoints"]},"2860":{"title":"Testing","titles":[]},"2861":{"title":"Quick start","titles":["Testing"]},"2862":{"title":"How it works","titles":["Testing"]},"2863":{"title":"Test file anatomy","titles":["Testing"]},"2864":{"title":"Assertions","titles":["Testing","Test file anatomy"]},"2865":{"title":"HTTP blocks: invoking endpoints","titles":["Testing","Test file anatomy"]},"2866":{"title":"The response table","titles":["Testing","Test file anatomy"]},"2867":{"title":"Transactions: when to begin/rollback","titles":["Testing","Test file anatomy"]},"2868":{"title":"Fixtures without inserting the whole database: deferrable constraints","titles":["Testing","Test file anatomy"]},"2869":{"title":"Reusing SQL: includes","titles":["Testing"]},"2870":{"title":"Per-file annotations","titles":["Testing"]},"2871":{"title":"Setup, Teardown, and named steps","titles":["Testing"]},"2872":{"title":"Scenario: dedicated test database per run","titles":["Testing"]},"2873":{"title":"Scenario: template database and per-test isolation","titles":["Testing"]},"2874":{"title":"Scenario: external migration runners","titles":["Testing"]},"2875":{"title":"Scenario: Docker","titles":["Testing"]},"2876":{"title":"Scenario: testing least-privilege (PoLP) setups","titles":["Testing"]},"2877":{"title":"Filtering and tags","titles":["Testing"]},"2878":{"title":"Watch mode","titles":["Testing"]},"2879":{"title":"Endpoint coverage","titles":["Testing"]},"2880":{"title":"Reporting, logging, CI","titles":["Testing"]},"2881":{"title":"Troubleshooting","titles":["Testing"]},"2882":{"title":"Reference","titles":["Testing"]}},"dirtCount":0,"index":[["≤512",{"2":{"2604":1}}],["−8",{"2":{"2398":1}}],["−12",{"2":{"2398":1}}],["−15",{"2":{"2398":2}}],["−5",{"2":{"2398":4}}],["−50",{"2":{"2397":1}}],["−2",{"2":{"2398":2}}],["−29",{"2":{"2397":1}}],["−39",{"2":{"2397":1}}],["δ",{"2":{"2397":1,"2398":2}}],["≥32",{"2":{"1460":1,"1792":1,"2375":1}}],["£",{"2":{"1431":1}}],["£51",{"2":{"1427":1}}],["▶",{"2":{"1381":1}}],["←",{"2":{"1381":1,"1570":2,"1571":2,"1577":2,"1792":1,"2762":4}}],["≠",{"2":{"1232":1,"1234":1,"1236":1,"1237":1,"1882":1,"1884":1,"1886":1,"1887":1}}],["③",{"2":{"1222":1}}],["🔴",{"0":{"2490":1},"2":{"2223":1}}],["🔐",{"2":{"1221":2}}],["🔧",{"2":{"1044":3}}],["②",{"2":{"1220":1,"1221":1,"1222":1}}],["①",{"2":{"1220":1,"1221":1,"1222":1}}],["≈",{"2":{"1169":1}}],["¹",{"2":{"1084":3}}],["🥉",{"2":{"1284":5,"1285":5}}],["🥈",{"2":{"1284":5,"1285":5}}],["🥇",{"2":{"1284":5,"1285":5}}],["🤖",{"2":{"1044":1}}],["🧑",{"2":{"1044":1}}],["────────────────────────────",{"2":{"1220":4,"1221":4,"1222":4}}],["──",{"2":{"1044":3}}],["↓",{"2":{"1005":3}}],["└──",{"2":{"976":4}}],["│",{"2":{"976":6}}],["├──",{"2":{"976":6}}],["пароль密码🔐",{"2":{"930":2}}],["×",{"2":{"872":1,"874":1,"1165":1,"1974":1,"2397":1,"2398":3,"2607":1}}],["~75",{"2":{"1401":1}}],["~74",{"2":{"868":1,"1037":1}}],["~60",{"2":{"1027":1}}],["~40",{"2":{"1350":1,"2270":1}}],["~4",{"2":{"910":1,"2309":1}}],["~80",{"2":{"1021":1,"1027":2,"1350":1,"2245":1}}],["~80kb",{"2":{"953":1,"968":1,"969":1,"971":1,"1099":1}}],["~8",{"2":{"910":1,"1064":1}}],["~",{"2":{"897":1}}],["~352",{"2":{"2372":1}}],["~35",{"2":{"1366":1}}],["~30mb",{"2":{"1084":1,"1088":1,"1127":1}}],["~300",{"2":{"873":1,"1181":2}}],["~30",{"2":{"872":1,"911":1,"1350":2,"2270":1,"2789":1}}],["~3",{"2":{"869":3,"873":2,"876":1}}],["~36",{"2":{"867":1}}],["~50",{"2":{"1064":1,"1181":3,"1350":1}}],["~550",{"2":{"869":1}}],["~5",{"2":{"868":1,"1349":2,"2397":1,"2398":1}}],["~18",{"2":{"2614":1}}],["~1s",{"2":{"2153":1,"2541":1,"2543":1,"2857":1}}],["~120",{"2":{"1366":1}}],["~125",{"2":{"872":1}}],["~170",{"2":{"1350":1}}],["~1mb",{"2":{"1169":1}}],["~160",{"2":{"1064":1}}],["~100",{"2":{"1181":1}}],["~10",{"2":{"1064":1,"2270":1}}],["~150",{"2":{"1181":1}}],["~15",{"2":{"910":1,"911":1,"1027":1,"1064":3}}],["~1",{"2":{"867":1,"2397":1,"2398":1}}],["~2s",{"2":{"2546":1}}],["~2866",{"2":{"2372":1}}],["~28",{"2":{"1991":1}}],["~25",{"2":{"1064":1}}],["~250",{"2":{"1027":1}}],["~200",{"2":{"1100":1,"1181":4,"2245":1,"2789":1}}],["~20mb",{"2":{"1084":1}}],["~20",{"2":{"1027":1,"1064":1,"1350":1}}],["~2",{"2":{"867":1,"869":1,"1349":1}}],["⚠️",{"0":{"2486":1,"2487":1},"2":{"835":2,"1094":3,"1097":2,"1098":2,"1100":2,"1101":2,"1102":3,"1104":2,"1109":2,"1111":3,"1279":7,"1875":1}}],["❌",{"2":{"835":11,"918":3,"1094":17,"1095":13,"1096":6,"1097":6,"1098":19,"1099":17,"1100":5,"1101":22,"1102":4,"1103":11,"1104":34,"1109":24,"1110":6,"1111":12,"1279":10}}],["✅",{"2":{"832":16,"835":14,"918":3,"1092":24,"1094":20,"1095":23,"1096":12,"1097":22,"1098":28,"1099":16,"1100":8,"1101":25,"1102":12,"1103":7,"1104":19,"1109":28,"1110":15,"1111":7,"1279":30,"2832":3}}],["^>",{"2":{"1427":1}}],["^",{"2":{"817":1,"897":1,"1386":1,"1792":4,"2142":4,"2144":3,"2146":6,"2148":1,"2328":1,"2575":5,"2840":1}}],["≡",{"2":{"687":1}}],["8+",{"2":{"2622":1}}],["89",{"2":{"1291":1,"1299":1}}],["899",{"2":{"1285":1,"1293":1}}],["8707",{"2":{"1792":1,"1830":1,"2223":1,"2481":1}}],["878",{"2":{"1293":1}}],["87",{"2":{"1287":1}}],["879",{"2":{"1285":1,"1295":1}}],["8192",{"2":{"1792":5,"1990":1,"1992":1,"1993":1,"2123":1,"2125":1}}],["813",{"2":{"1301":1}}],["816",{"2":{"1297":1}}],["81",{"2":{"1289":1,"1291":1,"1297":1}}],["817",{"2":{"1287":1,"2825":1}}],["818",{"2":{"1267":1,"2825":1}}],["869",{"2":{"1293":1}}],["86ms",{"2":{"1291":1}}],["863",{"2":{"1287":1}}],["86",{"2":{"1284":1,"1297":1,"2397":1}}],["8443",{"2":{"2118":2,"2703":2}}],["847",{"2":{"1299":1}}],["842",{"2":{"1289":1}}],["84ms",{"2":{"1287":1,"1288":1}}],["843",{"2":{"1284":1}}],["84",{"2":{"1277":3,"1278":1}}],["85",{"2":{"1268":1,"1291":1,"1322":1}}],["850",{"2":{"1265":1}}],["854542",{"2":{"1023":1}}],["83ms",{"2":{"1297":1}}],["836",{"2":{"1290":1}}],["835",{"2":{"1288":1}}],["832",{"2":{"1270":1}}],["83",{"2":{"1257":1,"1289":1,"1299":2}}],["821",{"2":{"2825":1}}],["826",{"2":{"1299":1}}],["822",{"2":{"1291":1}}],["824",{"2":{"1277":1,"1285":1,"1297":1}}],["82",{"2":{"1091":1,"1268":1,"1284":2,"1291":1,"1295":1,"2398":1}}],["8x",{"2":{"1090":1}}],["8px",{"2":{"965":1,"1061":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["882",{"2":{"1289":1}}],["883",{"2":{"1288":1}}],["88ms",{"2":{"1090":1,"1289":1}}],["88",{"2":{"868":1,"1091":1,"1262":1,"1264":1,"1268":1,"1291":1}}],["889",{"2":{"867":1}}],["80ms",{"2":{"1288":1,"1291":1}}],["803",{"2":{"1285":1,"1293":1}}],["806",{"2":{"1277":1}}],["807",{"2":{"1269":1,"1278":1}}],["80",{"2":{"867":1,"869":2,"911":1,"1036":1,"1277":2,"1299":1,"1322":1,"1366":1}}],["8080",{"2":{"451":2,"970":1,"995":1,"1047":1,"1117":2,"1199":2,"1202":1,"1207":2,"1343":2,"1345":2,"1380":1,"1386":1,"1420":1,"1433":2,"1711":1,"1773":3,"1775":1,"1792":1,"2011":1,"2116":1,"2117":1,"2118":2,"2162":1,"2354":1,"2699":1,"2701":1,"2702":1,"2703":2,"2717":2,"2788":2,"2789":2,"2790":2,"2791":2,"2809":3,"2823":2,"2824":3,"2825":1}}],["8",{"0":{"2232":1,"2399":1,"2616":1,"2657":1},"1":{"2617":1,"2618":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1},"2":{"408":2,"469":2,"817":2,"848":2,"892":1,"904":1,"913":1,"930":2,"1044":1,"1152":1,"1165":2,"1192":1,"1255":2,"1257":3,"1267":1,"1272":1,"1285":2,"1293":2,"1297":5,"1299":5,"1301":1,"1302":1,"1307":4,"1308":3,"1309":3,"1310":3,"1317":1,"1321":3,"1372":8,"1609":2,"1625":1,"1685":1,"1708":1,"1712":1,"1715":1,"1792":7,"1991":2,"2056":2,"2125":1,"2144":1,"2145":2,"2146":2,"2164":1,"2165":2,"2232":1,"2236":1,"2309":1,"2386":1,"2399":1,"2588":2,"2621":1,"2622":1,"2633":1,"2837":1}}],["9a",{"2":{"2144":5}}],["9a7wiy",{"2":{"1051":2}}],["95",{"2":{"1293":1,"1297":1}}],["956",{"2":{"1291":1}}],["9822970+02",{"2":{"1408":1}}],["984",{"2":{"1301":1}}],["988",{"2":{"1293":1,"1295":1,"1297":1}}],["98ms",{"2":{"1290":1}}],["98",{"2":{"1288":1,"1291":1}}],["989",{"2":{"1285":2}}],["928",{"2":{"2824":1}}],["92ms",{"2":{"1290":1,"1297":1}}],["921",{"2":{"1285":1,"1297":1}}],["92",{"2":{"1277":1}}],["941",{"2":{"1299":1}}],["947",{"2":{"1297":1}}],["949",{"2":{"1290":1,"1295":1}}],["94",{"2":{"1277":1,"1287":1,"1288":1,"2621":1}}],["940",{"2":{"1090":1,"1284":1}}],["9728",{"0":{"1833":1},"2":{"1792":2,"1827":1,"1831":1,"2223":1,"2481":2}}],["970",{"2":{"1293":1,"1297":1}}],["97",{"2":{"1293":1,"1299":2,"2621":1}}],["977",{"2":{"1289":1,"1291":1}}],["977ms",{"2":{"1268":1,"1271":1}}],["97ms",{"2":{"1289":1}}],["971",{"2":{"1285":1}}],["974",{"2":{"1269":1}}],["916",{"2":{"1301":1}}],["914",{"2":{"1290":1}}],["917",{"2":{"1287":1}}],["912",{"2":{"1285":1,"1297":1}}],["919",{"2":{"1285":1,"1299":1}}],["91",{"2":{"1255":1,"1257":1,"1264":1,"1284":1,"1287":2,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":2,"2397":1}}],["937",{"2":{"2824":3}}],["930",{"2":{"1299":1}}],["93ms",{"2":{"1290":1,"1299":1}}],["939",{"2":{"1289":1,"2824":2}}],["93",{"2":{"1023":1,"1268":1,"1289":1,"1291":1,"1293":1,"1299":2,"1714":1}}],["93561",{"2":{"1023":1}}],["961",{"2":{"2824":1}}],["966",{"2":{"1301":1}}],["964",{"2":{"1287":1,"1288":1}}],["96ms",{"2":{"1287":1,"1291":1}}],["960",{"2":{"1285":1,"1301":1}}],["96",{"2":{"874":1,"1277":2,"1289":1,"1295":1,"1301":1,"1714":1}}],["998",{"2":{"2824":2}}],["990",{"2":{"1293":1}}],["99ms",{"2":{"1291":1}}],["991",{"2":{"1291":1}}],["994",{"2":{"1290":1}}],["999",{"2":{"992":1,"1079":1,"2824":1,"2868":1}}],["9999",{"2":{"956":1,"1374":1}}],["99999",{"2":{"956":1,"1374":1}}],["9999999999",{"2":{"956":1,"1374":1}}],["99",{"2":{"562":2,"773":1,"956":2,"1189":2,"1192":1,"1277":1,"1374":2,"1442":1}}],["9",{"0":{"2231":1,"2675":1},"1":{"2676":1,"2677":1,"2678":1,"2679":1},"2":{"383":2,"817":1,"867":1,"869":2,"871":1,"913":2,"927":2,"1010":1,"1019":4,"1020":1,"1021":6,"1071":1,"1074":1,"1090":4,"1255":1,"1257":2,"1264":1,"1267":2,"1270":1,"1277":1,"1284":1,"1285":5,"1293":2,"1295":1,"1297":2,"1299":2,"1301":2,"1376":9,"1382":1,"1427":2,"1429":2,"1792":2,"2098":1,"2107":1,"2144":2,"2146":3,"2148":1,"2164":1,"2165":2,"2231":1,"2348":2,"2385":1,"2435":2,"2526":2,"2534":1,"2535":1,"2537":1,"2575":1,"2621":1,"2762":4,"2766":1,"2770":1,"2860":2,"2871":1}}],["907",{"2":{"1299":1}}],["90ms",{"2":{"1297":1}}],["906",{"2":{"1295":1}}],["908",{"2":{"1285":1}}],["900",{"2":{"622":1,"868":1,"1265":1}}],["90",{"2":{"188":1,"911":2,"1044":3,"1054":1,"1268":1,"1291":1,"1299":3,"1322":1,"1650":1,"1651":1,"1663":2,"1792":1,"2296":1,"2297":2}}],["90s",{"2":{"138":1,"1435":1,"1443":1}}],["✗",{"2":{"382":2,"650":1,"996":1,"2334":2,"2535":1}}],["✓",{"2":{"382":2,"996":1,"1220":5,"1221":4,"1222":4,"2104":1,"2334":2,"2535":1,"2537":1}}],["`+`",{"2":{"1792":1}}],["`|`",{"2":{"1792":1}}],["`authorization",{"2":{"1792":1}}],["`authorize`",{"2":{"1792":2}}],["`wrapintransaction",{"2":{"1792":1}}],["`requestheadersmode`",{"2":{"1792":1}}],["`requestheadersparametername`",{"2":{"1792":1}}],["`rate",{"2":{"1792":2}}],["`raise",{"2":{"1792":2}}],["`bodyjson`",{"2":{"1792":2}}],["`bearertoken`",{"2":{"1792":1}}],["`bearer",{"2":{"1063":1}}],["`querystring`",{"2":{"1792":2}}],["`get",{"2":{"1792":1}}],["`get`",{"2":{"1792":3}}],["`trace`",{"2":{"1792":1}}],["`type`",{"2":{"1792":2}}],["`openapi",{"2":{"1792":2}}],["`options`",{"2":{"1792":1}}],["`onlywithhttptag`",{"2":{"1792":1}}],["`onlyannotated`",{"2":{"1792":2}}],["`head`",{"2":{"1792":1}}],["`http`",{"2":{"1792":1}}],["`public`",{"2":{"2256":3}}],["`put`",{"2":{"1792":1}}],["`parserequest",{"2":{"1792":1}}],["`parseurl",{"2":{"1792":1}}],["`parseall`",{"2":{"1792":1}}],["`parameters`",{"2":{"1792":1}}],["`parameter`",{"2":{"1792":2}}],["`patch`",{"2":{"1792":1}}],["`post`",{"2":{"1792":2}}],["`logcommands`",{"2":{"1792":1}}],["`live`",{"2":{"1529":1}}],["`mcp`",{"2":{"1792":2}}],["`is",{"2":{"1792":1}}],["`ignore`",{"2":{"1792":3}}],["`import",{"2":{"1571":1,"1792":1}}],["`imported",{"2":{"894":1}}],["`nocontent`",{"2":{"1792":1}}],["`nullliteral`",{"2":{"1792":2}}],["`null`",{"2":{"1792":10}}],["`name`",{"2":{"1792":3}}],["`validationoptions",{"2":{"1792":1}}],["`signinasync",{"2":{"1792":1}}],["`search",{"2":{"1792":1}}],["`set",{"2":{"1792":3}}],["`source`",{"2":{"1792":1}}],["`sql`",{"2":{"1792":1}}],["`statusmessage`",{"2":{"1792":1}}],["`statuscode`",{"2":{"1792":1}}],["`scheme`",{"2":{"1792":1}}],["`keep`",{"2":{"1792":1}}],["`emptystring`",{"2":{"1792":2}}],["`endpointcreated`",{"2":{"1792":7}}],["`enabled`",{"2":{"1792":2}}],["`enabled",{"2":{"1792":1}}],["`export`",{"2":{"1792":1}}],["`export",{"2":{"1571":1}}],["`jwt`",{"2":{"1792":1}}],["`claimsidentity`",{"2":{"1792":1}}],["`context",{"2":{"1792":1}}],["`context`",{"2":{"1792":1}}],["`connect`",{"2":{"1792":1}}],["`cookiename`",{"2":{"1792":1}}],["`cookies`",{"2":{"1792":1}}],["`create",{"2":{"1792":1}}],["`cacheoptions",{"2":{"1792":1}}],["`cached",{"2":{"1792":1}}],["`cache",{"2":{"1342":1,"1792":3}}],["`user",{"2":{"1320":2}}],["`1",{"2":{"1024":1}}],["`delete`",{"2":{"1792":2}}],["`defaultpolicy`",{"2":{"1792":1}}],["`default`",{"2":{"370":1,"378":1}}],["`do",{"2":{"1792":1}}],["`data",{"2":{"961":2}}],["`",{"2":{"894":1,"961":1,"996":8,"1024":2,"1026":1,"1063":1,"1320":1,"1361":1,"1364":2,"1409":1,"1410":1,"1571":2,"1574":2,"1575":1,"1792":36,"2277":1,"2431":1,"2830":1}}],["`$",{"2":{"894":1,"1024":1,"1026":1,"1361":1,"1410":1,"1574":3,"1575":1,"2277":1,"2830":1}}],["``",{"2":{"388":1}}],["`=`",{"2":{"370":1,"378":1}}],["z0",{"2":{"2144":1,"2146":2}}],["zagreb",{"2":{"2453":1}}],["za",{"2":{"2144":1}}],["zone",{"2":{"995":1,"1050":1,"1792":1,"1856":1,"2451":2}}],["zk",{"2":{"927":2}}],["z",{"2":{"927":4,"1792":3,"1856":4,"2224":2,"2450":1,"2451":2,"2453":1,"2454":3,"2455":2,"2456":4,"2528":2,"2861":2,"2864":2,"2866":1}}],["zipcode",{"2":{"332":2,"1973":2,"2587":2,"2590":1}}],["zip",{"2":{"332":1,"1973":1,"2010":3,"2587":1}}],["zero",{"0":{"866":1,"947":1,"951":1,"1331":1,"1438":1,"1746":1,"2347":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1},"2":{"258":1,"388":1,"421":1,"445":1,"529":1,"587":1,"834":1,"854":1,"868":1,"894":1,"947":1,"969":1,"1004":1,"1008":1,"1027":1,"1037":5,"1043":1,"1048":1,"1074":1,"1099":1,"1108":1,"1117":1,"1127":1,"1137":1,"1180":1,"1275":1,"1280":1,"1322":1,"1327":1,"1366":1,"1377":1,"1382":1,"1383":1,"1398":1,"1405":1,"1414":2,"1421":1,"1438":2,"1439":2,"1440":1,"1441":1,"1442":1,"1792":1,"1861":1,"1929":1,"2277":1,"2284":1,"2309":1,"2372":1,"2389":1,"2392":1,"2399":1,"2528":1,"2532":1,"2537":1,"2540":1,"2546":1,"2776":1,"2802":1,"2845":1,"2864":1}}],["q=members",{"2":{"1692":1,"1792":1}}],["q2",{"2":{"964":2}}],["q",{"2":{"964":2}}],["q1",{"2":{"325":2,"2319":1}}],["queues",{"2":{"1351":1}}],["queuelimit",{"2":{"1158":1,"1161":1,"1792":6,"1951":2,"1952":2,"1953":2,"1954":2,"1959":1,"1960":5,"2257":4,"2443":4,"2471":1,"2551":1}}],["queue",{"2":{"948":1,"1105":1,"1107":1,"1158":1,"1161":1,"1165":1,"1324":1}}],["queued",{"2":{"868":1,"1951":1,"1952":1,"1953":1,"1954":2}}],["questionable",{"2":{"1096":1}}],["questions",{"2":{"838":1,"2795":1}}],["question",{"2":{"831":1,"859":2,"861":1,"864":2,"1104":1,"1324":1}}],["queries",{"0":{"1370":1,"2751":1},"2":{"577":1,"837":1,"845":1,"848":1,"856":1,"859":1,"919":1,"920":1,"948":2,"966":1,"975":1,"1006":1,"1049":1,"1066":1,"1080":1,"1084":1,"1096":5,"1101":3,"1121":1,"1122":1,"1126":1,"1127":2,"1149":1,"1150":1,"1167":1,"1176":2,"1177":1,"1205":1,"1255":1,"1398":3,"1399":1,"1401":1,"1405":1,"1519":3,"1525":1,"1529":1,"1599":1,"1618":1,"1632":3,"1746":1,"1769":1,"1792":5,"1802":1,"1974":1,"2000":1,"2009":1,"2047":1,"2052":3,"2060":1,"2063":1,"2087":1,"2256":2,"2330":1,"2346":1,"2347":1,"2607":1,"2615":2,"2635":4,"2712":1,"2751":1,"2795":1,"2799":1,"2824":1,"2825":1,"2841":1,"2842":1,"2851":1}}],["queried",{"2":{"188":1,"2296":1}}],["query2",{"2":{"2008":1}}],["query1",{"2":{"2008":1}}],["querystring",{"2":{"1846":1,"1847":1,"1924":3,"1925":1,"2509":1,"2511":1,"2812":1}}],["querystringnullhandlinghandler",{"2":{"2597":1}}],["querystringnullhandling",{"0":{"1854":1,"2595":1},"2":{"470":1,"1792":1,"1836":1,"1853":1,"2594":1,"2595":1,"2597":1,"2666":1}}],["querytext",{"2":{"1370":1,"2850":1}}],["querying",{"2":{"851":1,"1174":1,"2063":1,"2350":1}}],["query=test",{"2":{"1033":1}}],["query=null",{"2":{"468":1}}],["query=",{"2":{"468":1}}],["query",{"0":{"137":1,"256":1,"423":1,"458":1,"520":1,"523":1,"1096":1,"1201":1,"1745":1,"1925":1,"2204":1,"2256":1,"2304":1,"2517":1,"2674":1,"2725":1,"2733":1},"1":{"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"1202":1,"1203":1,"1204":1,"1205":1},"2":{"75":2,"90":1,"106":2,"132":1,"139":1,"149":1,"152":1,"165":4,"167":1,"168":4,"186":1,"212":1,"226":3,"231":1,"250":2,"253":1,"258":1,"277":1,"281":1,"313":2,"385":1,"387":1,"408":2,"409":1,"414":3,"415":1,"423":3,"436":5,"446":2,"448":2,"452":2,"454":1,"458":6,"459":2,"460":1,"462":1,"463":1,"466":2,"467":1,"468":6,"469":1,"515":4,"516":2,"517":4,"518":1,"520":2,"522":1,"523":1,"524":2,"526":1,"558":2,"573":1,"579":1,"582":1,"585":1,"587":1,"615":1,"616":1,"624":2,"639":1,"844":1,"848":4,"851":1,"856":1,"857":1,"860":1,"861":1,"868":1,"873":2,"874":1,"918":3,"919":2,"920":2,"948":2,"956":1,"967":1,"968":1,"974":1,"1019":1,"1023":1,"1026":6,"1033":2,"1038":1,"1044":1,"1060":1,"1066":1,"1067":2,"1078":1,"1080":1,"1084":1,"1096":2,"1098":1,"1102":1,"1106":1,"1122":1,"1125":1,"1127":1,"1129":2,"1130":1,"1132":1,"1153":1,"1176":1,"1180":1,"1183":1,"1184":1,"1185":1,"1197":1,"1202":2,"1205":1,"1207":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":2,"1236":2,"1239":1,"1255":1,"1258":1,"1304":1,"1324":1,"1325":1,"1326":2,"1368":1,"1370":2,"1378":1,"1382":1,"1385":2,"1398":3,"1405":1,"1408":2,"1412":1,"1427":1,"1431":3,"1435":1,"1477":1,"1504":2,"1525":1,"1559":1,"1569":4,"1571":1,"1574":3,"1575":1,"1582":1,"1618":1,"1672":1,"1674":2,"1679":1,"1684":1,"1689":1,"1742":1,"1759":2,"1769":1,"1771":1,"1792":28,"1844":1,"1853":1,"1854":1,"1855":1,"1864":4,"1898":1,"1901":1,"1906":2,"1912":1,"1917":2,"1924":5,"1925":5,"1928":1,"1974":2,"2008":1,"2034":1,"2047":1,"2056":2,"2059":1,"2060":1,"2063":1,"2104":1,"2106":1,"2108":1,"2110":1,"2156":2,"2177":1,"2185":1,"2204":3,"2222":4,"2247":1,"2250":1,"2254":1,"2255":5,"2256":6,"2277":3,"2278":1,"2283":2,"2290":1,"2293":1,"2302":2,"2303":1,"2304":1,"2318":1,"2321":3,"2322":2,"2324":2,"2339":1,"2342":1,"2346":1,"2347":1,"2369":1,"2395":1,"2398":1,"2459":1,"2481":1,"2494":2,"2498":1,"2509":1,"2510":1,"2511":4,"2513":4,"2517":2,"2518":2,"2519":2,"2520":2,"2521":1,"2523":4,"2529":1,"2530":1,"2542":2,"2549":2,"2555":2,"2558":1,"2559":1,"2586":1,"2595":2,"2596":1,"2607":2,"2608":2,"2614":2,"2634":1,"2635":3,"2665":2,"2666":1,"2674":1,"2701":1,"2712":1,"2713":1,"2751":1,"2807":1,"2811":3,"2812":2,"2814":1,"2833":1,"2839":1,"2840":1,"2849":1,"2850":2,"2858":1,"2865":1,"2866":1,"2878":1}}],["quits",{"2":{"2106":1,"2532":1,"2537":1}}],["quic",{"2":{"1993":1}}],["quickly",{"2":{"919":1,"1123":1,"1171":1,"2772":1}}],["quick",{"0":{"2012":1,"2211":1,"2698":1,"2818":1,"2861":1},"1":{"2699":1,"2700":1,"2819":1,"2820":1,"2821":1,"2822":1,"2823":1,"2824":1,"2825":1,"2826":1},"2":{"136":3,"277":1,"523":3,"577":1,"836":1,"912":1,"959":1,"1037":1,"1073":1,"1074":1,"1154":2,"1385":1,"1405":1,"1599":1,"2054":1,"2793":1}}],["quietest",{"2":{"2801":1}}],["quietly",{"2":{"847":1,"860":1,"861":1,"872":1,"874":1,"1081":1}}],["quiet",{"2":{"633":1,"1171":1,"1792":1,"2094":1,"2107":1,"2536":1,"2537":3}}],["quarter=2",{"2":{"964":1}}],["quarter",{"2":{"964":3,"1792":2}}],["quarterly",{"2":{"325":7,"964":2}}],["quantified",{"0":{"872":1}}],["quantity",{"2":{"845":1,"863":1,"1044":2,"1187":1,"1188":1,"1189":1,"1191":3,"1192":2,"1373":1}}],["qualification",{"2":{"933":1}}],["qualified",{"2":{"582":1,"1792":1}}],["qualifying",{"2":{"933":1}}],["quality",{"0":{"875":1}}],["qualitative",{"2":{"872":1}}],["qualitatively",{"2":{"869":1,"872":1,"873":1}}],["quagmire",{"2":{"840":2,"857":1}}],["quoting",{"2":{"2270":1}}],["quotetext",{"2":{"2270":1}}],["quote",{"2":{"847":1,"851":2,"2040":1,"2270":1,"2476":1,"2603":1}}],["quotestyle=quotestyle",{"2":{"1202":1}}],["quotes",{"2":{"768":2,"786":3,"891":2,"1582":1,"1608":1,"1792":1,"2007":1,"2040":1,"2128":1,"2270":1,"2272":1,"2328":1,"2476":1,"2546":1,"2589":1,"2603":3}}],["quoted",{"2":{"378":3,"379":1,"869":1,"1608":1,"1792":1,"2272":1,"2333":3,"2496":1,"2528":1,"2531":1,"2540":1,"2687":1,"2845":1,"2863":1}}],["quota",{"2":{"479":1,"1069":1,"1955":1,"2379":1,"2438":1}}],["quot",{"0":{"375":2,"467":2,"564":2,"2249":4,"2544":2,"2734":2,"2754":2,"2755":2,"2799":2},"2":{"0":4,"63":2,"101":4,"102":2,"106":2,"108":2,"157":8,"184":6,"186":2,"220":2,"257":6,"289":2,"290":4,"322":8,"323":2,"361":12,"376":8,"383":4,"388":2,"408":2,"423":2,"436":2,"460":2,"464":4,"469":2,"551":2,"553":2,"565":2,"613":2,"615":2,"621":8,"623":8,"656":2,"704":2,"734":2,"762":2,"765":2,"772":2,"826":10,"831":2,"834":2,"835":2,"836":2,"838":6,"840":2,"843":8,"844":2,"845":2,"847":2,"848":2,"851":12,"852":4,"854":4,"855":6,"857":2,"859":2,"860":8,"863":4,"864":4,"865":4,"868":6,"871":2,"872":8,"876":12,"877":2,"904":4,"905":2,"937":2,"948":4,"952":2,"994":6,"1009":4,"1038":2,"1039":2,"1041":4,"1052":2,"1061":2,"1066":2,"1067":14,"1068":2,"1069":6,"1070":8,"1071":4,"1079":2,"1098":6,"1101":4,"1111":2,"1135":10,"1148":4,"1150":2,"1157":2,"1208":2,"1217":14,"1224":2,"1225":6,"1226":12,"1227":6,"1228":6,"1229":6,"1230":8,"1232":6,"1233":8,"1235":4,"1237":4,"1239":2,"1255":6,"1258":2,"1280":2,"1338":4,"1373":2,"1382":2,"1384":2,"1401":2,"1409":2,"1412":2,"1414":2,"1421":2,"1427":2,"1441":2,"1447":30,"1451":12,"1454":20,"1459":2,"1464":16,"1471":10,"1472":2,"1474":6,"1475":2,"1477":4,"1489":4,"1499":10,"1504":2,"1511":18,"1518":4,"1519":2,"1521":14,"1523":8,"1524":4,"1527":2,"1540":4,"1544":4,"1556":8,"1558":4,"1559":2,"1565":4,"1569":2,"1577":2,"1582":6,"1588":2,"1604":2,"1609":6,"1639":4,"1641":2,"1642":2,"1643":2,"1644":4,"1651":22,"1670":2,"1684":14,"1685":4,"1687":4,"1689":2,"1722":12,"1753":32,"1759":2,"1764":8,"1766":6,"1769":10,"1802":12,"1803":2,"1804":4,"1805":4,"1807":10,"1808":6,"1809":2,"1818":2,"1819":4,"1822":2,"1823":6,"1824":8,"1825":2,"1827":2,"1837":4,"1840":2,"1841":2,"1844":2,"1848":8,"1853":6,"1854":6,"1855":2,"1856":8,"1857":2,"1858":2,"1874":2,"1875":6,"1876":12,"1877":6,"1878":6,"1879":6,"1880":8,"1882":6,"1885":4,"1887":4,"1898":6,"1906":22,"1908":2,"1911":2,"1912":2,"1917":16,"1918":12,"1937":2,"1948":2,"1949":2,"1951":4,"1952":2,"1953":2,"1954":2,"1956":4,"1957":4,"1958":2,"1959":2,"1974":2,"2000":10,"2006":4,"2016":10,"2017":2,"2034":6,"2038":18,"2040":10,"2047":16,"2060":10,"2075":2,"2077":2,"2094":20,"2095":2,"2101":8,"2105":2,"2106":4,"2107":2,"2108":2,"2109":14,"2110":2,"2111":18,"2114":2,"2117":6,"2124":6,"2125":8,"2126":2,"2127":4,"2128":6,"2129":2,"2130":10,"2131":2,"2141":2,"2154":2,"2156":8,"2166":2,"2179":4,"2221":8,"2223":2,"2225":2,"2253":12,"2265":14,"2267":2,"2273":4,"2283":2,"2292":6,"2293":2,"2297":12,"2319":10,"2320":4,"2323":4,"2324":2,"2330":16,"2337":2,"2338":10,"2339":32,"2340":6,"2342":4,"2343":2,"2348":4,"2351":4,"2353":6,"2359":4,"2363":2,"2364":4,"2365":2,"2375":2,"2376":18,"2378":6,"2379":8,"2380":68,"2381":6,"2385":2,"2389":6,"2391":2,"2393":4,"2399":2,"2400":2,"2406":2,"2414":8,"2416":4,"2419":4,"2424":2,"2425":2,"2428":2,"2429":2,"2430":6,"2434":2,"2437":2,"2438":10,"2440":2,"2442":4,"2445":2,"2452":10,"2453":2,"2454":2,"2455":6,"2463":2,"2468":2,"2471":2,"2476":6,"2479":2,"2481":10,"2484":2,"2486":6,"2493":2,"2494":4,"2496":2,"2497":2,"2519":2,"2521":2,"2530":2,"2531":2,"2532":18,"2533":2,"2535":2,"2536":2,"2537":2,"2539":8,"2540":2,"2542":8,"2544":14,"2551":28,"2554":4,"2555":6,"2565":6,"2566":4,"2572":2,"2575":2,"2586":18,"2588":16,"2589":2,"2595":6,"2596":2,"2603":4,"2607":12,"2608":2,"2618":6,"2629":2,"2648":6,"2659":6,"2665":2,"2687":2,"2697":4,"2702":6,"2721":4,"2722":6,"2724":2,"2725":4,"2750":4,"2752":6,"2769":12,"2794":2,"2795":6,"2798":2,"2801":6,"2803":2,"2814":12,"2823":2,"2828":2,"2835":2,"2840":4,"2841":10,"2842":8,"2861":2,"2868":2,"2871":20,"2872":2,"2879":2,"2880":2}}],["|",{"2":{"306":2,"491":1,"603":1,"723":3,"872":1,"894":5,"920":50,"938":9,"961":3,"995":14,"996":7,"1024":11,"1026":5,"1317":2,"1318":1,"1342":5,"1366":3,"1386":6,"1408":10,"1410":1,"1413":3,"1416":2,"1553":2,"1558":2,"1567":9,"1569":3,"1570":6,"1571":5,"1792":5,"1909":1,"2038":2,"2171":2,"2247":2,"2252":16,"2273":4,"2359":4,"2426":7,"2451":1,"2457":1,"2540":1,"2590":12,"2611":11,"2760":4,"2807":3,"2828":2,"2845":1}}],["|||",{"2":{"344":1}}],["||",{"2":{"250":2,"520":4,"532":1,"834":1,"885":1,"894":1,"1021":5,"1026":2,"1038":4,"1338":2,"1339":4,"1366":2,"1376":1,"1410":1,"1427":3,"2285":1,"2452":1,"2762":1,"2815":1}}],[">default",{"2":{"1792":1}}],[">download",{"2":{"961":1,"1200":1}}],[">get",{"2":{"1061":1}}],[">view",{"2":{"961":1}}],[">logout",{"2":{"938":1,"1061":1}}],[">login",{"2":{"938":1,"1061":2}}],[">who",{"2":{"938":1,"1061":1}}],[">+",{"2":{"836":1}}],[">customer",{"2":{"836":1}}],[">admin",{"2":{"836":1}}],[">internal",{"2":{"836":1}}],[">>",{"2":{"695":1,"700":1,"764":1,"765":2,"766":1,"774":2,"883":2,"884":3,"888":3,"903":6,"904":3,"905":2,"1021":1,"1078":1,"1214":4,"1215":3,"1216":1,"1232":4,"1239":2,"1338":6,"1339":7,"1357":13,"1376":1,"1386":1,"1408":2,"1410":6,"1416":1,"1571":1,"1792":1,"2461":1,"2530":1,"2572":1,"2815":4,"2861":1,"2866":2,"2868":1,"2873":1}}],[">broadcaster",{"2":{"663":1}}],[">browser",{"2":{"663":1}}],[">using",{"2":{"663":1}}],[">eventsource",{"2":{"663":1}}],[">=",{"2":{"310":1,"894":1,"930":1,"1366":1,"1410":1,"2110":1}}],[">|filtered",{"2":{"663":1}}],[">|post|",{"2":{"663":1}}],[">|raise",{"2":{"663":1}}],[">|raise|",{"2":{"650":2}}],[">|registers",{"2":{"663":1}}],[">|opens",{"2":{"663":1}}],[">|",{"2":{"306":2,"2171":2,"2760":4,"2807":3,"2828":2}}],[">",{"2":{"297":4,"306":1,"310":1,"566":2,"650":11,"663":3,"765":1,"833":4,"836":4,"881":5,"883":1,"884":1,"888":2,"894":1,"904":1,"905":2,"922":2,"928":1,"929":3,"930":3,"938":7,"949":3,"995":2,"1021":3,"1024":1,"1061":8,"1074":1,"1078":2,"1086":2,"1087":2,"1088":1,"1130":1,"1148":3,"1184":3,"1185":2,"1211":2,"1214":1,"1220":6,"1221":6,"1222":6,"1234":1,"1235":1,"1305":3,"1317":1,"1333":7,"1339":9,"1342":1,"1366":2,"1376":3,"1386":1,"1408":1,"1427":3,"1431":1,"1491":3,"1518":3,"1567":1,"1569":1,"1685":1,"1792":16,"1868":2,"2039":1,"2171":3,"2187":1,"2247":1,"2255":9,"2265":3,"2321":1,"2359":1,"2461":1,"2526":1,"2572":1,"2677":1,"2682":1,"2694":1,"2700":1,"2760":1,"2807":4,"2824":2,"2825":2,"2828":2,"2860":1,"2868":1,"2869":1}}],["768",{"2":{"1991":1}}],["765",{"2":{"1299":1}}],["760",{"2":{"1293":1}}],["763",{"2":{"1293":1}}],["76ms",{"2":{"1288":1,"1297":1}}],["761",{"2":{"1285":1,"1295":1}}],["76",{"2":{"1278":1,"1293":2}}],["764",{"2":{"1269":1,"1285":1}}],["778",{"2":{"2825":1}}],["772",{"2":{"1291":1}}],["774",{"2":{"1290":1}}],["77",{"2":{"1277":1,"1288":1,"1299":1,"1301":1,"1427":1}}],["738",{"2":{"2825":1}}],["737",{"2":{"1293":1}}],["73",{"2":{"1277":1,"1284":1,"1287":1,"1291":1,"1293":1,"1295":1,"1299":1}}],["792",{"2":{"1297":1}}],["79ms",{"2":{"1290":1}}],["79",{"2":{"1268":1,"1277":2,"1284":2,"1287":1,"1290":1,"1291":1,"1299":1}}],["79851",{"2":{"1023":1}}],["7519",{"2":{"1445":1,"1453":1,"1457":1,"2554":1}}],["759",{"2":{"1295":1}}],["753",{"2":{"1291":1,"2825":2}}],["75ms",{"2":{"1287":1,"1299":1}}],["75",{"2":{"1277":1,"1291":1,"1299":1,"2879":1}}],["758",{"2":{"1270":1}}],["750",{"2":{"1181":1,"2825":3}}],["756",{"2":{"867":1,"874":1,"875":1,"1285":1,"1297":1}}],["788",{"2":{"1295":1}}],["781",{"2":{"1289":1}}],["787",{"2":{"1289":1}}],["785",{"2":{"1101":1}}],["78",{"2":{"1091":1,"1277":1,"1291":2,"1299":1,"2435":1}}],["7807",{"2":{"835":1,"1109":1,"1111":4,"1127":1,"1218":1,"2240":1,"2255":1,"2271":1}}],["7x",{"2":{"1090":1}}],["701",{"2":{"1301":1}}],["70ms",{"2":{"1290":1,"1299":1}}],["702",{"2":{"1285":1,"1291":1}}],["705",{"2":{"1269":1}}],["70",{"2":{"1027":1,"1277":1,"1290":1,"1366":1,"1401":2,"1429":1,"2621":1}}],["700",{"2":{"867":1,"1414":1}}],["71ms",{"2":{"1293":1}}],["715",{"2":{"1293":1}}],["711",{"2":{"1285":1,"1289":1}}],["710",{"2":{"1285":1,"1295":1,"1301":1}}],["71",{"2":{"1284":1,"1288":2,"1291":2}}],["71828",{"2":{"956":1,"1374":1}}],["7128",{"2":{"333":2}}],["7+",{"2":{"869":1,"1084":1,"1088":1,"1094":1,"1119":1,"1127":1}}],["74ms",{"2":{"1297":1}}],["743",{"2":{"1288":1}}],["749",{"2":{"1084":1,"1090":2,"1267":1,"1284":1,"1289":1}}],["740946",{"2":{"1023":1}}],["74",{"0":{"866":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1},"2":{"333":2,"867":1,"869":2,"872":2,"1037":1,"1295":1,"1299":2,"1383":1}}],["720",{"2":{"1289":1}}],["7230",{"2":{"1017":1,"1726":1,"2264":1}}],["72",{"0":{"927":1},"1":{"928":1,"929":1,"930":1},"2":{"308":1,"921":1,"927":1,"928":3,"929":1,"930":1,"944":1,"1049":1,"1284":3,"1289":1,"1291":2,"1301":1,"1714":1,"2177":1}}],["7",{"0":{"1394":1,"2233":1,"2570":1,"2619":1,"2646":1},"1":{"1395":1,"1396":1,"2571":1,"2572":1,"2620":1,"2621":1,"2622":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1},"2":{"257":2,"869":3,"873":1,"876":1,"878":1,"883":2,"884":2,"886":4,"894":1,"902":2,"904":5,"913":2,"930":2,"947":1,"956":1,"966":1,"1037":1,"1053":1,"1090":1,"1236":1,"1237":1,"1255":3,"1257":2,"1263":1,"1270":2,"1285":6,"1287":3,"1288":3,"1289":3,"1290":3,"1291":3,"1293":4,"1295":6,"1297":4,"1299":4,"1301":6,"1374":1,"1382":1,"1429":2,"1453":1,"1454":3,"1463":1,"1464":2,"1792":4,"1886":1,"1887":1,"2164":1,"2165":2,"2233":1,"2236":1,"2238":1,"2277":2,"2376":2,"2377":1,"2385":1,"2386":2,"2398":3,"2435":1,"2546":1,"2554":2,"2571":1,"2588":2,"2621":2}}],["65535",{"2":{"1792":2,"1992":2}}],["65536",{"2":{"1792":1,"1990":1}}],["651",{"2":{"1295":1}}],["65",{"2":{"1293":1,"1299":1,"1991":1}}],["642",{"2":{"1301":1}}],["64",{"2":{"1289":1,"1290":1,"1297":1,"1299":1,"1301":1,"1516":1,"1618":1,"1620":1,"1714":2,"1792":2,"1991":1,"2265":1,"2614":1}}],["649",{"2":{"1288":1}}],["64ms",{"2":{"1287":1,"1297":1}}],["69ms",{"2":{"1301":1}}],["69",{"2":{"1295":1}}],["694",{"2":{"1291":2}}],["690",{"2":{"1289":1,"1301":1}}],["698",{"2":{"1288":1}}],["692",{"2":{"1287":2}}],["691",{"2":{"1284":1,"1290":1,"1291":1}}],["697",{"2":{"1270":1}}],["619",{"2":{"1301":1}}],["619455",{"2":{"1023":1}}],["613",{"2":{"1291":1,"1293":1}}],["61ms",{"2":{"1290":1,"1293":1,"1295":1}}],["610",{"2":{"1289":1,"1291":1}}],["615",{"2":{"1285":1,"1297":1}}],["618",{"2":{"1285":1,"1299":1}}],["61",{"2":{"1284":1}}],["687",{"2":{"1295":1}}],["683",{"2":{"1291":1}}],["685",{"2":{"1291":1}}],["68ms",{"2":{"1287":1}}],["68",{"2":{"1277":1,"2397":1}}],["620",{"2":{"1297":1}}],["623",{"2":{"1293":1}}],["62ms",{"2":{"1290":2}}],["624",{"2":{"1272":1,"1285":1}}],["629",{"2":{"1270":1,"1285":1}}],["628",{"2":{"1264":1,"1295":1}}],["66ms",{"2":{"1299":1}}],["661",{"2":{"1297":1}}],["663",{"2":{"1267":1}}],["6666666666666667",{"2":{"916":1,"917":1}}],["63ms",{"2":{"1301":1}}],["63",{"2":{"1297":1,"1301":1,"2546":1}}],["637",{"2":{"1291":1}}],["6379",{"2":{"121":1,"1067":1,"1146":1,"1147":1,"1177":1,"1510":1,"1514":1,"1515":1,"1534":1,"1792":1,"2274":1}}],["636",{"2":{"1291":1}}],["636ms",{"2":{"1091":1}}],["631",{"2":{"1285":1,"1295":1}}],["6x",{"2":{"1090":1,"1121":1,"1127":1}}],["6c",{"2":{"930":1}}],["6b",{"2":{"930":1}}],["6a",{"2":{"930":1}}],["6×",{"2":{"872":1,"874":2,"1382":1}}],["672",{"2":{"1301":1}}],["673",{"2":{"1301":1}}],["676",{"2":{"1297":1}}],["6750",{"2":{"2481":1}}],["675",{"2":{"1297":1}}],["67",{"2":{"1289":1,"1297":1}}],["677",{"2":{"1285":1}}],["67ms",{"2":{"1090":1,"1287":1,"1289":1,"1290":1}}],["679",{"2":{"867":1,"868":1,"872":1,"873":1,"1301":1}}],["67890",{"2":{"893":1}}],["6789",{"2":{"184":1,"186":1,"2292":1,"2293":1}}],["6",{"0":{"1218":1,"1362":1,"1393":1,"2234":1,"2568":1,"2612":1,"2630":1,"2636":1,"2639":1,"2643":1},"1":{"1363":1,"2569":1,"2613":1,"2614":1,"2615":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2637":1,"2638":1,"2640":1,"2641":1,"2642":1,"2644":1,"2645":1},"2":{"478":1,"851":1,"852":1,"863":1,"869":2,"873":2,"913":1,"915":1,"930":1,"1090":2,"1107":1,"1152":2,"1159":2,"1177":1,"1255":2,"1257":5,"1262":1,"1267":1,"1270":1,"1274":1,"1277":2,"1285":3,"1290":1,"1291":1,"1293":1,"1295":4,"1297":1,"1301":1,"1352":1,"1355":2,"1357":3,"1358":3,"1362":2,"1366":1,"1410":3,"1412":1,"1429":1,"1617":1,"1622":1,"1623":1,"1676":1,"1677":1,"1701":2,"1762":2,"1792":2,"1952":2,"1960":1,"1991":1,"2014":2,"2045":2,"2164":1,"2165":2,"2222":2,"2234":4,"2236":1,"2238":1,"2257":1,"2398":1,"2504":2,"2506":3,"2588":5,"2621":4,"2622":1,"2789":1,"2790":1,"2791":1,"2824":1,"2825":1}}],["606",{"2":{"1291":1}}],["60ms",{"2":{"1289":1}}],["603",{"2":{"1289":1,"1293":1}}],["604800",{"2":{"1455":1,"2554":1}}],["604",{"2":{"1287":1}}],["607ms",{"2":{"1091":1,"1268":1}}],["60",{"2":{"476":1,"477":1,"478":1,"479":1,"1053":1,"1062":1,"1069":2,"1071":1,"1145":1,"1158":2,"1159":1,"1162":2,"1177":1,"1245":1,"1255":1,"1277":1,"1278":1,"1288":1,"1291":1,"1401":1,"1453":1,"1454":3,"1463":1,"1464":3,"1510":1,"1511":1,"1513":1,"1721":1,"1722":1,"1792":9,"1893":1,"1951":2,"1952":2,"1955":2,"1958":1,"1959":1,"1960":3,"2175":1,"2257":2,"2270":1,"2376":2,"2377":1,"2379":2,"2441":1,"2470":1,"2471":1,"2502":1,"2554":2,"2737":1,"2769":2}}],["600+",{"2":{"1402":2}}],["6000000000000000",{"2":{"916":1,"917":1}}],["600",{"2":{"309":1,"363":1,"869":1,"873":1,"1049":1,"1171":3,"1265":2,"1638":1,"1639":1,"1646":1,"1792":2,"2073":1,"2075":1,"2080":1}}],["+7",{"2":{"2398":1}}],["+7dai62pqaybwjqihu96xpzmzyu",{"2":{"1051":2}}],["+5",{"2":{"2398":2}}],["+2",{"2":{"2398":1}}],["+0",{"2":{"2398":2}}],["+0000",{"2":{"1023":1}}],["+3",{"2":{"2398":3}}],["+10",{"2":{"2398":2}}],["+1",{"2":{"2398":1}}],["+$",{"2":{"1792":1,"2142":1,"2146":1,"2575":1}}],["+x",{"2":{"1117":1,"2782":1,"2783":1,"2784":1}}],["+=",{"2":{"996":1}}],["+",{"0":{"1114":1,"1115":2,"1423":1,"1714":1,"2380":1,"2493":1},"1":{"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1,"2381":1},"2":{"109":1,"214":4,"297":1,"308":1,"310":2,"313":1,"320":3,"378":1,"395":1,"414":2,"423":2,"429":1,"436":2,"446":5,"622":1,"723":2,"764":1,"765":1,"766":1,"774":1,"817":1,"833":1,"835":1,"860":2,"869":7,"871":1,"873":1,"874":1,"881":1,"883":1,"884":1,"885":2,"888":1,"894":2,"904":1,"911":1,"928":2,"929":3,"933":2,"938":1,"961":2,"969":1,"995":2,"1005":1,"1027":2,"1037":4,"1047":1,"1064":1,"1084":1,"1102":1,"1106":2,"1108":1,"1127":1,"1180":1,"1181":20,"1184":2,"1211":1,"1214":1,"1232":1,"1234":1,"1255":4,"1257":4,"1317":2,"1320":2,"1322":1,"1325":1,"1333":1,"1338":1,"1339":2,"1342":1,"1351":1,"1366":3,"1379":1,"1386":1,"1408":1,"1410":2,"1413":2,"1427":2,"1429":4,"1431":2,"1458":1,"1515":1,"1522":2,"1567":2,"1568":2,"1569":1,"1571":1,"1572":3,"1573":2,"1574":1,"1581":1,"1743":4,"1774":1,"1792":10,"1830":2,"1831":1,"1868":1,"1909":1,"2106":1,"2142":2,"2144":1,"2146":5,"2148":1,"2158":1,"2166":2,"2167":2,"2177":1,"2188":2,"2225":1,"2247":1,"2265":2,"2310":1,"2313":1,"2323":1,"2330":2,"2332":2,"2333":4,"2347":1,"2372":1,"2375":1,"2380":1,"2416":1,"2421":1,"2435":4,"2438":1,"2479":1,"2481":3,"2482":2,"2492":1,"2498":1,"2502":4,"2506":1,"2523":1,"2531":1,"2537":2,"2538":1,"2540":1,"2545":1,"2575":3,"2655":1,"2745":1,"2762":5,"2765":3,"2795":1,"2807":1,"2811":6,"2828":1,"2830":1,"2832":1,"2836":2,"2845":1,"2869":1,"2870":1}}],["→",{"0":{"854":1,"855":1,"856":1,"857":1},"2":{"105":2,"106":2,"107":3,"187":1,"212":2,"254":1,"255":1,"256":1,"257":1,"263":2,"291":2,"298":3,"299":1,"301":4,"303":2,"304":2,"306":3,"376":1,"379":3,"388":1,"390":1,"395":2,"405":1,"406":1,"408":3,"414":5,"439":3,"462":4,"463":3,"464":4,"469":2,"524":2,"553":1,"554":1,"555":1,"611":1,"612":1,"613":1,"691":1,"699":2,"849":2,"854":3,"869":1,"871":4,"872":3,"874":1,"876":1,"904":1,"975":3,"1008":8,"1015":2,"1037":1,"1041":3,"1043":2,"1044":9,"1045":3,"1067":5,"1104":1,"1111":2,"1359":3,"1381":1,"1405":21,"1408":1,"1458":3,"1521":3,"1525":3,"1576":3,"1580":1,"1605":2,"1706":2,"1745":2,"1747":2,"1792":8,"1824":3,"1862":1,"1924":2,"2040":3,"2111":1,"2171":1,"2176":3,"2177":2,"2183":1,"2184":1,"2186":1,"2187":3,"2222":1,"2277":4,"2282":3,"2294":1,"2302":5,"2319":1,"2320":3,"2321":3,"2324":2,"2327":2,"2333":3,"2346":2,"2357":2,"2375":3,"2380":1,"2381":4,"2384":1,"2386":5,"2397":1,"2433":1,"2435":4,"2461":2,"2465":5,"2476":2,"2481":14,"2483":2,"2497":2,"2498":1,"2504":2,"2509":2,"2513":1,"2532":1,"2534":2,"2536":1,"2537":2,"2540":1,"2544":2,"2588":2,"2590":2,"2607":7,"2611":3,"2664":1,"2665":3,"2688":2,"2733":1,"2815":1,"2832":2,"2840":2,"2843":6,"2845":1,"2848":4,"2851":3,"2867":2,"2872":3}}],["4px",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["498",{"2":{"1301":1}}],["499",{"2":{"1299":1}}],["49ms",{"2":{"1295":1,"2535":1}}],["491",{"2":{"1288":1,"1299":1}}],["492",{"2":{"1285":1}}],["49",{"2":{"1277":1,"1301":1,"2824":11}}],["47",{"2":{"1408":1,"2824":2}}],["476",{"2":{"1297":1}}],["47ms",{"2":{"1288":1,"1293":1,"1301":1}}],["478",{"2":{"1284":1,"1287":1,"1295":1}}],["479",{"2":{"1270":1,"1285":1}}],["470",{"2":{"1270":1,"1284":1,"1285":1,"1295":1}}],["471",{"2":{"1267":1,"1284":1,"1287":1}}],["414",{"2":{"1431":1,"1792":1,"1917":1,"1925":1,"2222":1,"2517":1}}],["41ms",{"2":{"1289":1,"1299":1,"1301":1}}],["411",{"2":{"1277":1}}],["41",{"2":{"1277":1,"1297":1,"1714":1,"2435":1}}],["410",{"2":{"1269":1,"1285":1,"1293":1}}],["419",{"2":{"867":1,"1267":1}}],["48",{"2":{"1293":1,"1714":1}}],["48ms",{"2":{"1291":1}}],["481",{"2":{"1287":1,"1293":1}}],["483",{"2":{"1287":1}}],["487",{"2":{"1285":1,"1295":1}}],["482",{"2":{"1284":1}}],["484",{"2":{"1284":1}}],["485",{"2":{"1267":1}}],["480",{"2":{"1090":1,"1267":1,"1284":1,"1287":1,"1290":1}}],["462",{"2":{"1301":1}}],["463",{"2":{"1291":1}}],["468",{"2":{"1291":1,"1293":1}}],["468ms",{"2":{"1091":1,"1268":1}}],["464",{"2":{"1284":1,"1287":1}}],["466",{"2":{"1284":1,"1287":1}}],["469",{"2":{"1262":1,"1264":1,"1290":1}}],["46",{"2":{"1165":1,"1301":1}}],["4x",{"2":{"948":1,"1090":1,"1269":1}}],["4mb",{"2":{"919":1}}],["4+",{"2":{"919":1,"1097":1,"2607":1,"2611":1}}],["4317",{"2":{"1792":1,"1800":1,"1807":2,"2804":1}}],["431",{"2":{"1431":1,"1792":1,"1917":1,"1925":1,"2222":1,"2517":1}}],["430",{"2":{"1301":1}}],["437",{"2":{"1295":1}}],["43ms",{"2":{"1290":1}}],["435",{"2":{"1269":1}}],["43",{"2":{"867":1,"1277":1,"1299":1}}],["4",{"0":{"858":1,"943":1,"1003":1,"1022":1,"1070":1,"1216":1,"1250":1,"1310":1,"1358":1,"1391":1,"2236":1,"2520":1,"2541":1,"2563":1,"2583":1,"2592":1,"2598":1,"2601":1,"2605":2,"2609":1,"2612":1,"2616":1,"2619":1,"2825":1},"1":{"859":1,"860":1,"861":1,"2542":1,"2543":1,"2564":1,"2565":1,"2566":1,"2567":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1,"2602":1,"2603":1,"2604":1,"2606":2,"2607":2,"2608":2,"2610":1,"2611":1,"2613":1,"2614":1,"2615":1,"2617":1,"2618":1,"2620":1,"2621":1,"2622":1},"2":{"695":1,"746":1,"760":1,"770":1,"843":1,"865":1,"867":2,"874":1,"875":1,"888":2,"897":1,"913":10,"916":2,"917":3,"918":4,"919":11,"930":2,"956":4,"1007":1,"1048":1,"1050":2,"1051":1,"1053":3,"1054":7,"1055":4,"1056":7,"1057":6,"1058":3,"1059":1,"1060":6,"1062":6,"1084":1,"1090":4,"1097":1,"1102":1,"1105":2,"1152":1,"1181":1,"1193":2,"1255":4,"1257":5,"1263":2,"1264":1,"1265":5,"1267":9,"1269":1,"1270":1,"1272":2,"1277":7,"1280":1,"1281":1,"1284":8,"1285":5,"1287":2,"1288":8,"1289":10,"1290":2,"1291":11,"1293":5,"1295":10,"1297":2,"1299":2,"1301":2,"1336":2,"1374":3,"1429":1,"1625":1,"1714":1,"1792":4,"2094":1,"2095":1,"2105":1,"2113":1,"2125":1,"2144":3,"2164":1,"2165":2,"2171":1,"2236":10,"2238":1,"2386":1,"2398":4,"2465":2,"2504":2,"2535":1,"2537":2,"2586":2,"2588":9,"2590":1,"2621":2,"2821":1,"2873":1,"2879":1,"2880":1}}],["447",{"2":{"1301":1}}],["448",{"2":{"1295":1}}],["442",{"2":{"1293":1}}],["449",{"2":{"1290":1}}],["44ms",{"2":{"1289":1,"1290":1}}],["445",{"2":{"1270":1}}],["4460",{"2":{"1101":1}}],["44",{"2":{"308":1,"1297":1,"2177":1,"2671":1}}],["42p18",{"2":{"2336":1}}],["42883",{"2":{"2242":1,"2255":2,"2496":1}}],["428",{"2":{"1297":1}}],["42703",{"2":{"1386":1,"2328":1,"2840":1}}],["427",{"2":{"1295":1}}],["424",{"2":{"1289":1,"1293":1}}],["42ms",{"2":{"1288":1,"1291":1}}],["426",{"2":{"1285":1,"1297":2}}],["425",{"2":{"1284":2,"1287":1}}],["42501",{"2":{"1111":2,"1669":1,"1673":1,"1674":1,"1678":1,"1792":1,"2255":2}}],["421",{"2":{"1267":1}}],["423",{"2":{"1090":1,"1267":1,"1284":1,"1289":1}}],["42",{"2":{"256":2,"378":2,"405":2,"406":2,"408":2,"527":1,"565":2,"566":1,"585":2,"615":1,"691":2,"956":1,"1041":1,"1069":1,"1257":1,"1277":1,"1290":1,"1291":1,"1374":1,"1738":1,"1824":1,"2277":2,"2283":2,"2320":2,"2321":1,"2333":3,"2335":2,"2337":2,"2339":1,"2665":2,"2823":1,"2848":1}}],["429",{"2":{"213":2,"214":1,"480":1,"1032":3,"1104":1,"1105":1,"1157":1,"1739":1,"1740":2,"1741":1,"1742":2,"1792":2,"1948":1,"1949":1,"1958":2,"1960":1,"2257":1,"2287":1,"2288":2,"2289":1,"2290":2,"2470":1,"2765":2}}],["457",{"2":{"1301":1}}],["459",{"2":{"1291":1}}],["45ms",{"2":{"1288":1,"1291":1}}],["452",{"2":{"1285":1,"1301":1}}],["458",{"2":{"1285":1}}],["450",{"2":{"887":2}}],["45678",{"2":{"887":1}}],["45",{"2":{"184":1,"186":1,"872":1,"900":1,"919":10,"956":1,"977":1,"980":1,"990":1,"995":1,"1287":1,"1374":1,"1464":1,"2292":1,"2293":1,"2376":1}}],["4th",{"2":{"50":1,"51":1,"1403":1}}],["40y",{"2":{"2861":1}}],["402",{"2":{"1293":1}}],["40ms",{"2":{"1290":2,"1293":1}}],["405",{"2":{"1290":1,"1824":1,"2258":1,"2481":1}}],["406",{"2":{"1284":1}}],["409",{"2":{"1111":3,"1678":1}}],["4096",{"2":{"747":1,"1792":3,"1992":1,"2123":1,"2125":1}}],["400s",{"2":{"2384":2}}],["400",{"0":{"2384":1,"2491":1},"2":{"817":1,"819":1,"1026":1,"1071":2,"1111":2,"1169":2,"1181":4,"1234":1,"1236":1,"1265":3,"1285":1,"1366":2,"1669":2,"1673":2,"1674":2,"1676":1,"1677":1,"1678":3,"1742":1,"1792":10,"1824":1,"2110":1,"2138":1,"2141":1,"2142":4,"2144":3,"2145":2,"2146":8,"2148":1,"2149":1,"2223":1,"2255":13,"2267":1,"2290":1,"2384":6,"2481":1,"2491":1,"2498":1,"2575":6,"2723":1}}],["40001",{"2":{"576":1,"577":3,"1152":2,"1153":1,"1154":3,"1155":1,"1177":1,"1587":1,"1592":1,"1597":3,"1598":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["40p01",{"2":{"576":1,"577":3,"1153":1,"1154":3,"1155":1,"1177":1,"1587":1,"1592":1,"1597":3,"1598":1,"1792":1}}],["40",{"2":{"333":2,"869":1,"1155":1,"1277":1,"1288":1,"1297":1,"1401":3}}],["404",{"0":{"2491":1,"2723":1},"2":{"210":1,"261":1,"263":1,"302":1,"309":1,"377":1,"447":1,"524":3,"1042":1,"1055":1,"1111":1,"1335":1,"1341":1,"1732":1,"1747":1,"1792":2,"1922":1,"1930":1,"2178":1,"2223":1,"2242":1,"2255":3,"2258":1,"2264":1,"2267":2,"2271":1,"2333":1,"2344":1,"2489":1,"2491":1,"2529":1,"2537":1,"2549":1,"2566":1,"2723":2,"2732":1,"2865":1,"2876":1,"2881":1}}],["403",{"2":{"25":1,"691":1,"1045":1,"1057":1,"1061":1,"1111":2,"1669":1,"1673":1,"1674":1,"1678":1,"1792":4,"1823":1,"1824":1,"1825":1,"2181":1,"2187":1,"2255":3,"2271":1,"2481":2,"2498":2}}],["401",{"2":{"16":1,"25":1,"35":1,"39":1,"60":1,"63":1,"64":1,"297":1,"298":1,"299":2,"301":2,"313":1,"690":1,"710":1,"934":1,"1045":1,"1055":1,"1077":1,"1111":1,"1269":1,"1480":1,"1688":1,"1792":2,"1825":2,"1827":1,"1830":1,"2176":1,"2187":1,"2271":2,"2420":2,"2481":3,"2498":2,"2529":1,"2823":1}}],["x00",{"2":{"2265":1,"2495":2}}],["x00null",{"2":{"2265":1}}],["x1f",{"2":{"2265":1,"2495":1}}],["xcontenttypeoptions",{"2":{"1792":1,"2015":1,"2016":1,"2017":1,"2027":1,"2028":1,"2029":1,"2632":1}}],["xsrf",{"2":{"1564":1,"1582":1,"1792":2}}],["xsrftokenheadername",{"2":{"1553":1,"1564":1,"1582":1,"1792":1}}],["xss",{"2":{"1448":1,"1792":1,"2016":1,"2020":1,"2632":1}}],["x86",{"2":{"1255":1}}],["xframeoptions",{"2":{"1792":1,"2015":1,"2016":1,"2018":1,"2027":1,"2028":1,"2029":1,"2632":1}}],["xf",{"2":{"1118":1}}],["xz",{"2":{"1118":2}}],["x64",{"0":{"2781":1,"2782":1},"2":{"1117":1,"1118":2,"2270":1,"2779":1,"2792":4}}],["x+e",{"2":{"1051":2}}],["xpath",{"0":{"1427":1,"1428":1},"2":{"1037":1,"1423":2,"1424":1,"1427":2,"1428":1,"1429":5,"1432":1,"2164":1,"2762":4,"2770":1}}],["xt",{"2":{"927":2}}],["xyz",{"2":{"915":2}}],["xmlparse",{"2":{"1423":1,"1427":1,"1428":1,"2762":2}}],["xml",{"0":{"1423":1},"1":{"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1},"2":{"918":1,"953":1,"1037":1,"1094":1,"1423":1,"1424":1,"1427":2,"1428":2,"1655":1,"1792":3,"1936":2,"1943":3,"2094":1,"2102":2,"2164":1,"2221":1,"2394":2,"2526":1,"2535":2,"2537":1,"2880":4}}],["xmlhttprequest",{"2":{"894":1,"1366":1,"1410":3}}],["xmin",{"2":{"854":1}}],["xhr",{"2":{"894":7,"1366":7,"1410":5}}],["xunit",{"2":{"876":1,"2535":1}}],["xls",{"2":{"769":1}}],["xlsx`",{"2":{"961":1}}],["xlsx",{"2":{"675":1,"678":1,"679":1,"769":1,"772":1,"773":2,"887":2,"893":1,"904":1,"947":1,"949":2,"959":1,"960":1,"964":2,"971":1,"1037":1,"1099":2,"1127":1,"1374":2,"1792":1,"2077":1,"2078":1,"2079":1,"2652":1,"2653":1}}],["x26",{"2":{"209":1,"864":2,"881":2,"894":4,"922":1,"930":2,"956":1,"995":4,"996":1,"1019":1,"1023":3,"1305":1,"1335":8,"1364":1,"1366":4,"1374":1,"1380":4,"1386":2,"1398":1,"1408":2,"1410":2,"1416":2,"1567":2,"1574":2,"1697":3,"1792":20,"2321":1,"2701":5,"2766":1}}],["x",{"0":{"1493":1,"2017":1,"2018":1},"2":{"101":1,"209":1,"297":2,"369":1,"375":1,"540":1,"546":1,"650":3,"668":1,"701":1,"834":1,"836":1,"868":1,"869":1,"903":3,"927":2,"930":1,"1100":3,"1317":1,"1326":2,"1416":1,"1489":1,"1493":1,"1494":1,"1521":1,"1582":3,"1620":1,"1643":1,"1651":1,"1659":1,"1661":1,"1703":2,"1705":3,"1706":2,"1711":4,"1788":2,"1792":21,"1836":1,"1848":1,"1859":2,"1863":1,"1905":1,"1957":2,"1974":1,"2018":1,"2030":1,"2106":1,"2202":2,"2247":1,"2297":1,"2332":3,"2379":2,"2380":1,"2391":1,"2399":1,"2438":1,"2526":2,"2528":2,"2529":1,"2536":1,"2537":1,"2539":1,"2565":2,"2586":2,"2607":1,"2632":6,"2633":8,"2701":1,"2739":1,"2795":2,"2833":1,"2835":2,"2860":2,"2861":1,"2864":2,"2865":1,"2866":1,"2881":1}}],["xxxxx",{"2":{"2872":1}}],["xxx=test",{"2":{"2679":1,"2696":1}}],["xxx",{"2":{"40":2}}],["x3c",{"2":{"14":3,"30":2,"45":2,"56":2,"69":1,"79":4,"92":1,"102":1,"113":3,"133":4,"145":2,"155":2,"175":2,"179":2,"193":2,"243":4,"268":2,"318":4,"340":1,"348":5,"358":4,"370":42,"399":1,"429":1,"459":2,"474":2,"498":2,"508":1,"516":2,"528":2,"537":2,"539":12,"550":3,"560":2,"570":4,"582":2,"599":1,"628":2,"637":5,"650":7,"651":3,"663":6,"674":3,"685":2,"719":8,"745":1,"809":6,"836":6,"894":3,"927":2,"929":2,"938":17,"956":1,"961":4,"965":9,"995":2,"996":38,"1024":1,"1026":3,"1038":1,"1042":1,"1061":33,"1086":2,"1087":2,"1088":1,"1150":1,"1200":2,"1211":2,"1214":1,"1234":1,"1317":1,"1342":1,"1366":5,"1374":1,"1386":4,"1408":6,"1409":2,"1410":4,"1416":2,"1427":8,"1491":4,"1567":1,"1568":1,"1569":1,"1571":2,"1574":1,"1685":10,"1792":45,"1868":2,"2039":5,"2040":2,"2073":2,"2075":2,"2080":2,"2148":1,"2247":1,"2255":9,"2256":4,"2257":2,"2265":8,"2266":3,"2310":1,"2313":1,"2357":1,"2359":4,"2405":1,"2461":3,"2476":2,"2575":1,"2632":4,"2832":2}}],["$schema",{"2":{"2670":1}}],["$scheme",{"2":{"1711":1}}],["$credential",{"2":{"1792":3}}],["$host",{"2":{"1711":1}}],["$proxy",{"2":{"1711":1}}],["$lib",{"2":{"1416":3,"1417":2,"1574":5,"1581":4}}],["$8",{"2":{"1217":1,"1237":1,"1792":3,"1887":1,"1893":2}}],["$7=usercontext",{"2":{"1220":1,"1221":1}}],["$7",{"2":{"1217":1,"1237":1,"1792":3,"1887":1,"1893":2}}],["$6=backupeligible",{"2":{"1220":1,"1221":1}}],["$6",{"2":{"1217":1,"1237":1,"1376":3,"1792":3,"1887":1,"1893":2}}],["$wrz7",{"2":{"927":2}}],["$",{"2":{"382":1,"817":1,"894":1,"897":1,"926":6,"961":2,"996":2,"1024":2,"1026":1,"1063":1,"1320":2,"1342":1,"1366":1,"1409":1,"1429":2,"1575":1,"1661":1,"1792":1,"2144":3,"2146":2,"2148":1,"2277":1,"2334":1,"2565":1,"2575":1,"2622":2,"2830":1}}],["$$",{"2":{"184":2,"186":2,"207":2,"208":2,"209":2,"263":4,"264":4,"292":2,"298":2,"308":4,"309":2,"310":4,"312":2,"313":2,"322":2,"351":2,"386":2,"415":2,"423":2,"426":2,"427":2,"428":2,"439":2,"449":2,"452":2,"454":2,"584":2,"621":2,"641":2,"646":2,"658":2,"659":2,"664":4,"665":2,"750":2,"751":2,"752":2,"755":2,"756":2,"764":2,"765":2,"766":2,"774":2,"777":2,"812":2,"813":2,"814":2,"815":2,"826":2,"883":2,"884":2,"888":2,"904":2,"928":2,"929":2,"930":2,"979":2,"980":2,"986":2,"988":2,"989":2,"990":6,"991":2,"992":2,"994":2,"1005":2,"1021":2,"1024":1,"1029":2,"1056":4,"1060":2,"1068":2,"1076":4,"1105":6,"1179":2,"1193":4,"1197":2,"1214":2,"1215":2,"1216":2,"1232":2,"1234":2,"1235":2,"1236":2,"1239":2,"1309":2,"1321":2,"1332":2,"1338":2,"1339":2,"1347":2,"1348":2,"1372":2,"1376":2,"1387":2,"1390":2,"1393":4,"1394":2,"1395":4,"1396":2,"1419":2,"1427":2,"1431":2,"1442":2,"1458":6,"1504":2,"1567":2,"1689":2,"1727":2,"1736":2,"1742":2,"1745":2,"1792":2,"1921":2,"1924":2,"1926":2,"2147":2,"2176":2,"2177":4,"2183":2,"2184":2,"2186":2,"2187":6,"2264":2,"2283":2,"2290":2,"2292":2,"2293":2,"2303":2,"2304":2,"2319":2,"2337":2,"2338":2,"2343":2,"2344":4,"2346":2,"2375":6,"2528":4,"2549":6,"2572":2,"2575":2,"2580":4,"2586":2,"2587":2,"2588":2,"2589":2,"2607":2,"2762":2,"2764":2,"2766":2,"2767":2,"2802":2,"2803":2,"2809":2,"2810":2,"2812":2,"2813":2,"2815":2,"2829":4,"2834":6,"2836":4,"2855":2,"2863":2,"2864":2}}],["$n",{"0":{"2846":1},"2":{"165":1,"168":1,"170":1,"306":1,"528":1,"529":1,"1372":1,"1386":1,"2284":1,"2319":1,"2321":3,"2323":3,"2540":4,"2845":2,"2855":1}}],["$5=source",{"2":{"1792":1}}],["$5=transports",{"2":{"1220":1,"1221":1}}],["$59",{"2":{"1044":1}}],["$5",{"2":{"31":1,"37":2,"38":1,"39":1,"40":1,"50":1,"1059":1,"1062":1,"1217":1,"1237":1,"1376":3,"1501":1,"1547":2,"1683":1,"1684":1,"1687":1,"1689":2,"1698":1,"1792":8,"1800":1,"1805":2,"1806":1,"1810":1,"1887":1,"1893":2,"2496":1,"2803":1,"2804":1}}],["$4=exception",{"2":{"1792":1}}],["$4=algorithm",{"2":{"1220":1,"1221":1}}],["$4=meta",{"2":{"881":3}}],["$4",{"0":{"762":1,"772":1},"2":{"31":1,"37":2,"38":1,"39":1,"40":1,"50":2,"760":1,"761":1,"764":1,"767":1,"770":1,"771":1,"774":1,"775":1,"787":1,"789":1,"882":1,"886":1,"893":2,"899":1,"900":1,"902":1,"904":2,"1059":1,"1062":1,"1217":2,"1237":1,"1239":1,"1376":2,"1398":2,"1501":1,"1547":2,"1683":1,"1684":1,"1687":1,"1689":2,"1698":1,"1792":15,"1800":1,"1805":2,"1806":1,"1810":1,"1887":1,"1888":1,"1893":3,"2123":2,"2125":1,"2128":2,"2129":1,"2130":2,"2131":1,"2132":2,"2496":1,"2572":1,"2803":1,"2804":1}}],["$3=timestamp",{"2":{"1792":1}}],["$3=usercontext",{"2":{"1222":1}}],["$3=publickey",{"2":{"1220":1,"1221":1}}],["$3=prev",{"2":{"881":3}}],["$3",{"0":{"885":1},"2":{"31":1,"37":2,"38":2,"39":1,"40":2,"41":1,"50":1,"305":1,"310":3,"312":1,"360":2,"380":2,"622":3,"760":2,"761":5,"764":1,"767":1,"770":2,"771":6,"774":1,"775":1,"787":1,"789":1,"797":2,"882":1,"883":1,"885":1,"886":1,"899":1,"900":1,"902":1,"904":1,"1056":2,"1059":1,"1062":3,"1150":2,"1197":2,"1217":2,"1237":1,"1239":1,"1371":2,"1372":2,"1376":2,"1398":4,"1473":1,"1501":1,"1503":1,"1504":2,"1505":1,"1547":2,"1567":3,"1683":1,"1684":1,"1687":1,"1689":3,"1698":1,"1727":4,"1792":16,"1800":1,"1805":2,"1806":1,"1810":1,"1887":1,"1888":1,"1893":3,"2123":2,"2128":2,"2129":1,"2130":2,"2131":1,"2132":2,"2147":2,"2333":2,"2803":1,"2804":1,"2829":1}}],["$2=42",{"2":{"2321":1}}],["$2=message",{"2":{"1792":1}}],["$2=newsigncount",{"2":{"1222":1}}],["$2=userhandle",{"2":{"1220":1,"1221":1}}],["$2=",{"2":{"1220":1,"1221":1,"1222":1}}],["$2=data",{"2":{"881":3}}],["$2=2024",{"2":{"372":1,"2846":1}}],["$2",{"2":{"31":1,"37":2,"38":1,"39":1,"40":1,"50":1,"71":2,"157":1,"184":2,"298":2,"305":1,"310":3,"312":2,"360":1,"369":1,"370":1,"372":3,"373":2,"376":3,"377":1,"380":2,"382":1,"503":2,"510":2,"520":2,"584":2,"586":2,"592":2,"622":3,"665":2,"760":1,"761":1,"764":1,"767":1,"770":1,"771":1,"774":1,"775":2,"787":1,"789":1,"797":2,"826":2,"827":2,"849":1,"882":1,"886":1,"899":1,"900":2,"902":1,"904":1,"1038":3,"1040":1,"1045":2,"1054":1,"1056":2,"1059":1,"1062":3,"1073":2,"1150":3,"1154":1,"1179":3,"1197":2,"1217":3,"1220":1,"1221":3,"1222":3,"1232":1,"1234":1,"1235":1,"1237":1,"1239":1,"1371":4,"1372":2,"1376":2,"1386":1,"1395":1,"1398":3,"1410":3,"1458":2,"1473":1,"1501":1,"1503":1,"1504":3,"1505":1,"1547":2,"1567":3,"1650":1,"1651":2,"1655":2,"1663":1,"1664":2,"1683":1,"1684":1,"1687":1,"1689":3,"1698":1,"1727":1,"1792":23,"1800":1,"1805":2,"1806":1,"1810":1,"1882":1,"1884":1,"1885":1,"1887":1,"1888":1,"1893":6,"2123":2,"2128":2,"2129":1,"2130":2,"2131":1,"2132":2,"2147":2,"2176":2,"2297":1,"2319":2,"2320":1,"2321":4,"2326":2,"2332":1,"2333":3,"2334":1,"2337":2,"2338":2,"2540":1,"2551":2,"2649":1,"2664":1,"2731":1,"2767":1,"2803":1,"2804":1,"2829":1,"2834":1,"2844":1,"2846":2,"2847":1,"2848":1,"2851":1}}],["$100",{"2":{"1044":2}}],["$1=value",{"2":{"2332":1}}],["$1=hello",{"2":{"2321":1}}],["$1=level",{"2":{"1792":1}}],["$1=credentialid",{"2":{"1220":1,"1221":1,"1222":2}}],["$1=challengeid",{"2":{"1220":1,"1221":1,"1222":1}}],["$1=n",{"2":{"881":1}}],["$1=2",{"2":{"881":1}}],["$1=2024",{"2":{"372":1,"2846":1}}],["$1=123",{"2":{"1386":1}}],["$1=1",{"2":{"881":1}}],["$1",{"2":{"31":1,"37":4,"38":1,"39":1,"40":1,"50":1,"61":2,"71":2,"136":2,"157":1,"167":2,"168":3,"184":2,"206":2,"298":2,"310":3,"312":2,"360":2,"369":1,"370":2,"372":3,"373":2,"375":4,"376":3,"377":1,"378":12,"380":2,"382":5,"383":5,"384":1,"415":2,"466":2,"503":2,"510":2,"520":2,"565":4,"584":2,"586":3,"592":2,"612":2,"613":2,"614":4,"621":2,"622":3,"623":3,"659":1,"665":2,"722":2,"750":2,"760":1,"761":1,"762":1,"764":1,"767":1,"770":1,"771":1,"772":1,"774":1,"775":1,"787":1,"789":1,"797":2,"811":2,"826":2,"827":2,"849":4,"882":1,"886":1,"893":1,"899":1,"900":1,"902":1,"904":1,"1038":4,"1045":2,"1054":1,"1056":2,"1059":1,"1062":3,"1070":3,"1073":2,"1102":1,"1142":2,"1150":2,"1154":3,"1179":5,"1197":2,"1217":5,"1220":3,"1221":3,"1222":4,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1239":1,"1368":2,"1371":4,"1372":2,"1373":2,"1375":3,"1376":2,"1378":1,"1379":1,"1386":10,"1387":2,"1391":3,"1395":5,"1396":2,"1398":8,"1405":2,"1410":2,"1412":3,"1458":2,"1473":1,"1501":1,"1503":1,"1504":3,"1505":1,"1547":2,"1567":3,"1650":1,"1651":2,"1655":2,"1663":1,"1664":4,"1683":1,"1684":1,"1687":1,"1689":4,"1698":1,"1727":1,"1738":1,"1792":28,"1800":1,"1805":2,"1806":1,"1810":1,"1852":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1893":8,"2010":4,"2011":1,"2012":2,"2123":2,"2128":2,"2129":1,"2130":2,"2131":1,"2132":2,"2147":2,"2176":2,"2221":1,"2283":2,"2297":1,"2319":2,"2320":5,"2321":5,"2322":2,"2323":3,"2326":2,"2329":1,"2332":5,"2333":15,"2334":5,"2335":5,"2336":3,"2337":3,"2338":2,"2339":6,"2342":3,"2348":6,"2354":1,"2383":1,"2540":2,"2551":2,"2649":1,"2664":1,"2731":1,"2733":2,"2734":1,"2762":2,"2767":1,"2774":6,"2803":1,"2804":1,"2813":2,"2829":3,"2834":2,"2844":1,"2846":2,"2847":3,"2848":1,"2851":1,"2852":1,"2855":1}}],["5th",{"2":{"1792":1}}],["54329",{"2":{"1792":1,"2111":1,"2532":1,"2875":1}}],["5432",{"2":{"1616":1,"1792":1,"2111":1,"2161":1,"2532":1,"2875":1}}],["54ms",{"2":{"1297":2,"1301":1}}],["545",{"2":{"1289":1}}],["54",{"2":{"1289":1,"1299":1,"1301":1}}],["540",{"2":{"1267":1,"1284":1}}],["512",{"2":{"1657":1,"2402":1}}],["518",{"2":{"1299":1}}],["514",{"2":{"1299":1}}],["511",{"2":{"1291":1}}],["519",{"2":{"1290":1}}],["515",{"2":{"1284":1,"1285":1,"1289":1}}],["51",{"2":{"1278":1,"1288":1}}],["572",{"2":{"2621":1}}],["576",{"2":{"1991":1}}],["57014",{"2":{"1669":1,"1673":1,"1674":1,"1678":1,"1792":1,"2255":6}}],["577",{"2":{"1295":1}}],["578",{"2":{"1290":1}}],["573",{"2":{"1269":1}}],["57",{"0":{"1595":1},"2":{"1155":1,"1290":1,"1297":1}}],["57p02",{"2":{"1153":1,"1587":1,"1595":1,"1792":1}}],["57p01",{"2":{"1153":1,"1155":1,"1587":1,"1595":1,"1792":1}}],["57p03",{"2":{"576":1,"577":1,"1152":3,"1153":1,"1154":1,"1155":1,"1177":1,"1587":1,"1595":1,"1597":1,"1598":1,"1617":1,"1622":1,"1624":1,"1625":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["58030",{"2":{"1587":1,"1595":1,"1792":1}}],["58000",{"2":{"1587":1,"1595":1,"1792":1}}],["583",{"2":{"1291":1,"1299":1}}],["583ms",{"2":{"1268":1}}],["587",{"2":{"1288":1}}],["58",{"0":{"1595":1},"2":{"1262":1,"1264":1,"1277":1,"1288":1,"1290":1,"1295":1,"1297":1,"1792":1}}],["58ms",{"2":{"1090":1,"1289":1,"1295":1,"1299":1}}],["588",{"2":{"1007":1,"1084":1,"1090":2,"1263":1,"1264":1,"1267":1,"1272":1,"1280":1,"1284":1,"1289":1,"2398":1}}],["52428800",{"2":{"1995":1}}],["526",{"2":{"1289":1}}],["52",{"2":{"1287":1,"1290":1,"1295":1,"1301":1,"2397":1}}],["527",{"2":{"1090":1,"1272":1,"1284":1,"1287":1}}],["52ms",{"2":{"1074":1,"1288":2,"1293":1,"2526":1,"2860":1}}],["5x",{"2":{"1090":4,"1267":3}}],["5xx",{"2":{"424":1,"2307":1}}],["59ms",{"2":{"1288":1,"1291":1}}],["594",{"2":{"1288":1}}],["593",{"2":{"1269":1}}],["59",{"2":{"956":4,"1044":2,"1091":1,"1277":3,"1291":1,"1374":4,"2452":2}}],["5b",{"2":{"930":1}}],["5a",{"2":{"930":1}}],["567",{"2":{"1301":1}}],["5678",{"2":{"772":1,"773":2}}],["56",{"2":{"1290":1,"1293":2,"1609":1,"2659":1}}],["56ms",{"2":{"1289":1}}],["565",{"2":{"1289":1}}],["563",{"2":{"1267":1}}],["560972",{"2":{"919":10}}],["553",{"2":{"1301":1}}],["557",{"2":{"1297":1}}],["55ms",{"2":{"1293":1}}],["558",{"2":{"1285":1}}],["55p03",{"2":{"1152":1,"1153":1,"1155":1,"1587":1,"1596":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["550ms",{"2":{"1268":1}}],["55000",{"2":{"1153":1,"1587":1,"1596":1,"1792":1}}],["55006",{"2":{"1152":1,"1153":1,"1155":1,"1587":1,"1596":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["550",{"2":{"873":1,"1291":1}}],["55",{"0":{"1596":1},"2":{"872":1,"919":10,"1155":1,"1277":1,"1291":1,"1299":2,"1792":1,"2824":1,"2825":10}}],["555",{"2":{"333":2}}],["5×",{"2":{"871":1,"872":1}}],["536",{"2":{"1991":1}}],["53ms",{"2":{"1288":1}}],["535",{"2":{"1285":1,"1293":1}}],["539",{"2":{"1264":1,"1287":1,"1299":1}}],["53400",{"2":{"1153":1,"1155":1,"1587":1,"1594":1,"1792":1}}],["53200",{"2":{"1153":1,"1587":1,"1594":1,"1792":1}}],["53100",{"2":{"1153":1,"1587":1,"1594":1,"1792":1}}],["53000",{"2":{"1153":1,"1155":1,"1587":1,"1594":1,"1792":1}}],["53",{"0":{"1594":1},"2":{"867":1,"1155":1,"1287":1,"1301":1,"1792":1,"2824":1}}],["53300",{"2":{"576":1,"577":1,"1152":2,"1153":1,"1154":1,"1587":1,"1594":1,"1597":1,"1598":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["5d",{"2":{"274":1}}],["5h",{"2":{"274":1,"280":1,"2212":1}}],["508",{"2":{"1299":1}}],["501",{"2":{"1297":1}}],["50ms",{"2":{"1288":1,"1299":1,"2535":1}}],["506",{"2":{"1287":1}}],["509",{"2":{"1285":1,"1301":1,"1651":1,"1659":1,"1661":1,"1792":2,"2297":1,"2565":2}}],["507",{"2":{"1284":1}}],["50+",{"2":{"834":1}}],["50",{"0":{"1295":1,"1297":1,"1301":1},"2":{"658":1,"659":1,"773":2,"869":5,"911":1,"1010":1,"1169":4,"1171":2,"1189":2,"1192":2,"1255":2,"1258":2,"1267":1,"1270":2,"1277":2,"1285":3,"1322":1,"1349":2,"1366":3,"1382":1,"1442":1,"2146":2,"2270":1,"2397":1,"2465":2,"2537":1,"2789":1,"2848":1}}],["502",{"2":{"424":1,"2307":1,"2813":1}}],["503",{"2":{"213":2,"214":1,"1032":3,"1104":1,"1105":1,"1301":1,"1739":1,"1740":2,"1741":1,"1742":1,"1766":1,"1767":1,"1770":1,"1782":1,"1792":2,"1958":2,"2287":1,"2288":2,"2289":1,"2290":1,"2470":1,"2472":1,"2634":2,"2765":2}}],["504",{"0":{"2756":1},"2":{"140":2,"424":1,"1285":1,"1669":1,"1672":2,"1678":1,"1792":2,"1928":1,"2253":1,"2255":3,"2307":1,"2398":1,"2549":1,"2813":1}}],["5004",{"2":{"1792":1}}],["5003",{"2":{"1792":1}}],["5002",{"2":{"1792":1}}],["5001",{"2":{"1792":1,"1984":1,"1986":1,"1987":1,"1988":1,"1989":1,"1995":2}}],["500+",{"2":{"872":1,"877":1,"1281":1}}],["500",{"0":{"1091":1,"1291":1},"2":{"271":1,"274":2,"301":1,"436":1,"869":3,"871":1,"873":1,"874":1,"876":1,"903":1,"968":1,"1026":2,"1037":1,"1111":1,"1164":1,"1169":5,"1171":1,"1181":2,"1255":3,"1258":1,"1262":1,"1264":1,"1265":1,"1268":2,"1269":1,"1271":1,"1284":1,"1285":1,"1299":1,"1402":1,"2089":2,"2242":1,"2271":1,"2384":1,"2398":3,"2405":1}}],["500mb",{"2":{"969":1,"1169":1}}],["500milliseconds",{"2":{"133":1}}],["500msec",{"2":{"133":1}}],["500ms",{"2":{"133":1,"271":1,"1165":1,"1590":1,"1792":1,"2156":1,"2212":1,"2542":1}}],["50000000",{"2":{"1810":1,"2804":1}}],["5000",{"2":{"38":1,"40":1,"61":1,"62":2,"85":1,"1792":1,"1984":1,"1995":2}}],["5seconds",{"2":{"269":1}}],["5sec",{"2":{"269":2}}],["5s",{"2":{"136":2,"213":5,"269":2,"277":1,"1030":1,"1032":1,"1105":1,"1165":1,"1740":4,"1742":2,"1769":1,"2060":1,"2288":4,"2290":2,"2765":2}}],["5months",{"2":{"280":1}}],["5ms",{"2":{"274":1}}],["5minutes",{"2":{"133":1,"272":1}}],["5min",{"2":{"133":1,"1792":1,"2212":2}}],["5m",{"2":{"92":1,"95":1,"119":1,"133":1,"213":1,"214":4,"271":1,"274":1,"278":1,"279":2,"1143":2,"1179":1,"1430":1,"1532":1,"1740":1,"1743":2,"1792":1,"2094":1,"2101":1,"2205":1,"2212":1,"2288":1,"2502":2,"2537":1,"2765":2}}],["5",{"0":{"95":1,"862":1,"944":1,"1004":1,"1217":1,"1251":1,"1361":1,"1392":1,"2235":1,"2544":1,"2609":1,"2623":1},"1":{"863":1,"864":1,"865":1,"2610":1,"2611":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1},"2":{"31":1,"92":1,"106":2,"107":2,"255":2,"268":1,"271":1,"272":1,"273":2,"274":1,"275":2,"280":2,"310":1,"408":2,"469":2,"577":3,"747":1,"772":1,"844":1,"865":1,"867":1,"869":7,"871":1,"872":7,"873":1,"874":2,"893":1,"911":2,"913":13,"916":1,"917":1,"919":8,"930":1,"967":1,"980":1,"990":1,"1044":2,"1067":3,"1069":2,"1071":1,"1076":1,"1098":1,"1101":1,"1147":1,"1149":1,"1150":1,"1152":1,"1153":1,"1154":3,"1161":2,"1177":5,"1183":1,"1185":2,"1187":1,"1188":3,"1189":1,"1192":5,"1193":1,"1196":1,"1199":2,"1200":1,"1202":1,"1203":2,"1207":2,"1214":1,"1227":1,"1232":1,"1234":1,"1254":1,"1255":1,"1257":2,"1269":2,"1272":1,"1277":1,"1285":2,"1288":2,"1291":2,"1293":4,"1295":4,"1316":1,"1320":1,"1329":1,"1335":1,"1337":1,"1339":1,"1342":1,"1366":1,"1373":2,"1382":1,"1386":2,"1414":1,"1429":2,"1430":1,"1453":1,"1454":1,"1458":2,"1511":1,"1515":2,"1519":1,"1520":1,"1521":1,"1523":1,"1525":2,"1529":1,"1587":1,"1589":1,"1590":3,"1597":3,"1598":2,"1625":1,"1633":2,"1676":1,"1677":1,"1740":1,"1763":1,"1764":1,"1769":2,"1773":3,"1778":1,"1792":17,"1827":1,"1866":2,"1877":1,"1893":1,"1954":2,"1960":1,"2011":1,"2046":1,"2047":1,"2060":1,"2123":1,"2125":1,"2164":1,"2165":2,"2235":1,"2236":1,"2257":1,"2267":1,"2274":1,"2277":2,"2279":1,"2288":1,"2354":1,"2375":2,"2377":1,"2380":2,"2385":1,"2386":3,"2407":1,"2481":1,"2531":1,"2551":1,"2554":2,"2569":1,"2580":1,"2588":5,"2621":3,"2622":1,"2634":2,"2635":2,"2665":2,"2745":1,"2833":1,"2869":1}}],["3d",{"2":{"2588":1,"2589":1}}],["3rd",{"2":{"1394":1}}],["3ms",{"2":{"1349":1}}],["396",{"2":{"1299":1}}],["39ms",{"2":{"1295":1,"1299":1}}],["399",{"2":{"1295":1}}],["391",{"2":{"1291":1}}],["392",{"2":{"1287":1}}],["395",{"2":{"1287":1}}],["397",{"2":{"1285":1}}],["345",{"2":{"1297":1}}],["34",{"2":{"1297":1,"1609":1,"2659":1}}],["34ms",{"2":{"1287":1,"1291":1,"1301":1}}],["342",{"2":{"1284":1,"1290":1}}],["347",{"2":{"1281":2}}],["33ms",{"2":{"1295":1,"1301":1}}],["335",{"2":{"1293":1}}],["330",{"2":{"1284":1}}],["331",{"2":{"1284":2,"1290":2}}],["36ms",{"2":{"1299":1}}],["365",{"2":{"1295":1}}],["366",{"2":{"1295":1}}],["363",{"2":{"1291":1,"1295":1}}],["361",{"2":{"1287":1,"1293":1}}],["362",{"2":{"1284":2,"1289":1}}],["36",{"2":{"1268":1,"1277":1,"1287":1,"1291":1}}],["3600",{"2":{"275":2,"1138":1,"1455":1,"1646":1,"2554":1}}],["32700",{"2":{"1824":1}}],["32768",{"2":{"1792":1,"1990":1}}],["32+chars",{"2":{"1458":2,"2375":2}}],["323",{"2":{"1301":1}}],["329",{"2":{"1290":1}}],["32602",{"2":{"1824":1}}],["32601",{"2":{"1824":1}}],["326",{"2":{"1285":1,"1299":1}}],["321",{"2":{"1277":1}}],["324",{"2":{"1269":1,"1285":1,"1295":1}}],["32",{"2":{"1053":1,"1062":1,"1152":1,"1214":4,"1232":3,"1234":1,"1255":1,"1287":1,"1453":1,"1454":1,"1625":1,"1792":3,"1882":1,"1991":2,"2175":1,"2435":1,"2554":2,"2737":1,"2823":1}}],["3226",{"2":{"1023":1}}],["3x",{"2":{"2359":1}}],["3xbvw23yn6j8b8srqmoerovsylfosxurry0g",{"2":{"1051":1}}],["3xam",{"2":{"927":2}}],["3+",{"2":{"930":1,"1097":1,"1322":1}}],["381",{"2":{"1289":1}}],["389",{"2":{"1288":1}}],["385",{"2":{"1285":1}}],["388",{"2":{"1284":1}}],["38",{"2":{"1277":1,"1287":1,"1301":1,"2621":1}}],["386",{"2":{"1277":1}}],["384",{"2":{"1243":1,"1293":1}}],["380",{"2":{"887":2,"1267":1}}],["387",{"2":{"852":1,"1290":1}}],["37ms",{"2":{"1291":1,"1293":1}}],["374",{"2":{"1290":1}}],["378",{"2":{"1290":1}}],["376",{"2":{"1287":1}}],["375",{"2":{"1272":1,"1284":1}}],["37",{"2":{"1091":1,"1268":1,"1288":1,"1291":1,"1295":1}}],["370",{"2":{"873":1,"1289":1}}],["377",{"2":{"852":1,"1267":1,"1272":1,"1284":3,"1289":1,"1290":1,"2398":1}}],["3×",{"2":{"872":2}}],["359",{"2":{"1301":1}}],["35ms",{"2":{"1297":1}}],["355",{"2":{"1291":1}}],["352",{"2":{"1290":1,"1293":1}}],["351",{"2":{"1284":1,"1290":1}}],["353",{"2":{"1284":1}}],["35",{"2":{"872":1,"874":1,"1277":1,"1427":1}}],["31t18",{"2":{"1408":1}}],["31t12",{"2":{"995":1}}],["319",{"2":{"1297":1}}],["31ms",{"2":{"1293":1,"1299":1}}],["313",{"2":{"1290":1,"1299":1}}],["317",{"2":{"1288":1}}],["314",{"2":{"1285":1}}],["31",{"2":{"372":2,"675":1,"956":2,"1023":1,"1067":1,"1277":1,"1289":1,"1290":1,"1374":2,"1400":1,"1714":1,"2319":1,"2845":1,"2846":2}}],["30m",{"2":{"2207":1}}],["30ms",{"2":{"1291":1,"1301":1}}],["30mb",{"2":{"1086":1,"1117":1,"2792":1}}],["302",{"2":{"1291":1}}],["307",{"2":{"1284":1,"1290":1}}],["309",{"2":{"1269":1}}],["30+",{"2":{"872":1}}],["305",{"2":{"868":3}}],["300s",{"2":{"2212":1}}],["3001",{"2":{"1335":1,"1338":1,"1340":1,"1433":1,"2808":1,"2814":1}}],["30000000",{"2":{"1792":2,"1800":1,"1804":2,"1990":1}}],["3000",{"2":{"1118":2,"1320":1}}],["300",{"2":{"835":1,"869":4,"873":2,"876":1,"894":1,"911":1,"1037":1,"1113":1,"1169":2,"1181":2,"1366":1,"1410":1,"1616":1}}],["30seconds",{"2":{"133":1}}],["30sec",{"2":{"133":1}}],["30s",{"2":{"133":1,"207":1,"211":3,"214":2,"279":1,"1034":1,"1426":1,"1430":1,"1431":1,"1726":1,"1731":2,"1733":1,"1743":1,"1769":1,"1775":1,"1792":5,"1837":1,"1917":1,"2060":1,"2093":1,"2094":2,"2101":1,"2193":3,"2212":1,"2253":2,"2264":3,"2502":1,"2537":2,"2581":1,"2591":2,"2634":1,"2635":1,"2762":3,"2765":1}}],["30",{"0":{"2560":1},"1":{"2561":1,"2562":1},"2":{"101":1,"133":1,"134":1,"211":3,"213":1,"268":1,"271":1,"272":1,"273":1,"274":2,"275":1,"430":1,"455":1,"577":1,"869":6,"911":1,"956":2,"977":2,"980":2,"990":2,"1034":2,"1154":1,"1254":1,"1255":2,"1259":1,"1277":2,"1287":1,"1295":1,"1322":1,"1340":1,"1366":3,"1374":2,"1382":1,"1429":1,"1447":1,"1451":1,"1454":1,"1458":1,"1462":1,"1464":1,"1520":1,"1523":1,"1534":1,"1597":1,"1598":1,"1698":1,"1731":6,"1740":1,"1792":14,"1800":1,"1804":3,"1837":1,"1863":1,"1916":1,"1917":1,"1931":1,"1990":1,"1991":2,"1992":1,"1995":1,"2067":1,"2212":3,"2238":1,"2253":2,"2264":2,"2288":1,"2308":1,"2375":1,"2376":2,"2380":2,"2427":1,"2549":1,"2765":2,"2814":2,"2824":1}}],["3",{"0":{"850":1,"942":1,"958":1,"1002":1,"1021":1,"1066":1,"1069":1,"1215":1,"1222":1,"1249":1,"1309":1,"1357":1,"1390":1,"1449":1,"1605":1,"1727":1,"1872":1,"1908":1,"1993":1,"2221":1,"2222":1,"2223":1,"2224":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":2,"2238":1,"2239":1,"2240":1,"2254":1,"2275":1,"2473":1,"2519":1,"2540":1,"2560":1,"2573":1,"2578":1,"2601":1,"2643":1,"2824":1},"1":{"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1909":1,"1910":1,"1911":1,"2276":1,"2277":1,"2278":1,"2279":1,"2474":1,"2475":1,"2476":1,"2477":1,"2561":1,"2562":1,"2574":1,"2575":1,"2576":1,"2577":1,"2579":1,"2580":1,"2581":1,"2582":1,"2602":1,"2603":1,"2604":1,"2644":1,"2645":1},"2":{"19":1,"20":1,"22":1,"35":1,"74":3,"213":1,"310":1,"317":2,"347":2,"408":2,"412":1,"415":1,"469":2,"559":1,"581":1,"618":1,"701":1,"823":1,"852":1,"865":1,"869":2,"871":1,"872":2,"873":1,"876":1,"885":3,"888":2,"892":1,"897":2,"898":1,"904":1,"911":3,"913":5,"916":1,"917":2,"918":4,"919":4,"921":1,"922":2,"924":7,"925":2,"926":3,"928":4,"929":3,"930":19,"932":3,"934":5,"935":3,"936":3,"937":7,"938":6,"941":1,"947":1,"948":1,"956":2,"966":1,"971":1,"977":1,"979":2,"986":3,"988":2,"990":1,"997":1,"1026":3,"1032":1,"1037":5,"1038":1,"1042":2,"1053":1,"1066":1,"1067":1,"1068":1,"1069":1,"1070":2,"1073":5,"1076":2,"1077":1,"1078":1,"1082":5,"1090":3,"1094":2,"1097":1,"1098":1,"1102":1,"1117":1,"1152":2,"1157":1,"1181":1,"1191":1,"1193":1,"1255":5,"1257":5,"1265":3,"1270":1,"1272":1,"1277":2,"1284":3,"1285":7,"1287":5,"1288":9,"1289":6,"1290":3,"1291":4,"1293":7,"1295":6,"1297":7,"1299":3,"1301":8,"1322":1,"1368":2,"1371":3,"1374":2,"1375":1,"1379":2,"1382":1,"1384":1,"1385":2,"1405":1,"1407":1,"1420":2,"1427":1,"1429":2,"1430":1,"1431":1,"1434":1,"1453":2,"1458":1,"1464":3,"1522":1,"1569":2,"1609":2,"1617":1,"1622":1,"1623":1,"1639":1,"1644":1,"1701":2,"1740":1,"1759":2,"1762":2,"1773":2,"1775":1,"1780":1,"1785":1,"1792":12,"1801":1,"1802":1,"1813":2,"1824":1,"1840":2,"1850":1,"1852":1,"1856":2,"1866":2,"1875":1,"1912":2,"1924":2,"1925":2,"1948":1,"1955":1,"1958":1,"1993":1,"2014":2,"2045":2,"2056":2,"2111":1,"2113":1,"2144":2,"2164":1,"2165":2,"2171":1,"2220":1,"2224":2,"2225":4,"2234":1,"2236":1,"2237":2,"2238":1,"2239":1,"2240":1,"2245":1,"2254":1,"2288":1,"2303":1,"2328":1,"2376":3,"2378":2,"2379":1,"2381":1,"2383":1,"2389":1,"2391":1,"2398":4,"2410":1,"2419":1,"2421":1,"2430":1,"2437":1,"2438":1,"2440":1,"2442":1,"2455":2,"2481":1,"2528":1,"2532":1,"2534":1,"2535":3,"2540":1,"2550":1,"2555":2,"2569":1,"2571":2,"2576":2,"2586":1,"2588":9,"2590":1,"2591":2,"2622":1,"2719":1,"2721":1,"2731":1,"2739":1,"2752":1,"2760":1,"2765":1,"2789":2,"2790":2,"2791":2,"2794":1,"2801":1,"2815":1,"2823":1,"2824":3,"2825":2,"2836":1,"2840":1,"2841":1,"2844":1,"2845":1,"2864":1,"2875":1,"2879":1,"2880":1}}],["=rule",{"2":{"1792":1,"2141":1,"2575":1}}],["=converted",{"2":{"1792":1,"2141":1,"2575":1}}],["=original",{"2":{"1792":1,"2141":1,"2575":1}}],["=`",{"2":{"1574":1}}],["=$",{"2":{"1574":2}}],["==",{"2":{"995":2,"996":2,"1366":4,"1386":1,"1408":1,"1409":1,"1416":2,"1567":1,"2247":2,"2452":2,"2562":1,"2566":1}}],["===",{"2":{"894":2,"1335":8,"1342":1,"1361":1,"1366":1,"1410":2,"2040":1,"2476":1,"2562":1}}],["=>",{"2":{"62":2,"666":1,"723":1,"894":4,"961":2,"1026":1,"1107":1,"1317":3,"1318":1,"1320":9,"1321":1,"1361":1,"1366":3,"1410":4,"1413":1,"1416":4,"1431":3,"1442":1,"1572":2,"1573":1,"1574":3,"1792":3,"2247":4,"2655":1,"2830":1,"2836":1}}],["=",{"0":{"2335":1},"2":{"16":2,"18":1,"19":1,"20":1,"30":2,"33":2,"35":1,"37":4,"38":1,"39":2,"40":1,"41":1,"45":2,"48":2,"49":1,"50":2,"60":1,"61":1,"62":1,"79":2,"115":2,"116":1,"133":2,"136":2,"155":4,"157":4,"158":1,"159":1,"162":1,"167":2,"168":1,"184":2,"186":1,"215":3,"216":1,"254":1,"255":2,"256":2,"257":2,"288":2,"292":1,"298":2,"301":2,"302":1,"308":2,"309":1,"310":6,"312":2,"313":2,"366":1,"370":5,"373":2,"374":1,"378":5,"387":2,"393":1,"395":1,"396":1,"401":1,"405":1,"406":2,"414":1,"415":2,"423":1,"426":2,"427":1,"428":1,"429":1,"436":1,"452":1,"462":4,"463":3,"464":4,"466":2,"467":3,"468":3,"469":2,"520":2,"527":4,"528":1,"531":1,"532":4,"534":1,"535":1,"542":1,"565":4,"570":2,"592":3,"611":1,"612":1,"613":1,"614":4,"621":1,"622":6,"623":2,"637":2,"646":3,"664":1,"665":3,"666":2,"673":1,"674":3,"677":2,"678":3,"679":7,"689":1,"691":2,"695":1,"700":2,"705":1,"710":1,"711":1,"715":1,"718":1,"719":8,"720":1,"722":5,"723":8,"724":2,"750":1,"751":2,"755":1,"756":5,"758":3,"764":2,"767":2,"770":1,"771":2,"773":1,"774":3,"775":3,"777":2,"780":1,"783":1,"785":4,"787":2,"789":2,"798":3,"811":2,"812":2,"815":3,"826":1,"835":1,"845":1,"849":8,"851":1,"864":1,"868":1,"886":3,"887":1,"888":1,"894":8,"896":1,"898":3,"899":2,"900":3,"902":1,"903":3,"904":5,"914":2,"916":4,"918":2,"928":5,"929":4,"930":8,"933":1,"934":2,"935":1,"936":4,"938":1,"941":1,"945":2,"949":1,"956":2,"957":8,"959":1,"960":4,"961":7,"964":3,"965":1,"967":1,"971":1,"979":1,"980":2,"982":1,"986":1,"988":1,"989":2,"990":8,"991":2,"994":1,"995":3,"996":15,"1017":2,"1021":16,"1024":2,"1026":22,"1033":2,"1038":1,"1042":1,"1045":2,"1054":1,"1055":2,"1056":5,"1057":2,"1058":2,"1060":7,"1063":3,"1067":1,"1068":1,"1074":1,"1076":3,"1078":2,"1094":1,"1097":3,"1098":1,"1099":1,"1102":1,"1104":1,"1105":4,"1107":4,"1111":1,"1113":1,"1114":1,"1135":2,"1141":2,"1142":2,"1150":1,"1154":1,"1179":1,"1185":1,"1188":1,"1192":1,"1197":3,"1202":3,"1214":3,"1215":1,"1216":5,"1217":4,"1218":3,"1220":1,"1221":2,"1222":3,"1232":7,"1233":1,"1234":5,"1235":4,"1236":2,"1237":3,"1239":5,"1279":3,"1284":2,"1308":2,"1309":2,"1316":2,"1317":8,"1318":8,"1320":11,"1321":4,"1326":3,"1332":1,"1335":12,"1338":6,"1339":13,"1342":3,"1347":1,"1354":1,"1357":3,"1358":18,"1360":3,"1361":3,"1364":2,"1366":24,"1368":2,"1371":1,"1372":5,"1374":4,"1375":1,"1376":15,"1379":1,"1386":8,"1387":2,"1390":1,"1391":2,"1393":2,"1394":2,"1395":3,"1396":2,"1398":3,"1399":1,"1405":1,"1408":5,"1409":2,"1410":12,"1412":4,"1413":6,"1414":2,"1415":5,"1416":7,"1419":1,"1427":3,"1429":1,"1431":3,"1442":2,"1458":2,"1471":1,"1480":4,"1481":2,"1504":7,"1523":2,"1561":2,"1565":1,"1567":1,"1568":1,"1569":1,"1571":1,"1572":2,"1573":2,"1574":3,"1575":1,"1582":1,"1655":1,"1664":2,"1688":2,"1689":2,"1696":3,"1738":4,"1743":1,"1753":2,"1792":45,"1822":1,"1823":1,"1851":1,"1882":2,"1883":1,"1884":2,"1885":2,"1886":1,"1887":3,"2010":2,"2011":1,"2012":1,"2040":1,"2049":2,"2075":2,"2076":2,"2077":2,"2078":1,"2079":6,"2094":3,"2098":1,"2148":15,"2176":2,"2177":1,"2178":1,"2179":3,"2183":3,"2184":1,"2185":1,"2187":4,"2193":8,"2221":1,"2247":9,"2251":1,"2252":5,"2255":17,"2256":2,"2257":2,"2265":4,"2267":1,"2277":7,"2283":5,"2285":4,"2292":1,"2293":1,"2303":1,"2310":1,"2313":1,"2320":4,"2321":1,"2322":2,"2330":7,"2333":1,"2335":5,"2338":1,"2339":5,"2342":3,"2354":1,"2359":2,"2360":2,"2372":1,"2382":1,"2424":1,"2431":4,"2436":2,"2437":2,"2438":2,"2452":1,"2455":1,"2461":2,"2476":1,"2481":2,"2490":2,"2502":1,"2526":1,"2527":1,"2528":3,"2530":2,"2531":1,"2534":1,"2535":3,"2537":6,"2540":6,"2546":1,"2575":11,"2591":8,"2622":1,"2635":3,"2648":1,"2649":3,"2651":1,"2652":1,"2653":6,"2655":2,"2656":3,"2664":3,"2733":1,"2739":1,"2768":2,"2774":5,"2775":1,"2811":1,"2813":2,"2815":3,"2829":4,"2830":3,"2833":4,"2834":3,"2836":5,"2845":3,"2848":1,"2849":1,"2860":1,"2861":2,"2862":1,"2864":3,"2865":1,"2866":2,"2868":1,"2869":1,"2871":1,"2873":1,"2876":1}}],["vb",{"2":{"2792":1}}],["vbilopav",{"2":{"1117":1,"1343":2,"1420":1,"1773":1,"1775":1,"2550":3,"2576":2,"2717":1,"2788":4,"2789":4,"2790":4,"2791":4}}],["vpc",{"2":{"1712":1}}],["vllm",{"2":{"1335":1}}],["v4",{"2":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["v0",{"2":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["v5",{"2":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["vcpus",{"2":{"1255":1}}],["v",{"2":{"1139":2,"1343":2,"2451":2,"2567":1,"2717":1,"2785":1,"2786":1,"2788":1,"2789":1,"2790":1,"2791":1}}],["v=2",{"2":{"1139":2}}],["v=1",{"2":{"1139":2}}],["v8",{"2":{"1107":1}}],["vms",{"2":{"2576":1,"2779":1,"2790":1}}],["vm",{"2":{"1094":1}}],["vus",{"2":{"1254":1,"1255":4,"1258":1}}],["vu",{"0":{"1091":1,"1265":1},"2":{"1084":2,"1258":1,"1262":1,"1264":4,"1266":1,"1267":4,"1268":2,"1269":3,"1270":2,"1271":1,"1272":2,"1284":2,"1285":5,"2398":1}}],["vulnerabilities",{"2":{"1064":1,"1792":1,"2014":1,"2632":2}}],["vulnerability",{"2":{"933":2}}],["v6",{"2":{"1019":1,"1023":1,"1026":1,"1032":1,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"2764":2,"2766":1}}],["v3+json",{"2":{"1736":1}}],["v3",{"0":{"1397":1,"2241":1,"2243":1,"2260":1,"2262":1,"2268":1,"2275":1,"2280":1,"2298":1,"2311":1,"2315":1,"2373":1,"2387":1,"2408":1,"2418":1,"2439":1,"2449":1,"2458":1,"2467":1,"2473":1,"2478":1,"2499":1,"2507":1,"2514":1,"2524":1,"2547":1,"2552":1,"2556":1,"2560":1,"2563":1,"2568":1,"2570":1,"2573":1,"2578":1,"2583":1,"2592":1,"2598":1,"2601":1,"2605":1,"2609":1,"2612":1,"2616":1,"2619":1,"2623":1,"2630":1,"2636":1,"2639":1,"2643":1,"2646":1,"2657":1,"2675":1},"1":{"1398":1,"1399":1,"2242":1,"2244":1,"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1,"2261":1,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2276":1,"2277":1,"2278":1,"2279":1,"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2312":1,"2313":1,"2314":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2419":1,"2420":1,"2421":1,"2422":1,"2423":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2429":1,"2430":1,"2431":1,"2432":1,"2433":1,"2434":1,"2435":1,"2436":1,"2437":1,"2438":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2474":1,"2475":1,"2476":1,"2477":1,"2479":1,"2480":1,"2481":1,"2482":1,"2483":1,"2484":1,"2485":1,"2486":1,"2487":1,"2488":1,"2489":1,"2490":1,"2491":1,"2492":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2498":1,"2500":1,"2501":1,"2502":1,"2503":1,"2504":1,"2505":1,"2506":1,"2508":1,"2509":1,"2510":1,"2511":1,"2512":1,"2513":1,"2515":1,"2516":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1,"2525":1,"2526":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2539":1,"2540":1,"2541":1,"2542":1,"2543":1,"2544":1,"2545":1,"2546":1,"2548":1,"2549":1,"2550":1,"2551":1,"2553":1,"2554":1,"2555":1,"2557":1,"2558":1,"2559":1,"2561":1,"2562":1,"2564":1,"2565":1,"2566":1,"2567":1,"2569":1,"2571":1,"2572":1,"2574":1,"2575":1,"2576":1,"2577":1,"2579":1,"2580":1,"2581":1,"2582":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1,"2602":1,"2603":1,"2604":1,"2606":1,"2607":1,"2608":1,"2610":1,"2611":1,"2613":1,"2614":1,"2615":1,"2617":1,"2618":1,"2620":1,"2621":1,"2622":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2637":1,"2638":1,"2640":1,"2641":1,"2642":1,"2644":1,"2645":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"919":1,"975":1,"1019":1,"1023":1,"1026":1,"1038":1,"1066":2,"1071":1,"1072":1,"1086":1,"1096":1,"1263":1,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"1368":1,"1380":1,"1384":1,"1397":1,"1398":2,"1420":1,"1691":1,"1792":2,"2013":1,"2221":1,"2222":3,"2223":1,"2224":4,"2225":3,"2226":1,"2227":1,"2228":1,"2229":2,"2230":1,"2231":1,"2232":1,"2233":1,"2234":4,"2235":1,"2236":9,"2237":2,"2238":7,"2239":4,"2240":2,"2515":1,"2766":1,"2859":2,"2882":1}}],["vnd",{"2":{"772":1,"773":1,"893":1,"1736":1}}],["v20",{"2":{"1695":2,"1792":2}}],["v2",{"2":{"403":2,"1606":1,"1691":1,"1692":4,"1694":2,"1792":7,"2215":2,"2385":1,"2438":1,"2689":1,"2691":1,"2697":2,"2727":1}}],["vscurrencies",{"2":{"1026":7}}],["vscurrenciescsv",{"2":{"1024":2}}],["vscurrenciescsv=usd",{"2":{"1023":1}}],["vs",{"0":{"330":1,"609":1,"831":1,"893":1,"968":1,"1067":2,"1083":2,"1167":1,"1272":1,"1319":1,"1323":1,"1363":1,"1378":1,"1387":1,"1390":1,"1457":1,"2087":1,"2204":1,"2858":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"969":1,"1084":2,"1085":2,"1086":2,"1087":2,"1088":2,"1089":2,"1090":2,"1091":2,"1092":2,"1093":2,"1094":2,"1095":2,"1096":2,"1097":2,"1098":2,"1099":2,"1100":2,"1101":2,"1102":2,"1103":2,"1104":2,"1105":2,"1106":2,"1107":2,"1108":2,"1109":2,"1110":2,"1111":2,"1112":2,"1113":2,"1114":2,"1115":2,"1116":2,"1117":2,"1118":2,"1119":2,"1120":2,"1121":2,"1122":2,"1123":2,"1124":2,"1125":2,"1126":2,"1127":2,"1320":1,"1321":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1},"2":{"226":1,"385":1,"587":1,"833":1,"836":1,"868":1,"873":1,"930":1,"1019":2,"1021":1,"1023":1,"1026":1,"1037":3,"1058":1,"1086":1,"1094":1,"1121":1,"1278":1,"1376":2,"1382":2,"1383":2,"1398":4,"1417":1,"1757":1,"1840":1,"1864":1,"1894":2,"1910":1,"2184":1,"2193":1,"2265":1,"2347":1,"2370":1,"2398":2,"2421":1,"2433":1,"2438":1,"2607":2,"2621":1,"2766":3,"2789":1}}],["vacuously",{"2":{"2528":1,"2864":1}}],["vacuum",{"2":{"966":1,"1792":1,"2050":1,"2635":2}}],["vanished",{"2":{"2157":1,"2543":1}}],["vanish",{"2":{"2110":1,"2530":1,"2866":1}}],["vast",{"2":{"1401":1}}],["vault",{"2":{"1100":1}}],["vaughn",{"2":{"851":3,"863":2}}],["varchar",{"2":{"447":1,"841":2,"952":1,"1922":1,"2258":1,"2394":2}}],["vars",{"2":{"390":1,"395":1,"2040":1,"2224":1,"2474":1}}],["var",{"2":{"390":3,"393":1,"894":1,"926":1,"1366":6,"1376":5,"1395":2,"1396":2,"1604":2,"1607":1,"1608":1,"1654":1,"1792":1,"1810":1,"2040":1,"2132":1,"2185":1,"2272":1,"2297":1,"2451":1,"2456":1,"2476":1,"2705":1,"2719":2,"2757":1,"2804":2}}],["varies",{"2":{"2398":1,"2445":1}}],["variations",{"0":{"2196":1}}],["variadic",{"2":{"1092":1}}],["variants",{"2":{"1380":1,"2389":1,"2457":1,"2498":1,"2859":1}}],["variant",{"2":{"531":1,"1433":1,"2550":1,"2789":2,"2790":2,"2791":2}}],["variables",{"0":{"390":1,"766":1,"1540":1,"1615":1,"1862":1,"2184":1,"2483":1,"2687":1,"2719":1},"1":{"1541":1,"1542":1,"1543":1,"2688":1,"2689":1,"2690":1},"2":{"306":1,"316":1,"388":1,"390":1,"728":1,"737":1,"766":1,"805":1,"926":1,"1067":1,"1394":2,"1457":1,"1475":2,"1538":1,"1540":1,"1551":1,"1574":1,"1604":2,"1606":1,"1607":1,"1608":2,"1661":1,"1785":1,"1787":1,"1792":7,"1794":1,"2170":1,"2184":1,"2185":1,"2188":1,"2223":2,"2272":3,"2483":1,"2572":1,"2680":1,"2681":1,"2687":1,"2689":2,"2697":1,"2705":1,"2772":1}}],["variable",{"0":{"502":1,"1606":1,"1607":1,"2040":1,"2497":1,"2689":1,"2690":1},"2":{"212":1,"306":1,"390":5,"394":1,"395":1,"499":1,"534":1,"926":1,"1394":1,"1395":2,"1416":1,"1475":3,"1540":1,"1604":1,"1605":3,"1615":1,"1661":1,"1738":1,"1792":15,"1848":1,"1849":1,"1862":2,"2038":3,"2040":3,"2184":1,"2223":1,"2224":1,"2474":1,"2476":3,"2497":4,"2540":1,"2543":1,"2645":2,"2681":1,"2687":3,"2688":3,"2689":1,"2690":1,"2697":1,"2705":3,"2764":1,"2768":1,"2845":1}}],["various",{"2":{"378":1,"1239":1,"1385":3,"1386":1,"2264":1,"2333":1,"2389":1}}],["vary",{"2":{"214":1,"1084":1,"1138":1,"2502":1}}],["valuable",{"2":{"974":1}}],["valuetask",{"2":{"2461":1,"2559":1}}],["value=",{"2":{"938":2,"1061":3,"1491":1,"1792":1,"2039":1}}],["valued",{"2":{"690":1}}],["value2",{"2":{"646":1,"650":1}}],["value2>",{"2":{"14":1,"637":1}}],["value1",{"2":{"646":1,"650":1}}],["value1>",{"2":{"14":1,"637":1}}],["value4|value5|value6",{"2":{"491":1}}],["value>",{"2":{"155":1,"370":10,"537":1}}],["value",{"0":{"32":1,"46":1,"80":1,"134":1,"379":1,"386":1,"533":1,"541":1,"1429":1,"2335":1,"2395":1,"2493":1},"1":{"33":1,"34":1,"35":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1},"2":{"22":1,"50":1,"51":1,"52":1,"58":1,"74":1,"75":1,"80":1,"81":1,"88":1,"106":1,"107":3,"133":1,"154":1,"155":3,"156":3,"158":1,"159":1,"162":1,"168":1,"170":1,"186":1,"187":7,"188":2,"203":1,"210":1,"212":2,"215":1,"240":1,"268":1,"297":2,"301":1,"302":1,"303":2,"304":2,"306":1,"309":1,"357":1,"358":2,"361":1,"362":2,"377":1,"378":4,"379":3,"383":3,"384":1,"386":2,"387":4,"388":2,"390":2,"395":2,"396":2,"408":2,"427":1,"436":1,"447":1,"448":1,"452":1,"454":3,"458":1,"460":2,"463":2,"466":1,"467":1,"468":1,"479":1,"499":1,"512":1,"515":1,"517":1,"527":2,"528":1,"529":3,"533":1,"534":4,"535":2,"540":1,"544":1,"551":1,"553":1,"583":1,"585":1,"586":1,"613":1,"615":1,"629":1,"638":1,"646":1,"675":1,"720":1,"760":1,"761":2,"763":1,"770":1,"771":2,"773":1,"775":1,"780":1,"801":1,"816":4,"827":1,"841":1,"848":1,"851":1,"873":1,"883":1,"885":1,"903":1,"948":1,"951":1,"956":1,"983":2,"1017":2,"1041":1,"1045":1,"1067":2,"1069":2,"1070":2,"1134":1,"1138":1,"1150":2,"1162":2,"1199":1,"1228":1,"1229":1,"1230":1,"1318":2,"1341":1,"1374":1,"1382":1,"1395":2,"1398":1,"1423":1,"1460":1,"1477":1,"1480":2,"1491":1,"1499":1,"1500":1,"1520":1,"1523":2,"1524":1,"1525":2,"1526":1,"1529":2,"1540":1,"1544":1,"1569":1,"1574":4,"1582":2,"1605":3,"1620":1,"1628":1,"1656":1,"1657":1,"1664":11,"1685":1,"1688":2,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1706":1,"1722":1,"1728":1,"1732":1,"1733":2,"1738":2,"1759":1,"1769":1,"1792":71,"1818":1,"1819":2,"1824":2,"1825":1,"1830":2,"1849":1,"1851":1,"1854":2,"1855":1,"1856":3,"1862":1,"1878":1,"1879":1,"1880":1,"1912":1,"1917":1,"1918":1,"1922":1,"1925":4,"1927":1,"1948":1,"1951":2,"1952":2,"1953":2,"1954":2,"1955":2,"1956":1,"1957":3,"1958":1,"1960":1,"1968":3,"2017":1,"2018":1,"2019":1,"2023":1,"2024":1,"2025":1,"2038":1,"2040":3,"2060":1,"2096":1,"2107":1,"2140":7,"2156":1,"2164":1,"2180":2,"2181":1,"2183":2,"2185":1,"2193":5,"2202":3,"2222":1,"2226":1,"2247":1,"2252":2,"2256":2,"2259":1,"2264":4,"2266":1,"2267":2,"2277":1,"2283":3,"2284":3,"2293":1,"2294":7,"2296":2,"2310":1,"2326":1,"2329":1,"2333":6,"2335":1,"2339":1,"2348":2,"2372":3,"2375":1,"2378":1,"2379":5,"2380":8,"2382":1,"2395":5,"2402":1,"2405":1,"2427":2,"2428":1,"2432":1,"2435":1,"2437":1,"2443":1,"2445":1,"2451":7,"2455":1,"2456":1,"2463":3,"2466":1,"2470":1,"2476":4,"2481":2,"2483":3,"2497":3,"2517":2,"2518":1,"2520":1,"2528":1,"2529":1,"2534":1,"2537":2,"2540":1,"2542":1,"2544":1,"2546":1,"2549":1,"2575":7,"2581":1,"2591":4,"2595":1,"2632":1,"2634":1,"2635":1,"2638":1,"2648":1,"2665":2,"2681":1,"2682":2,"2687":1,"2688":3,"2689":1,"2697":1,"2725":1,"2733":1,"2734":4,"2763":1,"2764":1,"2768":1,"2771":2,"2812":1,"2814":1,"2845":1,"2848":2,"2852":1,"2864":1,"2865":1}}],["values",{"0":{"81":1,"156":1,"274":1,"279":1,"286":1,"377":1,"460":1,"490":1,"499":1,"517":1,"551":1,"629":1,"638":1,"644":1,"798":1,"1500":1,"1526":1,"1854":1,"1855":1,"1862":1,"2333":1,"2517":1,"2577":1,"2682":1,"2768":1,"2848":1},"1":{"157":1,"378":1,"379":1,"380":1,"381":1},"2":{"22":1,"25":1,"34":2,"41":2,"87":1,"88":2,"116":1,"156":1,"182":2,"184":2,"188":3,"212":1,"213":1,"214":1,"237":2,"248":1,"253":1,"267":1,"286":4,"292":1,"308":1,"310":1,"337":1,"348":1,"360":2,"361":1,"365":1,"368":1,"377":1,"387":1,"388":1,"390":1,"395":1,"404":1,"439":1,"448":1,"452":1,"458":2,"460":1,"462":1,"470":1,"488":1,"490":1,"535":1,"544":2,"556":1,"582":1,"589":1,"595":1,"641":1,"644":1,"675":1,"680":1,"715":1,"725":1,"760":1,"761":3,"764":1,"765":1,"766":1,"768":1,"770":1,"771":3,"774":1,"776":3,"786":1,"788":3,"790":1,"801":6,"809":1,"813":1,"814":1,"826":1,"843":2,"852":3,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"888":3,"891":1,"892":3,"898":1,"904":1,"913":3,"915":1,"934":1,"938":1,"956":1,"963":1,"977":2,"989":1,"990":1,"992":1,"994":1,"997":1,"1016":1,"1033":1,"1051":1,"1054":1,"1074":1,"1079":1,"1100":1,"1102":2,"1142":1,"1150":1,"1214":1,"1215":2,"1232":1,"1234":1,"1239":1,"1255":1,"1307":1,"1309":1,"1321":1,"1332":1,"1338":1,"1339":1,"1360":1,"1370":1,"1372":1,"1374":1,"1376":1,"1378":1,"1393":1,"1395":1,"1419":1,"1442":1,"1449":1,"1460":1,"1470":1,"1471":1,"1475":2,"1477":4,"1481":1,"1517":1,"1523":2,"1526":1,"1547":2,"1557":1,"1582":1,"1590":1,"1604":2,"1605":1,"1607":2,"1608":1,"1620":1,"1645":1,"1655":1,"1664":3,"1689":2,"1703":1,"1723":1,"1733":1,"1738":1,"1740":1,"1755":1,"1792":46,"1823":1,"1824":2,"1844":1,"1852":2,"1853":1,"1854":2,"1856":1,"1857":1,"1862":2,"1917":1,"1923":1,"1924":2,"1926":1,"1928":1,"1958":2,"2009":1,"2016":1,"2037":1,"2039":2,"2040":5,"2056":1,"2089":1,"2128":1,"2129":1,"2130":3,"2131":1,"2140":1,"2147":2,"2149":1,"2183":2,"2200":2,"2210":1,"2216":1,"2222":1,"2223":1,"2224":3,"2258":1,"2261":1,"2264":2,"2265":4,"2272":1,"2277":1,"2282":3,"2286":1,"2288":1,"2291":2,"2292":1,"2296":3,"2297":1,"2330":1,"2333":1,"2335":1,"2338":1,"2348":1,"2372":1,"2375":1,"2377":1,"2380":3,"2383":2,"2394":2,"2397":1,"2405":1,"2428":1,"2432":1,"2435":3,"2436":2,"2450":2,"2452":1,"2455":1,"2468":1,"2470":1,"2474":2,"2476":2,"2477":1,"2483":2,"2493":2,"2496":1,"2497":2,"2502":1,"2509":1,"2510":1,"2526":1,"2544":1,"2549":3,"2551":3,"2572":2,"2575":3,"2577":4,"2586":2,"2588":1,"2589":1,"2595":3,"2597":1,"2632":5,"2633":1,"2645":2,"2674":1,"2678":1,"2681":1,"2684":1,"2687":2,"2689":1,"2692":1,"2695":1,"2712":2,"2719":1,"2723":1,"2726":1,"2739":1,"2750":1,"2759":1,"2779":1,"2798":1,"2803":1,"2812":4,"2815":1,"2822":1,"2829":2,"2836":1,"2850":1,"2855":4,"2860":1,"2868":1,"2869":1}}],["value3>",{"2":{"14":1}}],["val2",{"2":{"383":1,"584":5,"2337":4,"2348":1}}],["val1",{"2":{"383":1,"584":5,"2337":4,"2348":1}}],["val$1",{"2":{"382":1,"2334":1}}],["val",{"2":{"334":1,"378":1,"679":3,"723":3,"956":10,"1374":10,"1974":1,"2333":1,"2607":1,"2852":2}}],["validaudience",{"2":{"1825":1}}],["validating",{"2":{"1487":1,"1792":2,"2137":1,"2575":1}}],["validationmode",{"2":{"2415":1}}],["validationtype",{"2":{"2148":3,"2575":2}}],["validationrule",{"2":{"2148":3,"2575":2}}],["validationrule>",{"2":{"2148":1,"2575":1}}],["validationresult",{"2":{"1026":2}}],["validationalgorithm",{"2":{"1650":1,"1651":1,"1657":1,"1663":1,"1792":1}}],["validationoptions",{"0":{"2446":1},"2":{"809":1,"817":2,"819":1,"820":1,"1792":1,"1948":1,"2138":1,"2142":1,"2144":1,"2145":1,"2146":1,"2148":2,"2225":1,"2378":1,"2384":1,"2440":1,"2442":2,"2446":1,"2448":1,"2575":3}}],["validation",{"0":{"109":1,"382":1,"811":1,"897":1,"1460":1,"1527":1,"1609":1,"1657":1,"2137":1,"2140":1,"2145":1,"2334":1,"2428":1,"2575":1,"2659":1,"2661":1,"2696":1},"1":{"2138":1,"2139":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1},"2":{"31":1,"37":1,"51":1,"54":1,"66":1,"225":1,"226":1,"747":3,"779":1,"781":2,"807":1,"809":1,"812":1,"815":1,"816":1,"817":1,"818":3,"819":1,"820":1,"822":2,"852":2,"864":1,"865":1,"868":3,"869":4,"873":1,"879":1,"880":1,"892":1,"910":2,"979":1,"1037":1,"1044":1,"1067":1,"1069":1,"1071":1,"1073":2,"1086":1,"1099":2,"1100":1,"1109":1,"1150":1,"1197":1,"1220":1,"1225":1,"1382":1,"1390":1,"1407":1,"1422":1,"1454":1,"1489":2,"1501":1,"1609":2,"1651":1,"1657":1,"1788":2,"1792":13,"1795":2,"1825":2,"1830":1,"1875":1,"2137":2,"2139":2,"2140":1,"2141":3,"2142":1,"2143":1,"2146":1,"2147":3,"2148":1,"2149":4,"2150":1,"2152":1,"2232":1,"2237":1,"2375":1,"2380":1,"2384":1,"2389":2,"2409":1,"2412":1,"2414":1,"2415":1,"2417":1,"2445":1,"2446":2,"2447":2,"2472":1,"2481":1,"2492":1,"2575":7,"2627":1,"2659":2,"2661":1,"2664":1,"2666":1,"2679":1,"2705":2}}],["validator",{"2":{"1026":1,"1027":1,"2410":1,"2411":1,"2412":2,"2415":1,"2440":1,"2441":1,"2442":1,"2443":1}}],["validateparametersasync",{"2":{"2615":1}}],["validateconfigkeys=error",{"2":{"2416":1}}],["validateconfigkeys",{"0":{"2414":1},"1":{"2415":1,"2416":1},"2":{"1603":1,"1604":1,"1609":2,"1792":1,"2225":2,"2389":1,"2414":2,"2416":2,"2441":1,"2442":1,"2445":1,"2659":2}}],["validatedashboardrequest",{"2":{"1026":2}}],["validated",{"0":{"38":1,"2410":1},"1":{"2411":1,"2412":1,"2413":1},"2":{"382":1,"1080":1,"1197":1,"1382":1,"1460":1,"1504":4,"1717":1,"1792":2,"1957":1,"2225":2,"2334":1,"2375":1,"2379":1,"2411":2,"2413":1,"2446":1,"2481":1,"2754":1,"2840":1}}],["validatesigncount",{"2":{"1227":1,"1792":1,"1877":1,"1893":1}}],["validates",{"0":{"1243":1,"2441":1,"2445":1,"2446":1},"1":{"2442":1,"2443":1,"2444":1},"2":{"30":1,"781":1,"786":1,"975":1,"982":1,"984":1,"986":1,"1045":1,"1080":1,"1243":3,"1358":2,"1378":1,"1436":1,"1618":1,"1621":1,"1792":2,"1824":1,"2171":1,"2444":1,"2445":1,"2669":1,"2679":1,"2696":1}}],["validate",{"0":{"807":1,"2414":1,"2669":1},"1":{"808":1,"809":1,"810":1,"811":1,"812":1,"813":1,"814":1,"815":1,"816":1,"817":1,"818":1,"819":1,"820":1,"821":1,"822":1,"2415":1,"2416":1},"2":{"29":1,"50":1,"60":1,"226":1,"564":2,"565":3,"747":3,"768":1,"807":1,"808":2,"809":3,"811":2,"812":2,"813":3,"814":2,"815":1,"817":2,"868":1,"869":1,"897":2,"1003":1,"1196":1,"1197":1,"1227":1,"1303":1,"1332":1,"1366":3,"1386":1,"1406":1,"1439":1,"1454":4,"1504":1,"1604":1,"1609":2,"1717":1,"1792":8,"1877":1,"2126":2,"2127":2,"2143":1,"2147":7,"2150":1,"2152":1,"2225":2,"2320":5,"2323":2,"2357":1,"2414":1,"2415":4,"2416":1,"2417":1,"2440":1,"2444":1,"2554":4,"2575":5,"2581":1,"2669":2,"2774":1,"2785":2}}],["validity",{"2":{"1447":1,"1459":1,"1792":1,"2375":1,"2377":1}}],["valid",{"2":{"14":1,"37":1,"38":4,"39":1,"40":1,"51":1,"101":1,"113":1,"212":1,"252":1,"382":2,"637":1,"747":1,"817":1,"819":1,"843":1,"855":1,"863":1,"864":2,"865":1,"904":1,"1227":1,"1360":1,"1386":1,"1394":3,"1396":1,"1410":1,"1580":1,"1621":1,"1792":6,"1857":1,"1877":1,"1983":1,"2020":6,"2056":1,"2142":1,"2144":2,"2146":3,"2148":1,"2187":1,"2192":1,"2200":1,"2326":1,"2334":2,"2413":1,"2415":1,"2491":1,"2495":1,"2498":1,"2529":1,"2575":2,"2589":1,"2634":1,"2674":1,"2678":1,"2865":1}}],["v1=",{"2":{"2438":1}}],["v14",{"2":{"1090":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["v1",{"2":{"207":1,"390":1,"394":1,"436":2,"452":3,"531":1,"924":3,"1044":1,"1050":1,"1105":1,"1107":1,"1138":1,"1287":3,"1288":3,"1289":3,"1290":3,"1291":3,"1293":3,"1295":3,"1297":3,"1299":3,"1301":3,"1335":3,"1347":1,"1726":1,"1733":2,"1773":1,"1842":1,"2062":1,"2215":2,"2264":3,"2438":1,"2483":1,"2719":1,"2768":1,"2880":1}}],["vocabulary",{"2":{"1435":1}}],["vocal",{"2":{"847":1}}],["volume",{"2":{"872":1,"1654":1,"1792":1,"2157":1,"2297":1}}],["volumes",{"2":{"847":1}}],["voluntarily",{"2":{"865":1}}],["volatility",{"2":{"175":1,"683":2,"684":1,"708":1,"1792":1}}],["volatile",{"0":{"1129":1},"2":{"175":4,"179":1,"180":1,"245":1,"684":2,"686":1,"1037":1,"1128":1,"1129":3,"1792":1,"1847":1,"2197":1,"2858":1}}],["void",{"0":{"285":1,"288":1,"586":1,"823":1,"827":1,"2338":2,"2853":1},"1":{"824":1,"825":1,"826":1,"827":1,"828":1,"829":1,"830":1},"2":{"18":1,"72":1,"157":1,"184":1,"187":1,"227":1,"285":1,"288":1,"292":1,"299":1,"351":1,"436":1,"438":1,"451":2,"453":1,"567":1,"582":2,"583":1,"586":3,"587":9,"588":2,"614":1,"646":1,"658":1,"659":1,"665":2,"761":1,"771":1,"823":1,"824":2,"826":3,"827":1,"828":1,"829":1,"894":1,"934":1,"983":1,"994":1,"1105":1,"1317":1,"1331":1,"1337":2,"1345":2,"1366":1,"1372":2,"1410":1,"1416":1,"1424":1,"1427":1,"1429":1,"1558":1,"1664":1,"1824":1,"1920":1,"2186":1,"2247":1,"2258":1,"2292":1,"2294":1,"2313":2,"2320":2,"2337":6,"2338":5,"2339":1,"2357":2,"2549":1,"2580":2,"2802":1,"2809":1,"2812":1,"2829":1,"2834":2,"2853":1,"2854":3,"2859":1}}],["vite",{"0":{"1581":1},"2":{"1416":1,"1422":1,"1574":2}}],["vibe",{"2":{"1402":1}}],["video",{"2":{"1323":1}}],["videos",{"2":{"849":1}}],["violation",{"2":{"992":1,"1111":2,"1593":1,"1792":1}}],["virginia",{"2":{"913":1}}],["virtually",{"2":{"845":1,"848":1}}],["virtual",{"0":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"2322":1,"2849":1},"2":{"165":1,"168":2,"238":1,"1255":1,"2323":1,"2859":1}}],["visitor",{"2":{"1430":1}}],["visit",{"2":{"1059":1,"1380":1,"1792":5,"2162":1}}],["visibility",{"2":{"868":1}}],["visible",{"0":{"1043":1},"2":{"390":1,"668":1,"874":1,"943":1,"945":1,"1015":1,"1069":1,"1792":1,"1851":1,"2185":1,"2382":1,"2384":1,"2393":1,"2416":1,"2537":1,"2577":1}}],["visualstudio",{"2":{"1792":1}}],["visually",{"2":{"1123":1,"1382":1}}],["visual",{"2":{"908":1,"1086":1,"1088":1,"1094":4,"1115":1,"1123":2,"1127":2,"1751":1,"1757":1,"2193":1,"2581":1}}],["visualization",{"2":{"866":1,"872":1,"1037":1,"1572":1,"1573":1}}],["vice",{"2":{"841":1}}],["vietnam",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"840":1}}],["view=aspnetcore",{"2":{"1792":6}}],["viewing",{"2":{"965":1,"2075":1,"2651":1}}],["viewer",{"2":{"691":1}}],["view",{"0":{"1096":1},"2":{"351":3,"959":1,"966":1,"970":1,"1068":1,"1084":1,"1114":1,"1122":1,"1125":1,"1126":1,"1127":1,"1385":4,"1439":1,"1792":1,"2220":1,"2729":1,"2760":1}}],["views",{"0":{"2729":1},"2":{"351":4,"957":1,"967":1,"1074":1,"1095":2,"1096":3,"1100":1,"1127":1,"2184":1,"2317":1,"2344":1,"2351":1,"2388":1,"2389":1,"2432":1,"2531":1,"2590":1,"2607":1,"2710":1}}],["via",{"0":{"646":1,"711":1,"1196":1},"2":{"41":1,"63":1,"101":1,"134":1,"167":1,"168":1,"173":1,"210":1,"212":1,"223":1,"261":2,"309":1,"320":1,"327":1,"336":1,"337":1,"377":1,"379":1,"390":1,"408":1,"426":1,"428":1,"458":1,"469":1,"480":1,"499":1,"502":1,"515":1,"529":1,"567":1,"639":1,"650":1,"655":1,"663":1,"669":1,"679":2,"711":1,"720":1,"723":1,"737":3,"745":1,"746":2,"752":1,"759":1,"766":1,"769":1,"801":1,"805":1,"829":1,"868":2,"875":1,"881":1,"902":1,"907":1,"964":1,"1033":1,"1049":1,"1050":1,"1059":1,"1094":1,"1097":1,"1098":5,"1100":2,"1101":2,"1102":2,"1104":3,"1105":3,"1107":1,"1111":1,"1115":1,"1135":2,"1137":1,"1139":1,"1150":2,"1167":1,"1204":1,"1276":1,"1304":2,"1309":1,"1320":1,"1338":1,"1370":1,"1372":1,"1413":1,"1420":1,"1447":1,"1475":1,"1511":1,"1519":1,"1529":1,"1576":1,"1632":1,"1649":1,"1689":2,"1704":1,"1732":1,"1746":1,"1747":2,"1792":10,"1802":1,"1825":1,"1840":1,"1843":1,"1849":1,"1909":1,"1923":1,"1926":1,"1929":1,"1930":1,"1957":1,"2018":1,"2040":1,"2079":1,"2097":1,"2111":1,"2122":1,"2124":2,"2125":1,"2128":1,"2130":1,"2164":1,"2167":1,"2175":1,"2185":1,"2187":2,"2190":2,"2221":1,"2222":2,"2223":1,"2227":1,"2252":1,"2264":1,"2265":1,"2282":1,"2284":1,"2297":1,"2309":1,"2318":1,"2320":2,"2321":1,"2322":1,"2324":2,"2333":2,"2334":1,"2344":1,"2346":1,"2347":2,"2372":1,"2379":1,"2380":1,"2381":1,"2392":1,"2405":1,"2413":1,"2456":1,"2461":1,"2481":1,"2490":1,"2496":1,"2506":1,"2509":1,"2513":1,"2522":1,"2532":1,"2536":1,"2537":1,"2545":2,"2549":1,"2572":1,"2587":2,"2611":1,"2621":1,"2653":1,"2656":1,"2664":1,"2665":1,"2670":1,"2691":1,"2719":1,"2772":1,"2774":1,"2795":1,"2816":1,"2823":1,"2835":1,"2840":1,"2842":1,"2850":1,"2876":1}}],["vectorized",{"2":{"2270":1}}],["vector",{"2":{"2270":1}}],["vendor",{"2":{"1792":3}}],["vendors",{"2":{"848":1,"859":1,"918":1}}],["ventured",{"2":{"1403":1}}],["velocity",{"2":{"1385":1}}],["ve",{"2":{"177":1,"838":1,"877":1,"934":1,"947":1,"1382":1,"1404":1,"1409":1,"2391":2}}],["vedran",{"2":{"2":1,"1096":1}}],["verdict",{"2":{"859":1}}],["vernon",{"2":{"851":3,"863":3,"865":1}}],["versus",{"2":{"852":1}}],["versa",{"2":{"841":1}}],["versioning",{"0":{"2215":1},"2":{"2438":2,"2727":1}}],["versioned",{"0":{"403":1},"2":{"924":3,"925":1,"1385":1}}],["versions",{"0":{"2710":1},"2":{"175":1,"872":1,"1013":1,"1096":1,"1157":1,"1254":1,"1263":1,"1272":1,"1792":1,"2220":1,"2607":1,"2668":1,"2680":1,"2710":1,"2785":2,"2786":2,"2788":1}}],["version",{"0":{"1257":1,"2221":1,"2222":1,"2223":1,"2224":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":1,"2238":1,"2239":1,"2240":1,"2242":1,"2244":1,"2245":1,"2261":1,"2263":1,"2269":1,"2276":1,"2281":1,"2299":1,"2312":1,"2316":1,"2374":1,"2388":1,"2409":1,"2419":1,"2440":1,"2450":1,"2459":1,"2468":1,"2474":1,"2479":1,"2500":1,"2508":1,"2515":1,"2525":1,"2548":1,"2553":1,"2557":1,"2561":1,"2564":1,"2569":1,"2571":1,"2574":1,"2579":1,"2584":1,"2593":1,"2599":1,"2602":1,"2606":1,"2610":1,"2613":1,"2617":1,"2620":1,"2624":1,"2631":1,"2637":1,"2640":1,"2644":1,"2647":1,"2658":1,"2668":1,"2676":1,"2711":1},"1":{"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1,"2264":1,"2265":1,"2266":1,"2267":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2277":1,"2278":1,"2279":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2313":1,"2314":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2549":1,"2550":1,"2551":1,"2554":1,"2555":1,"2558":1,"2559":1,"2562":1,"2565":1,"2566":1,"2567":1,"2572":1,"2575":1,"2576":1,"2577":1,"2580":1,"2581":1,"2582":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2594":1,"2595":1,"2596":1,"2597":1,"2600":1,"2603":1,"2604":1,"2607":1,"2608":1,"2611":1,"2614":1,"2615":1,"2618":1,"2621":1,"2622":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2632":1,"2633":1,"2634":1,"2635":1,"2638":1,"2641":1,"2642":1,"2645":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1,"2677":1,"2678":1,"2679":1},"2":{"1":1,"7":2,"19":1,"20":1,"22":1,"203":1,"317":1,"327":1,"347":1,"408":1,"412":1,"469":1,"559":1,"581":1,"618":1,"823":1,"837":1,"838":1,"843":1,"852":1,"854":1,"855":1,"860":2,"911":1,"920":1,"924":1,"946":1,"966":1,"971":1,"1017":1,"1036":1,"1065":1,"1073":3,"1086":1,"1102":1,"1113":1,"1127":1,"1139":1,"1208":1,"1247":1,"1253":1,"1255":1,"1257":2,"1327":1,"1335":1,"1336":1,"1338":1,"1339":1,"1351":1,"1367":1,"1368":1,"1378":1,"1384":1,"1398":1,"1402":2,"1405":1,"1407":1,"1453":1,"1582":2,"1609":1,"1701":1,"1728":1,"1762":1,"1785":1,"1792":11,"1800":1,"1807":1,"1808":2,"1813":1,"1819":1,"1824":3,"1866":1,"1898":1,"2014":1,"2045":1,"2056":1,"2116":2,"2117":2,"2119":4,"2220":1,"2221":1,"2222":1,"2223":1,"2224":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":1,"2238":1,"2239":1,"2240":1,"2245":3,"2254":1,"2261":3,"2264":1,"2389":2,"2438":3,"2440":1,"2448":1,"2481":2,"2529":1,"2550":2,"2555":1,"2567":2,"2569":1,"2571":2,"2576":1,"2586":1,"2607":1,"2668":2,"2679":2,"2701":2,"2702":2,"2704":4,"2713":1,"2779":1,"2785":2,"2786":1,"2788":1,"2789":3,"2790":2,"2791":2,"2819":1,"2823":1,"2824":2,"2825":1}}],["verbosity",{"2":{"2628":1,"2629":1}}],["verbose",{"0":{"2824":1},"2":{"631":1,"1792":10,"1800":4,"1801":2,"1802":2,"1803":2,"1804":2,"1805":2,"1806":1,"1807":2,"1863":1,"2104":1,"2108":1,"2364":1,"2536":2,"2544":2,"2750":2,"2751":1,"2794":1,"2795":1,"2798":2,"2799":1,"2800":1,"2803":1,"2824":1,"2825":1,"2880":2}}],["verb",{"0":{"1924":1,"2843":1},"2":{"868":1,"2222":1,"2319":2,"2509":1,"2513":1,"2811":1,"2843":1}}],["verbatim",{"2":{"324":1,"388":1,"436":1,"452":1,"1368":1,"1408":1,"1566":1,"1924":2,"2451":1,"2509":1,"2512":1,"2529":1,"2726":1,"2865":1}}],["very",{"2":{"277":1,"843":1,"847":1,"848":1,"911":1,"918":1,"930":3,"1104":1,"1139":1,"1254":1,"1386":1,"1393":1,"1401":1,"1402":1,"1404":1,"2376":1,"2532":1}}],["verifiable",{"2":{"2175":1}}],["verifies",{"2":{"298":1,"309":2,"364":1,"368":1,"453":1,"1049":1,"1055":3,"1210":1,"1215":1,"1216":1,"1243":1,"1410":1,"1770":1,"2187":1,"2435":1,"2438":1}}],["verified",{"2":{"0":1,"41":1,"63":1,"366":1,"1046":1,"1214":1,"1220":1,"1253":1,"1870":1,"2453":1,"2465":1,"2481":1,"2546":2}}],["verification",{"0":{"307":1,"310":1,"1055":1,"1056":1,"1473":1},"1":{"308":1,"309":1,"310":1,"1056":1},"2":{"33":1,"63":1,"298":1,"300":1,"305":1,"310":6,"312":2,"942":1,"1037":1,"1048":3,"1049":2,"1055":1,"1056":10,"1062":2,"1064":3,"1098":1,"1209":1,"1211":1,"1214":2,"1220":2,"1228":3,"1235":1,"1236":3,"1237":1,"1238":1,"1239":1,"1243":1,"1248":1,"1252":1,"1471":2,"1472":5,"1480":1,"1792":6,"1867":1,"1868":2,"1870":1,"1878":3,"1882":1,"1885":1,"1886":1,"1887":1,"1888":1,"2164":1,"2165":1,"2177":2,"2181":1,"2342":3}}],["verifytoken",{"2":{"1320":1}}],["verifychallengecommand",{"0":{"1235":1,"1885":1},"2":{"1792":1,"1893":1}}],["verifying",{"0":{"2177":1},"2":{"307":1,"1211":1,"1792":1}}],["verify",{"0":{"308":1,"929":1},"2":{"1":2,"298":4,"307":2,"308":4,"309":1,"313":1,"922":1,"929":2,"930":22,"934":1,"988":1,"989":1,"990":3,"1068":1,"1217":1,"1220":1,"1221":1,"1222":1,"1228":1,"1230":2,"1232":1,"1235":1,"1366":1,"1371":1,"1382":1,"1792":7,"1878":1,"1880":2,"1893":1,"2176":2,"2177":6,"2187":1,"2456":1,"2627":3}}],["u8",{"2":{"2399":1}}],["u3",{"2":{"1792":1,"1800":1,"1809":2,"1810":1}}],["uv",{"2":{"1792":4}}],["u2",{"2":{"1333":2}}],["u1",{"2":{"1333":1}}],["uri=",{"2":{"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1697":1,"1792":5}}],["uri",{"2":{"1111":1,"1671":1,"1696":1,"1792":2,"1830":1,"1925":1,"1994":1,"2255":1,"2781":1}}],["url=jdbc",{"2":{"2874":1}}],["url=>url`",{"2":{"1792":1}}],["urlpath",{"0":{"1817":1},"2":{"1792":4,"1814":1,"1830":1,"1831":1,"1897":1,"1898":1,"1907":1,"1911":1,"2254":1,"2434":1,"2481":2}}],["urlpathprefix=",{"2":{"1606":1,"2689":1,"2691":1,"2692":3,"2697":1,"2719":1}}],["urlpathprefix",{"2":{"1606":1,"1792":1,"1836":1,"1841":1,"1842":1,"1863":1,"2529":1,"2686":1,"2690":2,"2697":2,"2701":1,"2723":1,"2865":1,"2881":1}}],["urls=",{"2":{"2699":1}}],["urls",{"0":{"2118":1,"2703":1},"2":{"669":1,"876":1,"1010":1,"1105":2,"1115":1,"1199":1,"1416":3,"1417":2,"1555":1,"1563":1,"1574":5,"1575":1,"1581":3,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1733":1,"1744":1,"1787":1,"1792":6,"1794":1,"1995":1,"2116":2,"2117":4,"2118":4,"2119":3,"2264":1,"2277":1,"2576":1,"2645":1,"2701":2,"2702":4,"2703":4,"2704":3,"2712":1}}],["urlencoded",{"2":{"209":1}}],["url",{"0":{"422":1,"436":1,"446":1,"533":1,"723":1,"961":1,"1413":1,"1572":1,"1676":1,"1841":1,"1842":1,"2286":1,"2327":1,"2654":1,"2655":1,"2656":1,"2727":1,"2811":1},"1":{"1842":1,"2655":1,"2656":1},"2":{"203":1,"212":1,"214":2,"215":1,"243":1,"245":1,"253":2,"259":1,"387":2,"394":1,"395":2,"396":1,"399":1,"404":1,"408":1,"409":1,"410":1,"413":2,"414":2,"422":3,"434":2,"436":5,"446":3,"452":1,"454":1,"469":1,"517":1,"527":1,"533":1,"650":5,"663":5,"664":2,"666":2,"667":1,"669":5,"679":5,"681":1,"719":3,"720":6,"723":3,"726":1,"835":2,"872":1,"957":3,"961":3,"1017":2,"1043":1,"1047":1,"1095":1,"1104":1,"1105":1,"1107":1,"1121":1,"1139":4,"1199":1,"1200":1,"1305":2,"1309":1,"1335":4,"1340":1,"1364":1,"1374":1,"1382":1,"1407":1,"1411":1,"1413":4,"1415":4,"1416":3,"1479":2,"1521":1,"1555":1,"1561":1,"1562":1,"1572":2,"1574":1,"1576":2,"1581":2,"1670":2,"1676":1,"1684":2,"1690":1,"1696":4,"1697":2,"1728":1,"1738":1,"1741":1,"1743":1,"1759":1,"1787":1,"1792":31,"1794":1,"1807":1,"1817":1,"1828":1,"1841":2,"1844":1,"1898":1,"1900":2,"1907":1,"1911":1,"1917":4,"1925":1,"1929":3,"1961":2,"1984":2,"1986":1,"1987":1,"1988":1,"1989":1,"1995":2,"2019":6,"2034":1,"2081":2,"2146":1,"2197":1,"2233":1,"2254":3,"2255":2,"2264":1,"2270":2,"2277":2,"2278":1,"2282":1,"2286":2,"2289":1,"2301":1,"2302":2,"2304":1,"2318":1,"2346":1,"2372":1,"2380":1,"2389":2,"2391":5,"2407":2,"2434":1,"2483":1,"2493":1,"2502":2,"2513":1,"2517":1,"2520":1,"2523":1,"2555":1,"2558":1,"2597":1,"2632":2,"2648":1,"2655":2,"2656":4,"2665":1,"2699":1,"2701":1,"2724":1,"2762":1,"2764":1,"2765":1,"2767":1,"2806":1,"2808":1,"2814":1,"2828":2,"2829":1,"2830":2,"2834":1,"2838":1,"2840":1}}],["ubuntu",{"0":{"2385":1},"2":{"1071":1,"2385":2}}],["ubiquitous",{"2":{"841":1,"851":1}}],["uqqw",{"2":{"927":2}}],["utf",{"0":{"2399":1},"2":{"1685":1,"1792":1,"2309":1,"2399":1}}],["utf8null",{"2":{"2399":1}}],["utf8colon",{"2":{"2399":1}}],["utf8comma",{"2":{"2399":1}}],["utf8closebracket",{"2":{"2399":1}}],["utf8closebrace",{"2":{"2399":1}}],["utf8openbracket",{"2":{"2399":1}}],["utf8openbrace",{"2":{"2399":1}}],["utf8",{"2":{"1202":1}}],["utils",{"2":{"1582":2}}],["utility",{"2":{"2367":1,"2531":1,"2841":1}}],["utilities",{"2":{"1582":1}}],["utilization",{"2":{"1167":1,"1324":1}}],["utc",{"2":{"1021":1,"1026":1,"1376":1,"1792":5,"1806":1,"1856":5,"2224":3,"2450":4,"2451":6,"2452":3,"2453":2,"2454":6,"2455":1,"2456":7,"2803":1}}],["ut",{"2":{"922":1}}],["ux",{"2":{"872":1,"1385":1,"1386":1,"2438":1}}],["ultimately",{"2":{"871":1}}],["uid",{"2":{"1114":1,"1620":2,"1792":1}}],["uis",{"2":{"831":1,"2670":1}}],["ui",{"0":{"834":1},"2":{"352":2,"833":3,"834":4,"836":1,"837":2,"838":1,"868":1,"872":1,"996":1,"1037":1,"1038":1,"1043":1,"1123":1,"1232":2,"1318":1,"1385":1,"1386":2,"1401":1,"1405":5,"1419":1,"1792":2,"1882":2,"2395":1,"2430":1,"2432":2,"2438":1}}],["uuid4",{"2":{"1366":1}}],["uuid",{"2":{"258":1,"585":1,"1232":1,"1234":1,"1235":1,"1255":1,"1358":1,"1366":2,"1792":2,"1882":1,"1884":1,"2144":2,"2277":1}}],["unfinished",{"2":{"2531":1}}],["unfortunately",{"2":{"918":1}}],["unforgettable",{"2":{"913":1}}],["unfolds",{"2":{"865":1}}],["un",{"2":{"2267":1}}],["unbound",{"0":{"1861":1}}],["unpartitioned",{"2":{"1792":1,"1956":1,"2379":1}}],["unprotected",{"2":{"182":1,"2295":1}}],["unprotect",{"2":{"182":1,"2295":1,"2297":1,"2405":1}}],["unhandled",{"2":{"2663":1}}],["unhandledcommentlines",{"2":{"2223":1,"2482":1,"2487":1}}],["unhealthy",{"2":{"1766":1,"1782":1,"1792":2,"2634":2}}],["unhold",{"2":{"864":1}}],["unglamorous",{"2":{"1382":1}}],["unusable",{"2":{"1792":1,"1917":1,"2222":1,"2812":1}}],["unusual",{"2":{"1279":1}}],["unused",{"2":{"388":1,"1792":1}}],["unencrypted",{"2":{"1664":1,"2291":1}}],["uneven",{"2":{"1254":1}}],["unexpected",{"2":{"1171":1,"1741":1,"2289":1}}],["unexpectedly",{"2":{"1149":1}}],["unwind",{"2":{"2532":1}}],["unwrapped",{"2":{"1077":1}}],["unwanted",{"2":{"849":1,"1792":1}}],["unmatched",{"2":{"1266":1,"1280":1,"1957":1,"2379":1}}],["unmodified",{"2":{"866":1}}],["unmistakably",{"2":{"848":1}}],["unqualified",{"2":{"582":1}}],["unquoted",{"2":{"378":1,"379":2,"388":1,"2333":3}}],["unscoped",{"2":{"2498":1}}],["unspecified",{"2":{"1447":1,"1792":1,"2426":1,"2428":1,"2436":1,"2451":1}}],["unsafe",{"0":{"1130":1},"2":{"1130":1,"1792":5,"2019":1,"2020":2,"2023":1,"2024":1,"2029":1,"2632":5}}],["unsurvivable",{"2":{"2532":1}}],["unsuccessful",{"2":{"840":1}}],["unsupported",{"2":{"280":2,"1460":1,"2113":1,"2375":1,"2535":1,"2881":1}}],["unset",{"2":{"395":1,"1460":1,"1605":2,"1792":2,"2038":1,"2375":1,"2435":1,"2497":2,"2498":1,"2688":2,"2719":1}}],["untestable",{"2":{"2879":1}}],["untested",{"2":{"1792":1,"2107":1,"2537":1,"2879":1}}],["untyped",{"2":{"2496":1}}],["untrusted",{"2":{"1824":1,"1972":1,"2040":1,"2481":1}}],["untouched",{"2":{"388":1,"843":1,"1605":1,"2497":1,"2498":1,"2531":1,"2534":1,"2540":1,"2688":1,"2845":1,"2862":1,"2869":1}}],["until",{"2":{"106":1,"214":2,"310":1,"851":1,"861":1,"865":1,"992":2,"994":1,"1046":1,"1076":2,"1081":1,"1101":1,"1150":1,"1161":1,"1519":1,"1524":1,"1743":2,"2106":1,"2402":1,"2461":1,"2466":1,"2494":1,"2502":2,"2869":1,"2875":1,"2878":1}}],["uncached",{"2":{"2466":1}}],["uncomment",{"2":{"1792":1}}],["uncommitted",{"2":{"1078":1,"2099":1,"2110":1,"2527":1,"2530":1,"2739":1,"2862":1,"2881":1}}],["uncontrolled",{"2":{"843":1}}],["unconditionally",{"2":{"179":1,"687":1,"1925":1,"2353":1}}],["unconditional",{"2":{"175":1}}],["unclosed",{"2":{"389":1,"2007":1,"2328":1}}],["unchanged",{"0":{"2856":1},"2":{"362":1,"389":1,"848":2,"1069":1,"1323":1,"1368":1,"1385":1,"1569":1,"1571":1,"1759":1,"1802":1,"1912":1,"1924":1,"1957":1,"2110":1,"2193":1,"2226":1,"2379":1,"2381":1,"2391":2,"2415":1,"2416":1,"2422":2,"2435":1,"2440":1,"2481":1,"2484":1,"2491":1,"2493":1,"2496":1,"2498":1,"2513":1,"2520":1,"2530":1,"2532":1,"2540":1,"2544":1,"2545":1,"2581":1,"2809":1}}],["unintentionally",{"2":{"1401":1}}],["universal",{"0":{"2664":1},"2":{"1169":1,"2232":1}}],["unified",{"2":{"1100":1,"1355":1,"1924":1}}],["uniformly",{"2":{"2222":1,"2509":1}}],["uniform",{"2":{"868":1}}],["unicode",{"2":{"930":2,"2589":1,"2607":1}}],["union",{"2":{"852":2}}],["unimpaired",{"2":{"848":1}}],["uniquely",{"2":{"2740":1}}],["uniquemodels",{"2":{"1553":1,"1559":1,"1792":1}}],["unique",{"2":{"756":3,"757":4,"784":3,"785":1,"852":2,"864":3,"961":1,"1053":1,"1095":1,"1111":1,"1129":1,"1139":1,"1213":2,"1214":1,"1237":1,"1307":1,"1336":1,"1358":3,"1363":1,"1366":3,"1384":1,"1460":1,"1487":1,"1655":1,"1792":9,"1887":1,"1894":1,"1906":1,"2127":1,"2167":1,"2326":1,"2375":1,"2590":1,"2871":1}}],["unix",{"0":{"342":1},"2":{"342":1}}],["unitprice",{"2":{"814":1}}],["units",{"0":{"269":1},"2":{"280":1,"1378":1,"1393":1,"2210":1}}],["unit",{"0":{"275":1,"987":1},"1":{"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1},"2":{"133":2,"268":5,"269":2,"280":3,"814":2,"832":1,"840":1,"849":1,"852":1,"863":1,"865":1,"875":1,"986":1,"989":1,"1005":2,"1074":1,"1076":1,"1077":1,"1132":1,"1181":1,"1187":1,"1188":1,"1189":1,"1192":2,"1373":1,"1378":1,"2211":1,"2212":1,"2417":1,"2546":2,"2858":1}}],["unnoticed",{"2":{"1436":1}}],["unnecessarily",{"2":{"2278":1}}],["unnecessary",{"2":{"108":1,"1136":1,"1206":1,"1399":1,"1439":1,"1533":1,"2258":1,"2350":1,"2353":1,"2540":1}}],["unnest",{"2":{"1427":1,"1429":1,"2762":2}}],["unnesting",{"2":{"1097":2}}],["unnested",{"2":{"915":1}}],["unnamedsinglecolumnset",{"0":{"2009":1},"2":{"1792":1,"1999":1,"2000":1,"2009":2,"2330":2,"2357":2,"2725":1,"2841":1,"2842":1,"2851":1}}],["unnamed",{"0":{"613":1,"2326":1},"2":{"299":1,"613":1,"615":1,"2339":1}}],["unreadable",{"0":{"2757":1}}],["unreachable",{"2":{"1767":1,"1770":1,"1792":2,"2634":2}}],["unreferenced",{"2":{"2380":1}}],["unregistered",{"2":{"1792":1,"1822":1}}],["unrecoverable",{"2":{"1664":1,"2297":1}}],["unrecognized",{"0":{"252":1},"2":{"2192":1,"2412":1,"2482":1,"2544":1}}],["unremarkable",{"2":{"1410":1}}],["unrestricted",{"2":{"1015":1}}],["unresolved",{"2":{"102":1,"109":1,"1150":1,"1527":1,"2380":1,"2497":1}}],["unrelated",{"2":{"319":1,"387":1,"395":1,"874":1,"1070":1,"1792":1,"1851":1,"2156":1,"2382":1,"2392":1,"2481":1,"2542":1,"2546":2,"2878":1}}],["unable",{"2":{"1593":1,"1624":1,"1792":1}}],["unavailable",{"2":{"452":1,"1338":1,"1339":1,"1766":1,"1767":1,"1770":1,"1782":1,"1792":1,"2634":1,"2815":1}}],["unaffected",{"2":{"188":1,"347":1,"614":1,"1068":1,"1792":1,"1908":1,"2157":1,"2223":1,"2296":1,"2428":1,"2435":1,"2466":1,"2482":1,"2494":1,"2505":1}}],["unauthorizedreturntoqueryparameter",{"2":{"1792":1,"2033":1,"2034":1,"2035":1,"2042":1}}],["unauthorizedredirectpath",{"2":{"1792":1,"2033":1,"2034":1,"2035":1,"2042":1}}],["unauthorized",{"2":{"16":1,"25":1,"39":1,"60":1,"63":1,"64":1,"297":1,"298":1,"299":2,"301":1,"934":1,"1480":1,"1688":1,"1792":2,"2034":1,"2176":1,"2271":2,"2608":1,"2823":1}}],["unauthenticated",{"0":{"798":1},"2":{"4":1,"25":1,"27":1,"224":1,"298":1,"934":2,"1162":1,"1465":1,"1475":1,"1477":1,"1827":1,"2039":1,"2176":1,"2490":1}}],["undefined",{"2":{"894":2,"938":2,"995":6,"997":1,"1024":1,"1317":3,"1318":1,"1321":1,"1326":1,"1342":3,"1366":2,"1386":3,"1408":3,"1410":1,"1416":6,"1553":1,"1558":1,"1567":3,"1792":1,"2242":1,"2247":4,"2255":1,"2273":2,"2359":2}}],["underline",{"2":{"1792":1}}],["underlying",{"2":{"848":1,"874":1,"983":1,"1139":1,"1851":1,"1984":1,"2382":1,"2459":1,"2751":1}}],["underexecutionidheadername",{"2":{"1620":1}}],["underscore",{"2":{"1792":2,"1967":1}}],["underscores",{"2":{"1606":1,"2144":1,"2689":1}}],["understates",{"2":{"872":1,"873":1}}],["understands",{"2":{"2528":1,"2863":1}}],["understanding",{"0":{"932":1,"1311":1},"1":{"1312":1,"1313":1,"1314":1,"1315":1,"1316":1},"2":{"872":1,"1155":1,"1716":1,"2577":1}}],["understand",{"2":{"436":1,"1942":1,"2677":1,"2693":1}}],["underappreciated",{"2":{"974":1}}],["underneath",{"2":{"848":1,"857":1}}],["under",{"0":{"352":1,"2427":1,"2476":1},"2":{"108":1,"214":1,"352":1,"448":1,"659":1,"848":1,"852":3,"854":1,"855":1,"860":1,"864":1,"865":1,"872":1,"953":1,"1044":2,"1046":1,"1064":1,"1079":1,"1089":1,"1121":1,"1162":1,"1164":1,"1168":1,"1171":1,"1180":2,"1266":1,"1324":2,"1329":1,"1460":1,"1464":1,"1522":1,"1609":1,"1697":1,"1792":6,"1826":1,"1924":1,"1925":1,"1955":1,"2010":1,"2104":1,"2106":1,"2108":1,"2112":1,"2125":1,"2129":1,"2131":1,"2157":1,"2222":1,"2224":1,"2225":1,"2330":1,"2375":1,"2376":1,"2377":1,"2378":1,"2380":1,"2381":1,"2385":1,"2389":1,"2398":2,"2407":1,"2409":1,"2412":1,"2413":1,"2419":1,"2422":1,"2423":1,"2424":1,"2425":1,"2431":1,"2432":1,"2436":2,"2438":1,"2456":1,"2466":2,"2470":1,"2481":1,"2498":1,"2521":1,"2532":1,"2533":1,"2535":1,"2536":1,"2537":1,"2540":4,"2543":2,"2544":1,"2546":1,"2661":1,"2769":1,"2794":1,"2802":2,"2814":1,"2824":1,"2835":1,"2845":2,"2857":1,"2869":1,"2873":1,"2878":2}}],["undo",{"2":{"177":1,"989":2}}],["unlocks",{"2":{"1081":1,"2375":1,"2380":1}}],["unlimited",{"2":{"928":1,"1511":1,"1616":1,"1792":1,"1991":2,"2265":1,"2379":1}}],["unlike",{"2":{"168":1,"377":1,"762":1,"924":1,"1054":1,"1094":1,"1200":1,"1363":1,"1391":1,"1453":1,"1823":1,"1867":1,"2333":1,"2712":1,"2848":1}}],["unless",{"2":{"108":2,"745":1,"1435":1,"1569":1,"1759":1,"1840":1,"1856":1,"1912":1,"2175":1,"2181":1,"2195":1,"2389":1,"2416":1,"2520":1,"2528":2,"2537":1,"2728":1,"2863":1,"2864":1}}],["unknown",{"0":{"2754":1},"2":{"102":1,"109":1,"212":1,"388":1,"390":1,"395":1,"704":1,"996":1,"1041":1,"1150":1,"1449":1,"1527":1,"1593":1,"1609":4,"1792":4,"1824":2,"1957":1,"2106":1,"2111":1,"2178":1,"2223":1,"2324":1,"2336":1,"2348":2,"2379":1,"2380":1,"2394":1,"2410":2,"2414":1,"2416":1,"2428":1,"2441":1,"2442":1,"2481":1,"2493":2,"2496":1,"2529":1,"2532":1,"2533":4,"2537":1,"2659":4,"2663":1,"2679":1,"2696":1,"2878":1}}],["um4k594nl6pbqx2el0lcbkkladof1k9atryky+g14f6bqptsckwo6qz1wj1d9tx",{"2":{"62":2}}],["usr",{"2":{"2782":1,"2783":1,"2784":1}}],["usable",{"2":{"1792":1,"1957":1,"2098":1,"2379":1}}],["usage",{"0":{"104":1,"276":1,"332":1,"415":1,"437":1,"953":1,"1277":1,"1737":1,"2147":1,"2303":1},"1":{"277":1,"278":1,"438":1,"439":1,"1278":1},"2":{"87":1,"88":2,"912":1,"914":1,"916":2,"918":1,"919":1,"926":2,"932":1,"947":1,"961":1,"1137":1,"1168":1,"1254":1,"1278":2,"1342":1,"1349":1,"1351":1,"1401":1,"1435":1,"1511":1,"1516":1,"1517":1,"1518":1,"1573":1,"1792":3,"2089":1,"2261":2,"2265":3,"2270":1,"2277":1,"2339":1,"2549":1,"2562":1,"2572":1,"2575":1,"2580":1,"2608":1,"2721":1,"2755":2}}],["usd",{"2":{"1023":4,"1024":4,"1823":1}}],["usual",{"2":{"436":1,"448":1,"453":1,"1382":1,"1423":1,"2533":1}}],["usually",{"2":{"307":1,"683":1,"845":1,"854":1,"1302":1,"1385":1,"1394":1,"1684":1,"1792":2,"2452":1,"2454":1,"2680":1}}],["us",{"0":{"1435":1},"1":{"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1},"2":{"269":1,"845":1,"849":1,"851":1,"852":1,"860":1,"912":1,"918":1,"920":2,"1037":2,"1386":4,"1388":1,"1391":1,"1401":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1792":16,"2257":4,"2632":2,"2633":1,"2634":1}}],["using",{"0":{"138":1,"306":1,"466":1,"467":1,"662":1,"766":1,"814":1,"915":1,"1547":1,"1599":1,"1615":1,"1632":1,"1771":1,"2063":1,"2762":1},"2":{"37":1,"41":1,"51":1,"56":1,"63":1,"79":1,"121":1,"133":1,"156":1,"168":1,"182":1,"184":1,"237":1,"245":1,"253":1,"286":1,"299":1,"309":1,"312":1,"357":1,"362":1,"364":1,"385":1,"404":1,"454":1,"470":1,"479":1,"480":1,"507":1,"522":2,"544":1,"556":1,"570":1,"637":1,"646":4,"650":1,"663":1,"664":1,"665":1,"666":1,"674":1,"718":1,"746":1,"749":1,"780":1,"809":2,"811":2,"812":2,"813":3,"814":2,"815":1,"817":2,"841":1,"851":1,"864":1,"868":2,"869":1,"876":1,"881":1,"903":1,"909":1,"911":1,"914":1,"915":1,"916":5,"918":4,"919":3,"920":2,"980":1,"982":2,"990":1,"1005":1,"1007":1,"1010":1,"1026":2,"1031":1,"1037":3,"1048":1,"1050":1,"1052":1,"1054":1,"1056":2,"1076":1,"1098":4,"1114":1,"1122":1,"1138":2,"1147":1,"1159":1,"1174":1,"1178":2,"1193":1,"1205":1,"1220":1,"1243":1,"1316":2,"1318":1,"1321":1,"1338":1,"1355":2,"1366":2,"1375":1,"1377":1,"1385":1,"1390":1,"1398":1,"1400":1,"1401":3,"1402":4,"1408":1,"1414":1,"1446":1,"1450":1,"1514":1,"1516":1,"1523":1,"1528":1,"1530":1,"1532":1,"1540":1,"1544":1,"1588":1,"1599":1,"1618":1,"1626":1,"1632":1,"1641":1,"1651":3,"1655":1,"1659":1,"1661":1,"1670":1,"1688":1,"1731":1,"1739":1,"1792":44,"1799":1,"1837":1,"1851":1,"1855":1,"1858":1,"1866":1,"1870":1,"1935":1,"1947":1,"1949":1,"1952":1,"1953":1,"1968":1,"2008":1,"2040":1,"2047":1,"2056":1,"2062":1,"2063":1,"2077":1,"2126":1,"2147":6,"2148":1,"2164":3,"2165":10,"2167":1,"2183":1,"2193":2,"2195":1,"2197":1,"2202":1,"2212":1,"2223":1,"2252":2,"2253":2,"2255":1,"2256":2,"2258":1,"2261":1,"2264":3,"2265":1,"2266":1,"2267":1,"2270":1,"2271":1,"2277":1,"2282":1,"2287":1,"2291":1,"2292":1,"2297":2,"2327":1,"2353":1,"2364":1,"2365":2,"2382":1,"2389":1,"2391":1,"2413":1,"2420":1,"2438":1,"2466":1,"2476":1,"2490":5,"2533":1,"2554":1,"2565":3,"2575":5,"2588":1,"2590":1,"2591":1,"2596":1,"2614":1,"2621":1,"2625":2,"2635":1,"2652":1,"2681":1,"2684":1,"2687":1,"2759":1,"2763":1,"2772":1,"2775":1,"2776":1,"2781":1,"2789":1,"2795":1,"2824":4,"2825":4,"2833":6,"2834":2,"2836":1}}],["usenpgsqlrest",{"2":{"2394":1,"2714":1}}],["useless",{"2":{"2391":1}}],["usegzipfallback",{"2":{"1792":1,"1936":1,"1937":1,"1941":1,"1944":2}}],["usebrotli",{"2":{"1792":1,"1936":1,"1937":1,"1940":1,"1944":2}}],["usekestrelhttpsconfiguration",{"2":{"1792":2,"1980":1}}],["useusehttpsredirection",{"2":{"1980":1}}],["useuserparameters",{"2":{"801":1,"937":1,"1469":1,"1477":2,"1483":1,"1539":1,"1544":2,"1548":1,"1792":3,"2183":2,"2185":1,"2187":1,"2394":2}}],["useusercontext",{"2":{"737":1,"766":1,"1058":1,"1062":1,"1469":1,"1475":2,"1483":1,"1539":1,"1540":2,"1548":1,"1792":4,"2184":2,"2185":1}}],["useurls",{"2":{"1792":1}}],["usejsonapplicationname",{"2":{"1617":1,"1618":1,"1620":1,"1633":1,"1792":3,"1848":1}}],["usehsts",{"2":{"1199":1,"1792":2,"1979":1,"1980":2,"1981":1,"1983":1,"1995":1,"2558":1}}],["usehttpsredirection",{"2":{"1199":1,"1792":2,"1979":1,"1980":1,"1981":1,"1982":1,"1995":1,"2558":1}}],["usehashedcachekeys",{"2":{"1067":2,"1510":1,"1511":2,"1516":1,"1792":2,"2265":3,"2495":1}}],["usec",{"2":{"269":1}}],["usemultipleconnections",{"2":{"150":1,"1176":1,"1177":1,"1630":1,"1631":1,"1632":1,"1633":1,"1792":1,"1836":1,"1837":1,"1863":1,"2701":1}}],["use",{"0":{"147":1,"221":1,"572":1,"573":1,"583":1,"1012":1,"1035":1,"1323":1,"1344":1,"1351":1,"1354":1,"1361":1,"1378":1,"1727":1,"1959":1,"2394":1,"2471":1,"2714":1,"2719":1,"2731":1},"1":{"1013":1,"1014":1,"1015":1,"1345":1,"1346":1,"1347":1,"1348":1},"2":{"51":1,"57":1,"58":1,"75":2,"120":1,"121":1,"133":1,"150":1,"168":1,"184":1,"186":1,"188":1,"206":1,"213":1,"258":1,"277":2,"280":1,"301":1,"302":1,"307":1,"309":1,"310":1,"313":1,"364":1,"366":1,"372":2,"384":1,"407":1,"408":1,"409":1,"419":1,"421":1,"436":1,"445":1,"453":1,"480":1,"493":1,"494":3,"518":2,"534":1,"537":1,"545":1,"567":1,"577":3,"586":1,"587":2,"653":1,"654":1,"663":1,"669":1,"679":3,"681":1,"683":1,"696":1,"722":1,"723":1,"724":1,"726":1,"757":1,"762":1,"767":2,"782":1,"784":1,"801":1,"809":1,"814":1,"817":1,"829":1,"832":1,"838":2,"847":1,"848":2,"851":2,"859":2,"860":1,"864":1,"893":1,"914":1,"917":1,"918":2,"920":1,"922":1,"934":2,"937":1,"943":1,"985":1,"995":1,"997":1,"1005":1,"1029":1,"1035":1,"1036":1,"1037":1,"1045":1,"1049":1,"1051":1,"1053":1,"1055":1,"1056":1,"1060":1,"1065":1,"1067":1,"1071":1,"1074":1,"1096":1,"1097":1,"1098":1,"1105":1,"1107":1,"1111":2,"1121":1,"1133":1,"1135":1,"1138":1,"1147":1,"1148":1,"1150":1,"1161":1,"1162":1,"1163":1,"1176":1,"1189":2,"1196":1,"1197":1,"1198":1,"1202":1,"1204":2,"1209":1,"1216":1,"1217":2,"1218":1,"1225":1,"1228":1,"1229":1,"1230":1,"1235":1,"1240":1,"1249":1,"1252":1,"1255":1,"1315":1,"1320":1,"1324":1,"1327":1,"1351":5,"1353":1,"1354":3,"1357":1,"1358":2,"1363":1,"1367":1,"1378":1,"1381":1,"1382":1,"1385":5,"1386":5,"1391":1,"1392":1,"1394":3,"1395":4,"1396":5,"1398":1,"1399":2,"1400":1,"1401":3,"1404":1,"1408":1,"1431":1,"1441":1,"1447":1,"1448":1,"1457":2,"1458":1,"1464":1,"1502":1,"1503":1,"1511":1,"1515":2,"1517":1,"1520":2,"1521":2,"1531":1,"1543":1,"1554":1,"1563":1,"1568":1,"1569":1,"1575":1,"1577":1,"1580":1,"1581":2,"1582":1,"1588":1,"1596":1,"1597":1,"1599":3,"1606":1,"1607":1,"1609":1,"1624":2,"1631":1,"1632":1,"1634":1,"1636":1,"1653":1,"1656":1,"1657":1,"1661":1,"1662":1,"1664":1,"1670":1,"1697":1,"1716":1,"1717":3,"1740":1,"1746":1,"1759":1,"1764":1,"1771":3,"1774":1,"1792":71,"1808":1,"1820":1,"1823":1,"1824":1,"1825":1,"1837":1,"1862":1,"1867":1,"1868":1,"1875":1,"1878":1,"1879":1,"1880":1,"1889":1,"1909":1,"1912":1,"1924":1,"1925":1,"1937":2,"1940":1,"1955":1,"1956":1,"1968":1,"1973":1,"1982":1,"2004":1,"2007":2,"2047":1,"2147":2,"2149":1,"2157":1,"2164":2,"2165":1,"2190":1,"2192":1,"2193":2,"2200":1,"2207":2,"2212":2,"2224":1,"2226":2,"2255":2,"2256":1,"2257":1,"2265":1,"2274":1,"2277":1,"2279":1,"2288":1,"2292":1,"2293":1,"2296":1,"2297":3,"2305":1,"2313":1,"2321":2,"2322":3,"2328":1,"2338":1,"2343":1,"2347":1,"2375":4,"2376":2,"2379":2,"2380":1,"2389":2,"2392":2,"2428":1,"2436":1,"2438":1,"2451":2,"2463":1,"2476":1,"2481":1,"2483":1,"2487":1,"2511":1,"2517":1,"2520":1,"2529":1,"2531":1,"2533":2,"2540":2,"2543":1,"2551":1,"2554":1,"2575":2,"2576":1,"2577":2,"2581":1,"2586":1,"2597":1,"2604":1,"2621":1,"2635":2,"2638":1,"2660":1,"2665":1,"2667":1,"2681":1,"2682":2,"2684":1,"2685":1,"2686":1,"2687":1,"2689":1,"2691":1,"2692":2,"2694":1,"2706":1,"2719":1,"2726":1,"2727":1,"2728":1,"2733":1,"2754":1,"2759":1,"2762":1,"2763":1,"2786":1,"2793":1,"2812":1,"2813":1,"2825":1,"2826":1,"2830":1,"2834":1,"2844":1,"2845":1,"2854":1,"2855":3,"2858":3,"2865":1,"2869":1,"2876":1}}],["uses",{"2":{"43":1,"105":1,"118":1,"133":1,"168":1,"202":1,"211":1,"214":1,"305":1,"307":1,"309":2,"319":1,"363":1,"384":1,"385":1,"395":1,"408":2,"417":1,"419":1,"430":1,"441":1,"443":1,"448":1,"449":1,"504":2,"511":1,"527":1,"646":1,"848":1,"851":1,"868":1,"875":1,"921":1,"933":2,"937":1,"951":1,"954":1,"980":1,"984":1,"987":1,"996":2,"1016":1,"1017":1,"1049":1,"1054":1,"1058":1,"1060":1,"1084":1,"1098":2,"1102":1,"1103":1,"1105":1,"1106":1,"1110":1,"1111":1,"1114":1,"1115":1,"1132":1,"1139":1,"1148":1,"1150":1,"1170":1,"1172":1,"1176":1,"1193":2,"1224":1,"1225":1,"1254":1,"1278":1,"1304":1,"1316":1,"1323":1,"1325":2,"1337":1,"1385":1,"1409":1,"1412":1,"1420":1,"1447":6,"1451":1,"1454":2,"1489":1,"1499":1,"1511":1,"1515":2,"1522":1,"1529":1,"1579":1,"1618":1,"1631":1,"1651":3,"1658":1,"1671":1,"1684":1,"1686":1,"1723":1,"1732":1,"1743":1,"1753":1,"1764":1,"1769":1,"1792":21,"1825":1,"1837":2,"1874":1,"1875":1,"1898":1,"1957":2,"1967":1,"2038":2,"2040":1,"2047":1,"2060":1,"2077":5,"2086":4,"2096":1,"2156":1,"2157":1,"2167":1,"2177":1,"2190":1,"2205":1,"2245":1,"2264":1,"2274":2,"2283":1,"2308":1,"2320":1,"2324":1,"2372":1,"2379":1,"2380":1,"2392":1,"2406":1,"2466":1,"2476":1,"2481":1,"2492":1,"2495":1,"2496":1,"2519":1,"2530":3,"2535":3,"2537":1,"2539":1,"2542":1,"2543":1,"2607":1,"2634":2,"2635":1,"2645":1,"2648":1,"2665":2,"2673":1,"2682":1,"2689":1,"2710":1,"2773":1,"2823":1,"2836":1,"2841":1,"2880":1}}],["usedefaultuploadmetadatacontextkey",{"2":{"1792":1,"2123":1,"2124":1}}],["usedefaultuploadmetadataparameter",{"2":{"1356":1,"1792":1,"2123":1,"2124":1,"2132":1}}],["usedefaultpasswordhasher",{"2":{"1196":1,"1469":1,"1482":1,"1498":1,"1499":2,"1502":2,"1503":1,"1505":1,"1792":2,"2551":1}}],["used",{"0":{"1076":1,"2494":1},"2":{"29":1,"40":1,"60":1,"108":3,"188":1,"251":1,"267":1,"279":1,"298":1,"302":1,"303":1,"305":1,"308":1,"310":1,"319":1,"356":1,"357":1,"362":2,"370":2,"378":1,"390":2,"409":1,"422":1,"423":1,"436":1,"446":2,"452":1,"582":1,"585":1,"646":1,"650":1,"726":1,"745":1,"747":2,"748":1,"781":1,"782":2,"784":2,"786":2,"841":2,"843":2,"847":1,"851":1,"904":1,"915":1,"926":1,"966":1,"1129":1,"1133":2,"1147":1,"1213":1,"1216":2,"1235":1,"1239":2,"1249":1,"1254":1,"1355":1,"1385":3,"1398":1,"1401":1,"1404":1,"1405":1,"1445":1,"1453":1,"1464":1,"1471":1,"1473":1,"1511":2,"1520":1,"1521":1,"1522":1,"1527":1,"1531":1,"1547":1,"1558":2,"1564":1,"1575":1,"1613":1,"1618":2,"1671":2,"1767":1,"1768":1,"1792":71,"1818":1,"1831":1,"1847":2,"1848":1,"1885":1,"1888":1,"1917":1,"1929":1,"1951":1,"1989":1,"2016":1,"2021":1,"2040":1,"2075":1,"2077":1,"2098":1,"2143":1,"2156":1,"2181":1,"2183":1,"2197":2,"2247":2,"2254":1,"2255":2,"2256":5,"2266":1,"2273":2,"2282":1,"2284":1,"2296":1,"2297":1,"2304":1,"2321":1,"2336":1,"2337":1,"2359":1,"2365":2,"2369":2,"2372":1,"2380":1,"2381":1,"2392":1,"2394":1,"2395":1,"2397":1,"2401":1,"2404":1,"2422":1,"2429":1,"2451":1,"2452":1,"2459":1,"2476":1,"2495":1,"2518":1,"2528":1,"2540":1,"2542":1,"2546":1,"2554":1,"2632":1,"2634":1,"2670":1,"2731":1,"2769":1,"2814":1,"2845":1,"2847":1,"2864":1}}],["userclaims=",{"2":{"1924":1,"2549":1}}],["usercount",{"2":{"1386":2}}],["usercontextcolumnname",{"2":{"1240":1}}],["usercontext",{"2":{"1220":2,"1221":2,"1792":3}}],["useredisbackend",{"2":{"1792":1,"2274":2,"2279":1,"2551":2}}],["useratelimiter",{"2":{"1792":1,"1822":1}}],["useragent",{"2":{"1792":2}}],["userapitypes",{"2":{"1570":1}}],["userapi",{"2":{"1570":1}}],["userinfo",{"2":{"1691":1,"1694":1,"1697":1,"1792":2}}],["userid=123",{"2":{"1386":1}}],["userid=",{"2":{"454":2,"1398":3,"1924":1,"2549":1,"2812":1}}],["userid",{"2":{"332":2,"376":1,"388":4,"389":2,"407":1,"938":1,"995":1,"996":3,"1215":1,"1216":1,"1239":1,"1320":4,"1366":1,"1369":1,"1386":9,"1387":1,"1391":2,"1395":2,"1396":1,"1398":3,"1408":2,"1409":1,"1567":2,"1570":1,"1973":2,"2040":1,"2476":1,"2493":3,"2540":2,"2587":2,"2590":1,"2842":1,"2845":1}}],["useroutinenameinsteadofendpoint",{"0":{"1576":1},"2":{"1416":1,"1417":1,"1553":1,"1563":1,"1576":1,"1581":1,"1792":1,"2274":1}}],["userdisplaynamecolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["userhandlecolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["userhandle",{"2":{"1222":1,"1792":1}}],["userverification",{"2":{"1222":1}}],["userverificationrequirement",{"0":{"1228":1,"1878":1},"2":{"1217":1,"1227":1,"1792":1,"1877":1,"1893":1,"2223":1,"2486":1}}],["user2",{"2":{"62":3}}],["user1",{"2":{"62":3,"1502":1,"2252":6}}],["user123",{"2":{"20":2,"22":1,"376":2,"643":1,"1620":1,"2314":2}}],["usernamecolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["username=postgres",{"2":{"2718":1,"2823":2,"2824":3,"2825":2,"2872":2}}],["username=user",{"2":{"2686":1,"2699":1}}],["username=readonly",{"2":{"2063":1}}],["username=report",{"2":{"1614":1}}],["username=myuser",{"2":{"1613":1}}],["username=app",{"2":{"1173":1,"1176":2,"1177":2,"1614":2,"1627":1,"1629":2,"2063":1,"2266":1}}],["username=api",{"2":{"1117":2}}],["username=alice",{"2":{"297":2}}],["username=",{"2":{"937":1,"1607":1,"1615":1,"1633":2,"1792":1,"1924":1,"2549":1,"2687":1,"2812":1}}],["usernames",{"2":{"864":1,"2831":2}}],["username>",{"2":{"56":1}}],["username",{"2":{"31":1,"37":2,"40":1,"56":2,"63":4,"297":2,"298":10,"302":3,"304":1,"305":1,"308":2,"309":5,"310":1,"312":8,"313":2,"332":2,"361":6,"376":1,"380":2,"593":1,"613":2,"738":1,"802":1,"864":1,"893":2,"905":2,"924":1,"934":6,"936":4,"937":2,"938":5,"977":2,"979":2,"980":3,"982":6,"983":1,"986":1,"988":2,"989":2,"990":7,"994":4,"995":4,"996":8,"1050":1,"1051":1,"1055":5,"1056":1,"1058":4,"1060":4,"1061":2,"1062":3,"1068":1,"1197":6,"1202":1,"1213":2,"1214":2,"1215":2,"1216":4,"1217":2,"1218":3,"1220":1,"1222":3,"1229":2,"1232":3,"1233":1,"1234":3,"1239":5,"1307":3,"1308":4,"1309":2,"1310":2,"1320":2,"1321":2,"1368":3,"1369":2,"1371":5,"1372":2,"1386":10,"1387":3,"1390":2,"1391":2,"1393":4,"1394":4,"1395":4,"1396":1,"1398":1,"1399":2,"1408":4,"1409":5,"1414":1,"1419":4,"1458":5,"1473":1,"1474":1,"1482":1,"1497":1,"1499":1,"1501":1,"1504":10,"1567":6,"1570":2,"1616":2,"1736":2,"1792":13,"1879":2,"1882":1,"1884":1,"1973":2,"2040":1,"2144":1,"2176":9,"2178":3,"2179":1,"2180":2,"2181":2,"2183":6,"2184":4,"2187":14,"2333":2,"2375":3,"2587":2,"2590":1,"2655":1,"2829":3,"2830":1,"2833":1,"2836":3,"2842":2}}],["user",{"0":{"16":1,"19":1,"20":1,"22":1,"107":1,"304":1,"360":1,"361":1,"453":1,"454":1,"479":1,"643":1,"728":1,"732":1,"733":1,"765":1,"766":1,"794":1,"797":1,"800":1,"803":1,"1058":1,"1066":1,"1069":1,"1162":1,"1220":1,"1221":1,"1287":1,"1288":1,"1475":1,"1477":1,"1540":1,"1544":1,"1870":1,"1871":1,"1926":1,"1955":1,"2314":2,"2379":1,"2572":1,"2733":1},"1":{"305":1,"306":1,"729":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"738":1,"739":1,"740":1,"741":1,"742":1,"795":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1476":1,"1478":1,"1541":1,"1542":1,"1543":1,"1545":1,"1546":1,"1547":1,"1956":1,"1957":1},"2":{"13":2,"14":2,"16":2,"18":2,"19":3,"20":4,"22":3,"25":3,"29":1,"30":1,"37":7,"38":5,"39":5,"40":3,"41":3,"43":2,"48":2,"50":4,"60":4,"61":9,"62":3,"63":1,"66":2,"105":4,"116":6,"165":1,"168":13,"169":3,"170":1,"208":5,"212":4,"215":5,"236":4,"248":3,"251":2,"288":4,"292":5,"297":4,"298":6,"302":1,"304":1,"305":5,"306":11,"308":1,"309":2,"310":13,"312":4,"313":2,"316":2,"332":4,"351":1,"357":1,"361":2,"369":1,"372":1,"373":1,"374":10,"375":4,"376":10,"378":5,"380":4,"382":1,"384":2,"388":1,"393":2,"401":4,"405":5,"406":5,"407":1,"428":2,"436":1,"452":2,"453":6,"454":16,"456":4,"479":5,"480":1,"488":2,"527":7,"529":1,"532":2,"535":1,"592":1,"611":2,"612":4,"613":2,"614":1,"621":2,"623":4,"638":4,"641":2,"643":3,"663":6,"664":7,"665":5,"666":7,"689":1,"690":1,"691":5,"692":1,"695":1,"705":2,"728":1,"729":2,"730":1,"732":1,"733":17,"734":3,"735":1,"736":7,"737":1,"738":8,"740":1,"741":2,"742":1,"762":3,"765":4,"766":3,"772":3,"794":2,"795":2,"797":20,"798":12,"799":6,"800":3,"801":1,"802":9,"803":2,"804":1,"805":2,"806":1,"811":8,"812":3,"813":2,"815":4,"817":1,"821":2,"826":4,"834":1,"835":5,"844":2,"866":1,"868":1,"872":1,"880":1,"882":1,"883":3,"884":2,"888":5,"893":2,"904":2,"905":4,"907":1,"910":1,"911":1,"915":1,"916":1,"918":1,"922":4,"924":1,"926":4,"932":2,"934":7,"936":6,"937":5,"941":1,"949":1,"966":3,"977":4,"979":2,"980":1,"982":1,"983":1,"986":1,"988":2,"989":4,"990":2,"992":3,"994":5,"995":1,"996":12,"1001":1,"1011":1,"1017":2,"1029":6,"1033":5,"1037":2,"1050":1,"1051":3,"1052":1,"1055":2,"1056":13,"1057":3,"1058":10,"1060":10,"1061":2,"1062":3,"1064":1,"1066":1,"1068":6,"1069":7,"1070":1,"1074":3,"1078":1,"1079":2,"1086":1,"1090":2,"1094":1,"1098":5,"1101":3,"1105":6,"1113":4,"1121":2,"1127":1,"1138":2,"1139":1,"1142":11,"1148":4,"1150":4,"1162":4,"1163":1,"1179":5,"1185":5,"1188":6,"1189":4,"1192":2,"1193":4,"1196":1,"1197":5,"1204":1,"1209":1,"1210":1,"1211":1,"1213":5,"1214":17,"1215":17,"1216":10,"1217":1,"1218":2,"1220":7,"1221":5,"1222":3,"1226":4,"1228":1,"1232":23,"1233":4,"1234":16,"1236":4,"1237":4,"1238":4,"1239":12,"1240":4,"1244":1,"1303":1,"1307":3,"1308":3,"1309":8,"1310":2,"1313":1,"1318":1,"1320":7,"1321":8,"1326":1,"1347":2,"1348":4,"1355":1,"1357":3,"1366":5,"1368":3,"1369":1,"1370":3,"1371":8,"1372":19,"1373":2,"1378":2,"1385":3,"1386":15,"1387":9,"1390":8,"1391":5,"1393":12,"1394":4,"1395":10,"1396":7,"1398":27,"1399":9,"1402":1,"1408":3,"1409":6,"1410":2,"1414":1,"1419":4,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":2,"1442":1,"1447":1,"1458":8,"1469":16,"1473":1,"1474":5,"1475":6,"1476":6,"1477":7,"1478":6,"1480":1,"1483":17,"1484":1,"1504":10,"1506":1,"1518":4,"1519":1,"1520":1,"1529":5,"1538":1,"1539":13,"1540":4,"1541":6,"1542":8,"1543":4,"1544":4,"1545":6,"1546":7,"1547":22,"1548":14,"1549":4,"1551":2,"1567":6,"1569":2,"1570":1,"1609":1,"1620":1,"1651":1,"1662":2,"1664":1,"1674":1,"1687":2,"1689":6,"1693":1,"1696":1,"1736":4,"1737":2,"1738":6,"1759":2,"1788":1,"1792":125,"1842":2,"1867":1,"1870":2,"1876":3,"1878":1,"1882":7,"1883":2,"1884":3,"1886":1,"1887":3,"1888":1,"1889":3,"1912":2,"1923":2,"1924":7,"1926":6,"1928":4,"1932":4,"1951":1,"1952":1,"1953":1,"1954":1,"1955":3,"1957":1,"1960":1,"1973":3,"2010":4,"2036":1,"2039":1,"2040":6,"2045":3,"2047":1,"2049":1,"2050":1,"2051":1,"2058":1,"2125":1,"2129":1,"2131":1,"2147":3,"2167":1,"2170":1,"2171":2,"2176":5,"2177":1,"2178":1,"2179":2,"2180":2,"2181":7,"2183":10,"2184":7,"2185":5,"2186":2,"2187":11,"2189":2,"2199":2,"2204":1,"2205":3,"2217":1,"2222":1,"2229":2,"2258":2,"2265":6,"2283":8,"2284":3,"2285":2,"2291":1,"2314":5,"2320":1,"2321":2,"2322":4,"2323":4,"2327":2,"2332":4,"2333":10,"2334":1,"2335":4,"2338":4,"2339":2,"2348":1,"2375":6,"2379":5,"2380":1,"2384":1,"2391":3,"2392":1,"2394":1,"2395":6,"2411":1,"2414":1,"2421":1,"2423":2,"2424":1,"2438":1,"2442":3,"2466":1,"2474":1,"2476":4,"2490":3,"2495":1,"2496":1,"2509":1,"2510":1,"2511":1,"2513":1,"2520":2,"2526":2,"2529":3,"2540":5,"2545":1,"2549":19,"2565":1,"2572":9,"2575":2,"2580":4,"2587":5,"2591":2,"2608":6,"2634":1,"2635":10,"2661":1,"2687":1,"2721":1,"2723":1,"2733":7,"2739":1,"2745":1,"2755":3,"2768":5,"2775":5,"2812":6,"2817":2,"2828":1,"2829":15,"2831":2,"2833":4,"2834":15,"2836":10,"2842":1,"2845":4,"2847":1,"2850":3,"2851":1,"2855":3,"2860":2,"2865":2,"2866":1,"2868":5,"2869":2,"2873":1,"2876":1,"2879":1}}],["users",{"0":{"62":1,"979":1,"1090":1,"1196":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1313":1,"1502":1},"2":{"9":1,"16":2,"18":2,"19":1,"35":1,"37":1,"51":1,"62":5,"63":1,"116":1,"119":3,"128":5,"167":3,"208":1,"212":1,"248":1,"249":6,"251":1,"264":10,"298":2,"302":1,"308":1,"309":2,"310":3,"312":2,"313":2,"314":2,"360":2,"361":1,"365":1,"366":1,"373":1,"374":1,"401":4,"403":2,"405":2,"406":2,"428":1,"451":5,"479":1,"488":1,"489":3,"493":1,"520":7,"562":3,"563":2,"566":3,"585":2,"592":1,"611":3,"612":1,"613":1,"614":1,"621":1,"623":1,"643":1,"646":1,"663":1,"689":1,"710":2,"715":2,"724":1,"811":2,"812":1,"813":1,"834":3,"835":4,"836":1,"837":1,"843":1,"868":1,"888":2,"902":1,"904":1,"922":1,"924":2,"932":1,"934":2,"938":2,"940":1,"973":1,"976":2,"977":4,"979":9,"980":3,"982":3,"983":2,"986":2,"988":8,"989":5,"990":5,"991":3,"992":1,"994":2,"995":2,"996":4,"1007":1,"1017":2,"1050":3,"1051":2,"1054":1,"1055":2,"1056":1,"1057":11,"1058":1,"1059":1,"1060":4,"1061":3,"1063":1,"1064":1,"1065":1,"1066":1,"1068":1,"1069":2,"1074":3,"1079":1,"1080":1,"1090":3,"1095":1,"1098":2,"1105":1,"1106":1,"1108":1,"1113":2,"1114":2,"1121":1,"1142":2,"1149":3,"1162":2,"1163":1,"1185":1,"1196":2,"1197":3,"1206":1,"1213":7,"1215":1,"1216":1,"1220":2,"1221":1,"1224":1,"1234":1,"1239":1,"1244":1,"1249":1,"1252":1,"1253":1,"1255":1,"1263":1,"1303":1,"1307":5,"1308":1,"1309":1,"1313":2,"1345":5,"1368":6,"1369":2,"1371":1,"1386":13,"1387":2,"1390":1,"1391":1,"1393":2,"1394":1,"1395":1,"1396":1,"1398":1,"1405":3,"1408":4,"1409":5,"1414":2,"1419":1,"1443":1,"1458":4,"1469":1,"1475":1,"1477":1,"1482":1,"1498":1,"1499":1,"1502":2,"1504":3,"1620":1,"1653":1,"1689":3,"1730":1,"1736":1,"1744":3,"1745":4,"1792":8,"1870":2,"1871":1,"1874":1,"1955":1,"1956":1,"1982":1,"2006":1,"2009":1,"2010":2,"2012":3,"2037":1,"2039":1,"2059":1,"2147":2,"2156":2,"2170":1,"2176":2,"2177":1,"2178":1,"2181":1,"2184":1,"2187":7,"2194":2,"2196":1,"2215":4,"2223":1,"2257":2,"2314":1,"2321":1,"2322":3,"2328":1,"2337":1,"2339":2,"2340":5,"2342":2,"2343":1,"2346":7,"2351":1,"2353":1,"2375":3,"2377":1,"2379":4,"2391":1,"2411":1,"2419":1,"2420":1,"2431":1,"2455":1,"2487":1,"2526":4,"2528":2,"2531":3,"2535":3,"2537":2,"2538":2,"2540":1,"2542":2,"2575":1,"2577":1,"2580":1,"2615":1,"2625":1,"2629":1,"2635":1,"2711":1,"2729":1,"2739":2,"2767":8,"2774":3,"2775":2,"2797":3,"2828":1,"2833":1,"2834":1,"2840":3,"2842":4,"2860":4,"2864":2,"2868":1,"2869":10,"2878":1}}],["usefully",{"2":{"320":1}}],["useful",{"2":{"10":1,"73":1,"165":1,"174":1,"286":1,"374":1,"376":1,"390":1,"531":1,"624":1,"720":1,"722":1,"826":1,"834":1,"902":1,"920":1,"974":1,"1069":1,"1073":1,"1100":1,"1230":1,"1343":1,"1396":2,"1412":1,"1414":1,"1416":1,"1489":1,"1492":1,"1519":1,"1576":1,"1618":1,"1632":1,"1664":1,"1670":1,"1771":1,"1781":1,"1792":11,"1852":1,"1880":1,"1957":1,"2040":1,"2105":1,"2255":1,"2256":1,"2291":1,"2338":1,"2350":1,"2379":1,"2383":1,"2550":1,"2577":1,"2580":1,"2633":1,"2635":2,"2648":1,"2656":1,"2751":1,"2791":1,"2809":1,"2833":1}}],["upcoming",{"2":{"2407":1}}],["uptime",{"2":{"2402":1}}],["updating",{"2":{"2395":1,"2597":1}}],["updates",{"0":{"1257":1},"2":{"660":2,"854":1,"857":1,"872":3,"1243":1,"1254":1,"1323":1,"1379":1,"1792":1,"2692":1,"2827":1}}],["updated",{"0":{"2673":1},"2":{"621":1,"664":1,"665":1,"849":1,"898":1,"904":1,"1020":1,"1021":1,"1139":1,"1239":1,"1376":1,"1714":1,"1856":1,"1888":1,"2050":1,"2615":1,"2834":2}}],["update",{"0":{"898":1},"2":{"106":1,"184":2,"257":2,"310":3,"565":1,"567":1,"592":1,"614":1,"622":3,"624":1,"625":1,"646":1,"663":3,"664":3,"665":2,"666":2,"812":3,"815":3,"848":1,"849":2,"852":1,"854":2,"868":1,"871":4,"888":1,"898":1,"911":1,"984":2,"985":1,"986":1,"988":1,"990":1,"997":1,"1021":1,"1026":1,"1045":1,"1054":1,"1056":2,"1060":1,"1071":1,"1107":1,"1139":2,"1191":1,"1193":2,"1203":2,"1216":2,"1239":2,"1314":1,"1316":1,"1338":2,"1339":3,"1376":1,"1407":1,"1419":1,"1437":2,"1440":1,"1655":1,"1664":1,"1689":1,"1792":5,"2277":2,"2279":1,"2292":1,"2319":1,"2320":2,"2339":1,"2342":3,"2385":1,"2389":1,"2392":1,"2395":2,"2455":1,"2498":1,"2679":1,"2774":1,"2834":6,"2843":1,"2851":1,"2874":1}}],["upgraded",{"2":{"1991":1,"2246":1}}],["upgrade",{"2":{"1464":1,"1948":1,"2223":1,"2258":1,"2376":1,"2378":1,"2380":1,"2389":1,"2490":1}}],["upgrades",{"0":{"2386":1},"2":{"1013":1,"1071":1,"1181":1}}],["upon",{"2":{"1402":1}}],["upper",{"2":{"1021":3,"1376":2}}],["upsert",{"0":{"898":1},"2":{"1689":2}}],["upstreams",{"2":{"2766":1}}],["upstream",{"0":{"1335":1,"2813":1},"2":{"75":2,"77":1,"214":3,"223":1,"412":2,"414":8,"415":2,"419":2,"423":3,"424":2,"429":1,"431":1,"433":1,"435":1,"436":3,"438":1,"439":3,"443":1,"447":2,"448":2,"449":1,"452":1,"453":4,"454":5,"456":1,"1067":1,"1101":1,"1104":3,"1105":11,"1328":1,"1331":3,"1332":3,"1333":2,"1335":1,"1337":1,"1338":3,"1340":3,"1341":1,"1343":3,"1346":1,"1348":2,"1350":1,"1396":3,"1430":2,"1431":5,"1433":2,"1475":1,"1743":3,"1789":1,"1792":5,"1915":1,"1917":5,"1918":1,"1920":1,"1922":2,"1923":1,"1925":3,"1926":1,"1927":1,"1928":3,"2164":1,"2185":1,"2300":2,"2302":7,"2303":2,"2304":3,"2305":2,"2307":2,"2310":1,"2313":2,"2404":1,"2463":1,"2466":1,"2500":1,"2502":2,"2509":2,"2517":3,"2521":2,"2549":9,"2580":3,"2770":1,"2771":1,"2806":3,"2807":5,"2808":1,"2809":1,"2810":4,"2811":4,"2812":4,"2813":3,"2814":4,"2815":3,"2816":1,"2817":3}}],["upfront",{"2":{"876":1}}],["uploadasync",{"2":{"2615":1}}],["upload+sse",{"2":{"2278":1}}],["uploadhandler",{"2":{"2267":1}}],["uploadhandlers",{"2":{"889":1,"1356":1,"1792":1,"2123":1,"2126":1,"2127":1,"2128":1,"2130":1,"2132":1,"2267":1,"2572":1}}],["uploadtolargeobject",{"2":{"1410":2}}],["uploadtofilesystem",{"2":{"1361":2,"1366":1}}],["uploadfile",{"2":{"1366":1}}],["uploadrepository",{"2":{"1366":1}}],["uploadpath",{"2":{"1366":2}}],["uploadoptions",{"2":{"889":1,"1356":1,"1792":1,"1836":2,"2123":1,"2126":1,"2127":1,"2128":1,"2130":1,"2132":1,"2267":1,"2701":1}}],["uploaded",{"2":{"748":2,"757":1,"762":1,"763":1,"772":1,"777":2,"779":1,"782":2,"784":4,"879":1,"887":1,"904":1,"1355":1,"1361":1,"1410":3,"2126":2,"2127":3,"2649":1}}],["uploads",{"0":{"778":1,"1352":1,"1360":1,"1410":1},"1":{"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"157":2,"393":1,"747":1,"764":3,"765":1,"766":1,"774":3,"779":2,"785":1,"791":2,"832":1,"878":1,"883":1,"884":1,"903":1,"904":1,"913":1,"1037":1,"1086":1,"1094":1,"1099":3,"1125":1,"1126":1,"1127":1,"1352":3,"1355":3,"1356":1,"1357":3,"1358":4,"1359":1,"1360":1,"1364":2,"1366":1,"1367":2,"1368":1,"1377":1,"1386":2,"1410":3,"1412":1,"1789":1,"1792":5,"1796":1,"1927":2,"1928":2,"2122":1,"2123":1,"2126":1,"2127":3,"2128":1,"2130":1,"2132":2,"2134":2,"2164":4,"2165":5,"2185":1,"2270":1,"2549":4,"2856":1}}],["upload",{"0":{"160":1,"234":1,"393":1,"743":1,"748":1,"763":1,"773":1,"782":1,"784":1,"786":1,"788":1,"886":1,"887":1,"1356":1,"1357":1,"1358":1,"1927":1,"2122":1,"2125":1,"2128":1,"2130":1,"2572":1,"2664":1},"1":{"744":1,"745":1,"746":1,"747":1,"748":1,"749":1,"750":1,"751":1,"752":1,"753":1,"754":1,"755":1,"756":1,"757":1,"758":1,"759":1,"760":1,"761":1,"762":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"769":1,"770":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"777":1,"778":1,"779":1,"780":1,"781":1,"782":1,"783":2,"784":1,"785":2,"786":1,"787":2,"788":1,"789":2,"790":1,"791":1,"792":1,"793":1,"887":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1,"2129":2,"2130":1,"2131":2,"2132":1,"2133":1,"2134":1,"2135":1,"2136":1},"2":{"157":5,"160":2,"164":2,"234":2,"386":1,"387":1,"393":4,"743":1,"744":2,"745":3,"747":2,"748":2,"750":7,"751":4,"752":5,"755":4,"756":4,"758":3,"763":1,"764":6,"765":1,"766":2,"767":4,"773":1,"774":6,"775":4,"777":6,"778":1,"779":1,"780":1,"781":4,"782":2,"783":3,"784":2,"785":3,"786":2,"787":4,"788":2,"789":4,"790":2,"792":2,"793":2,"878":1,"879":1,"880":2,"881":2,"883":1,"884":1,"885":1,"886":8,"892":1,"894":3,"899":2,"900":2,"902":4,"903":1,"904":9,"910":2,"1037":1,"1099":2,"1105":1,"1121":1,"1354":1,"1355":2,"1356":2,"1357":5,"1358":13,"1359":1,"1360":1,"1364":5,"1366":31,"1367":3,"1382":1,"1410":12,"1564":1,"1569":1,"1759":1,"1789":1,"1792":28,"1796":1,"1836":1,"1912":1,"1927":1,"2082":2,"2107":1,"2122":1,"2123":2,"2124":9,"2125":1,"2126":1,"2127":2,"2128":1,"2129":1,"2130":1,"2131":1,"2132":1,"2133":2,"2136":2,"2185":3,"2222":1,"2232":1,"2233":1,"2247":1,"2278":1,"2332":1,"2364":1,"2371":1,"2481":1,"2493":1,"2496":1,"2498":1,"2520":1,"2529":1,"2537":1,"2549":1,"2569":1,"2572":3,"2581":1,"2615":2,"2648":1,"2649":4,"2664":3,"2856":1,"2865":1,"2879":1,"2881":1}}],["up",{"0":{"1046":1,"1404":1,"1416":1,"2737":1,"2747":1,"2799":1},"2":{"31":1,"310":2,"534":1,"760":1,"770":1,"841":1,"843":1,"845":1,"847":1,"851":2,"859":1,"861":1,"864":1,"868":2,"871":1,"872":4,"873":2,"876":1,"918":1,"946":1,"948":1,"970":1,"994":1,"997":2,"1012":1,"1014":1,"1047":1,"1049":1,"1067":2,"1068":2,"1074":2,"1076":2,"1079":1,"1098":1,"1101":1,"1119":1,"1152":1,"1158":1,"1160":1,"1161":1,"1168":2,"1169":1,"1183":1,"1220":1,"1224":1,"1234":1,"1252":1,"1254":1,"1255":1,"1258":1,"1324":1,"1379":1,"1380":1,"1384":2,"1395":1,"1401":2,"1402":1,"1405":1,"1406":1,"1408":1,"1418":1,"1433":2,"1441":1,"1442":1,"1522":1,"1624":1,"1635":1,"1664":1,"1687":1,"1792":2,"1870":1,"1874":1,"1895":1,"1923":1,"1997":1,"2103":1,"2111":1,"2162":2,"2168":1,"2181":1,"2221":1,"2284":1,"2291":1,"2385":2,"2391":1,"2414":2,"2466":1,"2476":1,"2509":1,"2529":1,"2532":1,"2537":1,"2540":1,"2614":1,"2615":1,"2706":1,"2798":1,"2807":2,"2820":1,"2830":1,"2845":1,"2869":1,"2871":1,"2872":1,"2873":1}}],["u",{"2":{"16":6,"19":3,"37":7,"116":2,"264":2,"298":12,"302":5,"309":7,"312":14,"313":4,"366":6,"401":2,"428":4,"811":4,"980":3,"982":3,"990":3,"1057":3,"1060":9,"1105":3,"1142":4,"1197":10,"1216":8,"1239":7,"1308":6,"1371":6,"1408":3,"1504":20,"2176":12,"2178":7,"2187":6,"2328":4,"2540":8,"2840":4,"2875":1}}],["0+",{"0":{"1605":1},"2":{"1464":1,"1875":1,"2376":1}}],["038",{"2":{"1289":1}}],["03ms",{"2":{"1289":1}}],["03",{"0":{"2298":1,"2311":1,"2315":1,"2473":1,"2563":1,"2643":1},"1":{"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2312":1,"2313":1,"2314":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2474":1,"2475":1,"2476":1,"2477":1,"2564":1,"2565":1,"2566":1,"2567":1,"2644":1,"2645":1},"2":{"1277":1,"1293":1,"1408":1,"2221":1,"2224":1,"2228":1,"2229":2,"2234":1,"2238":1}}],["048",{"2":{"1991":1}}],["044",{"2":{"1295":1,"1301":1}}],["04ms",{"2":{"1295":1}}],["042",{"2":{"1269":1,"1285":1,"1293":1}}],["04",{"0":{"2373":1,"2385":1,"2568":1},"1":{"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2569":1},"2":{"1071":2,"2227":1,"2238":1,"2385":3,"2823":1,"2824":1}}],["0760485",{"2":{"2824":1}}],["079",{"2":{"1301":1}}],["070",{"2":{"1295":1,"1299":1}}],["07ms",{"2":{"1287":1}}],["071",{"2":{"1285":1}}],["073",{"2":{"1285":1,"1297":1}}],["07",{"0":{"2646":1},"1":{"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1},"2":{"1023":1,"1289":1,"1290":1,"1427":1,"2221":1,"2233":1,"2670":1}}],["027",{"2":{"1295":1,"1299":1}}],["028",{"2":{"1270":1,"1285":1,"1287":1}}],["02ms",{"2":{"1090":1,"1289":1,"1295":1}}],["02",{"0":{"2280":1,"2467":1,"2630":1,"2636":2,"2639":2,"2643":1,"2646":1,"2657":1,"2675":1},"1":{"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2637":2,"2638":2,"2640":2,"2641":2,"2642":2,"2644":1,"2645":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"1023":1,"1289":1,"1792":1,"1990":1,"1995":1,"2224":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":6,"2397":1}}],["095",{"2":{"1299":1}}],["093",{"2":{"1299":1}}],["09ms",{"2":{"1295":1}}],["098",{"2":{"1288":1,"1289":1}}],["090",{"2":{"1285":1}}],["096",{"2":{"1285":1,"2398":1}}],["09",{"0":{"2387":1},"1":{"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1},"2":{"956":1,"977":1,"980":1,"990":1,"1293":1,"1374":1,"2226":1,"2825":10}}],["060",{"2":{"1297":1,"2621":1}}],["064",{"2":{"1293":1}}],["06ms",{"2":{"1287":1,"1297":1}}],["061",{"2":{"1285":1,"2398":1}}],["065",{"2":{"1269":1,"1272":1,"1285":2,"1295":1,"2398":1}}],["06",{"0":{"2458":1,"2467":1,"2473":1},"1":{"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2474":1,"2475":1,"2476":1,"2477":1},"2":{"956":2,"995":1,"1023":1,"1287":1,"1289":1,"1295":1,"1374":2,"1391":1,"2222":3,"2223":1,"2224":3,"2452":2}}],["0850561",{"2":{"2825":1}}],["087",{"2":{"2823":1}}],["088",{"2":{"1295":1}}],["08ms",{"2":{"1287":1,"1288":1,"1295":1}}],["080",{"2":{"1285":1}}],["08007",{"2":{"1587":1,"1593":1,"1792":1}}],["08004",{"2":{"1152":1,"1153":1,"1587":1,"1593":1,"1598":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["08001",{"2":{"1152":1,"1153":1,"1587":1,"1593":1,"1598":1,"1617":1,"1622":1,"1624":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["08006",{"2":{"576":1,"577":2,"1152":3,"1153":1,"1154":2,"1177":1,"1587":1,"1593":1,"1597":2,"1598":1,"1617":1,"1622":1,"1624":1,"1625":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["08003",{"2":{"576":1,"577":2,"1152":3,"1153":1,"1154":2,"1177":1,"1587":1,"1593":1,"1597":2,"1598":1,"1617":1,"1622":1,"1624":1,"1625":1,"1633":1,"1792":2,"2824":1,"2825":1}}],["08000",{"2":{"576":1,"577":2,"1152":3,"1153":1,"1154":2,"1155":1,"1177":1,"1587":1,"1593":1,"1597":2,"1598":1,"1617":1,"1622":1,"1624":1,"1625":1,"1633":1,"1792":2}}],["089",{"2":{"1285":1,"1293":1}}],["08p01",{"2":{"1155":1,"1587":1,"1593":1,"1792":1}}],["08",{"0":{"1593":1,"2573":1},"1":{"2574":1,"2575":1,"2576":1,"2577":1},"2":{"919":5,"1155":1,"1297":1,"1792":1,"2237":1,"2452":1}}],["0517179",{"2":{"2824":1}}],["0575787",{"2":{"2823":1}}],["058",{"2":{"1293":1}}],["05ms",{"2":{"1287":1}}],["05",{"0":{"2387":1,"2408":1,"2439":1,"2449":1,"2570":1},"1":{"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2571":1,"2572":1},"2":{"214":2,"1391":1,"1743":1,"1856":3,"2224":1,"2225":3,"2226":1,"2238":1,"2452":1,"2453":1,"2502":1}}],["01ms",{"2":{"1301":2}}],["010",{"2":{"1277":1}}],["015",{"2":{"1269":1}}],["01",{"0":{"2458":1,"2563":1,"2568":1,"2570":1,"2573":1,"2578":1,"2583":1,"2592":1,"2598":1,"2601":1,"2605":1,"2609":1,"2612":1,"2616":1,"2619":1,"2623":1,"2630":1},"1":{"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2564":1,"2565":1,"2566":1,"2567":1,"2569":1,"2571":1,"2572":1,"2574":1,"2575":1,"2576":1,"2577":1,"2579":1,"2580":1,"2581":1,"2582":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1,"2602":1,"2603":1,"2604":1,"2606":1,"2607":1,"2608":1,"2610":1,"2611":1,"2613":1,"2614":1,"2615":1,"2617":1,"2618":1,"2620":1,"2621":1,"2622":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1},"2":{"213":1,"372":4,"919":10,"956":4,"977":5,"980":5,"990":5,"1067":6,"1189":2,"1192":1,"1374":4,"1391":2,"1740":1,"1792":1,"1992":1,"2224":1,"2234":1,"2235":1,"2236":9,"2237":2,"2238":3,"2288":1,"2319":2,"2845":2,"2846":4}}],["002",{"2":{"2824":1}}],["00+02",{"2":{"1856":1}}],["00z",{"2":{"1856":1,"2452":1,"2453":1}}],["00ms",{"2":{"1299":1}}],["001",{"2":{"948":1}}],["0060",{"2":{"333":2}}],["000+",{"2":{"1266":1,"1269":1,"1280":1,"1281":1}}],["0001",{"2":{"956":1,"1374":1}}],["000",{"2":{"309":1,"363":1,"867":1,"869":9,"871":2,"873":4,"948":1,"1049":1,"1991":2}}],["00",{"2":{"211":2,"213":2,"214":4,"566":1,"956":7,"963":3,"977":6,"980":6,"990":6,"995":1,"1023":1,"1034":2,"1189":1,"1277":3,"1291":2,"1340":2,"1374":7,"1391":2,"1408":1,"1677":1,"1731":2,"1740":2,"1743":2,"1792":12,"1856":5,"1916":2,"1917":2,"1931":2,"1990":4,"1992":4,"1995":4,"2077":2,"2080":1,"2264":2,"2288":2,"2452":2,"2453":1,"2502":2,"2652":2,"2765":2,"2814":4,"2823":3,"2824":6,"2825":3}}],["0",{"0":{"1066":1,"1397":1,"1449":1,"1908":1,"2240":1,"2241":1,"2243":2,"2254":1,"2262":1,"2280":1,"2298":1,"2315":1,"2373":1,"2387":1,"2418":1,"2449":1,"2478":1,"2499":1,"2524":1,"2547":1,"2573":1,"2583":1,"2623":1,"2630":1,"2646":1,"2657":1,"2675":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1398":1,"1399":1,"1909":1,"1910":1,"1911":1,"2242":1,"2244":2,"2245":2,"2246":2,"2247":2,"2248":2,"2249":2,"2250":2,"2251":2,"2252":2,"2253":2,"2254":2,"2255":2,"2256":2,"2257":2,"2258":2,"2259":2,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2419":1,"2420":1,"2421":1,"2422":1,"2423":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2429":1,"2430":1,"2431":1,"2432":1,"2433":1,"2434":1,"2435":1,"2436":1,"2437":1,"2438":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2479":1,"2480":1,"2481":1,"2482":1,"2483":1,"2484":1,"2485":1,"2486":1,"2487":1,"2488":1,"2489":1,"2490":1,"2491":1,"2492":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2498":1,"2500":1,"2501":1,"2502":1,"2503":1,"2504":1,"2505":1,"2506":1,"2525":1,"2526":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2539":1,"2540":1,"2541":1,"2542":1,"2543":1,"2544":1,"2545":1,"2546":1,"2548":1,"2549":1,"2550":1,"2551":1,"2574":1,"2575":1,"2576":1,"2577":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"7":2,"81":1,"83":1,"87":1,"88":1,"268":1,"274":1,"310":3,"317":2,"347":2,"388":1,"408":2,"412":1,"469":2,"559":1,"577":4,"581":1,"618":1,"720":1,"764":1,"765":1,"766":1,"774":1,"817":1,"823":1,"867":2,"869":3,"874":1,"883":1,"884":1,"885":2,"888":2,"892":1,"894":3,"904":2,"911":1,"917":1,"918":2,"928":2,"929":4,"937":1,"947":1,"956":3,"961":1,"963":4,"966":1,"975":1,"990":2,"991":5,"995":4,"998":2,"1023":2,"1027":3,"1037":4,"1038":2,"1044":3,"1047":2,"1059":1,"1061":1,"1062":1,"1066":3,"1069":2,"1071":1,"1072":1,"1073":4,"1074":2,"1086":1,"1096":1,"1098":1,"1102":1,"1138":1,"1152":1,"1153":2,"1154":4,"1157":1,"1165":1,"1177":3,"1213":1,"1255":5,"1257":10,"1262":1,"1272":1,"1274":1,"1287":4,"1288":4,"1289":4,"1290":4,"1291":4,"1293":4,"1295":4,"1297":4,"1299":4,"1301":4,"1317":1,"1335":2,"1349":1,"1350":1,"1366":10,"1368":2,"1374":3,"1380":3,"1384":2,"1385":2,"1386":3,"1397":1,"1398":1,"1405":1,"1408":2,"1410":2,"1416":3,"1417":1,"1420":1,"1427":2,"1429":7,"1430":1,"1431":1,"1433":4,"1434":1,"1458":1,"1464":1,"1511":1,"1553":1,"1554":1,"1565":2,"1567":1,"1577":3,"1579":1,"1581":1,"1582":5,"1587":1,"1589":1,"1590":4,"1597":4,"1598":2,"1609":2,"1616":2,"1623":1,"1625":1,"1633":1,"1639":1,"1644":1,"1683":1,"1684":2,"1685":2,"1691":1,"1692":1,"1693":1,"1694":3,"1695":3,"1696":1,"1697":1,"1701":2,"1707":2,"1708":7,"1711":2,"1712":5,"1714":20,"1715":3,"1752":1,"1753":2,"1758":1,"1762":2,"1785":1,"1792":71,"1800":1,"1801":1,"1802":1,"1807":1,"1808":1,"1813":2,"1814":2,"1819":4,"1840":2,"1850":1,"1856":2,"1866":2,"1897":2,"1898":2,"1899":2,"1907":2,"1917":1,"1925":1,"1948":1,"1955":1,"1959":1,"1995":8,"2011":2,"2013":1,"2014":2,"2033":1,"2037":1,"2041":1,"2045":2,"2056":2,"2077":3,"2080":1,"2093":1,"2094":4,"2099":1,"2101":1,"2105":1,"2106":1,"2107":3,"2113":2,"2118":8,"2138":1,"2141":2,"2142":4,"2144":8,"2145":2,"2146":6,"2148":3,"2154":1,"2156":1,"2162":2,"2220":1,"2221":1,"2222":1,"2223":1,"2224":2,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":1,"2238":1,"2239":1,"2240":4,"2245":2,"2247":1,"2254":5,"2265":1,"2354":2,"2376":1,"2378":2,"2385":1,"2386":8,"2389":1,"2391":1,"2398":4,"2410":1,"2416":1,"2419":1,"2421":1,"2430":1,"2437":1,"2438":1,"2442":1,"2455":2,"2465":1,"2471":1,"2481":2,"2493":1,"2517":1,"2526":2,"2535":3,"2537":9,"2542":1,"2551":3,"2566":1,"2575":8,"2576":1,"2633":9,"2652":2,"2669":1,"2692":1,"2703":8,"2719":1,"2721":1,"2731":1,"2739":1,"2752":1,"2762":4,"2785":1,"2794":1,"2801":1,"2814":1,"2823":4,"2824":6,"2825":3,"2841":1,"2844":1,"2859":2,"2860":2,"2861":1,"2868":1,"2875":1,"2878":1,"2880":1,"2882":1}}],["jdbc",{"2":{"2540":2,"2845":1}}],["jenkins",{"2":{"2102":1}}],["jetbrains",{"2":{"876":1}}],["jit",{"0":{"1272":1,"2245":1,"2789":1},"2":{"1090":4,"1091":1,"1254":1,"1255":1,"1259":1,"1263":2,"1264":1,"1265":1,"1266":2,"1267":1,"1268":1,"1269":1,"1270":1,"1272":3,"1277":1,"1278":1,"1279":1,"1280":1,"1284":1,"1285":1,"1287":4,"1288":4,"1289":4,"1290":4,"1291":4,"1293":4,"1295":4,"1297":4,"1299":4,"1301":4,"2242":1,"2245":5,"2744":1,"2789":13}}],["jpeg",{"2":{"1099":1,"1359":1,"1792":1,"2123":1,"2125":1}}],["jpy",{"2":{"1023":2,"1024":1}}],["jpg",{"2":{"157":2,"747":1,"1358":1,"1359":2}}],["jsjsimport",{"2":{"2830":1,"2836":1}}],["js",{"0":{"1026":1},"2":{"1025":1,"1026":4,"1027":1,"1064":1,"1101":1,"1106":1,"1255":1,"1320":2,"1350":1,"1416":1,"1422":1,"1559":1,"1574":1,"1580":2,"1582":1,"1792":1,"2036":1,"2040":1,"2247":1,"2476":1,"2872":1}}],["jsdoc",{"2":{"995":1,"1002":1,"2247":3,"2358":1,"2359":1}}],["jsonpath",{"2":{"2394":2}}],["jsonname",{"2":{"2372":1}}],["jsonconfigurationprovider",{"2":{"2824":2,"2825":2}}],["jsoncolumnnames",{"2":{"2372":2}}],["jsonc",{"2":{"2414":1,"2415":1,"2416":1,"2677":1,"2678":1,"2682":1,"2694":2}}],["jsoncjsonc",{"2":{"106":1,"107":1,"390":1,"1067":1,"1068":1,"1069":1,"1070":1,"1102":1,"1150":1,"1162":1,"1408":1,"1416":1,"1417":2,"1418":1,"1458":1,"1520":1,"1524":1,"1525":1,"1526":1,"1529":1,"1574":1,"1579":1,"1580":1,"1581":1,"1582":1,"1605":1,"1822":1,"1823":1,"1851":1,"1852":1,"1955":1,"1958":1,"1959":1,"2375":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2426":1,"2427":1,"2429":1,"2434":1,"2470":1,"2471":1,"2476":1,"2497":1,"2534":1,"2536":1,"2537":1,"2632":1,"2633":1,"2634":1,"2635":1,"2688":1,"2768":1}}],["jsonvalueformatter",{"2":{"2372":1}}],["jsontimestampsareutc",{"0":{"2455":1},"2":{"1792":1,"1856":3,"2224":1,"2453":1,"2455":3}}],["jsonb",{"0":{"2496":1},"2":{"582":1,"585":1,"695":1,"700":2,"736":2,"952":1,"982":4,"1019":2,"1020":2,"1021":4,"1031":2,"1074":1,"1078":3,"1092":1,"1216":2,"1232":3,"1234":2,"1239":3,"1279":3,"1339":2,"1376":4,"1398":1,"1426":1,"1543":1,"1687":2,"1689":4,"1792":2,"2109":2,"2110":1,"2223":1,"2247":1,"2394":2,"2496":2,"2498":2,"2526":1,"2530":4,"2531":2,"2535":2,"2540":1,"2546":1,"2763":3,"2764":3,"2766":2,"2845":1,"2860":1,"2861":1,"2866":2,"2868":1,"2869":3,"2873":1}}],["jsonjson",{"2":{"121":2,"140":1,"279":1,"305":1,"309":1,"310":1,"332":2,"333":1,"334":1,"335":1,"336":1,"354":1,"430":1,"436":1,"449":1,"455":1,"470":1,"476":1,"477":1,"478":1,"479":1,"556":1,"562":1,"565":1,"566":1,"577":1,"609":2,"611":1,"612":1,"614":1,"622":1,"695":1,"705":1,"762":1,"763":1,"772":1,"773":1,"775":1,"817":1,"819":1,"887":2,"889":1,"893":2,"900":1,"903":1,"914":2,"915":1,"916":3,"917":3,"918":1,"919":2,"937":1,"958":1,"963":1,"964":1,"965":1,"966":1,"967":1,"998":1,"1022":1,"1023":1,"1052":1,"1053":1,"1054":1,"1056":1,"1058":1,"1059":1,"1062":1,"1111":1,"1141":1,"1145":1,"1146":1,"1147":2,"1148":1,"1149":1,"1152":2,"1153":1,"1154":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1166":1,"1168":1,"1171":1,"1173":1,"1174":1,"1176":1,"1177":1,"1196":1,"1197":1,"1199":1,"1217":2,"1240":1,"1241":1,"1245":1,"1340":1,"1356":1,"1359":1,"1360":1,"1369":1,"1370":1,"1375":1,"1386":3,"1391":1,"1445":1,"1446":1,"1449":1,"1450":1,"1453":1,"1455":1,"1457":1,"1462":1,"1463":1,"1464":1,"1469":1,"1476":1,"1478":1,"1482":1,"1483":1,"1488":1,"1494":1,"1498":1,"1502":1,"1503":1,"1505":1,"1510":1,"1513":1,"1514":1,"1515":2,"1516":1,"1517":1,"1518":1,"1534":2,"1539":1,"1541":1,"1542":1,"1545":1,"1546":1,"1548":1,"1553":1,"1587":1,"1590":1,"1597":1,"1598":1,"1603":1,"1607":1,"1608":1,"1609":1,"1613":1,"1614":1,"1615":1,"1617":1,"1620":1,"1622":1,"1625":1,"1627":1,"1628":1,"1629":1,"1630":1,"1633":1,"1638":1,"1640":1,"1641":1,"1642":1,"1643":1,"1646":2,"1650":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1660":1,"1661":1,"1662":1,"1663":2,"1669":1,"1672":1,"1673":1,"1676":1,"1677":1,"1678":2,"1683":1,"1690":1,"1697":1,"1698":1,"1702":1,"1706":2,"1707":1,"1708":1,"1709":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1721":1,"1735":1,"1752":1,"1758":3,"1759":1,"1763":1,"1769":2,"1770":1,"1771":1,"1776":1,"1778":1,"1779":1,"1780":1,"1781":1,"1792":1,"1800":1,"1802":1,"1803":1,"1804":1,"1805":1,"1807":1,"1809":1,"1810":1,"1814":1,"1824":1,"1833":1,"1836":1,"1839":3,"1842":1,"1856":1,"1859":1,"1863":2,"1867":1,"1889":1,"1890":1,"1892":1,"1893":1,"1897":1,"1899":1,"1900":1,"1902":1,"1903":1,"1904":1,"1905":1,"1907":1,"1909":1,"1911":1,"1912":1,"1916":1,"1927":1,"1931":1,"1936":1,"1944":2,"1948":1,"1951":1,"1952":1,"1953":1,"1954":1,"1960":1,"1966":1,"1968":1,"1970":1,"1971":1,"1973":3,"1974":2,"1975":1,"1979":1,"1981":1,"1984":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1992":1,"1993":1,"1994":1,"1995":1,"1999":1,"2001":1,"2003":1,"2008":1,"2009":2,"2010":2,"2011":1,"2012":1,"2015":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":2,"2023":1,"2024":1,"2025":1,"2027":1,"2028":1,"2029":1,"2033":1,"2035":1,"2037":1,"2039":1,"2040":1,"2042":1,"2046":1,"2054":1,"2055":1,"2058":1,"2059":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2066":1,"2067":1,"2068":1,"2069":1,"2073":1,"2075":1,"2077":1,"2080":1,"2085":1,"2089":1,"2093":1,"2095":1,"2098":1,"2102":1,"2104":1,"2111":2,"2112":1,"2116":1,"2118":2,"2119":1,"2123":1,"2126":1,"2127":1,"2128":1,"2130":1,"2132":1,"2138":1,"2142":1,"2144":1,"2145":1,"2146":1,"2154":1,"2173":1,"2174":1,"2175":1,"2181":1,"2183":1,"2184":1,"2187":1,"2208":1,"2254":1,"2255":3,"2257":1,"2264":1,"2265":3,"2266":2,"2271":1,"2272":1,"2273":1,"2274":1,"2279":1,"2297":2,"2308":1,"2320":1,"2326":1,"2330":1,"2342":2,"2350":1,"2410":1,"2415":1,"2441":1,"2532":1,"2542":1,"2544":1,"2549":2,"2554":3,"2555":1,"2565":3,"2572":1,"2575":1,"2586":2,"2587":3,"2588":1,"2589":2,"2595":1,"2596":1,"2607":2,"2628":1,"2659":1,"2686":1,"2687":2,"2689":1,"2697":1,"2701":1,"2703":2,"2704":1,"2718":1,"2737":1,"2746":1,"2749":1,"2750":1,"2752":1,"2757":1,"2761":1,"2769":1,"2795":1,"2797":1,"2798":1,"2799":1,"2800":1,"2801":1,"2804":4,"2808":1,"2814":1,"2821":1,"2825":1,"2835":1,"2841":1,"2842":1,"2850":1,"2861":1,"2871":1,"2872":1,"2873":1,"2874":1,"2875":1,"2880":1}}],["json",{"0":{"72":1,"257":1,"521":1,"555":1,"734":1,"775":1,"799":1,"900":1,"912":1,"917":1,"918":1,"1097":1,"1279":1,"1296":1,"1620":1,"1856":1,"1973":1,"2055":1,"2217":1,"2271":1,"2397":1,"2399":1,"2491":1,"2496":2,"2554":1,"2587":1,"2589":1,"2668":1,"2669":1,"2726":1},"1":{"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":2,"920":2,"1297":1,"2398":1},"2":{"7":3,"16":3,"19":2,"20":2,"21":1,"37":2,"38":2,"39":2,"40":2,"48":3,"50":2,"71":4,"72":2,"73":1,"87":1,"104":1,"106":1,"115":1,"116":2,"117":1,"136":3,"137":1,"165":1,"167":1,"180":1,"182":1,"186":1,"188":1,"207":5,"208":3,"209":4,"210":2,"227":2,"229":1,"258":1,"263":2,"264":6,"279":1,"328":2,"330":1,"334":1,"335":1,"336":2,"337":3,"338":1,"351":1,"352":1,"361":2,"374":2,"388":1,"395":1,"401":2,"405":1,"406":1,"415":8,"423":2,"426":4,"427":3,"428":2,"429":1,"436":1,"439":6,"447":2,"449":4,"452":7,"453":1,"454":2,"470":1,"484":1,"487":1,"494":1,"499":1,"503":5,"510":5,"511":3,"512":2,"515":2,"516":1,"517":3,"518":1,"521":3,"524":2,"540":3,"542":1,"545":2,"556":1,"559":1,"567":1,"585":1,"593":1,"594":1,"607":2,"609":2,"613":1,"615":2,"617":1,"618":1,"673":1,"675":1,"691":2,"695":1,"700":1,"705":1,"710":3,"739":1,"748":1,"750":3,"751":3,"752":1,"755":3,"756":3,"758":1,"760":2,"761":2,"762":1,"763":1,"764":4,"765":1,"766":1,"767":1,"770":4,"771":5,"772":1,"773":1,"774":4,"775":5,"776":2,"777":3,"778":1,"783":1,"785":1,"787":1,"788":3,"789":1,"799":3,"802":1,"811":3,"812":2,"813":2,"814":2,"815":2,"823":1,"829":1,"833":3,"835":5,"851":6,"867":2,"868":9,"871":1,"873":2,"874":1,"882":1,"883":1,"884":1,"885":2,"886":3,"887":1,"888":1,"889":1,"892":2,"894":2,"899":1,"900":5,"902":1,"903":3,"904":4,"912":1,"914":1,"915":1,"916":2,"917":2,"918":7,"919":5,"920":1,"938":1,"952":1,"956":5,"958":1,"967":1,"976":1,"995":6,"1007":1,"1017":2,"1019":2,"1023":1,"1024":2,"1026":5,"1029":4,"1030":1,"1031":1,"1032":1,"1033":1,"1037":2,"1039":2,"1041":2,"1046":2,"1060":2,"1064":1,"1074":2,"1077":1,"1078":3,"1080":2,"1081":1,"1092":1,"1095":1,"1096":1,"1097":4,"1098":1,"1101":1,"1102":5,"1104":1,"1105":15,"1107":4,"1109":1,"1111":1,"1127":1,"1135":1,"1138":3,"1139":2,"1141":1,"1142":3,"1179":10,"1181":7,"1189":1,"1207":1,"1214":4,"1215":3,"1216":2,"1217":1,"1218":1,"1220":1,"1221":2,"1222":1,"1232":8,"1233":1,"1234":3,"1236":4,"1237":2,"1239":5,"1255":4,"1258":2,"1259":1,"1270":2,"1276":1,"1279":5,"1285":1,"1305":1,"1309":2,"1318":1,"1320":2,"1321":2,"1332":5,"1335":8,"1338":7,"1339":10,"1341":2,"1342":3,"1343":2,"1347":6,"1348":2,"1356":1,"1357":3,"1358":3,"1362":1,"1366":2,"1367":2,"1370":1,"1372":2,"1373":1,"1374":5,"1375":1,"1376":1,"1378":2,"1382":1,"1386":3,"1391":2,"1398":4,"1399":4,"1402":1,"1407":1,"1408":3,"1410":5,"1412":1,"1416":3,"1417":2,"1418":3,"1420":3,"1427":1,"1431":1,"1434":1,"1445":1,"1452":1,"1453":1,"1456":1,"1469":1,"1470":1,"1471":1,"1475":2,"1477":1,"1523":1,"1540":1,"1543":1,"1544":1,"1547":4,"1553":1,"1558":1,"1559":1,"1567":2,"1568":1,"1575":1,"1581":1,"1609":1,"1618":1,"1620":1,"1671":2,"1684":1,"1687":3,"1722":1,"1725":1,"1727":5,"1730":2,"1732":2,"1733":1,"1736":4,"1742":2,"1745":4,"1792":70,"1813":1,"1817":1,"1822":1,"1824":7,"1849":2,"1856":2,"1882":4,"1883":1,"1884":2,"1886":1,"1887":2,"1888":2,"1897":2,"1898":2,"1907":2,"1911":2,"1918":1,"1921":6,"1922":2,"1924":9,"1926":2,"1936":2,"1943":3,"1948":1,"1951":1,"1959":1,"1967":2,"1974":2,"2000":2,"2008":1,"2010":1,"2012":1,"2040":3,"2047":1,"2055":2,"2056":2,"2067":1,"2068":1,"2072":1,"2076":1,"2078":1,"2092":4,"2096":1,"2097":1,"2109":1,"2110":1,"2124":2,"2125":1,"2129":2,"2130":1,"2131":4,"2147":3,"2155":3,"2156":1,"2164":1,"2165":1,"2171":3,"2175":1,"2184":1,"2187":1,"2193":1,"2204":2,"2206":1,"2207":1,"2208":1,"2217":1,"2222":1,"2223":3,"2224":2,"2226":1,"2236":1,"2247":3,"2254":2,"2255":6,"2259":1,"2264":6,"2265":3,"2267":1,"2270":2,"2271":2,"2273":2,"2274":1,"2277":2,"2279":1,"2283":2,"2290":2,"2291":1,"2293":1,"2296":1,"2297":1,"2303":5,"2304":2,"2310":1,"2313":1,"2320":3,"2321":1,"2322":1,"2326":1,"2330":1,"2338":2,"2339":4,"2342":1,"2344":2,"2346":4,"2356":1,"2360":1,"2372":2,"2377":1,"2378":1,"2380":2,"2389":3,"2394":2,"2397":1,"2398":1,"2400":1,"2406":1,"2414":1,"2415":3,"2417":1,"2419":1,"2428":1,"2431":1,"2434":2,"2435":1,"2438":2,"2450":2,"2451":1,"2454":2,"2455":1,"2456":1,"2471":1,"2476":2,"2477":1,"2481":10,"2486":3,"2491":3,"2493":1,"2496":4,"2498":8,"2509":3,"2511":1,"2512":2,"2513":1,"2522":2,"2526":1,"2529":1,"2530":1,"2537":7,"2542":1,"2543":1,"2545":2,"2546":1,"2549":14,"2551":1,"2555":1,"2566":1,"2572":4,"2575":2,"2585":1,"2586":5,"2587":3,"2588":7,"2589":5,"2590":1,"2594":1,"2600":1,"2603":1,"2607":2,"2614":1,"2621":1,"2635":5,"2641":2,"2648":1,"2649":1,"2650":1,"2659":1,"2662":2,"2664":1,"2666":1,"2667":2,"2668":2,"2669":1,"2670":2,"2671":1,"2673":2,"2674":1,"2677":2,"2678":1,"2681":2,"2682":1,"2684":8,"2685":6,"2686":1,"2689":1,"2690":1,"2691":1,"2694":2,"2695":2,"2697":2,"2700":2,"2717":2,"2718":1,"2724":1,"2725":1,"2739":1,"2754":2,"2763":3,"2764":1,"2765":1,"2766":4,"2767":4,"2769":1,"2772":1,"2785":2,"2788":2,"2789":2,"2790":2,"2791":2,"2810":7,"2812":2,"2813":8,"2815":6,"2821":1,"2824":4,"2825":3,"2829":4,"2830":1,"2836":2,"2841":1,"2850":1,"2851":1,"2853":1,"2854":1,"2857":1,"2861":1,"2865":1,"2866":2,"2872":2,"2873":1,"2877":2,"2878":2,"2880":2}}],["j",{"2":{"927":2}}],["jack",{"2":{"913":1}}],["jan",{"2":{"1023":1}}],["january",{"2":{"878":1,"887":1,"912":1,"921":1,"1010":1,"1048":1,"1083":1,"1128":1,"1135":1,"1183":1,"1209":1,"1254":1,"1302":1,"1328":1,"1352":1,"1400":1,"1435":1}}],["jane",{"2":{"22":1,"128":2,"489":2,"493":2,"643":1,"646":1,"913":1,"916":3,"917":2,"918":3,"1386":2,"2314":1}}],["javajava",{"2":{"1366":1}}],["java24",{"2":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["java",{"2":{"860":1,"1255":1,"1257":2,"1366":1,"2880":1}}],["javascriptjavascriptfetch",{"2":{"1492":1}}],["javascriptjavascriptimport",{"2":{"1320":1}}],["javascriptjavascript",{"2":{"1026":1,"1320":1}}],["javascript",{"0":{"1580":1,"2360":1},"2":{"834":1,"961":1,"1004":1,"1086":1,"1094":3,"1107":1,"1200":1,"1343":1,"1385":1,"1386":2,"1394":2,"1413":1,"1432":1,"1447":1,"1448":1,"1552":1,"1559":1,"1580":1,"1684":1,"1685":1,"1792":7,"1936":1,"1943":2,"2020":1,"2036":1,"2168":1,"2247":1,"2360":2,"2550":1,"2562":1,"2566":1,"2626":1,"2772":1,"2775":1,"2791":2}}],["jumped",{"2":{"2571":1}}],["jumping",{"2":{"1266":1}}],["july",{"2":{"1072":1}}],["junior",{"2":{"1403":1}}],["junitoutput=results",{"2":{"2880":1}}],["junitoutput",{"0":{"2102":1},"2":{"1792":1,"2093":1,"2094":1,"2102":1,"2535":1,"2537":1,"2880":1}}],["junit",{"2":{"1094":1,"1792":1,"2094":1,"2102":2,"2221":1,"2526":1,"2528":1,"2535":1,"2537":1,"2864":1,"2880":1}}],["junction",{"2":{"841":1}}],["june",{"2":{"831":1,"852":1,"1038":1,"1423":1}}],["justifications",{"2":{"860":1}}],["just",{"0":{"1017":1,"1410":1,"1420":1,"2180":1},"2":{"1":2,"319":1,"351":1,"376":1,"448":1,"650":1,"665":1,"694":1,"841":4,"844":4,"845":3,"847":4,"848":1,"849":3,"851":9,"852":2,"860":1,"861":1,"864":3,"868":1,"876":2,"879":2,"880":1,"888":1,"904":2,"918":1,"920":3,"942":1,"947":1,"948":1,"961":2,"977":1,"980":1,"990":1,"994":1,"1010":1,"1011":1,"1037":2,"1047":1,"1067":1,"1068":1,"1073":3,"1075":1,"1078":1,"1081":1,"1082":1,"1126":1,"1127":1,"1134":1,"1141":1,"1150":1,"1180":1,"1200":1,"1208":1,"1209":1,"1215":1,"1220":1,"1280":1,"1281":1,"1302":1,"1305":1,"1366":2,"1372":1,"1385":2,"1386":6,"1389":2,"1393":1,"1394":1,"1399":2,"1400":1,"1401":3,"1402":2,"1403":2,"1405":2,"1409":1,"1412":1,"1416":1,"1419":1,"1423":1,"1428":1,"1429":1,"1435":1,"1438":1,"1443":2,"1459":1,"1517":1,"1770":1,"1792":5,"1870":1,"1974":1,"2107":1,"2177":1,"2180":1,"2252":1,"2265":1,"2313":1,"2314":1,"2323":1,"2358":1,"2375":1,"2466":1,"2494":1,"2504":1,"2532":1,"2537":2,"2538":1,"2542":1,"2577":1,"2607":1,"2709":1,"2760":1,"2779":1,"2789":1,"2797":1,"2809":1,"2830":1,"2833":1,"2839":1,"2868":1,"2876":1}}],["jorge",{"2":{"1442":2}}],["journey",{"2":{"1384":1}}],["jobs",{"2":{"2872":1}}],["job",{"2":{"661":3,"831":1,"847":1,"851":1,"852":2,"855":1,"861":1,"864":1,"1075":1,"1403":2,"2437":1,"2833":1,"2880":1}}],["joining",{"2":{"1133":2}}],["joined",{"2":{"918":1,"977":1,"980":1,"990":1}}],["joins",{"2":{"851":2,"852":1,"857":1,"920":1,"956":1,"980":1,"1079":1,"1096":3,"1205":1,"1385":1,"2868":1}}],["join",{"2":{"188":1,"848":1,"852":5,"856":1,"857":2,"860":4,"861":1,"914":1,"916":3,"918":4,"980":1,"982":1,"990":1,"1026":2,"1105":2,"1133":3,"1197":1,"1320":2,"1375":1,"1408":1,"1429":1,"1504":2,"1574":2,"2296":1,"2540":1}}],["johnson",{"2":{"913":1,"919":2}}],["johndoe",{"2":{"488":1}}],["john",{"2":{"14":1,"19":2,"34":1,"128":2,"361":1,"488":1,"489":2,"493":2,"643":1,"646":1,"1386":6,"1391":2,"1393":4,"2314":1}}],["jwtbearer",{"2":{"2386":1,"2567":1}}],["jwtbearerdefaults",{"2":{"1454":1}}],["jwtvalidatelifetime",{"2":{"1453":1,"1454":1,"1459":1,"1792":1,"2375":1,"2412":1,"2554":2}}],["jwtvalidateaudience",{"2":{"1453":1,"1454":1,"1459":1,"1463":1,"1792":1,"2375":1,"2412":1,"2554":2}}],["jwtvalidateissuersigningkey",{"2":{"1453":1,"1454":1,"1459":1,"1792":1,"2375":1,"2412":1,"2554":2}}],["jwtvalidateissuer",{"2":{"1453":1,"1454":1,"1459":1,"1463":1,"1792":1,"2375":1,"2412":1,"2554":2}}],["jwtexpire",{"2":{"1071":1,"1453":1,"1454":1,"1458":1,"1459":1,"1463":1,"1464":2,"1792":2,"2375":3,"2376":1,"2377":1,"2412":1,"2737":1}}],["jwtexpireminutes",{"2":{"1053":1,"1062":1,"1071":1,"1464":1,"2175":1,"2376":1,"2554":2}}],["jwtrefreshexpire",{"2":{"1453":1,"1454":1,"1459":1,"1463":1,"1464":1,"1792":2,"2375":2,"2376":1,"2377":1,"2412":1}}],["jwtrefreshexpiredays",{"2":{"1053":1,"1464":1,"2376":1,"2554":2}}],["jwtrefreshpath",{"2":{"1053":1,"1062":1,"1453":1,"1454":1,"1458":1,"1459":1,"1460":1,"1792":3,"2375":4,"2412":1,"2554":2}}],["jwtaudience",{"2":{"1053":1,"1453":1,"1454":2,"1459":1,"1463":1,"1792":3,"2175":1,"2375":1,"2412":1,"2554":2}}],["jwtauthscheme",{"2":{"1053":1,"1062":1,"1454":1,"1460":1,"1792":2,"2175":2,"2375":1,"2554":1}}],["jwtauth",{"2":{"1053":1,"1062":1,"1445":1,"1453":1,"1454":1,"1458":1,"1463":1,"1464":1,"1792":1,"1825":1,"2175":1,"2375":1,"2377":1,"2554":2,"2737":1}}],["jwtissuer",{"2":{"1053":1,"1453":1,"1454":2,"1459":1,"1463":1,"1792":3,"2175":1,"2375":1,"2412":1,"2554":2}}],["jwtsecret",{"2":{"1053":1,"1062":1,"1453":1,"1454":1,"1457":2,"1458":3,"1459":2,"1460":1,"1463":1,"1464":1,"1792":3,"2175":1,"2375":6,"2412":1,"2413":1,"2554":2,"2737":1}}],["jwtclockskew",{"2":{"279":1,"1453":1,"1454":1,"1459":1,"1792":1,"2375":2,"2412":1,"2554":2}}],["jwt",{"0":{"1453":1,"1454":1,"1457":1,"1463":1,"2175":1,"2554":1,"2737":1},"1":{"1454":1,"1455":1,"1456":1,"1457":1},"2":{"25":1,"302":2,"303":1,"312":3,"315":1,"529":1,"535":1,"835":1,"934":1,"1037":1,"1045":2,"1048":1,"1053":4,"1054":2,"1055":1,"1061":2,"1062":2,"1063":2,"1064":2,"1066":1,"1068":1,"1070":1,"1086":1,"1098":8,"1102":7,"1106":1,"1126":1,"1127":1,"1216":2,"1221":3,"1222":2,"1249":1,"1322":1,"1444":1,"1445":1,"1453":5,"1454":3,"1455":1,"1456":3,"1457":4,"1458":11,"1459":2,"1460":3,"1463":1,"1464":1,"1788":1,"1792":22,"1795":1,"1825":4,"1852":1,"1894":1,"1902":2,"1906":1,"1907":2,"1911":2,"2164":1,"2165":1,"2170":1,"2171":2,"2175":1,"2178":1,"2188":1,"2189":1,"2227":1,"2238":1,"2254":3,"2284":1,"2375":16,"2377":2,"2383":1,"2410":1,"2412":1,"2413":1,"2421":1,"2422":1,"2423":1,"2429":1,"2434":2,"2435":1,"2438":5,"2554":10,"2736":1}}],["1junit",{"2":{"2880":1}}],["1verbose",{"2":{"2799":1}}],["1validationoptions",{"2":{"2446":1}}],["1validation",{"2":{"1609":1}}],["1include",{"2":{"2694":1}}],["1runs",{"2":{"2878":1}}],["1run",{"2":{"2543":1}}],["1resulting",{"2":{"2697":1}}],["1response",{"2":{"537":1}}],["1redirect",{"2":{"2694":1}}],["1returns",{"2":{"1737":1,"2554":1}}],["1returning",{"2":{"885":1}}],["1renders",{"2":{"959":1}}],["1recommended",{"2":{"308":1}}],["1column",{"2":{"2842":1}}],["1code",{"2":{"2797":1}}],["1codenpgsqlrest",{"2":{"2537":1}}],["1command",{"2":{"2697":1}}],["1cache",{"2":{"2622":1}}],["1click",{"2":{"1200":1}}],["1use",{"2":{"2326":1}}],["1uses",{"2":{"92":1}}],["1produces",{"2":{"2326":1}}],["1px",{"2":{"965":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["1enable",{"2":{"2010":1}}],["1each",{"2":{"1008":1,"2445":1,"2752":1}}],["1after",{"2":{"2342":1,"2555":1,"2622":1}}],["1also",{"2":{"2301":1}}],["1a",{"2":{"1823":1,"2405":1,"2537":1,"2864":1}}],["1any",{"2":{"975":1}}],["1annotations",{"2":{"685":1}}],["1openapi",{"2":{"2555":1}}],["1or",{"2":{"1782":1,"2749":1}}],["1one",{"2":{"1008":1,"2110":1}}],["1get",{"2":{"1730":1}}],["1new",{"2":{"2586":1,"2588":1}}],["1no",{"2":{"1369":1}}],["1npgsqlrest",{"2":{"1023":1}}],["164",{"2":{"2146":1,"2621":1}}],["16456",{"2":{"903":1}}],["16384",{"2":{"1356":1,"1792":2,"1992":1,"2132":1}}],["165",{"2":{"1301":1}}],["16ms",{"2":{"1301":1}}],["169",{"2":{"1297":1}}],["162",{"2":{"1290":1,"1293":1,"1714":2}}],["168",{"2":{"1277":2,"1454":1,"1707":1,"1708":1,"1792":3,"2633":2}}],["160",{"2":{"1267":1}}],["16t08",{"2":{"919":5}}],["16",{"0":{"1429":1,"2224":1,"2449":1,"2458":1,"2467":1,"2473":1,"2583":1,"2601":1},"1":{"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2474":1,"2475":1,"2476":1,"2477":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2602":1,"2603":1,"2604":1},"2":{"919":5,"977":2,"980":2,"990":2,"1152":1,"1189":1,"1269":1,"1272":1,"1277":1,"1285":2,"1287":2,"1290":2,"1291":1,"1293":1,"1301":1,"1383":1,"1423":1,"1433":1,"1625":1,"1708":2,"1712":1,"1714":1,"1792":4,"1856":2,"1958":1,"2164":1,"2224":5,"2236":2,"2397":1,"2398":1,"2435":3,"2455":2,"2535":1,"2633":2,"2770":1,"2869":1}}],["1building",{"2":{"885":1}}],["1bad",{"2":{"382":1,"2334":1}}],["13+",{"2":{"2792":1}}],["131",{"2":{"1714":1}}],["130",{"2":{"1295":1}}],["137",{"2":{"1293":1,"1297":1}}],["132",{"2":{"1290":1,"1295":1}}],["13ms",{"2":{"1287":1,"1293":1}}],["139",{"2":{"1281":1}}],["136",{"2":{"1281":1,"1295":1}}],["133",{"2":{"1270":1,"1277":2,"1281":1,"1285":1,"1290":1,"1297":1,"1301":1}}],["138",{"2":{"1269":1,"1285":1}}],["13",{"0":{"1066":1,"2227":1,"2262":1,"2311":1,"2373":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2312":1,"2313":1,"2314":1,"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1},"2":{"852":1,"869":1,"1037":1,"1066":3,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1098":1,"1102":1,"1157":1,"1212":1,"1288":6,"1297":1,"1299":2,"1420":1,"1458":1,"1464":2,"1522":1,"1714":2,"1792":1,"1850":1,"1948":1,"1955":1,"2164":1,"2227":1,"2229":1,"2239":1,"2376":2,"2378":1,"2381":1,"2389":1,"2397":1,"2419":1,"2421":1,"2437":1,"2442":1,"2607":1,"2710":1,"2819":1}}],["1949",{"2":{"2407":1}}],["197",{"2":{"1714":1}}],["1970",{"2":{"848":1,"852":2}}],["190",{"2":{"1714":1}}],["19ms",{"2":{"1293":1,"1297":1}}],["198",{"2":{"1277":1,"1714":1}}],["1984",{"2":{"335":2,"913":1,"918":2,"919":2,"2586":3}}],["192",{"2":{"1277":1,"1291":1,"1656":4,"1707":1,"1708":1,"1714":1,"1792":4,"1991":1,"2633":2}}],["1999",{"2":{"956":2,"1189":1,"1374":2}}],["19",{"0":{"2221":1,"2524":1,"2609":1},"1":{"2525":1,"2526":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2539":1,"2540":1,"2541":1,"2542":1,"2543":1,"2544":1,"2545":1,"2546":1,"2610":1,"2611":1},"2":{"876":1,"961":1,"977":1,"980":1,"986":1,"990":1,"1037":2,"1072":1,"1073":2,"1074":2,"1076":2,"1082":4,"1094":2,"1189":1,"1277":1,"1290":3,"1293":2,"1297":1,"1301":1,"1379":1,"1407":1,"1785":1,"1792":5,"1801":1,"1802":1,"2107":1,"2167":1,"2221":1,"2236":1,"2397":1,"2435":1,"2526":2,"2535":1,"2537":1,"2545":1,"2731":1,"2739":1,"2752":1,"2794":1,"2801":1,"2844":1,"2859":1,"2860":3,"2882":1}}],["1tags",{"2":{"2097":1}}],["1tag",{"2":{"2097":1}}],["1to",{"2":{"1973":1}}],["1ttzr19",{"2":{"845":1}}],["1that",{"2":{"2861":1}}],["1this",{"2":{"334":1,"933":1,"1015":1,"2009":1,"2208":1,"2586":1,"2588":1,"2589":1,"2682":1}}],["1the",{"2":{"102":1,"155":1,"173":1,"335":1,"414":1,"436":1,"619":1,"694":1,"885":1,"982":1,"992":1,"2366":1,"2526":1,"2694":1,"2811":1}}],["149",{"2":{"1295":1,"1301":1}}],["1499",{"2":{"1189":1,"1192":1}}],["145",{"2":{"1290":1,"2435":1}}],["142",{"2":{"1290":1}}],["141",{"2":{"1290":1,"1714":1}}],["1415",{"2":{"956":1,"1374":1}}],["146",{"2":{"1289":1}}],["14ms",{"2":{"1287":1}}],["147",{"2":{"1284":1}}],["140",{"2":{"1281":1}}],["143",{"2":{"1277":1}}],["144",{"2":{"930":1}}],["14",{"0":{"1254":1,"2226":1,"2387":1,"2578":1},"1":{"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1,"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2579":1,"2580":1,"2581":1,"2582":1},"2":{"817":1,"867":1,"947":1,"956":2,"957":1,"959":2,"961":3,"966":1,"970":1,"977":1,"980":1,"990":1,"1068":4,"1071":1,"1089":1,"1096":1,"1098":2,"1255":1,"1257":1,"1269":4,"1281":2,"1285":2,"1288":2,"1289":2,"1293":2,"1297":1,"1374":2,"1383":1,"1385":1,"1413":1,"1442":1,"1446":1,"1447":3,"1458":2,"1464":4,"1714":1,"1792":3,"1810":1,"2144":1,"2146":1,"2148":1,"2164":1,"2165":2,"2226":1,"2237":1,"2375":2,"2376":4,"2377":1,"2386":1,"2391":1,"2575":1,"2804":1}}],["173",{"2":{"1714":1}}],["176",{"2":{"1301":1}}],["175",{"2":{"1297":1,"1299":1}}],["179",{"2":{"1297":1}}],["177",{"2":{"1290":1}}],["17ms",{"2":{"1289":1,"1295":1}}],["171",{"2":{"1289":1}}],["178",{"2":{"1284":1,"1288":1}}],["172",{"2":{"1284":1,"1708":1,"1712":1,"1714":1,"1792":1,"2633":1}}],["17",{"0":{"1425":1,"1605":1,"2223":1,"2478":1,"2605":1},"1":{"1426":1,"1427":1,"1428":1,"2479":1,"2480":1,"2481":1,"2482":1,"2483":1,"2484":1,"2485":1,"2486":1,"2487":1,"2488":1,"2489":1,"2490":1,"2491":1,"2492":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2498":1,"2606":1,"2607":1,"2608":1},"2":{"317":2,"977":1,"980":1,"990":1,"1037":1,"1038":2,"1269":2,"1288":3,"1290":1,"1293":1,"1423":1,"1426":2,"1427":4,"1430":1,"1433":2,"1639":1,"1644":1,"1714":1,"1813":2,"1840":2,"1875":1,"2164":1,"2223":1,"2236":1,"2535":1,"2607":1,"2719":1,"2721":1,"2762":1,"2770":1,"2841":1,"2875":1}}],["158",{"2":{"1714":1}}],["154",{"2":{"1382":1}}],["153",{"2":{"1297":1}}],["15ms",{"2":{"1297":1}}],["151",{"2":{"1290":2}}],["1517",{"2":{"867":1,"871":1}}],["156",{"2":{"1023":1,"1270":1,"1297":1}}],["150",{"2":{"566":1,"869":1,"911":1,"1290":1,"1332":1,"1335":2,"1338":1,"1339":1,"1342":1}}],["15",{"0":{"1449":1,"1908":1,"2225":1,"2260":1,"2408":1,"2418":1,"2439":1,"2592":1,"2598":1},"1":{"1909":1,"1910":1,"1911":1,"2261":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2419":1,"2420":1,"2421":1,"2422":1,"2423":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2429":1,"2430":1,"2431":1,"2432":1,"2433":1,"2434":1,"2435":1,"2436":1,"2437":1,"2438":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1},"2":{"310":1,"347":2,"869":3,"872":2,"956":2,"977":1,"980":1,"990":1,"1038":1,"1043":1,"1045":1,"1047":2,"1189":1,"1192":1,"1269":2,"1272":1,"1285":1,"1288":3,"1293":1,"1295":1,"1366":4,"1374":2,"1386":1,"1616":1,"1714":1,"2166":1,"2225":7,"2236":2,"2239":1,"2364":1,"2410":1,"2430":1,"2435":1,"2438":1,"2440":1,"2447":1,"2448":1}}],["1looking",{"2":{"296":1}}],["1year",{"2":{"280":1}}],["1matched",{"2":{"2096":1}}],["1mark",{"2":{"184":1,"186":1}}],["1min",{"2":{"1769":1,"1792":3,"2060":1,"2253":2,"2634":1,"2635":1}}],["1m",{"2":{"279":1,"1792":1,"1837":1,"2156":1,"2542":1}}],["1suites",{"2":{"2877":1}}],["1supports",{"2":{"340":1,"599":1}}],["1sqlsqlcomment",{"2":{"2768":1}}],["1see",{"2":{"2746":1,"2757":1}}],["1setup",{"2":{"2537":1}}],["1skipped",{"2":{"2342":1}}],["1save",{"2":{"1080":1,"2857":1}}],["1streams",{"2":{"959":1}}],["1since",{"2":{"252":1,"2537":1}}],["1s",{"2":{"213":6,"214":1,"1032":1,"1105":1,"1740":5,"1742":2,"1792":1,"2288":5,"2290":2,"2765":2}}],["1with",{"2":{"332":1,"609":1,"1175":1,"1782":1,"1973":1,"2009":1,"2010":1,"2587":1}}],["1weeks",{"2":{"133":1}}],["1week",{"2":{"133":1}}],["1w",{"2":{"92":1,"133":1,"1143":1}}],["1disables",{"2":{"175":1}}],["1days",{"2":{"133":1}}],["1day",{"2":{"133":1,"272":1}}],["1d",{"2":{"92":1,"97":1,"133":1,"271":1,"278":1,"1143":1,"1179":1,"2205":1}}],["1hours",{"2":{"133":1}}],["1hour",{"2":{"133":1,"272":1}}],["1h",{"2":{"92":1,"96":1,"118":1,"133":1,"214":1,"271":1,"277":1,"1143":1,"1179":1,"1532":1,"1743":1,"1792":1,"2101":1,"2205":2,"2212":1,"2502":1,"2537":1}}],["103",{"2":{"1714":3}}],["1024",{"2":{"1510":1,"1511":1,"1515":1,"1792":2,"2274":1,"2279":1}}],["102400",{"2":{"1360":1}}],["10ms",{"2":{"1288":1,"1293":1,"1349":2}}],["107",{"2":{"1284":1,"1299":1}}],["105",{"2":{"1269":1,"1289":1}}],["1058",{"2":{"933":1}}],["1048576",{"2":{"1510":1,"1511":1,"1515":1,"1792":3,"1990":1,"2274":1,"2279":1}}],["104",{"2":{"1264":1,"1269":1,"1285":1,"1293":1,"1714":2}}],["106",{"2":{"1091":1,"1262":1,"1264":1,"1268":1,"1290":1,"1291":1}}],["101",{"0":{"1128":1},"1":{"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1},"2":{"1037":1,"1128":1,"1270":1,"1284":1,"1285":1,"1289":1,"1714":1,"2398":1}}],["10×",{"2":{"872":1}}],["1080p",{"2":{"1044":2}}],["108",{"2":{"851":2,"1382":2,"1714":1}}],["10s",{"2":{"92":1,"94":1,"208":1,"213":1,"214":1,"278":1,"1017":1,"1019":3,"1032":1,"1033":1,"1105":1,"1143":1,"1398":1,"1736":1,"1740":1,"1775":2,"2205":1,"2288":1,"2764":1,"2765":2,"2766":2}}],["10",{"0":{"94":1,"1288":1,"1295":1,"2230":1,"2246":1,"2280":1,"2298":1},"1":{"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1},"2":{"88":1,"92":1,"255":2,"477":1,"577":2,"869":5,"872":3,"910":1,"911":1,"913":1,"953":1,"956":4,"969":1,"977":1,"980":1,"990":1,"1090":1,"1132":1,"1153":1,"1154":2,"1158":2,"1159":1,"1160":6,"1161":2,"1162":2,"1177":1,"1187":2,"1192":2,"1245":1,"1255":4,"1257":4,"1270":1,"1277":2,"1284":2,"1285":4,"1288":2,"1289":1,"1295":1,"1297":1,"1299":2,"1301":2,"1328":1,"1336":2,"1337":2,"1338":5,"1339":6,"1366":5,"1374":3,"1408":1,"1587":1,"1589":1,"1590":2,"1597":2,"1598":1,"1633":1,"1639":1,"1707":1,"1708":1,"1712":1,"1715":1,"1773":1,"1779":1,"1792":13,"1893":1,"1951":2,"1952":2,"1953":6,"1954":2,"1955":1,"1958":1,"1959":2,"1960":7,"2060":1,"2061":1,"2068":1,"2164":1,"2165":2,"2223":1,"2229":1,"2230":1,"2240":1,"2245":2,"2246":1,"2257":5,"2258":1,"2277":2,"2379":1,"2386":8,"2397":4,"2398":3,"2441":1,"2470":1,"2471":2,"2551":3,"2633":2,"2711":1,"2816":1}}],["1001",{"2":{"2465":1}}],["100+",{"2":{"2270":1}}],["100kb",{"0":{"1299":1},"2":{"1285":1}}],["100ms",{"2":{"213":1,"1740":1,"2288":1}}],["10000",{"2":{"1026":2,"1721":1,"1722":1,"1792":1,"2502":1,"2769":2}}],["10001",{"2":{"332":3,"1973":3,"2010":2,"2587":3}}],["1000",{"0":{"1133":1},"2":{"271":1,"478":1,"1067":2,"1133":1,"1149":1,"1168":3,"1169":1,"1177":3,"1317":1,"1320":1,"1416":1,"1510":1,"1511":1,"1517":1,"1529":1,"1792":1,"1995":1,"2247":1,"2265":3,"2463":1,"2466":1,"2621":1}}],["1000microseconds",{"2":{"133":1}}],["1000usec",{"2":{"133":1}}],["1000us",{"2":{"133":1,"271":1}}],["1000+",{"2":{"88":1,"1132":1,"1169":1}}],["100",{"0":{"1090":1,"1091":1,"1132":1,"1265":1,"1289":1,"1290":2,"1291":1,"1293":1},"2":{"86":1,"476":1,"479":1,"763":1,"773":1,"869":2,"872":1,"894":1,"911":1,"930":1,"948":2,"953":1,"1007":1,"1044":1,"1069":3,"1074":1,"1084":2,"1090":3,"1158":1,"1159":1,"1160":3,"1162":4,"1165":2,"1166":4,"1169":2,"1171":3,"1181":2,"1189":1,"1255":4,"1258":2,"1262":2,"1263":1,"1264":4,"1265":2,"1267":1,"1268":2,"1269":1,"1270":1,"1271":1,"1272":3,"1281":1,"1283":1,"1284":6,"1285":2,"1289":1,"1295":1,"1297":1,"1301":1,"1322":1,"1338":1,"1339":1,"1349":1,"1361":1,"1391":1,"1410":1,"1616":1,"1792":9,"1951":2,"1952":2,"1953":2,"1955":1,"1960":4,"1990":3,"1991":1,"1992":1,"2089":2,"2107":3,"2145":2,"2245":1,"2257":3,"2258":1,"2378":2,"2379":1,"2397":3,"2398":5,"2526":1,"2537":3,"2551":1,"2789":1,"2860":2,"2869":1,"2879":1}}],["188",{"2":{"1714":1}}],["187",{"2":{"1301":1}}],["183",{"2":{"1297":1}}],["184",{"2":{"1297":1}}],["1800+",{"2":{"1402":1}}],["180",{"2":{"1288":1}}],["182",{"2":{"1285":1}}],["181",{"2":{"1284":1,"1301":1}}],["18",{"0":{"2222":1,"2499":1,"2507":1,"2514":1},"1":{"2500":1,"2501":1,"2502":1,"2503":1,"2504":1,"2505":1,"2506":1,"2508":1,"2509":1,"2510":1,"2511":1,"2512":1,"2513":1,"2515":1,"2516":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1},"2":{"74":3,"977":1,"980":1,"990":1,"1287":2,"1290":1,"1299":1,"1430":1,"1431":7,"1433":3,"1434":1,"1569":2,"1714":2,"1759":2,"1912":2,"1924":2,"1925":2,"2164":1,"2222":3,"2515":1,"2535":1,"2621":1,"2816":1}}],["11pt",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["114",{"2":{"1293":1,"1297":1,"1714":1}}],["11ms",{"2":{"1287":1,"1295":2,"1299":1}}],["116",{"2":{"1281":1,"1285":1,"1301":1}}],["111",{"2":{"1277":1,"1278":1,"1284":1,"1290":1}}],["113",{"2":{"1069":2}}],["110",{"2":{"867":1,"874":1,"875":1,"1289":1}}],["1100",{"2":{"622":1}}],["11",{"0":{"2229":1,"2241":1,"2243":1,"2298":1,"2311":1,"2408":1,"2439":1,"2657":1},"1":{"2242":1,"2244":1,"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2312":1,"2313":1,"2314":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1},"2":{"19":1,"20":1,"22":1,"317":1,"412":1,"919":1,"977":1,"980":1,"990":1,"1039":1,"1090":2,"1277":1,"1278":1,"1285":3,"1289":5,"1293":1,"1295":1,"1297":1,"1299":2,"1301":3,"1383":1,"1792":1,"1813":1,"1824":3,"2225":3,"2229":2,"2232":1,"2240":2,"2397":1,"2398":1,"2452":1,"2481":2,"2823":1,"2824":1}}],["12on",{"2":{"2879":1}}],["12or",{"2":{"79":1,"133":1,"570":1,"2049":1}}],["12resolved",{"2":{"2768":1}}],["12response",{"2":{"493":1}}],["12placeholder",{"2":{"2688":1}}],["12new",{"2":{"2589":1}}],["12note",{"2":{"2252":1,"2823":1}}],["12no",{"2":{"961":1}}],["12both",{"2":{"2340":1,"2410":1}}],["12impact",{"2":{"2622":1}}],["12in",{"2":{"2337":1}}],["12if",{"2":{"745":1}}],["12jsonjson",{"2":{"2095":1}}],["12when",{"2":{"2848":1}}],["12with",{"2":{"2009":1}}],["12work",{"2":{"308":1}}],["124",{"2":{"1301":1,"1386":1}}],["1247",{"2":{"887":1}}],["122",{"2":{"1293":1}}],["12ms",{"2":{"1289":1,"1291":1,"1297":1}}],["12use",{"2":{"1195":1,"2321":1}}],["12every",{"2":{"1193":1}}],["12cookiemultisessions",{"2":{"1068":1}}],["12creates",{"2":{"247":1}}],["12k",{"2":{"1037":1}}],["127",{"2":{"995":1,"1047":1,"1257":1,"1380":1,"1386":1,"1433":2,"1711":1,"2011":1,"2162":1,"2354":1}}],["12500",{"2":{"566":1}}],["12this",{"2":{"2056":1,"2755":1,"2797":1}}],["12that",{"2":{"1369":1}}],["12these",{"2":{"2359":1}}],["12the",{"2":{"133":1,"187":1,"383":2,"560":1,"570":1,"982":1,"1139":1,"1192":1,"1570":1,"1573":1,"2095":1,"2294":1,"2305":1,"2348":2,"2394":1,"2395":1,"2847":1,"2849":1}}],["12texttext──",{"2":{"1044":1}}],["12typescripttypescript",{"2":{"1571":1}}],["12type",{"2":{"516":1}}],["12self",{"2":{"1929":1}}],["12sql",{"2":{"1408":1}}],["12sqlpage",{"2":{"834":1}}],["12sqlsql",{"2":{"417":1,"441":1,"442":1,"443":1,"444":1,"445":1}}],["12space",{"2":{"113":1,"637":1}}],["128",{"2":{"309":1,"363":1,"1049":1,"1255":1,"1257":1,"1287":1,"1288":1,"1289":1,"1290":2,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1656":4,"1714":1,"1792":2,"2621":1}}],["1200+",{"2":{"1402":2}}],["120",{"2":{"275":2}}],["12also",{"2":{"2824":1}}],["12an",{"2":{"927":1}}],["12a",{"2":{"214":1,"2502":1}}],["12",{"0":{"1397":1,"2228":1,"2260":1,"2262":1,"2268":1,"2275":1,"2315":1,"2547":1,"2552":1,"2556":1,"2560":1},"1":{"1398":1,"1399":1,"2261":1,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2276":1,"2277":1,"2278":1,"2279":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2548":1,"2549":1,"2550":1,"2551":1,"2553":1,"2554":1,"2555":1,"2557":1,"2558":1,"2559":1,"2561":1,"2562":1},"2":{"30":1,"45":1,"56":1,"79":1,"145":1,"166":1,"179":1,"186":1,"193":1,"268":1,"274":1,"308":3,"358":1,"372":2,"459":1,"474":1,"491":1,"498":1,"544":1,"559":1,"581":1,"618":1,"628":1,"637":1,"795":1,"809":1,"823":1,"824":1,"868":1,"914":2,"915":1,"916":3,"917":2,"918":1,"956":2,"967":1,"975":1,"995":1,"1037":1,"1067":1,"1073":2,"1077":1,"1078":1,"1082":2,"1086":1,"1090":1,"1152":2,"1257":1,"1269":3,"1285":1,"1289":3,"1293":2,"1301":1,"1346":1,"1368":2,"1374":2,"1375":4,"1379":1,"1380":1,"1384":2,"1385":1,"1397":1,"1398":1,"1405":1,"1442":1,"1447":1,"1464":2,"1605":1,"1609":1,"1617":1,"1622":1,"1623":1,"1708":1,"1712":1,"1792":3,"2013":1,"2144":1,"2162":1,"2164":1,"2165":2,"2177":3,"2215":1,"2228":1,"2238":4,"2239":4,"2252":1,"2293":1,"2306":1,"2319":1,"2328":1,"2376":2,"2377":1,"2378":1,"2379":1,"2386":2,"2407":1,"2452":1,"2483":1,"2497":1,"2633":1,"2659":1,"2669":1,"2674":1,"2758":1,"2785":3,"2792":1,"2804":1,"2823":1,"2824":13,"2825":11,"2840":1,"2845":1,"2846":2,"2859":1}}],["12values",{"2":{"14":1}}],["123get",{"2":{"2731":1,"2842":1}}],["123output",{"2":{"2678":1}}],["123example",{"2":{"2587":1,"2659":1}}],["123now",{"2":{"2321":1}}],["123to",{"2":{"2118":1,"2703":1}}],["123that",{"2":{"835":1}}],["123this",{"2":{"463":1,"919":1,"1386":1,"2201":1,"2350":1}}],["123the",{"2":{"75":1,"104":1,"320":1,"1148":1,"2110":1,"2217":1,"2218":1,"2392":1,"2695":1,"2832":1}}],["123for",{"2":{"2039":1,"2255":1}}],["123post",{"2":{"1730":1}}],["123production",{"2":{"1417":1}}],["123useful",{"2":{"1572":1}}],["123unauthenticated",{"2":{"16":1}}],["123matches",{"2":{"1526":1}}],["123key",{"2":{"1518":1,"2265":1}}],["123both",{"2":{"1414":1}}],["123behavior",{"2":{"106":1}}],["123drop",{"2":{"1368":1}}],["123debugging",{"2":{"2530":1}}],["123decrypt",{"2":{"186":1,"2293":1}}],["123deprecated",{"2":{"174":1}}],["123coverage",{"2":{"2107":1}}],["123configurable",{"2":{"1111":1}}],["123creates",{"2":{"401":1,"402":1}}],["123any",{"2":{"2864":1}}],["123alternatively",{"2":{"2824":1}}],["123all",{"2":{"674":1}}],["123available",{"2":{"2704":1}}],["123after",{"2":{"2366":1}}],["123a",{"2":{"1743":1}}],["123access",{"2":{"22":1}}],["123level",{"2":{"651":1}}],["123remember",{"2":{"2717":1}}],["123returns",{"2":{"585":1,"2337":1}}],["123response",{"2":{"487":1}}],["123headers",{"2":{"502":1}}],["123without",{"2":{"2252":1}}],["123with",{"2":{"476":1,"477":1,"1968":1,"2011":1}}],["123when",{"2":{"438":1,"1524":1,"2354":1}}],["123if",{"2":{"462":1,"553":1,"554":1,"555":1,"660":1,"661":1,"662":1}}],["123server",{"2":{"2155":1}}],["123self",{"2":{"445":1}}],["123sql",{"2":{"1414":1}}],["123sqlsql",{"2":{"418":1,"419":1,"420":1,"421":1}}],["123same",{"2":{"1161":1}}],["123sources",{"2":{"1069":1}}],["123summing",{"2":{"885":1}}],["123supported",{"2":{"582":1}}],["123specify",{"2":{"169":1}}],["1234npgsqlrest",{"2":{"2821":1}}],["1234now",{"2":{"1430":1}}],["1234inline",{"2":{"2340":1}}],["1234if",{"2":{"662":1,"686":1,"1531":1}}],["1234default",{"2":{"2322":1}}],["1234generated",{"2":{"1574":1}}],["1234two",{"2":{"1408":1}}],["1234that",{"2":{"949":1,"1405":1}}],["1234this",{"2":{"464":1,"918":1,"1138":1,"2194":1}}],["1234then",{"2":{"1047":1}}],["1234the",{"2":{"325":1,"527":1,"965":1,"1176":1,"1371":1,"1433":1,"2216":1,"2831":1,"2854":1}}],["1234set",{"2":{"1577":1}}],["1234specify",{"2":{"1531":1}}],["1234sqlsqlbegin",{"2":{"2869":1}}],["1234sqlsql",{"2":{"1154":1}}],["1234supported",{"2":{"1143":1}}],["1234signs",{"2":{"288":1}}],["1234profile",{"2":{"2380":1}}],["1234produces",{"2":{"563":1}}],["1234postgrest",{"2":{"1087":1,"1114":1}}],["1234when",{"2":{"1017":1,"1141":1}}],["1234with",{"2":{"478":1,"479":1}}],["1234csharpcsharppublic",{"2":{"2255":1}}],["1234configuration",{"2":{"1974":1,"2607":1,"2651":1,"2652":1}}],["1234clients",{"2":{"644":1}}],["1234custom",{"2":{"175":1}}],["1234endpoint",{"2":{"2537":1}}],["1234exporttypes",{"2":{"1571":1}}],["1234events",{"2":{"643":1}}],["1234equivalent",{"2":{"641":1}}],["1234only",{"2":{"642":1}}],["1234result",{"2":{"2343":1}}],["1234response",{"2":{"2264":1}}],["1234receives",{"2":{"631":1,"632":1,"633":1}}],["1234returns",{"2":{"585":1}}],["1234here",{"2":{"390":1}}],["1234add",{"2":{"2750":1,"2798":1}}],["1234and",{"2":{"917":1}}],["1234a",{"2":{"302":1}}],["1234all",{"2":{"262":1,"645":1,"2323":1}}],["1234method",{"2":{"243":1}}],["12345by",{"2":{"2823":1}}],["12345both",{"2":{"1135":1,"2596":1}}],["12345previous",{"2":{"2589":1}}],["12345default",{"2":{"2426":1}}],["12345different",{"2":{"1142":1}}],["12345unlike",{"2":{"2302":1}}],["12345uses",{"2":{"1653":1,"1778":1}}],["12345use",{"2":{"168":1,"1642":1,"1643":1}}],["12345old",{"2":{"2255":1}}],["12345or",{"2":{"2010":1}}],["12345reference",{"2":{"2021":1}}],["12345result",{"2":{"2320":1}}],["12345results",{"2":{"1149":1}}],["12345response",{"2":{"539":1}}],["12345null",{"2":{"2010":1}}],["12345npgsql",{"2":{"1173":1}}],["12345jsonjson",{"2":{"2010":1}}],["12345filter",{"2":{"1839":1}}],["12345to",{"2":{"2786":1}}],["12345target",{"2":{"2266":1}}],["12345tip",{"2":{"1769":1}}],["12345that",{"2":{"1074":1}}],["12345this",{"2":{"905":1,"914":1,"1386":1,"1607":1,"1615":1,"2076":1,"2656":1,"2687":1,"2791":1,"2840":1}}],["12345then",{"2":{"2868":1}}],["12345the",{"2":{"105":1,"180":1,"251":1,"353":1,"414":1,"819":1,"902":1,"1032":1,"2272":1,"2359":1,"2789":1,"2790":1}}],["12345keys",{"2":{"1660":1}}],["12345every",{"2":{"2802":1}}],["12345exclude",{"2":{"1839":1}}],["12345example",{"2":{"1609":1}}],["12345equivalent",{"2":{"1387":1}}],["12345if",{"2":{"1532":1,"1658":1}}],["12345insert",{"2":{"1051":1}}],["123455",{"2":{"1193":1}}],["12345common",{"2":{"1138":1}}],["12345clients",{"2":{"666":1}}],["12345see",{"2":{"2718":1}}],["12345set",{"2":{"1241":1}}],["12345supported",{"2":{"2264":1}}],["12345supabase",{"2":{"1119":1}}],["12345structuredcontent",{"2":{"1824":1}}],["12345sql",{"2":{"1398":2}}],["12345sqlsqlcomment",{"2":{"1532":1}}],["12345sqlsql",{"2":{"711":1,"2861":1}}],["123452",{"2":{"1105":1}}],["12345you",{"2":{"915":1}}],["12345after",{"2":{"2611":1}}],["12345accepted",{"2":{"2394":1}}],["12345a",{"2":{"2328":1}}],["12345and",{"2":{"1386":1}}],["12345as",{"2":{"914":1}}],["12345available",{"2":{"470":1,"556":1}}],["12345when",{"2":{"2655":1,"2689":1}}],["12345where",{"2":{"1574":1}}],["12345warning",{"2":{"1706":1}}],["12345want",{"2":{"834":1}}],["12345without",{"2":{"584":1}}],["12345with",{"2":{"323":1,"900":1}}],["12345wrap",{"2":{"308":1}}],["12345here",{"2":{"168":1}}],["12345get",{"2":{"167":1,"612":1,"613":1,"2322":1,"2845":1}}],["123456failures",{"2":{"2535":1}}],["123456forwarded",{"2":{"1707":1}}],["123456for",{"2":{"121":1}}],["123456it",{"2":{"2461":1}}],["123456if",{"2":{"1517":1,"1706":1,"1709":1,"1770":1}}],["123456parallel",{"2":{"2346":1}}],["123456parsequery",{"2":{"1575":1}}],["123456previously",{"2":{"2271":1}}],["123456returns",{"2":{"2054":1,"2055":1}}],["123456result",{"2":{"623":1}}],["123456response",{"2":{"562":1}}],["123456teardown",{"2":{"2112":1}}],["123456tip",{"2":{"2020":1}}],["123456this",{"2":{"767":1,"1708":1,"2021":1,"2062":1,"2265":1,"2774":1,"2860":1}}],["123456there",{"2":{"2726":1}}],["123456these",{"2":{"2079":1,"2653":1}}],["123456then",{"2":{"1141":1}}],["123456the",{"2":{"215":1,"419":1,"622":1,"775":1,"915":1,"1042":1,"1078":1,"1102":1,"1456":1,"1608":1,"1769":1,"2017":1,"2060":1,"2733":1}}],["123456note",{"2":{"1833":1}}],["123456now",{"2":{"1398":1}}],["123456databasepollinginterval",{"2":{"2542":1}}],["123456danger",{"2":{"1716":1}}],["123456deferred",{"2":{"1079":1}}],["123456stores",{"2":{"1654":1}}],["123456self",{"2":{"421":1}}],["123456errors",{"2":{"1568":1}}],["123456example",{"2":{"1518":1}}],["123456every",{"2":{"1203":1}}],["123456and",{"2":{"2185":1}}],["123456any",{"2":{"2058":1}}],["123456all",{"2":{"1781":1}}],["123456azure",{"2":{"1713":1}}],["123456a",{"2":{"1398":1,"1738":1}}],["123456usage",{"2":{"1148":1}}],["123456how",{"2":{"1067":1}}],["123456with",{"2":{"1017":1,"1166":1}}],["123456when",{"2":{"48":1,"1202":1}}],["123456calling",{"2":{"964":1}}],["123456getting",{"2":{"2855":1}}],["123456get",{"2":{"376":1}}],["123456because",{"2":{"324":1}}],["123456",{"2":{"129":1,"136":1,"333":1,"354":1,"378":1,"418":1,"420":1,"466":1,"678":1,"722":1,"750":1,"787":1,"789":1,"811":1,"898":1,"1030":1,"1452":1,"1492":1,"1630":1,"1677":1,"1711":1,"1803":1,"1839":1,"1842":1,"2018":1,"2019":1,"2066":1,"2116":1,"2154":1,"2266":1,"2628":1,"2653":1,"2664":1,"2687":1,"2809":1,"2861":1,"2876":1}}],["1234567get",{"2":{"2846":1}}],["1234567connecting",{"2":{"2830":1}}],["1234567cache",{"2":{"2265":1}}],["1234567caveat",{"2":{"1326":1}}],["1234567note",{"2":{"2786":1}}],["1234567npgsqlrest",{"2":{"2768":1}}],["1234567environment",{"2":{"2697":1}}],["1234567excel",{"2":{"893":1}}],["1234567previous",{"2":{"2588":1}}],["1234567per",{"2":{"964":1}}],["1234567behaviour",{"2":{"2476":1}}],["1234567before",{"2":{"2342":1}}],["1234567best",{"2":{"1145":1,"1146":1}}],["1234567void",{"2":{"2273":1}}],["1234567via",{"2":{"2208":1}}],["1234567when",{"2":{"2272":1}}],["1234567without",{"2":{"372":1}}],["1234567only",{"2":{"2059":1}}],["1234567or",{"2":{"1176":1}}],["1234567for",{"2":{"1973":1}}],["1234567aws",{"2":{"1712":1}}],["1234567any",{"2":{"1052":1}}],["1234567default",{"2":{"1974":1,"2607":1}}],["1234567development",{"2":{"1534":1}}],["1234567direct",{"2":{"1930":1}}],["1234567danger",{"2":{"1641":1}}],["1234567if",{"2":{"903":1}}],["1234567result",{"2":{"621":1,"2339":1}}],["1234567request",{"2":{"520":1}}],["1234567so",{"2":{"2760":1}}],["1234567single",{"2":{"2011":1}}],["1234567similar",{"2":{"1118":1}}],["1234567stores",{"2":{"1655":1}}],["1234567small",{"2":{"1073":1}}],["1234567schema",{"2":{"1005":1}}],["1234567send",{"2":{"2836":1}}],["1234567setting",{"2":{"2377":1}}],["1234567set",{"2":{"1890":1}}],["1234567selective",{"2":{"710":1}}],["1234567see",{"2":{"121":1,"1514":1,"2737":1}}],["1234567sqlsqlcomment",{"2":{"390":1}}],["1234567sqlsql",{"2":{"354":1,"960":1,"1135":1}}],["1234567token",{"2":{"2554":1}}],["1234567trust",{"2":{"1715":1}}],["1234567there",{"2":{"650":1}}],["1234567the",{"2":{"531":1,"586":1,"1513":1,"1608":1,"1664":1,"2803":1}}],["1234567this",{"2":{"429":1,"966":1,"1343":1,"1968":1,"2313":1}}],["1234567tag",{"2":{"348":1}}],["1234567test",{"2":{"61":1,"695":1}}],["1234567",{"2":{"71":1,"271":1,"360":1,"375":1,"382":1,"503":1,"663":1,"689":1,"758":1,"760":1,"770":1,"917":1,"1022":1,"1147":1,"1196":1,"1364":1,"1455":1,"1494":1,"1534":1,"1541":1,"1545":1,"1614":1,"1678":1,"1735":1,"1779":1,"1805":1,"1867":1,"1927":1,"1970":1,"1971":1,"1979":1,"1981":1,"2162":1,"2214":1,"2310":1,"2334":1,"2549":1,"2587":1,"2761":1}}],["12345678behind",{"2":{"2835":1}}],["12345678both",{"2":{"310":1}}],["12345678sqlsql",{"2":{"2809":1}}],["12345678see",{"2":{"2774":1}}],["12345678host",{"2":{"2808":1}}],["12345678hybridcache",{"2":{"1515":1}}],["12345678opentelemetry",{"2":{"2804":1}}],["12345678options",{"2":{"2565":1}}],["12345678or",{"2":{"2297":1}}],["12345678get",{"2":{"2319":1}}],["12345678filepattern",{"2":{"2841":1}}],["12345678function",{"2":{"2824":1}}],["12345678flow",{"2":{"2187":1}}],["12345678for",{"2":{"35":1,"1217":1,"2792":1}}],["12345678individual",{"2":{"2008":1}}],["12345678if",{"2":{"997":1}}],["12345678warning",{"2":{"2089":1}}],["12345678with",{"2":{"2006":1}}],["12345678when",{"2":{"380":1,"1516":1,"1759":1,"1912":1,"2333":1}}],["12345678default",{"2":{"1973":1}}],["12345678note",{"2":{"2822":1}}],["12345678now",{"2":{"1413":1}}],["12345678nginx",{"2":{"1711":1}}],["12345678this",{"2":{"1168":1,"1171":1,"1579":1,"2833":1}}],["12345678three",{"2":{"1150":1}}],["12345678then",{"2":{"2821":1}}],["12345678the",{"2":{"184":1,"1068":1,"1859":1}}],["12345678transform",{"2":{"888":1}}],["12345678two",{"2":{"864":1}}],["12345678eight",{"2":{"1368":1}}],["12345678even",{"2":{"715":1}}],["12345678events",{"2":{"641":1}}],["12345678equivalent",{"2":{"247":1}}],["12345678result",{"2":{"614":1}}],["12345678response",{"2":{"566":1}}],["12345678post",{"2":{"565":1,"2320":1}}],["12345678an",{"2":{"2003":1}}],["12345678and",{"2":{"1398":1,"1399":1}}],["12345678as",{"2":{"1386":1}}],["12345678a",{"2":{"386":1,"836":1}}],["12345678common",{"2":{"1731":1}}],["12345678change",{"2":{"309":1}}],["12345678call",{"2":{"254":1,"255":1,"256":1,"257":1}}],["12345678creates",{"2":{"248":1,"249":1,"250":1}}],["12345678",{"2":{"37":1,"211":1,"436":1,"510":1,"710":1,"719":1,"723":1,"785":1,"957":1,"1316":1,"1418":1,"1450":1,"1462":1,"1603":1,"1663":1,"1725":1,"1758":1,"1776":1,"1892":1,"2012":1,"2027":1,"2069":1,"2085":1,"2173":1,"2174":1,"2297":1,"2415":1,"2684":1,"2691":1,"2782":1,"2783":1,"2784":1}}],["123456789verbose",{"2":{"2800":1}}],["123456789configuration",{"2":{"2265":1}}],["123456789jsonjson",{"2":{"2256":1}}],["123456789multiple",{"2":{"2202":1}}],["123456789an",{"2":{"2531":1}}],["123456789a",{"2":{"2111":1}}],["123456789guarantees",{"2":{"2040":1}}],["123456789get",{"2":{"611":1}}],["123456789high",{"2":{"1944":1}}],["123456789uses",{"2":{"1662":1,"1780":1}}],["123456789using",{"2":{"961":1}}],["123456789example",{"2":{"2565":1}}],["123456789encrypts",{"2":{"1661":1}}],["123456789equivalent",{"2":{"438":1,"1920":1}}],["123456789two",{"2":{"1431":1}}],["123456789that",{"2":{"1073":1}}],["123456789the",{"2":{"415":1,"700":1,"860":1,"1056":1,"1152":1,"1154":1,"1567":1}}],["123456789you",{"2":{"1386":1}}],["123456789browser",{"2":{"2836":1}}],["123456789breaking",{"2":{"1157":1,"1948":1}}],["123456789both",{"2":{"724":1}}],["123456789npgsqlrest",{"2":{"1086":1}}],["123456789now",{"2":{"305":1}}],["123456789key",{"2":{"893":1}}],["123456789syntax",{"2":{"2529":1}}],["123456789status",{"2":{"1360":1}}],["123456789sql",{"2":{"1371":1}}],["123456789sqlpage",{"2":{"833":1}}],["123456789sqlsql",{"2":{"665":1,"1179":1,"1458":1,"1664":1}}],["123456789see",{"2":{"430":1,"455":1,"577":1}}],["123456789warning",{"2":{"1640":1}}],["123456789with",{"2":{"436":1,"1054":1,"2181":1}}],["123456789when",{"2":{"157":1}}],["123456789response",{"2":{"128":1}}],["12345678910key",{"2":{"2828":1}}],["12345678910features",{"2":{"2580":1}}],["12345678910apply",{"2":{"2471":1}}],["12345678910admin",{"2":{"2187":1}}],["12345678910note",{"2":{"2207":1}}],["12345678910random",{"2":{"2098":1}}],["12345678910request",{"2":{"521":1,"523":1}}],["12345678910response",{"2":{"335":1,"488":1}}],["12345678910useful",{"2":{"2063":1}}],["12345678910users",{"2":{"21":1}}],["12345678910set",{"2":{"1959":1}}],["12345678910serve",{"2":{"1758":1}}],["12345678910tip",{"2":{"1543":1}}],["12345678910this",{"2":{"1187":1,"1672":1,"1771":1,"2580":1}}],["12345678910that",{"2":{"958":1}}],["12345678910then",{"2":{"1192":1}}],["12345678910the",{"2":{"186":1,"322":1,"1139":1,"1197":1,"1337":1,"2175":1,"2293":1,"2540":1}}],["12345678910hybrid",{"2":{"1147":1}}],["12345678910or",{"2":{"1138":1,"1141":1,"1142":1}}],["12345678910only",{"2":{"18":1,"19":1,"20":1}}],["12345678910configuration",{"2":{"2265":1}}],["12345678910clients",{"2":{"542":1}}],["12345678910calling",{"2":{"2304":1}}],["12345678910call",{"2":{"405":1,"406":1}}],["12345678910without",{"2":{"2339":1}}],["12345678910with",{"2":{"423":1,"1045":1}}],["12345678910when",{"2":{"336":1,"1569":1}}],["12345678910placeholders",{"2":{"212":1}}],["12345678910default",{"2":{"140":1}}],["12345678910different",{"2":{"116":1}}],["1234567891011levels",{"2":{"2804":1}}],["1234567891011channel",{"2":{"2795":1}}],["1234567891011or",{"2":{"2874":1}}],["1234567891011output",{"2":{"2607":1}}],["1234567891011only",{"2":{"1161":1}}],["1234567891011on",{"2":{"453":1}}],["1234567891011produced",{"2":{"2410":1}}],["1234567891011protect",{"2":{"1149":1}}],["1234567891011move",{"2":{"2378":1}}],["1234567891011skiptypes",{"2":{"1580":1}}],["1234567891011signs",{"2":{"289":1,"290":1}}],["1234567891011single",{"2":{"277":1,"2212":1}}],["1234567891011available",{"2":{"1174":1}}],["1234567891011100",{"2":{"1158":1}}],["123456789101112finally",{"2":{"2824":1}}],["123456789101112for",{"2":{"1063":1}}],["123456789101112jsonjson",{"2":{"2803":1}}],["123456789101112jsoncjsonc",{"2":{"2532":1}}],["123456789101112endpoints",{"2":{"2381":1}}],["123456789101112equivalent",{"2":{"48":1,"157":1,"184":1,"288":1,"1567":1,"1664":1}}],["123456789101112cache",{"2":{"2274":1}}],["123456789101112codepost",{"2":{"187":1,"2294":1}}],["123456789101112production",{"2":{"1663":1}}],["123456789101112per",{"2":{"1111":1,"1852":1,"2383":1}}],["123456789101112sqlsql",{"2":{"2813":1}}],["123456789101112sqlsqlcreate",{"2":{"2183":1}}],["123456789101112seed",{"2":{"1442":1}}],["123456789101112single",{"2":{"1117":1}}],["123456789101112output",{"2":{"1974":1}}],["123456789101112on",{"2":{"1408":1}}],["123456789101112or",{"2":{"1398":1}}],["123456789101112all",{"2":{"1398":1}}],["123456789101112any",{"2":{"1068":1}}],["123456789101112because",{"2":{"2836":1}}],["123456789101112but",{"2":{"1390":1}}],["123456789101112browser",{"2":{"492":1}}],["123456789101112when",{"2":{"1331":1,"1515":1,"2264":1}}],["123456789101112without",{"2":{"374":1,"2338":1}}],["123456789101112this",{"2":{"1020":1}}],["123456789101112the",{"2":{"213":1,"351":1,"938":1,"1413":1,"1420":1,"1690":1,"1740":1,"1951":1,"2206":1,"2288":1,"2292":1,"2391":1}}],["123456789101112returns",{"2":{"1370":1,"2850":1}}],["123456789101112response",{"2":{"334":1,"491":1,"540":1,"541":1}}],["123456789101112rows",{"2":{"949":1}}],["123456789101112now",{"2":{"982":1}}],["123456789101112no",{"2":{"888":1,"1419":1}}],["123456789101112note",{"2":{"545":1}}],["123456789101112",{"2":{"279":1,"365":1,"592":1,"772":1,"1340":1,"1463":1,"1909":1,"1966":1,"2035":1,"2077":1,"2111":1,"2126":1,"2138":1,"2147":1,"2186":1,"2196":1,"2330":1,"2807":1}}],["12345678910111213point",{"2":{"2875":1}}],["12345678910111213an",{"2":{"2869":1}}],["12345678910111213long",{"2":{"2812":1}}],["12345678910111213one",{"2":{"2767":1}}],["12345678910111213response",{"2":{"2549":1}}],["12345678910111213request",{"2":{"544":1}}],["12345678910111213htmlhtml",{"2":{"2476":1}}],["12345678910111213when",{"2":{"1733":1}}],["12345678910111213development",{"2":{"1646":1}}],["12345678910111213for",{"2":{"1217":1}}],["12345678910111213example",{"2":{"2266":1}}],["12345678910111213each",{"2":{"1193":1}}],["12345678910111213equivalent",{"2":{"415":1}}],["12345678910111213this",{"2":{"1391":1,"2869":1}}],["12345678910111213the",{"2":{"998":1,"2303":1}}],["12345678910111213that",{"2":{"916":1}}],["12345678910111213supported",{"2":{"2611":1}}],["12345678910111213sqlsqlcreate",{"2":{"2184":1}}],["12345678910111213sqlsql",{"2":{"1179":1}}],["12345678910111213see",{"2":{"1058":1}}],["12345678910111213sequences",{"2":{"695":1}}],["12345678910111213store",{"2":{"308":1}}],["12345678910111213both",{"2":{"366":1}}],["12345678910111213",{"2":{"60":1,"476":1,"477":1,"490":1,"543":1,"1503":1,"1505":1,"1952":1,"1953":1,"1986":1,"2015":1,"2029":1,"2079":1,"2335":1,"2432":1}}],["1234567891011121314deferrable",{"2":{"2868":1}}],["1234567891011121314upload",{"2":{"2549":1}}],["1234567891011121314using",{"2":{"2264":1}}],["1234567891011121314failed",{"2":{"2441":1}}],["1234567891011121314for",{"2":{"1482":1,"2429":1}}],["1234567891011121314values",{"2":{"2040":1}}],["1234567891011121314validation",{"2":{"1449":1}}],["1234567891011121314generate",{"2":{"1758":1}}],["1234567891011121314set",{"2":{"1570":1}}],["1234567891011121314sqlsqlcomment",{"2":{"107":1}}],["1234567891011121314breaking",{"2":{"1464":1}}],["1234567891011121314you",{"2":{"1410":1}}],["1234567891011121314what",{"2":{"1386":1}}],["1234567891011121314when",{"2":{"1305":1}}],["1234567891011121314with",{"2":{"1348":1}}],["1234567891011121314all",{"2":{"1105":1}}],["1234567891011121314and",{"2":{"835":1}}],["1234567891011121314if",{"2":{"986":1}}],["1234567891011121314that",{"2":{"1059":1}}],["1234567891011121314the",{"2":{"914":1,"926":1,"1033":1,"2873":1}}],["1234567891011121314test",{"2":{"40":1,"705":1}}],["1234567891011121314npgsqlrest",{"2":{"881":1,"1192":1}}],["1234567891011121314response",{"2":{"489":1}}],["1234567891011121314",{"2":{"278":1,"426":1,"478":1,"894":1,"1179":1,"1245":1,"1375":1,"1446":1,"1498":1,"1599":1,"1629":1,"1689":1,"1807":1,"1987":1,"1999":1,"2183":1,"2199":1}}],["1234567891011121314equivalent",{"2":{"128":1}}],["123456789101112131415direct",{"2":{"2344":1}}],["123456789101112131415if",{"2":{"1742":1,"2290":1}}],["123456789101112131415we",{"2":{"1386":1}}],["123456789101112131415a",{"2":{"2764":1}}],["123456789101112131415all",{"2":{"2332":1}}],["123456789101112131415annotations",{"2":{"1362":1}}],["123456789101112131415as",{"2":{"916":1}}],["123456789101112131415see",{"2":{"1361":1}}],["123456789101112131415sqlsqlcomment",{"2":{"106":1}}],["123456789101112131415everything",{"2":{"1113":1}}],["123456789101112131415cache",{"2":{"2205":1}}],["123456789101112131415calling",{"2":{"916":1}}],["123456789101112131415custom",{"2":{"2193":1}}],["123456789101112131415configuration",{"2":{"1058":1}}],["123456789101112131415open",{"2":{"970":1}}],["123456789101112131415this",{"2":{"2591":1}}],["123456789101112131415that",{"2":{"886":1}}],["123456789101112131415then",{"2":{"1196":1}}],["123456789101112131415the",{"2":{"50":1,"812":1,"935":1,"1044":1,"1308":1}}],["123456789101112131415for",{"2":{"659":1}}],["123456789101112131415result",{"2":{"622":1}}],["123456789101112131415request",{"2":{"493":1}}],["123456789101112131415both",{"2":{"306":1,"814":1}}],["123456789101112131415behavior",{"2":{"214":1}}],["123456789101112131415",{"2":{"263":1,"1752":1,"1931":1,"1988":1,"1989":1,"2061":1,"2127":1,"2128":1,"2255":1,"2686":1}}],["12345678910111213141516login",{"2":{"2554":1}}],["12345678910111213141516configuration",{"2":{"2265":1}}],["12345678910111213141516http",{"2":{"2264":1}}],["12345678910111213141516my",{"2":{"2187":1}}],["12345678910111213141516for",{"2":{"1984":1}}],["12345678910111213141516separate",{"2":{"1571":1}}],["12345678910111213141516sql",{"2":{"1371":1}}],["12345678910111213141516sqlsql",{"2":{"1179":1}}],["12345678910111213141516tip",{"2":{"1547":1}}],["12345678910111213141516the",{"2":{"1150":1,"1410":1,"1697":1}}],["12345678910111213141516there",{"2":{"297":1}}],["12345678910111213141516as",{"2":{"1394":1}}],["12345678910111213141516and",{"2":{"915":1}}],["12345678910111213141516warning",{"2":{"1502":1}}],["12345678910111213141516what",{"2":{"2176":1}}],["12345678910111213141516when",{"2":{"1191":1}}],["12345678910111213141516why",{"2":{"1185":1}}],["12345678910111213141516we",{"2":{"915":1}}],["12345678910111213141516without",{"2":{"2337":1}}],["12345678910111213141516with",{"2":{"454":1}}],["12345678910111213141516if",{"2":{"658":1}}],["1234567891011121314151617default",{"2":{"2587":1}}],["1234567891011121314151617codenpgsqlrest",{"2":{"2526":1}}],["1234567891011121314151617configuration",{"2":{"2346":1}}],["1234567891011121314151617if",{"2":{"1409":1}}],["1234567891011121314151617inline",{"2":{"378":1}}],["1234567891011121314151617as",{"2":{"1395":1}}],["1234567891011121314151617here",{"2":{"1387":1}}],["1234567891011121314151617route",{"2":{"1176":1}}],["1234567891011121314151617key",{"2":{"1105":1}}],["1234567891011121314151617the",{"2":{"1193":1,"2171":1,"2872":1}}],["1234567891011121314151617these",{"2":{"1056":1}}],["1234567891011121314151617then",{"2":{"817":1}}],["1234567891011121314151617notice",{"2":{"961":1}}],["1234567891011121314151617where",{"2":{"584":1}}],["1234567891011121314151617when",{"2":{"361":1,"904":1,"2549":1}}],["1234567891011121314151617",{"2":{"428":1,"449":1,"813":1,"992":1,"1023":1,"1029":1,"1240":1,"1336":1,"1529":1,"1650":1,"1900":1,"1992":1,"2130":1,"2427":1}}],["123456789101112131415161718with",{"2":{"1416":1}}],["123456789101112131415161718we",{"2":{"849":1}}],["123456789101112131415161718note",{"2":{"1153":1,"1582":1}}],["123456789101112131415161718supabase",{"2":{"1088":1}}],["123456789101112131415161718sqlsqlcomment",{"2":{"1069":1}}],["123456789101112131415161718the",{"2":{"2283":1}}],["123456789101112131415161718this",{"2":{"723":1}}],["123456789101112131415161718test",{"2":{"62":1}}],["123456789101112131415161718",{"2":{"292":1,"312":1,"751":1,"887":1,"1310":1,"1333":1,"1510":1,"1587":1,"1617":1,"1889":1,"2037":1,"2145":1,"2255":1,"2534":1,"2572":1,"2575":1,"2762":1}}],["12345678910111213141516171819a",{"2":{"2470":1}}],["12345678910111213141516171819and",{"2":{"917":1,"2804":1}}],["12345678910111213141516171819behavior",{"2":{"1924":1}}],["12345678910111213141516171819prod",{"2":{"1417":1}}],["12345678910111213141516171819your",{"2":{"1184":1}}],["12345678910111213141516171819so",{"2":{"916":1}}],["12345678910111213141516171819when",{"2":{"916":1}}],["12345678910111213141516171819if",{"2":{"777":1}}],["1234567891011121314151617181920migrations",{"2":{"2873":1}}],["1234567891011121314151617181920load",{"2":{"2836":1}}],["1234567891011121314151617181920previous",{"2":{"2586":1}}],["1234567891011121314151617181920per",{"2":{"2375":1}}],["1234567891011121314151617181920key",{"2":{"2549":1}}],["1234567891011121314151617181920and",{"2":{"1391":1}}],["1234567891011121314151617181920assign",{"2":{"1154":1}}],["12345678910111213141516171819203",{"2":{"1105":1}}],["1234567891011121314151617181920the",{"2":{"903":1,"1188":1,"1218":1,"2193":1,"2434":1}}],["1234567891011121314151617181920then",{"2":{"577":1}}],["1234567891011121314151617181920each",{"2":{"479":1}}],["123456789101112131415161718192021now",{"2":{"2825":1}}],["123456789101112131415161718192021notes",{"2":{"1067":1}}],["123456789101112131415161718192021example",{"2":{"2575":1}}],["123456789101112131415161718192021a",{"2":{"1958":1}}],["123456789101112131415161718192021development",{"2":{"1863":1}}],["123456789101112131415161718192021response",{"2":{"1386":1}}],["12345678910111213141516171819202122codenpgsqlrest",{"2":{"2860":1}}],["12345678910111213141516171819202122calling",{"2":{"914":1}}],["12345678910111213141516171819202122here",{"2":{"2810":1}}],["12345678910111213141516171819202122test",{"2":{"1061":1}}],["12345678910111213141516171819202122the",{"2":{"884":1,"922":1,"1355":1,"1911":1}}],["12345678910111213141516171819202122without",{"2":{"1054":1}}],["12345678910111213141516171819202122when",{"2":{"679":1}}],["12345678910111213141516171819202122you",{"2":{"904":1}}],["12345678910111213141516171819202122equivalent",{"2":{"797":1}}],["12345678910111213141516171819202122",{"2":{"773":1,"883":1,"889":1,"991":1,"1236":1,"1539":1,"1810":1,"1814":1,"1897":1,"2255":1,"2256":1}}],["1234567891011121314151617181920212223features",{"2":{"2590":1}}],["1234567891011121314151617181920212223notes",{"2":{"2581":1}}],["1234567891011121314151617181920212223login",{"2":{"2187":1}}],["1234567891011121314151617181920212223tip",{"2":{"1714":1}}],["1234567891011121314151617181920212223the",{"2":{"646":1}}],["1234567891011121314151617181920212223sqlsql",{"2":{"1056":1}}],["1234567891011121314151617181920212223in",{"2":{"439":1}}],["1234567891011121314151617181920212223",{"2":{"313":1,"1373":1,"1921":1,"2033":1}}],["123456789101112131415161718192021222324value",{"2":{"2333":1}}],["123456789101112131415161718192021222324first",{"2":{"1395":1}}],["123456789101112131415161718192021222324frontend",{"2":{"1342":1}}],["123456789101112131415161718192021222324key",{"2":{"1358":1}}],["123456789101112131415161718192021222324red",{"2":{"994":1}}],["123456789101112131415161718192021222324the",{"2":{"936":1,"1429":1}}],["123456789101112131415161718192021222324",{"2":{"310":1,"756":1,"1655":1,"1669":1,"1698":1,"2144":1}}],["12345678910111213141516171819202122232425sqlsql",{"2":{"2834":1}}],["12345678910111213141516171819202122232425see",{"2":{"1356":1}}],["12345678910111213141516171819202122232425in",{"2":{"1396":1}}],["12345678910111213141516171819202122232425",{"2":{"1317":1,"1936":1}}],["12345678910111213141516171819202122232425open",{"2":{"1076":1}}],["12345678910111213141516171819202122232425consoleconsole$",{"2":{"1074":1}}],["12345678910111213141516171819202122232425each",{"2":{"1053":1}}],["12345678910111213141516171819202122232425equivalent",{"2":{"298":1,"2176":1}}],["12345678910111213141516171819202122232425notice",{"2":{"979":1}}],["1234567891011121314151617181920212223242526sqlsqlcomment",{"2":{"2380":1}}],["1234567891011121314151617181920212223242526see",{"2":{"1197":1}}],["1234567891011121314151617181920212223242526",{"2":{"1773":1}}],["1234567891011121314151617181920212223242526development",{"2":{"1678":1}}],["1234567891011121314151617181920212223242526both",{"2":{"1193":1}}],["1234567891011121314151617181920212223242526the",{"2":{"988":1}}],["1234567891011121314151617181920212223242526response",{"2":{"333":1}}],["123456789101112131415161718192021222324252627sqlsql",{"2":{"2375":1}}],["123456789101112131415161718192021222324252627key",{"2":{"2277":1}}],["123456789101112131415161718192021222324252627what",{"2":{"1581":1}}],["123456789101112131415161718192021222324252627when",{"2":{"888":1}}],["123456789101112131415161718192021222324252627a",{"2":{"1458":1}}],["123456789101112131415161718192021222324252627as",{"2":{"917":1}}],["123456789101112131415161718192021222324252627endpoints",{"2":{"1520":1}}],["123456789101112131415161718192021222324252627edge",{"2":{"1107":1}}],["123456789101112131415161718192021222324252627equivalent",{"2":{"312":1}}],["123456789101112131415161718192021222324252627the",{"2":{"1024":1}}],["123456789101112131415161718192021222324252627in",{"2":{"956":1}}],["12345678910111213141516171819202122232425262728rule",{"2":{"2575":1}}],["12345678910111213141516171819202122232425262728partition",{"2":{"2379":1}}],["12345678910111213141516171819202122232425262728per",{"2":{"1162":1}}],["12345678910111213141516171819202122232425262728",{"2":{"1955":1,"1995":1,"2142":1,"2834":1}}],["12345678910111213141516171819202122232425262728equivalent",{"2":{"1547":1}}],["1234567891011121314151617181920212223242526272829the",{"2":{"1199":1}}],["1234567891011121314151617181920212223242526272829sqlsql",{"2":{"1150":1}}],["1234567891011121314151617181920212223242526272829",{"2":{"919":1,"1320":1,"2132":1}}],["1234567891011121314151617181920212223242526272829with",{"2":{"919":1}}],["123456789101112131415161718192021222324252627282930proxy",{"2":{"2549":1}}],["123456789101112131415161718192021222324252627282930postgresql",{"2":{"1567":1}}],["123456789101112131415161718192021222324252627282930frontend",{"2":{"1321":1}}],["123456789101112131415161718192021222324252627282930and",{"2":{"1366":1}}],["123456789101112131415161718192021222324252627282930a",{"2":{"452":1}}],["123456789101112131415161718192021222324252627282930",{"2":{"207":1,"2766":1,"2829":1}}],["12345678910111213141516171819202122232425262728293031equivalent",{"2":{"1689":1}}],["12345678910111213141516171819202122232425262728293031sqlsql",{"2":{"1529":1}}],["12345678910111213141516171819202122232425262728293031note",{"2":{"1386":1}}],["12345678910111213141516171819202122232425262728293031fastapi",{"2":{"1366":1}}],["1234567891011121314151617181920212223242526272829303132sqlsql",{"2":{"2829":1}}],["1234567891011121314151617181920212223242526272829303132a",{"2":{"2537":1}}],["1234567891011121314151617181920212223242526272829303132",{"2":{"1431":1,"1483":1}}],["1234567891011121314151617181920212223242526272829303132the",{"2":{"1357":1}}],["1234567891011121314151617181920212223242526272829303132that",{"2":{"1309":1}}],["1234567891011121314151617181920212223242526272829303132test",{"2":{"938":1}}],["123456789101112131415161718192021222324252627282930313233equivalent",{"2":{"1504":1}}],["123456789101112131415161718192021222324252627282930313233from",{"2":{"1410":1}}],["123456789101112131415161718192021222324252627282930313233spring",{"2":{"1366":1}}],["123456789101112131415161718192021222324252627282930313233this",{"2":{"980":1}}],["1234567891011121314151617181920212223242526272829303132333435sqlsql",{"2":{"2762":1}}],["123456789101112131415161718192021222324252627282930313233343536",{"2":{"1907":1}}],["1234567891011121314151617181920212223242526272829303132333435363738",{"2":{"1736":1,"2255":1,"2264":1}}],["1234567891011121314151617181920212223242526272829303132333435363738this",{"2":{"1393":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940the",{"2":{"1216":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041",{"2":{"1332":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142several",{"2":{"1372":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142",{"2":{"1234":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243see",{"2":{"1836":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243four",{"2":{"1408":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243",{"2":{"1232":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445post",{"2":{"2815":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849",{"2":{"2257":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950the",{"2":{"1427":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051",{"2":{"2123":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051plus",{"2":{"1320":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051the",{"2":{"1060":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152",{"2":{"2146":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253typed",{"2":{"1366":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354this",{"2":{"1177":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657",{"2":{"2247":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758",{"2":{"1062":1,"1960":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566added",{"2":{"2634":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768",{"2":{"2632":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970",{"2":{"2254":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071parameters",{"2":{"1376":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374",{"2":{"1221":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677what",{"2":{"1338":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495",{"2":{"2701":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102",{"2":{"1339":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137",{"2":{"1792":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158plus",{"2":{"1026":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121that",{"2":{"920":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104the",{"2":{"996":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778",{"2":{"1222":1,"2635":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677",{"2":{"990":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273",{"2":{"1220":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869key",{"2":{"995":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364that",{"2":{"1021":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960",{"2":{"913":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455this",{"2":{"1335":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455",{"2":{"930":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152the",{"2":{"1214":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152using",{"2":{"894":1}}],["12345678910111213141516171819202122232425262728293031323334353637383940414243444546",{"2":{"774":1,"1469":1}}],["123456789101112131415161718192021222324252627282930313233343536373839404142434445",{"2":{"209":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041key",{"2":{"937":1}}],["1234567891011121314151617181920212223242526272829303132333435363738394041so",{"2":{"918":1}}],["123456789101112131415161718192021222324252627282930313233343536373839npgsqlrest",{"2":{"1215":1}}],["123456789101112131415161718192021222324252627282930313233343536373839",{"2":{"764":1,"1553":1,"1893":1}}],["123456789101112131415161718192021222324252627282930313233343536373839equivalent",{"2":{"37":1}}],["12345678910111213141516171819202122232425262728293031323334353637",{"2":{"1318":1,"2633":1}}],["12345678910111213141516171819202122232425262728293031323334353637test",{"2":{"38":1}}],["12345678910111213141516171819202122232425262728293031323334the",{"2":{"1416":1}}],["12345678910111213141516171819202122232425262728293031323334key",{"2":{"1055":1}}],["12345678910111213141516171819202122232425262728293031323334",{"2":{"929":1,"1213":1}}],["123456789101112131415161718192021222324252627282930313233",{"2":{"928":1,"2093":1}}],["12345678910111213141516171819202122232425262728293031the",{"2":{"934":1}}],["12345678910111213141516171819202122232425262728293031this",{"2":{"39":1}}],["12345678910111213141516171819202122232425262728293031as",{"2":{"916":1}}],["12345678910111213141516171819202122232425262728293031",{"2":{"208":1,"977":1,"1239":1,"1800":1}}],["12345678910111213141516171819202122232425262728this",{"2":{"918":1}}],["123456789101112131415161718192021222324252627",{"2":{"664":1,"1347":1,"2148":1}}],["123456789101112131415161718192021",{"2":{"451":1,"798":1,"1345":1,"1548":1,"1916":1}}],["1234567891011121314151617181920",{"2":{"427":1,"665":1,"691":1,"765":1,"766":1,"1024":1,"1307":1,"1342":1,"1458":1,"1597":1,"1633":1,"2042":1}}],["12345678910111213141516171819default",{"2":{"332":1}}],["12345678910111213141516171819the",{"2":{"309":1,"989":1,"1057":1}}],["12345678910111213141516171819",{"2":{"264":1,"736":1,"799":1,"1235":1}}],["123456789101112131415161718equivalent",{"2":{"206":1,"733":1,"2147":1}}],["12345678910111213141516",{"2":{"298":1,"370":1,"755":1,"815":1,"924":1,"1163":1,"1374":1,"1453":1,"1504":1,"1598":1,"1673":1,"1721":1,"1926":1,"1990":1,"2046":1,"2073":1,"2080":1,"2184":1}}],["12345678910111213141516equivalent",{"2":{"61":1,"750":1,"1727":1}}],["1234567891011which",{"2":{"1359":1}}],["1234567891011wrapintransaction",{"2":{"1070":1}}],["1234567891011without",{"2":{"826":1}}],["1234567891011note",{"2":{"1050":1}}],["1234567891011http",{"2":{"1038":1}}],["1234567891011three",{"2":{"2871":1}}],["1234567891011that",{"2":{"1398":1}}],["1234567891011this",{"2":{"1019":1,"1386":1,"1574":1,"2078":1,"2200":1,"2775":1}}],["1234567891011then",{"2":{"1207":1}}],["1234567891011the",{"2":{"449":1,"705":1,"976":1,"1019":1,"1159":1,"1160":1,"2314":1}}],["1234567891011for",{"2":{"887":1}}],["1234567891011equivalent",{"2":{"510":1,"592":1,"677":1,"722":1,"811":1,"1632":1,"2076":1}}],["1234567891011both",{"2":{"352":1}}],["1234567891011directives",{"2":{"203":1}}],["1234567891011",{"2":{"137":1,"291":1,"408":1,"469":1,"593":1,"594":1,"734":1,"735":1,"752":1,"762":1,"763":1,"967":1,"1034":1,"1321":1,"1546":1,"1628":1,"1685":1,"1763":1,"1802":1,"1902":1,"1904":1,"1905":1,"1954":1,"1975":1,"2075":1,"2532":1,"2699":1,"2788":1,"2814":1,"2825":1}}],["1234567891011use",{"2":{"119":1,"2177":1}}],["12345678910",{"2":{"72":1,"117":1,"408":1,"467":1,"468":1,"511":1,"963":1,"1051":1,"1189":1,"1426":1,"1445":1,"1488":1,"1542":1,"1638":1,"1646":1,"1683":1,"1727":1,"1804":1,"1863":1,"1899":1,"1903":1,"1994":1,"2068":1,"2279":1,"2555":1,"2689":1,"2739":1,"2769":1}}],["12345678910equivalent",{"2":{"7":1,"16":1,"71":1,"104":1,"115":1,"136":1,"360":1,"401":1,"466":1,"487":1,"503":1,"520":1,"539":1}}],["123456789",{"2":{"9":1,"17":1,"62":1,"522":1,"733":1,"797":1,"897":1,"965":1,"1152":1,"1313":1,"1412":1,"1622":1,"1625":1,"1702":1,"1745":1,"1775":1,"1944":1,"1993":1,"2028":1,"2064":1,"2067":1,"2104":1,"2204":1,"2308":1,"2357":1,"2544":1,"2565":1,"2700":1,"2813":1,"2865":1}}],["12345",{"2":{"34":1,"38":1,"40":1,"49":1,"57":1,"58":1,"206":1,"272":1,"280":1,"342":1,"343":1,"392":1,"565":1,"566":1,"614":1,"677":1,"783":1,"800":1,"817":1,"827":1,"893":1,"899":1,"903":2,"960":1,"1154":1,"1211":1,"1380":1,"1433":1,"1457":1,"1476":1,"1478":1,"1606":1,"1613":1,"1627":1,"1632":1,"1656":1,"1657":1,"1676":1,"1728":1,"1744":1,"1809":1,"1851":1,"1856":1,"1868":1,"2012":1,"2023":1,"2024":1,"2025":1,"2255":1,"2257":1,"2265":1,"2382":1,"2451":1,"2572":1,"2575":1,"2595":1,"2649":1,"2665":1,"2685":1,"2762":1,"2765":1,"2804":1,"2861":1}}],["1234",{"2":{"24":1,"61":1,"94":1,"95":1,"96":1,"97":1,"118":1,"184":1,"265":1,"273":1,"274":1,"275":1,"318":1,"333":2,"344":1,"351":1,"393":1,"394":1,"413":1,"434":1,"521":1,"532":1,"562":1,"574":1,"601":1,"602":1,"603":1,"604":1,"711":1,"732":1,"762":1,"763":1,"828":1,"938":1,"1130":1,"1138":1,"1161":1,"1370":1,"1491":1,"1525":1,"1533":1,"1569":1,"1730":1,"1824":3,"1855":1,"2178":1,"2202":1,"2212":1,"2285":1,"2292":1,"2526":1,"2596":1,"2665":1,"2692":1,"2781":1,"2850":1,"2852":1,"2880":1}}],["123",{"2":{"7":1,"8":1,"23":1,"83":1,"84":1,"85":1,"86":1,"107":1,"115":1,"128":1,"138":1,"147":1,"148":1,"149":1,"184":1,"186":1,"195":1,"196":1,"254":2,"314":1,"325":1,"332":3,"373":1,"403":1,"406":2,"417":1,"441":1,"442":1,"443":1,"444":1,"489":1,"493":1,"501":1,"533":1,"550":1,"564":1,"572":1,"573":1,"775":1,"896":1,"900":1,"925":1,"941":1,"1017":3,"1078":1,"1142":1,"1158":1,"1189":1,"1290":1,"1312":1,"1314":1,"1315":1,"1326":2,"1378":1,"1386":3,"1391":1,"1393":3,"1394":1,"1590":1,"1620":1,"1726":1,"1747":1,"1920":1,"1973":3,"2001":1,"2010":2,"2040":1,"2041":1,"2092":1,"2118":1,"2119":1,"2181":1,"2257":1,"2277":2,"2286":1,"2292":1,"2293":1,"2476":1,"2528":1,"2533":1,"2536":1,"2587":3,"2703":1,"2785":2,"2866":1}}],["1",{"0":{"96":1,"842":1,"940":1,"956":1,"1000":1,"1019":1,"1067":1,"1090":1,"1213":1,"1220":1,"1247":1,"1265":1,"1287":2,"1288":1,"1289":1,"1297":1,"1307":1,"1355":1,"1388":1,"1395":1,"1401":1,"1725":1,"1825":1,"1870":1,"2172":1,"2239":1,"2241":1,"2260":2,"2262":1,"2268":1,"2275":1,"2311":1,"2408":1,"2458":1,"2507":1,"2517":1,"2526":1,"2552":1,"2578":1,"2586":1,"2592":1,"2636":1,"2820":1},"1":{"843":1,"844":1,"845":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"2173":1,"2174":1,"2175":1,"2242":1,"2261":2,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2276":1,"2277":1,"2278":1,"2279":1,"2312":1,"2313":1,"2314":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2508":1,"2509":1,"2510":1,"2511":1,"2512":1,"2513":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2553":1,"2554":1,"2555":1,"2579":1,"2580":1,"2581":1,"2582":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2637":1,"2638":1,"2821":1,"2822":1},"2":{"5":1,"7":2,"19":1,"20":1,"22":1,"34":1,"35":1,"38":2,"39":1,"40":1,"48":1,"69":1,"81":2,"84":1,"88":1,"92":3,"105":3,"106":2,"107":2,"115":2,"126":1,"128":1,"169":1,"184":1,"263":1,"268":1,"271":2,"272":2,"273":4,"274":1,"275":3,"283":1,"310":2,"313":1,"329":1,"332":4,"333":2,"334":2,"335":2,"388":1,"399":1,"462":1,"485":1,"488":1,"489":1,"493":1,"508":1,"528":1,"531":2,"542":2,"562":2,"565":2,"577":3,"590":1,"608":1,"609":3,"611":3,"612":2,"614":3,"621":1,"622":3,"623":1,"664":1,"665":1,"691":1,"695":1,"696":1,"699":1,"701":1,"704":1,"705":2,"709":1,"711":2,"714":1,"715":1,"730":1,"760":1,"761":2,"762":2,"764":1,"765":1,"766":1,"770":1,"771":1,"772":2,"774":1,"775":1,"817":2,"826":1,"852":1,"865":1,"867":1,"869":2,"871":2,"872":1,"873":1,"874":1,"877":1,"881":1,"882":1,"883":3,"884":1,"885":3,"888":4,"893":2,"896":1,"897":1,"898":1,"900":1,"904":1,"911":1,"913":6,"914":1,"915":1,"916":3,"917":2,"918":8,"919":12,"924":1,"926":1,"928":3,"929":4,"930":5,"937":1,"941":2,"948":1,"956":3,"969":1,"977":2,"979":1,"982":1,"986":1,"988":1,"989":1,"990":2,"994":1,"995":1,"997":1,"1027":3,"1044":5,"1045":1,"1047":1,"1053":3,"1062":2,"1067":2,"1068":2,"1070":1,"1073":1,"1079":1,"1080":1,"1084":1,"1090":5,"1098":1,"1101":1,"1105":1,"1117":1,"1132":1,"1135":4,"1138":1,"1139":1,"1141":2,"1142":1,"1147":1,"1150":6,"1152":3,"1153":1,"1154":3,"1160":1,"1177":2,"1181":2,"1189":1,"1191":1,"1192":1,"1193":2,"1195":1,"1199":2,"1220":1,"1221":1,"1222":1,"1232":1,"1255":9,"1257":13,"1258":2,"1263":1,"1264":3,"1265":2,"1266":1,"1267":4,"1268":1,"1270":4,"1271":1,"1272":2,"1277":1,"1278":1,"1283":1,"1284":9,"1285":15,"1287":6,"1288":4,"1289":7,"1290":4,"1291":6,"1293":4,"1295":4,"1297":6,"1299":16,"1301":5,"1309":1,"1322":2,"1335":1,"1336":1,"1338":1,"1339":3,"1349":1,"1363":1,"1368":1,"1369":2,"1374":3,"1375":2,"1380":2,"1386":1,"1391":1,"1419":2,"1420":1,"1427":2,"1429":6,"1431":1,"1433":2,"1450":1,"1451":3,"1453":2,"1454":1,"1458":2,"1464":3,"1504":1,"1511":2,"1515":1,"1519":1,"1520":2,"1521":1,"1523":1,"1525":1,"1529":6,"1582":2,"1587":1,"1589":1,"1590":2,"1597":3,"1598":1,"1617":1,"1620":1,"1622":1,"1623":1,"1625":1,"1633":1,"1676":1,"1677":1,"1685":2,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1702":1,"1703":1,"1706":1,"1707":3,"1711":2,"1712":1,"1715":1,"1716":1,"1740":1,"1752":1,"1753":2,"1758":1,"1769":1,"1792":54,"1800":1,"1807":1,"1808":1,"1814":1,"1819":2,"1822":1,"1823":1,"1824":1,"1825":1,"1827":1,"1852":1,"1897":1,"1898":1,"1907":1,"1924":2,"1973":3,"1974":5,"1991":2,"2010":2,"2011":1,"2039":1,"2060":1,"2061":1,"2098":1,"2102":1,"2109":1,"2113":1,"2129":1,"2131":1,"2141":1,"2144":2,"2146":2,"2148":2,"2157":1,"2162":3,"2164":1,"2165":2,"2171":1,"2173":1,"2174":1,"2179":1,"2180":1,"2187":1,"2212":2,"2222":1,"2223":1,"2224":1,"2225":3,"2229":1,"2234":1,"2236":1,"2237":1,"2238":1,"2239":5,"2240":1,"2253":2,"2254":2,"2257":1,"2267":1,"2274":1,"2279":1,"2288":1,"2292":1,"2319":1,"2320":2,"2324":1,"2338":1,"2339":5,"2342":7,"2344":1,"2354":1,"2375":2,"2376":2,"2377":2,"2378":1,"2380":3,"2381":1,"2383":1,"2397":4,"2398":4,"2414":2,"2415":1,"2427":1,"2440":1,"2465":2,"2481":4,"2493":1,"2515":1,"2528":1,"2530":2,"2532":1,"2533":1,"2534":2,"2535":2,"2537":2,"2540":2,"2543":2,"2545":1,"2550":1,"2551":3,"2555":1,"2572":1,"2575":3,"2580":1,"2586":16,"2587":4,"2588":9,"2590":1,"2591":1,"2607":6,"2614":1,"2621":3,"2633":5,"2634":1,"2635":1,"2655":1,"2659":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2677":1,"2679":1,"2692":1,"2695":1,"2696":1,"2760":1,"2762":1,"2763":1,"2785":1,"2792":1,"2801":1,"2815":1,"2823":2,"2824":2,"2834":2,"2836":1,"2842":1,"2845":2,"2866":1,"2868":1,"2871":1,"2872":1,"2873":4,"2877":1,"2880":1}}],["k8s",{"2":{"2476":1}}],["krb5",{"2":{"2385":1}}],["k6",{"2":{"1255":1}}],["kb",{"2":{"1255":1,"1792":2,"1991":3,"2125":2,"2397":8,"2398":1}}],["kong",{"2":{"1088":2,"1101":1,"1114":1,"1119":1}}],["kanel",{"2":{"1097":1}}],["karen",{"2":{"913":1}}],["kafka",{"2":{"913":2}}],["kubernetes",{"0":{"1715":1,"1772":1},"1":{"1773":1,"1774":1},"2":{"868":1,"869":1,"1100":1,"1127":1,"1420":1,"1762":1,"1767":1,"1768":1,"1780":1,"1791":1,"1792":4,"2040":2,"2450":1,"2474":1,"2634":5}}],["knobs",{"2":{"2419":1,"2429":1,"2431":1}}],["knob",{"2":{"868":1,"2438":1}}],["know",{"2":{"844":1,"845":1,"847":1,"848":1,"849":2,"851":1,"852":1,"854":1,"866":1,"878":1,"879":1,"888":1,"904":2,"911":1,"986":1,"1056":1,"1132":1,"1133":1,"1150":1,"1206":1,"1385":1,"1386":4,"1399":1,"1400":1,"1402":2,"1441":1,"1442":1,"1518":1,"1767":1,"1768":1,"1792":2,"2265":1,"2634":2,"2815":1,"2868":1}}],["knowing",{"2":{"843":1,"2795":1,"2867":1}}],["knows",{"2":{"305":1,"841":1,"844":1,"851":1,"852":1,"868":1,"944":1,"1401":1,"1403":1,"1431":1,"1437":1,"2537":1,"2540":1,"2845":1,"2879":1}}],["knownnetworks",{"2":{"1702":1,"1703":1,"1708":2,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1717":1,"1792":2,"2633":2}}],["knownproxies",{"2":{"1702":1,"1703":1,"1707":1,"1708":1,"1711":1,"1713":1,"1716":1,"1717":1,"1792":1,"2633":1}}],["known",{"0":{"1707":1,"1708":1,"2466":1},"2":{"4":1,"13":1,"29":1,"44":1,"55":1,"68":1,"78":1,"91":1,"125":1,"132":1,"144":1,"165":1,"182":1,"192":1,"261":1,"282":1,"296":1,"328":1,"339":1,"357":1,"369":1,"383":1,"412":1,"433":1,"458":1,"473":1,"484":1,"497":1,"507":1,"515":1,"549":1,"569":1,"589":1,"598":1,"607":1,"618":1,"627":1,"636":1,"649":1,"683":1,"794":1,"823":1,"933":1,"1604":1,"1609":2,"1703":2,"1717":1,"1792":5,"1831":3,"1833":1,"2481":1,"2633":3,"2659":1,"2661":1,"2669":1}}],["knew",{"2":{"852":1}}],["kid",{"2":{"1403":1}}],["kicks",{"2":{"1162":1}}],["kit",{"2":{"1106":1}}],["kiota",{"2":{"873":1}}],["kind=utc",{"2":{"2451":1}}],["kind=unspecified",{"2":{"1856":1,"2455":1}}],["kind=local",{"2":{"1856":1,"2451":1,"2455":1}}],["kinda",{"2":{"1400":1}}],["kinds",{"2":{"871":1,"1792":1,"2107":1,"2529":1,"2537":1,"2865":1,"2879":1}}],["kind",{"2":{"835":1,"865":1,"872":1,"874":1,"1385":1,"1773":1,"2451":2}}],["killed",{"2":{"2157":2,"2442":1,"2543":2,"2758":1,"2881":1}}],["kill",{"2":{"214":1,"844":1,"1722":1,"1792":2,"2153":1,"2502":1,"2532":1,"2537":1,"2769":1}}],["kernel",{"2":{"2385":1}}],["kebabcaseurls",{"2":{"1792":1,"1836":1,"1841":1,"1842":1,"1863":1,"2701":1}}],["kebabcaselurls",{"2":{"1609":1,"2659":1}}],["kebab",{"2":{"1576":1,"1792":1,"1841":1}}],["kestrel",{"0":{"1984":1,"1994":1,"2661":1},"1":{"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1},"2":{"874":1,"1199":2,"1466":1,"1609":2,"1611":1,"1635":1,"1648":1,"1666":1,"1718":1,"1787":1,"1792":3,"1794":1,"1812":1,"1946":1,"1963":1,"1978":1,"1980":1,"1981":1,"1984":3,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1992":1,"1993":1,"1994":1,"1995":1,"2091":1,"2121":1,"2398":1,"2645":4,"2661":2,"2701":1,"2706":1,"2776":2,"2823":1,"2824":1}}],["kept",{"2":{"337":1,"388":1,"852":1,"1379":1,"1607":1,"1840":1,"2103":1,"2267":1,"2482":1}}],["keepalivepingpolicy",{"2":{"1792":1,"1992":1}}],["keepalivepingtimeout",{"2":{"1792":1,"1992":1}}],["keepalivepingdelay",{"2":{"1792":1,"1992":1}}],["keepalivetimeout",{"2":{"1792":1,"1990":1,"1991":1,"1995":1}}],["keep",{"0":{"2103":1},"2":{"316":1,"531":1,"565":1,"567":1,"845":1,"851":2,"852":1,"869":1,"871":1,"902":1,"917":1,"974":1,"1038":1,"1074":1,"1076":1,"1125":1,"1126":1,"1128":1,"1253":1,"1254":1,"1412":1,"1422":1,"1615":1,"1714":1,"1792":3,"1823":1,"1991":1,"2093":1,"2094":1,"2110":1,"2112":1,"2320":1,"2416":1,"2486":1,"2495":1,"2530":1,"2532":1,"2537":3,"2540":1,"2672":1,"2724":1,"2758":1,"2795":1,"2803":1,"2835":1,"2857":1,"2872":1,"2881":1}}],["keeps",{"2":{"298":1,"307":1,"349":1,"354":1,"383":1,"448":1,"565":1,"663":1,"848":1,"852":1,"864":1,"1045":1,"1049":1,"1054":1,"1067":2,"1108":1,"1113":1,"1156":1,"1270":1,"1412":1,"1571":1,"1792":1,"1802":1,"2106":1,"2157":1,"2164":1,"2176":1,"2177":1,"2391":1,"2430":1,"2484":1,"2537":3,"2543":1,"2544":1,"2773":1,"2795":1}}],["keeping",{"2":{"174":1,"307":1,"851":1,"916":1,"1033":1,"1094":1,"1832":1,"1833":1,"2419":1,"2481":1,"2629":1}}],["keypath",{"2":{"1792":1,"1987":1}}],["keyencryption",{"2":{"1650":1,"1651":1,"1660":1,"1661":1,"1662":1,"1792":1,"2297":1,"2565":4}}],["keyed",{"2":{"567":1,"1069":1,"1157":2,"1792":1,"1948":1,"1949":1,"2223":1,"2372":1,"2378":1,"2440":1,"2442":1}}],["keycloak",{"2":{"1045":1,"1825":1}}],["key=secret123",{"2":{"1733":1,"2264":1}}],["key=sk",{"2":{"1044":1}}],["key=value",{"2":{"1608":1,"1792":1,"2272":1,"2662":1,"2691":1,"2692":1,"2693":1}}],["key=api",{"2":{"187":1,"2294":1}}],["keyboard",{"2":{"1037":1,"1381":1}}],["key>",{"2":{"155":1,"1792":3}}],["key",{"0":{"116":1,"117":1,"258":1,"992":1,"1261":1,"1278":1,"1516":1,"1609":1,"1659":1,"1905":1,"1928":1,"1987":1,"2297":1,"2494":1,"2495":1,"2565":1,"2659":1,"2754":1},"1":{"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1660":1,"1661":1,"1662":1},"2":{"101":1,"105":1,"108":1,"109":1,"113":1,"120":1,"154":1,"155":2,"158":1,"159":1,"162":1,"170":1,"187":8,"188":2,"189":1,"191":1,"207":2,"212":2,"214":4,"240":1,"306":1,"363":1,"387":1,"390":4,"394":2,"396":1,"480":1,"504":1,"528":1,"531":4,"532":3,"534":1,"535":2,"555":1,"565":1,"567":1,"586":1,"694":1,"737":2,"738":1,"739":1,"746":1,"764":1,"774":1,"780":1,"827":1,"848":1,"851":2,"852":3,"868":2,"888":1,"894":1,"913":3,"924":1,"932":1,"956":1,"977":2,"985":1,"992":1,"993":1,"1005":1,"1049":1,"1050":1,"1053":1,"1054":2,"1062":1,"1069":3,"1075":1,"1088":1,"1090":1,"1097":1,"1101":1,"1105":2,"1107":1,"1111":2,"1142":1,"1148":1,"1150":1,"1169":1,"1181":2,"1209":1,"1210":5,"1213":5,"1215":7,"1217":1,"1222":1,"1232":1,"1233":1,"1234":1,"1236":7,"1237":2,"1238":1,"1240":2,"1243":1,"1307":2,"1336":1,"1338":1,"1339":1,"1355":1,"1374":1,"1453":1,"1454":2,"1458":1,"1475":2,"1511":1,"1516":1,"1518":1,"1519":1,"1521":1,"1527":1,"1531":2,"1540":3,"1544":1,"1574":5,"1609":2,"1651":4,"1653":1,"1655":4,"1661":1,"1664":11,"1678":1,"1684":4,"1697":1,"1726":1,"1727":2,"1733":4,"1738":1,"1743":2,"1788":1,"1792":41,"1795":1,"1802":1,"1867":1,"1886":3,"1887":2,"1889":2,"1905":2,"1948":1,"1951":1,"1956":2,"1987":2,"2040":1,"2075":2,"2077":2,"2124":2,"2125":1,"2126":1,"2127":1,"2128":1,"2129":1,"2130":1,"2131":1,"2143":1,"2175":1,"2184":2,"2193":1,"2205":1,"2223":1,"2224":1,"2232":1,"2249":1,"2252":2,"2264":8,"2265":5,"2279":3,"2283":2,"2285":2,"2291":1,"2294":8,"2296":3,"2297":6,"2320":1,"2323":2,"2336":1,"2337":1,"2340":1,"2353":2,"2370":1,"2372":1,"2375":1,"2378":1,"2379":1,"2380":5,"2389":1,"2405":2,"2409":1,"2410":2,"2411":2,"2413":1,"2414":1,"2415":1,"2416":1,"2419":1,"2431":1,"2436":2,"2438":5,"2441":1,"2445":1,"2446":1,"2447":1,"2461":2,"2463":3,"2465":1,"2466":3,"2472":1,"2476":1,"2477":2,"2482":1,"2483":2,"2486":1,"2494":3,"2495":5,"2502":3,"2519":2,"2539":1,"2544":1,"2551":1,"2554":4,"2558":1,"2572":1,"2580":2,"2591":1,"2614":1,"2622":2,"2659":1,"2661":1,"2678":1,"2679":1,"2692":1,"2695":1,"2705":2,"2734":1,"2737":1,"2765":2,"2768":4,"2795":2,"2836":1,"2847":1,"2850":1,"2852":1,"2868":1}}],["keyword",{"0":{"49":1,"2484":1},"2":{"133":1,"277":1,"369":1,"375":1,"564":1,"992":1,"1042":1,"1559":1,"1792":1,"2192":1,"2212":1,"2505":1,"2529":1,"2822":1}}],["keywords",{"0":{"17":1,"172":1,"178":1,"242":1,"326":1,"355":1,"398":1,"522":1,"729":1,"744":1,"808":1},"2":{"221":1,"515":1,"1335":3,"1336":1,"1339":13,"2191":2,"2192":1,"2482":2}}],["keystroke",{"2":{"1076":1}}],["keys",{"0":{"738":1,"739":1,"1142":1,"2279":1},"2":{"1":1,"188":3,"238":1,"390":2,"393":1,"559":1,"567":1,"588":1,"617":1,"626":1,"775":1,"829":1,"851":2,"852":2,"857":1,"864":1,"868":2,"869":1,"877":1,"900":1,"1054":9,"1067":2,"1079":2,"1096":1,"1098":1,"1105":1,"1133":1,"1139":1,"1210":1,"1211":1,"1213":1,"1243":1,"1251":2,"1370":2,"1458":1,"1475":2,"1477":1,"1511":7,"1516":2,"1518":1,"1540":1,"1542":1,"1574":1,"1604":1,"1606":1,"1609":4,"1650":3,"1651":6,"1653":2,"1654":3,"1655":9,"1656":1,"1657":1,"1659":1,"1661":1,"1662":4,"1663":3,"1664":1,"1792":36,"1862":1,"1867":1,"1868":1,"1908":1,"2000":1,"2008":3,"2013":1,"2225":2,"2265":14,"2279":1,"2296":3,"2297":12,"2320":1,"2326":1,"2330":1,"2340":2,"2372":1,"2375":1,"2406":1,"2410":2,"2413":2,"2425":1,"2427":1,"2428":1,"2436":3,"2438":4,"2444":1,"2464":1,"2465":1,"2472":1,"2486":1,"2495":2,"2504":1,"2546":1,"2551":3,"2565":3,"2622":1,"2659":4,"2662":1,"2669":1,"2678":1,"2679":2,"2692":1,"2696":2,"2741":1,"2745":1,"2754":1,"2757":2,"2764":1,"2841":1,"2853":1,"2859":1,"2868":1}}],["otel",{"2":{"2804":1}}],["otlresourceattributes",{"2":{"1792":1}}],["otlpresourceattributes",{"2":{"1800":1,"1807":2,"2804":1}}],["otlpminimumlevel",{"2":{"1792":1,"1800":1,"1807":2,"2804":1}}],["otlpheaders",{"2":{"1792":1,"1800":1,"1807":2}}],["otlpprotocol",{"2":{"1792":1,"1800":1,"1807":2,"2804":1}}],["otlpendpoint",{"2":{"1792":1,"1800":1,"1807":2,"2804":1}}],["otlp",{"2":{"1792":2,"1807":5,"2804":1}}],["others",{"2":{"567":1,"1075":1,"1129":1,"1147":1,"1792":1,"2381":1,"2530":1,"2767":1}}],["otherwise",{"2":{"177":1,"349":1,"384":1,"436":1,"447":2,"745":1,"844":1,"867":1,"869":1,"934":1,"1174":2,"1185":1,"1525":1,"1527":1,"1609":1,"1792":7,"1847":1,"1922":1,"1924":1,"2094":1,"2107":1,"2187":1,"2197":1,"2428":1,"2509":1,"2537":2,"2659":1,"2723":1,"2763":1,"2811":1,"2823":1,"2879":1}}],["other",{"0":{"306":1,"534":1,"543":1,"906":1,"1071":1,"1397":1,"1415":1,"2252":1,"2258":1,"2267":1},"1":{"907":1,"908":1,"909":1,"1398":1,"1399":1,"2259":1},"2":{"33":1,"109":1,"174":1,"175":1,"179":1,"188":1,"214":1,"245":1,"296":1,"297":2,"301":1,"316":1,"320":1,"334":1,"387":1,"390":1,"418":2,"446":2,"518":1,"528":1,"585":1,"587":2,"624":1,"684":1,"686":1,"695":2,"702":1,"815":1,"831":2,"835":1,"838":1,"840":1,"841":2,"843":2,"844":1,"847":1,"851":1,"852":3,"861":3,"864":1,"865":1,"868":2,"871":1,"876":1,"933":2,"934":1,"948":1,"989":1,"1005":1,"1035":1,"1098":1,"1101":1,"1185":1,"1190":1,"1232":1,"1254":3,"1386":2,"1388":1,"1390":1,"1394":1,"1397":1,"1400":1,"1403":1,"1404":1,"1409":2,"1410":1,"1422":1,"1439":1,"1523":1,"1527":1,"1559":1,"1651":1,"1658":1,"1662":1,"1664":1,"1688":1,"1733":1,"1738":1,"1792":5,"1824":2,"1847":1,"1888":1,"1895":1,"1898":1,"1974":1,"2020":2,"2099":1,"2106":1,"2175":1,"2189":1,"2256":1,"2264":1,"2267":1,"2284":1,"2289":1,"2291":1,"2296":1,"2297":1,"2306":1,"2319":1,"2365":1,"2372":1,"2378":1,"2380":1,"2394":1,"2442":2,"2453":1,"2466":1,"2481":2,"2484":1,"2511":1,"2528":2,"2531":3,"2533":1,"2537":1,"2603":1,"2607":1,"2608":1,"2629":1,"2632":1,"2691":1,"2811":3,"2841":1,"2864":2,"2878":1}}],["octocat",{"2":{"1737":1}}],["occasional",{"2":{"1160":1}}],["occurred",{"2":{"2336":1,"2608":1}}],["occurrence",{"2":{"319":1}}],["occurs",{"2":{"1672":1,"1742":1,"1792":2,"2255":2,"2290":1,"2684":1}}],["occur",{"2":{"1151":1,"1672":1,"1792":2}}],["oven",{"2":{"1420":1}}],["overcomplicate",{"2":{"1402":1}}],["overcomes",{"2":{"928":1}}],["overcounted",{"2":{"869":1}}],["overkill",{"2":{"908":1}}],["overflow",{"2":{"876":1}}],["overall",{"0":{"875":1},"2":{"1160":1,"1402":1,"1764":1,"1766":1,"1792":2,"2634":3}}],["overwhelming",{"2":{"1147":1}}],["overwhelmingly",{"2":{"860":1,"873":1}}],["overwritten",{"2":{"2183":1}}],["overwrite",{"2":{"1554":1,"1753":1,"1792":3,"1898":1,"2254":1}}],["overwrites",{"2":{"448":1,"2127":1}}],["overwriting",{"2":{"701":1}}],["overstate",{"2":{"849":1}}],["oversized",{"2":{"75":1,"1431":1,"1925":1,"2517":1,"2523":1}}],["overloaded",{"2":{"2597":1,"2666":1}}],["overload",{"2":{"2451":2,"2455":1,"2597":1,"2666":2}}],["overloading",{"2":{"835":1,"1095":1,"2858":1}}],["overlay",{"2":{"873":1,"2872":1}}],["overlapping",{"2":{"864":2,"1159":1}}],["overlap",{"2":{"845":1,"864":2,"2538":1}}],["overengineering",{"2":{"841":1}}],["overview",{"0":{"202":1,"1211":1,"1445":1,"1469":1,"1488":1,"1498":1,"1510":1,"1520":1,"1539":1,"1553":1,"1587":1,"1638":1,"1650":1,"1669":1,"1683":1,"1702":1,"1721":1,"1752":1,"1763":1,"1800":1,"1814":1,"1836":1,"1867":1,"1897":1,"1916":1,"1936":1,"1948":1,"1966":1,"1999":1,"2015":1,"2033":1,"2046":1,"2073":1,"2085":1,"2093":1,"2123":1,"2138":1,"2154":1,"2701":1,"2772":1},"1":{"2773":1,"2774":1,"2775":1,"2776":1},"2":{"912":1,"2701":1}}],["over",{"0":{"858":1,"862":1},"1":{"859":1,"860":1,"861":1,"863":1,"864":1,"865":1},"2":{"174":1,"307":1,"308":1,"317":1,"349":1,"840":1,"841":4,"843":1,"847":2,"848":1,"852":7,"856":1,"860":4,"861":1,"872":1,"874":1,"918":1,"948":1,"1039":1,"1041":1,"1043":2,"1045":1,"1046":1,"1075":1,"1096":4,"1111":1,"1125":1,"1140":1,"1170":1,"1177":1,"1183":1,"1199":1,"1205":1,"1256":1,"1385":1,"1386":1,"1392":1,"1399":1,"1401":1,"1440":1,"1523":1,"1792":3,"1840":1,"1910":1,"1961":1,"1983":1,"2223":1,"2266":1,"2297":1,"2380":2,"2389":1,"2402":2,"2433":1,"2435":1,"2441":1,"2451":1,"2452":1,"2463":1,"2464":1,"2465":1,"2466":1,"2472":1,"2481":2,"2490":1,"2537":2,"2628":1,"2827":1,"2858":1}}],["overhead",{"0":{"1004":1,"1013":1,"1269":1,"1292":1,"1746":1,"2347":1},"1":{"1293":1},"2":{"88":1,"388":1,"421":1,"445":1,"993":2,"1004":1,"1007":2,"1067":1,"1169":1,"1174":2,"1254":1,"1255":2,"1258":1,"1269":2,"1275":1,"1276":1,"1398":1,"1399":3,"1516":1,"1746":1,"1792":1,"1861":1,"1929":1,"2089":1,"2265":1,"2270":1,"2309":1,"2347":1,"2350":1,"2353":1,"2372":1,"2614":1}}],["overridable",{"2":{"1738":1,"2224":1,"2468":1,"2835":1}}],["overridden",{"0":{"2395":1},"2":{"108":2,"470":1,"529":1,"556":1,"1068":1,"1340":1,"1792":3,"1837":1,"1843":1,"1855":1,"1858":1,"1994":1,"2008":1,"2047":1,"2056":1,"2079":1,"2226":1,"2257":1,"2284":1,"2450":1,"2596":1,"2653":1,"2719":1}}],["overrideexpiration",{"2":{"2461":1}}],["override",{"0":{"325":1,"373":1,"419":1,"1459":1,"1606":1,"2056":1,"2305":1,"2427":1,"2674":1},"2":{"27":1,"101":1,"105":1,"108":1,"110":1,"223":1,"318":1,"326":1,"347":1,"370":1,"373":1,"375":1,"384":1,"418":1,"442":1,"443":1,"448":1,"523":1,"650":1,"669":1,"801":1,"868":2,"873":1,"964":1,"1040":1,"1067":1,"1068":2,"1105":1,"1150":1,"1316":1,"1459":2,"1471":1,"1480":1,"1523":1,"1540":1,"1544":1,"1547":1,"1604":1,"1606":3,"1792":9,"1824":1,"1860":1,"1958":1,"2056":1,"2094":1,"2109":1,"2155":1,"2306":1,"2320":1,"2321":1,"2327":1,"2375":3,"2380":1,"2381":1,"2395":1,"2410":1,"2413":1,"2430":1,"2435":2,"2470":2,"2472":2,"2481":1,"2490":1,"2681":1,"2684":1,"2689":4,"2691":3,"2733":1,"2768":1,"2808":1,"2831":1}}],["overrides",{"0":{"2079":1,"2470":1,"2699":1},"2":{"10":1,"105":2,"139":1,"352":1,"384":1,"409":1,"646":1,"868":1,"964":1,"1068":1,"1414":1,"1459":1,"1521":1,"1533":1,"1569":1,"1792":3,"1913":1,"2201":1,"2266":1,"2319":1,"2375":1,"2380":3,"2461":1,"2520":1,"2655":1,"2662":1,"2674":1,"2679":2,"2684":1,"2692":1,"2693":1,"2694":1,"2695":1,"2700":1,"2705":1,"2843":1,"2846":1}}],["overriding",{"2":{"4":1,"1951":2,"1952":2,"1953":2,"1954":2,"1958":1,"2472":1}}],["osx",{"2":{"2784":1,"2792":1}}],["os",{"2":{"1403":1,"1792":1,"2111":1,"2297":1,"2792":1,"2871":1}}],["oszknl4j",{"2":{"1051":2}}],["omission",{"2":{"2523":1}}],["omitparameterfromgeneratedrequest",{"2":{"2520":1}}],["omits",{"2":{"1792":2,"1958":1,"2109":1,"2470":1,"2486":1,"2530":1,"2537":1}}],["omitautomaticparameters",{"0":{"1569":1},"2":{"1431":1,"1553":1,"1559":2,"1569":3,"1752":1,"1753":1,"1759":3,"1792":3,"1897":1,"1898":1,"1912":3,"2164":1,"2222":1,"2520":1,"2523":1}}],["omitting",{"0":{"1759":1,"1912":1},"2":{"408":1,"1753":1,"1898":1,"2665":1}}],["omitted",{"0":{"653":1},"2":{"268":1,"409":1,"468":1,"653":1,"675":2,"1067":1,"1759":1,"1792":4,"1912":1,"2360":1,"2380":1,"2486":1,"2520":2,"2523":1,"2544":1,"2836":1}}],["omit",{"0":{"2520":1},"2":{"301":1,"1374":1,"1559":1,"1753":1,"1792":6,"1898":1,"1951":2,"1952":2,"1953":2,"1954":2,"2075":2,"2222":1,"2381":1,"2477":1,"2515":1}}],["ommitted",{"2":{"913":1}}],["ollama",{"2":{"1335":1}}],["olivia",{"2":{"913":1}}],["oltp",{"2":{"848":1,"1205":1}}],["older",{"2":{"1792":1,"2220":1}}],["oldestfirst",{"2":{"1161":1,"1792":1,"1954":2,"1960":1,"2257":1,"2443":1}}],["oldest",{"2":{"860":1,"1954":1}}],["old",{"0":{"1079":1},"2":{"174":1,"188":2,"369":1,"370":16,"592":2,"857":1,"864":1,"913":1,"984":1,"985":1,"997":1,"1157":1,"1159":1,"1400":1,"1403":1,"1435":1,"1644":1,"1948":1,"1974":1,"2216":1,"2279":1,"2296":2,"2297":2,"2332":2,"2378":1,"2416":1,"2486":1,"2498":1,"2539":1,"2607":1}}],["oh",{"2":{"844":1,"851":1,"1384":1,"1442":2}}],["oooga",{"2":{"1401":1}}],["oom",{"2":{"969":1}}],["oop",{"2":{"843":1,"851":1,"1435":1}}],["oo",{"2":{"841":7,"859":1}}],["oauth2",{"2":{"1691":3,"1694":2,"1792":5}}],["oauth",{"0":{"1059":1,"1682":1,"1690":1,"1825":1},"1":{"1060":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":2,"1692":2,"1693":2,"1694":2,"1695":2,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1},"2":{"835":1,"868":1,"1037":1,"1045":1,"1048":3,"1059":1,"1060":4,"1064":2,"1065":1,"1086":1,"1098":2,"1126":1,"1445":2,"1465":2,"1466":2,"1485":1,"1507":1,"1550":1,"1682":1,"1684":2,"1685":1,"1686":1,"1687":1,"1690":1,"1692":2,"1693":2,"1695":2,"1696":2,"1697":3,"1698":1,"1788":2,"1792":7,"1825":1,"1831":2,"1894":2,"1895":2,"2164":1,"2175":1,"2189":1,"2223":1,"2481":2,"2736":1}}],["odbc",{"2":{"834":1}}],["oids",{"2":{"2324":2}}],["oidc",{"2":{"1694":1,"1792":1}}],["oid=$",{"2":{"1364":1}}],["oid",{"0":{"751":1},"2":{"748":2,"751":4,"753":5,"777":3,"782":3,"903":4,"1355":3,"1357":3,"1358":3,"1359":1,"1362":2,"1363":1,"1364":2,"1366":1,"1367":1,"1410":3,"1412":1}}],["our",{"2":{"428":1,"845":1,"847":1,"849":3,"873":1,"914":1,"916":1,"918":2,"927":1,"989":1,"992":2,"1254":1,"1338":2,"1386":1,"1388":1,"1395":3,"1396":3,"1398":2,"1399":2,"2438":1,"2823":1,"2824":4}}],["out2",{"2":{"2807":2}}],["out1",{"2":{"2807":2}}],["outfile",{"2":{"2781":1}}],["outright",{"2":{"2425":1,"2453":1}}],["outage",{"2":{"1774":1}}],["outperforming",{"2":{"1262":1}}],["outputting",{"2":{"2667":1}}],["outputtemplate",{"2":{"1605":1,"1792":1,"1800":1,"1809":2,"1810":1,"2497":1,"2688":1}}],["outputformat",{"2":{"966":1,"967":2,"1792":1,"2046":1,"2047":1,"2054":1,"2055":1,"2056":1,"2066":1,"2067":1,"2068":1,"2069":1,"2635":1,"2638":1,"2674":1}}],["outputschema",{"2":{"1040":1,"1824":1,"2481":1,"2498":1}}],["outputs",{"2":{"852":1,"1373":1,"1799":1,"1946":1,"1974":1,"2358":1,"2607":1,"2662":1,"2668":1,"2670":1,"2671":1,"2672":1,"2694":1}}],["output",{"0":{"228":1,"229":1,"381":1,"487":1,"1374":1,"1803":1,"1804":1,"1805":1,"1807":1,"1809":1,"2053":1,"2206":1,"2662":1,"2677":1},"1":{"1806":1,"1808":1,"2054":1,"2055":1,"2056":1},"2":{"38":1,"57":1,"58":1,"61":1,"90":1,"125":1,"131":1,"167":1,"229":2,"339":1,"346":1,"388":1,"439":1,"484":1,"548":1,"587":1,"596":1,"598":1,"606":1,"675":2,"679":1,"830":1,"835":1,"876":1,"965":1,"966":1,"1037":1,"1076":1,"1110":1,"1195":1,"1259":1,"1374":1,"1401":1,"1407":2,"1414":1,"1417":1,"1435":1,"1554":1,"1559":1,"1566":1,"1569":1,"1571":1,"1609":1,"1759":1,"1791":1,"1792":9,"1798":1,"1803":2,"1804":1,"1805":1,"1807":2,"1809":2,"1973":1,"2047":1,"2102":1,"2104":1,"2108":1,"2164":1,"2221":1,"2231":1,"2261":1,"2309":1,"2322":2,"2329":1,"2333":1,"2360":2,"2389":2,"2404":1,"2407":1,"2414":1,"2415":2,"2430":1,"2456":1,"2481":1,"2484":1,"2520":1,"2535":3,"2546":1,"2567":1,"2586":1,"2589":1,"2603":1,"2635":2,"2648":1,"2659":1,"2662":1,"2666":1,"2672":1,"2673":1,"2677":1,"2678":1,"2679":2,"2694":3,"2695":1,"2696":1,"2726":1,"2792":3,"2795":1,"2804":1,"2880":2}}],["outweighs",{"2":{"975":1}}],["outofmemoryexception",{"2":{"948":1}}],["outlaws",{"2":{"865":1}}],["outgoing",{"2":{"414":1,"1434":1,"1738":1,"1739":1,"2282":1,"2283":1,"2287":1,"2302":1}}],["outerheight",{"2":{"1792":2}}],["outerwidth",{"2":{"1792":2}}],["outer",{"2":{"334":5,"1961":1,"1974":5,"2403":1,"2607":5}}],["outcomes",{"2":{"2106":1,"2536":1,"2537":1}}],["outcome",{"2":{"310":1}}],["outside",{"2":{"263":1,"716":1,"836":1,"940":1,"1078":1,"1435":1,"1930":1,"2344":1,"2545":1}}],["outbound",{"0":{"394":1,"531":1},"2":{"214":3,"386":1,"387":1,"390":2,"529":1,"535":1,"1014":1,"1106":1,"1107":1,"1430":1,"1722":1,"1743":3,"1792":3,"1862":1,"1934":1,"2107":1,"2185":1,"2222":2,"2483":2,"2500":2,"2502":2,"2504":1,"2506":1,"2510":1,"2529":1,"2537":1,"2759":1,"2760":1,"2762":1,"2765":1,"2865":1,"2879":1}}],["out",{"0":{"324":1,"412":1,"417":1,"418":1,"419":1,"421":1,"2186":1,"2300":1,"2310":1,"2404":1,"2437":1,"2455":1,"2487":1},"1":{"413":1,"414":1,"415":1,"416":1,"417":1,"418":1,"419":1,"420":1,"421":1,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1},"2":{"27":1,"74":1,"140":2,"223":1,"224":1,"282":1,"284":1,"285":1,"286":3,"288":1,"289":1,"290":1,"291":2,"298":1,"307":1,"308":1,"316":2,"349":1,"354":1,"363":1,"413":4,"414":3,"415":2,"417":2,"418":2,"419":2,"420":2,"421":2,"423":3,"426":1,"427":1,"428":1,"429":2,"456":1,"832":1,"833":2,"838":2,"843":2,"848":1,"851":3,"852":4,"857":1,"860":1,"861":1,"865":1,"868":1,"869":1,"876":1,"880":1,"966":1,"1006":1,"1033":1,"1037":1,"1042":1,"1044":1,"1081":1,"1082":1,"1092":1,"1104":1,"1105":2,"1123":1,"1126":1,"1162":1,"1318":1,"1322":1,"1326":1,"1376":2,"1386":2,"1394":1,"1396":4,"1398":1,"1401":1,"1404":2,"1405":1,"1412":2,"1427":1,"1428":2,"1437":1,"1441":3,"1442":1,"1460":1,"1465":1,"1467":1,"1481":2,"1484":1,"1594":1,"1615":1,"1669":1,"1672":1,"1678":1,"1699":1,"1745":1,"1759":1,"1792":7,"1856":1,"1898":1,"1912":1,"1934":1,"1961":1,"2101":1,"2170":1,"2175":1,"2176":1,"2186":3,"2189":1,"2224":1,"2229":2,"2253":1,"2255":2,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2309":1,"2310":2,"2313":3,"2346":1,"2347":1,"2375":1,"2388":1,"2398":1,"2404":1,"2419":1,"2421":1,"2424":1,"2431":1,"2438":1,"2451":1,"2464":1,"2466":1,"2498":1,"2518":1,"2533":1,"2535":1,"2537":1,"2539":1,"2608":1,"2728":1,"2776":1,"2806":1,"2813":3,"2817":1,"2835":1,"2855":1}}],["owasp",{"2":{"307":1,"309":1,"363":1,"1049":1,"1064":1,"1792":1,"2030":1,"2177":1,"2632":1}}],["owning",{"2":{"1382":1}}],["owns",{"2":{"865":1,"2527":1,"2833":1,"2862":1,"2867":1}}],["own",{"0":{"2767":1},"2":{"212":1,"221":1,"298":1,"309":1,"336":1,"377":1,"448":1,"479":1,"650":1,"694":1,"695":1,"696":1,"705":1,"706":1,"714":1,"716":1,"833":1,"834":1,"837":1,"843":1,"864":3,"865":1,"869":1,"914":1,"917":1,"930":1,"986":1,"1037":1,"1044":1,"1045":3,"1068":1,"1069":1,"1070":1,"1073":1,"1074":3,"1075":1,"1077":1,"1078":2,"1079":1,"1086":1,"1094":2,"1096":1,"1098":1,"1101":1,"1106":1,"1107":1,"1108":1,"1113":1,"1114":1,"1115":1,"1127":1,"1132":1,"1145":1,"1150":1,"1162":2,"1191":1,"1193":1,"1248":1,"1254":1,"1326":2,"1378":1,"1383":1,"1385":2,"1391":1,"1398":2,"1400":1,"1404":3,"1416":1,"1435":1,"1458":1,"1460":1,"1511":1,"1574":1,"1581":1,"1685":1,"1741":1,"1792":15,"1822":1,"1823":2,"1825":4,"1827":1,"1917":1,"1955":2,"1958":1,"1959":1,"1961":4,"1973":1,"2010":1,"2099":1,"2108":1,"2109":1,"2157":1,"2176":1,"2177":1,"2285":1,"2289":1,"2333":1,"2346":1,"2375":2,"2379":2,"2395":1,"2422":1,"2465":1,"2466":1,"2470":1,"2471":1,"2472":2,"2481":1,"2482":1,"2525":1,"2526":1,"2527":3,"2528":1,"2529":1,"2530":1,"2531":2,"2532":1,"2533":2,"2536":1,"2540":2,"2543":2,"2714":1,"2739":1,"2759":2,"2767":1,"2794":1,"2803":1,"2804":1,"2833":1,"2845":1,"2860":1,"2862":3,"2863":1,"2866":1,"2867":1,"2871":1,"2873":2,"2875":1,"2880":1}}],["o",{"2":{"297":2,"845":2,"849":1,"851":5,"1091":1,"1105":4,"1167":2,"1179":2,"1255":1,"1258":1,"1268":1,"1274":1,"1324":1,"1691":1,"1792":1,"1822":2,"1994":1,"2087":1,"2614":2,"2615":1,"2621":3,"2685":2,"2760":1,"2782":1,"2783":1,"2784":1}}],["okay",{"2":{"843":1}}],["ok",{"2":{"247":2,"301":1,"449":3,"540":1,"541":1,"551":2,"748":1,"763":1,"841":1,"887":1,"903":2,"995":4,"1255":1,"1342":2,"1359":1,"1360":1,"1366":2,"1384":1,"1386":2,"1403":1,"1404":1,"1408":2,"1416":2,"1435":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1442":2,"1567":2,"1766":1,"1767":1,"1768":1,"1770":1,"1782":1,"1792":2,"1855":2,"2415":1,"2562":2,"2596":2,"2824":1,"2866":1}}],["obscure",{"2":{"874":1}}],["observe",{"2":{"2466":2}}],["observed",{"2":{"865":1,"877":1}}],["observable",{"2":{"2405":1}}],["observability",{"0":{"1110":1},"2":{"868":1,"877":1,"1386":1}}],["observations",{"0":{"1278":1},"2":{"1387":1}}],["observation",{"2":{"852":1}}],["obsolete",{"2":{"840":1,"1435":1,"1443":1,"2255":1}}],["obviously",{"2":{"851":1,"918":1,"1386":1}}],["obvious",{"2":{"844":1,"876":1,"1078":1,"1409":1}}],["obfuscation",{"2":{"596":1,"868":1,"1110":1}}],["obfuscates",{"2":{"2750":1,"2798":1}}],["obfuscateauthparameterlogvalues",{"2":{"1469":1,"1470":1,"1483":1,"1792":1,"2558":1}}],["obfuscate",{"2":{"190":1,"236":1,"368":1,"589":1,"1470":1,"1792":1,"1811":1,"2805":1}}],["object>",{"2":{"1792":1,"2632":1}}],["objections",{"2":{"876":2}}],["objection",{"2":{"843":1}}],["objects",{"0":{"916":1,"917":1,"1352":1,"1362":1,"1363":1},"1":{"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":2,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"227":1,"328":1,"330":1,"334":1,"335":2,"336":1,"337":2,"567":1,"568":1,"583":1,"588":1,"614":1,"626":1,"746":1,"749":1,"791":1,"844":4,"845":1,"851":6,"854":1,"856":1,"861":1,"863":1,"871":1,"914":1,"916":1,"917":1,"919":2,"926":1,"969":1,"1037":1,"1069":1,"1095":1,"1097":1,"1099":2,"1102":1,"1111":1,"1113":1,"1121":1,"1126":1,"1157":1,"1211":1,"1243":1,"1352":1,"1362":1,"1365":1,"1375":1,"1412":1,"1429":1,"1792":6,"1948":1,"1967":2,"1974":1,"2000":1,"2009":1,"2122":1,"2126":1,"2134":1,"2156":1,"2164":1,"2165":1,"2209":1,"2217":1,"2320":1,"2330":1,"2339":1,"2378":1,"2532":1,"2537":1,"2542":1,"2546":1,"2586":1,"2587":1,"2588":1,"2607":1,"2701":1,"2840":1,"2842":1,"2851":1,"2854":1,"2878":1}}],["object",{"0":{"749":1,"753":1,"782":1,"841":1,"914":1,"1671":1,"2126":1},"1":{"750":1,"751":1,"752":1,"753":1,"783":1},"2":{"87":1,"207":1,"209":2,"210":1,"227":1,"264":1,"306":1,"361":1,"374":1,"390":1,"415":2,"423":1,"426":1,"427":2,"428":1,"429":1,"439":2,"447":1,"449":2,"452":3,"503":1,"510":1,"512":1,"567":1,"607":1,"609":1,"615":1,"618":1,"745":1,"746":2,"747":2,"748":3,"751":2,"753":9,"761":1,"762":1,"771":1,"772":1,"777":3,"781":1,"782":12,"783":2,"812":1,"813":1,"814":1,"815":1,"840":2,"841":10,"843":3,"844":6,"845":1,"848":1,"849":1,"851":12,"852":2,"854":2,"855":1,"856":2,"857":1,"864":2,"871":2,"874":1,"876":1,"902":3,"903":6,"904":1,"914":3,"915":1,"916":3,"919":1,"948":1,"968":1,"1021":1,"1024":2,"1029":1,"1041":2,"1069":1,"1086":1,"1095":1,"1097":1,"1105":4,"1157":2,"1179":2,"1214":1,"1215":2,"1216":1,"1232":2,"1234":1,"1236":1,"1239":1,"1255":1,"1258":1,"1309":1,"1321":1,"1332":1,"1338":3,"1339":6,"1341":1,"1347":2,"1353":1,"1354":1,"1355":1,"1356":1,"1358":7,"1359":1,"1363":1,"1364":1,"1370":2,"1372":1,"1376":1,"1386":1,"1402":1,"1403":1,"1410":5,"1415":1,"1427":1,"1475":1,"1477":1,"1499":1,"1511":1,"1521":1,"1540":1,"1544":1,"1558":1,"1564":1,"1574":1,"1588":1,"1596":2,"1618":1,"1624":2,"1670":1,"1684":1,"1727":2,"1732":1,"1736":1,"1745":1,"1792":21,"1807":2,"1824":3,"1848":1,"1852":1,"1857":1,"1862":2,"1921":2,"1922":1,"1924":1,"1926":1,"1948":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1973":1,"1974":1,"2000":1,"2018":1,"2034":1,"2038":4,"2039":1,"2040":1,"2094":2,"2111":2,"2112":1,"2123":2,"2124":1,"2125":1,"2126":3,"2139":1,"2143":1,"2147":2,"2195":1,"2247":1,"2264":2,"2267":1,"2273":1,"2303":1,"2304":1,"2310":1,"2313":1,"2320":2,"2330":1,"2339":3,"2346":1,"2357":2,"2372":1,"2378":1,"2383":1,"2442":1,"2456":1,"2461":2,"2476":2,"2481":3,"2482":1,"2483":1,"2491":1,"2492":1,"2498":1,"2549":5,"2575":1,"2586":1,"2588":1,"2607":1,"2664":2,"2725":1,"2763":1,"2764":1,"2766":1,"2767":1,"2810":2,"2813":2,"2815":3,"2829":2,"2836":1,"2841":1,"2850":1,"2852":1,"2871":1}}],["opportunity",{"2":{"2287":1}}],["opportunistically",{"2":{"1130":1}}],["opposed",{"2":{"1402":1}}],["opposite",{"2":{"876":1,"1203":1}}],["opinionated",{"2":{"1404":1}}],["opinion",{"2":{"839":1,"1405":1,"1435":1}}],["ops",{"2":{"347":1,"836":2,"2432":1}}],["op",{"2":{"317":1,"663":2,"664":1,"665":1,"1792":1,"2452":1,"2495":1}}],["opted",{"2":{"1792":2,"1813":1,"1824":1,"1832":1,"1833":1,"2223":1,"2479":1,"2481":2}}],["opts",{"2":{"1042":1,"2175":1,"2728":1,"2750":1,"2798":1}}],["optimal",{"2":{"1792":2,"1936":1,"1937":2,"1938":1,"1944":1,"2576":1,"2790":1}}],["optimizing",{"2":{"974":1,"1382":1,"1792":1,"2084":1}}],["optimize",{"2":{"1129":1,"1132":1,"1133":1}}],["optimized",{"2":{"874":1,"881":1,"1007":1,"2270":1,"2398":3,"2604":1}}],["optimizer",{"2":{"860":1}}],["optimizations",{"0":{"2614":1,"2622":1},"2":{"1254":2,"2236":1,"2270":3,"2614":1}}],["optimization",{"0":{"1128":1,"1164":1,"2347":1,"2621":1},"1":{"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1},"2":{"874":2,"1037":2,"1128":2,"1129":2,"1132":1,"1133":1,"1134":1,"1274":1,"1377":1,"1398":1,"1402":2,"1994":1,"2858":1}}],["optimistic",{"2":{"854":1}}],["opting",{"2":{"320":1,"1042":1,"2481":1}}],["optional",{"0":{"408":1,"1605":1,"2193":1,"2497":1,"2581":1,"2591":1,"2665":1,"2685":1,"2688":1,"2732":1},"2":{"155":1,"166":1,"244":1,"268":2,"301":1,"310":1,"370":1,"375":1,"381":1,"408":3,"409":1,"469":1,"555":1,"564":1,"650":2,"668":1,"798":2,"869":1,"995":1,"1040":1,"1042":1,"1050":1,"1063":1,"1074":2,"1076":1,"1102":1,"1181":1,"1199":1,"1217":1,"1218":1,"1221":1,"1222":1,"1237":1,"1239":3,"1305":1,"1410":1,"1515":1,"1567":1,"1569":1,"1573":1,"1604":1,"1605":1,"1690":1,"1759":1,"1792":32,"1820":1,"1821":1,"1829":1,"1852":1,"1887":1,"1888":1,"1906":1,"1912":1,"1951":3,"1952":3,"1953":3,"1954":3,"2094":1,"2102":1,"2177":1,"2193":2,"2222":1,"2223":2,"2232":1,"2237":1,"2247":2,"2254":2,"2255":3,"2265":1,"2274":1,"2333":1,"2383":1,"2406":1,"2429":1,"2436":2,"2476":1,"2477":1,"2497":2,"2498":2,"2519":1,"2520":1,"2528":1,"2529":1,"2537":1,"2581":2,"2591":1,"2634":1,"2665":2,"2674":1,"2678":1,"2684":1,"2685":7,"2688":1,"2719":1,"2723":1,"2762":2,"2765":1,"2779":1,"2788":1,"2810":1,"2811":1,"2825":1,"2848":1,"2864":1,"2869":1}}],["optionally",{"2":{"13":1,"288":1,"369":1,"452":1,"638":1,"688":2,"1243":1,"1396":1,"2252":1,"2274":1,"2332":1,"2526":1,"2728":1,"2781":1,"2782":1,"2783":1,"2784":1,"2831":1,"2846":1}}],["option",{"0":{"308":1,"309":1,"2272":1,"2350":1,"2382":1,"2383":1,"2821":1,"2822":1},"2":{"52":1,"300":1,"305":1,"307":3,"310":1,"334":1,"739":1,"747":1,"753":1,"757":1,"768":1,"776":1,"859":1,"891":1,"892":1,"917":1,"919":1,"963":1,"967":1,"971":1,"1063":1,"1102":1,"1115":1,"1117":3,"1147":1,"1217":1,"1340":1,"1752":1,"1753":1,"1754":1,"1758":3,"1792":24,"2172":1,"2177":1,"2181":1,"2249":1,"2251":2,"2253":2,"2257":1,"2259":4,"2265":2,"2272":1,"2274":2,"2383":1,"2484":1,"2515":1,"2517":2,"2520":1,"2537":1,"2539":1,"2565":1,"2572":1,"2586":1,"2587":1,"2641":2,"2660":1,"2719":1,"2728":1,"2759":1,"2794":2,"2801":1,"2805":1,"2882":1}}],["options",{"0":{"747":1,"753":1,"757":1,"768":1,"776":1,"890":1,"891":1,"892":1,"967":1,"1353":1,"1468":1,"1493":1,"1509":1,"1558":1,"1562":1,"1563":1,"1622":1,"1630":1,"1652":1,"1659":1,"1720":1,"1751":1,"1754":1,"1813":1,"1815":1,"1826":1,"1835":1,"1896":1,"1915":1,"1965":1,"1994":1,"2017":1,"2018":1,"2072":1,"2122":1,"2137":1,"2565":1,"2594":1,"2629":1,"2705":1,"2749":1},"1":{"891":1,"892":1,"1354":1,"1469":1,"1470":1,"1471":1,"1472":1,"1473":1,"1474":1,"1475":1,"1476":1,"1477":1,"1478":1,"1479":1,"1480":1,"1481":1,"1482":1,"1483":1,"1484":1,"1485":1,"1486":1,"1510":1,"1511":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1517":1,"1518":1,"1519":1,"1520":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1536":1,"1537":1,"1623":1,"1624":1,"1625":1,"1631":1,"1632":1,"1653":1,"1654":1,"1655":1,"1660":1,"1661":1,"1662":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1732":1,"1733":1,"1734":1,"1735":1,"1736":1,"1737":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1752":1,"1753":1,"1754":1,"1755":1,"1756":1,"1757":1,"1758":1,"1759":1,"1760":1,"1761":1,"1814":1,"1815":1,"1816":2,"1817":2,"1818":2,"1819":2,"1820":2,"1821":2,"1822":2,"1823":2,"1824":1,"1825":1,"1826":1,"1827":2,"1828":2,"1829":2,"1830":2,"1831":2,"1832":2,"1833":1,"1834":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1863":1,"1864":1,"1865":1,"1897":1,"1898":1,"1899":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":1,"1913":1,"1914":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":1,"1921":1,"1922":1,"1923":1,"1924":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1,"2080":1,"2081":1,"2082":1,"2083":1,"2123":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1,"2129":1,"2130":1,"2131":1,"2132":1,"2133":1,"2134":1,"2135":1,"2136":1,"2138":1,"2139":1,"2140":1,"2141":1,"2142":1,"2143":1,"2144":1,"2145":1,"2146":1,"2147":1,"2148":1,"2149":1,"2150":1,"2151":1,"2152":1,"2595":1,"2596":1},"2":{"11":1,"26":1,"28":1,"42":1,"53":1,"65":1,"76":1,"89":1,"98":1,"100":1,"101":1,"111":1,"119":1,"121":2,"122":1,"124":1,"141":1,"143":1,"150":1,"151":1,"164":2,"210":1,"217":1,"243":1,"259":1,"266":2,"293":1,"295":1,"315":1,"334":1,"338":1,"367":1,"410":1,"430":1,"431":1,"432":1,"455":1,"456":1,"457":1,"471":1,"505":1,"513":1,"525":1,"535":1,"557":1,"634":2,"647":2,"670":2,"672":1,"673":1,"680":1,"747":1,"753":1,"757":1,"768":1,"776":1,"779":1,"793":1,"851":1,"868":1,"915":1,"917":1,"1032":1,"1033":1,"1037":2,"1067":1,"1070":1,"1100":2,"1102":2,"1127":1,"1182":1,"1217":1,"1220":2,"1221":2,"1222":2,"1223":1,"1226":5,"1232":1,"1233":1,"1234":1,"1235":1,"1252":1,"1356":1,"1358":1,"1361":1,"1431":1,"1434":1,"1458":1,"1460":1,"1466":1,"1482":1,"1485":1,"1489":1,"1493":1,"1506":1,"1507":1,"1514":1,"1536":1,"1549":1,"1550":1,"1558":1,"1584":1,"1586":1,"1601":1,"1612":1,"1645":1,"1680":1,"1696":1,"1700":1,"1749":1,"1761":2,"1785":1,"1787":2,"1788":2,"1790":1,"1792":35,"1794":2,"1795":1,"1797":1,"1836":4,"1850":1,"1865":3,"1870":1,"1871":1,"1872":1,"1876":5,"1885":1,"1914":1,"1933":1,"1965":1,"1977":3,"1978":1,"1984":1,"1992":1,"1993":1,"2010":1,"2013":1,"2018":1,"2030":1,"2082":2,"2135":1,"2148":2,"2151":1,"2169":2,"2170":2,"2177":1,"2181":1,"2189":2,"2219":1,"2255":4,"2256":3,"2257":1,"2258":4,"2264":2,"2265":1,"2266":1,"2267":1,"2273":2,"2291":1,"2297":1,"2359":1,"2375":2,"2420":1,"2423":1,"2429":1,"2459":1,"2495":1,"2533":1,"2545":1,"2554":1,"2565":1,"2575":1,"2577":1,"2594":1,"2596":1,"2629":2,"2632":6,"2651":1,"2652":1,"2686":1,"2701":3,"2706":1,"2718":1,"2721":1,"2745":1,"2793":1,"2806":1,"2824":1,"2825":1,"2826":3,"2827":1,"2835":1,"2859":1}}],["opt",{"0":{"1040":1,"2455":1,"2520":1,"2587":1},"1":{"1041":1},"2":{"214":1,"317":1,"390":1,"917":1,"1046":1,"1102":2,"1150":1,"1406":1,"1430":1,"1511":1,"1519":1,"1569":1,"1722":1,"1743":1,"1759":1,"1792":2,"1834":1,"1856":1,"1912":1,"2222":1,"2224":1,"2380":1,"2381":1,"2389":1,"2481":1,"2483":1,"2502":2,"2515":1,"2520":1,"2765":1}}],["opaque",{"2":{"188":1,"1457":1,"1792":3,"2296":1,"2444":1,"2528":1,"2554":1}}],["operate",{"2":{"902":1}}],["operates",{"2":{"852":1,"2545":1}}],["operator",{"2":{"852":1,"933":3,"1155":1,"2040":1}}],["operators",{"2":{"852":3,"856":1,"933":1,"1096":5,"1127":1,"1385":2,"2492":1,"2530":1,"2540":1,"2845":1}}],["operating",{"2":{"852":1,"2786":1}}],["operational",{"2":{"1801":1,"2434":1,"2635":1}}],["operations",{"0":{"1346":1,"2216":1},"2":{"439":1,"577":1,"851":2,"852":2,"953":1,"974":2,"1038":1,"1098":2,"1132":2,"1161":1,"1163":1,"1167":3,"1281":1,"1328":1,"1329":2,"1349":1,"1599":1,"1792":1,"1801":1,"1994":1,"2087":2,"2270":3,"2532":1,"2614":2,"2615":4,"2621":1}}],["operation",{"0":{"856":1},"2":{"175":1,"195":1,"284":1,"477":2,"572":1,"852":2,"919":3,"1045":1,"1075":1,"1111":1,"1130":1,"1179":1,"1213":2,"1214":1,"1232":1,"1234":1,"1235":4,"1329":1,"1599":1,"1678":1,"1792":5,"1885":1,"1912":1,"2092":1,"2218":1,"2527":1,"2545":1,"2621":1,"2729":1,"2862":1}}],["openconnectionasync",{"2":{"2559":1,"2615":1}}],["opener",{"0":{"2023":1},"2":{"1792":1,"2632":2}}],["opened",{"2":{"436":1,"650":1,"807":1,"818":1,"1105":1,"1141":1,"1150":1,"1305":1,"1337":1,"1928":1,"2137":1,"2149":1,"2463":1,"2534":1,"2549":1,"2575":1,"2807":2,"2809":1}}],["openreadstream",{"2":{"1366":1}}],["openresty",{"2":{"1106":1}}],["openai",{"2":{"1105":4}}],["openapiannotationtests",{"2":{"2435":1}}],["openapifiltertests",{"2":{"2435":1}}],["openapitests",{"2":{"2435":2}}],["openapitags",{"2":{"2223":1,"2435":1,"2487":2}}],["openapihide",{"2":{"2223":1,"2433":1,"2435":1,"2487":2}}],["openapioptions",{"2":{"354":1,"868":1,"1792":1,"1836":2,"1897":1,"1899":1,"1900":1,"1907":1,"1909":1,"1911":1,"1912":1,"2254":2,"2430":1,"2431":2,"2434":1,"2436":1,"2520":1,"2551":1,"2701":1}}],["openapi",{"0":{"347":1,"1896":1,"2254":1,"2430":1,"2432":1,"2487":1,"2555":1},"1":{"348":1,"349":1,"350":1,"351":1,"352":1,"353":1,"354":1,"355":1,"356":1,"1897":1,"1898":1,"1899":1,"1900":1,"1901":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"1907":1,"1908":1,"1909":1,"1910":1,"1911":1,"1912":1,"1913":1,"1914":1,"2431":1,"2432":1,"2433":1,"2434":1},"2":{"74":1,"223":2,"347":4,"348":7,"349":3,"351":3,"352":3,"353":1,"354":3,"355":6,"356":2,"381":1,"835":2,"837":1,"868":2,"869":2,"1008":1,"1027":1,"1042":1,"1095":1,"1366":1,"1385":1,"1386":1,"1422":1,"1569":1,"1584":2,"1759":1,"1789":2,"1792":14,"1796":2,"1818":1,"1836":1,"1896":1,"1897":2,"1898":5,"1899":1,"1900":1,"1901":1,"1907":2,"1908":1,"1910":1,"1911":4,"1913":4,"2157":1,"2221":1,"2222":2,"2223":2,"2225":5,"2240":1,"2254":16,"2333":1,"2419":5,"2430":2,"2432":10,"2433":1,"2434":3,"2435":7,"2436":2,"2438":3,"2479":1,"2482":1,"2487":6,"2489":2,"2518":1,"2520":2,"2523":2,"2527":1,"2543":1,"2555":3,"2714":1,"2848":1,"2857":1,"2862":1}}],["openssl",{"2":{"2385":1}}],["opens",{"2":{"859":1,"1161":1,"1329":1,"1331":1,"2391":1,"2407":1,"2528":1,"2863":1}}],["opentelemetry",{"0":{"1807":1,"2804":1},"1":{"1808":1},"2":{"835":1,"1110":2,"1792":1,"1799":1,"1807":1,"2794":1}}],["openxmlformats",{"2":{"772":1,"773":1,"893":1}}],["opening",{"2":{"389":1,"438":1,"679":1,"1363":1,"1618":1,"1621":1,"1792":2,"1920":1,"2247":1}}],["open",{"0":{"2066":1},"2":{"3":1,"106":1,"323":4,"650":1,"666":2,"832":1,"848":1,"859":1,"860":1,"894":1,"961":1,"1018":1,"1019":1,"1023":1,"1026":1,"1032":1,"1066":1,"1067":1,"1068":1,"1076":1,"1207":1,"1366":3,"1385":1,"1410":1,"1433":2,"1525":1,"1529":1,"1609":1,"1792":1,"1991":1,"2110":1,"2411":2,"2413":1,"2440":1,"2442":1,"2446":1,"2527":1,"2530":1,"2559":1,"2661":1,"2764":2,"2766":1,"2776":1,"2830":1,"2862":1}}],["onto",{"2":{"2511":1,"2867":1}}],["onrejected",{"2":{"2470":1}}],["onward",{"2":{"2112":1,"2532":1}}],["onprogress",{"2":{"1410":1}}],["online",{"2":{"1342":1,"1792":2}}],["onload",{"2":{"894":1,"1366":1,"1410":1}}],["onlyannotated",{"2":{"320":1,"1792":4,"1836":1,"1840":5,"1863":1,"2195":1,"2223":1,"2482":2,"2721":1,"2841":1}}],["onlywithhttptag",{"0":{"2367":1},"2":{"244":2,"320":1,"1792":2,"1840":2,"1999":1,"2000":1,"2004":2,"2195":1,"2209":1,"2330":2,"2367":1,"2482":1,"2701":1,"2841":1}}],["only",{"0":{"180":1,"320":1,"633":1,"661":1,"686":1,"723":1,"899":1,"961":1,"1042":1,"1413":1,"1570":1,"1662":1,"1747":1,"1930":1,"2028":1,"2344":1,"2487":1,"2489":1,"2494":1,"2656":1},"2":{"9":1,"64":1,"109":1,"133":1,"173":1,"175":4,"176":1,"177":1,"179":1,"188":2,"213":2,"214":4,"223":1,"239":1,"244":1,"251":1,"261":2,"262":2,"277":1,"286":1,"289":1,"291":1,"297":1,"299":1,"303":1,"310":1,"314":1,"317":1,"319":2,"320":9,"325":1,"327":1,"337":1,"347":1,"378":1,"384":1,"388":3,"390":1,"414":1,"422":1,"436":1,"438":1,"446":1,"448":1,"452":1,"453":1,"454":1,"458":1,"512":1,"529":1,"531":1,"559":1,"567":1,"582":1,"585":1,"587":1,"609":1,"615":1,"625":1,"633":1,"638":1,"641":1,"646":1,"650":1,"656":2,"660":1,"661":1,"663":1,"664":1,"666":1,"668":1,"673":2,"675":2,"679":4,"681":1,"683":2,"685":1,"689":2,"693":2,"698":2,"703":2,"706":1,"708":2,"710":1,"711":1,"713":2,"719":1,"720":2,"723":3,"726":1,"745":1,"747":2,"762":1,"768":1,"770":1,"771":1,"772":2,"786":1,"823":1,"826":1,"837":1,"841":3,"843":1,"844":1,"845":1,"849":1,"851":3,"855":1,"863":1,"864":2,"865":4,"871":1,"872":1,"884":1,"891":1,"892":1,"904":1,"911":1,"919":2,"922":3,"924":2,"926":4,"932":1,"933":1,"937":2,"941":1,"945":1,"946":1,"949":1,"951":1,"957":3,"961":2,"980":1,"985":1,"1015":1,"1021":1,"1032":1,"1037":1,"1040":1,"1042":3,"1045":4,"1046":1,"1049":1,"1050":2,"1056":1,"1057":2,"1068":1,"1069":1,"1090":1,"1094":1,"1097":1,"1098":3,"1099":1,"1100":1,"1101":3,"1104":1,"1105":2,"1106":1,"1107":2,"1108":1,"1111":1,"1127":10,"1129":1,"1130":1,"1133":2,"1135":1,"1138":1,"1139":1,"1147":1,"1162":3,"1164":1,"1174":2,"1181":4,"1185":4,"1199":1,"1204":1,"1206":1,"1208":1,"1210":1,"1213":1,"1224":1,"1228":1,"1233":1,"1251":1,"1281":2,"1309":2,"1312":1,"1313":1,"1316":1,"1321":1,"1322":1,"1326":2,"1333":1,"1350":1,"1357":1,"1358":2,"1367":1,"1374":1,"1382":2,"1386":1,"1392":1,"1395":1,"1396":1,"1399":1,"1401":1,"1402":2,"1403":1,"1406":2,"1408":1,"1411":1,"1412":1,"1413":2,"1420":1,"1430":2,"1431":2,"1438":1,"1447":1,"1457":1,"1458":2,"1459":2,"1489":1,"1493":1,"1511":3,"1521":2,"1522":1,"1558":2,"1569":2,"1570":1,"1575":2,"1632":1,"1639":1,"1644":1,"1651":1,"1662":3,"1664":1,"1688":1,"1690":2,"1707":1,"1717":2,"1738":1,"1740":1,"1741":1,"1743":4,"1747":1,"1748":1,"1771":1,"1792":56,"1801":1,"1813":1,"1823":4,"1824":2,"1825":2,"1832":1,"1834":1,"1839":1,"1840":3,"1844":1,"1845":1,"1851":1,"1856":1,"1858":2,"1862":2,"1867":1,"1868":1,"1874":1,"1878":1,"1883":1,"1898":3,"1906":3,"1908":1,"1911":3,"1922":1,"1924":1,"1929":1,"1930":1,"1942":1,"1955":1,"1956":1,"1958":2,"1961":1,"1967":1,"1970":1,"1983":2,"2004":2,"2005":1,"2006":1,"2011":2,"2018":1,"2019":2,"2021":1,"2025":2,"2063":1,"2072":2,"2081":1,"2092":1,"2094":1,"2097":1,"2104":1,"2128":1,"2153":1,"2156":1,"2157":1,"2166":1,"2173":1,"2187":2,"2195":1,"2209":1,"2212":1,"2222":2,"2223":3,"2247":3,"2250":1,"2273":1,"2274":1,"2284":1,"2288":1,"2289":1,"2291":1,"2296":2,"2297":3,"2309":1,"2314":1,"2319":1,"2321":1,"2323":1,"2330":1,"2332":2,"2337":1,"2338":2,"2339":1,"2344":1,"2350":1,"2353":1,"2354":2,"2360":1,"2375":4,"2379":2,"2380":3,"2382":1,"2386":1,"2389":1,"2392":2,"2393":1,"2394":2,"2395":1,"2398":2,"2399":1,"2402":1,"2411":1,"2414":2,"2420":1,"2421":4,"2423":2,"2427":1,"2429":1,"2430":2,"2431":2,"2432":1,"2434":2,"2436":1,"2441":1,"2442":1,"2445":1,"2448":1,"2450":1,"2451":1,"2452":1,"2466":3,"2468":1,"2470":2,"2472":1,"2481":7,"2482":3,"2483":3,"2486":3,"2487":2,"2489":2,"2490":2,"2493":1,"2498":1,"2502":3,"2505":1,"2506":1,"2512":2,"2519":1,"2522":1,"2528":2,"2533":1,"2535":1,"2537":4,"2539":1,"2540":2,"2542":1,"2543":1,"2544":1,"2554":1,"2565":2,"2577":1,"2580":1,"2586":1,"2589":1,"2608":1,"2611":1,"2632":2,"2633":2,"2635":2,"2641":1,"2656":3,"2664":1,"2678":1,"2712":1,"2721":2,"2741":1,"2755":1,"2763":1,"2765":3,"2810":1,"2822":1,"2829":1,"2831":1,"2832":1,"2833":2,"2834":5,"2841":2,"2853":1,"2856":1,"2864":1,"2867":2,"2868":1,"2869":4,"2876":1}}],["ongoing",{"2":{"865":1,"869":1,"872":1,"1206":1}}],["onion",{"2":{"840":1}}],["onmessage",{"2":{"666":1,"1317":4,"1318":2,"1321":1,"1416":6,"2247":8,"2830":3,"2836":1}}],["once",{"0":{"2504":1},"2":{"101":1,"306":1,"314":1,"386":1,"390":1,"650":1,"666":1,"696":1,"705":1,"711":1,"841":3,"845":2,"847":1,"851":1,"852":1,"865":1,"872":1,"924":2,"1129":3,"1159":1,"1181":1,"1190":1,"1192":1,"1193":2,"1281":1,"1305":1,"1402":1,"1405":2,"1406":1,"1419":1,"1428":1,"1429":1,"1722":1,"1792":6,"1823":1,"1824":1,"1862":1,"2038":1,"2040":2,"2094":2,"2098":1,"2106":2,"2107":1,"2112":1,"2147":1,"2158":1,"2167":1,"2185":1,"2224":1,"2320":1,"2392":1,"2450":1,"2452":1,"2463":1,"2466":1,"2474":1,"2476":1,"2483":1,"2498":1,"2502":1,"2504":2,"2532":3,"2534":2,"2537":7,"2546":1,"2621":1,"2622":1,"2762":1,"2766":1,"2851":1,"2871":3,"2873":1,"2878":2,"2879":1}}],["onerror",{"2":{"894":1,"1366":1,"2267":1}}],["ones",{"2":{"286":1,"448":1,"710":1,"845":1,"864":2,"872":1,"1121":1,"1180":1,"1193":1,"1398":1,"1792":1,"1922":1,"2107":1,"2397":1,"2468":1,"2614":1,"2684":1,"2802":1,"2810":1}}],["one",{"0":{"312":1,"812":1,"904":1,"959":1,"1039":1,"1043":1,"1370":1,"1418":1,"1577":1,"2725":1,"2740":1},"1":{"960":1},"2":{"21":1,"74":1,"102":1,"109":1,"175":1,"179":1,"214":2,"216":1,"237":1,"263":1,"297":3,"298":1,"302":1,"307":2,"308":1,"347":1,"357":1,"385":1,"386":1,"439":1,"448":1,"453":1,"479":1,"529":2,"609":1,"650":1,"663":3,"666":1,"667":1,"685":1,"686":1,"699":1,"703":1,"704":1,"713":1,"716":1,"763":1,"773":1,"778":1,"831":2,"835":1,"836":1,"838":1,"840":2,"841":11,"843":1,"844":6,"845":2,"847":1,"848":5,"849":3,"851":1,"852":6,"853":1,"854":2,"855":2,"856":2,"857":2,"859":1,"860":2,"861":2,"863":3,"864":7,"865":5,"866":1,"868":5,"871":5,"872":5,"873":6,"874":2,"876":1,"887":2,"903":1,"904":1,"910":2,"911":1,"913":2,"916":1,"918":2,"919":2,"920":1,"922":1,"947":1,"948":3,"949":2,"968":1,"971":1,"1010":1,"1011":1,"1017":1,"1035":1,"1037":2,"1038":2,"1039":1,"1043":1,"1044":3,"1045":1,"1046":1,"1069":2,"1070":1,"1073":1,"1075":2,"1076":3,"1077":1,"1078":2,"1079":4,"1080":1,"1081":2,"1082":1,"1098":3,"1099":1,"1101":1,"1105":1,"1121":1,"1134":1,"1135":1,"1137":1,"1138":1,"1147":1,"1150":2,"1156":1,"1162":1,"1164":1,"1184":1,"1191":1,"1200":1,"1203":1,"1232":1,"1235":1,"1253":1,"1279":1,"1303":1,"1305":1,"1327":1,"1329":1,"1352":2,"1355":1,"1359":1,"1370":2,"1372":1,"1376":1,"1377":2,"1378":1,"1382":7,"1385":1,"1386":2,"1393":1,"1394":2,"1395":1,"1398":1,"1399":2,"1400":1,"1401":2,"1402":2,"1404":1,"1405":1,"1406":2,"1410":1,"1414":3,"1415":1,"1416":3,"1422":1,"1427":1,"1429":1,"1430":2,"1431":1,"1435":1,"1436":1,"1439":2,"1441":2,"1442":1,"1458":2,"1459":1,"1460":3,"1480":1,"1519":2,"1521":1,"1522":4,"1559":1,"1571":1,"1575":1,"1579":1,"1581":2,"1608":1,"1621":1,"1640":1,"1741":1,"1743":1,"1745":1,"1756":2,"1792":28,"1851":1,"1861":2,"1909":2,"1911":3,"1930":1,"1955":1,"1959":1,"1974":1,"2094":1,"2096":1,"2097":1,"2103":1,"2109":1,"2110":1,"2111":2,"2113":2,"2153":1,"2160":1,"2166":1,"2167":1,"2171":1,"2172":1,"2175":4,"2176":2,"2184":1,"2199":1,"2221":1,"2222":2,"2272":1,"2284":2,"2289":1,"2319":1,"2320":2,"2344":1,"2346":1,"2357":1,"2366":2,"2375":5,"2379":1,"2380":7,"2381":1,"2382":1,"2389":1,"2391":1,"2395":1,"2406":1,"2411":1,"2412":1,"2421":1,"2422":1,"2424":1,"2428":1,"2431":1,"2433":1,"2438":3,"2440":1,"2462":1,"2463":2,"2464":2,"2465":2,"2466":3,"2471":1,"2479":1,"2482":2,"2498":2,"2502":2,"2504":5,"2506":3,"2518":1,"2519":1,"2528":3,"2529":2,"2530":4,"2531":4,"2532":2,"2533":2,"2535":5,"2537":5,"2540":3,"2541":1,"2542":1,"2543":3,"2545":2,"2546":2,"2586":1,"2597":2,"2607":1,"2615":1,"2721":1,"2729":1,"2731":2,"2742":1,"2762":1,"2765":1,"2766":1,"2767":1,"2768":1,"2807":1,"2809":1,"2811":1,"2812":1,"2814":1,"2821":1,"2827":1,"2828":2,"2830":1,"2832":1,"2833":1,"2834":2,"2835":1,"2842":1,"2844":1,"2845":3,"2850":2,"2855":1,"2857":1,"2864":3,"2865":1,"2866":1,"2867":1,"2868":3,"2869":2,"2873":1,"2877":1,"2879":1}}],["on",{"0":{"24":1,"381":1,"668":1,"669":1,"812":1,"870":1,"985":1,"1054":1,"1134":2,"1389":1,"1524":1,"2379":1,"2398":1,"2404":1,"2482":1,"2495":1,"2504":1,"2582":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1},"2":{"3":1,"7":1,"8":1,"9":2,"16":1,"17":3,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"35":1,"37":1,"38":1,"39":1,"40":1,"41":1,"48":1,"49":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"75":2,"83":1,"84":1,"85":1,"86":1,"87":2,"94":1,"95":1,"96":1,"97":1,"104":1,"105":1,"106":1,"107":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"128":1,"129":1,"136":1,"137":1,"138":1,"139":1,"142":1,"147":1,"148":1,"149":1,"157":1,"174":1,"175":1,"177":1,"180":1,"184":4,"186":4,"187":3,"188":2,"195":1,"196":1,"201":1,"203":1,"206":1,"207":1,"208":1,"209":2,"210":1,"211":2,"212":1,"213":10,"214":8,"215":3,"220":1,"223":1,"230":1,"239":1,"244":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"254":1,"255":1,"256":1,"257":1,"258":1,"263":2,"264":2,"265":1,"266":1,"277":3,"278":3,"285":1,"288":1,"289":1,"290":1,"291":1,"292":1,"298":6,"307":2,"308":2,"309":2,"310":2,"312":2,"313":1,"314":1,"316":1,"317":1,"320":1,"322":1,"323":1,"324":1,"325":1,"327":1,"332":1,"333":1,"334":1,"335":1,"342":1,"343":1,"344":1,"347":1,"351":1,"352":2,"353":1,"354":1,"360":1,"361":1,"365":1,"366":1,"369":1,"374":2,"378":1,"386":2,"390":1,"392":1,"393":1,"394":1,"401":1,"402":1,"403":1,"405":1,"406":1,"408":2,"415":1,"417":1,"418":1,"419":1,"420":1,"421":2,"423":1,"426":1,"427":1,"428":1,"436":4,"438":1,"439":1,"441":1,"442":1,"443":1,"444":1,"445":2,"449":1,"451":2,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"476":1,"477":1,"478":1,"479":1,"480":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"501":1,"502":1,"503":1,"510":1,"511":1,"520":1,"521":1,"522":2,"523":1,"527":1,"529":1,"531":2,"532":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"553":1,"554":1,"555":1,"560":3,"562":1,"563":1,"567":1,"572":1,"573":1,"574":1,"575":1,"577":2,"584":1,"587":1,"592":1,"593":1,"594":1,"601":1,"602":1,"603":1,"604":1,"611":1,"616":1,"619":3,"631":1,"632":1,"633":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"650":5,"651":1,"658":1,"660":2,"661":2,"662":2,"664":2,"666":2,"667":1,"668":1,"669":2,"677":1,"678":1,"679":1,"683":2,"684":1,"686":1,"693":1,"694":1,"695":2,"704":1,"706":2,"707":1,"708":1,"714":1,"716":1,"717":1,"722":1,"723":1,"724":2,"732":1,"733":1,"734":1,"735":1,"736":1,"746":1,"750":1,"751":1,"752":1,"754":1,"755":1,"756":1,"758":1,"764":1,"767":1,"774":1,"775":1,"777":2,"781":1,"783":1,"785":1,"787":1,"789":1,"791":1,"797":1,"798":1,"799":1,"800":1,"809":1,"811":1,"812":1,"813":1,"814":1,"815":1,"817":1,"818":1,"827":1,"828":2,"829":1,"831":2,"834":1,"835":2,"837":3,"838":1,"840":2,"841":5,"843":2,"844":1,"845":3,"847":4,"848":14,"849":3,"851":4,"852":5,"855":1,"859":2,"860":2,"861":1,"864":1,"866":4,"868":4,"869":2,"871":3,"872":8,"873":6,"875":1,"876":8,"878":1,"879":1,"886":1,"892":1,"898":1,"899":1,"900":1,"902":1,"903":1,"904":3,"907":1,"910":1,"911":1,"914":3,"915":2,"916":5,"917":1,"918":1,"919":3,"920":3,"922":1,"924":1,"926":3,"930":1,"932":1,"933":3,"934":2,"935":1,"936":1,"941":1,"946":1,"949":1,"957":2,"960":2,"961":1,"964":1,"965":1,"967":1,"976":1,"977":1,"978":1,"979":3,"980":2,"981":1,"985":3,"986":2,"988":2,"990":2,"994":1,"995":1,"1003":1,"1005":2,"1009":2,"1013":1,"1017":1,"1019":2,"1021":1,"1030":1,"1032":5,"1033":2,"1034":3,"1037":1,"1043":3,"1044":3,"1045":1,"1047":1,"1049":6,"1054":4,"1055":2,"1057":1,"1058":1,"1060":1,"1065":1,"1067":4,"1068":1,"1069":1,"1070":2,"1071":1,"1073":2,"1074":2,"1076":6,"1078":3,"1079":2,"1080":2,"1084":1,"1086":1,"1088":1,"1094":5,"1095":1,"1096":1,"1098":6,"1101":2,"1102":1,"1104":1,"1105":10,"1107":2,"1108":1,"1111":1,"1113":3,"1114":2,"1117":1,"1121":2,"1125":1,"1128":1,"1130":1,"1133":2,"1134":3,"1135":2,"1138":3,"1139":1,"1141":1,"1142":2,"1143":1,"1149":1,"1150":4,"1154":2,"1158":1,"1161":1,"1162":1,"1163":3,"1166":1,"1176":5,"1179":2,"1183":2,"1187":1,"1189":1,"1193":1,"1196":1,"1197":1,"1203":1,"1204":1,"1205":1,"1208":1,"1209":1,"1213":2,"1229":1,"1239":1,"1245":1,"1253":1,"1255":1,"1272":1,"1280":1,"1303":1,"1305":1,"1308":1,"1309":1,"1310":1,"1312":1,"1313":2,"1314":1,"1315":1,"1320":8,"1321":1,"1324":1,"1325":1,"1326":1,"1331":1,"1332":1,"1334":1,"1336":1,"1337":1,"1338":2,"1339":2,"1345":2,"1347":1,"1348":1,"1351":2,"1355":1,"1358":3,"1359":1,"1362":1,"1364":1,"1366":1,"1368":1,"1371":1,"1376":2,"1378":4,"1379":1,"1382":5,"1384":2,"1385":5,"1386":6,"1387":1,"1388":2,"1389":1,"1390":2,"1393":2,"1394":1,"1395":1,"1396":2,"1397":1,"1398":4,"1399":3,"1400":3,"1401":2,"1402":1,"1403":4,"1404":2,"1405":1,"1406":1,"1407":5,"1408":1,"1409":3,"1414":1,"1418":2,"1419":2,"1421":1,"1422":2,"1423":1,"1426":1,"1427":1,"1430":2,"1431":3,"1432":1,"1433":1,"1435":3,"1436":3,"1437":1,"1440":1,"1447":1,"1449":3,"1460":3,"1465":1,"1467":1,"1472":3,"1486":1,"1499":1,"1504":2,"1513":1,"1515":1,"1517":1,"1519":1,"1522":1,"1525":1,"1529":4,"1531":2,"1532":2,"1533":1,"1535":1,"1537":1,"1547":1,"1567":1,"1569":2,"1591":1,"1599":3,"1620":1,"1632":1,"1644":1,"1653":2,"1655":1,"1662":4,"1664":5,"1670":1,"1686":1,"1689":1,"1699":1,"1704":1,"1718":1,"1722":1,"1726":2,"1728":1,"1730":3,"1731":2,"1732":1,"1733":1,"1736":2,"1738":3,"1740":7,"1741":3,"1742":2,"1743":3,"1744":2,"1747":1,"1759":2,"1789":1,"1792":50,"1822":3,"1825":3,"1832":1,"1833":2,"1834":1,"1851":3,"1855":1,"1856":3,"1879":1,"1911":1,"1912":2,"1920":1,"1921":1,"1923":1,"1924":4,"1925":2,"1926":1,"1929":2,"1930":2,"1955":1,"1961":2,"1974":1,"2020":1,"2040":1,"2060":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":2,"2094":2,"2096":1,"2099":1,"2106":3,"2109":2,"2110":2,"2111":2,"2116":1,"2117":2,"2118":1,"2119":1,"2133":1,"2134":1,"2136":1,"2141":1,"2147":1,"2149":1,"2153":1,"2154":1,"2155":3,"2156":2,"2157":4,"2158":2,"2160":3,"2164":1,"2167":1,"2171":1,"2175":1,"2176":1,"2177":2,"2181":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":3,"2190":1,"2192":1,"2193":5,"2194":1,"2196":5,"2199":3,"2200":3,"2201":1,"2202":3,"2204":2,"2205":3,"2206":2,"2207":1,"2214":2,"2215":2,"2216":1,"2217":1,"2218":1,"2221":5,"2222":2,"2223":4,"2247":1,"2252":7,"2255":6,"2258":1,"2261":1,"2264":4,"2265":1,"2267":2,"2270":2,"2277":5,"2283":2,"2284":1,"2285":1,"2288":7,"2289":3,"2290":2,"2292":3,"2293":4,"2294":3,"2296":2,"2297":3,"2303":1,"2304":1,"2305":1,"2306":1,"2309":1,"2314":4,"2323":1,"2332":2,"2333":2,"2337":2,"2338":1,"2339":1,"2340":3,"2344":3,"2346":5,"2353":2,"2358":1,"2362":1,"2363":1,"2367":1,"2375":4,"2379":1,"2380":5,"2382":3,"2385":2,"2388":1,"2389":2,"2391":3,"2392":1,"2393":1,"2394":1,"2396":1,"2397":3,"2398":5,"2402":1,"2406":1,"2407":2,"2413":1,"2414":2,"2415":2,"2419":2,"2420":1,"2422":1,"2425":4,"2429":1,"2430":1,"2431":1,"2432":3,"2433":1,"2435":2,"2436":1,"2438":4,"2444":1,"2446":1,"2450":2,"2451":2,"2452":1,"2454":2,"2455":2,"2465":1,"2466":3,"2476":1,"2479":1,"2481":5,"2482":1,"2483":1,"2490":1,"2494":1,"2495":1,"2498":1,"2500":1,"2502":2,"2504":3,"2506":1,"2509":2,"2513":1,"2518":1,"2520":3,"2521":1,"2523":1,"2525":2,"2526":2,"2527":2,"2530":4,"2531":2,"2532":5,"2533":1,"2534":2,"2535":1,"2537":6,"2539":2,"2540":1,"2541":3,"2542":2,"2543":7,"2545":3,"2546":4,"2549":10,"2551":1,"2575":5,"2576":2,"2580":4,"2581":4,"2587":2,"2591":2,"2596":1,"2597":1,"2607":2,"2608":1,"2614":1,"2625":2,"2632":1,"2634":1,"2635":1,"2638":1,"2648":1,"2649":1,"2651":1,"2652":1,"2653":2,"2655":1,"2656":1,"2664":3,"2665":2,"2679":2,"2682":1,"2694":1,"2696":1,"2701":1,"2702":2,"2703":1,"2704":1,"2705":1,"2719":1,"2721":4,"2739":2,"2742":3,"2744":1,"2755":3,"2757":1,"2758":1,"2760":1,"2761":1,"2762":3,"2763":1,"2764":2,"2765":4,"2766":3,"2767":3,"2768":3,"2774":1,"2775":1,"2776":3,"2785":2,"2790":2,"2792":3,"2794":2,"2795":1,"2800":1,"2802":3,"2804":1,"2807":1,"2809":1,"2810":1,"2812":2,"2813":1,"2815":3,"2817":1,"2822":1,"2823":1,"2824":4,"2825":2,"2829":1,"2830":1,"2831":1,"2832":2,"2834":3,"2835":2,"2836":2,"2838":1,"2839":1,"2840":1,"2852":1,"2855":1,"2857":1,"2858":1,"2860":1,"2862":2,"2866":1,"2867":3,"2868":1,"2869":2,"2870":1,"2871":4,"2872":1,"2873":2,"2874":1,"2876":1,"2877":1,"2878":6,"2880":2,"2881":2}}],["ors",{"2":{"2456":1}}],["orthogonally",{"2":{"2537":1}}],["orthogonal",{"2":{"2423":1}}],["orthodoxy",{"2":{"845":2,"861":1}}],["orchestrators",{"2":{"1792":1,"2634":1}}],["orchestration",{"2":{"1100":1,"1108":2,"1762":1,"2346":1,"2634":1}}],["orchestrating",{"2":{"1088":1,"1746":1,"2347":1}}],["orphans",{"2":{"2534":1}}],["orphaned",{"2":{"1354":1}}],["orphan",{"2":{"992":1,"2157":1,"2543":1,"2546":1}}],["orange",{"2":{"2535":1}}],["oraoracle",{"2":{"918":1}}],["oracle",{"2":{"848":4,"918":3}}],["organization",{"2":{"1414":2,"1753":1,"1792":1}}],["organizations",{"2":{"841":1,"1013":1}}],["organized",{"2":{"848":1}}],["org",{"2":{"841":1,"860":1,"1676":1,"1677":1,"1792":8,"2255":4,"2632":3}}],["orientation",{"2":{"1792":2}}],["oriented",{"2":{"840":1,"841":4,"843":1,"1128":1,"1403":1,"1799":1,"1811":1,"2013":1,"2794":1}}],["originate",{"2":{"2282":1}}],["originates",{"2":{"1738":1}}],["originating",{"2":{"650":1,"668":1,"669":1}}],["originally",{"2":{"1128":1}}],["originals",{"2":{"843":1}}],["original",{"0":{"902":1},"1":{"903":1},"2":{"299":1,"348":1,"370":1,"383":1,"384":1,"388":1,"414":1,"415":1,"423":1,"652":1,"748":1,"762":1,"772":1,"809":1,"818":1,"857":1,"865":1,"883":1,"902":3,"903":3,"995":1,"1002":1,"1107":1,"1148":1,"1235":1,"1335":1,"1338":3,"1518":1,"1705":2,"1792":5,"1885":1,"2265":1,"2302":2,"2303":1,"2304":1,"2432":1,"2435":1,"2495":1,"2575":1,"2590":1,"2633":2}}],["origins",{"0":{"1640":1,"1641":1},"1":{"1641":1},"2":{"545":1,"1225":1,"1449":1,"1637":1,"1639":2,"1640":2,"1641":1,"1644":1,"1646":2,"1792":5,"1875":1,"2419":1}}],["origin",{"0":{"1449":1,"1714":1,"2022":1,"2023":1,"2024":1,"2025":1,"2429":1},"1":{"2023":1,"2024":1,"2025":1},"2":{"545":1,"1243":1,"1382":1,"1447":1,"1637":1,"1639":1,"1640":2,"1641":1,"1644":2,"1788":1,"1792":38,"1795":1,"1823":5,"1824":2,"1867":1,"1875":2,"1963":1,"2015":2,"2016":5,"2018":1,"2019":19,"2021":1,"2023":6,"2024":4,"2025":6,"2027":2,"2029":4,"2030":1,"2225":1,"2425":2,"2427":1,"2429":1,"2435":1,"2436":1,"2438":3,"2481":2,"2486":1,"2492":2,"2627":1,"2632":34}}],["orms",{"2":{"1382":1}}],["orm",{"2":{"832":1,"840":1,"851":2,"852":1,"854":1,"869":1,"871":3,"874":2,"875":1,"876":1,"945":1,"993":1,"1006":2,"1007":1,"1008":1,"1276":1,"1366":1,"1403":1,"1405":5,"1440":1,"1441":1,"2709":1}}],["orwell",{"2":{"335":2,"913":1,"914":2,"916":3,"917":2,"918":3,"919":2,"2586":3}}],["ordinal",{"2":{"1792":1,"2109":1,"2110":1,"2530":1,"2540":1}}],["ordinary",{"2":{"297":1,"848":1,"1253":1,"2438":1,"2526":1,"2529":1,"2545":1,"2739":1,"2834":1,"2860":2,"2865":1,"2868":1}}],["orderid",{"2":{"1044":1,"1107":2,"1193":1}}],["ordering",{"0":{"716":1},"2":{"868":1,"1096":1,"1135":1,"1738":1,"2490":1,"2498":1}}],["ordered",{"2":{"860":1,"1044":1,"1403":1,"1792":1,"1956":1,"2379":1,"2741":1,"2868":1,"2875":1}}],["orders",{"2":{"168":1,"352":3,"406":2,"427":3,"451":5,"562":3,"563":2,"565":3,"566":4,"614":2,"841":1,"856":1,"885":1,"1105":1,"1107":1,"1179":2,"1193":2,"1345":5,"1398":10,"1435":1,"1440":1,"1744":3,"1745":4,"2320":3,"2339":2,"2340":5,"2346":7,"2380":1,"2432":2,"2438":2,"2727":1,"2733":1,"2767":7,"2774":3,"2868":1}}],["order",{"0":{"52":1,"1910":1,"2424":1,"2433":1},"2":{"23":1,"31":1,"52":1,"214":1,"263":1,"319":2,"349":1,"352":2,"406":5,"427":3,"439":1,"448":1,"529":1,"531":2,"565":4,"699":1,"704":1,"714":1,"716":2,"818":1,"834":2,"835":1,"851":1,"856":1,"860":2,"1038":1,"1041":1,"1042":1,"1043":1,"1044":4,"1070":1,"1079":1,"1105":4,"1125":1,"1173":1,"1179":5,"1187":2,"1188":4,"1189":2,"1191":3,"1192":4,"1193":7,"1310":1,"1373":4,"1385":1,"1388":1,"1398":2,"1429":1,"1439":2,"1440":1,"1523":1,"1792":4,"1852":1,"1910":1,"2094":2,"2112":1,"2149":1,"2207":1,"2284":1,"2320":4,"2344":1,"2380":1,"2383":1,"2422":2,"2424":2,"2433":1,"2435":1,"2481":1,"2528":1,"2532":1,"2533":1,"2537":2,"2575":1,"2681":1,"2684":1,"2741":1,"2774":2,"2813":1,"2825":1,"2836":1,"2863":1,"2868":2,"2870":1,"2871":1,"2873":1}}],["or",{"0":{"643":1,"898":1,"1070":2,"2496":1,"2713":1,"2726":1,"2731":1},"2":{"3":1,"4":1,"13":2,"14":1,"22":1,"25":1,"29":1,"31":1,"33":3,"35":2,"44":1,"55":1,"63":2,"68":1,"75":1,"78":1,"91":1,"101":1,"108":1,"122":1,"125":1,"132":1,"144":1,"165":2,"171":1,"173":1,"180":1,"182":1,"188":2,"192":1,"203":1,"210":1,"211":1,"213":1,"214":1,"215":1,"223":1,"239":1,"241":1,"243":1,"245":2,"253":1,"258":2,"261":2,"267":1,"268":1,"277":1,"280":1,"282":1,"291":1,"296":2,"297":2,"299":2,"302":1,"305":1,"307":2,"310":2,"312":3,"317":1,"320":1,"328":1,"334":1,"335":1,"337":1,"339":1,"347":2,"357":1,"369":2,"370":2,"373":1,"376":1,"378":1,"382":2,"383":1,"384":1,"386":1,"388":4,"390":2,"409":1,"412":1,"422":1,"423":1,"433":1,"435":1,"436":3,"439":1,"446":2,"447":1,"458":1,"463":1,"464":1,"473":1,"480":2,"484":1,"497":1,"507":1,"512":1,"515":2,"527":2,"528":1,"529":2,"531":1,"549":1,"551":1,"569":1,"582":1,"587":1,"589":1,"598":1,"607":1,"618":1,"627":1,"636":2,"637":1,"638":1,"641":1,"643":1,"649":1,"669":1,"673":3,"683":1,"684":1,"685":1,"686":1,"693":1,"694":1,"696":1,"699":1,"701":1,"703":2,"704":2,"706":1,"709":2,"713":3,"714":2,"715":1,"720":5,"722":1,"723":1,"747":1,"770":1,"771":2,"794":1,"801":1,"807":1,"809":2,"816":1,"823":1,"833":2,"834":2,"835":1,"836":1,"837":4,"840":1,"841":3,"844":4,"845":3,"847":1,"848":9,"849":1,"851":1,"854":1,"856":1,"860":2,"865":2,"868":2,"869":6,"871":1,"872":4,"873":2,"875":2,"876":3,"878":1,"880":2,"881":1,"883":1,"884":1,"885":1,"886":1,"888":2,"897":1,"902":1,"903":1,"904":6,"907":3,"910":1,"914":1,"915":2,"916":4,"917":1,"918":2,"919":1,"922":1,"928":1,"929":1,"932":1,"933":1,"934":2,"935":1,"936":1,"941":1,"942":1,"953":1,"956":1,"957":1,"961":1,"966":1,"967":1,"971":1,"974":1,"979":1,"980":1,"982":2,"983":1,"987":1,"988":1,"990":1,"993":1,"994":2,"996":1,"1005":1,"1006":1,"1017":1,"1027":1,"1029":1,"1031":1,"1035":1,"1036":2,"1037":2,"1038":6,"1042":2,"1045":1,"1046":1,"1047":2,"1049":3,"1055":2,"1056":4,"1057":2,"1060":1,"1063":2,"1064":1,"1066":2,"1067":3,"1068":3,"1073":1,"1076":2,"1077":1,"1079":2,"1080":1,"1081":2,"1084":1,"1086":2,"1094":1,"1095":1,"1096":5,"1097":1,"1098":6,"1099":1,"1100":2,"1101":6,"1102":2,"1103":1,"1104":1,"1106":2,"1107":1,"1108":1,"1111":1,"1113":3,"1121":2,"1125":3,"1126":5,"1128":1,"1133":1,"1134":1,"1135":1,"1137":1,"1139":2,"1140":1,"1148":1,"1150":3,"1162":1,"1185":1,"1203":1,"1204":1,"1205":1,"1206":3,"1207":1,"1209":2,"1210":1,"1214":1,"1215":1,"1216":1,"1220":1,"1222":1,"1228":1,"1232":2,"1234":1,"1235":3,"1236":1,"1239":1,"1241":1,"1251":1,"1254":3,"1255":1,"1279":1,"1303":2,"1305":2,"1313":1,"1326":1,"1327":1,"1332":1,"1346":1,"1351":1,"1352":1,"1355":1,"1357":1,"1362":1,"1366":1,"1368":3,"1374":1,"1375":1,"1377":1,"1378":2,"1381":1,"1382":1,"1385":2,"1386":2,"1387":1,"1388":2,"1390":1,"1391":1,"1393":2,"1394":3,"1395":1,"1396":2,"1398":1,"1401":1,"1402":1,"1403":2,"1405":1,"1406":1,"1407":4,"1409":1,"1412":1,"1413":2,"1414":2,"1417":1,"1419":2,"1420":1,"1422":2,"1423":1,"1435":1,"1436":2,"1438":1,"1442":1,"1458":2,"1459":1,"1460":2,"1470":1,"1471":1,"1480":1,"1499":2,"1511":3,"1516":2,"1519":1,"1521":1,"1523":3,"1524":2,"1526":1,"1540":1,"1544":1,"1556":1,"1567":1,"1568":1,"1572":1,"1582":1,"1605":1,"1608":1,"1609":1,"1616":1,"1620":2,"1651":3,"1653":1,"1659":1,"1664":2,"1672":1,"1685":1,"1689":1,"1717":1,"1731":1,"1732":1,"1737":1,"1738":2,"1740":1,"1741":1,"1743":1,"1753":5,"1759":1,"1766":1,"1769":2,"1782":1,"1789":1,"1792":143,"1806":1,"1807":1,"1816":1,"1822":1,"1823":1,"1824":4,"1825":2,"1830":1,"1840":2,"1846":1,"1847":2,"1852":3,"1853":1,"1862":1,"1866":1,"1867":1,"1878":1,"1885":2,"1890":1,"1901":1,"1906":2,"1911":1,"1915":1,"1917":3,"1922":2,"1924":1,"1925":2,"1927":1,"1955":1,"1958":1,"1959":1,"1961":1,"1969":1,"1974":2,"2000":2,"2001":1,"2007":1,"2010":2,"2018":1,"2024":1,"2038":1,"2039":1,"2040":5,"2047":1,"2059":1,"2060":2,"2063":1,"2072":2,"2075":1,"2077":1,"2083":1,"2094":2,"2097":1,"2098":1,"2099":1,"2100":1,"2101":1,"2106":4,"2107":1,"2109":1,"2110":2,"2111":2,"2112":1,"2113":3,"2125":1,"2131":2,"2137":1,"2140":1,"2144":1,"2153":1,"2155":1,"2156":4,"2167":1,"2170":2,"2171":1,"2172":1,"2177":1,"2178":1,"2181":1,"2183":1,"2184":1,"2185":1,"2193":1,"2195":2,"2197":2,"2200":1,"2210":1,"2222":2,"2223":1,"2247":2,"2251":1,"2252":11,"2253":4,"2254":1,"2255":4,"2256":2,"2257":1,"2258":3,"2264":2,"2265":1,"2271":1,"2272":1,"2277":3,"2282":2,"2284":1,"2288":1,"2289":2,"2291":2,"2296":3,"2297":1,"2301":1,"2309":1,"2320":1,"2321":1,"2325":1,"2330":1,"2334":2,"2337":1,"2339":1,"2352":1,"2353":2,"2375":5,"2379":1,"2380":9,"2381":1,"2383":3,"2384":1,"2389":2,"2392":3,"2394":1,"2395":1,"2396":1,"2397":2,"2398":2,"2401":1,"2405":1,"2406":1,"2412":1,"2416":1,"2419":2,"2422":2,"2429":1,"2430":1,"2431":1,"2436":1,"2438":5,"2450":1,"2451":1,"2464":1,"2466":4,"2470":1,"2471":1,"2476":3,"2481":3,"2482":2,"2483":2,"2484":1,"2489":1,"2490":3,"2496":1,"2497":1,"2502":1,"2509":1,"2517":1,"2519":2,"2525":1,"2527":2,"2528":1,"2529":3,"2530":3,"2531":1,"2532":4,"2533":4,"2534":1,"2537":6,"2540":2,"2541":2,"2542":4,"2544":1,"2549":2,"2565":2,"2572":1,"2575":4,"2586":1,"2587":4,"2589":1,"2600":1,"2607":2,"2611":1,"2615":1,"2625":1,"2632":1,"2634":3,"2635":6,"2650":1,"2664":1,"2667":1,"2669":1,"2670":1,"2673":1,"2678":1,"2679":1,"2684":2,"2685":1,"2688":1,"2694":1,"2695":1,"2712":1,"2716":1,"2717":1,"2719":1,"2721":2,"2723":1,"2725":1,"2727":1,"2732":1,"2733":1,"2734":2,"2742":3,"2745":1,"2751":1,"2754":1,"2756":1,"2758":1,"2759":2,"2760":1,"2762":2,"2763":1,"2764":2,"2765":1,"2772":1,"2781":1,"2786":2,"2797":1,"2802":2,"2803":2,"2806":2,"2809":2,"2810":2,"2812":1,"2814":1,"2815":1,"2817":1,"2819":1,"2820":1,"2821":1,"2828":1,"2830":1,"2832":2,"2833":1,"2834":1,"2835":1,"2841":2,"2844":1,"2845":2,"2851":1,"2852":1,"2855":1,"2858":1,"2862":2,"2864":1,"2865":1,"2866":1,"2867":1,"2870":1,"2871":3,"2875":1,"2876":1,"2877":1,"2878":3,"2881":4}}],["often",{"2":{"831":1,"833":1,"843":1,"869":1,"922":1,"941":1,"948":1,"1013":1,"1014":1,"1125":1,"1153":1,"1206":2,"1511":1,"1738":1,"1792":1,"1953":1,"2282":1,"2502":1,"2559":1,"2769":1,"2834":1}}],["offload",{"2":{"2177":1}}],["offloading",{"2":{"1049":1,"2177":1}}],["offset",{"2":{"1792":3,"1856":4,"2224":3,"2450":3,"2451":4,"2452":1,"2453":1,"2454":2,"2455":1,"2456":1}}],["offs",{"2":{"1404":1}}],["official",{"2":{"869":1,"1101":1,"1420":1,"2779":1}}],["officedocument",{"2":{"772":1,"773":1,"893":1}}],["offer",{"2":{"1098":1,"1280":1,"2537":1}}],["offers",{"2":{"834":1,"865":1,"1088":1,"1094":1,"1097":1,"1098":1,"1107":1,"1330":1,"1385":1,"2482":1,"2789":1}}],["offending",{"2":{"102":1,"109":1,"1449":1,"1460":1,"1792":1,"2375":1,"2376":1,"2428":1,"2435":1}}],["off",{"0":{"2544":1,"2724":1},"2":{"88":1,"307":1,"720":1,"848":1,"849":1,"873":1,"874":1,"948":1,"1007":1,"1045":1,"1079":1,"1139":1,"1253":1,"1384":2,"1605":1,"1792":4,"1801":1,"1802":1,"1851":1,"2104":2,"2114":1,"2221":1,"2382":1,"2497":1,"2527":1,"2530":1,"2534":1,"2536":2,"2544":6,"2688":1,"2752":3,"2765":1,"2794":1,"2800":2,"2801":2,"2862":1,"2880":2}}],["of",{"0":{"22":1,"324":1,"335":1,"839":2,"873":1,"885":1,"916":1,"922":1,"975":1,"999":1,"1000":1,"1185":1,"1203":1,"1246":1,"1281":1,"1403":1,"1405":1,"1406":1,"1526":1,"2437":1,"2487":1,"2586":1,"2726":1,"2734":1,"2740":1},"1":{"840":2,"841":2,"842":2,"843":2,"844":2,"845":2,"846":2,"847":2,"848":2,"849":2,"850":2,"851":2,"852":2,"853":2,"854":2,"855":2,"856":2,"857":2,"858":2,"859":2,"860":2,"861":2,"862":2,"863":2,"864":2,"865":2,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"1":3,"3":1,"17":1,"21":1,"22":1,"23":1,"41":1,"51":2,"74":3,"75":1,"78":1,"81":1,"88":1,"108":1,"113":1,"119":1,"161":1,"163":1,"165":1,"167":1,"175":1,"179":1,"212":1,"213":1,"214":3,"227":1,"229":1,"253":1,"298":1,"302":1,"304":1,"305":1,"307":1,"308":2,"309":1,"310":1,"316":1,"317":1,"319":2,"328":1,"334":1,"335":3,"336":1,"337":3,"347":1,"349":1,"352":1,"358":2,"360":2,"361":2,"363":2,"364":2,"365":1,"369":3,"370":1,"375":1,"377":1,"378":1,"382":1,"386":1,"387":1,"388":1,"390":1,"394":1,"414":3,"422":1,"436":1,"439":1,"446":1,"448":4,"449":1,"454":1,"479":1,"480":1,"484":1,"494":1,"522":1,"527":1,"533":1,"567":1,"575":1,"595":1,"607":1,"615":1,"617":1,"639":1,"643":1,"644":1,"645":1,"646":1,"650":1,"663":1,"673":1,"685":2,"689":2,"693":1,"698":2,"704":1,"711":1,"716":1,"720":1,"741":1,"747":3,"748":1,"760":1,"761":1,"762":1,"770":1,"771":2,"772":1,"773":1,"776":1,"781":2,"782":2,"784":2,"786":1,"788":1,"809":1,"823":1,"831":2,"832":2,"834":1,"835":2,"836":1,"838":3,"840":7,"841":7,"843":11,"844":18,"845":9,"847":4,"848":7,"849":8,"851":23,"852":18,"855":3,"856":1,"857":9,"859":7,"860":6,"861":4,"863":2,"864":6,"865":7,"866":1,"867":1,"868":10,"869":11,"871":7,"872":25,"873":14,"874":6,"875":1,"876":4,"877":1,"879":3,"880":2,"892":1,"894":1,"910":1,"911":1,"912":2,"913":1,"914":2,"916":6,"917":1,"918":5,"919":5,"920":9,"921":2,"922":1,"924":1,"928":2,"929":1,"932":2,"940":1,"946":1,"947":4,"948":2,"951":1,"953":1,"966":1,"968":1,"969":2,"971":2,"972":1,"974":1,"975":4,"981":1,"982":1,"983":1,"985":2,"992":2,"995":1,"996":3,"1005":2,"1006":1,"1007":1,"1008":1,"1009":1,"1010":1,"1013":1,"1015":1,"1021":1,"1024":2,"1026":1,"1032":1,"1033":1,"1035":1,"1036":2,"1037":9,"1038":2,"1039":1,"1041":1,"1044":3,"1046":2,"1049":1,"1058":1,"1059":1,"1064":6,"1065":3,"1066":1,"1069":1,"1073":2,"1074":2,"1075":2,"1076":3,"1077":2,"1078":1,"1081":2,"1082":2,"1083":1,"1088":2,"1094":2,"1096":1,"1097":3,"1102":2,"1104":1,"1105":1,"1108":1,"1114":1,"1121":1,"1122":1,"1123":1,"1128":2,"1129":3,"1132":1,"1133":5,"1134":1,"1135":1,"1136":1,"1148":1,"1150":1,"1151":1,"1157":2,"1160":1,"1161":1,"1162":1,"1164":2,"1165":2,"1167":1,"1168":2,"1169":1,"1171":1,"1172":1,"1175":1,"1176":3,"1178":1,"1181":4,"1185":1,"1188":1,"1189":1,"1193":1,"1196":1,"1203":1,"1208":1,"1209":1,"1211":1,"1217":1,"1224":1,"1232":1,"1234":1,"1247":1,"1254":4,"1255":1,"1278":1,"1281":5,"1302":3,"1304":1,"1305":1,"1309":1,"1322":1,"1324":2,"1325":2,"1327":1,"1328":1,"1329":1,"1351":2,"1366":2,"1367":1,"1368":1,"1370":1,"1373":1,"1375":1,"1378":1,"1381":1,"1382":10,"1384":1,"1385":17,"1386":8,"1388":1,"1390":4,"1391":1,"1393":1,"1394":8,"1395":2,"1396":4,"1398":5,"1399":4,"1400":6,"1401":11,"1402":7,"1403":16,"1404":7,"1405":3,"1409":1,"1411":1,"1412":3,"1414":1,"1420":1,"1421":1,"1422":1,"1423":1,"1427":2,"1428":1,"1429":1,"1430":3,"1431":2,"1432":3,"1435":1,"1437":1,"1438":1,"1439":2,"1440":1,"1441":4,"1442":4,"1458":2,"1460":2,"1464":2,"1472":1,"1475":1,"1477":1,"1489":1,"1511":3,"1515":1,"1521":1,"1522":1,"1523":1,"1526":1,"1540":1,"1544":1,"1558":1,"1559":1,"1563":1,"1567":1,"1568":2,"1574":1,"1581":1,"1588":1,"1593":1,"1594":1,"1605":1,"1609":2,"1615":1,"1620":1,"1639":3,"1651":3,"1655":1,"1670":1,"1703":6,"1704":2,"1705":1,"1706":1,"1707":1,"1722":1,"1733":1,"1740":1,"1741":2,"1743":3,"1744":1,"1746":1,"1759":2,"1765":1,"1785":1,"1792":167,"1802":1,"1804":1,"1822":1,"1823":1,"1824":3,"1825":1,"1838":4,"1840":1,"1856":3,"1857":1,"1862":2,"1874":1,"1882":1,"1884":1,"1906":1,"1912":2,"1917":3,"1925":2,"1947":1,"1948":1,"1949":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1961":1,"1967":4,"1974":7,"1991":7,"2000":1,"2005":1,"2006":1,"2009":3,"2011":2,"2016":1,"2020":1,"2040":2,"2072":1,"2076":1,"2078":1,"2086":4,"2092":1,"2094":6,"2097":2,"2098":2,"2099":1,"2101":1,"2102":1,"2104":1,"2105":1,"2106":1,"2107":3,"2108":1,"2110":1,"2111":2,"2125":3,"2129":1,"2130":1,"2131":1,"2141":2,"2154":1,"2156":2,"2164":1,"2165":1,"2175":1,"2176":1,"2177":1,"2181":1,"2182":1,"2184":1,"2186":1,"2187":1,"2191":1,"2192":1,"2200":1,"2206":1,"2217":1,"2219":1,"2221":1,"2222":1,"2224":1,"2245":1,"2247":2,"2253":4,"2254":9,"2255":2,"2257":1,"2258":4,"2259":2,"2261":1,"2264":1,"2265":1,"2266":2,"2267":2,"2270":1,"2271":1,"2277":1,"2284":1,"2288":1,"2289":2,"2310":1,"2314":1,"2320":1,"2322":1,"2323":1,"2324":2,"2325":1,"2326":1,"2330":1,"2332":2,"2333":1,"2334":1,"2336":2,"2337":1,"2338":1,"2339":2,"2347":3,"2348":2,"2350":1,"2353":1,"2354":1,"2357":2,"2358":2,"2359":2,"2364":1,"2366":1,"2367":1,"2370":1,"2375":4,"2376":2,"2377":2,"2378":2,"2379":2,"2380":3,"2385":2,"2388":2,"2389":2,"2391":1,"2393":1,"2394":1,"2397":4,"2398":6,"2399":1,"2401":2,"2405":1,"2407":1,"2410":1,"2411":2,"2412":1,"2413":2,"2414":2,"2415":1,"2419":1,"2421":2,"2424":2,"2428":1,"2431":2,"2432":2,"2435":2,"2437":1,"2438":6,"2445":2,"2446":2,"2450":1,"2451":1,"2452":1,"2455":1,"2459":1,"2463":1,"2464":4,"2466":2,"2468":1,"2476":3,"2479":1,"2481":1,"2482":1,"2483":3,"2484":1,"2486":3,"2492":1,"2494":1,"2495":1,"2497":1,"2500":1,"2502":3,"2504":2,"2509":2,"2517":1,"2518":4,"2519":1,"2523":1,"2527":1,"2528":3,"2529":4,"2530":2,"2531":1,"2532":2,"2533":1,"2534":2,"2535":2,"2536":1,"2537":6,"2539":1,"2540":1,"2541":1,"2542":1,"2543":2,"2551":1,"2554":1,"2565":1,"2569":1,"2576":1,"2577":1,"2586":8,"2588":3,"2589":1,"2590":3,"2597":1,"2603":3,"2607":8,"2611":3,"2614":3,"2615":2,"2618":1,"2621":1,"2626":1,"2632":1,"2633":9,"2634":3,"2635":1,"2641":1,"2642":1,"2650":1,"2659":1,"2663":1,"2679":1,"2681":1,"2688":1,"2692":1,"2701":1,"2706":1,"2744":1,"2759":1,"2760":2,"2764":1,"2765":1,"2767":2,"2771":1,"2773":1,"2776":2,"2789":1,"2792":1,"2793":1,"2794":1,"2795":1,"2800":1,"2802":1,"2809":1,"2810":1,"2811":1,"2812":1,"2814":3,"2815":2,"2816":1,"2824":1,"2826":1,"2829":1,"2830":1,"2833":2,"2835":1,"2841":2,"2842":1,"2851":1,"2852":1,"2858":1,"2862":1,"2863":1,"2864":2,"2865":1,"2869":1,"2871":3,"2878":1,"2879":1}}],["gmt",{"2":{"2823":1,"2824":1}}],["gc",{"2":{"2397":1,"2398":2,"2604":1}}],["gcm",{"2":{"1656":7,"1663":1,"1792":3}}],["gcp",{"2":{"1094":1}}],["gs",{"2":{"1427":1}}],["globs",{"2":{"2538":2}}],["glob",{"0":{"2371":1},"2":{"1386":1,"1792":4,"2000":1,"2002":2,"2094":2,"2095":1,"2096":1,"2221":1,"2228":1,"2318":1,"2330":1,"2371":1,"2525":1,"2537":3,"2538":1,"2539":2,"2840":1,"2841":1,"2877":1}}],["globally",{"2":{"65":1,"214":1,"336":2,"547":1,"655":1,"917":2,"964":1,"1113":1,"1139":1,"1540":1,"1544":1,"1973":1,"2010":1,"2183":1,"2184":1,"2222":1,"2468":1,"2587":1,"2756":1,"2765":1,"2786":1}}],["global",{"0":{"336":1},"2":{"4":1,"10":1,"42":1,"52":1,"53":1,"139":1,"141":1,"422":2,"436":1,"446":3,"479":1,"480":1,"636":1,"650":1,"663":1,"666":1,"668":1,"680":1,"720":1,"725":1,"851":1,"917":1,"1066":1,"1069":1,"1150":1,"1162":2,"1324":3,"1325":2,"1415":1,"1529":1,"1559":1,"1571":1,"1672":1,"1722":1,"1792":9,"1929":1,"1951":4,"1952":4,"1953":4,"1954":4,"1955":2,"1957":2,"1958":5,"2201":1,"2224":1,"2379":3,"2435":1,"2470":4,"2472":2,"2484":1,"2502":1,"2533":1,"2594":1,"2611":1,"2641":1,"2655":1,"2728":1,"2769":1,"2835":1}}],["glue",{"2":{"1382":3,"1405":1}}],["gb",{"2":{"1255":2,"1278":1}}],["gbp",{"2":{"1023":2,"1024":1}}],["gdpr",{"2":{"1251":1}}],["gpt",{"2":{"1105":1}}],["gzip",{"0":{"1941":1},"2":{"1101":1,"1790":1,"1792":1,"1797":1,"1935":1,"1937":1,"1940":1,"1941":3}}],["gaming",{"2":{"1323":1}}],["game",{"2":{"843":1,"852":1,"1081":1}}],["garbage",{"2":{"1254":1}}],["garcia",{"2":{"913":1}}],["gained",{"2":{"2447":1}}],["gains",{"0":{"2461":1},"2":{"944":1,"1274":1,"2270":1,"2398":1,"2415":1,"2461":1,"2476":1,"2621":1}}],["gain",{"2":{"872":1,"1125":1}}],["gave",{"2":{"851":1}}],["gather",{"2":{"1130":1}}],["gathered",{"2":{"851":1}}],["gating",{"2":{"1082":1,"1094":1,"1792":1,"2106":1,"2537":1}}],["gates",{"2":{"1792":1,"1827":1,"2438":1,"2481":1}}],["gate",{"2":{"874":1,"1045":1,"1419":1,"1792":1,"1825":1,"2107":1,"2113":1,"2221":1,"2423":1,"2463":2,"2466":3,"2482":1,"2537":1,"2546":1,"2878":1,"2879":1,"2880":1}}],["gated",{"2":{"320":1,"1792":1}}],["gateways",{"2":{"1329":1}}],["gateway",{"0":{"451":1,"1328":1,"1333":1,"1345":1,"1780":1,"2756":1,"2815":1},"1":{"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1},"2":{"140":1,"453":2,"876":1,"1010":1,"1011":1,"1088":1,"1100":1,"1101":1,"1114":1,"1328":1,"1350":1,"1351":1,"1672":1,"1792":1,"1824":1,"1928":1,"2255":1,"2549":1,"2806":2,"2812":3}}],["gaps",{"2":{"860":1}}],["gap",{"2":{"845":1,"857":1,"973":2,"1090":1,"1091":1,"1111":1,"1263":1,"1266":1,"1268":1,"1394":1,"2848":1}}],["grpc",{"2":{"1792":2,"1800":1,"1807":3,"2804":1}}],["grind",{"2":{"1324":1}}],["grievances",{"2":{"853":1}}],["growing",{"2":{"1379":1}}],["growth",{"2":{"861":1,"1164":1,"1168":1}}],["grows",{"2":{"860":1,"1164":1,"1171":1,"1193":1,"1270":1,"2297":1}}],["grow",{"2":{"857":1,"1165":1}}],["groupname",{"2":{"1974":1,"2607":1}}],["group1",{"2":{"1974":2,"2607":2}}],["groupby",{"2":{"852":1}}],["groupjoin",{"2":{"852":1}}],["grouped",{"2":{"720":1,"1285":1,"1414":1,"2432":1,"2442":1,"2535":1}}],["grouping",{"0":{"1414":1},"2":{"347":1,"857":1,"868":1,"1913":1,"2430":1,"2432":1}}],["group",{"0":{"352":1},"2":{"158":1,"352":1,"724":1,"852":1,"860":4,"861":1,"914":1,"916":2,"918":1,"1096":1,"1105":1,"1130":1,"1197":1,"1375":1,"1414":1,"1504":2,"1974":1,"2247":1,"2537":1,"2607":1}}],["grafana",{"2":{"2804":1}}],["graviton",{"2":{"2576":1,"2779":1,"2783":1,"2790":1}}],["grammar",{"2":{"2535":1,"2759":1,"2771":1}}],["grail",{"2":{"1382":1}}],["grained",{"2":{"1071":1,"1464":1,"1792":1,"2376":1,"2628":1}}],["gradual",{"2":{"1164":1,"1229":1,"1879":1}}],["gradually",{"2":{"1159":1}}],["grade",{"2":{"866":1,"1381":1,"1382":1,"1417":1,"2438":1,"2776":1}}],["grants",{"2":{"1385":1,"1792":1}}],["grant",{"2":{"926":3,"1792":1,"2016":1,"2024":1,"2156":1,"2542":1,"2632":1,"2755":1,"2876":1}}],["granted",{"2":{"22":1,"926":1}}],["graceful",{"0":{"2362":1},"2":{"2106":1,"2113":1,"2157":1,"2537":1,"2543":1,"2546":1,"2878":1,"2881":1}}],["gracefully",{"2":{"991":1,"2157":1,"2362":1,"2543":2}}],["grace",{"2":{"913":1,"2529":2,"2530":1,"2865":2}}],["graphql",{"2":{"1122":1,"1127":1}}],["graph",{"0":{"856":1},"2":{"857":1,"860":1,"864":1,"874":1,"1075":1,"1409":2,"1694":1,"1695":2,"1792":3,"2868":2}}],["grey",{"2":{"2535":1}}],["green",{"2":{"994":1,"1076":1,"1080":1,"1081":1,"2513":1,"2523":1,"2535":1,"2546":1,"2879":1}}],["greeting",{"2":{"378":1,"2335":1}}],["grep",{"2":{"872":1,"1382":1,"1416":1,"1581":1}}],["grew",{"2":{"863":1,"1404":2}}],["greatest",{"2":{"2398":1}}],["greater",{"2":{"1792":1,"2177":1}}],["greatly",{"2":{"912":1}}],["great",{"2":{"843":1,"1385":1,"1396":1,"1398":1,"1401":1,"1402":1,"2184":2}}],["gt",{"0":{"1313":1},"2":{"63":1,"81":1,"108":1,"214":1,"215":1,"319":3,"326":4,"355":4,"364":2,"395":1,"423":2,"429":1,"436":2,"454":1,"527":1,"528":1,"531":1,"534":1,"615":2,"646":1,"650":3,"666":1,"669":2,"849":1,"852":1,"868":1,"1040":4,"1078":3,"1102":1,"1107":2,"1171":1,"1189":1,"1358":1,"1367":1,"1385":1,"1408":1,"1410":1,"1412":2,"1413":3,"1414":1,"1416":1,"1424":1,"1428":2,"1460":1,"1520":1,"1561":2,"1572":2,"1581":1,"1582":1,"1738":1,"1743":1,"1822":1,"1824":1,"1913":1,"2018":4,"2040":1,"2075":1,"2077":1,"2109":1,"2125":3,"2225":1,"2270":1,"2310":1,"2313":2,"2319":2,"2337":2,"2339":2,"2375":1,"2380":2,"2399":1,"2400":2,"2402":1,"2412":1,"2419":1,"2422":1,"2423":1,"2427":1,"2432":3,"2435":1,"2436":1,"2437":1,"2443":1,"2452":1,"2462":2,"2481":5,"2482":1,"2487":1,"2490":1,"2502":2,"2535":5,"2604":1,"2614":1,"2823":1,"2830":2,"2835":1,"2843":2}}],["giving",{"2":{"2858":1}}],["give",{"2":{"319":1,"372":1,"669":1,"920":1,"1081":1,"1086":1,"1096":1,"1391":1,"1392":1,"1406":1,"1441":1,"2039":1,"2183":1,"2533":1,"2723":1,"2732":1,"2733":1,"2734":1,"2740":1,"2766":1,"2867":1}}],["gives",{"2":{"308":1,"666":1,"705":1,"835":1,"869":1,"876":2,"907":1,"920":1,"966":1,"986":1,"1049":1,"1066":1,"1069":1,"1088":1,"1162":1,"1185":1,"1386":2,"1409":1,"1581":1,"2466":1,"2492":1,"2531":1,"2840":1,"2846":1}}],["given",{"2":{"51":1,"214":1,"319":1,"848":2,"852":1,"1162":1,"1385":1,"1401":1,"1743":1,"2038":2,"2470":1,"2502":1,"2529":1,"2693":1,"2765":1,"2828":1}}],["gigantic",{"2":{"1396":1}}],["gitlab",{"2":{"2102":1}}],["gitignore",{"2":{"1407":1}}],["git",{"2":{"970":2,"1119":1,"1207":2,"1380":1,"1407":1,"2162":1,"2792":2}}],["github",{"0":{"1693":1},"2":{"3":1,"868":1,"878":1,"913":1,"914":1,"919":1,"920":2,"921":1,"947":1,"970":1,"976":1,"1010":1,"1047":1,"1048":1,"1060":1,"1098":2,"1117":1,"1118":1,"1119":1,"1123":1,"1183":1,"1207":1,"1302":1,"1328":1,"1352":1,"1380":1,"1445":1,"1465":1,"1605":2,"1682":1,"1685":1,"1687":1,"1690":1,"1693":4,"1698":4,"1736":9,"1737":2,"1788":1,"1792":12,"1894":1,"2102":1,"2160":1,"2162":1,"2267":1,"2386":1,"2497":2,"2576":2,"2625":1,"2688":2,"2736":1,"2781":1,"2782":1,"2783":1,"2784":1,"2790":1,"2792":2,"2880":1}}],["gist",{"2":{"864":2}}],["gif",{"2":{"747":1,"1099":1,"1358":1,"1792":1,"2123":1,"2125":1}}],["g",{"2":{"31":1,"74":2,"108":1,"109":1,"155":1,"175":1,"188":1,"210":1,"212":2,"268":1,"286":1,"297":1,"302":1,"349":1,"370":2,"387":2,"388":3,"390":1,"415":1,"423":2,"446":1,"447":1,"448":1,"453":1,"582":3,"583":2,"587":1,"615":1,"663":1,"720":1,"734":1,"747":1,"748":1,"761":2,"768":1,"771":1,"776":1,"781":1,"786":1,"826":1,"868":1,"892":1,"927":2,"961":1,"967":1,"996":2,"1060":1,"1066":1,"1088":1,"1097":1,"1098":1,"1101":1,"1111":1,"1117":1,"1193":1,"1225":2,"1232":1,"1233":1,"1237":1,"1239":2,"1284":1,"1385":2,"1386":1,"1409":1,"1427":2,"1431":1,"1447":1,"1449":1,"1451":1,"1454":2,"1493":1,"1511":1,"1519":3,"1521":1,"1523":1,"1525":1,"1569":1,"1575":1,"1590":1,"1605":1,"1609":1,"1614":1,"1664":1,"1685":1,"1687":1,"1706":1,"1732":1,"1738":1,"1741":2,"1767":1,"1792":34,"1823":2,"1825":1,"1831":1,"1837":1,"1840":1,"1856":2,"1862":1,"1875":2,"1882":1,"1887":1,"1906":1,"1917":1,"1922":1,"1925":1,"1957":3,"1958":1,"1991":1,"2004":1,"2062":1,"2110":1,"2112":1,"2177":1,"2193":1,"2195":1,"2210":1,"2258":1,"2264":1,"2265":1,"2282":1,"2283":1,"2289":2,"2291":1,"2296":1,"2303":1,"2318":1,"2322":1,"2329":2,"2330":1,"2336":1,"2337":1,"2338":1,"2339":1,"2347":1,"2357":1,"2358":1,"2360":1,"2364":2,"2365":2,"2367":1,"2379":3,"2380":2,"2413":1,"2430":1,"2434":1,"2438":1,"2444":2,"2445":1,"2446":1,"2453":1,"2483":2,"2487":1,"2490":1,"2497":3,"2505":2,"2517":1,"2518":4,"2519":1,"2520":1,"2529":1,"2530":2,"2532":3,"2533":1,"2537":1,"2544":1,"2549":1,"2566":1,"2572":2,"2586":1,"2590":1,"2591":1,"2597":1,"2603":1,"2634":1,"2635":1,"2645":1,"2649":1,"2656":1,"2659":1,"2679":2,"2688":1,"2692":1,"2696":1,"2767":1,"2768":1,"2779":1,"2786":1,"2811":1,"2840":1,"2847":1,"2854":1}}],["guest",{"2":{"1792":1,"2038":1,"2039":1}}],["guessed",{"2":{"1396":1}}],["guessing",{"2":{"1386":1}}],["guess",{"2":{"855":1,"1402":1,"2577":1}}],["guts",{"2":{"1438":1}}],["guru",{"2":{"1393":1}}],["guidance",{"2":{"1792":2,"1820":1,"1823":1}}],["guid",{"2":{"1366":1}}],["guided",{"2":{"1402":1}}],["guidelines",{"0":{"1169":1}}],["guide",{"0":{"2190":1,"2680":1,"2777":1},"1":{"2191":1,"2192":1,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2211":1,"2212":1,"2213":1,"2214":1,"2215":1,"2216":1,"2217":1,"2218":1,"2219":1,"2681":1,"2682":1,"2683":1,"2684":1,"2685":1,"2686":1,"2687":1,"2688":1,"2689":1,"2690":1,"2691":1,"2692":1,"2693":1,"2694":1,"2695":1,"2696":1,"2697":1,"2698":1,"2699":1,"2700":1,"2701":1,"2702":1,"2703":1,"2704":1,"2705":1,"2706":1,"2778":1,"2779":1,"2780":1,"2781":1,"2782":1,"2783":1,"2784":1,"2785":1,"2786":1,"2787":1,"2788":1,"2789":1,"2790":1,"2791":1,"2792":1,"2793":1},"2":{"11":3,"26":3,"42":2,"53":2,"65":3,"76":2,"89":2,"98":2,"122":2,"130":2,"141":2,"151":2,"164":2,"170":1,"189":1,"198":2,"217":3,"220":1,"259":2,"281":1,"293":3,"296":1,"306":1,"315":1,"327":1,"338":1,"345":2,"356":1,"367":2,"385":2,"396":1,"410":2,"431":1,"456":1,"471":2,"481":2,"495":2,"505":2,"513":2,"525":2,"547":2,"557":2,"568":1,"578":2,"588":1,"596":3,"605":2,"617":1,"626":1,"634":3,"647":3,"670":3,"680":2,"692":1,"697":1,"702":1,"707":1,"712":1,"717":1,"725":2,"740":2,"790":2,"804":2,"820":2,"830":1,"1037":1,"1073":1,"1074":1,"1079":1,"1082":1,"1135":1,"1368":1,"1380":1,"1386":3,"1396":1,"1405":1,"1465":3,"1484":3,"1495":2,"1506":3,"1535":2,"1549":3,"1583":2,"1600":2,"1610":2,"1634":2,"1647":2,"1665":2,"1679":2,"1699":3,"1748":3,"1760":2,"1785":1,"1799":1,"1802":1,"1811":3,"1834":1,"1864":2,"1894":1,"1913":2,"1932":3,"1945":2,"1962":2,"1976":2,"1996":2,"2013":1,"2043":2,"2081":2,"2090":2,"2092":1,"2114":1,"2120":2,"2133":2,"2150":2,"2158":1,"2159":2,"2170":1,"2190":2,"2706":1,"2716":1,"2740":1,"2741":1,"2750":1,"2759":1,"2769":1,"2771":1,"2774":1,"2793":1,"2794":1,"2800":1,"2805":1,"2806":1,"2817":1,"2818":1,"2819":1,"2826":2,"2827":1,"2836":1,"2838":1,"2839":1,"2859":1,"2860":1}}],["gucs",{"2":{"1079":1,"1851":2,"2382":2}}],["guc",{"2":{"1070":2,"1792":1,"1851":1,"2099":1,"2382":1,"2527":1,"2862":1}}],["guarantees",{"2":{"1122":1,"1382":1,"2872":1}}],["guaranteed",{"2":{"864":1,"2112":1,"2221":1,"2532":1}}],["guard",{"0":{"1925":1},"2":{"1431":1,"1792":1,"1917":2,"1925":2,"2267":1,"2504":1,"2517":1,"2521":1,"2814":1}}],["guardrails",{"2":{"1123":1,"2389":1}}],["guarding",{"2":{"863":1}}],["guards",{"2":{"863":1,"1792":2}}],["guarded",{"2":{"852":2,"2812":1}}],["guy",{"2":{"1":1}}],["god",{"2":{"1441":2,"1442":1}}],["governed",{"2":{"1382":1}}],["goroutine",{"2":{"1275":1}}],["gold",{"2":{"927":1}}],["googleapis",{"2":{"1691":2,"1792":2}}],["google",{"0":{"1691":1},"2":{"868":1,"1013":1,"1037":1,"1048":1,"1050":1,"1059":5,"1060":2,"1061":2,"1062":3,"1064":1,"1098":2,"1123":1,"1445":1,"1465":1,"1682":1,"1685":1,"1687":1,"1690":4,"1691":2,"1698":4,"1788":1,"1792":5,"1894":1,"2175":1,"2188":1,"2189":1,"2736":1}}],["good",{"0":{"1430":1},"2":{"836":1,"847":1,"851":1,"860":1,"920":1,"933":1,"936":1,"1386":2,"1390":1,"1401":1,"1402":2,"1403":1,"1581":1,"1738":1,"2297":1}}],["gone",{"2":{"849":1,"865":1,"871":1}}],["gotrue",{"2":{"1088":2,"1098":2,"1119":1,"1126":1}}],["got",{"2":{"841":1,"1037":1,"1073":1,"1075":1,"1382":1,"1386":1,"2453":1}}],["going",{"2":{"841":1,"851":1,"1135":1,"1384":1,"1401":1,"1402":1,"1412":1}}],["goal",{"2":{"834":1,"835":1,"843":1,"864":1,"1081":1}}],["goes",{"0":{"2811":1},"2":{"646":3,"840":1,"841":1,"847":1,"848":1,"851":1,"852":1,"859":1,"872":1,"874":2,"948":1,"1316":2,"1396":1,"1398":1,"1431":1,"2806":1,"2813":1,"2845":1}}],["go",{"0":{"1275":1,"1383":1,"2521":1},"2":{"3":1,"837":1,"840":1,"841":1,"848":1,"852":1,"859":1,"1007":1,"1014":1,"1048":1,"1073":1,"1074":1,"1079":1,"1081":1,"1106":1,"1255":2,"1257":1,"1264":2,"1265":1,"1266":1,"1268":1,"1269":2,"1270":2,"1275":1,"1277":1,"1278":1,"1279":1,"1280":2,"1281":2,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1393":1,"2438":1,"2511":1,"2794":1}}],["gexec",{"2":{"2758":1}}],["geometric",{"2":{"2540":1}}],["geolocation",{"2":{"1792":1,"2021":1,"2632":1}}],["geolocation=",{"2":{"1792":2,"2021":2,"2029":1,"2632":2}}],["george",{"2":{"913":1,"914":2,"916":3,"917":2,"918":3,"919":2,"2586":3}}],["gen0",{"2":{"2398":1}}],["genuine",{"2":{"1406":1,"2384":1}}],["genuinely",{"2":{"834":1,"841":1,"852":1,"871":1,"873":1,"875":1,"876":1,"1327":1,"1404":1,"1856":1,"2455":1,"2462":1,"2497":1,"2868":1}}],["generous",{"2":{"1163":1,"1400":1}}],["generic",{"2":{"1111":1,"2359":1,"2492":1}}],["generalchat",{"2":{"1326":1}}],["generally",{"2":{"851":1,"1401":1}}],["general",{"0":{"159":1,"1224":1,"1470":1,"1554":1,"1874":1,"2074":1,"2124":1,"2708":1},"1":{"2709":1,"2710":1,"2711":1,"2712":1,"2713":1,"2714":1},"2":{"646":1,"918":1,"963":1,"1098":1,"1152":1,"1255":1,"1326":1,"1404":1,"1485":1,"1584":1,"1624":1,"1749":1,"1761":1,"1792":4,"1801":1,"1835":1,"1914":1,"1933":1,"2077":1,"2082":1,"2135":1,"2652":1}}],["generation",{"0":{"722":1,"961":1,"1095":1,"1200":1,"1412":1,"1552":1,"1559":1,"1754":1,"2247":1,"2389":1,"2519":1,"2590":1},"1":{"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1},"2":{"164":2,"681":1,"718":3,"720":1,"722":1,"725":2,"726":1,"727":2,"792":1,"834":1,"835":1,"868":3,"869":1,"908":1,"909":1,"912":1,"918":1,"951":1,"954":1,"972":1,"998":1,"1005":1,"1009":1,"1027":1,"1037":2,"1086":3,"1088":1,"1094":4,"1095":3,"1097":2,"1121":1,"1127":2,"1181":1,"1355":1,"1361":1,"1386":1,"1394":1,"1403":1,"1406":2,"1408":1,"1412":1,"1416":1,"1489":1,"1554":1,"1559":1,"1584":2,"1753":2,"1759":1,"1761":2,"1787":1,"1789":4,"1792":7,"1794":1,"1796":4,"1835":1,"1836":1,"1865":2,"1898":3,"1912":1,"1914":1,"2013":1,"2081":2,"2157":1,"2164":1,"2165":1,"2169":2,"2222":1,"2254":4,"2265":1,"2267":1,"2278":1,"2364":1,"2527":1,"2543":1,"2554":1,"2569":1,"2654":1,"2709":1,"2772":2,"2826":2,"2838":1,"2857":1,"2862":1}}],["generating",{"0":{"57":1,"58":1,"1051":1,"2360":1,"2694":1},"2":{"1214":1,"1552":1,"1751":1,"1792":4,"1896":1,"1998":1,"2356":1,"2641":2}}],["generatereport",{"2":{"429":1,"2310":1}}],["generates",{"2":{"429":1,"615":1,"723":1,"784":1,"894":1,"961":1,"995":1,"1024":1,"1063":1,"1067":1,"1097":2,"1127":1,"1210":1,"1317":1,"1342":1,"1361":1,"1366":1,"1792":1,"2207":1,"2277":1,"2310":1,"2313":1,"2339":1,"2351":1,"2357":1,"2590":1,"2641":1,"2656":1,"2682":1,"2775":1,"2830":1}}],["generated",{"0":{"381":1,"894":1,"995":1,"1024":1,"1063":1,"1317":1,"1342":1,"1361":1,"1410":1,"1566":1,"2489":1,"2520":1},"1":{"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1},"2":{"56":1,"163":1,"245":1,"351":1,"409":1,"429":2,"436":1,"565":1,"695":1,"720":3,"724":2,"764":1,"774":1,"833":1,"834":1,"835":1,"836":1,"837":2,"867":1,"868":1,"869":1,"871":1,"873":1,"874":1,"875":1,"876":2,"877":1,"878":1,"880":1,"894":1,"911":1,"913":3,"914":2,"915":2,"917":1,"918":1,"920":2,"924":1,"938":2,"961":1,"968":1,"975":1,"976":1,"977":2,"995":1,"1002":1,"1004":1,"1005":1,"1006":1,"1008":1,"1009":1,"1020":1,"1024":1,"1027":3,"1037":1,"1042":1,"1043":1,"1046":1,"1050":1,"1051":1,"1096":2,"1121":1,"1126":1,"1200":1,"1213":1,"1254":1,"1304":1,"1307":2,"1317":2,"1318":1,"1321":1,"1322":1,"1326":1,"1342":1,"1350":1,"1352":1,"1355":2,"1366":5,"1367":1,"1368":1,"1381":1,"1382":2,"1384":1,"1385":6,"1386":2,"1390":1,"1391":1,"1401":1,"1403":1,"1405":1,"1409":3,"1410":1,"1412":1,"1414":2,"1416":3,"1417":1,"1418":1,"1419":1,"1420":3,"1421":2,"1422":1,"1431":1,"1436":1,"1554":1,"1558":1,"1559":2,"1562":1,"1565":1,"1566":1,"1567":1,"1569":3,"1571":1,"1574":1,"1576":1,"1581":1,"1582":1,"1753":1,"1759":2,"1792":12,"1841":1,"1898":1,"1907":1,"1908":1,"1911":1,"1912":2,"2098":1,"2164":1,"2165":1,"2191":1,"2197":1,"2222":1,"2247":4,"2254":1,"2257":1,"2258":1,"2265":1,"2273":1,"2278":1,"2310":2,"2313":1,"2333":1,"2340":1,"2359":2,"2360":1,"2388":1,"2389":2,"2391":1,"2484":1,"2489":4,"2515":2,"2518":1,"2519":1,"2520":3,"2521":1,"2523":1,"2527":1,"2533":1,"2534":1,"2554":1,"2555":2,"2562":1,"2566":2,"2590":2,"2600":1,"2648":1,"2655":1,"2672":1,"2723":1,"2830":2,"2836":4,"2848":1,"2862":1,"2867":2,"2871":1}}],["generate",{"2":{"38":2,"57":2,"58":2,"61":2,"62":1,"137":2,"148":1,"325":1,"415":6,"423":5,"429":1,"656":2,"679":1,"757":1,"957":1,"975":2,"1037":1,"1049":2,"1051":1,"1096":1,"1105":2,"1107":1,"1125":1,"1161":2,"1179":1,"1195":1,"1207":1,"1214":2,"1255":1,"1338":1,"1358":1,"1366":3,"1385":5,"1386":6,"1388":1,"1394":1,"1405":2,"1754":3,"1758":1,"1792":4,"1858":2,"2127":1,"2228":1,"2251":1,"2303":5,"2304":3,"2310":1,"2313":2,"2317":1,"2357":2,"2611":1,"2700":1,"2775":1,"2813":3,"2826":1}}],["generators",{"2":{"1569":1,"1759":1,"1912":1,"2222":3,"2518":1,"2520":2,"2523":1}}],["generator",{"0":{"1406":1},"1":{"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"1":1,"74":1,"429":1,"868":1,"871":1,"875":1,"1037":1,"1406":1,"1410":2,"1413":1,"1422":2,"1567":1,"2273":1,"2310":1,"2313":1,"2356":1,"2357":1,"2358":1,"2359":1,"2391":1,"2484":1,"2518":1,"2519":1,"2523":1,"2611":1}}],["gen",{"2":{"308":4,"592":1,"813":1,"928":1,"1214":2,"1232":2,"1234":1,"1307":2,"2147":2,"2177":3,"2575":1}}],["get|set",{"2":{"2435":1}}],["getdatatypename",{"2":{"2324":1,"2370":1}}],["getdataurl",{"2":{"723":1,"961":4,"1413":1}}],["getname",{"2":{"2324":1}}],["getorcreateasync",{"0":{"2461":1},"2":{"2224":1,"2461":1,"2462":1,"2463":1}}],["getoriginalfilename",{"2":{"1366":2}}],["get`",{"2":{"1792":1}}],["getitem",{"2":{"1582":1}}],["getid",{"2":{"1366":1}}],["getinputstream",{"2":{"1366":2}}],["getconfigstr",{"2":{"2498":1,"2648":1}}],["getconfigint",{"2":{"2498":1}}],["getconfigbool",{"2":{"2497":1,"2498":1}}],["getcontenttype",{"2":{"1366":1}}],["getcolumnschema",{"2":{"2324":1}}],["getclientipaddress",{"2":{"1957":1,"2379":1}}],["getcurrentuserid",{"2":{"1366":1}}],["getuserid",{"2":{"1366":1}}],["getusers",{"2":{"995":1,"996":2,"1061":1,"1063":1,"1386":1,"1408":1,"1409":4}}],["getextension",{"2":{"1366":2}}],["getelementbyid",{"2":{"996":1,"1361":1}}],["getallelements",{"2":{"1792":1}}],["getallelementscommand",{"2":{"1054":1,"1650":1,"1651":1,"1655":2,"1663":1,"1792":1,"2297":2,"2551":1}}],["getauthtoken",{"2":{"1320":1}}],["getmessages",{"2":{"1318":1,"2836":3}}],["getting",{"0":{"1252":1,"2162":1},"2":{"1064":1,"1390":1,"1403":1,"1426":1,"1701":1,"2377":1,"2633":1}}],["getter",{"2":{"856":1}}],["getproduct",{"2":{"1575":1}}],["getprices",{"2":{"1026":2}}],["getposts",{"2":{"995":1,"996":2,"1408":1,"1409":1}}],["getrates",{"2":{"1026":2}}],["getfinancialdashboard",{"2":{"1024":2,"1026":2}}],["getspan",{"2":{"2400":1}}],["getsize",{"2":{"1366":1}}],["gets",{"0":{"1210":1,"1566":1},"1":{"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1},"2":{"479":1,"695":1,"838":1,"841":2,"856":2,"860":1,"861":2,"868":1,"871":1,"877":1,"919":1,"951":1,"961":1,"1039":1,"1070":1,"1073":1,"1074":1,"1079":1,"1081":1,"1101":1,"1107":2,"1139":1,"1203":1,"1214":1,"1337":1,"1378":1,"1404":1,"1406":1,"1423":1,"1435":1,"1441":1,"1442":1,"1460":1,"1705":3,"1792":6,"1825":1,"1955":2,"2342":1,"2375":1,"2379":2,"2388":1,"2484":1,"2526":1,"2527":1,"2633":3,"2732":1,"2739":1,"2829":1,"2860":1,"2862":1,"2868":1}}],["get",{"0":{"206":1,"207":1,"979":1,"980":1,"1380":1,"2723":1,"2725":1,"2733":1},"2":{"1":1,"7":5,"8":1,"9":2,"16":5,"19":3,"20":3,"22":2,"23":1,"37":1,"38":4,"40":4,"48":2,"49":1,"60":2,"61":5,"62":4,"83":1,"84":2,"85":1,"86":1,"94":2,"95":2,"96":2,"97":2,"104":5,"105":2,"106":1,"107":2,"115":5,"116":3,"117":3,"118":2,"119":3,"128":2,"129":1,"136":2,"137":1,"147":2,"148":1,"149":1,"175":2,"180":1,"186":7,"187":4,"196":1,"204":1,"206":2,"207":2,"209":1,"211":2,"212":2,"213":3,"214":7,"215":2,"243":1,"245":4,"247":5,"249":4,"250":2,"251":2,"254":4,"255":4,"256":4,"258":2,"263":9,"264":6,"265":1,"277":3,"278":6,"307":1,"314":2,"320":3,"322":7,"323":1,"332":3,"333":3,"334":3,"335":3,"342":1,"343":1,"344":1,"352":2,"353":2,"354":1,"372":6,"374":7,"376":2,"380":1,"381":1,"386":2,"390":1,"392":2,"394":1,"401":6,"402":2,"403":2,"405":4,"406":4,"408":9,"414":1,"415":3,"418":2,"419":3,"420":2,"421":2,"423":2,"426":1,"427":1,"429":1,"436":10,"438":5,"439":3,"449":3,"451":2,"452":5,"453":1,"454":3,"462":2,"463":2,"464":3,"466":8,"467":8,"468":3,"469":5,"476":1,"478":1,"479":1,"487":5,"488":3,"489":1,"490":1,"491":1,"492":1,"493":2,"501":1,"502":1,"518":1,"520":3,"521":4,"527":4,"531":3,"533":1,"539":5,"540":3,"541":1,"542":3,"543":1,"544":2,"545":2,"553":2,"554":1,"555":2,"562":1,"563":1,"566":1,"573":1,"577":1,"584":1,"585":2,"601":1,"602":1,"603":1,"604":1,"611":3,"612":3,"613":3,"664":1,"665":1,"677":5,"678":2,"679":3,"686":1,"689":2,"710":4,"722":5,"723":4,"724":4,"732":1,"733":4,"734":2,"735":2,"736":2,"797":4,"798":2,"799":2,"800":1,"811":5,"835":5,"838":2,"841":1,"851":1,"852":1,"859":1,"861":1,"868":1,"872":1,"879":1,"887":1,"903":1,"914":11,"915":1,"916":13,"917":5,"918":5,"920":1,"926":1,"936":1,"949":2,"956":1,"957":2,"959":2,"960":2,"961":3,"964":2,"965":2,"976":4,"979":8,"980":6,"981":1,"982":1,"985":1,"986":2,"988":7,"989":3,"990":9,"991":4,"995":8,"1005":1,"1009":1,"1017":1,"1019":3,"1021":3,"1023":2,"1026":3,"1032":1,"1033":2,"1034":3,"1038":2,"1042":1,"1054":2,"1057":4,"1061":2,"1065":1,"1067":3,"1068":1,"1069":1,"1073":3,"1074":3,"1076":4,"1077":1,"1080":2,"1081":1,"1082":1,"1086":1,"1104":1,"1105":6,"1107":2,"1113":3,"1121":2,"1135":7,"1138":9,"1139":4,"1141":5,"1142":8,"1143":2,"1148":5,"1149":3,"1150":6,"1154":2,"1158":1,"1163":2,"1176":5,"1179":6,"1189":1,"1193":2,"1196":1,"1204":1,"1205":1,"1206":1,"1220":1,"1221":1,"1222":2,"1226":3,"1232":1,"1255":5,"1259":1,"1281":1,"1310":3,"1331":1,"1335":1,"1337":1,"1345":2,"1347":4,"1348":1,"1362":4,"1363":1,"1364":1,"1365":1,"1366":2,"1368":7,"1369":3,"1370":1,"1371":1,"1373":2,"1374":3,"1375":1,"1376":1,"1378":2,"1381":1,"1386":19,"1387":2,"1390":2,"1393":5,"1398":21,"1399":2,"1400":1,"1401":1,"1403":1,"1405":5,"1408":9,"1409":1,"1410":1,"1412":5,"1413":3,"1414":4,"1416":2,"1419":1,"1426":1,"1427":2,"1430":2,"1431":1,"1441":1,"1442":1,"1518":5,"1522":1,"1529":7,"1531":4,"1532":4,"1533":2,"1547":4,"1567":4,"1568":1,"1569":1,"1575":1,"1599":1,"1632":5,"1642":1,"1646":1,"1650":1,"1651":1,"1655":2,"1663":1,"1664":4,"1726":1,"1727":3,"1729":1,"1730":3,"1731":2,"1733":4,"1736":4,"1738":4,"1740":3,"1742":2,"1743":3,"1744":2,"1745":1,"1747":1,"1792":11,"1823":1,"1824":1,"1842":2,"1846":1,"1847":5,"1876":3,"1920":5,"1921":3,"1924":3,"1926":1,"1929":1,"1930":6,"1973":1,"1974":1,"2004":1,"2006":1,"2009":1,"2010":4,"2012":4,"2056":1,"2076":5,"2078":3,"2079":5,"2106":1,"2181":2,"2183":1,"2184":1,"2187":4,"2193":5,"2194":4,"2196":2,"2197":4,"2202":4,"2204":1,"2205":6,"2206":2,"2207":2,"2214":2,"2215":4,"2217":2,"2222":2,"2247":2,"2251":1,"2255":6,"2256":2,"2257":2,"2264":8,"2265":11,"2266":1,"2277":14,"2283":5,"2286":1,"2288":3,"2290":2,"2293":7,"2294":4,"2297":1,"2302":1,"2303":2,"2304":2,"2305":2,"2306":1,"2310":1,"2313":1,"2314":4,"2319":5,"2321":2,"2327":2,"2328":1,"2329":1,"2333":2,"2337":1,"2339":4,"2340":1,"2344":9,"2346":4,"2347":2,"2358":1,"2366":6,"2379":1,"2380":2,"2381":1,"2389":1,"2391":1,"2429":1,"2432":2,"2438":2,"2455":2,"2459":1,"2463":1,"2481":3,"2483":1,"2502":3,"2506":1,"2511":1,"2513":2,"2519":2,"2523":2,"2526":4,"2529":1,"2531":2,"2535":2,"2536":1,"2537":4,"2538":2,"2539":1,"2540":1,"2549":8,"2551":2,"2566":1,"2580":6,"2581":3,"2586":1,"2587":3,"2588":1,"2589":1,"2591":2,"2607":1,"2622":1,"2651":2,"2652":2,"2653":4,"2656":2,"2665":9,"2666":1,"2674":1,"2721":1,"2723":1,"2726":1,"2731":2,"2733":1,"2739":2,"2760":1,"2762":5,"2764":5,"2765":2,"2766":5,"2767":4,"2768":4,"2772":2,"2774":4,"2775":7,"2797":5,"2809":3,"2810":1,"2811":1,"2812":1,"2813":3,"2821":2,"2822":1,"2824":5,"2825":3,"2830":2,"2834":3,"2836":3,"2840":3,"2842":3,"2843":1,"2845":3,"2846":3,"2850":1,"2860":4,"2861":2,"2865":1,"2868":2,"2869":4,"2878":2,"2881":1}}],["lj",{"2":{"1792":1,"1800":1,"1809":2,"1810":1}}],["lgjsqahngjf9dn0w+2vaf+edgxss14e9ag+dezupgdsftjj8duphu6cfromb6uqp",{"2":{"1189":1,"1195":2,"1196":1,"1373":1}}],["l2",{"2":{"1147":1,"1511":1,"1515":2,"1792":4,"2274":2}}],["l1",{"2":{"1147":1,"1511":1,"1515":1,"1792":3,"2274":1}}],["luis",{"2":{"1442":2}}],["luckily",{"2":{"1402":1}}],["lucky",{"2":{"1402":1,"1403":1}}],["lumps",{"2":{"1162":1}}],["lua",{"2":{"1106":1}}],["lunch",{"2":{"865":1}}],["l7",{"2":{"927":2}}],["l",{"2":{"922":2,"2784":1}}],["lying",{"2":{"844":1}}],["lng",{"2":{"333":2}}],["lr",{"2":{"306":1,"650":1,"663":1,"833":3,"949":1,"1086":1,"1087":1,"1088":3,"1211":1,"1220":1,"1221":1,"1222":1,"1868":1}}],["llm",{"2":{"871":1,"1335":1,"1419":1}}],["ll",{"2":{"175":1,"832":1,"841":1,"992":1,"1061":1,"1127":1,"1220":1,"1400":1,"1443":1,"1870":1,"2270":1,"2818":1}}],["lts",{"0":{"2385":1},"2":{"1071":1,"2385":2}}],["lt",{"0":{"1313":1},"2":{"63":1,"108":1,"214":1,"215":1,"319":3,"326":4,"355":4,"364":2,"395":1,"423":2,"429":1,"436":2,"454":1,"527":1,"528":1,"534":1,"615":2,"646":1,"650":3,"666":1,"669":2,"845":1,"849":1,"852":1,"868":1,"1040":4,"1102":1,"1107":2,"1189":1,"1358":1,"1367":1,"1408":1,"1410":1,"1412":2,"1413":3,"1414":1,"1416":1,"1424":1,"1428":2,"1460":1,"1520":1,"1572":2,"1581":1,"1738":1,"1743":1,"1824":1,"1913":1,"2018":4,"2040":1,"2075":1,"2077":1,"2109":1,"2225":1,"2270":1,"2310":1,"2313":2,"2337":2,"2339":2,"2375":1,"2380":2,"2399":1,"2400":2,"2402":1,"2412":1,"2419":1,"2422":1,"2423":1,"2427":1,"2432":3,"2435":1,"2436":1,"2437":1,"2443":1,"2462":2,"2481":5,"2482":1,"2487":1,"2490":1,"2502":2,"2535":5,"2604":1,"2614":1,"2794":5,"2823":1,"2830":2,"2835":1}}],["leverage",{"2":{"2270":1,"2580":1}}],["level1",{"2":{"2611":1}}],["level2",{"2":{"2611":1}}],["level3",{"2":{"2611":1}}],["level4",{"2":{"2611":1}}],["leveled",{"2":{"2536":1,"2537":1}}],["levels",{"0":{"1801":1,"1802":1,"1938":1},"2":{"656":1,"864":1,"919":1,"920":1,"1057":1,"1097":1,"1151":1,"1177":1,"1254":1,"1255":1,"1791":1,"1798":1,"1801":1,"1802":1,"2219":1,"2250":1,"2537":1,"2544":1,"2607":1,"2611":1,"2628":1,"2680":1,"2689":1,"2692":1,"2794":2,"2800":2,"2869":1}}],["level>",{"2":{"628":2,"651":1}}],["level",{"0":{"349":1,"627":1,"631":1,"632":1,"633":1,"655":1,"656":1,"660":1,"661":1,"662":1,"921":1,"981":1,"1003":1,"1268":1,"1858":1,"2115":1,"2250":1,"2251":1,"2364":1,"2426":1,"2431":1,"2702":1,"2832":2},"1":{"628":1,"629":1,"630":1,"631":1,"632":1,"633":1,"634":1,"635":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"982":1,"983":1,"984":1,"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2703":1,"2704":1},"2":{"64":1,"109":1,"111":1,"162":3,"182":1,"233":2,"356":1,"627":2,"628":2,"629":1,"631":1,"632":1,"633":1,"648":2,"650":1,"653":3,"655":1,"656":5,"658":1,"662":3,"668":2,"669":1,"671":2,"696":1,"704":1,"705":1,"716":1,"841":1,"871":2,"872":1,"905":1,"907":1,"909":1,"919":2,"921":1,"922":1,"975":1,"986":1,"993":1,"1037":1,"1065":1,"1067":1,"1071":1,"1079":1,"1097":1,"1098":2,"1100":5,"1101":2,"1104":1,"1114":2,"1122":1,"1125":1,"1136":2,"1185":2,"1190":1,"1193":3,"1285":1,"1354":1,"1378":1,"1385":1,"1394":1,"1403":1,"1520":1,"1568":1,"1609":1,"1649":1,"1658":1,"1664":1,"1787":1,"1792":23,"1794":1,"1800":1,"1801":1,"1802":2,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1,"1809":2,"1810":1,"1820":2,"1844":1,"1851":1,"1857":1,"1858":3,"1860":2,"1861":1,"1937":1,"1938":1,"1961":2,"2002":1,"2007":1,"2092":1,"2106":1,"2108":1,"2117":1,"2154":1,"2164":2,"2165":1,"2184":1,"2208":1,"2250":1,"2251":2,"2252":10,"2258":1,"2291":1,"2328":1,"2364":2,"2366":1,"2381":1,"2382":1,"2384":3,"2391":1,"2392":1,"2419":1,"2427":1,"2428":1,"2430":1,"2490":1,"2506":1,"2533":1,"2535":2,"2537":1,"2542":1,"2544":2,"2586":1,"2628":2,"2629":2,"2661":1,"2689":1,"2699":1,"2702":1,"2721":1,"2749":1,"2750":1,"2751":1,"2794":2,"2795":1,"2798":1,"2802":2,"2803":5,"2824":1,"2825":2,"2827":3,"2828":3,"2829":1,"2830":2,"2832":10,"2834":1,"2835":3,"2838":2,"2858":1,"2873":1,"2878":1,"2879":1}}],["lexicon",{"2":{"1335":1}}],["legitimate",{"2":{"1185":1,"1329":1,"1605":1,"2409":1,"2497":1,"2688":1}}],["legitimately",{"2":{"852":1,"2105":1}}],["legal",{"2":{"1079":1,"2492":1,"2540":1,"2868":1,"2869":1}}],["legacy",{"0":{"2376":1},"2":{"1071":1,"1464":2,"1792":2,"1856":2,"2376":2,"2455":1}}],["len",{"2":{"928":4,"929":4}}],["lengthcomputable",{"2":{"894":1,"1366":1,"1410":1}}],["length",{"0":{"1925":1,"2145":1},"2":{"308":1,"894":2,"928":3,"929":3,"930":2,"995":2,"1049":1,"1332":1,"1335":8,"1338":10,"1339":3,"1340":1,"1366":4,"1382":1,"1386":1,"1408":1,"1410":2,"1416":1,"1431":2,"1511":2,"1567":1,"1589":1,"1792":11,"1916":2,"1917":4,"1925":1,"1931":2,"2098":2,"2145":1,"2265":1,"2359":1,"2399":1,"2492":2,"2495":1,"2521":1,"2531":1,"2534":2,"2535":1,"2549":2,"2566":1,"2575":2,"2626":1,"2814":3,"2823":1,"2869":1,"2871":2}}],["leo",{"2":{"913":1}}],["lee",{"2":{"913":1}}],["lesson",{"2":{"861":1}}],["less",{"0":{"941":1},"2":{"845":1,"860":1,"871":2,"911":1,"1027":1,"1123":1,"1270":1,"1322":1,"1366":1,"1401":3,"1409":1,"1440":1,"1708":1,"1792":3,"1917":1,"1925":1,"2422":1,"2451":1,"2633":2}}],["left",{"0":{"2453":1},"2":{"212":1,"388":1,"849":1,"852":1,"860":1,"914":1,"916":2,"918":1,"1043":2,"1045":1,"1068":1,"1073":1,"1074":1,"1079":1,"1197":1,"1338":1,"1339":1,"1375":1,"1399":1,"1404":1,"1504":2,"1605":1,"1759":1,"1912":1,"2493":1,"2494":1,"2497":2,"2498":1,"2518":1,"2688":1,"2868":1}}],["leftovers",{"2":{"2758":1}}],["leftover",{"0":{"2758":1},"2":{"175":1,"2881":1}}],["leaf",{"2":{"1824":1}}],["lead",{"2":{"1262":1,"1263":1,"2463":1,"2466":7}}],["leader",{"2":{"1130":1,"1264":1}}],["leads",{"0":{"1263":1},"2":{"1091":1,"1262":1,"1280":1}}],["leading",{"2":{"659":1,"694":1,"704":1,"709":1,"714":1,"851":1,"1042":1,"1792":1,"2378":1,"2505":1,"2529":1,"2531":1,"2532":1,"2533":1,"2534":1,"2870":1}}],["learned",{"2":{"1401":1,"1404":1}}],["learning",{"2":{"977":1,"980":1,"990":1,"1084":1,"1403":1}}],["learn",{"2":{"911":1,"1128":1,"1401":1,"1405":1,"1443":1,"1792":14,"2257":4,"2633":1,"2634":1}}],["learns",{"2":{"864":1}}],["leaner",{"2":{"869":1}}],["lean",{"2":{"838":1,"1326":1}}],["leaked",{"2":{"2222":1,"2519":1,"2532":1}}],["leaking",{"2":{"1055":1,"1079":1,"2482":1}}],["leaks",{"2":{"715":1,"1070":1,"2040":1}}],["leak",{"2":{"319":1,"374":1,"1070":1,"1209":1,"1441":1,"1458":1,"1792":1,"2375":1,"2402":1,"2404":1,"2481":1,"2881":1}}],["leave",{"2":{"317":1,"347":1,"840":1,"1042":1,"1792":1,"1851":1,"1856":1,"2040":1,"2382":1,"2432":1,"2477":1,"2860":1}}],["leaves",{"2":{"215":1,"527":1,"860":1,"1033":1,"1210":1,"1738":1,"2103":1,"2283":1,"2426":1,"2531":1,"2802":1}}],["leaving",{"2":{"174":1,"843":1,"2222":1,"2362":1,"2527":1,"2577":1}}],["least",{"0":{"922":1,"1185":1,"2876":1},"2":{"21":1,"175":1,"179":1,"685":1,"817":1,"843":1,"845":3,"921":1,"922":1,"932":1,"1009":1,"1037":1,"1053":1,"1062":1,"1065":1,"1185":1,"1386":1,"1401":1,"1405":2,"1441":1,"1443":1,"1453":1,"1454":1,"1480":1,"1522":1,"1575":1,"1640":1,"1792":3,"1801":1,"1861":1,"2094":1,"2097":1,"2107":1,"2113":2,"2140":1,"2145":1,"2146":1,"2148":1,"2164":1,"2175":1,"2199":1,"2535":2,"2537":3,"2554":1,"2575":1,"2737":1,"2755":1,"2809":1,"2879":1}}],["letting",{"2":{"1326":1,"1385":1,"2810":1}}],["letters",{"2":{"382":1,"2334":1}}],["letter",{"2":{"382":1,"1026":1,"2334":1}}],["lets",{"2":{"298":1,"386":1,"423":1,"691":1,"876":1,"912":1,"1010":1,"1055":1,"1137":1,"1209":1,"1813":1,"2176":1,"2185":1,"2474":1,"2482":1,"2693":1,"2767":1,"2811":1}}],["let",{"2":{"1":1,"309":1,"319":1,"679":1,"838":1,"840":1,"841":4,"843":1,"848":1,"851":1,"852":2,"854":1,"860":1,"876":1,"894":1,"918":1,"919":1,"974":1,"992":1,"1026":1,"1073":2,"1079":1,"1123":1,"1150":2,"1220":1,"1253":1,"1281":1,"1317":1,"1318":1,"1366":1,"1381":1,"1382":1,"1384":2,"1385":2,"1386":4,"1387":1,"1392":1,"1394":1,"1397":1,"1398":1,"1399":1,"1402":2,"1404":1,"1410":1,"1416":1,"1419":2,"1431":1,"1435":1,"1519":2,"1870":1,"2177":1,"2178":1,"2183":1,"2247":1,"2380":1,"2438":1,"2532":1,"2759":1,"2769":1,"2801":1,"2804":1,"2823":1,"2824":2,"2825":1,"2873":1}}],["loki",{"2":{"2804":1}}],["london",{"2":{"1733":1,"2264":1}}],["long",{"0":{"96":1,"137":1,"272":1,"2452":1},"2":{"106":1,"269":1,"277":1,"370":1,"574":1,"658":4,"841":1,"847":1,"868":1,"930":14,"944":1,"1014":1,"1053":1,"1062":1,"1066":1,"1067":1,"1076":1,"1121":1,"1139":1,"1143":1,"1150":2,"1179":1,"1227":1,"1325":1,"1326":1,"1383":1,"1398":1,"1401":1,"1406":1,"1453":1,"1458":1,"1511":1,"1516":2,"1529":3,"1645":1,"1678":1,"1792":3,"1823":1,"1877":1,"1925":1,"2175":1,"2211":1,"2222":1,"2265":5,"2332":1,"2375":1,"2427":1,"2450":1,"2459":1,"2554":1,"2615":1,"2737":1,"2833":1,"2835":1}}],["longer",{"0":{"2352":1,"2384":1,"2492":1,"2517":1},"2":{"74":1,"650":1,"686":1,"871":1,"872":1,"920":1,"975":1,"1069":1,"1096":1,"1419":1,"1511":3,"1620":1,"1792":5,"1823":1,"1917":1,"1925":1,"2223":1,"2225":2,"2226":1,"2265":2,"2389":4,"2495":1,"2517":1,"2518":1,"2539":1,"2679":1,"2812":1}}],["loving",{"2":{"1401":1}}],["love",{"2":{"843":1,"1404":1}}],["lousy",{"2":{"1385":1}}],["loud",{"2":{"704":1,"2110":1,"2380":1,"2530":1}}],["loudly",{"2":{"701":1,"1076":2,"2109":1,"2530":1}}],["los",{"2":{"2453":1,"2456":1}}],["loss",{"2":{"2353":1,"2378":1}}],["lose",{"2":{"1325":1,"2532":1}}],["loses",{"2":{"1270":1}}],["losing",{"2":{"868":1,"1378":1,"1391":1}}],["lost",{"2":{"188":1,"854":1,"1054":1,"1101":1,"1152":1,"1624":1,"1653":1,"1664":1,"1792":1,"2296":1,"2297":2,"2402":1,"2495":1}}],["loadeventstart",{"2":{"1792":2}}],["loadeventend",{"2":{"1792":2}}],["loaded",{"2":{"317":1,"347":1,"894":4,"1361":2,"1366":2,"1410":4,"1608":1,"1792":1,"2195":1,"2272":1,"2432":1,"2487":1}}],["loadmessagehistory",{"2":{"1320":1}}],["loadposts",{"2":{"996":2}}],["loadusers",{"2":{"996":2,"1409":1}}],["loading",{"0":{"1608":1},"2":{"871":1,"907":1,"911":1,"996":2,"1604":1,"1685":3,"1792":1,"2016":1,"2024":1,"2272":1,"2632":2,"2705":1}}],["loads",{"2":{"844":1,"909":1,"2684":1}}],["load",{"0":{"1175":1,"1715":1},"2":{"844":1,"849":1,"851":1,"852":1,"854":4,"855":1,"856":1,"861":1,"864":1,"865":1,"868":1,"948":1,"996":2,"1067":1,"1121":1,"1135":1,"1136":1,"1161":1,"1164":1,"1168":1,"1170":1,"1171":2,"1172":1,"1175":5,"1176":1,"1177":3,"1180":4,"1182":1,"1205":1,"1206":1,"1255":2,"1281":1,"1316":1,"1324":4,"1329":2,"1337":1,"1366":1,"1608":1,"1618":1,"1626":1,"1628":1,"1706":1,"1712":1,"1767":1,"1774":1,"1792":4,"2016":1,"2020":1,"2024":1,"2025":1,"2060":1,"2266":2,"2272":1,"2397":1,"2398":1,"2438":1,"2466":1,"2504":1,"2580":1,"2632":1,"2633":1,"2634":1,"2684":1,"2803":1,"2836":1}}],["lot",{"2":{"843":2,"845":2,"859":1,"920":1,"1386":1,"1393":1,"1401":1,"1404":1}}],["lo",{"2":{"750":3,"751":2,"752":2,"777":2,"922":2,"1355":1,"1362":1,"1363":1,"1412":3}}],["loc",{"2":{"869":13,"872":3,"873":5,"876":1,"1037":2,"1181":10,"1421":1}}],["locating",{"2":{"2002":1}}],["locations",{"2":{"1139":1,"1353":1,"2286":1}}],["location",{"2":{"333":3,"533":1,"961":1,"1413":1,"1651":1,"1653":1,"1792":9,"1846":1,"1906":1,"1988":1,"2261":1,"2297":1,"2328":1,"2781":1}}],["located",{"0":{"988":1},"2":{"1005":1,"1006":1,"1009":1,"1792":2,"2095":2,"2167":1,"2538":1,"2539":1,"2545":1,"2861":1}}],["locates",{"2":{"876":1}}],["local=false",{"2":{"1850":1,"2382":1}}],["local=true`",{"2":{"1792":1}}],["local=true",{"2":{"1070":1,"1102":1,"1850":1,"2382":1}}],["locally",{"2":{"1792":2,"1917":1,"1927":2,"2161":1,"2549":2,"2786":1,"2814":1}}],["localstorage",{"2":{"1582":1}}],["locality",{"2":{"873":1}}],["local",{"2":{"390":1,"452":7,"848":1,"1080":1,"1094":1,"1102":1,"1105":2,"1147":1,"1318":1,"1325":1,"1328":1,"1334":1,"1347":5,"1351":1,"1511":1,"1515":2,"1651":1,"1662":1,"1792":8,"1850":1,"1856":4,"2040":2,"2224":1,"2274":1,"2346":2,"2382":1,"2394":1,"2428":1,"2451":4,"2452":1,"2453":1,"2454":2,"2455":2,"2476":2,"2565":1,"2572":1,"2684":1,"2685":3,"2782":1,"2783":1,"2784":1,"2855":1}}],["localcacheexpiration",{"2":{"279":1,"2274":1,"2279":1}}],["localhost",{"2":{"38":1,"40":1,"61":1,"62":2,"121":1,"868":1,"970":1,"1146":1,"1147":1,"1199":4,"1202":1,"1207":3,"1225":1,"1320":1,"1338":1,"1340":1,"1510":1,"1514":1,"1515":1,"1616":1,"1711":1,"1775":1,"1792":10,"1800":1,"1807":2,"1875":1,"1984":2,"1986":1,"1987":1,"1988":2,"1989":1,"1995":2,"2116":1,"2117":1,"2118":2,"2274":1,"2699":1,"2701":1,"2702":1,"2703":2,"2717":1,"2808":1,"2814":1,"2823":2,"2824":3,"2825":1,"2874":1}}],["locks",{"2":{"1325":1,"2873":1}}],["lockstep",{"2":{"871":1}}],["lockout",{"2":{"1056":2,"1064":1}}],["locking",{"0":{"1045":1},"2":{"1325":2,"2498":1}}],["locked",{"2":{"310":1}}],["lock",{"0":{"1596":1},"2":{"310":1,"855":1,"876":2,"967":1,"1155":1,"1324":4,"1325":1,"1596":1,"1624":2,"1792":3,"2342":1,"2614":1,"2869":2}}],["lowest",{"2":{"1278":1,"2681":1}}],["lowercases",{"2":{"2861":1}}],["lowercase",{"2":{"653":1,"1697":1,"1792":2,"2098":1,"2528":1,"2530":1,"2534":1,"2864":1,"2871":1}}],["lowercased",{"2":{"74":1,"2518":1}}],["lower",{"0":{"2397":1},"1":{"2398":1},"2":{"88":1,"888":2,"1132":1,"1193":1,"1265":1,"1792":1,"1938":1,"2226":1,"2270":1,"2861":1}}],["low",{"0":{"1263":1},"2":{"88":1,"307":1,"873":1,"1042":1,"1044":4,"1084":1,"1145":1,"1228":1,"1263":1,"1275":1,"1280":1,"1385":1,"1878":1,"2177":1}}],["logo",{"0":{"2582":1}}],["logouthandler",{"2":{"2615":1}}],["logoutpath",{"2":{"1469":1,"1479":1,"1483":1,"1792":1}}],["logout",{"0":{"282":1,"284":1,"288":1,"289":1,"290":1,"291":1,"292":1,"935":1,"1371":1,"1479":1,"1481":1},"1":{"283":1,"284":1,"285":2,"286":2,"287":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"294":1,"295":1,"1480":1,"1481":1},"2":{"27":1,"224":1,"283":1,"284":1,"286":1,"288":2,"289":4,"290":4,"291":7,"292":5,"293":1,"316":1,"691":1,"922":1,"935":5,"938":1,"1061":1,"1064":1,"1098":1,"1318":1,"1371":3,"1460":4,"1465":1,"1466":1,"1467":1,"1468":1,"1479":2,"1483":1,"1484":1,"1507":1,"1550":1,"1699":1,"1700":1,"1792":5,"2107":1,"2170":1,"2186":4,"2189":1,"2323":1,"2375":4,"2481":1,"2529":1,"2537":1,"2581":1,"2856":1,"2865":1,"2879":1,"2881":1}}],["logeventlevel",{"2":{"2544":1}}],["logendpointcreatedinfo",{"2":{"2258":1}}],["loglevel",{"2":{"2405":1}}],["logannotationsetinfo",{"2":{"2258":1}}],["loguploadparameters",{"2":{"1792":1,"2123":1,"2124":1,"2132":1}}],["loguploadevent",{"2":{"1792":1,"2123":1,"2124":1,"2132":1}}],["logconnectionnoticeeventsmode",{"2":{"1792":1,"1836":1,"1844":1,"1863":2,"2701":1,"2802":1}}],["logconnectionnoticeevents",{"2":{"1792":1,"1836":1,"1844":1,"1863":2,"2701":1,"2802":1}}],["logcommandtext",{"0":{"2011":1,"2354":1},"2":{"1792":1,"2011":2,"2354":1}}],["logcommandparameters",{"2":{"1792":1,"1836":1,"1844":1,"1863":2,"2597":1,"2701":1,"2750":1,"2798":1}}],["logcommands",{"2":{"1609":1,"1792":2,"1802":1,"1836":1,"1844":2,"1863":2,"2011":1,"2354":1,"2597":1,"2659":1,"2701":1,"2750":2,"2795":1,"2798":2}}],["logcommand",{"2":{"1609":1,"2659":1}}],["loggers",{"2":{"2544":1}}],["loggername",{"0":{"2108":1},"2":{"1792":3,"1802":1,"2093":1,"2094":1,"2536":1,"2537":1,"2795":1}}],["logger",{"0":{"2544":1,"2752":1},"2":{"1792":3,"1806":1,"2221":1,"2258":3,"2525":1,"2544":5,"2752":1}}],["logged",{"0":{"2405":1},"2":{"310":1,"319":1,"383":1,"384":1,"587":1,"675":1,"704":1,"1218":1,"1221":1,"1232":1,"1792":4,"1875":1,"1957":1,"1961":1,"2007":2,"2011":2,"2157":1,"2179":1,"2226":1,"2258":1,"2328":2,"2337":1,"2354":2,"2364":1,"2379":1,"2384":4,"2392":1,"2405":1,"2481":1,"2491":1,"2492":1,"2495":1,"2536":1,"2540":1,"2543":1,"2558":1,"2622":1,"2722":1,"2802":1}}],["logging",{"0":{"1799":1,"1844":1,"2186":1,"2366":1,"2536":1,"2628":1,"2748":1,"2794":1,"2803":1,"2824":1,"2880":1},"1":{"1800":1,"1801":1,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1,"1808":1,"1809":1,"1810":1,"1811":1,"1812":1,"1845":1,"2749":1,"2750":1,"2751":1,"2752":1,"2795":1,"2796":1,"2797":1,"2798":1,"2799":1,"2800":1,"2801":1,"2802":1,"2803":1,"2804":1,"2805":1},"2":{"41":1,"596":4,"1056":1,"1061":1,"1064":1,"1110":1,"1244":1,"1253":1,"1254":2,"1318":1,"1351":1,"1386":1,"1403":1,"1601":2,"1680":2,"1704":1,"1718":2,"1783":1,"1791":1,"1792":9,"1798":1,"1799":2,"1801":1,"1802":1,"1803":1,"1804":1,"1805":2,"1807":1,"1810":1,"1811":1,"1863":1,"1946":2,"2071":2,"2104":1,"2114":1,"2170":1,"2208":1,"2219":2,"2235":1,"2258":4,"2261":1,"2267":1,"2364":1,"2384":1,"2392":1,"2405":1,"2406":1,"2535":1,"2597":2,"2614":1,"2622":3,"2628":1,"2629":1,"2672":1,"2701":2,"2750":1,"2794":3,"2802":2,"2803":1,"2805":2,"2824":2,"2825":2}}],["log",{"0":{"1801":1,"2361":1,"2363":1,"2364":1,"2544":1,"2629":1,"2750":1},"1":{"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1},"2":{"64":1,"109":1,"292":2,"310":1,"320":1,"382":2,"595":1,"868":1,"894":1,"934":1,"970":1,"1024":2,"1054":1,"1056":1,"1061":1,"1071":1,"1110":2,"1199":1,"1218":2,"1239":2,"1254":1,"1320":2,"1342":3,"1361":1,"1409":2,"1410":1,"1500":2,"1521":1,"1527":1,"1677":1,"1718":1,"1783":1,"1791":1,"1792":28,"1798":1,"1799":1,"1800":3,"1801":1,"1802":5,"1803":2,"1804":8,"1805":4,"1806":2,"1807":2,"1809":2,"1810":4,"1844":4,"1845":3,"1861":1,"2007":3,"2011":1,"2094":1,"2104":2,"2108":3,"2111":1,"2114":1,"2124":2,"2208":3,"2221":1,"2223":1,"2261":1,"2328":1,"2330":2,"2334":2,"2348":1,"2354":1,"2363":1,"2366":2,"2372":2,"2380":1,"2384":1,"2401":1,"2482":2,"2495":1,"2532":1,"2535":2,"2536":2,"2537":3,"2544":2,"2551":1,"2597":2,"2628":3,"2629":2,"2699":2,"2701":1,"2721":1,"2749":2,"2750":2,"2751":1,"2752":2,"2794":1,"2795":3,"2797":1,"2798":2,"2799":1,"2800":3,"2801":1,"2802":2,"2803":5,"2804":9,"2809":1,"2824":2,"2825":1,"2830":1,"2840":1,"2880":2}}],["logs",{"0":{"2384":1,"2802":1},"2":{"64":1,"190":1,"214":1,"236":1,"298":1,"316":1,"368":1,"388":1,"589":1,"595":1,"868":1,"1015":2,"1071":1,"1111":1,"1318":1,"1386":1,"1449":1,"1470":1,"1609":2,"1670":1,"1704":1,"1792":9,"1799":1,"1800":1,"1804":2,"1807":1,"1811":1,"1822":2,"1825":1,"1844":2,"2011":1,"2176":1,"2216":1,"2255":1,"2328":2,"2354":1,"2364":1,"2366":1,"2384":1,"2414":1,"2428":1,"2481":2,"2492":1,"2493":1,"2529":1,"2533":2,"2535":1,"2536":1,"2543":1,"2558":2,"2629":2,"2659":2,"2721":1,"2749":1,"2794":1,"2795":3,"2798":1,"2802":1,"2803":2,"2805":1,"2835":1,"2841":1,"2855":1,"2865":1,"2880":1}}],["logically",{"2":{"841":1,"848":1}}],["logical",{"0":{"1414":1},"2":{"663":1,"841":14,"845":3,"848":4,"1205":1,"1405":1,"1416":1}}],["logic",{"0":{"213":1,"1032":1,"1247":1,"1394":1,"1739":1,"2287":1},"1":{"1395":1,"1396":1,"1740":1,"1741":1,"1742":1,"2288":1,"2289":1,"2290":1},"2":{"41":1,"54":1,"66":1,"414":1,"832":1,"836":1,"838":1,"841":1,"849":1,"859":6,"860":2,"861":1,"868":1,"873":1,"876":1,"902":1,"911":1,"921":1,"945":2,"956":1,"974":4,"1005":1,"1011":1,"1035":1,"1037":1,"1049":1,"1052":1,"1067":1,"1086":1,"1096":3,"1104":1,"1105":1,"1106":2,"1108":2,"1115":1,"1121":2,"1126":1,"1127":1,"1135":1,"1179":1,"1180":1,"1181":6,"1184":1,"1205":1,"1209":1,"1211":1,"1244":1,"1247":1,"1281":1,"1303":1,"1320":1,"1343":1,"1350":1,"1351":1,"1377":1,"1378":2,"1385":2,"1394":2,"1396":4,"1403":2,"1405":2,"1617":1,"1684":1,"1825":1,"1868":1,"2230":1,"2300":1,"2320":1,"2329":1,"2423":1,"2438":1,"2481":1,"2773":1,"2855":1,"2858":1}}],["loginhandler",{"2":{"2615":1}}],["loginurl",{"2":{"1416":1,"1581":1,"2655":1}}],["loginpath",{"2":{"1226":1,"1469":1,"1479":1,"1483":1,"1792":5,"1876":1}}],["loginoptionspath",{"2":{"1226":1,"1792":1,"1876":1}}],["loginresult",{"2":{"1218":3}}],["logins",{"2":{"1060":1,"1458":1,"1792":1,"2375":1,"2468":1}}],["logincommand",{"2":{"1059":1,"1062":1,"1683":1,"1684":1,"1686":1,"1687":1,"1689":1,"1698":1,"1792":1,"2496":1}}],["login",{"0":{"296":1,"297":1,"312":1,"314":1,"364":1,"366":1,"593":1,"691":1,"934":1,"1055":1,"1060":1,"1222":1,"1308":1,"1371":1,"1455":1,"1471":1,"1479":1,"1480":1,"1686":1,"1689":1,"1872":1,"1959":1,"2176":1,"2180":1,"2259":1,"2471":1},"1":{"297":1,"298":1,"299":1,"300":1,"301":1,"302":1,"303":1,"304":1,"305":1,"306":1,"307":1,"308":1,"309":1,"310":1,"311":1,"312":1,"313":1,"314":1,"315":1,"316":1,"365":1,"366":1,"1056":1,"1480":1,"1481":1,"1687":1,"1688":1,"1689":1,"2177":1,"2178":1},"2":{"10":1,"11":1,"12":1,"26":1,"27":1,"32":1,"35":1,"43":2,"65":1,"224":1,"293":1,"294":1,"296":2,"297":4,"298":6,"299":1,"300":1,"301":4,"302":1,"304":2,"305":1,"306":2,"308":1,"309":3,"310":2,"312":6,"313":4,"315":2,"316":1,"364":2,"366":5,"368":1,"531":1,"593":1,"597":1,"691":1,"700":5,"701":1,"922":1,"926":1,"934":7,"937":1,"938":4,"1037":1,"1045":1,"1048":1,"1050":2,"1053":1,"1055":6,"1056":3,"1058":2,"1059":1,"1060":6,"1061":2,"1062":1,"1064":2,"1065":1,"1068":1,"1078":2,"1098":3,"1123":1,"1197":2,"1209":1,"1210":1,"1216":1,"1218":3,"1222":5,"1226":3,"1234":2,"1235":1,"1236":2,"1239":3,"1308":4,"1318":1,"1371":3,"1435":1,"1455":1,"1458":10,"1465":1,"1466":1,"1467":1,"1468":1,"1470":3,"1471":1,"1472":1,"1473":1,"1479":2,"1480":1,"1483":2,"1484":1,"1486":1,"1503":1,"1504":3,"1505":1,"1507":1,"1550":1,"1616":2,"1683":1,"1684":4,"1686":2,"1687":1,"1688":2,"1689":5,"1693":2,"1694":2,"1695":1,"1698":1,"1699":1,"1700":1,"1792":55,"1825":3,"1866":1,"1872":2,"1876":3,"1884":1,"1886":1,"1888":2,"1898":1,"1958":6,"1959":5,"2035":1,"2042":1,"2107":1,"2164":1,"2167":2,"2170":2,"2171":8,"2175":1,"2176":7,"2177":3,"2180":2,"2181":2,"2182":2,"2187":5,"2188":1,"2189":2,"2224":1,"2227":1,"2259":1,"2267":1,"2323":1,"2375":7,"2420":1,"2428":1,"2431":1,"2434":1,"2437":1,"2441":3,"2442":1,"2468":3,"2470":2,"2471":5,"2481":1,"2529":1,"2535":1,"2537":3,"2545":1,"2554":3,"2581":1,"2655":3,"2737":1,"2836":1,"2856":1,"2865":1,"2879":1,"2881":2}}],["loose",{"2":{"2428":1,"2456":1}}],["loosen",{"2":{"2020":1}}],["looming",{"2":{"1405":1}}],["loopback",{"2":{"2346":1,"2347":1}}],["looping",{"2":{"2157":1,"2543":1}}],["loops",{"2":{"1081":1,"1394":2,"1704":1,"2362":1,"2607":1}}],["loop",{"0":{"1418":1,"1442":1,"2857":1},"2":{"3":1,"860":4,"861":1,"871":1,"872":2,"874":1,"876":1,"928":2,"929":2,"996":1,"1021":2,"1044":1,"1073":1,"1076":1,"1080":1,"1081":2,"1133":1,"1376":2,"1382":1,"1386":1,"1393":1,"1401":1,"1418":1,"1419":1,"1421":1,"1442":1,"1443":1,"2159":1,"2289":1,"2339":1,"2372":1,"2402":1,"2403":2,"2504":5,"2532":1,"2537":1,"2857":1,"2878":1}}],["looking",{"2":{"948":1,"1064":1,"1082":1,"2191":1}}],["looked",{"2":{"534":1,"1664":1,"1923":1,"2284":1,"2291":1,"2395":1,"2509":1}}],["looks",{"0":{"2392":1},"2":{"388":1,"395":1,"856":1,"915":1,"916":2,"917":1,"968":1,"1037":1,"1416":1,"1417":1,"1566":1,"1567":1,"2226":1,"2247":1,"2429":1,"2823":1}}],["lookups",{"2":{"2614":1}}],["lookup",{"0":{"2621":1},"2":{"136":3,"277":1,"388":1,"452":1,"577":1,"1154":4,"1244":1,"1599":1,"1792":1,"1974":1,"2236":1,"2381":1,"2422":1,"2477":1,"2580":1,"2607":1,"2614":1,"2621":5,"2815":1}}],["look",{"2":{"1":1,"841":1,"845":1,"849":1,"851":1,"852":1,"857":1,"860":2,"876":1,"918":1,"919":1,"1098":1,"1234":1,"1385":1,"1386":2,"1399":1,"1406":1,"1439":1,"1442":1,"1792":1,"2823":1}}],["lazy",{"2":{"2462":1,"2482":1,"2502":1,"2614":1}}],["lazily",{"2":{"1522":1}}],["lack",{"2":{"2428":1}}],["lacking",{"2":{"1825":1}}],["lacks",{"2":{"25":1,"857":1,"876":1,"1674":1,"2423":1,"2755":1}}],["layout",{"0":{"2538":1},"2":{"2095":1,"2167":1,"2537":1,"2539":1,"2545":1,"2861":1}}],["layouts",{"2":{"1792":1,"2095":1}}],["layering",{"2":{"868":1}}],["layered",{"2":{"868":1,"869":1,"873":1,"1005":1,"1193":2,"2537":1}}],["layer",{"0":{"1336":1},"2":{"838":1,"841":1,"843":2,"849":1,"851":1,"860":2,"864":1,"865":1,"866":1,"867":2,"868":1,"869":2,"871":4,"873":3,"874":1,"875":1,"876":1,"946":1,"953":1,"973":1,"974":1,"984":1,"996":1,"1006":3,"1007":1,"1008":1,"1011":2,"1037":1,"1038":1,"1039":1,"1046":2,"1077":1,"1094":1,"1098":1,"1101":1,"1104":1,"1105":1,"1106":1,"1111":1,"1184":1,"1193":1,"1276":1,"1303":1,"1333":1,"1350":1,"1390":1,"1401":1,"1405":1,"1409":1,"1421":1,"2175":1,"2347":1,"2438":1,"2456":1,"2466":1,"2481":2,"2712":1}}],["layers",{"0":{"831":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1},"2":{"836":1,"851":2,"868":1,"871":1,"1006":1,"1009":2,"1136":1,"1276":1,"1328":1,"1382":1,"1405":1,"1418":1,"1422":1}}],["lax",{"2":{"1447":2,"1792":2,"2425":2,"2426":1,"2428":1,"2436":1,"2492":1}}],["laptop",{"0":{"1429":1},"2":{"1423":1,"2164":1}}],["laptops",{"2":{"1423":1}}],["lags",{"2":{"1271":1}}],["launched",{"2":{"1130":1}}],["laid",{"2":{"851":1}}],["land",{"2":{"1107":1,"1414":1,"2398":2,"2419":1,"2438":1}}],["lands",{"2":{"352":1,"2866":1,"2871":1}}],["lang=en",{"2":{"541":2}}],["languages",{"0":{"1970":1,"1971":1,"1972":1},"2":{"841":2,"1382":2,"1394":2,"1403":1,"1792":3,"1969":1,"1970":1,"1971":1}}],["language",{"0":{"1969":1},"1":{"1970":1,"1971":1,"1972":1},"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"37":2,"38":2,"39":2,"40":1,"48":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"104":1,"115":1,"116":1,"117":1,"119":1,"128":1,"136":1,"137":1,"157":1,"184":1,"186":1,"206":1,"207":1,"208":1,"209":1,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"263":2,"264":2,"288":1,"289":1,"290":1,"291":1,"292":1,"298":1,"308":2,"309":1,"310":2,"312":1,"313":1,"322":1,"332":1,"333":1,"334":1,"335":1,"351":1,"360":1,"361":1,"365":1,"366":1,"374":1,"386":1,"401":1,"405":1,"406":1,"408":2,"415":1,"423":1,"426":1,"427":1,"428":1,"436":1,"438":1,"439":1,"449":1,"451":2,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"592":1,"593":1,"594":1,"611":1,"646":1,"658":1,"664":2,"677":1,"679":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"750":1,"751":1,"752":1,"755":1,"756":1,"764":2,"765":1,"766":1,"774":2,"777":1,"797":1,"798":1,"799":1,"811":1,"812":1,"813":1,"814":1,"815":1,"835":1,"841":1,"848":2,"851":1,"860":3,"865":1,"871":2,"872":2,"873":1,"874":1,"876":2,"883":1,"884":1,"886":1,"888":1,"904":2,"914":2,"915":1,"916":3,"918":1,"928":1,"929":1,"934":1,"935":1,"936":1,"956":1,"979":1,"980":1,"982":1,"988":1,"990":1,"994":1,"1005":1,"1021":1,"1029":1,"1037":1,"1054":2,"1055":1,"1056":2,"1057":1,"1058":1,"1060":1,"1068":1,"1076":1,"1078":1,"1105":4,"1113":1,"1135":1,"1138":1,"1139":1,"1141":1,"1142":1,"1149":1,"1179":3,"1188":1,"1192":1,"1197":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1255":1,"1308":1,"1309":1,"1310":1,"1321":1,"1331":1,"1332":1,"1337":1,"1338":1,"1339":1,"1345":2,"1347":1,"1348":1,"1357":1,"1362":1,"1368":1,"1382":4,"1387":1,"1390":1,"1393":1,"1394":9,"1401":2,"1403":2,"1404":1,"1427":1,"1429":1,"1431":1,"1458":3,"1504":1,"1547":1,"1567":1,"1632":1,"1655":2,"1689":1,"1727":1,"1736":1,"1742":1,"1745":1,"1787":1,"1792":4,"1794":1,"1920":1,"1921":1,"1924":1,"1926":1,"1967":2,"1972":3,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2147":1,"2156":1,"2176":1,"2177":2,"2183":1,"2184":1,"2186":1,"2187":3,"2264":1,"2277":4,"2283":1,"2290":1,"2292":1,"2293":1,"2303":1,"2304":1,"2319":1,"2344":2,"2346":1,"2375":3,"2542":1,"2549":4,"2572":1,"2575":1,"2580":2,"2586":1,"2587":1,"2588":1,"2589":1,"2607":1,"2762":1,"2764":1,"2766":1,"2767":1,"2775":1,"2802":1,"2803":1,"2809":1,"2810":1,"2812":1,"2813":1,"2815":1,"2822":1,"2829":1,"2834":2,"2836":2,"2839":1,"2855":1}}],["latency",{"2":{"1090":1,"1091":1,"1164":1,"1165":1,"1180":1,"1263":1,"1268":1,"1271":1,"1280":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1349":2,"1440":1,"2398":3,"2504":1}}],["lately",{"2":{"912":1,"1254":1}}],["latest",{"0":{"1792":1,"2221":1},"1":{"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1},"2":{"531":1,"1019":1,"1023":1,"1026":1,"1032":1,"1117":2,"1118":1,"1254":1,"1343":2,"1773":1,"1775":1,"1785":2,"1792":1,"2385":3,"2550":3,"2576":2,"2717":1,"2764":2,"2766":1,"2776":1,"2779":1,"2781":1,"2782":1,"2783":1,"2784":1,"2788":5,"2789":4,"2790":4,"2791":4,"2792":1}}],["lateral",{"2":{"857":1,"1096":1}}],["later",{"2":{"180":1,"529":1,"686":1,"844":1,"848":1,"859":1,"865":1,"872":1,"874":1,"888":1,"902":1,"903":1,"911":1,"1080":1,"1157":1,"1187":1,"1214":1,"1232":1,"1401":1,"1403":2,"1792":1,"1948":1,"1949":1,"1960":1,"2156":1,"2171":1,"2257":1,"2434":1,"2542":1,"2546":1,"2684":1,"2742":1,"2819":1}}],["lat",{"2":{"333":2}}],["lastname",{"2":{"914":2,"915":1,"916":6,"917":4,"918":2,"919":2,"920":5,"1375":1}}],["lastresult",{"2":{"763":2,"885":1,"887":1,"894":1,"903":3}}],["last",{"2":{"213":1,"319":2,"427":1,"763":1,"773":1,"840":1,"841":1,"851":1,"856":1,"880":1,"885":1,"913":2,"914":4,"915":4,"916":5,"918":3,"921":1,"1020":1,"1021":2,"1026":1,"1050":2,"1056":3,"1058":2,"1060":2,"1073":1,"1213":1,"1216":2,"1239":1,"1249":1,"1254":2,"1336":1,"1338":1,"1339":2,"1375":2,"1376":2,"1401":1,"1405":1,"1440":1,"1741":1,"1792":2,"2050":1,"2110":1,"2289":1,"2407":1,"2530":1,"2531":1,"2533":2,"2537":1}}],["largeobjectuploadhandler",{"2":{"2615":1}}],["largeobjectchecktext",{"2":{"1792":1,"2123":1,"2126":2}}],["largeobjectcheckimage",{"2":{"1356":1,"1792":1,"2123":1,"2126":2}}],["largeobjectkey",{"2":{"1356":1,"1792":1,"2123":1,"2126":2}}],["largeobjectenabled",{"2":{"1356":1,"1792":1,"2123":1,"2126":2,"2132":1}}],["largeobject",{"2":{"1353":1}}],["largest",{"2":{"872":1,"873":1,"2398":2}}],["larger",{"0":{"1091":1},"2":{"872":2,"1067":1,"1091":1,"1133":1,"1193":1,"1262":1,"2245":1,"2398":1,"2604":1,"2789":1}}],["large",{"0":{"85":1,"749":1,"753":1,"782":1,"1262":1,"1268":1,"1298":1,"1352":1,"1362":1,"1363":1,"2126":1,"2517":1},"1":{"750":1,"751":1,"752":1,"753":1,"783":1,"1299":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":2,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"75":1,"87":1,"745":1,"746":3,"747":2,"748":3,"749":1,"751":2,"753":9,"777":3,"781":1,"782":12,"783":2,"791":1,"852":1,"902":3,"903":6,"904":1,"911":1,"919":1,"968":1,"971":1,"1037":1,"1067":1,"1099":2,"1121":1,"1126":1,"1149":2,"1161":2,"1165":1,"1255":3,"1258":1,"1274":1,"1280":1,"1285":4,"1299":1,"1351":1,"1352":1,"1353":1,"1354":2,"1355":1,"1356":1,"1358":7,"1359":1,"1362":1,"1363":1,"1364":1,"1365":1,"1410":5,"1412":1,"1431":1,"1432":1,"1511":1,"1516":2,"1632":2,"1792":7,"1925":4,"1927":1,"1928":1,"1974":1,"2122":1,"2123":2,"2124":1,"2125":1,"2126":4,"2134":1,"2164":1,"2165":1,"2222":1,"2265":3,"2270":2,"2398":1,"2466":1,"2517":1,"2521":1,"2549":1,"2607":1,"2664":2,"2812":1}}],["labelled",{"2":{"2451":1}}],["labeling",{"2":{"1134":1}}],["label>",{"2":{"1061":8}}],["labels",{"0":{"1128":1},"1":{"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1},"2":{"948":1,"1037":1,"1128":1,"1134":1,"1386":2,"2535":1}}],["label",{"2":{"1":1,"334":2,"859":1,"1133":1,"1386":1,"1792":1,"1974":2,"2038":1,"2040":4,"2474":1,"2476":3,"2482":1,"2607":2,"2732":1}}],["labor",{"2":{"1":1,"1211":1}}],["libgssapi",{"2":{"2385":1}}],["libssl3",{"2":{"2385":1}}],["lib",{"2":{"1654":1,"2297":1,"2757":1}}],["libraries",{"2":{"849":1,"851":1,"881":1,"911":1,"958":1,"1010":1,"1036":1,"1064":1,"1099":1,"1209":1,"1248":1,"2289":1,"2625":1,"2772":1,"2775":1}}],["library",{"2":{"1":1,"845":1,"856":1,"879":1,"910":1,"951":1,"971":2,"1011":1,"1078":1,"1096":1,"1181":1,"1416":1,"1422":1,"1515":1,"1572":1,"1581":1,"1840":2,"2077":1,"2148":1,"2195":1,"2226":1,"2254":1,"2257":1,"2258":1,"2274":1,"2386":1,"2389":3,"2409":1,"2431":1,"2440":1,"2448":1,"2455":1,"2465":1,"2555":1,"2571":1,"2628":2,"2652":1,"2711":1,"2729":1,"2795":1}}],["licenses",{"2":{"1206":3,"1208":1}}],["licensed",{"2":{"2":1}}],["lifted",{"2":{"2403":1}}],["lifting",{"2":{"876":1,"1017":1}}],["lifetime",{"2":{"872":2,"873":1,"1616":3,"1792":3}}],["life",{"2":{"861":1,"1404":2}}],["lifecycle",{"2":{"716":1,"851":1,"1099":1,"1303":1,"1824":1,"2112":1,"2481":1,"2498":1,"2875":1}}],["lightweight",{"2":{"1087":1,"1269":1,"1343":1,"1385":2,"2369":1,"2550":1,"2791":1,"2806":1}}],["lighthouse",{"2":{"913":1}}],["light",{"2":{"852":1}}],["lied",{"2":{"854":1}}],["lie",{"2":{"844":1,"873":1,"875":1,"1279":1,"2452":1}}],["lies",{"2":{"841":1,"872":1}}],["liteprofile",{"2":{"1692":1,"1792":1}}],["literals",{"0":{"2399":1},"2":{"1575":1,"2040":1,"2399":1,"2528":1,"2863":1}}],["literally",{"2":{"389":1,"452":1,"1403":1}}],["literal",{"2":{"212":1,"378":1,"379":1,"388":2,"389":2,"390":1,"395":1,"408":3,"460":2,"462":1,"463":1,"464":2,"467":1,"468":1,"469":4,"551":2,"555":1,"616":1,"848":1,"1067":1,"1523":1,"1582":1,"1792":4,"1854":2,"1855":1,"1956":1,"2261":1,"2277":1,"2333":2,"2339":1,"2379":1,"2380":1,"2381":1,"2400":1,"2476":1,"2493":1,"2529":1,"2531":1,"2533":1,"2595":2,"2596":1,"2603":1,"2665":3}}],["little",{"2":{"841":1,"1385":1,"1401":1,"1435":1,"2398":1}}],["likely",{"2":{"1401":1,"1402":1,"1792":1,"1861":1,"2835":1}}],["like",{"0":{"1017":1,"2392":1},"2":{"165":1,"184":1,"374":1,"376":2,"388":2,"390":2,"395":1,"448":1,"452":1,"587":1,"690":1,"775":1,"801":1,"832":1,"834":1,"835":1,"837":1,"840":1,"841":2,"845":1,"847":1,"851":3,"852":1,"857":1,"859":2,"876":1,"879":1,"880":1,"904":1,"915":1,"916":3,"917":1,"918":2,"919":1,"920":1,"948":1,"968":1,"1012":1,"1033":1,"1037":1,"1050":1,"1067":1,"1073":2,"1081":1,"1096":1,"1097":1,"1098":2,"1102":1,"1114":1,"1122":1,"1127":1,"1129":1,"1130":1,"1139":1,"1167":1,"1210":1,"1326":1,"1385":2,"1386":7,"1390":1,"1396":1,"1398":1,"1399":3,"1401":2,"1402":2,"1403":3,"1404":2,"1405":3,"1409":1,"1416":1,"1417":1,"1421":1,"1422":1,"1424":1,"1432":1,"1437":1,"1477":1,"1566":1,"1567":2,"1609":1,"1619":1,"1623":1,"1792":2,"2106":1,"2183":1,"2184":1,"2193":2,"2225":1,"2226":1,"2247":1,"2266":1,"2277":1,"2282":1,"2284":1,"2287":1,"2292":1,"2313":1,"2323":1,"2328":1,"2336":1,"2342":1,"2347":1,"2369":1,"2378":1,"2395":1,"2407":1,"2410":1,"2413":1,"2420":1,"2429":1,"2441":1,"2468":1,"2476":1,"2493":2,"2511":1,"2519":1,"2528":1,"2531":1,"2532":3,"2535":1,"2537":2,"2589":1,"2645":1,"2661":1,"2664":1,"2667":1,"2679":1,"2721":1,"2758":1,"2810":1,"2823":2,"2863":1,"2866":1,"2878":1}}],["likes",{"2":{"1":1}}],["limitation",{"2":{"919":2,"927":1,"928":1,"1097":1,"1145":1,"2319":1,"2529":1,"2588":1,"2590":2,"2855":1}}],["limitations",{"0":{"919":1,"2466":1,"2855":1},"2":{"851":1,"919":2,"920":1,"1106":1,"1378":1,"1394":2,"2481":1,"2586":2}}],["limited",{"0":{"2069":1},"2":{"916":1,"919":1,"945":1,"1013":1,"1111":1,"1160":1,"1163":1,"1179":1,"1185":1,"1382":1,"1458":1,"1618":2,"1620":1,"1742":5,"1771":1,"1792":3,"1822":1,"1958":1,"2256":2,"2290":5,"2375":1,"2468":1,"2608":1}}],["limiter",{"0":{"473":1,"1947":1,"2257":1},"1":{"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"1948":1,"1949":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1957":1,"1958":1,"1959":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1},"2":{"235":1,"473":3,"474":2,"476":2,"477":2,"478":1,"479":1,"480":1,"481":1,"483":1,"835":1,"868":1,"869":1,"873":1,"1069":3,"1113":1,"1158":1,"1161":2,"1163":3,"1179":2,"1182":2,"1217":1,"1224":1,"1253":1,"1406":1,"1718":1,"1790":1,"1792":14,"1797":1,"1822":4,"1824":2,"1874":1,"1894":1,"1895":1,"1947":1,"1949":1,"1951":2,"1952":1,"1953":1,"1954":1,"1955":1,"1956":1,"1959":2,"1961":3,"1962":1,"1964":1,"2047":1,"2061":1,"2070":1,"2218":2,"2224":1,"2240":1,"2257":11,"2258":1,"2379":3,"2438":1,"2441":1,"2447":2,"2468":1,"2470":1,"2471":1,"2472":1,"2481":2,"2558":1,"2634":1,"2635":1,"2747":2}}],["limits",{"0":{"1066":1,"1069":1,"1077":1,"1990":1,"1991":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1991":1},"2":{"879":1,"1037":1,"1066":1,"1115":1,"1121":1,"1158":1,"1161":1,"1163":1,"1177":1,"1185":1,"1258":1,"1609":1,"1703":1,"1706":1,"1792":3,"1951":1,"1952":1,"1953":1,"1954":1,"1984":1,"1990":2,"1992":1,"1993":1,"1995":1,"2379":1,"2633":1,"2645":1,"2661":1}}],["limit=",{"2":{"212":1}}],["limit",{"0":{"927":1,"944":1,"1706":1},"1":{"928":1,"929":1,"930":1},"2":{"119":2,"212":3,"263":1,"478":2,"480":1,"488":1,"531":2,"919":1,"921":1,"926":1,"927":1,"928":1,"944":1,"1049":2,"1101":1,"1105":1,"1107":2,"1149":1,"1158":1,"1162":1,"1179":1,"1232":1,"1245":2,"1385":1,"1429":1,"1511":1,"1517":2,"1594":1,"1624":1,"1704":1,"1717":1,"1792":9,"1804":2,"1824":1,"1893":2,"1925":1,"1949":2,"1951":1,"1952":1,"1953":1,"1954":1,"1961":2,"2061":2,"2257":5,"2265":2,"2344":1,"2441":1,"2463":2,"2464":2,"2465":2,"2466":1,"2517":1,"2848":1}}],["limiting",{"0":{"479":1,"1156":1,"1157":1,"1161":1,"1162":1,"1245":1,"1955":1,"1961":1,"2061":1,"2218":1,"2379":1,"2747":1},"1":{"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1956":1,"1957":1},"2":{"41":1,"235":1,"473":1,"478":1,"479":1,"480":2,"481":1,"483":1,"835":1,"836":1,"837":1,"868":2,"869":3,"873":1,"877":1,"1032":1,"1037":2,"1066":1,"1069":1,"1086":1,"1101":9,"1105":1,"1109":1,"1113":1,"1121":1,"1125":1,"1127":3,"1135":1,"1156":2,"1159":1,"1163":1,"1179":1,"1180":2,"1181":1,"1182":3,"1245":1,"1250":1,"1252":1,"1351":1,"1386":1,"1407":1,"1704":1,"1718":1,"1739":1,"1790":1,"1792":6,"1797":1,"1824":1,"1894":1,"1895":1,"1947":1,"1949":2,"1950":3,"1951":1,"1952":1,"1953":1,"1954":1,"1962":1,"1964":1,"2070":1,"2257":5,"2287":1,"2378":1,"2389":1,"2438":1,"2580":1,"2634":1,"2635":1,"2809":1}}],["listens",{"2":{"2830":1,"2832":1,"2838":1}}],["listening",{"2":{"1460":1,"1792":2,"2116":1,"2117":2,"2119":2,"2375":2,"2699":1,"2701":1,"2702":2,"2704":2,"2823":1,"2824":2,"2825":1}}],["listener",{"2":{"1325":1,"1326":3,"2828":1}}],["listeners",{"2":{"663":1,"669":1,"1326":1}}],["listen",{"0":{"1324":1},"1":{"1325":1},"2":{"650":1,"1037":1,"1103":2,"1106":2,"1324":2,"1325":1,"1792":1,"2118":1,"2342":1,"2703":1}}],["listed",{"2":{"175":1,"179":1,"213":1,"685":1,"704":1,"1074":1,"1690":1,"1741":1,"1792":5,"1832":1,"2097":1,"2289":1,"2389":1,"2477":1,"2483":1,"2494":1,"2526":1,"2531":1,"2535":1,"2860":1,"2869":2}}],["listings",{"2":{"1432":1}}],["listing",{"2":{"102":1,"109":1,"1150":1,"1527":1,"1792":1,"1832":1,"2164":1,"2380":1}}],["list",{"0":{"2200":1,"2494":1},"2":{"101":1,"105":1,"108":2,"109":1,"213":1,"305":1,"317":1,"323":3,"384":1,"529":1,"684":1,"781":2,"833":1,"834":1,"852":1,"868":1,"876":1,"885":1,"902":1,"916":1,"1013":1,"1032":1,"1039":2,"1042":2,"1043":1,"1102":1,"1155":1,"1175":1,"1360":1,"1382":2,"1385":1,"1386":1,"1436":1,"1437":1,"1442":4,"1460":1,"1521":2,"1523":1,"1527":1,"1533":1,"1596":1,"1616":1,"1639":4,"1644":1,"1674":1,"1703":3,"1714":1,"1740":1,"1792":31,"1813":1,"1821":1,"1823":2,"1824":1,"1832":1,"1833":1,"1838":4,"1898":2,"1956":1,"1967":2,"2038":1,"2040":2,"2107":1,"2125":1,"2168":2,"2196":1,"2200":1,"2223":2,"2288":1,"2375":1,"2379":1,"2380":3,"2431":2,"2436":2,"2477":1,"2481":3,"2486":1,"2492":1,"2494":2,"2498":1,"2504":1,"2626":1,"2633":3,"2662":1,"2749":1,"2765":1,"2785":1,"2797":1,"2812":1}}],["lists",{"2":{"14":1,"113":1,"220":1,"637":1,"687":1,"1409":1,"1792":2,"1833":1,"1862":1,"1928":1,"2097":1,"2104":1,"2476":1,"2481":1,"2535":1,"2537":1,"2549":1,"2880":1}}],["linux",{"0":{"2576":1,"2782":1,"2783":1},"2":{"1117":1,"1118":2,"1653":3,"1664":1,"1792":2,"2157":1,"2237":1,"2297":1,"2353":1,"2385":1,"2450":1,"2543":1,"2576":4,"2716":1,"2757":1,"2776":1,"2779":2,"2783":1,"2790":1,"2792":3}}],["linux64",{"2":{"1117":3,"2779":1,"2782":1}}],["linq",{"2":{"852":6}}],["liner",{"2":{"2873":1}}],["line2",{"2":{"2589":1}}],["line1",{"2":{"2589":3}}],["lines",{"0":{"873":1,"1281":1},"2":{"319":3,"809":1,"856":1,"867":1,"868":10,"869":4,"871":1,"873":2,"874":1,"875":1,"879":1,"910":4,"911":4,"968":1,"1010":1,"1021":1,"1027":6,"1036":2,"1037":2,"1059":1,"1064":12,"1079":1,"1102":1,"1181":2,"1281":3,"1302":2,"1309":1,"1322":4,"1350":7,"1368":2,"1401":1,"1408":1,"1414":1,"1432":1,"1565":1,"1608":1,"1792":1,"1802":1,"2192":1,"2272":1,"2366":1,"2372":2,"2393":1,"2452":1,"2481":2,"2529":3,"2533":2,"2535":1,"2537":1,"2575":1,"2795":1,"2800":1,"2802":1,"2865":1}}],["line",{"0":{"24":1,"251":1,"339":1,"342":1,"343":1,"562":1,"563":1,"1009":1,"2365":1,"2691":1,"2692":1,"2699":1,"2780":1,"2785":1},"1":{"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"2692":1,"2781":1,"2782":1,"2783":1,"2784":1},"2":{"128":2,"129":1,"131":1,"159":3,"203":1,"211":2,"229":1,"251":1,"319":1,"323":1,"324":1,"339":1,"340":1,"342":1,"343":1,"344":1,"378":1,"489":1,"490":1,"491":1,"493":1,"494":1,"496":1,"560":3,"562":1,"563":1,"606":1,"619":3,"666":1,"685":2,"687":1,"689":1,"694":1,"704":1,"706":2,"709":1,"714":1,"845":1,"852":3,"860":1,"864":1,"865":1,"869":1,"871":1,"872":5,"873":1,"919":1,"921":1,"947":1,"981":1,"1042":1,"1076":1,"1136":1,"1181":3,"1189":2,"1281":1,"1366":1,"1368":1,"1373":1,"1382":1,"1386":2,"1401":1,"1402":1,"1408":1,"1409":2,"1419":1,"1431":1,"1608":1,"1731":2,"1785":1,"1792":6,"1917":1,"1925":1,"1991":1,"2094":1,"2111":1,"2155":1,"2190":1,"2191":3,"2192":2,"2206":1,"2208":1,"2222":2,"2264":1,"2272":1,"2323":1,"2328":3,"2329":1,"2333":1,"2340":3,"2358":1,"2359":1,"2365":4,"2366":3,"2393":1,"2482":1,"2489":1,"2502":1,"2505":2,"2517":1,"2528":2,"2529":8,"2531":6,"2532":1,"2533":2,"2535":3,"2537":2,"2663":1,"2679":1,"2680":1,"2681":3,"2684":1,"2687":1,"2691":2,"2697":2,"2719":1,"2726":2,"2727":1,"2729":1,"2760":1,"2762":3,"2765":1,"2772":1,"2795":1,"2802":1,"2812":1,"2823":2,"2824":1,"2825":2,"2840":1,"2852":1,"2864":1,"2865":7,"2874":1,"2879":1,"2880":1,"2881":1}}],["linking",{"2":{"1792":1}}],["linkedin",{"0":{"1692":1},"2":{"868":1,"1060":1,"1393":1,"1403":1,"1445":1,"1465":1,"1682":1,"1690":1,"1692":5,"1788":1,"1792":8,"1894":1,"2736":1}}],["links",{"2":{"386":1,"970":1,"1572":1,"2182":1}}],["link",{"2":{"0":1,"158":1,"834":1,"841":1,"919":1,"920":1,"961":1,"1200":2,"1401":1}}],["livez",{"2":{"1780":2}}],["livenessprobe",{"2":{"1773":1}}],["liveness",{"0":{"1768":1},"2":{"1764":1,"1774":2,"1776":1,"1792":2,"2634":3}}],["livepath",{"2":{"1763":1,"1764":1,"1776":1,"1780":1,"1792":1,"2634":1}}],["live=false",{"2":{"1525":1}}],["live=true",{"2":{"1067":3,"1101":1,"1150":1,"1519":1,"1525":1}}],["lived",{"0":{"1068":1},"2":{"855":1,"1037":1,"1066":2,"1068":1,"1121":2,"1325":1,"1458":2,"1792":3,"2375":2,"2427":2,"2835":1}}],["lives",{"0":{"855":1},"2":{"354":1,"840":1,"844":2,"845":1,"851":1,"854":1,"861":1,"865":1,"866":1,"868":1,"956":1,"978":1,"1010":1,"1039":1,"1070":1,"1077":1,"1086":1,"1108":1,"1184":1,"1368":1,"1423":1,"1435":1,"1442":1,"2171":1,"2481":1,"2520":1,"2542":1,"2762":1,"2860":1}}],["live",{"0":{"1067":1,"1768":1},"2":{"3":1,"83":1,"94":1,"278":1,"832":1,"848":1,"851":1,"852":2,"857":1,"860":1,"865":1,"868":1,"873":2,"988":1,"1037":1,"1043":1,"1067":6,"1076":1,"1080":2,"1100":2,"1115":1,"1121":1,"1150":4,"1323":1,"1327":1,"1368":1,"1373":1,"1378":1,"1394":1,"1406":2,"1408":1,"1414":1,"1524":1,"1525":1,"1529":3,"1763":1,"1764":1,"1770":1,"1773":1,"1792":2,"1826":1,"2050":1,"2156":1,"2166":1,"2267":1,"2407":1,"2409":1,"2431":1,"2440":1,"2465":1,"2466":1,"2537":1,"2542":1,"2546":2,"2634":3,"2742":1,"2860":1}}],["ns",{"2":{"2621":10}}],["nswag",{"2":{"869":1,"873":1}}],["nline2",{"2":{"2589":2}}],["nginxnginxlocation",{"2":{"1711":1}}],["nginx",{"0":{"1711":1},"2":{"1101":1,"1106":1,"1701":1,"1792":1,"1859":1,"2633":2,"2835":2}}],["nkquuqudndsnbn8bacvcqlcqkhs",{"2":{"1051":1}}],["npm",{"0":{"2786":1},"2":{"1117":2,"2106":1,"2532":1,"2716":2,"2786":3,"2878":1}}],["npx",{"2":{"1047":1,"1117":1,"2786":5}}],["npoi",{"2":{"968":1}}],["npgsqlretryextensions",{"2":{"2372":1}}],["npgsqlrestserializercontext",{"2":{"2600":1}}],["npgsqlrestbuilder",{"2":{"2372":1}}],["npgsqlrestendpoint",{"2":{"2372":4,"2384":1,"2615":1,"2621":1}}],["npgsqlrestparameter",{"2":{"2372":1}}],["npgsqlrestauthenticationoptions",{"2":{"2259":4,"2554":1}}],["npgsqlrest=debug",{"2":{"2208":1,"2699":1,"2795":1,"2824":2}}],["npgsqlrestoptions",{"0":{"2455":1},"2":{"1840":1,"2148":1,"2264":1,"2266":1,"2369":1,"2455":1,"2483":1,"2575":1,"2714":1}}],["npgsqlresttests",{"2":{"2258":1,"2417":2,"2435":4,"2447":1,"2448":1,"2456":1,"2457":3,"2465":1,"2472":1,"2513":1,"2523":2,"2546":1}}],["npgsqlresttest",{"2":{"1792":3,"1802":2,"2093":1,"2094":1,"2104":1,"2108":1,"2535":1,"2536":2,"2537":1,"2544":1,"2752":1,"2795":2,"2800":2,"2804":1,"2805":1,"2880":2}}],["npgsqlrestclient",{"0":{"2567":1},"2":{"1792":2,"1802":3,"2104":1,"2157":1,"2386":1,"2389":2,"2409":1,"2419":1,"2422":1,"2440":1,"2448":2,"2536":1,"2543":1,"2544":1,"2575":1,"2627":1,"2628":3,"2752":1,"2795":4,"2800":1,"2804":1,"2880":1}}],["npgsqlrest",{"0":{"831":1,"835":1,"868":1,"878":1,"880":1,"921":1,"937":1,"949":1,"1010":1,"1015":1,"1016":1,"1049":1,"1066":1,"1083":1,"1086":1,"1105":1,"1113":1,"1117":1,"1121":1,"1125":1,"1126":1,"1135":1,"1209":1,"1243":1,"1263":1,"1276":1,"1304":1,"1305":1,"1319":1,"1321":1,"1325":1,"1328":1,"1330":1,"1333":1,"1401":1,"1402":1,"1403":1,"1406":1,"1630":1,"1631":1,"1835":1,"2317":1,"2481":1,"2611":1,"2709":1,"2713":1,"2716":1,"2751":1,"2777":1,"2823":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1320":1,"1321":1,"1329":1,"1330":1,"1331":2,"1332":2,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1631":1,"1632":1,"1836":1,"1837":1,"1838":1,"1839":1,"1840":1,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1846":1,"1847":1,"1848":1,"1849":1,"1850":1,"1851":1,"1852":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1858":1,"1859":1,"1860":1,"1861":1,"1862":1,"1863":1,"1864":1,"1865":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2778":1,"2779":1,"2780":1,"2781":1,"2782":1,"2783":1,"2784":1,"2785":1,"2786":1,"2787":1,"2788":1,"2789":1,"2790":1,"2791":1,"2792":1,"2793":1},"2":{"1":1,"2":1,"11":1,"31":1,"33":1,"38":3,"40":1,"46":1,"52":1,"56":1,"57":2,"58":2,"61":2,"62":4,"76":1,"89":1,"134":1,"139":1,"141":1,"143":1,"150":1,"151":1,"164":1,"202":1,"220":1,"239":1,"259":1,"267":1,"279":1,"284":1,"285":1,"297":4,"298":2,"300":1,"305":3,"306":1,"307":1,"308":1,"309":4,"310":2,"317":1,"336":1,"354":1,"364":1,"388":2,"390":2,"395":1,"410":1,"414":1,"429":1,"430":1,"435":1,"436":5,"448":1,"449":1,"453":1,"455":1,"470":1,"471":1,"504":1,"505":1,"513":1,"525":1,"534":1,"556":1,"557":1,"634":1,"647":1,"650":1,"668":1,"670":1,"672":1,"689":1,"693":1,"698":1,"703":1,"708":1,"710":3,"713":1,"831":3,"832":1,"833":2,"834":1,"835":4,"836":2,"837":2,"838":3,"839":1,"866":3,"868":4,"869":2,"872":2,"873":2,"876":5,"878":4,"881":1,"889":1,"894":1,"907":1,"909":1,"910":1,"911":1,"912":1,"913":2,"914":1,"916":1,"917":2,"918":3,"919":2,"920":2,"921":3,"922":1,"934":2,"935":1,"936":1,"937":1,"938":1,"946":1,"947":4,"949":1,"954":1,"958":1,"961":1,"963":1,"964":1,"965":1,"968":1,"969":1,"970":3,"972":1,"975":2,"976":4,"978":1,"979":1,"984":1,"985":1,"986":2,"995":1,"997":2,"998":2,"1005":2,"1007":1,"1008":1,"1010":3,"1015":4,"1016":2,"1017":1,"1022":1,"1024":1,"1027":1,"1037":12,"1038":1,"1039":2,"1043":1,"1045":2,"1048":5,"1049":5,"1051":3,"1052":2,"1053":1,"1055":4,"1056":2,"1058":1,"1060":2,"1062":1,"1064":1,"1066":1,"1067":1,"1070":1,"1072":1,"1073":2,"1074":2,"1078":1,"1080":2,"1083":1,"1084":1,"1086":2,"1088":3,"1090":5,"1091":2,"1092":1,"1094":5,"1095":3,"1096":6,"1097":5,"1098":10,"1099":2,"1100":6,"1101":4,"1102":6,"1103":2,"1104":1,"1105":5,"1108":2,"1109":1,"1110":2,"1111":2,"1117":8,"1125":1,"1126":1,"1127":27,"1135":2,"1136":1,"1138":1,"1140":1,"1144":1,"1148":1,"1151":1,"1156":1,"1166":1,"1167":1,"1170":1,"1172":1,"1176":1,"1177":1,"1178":1,"1181":4,"1183":4,"1184":1,"1190":2,"1196":1,"1197":1,"1199":1,"1205":1,"1206":1,"1207":4,"1209":1,"1211":5,"1214":1,"1215":1,"1216":1,"1219":1,"1231":1,"1241":1,"1243":1,"1248":1,"1254":2,"1255":2,"1257":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1272":1,"1276":1,"1277":2,"1278":1,"1279":1,"1280":2,"1281":2,"1284":2,"1285":2,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"1302":4,"1304":2,"1305":3,"1309":1,"1317":2,"1322":2,"1325":3,"1326":3,"1327":1,"1328":3,"1329":1,"1331":2,"1332":1,"1333":1,"1337":1,"1338":2,"1340":1,"1342":1,"1343":4,"1348":1,"1350":2,"1351":1,"1352":4,"1356":1,"1357":1,"1359":1,"1361":1,"1366":3,"1367":1,"1368":3,"1376":1,"1377":1,"1380":3,"1381":1,"1383":1,"1384":2,"1385":8,"1386":4,"1388":1,"1394":1,"1396":4,"1398":4,"1400":3,"1401":5,"1402":3,"1403":4,"1404":4,"1405":1,"1406":4,"1408":3,"1414":1,"1418":4,"1419":1,"1420":5,"1421":1,"1422":1,"1423":1,"1426":1,"1430":1,"1431":2,"1434":1,"1435":1,"1436":1,"1443":2,"1444":1,"1445":1,"1468":1,"1469":1,"1482":1,"1483":1,"1485":2,"1498":1,"1499":1,"1502":1,"1503":1,"1505":1,"1518":1,"1536":2,"1539":1,"1548":1,"1552":1,"1553":1,"1579":1,"1580":1,"1581":1,"1582":1,"1584":2,"1606":2,"1609":2,"1612":2,"1620":3,"1621":1,"1626":1,"1630":2,"1633":1,"1654":1,"1672":1,"1680":1,"1682":1,"1690":1,"1721":1,"1723":1,"1733":1,"1735":1,"1736":1,"1744":1,"1749":2,"1751":1,"1752":1,"1753":1,"1758":3,"1759":1,"1761":2,"1765":1,"1773":2,"1775":1,"1785":1,"1787":1,"1792":38,"1794":1,"1800":1,"1802":2,"1810":2,"1813":1,"1814":1,"1818":1,"1822":2,"1825":6,"1833":1,"1835":1,"1836":2,"1839":3,"1840":1,"1842":1,"1848":2,"1851":1,"1852":1,"1856":1,"1859":1,"1863":3,"1866":1,"1868":4,"1869":1,"1881":1,"1890":1,"1894":2,"1896":1,"1897":2,"1898":1,"1899":1,"1900":1,"1907":1,"1909":1,"1911":2,"1912":1,"1914":2,"1915":1,"1916":1,"1923":1,"1927":1,"1929":1,"1931":1,"1933":2,"1966":1,"1968":1,"1970":1,"1971":1,"1973":1,"1974":2,"1975":1,"1977":1,"1999":1,"2011":1,"2012":1,"2013":1,"2073":1,"2075":1,"2077":1,"2080":1,"2082":2,"2092":4,"2104":1,"2111":2,"2123":1,"2126":1,"2127":1,"2128":1,"2130":1,"2132":1,"2135":2,"2148":1,"2153":2,"2154":1,"2155":2,"2156":2,"2160":2,"2162":4,"2164":1,"2167":1,"2168":1,"2170":1,"2171":3,"2175":1,"2176":2,"2177":2,"2181":1,"2182":1,"2183":3,"2184":2,"2187":2,"2190":1,"2193":1,"2207":1,"2208":2,"2219":1,"2221":1,"2223":2,"2224":1,"2226":1,"2228":1,"2245":2,"2253":1,"2254":4,"2255":2,"2258":1,"2264":3,"2265":3,"2267":2,"2291":1,"2297":1,"2302":1,"2308":1,"2310":1,"2313":1,"2317":2,"2323":1,"2330":1,"2382":1,"2383":1,"2386":1,"2389":6,"2391":1,"2392":1,"2394":1,"2406":1,"2414":1,"2416":2,"2419":3,"2422":1,"2430":2,"2431":1,"2434":1,"2436":1,"2438":5,"2451":1,"2455":1,"2457":1,"2466":1,"2479":1,"2481":4,"2483":1,"2484":2,"2493":1,"2495":1,"2509":1,"2525":1,"2529":1,"2532":2,"2533":1,"2536":1,"2537":1,"2539":1,"2541":2,"2542":1,"2544":1,"2549":3,"2550":4,"2551":4,"2571":1,"2576":3,"2581":2,"2587":1,"2595":1,"2596":1,"2607":2,"2611":1,"2628":3,"2638":1,"2641":1,"2659":1,"2669":1,"2678":2,"2680":1,"2681":1,"2682":1,"2684":4,"2685":2,"2686":2,"2687":1,"2689":3,"2690":2,"2691":5,"2692":6,"2694":1,"2695":3,"2697":5,"2699":5,"2700":5,"2701":3,"2709":1,"2713":1,"2714":1,"2716":1,"2717":2,"2719":2,"2721":2,"2724":1,"2729":2,"2739":1,"2742":2,"2749":3,"2750":4,"2751":1,"2752":1,"2754":1,"2757":1,"2759":1,"2760":3,"2761":1,"2762":1,"2766":1,"2767":1,"2768":1,"2769":2,"2772":1,"2773":2,"2774":1,"2775":1,"2776":3,"2781":4,"2782":6,"2783":6,"2784":6,"2785":8,"2786":6,"2788":7,"2789":5,"2790":5,"2791":6,"2792":4,"2794":1,"2795":4,"2797":2,"2798":2,"2799":1,"2800":1,"2801":1,"2802":1,"2804":4,"2806":1,"2807":3,"2808":1,"2809":1,"2810":2,"2812":1,"2814":2,"2815":1,"2818":1,"2819":1,"2820":1,"2821":2,"2823":7,"2824":17,"2825":16,"2826":2,"2827":3,"2828":1,"2830":1,"2833":1,"2835":2,"2839":1,"2841":1,"2855":1,"2856":1,"2860":2,"2875":4,"2878":1,"2880":2}}],["npgsqlexception",{"2":{"2255":2}}],["npgsqlmultihostdatasource",{"2":{"1626":1,"2266":1}}],["npgsqlbatchcommand",{"2":{"1852":1,"2320":1,"2383":1}}],["npgsqlbatch",{"2":{"1070":1,"1102":1,"1370":1,"2320":1,"2372":2,"2850":1}}],["npgsqldatasource>",{"2":{"2266":1}}],["npgsqldatareader",{"2":{"949":1}}],["npgsqldbtype",{"2":{"379":1,"2333":1,"2394":1,"2621":1}}],["npgsql",{"2":{"379":1,"869":1,"874":1,"1070":1,"1167":1,"1170":1,"1172":1,"1174":1,"1175":1,"1182":2,"1276":2,"1616":1,"1626":1,"1628":1,"1792":3,"1851":1,"2266":2,"2333":1,"2382":1,"2451":2,"2498":1,"2545":1,"2634":1,"2823":2}}],["nitpick",{"2":{"1389":1}}],["nice",{"2":{"876":1,"1388":1,"1392":1,"2430":1}}],["nicely",{"2":{"860":1}}],["nine",{"2":{"868":1}}],["nightly",{"2":{"855":1,"861":1}}],["n+1",{"2":{"856":1,"861":1,"948":1,"1096":1,"1133":1}}],["nyc",{"2":{"851":1}}],["n3",{"2":{"833":1}}],["n2",{"2":{"833":1}}],["n1",{"2":{"833":1}}],["nr",{"2":{"833":1,"836":3,"881":4,"1184":2,"2760":4,"2807":2}}],["nanoseconds",{"2":{"2309":1}}],["nag",{"2":{"1792":1,"2107":1,"2537":1}}],["nagged",{"2":{"1385":1}}],["naive",{"2":{"1430":1,"1792":2,"1856":3,"2224":1,"2450":1,"2451":2,"2454":1,"2455":2,"2456":2}}],["narration",{"0":{"1382":1}}],["narrowing",{"2":{"1792":1,"2107":1,"2537":1}}],["narrows",{"2":{"1091":1,"1268":1,"2537":1}}],["narrow",{"2":{"852":1,"1106":1,"2094":1,"2261":1,"2537":1,"2877":1}}],["narrowed",{"2":{"708":1,"1263":1,"1266":1,"1792":1,"2094":1,"2107":2,"2537":3,"2879":1}}],["navigator",{"2":{"1220":1,"1221":1,"1222":1,"1792":10}}],["navigate",{"2":{"856":1,"961":2,"970":1,"1207":1,"2162":1}}],["navigation",{"0":{"1413":1},"2":{"679":1,"720":1,"723":1,"852":1,"1037":1,"1412":1,"1413":1,"1792":3,"2656":1}}],["natural",{"2":{"974":1,"1078":1,"1390":1,"1401":1,"2829":1}}],["naturally",{"0":{"984":1},"2":{"214":1,"843":1,"1178":1,"1193":1,"1404":2,"2502":1}}],["nature",{"2":{"841":1,"861":1}}],["natively",{"2":{"865":1,"1378":2,"2576":1,"2790":1,"2858":1}}],["native",{"0":{"952":1},"2":{"376":1,"436":1,"458":1,"909":1,"918":1,"947":1,"952":1,"968":1,"971":1,"1007":1,"1037":1,"1073":3,"1080":1,"1098":3,"1099":1,"1127":6,"1274":1,"1279":1,"1374":1,"1382":1,"1385":1,"1386":4,"1792":2,"1851":1,"1866":1,"2382":1,"2385":1,"2508":1,"2540":1,"2544":1,"2576":2,"2600":1,"2625":1,"2732":1,"2744":1,"2762":1,"2776":1,"2792":1,"2812":1,"2827":1,"2845":1,"2848":1,"2855":1}}],["naming",{"0":{"565":1,"566":1,"699":1,"1841":1,"2690":1},"1":{"1842":1},"2":{"245":1,"259":1,"309":1,"374":1,"388":1,"410":1,"871":1,"976":1,"1581":1,"1605":2,"1787":1,"1794":1,"2094":1,"2107":1,"2197":1,"2240":1,"2252":1,"2320":1,"2359":1,"2376":1,"2394":1,"2395":1,"2492":1,"2493":1,"2497":2,"2530":1,"2537":1,"2546":1,"2688":2,"2689":1,"2701":1,"2731":1,"2879":1}}],["nameconverter",{"2":{"2327":1,"2540":1,"2724":1,"2842":1,"2845":1}}],["name→default",{"2":{"2040":1,"2476":1}}],["namepattern",{"2":{"1752":1,"1753":1,"1758":1,"1792":1,"2551":1}}],["name=example",{"2":{"2824":1,"2825":1}}],["name=value",{"2":{"2529":1,"2865":1}}],["name=hello",{"2":{"2321":1}}],["name=file",{"2":{"1366":1}}],["name=",{"2":{"1061":3,"1491":1,"1792":1,"2039":1}}],["name=john",{"2":{"520":1}}],["namenotsimilarto",{"2":{"349":1,"1792":2,"1836":1,"1838":1,"1839":1,"1897":1,"1898":1,"1909":2,"1910":1,"1911":1,"2225":1,"2419":1,"2431":1,"2433":1,"2434":1,"2435":1,"2436":1,"2701":1}}],["name>`",{"2":{"1792":4}}],["name>",{"2":{"45":2,"69":1,"102":1,"145":2,"193":2,"318":1,"348":2,"370":26,"474":2,"508":1,"528":1,"537":1,"560":2,"570":4,"582":1,"674":1,"719":1,"809":3,"1792":2}}],["named",{"0":{"147":1,"195":1,"573":1,"1392":1,"2375":1,"2410":1,"2420":1,"2532":1,"2540":1,"2731":1,"2845":1,"2871":1},"1":{"2411":1,"2412":1,"2413":1,"2421":1,"2422":1,"2423":1,"2424":1},"2":{"39":1,"101":1,"123":1,"144":1,"230":1,"236":1,"239":3,"299":1,"309":1,"385":1,"569":1,"693":1,"700":1,"701":1,"703":1,"713":1,"747":1,"868":1,"869":1,"934":1,"964":1,"1060":1,"1073":1,"1082":1,"1086":1,"1095":1,"1097":1,"1098":3,"1101":2,"1109":1,"1111":4,"1150":1,"1174":1,"1176":1,"1217":1,"1224":1,"1250":1,"1370":1,"1378":1,"1379":1,"1392":1,"1458":2,"1511":1,"1520":1,"1533":1,"1535":1,"1559":1,"1571":1,"1588":1,"1613":1,"1614":1,"1634":1,"1636":1,"1670":1,"1673":1,"1688":1,"1764":1,"1771":1,"1792":13,"1822":1,"1849":1,"1874":1,"1949":1,"1958":1,"2047":1,"2094":1,"2103":1,"2111":3,"2139":1,"2161":1,"2167":2,"2181":1,"2221":2,"2225":2,"2227":1,"2228":1,"2255":1,"2256":1,"2266":1,"2332":1,"2340":1,"2375":1,"2380":2,"2413":4,"2419":2,"2420":3,"2421":3,"2422":3,"2423":4,"2424":2,"2435":6,"2470":1,"2484":1,"2490":1,"2525":1,"2532":2,"2533":4,"2534":1,"2537":1,"2538":1,"2540":2,"2544":1,"2545":3,"2546":2,"2635":1,"2649":1,"2664":1,"2731":1,"2733":1,"2740":1,"2752":1,"2758":1,"2844":1,"2859":1,"2870":3,"2871":1,"2874":1}}],["nameidentifier",{"2":{"34":1}}],["name",{"0":{"19":1,"48":1,"68":1,"325":1,"407":1,"507":1,"510":1,"511":1,"534":1,"559":1,"662":1,"1240":1,"1619":1,"1620":1,"1658":1,"1838":1,"1889":1,"1909":1,"2249":1,"2314":1,"2410":1,"2441":1,"2483":1,"2493":1,"2494":1,"2497":2,"2518":1,"2519":1,"2540":1,"2845":1},"1":{"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"560":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1,"1839":1,"2411":1,"2412":1,"2413":1,"2442":1,"2443":1,"2444":1},"2":{"19":1,"22":2,"25":1,"31":1,"33":2,"34":3,"35":1,"37":2,"38":7,"39":2,"40":2,"44":1,"45":2,"51":1,"58":1,"60":3,"61":8,"62":3,"63":1,"68":1,"69":1,"71":2,"72":1,"74":7,"75":3,"102":3,"108":1,"109":3,"119":2,"128":4,"144":1,"145":1,"148":1,"156":2,"159":1,"166":4,"167":1,"168":1,"184":1,"186":4,"203":1,"208":3,"210":1,"212":3,"215":1,"226":4,"228":1,"238":1,"239":1,"245":1,"248":3,"250":1,"257":4,"258":2,"286":1,"297":2,"299":2,"304":2,"305":3,"306":6,"309":2,"310":2,"318":3,"319":1,"325":2,"326":3,"327":1,"332":1,"333":2,"334":2,"347":1,"352":1,"355":2,"366":2,"369":1,"370":7,"374":2,"376":5,"378":1,"384":2,"386":4,"387":2,"388":9,"390":12,"395":6,"396":1,"407":2,"428":1,"436":3,"447":1,"448":2,"452":1,"453":1,"454":1,"473":1,"479":2,"480":2,"488":2,"489":3,"493":4,"504":1,"506":2,"507":2,"508":1,"510":2,"511":1,"513":1,"520":5,"526":1,"527":3,"528":4,"529":1,"532":5,"534":2,"535":1,"541":1,"544":1,"562":3,"563":2,"565":1,"570":1,"582":1,"588":1,"609":2,"611":3,"612":2,"613":1,"614":2,"617":1,"621":2,"623":2,"626":1,"650":1,"653":1,"663":1,"674":2,"675":4,"677":2,"678":1,"679":3,"692":1,"694":1,"695":1,"696":1,"698":1,"699":1,"701":2,"702":1,"704":2,"705":1,"709":2,"714":1,"715":1,"720":2,"723":3,"733":4,"736":2,"738":2,"747":1,"748":1,"756":4,"757":6,"762":3,"764":2,"765":1,"766":1,"770":1,"772":4,"773":1,"774":4,"775":1,"776":2,"779":1,"781":1,"784":3,"785":1,"788":3,"797":5,"798":3,"801":1,"802":3,"809":5,"813":4,"814":4,"816":1,"834":4,"835":3,"841":3,"861":1,"876":1,"880":1,"882":1,"883":2,"884":4,"888":5,"892":2,"893":1,"894":1,"898":3,"899":1,"900":1,"903":1,"904":3,"913":6,"914":8,"915":12,"916":10,"918":7,"956":1,"957":5,"960":1,"961":2,"964":1,"973":2,"996":1,"997":3,"998":1,"1017":2,"1031":1,"1038":3,"1040":4,"1042":1,"1044":3,"1045":1,"1052":1,"1053":1,"1054":5,"1056":4,"1060":2,"1067":1,"1068":2,"1069":5,"1070":1,"1076":4,"1080":1,"1082":1,"1094":1,"1098":2,"1102":3,"1105":3,"1107":1,"1111":1,"1113":1,"1138":1,"1142":1,"1149":2,"1150":2,"1157":2,"1162":2,"1176":2,"1185":1,"1187":1,"1188":5,"1189":2,"1191":3,"1192":2,"1193":4,"1197":2,"1204":1,"1213":1,"1214":8,"1215":3,"1217":1,"1220":3,"1221":2,"1224":1,"1225":2,"1232":5,"1234":5,"1240":2,"1308":1,"1309":3,"1321":3,"1343":1,"1347":1,"1355":2,"1357":1,"1358":3,"1366":3,"1372":7,"1373":2,"1374":1,"1375":4,"1398":1,"1405":1,"1409":3,"1410":3,"1413":1,"1414":1,"1431":3,"1436":2,"1447":2,"1451":1,"1454":1,"1458":10,"1460":1,"1469":5,"1470":1,"1474":1,"1476":2,"1477":3,"1478":2,"1480":1,"1483":5,"1489":3,"1494":1,"1504":4,"1520":1,"1522":1,"1523":1,"1527":3,"1539":4,"1540":1,"1541":2,"1542":2,"1543":1,"1544":3,"1545":2,"1546":2,"1547":5,"1548":4,"1554":2,"1557":1,"1559":3,"1563":2,"1564":1,"1571":3,"1576":1,"1577":1,"1581":2,"1588":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1604":2,"1605":2,"1608":1,"1616":1,"1618":3,"1620":2,"1631":1,"1651":2,"1655":6,"1669":1,"1670":1,"1673":1,"1674":1,"1678":1,"1684":1,"1685":1,"1687":2,"1688":1,"1689":12,"1695":1,"1697":2,"1722":6,"1728":1,"1730":2,"1732":1,"1733":2,"1738":1,"1752":1,"1753":5,"1755":1,"1758":1,"1773":2,"1792":202,"1800":1,"1802":3,"1806":1,"1807":1,"1808":3,"1818":2,"1822":3,"1824":4,"1834":1,"1837":1,"1839":1,"1847":1,"1848":4,"1851":1,"1852":3,"1862":4,"1874":1,"1875":2,"1882":3,"1884":1,"1889":2,"1898":2,"1902":1,"1903":1,"1904":1,"1905":1,"1906":2,"1907":2,"1909":1,"1911":1,"1913":1,"1918":6,"1922":2,"1924":2,"1925":1,"1926":1,"1948":4,"1949":2,"1951":1,"1955":2,"1957":6,"1960":2,"1968":2,"1973":1,"1974":5,"2009":4,"2010":1,"2012":1,"2013":1,"2034":1,"2038":6,"2039":2,"2040":5,"2042":1,"2047":1,"2075":1,"2076":2,"2077":3,"2078":1,"2079":8,"2081":1,"2093":1,"2094":1,"2098":1,"2108":1,"2109":6,"2110":2,"2111":4,"2112":1,"2117":1,"2119":2,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1,"2130":2,"2141":3,"2143":1,"2146":2,"2147":9,"2156":1,"2164":1,"2171":1,"2175":1,"2180":2,"2181":4,"2182":2,"2183":2,"2184":1,"2185":1,"2186":1,"2193":1,"2197":1,"2202":2,"2204":1,"2214":1,"2218":1,"2221":3,"2222":5,"2223":4,"2224":1,"2225":2,"2229":1,"2247":1,"2252":1,"2254":7,"2255":10,"2256":2,"2257":8,"2259":1,"2264":10,"2266":2,"2272":1,"2277":6,"2283":1,"2284":3,"2285":4,"2293":3,"2297":2,"2314":2,"2320":1,"2321":3,"2322":2,"2323":6,"2326":1,"2328":1,"2329":2,"2330":1,"2332":4,"2335":1,"2336":1,"2339":6,"2340":4,"2343":1,"2359":2,"2364":1,"2369":1,"2375":8,"2378":6,"2379":9,"2380":4,"2382":1,"2383":3,"2394":1,"2395":1,"2410":1,"2411":2,"2412":2,"2413":1,"2417":1,"2419":1,"2422":4,"2423":2,"2427":1,"2431":1,"2432":2,"2434":1,"2435":1,"2436":3,"2437":3,"2440":2,"2442":3,"2443":2,"2444":1,"2447":2,"2470":1,"2476":4,"2477":1,"2481":6,"2482":1,"2483":6,"2484":4,"2493":1,"2497":3,"2498":2,"2504":3,"2505":1,"2509":1,"2518":9,"2519":5,"2521":1,"2522":2,"2523":6,"2525":1,"2526":1,"2528":2,"2529":4,"2530":7,"2531":1,"2532":5,"2533":11,"2534":2,"2535":2,"2537":5,"2540":11,"2542":1,"2546":2,"2549":3,"2551":2,"2554":1,"2555":1,"2572":3,"2575":10,"2581":1,"2586":4,"2587":1,"2597":1,"2607":6,"2634":3,"2635":1,"2653":7,"2666":1,"2671":1,"2678":1,"2681":1,"2687":3,"2688":2,"2695":1,"2701":1,"2702":1,"2704":2,"2717":2,"2719":2,"2723":1,"2726":1,"2731":3,"2733":2,"2764":2,"2769":6,"2771":1,"2774":1,"2775":2,"2785":1,"2788":3,"2789":1,"2790":1,"2791":1,"2795":4,"2799":1,"2803":1,"2804":2,"2812":1,"2816":1,"2829":5,"2836":3,"2840":1,"2842":2,"2844":1,"2845":7,"2852":1,"2858":1,"2859":1,"2860":1,"2864":2,"2865":3,"2869":1,"2870":3,"2871":2,"2873":1,"2875":1,"2879":1,"2880":2}}],["namespaced",{"2":{"2434":1,"2482":1}}],["namespace",{"2":{"1802":2,"2825":1}}],["namesimilarto",{"2":{"349":1,"1792":3,"1836":1,"1838":1,"1839":1,"1897":1,"1898":2,"1909":2,"1910":1,"2225":1,"2419":1,"2431":2,"2433":1,"2435":1,"2436":1,"2438":1,"2701":1}}],["names",{"0":{"125":1,"449":1,"643":1,"814":1,"964":1,"1918":1,"2249":1,"2724":1},"1":{"126":1,"127":1,"128":1,"129":1,"130":1,"131":1},"2":{"13":1,"14":2,"33":1,"34":1,"41":1,"74":1,"102":1,"125":2,"129":1,"159":3,"210":2,"212":1,"229":1,"245":1,"258":1,"269":1,"286":2,"299":2,"300":2,"305":2,"346":1,"369":3,"372":2,"382":1,"384":1,"388":1,"390":1,"407":1,"447":2,"448":2,"449":2,"496":1,"537":1,"606":1,"638":3,"641":1,"643":2,"704":1,"706":3,"709":1,"714":1,"784":1,"814":1,"818":3,"852":1,"915":2,"948":1,"995":1,"1031":1,"1057":1,"1102":1,"1193":1,"1240":1,"1249":1,"1341":1,"1370":1,"1378":1,"1386":2,"1390":1,"1392":1,"1408":1,"1416":2,"1460":3,"1471":1,"1475":3,"1477":4,"1481":1,"1540":1,"1544":2,"1546":1,"1554":1,"1562":3,"1567":1,"1576":5,"1581":3,"1609":1,"1651":1,"1658":1,"1686":1,"1725":1,"1732":3,"1733":1,"1792":44,"1838":6,"1841":3,"1862":3,"1889":1,"1898":1,"1918":1,"1921":1,"1922":2,"1967":3,"1968":1,"1974":1,"2009":1,"2038":1,"2040":1,"2102":2,"2111":1,"2124":1,"2127":1,"2167":1,"2197":1,"2206":1,"2221":1,"2225":1,"2264":2,"2277":1,"2279":1,"2297":1,"2321":1,"2324":1,"2326":2,"2329":1,"2332":1,"2334":1,"2340":1,"2359":1,"2372":3,"2375":3,"2378":1,"2380":2,"2385":1,"2411":1,"2413":1,"2417":1,"2422":1,"2431":2,"2438":1,"2442":1,"2444":1,"2445":2,"2446":2,"2476":2,"2483":2,"2493":1,"2518":1,"2530":1,"2532":2,"2533":2,"2537":1,"2549":1,"2575":3,"2607":1,"2608":1,"2635":1,"2661":1,"2695":1,"2723":3,"2724":3,"2763":1,"2769":1,"2795":1,"2810":1,"2814":1,"2842":1,"2846":2,"2865":1,"2870":1,"2871":1,"2880":2}}],["n",{"2":{"128":2,"129":1,"297":2,"340":2,"342":1,"343":1,"489":1,"490":1,"491":1,"493":1,"599":1,"860":1,"871":1,"881":1,"885":1,"996":1,"1108":6,"1138":1,"1189":2,"1305":4,"1333":4,"1373":1,"1381":1,"1431":2,"1792":3,"2093":1,"2107":1,"2109":2,"2110":1,"2140":2,"2206":1,"2459":1,"2464":2,"2504":1,"2530":3,"2531":1,"2537":2,"2540":1,"2543":1,"2575":2,"2589":1,"2614":1,"2726":1,"2845":1}}],["neighboring",{"2":{"2534":1}}],["neither",{"2":{"301":1,"436":1,"529":1,"838":1,"841":1,"1095":1,"1107":1,"1571":1,"1792":2,"1823":1,"2155":1,"2435":1,"2465":1,"2481":1,"2482":2,"2484":1,"2490":1,"2518":1,"2541":1}}],["neutral",{"2":{"2223":1,"2479":1,"2481":1,"2482":1}}],["neat",{"2":{"1393":1,"1398":1,"1403":1}}],["nearly",{"2":{"869":1,"884":1,"994":1,"1090":1,"1266":1,"1269":1,"1272":1}}],["near",{"2":{"868":1,"2397":1,"2533":1,"2534":1,"2537":1}}],["negligible",{"2":{"1974":1,"2607":1}}],["negotiating",{"2":{"871":1}}],["negative",{"2":{"87":2,"865":1,"1910":1,"2433":1}}],["necessary",{"2":{"869":1,"922":1,"1386":1,"1708":1,"2840":1}}],["nest",{"2":{"917":1,"1097":1,"1285":2,"2531":1,"2869":1}}],["nesting",{"2":{"334":1,"337":1,"919":4,"920":1,"1097":3,"2369":1,"2611":2}}],["nestedjsonforcompositetypes",{"0":{"2010":1},"2":{"336":2,"338":2,"917":2,"1792":2,"1966":1,"1967":1,"1973":5,"1975":1,"1999":1,"2000":1,"2010":2,"2325":1,"2330":2,"2369":3,"2587":3,"2611":2,"2641":2,"2642":1}}],["nested",{"0":{"328":1,"330":1,"334":1,"406":1,"912":1,"917":1,"918":1,"1097":1,"1296":1,"1375":1,"1973":1,"1974":1,"2217":1,"2587":1,"2607":1,"2611":1},"1":{"329":1,"330":1,"331":1,"332":1,"333":1,"334":1,"335":1,"336":1,"337":1,"338":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":2,"920":2,"1297":1},"2":{"227":2,"328":3,"329":1,"330":2,"332":3,"333":1,"334":3,"335":3,"336":2,"337":1,"338":1,"860":1,"912":1,"917":3,"918":5,"919":3,"1037":1,"1077":1,"1097":8,"1127":1,"1133":2,"1255":5,"1258":2,"1285":2,"1297":1,"1375":3,"1606":1,"1792":5,"1967":2,"1973":2,"1974":6,"2000":2,"2010":4,"2092":1,"2154":1,"2164":1,"2165":1,"2184":1,"2217":4,"2236":2,"2325":2,"2329":2,"2330":2,"2356":1,"2398":1,"2586":4,"2587":9,"2588":3,"2589":1,"2590":2,"2600":1,"2603":1,"2607":7,"2611":4,"2614":1,"2641":3,"2689":1,"2701":1,"2725":1,"2856":1}}],["nextresultasync",{"2":{"2372":1}}],["nextval",{"2":{"695":1,"1079":1,"2867":1,"2873":1}}],["next",{"0":{"1383":1,"1466":1,"1485":1,"1496":1,"1507":1,"1536":1,"1550":1,"1584":1,"1601":1,"1611":1,"1635":1,"1648":1,"1666":1,"1680":1,"1700":1,"1719":1,"1749":1,"1761":1,"1784":1,"1812":1,"1865":1,"1895":1,"1914":1,"1933":1,"1946":1,"1963":1,"1977":1,"1997":1,"2031":1,"2044":1,"2071":1,"2082":1,"2091":1,"2121":1,"2135":1,"2151":1,"2169":1,"2219":1,"2706":1,"2793":1,"2826":1},"2":{"214":1,"560":1,"581":1,"614":1,"615":1,"619":1,"685":1,"760":1,"761":2,"770":1,"771":2,"831":1,"843":1,"852":1,"857":1,"860":1,"864":1,"868":1,"872":1,"873":1,"875":1,"883":1,"885":1,"914":1,"982":1,"1070":2,"1074":1,"1080":1,"1101":1,"1158":1,"1173":1,"1183":1,"1203":1,"1208":1,"1253":1,"1320":3,"1401":1,"1409":1,"1416":1,"1421":1,"1422":3,"1438":1,"1574":1,"1792":1,"1851":1,"2095":1,"2157":1,"2167":1,"2320":1,"2339":1,"2340":1,"2382":1,"2428":1,"2438":1,"2502":1,"2537":1,"2543":1,"2821":1,"2823":1,"2852":1,"2861":1,"2878":1}}],["net9",{"2":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["net10",{"2":{"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"2258":1}}],["networks",{"0":{"1708":1},"2":{"1100":1,"1792":1,"2633":1}}],["network",{"0":{"1014":1},"2":{"213":2,"421":1,"445":1,"861":1,"918":1,"993":1,"1014":2,"1015":1,"1032":1,"1067":1,"1070":1,"1078":1,"1137":1,"1204":3,"1208":1,"1324":1,"1439":1,"1516":1,"1703":1,"1708":2,"1712":1,"1715":1,"1739":1,"1740":1,"1741":2,"1792":1,"1852":1,"1929":1,"2157":1,"2265":1,"2287":1,"2288":1,"2289":2,"2346":1,"2347":1,"2383":2,"2398":2,"2527":1,"2543":1,"2633":1,"2811":1,"2860":1}}],["net",{"0":{"869":1,"2246":1,"2711":1,"2714":1},"2":{"182":1,"188":1,"408":1,"832":1,"868":3,"869":10,"871":2,"873":6,"874":2,"876":2,"954":1,"963":1,"986":1,"1005":1,"1049":3,"1054":1,"1098":1,"1100":1,"1104":1,"1105":1,"1106":1,"1107":2,"1126":1,"1156":1,"1164":1,"1255":7,"1257":8,"1265":2,"1269":1,"1275":1,"1277":3,"1279":1,"1281":2,"1284":3,"1285":3,"1366":1,"1409":1,"1421":1,"1445":1,"1447":2,"1450":2,"1457":2,"1458":1,"1460":1,"1523":1,"1718":1,"1783":1,"1792":8,"1802":2,"1822":1,"2077":2,"2240":1,"2245":2,"2246":1,"2257":1,"2258":1,"2291":1,"2296":1,"2324":1,"2375":2,"2380":1,"2397":1,"2419":1,"2421":1,"2422":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2435":1,"2436":2,"2437":1,"2438":1,"2470":1,"2481":1,"2527":1,"2554":2,"2645":1,"2665":1,"2711":1,"2776":2,"2789":1,"2792":2,"2794":1,"2795":2,"2862":1}}],["nevertheless",{"2":{"1386":1}}],["never",{"0":{"1040":1,"2381":1},"1":{"1041":1},"2":{"74":1,"107":1,"177":1,"184":1,"188":1,"212":1,"214":1,"215":1,"298":1,"317":1,"319":1,"320":1,"383":1,"388":1,"390":2,"394":1,"424":1,"438":1,"446":1,"448":1,"453":2,"527":1,"528":1,"582":1,"587":2,"650":1,"664":1,"669":1,"683":1,"710":1,"715":1,"838":1,"840":1,"844":1,"848":1,"849":1,"851":3,"852":4,"863":1,"864":3,"865":1,"872":1,"936":1,"937":1,"942":1,"949":2,"989":1,"1033":1,"1037":1,"1042":1,"1043":1,"1046":1,"1067":1,"1074":1,"1075":2,"1078":2,"1079":4,"1105":1,"1137":1,"1138":1,"1195":1,"1210":1,"1305":1,"1331":2,"1363":1,"1382":1,"1403":1,"1409":1,"1419":2,"1420":1,"1424":1,"1437":1,"1441":2,"1532":1,"1664":1,"1738":2,"1741":1,"1743":1,"1792":8,"2018":1,"2019":1,"2038":1,"2040":2,"2094":1,"2095":1,"2107":1,"2111":1,"2156":2,"2183":1,"2282":1,"2283":2,"2289":1,"2292":1,"2296":1,"2307":1,"2320":1,"2337":2,"2362":1,"2381":7,"2384":1,"2391":3,"2459":1,"2463":1,"2465":1,"2466":3,"2477":3,"2481":3,"2492":1,"2493":1,"2502":1,"2505":1,"2506":1,"2518":1,"2527":4,"2532":1,"2533":1,"2534":1,"2535":1,"2537":1,"2538":2,"2540":2,"2542":2,"2546":1,"2632":1,"2712":2,"2741":1,"2768":2,"2795":1,"2807":1,"2809":3,"2813":1,"2815":1,"2828":1,"2834":2,"2845":2,"2861":1,"2862":4,"2868":5,"2871":2,"2872":2,"2878":1}}],["newest",{"2":{"2297":1}}],["newer",{"2":{"876":1,"2385":2,"2710":1}}],["newdb",{"2":{"2167":1,"2545":1,"2860":1,"2872":1}}],["newguid",{"2":{"1366":1}}],["newstock",{"2":{"1045":1}}],["newuser",{"2":{"994":2}}],["newly",{"2":{"989":1}}],["newlines",{"2":{"1360":1,"2589":1}}],["newline",{"2":{"340":2,"599":1,"1189":1,"1792":1,"1800":1,"1809":2,"1810":1}}],["neward",{"2":{"840":2,"857":1}}],["newname",{"2":{"257":1,"2277":1}}],["new",{"0":{"339":1,"917":1,"918":1,"1220":1,"1256":1,"1258":1,"1285":1,"1449":1,"1870":1,"1908":1,"2251":1,"2282":1,"2287":1,"2291":1,"2300":1,"2317":1,"2331":1,"2332":1,"2337":1,"2338":1,"2339":1,"2344":1,"2365":1,"2375":1,"2377":1,"2379":1,"2380":1,"2382":1,"2383":1,"2390":1,"2391":1,"2471":1,"2480":1,"2481":1,"2501":1,"2625":1,"2632":1,"2633":1,"2634":1,"2635":1,"2649":1,"2650":1,"2659":1},"1":{"340":1,"341":1,"342":1,"343":1,"344":1,"345":1,"346":1,"919":1,"920":1,"1257":1,"1258":1,"1259":1,"1260":1,"1909":1,"1910":1,"1911":1,"2283":1,"2284":1,"2285":1,"2286":1,"2288":1,"2289":1,"2290":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2381":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2481":1,"2482":1,"2483":1,"2484":1,"2502":1,"2651":1,"2652":1,"2653":1},"2":{"74":1,"128":2,"129":1,"131":1,"159":3,"229":1,"257":4,"317":1,"332":3,"339":1,"340":1,"342":1,"343":1,"344":1,"347":1,"370":14,"390":1,"408":1,"469":1,"489":1,"490":1,"491":1,"493":1,"494":1,"496":1,"531":1,"592":2,"606":1,"663":1,"666":1,"843":1,"852":1,"860":1,"865":1,"871":2,"872":1,"874":1,"876":1,"885":3,"888":2,"894":4,"912":1,"915":1,"961":1,"973":1,"977":1,"980":1,"984":1,"985":2,"990":6,"994":1,"996":1,"997":2,"1026":2,"1063":1,"1067":1,"1069":1,"1073":1,"1107":1,"1139":1,"1165":2,"1166":1,"1175":1,"1189":2,"1192":1,"1193":1,"1203":3,"1214":1,"1216":2,"1220":1,"1224":1,"1226":2,"1232":1,"1233":2,"1238":2,"1239":3,"1254":2,"1255":5,"1257":1,"1258":2,"1259":1,"1277":1,"1317":1,"1320":2,"1335":2,"1338":1,"1366":9,"1373":1,"1377":1,"1382":1,"1384":1,"1386":1,"1401":2,"1402":1,"1404":3,"1406":1,"1409":2,"1410":4,"1416":1,"1419":3,"1422":1,"1438":1,"1453":1,"1456":1,"1458":1,"1464":1,"1569":1,"1573":1,"1609":1,"1701":1,"1722":1,"1759":1,"1762":1,"1792":15,"1804":1,"1813":1,"1850":1,"1856":1,"1866":1,"1870":1,"1874":1,"1876":2,"1883":1,"1888":1,"1912":1,"1925":1,"1955":1,"1958":1,"1973":3,"2010":2,"2014":1,"2045":1,"2056":1,"2094":1,"2100":1,"2106":1,"2107":1,"2148":6,"2157":1,"2160":1,"2165":1,"2167":1,"2206":1,"2216":1,"2221":6,"2222":3,"2223":4,"2224":3,"2225":2,"2226":1,"2228":2,"2245":1,"2247":2,"2251":1,"2252":2,"2254":3,"2255":13,"2256":1,"2259":1,"2264":1,"2265":2,"2266":1,"2272":1,"2273":1,"2277":5,"2279":2,"2287":1,"2291":1,"2297":1,"2300":1,"2314":3,"2323":1,"2329":1,"2332":1,"2337":1,"2338":1,"2339":1,"2354":1,"2359":1,"2365":1,"2370":1,"2372":4,"2376":2,"2378":1,"2383":1,"2389":1,"2391":1,"2392":1,"2393":1,"2406":2,"2407":4,"2409":1,"2417":1,"2419":1,"2425":1,"2429":1,"2435":2,"2436":2,"2438":2,"2447":1,"2448":3,"2455":2,"2456":1,"2457":1,"2461":2,"2466":1,"2472":1,"2476":1,"2477":1,"2481":1,"2482":3,"2484":1,"2489":1,"2490":1,"2498":1,"2502":1,"2515":1,"2517":1,"2520":1,"2521":1,"2537":3,"2538":1,"2539":1,"2540":1,"2543":1,"2545":1,"2546":1,"2550":1,"2554":2,"2565":1,"2572":1,"2575":6,"2576":2,"2581":1,"2587":3,"2614":1,"2615":1,"2621":1,"2629":1,"2634":1,"2654":1,"2659":1,"2667":1,"2714":1,"2726":2,"2829":2,"2836":2,"2857":1,"2879":1}}],["needing",{"2":{"2165":1,"2381":1}}],["needsescape",{"2":{"2621":1}}],["needs",{"2":{"453":1,"454":1,"683":1,"835":1,"836":1,"841":1,"851":1,"854":1,"864":1,"865":1,"873":1,"916":1,"926":1,"932":1,"956":1,"1036":1,"1038":1,"1049":1,"1059":1,"1065":1,"1070":1,"1132":1,"1179":1,"1205":1,"1281":1,"1382":1,"1388":1,"1390":2,"1394":1,"1402":2,"1405":1,"1420":1,"1519":1,"1738":1,"1792":4,"1825":1,"1922":1,"2020":1,"2155":1,"2157":1,"2438":1,"2534":1,"2540":1,"2541":1,"2543":1,"2632":1,"2721":1,"2722":1,"2733":1,"2776":1,"2868":3,"2873":1}}],["needed",{"2":{"169":1,"177":1,"304":1,"309":1,"363":1,"683":2,"686":1,"868":1,"869":1,"911":1,"914":1,"916":1,"920":1,"946":1,"953":1,"968":1,"971":1,"994":1,"1036":1,"1049":1,"1052":1,"1063":1,"1065":1,"1075":1,"1086":2,"1097":1,"1098":1,"1103":1,"1121":1,"1125":1,"1126":1,"1165":1,"1177":1,"1206":3,"1208":1,"1247":1,"1304":1,"1327":1,"1328":2,"1329":1,"1351":1,"1353":1,"1367":1,"1385":1,"1408":1,"1419":1,"1690":1,"1792":2,"1859":1,"2020":1,"2176":1,"2256":1,"2282":1,"2317":1,"2353":1,"2379":1,"2437":1,"2534":1,"2538":1,"2731":1,"2845":2,"2867":1,"2871":1}}],["need",{"0":{"1205":1,"2741":1},"2":{"73":1,"101":1,"165":1,"168":1,"175":1,"177":2,"209":1,"215":1,"297":1,"308":1,"316":1,"373":1,"376":1,"448":1,"624":1,"656":1,"696":1,"826":1,"836":1,"837":2,"838":2,"840":2,"845":1,"848":2,"851":2,"873":1,"879":1,"884":1,"902":1,"904":1,"911":1,"918":1,"949":1,"960":1,"971":1,"974":1,"993":1,"994":1,"1026":1,"1050":1,"1063":1,"1065":2,"1073":1,"1074":1,"1077":1,"1079":1,"1081":1,"1096":1,"1098":1,"1099":1,"1106":1,"1121":5,"1122":2,"1123":1,"1125":1,"1127":2,"1134":1,"1135":1,"1139":1,"1140":1,"1141":1,"1142":1,"1191":1,"1205":2,"1206":1,"1220":2,"1252":1,"1320":1,"1323":1,"1326":1,"1327":1,"1328":1,"1335":1,"1337":1,"1351":1,"1353":1,"1354":3,"1366":1,"1377":1,"1378":1,"1385":2,"1386":1,"1388":3,"1389":2,"1390":3,"1395":1,"1396":2,"1398":1,"1401":6,"1403":2,"1405":1,"1410":1,"1411":3,"1415":1,"1417":1,"1419":2,"1431":1,"1432":2,"1441":1,"1515":2,"1518":1,"1572":1,"1582":1,"1690":3,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1792":5,"1825":1,"2098":2,"2177":1,"2265":1,"2338":1,"2351":1,"2380":1,"2381":1,"2389":1,"2424":1,"2438":2,"2534":1,"2540":1,"2550":1,"2551":1,"2634":1,"2642":1,"2751":1,"2763":1,"2772":1,"2791":1,"2809":1,"2810":1,"2821":1,"2853":1,"2855":1,"2866":1,"2868":1,"2873":1}}],["nuget",{"0":{"2386":1},"2":{"869":2,"1071":1,"1096":1,"2254":1,"2389":1,"2711":1,"2714":2,"2716":1,"2729":1}}],["numbering",{"2":{"699":1,"2530":1,"2531":1,"2869":1}}],["numbered",{"2":{"625":1,"848":1}}],["number>>",{"2":{"1026":1}}],["number>",{"2":{"268":2,"1026":1}}],["numbers",{"0":{"911":1,"1027":1,"1322":1,"1349":1},"1":{"1350":1},"2":{"133":1,"866":1,"869":1,"872":1,"877":1,"952":2,"963":1,"1037":2,"1276":1,"1279":1,"1280":1,"1383":1,"1421":1,"1431":1,"1664":1,"1792":2,"1824":1,"1924":1,"2212":1,"2291":1,"2342":1,"2398":1,"2509":1,"2576":1,"2662":1,"2744":1}}],["number",{"2":{"78":1,"81":1,"119":1,"213":1,"214":1,"268":2,"280":1,"378":1,"426":1,"575":1,"594":1,"618":1,"625":1,"772":1,"773":1,"817":1,"849":1,"855":1,"867":1,"871":2,"873":1,"883":2,"893":1,"894":6,"916":1,"920":23,"929":1,"938":4,"948":1,"952":3,"995":7,"996":1,"1024":1,"1032":1,"1040":1,"1133":2,"1164":1,"1165":1,"1193":1,"1222":1,"1317":2,"1342":4,"1366":6,"1382":1,"1386":3,"1408":3,"1410":2,"1416":1,"1431":2,"1511":1,"1553":1,"1558":1,"1567":3,"1569":1,"1571":3,"1575":1,"1623":1,"1651":1,"1703":1,"1722":1,"1740":1,"1743":1,"1792":17,"1804":1,"1947":1,"1952":1,"1953":1,"1954":1,"1974":2,"1991":3,"2086":4,"2099":1,"2101":1,"2141":2,"2144":1,"2146":1,"2148":1,"2156":1,"2247":3,"2257":1,"2265":1,"2273":2,"2288":1,"2333":1,"2357":4,"2359":4,"2360":1,"2446":1,"2464":2,"2502":1,"2531":1,"2537":1,"2575":1,"2590":8,"2607":2,"2611":5,"2633":1,"2765":1,"2848":1}}],["numeric",{"0":{"963":1},"2":{"9":1,"263":2,"301":2,"333":2,"585":1,"594":2,"677":1,"814":2,"885":1,"898":1,"916":1,"952":1,"956":5,"1038":1,"1040":1,"1187":2,"1192":2,"1193":2,"1203":1,"1255":1,"1336":2,"1339":2,"1374":4,"1427":2,"1429":2,"1431":1,"1436":1,"1471":1,"1792":4,"1930":1,"2076":1,"2077":1,"2078":1,"2079":1,"2214":1,"2258":1,"2344":2,"2397":1,"2456":1,"2540":1,"2588":1,"2621":1,"2652":1,"2762":3,"2802":1,"2815":2,"2845":1}}],["nullability",{"2":{"1408":1}}],["nullable",{"0":{"2495":1},"2":{"466":5,"845":1,"995":2,"1040":1,"1050":1,"1222":1,"1355":1,"1818":1,"1820":1,"1821":1,"1822":1,"1830":1,"1831":1,"2481":1,"2495":1}}],["nullif",{"2":{"1057":1,"1058":5,"2184":5}}],["nulls",{"2":{"816":1,"1079":1,"2149":1,"2869":1}}],["nullliteral",{"0":{"464":1},"2":{"470":1,"556":1,"1854":1,"1855":1,"2595":1,"2596":1}}],["null",{"0":{"458":1,"466":1,"467":2,"549":1,"553":1,"554":1,"555":1,"1134":3,"1853":1,"2594":1},"1":{"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"550":1,"551":1,"552":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"1854":1,"1855":1,"2595":1,"2596":1},"2":{"31":1,"35":1,"40":2,"60":1,"61":1,"62":1,"101":1,"106":3,"140":2,"188":4,"212":1,"226":2,"227":2,"286":1,"291":2,"313":2,"337":2,"378":8,"379":1,"380":4,"388":2,"395":1,"408":9,"439":6,"446":1,"447":2,"448":1,"449":3,"452":3,"454":2,"458":6,"459":2,"460":6,"462":5,"463":4,"464":8,"466":3,"467":3,"468":3,"469":9,"471":1,"472":2,"503":1,"510":1,"511":1,"512":1,"526":2,"529":3,"544":1,"549":4,"550":3,"551":2,"553":2,"554":2,"555":4,"557":1,"558":2,"614":1,"616":4,"617":1,"679":2,"723":5,"747":6,"750":1,"751":1,"755":1,"756":1,"761":2,"764":4,"768":2,"771":3,"774":4,"776":2,"777":1,"786":3,"798":1,"801":1,"811":2,"812":2,"814":1,"816":3,"819":1,"826":1,"845":1,"852":1,"864":1,"872":1,"885":1,"886":1,"891":2,"892":3,"894":2,"896":1,"897":2,"904":2,"913":4,"914":1,"916":4,"918":1,"920":50,"924":3,"936":3,"938":7,"956":10,"961":3,"977":5,"995":12,"996":7,"1023":2,"1024":10,"1026":10,"1033":1,"1038":4,"1040":1,"1050":7,"1054":2,"1067":3,"1079":1,"1105":3,"1134":6,"1139":1,"1150":3,"1157":1,"1188":1,"1213":13,"1214":3,"1215":1,"1216":1,"1224":4,"1225":4,"1226":1,"1232":2,"1234":7,"1235":1,"1236":5,"1239":2,"1241":1,"1307":6,"1309":2,"1318":3,"1321":2,"1332":5,"1336":2,"1338":5,"1339":7,"1342":4,"1347":2,"1348":1,"1355":5,"1357":2,"1360":1,"1366":5,"1371":4,"1372":2,"1373":1,"1374":8,"1375":2,"1386":5,"1393":2,"1394":1,"1395":1,"1398":2,"1408":10,"1410":3,"1413":3,"1419":2,"1427":2,"1431":2,"1446":6,"1447":13,"1450":1,"1451":3,"1454":7,"1459":1,"1464":1,"1469":8,"1470":2,"1472":2,"1475":3,"1477":2,"1479":4,"1482":2,"1488":1,"1489":2,"1498":2,"1499":3,"1501":1,"1510":3,"1511":5,"1520":1,"1521":1,"1523":3,"1524":2,"1525":1,"1526":2,"1529":2,"1539":1,"1540":1,"1553":6,"1554":2,"1555":1,"1558":1,"1560":2,"1564":1,"1567":11,"1569":3,"1570":6,"1571":5,"1603":1,"1604":1,"1617":2,"1618":4,"1620":2,"1630":1,"1631":2,"1633":1,"1650":5,"1651":9,"1655":2,"1656":1,"1657":1,"1658":1,"1663":1,"1669":12,"1671":3,"1672":2,"1673":10,"1678":13,"1684":1,"1689":1,"1691":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1703":1,"1706":1,"1738":1,"1752":1,"1753":2,"1763":1,"1764":3,"1769":2,"1792":229,"1806":1,"1814":6,"1818":2,"1819":1,"1820":2,"1821":2,"1822":2,"1824":1,"1830":2,"1831":2,"1836":13,"1837":4,"1838":8,"1846":2,"1847":2,"1848":2,"1853":2,"1854":5,"1855":3,"1863":1,"1864":4,"1874":4,"1875":4,"1876":1,"1884":1,"1885":1,"1890":1,"1893":1,"1897":4,"1898":7,"1916":1,"1917":1,"1921":6,"1924":6,"1926":2,"1948":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1966":3,"1967":6,"1975":1,"1991":2,"2010":2,"2015":5,"2016":8,"2038":2,"2039":1,"2040":2,"2046":3,"2047":5,"2060":1,"2062":1,"2073":4,"2075":4,"2077":9,"2080":2,"2085":4,"2086":8,"2093":3,"2094":4,"2107":1,"2109":2,"2116":1,"2117":1,"2123":3,"2125":5,"2128":1,"2130":3,"2132":1,"2138":2,"2140":3,"2142":2,"2146":2,"2149":1,"2183":3,"2184":1,"2187":2,"2223":1,"2253":2,"2254":5,"2255":17,"2256":6,"2257":3,"2258":2,"2265":5,"2267":2,"2271":2,"2273":2,"2283":1,"2284":2,"2296":4,"2297":1,"2309":1,"2333":13,"2335":2,"2338":1,"2339":3,"2359":2,"2375":1,"2377":1,"2380":7,"2400":1,"2426":3,"2428":1,"2431":2,"2436":4,"2461":1,"2476":2,"2481":1,"2495":4,"2496":1,"2498":2,"2528":1,"2530":2,"2537":6,"2540":1,"2544":1,"2545":1,"2549":15,"2551":1,"2558":1,"2565":3,"2572":1,"2575":6,"2580":2,"2586":2,"2588":1,"2590":12,"2595":6,"2596":4,"2607":7,"2611":11,"2632":13,"2633":1,"2634":3,"2635":8,"2648":1,"2665":9,"2701":10,"2702":1,"2723":1,"2732":1,"2762":1,"2763":1,"2764":2,"2803":4,"2810":6,"2812":2,"2814":1,"2815":5,"2829":4,"2836":6,"2845":1,"2848":2,"2864":1,"2868":2,"2869":5}}],["noise",{"2":{"2342":1,"2364":1,"2366":1,"2397":2,"2629":1,"2869":1}}],["noinherit",{"2":{"926":1,"1435":1}}],["nosniff",{"2":{"1792":2,"2015":1,"2016":1,"2017":2,"2027":1,"2028":1,"2029":1,"2632":3}}],["nosuperuser",{"2":{"926":1,"1435":1}}],["nocompression",{"2":{"1792":1,"1937":1,"1938":1}}],["nocontent",{"2":{"556":2,"1855":1,"2596":2}}],["nocreaterole",{"2":{"926":1,"1435":1}}],["nocreatedb",{"2":{"926":1,"1435":1}}],["noah",{"2":{"913":1}}],["novel",{"2":{"913":1}}],["nodes",{"2":{"1046":1,"2481":1}}],["node",{"0":{"1026":1},"2":{"848":1,"866":1,"867":1,"868":1,"1025":1,"1027":1,"1064":1,"1106":1,"1255":1,"1320":1,"1350":1,"1429":4}}],["nobody",{"2":{"843":1,"860":2,"1435":1,"2391":1,"2869":1}}],["noreplication",{"2":{"926":1}}],["normalizes",{"2":{"2590":1}}],["normalize",{"2":{"2528":1,"2861":3,"2864":1}}],["normalizedprice",{"2":{"1429":1}}],["normalized",{"2":{"888":1,"1385":1,"1429":1,"2530":1,"2861":1}}],["normal",{"0":{"1068":1},"2":{"436":1,"448":1,"915":1,"1051":1,"1066":1,"1068":2,"1098":1,"1121":1,"1134":1,"1171":1,"1180":1,"1394":1,"1458":1,"1792":1,"2092":1,"2157":1,"2171":1,"2176":1,"2309":1,"2363":1,"2375":1,"2414":1,"2415":1,"2416":1,"2527":1,"2535":1,"2543":1,"2545":1,"2862":1}}],["normally",{"2":{"261":1,"829":1,"1171":1,"1329":1,"1382":1,"1722":1,"1792":1,"1925":1,"2410":1,"2414":1,"2502":1,"2829":1,"2872":1}}],["nor",{"2":{"301":1,"436":1,"529":1,"841":1,"851":1,"1095":1,"1792":2,"1823":1,"2482":1,"2518":1}}],["nowhere",{"2":{"865":1,"2510":1}}],["now",{"0":{"2314":1,"2378":1,"2402":1,"2405":1,"2414":1,"2420":1,"2446":1,"2451":1,"2491":1},"1":{"2415":1,"2416":1,"2421":1,"2422":1,"2423":1,"2424":1},"2":{"106":1,"310":2,"427":1,"531":1,"566":2,"576":1,"840":1,"841":1,"844":3,"845":1,"848":1,"849":1,"851":2,"852":1,"855":1,"857":1,"860":1,"864":2,"865":1,"888":1,"898":1,"904":1,"912":1,"913":1,"914":1,"915":1,"916":4,"917":3,"918":4,"948":1,"977":1,"986":1,"990":2,"992":1,"994":1,"1056":1,"1059":1,"1060":1,"1067":2,"1068":2,"1069":1,"1071":3,"1073":2,"1074":1,"1080":1,"1097":1,"1101":1,"1150":2,"1157":1,"1193":1,"1209":1,"1213":3,"1214":1,"1216":1,"1232":1,"1234":1,"1235":1,"1239":1,"1254":3,"1259":1,"1262":1,"1266":1,"1272":2,"1280":1,"1307":1,"1336":2,"1338":1,"1339":2,"1355":1,"1382":1,"1384":2,"1385":1,"1386":6,"1392":1,"1394":1,"1397":1,"1398":2,"1399":2,"1400":3,"1401":3,"1402":3,"1405":1,"1517":1,"1519":1,"1524":1,"1571":1,"1574":1,"1595":1,"1624":1,"1644":1,"1792":3,"1924":2,"1948":1,"1955":1,"1958":1,"2222":4,"2223":4,"2224":4,"2225":1,"2229":1,"2247":2,"2250":1,"2253":2,"2258":1,"2259":1,"2265":2,"2267":3,"2271":2,"2273":1,"2310":1,"2313":2,"2314":2,"2317":1,"2333":1,"2334":1,"2335":1,"2336":1,"2342":1,"2346":1,"2347":2,"2348":2,"2350":1,"2351":1,"2352":1,"2353":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":2,"2362":2,"2364":1,"2366":1,"2367":1,"2369":1,"2370":1,"2371":1,"2372":1,"2378":1,"2379":1,"2384":1,"2385":1,"2391":1,"2392":1,"2393":2,"2394":1,"2395":1,"2397":1,"2399":1,"2402":1,"2403":1,"2404":1,"2405":2,"2412":1,"2413":1,"2415":1,"2419":1,"2422":2,"2443":1,"2445":1,"2446":2,"2451":1,"2453":1,"2454":2,"2456":1,"2462":1,"2464":1,"2470":2,"2471":1,"2481":1,"2482":2,"2483":1,"2486":6,"2487":1,"2489":2,"2491":1,"2492":2,"2493":2,"2495":2,"2496":2,"2500":1,"2502":1,"2505":2,"2509":1,"2510":1,"2511":2,"2517":1,"2518":3,"2519":1,"2521":1,"2532":1,"2540":1,"2544":1,"2551":1,"2555":2,"2558":2,"2566":1,"2572":2,"2577":1,"2580":1,"2586":1,"2588":1,"2589":1,"2590":1,"2591":2,"2597":1,"2603":1,"2615":2,"2621":2,"2622":1,"2641":2,"2645":1,"2648":1,"2662":2,"2663":1,"2664":1,"2665":1,"2673":1,"2674":1,"2677":1,"2679":2,"2802":1,"2823":1,"2826":1,"2836":1}}],["nonclosingmemorystream",{"2":{"2372":1}}],["nonsense",{"2":{"1403":2,"2540":1}}],["nonewlines",{"2":{"1360":1}}],["none",{"2":{"448":1,"747":1,"840":1,"844":1,"857":2,"869":1,"872":1,"969":1,"1027":1,"1067":1,"1108":1,"1217":1,"1227":1,"1230":1,"1322":2,"1447":4,"1449":1,"1556":1,"1557":1,"1650":1,"1651":2,"1660":1,"1753":1,"1755":1,"1792":17,"1801":1,"1877":1,"1880":1,"1893":1,"2023":1,"2024":1,"2028":2,"2125":1,"2221":1,"2297":1,"2319":1,"2425":1,"2426":3,"2427":1,"2428":1,"2429":1,"2436":4,"2438":1,"2451":1,"2540":1,"2544":2,"2565":2,"2632":2,"2711":1,"2752":1,"2801":1}}],["non",{"0":{"2394":1},"2":{"35":1,"64":1,"188":1,"213":1,"214":1,"245":1,"298":2,"320":1,"448":1,"624":2,"694":1,"695":1,"716":1,"747":2,"753":2,"757":2,"770":1,"771":1,"772":1,"773":1,"776":1,"782":3,"784":3,"786":3,"930":1,"992":1,"1069":1,"1070":1,"1079":2,"1107":1,"1109":1,"1162":1,"1174":1,"1447":1,"1605":1,"1618":1,"1740":1,"1741":2,"1743":1,"1792":14,"1856":1,"1862":1,"1898":1,"1924":1,"1956":1,"1957":1,"2003":1,"2099":1,"2106":1,"2125":1,"2156":1,"2197":1,"2221":1,"2226":1,"2256":2,"2270":1,"2288":1,"2289":2,"2296":1,"2309":1,"2342":1,"2379":2,"2406":1,"2429":1,"2450":2,"2454":1,"2456":1,"2481":1,"2482":1,"2483":1,"2497":1,"2498":1,"2502":1,"2512":1,"2527":1,"2529":1,"2531":1,"2532":1,"2537":1,"2542":1,"2614":1,"2687":1,"2688":1,"2862":1,"2867":1}}],["no",{"0":{"275":1,"308":1,"320":1,"468":1,"888":2,"942":1,"1004":1,"1042":1,"1106":1,"1200":1,"1248":1,"1388":1,"1389":1,"1390":1,"1417":1,"1420":1,"1580":1,"1660":1,"2343":1,"2348":1,"2352":1,"2384":1,"2492":1,"2494":1,"2517":1},"1":{"1418":1,"1419":1,"1420":1,"1421":1},"2":{"2":2,"31":1,"35":2,"60":2,"63":2,"74":1,"106":1,"109":1,"110":1,"167":1,"169":1,"175":1,"179":1,"182":1,"188":1,"214":3,"227":1,"286":1,"297":2,"299":2,"301":1,"304":2,"317":1,"319":1,"320":6,"325":2,"327":1,"347":1,"370":1,"376":1,"378":1,"388":1,"389":2,"390":1,"422":1,"436":1,"438":1,"446":4,"452":2,"453":3,"458":1,"460":1,"462":1,"487":1,"529":1,"540":1,"543":1,"544":1,"551":3,"554":2,"565":1,"567":1,"582":2,"583":1,"586":2,"587":3,"615":1,"616":3,"618":1,"646":1,"650":2,"663":2,"664":1,"665":1,"683":1,"684":2,"686":2,"690":1,"701":1,"703":1,"713":1,"745":1,"761":1,"771":1,"823":1,"826":1,"829":4,"832":3,"833":2,"835":7,"837":1,"841":3,"843":3,"844":2,"845":1,"847":1,"848":7,"849":5,"851":12,"852":4,"861":4,"864":5,"866":3,"867":5,"868":4,"871":7,"872":5,"873":3,"874":4,"875":2,"876":3,"880":4,"888":1,"894":1,"901":1,"904":1,"907":3,"908":1,"909":1,"911":4,"916":1,"920":2,"922":1,"926":4,"941":2,"944":1,"946":1,"949":2,"954":3,"958":3,"961":1,"968":1,"971":4,"973":1,"974":1,"975":2,"991":1,"993":3,"994":3,"997":1,"1000":1,"1006":6,"1007":3,"1009":2,"1010":4,"1014":1,"1015":1,"1036":6,"1037":9,"1038":1,"1042":2,"1045":1,"1046":2,"1049":2,"1051":1,"1052":1,"1061":1,"1063":1,"1065":2,"1068":1,"1069":2,"1070":4,"1075":1,"1076":4,"1077":1,"1078":6,"1079":1,"1081":1,"1086":3,"1094":1,"1095":2,"1096":2,"1097":1,"1098":3,"1099":1,"1100":4,"1101":3,"1102":5,"1103":1,"1105":3,"1106":4,"1107":2,"1108":4,"1111":5,"1121":1,"1125":1,"1126":2,"1127":2,"1137":2,"1138":2,"1141":1,"1150":3,"1165":1,"1168":1,"1169":1,"1176":1,"1180":1,"1181":5,"1183":1,"1185":2,"1193":1,"1200":3,"1208":4,"1209":3,"1210":3,"1217":1,"1226":4,"1229":1,"1233":1,"1247":1,"1248":2,"1251":2,"1255":2,"1257":4,"1276":3,"1281":3,"1304":2,"1325":4,"1326":1,"1327":1,"1329":1,"1331":2,"1332":1,"1333":1,"1337":2,"1342":1,"1349":1,"1351":1,"1354":1,"1360":1,"1366":4,"1367":1,"1368":3,"1369":1,"1372":2,"1378":7,"1382":1,"1384":2,"1385":5,"1386":1,"1389":2,"1390":1,"1394":4,"1396":2,"1398":1,"1399":1,"1400":1,"1401":12,"1402":2,"1403":2,"1405":2,"1406":3,"1408":3,"1409":1,"1410":1,"1412":1,"1413":1,"1417":1,"1419":3,"1420":1,"1422":3,"1423":2,"1427":1,"1428":1,"1429":1,"1431":2,"1432":1,"1435":5,"1436":1,"1438":3,"1442":4,"1480":1,"1481":1,"1501":1,"1518":1,"1521":1,"1522":3,"1523":1,"1524":1,"1527":1,"1529":1,"1531":1,"1532":1,"1557":1,"1559":1,"1569":1,"1571":2,"1577":1,"1588":1,"1605":1,"1609":1,"1613":1,"1618":1,"1639":1,"1640":1,"1651":1,"1655":1,"1696":4,"1742":1,"1743":2,"1746":3,"1753":1,"1755":1,"1759":1,"1792":40,"1806":1,"1813":1,"1816":1,"1820":1,"1824":1,"1825":3,"1828":1,"1834":1,"1840":1,"1850":1,"1852":3,"1854":1,"1855":2,"1856":2,"1859":2,"1861":2,"1868":1,"1879":1,"1908":2,"1912":2,"1917":1,"1920":1,"1928":2,"1929":1,"1938":1,"1956":1,"1957":2,"1974":2,"2002":1,"2007":1,"2019":2,"2028":1,"2033":3,"2037":3,"2040":2,"2041":3,"2094":1,"2095":1,"2099":1,"2105":1,"2109":1,"2113":1,"2141":2,"2156":2,"2157":2,"2164":1,"2171":2,"2176":1,"2183":1,"2195":1,"2222":2,"2223":4,"2225":2,"2226":1,"2265":2,"2270":1,"2278":1,"2284":1,"2287":1,"2289":4,"2290":1,"2291":1,"2296":1,"2309":1,"2317":1,"2319":1,"2320":1,"2324":2,"2328":1,"2333":1,"2337":3,"2338":3,"2339":3,"2347":3,"2348":1,"2357":1,"2359":1,"2360":2,"2363":1,"2371":1,"2378":1,"2379":3,"2380":3,"2381":3,"2383":4,"2385":1,"2389":5,"2391":3,"2392":4,"2393":1,"2395":1,"2396":1,"2398":4,"2406":1,"2409":2,"2415":1,"2419":3,"2420":1,"2423":4,"2426":1,"2429":2,"2430":2,"2431":4,"2432":1,"2435":1,"2436":2,"2437":1,"2438":2,"2448":3,"2450":2,"2451":2,"2452":1,"2454":3,"2456":1,"2457":2,"2461":2,"2472":1,"2476":1,"2481":8,"2482":1,"2484":1,"2487":1,"2489":2,"2490":1,"2494":1,"2495":3,"2497":1,"2498":1,"2502":3,"2518":1,"2520":2,"2522":1,"2523":3,"2527":4,"2529":3,"2530":1,"2531":2,"2534":1,"2535":2,"2536":1,"2537":4,"2538":1,"2539":1,"2540":1,"2542":2,"2543":2,"2544":2,"2549":2,"2551":3,"2558":1,"2566":1,"2575":2,"2586":1,"2588":1,"2590":1,"2595":1,"2596":2,"2607":2,"2632":2,"2659":1,"2679":1,"2684":1,"2685":1,"2688":1,"2694":1,"2709":2,"2712":1,"2721":1,"2723":1,"2731":1,"2732":1,"2741":2,"2744":2,"2760":2,"2767":1,"2774":3,"2792":1,"2802":2,"2807":4,"2809":2,"2811":5,"2814":1,"2815":1,"2824":1,"2825":1,"2835":6,"2836":2,"2839":4,"2845":1,"2848":1,"2853":3,"2854":1,"2858":1,"2859":1,"2860":3,"2862":1,"2865":2,"2867":1,"2868":4,"2874":2,"2878":1,"2879":1,"2880":1,"2881":3}}],["notable",{"0":{"1071":1},"2":{"1107":1,"1266":1}}],["notation",{"0":{"2377":1},"2":{"851":1,"1071":1,"2376":1}}],["notoriously",{"2":{"1064":1}}],["notnull",{"2":{"816":1,"1788":1,"1792":2,"1795":1,"2138":1,"2140":2,"2141":1,"2142":1,"2146":1,"2446":1,"2575":4}}],["notion",{"2":{"841":1}}],["noting",{"2":{"841":1,"1386":1}}],["notifies",{"2":{"2834":1}}],["notifications",{"2":{"646":2,"1323":2,"1326":2,"1327":1,"1386":2,"1824":1,"2391":1,"2481":1,"2490":1,"2833":1}}],["notification",{"2":{"621":1,"643":1,"644":1,"1035":1,"1313":1,"1324":1}}],["notify",{"0":{"1324":1},"1":{"1325":1},"2":{"621":2,"1030":1,"1037":1,"1103":2,"1106":2,"1324":5,"1325":4,"2342":1,"2343":1,"2803":1,"2833":1}}],["notice`",{"2":{"1792":2}}],["noticed",{"2":{"1401":1}}],["notices",{"2":{"650":2,"668":3,"868":1,"1305":1,"1325":2,"1792":2,"1811":1,"2094":1,"2104":1,"2392":1,"2535":2,"2537":1,"2802":1}}],["notice",{"0":{"632":1,"660":1,"1845":1,"1858":1,"2251":1},"2":{"233":1,"627":1,"629":2,"631":1,"632":2,"646":4,"648":1,"651":1,"653":3,"655":1,"656":4,"660":2,"662":3,"671":1,"852":1,"860":1,"864":1,"865":1,"915":1,"930":1,"992":1,"1056":1,"1103":1,"1305":2,"1309":1,"1316":3,"1325":2,"1327":1,"1386":1,"1408":1,"1416":1,"1442":1,"1792":4,"1844":1,"1857":2,"1858":2,"1859":1,"1860":1,"1861":1,"2104":1,"2108":1,"2251":2,"2252":11,"2392":2,"2535":1,"2536":1,"2802":2,"2828":1,"2832":7,"2833":2,"2835":2,"2838":1,"2880":2}}],["nothing",{"2":{"74":1,"167":1,"298":2,"388":2,"665":1,"847":1,"849":3,"851":3,"857":1,"860":1,"864":1,"932":1,"968":1,"1040":1,"1046":1,"1070":1,"1073":3,"1074":1,"1077":1,"1080":2,"1106":1,"1137":1,"1185":1,"1382":1,"1384":1,"1391":1,"1401":3,"1403":1,"1406":1,"1432":1,"1435":1,"1792":1,"1821":1,"1825":1,"2019":3,"2157":1,"2176":1,"2186":2,"2398":1,"2412":1,"2468":1,"2518":1,"2537":1,"2543":1,"2544":1,"2733":1,"2758":1,"2815":1,"2868":1,"2881":1}}],["notempty",{"2":{"816":1,"1026":3,"1792":2,"2140":2,"2141":1,"2142":1,"2146":1,"2149":1,"2446":1,"2575":4}}],["notes",{"0":{"696":1,"701":1,"2296":1,"2512":1,"2522":1,"2545":1},"2":{"319":1,"320":1,"452":2,"1037":1,"1066":1,"1067":1,"1071":1,"1347":2,"1380":1,"1381":1,"1382":1,"1664":1,"2013":1,"2482":1,"2802":1,"2859":1}}],["note",{"0":{"324":1,"1054":1,"1279":1,"1528":1,"2477":1,"2511":1},"2":{"60":1,"209":1,"324":1,"762":1,"771":1,"772":1,"916":1,"918":1,"926":2,"1096":1,"1098":1,"1142":1,"1225":1,"1382":1,"1477":1,"1618":1,"1792":18,"1875":1,"1924":1,"2220":1,"2252":1,"2319":1,"2455":1,"2461":1,"2551":1,"2571":1,"2586":1,"2611":1,"2621":1,"2632":2,"2634":1,"2635":1,"2800":1,"2815":1}}],["not",{"0":{"395":1,"691":1,"836":1,"854":1,"855":1,"856":1,"857":1,"1012":1,"1170":1,"1324":1,"1395":1,"1924":1,"2365":1,"2378":1,"2410":1,"2441":1,"2490":1,"2491":1,"2734":1},"1":{"1013":1,"1014":1,"1015":1,"1325":1,"2411":1,"2412":1,"2413":1,"2442":1,"2443":1,"2444":1},"2":{"1":1,"31":1,"46":1,"63":1,"64":1,"109":1,"119":1,"134":1,"165":2,"173":1,"174":2,"175":1,"188":3,"209":1,"223":1,"238":1,"239":1,"244":2,"245":2,"252":1,"261":1,"263":2,"280":1,"300":1,"308":1,"309":1,"317":1,"324":1,"347":1,"348":1,"370":1,"377":1,"383":1,"387":2,"388":2,"389":1,"390":1,"423":1,"429":1,"436":2,"439":2,"446":1,"448":2,"449":1,"452":2,"454":1,"462":2,"480":1,"518":1,"524":1,"527":1,"534":1,"535":1,"567":1,"587":1,"618":2,"621":1,"624":2,"625":2,"650":4,"656":8,"668":1,"669":1,"673":1,"675":1,"679":1,"684":1,"701":1,"704":1,"706":1,"722":1,"747":1,"757":1,"762":1,"764":3,"774":3,"801":1,"811":2,"812":3,"813":1,"814":1,"815":1,"816":2,"831":1,"834":3,"835":2,"837":1,"841":5,"844":5,"845":4,"847":3,"848":1,"849":1,"851":6,"852":18,"853":1,"854":2,"857":5,"860":8,"861":3,"864":7,"865":1,"868":4,"869":4,"872":2,"873":4,"874":1,"875":1,"876":5,"877":2,"878":1,"893":1,"910":1,"913":4,"916":1,"918":2,"919":1,"920":1,"924":4,"926":1,"930":7,"934":1,"937":1,"946":2,"952":1,"956":2,"957":1,"963":1,"968":1,"973":1,"977":5,"979":1,"980":1,"983":2,"985":1,"986":1,"988":1,"989":1,"990":2,"991":1,"993":1,"994":1,"996":2,"997":1,"1001":2,"1005":1,"1013":1,"1015":2,"1044":1,"1050":3,"1054":2,"1055":2,"1060":2,"1066":1,"1067":2,"1068":1,"1075":2,"1076":1,"1077":2,"1079":2,"1080":3,"1081":1,"1094":2,"1096":2,"1097":1,"1098":1,"1100":2,"1101":3,"1102":1,"1104":1,"1105":2,"1106":1,"1111":4,"1125":1,"1127":1,"1132":1,"1138":1,"1141":1,"1149":1,"1157":1,"1169":1,"1170":1,"1174":1,"1180":1,"1181":2,"1184":1,"1185":1,"1188":1,"1198":1,"1206":3,"1210":1,"1213":10,"1214":1,"1225":1,"1228":1,"1233":1,"1234":1,"1235":1,"1236":1,"1239":1,"1279":1,"1305":1,"1307":6,"1328":1,"1329":1,"1332":2,"1335":1,"1336":2,"1338":2,"1339":4,"1351":1,"1355":5,"1358":1,"1360":2,"1362":1,"1366":1,"1368":1,"1372":1,"1374":1,"1382":2,"1385":4,"1386":2,"1388":1,"1390":1,"1393":2,"1394":2,"1395":1,"1396":1,"1397":1,"1399":1,"1400":1,"1401":2,"1402":3,"1403":6,"1405":1,"1408":2,"1409":2,"1410":2,"1411":1,"1416":1,"1417":2,"1419":3,"1420":1,"1426":1,"1427":1,"1432":1,"1435":1,"1438":2,"1447":1,"1459":1,"1460":1,"1475":2,"1477":2,"1500":1,"1511":2,"1517":2,"1518":1,"1523":1,"1527":1,"1528":1,"1570":1,"1571":1,"1574":1,"1581":2,"1593":1,"1596":2,"1605":2,"1620":1,"1621":1,"1624":2,"1628":2,"1641":2,"1644":1,"1651":1,"1653":1,"1655":3,"1678":1,"1690":1,"1703":1,"1704":1,"1716":1,"1717":1,"1722":1,"1738":1,"1741":1,"1747":1,"1767":1,"1768":1,"1792":97,"1822":3,"1824":1,"1825":3,"1832":1,"1840":1,"1847":1,"1856":1,"1858":2,"1861":1,"1875":1,"1885":1,"1921":1,"1922":1,"1923":1,"1924":2,"1929":1,"1930":2,"1937":1,"1961":2,"1994":1,"2006":1,"2016":3,"2019":1,"2038":1,"2040":2,"2072":1,"2077":2,"2092":1,"2098":1,"2106":2,"2107":1,"2109":1,"2110":1,"2117":1,"2124":1,"2138":1,"2142":2,"2145":1,"2146":3,"2147":2,"2154":1,"2177":1,"2193":1,"2197":2,"2223":1,"2225":1,"2242":1,"2245":1,"2247":1,"2251":1,"2253":1,"2254":2,"2255":4,"2258":2,"2261":1,"2265":3,"2266":3,"2267":3,"2271":1,"2289":1,"2296":3,"2309":1,"2310":1,"2314":1,"2319":1,"2322":1,"2323":1,"2328":1,"2333":1,"2336":1,"2337":1,"2342":2,"2343":1,"2344":2,"2346":1,"2359":1,"2363":1,"2365":1,"2372":1,"2375":2,"2380":2,"2384":2,"2388":1,"2392":1,"2394":2,"2398":2,"2411":1,"2412":1,"2414":1,"2424":1,"2429":1,"2430":1,"2437":2,"2438":7,"2450":1,"2451":1,"2453":2,"2455":1,"2459":1,"2463":1,"2464":1,"2465":1,"2466":4,"2476":1,"2481":3,"2482":1,"2490":1,"2491":2,"2492":1,"2496":1,"2497":2,"2498":1,"2502":1,"2504":1,"2506":1,"2509":1,"2510":1,"2511":1,"2512":1,"2513":1,"2519":2,"2528":2,"2529":3,"2530":2,"2531":1,"2533":1,"2534":1,"2537":4,"2539":1,"2540":1,"2542":1,"2549":1,"2551":2,"2572":1,"2575":5,"2577":1,"2581":1,"2586":1,"2588":2,"2589":1,"2597":1,"2626":1,"2632":8,"2633":1,"2634":1,"2641":1,"2645":2,"2648":2,"2666":1,"2682":1,"2688":2,"2702":1,"2721":1,"2723":1,"2729":1,"2762":1,"2763":1,"2788":1,"2803":3,"2807":1,"2809":1,"2810":1,"2814":2,"2815":2,"2823":1,"2828":2,"2830":1,"2832":2,"2836":4,"2840":2,"2842":1,"2845":1,"2848":1,"2849":1,"2855":1,"2864":2,"2865":3,"2868":2,"2869":5,"2876":1,"2878":1}}],["rm",{"2":{"1792":1,"2111":1,"2532":1,"2534":1,"2875":1}}],["rss",{"2":{"1432":1}}],["rs256",{"2":{"1236":1,"1237":1,"1792":1,"1886":1,"1887":1}}],["rsa",{"2":{"1211":1,"1243":1}}],["rpid",{"2":{"1222":1}}],["rp",{"2":{"1220":1,"1225":1,"1875":1}}],["rpc",{"2":{"1039":1,"1041":1,"1046":1,"1097":1,"1121":1,"1125":2,"1792":1,"1813":1,"1817":1,"1822":1,"1824":2,"2481":3}}],["rls",{"0":{"1114":1,"1115":1},"2":{"1084":1,"1098":2,"1115":1,"1122":1,"1125":2,"1385":1}}],["rfpqb6nkcot2ll",{"2":{"1051":2}}],["rfc7231",{"2":{"1676":1,"1677":1}}],["rfc3986",{"2":{"1671":1,"1792":1,"2255":1}}],["rfc",{"0":{"1833":1},"2":{"835":1,"1017":1,"1109":1,"1111":5,"1127":1,"1218":1,"1398":1,"1401":1,"1445":1,"1453":1,"1457":1,"1670":1,"1676":1,"1726":1,"1792":4,"1827":1,"1830":1,"1831":1,"2193":1,"2207":1,"2223":2,"2240":1,"2255":2,"2264":1,"2271":1,"2481":4,"2554":1,"2581":1}}],["rbac",{"2":{"1037":1,"1048":1,"1057":1,"1098":1,"2164":2,"2165":1,"2737":1}}],["rds",{"0":{"1070":1},"2":{"1013":1,"1066":1,"1070":1,"1101":1,"1102":2,"1121":1,"1528":1,"1792":1,"1851":1,"2382":1}}],["rdbms",{"2":{"841":4,"843":1,"844":6,"845":2,"847":1,"848":6,"849":1,"851":1,"1037":1,"1403":1,"1405":6}}],["rn",{"2":{"881":2}}],["rnd3",{"2":{"2534":3}}],["rndn",{"2":{"1792":2,"2534":2,"2871":2}}],["rnd6",{"2":{"1792":1,"2534":3}}],["rnd10",{"2":{"1792":1,"2098":1,"2534":1,"2871":1}}],["rnd1",{"2":{"1792":1,"2098":1,"2534":1,"2871":1}}],["rnd",{"2":{"696":1,"697":1,"2103":1,"2111":1,"2221":1,"2532":1,"2758":1,"2874":1,"2881":1}}],["rnd5",{"2":{"695":2,"696":2,"705":3,"1079":1,"1792":2,"2098":4,"2111":4,"2167":1,"2532":4,"2545":2,"2740":1,"2871":2,"2872":4,"2873":12,"2874":3}}],["r2",{"2":{"881":2,"1220":2,"1221":2,"1222":2}}],["r1",{"2":{"881":2,"1220":2,"1221":2,"1222":2}}],["r",{"2":{"297":2,"340":1,"343":1,"845":3,"849":1,"851":6,"916":4,"924":1,"928":1,"929":1,"934":1,"935":1,"936":1,"938":1,"976":2,"977":1,"1055":1,"1056":2,"1057":1,"1060":1,"1179":2,"1197":3,"1419":4,"1429":6,"1504":6,"2540":3,"2589":1,"2792":3}}],["rigid",{"0":{"879":1}}],["rightfully",{"2":{"852":1}}],["right",{"0":{"947":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1},"2":{"307":1,"841":1,"844":2,"849":2,"854":1,"864":1,"913":1,"988":1,"1010":1,"1037":1,"1043":2,"1067":1,"1070":2,"1076":2,"1078":1,"1127":1,"1148":1,"1399":2,"1400":2,"1401":2,"1403":3,"1405":1,"1421":2,"1430":1,"2164":1,"2421":1,"2424":1,"2466":1,"2498":1,"2537":1,"2804":1,"2866":1}}],["ride",{"2":{"1431":1,"1823":1}}],["riddance",{"2":{"1401":1}}],["riddle",{"2":{"844":1,"848":1}}],["rid",{"2":{"851":1,"2668":1,"2792":1}}],["risks",{"2":{"2466":1}}],["risk",{"2":{"874":1,"969":1,"1070":1,"1102":1,"1706":1,"1852":1,"2383":1}}],["risky",{"2":{"195":1,"1111":1}}],["rise",{"0":{"1274":1},"2":{"841":1}}],["rivals",{"0":{"836":1}}],["richer",{"2":{"2094":1,"2104":1,"2438":1}}],["rich",{"2":{"835":1,"1385":2,"1403":1,"1405":1}}],["ring",{"2":{"188":1,"2296":1,"2297":2}}],["rust",{"2":{"832":1,"1090":1,"1255":2,"1257":1,"1265":1,"1268":1,"1277":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["rule3",{"2":{"2575":1}}],["rule3>",{"2":{"809":1}}],["rule2",{"2":{"2575":1}}],["rule2>",{"2":{"809":1}}],["rule1",{"2":{"2575":1}}],["rule1>",{"2":{"809":1}}],["rule",{"0":{"811":1,"2141":1,"2144":1,"2446":1},"2":{"74":1,"109":2,"299":1,"301":1,"560":1,"619":1,"809":3,"816":1,"819":1,"845":1,"848":2,"854":1,"863":2,"865":1,"922":1,"1067":2,"1071":1,"1150":1,"1378":1,"1523":2,"1527":7,"1792":5,"1924":1,"2141":1,"2143":1,"2180":1,"2225":1,"2340":1,"2380":4,"2384":1,"2415":1,"2445":1,"2446":4,"2447":1,"2509":1,"2518":1,"2520":1,"2575":1,"2666":1,"2722":1}}],["rules`",{"2":{"1792":1}}],["rules",{"0":{"74":1,"379":1,"812":1,"816":1,"817":1,"1523":1,"2142":1,"2143":1,"2145":1,"2192":1,"2446":1,"2690":1,"2692":1,"2851":1},"1":{"75":1,"1524":1,"1525":1,"1526":1,"2144":1,"2145":1},"2":{"101":1,"104":1,"105":1,"108":2,"109":1,"110":1,"123":1,"156":1,"214":1,"230":1,"382":1,"390":1,"395":2,"422":1,"544":1,"712":1,"809":1,"816":1,"817":2,"818":1,"820":1,"822":1,"836":1,"845":1,"848":1,"849":2,"863":2,"865":1,"868":2,"869":1,"1037":1,"1067":3,"1069":1,"1101":3,"1127":2,"1135":1,"1150":3,"1247":1,"1403":1,"1511":1,"1521":1,"1523":2,"1527":1,"1688":1,"1788":1,"1792":7,"1795":1,"1922":1,"1948":1,"2111":1,"2138":1,"2139":3,"2142":2,"2143":2,"2144":1,"2145":1,"2146":2,"2147":1,"2148":1,"2149":2,"2150":1,"2152":1,"2225":1,"2320":1,"2333":1,"2334":1,"2378":1,"2380":2,"2384":1,"2440":1,"2442":2,"2446":3,"2448":1,"2481":1,"2498":1,"2502":1,"2529":1,"2575":6}}],["runmigrationtool",{"2":{"1792":1,"2111":1,"2532":1}}],["runaway",{"2":{"1067":1}}],["runnable",{"2":{"1047":1,"1433":1,"2188":1,"2837":1}}],["runners",{"0":{"2874":1},"2":{"2111":1,"2456":1,"2576":1,"2790":1}}],["runner",{"0":{"1074":1,"2092":1,"2167":1,"2526":1,"2800":1},"1":{"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1,"2099":1,"2100":1,"2101":1,"2102":1,"2103":1,"2104":1,"2105":1,"2106":1,"2107":1,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1},"2":{"239":1,"689":1,"693":2,"697":1,"698":1,"702":1,"703":1,"707":1,"708":1,"712":1,"713":1,"717":1,"876":1,"986":2,"1073":3,"1074":1,"1076":1,"1078":1,"1094":2,"1379":1,"1385":1,"1442":1,"1789":2,"1792":6,"1802":1,"2092":1,"2094":1,"2104":1,"2107":1,"2108":1,"2112":1,"2156":1,"2158":1,"2159":2,"2167":1,"2221":1,"2450":1,"2525":1,"2526":2,"2527":1,"2528":1,"2532":2,"2536":1,"2537":5,"2541":1,"2542":1,"2544":1,"2545":1,"2722":1,"2739":1,"2752":1,"2758":1,"2786":1,"2794":1,"2795":2,"2800":1,"2802":1,"2859":1,"2860":3,"2861":1,"2862":1,"2871":1,"2875":1,"2879":1,"2880":1,"2882":1}}],["running",{"0":{"137":1,"970":1},"2":{"1":1,"284":1,"395":1,"574":1,"658":4,"844":1,"852":1,"860":3,"865":1,"868":1,"885":1,"926":1,"966":1,"1014":1,"1043":1,"1044":1,"1080":2,"1104":1,"1107":1,"1255":1,"1320":1,"1334":1,"1382":1,"1385":2,"1396":1,"1416":1,"1443":1,"1515":1,"1654":1,"1701":1,"1762":1,"1768":1,"1770":1,"1792":7,"1859":1,"2052":2,"2094":1,"2099":1,"2161":2,"2184":1,"2221":1,"2346":1,"2398":1,"2414":1,"2476":1,"2527":1,"2543":1,"2600":1,"2615":2,"2633":2,"2634":3,"2635":1,"2694":1,"2742":1,"2818":1,"2819":1,"2823":1,"2826":1,"2833":1,"2857":1,"2860":1,"2878":1}}],["run",{"0":{"2110":1,"2717":1,"2740":1,"2823":1,"2872":1},"2":{"239":4,"310":1,"453":1,"528":1,"529":1,"689":1,"690":1,"693":2,"696":2,"698":1,"703":2,"704":1,"705":1,"707":1,"708":1,"713":2,"714":1,"716":2,"717":1,"832":1,"836":1,"848":2,"860":1,"861":1,"864":1,"873":1,"874":1,"876":1,"914":1,"920":1,"924":2,"926":1,"930":1,"932":4,"970":2,"976":3,"982":1,"985":1,"986":1,"988":1,"993":2,"997":5,"1003":1,"1005":2,"1044":1,"1045":1,"1047":4,"1073":1,"1075":1,"1076":1,"1079":3,"1081":1,"1082":1,"1086":1,"1094":1,"1105":1,"1107":1,"1115":1,"1117":1,"1118":2,"1121":1,"1130":2,"1145":1,"1202":1,"1207":1,"1254":3,"1343":1,"1380":2,"1401":2,"1407":1,"1418":2,"1419":3,"1420":3,"1422":1,"1433":5,"1435":1,"1442":1,"1789":1,"1792":21,"1832":1,"1833":1,"1850":1,"1911":1,"2092":1,"2094":6,"2098":2,"2103":2,"2106":4,"2107":4,"2110":3,"2111":2,"2112":4,"2113":1,"2156":1,"2158":1,"2162":3,"2167":6,"2168":5,"2383":2,"2397":1,"2415":1,"2433":1,"2438":1,"2456":1,"2463":1,"2466":1,"2481":1,"2490":1,"2525":1,"2527":2,"2528":1,"2530":3,"2532":8,"2533":3,"2534":6,"2535":1,"2537":14,"2542":1,"2545":2,"2546":1,"2667":1,"2689":1,"2717":1,"2721":1,"2739":2,"2740":2,"2754":1,"2755":1,"2758":1,"2760":1,"2766":1,"2785":1,"2788":5,"2789":2,"2790":2,"2791":2,"2792":1,"2806":1,"2807":2,"2809":1,"2830":1,"2835":2,"2857":1,"2858":1,"2860":1,"2861":1,"2862":2,"2864":1,"2866":1,"2868":2,"2869":1,"2870":4,"2871":3,"2872":3,"2873":3,"2874":3,"2875":2,"2876":1,"2878":2,"2879":3,"2880":1,"2881":2}}],["runs",{"0":{"2751":1},"2":{"215":1,"297":1,"307":3,"320":1,"324":1,"347":1,"453":1,"527":1,"529":1,"587":1,"618":1,"666":1,"694":1,"705":1,"707":1,"708":1,"710":1,"711":1,"714":1,"715":1,"717":1,"818":1,"829":1,"860":1,"871":3,"875":1,"924":1,"926":1,"933":1,"934":1,"979":1,"982":1,"985":1,"986":2,"994":1,"1009":1,"1041":1,"1049":3,"1070":1,"1076":1,"1078":1,"1079":2,"1080":2,"1081":1,"1106":1,"1188":1,"1255":1,"1343":1,"1379":1,"1406":1,"1409":1,"1418":2,"1420":1,"1423":1,"1433":1,"1438":1,"1442":1,"1704":1,"1792":15,"2092":1,"2094":1,"2096":1,"2097":1,"2099":1,"2106":4,"2107":2,"2111":1,"2112":3,"2149":1,"2153":2,"2156":1,"2157":1,"2158":2,"2177":2,"2184":1,"2221":2,"2391":1,"2394":1,"2421":1,"2422":1,"2438":1,"2452":1,"2465":1,"2466":1,"2481":2,"2527":3,"2528":1,"2529":1,"2531":1,"2532":4,"2533":5,"2534":1,"2537":12,"2541":3,"2542":2,"2543":1,"2608":1,"2712":1,"2742":3,"2760":2,"2762":1,"2766":1,"2776":1,"2807":1,"2809":1,"2810":1,"2813":2,"2815":1,"2828":1,"2829":1,"2834":1,"2854":1,"2862":2,"2863":1,"2865":1,"2871":2,"2872":1,"2875":1,"2877":1,"2878":6,"2879":1}}],["runtimes",{"2":{"1104":1,"1108":1}}],["runtime",{"0":{"1004":1,"1023":1,"1107":1,"1343":1,"2550":1,"2750":1,"2791":1},"2":{"1":1,"156":1,"238":1,"582":1,"585":1,"587":3,"646":1,"650":1,"832":1,"860":1,"872":1,"954":1,"973":1,"985":1,"997":1,"1007":1,"1080":1,"1104":1,"1106":1,"1107":1,"1108":3,"1115":1,"1126":1,"1127":1,"1316":1,"1343":1,"1407":1,"1420":2,"1422":3,"1527":1,"1568":1,"1974":2,"2161":1,"2238":1,"2245":2,"2283":1,"2337":2,"2372":1,"2378":1,"2380":1,"2385":1,"2394":1,"2414":2,"2443":1,"2448":1,"2479":1,"2496":1,"2519":1,"2540":1,"2550":2,"2577":1,"2590":1,"2607":2,"2668":1,"2744":1,"2789":2,"2790":1,"2791":3,"2792":1,"2831":1,"2833":1,"2840":2,"2845":1}}],["raspberry",{"2":{"2576":1,"2779":1,"2783":1,"2790":1}}],["rare",{"2":{"1403":1,"2424":1}}],["rarely",{"2":{"177":1,"683":1,"916":1,"1069":1,"1162":1,"1179":1,"1230":1,"1412":1,"1792":1,"1880":1,"2351":1,"2466":1}}],["rabbitmq",{"2":{"1303":1}}],["rapidly",{"2":{"1329":1}}],["rapid",{"2":{"1094":1,"1324":1}}],["radio",{"2":{"1061":3}}],["radius",{"2":{"868":1,"1458":1,"1792":1,"2375":1}}],["races",{"2":{"865":1,"2498":1}}],["race",{"0":{"1440":1},"2":{"864":1,"865":1,"1181":1,"1402":1,"1440":1,"1441":1,"1442":1,"2375":1,"2532":1,"2576":1}}],["ramp",{"2":{"876":1}}],["ram",{"2":{"847":1,"848":3,"852":1,"857":1,"860":1,"1255":1}}],["rage",{"2":{"840":1}}],["rants",{"2":{"1403":1}}],["rant",{"2":{"1400":1,"1403":1}}],["ranks",{"2":{"1429":1}}],["ranking",{"2":{"860":1,"1429":1}}],["rank",{"2":{"860":1}}],["randomuuid",{"2":{"1317":1,"1366":1,"1416":1,"2247":1}}],["random",{"2":{"845":1,"1079":1,"1214":6,"1232":4,"1234":2,"1792":3,"1882":2,"1884":1,"2098":1,"2534":1,"2871":2}}],["ranges",{"2":{"845":1,"863":1,"864":1,"1703":1,"1708":2,"1714":1,"1792":1,"2633":1}}],["range",{"2":{"841":1,"869":1,"1067":2,"1096":1,"1258":1,"1265":1,"1385":1,"1712":1,"1715":1,"1792":2,"2380":2,"2445":1,"2633":1}}],["ran",{"2":{"695":1,"919":1,"1044":1,"2392":1,"2421":1,"2450":1,"2495":1,"2758":1,"2867":1,"2881":1}}],["raising",{"2":{"216":1,"1106":1,"2184":1,"2535":1}}],["raised",{"2":{"777":1,"2384":1,"2763":1,"2802":1}}],["raises",{"2":{"650":1,"663":1,"777":1,"903":1,"1309":1,"1792":1,"2255":1,"2528":2,"2795":1,"2864":2}}],["raise",{"0":{"646":1,"1316":1,"1861":1,"2392":1,"2832":1},"2":{"208":1,"308":1,"631":3,"632":2,"633":1,"641":1,"646":5,"650":4,"656":6,"658":4,"659":3,"660":1,"661":1,"663":1,"664":2,"665":1,"666":1,"668":1,"777":3,"868":1,"897":2,"930":1,"1056":2,"1060":1,"1103":2,"1111":1,"1121":1,"1302":1,"1304":2,"1305":6,"1309":2,"1316":3,"1321":1,"1324":1,"1325":4,"1327":1,"1366":2,"1372":2,"1394":2,"1395":2,"1416":2,"1427":2,"1442":1,"1674":2,"1792":5,"1799":1,"1844":1,"1857":1,"1858":4,"1861":1,"2104":2,"2108":1,"2164":1,"2226":1,"2255":3,"2384":1,"2391":2,"2392":4,"2406":1,"2466":1,"2490":1,"2535":1,"2536":1,"2762":1,"2795":2,"2802":5,"2805":1,"2827":2,"2828":3,"2829":6,"2832":4,"2833":4,"2834":2,"2835":2,"2836":1,"2880":2}}],["raw",{"0":{"229":1,"484":1,"487":1,"488":1,"2206":1},"1":{"485":1,"486":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"495":1,"496":1},"2":{"68":1,"73":1,"90":2,"125":1,"128":2,"129":1,"131":2,"159":6,"188":1,"229":4,"339":2,"342":1,"343":1,"344":1,"346":2,"379":1,"429":1,"439":1,"484":3,"485":1,"487":2,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"548":2,"598":2,"601":1,"602":1,"603":1,"604":1,"606":2,"617":2,"747":1,"830":2,"857":1,"861":1,"869":1,"871":1,"872":1,"880":1,"888":2,"904":1,"911":1,"942":1,"971":1,"1060":1,"1070":1,"1096":1,"1102":1,"1109":1,"1127":1,"1189":2,"1196":1,"1260":1,"1279":1,"1362":2,"1373":2,"1382":2,"1412":3,"1426":1,"1429":3,"1431":1,"1687":1,"1792":2,"1824":1,"1852":1,"1917":1,"1924":1,"1925":1,"1927":2,"2193":4,"2206":4,"2296":1,"2309":1,"2310":1,"2313":1,"2323":1,"2329":5,"2333":1,"2372":1,"2383":1,"2405":2,"2483":1,"2509":1,"2549":2,"2581":3,"2591":3,"2664":1,"2726":2,"2751":1,"2799":1,"2814":1,"2846":1,"2856":1}}],["rationale",{"2":{"2319":1}}],["ratios",{"2":{"1940":1}}],["ratio",{"2":{"1792":1,"1938":3,"2107":1,"2537":1,"2879":1}}],["rating",{"2":{"913":3,"916":2,"919":5,"1423":1,"1429":7,"2611":1}}],["rated",{"2":{"1429":1}}],["ratelimiting",{"2":{"1245":1,"1893":1}}],["ratelimiterperpolicytestfixture",{"2":{"2472":1}}],["ratelimiterperpolicytests",{"2":{"2472":1}}],["ratelimiterpolicy",{"0":{"1822":1},"2":{"480":1,"967":1,"1217":1,"1224":1,"1245":1,"1792":4,"1814":1,"1822":2,"1824":1,"1874":1,"1893":1,"1961":1,"2046":1,"2047":1,"2061":1,"2068":1,"2257":1,"2481":2,"2634":1,"2635":1}}],["ratelimiteroptions",{"0":{"2378":1,"2441":1},"1":{"2442":1,"2443":1,"2444":1},"2":{"476":1,"477":1,"478":1,"479":1,"1069":2,"1157":2,"1162":1,"1177":1,"1792":4,"1948":2,"1951":2,"1952":2,"1953":2,"1954":2,"1955":1,"1958":3,"1960":1,"2047":1,"2061":1,"2225":1,"2257":1,"2379":1,"2440":1,"2441":2,"2442":1,"2443":1,"2468":2,"2470":2,"2551":4,"2634":2,"2635":1,"2701":1}}],["rates",{"2":{"263":6,"1010":1,"1011":1,"1018":1,"1020":2,"1021":8,"1024":1,"1026":3,"1376":7,"1516":1,"1930":3,"2265":1,"2344":7,"2764":4,"2766":1}}],["rate",{"0":{"473":1,"479":1,"1066":1,"1069":1,"1156":1,"1157":1,"1162":1,"1245":1,"1947":1,"1955":1,"1961":1,"2061":1,"2218":1,"2257":1,"2379":1,"2747":1},"1":{"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1948":1,"1949":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1956":2,"1957":2,"1958":1,"1959":1,"1960":1,"1961":1,"1962":1,"1963":1,"1964":1},"2":{"41":1,"235":2,"383":2,"473":4,"474":2,"476":2,"477":2,"478":2,"479":2,"480":3,"481":2,"483":2,"835":2,"836":1,"837":1,"868":3,"869":4,"873":2,"877":1,"969":1,"1018":1,"1019":4,"1021":12,"1023":2,"1024":2,"1032":2,"1037":3,"1066":2,"1069":3,"1086":1,"1101":10,"1105":1,"1109":1,"1113":2,"1121":2,"1125":1,"1127":3,"1135":1,"1156":2,"1158":1,"1159":1,"1160":2,"1161":4,"1163":4,"1177":1,"1179":3,"1180":2,"1181":1,"1182":5,"1217":1,"1224":1,"1245":1,"1250":1,"1252":1,"1253":1,"1351":1,"1376":14,"1382":1,"1386":1,"1406":1,"1407":1,"1704":1,"1718":2,"1739":1,"1742":5,"1790":2,"1792":18,"1797":2,"1822":4,"1824":4,"1874":1,"1894":2,"1895":2,"1947":2,"1949":5,"1950":2,"1951":2,"1952":1,"1953":1,"1954":1,"1955":1,"1958":1,"1959":2,"1961":5,"1962":2,"1964":2,"2047":1,"2061":1,"2070":2,"2218":2,"2224":1,"2240":1,"2257":16,"2258":1,"2287":1,"2290":5,"2348":2,"2378":1,"2379":2,"2389":1,"2438":2,"2441":1,"2447":2,"2468":2,"2470":1,"2471":1,"2481":2,"2558":1,"2580":1,"2634":2,"2635":2,"2747":2,"2764":4,"2766":7,"2809":1}}],["rather",{"2":{"1":1,"214":1,"216":1,"325":1,"701":1,"723":1,"847":1,"868":1,"872":1,"876":1,"1102":1,"1159":1,"1161":1,"1272":1,"1326":1,"1413":1,"1416":1,"1443":1,"1527":1,"1607":1,"1856":1,"1925":1,"2222":1,"2377":1,"2380":1,"2384":1,"2405":1,"2440":1,"2454":1,"2463":1,"2466":1,"2511":1,"2513":1,"2531":1,"2535":1,"2603":1,"2656":1,"2802":1,"2812":1}}],["roof",{"2":{"1385":1}}],["room",{"2":{"1320":2,"1386":1}}],["rooms",{"2":{"1312":1}}],["rootpath",{"2":{"937":1,"998":1,"1792":3,"2033":1,"2034":1,"2036":1,"2042":1}}],["root",{"0":{"2411":1,"2421":1,"2426":1,"2442":1},"2":{"108":1,"863":2,"1067":3,"1068":1,"1150":4,"1458":1,"1459":2,"1460":1,"1520":2,"1522":2,"1529":1,"1792":4,"2034":1,"2225":1,"2365":1,"2375":5,"2380":1,"2427":2,"2435":3,"2445":1,"2495":1}}],["robust",{"2":{"1428":1}}],["robin",{"2":{"1175":1}}],["robinson",{"2":{"913":1}}],["robert",{"2":{"847":1}}],["romance",{"2":{"913":1}}],["road",{"0":{"833":1},"2":{"1385":1}}],["rotates",{"2":{"1175":1,"2297":1}}],["rotate",{"2":{"188":1,"1251":1,"2296":1,"2438":3}}],["rotated",{"2":{"188":1,"1651":1,"1792":1,"2296":1}}],["rotation",{"2":{"188":1,"189":1,"1110":1,"2296":1,"2297":1,"2405":1,"2438":2}}],["rough",{"2":{"2493":1}}],["roughly",{"2":{"868":3,"871":1,"872":6,"873":1,"1414":1,"2324":1}}],["rounds",{"2":{"1382":1}}],["round",{"2":{"854":1,"861":1,"874":1,"894":1,"1070":2,"1076":1,"1102":2,"1137":1,"1175":1,"1361":1,"1370":1,"1410":1,"1427":1,"1429":1,"1439":1,"1440":1,"1792":1,"1850":1,"1852":1,"2282":1,"2320":1,"2383":2,"2398":1,"2456":2,"2496":1,"2498":1,"2546":1,"2762":2,"2767":1,"2774":1,"2811":1,"2850":1}}],["roundtrip",{"0":{"187":1,"2294":1}}],["router",{"2":{"1026":4}}],["routed",{"2":{"876":1,"1929":1,"2483":1}}],["route",{"0":{"320":1,"1042":1},"2":{"261":1,"320":4,"325":1,"327":1,"395":1,"448":1,"480":1,"835":1,"859":1,"865":1,"867":1,"868":2,"873":1,"1011":1,"1042":1,"1345":1,"1382":2,"1431":1,"1632":2,"1792":1,"1822":1,"1824":2,"1834":1,"1840":1,"1961":7,"2195":1,"2223":1,"2344":1,"2372":1,"2429":1,"2463":1,"2481":4,"2482":1,"2489":3,"2803":1}}],["routes",{"2":{"108":1,"835":1,"1026":1,"1027":1,"1046":1,"1177":1,"1406":1,"1747":1,"1930":1,"2277":1,"2438":1,"2461":1}}],["routing",{"0":{"223":1,"1176":1},"2":{"212":1,"390":1,"395":1,"650":1,"835":2,"868":1,"869":1,"1007":1,"1074":1,"1114":1,"1174":1,"1181":2,"1182":1,"1255":1,"1276":1,"1281":1,"1329":1,"1351":1,"1822":1,"2347":1,"2407":1,"2527":1,"2739":1,"2860":1}}],["routinecachetests",{"2":{"2465":1}}],["routinecache",{"2":{"2462":1}}],["routinetype",{"2":{"2372":1}}],["routineendpoints",{"2":{"2435":1}}],["routineendpoint",{"0":{"2482":1},"2":{"2223":2,"2461":1,"2482":2,"2487":2,"2518":1,"2520":1}}],["routineoptions",{"2":{"336":1,"917":1,"1792":1,"1836":2,"1966":1,"1968":1,"1970":1,"1971":1,"1973":1,"1974":1,"1975":1,"2156":1,"2350":2,"2587":1,"2607":1,"2641":1,"2701":1}}],["routinesstatspath",{"2":{"1792":1,"2046":1,"2047":1,"2064":1,"2635":1}}],["routines",{"0":{"352":1,"1378":1,"1387":1,"2049":2,"2775":1},"1":{"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1},"2":{"244":3,"673":2,"868":1,"917":1,"966":1,"1038":1,"1042":1,"1067":1,"1096":1,"1100":1,"1126":1,"1385":2,"1386":1,"1389":1,"1392":2,"1393":3,"1394":1,"1396":2,"1406":1,"1407":1,"1509":1,"1511":1,"1516":3,"1530":1,"1630":1,"1631":1,"1632":1,"1789":1,"1792":13,"1813":2,"1824":2,"1837":1,"1838":1,"1840":4,"1848":1,"1849":1,"1898":1,"1961":1,"1965":1,"1969":1,"1970":1,"2046":1,"2047":1,"2056":1,"2072":1,"2156":1,"2223":1,"2258":1,"2265":2,"2350":1,"2388":1,"2389":1,"2397":1,"2430":1,"2434":2,"2438":1,"2479":1,"2481":1,"2527":1,"2542":1,"2608":2,"2614":1,"2635":2,"2674":1,"2857":1,"2862":1,"2878":1}}],["routinesources",{"2":{"2369":1}}],["routinesource",{"0":{"2164":1,"2350":1},"2":{"175":1,"179":1,"684":1,"2317":1,"2352":1,"2364":1,"2369":1}}],["routine",{"0":{"322":1,"351":1,"1530":1,"1965":1,"2265":1,"2432":1,"2494":1,"2542":1},"1":{"1531":1,"1532":1,"1533":1,"1966":1,"1967":1,"1968":1,"1969":1,"1970":1,"1971":1,"1972":1,"1973":1,"1974":1,"1975":1,"1976":1,"1977":1},"2":{"112":1,"171":1,"174":1,"175":1,"176":2,"179":1,"181":1,"223":1,"239":1,"296":1,"305":1,"317":3,"318":1,"319":2,"320":2,"322":1,"334":1,"338":1,"347":2,"349":1,"352":1,"354":1,"386":1,"390":1,"527":2,"528":1,"531":2,"675":2,"683":2,"685":1,"693":1,"703":1,"708":1,"713":1,"833":1,"868":1,"915":1,"917":3,"967":1,"1038":1,"1040":3,"1041":1,"1042":2,"1043":1,"1045":2,"1046":2,"1070":4,"1094":1,"1102":4,"1378":1,"1385":1,"1387":1,"1388":1,"1392":1,"1396":1,"1398":1,"1401":1,"1416":1,"1470":1,"1471":1,"1521":1,"1522":1,"1523":1,"1524":1,"1527":1,"1531":1,"1556":1,"1557":2,"1562":1,"1563":1,"1576":2,"1581":2,"1753":1,"1755":2,"1758":1,"1787":2,"1789":1,"1792":42,"1794":2,"1813":1,"1822":3,"1824":8,"1825":1,"1832":1,"1834":1,"1836":1,"1838":4,"1840":1,"1847":1,"1850":2,"1852":1,"1862":2,"1865":2,"1898":2,"1908":1,"1909":1,"1910":1,"1913":1,"1923":1,"1961":1,"1967":2,"1974":1,"2010":1,"2047":1,"2049":1,"2077":1,"2106":2,"2154":1,"2156":3,"2157":1,"2171":1,"2195":1,"2223":1,"2224":1,"2225":1,"2239":1,"2255":1,"2258":1,"2265":1,"2266":1,"2325":1,"2330":1,"2356":1,"2370":2,"2372":2,"2380":3,"2383":2,"2392":1,"2419":1,"2430":1,"2431":2,"2432":2,"2433":1,"2436":2,"2438":2,"2459":1,"2481":10,"2483":1,"2489":2,"2494":3,"2495":1,"2502":1,"2509":2,"2537":1,"2540":1,"2542":2,"2543":1,"2546":1,"2607":1,"2608":1,"2635":1,"2672":1,"2721":2,"2742":2,"2759":1,"2760":2,"2806":1,"2807":3,"2824":1,"2825":1,"2828":1,"2845":1,"2878":3}}],["rollonfilesizelimit",{"2":{"1792":1,"1800":1,"1804":2,"1810":1}}],["rolling",{"2":{"1438":1,"1792":1,"1804":1,"2794":1,"2804":2}}],["rollup",{"2":{"1418":4,"1420":1}}],["rolls",{"2":{"903":1,"1073":1,"1074":1,"1442":1,"2527":1,"2531":1,"2533":1,"2739":1,"2862":1,"2869":1}}],["roll",{"2":{"835":1,"860":1,"1078":1,"1094":1,"2869":1}}],["rolled",{"2":{"777":2,"869":2,"875":1,"881":1,"901":1,"992":1,"1046":1,"2481":1}}],["rollbackasync",{"2":{"2267":1}}],["rollbacks",{"2":{"1792":1,"2110":1,"2530":1}}],["rollback",{"0":{"777":1,"989":1,"2867":1},"2":{"174":1,"624":1,"695":1,"715":1,"777":1,"779":1,"876":1,"879":1,"880":1,"986":1,"989":4,"990":2,"991":1,"992":2,"993":1,"994":3,"1005":2,"1074":2,"1076":1,"1079":3,"1393":1,"1419":1,"1442":2,"2110":1,"2342":1,"2526":1,"2527":2,"2530":1,"2531":1,"2533":1,"2537":1,"2545":1,"2739":1,"2741":1,"2858":1,"2860":1,"2862":2,"2866":1,"2867":3,"2868":3,"2869":3,"2873":1,"2881":2}}],["role2",{"2":{"305":1}}],["role1",{"2":{"305":1}}],["role",{"0":{"18":1,"107":1,"314":1,"926":1,"1048":1,"1057":1,"2059":1,"2200":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":2,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1},"2":{"14":1,"18":1,"22":1,"25":1,"34":2,"37":2,"305":2,"314":1,"320":1,"327":1,"638":1,"642":1,"643":1,"664":1,"665":1,"690":1,"700":2,"834":1,"835":1,"907":1,"922":2,"926":4,"932":4,"937":2,"943":1,"946":1,"1037":1,"1045":1,"1048":2,"1057":6,"1061":1,"1064":3,"1065":1,"1078":1,"1094":1,"1098":1,"1102":1,"1105":1,"1107":1,"1108":1,"1113":1,"1174":1,"1185":2,"1197":1,"1326":1,"1382":1,"1414":1,"1441":1,"1504":2,"1792":3,"1824":1,"1825":3,"1834":1,"2042":1,"2164":1,"2181":2,"2188":1,"2252":6,"2314":2,"2423":4,"2438":1,"2481":2,"2490":5,"2498":3,"2529":1,"2540":2,"2635":1,"2755":2,"2834":2,"2876":4}}],["roles=viewer",{"2":{"691":1}}],["roles=auditor",{"2":{"689":1}}],["roles=admin",{"2":{"689":1,"691":1,"2529":1,"2865":1}}],["roles",{"0":{"21":1,"22":1,"642":1,"1313":1},"2":{"13":1,"21":1,"302":1,"304":1,"305":5,"309":2,"312":3,"314":2,"453":1,"638":1,"641":1,"663":3,"664":4,"665":4,"666":2,"669":1,"690":2,"724":1,"733":4,"738":3,"797":5,"798":3,"801":1,"802":3,"967":1,"1048":2,"1050":1,"1051":2,"1055":2,"1058":4,"1060":2,"1061":2,"1062":5,"1078":1,"1098":1,"1179":1,"1197":2,"1313":1,"1414":2,"1469":5,"1474":2,"1476":2,"1477":1,"1478":2,"1483":5,"1504":4,"1539":4,"1541":2,"1542":2,"1543":1,"1545":2,"1546":2,"1547":5,"1548":4,"1618":1,"1792":9,"1926":1,"2047":1,"2059":1,"2164":1,"2165":2,"2171":1,"2178":1,"2179":1,"2180":2,"2181":7,"2183":1,"2184":4,"2187":13,"2188":1,"2199":2,"2200":1,"2226":1,"2256":2,"2258":1,"2314":2,"2392":1,"2395":4,"2459":1,"2498":1,"2529":1,"2540":1,"2549":1,"2635":1,"2728":1,"2797":1,"2831":2,"2834":8}}],["rowbuilder",{"2":{"2614":1}}],["rowdescription",{"2":{"2324":1}}],["row2",{"2":{"1088":1}}],["row1",{"2":{"1088":1}}],["rowversion",{"2":{"854":1}}],["rowindex",{"2":{"762":1,"772":4,"893":2}}],["rowcommanduserclaimskey",{"2":{"762":2,"765":1,"772":2,"905":1,"1792":1,"2123":1,"2125":1,"2129":1,"2131":1,"2572":2}}],["rows`",{"2":{"894":1}}],["rows",{"0":{"78":1,"896":1,"1131":1,"1133":1},"1":{"79":1,"80":1,"81":1,"82":1,"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1,"1132":1,"1133":1},"2":{"35":2,"78":1,"79":2,"80":1,"81":3,"83":1,"84":1,"85":1,"87":2,"119":2,"142":1,"159":2,"231":1,"299":1,"494":1,"529":1,"567":2,"586":1,"614":1,"615":1,"616":1,"622":1,"624":1,"746":2,"761":2,"770":1,"771":3,"772":2,"773":4,"829":1,"833":1,"848":2,"860":1,"861":1,"864":1,"865":1,"871":1,"881":1,"885":1,"887":2,"893":1,"902":1,"903":1,"909":1,"948":1,"953":2,"971":1,"991":3,"1037":1,"1041":1,"1067":1,"1077":1,"1079":2,"1098":1,"1133":2,"1149":1,"1511":1,"1517":1,"1651":1,"1655":1,"1688":1,"1792":2,"1824":1,"2103":1,"2128":1,"2130":1,"2265":3,"2270":1,"2284":1,"2320":2,"2329":1,"2337":1,"2339":2,"2342":1,"2357":2,"2398":3,"2463":1,"2465":1,"2466":1,"2527":2,"2528":1,"2621":1,"2741":2,"2813":2,"2851":1,"2858":1,"2862":2,"2864":1,"2868":2,"2869":1}}],["row",{"0":{"344":1,"760":1,"761":1,"762":1,"765":1,"770":1,"771":1,"772":1,"775":1,"878":1,"882":1,"883":1,"884":1,"885":1,"893":1,"900":1,"2129":1,"2131":1,"2725":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":2,"884":2,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1},"2":{"16":2,"19":1,"20":1,"34":1,"35":1,"41":1,"81":1,"83":1,"87":1,"116":1,"125":1,"131":1,"136":2,"142":1,"229":1,"231":1,"264":1,"297":6,"298":1,"299":3,"301":2,"308":1,"309":1,"332":1,"333":2,"334":2,"335":2,"339":1,"346":1,"401":1,"415":2,"426":1,"496":2,"531":1,"567":1,"606":2,"609":1,"615":1,"759":1,"760":8,"761":13,"762":3,"763":1,"764":11,"765":6,"766":5,"767":2,"768":3,"769":1,"770":9,"771":11,"772":5,"773":1,"774":11,"775":5,"776":5,"786":3,"787":2,"788":6,"789":2,"791":1,"811":2,"844":2,"848":2,"849":1,"852":1,"854":2,"865":1,"878":2,"880":7,"881":5,"882":4,"883":13,"884":7,"885":5,"886":3,"888":11,"891":2,"892":4,"893":4,"897":6,"898":3,"899":2,"900":4,"901":2,"902":4,"903":2,"904":9,"905":2,"907":2,"910":1,"911":2,"916":5,"918":2,"948":3,"949":3,"951":1,"969":1,"992":1,"996":4,"1037":3,"1075":1,"1079":1,"1098":1,"1099":4,"1105":2,"1114":2,"1122":1,"1133":1,"1142":2,"1189":2,"1192":1,"1370":1,"1375":1,"1382":1,"1410":1,"1412":1,"1688":1,"1792":11,"1973":1,"1974":5,"2010":1,"2110":1,"2123":2,"2125":2,"2128":3,"2129":2,"2130":4,"2131":2,"2132":2,"2134":1,"2164":2,"2165":2,"2171":1,"2176":2,"2177":1,"2180":1,"2184":1,"2206":1,"2303":1,"2320":1,"2339":1,"2348":1,"2372":1,"2397":3,"2398":4,"2463":1,"2496":2,"2498":1,"2528":2,"2530":1,"2572":7,"2586":3,"2587":1,"2607":5,"2614":1,"2649":2,"2664":2,"2725":1,"2813":2,"2829":1,"2852":1,"2864":1,"2868":1}}],["rerun",{"2":{"2106":1,"2156":2,"2537":1,"2542":1,"2546":2}}],["rebinding",{"2":{"1792":1,"1823":1,"2481":1}}],["rebuilt",{"2":{"852":2}}],["rebuilds",{"2":{"1792":1,"2106":1,"2153":1,"2162":1,"2168":1,"2221":1,"2541":1,"2542":1,"2742":1,"2878":2}}],["rebuilding",{"2":{"876":1,"2474":1}}],["rebuild",{"2":{"324":3,"1080":1,"1792":1,"2040":1,"2106":1,"2156":1,"2158":1,"2221":1,"2537":3}}],["rewards",{"2":{"876":1}}],["rewrote",{"2":{"874":1}}],["rewriting",{"0":{"2348":1},"2":{"872":1,"2348":2,"2545":1}}],["rewriter",{"2":{"2540":1,"2546":1}}],["rewrites",{"2":{"871":1,"872":2,"1409":1,"1418":2,"1422":1,"2527":1,"2862":1}}],["rewrite",{"2":{"860":1,"872":1,"874":2,"1438":1}}],["rewritten",{"2":{"383":1,"861":1,"872":2,"2540":1,"2845":1}}],["reinventing",{"2":{"869":1}}],["reimplementation",{"2":{"861":1}}],["reimplemented",{"2":{"852":1}}],["redundancy",{"2":{"1354":1}}],["reducing",{"2":{"1516":1,"1935":1,"2017":1,"2265":1,"2359":1,"2580":1,"2604":1,"2614":1}}],["reduces",{"2":{"1511":1,"1559":1,"1792":2,"1974":1,"2265":1,"2366":1,"2506":1,"2607":1}}],["reduce",{"2":{"974":1,"1011":1,"1036":1,"1135":1,"1431":1,"1645":1,"2060":1,"2364":1,"2466":1,"2559":1,"2629":1}}],["reduced",{"2":{"843":1,"1349":1,"1439":1,"2270":1,"2397":1,"2398":1,"2614":1,"2615":1}}],["reduction",{"2":{"871":1,"911":1,"2398":1}}],["red",{"2":{"1076":1,"1080":1,"2415":1,"2535":3}}],["redeployment",{"0":{"888":1},"2":{"879":1,"880":1,"888":1,"911":1}}],["redeploy",{"2":{"878":1,"879":1,"910":2,"974":1}}],["redesign",{"2":{"865":1}}],["redraws",{"2":{"864":1}}],["redoing",{"2":{"861":1}}],["redoc",{"2":{"352":1,"2432":1}}],["reddit",{"2":{"845":2,"854":1,"863":1,"864":1}}],["redirected",{"2":{"1982":1,"2535":1,"2662":1,"2667":1,"2673":1,"2679":2,"2694":1}}],["redirection",{"0":{"1982":1}}],["redirecting",{"0":{"75":1},"2":{"1792":1}}],["redirectcount",{"2":{"1792":2}}],["redirects",{"2":{"1704":1}}],["redirect",{"2":{"1684":2,"1685":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1704":1,"1792":10,"1980":1,"2034":1,"2518":1,"2521":1,"2523":3}}],["redirecturl",{"2":{"1684":1,"1792":1}}],["rediscache",{"2":{"2462":1}}],["rediscover",{"2":{"861":1}}],["redisconfiguration",{"2":{"121":1,"1067":2,"1146":1,"1147":1,"1150":1,"1177":1,"1510":1,"1511":1,"1514":1,"1515":1,"1522":1,"1534":1,"1792":1,"2274":1}}],["redis",{"0":{"1146":1,"1514":1},"2":{"101":1,"105":2,"121":4,"122":1,"835":1,"868":1,"869":2,"1067":12,"1101":3,"1121":1,"1146":1,"1147":2,"1150":8,"1177":2,"1181":2,"1303":1,"1320":8,"1322":4,"1511":6,"1514":3,"1515":4,"1516":5,"1519":1,"1520":2,"1521":1,"1522":4,"1529":2,"1534":4,"1792":16,"2265":5,"2274":7,"2380":4,"2381":1,"2386":1,"2445":2,"2462":1,"2465":1,"2466":1,"2494":1,"2495":2,"2745":1}}],["reliability",{"2":{"2265":1,"2776":1}}],["reliable",{"0":{"2393":1},"2":{"1385":1,"2226":1}}],["reliably",{"0":{"2518":1},"2":{"349":1,"2540":1}}],["relied",{"2":{"1125":1,"1644":1,"2451":1,"2454":1,"2539":1}}],["relies",{"2":{"1114":1,"1718":1,"2297":1,"2465":1,"2498":1}}],["relabels",{"2":{"2451":1}}],["relaxing",{"2":{"2869":1}}],["relaxation",{"2":{"2543":1}}],["relaxed",{"2":{"2040":1,"2481":1,"2857":1,"2869":1}}],["relax",{"2":{"1079":1,"2869":3}}],["relates",{"2":{"841":1}}],["related",{"0":{"11":1,"12":1,"26":1,"27":1,"42":1,"43":1,"53":1,"54":1,"65":1,"66":1,"76":1,"77":1,"89":1,"90":1,"98":1,"99":1,"110":1,"122":1,"123":1,"130":1,"131":1,"141":1,"142":1,"151":1,"152":1,"164":1,"170":1,"176":1,"181":1,"189":1,"190":1,"198":1,"199":1,"217":1,"218":1,"259":1,"260":1,"266":1,"281":1,"293":1,"294":1,"315":1,"316":1,"327":1,"338":1,"345":1,"346":1,"356":1,"367":1,"368":1,"385":1,"396":1,"410":1,"411":1,"431":1,"456":1,"471":1,"472":1,"481":1,"482":1,"495":1,"496":1,"505":1,"506":1,"513":1,"514":1,"525":1,"526":1,"535":1,"547":1,"548":1,"557":1,"558":1,"568":1,"578":1,"579":1,"588":1,"596":1,"597":1,"605":1,"606":1,"617":1,"626":1,"634":1,"635":1,"647":1,"648":1,"670":1,"671":1,"680":1,"681":1,"688":1,"692":1,"697":1,"702":1,"707":1,"712":1,"717":1,"725":1,"726":1,"740":1,"741":1,"790":1,"792":1,"804":1,"805":1,"820":1,"821":1,"830":1,"1182":1,"1434":1,"1465":1,"1484":1,"1495":1,"1506":1,"1535":1,"1549":1,"1583":1,"1600":1,"1610":1,"1634":1,"1647":1,"1665":1,"1679":1,"1699":1,"1718":1,"1748":1,"1760":1,"1783":1,"1793":1,"1811":1,"1834":1,"1860":1,"1864":1,"1894":1,"1913":1,"1932":1,"1945":1,"1962":1,"1976":1,"1996":1,"2013":1,"2030":1,"2043":1,"2070":1,"2081":1,"2090":1,"2114":1,"2120":1,"2133":1,"2150":1,"2159":1,"2189":1,"2249":1,"2771":1,"2805":1,"2817":1,"2838":1,"2859":1},"1":{"1794":1,"1795":1,"1796":1,"1797":1,"1798":1},"2":{"221":1,"841":1,"948":1,"992":1,"1096":1,"1189":1,"1397":1,"1630":1,"1836":1,"1979":1,"2164":1,"2166":1,"2493":1,"2700":1,"2802":1}}],["relation",{"2":{"852":1}}],["relations",{"2":{"851":1}}],["relationships",{"2":{"918":2,"1974":1,"2607":1}}],["relationship",{"2":{"845":1,"913":1}}],["relational",{"0":{"841":1},"2":{"840":3,"841":13,"844":2,"845":1,"848":4,"851":9,"852":2,"856":1,"859":1,"871":1,"1078":1,"1394":1,"1403":4,"1429":1}}],["relative",{"0":{"421":1,"445":1,"1744":1,"1929":1,"2346":1},"1":{"1745":1,"1746":1,"1747":1,"1930":1},"2":{"243":1,"261":1,"421":1,"422":2,"423":1,"436":1,"445":1,"446":5,"1398":3,"1744":1,"1792":5,"1917":1,"1929":3,"2036":1,"2096":1,"2329":1,"2344":1,"2346":3,"2347":1,"2372":1,"2398":2,"2531":2,"2537":1,"2648":1,"2767":1,"2811":2,"2869":2,"2877":1}}],["reloads",{"2":{"1094":1}}],["reload",{"2":{"967":1,"1094":1,"1792":2,"2049":2,"2635":2}}],["rely",{"2":{"941":1,"1054":1,"1098":1,"1193":1,"1664":1,"2393":1,"2438":1}}],["relyingpartyorigins",{"2":{"1225":1,"1792":1,"1875":1,"1893":1,"2492":1}}],["relyingpartyname",{"2":{"1225":1,"1792":1,"1875":1,"1893":1}}],["relyingpartyid",{"2":{"1225":1,"1792":1,"1875":1,"1893":1}}],["relying",{"0":{"1225":1,"1875":1},"2":{"868":1,"1176":1,"1225":1,"1243":1,"1792":2,"1875":1,"2551":1}}],["relevant",{"2":{"1419":1,"2428":1}}],["released",{"0":{"2403":1,"2404":1},"2":{"2226":1,"2403":1}}],["releases",{"2":{"1117":1,"1118":1,"1792":1,"2781":1,"2782":1,"2783":1,"2784":1,"2792":2}}],["release",{"2":{"865":2,"872":1,"1071":1,"1380":1,"1384":1,"2013":1,"2342":1,"2385":1,"2388":1,"2407":1,"2419":1,"2424":1,"2435":1,"2437":1,"2438":1,"2440":2,"2450":1,"2455":1,"2459":1,"2468":1,"2474":1,"2479":2,"2500":2,"2508":1,"2515":1,"2525":1,"2576":3,"2779":2,"2792":3,"2859":1}}],["relentlessly",{"2":{"840":1}}],["reluctantly",{"2":{"859":1}}],["regression",{"2":{"2447":1,"2498":1,"2506":1,"2537":1}}],["regtype",{"2":{"2370":1,"2608":1}}],["region",{"2":{"1107":1,"1191":1,"2531":1,"2533":1}}],["registry",{"2":{"703":1,"704":1,"707":1,"713":1,"714":1,"2106":1,"2221":1,"2532":1,"2537":1,"2871":1}}],["registrationpath",{"2":{"1226":1,"1792":1,"1876":1}}],["registrationoptionspath",{"2":{"1226":1,"1792":1,"1876":1}}],["registrations",{"2":{"864":1,"873":1,"1249":1}}],["registration",{"0":{"360":1,"361":1,"364":1,"365":1,"1052":1,"1220":1,"1870":1},"1":{"365":1,"366":1},"2":{"308":1,"316":1,"357":1,"364":1,"366":1,"835":1,"867":1,"868":1,"869":1,"1037":1,"1052":2,"1210":1,"1213":2,"1214":3,"1215":1,"1217":2,"1218":2,"1220":8,"1221":1,"1224":1,"1225":1,"1226":2,"1232":2,"1233":1,"1235":2,"1238":1,"1253":1,"1792":14,"1870":3,"1874":1,"1875":1,"1876":2,"1883":1,"1885":1,"1893":2,"2410":1,"2422":3,"2424":1,"2435":1}}],["registerauthschemes",{"2":{"2412":1}}],["registerpath",{"2":{"1792":2}}],["registers",{"2":{"650":1,"664":1,"669":1,"1309":1,"1408":1,"1458":1,"1792":1,"2375":1,"2828":1,"2829":1,"2830":1}}],["register",{"2":{"360":3,"365":3,"813":2,"1068":1,"1098":1,"1150":1,"1218":1,"1220":4,"1226":2,"1233":1,"1238":1,"1244":1,"1521":1,"1792":4,"1870":2,"1876":2,"2147":3,"2389":2,"2407":1,"2420":1,"2422":1,"2575":2,"2834":1}}],["registering",{"2":{"308":1,"309":1,"2177":2,"2378":1}}],["registeredapps",{"2":{"1792":1}}],["registered",{"2":{"102":1,"109":1,"174":1,"1218":1,"1422":1,"1458":1,"1527":2,"1792":2,"1816":1,"1822":1,"1824":1,"2352":1,"2380":1,"2393":1,"2419":1,"2421":1,"2422":1,"2423":1,"2430":1,"2441":1,"2481":2}}],["regenerating",{"2":{"1094":1,"1409":1,"2221":1,"2742":1,"2878":1}}],["regeneration",{"2":{"871":1}}],["regenerates",{"2":{"872":1,"975":1,"978":1,"984":1,"985":1,"997":2,"1080":1,"1407":1,"1419":1,"2543":1,"2857":1}}],["regenerate",{"2":{"871":1,"1005":1,"1407":2,"2157":1}}],["regenerated",{"2":{"868":1,"869":1,"872":1,"873":1,"1379":1,"1414":1}}],["regexp",{"2":{"1427":2,"1428":1}}],["regex",{"0":{"1427":1,"1428":1,"2144":1},"2":{"812":1,"816":1,"817":1,"1424":1,"1428":2,"1788":1,"1792":3,"1795":1,"1909":1,"2140":1,"2141":2,"2142":1,"2144":3,"2146":3,"2148":1,"2446":1,"2575":5,"2762":1}}],["regularly",{"2":{"851":1,"1401":1}}],["regular",{"2":{"448":2,"663":1,"669":1,"1060":1,"1105":1,"1316":1,"1333":1,"1386":1,"1407":1,"1792":1,"2140":1,"2141":1,"2282":1,"2575":2}}],["regardless",{"2":{"317":1,"319":1,"335":1,"347":1,"422":1,"446":1,"639":1,"645":1,"650":1,"720":1,"872":1,"951":1,"953":1,"1129":1,"1161":1,"1176":1,"1326":1,"1567":1,"1741":1,"1792":3,"1856":1,"2005":1,"2006":1,"2011":1,"2102":1,"2106":1,"2107":1,"2271":1,"2284":1,"2289":1,"2323":1,"2354":1,"2380":1,"2413":1,"2414":1,"2421":1,"2424":1,"2432":1,"2445":1,"2446":1,"2451":1,"2468":1,"2481":2,"2494":1,"2495":1,"2509":1,"2537":2,"2692":1,"2795":1,"2800":1,"2833":1,"2858":1}}],["reusing",{"0":{"2531":1,"2869":1},"2":{"701":1,"2504":1}}],["reuse",{"0":{"1190":1},"1":{"1191":1,"1192":1,"1193":1},"2":{"884":1,"915":1,"1190":1,"1192":1,"1209":1,"1386":1,"1398":2,"1792":1,"1994":1,"2438":1,"2533":1,"2614":1,"2622":1}}],["reused",{"2":{"386":1,"914":1,"951":1,"1070":1,"1181":1,"1187":1,"1193":1,"1792":1,"1851":1,"1867":1,"2382":1,"2500":1,"2622":2}}],["reuses",{"2":{"214":1,"1430":1,"1459":1,"1743":1,"1792":1,"1825":2,"2375":1,"2481":2}}],["reusable",{"0":{"1187":1},"2":{"308":1,"916":1,"1792":1,"2094":1,"2111":1,"2359":1,"2407":1,"2531":1,"2537":1,"2869":1,"2871":1}}],["revalidate",{"2":{"1138":1,"1792":1,"2033":1,"2037":1,"2041":1,"2551":1}}],["revalidation",{"2":{"1101":1}}],["revolve",{"2":{"912":1}}],["revoke",{"2":{"292":1,"2156":1,"2438":1,"2542":1}}],["reveal",{"2":{"1792":2,"2059":1,"2634":1,"2635":1}}],["reveals",{"2":{"1258":1,"1269":1,"1398":1,"1832":1}}],["reverts",{"2":{"1957":1,"2379":1}}],["revert",{"2":{"1129":2}}],["reversible",{"2":{"876":1}}],["reversed",{"2":{"2533":1}}],["reverses",{"2":{"414":1,"2300":1,"2813":1}}],["reverse",{"0":{"1328":1,"1344":1,"2549":1},"1":{"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":2,"1346":2,"1347":2,"1348":2,"1349":1,"1350":1,"1351":1},"2":{"223":1,"266":1,"431":1,"433":2,"435":1,"835":1,"876":1,"1037":1,"1100":2,"1101":1,"1106":1,"1328":1,"1330":1,"1333":1,"1351":1,"1396":1,"1701":1,"1704":1,"1789":1,"1792":2,"1824":1,"1915":1,"2164":3,"2165":1,"2238":1,"2549":1,"2633":2,"2806":1,"2807":1,"2816":2,"2817":1}}],["revenue",{"2":{"325":2,"351":2,"566":2}}],["revives",{"2":{"2543":1}}],["revised",{"2":{"869":1}}],["revisit",{"2":{"324":1}}],["reviewed",{"2":{"1402":2,"1404":1}}],["reviewername",{"2":{"919":5,"2611":1}}],["reviewer",{"2":{"913":2}}],["reviewtext",{"2":{"919":5,"2611":1}}],["reviewid",{"2":{"919":5,"2611":1}}],["reviews",{"2":{"255":2,"256":2,"861":1,"913":5,"916":2,"919":6,"920":1,"2277":4,"2611":3}}],["review",{"2":{"255":5,"913":3,"916":1,"945":1,"1081":1,"1382":2,"2277":5}}],["refined",{"2":{"2531":1}}],["refactored",{"2":{"2576":1}}],["refactor",{"0":{"2249":1},"2":{"874":1,"994":1,"2240":1,"2258":1}}],["refactoring",{"0":{"2369":1},"2":{"872":1,"876":2,"1409":1,"1581":1,"2228":1,"2258":4}}],["refactors",{"2":{"872":2}}],["reflecting",{"2":{"995":1}}],["reflection",{"2":{"954":1,"1046":1,"2481":1,"2600":2,"2744":1}}],["reflect",{"0":{"2249":1},"2":{"841":1,"2259":1}}],["reflects",{"2":{"531":1,"772":1,"843":1}}],["refused",{"2":{"1830":1}}],["refuses",{"2":{"845":1,"1045":1,"1406":1}}],["refuse",{"2":{"841":1}}],["referrerpolicy",{"2":{"1792":1,"2015":1,"2016":1,"2019":1,"2027":1,"2028":1,"2029":1,"2632":1}}],["referrer",{"0":{"2019":1},"2":{"1100":1,"1792":6,"2016":1,"2019":4,"2028":1,"2632":6}}],["refer",{"2":{"814":1}}],["referencing",{"0":{"421":1,"445":1,"1398":1,"1744":1,"1929":1,"2345":1,"2346":1},"1":{"1399":1,"1745":1,"1746":1,"1747":1,"1930":1,"2346":1,"2347":1,"2348":1},"2":{"165":1,"167":1,"261":1,"266":2,"421":1,"445":1,"446":1,"528":1,"1079":1,"1150":1,"1399":1,"1746":1,"1747":1,"1929":2,"2111":1,"2322":1,"2329":1,"2344":1,"2347":1,"2372":2,"2378":1,"2484":1,"2504":1,"2532":1,"2607":1,"2741":1,"2868":1}}],["references",{"0":{"232":1},"2":{"109":2,"150":1,"156":1,"197":1,"575":1,"583":1,"587":1,"775":1,"841":1,"856":1,"876":1,"900":1,"913":2,"975":1,"977":1,"992":1,"997":1,"1079":1,"1176":1,"1213":1,"1307":1,"1409":1,"1527":2,"2007":1,"2328":1,"2389":1,"2394":1,"2545":1,"2840":1,"2854":1,"2868":2}}],["referenced",{"2":{"109":1,"390":1,"529":1,"704":1,"876":1,"1111":1,"1150":1,"1504":1,"1527":1,"1678":1,"1689":1,"1792":6,"2111":1,"2139":1,"2185":1,"2380":2,"2532":2,"2537":1,"2681":1,"2687":1,"2871":1}}],["reference",{"0":{"220":1,"221":1,"267":1,"1223":1,"1231":1,"1447":1,"1451":1,"1454":1,"1489":1,"1511":1,"1588":1,"1604":1,"1618":1,"1623":1,"1631":1,"1639":1,"1651":1,"1670":1,"1684":1,"1696":1,"1703":1,"1722":1,"1753":1,"1764":1,"1785":1,"1786":1,"1792":1,"1873":1,"1881":1,"1898":1,"1917":1,"1937":1,"1949":1,"1980":1,"1991":1,"2016":1,"2034":1,"2038":1,"2047":1,"2086":1,"2117":1,"2139":1,"2211":1,"2330":1,"2537":1,"2698":1,"2882":1},"1":{"221":1,"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1,"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"278":1,"279":1,"280":1,"281":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":2,"1233":2,"1234":2,"1235":2,"1236":2,"1237":2,"1238":2,"1239":2,"1240":1,"1241":1,"1685":1,"1697":1,"1786":1,"1787":2,"1788":2,"1789":2,"1790":2,"1791":2,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1874":1,"1875":1,"1876":1,"1877":1,"1878":1,"1879":1,"1880":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"2699":1,"2700":1},"2":{"98":1,"110":2,"121":1,"122":1,"141":1,"215":1,"217":1,"220":1,"296":1,"349":1,"384":1,"430":1,"431":1,"455":1,"456":1,"529":2,"577":1,"851":1,"1037":1,"1047":1,"1066":1,"1111":2,"1135":1,"1150":1,"1182":1,"1214":1,"1217":1,"1223":1,"1252":1,"1305":1,"1368":1,"1380":1,"1415":1,"1416":1,"1533":1,"1535":1,"1664":1,"1671":1,"1678":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1738":1,"1743":1,"1748":1,"1750":1,"1785":2,"1792":10,"1922":1,"2020":1,"2030":1,"2111":1,"2169":2,"2170":1,"2210":1,"2217":1,"2219":2,"2255":1,"2284":2,"2321":1,"2322":1,"2381":1,"2577":1,"2632":3,"2633":1,"2634":1,"2705":1,"2706":4,"2759":1,"2768":1,"2793":4,"2794":1,"2803":1,"2806":1,"2826":4,"2827":1,"2841":1,"2858":1,"2860":1,"2870":1}}],["refreshexpiresin",{"2":{"1455":1,"2554":1}}],["refreshing",{"2":{"1451":1,"1454":1}}],["refreshtoken",{"2":{"1222":1,"1452":1,"1455":1,"1456":1,"1792":2,"2554":2}}],["refresh",{"0":{"1452":1,"1456":1},"2":{"215":1,"292":2,"351":7,"531":2,"1053":2,"1062":2,"1064":2,"1067":1,"1147":1,"1183":1,"1203":1,"1208":1,"1450":1,"1451":1,"1452":4,"1453":1,"1454":2,"1456":5,"1458":3,"1460":3,"1511":1,"1738":1,"1792":11,"2174":1,"2375":7,"2377":1,"2423":1,"2432":1,"2438":2,"2554":8,"2615":2}}],["req",{"2":{"207":4,"215":1,"510":4,"527":1,"532":1,"1026":3,"1033":1,"1084":2,"1090":4,"1104":1,"1105":1,"1107":2,"1262":2,"1263":1,"1264":6,"1265":5,"1266":1,"1268":5,"1269":19,"1270":12,"1272":6,"1275":1,"1280":2,"1281":2,"1283":1,"1335":9,"1431":1,"1727":5,"1733":2,"1738":1,"1742":4,"1792":2,"2171":2,"2264":6,"2283":3,"2290":4,"2398":11,"2768":1,"2807":1}}],["requiring",{"2":{"336":1,"1098":1,"1100":1,"1146":1,"1792":2,"2034":1,"2035":1,"2207":1,"2254":1,"2282":1,"2625":1}}],["requiremnt",{"2":{"2824":1}}],["requirement",{"0":{"244":1,"1178":1,"1500":1},"2":{"133":1,"1077":1,"1499":1,"1792":2,"1832":1,"1910":1,"2212":1}}],["requirements",{"0":{"64":1,"2486":1},"2":{"871":1,"872":2,"1067":1,"1113":1,"1272":1,"1382":1,"1686":1,"1792":3,"1825":1}}],["requireauthenticatedsignin",{"2":{"1792":1}}],["requireauthorization",{"0":{"1827":1},"2":{"967":2,"1792":4,"1814":1,"1825":1,"1833":1,"2046":1,"2047":2,"2052":1,"2058":1,"2059":2,"2067":1,"2068":1,"2069":1,"2481":3,"2634":1,"2635":2,"2638":1}}],["required>",{"2":{"1792":2}}],["required",{"0":{"1200":1,"1318":1,"1605":1,"2497":1,"2688":1,"2711":1},"2":{"64":1,"182":1,"244":1,"297":1,"298":1,"381":1,"436":1,"448":1,"664":1,"768":1,"776":1,"803":1,"812":2,"813":2,"814":1,"816":2,"817":1,"863":1,"864":1,"869":1,"872":1,"880":1,"891":1,"892":1,"897":2,"911":1,"961":1,"1000":1,"1015":1,"1026":3,"1064":1,"1066":1,"1086":1,"1102":1,"1108":1,"1199":2,"1217":2,"1226":4,"1227":2,"1228":2,"1229":1,"1243":1,"1322":1,"1353":1,"1419":1,"1447":1,"1459":1,"1469":1,"1482":1,"1498":1,"1499":2,"1500":1,"1502":1,"1503":1,"1505":1,"1521":1,"1523":2,"1528":1,"1604":1,"1605":1,"1651":1,"1664":1,"1674":1,"1696":1,"1788":1,"1792":26,"1795":1,"1823":1,"1825":1,"1851":1,"1877":2,"1878":2,"1879":1,"1893":2,"1957":1,"2049":1,"2140":1,"2141":3,"2142":3,"2146":3,"2147":4,"2148":3,"2149":1,"2162":1,"2223":2,"2245":1,"2289":1,"2291":1,"2333":1,"2366":1,"2375":1,"2379":2,"2382":1,"2385":1,"2423":2,"2429":2,"2436":1,"2446":1,"2486":2,"2495":1,"2497":1,"2498":1,"2546":1,"2555":2,"2575":12,"2632":1,"2633":1,"2688":1,"2719":1,"2723":1,"2732":1,"2848":1,"2874":1}}],["requires",{"0":{"60":1},"2":{"13":1,"17":1,"63":1,"121":1,"150":1,"320":1,"390":1,"641":2,"673":1,"710":1,"718":1,"841":1,"871":2,"876":1,"879":1,"907":1,"909":1,"935":1,"945":1,"948":1,"1008":1,"1011":1,"1019":1,"1049":3,"1064":1,"1068":1,"1088":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":2,"1108":4,"1118":1,"1119":1,"1127":1,"1178":1,"1205":1,"1214":1,"1221":3,"1226":2,"1280":1,"1303":1,"1325":1,"1363":1,"1368":1,"1378":1,"1582":1,"1644":1,"1792":7,"1827":1,"1871":1,"1876":2,"2040":1,"2175":1,"2187":1,"2194":1,"2330":1,"2540":2,"2586":1,"2588":1,"2635":1,"2687":1,"2728":1,"2775":1,"2823":1}}],["requiresauthorization=false",{"2":{"2691":1,"2697":1,"2699":1,"2824":2}}],["requiresauthorization=true",{"2":{"2689":1}}],["requiresauthorizationonly",{"2":{"349":1,"356":1,"1792":1,"1897":1,"1898":1,"1909":1,"1910":1,"1911":1,"2225":1,"2419":1,"2431":1,"2433":1,"2434":1,"2435":1,"2436":1}}],["requiresauthorization",{"2":{"4":1,"10":1,"11":1,"298":1,"937":1,"998":1,"1045":1,"1062":1,"1792":1,"1836":1,"1843":1,"1863":2,"2175":1,"2187":1,"2201":1,"2431":1,"2433":1,"2686":1,"2687":1,"2697":2,"2701":1,"2728":1}}],["require",{"0":{"16":1,"18":1,"2058":1,"2199":1},"2":{"12":1,"13":1,"216":1,"224":1,"260":1,"294":1,"456":1,"482":1,"597":1,"741":1,"805":1,"821":1,"908":1,"957":1,"1008":1,"1026":4,"1095":1,"1097":2,"1100":1,"1113":1,"1114":1,"1189":1,"1200":1,"1221":1,"1279":1,"1320":2,"1378":3,"1460":1,"1465":1,"1467":1,"1486":1,"1616":1,"1696":1,"1699":1,"1792":9,"1843":1,"1867":1,"1871":1,"1898":1,"2024":4,"2029":1,"2047":1,"2049":1,"2160":1,"2175":1,"2199":2,"2270":1,"2375":1,"2401":1,"2438":2,"2632":1,"2634":2,"2635":1,"2761":1,"2820":1,"2858":1}}],["requestbody",{"2":{"1912":1,"2520":1}}],["requesting",{"2":{"1840":1,"2466":1}}],["requestinit",{"2":{"1063":2,"1415":1,"1561":2,"1792":2}}],["request=>request`",{"2":{"1792":1}}],["requestverificationtoken",{"2":{"1488":2,"1489":2,"1491":1,"1492":1,"1792":4}}],["requestparam",{"2":{"1366":1}}],["requestparamtype",{"2":{"436":1,"1924":1,"2222":1,"2509":1,"2511":2,"2513":1}}],["requestheader",{"2":{"1070":1,"1102":1,"1792":1,"1852":1,"2383":1}}],["requestheaderstimeout",{"2":{"1792":1,"1990":1,"1991":1,"1995":1}}],["requestheadersparametername",{"2":{"504":1,"1792":1,"1836":1,"1848":1,"1849":1,"1863":1,"2701":1}}],["requestheaderscontextkey",{"2":{"504":1,"1792":1,"1836":1,"1848":1,"2701":1}}],["requestheadersmode",{"2":{"504":1,"1792":2,"1836":1,"1848":1,"1863":1,"2551":1,"2701":1}}],["requested",{"2":{"312":1,"320":1,"1021":1,"1026":1,"1643":1,"2481":1}}],["request",{"0":{"206":1,"208":1,"226":1,"497":1,"507":1,"515":1,"639":1,"736":1,"1370":1,"1848":1,"1849":1,"2056":1,"2203":1,"2384":1,"2395":1,"2484":1,"2491":2,"2504":1,"2520":1,"2811":1},"1":{"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"504":1,"505":1,"506":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"525":1,"526":1,"1849":1,"2204":1},"2":{"31":1,"41":1,"68":1,"73":1,"74":1,"75":1,"77":2,"88":1,"106":2,"108":1,"165":2,"201":1,"202":2,"203":2,"206":1,"210":1,"211":2,"212":2,"214":9,"215":1,"216":1,"223":1,"226":3,"253":1,"260":1,"266":1,"302":1,"306":3,"377":1,"385":1,"386":3,"387":1,"388":2,"390":3,"394":1,"395":2,"414":8,"419":1,"423":2,"435":1,"436":4,"439":1,"446":5,"447":2,"448":3,"452":2,"453":6,"454":1,"466":1,"467":1,"468":1,"472":1,"497":2,"498":2,"499":1,"501":1,"502":2,"503":2,"505":1,"506":1,"507":2,"508":1,"510":7,"511":1,"512":1,"514":1,"515":3,"516":1,"517":1,"520":2,"521":1,"522":1,"524":1,"527":1,"529":1,"531":1,"534":2,"639":1,"689":1,"690":1,"710":1,"719":1,"720":2,"723":3,"733":6,"734":2,"735":1,"736":4,"738":4,"741":2,"752":1,"766":1,"778":1,"829":1,"835":1,"864":1,"869":1,"871":1,"872":2,"873":1,"874":2,"876":1,"894":2,"938":3,"948":1,"961":3,"967":1,"1010":2,"1016":2,"1017":1,"1019":2,"1024":1,"1030":1,"1036":1,"1041":1,"1057":1,"1058":9,"1062":4,"1063":4,"1064":1,"1067":1,"1069":3,"1070":6,"1071":1,"1074":1,"1077":1,"1078":1,"1101":2,"1102":12,"1104":2,"1105":4,"1106":2,"1107":1,"1109":1,"1110":1,"1122":1,"1137":1,"1147":2,"1150":3,"1160":1,"1161":1,"1162":2,"1167":1,"1169":1,"1225":1,"1228":2,"1229":1,"1230":1,"1232":1,"1233":1,"1234":2,"1236":1,"1255":1,"1258":1,"1317":1,"1326":2,"1329":1,"1331":1,"1332":1,"1333":1,"1340":1,"1341":1,"1342":2,"1349":1,"1363":2,"1366":3,"1370":1,"1396":1,"1398":1,"1401":1,"1407":3,"1410":2,"1413":3,"1415":1,"1416":2,"1423":1,"1426":1,"1427":1,"1430":1,"1431":2,"1434":1,"1469":4,"1475":1,"1476":3,"1483":4,"1487":1,"1521":1,"1523":2,"1539":4,"1540":2,"1541":3,"1542":6,"1543":5,"1548":4,"1559":1,"1561":1,"1564":1,"1567":2,"1568":2,"1569":4,"1571":1,"1572":3,"1575":2,"1582":1,"1618":1,"1620":5,"1639":1,"1643":1,"1645":1,"1674":2,"1676":1,"1677":1,"1678":1,"1696":1,"1718":1,"1722":2,"1723":1,"1728":1,"1730":1,"1731":2,"1732":1,"1733":2,"1736":1,"1738":5,"1742":2,"1743":3,"1745":1,"1746":1,"1759":3,"1787":1,"1792":72,"1794":1,"1822":1,"1823":1,"1824":1,"1825":1,"1827":2,"1830":2,"1835":1,"1836":1,"1848":4,"1849":1,"1850":1,"1851":2,"1852":3,"1856":1,"1864":2,"1878":2,"1879":1,"1880":1,"1882":1,"1883":1,"1884":1,"1898":1,"1912":1,"1917":3,"1918":1,"1922":2,"1924":3,"1925":5,"1926":3,"1928":2,"1929":1,"1950":1,"1951":2,"1952":2,"1953":2,"1954":2,"1955":2,"1956":1,"1957":1,"1958":3,"1961":1,"1974":1,"1990":1,"1991":6,"2040":2,"2047":1,"2056":1,"2060":1,"2110":1,"2123":1,"2124":1,"2164":1,"2171":2,"2182":1,"2183":1,"2184":11,"2185":2,"2204":2,"2222":4,"2223":2,"2226":1,"2247":1,"2250":1,"2255":4,"2258":1,"2264":7,"2267":1,"2270":1,"2277":2,"2278":1,"2282":1,"2283":1,"2286":1,"2290":2,"2302":4,"2305":1,"2320":1,"2322":1,"2323":1,"2333":1,"2346":1,"2347":1,"2357":1,"2363":1,"2372":2,"2379":4,"2380":1,"2382":3,"2383":3,"2394":1,"2395":4,"2397":1,"2420":1,"2421":2,"2422":1,"2423":1,"2424":1,"2428":1,"2438":2,"2465":1,"2466":1,"2468":1,"2470":1,"2476":1,"2481":1,"2482":1,"2483":2,"2484":1,"2489":1,"2491":2,"2493":2,"2502":7,"2504":2,"2505":2,"2509":2,"2512":1,"2515":1,"2517":2,"2518":3,"2519":2,"2520":5,"2521":1,"2523":1,"2529":11,"2530":1,"2536":1,"2549":8,"2555":2,"2572":1,"2580":1,"2597":1,"2607":1,"2614":2,"2615":5,"2621":1,"2622":3,"2656":1,"2674":1,"2701":3,"2712":1,"2723":1,"2732":1,"2759":1,"2760":2,"2762":6,"2764":1,"2765":2,"2767":2,"2806":2,"2807":2,"2811":1,"2812":2,"2813":1,"2814":3,"2815":1,"2829":2,"2830":2,"2831":1,"2833":2,"2835":1,"2848":1,"2865":8,"2881":1}}],["requestsendpoint",{"2":{"2482":1}}],["requests",{"0":{"1030":1,"1090":1,"2420":1,"2764":1},"1":{"2421":1,"2422":1,"2423":1,"2424":1},"2":{"3":1,"10":1,"16":1,"25":1,"60":1,"64":3,"120":1,"201":1,"214":3,"216":1,"431":1,"433":1,"451":1,"480":4,"855":1,"934":1,"957":1,"1007":1,"1012":1,"1014":1,"1023":1,"1033":1,"1067":1,"1069":1,"1070":2,"1090":1,"1091":1,"1105":1,"1106":2,"1107":2,"1137":1,"1141":1,"1142":1,"1147":1,"1157":1,"1158":3,"1160":1,"1161":3,"1162":2,"1164":1,"1165":5,"1166":1,"1168":2,"1169":2,"1170":1,"1171":1,"1177":1,"1180":1,"1241":1,"1283":1,"1285":1,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"1328":2,"1329":1,"1340":1,"1345":1,"1363":1,"1386":1,"1420":1,"1422":1,"1423":1,"1430":2,"1449":1,"1487":1,"1489":1,"1492":1,"1515":1,"1620":1,"1639":4,"1640":1,"1641":1,"1644":2,"1645":1,"1704":1,"1720":1,"1723":1,"1739":1,"1743":4,"1745":1,"1753":1,"1755":1,"1757":1,"1783":1,"1789":1,"1792":39,"1807":1,"1823":2,"1827":1,"1840":2,"1848":1,"1851":1,"1890":1,"1915":1,"1917":1,"1947":1,"1948":1,"1949":1,"1951":3,"1952":3,"1953":2,"1954":4,"1955":1,"1958":2,"1960":1,"1980":1,"1982":1,"1994":1,"2016":1,"2019":2,"2025":2,"2034":1,"2087":1,"2172":1,"2195":1,"2224":1,"2225":1,"2257":3,"2264":2,"2274":1,"2282":1,"2287":1,"2346":1,"2380":1,"2382":1,"2398":1,"2419":1,"2422":1,"2425":1,"2441":1,"2459":1,"2463":1,"2464":1,"2465":2,"2466":1,"2470":1,"2482":2,"2486":1,"2491":1,"2502":2,"2504":1,"2549":1,"2580":1,"2614":1,"2615":4,"2627":1,"2632":2,"2744":1,"2759":1,"2765":1,"2840":1,"2841":1}}],["remarks",{"2":{"1386":1,"2247":1,"2358":1}}],["remarkable",{"2":{"863":1}}],["remains",{"2":{"173":1,"362":1,"848":1,"1091":1,"1171":1,"1266":1,"1280":1,"1350":1,"1569":1,"1830":1,"1851":1,"2382":1,"2389":2,"2423":1,"2429":1,"2481":1,"2532":1,"2731":1}}],["remain",{"2":{"101":1,"848":1,"944":1,"975":1,"1097":1,"1227":1,"1271":1,"1405":1,"1522":1,"1609":1,"1877":1,"2278":1,"2297":1,"2416":1,"2504":1,"2511":1,"2611":1,"2661":1}}],["remaining",{"2":{"75":1,"1097":1,"1925":1,"2459":1,"2521":1,"2533":1}}],["remember",{"2":{"852":1,"854":1,"1401":1,"1402":1,"1403":1,"2103":1,"2427":2}}],["removal",{"2":{"2319":1}}],["removetraceid",{"2":{"1669":1,"1670":1,"1677":1,"1678":2,"1792":1,"2255":1,"2267":1}}],["removetypeurl",{"2":{"1669":1,"1670":1,"1671":1,"1676":1,"1678":2,"1792":2,"2255":2}}],["removed",{"0":{"2250":1,"2376":1,"2389":1,"2660":1},"2":{"996":1,"1071":1,"1096":1,"1382":4,"1385":1,"1464":3,"1513":1,"1518":1,"2223":1,"2224":1,"2242":1,"2250":1,"2255":3,"2258":1,"2265":1,"2267":2,"2376":3,"2389":1,"2406":1,"2436":1,"2446":1,"2448":1,"2487":1,"2660":1,"2673":1}}],["removes",{"2":{"849":1,"865":1,"947":1,"992":1,"1518":2,"1580":1,"1792":1,"2265":2,"2558":1,"2576":1,"2875":1}}],["remove",{"2":{"849":1,"851":1,"1405":1,"1670":2,"1774":1,"1792":2,"2255":2,"2258":1,"2389":1,"2394":1}}],["removing",{"2":{"174":1,"851":1,"1324":1}}],["remoteipaddress",{"2":{"1957":1,"2379":1}}],["remote",{"2":{"848":1,"876":1}}],["rented",{"2":{"2402":1,"2403":1}}],["rents",{"2":{"2402":1}}],["rent",{"0":{"2402":1},"2":{"2397":1,"2614":1}}],["rentals",{"0":{"2403":1},"2":{"2226":1}}],["renowned",{"2":{"851":1}}],["rendermessage",{"2":{"2836":2}}],["renderpost",{"2":{"996":2}}],["renderuserrow",{"2":{"996":2}}],["renders",{"2":{"679":1,"831":1,"965":1,"966":1,"1792":3,"2075":1,"2076":1,"2077":1,"2466":1,"2535":1,"2651":1,"2652":1}}],["renderers",{"0":{"2650":1},"1":{"2651":1,"2652":1,"2653":1},"2":{"2233":1}}],["renderer",{"2":{"675":1,"1105":1,"1107":1,"2400":1}}],["rendered",{"2":{"415":1,"673":2,"919":1,"1432":1,"1792":1,"2016":1,"2018":1,"2072":1,"2303":1,"2632":1,"2650":1,"2813":1}}],["render",{"2":{"415":6,"420":4,"423":2,"426":2,"614":1,"616":1,"675":2,"834":2,"996":2,"1105":1,"1107":1,"1432":1,"2083":1,"2303":4,"2339":1,"2397":1,"2535":1,"2813":5}}],["rendering",{"0":{"426":1},"2":{"161":1,"164":1,"228":1,"414":1,"673":2,"680":1,"682":1,"726":1,"868":1,"949":1,"966":1,"1043":1,"1099":1,"1104":1,"1105":1,"1396":1,"1789":1,"1792":1,"2072":2,"2300":1,"2339":1,"2372":3,"2396":1,"2398":2,"2403":1,"2650":1,"2813":1}}],["renaming",{"2":{"382":1,"384":1,"872":1,"1409":1,"2221":1,"2228":1,"2334":1,"2540":1}}],["renames",{"2":{"382":1,"2320":1,"2332":1,"2334":1}}],["renamed",{"0":{"376":1,"2279":1},"2":{"376":1,"384":1,"565":2,"567":1,"875":1,"996":3,"1409":1,"2279":1,"2369":2,"2406":1,"2411":1,"2436":1,"2558":1}}],["rename",{"0":{"372":1,"373":1,"374":1,"382":1,"2332":1,"2334":1},"2":{"170":1,"237":1,"238":1,"369":1,"372":2,"374":2,"376":1,"378":3,"384":2,"559":1,"568":1,"588":1,"617":1,"626":1,"872":1,"985":1,"1409":2,"1436":1,"2013":2,"2249":1,"2323":5,"2332":5,"2333":5,"2372":2,"2540":1,"2769":1,"2845":1,"2852":1,"2859":1}}],["repetition",{"2":{"2359":1,"2540":1,"2546":1,"2845":1}}],["repeating",{"2":{"1424":1,"1428":1,"2359":1}}],["repeats",{"2":{"847":1,"1165":1}}],["repeatable",{"2":{"690":1,"704":1,"709":1,"714":1,"924":1,"976":2,"1385":2,"1792":1,"2529":1,"2533":1,"2858":1,"2865":1,"2870":1}}],["repeat",{"2":{"319":1,"930":3,"1254":2}}],["repeatedly",{"2":{"2615":1,"2731":1}}],["repeated",{"2":{"310":1,"918":2,"1338":1,"1823":1,"2221":1,"2359":1,"2500":1,"2530":1,"2546":1,"2815":1}}],["reproducible",{"2":{"1037":1,"1381":1,"1382":2}}],["reproduces",{"2":{"1856":1,"2455":1}}],["reproduce",{"2":{"871":1,"1078":1,"2110":1,"2530":1}}],["reprocess",{"2":{"902":1}}],["represents",{"2":{"1792":1}}],["representation",{"2":{"852":1,"1403":1,"2586":1}}],["representations",{"2":{"848":1}}],["represent",{"2":{"458":1,"2689":1}}],["repos",{"2":{"2105":1}}],["repositories",{"2":{"851":1,"873":1,"1401":1}}],["repository",{"0":{"867":1},"2":{"840":1,"849":1,"851":6,"852":1,"860":1,"866":1,"867":1,"868":1,"869":2,"871":2,"872":1,"873":2,"970":1,"1006":2,"1207":1,"1281":1,"1366":2,"1368":1,"1382":1,"1400":1,"1405":2,"1408":1,"1417":1,"1579":1,"2160":1,"2162":1,"2792":2,"2839":1,"2856":1,"2860":1}}],["repo",{"0":{"1579":1},"2":{"1382":2,"1405":1,"1441":1,"2465":1}}],["reporter",{"2":{"2880":2}}],["reported",{"2":{"877":1,"1792":2,"1818":1,"1819":1,"1823":1,"2100":1,"2101":1,"2106":1,"2528":4,"2531":1,"2763":1,"2864":4,"2869":1}}],["reportid=3",{"2":{"415":2,"2303":2,"2813":1}}],["reporting",{"0":{"148":1,"2535":1,"2880":1},"2":{"3":1,"148":1,"844":1,"876":1,"956":1,"986":1,"1184":1,"1205":2,"1206":2,"1208":1,"1410":1,"2221":1,"2412":1,"2742":1}}],["report",{"0":{"1188":1},"2":{"97":1,"105":1,"117":2,"137":2,"148":1,"167":2,"277":1,"325":9,"386":4,"415":11,"423":6,"429":1,"448":1,"492":3,"543":3,"544":3,"677":3,"678":3,"836":1,"887":2,"947":1,"948":2,"949":1,"956":3,"960":3,"964":4,"965":1,"971":3,"986":1,"1042":3,"1044":1,"1080":1,"1142":1,"1161":3,"1176":1,"1179":2,"1183":1,"1184":2,"1185":3,"1187":2,"1188":3,"1189":2,"1191":2,"1192":7,"1193":10,"1196":1,"1200":2,"1202":1,"1203":3,"1207":1,"1254":1,"1373":1,"1413":1,"1632":3,"1792":3,"2076":3,"2078":2,"2079":5,"2094":2,"2102":1,"2104":2,"2107":3,"2158":1,"2207":2,"2303":8,"2304":4,"2310":1,"2322":2,"2452":1,"2528":1,"2535":3,"2537":3,"2546":1,"2651":1,"2652":1,"2653":4,"2800":2,"2805":1,"2813":12,"2833":1,"2864":1,"2876":1,"2880":1}}],["reports",{"2":{"3":1,"353":2,"372":4,"677":2,"836":1,"866":1,"1001":1,"1037":1,"1073":2,"1203":1,"1208":1,"1632":2,"1764":1,"1766":1,"1792":5,"2007":1,"2035":1,"2076":2,"2078":1,"2079":1,"2107":1,"2207":1,"2314":1,"2319":3,"2327":2,"2364":1,"2415":2,"2526":1,"2537":2,"2634":2,"2731":2,"2845":3,"2846":3,"2879":1}}],["replenishment",{"2":{"1953":1}}],["replenishmentperiodseconds",{"2":{"477":1,"1160":1,"1792":1,"1953":2,"1960":1,"2257":1,"2443":1,"2551":1}}],["replenish",{"2":{"1951":1,"1952":1,"1953":1}}],["replay",{"2":{"1078":1,"1213":1}}],["replacing",{"2":{"369":1,"868":1,"2621":1}}],["replacement",{"0":{"2039":1},"2":{"1385":2,"2376":1,"2627":1,"2645":1}}],["replaces",{"2":{"388":1,"867":1,"1094":1,"1388":1,"2038":1}}],["replace",{"2":{"347":1,"348":2,"355":2,"878":1,"880":1,"883":1,"884":1,"886":1,"888":2,"904":2,"910":1,"928":1,"929":1,"934":1,"935":1,"936":1,"956":1,"961":1,"979":1,"980":1,"982":1,"988":1,"990":1,"994":1,"1055":1,"1056":2,"1057":1,"1060":1,"1076":3,"1080":1,"1086":1,"1125":3,"1126":6,"1134":1,"1203":1,"1209":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1357":1,"1362":1,"1364":1,"1368":2,"1379":1,"1386":1,"1387":1,"1390":1,"1393":1,"1398":1,"1407":1,"1419":1,"1427":1,"1428":1,"1429":1,"1436":1,"1438":1,"1442":1,"1604":1,"1792":4,"2037":1,"2106":1,"2111":1,"2156":2,"2297":1,"2432":1,"2532":1,"2542":2,"2546":1,"2742":1,"2878":1}}],["replaced",{"2":{"212":1,"386":1,"595":1,"865":1,"926":1,"983":1,"998":1,"1017":1,"1577":1,"1684":1,"1733":1,"1792":3,"2039":2,"2264":1,"2267":1,"2456":1,"2559":1,"2614":1,"2622":1,"2645":1,"2764":1}}],["reply",{"2":{"857":1}}],["replication",{"2":{"1205":1}}],["replicating",{"2":{"1205":1}}],["replicated",{"2":{"848":1}}],["replica3",{"2":{"1175":1}}],["replica2",{"2":{"1173":1,"1175":1,"1176":1,"1177":2,"1627":1,"1629":2,"2266":1}}],["replica1",{"2":{"1173":1,"1177":1,"1627":1,"1629":1,"2266":1}}],["replicas",{"2":{"844":2,"1176":2,"1177":2,"1178":1,"1180":1,"1382":1,"1614":1,"1632":2}}],["replica",{"0":{"149":1,"1176":1},"2":{"149":1,"844":1,"967":1,"1101":1,"1174":1,"1178":1,"1181":1,"1205":1,"1206":1,"1208":1,"1629":1,"1633":1,"1792":1,"2063":1}}],["retention",{"2":{"2804":1}}],["returing",{"2":{"2267":1}}],["returnerrorasync",{"2":{"2559":1,"2615":1}}],["returned",{"2":{"119":1,"182":1,"186":1,"188":2,"286":2,"297":3,"298":1,"301":1,"303":1,"304":1,"412":1,"415":1,"424":1,"438":1,"549":1,"609":2,"613":1,"615":1,"700":1,"778":1,"885":1,"901":1,"934":1,"980":1,"1067":1,"1105":1,"1107":1,"1133":2,"1149":1,"1239":1,"1244":1,"1386":1,"1390":1,"1480":1,"1481":1,"1511":1,"1517":2,"1664":1,"1742":1,"1792":12,"1820":1,"1823":1,"1824":2,"1853":1,"1915":1,"1920":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1958":2,"2141":1,"2265":2,"2271":1,"2290":1,"2291":1,"2293":1,"2296":2,"2300":1,"2303":1,"2307":1,"2339":1,"2352":1,"2384":1,"2402":1,"2420":2,"2421":1,"2468":1,"2494":1,"2495":1,"2549":1,"2554":1,"2580":1,"2611":1,"2813":1,"2866":1}}],["returnnpgsqlexceptionmessage",{"2":{"2255":2}}],["returntopathquerystringkey",{"2":{"1684":1,"1792":1}}],["returntopath",{"2":{"1683":1,"1684":1,"1698":1,"1792":2}}],["returning",{"0":{"119":1,"914":1,"916":1,"1149":1,"1332":1,"1517":1},"2":{"87":1,"119":1,"120":1,"186":1,"237":1,"248":1,"304":1,"310":1,"360":2,"361":1,"365":1,"377":1,"567":2,"587":1,"592":1,"673":3,"849":1,"880":1,"885":1,"915":1,"916":2,"918":2,"928":1,"934":1,"973":1,"982":1,"1021":1,"1045":1,"1067":1,"1077":1,"1084":1,"1098":1,"1100":1,"1149":1,"1185":1,"1193":1,"1214":1,"1215":1,"1232":1,"1234":1,"1235":1,"1309":1,"1321":1,"1329":1,"1351":1,"1357":1,"1363":1,"1372":1,"1378":1,"1402":1,"1410":1,"1458":2,"1511":1,"1517":1,"1689":2,"1792":7,"1928":1,"1956":1,"1973":1,"2009":1,"2072":2,"2075":1,"2077":1,"2147":1,"2186":2,"2265":2,"2270":1,"2293":1,"2320":2,"2324":1,"2333":1,"2357":1,"2375":1,"2379":1,"2389":1,"2437":1,"2482":1,"2528":1,"2549":1,"2618":2,"2829":2,"2836":1,"2851":1,"2864":1}}],["return",{"0":{"32":1,"286":1,"299":1,"309":1,"553":1,"554":1,"555":1,"982":1,"983":1,"1020":1,"1396":1,"2337":1,"2726":1},"1":{"33":1,"34":1,"35":1},"2":{"29":1,"34":1,"35":4,"39":1,"60":1,"90":1,"186":2,"207":2,"208":1,"209":3,"227":2,"229":1,"238":1,"261":1,"264":1,"298":2,"299":1,"308":1,"309":1,"313":3,"364":1,"414":1,"415":1,"423":1,"426":1,"427":1,"428":1,"429":3,"439":3,"449":2,"452":2,"454":1,"484":1,"528":1,"548":1,"567":1,"568":1,"581":1,"583":1,"586":1,"587":1,"588":1,"607":1,"609":1,"614":1,"615":2,"617":1,"626":1,"673":1,"720":1,"722":1,"748":1,"750":1,"751":1,"752":1,"755":1,"756":1,"760":1,"761":2,"763":1,"764":1,"765":1,"766":1,"770":1,"771":2,"773":1,"774":1,"777":1,"812":1,"813":1,"814":1,"815":1,"823":1,"852":1,"869":1,"872":2,"883":3,"884":1,"885":3,"888":2,"894":2,"896":1,"903":1,"904":1,"914":4,"915":1,"916":7,"918":4,"919":1,"928":1,"929":3,"934":1,"935":1,"956":1,"974":1,"979":1,"982":2,"983":6,"984":1,"985":1,"989":1,"990":1,"991":2,"995":3,"996":4,"1003":1,"1005":1,"1020":2,"1021":1,"1026":5,"1029":1,"1040":1,"1049":1,"1060":2,"1063":2,"1065":1,"1076":1,"1095":1,"1097":5,"1105":6,"1107":1,"1129":3,"1133":1,"1134":1,"1179":1,"1189":1,"1191":1,"1192":1,"1197":1,"1214":1,"1215":1,"1216":2,"1231":1,"1232":3,"1233":1,"1234":4,"1235":3,"1236":3,"1237":1,"1238":1,"1239":4,"1279":1,"1318":1,"1332":4,"1335":5,"1338":5,"1339":4,"1342":1,"1346":1,"1347":1,"1348":1,"1357":1,"1366":9,"1368":1,"1376":1,"1378":3,"1386":4,"1390":2,"1391":2,"1396":3,"1398":1,"1402":2,"1408":1,"1409":1,"1410":2,"1415":1,"1416":1,"1423":2,"1427":2,"1431":1,"1432":1,"1460":1,"1480":1,"1481":1,"1504":3,"1557":1,"1558":1,"1567":1,"1568":2,"1574":2,"1575":1,"1651":2,"1655":2,"1671":1,"1684":2,"1686":1,"1688":1,"1689":2,"1727":2,"1736":2,"1742":1,"1745":1,"1755":1,"1781":1,"1782":1,"1792":36,"1824":2,"1851":1,"1882":2,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1921":2,"1924":1,"1926":1,"1967":1,"2000":1,"2009":1,"2033":1,"2034":2,"2035":1,"2042":1,"2072":1,"2147":1,"2156":1,"2164":2,"2177":2,"2181":1,"2186":1,"2206":1,"2247":2,"2255":4,"2258":1,"2264":2,"2267":2,"2271":2,"2273":1,"2283":1,"2290":1,"2293":2,"2302":1,"2303":1,"2304":1,"2310":4,"2313":2,"2318":1,"2320":1,"2324":1,"2330":1,"2338":2,"2339":3,"2346":1,"2375":1,"2382":1,"2468":1,"2481":1,"2498":1,"2542":1,"2549":5,"2572":1,"2575":1,"2608":1,"2614":1,"2672":1,"2725":2,"2760":1,"2762":1,"2766":1,"2774":1,"2807":1,"2810":2,"2815":4,"2840":1,"2841":1,"2842":1,"2852":1,"2853":1,"2854":1,"2855":3,"2858":1}}],["returns",{"0":{"581":1,"1134":1,"2337":1,"2491":1,"2618":1,"2725":1,"2854":1},"1":{"582":1,"583":1,"584":1,"585":1,"586":1,"587":1,"588":1},"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"25":2,"30":1,"34":1,"37":2,"38":3,"39":2,"40":2,"41":1,"48":1,"50":1,"60":1,"61":2,"62":3,"63":1,"71":1,"72":1,"104":1,"115":1,"116":1,"117":1,"119":2,"128":1,"136":1,"137":1,"139":1,"157":1,"184":1,"186":2,"187":2,"206":1,"207":1,"208":1,"209":1,"238":1,"247":1,"248":1,"249":1,"250":1,"251":1,"254":1,"255":1,"256":1,"257":1,"263":3,"264":2,"285":1,"286":1,"288":1,"289":1,"290":1,"291":2,"292":1,"297":3,"298":1,"299":3,"301":2,"305":2,"308":2,"309":2,"312":1,"313":1,"322":1,"324":1,"330":1,"332":1,"333":1,"334":1,"335":1,"351":1,"360":1,"361":1,"365":1,"366":1,"374":1,"386":1,"401":1,"405":1,"406":1,"408":2,"415":2,"423":1,"426":1,"427":1,"428":1,"429":1,"435":1,"436":1,"438":1,"439":1,"447":1,"449":1,"451":2,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"480":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"494":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"524":1,"529":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"549":1,"551":3,"565":1,"575":1,"582":3,"583":1,"584":3,"585":3,"586":3,"587":4,"592":1,"593":1,"594":1,"611":1,"614":1,"615":1,"616":1,"646":1,"658":1,"677":1,"679":2,"690":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"750":1,"751":1,"752":1,"755":1,"756":1,"760":1,"761":1,"764":2,"765":1,"766":1,"770":1,"771":1,"774":2,"777":1,"797":1,"798":1,"799":1,"803":1,"811":1,"812":1,"813":1,"814":1,"815":1,"818":1,"819":1,"826":2,"829":1,"833":1,"835":1,"843":1,"852":1,"871":1,"872":2,"881":1,"883":1,"884":1,"885":1,"886":1,"888":1,"904":2,"914":4,"915":2,"916":4,"918":2,"928":1,"929":1,"934":1,"935":1,"936":1,"937":1,"956":1,"975":1,"979":2,"980":2,"982":5,"983":4,"986":2,"988":2,"989":2,"990":2,"991":1,"994":1,"995":2,"1002":1,"1021":1,"1023":1,"1029":1,"1033":1,"1041":1,"1042":1,"1049":1,"1053":1,"1054":1,"1055":4,"1057":1,"1058":1,"1060":1,"1068":2,"1069":1,"1070":1,"1076":2,"1105":4,"1113":1,"1133":1,"1134":1,"1135":1,"1138":1,"1139":1,"1141":1,"1142":1,"1148":2,"1149":1,"1179":2,"1185":1,"1188":1,"1190":1,"1191":3,"1192":1,"1193":5,"1197":1,"1203":1,"1214":2,"1215":1,"1216":2,"1220":3,"1221":3,"1222":4,"1232":1,"1234":1,"1235":2,"1236":1,"1239":1,"1255":2,"1279":1,"1308":1,"1310":1,"1331":3,"1332":2,"1337":2,"1338":4,"1339":1,"1345":2,"1347":1,"1348":1,"1357":2,"1362":2,"1368":1,"1369":1,"1370":1,"1372":1,"1376":3,"1378":1,"1386":2,"1387":1,"1390":1,"1393":1,"1396":2,"1399":1,"1408":1,"1410":1,"1412":1,"1416":1,"1427":1,"1431":1,"1455":1,"1456":1,"1458":6,"1470":1,"1504":1,"1517":1,"1518":3,"1547":1,"1567":1,"1632":1,"1655":1,"1664":2,"1688":1,"1689":1,"1727":1,"1733":1,"1736":1,"1742":3,"1745":1,"1770":2,"1792":17,"1824":4,"1825":2,"1855":3,"1885":1,"1920":1,"1921":1,"1922":1,"1924":1,"1926":1,"1930":1,"1957":1,"1958":2,"1973":1,"1974":2,"2049":1,"2050":1,"2051":1,"2052":1,"2076":1,"2078":2,"2079":1,"2147":1,"2149":1,"2156":1,"2164":1,"2171":3,"2176":3,"2177":2,"2180":1,"2181":1,"2183":1,"2184":2,"2186":1,"2187":3,"2194":1,"2223":1,"2247":2,"2258":1,"2264":1,"2265":4,"2267":1,"2277":4,"2283":2,"2284":1,"2290":3,"2292":1,"2293":2,"2294":2,"2303":2,"2304":1,"2310":1,"2313":1,"2320":1,"2337":9,"2338":1,"2339":4,"2344":4,"2346":1,"2375":6,"2379":1,"2402":1,"2422":1,"2466":1,"2470":1,"2472":2,"2489":1,"2529":1,"2542":1,"2546":1,"2549":4,"2554":1,"2572":1,"2575":1,"2580":2,"2586":2,"2587":2,"2588":1,"2589":2,"2596":4,"2607":2,"2634":5,"2635":4,"2665":2,"2678":1,"2725":1,"2762":1,"2764":1,"2766":1,"2767":2,"2775":1,"2802":1,"2806":1,"2807":1,"2809":1,"2810":2,"2812":1,"2813":2,"2815":1,"2822":1,"2824":1,"2836":1,"2839":1,"2840":1,"2842":2,"2854":7,"2855":1,"2859":1}}],["retyping",{"2":{"2228":1}}],["retypes",{"2":{"2221":1,"2332":1}}],["retype",{"0":{"2332":1},"2":{"170":1,"237":1,"369":1,"568":1,"2013":1,"2323":1,"2332":2,"2333":1,"2372":1,"2540":1,"2546":1,"2845":1,"2859":1}}],["retainedfilecountlimit",{"2":{"1792":1,"1800":1,"1804":2,"1810":1,"2804":1}}],["retain",{"2":{"1792":1,"1804":1}}],["retried",{"2":{"1741":1,"1742":1,"1792":1,"2253":1,"2289":1,"2290":1,"2438":1}}],["retrieval",{"2":{"1244":1}}],["retrieving",{"2":{"903":1}}],["retrieves",{"2":{"1655":1,"1792":1}}],["retrieve",{"2":{"187":1,"1362":1,"1651":1,"1664":1,"1886":1,"2294":1,"2590":1}}],["retries",{"0":{"1152":1,"1153":1,"2765":1},"1":{"1154":1},"2":{"213":6,"575":3,"1032":3,"1105":1,"1151":2,"1154":2,"1177":2,"1179":1,"1180":2,"1181":2,"1589":2,"1590":1,"1591":1,"1598":1,"1601":1,"1623":1,"1625":1,"1739":1,"1740":2,"1741":5,"1775":1,"1792":2,"2287":1,"2288":2,"2289":5,"2759":1,"2765":2}}],["retrying",{"2":{"2258":1}}],["retryoptions",{"2":{"1152":2,"1177":1,"1617":1,"1622":2,"1625":1,"1633":1,"1792":1}}],["retrysequenceseconds=1",{"2":{"2824":1,"2825":1}}],["retrysequenceseconds",{"2":{"575":1,"577":3,"1152":3,"1153":1,"1154":3,"1177":2,"1587":1,"1589":1,"1590":2,"1597":3,"1598":1,"1617":1,"1622":1,"1623":1,"1625":1,"1633":1,"1792":2}}],["retry",{"0":{"213":1,"569":1,"576":1,"1032":1,"1151":1,"1154":1,"1586":1,"1590":1,"1622":1,"1623":1,"1625":1,"1739":1,"2287":1},"1":{"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"580":1,"1152":1,"1153":1,"1154":1,"1155":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1600":1,"1601":1,"1602":1,"1623":1,"1624":1,"1625":1,"1740":1,"1741":1,"1742":1,"2288":1,"2289":1,"2290":1},"2":{"142":2,"203":3,"213":9,"214":1,"231":2,"569":3,"570":4,"572":1,"573":1,"574":1,"575":3,"577":5,"578":2,"580":2,"837":1,"868":1,"869":1,"1011":1,"1032":3,"1037":1,"1101":4,"1104":2,"1105":3,"1108":2,"1109":1,"1113":1,"1121":1,"1126":1,"1135":1,"1151":1,"1152":5,"1153":3,"1154":4,"1155":3,"1171":1,"1179":1,"1180":1,"1181":3,"1182":6,"1217":1,"1224":1,"1250":2,"1351":1,"1586":1,"1588":4,"1589":1,"1590":5,"1592":1,"1599":6,"1600":2,"1601":1,"1602":2,"1617":1,"1622":1,"1623":2,"1739":1,"1740":6,"1741":2,"1742":2,"1790":2,"1792":9,"1797":2,"1874":1,"1958":1,"2222":1,"2230":2,"2287":2,"2288":6,"2289":4,"2290":2,"2320":1,"2329":1,"2372":1,"2459":1,"2466":1,"2470":1,"2502":1,"2505":1,"2765":4,"2824":1,"2825":1}}],["rec",{"2":{"2398":4}}],["recipients",{"0":{"2833":1},"2":{"1792":1,"2827":1}}],["recipes",{"0":{"2796":1},"1":{"2797":1,"2798":1,"2799":1,"2800":1,"2801":1},"2":{"1799":1,"1811":1}}],["recipe",{"0":{"1065":1,"1424":1},"2":{"1429":1}}],["recall",{"2":{"1324":1}}],["recap",{"2":{"1073":1}}],["recreation",{"2":{"984":1,"985":1}}],["recreating",{"2":{"983":1,"985":1,"993":1}}],["recreates",{"2":{"982":1,"997":2}}],["recreated",{"0":{"985":1},"2":{"978":1,"2110":1,"2530":1}}],["recreate",{"2":{"976":2,"977":1,"1005":1,"1076":1}}],["recursively",{"2":{"1792":2,"1974":1,"2537":1,"2607":1,"2611":1,"2825":1}}],["recursive",{"0":{"2371":1},"2":{"857":1,"860":2,"1096":1,"1792":1,"2003":2,"2228":1,"2330":1,"2356":1,"2371":2}}],["receiving",{"2":{"1991":1,"2496":1}}],["received",{"2":{"1792":1,"2395":1,"2513":1,"2523":1}}],["receives",{"0":{"2831":1},"2":{"31":1,"68":1,"212":1,"361":2,"362":1,"414":1,"419":1,"439":1,"453":1,"462":1,"507":1,"524":2,"636":1,"658":1,"660":1,"661":1,"666":1,"760":1,"763":1,"770":1,"773":1,"779":1,"871":1,"880":1,"881":1,"885":2,"886":1,"887":1,"903":1,"904":1,"911":1,"1015":1,"1052":1,"1105":1,"1108":1,"1111":1,"1137":1,"1165":1,"1197":1,"1304":1,"1311":1,"1326":1,"1327":1,"1331":1,"1338":1,"1357":1,"1410":1,"1431":1,"1544":2,"1651":1,"1655":1,"1687":1,"1733":1,"1792":1,"1806":1,"1923":1,"1924":1,"1927":1,"2264":1,"2302":1,"2305":1,"2319":1,"2509":2,"2549":1,"2827":1,"2828":1,"2831":1,"2834":1,"2836":1,"2838":1}}],["receive",{"2":{"16":1,"310":1,"358":1,"423":1,"638":3,"641":2,"642":1,"644":1,"645":1,"650":1,"880":3,"1019":1,"1056":1,"1057":1,"1165":1,"1309":1,"1312":1,"1313":2,"1314":1,"1326":1,"1372":1,"1396":1,"1473":1,"1767":1,"1792":3,"1918":1,"2247":1,"2304":1,"2319":1,"2393":1,"2490":1,"2634":1,"2829":1,"2831":1,"2855":1,"2858":1}}],["recently",{"2":{"1401":1}}],["recent",{"2":{"320":1,"874":1}}],["reconnectiondelay",{"2":{"1320":1}}],["reconnectionattempts",{"2":{"1320":1}}],["reconnection",{"2":{"1303":1,"1309":1,"1320":2,"1323":1}}],["reconnections",{"2":{"1303":1}}],["recognizable",{"2":{"2535":1}}],["recognize",{"2":{"1382":1,"2482":1}}],["recognizes",{"2":{"429":1,"2310":1,"2313":1}}],["recognized",{"0":{"326":1,"355":1},"2":{"221":1,"383":1,"390":1,"675":1,"1792":1,"2191":1,"2505":1}}],["recognition",{"2":{"1210":1}}],["recovers",{"2":{"1180":1}}],["recovery",{"2":{"1066":1,"1068":10,"1098":1,"1174":1,"1176":2,"1249":1,"1324":1,"1774":1,"1792":2,"2546":1}}],["recompiles",{"2":{"1418":1}}],["recomputed",{"2":{"308":1}}],["recommendation",{"2":{"851":1,"1253":1,"2297":1}}],["recommended",{"0":{"271":1,"2027":1,"2821":1},"2":{"307":2,"309":1,"363":1,"448":1,"967":1,"1049":1,"1067":1,"1111":1,"1220":1,"1230":1,"1232":1,"1416":1,"1448":1,"1516":1,"1574":1,"1615":1,"1641":1,"1703":1,"1792":8,"1856":2,"1870":1,"1874":1,"1880":1,"1882":1,"1994":1,"2017":1,"2019":1,"2160":1,"2177":3,"2193":2,"2212":1,"2223":1,"2265":1,"2296":1,"2389":1,"2429":1,"2455":1,"2490":1,"2492":1,"2632":3,"2633":1,"2729":1,"2740":1,"2789":1,"2820":1}}],["recorded",{"2":{"860":1}}],["record",{"0":{"299":1,"854":1,"1090":1,"1265":1,"1287":1,"1289":1},"2":{"119":1,"120":1,"227":1,"297":1,"299":2,"428":1,"554":1,"607":2,"852":2,"854":1,"857":2,"915":1,"934":1,"982":1,"990":1,"1026":3,"1041":1,"1060":1,"1111":2,"1187":1,"1188":1,"1190":1,"1192":4,"1193":7,"1263":1,"1264":2,"1272":1,"1284":1,"1339":1,"1386":2,"1393":1,"1419":1,"1480":1,"1517":1,"1574":1,"1678":2,"1688":1,"1792":8,"1824":1,"2265":1,"2339":2,"2366":1,"2398":1,"2481":1,"2498":1,"2815":1}}],["records",{"0":{"1091":1,"1288":1,"1290":1,"1291":1,"1295":1},"2":{"87":1,"568":1,"588":1,"626":1,"869":1,"1035":1,"1100":1,"1185":1,"1255":2,"1262":2,"1264":2,"1268":2,"1270":2,"1271":1,"1272":1,"1284":1,"1285":1,"1352":1,"1386":1,"1480":1,"1664":1,"1792":4,"2265":1,"2291":1,"2422":1,"2463":1,"2466":1,"2537":1,"2879":1}}],["rejectionstatuscode",{"2":{"2470":1}}],["rejection",{"0":{"897":1},"2":{"1792":2,"1910":1,"1958":1,"1959":1,"2224":1,"2417":1,"2468":1,"2470":1,"2471":1,"2546":1}}],["reject",{"2":{"894":3,"1199":1,"1366":3,"1410":2,"2149":1}}],["rejected",{"2":{"75":1,"302":1,"382":2,"691":1,"710":1,"1511":1,"1593":1,"1624":2,"1792":6,"1823":1,"1827":1,"1830":1,"1958":4,"2178":1,"2187":1,"2334":2,"2380":1,"2423":1,"2441":1,"2468":1,"2470":1,"2481":1,"2497":1,"2517":1,"2529":1,"2540":2,"2845":1,"2865":1}}],["rejects",{"2":{"64":1,"1431":1,"1792":1,"1925":1,"1951":2,"1952":2,"1953":2,"1954":2,"2107":1,"2224":1,"2433":1,"2453":2,"2495":1,"2537":1}}],["resume",{"2":{"2407":1}}],["resulttypename",{"2":{"2359":1}}],["resulting",{"2":{"916":2}}],["resultprefix",{"0":{"2008":1},"2":{"567":1,"1792":1,"1999":1,"2000":1,"2008":3,"2320":1,"2330":2,"2841":1}}],["result3",{"2":{"567":1,"614":1,"826":1,"2008":1,"2320":1,"2338":1,"2339":1,"2342":1}}],["result2",{"2":{"559":1,"565":2,"567":1,"614":1,"622":1,"826":1,"1386":1,"1792":1,"2008":2,"2320":2,"2330":1,"2338":1,"2339":1,"2340":1,"2342":2,"2357":1,"2841":1,"2852":1}}],["result1",{"2":{"559":1,"567":1,"614":1,"622":1,"826":1,"1386":1,"1792":1,"2008":2,"2320":1,"2330":1,"2338":1,"2339":1,"2340":1,"2342":4,"2841":1,"2852":1}}],["results",{"0":{"565":1,"566":1,"616":1,"1041":1,"1260":1,"1282":1},"1":{"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1},"2":{"112":1,"120":1,"159":1,"238":1,"484":1,"549":1,"567":1,"568":2,"582":1,"583":1,"586":1,"609":1,"614":1,"615":2,"616":1,"622":1,"626":1,"673":3,"830":1,"833":1,"834":1,"848":2,"869":1,"918":1,"965":1,"990":2,"1029":1,"1067":3,"1089":1,"1098":1,"1129":3,"1254":3,"1260":1,"1279":1,"1281":1,"1283":1,"1284":1,"1285":1,"1338":1,"1357":2,"1358":1,"1370":1,"1376":1,"1385":1,"1393":2,"1398":2,"1789":1,"1792":4,"1824":1,"1853":1,"2072":2,"2075":1,"2077":1,"2083":1,"2102":1,"2205":1,"2320":1,"2337":1,"2339":4,"2347":1,"2464":1,"2481":1,"2494":1,"2526":1,"2535":1,"2536":1,"2621":1,"2650":1,"2651":1,"2652":1,"2855":1,"2858":1,"2880":2}}],["result",{"0":{"185":1,"559":1,"1391":1,"1396":1,"1688":1,"2293":1,"2340":1,"2343":2,"2359":1,"2813":1,"2851":1},"1":{"186":1,"560":1,"561":1,"562":1,"563":1,"564":1,"565":1,"566":1,"567":1,"568":1},"2":{"1":1,"31":1,"32":1,"35":2,"41":1,"43":1,"88":1,"109":1,"119":2,"182":1,"186":2,"214":1,"215":1,"216":1,"223":1,"237":1,"238":2,"286":1,"297":1,"298":1,"299":2,"320":1,"337":1,"395":1,"412":1,"414":3,"419":1,"421":1,"423":2,"439":1,"456":1,"460":1,"527":1,"553":1,"554":1,"555":1,"559":1,"560":4,"562":3,"563":4,"564":2,"565":3,"566":3,"567":3,"582":1,"584":8,"586":2,"587":3,"588":2,"607":1,"609":1,"613":1,"617":3,"618":4,"619":1,"621":1,"622":2,"623":1,"625":1,"626":2,"700":3,"701":1,"760":2,"761":1,"764":2,"765":2,"766":2,"770":2,"771":1,"773":3,"774":2,"823":1,"824":1,"829":1,"848":3,"879":1,"880":1,"882":1,"883":2,"884":2,"885":6,"887":2,"888":2,"904":2,"918":3,"928":4,"934":1,"948":1,"949":2,"990":3,"1005":1,"1016":1,"1020":1,"1021":15,"1023":1,"1041":2,"1044":1,"1046":1,"1067":1,"1076":4,"1084":1,"1086":1,"1095":1,"1100":1,"1104":1,"1105":2,"1149":1,"1180":1,"1189":1,"1197":1,"1218":3,"1259":1,"1332":5,"1335":2,"1338":10,"1339":13,"1342":3,"1346":1,"1370":4,"1376":15,"1378":5,"1386":5,"1390":7,"1391":6,"1393":4,"1396":8,"1399":5,"1416":1,"1435":1,"1460":1,"1501":1,"1511":1,"1517":2,"1527":1,"1664":1,"1684":1,"1686":2,"1688":2,"1792":10,"1824":3,"1856":1,"1924":1,"1968":1,"1999":1,"2000":2,"2008":4,"2009":1,"2013":2,"2076":1,"2129":1,"2131":1,"2156":1,"2157":1,"2162":1,"2176":1,"2228":1,"2265":3,"2267":1,"2283":1,"2291":1,"2293":2,"2300":1,"2302":2,"2304":1,"2305":1,"2320":9,"2323":6,"2330":4,"2337":7,"2338":1,"2339":1,"2340":10,"2342":2,"2343":1,"2348":1,"2357":1,"2359":1,"2372":3,"2375":1,"2403":1,"2421":1,"2451":3,"2462":1,"2466":2,"2481":2,"2502":1,"2510":1,"2526":1,"2527":1,"2529":1,"2543":1,"2545":1,"2546":1,"2586":1,"2725":1,"2760":1,"2767":1,"2774":2,"2806":1,"2807":2,"2813":1,"2815":10,"2817":1,"2841":2,"2850":2,"2852":3,"2853":1,"2854":5,"2859":2,"2862":1}}],["res2",{"2":{"1220":2,"1221":2,"1222":2}}],["res1",{"2":{"1220":2,"1221":2,"1222":2}}],["residue",{"2":{"2531":1}}],["resident",{"2":{"1792":1}}],["residentkeyrequirement",{"0":{"1229":1,"1879":1},"2":{"1217":1,"1227":1,"1792":1,"1877":1,"1893":1,"2223":1,"2486":1}}],["resistant",{"2":{"1792":1,"1866":1,"2625":1}}],["resilience",{"0":{"1250":1},"2":{"2498":1}}],["resilient",{"2":{"1180":1}}],["res",{"2":{"1026":3}}],["reshaping",{"2":{"2546":1}}],["reshape",{"2":{"2806":1,"2809":1,"2869":1}}],["reshapes",{"2":{"2156":1,"2542":1}}],["reshaped",{"2":{"872":1}}],["reshuffles",{"2":{"872":1}}],["rescue",{"2":{"855":1}}],["research",{"2":{"918":2}}],["reserved",{"2":{"2459":1}}],["reserve",{"2":{"390":1,"1327":1,"1792":1,"1862":1,"2483":1}}],["resets",{"2":{"771":1,"1925":1}}],["reset",{"2":{"310":1,"624":1,"1070":1,"1674":1,"1792":1,"2255":4,"2342":1,"2517":1,"2531":1,"2824":1,"2825":1,"2841":1}}],["restock",{"2":{"1045":2}}],["restoring",{"2":{"994":1,"2466":1}}],["restored",{"2":{"1792":1}}],["restores",{"2":{"1079":1,"2869":1}}],["restore",{"2":{"993":1,"2224":1,"2455":1,"2539":1}}],["rests",{"2":{"922":1}}],["restructuring",{"2":{"872":1}}],["restructured",{"2":{"872":1}}],["restricts",{"2":{"1057":1,"1130":1}}],["restriction",{"2":{"1068":1,"1130":1}}],["restricting",{"2":{"1048":1,"1792":1}}],["restrictive",{"2":{"663":1,"1792":1,"2020":1}}],["restricted",{"0":{"926":1,"1130":1},"2":{"932":1,"937":2,"946":1,"1064":1,"1130":1,"2876":3}}],["restrict",{"0":{"2728":1},"2":{"13":1,"967":1,"1013":1,"1709":1,"1792":1,"2047":1,"2314":1,"2635":1,"2831":1}}],["restarting",{"2":{"2543":1}}],["restart",{"0":{"2757":1},"2":{"390":1,"844":1,"871":2,"872":2,"875":1,"880":1,"888":1,"911":1,"1054":1,"1151":1,"1155":1,"1368":1,"1406":1,"1409":1,"1414":1,"1418":1,"1419":1,"1422":2,"1438":1,"1653":1,"1768":2,"1774":2,"1789":1,"1792":7,"2040":2,"2049":1,"2157":5,"2297":1,"2353":1,"2476":1,"2495":1,"2525":1,"2537":2,"2542":1,"2543":5,"2634":2,"2635":1,"2878":1}}],["restarts",{"2":{"214":1,"1054":2,"1067":1,"1094":1,"1146":1,"1379":1,"1654":1,"1743":1,"1792":1,"2153":1,"2221":1,"2502":1,"2541":1,"2542":1,"2543":1,"2546":2,"2742":1,"2857":1,"2878":1}}],["restful",{"2":{"253":1,"2277":1}}],["rest",{"0":{"912":1,"1042":1,"1254":1,"1368":1,"1384":1,"1386":1},"1":{"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":1,"1404":1},"2":{"214":1,"299":1,"319":1,"320":5,"325":1,"831":2,"833":2,"835":1,"836":1,"876":1,"918":1,"920":1,"947":1,"973":1,"1010":1,"1037":8,"1038":2,"1042":1,"1043":2,"1045":1,"1046":2,"1073":1,"1074":1,"1079":1,"1083":1,"1084":2,"1086":3,"1088":1,"1089":1,"1094":2,"1095":1,"1104":1,"1106":1,"1111":1,"1121":1,"1135":1,"1156":1,"1253":1,"1281":1,"1303":1,"1304":1,"1327":1,"1368":1,"1381":1,"1382":1,"1383":1,"1385":7,"1386":5,"1398":2,"1405":1,"1432":1,"1435":1,"1659":1,"1660":1,"1664":1,"1751":1,"1757":1,"1789":1,"1792":4,"1899":1,"1907":2,"1910":1,"1911":1,"1925":1,"1998":1,"2166":3,"2176":1,"2193":1,"2228":1,"2291":1,"2297":2,"2317":1,"2318":1,"2388":1,"2389":2,"2433":1,"2434":1,"2455":1,"2462":1,"2463":1,"2479":1,"2481":1,"2502":1,"2528":1,"2535":1,"2543":1,"2565":1,"2672":1,"2709":1,"2712":1,"2772":3,"2773":1,"2774":1,"2818":1,"2826":1,"2839":1,"2840":1,"2857":1,"2864":1}}],["respawns",{"2":{"2543":1}}],["responding",{"2":{"1768":1,"1774":1,"1781":1,"1792":1,"2634":1,"2810":1}}],["responds",{"2":{"386":1,"436":1,"1792":1,"2634":1,"2861":1}}],["responsibilities",{"2":{"2391":1}}],["responsibility",{"2":{"1830":1}}],["responsiveness",{"2":{"1168":1}}],["responsive",{"2":{"1049":1,"1171":1}}],["responseheadersfield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responseheadersparameter",{"2":{"448":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responsetypecolumnname",{"2":{"1469":1,"1471":1,"1792":1,"2259":1}}],["responsetext",{"2":{"894":2,"1366":2,"1410":2}}],["responsetemptable",{"0":{"2109":1},"1":{"2110":1},"2":{"699":1,"1792":1,"2093":1,"2094":1,"2110":1,"2530":3,"2537":1,"2866":1}}],["responseerrormessagefield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responseerrormessageparameter",{"2":{"448":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responseentity",{"2":{"1366":4}}],["responsecompression",{"2":{"1792":1,"1936":1,"1944":2,"2626":1,"2627":1,"2701":1,"2746":1}}],["responsecontenttypefield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responsecontenttypeparameter",{"2":{"448":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responsecache",{"2":{"873":1}}],["responsecacheattribute",{"2":{"869":1}}],["response>",{"2":{"429":1,"2310":1,"2313":1}}],["responsesuccessfield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responsesuccessparameter",{"2":{"448":1,"449":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responsestatuscode",{"2":{"1923":1,"2509":1}}],["responsestatuscodefield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responsestatuscodeparameter",{"2":{"448":1,"449":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responses",{"0":{"2110":1,"2271":1,"2384":1},"2":{"88":1,"140":1,"214":2,"227":1,"472":1,"549":1,"916":1,"920":1,"967":1,"1011":1,"1014":1,"1019":1,"1023":1,"1037":2,"1071":1,"1078":1,"1097":1,"1105":1,"1107":1,"1109":2,"1111":5,"1137":1,"1177":1,"1180":1,"1255":1,"1274":1,"1279":1,"1328":2,"1349":1,"1376":2,"1398":1,"1413":1,"1430":1,"1558":2,"1645":1,"1668":1,"1670":3,"1673":1,"1676":1,"1677":1,"1722":3,"1743":2,"1746":1,"1764":1,"1769":1,"1782":1,"1792":18,"1853":1,"1857":1,"1864":1,"1865":1,"1928":1,"1937":2,"1942":1,"1994":1,"2000":1,"2008":1,"2010":1,"2014":1,"2016":1,"2047":1,"2060":1,"2110":3,"2151":1,"2164":2,"2165":1,"2224":1,"2255":4,"2271":2,"2273":2,"2278":1,"2347":1,"2388":1,"2393":1,"2397":1,"2445":1,"2459":1,"2463":2,"2465":1,"2466":1,"2502":2,"2506":1,"2530":2,"2549":1,"2558":1,"2566":1,"2580":2,"2596":1,"2627":1,"2632":3,"2634":2,"2635":2,"2769":1,"2816":1,"2835":1}}],["responsebodyfield",{"2":{"1721":1,"1722":1,"1792":1,"2264":1,"2769":1}}],["responsebodyparameter",{"2":{"448":1,"449":1,"1792":1,"1916":1,"1918":1,"2549":1}}],["responsebody",{"2":{"74":1,"75":3,"1569":4,"1923":1,"2509":1,"2518":2,"2519":3,"2520":1,"2523":2}}],["response",{"0":{"84":1,"140":1,"210":1,"214":1,"227":1,"361":1,"447":1,"536":1,"549":1,"698":1,"819":1,"1031":1,"1341":1,"1455":1,"1471":1,"1558":1,"1568":1,"1675":1,"1732":1,"1743":1,"1782":1,"1918":1,"1922":1,"1935":1,"2202":1,"2338":1,"2484":1,"2502":1,"2530":1,"2580":1,"2626":1,"2746":1,"2763":1,"2866":1},"1":{"448":1,"449":1,"537":1,"538":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"550":1,"551":1,"552":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"699":1,"700":1,"701":1,"702":1,"1676":1,"1677":1,"1936":1,"1937":1,"1938":1,"1939":1,"1940":1,"1941":1,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1},"2":{"33":1,"45":1,"48":1,"51":1,"74":3,"75":2,"78":1,"81":1,"83":1,"84":1,"87":2,"88":1,"112":1,"120":1,"139":1,"140":2,"141":1,"186":1,"197":1,"202":1,"206":4,"208":4,"210":2,"212":2,"213":1,"214":1,"215":2,"227":3,"230":1,"239":2,"285":1,"300":1,"303":1,"383":2,"386":1,"387":2,"388":1,"390":4,"396":1,"412":1,"414":1,"415":1,"429":6,"435":1,"436":1,"438":2,"439":5,"447":3,"448":1,"454":1,"472":1,"492":1,"493":1,"496":1,"527":1,"536":1,"542":1,"544":1,"546":1,"549":2,"550":3,"551":1,"553":2,"554":1,"555":2,"559":1,"586":1,"587":2,"588":1,"616":3,"617":1,"618":2,"621":1,"622":1,"624":2,"636":1,"669":1,"675":1,"689":1,"691":5,"692":2,"695":1,"698":1,"699":4,"700":1,"701":4,"705":1,"710":1,"720":1,"819":1,"823":1,"829":1,"835":1,"868":4,"869":3,"872":2,"873":3,"876":2,"877":1,"894":7,"914":1,"915":1,"916":1,"917":2,"918":1,"938":3,"948":1,"949":1,"968":1,"995":18,"996":2,"1010":1,"1011":1,"1016":1,"1021":12,"1023":3,"1024":10,"1026":11,"1031":2,"1035":1,"1058":1,"1063":1,"1074":3,"1078":5,"1082":1,"1097":1,"1101":6,"1104":2,"1105":10,"1106":1,"1107":1,"1108":1,"1109":1,"1111":1,"1135":1,"1137":1,"1138":2,"1218":1,"1220":2,"1221":2,"1222":2,"1235":1,"1239":1,"1255":2,"1258":1,"1331":3,"1332":7,"1335":5,"1337":1,"1338":5,"1341":2,"1342":10,"1349":2,"1351":1,"1355":3,"1357":1,"1361":4,"1362":1,"1363":1,"1366":4,"1372":1,"1376":13,"1378":1,"1386":11,"1391":2,"1396":2,"1398":2,"1407":2,"1408":11,"1409":1,"1410":9,"1412":1,"1413":1,"1415":2,"1416":8,"1427":6,"1430":1,"1431":4,"1455":1,"1456":1,"1470":1,"1471":3,"1480":1,"1553":1,"1558":4,"1567":11,"1568":2,"1569":2,"1575":4,"1671":2,"1672":1,"1691":1,"1692":1,"1694":1,"1695":1,"1697":1,"1722":4,"1723":1,"1725":1,"1730":3,"1731":2,"1732":2,"1733":2,"1736":6,"1737":1,"1738":2,"1741":2,"1743":1,"1745":1,"1766":1,"1767":1,"1768":1,"1790":2,"1791":1,"1792":63,"1797":2,"1798":1,"1855":2,"1859":1,"1862":3,"1864":1,"1885":1,"1915":1,"1917":2,"1918":3,"1920":2,"1921":2,"1922":4,"1928":3,"1935":2,"1937":2,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1991":1,"1994":1,"2016":1,"2017":1,"2038":1,"2093":2,"2094":1,"2109":8,"2110":6,"2165":1,"2167":1,"2185":1,"2202":1,"2205":1,"2221":1,"2222":3,"2223":1,"2235":1,"2237":1,"2247":8,"2255":5,"2259":1,"2264":12,"2267":1,"2270":1,"2273":2,"2283":5,"2289":2,"2293":1,"2300":1,"2302":1,"2303":1,"2310":6,"2313":6,"2329":1,"2338":1,"2339":1,"2342":1,"2343":1,"2346":1,"2348":1,"2356":1,"2357":1,"2359":2,"2360":1,"2363":1,"2372":1,"2396":1,"2397":2,"2398":4,"2400":1,"2463":1,"2466":3,"2483":3,"2484":1,"2491":1,"2493":2,"2494":1,"2500":1,"2502":1,"2504":1,"2506":1,"2518":4,"2519":2,"2523":2,"2525":1,"2526":2,"2527":3,"2529":4,"2530":18,"2531":2,"2536":1,"2537":4,"2545":1,"2549":9,"2554":2,"2559":1,"2562":5,"2566":4,"2580":2,"2590":1,"2596":2,"2614":1,"2615":2,"2626":2,"2632":1,"2641":1,"2648":1,"2656":1,"2723":1,"2726":2,"2739":2,"2746":1,"2759":2,"2760":6,"2762":7,"2763":4,"2764":4,"2765":1,"2766":7,"2769":2,"2771":1,"2806":2,"2807":6,"2809":1,"2810":5,"2812":1,"2813":2,"2814":4,"2817":1,"2835":1,"2836":1,"2841":1,"2851":1,"2852":1,"2853":1,"2860":3,"2861":2,"2862":3,"2865":3,"2866":10,"2868":1,"2869":3,"2873":1,"2876":1,"2881":1,"2882":1}}],["respect",{"2":{"2339":1,"2615":1}}],["respects",{"2":{"347":1}}],["respective",{"2":{"336":1}}],["respectively",{"2":{"213":1,"1740":1,"2185":1,"2288":1,"2377":1}}],["resort",{"2":{"319":1}}],["resolute",{"2":{"2385":1}}],["resolution",{"0":{"52":1,"422":1,"446":1,"2607":1},"2":{"334":1,"422":1,"436":1,"1126":1,"1593":1,"1792":2,"1917":1,"1929":2,"1956":1,"2024":1,"2236":1,"2346":1,"2379":1,"2477":1,"2498":1,"2518":1,"2597":1,"2632":1,"2666":2}}],["resolveenv",{"2":{"2498":1}}],["resolver",{"2":{"2493":1,"2498":1}}],["resolvetypedescriptor",{"2":{"2370":1}}],["resolvenestedcompositetypes",{"0":{"2607":1},"2":{"334":1,"337":1,"338":1,"919":4,"1097":1,"1792":1,"1966":1,"1967":1,"1974":3,"1975":1,"2586":4,"2607":3}}],["resolve",{"0":{"1974":1,"2483":1},"2":{"167":1,"238":1,"384":1,"388":1,"390":1,"581":1,"696":1,"894":3,"933":1,"1162":1,"1366":3,"1410":3,"1416":2,"2223":1,"2247":2,"2337":1,"2346":1,"2370":1,"2385":1,"2476":1,"2483":2,"2493":1,"2518":1,"2608":1,"2666":1,"2764":1,"2771":1,"2847":1,"2854":1,"2868":1}}],["resolvedoptions",{"2":{"1792":1}}],["resolved",{"0":{"215":1,"388":1,"527":1,"532":1,"533":1,"1033":1,"1738":1,"2282":1,"2285":1,"2286":1},"1":{"389":1,"528":1,"529":1,"530":1,"531":1,"532":1,"533":1,"534":1,"535":1,"2283":1,"2284":1,"2285":1,"2286":1},"2":{"156":1,"212":1,"214":6,"215":2,"337":1,"388":1,"390":1,"395":2,"396":1,"436":1,"446":3,"527":1,"528":1,"529":7,"533":1,"534":1,"535":1,"582":1,"584":1,"841":1,"961":1,"1033":1,"1067":1,"1069":1,"1104":1,"1105":3,"1108":1,"1109":1,"1150":1,"1153":1,"1398":1,"1523":1,"1527":1,"1569":1,"1592":1,"1738":3,"1743":5,"1759":1,"1792":7,"1818":1,"1862":1,"1912":1,"1923":1,"1924":1,"1958":1,"1967":1,"1974":1,"2038":1,"2040":1,"2079":1,"2184":1,"2185":1,"2222":2,"2230":1,"2282":3,"2283":1,"2284":4,"2285":1,"2286":1,"2324":1,"2337":1,"2380":1,"2470":1,"2476":1,"2483":2,"2493":1,"2502":5,"2509":1,"2510":1,"2513":1,"2520":1,"2607":2,"2653":1,"2764":1,"2765":1,"2771":1,"2811":1,"2812":1,"2854":1}}],["resolves",{"2":{"74":1,"215":1,"390":2,"919":1,"957":1,"1033":1,"1069":1,"1097":1,"1101":1,"1162":1,"1738":1,"1792":1,"1956":1,"1974":1,"2098":1,"2379":1,"2394":1,"2476":1,"2496":1,"2498":1,"2504":1,"2518":1,"2607":1,"2768":1,"2854":1}}],["resources",{"2":{"576":1,"1278":1,"1594":1,"1792":2,"2016":1,"2024":3,"2632":1}}],["resource",{"0":{"1277":1,"1594":1,"1808":1,"1825":1,"1833":1,"2025":1},"1":{"1278":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1},"2":{"37":3,"51":1,"478":1,"533":1,"1045":2,"1096":2,"1115":1,"1122":1,"1127":1,"1139":1,"1155":1,"1254":1,"1259":1,"1278":1,"1632":1,"1637":1,"1639":1,"1788":1,"1792":12,"1795":1,"1807":1,"1808":1,"1825":2,"1827":1,"1828":1,"1829":1,"1830":3,"1831":3,"1833":1,"1963":1,"2016":1,"2025":2,"2030":1,"2223":2,"2286":1,"2481":5,"2615":2,"2632":5,"2804":1}}],["reaping",{"2":{"2543":1}}],["reassemble",{"2":{"2522":1}}],["reassurance",{"2":{"876":1}}],["reasonably",{"2":{"1432":1}}],["reasonable",{"2":{"843":1,"859":2,"864":1}}],["reason",{"2":{"841":2,"849":1,"872":1,"1079":1,"1081":1,"1220":1,"1856":1,"1974":1,"2540":1,"2607":1}}],["reasons",{"2":{"307":1,"663":1,"1254":1,"1385":1,"1386":1,"1969":1}}],["react",{"2":{"310":1,"1436":1}}],["reaching",{"2":{"2385":1,"2435":1}}],["reaches",{"2":{"664":1,"972":1,"1011":1,"1137":1,"1251":1,"1423":1,"1822":1,"2490":2,"2768":1}}],["reached",{"2":{"177":1,"843":1,"1158":1,"1405":1,"1624":1,"1792":1,"1804":1,"1951":1,"1952":1,"1953":1,"1954":1,"1958":1,"1961":1,"2392":1,"2470":1}}],["reach",{"2":{"175":1,"298":1,"324":1,"436":1,"452":1,"454":1,"650":2,"683":1,"837":3,"1005":1,"1042":1,"1052":1,"1208":1,"1441":1,"1770":1,"2176":1,"2717":1,"2797":1,"2876":1}}],["reachable",{"2":{"174":1,"322":1,"347":1,"351":1,"1621":1,"1911":1,"2040":1,"2432":1}}],["readtoendasync",{"2":{"2615":1}}],["readasync",{"2":{"2615":1}}],["readallasync",{"2":{"2362":1}}],["readability",{"2":{"2261":1}}],["readable",{"0":{"2667":1},"1":{"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"701":1,"1054":1,"1142":1,"1225":1,"1792":1,"1875":1,"2056":2,"2232":1,"2389":1}}],["readinessprobe",{"2":{"1773":1}}],["readiness",{"0":{"1767":1,"2438":1},"2":{"1351":1,"1764":1,"1770":1,"1774":1,"1776":1,"1792":3,"2634":4}}],["reading",{"0":{"2763":1},"2":{"1":1,"296":1,"315":1,"782":1,"784":1,"876":1,"913":1,"919":2,"1047":1,"1401":2,"1428":1,"1489":1,"2189":1,"2448":1,"2759":1}}],["readreplica",{"2":{"1176":5,"1177":2,"1179":3,"1614":1,"1632":2,"1633":1}}],["readwrite",{"2":{"1174":1,"1628":1,"1792":1,"2266":2}}],["readonlyspan",{"2":{"2399":1,"2400":1}}],["readonly",{"2":{"1174":2,"1628":2,"1629":2,"1792":1,"2266":3,"2399":1}}],["readyz",{"2":{"1780":2}}],["readypath",{"2":{"1763":1,"1764":1,"1776":1,"1780":1,"1792":1,"2634":1}}],["ready",{"0":{"1767":1,"1959":1,"2471":1},"2":{"1064":1,"1065":1,"1100":2,"1403":1,"1763":1,"1764":1,"1767":4,"1770":1,"1773":1,"1774":1,"1792":5,"1995":1,"2224":1,"2634":5,"2682":1,"2694":1,"2726":1,"2772":1,"2775":1,"2833":1,"2871":1}}],["reader",{"2":{"952":1,"1792":5,"2324":2,"2403":1}}],["readers",{"2":{"859":1,"2372":1}}],["reads",{"2":{"133":1,"277":1,"297":2,"309":1,"454":1,"636":1,"650":1,"663":1,"848":1,"1042":1,"1070":1,"1102":3,"1174":1,"1305":1,"1382":2,"1408":1,"1418":1,"1429":1,"1605":1,"1957":3,"2040":1,"2176":1,"2187":2,"2191":1,"2212":1,"2223":1,"2379":3,"2391":1,"2412":1,"2476":1,"2497":1,"2681":1,"2688":1,"2763":1,"2828":1,"2867":1}}],["read",{"0":{"9":1,"149":1,"1176":1,"1427":1,"2214":1,"2465":1},"2":{"9":1,"149":2,"175":1,"187":1,"188":1,"212":1,"215":1,"299":1,"306":4,"316":2,"390":2,"448":1,"453":1,"454":1,"528":1,"531":1,"835":1,"836":1,"844":1,"861":1,"863":1,"868":2,"907":1,"913":1,"940":1,"967":1,"1100":1,"1101":1,"1102":1,"1174":3,"1176":2,"1177":1,"1180":1,"1181":1,"1206":1,"1366":2,"1400":1,"1403":1,"1406":1,"1407":1,"1422":1,"1427":1,"1432":1,"1439":2,"1441":1,"1471":1,"1524":1,"1614":1,"1628":2,"1632":2,"1771":1,"1792":12,"1823":3,"1833":1,"1862":1,"2040":1,"2051":1,"2063":2,"2106":1,"2156":1,"2171":1,"2184":1,"2188":2,"2266":2,"2294":1,"2296":1,"2319":1,"2338":1,"2369":1,"2399":1,"2410":1,"2415":1,"2452":1,"2466":1,"2476":1,"2483":1,"2497":1,"2498":1,"2533":1,"2535":1,"2537":1,"2539":1,"2542":1,"2633":1,"2635":1,"2867":1}}],["realtime",{"2":{"1088":2,"1119":1,"1126":1}}],["realistic",{"2":{"869":2,"1170":1,"1254":2}}],["realized",{"2":{"1403":1}}],["realize",{"2":{"868":1,"1403":1,"1405":6}}],["reality",{"0":{"844":1,"848":1,"852":1,"860":1,"864":1},"2":{"844":1,"848":1,"855":1,"993":1,"1006":1,"2452":1}}],["really",{"2":{"843":1,"847":1,"849":1,"860":1,"865":1,"1037":1,"1384":3,"1386":2,"1403":2,"1435":1,"1442":1}}],["realms",{"2":{"51":1}}],["realm=",{"2":{"48":1,"63":1}}],["realm",{"0":{"44":1,"48":1,"52":1},"1":{"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1},"2":{"31":1,"37":1,"38":4,"39":1,"40":1,"43":2,"44":3,"45":6,"46":1,"48":2,"49":1,"50":2,"51":4,"52":3,"53":1,"66":2,"225":2,"1469":1,"1482":1,"1498":1,"1499":2,"1501":1,"1502":1,"1503":1,"1505":1,"1506":2,"1508":2,"1792":3}}],["real",{"0":{"1044":1,"1103":1,"1302":1,"1304":1,"1320":1,"1372":1,"1416":1,"1417":1,"1437":1,"2740":1,"2836":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1418":1,"1419":1,"1420":1,"1421":1},"2":{"1":1,"327":1,"338":1,"448":1,"527":1,"585":1,"690":2,"691":1,"836":1,"837":1,"841":1,"844":3,"848":1,"851":4,"852":1,"857":1,"864":2,"865":2,"866":1,"869":2,"873":2,"874":2,"875":1,"876":2,"877":2,"916":1,"956":1,"986":1,"1018":1,"1037":7,"1038":1,"1043":1,"1044":1,"1066":1,"1067":1,"1073":2,"1074":2,"1075":1,"1078":1,"1080":1,"1081":3,"1094":2,"1103":2,"1121":1,"1127":1,"1302":3,"1303":1,"1309":2,"1323":1,"1324":1,"1325":1,"1327":1,"1335":1,"1372":1,"1381":1,"1382":4,"1385":2,"1386":1,"1394":1,"1399":1,"1403":2,"1406":1,"1409":1,"1414":1,"1417":1,"1428":1,"1432":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1519":1,"1525":1,"1566":1,"1569":1,"1581":1,"1704":1,"1705":1,"1792":2,"1834":1,"1857":1,"1957":1,"2092":1,"2164":2,"2165":1,"2166":1,"2333":1,"2379":1,"2388":1,"2393":1,"2395":1,"2472":1,"2479":1,"2505":1,"2529":1,"2534":1,"2621":1,"2633":2,"2739":1,"2827":2,"2836":1,"2837":1,"2838":1,"2860":1,"2872":1}}],["re",{"0":{"836":1},"2":{"1":1,"176":1,"177":1,"180":1,"214":1,"390":1,"453":1,"688":1,"837":3,"841":1,"852":1,"860":1,"865":1,"868":2,"874":1,"876":1,"948":1,"952":1,"953":1,"994":1,"1013":1,"1036":1,"1068":1,"1071":1,"1080":4,"1096":1,"1122":1,"1123":1,"1140":1,"1141":2,"1170":1,"1181":1,"1210":1,"1232":2,"1318":1,"1333":1,"1371":1,"1377":2,"1382":2,"1401":1,"1407":1,"1409":1,"1414":1,"1419":2,"1426":1,"1436":2,"1441":1,"1442":1,"1443":1,"1493":1,"1528":1,"1690":1,"1789":1,"1792":9,"1856":1,"2040":1,"2106":6,"2110":1,"2153":2,"2156":2,"2158":2,"2160":1,"2181":1,"2185":1,"2221":1,"2377":1,"2398":2,"2432":1,"2438":2,"2455":1,"2463":1,"2466":1,"2476":1,"2502":1,"2525":1,"2530":1,"2537":6,"2541":2,"2542":3,"2543":1,"2555":1,"2633":1,"2634":1,"2635":1,"2722":1,"2742":3,"2809":1,"2812":1,"2820":1,"2855":1,"2878":5}}],["2+",{"2":{"1792":1,"2109":1,"2537":1}}],["2x",{"2":{"1267":1}}],["2xx",{"2":{"210":1,"214":1,"447":1,"1031":1,"1341":1,"1722":1,"1732":1,"1740":1,"1743":1,"1918":1,"1922":1,"2109":1,"2264":1,"2288":1,"2502":1,"2530":1,"2549":1,"2763":1,"2765":1,"2810":1}}],["292",{"2":{"1284":1,"1295":1}}],["291",{"2":{"1281":1,"1288":1}}],["297",{"2":{"1269":1}}],["29",{"2":{"1189":1,"1192":1,"1277":2,"1287":1,"1288":1,"1289":2,"1299":1,"2546":1,"2621":1,"2824":10}}],["29ms",{"2":{"1090":1,"1289":1,"1291":1,"1295":1}}],["2d",{"2":{"1097":1,"2588":3,"2589":1}}],["27ms",{"2":{"1297":1,"1299":1}}],["277",{"2":{"1295":1}}],["275",{"2":{"1289":1}}],["2754",{"2":{"1023":1}}],["270",{"2":{"1287":1,"1299":1,"1301":1}}],["27",{"0":{"2243":1},"1":{"2244":1,"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1},"2":{"1287":2,"1289":1,"2240":1,"2397":1,"2621":1,"2823":1}}],["272",{"2":{"1287":1}}],["276",{"2":{"1277":1}}],["271",{"2":{"1090":1,"1267":1,"1284":2,"1287":1,"1289":1}}],["286",{"2":{"1297":1}}],["281",{"2":{"1290":1,"1293":1}}],["28ms",{"2":{"1288":1}}],["288",{"2":{"1288":1,"2824":1}}],["284",{"2":{"1287":1,"1288":1}}],["287",{"2":{"1285":1}}],["285",{"2":{"1284":1}}],["282",{"2":{"1284":1,"1295":1}}],["289",{"2":{"1284":1}}],["28+",{"2":{"1096":2,"1127":1}}],["28",{"0":{"2241":1,"2623":1},"1":{"2242":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1},"2":{"1090":1,"1287":5,"1289":1,"1290":1,"1293":1,"1295":1,"1382":1,"2235":1,"2240":1}}],["267",{"2":{"1293":1}}],["269",{"2":{"1293":1}}],["2616",{"2":{"1398":1,"1401":1}}],["261",{"2":{"1289":1,"1297":1}}],["264",{"2":{"1288":1}}],["266",{"2":{"1285":1,"1295":1}}],["260",{"2":{"1284":1}}],["262",{"2":{"1284":1,"1288":1,"1289":1,"1297":1}}],["265",{"2":{"1284":1,"1289":1,"1301":1}}],["26",{"0":{"2385":1,"2616":1},"1":{"2617":1,"2618":1},"2":{"1071":1,"1290":1,"1299":1,"2222":1,"2236":1,"2385":2,"2823":1}}],["2bkqdk4rhhwlfwlxx7mnpcluupdtqli1jidqyqmnjbgu",{"2":{"1051":2}}],["2gb+",{"2":{"969":1}}],["212",{"2":{"1290":1}}],["21ms",{"2":{"1290":1,"1293":1}}],["210",{"2":{"1289":1,"1366":1}}],["219",{"2":{"1288":1}}],["216",{"2":{"1281":1}}],["21",{"0":{"2275":1,"2612":1,"2619":1},"1":{"2276":1,"2277":1,"2278":1,"2279":1,"2613":1,"2614":1,"2615":1,"2620":1,"2621":1,"2622":1},"2":{"1082":1,"1277":1,"1278":1,"1281":2,"1287":1,"1290":2,"1295":1,"1301":1,"1714":1,"2167":1,"2236":2,"2239":1,"2531":1,"2545":1,"2546":1,"2860":1,"2873":1}}],["2147483647",{"2":{"956":1,"1374":1}}],["217",{"2":{"867":1,"868":1,"1290":1}}],["2394",{"2":{"2546":1}}],["23ms",{"2":{"1293":1,"1299":1,"1301":1}}],["234",{"2":{"1293":1,"1299":1,"1714":1}}],["236",{"2":{"1289":1}}],["232",{"2":{"1288":1,"2621":1}}],["235",{"2":{"1285":1}}],["23503",{"2":{"1111":2,"1678":1}}],["23505",{"2":{"1111":3,"1678":1}}],["231",{"2":{"1285":1,"1295":1}}],["2301",{"2":{"2523":1}}],["230",{"2":{"1285":1,"1301":1}}],["233",{"2":{"1284":1}}],["237",{"2":{"1277":1,"1301":1}}],["23",{"0":{"2315":1,"2552":1,"2675":1},"1":{"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2553":1,"2554":1,"2555":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"956":2,"1255":1,"1288":1,"1290":1,"1297":1,"1374":2,"2222":2,"2228":1,"2231":1,"2238":1,"2397":1,"2621":1}}],["2pd$e7e",{"2":{"927":1}}],["2pd$e7emore",{"2":{"927":1}}],["2288",{"2":{"2513":1}}],["229",{"2":{"1297":1}}],["22ms",{"2":{"1291":1,"1293":1,"1301":1}}],["225",{"2":{"1288":1,"1297":1}}],["22",{"0":{"2547":1},"1":{"2548":1,"2549":1,"2550":1,"2551":1},"2":{"1287":1,"1289":1,"1290":3,"1299":1,"1714":6,"2238":1,"2546":1,"2621":1}}],["222",{"2":{"1285":1,"1301":1}}],["221",{"2":{"1285":1}}],["223",{"2":{"1284":1,"1288":1}}],["226",{"2":{"1270":1}}],["2201980+01",{"2":{"995":1}}],["220",{"2":{"873":1,"1284":2,"1288":1,"1301":1}}],["22c",{"2":{"322":1}}],["245",{"2":{"1714":1}}],["245678",{"2":{"1359":1}}],["24ms",{"2":{"1301":1}}],["241",{"2":{"1295":1}}],["246",{"2":{"1289":1}}],["248",{"2":{"1285":1,"1289":1,"1299":1}}],["244",{"2":{"1284":1,"1288":1,"1714":1}}],["243",{"2":{"1284":1,"1288":1}}],["240",{"2":{"1255":1,"1714":2}}],["24",{"0":{"2373":1,"2556":1},"1":{"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2557":1,"2558":1,"2559":1},"2":{"566":2,"867":1,"1255":1,"1257":3,"1290":1,"1295":1,"1714":1,"2227":1,"2238":1}}],["2weeks",{"2":{"272":1}}],["2w",{"2":{"271":1}}],["20x",{"2":{"2861":1}}],["20t03",{"2":{"2453":1}}],["20t08",{"2":{"1856":1}}],["20t06",{"2":{"1856":2,"2452":1}}],["20th",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1}}],["20email",{"2":{"1694":1,"1695":1,"1792":2}}],["20profile",{"2":{"1694":1,"1792":1}}],["20r",{"2":{"1692":1,"1792":1}}],["208",{"2":{"1295":1}}],["205",{"2":{"1289":1,"1297":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":2,"2255":8}}],["20ms",{"2":{"1289":1,"1295":1}}],["20k+",{"2":{"1275":1}}],["203",{"2":{"1069":1,"1281":1,"1293":1,"2386":1}}],["201",{"2":{"2386":1}}],["2018",{"2":{"933":1}}],["2013",{"2":{"863":1}}],["2014",{"2":{"851":1,"1122":1}}],["20",{"0":{"1300":1,"2268":1,"2449":1},"1":{"1301":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1},"2":{"857":1,"869":3,"872":1,"874":1,"910":1,"947":1,"977":1,"980":1,"990":1,"1082":1,"1107":1,"1255":1,"1258":1,"1264":1,"1266":1,"1269":4,"1277":1,"1280":1,"1281":1,"1285":2,"1290":1,"1293":1,"1322":1,"1366":3,"1714":3,"2144":2,"2167":1,"2224":1,"2239":1,"2270":1,"2398":1,"2545":1,"2860":1,"2861":1,"2868":1,"2872":1}}],["202",{"2":{"1824":1,"2481":1}}],["2023",{"2":{"1400":1,"1402":1}}],["2024",{"2":{"977":5,"980":5,"990":5,"1189":2,"1192":1,"1391":2}}],["202601211416",{"2":{"1260":2}}],["2026",{"0":{"1254":1,"1272":1,"2280":1,"2298":1,"2311":1,"2315":1,"2373":1,"2387":1,"2408":1,"2439":1,"2449":1,"2458":1,"2467":1,"2473":1,"2675":1},"1":{"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1,"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2312":1,"2313":1,"2314":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2474":1,"2475":1,"2476":1,"2477":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"831":1,"866":1,"878":1,"912":1,"919":10,"921":1,"947":1,"964":2,"1010":1,"1023":1,"1037":1,"1038":1,"1048":1,"1066":1,"1072":1,"1083":1,"1089":1,"1128":1,"1135":1,"1183":1,"1209":1,"1254":1,"1280":1,"1302":1,"1328":1,"1352":1,"1368":1,"1384":1,"1400":1,"1405":1,"1406":1,"1408":1,"1423":1,"1435":1,"1856":3,"2221":1,"2222":3,"2223":1,"2224":4,"2225":3,"2226":1,"2227":1,"2228":1,"2229":2,"2230":1,"2231":1,"2452":1,"2453":1}}],["202512280753",{"2":{"1260":1}}],["2025",{"0":{"1266":1,"2241":1,"2243":1,"2260":1,"2262":1,"2268":1,"2275":1,"2547":1,"2552":1,"2556":1,"2560":1,"2563":1,"2568":1,"2570":1,"2573":1,"2578":1,"2583":1,"2592":1,"2598":1,"2601":1,"2605":1,"2609":1,"2612":1,"2616":1,"2619":1,"2623":1,"2630":1,"2636":1,"2639":1,"2643":1,"2646":1,"2657":1},"1":{"2242":1,"2244":1,"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1,"2261":1,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2276":1,"2277":1,"2278":1,"2279":1,"2548":1,"2549":1,"2550":1,"2551":1,"2553":1,"2554":1,"2555":1,"2557":1,"2558":1,"2559":1,"2561":1,"2562":1,"2564":1,"2565":1,"2566":1,"2567":1,"2569":1,"2571":1,"2572":1,"2574":1,"2575":1,"2576":1,"2577":1,"2579":1,"2580":1,"2581":1,"2582":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1,"2602":1,"2603":1,"2604":1,"2606":1,"2607":1,"2608":1,"2610":1,"2611":1,"2613":1,"2614":1,"2615":1,"2617":1,"2618":1,"2620":1,"2621":1,"2622":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2637":1,"2638":1,"2640":1,"2641":1,"2642":1,"2644":1,"2645":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1},"2":{"308":1,"309":1,"317":1,"363":1,"956":2,"972":1,"995":1,"1039":1,"1049":1,"1254":1,"1256":1,"1260":1,"1262":1,"1266":1,"1374":2,"1400":1,"1401":1,"1402":1,"1792":1,"1813":1,"1824":3,"2177":1,"2232":1,"2233":1,"2234":4,"2235":1,"2236":9,"2237":2,"2238":7,"2239":4,"2240":2,"2481":2,"2744":1,"2823":1,"2824":1}}],["2048",{"2":{"1792":2,"1916":1,"1917":1,"1925":1,"1931":1,"2222":1,"2517":1,"2812":1,"2814":1}}],["204",{"0":{"554":1},"2":{"227":1,"551":2,"554":2,"586":1,"587":1,"616":1,"823":1,"826":1,"829":1,"1372":1,"1792":1,"1855":1,"2337":1,"2338":2,"2339":1,"2596":1,"2853":1,"2859":1}}],["2000",{"2":{"956":2,"1374":2}}],["2007",{"2":{"852":1}}],["2002",{"2":{"851":1}}],["2003",{"2":{"851":2,"859":1}}],["2004",{"2":{"843":1}}],["200",{"2":{"33":1,"35":1,"210":1,"301":2,"313":1,"447":1,"551":2,"689":1,"691":1,"700":1,"705":1,"872":1,"894":2,"930":2,"968":1,"996":2,"1064":1,"1074":2,"1078":1,"1104":1,"1105":1,"1107":1,"1168":4,"1169":4,"1181":1,"1214":2,"1215":1,"1232":3,"1234":3,"1236":3,"1237":2,"1254":1,"1255":2,"1258":1,"1267":1,"1269":1,"1288":1,"1322":1,"1341":1,"1342":1,"1361":1,"1366":1,"1391":1,"1409":1,"1410":2,"1471":1,"1480":1,"1714":1,"1732":1,"1766":1,"1767":1,"1768":1,"1770":1,"1782":1,"1792":9,"1855":2,"1882":2,"1884":2,"1886":2,"1887":2,"1922":1,"2247":1,"2264":1,"2526":2,"2536":1,"2549":1,"2562":2,"2596":2,"2634":2,"2739":2,"2824":1,"2860":2,"2861":1,"2866":1}}],["200+",{"2":{"1":1}}],["2s",{"2":{"213":5,"214":1,"1032":1,"1105":1,"1740":4,"1742":2,"1792":2,"2106":1,"2154":2,"2156":1,"2288":4,"2290":2,"2542":3,"2765":2,"2878":1}}],["2min",{"2":{"137":1,"211":1,"277":1,"574":1,"1731":1,"2765":1}}],["258",{"2":{"1293":1}}],["250ms",{"2":{"1590":1}}],["250",{"2":{"1289":1,"1293":1,"2245":1,"2789":1}}],["250+",{"2":{"1036":1}}],["259",{"2":{"1288":1,"1295":1}}],["254",{"2":{"1287":1}}],["257",{"2":{"1236":1,"1237":1,"1290":1,"1792":1,"1886":1,"1887":1}}],["255",{"2":{"841":2}}],["256",{"2":{"308":2,"309":1,"363":1,"1049":1,"1067":3,"1107":1,"1243":1,"1277":1,"1510":1,"1511":1,"1516":1,"1656":5,"1657":1,"1663":1,"1792":3,"2177":1,"2265":3,"2495":1}}],["25",{"0":{"1299":1,"2280":1},"1":{"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1},"2":{"80":1,"81":2,"88":1,"268":1,"317":1,"869":1,"872":1,"1037":1,"1039":1,"1044":1,"1071":1,"1255":1,"1257":1,"1268":1,"1284":1,"1285":1,"1287":2,"1288":1,"1289":1,"1290":1,"1291":2,"1293":1,"1295":1,"1297":2,"1299":1,"1301":1,"1309":1,"1322":1,"1366":1,"1590":1,"1623":1,"1792":3,"1813":1,"1824":3,"2230":1,"2385":1,"2481":2}}],["2",{"0":{"846":1,"941":1,"957":1,"1001":1,"1020":1,"1068":1,"1214":1,"1221":1,"1248":1,"1308":1,"1356":1,"1389":1,"1396":1,"1402":1,"1726":1,"1825":1,"1871":1,"1992":1,"2176":1,"2238":1,"2268":1,"2439":1,"2467":1,"2514":1,"2518":1,"2539":1,"2547":1,"2552":1,"2556":2,"2560":1,"2563":1,"2568":1,"2570":1,"2587":1,"2598":1,"2639":1,"2823":1},"1":{"847":1,"848":1,"849":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"2177":1,"2178":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2515":1,"2516":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1,"2548":1,"2549":1,"2550":1,"2551":1,"2553":1,"2554":1,"2555":1,"2557":2,"2558":2,"2559":2,"2561":1,"2562":1,"2564":1,"2565":1,"2566":1,"2567":1,"2569":1,"2571":1,"2572":1,"2599":1,"2600":1,"2640":1,"2641":1,"2642":1},"2":{"1":1,"35":1,"74":3,"128":1,"271":1,"272":1,"274":2,"275":1,"310":1,"335":2,"363":1,"489":1,"493":1,"577":2,"622":1,"691":2,"696":1,"699":1,"761":1,"864":1,"865":1,"867":1,"868":1,"869":2,"871":1,"872":2,"881":1,"883":1,"885":1,"888":2,"897":3,"898":1,"911":1,"913":4,"914":1,"916":7,"917":6,"918":7,"919":8,"927":2,"929":1,"930":2,"956":2,"976":4,"977":11,"979":8,"980":6,"982":4,"986":2,"988":8,"989":2,"990":11,"991":4,"992":2,"994":5,"995":4,"997":1,"998":4,"1027":1,"1044":2,"1045":1,"1049":1,"1053":1,"1073":1,"1074":3,"1090":1,"1107":1,"1117":1,"1121":1,"1127":1,"1130":2,"1142":1,"1152":1,"1153":1,"1154":2,"1177":2,"1181":1,"1187":2,"1189":1,"1191":1,"1192":2,"1193":1,"1203":1,"1220":1,"1221":1,"1222":1,"1257":4,"1270":2,"1272":1,"1285":12,"1287":10,"1293":2,"1295":3,"1297":7,"1301":8,"1309":1,"1336":2,"1369":1,"1374":2,"1382":2,"1386":1,"1391":1,"1400":1,"1408":3,"1409":1,"1427":2,"1431":1,"1433":2,"1442":2,"1451":1,"1453":2,"1569":2,"1587":1,"1589":1,"1590":2,"1597":2,"1598":1,"1625":1,"1633":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1706":1,"1713":1,"1714":1,"1731":1,"1740":1,"1759":2,"1792":17,"1824":1,"1825":1,"1899":1,"1912":2,"1925":2,"1958":1,"1974":2,"1991":1,"1992":1,"1994":1,"2094":1,"2101":1,"2107":3,"2113":1,"2141":1,"2148":2,"2164":2,"2165":2,"2171":1,"2222":1,"2223":1,"2224":1,"2225":3,"2234":1,"2236":1,"2238":8,"2239":1,"2258":1,"2264":1,"2288":1,"2385":1,"2386":2,"2397":1,"2398":1,"2481":1,"2526":1,"2530":1,"2534":1,"2535":3,"2537":3,"2545":1,"2550":1,"2569":1,"2571":3,"2575":1,"2586":4,"2588":9,"2590":1,"2607":3,"2621":1,"2622":1,"2756":1,"2760":1,"2762":4,"2770":1,"2815":1,"2836":1,"2842":1,"2860":3,"2866":1,"2873":2,"2879":1,"2880":1}}],["yy",{"2":{"1792":1,"2077":1}}],["yyyy",{"2":{"776":2,"889":2,"892":2,"963":4,"1792":5,"1810":1,"2077":3,"2080":1,"2123":2,"2130":4,"2652":3}}],["yyy",{"2":{"40":2}}],["yamlyaml",{"2":{"2880":1}}],["yamlyamlservices",{"2":{"1775":1}}],["yamlyamlapiversion",{"2":{"1773":1}}],["yaaaarr",{"2":{"1386":1}}],["yields",{"2":{"2038":1,"2534":1}}],["yield",{"2":{"1399":1}}],["yellow",{"2":{"2415":1}}],["yeah",{"2":{"844":1,"851":1,"1402":1,"1403":1,"1442":1}}],["year=2026",{"2":{"964":1}}],["year",{"2":{"105":3,"113":1,"117":2,"137":1,"840":1,"860":2,"871":1,"964":3,"1139":1,"1142":1,"1254":3,"1363":1,"1401":1,"2385":1}}],["years",{"0":{"1076":1},"2":{"1":1,"840":2,"848":1,"851":2,"857":1,"859":1,"947":1,"1071":1,"1382":2,"1385":1,"1386":1,"1400":1,"1401":1,"1403":1}}],["yet",{"2":{"584":1,"868":1,"913":1,"919":2,"948":1,"1196":1,"1399":1,"1443":1,"2337":1,"2438":2}}],["yes",{"2":{"446":3,"803":2,"911":1,"1257":8,"1402":1,"1696":3,"1957":2,"2141":1,"2575":1,"2712":1,"2714":1,"2719":1,"2740":1,"2741":1,"2807":1,"2811":1}}],["y",{"2":{"369":1,"375":1,"834":1,"836":1,"930":1,"1521":1,"1792":1,"2332":2,"2380":1,"2528":2,"2795":1,"2861":1,"2864":2,"2866":1}}],["york",{"2":{"332":3,"1973":3,"2010":2,"2587":3}}],["younger",{"2":{"876":1}}],["yourself",{"2":{"298":1,"307":1,"308":1,"859":1,"1096":1,"1402":1,"1410":1,"1655":1,"2869":1}}],["your",{"0":{"306":1,"956":1,"1065":1,"1202":1,"1304":1,"2182":1,"2767":1,"2802":1,"2820":1},"1":{"2183":1,"2184":1,"2185":1,"2821":1,"2822":1},"2":{"296":1,"297":2,"298":1,"305":2,"307":1,"309":1,"310":1,"319":3,"320":1,"435":1,"436":1,"448":2,"455":1,"646":1,"653":1,"654":1,"801":1,"833":1,"834":4,"836":2,"837":2,"838":1,"840":1,"841":2,"848":2,"851":3,"852":11,"854":1,"855":1,"857":1,"859":1,"860":3,"861":1,"864":2,"876":1,"878":1,"879":1,"880":3,"881":3,"894":1,"903":2,"904":1,"911":1,"921":1,"926":1,"946":1,"948":1,"949":1,"952":2,"956":3,"958":1,"961":1,"965":1,"966":1,"968":1,"972":1,"973":5,"974":1,"975":2,"982":2,"988":1,"989":1,"994":2,"1002":1,"1003":1,"1005":2,"1010":1,"1013":1,"1022":1,"1031":2,"1035":1,"1037":1,"1038":3,"1042":1,"1044":1,"1045":2,"1049":2,"1052":2,"1053":1,"1060":1,"1062":1,"1065":3,"1071":1,"1073":1,"1074":1,"1078":2,"1079":2,"1080":3,"1081":1,"1082":1,"1086":2,"1094":3,"1098":2,"1107":1,"1111":1,"1113":3,"1122":2,"1123":2,"1126":1,"1127":1,"1128":1,"1135":2,"1136":1,"1137":3,"1139":3,"1141":2,"1147":1,"1156":1,"1165":1,"1169":2,"1170":3,"1171":1,"1173":1,"1176":1,"1187":1,"1191":1,"1193":1,"1195":1,"1196":1,"1197":1,"1199":1,"1202":2,"1204":3,"1207":1,"1209":2,"1210":3,"1211":2,"1215":2,"1217":1,"1220":1,"1225":1,"1231":1,"1239":1,"1240":1,"1244":1,"1247":2,"1251":1,"1252":2,"1253":2,"1281":1,"1324":1,"1327":1,"1332":3,"1340":1,"1341":1,"1350":1,"1359":1,"1363":1,"1365":1,"1366":4,"1367":1,"1368":2,"1378":1,"1381":2,"1382":4,"1385":3,"1386":6,"1396":2,"1401":1,"1402":1,"1403":14,"1404":2,"1405":3,"1406":6,"1407":1,"1409":3,"1412":1,"1413":1,"1414":2,"1416":2,"1417":2,"1418":2,"1419":6,"1422":3,"1423":1,"1432":1,"1435":2,"1436":1,"1441":3,"1443":1,"1448":1,"1453":3,"1457":1,"1464":1,"1477":1,"1581":1,"1685":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1697":2,"1704":4,"1732":1,"1757":1,"1792":23,"1825":1,"1833":1,"1868":4,"1870":1,"1875":1,"1881":1,"1889":1,"1922":1,"1942":1,"1948":1,"1974":3,"1981":1,"1990":1,"2016":1,"2019":1,"2020":1,"2021":1,"2023":1,"2059":1,"2092":1,"2111":2,"2166":1,"2170":1,"2171":1,"2175":2,"2176":1,"2177":4,"2179":1,"2180":1,"2181":4,"2182":1,"2183":1,"2184":1,"2279":1,"2376":1,"2378":1,"2388":1,"2389":2,"2391":1,"2394":1,"2406":1,"2438":1,"2481":1,"2486":1,"2487":1,"2525":1,"2532":1,"2543":1,"2551":1,"2554":3,"2586":1,"2607":3,"2632":3,"2634":2,"2635":1,"2682":1,"2713":2,"2714":1,"2717":1,"2737":1,"2741":1,"2759":3,"2760":2,"2763":1,"2766":1,"2767":1,"2772":2,"2773":2,"2775":2,"2776":1,"2785":1,"2786":1,"2793":1,"2795":3,"2802":3,"2803":1,"2807":2,"2809":1,"2811":1,"2813":1,"2818":2,"2821":1,"2822":1,"2823":1,"2825":1,"2826":1,"2833":1,"2836":1,"2840":1,"2857":1,"2859":1,"2860":1,"2861":1,"2868":1,"2881":1}}],["you",{"0":{"910":1,"1205":1,"1244":1},"1":{"911":1},"2":{"1":2,"62":1,"73":1,"88":1,"101":1,"165":1,"168":1,"175":1,"177":3,"180":1,"253":1,"286":1,"297":1,"298":2,"304":3,"306":1,"307":2,"308":3,"310":1,"319":3,"334":1,"336":1,"370":1,"373":1,"376":1,"389":1,"390":3,"436":1,"448":2,"449":1,"469":1,"470":1,"527":2,"556":1,"583":1,"587":1,"624":2,"650":1,"653":1,"654":2,"656":2,"669":1,"751":1,"832":2,"833":2,"834":1,"835":3,"836":2,"837":9,"838":3,"841":1,"843":1,"844":4,"845":1,"847":1,"848":12,"851":8,"852":10,"854":2,"856":5,"857":3,"859":4,"860":6,"861":1,"866":1,"869":2,"873":2,"876":3,"877":1,"878":1,"879":3,"880":4,"884":2,"887":1,"888":1,"894":1,"901":1,"902":2,"903":2,"904":2,"907":1,"910":1,"911":3,"920":3,"934":1,"948":1,"949":1,"953":1,"960":2,"961":2,"965":1,"966":2,"971":2,"973":1,"974":3,"982":3,"983":1,"985":3,"986":1,"990":1,"992":2,"994":3,"997":2,"1001":1,"1005":1,"1006":2,"1008":1,"1009":2,"1010":1,"1013":1,"1026":1,"1036":1,"1037":1,"1038":1,"1039":1,"1040":1,"1044":1,"1045":1,"1046":2,"1049":2,"1054":1,"1061":1,"1063":2,"1064":1,"1065":6,"1068":3,"1069":1,"1073":6,"1074":3,"1075":1,"1076":1,"1077":2,"1078":1,"1079":3,"1080":5,"1081":7,"1086":1,"1088":1,"1094":2,"1095":2,"1096":9,"1098":2,"1099":1,"1102":1,"1104":1,"1105":1,"1121":13,"1122":5,"1123":5,"1127":2,"1128":1,"1130":1,"1133":4,"1134":2,"1138":1,"1139":4,"1140":2,"1141":2,"1145":2,"1147":1,"1150":2,"1155":1,"1157":1,"1160":1,"1161":1,"1162":2,"1165":1,"1166":1,"1170":1,"1174":1,"1176":2,"1180":1,"1181":3,"1184":1,"1191":2,"1193":1,"1202":1,"1203":3,"1205":3,"1206":2,"1209":2,"1210":1,"1215":1,"1218":1,"1219":1,"1220":3,"1240":1,"1241":1,"1249":1,"1251":1,"1252":1,"1253":1,"1254":1,"1281":1,"1302":1,"1304":1,"1305":1,"1320":1,"1323":1,"1324":1,"1326":2,"1328":1,"1352":1,"1354":5,"1359":1,"1365":1,"1366":3,"1377":5,"1378":1,"1382":6,"1384":5,"1385":11,"1386":18,"1391":1,"1393":6,"1394":1,"1395":1,"1396":1,"1398":2,"1400":2,"1401":5,"1402":4,"1403":14,"1404":5,"1405":13,"1406":3,"1407":1,"1408":1,"1409":4,"1410":2,"1414":1,"1417":1,"1419":4,"1421":1,"1431":2,"1432":2,"1435":2,"1436":1,"1438":2,"1441":3,"1443":4,"1464":1,"1493":1,"1515":2,"1516":1,"1519":1,"1527":1,"1528":1,"1568":1,"1569":1,"1572":1,"1574":1,"1576":1,"1580":1,"1581":1,"1607":1,"1614":1,"1625":1,"1640":1,"1644":1,"1655":1,"1685":1,"1690":4,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1697":1,"1704":1,"1706":1,"1732":1,"1757":1,"1759":1,"1771":1,"1781":1,"1792":14,"1822":1,"1825":2,"1856":1,"1868":1,"1869":1,"1870":1,"1912":1,"1922":1,"1942":1,"1948":2,"1961":1,"1969":1,"1974":1,"1983":1,"1989":1,"2001":1,"2004":1,"2039":1,"2040":1,"2143":1,"2148":1,"2161":1,"2171":2,"2175":2,"2177":3,"2178":1,"2180":3,"2184":3,"2192":1,"2193":1,"2200":1,"2252":2,"2257":1,"2265":1,"2270":1,"2314":1,"2376":1,"2378":2,"2380":2,"2381":1,"2389":4,"2391":2,"2406":1,"2424":1,"2430":1,"2438":7,"2455":1,"2486":1,"2520":1,"2530":2,"2531":1,"2532":2,"2533":1,"2539":1,"2540":2,"2543":1,"2549":1,"2550":1,"2551":2,"2587":1,"2607":1,"2633":2,"2635":1,"2684":1,"2693":2,"2696":1,"2712":1,"2713":2,"2728":1,"2729":2,"2741":1,"2751":1,"2759":1,"2763":2,"2769":2,"2771":1,"2772":1,"2776":1,"2779":1,"2785":2,"2791":1,"2792":1,"2795":1,"2799":1,"2803":2,"2806":2,"2809":3,"2810":4,"2812":1,"2818":2,"2819":1,"2825":1,"2826":1,"2827":1,"2830":3,"2834":1,"2835":1,"2836":1,"2840":1,"2855":1,"2857":1,"2858":1,"2868":1,"2869":1,"2871":1,"2878":1}}],["mv",{"2":{"2782":1,"2783":1,"2784":1}}],["mcr",{"2":{"2450":1}}],["mcrowbuilder",{"2":{"2403":1}}],["mccompositebuffer",{"2":{"2403":1}}],["mcpoptions",{"2":{"317":1,"480":1,"1792":1,"1813":1,"1814":1,"1826":1,"1961":1,"2481":3}}],["mcp",{"0":{"317":1,"320":1,"322":1,"1038":1,"1039":1,"1042":1,"1813":1,"2166":1,"2481":2},"1":{"318":1,"319":1,"320":1,"321":1,"322":1,"323":1,"324":1,"325":1,"326":1,"327":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1814":1,"1815":1,"1816":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1825":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1834":1},"2":{"223":2,"317":6,"318":5,"319":4,"320":15,"322":2,"323":2,"324":4,"325":5,"326":5,"327":5,"480":1,"529":1,"1037":4,"1038":4,"1039":3,"1040":5,"1042":9,"1043":3,"1044":2,"1045":2,"1046":3,"1047":5,"1383":1,"1401":1,"1789":2,"1792":13,"1813":5,"1814":1,"1816":3,"1817":2,"1818":1,"1819":1,"1820":1,"1822":7,"1824":9,"1825":5,"1827":1,"1831":1,"1833":3,"1834":7,"1840":3,"1961":3,"2166":7,"2195":2,"2223":6,"2479":4,"2481":27,"2482":11,"2489":4,"2498":1,"2714":1,"2721":1,"2841":1}}],["m4",{"2":{"2397":1}}],["mdn",{"2":{"2020":1,"2021":1}}],["md5",{"2":{"1332":2,"1338":1,"1339":1,"2815":1}}],["md",{"2":{"1260":1,"2267":4}}],["mb",{"2":{"1107":2,"1277":26,"1278":6,"1511":1,"1792":1,"1804":1,"1991":2,"2245":2,"2397":2,"2398":2,"2789":2}}],["mrs",{"2":{"913":1}}],["mm",{"2":{"776":4,"889":4,"892":4,"963":7,"1792":14,"1800":1,"1809":2,"1810":2,"1917":1,"2077":6,"2080":1,"2094":1,"2101":1,"2123":4,"2130":8,"2156":1,"2537":1,"2542":1,"2652":5,"2814":1}}],["ml",{"0":{"427":1},"2":{"414":1,"427":2,"1104":1,"1105":1,"1335":1,"1343":1,"1346":1,"1396":1,"2300":1}}],["mssql",{"2":{"1391":1}}],["msg",{"2":{"1318":2,"1320":4,"2830":3}}],["msec",{"2":{"269":1}}],["ms",{"2":{"269":1,"874":2}}],["meet",{"2":{"2776":1}}],["meets",{"2":{"1792":1}}],["mental",{"2":{"1435":1}}],["mentor",{"2":{"1403":1}}],["mentioned",{"2":{"1402":1}}],["mention",{"2":{"1401":1,"2394":1,"2869":1}}],["mentioning",{"2":{"1389":1,"1396":1,"1397":1}}],["menu",{"2":{"841":1}}],["messaging",{"2":{"1037":1,"1309":2,"1327":1}}],["message>",{"2":{"2405":1}}],["messageonly",{"2":{"1792":1,"1845":1,"2802":1}}],["messageinput",{"2":{"1318":2}}],["messagetext",{"2":{"1318":3,"1321":1,"1326":1,"1372":1,"2836":1}}],["messageevent",{"2":{"1317":1,"1318":1,"1416":1,"2247":1}}],["messagecolumnname",{"2":{"1240":1,"1792":1,"1889":1,"2259":1}}],["messages",{"0":{"631":1,"2802":1},"2":{"658":1,"660":1,"661":1,"826":1,"1105":1,"1303":1,"1304":2,"1305":1,"1307":2,"1309":2,"1310":3,"1320":4,"1321":1,"1323":1,"1325":1,"1372":1,"1792":2,"1799":1,"1809":1,"2247":1,"2251":1,"2338":1,"2364":2,"2366":1,"2372":2,"2490":1,"2795":1,"2802":2,"2829":2,"2836":7}}],["message",{"0":{"313":1,"1309":1,"1310":1,"1958":1,"2119":1,"2704":1},"1":{"1959":1},"2":{"33":1,"206":1,"207":2,"208":2,"209":3,"210":2,"216":1,"300":1,"303":1,"313":1,"439":2,"447":2,"480":1,"748":1,"817":2,"818":1,"819":1,"826":3,"1019":2,"1021":2,"1026":2,"1031":1,"1037":1,"1071":1,"1103":1,"1105":5,"1111":2,"1192":4,"1193":2,"1214":1,"1215":1,"1216":1,"1220":1,"1221":1,"1232":3,"1234":3,"1236":3,"1237":2,"1239":2,"1240":1,"1302":1,"1303":3,"1304":1,"1305":2,"1307":2,"1309":13,"1310":4,"1317":2,"1318":1,"1320":9,"1321":12,"1332":2,"1338":2,"1339":2,"1341":1,"1351":1,"1372":15,"1382":1,"1398":2,"1416":1,"1426":1,"1427":1,"1431":1,"1460":1,"1464":1,"1471":1,"1480":1,"1671":1,"1684":2,"1721":1,"1722":2,"1725":1,"1727":2,"1728":1,"1732":2,"1736":3,"1742":3,"1787":1,"1792":32,"1794":1,"1800":1,"1806":1,"1809":2,"1810":1,"1845":3,"1882":2,"1884":2,"1886":2,"1887":2,"1889":1,"1916":1,"1918":2,"1921":2,"1922":2,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1958":3,"1959":1,"2011":2,"2117":1,"2119":1,"2138":1,"2141":2,"2142":4,"2144":3,"2145":2,"2146":8,"2148":3,"2247":1,"2255":4,"2259":1,"2264":7,"2289":1,"2290":3,"2321":1,"2324":2,"2338":3,"2354":2,"2363":2,"2375":1,"2376":1,"2391":1,"2394":1,"2428":1,"2435":1,"2446":1,"2468":2,"2470":1,"2471":1,"2472":4,"2528":3,"2535":2,"2549":5,"2551":2,"2575":8,"2663":1,"2702":1,"2704":1,"2762":3,"2763":3,"2764":2,"2766":2,"2769":2,"2803":4,"2810":3,"2814":1,"2815":2,"2821":1,"2828":4,"2829":25,"2830":4,"2833":1,"2836":16,"2864":2}}],["members",{"2":{"1974":2,"2607":2}}],["membership",{"2":{"1910":2,"2423":2,"2433":2}}],["mem",{"2":{"919":2}}],["memorystream",{"2":{"2404":1}}],["memorycachepruneintervalseconds",{"2":{"1145":1,"1510":1,"1511":1,"1513":2,"1534":1,"1792":1}}],["memory",{"0":{"86":1,"953":1,"969":1,"1145":1,"1513":1},"2":{"87":1,"88":3,"101":1,"104":3,"106":1,"108":1,"121":3,"122":1,"214":1,"835":1,"840":1,"841":2,"843":4,"844":12,"845":1,"848":5,"849":5,"851":21,"852":9,"854":3,"855":2,"859":2,"860":2,"861":2,"863":1,"864":2,"865":1,"868":1,"875":1,"909":1,"919":4,"920":1,"947":2,"948":3,"949":1,"951":1,"958":1,"968":2,"969":1,"1007":1,"1037":1,"1054":1,"1067":1,"1075":1,"1099":1,"1101":3,"1107":1,"1121":1,"1141":1,"1145":2,"1147":1,"1149":1,"1150":7,"1155":1,"1168":1,"1169":2,"1170":1,"1171":1,"1259":1,"1274":1,"1277":3,"1278":3,"1403":1,"1406":1,"1407":1,"1422":1,"1510":1,"1511":6,"1513":2,"1515":3,"1516":3,"1517":1,"1519":1,"1520":4,"1521":1,"1522":2,"1529":5,"1533":1,"1534":2,"1594":1,"1722":2,"1743":1,"1792":20,"1927":1,"1928":1,"1974":3,"2038":1,"2089":1,"2246":1,"2265":5,"2274":5,"2297":1,"2346":1,"2380":6,"2381":1,"2401":1,"2445":2,"2462":1,"2465":2,"2466":1,"2494":1,"2495":2,"2502":2,"2549":2,"2607":3,"2614":1,"2634":1,"2635":1,"2745":1,"2757":1,"2769":1}}],["measurably",{"2":{"876":1}}],["measures",{"2":{"1258":1}}],["measurement",{"2":{"1132":1}}],["measured",{"2":{"872":2,"874":1,"1076":1,"1381":1,"1382":4,"1443":1,"2397":1,"2398":2}}],["measure",{"2":{"851":1,"1386":1,"2744":1}}],["meanwhile",{"2":{"1366":1}}],["meant",{"2":{"916":1,"1067":1,"1068":1,"1069":1,"1385":1,"2394":1,"2414":1}}],["mean",{"2":{"841":1,"868":1,"1079":1,"1385":2,"1386":1,"1393":1,"1395":1,"1402":1,"1403":1,"2376":1,"2388":1,"2758":1}}],["meaningfully",{"2":{"872":1,"2529":1}}],["meaningful",{"2":{"372":1,"666":1,"669":1,"852":1,"869":1,"876":1,"1400":1,"2326":1,"2342":1,"2846":1}}],["meaning",{"2":{"92":1,"703":1,"713":1,"1073":1,"1138":1,"1386":1,"1401":1,"1792":2,"2113":1,"2763":1,"2810":1}}],["means",{"0":{"1421":1},"2":{"39":1,"213":1,"214":1,"349":1,"390":1,"841":1,"848":1,"852":1,"864":2,"868":1,"871":1,"872":1,"910":1,"911":1,"924":1,"927":1,"941":1,"959":1,"978":1,"1009":1,"1064":1,"1068":1,"1077":1,"1080":1,"1088":1,"1162":1,"1181":1,"1208":1,"1247":1,"1302":1,"1327":1,"1368":1,"1373":1,"1378":1,"1386":3,"1388":1,"1398":1,"1399":1,"1403":1,"1437":1,"1440":3,"1740":1,"1743":1,"1792":1,"1851":1,"2004":1,"2099":1,"2107":1,"2288":1,"2297":1,"2314":1,"2382":1,"2389":1,"2502":1,"2529":1,"2537":1,"2829":1,"2879":1}}],["mechanically",{"2":{"857":1}}],["mechanical",{"2":{"847":1,"872":2,"1069":1,"2378":1}}],["mechanisms",{"2":{"306":1,"1054":1,"1064":1}}],["mechanism",{"2":{"168":1,"212":1,"306":1,"386":1,"535":1,"847":1,"859":1,"873":1,"932":1,"1098":1,"1105":1,"1111":1,"1304":1,"1326":1,"2040":1,"2477":1,"2764":1}}],["me",{"2":{"844":1,"848":1,"920":1,"1044":2,"1057":1,"1073":1,"1128":1,"1384":1,"1385":4,"1386":1,"1398":1,"1400":1,"1401":4,"1402":6,"1403":4,"1435":1,"1692":1,"1695":1,"1792":2,"2427":2}}],["medical",{"2":{"1100":1,"1664":1,"2291":1}}],["mediates",{"2":{"851":1}}],["media",{"2":{"494":1,"546":1}}],["medium",{"0":{"95":1},"2":{"848":1,"1084":2,"1128":1,"1403":1}}],["merging",{"2":{"917":1,"1924":1,"2512":1}}],["merges",{"2":{"1190":1}}],["merge",{"2":{"857":1,"860":1,"916":1,"1559":1,"2810":1}}],["merged",{"2":{"384":1,"872":1,"915":1,"916":1,"971":1,"1097":2,"1792":2,"1924":2,"1968":1,"2222":1,"2321":1,"2509":1,"2511":1,"2513":1,"2531":1,"2577":1,"2766":1,"2812":1}}],["merely",{"2":{"843":1,"2505":1}}],["mermaidflowchart",{"2":{"297":1,"306":1,"650":1,"663":1,"833":1,"836":1,"881":1,"922":1,"949":1,"1086":1,"1087":1,"1088":1,"1184":1,"1185":1,"1211":1,"1220":1,"1221":1,"1222":1,"1305":1,"1333":1,"1868":1,"2171":1,"2760":1,"2807":1,"2828":1}}],["metered",{"2":{"1961":1}}],["metrics",{"2":{"1100":2,"1150":1,"1529":1}}],["metric",{"2":{"866":1,"911":1,"969":1,"1349":1}}],["metamorphosis",{"2":{"913":1}}],["metaphor",{"2":{"852":1}}],["meta",{"0":{"763":1,"773":1},"2":{"750":4,"751":3,"755":3,"756":3,"758":1,"760":1,"764":5,"765":3,"766":2,"767":1,"770":1,"774":6,"775":1,"777":3,"783":1,"785":1,"787":1,"789":1,"883":3,"884":4,"886":4,"887":1,"888":4,"902":1,"903":2,"904":7,"905":2,"1356":1,"1357":4,"1358":4,"1367":1,"1410":1,"1428":1,"1574":1,"1685":1,"1792":3,"2125":1,"2531":1,"2572":2}}],["metadata=",{"2":{"1827":1,"2481":1}}],["metadataqueryschema",{"2":{"1617":1,"1618":1,"1792":1,"2256":3,"2558":1}}],["metadataqueryconnectionname",{"2":{"1617":1,"1618":1,"1792":1,"2256":3}}],["metadata",{"0":{"748":1,"752":1,"762":1,"763":1,"772":1,"773":1,"887":1,"893":1,"903":1,"1359":1,"1833":1,"2256":1,"2751":1},"1":{"1360":1},"2":{"71":2,"668":1,"748":1,"750":2,"751":1,"752":4,"755":1,"756":1,"758":1,"760":1,"761":1,"762":2,"763":1,"764":1,"765":1,"767":1,"770":1,"771":1,"772":2,"773":1,"774":1,"775":1,"777":1,"778":1,"779":1,"783":1,"785":1,"787":1,"789":1,"880":1,"881":1,"882":1,"883":1,"884":2,"885":1,"886":3,"893":2,"902":1,"904":2,"905":1,"1007":1,"1045":1,"1099":1,"1129":1,"1178":1,"1193":1,"1210":1,"1249":1,"1276":1,"1279":1,"1352":1,"1357":1,"1358":4,"1359":1,"1360":1,"1366":1,"1385":1,"1386":1,"1388":1,"1410":1,"1569":1,"1618":2,"1759":1,"1773":1,"1792":27,"1828":1,"1829":1,"1831":1,"1899":1,"1912":1,"1974":2,"2123":2,"2124":6,"2125":1,"2129":1,"2131":1,"2132":1,"2222":1,"2223":1,"2256":9,"2332":1,"2370":1,"2372":1,"2399":1,"2481":2,"2482":1,"2496":1,"2498":1,"2520":1,"2572":3,"2590":1,"2600":1,"2607":2,"2608":2,"2672":1,"2772":1,"2824":1,"2825":1}}],["met",{"2":{"639":1}}],["method=",{"2":{"1491":1,"1792":1}}],["methodology",{"2":{"1084":1,"2744":2}}],["methodologies",{"2":{"840":1}}],["method>",{"2":{"243":2}}],["method",{"0":{"248":1,"250":1,"252":1,"402":1,"419":1,"420":1,"443":1,"444":1,"1846":1,"2305":1},"1":{"1847":1},"2":{"35":3,"75":1,"203":1,"214":2,"244":1,"245":1,"252":1,"413":2,"414":2,"419":1,"420":1,"423":1,"429":1,"434":2,"436":1,"443":1,"444":1,"526":1,"803":1,"835":4,"841":5,"845":2,"848":1,"852":2,"860":1,"869":2,"938":1,"995":2,"1055":1,"1073":1,"1081":1,"1105":2,"1107":1,"1335":4,"1342":1,"1386":1,"1408":1,"1416":1,"1431":1,"1492":1,"1567":1,"1568":1,"1569":1,"1575":1,"1651":1,"1743":2,"1792":8,"1822":1,"1824":1,"1844":1,"1846":1,"1924":2,"1925":1,"2110":1,"2196":3,"2197":1,"2222":1,"2247":1,"2258":2,"2301":1,"2302":2,"2305":1,"2310":1,"2313":1,"2347":1,"2370":1,"2372":1,"2403":1,"2461":3,"2481":1,"2482":1,"2502":2,"2509":1,"2511":1,"2517":1,"2529":2,"2530":1,"2549":3,"2565":1,"2614":1,"2672":1,"2723":1,"2762":1,"2765":1,"2811":2,"2865":2}}],["methods",{"0":{"204":1,"1642":1,"1729":1,"2736":1},"2":{"11":1,"26":1,"65":1,"258":1,"293":1,"518":2,"545":2,"848":1,"873":1,"1030":1,"1061":1,"1104":1,"1105":2,"1445":1,"1485":1,"1496":1,"1507":1,"1550":1,"1635":1,"1639":1,"1642":2,"1648":1,"1666":1,"1792":1,"1824":1,"1833":1,"1847":1,"1895":1,"1933":1,"1997":1,"2031":1,"2044":1,"2264":1,"2277":1,"2347":1,"2372":1,"2519":1,"2529":1,"2559":1,"2614":1,"2615":1,"2706":1}}],["mundane",{"2":{"1304":1}}],["muting",{"2":{"2752":1}}],["mutes",{"2":{"2801":1}}],["mute",{"0":{"2544":1,"2800":1},"2":{"1384":1,"1792":1,"2114":1,"2221":1,"2525":1,"2544":1,"2880":1}}],["mutex",{"2":{"1324":1}}],["mutually",{"2":{"1129":1}}],["mutation",{"2":{"2321":1}}],["mutations",{"2":{"1746":1,"2319":1,"2347":1,"2723":1,"2843":1}}],["mutating",{"2":{"214":1,"1045":1,"2502":1}}],["mutated",{"2":{"854":1,"1076":1}}],["mutate",{"2":{"849":1,"851":1,"854":1,"2533":1}}],["mutable",{"2":{"843":1}}],["much",{"2":{"847":3,"848":1,"855":1,"868":1,"920":1,"1083":1,"1132":1,"1133":1,"1254":2,"1281":1,"1385":2,"1390":2,"1399":2,"1401":1,"1402":2,"1405":2,"1435":1,"1792":2,"2016":1,"2019":1,"2398":1,"2632":1}}],["multicmdwritewrapper",{"2":{"2372":1}}],["multicommandinfo",{"2":{"2372":2}}],["multidimensional",{"0":{"2588":1},"2":{"2236":1,"2588":4,"2589":1,"2590":4}}],["multinamepattern",{"2":{"1792":2,"2093":1,"2109":1,"2530":1,"2537":1}}],["multihostconnectiontargets",{"2":{"1174":1,"1176":1,"1177":1,"1617":1,"1618":1,"1628":1,"1629":1,"1792":1,"2266":1}}],["multiparams",{"2":{"2456":1}}],["multiparamsquerystringtests2",{"2":{"2452":1,"2457":1}}],["multiparamstests2",{"2":{"2452":1,"2457":1}}],["multipartfile",{"2":{"1366":1}}],["multipart",{"2":{"779":1,"879":1,"881":2,"1792":1,"1917":1,"1924":1,"1927":2,"2512":1,"2549":2,"2814":1}}],["multiplying",{"2":{"2504":1}}],["multiply",{"2":{"1382":1}}],["multiplier",{"2":{"872":2}}],["multiplicity",{"2":{"844":1}}],["multiple",{"0":{"21":1,"62":1,"117":1,"209":1,"255":1,"290":1,"312":1,"333":1,"353":1,"488":1,"532":1,"540":1,"644":1,"778":1,"812":1,"813":1,"990":1,"1029":1,"1048":1,"1053":1,"1154":1,"1196":1,"1370":1,"1391":1,"1597":1,"1614":1,"1632":1,"2285":1,"2766":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":2,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1},"2":{"62":6,"109":1,"119":1,"123":1,"151":1,"209":1,"215":1,"216":2,"230":1,"280":2,"286":1,"299":1,"302":1,"305":1,"310":1,"348":1,"355":1,"494":2,"529":1,"559":1,"615":1,"656":1,"663":1,"687":1,"747":1,"767":1,"771":1,"778":1,"809":1,"837":1,"844":5,"849":1,"887":1,"903":1,"914":2,"915":2,"916":2,"918":1,"919":2,"920":2,"930":1,"973":1,"974":2,"990":1,"1008":1,"1015":1,"1029":1,"1034":1,"1035":1,"1036":1,"1037":1,"1048":2,"1053":1,"1064":3,"1067":2,"1088":1,"1098":3,"1099":1,"1101":1,"1102":1,"1104":1,"1105":2,"1110":1,"1111":1,"1138":1,"1142":1,"1145":1,"1147":1,"1149":1,"1150":1,"1173":1,"1175":1,"1176":1,"1178":1,"1187":1,"1193":3,"1196":1,"1205":1,"1302":1,"1358":1,"1378":3,"1386":1,"1391":2,"1398":1,"1418":1,"1445":1,"1447":1,"1458":1,"1515":3,"1517":1,"1519":1,"1597":1,"1614":1,"1688":1,"1713":1,"1738":1,"1745":1,"1746":2,"1792":7,"1907":1,"1960":1,"1985":1,"2117":1,"2118":1,"2146":1,"2149":1,"2164":1,"2175":1,"2184":1,"2192":1,"2199":1,"2200":1,"2265":1,"2270":2,"2274":2,"2277":1,"2284":1,"2285":1,"2320":1,"2339":1,"2346":1,"2347":3,"2348":1,"2359":1,"2366":1,"2375":1,"2380":1,"2422":1,"2433":1,"2438":1,"2466":1,"2528":1,"2529":2,"2540":1,"2551":1,"2575":1,"2586":1,"2590":1,"2597":1,"2633":1,"2648":1,"2680":1,"2684":1,"2685":1,"2697":1,"2702":1,"2703":1,"2737":1,"2759":1,"2774":1,"2823":1,"2845":1,"2850":1,"2858":1,"2865":2}}],["multisets",{"2":{"918":2}}],["multiset",{"0":{"912":1,"918":1},"1":{"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":2,"920":2},"2":{"338":2,"912":1,"918":6,"919":1,"920":1,"1037":1,"1097":3,"2164":2}}],["multi",{"0":{"106":1,"251":1,"541":1,"614":1,"826":1,"1070":1,"1102":1,"1173":1,"1370":1,"1626":1,"1627":1,"1629":1,"2266":1,"2320":1,"2340":1,"2357":1,"2403":1,"2850":1},"1":{"1627":1,"1628":1,"1629":1,"2851":1,"2852":1,"2853":1},"2":{"41":1,"121":1,"170":1,"238":2,"379":1,"384":1,"559":2,"568":2,"586":1,"587":1,"588":1,"614":1,"615":2,"616":1,"617":1,"618":1,"625":1,"626":2,"690":1,"701":1,"801":1,"829":1,"830":1,"834":1,"835":1,"837":1,"844":2,"865":2,"869":1,"1005":1,"1037":3,"1066":2,"1070":1,"1086":1,"1095":2,"1101":2,"1102":2,"1107":1,"1121":3,"1125":1,"1127":2,"1146":1,"1172":1,"1176":1,"1180":1,"1181":1,"1182":1,"1217":1,"1224":1,"1250":1,"1386":2,"1410":1,"1416":1,"1477":1,"1618":1,"1626":1,"1627":1,"1792":4,"1852":1,"1874":1,"2000":1,"2007":1,"2008":1,"2009":1,"2011":1,"2013":1,"2167":1,"2183":1,"2228":2,"2239":1,"2266":7,"2297":1,"2319":1,"2320":1,"2321":1,"2323":1,"2328":1,"2330":2,"2333":1,"2337":1,"2338":2,"2339":3,"2340":1,"2342":1,"2354":1,"2357":2,"2372":3,"2383":1,"2397":1,"2403":1,"2435":2,"2498":1,"2540":1,"2545":1,"2546":1,"2774":1,"2841":1,"2845":1,"2855":1,"2858":1,"2859":1}}],["must",{"2":{"21":1,"60":1,"63":1,"102":1,"182":1,"206":1,"243":1,"299":1,"324":1,"362":1,"377":1,"382":1,"384":1,"390":1,"430":1,"473":1,"480":1,"512":1,"527":1,"528":2,"534":1,"570":1,"587":2,"650":1,"673":1,"696":1,"704":1,"714":1,"718":1,"812":1,"816":1,"817":2,"819":1,"841":1,"845":4,"849":2,"851":1,"854":1,"860":1,"863":3,"864":3,"879":1,"907":1,"934":1,"983":4,"989":1,"1013":1,"1026":1,"1049":1,"1067":1,"1075":1,"1098":3,"1101":1,"1106":1,"1107":3,"1108":2,"1138":1,"1174":2,"1178":1,"1191":1,"1217":1,"1228":1,"1229":1,"1396":1,"1400":1,"1408":1,"1454":1,"1458":1,"1460":5,"1480":1,"1592":1,"1628":4,"1640":1,"1644":1,"1651":1,"1655":2,"1661":1,"1664":1,"1688":1,"1727":1,"1738":1,"1792":44,"1822":1,"1827":1,"1830":2,"1849":1,"1878":1,"1879":1,"1909":2,"1951":1,"1952":1,"1953":1,"1954":1,"2001":1,"2033":1,"2037":1,"2041":1,"2097":1,"2140":3,"2142":1,"2144":3,"2145":2,"2146":5,"2148":2,"2181":1,"2192":2,"2199":1,"2218":1,"2254":1,"2257":1,"2266":4,"2291":2,"2297":1,"2308":1,"2333":1,"2334":1,"2337":1,"2369":1,"2375":6,"2395":1,"2431":1,"2433":1,"2456":1,"2481":1,"2486":1,"2528":1,"2529":3,"2531":1,"2537":2,"2540":1,"2551":1,"2575":5,"2633":1,"2634":1,"2723":1,"2750":1,"2769":1,"2792":1,"2798":1,"2809":1,"2812":1,"2814":1,"2845":1,"2848":1,"2854":1,"2864":1,"2865":2,"2868":1}}],["mount",{"2":{"2297":1}}],["mounts",{"2":{"2157":2,"2543":1}}],["mouse",{"2":{"1044":3}}],["mozilla",{"2":{"1792":2,"2632":2}}],["monitors",{"2":{"2159":1}}],["monitoring",{"0":{"2068":1},"2":{"966":2,"1100":1,"1181":1,"1259":1,"1277":1,"1278":1,"1386":1,"1403":1,"1619":1,"1762":1,"1781":1,"1791":1,"1792":3,"2045":1,"2055":1,"2068":2,"2071":1,"2401":1,"2634":3,"2635":3}}],["monitor",{"2":{"919":1,"1601":1}}],["months",{"2":{"1065":1,"1071":1,"1382":1,"1400":1,"2297":1,"2398":1}}],["month",{"2":{"415":2,"834":2,"2303":1,"2385":1,"2813":2}}],["monthly",{"2":{"415":2,"678":1,"834":1,"960":2,"2079":1,"2303":1,"2653":1,"2813":2}}],["mocked",{"2":{"1442":1}}],["mocking",{"2":{"1415":1}}],["mocks",{"2":{"865":1,"1075":1}}],["mock",{"2":{"860":1,"865":1,"875":1,"1005":1,"1081":1,"1405":1}}],["moment",{"2":{"852":1,"856":1,"857":1,"864":1,"869":1,"984":1,"1080":1,"1384":1,"1400":1,"1406":1,"1418":1,"1421":1,"2466":1}}],["moving",{"2":{"852":1,"861":2,"864":1,"913":1,"1115":1,"1303":1,"1792":1,"1948":1,"2171":1,"2434":1,"2518":1}}],["moved",{"0":{"2487":1},"2":{"888":1,"2258":2,"2267":1,"2364":3,"2369":1,"2372":1,"2397":1}}],["moves",{"2":{"847":1,"848":1,"849":1,"1049":1}}],["move",{"2":{"841":1,"847":1,"848":1,"849":1,"878":1,"1078":1,"1125":1,"1378":1,"1381":1,"1437":1,"1438":1,"1925":1,"2781":1,"2782":1,"2783":1,"2784":1}}],["mobile",{"2":{"833":1,"835":1,"836":2,"837":1,"1447":1,"1792":1,"2174":1,"2225":1,"2429":1,"2436":1}}],["motivation",{"2":{"695":1,"2873":1}}],["mostly",{"2":{"845":1,"869":1,"1366":1,"1382":1,"2393":1}}],["most",{"2":{"175":1,"297":1,"320":1,"436":1,"683":1,"831":1,"836":1,"837":1,"847":1,"848":1,"852":2,"856":1,"859":5,"861":1,"866":1,"868":1,"869":3,"873":1,"875":1,"876":2,"877":1,"878":1,"913":1,"916":1,"921":1,"946":1,"974":1,"988":1,"992":1,"994":1,"1078":2,"1115":1,"1122":1,"1129":1,"1139":1,"1147":1,"1209":1,"1217":1,"1220":1,"1228":1,"1230":1,"1262":1,"1266":1,"1270":1,"1271":1,"1272":1,"1323":1,"1376":1,"1382":1,"1385":1,"1398":1,"1401":1,"1403":2,"1404":2,"1417":1,"1792":6,"1801":2,"1852":1,"1878":1,"1880":1,"2088":1,"2140":1,"2319":1,"2383":1,"2398":1,"2424":1,"2529":1,"2533":1,"2575":1,"2711":1,"2721":1,"2795":1,"2843":1,"2856":1,"2865":1,"2869":1,"2873":1}}],["modification",{"2":{"2319":1}}],["modifications",{"2":{"989":1,"1148":1}}],["modifiers",{"2":{"2258":1}}],["modifier",{"2":{"1792":1,"2258":1}}],["modifies",{"2":{"863":1}}],["modified",{"2":{"863":1,"1391":1,"1398":1,"2395":1}}],["modify",{"2":{"940":1,"1106":1,"1203":1,"1247":1,"1840":1,"2004":1,"2209":1,"2776":1}}],["modifying",{"2":{"171":1,"868":1}}],["modules",{"2":{"834":1,"868":1,"1037":1,"1193":3,"1385":1,"1386":1,"1414":1,"1559":1,"1792":1,"2484":1}}],["module",{"0":{"724":1,"1414":1},"2":{"719":2,"720":3,"724":4,"868":3,"1026":1,"1193":3,"1386":2,"1411":1,"1414":4,"1416":1,"1559":1,"1560":2,"1571":3,"1574":1,"1581":1,"1792":5,"2247":5,"2484":2}}],["mode=require",{"2":{"1633":2}}],["moderate",{"2":{"1179":1}}],["moderator",{"2":{"21":1}}],["modern",{"2":{"841":2,"845":3,"847":1,"848":2,"865":1,"876":2,"1064":2,"1399":1,"1405":1,"1449":1,"2425":1,"2776":1}}],["mode>",{"2":{"459":2,"498":2,"550":3}}],["modest",{"2":{"873":1,"2270":1,"2621":1}}],["modes",{"0":{"1845":1,"1849":1,"1919":1},"1":{"1920":1,"1921":1},"2":{"320":1,"876":1,"1073":1,"1097":1,"1101":1,"1105":3,"1330":1,"1609":1,"1840":1,"2221":1,"2356":1,"2541":1,"2543":1,"2659":1}}],["modelcontextprotocol",{"2":{"1047":1}}],["modeling",{"2":{"851":1}}],["models",{"2":{"841":13,"845":1,"849":1,"865":1,"1006":1,"1230":1,"1366":1,"1382":1,"1385":1,"1435":1,"1559":2,"1792":4,"1880":1,"2438":1}}],["model",{"0":{"2481":1},"2":{"223":1,"317":1,"835":1,"841":11,"845":17,"848":8,"849":1,"851":4,"852":6,"855":1,"856":1,"857":1,"863":2,"864":4,"865":2,"869":1,"871":4,"873":2,"916":1,"943":1,"1037":1,"1038":1,"1039":1,"1042":1,"1044":2,"1045":1,"1087":1,"1098":1,"1105":1,"1325":1,"1335":3,"1336":1,"1338":4,"1339":4,"1366":1,"1385":1,"1403":2,"1419":1,"1435":1,"1558":1,"1789":1,"1792":6,"1813":2,"1823":1,"2157":1,"2166":1,"2289":1,"2438":2,"2463":1,"2479":1,"2481":1,"2502":1,"2543":1,"2545":1,"2729":1,"2828":1}}],["mode",{"0":{"229":1,"438":1,"439":1,"462":1,"463":1,"464":1,"497":1,"1080":1,"1330":1,"1331":1,"1332":1,"1351":1,"1756":1,"1840":1,"1920":1,"1921":1,"2106":1,"2153":1,"2206":1,"2209":1,"2367":1,"2414":1,"2541":1,"2742":1,"2809":1,"2810":1,"2857":1,"2878":1},"1":{"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"504":1,"505":1,"506":1,"1331":1,"1332":1,"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2415":1,"2416":1,"2542":1,"2543":1},"2":{"125":1,"131":1,"159":1,"226":1,"244":2,"339":1,"346":1,"435":2,"439":2,"448":1,"453":1,"469":2,"484":1,"498":1,"501":1,"502":1,"503":2,"504":3,"505":1,"507":1,"510":2,"511":1,"512":1,"514":2,"598":1,"606":1,"691":1,"741":1,"830":1,"868":1,"876":1,"918":1,"986":1,"1037":2,"1067":1,"1070":2,"1072":1,"1073":2,"1080":1,"1081":1,"1082":1,"1094":2,"1097":1,"1101":1,"1102":4,"1121":1,"1130":1,"1332":1,"1337":1,"1338":1,"1341":1,"1349":1,"1351":3,"1379":1,"1396":1,"1398":1,"1399":1,"1414":1,"1418":1,"1419":2,"1511":1,"1525":1,"1528":2,"1609":1,"1616":2,"1628":2,"1656":6,"1753":1,"1756":1,"1789":1,"1792":12,"1824":1,"1840":2,"1845":1,"1848":2,"1849":1,"1851":3,"1864":1,"1915":2,"1928":2,"2004":1,"2007":3,"2010":2,"2098":1,"2106":3,"2113":1,"2153":1,"2154":1,"2155":1,"2156":1,"2164":1,"2168":1,"2206":1,"2209":1,"2221":2,"2225":1,"2261":1,"2266":2,"2300":1,"2328":3,"2329":2,"2353":1,"2372":1,"2382":3,"2414":2,"2415":5,"2416":1,"2435":1,"2489":1,"2492":1,"2525":1,"2527":2,"2537":2,"2541":1,"2542":1,"2546":3,"2549":6,"2726":1,"2742":1,"2806":2,"2807":3,"2809":2,"2814":1,"2815":1,"2816":1,"2824":1,"2857":1,"2862":2,"2878":1}}],["more",{"0":{"311":1,"939":1},"1":{"312":1,"313":1,"314":1,"940":1,"941":1,"942":1,"943":1,"944":1},"2":{"1":1,"87":1,"88":1,"302":1,"307":1,"347":1,"439":1,"559":1,"658":1,"659":1,"699":1,"703":1,"706":1,"713":1,"831":1,"841":5,"843":2,"844":1,"845":3,"847":2,"851":2,"855":1,"859":2,"869":1,"871":1,"875":1,"903":1,"908":1,"915":1,"916":2,"917":1,"918":1,"920":1,"933":1,"946":2,"997":1,"1037":1,"1073":1,"1077":2,"1086":2,"1115":1,"1119":1,"1123":1,"1129":1,"1133":1,"1140":1,"1150":1,"1163":1,"1176":1,"1185":1,"1187":1,"1197":1,"1254":1,"1305":1,"1349":1,"1363":1,"1385":18,"1386":4,"1390":2,"1401":1,"1402":1,"1403":2,"1404":1,"1405":1,"1406":1,"1409":1,"1428":1,"1511":1,"1625":1,"1628":1,"1792":2,"1801":1,"2160":1,"2175":1,"2177":1,"2246":1,"2255":1,"2258":1,"2266":1,"2388":2,"2393":1,"2421":1,"2422":1,"2466":1,"2530":1,"2562":1,"2682":1,"2792":1,"2834":1}}],["mytype",{"2":{"1792":1}}],["myths",{"0":{"994":1}}],["mycustomprovider",{"2":{"1697":2}}],["mypassword",{"2":{"1616":1}}],["mypassword123",{"2":{"930":2}}],["myuser",{"2":{"1616":1}}],["mydb",{"2":{"1616":1}}],["myapi",{"2":{"1502":1,"1503":1,"1505":1,"1620":1,"1758":1,"2686":1,"2795":1}}],["myapp",{"2":{"1483":1,"1646":2,"1711":2,"1792":1,"1995":1,"2062":4,"2635":1}}],["myapplication",{"2":{"48":3}}],["mysterious",{"2":{"997":1}}],["myself",{"2":{"859":1,"920":2,"1385":1,"1393":1,"1402":1,"1404":1}}],["mysql",{"2":{"834":1,"837":1,"848":4,"918":1}}],["myisam",{"2":{"848":1}}],["myb55+6lw6iiuoi3oplkysoas8j0nniuq+qe2sgaks3r62ngdjrorhx75+zmlc7t",{"2":{"38":2,"57":1,"61":3}}],["my",{"0":{"2718":1,"2721":1,"2722":1,"2725":1,"2739":1,"2740":1,"2741":1,"2799":1},"2":{"16":3,"19":2,"31":1,"38":6,"57":1,"58":2,"61":6,"155":2,"213":3,"215":2,"252":1,"376":2,"378":1,"382":1,"383":3,"402":1,"417":3,"418":5,"419":3,"420":3,"421":3,"441":2,"442":2,"443":2,"444":2,"445":2,"490":1,"511":2,"527":1,"532":2,"582":2,"584":4,"653":2,"654":5,"662":6,"760":1,"770":1,"841":1,"844":2,"857":1,"859":1,"860":2,"918":1,"920":1,"967":1,"977":1,"980":1,"990":1,"1009":1,"1017":1,"1076":1,"1077":2,"1080":1,"1081":2,"1128":1,"1134":1,"1150":1,"1232":1,"1369":1,"1370":2,"1380":1,"1384":2,"1385":3,"1398":2,"1400":5,"1401":2,"1402":3,"1403":5,"1404":8,"1421":1,"1435":1,"1442":1,"1463":2,"1529":1,"1658":1,"1738":2,"1740":3,"1792":8,"1855":1,"1882":1,"1893":1,"1899":2,"1907":1,"1929":1,"1968":1,"1988":1,"2162":1,"2164":1,"2165":2,"2173":1,"2175":2,"2187":4,"2193":9,"2196":5,"2252":2,"2255":3,"2283":4,"2285":2,"2288":3,"2305":1,"2306":2,"2314":1,"2321":3,"2334":1,"2335":1,"2337":2,"2346":1,"2348":4,"2370":2,"2410":1,"2531":1,"2549":6,"2572":1,"2575":1,"2581":3,"2591":6,"2596":1,"2645":1,"2649":1,"2664":1,"2721":1,"2729":1,"2755":4,"2765":1,"2768":2,"2822":3,"2823":1,"2824":9,"2825":4,"2832":2,"2850":2,"2854":1}}],["mimic",{"2":{"1401":1}}],["mimetype=image",{"2":{"1412":1}}],["mimetype=$",{"2":{"1364":1}}],["mimetype",{"2":{"1412":4}}],["mime",{"0":{"758":1,"1943":1},"2":{"747":4,"748":1,"753":4,"757":4,"758":3,"762":1,"772":1,"781":4,"782":4,"784":4,"786":4,"788":4,"879":1,"880":1,"882":1,"1189":1,"1358":4,"1360":1,"1362":3,"1792":5,"1937":2,"1943":1,"2016":1,"2017":1,"2125":2,"2364":1,"2371":1,"2626":1,"2632":2}}],["mid",{"2":{"1265":1,"1266":1,"1402":1,"1792":1,"2463":1,"2466":1,"2537":1,"2615":1}}],["middle",{"2":{"875":1}}],["middlewares",{"2":{"2375":1}}],["middleware",{"0":{"2463":1,"2632":1,"2633":1},"2":{"868":2,"869":2,"873":1,"874":1,"1010":1,"1026":4,"1037":1,"1066":1,"1100":3,"1101":1,"1106":1,"1108":1,"1111":1,"1156":1,"1181":3,"1320":1,"1322":1,"1350":1,"1460":2,"1568":1,"1701":1,"1703":2,"1762":1,"1792":12,"1822":1,"1827":1,"1830":1,"1835":1,"2014":3,"2016":1,"2234":2,"2255":1,"2257":2,"2258":1,"2266":1,"2362":1,"2375":3,"2421":1,"2438":3,"2481":1,"2527":1,"2537":1,"2626":3,"2632":4,"2633":2,"2714":1,"2862":1}}],["mia",{"2":{"913":1}}],["miller",{"2":{"913":1}}],["millions",{"2":{"969":1}}],["million",{"2":{"860":1,"948":1,"953":1,"969":1}}],["millisecond",{"2":{"269":1}}],["milliseconds",{"2":{"133":1,"269":2,"271":1,"274":1,"855":1,"1076":1,"1080":1,"1081":1,"1164":1,"1442":1,"1443":1,"1746":1,"2247":2,"2347":1,"2398":1,"2742":1,"2767":1,"2873":1,"2878":1}}],["microphone",{"2":{"2021":1}}],["microphone=",{"2":{"1792":2,"2021":2,"2029":1,"2632":2}}],["microsoftonline",{"2":{"1694":2,"1792":2}}],["microsoft",{"0":{"1450":1,"1457":1,"1694":1},"1":{"1451":1,"1452":1},"2":{"851":2,"868":1,"1053":1,"1054":2,"1060":1,"1098":4,"1147":1,"1445":2,"1450":1,"1451":1,"1452":1,"1453":1,"1457":1,"1465":1,"1515":2,"1682":1,"1690":1,"1694":2,"1718":1,"1783":1,"1788":1,"1792":26,"1800":1,"1802":3,"1810":1,"1894":1,"1984":1,"2257":4,"2274":2,"2386":4,"2450":1,"2462":1,"2465":2,"2495":1,"2554":3,"2567":2,"2628":1,"2633":1,"2634":1,"2736":2,"2792":1,"2795":2,"2804":1}}],["microservice",{"2":{"1035":1,"1343":1,"2759":1}}],["microservices",{"2":{"451":1,"840":1,"1064":1,"1328":2,"1329":1,"1345":1,"1457":1,"2554":1}}],["microsecond",{"2":{"269":1}}],["microseconds",{"2":{"133":1,"269":2,"271":1,"1746":1,"2347":1,"2398":1,"2767":1}}],["micro",{"2":{"848":1,"1254":1,"2397":1,"2398":1,"2621":1}}],["migrator",{"2":{"2534":1}}],["migratetemplate",{"2":{"2873":2}}],["migrates",{"2":{"2872":1}}],["migrate",{"2":{"1157":1,"1377":1,"1792":1,"1948":2,"2378":1,"2532":1,"2534":2,"2874":2}}],["migrated",{"2":{"1073":1,"2112":1,"2533":1,"2546":1}}],["migration",{"0":{"1124":1,"2874":1},"1":{"1125":1,"1126":1},"2":{"871":1,"924":2,"925":1,"930":1,"934":1,"938":1,"979":1,"982":2,"984":1,"985":2,"986":2,"987":1,"988":1,"1003":1,"1066":1,"1069":1,"1071":2,"1073":1,"1126":1,"1188":1,"1229":1,"1366":2,"1368":2,"1369":1,"1378":1,"1385":2,"1388":1,"1401":1,"1403":1,"1408":1,"1419":1,"1464":1,"1792":1,"1879":1,"2111":3,"2279":1,"2367":1,"2376":1,"2378":1,"2389":1,"2442":1,"2532":3,"2762":1,"2841":1,"2871":1,"2874":2,"2878":1}}],["migrations",{"0":{"1388":1},"2":{"696":1,"867":1,"924":2,"926":3,"932":1,"970":1,"976":2,"997":1,"1005":1,"1082":1,"1098":1,"1207":1,"1247":1,"1378":1,"1792":1,"2092":1,"2111":1,"2114":1,"2162":1,"2167":2,"2168":2,"2532":2,"2534":1,"2740":1,"2858":1,"2874":2}}],["might",{"2":{"334":1,"841":1,"851":1,"866":1,"920":1,"994":1,"1129":1,"1134":1,"1150":1,"1386":2,"1396":2,"1399":1,"1403":1,"1625":1,"1792":2}}],["mirrored",{"2":{"2110":1,"2436":1,"2530":1}}],["mirrors",{"2":{"1924":1,"2483":1,"2509":1}}],["mirroring",{"2":{"1818":1}}],["mirror",{"2":{"436":1,"707":1,"717":1,"1082":1,"1792":1,"2109":1,"2110":1,"2530":1,"2535":1,"2537":1,"2866":1}}],["minworker",{"2":{"2678":1,"2695":1}}],["minworkerthreads",{"2":{"1166":2,"1168":1,"1169":1,"1171":1,"1792":1,"2085":1,"2086":1,"2089":1}}],["mine",{"2":{"1401":1}}],["mind",{"2":{"1382":1,"1385":1,"1394":1,"2750":1,"2835":1}}],["mindset",{"2":{"838":1}}],["minapi",{"2":{"1287":3,"1288":3,"1289":3,"1290":3,"1291":3,"1293":3,"1295":3,"1297":3,"1299":3,"1301":3}}],["mincompletionportthreads",{"2":{"1166":1,"1168":1,"1169":1,"1171":1,"1792":1,"2085":1,"2086":1,"2089":1}}],["minor",{"2":{"884":1,"1071":1,"2450":2,"2461":1}}],["minus",{"2":{"852":1,"2812":1}}],["minute",{"2":{"106":1,"269":1,"861":1,"1069":1,"1147":1,"1150":3,"1162":3,"1177":1,"1458":1,"1515":1,"1525":1,"1529":2,"1769":1,"1792":5,"1958":1,"1959":2,"2060":1,"2061":1,"2211":1,"2253":2,"2274":1,"2279":1,"2375":1,"2470":1,"2471":2,"2634":1,"2635":1}}],["minutes",{"0":{"95":1},"2":{"92":1,"106":1,"107":2,"133":1,"211":1,"269":2,"271":1,"272":1,"273":2,"274":2,"275":1,"310":1,"834":1,"872":4,"911":1,"1067":3,"1071":2,"1098":1,"1101":1,"1143":1,"1147":1,"1149":1,"1150":1,"1177":1,"1206":2,"1214":1,"1232":1,"1234":1,"1316":1,"1322":1,"1382":1,"1403":1,"1430":1,"1447":1,"1451":1,"1453":2,"1454":4,"1458":2,"1463":1,"1464":3,"1511":1,"1515":2,"1519":1,"1520":1,"1521":1,"1523":1,"1525":1,"1529":1,"1639":1,"1731":1,"1792":16,"1991":1,"2211":2,"2212":1,"2264":1,"2274":1,"2279":1,"2375":2,"2376":2,"2377":2,"2380":2,"2554":2,"2580":1,"2737":1,"2745":1,"2756":1,"2833":1}}],["minlength",{"2":{"817":2,"1792":3,"2140":1,"2141":3,"2145":2,"2146":2,"2148":2,"2446":2,"2575":4,"2666":1}}],["minimized",{"0":{"1440":1},"2":{"1440":1,"1441":1,"1442":1}}],["minimum",{"0":{"1008":1,"1166":1},"2":{"627":1,"872":1,"922":1,"1165":1,"1169":1,"1616":2,"1792":7,"1802":1,"1803":1,"1804":1,"1805":1,"1807":1,"2086":2,"2141":1,"2384":1,"2544":1,"2554":1,"2575":1,"2794":1,"2804":1,"2824":1,"2825":1}}],["minimallevels",{"0":{"2544":1},"2":{"1792":3,"1800":1,"1801":1,"1802":1,"1810":1,"2104":1,"2108":1,"2114":1,"2208":2,"2221":1,"2536":2,"2537":1,"2544":2,"2628":1,"2699":1,"2749":1,"2750":1,"2752":2,"2794":1,"2795":3,"2797":1,"2798":1,"2799":1,"2800":1,"2801":1,"2804":3,"2824":2,"2825":1,"2880":1}}],["minimal",{"0":{"298":1,"1269":1,"1292":1,"1318":1,"1408":1,"1579":1,"1802":1,"1892":1},"1":{"1293":1},"2":{"308":2,"577":3,"869":3,"926":2,"945":1,"951":1,"1007":1,"1154":4,"1217":1,"1251":1,"1254":1,"1255":8,"1258":2,"1263":1,"1264":1,"1266":1,"1272":1,"1275":1,"1278":1,"1285":2,"1293":1,"1597":1,"1599":2,"1690":1,"1867":1,"2187":1,"2258":1,"2297":1,"2398":1,"2836":1,"2880":1}}],["min",{"2":{"269":1,"817":2,"872":4,"1026":1,"1285":2,"1429":5,"2146":1,"2147":2,"2148":1,"2211":1}}],["misdeclaration",{"2":{"2394":1}}],["misleading",{"2":{"1569":1,"1912":1,"2491":1,"2520":1}}],["miserable",{"2":{"1079":1}}],["misbehaving",{"2":{"864":1}}],["misunderstandings",{"2":{"841":1}}],["misconfigured",{"2":{"2405":1}}],["misconfiguration",{"2":{"109":1,"1527":1,"2380":1}}],["misconception",{"0":{"842":1,"846":1,"850":1},"1":{"843":1,"844":1,"845":1,"847":1,"848":1,"849":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1},"2":{"841":3,"845":1,"852":3,"855":1,"859":1,"860":3,"861":1,"863":1,"864":3,"865":1,"993":1,"1210":1}}],["misconceptions",{"2":{"841":2,"859":1,"865":1}}],["mismatch",{"0":{"841":1},"2":{"840":1,"841":7,"851":10,"852":2,"865":1,"871":3,"978":1,"982":2,"2370":1,"2400":1,"2405":1,"2558":1}}],["mismatches",{"2":{"587":1,"997":1,"1005":2,"2551":1}}],["misspelling",{"2":{"2493":1}}],["mission",{"2":{"1354":1}}],["missing",{"0":{"2363":1,"2438":1},"2":{"63":1,"388":1,"390":1,"841":1,"876":1,"897":1,"1079":1,"1399":1,"1460":1,"1521":1,"1524":1,"1527":2,"1792":4,"1861":1,"1862":1,"2038":1,"2039":1,"2040":1,"2184":1,"2223":1,"2242":1,"2258":1,"2267":3,"2352":1,"2375":1,"2412":1,"2417":1,"2430":1,"2443":1,"2447":1,"2476":1,"2481":1,"2497":1,"2529":1,"2558":2,"2615":1,"2666":2,"2684":1,"2685":1,"2723":1,"2732":1,"2797":1,"2824":2,"2825":1,"2835":1,"2865":1,"2868":1,"2876":1,"2881":1}}],["misses",{"2":{"872":1,"1180":1,"2879":1}}],["missed",{"0":{"2392":1},"2":{"872":1,"2226":1,"2406":1,"2407":1,"2442":1}}],["miss",{"2":{"836":1,"1191":1}}],["mistaken",{"2":{"2493":1,"2861":1}}],["mistake",{"2":{"3":1,"214":1,"2502":1}}],["mixing",{"2":{"1179":1,"2540":1,"2546":1,"2845":1}}],["mixed",{"2":{"874":1,"1255":1,"1529":1,"2193":1,"2200":1,"2319":1,"2523":1,"2532":1,"2545":1,"2546":1,"2581":2,"2611":1,"2843":1}}],["mix",{"0":{"22":1},"2":{"394":1,"448":1,"873":1,"2192":1,"2193":1,"2314":1,"2381":1}}],["mitigations",{"2":{"1942":1}}],["mitigated",{"2":{"876":1}}],["mitigate",{"2":{"848":1}}],["mit",{"2":{"2":1,"832":1}}],["m",{"2":{"1":1,"269":1,"280":2,"864":1,"903":5,"1357":15,"1410":7,"1427":2,"1431":2,"1792":2,"2077":1,"2107":1,"2211":1}}],["massive",{"2":{"1384":1}}],["masterpiece",{"2":{"913":1,"919":2,"1385":1,"1386":1,"1401":1}}],["master",{"2":{"844":2,"1224":1,"2398":1}}],["magic",{"0":{"1309":1},"2":{"1358":1}}],["magically",{"2":{"868":1}}],["macbook",{"2":{"1218":1}}],["macos",{"0":{"2784":1},"2":{"1117":1,"2157":1,"2543":1,"2716":1,"2776":1,"2779":2,"2792":1}}],["machine",{"0":{"2667":1},"1":{"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"848":3,"849":1,"1009":1,"1382":1,"1651":1,"1662":2,"1792":1,"2056":1,"2232":1,"2415":1,"2565":1}}],["machinery",{"2":{"844":1,"848":1,"851":1,"852":1,"860":1,"864":1,"865":1,"2534":1}}],["machines",{"2":{"1":2,"848":2,"1382":1}}],["major",{"2":{"1073":1,"1254":1,"1262":1,"2258":1,"2265":1}}],["majority",{"2":{"840":1,"1401":1}}],["malicious",{"2":{"933":3,"1716":1,"1792":2,"2633":1}}],["malformed",{"0":{"2491":1},"2":{"63":1,"879":1,"1041":1,"1824":1,"2007":1,"2223":1,"2328":1,"2402":1,"2481":1,"2492":1,"2498":1,"2648":1}}],["march",{"2":{"1368":1,"1405":1}}],["marched",{"2":{"840":1}}],["margin",{"2":{"1061":1,"1203":1}}],["martinez",{"2":{"913":1}}],["martinfowler",{"2":{"851":1}}],["martin",{"2":{"847":3,"848":1,"851":1,"859":1}}],["marriage",{"2":{"840":2}}],["mariadb",{"2":{"834":1}}],["markup",{"0":{"2399":1},"2":{"2400":1}}],["marker",{"2":{"2265":1,"2495":2}}],["marketing",{"2":{"2869":1}}],["marketplace",{"2":{"1792":1}}],["market",{"2":{"1011":1,"1123":1}}],["marked",{"2":{"284":1,"354":1,"381":1,"408":1,"932":1,"1129":1,"1258":1,"1792":2,"1915":1,"1974":1,"2333":1,"2489":1,"2535":1,"2543":1,"2549":1,"2607":1,"2665":1}}],["marks",{"2":{"435":1,"844":1,"934":1}}],["marking",{"2":{"176":1}}],["mark",{"2":{"12":1,"27":2,"180":1,"223":2,"224":2,"261":1,"266":1,"282":1,"294":1,"296":1,"316":1,"433":1,"589":1,"597":1,"618":1,"743":1,"1398":1,"1410":1,"1465":2,"1467":2,"1484":2,"1486":1,"1531":1,"1699":2,"1748":1,"2186":1,"2292":1,"2293":1,"2344":1,"2685":1,"2828":1,"2838":1}}],["making",{"2":{"624":1,"974":1,"975":1,"995":1,"1012":1,"1014":1,"1382":3,"1774":1,"1792":1,"2314":1,"2347":1,"2384":1,"2572":1,"2577":1,"2868":1}}],["makes",{"0":{"950":1},"1":{"951":1,"952":1,"953":1,"954":1},"2":{"202":1,"215":1,"383":1,"559":1,"586":1,"690":1,"843":1,"847":1,"852":1,"855":1,"864":1,"878":1,"932":1,"943":1,"1015":1,"1017":1,"1038":2,"1045":1,"1063":1,"1067":1,"1068":1,"1075":1,"1105":2,"1133":1,"1139":1,"1150":1,"1162":1,"1309":1,"1372":1,"1376":1,"1377":1,"1382":1,"1398":1,"1400":1,"1404":1,"1426":1,"1427":1,"1567":1,"1571":1,"1738":1,"2112":1,"2156":1,"2283":1,"2337":1,"2348":1,"2388":1,"2393":1,"2459":1,"2466":1,"2468":1,"2479":1,"2508":1,"2529":1,"2539":1,"2540":1,"2542":1,"2677":1,"2768":1,"2806":1,"2811":1,"2829":1,"2833":1}}],["make",{"0":{"2732":1},"2":{"0":1,"201":1,"305":1,"701":1,"841":1,"843":1,"848":1,"851":3,"872":1,"948":1,"961":1,"967":1,"990":1,"994":1,"1005":1,"1015":1,"1075":1,"1106":2,"1133":1,"1180":1,"1189":1,"1254":3,"1373":1,"1386":1,"1404":1,"1431":1,"1447":1,"1449":1,"1572":1,"1640":1,"1720":1,"1723":1,"1733":1,"1792":3,"2040":1,"2106":1,"2264":3,"2425":1,"2537":1,"2723":1,"2759":1,"2768":1,"2782":1,"2783":1,"2784":1,"2824":1,"2841":1,"2869":1}}],["mainly",{"2":{"912":1}}],["mainstream",{"2":{"859":2}}],["maintenance",{"0":{"351":1},"2":{"646":1,"872":1,"974":1,"1008":1,"1206":1,"1281":1,"1316":1,"2372":1,"2434":1,"2532":1,"2534":1,"2833":1}}],["maintaining",{"2":{"907":1,"947":1,"1160":1,"1181":1,"1263":1}}],["maintainability",{"2":{"860":1,"1037":1,"1281":1,"2258":1}}],["maintainable",{"2":{"859":2,"860":2,"946":1,"1385":1}}],["maintain",{"2":{"845":1,"849":2,"851":1,"873":1,"879":1,"911":1,"1006":1,"1009":1,"1046":1,"1108":1,"1119":1,"1390":1,"1406":1,"1422":1,"1519":1,"1574":1,"2380":1}}],["maintains",{"2":{"188":1,"1145":1,"1262":1,"1305":1,"2296":1,"2614":1}}],["maintainer",{"2":{"3":1}}],["maintained",{"2":{"0":1,"1":1,"2":1,"1013":1,"1382":1,"1414":1,"1419":1}}],["main",{"0":{"1766":1,"2426":1},"2":{"332":3,"693":1,"763":1,"773":1,"851":1,"852":1,"881":1,"886":1,"916":1,"1070":1,"1098":1,"1102":1,"1106":1,"1396":1,"1458":2,"1460":3,"1764":1,"1792":9,"1850":1,"1852":1,"1973":3,"2010":2,"2094":1,"2098":1,"2375":6,"2383":2,"2386":1,"2419":1,"2420":1,"2421":4,"2422":6,"2423":2,"2424":2,"2435":4,"2534":1,"2537":1,"2587":3,"2634":1,"2824":1,"2825":1}}],["maxparallelism",{"0":{"2099":1},"2":{"1792":1,"2093":1,"2094":1,"2527":1,"2533":1,"2537":1,"2862":1}}],["maxprice",{"2":{"1038":1,"1040":2,"1044":1,"1571":1}}],["maxframesize",{"2":{"1792":1,"1992":1}}],["maxforwardedqueryparamlength",{"2":{"75":1,"1431":1,"1792":1,"1916":1,"1917":1,"1925":2,"1931":1,"2222":1,"2517":1,"2522":1,"2812":1,"2814":1}}],["maxstreamsperconnection",{"2":{"1792":1,"1992":1}}],["maxreadframesize",{"2":{"1792":1}}],["maxresponsebuffersize",{"2":{"1792":1,"1990":1,"1991":1}}],["maxrequestlinesize",{"2":{"1792":1,"1990":1,"1991":1}}],["maxrequestheaderfieldsize",{"2":{"1792":2,"1992":1,"1993":1}}],["maxrequestheaderstotalsize",{"2":{"1792":1,"1990":1,"1991":1}}],["maxrequestheadercount",{"2":{"1792":1,"1990":1,"1991":1}}],["maxrequestbuffersize",{"2":{"1792":1,"1990":1,"1991":1}}],["maxrequestbodysize",{"2":{"1792":1,"1990":1,"1991":1,"1995":1}}],["maxkeywords",{"2":{"1342":2}}],["maxlength",{"2":{"1342":2,"1792":3,"2140":1,"2141":3,"2145":2,"2146":2,"2446":2,"2575":4,"2666":1}}],["maxconcurrentupgradedconnections",{"2":{"1792":1,"1990":1,"1991":1}}],["maxconcurrentconnections",{"2":{"1792":1,"1990":1,"1991":1,"1995":1}}],["maxcompletionportthreads",{"2":{"1168":1,"1171":1,"1792":1,"2085":1,"2086":1,"2089":1}}],["maxcacheentries",{"2":{"214":1,"1721":1,"1722":1,"1743":1,"1792":1,"2222":1,"2502":1,"2769":2}}],["maxcacheablerows",{"2":{"119":1,"1067":2,"1149":1,"1177":1,"1510":1,"1511":1,"1517":2,"1529":1,"1792":1,"2265":3,"2463":1,"2466":3}}],["maxworkerthreads",{"2":{"1168":1,"1171":1,"1792":1,"2085":1,"2086":1,"2089":1}}],["max",{"2":{"427":1,"542":1,"675":1,"928":4,"929":4,"1026":1,"1068":1,"1098":1,"1138":5,"1139":1,"1169":1,"1332":1,"1335":6,"1338":2,"1339":4,"1362":1,"1363":1,"1429":4,"1459":1,"1792":1,"2094":1,"2146":1,"2147":2,"2202":1,"2207":1,"2375":1,"2769":1,"2775":1,"2814":1}}],["maximumpayloadbytes",{"2":{"2274":1,"2279":1}}],["maximumkeylength",{"2":{"2274":1,"2279":1}}],["maximum",{"0":{"1008":1},"2":{"119":1,"747":1,"975":1,"1038":1,"1177":2,"1511":3,"1589":1,"1616":3,"1633":2,"1639":1,"1722":1,"1792":13,"1804":2,"1917":1,"1951":2,"1952":2,"1953":2,"1954":2,"1991":8,"2086":2,"2099":1,"2125":1,"2141":1,"2265":1,"2575":1}}],["maybe",{"2":{"840":1,"841":1,"844":2,"1221":1,"1394":1,"1399":1,"1400":1,"1401":4,"2497":1}}],["may",{"2":{"106":1,"203":1,"395":1,"454":1,"528":1,"587":1,"704":1,"709":1,"714":1,"768":1,"844":1,"859":1,"866":1,"891":1,"915":1,"916":2,"918":1,"1013":1,"1014":1,"1075":1,"1097":1,"1130":1,"1205":1,"1281":1,"1708":1,"1792":6,"1942":1,"2016":1,"2020":1,"2052":1,"2105":1,"2128":1,"2466":2,"2470":2,"2529":1,"2531":1,"2532":1,"2533":1,"2551":1,"2632":1,"2634":1,"2635":1,"2765":1,"2779":1,"2865":1,"2870":1}}],["manifests",{"2":{"2452":1}}],["manipulate",{"2":{"691":1,"933":2}}],["mandating",{"2":{"1792":1}}],["mandatory",{"2":{"1198":1}}],["mandated",{"2":{"851":1}}],["managing",{"0":{"1419":1},"2":{"1100":1,"1385":1}}],["manages",{"2":{"881":2,"945":1,"1088":1,"1167":1}}],["managed",{"2":{"869":1,"873":1,"926":1,"1013":2,"1015":1,"1084":2,"1088":1,"1094":3,"1101":2,"1107":1,"1115":1,"1123":1,"1127":2,"1230":1,"1792":1,"1880":1}}],["management",{"0":{"2297":1},"2":{"841":1,"869":1,"872":1,"879":1,"880":1,"926":1,"1026":1,"1037":1,"1048":1,"1054":1,"1064":2,"1094":1,"1098":1,"1108":1,"1115":1,"1123":1,"1181":1,"1211":1,"1274":1,"1281":1,"1303":2,"1309":1,"1320":1,"2353":1,"2438":1}}],["managers",{"2":{"865":1}}],["manager",{"2":{"543":1,"544":1,"637":1,"644":1,"663":3,"664":1,"665":1,"1045":4,"1057":2,"1081":1,"2199":1,"2200":3,"2207":2,"2391":2,"2728":1,"2831":2,"2834":3}}],["manage",{"2":{"21":2,"841":1,"845":1,"968":1,"1011":1,"1127":1,"1303":1,"1402":1,"2874":1}}],["man",{"2":{"913":1,"1442":1}}],["manually",{"2":{"879":1,"968":1,"1063":1,"1102":1,"1107":2,"1108":2,"1203":1,"1378":1,"1388":1,"1393":2,"1419":1,"2103":1,"2257":1,"2590":1,"2758":1,"2779":1,"2881":1}}],["manual",{"0":{"2779":1},"2":{"803":1,"835":1,"909":1,"1000":1,"1006":2,"1008":2,"1027":1,"1036":2,"1049":1,"1097":1,"1098":1,"1101":1,"1108":1,"1181":2,"1322":1,"1366":2,"1393":1,"1419":1,"2638":1}}],["many",{"0":{"1300":1},"1":{"1301":1},"2":{"87":1,"158":1,"480":1,"576":1,"706":1,"841":2,"852":1,"859":1,"869":1,"872":3,"913":2,"918":2,"1013":1,"1067":1,"1101":1,"1125":1,"1128":1,"1152":1,"1157":1,"1167":1,"1169":1,"1205":1,"1254":1,"1255":3,"1258":1,"1285":2,"1301":1,"1385":1,"1401":1,"1402":1,"1403":4,"1405":2,"1516":2,"1594":1,"1624":1,"1706":1,"1792":6,"1948":1,"1949":1,"1958":3,"1959":1,"1960":1,"2193":1,"2257":1,"2265":1,"2270":1,"2398":1,"2438":1,"2468":1,"2470":2,"2471":1}}],["maps",{"2":{"168":1,"395":1,"833":2,"834":1,"837":1,"1058":1,"1106":1,"1410":1,"1672":1,"1792":1,"1862":1,"2795":1}}],["mappers",{"2":{"873":1,"1435":1}}],["mapped",{"0":{"448":1,"2394":1},"2":{"60":1,"61":1,"62":1,"168":1,"197":1,"376":1,"845":2,"861":1,"937":1,"1071":1,"1188":1,"1538":1,"1792":2,"1802":1,"1822":1,"1824":1,"1922":1,"1923":1,"2226":1,"2366":4,"2384":1,"2394":2,"2395":1,"2509":1,"2540":1,"2597":1,"2733":1,"2797":1,"2824":2,"2825":1,"2845":1}}],["mappings",{"0":{"1674":1},"2":{"299":1,"879":1,"880":1,"1006":1,"1111":1,"1240":1,"1678":1,"2221":1,"2529":1,"2540":1,"2845":1}}],["mapping",{"0":{"168":1,"376":1,"802":1,"952":1,"1390":2,"1538":1,"1541":1,"1542":1,"1545":1,"1546":1,"1671":1,"1672":1},"1":{"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1},"2":{"32":1,"165":1,"168":2,"170":1,"304":1,"306":3,"315":1,"376":1,"384":1,"737":1,"740":2,"742":2,"804":2,"806":2,"835":1,"841":3,"845":1,"849":1,"851":7,"860":1,"865":1,"867":1,"869":1,"871":2,"874":1,"875":1,"947":1,"968":1,"1098":1,"1109":2,"1111":5,"1378":1,"1390":3,"1399":3,"1429":1,"1475":1,"1477":3,"1484":2,"1485":1,"1506":2,"1507":1,"1540":1,"1544":1,"1549":2,"1668":1,"1670":2,"1671":1,"1673":1,"1788":2,"1792":6,"2170":1,"2184":1,"2189":1,"2242":1,"2253":1,"2255":2,"2256":1,"2258":1,"2267":2,"2320":1,"2322":2,"2324":1,"2329":1,"2384":1,"2394":2,"2498":1,"2537":1,"2546":1,"2733":2,"2849":1}}],["map",{"0":{"2795":1},"2":{"43":1,"66":1,"315":1,"821":1,"831":1,"833":1,"841":1,"852":1,"875":1,"937":1,"1011":1,"1026":1,"1077":1,"1111":2,"1122":1,"1189":1,"1193":1,"1366":1,"1399":2,"1431":1,"1540":2,"1542":1,"1544":2,"1546":1,"1551":2,"1574":2,"2189":1,"2221":1,"2242":1,"2255":3,"2258":1,"2481":1,"2723":2}}],["maturity",{"2":{"1127":1}}],["mature",{"2":{"1122":1,"1405":1}}],["math",{"2":{"894":1,"1361":1,"1410":1}}],["mathematical",{"2":{"852":1,"1129":1}}],["materialization",{"2":{"874":2}}],["materialized",{"2":{"351":7,"949":1,"2432":1}}],["materials",{"2":{"860":1}}],["matrix",{"0":{"1093":1},"1":{"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1},"2":{"320":1,"918":1,"2417":1,"2588":3}}],["matchall",{"2":{"1431":1}}],["match",{"2":{"73":1,"102":1,"108":1,"305":1,"309":1,"389":1,"447":1,"473":1,"480":2,"524":1,"528":2,"570":1,"587":1,"639":1,"816":1,"984":2,"995":1,"1060":1,"1067":1,"1076":1,"1148":1,"1150":1,"1178":1,"1326":1,"1386":1,"1388":1,"1390":1,"1414":1,"1416":1,"1419":1,"1523":4,"1526":1,"1576":1,"1581":1,"1792":9,"1910":2,"2075":1,"2077":1,"2096":1,"2097":1,"2140":1,"2141":1,"2181":2,"2218":1,"2250":1,"2252":1,"2356":1,"2380":5,"2394":1,"2411":1,"2424":1,"2433":2,"2452":1,"2456":1,"2486":1,"2504":1,"2537":1,"2539":1,"2540":2,"2551":1,"2558":1,"2575":1,"2590":1,"2611":1,"2634":1,"2692":1,"2845":2,"2880":1}}],["matchers",{"2":{"2546":1}}],["matched",{"2":{"31":1,"38":2,"74":3,"299":1,"388":1,"395":1,"448":1,"801":1,"818":1,"1067":1,"1378":1,"1792":4,"1862":1,"1898":1,"1922":1,"2004":1,"2314":1,"2380":1,"2411":1,"2431":1,"2483":1,"2493":2,"2518":2,"2522":1,"2535":1,"2546":1,"2678":1,"2695":1,"2840":1,"2841":1}}],["matches",{"0":{"2314":1,"2518":1},"2":{"19":1,"20":1,"22":1,"175":2,"179":1,"213":1,"298":1,"388":3,"527":1,"683":1,"684":1,"685":1,"871":1,"872":1,"916":2,"1067":1,"1170":1,"1243":1,"1419":1,"1422":1,"1427":1,"1523":3,"1741":1,"1792":14,"1823":1,"1861":1,"1909":2,"2002":4,"2007":1,"2009":1,"2176":1,"2222":1,"2229":1,"2283":1,"2289":2,"2314":1,"2328":1,"2330":1,"2347":1,"2370":1,"2371":6,"2380":1,"2392":2,"2423":1,"2431":2,"2452":1,"2518":1,"2519":1,"2529":1,"2536":1,"2537":1,"2539":3,"2546":1,"2678":2,"2695":2,"2723":1,"2733":1,"2831":1,"2835":1,"2841":2,"2865":1,"2877":1,"2881":1}}],["matching",{"0":{"74":1,"407":1,"641":1,"1314":1,"2371":1,"2493":1},"1":{"75":1},"2":{"25":1,"74":1,"106":1,"156":1,"165":1,"212":1,"214":2,"258":1,"305":1,"308":1,"309":1,"376":1,"407":1,"544":1,"638":2,"641":4,"643":1,"644":1,"646":1,"650":1,"668":2,"687":1,"709":1,"801":1,"809":1,"937":1,"983":1,"1067":1,"1098":1,"1101":1,"1103":1,"1181":1,"1305":2,"1309":1,"1314":2,"1368":1,"1390":2,"1414":1,"1430":1,"1725":1,"1733":1,"1743":2,"1792":4,"1824":1,"1838":4,"1898":1,"1921":1,"1922":1,"1948":1,"2002":1,"2036":1,"2106":1,"2157":1,"2183":1,"2223":1,"2228":1,"2250":1,"2252":2,"2264":1,"2270":2,"2277":1,"2313":1,"2314":1,"2318":1,"2322":1,"2330":1,"2365":1,"2371":2,"2372":1,"2378":2,"2443":1,"2451":1,"2490":2,"2491":1,"2502":1,"2537":2,"2539":1,"2543":1,"2546":1,"2549":1,"2555":1,"2614":1,"2641":1,"2677":1,"2678":1,"2722":1,"2723":2,"2732":1,"2764":1,"2802":1,"2828":2,"2831":2}}],["matters",{"2":{"319":1,"636":1,"843":1,"848":1,"861":1,"872":1,"1146":1,"1185":1,"1354":1,"1417":1,"1516":1,"2265":1,"2466":1,"2486":1}}],["matter",{"0":{"870":1,"1704":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1},"2":{"23":1,"1167":1,"1281":1,"1393":1,"1401":1,"1402":1,"1409":1,"2207":1,"2429":1}}],["made",{"0":{"1":1,"878":1,"1043":1,"1064":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"1065":1},"2":{"216":1,"424":1,"791":1,"840":1,"848":1,"851":1,"852":3,"857":2,"876":1,"1015":1,"1037":1,"1078":1,"1332":1,"1382":1,"1385":1,"1398":1,"1404":2,"1409":1,"1608":1,"1792":1,"2019":1,"2134":1,"2264":1,"2272":1,"2307":1,"2378":1,"2440":1,"2500":1,"2518":1,"2527":1,"2726":1,"2868":1,"2871":1}}],["dll",{"2":{"2157":1,"2543":2}}],["dns",{"2":{"1792":1,"1823":1,"2481":1,"2763":1}}],["d4d4d4",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["dpapi",{"0":{"1662":1},"2":{"1651":2,"1659":1,"1662":2,"1792":3,"2297":1,"2565":5}}],["dpapilocalmachine",{"2":{"1650":1,"1651":1,"1662":2,"1792":1,"2565":3}}],["django",{"2":{"1037":1,"1255":1,"1257":1,"1265":1,"1271":2,"1277":1,"1278":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"2534":1,"2874":1}}],["dto",{"2":{"867":1,"869":2,"871":2,"873":4,"874":1,"875":1,"1366":1,"1382":1,"1406":1,"1419":1,"1422":1}}],["dtos",{"2":{"856":1,"869":1,"873":3,"1011":1,"1366":1,"1401":1,"1409":1,"1435":1}}],["dwell",{"2":{"847":1}}],["duties",{"2":{"2543":1}}],["dusty",{"2":{"1435":1}}],["duedate",{"2":{"1391":2}}],["due",{"2":{"1391":1}}],["dumping",{"2":{"2415":1,"2679":1}}],["dumped",{"2":{"2389":1}}],["dumps",{"2":{"2364":1}}],["dump",{"2":{"1365":1,"1441":1,"2111":1,"2414":1,"2415":1,"2416":1}}],["dumb",{"2":{"921":1}}],["duplicating",{"2":{"1193":1}}],["duplication",{"0":{"1191":1},"2":{"2247":1}}],["duplicate",{"0":{"2326":1},"2":{"865":1,"1249":1,"1792":1,"2109":1,"2267":1,"2326":1,"2500":1,"2530":1,"2679":1}}],["duplicated",{"2":{"838":1,"865":1,"1043":1,"1191":2,"1825":1,"2481":1}}],["dual",{"2":{"1047":1,"2166":1,"2479":1}}],["duckdb",{"2":{"834":1,"837":1,"848":1}}],["durability",{"2":{"852":1}}],["durable",{"2":{"852":2,"857":1}}],["durations",{"2":{"1071":1,"1464":1,"2376":1}}],["duration",{"0":{"1769":1,"2210":1},"1":{"2211":1,"2212":1},"2":{"110":1,"122":1,"217":1,"232":1,"267":1,"1255":1,"1447":1,"1535":1,"1537":1,"1764":1,"1792":3,"1951":1,"1952":1,"2047":1,"2052":1,"2210":1,"2634":1,"2635":1}}],["during",{"2":{"174":1,"366":2,"582":1,"587":1,"668":1,"786":1,"788":1,"851":1,"861":1,"938":1,"953":1,"984":1,"986":1,"987":1,"997":1,"1001":1,"1073":1,"1094":1,"1129":1,"1152":1,"1153":1,"1164":1,"1165":1,"1171":1,"1180":1,"1210":2,"1225":1,"1236":1,"1239":1,"1254":2,"1259":1,"1277":1,"1304":1,"1309":1,"1324":3,"1325":1,"1326":1,"1386":3,"1396":1,"1400":1,"1401":1,"1417":1,"1618":1,"1685":1,"1792":2,"1875":1,"1886":1,"1888":1,"1974":1,"1983":1,"2007":2,"2088":1,"2092":1,"2336":1,"2337":3,"2363":1,"2366":1,"2372":1,"2397":1,"2428":1,"2466":1,"2554":1,"2607":1,"2615":1,"2786":1,"2847":1}}],["ddd",{"0":{"1443":1},"2":{"839":1,"841":1,"844":1,"845":1,"851":5,"863":1,"965":1,"1403":1,"1435":1}}],["dd",{"2":{"776":2,"889":2,"892":2,"963":4,"1792":5,"1810":1,"2077":3,"2080":1,"2123":2,"2130":4,"2652":3}}],["ddl",{"2":{"206":1,"854":1,"1075":1,"1079":1,"1082":1,"1098":2,"1368":2,"1369":1,"1378":1,"1385":1,"1388":2,"1727":1,"2532":1,"2820":1,"2858":1,"2869":1,"2871":1,"2873":1,"2876":1}}],["dml",{"2":{"624":1,"2342":1}}],["db=app",{"2":{"2873":1}}],["dbg",{"2":{"2011":1,"2354":1,"2366":4,"2797":2,"2824":10,"2825":9}}],["dbas",{"2":{"1013":1}}],["dba",{"2":{"864":1,"926":1,"1792":2,"2059":2,"2635":2}}],["dbnull",{"2":{"188":1,"379":1,"1523":1,"1792":1,"2140":1,"2284":1,"2296":1,"2333":1,"2380":1,"2575":1}}],["db",{"0":{"531":1},"2":{"147":1,"396":1,"527":1,"534":1,"836":3,"872":1,"876":1,"945":2,"970":1,"997":2,"1005":1,"1047":1,"1102":4,"1104":1,"1106":1,"1173":3,"1176":3,"1177":5,"1320":1,"1325":1,"1333":3,"1342":1,"1349":2,"1366":2,"1380":1,"1431":1,"1433":2,"1608":1,"1616":1,"1627":3,"1629":6,"1738":2,"1792":1,"2098":1,"2111":2,"2161":1,"2162":1,"2168":2,"2266":3,"2272":1,"2284":1,"2463":1,"2466":2,"2495":1,"2512":1,"2533":2,"2534":2,"2687":4,"2807":1,"2809":1,"2871":1,"2872":2,"2873":1}}],["dynamically",{"2":{"646":1,"1406":1,"1420":1,"1618":1,"1620":1,"1708":1,"1792":2,"2633":1}}],["dynamic",{"0":{"156":1,"392":1,"493":1,"544":1,"646":1,"679":1,"880":1,"888":1,"1316":1,"1374":1,"1525":1,"2764":1},"1":{"157":1},"2":{"123":1,"156":1,"230":1,"396":1,"452":1,"493":1,"674":1,"679":1,"680":1,"725":1,"784":2,"790":1,"880":1,"883":1,"957":1,"959":1,"960":1,"987":1,"1101":1,"1105":1,"1407":1,"1417":1,"1422":1,"1582":1,"1684":1,"1792":1,"2079":1,"2653":1,"2759":1}}],["dry",{"2":{"1190":1}}],["dr",{"0":{"1074":1}}],["drift",{"2":{"869":1,"871":2,"872":2,"873":1,"875":1,"1006":1,"1046":1,"1382":1,"1400":1,"1406":1,"1407":1,"1409":2,"2518":1}}],["drifting",{"2":{"865":1}}],["drive",{"2":{"2017":1,"2185":1}}],["driver",{"2":{"1274":1,"1382":1,"2394":2,"2540":1}}],["drives",{"2":{"1043":1,"2106":1,"2154":1,"2185":1,"2432":1,"2435":1,"2472":1}}],["driven",{"0":{"1197":1},"2":{"302":1,"840":1,"841":3,"843":1,"851":5,"860":1,"863":2,"873":1,"1066":1,"1104":1,"1328":1,"1351":1,"1405":1,"1416":1,"1852":1,"2171":1,"2383":1,"2389":2,"2412":1,"2434":1,"2546":1}}],["driving",{"0":{"1044":1},"2":{"327":1,"1038":1,"1834":1,"2166":1,"2479":1}}],["dramatically",{"2":{"1266":1,"1324":1,"1440":1,"2398":1}}],["drawbacks",{"2":{"1012":1}}],["drawn",{"2":{"864":1}}],["dragging",{"2":{"948":1}}],["draft",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"101":1,"839":1,"1399":2,"2380":1,"2670":1}}],["drew",{"2":{"852":1}}],["dressed",{"2":{"851":1}}],["droptemplate",{"2":{"2873":1}}],["droptestdb",{"2":{"2873":1}}],["droptestdatabase",{"2":{"1792":1,"2111":1,"2532":1}}],["dropdatabase",{"2":{"2111":1,"2112":1,"2532":2,"2534":2,"2871":2,"2872":2}}],["drops",{"2":{"1324":1,"1431":1,"1440":1,"2110":1,"2157":1,"2530":1,"2533":1,"2537":1,"2543":2,"2740":1,"2758":1,"2869":1,"2872":1}}],["dropisolateddb1",{"2":{"2873":2}}],["dropisolateddb",{"2":{"695":1,"705":2,"711":1,"715":2,"2533":1}}],["drop",{"2":{"449":1,"584":1,"650":1,"697":1,"705":1,"714":1,"835":1,"857":1,"876":1,"924":1,"925":1,"977":1,"1076":2,"1079":1,"1086":1,"1151":1,"1368":1,"1376":2,"1395":1,"1396":2,"1424":1,"1427":1,"1436":1,"1441":1,"1443":1,"1447":1,"1449":1,"1576":1,"1792":3,"1898":1,"2098":1,"2106":1,"2111":3,"2156":1,"2337":1,"2378":1,"2397":2,"2428":1,"2431":1,"2434":2,"2466":1,"2532":4,"2533":1,"2534":3,"2542":1,"2546":1,"2758":2,"2855":1,"2869":3,"2871":3,"2872":1,"2873":1,"2878":1,"2881":1}}],["dropping",{"2":{"171":1,"835":1,"1792":1,"1948":1,"2425":1}}],["dropped",{"2":{"109":1,"872":1,"1080":1,"1449":1,"1527":3,"1569":1,"1581":1,"1792":1,"1957":1,"2106":2,"2379":1,"2380":1,"2425":1,"2505":1,"2537":2,"2546":1,"2742":1,"2878":2}}],["dark",{"2":{"2535":1}}],["damage",{"2":{"1185":1}}],["damn",{"2":{"1132":1,"1384":2}}],["dangerous",{"0":{"941":1},"2":{"973":1}}],["dance",{"2":{"849":1,"1379":1}}],["davis",{"2":{"913":1,"919":2}}],["david",{"2":{"913":1,"919":2}}],["dalloway",{"2":{"913":1}}],["dapper",{"2":{"869":1,"1255":1,"1257":1,"1265":1,"1269":1,"1277":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["datname",{"2":{"2758":2}}],["dateonly",{"2":{"2224":1,"2453":2}}],["dated",{"2":{"1400":1}}],["datestr",{"2":{"961":3}}],["dates",{"2":{"952":2,"963":1}}],["daterange",{"2":{"864":1}}],["dateend",{"2":{"845":1}}],["datebegin",{"2":{"845":1}}],["datetimekind",{"2":{"2451":1}}],["datetimeformat",{"2":{"1792":1}}],["datetimestyles",{"2":{"2451":3}}],["datetimes",{"2":{"952":1}}],["datetime",{"0":{"963":1,"2451":1},"2":{"776":2,"788":3,"892":2,"952":1,"1792":4,"2077":1,"2130":1,"2224":1,"2451":3,"2452":2,"2453":2,"2455":1,"2621":1,"2652":1}}],["date=2024",{"2":{"372":2,"2319":2,"2846":2}}],["date",{"2":{"106":1,"372":2,"427":1,"585":1,"679":2,"723":2,"776":2,"788":3,"839":1,"860":2,"864":3,"888":9,"892":2,"897":1,"952":1,"956":5,"961":1,"996":2,"1073":4,"1092":1,"1179":7,"1187":2,"1188":2,"1189":1,"1191":1,"1192":3,"1193":3,"1255":1,"1320":1,"1335":1,"1373":2,"1374":4,"1391":1,"1398":1,"1792":3,"2130":1,"2221":1,"2222":1,"2223":1,"2224":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":1,"2238":1,"2239":1,"2240":1,"2319":2,"2380":2,"2445":1,"2453":5,"2621":1,"2731":2,"2823":1,"2824":1,"2845":6,"2846":2}}],["datadog",{"2":{"2804":1}}],["data2",{"2":{"2008":1}}],["data1",{"2":{"2008":1}}],["datasources",{"2":{"2266":2}}],["datasource",{"2":{"1792":2,"2364":1,"2824":1,"2825":1}}],["datasets",{"2":{"87":1,"88":1,"918":1,"919":1,"1133":1}}],["dataset",{"2":{"84":1,"949":1,"951":1}}],["datacell",{"2":{"951":1}}],["databa",{"2":{"913":1}}],["databasepollinginterval",{"0":{"2156":1},"2":{"1792":1,"2106":1,"2154":2,"2542":1,"2878":1}}],["database`",{"2":{"1792":1}}],["databasecheckname",{"2":{"1792":1,"2634":1}}],["database=myapp",{"2":{"1771":2,"2063":2}}],["database=mydb",{"2":{"1117":2,"1173":1,"1176":2,"1177":2,"1613":1,"1614":2,"1627":1,"1629":2,"2266":1,"2686":1,"2689":1,"2691":1,"2699":1,"2718":1,"2823":2,"2824":3,"2825":2}}],["database=analytics",{"2":{"1614":1}}],["database=appdb",{"2":{"2534":1}}],["database=app",{"2":{"695":2,"1792":1,"2098":2,"2534":1,"2872":1,"2873":3,"2874":1}}],["database=",{"2":{"937":1,"1607":1,"1615":1,"1633":2,"1792":1,"2687":1}}],["database=postgres",{"2":{"695":1,"2098":1,"2534":1,"2872":1,"2873":1}}],["databases",{"0":{"2758":1},"2":{"150":1,"696":1,"841":1,"844":2,"848":2,"859":1,"918":1,"921":1,"977":1,"980":1,"990":1,"993":1,"994":1,"1013":1,"1075":3,"1081":1,"1082":2,"1094":1,"1130":1,"1172":1,"1178":2,"1205":1,"1403":1,"1614":1,"1789":1,"1792":1,"2092":1,"2103":1,"2114":1,"2167":1,"2221":1,"2534":1,"2545":1,"2546":1,"2740":1,"2758":1,"2869":1,"2881":1}}],["database",{"0":{"148":1,"855":1,"921":1,"977":1,"993":1,"1003":1,"1075":1,"1197":1,"1213":1,"1331":1,"1503":1,"1655":1,"1770":1,"1781":1,"2504":1,"2534":1,"2542":1,"2718":1,"2740":1,"2741":1,"2868":1,"2872":1,"2873":1},"1":{"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1504":1,"1771":1},"2":{"37":1,"41":1,"87":1,"144":1,"151":1,"153":1,"165":1,"173":1,"182":1,"184":1,"188":1,"215":1,"226":1,"236":1,"307":3,"357":1,"362":1,"424":1,"436":1,"438":2,"549":1,"569":1,"587":1,"618":1,"625":1,"694":1,"695":1,"696":1,"697":2,"705":3,"711":2,"714":1,"715":1,"777":1,"807":2,"818":1,"832":2,"834":2,"836":3,"837":1,"840":1,"841":5,"844":7,"845":5,"847":3,"848":1,"849":2,"851":5,"852":4,"855":1,"856":1,"857":1,"859":2,"860":1,"864":2,"865":3,"868":1,"871":1,"872":1,"873":1,"874":2,"875":1,"876":3,"877":1,"903":1,"909":1,"916":1,"917":1,"918":4,"919":1,"920":1,"921":2,"922":2,"926":1,"940":1,"941":1,"943":1,"946":2,"948":2,"966":1,"970":1,"972":1,"973":2,"974":6,"975":1,"982":1,"985":2,"986":1,"993":2,"994":3,"997":1,"1002":1,"1005":3,"1006":2,"1007":1,"1008":1,"1009":2,"1010":1,"1013":2,"1014":7,"1015":4,"1033":1,"1035":1,"1037":6,"1049":5,"1054":4,"1065":1,"1071":1,"1073":1,"1074":1,"1075":10,"1078":2,"1079":5,"1080":3,"1082":2,"1083":1,"1086":2,"1091":1,"1094":4,"1095":2,"1098":1,"1099":1,"1100":1,"1104":3,"1105":3,"1106":2,"1107":3,"1108":1,"1113":1,"1115":1,"1123":1,"1127":1,"1129":3,"1133":1,"1136":1,"1141":2,"1147":2,"1148":1,"1151":1,"1152":1,"1161":1,"1167":3,"1169":1,"1178":1,"1180":2,"1184":1,"1185":3,"1190":1,"1193":1,"1203":1,"1205":2,"1206":3,"1208":1,"1209":2,"1210":1,"1217":2,"1224":2,"1250":2,"1255":2,"1258":1,"1268":1,"1269":1,"1276":1,"1280":1,"1281":1,"1303":1,"1320":1,"1324":2,"1325":1,"1328":2,"1329":3,"1331":1,"1337":1,"1342":1,"1347":2,"1349":1,"1351":3,"1353":1,"1354":3,"1357":2,"1363":2,"1365":1,"1366":5,"1368":2,"1370":1,"1379":1,"1382":3,"1386":4,"1388":2,"1394":1,"1396":1,"1398":1,"1399":2,"1402":1,"1403":10,"1405":4,"1406":1,"1407":1,"1408":1,"1409":2,"1419":3,"1421":3,"1422":2,"1432":1,"1435":1,"1439":1,"1442":2,"1466":1,"1470":1,"1504":1,"1515":1,"1536":1,"1586":1,"1601":1,"1608":1,"1609":1,"1611":1,"1612":1,"1613":1,"1616":2,"1619":1,"1621":1,"1624":1,"1630":1,"1632":1,"1634":1,"1651":3,"1653":1,"1655":2,"1661":1,"1663":2,"1664":2,"1678":1,"1700":1,"1738":1,"1749":1,"1753":3,"1756":2,"1758":2,"1764":1,"1767":2,"1768":2,"1769":1,"1770":2,"1771":1,"1774":2,"1778":1,"1781":1,"1783":1,"1787":1,"1789":1,"1790":1,"1792":38,"1794":1,"1797":1,"1799":1,"1805":2,"1812":1,"1818":1,"1840":1,"1853":1,"1865":1,"1867":1,"1868":1,"1874":2,"1898":1,"1920":1,"1928":1,"1974":1,"1997":1,"2040":1,"2047":1,"2059":1,"2060":2,"2063":1,"2071":1,"2087":1,"2091":1,"2098":3,"2103":1,"2106":3,"2110":2,"2111":5,"2112":2,"2121":1,"2137":2,"2149":1,"2153":3,"2154":1,"2155":1,"2156":3,"2157":2,"2158":1,"2161":1,"2162":1,"2164":2,"2165":1,"2167":4,"2168":1,"2171":1,"2177":4,"2194":1,"2222":1,"2224":1,"2254":1,"2257":1,"2274":1,"2282":1,"2291":1,"2292":1,"2296":2,"2297":3,"2307":1,"2320":1,"2337":1,"2367":1,"2384":1,"2456":2,"2459":1,"2463":1,"2464":1,"2477":1,"2481":1,"2500":1,"2504":1,"2525":1,"2527":1,"2530":2,"2531":2,"2532":8,"2533":6,"2534":7,"2536":1,"2537":3,"2541":4,"2542":2,"2543":2,"2545":2,"2546":3,"2549":1,"2565":1,"2575":2,"2580":1,"2596":1,"2607":1,"2608":2,"2615":6,"2621":1,"2634":9,"2635":5,"2669":1,"2672":1,"2686":1,"2699":1,"2701":1,"2706":1,"2709":1,"2717":1,"2721":1,"2722":1,"2724":1,"2740":1,"2742":3,"2755":1,"2758":2,"2766":1,"2772":2,"2774":1,"2785":1,"2802":1,"2803":2,"2806":1,"2807":1,"2810":1,"2815":2,"2816":1,"2818":1,"2819":2,"2820":1,"2822":1,"2823":1,"2825":1,"2840":1,"2850":1,"2854":1,"2857":1,"2858":1,"2862":2,"2867":3,"2869":1,"2871":5,"2872":5,"2873":10,"2874":1,"2875":1,"2878":4,"2881":1}}],["datagrip",{"2":{"876":1}}],["data=",{"2":{"383":1,"2348":1}}],["dataprotection",{"0":{"2353":1,"2565":1},"2":{"182":1,"1054":1,"1650":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1660":1,"1661":1,"1662":1,"1663":2,"1792":1,"2291":1,"2297":7,"2353":3,"2551":2,"2565":3,"2701":1,"2757":1}}],["data",{"0":{"452":1,"775":1,"842":1,"850":1,"900":1,"987":1,"992":1,"1054":1,"1241":1,"1284":1,"1286":1,"1347":1,"1649":1,"1890":1,"2291":1,"2734":1,"2757":1},"1":{"843":1,"844":1,"845":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1},"2":{"22":1,"48":2,"83":1,"85":1,"86":1,"94":1,"104":2,"106":2,"107":1,"168":1,"175":1,"182":2,"184":1,"188":1,"189":1,"191":1,"206":3,"209":8,"211":2,"213":3,"214":4,"215":2,"263":1,"264":1,"265":1,"278":1,"334":4,"383":1,"401":6,"415":2,"423":1,"426":1,"427":1,"436":7,"438":3,"439":1,"445":2,"446":6,"447":1,"449":1,"452":5,"453":1,"454":2,"467":5,"491":2,"492":1,"493":3,"503":3,"510":3,"521":3,"527":2,"533":1,"540":2,"566":1,"584":1,"586":1,"595":1,"621":2,"666":1,"678":1,"679":3,"686":1,"722":3,"723":4,"732":1,"762":1,"763":1,"764":2,"765":1,"766":1,"772":1,"773":2,"774":2,"775":1,"776":1,"777":1,"779":1,"782":1,"784":1,"788":1,"828":1,"832":1,"834":1,"840":1,"841":14,"843":3,"844":6,"845":3,"847":3,"848":8,"849":1,"851":13,"852":9,"857":2,"859":3,"860":8,"861":5,"863":2,"864":2,"865":1,"868":2,"869":4,"871":1,"877":1,"878":1,"879":1,"880":1,"881":3,"883":3,"884":1,"887":1,"888":1,"893":2,"903":3,"904":1,"909":1,"911":1,"913":3,"916":1,"918":1,"921":1,"922":1,"924":1,"938":1,"940":1,"941":1,"948":3,"956":1,"957":1,"959":2,"960":2,"961":3,"964":1,"965":1,"971":1,"974":2,"979":2,"980":1,"986":3,"987":2,"988":2,"989":3,"990":2,"991":2,"992":1,"1003":1,"1010":2,"1011":2,"1015":3,"1018":1,"1021":6,"1026":8,"1030":1,"1034":3,"1035":3,"1037":2,"1047":1,"1054":16,"1060":3,"1066":2,"1070":1,"1076":2,"1091":1,"1096":1,"1098":4,"1100":1,"1105":4,"1107":2,"1111":1,"1114":1,"1121":1,"1122":1,"1138":1,"1139":5,"1145":1,"1147":2,"1148":3,"1150":3,"1163":1,"1176":2,"1179":1,"1183":1,"1184":1,"1185":6,"1188":1,"1191":2,"1193":1,"1202":1,"1203":3,"1205":4,"1206":4,"1207":1,"1208":1,"1210":3,"1216":1,"1217":1,"1218":1,"1222":1,"1228":1,"1236":1,"1237":1,"1239":6,"1251":3,"1255":2,"1266":1,"1271":1,"1278":1,"1280":1,"1309":1,"1317":1,"1318":1,"1320":2,"1321":1,"1323":1,"1327":1,"1329":1,"1338":1,"1347":3,"1348":2,"1349":1,"1351":2,"1362":1,"1363":1,"1373":2,"1374":1,"1376":5,"1382":3,"1385":1,"1386":2,"1393":1,"1394":1,"1403":2,"1404":1,"1405":4,"1412":2,"1413":2,"1416":1,"1429":3,"1432":1,"1435":2,"1441":1,"1481":1,"1496":1,"1515":1,"1518":2,"1519":3,"1524":1,"1547":3,"1632":5,"1649":1,"1650":3,"1651":7,"1655":16,"1656":1,"1657":1,"1658":1,"1659":1,"1662":1,"1663":2,"1664":3,"1684":2,"1687":2,"1689":2,"1723":1,"1730":2,"1731":2,"1733":1,"1737":1,"1738":3,"1740":3,"1742":2,"1746":2,"1747":1,"1788":1,"1792":43,"1795":1,"1811":1,"1868":1,"1878":1,"1886":1,"1887":1,"1888":1,"1893":1,"1917":1,"1918":1,"1920":5,"1921":2,"1922":1,"1924":2,"1926":1,"1927":3,"1929":1,"1974":3,"2008":1,"2029":1,"2049":1,"2050":1,"2051":1,"2052":2,"2079":1,"2130":1,"2156":1,"2202":1,"2230":1,"2247":1,"2256":2,"2264":2,"2265":2,"2266":4,"2270":1,"2274":1,"2283":4,"2286":1,"2288":3,"2290":2,"2291":3,"2292":1,"2296":1,"2297":7,"2303":1,"2304":1,"2314":1,"2322":1,"2336":1,"2337":1,"2343":2,"2344":1,"2346":1,"2347":3,"2348":1,"2357":1,"2381":3,"2398":3,"2399":1,"2421":1,"2495":1,"2496":2,"2526":1,"2531":1,"2540":1,"2542":1,"2549":8,"2551":5,"2558":1,"2565":1,"2572":1,"2580":3,"2588":1,"2607":3,"2635":5,"2653":1,"2656":1,"2739":1,"2750":1,"2757":2,"2765":1,"2768":3,"2775":5,"2798":1,"2810":3,"2811":2,"2814":1,"2830":1,"2836":1,"2852":1,"2854":1,"2860":1,"2868":1}}],["daily",{"0":{"97":1},"2":{"97":1}}],["dashboardcontroller",{"2":{"1026":1}}],["dashboards",{"2":{"836":1,"866":1,"1323":1,"1327":1,"1399":1}}],["dashboard",{"0":{"1018":1,"1115":1},"1":{"1019":1,"1020":1,"1021":1,"1022":1},"2":{"95":1,"104":5,"168":2,"251":1,"264":1,"278":1,"353":4,"479":1,"562":1,"563":1,"566":1,"834":1,"836":1,"837":1,"960":1,"1010":1,"1011":1,"1018":1,"1020":1,"1021":5,"1023":1,"1026":3,"1036":1,"1069":1,"1084":1,"1088":2,"1094":2,"1107":1,"1115":1,"1123":1,"1127":2,"1138":1,"1143":1,"1150":1,"1163":1,"1376":6,"1398":4,"1399":1,"1529":1,"1533":1,"1698":1,"1745":1,"2035":1,"2042":1,"2346":1,"2535":1,"2766":3,"2767":4,"2770":1}}],["days",{"2":{"133":1,"188":1,"269":2,"280":1,"427":1,"851":1,"911":1,"1068":2,"1071":1,"1098":1,"1322":1,"1403":1,"1446":1,"1447":3,"1451":1,"1453":1,"1454":3,"1458":1,"1462":1,"1463":1,"1464":4,"1651":1,"1698":1,"1792":9,"2211":2,"2296":1,"2375":1,"2376":4,"2377":2,"2427":1}}],["day",{"2":{"92":1,"269":1,"271":1,"272":1,"273":1,"427":1,"848":1,"860":1,"866":1,"1068":2,"1078":1,"1082":1,"1098":1,"1143":1,"1401":1,"1419":2,"1458":1,"2211":1,"2375":1,"2376":1,"2377":1}}],["dxnlcji6cgfzczi=",{"2":{"62":1}}],["dxnlcje6cgfzcze=",{"2":{"62":1}}],["degree",{"2":{"2536":1}}],["degrades",{"2":{"1165":1}}],["degraded",{"2":{"1098":1,"1766":1,"1782":1,"1792":2,"2634":2}}],["demomode",{"2":{"2040":1,"2476":1}}],["demons",{"0":{"1079":1}}],["demonstrate",{"2":{"2188":1}}],["demonstrates",{"2":{"915":1,"921":1,"936":1,"996":1,"1220":1,"1275":1,"2868":1}}],["demonstrating",{"2":{"938":1,"1061":1,"1207":2,"2160":1,"2380":1,"2856":1}}],["demonstration",{"2":{"926":1}}],["demos",{"2":{"877":1}}],["demo",{"0":{"938":1,"1061":1},"2":{"877":1,"1047":1,"1382":1,"1433":5,"1792":1,"2038":1,"2040":4,"2164":3,"2476":4,"2762":1,"2770":2,"2816":1}}],["demand",{"2":{"876":1,"2438":1}}],["debounced",{"2":{"2537":1,"2543":1}}],["debate",{"2":{"1403":1}}],["debt",{"2":{"864":1}}],["debuglogcommentannotationevents",{"2":{"1792":1,"1844":1,"2629":1}}],["debuglogendpointcreateevents",{"2":{"1792":1,"1844":1,"2629":1}}],["debugtable=",{"2":{"2110":1}}],["debugtable",{"0":{"2110":1},"2":{"1792":2,"2109":1,"2110":1,"2530":1,"2537":1,"2866":1}}],["debugger",{"2":{"1792":1}}],["debuggability",{"2":{"1511":1,"1792":1,"2265":1}}],["debugging",{"0":{"2208":1,"2748":1},"1":{"2749":1,"2750":1,"2751":1,"2752":1},"2":{"872":2,"1015":1,"1111":1,"1181":2,"1678":1,"1792":3,"1801":2,"1994":1,"2045":1,"2054":1,"2109":1,"2110":1,"2255":1,"2366":1,"2394":1,"2492":1,"2530":1,"2537":1,"2577":1,"2597":1,"2635":2,"2805":1}}],["debug",{"0":{"2363":1,"2629":1,"2799":1},"2":{"64":1,"320":1,"704":1,"1014":1,"1082":1,"1199":1,"1500":1,"1792":13,"1801":1,"1802":2,"1806":1,"1844":3,"2011":1,"2108":1,"2110":3,"2111":1,"2208":2,"2258":1,"2261":1,"2267":1,"2354":1,"2363":1,"2364":4,"2366":3,"2384":1,"2482":1,"2530":1,"2532":1,"2536":1,"2628":1,"2629":4,"2721":1,"2749":2,"2751":1,"2794":1,"2795":1,"2797":1,"2802":1,"2804":1,"2824":1,"2825":2,"2880":1}}],["dedup",{"2":{"2506":1}}],["deduplicated",{"2":{"2590":1}}],["deduplicate",{"2":{"860":1}}],["dedicate",{"2":{"1403":1}}],["dedicates",{"2":{"847":1}}],["dedicated",{"0":{"2534":1,"2872":1},"2":{"841":2,"1035":1,"1205":1,"1255":1,"1325":1,"1398":1,"1533":1,"1792":2,"2098":1,"2156":1,"2221":1,"2267":1,"2381":1,"2542":1,"2832":1}}],["denial",{"2":{"1792":1,"2634":1,"2876":1}}],["denied",{"0":{"2755":1},"2":{"39":3,"2608":1}}],["denies",{"0":{"39":1}}],["denormalize",{"2":{"1437":1,"1438":1}}],["denormalized",{"2":{"1385":1}}],["deno",{"0":{"1107":1},"2":{"1104":1,"1107":4,"1108":3,"1115":1,"1126":1}}],["denser",{"2":{"869":1,"873":1}}],["dense",{"2":{"869":1}}],["deny",{"2":{"844":1,"1792":4,"1898":1,"2015":1,"2016":2,"2018":2,"2027":1,"2028":1,"2431":1,"2436":2,"2632":3}}],["devtools",{"2":{"2040":1}}],["dev123",{"2":{"1199":2,"1207":1}}],["devops",{"2":{"926":1}}],["dev",{"0":{"1417":1,"2857":1},"1":{"1418":1,"1419":1,"1420":1,"1421":1},"2":{"868":3,"871":1,"872":1,"873":1,"875":1,"970":1,"997":1,"1037":1,"1047":1,"1094":3,"1199":2,"1207":1,"1380":1,"1407":1,"1414":1,"1417":1,"1418":2,"1419":2,"1433":2,"1792":1,"2153":1,"2157":2,"2159":1,"2162":1,"2168":1,"2450":1,"2537":2,"2543":2,"2878":1,"2880":1}}],["developing",{"2":{"1401":1}}],["developement",{"2":{"1254":1}}],["developer",{"2":{"871":1,"872":1,"972":1,"1382":2,"1385":1,"1403":1,"1693":1,"1792":2,"2389":1,"2395":1,"2632":2}}],["developers",{"0":{"1443":1},"2":{"859":2,"1435":1,"1692":1,"1695":1,"1792":4,"2193":1}}],["developed",{"2":{"851":1,"1073":1,"1386":1}}],["development",{"0":{"973":1,"1181":1,"1402":1,"1716":1,"2066":1},"2":{"121":1,"868":1,"871":1,"914":1,"997":1,"1001":1,"1009":1,"1037":1,"1064":1,"1073":2,"1080":2,"1081":1,"1086":1,"1094":1,"1145":1,"1181":1,"1199":1,"1206":1,"1225":1,"1254":1,"1281":2,"1303":1,"1384":1,"1386":3,"1401":2,"1402":2,"1403":2,"1417":1,"1418":3,"1792":4,"1875":1,"1983":1,"2007":1,"2117":1,"2245":1,"2255":1,"2366":1,"2681":1,"2684":2,"2685":1,"2699":1,"2702":1,"2776":1,"2798":1,"2804":1,"2824":3,"2825":1}}],["deviceinfo",{"2":{"1792":1}}],["devicememory",{"2":{"1792":2}}],["devicepixelratio",{"2":{"1792":1}}],["devicename",{"2":{"1214":2,"1215":1,"1218":1,"1221":1,"1232":3,"1792":3,"1882":1}}],["device",{"2":{"847":4,"848":5,"851":1,"859":1,"1037":1,"1068":1,"1098":3,"1209":1,"1210":3,"1213":1,"1215":1,"1228":1,"1230":1,"1249":1,"1792":5,"1866":1,"1879":1,"1880":1,"2164":1,"2625":1}}],["devices",{"0":{"846":1},"1":{"847":1,"848":1,"849":1},"2":{"841":1,"848":2,"1792":1,"1867":1,"2625":1}}],["dead",{"2":{"2050":1,"2809":1}}],["deadlock",{"2":{"1592":2,"1792":1}}],["deadlocks",{"2":{"576":1,"1151":1,"1153":1,"1155":1}}],["deallocate",{"2":{"2342":1}}],["deals",{"2":{"1382":1}}],["deal",{"2":{"1073":1,"1385":1,"1390":1,"1438":1}}],["dealt",{"2":{"860":1}}],["dealing",{"2":{"840":1,"1140":1,"1181":1,"1385":1}}],["derivation",{"0":{"2327":1},"2":{"363":1,"1049":1}}],["derived",{"2":{"215":1,"322":1,"436":1,"527":1,"534":1,"662":1,"1007":1,"1040":1,"1046":1,"1276":1,"1404":1,"1738":1,"1792":2,"1824":2,"1830":1,"1955":1,"2318":1,"2327":1,"2379":1,"2415":1,"2481":3,"2840":1}}],["deeply",{"2":{"2607":1,"2611":1}}],["deeper",{"2":{"1098":1}}],["deep",{"0":{"334":1,"2607":1,"2611":1},"2":{"334":1,"337":1,"837":1,"840":1,"845":1,"919":2,"1064":1,"1097":2,"1409":1,"2236":1,"2394":1,"2611":1}}],["destructive",{"2":{"2319":1,"2843":1,"2873":1}}],["destination",{"0":{"393":1},"2":{"2803":1}}],["desired",{"2":{"1254":1,"1792":1,"2351":1,"2781":1}}],["designation",{"2":{"2117":1,"2702":1}}],["designate",{"2":{"1925":1}}],["designated",{"2":{"305":1,"1924":1,"2509":1}}],["designed",{"2":{"848":3,"851":1,"860":1,"863":2,"951":1,"965":1,"1185":1,"1326":1,"1377":1,"1382":1,"1385":1,"1396":1,"1401":1,"1421":1,"2667":1}}],["design",{"0":{"1050":1,"1251":1},"1":{"1051":1,"1052":1},"2":{"840":1,"841":4,"843":1,"845":1,"851":7,"863":3,"873":1,"922":1,"1078":1,"1096":1,"1121":1,"1193":1,"1382":1,"1385":1,"1395":1,"1396":1,"1399":1,"1402":1,"1961":1,"2405":1,"2437":1,"2504":1,"2713":1,"2729":1,"2881":1}}],["deserialization",{"2":{"1258":1,"1399":2,"2347":1,"2600":1}}],["deserialized",{"2":{"861":1}}],["deserves",{"2":{"1076":1,"1400":1}}],["desktop",{"2":{"1047":1,"2157":2,"2543":1}}],["desynchronization",{"2":{"1008":1}}],["despite",{"2":{"666":1,"1580":1}}],["descent",{"2":{"2443":1}}],["descended",{"2":{"2411":1}}],["descope",{"2":{"1098":1}}],["desc",{"2":{"263":1,"318":1,"326":1,"531":2,"1429":1,"2344":1,"2481":1,"2482":1}}],["descriptors",{"2":{"1974":1,"2607":1}}],["descriptive",{"2":{"559":1}}],["descriptions",{"2":{"2545":1,"2677":1,"2682":2}}],["description",{"0":{"319":1,"323":1,"324":1,"414":1,"435":1},"2":{"31":1,"121":1,"210":1,"310":1,"318":4,"319":8,"322":1,"323":1,"324":3,"326":4,"447":1,"499":1,"517":1,"551":1,"576":1,"638":1,"675":1,"720":1,"738":1,"739":1,"746":1,"747":1,"748":2,"753":1,"757":1,"761":1,"762":1,"763":1,"768":1,"771":1,"772":1,"773":1,"776":1,"781":1,"782":1,"784":1,"786":1,"788":1,"802":1,"816":1,"834":1,"859":1,"882":1,"891":1,"892":1,"967":1,"1031":2,"1040":4,"1042":1,"1152":1,"1155":1,"1217":1,"1224":1,"1225":1,"1226":1,"1227":1,"1232":1,"1234":1,"1236":1,"1237":2,"1239":2,"1255":1,"1340":1,"1341":2,"1404":2,"1447":1,"1451":1,"1454":1,"1470":1,"1471":1,"1472":1,"1473":1,"1474":1,"1475":1,"1477":1,"1479":1,"1480":1,"1489":1,"1499":1,"1500":1,"1501":1,"1511":1,"1521":1,"1523":1,"1540":1,"1544":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1588":1,"1589":1,"1592":1,"1604":1,"1616":1,"1618":1,"1620":1,"1623":1,"1624":1,"1628":1,"1631":1,"1639":1,"1651":1,"1656":1,"1657":1,"1670":1,"1671":1,"1674":1,"1684":1,"1687":1,"1696":1,"1703":1,"1722":1,"1731":1,"1732":1,"1753":1,"1754":1,"1755":1,"1756":1,"1764":1,"1792":6,"1801":1,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1,"1808":1,"1809":1,"1821":1,"1823":2,"1824":1,"1837":1,"1838":1,"1840":2,"1841":1,"1843":1,"1844":1,"1845":1,"1846":1,"1848":1,"1849":1,"1850":1,"1853":1,"1854":1,"1855":1,"1856":1,"1857":1,"1861":1,"1862":1,"1874":1,"1875":1,"1876":1,"1877":1,"1882":1,"1884":1,"1886":1,"1887":2,"1888":1,"1898":2,"1900":2,"1902":1,"1903":1,"1904":1,"1905":1,"1906":3,"1907":3,"1911":1,"1917":1,"1918":1,"1922":1,"1927":1,"1937":1,"1938":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1956":1,"1967":1,"1972":1,"1980":1,"1991":1,"1994":1,"2000":1,"2002":1,"2004":1,"2005":1,"2007":1,"2016":1,"2018":1,"2019":1,"2023":1,"2024":1,"2025":1,"2034":1,"2036":1,"2038":1,"2047":1,"2074":1,"2075":1,"2077":1,"2086":1,"2094":1,"2109":1,"2117":1,"2119":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1,"2129":1,"2130":1,"2131":1,"2139":1,"2140":1,"2141":1,"2154":1,"2164":1,"2165":1,"2166":1,"2167":1,"2168":1,"2247":1,"2254":5,"2264":1,"2266":1,"2297":1,"2323":1,"2330":1,"2434":1,"2481":7,"2482":1,"2549":1,"2565":1,"2575":2,"2671":1,"2702":1,"2769":1,"2814":1,"2835":1,"2841":1}}],["describing",{"2":{"843":1,"1039":1,"1422":1,"2670":1,"2694":1}}],["describes",{"2":{"1421":1}}],["described",{"2":{"840":1,"872":1,"986":1,"1080":1,"2106":1,"2112":1,"2158":1,"2321":1,"2367":1,"2406":1,"2482":1,"2537":1,"2540":1,"2541":1,"2840":1,"2845":1}}],["describe",{"0":{"2336":1,"2337":1,"2367":1,"2854":1},"2":{"238":1,"383":1,"581":1,"582":1,"586":2,"587":5,"696":1,"829":2,"1181":1,"1792":2,"1802":1,"2000":1,"2007":2,"2098":1,"2324":1,"2328":1,"2336":1,"2337":4,"2367":1,"2533":1,"2534":1,"2540":2,"2558":1,"2722":1,"2751":1,"2795":1,"2799":1,"2840":2,"2847":1,"2854":2,"2859":1}}],["delta",{"2":{"1080":1,"2106":1,"2158":1,"2537":1,"2878":1}}],["delegates",{"2":{"847":2,"904":1,"2462":1,"2649":1}}],["delegated",{"2":{"747":1,"843":2,"847":1,"2664":1}}],["delegate",{"2":{"747":1,"781":1,"892":1,"1101":1,"1197":1,"2621":2}}],["deleted",{"2":{"777":2,"2050":1}}],["delete",{"2":{"18":4,"204":1,"243":1,"258":2,"288":2,"292":1,"518":1,"567":1,"623":1,"624":1,"625":1,"691":4,"701":1,"835":1,"848":1,"991":3,"1104":1,"1105":1,"1107":1,"1213":1,"1235":2,"1382":1,"1487":1,"1642":1,"1646":1,"1729":1,"1792":1,"1846":1,"1847":1,"2264":1,"2277":2,"2319":3,"2320":1,"2321":1,"2342":1,"2389":1,"2429":1,"2481":1,"2498":1,"2529":1,"2723":1,"2762":1,"2843":3,"2851":1,"2865":1,"2879":1}}],["deliberate",{"2":{"2376":1,"2378":1,"2466":2}}],["deliberately",{"2":{"307":1,"1096":1,"1409":1,"1639":1,"1644":1,"1792":2,"2107":1,"2112":1,"2177":1,"2180":1,"2486":1,"2529":1,"2537":1,"2729":1,"2828":1,"2845":1,"2865":1,"2876":1}}],["delivered",{"0":{"2490":1},"2":{"1372":1,"2223":1,"2490":1}}],["deliver",{"2":{"1281":1,"1402":1}}],["delivering",{"2":{"1280":1}}],["delivery",{"2":{"1183":1,"1206":1,"1325":1,"1353":1,"2407":1,"2490":1,"2498":1}}],["delivers",{"2":{"946":1,"1009":1}}],["delimit",{"2":{"494":2}}],["delimited",{"0":{"491":1},"2":{"2495":1}}],["delimiter=",{"2":{"1202":1}}],["delimiters",{"0":{"767":1},"2":{"767":2,"768":2,"786":3,"787":1,"886":2,"891":1,"2270":1}}],["delimiter",{"2":{"131":2,"346":1,"496":2,"606":1,"767":1,"768":1,"891":1,"1189":2,"2128":1,"2270":1,"2726":1}}],["delays",{"2":{"213":2,"575":1,"1032":1,"1101":1,"1105":1,"1152":1,"1168":1,"1589":1,"1739":1,"1740":1,"2088":1,"2287":1,"2288":1}}],["delay",{"2":{"203":3,"213":7,"214":1,"848":1,"1032":4,"1101":1,"1105":1,"1108":1,"1171":1,"1590":1,"1645":1,"1739":1,"1740":6,"1742":1,"2222":1,"2230":1,"2287":1,"2288":6,"2290":1,"2466":1,"2502":1,"2505":1,"2765":3,"2835":1}}],["depletes",{"2":{"1329":1}}],["deploying",{"2":{"2089":1}}],["deploys",{"2":{"1086":1,"1432":1}}],["deployed",{"2":{"869":1,"1073":1,"1107":2,"1115":1,"2040":1,"2452":1,"2474":1}}],["deploy",{"2":{"534":1,"1073":1,"1087":1,"1094":1,"1204":1,"1322":1,"1420":1,"1438":1,"1527":1,"1792":2,"2633":1,"2634":1}}],["deployment",{"0":{"1116":1,"1773":1},"1":{"1117":1,"1118":1,"1119":1},"2":{"390":1,"534":1,"832":1,"878":1,"954":1,"986":1,"1009":1,"1013":1,"1037":1,"1070":1,"1083":1,"1084":1,"1086":1,"1094":2,"1095":1,"1108":2,"1121":1,"1127":2,"1144":1,"1204":1,"1272":1,"1303":1,"1304":1,"1322":1,"1327":1,"1403":1,"1405":1,"1414":1,"1773":1,"1825":1,"2056":1,"2490":1}}],["deployments",{"2":{"121":2,"1054":1,"1145":1,"1146":1,"1152":1,"1172":1,"1343":2,"1615":1,"1653":1,"1708":1,"1792":1,"1856":1,"1967":1,"2350":1,"2455":1}}],["depended",{"2":{"2110":1,"2530":1}}],["dependents",{"2":{"2106":1,"2537":1,"2878":1}}],["dependent",{"2":{"1098":1,"2157":1,"2543":1}}],["dependencies",{"0":{"1248":1},"2":{"970":1,"1027":3,"1036":1,"1064":1,"1075":1,"1117":1,"1145":1,"1200":1,"1322":1,"2162":1,"2289":1,"2385":1,"2776":1,"2792":1}}],["dependency",{"2":{"869":1,"1026":1,"1044":1,"1075":1,"2634":1,"2741":1,"2868":2}}],["depend",{"2":{"1133":1,"1239":1,"1792":1,"1856":1,"1974":1,"2389":1,"2607":1,"2873":1}}],["depending",{"2":{"312":1,"587":1,"683":1,"957":1,"1355":1,"1385":1,"1499":1,"1519":1,"1525":1,"1792":3,"2337":1,"2541":1}}],["depends",{"2":{"244":1,"386":1,"616":1,"695":1,"1067":1,"1359":1,"1366":1,"2252":1,"2398":1,"2455":1,"2807":1,"2867":1}}],["depths",{"2":{"1097":1}}],["depth",{"0":{"940":1,"1185":1,"1297":1},"2":{"306":1,"334":1,"337":1,"338":1,"857":1,"919":1,"933":1,"1097":1,"1185":1,"1255":1,"1285":1,"1717":1,"1894":1,"1967":1,"1974":1,"2003":1,"2107":1,"2371":1,"2398":1,"2531":1,"2537":1,"2607":1,"2868":1}}],["deprecation",{"2":{"174":1}}],["deprecated",{"2":{"174":1}}],["department=engineering",{"2":{"1405":1}}],["department",{"2":{"113":1,"117":2,"167":3,"1142":1,"1405":2,"2207":4,"2322":3,"2774":3}}],["dec",{"2":{"2823":1,"2824":1}}],["decrement",{"2":{"1439":1}}],["decrease",{"2":{"1324":1}}],["decryption",{"0":{"2405":1},"2":{"187":1,"188":1,"1649":1,"1664":1,"1667":1,"1792":1,"2226":1,"2294":1,"2296":1,"2329":1,"2405":2}}],["decrypted",{"2":{"182":2,"186":1,"187":1,"188":2,"1100":1,"1664":1,"2291":1,"2293":1,"2294":1,"2295":1,"2296":2}}],["decrypt",{"0":{"182":1,"185":1,"2291":1,"2293":1},"1":{"183":1,"184":1,"185":1,"186":2,"187":1,"188":1,"189":1,"190":1,"191":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1},"2":{"182":1,"186":6,"187":1,"188":1,"237":2,"868":1,"1100":1,"1127":1,"1649":1,"1651":2,"1658":1,"1662":1,"1664":4,"1665":1,"1667":1,"1792":1,"2230":1,"2291":1,"2293":6,"2294":1,"2295":1,"2296":1,"2297":3,"2323":1,"2329":1,"2353":2,"2405":1,"2421":1}}],["deck",{"2":{"1037":1,"1381":2,"1382":1}}],["december",{"2":{"972":1,"1400":1,"1402":1}}],["decoupling",{"2":{"2490":1}}],["decoupled",{"2":{"860":1}}],["decoder",{"2":{"2492":1}}],["decode",{"0":{"2492":1},"2":{"2492":1}}],["decoded",{"2":{"63":1,"1457":1,"2492":1,"2554":1,"2603":1}}],["decoding",{"2":{"1218":1}}],["decades",{"2":{"852":1,"856":1,"857":1,"860":1,"1075":1,"1385":1}}],["decade",{"2":{"851":1,"1403":1}}],["deciding",{"2":{"860":1,"1820":1}}],["decided",{"2":{"1924":1,"2828":2}}],["decide",{"2":{"854":1,"874":1,"985":1,"1042":1,"1130":1,"1249":1,"1403":1,"1435":1,"2415":1,"2511":1}}],["decides",{"2":{"636":1,"650":1,"669":1,"855":1,"1044":1,"1108":2,"1133":1,"2171":1,"2172":1}}],["decision",{"2":{"855":2,"868":1,"1078":1,"2422":1,"2868":1}}],["decisions",{"2":{"855":1,"2435":1}}],["decisively",{"2":{"834":1,"835":1}}],["decimals",{"2":{"952":1,"1623":1,"2212":1}}],["decimal",{"0":{"274":1},"2":{"268":1,"280":1,"1590":1,"1792":2}}],["declarations",{"2":{"1385":1,"1386":1,"1401":1,"1403":1,"1570":1,"1571":1,"2484":1}}],["declaration",{"2":{"849":2,"860":3,"864":1,"916":1,"983":1,"1042":2,"1394":1,"1403":1,"1523":1,"1792":1,"2380":1}}],["declarative",{"0":{"1105":1,"2773":1},"1":{"2774":1,"2775":1},"2":{"835":1,"841":1,"860":2,"868":1,"918":1,"1037":1,"1066":1,"1102":3,"1104":1,"1108":1,"1121":2,"1127":4,"1181":1,"1382":4,"1386":1,"1394":1,"1403":1,"1406":1,"2772":1,"2858":1}}],["declaratively",{"2":{"835":1,"857":1,"1406":1,"2190":1,"2773":1}}],["declaring",{"2":{"448":1,"1382":1,"2496":1}}],["declares",{"2":{"582":1,"1376":1,"1382":1,"1460":1,"1792":1,"2097":1,"2375":1,"2533":1,"2537":1,"2760":1,"2807":3}}],["declared",{"2":{"175":3,"180":1,"320":1,"327":1,"447":1,"587":1,"684":3,"711":1,"835":1,"849":1,"854":1,"864":1,"868":1,"982":2,"1016":1,"1070":1,"1098":1,"1101":1,"1113":1,"1189":1,"1403":1,"1407":1,"1408":1,"1792":1,"1834":1,"1922":2,"2016":1,"2017":1,"2097":1,"2413":1,"2444":1,"2481":2,"2498":1,"2632":1,"2868":1}}],["declare",{"0":{"2337":1},"2":{"1":1,"310":1,"428":1,"438":1,"448":1,"452":1,"527":1,"583":1,"585":1,"665":1,"708":1,"848":1,"860":1,"876":1,"928":1,"929":1,"930":1,"990":2,"991":1,"1021":1,"1060":1,"1128":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1239":1,"1309":1,"1321":1,"1332":1,"1338":1,"1339":1,"1347":1,"1372":1,"1376":1,"1386":1,"1389":1,"1393":1,"1394":1,"1395":3,"1396":1,"1401":1,"1403":1,"1419":1,"1427":1,"1689":1,"1792":1,"2183":1,"2337":2,"2394":1,"2741":1,"2762":1,"2763":2,"2809":1,"2810":2,"2815":1,"2829":2,"2834":1,"2836":1,"2855":1,"2868":1,"2877":1}}],["def",{"2":{"1366":1}}],["defers",{"2":{"2868":1}}],["defer",{"2":{"992":1,"2868":1,"2869":1}}],["deferrable",{"0":{"992":1,"2868":1},"2":{"977":1,"986":1,"992":4,"1005":2,"1075":2,"1079":2,"1082":1,"2167":1,"2545":1,"2741":1,"2868":5,"2869":1}}],["deferred",{"2":{"872":1,"876":1,"992":4,"1079":1,"2741":2,"2868":2}}],["defense",{"0":{"940":1,"1185":1},"2":{"921":1,"933":1,"981":1,"1136":1,"1185":1,"2020":1}}],["defensible",{"2":{"873":1}}],["defect",{"2":{"857":2}}],["defining",{"0":{"2762":1},"2":{"1398":1,"1690":1,"1792":2,"2277":1,"2759":1}}],["definitions",{"0":{"1730":1},"2":{"966":1,"978":1,"981":1,"1016":1,"1027":1,"1036":1,"1121":1,"1350":1,"1744":1,"1792":1,"2051":1,"2160":1,"2329":1,"2346":1,"2347":1,"2372":2,"2635":2,"2775":1}}],["definition",{"0":{"1726":1,"1728":1},"1":{"1729":1,"1730":1},"2":{"203":1,"582":1,"584":1,"852":1,"864":1,"871":1,"978":1,"980":1,"992":1,"1010":1,"1016":1,"1017":1,"1023":1,"1076":1,"1105":2,"1193":1,"1369":1,"1398":1,"1403":1,"1408":1,"1723":2,"1726":1,"1792":1,"2156":1,"2264":3,"2337":2,"2542":1,"2854":1}}],["definer",{"0":{"932":1},"2":{"298":1,"351":1,"922":3,"932":4,"933":5,"934":1,"935":1,"936":1,"943":1,"946":1,"1065":1,"1068":1,"1179":1,"1184":2,"1185":4,"1188":1,"1192":1,"1214":1,"1215":1,"1216":1,"1308":1,"1458":3,"1567":1,"1655":2,"2176":1,"2186":1,"2187":1,"2375":3}}],["defines",{"2":{"197":1,"202":1,"213":1,"458":1,"480":1,"575":1,"852":1,"859":1,"863":1,"956":1,"1019":1,"1032":1,"1152":1,"1187":1,"1191":1,"1426":1,"1460":1,"1590":1,"1613":1,"1740":1,"1792":4,"2016":1,"2020":1,"2171":1,"2288":1,"2375":1,"2632":2,"2762":1}}],["defined",{"2":{"101":1,"102":1,"104":1,"150":1,"197":1,"206":2,"480":1,"570":1,"575":2,"584":1,"809":1,"817":1,"915":1,"916":1,"917":1,"918":1,"924":1,"973":1,"1096":1,"1108":1,"1197":1,"1247":1,"1402":1,"1435":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1501":1,"1609":1,"1727":1,"1792":10,"2164":1,"2282":1,"2287":1,"2422":1,"2532":1,"2551":1,"2634":1,"2635":1,"2661":1}}],["define",{"0":{"165":1,"978":1,"1019":1,"1020":1,"1187":1,"2849":1},"1":{"166":1,"167":1,"168":1,"169":1,"170":1,"979":1,"980":1},"2":{"62":1,"165":2,"166":2,"167":2,"168":2,"169":3,"201":1,"206":1,"223":1,"238":2,"253":1,"266":1,"310":1,"411":1,"526":1,"577":1,"578":1,"868":1,"914":1,"916":1,"918":1,"933":1,"982":1,"1000":1,"1008":1,"1010":1,"1037":1,"1105":2,"1111":1,"1113":1,"1154":1,"1176":1,"1190":1,"1192":1,"1193":1,"1196":1,"1203":1,"1208":1,"1211":1,"1219":1,"1281":1,"1328":1,"1374":4,"1398":1,"1399":1,"1401":1,"1412":1,"1413":3,"1434":1,"1597":1,"1614":1,"1673":1,"1697":1,"1736":1,"1792":1,"1869":1,"1901":1,"1989":1,"2322":4,"2323":3,"2381":1,"2747":1,"2759":1,"2762":1,"2849":2,"2859":1}}],["defaultapp",{"2":{"2697":2}}],["defaultauthenticationtype",{"2":{"937":1,"1469":1,"1470":1,"1483":1,"1792":1}}],["defaultdataprotector",{"2":{"2291":1}}],["defaultdisplaynameclaimtype",{"2":{"1792":1}}],["defaultratelimitingpolicy",{"2":{"2257":1}}],["defaultrequestparamtype",{"2":{"1792":1,"1836":1,"1846":1,"1847":1,"2701":1}}],["defaultroutinecache",{"2":{"108":1}}],["defaultroleclaimtype",{"2":{"22":1,"305":2,"641":1,"1062":1,"1469":1,"1474":1,"1483":1,"1792":1,"2181":2,"2187":1,"2314":1}}],["defaulthttpmethod",{"2":{"1792":1,"1836":1,"1846":1,"1847":1,"2701":1}}],["defaulted",{"2":{"2422":1}}],["defaulterrorcodepolicy",{"2":{"1669":1,"1670":1,"1673":1,"1678":2,"1792":1,"2255":2}}],["defaultexpiration",{"2":{"279":1,"1792":2,"2274":1,"2279":1}}],["default=",{"2":{"1606":1,"2689":1,"2691":1,"2699":1,"2823":2,"2824":2}}],["defaultjsontype",{"2":{"1416":1,"1417":1,"1553":1,"1559":1,"1581":1,"1792":1}}],["defaultpolicy",{"2":{"1157":1,"1177":1,"1792":2,"1948":1,"1949":1,"1959":1,"1960":1,"2257":1,"2471":1}}],["defaultuploadmetadatacontextkey",{"2":{"1792":1,"2123":1,"2124":1}}],["defaultuploadmetadataparametername",{"2":{"1356":1,"1792":1,"2123":1,"2124":1,"2132":1}}],["defaultuploadhandler",{"2":{"745":1,"1792":1,"2123":1,"2124":1,"2132":1,"2615":1}}],["defaultuseridclaimtype",{"2":{"20":1,"22":1,"305":2,"310":1,"641":1,"1062":1,"1469":1,"1474":1,"1483":1,"1792":1,"2181":2,"2187":1,"2314":1}}],["defaulttimeout",{"2":{"430":1,"455":1,"1340":2,"1792":1,"1916":1,"1917":1,"1931":1,"2308":1,"2549":1,"2814":2}}],["defaulting",{"2":{"320":1,"1571":1,"2482":1,"2484":1}}],["defaultkeylifetimedays",{"2":{"188":1,"1054":1,"1650":1,"1651":1,"1663":2,"1792":1,"2296":1,"2297":2}}],["default",{"0":{"46":1,"80":1,"134":1,"169":1,"180":1,"245":1,"330":1,"377":1,"417":1,"441":1,"462":1,"468":1,"470":1,"511":1,"518":1,"556":1,"572":1,"609":1,"655":1,"662":1,"699":1,"738":1,"798":1,"802":1,"816":1,"1132":1,"1133":1,"1474":1,"1476":1,"1478":1,"1541":1,"1545":1,"1567":1,"1591":1,"1624":1,"1653":1,"1660":1,"1674":1,"1792":1,"1847":1,"1943":1,"1989":1,"2041":1,"2054":1,"2142":1,"2197":1,"2333":1,"2335":2,"2342":1,"2351":1,"2353":1,"2471":1,"2551":1,"2577":1,"2682":1,"2684":1,"2694":1,"2848":1},"1":{"378":1,"379":1,"380":1,"381":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1},"2":{"31":1,"33":1,"46":1,"52":1,"76":1,"80":1,"81":1,"88":1,"89":1,"101":1,"106":1,"108":1,"133":1,"134":1,"141":1,"143":1,"164":1,"166":1,"175":1,"177":1,"180":1,"182":1,"188":1,"196":1,"244":2,"256":1,"291":1,"299":1,"300":1,"301":1,"305":1,"307":1,"308":1,"309":1,"318":1,"320":2,"330":1,"334":2,"336":1,"337":1,"347":1,"348":3,"352":2,"355":2,"363":1,"370":5,"377":3,"378":12,"380":3,"390":1,"408":5,"409":1,"418":1,"419":1,"436":3,"439":6,"442":1,"443":1,"448":1,"449":3,"452":3,"454":2,"460":1,"462":1,"469":1,"470":3,"471":1,"503":1,"504":1,"505":1,"510":1,"511":2,"512":1,"513":1,"523":1,"525":1,"551":1,"556":3,"557":1,"559":1,"565":2,"567":2,"572":1,"574":1,"577":2,"609":1,"616":1,"624":2,"629":1,"646":1,"653":2,"655":1,"658":1,"659":1,"662":2,"675":1,"684":1,"693":1,"698":1,"700":1,"701":1,"704":1,"737":1,"745":2,"746":1,"747":1,"749":1,"762":1,"765":1,"768":1,"776":1,"801":3,"816":1,"841":3,"859":1,"868":1,"869":1,"891":1,"892":1,"905":1,"913":1,"919":3,"932":1,"937":1,"946":1,"963":1,"966":1,"967":1,"977":2,"992":1,"1033":1,"1038":2,"1040":2,"1046":1,"1049":2,"1054":1,"1060":1,"1067":5,"1071":1,"1092":1,"1096":1,"1097":2,"1102":2,"1105":3,"1129":2,"1130":1,"1134":1,"1139":1,"1150":5,"1152":1,"1153":2,"1154":2,"1164":1,"1170":1,"1173":1,"1174":2,"1176":3,"1177":5,"1188":1,"1213":5,"1216":1,"1217":2,"1220":2,"1224":3,"1225":1,"1226":1,"1227":1,"1239":1,"1307":1,"1316":1,"1332":5,"1336":3,"1338":5,"1339":6,"1340":1,"1347":2,"1348":1,"1355":1,"1371":4,"1373":1,"1375":1,"1385":1,"1386":2,"1394":2,"1398":2,"1414":1,"1427":1,"1430":1,"1431":1,"1447":7,"1451":2,"1454":3,"1456":1,"1459":1,"1460":1,"1464":1,"1470":1,"1471":1,"1472":1,"1474":1,"1475":1,"1477":1,"1479":1,"1481":1,"1489":2,"1493":1,"1499":1,"1511":4,"1515":1,"1521":3,"1529":3,"1540":1,"1544":1,"1547":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":2,"1559":2,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1567":5,"1569":2,"1570":1,"1571":1,"1574":1,"1576":1,"1577":1,"1579":1,"1587":2,"1588":3,"1589":1,"1591":1,"1597":2,"1598":2,"1599":2,"1604":1,"1606":1,"1607":2,"1609":1,"1613":1,"1614":1,"1615":2,"1617":1,"1618":4,"1620":1,"1621":1,"1623":1,"1624":1,"1627":1,"1628":2,"1629":2,"1631":1,"1633":1,"1639":2,"1644":2,"1650":1,"1651":5,"1653":3,"1656":1,"1657":1,"1662":1,"1664":1,"1669":2,"1670":4,"1671":1,"1673":2,"1676":1,"1678":3,"1684":2,"1685":1,"1691":2,"1692":2,"1693":2,"1694":2,"1695":2,"1696":4,"1703":1,"1722":1,"1753":1,"1755":1,"1759":1,"1764":2,"1771":1,"1785":3,"1792":172,"1802":1,"1803":1,"1804":1,"1805":1,"1807":1,"1808":2,"1809":1,"1813":1,"1816":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":2,"1823":2,"1827":2,"1828":1,"1829":1,"1830":1,"1831":1,"1832":2,"1833":1,"1837":2,"1838":1,"1840":4,"1841":1,"1842":1,"1843":1,"1844":1,"1845":1,"1846":1,"1848":1,"1849":1,"1850":1,"1851":1,"1853":1,"1854":1,"1855":1,"1856":3,"1857":2,"1858":2,"1861":1,"1862":2,"1870":2,"1874":3,"1875":1,"1876":1,"1877":1,"1898":1,"1912":1,"1917":3,"1918":1,"1921":6,"1924":7,"1925":1,"1926":2,"1927":1,"1937":1,"1938":1,"1943":1,"1949":2,"1951":1,"1952":1,"1953":1,"1954":1,"1956":1,"1958":1,"1967":1,"1968":1,"1969":1,"1972":2,"1973":1,"1974":1,"1980":1,"1989":2,"1991":1,"1994":1,"2000":2,"2001":1,"2004":2,"2005":1,"2006":1,"2007":2,"2008":2,"2009":2,"2010":1,"2011":1,"2016":1,"2018":1,"2020":2,"2023":1,"2024":1,"2028":1,"2029":1,"2034":1,"2038":3,"2039":1,"2040":2,"2041":1,"2047":2,"2063":1,"2074":1,"2075":1,"2077":3,"2086":1,"2088":1,"2094":1,"2098":1,"2106":1,"2107":1,"2108":1,"2109":1,"2111":2,"2117":2,"2124":2,"2125":1,"2126":1,"2127":1,"2128":1,"2129":1,"2130":1,"2131":1,"2139":2,"2141":2,"2142":1,"2149":1,"2154":1,"2155":1,"2161":1,"2175":2,"2177":1,"2181":1,"2183":2,"2184":1,"2186":1,"2195":1,"2196":1,"2209":1,"2212":1,"2219":1,"2221":2,"2222":2,"2223":2,"2224":1,"2242":1,"2247":1,"2251":1,"2252":2,"2253":1,"2254":2,"2255":11,"2256":6,"2257":3,"2258":2,"2259":2,"2264":6,"2265":4,"2266":4,"2267":5,"2273":2,"2274":2,"2277":1,"2283":1,"2291":1,"2296":1,"2297":5,"2306":1,"2320":2,"2323":1,"2325":1,"2327":1,"2328":1,"2329":1,"2330":2,"2333":23,"2335":5,"2339":1,"2342":1,"2350":1,"2353":2,"2354":1,"2359":2,"2364":1,"2367":1,"2375":2,"2376":1,"2377":1,"2379":1,"2380":4,"2382":1,"2384":2,"2389":2,"2392":1,"2406":1,"2409":1,"2414":1,"2415":1,"2419":1,"2421":2,"2422":2,"2423":1,"2426":1,"2427":1,"2428":2,"2431":1,"2432":2,"2435":3,"2436":4,"2450":1,"2451":1,"2452":1,"2455":1,"2461":3,"2463":1,"2465":1,"2466":1,"2470":1,"2476":3,"2481":4,"2482":3,"2483":1,"2484":2,"2486":2,"2497":2,"2502":3,"2511":1,"2517":1,"2520":1,"2527":1,"2529":2,"2530":4,"2532":3,"2534":1,"2537":2,"2539":2,"2540":2,"2541":1,"2542":1,"2544":1,"2545":2,"2549":16,"2551":1,"2554":12,"2558":2,"2565":1,"2572":1,"2575":4,"2577":1,"2586":1,"2587":2,"2595":2,"2596":2,"2607":1,"2626":1,"2629":2,"2632":5,"2633":1,"2635":3,"2638":1,"2641":2,"2652":2,"2659":1,"2665":5,"2666":1,"2677":1,"2682":1,"2684":2,"2686":1,"2687":2,"2688":2,"2690":2,"2694":1,"2700":1,"2702":1,"2718":1,"2719":2,"2721":1,"2722":3,"2723":6,"2724":1,"2725":2,"2729":1,"2732":5,"2733":1,"2757":1,"2762":1,"2764":1,"2769":1,"2779":2,"2788":1,"2802":2,"2804":1,"2808":1,"2810":4,"2812":4,"2814":2,"2815":4,"2822":1,"2823":1,"2824":1,"2825":4,"2829":1,"2830":1,"2831":2,"2832":4,"2833":3,"2835":2,"2836":1,"2840":1,"2841":2,"2842":1,"2845":2,"2848":4,"2849":1,"2852":1,"2862":1,"2865":1,"2866":1,"2868":1,"2871":2,"2878":1,"2879":1,"2881":1}}],["defaultnameclaimtype",{"2":{"19":1,"22":1,"305":2,"310":1,"641":1,"1062":1,"1469":1,"1474":1,"1483":1,"1792":1,"2181":2,"2187":1,"2314":1}}],["defaultsseeventnoticelevel",{"2":{"2251":1}}],["defaultserversenteventseventnoticelevel",{"2":{"655":1,"1792":1,"1836":1,"1857":1,"1858":1,"1859":1,"2251":1,"2701":1,"2832":1,"2835":2}}],["defaultstrategy",{"2":{"577":1,"1153":1,"1154":1,"1177":1,"1587":1,"1588":1,"1597":1,"1598":1,"1792":1}}],["defaults",{"0":{"275":1,"2486":1},"2":{"1":1,"101":1,"105":1,"134":1,"169":1,"237":1,"268":1,"309":2,"377":1,"380":1,"381":2,"448":1,"449":1,"653":1,"675":2,"801":1,"803":1,"868":2,"964":1,"1135":1,"1170":1,"1547":1,"1604":1,"1605":2,"1609":1,"1644":1,"1690":2,"1778":1,"1792":9,"1840":2,"1908":1,"1922":1,"1971":1,"2086":4,"2095":1,"2117":1,"2181":1,"2223":2,"2224":1,"2261":1,"2333":4,"2351":1,"2353":1,"2377":1,"2380":1,"2410":1,"2411":1,"2419":2,"2425":1,"2430":1,"2435":1,"2442":1,"2445":1,"2470":1,"2481":1,"2482":2,"2486":5,"2497":1,"2498":1,"2536":1,"2540":1,"2546":1,"2551":3,"2577":2,"2659":1,"2669":1,"2670":1,"2682":1,"2688":1,"2702":1,"2721":1,"2723":1,"2769":1,"2795":1,"2805":1,"2835":1,"2845":1,"2848":1,"2859":1,"2861":1}}],["detaching",{"2":{"2466":1}}],["detail",{"2":{"819":1,"841":1,"847":1,"852":1,"865":1,"894":1,"938":2,"982":1,"995":5,"1111":1,"1185":1,"1342":1,"1366":1,"1386":1,"1403":6,"1408":1,"1414":1,"1553":1,"1558":1,"1567":2,"1792":1,"1802":1,"2158":1,"2177":1,"2255":1,"2271":1,"2273":2,"2359":2,"2795":1,"2880":1}}],["detailedreport",{"0":{"2104":1},"2":{"1792":1,"2093":1,"2094":1,"2535":1,"2537":1,"2800":1,"2802":1,"2880":1}}],["detailed",{"2":{"646":1,"1097":1,"1223":1,"1254":1,"1260":1,"1281":1,"1284":1,"1472":1,"1482":1,"1620":1,"1792":2,"1801":1,"2364":1,"2366":1,"2537":1,"2634":1,"2713":1}}],["details",{"2":{"35":1,"140":1,"199":1,"256":4,"334":1,"841":1,"843":1,"847":1,"849":1,"851":1,"903":1,"916":6,"917":3,"933":1,"948":1,"1032":1,"1033":1,"1054":1,"1055":1,"1058":1,"1060":1,"1073":1,"1084":1,"1102":1,"1109":1,"1111":8,"1193":2,"1218":1,"1386":3,"1396":1,"1401":2,"1403":1,"1620":1,"1628":1,"1669":5,"1671":2,"1672":1,"1673":4,"1678":7,"1686":1,"1743":1,"1792":8,"1801":1,"2217":1,"2240":1,"2253":1,"2255":9,"2266":1,"2271":1,"2277":4,"2364":1,"2476":1,"2625":1,"2774":1,"2870":1}}],["detected",{"2":{"1225":2,"1470":1,"1592":1,"1627":1,"1684":1,"1792":3,"1875":1,"2106":2,"2156":2,"2346":1,"2372":1,"2542":2,"2543":2,"2869":1,"2878":1}}],["detection",{"0":{"2843":1},"2":{"1218":1,"1472":1,"2266":1,"2270":3,"2319":2,"2417":1,"2438":1,"2447":1,"2492":1,"2546":1,"2607":1,"2648":1,"2673":1,"2878":1}}],["detects",{"2":{"997":1,"1174":1,"1337":1,"1792":1,"2157":1,"2543":1}}],["detect",{"2":{"854":1,"865":1,"1227":1,"1877":1,"2401":1}}],["determine",{"0":{"2734":1},"2":{"1403":1,"1762":1,"1792":2,"2336":1,"2634":1}}],["determines",{"2":{"872":1,"1589":1,"1658":1,"1792":4,"1858":1,"1888":1}}],["determined",{"2":{"52":1,"140":1}}],["determining",{"2":{"782":1,"784":1,"786":1,"2597":1}}],["deterministic",{"2":{"695":2,"2167":1,"2424":1,"2452":1,"2456":2,"2533":1,"2545":1,"2546":1,"2873":2}}],["d",{"2":{"1":1,"269":1,"273":1,"817":1,"847":1,"897":3,"1005":1,"1064":1,"1080":1,"1086":1,"1088":1,"1119":1,"1206":1,"1416":1,"1418":1,"1419":1,"1431":1,"1442":1,"1443":1,"1559":2,"1570":3,"1571":2,"1577":2,"1581":1,"1704":1,"1792":4,"2077":1,"2111":1,"2144":1,"2146":1,"2148":1,"2211":1,"2438":1,"2484":2,"2532":2,"2575":1,"2588":2,"2874":1,"2875":1}}],["dictionaries",{"2":{"1605":1,"2497":1,"2688":1}}],["dictionary",{"2":{"480":1,"860":1,"1499":1,"1792":4,"1974":1,"2148":1,"2255":3,"2256":2,"2266":3,"2411":1,"2440":1,"2442":1,"2444":1,"2446":1,"2483":1,"2504":1,"2575":1,"2607":1,"2614":2}}],["dict",{"0":{"2378":1},"2":{"1458":1,"2375":1,"2411":1,"2413":1}}],["died",{"2":{"1382":1}}],["dies",{"2":{"852":1}}],["ditch",{"2":{"1130":1}}],["dimensionality",{"2":{"2590":1}}],["dimensional",{"2":{"2588":1,"2590":2}}],["dimension",{"2":{"871":1,"1270":1}}],["dimensions",{"0":{"870":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1}}],["di",{"2":{"867":1,"868":2,"869":2,"873":1,"1076":1}}],["dir",{"2":{"2371":4}}],["dirty",{"2":{"849":1}}],["directories",{"2":{"2668":1,"2841":1}}],["directory",{"2":{"757":2,"784":1,"848":1,"1074":1,"1212":1,"1278":1,"1358":1,"1386":2,"1654":1,"1792":1,"2002":2,"2003":1,"2034":1,"2036":2,"2117":1,"2127":2,"2162":1,"2297":1,"2317":1,"2318":1,"2371":2,"2537":1,"2684":2,"2702":1,"2774":1,"2821":1,"2825":2}}],["direction",{"2":{"414":1,"833":2,"861":2,"872":1,"1088":3,"1220":2,"1221":2,"1222":2,"1402":1}}],["directives",{"0":{"211":1,"1731":1,"2505":1},"2":{"214":1,"546":1,"1138":1,"2020":2,"2222":1,"2264":1,"2287":1,"2502":3,"2505":1,"2529":2,"2762":1,"2765":1,"2865":1}}],["directive",{"0":{"2502":1},"2":{"203":6,"213":1,"214":2,"239":2,"689":1,"690":1,"698":1,"699":1,"702":1,"1017":1,"1032":1,"1138":1,"1430":1,"1493":1,"1722":1,"1739":1,"1743":2,"1792":2,"2109":1,"2222":1,"2287":1,"2502":2,"2505":2,"2506":1,"2759":1,"2765":1,"2769":1,"2771":1}}],["direct",{"0":{"1568":1},"2":{"261":1,"831":1,"851":1,"941":1,"993":1,"1007":1,"1009":1,"1107":1,"1111":1,"1117":1,"1125":1,"1126":2,"1176":1,"1185":2,"1207":1,"1217":1,"1230":1,"1276":1,"1351":1,"1354":1,"1385":4,"1747":1,"1792":2,"1880":1,"2054":1,"2372":1,"2622":1,"2635":2,"2645":1}}],["directly",{"0":{"2729":1},"2":{"173":1,"253":1,"404":1,"415":1,"421":1,"424":1,"435":1,"436":1,"438":1,"445":1,"448":1,"454":1,"679":1,"683":1,"691":1,"832":1,"834":1,"843":1,"860":1,"866":1,"871":1,"874":1,"875":1,"918":1,"922":1,"932":1,"934":1,"940":1,"949":1,"952":1,"974":1,"988":1,"1012":1,"1014":1,"1037":4,"1038":1,"1052":1,"1086":1,"1094":1,"1096":1,"1098":1,"1099":1,"1103":1,"1105":2,"1113":1,"1122":1,"1138":1,"1174":1,"1176":1,"1183":1,"1184":1,"1185":2,"1193":1,"1202":1,"1203":1,"1208":1,"1254":1,"1331":1,"1337":1,"1363":1,"1373":1,"1378":1,"1393":1,"1396":1,"1397":1,"1398":1,"1399":3,"1409":1,"1412":2,"1419":1,"1435":1,"1502":1,"1528":1,"1746":1,"1757":1,"1792":2,"1822":1,"1824":1,"1915":1,"1920":1,"1927":1,"1929":1,"1961":1,"2040":1,"2165":1,"2190":1,"2228":1,"2277":1,"2287":1,"2303":1,"2307":1,"2309":1,"2317":1,"2347":1,"2358":1,"2389":1,"2399":1,"2417":1,"2435":1,"2451":2,"2465":1,"2487":1,"2549":3,"2586":1,"2614":1,"2635":1,"2678":1,"2689":1,"2695":1,"2807":1,"2822":1,"2830":1,"2839":1,"2876":1}}],["directs",{"2":{"73":1}}],["differs",{"2":{"1135":1,"1460":1,"2375":1,"2854":1}}],["differ",{"2":{"1083":1,"1102":1,"1382":1,"2811":1}}],["differences",{"0":{"803":1,"893":1},"2":{"848":2,"884":1,"1049":1,"1097":1,"1279":1,"1387":1,"1396":1,"1792":1,"2554":1}}],["difference",{"2":{"414":1,"587":1,"832":1,"833":1,"844":1,"845":1,"893":1,"1041":1,"1088":1,"1090":1,"1098":1,"1101":2,"1102":1,"1108":1,"1233":1,"1238":1,"1272":1,"1385":3,"2538":1}}],["differentiate",{"2":{"1139":1,"2265":1}}],["differently",{"2":{"683":1,"841":1,"1386":1,"2498":1}}],["different",{"0":{"831":1,"1431":1,"1771":1,"2063":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1},"2":{"1":1,"51":2,"150":2,"168":1,"239":1,"356":1,"387":1,"395":2,"436":2,"451":1,"646":2,"650":1,"666":2,"669":1,"720":1,"724":1,"831":1,"836":1,"838":1,"841":6,"843":2,"844":1,"847":3,"848":9,"849":1,"852":1,"857":1,"859":1,"860":1,"866":1,"872":1,"874":1,"876":1,"879":1,"920":1,"930":1,"967":1,"990":1,"1067":1,"1094":1,"1096":1,"1098":2,"1101":1,"1104":1,"1105":2,"1111":1,"1121":1,"1127":1,"1128":2,"1129":1,"1135":1,"1137":1,"1139":1,"1144":1,"1154":2,"1163":2,"1175":1,"1193":1,"1240":1,"1254":1,"1281":1,"1345":1,"1374":1,"1396":1,"1398":1,"1402":1,"1403":1,"1411":1,"1414":1,"1458":1,"1519":4,"1525":1,"1527":1,"1597":1,"1614":2,"1632":1,"1637":1,"1651":1,"1658":1,"1732":1,"1771":1,"1792":9,"1830":1,"1837":1,"1889":1,"1911":1,"1968":1,"2063":1,"2247":1,"2265":1,"2273":1,"2297":1,"2346":1,"2347":1,"2375":1,"2380":2,"2389":2,"2407":1,"2419":1,"2421":1,"2429":1,"2452":1,"2504":1,"2534":1,"2597":1,"2635":1,"2666":1,"2679":1,"2680":3,"2765":1,"2856":1}}],["difficult",{"2":{"994":1,"1005":1,"1014":1}}],["difficulty",{"2":{"841":1}}],["difficulties",{"2":{"841":6}}],["diverged",{"2":{"2450":1}}],["dividing",{"2":{"1952":1}}],["divided",{"2":{"1159":1}}],["division",{"2":{"1":1,"1211":1}}],["div>",{"2":{"938":2,"1061":3}}],["div",{"2":{"938":2,"1061":3,"1429":1}}],["divorce",{"2":{"840":1}}],["didn",{"2":{"852":1,"856":1,"872":1,"874":1,"1382":1,"1391":1,"1401":1,"1402":3,"1437":1,"2394":1}}],["did",{"2":{"625":1,"840":2,"852":2,"859":1,"866":1,"920":1,"1031":1,"1080":1,"1382":1,"1385":2,"1527":1,"2389":1,"2414":1,"2551":1}}],["digging",{"2":{"2401":1}}],["dig",{"2":{"840":1,"2530":1}}],["digitalocean",{"2":{"1086":1,"1094":1,"1121":1}}],["digit",{"2":{"382":1,"1792":1,"2098":1,"2334":1,"2397":1,"2534":1,"2871":1}}],["digits",{"2":{"382":1,"2334":1}}],["digest",{"2":{"308":6,"2177":4}}],["dialect",{"2":{"848":1}}],["dialogs",{"2":{"51":1}}],["dialog",{"2":{"45":1,"1695":1,"1792":1}}],["diagnose",{"2":{"1014":1,"2428":1}}],["diagnostics",{"0":{"2492":1},"2":{"2407":1,"2481":1,"2535":1,"2800":1,"2805":1}}],["diagnostic",{"2":{"354":2,"868":1,"1792":1,"2104":1}}],["diagram",{"2":{"845":1,"1403":1}}],["disks",{"2":{"848":2}}],["disk",{"2":{"847":2,"848":7,"849":2,"851":1,"919":1,"1095":1,"1155":1,"1324":1,"1353":1,"1378":1,"1403":1,"1405":1,"1406":2,"1407":1,"1420":1,"1422":1,"1594":1,"1792":1}}],["dispatches",{"2":{"2438":1}}],["dispatcher",{"2":{"2421":3}}],["dispatched",{"2":{"1852":1,"2383":1,"2811":1}}],["dispatch",{"2":{"2225":1,"2372":1,"2422":2,"2435":1,"2621":3}}],["dispute",{"2":{"864":1}}],["disposes",{"2":{"855":1}}],["disposed",{"2":{"714":1,"716":1,"2404":1,"2466":1}}],["disposable",{"2":{"845":1}}],["disposition",{"2":{"386":2,"392":1,"492":1,"493":2,"543":1,"544":2,"546":1,"1189":2,"1373":2}}],["displaying",{"0":{"1364":1}}],["displayname",{"2":{"1214":1,"1218":1,"1220":2,"1221":1}}],["displays",{"2":{"963":1,"1068":1,"2577":1}}],["display",{"2":{"305":1,"428":1,"996":2,"1024":2,"1060":1,"1214":1,"1232":3,"1240":1,"1318":1,"1687":1,"1792":7,"1802":1,"1882":2,"1889":1,"2181":1,"2567":1,"2663":1}}],["displayed",{"2":{"45":1,"51":1,"1792":2,"2117":1,"2702":1,"2795":1}}],["disconnected`",{"2":{"1320":1}}],["disconnecteventsource",{"2":{"1318":1}}],["disconnection",{"2":{"1320":1}}],["disconnect",{"2":{"1318":1,"1320":2,"2498":1,"2615":2}}],["discouraged",{"2":{"1228":1,"1229":1,"1792":4,"1878":1,"1879":1}}],["discount",{"2":{"1193":2}}],["discoverable",{"2":{"1218":1,"1222":1,"1229":2,"1234":2,"1792":8,"1832":1,"1833":1,"1879":2,"1884":1,"2481":1}}],["discovery",{"0":{"2539":1,"2799":1},"2":{"1080":1,"1408":1,"1789":1,"1792":7,"1802":2,"1833":2,"2094":2,"2095":1,"2106":1,"2108":1,"2112":1,"2156":3,"2159":1,"2221":1,"2532":1,"2536":1,"2537":1,"2539":1,"2542":2,"2546":1,"2608":1,"2710":1,"2722":1,"2751":1,"2795":2,"2799":1,"2861":1,"2871":1,"2878":1,"2880":1}}],["discovers",{"2":{"927":1,"1792":1,"2526":1,"2672":1}}],["discovered",{"2":{"865":1,"1792":4,"2094":2,"2096":1,"2105":1,"2156":1,"2537":1,"2542":1,"2721":2,"2751":1,"2872":1,"2878":1}}],["discover",{"2":{"317":1,"932":1,"973":1,"983":1,"1001":1,"1037":1,"1038":1,"1045":1,"1076":1,"1813":1,"1827":1,"2166":1,"2479":1,"2481":2,"2608":1,"2693":1}}],["discard",{"2":{"1070":1,"1851":1,"2342":1,"2382":1}}],["discarded",{"2":{"299":1,"309":1,"1070":1,"1851":1,"2382":1,"2395":1,"2510":1,"2867":1}}],["disciplined",{"2":{"869":1}}],["discipline",{"2":{"852":1,"863":2,"864":2,"865":1}}],["discussed",{"2":{"871":1}}],["discussion",{"2":{"851":2,"948":1}}],["discuss",{"2":{"841":1}}],["dist",{"2":{"1420":2,"2792":3}}],["disturbing",{"2":{"913":1}}],["distinguished",{"2":{"2347":1,"2421":1}}],["distinguishes",{"2":{"369":1,"1792":1}}],["distinction",{"2":{"1417":1,"2193":1,"2581":1,"2607":1}}],["distinct",{"2":{"214":1,"693":1,"708":1,"849":2,"852":1,"868":1,"916":1,"1098":1,"1219":1,"1460":1,"1519":1,"1722":1,"1743":1,"1792":2,"1869":1,"2040":1,"2098":1,"2222":1,"2265":1,"2375":1,"2380":1,"2464":1,"2465":1,"2477":1,"2504":4,"2506":1,"2534":1,"2535":1,"2662":1,"2769":1,"2871":1}}],["distributes",{"2":{"1180":2}}],["distributed",{"2":{"121":1,"848":1,"1013":1,"1110":1,"1150":1,"1151":1,"1180":1,"1511":1,"1514":1,"1515":3,"1529":1,"1792":2,"2274":3}}],["distributing",{"2":{"1175":2}}],["distribution",{"0":{"1013":1,"2776":1},"2":{"233":1,"635":1,"671":1,"857":1,"860":1,"1107":1,"1303":1,"1304":1,"1860":1,"2157":1,"2543":1}}],["disallowed",{"2":{"1458":1,"2375":1}}],["disadvantages",{"2":{"1386":1,"1396":1}}],["disaster",{"2":{"922":1,"1402":1}}],["disappears",{"2":{"180":1}}],["disabling",{"2":{"87":1,"174":1,"1527":1,"2380":1}}],["disablestringreuse",{"2":{"1609":1,"1792":1,"1994":2,"2661":1}}],["disables",{"2":{"1479":2,"1489":1,"1792":7,"1816":1,"1917":1,"2000":1,"2003":1,"2021":1,"2094":2,"2095":1,"2101":1,"2154":1,"2156":1,"2463":1,"2537":2,"2542":1,"2632":1,"2814":1}}],["disable",{"0":{"83":1,"180":1,"722":1,"1412":1},"2":{"81":1,"175":1,"223":1,"334":1,"720":6,"1224":1,"1226":1,"1241":1,"1511":1,"1616":1,"1671":1,"1764":1,"1769":1,"1792":24,"1848":1,"1859":1,"1874":1,"1876":1,"1890":1,"1925":1,"1974":1,"1994":1,"2000":1,"2001":1,"2041":1,"2047":1,"2060":1,"2125":1,"2254":1,"2255":1,"2265":1,"2330":1,"2342":1,"2350":1,"2517":1,"2539":1,"2572":1,"2607":1,"2634":2,"2635":2,"2699":1,"2824":2}}],["disabled",{"0":{"171":1,"2351":1,"2353":1},"1":{"172":1,"173":1,"174":1,"175":1,"176":1},"2":{"64":1,"172":2,"173":1,"174":1,"175":3,"177":3,"180":2,"181":1,"223":1,"688":1,"704":1,"720":1,"1220":1,"1254":1,"1386":1,"1417":1,"1420":1,"1521":1,"1639":1,"1792":6,"1813":1,"1870":1,"1959":1,"2111":2,"2156":1,"2224":1,"2330":1,"2352":2,"2353":1,"2380":2,"2471":1,"2481":1,"2527":1,"2532":2,"2535":1,"2537":1,"2545":1,"2581":1,"2586":2,"2600":2,"2611":1,"2687":1,"2721":1,"2824":1,"2825":1,"2862":1,"2871":1}}],["disagreed",{"2":{"2486":1}}],["disagreement",{"2":{"1403":1}}],["disagree",{"2":{"1":1,"74":1,"875":1}}],["dorny",{"2":{"2880":1}}],["dollar",{"2":{"2528":1,"2540":1,"2546":1,"2845":1,"2863":1}}],["dog",{"2":{"1400":1}}],["dotnet",{"2":{"1046":1,"1076":1,"1207":1,"1792":4,"2157":3,"2450":1,"2456":1,"2481":1,"2543":4,"2792":4,"2874":1}}],["domcontentloadedeventend",{"2":{"1792":2}}],["domcomplete",{"2":{"1792":2}}],["dominteractive",{"2":{"1792":2}}],["dominate",{"2":{"2398":1}}],["dominates",{"0":{"1262":1},"2":{"873":1,"1091":1,"1268":1}}],["dominance",{"0":{"1275":1}}],["dominant",{"2":{"873":1}}],["dom",{"2":{"1432":1}}],["domains",{"2":{"852":1}}],["domaindrivendesign",{"2":{"845":1}}],["domain",{"2":{"840":1,"841":3,"843":4,"845":5,"851":13,"852":3,"855":1,"859":1,"860":1,"863":4,"864":2,"865":3,"1225":1,"1435":1,"1447":1,"1448":1,"1704":1,"1792":5,"1875":1,"2419":1}}],["dozens",{"2":{"1406":1}}],["dozen",{"2":{"868":1,"992":1,"1059":1,"1414":1}}],["doubt",{"2":{"843":1}}],["doubled",{"2":{"2603":1}}],["double",{"2":{"585":1,"865":1,"956":1,"1606":1,"1608":1,"1792":1,"2272":1,"2309":1,"2412":1,"2438":3,"2603":2,"2689":1}}],["doing",{"0":{"868":1},"2":{"841":2,"849":1,"852":1,"861":1,"1366":1,"1378":1,"1382":1,"1385":2,"1399":1,"1401":1,"1403":5,"1405":1,"1440":1,"2812":1}}],["downgraded",{"2":{"2363":1}}],["downgrades",{"2":{"2019":2}}],["downgrade",{"0":{"2363":1},"2":{"1792":1,"2019":1,"2632":1}}],["downtime",{"0":{"1438":1},"2":{"1438":2,"1439":1,"1440":1,"1441":1,"1442":1}}],["down",{"0":{"1045":1},"2":{"947":1,"948":1,"967":1,"974":1,"1037":1,"1135":1,"1352":1,"1384":1,"1385":1,"1792":1,"1958":1,"2362":1,"2470":1,"2532":1,"2543":1,"2742":1,"2881":1}}],["downstream",{"2":{"690":1,"1421":1,"2452":1,"2455":1}}],["downloading",{"2":{"1792":1,"2576":1}}],["downloadable",{"2":{"1099":1}}],["downloads",{"2":{"673":1,"720":1,"723":1,"957":1,"964":1,"1200":1,"1207":1,"1412":1,"1413":1,"2072":1,"2162":1,"2650":1,"2656":1,"2779":1,"2786":1}}],["download",{"0":{"392":1,"492":1,"493":1,"678":1,"2778":1,"2780":1},"1":{"2779":1,"2780":1,"2781":2,"2782":2,"2783":2,"2784":2,"2785":1},"2":{"492":3,"546":1,"675":2,"679":1,"902":1,"947":2,"948":1,"949":2,"957":1,"959":2,"960":1,"961":3,"968":1,"970":1,"1086":1,"1117":2,"1118":2,"1189":1,"1373":1,"1413":1,"1792":4,"2017":1,"2077":1,"2078":1,"2079":1,"2615":1,"2652":1,"2653":1,"2779":1,"2781":2,"2782":2,"2783":2,"2784":2,"2792":1}}],["do",{"0":{"621":1,"1395":1,"1396":1,"2716":1,"2717":1,"2718":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2732":1,"2733":1,"2737":1,"2739":1,"2745":1,"2746":1,"2747":1,"2749":1,"2750":1,"2751":1,"2752":1,"2855":1},"2":{"165":1,"184":2,"319":1,"387":1,"414":1,"452":1,"583":1,"584":1,"621":2,"624":3,"625":2,"641":1,"650":1,"658":1,"659":2,"664":1,"665":2,"826":2,"831":1,"834":1,"837":1,"838":2,"844":3,"845":1,"848":1,"849":1,"851":5,"852":2,"859":1,"864":1,"875":1,"876":1,"898":1,"912":1,"916":1,"919":2,"920":1,"932":1,"975":1,"979":1,"980":1,"988":1,"989":1,"990":3,"991":1,"992":1,"994":2,"1005":1,"1039":1,"1054":1,"1073":1,"1074":1,"1075":1,"1076":3,"1077":3,"1082":1,"1104":1,"1130":3,"1133":1,"1139":2,"1185":1,"1309":1,"1339":1,"1372":3,"1376":2,"1378":2,"1382":1,"1384":3,"1385":2,"1386":4,"1388":1,"1391":1,"1393":2,"1394":5,"1395":3,"1396":4,"1400":2,"1401":1,"1402":1,"1403":3,"1404":1,"1405":3,"1416":1,"1417":1,"1419":1,"1423":2,"1431":3,"1435":1,"1437":1,"1438":1,"1441":1,"1442":1,"1570":1,"1574":1,"1581":1,"1655":1,"1664":1,"1689":1,"1716":1,"1792":2,"1941":1,"2110":1,"2167":1,"2193":1,"2221":1,"2292":1,"2300":1,"2319":6,"2337":1,"2338":3,"2342":1,"2343":1,"2372":1,"2393":1,"2415":1,"2431":1,"2468":1,"2528":5,"2530":1,"2535":1,"2546":1,"2581":1,"2586":1,"2755":2,"2788":1,"2795":1,"2802":1,"2810":1,"2815":1,"2829":1,"2834":1,"2841":1,"2843":1,"2851":1,"2854":1,"2855":5,"2858":1,"2863":1,"2864":3,"2875":1,"2881":1}}],["doe",{"2":{"34":1,"128":1,"489":1,"493":1,"643":1,"646":1,"1386":8,"1391":2,"1393":4}}],["doesn",{"0":{"1210":1,"1432":1,"2721":1,"2722":1},"2":{"23":1,"168":2,"175":1,"303":2,"376":1,"389":1,"454":1,"480":1,"524":1,"528":1,"584":1,"684":1,"737":1,"784":1,"845":1,"847":2,"848":4,"869":1,"871":2,"873":2,"876":2,"918":1,"933":1,"947":1,"953":1,"961":1,"984":1,"1067":1,"1075":1,"1076":2,"1079":1,"1134":1,"1139":1,"1171":1,"1238":1,"1326":1,"1351":1,"1358":1,"1376":1,"1385":1,"1386":5,"1394":2,"1396":2,"1404":1,"1409":1,"1414":1,"1415":1,"1422":1,"1435":1,"1543":1,"1792":4,"1832":1,"2127":1,"2156":1,"2207":1,"2297":1,"2337":1,"2389":1,"2434":1,"2438":1,"2483":1,"2835":2}}],["does",{"0":{"666":1,"2713":1},"2":{"1":1,"215":1,"436":1,"453":2,"618":1,"621":1,"650":1,"665":1,"668":1,"669":1,"762":1,"834":2,"841":2,"848":2,"849":1,"851":3,"852":2,"854":1,"857":1,"861":2,"864":2,"872":1,"873":1,"876":1,"877":1,"893":1,"918":1,"919":1,"920":1,"932":1,"979":1,"980":1,"985":1,"986":1,"988":1,"990":1,"996":2,"1017":1,"1038":1,"1045":1,"1076":1,"1077":2,"1096":1,"1098":1,"1105":1,"1111":1,"1125":1,"1211":1,"1382":1,"1386":1,"1396":1,"1405":1,"1406":1,"1409":1,"1410":1,"1412":1,"1419":1,"1428":1,"1431":1,"1435":1,"1438":1,"1441":1,"1523":1,"1528":1,"1593":1,"1624":1,"1651":1,"1653":1,"1655":1,"1678":1,"1768":1,"1792":4,"1822":1,"1825":2,"1961":2,"2040":1,"2098":1,"2171":1,"2175":1,"2328":1,"2380":1,"2391":1,"2437":1,"2451":1,"2453":1,"2463":1,"2466":1,"2481":2,"2496":1,"2511":2,"2528":1,"2531":1,"2539":1,"2542":3,"2634":1,"2677":1,"2760":1,"2765":1,"2809":1,"2830":1,"2832":1,"2840":1,"2864":1}}],["donottrack",{"2":{"1792":2}}],["dont",{"2":{"920":1}}],["done",{"0":{"947":1,"1435":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1},"2":{"614":2,"861":1,"871":1,"872":2,"877":1,"913":1,"974":1,"1037":3,"1044":2,"1065":1,"1132":1,"1254":1,"1390":1,"1399":1,"1419":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"2164":1,"2339":2,"2731":1,"2875":1}}],["don",{"0":{"910":1},"1":{"911":1},"2":{"33":1,"101":1,"175":1,"177":1,"307":1,"308":2,"319":1,"390":1,"499":1,"583":1,"587":1,"639":1,"691":1,"843":2,"844":1,"847":1,"849":2,"851":1,"852":2,"859":2,"869":1,"873":1,"876":1,"884":1,"896":1,"901":1,"904":1,"911":1,"918":1,"960":1,"961":1,"975":1,"1006":1,"1036":1,"1040":1,"1050":1,"1065":1,"1067":1,"1068":1,"1073":1,"1074":1,"1076":1,"1078":1,"1079":2,"1081":1,"1145":1,"1150":1,"1166":1,"1170":1,"1210":1,"1228":1,"1326":1,"1328":1,"1337":1,"1385":2,"1386":2,"1388":2,"1390":1,"1393":1,"1395":1,"1399":1,"1400":2,"1401":3,"1402":4,"1403":4,"1405":1,"1411":1,"1441":2,"1442":1,"1511":1,"1572":1,"1580":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1774":1,"1792":8,"1801":1,"1823":1,"1825":2,"1849":1,"1878":1,"1941":1,"1974":1,"1983":1,"2016":1,"2024":1,"2157":1,"2160":1,"2177":2,"2180":1,"2183":1,"2193":1,"2250":1,"2342":1,"2392":1,"2398":1,"2406":1,"2415":1,"2433":1,"2438":1,"2489":1,"2533":1,"2543":1,"2607":1,"2632":1,"2685":1,"2741":1,"2820":1,"2827":1,"2830":1,"2835":2,"2836":1,"2840":1,"2854":1,"2867":2}}],["doc",{"2":{"1427":3,"1429":1,"1792":2,"2430":2}}],["dockerfiles",{"2":{"2576":1}}],["dockerfiledockerfilefrom",{"2":{"1420":1}}],["docker",{"0":{"1343":1,"1715":1,"1775":1,"2245":1,"2385":1,"2550":1,"2576":1,"2717":1,"2787":1,"2875":1},"1":{"2788":1,"2789":1,"2790":1,"2791":1},"2":{"994":1,"1013":1,"1071":1,"1078":1,"1082":1,"1084":1,"1100":1,"1117":3,"1118":2,"1119":4,"1127":1,"1255":1,"1343":2,"1420":1,"1442":1,"1654":3,"1663":1,"1762":1,"1792":6,"2092":1,"2111":4,"2114":1,"2157":4,"2237":1,"2238":1,"2242":1,"2245":1,"2297":1,"2353":1,"2385":1,"2532":5,"2534":3,"2543":3,"2550":1,"2576":6,"2634":3,"2711":1,"2716":1,"2717":1,"2788":5,"2789":3,"2790":3,"2791":3,"2871":1,"2875":3}}],["doctype",{"2":{"965":1,"1685":1,"1792":1}}],["documenting",{"2":{"1912":1,"2577":1}}],["documentdescription",{"2":{"1792":1,"1897":1,"1898":1,"1899":1,"1907":1,"1911":1,"2254":1,"2434":1}}],["documentversion",{"2":{"1792":1,"1897":1,"1898":1,"1899":1,"1907":1,"2254":1}}],["documenttitle",{"2":{"1792":1,"1897":1,"1898":1,"1899":1,"1907":1,"1911":1,"2254":1,"2434":1}}],["documentation",{"0":{"251":1,"1002":1,"1182":1},"2":{"251":1,"624":1,"869":1,"1006":2,"1009":1,"1055":1,"1060":1,"1111":1,"1123":1,"1155":1,"1182":1,"1197":1,"1386":1,"1400":1,"1404":1,"1472":1,"1670":1,"1676":1,"1686":1,"1694":1,"1695":1,"1718":1,"1757":1,"1758":1,"1783":1,"1785":1,"1789":1,"1792":3,"1796":1,"1898":1,"1951":1,"1952":1,"1953":1,"1954":1,"1984":1,"2192":1,"2194":1,"2255":1,"2267":1,"2411":1,"2459":1,"2496":1,"2505":1,"2546":1,"2586":1,"2677":1,"2693":1,"2705":1}}],["document",{"0":{"1899":1,"1911":1},"2":{"223":1,"347":2,"348":2,"349":1,"354":1,"355":4,"356":1,"851":1,"961":1,"996":6,"1042":1,"1202":1,"1360":1,"1361":1,"1427":1,"1428":1,"1792":15,"1818":1,"1828":1,"1830":1,"1831":1,"1833":2,"1898":1,"1899":1,"1901":1,"1908":1,"1911":4,"1912":2,"2016":2,"2023":1,"2024":1,"2254":5,"2419":1,"2432":2,"2434":3,"2436":2,"2438":3,"2481":1,"2489":1,"2632":3,"2762":2}}],["documents",{"0":{"2430":1},"1":{"2431":1,"2432":1,"2433":1,"2434":1},"2":{"1":1,"267":1,"386":1,"448":1,"1792":1,"2023":1,"2438":1}}],["documented",{"2":{"1":1,"158":1,"856":1,"868":1,"1324":1,"1792":4,"1898":3,"1909":1,"1912":1,"2410":1,"2411":1,"2430":1,"2431":2,"2433":2,"2435":1,"2438":2,"2486":1,"2490":1,"2494":1,"2551":1,"2555":1}}],["docs",{"0":{"1":1,"2489":1},"2":{"1":3,"3":1,"868":1,"878":1,"913":1,"921":1,"947":1,"970":2,"976":1,"1010":1,"1047":1,"1048":1,"1098":1,"1183":1,"1207":2,"1302":1,"1328":1,"1352":1,"1380":2,"1401":1,"1404":1,"1792":6,"2162":2,"2222":1,"2225":1,"2255":4,"2410":1,"2413":1,"2632":2}}],["iuploadhandler",{"2":{"2615":1}}],["iuploadtolargeobjectresponse",{"2":{"1410":1}}],["iuploadtolargeobjectrequest",{"2":{"1410":1}}],["iuploadtofilesystemrequest",{"2":{"1366":1}}],["iuploadtofilesystemresponse",{"2":{"1366":2}}],["iquerycollection",{"2":{"2614":2}}],["ibufferwriter",{"2":{"2400":1}}],["ibooksinfo",{"2":{"920":3}}],["ibooks",{"2":{"920":4,"2590":2,"2611":2}}],["iaddress",{"2":{"2590":2}}],["iactionresult>",{"2":{"1366":1}}],["iaianalyzerequest",{"2":{"1342":2}}],["iauthor",{"2":{"920":7}}],["ioexception",{"2":{"1366":2}}],["io",{"2":{"1320":10,"1322":1,"1366":1,"1423":1,"1457":1,"1595":1,"1792":2,"2267":1,"2554":1}}],["iendpointcreatehandler",{"2":{"2438":1,"2482":2}}],["iendpointsource",{"0":{"2369":1},"2":{"2228":1,"2369":4}}],["ienumerable",{"2":{"852":1}}],["ietf",{"2":{"1111":1,"1676":1,"1677":1}}],["iwhoamirequest",{"2":{"938":1,"1567":3,"1568":1,"1570":1}}],["iwhoamiresponse>",{"2":{"1568":1}}],["iwhoamiresponse",{"2":{"938":2,"1567":4,"1568":1,"1570":1}}],["i+1",{"2":{"929":2}}],["i++",{"2":{"894":1,"1366":1,"1410":1}}],["ijk",{"2":{"915":2}}],["ivy",{"2":{"913":1}}],["icancelcomputerequest",{"2":{"1581":1}}],["icomputevisualizationresponse",{"2":{"1416":2}}],["icomputevisualizationrequest",{"2":{"1416":1,"1572":1}}],["icon",{"2":{"844":1}}],["icreateauthorresponse",{"2":{"920":1}}],["icreateauthorrequest",{"2":{"920":1}}],["icsvuploadrequest",{"2":{"894":1}}],["icsvuploadresponse",{"2":{"894":2}}],["ixmlrepository",{"2":{"868":1}}],["ii",{"2":{"851":1}}],["iloginrequest",{"2":{"938":2}}],["illustrative",{"2":{"2442":1}}],["illustrate",{"2":{"868":1,"920":1}}],["illusion",{"2":{"849":2,"851":7,"852":1}}],["illogical",{"2":{"849":1}}],["ilike",{"2":{"250":1,"520":2,"1038":2,"1096":1}}],["ignoring",{"2":{"2376":1}}],["ignores",{"2":{"927":1,"1792":1}}],["ignored",{"0":{"2505":1},"2":{"109":1,"214":1,"244":1,"251":1,"286":1,"303":1,"324":1,"378":1,"422":1,"446":1,"527":1,"585":1,"704":1,"747":1,"928":1,"1360":1,"1386":1,"1395":1,"1470":1,"1569":1,"1609":1,"1722":1,"1743":1,"1759":1,"1769":1,"1792":10,"1816":1,"1858":1,"1912":1,"1917":1,"2004":1,"2060":1,"2074":1,"2111":1,"2157":1,"2192":1,"2194":1,"2222":1,"2337":1,"2365":1,"2395":1,"2428":1,"2444":1,"2502":2,"2506":1,"2520":1,"2529":2,"2532":1,"2533":1,"2537":1,"2543":1,"2634":1,"2635":1,"2659":1,"2797":1,"2814":1,"2865":1,"2871":1}}],["ignore",{"0":{"462":1,"501":1},"2":{"64":1,"244":1,"348":1,"355":1,"460":1,"462":1,"468":1,"470":1,"499":1,"501":1,"583":1,"1139":1,"1199":1,"1499":1,"1500":1,"1609":1,"1792":18,"1836":1,"1840":2,"1849":1,"1853":1,"1854":1,"2004":1,"2038":1,"2209":2,"2393":1,"2414":1,"2415":1,"2417":1,"2432":1,"2435":1,"2487":1,"2551":1,"2595":1,"2659":1}}],["igetuserwithaddressresponse",{"2":{"2590":1}}],["igetuserresponse",{"2":{"1419":1}}],["igetusersresponse",{"2":{"995":3,"996":2,"1386":4,"1408":2,"1409":1}}],["igetfinancialdashboardresponse",{"2":{"1024":2}}],["igetfinancialdashboardrequest",{"2":{"1024":2}}],["igetpostsresponse",{"2":{"985":1,"995":3,"996":2,"1408":4}}],["igetauthorswithbooksresponse",{"2":{"2590":1}}],["igetauthorswithdetailstypenestedresponse",{"2":{"920":1}}],["igetauthorswithdetailstypenestedrequest",{"2":{"920":1}}],["igetauthorswithdetailstyperesponse",{"2":{"920":1}}],["igetauthorswithdetailstyperequest",{"2":{"920":1}}],["igetauthorswithdetailsnestedresponse",{"2":{"920":1}}],["igetauthorswithdetailsnestedrequest",{"2":{"920":1}}],["igetauthorswithdetailsresponse",{"2":{"920":1}}],["igetauthorswithdetailsrequest",{"2":{"920":1}}],["igetauthorsandbooksandreviewsresponse",{"2":{"920":1}}],["igetauthorsandbooksandreviewsrequest",{"2":{"920":1}}],["igetauthorsandbooksresponse",{"2":{"920":1}}],["igetauthorsandbooksrequest",{"2":{"920":1}}],["igetauthorsresponse",{"2":{"920":1}}],["igetauthorsrequest",{"2":{"920":1}}],["igetauthorinforesponse",{"2":{"920":1}}],["igetauthorinforequest",{"2":{"920":1}}],["igetauthorresponse",{"2":{"920":1}}],["igetauthorrequest",{"2":{"920":1}}],["igetdatarequest",{"2":{"723":2,"961":2,"1413":2}}],["ireviews",{"2":{"2611":3}}],["iresponse",{"2":{"615":2,"2339":2}}],["iroutinesource",{"0":{"2369":1},"2":{"2228":1,"2369":3,"2642":2}}],["iroutinecache",{"0":{"2461":1},"2":{"2224":1,"2459":1,"2461":1}}],["irony",{"2":{"845":1}}],["ir",{"0":{"2531":1},"2":{"699":1,"706":1,"711":2,"1079":1,"1792":1,"2097":1,"2167":1,"2221":1,"2531":4,"2532":1,"2533":2,"2545":1,"2869":3,"2873":1}}],["irrelevant",{"2":{"448":1,"2869":1}}],["imathmodulecancelcomputerequest",{"2":{"1581":1}}],["imaginary",{"2":{"2531":1}}],["imagine",{"2":{"982":1,"1401":1,"1405":1}}],["imageio",{"2":{"1366":1}}],["imageine",{"2":{"918":1}}],["image",{"0":{"1343":1,"1352":1,"2550":1,"2576":1,"2788":1,"2789":1,"2790":1,"2791":1},"1":{"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"414":1,"722":3,"747":2,"753":2,"757":2,"758":1,"768":1,"776":1,"782":3,"783":1,"784":3,"785":1,"791":2,"903":1,"1037":2,"1086":1,"1099":2,"1105":1,"1272":1,"1343":2,"1352":2,"1358":8,"1359":1,"1360":2,"1362":2,"1364":1,"1366":11,"1367":1,"1386":1,"1410":4,"1412":4,"1420":4,"1773":1,"1775":1,"1792":2,"1936":1,"1943":1,"2125":1,"2126":1,"2127":1,"2132":1,"2134":2,"2157":1,"2164":3,"2165":3,"2237":1,"2238":1,"2242":1,"2245":4,"2300":1,"2450":1,"2543":1,"2550":2,"2576":4,"2711":1,"2716":1,"2788":2,"2789":4,"2790":3,"2791":4,"2874":1}}],["images",{"0":{"1362":1,"1364":1,"2385":1},"1":{"1363":1},"2":{"157":2,"722":2,"747":1,"1013":1,"1071":1,"1121":1,"1354":2,"1362":1,"1363":1,"1364":1,"1412":1,"1792":1,"2020":1,"2385":3}}],["img>",{"2":{"1427":1}}],["img",{"2":{"1366":2,"1412":2,"1424":1,"1427":1,"1428":1,"2020":1,"2029":1}}],["imgurl",{"2":{"1364":2}}],["immune",{"2":{"2110":1,"2530":1}}],["immutability",{"2":{"843":2}}],["immutable",{"0":{"180":1,"686":1,"1129":1},"2":{"175":2,"179":1,"180":2,"684":2,"686":1,"687":2,"1037":1,"1067":1,"1128":1,"1129":2,"1362":1,"1363":1,"2381":1,"2858":1}}],["immediate",{"2":{"969":1,"974":1,"986":1,"1153":2,"1325":1,"1393":1,"1590":1,"1792":1,"2615":1,"2633":1}}],["immediately",{"2":{"83":1,"324":1,"705":1,"807":1,"852":1,"952":1,"983":1,"986":1,"992":1,"996":1,"1075":1,"1166":2,"1386":2,"1394":1,"1398":1,"1401":2,"1409":1,"1419":1,"1572":1,"1742":1,"2137":1,"2149":1,"2290":1,"2380":1,"2504":1,"2532":1,"2537":1,"2540":1,"2543":1,"2575":1,"2580":1,"2615":1,"2857":1,"2878":1}}],["impatient",{"2":{"2532":1}}],["impacted",{"2":{"1384":1}}],["impacting",{"2":{"1205":1,"1254":1}}],["impact",{"0":{"2398":1},"2":{"87":2,"258":1,"919":1,"2277":1,"2490":1}}],["improving",{"2":{"2580":1}}],["improves",{"2":{"1511":1,"1792":1,"2265":1}}],["improve",{"2":{"1011":1,"1792":1,"2265":1}}],["improved",{"0":{"2364":1,"2663":1},"2":{"974":1,"1090":1,"1259":1,"1266":2,"1516":1,"2247":1,"2261":1,"2615":1}}],["improvements",{"0":{"1399":1,"2247":1,"2255":1,"2256":1,"2265":1,"2278":1,"2355":1,"2361":1,"2559":1,"2604":1,"2679":1},"1":{"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1},"2":{"1254":2,"1256":1,"2231":1,"2239":1,"2240":2,"2255":1,"2265":1,"2270":2,"2398":1,"2576":1,"2621":1}}],["improvement",{"0":{"2446":1},"2":{"874":1,"1073":1,"1090":2,"1399":1,"2225":1,"2621":1}}],["impressive",{"2":{"1279":1}}],["importable",{"0":{"1571":1},"2":{"1559":1,"1571":1,"1792":1,"2484":1}}],["importantly",{"2":{"2177":1,"2542":1}}],["important",{"0":{"1279":1,"1377":1},"2":{"307":1,"436":1,"446":1,"573":1,"656":1,"845":1,"872":1,"954":1,"1067":1,"1129":1,"1305":1,"1384":1,"1386":1,"1387":1,"1393":1,"1398":1,"1400":1,"1402":1,"1403":1,"1792":1,"1858":1,"2177":1,"2398":1,"2629":1}}],["importparsequeryfrom",{"0":{"1574":1},"2":{"1416":1,"1417":1,"1553":1,"1560":1,"1574":1,"1581":1,"1582":1,"1792":1}}],["importbaseurlfrom",{"0":{"1574":1},"2":{"1416":1,"1417":1,"1553":1,"1560":1,"1574":1,"1581":1,"1582":1,"1792":1}}],["importing",{"2":{"888":1,"2359":1}}],["importedcsv",{"2":{"1202":2}}],["imported",{"2":{"888":3,"1559":1,"1571":1,"1792":1,"2484":2}}],["imports",{"0":{"1582":1},"2":{"868":1,"888":1,"901":1,"903":1,"908":2,"1037":1,"1386":1,"1409":1,"1414":1,"1559":1,"1571":1,"1792":2}}],["import",{"0":{"1560":1},"2":{"855":1,"878":2,"879":1,"888":2,"905":1,"1107":1,"1416":1,"1422":1,"1560":3,"1571":2,"1574":4,"1581":1,"1582":2,"1792":2,"2132":2,"2484":1}}],["impossible",{"0":{"1075":1},"2":{"875":1,"994":1,"1037":1,"1075":1,"2518":1}}],["impossibility",{"2":{"853":1}}],["imposed",{"2":{"848":1}}],["impersonated",{"2":{"865":1}}],["impersonates",{"2":{"852":1}}],["impersonating",{"2":{"852":2}}],["imperative",{"2":{"835":2,"868":1,"1067":1,"1108":1}}],["impede",{"2":{"851":1}}],["impedance",{"0":{"841":1},"2":{"840":1,"841":3,"851":8,"852":1,"865":1,"871":3}}],["implications",{"2":{"1133":1,"1716":1,"1792":2,"1942":1,"2750":1}}],["implies",{"2":{"101":1,"108":1,"720":1,"1533":1,"2380":1,"2656":1}}],["implementing",{"0":{"1209":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1},"2":{"845":1,"863":1,"910":1,"1037":2,"1064":1}}],["implemented",{"2":{"841":1,"845":3,"848":1,"852":1,"868":1,"1057":1,"1098":1,"1181":1,"1396":1,"2544":1}}],["implementable",{"2":{"841":1}}],["implementations",{"2":{"845":1,"848":2,"869":1,"879":1,"1049":1,"2369":1,"2615":1,"2642":1}}],["implementation",{"0":{"1218":1},"2":{"363":1,"841":2,"845":2,"848":1,"857":1,"868":2,"872":1,"874":1,"916":1,"951":1,"1024":1,"1025":1,"1049":1,"1064":1,"1073":1,"1098":1,"1100":1,"1101":1,"1113":1,"1181":2,"1211":2,"1218":1,"1325":2,"1412":1,"1504":1,"1689":1,"1868":1,"2309":1,"2421":1,"2461":2,"2494":1,"2625":1}}],["implements",{"2":{"317":1,"841":1,"848":1,"865":1,"1039":1,"1185":1,"1792":1,"1813":1,"1824":1,"2481":1}}],["implement",{"2":{"41":1,"845":1,"852":1,"879":1,"911":1,"922":1,"994":2,"1056":1,"1108":2,"1181":1,"1209":1,"1252":1,"1281":1,"1322":1,"1385":1,"1396":1,"2369":1}}],["iprocessorderrequest",{"2":{"2357":1}}],["iprocessorderresponse>",{"2":{"2357":1}}],["iprocessorderresponse",{"2":{"2357":1}}],["ips",{"2":{"1704":1,"1708":1,"1714":1,"1718":1,"1792":1,"2633":1}}],["ipingresponse",{"2":{"1569":1}}],["ipaddress=",{"2":{"1924":1,"2549":1}}],["ipaddress=203",{"2":{"1069":1}}],["ipaddressparametername",{"2":{"1469":1,"1477":1,"1483":1,"1539":1,"1544":1,"1546":1,"1548":1,"1792":1}}],["ipaddresscontextkey",{"2":{"1469":1,"1475":1,"1483":1,"1539":1,"1540":1,"1548":1,"1792":1,"2184":1}}],["ipaddress",{"2":{"479":1,"1069":1,"1070":1,"1102":1,"1162":2,"1792":5,"1852":1,"1955":2,"1957":1,"1958":1,"1959":1,"1960":1,"2379":3,"2383":1,"2441":1,"2470":1,"2471":1}}],["ipasswordhasher",{"2":{"309":1,"363":1}}],["ip",{"0":{"735":1,"2812":1},"2":{"41":1,"436":1,"453":1,"480":1,"735":2,"738":2,"799":5,"802":2,"835":1,"1069":2,"1101":3,"1102":1,"1121":1,"1162":3,"1225":1,"1237":1,"1239":2,"1241":3,"1469":2,"1475":3,"1477":3,"1483":2,"1539":2,"1540":2,"1543":2,"1544":2,"1546":1,"1547":5,"1548":2,"1569":1,"1616":1,"1684":2,"1701":1,"1703":1,"1704":3,"1705":2,"1707":2,"1714":1,"1759":1,"1792":27,"1852":1,"1875":1,"1890":3,"1912":1,"1923":1,"1924":2,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1957":2,"1959":1,"2184":2,"2222":2,"2379":3,"2383":1,"2438":1,"2471":1,"2509":1,"2510":1,"2511":1,"2513":1,"2520":1,"2549":1,"2633":6,"2806":1,"2812":3}}],["idataprotector",{"2":{"2405":1}}],["id>",{"2":{"1792":1}}],["idle",{"2":{"1616":2}}],["idx",{"2":{"1336":1}}],["idp",{"2":{"1045":1,"1792":1,"1825":4}}],["idictionary",{"2":{"2266":1,"2482":1}}],["idiomatic",{"2":{"2562":1}}],["idiom",{"2":{"876":1,"2531":1,"2533":2}}],["idistributedcache",{"2":{"869":1}}],["idempotent",{"2":{"2533":1}}],["idempotency",{"2":{"2438":2}}],["ides",{"2":{"876":1,"2247":1}}],["ide",{"2":{"876":2,"1002":1,"1406":1,"1409":2,"1418":1,"2670":1}}],["idea",{"0":{"832":1},"2":{"831":2,"848":1,"851":2,"1073":1,"1082":2,"1386":2,"1402":1}}],["ideal",{"2":{"212":1,"851":3,"1035":1,"1098":1,"1105":1,"1127":1,"1160":1,"1323":1,"2054":1}}],["identity",{"0":{"305":1,"2181":1},"2":{"305":1,"320":1,"327":1,"380":1,"453":3,"764":1,"774":1,"834":1,"851":1,"852":1,"868":1,"913":3,"924":1,"977":2,"1050":1,"1098":1,"1204":1,"1213":1,"1307":2,"1355":1,"1694":1,"1787":1,"1792":4,"1794":1,"1824":1,"1827":1,"2115":1,"2170":1,"2171":3,"2181":1,"2183":2,"2333":1,"2379":1,"2395":1,"2423":2,"2702":1,"2812":1,"2817":1,"2836":1}}],["identically",{"2":{"155":1,"320":1,"665":1,"927":1,"1045":1,"1125":1,"1135":1,"1266":1,"1272":1,"1567":1,"1686":1,"1840":1,"2190":1,"2193":1,"2200":1,"2481":1,"2856":1}}],["identical",{"2":{"120":1,"847":1,"884":1,"1089":1,"1255":1,"1279":1,"1350":1,"1357":1,"1792":2,"1840":1,"2222":1,"2224":1,"2378":1,"2423":1,"2459":1,"2463":1,"2464":1,"2482":1,"2504":1,"2535":1,"2540":1,"2841":1,"2845":1,"2873":1}}],["identification",{"2":{"2686":1,"2701":1}}],["identified",{"2":{"448":1}}],["identifies",{"2":{"309":1,"1023":1,"1225":1,"1472":1,"1792":3,"1875":1,"2255":1}}],["identifier=42",{"2":{"1069":1}}],["identifier",{"2":{"34":1,"37":1,"38":2,"39":1,"40":1,"168":1,"305":1,"382":1,"384":1,"388":1,"479":1,"738":1,"762":1,"802":1,"903":1,"1068":1,"1069":1,"1162":1,"1214":1,"1232":1,"1234":1,"1237":1,"1318":1,"1355":1,"1458":4,"1655":1,"1792":5,"1882":1,"1884":1,"1887":1,"1906":1,"1955":1,"1957":1,"1960":1,"2117":1,"2125":1,"2334":1,"2375":3,"2379":2,"2395":1,"2493":2,"2494":1,"2540":1,"2572":1,"2702":1}}],["identifiers",{"0":{"22":1},"2":{"388":1,"2314":1}}],["identifying",{"2":{"1671":1,"2277":1}}],["identify",{"2":{"51":2,"1619":1}}],["id=7",{"2":{"2876":1}}],["id=user",{"2":{"1366":1}}],["id=2",{"2":{"691":1}}],["id=42",{"2":{"436":2,"527":1,"689":1,"1033":1,"1412":1,"1738":1,"2283":1,"2529":1,"2865":1}}],["id=123",{"2":{"523":1,"1148":3,"1518":3,"2265":3}}],["id=1",{"2":{"297":2,"374":2,"612":1,"613":1,"691":1,"1074":2,"1121":1,"2526":1,"2739":1,"2774":1,"2860":1,"2869":2}}],["id=",{"2":{"209":1,"834":1,"938":7,"1061":8,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1697":1,"1792":5}}],["id=5",{"2":{"167":1,"2322":1}}],["id",{"0":{"20":1,"1326":1,"2314":1,"2733":1},"2":{"16":4,"18":3,"19":2,"20":3,"21":1,"22":1,"25":1,"37":1,"105":3,"115":2,"116":5,"119":2,"128":3,"136":5,"167":3,"168":5,"169":3,"184":7,"186":7,"209":2,"212":3,"215":3,"248":1,"254":3,"255":6,"256":3,"257":3,"258":1,"288":4,"292":4,"298":4,"302":1,"304":1,"305":2,"306":5,"309":2,"310":11,"312":3,"313":2,"320":1,"332":1,"333":2,"334":2,"335":1,"352":2,"360":2,"361":2,"365":1,"366":2,"370":2,"372":1,"373":2,"374":7,"375":4,"376":3,"378":5,"380":2,"382":1,"384":1,"388":1,"392":1,"393":2,"401":2,"405":3,"406":6,"407":1,"408":6,"415":3,"423":3,"426":6,"427":3,"428":3,"448":1,"453":1,"454":5,"469":5,"489":2,"493":2,"523":1,"527":4,"542":1,"553":1,"554":1,"562":4,"563":2,"565":7,"592":2,"609":2,"611":7,"612":4,"613":2,"614":8,"621":4,"622":10,"623":5,"639":3,"650":2,"658":1,"659":1,"664":2,"665":3,"666":1,"668":2,"677":2,"691":2,"695":3,"722":5,"733":4,"736":2,"738":2,"762":1,"764":1,"765":3,"766":2,"772":1,"774":1,"797":5,"798":3,"802":2,"811":7,"812":4,"815":3,"826":4,"834":1,"835":7,"849":1,"851":2,"860":3,"864":1,"880":1,"883":3,"884":2,"885":5,"888":2,"893":2,"904":2,"905":2,"913":9,"914":12,"915":2,"916":25,"918":16,"924":1,"934":3,"936":4,"937":2,"977":5,"979":2,"980":1,"982":1,"986":1,"988":2,"990":2,"992":4,"995":1,"1017":2,"1029":1,"1033":3,"1038":1,"1044":1,"1045":2,"1050":1,"1055":2,"1056":9,"1057":2,"1058":6,"1059":1,"1060":7,"1062":4,"1068":4,"1070":3,"1073":2,"1076":2,"1079":2,"1095":1,"1098":2,"1102":2,"1105":10,"1107":1,"1110":1,"1113":5,"1114":1,"1121":1,"1135":2,"1141":2,"1142":8,"1149":2,"1150":2,"1154":2,"1179":3,"1187":1,"1188":1,"1189":1,"1191":3,"1192":2,"1193":6,"1197":5,"1210":1,"1213":6,"1214":7,"1215":9,"1216":10,"1220":3,"1221":2,"1222":2,"1229":1,"1232":20,"1233":1,"1234":16,"1235":5,"1236":9,"1237":2,"1238":1,"1239":14,"1240":1,"1243":1,"1305":1,"1307":4,"1308":2,"1309":10,"1310":4,"1317":6,"1320":2,"1321":10,"1326":6,"1336":1,"1355":2,"1357":3,"1366":5,"1368":3,"1369":1,"1371":4,"1372":14,"1373":1,"1375":4,"1378":1,"1386":11,"1387":7,"1390":5,"1391":4,"1393":7,"1394":3,"1395":9,"1396":7,"1398":9,"1399":4,"1405":1,"1408":3,"1410":2,"1412":2,"1414":2,"1416":4,"1419":3,"1458":4,"1469":5,"1473":1,"1474":2,"1476":2,"1478":2,"1483":5,"1504":10,"1520":1,"1529":2,"1531":2,"1539":4,"1541":2,"1542":4,"1543":1,"1545":2,"1546":3,"1547":5,"1548":4,"1567":4,"1570":1,"1571":1,"1573":3,"1575":1,"1620":4,"1664":1,"1689":11,"1690":1,"1696":2,"1697":1,"1698":2,"1738":3,"1792":43,"1836":1,"1848":2,"1852":1,"1863":1,"1879":1,"1882":2,"1884":1,"1885":2,"1886":3,"1887":2,"1888":1,"1889":1,"1924":2,"1926":1,"1973":1,"1974":4,"2010":8,"2012":1,"2040":4,"2076":2,"2078":1,"2079":1,"2171":1,"2176":4,"2178":1,"2179":1,"2180":2,"2181":3,"2183":8,"2184":6,"2185":2,"2187":10,"2205":2,"2229":1,"2247":8,"2250":1,"2277":17,"2283":5,"2284":1,"2291":2,"2292":4,"2293":7,"2303":2,"2304":3,"2314":2,"2319":2,"2320":7,"2322":4,"2323":4,"2327":1,"2328":2,"2332":4,"2333":8,"2334":1,"2335":4,"2338":4,"2339":14,"2340":4,"2342":6,"2343":1,"2357":1,"2375":3,"2380":1,"2383":1,"2391":2,"2394":5,"2395":3,"2407":1,"2432":1,"2476":3,"2526":1,"2540":9,"2546":1,"2549":3,"2555":3,"2572":3,"2580":2,"2586":6,"2587":1,"2607":7,"2665":4,"2701":1,"2723":1,"2726":1,"2731":1,"2733":6,"2768":3,"2774":8,"2775":4,"2802":4,"2812":1,"2813":3,"2829":18,"2833":7,"2834":5,"2836":12,"2840":2,"2842":1,"2845":6,"2846":1,"2847":1,"2855":3,"2860":1,"2867":1,"2868":5,"2869":1,"2873":1,"2881":1}}],["ids=bitcoin",{"2":{"1023":1}}],["ids=",{"2":{"1019":1,"1398":1,"2766":1}}],["ids",{"0":{"643":1},"2":{"13":1,"14":1,"638":2,"639":1,"641":1,"643":1,"695":1,"1019":2,"1021":1,"1026":1,"1111":1,"1232":1,"1234":1,"1326":2,"1376":2,"1398":3,"1792":1,"1882":1,"1884":1,"2040":1,"2167":1,"2474":1,"2533":1,"2545":1,"2546":1,"2766":2,"2831":2,"2867":1,"2873":2}}],["i",{"0":{"936":1,"1076":1,"1371":1,"2531":1,"2714":1,"2716":1,"2717":1,"2718":1,"2719":1,"2723":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2729":1,"2731":1,"2732":1,"2733":1,"2737":1,"2739":1,"2745":1,"2746":1,"2747":1,"2749":1,"2750":1,"2751":1,"2752":1},"2":{"1":1,"307":1,"426":3,"699":1,"706":1,"838":2,"841":2,"844":1,"845":2,"847":1,"851":2,"852":2,"859":3,"876":1,"894":4,"912":1,"920":5,"922":1,"928":3,"929":2,"936":3,"938":1,"947":2,"1037":1,"1044":1,"1058":2,"1061":1,"1064":1,"1073":4,"1076":3,"1079":1,"1080":3,"1088":1,"1091":1,"1096":1,"1105":3,"1128":1,"1167":2,"1254":8,"1255":1,"1258":1,"1268":1,"1274":1,"1324":1,"1366":4,"1371":2,"1382":2,"1384":4,"1385":22,"1386":2,"1393":2,"1395":1,"1396":1,"1398":1,"1399":5,"1400":10,"1401":12,"1402":27,"1403":24,"1404":12,"1405":1,"1410":4,"1421":1,"1435":2,"1437":2,"1442":3,"1443":1,"1567":4,"1568":1,"1576":2,"1792":1,"1832":1,"1994":1,"2087":1,"2097":1,"2156":1,"2183":2,"2184":2,"2221":1,"2366":9,"2376":1,"2531":1,"2532":1,"2536":1,"2542":1,"2615":1,"2621":1,"2716":1,"2758":1,"2760":1,"2795":1,"2823":1,"2824":1,"2869":1}}],["iframe",{"2":{"2018":1}}],["iframe>",{"2":{"1792":1,"2632":1}}],["iframes",{"2":{"1792":2}}],["iformfile",{"2":{"1366":1}}],["if",{"2":{"1":1,"22":1,"31":3,"38":2,"41":2,"46":1,"63":1,"134":1,"139":1,"169":1,"175":1,"177":1,"180":1,"188":1,"207":2,"208":2,"209":4,"210":1,"213":1,"268":1,"285":1,"286":2,"297":2,"301":1,"308":1,"309":1,"310":2,"313":2,"319":2,"363":1,"370":1,"383":1,"388":2,"390":1,"395":1,"422":1,"424":2,"436":2,"439":2,"446":1,"447":3,"448":1,"449":2,"452":2,"454":1,"480":1,"528":1,"529":2,"587":1,"615":1,"625":1,"641":2,"650":1,"653":1,"654":1,"656":1,"675":3,"699":1,"701":1,"705":1,"706":1,"711":1,"715":1,"747":2,"757":1,"761":1,"762":1,"770":1,"771":2,"772":1,"776":1,"777":1,"781":2,"782":3,"784":4,"786":1,"829":2,"832":1,"834":1,"835":1,"838":1,"841":2,"843":1,"844":5,"848":2,"851":4,"852":2,"857":1,"859":2,"864":2,"869":2,"873":1,"875":1,"876":2,"877":1,"884":1,"888":2,"892":2,"894":5,"896":1,"897":4,"901":1,"902":1,"903":1,"904":2,"911":1,"914":2,"915":1,"916":1,"918":1,"919":2,"920":1,"924":2,"925":1,"928":2,"929":6,"933":2,"940":1,"941":1,"960":1,"971":1,"975":1,"977":1,"982":1,"983":1,"984":2,"996":5,"1021":6,"1024":2,"1026":5,"1031":1,"1055":1,"1060":2,"1063":1,"1071":1,"1074":1,"1076":3,"1078":1,"1081":1,"1082":2,"1094":2,"1096":2,"1105":4,"1129":2,"1132":1,"1133":2,"1134":2,"1145":1,"1150":2,"1157":1,"1165":1,"1173":1,"1174":2,"1177":1,"1178":1,"1185":1,"1197":1,"1217":1,"1218":2,"1224":1,"1225":3,"1228":2,"1229":1,"1232":2,"1234":6,"1235":1,"1236":2,"1239":2,"1240":1,"1253":1,"1254":1,"1302":1,"1317":1,"1318":2,"1326":1,"1332":4,"1335":4,"1338":5,"1339":4,"1341":1,"1342":2,"1358":1,"1361":1,"1364":1,"1366":9,"1376":6,"1377":2,"1385":3,"1386":8,"1388":1,"1391":1,"1393":1,"1394":4,"1395":2,"1399":2,"1400":1,"1401":4,"1402":1,"1403":4,"1404":4,"1405":3,"1406":1,"1409":2,"1410":5,"1414":1,"1416":3,"1427":5,"1435":1,"1441":2,"1443":3,"1447":4,"1451":1,"1454":3,"1459":1,"1464":1,"1470":1,"1489":1,"1493":1,"1499":1,"1501":1,"1504":3,"1511":2,"1518":2,"1522":2,"1523":1,"1528":1,"1568":1,"1574":1,"1618":1,"1620":1,"1621":1,"1631":1,"1644":1,"1651":3,"1664":1,"1689":2,"1690":1,"1704":1,"1708":1,"1717":1,"1722":1,"1727":2,"1732":2,"1736":2,"1741":2,"1742":1,"1753":2,"1762":1,"1767":2,"1768":1,"1781":2,"1792":100,"1806":1,"1818":1,"1825":2,"1837":2,"1858":1,"1874":1,"1875":2,"1878":1,"1879":1,"1884":1,"1885":1,"1889":1,"1898":1,"1918":1,"1921":2,"1922":2,"1940":1,"1942":1,"1948":1,"1956":1,"1957":1,"1967":3,"1974":3,"1983":1,"2003":1,"2004":1,"2018":1,"2040":1,"2047":1,"2086":4,"2097":1,"2109":1,"2111":2,"2117":1,"2127":1,"2131":1,"2156":1,"2177":1,"2181":1,"2184":1,"2247":3,"2250":1,"2252":1,"2254":2,"2255":1,"2256":3,"2264":3,"2265":4,"2266":1,"2267":1,"2283":1,"2284":2,"2289":2,"2290":1,"2296":1,"2297":1,"2307":2,"2320":1,"2337":1,"2339":1,"2375":1,"2376":1,"2378":1,"2379":2,"2380":1,"2381":1,"2389":1,"2391":1,"2394":1,"2395":1,"2402":1,"2403":1,"2406":1,"2424":1,"2438":5,"2466":3,"2476":1,"2481":3,"2486":1,"2487":1,"2490":1,"2527":1,"2528":2,"2530":1,"2531":2,"2532":3,"2533":1,"2534":2,"2537":1,"2539":1,"2542":1,"2549":3,"2551":1,"2607":3,"2621":1,"2632":2,"2633":4,"2634":8,"2635":1,"2649":1,"2682":1,"2684":1,"2685":2,"2702":1,"2721":1,"2729":1,"2762":2,"2767":1,"2788":1,"2809":1,"2810":4,"2811":1,"2812":1,"2813":2,"2815":4,"2822":1,"2823":1,"2835":2,"2862":1,"2864":2,"2869":2,"2871":1,"2872":1,"2873":1,"2876":1,"2880":1}}],["itestsseresponse",{"2":{"2247":3}}],["iterating",{"2":{"1792":1,"2096":1,"2537":1,"2877":1}}],["iteration",{"2":{"861":1,"872":3,"2614":1}}],["iterations",{"2":{"309":1,"363":1,"872":1,"1049":1,"2397":1}}],["iterative",{"2":{"861":1}}],["iterates",{"2":{"669":1,"948":1}}],["itemname=humao",{"2":{"1792":1}}],["itemprop=",{"2":{"1429":1}}],["item",{"2":{"335":4,"408":4,"469":2,"860":2,"865":1,"2364":1,"2411":1,"2586":4,"2665":4}}],["itemscope",{"2":{"1429":1}}],["items",{"2":{"136":1,"180":1,"408":5,"426":4,"469":3,"1041":1,"1044":2,"1105":2,"1107":1,"1511":1,"1792":2,"1824":2,"1974":2,"2223":1,"2481":1,"2482":2,"2607":2,"2665":5}}],["its",{"2":{"75":2,"221":1,"308":1,"309":1,"317":1,"322":1,"336":1,"347":1,"377":1,"383":1,"388":1,"395":1,"412":1,"436":2,"438":1,"439":1,"448":1,"527":1,"528":1,"535":1,"565":1,"618":1,"650":2,"663":2,"666":1,"667":2,"686":1,"694":3,"695":1,"705":1,"706":2,"716":1,"843":3,"851":1,"852":2,"857":1,"859":2,"863":2,"864":2,"865":6,"922":1,"930":1,"988":1,"1039":1,"1040":1,"1043":1,"1044":1,"1046":2,"1052":1,"1068":1,"1069":1,"1070":1,"1075":1,"1076":1,"1077":2,"1079":2,"1080":1,"1094":1,"1098":2,"1100":1,"1105":1,"1107":1,"1111":1,"1113":1,"1127":1,"1132":1,"1145":1,"1150":1,"1162":1,"1165":1,"1183":1,"1191":1,"1193":1,"1248":1,"1262":1,"1263":1,"1326":2,"1378":1,"1391":1,"1405":1,"1414":1,"1417":1,"1433":1,"1435":1,"1458":3,"1460":1,"1511":1,"1741":1,"1792":18,"1824":2,"1830":1,"1917":1,"1955":1,"1958":1,"1959":1,"1961":1,"2010":1,"2018":1,"2097":1,"2099":1,"2106":2,"2108":2,"2109":1,"2155":1,"2157":1,"2175":1,"2222":1,"2259":1,"2289":1,"2333":1,"2352":1,"2375":3,"2379":1,"2393":1,"2394":1,"2412":1,"2413":1,"2414":1,"2421":1,"2422":1,"2425":1,"2430":2,"2444":1,"2466":1,"2470":1,"2471":1,"2472":2,"2481":1,"2482":2,"2484":1,"2495":1,"2502":1,"2510":1,"2511":1,"2526":1,"2527":2,"2528":2,"2529":1,"2530":1,"2531":2,"2532":1,"2533":2,"2535":1,"2536":1,"2537":3,"2540":1,"2543":3,"2544":1,"2678":1,"2695":2,"2721":1,"2751":1,"2758":1,"2762":1,"2794":1,"2804":1,"2806":1,"2807":1,"2810":1,"2813":3,"2828":2,"2829":1,"2830":1,"2833":2,"2834":1,"2857":1,"2862":2,"2863":1,"2864":1,"2866":1,"2867":1,"2869":2,"2871":1,"2873":1,"2874":1,"2878":1,"2880":1}}],["itself",{"0":{"1381":1},"1":{"1382":1,"1383":1},"2":{"1":2,"168":1,"206":1,"216":1,"303":1,"310":1,"347":1,"395":1,"438":1,"534":1,"843":1,"844":2,"849":1,"851":1,"852":2,"861":1,"863":1,"869":1,"871":1,"872":1,"873":1,"876":2,"940":1,"981":1,"982":1,"1037":1,"1039":1,"1041":1,"1080":1,"1094":1,"1102":1,"1382":1,"1385":2,"1390":1,"1400":1,"1402":1,"1407":1,"1603":1,"1727":1,"1792":2,"1833":1,"1924":1,"1958":1,"2157":2,"2223":1,"2389":1,"2441":1,"2481":2,"2487":1,"2492":1,"2509":1,"2532":1,"2543":2,"2705":1,"2763":1,"2795":2,"2800":1,"2839":1,"2878":1}}],["it",{"0":{"324":1,"349":1,"387":1,"534":1,"870":1,"881":1,"1025":1,"1047":1,"1077":1,"1179":1,"1404":1,"1432":1,"1433":1,"1824":1,"1868":1,"2188":1,"2283":1,"2302":1,"2318":1,"2527":1,"2712":1,"2714":1,"2717":1,"2744":1,"2760":1,"2770":1,"2807":1,"2816":1,"2837":1,"2840":1,"2862":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1,"1026":1},"2":{"1":5,"22":1,"109":1,"139":1,"168":1,"171":1,"182":1,"188":1,"206":1,"212":1,"214":2,"215":2,"245":1,"252":1,"298":1,"301":1,"302":1,"306":1,"307":3,"308":4,"309":1,"317":3,"319":2,"320":1,"324":1,"325":1,"334":1,"349":2,"351":1,"354":1,"364":1,"376":1,"383":1,"386":1,"388":2,"390":2,"415":1,"423":1,"426":1,"435":2,"436":3,"438":1,"446":1,"448":3,"452":2,"453":4,"454":2,"480":1,"527":2,"528":4,"529":1,"531":2,"560":2,"562":1,"581":2,"586":2,"614":2,"618":1,"619":2,"646":1,"650":5,"658":1,"660":1,"661":1,"664":1,"666":1,"683":1,"687":1,"691":1,"693":1,"695":1,"703":1,"704":1,"708":1,"713":1,"714":1,"784":1,"826":1,"831":3,"833":1,"834":4,"835":4,"836":1,"837":1,"838":2,"840":2,"841":12,"843":8,"844":4,"845":10,"847":13,"848":16,"849":12,"851":11,"852":34,"853":1,"854":4,"855":2,"856":1,"857":8,"859":5,"860":11,"861":2,"863":5,"864":10,"865":2,"866":3,"868":4,"869":2,"871":4,"872":3,"873":1,"874":3,"875":1,"876":3,"877":4,"878":1,"880":1,"885":1,"886":1,"894":1,"904":2,"911":1,"915":1,"917":1,"918":2,"919":1,"920":4,"921":1,"922":1,"927":3,"932":1,"933":1,"934":2,"942":1,"946":2,"947":4,"948":4,"949":1,"961":2,"966":1,"972":1,"979":1,"983":1,"986":1,"990":1,"994":1,"995":1,"1005":1,"1007":1,"1009":2,"1021":1,"1033":1,"1037":3,"1038":3,"1039":2,"1042":2,"1044":1,"1045":2,"1046":1,"1049":1,"1055":2,"1059":1,"1061":1,"1064":2,"1065":1,"1067":1,"1068":2,"1073":8,"1074":1,"1075":10,"1076":6,"1077":1,"1078":3,"1080":9,"1081":3,"1082":1,"1084":1,"1086":2,"1095":1,"1096":2,"1101":2,"1102":1,"1105":1,"1106":4,"1111":1,"1126":1,"1127":2,"1129":2,"1130":1,"1132":3,"1133":3,"1134":3,"1139":3,"1141":1,"1147":1,"1148":1,"1161":1,"1162":1,"1165":1,"1173":1,"1180":1,"1181":2,"1185":2,"1190":1,"1193":1,"1196":1,"1203":1,"1210":1,"1231":1,"1252":1,"1255":1,"1270":1,"1304":1,"1305":1,"1309":2,"1318":1,"1326":1,"1331":1,"1337":1,"1341":1,"1343":1,"1358":1,"1359":1,"1363":1,"1368":1,"1373":1,"1374":1,"1378":1,"1381":1,"1382":11,"1384":9,"1385":12,"1386":15,"1389":2,"1390":5,"1391":3,"1392":1,"1393":1,"1394":5,"1395":3,"1396":9,"1398":3,"1399":4,"1400":4,"1401":10,"1402":17,"1403":20,"1404":13,"1405":3,"1406":10,"1407":2,"1408":1,"1409":5,"1413":1,"1418":2,"1419":4,"1420":2,"1421":3,"1422":3,"1423":1,"1427":1,"1429":2,"1430":1,"1431":5,"1432":3,"1435":2,"1436":1,"1437":5,"1438":1,"1439":4,"1441":3,"1442":2,"1443":1,"1511":1,"1515":1,"1517":1,"1527":1,"1528":1,"1533":1,"1559":1,"1569":2,"1571":1,"1605":1,"1618":1,"1686":1,"1727":2,"1738":4,"1742":1,"1743":1,"1759":1,"1792":51,"1802":1,"1813":1,"1818":1,"1822":2,"1823":4,"1824":3,"1825":1,"1827":1,"1830":3,"1833":1,"1851":1,"1856":2,"1912":1,"1924":1,"1925":3,"1929":1,"1940":1,"1948":1,"1959":2,"1961":2,"1974":2,"1983":1,"2040":1,"2092":3,"2096":1,"2098":3,"2102":1,"2106":1,"2107":3,"2110":5,"2111":2,"2112":1,"2127":1,"2154":2,"2162":1,"2164":2,"2167":1,"2171":2,"2175":1,"2176":1,"2177":6,"2178":1,"2183":1,"2184":1,"2185":1,"2195":1,"2197":1,"2208":1,"2221":3,"2242":1,"2252":1,"2256":1,"2265":1,"2266":1,"2267":1,"2274":1,"2283":1,"2290":1,"2291":1,"2296":1,"2297":1,"2303":1,"2314":1,"2320":1,"2321":1,"2337":1,"2338":1,"2339":2,"2344":1,"2348":2,"2353":1,"2378":1,"2380":2,"2382":1,"2388":1,"2389":2,"2391":1,"2393":1,"2395":1,"2398":1,"2402":1,"2406":1,"2416":1,"2420":1,"2421":1,"2437":1,"2438":1,"2444":1,"2451":2,"2453":2,"2455":1,"2461":4,"2463":2,"2466":3,"2470":1,"2471":2,"2477":2,"2481":7,"2482":2,"2495":2,"2497":1,"2517":1,"2518":3,"2520":2,"2527":1,"2528":4,"2529":2,"2530":5,"2531":3,"2532":3,"2533":1,"2534":3,"2535":2,"2537":14,"2539":3,"2540":1,"2542":2,"2543":3,"2546":1,"2577":1,"2586":1,"2588":1,"2607":2,"2677":1,"2678":1,"2688":1,"2695":1,"2722":1,"2723":2,"2732":2,"2733":1,"2734":1,"2740":1,"2741":1,"2742":1,"2749":1,"2758":1,"2762":3,"2763":1,"2765":1,"2767":1,"2768":3,"2770":1,"2771":1,"2772":1,"2779":1,"2782":1,"2783":1,"2784":1,"2788":3,"2789":1,"2790":1,"2791":1,"2795":5,"2798":1,"2803":2,"2806":2,"2808":1,"2810":1,"2811":1,"2813":2,"2814":2,"2815":1,"2823":2,"2824":1,"2828":1,"2829":2,"2830":1,"2831":1,"2833":1,"2834":1,"2835":1,"2838":1,"2845":1,"2852":1,"2854":1,"2855":1,"2860":1,"2862":1,"2864":3,"2865":1,"2867":1,"2868":3,"2869":1,"2872":3,"2873":1,"2876":1,"2878":4,"2879":3}}],["inlining",{"2":{"1574":1}}],["inlined",{"2":{"1571":1,"2484":1}}],["inline",{"0":{"563":1,"623":1},"2":{"318":1,"319":1,"324":1,"326":1,"560":1,"619":1,"911":1,"1125":1,"1192":1,"1375":1,"1570":1,"1571":1,"1792":3,"2000":1,"2010":1,"2020":2,"2029":1,"2112":1,"2320":1,"2330":1,"2357":1,"2359":1,"2481":2,"2484":1,"2532":2,"2537":1,"2632":2,"2677":1,"2682":1,"2694":1,"2734":1,"2832":1,"2852":1,"2871":1}}],["ingredients",{"2":{"1005":1}}],["ingestion",{"0":{"878":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1},"2":{"791":1,"1037":1,"1086":1,"1099":3,"1386":1,"2134":1,"2164":2,"2165":1}}],["ingest",{"2":{"75":1}}],["initiator",{"2":{"2833":1}}],["initiates",{"2":{"1234":1,"1884":1}}],["initialstreamwindowsize",{"2":{"1792":1,"1992":1}}],["initialconnectionwindowsize",{"2":{"1792":1,"1992":1}}],["initialization",{"2":{"1974":2,"2372":1,"2607":2}}],["initializing",{"2":{"1792":1}}],["initialized",{"2":{"1824":1,"2481":1,"2614":1}}],["initialize",{"2":{"996":1,"1043":1,"1792":3,"1818":1,"1819":1,"1820":1,"1823":1,"1824":1,"2481":1}}],["initialdelayseconds",{"2":{"1773":2}}],["initial",{"2":{"1386":1,"2363":1}}],["initially",{"2":{"1073":1}}],["init",{"2":{"996":2}}],["inactive",{"2":{"990":4,"1792":1,"2156":1}}],["inaccuracy",{"2":{"3":1}}],["inert",{"2":{"1792":1,"2092":1,"2420":1,"2545":1}}],["inefficient",{"2":{"918":1}}],["inevitable",{"2":{"872":1,"1151":1}}],["inherited",{"2":{"1958":1}}],["inheritance",{"2":{"1459":1,"2375":1,"2427":1,"2435":1}}],["inherits",{"2":{"1068":1,"1102":1,"1111":1,"1193":1,"1792":2,"1958":1,"2427":1,"2470":1,"2472":1,"2874":1}}],["inherit",{"2":{"852":1,"864":1,"1193":1,"1459":1,"1792":3,"1951":2,"1952":2,"1953":2,"1954":2,"2375":1,"2427":1}}],["inherent",{"2":{"376":1}}],["innocent",{"2":{"856":1}}],["innodb",{"2":{"848":1}}],["innerheight",{"2":{"1792":2}}],["innerhtml",{"2":{"996":7,"1409":1}}],["innerwidth",{"2":{"1792":2}}],["innerval",{"2":{"334":1,"1974":1,"2607":1}}],["inner",{"2":{"334":7,"389":1,"919":1,"1967":1,"1974":10,"2403":1,"2404":1,"2586":1,"2607":10}}],["incl",{"2":{"2493":1}}],["inclusion",{"2":{"347":1,"1792":1,"1908":1}}],["including",{"2":{"168":1,"251":1,"320":1,"338":1,"387":1,"626":1,"690":1,"872":1,"893":1,"903":1,"912":1,"916":2,"1015":1,"1030":1,"1049":1,"1071":1,"1073":1,"1082":1,"1096":1,"1097":1,"1101":1,"1111":1,"1304":1,"1317":1,"1398":1,"1420":1,"1468":1,"1472":1,"1609":1,"1612":1,"1682":1,"1718":1,"1792":9,"1851":1,"1974":1,"1978":1,"1984":1,"2002":1,"2049":1,"2050":1,"2051":1,"2059":1,"2107":1,"2114":1,"2156":1,"2157":1,"2158":1,"2185":1,"2278":1,"2356":1,"2369":1,"2371":1,"2382":1,"2435":1,"2463":1,"2490":1,"2498":2,"2529":2,"2533":1,"2537":1,"2540":1,"2542":1,"2546":3,"2577":1,"2591":1,"2607":1,"2635":3,"2661":1,"2668":1,"2705":1,"2737":1,"2744":1,"2749":1,"2758":1,"2845":1,"2865":1,"2869":1,"2881":1}}],["includelanguages",{"2":{"1792":1,"1966":1,"1967":1,"1970":1,"1975":1}}],["includenames",{"2":{"1792":1,"1836":1,"1838":1,"2701":1}}],["includemimetypes",{"2":{"1792":1,"1936":1,"1937":1,"1943":1}}],["includeparseurlparam",{"2":{"1553":1,"1561":1,"1792":1}}],["includeparserequestparam",{"2":{"1062":1,"1063":2,"1553":1,"1561":1,"1792":1}}],["includehost",{"2":{"1553":1,"1555":1,"1581":1,"1792":1}}],["includereviews=true",{"2":{"256":1,"2277":1}}],["include",{"0":{"1970":1},"2":{"125":1,"229":1,"256":2,"346":1,"349":1,"354":1,"374":5,"404":1,"448":1,"496":1,"544":1,"606":1,"711":1,"747":1,"753":1,"757":1,"758":1,"762":1,"781":1,"782":1,"784":1,"786":1,"788":1,"856":1,"933":1,"995":1,"1079":1,"1111":1,"1189":1,"1491":1,"1554":1,"1555":1,"1556":1,"1558":1,"1561":2,"1574":1,"1676":1,"1677":1,"1753":1,"1764":1,"1768":1,"1792":24,"1838":4,"1839":1,"1844":1,"1898":1,"1937":1,"1967":1,"2007":1,"2011":1,"2016":3,"2125":1,"2247":2,"2254":2,"2265":1,"2277":2,"2279":1,"2328":1,"2347":1,"2354":1,"2364":1,"2397":1,"2481":1,"2531":6,"2533":1,"2537":2,"2546":2,"2572":2,"2597":1,"2632":8,"2634":1,"2750":1,"2779":1,"2798":1,"2869":1}}],["includedmimetypepatterns",{"2":{"1792":1,"2123":1,"2125":1,"2132":1}}],["includedatabasecheck",{"2":{"1763":1,"1764":1,"1767":1,"1770":2,"1779":1,"1781":1,"1792":2,"2634":2}}],["included",{"2":{"45":1,"186":1,"618":1,"706":1,"711":1,"747":1,"753":1,"757":1,"758":1,"781":1,"782":1,"784":1,"786":1,"788":1,"878":1,"885":1,"1073":1,"1080":1,"1254":1,"1352":1,"1354":1,"1358":1,"1365":1,"1619":1,"1792":6,"1969":1,"2016":1,"2019":1,"2062":1,"2097":1,"2106":1,"2153":1,"2156":1,"2278":1,"2293":1,"2391":1,"2415":1,"2428":1,"2525":1,"2531":5,"2537":1,"2541":1,"2542":1,"2558":1,"2632":1,"2635":1,"2678":1,"2695":1,"2721":1,"2779":1,"2785":1,"2869":2,"2875":1}}],["includestatuscode",{"0":{"1568":1},"2":{"1553":1,"1558":4,"1567":1,"1568":1,"1581":1,"1792":3,"2273":3,"2359":1}}],["includeschemainnames",{"2":{"1416":1,"1417":1,"1553":1,"1554":1,"1576":1,"1580":1,"1581":1,"1792":1,"2274":1}}],["includeschemas",{"2":{"349":2,"354":2,"937":2,"998":1,"1062":1,"1792":3,"1836":1,"1838":1,"1839":1,"1863":1,"1897":1,"1898":2,"1909":1,"1910":1,"1911":1,"2187":1,"2225":1,"2419":1,"2431":2,"2433":1,"2434":1,"2435":1,"2436":2,"2701":1}}],["includes",{"0":{"2531":1,"2869":1},"2":{"32":1,"38":1,"170":1,"446":1,"539":1,"540":1,"541":1,"639":2,"699":1,"761":1,"770":1,"771":1,"772":2,"884":1,"893":1,"938":1,"961":1,"986":1,"1002":1,"1042":1,"1061":1,"1086":1,"1094":1,"1098":1,"1100":1,"1127":1,"1190":1,"1193":1,"1207":1,"1248":1,"1252":1,"1339":1,"1341":1,"1343":1,"1455":1,"1644":1,"1690":1,"1767":1,"1792":4,"1929":1,"1959":1,"1967":1,"2062":1,"2125":1,"2129":1,"2131":1,"2156":1,"2166":1,"2206":1,"2221":1,"2377":1,"2471":1,"2531":1,"2532":1,"2533":2,"2537":1,"2542":1,"2550":1,"2554":1,"2597":1,"2634":2,"2635":1,"2677":1,"2723":1,"2751":1,"2776":1,"2785":1,"2791":1,"2802":1,"2833":1,"2869":2}}],["inch",{"2":{"1437":1}}],["inc",{"2":{"1189":1}}],["increaed",{"2":{"1254":1}}],["increases",{"2":{"1169":1}}],["increase",{"0":{"1170":1},"2":{"1167":1,"1170":1,"1706":1,"1792":1,"2089":1,"2633":1}}],["increasingly",{"2":{"1384":1}}],["increasing",{"2":{"1032":1,"1267":1}}],["incredible",{"2":{"920":1}}],["incremented",{"2":{"2648":1}}],["increment",{"2":{"896":1,"1056":1}}],["incident",{"2":{"861":1}}],["incarnations",{"2":{"841":1}}],["incomplete",{"2":{"2411":1}}],["incompletely",{"2":{"1409":1}}],["incompatible",{"2":{"2297":1}}],["incoming",{"2":{"414":5,"423":1,"431":1,"436":3,"446":2,"452":1,"1396":1,"1704":1,"1792":1,"1915":1,"1924":1,"2247":1,"2302":1,"2419":1,"2450":1,"2509":1,"2549":1,"2807":2,"2811":3}}],["inconsistent",{"2":{"1191":1,"2414":1,"2493":1,"2510":1}}],["incorrectly",{"2":{"2358":1,"2588":1,"2597":1}}],["incorrect",{"2":{"587":1,"2258":3,"2313":1,"2551":2,"2611":1,"2641":1}}],["invlude",{"2":{"2247":1}}],["invitation",{"2":{"1220":1}}],["invisible",{"2":{"666":1,"695":1,"873":1,"1077":1,"1437":1,"2384":1,"2421":1,"2434":1,"2450":1,"2531":1}}],["inverted",{"2":{"2678":1}}],["inverts",{"2":{"1304":1,"2389":1}}],["invent",{"2":{"1382":1}}],["inventory",{"2":{"1042":2,"1044":1}}],["invented",{"2":{"865":1}}],["invest",{"2":{"857":1,"876":1}}],["investment",{"2":{"857":1}}],["invariantculture",{"2":{"2451":1}}],["invariant",{"2":{"863":1,"864":7,"865":3}}],["invariants",{"2":{"843":1,"863":3,"864":2,"865":1,"2527":1,"2862":1}}],["invaluable",{"2":{"843":1,"2798":1}}],["invalidcastexception",{"2":{"2394":2}}],["invalidformat",{"2":{"1360":1}}],["invalidimage",{"2":{"1360":2}}],["invalidoperationexception",{"2":{"1157":1,"1527":1,"1948":1,"2378":1,"2380":1,"2600":1}}],["invalidmimetype",{"2":{"748":1,"1360":1}}],["invalidated",{"2":{"1148":2,"1518":3,"2265":3}}],["invalidates",{"2":{"1068":1}}],["invalidate",{"2":{"1067":3,"1148":3,"1177":1,"1518":2,"1529":1,"1792":2,"2265":4}}],["invalidatecachesuffix",{"2":{"108":1,"1067":2,"1148":1,"1177":1,"1181":1,"1510":1,"1511":1,"1518":2,"1529":1,"1792":1,"2265":4}}],["invalidating",{"2":{"1054":1,"2353":1}}],["invalidation",{"0":{"1148":1,"1518":1},"2":{"108":1,"852":1,"868":1,"869":1,"1101":1,"1139":2,"1140":1,"1148":2,"1181":3,"1511":2,"1518":2,"1792":4,"2265":8,"2498":1}}],["invalid",{"0":{"280":1,"2360":1,"2495":1},"2":{"35":1,"313":1,"382":2,"864":1,"897":1,"904":1,"1111":1,"1360":1,"1366":4,"1460":1,"1527":3,"1678":1,"1741":1,"1957":1,"2007":1,"2223":1,"2289":1,"2328":1,"2334":2,"2360":2,"2367":1,"2375":1,"2377":1,"2379":1,"2417":1,"2428":1,"2435":1,"2443":1,"2492":1,"2495":1,"2497":1,"2588":2,"2589":2,"2648":1,"2649":1,"2679":1}}],["invocations",{"2":{"2462":1}}],["invocation",{"2":{"1398":1,"1792":1,"1802":1,"1824":1,"1961":2,"2104":1,"2108":1,"2167":1,"2221":1,"2258":1,"2372":1,"2536":1,"2545":1,"2795":1,"2800":1,"2880":1}}],["involving",{"2":{"1262":1}}],["involves",{"2":{"2858":1}}],["involved",{"2":{"1331":1,"1406":1}}],["involvement",{"2":{"1108":1}}],["involve",{"2":{"974":1}}],["invoking",{"0":{"2529":1,"2865":1},"2":{"876":1}}],["invokeasync",{"2":{"2504":1}}],["invokeallasync",{"2":{"2504":1}}],["invokes",{"2":{"1078":1,"1822":1,"2264":1,"2739":1,"2860":1}}],["invoker",{"2":{"932":1}}],["invoke",{"2":{"423":1,"669":1,"1037":1,"1398":1,"1792":2,"2304":1,"2344":1,"2525":1,"2772":1,"2781":1,"2803":1,"2876":1}}],["invoked",{"2":{"421":1,"445":1,"480":1,"694":1,"1074":1,"1094":1,"1746":1,"1792":1,"1929":1,"2107":1,"2347":1,"2537":2,"2543":1,"2862":1,"2879":2,"2881":1}}],["invoiceid",{"2":{"1391":2}}],["invoicecount",{"2":{"1386":3}}],["invoices",{"2":{"1386":2,"1391":3}}],["invoice",{"2":{"392":1,"426":14,"844":2,"855":1,"861":1,"1105":2,"1386":1,"1391":1}}],["inf",{"2":{"2802":1,"2823":1,"2824":2,"2825":1}}],["inflicted",{"2":{"2156":1,"2542":1,"2803":1}}],["inflates",{"2":{"1823":1}}],["influential",{"2":{"847":1}}],["infra",{"2":{"1441":1}}],["infrastructure",{"0":{"1100":1,"1259":1,"1303":1,"1791":1,"1798":1},"2":{"843":2,"845":3,"847":2,"851":1,"873":1,"908":1,"947":1,"1054":1,"1086":1,"1094":1,"1101":1,"1103":1,"1105":1,"1106":1,"1108":1,"1123":1,"1127":2,"1135":1,"1137":1,"1180":1,"1181":1,"1304":1,"1322":2,"1327":1,"1354":1,"1382":1,"1385":1,"1386":1,"1406":2,"1713":1,"1792":1,"2634":1,"2802":1}}],["infinite",{"0":{"882":1,"2381":1},"1":{"883":1,"884":1},"2":{"1704":1,"2607":1}}],["infer",{"2":{"916":1,"1378":1,"2336":1,"2734":1,"2847":1}}],["inference",{"0":{"427":1},"2":{"414":1,"587":1,"871":1,"1037":1,"1104":1,"1105":1,"1335":1,"1396":1,"2300":1,"2540":1,"2845":1}}],["inferred",{"2":{"373":2,"835":2,"2318":1,"2324":2,"2774":1,"2840":1,"2843":1}}],["infourl",{"2":{"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1792":5}}],["informix",{"2":{"918":2}}],["information",{"2":{"109":1,"779":1,"905":1,"918":2,"1133":1,"1193":1,"1210":1,"1472":1,"1521":1,"1527":1,"1792":16,"1800":1,"1801":3,"1802":6,"1806":1,"1810":3,"1839":1,"1974":1,"2016":1,"2019":2,"2052":1,"2059":1,"2254":1,"2380":1,"2384":1,"2536":1,"2607":1,"2628":1,"2629":1,"2632":2,"2634":1,"2635":1,"2668":1,"2785":1,"2786":1,"2792":1,"2794":1,"2795":3,"2804":8}}],["info",{"0":{"631":1,"1899":1,"2248":1,"2249":1},"1":{"2249":1,"2250":1,"2251":1,"2252":1},"2":{"7":3,"220":1,"333":5,"488":2,"629":2,"631":2,"641":1,"650":6,"651":1,"653":4,"655":1,"656":3,"658":4,"659":3,"662":2,"663":2,"664":1,"665":1,"666":2,"735":2,"771":1,"835":1,"868":1,"914":7,"916":5,"948":1,"966":1,"1060":1,"1103":1,"1113":1,"1191":1,"1193":3,"1233":1,"1254":1,"1305":4,"1309":3,"1317":1,"1321":1,"1324":1,"1325":1,"1327":1,"1372":2,"1390":1,"1393":1,"1416":1,"1573":1,"1696":1,"1792":6,"1836":1,"1857":2,"1858":1,"1883":1,"1898":3,"2249":6,"2251":1,"2252":9,"2254":3,"2261":2,"2372":1,"2391":1,"2392":1,"2490":1,"2635":2,"2672":1,"2701":1,"2795":1,"2802":2,"2828":4,"2829":4,"2830":2,"2832":6,"2833":1,"2834":3,"2835":3,"2836":1}}],["injecting",{"2":{"1717":1,"2372":1,"2881":1}}],["injection",{"0":{"941":1,"1165":1,"2040":1,"2712":1},"2":{"529":1,"869":1,"933":1,"941":2,"945":2,"1070":1,"1102":1,"1166":1,"1168":1,"1171":1,"1185":1,"1528":1,"1709":1,"1717":1,"1792":2,"1852":1,"2016":1,"2020":1,"2284":1,"2383":1,"2632":1,"2633":1}}],["injects",{"2":{"690":1,"801":1,"868":1,"886":1,"1358":1,"2171":1,"2346":1,"2527":1,"2862":1}}],["inject",{"0":{"531":1},"2":{"306":1,"527":1,"691":1,"1063":1,"1105":1,"1706":1,"2040":1,"2224":1,"2493":1,"2529":1,"2712":1,"2865":1}}],["injected",{"2":{"168":1,"215":1,"309":1,"363":1,"692":1,"936":1,"937":1,"1098":1,"1357":1,"2474":1,"2483":1}}],["inputschema",{"2":{"1039":1,"1040":1,"1043":1,"1044":1,"1824":1,"2481":1}}],["inputs",{"2":{"852":1,"874":1,"1066":1,"1102":1,"1524":1,"1792":1,"1852":1,"2223":1,"2224":1,"2270":1,"2380":1,"2383":1,"2402":1,"2494":1,"2604":2,"2815":1}}],["input",{"0":{"1134":2},"2":{"215":1,"308":1,"322":1,"357":1,"511":1,"529":4,"928":4,"929":3,"938":2,"1061":5,"1101":1,"1134":2,"1361":1,"1417":1,"1435":1,"1491":1,"1519":1,"1525":1,"1569":1,"1792":2,"2039":1,"2040":1,"2177":1,"2270":1,"2284":1,"2453":1,"2496":1,"2760":1}}],["inbound",{"2":{"214":1,"2502":1,"2504":1}}],["industry",{"2":{"840":1,"844":1,"851":1,"852":1,"857":1,"861":1,"865":1,"1075":1,"1098":1,"1445":1,"1453":1,"1457":1,"2554":1}}],["indefinite",{"2":{"2492":1}}],["indefinitely",{"2":{"2362":1}}],["independence",{"2":{"848":2,"2456":1}}],["independent",{"0":{"2451":1},"2":{"336":1,"337":1,"414":1,"650":1,"667":1,"853":1,"1038":1,"1042":1,"1746":1,"1792":1,"1856":1,"1973":1,"2010":1,"2040":1,"2098":1,"2224":1,"2302":1,"2347":1,"2384":1,"2415":1,"2534":1,"2744":1,"2752":1,"2794":1,"2871":1}}],["independently",{"2":{"320":1,"1107":1,"1115":1,"1142":1,"1405":1,"1741":1,"1792":2,"2108":1,"2289":1,"2352":1,"2481":1,"2536":1,"2544":1,"2546":1,"2794":1,"2858":1}}],["indeed",{"2":{"841":1,"1132":2}}],["indexing",{"2":{"1129":1}}],["indexesstatspath",{"2":{"1792":1,"2046":1,"2047":1,"2064":1,"2635":1}}],["indexes",{"0":{"2051":2},"2":{"848":2,"857":1,"861":2,"868":1,"966":2,"1100":1,"1406":1,"1409":1,"1792":3,"2045":1,"2046":1,"2047":1,"2051":1,"2064":1,"2635":5}}],["indexed",{"2":{"696":1,"876":1,"1792":1,"2098":1,"2534":1,"2545":1,"2873":1}}],["index",{"2":{"188":1,"324":3,"760":2,"761":1,"762":1,"764":4,"765":3,"766":3,"770":2,"771":1,"772":1,"774":4,"848":3,"857":1,"860":1,"868":1,"869":1,"876":1,"882":1,"883":3,"884":4,"888":4,"893":1,"896":1,"897":2,"904":3,"938":1,"966":2,"976":1,"1107":1,"1200":1,"1213":1,"1336":1,"1792":4,"2040":2,"2047":1,"2050":1,"2051":3,"2129":1,"2131":1,"2296":1,"2476":1,"2572":2,"2635":3}}],["indirect",{"2":{"1230":1,"1792":1,"1880":1}}],["indicating",{"2":{"1792":2}}],["indicative",{"2":{"845":1}}],["indicator",{"2":{"1480":1,"1918":1}}],["indicated",{"2":{"1940":1,"1941":1}}],["indicate",{"2":{"1053":1,"1792":2}}],["indicates",{"2":{"38":1,"1767":1,"1768":1,"1792":1,"2016":1,"2025":1,"2088":1,"2632":1}}],["individually",{"2":{"868":1,"1098":1,"1113":1,"1385":2,"1792":2,"2321":1}}],["individual",{"0":{"2544":1},"2":{"239":2,"369":1,"718":1,"826":1,"830":1,"915":1,"1097":1,"1385":1,"1511":1,"1543":1,"1631":1,"1632":1,"1704":1,"1722":1,"1792":13,"1837":1,"2010":1,"2111":1,"2221":1,"2332":1,"2364":1,"2366":1,"2525":1,"2533":1,"2831":1,"2840":1,"2853":1,"2870":1}}],["insanely",{"2":{"1393":1}}],["insufficient",{"2":{"1111":1,"1594":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":2,"1825":1,"2255":2,"2481":1,"2721":1}}],["insulate",{"2":{"848":1}}],["ins",{"2":{"869":1}}],["inspects",{"2":{"1823":1}}],["inspected",{"2":{"1792":1,"2094":1,"2103":1}}],["inspection",{"2":{"1457":1,"1792":1,"2465":1,"2554":1}}],["inspector",{"2":{"1047":2}}],["inspect",{"0":{"2110":1},"2":{"864":1,"1054":1,"1106":1,"1150":1,"1523":1,"1609":1,"1792":1,"2380":1,"2530":1,"2532":1,"2537":1,"2660":1,"2693":1,"2700":1,"2785":1}}],["insist",{"2":{"847":1}}],["inside",{"0":{"2714":1},"2":{"176":1,"298":1,"306":1,"310":1,"337":1,"395":1,"529":1,"583":1,"650":1,"663":1,"689":2,"698":1,"836":1,"848":1,"852":1,"855":1,"861":1,"865":1,"872":1,"874":1,"876":1,"919":1,"932":1,"946":1,"993":1,"1037":1,"1073":1,"1074":2,"1078":1,"1106":1,"1108":1,"1396":1,"1419":1,"1792":2,"2092":1,"2106":1,"2109":1,"2154":1,"2167":1,"2338":1,"2394":1,"2403":1,"2427":1,"2444":1,"2445":1,"2446":2,"2463":1,"2525":1,"2527":1,"2528":2,"2529":1,"2531":3,"2537":1,"2586":3,"2678":1,"2695":1,"2717":1,"2828":1,"2854":1,"2855":2,"2858":1,"2860":1,"2861":1,"2862":1,"2864":1,"2869":1}}],["insecure",{"2":{"2282":1}}],["insensitive",{"0":{"2493":1},"2":{"212":1,"258":1,"269":1,"379":1,"390":1,"395":1,"407":1,"448":1,"464":1,"522":1,"537":1,"687":1,"709":1,"809":1,"1042":1,"1459":1,"1460":1,"1523":1,"1792":7,"1854":1,"1922":1,"1967":2,"2036":1,"2097":1,"2192":1,"2223":1,"2277":1,"2333":1,"2375":2,"2380":1,"2435":1,"2493":1,"2537":2,"2544":1,"2546":1,"2595":1,"2614":1,"2678":1,"2679":1,"2692":1,"2694":1,"2695":2,"2700":1,"2764":1,"2877":1}}],["insensitively",{"2":{"74":1,"388":1,"395":1,"528":1,"818":1,"1792":1,"1862":1,"2222":1,"2483":1,"2493":1,"2518":1}}],["inserting",{"0":{"2868":1}}],["inserts",{"2":{"864":1,"901":1,"1357":1,"1367":1,"1410":1,"2531":1,"2739":1,"2829":1,"2868":1}}],["inserted",{"0":{"2741":1},"2":{"715":1,"880":1,"885":1,"987":1,"989":2,"1079":1,"1357":1,"1410":1,"2050":1,"2868":1}}],["insert",{"0":{"898":1},"2":{"175":1,"184":2,"248":1,"292":1,"310":1,"360":2,"361":1,"365":1,"567":1,"583":1,"624":1,"625":1,"715":1,"764":1,"765":1,"766":1,"774":1,"813":1,"814":1,"826":1,"848":1,"864":1,"881":1,"883":1,"884":1,"885":2,"888":3,"904":1,"913":3,"915":2,"938":1,"977":2,"989":3,"990":2,"992":4,"994":1,"1054":1,"1074":2,"1075":1,"1078":1,"1079":3,"1107":1,"1214":1,"1215":2,"1232":1,"1234":1,"1239":1,"1307":2,"1309":1,"1321":1,"1332":1,"1338":1,"1339":1,"1357":2,"1372":1,"1393":1,"1410":1,"1419":1,"1439":1,"1442":1,"1655":1,"1664":1,"1689":2,"2147":2,"2292":1,"2319":1,"2320":1,"2338":1,"2342":1,"2389":1,"2498":1,"2526":1,"2527":1,"2572":1,"2575":1,"2739":1,"2741":1,"2803":1,"2815":1,"2828":1,"2829":3,"2836":1,"2843":1,"2851":1,"2855":1,"2860":1,"2862":1,"2868":4,"2869":2}}],["instinct",{"2":{"864":1}}],["instantiated",{"2":{"1522":1,"1792":1,"2380":1}}],["instantly",{"2":{"1171":1,"1346":1,"2532":1}}],["instantaneous",{"2":{"994":1}}],["instant",{"2":{"888":1,"993":1,"2451":1,"2533":1,"2534":1,"2537":1,"2621":1,"2873":1}}],["instanceidrequestheadername",{"2":{"1792":1,"1836":1,"1848":1,"2701":1}}],["instances",{"2":{"844":1,"974":2,"1015":1,"1067":1,"1145":1,"1147":1,"1515":3,"1792":3,"2098":1,"2274":3,"2466":2,"2534":1,"2614":2,"2871":1}}],["instance",{"2":{"108":1,"121":2,"307":1,"863":1,"864":1,"865":1,"1013":1,"1015":1,"1086":1,"1107":1,"1145":2,"1146":1,"1150":1,"1255":1,"1386":1,"1522":1,"1744":1,"1792":3,"1848":1,"2177":1,"2274":1,"2297":2,"2329":1,"2346":1,"2380":1,"2389":1,"2438":1,"2466":1}}],["installed",{"2":{"1013":2,"1106":1,"1343":1,"2161":1,"2550":1,"2791":1,"2792":1,"2819":1}}],["installation",{"0":{"1013":1,"2715":1,"2777":1,"2779":1,"2786":1,"2787":1},"1":{"2716":1,"2717":1,"2718":1,"2719":1,"2778":1,"2779":1,"2780":1,"2781":1,"2782":1,"2783":1,"2784":1,"2785":1,"2786":1,"2787":1,"2788":2,"2789":2,"2790":2,"2791":2,"2792":1,"2793":1},"2":{"2716":1,"2785":1,"2786":1,"2819":1}}],["install",{"0":{"2716":1},"2":{"876":1,"958":1,"968":2,"970":2,"1010":1,"1075":1,"1117":1,"1380":1,"1420":1,"2162":2,"2786":4}}],["installments",{"2":{"861":1}}],["instructing",{"2":{"1983":1}}],["instruction",{"2":{"1081":1,"2270":1}}],["instructions",{"0":{"1820":1},"2":{"327":1,"1044":1,"1792":3,"1814":1,"1820":2,"1823":2,"2270":1,"2481":1}}],["instruct",{"2":{"1081":1,"1792":1,"2632":1}}],["instructs",{"2":{"860":1}}],["instead",{"0":{"2726":1,"2740":1},"2":{"0":1,"75":1,"108":1,"214":1,"227":1,"229":1,"253":1,"328":1,"336":1,"352":1,"370":1,"377":1,"378":1,"382":1,"388":1,"414":1,"423":1,"449":1,"479":1,"480":1,"484":1,"494":1,"522":1,"527":1,"528":1,"545":1,"581":1,"607":1,"615":1,"617":1,"673":1,"691":1,"693":1,"698":1,"704":1,"741":1,"776":1,"823":1,"829":1,"834":1,"848":1,"857":1,"861":1,"872":1,"878":1,"892":1,"917":1,"919":1,"920":2,"982":1,"985":2,"1058":1,"1066":1,"1121":1,"1148":1,"1176":2,"1189":1,"1196":1,"1304":1,"1324":1,"1325":2,"1370":1,"1373":1,"1375":1,"1382":2,"1385":2,"1394":1,"1399":2,"1402":1,"1412":1,"1414":1,"1427":1,"1429":1,"1430":1,"1435":1,"1464":1,"1559":1,"1563":1,"1568":1,"1574":1,"1576":1,"1581":1,"1605":1,"1609":1,"1651":1,"1662":1,"1704":2,"1705":1,"1743":1,"1744":1,"1746":1,"1792":19,"1802":1,"1822":1,"1825":1,"1833":1,"1917":2,"1924":1,"1925":1,"1955":1,"1957":1,"1967":2,"1968":1,"1974":1,"2000":1,"2009":1,"2072":1,"2076":1,"2078":1,"2094":2,"2098":1,"2104":1,"2105":1,"2111":1,"2130":1,"2156":1,"2177":1,"2181":1,"2184":1,"2206":1,"2217":1,"2221":1,"2222":1,"2242":1,"2245":1,"2247":2,"2253":3,"2258":3,"2259":1,"2267":2,"2277":1,"2310":1,"2324":1,"2326":1,"2330":1,"2333":1,"2334":1,"2336":1,"2337":2,"2338":1,"2339":2,"2347":3,"2348":2,"2357":1,"2358":2,"2359":1,"2364":1,"2366":1,"2367":1,"2376":2,"2379":1,"2381":1,"2393":1,"2397":1,"2399":1,"2405":1,"2411":1,"2414":1,"2432":1,"2446":1,"2463":2,"2464":1,"2476":1,"2484":1,"2487":1,"2492":1,"2497":1,"2502":1,"2517":2,"2518":1,"2527":1,"2529":2,"2530":1,"2532":1,"2533":2,"2534":1,"2537":1,"2540":1,"2542":1,"2543":1,"2551":1,"2554":1,"2565":1,"2576":1,"2586":3,"2588":1,"2590":2,"2597":1,"2603":2,"2607":1,"2614":2,"2618":1,"2633":3,"2641":1,"2650":1,"2659":1,"2660":1,"2663":1,"2679":1,"2687":1,"2688":1,"2767":1,"2771":1,"2789":1,"2795":1,"2814":1,"2815":1,"2840":1,"2841":1,"2852":1,"2854":1,"2855":1,"2862":1,"2865":1,"2871":1,"2881":1}}],["int32",{"2":{"2555":1}}],["int>",{"2":{"2255":1}}],["intl",{"2":{"1792":1}}],["introspection",{"0":{"2324":1}}],["introspects",{"2":{"1419":1}}],["introducing",{"2":{"2160":1}}],["introduces",{"2":{"948":1,"1070":1,"1102":1,"1256":1,"1368":1,"2525":1}}],["introduced",{"2":{"848":1,"966":1,"1073":1,"1402":1,"2419":1,"2591":1,"2621":1}}],["introduction",{"0":{"840":1,"1073":1,"1385":1},"2":{"220":1,"1386":1,"1785":1}}],["intuitive",{"2":{"1390":1}}],["into",{"0":{"75":1,"531":1,"1038":1,"1183":1,"2733":1,"2803":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1},"2":{"75":1,"77":1,"165":1,"167":1,"184":2,"214":1,"215":3,"248":1,"292":1,"297":1,"300":1,"308":1,"310":2,"312":1,"319":1,"320":1,"327":1,"328":1,"330":1,"335":1,"360":2,"361":1,"365":1,"374":1,"390":1,"395":2,"428":1,"439":1,"452":1,"527":2,"531":1,"698":1,"715":1,"720":1,"724":1,"728":1,"737":1,"764":1,"765":1,"766":1,"774":1,"801":1,"813":1,"814":1,"826":1,"831":1,"832":1,"834":2,"840":1,"843":1,"851":2,"852":2,"856":2,"857":1,"861":1,"863":1,"865":1,"868":2,"869":1,"871":1,"872":1,"876":2,"878":2,"879":1,"881":1,"883":1,"884":1,"885":2,"886":1,"888":3,"898":1,"903":1,"904":1,"909":1,"913":3,"915":2,"916":2,"917":1,"918":1,"919":1,"928":1,"937":1,"938":1,"946":1,"947":1,"948":1,"959":1,"965":2,"966":1,"977":2,"989":1,"990":3,"991":2,"992":1,"994":1,"1010":2,"1016":1,"1033":1,"1035":1,"1037":4,"1038":1,"1042":1,"1049":1,"1051":1,"1054":1,"1070":1,"1073":1,"1074":2,"1075":1,"1077":1,"1078":2,"1079":1,"1088":1,"1095":1,"1096":1,"1097":2,"1099":1,"1105":2,"1106":1,"1111":1,"1126":1,"1150":1,"1159":1,"1162":1,"1183":1,"1185":1,"1190":1,"1192":1,"1202":1,"1208":1,"1214":2,"1215":3,"1232":3,"1234":3,"1235":1,"1239":1,"1302":1,"1305":1,"1307":1,"1309":2,"1321":2,"1323":1,"1327":1,"1332":2,"1338":2,"1339":2,"1347":1,"1351":1,"1357":3,"1358":1,"1372":3,"1373":1,"1374":1,"1378":1,"1382":2,"1383":1,"1391":1,"1393":2,"1394":1,"1395":2,"1396":1,"1402":1,"1403":3,"1404":1,"1410":1,"1419":2,"1420":2,"1424":1,"1427":2,"1429":1,"1430":1,"1431":2,"1436":1,"1442":1,"1519":1,"1559":1,"1570":1,"1582":1,"1608":1,"1655":1,"1664":1,"1689":4,"1738":2,"1743":1,"1792":9,"1834":1,"1856":1,"1862":1,"1924":2,"1925":3,"1973":1,"2010":1,"2038":1,"2040":4,"2075":1,"2107":1,"2109":2,"2110":1,"2111":1,"2113":1,"2147":2,"2156":1,"2167":1,"2171":1,"2176":1,"2184":1,"2217":1,"2221":1,"2222":1,"2224":1,"2257":1,"2272":1,"2292":1,"2318":1,"2322":1,"2338":1,"2348":1,"2366":1,"2369":1,"2372":1,"2380":1,"2381":1,"2389":2,"2411":1,"2461":1,"2474":1,"2476":1,"2477":2,"2479":1,"2481":1,"2482":1,"2483":1,"2484":1,"2489":1,"2492":1,"2493":2,"2495":1,"2502":2,"2504":1,"2509":1,"2511":1,"2513":1,"2517":2,"2518":1,"2519":1,"2521":1,"2522":1,"2526":1,"2529":1,"2530":3,"2531":1,"2532":1,"2535":1,"2536":1,"2537":2,"2542":1,"2572":1,"2575":1,"2587":1,"2651":1,"2678":1,"2695":1,"2712":2,"2713":2,"2739":1,"2765":1,"2766":2,"2768":1,"2772":1,"2795":1,"2802":1,"2803":2,"2806":1,"2807":2,"2812":1,"2815":3,"2828":1,"2829":5,"2836":2,"2840":1,"2849":1,"2855":1,"2860":2,"2866":1,"2868":1,"2869":1,"2873":1,"2879":1}}],["int",{"2":{"18":2,"21":2,"33":1,"37":1,"38":1,"39":1,"105":2,"116":2,"117":2,"119":1,"128":1,"136":2,"137":2,"184":2,"186":5,"206":1,"207":1,"208":1,"209":2,"210":1,"212":2,"215":1,"248":1,"251":1,"254":2,"255":4,"256":2,"257":2,"258":1,"298":1,"299":1,"301":1,"309":1,"310":5,"312":1,"313":3,"320":1,"332":1,"333":1,"334":1,"335":1,"360":1,"365":1,"366":1,"374":2,"392":1,"393":1,"405":2,"406":4,"408":4,"415":2,"423":2,"426":2,"427":2,"428":2,"439":2,"447":1,"449":2,"452":2,"469":2,"489":1,"493":1,"523":2,"527":1,"553":1,"554":1,"584":1,"611":3,"658":2,"659":1,"664":2,"665":2,"677":1,"679":2,"700":1,"722":2,"723":2,"733":3,"736":2,"747":3,"748":2,"760":1,"761":1,"762":1,"764":5,"765":4,"766":4,"770":1,"771":1,"772":2,"773":1,"774":5,"797":3,"798":2,"811":3,"812":2,"815":2,"826":1,"828":1,"835":2,"882":1,"883":4,"884":4,"888":5,"903":1,"904":4,"905":1,"913":6,"914":5,"915":1,"916":9,"917":1,"918":2,"924":1,"928":2,"929":3,"934":1,"952":1,"956":6,"964":2,"977":3,"983":1,"990":1,"991":1,"992":1,"1019":2,"1031":1,"1033":2,"1045":2,"1050":1,"1055":1,"1056":1,"1057":1,"1058":2,"1060":2,"1068":1,"1092":1,"1105":7,"1113":2,"1142":4,"1149":1,"1150":1,"1154":1,"1179":1,"1187":2,"1191":6,"1192":2,"1193":6,"1197":1,"1213":3,"1214":1,"1215":3,"1216":3,"1232":4,"1234":3,"1236":5,"1237":2,"1239":3,"1255":1,"1279":1,"1307":3,"1308":1,"1309":3,"1310":2,"1321":3,"1332":2,"1336":1,"1338":4,"1339":3,"1341":1,"1355":2,"1357":1,"1368":1,"1372":3,"1374":4,"1375":1,"1378":1,"1387":3,"1390":3,"1393":2,"1394":1,"1395":5,"1396":2,"1398":2,"1399":1,"1410":1,"1412":1,"1426":1,"1431":1,"1436":1,"1480":1,"1504":1,"1511":5,"1529":1,"1531":1,"1547":3,"1605":2,"1639":1,"1651":1,"1671":1,"1689":2,"1703":1,"1722":2,"1725":1,"1732":1,"1736":1,"1738":1,"1742":2,"1743":1,"1792":6,"1804":2,"1824":1,"1877":1,"1882":1,"1884":1,"1886":2,"1887":2,"1917":1,"1921":2,"1922":1,"1924":2,"1926":2,"1949":1,"1951":4,"1952":5,"1953":5,"1954":3,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2086":4,"2094":2,"2109":1,"2125":3,"2129":1,"2131":1,"2176":1,"2184":3,"2185":1,"2187":4,"2205":1,"2253":1,"2264":2,"2265":2,"2277":11,"2283":4,"2290":2,"2292":2,"2293":5,"2303":2,"2304":2,"2337":1,"2338":1,"2339":2,"2394":2,"2497":2,"2530":1,"2540":1,"2549":7,"2572":4,"2586":4,"2587":1,"2588":3,"2590":4,"2597":1,"2607":1,"2622":1,"2665":4,"2688":2,"2762":2,"2763":1,"2764":1,"2766":2,"2768":1,"2775":2,"2802":2,"2810":3,"2813":2,"2815":2,"2829":5,"2834":4,"2836":5,"2845":1,"2855":1,"2866":1,"2868":2}}],["intellisense",{"2":{"2247":1}}],["intended",{"2":{"1176":1,"1792":1}}],["intend",{"2":{"1096":2}}],["intensive",{"2":{"307":1,"1049":1,"2177":2}}],["intentional",{"2":{"2395":1}}],["intentionally",{"2":{"1049":1,"1106":1,"2406":1}}],["intention",{"2":{"1403":1}}],["intent",{"2":{"1":1,"448":1,"624":1,"1382":3,"1403":1,"1405":1,"2376":1}}],["intercepted",{"2":{"2878":1}}],["intercept",{"2":{"2758":2,"2881":1}}],["intercepts",{"2":{"1704":1,"2112":1,"2443":1,"2448":1,"2532":1}}],["interrupted",{"2":{"2113":1,"2535":1}}],["interoperable",{"2":{"1453":1}}],["interoperability",{"2":{"1111":1,"1457":1,"2554":1}}],["internet",{"2":{"1401":1}}],["internals",{"2":{"2795":1}}],["internalsvisibleto",{"2":{"2258":1}}],["internalonly",{"2":{"2489":1}}],["internalrequesthandler",{"2":{"1746":1,"1929":1,"2347":1,"2372":1}}],["internal",{"0":{"261":1,"263":1,"264":1,"351":1,"1715":1,"1747":1,"1930":1,"2344":2,"2347":1,"2368":1,"2372":1,"2489":1},"1":{"262":1,"263":1,"264":1,"265":1,"266":1,"2369":1,"2370":1,"2371":1,"2372":1},"2":{"176":2,"223":2,"261":5,"262":4,"263":3,"264":4,"265":2,"301":1,"320":3,"324":1,"327":1,"369":1,"374":1,"415":3,"418":2,"420":2,"421":2,"422":1,"426":1,"427":1,"428":1,"446":4,"451":2,"453":1,"454":2,"834":1,"836":2,"837":3,"843":1,"848":1,"924":1,"937":1,"940":1,"1011":1,"1105":1,"1107":1,"1117":1,"1133":1,"1204":2,"1208":1,"1237":1,"1254":1,"1329":1,"1345":2,"1348":1,"1351":1,"1398":3,"1504":2,"1518":1,"1704":1,"1745":2,"1746":1,"1747":3,"1748":2,"1792":5,"1834":1,"1839":3,"1863":1,"1887":1,"1909":1,"1911":2,"1929":1,"1930":4,"1931":1,"1967":1,"1969":1,"1971":1,"1972":2,"2242":1,"2265":1,"2271":1,"2303":2,"2306":1,"2329":1,"2344":3,"2346":3,"2347":4,"2370":1,"2372":1,"2419":1,"2430":1,"2434":2,"2438":2,"2481":2,"2482":1,"2489":1,"2495":1,"2522":1,"2545":1,"2717":1,"2759":1,"2767":1,"2809":4,"2811":1,"2812":1,"2813":2}}],["internally",{"2":{"174":1,"263":1,"1084":1,"1102":1,"1219":1,"1305":1,"1869":1,"2284":1,"2344":1,"2432":1}}],["intervention",{"2":{"1155":1}}],["intervals",{"2":{"1158":1,"1623":1,"1951":1}}],["interval",{"0":{"267":1,"2377":1},"1":{"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"278":1,"279":1,"280":1,"281":1},"2":{"92":1,"98":2,"108":1,"110":1,"118":1,"122":1,"133":1,"141":2,"211":1,"214":4,"217":1,"232":1,"267":1,"279":1,"310":1,"566":2,"1067":1,"1071":1,"1098":1,"1150":2,"1214":1,"1232":1,"1234":1,"1447":1,"1451":1,"1454":3,"1460":1,"1464":1,"1511":2,"1521":1,"1523":1,"1527":1,"1532":1,"1535":1,"1722":1,"1731":1,"1743":2,"1764":1,"1769":1,"1775":1,"1792":16,"1837":1,"1917":1,"2047":1,"2060":1,"2205":1,"2210":1,"2253":2,"2375":1,"2376":3,"2377":2,"2380":3,"2381":1,"2502":2,"2634":1,"2635":1,"2765":1,"2814":1}}],["interval>",{"2":{"92":1,"133":4,"1792":1}}],["interesting",{"2":{"1075":1}}],["interested",{"2":{"977":1,"980":1,"990":1,"1403":1,"1443":1}}],["interim",{"2":{"1071":1,"2385":1}}],["interpolation",{"2":{"1070":1,"1102":1,"1852":1,"2383":1}}],["interprets",{"2":{"1792":1}}],["interpreting",{"2":{"1279":1}}],["interpretation",{"2":{"35":1,"43":1,"1792":1,"2224":1}}],["interpreted",{"2":{"32":1,"286":1,"458":1,"460":2,"463":1,"464":1,"1792":4,"1853":1,"1854":2,"1856":2,"2224":1,"2450":1,"2454":2,"2595":2}}],["interfere",{"2":{"989":1}}],["interfacename",{"2":{"2590":2}}],["interface",{"0":{"2369":1,"2590":1},"2":{"720":1,"723":2,"834":1,"872":8,"873":1,"894":1,"920":23,"938":3,"961":2,"985":1,"995":2,"1020":1,"1024":1,"1026":2,"1061":1,"1094":1,"1115":1,"1193":4,"1342":1,"1366":1,"1386":1,"1408":2,"1409":1,"1413":1,"1559":1,"1567":1,"1569":3,"1570":2,"1571":5,"1581":1,"1792":1,"2228":1,"2357":1,"2369":2,"2438":1,"2461":1,"2482":2,"2484":4,"2520":1,"2590":3,"2611":1,"2614":1,"2615":1,"2642":1,"2656":2}}],["interfaces",{"0":{"1043":1,"1571":1,"2484":1},"2":{"381":1,"920":1,"975":1,"984":1,"995":1,"1005":2,"1006":1,"1037":2,"1038":1,"1097":1,"1193":1,"1355":1,"1366":3,"1401":1,"1406":1,"1407":1,"1416":1,"1559":1,"1570":3,"1571":3,"1580":1,"1792":1,"2118":1,"2166":1,"2333":1,"2356":1,"2369":1,"2481":1,"2484":2,"2590":3,"2611":1,"2641":1,"2703":1,"2826":1}}],["interactive",{"2":{"1037":1,"1381":1,"1792":1,"1825":2,"2153":1,"2537":1}}],["interaction",{"2":{"469":1,"2438":1}}],["interact",{"2":{"974":1,"1630":1}}],["inter",{"2":{"871":1}}],["intermediate",{"2":{"871":2,"949":1,"1009":1,"2309":1,"2614":1,"2622":1}}],["intermediary",{"2":{"851":1}}],["intermittently",{"2":{"865":1}}],["intersect",{"2":{"852":2}}],["integrating",{"2":{"1104":1,"1204":1}}],["integrations",{"2":{"1035":1,"1416":1,"1581":1,"1911":1,"2434":1}}],["integration",{"0":{"905":1,"1104":1,"1183":1,"1201":1,"1772":1,"1780":1,"2068":1,"2438":1,"2627":1,"2667":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":2,"1203":2,"1204":2,"1205":2,"1206":1,"1207":1,"1208":1,"1773":1,"1774":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"1":1,"876":1,"880":1,"909":1,"910":1,"986":1,"1006":1,"1036":1,"1048":1,"1064":2,"1098":1,"1106":1,"1127":1,"1181":1,"1206":1,"1207":1,"1303":2,"1322":1,"1323":1,"1343":1,"1402":4,"1405":1,"2055":1,"2164":2,"2232":1,"2257":1,"2258":1,"2266":1,"2393":1,"2438":2,"2452":1,"2459":1,"2627":1}}],["integrated",{"2":{"1126":1,"2537":1}}],["integrates",{"2":{"1105":1,"1122":1,"1156":1}}],["integrate",{"2":{"879":1,"1098":1,"1105":1,"1213":1}}],["integral",{"2":{"872":1}}],["integrity",{"0":{"862":1},"1":{"863":1,"864":1,"865":1},"2":{"841":1,"844":1,"845":3,"855":1,"863":1,"1354":1,"2498":1}}],["integers",{"2":{"952":1,"1464":1,"2376":1}}],["integer",{"0":{"2376":1},"2":{"35":2,"169":1,"188":1,"211":1,"268":1,"370":1,"373":1,"375":2,"378":1,"447":1,"567":1,"582":1,"584":2,"585":3,"915":1,"983":1,"995":1,"1034":1,"1071":1,"1464":1,"1731":1,"1922":1,"2247":1,"2253":2,"2265":1,"2296":1,"2320":1,"2323":1,"2332":2,"2333":2,"2335":2,"2337":3,"2376":1,"2394":1,"2555":1,"2590":1,"2847":1,"2848":1,"2851":1,"2854":1}}],["in",{"0":{"91":1,"158":1,"276":1,"278":1,"306":1,"308":1,"309":1,"363":1,"533":1,"733":1,"765":1,"833":1,"855":1,"867":1,"869":1,"912":1,"940":1,"942":1,"966":1,"986":1,"988":1,"1010":1,"1039":1,"1040":1,"1049":1,"1055":1,"1105":1,"1138":1,"1185":1,"1250":1,"1256":1,"1272":1,"1305":1,"1328":1,"1370":1,"1395":1,"1397":1,"1402":1,"1409":1,"1421":1,"1431":2,"1449":1,"1532":1,"1543":1,"1599":1,"1619":1,"1727":1,"1862":1,"1905":1,"1908":1,"2182":1,"2188":1,"2286":1,"2348":1,"2384":1,"2402":1,"2520":1,"2540":1,"2544":1,"2587":1,"2717":1,"2731":1,"2766":1,"2770":1,"2802":1,"2816":1,"2837":1},"1":{"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"159":1,"160":1,"161":1,"162":1,"163":1,"277":1,"278":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"967":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1041":1,"1056":1,"1257":1,"1258":1,"1259":1,"1260":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1398":1,"1399":1,"1909":1,"1910":1,"1911":1,"2183":1,"2184":1,"2185":1},"2":{"2":1,"12":1,"31":2,"33":2,"37":1,"41":1,"45":2,"51":2,"52":1,"63":3,"74":1,"78":1,"87":2,"92":1,"94":1,"95":1,"96":1,"97":1,"101":1,"102":1,"104":1,"105":1,"106":1,"109":1,"110":1,"118":1,"119":2,"120":1,"121":2,"123":1,"125":1,"134":1,"139":1,"150":2,"165":4,"167":2,"168":4,"173":1,"175":1,"182":1,"186":1,"187":1,"190":1,"197":1,"206":2,"209":1,"212":2,"214":3,"215":1,"216":4,"220":2,"224":1,"226":1,"227":1,"230":1,"238":1,"239":1,"244":1,"253":1,"258":1,"278":3,"279":1,"281":1,"294":1,"296":2,"297":2,"298":4,"300":3,"301":1,"302":1,"303":1,"305":3,"306":4,"307":5,"308":4,"309":5,"310":2,"312":3,"316":1,"317":3,"319":1,"324":1,"337":2,"338":1,"347":2,"349":1,"351":1,"352":4,"353":1,"354":2,"357":1,"362":5,"363":1,"364":2,"368":1,"369":1,"370":1,"377":1,"378":1,"381":1,"383":1,"384":2,"386":1,"387":2,"388":1,"390":4,"395":5,"396":1,"397":1,"407":1,"408":3,"414":1,"415":1,"421":1,"423":1,"426":1,"428":1,"436":2,"439":1,"445":1,"446":2,"448":3,"452":1,"453":3,"455":1,"469":3,"470":1,"472":1,"473":1,"480":3,"493":1,"504":1,"527":1,"529":2,"533":1,"549":1,"556":1,"558":1,"559":1,"560":1,"567":1,"568":1,"570":1,"575":4,"577":2,"582":1,"584":1,"585":1,"586":2,"587":2,"588":1,"589":1,"595":2,"597":1,"614":1,"615":1,"616":1,"617":1,"618":2,"619":1,"621":1,"624":3,"626":2,"650":1,"653":1,"673":1,"679":1,"687":1,"689":1,"690":1,"691":2,"692":1,"694":1,"695":2,"696":1,"699":3,"703":1,"704":3,"705":1,"706":1,"709":1,"711":2,"713":1,"714":3,"715":1,"716":2,"718":1,"720":3,"722":1,"724":1,"737":1,"741":1,"747":3,"748":1,"762":1,"765":1,"768":2,"772":1,"776":1,"778":1,"786":4,"788":1,"801":1,"809":1,"817":2,"818":1,"819":1,"831":1,"832":3,"834":2,"835":4,"836":2,"837":1,"840":5,"841":15,"843":11,"844":15,"845":11,"847":3,"848":18,"849":8,"851":38,"852":14,"854":4,"855":5,"856":1,"857":3,"859":10,"860":10,"861":7,"863":5,"864":8,"865":9,"866":1,"867":1,"868":12,"869":12,"871":11,"872":12,"873":12,"874":11,"875":2,"876":12,"877":2,"878":1,"880":9,"882":1,"884":1,"885":1,"889":1,"891":2,"892":1,"894":1,"902":1,"903":2,"904":2,"905":1,"911":2,"912":2,"913":1,"915":2,"916":3,"917":4,"918":7,"919":7,"920":5,"922":2,"924":1,"926":4,"928":1,"929":1,"933":2,"934":5,"936":1,"941":1,"942":1,"943":1,"945":4,"946":1,"948":5,"949":1,"956":2,"957":1,"958":1,"959":1,"961":2,"963":2,"965":1,"966":2,"967":1,"968":2,"970":1,"971":1,"973":3,"974":2,"977":1,"979":2,"980":2,"982":2,"983":2,"984":1,"985":1,"986":2,"988":2,"990":5,"992":1,"993":2,"994":5,"996":3,"997":2,"998":1,"1005":3,"1007":1,"1008":1,"1009":2,"1010":2,"1015":3,"1016":1,"1017":1,"1020":1,"1021":1,"1022":1,"1023":1,"1026":1,"1027":1,"1029":1,"1031":2,"1035":1,"1036":2,"1037":9,"1038":1,"1039":2,"1040":1,"1041":1,"1042":3,"1044":1,"1046":2,"1048":3,"1049":9,"1052":3,"1054":3,"1055":1,"1056":1,"1061":2,"1064":2,"1065":1,"1066":1,"1067":1,"1068":3,"1069":1,"1070":5,"1071":3,"1073":9,"1074":4,"1075":3,"1076":4,"1078":5,"1079":4,"1080":3,"1081":1,"1082":3,"1083":2,"1084":1,"1086":6,"1090":1,"1091":1,"1094":6,"1096":3,"1097":2,"1098":6,"1099":2,"1100":4,"1101":6,"1102":9,"1104":2,"1105":8,"1106":2,"1107":4,"1108":6,"1109":2,"1111":4,"1113":3,"1115":2,"1121":1,"1122":1,"1126":1,"1127":4,"1129":2,"1130":4,"1132":1,"1133":2,"1134":1,"1135":6,"1139":1,"1140":1,"1141":1,"1143":1,"1147":1,"1149":1,"1150":3,"1151":1,"1155":1,"1157":1,"1162":2,"1164":1,"1173":2,"1174":3,"1176":2,"1179":4,"1181":3,"1182":1,"1183":1,"1184":1,"1185":5,"1188":1,"1189":1,"1191":1,"1193":2,"1195":2,"1196":2,"1197":1,"1198":1,"1199":1,"1200":1,"1202":2,"1203":1,"1204":1,"1205":1,"1206":2,"1207":1,"1208":2,"1209":3,"1212":1,"1213":1,"1217":1,"1218":2,"1220":1,"1221":1,"1231":1,"1232":4,"1237":1,"1239":1,"1241":1,"1247":1,"1251":1,"1254":2,"1255":1,"1258":1,"1262":1,"1263":2,"1266":3,"1270":1,"1271":2,"1275":1,"1276":1,"1277":1,"1278":1,"1279":2,"1281":3,"1283":1,"1305":1,"1316":1,"1318":2,"1322":1,"1324":1,"1326":1,"1327":1,"1328":3,"1332":1,"1335":1,"1340":1,"1341":1,"1343":1,"1346":1,"1350":2,"1351":1,"1352":1,"1353":1,"1354":1,"1356":1,"1357":1,"1358":1,"1360":2,"1362":1,"1365":1,"1366":1,"1367":2,"1368":3,"1370":3,"1371":1,"1372":1,"1374":1,"1376":5,"1378":3,"1379":1,"1381":2,"1382":6,"1383":2,"1384":2,"1385":13,"1386":13,"1388":4,"1389":1,"1390":3,"1391":3,"1392":1,"1393":1,"1394":8,"1395":2,"1396":4,"1397":1,"1398":12,"1399":4,"1400":6,"1401":7,"1402":10,"1403":9,"1404":6,"1405":6,"1406":2,"1407":5,"1408":1,"1409":5,"1412":2,"1413":1,"1414":8,"1416":1,"1417":1,"1418":4,"1419":3,"1420":3,"1421":1,"1422":3,"1423":2,"1424":1,"1426":1,"1427":1,"1429":2,"1430":1,"1431":3,"1432":1,"1433":2,"1434":1,"1435":3,"1436":1,"1437":1,"1439":1,"1441":2,"1442":1,"1443":2,"1444":1,"1447":1,"1451":1,"1453":2,"1454":2,"1457":1,"1458":3,"1460":1,"1464":2,"1465":1,"1467":1,"1470":2,"1472":3,"1484":1,"1486":1,"1490":1,"1491":1,"1492":1,"1499":1,"1502":3,"1511":8,"1513":1,"1515":3,"1519":1,"1523":1,"1527":1,"1528":2,"1532":1,"1535":1,"1537":1,"1554":1,"1555":1,"1556":1,"1558":2,"1564":1,"1567":1,"1569":3,"1570":1,"1571":2,"1575":1,"1579":1,"1581":3,"1589":1,"1596":2,"1604":1,"1607":3,"1609":3,"1612":1,"1616":4,"1619":2,"1620":3,"1623":1,"1624":2,"1628":2,"1639":3,"1644":3,"1654":2,"1655":1,"1661":1,"1664":1,"1671":2,"1674":1,"1684":3,"1685":1,"1697":2,"1699":1,"1701":2,"1703":1,"1704":1,"1708":1,"1716":1,"1722":3,"1727":1,"1733":1,"1738":1,"1741":1,"1743":2,"1745":1,"1746":2,"1753":1,"1754":1,"1759":3,"1762":2,"1764":1,"1792":185,"1799":1,"1804":1,"1806":1,"1811":1,"1813":3,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":2,"1825":2,"1828":1,"1829":1,"1830":2,"1832":1,"1833":1,"1834":1,"1840":2,"1844":1,"1848":1,"1850":3,"1851":2,"1852":1,"1853":1,"1856":1,"1858":1,"1862":1,"1864":2,"1866":2,"1867":1,"1870":1,"1875":1,"1881":1,"1882":2,"1887":1,"1890":1,"1894":1,"1898":4,"1904":1,"1905":2,"1906":1,"1907":1,"1908":2,"1910":1,"1912":3,"1922":2,"1925":3,"1928":1,"1929":2,"1942":1,"1948":2,"1951":1,"1952":1,"1953":1,"1955":1,"1956":1,"1958":1,"1961":3,"1967":1,"1968":1,"1969":1,"1970":1,"1973":1,"1974":3,"1981":1,"1983":1,"1991":1,"1994":1,"2000":2,"2002":1,"2003":2,"2004":1,"2005":2,"2007":3,"2008":1,"2009":1,"2010":4,"2011":1,"2012":1,"2014":2,"2016":1,"2018":1,"2036":2,"2038":3,"2039":1,"2040":3,"2045":2,"2049":2,"2052":2,"2056":2,"2059":1,"2086":2,"2092":1,"2094":2,"2095":2,"2097":3,"2098":3,"2100":1,"2106":2,"2107":1,"2110":2,"2111":1,"2112":2,"2116":1,"2117":1,"2119":1,"2125":4,"2128":1,"2130":1,"2139":1,"2143":1,"2147":1,"2149":1,"2153":2,"2156":2,"2157":2,"2158":3,"2160":1,"2164":4,"2167":3,"2170":3,"2171":2,"2172":2,"2174":1,"2176":3,"2177":10,"2179":2,"2181":3,"2182":1,"2183":1,"2184":1,"2185":2,"2187":3,"2188":2,"2189":2,"2190":4,"2191":1,"2193":1,"2205":1,"2207":1,"2208":1,"2210":1,"2212":1,"2216":1,"2218":1,"2221":5,"2222":3,"2223":2,"2226":1,"2242":1,"2247":4,"2250":2,"2251":1,"2253":1,"2254":7,"2255":6,"2256":3,"2257":2,"2261":2,"2264":6,"2265":8,"2266":3,"2267":5,"2273":1,"2274":4,"2277":2,"2278":1,"2279":1,"2282":5,"2283":1,"2284":2,"2286":1,"2287":1,"2289":2,"2291":1,"2293":1,"2294":1,"2297":5,"2300":1,"2303":1,"2317":2,"2319":1,"2321":1,"2322":2,"2323":3,"2325":1,"2328":2,"2329":1,"2330":1,"2333":2,"2336":2,"2337":2,"2339":2,"2340":2,"2342":2,"2346":5,"2347":3,"2348":2,"2351":1,"2354":2,"2356":1,"2358":1,"2359":1,"2360":1,"2363":1,"2365":1,"2371":2,"2372":8,"2375":2,"2376":2,"2378":1,"2379":2,"2380":4,"2381":1,"2382":3,"2383":2,"2384":3,"2389":7,"2391":1,"2392":2,"2393":1,"2395":1,"2398":2,"2399":1,"2400":1,"2402":1,"2403":1,"2404":1,"2409":1,"2410":2,"2411":1,"2415":2,"2417":2,"2419":3,"2420":2,"2422":4,"2424":3,"2426":1,"2428":2,"2430":1,"2431":2,"2432":2,"2433":2,"2435":2,"2437":3,"2438":6,"2440":2,"2443":1,"2448":2,"2450":1,"2451":1,"2455":1,"2459":1,"2462":3,"2463":2,"2464":1,"2465":2,"2466":1,"2472":1,"2476":2,"2477":1,"2479":1,"2481":9,"2483":2,"2484":2,"2486":4,"2487":1,"2492":2,"2495":2,"2502":4,"2504":2,"2508":1,"2511":1,"2515":1,"2517":1,"2518":2,"2520":1,"2525":2,"2526":1,"2527":5,"2528":3,"2529":3,"2530":3,"2531":6,"2532":4,"2533":7,"2534":3,"2535":5,"2537":8,"2538":2,"2540":2,"2541":3,"2542":3,"2543":3,"2544":1,"2545":2,"2546":3,"2549":2,"2551":2,"2555":6,"2558":4,"2559":1,"2566":1,"2569":1,"2572":4,"2575":2,"2577":1,"2580":3,"2585":1,"2586":3,"2589":2,"2590":3,"2591":1,"2594":1,"2595":1,"2597":1,"2603":1,"2607":4,"2608":2,"2614":1,"2621":1,"2622":2,"2627":1,"2632":1,"2633":2,"2634":5,"2635":5,"2638":2,"2645":1,"2648":3,"2651":1,"2652":1,"2659":1,"2662":2,"2665":1,"2666":1,"2667":1,"2673":1,"2678":1,"2681":3,"2682":1,"2684":1,"2687":3,"2688":1,"2694":1,"2695":1,"2701":1,"2702":1,"2704":1,"2712":1,"2714":1,"2718":1,"2719":2,"2721":1,"2723":2,"2724":2,"2725":1,"2727":2,"2734":1,"2739":2,"2740":1,"2741":3,"2742":3,"2745":2,"2747":1,"2750":3,"2754":1,"2755":1,"2756":1,"2757":1,"2759":2,"2760":1,"2762":3,"2764":1,"2765":1,"2766":2,"2767":1,"2768":1,"2769":1,"2770":1,"2771":1,"2774":2,"2775":1,"2779":1,"2785":1,"2786":1,"2789":2,"2795":4,"2798":4,"2800":1,"2802":5,"2805":1,"2809":3,"2810":1,"2811":1,"2812":1,"2813":1,"2815":2,"2816":1,"2817":1,"2821":1,"2822":3,"2823":2,"2824":5,"2825":3,"2827":1,"2830":1,"2833":1,"2834":1,"2835":1,"2836":2,"2839":1,"2841":2,"2845":2,"2848":2,"2849":1,"2850":3,"2854":1,"2855":1,"2856":1,"2860":4,"2862":3,"2863":2,"2864":1,"2865":2,"2866":1,"2868":6,"2869":4,"2870":1,"2871":3,"2872":2,"2873":3,"2874":2,"2876":2,"2878":2,"2881":4}}],["istext",{"2":{"2621":1}}],["isjson",{"2":{"2621":1}}],["isready",{"2":{"2534":1,"2875":1}}],["isbodyparameter",{"2":{"2518":1}}],["isformattable",{"2":{"2372":1}}],["ispatternmatch",{"2":{"2270":1,"2371":2}}],["isarray",{"2":{"1574":1}}],["isauthenticated",{"2":{"1366":1,"1792":1,"2379":1,"2423":1}}],["iss",{"2":{"1454":1,"1792":2}}],["issuance",{"2":{"2438":1}}],["issuccess",{"2":{"1792":1,"2093":1,"2109":1,"2537":1}}],["issued",{"2":{"1792":1,"1830":1,"2420":1,"2438":1}}],["issuer",{"2":{"1454":2,"1792":3,"1828":1,"2554":2}}],["issue",{"2":{"302":1,"1101":2,"1193":1,"1500":1,"1792":1,"2175":1,"2178":1,"2438":1,"2569":2,"2572":1,"2600":1,"2626":1}}],["issues",{"0":{"1014":1,"1593":1,"1596":1},"2":{"3":1,"133":1,"277":1,"297":1,"576":1,"841":1,"1014":1,"1015":1,"1037":1,"1070":1,"1149":1,"1152":1,"1155":1,"1254":1,"1324":1,"1385":1,"1403":1,"1527":1,"1792":2,"1851":1,"1983":1,"2171":1,"2212":1,"2382":1,"2577":1,"2589":1,"2871":1}}],["issuing",{"2":{"297":1,"2110":1,"2530":1,"2532":1}}],["isearchproductsresponse",{"2":{"1571":3}}],["isearchproductsrequest",{"2":{"1571":3}}],["isearchrequest",{"2":{"1569":2}}],["isendmessagerequest",{"2":{"1317":1}}],["iserror",{"2":{"1041":1,"1824":2,"2481":1}}],["isempty",{"2":{"1026":1,"1366":1}}],["islength",{"2":{"1026":1}}],["isdevelopment",{"2":{"873":1}}],["isvalid",{"2":{"845":1,"863":1,"864":1}}],["isopendictionarysection",{"2":{"2448":1}}],["iso",{"2":{"695":1,"705":2,"848":2,"860":1,"1856":2,"2224":1,"2381":1,"2450":1,"2451":1,"2453":1,"2454":2,"2873":4}}],["isolates",{"2":{"1107":1,"1255":1}}],["isolate",{"0":{"1427":1},"2":{"947":1,"1424":1,"1427":1,"2023":1,"2533":1,"2881":1}}],["isolated2",{"2":{"2873":1}}],["isolated1",{"2":{"2873":2}}],["isolated",{"2":{"695":2,"705":2,"711":4,"715":1,"851":1,"986":1,"1005":1,"1014":1,"1067":1,"1074":1,"1081":1,"1326":1,"1429":1,"1792":1,"2221":1,"2526":1,"2531":1,"2533":2,"2537":1,"2545":1,"2546":1,"2869":1,"2873":2}}],["isolating",{"2":{"843":1}}],["isolation+slow",{"2":{"711":1}}],["isolation",{"0":{"989":1,"1079":1,"2873":1},"2":{"694":1,"697":1,"711":1,"864":1,"876":1,"986":1,"993":1,"1074":2,"1079":3,"1082":1,"1393":1,"1632":1,"2023":1,"2092":1,"2167":3,"2297":2,"2380":1,"2533":1,"2537":1,"2545":2,"2740":1,"2858":1,"2860":1,"2867":1,"2873":5}}],["isnullorempty",{"2":{"2648":1}}],["isnumeric",{"2":{"2621":1}}],["isnan",{"2":{"1431":1}}],["isn",{"0":{"877":1,"1410":1,"2799":1},"2":{"302":1,"304":1,"351":1,"663":1,"831":1,"838":1,"841":2,"857":1,"871":1,"874":1,"876":1,"877":2,"904":1,"948":1,"1005":1,"1079":1,"1104":1,"1140":1,"1150":2,"1281":1,"1385":1,"1421":1,"1428":1,"1429":1,"1431":1,"1522":1,"1527":2,"1792":1,"2394":1,"2432":1,"2751":1,"2868":1}}],["is",{"0":{"375":1,"388":1,"436":1,"564":1,"653":1,"654":1,"834":1,"835":1,"841":1,"868":1,"877":1,"939":1,"993":1,"1006":1,"1039":1,"1065":1,"1075":1,"1080":1,"1304":1,"2378":1,"2395":1,"2709":1,"2711":1,"2712":1,"2741":1,"2742":1,"2744":1,"2757":1},"1":{"389":1,"940":1,"941":1,"942":1,"943":1,"944":1,"1007":1,"1008":1,"1009":1},"2":{"0":1,"1":6,"2":1,"3":1,"7":1,"8":1,"9":2,"16":1,"17":3,"18":1,"19":1,"20":1,"21":1,"22":3,"23":1,"24":1,"32":1,"37":1,"38":1,"39":2,"40":2,"41":3,"46":1,"48":1,"49":1,"50":2,"51":2,"52":1,"60":2,"61":1,"62":1,"63":5,"64":2,"71":1,"72":1,"74":2,"75":2,"80":1,"83":1,"84":1,"85":1,"86":1,"87":1,"94":1,"95":1,"96":1,"97":1,"101":1,"104":1,"105":2,"106":3,"107":1,"108":5,"109":1,"115":1,"116":1,"117":1,"118":1,"119":2,"120":1,"128":1,"129":1,"134":1,"136":1,"137":1,"138":1,"139":1,"140":1,"147":1,"148":1,"149":1,"155":2,"156":1,"157":1,"158":1,"165":1,"168":4,"169":1,"173":1,"174":2,"175":4,"180":3,"182":1,"184":2,"186":5,"187":3,"188":4,"195":1,"196":1,"203":1,"206":2,"207":1,"208":1,"209":2,"211":2,"212":1,"213":4,"214":12,"215":3,"216":1,"245":2,"247":1,"248":1,"249":1,"250":1,"251":3,"252":2,"254":1,"255":1,"256":1,"257":1,"258":1,"263":3,"264":2,"277":3,"278":3,"279":1,"284":1,"286":1,"288":1,"289":1,"290":1,"291":1,"292":1,"296":1,"297":7,"298":5,"299":2,"301":1,"302":2,"303":2,"304":2,"306":2,"307":6,"308":4,"309":3,"312":1,"313":2,"314":2,"317":5,"318":1,"319":5,"320":5,"322":2,"323":2,"324":3,"325":4,"326":1,"327":1,"332":1,"333":1,"334":2,"335":1,"337":1,"342":1,"343":1,"344":1,"347":4,"349":1,"351":1,"352":2,"353":1,"354":2,"357":1,"358":2,"360":3,"361":2,"362":3,"364":2,"365":2,"366":1,"369":2,"370":9,"374":1,"375":5,"376":2,"377":3,"378":1,"383":5,"384":3,"386":3,"387":2,"388":7,"389":4,"390":9,"392":1,"393":1,"394":1,"395":2,"401":1,"402":1,"403":1,"405":1,"406":1,"407":1,"408":3,"409":1,"412":1,"414":2,"415":1,"417":1,"418":1,"419":1,"420":1,"421":2,"422":2,"423":4,"424":2,"426":1,"427":1,"428":1,"436":13,"438":3,"439":4,"441":1,"442":1,"443":1,"444":1,"445":2,"446":6,"447":2,"448":5,"449":1,"451":2,"452":6,"453":3,"454":4,"460":3,"462":3,"463":1,"464":1,"466":1,"467":1,"468":1,"469":1,"476":1,"477":1,"478":1,"479":1,"480":2,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"501":1,"502":1,"503":1,"504":1,"510":1,"511":1,"512":1,"520":1,"521":1,"522":2,"523":1,"527":3,"528":2,"529":3,"531":3,"532":1,"535":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"553":2,"554":2,"555":2,"560":2,"564":3,"567":1,"572":1,"573":1,"574":1,"577":2,"581":1,"582":2,"584":1,"585":1,"586":1,"587":6,"592":1,"593":1,"594":1,"601":1,"602":1,"603":1,"604":1,"609":2,"611":1,"613":1,"614":1,"615":2,"618":1,"619":1,"624":2,"625":1,"631":1,"632":1,"633":1,"636":1,"639":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":6,"650":6,"652":1,"653":3,"654":2,"655":1,"658":2,"660":2,"661":2,"662":4,"663":2,"664":3,"665":1,"666":3,"667":1,"669":1,"675":4,"677":1,"678":1,"679":1,"683":1,"686":3,"687":1,"690":3,"691":1,"693":1,"694":1,"695":2,"700":1,"701":2,"704":4,"706":1,"708":1,"709":2,"710":1,"714":2,"716":1,"722":1,"723":1,"724":2,"732":1,"733":1,"734":2,"735":1,"736":1,"737":1,"745":2,"747":5,"750":3,"751":2,"752":2,"755":2,"756":2,"758":2,"761":2,"762":2,"764":2,"767":2,"768":1,"770":1,"771":3,"772":3,"774":2,"775":5,"776":1,"777":6,"781":1,"782":2,"783":2,"784":2,"785":2,"787":2,"788":2,"789":2,"797":1,"798":1,"799":1,"800":1,"807":2,"809":1,"811":1,"812":1,"813":1,"814":1,"815":1,"817":1,"828":1,"829":1,"831":1,"832":1,"833":6,"834":4,"835":6,"836":3,"837":3,"838":1,"840":1,"841":22,"843":13,"844":19,"845":14,"847":11,"848":13,"849":16,"851":14,"852":41,"853":1,"854":5,"855":3,"856":1,"857":13,"859":12,"860":17,"861":8,"863":9,"864":12,"865":6,"866":4,"868":11,"869":7,"871":10,"872":18,"873":13,"874":8,"875":7,"876":11,"877":3,"880":2,"882":1,"884":1,"885":1,"886":2,"888":2,"892":1,"897":2,"899":1,"900":3,"901":2,"902":4,"904":7,"905":1,"907":1,"909":1,"910":1,"911":1,"913":2,"914":6,"915":4,"916":12,"917":4,"918":6,"919":6,"920":7,"921":3,"924":2,"926":2,"927":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"943":2,"946":2,"947":2,"948":1,"949":3,"953":2,"956":1,"957":1,"960":2,"961":2,"964":1,"965":2,"967":1,"971":1,"974":1,"976":1,"977":2,"978":2,"979":3,"980":3,"982":1,"983":3,"985":1,"986":4,"987":1,"988":3,"990":3,"991":2,"992":1,"993":4,"994":4,"997":1,"998":1,"1005":1,"1008":1,"1014":1,"1015":1,"1017":2,"1019":2,"1021":2,"1024":1,"1026":3,"1030":1,"1032":1,"1033":2,"1034":3,"1038":3,"1039":1,"1040":2,"1042":3,"1043":1,"1044":4,"1045":1,"1046":2,"1047":1,"1048":1,"1049":2,"1050":2,"1051":2,"1054":2,"1055":1,"1057":4,"1063":1,"1064":2,"1065":1,"1067":7,"1068":3,"1069":6,"1070":5,"1073":7,"1074":4,"1075":12,"1076":10,"1077":6,"1078":6,"1079":7,"1080":7,"1081":1,"1082":4,"1084":1,"1086":2,"1088":2,"1090":1,"1094":1,"1095":1,"1096":3,"1097":1,"1098":3,"1101":4,"1102":7,"1103":1,"1105":7,"1106":2,"1108":2,"1111":4,"1113":2,"1121":1,"1122":2,"1123":1,"1127":5,"1128":2,"1129":2,"1132":1,"1133":2,"1134":4,"1135":3,"1136":1,"1137":2,"1138":3,"1139":4,"1141":5,"1142":2,"1143":1,"1149":1,"1150":8,"1153":3,"1154":2,"1155":1,"1157":1,"1158":2,"1159":1,"1160":2,"1161":1,"1163":3,"1174":1,"1176":5,"1178":1,"1179":2,"1180":1,"1181":2,"1183":2,"1185":3,"1188":1,"1189":2,"1190":1,"1192":2,"1196":1,"1198":2,"1200":1,"1203":1,"1209":1,"1210":2,"1212":1,"1214":1,"1220":2,"1228":1,"1231":1,"1232":1,"1234":2,"1239":1,"1247":1,"1253":1,"1254":2,"1278":1,"1280":1,"1281":1,"1304":1,"1305":1,"1308":1,"1309":2,"1310":1,"1312":1,"1313":2,"1314":1,"1315":1,"1318":1,"1321":1,"1323":2,"1325":1,"1326":2,"1329":2,"1331":2,"1332":3,"1337":2,"1338":3,"1339":4,"1342":2,"1345":2,"1347":2,"1348":1,"1351":1,"1352":1,"1355":1,"1358":9,"1360":2,"1362":1,"1363":2,"1366":3,"1368":4,"1369":1,"1370":2,"1372":1,"1375":1,"1376":1,"1378":1,"1379":1,"1381":2,"1382":22,"1384":5,"1385":12,"1386":23,"1387":1,"1388":2,"1389":3,"1390":11,"1391":3,"1392":3,"1393":8,"1394":10,"1395":5,"1396":6,"1398":9,"1399":12,"1400":2,"1401":12,"1402":8,"1403":33,"1404":8,"1405":7,"1406":6,"1407":2,"1408":4,"1409":6,"1410":1,"1412":2,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":5,"1421":6,"1422":4,"1423":3,"1426":3,"1427":4,"1428":1,"1429":3,"1430":2,"1431":8,"1432":2,"1435":3,"1437":1,"1438":1,"1439":1,"1441":2,"1442":1,"1443":1,"1447":2,"1448":1,"1450":1,"1454":2,"1455":1,"1458":2,"1459":1,"1460":1,"1470":1,"1475":3,"1477":3,"1493":1,"1499":1,"1500":1,"1502":1,"1504":1,"1511":6,"1515":1,"1516":1,"1517":1,"1518":1,"1519":1,"1522":2,"1523":2,"1524":1,"1526":1,"1527":2,"1529":5,"1531":2,"1532":3,"1533":3,"1540":2,"1543":1,"1544":2,"1547":2,"1554":1,"1558":3,"1559":2,"1567":3,"1569":7,"1570":1,"1571":1,"1572":1,"1573":1,"1574":2,"1575":1,"1577":1,"1579":1,"1580":1,"1588":1,"1599":3,"1603":1,"1605":2,"1606":1,"1607":1,"1608":2,"1609":1,"1613":2,"1615":2,"1616":1,"1618":2,"1619":2,"1620":4,"1621":1,"1624":1,"1628":1,"1632":3,"1640":1,"1641":1,"1644":1,"1651":2,"1655":2,"1662":1,"1664":5,"1670":1,"1671":2,"1676":1,"1677":1,"1684":1,"1685":1,"1686":2,"1688":2,"1689":2,"1690":1,"1696":1,"1701":1,"1708":1,"1709":1,"1722":2,"1726":1,"1730":3,"1731":2,"1733":1,"1736":2,"1738":4,"1740":3,"1741":1,"1742":3,"1743":5,"1744":2,"1746":2,"1747":1,"1753":2,"1759":6,"1762":1,"1767":3,"1768":2,"1770":3,"1771":1,"1781":1,"1792":267,"1802":2,"1804":1,"1813":2,"1816":1,"1817":1,"1818":1,"1821":1,"1822":3,"1823":6,"1824":5,"1825":6,"1827":1,"1828":1,"1830":3,"1831":2,"1832":2,"1833":5,"1834":1,"1844":2,"1847":5,"1848":2,"1850":4,"1851":3,"1852":2,"1854":1,"1855":1,"1856":1,"1858":1,"1859":1,"1861":1,"1862":1,"1870":2,"1875":1,"1878":1,"1894":1,"1908":1,"1911":2,"1912":8,"1915":1,"1920":2,"1921":2,"1922":2,"1924":7,"1925":3,"1926":2,"1927":1,"1928":1,"1929":5,"1930":2,"1937":1,"1940":1,"1941":1,"1948":1,"1949":2,"1951":2,"1952":1,"1953":1,"1954":1,"1955":1,"1957":4,"1958":2,"1959":1,"1961":6,"1974":3,"1982":1,"1983":1,"2001":1,"2002":2,"2003":1,"2006":2,"2007":1,"2010":2,"2011":2,"2012":1,"2016":1,"2017":1,"2018":2,"2020":1,"2023":1,"2036":1,"2038":1,"2039":1,"2040":6,"2047":2,"2054":1,"2076":1,"2078":1,"2079":2,"2092":2,"2093":1,"2094":2,"2096":1,"2101":1,"2102":1,"2106":5,"2107":2,"2109":4,"2110":3,"2111":4,"2112":4,"2126":2,"2127":2,"2131":1,"2137":2,"2142":1,"2146":1,"2147":1,"2148":1,"2153":2,"2154":2,"2155":2,"2156":3,"2157":4,"2165":1,"2166":1,"2170":1,"2171":3,"2172":1,"2175":2,"2176":3,"2177":6,"2178":2,"2179":1,"2180":3,"2181":1,"2182":1,"2183":4,"2184":4,"2185":1,"2186":1,"2187":4,"2192":1,"2193":8,"2194":1,"2195":1,"2196":5,"2197":4,"2199":3,"2200":3,"2201":1,"2202":3,"2204":2,"2205":3,"2206":2,"2207":2,"2214":2,"2215":2,"2216":1,"2217":1,"2218":1,"2221":2,"2222":3,"2223":3,"2226":1,"2245":1,"2247":5,"2250":1,"2251":1,"2252":2,"2253":3,"2254":1,"2255":11,"2256":7,"2258":1,"2259":2,"2264":3,"2265":10,"2266":3,"2267":6,"2272":2,"2273":2,"2274":2,"2277":5,"2283":3,"2284":3,"2285":1,"2288":3,"2289":1,"2290":3,"2291":4,"2292":2,"2293":5,"2294":3,"2296":5,"2297":3,"2300":1,"2302":2,"2303":1,"2304":3,"2305":1,"2306":1,"2307":2,"2309":1,"2314":4,"2318":4,"2319":1,"2320":2,"2321":1,"2322":1,"2323":6,"2324":1,"2327":1,"2328":1,"2332":7,"2333":6,"2335":2,"2336":1,"2337":7,"2338":1,"2339":3,"2340":2,"2342":1,"2344":3,"2346":3,"2347":1,"2350":2,"2351":1,"2353":1,"2354":2,"2359":1,"2360":5,"2363":1,"2367":1,"2371":1,"2375":4,"2376":1,"2377":1,"2378":4,"2379":7,"2380":5,"2381":2,"2382":6,"2383":4,"2384":2,"2385":1,"2389":5,"2391":5,"2394":5,"2395":3,"2397":1,"2398":3,"2399":1,"2404":1,"2405":4,"2406":1,"2412":2,"2413":1,"2416":4,"2419":1,"2422":3,"2423":5,"2424":6,"2425":1,"2426":1,"2428":2,"2429":1,"2432":5,"2434":2,"2435":2,"2436":2,"2437":2,"2438":4,"2443":2,"2444":1,"2446":1,"2451":1,"2452":5,"2453":1,"2454":1,"2455":2,"2456":1,"2461":3,"2463":1,"2464":1,"2465":2,"2466":8,"2470":3,"2471":1,"2472":1,"2474":2,"2476":4,"2477":6,"2479":1,"2481":14,"2482":2,"2483":3,"2484":1,"2486":3,"2487":1,"2490":1,"2491":2,"2492":3,"2493":2,"2494":1,"2495":2,"2497":2,"2500":1,"2502":5,"2504":3,"2505":1,"2509":3,"2517":2,"2518":2,"2520":5,"2522":2,"2523":1,"2526":2,"2527":5,"2528":9,"2529":8,"2530":6,"2531":6,"2532":8,"2533":6,"2534":2,"2535":5,"2537":15,"2538":2,"2539":2,"2540":11,"2541":3,"2542":2,"2543":6,"2544":1,"2545":3,"2546":1,"2549":16,"2554":1,"2558":3,"2559":1,"2575":6,"2576":1,"2580":5,"2581":4,"2586":3,"2587":3,"2588":2,"2590":3,"2591":3,"2595":1,"2596":1,"2607":3,"2611":2,"2615":1,"2621":1,"2632":5,"2633":4,"2634":11,"2635":2,"2641":2,"2645":1,"2648":1,"2649":1,"2651":1,"2652":1,"2653":2,"2655":1,"2656":3,"2662":2,"2664":4,"2665":3,"2672":1,"2673":1,"2678":3,"2679":1,"2682":1,"2684":1,"2685":1,"2687":1,"2688":3,"2694":2,"2695":2,"2705":1,"2709":1,"2712":2,"2714":1,"2717":1,"2719":1,"2721":2,"2723":2,"2726":1,"2728":1,"2729":2,"2731":2,"2732":1,"2741":1,"2742":1,"2744":1,"2745":1,"2749":1,"2752":1,"2755":1,"2759":1,"2760":1,"2762":5,"2764":4,"2765":3,"2766":4,"2767":5,"2768":3,"2769":1,"2772":1,"2775":1,"2776":2,"2779":2,"2785":1,"2788":1,"2789":1,"2790":1,"2791":1,"2792":1,"2794":5,"2795":4,"2797":2,"2800":1,"2802":1,"2803":2,"2804":2,"2806":1,"2807":3,"2808":1,"2809":7,"2810":1,"2811":2,"2812":3,"2813":4,"2815":5,"2822":2,"2823":3,"2824":5,"2825":1,"2827":1,"2828":8,"2829":7,"2831":1,"2832":2,"2833":4,"2834":3,"2835":5,"2836":2,"2840":6,"2841":1,"2843":1,"2845":7,"2847":2,"2848":2,"2849":1,"2850":2,"2854":1,"2855":1,"2856":1,"2857":2,"2858":2,"2860":1,"2861":1,"2862":6,"2863":1,"2864":7,"2865":7,"2866":1,"2867":1,"2868":6,"2869":4,"2871":3,"2872":2,"2873":5,"2874":1,"2875":1,"2876":1,"2878":3,"2879":1,"2880":1,"2881":1}}],["hstsbuilderextensions",{"2":{"1792":1}}],["hsts",{"0":{"1983":1},"2":{"1792":1,"1980":1,"1983":1}}],["hs256",{"2":{"1454":1,"1460":1,"1792":2,"2375":1,"2554":1}}],["hmacsha512",{"2":{"1657":2,"1663":1,"1792":1}}],["hmacsha256",{"2":{"1657":1,"1792":1}}],["hmac",{"2":{"1657":2,"2438":1}}],["h4",{"2":{"1429":1}}],["href=",{"2":{"961":2,"1061":1,"1200":1}}],["href",{"2":{"961":2,"1413":2,"1572":1,"1581":1,"1792":2}}],["h2>posts",{"2":{"996":1}}],["h2>",{"2":{"938":1,"996":1,"1061":1}}],["h2>login",{"2":{"938":1,"1061":1}}],["hp",{"2":{"922":1}}],["hurry",{"2":{"1254":1}}],["hurt",{"2":{"1170":1}}],["hunt",{"2":{"2394":1}}],["hunting",{"2":{"1193":1}}],["hundreds",{"2":{"872":1,"879":1,"1302":1,"1324":1}}],["hundred",{"2":{"856":1,"867":1,"868":1}}],["huge",{"2":{"918":1,"1385":1,"1391":1,"1399":1}}],["humans",{"2":{"1414":1}}],["human",{"2":{"0":1,"868":1,"912":1,"1225":1,"1382":2,"1402":1,"1792":1,"1875":1,"2056":1,"2389":1}}],["hh",{"2":{"776":2,"889":2,"892":2,"963":3,"1792":8,"1800":1,"1809":2,"1810":1,"1917":1,"2077":2,"2094":1,"2101":1,"2123":2,"2130":4,"2156":1,"2537":1,"2542":1,"2652":2,"2814":1}}],["h1>users",{"2":{"996":1}}],["h1>",{"2":{"539":2,"996":1}}],["h1>hello",{"2":{"539":2}}],["hydrated",{"2":{"854":1}}],["hyphen",{"2":{"382":1,"2334":1}}],["hybridcachewrapper",{"2":{"2462":1,"2495":1}}],["hybridcachemaximumpayloadbytes",{"2":{"1510":1,"1511":1,"1515":1,"1792":1,"2279":2}}],["hybridcachemaximumkeylength",{"2":{"1510":1,"1511":1,"1515":1,"1792":1,"2279":2}}],["hybridcachelocalcacheexpiration",{"2":{"1147":1,"1510":1,"1511":1,"1515":1,"1792":1,"2279":2}}],["hybridcachedefaultexpiration",{"2":{"1147":1,"1177":1,"1510":1,"1511":2,"1515":2,"1792":1,"2279":2}}],["hybridcacheuseredisbackend",{"2":{"1147":2,"1177":1,"1510":1,"1511":2,"1515":3,"1792":2,"2279":2,"2551":1}}],["hybridcache",{"0":{"2274":1,"2279":1,"2495":1},"2":{"1147":1,"1511":1,"1515":4,"1522":1,"1792":2,"2223":1,"2238":1,"2274":4,"2279":2,"2380":1,"2459":1,"2462":2,"2465":1,"2466":1,"2495":2,"2745":1}}],["hybrid",{"0":{"1147":1,"1515":1},"2":{"101":1,"107":1,"835":1,"848":1,"868":1,"1101":2,"1106":1,"1121":1,"1147":4,"1177":1,"1180":1,"1181":1,"1237":1,"1511":6,"1515":3,"1521":1,"1792":10,"1887":1,"2274":3,"2279":1,"2380":4,"2386":1,"2445":2,"2465":1,"2494":1,"2495":4}}],["h5",{"2":{"280":1}}],["htm",{"2":{"1792":1,"2042":1}}],["htmlkey",{"2":{"1792":1,"2073":1,"2075":2,"2080":1,"2651":1}}],["htmlinputelement",{"2":{"1361":1}}],["htmlfooter",{"2":{"965":1,"1792":1,"2073":1,"2075":2,"2080":1,"2651":1}}],["htmlheader",{"2":{"965":1,"1792":1,"2073":1,"2075":2,"2080":1,"2651":1}}],["htmlhtml",{"2":{"938":1,"961":1,"1061":1,"1200":1,"1491":1,"1685":1,"2039":1,"2040":1}}],["htmllink",{"2":{"961":1}}],["htmlenabled",{"2":{"958":1,"965":1,"1792":1,"2073":1,"2075":2,"2080":1,"2651":1}}],["html>",{"2":{"539":4,"965":3,"1685":3,"1792":3}}],["html",{"0":{"677":1,"965":1,"2054":1,"2075":1,"2651":1,"2726":1},"1":{"2076":1},"2":{"161":1,"167":1,"228":1,"539":6,"673":1,"675":2,"677":2,"679":1,"680":1,"682":1,"833":3,"834":1,"851":1,"868":2,"938":1,"957":2,"959":3,"960":2,"961":4,"965":4,"966":4,"967":2,"970":1,"976":2,"1037":1,"1094":1,"1099":1,"1200":1,"1207":1,"1374":2,"1423":1,"1424":1,"1426":2,"1427":2,"1428":1,"1429":1,"1430":1,"1431":9,"1432":2,"1491":1,"1676":1,"1677":1,"1684":1,"1685":1,"1789":1,"1792":25,"1925":1,"1936":1,"1943":1,"2033":1,"2035":1,"2036":3,"2037":1,"2038":1,"2040":2,"2042":2,"2046":1,"2047":3,"2054":2,"2056":2,"2066":1,"2069":1,"2072":1,"2073":1,"2075":7,"2076":3,"2080":2,"2083":1,"2164":3,"2165":1,"2202":2,"2233":1,"2255":4,"2322":1,"2329":1,"2476":1,"2517":1,"2635":6,"2650":1,"2651":2,"2674":1,"2726":1,"2762":4,"2770":1,"2816":1}}],["httpfiles",{"0":{"2555":1},"2":{"2364":1,"2489":1,"2523":1,"2555":2}}],["httpfileoptions",{"2":{"1752":1,"1758":3,"1759":1,"1792":1,"1836":2,"2520":1,"2551":1,"2701":1}}],["httprequest",{"2":{"1957":1,"2379":1}}],["httpprotobuf",{"2":{"1792":1,"1807":1}}],["httppost",{"2":{"1366":1}}],["httpget",{"2":{"1773":2}}],["http3",{"2":{"1609":1,"1792":1,"1993":1,"2661":1}}],["http2",{"2":{"1609":1,"1792":1,"1992":1,"2661":1}}],["httpexception",{"2":{"1366":2}}],["httpcontext",{"2":{"1070":1,"1102":1,"1162":1,"1852":1,"1955":1,"1957":2,"2379":4,"2383":1,"2482":1}}],["httpclienttypes",{"2":{"2372":1}}],["httpclienttypehandler",{"2":{"2372":1,"2504":1}}],["httpclient",{"2":{"1011":1,"2372":1}}],["httpclientoptions",{"2":{"214":1,"216":1,"1022":1,"1721":1,"1732":1,"1735":1,"1792":1,"2222":1,"2264":3,"2267":1,"2346":1,"2372":1,"2502":1,"2761":1,"2765":1,"2769":2,"2771":1}}],["httphttppost",{"2":{"1452":1,"1456":1}}],["httphttp",{"2":{"915":1,"1386":1}}],["httphttpget",{"2":{"521":1,"2555":2}}],["httpsredirection",{"2":{"2558":1}}],["httpsdefaultcert",{"2":{"1792":1}}],["httpsinlinecertstore",{"2":{"1792":1}}],["httpsinlinecertandkeyfile",{"2":{"1792":1}}],["httpsinlinecertfile",{"2":{"1792":1}}],["httpspolicybuilderextensions",{"2":{"1792":1}}],["https",{"0":{"1942":1,"1981":1,"1982":1},"2":{"75":1,"206":1,"207":1,"208":1,"209":2,"211":2,"212":1,"213":3,"214":5,"215":1,"390":1,"394":1,"415":3,"418":2,"420":2,"423":4,"426":1,"427":1,"428":1,"430":1,"436":4,"442":2,"444":2,"446":9,"449":1,"451":2,"452":2,"453":1,"454":2,"455":1,"531":1,"533":1,"841":1,"845":1,"851":1,"970":1,"1017":2,"1019":2,"1023":2,"1026":2,"1030":1,"1032":1,"1033":1,"1034":3,"1105":4,"1107":3,"1109":1,"1117":1,"1118":1,"1119":1,"1184":1,"1199":5,"1202":1,"1207":4,"1225":1,"1345":2,"1347":1,"1348":1,"1380":1,"1398":1,"1426":1,"1430":1,"1431":1,"1448":1,"1449":1,"1466":1,"1611":1,"1635":1,"1640":2,"1646":2,"1648":1,"1666":1,"1676":1,"1677":1,"1691":3,"1692":4,"1693":3,"1694":3,"1695":3,"1697":3,"1704":2,"1705":1,"1714":1,"1718":1,"1726":1,"1730":3,"1731":2,"1733":2,"1736":1,"1738":1,"1740":3,"1742":1,"1743":1,"1792":64,"1812":1,"1833":2,"1875":1,"1900":2,"1907":1,"1911":1,"1920":2,"1921":1,"1924":1,"1926":1,"1931":1,"1937":1,"1942":1,"1946":1,"1963":1,"1978":1,"1979":1,"1980":2,"1981":1,"1982":1,"1983":1,"1984":2,"1986":2,"1987":2,"1988":2,"1989":3,"1995":4,"2091":1,"2118":2,"2121":1,"2162":1,"2254":1,"2255":4,"2257":4,"2264":3,"2267":1,"2283":1,"2286":1,"2288":3,"2290":1,"2303":2,"2306":1,"2308":1,"2429":1,"2434":1,"2483":1,"2502":1,"2549":3,"2580":1,"2632":3,"2633":3,"2634":1,"2703":2,"2706":1,"2760":1,"2762":3,"2764":2,"2765":1,"2766":2,"2768":2,"2781":1,"2782":1,"2783":1,"2784":1,"2792":2,"2810":1,"2811":6,"2812":1,"2813":2}}],["http",{"0":{"23":1,"75":1,"201":1,"223":1,"241":1,"248":1,"264":1,"320":1,"322":1,"394":1,"402":1,"419":1,"443":1,"1010":1,"1012":1,"1016":1,"1017":2,"1019":1,"1035":1,"1137":1,"1269":1,"1275":1,"1292":1,"1423":1,"1426":1,"1492":1,"1720":1,"1723":1,"1724":1,"1726":1,"1728":1,"1746":1,"1751":1,"1757":1,"1846":1,"1924":1,"1926":1,"1983":1,"1992":1,"1993":1,"2195":1,"2264":1,"2287":1,"2305":1,"2345":1,"2346":1,"2347":1,"2502":1,"2504":1,"2505":1,"2518":1,"2529":1,"2759":1,"2761":1,"2843":1,"2865":1},"1":{"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"242":1,"243":1,"244":1,"245":1,"246":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"259":1,"260":1,"1011":1,"1012":1,"1013":2,"1014":2,"1015":2,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1138":1,"1139":1,"1293":1,"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1,"1721":1,"1722":1,"1723":1,"1724":1,"1725":2,"1726":2,"1727":2,"1728":1,"1729":2,"1730":2,"1731":1,"1732":1,"1733":1,"1734":1,"1735":1,"1736":1,"1737":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"1752":1,"1753":1,"1754":1,"1755":1,"1756":1,"1757":1,"1758":1,"1759":1,"1760":1,"1761":1,"1847":1,"2196":1,"2197":1,"2288":1,"2289":1,"2290":1,"2346":1,"2347":1,"2348":1,"2760":1,"2761":1,"2762":1,"2763":1,"2764":1,"2765":1,"2766":1,"2767":1,"2768":1,"2769":1,"2770":1,"2771":1},"2":{"7":2,"8":1,"9":2,"16":2,"17":3,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"37":1,"38":1,"40":1,"44":1,"48":2,"49":1,"55":1,"61":2,"62":2,"63":1,"71":2,"72":1,"74":3,"75":2,"77":2,"83":1,"84":1,"85":1,"86":1,"94":1,"95":1,"96":1,"97":1,"104":2,"105":1,"106":1,"107":1,"115":2,"116":1,"117":1,"118":1,"119":1,"128":2,"129":1,"136":2,"137":1,"138":1,"140":1,"147":1,"148":1,"149":1,"157":1,"165":4,"166":1,"167":1,"171":1,"173":1,"174":3,"175":1,"180":1,"184":3,"187":1,"195":1,"196":1,"197":1,"201":2,"202":4,"203":2,"206":3,"209":1,"210":2,"212":2,"213":2,"214":2,"215":1,"216":5,"217":3,"218":2,"219":2,"223":6,"225":1,"238":1,"239":2,"241":1,"242":1,"243":3,"244":5,"247":2,"248":1,"249":1,"250":1,"251":2,"252":2,"254":1,"255":1,"256":1,"257":1,"258":1,"261":4,"263":2,"264":2,"265":1,"266":4,"277":3,"278":3,"288":2,"289":1,"290":1,"291":1,"292":1,"297":1,"298":2,"300":1,"301":1,"309":1,"312":2,"313":2,"314":1,"320":9,"322":1,"323":1,"324":1,"325":1,"327":2,"332":1,"333":1,"334":1,"335":1,"342":1,"343":1,"344":1,"347":1,"351":2,"352":2,"353":1,"354":1,"360":1,"361":1,"365":1,"366":1,"370":2,"372":2,"374":1,"380":1,"383":3,"384":2,"386":2,"387":2,"388":1,"390":2,"392":1,"394":1,"395":1,"396":1,"397":1,"401":2,"402":1,"403":1,"405":1,"406":1,"408":2,"409":1,"411":1,"413":2,"414":2,"415":2,"418":2,"419":3,"420":3,"421":3,"423":2,"426":1,"427":1,"428":1,"431":2,"434":2,"436":4,"438":2,"439":1,"443":1,"444":1,"445":1,"447":1,"449":1,"451":2,"452":1,"453":3,"454":1,"456":2,"466":1,"469":1,"476":1,"477":1,"478":1,"479":1,"480":2,"487":2,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"497":1,"501":1,"502":1,"503":2,"510":2,"511":1,"520":2,"521":1,"522":2,"523":1,"526":1,"527":2,"529":1,"531":1,"533":1,"535":4,"536":1,"537":1,"539":2,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"553":1,"554":2,"555":1,"562":1,"563":1,"565":1,"566":1,"572":1,"573":1,"574":1,"577":2,"584":1,"585":2,"586":1,"592":2,"593":1,"594":1,"601":1,"602":1,"603":1,"604":1,"611":1,"612":1,"613":1,"614":1,"616":1,"621":1,"622":1,"623":1,"631":1,"632":1,"633":1,"641":2,"642":1,"643":1,"644":1,"645":1,"646":1,"658":1,"659":1,"660":1,"661":1,"662":2,"664":2,"665":2,"677":2,"678":1,"679":1,"686":1,"689":2,"692":1,"694":2,"698":2,"699":2,"702":1,"704":1,"706":1,"709":1,"714":1,"722":2,"723":1,"724":2,"732":1,"733":1,"741":1,"750":1,"764":1,"774":1,"783":1,"785":1,"787":1,"789":1,"797":1,"800":1,"811":2,"812":1,"813":1,"814":1,"815":1,"817":1,"818":1,"819":1,"826":1,"827":1,"828":1,"835":4,"837":1,"866":1,"867":2,"868":4,"871":1,"872":2,"874":1,"876":4,"881":4,"886":1,"899":1,"900":1,"902":1,"904":1,"914":3,"915":3,"916":3,"917":1,"918":1,"920":1,"934":1,"935":1,"936":1,"949":2,"957":1,"960":2,"964":1,"965":1,"966":1,"970":1,"979":2,"980":1,"986":1,"988":1,"990":1,"995":1,"1009":1,"1010":7,"1011":1,"1012":3,"1013":2,"1014":4,"1015":6,"1016":5,"1017":3,"1019":2,"1021":1,"1022":1,"1023":4,"1027":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1035":1,"1036":1,"1037":6,"1038":1,"1039":1,"1041":1,"1042":3,"1045":2,"1047":2,"1055":1,"1057":1,"1067":1,"1068":1,"1069":1,"1073":4,"1074":1,"1077":1,"1078":4,"1080":1,"1082":1,"1086":1,"1094":4,"1095":1,"1098":1,"1100":1,"1103":1,"1104":8,"1105":15,"1106":4,"1107":6,"1108":1,"1111":9,"1113":1,"1126":5,"1135":2,"1136":1,"1137":2,"1138":5,"1139":2,"1140":1,"1141":2,"1142":3,"1143":1,"1149":1,"1150":4,"1154":4,"1158":1,"1161":2,"1163":3,"1167":1,"1176":3,"1179":4,"1180":1,"1181":1,"1189":2,"1196":1,"1199":3,"1211":1,"1214":1,"1232":1,"1234":1,"1236":1,"1237":1,"1254":1,"1255":2,"1258":1,"1264":1,"1266":1,"1269":3,"1275":2,"1280":1,"1281":1,"1308":1,"1309":1,"1310":1,"1320":1,"1321":1,"1323":1,"1325":2,"1331":1,"1332":1,"1337":1,"1338":3,"1339":1,"1340":1,"1341":1,"1345":2,"1347":1,"1348":2,"1350":1,"1358":3,"1362":1,"1368":2,"1369":1,"1370":2,"1371":3,"1372":1,"1373":1,"1374":2,"1375":1,"1376":4,"1377":1,"1378":2,"1380":1,"1385":1,"1386":11,"1387":2,"1389":1,"1390":1,"1391":1,"1393":2,"1394":1,"1395":2,"1396":2,"1398":16,"1399":2,"1401":3,"1403":1,"1405":1,"1406":3,"1407":1,"1408":3,"1410":1,"1412":1,"1413":1,"1414":2,"1421":1,"1422":2,"1423":2,"1424":1,"1426":2,"1427":2,"1431":4,"1432":2,"1433":2,"1434":3,"1446":1,"1447":1,"1458":1,"1471":1,"1475":1,"1480":1,"1482":1,"1484":1,"1489":1,"1497":1,"1504":1,"1529":4,"1531":2,"1532":2,"1533":1,"1547":1,"1549":1,"1567":2,"1569":3,"1584":2,"1599":3,"1632":2,"1639":1,"1642":1,"1664":3,"1668":1,"1670":2,"1671":1,"1672":1,"1673":1,"1674":1,"1684":1,"1704":2,"1705":1,"1711":1,"1719":1,"1720":2,"1722":7,"1723":5,"1726":1,"1727":3,"1728":3,"1732":1,"1733":2,"1736":2,"1739":2,"1740":1,"1741":4,"1743":1,"1744":1,"1745":1,"1746":2,"1747":4,"1748":3,"1750":2,"1751":2,"1753":1,"1754":2,"1756":2,"1757":4,"1758":3,"1759":2,"1775":1,"1782":1,"1788":2,"1789":4,"1792":74,"1796":4,"1800":1,"1802":1,"1807":2,"1813":1,"1817":1,"1823":2,"1824":3,"1825":3,"1827":1,"1834":2,"1835":1,"1836":1,"1840":4,"1846":1,"1862":2,"1864":2,"1868":1,"1882":1,"1884":1,"1886":1,"1887":1,"1901":1,"1902":1,"1903":2,"1906":3,"1907":1,"1908":1,"1911":1,"1912":2,"1914":2,"1915":1,"1917":1,"1918":1,"1920":2,"1921":1,"1922":1,"1923":2,"1924":3,"1925":3,"1926":2,"1928":1,"1929":2,"1930":4,"1933":2,"1935":1,"1937":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1961":3,"1980":1,"1982":1,"1984":2,"1992":1,"1993":1,"1994":1,"1995":3,"2004":3,"2006":1,"2010":2,"2011":1,"2012":1,"2014":1,"2016":1,"2045":1,"2076":2,"2078":1,"2079":2,"2087":1,"2092":1,"2094":1,"2104":1,"2108":1,"2109":4,"2110":2,"2116":1,"2117":1,"2118":2,"2141":1,"2147":2,"2149":1,"2157":1,"2162":2,"2164":10,"2165":3,"2167":1,"2168":1,"2173":1,"2176":2,"2181":1,"2183":1,"2184":1,"2185":2,"2186":1,"2187":3,"2192":3,"2193":10,"2194":2,"2195":2,"2196":6,"2199":3,"2200":3,"2201":1,"2202":3,"2204":2,"2205":3,"2206":2,"2207":2,"2209":1,"2214":2,"2215":2,"2216":1,"2217":1,"2218":1,"2221":2,"2222":9,"2223":2,"2230":1,"2239":1,"2242":2,"2247":1,"2254":4,"2255":7,"2258":1,"2264":19,"2277":5,"2282":5,"2283":2,"2286":1,"2287":3,"2288":1,"2289":4,"2292":2,"2294":1,"2302":2,"2303":1,"2304":1,"2305":2,"2306":1,"2319":6,"2320":1,"2322":3,"2329":4,"2330":1,"2332":1,"2333":1,"2337":1,"2338":1,"2339":3,"2342":1,"2344":6,"2346":6,"2347":5,"2348":3,"2354":1,"2363":1,"2366":2,"2367":2,"2372":1,"2380":1,"2384":1,"2389":1,"2391":2,"2407":1,"2422":1,"2428":1,"2432":3,"2434":1,"2451":1,"2463":1,"2472":1,"2481":9,"2482":3,"2483":2,"2489":4,"2493":1,"2500":4,"2502":2,"2504":6,"2505":1,"2509":3,"2510":2,"2512":2,"2513":1,"2515":1,"2517":2,"2518":4,"2519":1,"2520":4,"2521":1,"2522":1,"2523":4,"2525":1,"2527":2,"2528":1,"2529":8,"2530":5,"2531":1,"2536":1,"2537":1,"2539":1,"2540":1,"2543":1,"2546":1,"2549":9,"2555":1,"2575":3,"2580":2,"2581":5,"2591":2,"2615":1,"2632":5,"2633":2,"2634":1,"2635":2,"2651":1,"2652":1,"2653":2,"2655":1,"2656":1,"2665":2,"2699":1,"2701":1,"2702":1,"2703":2,"2721":3,"2722":1,"2723":2,"2726":1,"2731":1,"2733":1,"2736":1,"2742":1,"2759":6,"2760":3,"2761":1,"2762":2,"2763":1,"2764":1,"2766":4,"2767":3,"2768":1,"2769":1,"2770":2,"2771":2,"2772":2,"2774":2,"2775":2,"2795":1,"2804":1,"2808":1,"2809":5,"2810":2,"2811":1,"2812":3,"2813":2,"2814":1,"2815":2,"2816":1,"2817":1,"2821":1,"2822":2,"2823":3,"2824":6,"2825":2,"2827":1,"2829":2,"2834":4,"2836":3,"2841":1,"2842":1,"2843":2,"2845":1,"2846":1,"2849":1,"2850":1,"2856":1,"2857":1,"2858":1,"2861":2,"2862":1,"2863":1,"2865":6,"2866":1,"2869":2,"2878":1,"2880":1}}],["hindsight",{"2":{"1078":1}}],["hinted",{"2":{"2452":1}}],["hints",{"0":{"2336":1,"2490":1,"2847":1},"2":{"1134":1,"1237":1,"1377":1,"1792":1,"1887":1,"2546":1,"2858":1}}],["hint|",{"2":{"663":2}}],["hint",{"0":{"646":1,"1316":1,"2490":1},"2":{"646":7,"650":1,"663":1,"664":1,"665":1,"666":1,"669":1,"1111":1,"1305":1,"1316":2,"1326":1,"2223":3,"2391":1,"2490":8,"2498":1,"2540":1,"2663":1,"2734":1,"2833":6,"2834":2,"2845":1}}],["hierarchical",{"2":{"918":2,"920":1,"1037":1,"1097":1,"1792":1,"2832":1}}],["hierarchies",{"2":{"857":1}}],["hierarchy",{"2":{"860":1,"2678":1,"2689":2,"2692":1,"2695":1}}],["hibernate",{"2":{"857":1,"860":1}}],["himself",{"2":{"852":1}}],["his",{"2":{"843":1,"859":2,"913":1}}],["history",{"0":{"1310":1},"2":{"310":1,"860":1,"867":1,"872":2,"1303":1,"1307":1,"1435":1,"1437":1,"2836":5}}],["historical",{"0":{"1067":1},"2":{"106":1,"1066":1,"1067":2,"1101":1,"1121":1,"1150":2,"1205":1,"1519":1,"1525":1,"1529":1,"2381":1}}],["hit",{"2":{"1076":1,"1141":1,"1180":1,"1332":1,"1338":1,"1342":1,"1381":1,"1516":1,"1961":1,"2265":1,"2379":1,"2391":1,"2421":1,"2438":1,"2463":1,"2495":1,"2506":1,"2580":1,"2815":2,"2817":1}}],["hitting",{"2":{"480":1,"967":1,"1147":1,"1363":1,"1515":1,"1792":2,"2274":1}}],["hits",{"2":{"436":1,"1180":1,"1338":1,"1339":1,"1342":1,"1430":1,"1792":1,"1961":1,"2835":1}}],["hid",{"2":{"2395":1}}],["hidden",{"0":{"2452":1},"2":{"348":1,"354":1,"355":1,"529":1,"859":1,"874":1,"1403":1,"1489":1,"1491":2,"1792":3,"2039":1,"2432":2,"2435":1,"2487":1}}],["hiding",{"2":{"174":1}}],["hide`",{"2":{"1792":1}}],["hides",{"2":{"320":1,"849":1,"876":1,"1376":1,"1792":2,"1832":1}}],["hide",{"0":{"351":1,"354":1},"2":{"171":1,"177":1,"181":1,"199":1,"223":1,"327":1,"347":2,"348":5,"349":1,"351":2,"354":3,"355":5,"688":1,"848":1,"1833":1,"1834":1,"1910":1,"1913":1,"2225":1,"2419":1,"2432":2,"2435":2,"2481":2,"2487":1}}],["highlighted",{"0":{"2662":1},"2":{"2673":1,"2678":1,"2694":1,"2695":1,"2785":1}}],["highlighting",{"2":{"1609":1,"2662":1,"2667":1,"2785":1}}],["high",{"0":{"1135":1,"1168":1,"1172":1,"1177":1,"1263":1},"1":{"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":2,"1174":2,"1175":2,"1176":2,"1177":2,"1178":2,"1179":1,"1180":1,"1181":1,"1182":1},"2":{"88":1,"844":1,"969":1,"1035":1,"1036":1,"1037":1,"1135":2,"1141":1,"1152":1,"1164":2,"1167":1,"1205":1,"1206":1,"1265":1,"1266":1,"1280":1,"1316":1,"1323":1,"1324":2,"1327":1,"1329":1,"1351":1,"1363":1,"1516":1,"1625":1,"1792":3,"1820":1,"2024":1,"2088":1,"2089":2,"2265":1,"2270":1,"2398":1,"2632":1,"2789":2,"2803":1}}],["higher",{"2":{"87":1,"88":1,"869":1,"1132":1,"1193":1,"1254":1,"1394":1,"1403":1,"1429":1,"1645":1,"1792":1,"2251":1,"2588":1}}],["highest",{"2":{"52":1,"319":1,"1278":2,"1285":1,"2481":1,"2681":1,"2691":1}}],["h",{"2":{"38":1,"40":1,"61":1,"62":2,"269":1,"1088":1,"1792":1,"2077":1,"2211":1,"2785":1,"2786":1}}],["hacks",{"2":{"1396":1}}],["hack",{"2":{"1395":2,"1396":1}}],["hacky",{"2":{"1066":1,"1394":2}}],["halves",{"2":{"1423":1}}],["halt",{"2":{"1324":1,"1792":1}}],["half",{"0":{"2741":1},"2":{"856":1,"1278":1,"1400":2,"1437":1,"1442":1,"2212":1,"2829":1,"2868":1}}],["halfway",{"2":{"715":1}}],["ha",{"2":{"1177":1}}],["haunted",{"2":{"1079":1}}],["haunting",{"2":{"913":1}}],["harness",{"2":{"986":1,"1075":1,"2465":1}}],["hardwareconcurrency",{"2":{"1792":2}}],["hardware",{"2":{"1086":1,"1217":1,"1230":2,"1255":1,"1792":3,"1880":2,"2270":1}}],["hardcoding",{"0":{"888":1}}],["hardcoded",{"2":{"880":1,"1111":1,"2496":1,"2576":1}}],["hardcode",{"2":{"878":1,"879":1,"960":1,"1111":1}}],["harden",{"2":{"2438":1}}],["hardening",{"0":{"2401":1},"1":{"2402":1,"2403":1,"2404":1,"2405":1},"2":{"2226":1,"2498":1}}],["hardest",{"2":{"864":1}}],["harder",{"2":{"845":1,"873":1,"875":1,"876":2,"1014":1,"1075":1,"1119":1,"1774":1}}],["hard",{"2":{"849":1,"857":1,"1075":2,"1076":1,"1378":1,"1394":1,"2112":1,"2157":2,"2221":1,"2428":1,"2532":1,"2534":1,"2543":2,"2758":1,"2871":1,"2881":1}}],["hater",{"2":{"1386":1}}],["hatch",{"2":{"857":1,"861":1,"1856":1,"2224":1,"2455":1}}],["hatches",{"2":{"857":1}}],["hat",{"2":{"857":1}}],["hang",{"2":{"1382":1,"2362":1}}],["hangs",{"2":{"852":1}}],["handy",{"2":{"2752":1}}],["handshake",{"0":{"2393":1},"2":{"1303":1,"1320":1,"1792":3,"1818":1,"1819":1,"1820":1,"2226":1,"2393":1}}],["hands",{"2":{"1044":1,"2160":1,"2479":1,"2759":1,"2774":1,"2810":1}}],["handed",{"2":{"876":1,"2430":1}}],["handling",{"0":{"389":1,"424":1,"458":1,"468":1,"549":1,"777":1,"1092":1,"1099":1,"1111":1,"1171":1,"1279":1,"1472":1,"1668":1,"1853":1,"1856":1,"2253":1,"2255":1,"2307":1,"2328":1,"2487":1,"2594":1,"2663":1},"1":{"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1,"550":1,"551":1,"552":1,"553":1,"554":1,"555":1,"556":1,"557":1,"558":1,"1473":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":1,"1675":1,"1676":1,"1677":1,"1678":1,"1679":1,"1680":1,"1681":1,"1854":1,"1855":1,"2595":1,"2596":1},"2":{"74":1,"140":1,"141":1,"192":1,"198":1,"200":1,"226":2,"227":2,"234":1,"235":1,"408":2,"458":1,"459":1,"460":1,"462":1,"466":2,"467":1,"468":1,"469":1,"471":1,"472":1,"525":1,"526":2,"529":1,"544":1,"549":1,"550":2,"557":1,"558":1,"569":1,"579":1,"617":1,"807":1,"834":1,"879":2,"880":2,"884":1,"901":1,"909":1,"910":1,"968":1,"995":1,"1011":2,"1026":1,"1037":1,"1111":3,"1118":1,"1121":1,"1127":2,"1151":1,"1164":1,"1165":1,"1181":1,"1255":1,"1258":1,"1269":1,"1280":2,"1317":1,"1320":2,"1350":1,"1366":2,"1394":2,"1407":1,"1410":1,"1466":1,"1468":1,"1493":1,"1507":1,"1518":1,"1550":1,"1558":1,"1567":1,"1568":1,"1586":1,"1668":1,"1679":1,"1681":1,"1686":1,"1700":1,"1738":1,"1784":1,"1787":2,"1789":1,"1791":1,"1792":4,"1794":2,"1796":1,"1798":1,"1835":1,"1854":1,"1855":2,"1864":2,"1865":2,"1928":1,"1965":1,"2031":1,"2122":1,"2124":1,"2137":1,"2151":1,"2222":1,"2230":1,"2240":1,"2255":2,"2258":1,"2265":1,"2273":1,"2284":1,"2320":1,"2360":1,"2372":2,"2417":1,"2481":1,"2491":1,"2518":1,"2549":1,"2559":1,"2575":1,"2595":2,"2596":2,"2614":1,"2615":1,"2665":2,"2701":2,"2705":1}}],["handleasync",{"2":{"2615":3}}],["handleparameterrename",{"2":{"2372":1}}],["handlecommentline",{"2":{"2223":1,"2482":1}}],["handleconnectionerror",{"2":{"1320":1}}],["handle~",{"2":{"1692":1,"1792":1}}],["handleerror",{"2":{"1582":1}}],["handled",{"2":{"904":1,"1049":1,"1101":1,"1106":1,"1394":1,"1792":1,"2555":1,"2607":1,"2632":1,"2666":1,"2767":1}}],["handler",{"0":{"746":1,"749":1,"754":1,"759":1,"769":1,"782":1,"784":1,"786":1,"788":1,"891":1,"892":1,"903":1,"904":1,"2075":1,"2077":1,"2126":1,"2127":1,"2128":1,"2130":1,"2664":1},"1":{"750":1,"751":1,"752":1,"753":1,"755":1,"756":1,"757":1,"758":1,"760":1,"761":1,"762":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"770":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"783":1,"785":1,"787":1,"789":1,"2076":1,"2078":1,"2079":1,"2129":1,"2131":1},"2":{"393":1,"396":1,"421":1,"445":1,"743":1,"745":4,"746":2,"747":7,"748":3,"749":1,"753":8,"757":11,"762":1,"768":1,"772":1,"776":1,"777":1,"781":4,"869":1,"881":1,"892":2,"902":3,"903":1,"904":8,"910":1,"1325":1,"1355":3,"1358":5,"1359":3,"1360":1,"1367":1,"1398":1,"1746":1,"1792":8,"1929":1,"2075":1,"2077":1,"2106":1,"2112":1,"2124":1,"2125":1,"2126":2,"2127":2,"2128":2,"2130":2,"2232":1,"2233":2,"2346":1,"2347":2,"2364":2,"2372":1,"2384":1,"2421":1,"2426":1,"2435":1,"2482":3,"2487":1,"2532":1,"2537":1,"2554":1,"2615":1,"2648":1,"2649":6,"2664":5}}],["handlers",{"0":{"902":1,"1356":1,"2125":1,"2572":1,"2664":1},"1":{"903":1},"2":{"160":1,"164":1,"747":1,"748":1,"777":1,"779":1,"780":1,"781":1,"790":1,"791":1,"793":1,"878":1,"889":1,"892":1,"902":2,"903":2,"904":1,"1099":1,"1356":1,"1358":1,"1359":1,"1367":1,"1792":11,"2074":1,"2080":1,"2082":1,"2122":1,"2125":3,"2134":1,"2232":1,"2329":1,"2332":1,"2365":1,"2372":1,"2482":1,"2504":1,"2550":1,"2615":2,"2664":1,"2791":1}}],["handles",{"2":{"379":1,"801":1,"871":1,"904":1,"991":1,"1015":2,"1088":1,"1098":1,"1099":1,"1100":1,"1102":1,"1152":1,"1153":1,"1171":3,"1174":1,"1180":1,"1181":1,"1211":1,"1218":1,"1276":1,"1279":1,"1309":1,"1331":1,"1366":1,"1410":1,"1419":1,"1477":1,"1868":2,"2333":1,"2356":1,"2357":1,"2370":1,"2381":1,"2543":1}}],["handle",{"2":{"63":1,"72":2,"429":1,"666":1,"879":1,"908":1,"968":1,"984":1,"1007":1,"1079":1,"1088":1,"1092":1,"1097":1,"1123":1,"1135":1,"1167":2,"1169":1,"1213":1,"1214":6,"1215":3,"1220":1,"1221":1,"1232":9,"1237":1,"1240":1,"1249":1,"1281":1,"1303":2,"1320":2,"1338":1,"1339":1,"1378":1,"1408":1,"1792":6,"1882":1,"1887":1,"1889":1,"2087":1,"2247":1,"2310":1,"2313":1,"2433":1,"2611":1,"2632":1,"2810":1,"2815":1}}],["hand",{"2":{"0":1,"1":1,"832":1,"834":1,"835":2,"843":1,"847":1,"848":1,"860":2,"861":1,"864":1,"867":3,"869":3,"872":4,"873":2,"875":1,"910":1,"976":1,"996":1,"1004":1,"1006":1,"1040":1,"1043":1,"1046":1,"1083":1,"1181":1,"1382":1,"1388":1,"1390":1,"1400":1,"1403":1,"1404":1,"1409":1,"1419":2,"1435":1,"2182":1,"2481":1,"2813":1,"2830":1,"2836":1}}],["had",{"2":{"851":2,"861":1,"864":1,"1082":1,"1254":1,"1385":1,"1398":1,"1400":1,"1402":4,"1403":2,"1409":1,"2282":1,"2314":1,"2389":1,"2391":1,"2405":1,"2456":1,"2493":1,"2495":1}}],["happiness",{"2":{"1386":1}}],["happily",{"2":{"848":1,"1073":1,"1394":1}}],["happy",{"2":{"1082":1,"1385":1,"2112":1,"2532":1}}],["happen",{"2":{"872":1,"922":1,"942":1,"1032":1,"1081":1,"1130":1,"1180":1,"1386":2,"1416":1,"1422":1,"2402":1}}],["happened",{"2":{"866":1,"872":1,"2395":1,"2411":1,"2454":1,"2802":1}}],["happening",{"2":{"863":1,"2824":1}}],["happens",{"0":{"1023":1},"2":{"214":1,"310":1,"335":1,"587":1,"835":1,"855":1,"860":1,"865":1,"982":1,"996":2,"997":1,"1165":1,"1210":1,"1305":1,"1338":2,"1403":1,"1409":1,"1524":1,"1743":1,"2176":1,"2580":1,"2807":1,"2829":1,"2860":1}}],["having",{"2":{"663":1,"941":1,"1388":1,"2394":1}}],["haven",{"2":{"1073":1,"1443":1}}],["have",{"0":{"910":1,"1076":1,"1435":1,"2749":1},"1":{"911":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1},"2":{"3":1,"21":1,"51":1,"87":2,"167":1,"175":1,"179":1,"209":1,"307":1,"376":1,"377":1,"454":1,"512":1,"636":1,"646":1,"650":1,"684":1,"747":1,"834":1,"837":1,"841":7,"843":4,"844":8,"845":7,"847":1,"848":4,"849":1,"851":6,"852":3,"856":1,"857":2,"859":1,"861":1,"864":1,"865":1,"869":1,"872":3,"873":2,"876":1,"893":1,"913":2,"915":1,"916":1,"918":3,"919":1,"920":2,"922":1,"933":1,"974":1,"982":1,"990":1,"995":1,"1037":2,"1065":1,"1067":1,"1073":2,"1074":1,"1075":2,"1076":1,"1079":1,"1105":1,"1107":1,"1134":1,"1165":1,"1206":1,"1221":1,"1304":1,"1305":1,"1354":1,"1378":1,"1382":2,"1385":11,"1386":10,"1388":1,"1389":2,"1390":1,"1391":3,"1392":4,"1393":4,"1394":2,"1396":2,"1397":1,"1398":2,"1399":3,"1401":3,"1403":2,"1404":2,"1405":1,"1428":1,"1431":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1516":2,"1522":1,"1568":1,"1690":1,"1706":1,"1781":1,"1792":7,"1813":1,"1825":1,"1827":1,"1856":1,"1922":1,"1942":1,"1961":1,"1973":1,"1983":1,"2140":2,"2141":1,"2156":1,"2161":1,"2177":1,"2199":1,"2254":1,"2255":1,"2265":2,"2279":1,"2285":1,"2289":1,"2333":1,"2339":1,"2378":1,"2380":1,"2389":1,"2400":1,"2414":2,"2425":1,"2452":1,"2468":1,"2483":1,"2509":1,"2537":1,"2542":1,"2575":2,"2586":1,"2633":1,"2691":1,"2722":1,"2766":1,"2792":2,"2818":1,"2819":1,"2824":1,"2826":1,"2858":1}}],["hasura",{"2":{"2744":1}}],["hasn",{"2":{"1137":1,"1243":1,"1792":1,"2398":1}}],["haskell",{"2":{"1087":1,"1122":2,"1255":1}}],["has",{"0":{"1077":1},"2":{"221":1,"320":1,"336":1,"390":1,"394":1,"408":1,"422":1,"438":1,"446":1,"447":1,"527":1,"567":1,"587":1,"613":1,"663":1,"684":1,"700":1,"703":1,"713":1,"768":1,"786":2,"835":2,"840":2,"841":4,"843":3,"844":2,"848":1,"849":1,"851":1,"852":3,"855":1,"857":2,"860":2,"861":1,"863":1,"864":2,"865":2,"867":1,"868":1,"869":1,"872":2,"873":2,"876":3,"877":1,"891":1,"897":1,"912":2,"922":2,"927":1,"928":1,"929":1,"932":1,"943":1,"945":2,"1004":1,"1006":1,"1012":1,"1014":1,"1036":1,"1042":1,"1051":1,"1053":1,"1061":3,"1065":1,"1067":1,"1068":1,"1075":4,"1077":1,"1078":1,"1081":2,"1090":1,"1094":1,"1097":1,"1098":2,"1099":1,"1100":6,"1101":5,"1102":2,"1105":1,"1106":1,"1108":1,"1122":1,"1126":1,"1135":1,"1137":1,"1165":1,"1166":1,"1191":1,"1263":1,"1278":3,"1324":1,"1326":1,"1331":1,"1332":1,"1337":1,"1360":1,"1382":1,"1384":1,"1385":2,"1395":1,"1398":1,"1403":1,"1404":1,"1405":1,"1409":1,"1417":1,"1423":1,"1458":1,"1511":1,"1521":1,"1523":1,"1527":3,"1571":1,"1575":1,"1589":1,"1609":1,"1671":1,"1696":4,"1792":11,"1822":1,"1824":1,"1825":1,"1832":1,"1852":1,"1917":1,"1920":1,"1921":1,"1922":1,"1928":1,"1929":1,"1957":1,"2010":1,"2092":1,"2109":2,"2111":1,"2155":1,"2258":1,"2264":1,"2265":1,"2336":1,"2366":3,"2375":1,"2379":2,"2380":2,"2383":1,"2389":1,"2392":2,"2393":1,"2398":1,"2422":1,"2438":1,"2446":1,"2459":1,"2482":1,"2484":1,"2489":1,"2532":1,"2537":2,"2544":1,"2549":2,"2566":1,"2600":1,"2608":1,"2611":1,"2621":1,"2665":1,"2673":1,"2789":1,"2794":2,"2797":1,"2814":1,"2824":2,"2825":1,"2839":1,"2856":1,"2868":1,"2871":1}}],["hashset",{"2":{"2614":2}}],["hashkeythreshold",{"2":{"1067":2,"1510":1,"1511":2,"1516":1,"1792":2,"2265":3,"2495":1}}],["hashing",{"0":{"1052":1,"1195":1,"1516":1},"2":{"307":3,"308":3,"363":1,"868":1,"921":1,"924":1,"927":1,"928":2,"942":1,"944":1,"945":2,"946":1,"1048":1,"1049":3,"1052":1,"1064":1,"1098":2,"1307":1,"1516":1,"2177":4,"2188":1,"2265":2}}],["hashcolumnname",{"2":{"300":1,"309":2,"1056":1,"1062":1,"1469":1,"1471":1,"1483":1,"1792":4}}],["hasher",{"0":{"309":1,"363":1,"1049":1},"2":{"63":1,"307":2,"309":2,"312":1,"316":1,"362":1,"363":1,"364":1,"366":1,"367":1,"1048":1,"1049":4,"1052":1,"1055":1,"1472":1,"1792":2,"2177":2,"2188":1}}],["hashes",{"0":{"57":1,"1051":1},"2":{"57":1,"62":1,"924":2,"928":1,"940":1,"942":1,"1049":3,"1051":2,"1067":1,"1133":1,"1251":1,"2177":1,"2495":1}}],["hashed",{"2":{"48":2,"49":1,"56":1,"308":1,"357":1,"358":2,"361":1,"362":1,"366":1,"368":2,"1052":2,"1133":1,"1196":1,"1499":1,"1502":1,"1511":2,"1516":1,"1518":1,"1792":4,"2156":1,"2265":4,"2542":1,"2878":1}}],["hash>",{"2":{"56":1}}],["hash",{"0":{"308":1,"309":1,"357":1,"928":1},"1":{"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1},"2":{"33":3,"37":2,"38":2,"56":2,"57":2,"61":2,"62":2,"190":2,"237":2,"297":2,"298":4,"299":1,"300":3,"308":13,"309":12,"312":6,"313":1,"316":2,"357":2,"358":2,"360":8,"361":6,"362":1,"364":5,"365":5,"366":3,"369":3,"375":1,"385":2,"592":3,"686":1,"813":1,"857":1,"860":1,"868":1,"922":1,"924":1,"927":2,"928":2,"929":1,"930":33,"934":1,"938":3,"1049":3,"1050":3,"1051":3,"1052":2,"1055":4,"1056":1,"1062":1,"1068":1,"1189":1,"1195":3,"1196":1,"1197":3,"1307":2,"1308":2,"1332":2,"1336":3,"1338":8,"1339":9,"1371":1,"1458":2,"1469":1,"1471":2,"1480":2,"1483":1,"1499":1,"1504":6,"1792":5,"2147":2,"2156":1,"2176":2,"2177":8,"2178":2,"2180":1,"2187":1,"2265":2,"2332":2,"2540":2,"2542":1,"2546":1,"2575":1,"2679":1,"2815":6}}],["hopper",{"2":{"2529":1,"2865":1}}],["hop",{"2":{"1409":1}}],["hope",{"2":{"920":1,"1008":1,"1386":1,"1404":1}}],["holy",{"2":{"1382":1}}],["holding",{"2":{"1824":1,"2157":1,"2543":1}}],["hold",{"2":{"844":1,"851":1,"852":2,"863":2,"864":1,"865":1,"916":1,"1280":1,"2873":1}}],["holds",{"0":{"1046":1},"2":{"305":1,"844":1,"848":1,"855":1,"913":1,"1160":1,"1792":1,"1925":1,"2110":1,"2530":1}}],["horse",{"2":{"1078":1}}],["horizontal",{"2":{"1014":1,"1303":1,"1320":1,"1322":1}}],["horizontally",{"2":{"307":1,"1015":1}}],["hoo",{"2":{"1385":1}}],["hook",{"2":{"1102":2,"1104":1,"1106":1,"2112":1,"2221":1,"2466":1,"2532":1,"2540":1,"2845":1}}],["hooks",{"2":{"1030":1,"2482":1,"2545":1}}],["hood",{"2":{"953":1,"2540":1,"2824":1,"2845":1}}],["hottest",{"2":{"1400":1}}],["hot",{"2":{"859":4,"861":2,"1007":1,"1174":1,"1275":1,"1400":2,"1519":1,"1628":2,"1792":3,"2266":2,"2400":1,"2559":1,"2614":1,"2621":1}}],["home",{"2":{"832":1,"852":1}}],["hosttimezoneindependencetests",{"2":{"2456":1,"2457":1}}],["hostname",{"2":{"1616":1,"1704":1,"1792":2}}],["hosts=true",{"2":{"1175":2,"1176":1,"1177":1}}],["hosts",{"0":{"1709":1},"2":{"1173":2,"1627":1,"1717":1,"1792":3,"1822":1,"1856":1,"1911":1,"2266":1,"2438":1,"2450":2,"2454":1}}],["hostingabstractionswebhostbuilderextensions",{"2":{"1792":1}}],["hosting",{"2":{"1084":2,"1088":2,"1094":4,"1119":1,"1123":1,"1127":3,"1792":2}}],["hosted",{"0":{"1119":1},"2":{"1084":1,"1100":1,"1107":1,"1121":1,"1127":2,"1423":1}}],["host=server1",{"2":{"2266":1}}],["host=analytics",{"2":{"1614":1}}],["host=replica",{"2":{"1614":1,"1771":1,"2063":1}}],["host=replica1",{"2":{"1176":1,"1177":1,"1629":1}}],["host=production",{"2":{"1606":1}}],["host=primary",{"2":{"1173":1,"1176":1,"1177":1,"1614":1,"1627":1,"1629":1,"1771":1,"2063":1,"2266":1}}],["host=http",{"2":{"1386":1}}],["host=host",{"2":{"1117":1}}],["host=",{"2":{"937":1,"1607":1,"1615":1,"1633":2,"1792":1,"2534":3,"2687":1}}],["host=localhost",{"2":{"695":3,"1117":1,"1613":1,"2686":1,"2689":1,"2691":1,"2699":1,"2718":1,"2823":2,"2824":3,"2825":2,"2872":2,"2874":1}}],["host",{"0":{"417":1,"418":1,"420":1,"441":1,"442":1,"444":1,"1173":1,"1555":1,"1626":1,"1627":1,"1629":1,"2266":1,"2306":1,"2451":1},"1":{"1627":1,"1628":1,"1629":1},"2":{"317":1,"347":1,"413":2,"414":3,"417":2,"418":1,"419":1,"420":1,"422":3,"423":5,"430":1,"434":2,"436":12,"441":2,"442":1,"443":1,"444":1,"446":9,"449":1,"452":3,"455":1,"834":1,"867":3,"868":2,"871":2,"872":1,"873":1,"876":2,"877":1,"915":1,"1037":2,"1084":1,"1100":1,"1101":2,"1105":1,"1121":1,"1172":1,"1173":1,"1175":1,"1176":1,"1180":1,"1181":1,"1182":1,"1255":1,"1331":1,"1340":3,"1386":1,"1555":2,"1616":1,"1618":1,"1626":1,"1627":1,"1703":1,"1704":1,"1705":2,"1709":3,"1711":1,"1717":1,"1792":21,"1802":1,"1822":1,"1824":1,"1825":4,"1827":1,"1830":2,"1833":1,"1856":5,"1911":3,"1916":2,"1917":3,"1929":1,"1931":2,"1994":1,"2019":1,"2224":3,"2239":1,"2266":7,"2301":1,"2306":1,"2308":1,"2389":1,"2419":1,"2425":1,"2432":1,"2434":1,"2438":3,"2450":2,"2451":4,"2452":2,"2453":2,"2454":3,"2455":2,"2456":2,"2481":3,"2531":1,"2543":1,"2549":6,"2555":2,"2633":7,"2634":1,"2645":1,"2648":1,"2687":1,"2717":1,"2744":1,"2794":1,"2795":2,"2807":2,"2808":1,"2811":5,"2814":4,"2815":2}}],["honoring",{"2":{"2453":1}}],["honor",{"0":{"2414":1},"1":{"2415":1,"2416":1},"2":{"2225":1,"2438":1,"2466":1}}],["honors",{"2":{"1957":1,"2379":1}}],["honored",{"2":{"313":1}}],["honestly",{"0":{"2465":1},"2":{"869":1,"877":1}}],["honest",{"0":{"876":1},"2":{"1":1,"838":1,"844":1,"852":1,"859":1,"861":1,"869":3,"872":1,"873":1,"876":1,"1254":1,"1385":1,"1400":1,"1402":1,"1404":1,"2388":1,"2537":1}}],["hours",{"2":{"133":1,"269":2,"274":1,"566":2,"861":1,"872":6,"1066":1,"1067":1,"1107":1,"1447":1,"1454":1,"1464":1,"1792":2,"2211":2,"2376":1,"2377":1}}],["hour",{"0":{"96":1},"2":{"92":1,"105":3,"106":2,"107":2,"269":1,"271":1,"272":1,"273":2,"274":1,"275":1,"542":1,"872":1,"1067":2,"1068":3,"1098":1,"1101":1,"1135":3,"1138":1,"1143":1,"1150":3,"1450":1,"1451":3,"1454":1,"1458":2,"1464":2,"1511":1,"1519":1,"1520":2,"1521":1,"1523":1,"1525":1,"1529":4,"1792":9,"2211":1,"2212":1,"2375":2,"2376":1,"2377":1,"2380":3,"2381":1,"2427":1,"2580":1}}],["however",{"2":{"975":1,"1385":1,"1394":1,"1400":1,"2453":1}}],["how",{"0":{"1":1,"221":1,"297":1,"304":1,"349":1,"388":1,"436":1,"448":1,"534":1,"650":1,"870":1,"881":1,"982":1,"1305":1,"1325":1,"1359":1,"1723":1,"1824":1,"1868":1,"2179":1,"2191":1,"2283":1,"2302":1,"2318":1,"2527":1,"2713":1,"2716":1,"2717":1,"2718":1,"2724":1,"2726":1,"2727":1,"2728":1,"2732":1,"2733":1,"2737":1,"2739":1,"2744":1,"2745":1,"2746":1,"2747":1,"2749":1,"2750":1,"2751":1,"2752":1,"2760":1,"2807":1,"2828":1,"2840":1,"2862":1},"1":{"305":1,"306":1,"389":1,"871":1,"872":1,"873":1,"874":1,"875":1,"1360":1,"2180":1,"2181":1,"2192":1,"2193":1,"2194":1},"2":{"3":1,"11":2,"26":2,"42":2,"53":2,"65":2,"76":2,"87":1,"89":2,"98":2,"122":2,"130":2,"141":2,"151":2,"164":2,"189":1,"197":1,"198":2,"217":2,"220":1,"259":2,"281":1,"293":2,"296":1,"306":1,"317":1,"327":1,"338":1,"345":2,"347":1,"356":1,"367":2,"384":1,"385":1,"388":1,"396":1,"410":2,"458":1,"471":2,"481":2,"495":2,"497":1,"505":2,"513":2,"515":1,"525":2,"527":1,"547":2,"549":1,"557":2,"568":1,"578":2,"588":1,"596":2,"605":2,"617":1,"626":1,"634":2,"647":2,"670":2,"673":1,"680":2,"725":2,"740":2,"790":2,"804":2,"820":2,"830":1,"838":2,"841":1,"844":1,"845":1,"848":1,"849":3,"851":3,"868":1,"872":1,"877":1,"912":1,"914":1,"918":1,"921":1,"934":1,"961":1,"963":2,"991":1,"996":1,"1037":2,"1039":1,"1073":1,"1076":1,"1080":1,"1083":1,"1096":1,"1104":1,"1132":1,"1139":1,"1143":1,"1179":1,"1181":1,"1227":1,"1249":2,"1254":1,"1279":1,"1281":1,"1302":1,"1384":3,"1398":1,"1400":2,"1401":3,"1403":6,"1404":1,"1405":3,"1406":1,"1409":1,"1431":1,"1465":2,"1472":1,"1484":2,"1495":2,"1506":2,"1511":1,"1513":1,"1535":2,"1538":1,"1549":2,"1583":2,"1600":2,"1603":1,"1610":2,"1630":1,"1634":2,"1645":1,"1647":2,"1665":2,"1679":2,"1686":1,"1699":2,"1706":1,"1738":1,"1748":2,"1760":2,"1785":1,"1792":12,"1811":2,"1820":1,"1822":1,"1834":1,"1840":1,"1844":1,"1848":1,"1853":2,"1856":1,"1864":2,"1877":1,"1913":2,"1922":1,"1924":1,"1932":2,"1945":2,"1953":1,"1961":1,"1962":2,"1974":1,"1976":2,"1996":2,"2000":1,"2004":1,"2007":1,"2010":1,"2016":3,"2019":1,"2023":1,"2025":1,"2043":2,"2081":2,"2090":2,"2120":2,"2133":2,"2150":2,"2164":1,"2170":3,"2171":1,"2172":1,"2190":2,"2191":1,"2203":1,"2207":1,"2209":1,"2328":1,"2398":1,"2415":1,"2432":1,"2450":1,"2502":1,"2509":1,"2543":1,"2607":1,"2632":4,"2705":1,"2759":1,"2769":1,"2771":1,"2795":1,"2806":1,"2827":1,"2871":2}}],["hex",{"2":{"2495":1}}],["hexagonal",{"2":{"840":1}}],["height",{"2":{"1792":2}}],["hetzner",{"2":{"1086":1,"1094":1,"1121":1,"1255":1}}],["hermetic",{"2":{"2875":1}}],["herd",{"2":{"1515":1}}],["her",{"2":{"913":1}}],["here",{"2":{"0":1,"48":2,"49":1,"386":1,"395":1,"454":1,"664":1,"836":1,"841":5,"843":5,"848":1,"852":3,"857":1,"859":1,"860":1,"864":1,"868":1,"871":2,"873":2,"874":1,"877":1,"913":1,"920":2,"922":1,"926":1,"927":1,"934":1,"968":1,"977":1,"980":1,"986":1,"987":1,"988":1,"990":1,"992":1,"996":2,"997":1,"1067":1,"1074":1,"1075":1,"1076":1,"1080":1,"1081":1,"1128":1,"1165":1,"1169":1,"1179":1,"1181":1,"1196":1,"1210":1,"1214":1,"1231":1,"1309":1,"1366":1,"1368":1,"1372":1,"1378":1,"1382":5,"1384":1,"1386":4,"1389":1,"1393":1,"1396":1,"1402":1,"1404":1,"1409":3,"1420":1,"1424":1,"1435":1,"1436":1,"1437":1,"1633":1,"1792":3,"1898":1,"1995":1,"2220":1,"2431":1,"2765":1,"2829":2,"2830":1,"2834":1}}],["henry",{"2":{"913":1}}],["hence",{"2":{"854":2,"1129":1}}],["hemingway",{"2":{"913":2}}],["he",{"2":{"843":1,"847":6,"851":4,"863":1,"990":1,"1384":1}}],["helm",{"2":{"1420":1}}],["hell",{"2":{"1385":1,"1402":1}}],["hello",{"2":{"378":1,"428":1,"462":1,"463":1,"464":1,"466":1,"487":3,"615":1,"956":1,"977":1,"980":1,"990":1,"1326":1,"1370":2,"1374":1,"2011":1,"2321":1,"2326":1,"2335":1,"2339":1,"2354":1,"2589":3,"2821":3,"2822":1,"2824":1,"2836":1,"2850":2}}],["held",{"2":{"865":1,"1722":1,"1792":1,"2466":1,"2504":1,"2517":1,"2769":1}}],["helpers",{"0":{"1573":1},"2":{"308":1,"867":1,"1101":1,"2372":1,"2391":1,"2448":1}}],["helper",{"0":{"263":1,"264":1},"2":{"263":1,"265":1,"1504":1,"1574":1,"1575":1,"1747":1,"1930":1,"2278":1,"2344":1,"2372":1,"2391":2,"2407":1,"2472":1,"2615":1}}],["helping",{"2":{"51":1,"2696":1}}],["helps",{"2":{"51":1,"595":1,"1155":1,"1404":1,"1619":1,"1792":3,"2016":1,"2632":1,"2633":1,"2693":1}}],["help",{"2":{"0":1,"851":1,"873":1,"912":1,"1097":1,"1385":1,"1400":2,"1401":1,"1402":2,"1792":1,"2663":1,"2679":2,"2785":3,"2786":3,"2788":2}}],["hey",{"2":{"378":1,"1404":1,"1435":1,"2335":1}}],["healing",{"2":{"2534":1}}],["healthz",{"2":{"1780":2}}],["healthy",{"2":{"1335":1,"1766":1,"1781":1,"1782":1,"1792":4,"2634":5}}],["healthcheck",{"2":{"1771":2,"1775":1}}],["healthchecks",{"2":{"868":1,"1763":1,"1769":2,"1770":1,"1771":1,"1776":1,"1778":1,"1779":1,"1780":1,"1781":1,"1792":1,"2634":2}}],["healthcare",{"2":{"1228":1,"1878":1}}],["health",{"0":{"1337":1,"1762":1,"1765":1,"1766":2,"1767":1,"1768":1,"1770":1,"1775":1,"2634":1},"1":{"1763":1,"1764":1,"1765":1,"1766":2,"1767":2,"1768":2,"1769":1,"1770":1,"1771":2,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1,"1778":1,"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1},"2":{"8":1,"10":1,"835":1,"868":2,"869":2,"873":1,"1100":7,"1127":1,"1181":1,"1328":2,"1329":2,"1331":4,"1333":1,"1335":2,"1337":5,"1342":3,"1349":3,"1351":1,"1719":2,"1762":2,"1763":3,"1764":8,"1765":1,"1766":1,"1769":1,"1770":2,"1771":2,"1773":2,"1774":1,"1775":1,"1776":4,"1782":1,"1783":2,"1791":2,"1792":26,"1898":1,"2070":2,"2234":1,"2431":1,"2434":1,"2634":30,"2638":1}}],["heartbeats",{"2":{"2407":1}}],["heartbeat",{"2":{"1320":1}}],["heard",{"2":{"864":1}}],["heavily",{"2":{"1402":1,"1404":1,"2398":1}}],["heaviest",{"2":{"874":1}}],["heavyweight",{"2":{"1075":1}}],["heavy",{"2":{"149":1,"868":1,"872":1,"876":2,"1017":1,"1069":1,"1091":1,"1105":1,"1162":1,"1176":2,"1205":1,"1208":1,"1266":1,"1280":1,"1404":1,"1632":1,"2388":1,"2398":1,"2622":1}}],["heap",{"2":{"848":1,"861":1,"951":1,"2399":1,"2559":1}}],["headless",{"2":{"1432":1}}],["headline",{"2":{"869":1,"871":1,"2397":1,"2479":1,"2500":1}}],["head>",{"2":{"965":2,"1685":2,"1792":2}}],["head",{"2":{"243":1}}],["headertablesize",{"2":{"1792":1,"1992":1}}],["headername",{"2":{"1488":1,"1489":1,"1494":1,"1792":1}}],["headerlines",{"2":{"1416":1,"1417":1,"1553":1,"1565":1,"1581":1,"1792":1}}],["header>",{"2":{"996":2}}],["headers`",{"2":{"1792":1}}],["headers",{"0":{"58":1,"128":1,"129":1,"207":1,"497":1,"501":1,"507":1,"536":1,"540":1,"541":1,"544":1,"545":1,"546":1,"736":1,"1137":1,"1138":1,"1556":1,"1564":1,"1565":1,"1582":1,"1643":1,"1701":1,"1704":1,"1705":1,"1848":1,"1849":1,"1926":1,"2014":1,"2041":1,"2202":1,"2286":1,"2358":1,"2505":1,"2632":1,"2633":1,"2812":1},"1":{"498":1,"499":1,"500":1,"501":1,"502":1,"503":1,"504":1,"505":1,"506":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"537":1,"538":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"546":1,"547":1,"548":1,"1138":1,"1139":1,"1557":1,"1702":1,"1703":1,"1704":1,"1705":1,"1706":1,"1707":1,"1708":1,"1709":1,"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1717":1,"1718":1,"1719":1,"1849":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2028":1,"2029":1,"2030":1,"2031":1},"2":{"58":1,"203":1,"207":1,"210":2,"211":2,"214":2,"215":1,"226":2,"227":2,"229":1,"387":3,"388":1,"390":1,"394":1,"395":1,"396":3,"439":1,"447":2,"453":4,"493":2,"496":1,"497":2,"498":2,"499":3,"501":1,"502":2,"503":8,"505":1,"506":1,"507":2,"508":1,"510":9,"511":3,"512":2,"514":1,"527":2,"536":1,"537":1,"540":1,"541":1,"544":1,"545":1,"546":1,"547":1,"700":1,"736":5,"741":2,"835":1,"868":2,"869":2,"876":1,"995":4,"1010":1,"1016":1,"1017":1,"1026":2,"1031":2,"1063":6,"1100":7,"1101":1,"1104":1,"1105":5,"1106":1,"1107":1,"1109":1,"1127":1,"1137":1,"1138":1,"1181":1,"1221":1,"1284":1,"1340":3,"1341":2,"1348":1,"1363":1,"1373":2,"1386":2,"1408":2,"1415":1,"1416":2,"1475":1,"1492":1,"1564":1,"1567":1,"1582":1,"1620":1,"1639":2,"1643":2,"1644":1,"1701":2,"1703":2,"1704":1,"1707":1,"1708":1,"1709":1,"1712":1,"1713":1,"1716":1,"1717":2,"1719":2,"1721":1,"1722":2,"1725":1,"1730":1,"1731":1,"1732":2,"1733":1,"1736":1,"1738":1,"1743":1,"1784":1,"1788":3,"1792":46,"1807":1,"1836":2,"1848":4,"1849":3,"1852":1,"1857":1,"1862":2,"1863":1,"1864":1,"1916":1,"1917":4,"1918":2,"1921":1,"1922":2,"1926":2,"1928":2,"1957":1,"1991":3,"2014":3,"2016":2,"2030":2,"2031":1,"2033":1,"2037":1,"2038":2,"2041":1,"2093":2,"2109":3,"2110":1,"2185":2,"2193":2,"2202":2,"2207":1,"2222":2,"2234":2,"2247":1,"2264":6,"2282":1,"2286":1,"2329":1,"2358":1,"2379":1,"2383":1,"2483":3,"2486":1,"2493":3,"2502":2,"2505":3,"2529":1,"2530":3,"2537":2,"2545":1,"2549":8,"2551":1,"2566":1,"2580":1,"2581":1,"2627":1,"2632":9,"2633":7,"2701":3,"2762":2,"2763":2,"2765":2,"2769":2,"2775":1,"2806":1,"2810":2,"2812":2,"2814":5,"2835":1,"2856":1,"2865":1,"2866":2}}],["header",{"0":{"533":1,"706":1,"896":1,"1492":1,"1493":1,"1557":1,"1755":1,"1905":1,"2363":1,"2533":1},"2":{"31":2,"38":1,"45":1,"48":1,"51":1,"58":1,"61":1,"63":4,"203":2,"210":1,"212":1,"215":1,"226":2,"346":1,"386":1,"387":2,"390":4,"447":1,"453":2,"480":1,"494":1,"496":1,"514":1,"533":1,"537":3,"540":1,"544":1,"546":1,"606":1,"639":1,"650":1,"668":1,"694":1,"704":1,"706":2,"709":1,"712":1,"714":1,"761":1,"835":1,"888":1,"896":1,"1017":2,"1031":1,"1033":1,"1063":2,"1101":2,"1102":1,"1109":1,"1121":1,"1135":1,"1138":2,"1162":1,"1189":1,"1197":2,"1317":1,"1326":4,"1341":1,"1412":1,"1482":1,"1489":3,"1492":1,"1493":1,"1494":1,"1497":1,"1501":2,"1556":2,"1557":2,"1564":1,"1565":1,"1620":2,"1703":1,"1705":2,"1706":1,"1709":1,"1711":3,"1717":1,"1722":1,"1728":2,"1732":1,"1733":1,"1738":1,"1753":2,"1755":2,"1784":1,"1788":1,"1792":48,"1823":2,"1824":3,"1833":1,"1848":2,"1852":2,"1859":1,"1862":1,"1864":1,"1901":1,"1905":2,"1906":2,"1918":1,"1922":1,"1925":1,"1928":2,"1940":1,"1941":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1957":2,"1980":1,"1983":1,"1994":3,"2005":1,"2006":1,"2016":1,"2018":1,"2031":1,"2097":2,"2111":1,"2174":1,"2185":1,"2193":1,"2202":3,"2250":1,"2254":1,"2264":4,"2282":1,"2283":1,"2286":1,"2323":2,"2329":1,"2330":1,"2358":1,"2363":1,"2379":2,"2383":2,"2391":1,"2421":1,"2422":1,"2435":1,"2438":2,"2481":1,"2483":1,"2493":1,"2505":2,"2531":2,"2533":3,"2537":1,"2546":1,"2549":3,"2626":1,"2632":9,"2633":3,"2726":1,"2763":1,"2764":1,"2768":1,"2812":2,"2833":1,"2841":1,"2869":1,"2870":1,"2873":1}}],["ep",{"2":{"2828":2}}],["epplus",{"2":{"968":1,"969":1}}],["eyjhbg",{"2":{"1455":2,"1456":1,"2554":3}}],["ego",{"2":{"1404":1}}],["eq",{"2":{"1107":1,"1125":1,"1385":1}}],["equal",{"2":{"865":1,"1165":1,"1523":1,"2380":1,"2529":1,"2865":1}}],["equally",{"2":{"320":1,"868":1,"2538":1}}],["equivalents",{"2":{"2376":1}}],["equivalent",{"0":{"869":1},"2":{"17":1,"169":1,"203":1,"262":1,"269":1,"375":1,"564":1,"687":1,"704":1,"709":1,"869":2,"1025":1,"1102":2,"1403":1,"1421":1,"1428":1,"1458":1,"2155":1,"2165":2,"2193":1,"2222":1,"2335":1,"2481":1,"2505":1,"2533":1,"2581":1,"2591":1,"2692":1,"2858":1}}],["europe",{"2":{"2453":1}}],["eur",{"2":{"1023":5,"1024":2,"2764":1}}],["etag",{"2":{"1138":1}}],["ethereum",{"2":{"1023":3,"1024":1}}],["etl",{"0":{"908":1,"1205":1},"2":{"908":1,"1203":1,"1205":1,"1206":2,"1208":1}}],["etc",{"0":{"908":1},"2":{"25":1,"167":1,"175":1,"213":1,"258":1,"354":1,"414":1,"429":1,"447":1,"528":1,"544":1,"567":1,"624":1,"625":1,"761":1,"845":1,"849":1,"869":1,"882":1,"883":1,"915":1,"926":1,"934":1,"1011":1,"1027":1,"1035":1,"1056":1,"1060":1,"1071":1,"1094":1,"1098":3,"1099":1,"1104":1,"1121":1,"1123":1,"1206":1,"1216":1,"1249":1,"1254":1,"1322":1,"1326":1,"1332":1,"1333":1,"1341":1,"1385":3,"1386":5,"1390":1,"1394":2,"1395":1,"1399":1,"1403":1,"1416":1,"1464":1,"1487":1,"1581":1,"1687":1,"1701":1,"1740":1,"1788":3,"1789":1,"1792":9,"1795":2,"1796":1,"1802":1,"1846":1,"1870":1,"1995":1,"2008":1,"2020":1,"2052":1,"2124":1,"2175":1,"2205":1,"2252":2,"2253":2,"2258":3,"2267":1,"2277":1,"2288":1,"2300":1,"2310":1,"2313":1,"2319":1,"2320":1,"2322":1,"2323":1,"2340":1,"2364":1,"2376":1,"2381":1,"2384":1,"2445":1,"2446":1,"2576":1,"2581":1,"2588":1,"2589":1,"2621":1,"2628":2,"2633":2,"2779":1,"2783":1,"2790":1,"2804":1,"2841":1,"2851":1}}],["eight",{"2":{"860":1,"871":1,"1303":1}}],["eighteen",{"2":{"857":1}}],["either",{"2":{"35":1,"177":1,"203":1,"258":1,"299":1,"388":1,"407":1,"435":1,"436":1,"809":1,"832":1,"844":1,"848":1,"852":1,"868":1,"871":1,"872":1,"873":1,"875":1,"904":2,"915":1,"917":1,"1040":1,"1067":1,"1068":1,"1096":1,"1098":1,"1113":1,"1150":1,"1235":1,"1388":1,"1391":1,"1393":1,"1402":1,"1403":1,"1406":1,"1419":1,"1422":1,"1442":1,"1460":1,"1792":5,"1832":1,"1852":1,"1885":1,"1915":1,"1958":1,"2181":1,"2200":1,"2277":1,"2375":1,"2383":1,"2389":1,"2422":1,"2470":1,"2549":1,"2575":1,"2587":1,"2684":1,"2871":1}}],["ef",{"2":{"857":1,"860":1,"871":1,"1255":1,"1257":1,"1265":1,"1277":2,"1279":1,"1281":1,"1284":2,"1285":2,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1293":2,"1295":2,"1297":2,"1299":2,"1301":2,"1382":1,"2534":1,"2874":2}}],["efforts",{"2":{"851":1}}],["effort",{"2":{"713":1,"840":1,"857":1,"920":1,"1792":2,"2094":1,"2112":1,"2532":1,"2533":2,"2871":1}}],["effectively",{"2":{"2190":1,"2346":1}}],["effective",{"2":{"863":1,"873":1,"915":1,"987":1,"2422":1,"2490":1,"2693":1,"2700":1}}],["effect",{"0":{"2464":1},"2":{"453":1,"567":1,"587":1,"684":1,"826":1,"868":1,"872":1,"888":1,"957":1,"974":1,"1040":1,"1189":1,"1415":1,"1559":1,"1571":1,"1645":1,"1792":1,"2092":1,"2338":1,"2432":1,"2484":1,"2487":1,"2694":1,"2835":1,"2868":1,"2870":1}}],["effects",{"0":{"381":1,"826":1},"2":{"823":1,"1106":1,"1309":1,"2333":1,"2338":1,"2853":1}}],["efficiently",{"2":{"1412":1}}],["efficient",{"2":{"87":1,"871":1,"920":1,"1133":1,"1176":1,"1274":1,"1275":1,"1276":1,"1405":1,"1435":1,"1927":1,"1928":1,"2246":1,"2309":1,"2347":1,"2549":2}}],["efficiency",{"0":{"86":1},"2":{"872":1,"1405":1,"1516":1,"2265":1}}],["echoing",{"2":{"2523":1}}],["echo",{"2":{"1792":1,"2111":1,"2456":1,"2532":1}}],["echoes",{"2":{"851":1,"1255":1,"2513":1}}],["ec",{"2":{"1211":1,"1243":2}}],["ec2",{"2":{"1086":1,"1094":1}}],["ecosystem",{"2":{"852":1,"876":1,"1049":1,"2157":1,"2258":1,"2543":1}}],["edb",{"2":{"918":1}}],["editing",{"2":{"872":1,"1323":1,"2670":1}}],["edition",{"2":{"865":1}}],["edits",{"2":{"872":2}}],["edit",{"2":{"871":1,"872":1,"1193":1,"1368":1,"1416":1,"1417":1,"1419":1,"1570":1,"1574":1,"1581":1,"2543":1,"2546":1}}],["editor",{"2":{"14":1,"21":1,"1076":2,"1757":1,"2110":1,"2530":1,"2540":1,"2754":1,"2845":1,"2866":1}}],["edge",{"0":{"1107":1,"1115":1},"2":{"869":1,"873":1,"874":1,"987":1,"1101":2,"1104":1,"1107":2,"1108":2,"1115":2,"1126":2,"1127":1,"1139":1,"1249":1,"2498":1,"2607":1}}],["edges",{"2":{"848":1,"857":1,"2493":1}}],["edgar",{"2":{"848":2}}],["errcodes",{"2":{"1792":3,"2255":4}}],["err",{"2":{"1320":3,"2410":2,"2535":1}}],["error=",{"2":{"1825":1}}],["errortypename",{"2":{"2359":1}}],["errortype",{"2":{"1553":1,"1558":2,"1792":1,"2273":2}}],["errorexpression",{"2":{"1553":1,"1558":1,"1792":1,"2273":2}}],["errored",{"2":{"713":1,"2528":1,"2864":1}}],["errormode",{"0":{"2007":1},"2":{"587":1,"1792":2,"1999":1,"2000":1,"2106":1,"2112":1,"2153":1,"2330":2,"2337":1,"2367":1,"2532":1,"2537":1,"2540":1,"2543":1,"2722":2,"2840":2,"2841":1,"2857":1}}],["errorcodemappingoptions>>",{"2":{"2255":1}}],["errorcodemappingoptions",{"2":{"2255":1}}],["errorcodes=08000",{"2":{"2824":1,"2825":1}}],["errorcodes",{"2":{"577":3,"1111":1,"1152":2,"1153":1,"1154":3,"1177":1,"1587":1,"1589":1,"1597":3,"1598":1,"1617":1,"1622":1,"1623":1,"1625":1,"1633":1,"1669":1,"1673":1,"1678":1,"1792":3,"2255":1}}],["errorcodepolicies",{"2":{"197":1,"1111":1,"1669":1,"1670":1,"1673":1,"1678":1,"1792":3,"2255":3}}],["errors",{"0":{"1595":1,"2756":1},"2":{"195":1,"197":1,"213":1,"216":1,"395":1,"424":1,"715":1,"737":1,"779":1,"984":1,"986":1,"995":1,"1026":4,"1032":1,"1041":1,"1080":1,"1111":3,"1217":1,"1224":1,"1338":1,"1339":1,"1386":1,"1401":1,"1408":1,"1418":1,"1419":2,"1543":1,"1582":1,"1586":1,"1609":1,"1641":1,"1668":1,"1670":2,"1739":1,"1741":2,"1792":3,"1801":2,"1824":1,"1874":1,"2007":7,"2106":1,"2253":1,"2267":1,"2287":1,"2289":2,"2307":1,"2328":7,"2336":1,"2367":1,"2384":1,"2410":1,"2481":1,"2498":1,"2528":1,"2537":1,"2597":1,"2659":1,"2758":1,"2763":1,"2810":1,"2813":1,"2840":2,"2841":1,"2864":1,"2878":1,"2880":1,"2881":1}}],["errorhandlingoptions",{"2":{"139":1,"140":1,"1669":1,"1672":1,"1673":1,"1678":2,"1792":1,"2253":1,"2255":4,"2267":3,"2384":1,"2558":1,"2701":1}}],["error",{"0":{"192":1,"424":1,"777":1,"819":1,"1111":1,"1155":1,"1591":1,"1624":1,"1668":1,"1671":1,"1672":1,"1673":1,"1674":1,"2255":1,"2271":1,"2273":1,"2307":1,"2328":1,"2359":1,"2394":1,"2663":1,"2734":1,"2755":1},"1":{"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1669":1,"1670":1,"1671":1,"1672":1,"1673":1,"1674":2,"1675":1,"1676":1,"1677":1,"1678":1,"1679":1,"1680":1,"1681":1},"2":{"102":1,"109":1,"140":1,"141":1,"188":1,"192":3,"193":2,"195":1,"196":1,"197":2,"198":2,"199":1,"200":2,"206":1,"207":3,"208":2,"209":5,"210":2,"213":2,"216":1,"235":2,"301":1,"424":4,"439":3,"447":3,"449":1,"452":1,"575":3,"576":1,"579":2,"587":1,"704":1,"777":1,"818":1,"819":1,"835":1,"869":1,"879":1,"880":1,"894":4,"901":1,"910":1,"938":2,"982":1,"985":1,"991":1,"995":5,"996":4,"997":1,"1011":1,"1019":2,"1020":2,"1021":4,"1024":1,"1026":11,"1031":2,"1074":1,"1080":1,"1101":1,"1105":7,"1109":5,"1111":20,"1127":1,"1152":2,"1155":2,"1181":2,"1218":1,"1232":1,"1234":1,"1236":1,"1237":1,"1317":1,"1320":3,"1332":3,"1335":1,"1338":3,"1339":3,"1341":2,"1342":2,"1350":1,"1366":5,"1378":1,"1386":4,"1388":1,"1390":1,"1394":2,"1395":1,"1398":2,"1402":1,"1407":1,"1408":3,"1409":2,"1410":6,"1415":1,"1416":1,"1422":1,"1426":1,"1427":1,"1431":1,"1558":4,"1567":4,"1568":1,"1589":1,"1591":1,"1595":2,"1596":1,"1605":2,"1609":1,"1623":1,"1624":2,"1668":1,"1670":6,"1671":3,"1673":1,"1674":1,"1676":1,"1677":1,"1678":1,"1679":2,"1680":1,"1681":2,"1721":1,"1722":2,"1725":1,"1727":4,"1732":2,"1736":3,"1737":1,"1740":1,"1741":2,"1742":4,"1791":2,"1792":35,"1798":2,"1801":1,"1806":1,"1822":1,"1865":2,"1882":1,"1884":1,"1886":1,"1887":1,"1916":1,"1918":2,"1921":3,"1922":2,"2007":4,"2094":1,"2100":1,"2101":1,"2107":1,"2111":2,"2113":3,"2141":1,"2151":2,"2155":1,"2157":1,"2184":1,"2225":1,"2226":1,"2240":1,"2242":3,"2253":1,"2255":24,"2264":7,"2267":3,"2271":5,"2273":5,"2288":1,"2289":3,"2290":4,"2296":1,"2307":4,"2320":1,"2321":1,"2328":5,"2329":1,"2330":2,"2337":1,"2359":3,"2360":2,"2377":1,"2384":3,"2394":1,"2405":1,"2412":1,"2414":1,"2415":1,"2416":2,"2417":1,"2441":1,"2442":1,"2445":1,"2481":1,"2492":1,"2497":2,"2506":1,"2519":1,"2526":1,"2528":2,"2529":1,"2531":1,"2532":2,"2533":2,"2535":7,"2537":1,"2541":1,"2543":3,"2549":6,"2558":1,"2559":1,"2566":2,"2575":1,"2597":2,"2608":2,"2648":2,"2659":1,"2663":1,"2679":1,"2684":1,"2685":2,"2688":2,"2696":1,"2701":1,"2719":1,"2722":2,"2762":3,"2763":3,"2764":3,"2766":2,"2769":2,"2794":1,"2810":5,"2813":1,"2814":1,"2815":3,"2840":1,"2857":1,"2860":1,"2864":2,"2865":1,"2871":2,"2876":1,"2880":2,"2881":1}}],["ergonomic",{"2":{"1044":3}}],["ergonomics",{"2":{"369":1,"1102":1,"2332":1}}],["ernest",{"2":{"913":1}}],["era",{"2":{"848":1,"871":1,"1384":1}}],["er",{"2":{"845":1,"1018":1,"1019":1,"1023":1,"1026":1,"1032":1,"2764":2,"2766":1}}],["eric",{"2":{"843":1,"847":1,"851":2,"863":1}}],["esbuild",{"2":{"1422":1}}],["es256",{"2":{"1236":1,"1237":1,"1792":1,"1886":1,"1887":1}}],["establishment",{"2":{"1593":1,"1792":1}}],["establishing",{"2":{"1152":1}}],["establish",{"2":{"877":1,"1593":1,"1624":1,"1792":1}}],["established",{"2":{"650":1,"851":1,"852":1,"1153":1,"1402":1,"2504":1}}],["est",{"2":{"869":1,"2398":2}}],["estimated",{"0":{"2398":1},"2":{"911":1,"1027":1,"1037":1,"1322":1}}],["estimates",{"2":{"872":1,"1130":1,"1181":1,"2398":1}}],["estimate",{"2":{"869":2,"872":1,"873":1,"1133":1,"2398":1}}],["essays",{"2":{"863":1}}],["essence",{"2":{"851":1}}],["essentially",{"2":{"1385":1,"2398":1}}],["essential",{"2":{"377":1,"913":1,"919":2,"1111":1,"2333":1}}],["escaping",{"0":{"2589":1},"2":{"2270":1,"2372":2,"2481":1,"2589":1,"2603":2}}],["escapes",{"2":{"2528":1,"2540":1,"2603":1,"2863":1}}],["escaped",{"2":{"1582":1,"2040":2,"2372":2,"2476":2,"2589":8,"2603":1}}],["escape",{"2":{"340":1,"389":1,"599":1,"857":3,"861":1,"1856":1,"2040":1,"2224":1,"2270":1,"2455":1}}],["escalation",{"2":{"2533":1}}],["escalating",{"2":{"857":1}}],["escalate",{"2":{"1441":1}}],["especially",{"2":{"87":1,"376":1,"919":1,"974":1,"1127":1,"1516":1,"1940":1,"2265":1,"2395":1,"2428":1}}],["elb",{"0":{"1712":1}}],["eliminating",{"2":{"1276":1,"2500":1,"2604":1,"2614":1,"2622":1}}],["eliminate",{"2":{"1006":1,"1166":1}}],["eliminated",{"2":{"873":2,"1037":1}}],["eliminates",{"2":{"826":1,"1180":1,"1274":1,"2342":1,"2399":1,"2576":1,"2622":1,"2853":1}}],["elite",{"2":{"1265":1}}],["eligible",{"2":{"1213":1,"1215":3,"1237":1,"1792":1,"1887":1}}],["elaborate",{"2":{"851":1,"1197":1}}],["elapses",{"2":{"214":1,"1743":1,"2502":1}}],["elem",{"2":{"2397":3}}],["elements",{"2":{"575":1,"903":2,"1339":1,"1357":2,"1410":2,"1427":1,"1692":1,"1792":1,"2265":1,"2270":1,"2551":1,"2586":1,"2607":1}}],["element",{"2":{"286":1,"763":1,"773":1,"887":1,"1651":2,"1655":2,"2551":1,"2604":1,"2608":1,"2621":1}}],["elevates",{"2":{"1185":1}}],["elevated",{"2":{"932":1,"933":1}}],["elegant",{"2":{"1075":1}}],["elegantly",{"2":{"843":1,"2868":1}}],["elsewhere",{"2":{"873":1,"1493":1,"1792":1}}],["else",{"0":{"2800":1},"2":{"207":1,"208":1,"209":1,"304":1,"313":1,"833":1,"841":1,"843":1,"848":1,"849":4,"851":1,"866":1,"894":1,"932":1,"994":1,"1021":2,"1045":1,"1060":1,"1069":1,"1079":1,"1106":1,"1162":3,"1327":1,"1342":1,"1364":1,"1366":1,"1399":1,"1403":1,"1404":4,"1410":2,"1429":1,"1435":1,"1459":1,"1579":1,"1727":2,"1736":1,"1792":1,"2157":1,"2171":1,"2264":1,"2375":1,"2427":2,"2450":1,"2452":1,"2490":1,"2543":1,"2733":1,"2764":1,"2810":1}}],["eating",{"2":{"1400":1}}],["eats",{"2":{"947":1}}],["earth",{"2":{"1382":1}}],["early",{"2":{"1338":1,"1399":1,"1400":1,"1527":1,"2007":1,"2270":1,"2339":1,"2352":1,"2529":1,"2696":1}}],["earliest",{"2":{"984":1}}],["earlier",{"2":{"175":1,"177":1,"529":1,"869":1,"872":1,"1080":1,"1096":1,"1157":1,"1388":1,"1400":1,"1402":2,"1464":1,"2376":1,"2378":1,"2379":1,"2415":1,"2684":1}}],["eager",{"2":{"856":1}}],["eaacatalog",{"2":{"851":1}}],["ease",{"2":{"974":1}}],["easily",{"2":{"915":1,"1393":1,"1403":1}}],["easier",{"2":{"307":1,"559":1,"1404":2,"2177":1,"2372":1,"2597":1}}],["easy",{"0":{"878":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1},"2":{"791":1,"841":1,"916":1,"995":1,"1037":1,"1075":1,"1135":1,"1386":2,"1792":1,"2047":1,"2134":1,"2677":1}}],["each",{"0":{"837":1,"1120":1,"1354":1,"2750":1},"1":{"1121":1,"1122":1,"1123":1},"2":{"22":1,"81":1,"83":1,"87":1,"158":1,"213":1,"214":1,"221":1,"286":1,"305":1,"336":2,"386":1,"388":1,"436":1,"448":1,"650":1,"656":1,"663":1,"667":1,"669":1,"684":1,"687":1,"696":1,"701":1,"759":1,"761":1,"762":1,"764":1,"768":1,"769":1,"771":2,"772":1,"774":1,"776":1,"786":1,"788":2,"831":1,"833":2,"844":1,"848":1,"851":3,"855":2,"856":1,"860":1,"868":1,"872":2,"873":1,"881":1,"886":1,"891":1,"892":1,"902":1,"918":2,"919":1,"927":1,"928":1,"948":2,"961":1,"985":1,"986":2,"989":2,"990":1,"993":1,"1008":1,"1011":1,"1023":1,"1027":1,"1032":1,"1039":1,"1069":1,"1070":2,"1083":1,"1098":3,"1101":1,"1102":1,"1107":1,"1111":1,"1135":1,"1144":1,"1145":1,"1150":3,"1159":1,"1162":2,"1165":1,"1168":1,"1169":1,"1174":1,"1175":1,"1176":2,"1180":1,"1181":1,"1185":1,"1191":1,"1219":1,"1231":2,"1279":1,"1280":1,"1281":1,"1303":1,"1326":1,"1329":1,"1355":1,"1359":1,"1363":1,"1378":1,"1382":4,"1385":2,"1386":4,"1391":1,"1398":1,"1406":1,"1408":1,"1409":1,"1410":1,"1414":1,"1427":2,"1458":1,"1460":2,"1511":2,"1518":1,"1523":1,"1527":1,"1564":1,"1566":1,"1572":1,"1577":1,"1589":1,"1618":1,"1621":1,"1628":1,"1651":1,"1658":1,"1671":1,"1696":1,"1740":1,"1741":1,"1792":35,"1823":1,"1824":1,"1844":2,"1852":3,"1869":1,"1881":1,"1924":1,"1948":1,"1955":2,"1957":2,"1958":2,"1974":2,"2010":1,"2096":1,"2098":1,"2099":2,"2103":1,"2108":1,"2109":1,"2110":1,"2112":1,"2128":1,"2130":1,"2141":1,"2160":1,"2165":1,"2166":1,"2167":1,"2168":1,"2184":1,"2191":1,"2192":1,"2208":1,"2254":1,"2265":2,"2285":1,"2288":1,"2289":1,"2297":1,"2318":3,"2320":2,"2321":1,"2324":1,"2359":1,"2364":1,"2375":3,"2377":1,"2378":2,"2379":5,"2380":3,"2383":3,"2399":1,"2401":1,"2407":1,"2417":1,"2436":1,"2456":1,"2459":1,"2466":1,"2470":1,"2476":1,"2482":1,"2504":2,"2506":1,"2523":1,"2526":1,"2527":1,"2528":1,"2530":2,"2533":1,"2534":2,"2535":2,"2537":3,"2544":2,"2590":1,"2607":2,"2611":1,"2615":1,"2622":1,"2671":1,"2677":1,"2680":1,"2765":1,"2794":2,"2795":1,"2804":1,"2815":1,"2830":1,"2836":1,"2839":1,"2840":2,"2851":1,"2858":1,"2862":1,"2863":1,"2866":1,"2868":1,"2871":1}}],["emoji",{"2":{"2589":1,"2607":1}}],["emerged",{"2":{"1266":1}}],["emerges",{"2":{"872":1}}],["embed",{"2":{"2018":1,"2389":1}}],["embed>",{"2":{"1792":1,"2632":1}}],["embeds",{"2":{"1111":1}}],["embedder",{"0":{"2024":1},"2":{"1792":1,"2632":2}}],["embedded",{"2":{"1078":1,"1096":1,"1792":4,"1824":1,"1924":1,"2021":1,"2221":1,"2258":1,"2389":1,"2399":1,"2509":1,"2529":1,"2865":1}}],["embedding",{"2":{"1096":2,"1122":1,"1127":1}}],["empirical",{"2":{"871":1}}],["emptystring",{"0":{"463":1},"2":{"470":2,"556":1,"1792":1,"1836":1,"1853":1,"1854":1,"1855":1,"2595":2,"2596":1}}],["empty",{"0":{"466":1,"553":1,"616":1,"991":1},"2":{"35":1,"212":1,"286":1,"298":1,"299":1,"388":1,"390":1,"395":1,"447":1,"460":3,"462":1,"463":4,"464":1,"466":2,"467":1,"468":1,"529":1,"551":2,"553":1,"614":1,"616":3,"617":1,"770":1,"771":1,"772":2,"773":1,"803":1,"812":1,"813":1,"815":1,"816":3,"829":1,"893":1,"930":5,"934":1,"991":3,"1069":1,"1162":1,"1222":1,"1225":1,"1234":1,"1241":1,"1360":2,"1431":2,"1432":1,"1523":1,"1526":1,"1527":1,"1639":1,"1640":1,"1688":1,"1708":1,"1709":1,"1713":1,"1716":1,"1792":33,"1823":1,"1824":1,"1828":1,"1853":1,"1854":3,"1855":2,"1862":1,"1875":1,"1890":1,"1898":1,"1956":1,"2000":1,"2003":1,"2038":2,"2040":1,"2047":1,"2094":1,"2095":1,"2096":1,"2105":1,"2109":1,"2140":2,"2142":2,"2146":2,"2147":2,"2149":1,"2176":1,"2258":3,"2265":1,"2271":1,"2284":1,"2330":1,"2339":3,"2379":1,"2380":3,"2431":2,"2436":2,"2466":1,"2476":1,"2482":1,"2491":1,"2492":1,"2494":1,"2530":1,"2537":3,"2539":1,"2572":1,"2575":6,"2586":1,"2589":1,"2595":3,"2596":2,"2607":2,"2633":3,"2635":1,"2648":2,"2764":1,"2810":1}}],["emitting",{"2":{"1569":1,"2831":1}}],["emitted",{"2":{"868":1,"1570":1,"1571":1,"1575":1,"1582":1,"1792":2,"2222":1,"2484":2,"2489":1,"2519":2,"2523":1,"2536":1,"2662":1,"2694":1}}],["emitter",{"2":{"664":1,"1326":2,"2391":2,"2392":1,"2834":3}}],["emits",{"2":{"868":2,"1043":1,"1327":1,"1410":1,"1413":1,"2359":1,"2395":1,"2416":1,"2472":1,"2484":1,"2519":1,"2544":1,"2827":1,"2830":1,"2834":1}}],["emit",{"0":{"2484":1},"2":{"835":1,"1320":2,"1325":1,"1559":1,"1570":1,"1571":1,"1577":1,"1792":1,"1824":1,"1844":2,"2667":1,"2829":2}}],["emitproc",{"2":{"663":2}}],["emiturl",{"2":{"663":2}}],["email=",{"2":{"2861":1}}],["email=a",{"2":{"297":2}}],["emailaddress",{"2":{"1692":2,"1792":2}}],["emailurl",{"2":{"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1792":5}}],["emails",{"2":{"1104":1,"1106":1}}],["email",{"0":{"428":1},"2":{"128":4,"167":1,"208":3,"298":4,"304":1,"308":2,"309":2,"312":3,"313":5,"333":2,"360":5,"365":3,"366":5,"380":2,"414":1,"428":6,"488":2,"489":3,"493":3,"611":3,"612":2,"695":1,"700":1,"809":2,"812":9,"813":5,"816":2,"818":2,"819":2,"834":2,"835":2,"888":5,"924":1,"934":3,"936":4,"937":2,"938":2,"977":2,"979":2,"986":1,"988":2,"989":1,"994":3,"995":2,"996":3,"1050":1,"1051":1,"1055":2,"1058":4,"1060":9,"1062":2,"1074":2,"1078":2,"1094":1,"1113":1,"1193":1,"1213":1,"1214":2,"1215":2,"1216":4,"1220":2,"1222":1,"1233":1,"1239":3,"1368":3,"1369":2,"1371":3,"1379":2,"1386":10,"1387":3,"1390":1,"1391":2,"1393":2,"1396":2,"1398":1,"1399":2,"1405":1,"1408":2,"1409":1,"1414":1,"1542":2,"1546":2,"1567":6,"1570":2,"1687":1,"1689":13,"1691":1,"1695":1,"1696":1,"1730":2,"1792":7,"1870":1,"2012":1,"2039":3,"2042":1,"2142":2,"2146":2,"2147":9,"2167":1,"2176":4,"2178":1,"2180":2,"2183":6,"2184":4,"2204":1,"2221":2,"2300":1,"2322":1,"2333":2,"2526":2,"2528":1,"2529":1,"2530":2,"2540":9,"2575":10,"2739":1,"2774":1,"2775":2,"2842":2,"2860":2,"2861":4,"2864":1,"2865":1,"2866":1,"2869":2,"2873":1}}],["evolutionary",{"2":{"1193":1}}],["evolves",{"2":{"2438":1}}],["evolve",{"2":{"1049":1,"1193":1,"1405":1,"1419":1}}],["evolved",{"2":{"872":2}}],["evaluations",{"2":{"1527":1}}],["evaluates",{"2":{"2185":1}}],["evaluate",{"2":{"1067":1,"2380":1}}],["evaluated",{"2":{"108":1,"388":1,"818":1,"1098":1,"1150":1,"1521":1,"1523":2,"1792":2,"1956":1,"2149":1,"2379":1,"2575":1}}],["evaporated",{"2":{"844":1}}],["evans",{"2":{"843":1,"847":3,"851":5,"852":1,"863":1}}],["eve",{"2":{"913":1,"919":2}}],["ever",{"0":{"1435":1},"1":{"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1},"2":{"215":1,"852":1,"859":1,"868":1,"972":1,"1037":1,"1068":1,"1076":1,"1078":1,"1210":1,"1251":1,"1328":1,"1382":1,"1402":1,"1403":1,"1412":1,"1435":3,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1522":1,"1664":1,"1813":1,"1825":1,"1862":1,"1868":1,"2291":1,"2391":1,"2399":1,"2482":1,"2483":1,"2532":1,"2878":1}}],["everyday",{"2":{"2537":1}}],["everybody",{"2":{"852":1}}],["everyone",{"2":{"841":1,"913":1,"919":2,"1045":1,"1069":1,"1162":2,"1254":1,"1315":1,"1316":1,"1382":2,"1399":1,"1400":1,"1404":1,"1957":1,"2379":1,"2828":1,"2833":1}}],["everywhere",{"2":{"529":1,"1079":1,"1190":1,"2359":1,"2534":1}}],["everything",{"0":{"2800":1},"2":{"307":1,"690":1,"706":1,"710":1,"833":1,"848":1,"851":1,"852":2,"865":1,"866":1,"910":1,"911":2,"920":1,"927":1,"937":1,"946":1,"957":1,"971":1,"986":1,"1036":1,"1065":1,"1080":1,"1081":1,"1086":1,"1088":1,"1184":1,"1189":1,"1208":1,"1252":1,"1281":1,"1327":2,"1328":1,"1331":1,"1351":1,"1367":1,"1382":2,"1395":1,"1403":2,"1404":1,"1421":1,"1441":1,"1459":1,"1579":1,"1792":3,"1910":1,"2096":1,"2106":3,"2110":1,"2153":1,"2157":1,"2158":1,"2171":1,"2177":1,"2375":1,"2433":1,"2527":1,"2529":1,"2530":1,"2531":1,"2537":2,"2541":1,"2543":1,"2546":1,"2721":1,"2728":1,"2731":1,"2742":1,"2751":1,"2850":1,"2860":1,"2862":1,"2865":1,"2867":1,"2869":1,"2878":4}}],["every",{"0":{"985":1,"2490":1,"2798":1},"2":{"3":1,"41":1,"74":1,"102":1,"109":1,"214":2,"297":2,"298":2,"304":1,"306":1,"352":1,"388":1,"446":1,"533":1,"636":4,"650":1,"666":1,"695":1,"704":1,"714":1,"826":1,"835":1,"840":1,"843":1,"845":2,"848":2,"849":2,"851":1,"852":8,"857":1,"860":2,"861":3,"864":4,"865":4,"868":3,"871":1,"872":8,"873":2,"875":2,"876":2,"878":1,"880":1,"905":1,"924":1,"930":1,"948":1,"951":1,"967":1,"976":1,"978":1,"979":1,"984":1,"985":1,"986":1,"988":2,"994":1,"997":1,"1003":1,"1005":2,"1009":1,"1013":1,"1049":1,"1064":1,"1070":1,"1073":1,"1075":2,"1076":1,"1079":1,"1080":3,"1082":1,"1094":1,"1098":1,"1102":2,"1113":1,"1135":1,"1139":1,"1147":1,"1150":1,"1160":1,"1162":1,"1164":1,"1181":1,"1183":1,"1191":1,"1193":1,"1203":1,"1208":1,"1255":1,"1281":1,"1305":2,"1326":2,"1329":3,"1337":1,"1363":1,"1377":1,"1379":1,"1381":1,"1382":6,"1385":2,"1400":1,"1402":2,"1407":2,"1408":2,"1409":2,"1410":1,"1414":1,"1418":1,"1424":1,"1427":1,"1428":1,"1430":2,"1435":2,"1436":5,"1437":1,"1441":1,"1442":1,"1460":1,"1527":1,"1567":2,"1569":1,"1579":1,"1582":1,"1605":1,"1620":1,"1722":1,"1743":1,"1759":1,"1792":14,"1802":1,"1821":1,"1822":1,"1823":2,"1825":2,"1827":1,"1832":1,"1833":1,"1844":1,"1850":1,"1909":1,"1912":1,"1958":1,"1961":1,"2004":3,"2005":1,"2040":1,"2060":1,"2094":1,"2104":1,"2109":1,"2110":2,"2111":1,"2156":1,"2157":2,"2171":2,"2172":1,"2175":1,"2180":1,"2182":1,"2184":1,"2185":1,"2221":1,"2223":1,"2247":1,"2323":1,"2330":1,"2338":1,"2363":1,"2375":1,"2380":1,"2381":1,"2382":1,"2389":1,"2391":1,"2394":1,"2395":2,"2397":1,"2413":1,"2419":1,"2422":1,"2430":2,"2443":1,"2445":1,"2450":1,"2452":1,"2459":1,"2468":1,"2481":1,"2490":1,"2494":3,"2495":2,"2497":1,"2502":1,"2504":1,"2510":1,"2511":1,"2518":1,"2520":1,"2530":2,"2531":2,"2532":2,"2533":1,"2536":2,"2537":5,"2542":2,"2543":2,"2545":1,"2559":1,"2614":1,"2677":1,"2682":1,"2688":1,"2694":1,"2742":1,"2749":1,"2758":1,"2759":1,"2769":1,"2771":1,"2795":3,"2800":2,"2803":2,"2805":1,"2815":1,"2817":1,"2825":1,"2828":1,"2831":1,"2836":2,"2839":1,"2841":2,"2853":1,"2857":1,"2858":1,"2866":1,"2867":1,"2868":1,"2871":1,"2872":1,"2873":1,"2878":2,"2879":1,"2880":1,"2881":1,"2882":1}}],["evenly",{"2":{"1175":1}}],["eventually",{"2":{"864":1}}],["eventual",{"2":{"844":1,"865":1}}],["event",{"0":{"1845":1,"2832":1},"2":{"292":1,"621":1,"636":4,"639":2,"646":5,"650":2,"663":1,"666":3,"669":1,"840":1,"894":4,"1030":2,"1103":1,"1104":1,"1239":1,"1304":1,"1305":1,"1309":3,"1316":3,"1317":2,"1318":2,"1321":2,"1326":1,"1327":1,"1366":4,"1410":4,"1416":2,"1573":1,"1792":3,"1844":1,"1860":1,"2157":1,"2223":1,"2247":2,"2250":2,"2252":1,"2343":1,"2391":1,"2393":1,"2407":2,"2464":1,"2490":3,"2543":1,"2803":2,"2827":1,"2828":6,"2829":1,"2830":1,"2831":2,"2833":1,"2834":1,"2835":1}}],["eventsource",{"2":{"636":1,"650":2,"666":4,"868":1,"1304":1,"1305":2,"1317":6,"1318":7,"1321":2,"1323":2,"1325":1,"1415":1,"1416":6,"1563":1,"1573":2,"1581":1,"2247":8,"2391":3,"2393":1,"2827":1,"2828":4,"2830":2,"2833":1,"2838":1}}],["events",{"0":{"162":1,"233":1,"627":1,"636":1,"650":1,"1302":1,"1857":1,"2248":2,"2249":1,"2490":1,"2827":1,"2831":1},"1":{"628":1,"629":1,"630":1,"631":1,"632":1,"633":1,"634":1,"635":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1858":1,"1859":1,"1860":1,"1861":1,"2249":2,"2250":2,"2251":2,"2252":2,"2828":1,"2829":1,"2830":1,"2831":1,"2832":1,"2833":1,"2834":1,"2835":1,"2836":1,"2837":1,"2838":1},"2":{"162":5,"233":3,"627":1,"628":1,"631":1,"632":1,"633":1,"634":1,"635":2,"636":1,"637":1,"638":3,"639":1,"641":4,"642":2,"643":1,"644":2,"645":1,"646":2,"647":1,"648":2,"649":2,"650":1,"654":3,"656":4,"658":2,"659":1,"662":1,"663":3,"664":3,"665":1,"666":3,"670":1,"671":2,"719":1,"720":3,"726":2,"835":1,"865":1,"868":2,"966":1,"1037":1,"1103":2,"1106":1,"1107":1,"1302":2,"1304":3,"1305":2,"1309":1,"1311":1,"1312":1,"1313":2,"1314":1,"1323":1,"1324":1,"1325":1,"1326":3,"1327":1,"1372":1,"1415":1,"1416":1,"1563":1,"1792":7,"1844":1,"1857":2,"1858":3,"1860":2,"1864":1,"2052":1,"2124":1,"2157":1,"2164":1,"2223":1,"2240":1,"2247":1,"2249":4,"2250":1,"2251":2,"2252":4,"2329":1,"2362":1,"2391":2,"2392":1,"2490":2,"2543":1,"2635":2,"2701":1,"2801":1,"2802":1,"2805":2,"2827":5,"2828":1,"2829":2,"2830":2,"2831":3,"2832":4,"2833":1,"2834":5,"2835":2,"2836":2,"2838":3}}],["even",{"2":{"188":1,"319":1,"349":1,"354":1,"529":1,"609":1,"667":1,"683":1,"695":1,"713":1,"834":1,"840":1,"841":1,"843":2,"844":1,"845":3,"847":1,"848":6,"849":1,"851":1,"857":1,"863":1,"869":1,"872":1,"884":1,"914":1,"917":1,"918":1,"919":2,"932":1,"933":1,"936":1,"940":1,"941":1,"946":1,"981":1,"1067":1,"1068":1,"1074":1,"1075":1,"1079":2,"1080":1,"1081":1,"1094":1,"1150":1,"1171":1,"1185":3,"1329":1,"1385":2,"1386":3,"1393":1,"1394":1,"1400":2,"1401":2,"1402":1,"1403":7,"1405":2,"1407":1,"1414":1,"1415":1,"1419":1,"1441":1,"1475":1,"1477":1,"1522":1,"1569":1,"1704":1,"1792":3,"1833":1,"2003":1,"2106":1,"2112":1,"2156":1,"2284":1,"2296":1,"2380":1,"2403":1,"2414":2,"2421":1,"2468":1,"2470":1,"2486":1,"2496":1,"2519":1,"2520":1,"2531":1,"2532":1,"2533":2,"2537":1,"2542":1,"2731":1,"2759":1,"2833":1,"2867":1,"2871":1,"2878":1}}],["evenings",{"2":{"1":1}}],["ehh4onl5eq==",{"2":{"40":1}}],["e",{"2":{"31":1,"74":2,"108":1,"109":1,"155":1,"175":1,"188":1,"210":1,"212":2,"268":1,"286":1,"297":1,"302":1,"349":1,"370":2,"387":2,"388":3,"390":1,"415":1,"423":2,"446":1,"447":1,"448":1,"453":1,"582":3,"583":2,"587":1,"615":1,"663":1,"720":1,"734":1,"747":1,"748":1,"761":2,"768":1,"771":1,"776":1,"781":1,"786":1,"826":1,"851":1,"852":1,"868":1,"876":1,"892":1,"961":2,"967":1,"996":2,"1060":1,"1066":1,"1088":1,"1097":1,"1098":1,"1101":1,"1111":1,"1193":1,"1225":2,"1232":1,"1233":1,"1237":1,"1239":2,"1284":1,"1366":1,"1385":2,"1386":1,"1409":1,"1427":1,"1447":1,"1449":1,"1451":1,"1454":2,"1493":1,"1511":1,"1519":3,"1521":1,"1523":1,"1525":1,"1569":1,"1575":1,"1590":1,"1605":1,"1609":1,"1614":1,"1664":1,"1685":1,"1687":1,"1706":1,"1732":1,"1738":1,"1741":2,"1767":1,"1792":35,"1823":2,"1825":1,"1831":1,"1832":1,"1837":1,"1840":1,"1856":2,"1862":1,"1875":2,"1882":1,"1887":1,"1906":1,"1917":1,"1922":1,"1925":1,"1957":3,"1958":1,"1991":1,"2004":1,"2062":1,"2110":1,"2111":1,"2112":1,"2146":1,"2156":1,"2177":1,"2193":1,"2195":1,"2210":1,"2258":1,"2264":1,"2265":1,"2282":1,"2283":1,"2289":2,"2291":1,"2296":1,"2303":1,"2318":1,"2322":1,"2329":2,"2330":1,"2336":1,"2337":1,"2338":1,"2339":1,"2347":1,"2357":1,"2358":1,"2360":1,"2364":2,"2365":2,"2367":1,"2379":3,"2380":2,"2413":1,"2430":1,"2434":1,"2438":1,"2444":2,"2445":1,"2446":1,"2453":1,"2483":2,"2487":1,"2490":1,"2497":3,"2505":2,"2517":1,"2518":4,"2519":1,"2520":1,"2529":1,"2530":2,"2532":4,"2533":1,"2536":1,"2537":1,"2542":1,"2544":1,"2549":1,"2566":1,"2572":2,"2586":1,"2589":1,"2590":1,"2591":1,"2597":1,"2603":1,"2634":1,"2635":1,"2645":1,"2649":1,"2656":1,"2659":1,"2679":2,"2688":1,"2692":1,"2696":1,"2767":1,"2768":1,"2779":1,"2811":1,"2830":2,"2836":2,"2840":1,"2847":1,"2854":1,"2875":1}}],["ex",{"2":{"1184":2}}],["exhausted",{"2":{"2459":1}}],["exhaust",{"2":{"1069":1}}],["exhaustion",{"0":{"1329":1},"2":{"213":1,"1037":1,"1741":1,"2289":1}}],["exfiltration",{"2":{"941":1}}],["exec",{"2":{"2875":1}}],["executables",{"2":{"2157":1,"2543":1,"2744":1,"2792":1}}],["executable",{"0":{"2778":1},"1":{"2779":1,"2780":1,"2781":1,"2782":1,"2783":1,"2784":1,"2785":1},"2":{"1084":1,"1086":2,"1087":1,"1840":1,"2261":1,"2389":1,"2576":1,"2709":1,"2711":1,"2714":1,"2716":1,"2776":1,"2779":1,"2782":2,"2783":2,"2784":2,"2786":1,"2792":2,"2821":1}}],["executebatchreaderwithretryasync",{"2":{"2320":1,"2372":1}}],["executenonquery",{"2":{"829":1}}],["execute",{"0":{"2798":1},"2":{"223":1,"317":1,"412":1,"414":1,"439":1,"456":1,"529":1,"786":1,"788":1,"829":1,"901":1,"926":1,"932":1,"933":1,"948":1,"988":1,"1076":2,"1095":1,"1104":1,"1105":3,"1129":1,"1161":1,"1166":1,"1324":1,"1343":1,"1388":1,"1393":1,"1398":1,"1399":1,"1684":1,"1733":1,"1792":3,"1805":1,"1813":1,"1961":1,"2087":1,"2245":1,"2256":2,"2264":1,"2283":1,"2284":1,"2300":1,"2302":1,"2346":1,"2466":1,"2481":1,"2532":1,"2533":1,"2545":1,"2550":1,"2721":1,"2755":1,"2774":1,"2791":1,"2795":1,"2809":1,"2852":1,"2853":1}}],["executescalar",{"2":{"528":1}}],["executes",{"0":{"2750":1},"2":{"216":1,"284":1,"285":1,"415":1,"423":1,"621":1,"703":1,"933":1,"986":1,"1015":1,"1016":2,"1023":1,"1029":1,"1044":1,"1219":1,"1220":1,"1221":1,"1222":1,"1305":2,"1386":1,"1394":1,"1396":1,"1426":1,"1524":1,"1686":1,"1723":2,"1792":1,"1824":2,"1869":1,"1870":1,"1871":1,"1872":1,"2283":1,"2303":1,"2304":1,"2463":1,"2526":1,"2537":1,"2834":1,"2850":1}}],["executed",{"2":{"41":1,"362":1,"438":1,"618":1,"622":1,"625":1,"823":1,"829":1,"848":1,"860":1,"880":1,"974":1,"1016":1,"1073":1,"1102":1,"1105":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1255":1,"1331":1,"1370":1,"1386":1,"1394":2,"1398":2,"1399":1,"1472":2,"1792":9,"1799":1,"1802":1,"1844":1,"1850":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"2104":1,"2108":1,"2338":1,"2342":1,"2391":1,"2459":1,"2528":2,"2532":1,"2540":1,"2580":1,"2807":1,"2809":2,"2845":1,"2863":1,"2880":1}}],["executive",{"0":{"1084":1}}],["executing",{"2":{"184":1,"202":1,"737":1,"1076":1,"1363":1,"1388":1,"1618":1,"1745":1,"1792":4,"2250":1,"2264":1,"2292":1,"2318":1,"2463":1,"2615":1,"2634":1,"2635":1,"2840":1}}],["executions",{"2":{"1792":1,"2463":1,"2464":1,"2465":2,"2466":1}}],["executionid",{"2":{"1317":2,"1416":3,"2247":3}}],["executionidheadername>",{"2":{"1792":1}}],["executionidheadername",{"2":{"639":1,"1792":1,"1836":1,"1848":1,"1863":1,"2701":1,"2833":1}}],["execution",{"0":{"1104":1,"1106":1,"1326":1,"2300":1,"2614":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1},"2":{"132":1,"140":1,"226":1,"277":1,"456":1,"549":1,"639":4,"650":1,"667":1,"668":3,"696":1,"761":1,"763":1,"771":1,"801":1,"807":1,"966":1,"968":1,"993":2,"1070":1,"1076":1,"1095":1,"1102":1,"1104":1,"1105":2,"1107":1,"1110":1,"1126":1,"1130":1,"1132":1,"1133":1,"1153":1,"1277":1,"1304":1,"1305":1,"1309":1,"1325":1,"1326":2,"1382":1,"1396":1,"1416":1,"1573":1,"1620":2,"1669":1,"1672":1,"1741":1,"1792":6,"1801":1,"1825":1,"1848":1,"1852":1,"1961":1,"2049":2,"2098":1,"2107":1,"2137":1,"2224":1,"2228":1,"2229":1,"2247":1,"2253":1,"2255":2,"2284":3,"2289":2,"2309":1,"2320":1,"2324":1,"2372":2,"2383":1,"2391":1,"2459":1,"2461":1,"2462":1,"2463":1,"2464":2,"2465":4,"2466":1,"2498":1,"2527":1,"2534":1,"2537":1,"2545":1,"2559":1,"2575":1,"2596":1,"2597":1,"2614":1,"2615":1,"2628":1,"2635":2,"2767":1,"2833":2,"2860":1,"2873":1}}],["exe",{"2":{"2781":2}}],["exercise",{"2":{"2417":1,"2529":1}}],["exercised",{"2":{"1792":1,"2107":1,"2465":1}}],["exercises",{"2":{"692":1,"875":1}}],["exited",{"2":{"2414":1,"2543":1}}],["exit",{"0":{"2113":1},"2":{"876":1,"1792":8,"1999":1,"2000":1,"2007":5,"2094":1,"2101":1,"2105":2,"2106":3,"2111":1,"2112":2,"2153":1,"2157":1,"2158":1,"2270":1,"2328":3,"2330":4,"2339":1,"2362":1,"2367":1,"2402":1,"2414":1,"2415":1,"2532":6,"2535":1,"2537":5,"2540":1,"2543":2,"2679":1,"2696":1,"2722":1,"2758":1,"2840":1,"2841":2,"2878":1,"2880":1}}],["exits",{"2":{"587":1,"1609":1,"2094":1,"2095":1,"2106":1,"2107":1,"2112":1,"2155":1,"2157":2,"2221":1,"2328":1,"2337":1,"2415":1,"2416":1,"2527":1,"2532":1,"2537":3,"2540":1,"2541":1,"2543":2,"2659":1,"2669":1,"2672":1,"2785":1,"2862":1,"2878":1,"2879":1}}],["existent",{"2":{"992":1}}],["existence",{"2":{"877":1}}],["existed",{"2":{"852":1,"872":1,"874":1,"1078":2,"1377":1,"1385":1,"2540":1,"2856":1}}],["existing",{"0":{"1221":1,"1871":1,"2714":1,"2856":1},"2":{"375":1,"414":1,"430":1,"835":1,"1054":1,"1102":1,"1105":1,"1193":2,"1203":1,"1213":1,"1218":1,"1220":1,"1221":3,"1222":1,"1226":2,"1232":8,"1233":1,"1237":1,"1238":1,"1253":1,"1368":1,"1388":1,"1419":1,"1460":1,"1520":1,"1522":1,"1554":1,"1571":1,"1722":1,"1753":1,"1792":14,"1840":1,"1851":1,"1870":1,"1872":1,"1876":1,"1882":1,"1893":2,"1898":1,"1908":1,"2193":1,"2254":1,"2258":1,"2266":1,"2283":1,"2287":1,"2289":1,"2297":1,"2300":1,"2302":1,"2308":1,"2313":1,"2314":1,"2317":1,"2323":1,"2332":2,"2371":1,"2375":1,"2378":1,"2380":2,"2382":1,"2389":1,"2391":1,"2397":1,"2406":1,"2413":1,"2419":1,"2422":1,"2426":1,"2427":1,"2428":1,"2429":1,"2430":1,"2431":1,"2435":3,"2452":1,"2456":1,"2461":1,"2474":1,"2482":1,"2484":1,"2495":1,"2502":2,"2513":1,"2581":2,"2587":1,"2679":1,"2692":1,"2723":1}}],["exists",{"0":{"2723":1},"2":{"168":1,"308":1,"313":1,"320":1,"438":1,"701":1,"705":1,"757":1,"847":1,"849":1,"851":1,"852":1,"860":2,"864":1,"865":1,"869":1,"924":2,"925":1,"973":1,"975":1,"977":1,"994":1,"1042":1,"1095":1,"1111":1,"1139":1,"1396":1,"1406":1,"1419":1,"1431":1,"1470":1,"1678":1,"1792":19,"1856":1,"1861":1,"2109":1,"2111":2,"2112":1,"2177":1,"2438":1,"2455":1,"2476":1,"2481":2,"2530":1,"2532":3,"2534":2,"2540":1,"2558":1,"2729":1,"2834":1,"2871":1,"2872":1,"2873":1}}],["exist",{"0":{"2797":1},"2":{"165":1,"362":1,"384":1,"583":1,"584":1,"587":1,"625":1,"691":1,"704":1,"714":1,"737":1,"784":1,"841":1,"845":1,"852":1,"864":2,"869":1,"871":2,"872":2,"873":2,"875":1,"985":1,"996":2,"1055":1,"1079":2,"1111":1,"1358":1,"1376":1,"1382":1,"1386":2,"1388":1,"1396":1,"1406":1,"1409":1,"1417":1,"1419":1,"1543":1,"1593":1,"1624":1,"1678":1,"1792":4,"1840":1,"1961":1,"2098":1,"2127":1,"2322":1,"2328":1,"2337":2,"2438":1,"2489":1,"2496":1,"2534":1,"2685":1,"2741":1,"2840":2,"2849":1,"2854":2,"2868":1}}],["ext",{"2":{"2539":1,"2760":2}}],["extremely",{"2":{"1275":1,"1401":2,"1403":1}}],["extreme",{"2":{"1168":1,"1386":1}}],["extrapolations",{"2":{"2398":1}}],["extra",{"2":{"583":1,"585":1,"1070":1,"1102":2,"1327":1,"1389":2,"1410":1,"1419":1,"1423":1,"1792":1,"1850":1,"2337":1,"2383":1,"2531":1,"2835":1,"2869":1}}],["extractkeywords",{"2":{"1335":1}}],["extractive",{"2":{"1335":1}}],["extraction",{"0":{"2359":1},"2":{"903":1,"2372":1}}],["extracts",{"2":{"1102":1,"1215":1,"1243":1,"2453":1}}],["extract",{"2":{"427":1,"1408":1}}],["extracted",{"2":{"209":1,"253":1,"404":1,"2277":1,"2318":1,"2456":1,"2840":1}}],["extensible",{"2":{"1385":1,"2255":1}}],["extensive",{"2":{"1205":1}}],["extensions",{"0":{"1012":1},"1":{"1013":1,"1014":1,"1015":1},"2":{"859":1,"1010":1,"1012":1,"1013":4,"1015":1,"1036":1,"1037":1,"1104":1,"1106":2,"1126":1,"1394":1,"1515":1,"1751":1,"1792":1,"2193":1,"2274":1,"2386":2,"2465":1,"2495":1,"2567":1}}],["extension",{"0":{"2482":1},"2":{"308":2,"864":1,"876":1,"924":1,"1013":1,"1048":1,"1049":2,"1104":2,"1105":1,"1107":3,"1255":1,"1394":2,"1580":1,"1792":2,"2177":2,"2223":1,"2327":1,"2372":1,"2479":1,"2481":1,"2759":1,"2802":1}}],["extendable",{"2":{"1385":1}}],["extend",{"2":{"1338":1,"1394":1}}],["extended",{"0":{"2591":1},"2":{"1193":1,"1258":1,"1259":1,"1598":1,"2265":1,"2369":1}}],["extending",{"2":{"1071":1}}],["extends",{"2":{"1020":1,"1193":2,"1351":1,"1394":1,"2385":1}}],["externally",{"2":{"2438":1}}],["external",{"0":{"1010":1,"1048":1,"1059":1,"1060":1,"1104":1,"1114":1,"1248":1,"1328":1,"1376":1,"1682":1,"2429":1,"2874":1},"1":{"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":2,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1105":1,"1106":1,"1107":1,"1108":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1},"2":{"201":1,"223":1,"308":1,"315":1,"414":1,"426":1,"436":5,"438":3,"452":1,"835":1,"837":2,"848":1,"868":1,"876":3,"1010":3,"1011":5,"1014":1,"1015":1,"1016":1,"1021":1,"1032":1,"1035":3,"1036":2,"1037":4,"1039":1,"1048":2,"1050":2,"1059":3,"1060":5,"1062":2,"1064":2,"1066":1,"1098":3,"1099":1,"1101":2,"1104":2,"1105":3,"1106":3,"1107":1,"1108":4,"1113":1,"1118":1,"1125":1,"1126":1,"1127":1,"1145":1,"1209":1,"1248":1,"1329":1,"1347":1,"1351":3,"1376":2,"1393":1,"1398":2,"1427":1,"1434":1,"1445":3,"1465":1,"1466":2,"1582":1,"1682":1,"1683":2,"1684":4,"1685":1,"1687":1,"1689":3,"1690":1,"1697":2,"1698":2,"1720":1,"1723":1,"1739":1,"1742":1,"1744":1,"1788":1,"1789":1,"1792":21,"1796":1,"1825":2,"1894":1,"1895":2,"1920":3,"1933":1,"2164":4,"2165":1,"2175":2,"2177":1,"2188":1,"2189":1,"2239":1,"2264":1,"2282":1,"2287":1,"2289":1,"2290":1,"2300":1,"2419":1,"2438":2,"2496":2,"2529":1,"2532":1,"2534":1,"2549":2,"2550":1,"2580":3,"2625":1,"2736":1,"2766":1,"2770":2,"2791":1,"2865":1,"2874":1}}],["exclusion",{"2":{"1792":3,"1898":1,"1928":1,"2431":1,"2436":1,"2519":2,"2539":1,"2549":1}}],["exclusions",{"2":{"1792":1}}],["exclusively",{"2":{"1400":1}}],["exclusive",{"2":{"1129":1,"2869":1,"2873":1}}],["excluderesponseheaders",{"2":{"1792":1,"1916":1,"1917":1,"1931":1,"2549":1,"2814":1}}],["excludelanguages",{"2":{"1792":1,"1966":1,"1967":1,"1971":1,"1975":1}}],["excludenames",{"2":{"1792":1,"1836":1,"1838":1,"2701":1}}],["excludemimetypes",{"2":{"1792":1,"1936":1,"1937":1}}],["excludeheaders",{"2":{"1340":2,"1792":1,"1916":1,"1917":1,"1931":1,"2549":1,"2814":1}}],["excludecredentialscolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["excludecredentials",{"2":{"1220":1,"1221":1}}],["excludes",{"2":{"624":1,"1967":1,"2156":1,"2526":2,"2542":1,"2860":2}}],["excludeschemas",{"2":{"349":1,"1792":2,"1836":1,"1838":1,"1839":1,"1863":1,"1897":1,"1898":1,"1909":1,"1910":1,"2225":1,"2419":1,"2431":1,"2433":1,"2435":1,"2436":1,"2701":1}}],["excludetag=slow",{"2":{"710":2,"1792":1,"2097":1,"2537":2,"2877":1}}],["excludetag",{"0":{"2097":1},"2":{"239":1,"708":1,"712":1,"1792":2,"2093":1,"2094":1,"2097":1,"2221":1,"2533":1,"2537":2,"2870":1}}],["exclude",{"0":{"1971":1,"2539":1},"2":{"238":1,"568":1,"710":1,"747":2,"753":1,"757":1,"781":1,"782":1,"784":1,"786":1,"788":1,"830":1,"864":2,"1214":1,"1220":1,"1221":1,"1232":2,"1240":1,"1358":1,"1792":8,"1838":4,"1882":1,"1889":1,"1917":2,"1937":1,"1967":1,"1971":1,"2094":1,"2097":1,"2125":2,"2221":1,"2343":1,"2432":1,"2812":1,"2852":1,"2877":1}}],["excludedmimetypepatterns",{"2":{"1792":1,"2123":1,"2125":1,"2132":1}}],["excluded",{"0":{"2489":1},"2":{"184":2,"529":1,"618":1,"622":1,"747":1,"753":1,"757":1,"781":1,"782":1,"784":1,"786":1,"788":1,"898":2,"990":1,"1054":1,"1339":5,"1358":1,"1460":1,"1655":1,"1664":1,"1689":1,"1792":5,"1969":2,"1972":2,"2107":1,"2222":1,"2292":1,"2342":1,"2375":1,"2523":2,"2535":1,"2537":1,"2539":1,"2555":4,"2722":1,"2861":1,"2879":1}}],["excited",{"2":{"1073":1,"1404":1}}],["exciting",{"2":{"912":1,"1397":1}}],["exchangeresult",{"2":{"1026":10}}],["exchangerateservice",{"2":{"1026":3}}],["exchangeservice",{"2":{"1026":2}}],["exchange",{"2":{"263":1,"383":2,"851":1,"1010":1,"1011":1,"1018":1,"1019":4,"1020":1,"1021":8,"1023":2,"1024":1,"1026":1,"1032":1,"1115":1,"1376":9,"1696":1,"2344":1,"2348":2,"2764":4,"2766":7}}],["excerpt",{"2":{"1418":1,"2328":1}}],["excessive",{"2":{"1170":1,"1769":1,"2258":1}}],["excessively",{"2":{"919":1,"1149":1}}],["except",{"2":{"216":1,"446":1,"710":1,"768":1,"776":1,"851":1,"852":2,"1080":1,"1366":1,"1382":1,"1385":2,"1792":2,"2180":1}}],["exceptions",{"2":{"216":1,"852":1,"864":1,"1071":1,"1106":1,"1568":1,"1741":1,"2242":1,"2289":1,"2384":1,"2763":1}}],["exception",{"0":{"2404":1},"2":{"208":1,"423":1,"424":1,"777":5,"859":1,"861":3,"897":2,"903":1,"1150":1,"1394":2,"1395":1,"1427":2,"1593":1,"1624":1,"1674":2,"1792":5,"1800":1,"1806":2,"1809":2,"1810":1,"2255":4,"2307":1,"2384":2,"2394":1,"2492":1,"2663":1,"2762":1,"2803":4}}],["excelrowdataasjson",{"2":{"1792":1,"2123":1,"2130":2,"2131":1}}],["excelnumericformat",{"2":{"963":2,"1792":1,"2073":1,"2077":2,"2080":1,"2652":2}}],["excellink",{"2":{"961":1}}],["excellently",{"2":{"1090":1}}],["excellent",{"2":{"909":1,"1280":1,"1342":1}}],["excelkey",{"2":{"958":1,"1792":2,"2073":1,"2077":2,"2080":1,"2123":1,"2130":2,"2652":1}}],["excelenabled",{"2":{"958":1,"963":1,"1792":1,"2073":1,"2077":2,"2080":1,"2652":1}}],["exceltimeformat",{"2":{"889":1,"1792":1,"2123":1,"2130":2}}],["exceldatetimeformat",{"2":{"889":1,"963":2,"1792":2,"2073":1,"2077":2,"2080":1,"2123":1,"2130":2,"2652":2}}],["exceldateformat",{"2":{"889":1,"1792":1,"2123":1,"2130":2}}],["exceldatareader",{"2":{"879":1,"881":1,"904":1,"2649":1}}],["excelallsheets",{"2":{"889":1,"1792":1,"2123":1,"2130":2}}],["exceluploadhandler",{"2":{"2615":1}}],["exceluploadrowcommand",{"2":{"1792":1,"2123":1,"2130":2,"2132":1}}],["exceluploadkey",{"2":{"889":1}}],["exceluploadenabled",{"2":{"889":1,"1792":1,"2123":1,"2130":2,"2132":1}}],["excelsheet=data",{"2":{"1374":1}}],["excelsheetname",{"2":{"964":1,"1792":2,"2073":1,"2077":2,"2080":1,"2123":1,"2130":2,"2652":1}}],["excelsheet",{"2":{"723":1,"961":2,"1374":2,"1413":3}}],["excelfilename",{"2":{"723":1,"961":2,"1374":2,"1413":3}}],["excelfilename=report",{"2":{"679":1,"1374":1}}],["excel",{"0":{"678":1,"769":1,"776":1,"788":1,"878":1,"884":1,"892":1,"893":1,"904":1,"947":1,"948":1,"955":1,"962":1,"1183":1,"1201":1,"1202":1,"1273":1,"1374":1,"2077":1,"2130":1,"2131":1,"2652":1},"1":{"770":1,"771":1,"772":1,"773":1,"774":1,"775":1,"776":1,"789":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":2,"957":2,"958":2,"959":1,"960":1,"961":1,"962":1,"963":2,"964":2,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":2,"1203":2,"1204":2,"1205":2,"1206":1,"1207":1,"1208":1,"1274":1,"1275":1,"1276":1,"2078":1,"2079":1,"2131":1},"2":{"161":1,"167":1,"228":3,"673":1,"674":2,"675":7,"678":3,"679":7,"680":1,"682":1,"723":6,"746":3,"747":1,"748":1,"762":1,"769":1,"770":1,"772":6,"773":2,"774":8,"775":4,"776":2,"777":1,"788":18,"789":3,"791":2,"868":1,"878":4,"879":1,"880":1,"881":2,"884":6,"885":1,"887":3,"889":2,"892":1,"893":3,"899":2,"900":3,"902":1,"904":8,"913":1,"947":3,"948":2,"949":1,"952":3,"953":1,"956":3,"957":10,"958":1,"959":3,"960":4,"961":6,"963":3,"964":3,"965":2,"966":1,"968":2,"970":1,"971":2,"1037":4,"1086":1,"1099":7,"1121":1,"1127":1,"1183":3,"1184":2,"1202":2,"1203":2,"1206":1,"1207":2,"1208":2,"1373":1,"1374":2,"1377":1,"1382":1,"1386":2,"1413":3,"1789":1,"1792":15,"2047":1,"2054":1,"2072":1,"2073":1,"2075":1,"2077":10,"2078":1,"2079":5,"2080":2,"2081":2,"2083":1,"2122":1,"2123":2,"2125":1,"2130":6,"2132":1,"2134":2,"2164":6,"2165":4,"2233":2,"2496":2,"2572":1,"2635":2,"2648":1,"2649":4,"2650":1,"2651":1,"2652":4,"2653":5,"2664":1,"2726":1,"2856":1}}],["exceed",{"2":{"953":1,"2145":1,"2146":1}}],["exceeded",{"2":{"480":1,"919":1,"1594":1,"1792":1,"1949":2}}],["exceeds",{"2":{"139":1,"1511":1,"1517":1,"1672":1,"1792":1,"2265":1,"2463":1}}],["exceeding",{"2":{"119":1,"1149":1,"1516":1,"2265":1}}],["exp",{"2":{"1792":1}}],["expansion",{"0":{"1192":1},"2":{"1190":1,"2329":1}}],["expands",{"2":{"1097":1,"1190":1,"1192":1}}],["expanding",{"2":{"328":1,"2217":1}}],["expanded",{"2":{"74":4,"75":1,"77":1,"286":1,"330":1,"335":1,"337":1,"912":1,"915":1,"1073":1,"1192":1,"1258":1,"1431":1,"2222":1,"2265":1,"2348":1,"2504":1,"2512":1,"2518":5,"2519":1,"2522":1,"2523":3,"2587":2,"2588":1}}],["expresses",{"2":{"865":1}}],["expressed",{"2":{"659":1,"665":1,"860":1,"1125":1,"2377":1}}],["expressive",{"2":{"920":1,"1150":1,"1382":1,"1403":1}}],["expressiveness",{"2":{"860":1,"1385":2}}],["expressing",{"2":{"868":1,"1404":1}}],["expression>",{"2":{"528":1}}],["expressions",{"0":{"215":1,"1033":1,"1738":1,"2282":1},"1":{"2283":1,"2284":1,"2285":1,"2286":1},"2":{"215":1,"388":1,"395":1,"436":1,"529":3,"1105":1,"1394":1,"1516":1,"1560":1,"1569":1,"1582":1,"1738":1,"1759":1,"1792":4,"1912":1,"1923":1,"2184":1,"2185":1,"2222":2,"2230":1,"2265":1,"2282":2,"2284":2,"2509":1,"2520":1}}],["expression",{"0":{"2273":1},"2":{"212":1,"215":1,"527":1,"528":2,"529":3,"852":1,"857":1,"866":1,"954":1,"1558":1,"1582":1,"1738":2,"1792":3,"2140":1,"2141":1,"2185":1,"2273":1,"2283":2,"2284":1,"2285":1,"2360":1,"2493":1,"2513":1,"2519":2,"2575":2,"2597":1,"2764":1,"2768":1,"2833":1}}],["express",{"2":{"860":2,"1025":1,"1026":5,"1027":1,"1064":1,"1385":1,"1403":1,"2380":1}}],["experiments",{"2":{"1399":1}}],["experiences",{"2":{"1171":1,"2088":1}}],["experience",{"2":{"972":1,"1009":1,"1073":1,"1098":1,"1421":1}}],["experienced",{"2":{"865":1,"872":1,"1123":1}}],["expertise",{"2":{"1064":1}}],["expert",{"2":{"851":1}}],["experts",{"2":{"851":1}}],["expecting",{"2":{"1168":1,"2454":1}}],["expectations",{"2":{"1076":1}}],["expect",{"2":{"859":1,"1499":1}}],["expects",{"2":{"786":1,"1077":1,"1214":1,"1482":1,"1792":3,"2491":1}}],["expected",{"2":{"56":1,"73":1,"916":1,"929":2,"979":1,"980":1,"986":3,"988":2,"990":2,"994":1,"1003":1,"1076":2,"1152":1,"1169":2,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1390":1,"1399":2,"1792":10,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"2428":1,"2824":1,"2876":1}}],["expensive",{"0":{"1346":1},"2":{"307":1,"477":2,"1161":1,"1163":1,"1179":1,"1328":1,"1346":1,"1351":1,"1363":1,"2177":1,"2218":1,"2815":1}}],["exploring",{"0":{"2693":1,"2700":1},"1":{"2694":1,"2695":1,"2696":1},"2":{"2682":1}}],["explore",{"2":{"1123":1,"2169":1}}],["exploration",{"2":{"1123":1}}],["exploits",{"2":{"941":1}}],["exploit",{"2":{"927":1,"1185":1}}],["explosion",{"2":{"918":1}}],["explaration",{"2":{"1254":1}}],["explain",{"2":{"876":1,"1076":1,"1385":1,"1386":1}}],["explains",{"2":{"852":1,"2007":1,"2170":1,"2190":1,"2328":1}}],["explained",{"0":{"461":1},"1":{"462":1,"463":1,"464":1},"2":{"924":1,"1185":1}}],["explanatory",{"2":{"706":1}}],["explanation",{"2":{"221":1}}],["explicitly",{"2":{"301":1,"319":1,"463":1,"464":1,"624":1,"871":1,"918":1,"933":1,"1098":1,"1134":1,"1396":1,"1408":1,"1459":1,"1599":1,"1644":1,"1792":2,"1875":1,"1961":1,"2001":1,"2016":1,"2024":1,"2343":1,"2351":1,"2353":1,"2375":1,"2384":1,"2438":1,"2442":1,"2479":1,"2486":1,"2494":1,"2551":1,"2577":2,"2632":1,"2682":1}}],["explicit",{"0":{"248":1,"313":1,"324":1},"2":{"0":1,"108":1,"297":1,"318":2,"319":5,"326":2,"624":1,"916":1,"974":1,"980":1,"983":1,"1005":1,"1031":1,"1040":1,"1096":2,"1460":1,"1581":1,"1639":1,"1644":1,"1674":1,"1792":5,"1840":1,"1850":1,"1948":1,"2004":1,"2039":1,"2319":2,"2330":1,"2336":1,"2346":1,"2351":1,"2375":1,"2377":1,"2378":1,"2381":2,"2382":1,"2388":1,"2389":2,"2422":2,"2477":1,"2481":4,"2486":1,"2494":1,"2545":1,"2648":1,"2843":2}}],["exponential",{"2":{"1101":1}}],["exposing",{"2":{"362":1,"1096":1,"1105":1,"1382":1,"1403":1,"2164":1,"2282":1}}],["exposeasendpoint",{"2":{"2660":1}}],["exposes",{"2":{"320":1,"663":1,"1100":1,"1166":1,"1185":1,"1406":1,"1410":1,"1792":1,"2045":1,"2194":1,"2391":1,"2470":1,"2635":1}}],["expose",{"0":{"322":1,"2729":1},"2":{"218":1,"223":2,"241":1,"318":3,"326":3,"431":1,"456":1,"835":1,"937":1,"979":1,"1040":2,"1074":1,"1083":1,"1096":2,"1125":1,"1185":1,"1258":1,"1382":1,"1420":1,"1435":1,"1789":1,"1792":1,"1864":1,"1942":1,"1970":1,"2166":1,"2195":1,"2196":1,"2223":1,"2389":1,"2430":1,"2481":3,"2721":1}}],["exposed",{"2":{"171":1,"244":1,"261":1,"436":1,"867":2,"868":1,"869":1,"872":1,"874":1,"937":2,"1038":1,"1040":1,"1095":1,"1096":1,"1185":2,"1385":1,"1398":1,"1504":1,"1747":1,"1792":2,"1813":1,"1838":1,"1840":1,"1930":1,"2095":1,"2195":1,"2344":1,"2391":1,"2438":1,"2477":1,"2538":1,"2539":2,"2788":1}}],["exposure",{"2":{"173":1,"317":1,"1185":1,"1251":1,"1792":1,"2017":1,"2558":1,"2841":1}}],["exporttypes",{"0":{"1571":1,"2484":1},"2":{"1553":1,"1559":1,"1571":1,"1792":1,"2484":2}}],["exporteventsources",{"0":{"1573":1},"2":{"1416":1,"1417":1,"1553":1,"1563":1,"1581":1,"1792":1,"2830":1,"2838":1}}],["exportedby",{"2":{"1193":1}}],["exported",{"2":{"720":1,"971":1,"1187":1,"1188":2,"1189":1,"1192":2,"1373":1,"1416":1,"1571":1,"1572":1,"1573":1,"1581":1,"2359":1,"2656":1}}],["exporting",{"2":{"953":1}}],["exporturls",{"0":{"1572":1},"2":{"720":1,"1415":1,"1416":1,"1417":1,"1553":1,"1563":1,"1581":1,"1792":1,"2655":1}}],["exports",{"0":{"947":1,"948":1,"1183":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1},"2":{"720":1,"723":1,"948":1,"968":1,"971":1,"1026":1,"1037":3,"1099":1,"1368":1,"1386":2,"1413":1,"1574":1,"2164":2,"2165":1,"2655":1,"2726":1}}],["export",{"0":{"489":1,"723":1,"955":1,"1373":1,"1563":1,"2484":1,"2654":1,"2655":1},"1":{"956":1,"957":1,"958":1,"2655":1,"2656":1},"2":{"85":1,"128":3,"129":1,"342":1,"343":1,"344":1,"386":3,"429":1,"489":2,"490":2,"491":2,"493":3,"543":2,"544":3,"601":1,"602":1,"603":1,"604":1,"719":1,"720":2,"832":1,"894":1,"938":2,"947":3,"948":7,"953":1,"958":1,"959":1,"961":1,"968":3,"969":1,"971":1,"995":2,"1024":1,"1099":3,"1127":1,"1163":1,"1199":2,"1203":1,"1207":1,"1317":2,"1342":1,"1366":1,"1377":1,"1386":1,"1408":2,"1415":2,"1559":1,"1563":2,"1567":1,"1571":3,"1572":1,"1574":1,"1606":2,"1792":2,"2081":1,"2164":1,"2165":1,"2206":3,"2233":1,"2247":1,"2310":1,"2313":1,"2357":1,"2484":3,"2655":1,"2656":1,"2689":4,"2856":1}}],["expirashun",{"2":{"2445":1}}],["expirations",{"2":{"1458":1,"1519":1,"2375":1,"2380":1}}],["expiration",{"0":{"118":1,"1143":1},"2":{"91":1,"101":1,"104":1,"105":1,"106":1,"108":1,"118":1,"120":1,"123":1,"214":1,"230":1,"232":1,"278":1,"281":1,"868":1,"1067":1,"1098":1,"1148":1,"1150":4,"1244":1,"1451":1,"1454":4,"1459":1,"1511":3,"1520":3,"1521":2,"1523":1,"1525":1,"1527":1,"1529":3,"1532":2,"1535":1,"1537":1,"1743":1,"1792":14,"2205":2,"2375":1,"2377":3,"2380":8,"2381":6,"2438":1,"2445":1,"2502":1,"2554":4,"2580":1}}],["expiring",{"0":{"2381":1},"2":{"1227":1,"2381":2}}],["expiretimespan",{"2":{"1459":1,"2375":1}}],["expire",{"2":{"1159":1,"1511":1,"1532":1,"1722":1,"1792":3,"2381":3,"2502":1}}],["expired",{"2":{"1137":1,"1235":1,"1243":1,"1511":1,"1513":1,"1722":1,"1792":3,"1885":1,"2494":1,"2502":1,"2769":1}}],["expires`",{"2":{"1792":1}}],["expiresin",{"2":{"1222":1,"1455":1,"2554":1}}],["expires",{"0":{"91":1,"105":1,"278":1,"1532":2},"1":{"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1},"2":{"91":1,"92":1,"94":1,"95":1,"96":1,"97":1,"101":1,"105":2,"108":1,"110":1,"118":1,"119":1,"120":1,"123":1,"230":1,"278":3,"281":1,"531":1,"835":1,"1068":1,"1113":1,"1135":2,"1143":1,"1147":2,"1149":1,"1179":3,"1213":1,"1214":1,"1232":1,"1234":1,"1235":1,"1511":1,"1515":1,"1521":1,"1529":1,"1532":2,"1535":1,"1537":1,"1792":5,"2033":1,"2037":1,"2041":1,"2205":1,"2207":1,"2210":1,"2212":1,"2274":1,"2329":1,"2380":2,"2381":3,"2551":1,"2580":3,"2745":1}}],["expiry",{"2":{"531":1,"1515":1,"1830":1,"2498":1,"2506":1}}],["examined",{"2":{"2110":1,"2528":1}}],["examine",{"2":{"847":1}}],["example6api",{"2":{"1361":1}}],["example10api",{"2":{"1342":1}}],["example14api",{"2":{"961":1}}],["example8api",{"2":{"1318":1,"1321":1,"2830":1,"2836":1}}],["example2api",{"2":{"976":1,"985":1,"995":1,"996":1,"997":2,"998":1}}],["example7api",{"2":{"894":1}}],["example",{"0":{"157":1,"174":1,"180":1,"187":1,"263":1,"264":1,"298":1,"380":1,"577":1,"584":1,"686":1,"695":1,"705":1,"710":1,"715":1,"750":1,"755":1,"764":1,"774":1,"783":1,"785":1,"787":1,"789":1,"883":1,"884":1,"913":1,"970":1,"1171":1,"1207":1,"1212":1,"1339":1,"1408":1,"1425":1,"1429":1,"1483":1,"1494":1,"1502":1,"1503":1,"1504":1,"1505":1,"1529":1,"1534":1,"1542":1,"1546":1,"1547":1,"1548":1,"1578":1,"1598":1,"1629":1,"1633":1,"1646":1,"1663":1,"1678":1,"1689":1,"1698":1,"1710":1,"1730":1,"1734":1,"1742":1,"1758":1,"1777":1,"1810":1,"1859":1,"1863":1,"1891":1,"1907":1,"1911":1,"1931":1,"1944":1,"1960":1,"1975":1,"1995":1,"2006":1,"2012":1,"2026":1,"2042":1,"2065":1,"2076":1,"2078":1,"2080":1,"2089":1,"2132":1,"2146":1,"2187":1,"2194":1,"2290":1,"2294":1,"2434":1,"2697":1,"2815":1,"2836":1},"1":{"585":1,"586":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1426":1,"1427":1,"1428":1,"1504":1,"1579":1,"1580":1,"1581":1,"1582":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1735":1,"1736":1,"1737":1,"1778":1,"1779":1,"1780":1,"1781":1,"1892":1,"1893":1,"2027":1,"2028":1,"2029":1,"2066":1,"2067":1,"2068":1,"2069":1},"2":{"57":1,"58":1,"75":1,"121":1,"128":2,"206":1,"208":1,"209":2,"211":3,"212":1,"213":3,"214":4,"215":1,"298":1,"302":1,"305":2,"308":2,"309":1,"310":1,"383":2,"390":1,"394":1,"423":4,"430":1,"436":4,"442":2,"444":2,"446":7,"449":2,"453":1,"455":1,"488":1,"489":2,"493":2,"531":1,"533":1,"563":1,"611":1,"612":1,"653":1,"654":1,"695":1,"700":1,"704":1,"841":1,"844":1,"845":3,"847":1,"848":1,"849":1,"851":1,"860":1,"864":1,"872":1,"883":2,"884":2,"886":4,"894":1,"902":2,"904":5,"911":1,"913":3,"914":4,"915":3,"916":6,"917":2,"918":5,"919":3,"920":2,"921":1,"922":2,"924":7,"925":2,"926":4,"928":4,"929":3,"930":15,"932":3,"933":1,"934":5,"935":3,"936":3,"937":4,"938":8,"941":1,"946":1,"947":1,"956":1,"957":1,"959":2,"961":3,"966":1,"970":1,"971":1,"972":1,"976":5,"977":12,"979":10,"980":6,"982":4,"986":4,"987":1,"988":10,"989":3,"990":10,"991":4,"992":2,"994":6,"995":4,"998":2,"1017":2,"1019":4,"1020":1,"1021":6,"1030":1,"1033":1,"1034":3,"1036":1,"1038":1,"1043":1,"1044":1,"1045":2,"1047":2,"1048":2,"1050":2,"1051":4,"1053":4,"1054":7,"1055":4,"1056":7,"1057":6,"1058":4,"1059":1,"1060":6,"1061":1,"1062":4,"1064":1,"1065":2,"1073":1,"1074":3,"1075":1,"1078":2,"1103":1,"1105":2,"1111":1,"1119":1,"1139":1,"1183":1,"1185":3,"1187":1,"1188":3,"1189":1,"1192":5,"1193":1,"1196":1,"1200":2,"1202":1,"1203":1,"1205":1,"1207":3,"1208":1,"1209":1,"1211":1,"1212":1,"1218":1,"1220":1,"1225":2,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1239":1,"1252":1,"1254":1,"1307":4,"1308":3,"1309":3,"1310":3,"1317":1,"1321":3,"1327":1,"1334":1,"1336":2,"1337":2,"1338":5,"1339":6,"1348":1,"1351":1,"1355":2,"1357":3,"1358":3,"1362":2,"1366":1,"1367":1,"1369":2,"1370":1,"1371":2,"1372":7,"1373":1,"1375":3,"1376":8,"1386":16,"1387":3,"1390":3,"1391":5,"1393":5,"1394":4,"1395":5,"1396":3,"1398":9,"1399":1,"1408":3,"1410":2,"1414":1,"1419":1,"1423":2,"1426":2,"1427":4,"1430":1,"1431":6,"1433":1,"1449":4,"1579":1,"1608":1,"1614":3,"1616":2,"1632":1,"1640":2,"1655":1,"1697":3,"1708":1,"1709":3,"1730":3,"1731":3,"1738":1,"1740":3,"1742":1,"1792":34,"1833":1,"1856":1,"1858":1,"1868":1,"1875":2,"1894":2,"1900":2,"1907":1,"1920":2,"1921":1,"1924":1,"1926":1,"1931":1,"1973":1,"1974":1,"2021":1,"2062":1,"2111":1,"2119":1,"2125":1,"2160":1,"2161":1,"2162":1,"2164":1,"2165":2,"2166":1,"2167":2,"2168":1,"2170":1,"2175":1,"2180":1,"2181":1,"2183":1,"2184":1,"2247":1,"2252":2,"2253":2,"2254":1,"2255":1,"2264":1,"2265":1,"2272":1,"2273":1,"2283":1,"2286":1,"2288":3,"2290":1,"2297":1,"2308":1,"2323":1,"2327":1,"2333":1,"2336":1,"2337":3,"2348":2,"2376":1,"2380":1,"2394":1,"2410":1,"2411":2,"2413":2,"2417":1,"2425":2,"2428":1,"2429":2,"2442":1,"2444":1,"2445":1,"2447":1,"2456":1,"2483":1,"2526":2,"2529":1,"2530":1,"2531":1,"2532":1,"2546":1,"2549":3,"2555":1,"2565":1,"2580":2,"2586":1,"2588":2,"2589":1,"2607":1,"2625":1,"2632":3,"2633":5,"2634":1,"2635":2,"2739":1,"2762":1,"2765":1,"2766":1,"2768":2,"2806":1,"2810":1,"2811":4,"2823":1,"2827":1,"2834":2,"2839":2,"2842":2,"2850":1,"2860":2,"2865":1,"2868":1,"2869":2,"2871":1,"2873":1}}],["examples",{"0":{"6":1,"15":1,"36":1,"47":1,"59":1,"70":1,"82":1,"93":1,"103":1,"114":1,"127":1,"135":1,"146":1,"194":1,"205":1,"246":1,"270":1,"287":1,"311":1,"321":1,"331":1,"341":1,"350":1,"359":1,"371":1,"391":1,"400":1,"425":1,"450":1,"465":1,"475":1,"486":1,"500":1,"509":1,"519":1,"530":1,"538":1,"552":1,"561":1,"571":1,"591":1,"600":1,"610":1,"620":1,"630":1,"640":1,"657":1,"676":1,"721":1,"731":1,"796":1,"810":1,"825":1,"1461":1,"1579":1,"1839":1,"1842":1,"2003":1,"2160":1,"2163":1,"2164":1,"2165":1,"2188":1,"2212":1,"2770":1,"2816":1,"2837":1},"1":{"7":1,"8":1,"9":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"37":1,"38":1,"39":1,"40":1,"48":1,"49":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"83":1,"84":1,"85":1,"86":1,"94":1,"95":1,"96":1,"97":1,"104":1,"105":1,"106":1,"107":1,"115":1,"116":1,"117":1,"118":1,"119":1,"128":1,"129":1,"136":1,"137":1,"138":1,"147":1,"148":1,"149":1,"195":1,"196":1,"206":1,"207":1,"208":1,"209":1,"247":1,"248":1,"249":1,"250":1,"251":1,"252":1,"271":1,"272":1,"273":1,"274":1,"275":1,"288":1,"289":1,"290":1,"291":1,"292":1,"312":1,"313":1,"314":1,"322":1,"323":1,"324":1,"325":1,"332":1,"333":1,"334":1,"335":1,"342":1,"343":1,"344":1,"351":1,"352":1,"353":1,"354":1,"360":1,"361":1,"372":1,"373":1,"374":1,"375":1,"392":1,"393":1,"394":1,"401":1,"402":1,"403":1,"426":1,"427":1,"428":1,"451":1,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"476":1,"477":1,"478":1,"479":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"501":1,"502":1,"503":1,"510":1,"511":1,"520":1,"521":1,"522":1,"523":1,"531":1,"532":1,"533":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"553":1,"554":1,"555":1,"562":1,"563":1,"564":1,"565":1,"566":1,"572":1,"573":1,"574":1,"592":1,"593":1,"594":1,"601":1,"602":1,"603":1,"604":1,"611":1,"612":1,"613":1,"614":1,"621":1,"622":1,"623":1,"631":1,"632":1,"633":1,"641":1,"642":1,"643":1,"644":1,"645":1,"658":1,"659":1,"660":1,"661":1,"662":1,"677":1,"678":1,"679":1,"722":1,"723":1,"724":1,"732":1,"733":1,"734":1,"735":1,"736":1,"797":1,"798":1,"799":1,"800":1,"811":1,"812":1,"813":1,"814":1,"815":1,"826":1,"827":1,"828":1,"1462":1,"1463":1,"1464":1,"2161":1,"2162":1,"2163":1,"2164":2,"2165":2,"2166":2,"2167":2,"2168":1,"2169":1},"2":{"92":1,"133":2,"221":1,"306":1,"338":1,"448":1,"845":1,"878":1,"913":1,"921":1,"947":1,"963":1,"970":1,"976":1,"1010":1,"1047":1,"1048":1,"1082":1,"1097":1,"1135":2,"1183":1,"1207":1,"1212":1,"1302":1,"1328":1,"1352":1,"1368":1,"1380":3,"1386":4,"1392":1,"1408":1,"1409":1,"1410":1,"1412":1,"1413":1,"1433":6,"1472":1,"1482":1,"1566":1,"1579":1,"1664":1,"1665":1,"1792":5,"1894":1,"2077":2,"2160":4,"2161":1,"2162":1,"2164":1,"2165":1,"2169":1,"2188":1,"2225":1,"2254":1,"2371":1,"2411":1,"2505":1,"2537":1,"2545":4,"2546":1,"2652":2,"2839":1,"2856":2,"2858":1,"2859":2,"2860":3,"2872":1,"2873":1}}],["exact",{"2":{"656":1,"859":1,"865":1,"879":1,"916":1,"1045":1,"1382":1,"1396":1,"1523":1,"1707":1,"1792":3,"1858":1,"2107":1,"2112":1,"2170":1,"2455":1,"2456":1,"2466":1,"2532":1,"2537":1,"2723":1,"2759":1,"2806":1,"2827":1,"2828":1,"2832":2,"2871":1,"2873":1}}],["exactly",{"2":{"32":1,"102":1,"304":1,"320":1,"370":1,"383":1,"390":1,"448":1,"690":2,"836":1,"845":1,"848":1,"851":1,"852":3,"855":1,"856":1,"857":1,"860":2,"863":1,"864":2,"874":1,"876":1,"920":1,"929":1,"980":1,"983":1,"995":1,"1002":1,"1045":1,"1065":1,"1079":1,"1096":2,"1122":1,"1176":1,"1185":1,"1214":1,"1366":1,"1371":1,"1382":3,"1385":2,"1401":1,"1423":1,"1430":1,"1431":1,"1441":1,"1442":1,"1792":2,"1825":1,"1958":1,"2348":1,"2377":1,"2379":1,"2389":2,"2422":1,"2452":1,"2461":1,"2465":3,"2470":1,"2477":1,"2487":1,"2498":2,"2506":1,"2511":1,"2527":1,"2528":1,"2529":1,"2531":1,"2533":1,"2546":1,"2677":1,"2724":1,"2742":1,"2799":1,"2862":1,"2864":1,"2868":2,"2869":1,"2876":1,"2878":1}}],["enlist=false",{"2":{"2824":1,"2825":1}}],["engaged",{"2":{"2459":1}}],["engages",{"2":{"2423":1,"2462":1}}],["engineer",{"2":{"865":1}}],["engineers",{"2":{"857":1}}],["engineering",{"2":{"847":2,"852":1,"1078":1,"1096":1}}],["engine=memory",{"2":{"848":1}}],["engine=",{"2":{"848":1}}],["engines",{"2":{"848":1}}],["engine",{"0":{"857":1},"2":{"377":1,"848":2,"849":1,"851":1,"852":1,"857":2,"860":3,"861":2,"864":2,"865":2,"874":1,"875":1,"1077":1,"1134":1,"1403":1,"1432":1,"1792":2,"1802":1,"2095":1,"2096":1,"2333":1,"2537":2,"2539":1,"2794":1,"2795":2}}],["enhancement",{"0":{"2371":1},"2":{"2371":1}}],["enjoy",{"2":{"1081":1,"1254":1,"1370":2,"2822":1,"2824":1,"2850":2}}],["enums",{"2":{"2267":1}}],["enum",{"2":{"872":1,"1605":1,"2277":1,"2330":3,"2372":1,"2497":1,"2621":2,"2670":1,"2688":1}}],["energy",{"2":{"871":1,"1037":1,"1403":1,"1405":3}}],["enemy",{"2":{"857":1,"865":1}}],["enforcing",{"2":{"864":1,"985":1}}],["enforcement",{"2":{"864":2,"865":1,"1199":1,"2223":1,"2490":1,"2506":1}}],["enforces",{"0":{"982":1},"2":{"843":1,"845":2,"863":1,"940":1,"974":1,"1419":1}}],["enforced",{"0":{"2490":1},"2":{"320":1,"327":1,"803":1,"852":1,"863":1,"864":2,"865":3,"921":1,"975":1,"983":1,"1004":1,"1005":3,"1009":1,"1045":1,"1077":1,"1098":1,"1792":1,"1825":1,"1830":1,"1832":1,"1833":1,"1834":1,"2223":1,"2481":1,"2498":1}}],["enforce",{"2":{"175":1,"864":2,"1500":1,"1792":1}}],["en",{"2":{"841":1,"1792":16,"2257":4,"2632":2,"2633":1,"2634":1}}],["enough",{"2":{"836":1,"864":1,"1037":1,"1079":1,"1150":1,"1165":1,"1402":2,"2459":1}}],["enabling",{"0":{"1141":1,"1157":1,"1981":1,"2689":1,"2761":1,"2808":1},"2":{"529":1,"880":1,"1094":1,"1105":2,"1111":1,"1130":1,"1825":1,"1942":1,"2110":1,"2185":1,"2284":1,"2329":1,"2353":1,"2481":1,"2530":1,"2550":1,"2615":1,"2625":1,"2759":1,"2791":1,"2806":1}}],["enableforhttps",{"2":{"1792":1,"1936":1,"1937":1,"1942":1,"1944":2}}],["enableregister",{"2":{"1217":2,"1220":2,"1224":1,"1253":1,"1792":1,"1870":1,"1874":1,"1893":1}}],["enables",{"2":{"179":2,"384":1,"414":1,"650":1,"666":1,"667":1,"761":1,"771":1,"779":1,"885":1,"904":1,"905":1,"998":1,"1062":1,"1187":1,"1362":1,"1458":1,"1511":1,"1745":1,"1792":5,"1816":1,"1979":1,"2024":1,"2264":1,"2300":1,"2346":1,"2347":1,"2371":1,"2531":1,"2532":1,"2554":1,"2633":1,"2664":1}}],["enabled=true",{"2":{"1521":1,"2155":1}}],["enabled",{"0":{"177":1,"1816":1,"2001":1,"2155":1,"2350":1},"1":{"178":1,"179":1,"180":1,"181":1},"2":{"102":1,"106":2,"107":1,"121":3,"176":1,"177":3,"178":2,"179":2,"180":2,"182":1,"216":1,"223":1,"317":1,"336":1,"378":1,"430":2,"436":1,"449":1,"455":1,"476":2,"477":2,"478":2,"479":2,"577":1,"624":1,"673":2,"688":1,"704":1,"718":2,"720":1,"868":2,"889":1,"919":1,"937":1,"958":1,"963":1,"966":1,"967":2,"1022":1,"1045":1,"1054":1,"1059":2,"1062":2,"1067":2,"1068":1,"1069":2,"1135":2,"1141":1,"1145":1,"1146":1,"1147":2,"1148":1,"1150":4,"1152":2,"1153":1,"1154":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":3,"1177":5,"1196":1,"1197":1,"1199":1,"1217":2,"1224":1,"1340":1,"1356":1,"1408":2,"1415":1,"1416":1,"1417":3,"1418":1,"1445":1,"1449":1,"1455":1,"1458":3,"1459":1,"1469":1,"1475":2,"1477":2,"1482":1,"1488":1,"1489":1,"1494":1,"1498":1,"1499":1,"1502":1,"1503":1,"1505":1,"1510":1,"1511":1,"1513":1,"1514":1,"1515":2,"1516":2,"1517":1,"1518":1,"1520":4,"1521":1,"1522":1,"1529":4,"1534":2,"1548":1,"1553":1,"1554":1,"1572":1,"1579":1,"1580":1,"1581":1,"1582":1,"1587":1,"1588":1,"1597":1,"1598":1,"1605":5,"1615":1,"1617":1,"1622":1,"1623":1,"1625":1,"1633":1,"1638":1,"1639":1,"1640":2,"1641":1,"1644":1,"1646":2,"1650":1,"1651":1,"1661":1,"1662":1,"1663":2,"1683":1,"1684":1,"1690":2,"1696":1,"1697":2,"1698":3,"1702":1,"1703":1,"1706":1,"1707":1,"1708":1,"1709":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1721":1,"1722":1,"1735":1,"1752":1,"1753":1,"1758":3,"1759":1,"1763":1,"1764":1,"1769":1,"1770":1,"1771":1,"1776":1,"1778":2,"1779":1,"1780":1,"1781":1,"1792":83,"1814":1,"1824":1,"1867":1,"1874":1,"1892":1,"1893":1,"1897":1,"1898":1,"1899":1,"1907":1,"1909":1,"1911":1,"1912":1,"1916":1,"1917":1,"1926":1,"1928":1,"1931":2,"1936":1,"1937":1,"1944":2,"1948":1,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1955":3,"1958":3,"1959":2,"1960":6,"1966":1,"1967":1,"1974":1,"1979":1,"1980":1,"1981":2,"1983":1,"1995":2,"1999":1,"2000":2,"2001":1,"2003":1,"2010":1,"2012":1,"2015":1,"2016":2,"2017":1,"2018":2,"2019":1,"2020":1,"2021":1,"2027":1,"2028":1,"2029":1,"2033":2,"2034":1,"2035":1,"2037":1,"2038":1,"2039":1,"2040":1,"2042":2,"2046":1,"2047":1,"2049":1,"2054":1,"2055":1,"2058":1,"2059":1,"2060":1,"2061":2,"2062":1,"2063":1,"2064":1,"2066":1,"2067":1,"2068":1,"2069":1,"2073":1,"2074":1,"2075":1,"2077":1,"2080":1,"2106":2,"2111":8,"2123":1,"2124":1,"2132":1,"2138":1,"2139":1,"2142":1,"2146":1,"2154":2,"2155":1,"2156":1,"2178":1,"2247":1,"2254":1,"2257":5,"2264":1,"2265":1,"2267":1,"2274":1,"2291":1,"2297":2,"2308":2,"2330":3,"2333":1,"2350":2,"2351":2,"2352":1,"2353":2,"2357":1,"2359":1,"2360":1,"2375":4,"2378":4,"2379":3,"2380":7,"2381":2,"2389":1,"2394":1,"2405":1,"2412":3,"2421":1,"2422":1,"2429":1,"2434":1,"2441":1,"2443":4,"2470":1,"2471":2,"2476":2,"2481":1,"2486":2,"2492":1,"2497":5,"2502":1,"2520":1,"2532":8,"2537":3,"2541":3,"2542":2,"2549":4,"2551":2,"2554":1,"2558":1,"2565":2,"2575":1,"2597":1,"2607":1,"2611":2,"2632":5,"2633":1,"2634":2,"2635":1,"2645":1,"2655":1,"2688":5,"2689":1,"2690":2,"2719":1,"2736":1,"2746":1,"2761":1,"2769":2,"2808":1,"2814":2,"2821":2,"2824":1,"2825":2,"2841":1,"2858":1,"2871":1,"2878":1}}],["enable",{"0":{"180":1,"732":1,"2745":1,"2746":1},"2":{"43":1,"54":1,"55":1,"99":1,"112":1,"131":1,"151":1,"170":2,"176":1,"177":1,"201":1,"223":1,"225":1,"230":1,"233":1,"236":1,"327":1,"336":1,"346":1,"455":1,"456":2,"606":1,"635":1,"648":1,"649":1,"688":1,"720":4,"728":1,"794":1,"851":1,"868":1,"889":1,"917":4,"924":1,"958":1,"1005":1,"1022":1,"1065":1,"1141":1,"1175":1,"1193":1,"1217":1,"1224":2,"1245":1,"1252":2,"1340":1,"1356":1,"1367":1,"1408":1,"1421":1,"1434":1,"1447":1,"1451":1,"1454":1,"1475":1,"1477":1,"1486":1,"1489":1,"1494":1,"1499":1,"1506":1,"1508":1,"1511":1,"1516":1,"1530":1,"1535":1,"1537":1,"1540":4,"1544":3,"1549":2,"1554":1,"1588":1,"1616":1,"1623":1,"1639":2,"1651":1,"1684":1,"1696":1,"1703":1,"1717":1,"1720":1,"1722":1,"1753":1,"1764":1,"1792":39,"1803":1,"1804":1,"1805":1,"1807":1,"1825":1,"1860":1,"1864":1,"1867":1,"1874":2,"1898":1,"1917":1,"1932":3,"1934":1,"1937":2,"1942":1,"1944":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1967":1,"1973":2,"1980":1,"1981":1,"1994":1,"2000":1,"2001":2,"2012":1,"2016":1,"2034":1,"2038":1,"2047":1,"2052":1,"2059":1,"2074":1,"2075":1,"2077":1,"2110":1,"2124":1,"2126":1,"2127":1,"2128":1,"2130":1,"2133":1,"2136":1,"2139":1,"2172":1,"2175":1,"2183":1,"2184":2,"2205":1,"2254":1,"2264":2,"2291":1,"2330":1,"2353":1,"2481":1,"2530":1,"2554":1,"2587":2,"2632":1,"2633":2,"2634":2,"2635":3,"2641":1,"2689":1,"2733":1,"2824":1,"2825":1,"2835":1,"2841":1}}],["enriched",{"2":{"452":2,"1338":1,"1347":2,"1974":1,"2607":1}}],["enrich",{"2":{"452":1,"1037":1,"1105":2,"1332":1,"2803":1,"2806":1}}],["enrichment",{"0":{"452":1,"1347":1},"2":{"876":1,"1029":1,"1035":1,"1328":1,"1351":1,"1974":1,"2607":1}}],["envfile",{"0":{"2272":1},"2":{"1603":1,"1604":1,"1608":2,"1792":1,"2272":3,"2719":1}}],["env",{"0":{"1608":1},"2":{"390":4,"395":1,"873":1,"926":1,"1107":2,"1119":2,"1441":1,"1574":1,"1604":3,"1605":3,"1607":1,"1608":4,"1792":2,"2040":6,"2119":1,"2224":1,"2272":4,"2456":1,"2474":1,"2476":3,"2477":1,"2497":3,"2534":1,"2688":3,"2704":1,"2705":2,"2719":3}}],["environmentname",{"2":{"1792":3,"1808":1,"2116":1,"2117":1,"2119":1,"2701":1,"2702":1,"2704":1}}],["environments",{"2":{"1145":1,"1146":1,"1708":1,"1792":2,"1944":1,"1974":1,"1983":1,"2059":1,"2346":1,"2543":1,"2607":1,"2635":1,"2680":1}}],["environment",{"0":{"390":1,"1606":1,"1607":1,"1615":1,"1862":1,"2040":1,"2483":1,"2497":1,"2687":1,"2689":1,"2690":1,"2719":1},"1":{"2688":1,"2689":1,"2690":1},"2":{"212":1,"388":1,"390":6,"394":1,"395":1,"534":1,"873":2,"926":1,"1026":1,"1067":1,"1080":1,"1255":1,"1416":1,"1457":1,"1574":1,"1604":3,"1606":1,"1607":1,"1608":2,"1615":1,"1633":1,"1661":2,"1738":1,"1785":1,"1787":1,"1792":17,"1794":1,"1800":2,"1807":2,"1808":3,"1856":1,"1862":3,"2038":1,"2040":3,"2117":1,"2119":3,"2223":2,"2224":2,"2272":3,"2474":2,"2476":1,"2477":1,"2483":3,"2532":1,"2537":1,"2543":2,"2645":2,"2680":1,"2681":1,"2687":2,"2689":2,"2690":1,"2697":1,"2702":1,"2704":3,"2705":3,"2764":1,"2768":1,"2772":1,"2804":3,"2874":1}}],["ensures",{"2":{"666":1,"933":1,"937":1,"944":1,"985":1,"989":1,"1147":1,"1371":1,"1621":1,"1982":1,"2359":1,"2589":1,"2603":1}}],["ensure",{"2":{"380":1,"844":1,"929":1,"1074":1,"1169":1,"1178":1,"1193":1,"1654":1,"2161":1,"2333":1,"2792":1,"2819":1}}],["ensuring",{"2":{"366":1,"930":1,"974":1,"986":1,"994":1,"2776":1}}],["encapsulate",{"2":{"974":1,"1096":1}}],["encapsulation",{"0":{"1437":1},"2":{"843":4,"849":1,"851":1,"1385":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1}}],["encoding=textencoding",{"2":{"1202":1}}],["encoding",{"2":{"843":1,"1138":1,"1198":1,"1218":1,"1340":1,"1792":2,"1916":2,"1917":2,"1931":2,"1940":1,"1941":1,"2309":1,"2495":1,"2549":2,"2814":2,"2824":1}}],["encoder",{"2":{"2040":1}}],["encodes",{"2":{"1403":1}}],["encodeuricomponent",{"2":{"1364":1,"1574":2}}],["encode",{"2":{"308":5,"1214":2,"1232":3,"1234":2,"2177":2}}],["encoded",{"2":{"58":1,"64":1,"1214":1,"1232":2,"1234":1,"1243":1,"1792":2,"1856":1,"1882":2,"1884":1,"1925":2,"2517":2}}],["enclosed",{"2":{"768":2,"786":3,"891":2,"2128":1}}],["encrypting",{"2":{"2565":1}}],["encryptionalgorithm",{"2":{"1650":1,"1651":1,"1656":1,"1663":1,"1792":1}}],["encryption",{"0":{"1054":1,"1656":1,"1659":1,"1660":1,"1661":1,"1662":1,"1664":1,"2565":1},"1":{"1660":1,"1661":1,"1662":1},"2":{"182":2,"187":1,"188":1,"189":1,"191":1,"868":1,"1054":1,"1100":5,"1127":1,"1198":1,"1496":1,"1649":2,"1651":4,"1656":1,"1658":1,"1660":1,"1662":2,"1664":3,"1667":1,"1788":1,"1792":6,"1795":1,"2291":2,"2294":1,"2296":1,"2297":3,"2329":1,"2565":2,"2856":1}}],["encrypts",{"2":{"184":1,"1098":1,"2292":1}}],["encrypt",{"0":{"182":1,"183":1,"2291":1,"2292":1},"1":{"183":1,"184":2,"185":1,"186":1,"187":1,"188":1,"189":1,"190":1,"191":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1},"2":{"182":1,"184":6,"187":1,"188":1,"237":2,"868":2,"1100":1,"1127":1,"1382":1,"1649":1,"1651":1,"1662":1,"1664":4,"1665":1,"1667":1,"2230":1,"2291":1,"2292":5,"2294":1,"2295":1,"2296":1,"2297":1,"2323":1,"2329":1,"2353":2}}],["encrypted",{"0":{"2757":1},"2":{"64":1,"182":2,"187":1,"188":5,"835":1,"1054":3,"1086":2,"1098":5,"1100":1,"1126":1,"1445":1,"1450":1,"1457":1,"1653":1,"1659":1,"1664":3,"1792":4,"2171":1,"2173":1,"2291":2,"2294":1,"2295":1,"2296":5,"2297":4,"2554":1}}],["entities",{"2":{"851":2,"852":1,"916":1}}],["entity",{"2":{"844":1,"845":1,"849":1,"857":1,"861":1,"871":1,"874":1,"1366":2}}],["entire",{"0":{"84":1},"2":{"81":1,"88":1,"119":1,"668":1,"847":1,"849":2,"851":2,"852":1,"861":1,"864":1,"865":1,"868":1,"872":2,"876":1,"888":1,"903":1,"909":1,"914":1,"915":1,"920":1,"948":3,"949":1,"958":1,"969":1,"977":1,"979":1,"993":1,"1005":1,"1006":1,"1021":1,"1075":2,"1076":2,"1096":2,"1098":1,"1108":1,"1185":1,"1211":1,"1309":2,"1324":1,"1399":1,"1400":1,"1401":1,"1403":1,"1404":1,"1406":1,"1431":1,"1517":1,"1557":1,"1755":1,"1756":1,"1758":1,"1792":3,"1868":1,"1909":1,"2265":1,"2320":1,"2337":1,"2389":1,"2481":1,"2537":1,"2615":1,"2678":1,"2695":1,"2853":1,"2871":1,"2875":1,"2879":1}}],["entirely",{"2":{"0":1,"301":1,"347":1,"421":1,"445":1,"582":1,"586":1,"587":1,"848":2,"851":2,"866":1,"871":1,"872":1,"942":1,"1033":1,"1037":1,"1038":1,"1066":1,"1067":1,"1069":1,"1075":1,"1101":1,"1108":1,"1139":1,"1150":2,"1162":1,"1209":1,"1328":1,"1338":1,"1384":1,"1398":1,"1511":1,"1524":1,"1525":1,"1529":1,"1569":1,"1575":1,"1746":1,"1792":6,"1925":1,"1929":1,"1956":1,"2021":1,"2265":1,"2337":2,"2346":1,"2347":1,"2379":1,"2414":1,"2421":1,"2440":1,"2510":1,"2537":2,"2544":1,"2632":1,"2656":1,"2762":1,"2794":1,"2815":1,"2840":1,"2854":1,"2874":1}}],["entra",{"2":{"1045":1,"1792":1,"1825":1}}],["entries",{"0":{"2381":1},"2":{"108":1,"116":1,"356":1,"1024":2,"1067":2,"1142":1,"1143":1,"1359":1,"1511":2,"1513":1,"1522":2,"1523":1,"1532":1,"1703":2,"1706":3,"1722":1,"1792":12,"1801":1,"1898":1,"1900":1,"1957":1,"2111":1,"2221":1,"2254":1,"2379":1,"2380":3,"2381":3,"2410":1,"2422":1,"2495":1,"2502":2,"2537":1,"2633":2,"2666":1,"2769":1}}],["entrypoint",{"2":{"1420":1}}],["entry",{"2":{"105":1,"347":1,"453":1,"650":1,"663":1,"693":1,"694":1,"778":1,"834":1,"887":1,"903":3,"976":1,"1067":1,"1102":1,"1147":1,"1148":1,"1305":1,"1458":1,"1518":3,"1521":1,"1523":2,"1527":1,"1792":9,"1802":1,"1852":1,"1957":1,"2094":1,"2098":1,"2111":1,"2112":1,"2254":1,"2265":3,"2375":1,"2379":1,"2380":3,"2381":1,"2383":1,"2411":1,"2427":1,"2432":1,"2436":1,"2489":1,"2504":1,"2532":1,"2533":1,"2534":1,"2537":1,"2544":1,"2679":1,"2752":1,"2812":1,"2828":1,"2871":1}}],["enters",{"2":{"1217":1,"1341":1,"1792":1}}],["enterprise",{"0":{"1064":1},"1":{"1065":1},"2":{"841":1,"851":1,"908":1,"948":1,"1121":1,"1127":1,"1230":2,"1382":2,"1792":3,"1880":2,"2438":1,"2776":1}}],["enter",{"2":{"51":1,"1200":1,"1202":1}}],["endless",{"2":{"1075":1}}],["ends",{"2":{"301":1,"685":1,"706":1,"861":1,"868":1,"873":1,"994":1,"1038":1,"1079":1,"1792":1,"1847":1,"2529":1,"2741":1,"2868":1,"2869":1}}],["endings",{"0":{"342":1,"343":1}}],["ending",{"2":{"245":1,"840":1,"849":1,"1792":1,"2197":1}}],["ended",{"2":{"106":1,"1066":1,"1067":1,"1402":1,"1525":1,"1529":1,"1609":1,"2391":1,"2661":1}}],["end",{"0":{"972":2,"1409":2},"1":{"973":2,"974":2,"975":2,"976":2,"977":2,"978":2,"979":2,"980":2,"981":2,"982":2,"983":2,"984":2,"985":2,"986":2,"987":2,"988":2,"989":2,"990":2,"991":2,"992":2,"993":2,"994":2,"995":2,"996":2,"997":2,"998":2,"999":2,"1000":2,"1001":2,"1002":2,"1003":2,"1004":2,"1005":2,"1006":2,"1007":2,"1008":2,"1009":2},"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"37":2,"38":2,"39":2,"40":1,"48":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"104":1,"106":1,"115":1,"116":1,"117":1,"119":1,"128":1,"136":1,"137":1,"157":1,"184":1,"186":1,"206":1,"207":2,"208":2,"209":3,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"264":1,"288":1,"289":1,"290":1,"291":1,"292":1,"296":2,"310":5,"313":2,"332":1,"333":1,"334":1,"335":1,"360":1,"361":1,"365":1,"366":1,"374":1,"401":1,"405":1,"406":1,"408":2,"415":1,"423":1,"426":1,"427":1,"428":1,"436":1,"438":1,"439":2,"449":2,"451":2,"452":2,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"584":2,"592":1,"593":1,"594":1,"611":1,"621":1,"641":1,"646":1,"658":1,"659":1,"664":2,"665":1,"677":1,"679":1,"685":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"750":1,"751":1,"752":1,"755":1,"756":1,"764":2,"765":1,"766":1,"774":2,"777":1,"797":1,"798":1,"799":1,"811":1,"812":1,"813":1,"814":1,"815":1,"826":1,"831":1,"833":2,"835":1,"845":1,"849":3,"851":1,"864":1,"866":2,"871":2,"872":4,"875":2,"883":1,"884":1,"886":1,"888":2,"896":1,"897":2,"904":2,"914":2,"915":1,"916":3,"918":1,"922":2,"928":3,"929":5,"930":1,"934":1,"935":1,"936":1,"946":2,"956":1,"972":2,"979":2,"980":2,"982":1,"986":1,"988":2,"989":3,"990":4,"991":1,"992":3,"994":2,"1005":1,"1009":2,"1021":5,"1029":1,"1037":4,"1054":2,"1055":1,"1056":2,"1057":1,"1058":1,"1060":2,"1065":2,"1076":1,"1086":3,"1088":3,"1105":5,"1111":2,"1113":1,"1127":2,"1135":1,"1138":1,"1139":1,"1141":1,"1142":1,"1149":1,"1179":5,"1183":1,"1184":1,"1185":2,"1188":1,"1192":1,"1197":1,"1214":1,"1215":1,"1216":1,"1220":3,"1221":3,"1222":3,"1232":2,"1234":3,"1235":1,"1236":2,"1239":2,"1308":1,"1309":1,"1310":1,"1321":1,"1331":1,"1332":3,"1337":1,"1338":3,"1339":3,"1345":2,"1347":1,"1348":1,"1357":1,"1362":1,"1368":1,"1372":2,"1376":6,"1386":2,"1393":1,"1394":2,"1395":5,"1396":2,"1409":4,"1419":1,"1427":3,"1429":1,"1431":1,"1433":2,"1442":2,"1504":2,"1547":1,"1632":1,"1655":2,"1689":2,"1727":3,"1736":2,"1742":1,"1745":1,"1920":1,"1921":2,"1924":1,"1926":1,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2147":1,"2164":4,"2170":2,"2264":2,"2283":1,"2290":1,"2292":1,"2293":1,"2303":1,"2304":1,"2337":2,"2338":1,"2342":1,"2343":1,"2346":1,"2385":1,"2398":2,"2407":2,"2435":2,"2498":2,"2521":2,"2528":1,"2532":1,"2546":6,"2549":4,"2572":1,"2575":1,"2580":2,"2762":2,"2764":1,"2766":3,"2775":1,"2798":1,"2802":1,"2809":1,"2810":2,"2812":1,"2815":3,"2818":1,"2822":1,"2826":2,"2829":2,"2834":3,"2836":1,"2855":2,"2858":2,"2864":1,"2868":2,"2871":1}}],["endpointcreated",{"2":{"2487":1}}],["endpointrequestingannotations",{"2":{"2482":1}}],["endpointsource",{"2":{"2824":1,"2825":1}}],["endpointsourcescreated",{"2":{"2369":1}}],["endpointsources",{"2":{"2369":1,"2389":1}}],["endpoints",{"0":{"265":1,"306":1,"828":1,"866":1,"966":1,"1148":1,"1186":1,"1368":1,"1386":1,"1398":1,"1412":1,"1413":1,"1518":1,"1747":1,"1930":1,"2048":1,"2182":1,"2489":1,"2504":1,"2529":1,"2634":1,"2635":1,"2672":1,"2674":1,"2720":1,"2739":1,"2749":1,"2767":1,"2797":1,"2798":1,"2806":1,"2839":1,"2865":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"967":1,"1187":1,"1188":1,"1189":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1399":1,"2049":1,"2050":1,"2051":1,"2052":1,"2183":1,"2184":1,"2185":1,"2721":1,"2722":1,"2723":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2729":1,"2807":1,"2808":1,"2809":1,"2810":1,"2811":1,"2812":1,"2813":1,"2814":1,"2815":1,"2816":1,"2817":1,"2840":1,"2841":1,"2842":1,"2843":1,"2844":1,"2845":1,"2846":1,"2847":1,"2848":1,"2849":1,"2850":1,"2851":1,"2852":1,"2853":1,"2854":1,"2855":1,"2856":1,"2857":1,"2858":1,"2859":1},"2":{"10":1,"91":1,"102":1,"109":1,"150":1,"165":1,"170":2,"174":1,"175":1,"177":1,"179":1,"220":1,"238":1,"244":3,"258":1,"296":1,"314":1,"316":2,"320":1,"324":1,"338":1,"352":2,"357":1,"369":1,"376":1,"382":1,"385":1,"388":1,"429":1,"431":1,"456":1,"470":2,"556":2,"559":2,"567":1,"609":1,"625":1,"656":1,"679":1,"681":1,"684":1,"691":1,"694":1,"718":1,"720":3,"722":1,"723":1,"724":2,"737":1,"778":1,"792":1,"801":1,"827":1,"828":1,"829":1,"833":1,"834":1,"835":1,"837":1,"867":1,"868":4,"869":4,"872":3,"876":1,"904":1,"911":1,"920":1,"925":2,"946":1,"948":1,"961":1,"966":3,"967":1,"971":1,"975":1,"986":1,"1007":1,"1036":1,"1037":4,"1045":1,"1048":1,"1052":1,"1064":1,"1065":2,"1067":2,"1073":4,"1074":1,"1077":2,"1078":3,"1080":2,"1084":1,"1086":2,"1095":6,"1096":5,"1100":5,"1101":1,"1106":1,"1121":1,"1122":1,"1125":4,"1127":2,"1138":1,"1140":1,"1141":2,"1142":1,"1148":1,"1150":3,"1158":1,"1162":1,"1163":1,"1176":1,"1177":1,"1178":1,"1181":1,"1182":1,"1183":1,"1184":1,"1185":1,"1190":1,"1193":1,"1199":1,"1200":1,"1204":1,"1205":1,"1208":2,"1211":3,"1220":1,"1221":1,"1226":1,"1245":1,"1250":1,"1253":1,"1276":1,"1281":1,"1303":1,"1327":1,"1328":2,"1329":2,"1343":1,"1351":2,"1367":1,"1368":1,"1371":1,"1377":2,"1379":1,"1380":1,"1382":1,"1383":1,"1385":11,"1386":4,"1393":1,"1396":1,"1398":3,"1404":1,"1405":1,"1406":1,"1411":1,"1412":1,"1413":1,"1414":2,"1420":2,"1441":1,"1465":1,"1467":1,"1468":1,"1470":1,"1475":1,"1477":1,"1486":1,"1511":1,"1518":1,"1519":1,"1520":2,"1522":1,"1527":1,"1535":1,"1537":1,"1540":2,"1544":2,"1552":1,"1569":1,"1573":1,"1575":2,"1581":1,"1588":1,"1599":1,"1600":1,"1609":1,"1664":1,"1679":1,"1688":1,"1690":1,"1699":1,"1719":1,"1745":1,"1747":1,"1748":1,"1751":1,"1754":1,"1758":1,"1759":1,"1762":1,"1764":1,"1765":1,"1782":1,"1784":1,"1789":1,"1791":2,"1792":84,"1825":2,"1838":1,"1840":6,"1841":1,"1843":1,"1846":2,"1847":1,"1861":1,"1868":3,"1871":1,"1876":1,"1895":1,"1896":1,"1898":2,"1908":2,"1912":1,"1913":1,"1915":1,"1917":1,"1924":2,"1927":1,"1930":1,"1932":2,"1934":1,"1947":1,"1949":1,"1961":1,"1962":1,"1964":1,"1973":2,"1984":2,"1986":1,"1987":1,"1988":1,"1989":2,"1995":1,"1998":1,"2000":1,"2001":1,"2004":2,"2009":1,"2010":2,"2011":2,"2013":1,"2045":2,"2047":3,"2058":1,"2059":2,"2070":1,"2092":1,"2095":2,"2106":1,"2107":1,"2112":1,"2133":1,"2136":1,"2153":1,"2156":2,"2158":1,"2159":1,"2160":1,"2164":1,"2165":2,"2167":1,"2170":1,"2175":1,"2184":1,"2185":1,"2189":1,"2190":2,"2191":1,"2198":1,"2208":1,"2209":3,"2222":2,"2228":1,"2234":2,"2254":2,"2257":3,"2258":1,"2265":3,"2267":1,"2270":1,"2271":1,"2277":1,"2278":3,"2309":1,"2310":1,"2313":4,"2317":2,"2320":2,"2325":2,"2329":2,"2330":1,"2332":1,"2334":1,"2338":1,"2339":1,"2346":2,"2347":3,"2351":2,"2354":2,"2356":2,"2357":3,"2358":1,"2362":1,"2365":1,"2372":1,"2380":3,"2384":1,"2388":1,"2389":4,"2394":1,"2419":1,"2420":2,"2431":2,"2434":1,"2436":1,"2438":1,"2481":1,"2482":1,"2489":3,"2494":1,"2500":1,"2504":1,"2508":1,"2515":1,"2520":1,"2523":1,"2525":2,"2527":1,"2529":2,"2532":1,"2537":2,"2538":2,"2539":1,"2540":1,"2541":1,"2542":3,"2549":2,"2550":1,"2558":1,"2572":1,"2580":3,"2634":10,"2635":9,"2638":4,"2641":2,"2645":1,"2656":1,"2661":1,"2666":1,"2672":2,"2674":1,"2706":1,"2709":1,"2725":1,"2731":1,"2737":1,"2739":1,"2742":2,"2749":1,"2755":1,"2759":1,"2767":1,"2772":3,"2773":2,"2774":2,"2775":1,"2791":1,"2793":1,"2795":1,"2797":2,"2802":1,"2806":1,"2811":1,"2812":2,"2820":1,"2821":1,"2822":1,"2825":1,"2826":1,"2834":1,"2838":1,"2839":1,"2841":1,"2859":1,"2860":1,"2862":1,"2865":1,"2872":1,"2876":1,"2878":3,"2879":1,"2881":1,"2882":2}}],["endpoint",{"0":{"7":1,"247":1,"284":1,"297":1,"314":1,"592":1,"593":1,"658":1,"659":1,"691":1,"886":1,"904":1,"955":1,"959":1,"1202":1,"1226":1,"1369":1,"1411":1,"1415":1,"1876":1,"1924":1,"2079":1,"2176":1,"2259":1,"2310":1,"2313":1,"2317":1,"2389":1,"2539":1,"2614":1,"2653":1,"2654":1,"2721":1,"2722":1,"2723":1,"2727":1,"2728":1,"2750":1,"2820":1,"2824":1,"2829":1,"2879":1},"1":{"285":1,"286":1,"887":1,"956":1,"957":1,"958":1,"960":1,"1412":1,"1413":1,"1414":1,"1415":1,"2177":1,"2178":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2655":1,"2656":1,"2821":1,"2822":1},"2":{"4":1,"7":1,"12":1,"13":1,"16":1,"18":1,"19":1,"20":1,"27":2,"31":1,"32":1,"37":2,"39":2,"41":2,"43":1,"48":1,"50":2,"52":1,"55":1,"61":1,"71":1,"101":2,"104":1,"108":1,"109":1,"110":1,"115":1,"128":1,"132":1,"136":1,"139":1,"144":1,"154":1,"156":1,"157":1,"160":1,"161":1,"163":1,"165":1,"167":2,"171":1,"173":1,"174":2,"175":1,"177":1,"179":1,"180":2,"181":1,"184":1,"192":1,"196":1,"206":2,"218":1,"223":6,"224":2,"227":1,"239":2,"241":1,"244":1,"247":1,"252":4,"261":2,"263":1,"264":2,"265":1,"266":1,"282":2,"284":1,"288":1,"294":1,"296":2,"297":3,"298":2,"301":1,"306":1,"312":1,"316":1,"317":1,"320":5,"325":1,"327":1,"336":3,"347":3,"351":2,"353":1,"360":1,"368":1,"369":2,"380":1,"384":1,"386":1,"388":1,"397":1,"401":1,"402":2,"411":1,"415":1,"421":2,"423":1,"429":1,"431":1,"433":1,"435":1,"436":7,"438":2,"439":1,"445":2,"448":2,"452":1,"453":1,"456":1,"466":1,"470":1,"473":1,"476":1,"478":1,"480":2,"487":1,"503":1,"510":1,"515":1,"520":1,"524":3,"526":1,"536":1,"539":1,"545":2,"556":1,"586":1,"589":1,"592":1,"595":1,"597":1,"615":1,"636":2,"641":3,"648":1,"649":1,"650":2,"652":2,"653":2,"654":3,"656":1,"658":2,"660":2,"661":2,"662":4,"668":1,"669":2,"675":2,"677":1,"688":2,"689":1,"690":1,"693":2,"696":1,"703":1,"708":1,"713":1,"720":2,"722":1,"728":1,"733":1,"743":1,"750":1,"764":1,"774":1,"780":1,"792":1,"794":1,"797":1,"807":1,"811":1,"819":1,"823":1,"829":1,"833":2,"835":9,"837":1,"838":1,"868":3,"869":7,"871":4,"872":2,"873":5,"886":1,"904":1,"910":1,"915":1,"934":1,"957":1,"964":2,"966":1,"979":1,"986":1,"1037":3,"1038":1,"1039":1,"1041":1,"1047":1,"1057":1,"1064":1,"1067":2,"1068":1,"1073":5,"1074":4,"1077":3,"1078":4,"1079":1,"1080":3,"1081":1,"1082":2,"1086":2,"1094":3,"1095":2,"1098":2,"1100":1,"1101":5,"1105":2,"1107":1,"1109":6,"1111":4,"1113":3,"1115":1,"1121":1,"1127":3,"1135":1,"1141":1,"1148":2,"1150":1,"1154":1,"1176":2,"1181":2,"1182":4,"1183":1,"1184":1,"1191":4,"1192":2,"1193":1,"1203":1,"1204":1,"1207":2,"1219":1,"1220":1,"1221":1,"1222":1,"1226":1,"1232":1,"1233":1,"1234":1,"1236":1,"1237":1,"1238":1,"1239":1,"1255":1,"1309":1,"1329":2,"1331":1,"1332":1,"1333":1,"1340":1,"1351":1,"1364":1,"1366":1,"1368":2,"1369":2,"1370":2,"1372":1,"1382":3,"1385":5,"1386":7,"1388":1,"1389":1,"1394":1,"1396":1,"1398":3,"1399":2,"1401":1,"1402":1,"1403":1,"1405":8,"1406":4,"1407":2,"1408":3,"1409":1,"1410":3,"1412":2,"1415":3,"1416":3,"1419":1,"1422":3,"1423":1,"1427":1,"1430":1,"1435":1,"1451":1,"1454":1,"1455":1,"1458":1,"1465":2,"1467":2,"1470":2,"1479":4,"1483":1,"1484":2,"1486":2,"1501":1,"1504":1,"1506":3,"1508":3,"1511":4,"1518":3,"1521":3,"1527":2,"1533":1,"1535":1,"1540":1,"1544":1,"1547":1,"1549":2,"1563":1,"1567":2,"1572":1,"1575":1,"1583":1,"1585":1,"1602":1,"1609":1,"1632":1,"1634":1,"1636":1,"1670":1,"1672":1,"1679":1,"1681":1,"1684":1,"1686":1,"1689":1,"1699":2,"1727":2,"1745":1,"1746":1,"1753":1,"1754":2,"1758":1,"1759":1,"1764":3,"1788":1,"1792":84,"1795":1,"1802":2,"1807":1,"1813":1,"1816":2,"1817":3,"1822":5,"1824":4,"1825":1,"1835":1,"1837":1,"1840":3,"1843":1,"1844":1,"1855":1,"1858":1,"1860":2,"1861":2,"1864":2,"1869":1,"1870":1,"1871":1,"1872":1,"1876":1,"1888":1,"1898":2,"1909":1,"1910":1,"1912":1,"1915":1,"1920":1,"1924":3,"1925":1,"1929":2,"1930":1,"1949":1,"1958":2,"1959":1,"1961":3,"1967":1,"1973":1,"1977":1,"2000":2,"2004":5,"2010":3,"2012":1,"2052":1,"2076":1,"2079":1,"2081":1,"2092":1,"2094":2,"2095":1,"2098":1,"2106":5,"2107":5,"2112":2,"2113":1,"2114":1,"2137":1,"2147":1,"2150":1,"2151":1,"2153":2,"2156":4,"2157":1,"2158":2,"2159":1,"2164":2,"2165":1,"2166":1,"2167":3,"2169":1,"2170":1,"2171":3,"2175":2,"2176":4,"2180":1,"2183":1,"2186":1,"2187":2,"2194":1,"2195":2,"2201":1,"2203":1,"2207":1,"2209":1,"2219":1,"2221":6,"2222":2,"2228":3,"2229":1,"2233":1,"2247":2,"2252":1,"2254":2,"2255":2,"2257":3,"2259":1,"2265":10,"2266":1,"2267":1,"2306":1,"2310":1,"2313":1,"2314":1,"2317":1,"2318":1,"2319":2,"2322":1,"2327":1,"2329":2,"2330":2,"2332":1,"2333":1,"2337":1,"2338":3,"2339":1,"2344":4,"2346":3,"2347":3,"2350":1,"2352":1,"2366":3,"2369":1,"2372":4,"2378":1,"2380":4,"2381":1,"2389":2,"2392":3,"2394":1,"2395":1,"2419":1,"2420":1,"2423":2,"2424":1,"2430":2,"2432":2,"2433":3,"2437":1,"2438":1,"2461":1,"2466":5,"2468":1,"2470":1,"2471":1,"2481":7,"2482":4,"2487":2,"2490":1,"2491":1,"2494":1,"2495":1,"2498":1,"2504":1,"2508":1,"2509":3,"2511":3,"2518":1,"2519":1,"2520":1,"2526":1,"2527":5,"2529":4,"2532":2,"2533":2,"2534":2,"2535":1,"2536":1,"2537":15,"2538":1,"2539":3,"2541":2,"2542":3,"2543":3,"2545":1,"2546":2,"2549":1,"2554":2,"2569":1,"2575":1,"2587":1,"2596":1,"2597":1,"2614":1,"2615":1,"2621":1,"2628":1,"2629":1,"2634":4,"2635":1,"2649":1,"2653":1,"2654":1,"2655":2,"2661":1,"2721":1,"2722":1,"2723":1,"2728":1,"2729":1,"2732":1,"2739":1,"2742":2,"2747":1,"2749":1,"2750":1,"2756":1,"2759":1,"2767":1,"2772":1,"2774":1,"2775":2,"2793":1,"2794":1,"2795":2,"2797":2,"2798":1,"2800":1,"2802":1,"2805":1,"2806":1,"2807":1,"2808":1,"2809":2,"2812":2,"2815":2,"2817":1,"2818":1,"2821":1,"2823":1,"2824":4,"2825":1,"2826":1,"2827":1,"2828":1,"2829":2,"2830":4,"2831":2,"2832":3,"2833":3,"2834":2,"2835":4,"2836":2,"2838":2,"2840":1,"2841":2,"2842":1,"2850":3,"2853":1,"2856":1,"2857":2,"2858":2,"2860":2,"2861":5,"2862":3,"2865":3,"2867":1,"2868":1,"2870":1,"2871":1,"2876":2,"2878":4,"2879":3,"2881":2}}],["ftl",{"2":{"2535":1}}],["fts",{"2":{"1096":1}}],["f5f5f5",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["fff",{"2":{"1792":1,"1800":1,"1809":2,"1810":1}}],["f2",{"2":{"1220":2,"1221":2,"1222":2}}],["f1",{"2":{"1220":2,"1221":2,"1222":2}}],["fks",{"2":{"2868":1,"2869":1}}],["fk",{"2":{"992":1,"1079":1,"2868":1}}],["fdw",{"2":{"848":1}}],["fp",{"2":{"841":2,"843":1}}],["fs",{"2":{"755":2,"756":2,"758":1,"1355":1}}],["fn",{"2":{"325":2,"881":1,"2760":2}}],["f",{"2":{"297":2,"852":1,"927":2,"1088":1,"1366":3,"1381":1,"1775":1,"1792":1,"2111":1,"2144":5,"2532":1,"2534":1,"2874":1,"2875":1}}],["flushasync",{"2":{"2615":1}}],["flushed",{"2":{"2495":1}}],["flush",{"2":{"2393":1}}],["fluent",{"2":{"1078":1}}],["fluency",{"2":{"876":1}}],["fledged",{"2":{"1458":1,"1792":1,"2375":1}}],["flexibility",{"0":{"882":1},"1":{"883":1,"884":1},"2":{"888":1,"1049":1,"1127":1,"1378":1,"2200":1}}],["flexible",{"0":{"880":1},"2":{"214":1,"1084":1,"1122":1,"1127":1,"1385":1,"1390":1,"2255":1}}],["flyway",{"2":{"1385":1,"2534":1,"2874":2}}],["floor",{"2":{"2803":1}}],["float8",{"2":{"956":3,"1374":3}}],["float",{"2":{"952":1,"956":1,"1374":1}}],["flow",{"0":{"364":1,"650":1},"1":{"365":1,"366":1},"2":{"296":1,"297":1,"300":1,"364":1,"414":1,"876":1,"938":1,"949":1,"1008":1,"1108":1,"1211":1,"1214":1,"1220":3,"1231":1,"1234":1,"1247":1,"1253":1,"1792":7,"1868":1,"1870":2,"1881":1,"2300":1,"2363":1,"2391":1,"2427":1,"2438":1,"2490":1,"2872":1}}],["flows",{"0":{"1219":1,"1869":1},"1":{"1220":1,"1221":1,"1222":1,"1870":1,"1871":1,"1872":1},"2":{"11":1,"26":1,"41":1,"65":1,"293":1,"636":1,"666":1,"996":1,"1066":1,"1068":1,"1098":1,"1202":1,"1218":1,"1219":1,"1229":1,"1235":1,"1394":1,"1458":1,"1792":2,"1869":1,"1879":1,"1885":1,"2171":1,"2375":1,"2795":1,"2881":1}}],["flagged",{"2":{"2413":1,"2442":1,"2444":1,"2535":1,"2881":1}}],["flagging",{"2":{"2410":1,"2447":1}}],["flag",{"2":{"1102":1,"1105":1,"1398":1,"1722":1,"1792":3,"1959":1,"2038":1,"2040":3,"2111":1,"2153":1,"2154":1,"2372":1,"2453":1,"2455":1,"2471":1,"2474":1,"2476":3,"2532":1,"2541":1,"2542":1,"2673":1,"2742":1,"2769":1,"2835":1,"2871":1,"2878":1}}],["flags",{"2":{"872":1,"1609":1,"2040":1,"2493":1,"2621":2,"2661":1}}],["flavors",{"2":{"1082":1,"1792":2,"2106":1,"2153":2,"2154":1,"2157":1,"2542":1,"2543":1,"2546":1}}],["flavor",{"2":{"840":1,"2153":1,"2155":1}}],["flattening",{"2":{"1375":1}}],["flattened",{"2":{"1097":1,"1973":1,"2000":1,"2330":1}}],["flat",{"2":{"335":1,"337":1,"848":1,"851":1,"916":1,"917":1,"918":1,"951":1,"1097":2,"1190":1,"1192":2,"1370":1,"1792":3,"1967":1,"2000":1,"2009":1,"2010":2,"2325":1,"2329":1,"2330":1,"2356":1,"2357":2,"2445":1,"2446":2,"2453":1,"2641":1,"2725":1,"2841":1,"2842":1,"2851":1}}],["flips",{"2":{"848":1,"1403":1,"2376":1}}],["flip",{"2":{"704":1,"1792":2,"1856":1,"2111":1,"2380":2,"2532":1,"2728":1,"2871":1}}],["flight",{"2":{"214":1,"1609":1,"1792":1,"2100":1,"2462":2,"2466":1,"2502":1,"2537":1,"2669":1,"2785":1}}],["future",{"0":{"1399":1},"2":{"1401":1,"1825":1,"2438":1,"2779":1}}],["funnel",{"2":{"2532":1}}],["funny",{"2":{"1404":1}}],["fundamentals",{"2":{"1792":1}}],["fundamental",{"2":{"1108":1}}],["fundamentally",{"2":{"946":1,"1105":1,"1137":1}}],["fun",{"2":{"977":1,"980":1,"990":1,"1404":1}}],["funcntions",{"2":{"920":1}}],["func",{"2":{"23":1,"174":2,"417":3,"418":3,"419":3,"420":3,"421":3,"441":2,"442":2,"443":2,"444":2,"445":2,"462":4,"463":3,"464":4,"501":1,"502":1,"511":2,"532":1,"1855":1,"1968":1,"2193":5,"2196":5,"2199":3,"2200":3,"2201":1,"2285":1,"2305":1,"2306":1,"2461":1,"2549":6,"2581":3,"2591":2,"2596":1,"2721":1}}],["func3",{"2":{"17":1}}],["func2",{"2":{"17":1,"522":1}}],["func1",{"2":{"17":1,"522":1}}],["functionally",{"2":{"1102":2,"1135":1,"2420":1}}],["functional",{"2":{"841":2,"843":1,"844":1,"1302":1,"2389":1,"2432":1}}],["functionality",{"2":{"455":1,"1588":1,"1722":1,"1792":4,"1917":1,"2264":2,"2265":1,"2555":1,"2776":1,"2792":1}}],["functionsql",{"2":{"2762":1,"2809":1,"2813":1,"2829":1,"2834":1}}],["functions",{"0":{"119":1,"285":1,"286":1,"930":1,"931":1,"974":1,"978":1,"985":1,"987":1,"1107":1,"1115":1,"1149":1,"1214":1,"1215":1,"1423":1,"1435":1,"1517":1,"2775":1,"2858":1},"1":{"932":1,"933":1,"934":1,"935":1,"936":1,"979":1,"980":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1},"2":{"119":1,"120":1,"174":1,"175":4,"201":1,"245":2,"265":1,"338":1,"366":1,"369":1,"377":1,"429":1,"615":1,"673":2,"684":4,"829":1,"833":1,"835":1,"836":1,"857":1,"860":1,"868":2,"871":2,"873":2,"874":2,"876":1,"911":1,"914":1,"915":1,"916":1,"920":2,"922":2,"924":1,"926":1,"930":1,"932":6,"933":7,"937":1,"940":2,"941":1,"942":1,"943":1,"945":1,"946":2,"956":1,"966":1,"967":2,"971":1,"972":1,"974":3,"975":2,"976":1,"997":2,"1000":1,"1003":1,"1005":2,"1006":1,"1007":1,"1036":1,"1037":6,"1038":1,"1043":1,"1049":1,"1063":1,"1064":1,"1065":1,"1074":1,"1076":2,"1084":1,"1086":2,"1095":2,"1096":5,"1098":4,"1099":2,"1104":1,"1105":1,"1107":5,"1108":1,"1113":1,"1115":2,"1121":1,"1125":5,"1126":4,"1127":2,"1129":1,"1130":2,"1132":1,"1133":1,"1135":2,"1149":1,"1179":1,"1184":1,"1185":3,"1187":1,"1193":3,"1203":1,"1208":3,"1209":2,"1211":3,"1231":1,"1240":1,"1244":1,"1247":1,"1252":3,"1255":2,"1304":1,"1327":1,"1333":1,"1351":1,"1357":1,"1367":1,"1377":2,"1378":8,"1385":5,"1386":1,"1390":1,"1405":3,"1409":1,"1414":2,"1416":3,"1419":1,"1423":1,"1435":5,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":2,"1442":1,"1511":1,"1517":1,"1558":1,"1563":2,"1570":2,"1571":2,"1581":1,"1655":1,"1720":1,"1723":1,"1789":1,"1792":15,"1796":1,"1868":3,"1881":1,"1889":1,"1907":1,"1933":1,"1965":1,"1967":1,"1972":2,"1973":1,"2009":1,"2045":1,"2049":4,"2064":1,"2072":2,"2075":1,"2077":1,"2106":1,"2156":2,"2164":2,"2165":1,"2190":1,"2197":2,"2227":1,"2239":1,"2256":1,"2264":2,"2265":2,"2273":1,"2310":1,"2313":1,"2317":2,"2325":1,"2327":1,"2332":1,"2333":1,"2338":1,"2339":1,"2344":1,"2350":1,"2357":1,"2388":1,"2389":2,"2456":1,"2484":1,"2531":1,"2542":2,"2580":1,"2586":1,"2588":1,"2590":1,"2597":1,"2618":1,"2635":6,"2666":1,"2709":1,"2713":1,"2722":1,"2723":1,"2732":1,"2755":1,"2772":1,"2774":1,"2775":1,"2802":1,"2820":1,"2836":1,"2858":4}}],["function",{"0":{"365":1,"366":1,"374":1,"611":1,"658":1,"664":1,"733":1,"760":1,"763":1,"770":1,"773":1,"828":1,"882":1,"883":1,"884":1,"886":1,"928":1,"929":1,"934":1,"935":1,"936":1,"956":1,"988":1,"1021":1,"1055":1,"1060":1,"1188":1,"1216":1,"1308":1,"1357":1,"1504":1,"1547":1,"1561":1,"1567":1,"1689":1,"1727":1,"2164":1,"2183":1,"2504":1,"2721":1,"2799":1,"2813":1,"2822":1},"1":{"883":1,"884":1,"887":1,"1056":1},"2":{"7":2,"8":1,"9":2,"16":2,"17":3,"18":2,"19":2,"20":2,"21":2,"22":1,"23":1,"24":1,"37":4,"38":3,"39":3,"40":2,"41":1,"43":1,"48":2,"49":1,"50":2,"51":1,"54":1,"60":2,"61":2,"62":2,"66":2,"71":2,"72":2,"75":1,"83":1,"84":1,"85":1,"86":1,"94":1,"95":1,"96":1,"97":1,"104":2,"105":1,"106":1,"107":1,"115":2,"116":2,"117":2,"118":1,"119":3,"128":2,"129":1,"136":2,"137":2,"138":1,"147":1,"148":1,"149":1,"156":1,"157":3,"173":1,"174":3,"175":3,"179":1,"180":3,"184":3,"186":5,"187":4,"195":1,"196":1,"202":2,"206":2,"207":1,"208":1,"209":2,"212":2,"213":1,"215":2,"216":3,"218":1,"220":2,"223":2,"225":1,"241":1,"244":1,"245":1,"247":2,"248":2,"249":2,"250":2,"251":1,"252":1,"254":2,"255":2,"256":2,"257":2,"263":4,"264":3,"277":3,"278":3,"284":1,"285":2,"286":1,"288":2,"289":2,"290":2,"291":2,"292":2,"296":1,"297":1,"298":2,"302":1,"304":1,"306":1,"308":3,"309":2,"312":3,"313":2,"314":1,"316":1,"320":1,"322":2,"323":1,"324":1,"325":1,"330":1,"332":2,"333":2,"334":2,"335":2,"342":1,"343":1,"344":1,"351":2,"352":2,"353":1,"354":1,"360":2,"361":2,"362":3,"363":1,"365":2,"366":2,"374":3,"376":1,"386":2,"392":1,"393":1,"401":2,"402":2,"403":1,"405":2,"406":2,"408":6,"412":1,"414":4,"415":2,"417":2,"418":2,"419":3,"420":2,"421":3,"423":4,"424":1,"426":2,"427":2,"428":2,"429":3,"431":1,"435":1,"436":7,"438":4,"439":6,"441":2,"442":2,"443":2,"444":2,"445":2,"447":1,"448":4,"449":3,"451":4,"452":3,"453":6,"454":4,"456":2,"458":1,"462":2,"466":2,"467":2,"468":2,"469":2,"476":1,"477":1,"478":1,"479":1,"487":2,"488":2,"489":2,"490":2,"491":2,"492":2,"493":2,"497":1,"499":2,"501":1,"502":1,"503":2,"510":2,"511":2,"520":2,"521":2,"522":2,"523":2,"527":1,"528":1,"531":1,"532":1,"539":2,"540":2,"541":2,"542":2,"543":2,"544":2,"545":2,"553":1,"554":1,"555":1,"572":1,"573":1,"574":1,"577":2,"592":2,"593":2,"594":2,"601":1,"602":1,"603":1,"604":1,"611":2,"631":1,"632":1,"633":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":3,"653":2,"654":2,"658":2,"659":1,"660":1,"661":1,"662":2,"673":1,"677":2,"678":1,"679":3,"683":2,"684":2,"686":2,"720":3,"722":2,"723":2,"724":2,"732":1,"733":2,"734":2,"735":2,"736":2,"737":1,"741":1,"750":2,"751":2,"752":2,"755":2,"756":2,"758":1,"760":2,"763":1,"764":3,"765":1,"766":1,"767":1,"770":2,"773":1,"774":3,"775":1,"777":3,"783":1,"785":1,"787":1,"789":1,"794":1,"797":2,"798":2,"799":2,"800":1,"801":1,"803":1,"811":2,"812":2,"813":2,"814":2,"815":2,"817":1,"828":2,"833":1,"835":3,"843":1,"852":2,"860":2,"868":2,"871":5,"872":1,"873":1,"874":2,"876":9,"878":1,"880":3,"881":3,"883":1,"884":3,"885":1,"886":4,"888":3,"894":3,"899":1,"900":1,"902":1,"903":2,"904":5,"910":2,"911":2,"914":5,"915":9,"916":6,"917":3,"918":4,"919":3,"922":1,"928":2,"929":1,"932":1,"933":4,"934":3,"935":2,"936":3,"937":2,"938":2,"947":1,"949":2,"956":2,"957":4,"959":1,"960":2,"961":1,"964":1,"965":1,"966":1,"968":1,"971":1,"975":3,"976":2,"978":4,"979":3,"980":4,"981":1,"982":7,"983":4,"984":2,"985":3,"986":4,"988":6,"989":1,"990":3,"991":3,"992":1,"994":4,"995":6,"996":6,"997":2,"1002":1,"1005":2,"1010":2,"1015":1,"1016":4,"1017":1,"1019":1,"1021":2,"1023":3,"1024":1,"1026":1,"1029":3,"1031":1,"1033":2,"1037":2,"1043":1,"1049":1,"1052":2,"1053":1,"1054":1,"1055":4,"1057":2,"1058":1,"1060":2,"1063":1,"1064":4,"1065":1,"1067":1,"1068":3,"1069":1,"1073":1,"1076":5,"1077":5,"1080":1,"1084":1,"1095":2,"1096":1,"1098":1,"1099":1,"1102":7,"1104":1,"1105":17,"1106":1,"1107":1,"1108":3,"1111":1,"1113":2,"1121":1,"1125":2,"1126":1,"1127":2,"1128":3,"1129":4,"1132":2,"1133":1,"1134":2,"1135":5,"1138":4,"1139":3,"1141":2,"1142":3,"1143":1,"1149":2,"1150":4,"1154":2,"1158":1,"1161":1,"1163":3,"1174":1,"1176":2,"1178":1,"1179":6,"1183":2,"1185":2,"1188":1,"1189":2,"1190":1,"1191":4,"1192":1,"1193":10,"1196":1,"1197":3,"1203":2,"1214":4,"1215":3,"1216":2,"1219":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1255":1,"1281":1,"1284":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1305":1,"1308":2,"1310":2,"1317":2,"1318":3,"1320":1,"1321":1,"1331":4,"1332":6,"1337":3,"1338":5,"1339":2,"1341":1,"1342":1,"1345":4,"1347":2,"1348":2,"1350":1,"1352":2,"1357":2,"1358":4,"1359":1,"1362":3,"1363":1,"1366":7,"1367":2,"1368":4,"1369":2,"1371":1,"1377":1,"1378":2,"1380":1,"1382":1,"1385":2,"1386":1,"1387":3,"1388":6,"1389":2,"1390":2,"1391":1,"1393":4,"1396":1,"1398":3,"1405":1,"1406":1,"1407":1,"1408":3,"1409":4,"1410":3,"1413":1,"1415":2,"1416":6,"1419":2,"1422":2,"1423":2,"1426":2,"1427":2,"1431":3,"1435":1,"1436":1,"1438":2,"1442":2,"1458":5,"1460":2,"1477":1,"1503":1,"1504":1,"1517":1,"1529":4,"1531":2,"1532":2,"1533":1,"1538":1,"1544":2,"1547":2,"1551":1,"1560":1,"1562":1,"1567":6,"1568":1,"1569":3,"1571":1,"1575":1,"1576":3,"1581":2,"1599":3,"1618":1,"1632":3,"1655":2,"1664":4,"1674":1,"1689":1,"1723":3,"1727":2,"1733":4,"1736":3,"1738":2,"1741":2,"1742":1,"1745":1,"1747":1,"1789":1,"1792":14,"1855":1,"1864":1,"1869":1,"1915":1,"1920":3,"1921":4,"1922":2,"1923":1,"1924":2,"1926":2,"1928":1,"1929":1,"1930":2,"1968":1,"1973":3,"1974":1,"2010":1,"2047":1,"2072":1,"2076":3,"2078":2,"2079":4,"2147":3,"2156":2,"2160":1,"2162":1,"2164":2,"2165":4,"2176":2,"2177":3,"2181":1,"2183":3,"2184":3,"2185":1,"2186":2,"2187":6,"2190":1,"2193":5,"2194":1,"2195":1,"2196":5,"2197":1,"2199":3,"2200":3,"2201":1,"2202":3,"2204":2,"2205":3,"2206":2,"2207":1,"2214":2,"2215":2,"2216":1,"2217":1,"2218":1,"2222":1,"2242":1,"2247":5,"2255":7,"2256":3,"2264":9,"2265":1,"2267":1,"2277":8,"2278":1,"2282":2,"2283":3,"2284":1,"2285":1,"2287":1,"2289":2,"2290":1,"2292":3,"2293":5,"2294":4,"2300":2,"2302":3,"2303":2,"2304":4,"2305":2,"2306":1,"2307":1,"2309":1,"2310":3,"2313":1,"2314":4,"2323":1,"2329":1,"2330":1,"2332":1,"2339":3,"2344":4,"2346":3,"2357":1,"2358":1,"2359":2,"2375":6,"2380":1,"2389":1,"2391":2,"2397":1,"2404":1,"2432":2,"2437":1,"2496":3,"2500":1,"2504":2,"2512":1,"2520":1,"2523":1,"2542":2,"2546":2,"2549":18,"2572":2,"2575":4,"2580":4,"2581":3,"2586":2,"2587":4,"2588":1,"2589":1,"2591":2,"2596":1,"2597":1,"2607":1,"2635":2,"2649":1,"2650":1,"2651":1,"2652":1,"2653":3,"2655":1,"2656":2,"2664":1,"2665":6,"2666":1,"2712":1,"2721":2,"2723":1,"2727":1,"2742":1,"2751":1,"2759":2,"2760":3,"2762":3,"2763":1,"2764":2,"2766":5,"2767":2,"2768":1,"2775":2,"2797":1,"2802":1,"2806":1,"2807":5,"2809":3,"2810":2,"2812":2,"2813":4,"2815":3,"2817":1,"2822":8,"2823":1,"2824":13,"2825":5,"2830":2,"2835":1,"2836":2,"2837":1,"2839":3,"2848":1,"2855":1,"2858":1,"2878":1}}],["fuss",{"2":{"848":2}}],["furthermore",{"2":{"1398":1}}],["further",{"2":{"180":1,"865":1,"1047":1,"1048":1,"1088":1,"1386":2,"1429":1,"2438":1,"2465":1}}],["fullstackandmessage",{"2":{"1792":1,"1845":1,"1863":1,"2802":1}}],["fullscreen",{"2":{"1381":1}}],["fully",{"2":{"529":1,"845":1,"849":2,"860":1,"1054":1,"1080":1,"1086":1,"1127":1,"1302":1,"1412":1,"1420":1,"1458":1,"1792":1,"1801":1,"1825":1,"1958":1,"1974":1,"2156":1,"2221":1,"2284":1,"2375":1,"2389":1,"2461":1,"2466":2,"2470":1,"2477":1,"2496":1,"2542":1,"2544":1,"2588":2,"2607":2,"2682":1,"2700":1,"2711":1,"2729":1,"2731":1,"2765":1,"2801":1}}],["full",{"0":{"187":1,"1282":1,"1339":1,"1893":1,"2029":1,"2294":1},"1":{"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1},"2":{"110":1,"215":1,"217":1,"292":2,"307":1,"308":1,"315":1,"320":1,"423":1,"431":1,"446":1,"456":1,"634":1,"647":1,"670":1,"692":1,"734":2,"799":2,"848":1,"868":1,"869":1,"872":3,"874":1,"876":1,"918":2,"920":1,"922":1,"945":2,"949":1,"954":1,"1009":1,"1032":1,"1033":1,"1054":1,"1055":1,"1060":1,"1066":1,"1067":3,"1070":1,"1073":1,"1074":1,"1082":1,"1084":2,"1086":1,"1088":1,"1094":1,"1096":5,"1097":1,"1099":1,"1121":1,"1135":1,"1150":1,"1155":1,"1197":1,"1207":1,"1212":1,"1230":1,"1247":1,"1278":1,"1323":1,"1324":1,"1335":1,"1339":1,"1351":1,"1368":1,"1380":2,"1382":1,"1398":1,"1402":1,"1408":1,"1409":1,"1412":1,"1415":1,"1416":1,"1465":1,"1484":1,"1506":1,"1533":1,"1549":1,"1556":1,"1557":1,"1560":1,"1594":1,"1664":1,"1686":1,"1699":1,"1722":1,"1738":1,"1743":1,"1748":1,"1753":2,"1755":1,"1758":2,"1792":14,"1845":1,"1880":1,"1894":1,"1911":1,"1922":1,"1929":1,"1932":1,"2011":2,"2013":1,"2019":5,"2023":1,"2092":1,"2094":1,"2104":1,"2106":1,"2107":1,"2114":1,"2156":1,"2177":2,"2220":1,"2221":1,"2242":1,"2244":1,"2261":1,"2263":1,"2269":1,"2276":1,"2281":1,"2299":1,"2312":1,"2316":1,"2320":1,"2354":2,"2358":1,"2359":2,"2374":1,"2384":1,"2388":1,"2409":1,"2419":1,"2434":1,"2440":1,"2450":1,"2453":1,"2459":1,"2468":1,"2474":1,"2479":1,"2498":1,"2500":1,"2502":1,"2508":1,"2513":1,"2515":1,"2523":1,"2525":1,"2527":1,"2529":1,"2535":1,"2537":4,"2539":1,"2540":1,"2546":2,"2548":1,"2553":1,"2557":1,"2561":1,"2564":1,"2569":1,"2571":1,"2574":1,"2577":1,"2579":1,"2584":1,"2586":1,"2593":1,"2599":1,"2602":1,"2606":1,"2610":1,"2613":1,"2617":1,"2620":1,"2624":1,"2631":1,"2632":1,"2637":1,"2640":1,"2644":1,"2647":1,"2658":1,"2670":1,"2672":1,"2676":1,"2678":1,"2694":1,"2695":1,"2713":1,"2723":1,"2739":1,"2750":1,"2771":1,"2837":1,"2859":1,"2860":1,"2862":1,"2865":1,"2868":1,"2872":1,"2879":2,"2880":1,"2881":1}}],["frustration",{"2":{"1402":1}}],["fraction",{"2":{"2621":1}}],["fragments",{"2":{"2531":1,"2712":1}}],["fragment",{"2":{"1428":1,"2531":1}}],["frankly",{"2":{"1404":1}}],["frank",{"2":{"913":1}}],["franz",{"2":{"913":1}}],["framed",{"2":{"2018":1}}],["frame>",{"2":{"1792":1,"2632":1}}],["frames",{"2":{"871":1,"2020":1}}],["frame",{"0":{"1493":1,"2018":1},"2":{"868":1,"869":1,"1100":1,"1489":1,"1493":3,"1788":1,"1792":10,"1845":1,"2016":1,"2018":2,"2020":1,"2028":1,"2030":1,"2632":4}}],["frameworks",{"0":{"1254":1,"1271":1,"1273":1},"1":{"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":2,"1275":2,"1276":2,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1},"2":{"869":1,"1007":1,"1089":1,"1092":1,"1097":2,"1254":3,"1255":2,"1259":1,"1265":1,"1279":1}}],["framework",{"0":{"2246":1},"2":{"383":1,"852":1,"857":1,"868":1,"869":3,"873":2,"876":1,"1007":1,"1074":1,"1075":1,"1076":1,"1079":1,"1090":1,"1091":1,"1104":1,"1254":1,"1255":4,"1257":1,"1258":1,"1267":2,"1268":1,"1269":2,"1270":1,"1276":1,"1279":1,"1280":1,"1281":3,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1393":1,"1419":1,"1447":1,"1451":1,"1454":2,"1464":1,"1792":6,"1822":1,"2157":1,"2240":1,"2246":1,"2348":1,"2377":1,"2420":1,"2438":1,"2543":1,"2794":1,"2795":1}}],["framing",{"2":{"851":1,"869":1,"2018":1}}],["friendlier",{"2":{"2495":1}}],["friendly",{"2":{"372":1,"1251":1,"1385":3,"2866":1}}],["fringe",{"2":{"847":1}}],["friday",{"2":{"836":1}}],["fronting",{"2":{"1101":1}}],["front",{"2":{"868":1,"1074":1,"1270":1,"1328":1,"1436":1,"2809":1,"2815":1}}],["frontends",{"2":{"1382":1}}],["frontend",{"0":{"1011":1,"1318":1},"2":{"251":1,"833":3,"834":4,"835":1,"836":1,"837":2,"867":1,"868":2,"869":1,"871":5,"872":2,"873":1,"875":1,"876":1,"877":1,"879":1,"894":1,"910":1,"911":1,"961":1,"973":1,"1008":2,"1020":1,"1024":1,"1037":1,"1038":1,"1080":1,"1086":1,"1094":2,"1127":1,"1303":1,"1318":1,"1320":1,"1322":1,"1366":3,"1382":1,"1406":2,"1407":1,"1409":2,"1412":1,"1413":1,"1414":3,"1417":2,"1418":1,"1419":2,"1420":2,"1422":2,"1436":1,"1441":1,"2419":1,"2543":1,"2775":1,"2857":1}}],["frozendictionary",{"2":{"2372":1}}],["frozen",{"2":{"851":1,"860":1,"861":1}}],["fromdate",{"2":{"2845":1}}],["fromdate=2024",{"2":{"2845":1}}],["fromdate=",{"2":{"2731":1}}],["from=base",{"2":{"1420":1}}],["from=2025",{"2":{"1067":3}}],["from=usd",{"2":{"263":1}}],["from",{"0":{"289":1,"290":1,"312":1,"323":1,"393":1,"544":1,"803":1,"947":1,"1010":1,"1125":1,"1126":1,"1266":1,"1362":1,"1368":1,"1406":1,"1608":1,"2389":1,"2489":1,"2520":1,"2539":1,"2712":1,"2792":1,"2830":1,"2834":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1363":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"0":1,"16":2,"18":1,"19":1,"20":1,"31":3,"35":1,"37":3,"38":1,"39":1,"40":1,"50":1,"74":1,"87":1,"106":3,"115":2,"116":1,"119":1,"128":2,"136":2,"156":1,"165":1,"167":1,"168":4,"171":1,"174":1,"175":1,"186":2,"209":1,"212":2,"214":1,"215":4,"221":1,"223":1,"238":2,"245":1,"249":1,"250":1,"253":1,"263":3,"264":1,"265":1,"286":4,"288":3,"289":1,"290":1,"291":3,"292":1,"298":2,"302":1,"309":2,"312":2,"313":2,"318":1,"322":1,"323":1,"325":1,"326":1,"336":1,"347":1,"348":2,"355":4,"356":1,"366":1,"372":3,"373":1,"374":1,"376":2,"383":2,"384":1,"386":1,"388":1,"390":3,"395":1,"396":1,"401":1,"404":1,"414":1,"415":2,"417":1,"419":1,"426":2,"427":2,"428":1,"429":2,"436":3,"439":1,"441":1,"443":1,"447":1,"448":3,"452":1,"454":3,"488":1,"489":1,"490":1,"493":1,"504":2,"517":2,"520":2,"527":3,"529":4,"531":2,"532":2,"534":2,"535":1,"542":1,"549":1,"562":2,"563":2,"565":2,"566":3,"568":1,"581":1,"582":2,"584":3,"585":3,"595":1,"611":1,"612":1,"613":1,"614":2,"618":1,"621":1,"622":6,"623":2,"624":1,"636":3,"646":1,"650":3,"656":1,"662":2,"663":1,"666":1,"673":1,"677":2,"679":1,"689":1,"691":2,"693":1,"694":1,"695":1,"696":1,"699":1,"700":2,"703":1,"705":1,"708":1,"710":1,"713":1,"715":1,"722":2,"723":1,"724":1,"761":1,"763":1,"765":1,"766":1,"771":1,"773":1,"781":1,"782":1,"784":1,"786":1,"788":4,"811":2,"830":1,"831":2,"833":2,"834":5,"835":3,"837":1,"841":2,"843":1,"845":1,"847":2,"848":6,"849":5,"851":3,"852":1,"854":1,"855":1,"856":2,"859":2,"860":4,"861":1,"863":3,"864":3,"865":1,"866":1,"868":4,"869":1,"871":1,"872":1,"873":3,"874":1,"876":3,"880":2,"882":1,"883":2,"894":1,"903":2,"905":1,"910":1,"912":1,"914":3,"916":5,"917":1,"918":9,"928":1,"929":1,"934":1,"936":2,"937":1,"949":1,"952":1,"956":1,"957":4,"961":2,"967":1,"972":1,"978":1,"979":2,"980":2,"982":1,"986":1,"988":2,"989":1,"990":7,"991":4,"994":1,"995":1,"996":1,"997":1,"1001":1,"1005":1,"1007":1,"1008":1,"1009":1,"1010":2,"1011":3,"1012":1,"1014":3,"1015":3,"1016":1,"1018":1,"1019":1,"1021":1,"1023":1,"1033":2,"1037":11,"1038":3,"1040":2,"1042":1,"1043":2,"1044":1,"1046":1,"1049":1,"1054":1,"1055":1,"1057":1,"1058":1,"1059":1,"1060":4,"1062":1,"1065":1,"1067":6,"1068":3,"1069":1,"1070":1,"1071":1,"1073":6,"1074":2,"1076":3,"1078":4,"1079":2,"1082":2,"1089":1,"1090":2,"1094":1,"1096":2,"1101":1,"1102":2,"1105":7,"1106":1,"1107":4,"1111":1,"1113":1,"1127":1,"1135":2,"1137":1,"1138":4,"1139":1,"1141":2,"1142":2,"1147":1,"1148":1,"1149":2,"1150":4,"1154":1,"1156":2,"1157":1,"1162":2,"1169":1,"1176":2,"1178":1,"1179":3,"1180":3,"1184":1,"1188":3,"1192":1,"1193":2,"1197":4,"1203":1,"1204":2,"1205":1,"1206":1,"1215":1,"1216":1,"1217":5,"1225":1,"1232":3,"1234":2,"1235":2,"1236":2,"1237":1,"1239":2,"1253":1,"1254":3,"1255":1,"1258":2,"1262":1,"1266":1,"1274":1,"1276":2,"1305":1,"1308":1,"1309":1,"1310":1,"1316":1,"1318":1,"1320":1,"1321":1,"1324":1,"1328":1,"1329":1,"1332":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":2,"1346":1,"1347":3,"1357":2,"1358":3,"1359":1,"1361":1,"1362":1,"1363":2,"1365":1,"1366":1,"1367":1,"1368":3,"1369":2,"1370":2,"1371":3,"1372":2,"1373":3,"1374":2,"1375":2,"1376":7,"1378":2,"1381":2,"1382":3,"1383":2,"1385":7,"1386":11,"1387":2,"1388":1,"1390":2,"1391":4,"1393":3,"1394":2,"1395":3,"1396":4,"1398":11,"1399":1,"1401":1,"1402":2,"1403":2,"1404":2,"1405":2,"1406":1,"1407":1,"1408":3,"1409":1,"1410":3,"1412":2,"1413":2,"1414":4,"1416":3,"1419":1,"1420":2,"1421":2,"1422":1,"1427":4,"1429":4,"1431":2,"1434":1,"1435":1,"1436":2,"1437":1,"1439":1,"1442":2,"1447":1,"1451":1,"1454":1,"1458":4,"1459":2,"1460":3,"1470":2,"1471":1,"1489":1,"1501":2,"1503":1,"1504":4,"1505":1,"1511":2,"1515":1,"1520":1,"1525":1,"1529":2,"1559":2,"1560":2,"1566":1,"1569":2,"1571":3,"1574":3,"1576":3,"1577":2,"1581":3,"1582":1,"1608":1,"1620":1,"1631":1,"1632":3,"1637":1,"1641":1,"1645":1,"1655":1,"1664":1,"1670":2,"1683":1,"1684":1,"1687":1,"1689":4,"1696":2,"1698":1,"1703":2,"1706":1,"1707":1,"1708":1,"1713":1,"1717":2,"1722":1,"1723":1,"1738":4,"1743":1,"1746":1,"1753":1,"1757":1,"1774":1,"1789":2,"1792":102,"1796":1,"1801":1,"1808":2,"1818":1,"1824":4,"1825":1,"1830":1,"1837":2,"1841":2,"1850":1,"1851":1,"1852":2,"1862":1,"1882":1,"1885":1,"1886":1,"1887":1,"1888":1,"1893":8,"1898":1,"1907":1,"1917":3,"1918":1,"1922":1,"1930":1,"1933":1,"1937":1,"1947":1,"1955":1,"1958":1,"1967":1,"1973":1,"1974":1,"1998":1,"2006":1,"2009":1,"2010":2,"2012":1,"2016":3,"2017":2,"2018":1,"2019":1,"2021":1,"2023":2,"2024":1,"2037":1,"2039":1,"2040":2,"2047":1,"2049":1,"2050":1,"2051":1,"2052":1,"2063":2,"2076":2,"2078":1,"2079":2,"2106":1,"2107":1,"2110":2,"2111":2,"2112":2,"2119":2,"2153":1,"2164":1,"2165":3,"2167":1,"2170":3,"2171":1,"2176":2,"2177":1,"2178":1,"2183":6,"2184":1,"2187":3,"2193":1,"2194":1,"2197":2,"2216":1,"2221":1,"2222":3,"2224":1,"2228":1,"2239":1,"2242":1,"2249":4,"2254":1,"2255":6,"2256":1,"2257":1,"2258":2,"2265":1,"2272":2,"2274":1,"2277":1,"2282":1,"2283":3,"2284":1,"2285":2,"2291":1,"2293":2,"2303":1,"2310":2,"2313":1,"2317":1,"2318":1,"2319":3,"2320":2,"2321":2,"2322":3,"2323":1,"2324":2,"2327":1,"2328":1,"2336":1,"2337":5,"2339":4,"2340":4,"2342":3,"2343":2,"2344":3,"2346":1,"2347":1,"2348":1,"2350":1,"2352":1,"2363":1,"2364":1,"2369":1,"2370":1,"2372":2,"2375":8,"2379":1,"2380":2,"2382":2,"2383":2,"2385":1,"2388":2,"2389":2,"2394":2,"2395":2,"2397":1,"2398":2,"2400":1,"2402":2,"2407":1,"2412":1,"2415":1,"2427":1,"2432":2,"2435":2,"2438":1,"2446":1,"2448":1,"2450":1,"2464":1,"2470":1,"2474":1,"2477":1,"2479":1,"2481":3,"2483":1,"2484":1,"2487":1,"2490":1,"2502":1,"2504":1,"2515":1,"2519":1,"2520":1,"2523":3,"2525":1,"2526":2,"2527":2,"2528":1,"2530":2,"2531":1,"2532":3,"2533":1,"2535":1,"2536":1,"2537":3,"2539":1,"2540":3,"2543":1,"2546":2,"2549":2,"2551":1,"2554":1,"2555":4,"2558":2,"2562":1,"2571":1,"2572":1,"2576":2,"2580":1,"2586":1,"2590":1,"2596":1,"2607":1,"2608":4,"2614":1,"2615":1,"2621":1,"2626":1,"2632":5,"2633":3,"2634":1,"2635":9,"2641":1,"2653":1,"2666":1,"2681":1,"2684":3,"2704":2,"2705":1,"2709":1,"2712":1,"2713":1,"2722":1,"2726":1,"2729":1,"2731":2,"2733":2,"2734":1,"2739":1,"2751":1,"2758":1,"2760":1,"2762":4,"2768":2,"2770":1,"2771":1,"2772":1,"2773":1,"2774":3,"2775":2,"2779":1,"2792":2,"2795":1,"2802":4,"2803":2,"2811":4,"2813":2,"2815":2,"2820":2,"2821":1,"2826":2,"2827":3,"2828":1,"2830":1,"2831":1,"2835":1,"2836":3,"2839":2,"2840":2,"2841":1,"2842":2,"2843":1,"2845":6,"2846":3,"2847":1,"2850":2,"2851":1,"2852":1,"2854":5,"2855":1,"2860":2,"2861":3,"2862":2,"2864":1,"2866":3,"2868":1,"2869":3,"2871":2,"2873":1,"2876":1,"2879":1}}],["frequently",{"2":{"1147":1,"1179":1,"1354":1,"1511":1,"1513":1,"1515":1,"1792":1,"2274":1,"2559":1,"2580":1}}],["frequency",{"2":{"1035":1,"1036":1,"1323":1,"1327":1,"1351":1}}],["fresh",{"2":{"107":1,"214":1,"695":1,"701":1,"1067":1,"1068":1,"1079":1,"1082":1,"1139":2,"1147":1,"1148":1,"1342":1,"1518":1,"1519":1,"1524":1,"1722":1,"1792":3,"2099":1,"2109":1,"2110":1,"2112":1,"2167":1,"2265":1,"2397":1,"2502":1,"2527":1,"2530":2,"2533":1,"2543":1,"2545":1,"2769":1,"2815":1,"2862":1,"2866":1,"2872":1,"2873":1}}],["freeing",{"2":{"2615":1}}],["freed",{"2":{"2157":1,"2543":1}}],["freely",{"2":{"448":1,"2193":1,"2207":1,"2581":1}}],["free",{"2":{"107":2,"319":1,"854":1,"869":3,"872":2,"873":2,"1018":1,"1037":1,"1044":1,"1206":3,"1400":2,"1401":1,"1421":1,"1443":1,"1792":1,"2380":1,"2614":1,"2776":1}}],["fonts",{"2":{"1943":1,"2020":1}}],["font",{"2":{"1792":7,"1936":4,"1943":4,"2020":1,"2029":1,"2073":3,"2075":3,"2080":3}}],["focused",{"2":{"2184":1,"2396":1,"2397":1}}],["focus",{"2":{"859":1,"975":1,"1084":1,"1385":1,"1386":1,"1421":1}}],["fowler",{"2":{"851":4,"859":2}}],["food",{"2":{"1400":1}}],["footnotes",{"2":{"2531":1}}],["footer>",{"2":{"996":2}}],["footprint",{"2":{"860":1,"1278":1}}],["foo",{"2":{"650":1}}],["folding",{"2":{"2493":1}}],["folder",{"2":{"913":1,"1086":1}}],["folded",{"2":{"874":1}}],["folds",{"2":{"388":1}}],["follow",{"2":{"687":1,"1080":1,"1111":1,"1403":1,"1480":1,"1792":1,"2193":1,"2509":1,"2511":1,"2529":1,"2543":1,"2857":1,"2865":1}}],["follows",{"0":{"1924":1},"2":{"422":1,"833":1,"853":1,"976":1,"1087":1,"1211":1,"1254":1,"1424":1,"1655":1,"1728":1,"2193":2,"2264":1,"2391":1,"2509":1,"2848":1}}],["followed",{"2":{"382":1,"826":1,"854":1,"1192":1,"2106":1,"2156":1,"2252":1,"2334":1,"2338":1,"2537":1,"2648":1}}],["following",{"2":{"31":1,"52":1,"158":1,"280":1,"446":1,"685":1,"748":1,"849":1,"914":1,"915":1,"918":2,"1254":1,"1386":3,"1391":1,"1403":1,"1589":1,"1620":1,"1671":1,"1792":3,"2002":1,"2141":1,"2222":1,"2438":1,"2513":1,"2586":1,"2681":1,"2792":1,"2824":1}}],["fought",{"2":{"852":1}}],["fourth",{"2":{"1152":1,"1590":1}}],["four",{"0":{"870":1,"882":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1,"883":1,"884":1},"2":{"33":1,"300":1,"304":1,"816":1,"852":2,"853":2,"857":1,"861":1,"864":1,"868":1,"873":1,"966":1,"1048":1,"1066":2,"1101":1,"1156":1,"1424":1,"1440":1,"1464":1,"1792":3,"1950":1,"2142":1,"2180":1,"2376":2,"2377":1,"2397":1,"2435":2,"2451":1,"2455":1,"2456":1,"2457":2,"2575":1,"2810":1,"2870":1}}],["foundation",{"2":{"832":1,"2670":1}}],["found",{"0":{"2491":1},"2":{"3":1,"63":1,"263":1,"309":1,"524":1,"587":1,"801":1,"913":1,"1055":1,"1060":1,"1235":1,"1236":1,"1254":1,"1335":1,"1339":1,"1394":1,"1395":1,"1427":1,"1518":1,"1792":6,"1885":1,"2095":1,"2113":1,"2220":1,"2242":1,"2255":3,"2258":1,"2265":1,"2266":1,"2267":1,"2271":1,"2337":1,"2363":1,"2491":1,"2535":1,"2537":1,"2880":1}}],["forbids",{"2":{"2519":1}}],["forbidden",{"2":{"25":1,"1057":1,"1061":1,"1674":1,"1792":2,"2255":1,"2271":1}}],["forgiven",{"2":{"2531":1}}],["forgery",{"2":{"1487":1,"1564":1,"1792":4}}],["forgets",{"2":{"1402":1}}],["forgetting",{"2":{"1128":1,"2107":1,"2392":1,"2537":1,"2879":1}}],["forget",{"2":{"847":1,"1107":1,"1325":1,"1527":1}}],["forgotten",{"2":{"1441":1}}],["forcing",{"2":{"1045":1,"1054":1}}],["forced",{"2":{"852":1,"1392":1,"1792":1,"2153":1,"2511":1,"2527":1,"2543":1,"2811":1,"2862":1,"2879":1}}],["forces",{"2":{"845":1,"1111":1,"1130":1,"1385":1,"1489":1,"1792":2,"2106":1,"2338":1,"2456":1,"2537":2,"2853":1}}],["force",{"0":{"520":1,"521":1,"2338":1},"2":{"74":1,"227":1,"389":1,"588":1,"705":1,"714":1,"823":1,"851":1,"1139":1,"1217":1,"1224":1,"1252":1,"1792":4,"1843":1,"1846":2,"1874":1,"2106":1,"2111":2,"2204":2,"2381":1,"2518":1,"2532":3,"2533":1,"2534":2,"2537":1,"2758":1,"2859":1,"2871":1,"2872":1,"2873":1}}],["foremost",{"2":{"1254":1}}],["forecast",{"2":{"1105":2}}],["foreach",{"2":{"1021":1,"1376":1,"2836":1}}],["forever",{"2":{"864":1,"1079":1,"1792":1,"2381":1}}],["foreign",{"2":{"848":2,"851":2,"852":3,"857":1,"864":1,"992":1,"1075":1,"1079":2,"1096":1,"1111":1,"2741":1,"2868":1}}],["forth",{"2":{"847":1,"974":1}}],["fork",{"0":{"833":1}}],["forwardchallenge",{"2":{"2437":1}}],["forwarddefaultselector",{"2":{"2421":1,"2422":1,"2435":1}}],["forwarduploadcontent",{"2":{"1792":1,"1916":1,"1917":1,"1927":1,"1928":1,"1931":1,"2549":5,"2814":1}}],["forwardlimit",{"2":{"1702":1,"1703":1,"1706":3,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1717":1,"1792":1,"2633":1}}],["forwardresponseheaders",{"2":{"1340":2,"1792":1,"1916":1,"1917":1,"1931":1,"2549":1,"2814":2}}],["forwardheaders",{"2":{"1340":2,"1792":1,"1916":1,"1917":1,"1931":1,"2549":1,"2814":2}}],["forwardsignout",{"2":{"2437":1}}],["forwards",{"2":{"414":1,"423":1,"433":1,"435":1,"436":3,"453":1,"868":1,"1331":2,"1332":1,"1338":1,"1348":1,"1396":2,"1825":1,"1925":1,"2185":1,"2302":2,"2513":1,"2806":1,"2807":1,"2809":1,"2816":1}}],["forwardedheaders",{"2":{"1702":1,"1706":2,"1707":1,"1708":1,"1709":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1792":1,"2633":1}}],["forwarded",{"0":{"1701":1,"1704":1,"2633":1},"1":{"1702":1,"1703":1,"1704":1,"1705":1,"1706":1,"1707":1,"1708":1,"1709":1,"1710":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1717":1,"1718":1,"1719":1},"2":{"320":1,"327":1,"415":1,"424":1,"436":2,"448":2,"452":3,"454":2,"668":1,"869":1,"1100":3,"1340":1,"1431":1,"1475":1,"1477":1,"1701":1,"1703":4,"1704":1,"1705":3,"1706":2,"1708":1,"1711":4,"1712":1,"1716":1,"1717":1,"1784":1,"1788":2,"1792":14,"1915":1,"1923":1,"1924":9,"1926":2,"1957":1,"1961":1,"2031":1,"2234":1,"2302":1,"2303":1,"2304":1,"2307":1,"2309":1,"2379":1,"2509":2,"2510":1,"2512":3,"2513":2,"2549":8,"2633":12,"2809":1,"2812":5,"2813":1,"2814":3,"2815":2}}],["forward",{"0":{"1706":1,"2813":1},"2":{"75":1,"77":1,"223":1,"412":2,"414":1,"421":1,"431":1,"436":1,"439":1,"451":1,"454":1,"456":1,"650":1,"949":1,"951":1,"1104":2,"1105":3,"1328":1,"1348":1,"1717":1,"1792":3,"1917":3,"1925":1,"1927":1,"2222":2,"2300":1,"2301":1,"2302":2,"2392":1,"2508":1,"2515":1,"2517":1,"2532":1,"2549":3,"2580":1,"2771":1,"2806":1,"2807":1,"2809":1,"2812":2,"2813":1,"2814":3,"2817":1,"2832":1}}],["forwarding",{"0":{"423":1,"1348":1,"1923":1,"1927":1,"2304":1,"2812":1},"1":{"1924":1,"1925":1,"1926":1},"2":{"75":1,"414":1,"436":1,"438":1,"453":1,"456":2,"1105":2,"1109":1,"1329":1,"1351":1,"1789":1,"1792":2,"1824":1,"1917":2,"1924":1,"1928":3,"1932":2,"2309":1,"2392":1,"2404":2,"2481":1,"2549":5,"2806":1,"2817":1}}],["for",{"0":{"85":1,"86":1,"180":1,"466":1,"467":1,"553":1,"554":1,"798":1,"868":1,"900":1,"904":1,"912":1,"1011":1,"1052":1,"1076":1,"1193":1,"1344":1,"1435":1,"1973":1,"2217":1,"2251":1,"2333":1,"2335":1,"2336":1,"2340":1,"2346":1,"2356":1,"2359":1,"2377":1,"2397":1,"2399":1,"2429":1,"2430":1,"2519":1,"2555":1,"2581":1,"2587":1,"2589":1,"2594":1,"2626":1,"2664":1,"2667":1,"2695":1,"2719":1,"2755":1},"1":{"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"1345":1,"1346":1,"1347":1,"1348":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"2398":1,"2431":1,"2432":1,"2433":1,"2434":1,"2595":1,"2596":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"10":1,"13":1,"25":1,"32":1,"33":3,"41":2,"51":1,"54":1,"55":1,"56":1,"57":1,"58":1,"66":1,"74":1,"81":1,"84":1,"87":1,"88":1,"91":1,"101":1,"106":1,"110":1,"112":1,"119":1,"120":1,"121":1,"132":1,"139":1,"140":1,"144":1,"154":1,"157":2,"165":2,"168":1,"174":2,"175":1,"176":1,"177":1,"188":1,"209":1,"210":1,"212":2,"213":1,"214":5,"215":1,"216":1,"220":2,"223":4,"228":1,"229":2,"232":1,"233":1,"238":1,"239":2,"244":1,"245":2,"277":1,"292":1,"296":3,"297":2,"298":2,"300":2,"302":2,"305":5,"307":2,"308":1,"310":2,"320":3,"322":3,"323":2,"324":1,"330":1,"334":1,"338":3,"339":1,"347":1,"348":2,"351":1,"362":1,"370":1,"376":3,"377":1,"384":1,"388":5,"389":1,"390":3,"393":1,"408":1,"409":1,"414":1,"419":1,"423":1,"424":2,"429":1,"430":1,"435":1,"438":1,"446":1,"447":1,"448":1,"449":1,"452":1,"453":1,"454":1,"455":1,"470":2,"480":1,"493":1,"523":1,"524":2,"536":1,"542":1,"556":2,"569":1,"577":3,"581":1,"582":1,"586":2,"587":5,"598":1,"624":3,"627":1,"646":1,"649":1,"650":1,"656":4,"668":1,"675":3,"680":1,"681":1,"683":3,"684":1,"685":2,"686":2,"687":4,"693":1,"696":1,"706":1,"716":1,"718":1,"720":5,"722":1,"723":1,"728":1,"737":1,"745":2,"747":2,"751":2,"753":2,"755":1,"756":1,"757":3,"758":1,"760":1,"761":2,"762":1,"764":2,"767":2,"771":2,"772":1,"773":1,"774":2,"775":1,"776":3,"777":1,"779":1,"781":1,"782":5,"783":1,"784":5,"785":1,"786":4,"787":1,"788":5,"789":1,"792":1,"794":1,"801":2,"823":1,"829":1,"834":1,"835":1,"836":2,"837":4,"840":1,"841":8,"844":6,"845":2,"847":1,"848":4,"849":3,"851":5,"852":6,"857":3,"860":4,"861":2,"863":2,"864":1,"868":4,"869":7,"871":1,"872":2,"874":2,"876":2,"879":3,"880":1,"881":3,"882":1,"883":1,"884":2,"885":1,"886":1,"891":1,"892":3,"893":1,"894":1,"899":1,"900":1,"902":2,"903":2,"904":5,"907":1,"908":1,"909":1,"911":1,"913":8,"914":2,"915":2,"916":6,"917":3,"918":9,"919":5,"920":2,"924":1,"925":2,"926":2,"927":1,"928":2,"929":2,"930":1,"933":2,"948":3,"951":2,"953":1,"954":1,"959":3,"961":4,"965":2,"966":1,"967":2,"968":3,"969":1,"971":1,"974":1,"975":2,"988":1,"992":1,"993":1,"994":1,"996":2,"1006":1,"1014":2,"1019":3,"1021":1,"1024":2,"1026":1,"1029":1,"1031":2,"1032":1,"1033":1,"1035":2,"1036":3,"1037":6,"1038":3,"1042":2,"1048":1,"1049":2,"1052":1,"1054":2,"1055":1,"1056":4,"1058":1,"1060":2,"1063":3,"1064":5,"1066":2,"1067":6,"1068":3,"1069":4,"1070":4,"1071":1,"1073":5,"1074":2,"1075":3,"1076":1,"1078":2,"1079":1,"1080":1,"1081":2,"1084":2,"1086":4,"1094":3,"1096":2,"1097":6,"1098":7,"1099":2,"1100":8,"1101":5,"1102":3,"1103":1,"1104":1,"1105":2,"1107":2,"1110":1,"1111":6,"1113":1,"1114":1,"1115":4,"1118":1,"1121":3,"1123":1,"1125":1,"1127":5,"1128":1,"1129":3,"1132":1,"1133":3,"1135":2,"1138":3,"1141":2,"1142":3,"1144":1,"1145":1,"1146":1,"1147":2,"1148":2,"1149":1,"1150":3,"1152":1,"1153":1,"1154":1,"1158":1,"1160":1,"1161":1,"1162":5,"1164":1,"1165":2,"1166":1,"1167":1,"1168":1,"1171":1,"1172":2,"1174":2,"1175":1,"1178":1,"1179":1,"1180":2,"1181":3,"1185":1,"1196":1,"1197":2,"1198":1,"1202":1,"1204":1,"1205":3,"1206":4,"1207":2,"1211":1,"1212":1,"1213":4,"1214":2,"1216":3,"1217":4,"1218":2,"1220":2,"1221":1,"1222":1,"1223":1,"1224":2,"1225":2,"1226":2,"1232":1,"1234":3,"1236":3,"1238":1,"1239":2,"1247":1,"1250":2,"1252":1,"1254":3,"1255":3,"1258":1,"1259":2,"1260":1,"1272":1,"1274":1,"1280":4,"1281":1,"1303":4,"1304":1,"1305":1,"1307":1,"1309":1,"1312":1,"1320":1,"1323":3,"1324":3,"1325":1,"1326":2,"1327":3,"1329":2,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1343":2,"1349":2,"1351":3,"1352":1,"1355":2,"1356":1,"1358":6,"1361":1,"1362":1,"1363":2,"1366":5,"1367":2,"1368":2,"1374":2,"1377":1,"1381":2,"1382":5,"1385":14,"1386":20,"1388":2,"1389":1,"1390":1,"1391":3,"1392":1,"1393":1,"1394":1,"1395":3,"1396":8,"1398":6,"1399":1,"1400":3,"1401":5,"1402":2,"1403":5,"1404":4,"1405":3,"1406":1,"1409":4,"1410":4,"1412":2,"1413":1,"1414":1,"1415":4,"1416":5,"1423":2,"1430":2,"1431":1,"1432":3,"1434":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":2,"1441":1,"1442":1,"1443":1,"1447":5,"1448":1,"1450":1,"1451":1,"1454":4,"1458":2,"1460":1,"1468":1,"1470":3,"1471":5,"1472":1,"1473":1,"1474":3,"1475":5,"1477":6,"1479":2,"1480":2,"1487":1,"1489":2,"1492":1,"1499":1,"1503":1,"1509":1,"1511":9,"1514":1,"1515":3,"1516":2,"1517":1,"1518":1,"1519":5,"1523":1,"1524":2,"1525":1,"1528":1,"1530":1,"1531":2,"1533":2,"1540":4,"1544":2,"1552":2,"1554":1,"1555":1,"1558":1,"1559":3,"1563":2,"1564":1,"1565":1,"1566":1,"1567":1,"1569":1,"1572":2,"1573":2,"1574":1,"1581":4,"1582":1,"1586":1,"1590":2,"1592":1,"1596":1,"1597":1,"1599":2,"1604":1,"1605":1,"1606":1,"1608":1,"1609":3,"1614":1,"1615":1,"1616":1,"1618":2,"1620":3,"1622":1,"1623":1,"1625":1,"1628":2,"1629":1,"1632":1,"1633":1,"1637":1,"1639":4,"1641":2,"1649":1,"1651":4,"1656":1,"1657":1,"1659":1,"1661":1,"1664":2,"1668":1,"1670":3,"1673":1,"1674":1,"1677":1,"1678":1,"1680":1,"1684":5,"1685":1,"1686":1,"1690":3,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":3,"1701":2,"1703":2,"1705":1,"1706":2,"1708":1,"1711":2,"1720":1,"1722":9,"1732":1,"1738":3,"1743":6,"1751":2,"1756":1,"1757":2,"1758":1,"1762":1,"1764":5,"1771":2,"1774":1,"1785":4,"1788":1,"1789":4,"1791":1,"1792":294,"1796":1,"1799":2,"1801":2,"1802":2,"1803":1,"1804":3,"1805":2,"1807":3,"1809":2,"1813":1,"1817":1,"1823":4,"1825":1,"1830":1,"1835":1,"1836":7,"1840":3,"1841":1,"1844":2,"1846":2,"1847":2,"1848":2,"1850":1,"1851":2,"1856":2,"1857":2,"1858":3,"1859":1,"1862":2,"1868":1,"1870":1,"1871":1,"1872":1,"1874":3,"1875":1,"1876":2,"1882":2,"1884":2,"1886":3,"1895":1,"1896":2,"1898":5,"1899":1,"1901":3,"1906":3,"1911":1,"1913":1,"1915":1,"1917":2,"1918":7,"1921":1,"1922":2,"1924":3,"1927":2,"1928":1,"1929":1,"1932":1,"1933":1,"1934":1,"1935":1,"1937":3,"1940":1,"1941":1,"1942":1,"1944":2,"1951":1,"1952":1,"1953":1,"1954":1,"1956":1,"1957":2,"1958":4,"1961":1,"1965":1,"1967":2,"1968":1,"1969":1,"1974":1,"1983":1,"1991":2,"1994":2,"1998":1,"2000":2,"2002":2,"2008":1,"2010":2,"2013":1,"2019":10,"2020":7,"2024":1,"2034":3,"2038":4,"2040":1,"2041":1,"2045":1,"2047":8,"2054":2,"2055":1,"2063":1,"2070":1,"2071":1,"2075":1,"2077":2,"2084":1,"2088":1,"2092":3,"2094":3,"2096":1,"2098":2,"2104":2,"2105":1,"2106":3,"2107":1,"2109":1,"2122":1,"2124":2,"2125":3,"2127":1,"2130":4,"2137":1,"2141":4,"2153":1,"2154":2,"2157":2,"2164":3,"2168":1,"2169":1,"2170":1,"2172":1,"2173":1,"2174":1,"2176":1,"2177":5,"2181":4,"2183":1,"2184":2,"2185":2,"2191":1,"2192":1,"2193":1,"2197":1,"2201":1,"2205":1,"2209":1,"2210":1,"2212":1,"2217":1,"2220":1,"2221":2,"2222":3,"2223":1,"2224":2,"2225":3,"2226":1,"2228":1,"2230":1,"2232":2,"2235":1,"2237":1,"2242":1,"2245":1,"2247":6,"2250":1,"2251":2,"2252":4,"2253":3,"2254":6,"2255":1,"2256":3,"2257":6,"2258":3,"2259":1,"2261":3,"2264":9,"2265":15,"2266":6,"2267":2,"2270":5,"2272":2,"2273":3,"2274":3,"2277":2,"2278":2,"2279":1,"2284":1,"2291":1,"2296":1,"2297":3,"2305":1,"2307":2,"2309":1,"2310":1,"2313":1,"2318":1,"2321":2,"2322":1,"2323":1,"2324":1,"2326":1,"2327":1,"2330":3,"2333":1,"2335":1,"2336":1,"2337":3,"2338":2,"2340":1,"2343":1,"2346":1,"2347":1,"2350":2,"2351":1,"2357":1,"2358":1,"2359":2,"2364":1,"2366":1,"2370":2,"2371":1,"2372":8,"2375":4,"2379":3,"2380":1,"2381":1,"2382":2,"2384":1,"2385":2,"2389":1,"2391":3,"2392":1,"2393":1,"2394":4,"2395":3,"2398":2,"2402":1,"2406":1,"2407":5,"2410":1,"2411":1,"2413":1,"2414":1,"2415":2,"2417":2,"2419":1,"2421":1,"2422":5,"2423":2,"2424":1,"2425":1,"2426":1,"2428":1,"2429":1,"2430":3,"2431":3,"2433":1,"2434":1,"2435":3,"2436":2,"2438":6,"2440":1,"2447":1,"2450":2,"2451":1,"2455":1,"2456":2,"2459":2,"2461":1,"2463":1,"2466":2,"2468":2,"2472":1,"2479":1,"2481":1,"2482":3,"2483":2,"2484":1,"2487":1,"2490":1,"2492":2,"2493":2,"2494":1,"2495":2,"2497":2,"2498":4,"2500":1,"2502":4,"2511":3,"2515":1,"2518":1,"2519":4,"2520":1,"2523":3,"2525":2,"2526":1,"2529":2,"2530":1,"2531":1,"2532":2,"2533":1,"2534":4,"2535":2,"2537":13,"2541":1,"2542":1,"2543":3,"2546":2,"2549":4,"2550":1,"2554":2,"2555":1,"2558":2,"2559":1,"2562":1,"2565":3,"2569":1,"2572":1,"2575":5,"2576":2,"2577":1,"2580":5,"2581":1,"2585":1,"2586":3,"2587":1,"2588":3,"2590":3,"2591":1,"2594":1,"2595":1,"2596":1,"2597":1,"2600":1,"2603":1,"2604":2,"2607":1,"2608":1,"2611":1,"2614":4,"2615":1,"2621":4,"2622":1,"2627":2,"2628":3,"2629":1,"2632":3,"2633":8,"2634":9,"2635":14,"2641":3,"2645":1,"2648":1,"2649":1,"2651":1,"2652":2,"2655":2,"2656":1,"2659":2,"2664":2,"2665":1,"2666":1,"2667":1,"2670":2,"2671":1,"2677":1,"2679":1,"2680":1,"2682":2,"2686":1,"2687":1,"2688":1,"2689":1,"2693":1,"2699":1,"2700":1,"2705":1,"2706":1,"2711":1,"2713":1,"2718":1,"2719":1,"2722":1,"2723":2,"2725":1,"2726":2,"2729":1,"2731":1,"2737":1,"2740":1,"2744":1,"2750":1,"2752":1,"2754":1,"2759":1,"2763":1,"2764":1,"2765":2,"2769":8,"2772":4,"2774":2,"2775":2,"2776":2,"2779":3,"2783":1,"2786":1,"2788":1,"2789":2,"2790":4,"2791":1,"2792":3,"2793":1,"2795":1,"2797":1,"2800":1,"2802":1,"2804":1,"2806":1,"2809":1,"2810":1,"2812":2,"2813":2,"2814":1,"2818":1,"2824":4,"2825":4,"2826":1,"2827":1,"2829":1,"2830":5,"2831":2,"2833":1,"2834":1,"2835":2,"2838":1,"2840":1,"2841":2,"2849":1,"2851":1,"2853":1,"2854":1,"2855":1,"2856":1,"2857":1,"2858":1,"2859":1,"2860":1,"2861":1,"2865":1,"2868":2,"2869":2,"2870":2,"2871":2,"2872":1,"2873":2,"2878":2,"2879":2,"2880":1}}],["form>",{"2":{"1491":1,"1792":1}}],["formfieldname",{"2":{"1488":1,"1489":1,"1792":2}}],["formed",{"2":{"1424":1,"1427":1,"1428":1}}],["formula",{"2":{"1169":1,"1429":1}}],["formulas",{"2":{"952":1}}],["formdata",{"2":{"894":4,"1366":5,"1410":6}}],["formal",{"2":{"1405":1}}],["formalized",{"2":{"848":1}}],["formatvalue",{"2":{"2372":1}}],["formattable",{"2":{"2614":1}}],["formatted",{"2":{"1618":1,"1792":1,"2679":1}}],["formatting",{"2":{"484":1,"971":1,"1792":1,"1809":2,"2206":1,"2270":1,"2328":1,"2635":1,"2829":1}}],["format=json",{"2":{"1792":1,"2056":1,"2674":1}}],["format=",{"2":{"1792":1,"2047":1}}],["format=excel",{"2":{"679":1,"959":1,"961":1,"1374":1}}],["format=html",{"2":{"167":1,"679":1,"959":1,"961":1,"2056":1,"2322":1,"2674":1}}],["format>",{"2":{"674":1}}],["formatstring",{"2":{"2270":1}}],["formats",{"0":{"280":1,"959":1,"963":1,"2053":1,"2210":1},"1":{"960":1,"2054":1,"2055":1,"2056":1,"2211":1,"2212":1},"2":{"133":1,"214":1,"277":1,"280":1,"848":1,"880":1,"1034":1,"1098":1,"1143":1,"1358":1,"1731":1,"2212":2,"2264":1,"2502":1,"2635":1}}],["format",{"0":{"138":1,"161":1,"228":1,"232":1,"267":1,"491":1,"673":1,"679":1,"900":1,"958":1,"960":1,"962":1,"965":1,"1728":1,"1782":1,"2054":1,"2055":1,"2056":1,"2072":1,"2650":1,"2651":1,"2652":1,"2674":1,"2686":1},"1":{"268":1,"269":1,"270":1,"271":1,"272":1,"273":1,"274":1,"275":1,"276":1,"277":1,"278":1,"279":1,"280":1,"281":1,"674":1,"675":1,"676":1,"677":1,"678":1,"679":1,"680":1,"681":1,"682":1,"963":1,"964":1,"1729":1,"1730":1,"2073":1,"2074":1,"2075":1,"2076":1,"2077":1,"2078":1,"2079":1,"2080":1,"2081":1,"2082":1,"2083":1,"2651":1,"2652":1,"2653":1},"2":{"63":1,"92":2,"98":2,"110":2,"118":1,"122":2,"133":1,"141":2,"156":1,"161":1,"164":2,"165":1,"167":9,"197":1,"211":3,"212":1,"213":1,"214":1,"217":2,"228":1,"232":2,"267":1,"279":1,"322":1,"383":1,"423":1,"428":1,"524":1,"537":1,"567":1,"664":1,"665":1,"673":3,"674":2,"675":7,"677":2,"678":1,"679":5,"680":1,"681":1,"682":1,"720":1,"723":5,"726":2,"747":4,"768":1,"776":6,"781":3,"782":2,"784":2,"786":3,"788":9,"814":1,"848":1,"849":1,"868":1,"879":2,"892":7,"897":2,"904":4,"911":1,"947":1,"949":2,"952":1,"956":1,"957":8,"959":2,"960":4,"961":4,"963":2,"964":1,"965":2,"966":3,"970":1,"971":1,"1017":1,"1034":1,"1054":1,"1098":2,"1099":2,"1109":1,"1111":3,"1138":1,"1237":1,"1279":2,"1360":1,"1374":5,"1386":1,"1398":1,"1401":1,"1410":1,"1413":5,"1447":1,"1450":1,"1451":1,"1454":3,"1457":1,"1511":2,"1518":1,"1521":1,"1532":1,"1535":2,"1608":1,"1620":1,"1685":1,"1689":1,"1726":1,"1728":1,"1731":3,"1733":1,"1740":1,"1743":1,"1748":1,"1750":1,"1764":1,"1769":1,"1789":1,"1792":33,"1837":1,"1844":1,"1887":1,"1906":1,"1917":2,"1974":1,"2039":1,"2047":3,"2056":2,"2060":1,"2072":3,"2074":2,"2075":2,"2076":2,"2077":8,"2078":1,"2079":4,"2081":2,"2083":1,"2130":3,"2146":1,"2164":2,"2165":3,"2193":1,"2202":1,"2205":1,"2207":1,"2210":1,"2233":1,"2253":2,"2255":1,"2261":1,"2264":4,"2265":1,"2271":2,"2272":1,"2288":1,"2304":1,"2320":1,"2322":8,"2329":1,"2348":1,"2370":1,"2372":1,"2380":1,"2554":1,"2555":1,"2603":1,"2607":1,"2611":1,"2634":1,"2635":2,"2649":1,"2650":2,"2651":1,"2652":3,"2653":4,"2656":4,"2664":3,"2674":1,"2686":1,"2692":1,"2726":1,"2758":1,"2765":1,"2814":1,"2823":1,"2833":2,"2834":2,"2849":5,"2856":1}}],["forms",{"0":{"1605":1},"2":{"262":1,"269":1,"277":1,"369":1,"370":1,"378":1,"834":1,"837":1,"1135":1,"1491":1,"1581":1,"1605":1,"1792":3,"2040":1,"2211":1,"2212":1,"2332":1,"2337":1,"2372":1,"2395":1,"2476":1,"2497":1,"2506":1,"2540":1,"2688":1,"2854":1}}],["form",{"0":{"8":1,"175":1,"196":1,"271":1,"272":1,"522":1,"664":1,"665":1,"1491":1},"2":{"175":3,"209":1,"326":1,"355":1,"370":1,"390":2,"417":2,"418":2,"419":2,"420":2,"421":2,"436":1,"441":2,"442":2,"443":2,"444":2,"445":2,"454":1,"779":1,"833":1,"834":1,"851":2,"857":1,"859":1,"879":1,"881":2,"918":1,"938":1,"1043":1,"1044":1,"1061":1,"1142":1,"1157":2,"1383":1,"1385":1,"1413":1,"1429":1,"1489":2,"1491":2,"1564":1,"1572":1,"1792":12,"1824":1,"1862":2,"1917":1,"1927":1,"1948":1,"2038":5,"2039":1,"2181":1,"2221":1,"2332":3,"2378":1,"2389":1,"2476":1,"2484":1,"2540":2,"2549":1,"2687":1,"2814":1,"2836":1,"2845":2}}],["fifth",{"2":{"1590":1}}],["fifteen",{"2":{"848":1,"867":1}}],["ficciones",{"2":{"1440":1,"1442":1}}],["fisy",{"2":{"1254":1}}],["fiaterror",{"2":{"1023":1,"1024":1,"1026":2}}],["fiatsuccess",{"2":{"1023":1,"1024":2,"1026":2}}],["fiatlastupdated",{"2":{"1023":1,"1024":1,"1026":2}}],["fiatrates",{"2":{"1023":1,"1024":2,"1026":2}}],["fiatbasecurrency",{"2":{"1023":1,"1024":1,"1026":2}}],["fiat",{"2":{"1018":1,"1020":6,"1021":7,"1376":4}}],["fidelity",{"2":{"2156":1,"2542":1}}],["fido2",{"0":{"2625":1},"2":{"869":1,"873":1,"1248":1,"1792":2,"1866":1,"2235":1,"2496":1,"2625":1,"2736":1}}],["fiddling",{"2":{"851":1}}],["fiddled",{"2":{"843":1}}],["fight",{"2":{"865":1,"1440":1}}],["figures",{"2":{"869":1}}],["figure",{"2":{"851":1,"872":1,"1381":1}}],["fibonacci",{"2":{"860":1}}],["five",{"2":{"857":1,"865":1,"868":1,"872":1,"1258":1,"1382":2,"1687":1,"1792":1,"1806":1,"1908":1,"2430":1,"2436":1,"2803":1}}],["fitting",{"2":{"860":1}}],["fit",{"2":{"831":1,"869":1,"1435":1,"2170":1,"2463":1}}],["fits",{"2":{"308":1,"831":1,"1135":1,"1280":1,"2177":1}}],["fixing",{"2":{"974":1,"2450":1,"2537":1}}],["fixes",{"0":{"2258":1,"2267":1,"2361":1,"2401":1,"2488":1,"2503":1,"2551":1,"2558":1,"2597":1,"2600":1,"2603":1,"2608":1,"2638":1,"2641":1,"2645":1,"2648":1,"2666":1},"1":{"2259":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2402":1,"2403":1,"2404":1,"2405":1,"2489":1,"2490":1,"2491":1,"2492":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2504":1,"2505":1},"2":{"856":1,"874":1,"1071":1,"2222":1,"2265":1,"2336":1,"2401":1,"2409":2,"2440":1,"2455":1,"2461":1,"2479":1,"2497":1,"2500":1,"2515":1,"2518":1}}],["fixedwindow",{"2":{"476":1,"479":1,"480":1,"868":1,"1069":1,"1158":1,"1162":2,"1792":5,"1950":1,"1951":2,"1955":2,"1958":1,"1959":1,"1960":2,"2257":2,"2378":2,"2379":2,"2441":1,"2443":1,"2444":1,"2470":1,"2471":1}}],["fixed",{"0":{"476":1,"987":1,"1158":1,"1951":1},"1":{"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1},"2":{"1":1,"308":1,"319":1,"388":1,"423":1,"476":3,"967":1,"987":1,"988":1,"990":1,"1067":1,"1101":1,"1158":3,"1163":1,"1511":1,"1516":1,"1577":1,"1792":5,"1856":1,"1950":1,"1951":4,"1960":1,"2177":1,"2247":1,"2257":2,"2258":3,"2261":1,"2265":4,"2267":5,"2274":1,"2278":1,"2360":1,"2378":2,"2442":1,"2490":1,"2494":1,"2551":2,"2555":1,"2558":5,"2566":1,"2569":1,"2572":1,"2589":1,"2597":3,"2600":1,"2603":1,"2608":1,"2611":1,"2615":1,"2618":1,"2626":1,"2634":1,"2638":3,"2641":2,"2645":1,"2648":2,"2666":1}}],["fix",{"0":{"2360":1,"2365":1,"2367":1,"2384":1,"2410":1,"2413":1,"2414":1,"2416":1,"2420":1,"2423":1,"2441":1,"2444":1,"2445":1,"2451":1,"2589":1,"2618":1},"1":{"2411":1,"2412":1,"2413":1,"2415":1,"2416":1,"2421":1,"2422":1,"2423":1,"2424":1,"2442":1,"2443":1,"2444":1},"2":{"852":2,"859":1,"871":1,"872":1,"933":1,"985":1,"1076":1,"1078":1,"1080":1,"1409":1,"1419":1,"1431":1,"2222":5,"2223":5,"2224":3,"2225":3,"2235":1,"2242":4,"2258":5,"2384":1,"2419":1,"2440":1,"2445":1,"2452":1,"2466":1,"2486":1,"2498":2,"2504":1,"2520":1,"2546":1,"2589":2,"2603":1,"2611":1,"2615":1,"2679":1,"2823":1,"2878":1}}],["fixtures",{"0":{"1079":1,"2741":1,"2868":1},"2":{"1079":3,"1082":1,"1094":1,"2110":1,"2114":1,"2153":1,"2167":2,"2530":1,"2531":2,"2541":1,"2545":2,"2739":1,"2867":1,"2869":4,"2876":1}}],["fixture",{"2":{"715":2,"876":1,"1073":1,"1074":6,"1075":1,"1078":2,"1079":1,"2103":1,"2106":1,"2456":1,"2472":1,"2526":2,"2527":1,"2531":3,"2533":1,"2537":1,"2546":1,"2741":1,"2860":2,"2862":1,"2868":6,"2869":6,"2878":1,"2881":1}}],["fingerprints",{"2":{"1210":1}}],["fingerprint",{"2":{"1098":1,"1792":1,"2546":1}}],["finishes",{"2":{"2466":1}}],["finished",{"2":{"852":1}}],["finishing",{"2":{"2440":1}}],["finish",{"2":{"874":1,"1792":1,"2100":1,"2537":1}}],["financialdashboardresponse",{"2":{"1026":1}}],["financialdashboardrequest",{"2":{"1026":1}}],["financial",{"0":{"1018":1},"1":{"1019":1,"1020":1,"1021":1,"1022":1},"2":{"1010":1,"1011":1,"1020":1,"1021":5,"1023":1,"1026":1,"1036":1,"1376":4,"1792":1,"2766":3,"2770":1}}],["finance",{"2":{"866":1,"1037":1,"1196":1}}],["finally",{"0":{"2402":1},"2":{"848":1,"851":1,"1073":1,"1384":1,"1386":1,"1395":1,"1405":1,"1416":1,"2226":1,"2247":1,"2402":1,"2403":1,"2404":1}}],["final",{"2":{"763":1,"773":1,"851":1,"885":1,"903":1,"982":1,"1399":1,"1404":1,"2038":1}}],["findmatchingpathparameter",{"2":{"2614":1}}],["findunknownconfigkeys",{"2":{"2411":1,"2417":1,"2442":1,"2443":1,"2448":1}}],["findings",{"0":{"1261":1},"1":{"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1},"2":{"1090":1}}],["findfirst",{"2":{"1070":1,"1957":1,"2379":1}}],["find",{"2":{"554":1,"876":1,"920":1,"1044":2,"1060":1,"1403":1,"1409":1,"1419":1,"1442":1,"1689":1,"1792":2}}],["finds",{"2":{"388":1,"1403":1,"1437":1}}],["finer",{"2":{"1071":1,"1464":1,"2376":1}}],["finest",{"2":{"913":1}}],["fine",{"2":{"307":1,"835":1,"841":1,"843":2,"852":1,"859":1,"916":1,"1070":1,"1329":1,"1384":1,"1386":1,"1398":1,"1792":1,"2177":1,"2628":1}}],["firing",{"2":{"2504":3}}],["fire",{"2":{"1106":1,"1107":1,"1325":1,"2397":1,"2398":2,"2459":1,"2506":1,"2546":1}}],["firewalls",{"2":{"1014":1}}],["fired",{"0":{"2504":1},"2":{"856":1,"1326":1,"2222":1,"2504":1,"2510":1}}],["fires",{"0":{"2832":1},"2":{"214":2,"388":1,"948":1,"1722":1,"1743":1,"1792":2,"2224":1,"2363":1,"2392":2,"2395":1,"2398":1,"2502":2,"2506":1,"2766":1,"2827":1,"2835":1,"2878":1}}],["firststackframeandmessage",{"2":{"1792":2,"1836":1,"1844":1,"1845":1,"1863":1,"2701":1,"2802":1}}],["firstname",{"2":{"914":2,"915":1,"916":6,"917":4,"918":2,"919":2,"920":5,"1375":1}}],["first",{"0":{"831":1,"1247":1,"2741":1,"2820":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"2821":1,"2822":1},"2":{"39":1,"41":1,"74":1,"108":1,"125":1,"133":1,"214":1,"223":1,"277":1,"299":1,"309":1,"349":1,"378":1,"412":1,"456":1,"565":1,"582":1,"585":1,"586":1,"614":1,"615":1,"694":1,"695":1,"704":1,"706":1,"709":1,"714":1,"747":2,"761":1,"771":1,"776":1,"781":2,"818":1,"831":1,"832":2,"836":1,"838":1,"843":1,"844":3,"852":3,"859":2,"863":1,"864":4,"865":1,"866":1,"868":1,"869":1,"873":1,"876":1,"877":1,"885":1,"892":1,"904":2,"913":2,"914":4,"915":4,"916":5,"918":4,"919":1,"932":1,"969":1,"977":1,"980":1,"981":1,"986":1,"990":1,"992":1,"994":2,"1019":1,"1029":1,"1037":1,"1066":1,"1067":1,"1069":1,"1076":1,"1081":1,"1084":1,"1095":1,"1101":1,"1105":1,"1133":1,"1136":1,"1150":1,"1152":1,"1153":1,"1162":1,"1187":1,"1211":1,"1213":1,"1217":1,"1229":1,"1254":1,"1285":1,"1332":1,"1335":1,"1338":1,"1346":1,"1351":1,"1358":2,"1360":1,"1363":1,"1369":1,"1370":4,"1375":2,"1380":1,"1385":1,"1386":2,"1390":1,"1396":1,"1400":1,"1401":1,"1402":1,"1406":1,"1408":1,"1418":1,"1429":1,"1431":1,"1472":1,"1523":1,"1590":1,"1613":1,"1628":2,"1631":1,"1688":1,"1703":1,"1792":15,"1837":1,"1845":1,"1879":1,"1910":1,"1954":1,"1956":1,"2005":1,"2006":2,"2094":1,"2098":1,"2100":1,"2125":1,"2130":1,"2149":1,"2162":1,"2164":1,"2165":2,"2192":1,"2206":1,"2212":1,"2223":1,"2266":3,"2300":1,"2323":1,"2330":1,"2333":1,"2337":1,"2339":2,"2379":1,"2380":1,"2393":1,"2414":1,"2422":2,"2424":2,"2427":1,"2433":1,"2438":2,"2452":1,"2494":2,"2502":1,"2518":1,"2528":4,"2529":3,"2532":1,"2533":2,"2534":1,"2537":1,"2575":1,"2633":1,"2684":1,"2721":1,"2725":1,"2793":1,"2797":1,"2806":1,"2813":1,"2818":2,"2822":3,"2823":3,"2824":10,"2825":4,"2841":1,"2850":5,"2864":3,"2865":3,"2870":1,"2881":1}}],["fil",{"2":{"388":1,"2493":1}}],["filtertoolsbyrole",{"0":{"1832":1},"2":{"1792":1,"1814":1,"1833":1,"2481":2}}],["filteredrates",{"2":{"1026":3}}],["filtered",{"2":{"521":3,"639":1,"1021":4,"1376":4,"2533":1,"2537":1,"2828":2,"2833":1}}],["filter=login",{"2":{"1792":1,"2092":1,"2094":1,"2096":1,"2537":1,"2877":1}}],["filter=active",{"2":{"467":1}}],["filter=",{"2":{"467":1}}],["filter=null",{"2":{"467":1}}],["filters",{"0":{"349":1,"1908":1,"1909":1,"2431":1},"1":{"1909":1,"1910":1,"1911":1},"2":{"349":1,"354":1,"356":1,"521":2,"668":1,"1096":1,"1305":1,"1326":1,"1385":2,"1792":2,"1909":1,"1910":1,"2156":1,"2419":1,"2430":1,"2433":2,"2435":1,"2438":1,"2542":1,"2751":1,"2799":1,"2804":1}}],["filtering",{"0":{"656":1,"758":1,"1838":1,"1839":1,"1969":1,"2062":1,"2430":1,"2629":1,"2877":1},"1":{"1839":1,"1970":1,"1971":1,"1972":1,"2431":1,"2432":1,"2433":1,"2434":1},"2":{"239":1,"356":1,"650":1,"980":1,"1096":3,"1122":1,"1127":1,"1305":1,"1326":1,"1787":1,"1792":1,"1794":1,"2097":1,"2159":1,"2184":1,"2221":2,"2225":1,"2369":1,"2391":1,"2419":1,"2533":1,"2546":1,"2701":1,"2870":1}}],["filter",{"0":{"354":1,"1910":1,"2096":1,"2433":1,"2678":2},"2":{"168":1,"188":1,"213":2,"223":1,"349":2,"356":1,"378":1,"467":5,"636":1,"638":1,"712":1,"852":1,"860":1,"967":1,"1021":1,"1026":1,"1032":1,"1043":1,"1098":1,"1309":1,"1326":2,"1431":1,"1741":3,"1792":10,"1838":1,"1908":1,"1911":2,"2047":1,"2062":1,"2093":1,"2094":1,"2097":1,"2106":1,"2107":1,"2221":1,"2231":2,"2289":3,"2296":1,"2333":1,"2419":1,"2430":1,"2431":4,"2435":2,"2438":2,"2494":1,"2537":6,"2546":1,"2608":1,"2635":1,"2678":1,"2679":1,"2695":1,"2877":2,"2879":1}}],["fills",{"2":{"948":1,"1039":1,"1371":1,"1372":1,"1426":1,"1923":1,"2183":1,"2187":1,"2283":1,"2509":1,"2760":2,"2762":1,"2810":1,"2848":1}}],["filled",{"0":{"2517":1,"2520":1},"2":{"376":2,"380":1,"383":1,"436":1,"454":1,"529":1,"534":1,"936":1,"1431":2,"1559":1,"1569":3,"1738":1,"1753":1,"1759":1,"1792":4,"1898":1,"1912":1,"1917":1,"1923":1,"1924":1,"1925":2,"2183":3,"2222":3,"2284":1,"2333":1,"2348":1,"2508":1,"2509":1,"2510":1,"2515":2,"2517":1,"2520":1}}],["fill",{"2":{"168":1,"380":1,"535":1,"2183":1,"2322":1,"2333":1,"2504":2}}],["filling",{"2":{"165":1,"384":1}}],["fileid",{"2":{"2648":1}}],["fileinput",{"2":{"894":1,"1361":2}}],["file`",{"2":{"1792":2}}],["fileminimumlevel",{"2":{"1792":1,"1800":1,"1804":2,"1810":1,"2804":2}}],["filemode",{"2":{"1366":1,"1752":1,"1753":2,"1758":2,"1792":2}}],["file>",{"2":{"1792":4}}],["fileoverwrite",{"2":{"1417":1,"1553":1,"1554":1,"1581":1,"1752":1,"1753":1,"1758":1,"1792":3,"1897":1,"1898":1,"1907":1,"2254":1,"2551":1}}],["filepattern",{"0":{"2002":1,"2095":1},"1":{"2003":1},"2":{"1135":1,"1408":1,"1792":4,"1999":1,"2000":1,"2003":3,"2010":1,"2012":1,"2093":1,"2094":1,"2095":3,"2096":1,"2106":1,"2330":2,"2534":1,"2537":3,"2539":2,"2821":1,"2825":1,"2841":1,"2861":1,"2872":1}}],["filepaths",{"2":{"1792":1,"2033":1,"2037":1,"2038":1,"2040":1,"2042":1,"2476":1}}],["filepath",{"2":{"748":1,"937":1,"998":2,"1062":1,"1357":2,"1359":2,"1360":1,"1364":2,"1366":10,"1408":1,"1416":1,"1417":1,"1553":1,"1554":1,"1577":1,"1579":1,"1580":1,"1581":1,"1582":1,"1792":3,"1800":1,"1804":2,"1810":1,"2804":2}}],["filelist",{"2":{"894":1,"1366":1,"1410":2}}],["filegroups",{"2":{"848":1}}],["file=report",{"2":{"544":1}}],["file=users",{"2":{"493":1}}],["file=q1",{"2":{"386":1}}],["filename>",{"2":{"674":1}}],["filename",{"2":{"392":2,"393":1,"493":1,"546":1,"675":1,"748":1,"762":2,"763":1,"764":1,"765":1,"766":1,"772":2,"773":2,"774":1,"883":2,"884":1,"887":3,"888":1,"893":2,"894":1,"903":3,"904":1,"957":1,"961":3,"1357":2,"1358":2,"1359":1,"1360":1,"1366":13,"1410":1,"1577":1,"1792":1,"1897":1,"1898":1,"1907":1,"1911":1,"2079":1,"2254":1,"2318":1,"2327":1,"2358":1,"2434":1,"2653":1,"2840":1}}],["filenames",{"2":{"387":1,"1358":1,"2841":1}}],["filename=report",{"2":{"544":1}}],["filename=users",{"2":{"493":1}}],["filename=q1",{"2":{"386":1}}],["filename=",{"2":{"386":1,"392":1,"492":1,"493":1,"543":1,"544":1,"1189":1,"1373":1}}],["filesizelimitbytes",{"2":{"1792":1,"1800":1,"1804":2,"1810":1,"2804":1}}],["filesize",{"2":{"1366":1}}],["filestream",{"2":{"1366":1}}],["filesystemuploadhandler",{"2":{"2615":1}}],["filesystemuseuniquefilename",{"2":{"1356":1,"1792":1,"2123":1,"2127":2,"2132":1}}],["filesystemchecktext",{"2":{"1792":1,"2123":1,"2127":2}}],["filesystemcheckimage",{"2":{"1356":1,"1792":1,"2123":1,"2127":2,"2132":1}}],["filesystemcreatepathifnotexists",{"2":{"1356":1,"1792":1,"2123":1,"2127":2,"2132":1}}],["filesystempath",{"2":{"1356":1,"1650":1,"1651":1,"1654":2,"1662":1,"1663":1,"1792":2,"2123":1,"2127":2,"2132":1,"2297":2,"2565":1,"2757":1}}],["filesystemkey",{"2":{"1356":1,"1792":1,"2123":1,"2127":2}}],["filesystemenabled",{"2":{"1356":1,"1792":1,"2123":1,"2127":2,"2132":1}}],["filesystem",{"2":{"869":1,"1651":1,"1653":1,"1654":1,"1662":1,"1663":1,"1664":1,"1792":4,"2157":1,"2296":1,"2297":2,"2543":1,"2565":1,"2757":1}}],["files",{"0":{"372":1,"383":1,"614":1,"1017":1,"1072":1,"1078":1,"1368":1,"1378":1,"1386":1,"1387":1,"1570":1,"1757":1,"2032":1,"2319":1,"2320":1,"2340":1,"2348":1,"2356":1,"2367":1,"2448":1,"2457":1,"2539":1,"2540":1,"2626":1,"2683":1,"2684":1,"2685":1,"2731":1,"2774":1,"2804":1,"2842":1,"2850":1,"2858":1},"1":{"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1,"2684":1,"2685":1,"2686":1,"2843":1,"2851":1,"2852":1,"2853":1},"2":{"170":1,"220":2,"239":2,"265":1,"279":1,"372":1,"377":1,"384":1,"559":1,"586":2,"587":1,"588":1,"595":1,"614":1,"615":2,"616":1,"617":1,"626":1,"659":1,"665":1,"689":2,"693":2,"696":1,"698":2,"703":3,"706":1,"708":2,"710":1,"713":3,"746":4,"747":3,"749":1,"754":1,"759":1,"769":1,"778":1,"784":1,"786":1,"829":1,"832":1,"833":1,"835":1,"837":1,"841":1,"848":2,"849":1,"867":1,"868":2,"869":1,"871":3,"872":1,"873":2,"874":1,"875":1,"879":1,"886":1,"894":8,"907":1,"911":3,"975":3,"985":1,"986":1,"988":1,"1006":1,"1017":1,"1027":1,"1036":1,"1037":5,"1038":2,"1043":2,"1044":1,"1073":3,"1074":1,"1077":1,"1078":2,"1080":1,"1082":1,"1083":1,"1084":1,"1086":2,"1094":3,"1095":2,"1096":4,"1099":1,"1108":1,"1113":2,"1114":1,"1121":3,"1125":4,"1126":4,"1127":3,"1135":3,"1154":1,"1176":1,"1179":1,"1353":1,"1354":2,"1358":1,"1361":1,"1363":1,"1366":9,"1368":1,"1371":1,"1377":2,"1378":9,"1379":1,"1382":4,"1384":1,"1385":2,"1386":10,"1388":3,"1389":2,"1390":2,"1391":2,"1392":2,"1393":1,"1394":2,"1396":1,"1397":1,"1398":1,"1401":3,"1404":1,"1405":2,"1406":1,"1407":1,"1408":2,"1410":9,"1414":3,"1416":1,"1417":2,"1418":1,"1419":2,"1420":3,"1554":2,"1565":1,"1574":3,"1581":1,"1582":1,"1584":1,"1615":1,"1661":1,"1728":1,"1751":1,"1753":1,"1754":2,"1757":3,"1758":2,"1789":2,"1791":1,"1792":34,"1796":1,"1798":1,"1804":3,"1836":1,"1896":1,"1898":1,"1914":1,"1927":1,"1967":1,"1998":1,"2000":1,"2002":1,"2003":2,"2004":2,"2007":2,"2009":1,"2034":1,"2035":1,"2036":3,"2037":1,"2038":2,"2042":1,"2092":1,"2094":4,"2095":2,"2097":2,"2099":2,"2107":1,"2113":1,"2114":1,"2126":1,"2127":2,"2128":1,"2130":1,"2135":1,"2153":3,"2155":1,"2156":1,"2157":3,"2162":1,"2165":11,"2166":1,"2167":2,"2168":1,"2190":2,"2221":5,"2228":2,"2235":1,"2254":2,"2258":1,"2264":1,"2267":1,"2278":1,"2317":3,"2319":1,"2321":2,"2323":1,"2328":2,"2330":2,"2333":1,"2337":2,"2338":1,"2339":3,"2340":1,"2342":1,"2344":1,"2348":1,"2350":1,"2359":1,"2367":4,"2372":2,"2388":1,"2428":1,"2435":1,"2474":1,"2479":1,"2481":1,"2525":2,"2526":2,"2527":4,"2528":1,"2532":1,"2533":1,"2535":3,"2537":8,"2538":1,"2539":3,"2540":1,"2541":3,"2542":1,"2543":4,"2545":2,"2555":1,"2577":1,"2621":1,"2626":1,"2627":1,"2648":2,"2649":1,"2662":1,"2680":1,"2681":2,"2684":5,"2685":3,"2686":1,"2687":1,"2691":1,"2693":1,"2694":1,"2695":1,"2705":1,"2709":1,"2713":1,"2722":2,"2723":2,"2729":1,"2732":1,"2739":1,"2742":1,"2772":5,"2774":2,"2775":1,"2794":1,"2804":1,"2820":2,"2826":1,"2837":1,"2839":1,"2840":1,"2841":2,"2844":1,"2845":1,"2855":1,"2856":1,"2857":3,"2858":4,"2859":1,"2860":2,"2861":1,"2862":4,"2869":1,"2877":1,"2878":1}}],["file",{"0":{"238":1,"239":1,"265":1,"377":1,"392":1,"492":1,"612":1,"659":1,"665":1,"754":1,"757":1,"778":1,"784":1,"902":1,"964":1,"988":1,"1099":1,"1352":1,"1363":1,"1368":1,"1565":1,"1577":1,"1580":1,"1608":1,"1654":1,"1751":1,"1756":1,"1804":1,"1986":1,"1987":1,"1998":1,"2127":1,"2165":1,"2333":1,"2336":1,"2357":1,"2358":1,"2528":1,"2533":1,"2686":1,"2694":1,"2722":1,"2799":1,"2821":1,"2825":1,"2839":1,"2863":1,"2870":1},"1":{"378":1,"379":1,"380":1,"381":1,"755":1,"756":1,"757":1,"758":1,"785":1,"903":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1752":1,"1753":1,"1754":1,"1755":1,"1756":1,"1757":1,"1758":1,"1759":1,"1760":1,"1761":1,"1999":1,"2000":1,"2001":1,"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2840":1,"2841":1,"2842":1,"2843":1,"2844":1,"2845":1,"2846":1,"2847":1,"2848":1,"2849":1,"2850":1,"2851":1,"2852":1,"2853":1,"2854":1,"2855":1,"2856":1,"2857":1,"2858":1,"2859":1,"2864":1,"2865":1,"2866":1,"2867":1,"2868":1},"2":{"7":1,"16":1,"37":1,"48":1,"61":1,"71":1,"74":1,"104":1,"115":1,"128":1,"136":1,"157":18,"160":1,"165":1,"170":3,"175":1,"179":1,"184":1,"206":2,"220":2,"228":1,"234":1,"238":1,"239":4,"241":1,"247":1,"288":1,"296":1,"298":1,"312":1,"320":3,"324":1,"325":1,"338":2,"351":1,"360":1,"369":1,"376":1,"377":1,"385":1,"386":3,"388":1,"393":2,"401":1,"415":1,"417":1,"418":1,"419":1,"420":1,"421":1,"438":1,"441":1,"442":1,"443":1,"444":1,"445":1,"466":1,"487":1,"492":1,"493":2,"503":1,"510":1,"520":1,"539":1,"544":2,"559":2,"567":1,"568":2,"587":2,"588":2,"592":1,"618":1,"624":1,"625":1,"626":2,"641":1,"659":1,"665":3,"674":1,"675":1,"677":1,"678":1,"679":3,"684":1,"693":1,"694":3,"695":1,"696":1,"697":1,"699":2,"703":1,"704":1,"705":3,"706":1,"707":2,"708":1,"709":1,"711":2,"713":2,"714":2,"715":2,"716":4,"717":2,"720":2,"722":1,"723":4,"724":2,"733":1,"743":1,"746":3,"747":7,"748":6,"750":1,"754":1,"755":1,"756":5,"757":18,"758":2,"762":3,"763":1,"764":2,"765":1,"766":1,"768":1,"772":3,"774":2,"777":2,"778":1,"779":3,"780":1,"781":1,"782":1,"784":20,"785":2,"788":1,"791":1,"797":1,"811":1,"829":1,"832":1,"833":4,"834":1,"835":7,"847":1,"848":1,"851":1,"868":2,"871":1,"872":2,"876":2,"878":1,"879":4,"880":4,"881":2,"882":1,"883":2,"884":1,"887":1,"888":3,"894":1,"902":6,"903":6,"904":8,"907":1,"909":1,"911":5,"914":1,"920":5,"926":1,"946":3,"956":1,"957":4,"959":2,"960":1,"961":2,"964":1,"971":3,"975":1,"976":1,"986":1,"988":1,"990":1,"994":1,"1005":1,"1010":1,"1027":3,"1036":3,"1037":7,"1038":1,"1065":3,"1073":7,"1074":1,"1076":2,"1077":2,"1078":1,"1079":1,"1080":3,"1082":2,"1086":6,"1094":7,"1095":4,"1096":1,"1099":6,"1110":2,"1118":1,"1121":2,"1125":1,"1126":1,"1127":5,"1135":4,"1138":1,"1141":1,"1142":1,"1150":2,"1161":1,"1179":2,"1182":2,"1189":1,"1196":1,"1199":1,"1200":1,"1208":3,"1254":1,"1323":1,"1327":3,"1351":4,"1352":1,"1353":1,"1354":2,"1355":8,"1356":1,"1357":4,"1358":11,"1359":4,"1360":6,"1361":1,"1363":2,"1364":2,"1365":1,"1366":37,"1367":4,"1368":6,"1369":2,"1370":1,"1371":1,"1372":2,"1373":2,"1374":3,"1375":1,"1376":3,"1377":3,"1378":1,"1379":1,"1380":4,"1382":5,"1386":8,"1387":1,"1391":1,"1394":2,"1396":2,"1398":2,"1401":1,"1405":3,"1406":2,"1407":3,"1408":2,"1409":1,"1410":9,"1412":2,"1413":3,"1414":1,"1416":2,"1417":1,"1418":1,"1419":3,"1422":5,"1442":2,"1458":1,"1502":1,"1504":2,"1547":1,"1554":1,"1559":3,"1567":2,"1569":1,"1570":4,"1571":5,"1574":2,"1577":1,"1579":1,"1580":1,"1581":2,"1584":1,"1603":1,"1604":1,"1607":1,"1608":3,"1632":1,"1651":3,"1661":1,"1663":1,"1664":1,"1689":2,"1727":2,"1751":1,"1752":1,"1753":6,"1754":4,"1756":2,"1758":3,"1787":1,"1789":4,"1791":1,"1792":70,"1794":1,"1796":2,"1798":1,"1799":1,"1804":4,"1810":1,"1836":1,"1898":3,"1912":1,"1914":1,"1920":1,"1928":1,"1973":2,"2000":3,"2001":1,"2003":1,"2004":4,"2005":2,"2007":4,"2008":1,"2010":2,"2011":4,"2012":2,"2013":2,"2032":1,"2034":2,"2036":2,"2038":2,"2076":1,"2078":1,"2079":4,"2081":1,"2082":1,"2092":1,"2094":2,"2096":1,"2097":3,"2099":1,"2101":2,"2106":3,"2109":2,"2110":3,"2111":3,"2112":1,"2114":2,"2120":1,"2122":2,"2123":1,"2124":2,"2125":1,"2127":5,"2128":1,"2132":2,"2133":1,"2134":1,"2135":1,"2136":1,"2147":1,"2153":2,"2155":1,"2157":6,"2158":2,"2159":3,"2160":1,"2164":2,"2165":18,"2166":1,"2167":1,"2171":1,"2176":1,"2185":4,"2190":1,"2193":2,"2221":3,"2222":2,"2228":1,"2247":2,"2254":3,"2272":4,"2278":1,"2297":1,"2318":2,"2319":1,"2320":1,"2323":2,"2325":1,"2328":3,"2329":1,"2330":2,"2332":1,"2333":1,"2336":1,"2337":1,"2338":1,"2339":1,"2354":4,"2356":1,"2357":2,"2358":4,"2359":1,"2360":2,"2364":3,"2365":1,"2366":4,"2371":9,"2372":1,"2394":1,"2435":1,"2456":1,"2481":2,"2482":3,"2484":6,"2489":1,"2493":1,"2504":1,"2512":1,"2518":2,"2523":1,"2525":1,"2526":2,"2527":3,"2528":5,"2529":1,"2530":4,"2531":9,"2532":3,"2533":16,"2534":1,"2535":5,"2536":3,"2537":16,"2539":3,"2540":6,"2541":3,"2542":1,"2543":7,"2545":3,"2546":5,"2549":1,"2565":2,"2577":1,"2581":1,"2589":3,"2615":1,"2627":1,"2648":1,"2649":1,"2653":4,"2662":1,"2664":3,"2677":2,"2682":2,"2684":1,"2687":1,"2694":1,"2700":1,"2701":1,"2705":1,"2712":1,"2714":1,"2719":1,"2722":3,"2723":1,"2725":1,"2727":1,"2729":2,"2731":2,"2742":2,"2751":2,"2762":2,"2774":2,"2776":1,"2779":2,"2788":1,"2795":1,"2799":1,"2804":1,"2809":1,"2813":1,"2821":3,"2825":3,"2826":3,"2829":1,"2834":1,"2839":1,"2840":2,"2841":3,"2842":1,"2844":1,"2845":2,"2850":1,"2856":3,"2857":1,"2859":3,"2860":1,"2861":1,"2862":3,"2863":2,"2864":3,"2865":1,"2866":1,"2867":1,"2869":3,"2870":5,"2871":2,"2872":1,"2873":2,"2878":4,"2880":2,"2881":2,"2882":1}}],["field2",{"2":{"383":1,"2348":1}}],["field1",{"2":{"383":1,"2348":1}}],["fields=id",{"2":{"1695":1,"1792":1}}],["fields",{"0":{"210":1,"1459":1,"1521":1,"1675":1,"1732":1,"1956":1,"2376":1,"2377":1,"2518":1},"1":{"1676":1,"1677":1},"2":{"74":1,"75":1,"77":1,"110":1,"202":1,"210":1,"328":1,"330":1,"335":1,"337":1,"436":1,"768":2,"786":4,"849":1,"851":1,"891":2,"897":2,"914":2,"915":2,"916":1,"917":1,"995":1,"1016":1,"1023":1,"1067":1,"1068":1,"1071":2,"1097":3,"1098":1,"1105":2,"1111":1,"1190":1,"1192":1,"1193":1,"1279":1,"1359":1,"1426":1,"1429":1,"1431":2,"1459":3,"1464":3,"1491":1,"1520":1,"1523":1,"1559":1,"1569":1,"1671":1,"1723":1,"1725":1,"1732":1,"1733":1,"1759":1,"1792":12,"1912":1,"1923":1,"1924":1,"1925":2,"1973":1,"1974":1,"2000":1,"2010":2,"2128":1,"2217":1,"2222":2,"2264":5,"2330":1,"2375":4,"2376":3,"2379":1,"2380":1,"2397":1,"2399":1,"2427":1,"2509":1,"2510":1,"2513":1,"2518":1,"2520":1,"2521":1,"2522":1,"2523":1,"2587":1,"2603":1,"2607":2,"2760":2,"2763":3,"2769":1,"2812":1}}],["field",{"0":{"75":1,"1031":1,"1268":1,"1491":1,"2504":1,"2618":1},"2":{"74":2,"75":2,"77":1,"210":2,"212":1,"216":1,"388":1,"786":1,"847":1,"851":1,"861":1,"904":1,"915":1,"1031":3,"1067":1,"1068":1,"1097":1,"1111":1,"1157":1,"1191":1,"1193":3,"1217":1,"1229":1,"1233":1,"1409":3,"1428":1,"1431":2,"1489":1,"1491":1,"1521":1,"1523":1,"1569":1,"1620":1,"1670":1,"1671":3,"1722":6,"1725":1,"1732":2,"1792":21,"1879":1,"1925":3,"1948":1,"1956":1,"1958":1,"1968":1,"1974":6,"2038":1,"2128":1,"2222":4,"2255":4,"2264":7,"2270":1,"2372":1,"2376":4,"2378":2,"2381":2,"2395":1,"2412":1,"2470":1,"2504":4,"2506":2,"2512":1,"2517":1,"2518":4,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1,"2551":2,"2586":1,"2589":1,"2607":6,"2618":1,"2641":1,"2763":2,"2769":7}}],["fell",{"2":{"2491":1}}],["feauture",{"2":{"917":1}}],["features",{"0":{"258":1,"1028":1,"1094":1,"1096":1,"1101":1,"1109":1,"1397":1,"1789":1,"1796":1,"1928":1,"2480":1,"2501":1,"2649":1,"2856":1},"1":{"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1398":1,"1399":1,"2481":1,"2482":1,"2483":1,"2484":1,"2502":1},"2":{"395":1,"427":1,"868":5,"869":2,"912":2,"995":1,"1037":1,"1062":1,"1064":1,"1073":1,"1098":1,"1101":1,"1106":1,"1121":1,"1125":1,"1127":1,"1179":1,"1180":1,"1182":1,"1323":1,"1368":1,"1382":1,"1385":1,"1386":1,"1396":1,"1397":1,"1402":1,"1518":1,"1528":1,"1792":2,"1979":1,"2016":1,"2021":2,"2160":1,"2265":1,"2266":1,"2277":1,"2314":1,"2329":1,"2409":1,"2534":1,"2549":1,"2627":1,"2632":2,"2701":1,"2856":1}}],["feature",{"0":{"1083":1,"1093":1,"2251":1,"2282":1,"2287":1,"2291":1,"2300":1,"2329":1,"2425":1,"2430":1,"2549":1,"2625":1,"2632":1,"2633":1,"2634":1,"2635":1,"2650":1,"2659":1},"1":{"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":2,"1095":2,"1096":2,"1097":2,"1098":2,"1099":2,"1100":2,"1101":2,"1102":2,"1103":2,"1104":2,"1105":2,"1106":2,"1107":2,"1108":2,"1109":2,"1110":2,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"2283":1,"2284":1,"2285":1,"2286":1,"2288":1,"2289":1,"2290":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2426":1,"2427":1,"2428":1,"2429":1,"2431":1,"2432":1,"2433":1,"2434":1,"2651":1,"2652":1,"2653":1},"2":{"3":1,"387":1,"395":1,"427":1,"803":1,"868":1,"871":2,"872":4,"958":1,"986":1,"1010":1,"1037":1,"1080":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1109":1,"1110":1,"1111":1,"1135":1,"1180":2,"1181":2,"1328":1,"1330":1,"1351":1,"1366":2,"1368":1,"1377":1,"1382":1,"1385":2,"1386":2,"1396":1,"1398":1,"1399":1,"1402":1,"1404":2,"1414":4,"1457":1,"1605":1,"1792":2,"1894":1,"2000":1,"2001":1,"2003":1,"2013":1,"2040":1,"2153":1,"2159":1,"2238":1,"2264":1,"2404":1,"2420":1,"2437":1,"2459":1,"2461":1,"2474":1,"2481":1,"2497":1,"2542":1,"2554":1,"2572":1,"2575":1,"2586":1,"2588":1,"2607":1,"2625":1,"2632":1,"2645":1,"2688":1,"2713":2,"2755":1,"2860":1,"2882":1}}],["february",{"2":{"859":1,"887":1,"947":1}}],["feet",{"2":{"852":1}}],["feel",{"2":{"832":1,"872":1}}],["feeds",{"2":{"167":1,"1011":1,"2322":1,"2849":1}}],["feed",{"2":{"165":1,"663":1,"833":1,"1309":1,"1373":1,"1374":1,"2391":1}}],["feedback",{"0":{"3":1},"2":{"3":1,"876":1,"986":1,"1081":2,"1386":1}}],["fetching",{"2":{"860":1,"1021":1}}],["fetched",{"2":{"263":1,"531":2,"851":1,"1015":1,"2051":1,"2344":1,"2766":1}}],["fetches",{"2":{"214":1,"859":1,"1018":1,"1147":1,"1423":1,"1427":1,"2502":1,"2764":1,"2816":1}}],["fetch",{"0":{"1410":1,"1426":1,"1431":1},"2":{"206":2,"209":1,"429":1,"452":1,"679":1,"720":1,"723":1,"834":1,"849":1,"856":1,"860":1,"872":2,"938":1,"948":1,"957":1,"961":3,"995":2,"1004":1,"1011":1,"1021":2,"1024":1,"1026":1,"1029":1,"1037":2,"1067":1,"1107":1,"1139":1,"1317":1,"1335":1,"1342":1,"1346":1,"1386":2,"1399":1,"1403":1,"1405":5,"1406":1,"1407":1,"1408":1,"1410":2,"1411":1,"1412":2,"1413":3,"1415":1,"1416":3,"1423":3,"1424":1,"1427":1,"1430":2,"1431":2,"1432":1,"1492":1,"1519":1,"1567":1,"1568":1,"1569":1,"1575":1,"1792":1,"2020":1,"2164":3,"2222":1,"2247":2,"2310":1,"2313":1,"2489":1,"2519":2,"2521":1,"2523":1,"2562":1,"2656":2,"2762":2,"2770":1,"2810":3,"2815":1,"2826":1,"2836":1}}],["fewer",{"2":{"843":1,"876":3,"1042":1,"1065":1,"1349":1,"1440":1,"2400":1}}],["few",{"2":{"0":1,"297":1,"386":1,"395":1,"869":1,"876":1,"910":1,"1064":1,"1181":1,"1209":1,"1302":1,"1385":1,"1432":1,"2176":1,"2398":1}}],["faq",{"0":{"2707":1},"1":{"2708":1,"2709":1,"2710":1,"2711":1,"2712":1,"2713":1,"2714":1,"2715":1,"2716":1,"2717":1,"2718":1,"2719":1,"2720":1,"2721":1,"2722":1,"2723":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2729":1,"2730":1,"2731":1,"2732":1,"2733":1,"2734":1,"2735":1,"2736":1,"2737":1,"2738":1,"2739":1,"2740":1,"2741":1,"2742":1,"2743":1,"2744":1,"2745":1,"2746":1,"2747":1,"2748":1,"2749":1,"2750":1,"2751":1,"2752":1,"2753":1,"2754":1,"2755":1,"2756":1,"2757":1,"2758":1},"2":{"2805":1}}],["fault",{"2":{"2384":1}}],["favor",{"2":{"2255":1}}],["favorite",{"2":{"452":2,"860":1,"1081":3,"1347":2,"1399":1,"1419":1}}],["fa",{"2":{"2144":5}}],["fan",{"2":{"1745":1,"2346":1,"2347":1,"2464":1,"2498":1}}],["fancy",{"2":{"1393":1}}],["faking",{"2":{"1075":1}}],["fakes",{"2":{"1075":1}}],["fake",{"2":{"1":1,"851":1,"875":1,"1706":1,"1717":1}}],["famous",{"2":{"852":1,"861":1}}],["family",{"2":{"711":1,"832":1,"1411":1,"1792":1,"2073":1,"2075":1,"2080":1,"2869":1}}],["familiar",{"2":{"1":1,"1010":1,"2193":1}}],["fatal",{"2":{"1792":4,"1801":1,"1806":1,"2414":1,"2415":1,"2544":2,"2794":1,"2801":2}}],["fat",{"2":{"844":1}}],["fairly",{"2":{"913":1}}],["fair",{"2":{"841":1,"845":1,"851":1,"1254":2}}],["failfast",{"0":{"2100":1},"2":{"1792":1,"2093":1,"2094":1,"2537":1}}],["failovers",{"2":{"1152":1}}],["failover",{"2":{"1101":2,"1121":1,"1172":1,"1177":1,"1180":2,"1181":2,"1182":1,"1618":1,"1626":1,"1628":1,"1792":1,"2266":2}}],["failing",{"2":{"299":1,"1080":1,"1081":1,"1792":1,"2094":1,"2104":2,"2242":1,"2376":1,"2378":1,"2492":1,"2528":1,"2533":1,"2535":3,"2537":1,"2546":1,"2802":1,"2864":1,"2878":1,"2880":2}}],["fail",{"0":{"1360":1,"1460":1},"2":{"102":1,"213":1,"587":1,"829":1,"982":1,"994":1,"996":2,"1032":1,"1154":1,"1157":1,"1385":1,"1386":2,"1409":1,"1449":1,"1464":1,"1704":1,"1739":1,"1741":1,"1792":6,"1948":1,"2007":1,"2094":1,"2225":2,"2287":1,"2289":1,"2328":1,"2375":1,"2376":1,"2377":1,"2378":1,"2380":1,"2389":1,"2392":1,"2394":1,"2409":1,"2415":1,"2416":1,"2428":1,"2435":1,"2528":1,"2532":1,"2533":1,"2535":2,"2537":2,"2600":1,"2608":1,"2634":1,"2800":1,"2864":1,"2875":1,"2880":1}}],["fails",{"2":{"48":1,"51":1,"63":1,"109":1,"308":1,"424":2,"584":1,"701":1,"715":1,"747":3,"781":2,"819":1,"829":2,"871":1,"872":1,"875":1,"876":1,"892":1,"901":1,"903":2,"904":2,"975":1,"982":1,"984":2,"985":1,"986":2,"996":1,"997":1,"1055":1,"1071":1,"1076":3,"1150":1,"1173":1,"1177":1,"1386":1,"1409":1,"1419":1,"1422":1,"1527":1,"1621":1,"1792":6,"2000":1,"2007":1,"2107":1,"2109":1,"2112":1,"2177":1,"2267":1,"2307":2,"2320":2,"2337":1,"2394":1,"2528":1,"2530":1,"2532":2,"2533":3,"2537":2,"2649":1,"2664":2,"2813":2,"2841":1,"2864":1,"2876":1,"2879":1,"2881":1}}],["failed",{"2":{"39":2,"41":1,"208":1,"209":1,"210":1,"310":2,"447":1,"449":1,"713":1,"777":1,"818":1,"930":11,"1021":2,"1031":1,"1056":8,"1062":1,"1074":1,"1105":1,"1152":1,"1320":1,"1341":1,"1354":1,"1360":1,"1410":1,"1427":2,"1624":1,"1674":1,"1722":1,"1732":1,"1792":1,"1918":1,"1922":1,"2094":1,"2103":1,"2107":1,"2113":2,"2141":1,"2149":1,"2258":1,"2264":1,"2365":1,"2405":2,"2445":1,"2491":1,"2492":1,"2496":1,"2504":1,"2526":1,"2528":2,"2535":1,"2536":1,"2537":2,"2549":1,"2575":1,"2597":1,"2722":1,"2762":1,"2763":1,"2810":2,"2860":1,"2864":1}}],["failurethreshold",{"2":{"1773":2}}],["failures",{"0":{"1592":1,"2405":1,"2492":1},"2":{"188":1,"213":1,"310":1,"569":1,"575":1,"879":1,"1041":1,"1071":1,"1101":1,"1135":1,"1151":1,"1152":1,"1153":2,"1155":1,"1177":1,"1180":1,"1250":1,"1622":1,"1623":1,"1624":1,"1792":2,"1824":2,"2007":2,"2226":1,"2271":1,"2296":1,"2324":1,"2328":1,"2384":1,"2481":2,"2491":1,"2880":1}}],["failure",{"0":{"35":1,"2401":1},"1":{"2402":1,"2403":1,"2404":1,"2405":1},"2":{"33":2,"142":1,"213":1,"214":1,"298":1,"300":1,"301":1,"309":1,"310":1,"818":1,"852":1,"1032":1,"1152":1,"1172":1,"1303":1,"1402":1,"1471":1,"1472":1,"1592":1,"1593":1,"1624":2,"1674":1,"1740":1,"1743":1,"1774":1,"1792":9,"2094":1,"2100":1,"2141":1,"2149":1,"2177":1,"2255":3,"2287":1,"2288":1,"2384":1,"2401":1,"2404":1,"2405":1,"2414":1,"2453":1,"2492":1,"2502":1,"2528":2,"2531":1,"2533":1,"2535":3,"2537":1,"2575":2,"2669":1,"2765":1,"2785":1,"2815":1,"2864":2,"2871":1,"2880":1}}],["facility",{"2":{"2802":1}}],["facial",{"2":{"1210":1}}],["facing",{"0":{"1911":1,"2430":1,"2434":1},"1":{"2431":1,"2432":1,"2433":1,"2434":1},"2":{"369":1,"414":1,"663":1,"836":2,"911":1,"1792":2,"2302":1,"2434":1,"2482":1}}],["face",{"2":{"1098":1,"1792":1}}],["facebook",{"0":{"1695":1},"2":{"868":1,"1060":1,"1445":1,"1465":1,"1682":1,"1690":1,"1695":5,"1788":1,"1792":8,"1894":1,"2736":1}}],["faces",{"2":{"852":2}}],["fact",{"2":{"666":1,"843":1,"848":1,"876":1,"1385":4,"1393":2,"1399":1,"1401":1,"1402":1,"1404":1,"2179":1}}],["factories",{"2":{"2741":1,"2868":1}}],["factory",{"2":{"868":1,"1181":1,"1304":1,"1317":1,"1416":1,"1581":1,"2459":1,"2461":2,"2462":1,"2463":2,"2466":2,"2830":1}}],["factor",{"2":{"41":1,"308":2,"1090":1,"1164":1,"1267":1,"2177":1,"2804":1}}],["facts",{"2":{"1":1,"1385":1}}],["falling",{"2":{"1792":1,"1818":1,"2348":1,"2377":1,"2405":1}}],["falls",{"2":{"675":1,"852":1,"904":1,"1162":1,"1464":1,"1819":1,"1941":1,"1956":1,"2224":1,"2266":1,"2377":1,"2379":1,"2405":1,"2422":1,"2453":1}}],["fall",{"2":{"380":1,"904":1,"1150":1,"1447":1,"1451":1,"1454":2,"1525":1,"1605":1,"1628":2,"1792":7,"1856":1,"2266":2,"2333":1,"2380":1,"2405":1,"2427":1,"2455":1,"2497":1,"2664":1,"2688":1}}],["fallback",{"0":{"904":1,"1941":1,"2664":1},"2":{"319":2,"378":1,"747":3,"781":2,"892":1,"904":4,"1162":1,"1792":3,"1937":1,"1955":1,"1957":1,"2020":1,"2232":1,"2233":1,"2326":1,"2333":1,"2370":1,"2379":2,"2435":1,"2481":1,"2486":1,"2551":3,"2649":4,"2664":4}}],["false`",{"2":{"1792":1}}],["false>",{"2":{"1792":1}}],["false",{"0":{"1568":1},"2":{"31":1,"33":1,"35":4,"39":3,"41":1,"256":1,"301":1,"317":1,"381":1,"586":1,"624":1,"704":1,"720":1,"722":3,"747":4,"768":1,"776":2,"892":2,"894":1,"919":2,"929":2,"956":1,"990":1,"998":1,"1021":2,"1026":2,"1067":1,"1068":2,"1070":1,"1102":1,"1147":1,"1148":1,"1150":3,"1199":2,"1213":1,"1220":1,"1224":2,"1338":1,"1339":1,"1356":1,"1360":2,"1366":1,"1374":1,"1386":1,"1410":1,"1412":3,"1415":3,"1416":1,"1417":2,"1445":4,"1447":1,"1451":1,"1454":3,"1458":2,"1459":1,"1462":1,"1469":4,"1470":1,"1475":1,"1477":1,"1482":1,"1488":3,"1489":3,"1493":1,"1498":1,"1499":1,"1501":1,"1502":2,"1510":3,"1511":4,"1515":2,"1518":1,"1521":1,"1529":1,"1539":2,"1540":1,"1544":1,"1553":9,"1554":1,"1559":4,"1561":2,"1563":2,"1568":1,"1569":2,"1570":1,"1571":3,"1576":1,"1577":1,"1580":2,"1581":1,"1603":1,"1604":1,"1608":1,"1616":1,"1617":1,"1618":1,"1630":1,"1631":1,"1633":1,"1638":2,"1639":2,"1641":1,"1644":1,"1646":1,"1650":2,"1651":2,"1662":1,"1669":1,"1670":1,"1676":1,"1677":1,"1678":3,"1684":1,"1702":1,"1703":1,"1721":1,"1722":2,"1752":2,"1753":2,"1759":1,"1763":1,"1764":1,"1781":1,"1792":155,"1800":3,"1804":2,"1805":2,"1807":2,"1810":1,"1814":3,"1816":2,"1824":1,"1827":2,"1832":2,"1836":3,"1837":1,"1844":2,"1850":1,"1851":2,"1856":2,"1863":3,"1870":1,"1874":2,"1897":3,"1898":3,"1912":1,"1916":2,"1917":2,"1927":2,"1931":1,"1936":2,"1937":2,"1948":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1956":1,"1957":1,"1959":2,"1966":1,"1967":2,"1973":1,"1974":1,"1975":1,"1979":1,"1980":1,"1988":1,"1994":8,"1999":2,"2000":3,"2001":1,"2009":1,"2011":1,"2015":1,"2016":1,"2033":2,"2034":1,"2037":1,"2038":2,"2040":2,"2046":2,"2047":2,"2073":1,"2074":2,"2093":4,"2094":5,"2107":1,"2111":6,"2123":11,"2124":4,"2125":1,"2126":4,"2127":4,"2130":4,"2132":2,"2154":2,"2156":1,"2222":1,"2223":1,"2254":1,"2255":1,"2257":3,"2264":1,"2265":3,"2267":1,"2272":1,"2274":3,"2277":1,"2279":1,"2330":4,"2333":1,"2337":1,"2342":1,"2350":2,"2351":1,"2353":1,"2354":2,"2360":2,"2372":1,"2375":3,"2378":2,"2379":2,"2380":4,"2382":2,"2406":1,"2415":2,"2431":1,"2436":1,"2453":1,"2455":4,"2471":3,"2476":2,"2484":4,"2486":2,"2502":1,"2520":1,"2528":1,"2532":6,"2537":7,"2542":1,"2549":4,"2551":4,"2554":3,"2565":1,"2586":1,"2587":1,"2607":1,"2629":2,"2632":1,"2633":1,"2634":2,"2635":2,"2641":1,"2686":1,"2692":1,"2697":1,"2701":5,"2724":1,"2765":1,"2769":2,"2814":2,"2815":1,"2848":1,"2864":1,"2879":1}}],["farewell",{"2":{"913":1}}],["farm",{"2":{"335":2,"913":1,"918":2,"919":2,"2586":3}}],["far",{"2":{"307":1,"847":1,"849":1,"859":1,"866":1,"871":2,"873":1,"908":1,"1401":1,"1403":1,"1404":1,"1428":1,"1431":1}}],["fastest",{"0":{"1137":1},"1":{"1138":1,"1139":1},"2":{"1123":1,"1127":1,"1137":1,"1402":1,"1792":2,"1937":1,"1938":2,"2274":1}}],["faster",{"2":{"691":1,"803":1,"871":1,"872":4,"874":1,"876":1,"911":1,"946":1,"993":1,"994":1,"1076":2,"1090":1,"1121":1,"1127":1,"1269":1,"1349":1,"1382":2,"1385":1,"1401":1,"1405":1,"2184":1,"2245":1,"2246":1,"2270":4,"2621":5,"2789":1}}],["fastify",{"2":{"1007":1,"1255":1,"1257":1,"1265":1,"1277":1,"1279":1,"1280":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["fastapi",{"2":{"868":1,"869":4,"871":1,"873":1,"876":1,"1037":1,"1255":1,"1257":1,"1265":1,"1268":1,"1271":2,"1277":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1335":1,"1409":1}}],["fast",{"0":{"993":1,"1460":1,"2744":1},"2":{"104":3,"577":1,"837":1,"907":1,"1005":1,"1015":1,"1074":1,"1081":1,"1147":1,"1150":4,"1154":2,"1180":1,"1353":1,"1354":2,"1365":1,"1382":1,"1386":1,"1393":1,"1401":1,"1449":1,"1515":2,"1520":1,"1529":3,"1533":1,"1599":1,"1792":2,"2007":1,"2096":1,"2270":2,"2274":1,"2328":1,"2375":1,"2376":1,"2378":1,"2380":1,"2394":1,"2416":1,"2428":1,"2435":1,"2445":1,"2528":1,"2533":1,"2776":1,"2841":1,"2864":1}}],["bdn",{"2":{"2397":1}}],["btn",{"2":{"938":3,"1061":4}}],["btree",{"2":{"864":1}}],["b9",{"2":{"927":2}}],["bqc6",{"2":{"927":2}}],["blade",{"2":{"1792":1}}],["blank",{"2":{"1409":1,"1819":1,"2529":1,"2762":1,"2865":1}}],["blast",{"2":{"868":1,"1458":1,"1792":1,"2375":1}}],["blindly",{"2":{"1170":1}}],["bleak",{"2":{"913":1,"919":2}}],["bluntly",{"2":{"847":1}}],["blunt",{"2":{"847":1}}],["blow",{"2":{"1067":1}}],["blowfish",{"2":{"928":1}}],["blobs",{"2":{"851":1}}],["blob",{"2":{"429":1,"851":3,"1412":1,"1792":1,"2310":1,"2313":1}}],["blocking",{"2":{"1107":1,"1170":1,"1274":1,"1792":1,"2760":1}}],["blocked",{"2":{"876":1,"1014":1,"1324":1,"2362":1}}],["blocks",{"0":{"1395":1,"1396":1,"2352":1,"2529":1,"2855":1,"2865":1},"2":{"583":1,"624":1,"687":1,"689":1,"694":1,"698":1,"699":2,"701":1,"849":1,"851":1,"990":1,"994":1,"1005":2,"1073":1,"1074":1,"1082":1,"1104":1,"1105":1,"1107":1,"1378":1,"1394":2,"1395":1,"1396":2,"1419":1,"1424":1,"1428":1,"1459":1,"1792":4,"2092":1,"2109":1,"2167":1,"2221":1,"2267":1,"2319":2,"2342":1,"2375":1,"2405":1,"2528":1,"2529":1,"2531":1,"2537":1,"2802":1,"2841":1,"2854":1,"2855":2,"2858":2,"2863":1,"2865":1,"2869":2}}],["block",{"0":{"621":1},"2":{"176":1,"239":2,"480":1,"621":1,"659":1,"689":1,"690":1,"691":1,"692":1,"694":1,"698":1,"699":2,"701":1,"702":1,"704":1,"706":1,"709":1,"714":1,"826":1,"868":1,"869":1,"979":1,"986":1,"989":1,"1014":1,"1067":1,"1068":1,"1069":2,"1074":1,"1075":1,"1076":1,"1077":2,"1079":1,"1082":1,"1101":2,"1106":1,"1121":1,"1162":2,"1372":1,"1376":1,"1378":1,"1382":1,"1393":1,"1394":5,"1395":1,"1396":1,"1442":1,"1459":1,"1792":10,"1824":2,"1951":1,"1952":1,"1953":1,"1954":1,"1957":2,"2075":2,"2094":1,"2109":5,"2110":4,"2167":1,"2190":1,"2221":1,"2319":2,"2323":1,"2338":2,"2365":2,"2375":1,"2379":2,"2389":3,"2438":1,"2447":2,"2481":1,"2528":6,"2529":5,"2530":7,"2535":2,"2537":3,"2539":1,"2546":1,"2843":1,"2855":2,"2861":1,"2863":1,"2864":3,"2865":4,"2866":2,"2881":1}}],["blog",{"0":{"791":1,"1037":1,"1065":1,"2134":1},"2":{"0":1,"327":1,"338":1,"847":1,"866":1,"1384":3,"1399":1,"1400":1,"1401":1,"1403":1,"1404":2,"1409":1,"1834":1,"1894":1,"2164":1,"2166":1,"2479":1,"2625":1,"2713":1,"2737":1}}],["b1",{"2":{"775":1,"900":1}}],["bmp",{"2":{"747":1,"1792":1,"2123":1,"2125":1}}],["bc",{"2":{"650":5,"663":2,"2265":1,"2828":3}}],["bcrypt",{"0":{"927":1,"944":1},"1":{"928":1,"929":1,"930":1},"2":{"308":3,"921":1,"927":1,"928":2,"944":1,"946":1,"1049":2,"2177":3}}],["brute",{"2":{"1217":1,"1224":1,"1252":1,"1792":1,"1874":1}}],["breaches",{"2":{"1867":1}}],["breach",{"2":{"1209":1,"1251":1,"1942":1}}],["breakdown",{"2":{"1128":1,"2713":1}}],["breaking",{"0":{"2368":1,"2376":1,"2378":1,"2454":1,"2485":1,"2642":1},"1":{"2369":1,"2370":1,"2371":1,"2372":1,"2486":1,"2487":1},"2":{"1069":1,"1193":1,"1792":1,"2164":1,"2165":1,"2223":2,"2224":1,"2258":1,"2369":1,"2419":1,"2448":1,"2450":1,"2479":1,"2482":1,"2537":1}}],["breakers",{"2":{"1011":1}}],["break",{"0":{"2517":1},"2":{"1001":1,"1080":1,"1428":1,"2040":1,"2434":1,"2476":1,"2543":1,"2546":1,"2878":1}}],["breaks",{"2":{"972":1,"973":1,"983":1,"2425":1}}],["brevity",{"2":{"913":1}}],["brief",{"2":{"1152":1,"1404":1}}],["briefly",{"2":{"1066":1}}],["bring",{"2":{"1045":1,"1825":1,"2481":1}}],["brings",{"2":{"835":1,"947":1,"1080":1,"1402":1,"2537":1,"2878":1}}],["brilliant",{"2":{"913":2,"919":2}}],["brittle",{"0":{"879":1}}],["bridges",{"2":{"1372":1}}],["bridge",{"2":{"841":1,"845":1,"1351":1,"1376":1,"2179":1,"2855":1}}],["brackets",{"2":{"2400":1}}],["bracket",{"2":{"1398":1}}],["brace",{"0":{"389":1},"2":{"389":1,"1605":1,"2270":1,"2497":1,"2688":1}}],["braces",{"2":{"387":1,"388":1,"2400":1}}],["branding",{"2":{"1685":1}}],["brand",{"2":{"990":2,"1404":1}}],["branched",{"2":{"2414":1}}],["branches",{"2":{"873":1}}],["branching",{"2":{"1394":3}}],["branch",{"2":{"835":1,"1260":2,"1792":1,"2309":1,"2407":1,"2763":1}}],["br",{"2":{"650":7,"663":6,"836":4,"949":1,"1940":1}}],["brotli",{"0":{"1940":1},"2":{"1101":1,"1790":1,"1792":2,"1797":1,"1935":1,"1937":2,"1940":2,"1941":1}}],["broke",{"2":{"1385":1,"1419":1}}],["broker",{"2":{"1302":1,"1303":1,"1304":1}}],["brokers",{"2":{"1037":1,"1103":1,"1372":1}}],["broken",{"2":{"382":1,"863":1,"1792":1,"2112":1,"2153":1,"2157":1,"2334":1,"2454":1,"2461":1,"2519":1,"2532":1,"2537":1,"2543":2,"2555":1,"2857":1}}],["browse",{"2":{"1045":1}}],["browsersessionmessagekey",{"2":{"1684":1,"1792":1}}],["browsersessionstatuskey",{"2":{"1684":1,"1792":1}}],["browsers",{"2":{"51":1,"1137":1,"1138":2,"1139":1,"1363":1,"1447":1,"1449":2,"1641":1,"1645":1,"1792":4,"1983":1,"2016":1,"2017":1,"2393":1,"2425":1,"2428":1,"2632":2}}],["browser",{"0":{"1413":1,"2830":1},"2":{"45":1,"51":1,"663":1,"679":1,"720":1,"723":1,"832":1,"949":3,"959":2,"961":1,"965":2,"966":1,"971":1,"1008":1,"1044":1,"1060":1,"1063":1,"1068":2,"1136":1,"1137":1,"1138":1,"1189":1,"1199":1,"1200":1,"1207":1,"1209":1,"1211":3,"1215":1,"1218":1,"1220":3,"1221":3,"1222":3,"1229":1,"1235":1,"1236":1,"1305":2,"1362":1,"1373":1,"1405":8,"1412":1,"1413":1,"1432":2,"1459":1,"1644":1,"1684":1,"1687":1,"1792":14,"1823":1,"1825":1,"1868":3,"1879":1,"1885":1,"1886":1,"2016":3,"2018":1,"2020":2,"2021":1,"2023":1,"2024":1,"2040":1,"2054":1,"2075":1,"2166":1,"2173":1,"2363":1,"2375":1,"2425":1,"2429":2,"2615":1,"2632":4,"2635":2,"2651":1,"2656":1,"2768":1,"2827":2,"2828":1,"2834":1,"2836":1}}],["brown",{"2":{"913":1,"919":2}}],["broader",{"2":{"868":1}}],["broadcasts",{"2":{"663":1,"1305":1,"1309":1,"1372":1,"2391":1,"2827":1,"2829":1,"2836":1,"2838":1}}],["broadcasting",{"2":{"650":1,"666":1,"2490":1}}],["broadcast",{"0":{"645":1},"2":{"642":1,"650":2,"663":1,"1103":1,"1305":1,"1309":1,"1312":1,"1315":1,"1318":1,"1320":1,"1792":1,"2391":2,"2392":1,"2828":1,"2829":4,"2832":3,"2833":1,"2834":1,"2835":3,"2836":3}}],["broadcaster",{"2":{"636":1,"650":3,"664":1,"666":2,"668":1,"669":1,"1305":1,"1309":1,"2362":2,"2393":1,"2407":1,"2828":2,"2833":1}}],["broad",{"2":{"349":1,"354":1,"941":1}}],["b>",{"2":{"348":1}}],["bf",{"2":{"308":3,"592":1,"813":1,"928":1,"1307":2,"2147":2,"2177":2,"2575":1}}],["bidirectional",{"2":{"1323":1,"1327":1}}],["bi",{"0":{"1183":1},"1":{"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1},"2":{"1037":2,"1183":2,"1184":1,"1203":2,"1206":4,"1208":1,"2164":2}}],["billing",{"2":{"1577":1,"2869":1}}],["billingapitypes",{"2":{"1577":1}}],["billingapi",{"2":{"1577":1}}],["bills",{"2":{"860":1}}],["bill",{"2":{"856":1,"865":1}}],["bilopavlović",{"2":{"2":1,"1096":1}}],["biometric",{"2":{"1209":1,"1210":2,"1217":1,"1220":1,"1221":1,"1222":1,"1228":2,"1251":1,"1792":4,"1867":1,"1868":1,"1878":2}}],["biometrics",{"2":{"1037":1,"1098":3,"1210":1,"1792":1,"1866":1,"2164":1,"2625":1}}],["bio",{"2":{"815":4}}],["bin",{"2":{"2782":1,"2783":1,"2784":1}}],["binaries",{"2":{"2576":1,"2744":1}}],["binary",{"0":{"1412":1},"2":{"722":1,"747":1,"832":4,"834":1,"866":1,"868":1,"904":1,"954":1,"1084":4,"1087":1,"1088":1,"1094":2,"1108":1,"1117":1,"1121":2,"1127":3,"1323":1,"1327":1,"1358":1,"1360":1,"1362":1,"1363":1,"1381":1,"1382":2,"1405":1,"1412":2,"2162":1,"2329":1,"2372":1,"2415":1,"2621":1,"2664":1,"2785":1}}],["bind",{"0":{"2395":1},"2":{"859":1,"2157":1,"2226":1,"2395":1,"2407":1,"2543":1,"2807":1}}],["binding",{"0":{"2689":1},"2":{"855":1,"869":1,"1074":1,"1102":3,"1825":1,"2115":1,"2223":1,"2394":1,"2424":1,"2438":1,"2481":1,"2496":1,"2498":3,"2527":1,"2645":1,"2687":1,"2689":1,"2702":1,"2739":1,"2860":1}}],["bindings",{"2":{"690":1}}],["bindable",{"2":{"380":1,"1371":1,"2333":1}}],["binds",{"2":{"215":1,"527":1,"852":1,"864":3,"865":1,"1407":1,"1738":1,"2540":1,"2807":1,"2845":1}}],["bitwise",{"2":{"2621":3}}],["bitcoin",{"2":{"1023":1,"1024":1}}],["bits",{"2":{"847":1}}],["bit",{"2":{"309":1,"363":1,"841":2,"845":1,"1049":1,"1394":1,"1395":2,"1396":1,"1400":1,"1405":1,"1656":6,"2423":2}}],["biggest",{"2":{"1385":1,"1403":1}}],["bigger",{"2":{"296":1}}],["big",{"0":{"2171":1},"2":{"844":2,"847":1,"849":2,"861":1,"1073":3,"1254":2,"1385":1,"1388":1,"1390":1,"1391":1,"1393":1,"1395":1,"1401":1,"2170":1}}],["bigquery",{"2":{"834":1}}],["bigint",{"2":{"258":1,"301":1,"585":1,"751":2,"777":2,"903":1,"952":1,"956":5,"1213":2,"1214":2,"1215":1,"1216":1,"1232":3,"1234":4,"1235":2,"1236":3,"1239":2,"1355":4,"1357":4,"1362":2,"1374":4,"1410":2,"1792":3,"1882":1,"1884":1,"1886":1,"1888":1,"2277":1}}],["b",{"0":{"309":1,"2822":1},"2":{"109":1,"307":1,"310":1,"355":1,"491":1,"650":5,"848":2,"914":2,"916":5,"918":4,"1086":2,"1087":1,"1088":2,"1193":1,"1211":1,"1305":2,"1375":2,"1401":1,"1429":6,"1431":2,"1792":4,"1868":1,"1974":2,"2330":2,"2371":2,"2422":2,"2432":1,"2533":2,"2586":1,"2588":2,"2607":2,"2725":1,"2828":2,"2841":1}}],["bxlfbmftztptev9wyxnzd29yza==",{"2":{"38":1,"58":1,"61":1}}],["borges",{"2":{"1442":3}}],["borrow",{"2":{"1435":1}}],["boring",{"2":{"1405":1}}],["border",{"2":{"965":2,"1792":2,"2073":2,"2075":2,"2080":2}}],["bonus",{"0":{"966":1},"1":{"967":1},"2":{"1404":1,"1436":1}}],["bolted",{"2":{"946":1}}],["bob",{"2":{"913":1,"919":2,"938":2,"977":2,"979":2,"980":2,"986":2,"988":2,"990":2,"1051":3,"1061":2,"1305":1,"1307":2,"1313":1,"2009":2,"2842":1}}],["bodies",{"0":{"1030":1,"2446":1},"2":{"876":1,"996":1,"1010":1,"1030":1,"1792":1,"1898":1,"2225":1,"2271":1,"2512":1,"2528":1,"2540":1,"2555":1,"2845":1,"2863":1}}],["bodyparamtobodytests",{"2":{"2523":1}}],["bodyparamgettests",{"2":{"2523":1}}],["bodyjson",{"2":{"1846":1,"1847":1,"1924":2,"2509":1,"2511":1,"2812":2}}],["body>loading",{"2":{"1792":1}}],["body>",{"2":{"539":4,"965":2,"1685":2,"1792":1}}],["bodycolumnname",{"2":{"300":1,"1469":1,"1470":1,"1471":1,"1792":4,"2259":2}}],["body",{"0":{"68":1,"71":1,"72":1,"75":1,"208":1,"257":1,"303":1,"521":1,"533":1,"1270":1,"1294":1,"2204":1,"2286":1,"2491":1,"2518":1,"2519":1},"1":{"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"1295":1},"2":{"33":4,"35":1,"68":2,"69":1,"71":2,"72":3,"73":2,"74":3,"75":4,"76":1,"77":3,"157":1,"165":1,"203":1,"206":3,"207":2,"208":2,"209":4,"210":2,"212":1,"214":2,"215":1,"226":3,"253":1,"257":1,"258":1,"264":2,"297":2,"300":3,"303":6,"313":3,"383":1,"385":1,"387":1,"394":1,"396":1,"409":1,"412":1,"414":4,"419":1,"423":1,"424":1,"428":1,"436":1,"438":1,"439":2,"447":2,"448":2,"449":3,"452":2,"453":1,"454":2,"493":1,"515":4,"516":2,"517":4,"518":1,"521":1,"522":2,"523":1,"524":2,"526":2,"527":1,"533":1,"553":1,"554":1,"555":1,"616":1,"650":1,"665":1,"669":1,"689":1,"695":1,"700":2,"716":1,"829":1,"915":1,"938":1,"982":1,"985":3,"996":1,"1016":1,"1017":2,"1019":2,"1021":2,"1026":1,"1029":2,"1031":3,"1074":1,"1078":2,"1104":1,"1105":10,"1107":1,"1214":5,"1220":3,"1221":3,"1222":3,"1232":4,"1233":2,"1234":3,"1239":1,"1254":1,"1255":2,"1258":1,"1264":1,"1270":1,"1285":1,"1331":1,"1332":5,"1338":3,"1339":2,"1341":2,"1342":1,"1347":2,"1348":2,"1366":2,"1376":2,"1398":8,"1399":1,"1407":1,"1426":3,"1427":1,"1431":13,"1438":1,"1469":1,"1470":1,"1471":2,"1480":2,"1489":1,"1559":1,"1569":1,"1686":1,"1688":1,"1721":1,"1722":2,"1725":1,"1727":2,"1728":1,"1730":1,"1732":2,"1733":1,"1736":2,"1738":1,"1742":3,"1743":2,"1744":2,"1745":2,"1759":2,"1792":31,"1864":1,"1882":2,"1883":2,"1884":2,"1912":1,"1916":1,"1918":2,"1921":2,"1922":2,"1924":9,"1925":7,"1926":1,"1991":1,"2011":1,"2093":2,"2109":3,"2110":2,"2164":2,"2180":1,"2183":1,"2204":2,"2222":8,"2223":1,"2255":2,"2259":1,"2264":8,"2271":1,"2277":4,"2282":1,"2283":3,"2286":2,"2290":3,"2300":1,"2302":1,"2305":1,"2307":1,"2321":1,"2346":4,"2348":1,"2354":1,"2391":2,"2395":2,"2398":1,"2438":1,"2481":1,"2483":1,"2491":3,"2493":1,"2498":3,"2502":2,"2509":5,"2511":1,"2512":1,"2513":3,"2517":3,"2518":8,"2519":8,"2520":2,"2521":3,"2523":11,"2526":1,"2529":4,"2530":4,"2531":1,"2533":1,"2535":1,"2537":2,"2549":8,"2555":1,"2566":1,"2580":1,"2615":1,"2626":1,"2666":1,"2762":6,"2763":4,"2764":3,"2765":1,"2766":4,"2767":4,"2769":2,"2807":2,"2809":4,"2810":5,"2812":1,"2813":2,"2814":1,"2815":2,"2816":1,"2828":2,"2829":1,"2834":2,"2835":1,"2860":1,"2861":1,"2865":2,"2866":2,"2868":1,"2869":2,"2873":1}}],["boilerplate",{"2":{"871":1,"872":1,"873":1,"879":1,"880":1,"1006":1,"1009":1,"1037":3,"1281":1,"1378":1,"1385":1,"1386":1,"1389":1,"1390":2,"1391":2,"1405":1,"1410":1,"1416":1,"2774":1}}],["box",{"2":{"307":1,"363":1,"880":1,"966":1,"1123":1,"1162":1,"1322":1,"1386":1,"1394":1,"1401":1,"2438":1,"2450":1,"2539":1,"2776":1,"2835":1}}],["boosted",{"2":{"1404":1}}],["booga",{"2":{"1401":1}}],["boo",{"2":{"1385":1}}],["boots",{"2":{"1418":1,"1420":1,"2465":1,"2472":1}}],["boot",{"2":{"1007":1,"1037":1,"1064":1,"1076":1,"1255":1,"1257":1,"1265":1,"1269":1,"1270":1,"1277":1,"1278":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1366":1,"2040":1,"2224":1,"2474":1}}],["bool",{"2":{"520":2,"747":4,"748":1,"757":2,"768":3,"776":2,"956":2,"1197":2,"1255":1,"1374":1,"1447":3,"1451":1,"1454":5,"1470":2,"1475":1,"1477":1,"1480":1,"1489":3,"1499":2,"1501":1,"1504":2,"1511":3,"1521":1,"1540":1,"1544":1,"1554":4,"1555":1,"1556":1,"1558":1,"1559":5,"1561":2,"1563":3,"1588":1,"1604":2,"1605":2,"1618":3,"1623":1,"1631":1,"1639":2,"1651":2,"1670":2,"1684":1,"1696":1,"1703":1,"1722":2,"1753":4,"1764":2,"1792":2,"1803":1,"1804":2,"1805":1,"1807":1,"1837":1,"1841":2,"1843":1,"1844":5,"1850":1,"1856":1,"1861":1,"1874":2,"1877":1,"1898":5,"1917":4,"1937":4,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1956":1,"1967":1,"1980":3,"2000":3,"2016":1,"2034":1,"2038":2,"2047":2,"2074":1,"2075":1,"2077":1,"2094":5,"2124":5,"2125":1,"2126":3,"2127":5,"2128":4,"2130":3,"2139":1,"2154":1,"2255":1,"2265":1,"2330":3,"2379":1,"2380":1,"2431":1,"2436":1,"2497":3,"2688":2}}],["booleans",{"2":{"952":1,"1824":1,"1924":1,"2509":1,"2662":1}}],["boolean",{"2":{"31":1,"33":1,"35":2,"37":1,"38":2,"39":2,"188":1,"206":1,"207":1,"208":1,"209":2,"210":1,"256":2,"301":2,"308":1,"370":1,"373":1,"374":2,"378":1,"439":2,"447":1,"449":2,"452":2,"520":1,"582":1,"584":1,"585":1,"592":1,"700":1,"756":4,"894":2,"903":1,"929":1,"952":2,"977":1,"995":2,"996":1,"1019":2,"1020":2,"1024":2,"1026":2,"1031":1,"1057":1,"1067":1,"1074":1,"1092":1,"1105":4,"1150":2,"1193":2,"1213":1,"1215":1,"1237":1,"1332":1,"1338":1,"1339":1,"1341":1,"1347":1,"1355":1,"1357":2,"1366":1,"1368":3,"1386":1,"1387":1,"1398":4,"1399":1,"1408":1,"1410":1,"1426":1,"1428":1,"1431":1,"1471":1,"1480":1,"1504":1,"1529":1,"1689":1,"1725":1,"1732":1,"1736":1,"1743":1,"1792":5,"1816":1,"1827":1,"1832":1,"1887":1,"1921":2,"1922":1,"1967":2,"2102":1,"2109":1,"2167":1,"2177":1,"2187":1,"2221":1,"2264":2,"2277":2,"2296":1,"2309":1,"2333":1,"2337":1,"2415":1,"2528":2,"2530":1,"2535":1,"2549":3,"2588":1,"2621":2,"2629":1,"2692":1,"2762":2,"2763":2,"2764":1,"2766":2,"2810":3,"2815":2,"2847":1,"2848":1,"2864":2,"2866":1,"2881":2}}],["bookkeeping",{"2":{"2537":1}}],["booked",{"2":{"865":1}}],["bookid",{"2":{"335":2,"918":4,"919":9,"920":2,"2586":2,"2590":1,"2611":3}}],["book",{"0":{"1425":1},"1":{"1426":1,"1427":1,"1428":1},"2":{"335":5,"843":1,"851":2,"860":1,"913":4,"916":4,"918":5,"919":1,"1403":1,"1423":1,"1424":1,"1427":6,"1430":1,"1431":3,"2164":2,"2586":5,"2611":1,"2760":2,"2762":5}}],["bookstore",{"2":{"1435":2,"1437":1,"1442":3}}],["books>",{"2":{"1427":2}}],["booksinfo",{"2":{"917":2,"920":2}}],["books",{"2":{"214":2,"335":4,"913":7,"914":3,"916":15,"917":4,"918":18,"919":3,"920":6,"1375":3,"1423":1,"1426":3,"1427":4,"1430":2,"1431":5,"1435":1,"1436":2,"1437":2,"1442":5,"1743":3,"2502":2,"2586":5,"2590":1,"2611":1,"2760":1,"2762":10}}],["boundaries",{"2":{"865":1,"940":1,"1159":1,"2002":1,"2371":1,"2840":1}}],["boundary",{"0":{"943":1},"2":{"390":1,"863":3,"864":1,"865":1,"872":1,"921":1,"2157":1,"2297":1,"2483":1,"2543":1}}],["bounds",{"2":{"841":1,"1208":1,"1429":2,"1459":1,"2375":1,"2502":1,"2540":1,"2845":1}}],["bounded",{"2":{"841":1,"861":1,"863":1,"1067":1,"2464":2}}],["bound",{"2":{"165":1,"170":1,"238":1,"377":2,"388":2,"439":1,"447":2,"448":1,"1070":2,"1102":1,"1167":1,"1792":2,"1852":2,"1922":2,"2087":1,"2322":1,"2323":1,"2333":2,"2383":2,"2395":2,"2496":1,"2540":1,"2546":1,"2572":1,"2845":1,"2848":2,"2849":1}}],["bottlenecks",{"2":{"2088":1}}],["bottleneck",{"2":{"1014":1,"2398":1}}],["bottom",{"0":{"1009":1},"2":{"3":1,"872":1,"1069":1,"1271":1,"1442":1,"1956":1,"2379":1}}],["bother",{"2":{"1401":1}}],["both",{"0":{"904":1,"2424":1},"2":{"41":1,"101":1,"106":1,"155":1,"168":1,"203":1,"213":1,"220":1,"290":1,"310":1,"324":1,"347":1,"353":1,"362":1,"369":1,"370":1,"388":1,"420":1,"423":2,"444":1,"587":1,"639":1,"650":1,"663":1,"666":1,"667":1,"691":1,"696":1,"701":1,"812":1,"818":1,"832":1,"836":3,"837":1,"838":1,"840":1,"841":1,"855":1,"864":3,"865":1,"868":1,"871":2,"873":1,"876":1,"884":2,"902":2,"903":1,"904":2,"920":1,"957":1,"959":1,"970":1,"1000":1,"1023":1,"1026":1,"1029":1,"1032":1,"1054":3,"1067":3,"1071":1,"1079":1,"1082":2,"1086":1,"1087":1,"1088":1,"1095":1,"1125":1,"1127":1,"1135":1,"1167":1,"1177":1,"1179":1,"1193":1,"1220":2,"1271":1,"1326":1,"1327":1,"1352":1,"1353":2,"1355":1,"1358":1,"1359":1,"1376":1,"1382":1,"1385":1,"1386":1,"1396":2,"1407":1,"1408":1,"1416":1,"1421":1,"1422":1,"1423":2,"1433":1,"1460":2,"1473":1,"1515":1,"1525":1,"1567":1,"1608":1,"1661":1,"1708":1,"1740":1,"1753":1,"1754":2,"1792":6,"1870":1,"1911":1,"2009":1,"2040":1,"2056":1,"2080":1,"2097":2,"2106":1,"2153":1,"2154":1,"2155":1,"2157":2,"2166":1,"2184":1,"2188":1,"2190":1,"2193":3,"2200":1,"2222":1,"2272":1,"2288":1,"2313":1,"2330":1,"2332":1,"2346":2,"2360":1,"2372":1,"2375":2,"2381":1,"2391":2,"2407":1,"2409":1,"2413":1,"2414":1,"2415":1,"2417":1,"2419":1,"2422":1,"2424":1,"2426":1,"2428":1,"2431":1,"2432":1,"2435":1,"2438":1,"2452":2,"2466":1,"2472":1,"2476":1,"2481":1,"2482":1,"2492":1,"2505":2,"2525":1,"2537":1,"2541":1,"2542":2,"2543":3,"2546":2,"2581":2,"2591":1,"2649":1,"2681":1,"2684":1,"2687":1,"2765":1,"2766":1,"2834":1,"2858":1,"2868":1}}],["bumped",{"2":{"2450":1}}],["bump",{"2":{"2448":1,"2461":1}}],["budge",{"2":{"1435":1}}],["buddy",{"2":{"1401":2,"1402":1,"1404":2}}],["buys",{"2":{"1180":1,"2412":1}}],["buy",{"2":{"1043":1}}],["bunch",{"2":{"1073":1,"1386":1,"1394":1,"1401":1}}],["bundler",{"2":{"1406":1}}],["bundle",{"2":{"1047":1,"1107":1,"1417":1,"1420":2,"2040":1,"2224":1,"2474":2}}],["bundles",{"0":{"1414":1},"2":{"101":1}}],["bun",{"0":{"1343":1,"2550":1,"2791":1},"2":{"970":3,"997":4,"1007":1,"1044":1,"1047":3,"1255":2,"1257":1,"1264":1,"1265":1,"1266":1,"1267":1,"1270":2,"1277":1,"1279":1,"1280":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1334":1,"1335":2,"1343":6,"1380":3,"1396":1,"1418":2,"1420":4,"1433":5,"2106":1,"2111":1,"2161":2,"2162":2,"2167":2,"2168":5,"2238":1,"2385":1,"2532":1,"2537":1,"2550":8,"2791":13,"2871":1,"2872":1,"2873":1,"2878":1}}],["busting",{"0":{"1139":1},"2":{"1139":1,"1769":1,"1792":2,"2060":1,"2634":1,"2635":1}}],["busy",{"2":{"919":1,"1164":1,"1165":1,"2803":1}}],["business",{"2":{"333":2,"414":1,"836":2,"841":4,"843":1,"844":2,"847":1,"849":3,"854":1,"860":1,"861":1,"863":3,"873":1,"902":1,"974":1,"1035":1,"1041":1,"1096":1,"1106":1,"1184":1,"1205":1,"1211":1,"1244":1,"1247":1,"1382":3,"1403":4,"1405":2,"1824":1,"1868":1,"2300":1,"2481":1}}],["burned",{"2":{"1075":1}}],["buried",{"2":{"1015":1}}],["burden",{"2":{"871":1,"1281":1}}],["bursts",{"2":{"1160":1,"1164":1,"1171":1,"1180":1,"2498":1}}],["bursting",{"2":{"1160":1}}],["burst",{"0":{"1171":1},"2":{"214":1,"1159":1,"1160":1,"1165":1,"1171":1,"1430":1,"1743":1,"2088":1,"2224":1,"2459":1,"2462":1,"2464":1,"2465":1,"2502":1}}],["bulk",{"2":{"857":1,"907":1,"2270":2}}],["bucketwindow",{"2":{"2257":1}}],["buckets",{"2":{"1066":1,"1101":1,"1121":1}}],["bucket",{"0":{"477":1,"1160":1,"1953":1},"2":{"477":3,"479":1,"480":2,"847":1,"1069":6,"1101":2,"1160":2,"1162":5,"1792":6,"1950":1,"1953":5,"1955":3,"1957":3,"1960":2,"2218":1,"2257":2,"2379":6,"2442":1,"2634":1}}],["buffers",{"2":{"2397":1}}],["buffersize",{"2":{"1356":1,"1792":1,"2123":1,"2125":1,"2132":1}}],["bufferedimage",{"2":{"1366":1}}],["buffered",{"2":{"87":1,"88":1}}],["buffering",{"0":{"83":1},"2":{"81":1,"87":2,"142":1,"231":1,"949":1,"953":2,"1859":3,"2835":2}}],["buffer",{"0":{"78":1,"84":1,"85":1,"86":1,"2404":1},"1":{"79":1,"80":1,"81":1,"82":1,"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1},"2":{"78":2,"79":4,"81":3,"83":1,"84":1,"85":1,"86":1,"89":1,"142":1,"159":3,"231":1,"747":5,"753":4,"757":4,"768":1,"776":2,"782":6,"784":6,"786":3,"968":1,"969":1,"971":1,"1007":1,"1255":1,"1258":1,"1792":3,"1991":2,"2125":3,"2329":1,"2364":1,"2402":1,"2404":1,"2591":1,"2835":1}}],["button>",{"2":{"938":3,"1061":4}}],["button",{"2":{"938":3,"948":1,"1061":4,"1384":1}}],["but",{"0":{"1077":1,"2723":1},"2":{"64":1,"109":1,"119":1,"165":1,"168":1,"174":1,"180":1,"206":1,"261":1,"263":1,"303":1,"308":1,"320":2,"354":1,"389":1,"390":1,"419":1,"454":1,"524":2,"587":1,"618":2,"621":1,"622":1,"639":1,"663":2,"710":1,"831":1,"834":1,"835":1,"841":10,"844":4,"845":3,"848":4,"849":1,"854":1,"855":1,"856":1,"857":2,"859":2,"860":3,"863":1,"866":1,"868":1,"869":2,"872":3,"873":4,"875":1,"876":5,"877":1,"884":1,"907":1,"908":1,"909":1,"912":1,"913":3,"916":2,"917":1,"918":2,"919":4,"920":2,"927":1,"934":1,"946":1,"953":1,"974":1,"987":1,"1012":1,"1036":1,"1042":2,"1045":1,"1048":1,"1049":2,"1054":1,"1063":1,"1070":1,"1073":1,"1075":1,"1077":2,"1088":1,"1096":1,"1097":1,"1100":3,"1101":2,"1102":4,"1106":2,"1107":1,"1111":1,"1118":1,"1133":1,"1149":1,"1160":2,"1165":1,"1169":1,"1171":2,"1178":1,"1180":1,"1185":1,"1199":1,"1205":1,"1236":1,"1254":1,"1271":1,"1281":1,"1329":1,"1338":1,"1378":1,"1382":1,"1385":5,"1386":6,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":6,"1395":4,"1396":2,"1397":1,"1398":2,"1399":3,"1400":3,"1401":7,"1402":6,"1403":3,"1404":2,"1405":3,"1411":1,"1413":1,"1420":1,"1430":1,"1431":2,"1432":1,"1435":2,"1511":2,"1517":1,"1527":2,"1528":1,"1533":1,"1572":1,"1576":1,"1645":1,"1664":1,"1708":1,"1747":1,"1792":15,"1802":1,"1823":1,"1850":1,"1861":1,"1930":2,"1938":1,"1941":1,"2177":1,"2187":1,"2193":1,"2245":1,"2255":1,"2265":1,"2282":1,"2291":1,"2305":1,"2332":1,"2344":3,"2347":1,"2380":1,"2383":1,"2384":1,"2393":1,"2394":1,"2395":4,"2405":1,"2415":1,"2419":1,"2420":2,"2424":1,"2425":1,"2428":1,"2430":1,"2431":1,"2438":5,"2442":1,"2446":1,"2451":1,"2459":1,"2461":1,"2466":1,"2472":1,"2481":1,"2490":1,"2491":1,"2493":1,"2494":1,"2495":1,"2506":1,"2510":1,"2527":1,"2529":1,"2532":1,"2588":1,"2590":1,"2597":1,"2608":1,"2729":1,"2779":1,"2789":1,"2798":1,"2811":1,"2815":1,"2823":1,"2834":1,"2835":2,"2852":1,"2862":1,"2865":1,"2876":1,"2881":2}}],["bugfix",{"0":{"2626":1}}],["buggy",{"2":{"2451":1}}],["bugs",{"2":{"843":5,"845":1,"857":1,"865":1,"872":2,"873":1,"1006":1,"1036":1,"1065":1,"2360":1,"2395":1,"2450":1,"2627":1}}],["bug",{"0":{"2361":1,"2558":1,"2597":1,"2600":1,"2603":1,"2608":1},"1":{"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1},"2":{"3":1,"852":1,"864":1,"865":1,"872":2,"873":1,"973":1,"974":1,"1281":1,"1409":1,"1856":1,"2409":1,"2411":1,"2448":1,"2452":2,"2453":1,"2455":1,"2490":1,"2500":1,"2603":1,"2648":1}}],["buildratelimiter",{"2":{"2443":1,"2472":1}}],["buildlabel",{"2":{"2040":1,"2476":1}}],["building",{"0":{"921":1,"955":1,"1018":1,"1306":1,"1334":1,"2792":1},"1":{"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"956":1,"957":1,"958":1,"1019":1,"1020":1,"1021":1,"1022":1,"1307":1,"1308":1,"1309":1,"1310":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1},"2":{"834":1,"837":1,"851":1,"852":1,"869":1,"912":1,"1068":1,"1079":1,"1123":1,"1185":1,"1193":1,"1303":1,"1399":1,"2614":5,"2712":1,"2792":1,"2826":1}}],["builds",{"2":{"388":1,"436":1,"851":1,"852":2,"867":1,"872":1,"877":1,"949":1,"1010":1,"1178":1,"1183":1,"1328":1,"1376":1,"1382":1,"1420":1,"1423":1,"2160":1,"2242":1,"2527":1,"2776":1,"2779":2,"2862":1,"2875":1}}],["builders",{"2":{"2868":1}}],["builder",{"2":{"78":1,"87":1,"679":1,"723":1,"834":1,"872":1,"961":1,"1413":1,"1572":1,"1581":1,"1792":3,"2267":2,"2419":1,"2422":2,"2472":1}}],["build",{"0":{"869":1,"985":1,"1302":1,"2576":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1},"2":{"1":1,"207":1,"209":2,"264":1,"361":1,"374":1,"388":1,"395":1,"415":2,"423":1,"426":1,"427":2,"428":1,"439":2,"449":2,"452":3,"503":1,"510":1,"812":1,"813":1,"814":1,"815":1,"834":1,"841":1,"851":1,"866":1,"867":1,"871":3,"872":1,"885":1,"916":1,"920":1,"921":1,"948":1,"958":1,"968":1,"972":1,"973":1,"975":1,"976":1,"978":1,"984":1,"985":3,"988":1,"994":1,"996":3,"997":5,"1001":1,"1005":2,"1021":1,"1026":1,"1029":1,"1037":4,"1047":1,"1049":1,"1065":1,"1071":1,"1076":1,"1081":1,"1094":1,"1105":4,"1108":1,"1161":1,"1179":2,"1181":1,"1200":1,"1203":1,"1214":1,"1215":1,"1216":1,"1232":2,"1234":1,"1236":1,"1239":1,"1302":1,"1309":1,"1321":1,"1326":1,"1332":1,"1338":3,"1339":6,"1347":2,"1372":1,"1376":1,"1382":1,"1402":1,"1404":1,"1406":1,"1407":2,"1409":2,"1413":1,"1417":1,"1418":1,"1419":1,"1420":2,"1421":2,"1422":4,"1432":1,"1436":2,"1442":1,"1727":2,"1736":1,"1745":1,"1792":3,"1921":2,"1924":1,"1926":1,"1974":2,"2038":1,"2040":5,"2107":1,"2147":2,"2160":1,"2164":1,"2165":1,"2168":1,"2223":1,"2224":1,"2237":1,"2242":1,"2264":1,"2303":1,"2304":1,"2346":1,"2372":1,"2385":1,"2386":1,"2438":2,"2456":1,"2474":1,"2476":3,"2481":2,"2493":1,"2537":1,"2549":4,"2575":1,"2576":4,"2586":1,"2588":1,"2607":2,"2764":1,"2766":1,"2767":1,"2790":1,"2792":4,"2810":2,"2813":3,"2815":3,"2829":2,"2830":1,"2836":1,"2858":1,"2879":1}}],["built",{"0":{"158":1,"309":1,"363":1,"436":1,"966":1,"986":1,"1049":1,"1055":1,"1250":1},"1":{"159":1,"160":1,"161":1,"162":1,"163":1,"967":1,"1056":1},"2":{"0":1,"2":1,"300":1,"307":2,"308":1,"309":2,"310":1,"312":3,"316":1,"362":1,"364":1,"414":1,"582":1,"585":1,"694":1,"832":1,"834":1,"835":1,"848":1,"859":1,"861":1,"866":1,"868":1,"869":8,"873":3,"876":1,"880":6,"918":1,"920":1,"946":1,"966":1,"969":1,"979":1,"1009":1,"1027":1,"1037":3,"1038":1,"1043":1,"1048":3,"1049":4,"1052":1,"1055":1,"1056":1,"1064":1,"1075":1,"1078":1,"1086":1,"1094":3,"1097":1,"1098":3,"1100":4,"1101":2,"1102":1,"1105":1,"1106":1,"1108":3,"1126":1,"1127":2,"1135":1,"1140":1,"1185":1,"1188":1,"1209":1,"1276":1,"1322":1,"1323":1,"1324":1,"1350":1,"1382":2,"1385":1,"1396":1,"1404":2,"1423":2,"1472":2,"1792":4,"1822":1,"1825":1,"2167":1,"2177":3,"2188":1,"2190":1,"2257":1,"2289":1,"2438":4,"2462":1,"2474":1,"2479":1,"2481":1,"2482":1,"2532":1,"2537":2,"2544":1,"2575":1,"2739":1,"2775":1,"2776":2,"2792":1,"2794":1,"2811":1,"2860":1,"2879":1}}],["bag",{"2":{"2482":1}}],["band",{"2":{"2397":1}}],["bandwidth",{"2":{"1944":1}}],["banking",{"2":{"1228":1,"1792":2,"1878":1}}],["banks",{"2":{"852":1}}],["bail",{"2":{"1427":1}}],["balancing",{"0":{"1175":1},"2":{"1172":1,"1175":1,"1177":1,"1180":2,"1182":1,"1618":1,"1626":1,"1628":1,"1792":1,"2266":2}}],["balances",{"2":{"1168":1}}],["balancers",{"2":{"1329":1,"1712":1,"1792":1,"2634":1}}],["balancer",{"0":{"1715":1},"2":{"844":1,"864":1,"1067":1,"1337":1,"1706":1,"1767":1,"1774":1,"1792":1,"2633":1}}],["balance",{"2":{"320":1,"622":8,"860":2,"1175":2,"1176":1,"1177":1,"1938":1}}],["balanced",{"2":{"88":1,"859":2,"1177":1,"1280":1}}],["badrequest",{"2":{"1366":4}}],["badly",{"2":{"874":1}}],["bad",{"0":{"2384":1,"2491":1},"2":{"843":1,"918":1,"1071":1,"1234":1,"1236":1,"1527":1,"1674":2,"1676":1,"1677":1,"1742":1,"1792":3,"2223":1,"2255":4,"2290":1,"2380":1,"2491":1}}],["badges",{"2":{"0":1}}],["badge",{"2":{"0":2}}],["bar",{"2":{"650":1,"834":2}}],["barely",{"2":{"1076":1}}],["bare",{"0":{"2494":1},"2":{"110":1,"214":2,"320":1,"323":1,"327":1,"436":1,"515":1,"585":1,"613":1,"615":1,"1042":1,"1415":1,"1428":1,"1429":1,"1431":1,"1743":2,"1759":1,"1834":1,"2040":1,"2223":2,"2337":1,"2339":1,"2435":1,"2455":1,"2476":1,"2481":1,"2482":2,"2489":2,"2490":1,"2491":1,"2502":2,"2519":1,"2520":1}}],["battery",{"2":{"1078":1}}],["battle",{"2":{"2":1,"876":1,"1402":1}}],["batched",{"2":{"2228":1}}],["batch",{"2":{"324":1,"1086":1,"1095":2,"1125":1,"1792":1,"1850":1,"1852":1,"2383":2}}],["backing",{"2":{"1655":1}}],["backup",{"0":{"1365":1},"2":{"1213":1,"1215":3,"1237":1,"1353":2,"1354":1,"1365":1,"1792":1,"1887":1}}],["backups",{"2":{"993":1,"994":1,"1354":2}}],["backoff",{"2":{"1101":2,"1104":1,"1155":1,"1181":1,"2459":1}}],["backslashes",{"2":{"2589":1}}],["backslash",{"2":{"340":1,"599":1,"2040":1,"2476":1,"2531":1,"2603":1}}],["backwards",{"2":{"921":1}}],["backward",{"2":{"330":1,"917":1,"1792":3,"1958":1,"1974":1,"2002":1,"2371":1,"2423":1,"2461":1,"2470":1,"2477":1,"2481":1,"2496":1,"2587":1,"2607":1}}],["background",{"2":{"324":1,"660":3,"844":1,"852":1,"855":1,"864":1,"1035":1,"1036":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["back",{"2":{"186":1,"188":1,"296":1,"313":1,"315":1,"380":1,"436":1,"675":1,"777":2,"833":1,"847":1,"848":2,"849":2,"851":1,"852":2,"854":2,"856":1,"857":1,"859":2,"860":2,"881":1,"901":1,"903":1,"904":2,"974":1,"992":1,"995":1,"1039":1,"1041":1,"1073":1,"1074":1,"1078":2,"1080":1,"1082":1,"1094":1,"1162":1,"1255":1,"1398":2,"1400":1,"1423":1,"1426":1,"1439":1,"1442":1,"1447":1,"1451":1,"1454":2,"1464":1,"1605":1,"1628":2,"1744":1,"1792":10,"1818":1,"1819":1,"1856":1,"1941":1,"2224":1,"2266":3,"2293":1,"2296":1,"2329":1,"2333":1,"2346":1,"2348":1,"2377":2,"2389":1,"2405":3,"2422":1,"2451":1,"2453":1,"2455":2,"2497":1,"2527":1,"2531":1,"2533":1,"2537":1,"2664":1,"2688":1,"2739":1,"2803":1,"2807":2,"2809":2,"2833":1,"2862":1,"2869":2,"2878":1}}],["backends",{"2":{"123":1,"230":1,"1099":1,"1144":1,"1519":2,"1522":1,"1792":1,"2380":2,"2465":1,"2494":1,"2495":1}}],["backend",{"0":{"866":1,"1011":1,"1026":1,"1381":1,"1522":1,"2462":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"1382":1,"1383":1},"2":{"98":1,"100":1,"101":1,"104":1,"105":1,"108":3,"111":1,"122":1,"124":1,"832":1,"866":1,"867":3,"868":1,"869":1,"872":2,"876":2,"911":1,"958":1,"1021":1,"1027":1,"1037":2,"1067":4,"1070":2,"1083":1,"1084":2,"1150":4,"1177":1,"1181":1,"1303":1,"1309":2,"1320":1,"1321":1,"1322":1,"1366":3,"1381":1,"1382":3,"1383":1,"1410":1,"1414":2,"1421":1,"1427":1,"1511":2,"1515":2,"1522":2,"1792":5,"1851":2,"2274":1,"2380":2,"2382":2,"2445":2,"2465":1,"2495":2,"2745":1,"2806":1,"2812":1,"2874":1}}],["backed",{"2":{"41":1,"844":3,"1067":1,"1328":1,"1792":1,"2274":1,"2399":1,"2465":1,"2504":1,"2815":1,"2816":1}}],["basis",{"2":{"177":1,"847":1,"917":2}}],["basics",{"2":{"879":1,"1082":1,"1792":1,"1801":1,"2164":1,"2165":1,"2167":1}}],["basically",{"2":{"845":1,"851":1,"1385":2,"1388":1,"1398":1,"1405":1,"1440":1}}],["basicauthhandler",{"2":{"2559":1,"2615":1}}],["basicauth",{"2":{"52":1,"1196":1,"1197":1,"1199":1,"1469":1,"1482":1,"1498":1,"1502":1,"1503":1,"1505":1,"1792":2,"1903":1,"2254":1,"2551":1}}],["basic",{"0":{"29":1,"37":1,"44":1,"55":1,"60":1,"61":1,"104":1,"206":1,"225":1,"247":1,"288":1,"332":1,"405":1,"415":1,"417":1,"437":1,"441":1,"487":1,"658":1,"659":1,"750":1,"755":1,"764":1,"774":1,"797":1,"1183":1,"1194":1,"1373":1,"1482":1,"1497":1,"1778":1,"1903":1,"2027":1,"2192":1,"2303":1,"2363":1,"2785":1},"1":{"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"438":1,"439":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":2,"1196":2,"1197":2,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1498":1,"1499":1,"1500":1,"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1},"2":{"25":1,"29":2,"30":1,"37":2,"38":8,"39":1,"40":7,"43":3,"44":2,"45":1,"48":3,"49":2,"50":1,"53":1,"54":3,"55":2,"56":2,"58":3,"60":4,"61":8,"62":11,"63":3,"64":1,"65":1,"66":2,"67":2,"225":4,"368":2,"436":1,"835":1,"961":1,"1037":2,"1086":1,"1098":2,"1183":2,"1184":3,"1185":2,"1188":1,"1189":4,"1193":1,"1196":2,"1197":5,"1198":1,"1199":3,"1202":1,"1207":2,"1208":1,"1281":1,"1373":3,"1468":1,"1482":3,"1484":2,"1485":2,"1486":2,"1497":2,"1499":1,"1500":1,"1501":3,"1503":1,"1504":3,"1505":2,"1506":5,"1508":4,"1515":1,"1549":3,"1550":2,"1788":2,"1792":17,"1795":1,"1901":1,"1903":2,"1906":1,"2164":2,"2165":3,"2167":1,"2196":1,"2206":1,"2254":3,"2363":2,"2438":1,"2481":1,"2545":1,"2549":2,"2591":1,"2679":1,"2736":1,"2785":1,"2860":1}}],["baseuploadhandler",{"2":{"2664":1}}],["baseurl",{"2":{"429":1,"723":1,"894":1,"938":1,"961":1,"995":3,"1026":4,"1317":1,"1342":1,"1366":1,"1386":1,"1408":2,"1410":1,"1413":1,"1416":1,"1560":1,"1567":1,"1568":1,"1569":1,"1571":1,"1572":2,"1573":1,"1574":4,"1575":1,"1581":2,"1792":1,"2247":1,"2277":1,"2310":1,"2313":1,"2648":1,"2655":1}}],["basecurrency=eur",{"2":{"2764":1}}],["basecurrency=usd",{"2":{"1023":1}}],["basecurrency",{"2":{"1024":2,"1026":8}}],["baselines",{"2":{"2156":1,"2542":1}}],["baseline",{"0":{"1269":1,"1292":1},"1":{"1293":1},"2":{"868":1,"1254":1,"1255":3,"1258":2,"1264":1,"1266":1,"1272":1,"1275":1,"1285":1,"2398":2,"2804":1}}],["bases",{"2":{"845":1}}],["base",{"0":{"2385":1},"2":{"74":1,"75":1,"1019":2,"1020":1,"1021":5,"1032":1,"1193":4,"1376":6,"1416":1,"1420":3,"1574":1,"1753":1,"1792":1,"2346":1,"2369":1,"2372":1,"2518":1,"2522":1,"2764":2,"2766":2}}],["base64url",{"2":{"1218":1,"1220":1,"1221":1}}],["base64",{"2":{"58":1,"63":1,"308":6,"1198":1,"1214":3,"1232":5,"1234":3,"1482":1,"1497":1,"1792":3,"1882":2,"1884":1,"2177":3}}],["based",{"0":{"1048":1,"1057":1,"2059":1,"2164":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":2,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1},"2":{"41":2,"66":1,"120":1,"188":1,"210":1,"305":1,"356":1,"363":1,"480":1,"669":1,"760":1,"761":1,"770":1,"771":1,"772":1,"835":1,"841":1,"851":1,"857":2,"860":1,"866":1,"869":1,"882":1,"883":1,"948":1,"961":1,"995":1,"1037":3,"1048":2,"1049":1,"1053":1,"1063":1,"1064":2,"1094":1,"1096":1,"1098":2,"1101":2,"1107":1,"1111":1,"1115":1,"1127":1,"1133":1,"1162":1,"1272":1,"1274":1,"1280":1,"1305":1,"1308":1,"1351":1,"1364":1,"1366":1,"1410":1,"1445":1,"1446":1,"1450":1,"1453":1,"1464":1,"1581":1,"1670":1,"1732":1,"1792":8,"1904":1,"1907":1,"1955":1,"2020":1,"2109":1,"2129":1,"2131":1,"2164":2,"2165":3,"2187":1,"2188":1,"2223":1,"2254":1,"2255":1,"2264":1,"2265":1,"2284":1,"2296":1,"2376":1,"2379":1,"2389":1,"2419":1,"2438":2,"2481":1,"2490":1,"2530":1,"2537":1,"2576":1,"2580":1,"2597":1,"2600":2,"2632":1,"2679":1,"2736":1,"2790":1,"2804":1,"2837":1,"2839":1,"2868":1}}],["bashbashbun",{"2":{"2162":1}}],["bashbashgit",{"2":{"1380":1,"2162":1,"2792":1}}],["bashbashdocker",{"2":{"1343":1}}],["bashbashdotnet",{"2":{"1199":2,"2792":1}}],["bashbashnpgsqlrest",{"2":{"1195":1,"2208":1,"2682":1,"2694":3,"2695":2,"2697":1}}],["bashbashcd",{"2":{"1047":1,"1433":2,"2162":1}}],["bashbashexport",{"2":{"1044":1,"2697":1}}],["bashbash",{"2":{"38":1,"40":1,"57":1,"58":1,"61":1,"62":1,"970":1,"997":1,"1051":1,"1117":1,"1118":1,"1119":1,"1207":1,"1606":1,"2684":1,"2685":1,"2689":1,"2691":1,"2692":1,"2699":1,"2700":1,"2782":1,"2783":1,"2784":1,"2785":5,"2786":2,"2788":1,"2789":1,"2790":1,"2791":1,"2792":1,"2823":1,"2824":1}}],["beyond",{"0":{"987":1},"1":{"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1},"2":{"928":1,"1086":1,"1127":1,"1181":1,"1351":1,"1971":1,"2112":1,"2532":1,"2804":1,"2833":1}}],["besides",{"2":{"860":1,"1385":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1}}],["best",{"0":{"1429":1},"2":{"88":1,"713":1,"844":1,"845":1,"857":1,"859":1,"861":1,"917":1,"920":1,"948":1,"1078":1,"1084":1,"1128":1,"1266":1,"1382":1,"1386":1,"1398":1,"1402":1,"1404":1,"1412":1,"1423":1,"1515":1,"1792":3,"1938":1,"2094":1,"2112":1,"2164":1,"2173":1,"2174":1,"2297":1,"2532":1,"2533":2,"2871":1}}],["benefits",{"0":{"999":1,"1193":1},"1":{"1000":1,"1001":1,"1002":1,"1003":1,"1004":1},"2":{"2371":1,"2615":1}}],["benefit",{"2":{"974":1,"1130":1,"1180":2,"1974":1,"2270":1,"2607":1}}],["beneath",{"2":{"847":1,"1042":1}}],["benchmarkdotnet",{"2":{"2397":1}}],["benchmarked",{"2":{"1084":1}}],["benchmark",{"0":{"1254":1,"1256":1,"1282":1},"1":{"1255":1,"1256":1,"1257":2,"1258":2,"1259":2,"1260":2,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":2,"1284":2,"1285":2,"1286":2,"1287":2,"1288":2,"1289":2,"1290":2,"1291":2,"1292":2,"1293":2,"1294":2,"1295":2,"1296":2,"1297":2,"1298":2,"1299":2,"1300":2,"1301":2},"2":{"877":1,"1037":1,"1084":1,"1089":2,"1170":1,"1254":5,"1256":2,"1258":1,"1260":3,"1277":1,"1281":1,"2397":1,"2398":4,"2621":1,"2744":1}}],["benchmarks",{"0":{"1089":1},"1":{"1090":1,"1091":1,"1092":1},"2":{"874":1,"1007":1,"1037":1,"1254":2,"1259":1,"2621":1,"2744":1}}],["bench",{"2":{"851":1}}],["believe",{"2":{"844":1,"845":1,"1076":1,"1385":1}}],["belief",{"2":{"841":2,"849":1,"1075":3,"1079":1}}],["belongs",{"2":{"1101":1,"2444":1,"2533":1}}],["belong",{"2":{"832":1,"859":1,"926":1,"2389":1}}],["below",{"2":{"212":1,"220":1,"309":1,"436":2,"560":1,"581":1,"614":1,"615":1,"619":1,"650":1,"761":1,"771":1,"845":1,"868":1,"871":1,"919":1,"924":1,"986":1,"1066":1,"1076":1,"1135":1,"1157":1,"1174":1,"1212":1,"1227":3,"1403":1,"1475":1,"1477":1,"1511":2,"1521":1,"1540":1,"1544":1,"1558":1,"1566":1,"1588":1,"1589":1,"1604":3,"1618":2,"1623":1,"1670":2,"1684":1,"1690":1,"1792":8,"1807":1,"1824":1,"1825":1,"1877":3,"1922":1,"1937":1,"2034":1,"2038":1,"2094":2,"2107":1,"2109":1,"2117":1,"2220":1,"2339":1,"2340":1,"2384":1,"2450":1,"2481":1,"2504":1,"2533":1,"2535":1,"2537":2,"2538":1,"2541":1,"2682":1,"2829":1,"2836":1,"2852":1,"2867":1,"2871":1}}],["been",{"2":{"840":1,"851":1,"852":1,"857":1,"872":3,"877":1,"912":2,"1073":1,"1075":1,"1081":1,"1122":1,"1382":2,"1385":1,"1401":1,"1403":1,"1792":1,"1813":1,"2255":1,"2279":1,"2389":2,"2400":1,"2434":1,"2446":1,"2600":1,"2673":1}}],["behind",{"0":{"1711":1},"2":{"836":1,"844":1,"856":1,"864":1,"874":1,"947":1,"1037":2,"1067":1,"1073":1,"1074":1,"1121":1,"1208":1,"1324":1,"1383":1,"1435":1,"1437":1,"1701":1,"1704":1,"1713":1,"1717":1,"1792":2,"1859":1,"2103":1,"2633":3,"2835":1}}],["behaviour",{"2":{"873":1}}],["behavioral",{"2":{"865":1}}],["behavior",{"0":{"10":1,"25":1,"41":1,"51":1,"63":1,"73":1,"87":1,"108":1,"120":1,"139":1,"150":1,"188":1,"197":1,"216":1,"245":1,"284":1,"330":1,"337":1,"362":1,"384":1,"409":1,"461":1,"468":1,"480":1,"494":1,"504":1,"512":1,"518":1,"524":1,"529":1,"567":1,"575":1,"587":1,"595":1,"609":1,"615":1,"625":1,"667":1,"687":1,"737":1,"779":1,"801":1,"818":1,"829":1,"1267":1,"1741":1,"1774":1,"1847":1,"1858":1,"2149":1,"2157":1,"2158":1,"2197":1,"2284":1,"2289":1,"2296":1,"2413":1,"2416":1,"2423":1,"2444":1,"2511":1},"1":{"285":1,"286":1,"462":1,"463":1,"464":1,"616":1,"668":1,"669":1,"738":1,"739":1,"802":1,"803":1},"2":{"1":1,"26":1,"28":1,"33":1,"64":1,"81":1,"160":1,"165":1,"167":1,"214":1,"215":1,"231":1,"244":2,"293":1,"295":1,"315":1,"332":1,"470":1,"523":1,"556":1,"616":1,"617":1,"650":1,"659":1,"737":1,"756":1,"780":1,"801":1,"841":3,"845":3,"859":3,"860":1,"917":1,"932":1,"1044":1,"1069":1,"1102":1,"1129":1,"1134":1,"1155":1,"1174":1,"1181":1,"1199":1,"1228":1,"1229":1,"1230":1,"1324":1,"1326":1,"1386":1,"1401":2,"1577":1,"1609":1,"1612":1,"1617":1,"1622":1,"1664":1,"1688":1,"1738":1,"1792":8,"1840":1,"1851":1,"1856":2,"1878":1,"1879":1,"1880":1,"1957":2,"2000":1,"2004":1,"2007":1,"2009":1,"2023":1,"2024":1,"2107":1,"2115":1,"2157":1,"2189":1,"2209":1,"2219":1,"2258":1,"2267":1,"2313":1,"2314":3,"2322":1,"2328":1,"2330":2,"2339":1,"2354":1,"2356":1,"2363":1,"2378":1,"2379":1,"2381":1,"2382":1,"2393":1,"2395":1,"2416":1,"2431":1,"2443":1,"2447":1,"2450":1,"2454":1,"2455":2,"2482":1,"2486":1,"2502":1,"2510":1,"2528":1,"2539":2,"2540":1,"2543":2,"2551":1,"2554":1,"2558":2,"2586":3,"2587":2,"2588":2,"2589":2,"2595":1,"2596":1,"2771":1,"2822":1,"2826":1,"2835":1,"2840":1,"2841":1,"2845":1,"2864":1,"2876":1}}],["behaving",{"2":{"857":1}}],["behaves",{"2":{"991":1,"2422":1,"2461":1,"2510":1,"2531":1}}],["behave",{"2":{"683":1,"852":1,"864":1,"1792":1,"1840":1,"1958":1,"2325":1,"2470":1,"2477":1,"2487":1,"2533":1,"2537":1}}],["bearing",{"2":{"861":1,"868":1,"1792":1,"1856":2,"2224":2,"2420":1,"2421":1,"2431":1,"2438":1,"2451":3,"2453":1,"2454":1}}],["bearerformat",{"2":{"1792":2,"1902":1,"1906":1,"1907":1,"1911":1,"2254":2,"2434":1}}],["bearerauth",{"2":{"1792":2,"1902":1,"1907":1,"1911":1,"2254":2,"2434":1}}],["bearertokendefaults",{"2":{"1451":1}}],["bearertokenexpire",{"2":{"1450":1,"1451":1,"1458":1,"1459":1,"1464":2,"1792":2,"2375":3,"2376":1,"2412":1}}],["bearertokenexpirehours",{"2":{"1053":1,"1062":1,"1464":1,"2174":1,"2376":1}}],["bearertoken",{"2":{"1068":1,"1098":1,"1451":1,"1458":2,"1459":1,"1460":2,"1792":2,"2227":1,"2375":5,"2412":1,"2438":1}}],["bearertokenrefreshpath",{"2":{"1053":1,"1062":1,"1450":1,"1451":1,"1458":1,"1459":1,"1460":1,"1792":3,"2174":1,"2375":4,"2412":1}}],["bearertokenauthscheme",{"2":{"1053":1,"1062":1,"1450":1,"1451":1,"1460":1,"1792":2,"2174":1,"2175":1,"2375":1}}],["bearertokenauth",{"2":{"1053":1,"1062":1,"1445":1,"1450":1,"1451":1,"1464":1,"1792":1,"2174":1}}],["bearer",{"0":{"1450":1,"1451":1,"1457":1,"1902":1,"2174":1},"1":{"1451":1,"1452":1},"2":{"207":1,"208":1,"209":1,"211":1,"212":2,"215":1,"286":1,"290":2,"297":1,"302":1,"303":1,"312":1,"315":1,"390":1,"394":1,"531":1,"533":1,"835":1,"934":1,"1017":2,"1030":1,"1033":1,"1037":1,"1048":1,"1053":1,"1054":4,"1061":1,"1063":2,"1064":1,"1086":1,"1098":5,"1105":1,"1126":1,"1216":1,"1221":1,"1444":1,"1445":1,"1450":1,"1451":2,"1452":1,"1453":1,"1454":1,"1455":1,"1457":1,"1458":1,"1470":1,"1485":1,"1507":1,"1550":1,"1699":1,"1700":1,"1726":1,"1730":1,"1731":1,"1733":2,"1738":2,"1742":1,"1788":1,"1792":15,"1825":3,"1827":2,"1830":1,"1833":1,"1894":1,"1901":1,"1902":2,"1906":2,"1907":2,"1911":1,"2164":1,"2165":1,"2170":1,"2171":2,"2174":1,"2188":1,"2189":1,"2254":6,"2264":3,"2283":2,"2286":1,"2290":1,"2375":1,"2377":1,"2421":2,"2422":1,"2423":1,"2429":1,"2434":1,"2435":1,"2438":1,"2481":2,"2483":1,"2554":6,"2736":2,"2768":2}}],["beautifulsoup",{"2":{"1423":1}}],["beautiful",{"2":{"843":1,"913":1,"1435":1}}],["beautifully",{"2":{"838":1}}],["beats",{"2":{"1080":1}}],["beat",{"2":{"701":1}}],["being",{"2":{"165":1,"167":1,"171":1,"182":2,"186":1,"384":1,"527":1,"772":1,"841":1,"852":2,"855":1,"857":1,"860":1,"864":1,"868":1,"874":1,"988":1,"1080":1,"1100":1,"1398":1,"1404":1,"1435":1,"1568":1,"1664":2,"1792":3,"2278":1,"2291":2,"2293":1,"2322":1,"2342":1,"2348":1,"2352":1,"2367":1,"2384":1,"2395":1,"2405":1,"2438":1,"2482":1,"2517":1,"2558":1,"2622":1,"2641":1,"2645":1,"2751":1,"2882":1}}],["bet",{"2":{"1404":1}}],["better",{"0":{"2249":1,"2741":1},"2":{"88":1,"369":1,"841":1,"851":1,"873":1,"875":1,"876":1,"912":1,"920":1,"975":1,"1035":1,"1081":1,"1133":1,"1258":1,"1266":1,"1271":1,"1323":1,"1382":1,"1385":1,"1399":1,"1511":1,"1792":2,"1940":1,"2193":1,"2247":1,"2258":3,"2259":1,"2261":1,"2265":1,"2279":1,"2321":1,"2332":3,"2531":1,"2581":1,"2615":1,"2789":1}}],["between",{"2":{"88":1,"268":1,"372":1,"575":1,"840":1,"841":3,"843":1,"845":2,"847":1,"848":2,"851":2,"852":2,"854":2,"857":1,"860":1,"864":1,"865":2,"871":2,"913":1,"915":1,"973":1,"974":1,"978":1,"1070":1,"1073":2,"1075":1,"1079":1,"1090":1,"1111":1,"1152":1,"1169":1,"1179":1,"1180":1,"1183":1,"1254":1,"1259":2,"1263":1,"1272":1,"1385":1,"1387":1,"1394":2,"1396":1,"1399":1,"1401":2,"1405":1,"1590":1,"1623":1,"1792":1,"1938":1,"2172":1,"2179":1,"2265":2,"2319":1,"2531":1,"2731":1,"2845":1,"2846":1}}],["becoming",{"2":{"1384":1}}],["becomes",{"0":{"252":1,"941":1},"2":{"34":3,"286":1,"297":1,"298":2,"304":1,"317":1,"320":1,"388":1,"436":1,"529":1,"775":1,"841":1,"848":1,"856":1,"878":1,"900":1,"926":1,"948":1,"984":1,"998":1,"1020":1,"1037":1,"1040":2,"1042":1,"1080":1,"1086":1,"1164":1,"1305":1,"1309":1,"1382":1,"1405":2,"1435":1,"1459":1,"1559":1,"1571":1,"1792":7,"1842":1,"1862":1,"1968":1,"2004":2,"2040":1,"2098":1,"2143":1,"2157":1,"2180":1,"2185":1,"2330":1,"2375":1,"2389":1,"2466":1,"2481":2,"2482":1,"2484":1,"2539":1,"2540":1,"2543":1,"2588":1,"2721":1,"2723":1,"2760":1,"2764":1,"2828":1,"2840":1,"2850":1,"2873":1}}],["become",{"0":{"304":1},"1":{"305":1,"306":1},"2":{"34":2,"41":2,"113":1,"244":2,"934":2,"956":1,"1040":1,"1044":1,"1081":1,"1125":1,"1126":2,"1165":1,"1239":1,"1304":1,"1370":1,"1378":1,"1405":1,"1408":2,"1480":1,"1567":1,"1688":1,"1792":3,"1813":1,"1888":1,"2004":3,"2102":1,"2171":1,"2389":1,"2470":1,"2802":1,"2828":1,"2832":1,"2841":1,"2848":1,"2880":1}}],["became",{"2":{"1080":1,"1404":1}}],["because",{"2":{"1":1,"39":1,"305":1,"320":1,"377":1,"453":1,"584":1,"669":1,"836":1,"841":2,"843":1,"845":4,"848":1,"851":3,"852":5,"854":1,"857":4,"860":2,"861":1,"863":1,"865":1,"869":1,"871":3,"873":1,"874":1,"915":1,"971":1,"973":1,"992":1,"993":1,"997":1,"1007":1,"1042":1,"1045":1,"1067":1,"1078":2,"1079":2,"1080":1,"1096":1,"1128":1,"1139":1,"1153":1,"1176":1,"1324":1,"1363":1,"1376":1,"1382":2,"1384":1,"1385":2,"1386":2,"1390":1,"1392":1,"1395":1,"1396":1,"1397":1,"1398":1,"1419":1,"1426":1,"1792":2,"2095":1,"2106":1,"2154":1,"2156":2,"2187":1,"2267":1,"2333":1,"2337":1,"2362":1,"2395":1,"2398":2,"2410":1,"2445":1,"2450":1,"2454":1,"2461":1,"2466":1,"2481":1,"2537":1,"2569":1,"2645":1,"2648":1,"2739":1,"2795":1,"2809":1,"2868":1,"2869":1,"2875":1}}],["beforeroutinecommand",{"2":{"1852":1,"2383":1}}],["beforeroutinecommands",{"0":{"1852":1,"2383":1},"2":{"1070":3,"1102":2,"1792":1,"1850":2,"1852":1,"2383":1}}],["before",{"0":{"23":1,"562":1,"1332":1,"2367":1},"2":{"74":1,"78":1,"81":2,"84":1,"87":1,"88":1,"182":2,"184":2,"186":2,"202":1,"203":1,"211":2,"213":1,"216":1,"226":1,"237":2,"239":1,"280":1,"308":1,"320":1,"354":1,"362":1,"364":1,"384":1,"448":1,"529":3,"560":1,"562":1,"619":1,"687":1,"689":1,"694":1,"703":1,"704":1,"705":1,"709":1,"714":1,"717":1,"737":1,"747":1,"766":1,"768":1,"781":1,"786":1,"801":1,"807":2,"818":1,"834":1,"852":2,"854":1,"869":1,"879":1,"972":1,"981":1,"982":2,"1005":1,"1011":1,"1016":1,"1029":1,"1032":1,"1052":1,"1067":1,"1068":1,"1070":1,"1075":1,"1076":1,"1094":1,"1095":1,"1100":2,"1102":1,"1106":1,"1138":1,"1165":1,"1170":1,"1180":1,"1215":1,"1220":1,"1227":1,"1235":1,"1236":1,"1259":1,"1351":1,"1368":1,"1377":1,"1386":1,"1393":1,"1398":1,"1402":2,"1409":1,"1410":1,"1415":1,"1423":1,"1426":1,"1616":1,"1618":1,"1651":1,"1664":2,"1731":2,"1740":1,"1792":15,"1804":1,"1848":1,"1850":1,"1870":1,"1885":1,"1924":1,"1928":1,"1956":1,"1958":1,"2005":1,"2006":1,"2038":1,"2075":1,"2089":1,"2094":1,"2112":1,"2128":1,"2137":2,"2149":1,"2161":1,"2177":1,"2184":1,"2222":1,"2247":2,"2264":2,"2288":1,"2291":2,"2292":2,"2293":2,"2320":1,"2323":1,"2330":1,"2340":1,"2359":1,"2363":1,"2366":1,"2367":1,"2372":1,"2378":1,"2379":1,"2383":1,"2389":1,"2391":1,"2393":1,"2397":1,"2404":1,"2407":1,"2422":2,"2438":2,"2444":1,"2461":1,"2470":1,"2477":1,"2481":1,"2487":2,"2495":1,"2502":1,"2505":2,"2511":1,"2528":1,"2529":1,"2532":2,"2533":2,"2534":1,"2537":1,"2540":1,"2549":1,"2555":1,"2559":1,"2566":1,"2572":1,"2575":2,"2577":1,"2580":1,"2611":1,"2621":1,"2622":1,"2626":1,"2679":1,"2693":1,"2696":1,"2760":2,"2762":2,"2763":1,"2765":2,"2792":1,"2810":1,"2815":1,"2819":1,"2840":1,"2841":1,"2845":1,"2856":1,"2864":1,"2865":1,"2867":1,"2870":2,"2871":1}}],["be",{"0":{"395":1,"1430":1},"2":{"14":1,"40":2,"48":1,"60":1,"63":2,"75":1,"77":1,"119":1,"121":1,"157":1,"167":1,"173":1,"174":1,"182":1,"188":1,"206":2,"209":1,"212":1,"243":1,"244":1,"258":1,"309":1,"320":1,"336":1,"358":1,"362":1,"363":1,"366":1,"370":1,"373":1,"376":1,"377":1,"378":1,"382":1,"387":1,"390":2,"394":1,"409":2,"430":1,"458":1,"470":1,"480":1,"512":1,"529":1,"534":1,"556":1,"560":1,"587":1,"618":1,"619":1,"646":1,"653":1,"654":1,"655":1,"658":1,"660":1,"661":1,"662":2,"663":1,"673":2,"696":1,"704":1,"708":1,"709":1,"714":1,"718":1,"724":1,"737":1,"745":1,"761":1,"768":1,"771":1,"778":1,"784":1,"786":1,"801":1,"809":1,"816":3,"817":2,"819":1,"841":5,"844":1,"845":2,"848":6,"849":3,"851":5,"852":5,"854":2,"857":1,"859":4,"860":1,"861":1,"863":3,"864":2,"865":1,"867":1,"868":1,"869":2,"872":1,"873":1,"876":2,"877":1,"891":1,"903":1,"907":1,"911":1,"912":1,"913":1,"914":1,"915":3,"916":1,"918":4,"919":3,"920":2,"921":1,"926":2,"934":1,"946":1,"956":1,"974":3,"990":1,"992":2,"993":1,"994":2,"1013":3,"1023":1,"1026":1,"1033":1,"1038":1,"1042":1,"1049":1,"1052":2,"1055":1,"1064":1,"1073":3,"1075":1,"1077":2,"1078":1,"1098":2,"1101":1,"1106":1,"1107":1,"1113":1,"1125":1,"1129":2,"1132":1,"1133":2,"1134":1,"1138":1,"1140":1,"1185":1,"1187":1,"1204":1,"1237":1,"1254":1,"1272":1,"1318":1,"1335":1,"1340":1,"1360":1,"1382":1,"1385":4,"1386":2,"1388":1,"1390":3,"1391":1,"1393":3,"1394":3,"1395":2,"1396":2,"1398":3,"1399":3,"1400":3,"1401":4,"1402":5,"1403":5,"1404":3,"1405":5,"1406":1,"1409":2,"1412":2,"1419":2,"1443":1,"1445":1,"1448":1,"1449":1,"1453":1,"1454":1,"1457":1,"1458":1,"1459":1,"1460":4,"1464":1,"1490":1,"1504":1,"1511":5,"1517":2,"1547":1,"1559":1,"1569":1,"1571":1,"1588":1,"1607":1,"1609":1,"1620":2,"1628":2,"1644":1,"1651":1,"1659":1,"1661":1,"1664":1,"1689":1,"1697":1,"1703":1,"1706":1,"1708":1,"1717":1,"1727":2,"1731":1,"1738":1,"1759":1,"1767":1,"1792":129,"1818":1,"1822":1,"1832":1,"1833":1,"1837":1,"1840":1,"1843":1,"1849":1,"1851":1,"1852":1,"1855":1,"1856":1,"1858":1,"1867":3,"1887":1,"1909":1,"1912":1,"1915":1,"1922":1,"1925":1,"1948":1,"1951":1,"1952":1,"1953":1,"1954":1,"1955":1,"1958":1,"1961":1,"1983":1,"1994":1,"2000":1,"2008":1,"2016":4,"2018":2,"2019":1,"2020":1,"2021":1,"2025":1,"2047":1,"2049":1,"2056":1,"2072":1,"2079":1,"2094":1,"2103":1,"2105":1,"2110":1,"2128":1,"2138":1,"2139":1,"2140":3,"2142":3,"2144":3,"2145":1,"2146":6,"2148":2,"2149":1,"2185":1,"2192":1,"2195":1,"2207":1,"2220":1,"2245":1,"2250":1,"2253":1,"2254":2,"2256":5,"2257":2,"2258":2,"2264":1,"2265":4,"2266":2,"2267":1,"2277":1,"2282":2,"2284":1,"2291":2,"2296":1,"2297":2,"2308":1,"2330":1,"2333":1,"2334":1,"2340":1,"2375":6,"2376":1,"2378":1,"2379":1,"2382":1,"2383":1,"2384":1,"2391":1,"2395":1,"2428":1,"2433":1,"2454":1,"2456":1,"2483":1,"2484":2,"2486":1,"2500":1,"2520":1,"2528":1,"2531":2,"2532":2,"2533":1,"2537":1,"2539":1,"2540":2,"2549":1,"2554":3,"2565":1,"2575":8,"2581":1,"2596":1,"2611":1,"2615":1,"2632":4,"2633":2,"2650":1,"2653":1,"2659":1,"2662":1,"2666":1,"2670":1,"2677":1,"2678":1,"2680":1,"2681":1,"2687":2,"2695":1,"2719":1,"2736":1,"2750":1,"2761":1,"2765":1,"2769":1,"2779":1,"2792":2,"2798":1,"2803":1,"2814":1,"2821":1,"2822":1,"2823":1,"2845":1,"2848":1,"2864":1,"2868":1,"2870":1,"2881":2}}],["begintransactionasync",{"2":{"2615":1}}],["beginning",{"2":{"848":1,"912":1,"1073":1,"2192":1}}],["begin",{"0":{"2867":1},"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"37":2,"38":2,"39":2,"40":1,"48":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"104":1,"115":1,"116":1,"117":1,"119":1,"128":1,"136":1,"137":1,"157":1,"184":1,"186":1,"206":1,"207":1,"208":1,"209":1,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"264":1,"288":1,"289":1,"290":1,"291":1,"292":1,"310":2,"313":1,"332":1,"333":1,"334":1,"335":1,"360":1,"361":1,"365":1,"366":1,"374":1,"401":1,"405":1,"406":1,"408":2,"415":1,"423":1,"426":1,"427":1,"428":1,"436":1,"438":1,"439":1,"449":1,"451":2,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"584":2,"592":1,"593":1,"594":1,"611":1,"621":1,"622":2,"624":1,"641":1,"646":1,"658":1,"659":1,"664":2,"665":1,"677":1,"679":1,"715":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"750":1,"751":1,"752":1,"755":1,"756":1,"764":2,"765":1,"766":1,"774":2,"777":1,"797":1,"798":1,"799":1,"811":1,"812":1,"813":1,"814":1,"815":1,"826":1,"835":1,"851":1,"864":1,"865":1,"879":1,"883":1,"884":1,"886":1,"888":1,"904":2,"914":2,"915":1,"916":3,"918":1,"928":1,"929":1,"930":1,"934":1,"935":1,"936":1,"956":1,"979":2,"980":2,"982":1,"986":1,"988":2,"989":1,"990":4,"991":1,"992":1,"994":2,"1021":1,"1029":1,"1054":2,"1055":1,"1056":2,"1057":1,"1058":1,"1060":1,"1070":2,"1074":1,"1076":1,"1102":1,"1105":3,"1113":1,"1135":1,"1138":1,"1139":1,"1141":1,"1142":1,"1149":1,"1179":2,"1188":1,"1192":1,"1197":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1308":1,"1309":1,"1310":1,"1321":1,"1331":1,"1332":1,"1337":1,"1338":1,"1339":1,"1345":2,"1347":1,"1348":1,"1357":1,"1362":1,"1368":1,"1372":2,"1376":2,"1393":1,"1394":1,"1395":4,"1396":2,"1419":1,"1427":1,"1431":1,"1442":1,"1504":1,"1547":1,"1632":1,"1655":2,"1689":1,"1727":1,"1736":1,"1742":1,"1745":1,"1792":2,"1850":1,"1852":1,"1920":1,"1921":1,"1924":1,"1926":1,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2147":1,"2264":1,"2283":1,"2290":1,"2292":1,"2293":1,"2303":1,"2304":1,"2337":2,"2338":1,"2342":2,"2343":1,"2346":1,"2382":1,"2383":1,"2526":1,"2527":2,"2528":1,"2545":1,"2549":3,"2572":1,"2575":1,"2580":2,"2762":1,"2766":1,"2775":1,"2802":1,"2809":1,"2810":1,"2812":1,"2815":1,"2822":1,"2829":2,"2834":3,"2836":1,"2841":1,"2851":1,"2855":1,"2860":1,"2862":2,"2864":1,"2867":2,"2881":1}}],["byschema",{"0":{"1577":1},"2":{"1416":1,"1417":1,"1553":1,"1554":2,"1577":1,"1580":1,"1581":1,"1792":2}}],["byconnectionname",{"2":{"1174":1,"1176":1,"1177":1,"1617":1,"1628":1,"1629":1,"1792":1,"2266":1}}],["byproduct",{"2":{"873":2}}],["bypassed",{"2":{"1326":1,"2223":1,"2495":1,"2645":1,"2767":1}}],["bypasses",{"2":{"106":1,"1792":1,"2346":1,"2481":1}}],["bypassauthenticated",{"2":{"1069":1,"1101":1,"1162":2,"1792":3,"1955":1,"1956":1,"1957":1,"1959":1,"2379":3,"2443":1,"2471":1}}],["bypassing",{"0":{"927":1},"1":{"928":1,"929":1,"930":1},"2":{"1066":1,"1385":1,"1822":1,"1824":1}}],["bypass",{"2":{"101":1,"108":1,"421":1,"445":1,"1067":2,"1101":2,"1150":2,"1398":1,"1519":2,"1523":1,"1524":1,"1525":1,"1529":1,"1746":1,"1792":1,"1929":1,"2347":1,"2380":2,"2840":1}}],["byte",{"0":{"927":1},"1":{"928":1,"929":1,"930":1},"2":{"308":1,"848":1,"921":1,"927":1,"928":1,"944":1,"948":2,"969":1,"1049":1,"2309":1,"2399":2,"2400":2,"2484":2,"2495":1,"2535":1,"2543":2,"2873":1}}],["bytesio",{"2":{"1366":1}}],["bytes",{"2":{"308":1,"747":2,"748":1,"762":1,"772":1,"848":1,"1214":5,"1232":5,"1234":1,"1235":1,"1358":1,"1410":1,"1412":2,"1511":1,"1792":11,"1804":1,"1882":3,"1885":1,"1991":1,"2125":1,"2177":1,"2309":1,"2399":1}}],["bytearraycontent",{"2":{"2309":1}}],["bytea",{"2":{"73":1,"585":1,"722":1,"1213":4,"1214":2,"1215":3,"1216":1,"1220":1,"1221":1,"1222":1,"1232":2,"1234":1,"1235":3,"1236":5,"1237":3,"1239":2,"1362":1,"1412":2,"1792":6,"1885":1,"1886":2,"1887":3,"1888":1}}],["by",{"0":{"19":1,"20":1,"107":1,"116":1,"180":1,"394":1,"1142":1,"1251":1,"1264":1,"1306":1,"1390":1,"2351":1,"2353":1,"2395":1,"2410":2,"2441":2,"2445":1},"1":{"1307":1,"1308":1,"1309":1,"1310":1,"2411":2,"2412":2,"2413":2,"2442":2,"2443":2,"2444":2},"2":{"0":1,"1":1,"2":1,"13":1,"33":1,"62":1,"64":1,"74":2,"75":1,"108":2,"109":1,"140":1,"168":1,"175":1,"176":1,"177":1,"179":1,"180":1,"181":1,"182":1,"188":1,"212":2,"223":1,"239":1,"251":1,"263":1,"297":1,"299":1,"300":1,"302":1,"320":1,"330":1,"334":2,"336":1,"337":1,"352":1,"356":1,"369":2,"382":1,"386":1,"390":1,"394":1,"395":1,"436":2,"448":3,"453":1,"454":1,"462":1,"463":1,"464":1,"511":1,"527":1,"528":2,"529":2,"531":2,"534":1,"559":1,"609":1,"638":1,"639":1,"652":1,"663":1,"668":1,"688":2,"689":1,"693":1,"694":1,"696":1,"698":1,"699":1,"703":1,"708":2,"713":1,"801":1,"816":1,"826":1,"834":2,"835":2,"836":1,"841":1,"843":1,"844":2,"848":1,"851":3,"852":3,"854":1,"859":2,"860":3,"861":4,"863":2,"864":1,"865":3,"868":2,"869":1,"872":1,"873":1,"875":1,"876":2,"878":1,"888":2,"914":3,"915":1,"916":3,"918":2,"919":3,"920":1,"928":1,"932":2,"936":1,"938":1,"946":1,"948":1,"949":1,"966":1,"974":1,"975":1,"976":1,"992":1,"1005":4,"1013":1,"1016":1,"1037":2,"1038":2,"1040":1,"1042":1,"1043":1,"1046":1,"1049":1,"1054":1,"1060":2,"1063":1,"1066":2,"1067":1,"1069":1,"1071":1,"1079":2,"1096":2,"1097":2,"1098":3,"1099":3,"1100":1,"1101":1,"1102":2,"1105":1,"1106":2,"1121":1,"1134":1,"1138":1,"1140":1,"1157":1,"1164":1,"1170":1,"1174":2,"1177":1,"1181":2,"1187":1,"1188":2,"1189":1,"1191":1,"1192":4,"1197":1,"1210":1,"1220":2,"1235":1,"1241":1,"1243":1,"1283":1,"1285":2,"1305":1,"1310":1,"1324":1,"1329":1,"1349":1,"1357":1,"1373":2,"1375":1,"1377":1,"1378":1,"1382":2,"1385":2,"1386":3,"1390":3,"1393":1,"1394":3,"1395":1,"1396":1,"1398":2,"1401":1,"1402":1,"1403":1,"1404":1,"1414":4,"1417":1,"1423":1,"1429":1,"1448":1,"1449":1,"1458":2,"1477":1,"1487":1,"1504":3,"1520":1,"1527":1,"1559":1,"1569":1,"1571":1,"1574":1,"1576":1,"1592":1,"1618":1,"1621":1,"1639":1,"1641":1,"1664":1,"1697":1,"1717":1,"1738":1,"1743":1,"1757":1,"1759":1,"1766":1,"1767":1,"1768":1,"1792":72,"1813":1,"1823":1,"1833":1,"1839":1,"1844":1,"1849":1,"1852":1,"1856":1,"1858":1,"1870":2,"1885":1,"1890":1,"1912":1,"1915":1,"1922":2,"1923":1,"1924":2,"1937":1,"1940":1,"1941":1,"1948":2,"1949":1,"1958":3,"1961":6,"1969":1,"1972":2,"1973":1,"1974":2,"1989":1,"2017":1,"2018":1,"2021":1,"2035":1,"2062":1,"2072":1,"2075":1,"2077":1,"2094":1,"2106":3,"2107":2,"2108":1,"2111":3,"2112":1,"2118":1,"2142":1,"2153":1,"2155":2,"2156":2,"2157":1,"2164":2,"2165":1,"2171":1,"2175":3,"2178":1,"2183":1,"2184":1,"2191":1,"2194":1,"2202":1,"2221":3,"2222":2,"2224":1,"2225":3,"2226":1,"2252":1,"2253":1,"2257":1,"2258":3,"2264":2,"2265":1,"2266":1,"2274":1,"2282":1,"2284":4,"2291":2,"2296":1,"2314":3,"2320":1,"2325":1,"2329":1,"2334":1,"2338":1,"2343":1,"2344":1,"2347":1,"2353":1,"2366":3,"2369":2,"2372":3,"2375":1,"2378":1,"2380":1,"2383":1,"2384":1,"2389":1,"2397":1,"2399":1,"2404":1,"2405":1,"2411":1,"2412":1,"2423":1,"2424":1,"2425":1,"2429":1,"2437":1,"2438":2,"2440":2,"2442":1,"2444":1,"2450":2,"2452":1,"2453":1,"2454":1,"2455":1,"2464":3,"2465":3,"2470":1,"2481":2,"2482":4,"2484":1,"2489":1,"2490":3,"2494":1,"2495":1,"2500":1,"2502":1,"2504":2,"2515":1,"2518":2,"2523":3,"2528":1,"2532":4,"2533":1,"2534":1,"2536":1,"2537":4,"2538":1,"2539":1,"2540":2,"2541":1,"2542":1,"2543":3,"2545":2,"2546":2,"2549":1,"2558":1,"2575":1,"2587":1,"2607":2,"2611":1,"2621":1,"2626":1,"2632":2,"2634":1,"2648":1,"2650":1,"2659":1,"2667":1,"2679":1,"2684":1,"2687":1,"2688":2,"2703":1,"2712":1,"2713":1,"2719":2,"2721":1,"2722":1,"2723":1,"2725":1,"2794":1,"2797":1,"2802":2,"2804":1,"2806":1,"2812":2,"2818":1,"2822":1,"2824":3,"2825":1,"2828":2,"2829":1,"2830":1,"2831":1,"2833":1,"2836":1,"2840":1,"2841":1,"2842":1,"2845":2,"2850":1,"2863":1,"2868":3,"2871":1,"2874":1,"2877":1,"2878":1,"2879":3,"2880":1,"2881":2}}],["svg",{"2":{"1943":1}}],["svg+xml",{"2":{"1792":1,"1936":1,"1943":1}}],["sveltekit",{"0":{"1581":1},"2":{"1416":1,"1574":1}}],["svelte",{"2":{"867":1}}],["srp",{"2":{"1184":1}}],["sr",{"2":{"1184":2,"1185":1}}],["src=",{"2":{"1412":1}}],["src",{"2":{"937":1,"976":1,"998":1,"1062":1,"1408":2,"1409":1,"1412":1,"1416":1,"1417":1,"1418":4,"1570":1,"1571":1,"1577":4,"1579":1,"1580":1,"1581":2,"1582":1,"1792":3,"2020":10,"2028":1,"2029":6,"2632":3}}],["sdks",{"2":{"1111":1}}],["sdk",{"2":{"1008":1,"2792":1}}],["sniffing",{"2":{"1792":1,"2016":1,"2017":1,"2632":2}}],["snapshots",{"2":{"1205":1}}],["snapshot",{"2":{"855":1,"864":1,"872":2}}],["snake",{"2":{"407":1,"995":1,"1408":1,"1567":1,"2724":1}}],["snowflake",{"2":{"834":1,"837":1,"848":1}}],["s3",{"2":{"650":1,"833":1}}],["s2",{"2":{"650":1,"833":1}}],["s1",{"2":{"650":1,"833":1}}],["sweep",{"2":{"2532":1}}],["sweet",{"2":{"1073":1}}],["swoole",{"0":{"1262":1,"1274":1},"2":{"1007":1,"1090":1,"1091":2,"1255":1,"1257":3,"1262":3,"1264":2,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1274":1,"1277":1,"1278":1,"1279":2,"1280":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["swarm",{"2":{"1762":1,"1792":1,"2634":2}}],["swashbuckle",{"2":{"869":2,"873":1}}],["swapping",{"2":{"1385":1}}],["swapped",{"2":{"860":1,"2106":1,"2537":1}}],["swap",{"2":{"848":1,"873":1,"874":1}}],["swagger",{"2":{"352":2,"835":1,"868":1,"869":1,"1095":1,"1789":1,"1796":1,"2430":1,"2432":2,"2438":1}}],["switched",{"2":{"2457":1,"2761":1}}],["switches",{"2":{"439":1,"696":1,"1070":1,"1102":1,"2798":1}}],["switching",{"2":{"848":1,"960":1,"1169":2,"1170":1,"2089":1,"2265":1}}],["switch",{"2":{"214":1,"838":1,"848":1,"1224":1,"1609":1,"1722":1,"1792":2,"1850":1,"2007":1,"2157":1,"2328":1,"2382":1,"2389":1,"2502":1,"2543":1,"2660":1,"2662":2,"2685":1,"2693":1,"2769":1}}],["skew",{"2":{"1792":1}}],["sku",{"2":{"898":2}}],["skimmed",{"2":{"1382":1}}],["skills",{"2":{"1401":1}}],["skill",{"2":{"876":1,"1401":2}}],["skiptypes",{"0":{"2360":1},"2":{"1553":1,"1559":2,"1571":1,"1580":1,"1792":2,"2360":3,"2484":1}}],["skipfunctionnames",{"2":{"1553":1,"1562":1,"1792":1}}],["skiproutinenames",{"2":{"1553":1,"1562":1,"1792":1}}],["skipnonquerycommands",{"0":{"624":1,"2341":1,"2342":1},"1":{"2342":1,"2343":1},"2":{"624":3,"626":1,"1792":1,"2342":3,"2343":1,"2841":1,"2851":1}}],["skippattern",{"0":{"2539":1},"2":{"1792":2,"2095":1,"2114":1,"2157":1,"2221":1,"2538":2,"2539":4,"2543":1,"2722":2,"2859":1,"2861":1}}],["skippaths",{"2":{"1553":1,"1562":1,"1792":1}}],["skipping",{"0":{"621":1,"622":1,"896":1},"2":{"624":2,"975":1,"1974":1,"2337":1,"2607":1}}],["skipped",{"2":{"320":1,"384":1,"586":1,"587":3,"625":3,"639":1,"720":1,"876":1,"1360":1,"1431":1,"1521":1,"1527":3,"1792":6,"1917":1,"1925":1,"1957":1,"2018":1,"2111":1,"2222":1,"2250":1,"2256":1,"2337":2,"2342":1,"2367":2,"2372":1,"2379":1,"2380":1,"2384":1,"2421":1,"2432":1,"2481":1,"2482":1,"2490":1,"2517":1,"2523":1,"2527":1,"2532":1,"2535":1,"2540":1,"2569":1,"2632":2,"2656":1,"2721":1,"2722":1,"2799":1,"2812":1,"2841":1,"2851":1,"2862":1}}],["skipschemas",{"2":{"1553":1,"1562":1,"1792":1}}],["skips",{"0":{"2367":1},"2":{"582":1,"587":1,"865":1,"1007":1,"1403":1,"1412":1,"1413":1,"1489":1,"1792":3,"2097":1,"2112":1,"2328":1,"2337":1,"2412":1,"2414":1,"2443":1,"2532":1,"2534":1,"2537":1,"2817":1,"2838":1,"2854":1}}],["skip",{"0":{"618":1,"1524":1,"1562":1,"2337":1,"2341":1,"2343":2,"2854":1},"1":{"619":1,"620":1,"621":1,"622":1,"623":1,"624":1,"625":1,"626":1,"2342":1,"2343":1},"2":{"107":1,"108":1,"123":1,"230":1,"238":2,"560":1,"568":1,"581":1,"584":2,"586":1,"587":1,"618":1,"619":3,"621":1,"622":2,"623":1,"624":3,"668":1,"704":2,"722":1,"826":1,"830":1,"888":1,"896":1,"1067":2,"1069":1,"1101":1,"1121":1,"1150":2,"1254":1,"1318":1,"1328":1,"1338":1,"1376":3,"1398":1,"1399":1,"1415":1,"1523":1,"1524":1,"1525":1,"1526":1,"1529":1,"1554":1,"1559":1,"1562":4,"1568":1,"1575":1,"1792":20,"1898":2,"1956":1,"2007":4,"2094":2,"2103":1,"2106":1,"2153":1,"2254":2,"2328":2,"2330":1,"2337":2,"2338":1,"2340":1,"2343":2,"2379":1,"2380":7,"2447":1,"2489":1,"2525":1,"2537":4,"2540":1,"2543":1,"2566":1,"2722":1,"2815":1,"2840":1,"2841":1,"2852":2,"2853":1,"2855":1,"2857":1,"2859":1,"2874":1}}],["sk",{"2":{"187":2,"2294":2}}],["ssd",{"2":{"1255":1}}],["ss",{"2":{"776":2,"889":2,"892":2,"963":1,"1792":7,"1800":1,"1809":2,"1810":1,"1917":1,"2077":1,"2094":1,"2101":1,"2123":2,"2130":4,"2156":1,"2537":1,"2542":1,"2652":1,"2814":1}}],["ssns",{"2":{"1382":1}}],["ssn",{"2":{"184":18,"186":4,"1100":1,"1664":1,"2291":1,"2292":12,"2293":4}}],["ssetestclient",{"2":{"2407":1}}],["sseresponseheaders",{"2":{"2249":1}}],["sse|",{"2":{"663":1}}],["sse",{"0":{"627":1,"636":1,"649":1,"652":1,"658":1,"659":1,"1305":1,"1309":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1323":1,"1372":1,"1573":1,"2249":1,"2362":1,"2391":3,"2392":1,"2393":1,"2490":1,"2827":1,"2828":1},"1":{"628":1,"629":1,"630":1,"631":1,"632":1,"633":1,"634":1,"635":1,"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1,"650":1,"651":1,"652":1,"653":2,"654":2,"655":1,"656":1,"657":1,"658":1,"659":1,"660":1,"661":1,"662":1,"663":1,"664":1,"665":1,"666":1,"667":1,"668":1,"669":1,"670":1,"671":1,"672":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"2828":1,"2829":1,"2830":1,"2831":1,"2832":1,"2833":1,"2834":1,"2835":1,"2836":1,"2837":1,"2838":1},"2":{"162":11,"233":5,"627":1,"628":2,"631":2,"632":2,"633":2,"634":1,"635":2,"636":1,"637":5,"639":1,"641":4,"642":2,"643":2,"644":2,"645":3,"646":2,"647":1,"648":2,"649":3,"650":9,"651":3,"652":2,"653":5,"654":3,"656":4,"658":2,"659":1,"660":2,"661":2,"662":5,"663":3,"664":6,"665":6,"666":2,"667":1,"668":2,"669":3,"670":1,"671":2,"672":1,"720":2,"726":2,"837":1,"868":1,"869":3,"877":1,"1035":1,"1036":1,"1103":1,"1121":1,"1126":1,"1127":1,"1302":2,"1304":2,"1305":7,"1309":9,"1311":1,"1312":2,"1313":4,"1314":2,"1315":2,"1317":2,"1318":1,"1321":2,"1323":2,"1325":3,"1327":2,"1351":1,"1368":1,"1372":5,"1377":1,"1415":1,"1416":4,"1573":2,"1581":1,"1792":9,"1824":1,"1848":1,"1857":3,"1858":4,"1859":1,"1860":4,"1861":5,"1864":1,"2107":1,"2164":2,"2165":1,"2223":2,"2226":5,"2240":1,"2247":9,"2249":5,"2250":1,"2251":3,"2252":17,"2270":1,"2278":1,"2314":2,"2323":1,"2329":1,"2362":3,"2388":1,"2391":13,"2392":10,"2393":1,"2406":2,"2407":2,"2481":2,"2490":4,"2498":1,"2529":1,"2537":1,"2591":1,"2802":1,"2827":4,"2828":5,"2829":7,"2830":2,"2831":7,"2832":5,"2834":8,"2835":7,"2836":4,"2837":1,"2838":4,"2856":2,"2865":1,"2879":1,"2881":1}}],["ssl=false",{"2":{"1146":1,"1510":1,"1514":1,"1792":1}}],["ssl=true",{"2":{"1067":2,"1534":1}}],["sslrequirement",{"2":{"64":1,"1199":2,"1469":1,"1482":1,"1498":1,"1499":1,"1502":1,"1503":1,"1505":1,"1792":1}}],["ssl",{"0":{"64":1,"1198":1,"1500":1,"1978":1,"1979":1},"1":{"1199":1,"1979":1,"1980":2,"1981":2,"1982":2,"1983":2,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1},"2":{"64":2,"1037":1,"1183":1,"1185":1,"1198":1,"1199":2,"1204":1,"1207":1,"1208":1,"1466":1,"1499":1,"1500":2,"1611":1,"1616":2,"1633":2,"1635":1,"1648":1,"1666":1,"1718":1,"1787":1,"1792":2,"1794":1,"1812":1,"1946":1,"1963":1,"1978":1,"1979":2,"1981":2,"1985":1,"1995":2,"2091":1,"2121":1,"2558":1,"2701":2,"2706":1}}],["symptom",{"2":{"2428":1,"2452":1}}],["symbol",{"2":{"876":1,"1409":1}}],["synthetic",{"2":{"2422":1,"2435":1,"2527":1}}],["syntaxes",{"2":{"2193":2,"2581":1,"2591":1}}],["syntax",{"0":{"5":1,"14":1,"30":1,"45":1,"56":1,"69":1,"79":1,"92":1,"102":1,"113":1,"126":1,"133":1,"145":1,"155":1,"166":1,"173":1,"179":1,"184":1,"186":1,"193":1,"203":1,"243":1,"262":1,"268":1,"283":1,"318":1,"329":1,"340":1,"348":1,"358":1,"370":1,"375":1,"378":1,"399":1,"413":1,"434":1,"459":1,"474":1,"485":1,"498":1,"508":1,"516":1,"528":1,"537":1,"550":1,"560":1,"564":1,"570":1,"582":1,"590":1,"599":1,"608":1,"619":1,"628":1,"637":1,"651":1,"674":1,"685":1,"694":1,"700":1,"704":1,"709":1,"714":1,"719":1,"730":1,"745":1,"795":1,"809":1,"824":1,"1017":1,"1740":1,"2196":1,"2200":1,"2288":1,"2301":1,"2662":1,"2692":1},"1":{"319":1},"2":{"79":1,"133":1,"158":1,"159":1,"162":1,"221":1,"253":1,"395":1,"404":1,"408":1,"493":1,"544":1,"570":1,"637":1,"680":1,"692":1,"725":1,"780":1,"790":1,"843":1,"852":1,"876":1,"916":1,"918":1,"926":1,"1010":1,"1017":1,"1037":1,"1078":1,"1086":1,"1382":1,"1398":1,"1464":1,"1580":1,"1582":1,"1605":1,"1607":1,"1608":1,"1609":1,"1664":1,"1665":1,"1792":8,"1809":1,"1909":1,"2007":1,"2040":1,"2094":1,"2155":1,"2193":2,"2210":1,"2252":2,"2272":1,"2277":1,"2283":1,"2328":1,"2333":1,"2340":1,"2358":1,"2360":1,"2365":1,"2376":1,"2431":1,"2476":1,"2497":1,"2519":1,"2529":1,"2531":1,"2533":1,"2575":1,"2581":3,"2591":1,"2597":1,"2662":1,"2665":1,"2667":1,"2671":1,"2673":1,"2681":1,"2687":1,"2688":1,"2691":1,"2694":1,"2705":1,"2763":1,"2785":2,"2841":1,"2848":1,"2865":1}}],["synonymous",{"2":{"863":2,"864":1}}],["synonym",{"2":{"860":1}}],["synced",{"2":{"1237":1,"1792":1,"1887":1}}],["synchronously",{"2":{"2106":1,"2112":1,"2532":1,"2537":1,"2559":1}}],["synchronous",{"2":{"1106":1,"1107":1,"1126":1,"1167":3,"1170":1,"1994":1,"2087":1,"2459":1}}],["synchronized",{"2":{"1008":1}}],["synchronization",{"2":{"973":1,"1000":1,"1008":1}}],["synctimeout=5000",{"2":{"1067":1,"1510":1,"1514":1,"1792":1}}],["sync",{"2":{"834":1,"845":1,"869":1,"871":1,"1006":1,"1038":1,"1094":1,"1104":1,"1422":1,"2324":1,"2840":1}}],["systems",{"2":{"841":1,"843":1,"845":3,"852":1,"948":2,"994":1,"1105":1,"1151":1,"1203":1,"1205":1,"1206":1,"1209":1,"1220":1,"1394":1,"1762":1,"1792":1,"2576":2,"2634":2,"2779":4,"2790":1}}],["system",{"0":{"754":1,"757":1,"784":1,"1352":1,"1363":1,"1595":1,"1654":1,"2127":1,"2438":1},"1":{"755":1,"756":1,"757":1,"758":1,"785":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"157":6,"267":1,"296":1,"393":2,"576":1,"645":1,"646":2,"746":3,"747":2,"748":2,"754":1,"755":1,"756":1,"757":11,"758":1,"777":1,"784":15,"785":2,"791":1,"841":2,"843":3,"847":1,"851":1,"857":2,"859":1,"860":2,"861":1,"867":1,"902":1,"903":1,"904":1,"921":1,"926":1,"933":1,"947":1,"948":1,"966":1,"967":1,"973":1,"1005":1,"1037":3,"1044":1,"1046":1,"1048":2,"1054":1,"1064":2,"1099":1,"1126":1,"1204":1,"1206":2,"1254":1,"1316":1,"1324":1,"1327":1,"1329":1,"1352":1,"1353":1,"1354":1,"1356":1,"1357":1,"1358":8,"1359":2,"1360":1,"1363":1,"1364":1,"1365":1,"1366":2,"1368":1,"1382":1,"1385":1,"1403":5,"1405":2,"1453":1,"1457":1,"1472":1,"1595":1,"1651":1,"1663":1,"1754":2,"1792":15,"1800":1,"1802":3,"1804":1,"1810":1,"2049":1,"2072":1,"2086":4,"2122":1,"2123":1,"2125":1,"2127":4,"2132":2,"2134":1,"2164":1,"2165":1,"2185":2,"2190":1,"2247":1,"2255":1,"2265":1,"2394":1,"2422":1,"2481":1,"2535":1,"2554":1,"2580":1,"2628":1,"2635":1,"2650":1,"2664":1,"2775":1,"2782":1,"2783":1,"2784":1,"2786":1,"2795":2,"2804":1,"2833":1,"2869":1}}],["slug",{"2":{"2146":2}}],["sleep",{"2":{"1259":1,"2875":1}}],["sleeps",{"2":{"859":1}}],["slate",{"2":{"989":1,"1070":1}}],["slots",{"2":{"1328":2,"2459":1}}],["slot",{"2":{"865":1,"1014":1,"1161":1,"2466":1}}],["slogan",{"2":{"863":1}}],["slowapi",{"2":{"869":1}}],["slowly",{"2":{"863":1,"1164":1,"1430":1}}],["slower",{"2":{"308":1,"803":1,"861":1,"1382":1,"1792":1,"1938":1,"2245":1,"2274":1,"2789":1}}],["slow",{"2":{"138":1,"277":1,"710":2,"711":1,"993":1,"994":1,"1005":1,"1132":1,"1768":1,"1792":2,"1958":1,"2402":1,"2466":1,"2470":1,"2537":2,"2545":1,"2615":1,"2634":1,"2815":1,"2873":1}}],["slipped",{"2":{"2413":1}}],["slips",{"2":{"1436":1}}],["sliding",{"0":{"1159":1,"1952":1},"2":{"1101":1,"1159":1,"1163":1,"1792":2,"1950":1,"1952":3,"1960":1,"2257":2,"2378":2,"2438":1,"2442":1}}],["slidingwindow",{"2":{"478":1,"480":1,"868":1,"1159":1,"1177":1,"1245":1,"1792":2,"1893":1,"1950":1,"1952":2,"1960":1,"2257":2,"2378":2,"2443":1}}],["slide",{"2":{"1037":1,"1382":2,"1383":1}}],["slides",{"2":{"1037":1,"1381":1,"1383":1}}],["slices",{"2":{"2546":1}}],["slice",{"2":{"869":1,"873":1,"961":1,"2398":1,"2540":2,"2845":2}}],["slightly",{"2":{"803":2,"1398":1,"2184":1}}],["slight",{"2":{"87":1,"88":1}}],["smoother",{"2":{"1159":1}}],["smoke",{"2":{"709":2,"710":3,"2097":1,"2167":1,"2537":1,"2545":1}}],["smeared",{"2":{"848":1}}],["smart",{"2":{"291":2,"1402":1}}],["smallestsize",{"2":{"1792":1,"1937":1,"1938":1,"1944":1}}],["smaller",{"2":{"869":1,"876":1,"1401":1}}],["small>",{"2":{"996":1}}],["small>$",{"2":{"996":1}}],["smallint",{"2":{"301":1}}],["small",{"0":{"86":1},"2":{"75":1,"84":1,"88":1,"307":1,"871":1,"872":2,"873":1,"1037":1,"1042":1,"1044":1,"1068":1,"1130":1,"1133":1,"1164":1,"1174":1,"1389":2,"1392":1,"1403":1,"1405":1,"1419":1,"1431":1,"1459":1,"1792":1,"1925":1,"2157":1,"2177":1,"2270":1,"2375":1,"2393":1,"2399":1,"2440":1,"2521":1,"2543":1,"2604":1,"2621":1,"2812":1,"2828":1}}],["smith",{"2":{"128":1,"489":1,"493":1,"643":1,"646":1,"913":1,"919":2,"1375":1}}],["sample",{"2":{"1792":1,"2125":1}}],["sameorigin",{"2":{"1792":4,"2016":1,"2018":2,"2029":1,"2632":2}}],["sameasrequest",{"2":{"1447":2,"1792":2,"2425":1,"2426":1,"2427":1,"2436":1}}],["samesite=strict",{"2":{"2438":1}}],["samesite=none",{"2":{"1447":1,"1792":1,"2428":2,"2429":1,"2435":1,"2438":1}}],["samesitemode",{"2":{"2435":1}}],["samesite",{"2":{"1447":1,"1792":2,"2425":1,"2436":2}}],["same",{"0":{"563":1,"988":1,"1178":1},"2":{"1":1,"32":1,"43":1,"74":1,"108":2,"213":1,"214":2,"279":1,"306":1,"308":1,"316":1,"319":1,"347":1,"364":1,"366":1,"388":1,"390":2,"395":1,"414":1,"421":1,"422":1,"423":1,"430":1,"436":2,"445":1,"515":2,"517":2,"529":1,"541":1,"560":2,"563":2,"587":2,"619":2,"636":1,"639":1,"646":1,"650":2,"659":2,"665":1,"666":1,"669":1,"687":1,"690":1,"696":1,"700":1,"712":1,"720":2,"724":1,"814":1,"831":2,"832":1,"834":1,"835":1,"836":3,"838":2,"841":2,"843":1,"844":2,"845":5,"847":1,"848":18,"852":3,"855":1,"860":1,"861":1,"863":1,"864":2,"865":2,"868":1,"871":3,"872":2,"873":1,"874":3,"875":1,"884":2,"902":1,"904":1,"915":2,"925":1,"957":1,"959":1,"966":1,"974":1,"975":1,"988":1,"990":1,"1005":2,"1017":1,"1038":1,"1041":1,"1043":1,"1045":2,"1046":1,"1052":1,"1054":1,"1060":1,"1067":2,"1068":2,"1070":3,"1073":1,"1076":1,"1078":1,"1082":1,"1086":1,"1096":1,"1102":1,"1105":1,"1108":1,"1126":1,"1129":8,"1135":5,"1148":2,"1150":2,"1154":1,"1175":1,"1176":1,"1178":1,"1179":2,"1193":4,"1205":1,"1233":1,"1238":2,"1255":3,"1281":1,"1304":1,"1305":1,"1326":2,"1327":1,"1328":1,"1343":2,"1349":1,"1366":1,"1367":1,"1368":1,"1370":1,"1371":1,"1374":1,"1382":3,"1385":2,"1386":1,"1394":2,"1398":2,"1407":1,"1408":1,"1410":2,"1421":1,"1422":2,"1424":1,"1429":1,"1433":1,"1436":1,"1438":1,"1440":1,"1441":2,"1442":1,"1447":1,"1504":1,"1511":1,"1518":3,"1522":3,"1559":1,"1567":1,"1570":1,"1684":1,"1686":1,"1688":1,"1689":2,"1696":1,"1733":1,"1740":1,"1741":1,"1743":2,"1744":1,"1792":36,"1823":1,"1824":1,"1825":1,"1830":1,"1850":1,"1851":1,"1856":2,"1862":1,"1883":1,"1888":1,"1911":1,"1923":1,"1929":1,"1961":3,"2018":1,"2019":7,"2021":1,"2023":3,"2024":1,"2025":5,"2029":2,"2038":1,"2040":2,"2095":2,"2096":1,"2098":2,"2106":1,"2107":1,"2111":2,"2155":1,"2156":2,"2157":1,"2175":1,"2182":1,"2193":1,"2202":1,"2224":1,"2256":1,"2264":1,"2265":5,"2284":1,"2288":1,"2289":1,"2304":1,"2308":1,"2320":3,"2321":3,"2325":1,"2327":1,"2329":1,"2340":2,"2346":2,"2347":1,"2356":1,"2375":1,"2380":4,"2382":1,"2383":1,"2385":1,"2389":3,"2391":2,"2398":1,"2400":1,"2411":1,"2413":1,"2414":1,"2415":1,"2423":1,"2425":1,"2427":2,"2431":2,"2434":1,"2438":1,"2440":2,"2442":1,"2445":3,"2446":1,"2450":1,"2451":3,"2452":1,"2453":1,"2457":1,"2461":1,"2466":1,"2468":1,"2472":1,"2476":2,"2481":2,"2483":1,"2484":1,"2486":1,"2493":1,"2500":1,"2502":3,"2504":3,"2509":2,"2510":1,"2518":1,"2523":1,"2529":1,"2531":1,"2533":2,"2534":5,"2535":1,"2537":4,"2539":1,"2540":4,"2542":3,"2543":1,"2545":1,"2550":1,"2586":1,"2589":1,"2590":1,"2597":2,"2632":9,"2648":1,"2666":1,"2722":1,"2727":2,"2731":1,"2765":1,"2779":1,"2791":1,"2792":1,"2811":1,"2828":1,"2830":1,"2833":1,"2834":1,"2845":2,"2851":1,"2852":1,"2856":1,"2857":1,"2869":1,"2871":3,"2872":1,"2878":1}}],["satisfies",{"2":{"1386":1}}],["satisfy",{"2":{"1075":1,"1832":1}}],["saving",{"2":{"872":1,"873":2,"1141":1}}],["savings",{"2":{"868":1,"869":1,"871":2,"873":1,"911":1,"1027":1,"1322":1}}],["savepoint",{"2":{"2342":1}}],["savechangesasync",{"2":{"1366":1}}],["save",{"2":{"844":1,"849":2,"851":1,"854":3,"876":1,"904":1,"920":1,"1037":1,"1073":1,"1076":1,"1080":4,"1320":1,"1366":7,"1401":2,"1405":1,"1407":2,"2543":1,"2664":1,"2878":1}}],["saved",{"0":{"872":1,"873":1,"1181":1},"2":{"157":1,"748":1,"872":2,"903":1,"1181":1}}],["sagas",{"2":{"865":1}}],["saw",{"2":{"861":1}}],["sans",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["sane",{"2":{"1385":1}}],["sanding",{"2":{"857":1}}],["sanctioned",{"2":{"704":1}}],["said",{"0":{"1075":1},"2":{"841":1,"872":1,"1404":1,"2486":2}}],["says",{"2":{"847":1,"848":1}}],["say",{"2":{"841":4,"843":2,"844":2,"845":1,"865":1,"947":1,"1046":1,"1067":1,"1134":1,"1382":1,"1385":1,"1386":1,"1399":1,"1400":1,"1403":2,"1404":1}}],["saying",{"2":{"701":1,"851":1,"1074":1}}],["salute",{"2":{"1404":1}}],["salesreportaudit",{"2":{"1193":1}}],["salesreportpublic",{"2":{"1193":1}}],["salesreportbase",{"2":{"1193":3}}],["sales",{"2":{"415":2,"834":2,"876":1,"887":1,"1179":2,"1184":3,"1185":5,"1187":1,"1188":3,"1189":2,"1191":7,"1192":5,"1193":10,"1196":1,"1200":2,"1202":1,"1203":1,"1207":2,"1373":2,"1413":1,"2303":1,"2813":2}}],["salt",{"2":{"308":4,"309":1,"363":1,"592":1,"813":1,"928":1,"1049":1,"1307":2,"2147":2,"2177":3,"2575":1}}],["safer",{"0":{"2486":1},"2":{"971":1,"2175":1,"2223":1}}],["safety",{"0":{"901":1,"1001":1,"1008":1,"1409":1,"1436":1},"2":{"803":1,"874":2,"875":1,"907":1,"917":1,"920":1,"946":1,"968":1,"972":1,"975":1,"986":1,"996":1,"1005":2,"1009":1,"1037":2,"1065":1,"1098":1,"1378":1,"1386":1,"1390":1,"1391":1,"1406":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"2164":1,"2165":1,"2466":1,"2502":1,"2527":1,"2826":1,"2862":1}}],["safely",{"2":{"308":1,"714":1,"1382":1}}],["safe",{"0":{"997":1,"1130":1,"1352":1,"1406":1,"2712":1},"1":{"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"106":1,"317":1,"324":1,"347":1,"528":1,"529":1,"918":1,"928":1,"1037":2,"1046":2,"1086":1,"1094":1,"1102":1,"1121":1,"1130":2,"1355":1,"1363":1,"1382":1,"1416":1,"1443":1,"1792":2,"1851":1,"2106":1,"2147":1,"2284":1,"2382":1,"2432":1,"2481":1,"2531":1,"2537":1,"2539":1,"2575":1,"2614":1}}],["scheduling",{"2":{"1275":1,"1792":1,"2094":1,"2100":1,"2537":1}}],["schemaonly",{"2":{"2318":1,"2324":1,"2840":1}}],["schemanotsimilarto",{"2":{"1792":1,"1836":1,"1838":1,"2701":1}}],["schemasimilarto",{"2":{"966":1,"967":2,"1792":2,"1836":1,"1838":1,"2046":1,"2047":1,"2062":1,"2069":1,"2635":1,"2701":1,"2721":1}}],["schemas",{"0":{"1414":1},"2":{"529":1,"724":1,"973":1,"1193":1,"1203":1,"1414":1,"1792":4,"1838":2,"1839":2,"1974":1,"2047":1,"2062":2,"2261":2,"2412":1,"2436":1,"2555":1,"2607":1,"2608":6,"2635":3,"2666":1}}],["schema",{"0":{"923":1,"924":1,"925":1,"977":1,"1050":1,"1178":1,"1191":1,"1213":1,"1307":1,"1336":1,"1355":1,"1577":1,"1838":1,"1909":1,"2062":1,"2069":1,"2670":1,"2755":1},"1":{"924":1,"925":1,"926":1,"1051":1,"1052":1,"1839":1},"2":{"322":1,"347":1,"348":1,"349":1,"352":1,"354":1,"582":1,"854":1,"872":2,"873":2,"875":2,"876":1,"888":1,"913":2,"922":4,"924":7,"925":4,"926":3,"932":2,"933":4,"937":2,"943":1,"945":1,"946":1,"967":2,"972":1,"973":1,"976":2,"977":4,"992":1,"997":1,"998":1,"1001":1,"1005":2,"1006":2,"1037":1,"1039":1,"1040":1,"1046":1,"1047":1,"1050":2,"1064":1,"1065":1,"1070":2,"1073":1,"1074":1,"1076":1,"1079":1,"1080":2,"1094":1,"1107":1,"1113":1,"1126":1,"1178":1,"1185":3,"1193":3,"1203":1,"1206":1,"1207":1,"1247":1,"1252":2,"1253":1,"1368":1,"1378":1,"1381":1,"1382":2,"1385":2,"1388":1,"1403":1,"1408":1,"1409":2,"1414":2,"1422":1,"1435":4,"1441":1,"1554":3,"1562":3,"1576":1,"1577":3,"1579":1,"1581":2,"1609":2,"1618":2,"1752":1,"1753":4,"1756":2,"1758":2,"1792":27,"1824":2,"1838":2,"1839":1,"1898":3,"1974":1,"2062":2,"2111":2,"2156":1,"2256":6,"2261":1,"2369":1,"2370":1,"2411":1,"2430":1,"2431":3,"2432":2,"2435":2,"2436":2,"2442":1,"2443":2,"2445":1,"2446":1,"2448":1,"2464":1,"2481":3,"2522":1,"2532":4,"2534":1,"2542":1,"2545":1,"2555":1,"2607":1,"2608":3,"2659":1,"2661":1,"2670":2,"2679":1,"2701":1,"2721":3,"2754":1,"2755":5,"2762":1,"2799":1,"2803":1,"2824":1,"2825":1,"2840":1,"2868":3,"2869":3,"2871":1,"2873":1,"2874":1,"2875":1}}],["schemename",{"2":{"2422":1}}],["schemecolumnname",{"2":{"300":1,"1469":1,"1471":1,"1483":1,"1792":3}}],["scheme=cookies",{"2":{"291":1}}],["schemes",{"0":{"290":1,"312":1,"1048":1,"1053":1,"1066":1,"1458":1,"1901":1,"2375":2,"2410":1,"2420":1,"2427":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":2,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1459":1,"1460":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"2411":1,"2412":1,"2413":1,"2421":1,"2422":1,"2423":1,"2424":1},"2":{"33":1,"285":1,"286":4,"288":1,"290":1,"291":1,"293":1,"302":1,"303":1,"305":1,"310":1,"315":2,"356":1,"691":1,"835":1,"837":1,"1037":2,"1048":2,"1053":1,"1064":2,"1065":1,"1068":1,"1098":7,"1127":1,"1197":1,"1458":4,"1459":1,"1460":3,"1464":1,"1792":8,"1898":1,"1901":1,"1907":1,"1994":1,"2164":1,"2175":1,"2178":1,"2184":1,"2186":1,"2225":3,"2227":2,"2254":1,"2375":7,"2409":1,"2410":5,"2412":1,"2413":2,"2419":3,"2420":1,"2421":1,"2422":4,"2423":1,"2424":1,"2427":2,"2435":1,"2436":1,"2438":1,"2440":1,"2554":1,"2737":1}}],["scheme",{"0":{"289":1,"291":1,"302":1,"1906":1,"2172":1,"2178":1,"2426":1,"2427":1},"1":{"2173":1,"2174":1,"2175":1},"2":{"33":3,"286":3,"289":1,"291":3,"296":1,"297":3,"298":5,"300":3,"302":5,"303":1,"307":1,"310":3,"312":6,"313":1,"835":1,"921":1,"934":6,"935":1,"937":1,"1053":5,"1055":4,"1056":8,"1060":3,"1061":4,"1065":1,"1068":5,"1098":5,"1216":4,"1222":1,"1239":4,"1249":1,"1308":1,"1371":1,"1447":1,"1451":1,"1454":1,"1458":15,"1459":2,"1460":13,"1469":1,"1471":2,"1473":1,"1480":2,"1481":2,"1483":1,"1686":1,"1688":1,"1792":31,"1830":1,"1888":1,"1902":1,"1903":1,"1906":5,"1907":1,"1911":1,"2019":1,"2170":1,"2171":3,"2172":1,"2175":2,"2176":5,"2177":1,"2178":6,"2180":1,"2186":3,"2187":1,"2189":1,"2225":2,"2227":2,"2254":4,"2375":29,"2410":1,"2411":3,"2412":2,"2413":2,"2417":1,"2420":3,"2421":9,"2422":9,"2423":11,"2424":3,"2427":1,"2434":1,"2435":6,"2437":4,"2438":2,"2481":1,"2554":2}}],["sc",{"2":{"949":2}}],["score",{"2":{"1336":1,"1339":7,"1423":1,"1429":1,"2164":1,"2815":7}}],["scores",{"0":{"870":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1}}],["scoping",{"2":{"1103":1,"1792":1,"2223":1,"2490":2}}],["scope=public",{"2":{"1695":1,"1792":1}}],["scope=r",{"2":{"1692":1,"1792":1}}],["scope=openid",{"2":{"1691":1,"1694":1,"1792":2}}],["scopessupported",{"0":{"1829":1},"2":{"1792":1,"1814":1,"2481":1}}],["scopes",{"0":{"1311":1,"1316":1},"1":{"1312":1,"1313":1,"1314":1,"1315":1,"1316":1},"2":{"646":1,"687":1,"708":1,"1121":1,"1573":1,"1690":1,"1792":2,"1829":2,"1833":1,"2223":1}}],["scope>",{"2":{"637":3}}],["scoped",{"0":{"2490":1},"2":{"176":1,"688":2,"876":1,"1036":1,"1070":1,"1304":1,"1529":2,"1850":1,"1851":2,"1911":1,"2223":1,"2382":3,"2434":1,"2490":1,"2498":2}}],["scope",{"0":{"636":1,"641":1,"642":1,"646":1,"1312":1,"1313":1,"1314":1,"1315":1,"1658":1,"1961":1,"2250":1,"2437":1,"2490":1,"2831":1},"1":{"637":1,"638":1,"639":1,"640":1,"641":1,"642":1,"643":1,"644":1,"645":1,"646":1,"647":1,"648":1},"2":{"162":3,"233":2,"480":1,"635":2,"636":4,"637":5,"639":1,"641":2,"642":1,"643":1,"644":1,"645":1,"646":6,"650":2,"663":1,"664":2,"665":3,"669":2,"671":2,"685":1,"849":1,"945":1,"1068":1,"1098":1,"1106":1,"1127":1,"1305":3,"1309":3,"1311":1,"1312":1,"1313":2,"1314":1,"1315":1,"1316":2,"1321":1,"1326":1,"1372":1,"1395":1,"1447":2,"1458":1,"1651":1,"1658":1,"1662":1,"1792":4,"1825":1,"1860":2,"2005":1,"2249":5,"2250":1,"2252":3,"2314":2,"2375":1,"2391":2,"2403":1,"2424":1,"2438":1,"2466":1,"2481":2,"2490":4,"2498":1,"2827":2,"2828":3,"2829":4,"2831":10,"2833":3,"2834":5,"2836":2,"2838":1}}],["scenmarios",{"2":{"918":1}}],["scene",{"2":{"843":1,"852":1}}],["scenario",{"0":{"1264":1,"2872":1,"2873":1,"2874":1,"2875":1,"2876":1},"2":{"697":1,"982":1,"985":1,"990":1,"1255":1,"1258":1,"1264":1,"1272":1,"1402":1,"1825":1,"1974":1,"2167":1,"2398":1,"2538":1,"2545":1,"2586":1,"2607":1,"2697":1,"2740":1}}],["scenarios",{"0":{"576":1,"990":1,"1258":1,"1262":1,"1263":1,"1285":1},"2":{"844":1,"876":1,"904":1,"916":1,"1066":1,"1091":1,"1105":1,"1144":1,"1145":1,"1146":1,"1152":1,"1205":1,"1228":1,"1254":2,"1255":1,"1258":1,"1262":1,"1263":1,"1266":2,"1271":1,"1396":1,"1625":1,"1792":3,"1878":1,"2088":1,"2111":1,"2114":1,"2245":1,"2255":1,"2270":1,"2380":1,"2398":3,"2438":1,"2532":1,"2550":1,"2585":1,"2611":1,"2664":1,"2789":1,"2791":1,"2871":1}}],["science",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"840":1}}],["scattered",{"2":{"945":1}}],["scaffolding",{"2":{"872":1,"1408":1}}],["scans",{"2":{"2318":1}}],["scan",{"2":{"857":1,"860":2,"966":2,"1096":1,"1792":2,"2050":1,"2051":1,"2157":1,"2422":1,"2543":1,"2635":4,"2825":1}}],["scanning",{"2":{"848":1,"2371":1}}],["scanned",{"2":{"388":1,"2721":1,"2799":1}}],["scalable",{"2":{"1385":1}}],["scalability",{"0":{"1135":1},"1":{"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1},"2":{"1037":1,"1135":1,"1324":1,"1325":2}}],["scalar",{"0":{"585":1},"2":{"120":1,"186":1,"299":1,"528":1,"582":2,"583":1,"585":2,"673":1,"1041":1,"1517":1,"1523":2,"1792":1,"1824":1,"2072":1,"2205":1,"2265":1,"2293":1,"2337":2,"2380":1,"2463":1,"2465":1,"2466":2,"2498":1,"2854":1}}],["scaling",{"0":{"1267":1,"1416":1},"2":{"1014":1,"1037":1,"1090":1,"1258":1,"1266":1,"1267":2,"1303":2,"1320":1,"1322":1}}],["scales",{"0":{"1007":1},"2":{"877":1,"1090":1,"1325":1}}],["scale",{"2":{"307":1,"841":1,"861":1,"871":1,"1015":1,"1119":1,"2177":1,"2270":1}}],["screams",{"2":{"1436":1}}],["screenshots",{"2":{"1442":1}}],["screen",{"2":{"838":1,"1060":1,"1080":1,"1435":1,"1687":1,"1792":5,"2543":1}}],["scrypt",{"2":{"1049":1}}],["scrutiny",{"2":{"876":1}}],["scrap",{"2":{"1433":5,"2164":3,"2762":1,"2770":2,"2816":1}}],["scraping",{"0":{"1423":1},"1":{"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1},"2":{"1037":1,"1162":1,"1423":2,"1430":1,"2164":5,"2762":1,"2770":2,"2816":1}}],["scraped",{"2":{"1430":1,"1431":3,"1925":1,"2164":1,"2517":1,"2816":1}}],["scraper",{"2":{"1037":1,"1423":2,"1424":1,"1432":1}}],["scrape",{"2":{"75":1}}],["scratch",{"2":{"869":1,"1065":1,"1792":1,"2111":1,"2532":1}}],["scroll",{"2":{"854":1}}],["script>",{"2":{"2040":2,"2476":2}}],["scripts",{"0":{"2531":1},"2":{"1073":1,"1074":1,"1086":1,"1125":1,"1343":1,"1385":4,"1386":5,"1403":1,"1418":1,"1792":1,"2168":1,"2367":1,"2481":1,"2482":1,"2531":1,"2550":1,"2791":1,"2841":1,"2869":1,"2874":1}}],["script",{"0":{"1386":1},"2":{"320":1,"835":2,"1073":1,"1076":2,"1209":1,"1211":2,"1218":2,"1385":1,"1386":2,"1388":2,"1394":2,"1401":2,"1404":1,"1792":2,"1868":2,"2020":2,"2029":1,"2221":1,"2319":1,"2632":1,"2709":1,"2869":1}}],["sh",{"2":{"2161":1}}],["shrinks",{"2":{"1440":1,"2869":1}}],["shy",{"2":{"1403":1}}],["shuts",{"2":{"2362":1}}],["shut",{"2":{"1384":3}}],["shutdown",{"0":{"2362":1},"2":{"1155":1,"1595":2,"1792":2}}],["shutter",{"2":{"852":1}}],["shifting",{"2":{"1856":1,"2450":1}}],["shifted",{"2":{"1856":1,"2398":1,"2451":2}}],["shifts",{"2":{"1266":1,"1792":1}}],["shift",{"2":{"1073":1,"1262":1,"2224":1,"2450":1,"2451":2,"2453":1}}],["shines",{"2":{"913":1,"1127":1,"1432":1,"2869":1}}],["shipped",{"2":{"861":1,"867":1,"872":1,"873":1,"874":1,"876":1,"877":1,"1076":1,"1096":1,"1402":1,"1959":1,"2377":1,"2461":1,"2471":1,"2486":3,"2493":1}}],["shipping",{"2":{"388":1}}],["ship",{"2":{"861":1,"865":1,"869":1,"872":1,"873":1,"1037":1,"1066":1,"1432":1,"1825":1,"2438":1,"2804":1}}],["ships",{"2":{"704":1,"834":1,"852":1,"868":1,"869":1,"1044":1,"1096":1,"1101":1,"1382":1,"2111":1,"2224":1,"2380":1,"2389":2,"2461":1,"2479":1,"2532":1,"2537":1,"2860":1,"2871":1}}],["shell",{"2":{"868":1,"1432":1,"1792":1,"2111":1,"2532":1,"2871":1,"2875":1}}],["sheetname",{"2":{"961":2}}],["sheet2",{"2":{"773":1}}],["sheet1",{"2":{"772":1,"773":1,"893":1}}],["sheets",{"0":{"899":1},"2":{"771":2,"773":1,"774":1,"776":3,"788":2,"789":1,"887":2,"892":2,"904":1,"2130":1}}],["sheet",{"2":{"228":1,"674":2,"675":1,"678":1,"679":3,"723":3,"770":1,"771":2,"772":5,"773":7,"774":3,"776":3,"788":2,"884":5,"887":3,"892":3,"893":4,"899":1,"904":3,"956":1,"957":4,"960":1,"964":1,"1374":1,"1413":1,"2079":4,"2081":1,"2130":2,"2653":4}}],["shshdocker",{"2":{"2717":1}}],["shshnpgsqlrest",{"2":{"2092":1,"2096":1,"2097":1,"2110":1,"2155":1,"2543":1,"2857":1,"2861":1,"2872":1,"2877":2,"2878":1}}],["shsh",{"2":{"710":1}}],["sha",{"2":{"308":2,"309":1,"363":1,"1049":1,"1067":1,"1657":2,"2177":1,"2495":1}}],["sha256",{"2":{"307":1,"308":4,"1049":1,"1511":1,"1516":1,"1792":1,"2177":2,"2265":2}}],["shapes",{"0":{"2520":1},"2":{"871":1,"1519":1,"1567":1,"1792":1,"2104":1,"2222":1,"2380":1,"2389":1,"2498":1,"2515":1,"2535":1,"2725":1,"2800":1,"2815":1,"2871":1}}],["shaped",{"0":{"394":1},"2":{"1041":1,"1077":1,"2463":1,"2493":1}}],["shape",{"0":{"1567":1,"1924":1,"2445":1},"2":{"214":1,"436":1,"665":1,"835":1,"848":1,"856":1,"868":1,"872":1,"873":1,"875":1,"1046":1,"1101":1,"1150":1,"1190":1,"1385":1,"1402":1,"1406":1,"1407":1,"1408":1,"1410":1,"1419":2,"1422":1,"1431":1,"1519":1,"1525":1,"1566":1,"1569":1,"1743":1,"1792":1,"1824":2,"2111":2,"2225":1,"2378":1,"2389":2,"2398":3,"2440":1,"2442":1,"2445":1,"2502":1,"2508":1,"2512":2,"2765":1,"2800":1,"2802":1,"2804":1,"2812":1}}],["sharpens",{"2":{"2388":1}}],["shards",{"2":{"1632":1}}],["sharding",{"2":{"1632":1}}],["sharded",{"2":{"848":1}}],["shares",{"2":{"831":1,"1073":1,"1105":1,"1162":1,"1957":1,"2157":1,"2379":1,"2466":1,"2543":1}}],["share",{"2":{"390":1,"666":1,"712":1,"836":1,"912":1,"915":1,"974":1,"1067":2,"1070":1,"1145":1,"1150":1,"1178":1,"1193":1,"1384":1,"1385":1,"1515":1,"1522":1,"1527":1,"1571":1,"1574":1,"1733":1,"1792":2,"1852":1,"1955":1,"2175":1,"2264":1,"2320":1,"2380":2,"2383":1,"2398":1,"2429":1,"2440":1,"2445":1,"2446":1,"2463":1,"2504":1,"2522":1,"2851":1,"2873":1}}],["sharedarraybuffer",{"2":{"1792":1,"2024":1,"2632":1}}],["shared",{"0":{"711":1,"747":1,"781":1,"832":1,"855":1},"2":{"74":1,"105":1,"156":1,"212":1,"214":1,"369":1,"386":1,"544":1,"695":1,"706":1,"711":3,"753":1,"757":1,"768":1,"776":1,"832":1,"841":1,"843":2,"852":3,"857":1,"1066":1,"1067":1,"1069":1,"1079":1,"1147":1,"1150":3,"1162":1,"1193":7,"1209":1,"1303":1,"1519":1,"1520":1,"1529":2,"1581":1,"1743":1,"1792":5,"1823":1,"2016":2,"2023":1,"2025":1,"2167":2,"2222":1,"2274":1,"2319":1,"2372":1,"2380":1,"2402":2,"2443":1,"2445":1,"2466":2,"2502":1,"2504":1,"2518":2,"2520":1,"2531":4,"2533":4,"2534":1,"2537":1,"2545":1,"2546":1,"2632":2,"2764":1,"2833":1,"2867":2,"2869":3,"2873":3,"2881":2}}],["sharing",{"2":{"108":1,"479":1,"1146":1,"1515":2,"1522":1,"1637":1,"1639":1,"1788":1,"1792":4,"1795":1,"1955":1,"1963":1,"2030":1,"2274":2,"2379":1,"2380":1,"2459":1,"2466":1,"2546":2,"2632":2}}],["shot",{"2":{"1422":1}}],["shotgun",{"2":{"840":2}}],["shocked",{"2":{"1400":1}}],["shop",{"2":{"869":1}}],["showing",{"2":{"1792":3,"2052":1,"2635":1}}],["showdisconnected",{"2":{"1320":1}}],["showed",{"2":{"1157":1,"1324":1,"2222":1,"2505":1,"2577":1}}],["shows",{"0":{"2577":1},"2":{"871":1,"914":1,"966":1,"1002":1,"1188":1,"1229":1,"1302":1,"1368":1,"1408":1,"1792":3,"1879":1,"2052":1,"2104":1,"2156":1,"2358":1,"2537":1,"2577":1,"2635":1,"2723":1,"2795":1,"2799":1}}],["show",{"2":{"622":1,"848":1,"1044":2,"1068":1,"1079":1,"1401":1,"1402":1,"1685":1,"1792":3,"2104":1,"2532":1,"2535":2,"2621":1,"2785":3,"2786":2,"2795":1}}],["shown",{"2":{"212":1,"1225":1,"1232":2,"1377":1,"1379":1,"1685":1,"1792":2,"1875":1,"1882":2,"2372":1,"2414":1,"2518":1,"2528":1,"2678":1,"2695":1,"2722":1,"2802":1,"2864":1}}],["shouldcommit",{"2":{"2267":1}}],["shouldn",{"2":{"168":1,"374":1,"1205":1,"1385":1,"1768":1,"1792":1,"2428":1,"2634":1}}],["should",{"0":{"2731":1},"2":{"73":1,"174":1,"307":1,"409":1,"458":1,"512":1,"663":1,"847":1,"849":1,"851":2,"859":2,"860":1,"921":1,"922":1,"926":2,"930":12,"946":1,"989":1,"990":3,"991":2,"994":1,"1049":1,"1052":1,"1054":1,"1132":1,"1204":1,"1231":1,"1232":1,"1233":1,"1238":1,"1393":2,"1399":1,"1404":1,"1405":1,"1406":1,"1419":2,"1574":1,"1653":1,"1792":10,"1823":1,"1983":1,"2016":3,"2018":1,"2019":1,"2020":1,"2025":1,"2267":1,"2351":1,"2353":1,"2391":1,"2452":1,"2456":2,"2528":1,"2533":1,"2611":1,"2632":3,"2831":1,"2864":1}}],["shortrun",{"2":{"2397":1}}],["shortcut",{"2":{"2040":1,"2413":1}}],["shortly",{"2":{"1958":1,"2470":1}}],["shorthand",{"2":{"1792":1,"2106":1,"2154":1,"2335":1,"2391":1,"2541":1,"2542":1,"2878":1}}],["shorter",{"2":{"1134":1,"1401":1,"1458":1,"1459":1,"1511":2,"1792":4,"2265":1,"2375":2}}],["shortening",{"2":{"1098":1}}],["short",{"0":{"8":1,"94":1,"136":1,"196":1,"271":1,"522":1,"1068":1,"1442":1},"2":{"269":1,"277":1,"913":1,"1037":1,"1066":1,"1067":1,"1068":5,"1121":1,"1134":1,"1150":1,"1254":1,"1378":1,"1385":1,"1419":1,"1442":1,"1458":4,"1529":1,"1792":5,"1823":2,"1910":1,"1961":1,"2145":1,"2211":1,"2212":1,"2375":4,"2397":1,"2410":4,"2413":1,"2415":1,"2427":2,"2433":1,"2466":2,"2713":1}}],["sudo",{"2":{"2782":1,"2783":1,"2784":1}}],["suddenly",{"2":{"1079":1,"1165":1,"2868":1}}],["sufficient",{"2":{"1140":1,"1323":1}}],["suffixed",{"2":{"1792":1,"1856":2,"2454":1,"2455":1,"2523":1}}],["suffix",{"2":{"211":1,"268":1,"381":1,"1034":1,"1366":1,"1511":1,"1731":1,"1753":1,"1792":2,"1821":1,"1823":2,"2265":1,"2333":1,"2456":4,"2519":1,"2539":2,"2848":1}}],["sustained",{"2":{"872":1,"1160":2,"2397":1,"2398":1,"2789":1}}],["suggests",{"2":{"872":1}}],["suggest",{"2":{"859":1,"1254":1,"1386":1}}],["suitable",{"2":{"851":1,"965":1,"1133":1,"1403":1,"1450":1,"2055":1,"2075":1,"2245":1,"2381":1,"2651":1}}],["suites",{"2":{"2534":1}}],["suited",{"2":{"975":1,"1035":1,"1144":1}}],["suite",{"0":{"2407":1},"2":{"221":1,"710":1,"865":1,"875":2,"1082":1,"1419":1,"2231":1,"2456":1,"2498":2,"2506":1,"2513":1,"2523":1,"2546":1,"2679":1}}],["supavisor",{"2":{"1101":1,"1102":2}}],["supabase",{"0":{"1070":1,"1083":1,"1088":1,"1107":1,"1115":1,"1119":1,"1123":1,"1126":1,"2713":1},"1":{"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1},"2":{"831":1,"1037":1,"1066":1,"1070":1,"1083":2,"1084":1,"1088":4,"1092":1,"1094":4,"1095":2,"1096":2,"1097":4,"1098":8,"1099":3,"1100":6,"1101":4,"1102":4,"1103":1,"1104":1,"1105":1,"1107":10,"1108":3,"1109":1,"1110":1,"1111":3,"1113":1,"1115":1,"1119":3,"1121":1,"1123":1,"1126":1,"1127":11,"1382":1,"1383":1,"1385":7,"1528":1,"1792":1,"1851":1,"1894":1,"2382":1}}],["supervises",{"2":{"1792":1,"2221":1}}],["supervisor",{"2":{"637":1,"644":1,"2157":3,"2199":1,"2200":3,"2543":5}}],["super",{"2":{"1394":2}}],["superior",{"0":{"1006":1},"1":{"1007":1,"1008":1,"1009":1},"2":{"946":1,"1280":1}}],["superuser",{"2":{"907":1,"926":3,"932":2,"933":2,"934":1,"937":1,"1618":1,"1792":1,"2256":2,"2459":1}}],["suppressxframeoptionsheader",{"2":{"1488":1,"1489":1,"1493":2,"1792":2,"2018":1,"2632":1}}],["suppressreadingtokenfromformbody",{"2":{"1488":1,"1489":1,"1494":1,"1792":1}}],["suppress",{"2":{"1415":1,"1792":2,"2430":1}}],["suppresses",{"2":{"319":1,"326":1,"347":1,"1040":1,"2481":1,"2629":2}}],["suppressed",{"2":{"173":1,"2414":1,"2672":1,"2879":1}}],["supposed",{"2":{"843":1,"1134":1,"1402":1}}],["supporting",{"2":{"1054":1,"1457":1,"2479":1,"2554":1}}],["supports",{"2":{"258":1,"379":1,"784":2,"876":1,"926":1,"1053":1,"1060":1,"1070":1,"1098":3,"1136":1,"1144":1,"1190":1,"1209":1,"1219":1,"1304":1,"1386":1,"1445":1,"1453":1,"1608":1,"1623":1,"1626":1,"1682":1,"1792":2,"1866":1,"1869":1,"1940":1,"1985":1,"2002":1,"2117":1,"2272":1,"2277":1,"2317":1,"2330":1,"2333":1,"2337":1,"2350":1,"2371":1,"2372":1,"2580":2,"2702":1,"2792":1,"2823":1}}],["supported",{"0":{"204":1,"269":1,"1395":1,"1729":1,"2710":1,"2736":1},"2":{"175":1,"267":1,"280":1,"370":1,"585":1,"684":1,"912":1,"918":1,"919":1,"1034":1,"1086":1,"1097":1,"1127":1,"1229":1,"1385":1,"1394":1,"1458":1,"1757":1,"1792":9,"1829":1,"1833":2,"1879":1,"1901":1,"1937":2,"2202":1,"2210":1,"2212":1,"2250":1,"2254":1,"2325":1,"2332":1,"2340":1,"2375":1,"2389":1,"2394":1,"2531":1,"2538":1,"2634":1,"2671":1,"2688":1,"2719":1,"2731":1,"2745":1,"2785":2,"2845":1}}],["support",{"0":{"1063":1,"1626":1,"2251":1,"2254":1,"2266":1,"2274":1,"2277":1,"2310":1,"2313":1,"2325":1,"2346":1,"2356":1,"2357":1,"2554":1,"2555":1,"2572":1,"2585":1,"2588":1,"2611":1},"1":{"1627":1,"1628":1,"1629":1,"2586":1,"2587":1},"2":{"32":1,"156":1,"158":1,"159":1,"162":1,"323":2,"408":1,"674":1,"767":1,"777":1,"778":1,"834":1,"876":1,"912":2,"918":2,"919":2,"1005":1,"1030":1,"1037":1,"1066":1,"1068":1,"1078":1,"1097":3,"1098":4,"1099":2,"1101":1,"1102":1,"1105":2,"1106":1,"1127":5,"1135":1,"1172":1,"1193":2,"1250":1,"1302":1,"1317":2,"1327":1,"1385":1,"1386":1,"1394":1,"1395":2,"1398":1,"1497":1,"1499":1,"1605":1,"1615":1,"1639":1,"1701":1,"1744":1,"1751":1,"1789":1,"1792":5,"1894":1,"1941":2,"1979":1,"2032":1,"2079":1,"2185":1,"2193":1,"2228":2,"2229":1,"2236":2,"2238":2,"2239":2,"2240":1,"2247":1,"2254":1,"2256":1,"2257":1,"2265":2,"2266":2,"2277":1,"2319":1,"2329":1,"2346":1,"2369":1,"2372":1,"2385":2,"2429":1,"2438":1,"2479":1,"2497":1,"2529":1,"2549":1,"2555":1,"2565":1,"2576":1,"2580":1,"2581":1,"2585":1,"2586":1,"2600":1,"2625":1,"2633":1,"2645":1,"2653":1,"2665":1,"2686":1,"2858":1,"2869":1}}],["supply",{"2":{"1738":1,"2723":1,"2768":1}}],["supplying",{"2":{"215":1}}],["supplied",{"2":{"106":1,"212":2,"388":1,"448":1,"527":1,"1792":1,"1923":1,"2183":1,"2247":1,"2250":1,"2267":1,"2282":1,"2395":2,"2432":1,"2466":1,"2712":1}}],["supplies",{"2":{"104":1,"105":1,"531":1,"1792":1,"2395":1}}],["summer",{"2":{"1443":1}}],["summit",{"2":{"851":1}}],["summing",{"2":{"761":1,"771":1}}],["summarization",{"0":{"1338":1},"2":{"1335":1,"1338":1}}],["summarizetext",{"2":{"1335":2}}],["summarize",{"2":{"320":2,"1332":3,"1335":1,"1338":5}}],["summary>",{"2":{"2255":6,"2256":4,"2257":2,"2265":8,"2266":2}}],["summary",{"0":{"1062":1,"1084":1,"1180":1,"1283":1,"1351":1,"1367":1,"1422":1,"2436":1},"1":{"1063":1,"1181":1,"1284":1,"1285":1},"2":{"351":2,"353":1,"1176":1,"1179":1,"1191":2,"1193":2,"1254":1,"1287":15,"1288":15,"1289":15,"1290":15,"1291":15,"1293":15,"1295":15,"1297":15,"1299":15,"1301":15,"1335":5,"1336":1,"1338":10,"1339":10,"1792":1,"2094":1,"2107":1,"2364":1,"2537":1}}],["sum",{"2":{"426":1,"566":2,"860":1,"885":1,"1398":1}}],["sunny",{"2":{"322":1}}],["subdomains",{"2":{"2429":1}}],["subdirectories",{"2":{"2003":1}}],["submit",{"2":{"1491":1,"1492":1,"2438":1}}],["submitted",{"2":{"309":2,"1490":1}}],["submission",{"0":{"1490":1},"1":{"1491":1,"1492":1}}],["subqueries",{"2":{"919":1}}],["subquery",{"2":{"528":1}}],["subtotal",{"2":{"869":2}}],["subgraph",{"2":{"833":2,"922":2,"1086":1,"1088":3,"1184":1,"1185":2,"1220":3,"1221":3,"1222":3}}],["subproc",{"2":{"663":1}}],["suburl",{"2":{"663":2}}],["subject",{"2":{"428":1,"859":1,"1402":1,"1792":2,"1988":1}}],["sub",{"2":{"347":2,"1302":1,"1303":1,"1320":4,"1322":1,"2432":1,"2443":1,"2447":2}}],["subsystem",{"2":{"872":2}}],["subset",{"2":{"852":1,"2430":1,"2529":1,"2537":1,"2865":1}}],["subsequent",{"2":{"120":1,"214":1,"625":1,"683":1,"747":1,"934":1,"1346":1,"1363":1,"1517":1,"1743":1,"2265":1,"2494":1,"2502":1}}],["subscribing",{"0":{"2830":1},"2":{"2827":1}}],["subscribes",{"2":{"668":1,"2830":1,"2834":1}}],["subscribed",{"2":{"666":1,"1103":1,"2391":1,"2834":1}}],["subscribe",{"0":{"2391":1,"2834":1},"2":{"663":4,"664":3,"665":2,"666":3,"669":1,"1320":1,"2226":1,"2391":8,"2393":1,"2827":1,"2828":2,"2829":1,"2830":1,"2832":2,"2834":8,"2836":1,"2838":1}}],["subscriber",{"0":{"669":1,"2490":1},"2":{"636":1,"650":3,"666":1,"668":1,"2223":1,"2226":1,"2391":1,"2393":1,"2407":1,"2490":1,"2498":2,"2836":1}}],["subscribers",{"2":{"636":1,"650":3,"669":1,"1309":1,"1792":1,"2391":2,"2392":2,"2407":1,"2490":6,"2498":1,"2827":1}}],["subscriptions",{"2":{"1094":1}}],["subscription",{"2":{"663":1,"1320":1,"2836":1}}],["substrate",{"2":{"877":1}}],["substring",{"2":{"309":1,"928":1,"929":1,"1427":1,"1429":1,"1792":1,"2094":1,"2096":1,"2537":2,"2762":2,"2877":1}}],["substitute",{"2":{"390":1,"834":1,"876":1,"933":1,"1733":1,"2264":1}}],["substituted",{"2":{"387":1,"390":1,"1010":1,"1019":1,"1605":2,"1862":1,"2040":1,"2264":1,"2476":1,"2483":1,"2497":2,"2534":1,"2688":2,"2871":1}}],["substitutes",{"2":{"212":3,"215":2,"395":1,"1016":1,"1023":1,"1033":1,"1723":1,"1733":2,"1738":2,"2040":1,"2264":2,"2283":1,"2477":1,"2768":1}}],["substitutions",{"2":{"2874":1}}],["substitutionenvironmentvariables",{"2":{"2483":1}}],["substitution",{"0":{"212":1,"386":1,"1733":1,"2483":1},"1":{"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1},"2":{"156":2,"212":1,"216":1,"387":2,"388":1,"395":1,"452":1,"527":1,"529":2,"535":2,"544":2,"926":1,"1094":1,"1104":1,"1105":2,"1661":1,"1792":1,"1862":1,"2185":1,"2223":1,"2264":1,"2282":1,"2284":1,"2481":1,"2493":2,"2764":1,"2771":1}}],["substantially",{"2":{"869":1}}],["substantial",{"2":{"0":1,"1011":1}}],["surfacing",{"2":{"2405":1,"2437":1}}],["surfaced",{"2":{"2411":1,"2415":1,"2416":1,"2450":1,"2481":1,"2489":1,"2491":1,"2515":1}}],["surfaces",{"2":{"871":1,"1407":1,"1792":2,"1822":1,"2528":1,"2864":1}}],["surface",{"2":{"175":1,"320":1,"388":1,"529":1,"847":1,"868":1,"872":3,"877":1,"943":1,"984":1,"985":1,"1042":1,"1071":1,"1080":1,"1096":1,"1414":1,"1527":1,"1568":1,"1792":1,"1823":1,"1911":2,"2380":1,"2389":1,"2430":1,"2434":2,"2446":1,"2457":1,"2461":1,"2481":1,"2537":1,"2729":1,"2879":1}}],["surprising",{"2":{"2376":1,"2380":1}}],["surprised",{"2":{"1400":1}}],["surprise",{"2":{"1080":1,"1096":1}}],["surreal",{"2":{"913":1}}],["surrounding",{"2":{"849":1,"1083":1,"2040":1,"2476":1}}],["survived",{"2":{"1401":1}}],["survive",{"2":{"876":1,"1135":1,"1403":1}}],["survives",{"2":{"389":1,"1067":1,"1079":1,"1792":1,"2537":1,"2873":1}}],["surely",{"2":{"1401":1}}],["sure",{"2":{"305":1,"843":1,"967":1,"1386":1,"1402":2,"2824":1}}],["succeed",{"2":{"881":1}}],["succeeded",{"2":{"310":2,"748":1,"1056":5,"1062":1,"1360":1}}],["succeeds",{"2":{"297":1,"299":1,"587":1,"689":1,"700":1,"941":1,"985":1,"997":1,"1078":1,"1153":1,"1358":1,"1792":1,"2187":1,"2428":1,"2535":1}}],["successfully",{"2":{"1385":1,"1394":1,"1455":1,"1792":1,"1844":1,"2554":1}}],["successful",{"2":{"34":1,"214":1,"747":1,"781":1,"934":1,"1056":1,"1237":1,"1238":1,"1239":1,"1357":2,"1430":1,"1480":1,"1628":1,"1686":1,"1743":1,"1792":3,"1887":1,"1888":1,"2125":1,"2266":1,"2502":1}}],["success",{"0":{"34":1},"2":{"33":2,"206":1,"207":2,"208":2,"209":4,"210":1,"213":1,"298":1,"300":1,"301":2,"310":2,"439":2,"447":1,"452":2,"700":1,"747":1,"748":1,"763":1,"773":2,"781":1,"812":1,"813":1,"814":1,"815":1,"887":3,"894":1,"903":3,"1019":2,"1020":2,"1021":6,"1026":12,"1031":1,"1045":1,"1105":7,"1179":1,"1218":2,"1220":1,"1221":1,"1237":1,"1332":2,"1338":2,"1339":2,"1341":1,"1347":1,"1355":1,"1357":2,"1358":1,"1359":1,"1360":3,"1366":1,"1376":4,"1385":1,"1398":5,"1399":1,"1410":1,"1426":1,"1427":1,"1431":2,"1471":2,"1472":1,"1480":2,"1721":1,"1722":2,"1725":1,"1727":2,"1732":1,"1736":2,"1741":1,"1743":1,"1792":13,"1887":1,"1916":1,"1918":2,"1921":2,"1922":1,"2093":1,"2105":1,"2109":1,"2110":1,"2147":2,"2177":1,"2222":1,"2264":5,"2289":1,"2402":1,"2502":1,"2530":2,"2537":1,"2549":4,"2575":1,"2669":1,"2762":3,"2763":4,"2764":2,"2766":4,"2769":2,"2785":1,"2810":3,"2814":1,"2815":2,"2866":1}}],["such",{"2":{"41":1,"75":1,"320":1,"723":1,"843":1,"844":1,"864":1,"872":1,"918":1,"1254":1,"1384":1,"1386":1,"1394":2,"1569":1,"1759":1,"1792":1,"1822":1,"1840":1,"1912":1,"1925":1,"2466":2,"2493":1,"2517":1,"2518":1,"2841":1}}],["sister",{"2":{"2440":1}}],["sigkills",{"2":{"2532":1}}],["sigkill",{"2":{"2157":1,"2543":1,"2546":1,"2758":1,"2881":1}}],["sigterm",{"2":{"2106":1,"2112":1,"2157":2,"2158":1,"2221":1,"2532":1,"2537":1,"2543":2,"2546":2,"2758":1,"2871":1,"2878":1,"2881":1}}],["sigint",{"2":{"2106":1,"2112":1,"2532":1,"2537":1,"2546":1}}],["signcountcolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["signup=false",{"2":{"1693":1,"1792":1}}],["signup",{"2":{"888":6}}],["signals",{"2":{"876":1}}],["signal",{"2":{"871":1,"1415":1,"2112":1,"2532":1,"2543":1}}],["signatures",{"2":{"871":1,"995":1,"1005":1,"1178":1,"1211":1,"1243":1,"1368":1,"1792":1,"2359":1,"2666":1,"2723":1,"2858":1}}],["signature",{"0":{"760":1,"770":1},"2":{"74":2,"75":1,"362":1,"448":1,"872":4,"975":1,"978":1,"985":1,"995":1,"1002":1,"1098":1,"1210":1,"1222":1,"1227":1,"1236":3,"1239":2,"1243":1,"1366":1,"1419":1,"1436":1,"1792":3,"1830":1,"1868":1,"1877":1,"1886":2,"1888":2,"2184":1,"2222":1,"2247":1,"2438":2,"2518":3,"2519":1,"2523":1}}],["significantly",{"2":{"1090":1,"1091":1,"1263":1,"1792":1,"2270":1,"2359":1,"2366":1,"2789":1}}],["significant",{"2":{"848":1,"869":1,"872":1,"2621":1}}],["signinasync",{"2":{"2424":1}}],["signinhtmltemplate",{"0":{"1685":1},"2":{"1684":1,"1792":1}}],["signing",{"2":{"1098":1,"1220":1,"1415":1,"1454":2,"1458":1,"1792":4,"1870":1,"2040":1,"2375":1,"2438":2,"2477":1,"2554":2}}],["signinurl",{"2":{"1059":1,"1683":1,"1684":1,"1792":1}}],["signin",{"2":{"296":1,"1059":2,"1061":1,"1683":1,"1684":1,"1697":1,"1792":4,"2554":1}}],["signed",{"2":{"297":1,"298":1,"305":1,"306":1,"1054":1,"1068":2,"1069":1,"1162":1,"1458":1,"1792":1,"1956":1,"2172":1,"2175":1,"2179":1,"2181":1,"2187":1,"2375":1,"2379":1,"2419":1,"2420":1,"2424":1,"2836":1}}],["signs",{"2":{"286":1,"291":2,"297":1,"934":1,"1210":1,"1460":1,"1792":1,"2176":1,"2186":2,"2375":1}}],["signoutasync",{"2":{"2437":1}}],["signout",{"2":{"282":1,"288":3}}],["sign",{"2":{"12":1,"27":1,"33":1,"224":2,"282":1,"284":1,"285":1,"286":2,"294":1,"296":1,"298":2,"300":1,"302":1,"312":1,"316":1,"597":1,"849":1,"868":1,"1213":1,"1215":3,"1216":4,"1222":1,"1224":1,"1236":3,"1239":5,"1240":1,"1465":2,"1467":2,"1481":2,"1484":2,"1486":1,"1684":2,"1685":1,"1697":1,"1699":2,"1792":12,"1874":1,"1886":1,"1888":1,"1889":1,"2170":1,"2176":2,"2186":1,"2187":1,"2189":3,"2420":1,"2437":1}}],["sibling",{"2":{"1570":1,"2092":1,"2154":1}}],["sizing",{"0":{"1169":1}}],["sized",{"2":{"1255":1,"2873":1}}],["size=len",{"2":{"1366":1}}],["size=50",{"2":{"1177":1,"1633":1}}],["size=100",{"2":{"1177":1,"1633":1}}],["sizes",{"2":{"966":1,"1266":1,"1792":2,"1935":1,"2050":1,"2051":1,"2059":1,"2124":1,"2364":1,"2400":1,"2635":3}}],["size",{"0":{"1170":1},"2":{"89":1,"324":1,"747":4,"748":2,"753":4,"757":4,"762":3,"763":1,"768":1,"772":3,"773":2,"776":2,"779":1,"782":6,"784":6,"786":3,"872":1,"879":1,"883":1,"887":1,"893":2,"894":1,"903":2,"948":1,"951":1,"971":1,"1060":1,"1107":1,"1272":1,"1285":1,"1355":2,"1357":3,"1359":1,"1360":1,"1366":1,"1410":3,"1511":1,"1616":2,"1687":1,"1792":6,"1804":2,"1974":1,"1991":5,"2073":1,"2075":1,"2080":1,"2125":2,"2245":3,"2270":1,"2451":1,"2607":1,"2789":1,"2804":1}}],["sieve",{"2":{"1101":1}}],["simd",{"0":{"2270":1},"2":{"2239":1,"2270":2}}],["simultaneous",{"2":{"1161":1}}],["simultaneously",{"2":{"948":1,"1053":1,"1098":1,"1147":1,"1745":1,"2270":1,"2346":1,"2736":1,"2873":1}}],["simulation",{"0":{"857":1},"2":{"852":3,"857":3,"860":1,"865":1}}],["simulated",{"2":{"854":1}}],["simulates",{"2":{"851":1,"1335":1}}],["simulate",{"2":{"851":1,"852":1,"1074":1}}],["simlpler",{"2":{"920":1}}],["similarly",{"2":{"1385":1}}],["similar",{"2":{"851":1,"918":1,"1087":1,"1102":1,"1303":1,"1401":1,"1728":1,"1792":8,"1838":4,"1898":2,"1909":1,"2047":1,"2062":1,"2264":1,"2431":1,"2436":2,"2635":1}}],["simplified",{"2":{"1728":1,"2264":1}}],["simplification",{"2":{"871":1,"1405":1}}],["simplify",{"2":{"1390":1,"1792":2}}],["simplicity",{"0":{"1405":1},"2":{"871":1,"1037":1,"1118":1,"1127":1,"1378":1,"1405":1}}],["simply",{"2":{"285":1,"304":1,"308":1,"389":1,"851":1,"857":1,"872":1,"873":1,"880":1,"920":1,"994":1,"1134":1,"1139":1,"1385":1,"1386":3,"1395":2,"1396":1,"1398":1,"1399":1,"1569":1,"1759":1,"1792":1,"1912":1,"2111":1,"2461":1,"2470":1,"2495":1,"2532":1,"2540":1,"2827":1,"2868":1}}],["simpler",{"2":{"307":1,"904":1,"918":1,"971":1,"975":1,"1006":1,"1088":1,"1094":1,"1097":1,"1103":1,"1126":1,"1323":1,"1385":2,"1398":1,"1405":2,"1792":1,"2820":1}}],["simplest",{"0":{"1369":1,"2173":1},"2":{"298":1,"308":1,"961":1,"1125":1,"1579":1,"2332":2,"2809":1}}],["simple",{"0":{"115":1,"360":1,"1064":1,"2194":1},"1":{"1065":1},"2":{"206":6,"438":1,"501":1,"663":1,"750":3,"752":2,"755":2,"835":1,"841":1,"849":1,"879":1,"908":1,"913":2,"914":1,"915":1,"918":1,"919":2,"930":1,"933":1,"934":1,"938":1,"948":1,"977":1,"987":1,"1005":1,"1019":1,"1023":1,"1026":1,"1073":2,"1076":1,"1084":3,"1121":2,"1123":1,"1125":1,"1126":1,"1205":1,"1206":1,"1207":1,"1302":1,"1323":1,"1335":2,"1351":1,"1372":1,"1385":1,"1386":3,"1387":2,"1389":1,"1398":3,"1401":1,"1402":1,"1553":1,"1556":2,"1557":1,"1581":1,"1685":1,"1730":1,"1752":1,"1753":3,"1755":1,"1758":1,"1792":7,"2164":1,"2165":2,"2180":1,"2205":1,"2589":1,"2766":1,"2772":1,"2823":1,"2837":2}}],["silicon",{"2":{"2576":1,"2779":1,"2790":1}}],["silenced",{"2":{"2879":1}}],["silence",{"0":{"2752":1,"2801":1},"2":{"1802":1,"2392":1,"2544":1,"2794":1}}],["silences",{"2":{"1801":1,"2537":1}}],["silent",{"0":{"2384":1,"2401":1,"2492":1},"1":{"2402":1,"2403":1,"2404":1,"2405":1},"2":{"704":1,"1071":1,"1150":1,"1792":1,"1801":1,"2221":1,"2224":1,"2353":1,"2378":1,"2384":1,"2401":1,"2402":1,"2405":2,"2451":2,"2493":1,"2544":2,"2752":1,"2801":1}}],["silently",{"0":{"2505":1},"2":{"308":1,"320":1,"382":1,"388":1,"701":1,"927":1,"928":1,"973":1,"1042":1,"1199":1,"1449":2,"1527":1,"1609":1,"1792":1,"1856":1,"2222":1,"2223":1,"2334":1,"2348":1,"2365":1,"2367":1,"2376":2,"2377":1,"2378":1,"2380":1,"2392":1,"2395":1,"2412":1,"2425":1,"2428":1,"2443":1,"2444":1,"2446":1,"2450":1,"2453":1,"2454":1,"2481":1,"2492":1,"2493":1,"2495":1,"2505":1,"2535":1,"2659":1}}],["silly",{"2":{"849":1}}],["sitting",{"2":{"851":1,"876":1,"1073":1,"1075":1}}],["sits",{"2":{"847":1,"848":1,"851":1,"852":1,"860":1,"1106":1,"1409":1,"1713":1,"2531":1,"2835":1}}],["situation",{"2":{"843":1,"2868":1}}],["sit",{"2":{"838":1,"859":1,"873":1,"2531":1}}],["sites",{"2":{"1209":1,"1423":1,"1867":1,"2400":1,"2405":1,"2414":1}}],["site",{"2":{"831":1,"834":1,"856":1,"866":1,"872":1,"1449":1,"1487":1,"1504":1,"1581":1,"1689":1,"1792":7,"1983":1,"2019":1,"2025":2,"2425":1,"2429":1,"2632":1}}],["six",{"2":{"448":1,"861":1,"2140":1,"2457":1}}],["sidesteps",{"2":{"921":1,"1428":1}}],["sides",{"2":{"666":1,"667":1,"1407":1}}],["side",{"0":{"668":1,"669":1,"826":1,"1140":1,"1218":1,"2768":1},"1":{"1141":1,"1142":1,"1143":1},"2":{"112":1,"182":1,"212":1,"215":1,"390":1,"395":1,"396":1,"527":1,"529":1,"534":1,"548":1,"667":1,"823":1,"826":1,"834":1,"837":2,"868":1,"871":1,"1033":1,"1037":2,"1039":1,"1043":2,"1068":1,"1084":1,"1096":6,"1100":1,"1104":1,"1105":1,"1106":1,"1108":1,"1109":1,"1111":1,"1121":1,"1122":1,"1126":1,"1127":1,"1136":1,"1137":1,"1139":1,"1140":1,"1149":1,"1181":1,"1209":1,"1232":1,"1234":1,"1241":1,"1279":1,"1399":2,"1403":1,"1409":1,"1410":2,"1419":1,"1432":1,"1448":1,"1459":1,"1569":1,"1738":3,"1769":1,"1792":5,"1882":1,"1884":1,"1890":1,"1923":2,"2020":1,"2040":2,"2156":1,"2164":1,"2230":1,"2282":1,"2284":1,"2291":1,"2338":2,"2346":1,"2375":1,"2384":1,"2391":2,"2394":1,"2477":1,"2495":1,"2496":1,"2509":2,"2520":1,"2542":1,"2634":1,"2635":1,"2759":1,"2762":1,"2768":2,"2771":1,"2835":1,"2853":1,"2868":2,"2878":1}}],["sink",{"2":{"2794":1,"2804":1}}],["sinks",{"2":{"2794":1}}],["sin",{"2":{"852":1}}],["singleton",{"2":{"1522":1,"1792":1,"2372":1,"2380":1}}],["single",{"0":{"254":1,"607":1,"609":1,"613":1,"811":1,"827":1,"914":1,"975":1,"1000":1,"1580":1,"2319":1,"2339":1,"2618":1,"2842":1},"1":{"608":1,"609":1,"610":1,"611":1,"612":1,"613":1,"614":1,"615":1,"616":1,"617":1,"2843":1},"2":{"102":1,"109":1,"121":1,"133":1,"186":1,"214":2,"227":2,"286":1,"302":1,"312":1,"335":1,"352":1,"378":2,"379":1,"383":1,"386":1,"480":1,"528":1,"560":1,"567":1,"568":2,"582":1,"583":1,"584":1,"585":3,"586":1,"587":2,"588":2,"607":3,"608":1,"609":3,"611":1,"612":1,"613":2,"614":5,"615":2,"616":1,"619":1,"626":2,"636":1,"663":1,"683":1,"705":1,"778":1,"827":1,"832":2,"833":1,"841":1,"844":2,"863":1,"864":1,"865":2,"867":1,"872":1,"873":1,"875":1,"876":1,"877":1,"901":1,"903":1,"904":1,"914":2,"916":1,"919":3,"920":1,"947":1,"954":1,"974":1,"975":1,"983":1,"996":2,"1005":1,"1010":1,"1029":1,"1038":1,"1039":1,"1041":2,"1045":2,"1067":2,"1069":1,"1070":1,"1074":1,"1077":1,"1084":5,"1086":3,"1087":2,"1088":1,"1094":1,"1098":3,"1099":1,"1101":1,"1102":2,"1105":1,"1107":2,"1121":1,"1127":3,"1145":1,"1150":2,"1162":2,"1172":2,"1181":1,"1192":1,"1193":1,"1206":1,"1235":1,"1266":1,"1302":1,"1304":1,"1305":1,"1322":1,"1367":1,"1370":5,"1376":2,"1378":2,"1381":1,"1382":2,"1385":1,"1386":8,"1391":4,"1393":1,"1398":3,"1402":4,"1409":1,"1410":1,"1416":1,"1427":2,"1429":1,"1430":1,"1431":1,"1439":1,"1450":1,"1457":1,"1458":1,"1459":1,"1517":1,"1522":1,"1527":1,"1577":1,"1608":1,"1651":1,"1655":2,"1743":2,"1745":1,"1746":1,"1758":1,"1792":15,"1813":1,"1817":1,"1824":4,"1852":2,"1885":1,"1911":1,"1917":1,"1925":1,"1955":1,"1957":2,"2000":1,"2002":2,"2009":3,"2040":1,"2056":1,"2149":1,"2156":1,"2179":1,"2212":1,"2222":1,"2224":1,"2252":1,"2265":1,"2270":1,"2272":1,"2274":1,"2277":1,"2287":1,"2293":1,"2297":1,"2320":2,"2330":3,"2333":3,"2337":5,"2339":16,"2340":1,"2346":1,"2347":2,"2348":2,"2354":1,"2357":3,"2364":1,"2366":3,"2372":1,"2375":2,"2379":2,"2380":3,"2383":2,"2397":1,"2398":1,"2400":1,"2419":1,"2423":1,"2435":3,"2438":2,"2445":1,"2452":1,"2456":1,"2461":1,"2463":1,"2464":1,"2470":1,"2474":1,"2481":3,"2482":1,"2502":2,"2504":1,"2512":1,"2518":2,"2522":1,"2529":1,"2531":2,"2540":2,"2542":1,"2546":1,"2554":1,"2590":2,"2618":1,"2649":1,"2725":3,"2744":1,"2762":2,"2774":1,"2776":1,"2814":1,"2830":1,"2833":1,"2841":2,"2842":1,"2845":1,"2850":2,"2851":1,"2852":3,"2854":2,"2865":1}}],["sincere",{"2":{"841":1}}],["since",{"2":{"19":1,"20":1,"22":1,"310":1,"325":1,"412":1,"429":1,"458":1,"559":1,"581":1,"618":1,"653":1,"823":1,"840":2,"844":1,"845":1,"849":1,"876":1,"892":1,"919":1,"932":1,"953":1,"986":1,"988":1,"990":1,"992":1,"1049":1,"1073":1,"1077":1,"1094":2,"1111":1,"1122":1,"1179":1,"1238":1,"1251":1,"1372":1,"1379":2,"1398":1,"1400":1,"1407":1,"1792":1,"1801":1,"1802":1,"1840":2,"2289":1,"2310":1,"2313":1,"2384":1,"2389":1,"2398":1,"2430":1,"2434":1,"2444":1,"2466":1,"2533":1,"2544":1,"2621":1,"2719":1,"2721":1,"2731":1,"2739":1,"2752":1,"2767":1,"2794":1,"2801":1,"2803":1,"2824":1,"2841":1,"2844":1,"2865":1}}],["spurious",{"2":{"2542":1}}],["spun",{"2":{"1522":1}}],["spoofed",{"2":{"1717":1}}],["spoof",{"2":{"1716":1,"1792":1,"2633":1}}],["spot",{"2":{"859":2,"861":1,"2531":1}}],["spots",{"2":{"859":1,"861":1}}],["sprint",{"2":{"1382":2}}],["spring",{"2":{"1007":1,"1037":1,"1064":1,"1255":1,"1257":1,"1265":1,"1269":1,"1270":1,"1277":1,"1278":1,"1279":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["spreadcheetah",{"2":{"949":1,"951":2,"969":1,"1099":1,"1792":2,"2077":2,"2652":1}}],["spread",{"2":{"860":1,"869":1,"1302":1}}],["spreadsheetml",{"2":{"772":1,"773":1,"893":1}}],["spreadsheet",{"2":{"161":1,"673":1,"951":1,"1099":1,"1202":1,"1789":1,"1792":1,"2072":1,"2077":1,"2650":1,"2652":1}}],["spinner",{"2":{"1685":1}}],["spinning",{"2":{"994":1,"1442":1}}],["spin",{"2":{"1076":1}}],["spikes",{"2":{"1164":1,"1171":1,"1180":1,"1324":1}}],["spike",{"2":{"948":1}}],["spills",{"2":{"919":1}}],["spawns",{"2":{"2157":1,"2543":1}}],["spas",{"2":{"1792":1}}],["spamming",{"2":{"1393":1}}],["spa",{"2":{"868":1,"1447":1,"1449":1,"2224":1,"2225":1,"2419":1,"2425":1,"2429":1,"2436":1,"2474":1}}],["spans",{"2":{"865":1}}],["span",{"2":{"864":1,"1429":1}}],["spanning",{"2":{"849":1,"872":1}}],["spanner",{"2":{"848":1}}],["sparc",{"2":{"848":1}}],["spatialite",{"2":{"834":1}}],["spaces",{"2":{"133":1,"277":1,"2212":1}}],["space",{"0":{"273":1},"2":{"14":1,"268":2,"687":1,"768":1,"786":2,"891":1,"1169":1,"2200":2,"2540":1,"2845":1}}],["sp",{"2":{"833":1,"836":3}}],["spell",{"2":{"1404":2}}],["speedup",{"2":{"874":1}}],["speed",{"2":{"872":1,"1353":1,"1938":1}}],["spend",{"2":{"2398":1}}],["spending",{"2":{"1328":1,"1403":1}}],["spends",{"2":{"831":1,"1079":1}}],["spent",{"2":{"851":1,"872":1,"1281":1,"1398":1,"1440":1}}],["speaker",{"2":{"1037":1,"1381":1,"1382":1}}],["speakers",{"2":{"851":1}}],["speaking",{"2":{"920":1,"1401":1}}],["speaks",{"2":{"848":2}}],["speak",{"2":{"840":1,"1179":1,"1958":1}}],["spec",{"2":{"347":1,"1039":1,"1385":1,"1386":1,"1422":1,"1773":2,"1792":1,"1824":1,"1908":1,"1911":1,"2393":1,"2419":1,"2432":1,"2481":2,"2762":1}}],["specifykind",{"2":{"1856":1,"2451":2,"2452":1,"2455":1}}],["specifying",{"2":{"397":1,"980":1,"1174":1,"1697":1,"2200":1}}],["specify",{"2":{"68":1,"144":1,"419":1,"420":1,"444":1,"526":1,"654":1,"751":1,"1052":1,"1067":1,"1142":1,"1173":1,"1217":1,"1382":1,"1394":1,"1631":1,"1632":1,"1640":2,"1642":1,"1643":1,"1707":1,"1708":1,"1717":1,"1792":3,"2047":1,"2256":1,"2259":1,"2305":1,"2322":1,"2634":1,"2635":1,"2684":2}}],["specifies",{"2":{"886":1,"934":1,"1917":1,"2814":1}}],["specified",{"0":{"654":1},"2":{"21":1,"22":1,"25":1,"46":1,"63":2,"73":1,"81":1,"113":1,"120":1,"169":1,"245":2,"518":1,"643":1,"644":1,"656":1,"669":1,"745":1,"809":1,"818":1,"934":1,"1531":1,"1532":1,"1588":1,"1608":1,"1613":1,"1654":1,"1731":1,"1764":1,"1792":6,"1858":1,"1983":1,"2047":1,"2124":1,"2140":1,"2197":2,"2251":1,"2254":1,"2264":1,"2266":1,"2272":1,"2575":2,"2634":1,"2635":1}}],["specifically",{"2":{"335":1,"841":1,"951":1}}],["specifications",{"2":{"2772":1}}],["specification",{"2":{"317":1,"1211":1,"1584":1,"1792":4,"1813":1,"1896":1,"1900":1,"2254":3,"2555":1}}],["specific",{"0":{"18":1,"289":1,"899":1,"1970":1,"2833":1},"2":{"52":1,"184":1,"186":1,"213":1,"220":1,"223":2,"286":2,"291":1,"313":1,"390":1,"641":1,"643":1,"646":2,"683":2,"748":1,"753":8,"757":11,"768":1,"776":2,"784":1,"788":1,"852":1,"868":3,"869":1,"873":1,"884":1,"892":1,"914":1,"967":2,"1048":1,"1068":1,"1075":1,"1079":1,"1100":1,"1104":1,"1105":1,"1111":1,"1141":1,"1179":1,"1185":1,"1192":1,"1230":1,"1231":1,"1313":2,"1358":2,"1445":1,"1448":1,"1530":1,"1540":1,"1544":1,"1573":1,"1599":1,"1612":1,"1613":1,"1632":1,"1644":1,"1646":1,"1740":1,"1764":1,"1771":1,"1792":12,"1801":1,"1839":1,"1856":1,"1880":1,"1881":1,"1932":1,"1958":1,"1970":1,"1992":1,"1993":1,"2020":1,"2035":1,"2036":1,"2047":2,"2063":1,"2186":1,"2193":1,"2199":1,"2201":1,"2205":1,"2207":1,"2256":1,"2257":2,"2279":1,"2288":1,"2292":1,"2293":1,"2314":1,"2412":1,"2424":1,"2437":2,"2466":1,"2468":1,"2550":1,"2581":2,"2608":1,"2635":2,"2655":1,"2722":1,"2750":1,"2776":1,"2789":1,"2790":1,"2791":1,"2792":1,"2798":1,"2827":1,"2828":1,"2833":2}}],["specialized",{"2":{"849":1}}],["specially",{"2":{"297":1,"1792":1,"2264":2}}],["special",{"0":{"33":1,"81":1,"300":1,"468":1,"950":1},"1":{"301":1,"302":1,"303":1,"951":1,"952":1,"953":1,"954":1},"2":{"32":1,"33":1,"35":1,"202":1,"297":2,"298":4,"299":1,"304":1,"315":1,"460":1,"462":1,"930":2,"956":1,"1374":1,"1386":1,"1401":2,"1480":1,"1686":2,"1688":2,"1792":3,"1854":1,"2176":1,"2180":1,"2181":1,"2189":1,"2191":1,"2255":1,"2589":5,"2595":1,"2807":1}}],["spliced",{"2":{"699":1,"2010":1,"2531":1,"2869":1}}],["splitting",{"0":{"2834":1},"2":{"928":1,"2528":1,"2827":1,"2863":1}}],["splits",{"2":{"841":1,"2391":1}}],["split",{"0":{"1431":1},"2":{"663":1,"836":1,"872":1,"874":1,"1026":3,"1414":1,"1417":1,"1437":1,"1438":1,"1792":1,"2226":1,"2228":1,"2318":1,"2369":1,"2372":1,"2482":1,"2805":1,"2815":1,"2840":1}}],["se",{"2":{"1394":1}}],["seven",{"2":{"1382":1,"2399":1}}],["severity",{"2":{"1792":2,"2108":1,"2392":2,"2536":1,"2802":1,"2880":1}}],["severe",{"2":{"1324":1}}],["severely",{"2":{"919":1}}],["several",{"2":{"267":1,"386":1,"696":1,"847":1,"849":1,"868":2,"872":1,"1011":1,"1073":1,"1254":1,"1256":1,"1266":1,"1526":1,"1746":1,"1792":1,"2098":1,"2172":1,"2178":1,"2210":1,"2347":1,"2397":1,"2533":1,"2534":1,"2614":1,"2766":1,"2767":1,"2866":1,"2871":1,"2873":1}}],["seamless",{"2":{"2266":1}}],["sea",{"2":{"913":1}}],["searching",{"0":{"2695":1}}],["searches",{"2":{"2678":1}}],["searched",{"2":{"933":1}}],["searchvalues",{"2":{"2270":1}}],["searchproductstypes",{"2":{"1571":3}}],["searchproducts",{"2":{"1043":1,"1571":3}}],["search",{"0":{"106":1,"933":1,"1070":1,"2678":1},"2":{"250":4,"324":3,"468":5,"520":4,"933":5,"934":2,"935":1,"936":1,"946":1,"1033":3,"1037":1,"1038":3,"1043":1,"1044":1,"1055":1,"1056":2,"1057":1,"1058":1,"1060":1,"1065":1,"1066":1,"1070":6,"1096":3,"1102":4,"1121":1,"1127":1,"1163":1,"1185":3,"1188":2,"1192":1,"1436":1,"1437":1,"1442":1,"1618":3,"1792":5,"1852":2,"2204":1,"2231":1,"2256":5,"2267":1,"2383":2,"2494":1,"2558":1,"2695":1,"2700":1}}],["semantics",{"0":{"690":1,"706":1},"2":{"1067":1,"1078":1,"1111":1,"1135":1,"1521":1,"1533":1,"1792":2,"2002":1,"2107":1,"2110":1,"2221":1,"2371":1,"2451":1,"2522":1,"2530":1,"2531":1,"2537":1,"2539":1,"2869":1}}],["semantically",{"2":{"666":1}}],["semantic",{"2":{"663":1,"845":1,"2453":1}}],["semicolons",{"2":{"2117":1,"2118":1,"2702":1,"2703":1}}],["semicolon",{"0":{"563":1},"2":{"560":1,"563":1,"619":1,"767":1,"786":1,"2320":1,"2340":1,"2528":1,"2863":1}}],["sequentially",{"2":{"829":1,"1394":1}}],["sequential",{"2":{"529":1,"772":1,"860":1,"1029":1,"2050":1,"2284":1,"2621":1}}],["sequence",{"0":{"1590":1},"2":{"389":1,"695":1,"1792":3,"1909":1,"2167":1,"2431":1,"2528":1,"2545":1,"2546":1,"2863":1,"2873":2,"2881":1}}],["sequences",{"2":{"340":1,"599":1,"860":1,"1079":1,"1101":1,"2270":1,"2533":1,"2867":1,"2873":1}}],["session=abc123",{"2":{"541":2,"2202":1}}],["session",{"0":{"1068":2,"1174":1,"1628":1},"2":{"453":1,"624":1,"728":1,"737":1,"805":1,"835":1,"1064":1,"1066":1,"1068":6,"1070":2,"1079":1,"1098":4,"1150":1,"1175":1,"1176":2,"1232":1,"1308":1,"1320":1,"1445":1,"1446":1,"1449":1,"1458":6,"1459":2,"1519":1,"1540":1,"1628":2,"1792":11,"1850":1,"1851":2,"1882":1,"2052":1,"2099":1,"2153":1,"2171":2,"2172":1,"2184":1,"2266":4,"2342":1,"2375":8,"2381":1,"2382":3,"2410":4,"2413":1,"2425":1,"2427":1,"2438":1,"2445":1,"2527":1,"2532":1,"2534":1,"2537":2,"2572":1,"2862":1}}],["sessionstorage",{"2":{"1684":2,"1792":2}}],["sessions",{"2":{"288":2,"623":1,"641":2,"832":1,"966":1,"1037":1,"1066":1,"1121":1,"1127":1,"1303":1,"1324":1,"1447":1,"1458":1,"1792":3,"2052":1,"2059":1,"2064":1,"2375":1,"2635":3}}],["segmented",{"2":{"944":1,"946":1}}],["segmentation",{"2":{"930":1}}],["segmentsperwindow",{"2":{"478":1,"1159":1,"1177":1,"1792":1,"1952":2,"1960":1,"2257":1,"2443":1}}],["segments",{"2":{"395":1,"928":1,"929":2,"930":3,"1159":3,"1952":2,"2286":1}}],["segment",{"2":{"387":1,"395":2,"408":1,"409":1,"652":1,"653":2,"654":1,"662":2,"927":2,"928":5,"929":8,"2372":2,"2481":1,"2665":1}}],["selfbaseurl",{"2":{"2346":2,"2372":1}}],["self",{"0":{"421":1,"445":1,"1119":1,"1398":1,"1744":1,"1929":1,"2250":1,"2345":1,"2346":1,"2347":1,"2767":1},"1":{"1399":1,"1745":1,"1746":1,"1747":1,"1930":1,"2346":1,"2347":1,"2348":1},"2":{"261":1,"266":2,"422":1,"423":2,"446":4,"480":2,"832":2,"1084":3,"1086":1,"1088":2,"1094":2,"1100":1,"1107":1,"1119":1,"1121":1,"1127":3,"1343":1,"1399":1,"1746":1,"1747":1,"1792":4,"1929":1,"1961":2,"2020":3,"2021":2,"2029":6,"2049":1,"2156":1,"2250":1,"2329":1,"2344":1,"2347":1,"2372":2,"2534":1,"2542":1,"2577":1,"2607":1,"2632":4,"2709":1,"2711":1,"2759":1,"2767":1,"2792":1,"2803":1,"2811":2}}],["selectors",{"2":{"2437":1}}],["selector",{"2":{"2423":1}}],["selects",{"2":{"693":1,"834":1,"856":1,"876":1,"1410":1,"1458":2,"1511":1,"1792":1,"2375":1,"2380":1,"2412":1}}],["selection",{"0":{"679":1},"2":{"852":1,"1044":1,"1096":1,"1101":1,"2261":1,"2445":1}}],["selective",{"2":{"286":1,"1519":1}}],["selecting",{"2":{"167":1,"1792":1,"2094":1,"2095":1,"2322":1,"2537":1,"2597":1}}],["selected",{"2":{"101":1,"2153":1}}],["select",{"2":{"7":2,"16":2,"19":1,"20":1,"30":1,"34":1,"35":3,"37":5,"38":3,"39":3,"40":2,"48":2,"50":2,"60":1,"61":2,"62":1,"71":1,"101":1,"104":2,"115":2,"116":1,"119":1,"123":1,"128":2,"136":2,"157":1,"167":1,"168":2,"175":1,"186":1,"206":2,"215":1,"230":1,"247":2,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"263":1,"264":1,"265":1,"289":1,"290":1,"291":1,"298":2,"302":1,"304":2,"308":2,"309":1,"312":2,"313":3,"322":1,"325":1,"332":1,"333":1,"334":1,"335":1,"366":1,"372":1,"373":1,"374":1,"376":1,"380":1,"383":2,"386":1,"401":1,"408":2,"415":3,"417":1,"418":1,"419":1,"420":1,"421":1,"426":2,"427":1,"428":1,"436":1,"438":3,"441":1,"442":1,"443":1,"444":1,"445":1,"451":2,"452":1,"453":1,"466":2,"467":1,"468":1,"469":1,"487":2,"488":1,"489":1,"490":1,"493":1,"503":1,"510":1,"520":2,"527":2,"531":1,"532":2,"539":2,"540":1,"541":1,"542":1,"545":1,"562":2,"563":2,"565":2,"566":3,"584":5,"585":2,"586":2,"611":1,"612":1,"613":1,"614":2,"621":1,"622":2,"623":1,"625":1,"665":1,"677":2,"679":1,"689":1,"691":2,"695":1,"700":4,"705":1,"710":1,"711":1,"715":1,"722":2,"723":1,"733":2,"734":1,"735":1,"736":1,"750":1,"764":2,"767":1,"774":2,"775":1,"787":1,"789":1,"797":2,"798":1,"799":1,"811":2,"826":2,"827":1,"834":3,"835":3,"848":4,"854":1,"886":2,"899":1,"900":1,"902":1,"903":2,"904":2,"914":2,"916":3,"918":2,"934":1,"935":1,"936":1,"956":1,"965":1,"967":1,"979":2,"980":2,"982":1,"986":1,"988":2,"989":1,"990":4,"991":2,"994":1,"1033":1,"1038":1,"1042":1,"1054":2,"1055":1,"1057":1,"1058":1,"1059":1,"1060":2,"1062":1,"1068":1,"1070":1,"1073":2,"1074":3,"1076":4,"1077":1,"1078":2,"1098":1,"1102":1,"1105":3,"1107":1,"1113":1,"1114":1,"1135":2,"1138":2,"1139":1,"1141":2,"1142":2,"1149":1,"1150":1,"1154":1,"1161":1,"1176":1,"1179":4,"1188":1,"1192":1,"1193":2,"1197":2,"1214":1,"1215":1,"1216":1,"1217":5,"1232":3,"1234":4,"1236":2,"1239":1,"1308":1,"1310":1,"1331":1,"1332":1,"1337":1,"1338":1,"1339":2,"1345":2,"1347":1,"1357":2,"1362":1,"1368":2,"1369":1,"1370":2,"1371":3,"1372":3,"1373":1,"1374":1,"1375":1,"1376":7,"1378":1,"1385":1,"1386":7,"1387":2,"1390":1,"1391":2,"1393":3,"1394":1,"1395":5,"1396":5,"1398":4,"1405":1,"1408":2,"1410":2,"1412":2,"1413":1,"1414":2,"1419":1,"1427":2,"1429":3,"1437":1,"1440":1,"1442":2,"1458":4,"1503":1,"1504":4,"1505":1,"1533":1,"1535":1,"1543":5,"1547":2,"1567":2,"1632":2,"1650":1,"1651":1,"1655":2,"1663":1,"1664":1,"1683":1,"1684":1,"1689":4,"1698":1,"1727":1,"1738":1,"1742":1,"1792":12,"1852":2,"1893":8,"1920":2,"1973":1,"1974":1,"2006":1,"2009":1,"2010":2,"2012":1,"2049":1,"2076":2,"2078":1,"2079":1,"2102":1,"2167":1,"2171":1,"2176":2,"2177":2,"2178":1,"2180":1,"2183":1,"2184":1,"2187":3,"2220":1,"2221":1,"2227":1,"2277":4,"2283":3,"2285":2,"2290":1,"2293":1,"2297":1,"2303":1,"2319":3,"2320":2,"2321":1,"2322":2,"2323":1,"2324":1,"2326":1,"2328":1,"2333":1,"2336":1,"2337":7,"2338":2,"2339":3,"2340":3,"2342":1,"2343":1,"2344":1,"2348":2,"2375":3,"2383":2,"2389":1,"2498":1,"2526":2,"2528":1,"2531":1,"2535":2,"2540":2,"2549":1,"2551":2,"2586":1,"2587":1,"2588":1,"2589":1,"2607":1,"2635":1,"2649":1,"2664":1,"2723":1,"2726":1,"2729":1,"2731":1,"2733":1,"2734":1,"2739":1,"2762":1,"2764":1,"2767":1,"2768":1,"2774":3,"2775":1,"2809":1,"2813":4,"2815":1,"2821":1,"2834":1,"2836":1,"2840":1,"2842":2,"2843":1,"2845":2,"2846":1,"2847":1,"2850":2,"2851":1,"2852":1,"2854":1,"2855":2,"2860":2,"2861":3,"2864":1,"2866":2,"2868":1,"2869":2,"2873":1,"2876":1,"2881":1}}],["serif",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["serial",{"2":{"1213":1,"1336":1}}],["serialiser",{"2":{"872":1}}],["serializable",{"2":{"864":1}}],["serialization",{"0":{"1284":1,"1286":1,"1296":1,"1592":1},"1":{"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1297":1},"2":{"576":1,"874":1,"1007":2,"1074":1,"1152":1,"1153":1,"1155":1,"1181":1,"1255":2,"1258":2,"1271":1,"1276":2,"1281":1,"1285":1,"1399":2,"1592":1,"1624":2,"1746":1,"1792":3,"1974":1,"2265":1,"2270":1,"2347":1,"2481":1,"2527":1,"2585":1,"2586":1,"2588":1,"2600":2,"2603":1,"2607":1,"2621":2,"2641":1,"2739":1,"2860":1}}],["serializing",{"2":{"851":1,"919":1,"1378":1,"1967":1}}],["serializer",{"2":{"1974":1,"2607":1}}],["serializeauthendpointsresponse",{"2":{"1469":1,"1470":1,"1792":1}}],["serializestring",{"2":{"2372":1}}],["serializes",{"2":{"1309":1,"1324":1,"1325":1,"2217":1,"2463":1,"2869":1}}],["serialized",{"2":{"330":1,"334":1,"335":2,"336":1,"337":2,"739":1,"802":1,"851":1,"861":1,"919":1,"1402":1,"1407":1,"1475":1,"1477":1,"1540":1,"1544":1,"1792":5,"1824":1,"1967":1,"1974":1,"2000":1,"2010":2,"2258":1,"2330":1,"2464":1,"2481":1,"2586":3,"2588":2,"2589":1,"2607":1}}],["serialize",{"2":{"227":1,"328":1,"851":1,"948":1,"2463":1,"2466":1,"2587":1}}],["serilog",{"2":{"1110":1,"1605":1,"1792":5,"1799":1,"1801":1,"1809":2,"2497":1,"2498":1,"2535":1,"2544":2,"2688":1,"2794":2,"2880":1}}],["seriously",{"2":{"859":1,"1385":1}}],["serious",{"2":{"852":1,"1401":1}}],["series",{"2":{"841":1,"866":1,"869":1,"1134":1,"1255":1,"1403":1}}],["serving",{"2":{"834":2,"868":1,"1086":1,"1094":3,"1138":1,"1208":1,"1354":2,"1363":1,"1365":1,"1386":1,"1406":1,"1419":1,"1420":1,"1422":1,"1754":1,"1791":1,"1798":1,"1856":1,"2032":1,"2034":1,"2135":1,"2157":1,"2223":1,"2254":1,"2539":1,"2543":1,"2546":1,"2627":1,"2857":1}}],["serviceprovidermode",{"2":{"2267":1}}],["serviceprovider",{"2":{"2267":1}}],["services",{"0":{"1328":1},"1":{"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1},"2":{"837":1,"844":1,"873":1,"947":1,"1010":1,"1013":1,"1015":1,"1026":2,"1035":4,"1036":1,"1037":1,"1066":1,"1084":1,"1088":3,"1094":1,"1098":1,"1099":1,"1104":1,"1105":1,"1108":1,"1118":1,"1127":2,"1181":1,"1209":1,"1248":1,"1255":1,"1302":1,"1322":1,"1328":1,"1329":1,"1350":1,"1351":1,"1382":1,"1423":1,"1789":1,"2175":1,"2529":1,"2580":2,"2865":1}}],["service",{"0":{"1104":1,"1334":1,"1335":1,"1431":1,"1713":1},"1":{"1105":1,"1106":1,"1107":1,"1108":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1},"2":{"412":1,"414":2,"415":3,"418":2,"420":2,"426":2,"427":2,"428":2,"429":1,"433":1,"435":1,"451":4,"453":1,"833":1,"835":1,"840":1,"855":1,"860":2,"869":1,"871":2,"873":1,"876":1,"948":1,"958":1,"968":1,"1006":1,"1008":1,"1011":2,"1027":1,"1036":1,"1037":1,"1068":1,"1084":1,"1088":1,"1099":1,"1100":1,"1101":1,"1104":1,"1105":3,"1106":1,"1107":3,"1108":2,"1115":1,"1127":1,"1181":1,"1259":1,"1277":2,"1303":1,"1328":3,"1331":1,"1332":1,"1333":2,"1334":1,"1335":5,"1338":1,"1339":1,"1342":3,"1343":1,"1345":4,"1348":1,"1349":1,"1350":1,"1382":1,"1396":2,"1401":1,"1405":2,"1431":2,"1433":2,"1457":1,"1701":1,"1713":1,"1766":1,"1767":2,"1770":1,"1782":1,"1792":8,"1800":3,"1807":3,"1808":3,"1915":1,"1917":1,"2164":4,"2165":2,"2171":1,"2300":2,"2302":1,"2303":2,"2304":1,"2306":1,"2310":1,"2313":1,"2419":1,"2549":1,"2550":1,"2554":1,"2580":1,"2633":2,"2634":3,"2717":1,"2791":1,"2804":2,"2806":1,"2809":6,"2813":4,"2815":3,"2816":2}}],["serves",{"2":{"831":1,"957":1,"959":1,"1045":1,"1073":1,"1086":1,"1127":1,"1363":1,"1822":1,"1833":1,"1911":1,"2543":1}}],["serve",{"0":{"1362":1},"1":{"1363":1},"2":{"666":1,"835":1,"1037":2,"1073":1,"1094":1,"1101":1,"1107":2,"1137":1,"1335":1,"1364":1,"1911":1,"2042":1,"2056":1,"2419":1,"2434":1,"2438":1,"2494":1,"2502":1,"2815":1}}],["served",{"2":{"214":1,"1363":1,"1385":1,"1404":1,"1412":1,"1722":1,"1743":1,"1792":6,"1828":1,"1831":1,"2040":1,"2224":1,"2474":1,"2477":1,"2481":1,"2502":1,"2626":1,"2634":1,"2635":1,"2835":1,"2868":1}}],["server2",{"2":{"2266":1}}],["serverversion",{"0":{"1819":1},"2":{"1792":1,"1814":1,"2481":1}}],["servername",{"0":{"1818":1},"2":{"1792":1,"1814":1,"2481":1}}],["serverinfo",{"2":{"1792":2,"1818":1,"1819":1,"1824":1,"2481":1}}],["serverless",{"2":{"1104":1,"1107":1}}],["serversenteventsresponseheaders",{"2":{"1792":1,"1836":1,"1857":1,"1859":1,"2249":1,"2701":1,"2835":2}}],["servers",{"0":{"1900":1},"2":{"841":1,"947":1,"948":1,"1014":1,"1049":1,"1151":1,"1174":2,"1175":1,"1176":2,"1180":1,"1621":1,"1792":6,"1833":1,"1897":1,"1898":3,"1900":1,"1907":1,"1911":1,"2254":3,"2434":1}}],["server",{"0":{"162":1,"233":1,"1045":1,"1140":1,"1141":1,"1183":1,"1302":1,"1304":1,"1420":1,"1714":1,"1825":1,"1857":1,"1978":1,"2157":1,"2166":1,"2248":1,"2481":1,"2520":1,"2543":1,"2768":1,"2827":1},"1":{"1141":1,"1142":1,"1143":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1858":1,"1859":1,"1860":1,"1861":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1,"2249":1,"2250":1,"2251":1,"2252":1,"2828":1,"2829":1,"2830":1,"2831":1,"2832":1,"2833":1,"2834":1,"2835":1,"2836":1,"2837":1,"2838":1},"2":{"112":1,"121":1,"184":1,"212":1,"215":3,"233":1,"301":1,"307":1,"317":1,"327":2,"356":1,"390":8,"395":1,"396":1,"415":1,"421":1,"436":2,"445":1,"527":3,"529":1,"534":2,"548":1,"627":1,"634":1,"635":1,"636":1,"647":1,"648":1,"649":1,"670":1,"726":1,"746":1,"754":1,"834":1,"835":1,"837":1,"848":4,"868":2,"871":1,"907":2,"918":1,"919":2,"920":1,"961":1,"970":1,"971":2,"1014":1,"1015":1,"1032":1,"1033":3,"1037":2,"1039":2,"1044":1,"1045":2,"1047":2,"1049":6,"1067":1,"1068":1,"1075":1,"1078":1,"1079":1,"1080":1,"1086":1,"1094":3,"1096":1,"1098":2,"1100":1,"1101":1,"1103":2,"1104":1,"1105":1,"1106":2,"1107":1,"1108":2,"1109":1,"1111":1,"1121":2,"1126":1,"1135":1,"1136":1,"1137":3,"1139":1,"1140":1,"1149":1,"1152":1,"1172":1,"1174":3,"1176":1,"1180":2,"1181":1,"1210":2,"1229":1,"1232":1,"1234":1,"1237":1,"1251":1,"1269":1,"1302":2,"1303":1,"1304":2,"1316":1,"1320":4,"1322":1,"1323":2,"1328":1,"1333":3,"1335":4,"1354":1,"1363":1,"1368":1,"1372":1,"1379":1,"1385":1,"1386":1,"1388":1,"1396":2,"1398":2,"1401":1,"1405":4,"1406":1,"1407":1,"1409":1,"1414":1,"1418":1,"1419":2,"1431":3,"1432":1,"1459":1,"1466":2,"1493":1,"1513":1,"1516":1,"1534":1,"1559":1,"1569":3,"1606":1,"1611":1,"1616":2,"1618":1,"1624":3,"1628":3,"1635":2,"1648":2,"1666":2,"1718":2,"1738":4,"1739":1,"1744":1,"1753":1,"1759":1,"1769":1,"1771":1,"1787":2,"1789":2,"1792":47,"1794":2,"1812":2,"1813":2,"1816":1,"1820":1,"1823":4,"1825":6,"1827":1,"1828":1,"1833":1,"1857":1,"1862":1,"1864":1,"1868":1,"1879":1,"1882":1,"1884":1,"1898":3,"1900":3,"1907":1,"1912":1,"1917":1,"1923":2,"1925":1,"1929":1,"1940":1,"1941":1,"1946":2,"1961":1,"1963":2,"1978":2,"1983":1,"1984":1,"1990":1,"1994":1,"2040":3,"2056":1,"2091":2,"2092":1,"2106":1,"2115":1,"2117":1,"2121":1,"2127":1,"2153":2,"2155":1,"2156":1,"2157":3,"2159":1,"2162":1,"2164":3,"2166":2,"2168":1,"2177":1,"2221":1,"2222":3,"2223":1,"2230":1,"2240":1,"2242":1,"2254":4,"2265":1,"2266":2,"2271":1,"2282":2,"2283":2,"2284":1,"2287":1,"2292":1,"2303":1,"2329":1,"2346":4,"2362":1,"2372":1,"2375":1,"2384":2,"2394":1,"2434":1,"2452":1,"2477":2,"2481":4,"2483":2,"2496":1,"2508":1,"2509":2,"2515":1,"2517":1,"2519":1,"2520":2,"2521":1,"2525":1,"2527":2,"2534":1,"2541":3,"2542":3,"2543":6,"2545":1,"2634":1,"2635":1,"2701":2,"2702":2,"2706":2,"2742":1,"2749":1,"2755":1,"2759":1,"2762":1,"2768":2,"2771":1,"2772":1,"2776":2,"2797":1,"2802":1,"2805":1,"2823":1,"2824":1,"2827":1,"2840":1,"2857":1,"2860":1,"2862":1,"2878":3}}],["separating",{"2":{"2063":1}}],["separation",{"2":{"848":1,"1015":1,"1385":1}}],["separators",{"2":{"2265":1}}],["separator",{"0":{"344":1,"598":1,"601":1,"602":1,"603":1,"604":1,"1968":1,"2365":1},"1":{"599":1,"600":1,"601":1,"602":1,"603":1,"604":1,"605":1,"606":1},"2":{"128":2,"129":1,"131":1,"159":3,"229":3,"339":1,"342":1,"343":1,"346":1,"489":1,"490":1,"491":1,"493":1,"494":1,"496":1,"537":1,"598":2,"599":1,"601":1,"602":1,"603":1,"604":1,"915":1,"1189":2,"1196":1,"1373":2,"1792":2,"1967":1,"1968":2,"2206":1,"2265":1,"2323":1,"2329":2,"2365":5,"2495":1,"2648":1,"2726":2}}],["separately",{"2":{"214":1,"856":1,"926":1,"927":1,"928":1,"1386":2,"1743":1,"1792":1,"1825":2,"2107":1,"2481":1,"2537":1,"2765":1,"2832":1}}],["separate",{"0":{"24":1,"562":1,"1107":1,"2628":1},"2":{"116":1,"328":1,"330":1,"378":1,"531":1,"560":1,"619":1,"656":1,"809":1,"832":1,"833":1,"845":1,"852":2,"868":1,"873":1,"874":1,"879":1,"904":1,"908":1,"946":1,"947":1,"948":1,"968":1,"971":1,"993":1,"1038":1,"1039":1,"1049":1,"1067":1,"1068":1,"1088":1,"1099":1,"1106":2,"1107":1,"1108":4,"1115":1,"1126":1,"1142":1,"1174":1,"1176":1,"1178":1,"1203":1,"1303":2,"1304":1,"1354":1,"1378":1,"1386":1,"1400":1,"1414":1,"1440":1,"1458":2,"1554":1,"1559":2,"1571":1,"1632":1,"1696":1,"1781":1,"1792":6,"2095":1,"2110":1,"2117":1,"2171":1,"2192":1,"2217":1,"2235":1,"2254":1,"2333":1,"2340":1,"2375":2,"2380":1,"2381":1,"2419":1,"2429":1,"2434":1,"2437":1,"2438":1,"2453":1,"2484":2,"2504":1,"2530":1,"2537":1,"2538":1,"2545":1,"2575":1,"2587":1,"2590":1,"2628":1,"2692":1,"2702":1,"2714":1,"2815":1,"2834":1,"2872":1}}],["separated",{"0":{"490":1},"2":{"14":1,"113":1,"490":1,"559":1,"637":1,"687":1,"704":1,"709":1,"714":1,"781":2,"809":1,"902":1,"1258":1,"1358":1,"1420":1,"1627":1,"1792":3,"2097":1,"2118":1,"2125":1,"2200":4,"2266":1,"2320":1,"2533":1,"2537":1,"2575":1,"2703":1,"2850":1,"2870":1}}],["sense",{"2":{"831":1,"843":1,"876":1,"913":1,"918":2,"961":1,"1133":1}}],["sensibility",{"2":{"913":1,"918":2}}],["sensible",{"2":{"308":1,"868":1,"1690":1,"1696":3,"2177":1,"2803":1}}],["sensitivity",{"2":{"544":1,"2493":1}}],["sensitive",{"0":{"589":1,"1068":1,"2216":1},"1":{"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"596":1,"597":1},"2":{"190":1,"199":1,"236":1,"298":3,"309":1,"312":2,"313":1,"316":1,"365":1,"366":1,"368":1,"589":2,"590":1,"592":2,"593":1,"594":1,"595":1,"868":2,"924":1,"1033":1,"1037":1,"1066":1,"1098":2,"1110":1,"1121":1,"1185":1,"1210":1,"1228":1,"1458":2,"1607":1,"1664":1,"1792":5,"1811":2,"1878":1,"2052":1,"2059":1,"2176":3,"2187":1,"2216":2,"2282":1,"2291":1,"2375":2,"2395":1,"2427":1,"2558":1,"2635":2,"2750":2,"2798":2,"2805":1}}],["sensitively",{"2":{"74":1,"2493":1,"2518":1}}],["sender",{"2":{"1326":1,"1372":2}}],["sendchatmessage",{"2":{"1318":1}}],["sendmessage",{"2":{"1317":1,"1318":2,"1320":1,"1321":2,"1326":1,"2830":1,"2836":3}}],["sends",{"2":{"383":1,"414":2,"419":1,"529":1,"936":1,"1067":1,"1255":1,"1431":1,"1792":1,"1983":1,"2174":1,"2284":1,"2305":1,"2320":1,"2348":1,"2451":1,"2830":1,"2834":1,"2836":1,"2851":1}}],["send",{"0":{"1309":1,"2813":1},"2":{"376":1,"414":1,"423":1,"427":1,"428":4,"852":1,"894":1,"948":1,"1097":1,"1106":1,"1107":1,"1305":2,"1309":3,"1317":2,"1318":1,"1321":3,"1366":1,"1410":1,"1492":1,"1757":1,"1792":6,"1848":1,"1849":2,"1856":1,"2011":2,"2019":8,"2183":1,"2302":1,"2354":2,"2452":1,"2455":1,"2632":1,"2733":1,"2768":1,"2806":1,"2828":3,"2829":3,"2830":4,"2836":3}}],["sending",{"0":{"428":1},"2":{"78":1,"84":1,"88":1,"237":1,"414":1,"621":1,"1104":1,"1304":1,"1396":1,"1489":1,"1792":1,"1848":1,"2185":1,"2247":1,"2300":1,"2453":1,"2829":1}}],["sentiment",{"2":{"1335":3,"1336":3,"1339":24,"2815":16}}],["sentence",{"2":{"841":1,"863":1}}],["sent",{"0":{"162":1,"233":1,"1302":1,"1857":1,"2248":1,"2827":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1858":1,"1859":1,"1860":1,"1861":1,"2249":1,"2250":1,"2251":1,"2252":1,"2828":1,"2829":1,"2830":1,"2831":1,"2832":1,"2833":1,"2834":1,"2835":1,"2836":1,"2837":1,"2838":1},"2":{"182":1,"184":1,"233":1,"383":1,"384":1,"386":1,"390":1,"454":1,"582":1,"587":2,"627":1,"634":1,"635":1,"636":1,"641":1,"643":1,"647":1,"648":1,"649":1,"656":10,"670":1,"726":1,"835":1,"868":1,"1037":1,"1063":1,"1100":1,"1103":2,"1302":1,"1304":1,"1323":2,"1372":1,"1664":1,"1684":1,"1792":13,"1807":1,"1820":1,"1857":1,"1858":1,"1862":1,"1864":1,"2164":1,"2240":1,"2291":1,"2292":1,"2337":2,"2348":1,"2362":1,"2363":1,"2395":1,"2425":1,"2450":1,"2451":1,"2454":1,"2483":1,"2580":1,"2701":1,"2712":1,"2805":1,"2813":1,"2827":1}}],["secondary",{"2":{"1511":1,"1515":1,"1792":1,"2274":1}}],["second",{"0":{"1090":1},"2":{"269":1,"308":1,"565":1,"614":1,"691":2,"737":1,"841":1,"855":1,"859":1,"860":2,"864":1,"865":1,"1007":1,"1029":1,"1079":1,"1080":1,"1107":2,"1152":2,"1158":1,"1160":1,"1169":1,"1283":1,"1285":1,"1370":2,"1390":1,"1395":1,"1406":1,"1409":1,"1433":1,"1543":1,"1590":2,"1740":3,"1778":1,"1792":3,"2102":1,"2106":1,"2157":1,"2184":1,"2211":1,"2212":1,"2288":3,"2339":1,"2528":1,"2532":1,"2537":1,"2543":1,"2744":1,"2850":2,"2864":1}}],["seconds",{"0":{"94":1,"138":1,"275":1},"2":{"92":1,"101":1,"133":2,"134":1,"211":2,"214":1,"268":1,"269":2,"271":1,"272":1,"273":1,"274":1,"275":4,"430":1,"455":1,"575":1,"844":1,"874":1,"967":1,"1034":1,"1071":1,"1080":1,"1138":2,"1143":1,"1152":3,"1159":1,"1160":2,"1165":1,"1254":1,"1255":2,"1259":1,"1329":1,"1337":1,"1407":1,"1418":1,"1454":1,"1464":1,"1511":1,"1520":1,"1523":1,"1589":1,"1590":4,"1616":3,"1623":1,"1639":1,"1722":1,"1731":5,"1743":1,"1763":1,"1764":1,"1769":2,"1779":1,"1780":1,"1792":18,"1837":1,"1863":1,"1951":1,"1952":1,"1991":1,"2046":1,"2047":1,"2060":2,"2067":1,"2068":1,"2094":1,"2101":1,"2156":2,"2211":2,"2212":2,"2253":4,"2308":1,"2376":1,"2380":2,"2502":1,"2537":1,"2542":2,"2549":1,"2634":2,"2635":2,"2742":1}}],["sec",{"2":{"269":1,"1090":1,"1091":1,"2211":1}}],["section",{"0":{"1603":1,"2537":1,"2705":1},"1":{"1604":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1610":1,"1611":1},"2":{"182":1,"220":1,"223":1,"347":1,"352":2,"851":1,"859":1,"996":1,"1068":1,"1105":1,"1223":1,"1400":1,"1458":1,"1459":2,"1460":1,"1566":1,"1603":1,"1609":1,"1613":1,"1617":1,"1620":1,"1622":1,"1630":1,"1676":1,"1677":1,"1787":1,"1792":12,"1794":1,"1813":1,"1825":1,"1837":1,"1898":5,"1979":1,"1981":1,"1984":1,"2092":2,"2106":2,"2120":1,"2154":1,"2160":1,"2172":1,"2253":1,"2254":6,"2255":2,"2272":1,"2279":1,"2291":1,"2350":1,"2352":1,"2358":1,"2375":4,"2378":1,"2427":1,"2432":1,"2481":1,"2533":2,"2537":1,"2541":1,"2542":1,"2551":1,"2558":2,"2661":1,"2678":5,"2695":5,"2701":1,"2705":2,"2719":1,"2724":1,"2750":1,"2802":1}}],["sections",{"0":{"1786":1},"1":{"1787":1,"1788":1,"1789":1,"1790":1,"1791":1},"2":{"158":1,"353":1,"1254":1,"2440":1,"2447":1}}],["securing",{"0":{"1194":1},"1":{"1195":1,"1196":1,"1197":1},"2":{"2838":1}}],["securityschemes",{"2":{"1792":1,"1897":1,"1898":1,"1902":1,"1903":1,"1904":1,"1905":1,"1907":1,"1911":1,"2254":1,"2434":1}}],["securityheaders",{"2":{"868":1,"1792":4,"2015":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":2,"2023":1,"2024":1,"2025":1,"2027":1,"2028":1,"2029":1,"2632":1}}],["security",{"0":{"236":1,"589":1,"921":1,"932":1,"943":1,"1100":1,"1242":1,"1441":1,"1448":1,"1564":1,"1717":1,"1788":1,"1795":1,"1901":1,"1906":1,"1983":1,"2014":1,"2020":1,"2027":1,"2057":1,"2477":1,"2490":1,"2632":1},"1":{"590":1,"591":1,"592":1,"593":1,"594":1,"595":1,"596":1,"597":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1243":1,"1244":1,"1245":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":1,"2015":1,"2016":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2022":1,"2023":1,"2024":1,"2025":1,"2026":1,"2027":1,"2028":1,"2029":1,"2030":1,"2031":1,"2058":1,"2059":1},"2":{"3":1,"190":1,"199":1,"215":1,"236":1,"298":5,"307":1,"309":1,"312":2,"313":1,"316":1,"351":1,"356":1,"368":1,"390":1,"589":2,"594":1,"638":1,"641":1,"645":1,"835":1,"868":4,"869":1,"921":3,"922":3,"932":5,"933":5,"934":2,"935":1,"936":1,"937":3,"940":1,"943":3,"945":2,"946":2,"1037":2,"1048":1,"1049":1,"1064":2,"1065":2,"1068":1,"1071":1,"1096":1,"1098":3,"1100":4,"1114":2,"1122":1,"1127":1,"1179":1,"1181":1,"1184":2,"1185":6,"1188":1,"1192":1,"1214":1,"1215":1,"1216":1,"1228":1,"1308":1,"1314":1,"1371":1,"1382":2,"1441":1,"1442":1,"1443":1,"1457":1,"1458":3,"1493":1,"1567":1,"1655":2,"1659":1,"1688":1,"1704":1,"1706":1,"1716":1,"1719":2,"1788":2,"1792":24,"1811":1,"1862":1,"1878":1,"1898":1,"1906":1,"1907":1,"1942":2,"1969":1,"1979":1,"1980":1,"1983":1,"2014":3,"2016":1,"2019":3,"2020":1,"2030":1,"2052":1,"2164":3,"2165":3,"2176":4,"2177":1,"2183":1,"2186":1,"2187":2,"2188":1,"2223":1,"2234":1,"2254":1,"2375":3,"2385":1,"2395":2,"2428":1,"2483":2,"2486":1,"2498":1,"2632":7,"2633":2,"2634":2,"2635":2,"2701":1,"2750":1,"2798":1,"2805":1,"2828":1,"2831":1}}],["secure=always",{"2":{"2428":1,"2435":1}}],["secured",{"0":{"1188":1,"2067":1},"2":{"1183":1,"1193":1,"1204":1,"1207":1,"2070":1}}],["securely",{"2":{"357":1,"362":1,"1105":1,"1457":1,"1792":3,"2632":1}}],["securezone",{"2":{"50":2}}],["secure",{"0":{"921":1,"939":1,"1352":1,"2216":1},"1":{"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":2,"941":2,"942":2,"943":2,"944":2,"945":1,"946":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1},"2":{"50":2,"215":1,"307":1,"363":1,"453":3,"527":2,"791":1,"903":1,"928":1,"944":1,"946":2,"1037":3,"1048":1,"1064":1,"1065":1,"1185":1,"1207":1,"1302":1,"1307":1,"1348":3,"1385":1,"1447":2,"1500":1,"1704":1,"1708":1,"1738":2,"1792":9,"1982":1,"2030":1,"2134":1,"2164":2,"2165":1,"2177":1,"2283":3,"2425":2,"2428":1,"2436":1,"2632":1,"2633":2,"2768":2,"2812":3}}],["secret123",{"2":{"361":3,"1195":1,"1502":1,"1733":2,"2264":2}}],["secret=",{"2":{"209":1}}],["secrets",{"0":{"942":1,"2768":1},"2":{"184":1,"186":1,"390":1,"396":1,"926":1,"1033":1,"1100":1,"1104":1,"1108":1,"1115":1,"1661":1,"1664":2,"1792":1,"1862":1,"2040":1,"2282":1,"2292":1,"2293":1,"2483":1,"2759":1,"2764":1}}],["secret",{"2":{"48":2,"186":2,"187":6,"209":2,"215":1,"390":1,"527":1,"533":1,"534":1,"700":1,"1053":1,"1059":1,"1062":2,"1098":2,"1108":1,"1126":1,"1209":1,"1453":1,"1454":1,"1457":1,"1458":3,"1460":1,"1463":1,"1464":1,"1664":6,"1690":1,"1696":1,"1697":1,"1698":2,"1738":1,"1792":10,"1862":1,"2038":1,"2040":1,"2175":2,"2230":1,"2283":2,"2286":1,"2293":2,"2294":6,"2375":4,"2477":1,"2483":1,"2554":1,"2737":1}}],["setapplicationnameinconnection",{"2":{"1617":1,"1618":1,"1619":1,"1633":1,"1792":2}}],["setfilepath",{"2":{"1366":1}}],["setfilesize",{"2":{"1366":1}}],["setfilename",{"2":{"1366":1}}],["setcontenttype",{"2":{"1366":1}}],["setuserid",{"2":{"1366":1}}],["setups",{"0":{"2876":1},"2":{"844":2,"1178":1,"1217":1,"1224":1,"1811":1,"1874":1,"2409":1}}],["setup",{"0":{"703":1,"913":1,"1199":1,"1307":1,"1581":1,"1736":1,"2112":1,"2429":1,"2532":1,"2533":1,"2715":1,"2871":1},"1":{"704":1,"705":1,"706":1,"707":1,"2716":1,"2717":1,"2718":1,"2719":1},"2":{"239":1,"694":1,"695":1,"697":1,"704":3,"705":2,"706":1,"711":1,"712":1,"715":1,"716":2,"717":1,"992":1,"1026":1,"1074":1,"1079":1,"1102":1,"1119":1,"1177":1,"1181":1,"1183":1,"1207":1,"1254":2,"1350":1,"1376":1,"1417":1,"1579":1,"1629":1,"1655":1,"1789":1,"1792":8,"1802":1,"1852":1,"2093":1,"2094":3,"2098":2,"2111":3,"2112":3,"2113":1,"2114":1,"2159":1,"2167":2,"2187":1,"2221":2,"2383":1,"2421":1,"2531":2,"2532":6,"2533":11,"2534":8,"2535":1,"2536":1,"2537":7,"2545":2,"2628":1,"2693":1,"2740":2,"2741":1,"2795":1,"2836":1,"2861":1,"2869":1,"2870":1,"2871":3,"2872":2,"2873":2,"2875":2,"2876":2,"2880":2,"2882":1}}],["settable",{"2":{"1569":2,"1792":3,"1912":1,"2520":1}}],["settimeout",{"2":{"1416":2,"2247":2}}],["settings",{"0":{"1224":1,"1225":1,"1227":1,"1447":1,"1451":1,"1454":1,"1470":1,"1475":1,"1477":1,"1489":1,"1499":1,"1511":1,"1554":1,"1588":1,"1589":1,"1604":1,"1612":1,"1617":1,"1618":1,"1623":1,"1631":1,"1639":1,"1651":1,"1670":1,"1684":1,"1696":1,"1703":1,"1722":1,"1753":1,"1764":1,"1787":1,"1794":1,"1837":1,"1873":1,"1874":1,"1875":1,"1877":1,"1898":1,"1906":1,"1917":1,"1937":1,"1949":1,"1967":1,"1978":1,"1980":1,"1992":1,"1993":1,"2000":1,"2016":1,"2034":1,"2038":1,"2047":1,"2074":1,"2086":1,"2094":1,"2115":1,"2116":1,"2117":1,"2124":1,"2125":1,"2139":1,"2695":1,"2702":1},"1":{"1228":1,"1229":1,"1230":1,"1476":1,"1478":1,"1590":1,"1613":1,"1614":1,"1615":1,"1616":1,"1617":1,"1618":2,"1619":2,"1620":2,"1621":2,"1622":1,"1623":1,"1624":1,"1625":1,"1626":1,"1627":1,"1628":1,"1629":1,"1630":1,"1631":1,"1632":1,"1633":1,"1634":1,"1635":1,"1636":1,"1685":1,"1697":1,"1874":1,"1875":1,"1876":1,"1877":1,"1878":2,"1879":2,"1880":2,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1,"2116":1,"2117":2,"2118":2,"2119":2,"2120":1,"2121":1,"2703":1,"2704":1},"2":{"100":1,"115":5,"124":1,"150":1,"153":1,"170":1,"189":1,"191":1,"217":1,"219":1,"240":1,"432":1,"457":1,"672":1,"790":1,"793":1,"937":1,"1058":2,"1102":1,"1135":6,"1141":5,"1166":1,"1182":1,"1217":1,"1232":1,"1329":1,"1380":1,"1394":1,"1395":2,"1416":1,"1444":1,"1466":1,"1468":1,"1472":1,"1475":1,"1477":1,"1482":1,"1485":1,"1496":1,"1529":1,"1536":2,"1584":1,"1589":1,"1601":1,"1604":1,"1608":1,"1611":1,"1612":1,"1630":1,"1649":1,"1680":1,"1690":2,"1693":1,"1697":1,"1700":1,"1743":1,"1749":2,"1761":1,"1783":1,"1785":1,"1787":3,"1788":2,"1790":2,"1792":18,"1794":3,"1795":2,"1797":2,"1812":1,"1825":1,"1842":1,"1855":1,"1865":1,"1914":1,"1918":1,"1922":1,"1933":1,"1934":1,"1935":1,"1978":1,"1997":1,"2013":1,"2071":1,"2082":1,"2084":1,"2088":1,"2091":1,"2115":1,"2120":1,"2121":1,"2125":1,"2135":1,"2205":1,"2256":2,"2257":1,"2377":1,"2409":1,"2542":1,"2591":2,"2645":1,"2678":1,"2689":1,"2691":1,"2693":1,"2700":1,"2701":1,"2702":1,"2706":1,"2718":1,"2750":1,"2769":1,"2841":1}}],["setting",{"0":{"624":1,"1058":1,"1138":1,"2341":1,"2354":1},"1":{"2342":1,"2343":1},"2":{"4":1,"10":1,"11":1,"143":1,"244":1,"306":2,"336":1,"446":1,"502":1,"504":2,"567":1,"584":2,"586":1,"616":1,"624":1,"655":1,"720":1,"728":1,"733":6,"734":1,"735":1,"736":3,"737":2,"745":1,"752":2,"757":2,"766":2,"803":1,"826":2,"915":1,"1057":1,"1058":6,"1098":1,"1102":2,"1132":1,"1199":1,"1224":1,"1225":1,"1226":1,"1227":1,"1372":4,"1395":2,"1447":1,"1448":1,"1449":1,"1451":1,"1454":1,"1459":1,"1464":1,"1470":1,"1471":1,"1472":1,"1474":1,"1475":2,"1477":1,"1479":1,"1489":1,"1499":1,"1511":1,"1513":1,"1540":1,"1543":7,"1544":1,"1554":1,"1555":1,"1556":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1588":1,"1589":1,"1604":1,"1606":1,"1609":1,"1618":1,"1623":1,"1631":1,"1639":1,"1645":1,"1651":1,"1658":1,"1670":1,"1672":1,"1684":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1703":1,"1706":1,"1722":1,"1753":1,"1764":1,"1792":12,"1802":1,"1803":1,"1804":1,"1805":1,"1807":1,"1808":2,"1809":1,"1837":1,"1838":1,"1840":1,"1841":1,"1843":1,"1844":1,"1846":1,"1848":1,"1850":1,"1853":1,"1856":1,"1857":1,"1858":1,"1861":1,"1862":1,"1874":1,"1875":1,"1876":1,"1877":1,"1898":1,"1906":1,"1917":1,"1918":1,"1929":1,"1937":1,"1949":1,"1951":1,"1952":1,"1953":1,"1954":1,"1967":1,"1973":2,"1980":1,"1991":1,"1994":1,"2000":1,"2010":2,"2011":1,"2016":1,"2018":1,"2034":1,"2038":1,"2047":1,"2074":1,"2075":1,"2077":1,"2086":1,"2089":1,"2094":2,"2106":1,"2109":1,"2117":1,"2118":1,"2124":1,"2125":1,"2126":1,"2127":1,"2128":1,"2130":1,"2139":1,"2154":2,"2184":8,"2201":1,"2209":1,"2252":1,"2255":1,"2256":1,"2257":1,"2267":1,"2320":1,"2323":1,"2325":1,"2330":1,"2335":1,"2337":2,"2338":3,"2350":1,"2351":1,"2353":1,"2354":2,"2375":1,"2392":1,"2406":1,"2428":1,"2455":1,"2530":1,"2537":1,"2541":1,"2572":1,"2626":1,"2648":1,"2655":1,"2659":1,"2674":1,"2677":2,"2682":1,"2689":1,"2694":1,"2695":1,"2697":1,"2702":1,"2703":1,"2719":1,"2769":1,"2771":1,"2795":1,"2814":1,"2817":1,"2829":2,"2835":1,"2841":1,"2852":1,"2855":2,"2878":1}}],["settles",{"2":{"860":1}}],["setof",{"2":{"249":1,"250":1,"520":1,"673":2,"835":1,"916":1,"979":2,"982":1,"983":1,"988":1,"1054":1,"1097":1,"1113":1,"1133":1,"1188":1,"1193":1,"1357":1,"1390":1,"1393":1,"1655":1,"1792":2,"2009":1,"2072":1,"2075":1,"2077":1,"2156":1,"2187":1,"2330":1,"2542":1,"2546":1,"2822":1,"2836":1}}],["setselfclient",{"2":{"2346":1,"2372":1}}],["sets",{"0":{"916":1,"1391":1,"1396":1},"2":{"51":2,"119":1,"277":1,"278":1,"302":1,"448":1,"453":1,"470":1,"556":1,"675":3,"720":1,"737":1,"845":1,"852":2,"857":1,"916":1,"1086":1,"1095":1,"1133":3,"1149":1,"1326":2,"1362":1,"1378":2,"1391":2,"1394":2,"1396":2,"1412":1,"1511":1,"1618":2,"1792":14,"2018":1,"2030":1,"2162":1,"2177":1,"2228":1,"2247":1,"2255":1,"2265":3,"2283":1,"2463":1,"2466":2,"2487":1,"2595":1,"2596":1,"2632":1,"2765":1,"2775":1,"2865":1}}],["set",{"0":{"48":1,"119":1,"539":1,"856":1,"1149":1,"1517":1,"1688":1,"2737":1,"2747":1},"2":{"29":1,"32":1,"35":3,"43":2,"44":1,"51":1,"54":1,"66":2,"78":1,"90":1,"91":1,"108":1,"119":2,"120":2,"123":1,"131":2,"132":1,"134":1,"141":1,"152":1,"154":1,"184":2,"218":1,"223":1,"225":2,"227":1,"230":1,"237":1,"239":1,"260":1,"281":2,"297":1,"309":1,"310":3,"327":1,"337":1,"339":1,"346":1,"378":1,"397":1,"411":1,"436":2,"463":1,"464":1,"470":1,"494":1,"496":3,"499":2,"506":1,"507":1,"514":1,"529":1,"534":1,"536":1,"541":6,"546":2,"556":1,"565":1,"579":1,"583":1,"584":2,"586":1,"592":1,"598":1,"606":1,"614":1,"622":2,"624":2,"627":1,"635":1,"646":1,"648":1,"656":1,"671":2,"673":1,"720":1,"747":4,"762":1,"766":1,"768":1,"772":1,"786":2,"812":1,"815":1,"826":3,"827":1,"841":3,"848":1,"849":1,"851":3,"852":4,"857":1,"871":1,"872":1,"876":1,"891":1,"898":1,"904":1,"917":1,"918":3,"919":1,"933":1,"934":1,"935":1,"936":1,"948":1,"949":2,"964":1,"967":1,"990":1,"992":1,"1041":2,"1045":2,"1054":1,"1055":1,"1056":3,"1057":1,"1058":1,"1060":2,"1063":1,"1067":2,"1070":7,"1076":1,"1079":1,"1084":1,"1102":6,"1108":1,"1129":2,"1130":1,"1133":1,"1138":1,"1139":1,"1150":1,"1166":1,"1169":1,"1185":1,"1188":1,"1189":1,"1192":1,"1213":1,"1216":1,"1220":1,"1222":1,"1224":1,"1226":1,"1239":1,"1252":1,"1326":1,"1338":1,"1339":2,"1372":4,"1378":1,"1385":3,"1386":1,"1390":5,"1391":1,"1394":2,"1395":2,"1396":1,"1399":1,"1402":1,"1415":1,"1416":1,"1435":1,"1447":2,"1451":1,"1454":4,"1459":1,"1475":2,"1477":2,"1493":1,"1506":2,"1508":2,"1511":7,"1517":3,"1521":1,"1522":1,"1532":1,"1535":1,"1537":1,"1540":1,"1568":1,"1571":1,"1605":3,"1608":1,"1618":2,"1620":1,"1635":1,"1644":1,"1651":2,"1655":2,"1664":1,"1671":1,"1679":1,"1686":2,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1703":1,"1711":3,"1712":1,"1764":1,"1769":1,"1792":157,"1802":1,"1822":1,"1824":4,"1825":1,"1830":1,"1833":1,"1848":1,"1849":2,"1850":3,"1851":2,"1852":2,"1858":1,"1864":2,"1874":1,"1875":1,"1876":1,"1895":1,"1898":1,"1922":1,"1923":1,"1925":1,"1958":2,"1959":1,"1967":1,"1981":1,"1997":1,"2016":3,"2038":1,"2047":1,"2049":2,"2060":1,"2072":1,"2075":2,"2094":1,"2107":1,"2108":1,"2110":1,"2117":1,"2125":1,"2129":1,"2131":1,"2157":1,"2175":2,"2184":1,"2187":1,"2202":3,"2205":1,"2208":1,"2251":3,"2252":5,"2253":1,"2254":4,"2255":10,"2256":6,"2257":7,"2258":2,"2265":12,"2266":1,"2267":1,"2272":1,"2284":1,"2292":1,"2297":1,"2320":2,"2333":1,"2336":1,"2337":3,"2338":3,"2339":1,"2342":3,"2346":1,"2350":1,"2351":1,"2353":1,"2360":1,"2366":3,"2372":3,"2375":1,"2381":1,"2382":4,"2383":4,"2392":1,"2406":1,"2411":2,"2413":1,"2416":1,"2436":1,"2445":2,"2446":1,"2450":1,"2456":1,"2465":2,"2466":1,"2470":2,"2471":1,"2481":3,"2486":1,"2494":1,"2497":2,"2498":2,"2509":1,"2517":1,"2530":1,"2531":1,"2537":3,"2539":2,"2543":1,"2551":1,"2558":1,"2565":1,"2572":4,"2577":2,"2586":1,"2632":8,"2633":1,"2634":2,"2635":4,"2649":1,"2656":1,"2682":1,"2688":3,"2689":1,"2699":1,"2702":1,"2706":1,"2718":1,"2721":1,"2724":1,"2725":1,"2734":1,"2741":1,"2749":1,"2751":1,"2763":1,"2774":1,"2794":1,"2795":2,"2797":1,"2810":1,"2820":1,"2824":2,"2825":1,"2830":1,"2831":2,"2832":2,"2838":2,"2840":1,"2841":2,"2847":1,"2851":1,"2852":1,"2855":2,"2858":1,"2866":1,"2868":2}}],["seeing",{"2":{"1799":1}}],["seem",{"2":{"1403":1}}],["seems",{"2":{"841":1,"2452":1,"2797":1}}],["seeded",{"2":{"2528":1,"2864":1,"2869":1}}],["seed",{"2":{"1047":1,"2531":1}}],["seeding",{"2":{"994":1}}],["seeddata",{"2":{"704":2}}],["seen",{"2":{"841":1,"947":1,"1385":1,"1388":1,"1392":1,"2438":1}}],["sees",{"2":{"182":1,"298":1,"636":1,"864":1,"942":1,"1043":1,"1070":2,"1078":1,"1100":1,"1384":1,"1409":1,"1664":1,"1704":3,"1792":1,"1823":1,"2283":1,"2291":1,"2527":2,"2540":1,"2633":1,"2836":1,"2845":1,"2862":2}}],["see",{"0":{"28":1,"67":1,"100":1,"111":1,"124":1,"143":1,"153":1,"191":1,"200":1,"219":1,"295":1,"432":1,"457":1,"483":1,"580":1,"672":1,"682":1,"727":1,"742":1,"793":1,"806":1,"822":1,"1467":1,"1486":1,"1508":1,"1537":1,"1551":1,"1585":1,"1602":1,"1636":1,"1667":1,"1681":1,"1750":1,"1934":1,"1964":1,"2083":1,"2136":1,"2152":1,"2188":1,"2749":1,"2751":1,"2770":1,"2797":1,"2798":1,"2816":1,"2837":1},"2":{"35":1,"75":1,"140":1,"150":1,"156":1,"182":1,"215":1,"220":1,"296":1,"298":1,"302":1,"306":1,"310":1,"319":1,"324":1,"334":1,"349":1,"387":1,"393":1,"394":1,"436":2,"480":1,"527":1,"529":1,"544":1,"650":3,"669":1,"761":1,"771":1,"779":1,"841":1,"845":1,"847":1,"848":1,"856":1,"864":1,"903":1,"911":1,"914":2,"916":2,"917":1,"918":3,"919":2,"920":4,"933":1,"946":1,"971":1,"1032":1,"1033":1,"1036":1,"1054":1,"1055":1,"1060":1,"1065":1,"1067":1,"1068":1,"1069":2,"1070":1,"1071":2,"1077":1,"1080":1,"1084":1,"1097":1,"1098":1,"1102":1,"1103":1,"1130":1,"1135":1,"1150":1,"1190":1,"1203":1,"1208":1,"1209":1,"1211":1,"1217":1,"1227":3,"1252":1,"1254":1,"1284":1,"1305":1,"1326":1,"1327":1,"1351":1,"1358":1,"1367":1,"1368":1,"1381":1,"1382":1,"1384":1,"1386":5,"1390":1,"1393":1,"1394":3,"1395":1,"1396":2,"1398":1,"1399":2,"1403":2,"1404":1,"1409":1,"1412":1,"1415":1,"1416":1,"1437":1,"1442":1,"1445":2,"1471":1,"1472":2,"1475":1,"1477":1,"1482":1,"1511":2,"1521":1,"1528":1,"1533":1,"1540":1,"1544":1,"1558":1,"1559":1,"1573":1,"1588":1,"1589":1,"1596":1,"1604":3,"1616":1,"1618":2,"1620":1,"1623":1,"1628":1,"1664":1,"1670":2,"1674":1,"1684":1,"1686":1,"1688":1,"1694":1,"1695":1,"1738":1,"1743":1,"1753":1,"1785":2,"1792":38,"1799":1,"1801":1,"1802":1,"1807":1,"1809":1,"1824":1,"1861":1,"1868":1,"1877":3,"1898":1,"1908":1,"1917":2,"1922":1,"1925":1,"1929":1,"1937":1,"1951":2,"1952":2,"1953":2,"1954":2,"1980":3,"1984":1,"2034":1,"2038":1,"2092":1,"2094":1,"2097":1,"2099":1,"2109":1,"2117":1,"2139":1,"2162":1,"2170":1,"2172":1,"2175":1,"2177":1,"2179":1,"2190":1,"2208":1,"2210":1,"2217":1,"2247":1,"2255":5,"2257":4,"2266":1,"2270":2,"2377":1,"2392":2,"2395":1,"2398":1,"2426":1,"2450":1,"2454":1,"2479":1,"2531":1,"2533":2,"2535":1,"2537":1,"2586":1,"2632":1,"2682":2,"2694":1,"2705":1,"2713":1,"2716":1,"2721":1,"2731":1,"2736":1,"2739":1,"2740":1,"2741":1,"2742":1,"2744":1,"2750":1,"2751":1,"2759":1,"2764":1,"2785":1,"2786":1,"2788":1,"2792":1,"2795":2,"2799":1,"2800":1,"2803":1,"2806":1,"2808":1,"2821":1,"2824":1,"2827":1,"2829":2,"2831":1,"2832":1,"2836":1,"2841":1,"2857":1,"2858":1,"2866":1,"2867":1,"2870":1,"2878":2}}],["stdout",{"2":{"2679":1,"2804":1}}],["stderr",{"2":{"2415":2,"2416":1,"2679":1,"2696":1}}],["std",{"2":{"1107":1}}],["steer",{"2":{"1044":1}}],["step2",{"2":{"1220":2,"1221":2,"1222":2}}],["step1",{"2":{"1220":2,"1221":2,"1222":2}}],["stepname",{"2":{"704":2,"714":2,"1792":4}}],["steps",{"0":{"1199":1,"1466":1,"1485":1,"1496":1,"1507":1,"1536":1,"1550":1,"1584":1,"1601":1,"1611":1,"1635":1,"1648":1,"1666":1,"1680":1,"1700":1,"1719":1,"1749":1,"1761":1,"1784":1,"1812":1,"1865":1,"1895":1,"1914":1,"1933":1,"1946":1,"1963":1,"1977":1,"1997":1,"2031":1,"2044":1,"2071":1,"2082":1,"2091":1,"2111":1,"2121":1,"2135":1,"2151":1,"2169":1,"2219":1,"2532":1,"2706":1,"2793":1,"2826":1,"2871":1},"2":{"239":2,"703":2,"704":3,"705":1,"707":1,"713":2,"714":2,"716":2,"834":1,"1070":1,"1079":1,"1424":1,"1792":4,"1852":1,"2093":1,"2094":4,"2111":4,"2112":2,"2167":1,"2221":1,"2383":1,"2531":1,"2532":8,"2533":6,"2534":2,"2536":2,"2537":2,"2545":2,"2740":1,"2762":1,"2870":3,"2871":4,"2872":1,"2873":1,"2874":2,"2876":1}}],["step",{"0":{"956":1,"957":1,"958":1,"1019":1,"1020":1,"1021":1,"1022":1,"1306":2,"1307":1,"1308":1,"1309":1,"1310":1,"1355":1,"1356":1,"1357":1,"1358":1,"1361":1,"1362":1,"1725":1,"1726":1,"1727":1,"2172":1,"2176":1,"2820":1,"2823":1,"2824":1,"2825":1},"1":{"1307":2,"1308":2,"1309":2,"1310":2,"1363":1,"2173":1,"2174":1,"2175":1,"2177":1,"2178":1,"2821":1,"2822":1},"2":{"238":1,"308":1,"581":1,"582":1,"587":2,"694":1,"704":1,"705":1,"706":2,"829":1,"856":1,"857":1,"861":1,"869":1,"1068":3,"1076":1,"1095":1,"1098":1,"1220":2,"1221":2,"1222":2,"1405":1,"1419":1,"1420":1,"1428":1,"1429":1,"1792":6,"2098":3,"2111":5,"2112":2,"2167":1,"2221":2,"2336":1,"2337":2,"2367":1,"2400":1,"2529":1,"2532":7,"2533":6,"2534":1,"2537":2,"2545":4,"2709":1,"2740":1,"2821":1,"2823":1,"2847":1,"2854":1,"2865":1,"2870":4,"2871":7,"2874":1}}],["stupid",{"2":{"1402":2}}],["studio",{"2":{"1086":1,"1088":1,"1094":2,"1119":1,"1123":1,"1751":1,"1757":1}}],["study",{"0":{"866":1,"877":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1},"2":{"866":1,"869":2,"876":2,"877":1,"1037":1,"1382":2,"1383":1,"1414":1,"1421":1}}],["sturdier",{"2":{"987":1}}],["stuff",{"2":{"927":1,"1385":1,"1394":1,"1402":1,"1435":1}}],["stuck",{"2":{"840":1,"1435":1,"1443":1,"2466":1}}],["sticky",{"2":{"1303":1}}],["sticks",{"2":{"695":1,"1070":1,"2867":1}}],["still",{"0":{"1205":1,"2438":1},"2":{"75":1,"105":1,"109":1,"188":1,"347":3,"362":1,"384":1,"436":1,"453":1,"587":1,"625":1,"696":1,"715":1,"829":1,"841":5,"844":1,"845":5,"848":2,"851":1,"852":1,"857":1,"859":2,"860":1,"869":3,"873":2,"911":1,"920":1,"971":1,"986":1,"1067":1,"1071":1,"1075":1,"1086":1,"1096":1,"1126":1,"1129":1,"1162":1,"1193":1,"1205":1,"1208":1,"1338":1,"1360":1,"1366":1,"1382":2,"1385":1,"1389":1,"1390":3,"1391":1,"1392":1,"1394":1,"1397":1,"1398":1,"1399":4,"1401":1,"1402":2,"1403":1,"1407":1,"1432":1,"1441":1,"1459":1,"1464":1,"1511":2,"1517":1,"1527":1,"1533":1,"1576":1,"1722":2,"1770":1,"1792":6,"1827":1,"1832":1,"1924":1,"1925":1,"1948":1,"1957":1,"1961":1,"2100":1,"2106":1,"2183":1,"2193":1,"2265":1,"2296":1,"2297":1,"2342":1,"2344":1,"2366":1,"2367":1,"2375":1,"2376":1,"2378":1,"2379":3,"2389":2,"2395":1,"2413":1,"2416":1,"2423":1,"2432":1,"2434":1,"2446":1,"2456":1,"2466":1,"2482":1,"2489":1,"2491":1,"2495":1,"2497":1,"2502":1,"2504":1,"2505":1,"2509":1,"2522":1,"2528":1,"2529":1,"2530":1,"2533":2,"2537":2,"2540":1,"2581":1,"2742":1,"2801":1,"2815":1,"2829":1,"2845":1,"2854":1,"2864":1,"2865":1,"2868":1,"2878":1}}],["stylesheets",{"2":{"2020":1}}],["styles",{"0":{"1557":1,"1755":1},"2":{"2188":1}}],["style=",{"2":{"1061":1}}],["style>",{"2":{"965":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["style>table",{"2":{"965":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["styled",{"2":{"959":1,"965":1,"2075":1,"2651":1}}],["style",{"0":{"375":1,"564":1,"1579":1},"2":{"375":1,"452":1,"845":1,"867":1,"874":1,"894":1,"1125":1,"1175":1,"1361":1,"1410":1,"1556":1,"1557":1,"1753":1,"1755":1,"1780":1,"1792":5,"1942":1,"2020":2,"2029":1,"2075":2,"2193":1,"2320":1,"2323":2,"2332":2,"2333":1,"2335":1,"2410":1,"2431":1,"2438":1,"2452":1,"2540":3,"2546":1,"2632":1,"2731":1,"2844":1,"2845":1,"2868":1,"2869":1}}],["st",{"2":{"332":3,"1184":1,"1185":1,"1973":3,"2010":2,"2587":3}}],["stolen",{"2":{"1185":1,"1867":1}}],["stock",{"2":{"865":1,"1011":1,"1038":1,"1042":5,"1044":7,"1045":3,"1439":1}}],["stopdockerpostgres",{"2":{"1792":1,"2111":1,"2532":1}}],["stopping",{"2":{"1435":1}}],["stopped",{"2":{"860":1,"2106":1,"2157":1,"2537":1,"2543":1,"2722":1}}],["stopafterfirstsuccess",{"2":{"1356":1,"1792":1,"2123":1,"2125":1,"2132":1}}],["stopwatch",{"2":{"872":1}}],["stop",{"2":{"747":2,"781":1,"848":1,"868":1,"1081":1,"1358":2,"1360":1,"1405":1,"1653":1,"1792":1,"1801":2,"2094":1,"2100":1,"2106":1,"2111":1,"2113":1,"2125":1,"2157":2,"2378":1,"2532":2,"2537":2,"2543":2,"2871":1,"2878":1}}],["stops",{"2":{"301":2,"378":1,"781":1,"818":1,"855":1,"860":1,"1068":1,"1080":1,"2002":1,"2149":1,"2371":1,"2533":1,"2543":1,"2575":1}}],["storms",{"2":{"2498":1,"2615":1}}],["stories",{"2":{"865":1,"873":1,"876":1}}],["storing",{"2":{"362":1,"364":1,"841":2,"843":1,"847":1,"851":1,"1099":1,"1210":1,"1412":1,"1516":1,"1661":1,"1664":1,"2265":1,"2266":1,"2291":1}}],["storage",{"0":{"846":1,"1353":1,"1652":1,"1653":1,"1654":1,"1655":1},"1":{"847":1,"848":1,"849":1,"1354":1,"1653":1,"1654":1,"1655":1},"2":{"189":1,"779":1,"782":1,"841":2,"847":5,"848":14,"849":4,"851":1,"859":2,"865":1,"868":1,"903":1,"921":1,"1054":3,"1064":1,"1088":2,"1098":1,"1099":3,"1101":1,"1119":1,"1126":1,"1147":1,"1180":1,"1181":1,"1211":1,"1213":1,"1244":1,"1253":1,"1352":1,"1358":2,"1363":1,"1364":1,"1365":1,"1367":2,"1403":2,"1410":1,"1437":1,"1515":1,"1650":1,"1651":3,"1653":4,"1654":1,"1655":1,"1661":1,"1662":1,"1663":4,"1664":2,"1788":1,"1792":3,"1795":1,"2164":1,"2165":1,"2222":1,"2296":1,"2297":6,"2353":2,"2565":2,"2757":2}}],["storeelement",{"2":{"1792":1}}],["storeelementcommand",{"2":{"1054":1,"1650":1,"1651":1,"1655":2,"1663":1,"1792":1,"2297":2,"2551":1}}],["storefront",{"2":{"1043":1,"2166":1}}],["store",{"0":{"902":1,"1044":1,"1988":1},"1":{"903":1},"2":{"184":5,"187":4,"327":1,"357":1,"540":1,"841":1,"847":2,"848":1,"851":7,"863":1,"888":2,"902":1,"903":1,"911":1,"1038":2,"1044":1,"1045":1,"1054":3,"1121":1,"1138":1,"1195":1,"1210":1,"1213":1,"1214":1,"1215":2,"1232":1,"1234":1,"1251":1,"1309":1,"1320":1,"1358":1,"1363":1,"1405":1,"1457":1,"1650":1,"1651":2,"1655":2,"1663":1,"1664":4,"1792":8,"1834":1,"1988":1,"2033":1,"2037":1,"2041":1,"2166":2,"2292":4,"2294":4,"2297":1,"2461":1,"2479":2,"2551":3,"2855":1}}],["stores",{"2":{"182":1,"184":1,"746":2,"749":1,"754":1,"841":2,"848":1,"902":1,"1100":1,"1210":1,"1213":1,"1338":1,"1410":1,"1430":1,"1655":1,"1664":1,"1792":1,"1868":1,"2174":1,"2291":1,"2292":1}}],["stored",{"0":{"531":1,"1210":1},"2":{"155":2,"187":1,"308":1,"309":2,"364":1,"396":1,"527":1,"534":1,"784":1,"841":2,"849":1,"874":1,"926":2,"934":1,"994":1,"1054":1,"1107":1,"1210":1,"1214":1,"1229":1,"1236":1,"1243":1,"1249":1,"1353":1,"1362":1,"1388":1,"1396":1,"1403":1,"1405":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1502":1,"1511":1,"1655":1,"1660":1,"1738":2,"1792":4,"1856":2,"1867":1,"1879":1,"1886":2,"2177":1,"2193":2,"2265":2,"2294":1,"2297":1,"2372":2,"2450":1,"2452":1,"2456":1,"2518":1,"2591":2,"2625":1}}],["story",{"2":{"1":1,"852":1,"873":1,"876":1,"1037":2,"1072":1,"1073":1,"1094":1,"1382":2,"1383":1,"1384":1,"1401":1,"1402":1,"1403":1,"1404":1,"1405":1,"2438":1}}],["str",{"2":{"1366":1}}],["struggle",{"0":{"1271":1}}],["structs",{"2":{"951":1}}],["structural",{"2":{"845":1,"851":1,"872":1,"873":1,"1041":1,"1824":1,"2359":1,"2481":1,"2662":1}}],["structurally",{"2":{"696":1,"874":1}}],["structuredcontent",{"2":{"1041":1,"1824":4,"2481":1,"2498":1}}],["structured",{"0":{"1041":1},"2":{"877":1,"903":1,"1037":1,"1110":1,"1428":1,"1432":1,"2668":1,"2829":1}}],["structures",{"0":{"850":1},"1":{"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1},"2":{"840":1,"841":1,"851":1,"852":1,"864":1,"865":1,"918":1,"1097":3,"1190":1,"1191":1,"1193":1,"2586":2,"2588":1,"2607":1}}],["structure",{"0":{"762":1,"772":1,"887":1,"888":1,"976":1,"2701":1},"2":{"587":1,"851":4,"852":2,"869":1,"878":2,"879":2,"880":2,"888":5,"910":2,"911":3,"916":2,"917":2,"918":1,"976":1,"979":1,"983":1,"986":1,"995":1,"1003":1,"1020":1,"1097":1,"1187":1,"1190":1,"1191":1,"1192":2,"1193":3,"1203":2,"1208":1,"1390":1,"1414":1,"1792":2,"1967":1,"1973":1,"2258":1,"2590":2,"2641":1,"2670":1,"2701":1}}],["strequalstoarray",{"2":{"2365":1}}],["strengths",{"2":{"1094":1,"1127":1}}],["street",{"2":{"332":3,"1973":3,"2010":3,"2587":3,"2590":1}}],["streamable",{"2":{"1039":1,"1047":1,"1792":2,"1813":1,"1817":1,"1823":1,"1824":1,"2223":1,"2481":1}}],["streams",{"2":{"650":1,"833":1,"909":1,"949":1,"1304":1,"1305":1,"1325":1,"1326":1,"1374":1,"1416":1,"1859":1,"2463":1,"2807":1}}],["streaming",{"0":{"947":1,"949":1,"2248":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"2249":1,"2250":1,"2251":1,"2252":1},"2":{"649":1,"835":1,"868":3,"869":2,"877":1,"947":1,"949":1,"968":1,"969":1,"1035":1,"1036":1,"1037":1,"1099":2,"1103":2,"1323":2,"1325":2,"1351":1,"1363":1,"1377":1,"1415":1,"1563":1,"1792":2,"1857":1,"1860":1,"1928":2,"2077":1,"2249":2,"2407":1,"2463":1,"2466":1,"2549":2,"2652":1,"2827":1,"2833":1,"2835":1}}],["streamed",{"2":{"436":1,"907":1,"1412":1,"1927":1,"2549":1,"2802":1,"2805":1,"2809":1}}],["stream",{"2":{"83":2,"86":1,"87":2,"88":1,"636":1,"650":2,"663":1,"669":2,"869":1,"913":1,"949":1,"971":1,"1037":1,"1103":1,"1305":1,"1327":1,"1366":2,"1573":1,"2372":1,"2391":1,"2463":1,"2466":1,"2498":1,"2771":1,"2806":1,"2807":1,"2809":1,"2827":2,"2828":1,"2830":2,"2835":1,"2836":1}}],["strongly",{"2":{"1020":1,"2223":1,"2296":1,"2490":1}}],["strong>auth",{"2":{"1061":1}}],["strong>",{"2":{"996":1,"1061":1}}],["strong>$",{"2":{"996":1}}],["strongest",{"2":{"921":1,"975":1}}],["stronger",{"0":{"834":1,"835":1},"2":{"308":1,"872":1,"876":1,"2486":1}}],["strong",{"2":{"307":1,"834":1,"857":1,"859":1,"873":1,"874":1,"1204":1,"1792":1,"2177":1}}],["stripped",{"2":{"2519":1,"2558":1,"2880":1}}],["stripe",{"2":{"2438":1}}],["strips",{"2":{"2365":1}}],["strip",{"2":{"845":1,"860":1,"1340":1,"1428":1,"2451":1}}],["stricter",{"2":{"975":1,"1162":1}}],["strictly",{"2":{"841":1,"869":1,"2528":1,"2863":1}}],["strict",{"0":{"1134":1,"1983":1},"2":{"195":1,"871":1,"1037":1,"1111":2,"1134":1,"1163":1,"1402":1,"1447":1,"1792":6,"1980":1,"1983":1,"2015":1,"2016":1,"2019":3,"2027":1,"2029":1,"2426":1,"2427":1,"2428":1,"2436":1,"2590":1,"2632":5}}],["stringbuilderpool",{"2":{"2614":1}}],["stringbuilders",{"2":{"2403":1}}],["stringbuilder",{"0":{"2403":1},"2":{"2236":1,"2397":1,"2604":1,"2614":4,"2622":1}}],["stringified",{"2":{"952":1}}],["stringify",{"2":{"938":1,"1107":1,"1320":1,"1342":1,"1523":1,"1792":1,"2380":1}}],["stringlength",{"2":{"869":1}}],["string>>",{"2":{"2359":1}}],["string>",{"2":{"340":1,"599":1}}],["strings",{"0":{"1613":1,"1627":1,"2589":1},"1":{"1614":1,"1615":1,"1616":1},"2":{"226":1,"253":1,"458":1,"558":1,"919":2,"948":1,"963":1,"1070":1,"1097":1,"1102":2,"1176":1,"1460":1,"1611":1,"1612":1,"1615":1,"1621":1,"1627":1,"1769":1,"1787":1,"1792":14,"1794":1,"1854":2,"1856":4,"1864":1,"1924":1,"1967":1,"1974":1,"2007":1,"2060":1,"2077":2,"2121":1,"2149":1,"2224":2,"2265":1,"2266":3,"2270":1,"2277":1,"2328":1,"2375":1,"2394":1,"2421":1,"2450":1,"2451":2,"2453":1,"2454":3,"2455":2,"2486":1,"2509":1,"2534":1,"2540":1,"2546":2,"2555":1,"2586":1,"2588":1,"2589":3,"2595":2,"2603":1,"2607":1,"2634":1,"2635":1,"2662":1,"2823":1,"2824":1,"2845":1,"2871":1,"2875":1}}],["string",{"0":{"256":1,"423":1,"458":1,"466":1,"467":1,"520":1,"523":1,"553":1,"1616":1,"1925":1,"2204":1,"2270":1,"2304":1,"2517":1,"2674":1},"1":{"459":1,"460":1,"461":1,"462":1,"463":1,"464":1,"465":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"472":1},"2":{"75":2,"78":1,"87":1,"150":1,"165":1,"188":1,"212":1,"226":2,"258":1,"306":1,"322":1,"378":1,"379":2,"385":1,"387":1,"388":1,"390":1,"408":3,"409":1,"414":3,"415":1,"423":2,"436":3,"446":1,"448":1,"452":2,"454":1,"458":3,"459":1,"460":3,"462":3,"463":3,"464":3,"466":4,"467":2,"468":3,"469":2,"515":3,"516":1,"517":3,"518":1,"520":2,"523":1,"524":1,"526":1,"528":1,"529":1,"551":3,"553":1,"558":1,"616":1,"696":1,"723":3,"747":6,"748":5,"762":3,"768":2,"772":4,"773":1,"776":5,"788":3,"816":1,"841":2,"845":1,"872":1,"894":8,"920":17,"930":2,"938":10,"952":2,"961":3,"995":13,"996":5,"1021":1,"1023":1,"1024":8,"1026":11,"1067":3,"1070":1,"1086":1,"1102":2,"1173":1,"1176":1,"1181":1,"1193":4,"1220":3,"1221":1,"1222":1,"1241":1,"1255":1,"1258":1,"1279":1,"1317":3,"1321":1,"1326":2,"1342":3,"1366":8,"1376":1,"1386":4,"1395":1,"1408":7,"1413":3,"1416":3,"1417":1,"1427":1,"1431":3,"1447":7,"1451":3,"1454":8,"1470":1,"1471":5,"1472":4,"1474":3,"1475":2,"1477":3,"1479":2,"1481":1,"1489":3,"1499":3,"1511":7,"1514":1,"1516":1,"1521":3,"1523":4,"1526":1,"1540":2,"1544":2,"1553":2,"1554":1,"1555":1,"1556":1,"1558":4,"1559":2,"1560":2,"1561":2,"1564":1,"1567":10,"1569":4,"1570":6,"1571":2,"1573":1,"1574":2,"1575":1,"1581":2,"1582":1,"1588":1,"1604":2,"1605":1,"1616":2,"1618":2,"1623":1,"1631":1,"1651":10,"1670":1,"1671":3,"1684":11,"1696":6,"1722":6,"1753":6,"1759":2,"1764":5,"1792":52,"1802":5,"1803":1,"1804":2,"1805":2,"1807":3,"1809":1,"1817":1,"1818":2,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1828":1,"1829":1,"1830":1,"1831":1,"1837":2,"1838":4,"1840":1,"1841":1,"1844":1,"1846":2,"1848":5,"1849":1,"1852":2,"1853":3,"1854":3,"1855":4,"1857":1,"1862":1,"1864":2,"1874":3,"1875":3,"1877":3,"1890":1,"1898":9,"1906":7,"1917":4,"1918":6,"1924":4,"1925":5,"1937":1,"1949":2,"1951":2,"1952":2,"1953":2,"1954":2,"1967":1,"1974":2,"1994":1,"2000":6,"2003":1,"2016":8,"2034":3,"2038":3,"2040":2,"2047":10,"2056":2,"2075":3,"2077":4,"2094":8,"2098":2,"2117":4,"2124":3,"2125":4,"2126":1,"2127":2,"2128":3,"2130":6,"2140":1,"2148":1,"2154":1,"2175":1,"2182":1,"2204":2,"2222":4,"2239":1,"2247":4,"2255":4,"2256":6,"2257":2,"2258":4,"2265":5,"2266":2,"2267":1,"2270":3,"2273":4,"2277":2,"2278":1,"2283":1,"2284":1,"2296":1,"2302":2,"2303":1,"2304":1,"2309":1,"2321":1,"2330":2,"2333":2,"2339":1,"2357":2,"2359":5,"2376":1,"2377":1,"2380":2,"2383":2,"2394":1,"2398":1,"2399":1,"2431":4,"2436":4,"2461":1,"2462":1,"2476":2,"2481":1,"2482":2,"2491":1,"2495":1,"2497":1,"2509":1,"2510":1,"2511":3,"2513":1,"2517":2,"2518":1,"2519":2,"2521":1,"2523":1,"2528":1,"2534":1,"2539":1,"2549":1,"2558":1,"2572":1,"2575":2,"2586":1,"2589":2,"2590":6,"2595":4,"2596":4,"2600":1,"2603":1,"2607":3,"2611":7,"2614":4,"2622":7,"2635":1,"2648":3,"2665":3,"2666":1,"2674":1,"2687":1,"2688":1,"2691":1,"2702":4,"2717":1,"2718":1,"2764":1,"2811":1,"2812":2,"2823":4,"2824":1,"2825":2,"2833":1,"2848":1,"2863":1,"2871":1}}],["strap",{"2":{"1435":1,"1436":1}}],["strange",{"2":{"1075":1}}],["strawman",{"2":{"852":1}}],["strategies",{"0":{"1136":1,"1151":1,"1154":1,"1597":1,"1599":1},"1":{"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1152":1,"1153":1,"1154":1,"1155":1},"2":{"575":1,"577":2,"578":1,"580":1,"849":1,"1037":1,"1109":1,"1153":1,"1154":3,"1171":1,"1177":1,"1182":3,"1250":1,"1367":1,"1586":1,"1587":1,"1588":3,"1597":2,"1598":1,"1599":1,"1600":1,"1792":3,"2273":1}}],["strategy",{"0":{"569":1,"572":1,"573":1,"1354":1,"1589":1},"1":{"570":1,"571":1,"572":1,"573":1,"574":1,"575":1,"576":1,"577":1,"578":1,"579":1,"580":1,"1590":1},"2":{"142":1,"231":1,"569":2,"570":8,"572":1,"574":1,"575":3,"1113":1,"1133":2,"1154":4,"1179":1,"1182":1,"1217":1,"1224":1,"1303":1,"1353":1,"1382":1,"1588":3,"1589":1,"1591":1,"1599":3,"1600":1,"1602":2,"1792":1,"1874":1,"2824":1,"2825":1}}],["stray",{"2":{"389":1}}],["straightforward",{"2":{"663":1,"943":1,"1024":1,"1050":1,"1087":1,"1096":1,"1318":1,"1398":1,"1405":1}}],["straight",{"2":{"3":1,"436":1,"837":1,"949":1,"1043":1,"1431":1,"1436":1,"2462":1,"2806":1,"2807":1,"2813":1}}],["stalled",{"2":{"2466":1}}],["stalls",{"2":{"2398":1}}],["staleness",{"0":{"854":1}}],["stale",{"2":{"844":1,"852":1,"872":1}}],["staging",{"2":{"1900":2,"2117":1,"2702":1}}],["stage",{"2":{"1420":1}}],["stakeholders",{"2":{"1382":1}}],["staple",{"2":{"1078":1}}],["starvation",{"2":{"2088":1}}],["starve",{"2":{"1162":1}}],["starving",{"2":{"1156":1}}],["star",{"2":{"920":1}}],["startdockerpostgres",{"2":{"1792":1,"2111":1,"2532":1}}],["starter",{"2":{"1106":1}}],["started",{"0":{"1252":1,"1380":1,"2162":1},"2":{"851":1,"852":1,"1400":2,"1401":2,"1402":2,"1403":2,"1404":1,"1792":1,"2116":1,"2117":1,"2119":1,"2365":1,"2414":1,"2440":1,"2701":1,"2702":1,"2704":1,"2772":1,"2823":1,"2824":2,"2825":1}}],["startswith",{"2":{"2365":1}}],["starts",{"2":{"382":2,"390":1,"989":1,"1107":1,"1164":1,"1171":1,"1175":1,"1233":1,"1419":1,"1422":1,"1433":1,"1792":2,"1847":1,"1883":1,"2208":1,"2334":2,"2505":1,"2543":1,"2760":1,"2868":1,"2875":1}}],["starting",{"0":{"2027":1},"2":{"245":1,"421":1,"422":1,"423":1,"445":1,"446":2,"646":1,"658":1,"659":1,"917":1,"918":1,"975":1,"1086":1,"1101":1,"1152":1,"1169":1,"1209":1,"1252":1,"1398":1,"1405":1,"1608":1,"1624":1,"1792":1,"1868":1,"1929":1,"2062":1,"2197":1,"2272":1,"2527":1,"2529":1,"2577":1,"2635":1,"2749":1,"2797":1,"2819":1,"2824":1,"2825":1,"2862":1}}],["start",{"0":{"1082":1,"2012":1,"2818":1,"2861":1},"1":{"2819":1,"2820":1,"2821":1,"2822":1,"2823":1,"2824":1,"2825":1,"2826":1},"2":{"243":1,"409":1,"771":1,"970":1,"997":1,"1047":1,"1166":1,"1179":3,"1207":1,"1272":1,"1377":1,"1378":1,"1384":2,"1385":1,"1386":1,"1393":1,"1401":1,"1402":2,"1405":2,"1406":2,"1443":1,"1621":1,"1775":1,"1792":1,"2020":1,"2110":1,"2111":1,"2160":1,"2162":1,"2168":1,"2191":1,"2192":1,"2413":1,"2529":1,"2530":1,"2532":1,"2534":1,"2741":1,"2789":1,"2793":1,"2823":2,"2825":2,"2871":1}}],["startupmessage",{"2":{"1792":1,"2116":1,"2117":2,"2119":1,"2701":1,"2702":1,"2704":1}}],["startups",{"2":{"1403":1}}],["startup",{"0":{"1460":1,"2119":1,"2394":1,"2704":1,"2751":1,"2754":1},"2":{"102":1,"109":3,"174":1,"214":1,"320":1,"388":1,"390":1,"395":1,"582":1,"583":1,"584":1,"587":3,"696":1,"829":1,"868":5,"1043":1,"1071":1,"1073":1,"1150":2,"1157":1,"1178":1,"1181":1,"1368":1,"1376":1,"1378":1,"1382":1,"1386":3,"1406":1,"1408":2,"1420":1,"1422":1,"1449":2,"1464":1,"1521":1,"1527":2,"1604":1,"1605":2,"1609":2,"1618":1,"1621":1,"1743":1,"1787":1,"1792":12,"1794":1,"1802":1,"1822":1,"1825":1,"1844":1,"1862":1,"1875":1,"1948":1,"1957":1,"1961":1,"1974":4,"2007":3,"2038":1,"2040":1,"2098":1,"2117":1,"2119":2,"2225":2,"2226":1,"2245":1,"2261":1,"2318":1,"2321":1,"2324":1,"2328":2,"2336":1,"2337":4,"2372":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":4,"2389":1,"2394":1,"2399":1,"2409":1,"2410":1,"2412":1,"2414":1,"2415":1,"2416":1,"2428":2,"2441":1,"2442":1,"2476":1,"2481":1,"2483":1,"2486":1,"2492":1,"2497":3,"2502":1,"2534":1,"2537":1,"2607":4,"2659":2,"2688":2,"2702":1,"2704":2,"2705":1,"2719":1,"2722":1,"2754":1,"2758":1,"2776":1,"2795":1,"2840":4,"2841":1,"2845":1,"2854":2,"2871":1,"2881":1}}],["standby",{"2":{"1174":5,"1175":1,"1628":5,"1792":6,"2266":6}}],["stand",{"2":{"876":1,"2531":1}}],["standing",{"2":{"860":1,"863":1,"2450":1}}],["standaloneregistrationoptionspath",{"2":{"1792":1}}],["standalone",{"0":{"2389":1},"2":{"515":1,"851":1,"1084":1,"1096":1,"1220":2,"1224":1,"1385":1,"1792":2,"1840":1,"1870":1,"1874":1,"2226":1,"2335":1,"2388":1,"2389":2,"2431":1,"2711":1,"2714":1,"2716":1,"2772":1}}],["standards",{"2":{"1049":1}}],["standardized",{"2":{"848":1,"2255":1}}],["standard",{"0":{"2788":1},"2":{"168":1,"537":1,"632":1,"835":1,"841":2,"848":2,"857":1,"860":1,"918":1,"927":1,"1015":1,"1037":1,"1054":1,"1075":1,"1098":3,"1103":1,"1111":2,"1113":1,"1126":1,"1139":1,"1177":2,"1247":1,"1323":1,"1325":3,"1394":2,"1399":1,"1405":1,"1435":1,"1445":1,"1453":1,"1457":1,"1458":1,"1599":1,"1778":1,"1792":1,"2002":1,"2094":1,"2102":1,"2155":1,"2157":1,"2190":1,"2193":2,"2207":1,"2245":1,"2319":1,"2357":1,"2371":1,"2375":1,"2529":1,"2543":1,"2554":2,"2581":1,"2607":1,"2686":1,"2693":1,"2710":1,"2763":1,"2792":1,"2842":1,"2865":1}}],["staff",{"2":{"644":2,"1042":1,"1045":1,"2199":1,"2200":3}}],["stackalloc",{"2":{"2604":1}}],["stackexchangeredis",{"2":{"2386":1,"2567":1}}],["stackexchange",{"2":{"1067":1,"1514":1,"1792":2,"2386":1}}],["stacks",{"2":{"873":1,"875":1,"1006":1,"1366":1}}],["stack",{"0":{"1006":1},"1":{"1007":1,"1008":1,"1009":1},"2":{"421":1,"445":1,"831":1,"837":1,"868":1,"869":2,"871":1,"872":8,"873":1,"874":1,"876":3,"945":1,"1005":1,"1006":2,"1009":1,"1065":1,"1078":1,"1084":1,"1086":1,"1094":1,"1169":1,"1382":1,"1398":1,"1409":2,"1416":1,"1423":1,"1746":1,"1792":2,"1845":2,"1929":1,"2242":1,"2347":1,"2384":1,"2385":1,"2394":1,"2604":1,"2663":1,"2767":1}}],["stampedes",{"2":{"1180":1}}],["stampede",{"0":{"2462":1},"2":{"214":1,"1101":2,"1147":3,"1177":1,"1180":1,"1181":1,"1430":1,"1511":1,"1515":4,"1743":1,"1792":4,"2222":1,"2224":1,"2274":3,"2459":2,"2461":1,"2462":1,"2495":1,"2502":1,"2765":1}}],["stable",{"0":{"1129":1},"2":{"175":2,"179":1,"180":1,"663":2,"666":1,"669":1,"684":2,"686":1,"687":2,"836":1,"1037":1,"1079":1,"1128":1,"1129":4,"1385":1,"1792":1,"2098":1,"2534":1,"2607":1,"2710":1,"2795":1,"2858":1,"2871":1}}],["stat",{"2":{"874":1,"966":4,"967":1,"1619":1,"1620":1,"1792":8,"2045":4,"2049":1,"2050":1,"2051":1,"2052":1,"2635":12}}],["statistics",{"0":{"966":1,"2635":1},"1":{"967":1},"2":{"857":1,"860":2,"861":1,"916":1,"966":1,"967":1,"1100":2,"1127":1,"1784":1,"1791":1,"1792":6,"2045":1,"2047":4,"2049":1,"2062":1,"2234":1,"2635":10}}],["static=anonymous",{"2":{"1069":1}}],["staticfiles",{"0":{"2476":1},"2":{"937":1,"998":1,"1792":1,"2033":1,"2035":1,"2037":1,"2040":1,"2042":1,"2224":1,"2371":2,"2476":1,"2483":1,"2551":1,"2627":1,"2701":1}}],["static",{"0":{"677":1,"678":1,"960":1,"972":1,"981":1,"1502":1,"2032":1,"2626":1},"1":{"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":2,"983":2,"984":2,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"2033":1,"2034":1,"2035":1,"2036":1,"2037":1,"2038":1,"2039":1,"2040":1,"2041":1,"2042":1,"2043":1,"2044":1},"2":{"96":1,"212":1,"278":1,"479":1,"532":1,"534":1,"542":2,"834":2,"868":1,"972":1,"974":1,"976":2,"981":1,"996":2,"998":3,"1037":1,"1069":1,"1086":2,"1094":3,"1096":1,"1118":2,"1121":1,"1127":3,"1138":1,"1162":2,"1328":1,"1329":1,"1351":1,"1363":2,"1364":1,"1368":1,"1386":1,"1405":1,"1407":1,"1409":3,"1417":1,"1420":1,"1422":1,"1504":1,"1738":1,"1791":2,"1792":12,"1798":2,"1825":1,"1955":2,"1957":1,"1960":1,"2032":1,"2034":2,"2035":1,"2037":1,"2038":3,"2040":2,"2042":1,"2135":2,"2164":2,"2165":3,"2224":1,"2235":1,"2258":2,"2285":1,"2372":1,"2379":3,"2381":2,"2399":2,"2474":1,"2476":1,"2477":1,"2532":1,"2534":1,"2621":1,"2626":1,"2627":1,"2840":1}}],["state=",{"2":{"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1697":1,"1792":5}}],["stateless",{"2":{"1450":1,"2174":1}}],["stated",{"2":{"857":1,"1134":1}}],["states",{"2":{"847":2,"922":1,"2393":1,"2496":1,"2868":1}}],["state",{"0":{"842":1},"1":{"843":1,"844":1,"845":1},"2":{"841":1,"843":14,"844":14,"845":3,"849":1,"851":3,"852":6,"854":1,"855":3,"865":1,"868":1,"1067":1,"1078":1,"1081":1,"1303":1,"1351":1,"1382":1,"1487":1,"1596":1,"1696":1,"1792":6,"2052":1,"2094":1,"2099":1,"2103":1,"2107":1,"2398":1,"2438":1,"2492":1,"2525":1,"2532":1,"2533":2,"2537":1,"2881":1}}],["statements",{"0":{"586":1,"1389":1},"2":{"384":1,"559":1,"586":1,"587":2,"622":1,"624":2,"646":1,"650":1,"656":2,"668":1,"694":1,"823":1,"826":1,"829":2,"874":1,"1103":1,"1121":1,"1130":1,"1302":1,"1304":1,"1325":1,"1370":1,"1378":1,"1389":2,"1394":1,"1419":1,"1560":1,"1792":1,"1844":1,"1857":1,"1858":2,"2011":2,"2164":1,"2221":1,"2318":1,"2319":1,"2320":2,"2321":3,"2323":1,"2337":2,"2338":2,"2342":1,"2354":1,"2391":1,"2528":1,"2529":1,"2531":2,"2540":1,"2545":1,"2546":1,"2731":1,"2774":1,"2828":1,"2832":1,"2840":2,"2845":1,"2850":1,"2851":1,"2853":1,"2863":1,"2865":1,"2869":1}}],["statement",{"0":{"562":1},"2":{"30":1,"560":3,"562":1,"563":1,"581":2,"582":2,"583":2,"584":1,"586":1,"587":3,"614":1,"615":1,"618":1,"619":3,"625":2,"694":1,"704":1,"706":1,"709":1,"714":1,"826":1,"829":1,"848":1,"849":1,"854":1,"860":1,"982":1,"1080":1,"1309":1,"1378":1,"1394":1,"1674":1,"1792":4,"1802":1,"2005":1,"2006":2,"2011":1,"2099":1,"2104":1,"2111":4,"2318":1,"2319":2,"2320":2,"2321":2,"2323":1,"2324":1,"2330":1,"2337":2,"2338":1,"2339":1,"2340":4,"2343":1,"2354":1,"2372":1,"2527":1,"2528":6,"2531":1,"2532":2,"2533":1,"2535":1,"2536":1,"2545":3,"2546":1,"2795":1,"2800":1,"2827":1,"2840":1,"2841":1,"2842":1,"2850":1,"2852":2,"2853":1,"2854":2,"2858":1,"2862":1,"2863":3,"2864":3,"2870":1,"2871":2,"2874":2,"2880":2}}],["stats=true",{"2":{"374":2}}],["stats",{"0":{"967":1,"2045":1,"2049":2,"2050":2,"2051":2,"2052":1,"2674":1},"1":{"2046":1,"2047":1,"2048":1,"2049":1,"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1,"2070":1,"2071":1},"2":{"95":1,"374":3,"646":1,"868":6,"869":1,"966":5,"967":4,"1100":5,"1143":1,"1278":1,"1338":1,"1339":1,"1398":9,"1784":1,"1791":1,"1792":13,"2045":1,"2046":5,"2047":8,"2054":1,"2055":1,"2056":3,"2058":2,"2059":3,"2060":1,"2061":3,"2062":1,"2063":5,"2064":5,"2066":1,"2067":1,"2068":1,"2069":1,"2164":1,"2165":1,"2635":17,"2638":3,"2674":4}}],["statusmessage",{"0":{"2470":1},"2":{"1157":1,"1792":4,"1948":1,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1958":6,"1959":1,"1960":1,"2224":1,"2257":1,"2468":1,"2470":5,"2471":1,"2472":1,"2551":1}}],["statustext",{"2":{"894":2,"1366":2}}],["statuscolumnname",{"2":{"300":1,"1240":1,"1469":1,"1471":1,"1483":1,"1792":5,"1889":1}}],["statuscode",{"0":{"2470":1},"2":{"140":1,"817":1,"1026":4,"1111":3,"1157":1,"1669":5,"1671":1,"1672":1,"1673":4,"1678":7,"1792":14,"1948":1,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1958":5,"1960":1,"2138":1,"2141":1,"2142":4,"2144":3,"2145":2,"2146":8,"2148":1,"2224":1,"2253":1,"2255":13,"2257":1,"2446":1,"2468":1,"2470":4,"2472":1,"2575":6}}],["status=active",{"2":{"521":1}}],["status=",{"2":{"101":1}}],["status",{"0":{"301":1,"313":1,"1958":1},"1":{"1959":1},"2":{"33":3,"35":6,"39":4,"41":1,"197":1,"206":1,"207":1,"208":1,"209":2,"210":3,"213":2,"247":4,"297":5,"299":1,"300":3,"301":2,"313":3,"378":1,"415":1,"424":1,"439":3,"447":3,"449":4,"452":1,"480":1,"540":1,"551":3,"565":3,"614":3,"689":1,"691":2,"700":2,"705":1,"710":1,"719":1,"720":2,"747":1,"748":2,"763":1,"818":1,"819":2,"829":1,"835":2,"843":1,"849":9,"852":1,"860":1,"887":1,"894":12,"903":2,"938":4,"995":11,"996":4,"1016":1,"1019":2,"1021":4,"1024":1,"1026":5,"1031":3,"1044":1,"1074":1,"1078":1,"1104":1,"1105":6,"1111":4,"1193":2,"1197":1,"1214":3,"1215":1,"1220":2,"1221":2,"1222":1,"1232":4,"1234":4,"1236":4,"1237":3,"1240":1,"1255":1,"1317":1,"1332":2,"1335":2,"1338":3,"1339":3,"1341":3,"1342":5,"1355":1,"1357":1,"1359":1,"1360":2,"1361":1,"1366":11,"1386":4,"1398":2,"1399":1,"1408":6,"1409":2,"1410":7,"1415":2,"1416":2,"1426":1,"1427":2,"1431":2,"1469":1,"1471":2,"1480":2,"1483":1,"1504":2,"1526":2,"1553":1,"1558":4,"1567":6,"1569":1,"1575":2,"1670":1,"1671":1,"1674":1,"1676":1,"1677":1,"1684":3,"1686":2,"1688":1,"1689":3,"1721":1,"1722":3,"1725":1,"1732":3,"1736":3,"1740":2,"1741":1,"1742":3,"1743":1,"1764":1,"1766":3,"1782":1,"1792":40,"1824":2,"1855":3,"1882":3,"1884":3,"1886":3,"1887":3,"1889":1,"1916":1,"1918":3,"1921":3,"1922":3,"1924":1,"1926":3,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"2093":2,"2109":3,"2110":3,"2128":1,"2141":1,"2149":1,"2176":1,"2180":1,"2247":5,"2255":8,"2264":6,"2271":1,"2273":2,"2278":1,"2283":3,"2288":2,"2289":3,"2290":3,"2303":1,"2307":1,"2320":3,"2333":1,"2339":3,"2357":1,"2359":4,"2360":1,"2380":3,"2384":1,"2468":1,"2470":1,"2472":3,"2526":1,"2530":3,"2537":2,"2549":11,"2562":2,"2575":1,"2580":1,"2596":3,"2618":2,"2634":4,"2732":1,"2733":1,"2739":1,"2762":4,"2763":3,"2764":1,"2765":1,"2766":2,"2769":2,"2774":2,"2809":7,"2810":8,"2813":1,"2814":1,"2815":3,"2848":1,"2860":1,"2861":1,"2866":3,"2876":1}}],["stays",{"0":{"324":1},"2":{"308":1,"351":1,"383":1,"390":1,"834":1,"836":1,"876":1,"946":1,"951":1,"1039":1,"1045":1,"1068":1,"1405":1,"1416":1,"1431":1,"1504":1,"1689":1,"1792":2,"1854":1,"1911":1,"2348":1,"2432":1,"2481":2,"2528":1,"2535":1,"2537":1,"2595":1,"2795":1,"2863":1}}],["stay",{"2":{"3":1,"390":1,"845":1,"952":2,"1008":1,"1015":1,"1143":1,"1402":1,"1459":1,"1792":2,"1854":1,"1924":1,"1925":1,"2040":1,"2107":1,"2224":1,"2375":1,"2380":1,"2456":1,"2520":1,"2535":1,"2595":1,"2869":1}}],["sqlfileparameterformatter",{"2":{"2372":1}}],["sqlfile",{"2":{"1792":3,"2111":2,"2372":1,"2531":1,"2532":4,"2534":1,"2545":1,"2871":1,"2874":1}}],["sqlfilesource",{"0":{"2165":1,"2166":1,"2317":1,"2352":1,"2354":1,"2539":1},"1":{"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1},"2":{"324":1,"336":1,"567":1,"624":1,"1135":2,"1408":1,"1792":4,"1836":2,"1999":1,"2001":1,"2010":1,"2011":1,"2012":1,"2095":2,"2106":2,"2112":1,"2153":1,"2221":1,"2228":1,"2330":1,"2342":1,"2352":2,"2354":1,"2364":1,"2369":2,"2532":1,"2537":3,"2538":1,"2539":1,"2543":2,"2722":1,"2821":1,"2825":1,"2841":1,"2861":1}}],["sqlconnection",{"2":{"1593":2,"1792":2}}],["sqlclient",{"2":{"1593":1,"1792":1}}],["sqlserver",{"2":{"1593":1,"1792":1}}],["sqlstate",{"2":{"1111":2,"2528":2,"2864":2}}],["sqlsqlgrant",{"2":{"2755":1}}],["sqlsqlwith",{"2":{"1429":1}}],["sqlsqlbasic",{"2":{"1195":1}}],["sqlsqlbegin",{"2":{"1079":1,"2531":1,"2739":1,"2855":1,"2868":1,"2869":1}}],["sqlsqluser",{"2":{"992":1}}],["sqlsqlupdate",{"2":{"849":1}}],["sqlsqlalter",{"2":{"967":1,"982":1,"2049":1}}],["sqlsqlset",{"2":{"933":1}}],["sqlsqlselect",{"2":{"834":1,"860":1,"918":2,"1078":1,"2110":1,"2326":1,"2340":1,"2528":1,"2530":1,"2758":1,"2852":1,"2864":1,"2866":1}}],["sqlsqldo",{"2":{"930":1,"986":1,"1419":1,"1442":1,"2528":1,"2864":1}}],["sqlsqlif",{"2":{"896":1}}],["sqlsqlinsert",{"2":{"308":1,"898":1,"903":1,"1051":1}}],["sqlsqlreturn",{"2":{"885":3}}],["sqlsqlcomment",{"2":{"8":1,"22":1,"23":1,"24":1,"49":1,"75":1,"83":1,"84":1,"85":1,"86":1,"94":1,"95":1,"96":1,"97":1,"105":1,"118":1,"129":1,"138":1,"147":1,"148":1,"149":1,"174":1,"180":1,"184":1,"186":1,"195":1,"196":1,"212":1,"214":1,"215":1,"251":1,"252":1,"277":1,"278":1,"314":1,"320":1,"323":1,"324":1,"325":1,"342":1,"343":1,"344":1,"352":1,"353":1,"392":1,"393":1,"394":1,"402":1,"403":1,"476":1,"477":1,"478":1,"479":1,"501":1,"502":1,"527":1,"531":1,"532":1,"553":1,"554":1,"555":1,"572":1,"573":1,"574":1,"601":1,"602":1,"603":1,"604":1,"631":1,"632":1,"633":1,"641":1,"642":1,"643":1,"644":1,"645":1,"660":1,"661":1,"662":2,"678":1,"686":1,"724":1,"732":1,"758":1,"767":1,"775":1,"783":1,"785":1,"787":1,"789":1,"800":1,"817":1,"828":1,"899":1,"900":1,"902":1,"917":1,"949":1,"957":1,"964":1,"965":1,"1017":1,"1030":1,"1033":1,"1067":1,"1068":1,"1111":1,"1138":2,"1142":1,"1143":1,"1158":1,"1161":1,"1176":1,"1189":1,"1196":1,"1312":1,"1314":1,"1315":1,"1430":1,"1531":2,"1532":1,"1533":1,"1726":1,"1730":3,"1738":1,"1747":1,"1855":1,"1929":1,"2079":1,"2181":1,"2185":1,"2194":1,"2201":1,"2202":2,"2207":1,"2214":1,"2215":1,"2216":1,"2217":1,"2218":1,"2255":1,"2285":1,"2292":1,"2293":1,"2305":1,"2306":1,"2483":1,"2502":1,"2575":1,"2587":1,"2596":1,"2649":1,"2651":1,"2652":1,"2653":2,"2655":1,"2656":1,"2664":1,"2765":1,"2768":1,"2824":1}}],["sqlsqlcreate",{"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"38":1,"39":1,"40":1,"48":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"104":1,"115":1,"116":1,"117":1,"119":1,"128":1,"136":1,"137":1,"157":1,"184":1,"186":2,"207":1,"208":1,"209":1,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"288":1,"289":1,"290":1,"291":1,"292":1,"298":1,"308":2,"309":1,"310":1,"312":1,"313":1,"322":1,"332":1,"333":1,"334":1,"335":1,"351":1,"360":1,"361":1,"365":1,"366":1,"374":1,"386":1,"401":1,"405":1,"406":1,"408":2,"415":1,"423":1,"426":1,"427":1,"428":1,"436":1,"438":1,"439":1,"449":1,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"584":1,"592":1,"593":1,"594":1,"611":1,"646":1,"658":1,"677":1,"679":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"750":1,"751":1,"752":1,"755":1,"756":1,"760":1,"765":1,"766":1,"770":1,"777":1,"797":1,"798":1,"799":1,"811":1,"812":1,"813":1,"814":1,"815":1,"835":1,"864":1,"883":1,"884":1,"886":1,"904":2,"913":1,"914":1,"915":1,"916":1,"918":1,"956":1,"982":1,"1020":1,"1021":1,"1029":1,"1054":1,"1058":1,"1068":1,"1105":3,"1138":1,"1139":1,"1141":1,"1142":1,"1149":1,"1187":1,"1188":1,"1192":2,"1197":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1308":1,"1309":1,"1310":1,"1321":1,"1332":1,"1336":1,"1337":1,"1338":1,"1339":1,"1347":1,"1348":1,"1357":1,"1362":1,"1368":1,"1387":1,"1390":1,"1393":1,"1398":3,"1399":1,"1426":1,"1427":1,"1431":1,"1504":1,"1547":1,"1567":1,"1632":1,"1655":1,"1689":1,"1725":1,"1727":1,"1742":1,"1743":1,"1744":1,"1745":1,"1920":1,"1921":1,"1926":1,"1968":1,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2147":1,"2176":1,"2177":1,"2186":1,"2187":3,"2264":1,"2283":1,"2290":1,"2292":1,"2293":2,"2303":1,"2304":1,"2549":2,"2575":1,"2580":2,"2586":1,"2587":1,"2588":1,"2589":1,"2607":1,"2665":2,"2763":1,"2764":1,"2766":1,"2767":1,"2775":1,"2802":1,"2803":1,"2809":1,"2810":1,"2812":1,"2813":1,"2815":1,"2822":1,"2829":1,"2836":3,"2868":1}}],["sqlsql",{"2":{"7":1,"9":1,"16":1,"17":1,"34":1,"35":1,"37":2,"48":1,"61":1,"71":1,"104":1,"115":1,"128":1,"136":1,"157":1,"167":1,"168":2,"169":2,"175":1,"184":1,"187":1,"206":2,"211":1,"213":1,"214":1,"247":1,"263":1,"264":1,"265":1,"288":1,"298":1,"302":1,"308":1,"312":1,"325":1,"351":1,"360":1,"372":1,"373":1,"375":1,"376":1,"378":2,"380":1,"382":1,"383":2,"401":1,"415":1,"417":1,"418":1,"419":1,"420":1,"421":1,"438":1,"441":1,"442":1,"443":1,"444":1,"445":1,"451":1,"466":1,"487":1,"503":1,"510":1,"520":1,"522":1,"539":1,"562":1,"563":1,"564":1,"565":1,"566":1,"577":1,"584":1,"585":2,"586":1,"592":1,"612":1,"613":1,"614":1,"621":1,"622":1,"623":1,"641":1,"659":1,"664":1,"665":1,"677":1,"689":1,"691":1,"694":1,"695":1,"700":1,"704":1,"705":1,"709":1,"710":1,"711":1,"714":1,"715":1,"722":1,"733":1,"750":1,"764":1,"774":1,"797":1,"811":1,"826":1,"827":1,"834":1,"835":1,"885":1,"888":3,"897":1,"905":1,"914":1,"915":1,"916":2,"924":1,"925":1,"926":1,"928":1,"929":1,"934":1,"935":1,"936":1,"938":1,"941":1,"960":1,"977":1,"979":1,"980":1,"988":1,"989":1,"990":1,"991":1,"992":1,"994":1,"1019":2,"1032":1,"1034":1,"1038":1,"1042":1,"1045":1,"1050":1,"1055":1,"1056":1,"1057":1,"1060":1,"1073":2,"1074":1,"1076":1,"1078":1,"1105":1,"1113":1,"1114":1,"1135":1,"1138":1,"1141":1,"1142":1,"1150":1,"1154":2,"1161":1,"1163":1,"1176":1,"1179":1,"1191":1,"1193":4,"1203":1,"1213":1,"1307":1,"1313":1,"1316":1,"1331":1,"1345":1,"1346":1,"1355":1,"1358":1,"1368":1,"1369":1,"1370":1,"1371":3,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1386":4,"1387":1,"1391":1,"1394":1,"1395":2,"1396":1,"1398":5,"1405":1,"1408":2,"1410":1,"1412":1,"1413":1,"1414":2,"1458":1,"1504":1,"1543":1,"1547":1,"1567":1,"1599":1,"1632":1,"1664":2,"1689":1,"1727":1,"1731":1,"1733":1,"1736":1,"1740":1,"1920":1,"1924":1,"1930":1,"2006":1,"2009":1,"2010":2,"2012":1,"2076":1,"2097":1,"2147":1,"2176":1,"2178":1,"2193":2,"2196":1,"2199":1,"2200":1,"2204":1,"2205":1,"2206":1,"2212":2,"2264":2,"2277":1,"2286":1,"2288":1,"2294":1,"2314":1,"2319":1,"2320":1,"2321":1,"2322":2,"2323":1,"2332":1,"2333":2,"2334":1,"2335":1,"2337":3,"2338":1,"2339":2,"2340":1,"2342":1,"2343":1,"2344":1,"2346":2,"2348":2,"2391":1,"2432":1,"2526":1,"2529":1,"2533":1,"2537":1,"2540":1,"2549":2,"2572":1,"2581":1,"2591":1,"2726":1,"2731":1,"2733":1,"2762":1,"2774":2,"2821":1,"2833":1,"2834":1,"2842":1,"2845":1,"2846":1,"2847":1,"2848":1,"2849":1,"2850":1,"2854":1,"2860":1,"2861":1,"2865":1,"2869":1,"2873":1,"2876":1}}],["sqlapi",{"2":{"1043":1,"1408":1,"1409":2}}],["sqlite",{"2":{"834":1,"837":1,"848":1}}],["sqlpage",{"0":{"831":1,"834":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1},"2":{"831":3,"832":1,"833":2,"834":2,"835":6,"836":2,"837":2,"838":3}}],["sql",{"0":{"238":1,"265":1,"308":1,"372":1,"377":1,"379":1,"383":1,"612":1,"659":1,"665":1,"831":1,"941":1,"981":1,"986":1,"1072":1,"1078":1,"1105":1,"1113":1,"1209":1,"1231":1,"1247":1,"1368":2,"1378":1,"1384":1,"1386":1,"1387":1,"1406":1,"1431":1,"1736":1,"1881":1,"1998":1,"2165":1,"2167":1,"2333":1,"2336":1,"2348":2,"2356":1,"2357":1,"2358":1,"2526":1,"2540":1,"2712":1,"2722":1,"2729":1,"2731":1,"2750":1,"2774":1,"2798":1,"2821":1,"2839":1,"2858":1,"2869":1},"1":{"378":1,"379":1,"380":1,"381":1,"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"982":1,"983":1,"984":1,"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":2,"1233":2,"1234":2,"1235":2,"1236":2,"1237":2,"1238":2,"1239":2,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1369":2,"1370":2,"1371":2,"1372":2,"1373":2,"1374":2,"1375":2,"1376":2,"1377":2,"1378":2,"1379":2,"1380":2,"1385":1,"1386":1,"1387":1,"1388":2,"1389":2,"1390":2,"1391":2,"1392":2,"1393":2,"1394":2,"1395":2,"1396":2,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":1,"1404":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1999":1,"2000":1,"2001":1,"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2840":1,"2841":1,"2842":1,"2843":1,"2844":1,"2845":1,"2846":1,"2847":1,"2848":1,"2849":1,"2850":1,"2851":1,"2852":1,"2853":1,"2854":1,"2855":1,"2856":1,"2857":1,"2858":1,"2859":1},"2":{"1":1,"7":4,"16":4,"18":1,"19":1,"20":1,"21":1,"30":4,"37":5,"38":2,"39":2,"40":1,"48":4,"50":1,"60":1,"61":4,"62":1,"71":4,"72":1,"74":1,"104":4,"115":4,"116":1,"117":1,"119":1,"128":4,"136":4,"137":1,"157":4,"165":3,"167":4,"168":6,"170":4,"173":1,"175":1,"179":1,"184":4,"206":5,"212":1,"215":2,"220":4,"238":2,"239":2,"241":1,"247":4,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"263":1,"264":1,"265":3,"288":4,"289":1,"290":1,"291":1,"296":1,"297":2,"298":5,"307":2,"308":2,"309":1,"312":4,"320":2,"322":1,"324":1,"325":3,"332":1,"333":1,"334":1,"335":1,"338":2,"347":1,"351":4,"360":4,"361":1,"365":1,"366":1,"369":1,"372":3,"374":1,"376":3,"377":2,"378":1,"383":1,"384":2,"385":1,"386":1,"388":1,"395":4,"396":1,"401":4,"405":1,"406":1,"408":3,"415":3,"417":3,"418":3,"419":3,"420":3,"421":3,"436":1,"438":4,"441":3,"442":3,"443":3,"444":3,"445":3,"448":1,"451":2,"453":1,"458":1,"463":1,"464":1,"466":4,"467":1,"468":1,"469":1,"487":4,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":4,"510":4,"511":1,"520":4,"521":1,"523":1,"527":3,"528":3,"529":3,"534":1,"539":4,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"559":3,"562":2,"563":2,"565":2,"566":2,"567":1,"568":2,"582":1,"587":3,"588":2,"592":4,"593":1,"594":1,"611":1,"612":2,"613":2,"614":3,"615":1,"618":1,"621":2,"622":2,"623":2,"624":1,"625":1,"626":2,"641":3,"659":1,"665":4,"677":4,"679":1,"684":1,"689":1,"693":1,"694":2,"698":1,"703":2,"704":1,"705":2,"706":1,"708":1,"709":1,"711":2,"713":2,"714":1,"722":4,"723":1,"733":4,"734":1,"735":1,"736":1,"750":3,"764":1,"774":1,"786":1,"788":1,"797":4,"798":1,"799":1,"811":4,"829":2,"831":4,"832":4,"833":6,"834":5,"835":6,"836":1,"837":2,"838":3,"841":2,"848":11,"849":1,"852":2,"856":1,"857":2,"859":6,"860":6,"861":3,"867":3,"868":4,"869":2,"871":6,"872":2,"873":5,"874":3,"875":1,"876":8,"877":1,"878":1,"881":2,"886":1,"891":1,"892":1,"904":1,"910":2,"911":5,"914":2,"915":1,"916":3,"918":5,"920":3,"924":2,"928":1,"929":1,"934":2,"935":2,"936":2,"938":1,"941":1,"945":2,"946":3,"947":1,"956":1,"966":1,"968":2,"971":3,"975":4,"976":4,"977":1,"979":2,"980":2,"982":1,"985":2,"986":3,"988":3,"990":2,"993":1,"994":4,"1003":1,"1006":2,"1009":1,"1010":2,"1012":1,"1013":1,"1016":1,"1021":1,"1027":1,"1033":1,"1036":4,"1037":16,"1038":2,"1043":3,"1044":1,"1046":1,"1047":1,"1049":3,"1050":1,"1052":1,"1054":3,"1055":2,"1056":2,"1057":2,"1058":1,"1060":1,"1064":8,"1065":3,"1067":1,"1068":1,"1070":3,"1073":10,"1074":2,"1075":1,"1076":3,"1077":2,"1078":4,"1079":2,"1080":3,"1082":4,"1084":3,"1086":6,"1094":4,"1095":5,"1096":9,"1098":1,"1100":1,"1101":1,"1102":11,"1104":1,"1105":2,"1106":1,"1108":1,"1109":1,"1111":3,"1113":5,"1121":3,"1125":5,"1126":4,"1127":4,"1135":12,"1138":4,"1139":1,"1141":4,"1142":4,"1149":1,"1150":3,"1154":5,"1161":3,"1176":3,"1179":8,"1182":2,"1188":1,"1192":1,"1208":3,"1209":1,"1211":4,"1217":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1231":1,"1240":1,"1244":1,"1247":2,"1252":2,"1253":1,"1302":1,"1308":1,"1309":1,"1310":1,"1321":1,"1322":1,"1327":3,"1328":1,"1331":1,"1333":1,"1337":1,"1345":2,"1350":2,"1351":4,"1352":1,"1357":1,"1362":1,"1366":4,"1367":3,"1368":7,"1369":1,"1370":4,"1371":6,"1372":2,"1373":1,"1374":3,"1375":1,"1376":3,"1377":4,"1378":9,"1379":3,"1380":4,"1382":4,"1383":1,"1384":1,"1385":8,"1386":20,"1387":2,"1388":2,"1389":2,"1390":4,"1391":2,"1392":2,"1393":2,"1394":8,"1396":4,"1397":1,"1398":17,"1399":2,"1401":6,"1403":10,"1404":2,"1405":15,"1406":4,"1407":3,"1408":11,"1409":3,"1410":4,"1412":3,"1413":3,"1414":4,"1416":1,"1419":2,"1422":1,"1423":1,"1424":1,"1429":1,"1431":1,"1432":1,"1435":1,"1442":1,"1443":1,"1458":6,"1504":4,"1516":1,"1547":4,"1567":5,"1576":1,"1581":2,"1624":2,"1632":4,"1655":3,"1664":5,"1689":4,"1727":4,"1738":1,"1789":4,"1792":50,"1799":1,"1802":2,"1836":1,"1838":4,"1850":2,"1852":4,"1868":2,"1869":1,"1870":1,"1871":1,"1872":1,"1881":1,"1889":1,"1894":1,"1920":4,"1923":1,"1961":1,"1967":1,"1970":1,"1972":2,"1973":3,"1974":1,"1975":1,"1998":1,"2000":4,"2001":1,"2002":1,"2003":10,"2004":5,"2005":2,"2007":5,"2009":2,"2010":8,"2011":8,"2012":6,"2013":2,"2076":4,"2078":1,"2079":1,"2092":2,"2094":1,"2095":7,"2098":1,"2104":1,"2106":2,"2111":8,"2112":1,"2113":1,"2114":1,"2125":1,"2147":3,"2153":3,"2155":1,"2157":2,"2159":2,"2160":1,"2164":4,"2165":27,"2166":2,"2167":5,"2169":1,"2170":1,"2171":3,"2176":5,"2177":4,"2179":1,"2182":1,"2183":1,"2184":3,"2185":1,"2186":1,"2187":4,"2188":1,"2190":4,"2221":5,"2228":3,"2265":1,"2277":4,"2282":1,"2283":1,"2284":2,"2292":1,"2317":3,"2318":4,"2319":4,"2320":2,"2321":1,"2322":6,"2323":4,"2325":1,"2326":1,"2327":3,"2328":4,"2329":1,"2330":4,"2332":1,"2333":4,"2336":1,"2337":2,"2338":2,"2339":3,"2340":1,"2342":1,"2344":2,"2348":5,"2350":1,"2354":8,"2356":1,"2357":2,"2358":4,"2365":1,"2366":8,"2367":2,"2371":14,"2372":2,"2375":3,"2383":5,"2388":1,"2394":3,"2420":1,"2423":1,"2438":2,"2459":1,"2479":1,"2481":2,"2482":2,"2493":1,"2496":1,"2504":1,"2509":1,"2512":1,"2522":1,"2525":5,"2526":4,"2527":1,"2528":2,"2529":3,"2531":7,"2532":11,"2533":2,"2534":6,"2535":5,"2536":1,"2537":5,"2538":5,"2539":5,"2540":4,"2541":4,"2543":4,"2545":2,"2546":1,"2549":1,"2572":1,"2586":1,"2587":1,"2588":1,"2589":1,"2597":2,"2607":1,"2614":2,"2628":1,"2665":1,"2671":1,"2706":1,"2709":2,"2712":5,"2713":1,"2714":1,"2722":1,"2723":1,"2725":1,"2727":1,"2729":2,"2731":1,"2732":1,"2734":1,"2739":3,"2742":1,"2751":1,"2760":1,"2762":2,"2764":1,"2767":1,"2768":1,"2771":2,"2772":4,"2773":2,"2774":8,"2775":1,"2785":1,"2793":1,"2795":4,"2799":1,"2802":1,"2803":1,"2806":1,"2809":3,"2813":4,"2817":1,"2820":2,"2821":7,"2822":2,"2824":1,"2825":4,"2826":6,"2829":2,"2834":4,"2836":2,"2837":1,"2839":3,"2840":7,"2841":4,"2842":3,"2843":1,"2844":1,"2845":4,"2846":2,"2848":2,"2849":2,"2850":4,"2855":1,"2856":3,"2857":2,"2858":4,"2859":4,"2860":6,"2861":8,"2862":1,"2863":1,"2864":1,"2865":1,"2869":4,"2870":1,"2871":5,"2872":3,"2873":5,"2874":2,"2878":4,"2880":2,"2882":1}}],["s",{"0":{"867":1,"927":1,"1049":1,"1256":1,"1274":1,"1275":1,"1276":1,"1406":1,"2390":1,"2438":1,"2733":1},"1":{"928":1,"929":1,"930":1,"1257":1,"1258":1,"1259":1,"1260":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1},"2":{"1":4,"22":1,"45":1,"74":1,"75":1,"101":1,"105":3,"108":4,"109":1,"168":2,"174":1,"206":1,"212":1,"215":1,"245":1,"252":1,"269":1,"273":1,"298":1,"308":1,"309":1,"319":2,"320":1,"322":1,"376":1,"384":1,"390":1,"393":1,"414":1,"415":1,"419":1,"424":1,"428":1,"429":1,"436":1,"439":1,"453":3,"454":2,"458":1,"480":1,"528":1,"582":1,"586":1,"587":1,"650":3,"664":1,"665":1,"666":1,"668":3,"669":4,"683":1,"684":1,"693":2,"694":1,"698":1,"701":1,"704":1,"709":1,"714":2,"747":1,"768":1,"773":1,"831":2,"834":4,"835":2,"836":3,"838":1,"840":2,"841":10,"843":2,"844":1,"845":1,"847":2,"848":5,"849":2,"851":6,"852":6,"855":1,"856":1,"857":1,"859":1,"860":2,"861":1,"863":2,"864":2,"865":1,"866":1,"867":1,"868":1,"869":1,"871":3,"872":3,"873":2,"876":6,"877":1,"878":1,"879":1,"880":1,"885":2,"886":1,"891":1,"905":1,"907":2,"913":1,"918":1,"921":1,"926":2,"927":2,"933":1,"934":1,"936":1,"944":1,"946":1,"948":4,"949":4,"954":1,"958":1,"961":1,"968":1,"972":1,"973":1,"975":1,"981":1,"982":1,"986":1,"987":1,"988":1,"990":1,"994":2,"995":1,"997":1,"1005":1,"1007":1,"1010":1,"1021":1,"1023":1,"1037":3,"1039":1,"1040":2,"1042":2,"1043":1,"1044":5,"1045":4,"1048":2,"1054":1,"1056":1,"1058":1,"1059":1,"1060":2,"1064":1,"1067":2,"1068":1,"1070":3,"1073":3,"1074":4,"1076":1,"1078":3,"1079":2,"1080":1,"1081":1,"1084":2,"1090":4,"1094":1,"1096":1,"1098":5,"1101":1,"1102":3,"1103":1,"1104":1,"1105":5,"1111":2,"1122":1,"1125":1,"1126":2,"1127":1,"1137":1,"1138":1,"1147":1,"1150":2,"1156":1,"1162":1,"1165":1,"1169":1,"1172":1,"1179":1,"1180":1,"1185":1,"1188":1,"1190":1,"1192":1,"1208":1,"1209":2,"1210":2,"1211":2,"1214":1,"1231":1,"1241":1,"1254":1,"1262":2,"1263":1,"1264":6,"1265":5,"1266":1,"1268":5,"1269":21,"1270":13,"1272":7,"1274":1,"1275":2,"1276":2,"1279":2,"1280":2,"1281":2,"1283":1,"1287":15,"1288":15,"1289":15,"1290":15,"1291":15,"1293":15,"1295":15,"1297":15,"1299":15,"1301":15,"1302":1,"1303":1,"1305":2,"1309":3,"1320":1,"1324":1,"1325":2,"1326":2,"1327":2,"1328":1,"1329":1,"1332":1,"1347":1,"1358":1,"1366":1,"1368":1,"1369":1,"1371":1,"1372":1,"1373":1,"1378":1,"1382":4,"1383":1,"1384":3,"1386":5,"1387":1,"1391":1,"1392":1,"1394":3,"1396":1,"1397":2,"1398":2,"1399":2,"1401":1,"1402":1,"1404":2,"1405":1,"1406":2,"1408":2,"1412":1,"1414":1,"1416":1,"1419":1,"1420":1,"1421":1,"1422":1,"1423":2,"1427":1,"1428":1,"1429":1,"1431":2,"1432":2,"1435":3,"1436":1,"1439":2,"1441":1,"1447":3,"1450":1,"1458":1,"1459":1,"1460":4,"1515":1,"1521":1,"1523":3,"1525":1,"1527":2,"1528":1,"1533":1,"1574":3,"1605":1,"1618":1,"1620":1,"1626":1,"1633":1,"1651":1,"1653":1,"1658":1,"1687":2,"1704":2,"1713":1,"1727":1,"1754":1,"1792":43,"1821":1,"1822":2,"1823":1,"1824":5,"1825":9,"1827":2,"1828":1,"1830":1,"1851":1,"1856":3,"1862":1,"1868":1,"1890":1,"1910":1,"1948":1,"1958":1,"1961":8,"1974":1,"1995":1,"2039":1,"2094":2,"2096":1,"2098":1,"2099":1,"2103":1,"2104":1,"2107":1,"2108":1,"2109":2,"2110":3,"2111":1,"2125":1,"2128":1,"2129":1,"2131":1,"2142":3,"2146":3,"2164":1,"2171":2,"2177":1,"2183":1,"2186":2,"2195":2,"2197":1,"2208":1,"2211":1,"2222":1,"2257":1,"2266":1,"2274":1,"2297":1,"2300":1,"2303":1,"2305":1,"2307":1,"2310":1,"2318":1,"2320":1,"2321":1,"2324":1,"2336":1,"2346":1,"2359":1,"2364":1,"2371":1,"2375":6,"2376":1,"2378":1,"2380":6,"2381":1,"2382":1,"2389":1,"2391":1,"2392":1,"2395":3,"2398":11,"2407":1,"2411":2,"2412":1,"2421":2,"2422":2,"2423":1,"2424":1,"2425":1,"2426":1,"2427":2,"2428":1,"2432":1,"2434":1,"2435":1,"2436":1,"2437":1,"2438":4,"2442":2,"2450":2,"2451":4,"2452":2,"2462":1,"2465":2,"2466":3,"2470":2,"2481":7,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2508":1,"2509":1,"2518":1,"2519":1,"2520":1,"2525":1,"2526":2,"2527":3,"2528":2,"2529":2,"2530":6,"2531":8,"2533":4,"2534":2,"2535":5,"2537":3,"2539":1,"2540":1,"2543":2,"2544":1,"2545":2,"2546":2,"2575":3,"2576":1,"2586":2,"2588":1,"2607":1,"2633":2,"2645":1,"2664":1,"2688":1,"2721":1,"2725":1,"2729":1,"2734":1,"2739":2,"2740":1,"2741":1,"2752":1,"2759":1,"2760":3,"2762":2,"2763":1,"2774":1,"2776":1,"2790":1,"2794":1,"2798":1,"2802":1,"2803":1,"2805":1,"2807":1,"2809":2,"2812":1,"2813":6,"2823":1,"2824":3,"2825":2,"2827":1,"2830":1,"2833":4,"2834":3,"2835":1,"2840":1,"2845":1,"2847":1,"2848":1,"2855":1,"2860":3,"2861":1,"2862":3,"2864":2,"2865":1,"2866":2,"2868":1,"2869":2,"2871":1,"2872":1,"2874":1,"2878":1,"2881":1}}],["soon",{"2":{"2393":1}}],["soft",{"0":{"1326":1},"2":{"1326":1}}],["software",{"2":{"1":1,"841":1,"845":1,"847":1,"851":2,"861":1,"1075":1,"1402":1,"1403":3,"1405":1}}],["socket",{"2":{"1320":20,"1322":1}}],["sockets",{"2":{"1254":1}}],["social",{"2":{"1123":1}}],["sophisticated",{"2":{"1147":1}}],["solid",{"2":{"965":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["solution",{"0":{"975":1,"1016":1,"1192":1,"1330":1},"1":{"1331":1,"1332":1},"2":{"851":4,"927":1,"989":1,"1108":1,"1122":1,"1382":1,"2868":1}}],["sole",{"2":{"844":1}}],["solely",{"2":{"168":1,"2481":1,"2482":1}}],["solvable",{"2":{"841":1}}],["solves",{"2":{"844":1,"975":1,"2868":1}}],["solved",{"2":{"841":1,"851":5,"852":1,"859":1}}],["solve",{"2":{"841":1,"851":2,"918":1,"1394":1,"1403":1,"1404":1,"2282":1}}],["sorted",{"2":{"1283":1,"1285":1}}],["sort",{"2":{"188":1,"860":1,"861":1,"874":1,"1133":1,"1391":1,"1400":1,"1401":2,"2296":1}}],["someunknown",{"2":{"2415":1}}],["somefunctionname",{"2":{"876":1}}],["someone",{"2":{"860":1,"861":1,"971":1,"982":1,"1401":1}}],["somebody",{"2":{"852":1}}],["somewhere",{"2":{"848":1,"873":1,"1075":1,"1401":1,"2392":1,"2450":1}}],["somehow",{"2":{"840":1,"2424":1}}],["something",{"2":{"832":1,"835":1,"841":2,"843":1,"848":1,"852":1,"860":1,"863":1,"916":2,"918":2,"932":1,"948":1,"986":1,"1074":1,"1077":1,"1130":1,"1162":1,"1254":1,"1386":3,"1399":2,"1401":2,"1403":3,"1404":1,"1405":1,"1423":2,"1435":1,"1436":1,"2155":1,"2537":1,"2541":1,"2867":1}}],["sometimes",{"2":{"663":1,"841":1,"848":1,"904":1,"1130":1,"1402":1,"1431":1}}],["some",{"0":{"565":1},"2":{"156":1,"844":4,"845":3,"847":1,"852":1,"855":1,"857":1,"861":1,"873":1,"876":1,"912":1,"919":1,"920":1,"953":1,"994":1,"1075":1,"1128":1,"1130":1,"1134":1,"1171":1,"1254":4,"1384":1,"1386":1,"1388":1,"1390":1,"1392":2,"1393":1,"1394":3,"1397":1,"1398":2,"1401":2,"1402":3,"1403":2,"1411":3,"1435":1,"1441":1,"1524":1,"1569":1,"1696":1,"1708":1,"1759":1,"1792":2,"1823":1,"1912":1,"2258":1,"2271":1,"2321":1,"2380":1,"2381":1,"2438":1,"2535":1,"2785":2}}],["so",{"0":{"1443":1,"2452":1},"2":{"74":2,"108":1,"214":2,"302":1,"304":1,"307":2,"308":1,"309":1,"317":1,"319":2,"320":2,"354":1,"388":2,"389":1,"394":1,"436":1,"448":1,"452":1,"453":4,"528":1,"529":1,"531":1,"650":2,"663":2,"664":1,"695":1,"706":1,"708":1,"714":1,"715":1,"831":1,"834":1,"838":1,"840":2,"841":5,"843":3,"844":3,"845":1,"848":3,"851":4,"852":2,"854":1,"856":1,"859":1,"860":2,"861":2,"863":1,"865":2,"868":1,"869":2,"872":2,"873":1,"876":1,"912":2,"917":1,"919":1,"932":2,"947":1,"957":1,"998":1,"1020":1,"1038":2,"1042":1,"1044":1,"1045":2,"1054":1,"1067":2,"1068":2,"1070":2,"1073":1,"1074":1,"1075":1,"1076":1,"1078":1,"1080":2,"1081":1,"1094":1,"1097":1,"1098":1,"1101":2,"1102":2,"1126":1,"1129":1,"1130":1,"1134":1,"1150":1,"1162":1,"1166":1,"1190":1,"1254":1,"1343":1,"1363":1,"1384":1,"1385":2,"1386":3,"1390":1,"1391":1,"1393":2,"1394":1,"1395":1,"1396":1,"1398":2,"1400":2,"1401":4,"1402":3,"1403":2,"1404":2,"1405":1,"1416":2,"1418":1,"1419":1,"1431":1,"1441":1,"1442":1,"1458":1,"1459":2,"1504":1,"1522":1,"1527":1,"1559":1,"1569":2,"1571":1,"1580":1,"1582":1,"1689":1,"1743":2,"1759":1,"1792":10,"1822":1,"1823":2,"1824":2,"1825":1,"1827":1,"1832":1,"1833":1,"1840":1,"1908":1,"1912":1,"1923":1,"1925":1,"1955":1,"1956":1,"1958":2,"1959":1,"2040":1,"2094":2,"2095":1,"2098":2,"2099":1,"2103":1,"2106":1,"2110":1,"2156":2,"2157":1,"2175":1,"2177":1,"2180":1,"2183":1,"2184":1,"2359":1,"2369":1,"2372":1,"2375":3,"2377":1,"2379":3,"2380":2,"2383":1,"2384":1,"2391":1,"2393":1,"2394":2,"2395":1,"2398":1,"2399":1,"2402":1,"2404":1,"2405":1,"2411":1,"2412":1,"2415":1,"2416":1,"2419":2,"2421":1,"2422":3,"2426":1,"2432":1,"2437":1,"2438":1,"2445":1,"2446":1,"2451":1,"2453":1,"2456":1,"2459":1,"2461":2,"2462":1,"2463":2,"2466":4,"2470":1,"2471":1,"2476":2,"2481":6,"2482":3,"2483":1,"2484":1,"2486":1,"2489":1,"2490":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2502":2,"2505":1,"2509":1,"2517":1,"2518":2,"2519":1,"2520":3,"2522":1,"2530":3,"2531":1,"2532":2,"2533":5,"2534":2,"2535":1,"2537":5,"2538":2,"2539":1,"2540":3,"2542":2,"2543":3,"2678":1,"2688":1,"2695":1,"2731":1,"2741":1,"2742":1,"2763":1,"2765":1,"2809":1,"2812":1,"2815":1,"2823":1,"2829":1,"2835":2,"2845":1,"2847":1,"2857":1,"2860":1,"2861":1,"2867":1,"2868":1,"2869":2,"2871":1,"2872":1,"2873":1,"2874":1,"2878":1,"2879":1}}],["sourcing",{"2":{"840":1}}],["sourcelink",{"2":{"2386":1}}],["sourced",{"2":{"2277":1}}],["sourcecontext",{"2":{"1792":2,"1800":1,"1809":2,"1810":1,"2094":1,"2108":1,"2795":1}}],["sourcescreated",{"2":{"2369":1}}],["sources",{"0":{"534":1,"2681":1},"2":{"212":1,"265":1,"479":1,"535":1,"615":1,"834":1,"1069":1,"1098":2,"1162":2,"1396":1,"1405":1,"1567":1,"1610":1,"1717":1,"1738":1,"1746":1,"1785":1,"1792":6,"1923":1,"1955":2,"1956":3,"1957":2,"1958":1,"1959":1,"1960":1,"2016":1,"2020":7,"2106":1,"2120":1,"2266":3,"2317":1,"2339":1,"2344":1,"2347":1,"2352":1,"2369":2,"2372":1,"2379":6,"2389":1,"2441":1,"2443":1,"2470":1,"2471":1,"2482":1,"2537":2,"2632":2,"2680":2,"2681":1,"2691":1,"2697":1,"2773":1,"2775":1}}],["source",{"0":{"975":1,"1000":1,"1043":1,"1368":1,"1957":1,"1998":1,"2317":1,"2542":1,"2792":1},"1":{"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1,"1999":1,"2000":1,"2001":1,"2002":1,"2003":1,"2004":1,"2005":1,"2006":1,"2007":1,"2008":1,"2009":1,"2010":1,"2011":1,"2012":1,"2013":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1},"2":{"0":1,"1":1,"77":1,"170":1,"175":1,"260":1,"264":1,"309":1,"319":1,"336":2,"338":1,"358":3,"362":1,"363":1,"364":1,"445":2,"472":1,"534":1,"568":2,"588":2,"626":2,"639":1,"832":1,"841":1,"844":4,"845":3,"851":2,"872":1,"873":1,"875":1,"878":1,"888":1,"902":1,"911":1,"913":2,"919":1,"920":2,"921":1,"946":1,"947":2,"966":1,"971":1,"975":1,"976":1,"995":1,"1005":1,"1008":1,"1010":1,"1036":1,"1037":2,"1038":1,"1046":1,"1048":1,"1065":1,"1069":1,"1070":4,"1102":2,"1135":1,"1162":1,"1176":1,"1182":1,"1183":1,"1193":1,"1202":2,"1205":1,"1208":1,"1212":1,"1254":1,"1281":1,"1287":15,"1288":15,"1289":15,"1290":15,"1291":15,"1293":15,"1295":15,"1297":15,"1299":15,"1301":15,"1302":1,"1305":1,"1327":1,"1328":1,"1351":1,"1352":1,"1367":1,"1368":1,"1377":2,"1380":1,"1382":3,"1385":9,"1386":2,"1389":1,"1406":1,"1407":3,"1414":1,"1419":1,"1421":1,"1422":2,"1433":1,"1708":1,"1789":1,"1792":13,"1802":2,"1806":1,"1836":1,"1852":2,"1929":1,"1956":2,"1973":1,"2000":1,"2001":1,"2010":1,"2012":1,"2013":1,"2114":1,"2153":1,"2155":1,"2156":3,"2157":1,"2159":1,"2164":1,"2165":2,"2166":1,"2221":1,"2228":1,"2250":1,"2256":2,"2258":1,"2266":1,"2317":1,"2328":1,"2330":1,"2346":1,"2364":3,"2371":1,"2379":3,"2383":2,"2394":1,"2395":1,"2481":1,"2482":1,"2495":2,"2509":1,"2525":1,"2537":2,"2539":1,"2541":3,"2542":2,"2543":1,"2600":1,"2633":1,"2682":1,"2697":1,"2714":1,"2725":1,"2774":2,"2776":1,"2792":1,"2794":1,"2803":5,"2804":1,"2824":1,"2825":1,"2826":3,"2839":1,"2841":2,"2856":2,"2858":2,"2859":1}}],["sounds",{"2":{"1":1}}],["cgroup",{"2":{"2385":1}}],["cfg",{"2":{"2171":2}}],["cfdj8n",{"2":{"184":1,"2292":1}}],["cwd",{"2":{"1792":2,"2096":1,"2531":1,"2537":1,"2869":1,"2877":1}}],["cmdlog",{"2":{"2614":1}}],["cmd",{"2":{"1775":1}}],["cbc",{"2":{"1656":6,"1792":3}}],["cbor",{"0":{"2492":1},"2":{"1098":1,"1211":3,"1215":1,"1243":1,"1248":1,"1868":3,"2492":1}}],["ccx33",{"2":{"1255":1}}],["cp",{"2":{"1119":1}}],["cpu",{"2":{"307":1,"1049":2,"1107":1,"1141":1,"1165":2,"1167":2,"1169":1,"1170":1,"1259":1,"1277":2,"1278":1,"1324":2,"2087":1,"2177":2,"2270":1,"2397":1,"2398":2,"2614":1}}],["cdns",{"2":{"1137":1,"1138":4,"1139":2,"1140":1,"1363":1}}],["cdn",{"2":{"1101":2,"1136":1,"1137":1,"1353":1,"1354":2,"1362":1,"1365":1,"1706":1}}],["cd",{"2":{"970":2,"1013":1,"1119":1,"1207":1,"1380":2,"2162":1,"2627":1,"2792":1}}],["cve",{"2":{"933":1}}],["cvv",{"2":{"594":1}}],["cz",{"2":{"927":2}}],["css",{"2":{"1792":2,"1936":1,"1943":1,"2075":2}}],["csrf",{"2":{"1487":1,"1494":1,"1788":1,"1792":3,"1795":1,"2030":1,"2044":1}}],["csharpcsharpstring",{"2":{"2622":1}}],["csharpcsharpcmdlog",{"2":{"2622":2}}],["csharpcsharpvaluetask",{"2":{"2461":1}}],["csharpcsharpvar",{"2":{"2148":1,"2575":1}}],["csharpcsharpdatetime",{"2":{"2451":1}}],["csharpcsharppublic",{"2":{"2257":1,"2265":3}}],["csharpcsharp",{"2":{"1366":1,"2255":2,"2256":1,"2257":1,"2266":1}}],["csproj",{"2":{"869":1}}],["csp",{"0":{"2029":1},"2":{"868":1,"869":1,"1788":1,"1792":1,"2020":1,"2632":1}}],["cs",{"2":{"867":1,"868":1,"869":1,"873":1,"2372":3,"2409":2,"2417":2,"2419":1,"2422":1,"2435":4,"2440":1,"2447":1,"2448":2,"2451":1,"2456":1,"2457":4,"2465":1,"2472":2,"2513":1,"2523":2,"2621":4}}],["csvcsv",{"2":{"1189":1,"1192":1}}],["csvuploadhandler",{"2":{"2615":1}}],["csvuploadhasfieldsenclosedinquotes",{"2":{"889":1,"1792":1,"2123":1,"2128":2}}],["csvuploadrowcommand",{"2":{"1792":1,"2123":1,"2128":2,"2132":1}}],["csvuploadcheckfilestatus",{"2":{"1792":1,"2123":1,"2128":2}}],["csvupload",{"2":{"894":3}}],["csvuploadsetwhitespacetonull",{"2":{"889":1,"1792":1,"2123":1,"2128":2}}],["csvuploaddelimiterchars",{"2":{"889":1,"1792":1,"2123":1,"2128":2,"2132":1}}],["csvuploadkey",{"2":{"889":1,"1792":1,"2123":1,"2128":2,"2267":1}}],["csvuploadenabled",{"2":{"889":1,"1792":1,"2123":1,"2128":2,"2132":1}}],["csvhelper",{"2":{"881":1}}],["csv",{"0":{"128":1,"489":1,"493":1,"601":1,"759":1,"768":1,"786":1,"878":1,"883":1,"891":1,"893":1,"904":1,"1183":1,"1186":1,"1189":1,"1373":1,"2128":1,"2129":1,"2726":1},"1":{"760":1,"761":1,"762":1,"763":1,"764":1,"765":1,"766":1,"767":1,"768":1,"787":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"1184":1,"1185":1,"1186":1,"1187":2,"1188":2,"1189":2,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"2129":1},"2":{"128":2,"386":4,"489":3,"492":2,"493":4,"543":2,"544":4,"601":1,"746":3,"747":4,"748":1,"758":1,"759":1,"760":1,"762":5,"763":3,"764":8,"765":2,"766":2,"767":3,"768":2,"777":1,"781":1,"786":16,"787":3,"791":2,"832":1,"848":1,"878":4,"879":3,"880":1,"881":2,"883":2,"884":1,"885":1,"886":6,"887":3,"889":2,"892":1,"893":5,"894":1,"902":5,"903":8,"904":8,"913":1,"1019":2,"1021":4,"1037":3,"1086":1,"1099":4,"1121":1,"1183":4,"1184":1,"1189":3,"1192":1,"1199":2,"1200":3,"1202":2,"1205":1,"1207":1,"1208":2,"1373":4,"1376":9,"1377":1,"1386":1,"1398":6,"1413":1,"1792":5,"2122":1,"2123":2,"2125":3,"2128":7,"2132":2,"2134":2,"2164":5,"2165":6,"2206":2,"2267":1,"2270":1,"2496":1,"2498":1,"2572":1,"2649":3,"2664":3,"2726":1,"2766":4,"2856":1}}],["ctrl+c",{"2":{"2106":2,"2112":1,"2157":1,"2158":1,"2221":1,"2362":1,"2532":3,"2537":1,"2543":1,"2742":1,"2758":1,"2871":1,"2878":2,"2881":1}}],["ctes",{"2":{"857":1,"860":2,"874":1,"876":1,"919":1,"1096":1}}],["ctx",{"2":{"306":2}}],["cqrs",{"2":{"840":1}}],["c1",{"2":{"775":1,"900":1}}],["center",{"2":{"1403":2}}],["centralized",{"2":{"945":1,"974":1}}],["central",{"0":{"1203":1},"2":{"851":1,"1184":1}}],["centrally",{"2":{"545":1}}],["centric",{"2":{"841":2,"1084":1,"1122":1}}],["ceil",{"2":{"928":1,"929":1}}],["ceiling",{"0":{"857":1},"2":{"1077":1,"1325":1}}],["certifications",{"2":{"1792":1}}],["certificatepassword",{"2":{"1650":1,"1651":1,"1661":1,"1792":1,"2565":3}}],["certificatepath",{"2":{"1650":1,"1651":1,"1661":1,"1792":1,"2565":3}}],["certificates",{"2":{"1204":2,"1609":1,"1651":1,"1659":1,"1792":3,"1981":1,"1983":1,"1984":1,"1985":1,"1989":1,"2385":1,"2565":2,"2661":1}}],["certificate",{"0":{"1661":1,"1985":1,"1988":1,"1989":1},"1":{"1986":1,"1987":1,"1988":1,"1989":1},"2":{"1199":3,"1207":1,"1609":1,"1651":4,"1661":4,"1662":1,"1792":11,"1984":2,"1986":2,"1987":2,"1988":1,"1989":2,"1995":1,"2297":1,"2565":6,"2645":1,"2661":1}}],["cert",{"2":{"1661":2,"1792":1,"1984":1,"1986":1,"1989":1,"1995":1,"2565":2}}],["certain",{"0":{"1273":1},"1":{"1274":1,"1275":1,"1276":1},"2":{"1792":1,"2395":1}}],["certainly",{"2":{"918":1,"1401":1}}],["certs",{"2":{"1199":2,"1207":1,"1995":1}}],["ceremonies",{"2":{"869":1}}],["ceremony",{"2":{"849":3,"860":1,"868":2,"1405":1}}],["cells",{"2":{"788":3,"948":1,"951":1,"963":2,"971":1,"1792":2,"2077":2,"2652":2}}],["cell",{"0":{"951":1},"2":{"775":1,"900":1,"947":1,"948":1,"951":2,"969":1}}],["c>",{"2":{"348":1}}],["cidr",{"2":{"1703":1,"1708":1,"1792":1,"2633":1}}],["circuits",{"2":{"1910":1,"2415":1,"2433":1,"2466":1}}],["circuit",{"2":{"1011":1,"2466":1}}],["ci",{"0":{"2880":1},"2":{"869":1,"986":1,"1013":1,"1082":2,"1094":1,"1407":1,"1417":1,"1420":1,"1792":2,"2102":1,"2106":2,"2107":1,"2110":1,"2114":1,"2221":1,"2450":1,"2526":1,"2530":1,"2535":2,"2537":4,"2627":1,"2740":1,"2872":1,"2874":1,"2878":1,"2879":1,"2880":1}}],["citext",{"2":{"2540":2}}],["cited",{"2":{"847":1,"872":1}}],["citizen",{"0":{"1430":1}}],["citus",{"2":{"848":1}}],["city",{"2":{"207":2,"322":6,"332":3,"390":2,"394":1,"448":1,"452":9,"531":3,"1347":6,"1726":1,"1727":2,"1733":4,"1823":2,"1973":3,"2010":3,"2264":8,"2483":1,"2587":3,"2590":1,"2768":1}}],["city=london",{"2":{"452":2,"1733":2,"2264":2}}],["city=",{"2":{"207":1,"390":1,"394":1,"452":1,"531":1,"1726":1,"1733":1,"2264":2,"2483":1,"2768":1}}],["ciphertext",{"2":{"182":1,"184":1,"186":1,"188":1,"1100":1,"1664":1,"2291":1,"2292":1,"2293":1,"2296":1,"2297":1,"2405":2}}],["cycles",{"2":{"1141":1,"2546":1,"2869":1}}],["cycle",{"2":{"174":1,"872":4,"876":1,"1049":1,"1080":1,"1094":1,"1379":1,"1401":1,"2221":1,"2324":1,"2498":1,"2531":1,"2607":1,"2742":1,"2857":1,"2878":1}}],["cl",{"2":{"2807":2}}],["clueless",{"2":{"1403":1}}],["clumsy",{"2":{"1378":1,"1391":1}}],["clusters",{"2":{"848":1,"1172":1}}],["cluster",{"2":{"848":1,"863":1,"864":1,"1177":1,"1420":1}}],["clustered",{"2":{"848":1}}],["clr",{"2":{"1075":1}}],["clock",{"2":{"1454":1,"1792":2,"1852":1,"2383":1,"2451":4,"2456":2,"2554":1}}],["closing",{"2":{"1068":1,"1404":1,"1618":1,"1621":1,"1792":2,"2075":1,"2247":1,"2372":1,"2527":1,"2862":1}}],["close=true",{"2":{"2824":1,"2825":1}}],["closeafterms",{"2":{"1317":1,"1416":2,"2247":3}}],["closest",{"2":{"2729":1}}],["closes",{"2":{"1068":1,"1073":1,"1440":1}}],["closer",{"2":{"871":1}}],["close",{"2":{"857":1,"865":1,"1113":1,"1318":1,"1384":1,"1416":1,"1424":1,"2247":1,"2773":1}}],["closedxml",{"2":{"968":1,"969":1}}],["closed",{"2":{"852":2,"876":1,"1067":1,"1090":1,"1616":1,"2533":1}}],["closely",{"2":{"847":1}}],["cloudfront",{"2":{"1139":1}}],["cloudflare",{"0":{"1714":1},"2":{"1139":1,"1701":1,"1714":2,"1792":1,"2633":2}}],["cloud",{"2":{"848":1,"1013":1,"1084":2,"1086":1,"1088":2,"1094":4,"1100":1,"1101":2,"1115":1,"1121":1,"1123":1,"1127":1,"1255":1,"1691":1,"1708":1,"1792":1}}],["cloned",{"2":{"1227":1,"1877":1,"2531":1,"2546":1}}],["clones",{"2":{"1079":1,"1082":1,"2114":1,"2167":2,"2533":1,"2546":1,"2740":1}}],["clone",{"2":{"695":3,"705":1,"715":1,"970":2,"1119":2,"1207":2,"1380":1,"2162":2,"2533":3,"2534":2,"2537":1,"2545":1,"2546":1,"2792":3,"2873":6,"2881":1}}],["cleared",{"2":{"1518":1,"2265":1}}],["clearer",{"2":{"683":1,"2412":1}}],["clearance",{"2":{"1259":1}}],["clearing",{"2":{"1148":1}}],["clears",{"2":{"1067":1,"1148":1}}],["clear",{"2":{"935":2,"1111":1,"1157":1,"1254":1,"1401":1,"1403":1,"1460":2,"1464":1,"1605":1,"1948":1,"2321":1,"2375":2,"2376":1,"2377":1,"2378":1,"2497":1,"2529":1,"2663":1,"2688":1,"2865":1}}],["cleaned",{"2":{"1427":5}}],["cleaner",{"2":{"369":1}}],["cleansing",{"2":{"1205":1}}],["cleanly",{"2":{"868":1,"965":1,"1094":1,"1410":1,"1440":1,"2362":1,"2391":1,"2413":1,"2520":1,"2521":1}}],["clean",{"2":{"839":1,"840":1,"847":1,"876":1,"916":1,"989":1,"1037":1,"1070":1,"1074":1,"1402":1,"1403":2,"1424":1,"1427":1,"1428":1,"1431":1,"2103":1,"2164":1,"2411":1,"2672":1}}],["cleanup",{"0":{"292":1},"2":{"288":1,"623":1,"994":1,"1074":1,"1076":1,"1309":1,"1393":1,"2615":2}}],["clicommandtests",{"2":{"2417":1}}],["clickjacking",{"2":{"1493":1,"1792":3,"2018":1,"2020":1,"2632":2}}],["clicking",{"2":{"1061":1}}],["click",{"2":{"961":1,"1419":1}}],["clicked",{"2":{"852":1}}],["clicks",{"2":{"844":1,"1232":1,"1882":1}}],["clickhouse",{"2":{"834":1,"837":1}}],["clitests",{"2":{"2417":1}}],["clientanalyticsdata",{"2":{"1684":1,"1792":1}}],["clientanalyticsipkey",{"2":{"1241":1,"1684":1,"1792":2,"1890":1}}],["clientdatajson",{"2":{"1220":1,"1221":1,"1222":1,"1792":3}}],["clientid",{"2":{"1059":1,"1062":1,"1690":2,"1696":1,"1697":1,"1698":2,"1792":5}}],["clientcodegen",{"2":{"718":1,"937":1,"998":1,"1062":1,"1408":1,"1416":1,"1417":2,"1553":1,"1579":1,"1580":1,"1581":1,"1582":1,"1792":1,"1836":2,"2273":1,"2484":1,"2648":1,"2701":1,"2830":1}}],["clientsecret",{"2":{"1059":1,"1062":1,"1690":2,"1696":1,"1697":1,"1698":2,"1792":5}}],["clients",{"2":{"458":1,"463":1,"464":1,"638":3,"641":1,"642":1,"643":1,"645":1,"646":1,"650":3,"666":1,"669":1,"836":1,"837":1,"872":1,"975":1,"1037":2,"1046":1,"1055":1,"1086":1,"1094":1,"1096":2,"1097":1,"1103":2,"1121":1,"1122":1,"1127":1,"1139":1,"1193":1,"1200":1,"1304":1,"1305":2,"1309":2,"1312":1,"1314":1,"1318":1,"1325":1,"1372":1,"1408":1,"1447":1,"1704":1,"1716":1,"1717":2,"1792":3,"1823":1,"1857":1,"1941":1,"1947":1,"2164":1,"2174":1,"2225":1,"2257":1,"2362":1,"2391":1,"2393":1,"2429":1,"2436":1,"2464":1,"2466":1,"2615":2,"2633":1,"2811":1,"2826":1,"2827":1,"2828":1,"2829":2,"2831":3,"2834":1,"2838":1}}],["client",{"0":{"163":1,"264":1,"429":1,"666":1,"735":1,"894":1,"961":1,"995":1,"1024":1,"1063":1,"1218":1,"1317":1,"1342":1,"1361":1,"1720":1,"2287":1,"2346":1,"2389":1,"2489":1,"2519":1,"2582":1,"2627":1,"2628":1,"2761":1},"1":{"1721":1,"1722":1,"1723":1,"1724":1,"1725":1,"1726":1,"1727":1,"1728":1,"1729":1,"1730":1,"1731":1,"1732":1,"1733":1,"1734":1,"1735":1,"1736":1,"1737":1,"1738":1,"1739":1,"1740":1,"1741":1,"1742":1,"1743":1,"1744":1,"1745":1,"1746":1,"1747":1,"1748":1,"1749":1,"1750":1,"2288":1,"2289":1,"2290":1},"2":{"74":1,"163":1,"164":1,"182":2,"184":1,"186":2,"209":6,"210":1,"212":1,"215":2,"217":2,"219":2,"223":1,"237":1,"261":2,"264":1,"266":2,"297":2,"302":1,"320":1,"376":1,"383":2,"390":1,"412":1,"414":3,"415":2,"419":1,"423":1,"424":2,"429":1,"436":3,"438":1,"439":2,"452":1,"453":1,"480":1,"527":3,"529":3,"534":1,"535":2,"615":1,"663":6,"666":1,"679":1,"681":1,"718":2,"720":2,"722":2,"725":1,"727":1,"735":2,"738":1,"792":1,"802":1,"833":2,"834":1,"835":3,"836":1,"867":2,"868":1,"869":3,"871":3,"872":5,"873":3,"875":1,"876":1,"877":1,"878":1,"880":1,"894":1,"901":1,"909":1,"910":1,"911":1,"914":1,"915":1,"936":1,"938":1,"948":1,"957":1,"961":1,"972":1,"973":2,"974":1,"975":1,"978":1,"984":2,"1008":2,"1010":1,"1011":1,"1020":1,"1023":1,"1024":1,"1027":3,"1032":1,"1033":3,"1036":1,"1037":1,"1042":1,"1043":2,"1045":1,"1047":1,"1054":1,"1059":2,"1062":2,"1067":1,"1077":1,"1078":1,"1080":1,"1084":1,"1086":1,"1087":1,"1088":1,"1094":2,"1095":1,"1096":7,"1098":1,"1100":2,"1104":1,"1105":7,"1107":1,"1108":2,"1111":2,"1122":1,"1126":2,"1127":1,"1139":1,"1156":1,"1162":1,"1181":1,"1209":1,"1211":2,"1237":1,"1239":1,"1241":2,"1252":2,"1279":1,"1302":1,"1303":1,"1304":1,"1317":1,"1318":1,"1320":2,"1321":1,"1323":1,"1325":2,"1326":2,"1331":2,"1332":1,"1333":1,"1338":2,"1340":2,"1342":1,"1350":1,"1352":1,"1357":2,"1361":1,"1366":2,"1367":1,"1368":1,"1372":1,"1378":1,"1379":1,"1381":1,"1382":5,"1385":1,"1396":1,"1398":5,"1399":2,"1403":2,"1405":4,"1406":1,"1407":1,"1408":1,"1409":3,"1410":1,"1411":1,"1412":1,"1421":1,"1422":1,"1431":1,"1432":1,"1434":1,"1448":1,"1540":1,"1543":1,"1544":1,"1552":1,"1554":1,"1559":1,"1567":2,"1569":2,"1571":1,"1580":1,"1582":2,"1583":1,"1585":1,"1624":2,"1664":1,"1684":1,"1690":2,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":3,"1697":3,"1698":4,"1701":1,"1704":3,"1705":1,"1718":1,"1722":1,"1738":2,"1739":1,"1744":1,"1745":1,"1747":1,"1751":1,"1757":1,"1759":1,"1789":2,"1792":39,"1796":2,"1802":1,"1827":1,"1840":3,"1851":1,"1862":1,"1868":2,"1887":1,"1888":1,"1890":2,"1912":1,"1915":1,"1917":1,"1920":1,"1923":1,"1928":1,"1933":1,"1937":1,"1940":1,"1957":1,"1959":1,"1961":3,"1974":1,"2040":1,"2081":1,"2092":1,"2157":1,"2164":4,"2165":3,"2169":1,"2171":1,"2174":1,"2178":1,"2183":2,"2184":1,"2185":1,"2193":1,"2195":1,"2221":1,"2222":2,"2223":1,"2226":1,"2230":1,"2235":1,"2247":1,"2253":1,"2254":1,"2255":2,"2256":1,"2257":3,"2259":2,"2264":3,"2267":2,"2273":1,"2282":5,"2283":2,"2284":2,"2286":1,"2287":1,"2291":2,"2292":1,"2293":2,"2300":1,"2302":3,"2303":2,"2304":1,"2305":1,"2307":2,"2310":1,"2313":1,"2329":2,"2339":1,"2344":2,"2346":4,"2347":1,"2348":2,"2356":1,"2357":1,"2358":1,"2359":1,"2372":1,"2379":1,"2382":1,"2384":2,"2386":1,"2388":1,"2389":1,"2391":3,"2393":1,"2407":1,"2429":1,"2431":1,"2455":1,"2463":1,"2466":3,"2471":1,"2477":1,"2481":1,"2482":1,"2483":1,"2484":3,"2489":1,"2515":1,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1,"2527":2,"2543":1,"2545":2,"2549":2,"2571":1,"2607":1,"2611":1,"2628":1,"2633":3,"2641":2,"2654":1,"2712":1,"2713":1,"2714":1,"2721":1,"2723":1,"2733":1,"2742":1,"2759":2,"2760":1,"2761":1,"2768":2,"2771":1,"2772":1,"2775":1,"2785":1,"2794":1,"2795":3,"2802":1,"2805":1,"2807":3,"2812":2,"2813":2,"2814":1,"2828":4,"2830":4,"2831":1,"2836":2,"2837":2,"2838":1,"2841":1,"2857":1,"2862":2,"2874":1,"2878":1}}],["cli",{"0":{"2414":1,"2663":1,"2667":1,"2679":1},"1":{"2415":1,"2416":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"56":1,"57":1,"58":1,"869":1,"1049":1,"1051":1,"1094":1,"1107":1,"1108":1,"1115":1,"1609":1,"2106":1,"2154":1,"2225":1,"2231":1,"2232":1,"2414":1,"2415":1,"2417":1,"2660":1,"2662":1,"2667":1,"2679":3,"2693":1}}],["clarity",{"2":{"2279":1}}],["clark",{"2":{"913":1}}],["clause",{"2":{"855":1,"861":1,"1378":1,"1442":1,"2267":1,"2732":1,"2848":1}}],["claude",{"2":{"0":1,"327":1,"1038":1,"1044":3,"1047":2,"1384":3,"1400":2,"1401":5,"1402":1,"1404":2,"1834":1,"2166":1,"2479":1}}],["class=",{"2":{"1427":3,"1429":1,"1431":1,"2762":2}}],["classified",{"2":{"2537":1}}],["classification",{"0":{"2364":1},"2":{"1181":1,"2621":1}}],["classic",{"2":{"695":1,"869":2,"871":1,"873":5,"1955":1,"2379":1,"2734":1,"2868":1,"2869":1,"2873":1}}],["classname",{"2":{"996":1,"2535":1}}],["classes",{"0":{"1155":1},"2":{"867":1,"1006":1,"1011":1,"1027":1,"1036":1,"1366":2,"1406":1,"1519":1}}],["class",{"0":{"1593":1,"1594":1,"1595":1,"1596":1},"2":{"841":1,"849":1,"864":1,"868":1,"872":1,"873":2,"986":1,"1026":2,"1066":1,"1101":1,"1155":1,"1366":1,"1429":1,"1432":1,"1624":1,"1792":5,"1856":1,"2255":1,"2265":3,"2372":1,"2401":1,"2438":2,"2450":1,"2453":1,"2455":1,"2486":1,"2621":1}}],["clash",{"2":{"448":1}}],["claimed",{"2":{"2482":1}}],["claiming",{"2":{"852":1}}],["claimtype",{"2":{"834":1,"1094":1,"1792":2,"2039":1}}],["claimtypes",{"2":{"34":3}}],["claim",{"0":{"168":1,"376":1,"689":1,"843":1,"847":1,"851":1,"859":1,"863":1,"1474":1,"2394":1,"2395":1},"1":{"690":1,"691":1,"692":1},"2":{"19":1,"20":1,"22":1,"32":1,"34":3,"41":2,"60":1,"61":1,"62":1,"63":1,"165":1,"168":4,"170":1,"239":1,"297":4,"298":2,"299":1,"304":4,"305":2,"306":9,"310":2,"314":2,"315":1,"376":3,"380":1,"384":2,"454":3,"479":1,"534":1,"689":4,"690":6,"691":4,"702":1,"737":1,"738":1,"801":3,"802":1,"841":1,"843":1,"859":2,"860":2,"861":1,"863":1,"864":1,"1060":1,"1064":1,"1069":1,"1070":5,"1074":2,"1094":1,"1102":4,"1105":1,"1121":1,"1162":2,"1188":1,"1239":1,"1454":4,"1474":3,"1475":2,"1477":3,"1480":2,"1540":3,"1544":3,"1547":1,"1569":1,"1686":2,"1688":2,"1738":1,"1759":1,"1792":29,"1830":1,"1852":5,"1912":1,"1923":1,"1924":1,"1955":2,"1957":3,"1960":1,"2037":1,"2038":2,"2039":1,"2040":5,"2179":1,"2180":3,"2181":3,"2182":1,"2183":7,"2184":2,"2185":2,"2187":2,"2188":1,"2189":1,"2221":2,"2222":1,"2226":2,"2258":1,"2314":1,"2322":2,"2333":1,"2379":5,"2383":5,"2394":6,"2395":5,"2407":1,"2474":1,"2476":5,"2481":1,"2509":1,"2510":1,"2511":1,"2513":1,"2520":1,"2526":1,"2529":9,"2540":2,"2546":1,"2549":1,"2554":4,"2572":1,"2733":3,"2739":1,"2845":2,"2849":1,"2860":1,"2865":5,"2869":2,"2876":1,"2881":1,"2882":1}}],["claimsidentity",{"2":{"1470":1}}],["claimsjsonparametername",{"2":{"1469":1,"1477":1,"1483":1,"1539":1,"1544":1,"1546":1,"1548":1,"1792":2}}],["claimsjsoncontextkey",{"2":{"734":1,"739":1,"1469":1,"1475":1,"1539":1,"1540":1,"1542":1,"1543":1,"1792":2,"2184":1}}],["claims",{"0":{"304":1,"305":1,"306":1,"733":1,"734":1,"765":1,"799":1,"1538":1,"2179":1,"2180":1,"2181":1,"2182":1,"2314":1,"2572":1,"2812":1},"1":{"305":1,"306":1,"1539":1,"1540":1,"1541":1,"1542":1,"1543":1,"1544":1,"1545":1,"1546":1,"1547":1,"1548":1,"1549":1,"1550":1,"1551":1,"2180":1,"2181":1,"2183":1,"2184":1,"2185":1},"2":{"11":1,"26":1,"29":1,"30":1,"34":1,"37":3,"38":2,"39":2,"40":2,"41":1,"43":1,"50":2,"65":1,"66":1,"165":1,"168":1,"215":1,"293":1,"296":2,"297":1,"298":2,"299":1,"300":1,"301":2,"304":2,"305":1,"306":3,"309":1,"315":4,"316":2,"376":2,"380":1,"436":1,"453":1,"454":1,"529":2,"535":1,"690":1,"728":1,"734":5,"737":2,"739":1,"740":1,"741":1,"742":1,"762":4,"765":4,"772":3,"794":1,"799":5,"801":2,"802":2,"804":1,"805":1,"806":1,"821":1,"841":1,"847":1,"851":1,"852":2,"868":1,"880":1,"882":1,"883":3,"884":1,"888":2,"893":2,"904":1,"905":4,"910":1,"934":2,"936":2,"937":2,"1058":2,"1065":1,"1066":1,"1077":1,"1086":1,"1094":1,"1098":7,"1101":1,"1102":6,"1106":1,"1188":1,"1216":2,"1221":1,"1232":6,"1239":2,"1244":1,"1253":1,"1348":2,"1371":1,"1372":1,"1469":1,"1475":4,"1477":6,"1480":1,"1483":1,"1484":1,"1485":2,"1506":1,"1507":2,"1538":1,"1539":1,"1540":3,"1542":2,"1543":3,"1544":3,"1546":2,"1547":5,"1548":1,"1551":2,"1688":1,"1689":1,"1788":1,"1792":28,"1852":1,"1882":2,"1888":1,"1923":1,"1924":3,"1928":2,"2038":2,"2039":2,"2040":1,"2123":1,"2125":4,"2129":2,"2131":2,"2170":3,"2171":7,"2176":2,"2179":1,"2180":2,"2181":1,"2182":1,"2183":2,"2184":1,"2185":2,"2187":3,"2188":2,"2189":3,"2222":1,"2224":1,"2229":1,"2258":2,"2284":1,"2314":2,"2322":1,"2333":1,"2383":1,"2423":1,"2465":1,"2476":2,"2482":1,"2509":1,"2529":1,"2549":6,"2572":6,"2627":1,"2806":1,"2812":3}}],["cult",{"2":{"2867":1}}],["cultureinfo",{"2":{"2451":1}}],["cuts",{"2":{"1401":1}}],["cut",{"2":{"1386":1,"2398":2}}],["cutting",{"2":{"868":1,"869":4,"873":3,"876":1,"2184":1}}],["custodianship",{"2":{"844":1}}],["custodian",{"2":{"843":2,"844":5}}],["customloginhandler",{"2":{"2554":1}}],["customserversenteventsresponseheaders",{"2":{"2249":2}}],["customtype",{"2":{"2504":1}}],["customtypefieldname",{"2":{"1792":1}}],["customtypeparameterseparator",{"2":{"915":1,"1792":2,"1966":1,"1967":1,"1968":1,"1975":1}}],["customrequestheaders",{"2":{"1792":1,"1836":1,"1848":1,"2701":1}}],["customapplicationname",{"2":{"1650":1,"1651":1,"1658":2,"1663":1,"1792":1,"2297":1}}],["customheaders",{"2":{"1553":1,"1564":1,"1582":2,"1792":1}}],["customhost",{"2":{"1553":1,"1555":1,"1792":1,"2648":2}}],["customimports",{"2":{"1553":1,"1560":1,"1582":1,"1792":1}}],["customizable",{"2":{"1385":1}}],["customization",{"0":{"1031":1},"2":{"1113":1,"1558":1,"2169":1,"2273":1}}],["customized",{"2":{"915":1}}],["customize",{"0":{"2727":1},"2":{"244":1,"965":1,"1386":2,"1576":1,"1685":1,"1732":1,"1776":1,"1969":1,"2119":1,"2704":1,"2776":1}}],["customizing",{"2":{"140":1,"2273":1}}],["customername",{"2":{"1193":1}}],["customers",{"2":{"1105":1}}],["customer",{"2":{"427":3,"836":1,"841":6,"856":1,"860":3,"948":1,"1105":2,"1107":1,"1187":1,"1188":1,"1189":1,"1191":3,"1192":2,"1193":9,"1373":1}}],["custom",{"0":{"71":1,"75":1,"154":1,"167":1,"201":1,"240":1,"249":1,"250":1,"344":1,"352":1,"401":1,"418":1,"442":1,"443":1,"449":1,"510":1,"604":1,"654":1,"724":1,"751":1,"756":1,"767":1,"780":1,"817":1,"912":1,"915":1,"1097":1,"1104":1,"1106":1,"1375":1,"1426":1,"1542":1,"1546":1,"1582":1,"1625":1,"1697":1,"1776":1,"1968":1,"2064":1,"2143":1,"2215":1,"2251":1,"2306":1,"2325":1,"2345":1,"2502":1,"2504":1,"2518":1,"2653":1,"2759":1},"1":{"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"163":1,"164":1,"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"787":1,"788":1,"789":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"1105":1,"1106":1,"1107":1,"1108":1,"2144":1,"2145":1,"2346":1,"2347":1,"2348":1,"2760":1,"2761":1,"2762":1,"2763":1,"2764":1,"2765":1,"2766":1,"2767":1,"2768":1,"2769":1,"2770":1,"2771":1},"2":{"41":1,"54":1,"66":1,"74":2,"75":1,"77":1,"79":1,"133":1,"154":1,"155":1,"160":2,"165":1,"170":2,"217":1,"223":2,"227":1,"228":1,"240":2,"243":1,"252":3,"260":1,"309":1,"337":1,"338":1,"344":1,"347":1,"363":1,"383":1,"387":4,"388":2,"390":3,"393":1,"394":1,"395":1,"396":2,"397":1,"402":2,"436":1,"527":2,"528":1,"533":1,"535":2,"536":1,"540":2,"546":1,"570":1,"579":1,"604":1,"637":1,"654":1,"669":1,"680":2,"684":1,"698":1,"718":1,"725":2,"751":3,"753":1,"756":2,"780":1,"790":2,"817":1,"868":2,"873":1,"912":4,"914":3,"915":3,"916":8,"917":2,"918":3,"920":1,"957":2,"1037":3,"1049":3,"1060":1,"1066":1,"1086":1,"1095":1,"1097":1,"1098":3,"1100":1,"1101":1,"1102":1,"1104":2,"1105":1,"1106":4,"1107":1,"1108":1,"1109":1,"1111":4,"1113":1,"1114":1,"1115":1,"1121":1,"1127":2,"1181":1,"1217":1,"1249":1,"1322":1,"1323":1,"1325":1,"1343":1,"1358":1,"1373":1,"1375":1,"1376":2,"1377":1,"1390":1,"1394":1,"1395":3,"1401":1,"1402":1,"1415":1,"1423":1,"1424":1,"1426":1,"1431":2,"1432":1,"1434":2,"1447":1,"1489":1,"1494":1,"1542":1,"1546":1,"1555":1,"1560":1,"1564":1,"1569":2,"1582":1,"1655":1,"1678":1,"1690":2,"1697":1,"1748":1,"1759":1,"1787":1,"1792":23,"1794":1,"1807":1,"1848":1,"1857":1,"1862":3,"1864":1,"1912":1,"1923":2,"1924":1,"1925":2,"1967":1,"1968":3,"2008":2,"2079":1,"2143":1,"2146":1,"2164":7,"2165":3,"2185":2,"2193":4,"2196":2,"2202":3,"2206":1,"2222":5,"2225":2,"2252":1,"2255":4,"2258":1,"2267":1,"2322":3,"2324":2,"2327":1,"2329":1,"2348":1,"2369":1,"2372":1,"2389":1,"2413":2,"2417":1,"2425":1,"2438":3,"2444":1,"2445":1,"2446":2,"2447":1,"2461":1,"2483":3,"2486":1,"2493":3,"2498":1,"2500":2,"2502":1,"2504":1,"2509":2,"2510":2,"2512":1,"2515":1,"2517":1,"2518":1,"2519":1,"2520":2,"2521":1,"2523":2,"2527":1,"2549":2,"2554":3,"2586":1,"2591":6,"2597":1,"2642":1,"2645":1,"2653":1,"2654":1,"2672":1,"2684":1,"2727":1,"2759":3,"2760":1,"2761":1,"2766":1,"2769":1,"2771":1,"2812":1,"2815":1,"2816":1,"2817":1,"2832":1,"2856":2,"2858":1,"2862":1}}],["curated",{"2":{"2419":1,"2430":1,"2438":1}}],["curtain",{"2":{"1437":1}}],["cursors",{"2":{"1130":1}}],["curve",{"2":{"1084":1,"1382":1}}],["currencies",{"2":{"1019":2,"1021":4,"1026":2,"1376":7,"1398":3,"2766":2}}],["currencies=usd",{"2":{"1023":1}}],["currencies=",{"2":{"1019":1,"1398":1,"2766":1}}],["currency",{"2":{"263":3,"1010":1,"1011":1,"1018":1,"1019":2,"1020":1,"1021":12,"1024":2,"1026":4,"1032":1,"1079":1,"1376":13,"1930":1,"2344":2,"2764":2,"2766":2}}],["currentuser",{"2":{"1988":1}}],["currentuser>",{"2":{"1792":1}}],["currently",{"2":{"323":2,"840":1,"1792":3,"2052":2,"2635":1}}],["current",{"0":{"1058":1,"1067":1},"2":{"16":2,"19":1,"20":1,"207":1,"288":2,"292":2,"306":2,"322":2,"390":1,"394":1,"401":1,"452":3,"502":1,"531":2,"584":2,"586":1,"592":1,"733":6,"734":1,"735":1,"736":3,"737":1,"752":1,"766":2,"772":1,"803":1,"826":2,"844":1,"872":2,"893":1,"978":1,"997":1,"1057":1,"1058":6,"1098":1,"1102":2,"1236":1,"1260":1,"1325":1,"1347":1,"1366":1,"1370":3,"1372":8,"1395":2,"1399":1,"1543":6,"1555":1,"1609":1,"1651":1,"1662":1,"1726":1,"1733":2,"1792":12,"1823":2,"1886":1,"1898":1,"2047":1,"2184":7,"2186":1,"2254":1,"2255":4,"2264":3,"2297":1,"2337":2,"2338":3,"2398":2,"2414":1,"2430":1,"2481":1,"2483":1,"2565":1,"2572":1,"2635":2,"2684":2,"2768":1,"2785":1,"2829":2,"2850":3,"2852":1,"2855":2}}],["curly",{"2":{"1398":1}}],["curl",{"2":{"38":1,"40":1,"61":1,"62":2,"1775":1,"2784":1,"2823":1,"2824":1}}],["crm",{"2":{"1792":1,"1823":3}}],["crt",{"0":{"1987":1},"2":{"1792":1}}],["crisis",{"2":{"1405":1}}],["criticized",{"2":{"1404":1}}],["critical",{"2":{"572":1,"577":1,"661":3,"927":1,"996":1,"1078":1,"1104":1,"1121":1,"1141":1,"1154":1,"1164":1,"1179":1,"1324":1,"1354":1,"1599":1,"1701":1,"1792":1,"1801":1,"2007":1,"2328":1,"2527":1,"2633":2,"2862":1}}],["criteria",{"2":{"1127":1}}],["crafting",{"2":{"1435":1}}],["crazy",{"2":{"1403":1}}],["crash",{"2":{"969":1,"971":1,"1595":1,"1605":1,"1792":1,"2157":1,"2223":1,"2367":1,"2394":1,"2497":2,"2534":1,"2543":1,"2546":1,"2688":1}}],["crashing",{"2":{"948":1,"1605":1,"2497":1,"2688":1}}],["crashes",{"2":{"947":1,"2157":1,"2543":1,"2679":1}}],["crucial",{"2":{"992":1}}],["cruds",{"2":{"1385":1}}],["crudsource",{"0":{"2351":1,"2352":1},"2":{"1096":1,"2226":1,"2317":1,"2351":3,"2352":2,"2369":1,"2389":10,"2551":1,"2729":1}}],["crud",{"0":{"2389":1},"2":{"175":1,"877":1,"1096":3,"1106":1,"1125":1,"1281":1,"1385":1,"2332":1,"2338":1,"2339":1,"2344":1,"2351":2,"2388":1,"2389":3,"2498":1,"2729":1}}],["crosses",{"2":{"2002":1,"2371":1,"2841":1}}],["crossoriginresourcepolicy",{"2":{"1792":1,"2015":1,"2016":1,"2025":1,"2029":1,"2632":1}}],["crossoriginembedderpolicy",{"2":{"1792":1,"2015":1,"2016":1,"2024":1,"2029":1,"2632":1}}],["crossoriginopenerpolicy",{"2":{"1792":1,"2015":1,"2016":1,"2023":1,"2024":1,"2029":1,"2632":1}}],["cross",{"0":{"663":1,"1449":1,"2022":1,"2023":1,"2024":1,"2025":1,"2429":1},"1":{"664":1,"665":1,"666":1,"2023":1,"2024":1,"2025":1},"2":{"650":1,"868":1,"869":4,"871":1,"872":3,"873":3,"876":3,"1193":1,"1205":1,"1206":1,"1305":1,"1409":1,"1429":1,"1447":1,"1449":1,"1457":1,"1487":1,"1637":1,"1639":1,"1640":1,"1644":2,"1788":1,"1792":19,"1795":1,"1963":1,"2015":1,"2016":4,"2019":6,"2023":3,"2024":3,"2025":2,"2027":1,"2029":1,"2030":1,"2157":1,"2184":1,"2225":1,"2413":1,"2417":1,"2425":2,"2427":1,"2429":1,"2435":1,"2436":1,"2438":3,"2444":1,"2447":1,"2466":1,"2486":1,"2543":1,"2546":1,"2554":1,"2632":18,"2776":1}}],["cron",{"2":{"531":1}}],["cryptographic",{"2":{"1210":1,"1211":1,"1235":1,"1243":1,"1248":1,"1792":1,"1885":1}}],["cryptography",{"2":{"307":1,"1209":1,"1867":1}}],["cryptoresult",{"2":{"1026":6}}],["cryptoservice",{"2":{"1026":2}}],["cryptosuccess",{"2":{"1023":1,"1024":2,"1026":2}}],["cryptoids",{"2":{"1026":7}}],["cryptoidscsv",{"2":{"1024":2}}],["cryptoidscsv=bitcoin",{"2":{"1023":1}}],["cryptoerror",{"2":{"1023":1,"1024":1,"1026":2}}],["cryptopriceservice",{"2":{"1026":3}}],["cryptoprices",{"2":{"1023":1,"1024":2,"1026":2}}],["crypto",{"2":{"1019":3,"1020":3,"1021":13,"1023":1,"1024":3,"1317":1,"1376":13,"1398":12,"1416":1,"2247":1,"2766":10}}],["cryptocurrency",{"2":{"1010":1,"1011":1,"1018":1,"1019":1,"1020":1}}],["crypt",{"2":{"37":1,"308":5,"592":2,"813":1,"928":1,"929":1,"1197":1,"1307":2,"1308":1,"1458":1,"1504":2,"2147":2,"2177":3,"2540":1,"2575":1}}],["cref=",{"2":{"1792":4}}],["creation",{"2":{"876":1,"975":1,"982":1,"1075":1,"1129":1,"1211":1,"1244":1,"1252":1,"1390":1,"1792":1,"1802":1,"1840":1,"1967":1,"2000":1,"2004":1,"2209":1,"2319":1,"2350":1,"2456":1,"2628":1}}],["creating",{"0":{"1186":1,"1724":1,"2829":1},"1":{"1187":1,"1188":1,"1189":1,"1725":1,"1726":1,"1727":1},"2":{"382":1,"1014":1,"1075":1,"1102":1,"1128":1,"1165":1,"1220":1,"1870":1,"2164":1,"2165":1,"2264":1,"2334":1,"2577":1,"2679":1,"2818":1,"2827":1}}],["createrundb",{"2":{"2873":2}}],["createtemplate",{"2":{"2873":2}}],["createtestsseeventsource",{"2":{"2247":1}}],["createtestdatabase",{"2":{"1792":1,"2111":1,"2532":1}}],["createxeventsource",{"2":{"2391":1}}],["createendpointsources",{"2":{"2352":1}}],["createelement",{"2":{"996":5}}],["createcomputevisualizationeventsource",{"2":{"1416":1,"1573":1}}],["createclient",{"2":{"1107":1,"1320":2}}],["createisolateddb1",{"2":{"2873":2}}],["createisolateddb",{"2":{"695":1,"705":2,"711":1,"715":1,"2533":1}}],["createseparatetypefile",{"0":{"1570":1},"2":{"1416":1,"1417":1,"1553":1,"1559":2,"1570":1,"1571":3,"1581":1,"1792":2,"2360":2,"2484":3}}],["createsendmessageeventsource",{"2":{"1317":2,"1318":2,"1321":2,"1326":3,"2830":3,"2836":3}}],["creates",{"2":{"252":1,"422":1,"446":1,"784":1,"886":1,"925":1,"974":1,"977":1,"986":1,"1005":1,"1011":1,"1073":1,"1079":1,"1094":2,"1164":1,"1204":1,"1215":1,"1220":2,"1221":1,"1308":1,"1324":1,"1374":1,"1388":1,"1511":1,"1792":7,"1870":1,"1929":1,"2098":1,"2176":1,"2265":2,"2317":1,"2482":1,"2533":2,"2615":1,"2709":1,"2773":1,"2774":2,"2775":1,"2839":1,"2849":1,"2872":1}}],["createdatabase",{"2":{"2111":1,"2112":1,"2532":2,"2534":2,"2871":2,"2872":2,"2875":1}}],["createdat",{"2":{"919":5,"995":1,"996":4,"1320":1,"1408":2,"2319":1}}],["createdb",{"2":{"704":2,"2533":1,"2534":1}}],["created",{"0":{"2749":1},"2":{"168":1,"173":1,"238":1,"244":1,"299":1,"372":2,"388":1,"566":2,"583":1,"694":1,"696":1,"701":2,"705":1,"913":1,"932":2,"938":1,"977":2,"980":3,"982":2,"983":1,"990":4,"992":1,"994":1,"995":1,"1073":4,"1098":1,"1213":3,"1249":1,"1253":1,"1307":1,"1309":5,"1310":3,"1321":5,"1336":1,"1372":5,"1400":1,"1403":1,"1408":2,"1689":2,"1792":11,"1844":1,"2109":1,"2167":1,"2187":1,"2265":1,"2291":1,"2318":1,"2319":2,"2337":1,"2364":2,"2482":1,"2529":1,"2530":1,"2532":1,"2546":1,"2629":1,"2731":1,"2749":1,"2762":1,"2795":1,"2797":1,"2822":1,"2823":1,"2824":2,"2825":1,"2829":10,"2836":7,"2840":2,"2845":2,"2846":2,"2854":1,"2865":1,"2872":1}}],["create",{"0":{"1021":1,"1355":1,"1357":1,"1725":1,"2820":1,"2825":1},"1":{"2821":1,"2822":1},"2":{"9":2,"37":2,"38":1,"39":1,"116":1,"187":2,"206":3,"207":1,"208":6,"209":2,"212":1,"248":3,"263":2,"264":3,"304":1,"308":1,"310":1,"332":1,"333":2,"334":2,"335":1,"337":1,"352":1,"357":1,"361":2,"451":2,"583":1,"584":1,"656":1,"664":2,"695":1,"697":1,"705":2,"756":3,"757":4,"764":3,"774":3,"784":2,"785":1,"814":2,"817":1,"845":1,"851":1,"876":1,"878":1,"879":1,"880":1,"888":2,"904":1,"910":1,"911":1,"913":2,"914":2,"915":8,"916":5,"924":4,"925":2,"926":4,"928":1,"929":1,"933":1,"934":1,"935":1,"936":1,"948":1,"975":1,"977":3,"979":1,"980":1,"983":1,"988":1,"990":1,"994":5,"1005":1,"1019":3,"1020":1,"1033":1,"1050":1,"1054":2,"1055":1,"1056":2,"1057":1,"1060":1,"1076":3,"1079":2,"1080":1,"1086":2,"1095":2,"1096":1,"1105":2,"1113":1,"1114":1,"1121":1,"1125":1,"1127":2,"1135":1,"1142":1,"1148":1,"1179":2,"1187":1,"1191":3,"1193":8,"1203":1,"1206":1,"1213":5,"1215":1,"1220":3,"1221":1,"1232":1,"1233":1,"1238":1,"1252":1,"1307":2,"1320":1,"1331":1,"1336":1,"1345":2,"1355":3,"1358":2,"1362":1,"1366":1,"1367":1,"1368":3,"1376":2,"1378":1,"1385":1,"1386":1,"1388":2,"1395":2,"1396":4,"1398":2,"1401":1,"1405":1,"1407":1,"1408":1,"1412":1,"1419":1,"1431":1,"1436":1,"1438":1,"1442":1,"1458":3,"1518":1,"1554":1,"1559":1,"1563":1,"1655":3,"1664":2,"1689":1,"1725":1,"1727":1,"1733":1,"1736":4,"1742":1,"1744":1,"1747":1,"1756":2,"1792":13,"1804":1,"1840":4,"1870":2,"1924":1,"1930":1,"1968":1,"1973":1,"1974":3,"2010":1,"2012":1,"2098":1,"2106":1,"2111":3,"2112":1,"2127":1,"2156":3,"2167":1,"2177":2,"2180":1,"2195":1,"2204":1,"2209":3,"2214":1,"2247":1,"2264":3,"2265":1,"2277":4,"2283":1,"2290":1,"2294":2,"2322":1,"2337":1,"2339":1,"2344":2,"2346":3,"2375":3,"2394":1,"2438":1,"2529":1,"2532":5,"2533":1,"2534":4,"2542":3,"2545":1,"2546":1,"2549":2,"2572":1,"2586":2,"2587":1,"2607":3,"2694":1,"2740":1,"2742":1,"2762":3,"2764":1,"2766":2,"2767":2,"2774":1,"2793":1,"2803":1,"2820":1,"2821":3,"2822":1,"2825":2,"2834":2,"2839":1,"2855":1,"2865":1,"2871":4,"2872":1,"2873":5,"2878":1}}],["credentialless",{"2":{"1792":1,"2024":1,"2632":1}}],["credentialid",{"2":{"1218":1,"1220":2,"1221":2,"1222":1,"1792":3}}],["credential",{"2":{"1037":1,"1098":1,"1210":1,"1211":1,"1213":1,"1215":4,"1216":3,"1218":1,"1229":2,"1232":2,"1234":3,"1235":1,"1236":5,"1237":3,"1239":5,"1244":1,"1792":8,"1879":2,"1882":1,"1884":1,"1885":1,"1886":3,"1887":3,"1888":2}}],["credentials",{"0":{"40":1,"60":1,"61":1,"1644":1,"2486":1},"2":{"29":1,"30":1,"31":2,"35":1,"37":1,"38":2,"40":1,"41":2,"51":3,"60":2,"63":2,"64":1,"297":1,"313":1,"316":1,"545":1,"926":5,"1065":1,"1185":1,"1196":1,"1198":1,"1200":1,"1202":1,"1214":1,"1218":1,"1220":2,"1221":2,"1222":3,"1229":1,"1232":2,"1234":2,"1240":2,"1415":1,"1441":2,"1470":1,"1615":1,"1621":1,"1639":1,"1641":1,"1644":1,"1771":1,"1792":15,"1879":1,"1882":1,"1884":2,"1889":2,"2024":1,"2063":2,"2161":1,"2171":2,"2363":1,"2486":1,"2558":1,"2635":1,"2819":1}}],["credited",{"2":{"2528":1,"2864":1}}],["credit",{"2":{"845":1,"859":1,"869":1,"1664":1,"2291":1}}],["creds",{"2":{"60":2}}],["caveman",{"2":{"1401":3}}],["caveats",{"2":{"1208":1,"1394":1,"2398":1}}],["caveat",{"2":{"873":1,"911":1,"971":1,"1126":1,"1279":2,"2540":1,"2845":1,"2867":1,"2869":1}}],["ca",{"2":{"1204":1,"2385":1}}],["cacm",{"2":{"852":1}}],["caching",{"0":{"115":1,"119":1,"214":1,"230":1,"1067":1,"1136":1,"1140":1,"1149":1,"1336":1,"1338":1,"1346":1,"1517":1,"1645":1,"1743":1,"1779":1,"2060":1,"2205":1,"2265":1,"2380":1,"2502":1,"2580":1,"2745":1,"2765":1},"1":{"1137":1,"1138":1,"1139":1,"1140":1,"1141":2,"1142":2,"1143":2,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"2381":1},"2":{"99":1,"101":1,"112":1,"119":1,"214":3,"230":1,"546":1,"548":1,"835":1,"836":1,"837":1,"852":2,"868":2,"869":2,"873":1,"876":1,"877":1,"1011":1,"1037":3,"1066":2,"1067":1,"1083":1,"1086":1,"1101":10,"1104":1,"1105":2,"1108":1,"1109":1,"1113":1,"1121":2,"1125":1,"1127":3,"1135":1,"1136":4,"1137":3,"1139":1,"1140":2,"1141":1,"1149":2,"1150":1,"1171":1,"1177":1,"1179":2,"1180":2,"1181":3,"1182":2,"1328":3,"1342":1,"1350":1,"1351":2,"1362":1,"1368":1,"1377":1,"1382":1,"1386":1,"1401":1,"1403":1,"1407":1,"1430":1,"1434":1,"1509":1,"1511":3,"1513":1,"1514":1,"1515":1,"1516":1,"1517":1,"1519":1,"1527":1,"1530":1,"1533":1,"1535":1,"1537":1,"1639":1,"1722":2,"1743":2,"1764":1,"1769":1,"1790":1,"1792":13,"1797":1,"2038":1,"2041":1,"2047":1,"2060":1,"2164":1,"2165":1,"2205":2,"2222":2,"2237":1,"2239":1,"2265":6,"2274":2,"2329":1,"2380":4,"2386":2,"2389":1,"2463":1,"2465":1,"2495":1,"2500":1,"2502":3,"2506":2,"2527":1,"2567":1,"2580":4,"2634":1,"2635":1,"2759":1,"2774":1,"2806":1,"2815":1,"2816":1,"2824":1,"2825":1,"2838":1,"2856":1,"2862":1}}],["cachekeystring",{"2":{"2622":1}}],["cachekeys",{"2":{"2614":1,"2622":2}}],["cacheparsedfile",{"2":{"1792":1,"2033":1,"2037":1,"2038":1,"2042":1}}],["cachepruneintervalseconds",{"2":{"214":1,"1721":1,"1722":1,"1743":1,"1792":1,"2222":1,"2502":1,"2769":2}}],["cacheable",{"2":{"1531":1}}],["cacheenabled",{"2":{"214":1,"1721":1,"1722":1,"1743":1,"1792":1,"2222":1,"2502":1,"2765":1,"2769":2}}],["cachestampedetests",{"2":{"2465":1}}],["caches",{"2":{"120":1,"214":2,"1177":1,"1430":1,"1743":2,"2466":1,"2502":1,"2815":2}}],["cacheoptions",{"0":{"2380":1,"2445":1},"1":{"2381":1},"2":{"102":1,"104":1,"106":1,"107":1,"121":2,"279":1,"1067":2,"1141":1,"1145":1,"1146":1,"1147":2,"1148":1,"1149":1,"1150":1,"1177":1,"1510":1,"1513":1,"1514":1,"1515":2,"1516":1,"1517":1,"1518":1,"1520":1,"1529":1,"1534":2,"1792":1,"1948":1,"2225":1,"2265":9,"2274":1,"2279":2,"2378":1,"2380":1,"2440":1,"2442":1,"2495":1,"2532":1,"2551":1,"2701":1}}],["cacheduration",{"2":{"967":2,"1763":1,"1764":1,"1769":2,"1779":1,"1780":1,"1792":2,"2046":1,"2047":1,"2060":1,"2067":1,"2068":1,"2634":1,"2635":1}}],["cached",{"0":{"105":1,"112":1,"1531":1,"2494":1,"2495":1,"2815":1},"1":{"113":1,"114":1,"115":1,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1},"2":{"91":1,"94":1,"95":1,"96":1,"97":1,"99":1,"101":2,"105":2,"107":3,"108":3,"110":2,"113":3,"115":2,"116":1,"117":1,"118":1,"119":5,"121":1,"180":1,"214":3,"230":1,"263":4,"265":1,"278":3,"548":1,"686":2,"835":1,"868":1,"1066":2,"1067":4,"1101":2,"1108":1,"1113":1,"1129":1,"1135":3,"1137":1,"1138":2,"1140":1,"1141":3,"1142":4,"1143":2,"1145":1,"1147":1,"1148":1,"1149":2,"1179":3,"1180":1,"1181":1,"1182":1,"1329":1,"1332":2,"1338":8,"1339":13,"1342":1,"1346":1,"1349":1,"1511":5,"1517":3,"1518":2,"1519":2,"1521":1,"1523":1,"1529":1,"1531":2,"1532":2,"1533":1,"1535":1,"1537":1,"1722":3,"1743":3,"1769":1,"1792":16,"1930":3,"2006":1,"2193":3,"2205":3,"2207":2,"2223":3,"2224":1,"2265":9,"2323":1,"2329":1,"2344":5,"2380":4,"2459":1,"2465":1,"2466":7,"2494":3,"2495":1,"2500":1,"2502":3,"2506":1,"2527":1,"2580":3,"2581":4,"2622":2,"2634":1,"2635":1,"2745":1,"2769":1,"2774":1,"2795":1,"2806":1,"2815":8,"2835":1,"2838":1,"2856":1,"2862":1}}],["cache",{"0":{"91":1,"94":1,"95":1,"96":1,"97":1,"101":1,"105":1,"116":1,"117":1,"118":1,"121":1,"278":1,"542":1,"686":1,"1066":1,"1137":2,"1138":1,"1139":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1150":1,"1430":1,"1509":1,"1512":1,"1513":1,"1514":1,"1515":1,"1516":1,"1518":1,"1519":1,"1532":2,"1533":1,"1769":1,"2370":1,"2380":1,"2381":1,"2494":1,"2495":1,"2502":1},"1":{"92":1,"93":1,"94":1,"95":1,"96":1,"97":1,"98":1,"99":1,"100":1,"102":1,"103":1,"104":1,"105":1,"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1138":2,"1139":2,"1145":1,"1146":1,"1147":1,"1510":1,"1511":1,"1512":1,"1513":2,"1514":2,"1515":2,"1516":1,"1517":1,"1518":1,"1519":1,"1520":2,"1521":2,"1522":2,"1523":2,"1524":2,"1525":2,"1526":2,"1527":2,"1528":2,"1529":2,"1530":1,"1531":1,"1532":1,"1533":1,"1534":1,"1535":1,"1536":1,"1537":1,"2381":1},"2":{"91":2,"92":1,"94":1,"95":1,"96":1,"97":1,"98":2,"100":2,"101":9,"102":1,"104":2,"105":5,"106":5,"107":1,"108":7,"109":4,"110":2,"111":2,"113":1,"116":1,"118":2,"119":2,"120":2,"121":6,"122":2,"123":3,"124":2,"203":3,"214":13,"230":4,"232":1,"278":4,"281":2,"540":1,"542":2,"543":2,"544":2,"546":1,"835":1,"852":2,"868":2,"869":2,"873":1,"967":1,"1037":2,"1066":1,"1067":12,"1101":9,"1113":1,"1135":2,"1137":1,"1138":10,"1139":5,"1141":1,"1142":2,"1143":1,"1144":1,"1145":1,"1146":2,"1147":6,"1148":4,"1149":1,"1150":17,"1179":4,"1180":5,"1181":6,"1182":2,"1332":6,"1333":2,"1334":1,"1336":3,"1338":11,"1339":7,"1342":1,"1346":2,"1362":2,"1363":5,"1406":1,"1430":2,"1434":1,"1511":14,"1513":1,"1515":9,"1516":6,"1518":6,"1519":4,"1520":5,"1521":3,"1522":3,"1523":1,"1524":1,"1527":4,"1529":8,"1531":2,"1532":4,"1533":4,"1534":1,"1535":4,"1537":2,"1645":1,"1722":1,"1743":9,"1764":1,"1769":1,"1778":1,"1790":1,"1792":40,"1797":1,"1974":8,"2033":3,"2037":3,"2038":1,"2041":2,"2047":1,"2060":2,"2202":1,"2205":4,"2207":3,"2210":1,"2212":1,"2222":2,"2223":2,"2224":2,"2228":1,"2265":24,"2274":8,"2329":1,"2370":1,"2380":14,"2381":3,"2445":2,"2459":4,"2461":2,"2463":3,"2464":2,"2465":2,"2466":1,"2494":2,"2495":4,"2498":3,"2502":11,"2505":2,"2506":3,"2551":3,"2580":8,"2607":8,"2614":1,"2622":3,"2634":2,"2635":2,"2745":3,"2765":6,"2769":2,"2775":2,"2806":1,"2809":1,"2810":1,"2815":6,"2817":1,"2835":3}}],["capped",{"2":{"2531":1,"2812":1}}],["capping",{"2":{"1382":1}}],["capacity",{"2":{"1958":1,"2470":1}}],["capabilities",{"0":{"1103":1},"2":{"1039":1,"1096":1,"1105":1,"1626":1,"2266":1,"2438":1,"2776":1}}],["capability",{"0":{"857":1},"2":{"835":1,"1104":1,"1106":1,"1218":1,"1824":1,"2481":1}}],["captcha",{"2":{"1220":1,"1870":1}}],["captures",{"2":{"1078":1,"1304":1,"1305":2,"1309":1,"1325":1,"1792":1,"2529":1,"2860":1}}],["capture",{"2":{"698":1,"2404":1,"2529":1}}],["captured",{"2":{"216":1,"239":1,"692":1,"1441":1,"1792":2,"2104":1,"2109":1,"2110":2,"2221":1,"2309":1,"2530":4,"2535":2,"2536":1,"2679":1,"2739":1,"2802":2,"2865":1,"2866":1,"2880":1}}],["cap",{"2":{"1068":1,"1161":1}}],["caps",{"2":{"1067":1,"1925":1,"2463":1,"2466":1}}],["camera",{"2":{"2021":1}}],["camera=",{"2":{"1792":1,"2021":1,"2029":1,"2632":1}}],["camel",{"2":{"1040":1,"1408":1,"1792":1}}],["camelcasenames",{"2":{"1792":1,"1836":1,"1841":1,"1863":1,"2701":1,"2724":1}}],["camelcased",{"0":{"2724":1},"2":{"1077":1}}],["camelcase",{"2":{"74":2,"258":1,"299":1,"388":1,"407":1,"454":1,"809":1,"814":1,"995":1,"1567":1,"1576":1,"1841":1,"2221":1,"2277":1,"2327":1,"2518":1,"2540":1,"2546":1,"2635":1,"2723":1,"2724":1,"2842":1,"2845":1}}],["came",{"0":{"1379":1},"2":{"650":1,"666":1,"843":1,"918":1,"1401":2,"2421":1,"2540":1}}],["cargo",{"2":{"2867":1}}],["carves",{"2":{"1428":1}}],["car",{"2":{"1077":1}}],["carol",{"2":{"913":1,"919":2,"1051":3,"1061":1}}],["carousels",{"2":{"834":1}}],["cartesian",{"2":{"852":1,"918":1}}],["caret",{"2":{"2328":1}}],["career",{"2":{"1403":2}}],["careful",{"2":{"873":1}}],["carefully",{"2":{"849":1,"1315":1}}],["care",{"2":{"848":1,"849":1,"904":1,"1079":1,"1132":1,"1150":1,"1402":1,"1404":1,"1421":1,"1428":1}}],["cards",{"2":{"834":1}}],["card",{"2":{"594":1,"833":1,"1424":1,"1429":1,"1664":1,"2291":1}}],["carriage",{"2":{"2589":1}}],["carried",{"2":{"1075":1,"2171":1,"2172":1,"2546":1}}],["carries",{"2":{"74":1,"317":1,"320":2,"659":1,"686":1,"976":1,"1040":1,"1070":1,"1824":1,"1924":2,"2171":1,"2424":1,"2481":3,"2482":1,"2509":1,"2512":1,"2545":1,"2795":1,"2833":1}}],["carryover",{"2":{"2099":1,"2527":1,"2862":1}}],["carrying",{"2":{"75":1,"1431":1,"1792":3,"1925":1,"2094":2,"2097":2,"2167":1,"2222":1,"2423":1,"2451":1,"2504":1,"2517":1,"2537":3,"2804":1}}],["carry",{"2":{"0":2,"706":1,"849":1,"1326":1,"1792":1,"1813":1,"1822":1,"1830":1,"1924":1,"1925":1,"1961":1,"2180":1,"2459":1,"2481":2,"2509":1,"2519":1,"2533":1,"2537":1}}],["catching",{"2":{"1409":1,"2164":1,"2165":1}}],["catch",{"2":{"1026":2,"1320":1,"1366":1,"1400":1,"1409":1,"1431":1,"2007":1,"2405":1,"2627":1,"2696":1}}],["catches",{"2":{"864":1,"997":1,"1005":1,"1421":1,"1609":1,"2007":1,"2328":1,"2389":1,"2394":1,"2659":1}}],["catastrophic",{"2":{"1402":1}}],["catastrophically",{"2":{"948":1}}],["catastrophe",{"2":{"860":1}}],["catalogs",{"2":{"933":1,"1432":1}}],["catalog",{"2":{"584":1,"868":1,"933":1,"934":1,"935":1,"936":1,"1038":1,"1055":1,"1056":2,"1057":2,"1058":6,"1060":1,"1138":3,"1179":2,"1185":1,"1188":1,"1192":1,"1406":1,"1419":1,"1422":1,"1824":1,"1839":1,"1974":2,"2164":1,"2324":1,"2337":1,"2350":1,"2537":1,"2541":2,"2590":1,"2607":3,"2710":1,"2740":1,"2751":1,"2792":1,"2795":1,"2799":1,"2854":1}}],["categorically",{"2":{"875":1}}],["categories",{"0":{"222":1},"1":{"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1}}],["category",{"0":{"2621":1},"2":{"860":1,"948":1,"1038":3,"1042":1,"1044":1,"1045":1,"1191":1,"1382":1,"1383":1,"1943":1,"2236":1,"2621":4}}],["cascade",{"2":{"924":1,"925":1,"977":1,"1213":1,"1329":1}}],["casttotext",{"2":{"2621":1}}],["casts",{"2":{"2348":1,"2359":1,"2540":1,"2546":1,"2845":1}}],["cast",{"2":{"888":1,"911":1,"1395":1,"2109":1,"2360":3,"2530":1,"2590":1,"2608":1,"2734":1}}],["casting",{"2":{"803":1,"916":2,"1031":1,"1097":1,"1098":1,"1581":1,"2258":1}}],["casing",{"2":{"348":1,"2432":1,"2435":1,"2679":1,"2692":1}}],["cased",{"2":{"1040":1}}],["cases",{"0":{"1344":1},"1":{"1345":1,"1346":1,"1347":1,"1348":1},"2":{"624":1,"848":1,"863":1,"869":1,"874":1,"987":1,"1070":1,"1205":1,"1249":1,"1327":1,"1385":1,"1386":1,"1405":1,"1597":1,"1746":1,"1792":1,"2164":1,"2343":1,"2347":1,"2375":1,"2381":1,"2463":1,"2498":1,"2523":1,"2607":1}}],["case",{"0":{"866":1,"877":1,"2493":1},"1":{"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1},"2":{"74":2,"105":1,"121":1,"212":1,"258":1,"269":1,"379":1,"388":1,"390":1,"395":2,"407":2,"446":1,"448":1,"464":1,"522":1,"528":1,"537":1,"544":1,"663":1,"687":1,"709":1,"809":1,"818":1,"841":1,"844":1,"845":2,"849":4,"851":2,"852":1,"866":1,"869":2,"876":2,"916":1,"934":1,"948":1,"961":1,"995":1,"1037":1,"1042":1,"1130":1,"1133":1,"1134":1,"1228":1,"1229":1,"1230":1,"1382":4,"1383":1,"1385":3,"1386":1,"1394":1,"1395":1,"1398":2,"1399":1,"1402":2,"1403":1,"1408":2,"1414":1,"1421":1,"1429":1,"1457":1,"1459":1,"1460":1,"1523":1,"1567":1,"1571":1,"1576":1,"1727":1,"1792":15,"1825":1,"1841":1,"1854":1,"1862":1,"1878":1,"1879":1,"1880":1,"1922":1,"1955":1,"1959":1,"1967":2,"2036":1,"2097":1,"2102":1,"2192":1,"2222":1,"2223":1,"2255":2,"2277":1,"2322":2,"2328":1,"2333":1,"2375":2,"2379":1,"2380":2,"2394":1,"2395":1,"2422":1,"2425":1,"2435":1,"2438":2,"2459":1,"2463":1,"2464":1,"2471":1,"2483":1,"2493":4,"2518":2,"2520":1,"2523":2,"2528":1,"2537":2,"2544":1,"2546":1,"2554":1,"2595":1,"2614":1,"2678":1,"2679":1,"2692":1,"2694":1,"2695":2,"2700":1,"2724":1,"2734":1,"2764":2,"2766":2,"2864":1,"2877":1,"2880":1}}],["calculation",{"2":{"2421":1}}],["calculate",{"2":{"180":1,"686":1}}],["calibri",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["callback",{"2":{"1063":3,"1410":2,"1416":1,"1792":7,"2247":3,"2369":1,"2487":1,"2554":1,"2830":1}}],["callbacks",{"0":{"310":1,"1056":1},"2":{"305":1,"310":1,"1037":1,"1048":1,"1049":1,"1056":3,"1064":2,"1366":1,"1367":1,"1410":1,"2177":1,"2181":1,"2188":1,"2506":1,"2760":1}}],["caller",{"2":{"320":1,"386":1,"390":2,"394":1,"429":1,"448":1,"453":4,"454":1,"527":1,"529":1,"531":1,"534":1,"679":1,"876":1,"1074":1,"1415":1,"1825":2,"1832":1,"1833":1,"1961":1,"2310":1,"2395":2,"2450":1,"2466":1,"2481":1,"2526":3,"2535":1,"2739":1,"2812":1,"2860":3}}],["callers",{"2":{"298":1,"351":1,"529":1,"872":2,"1408":1,"1792":1,"1856":1,"2176":1,"2187":1,"2313":1,"2453":1,"2454":2,"2455":1,"2461":1}}],["called",{"0":{"1134":1},"2":{"157":1,"174":1,"361":1,"679":2,"841":1,"843":1,"844":1,"848":2,"851":1,"859":1,"918":1,"932":1,"983":1,"1017":1,"1029":1,"1102":1,"1129":2,"1134":1,"1329":1,"1382":1,"1385":1,"1394":1,"1398":1,"1401":1,"1431":1,"1792":8,"1825":1,"2372":1,"2451":1,"2481":1,"2504":1,"2559":2,"2622":1,"2813":1,"2815":1,"2829":1}}],["call",{"0":{"394":1,"531":1,"691":1,"1010":1,"1038":1,"2347":1},"1":{"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1},"2":{"214":5,"215":1,"239":1,"310":2,"317":1,"320":1,"324":1,"327":1,"383":1,"386":1,"387":1,"388":1,"394":1,"422":1,"423":1,"424":1,"436":1,"446":3,"448":1,"453":2,"454":1,"480":1,"528":1,"529":1,"689":2,"701":1,"835":1,"836":1,"848":1,"852":1,"856":1,"872":1,"876":1,"883":1,"885":1,"914":1,"915":2,"918":1,"922":1,"932":2,"933":1,"940":1,"941":1,"945":1,"966":1,"1029":2,"1035":2,"1037":3,"1038":2,"1039":1,"1041":1,"1043":2,"1044":2,"1045":1,"1054":1,"1056":2,"1062":2,"1063":1,"1070":2,"1074":1,"1077":1,"1078":2,"1102":2,"1105":2,"1106":1,"1107":1,"1126":1,"1130":1,"1132":1,"1134":1,"1154":1,"1185":2,"1209":1,"1317":1,"1332":1,"1338":2,"1346":1,"1348":2,"1383":1,"1398":5,"1399":1,"1405":2,"1409":3,"1410":2,"1426":1,"1430":1,"1431":1,"1434":1,"1438":1,"1439":1,"1441":3,"1504":1,"1567":1,"1581":1,"1650":1,"1651":1,"1655":1,"1663":1,"1689":1,"1722":1,"1738":1,"1743":3,"1744":1,"1746":1,"1747":2,"1792":13,"1800":1,"1805":2,"1810":1,"1813":1,"1820":1,"1822":1,"1824":2,"1825":1,"1827":1,"1832":2,"1833":2,"1834":1,"1850":1,"1852":1,"1929":1,"1930":2,"1961":1,"2049":1,"2123":2,"2128":2,"2130":2,"2132":2,"2164":1,"2166":1,"2177":1,"2222":2,"2223":2,"2277":4,"2283":1,"2297":1,"2307":1,"2329":1,"2344":1,"2346":1,"2347":2,"2348":1,"2383":2,"2393":1,"2397":2,"2400":2,"2405":1,"2414":1,"2466":1,"2479":2,"2481":6,"2494":1,"2495":1,"2498":1,"2500":1,"2502":6,"2504":4,"2506":4,"2510":1,"2526":1,"2527":2,"2529":1,"2551":2,"2635":2,"2759":1,"2760":2,"2763":1,"2764":1,"2765":2,"2767":2,"2768":1,"2769":1,"2770":1,"2802":1,"2803":1,"2804":1,"2807":1,"2810":1,"2811":4,"2815":1,"2817":1,"2830":2,"2833":1,"2858":1,"2862":1,"2865":1}}],["calling",{"2":{"214":1,"423":1,"865":1,"876":1,"932":1,"1010":1,"1016":1,"1104":1,"1132":1,"1134":1,"1215":1,"1393":1,"1449":1,"1518":1,"1733":1,"1743":1,"1792":3,"1832":1,"1961":1,"2264":1,"2265":1,"2346":2,"2425":1,"2481":1,"2580":1}}],["calls",{"0":{"209":1,"1029":1,"1376":1,"1744":1,"1929":1,"2345":1,"2346":1,"2766":1,"2767":1},"1":{"1745":1,"1746":1,"1747":1,"1930":1,"2346":1,"2347":1,"2348":1},"2":{"184":1,"186":1,"209":1,"223":1,"261":3,"264":1,"266":2,"285":1,"327":1,"383":1,"390":1,"395":1,"415":1,"421":1,"423":1,"436":1,"445":1,"446":1,"480":2,"527":1,"529":1,"535":1,"826":1,"845":1,"849":1,"871":1,"874":1,"876":1,"881":1,"919":1,"920":1,"933":1,"1004":1,"1010":2,"1011":2,"1014":2,"1015":4,"1016":1,"1023":2,"1029":1,"1033":1,"1037":1,"1039":1,"1043":1,"1060":1,"1070":1,"1102":4,"1104":4,"1106":2,"1107":1,"1108":3,"1126":1,"1129":2,"1132":1,"1133":1,"1193":2,"1211":1,"1215":1,"1231":1,"1328":1,"1331":1,"1338":2,"1340":1,"1346":2,"1351":1,"1376":3,"1398":2,"1399":1,"1403":1,"1405":5,"1409":1,"1419":1,"1428":1,"1434":1,"1440":1,"1441":1,"1517":1,"1745":1,"1746":1,"1747":1,"1789":1,"1792":4,"1796":1,"1824":1,"1834":1,"1850":2,"1862":2,"1868":1,"1881":1,"1929":1,"1933":1,"1961":3,"2164":3,"2165":3,"2183":1,"2184":1,"2185":1,"2222":1,"2239":1,"2265":1,"2283":1,"2284":1,"2292":1,"2293":1,"2297":1,"2303":1,"2338":1,"2344":1,"2346":1,"2347":1,"2348":1,"2372":2,"2382":1,"2383":1,"2438":1,"2463":1,"2483":1,"2500":2,"2504":2,"2506":1,"2527":1,"2529":1,"2532":1,"2533":1,"2540":1,"2550":1,"2580":1,"2614":1,"2622":2,"2656":1,"2759":3,"2765":1,"2766":2,"2767":1,"2770":2,"2791":1,"2810":1,"2834":1,"2836":1,"2845":1,"2865":1,"2870":1}}],["callable",{"2":{"173":1,"263":1,"320":1,"448":1,"1077":1,"1378":1,"1393":1,"1412":1,"2344":1,"2489":1,"2543":1,"2857":1}}],["caution",{"2":{"1511":1,"1792":1,"2265":1}}],["causing",{"2":{"1139":1,"1254":1,"2362":1,"2367":1,"2546":1,"2597":1,"2648":1}}],["caused",{"2":{"1324":1,"2258":1,"2353":1,"2409":1,"2666":1}}],["causes",{"2":{"664":1,"2258":1,"2359":1,"2721":1,"2722":1}}],["cause",{"0":{"2411":1,"2421":1,"2442":1},"2":{"102":1,"948":1,"1641":1,"1774":1,"1792":1,"1983":1,"2088":1,"2365":1,"2380":1,"2445":1,"2495":1,"2504":1,"2529":1,"2542":1,"2685":1,"2865":1}}],["caught",{"2":{"109":1,"864":1,"978":1,"982":1,"997":1,"1005":1,"1409":1,"1527":1,"2007":1,"2328":1,"2413":1,"2444":2,"2445":1,"2840":1}}],["cadence",{"2":{"106":1,"872":1,"1067":1}}],["candidates",{"2":{"2482":1}}],["cancels",{"2":{"2466":3}}],["cancellable",{"2":{"2615":1}}],["cancellation",{"2":{"2466":3,"2615":7}}],["cancellationtoken",{"0":{"2615":1},"2":{"2236":1,"2461":3,"2615":3}}],["cancelled",{"2":{"139":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":2,"2255":2,"2615":2}}],["canceled",{"2":{"1674":1,"1792":2,"2255":5}}],["cancelcomputeurl",{"2":{"1572":1}}],["cancel",{"2":{"1043":1,"1572":1}}],["canonical",{"2":{"305":1,"864":1,"1792":1,"1830":1,"2181":1,"2451":1,"2481":1}}],["cannot",{"2":{"63":1,"108":1,"188":2,"389":1,"414":1,"529":1,"576":1,"691":1,"816":3,"852":1,"857":3,"864":3,"865":1,"872":1,"873":1,"919":2,"922":1,"932":1,"940":1,"1054":1,"1081":1,"1106":1,"1115":1,"1129":2,"1133":1,"1185":3,"1378":1,"1408":1,"1441":3,"1448":1,"1522":1,"1595":1,"1624":3,"1651":1,"1658":1,"1792":7,"1818":1,"1856":1,"1867":3,"1961":1,"2040":1,"2099":1,"2110":2,"2138":1,"2140":3,"2142":2,"2146":2,"2153":1,"2284":1,"2296":2,"2297":1,"2300":1,"2380":1,"2438":1,"2455":1,"2476":1,"2529":1,"2530":2,"2531":1,"2533":1,"2575":5,"2608":3,"2733":1,"2855":2,"2865":1,"2876":1,"2881":1}}],["can",{"0":{"1038":1,"1396":1,"2483":1,"2714":1,"2719":1,"2729":1,"2740":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1},"2":{"9":2,"14":1,"18":1,"19":1,"20":1,"41":1,"51":1,"62":1,"77":1,"87":2,"108":1,"119":1,"206":1,"209":1,"211":1,"212":1,"244":1,"253":1,"258":2,"302":1,"306":1,"309":1,"310":1,"312":1,"317":1,"327":1,"336":1,"362":1,"363":1,"366":1,"370":1,"373":1,"377":1,"378":1,"390":2,"404":1,"407":1,"409":2,"411":1,"414":1,"448":1,"453":2,"458":2,"469":1,"470":2,"529":3,"534":1,"542":1,"544":1,"556":2,"560":1,"587":1,"619":1,"646":1,"650":2,"655":1,"664":1,"669":1,"673":1,"687":1,"691":1,"706":1,"708":1,"711":1,"714":1,"737":1,"751":1,"801":1,"809":2,"814":1,"832":1,"834":1,"835":2,"836":1,"841":3,"843":1,"844":1,"845":2,"847":1,"848":8,"851":3,"852":8,"854":1,"856":1,"859":1,"860":1,"861":1,"863":1,"864":5,"868":1,"871":1,"872":2,"873":2,"875":2,"877":1,"884":1,"902":1,"903":1,"904":2,"912":1,"913":2,"914":4,"915":3,"916":4,"917":5,"918":6,"919":3,"920":2,"922":2,"926":1,"932":2,"933":1,"934":1,"941":1,"945":1,"947":1,"960":1,"965":1,"966":1,"974":3,"990":1,"992":2,"993":2,"994":2,"1013":1,"1015":2,"1023":1,"1029":1,"1032":1,"1033":1,"1037":2,"1038":2,"1039":1,"1041":1,"1042":1,"1045":2,"1046":1,"1048":1,"1049":2,"1052":1,"1055":1,"1057":1,"1059":1,"1070":1,"1073":4,"1075":1,"1077":4,"1078":3,"1079":2,"1081":1,"1083":1,"1095":1,"1096":1,"1097":2,"1098":2,"1100":1,"1101":1,"1102":1,"1106":3,"1107":1,"1108":1,"1111":1,"1113":1,"1114":1,"1122":1,"1125":1,"1128":1,"1129":3,"1130":1,"1133":4,"1134":1,"1138":4,"1139":2,"1140":1,"1147":1,"1148":1,"1161":1,"1162":1,"1163":1,"1166":1,"1169":1,"1170":1,"1174":1,"1176":1,"1183":1,"1185":6,"1187":1,"1193":4,"1206":1,"1209":1,"1213":1,"1218":1,"1224":1,"1237":1,"1240":1,"1241":1,"1244":1,"1254":3,"1272":1,"1305":1,"1309":1,"1325":1,"1326":1,"1332":1,"1340":1,"1343":1,"1363":1,"1372":1,"1373":1,"1377":2,"1378":1,"1382":2,"1383":1,"1385":5,"1386":16,"1388":1,"1390":4,"1391":4,"1392":1,"1393":6,"1394":6,"1395":3,"1396":4,"1398":5,"1399":2,"1402":1,"1403":8,"1405":10,"1406":1,"1412":1,"1413":1,"1419":1,"1431":1,"1435":3,"1437":3,"1440":1,"1441":3,"1445":1,"1453":1,"1457":1,"1459":1,"1464":1,"1490":1,"1504":1,"1511":1,"1515":1,"1516":1,"1518":1,"1547":1,"1559":1,"1571":2,"1588":1,"1606":1,"1607":1,"1609":1,"1614":1,"1620":1,"1632":1,"1640":1,"1651":1,"1659":1,"1662":1,"1685":1,"1689":1,"1697":1,"1706":2,"1716":1,"1717":1,"1727":1,"1731":1,"1732":1,"1733":1,"1738":1,"1739":1,"1770":1,"1792":45,"1820":1,"1822":1,"1825":1,"1827":1,"1832":1,"1833":2,"1834":1,"1837":1,"1840":1,"1843":1,"1852":1,"1855":1,"1858":1,"1868":1,"1874":1,"1887":1,"1915":1,"1924":2,"1955":1,"1958":2,"1969":1,"1983":1,"1989":1,"2000":1,"2008":1,"2016":1,"2021":1,"2025":1,"2039":1,"2047":1,"2056":1,"2058":1,"2059":2,"2079":1,"2089":1,"2094":1,"2098":1,"2103":1,"2139":1,"2141":1,"2143":1,"2148":1,"2149":1,"2166":1,"2175":1,"2178":1,"2179":1,"2182":1,"2184":1,"2185":1,"2192":1,"2195":1,"2200":1,"2207":1,"2220":1,"2223":1,"2227":1,"2245":1,"2252":2,"2253":1,"2257":2,"2264":2,"2265":1,"2274":1,"2277":2,"2284":1,"2285":1,"2287":1,"2297":2,"2302":1,"2314":1,"2330":1,"2333":1,"2340":1,"2344":1,"2347":1,"2375":1,"2379":1,"2383":1,"2389":1,"2393":1,"2395":1,"2397":2,"2402":1,"2404":1,"2415":1,"2419":1,"2422":1,"2434":1,"2438":2,"2455":1,"2466":1,"2476":1,"2479":3,"2481":4,"2482":1,"2483":1,"2484":1,"2500":1,"2502":1,"2509":1,"2511":1,"2519":1,"2527":1,"2530":1,"2531":1,"2532":3,"2533":2,"2534":2,"2537":2,"2540":2,"2542":1,"2549":2,"2554":2,"2565":1,"2575":2,"2580":1,"2581":1,"2587":1,"2596":1,"2615":1,"2632":1,"2633":1,"2634":1,"2635":3,"2653":1,"2662":1,"2670":1,"2677":1,"2678":1,"2680":1,"2681":1,"2684":1,"2687":1,"2689":1,"2693":1,"2695":1,"2712":1,"2719":1,"2736":1,"2741":1,"2755":2,"2758":2,"2764":1,"2765":1,"2768":1,"2779":1,"2785":2,"2799":1,"2803":3,"2806":1,"2808":1,"2812":2,"2815":1,"2820":1,"2822":1,"2824":2,"2825":1,"2827":1,"2830":1,"2831":1,"2833":2,"2840":1,"2847":2,"2858":2,"2862":1,"2869":2,"2875":1,"2881":1}}],["chinese",{"2":{"2607":1}}],["chip",{"2":{"2535":2}}],["children",{"2":{"2532":1}}],["child",{"2":{"2157":7,"2543":10}}],["chilling",{"2":{"913":1,"919":2}}],["chmod",{"2":{"1117":1,"2782":1,"2783":1,"2784":1}}],["chf",{"2":{"1024":1}}],["choking",{"2":{"2615":1}}],["chose",{"2":{"2389":1,"2415":1}}],["chosen",{"2":{"860":2,"2155":1,"2442":1}}],["choosing",{"0":{"2178":1},"2":{"857":1,"868":1,"1044":1,"2421":1}}],["chooses",{"2":{"1130":1,"1133":1,"2175":1}}],["choose",{"0":{"837":1,"1120":1,"1121":1,"1122":1,"1123":1},"1":{"1121":1,"1122":1,"1123":1},"2":{"679":1,"848":1,"860":1,"879":1,"1055":1,"1094":2,"1096":2,"1367":1,"1385":1,"2178":1,"2193":1,"2353":1,"2712":1,"2806":1}}],["choices",{"2":{"1403":1}}],["choice",{"2":{"852":1,"876":1,"1127":1,"1272":1,"1280":1,"1405":1,"1412":1,"1792":1,"2466":1,"2829":1}}],["cheaply",{"2":{"2767":1}}],["cheaper",{"2":{"1429":1}}],["cheapest",{"2":{"1044":2}}],["cheap",{"2":{"1075":1,"1382":3,"2482":1,"2809":1}}],["cheating",{"2":{"852":1}}],["chen",{"2":{"913":1}}],["chew",{"2":{"872":1}}],["cheerfully",{"2":{"859":1}}],["checkout",{"2":{"1386":1}}],["checklist",{"0":{"2429":1},"2":{"910":1,"1382":1}}],["checker",{"2":{"871":1,"872":1,"1037":1,"1073":1,"1080":1,"1404":2}}],["checked",{"2":{"1":1,"22":1,"354":1,"992":2,"1061":1,"1075":1,"1076":1,"1382":1,"1420":1,"2184":1,"2433":1,"2741":1,"2868":1,"2872":1}}],["checking",{"0":{"972":1,"981":1,"1004":1},"1":{"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":2,"983":2,"984":2,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1},"2":{"747":1,"879":1,"972":1,"974":1,"975":2,"976":2,"981":1,"985":1,"996":3,"998":3,"1009":1,"1037":1,"1086":1,"1096":1,"1127":1,"1368":1,"1386":1,"1405":1,"1408":1,"1409":3,"1419":1,"1609":1,"1792":2,"2098":1,"2164":3,"2165":2,"2534":1,"2597":1,"2661":1,"2722":1,"2840":1,"2858":1,"2868":1}}],["checks",{"0":{"1762":1},"1":{"1763":1,"1764":1,"1765":1,"1766":1,"1767":1,"1768":1,"1769":1,"1770":1,"1771":1,"1772":1,"1773":1,"1774":1,"1775":1,"1776":1,"1777":1,"1778":1,"1779":1,"1780":1,"1781":1,"1782":1,"1783":1,"1784":1},"2":{"10":1,"37":1,"320":1,"327":1,"638":1,"641":1,"690":1,"696":1,"782":2,"784":2,"807":1,"835":1,"864":1,"869":2,"873":1,"992":2,"1079":2,"1080":1,"1100":1,"1127":1,"1181":1,"1243":1,"1328":2,"1329":1,"1337":1,"1338":1,"1351":1,"1358":1,"1719":1,"1762":1,"1764":2,"1766":1,"1768":1,"1771":2,"1774":1,"1783":1,"1791":1,"1792":6,"1824":1,"1834":1,"1974":1,"2070":1,"2137":1,"2171":1,"2181":2,"2309":1,"2481":1,"2490":2,"2529":1,"2562":1,"2566":1,"2575":1,"2607":1,"2608":1,"2621":1,"2634":7,"2659":1,"2868":2}}],["check",{"0":{"1337":1,"1765":1,"1770":1,"1775":1,"1781":1,"2634":1},"1":{"1766":1,"1767":1,"1768":1,"1771":1},"2":{"8":1,"298":1,"354":1,"747":3,"753":4,"757":4,"768":3,"776":2,"781":1,"782":4,"783":1,"784":4,"785":1,"786":2,"835":1,"844":1,"845":2,"851":1,"852":2,"854":1,"864":5,"865":1,"904":2,"913":1,"914":1,"992":1,"1041":1,"1045":1,"1079":1,"1080":1,"1100":3,"1129":1,"1213":1,"1254":1,"1332":1,"1335":1,"1338":1,"1339":1,"1342":1,"1349":3,"1358":6,"1360":2,"1386":2,"1405":1,"1410":2,"1439":1,"1504":1,"1609":1,"1719":1,"1762":1,"1764":3,"1765":1,"1767":1,"1769":1,"1776":1,"1778":1,"1782":1,"1783":1,"1791":1,"1792":9,"1825":1,"2070":1,"2106":1,"2128":1,"2176":1,"2234":1,"2266":1,"2267":1,"2314":1,"2389":1,"2394":1,"2481":2,"2493":1,"2537":1,"2621":2,"2634":10,"2638":1,"2664":2,"2669":1,"2721":2,"2723":1,"2785":2,"2786":1,"2788":1,"2868":2,"2878":1,"2881":1}}],["chunked",{"2":{"1255":1,"1258":1,"2824":1}}],["chunk",{"2":{"849":1}}],["chunks",{"2":{"845":1,"851":1}}],["churn",{"2":{"427":4,"1382":1}}],["challenging",{"2":{"1385":1}}],["challenged",{"2":{"1792":1}}],["challengecolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["challengecommand",{"0":{"1197":1},"2":{"1197":2,"1204":1,"1469":1,"1482":1,"1498":1,"1499":1,"1503":1,"1504":1,"1505":1,"1792":1}}],["challengetimeoutminutes",{"2":{"1227":1,"1792":1,"1877":1,"1893":1}}],["challengeaddexistingusercommand",{"0":{"1232":1,"1882":1},"2":{"1221":1,"1233":1,"1237":1,"1792":3,"1871":1,"1883":1,"1893":1}}],["challengeauthenticationcommand",{"0":{"1234":1,"1884":1},"2":{"1217":1,"1222":1,"1792":1,"1872":1,"1893":1}}],["challengeidcolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["challengeid",{"2":{"1220":2,"1221":2,"1222":2,"1792":3}}],["challengeregistrationcommand",{"0":{"1233":1,"1883":1},"2":{"1217":1,"1220":1,"1792":1,"1870":1,"1893":1}}],["challenges",{"2":{"1213":2,"1214":2,"1227":1,"1232":1,"1234":1,"1235":1,"1252":1,"1792":1,"1877":1,"2481":1}}],["challenge",{"0":{"37":1,"38":1,"39":1,"40":1,"50":1,"60":1,"1214":1,"1501":1,"1504":1},"2":{"29":1,"30":1,"31":1,"32":1,"37":6,"38":5,"39":3,"40":7,"41":4,"42":1,"50":2,"51":1,"60":2,"63":1,"868":1,"1045":1,"1139":1,"1197":1,"1210":1,"1211":1,"1213":2,"1214":16,"1217":2,"1220":7,"1221":7,"1222":7,"1226":1,"1232":13,"1234":14,"1235":12,"1236":1,"1240":2,"1243":1,"1244":1,"1252":1,"1253":1,"1482":1,"1499":1,"1503":1,"1504":2,"1506":1,"1508":1,"1792":19,"1825":1,"1827":1,"1876":1,"1882":2,"1884":3,"1885":4,"1887":1,"1889":2,"1893":4,"2363":1,"2437":1,"2559":2}}],["chased",{"2":{"1382":1}}],["champion",{"2":{"1266":2}}],["chatter",{"2":{"1439":1}}],["chatty",{"2":{"861":1}}],["chat",{"0":{"1302":1,"1306":1,"1320":1,"1372":1,"2836":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":2,"1308":2,"1309":2,"1310":2,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1},"2":{"1037":1,"1103":1,"1105":1,"1302":4,"1303":1,"1307":1,"1312":1,"1320":7,"1323":2,"1327":1,"1372":2,"2164":3,"2165":3,"2827":1,"2829":1,"2834":1,"2836":1,"2837":4}}],["chapter",{"0":{"1404":1},"2":{"843":2,"847":1,"851":1,"852":1,"854":1,"861":2,"863":1}}],["charge",{"2":{"2438":1}}],["charger",{"2":{"1044":2}}],["charges",{"2":{"876":1}}],["charlie",{"2":{"977":2,"979":2,"980":1,"986":2,"988":2,"990":5,"2009":2}}],["charset=",{"2":{"1685":1,"1792":1}}],["chars>",{"2":{"956":1,"1374":1}}],["chars",{"2":{"930":2,"1460":1,"1792":3,"2177":1,"2375":1,"2402":1,"2604":1}}],["char",{"2":{"928":1,"1792":4,"1909":1,"2270":1,"2394":2,"2402":2,"2431":1,"2435":1,"2604":1}}],["charts",{"2":{"834":1,"837":1,"860":1,"866":1,"971":1,"1206":1}}],["chart",{"2":{"833":1,"834":2}}],["characters",{"2":{"675":1,"747":1,"767":1,"782":1,"784":1,"786":2,"817":1,"928":1,"930":4,"1053":1,"1062":1,"1453":1,"1454":1,"1511":3,"1618":1,"1620":1,"1792":10,"1917":1,"2002":2,"2125":1,"2140":2,"2141":2,"2144":1,"2145":2,"2146":2,"2148":1,"2175":1,"2265":3,"2270":1,"2371":2,"2554":2,"2575":2,"2589":7,"2603":1,"2607":1,"2662":1,"2737":1}}],["character",{"2":{"308":1,"382":1,"768":1,"891":1,"1516":1,"2002":1,"2128":1,"2265":2,"2334":1,"2534":1,"2540":1,"2575":2,"2841":1}}],["chains",{"2":{"1717":1}}],["chain",{"2":{"871":1,"1230":1,"1706":2,"1792":2,"1880":1,"2533":1,"2621":1,"2633":1}}],["chaining",{"0":{"885":1},"2":{"760":1,"761":1,"771":1,"882":1,"883":1,"1193":1,"2490":1}}],["chained",{"2":{"209":1}}],["chances",{"2":{"2400":1}}],["chance",{"2":{"1409":1}}],["changing",{"2":{"390":1,"852":1,"1350":1,"1368":1,"1487":1,"1792":2,"2056":1,"2438":1,"2476":1}}],["changelog",{"0":{"2220":1,"2241":1,"2243":1,"2260":1,"2262":1,"2268":1,"2275":1,"2280":1,"2298":1,"2311":1,"2315":1,"2373":1,"2387":1,"2408":1,"2418":1,"2439":1,"2449":1,"2458":1,"2467":1,"2473":1,"2478":1,"2499":1,"2507":1,"2514":1,"2524":1,"2547":1,"2552":1,"2556":1,"2560":1,"2563":1,"2568":1,"2570":1,"2573":1,"2578":1,"2583":1,"2592":1,"2598":1,"2601":1,"2605":1,"2609":1,"2612":1,"2616":1,"2619":1,"2623":1,"2630":1,"2636":1,"2639":1,"2643":1,"2646":1,"2657":1,"2675":1},"1":{"2221":1,"2222":1,"2223":1,"2224":1,"2225":1,"2226":1,"2227":1,"2228":1,"2229":1,"2230":1,"2231":1,"2232":1,"2233":1,"2234":1,"2235":1,"2236":1,"2237":1,"2238":1,"2239":1,"2240":1,"2242":1,"2244":1,"2245":1,"2246":1,"2247":1,"2248":1,"2249":1,"2250":1,"2251":1,"2252":1,"2253":1,"2254":1,"2255":1,"2256":1,"2257":1,"2258":1,"2259":1,"2261":1,"2263":1,"2264":1,"2265":1,"2266":1,"2267":1,"2269":1,"2270":1,"2271":1,"2272":1,"2273":1,"2274":1,"2276":1,"2277":1,"2278":1,"2279":1,"2281":1,"2282":1,"2283":1,"2284":1,"2285":1,"2286":1,"2287":1,"2288":1,"2289":1,"2290":1,"2291":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2299":1,"2300":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":1,"2312":1,"2313":1,"2314":1,"2316":1,"2317":1,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1,"2331":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1,"2345":1,"2346":1,"2347":1,"2348":1,"2349":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2361":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2368":1,"2369":1,"2370":1,"2371":1,"2372":1,"2374":1,"2375":1,"2376":1,"2377":1,"2378":1,"2379":1,"2380":1,"2381":1,"2382":1,"2383":1,"2384":1,"2385":1,"2386":1,"2388":1,"2389":1,"2390":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2396":1,"2397":1,"2398":1,"2399":1,"2400":1,"2401":1,"2402":1,"2403":1,"2404":1,"2405":1,"2406":1,"2407":1,"2409":1,"2410":1,"2411":1,"2412":1,"2413":1,"2414":1,"2415":1,"2416":1,"2417":1,"2419":1,"2420":1,"2421":1,"2422":1,"2423":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2429":1,"2430":1,"2431":1,"2432":1,"2433":1,"2434":1,"2435":1,"2436":1,"2437":1,"2438":1,"2440":1,"2441":1,"2442":1,"2443":1,"2444":1,"2445":1,"2446":1,"2447":1,"2448":1,"2450":1,"2451":1,"2452":1,"2453":1,"2454":1,"2455":1,"2456":1,"2457":1,"2459":1,"2460":1,"2461":1,"2462":1,"2463":1,"2464":1,"2465":1,"2466":1,"2468":1,"2469":1,"2470":1,"2471":1,"2472":1,"2474":1,"2475":1,"2476":1,"2477":1,"2479":1,"2480":1,"2481":1,"2482":1,"2483":1,"2484":1,"2485":1,"2486":1,"2487":1,"2488":1,"2489":1,"2490":1,"2491":1,"2492":1,"2493":1,"2494":1,"2495":1,"2496":1,"2497":1,"2498":1,"2500":1,"2501":1,"2502":1,"2503":1,"2504":1,"2505":1,"2506":1,"2508":1,"2509":1,"2510":1,"2511":1,"2512":1,"2513":1,"2515":1,"2516":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1,"2522":1,"2523":1,"2525":1,"2526":1,"2527":1,"2528":1,"2529":1,"2530":1,"2531":1,"2532":1,"2533":1,"2534":1,"2535":1,"2536":1,"2537":1,"2538":1,"2539":1,"2540":1,"2541":1,"2542":1,"2543":1,"2544":1,"2545":1,"2546":1,"2548":1,"2549":1,"2550":1,"2551":1,"2553":1,"2554":1,"2555":1,"2557":1,"2558":1,"2559":1,"2561":1,"2562":1,"2564":1,"2565":1,"2566":1,"2567":1,"2569":1,"2571":1,"2572":1,"2574":1,"2575":1,"2576":1,"2577":1,"2579":1,"2580":1,"2581":1,"2582":1,"2584":1,"2585":1,"2586":1,"2587":1,"2588":1,"2589":1,"2590":1,"2591":1,"2593":1,"2594":1,"2595":1,"2596":1,"2597":1,"2599":1,"2600":1,"2602":1,"2603":1,"2604":1,"2606":1,"2607":1,"2608":1,"2610":1,"2611":1,"2613":1,"2614":1,"2615":1,"2617":1,"2618":1,"2620":1,"2621":1,"2622":1,"2624":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2631":1,"2632":1,"2633":1,"2634":1,"2635":1,"2637":1,"2638":1,"2640":1,"2641":1,"2642":1,"2644":1,"2645":1,"2647":1,"2648":1,"2649":1,"2650":1,"2651":1,"2652":1,"2653":1,"2654":1,"2655":1,"2656":1,"2658":1,"2659":1,"2660":1,"2661":1,"2662":1,"2663":1,"2664":1,"2665":1,"2666":1,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1,"2674":1,"2676":1,"2677":1,"2678":1,"2679":1},"2":{"1066":1,"1069":1,"1071":1,"1082":1,"1380":1,"2013":1,"2220":3,"2242":1,"2244":1,"2261":1,"2263":1,"2269":1,"2276":1,"2281":1,"2299":1,"2312":1,"2316":1,"2374":1,"2388":1,"2409":1,"2419":1,"2440":1,"2450":1,"2459":1,"2468":1,"2474":1,"2479":1,"2500":1,"2508":1,"2515":1,"2525":1,"2548":1,"2553":1,"2557":1,"2561":1,"2564":1,"2569":1,"2571":1,"2574":1,"2579":1,"2584":1,"2593":1,"2599":1,"2602":1,"2606":1,"2610":1,"2613":1,"2617":1,"2620":1,"2624":1,"2631":1,"2637":1,"2640":1,"2644":1,"2647":1,"2658":1,"2676":1,"2859":1,"2882":1}}],["changed",{"0":{"1266":1,"2249":1,"2412":1,"2415":1,"2422":1,"2443":1,"2460":1,"2469":1,"2475":1,"2509":1,"2516":1},"1":{"2461":1,"2462":1,"2463":1,"2470":1,"2471":1,"2476":1,"2477":1,"2510":1,"2511":1,"2512":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1},"2":{"655":1,"686":1,"843":1,"851":1,"872":1,"1073":1,"1080":1,"1257":1,"1382":2,"1403":1,"1419":1,"1639":1,"1644":1,"1792":9,"2106":3,"2153":1,"2156":2,"2158":1,"2247":1,"2252":1,"2259":1,"2370":1,"2486":1,"2522":1,"2537":3,"2541":1,"2542":2,"2562":1,"2614":1,"2742":2,"2822":1,"2878":3}}],["change",{"0":{"592":1,"1419":1,"2454":1,"2511":1},"2":{"305":1,"308":1,"423":1,"436":1,"592":3,"834":1,"848":1,"849":1,"852":1,"854":2,"871":2,"872":6,"874":1,"878":1,"879":1,"880":1,"910":1,"911":1,"924":1,"972":1,"973":1,"975":1,"984":1,"997":2,"1046":1,"1067":1,"1068":1,"1069":1,"1129":3,"1130":1,"1139":1,"1157":1,"1183":2,"1193":4,"1203":1,"1374":1,"1382":3,"1403":1,"1406":1,"1407":1,"1409":1,"1418":1,"1422":1,"1430":1,"1436":2,"1464":1,"1792":3,"1908":1,"1948":1,"2040":1,"2106":2,"2153":2,"2156":3,"2157":4,"2181":1,"2216":1,"2242":1,"2258":1,"2357":1,"2389":1,"2395":1,"2398":1,"2426":1,"2434":1,"2450":1,"2454":1,"2479":1,"2487":1,"2537":1,"2539":1,"2541":2,"2542":3,"2543":6,"2546":3,"2699":1,"2857":1,"2878":3}}],["changes",{"0":{"984":1,"1071":1,"1259":1,"2248":1,"2252":1,"2258":1,"2259":1,"2267":1,"2349":1,"2368":1,"2372":1,"2485":1,"2642":1},"1":{"2249":1,"2250":1,"2251":1,"2252":1,"2259":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2369":1,"2370":1,"2371":1,"2372":1,"2486":1,"2487":1},"2":{"106":1,"180":1,"587":1,"686":1,"777":1,"848":2,"849":1,"863":1,"872":4,"875":1,"879":2,"880":1,"888":2,"902":1,"910":1,"911":1,"974":1,"982":1,"983":1,"988":1,"996":2,"1001":1,"1005":1,"1046":1,"1094":1,"1101":1,"1139":3,"1170":1,"1179":1,"1203":1,"1205":1,"1206":1,"1208":1,"1247":1,"1254":2,"1357":1,"1363":1,"1367":1,"1377":1,"1379":1,"1386":1,"1388":1,"1409":1,"1460":1,"1559":1,"1645":1,"1690":1,"1789":1,"1792":4,"2106":1,"2154":1,"2156":5,"2157":1,"2158":1,"2164":1,"2165":1,"2168":1,"2221":2,"2270":1,"2289":1,"2375":1,"2378":1,"2385":1,"2393":1,"2396":1,"2409":1,"2419":2,"2423":1,"2437":1,"2448":2,"2450":1,"2457":2,"2525":1,"2537":3,"2540":1,"2542":5,"2543":1,"2546":2,"2597":1,"2615":1,"2621":1,"2742":2,"2878":2}}],["channels",{"0":{"1326":1},"2":{"1799":1,"1811":1,"2362":2,"2481":1,"2752":1,"2794":1,"2804":1}}],["channelname",{"2":{"1318":3,"1321":2}}],["channel",{"0":{"2795":1,"2801":1},"2":{"3":1,"1305":1,"1318":2,"1320":1,"1326":1,"1792":3,"1801":1,"1802":2,"2094":1,"2104":1,"2108":1,"2114":1,"2343":1,"2536":1,"2537":1,"2750":1,"2752":1,"2794":3,"2795":6,"2798":1,"2801":1,"2802":1,"2803":1,"2804":1,"2805":1,"2828":1,"2880":2}}],["c",{"0":{"2487":1},"2":{"1":1,"297":2,"491":1,"650":1,"845":1,"847":1,"852":2,"856":1,"860":1,"865":1,"866":1,"867":1,"868":1,"869":3,"873":2,"881":1,"1026":2,"1037":1,"1086":2,"1087":1,"1088":1,"1105":4,"1211":1,"1333":1,"1366":1,"1792":2,"1840":1,"1868":1,"1967":1,"1969":1,"1971":1,"1972":2,"2171":3,"2223":1,"2265":1,"2371":1,"2479":1,"2483":1,"2588":2,"2760":2,"2792":3,"2828":2}}],["coherent",{"2":{"2498":1}}],["coherently",{"2":{"2379":1}}],["coalescing",{"2":{"1430":1,"2461":1,"2465":2,"2466":5,"2502":1}}],["coalesced",{"2":{"2463":1,"2466":1}}],["coalesce",{"2":{"452":1,"528":1,"764":1,"765":1,"766":1,"774":2,"883":2,"884":2,"885":3,"888":1,"904":2,"929":1,"1021":2,"1214":1,"1232":1,"1234":1,"1338":1,"1339":1,"1347":1,"1427":1,"2461":1,"2462":1,"2466":1,"2762":1,"2765":1,"2810":1,"2815":1}}],["coalesces",{"2":{"214":1,"1743":1,"2463":1,"2466":1,"2502":1}}],["cose",{"2":{"1236":1,"1237":2,"1792":2,"1886":1,"1887":2}}],["costs",{"2":{"872":2,"1181":1,"1403":1,"2537":1,"2868":1}}],["costumes",{"2":{"853":1}}],["cost",{"0":{"845":1,"849":1,"853":1,"861":1,"865":1,"1131":1,"1132":1,"1206":1},"1":{"854":1,"855":1,"856":1,"857":1,"1132":1,"1133":1},"2":{"308":1,"845":1,"849":1,"857":1,"860":1,"861":1,"865":1,"869":4,"871":1,"872":1,"873":1,"874":1,"1037":1,"1132":3,"1281":1,"1974":1,"2324":1,"2607":1,"2858":1}}],["coffee",{"2":{"1081":1}}],["coincidentally",{"2":{"2452":1}}],["coincidence",{"2":{"851":1}}],["coingecko",{"2":{"1018":2,"1019":2,"1023":2,"1026":1,"1398":1,"2766":1}}],["co",{"0":{"988":1},"2":{"1005":1,"1006":1,"1009":1,"1107":1,"1382":1,"1400":1,"1792":2,"2095":2,"2167":1,"2538":1,"2539":1,"2545":1,"2861":1}}],["cockroachdb",{"2":{"848":2}}],["coding",{"2":{"876":1,"1081":1,"1370":2,"1402":2,"2822":1,"2824":1,"2850":2}}],["codified",{"2":{"848":1,"863":1}}],["codd",{"2":{"848":2,"852":3}}],["codenpgsqlrest",{"2":{"2526":1,"2537":2,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2677":1,"2678":1}}],["codewarn",{"2":{"2392":1}}],["codewww",{"2":{"48":1}}],["codepass",{"2":{"2535":1}}],["codepghost=localhost",{"2":{"2272":1}}],["codepostgresql",{"2":{"975":1,"1005":1}}],["codetype",{"2":{"2446":1}}],["codetimeout",{"2":{"2264":1}}],["codetarget",{"2":{"414":1,"436":1,"2811":1}}],["coderate",{"2":{"2257":1}}],["codeunhealthy",{"2":{"1782":1}}],["codehealthy",{"2":{"1782":1}}],["codehost=replica1",{"2":{"1175":1}}],["codehttp",{"2":{"243":1}}],["codefinalize",{"2":{"1130":1}}],["codefor",{"2":{"685":1}}],["codemethod",{"2":{"1017":1,"1728":1,"2264":1}}],["codedatabase",{"2":{"1008":2}}],["codedecrypt",{"2":{"186":1}}],["code2",{"2":{"976":1}}],["codeenabled",{"2":{"2445":1}}],["codeendpoint",{"2":{"2394":1,"2395":1,"2879":1}}],["codeencrypt",{"2":{"184":1}}],["codeerror",{"2":{"919":1,"982":1,"2255":1}}],["codebases",{"2":{"859":1,"947":1}}],["codebase",{"2":{"849":1,"869":1,"871":1,"872":1,"968":1,"1011":1,"1135":1,"1179":1,"1402":4,"1441":1}}],["codegen",{"0":{"1417":2,"1420":1},"1":{"1418":2,"1419":2,"1420":2,"1421":2},"2":{"835":2,"868":1,"1416":1,"1417":2,"1418":1,"1419":1,"1420":3}}],["codeget",{"2":{"462":2,"463":1,"464":1,"959":2,"1017":1,"1023":1,"1139":1,"1148":1,"1518":1,"1737":1,"1823":1,"2056":1,"2265":1,"2321":1,"2674":1,"2762":1}}],["codecolumn",{"2":{"2405":1}}],["codecontent",{"2":{"493":1,"544":1}}],["codecache",{"2":{"2041":1}}],["codeclient",{"2":{"414":1,"1015":1,"2302":1}}],["codevalue1|value2|value3",{"2":{"491":1}}],["code5",{"2":{"280":1}}],["code19",{"2":{"2107":1,"2537":1}}],["code1",{"2":{"274":1}}],["code30",{"2":{"273":1,"275":1}}],["code30seconds",{"2":{"272":1}}],["code30s",{"2":{"271":1}}],["codesystem",{"2":{"2394":1}}],["codesqlfilesource",{"2":{"2328":1,"2840":1}}],["codesse",{"2":{"2252":3}}],["codes",{"0":{"1591":1,"1624":1,"2113":1},"1":{"1592":1,"1593":1,"1594":1,"1595":1,"1596":1},"2":{"197":1,"210":1,"213":1,"447":1,"575":3,"576":1,"963":1,"1031":1,"1068":3,"1098":1,"1104":1,"1105":1,"1111":2,"1152":1,"1155":3,"1220":1,"1341":1,"1589":1,"1591":1,"1596":1,"1623":1,"1624":1,"1670":1,"1673":1,"1674":1,"1686":1,"1722":1,"1732":1,"1740":1,"1741":1,"1782":1,"1792":10,"1922":1,"2077":2,"2255":10,"2264":1,"2265":1,"2267":1,"2288":1,"2289":1,"2381":1,"2535":1,"2549":1,"2662":1,"2765":1,"2880":1}}],["codeid",{"2":{"128":1,"489":1,"493":1}}],["code",{"0":{"192":1,"313":1,"866":1,"873":1,"942":1,"996":1,"1008":1,"1104":1,"1106":1,"1155":1,"1200":1,"1281":1,"1318":1,"1319":1,"1350":1,"1406":1,"1552":1,"1673":1,"1674":1,"1958":1,"2247":1},"1":{"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"867":1,"868":1,"869":1,"870":1,"871":1,"872":1,"873":1,"874":1,"875":1,"876":1,"877":1,"1105":1,"1106":1,"1107":1,"1108":1,"1320":1,"1321":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1553":1,"1554":1,"1555":1,"1556":1,"1557":1,"1558":1,"1559":1,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1566":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"1578":1,"1579":1,"1580":1,"1581":1,"1582":1,"1583":1,"1584":1,"1585":1,"1674":1,"1959":1},"2":{"1":4,"5":1,"14":1,"30":1,"33":1,"35":2,"45":1,"56":1,"69":1,"74":1,"79":2,"92":1,"102":1,"113":1,"126":1,"133":2,"145":1,"155":1,"163":1,"164":1,"166":1,"173":1,"175":1,"179":1,"192":2,"193":3,"195":1,"196":1,"198":1,"200":1,"203":1,"206":1,"207":1,"208":1,"209":2,"210":2,"213":2,"235":1,"262":1,"268":1,"283":1,"296":1,"301":1,"309":1,"318":1,"329":1,"332":1,"340":1,"348":1,"358":1,"363":1,"370":1,"399":1,"413":1,"415":1,"434":1,"439":2,"447":2,"452":1,"459":1,"474":1,"480":1,"485":1,"498":1,"508":1,"516":1,"528":1,"533":1,"537":1,"550":1,"551":3,"560":1,"570":2,"579":2,"582":1,"590":1,"599":1,"608":1,"619":1,"628":1,"637":2,"651":1,"663":1,"674":1,"718":2,"719":2,"720":3,"725":1,"727":1,"730":1,"745":1,"795":1,"809":1,"818":1,"819":1,"824":1,"831":1,"834":1,"835":1,"845":1,"849":3,"857":1,"859":3,"860":2,"867":3,"868":4,"871":3,"872":1,"873":3,"875":1,"877":1,"878":2,"879":3,"880":1,"894":1,"901":1,"911":3,"912":1,"913":2,"914":1,"915":1,"919":1,"920":3,"921":1,"927":1,"945":2,"947":3,"948":1,"954":1,"956":1,"968":2,"972":1,"973":3,"974":1,"975":1,"976":2,"978":1,"982":1,"983":1,"984":2,"995":1,"996":1,"1002":1,"1005":1,"1006":1,"1009":2,"1010":1,"1019":2,"1021":2,"1024":1,"1026":1,"1027":2,"1031":2,"1036":1,"1037":4,"1043":1,"1044":1,"1048":1,"1064":2,"1065":1,"1066":2,"1068":6,"1086":3,"1094":3,"1098":1,"1101":1,"1104":1,"1105":5,"1106":1,"1107":1,"1108":2,"1109":2,"1111":7,"1121":1,"1127":2,"1139":1,"1152":1,"1167":2,"1181":2,"1183":2,"1184":1,"1193":1,"1214":1,"1232":1,"1234":1,"1236":1,"1237":1,"1247":1,"1252":2,"1254":1,"1280":1,"1281":7,"1302":2,"1322":3,"1328":1,"1332":2,"1338":3,"1339":3,"1341":1,"1352":1,"1357":1,"1360":1,"1361":1,"1366":4,"1367":1,"1368":1,"1382":7,"1383":1,"1384":3,"1385":2,"1386":1,"1394":2,"1398":3,"1399":1,"1400":2,"1401":6,"1402":4,"1404":1,"1405":2,"1406":4,"1407":1,"1409":3,"1414":1,"1415":1,"1416":1,"1421":1,"1422":1,"1423":1,"1426":1,"1427":1,"1431":2,"1471":1,"1480":1,"1552":1,"1554":1,"1557":1,"1558":2,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1608":1,"1609":1,"1624":1,"1670":3,"1671":2,"1674":1,"1679":1,"1681":1,"1684":1,"1721":1,"1722":2,"1725":1,"1732":2,"1736":2,"1741":1,"1742":3,"1743":1,"1755":1,"1757":1,"1759":1,"1761":2,"1789":2,"1792":32,"1796":2,"1825":1,"1836":1,"1855":3,"1865":2,"1882":1,"1884":1,"1886":1,"1887":1,"1912":1,"1916":1,"1918":2,"1921":2,"1922":2,"1924":1,"1926":2,"1949":1,"1951":2,"1952":2,"1953":2,"1954":2,"1973":1,"1974":1,"2011":1,"2016":1,"2020":1,"2040":1,"2077":2,"2081":2,"2095":1,"2101":1,"2107":1,"2109":1,"2113":1,"2141":1,"2149":1,"2157":1,"2164":1,"2169":2,"2193":2,"2221":1,"2222":1,"2242":1,"2255":12,"2264":5,"2273":2,"2278":1,"2283":3,"2289":3,"2290":3,"2301":1,"2303":1,"2320":1,"2329":1,"2354":1,"2360":1,"2366":2,"2389":1,"2410":1,"2414":1,"2437":1,"2452":1,"2455":2,"2468":1,"2470":1,"2472":3,"2486":5,"2487":1,"2518":1,"2527":2,"2530":1,"2532":1,"2535":1,"2537":3,"2543":2,"2549":8,"2551":1,"2566":2,"2569":1,"2575":1,"2580":1,"2581":1,"2587":1,"2588":1,"2596":3,"2607":1,"2632":1,"2652":2,"2654":1,"2659":1,"2669":1,"2679":1,"2696":1,"2709":1,"2742":1,"2762":3,"2763":2,"2764":1,"2766":2,"2769":2,"2772":2,"2785":1,"2802":1,"2809":1,"2810":5,"2814":1,"2815":3,"2823":1,"2824":2,"2825":1,"2826":1,"2827":1,"2831":1,"2832":1,"2839":1,"2857":1,"2862":2,"2879":1}}],["copilot",{"2":{"1384":2}}],["copied",{"2":{"852":1,"1157":1}}],["copies",{"2":{"844":3,"845":1,"1178":1,"1420":1}}],["copyto",{"2":{"2400":1}}],["copytoasync",{"2":{"1366":1}}],["copy",{"0":{"854":1,"907":1},"2":{"832":1,"844":1,"852":5,"854":2,"857":3,"861":1,"864":1,"907":1,"911":1,"920":1,"948":2,"959":1,"965":1,"966":1,"1065":2,"1094":1,"1138":1,"1366":1,"1420":3,"1440":1,"1792":2,"2047":1,"2054":1,"2075":1,"2111":1,"2532":1,"2533":1,"2635":2,"2651":1,"2678":1,"2695":1,"2871":1,"2873":2}}],["courage",{"2":{"872":1}}],["course",{"2":{"840":1,"844":3,"914":1,"1396":1}}],["couldn",{"2":{"934":1,"2336":1,"2380":1,"2421":1,"2608":1,"2734":1}}],["could",{"0":{"2734":1},"2":{"851":2,"852":2,"868":1,"919":1,"927":1,"933":1,"934":1,"1037":1,"1060":1,"1069":1,"1165":1,"1185":1,"1302":1,"1335":1,"1338":1,"1394":1,"1399":1,"1403":1,"1405":2,"1441":1,"1792":1,"1832":1,"1851":1,"2336":1,"2382":1,"2459":2,"2483":1,"2484":1,"2589":2,"2641":1}}],["couples",{"2":{"840":1,"1111":1}}],["couple",{"2":{"834":1,"1385":2,"1403":1,"1428":1}}],["counted",{"2":{"1792":1,"2107":1,"2421":1,"2528":1,"2531":1,"2535":1,"2537":1,"2864":1}}],["counterpart",{"2":{"1126":1,"2804":1,"2839":1,"2856":1}}],["counters",{"2":{"1056":1,"1243":1,"2531":1}}],["counterexample",{"2":{"857":1}}],["counter",{"2":{"310":1,"772":1,"896":1,"1210":1,"1227":1,"1236":1,"1239":1,"1792":1,"1877":1,"1886":1,"1888":1,"2648":1}}],["country",{"2":{"1037":1,"1079":1,"1405":1,"2868":1}}],["countless",{"2":{"849":1}}],["counting",{"2":{"761":1,"771":1,"885":1,"1056":1}}],["counts",{"2":{"622":1,"771":1,"829":1,"872":1,"966":4,"1049":1,"1170":1,"1792":4,"2049":1,"2050":2,"2051":1,"2465":1,"2506":1,"2530":1,"2635":8}}],["count",{"2":{"231":1,"427":1,"565":1,"566":4,"567":1,"575":1,"585":2,"586":1,"614":1,"624":1,"699":1,"706":1,"711":1,"715":1,"852":1,"854":1,"861":1,"868":1,"869":1,"871":1,"873":1,"883":1,"914":1,"916":5,"929":4,"979":1,"980":1,"986":1,"988":1,"989":1,"990":6,"991":7,"1076":1,"1169":1,"1181":1,"1213":1,"1215":3,"1216":4,"1222":1,"1236":3,"1239":5,"1240":1,"1336":1,"1338":3,"1339":7,"1366":1,"1375":1,"1386":3,"1398":1,"1442":2,"1792":9,"1886":1,"1888":1,"1889":1,"2011":1,"2094":1,"2097":1,"2099":1,"2320":2,"2337":3,"2339":1,"2342":1,"2354":1,"2357":1,"2364":1,"2398":1,"2435":1,"2506":1,"2527":1,"2528":1,"2531":1,"2537":1,"2597":1,"2614":1,"2774":1,"2851":1,"2862":1,"2864":1,"2869":1}}],["count>",{"2":{"79":4}}],["coerces",{"2":{"2183":1}}],["coercion",{"2":{"801":1,"1477":1}}],["coep",{"2":{"1100":1}}],["coexist",{"2":{"369":1,"2332":1}}],["cover",{"2":{"1083":1,"1152":1,"1385":1,"1419":1,"1624":1,"1792":1,"1825":1,"1909":1,"2417":1,"2506":2}}],["coveragethreshold=100",{"2":{"2094":1}}],["coveragethreshold",{"0":{"2107":1},"2":{"1792":2,"2093":1,"2094":1,"2107":1,"2537":2,"2879":1}}],["coverage",{"0":{"2107":1,"2465":1,"2472":1,"2879":1},"2":{"874":2,"876":1,"986":1,"1074":1,"1082":1,"1094":1,"1402":1,"1789":1,"1792":3,"2093":1,"2094":3,"2107":4,"2113":1,"2167":1,"2221":1,"2278":1,"2435":1,"2447":1,"2465":1,"2537":9,"2546":1,"2860":1,"2879":3,"2880":1}}],["covering",{"2":{"868":1,"1067":1,"1822":1,"2111":1,"2380":1,"2447":1,"2457":1,"2498":1,"2532":1}}],["covers",{"2":{"624":1,"911":1,"1067":1,"1079":1,"1080":1,"1094":1,"1327":1,"1328":1,"1406":1,"1411":1,"1444":1,"1569":1,"1609":1,"1612":1,"1759":1,"1792":3,"1825":1,"1912":1,"1943":1,"1978":1,"2110":1,"2112":1,"2438":1,"2445":1,"2456":1,"2466":1,"2493":1,"2513":1,"2520":1,"2523":1,"2530":1,"2532":1,"2534":1,"2542":1,"2661":1,"2759":1,"2806":1,"2827":1,"2860":1,"2868":1}}],["covered",{"2":{"1":1,"306":1,"874":1,"1174":1,"1402":1,"1414":1,"2107":1,"2343":1,"2472":1,"2490":1,"2523":2,"2537":1,"2546":1,"2879":1}}],["cooiename",{"2":{"2413":1}}],["cool",{"2":{"1397":1}}],["coop",{"2":{"1100":1,"1792":1,"2632":1}}],["cooperating",{"2":{"1070":1,"1102":1}}],["coordination",{"2":{"1325":1}}],["coordinated",{"2":{"865":1}}],["coords",{"2":{"333":2}}],["cookieenabled",{"2":{"1792":1}}],["cookiehttponly",{"2":{"1446":1,"1447":1,"1448":1,"1449":1,"1459":1,"1462":1,"1464":1,"1792":1,"2375":1,"2410":2,"2412":1,"2427":1,"2429":1}}],["cookiedomain",{"2":{"1446":1,"1447":1,"1448":1,"1449":1,"1459":1,"1792":1,"2375":1,"2412":1,"2427":1,"2429":1}}],["cookiepath",{"2":{"1446":1,"1447":1,"1459":1,"1792":1,"2375":1,"2412":1,"2427":1}}],["cookiemultisessions",{"2":{"1068":1,"1446":1,"1447":1,"1458":2,"1459":2,"1462":1,"1792":2,"2375":4,"2412":1,"2427":1}}],["cookievalid",{"2":{"1068":3,"1071":1,"1446":1,"1447":1,"1458":3,"1459":1,"1462":1,"1464":2,"1698":1,"1792":2,"2375":5,"2376":1,"2377":1,"2412":1,"2427":2}}],["cookievaliddays",{"2":{"937":1,"1053":1,"1062":1,"1071":1,"1464":1,"2173":1,"2187":1,"2376":1}}],["cookiename",{"2":{"937":1,"1053":1,"1446":1,"1447":1,"1459":1,"1460":1,"1488":1,"1489":1,"1792":2,"2173":1,"2187":1,"2375":2,"2410":2,"2412":1,"2422":1}}],["cookieauthenticationoptions",{"2":{"2435":1}}],["cookieauthenticationdefaults",{"2":{"1447":1}}],["cookieauthscheme",{"2":{"937":1,"1053":1,"1062":1,"1446":1,"1447":1,"1460":1,"1792":2,"2173":1,"2175":1,"2187":1,"2375":1}}],["cookieauth",{"2":{"937":2,"1053":1,"1062":1,"1068":1,"1445":1,"1446":1,"1447":1,"1449":1,"1458":1,"1462":1,"1464":1,"1698":1,"1792":3,"1892":1,"1893":1,"1904":1,"1907":1,"2173":1,"2187":1,"2254":2,"2375":1,"2377":1,"2420":1,"2426":1,"2427":1,"2429":1,"2690":2}}],["cookieschemesinorder",{"2":{"2422":1,"2435":1}}],["cookiesenabled",{"2":{"1792":1}}],["cookiesecurepolicy",{"2":{"2435":1}}],["cookiesecure=always",{"2":{"1449":1}}],["cookiesecure",{"0":{"2425":1},"1":{"2426":1,"2427":1,"2428":1,"2429":1},"2":{"1446":1,"1447":1,"1449":3,"1459":1,"1792":2,"2225":1,"2412":1,"2419":1,"2426":1,"2427":2,"2429":1,"2436":1}}],["cookiesamesite=",{"2":{"1792":1}}],["cookiesamesite=none",{"2":{"1449":1}}],["cookiesamesite",{"0":{"2425":1},"1":{"2426":1,"2427":1,"2428":1,"2429":1},"2":{"1446":1,"1447":2,"1449":3,"1459":1,"1792":1,"2225":1,"2412":1,"2419":1,"2426":1,"2427":2,"2428":1,"2429":1,"2436":1}}],["cookies",{"0":{"1449":1},"2":{"289":2,"290":2,"291":1,"298":2,"302":1,"312":2,"541":2,"546":1,"868":1,"934":2,"935":1,"937":2,"1037":1,"1048":1,"1053":2,"1054":3,"1055":1,"1060":2,"1061":3,"1062":1,"1063":1,"1064":2,"1068":1,"1098":3,"1216":2,"1239":2,"1249":1,"1308":1,"1322":1,"1323":1,"1371":2,"1446":1,"1447":2,"1458":7,"1459":1,"1460":2,"1639":1,"1644":1,"1649":1,"1653":1,"1704":1,"1792":9,"1904":1,"1907":1,"2173":1,"2176":2,"2178":1,"2187":2,"2202":1,"2227":1,"2254":2,"2353":1,"2375":9,"2376":1,"2410":2,"2412":1,"2413":1,"2421":2,"2423":3,"2427":3,"2428":1,"2436":1,"2438":1,"2486":1}}],["cookie",{"0":{"1446":1,"1447":1,"1448":1,"1462":1,"1904":1,"2173":1,"2420":1,"2424":1,"2426":1},"1":{"1447":1,"1448":1,"1449":1,"2421":1,"2422":1,"2423":1,"2424":1},"2":{"25":1,"286":1,"289":3,"297":3,"302":1,"303":1,"312":1,"313":1,"315":1,"541":4,"546":1,"835":1,"869":1,"934":2,"935":1,"936":1,"937":2,"1037":1,"1053":1,"1054":2,"1063":1,"1068":3,"1086":1,"1098":5,"1126":1,"1216":1,"1222":1,"1239":1,"1304":1,"1308":1,"1323":1,"1327":1,"1371":1,"1444":1,"1445":1,"1446":1,"1447":8,"1448":1,"1449":3,"1458":2,"1459":1,"1460":1,"1485":1,"1489":1,"1507":1,"1550":1,"1651":1,"1699":1,"1700":1,"1704":1,"1788":1,"1792":23,"1795":1,"1852":1,"1894":1,"1901":1,"1904":2,"1906":2,"1907":2,"1911":1,"2164":2,"2165":1,"2170":1,"2171":3,"2173":1,"2187":2,"2188":2,"2189":1,"2202":2,"2225":2,"2254":5,"2353":1,"2375":4,"2377":1,"2383":1,"2419":5,"2420":3,"2421":7,"2422":13,"2423":3,"2424":3,"2425":3,"2427":3,"2429":3,"2434":1,"2435":5,"2436":2,"2438":4,"2554":1,"2736":1,"2836":1}}],["coroutine",{"2":{"1274":1}}],["corbado",{"2":{"1098":1}}],["corner",{"2":{"861":1}}],["corroborates",{"2":{"872":1}}],["corrupted",{"2":{"865":1}}],["corrupt",{"2":{"843":1,"1081":1}}],["correlate",{"2":{"1792":2,"2255":1}}],["correlating",{"2":{"1670":1}}],["correlation",{"0":{"639":1,"1326":1},"2":{"650":1,"668":1,"1111":2,"1326":1,"1677":1,"1848":1,"2833":1}}],["corrected",{"2":{"2450":1}}],["correctness",{"2":{"874":2,"1122":1,"1592":1,"1792":1,"2265":1}}],["correctly",{"2":{"373":1,"852":1,"857":1,"930":5,"1042":1,"1092":1,"1401":1,"1421":1,"1518":1,"1762":1,"1792":1,"2265":1,"2274":1,"2297":1,"2347":1,"2356":1,"2360":1,"2365":1,"2420":1,"2441":1,"2490":1,"2492":1,"2496":1,"2558":1,"2572":1,"2589":1,"2603":1,"2607":1,"2611":1,"2627":1,"2634":1,"2641":1,"2648":1,"2666":1,"2679":1,"2692":1}}],["correct",{"2":{"298":1,"845":1,"930":1,"986":1,"990":1,"1077":1,"1078":1,"1135":1,"1280":1,"1621":1,"1701":1,"1718":1,"1792":1,"2176":1,"2358":1,"2416":1,"2437":1,"2454":1,"2465":1,"2470":1,"2495":1,"2590":1,"2611":2,"2633":1,"2835":1}}],["corresponds",{"2":{"2320":1}}],["correspondingly",{"2":{"1119":1}}],["corresponding",{"2":{"408":1,"845":1,"872":1,"1017":1,"1414":1,"1422":1,"1518":1,"1782":1,"2259":2,"2265":1,"2665":1}}],["correspond",{"2":{"165":1,"168":1}}],["cors",{"0":{"545":1,"1637":1,"2486":1},"1":{"1638":1,"1639":1,"1640":1,"1641":1,"1642":1,"1643":1,"1644":1,"1645":1,"1646":1,"1647":1,"1648":1},"2":{"545":4,"547":2,"868":1,"869":1,"1109":1,"1320":1,"1386":1,"1449":1,"1637":1,"1638":1,"1639":5,"1640":2,"1641":2,"1642":1,"1643":1,"1645":1,"1646":2,"1788":1,"1792":6,"1795":1,"1963":1,"2024":1,"2030":1,"2223":1,"2429":3,"2486":2,"2551":1,"2627":2,"2678":1,"2695":1,"2700":1,"2701":1,"2809":1}}],["corporate",{"2":{"1204":2,"1230":1,"1792":1,"1880":1}}],["corp",{"2":{"333":2,"1100":1,"1189":1,"1192":1,"1792":1,"2024":4,"2029":1,"2632":1}}],["cores",{"2":{"1165":2,"1170":1}}],["core",{"0":{"869":1,"1095":1,"1787":1,"1794":1,"2332":1,"2344":1,"2487":1,"2628":1,"2714":1},"2":{"304":1,"408":1,"831":1,"843":1,"848":2,"860":1,"868":1,"869":5,"871":3,"873":2,"876":1,"1039":1,"1054":1,"1084":1,"1098":1,"1156":1,"1193":1,"1255":1,"1257":1,"1265":1,"1279":1,"1366":1,"1382":1,"1409":1,"1421":1,"1445":1,"1450":2,"1457":1,"1458":1,"1718":1,"1783":1,"1787":1,"1792":13,"1794":1,"1802":1,"1825":1,"2013":1,"2235":1,"2257":5,"2258":2,"2375":1,"2389":1,"2465":1,"2470":1,"2481":2,"2482":4,"2487":1,"2518":1,"2520":1,"2534":1,"2545":1,"2554":1,"2555":1,"2571":1,"2628":2,"2633":1,"2634":1,"2645":1,"2665":1,"2701":1,"2795":2,"2874":1}}],["cols",{"2":{"2171":3,"2398":2}}],["colors",{"2":{"2535":2,"2662":1,"2678":1,"2880":1}}],["colordepth",{"2":{"1792":2}}],["color",{"2":{"1427":2,"1431":1,"1792":1,"2073":1,"2075":1,"2080":1,"2535":2,"2662":1,"2762":2}}],["colons",{"2":{"2692":1}}],["colon",{"2":{"537":1,"2540":1}}],["cold",{"2":{"1107":1,"1166":1,"1272":1,"1437":1,"2224":1,"2459":1,"2461":1,"2464":1,"2465":2,"2789":1}}],["col3",{"2":{"490":1}}],["col2",{"2":{"490":1}}],["col1",{"2":{"490":1}}],["collaborative",{"2":{"1323":1}}],["collapsing",{"2":{"1037":1}}],["collapses",{"2":{"1430":1,"1569":1,"1759":1,"2224":1,"2462":1,"2520":1,"2540":1,"2845":1}}],["collapsed",{"2":{"1041":1,"2400":1,"2481":1}}],["collapse",{"2":{"965":2,"1324":1,"1382":1,"1405":1,"1792":2,"2073":2,"2075":2,"2080":2}}],["collision",{"2":{"1460":1,"2040":1,"2375":1,"2395":1,"2410":1,"2476":1}}],["collisions",{"2":{"915":1,"1554":1,"1792":2,"2265":2}}],["colliding",{"2":{"701":1,"2422":1}}],["collide",{"2":{"108":1,"1460":1,"1522":1,"1792":1,"2375":1,"2380":1,"2534":1,"2872":1}}],["colleague",{"2":{"836":1}}],["collectors",{"2":{"2495":1}}],["collector",{"2":{"1792":1,"1807":1,"2794":1,"2804":2}}],["collections",{"2":{"851":1,"852":1,"856":1,"949":1}}],["collection",{"2":{"841":1,"851":9,"852":6,"1241":1,"1254":1,"1792":7,"1890":1}}],["collect",{"2":{"439":1,"1241":1,"1890":1,"2347":1,"2804":1}}],["col",{"2":{"306":2,"1792":2,"2330":2}}],["column2",{"2":{"2326":1}}],["column1",{"2":{"2326":1}}],["columnar",{"2":{"848":3}}],["column",{"0":{"125":1,"301":1,"302":1,"303":1,"308":1,"309":1,"613":1,"1240":1,"1664":1,"1889":1,"2405":1,"2724":1},"1":{"126":1,"127":1,"128":1,"129":1,"130":1,"131":1},"2":{"33":2,"34":1,"35":1,"39":2,"41":1,"125":1,"129":1,"131":1,"159":2,"182":2,"186":2,"188":1,"229":3,"237":1,"297":6,"298":4,"299":3,"300":2,"301":2,"304":4,"305":3,"306":2,"309":4,"312":1,"313":1,"330":1,"346":2,"364":1,"496":2,"528":1,"582":2,"585":2,"587":2,"598":1,"606":1,"613":1,"615":2,"702":1,"841":1,"845":1,"854":2,"868":1,"871":1,"872":1,"875":1,"878":1,"879":2,"880":1,"885":2,"888":2,"934":1,"965":1,"973":2,"982":3,"984":1,"995":1,"997":3,"1055":1,"1068":1,"1080":1,"1096":1,"1098":1,"1100":3,"1127":1,"1189":2,"1192":2,"1203":2,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1237":1,"1239":1,"1240":1,"1284":1,"1370":1,"1378":1,"1382":1,"1386":4,"1390":3,"1408":2,"1412":1,"1429":1,"1436":2,"1458":2,"1471":6,"1480":2,"1649":1,"1651":1,"1655":1,"1664":2,"1667":1,"1686":1,"1688":2,"1792":23,"1882":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1889":1,"2000":1,"2007":1,"2009":1,"2010":1,"2102":1,"2109":2,"2156":1,"2175":1,"2176":1,"2177":1,"2178":1,"2180":3,"2181":1,"2182":1,"2206":1,"2226":1,"2227":1,"2259":1,"2291":2,"2293":1,"2296":1,"2324":1,"2326":2,"2328":4,"2329":1,"2330":2,"2337":3,"2339":2,"2357":2,"2372":2,"2375":1,"2397":1,"2405":1,"2451":2,"2453":1,"2528":2,"2530":3,"2537":2,"2542":1,"2586":1,"2587":1,"2725":1,"2726":1,"2840":2,"2841":1,"2842":1,"2851":1,"2854":1,"2864":2,"2869":3,"2881":1}}],["columnstore",{"2":{"848":1}}],["columns",{"0":{"33":1,"185":1,"300":1,"304":1,"333":1,"488":1,"1471":1,"2180":1,"2293":1,"2326":1,"2587":1},"1":{"186":1,"301":1,"302":1,"303":1,"305":1,"306":1},"2":{"32":1,"34":1,"35":1,"125":1,"126":1,"128":2,"159":1,"186":3,"188":3,"227":1,"238":1,"297":2,"304":1,"315":1,"328":2,"330":2,"335":1,"336":1,"337":1,"489":1,"493":1,"494":1,"581":1,"582":2,"583":1,"584":1,"585":1,"587":1,"700":1,"848":1,"916":2,"934":1,"956":2,"969":1,"980":1,"983":2,"995":1,"1040":1,"1060":1,"1079":1,"1100":1,"1189":2,"1190":2,"1192":1,"1196":1,"1203":1,"1214":1,"1232":1,"1233":1,"1234":1,"1236":1,"1237":1,"1238":1,"1239":4,"1355":1,"1367":1,"1368":1,"1373":2,"1375":1,"1378":1,"1399":3,"1460":1,"1470":1,"1480":2,"1581":1,"1686":2,"1688":3,"1792":11,"1824":2,"1882":1,"1883":1,"1884":1,"1886":1,"1887":1,"1888":2,"1967":1,"2000":1,"2010":1,"2093":1,"2094":1,"2109":5,"2170":1,"2171":4,"2176":1,"2180":2,"2181":2,"2189":1,"2206":2,"2217":2,"2293":3,"2296":3,"2318":1,"2324":1,"2325":1,"2330":1,"2337":4,"2356":1,"2375":1,"2397":1,"2398":1,"2481":1,"2530":1,"2537":1,"2586":1,"2587":3,"2590":2,"2600":1,"2641":1,"2672":1,"2774":1,"2840":1,"2854":4,"2866":1,"2869":5}}],["comibine",{"2":{"916":1}}],["comfortable",{"2":{"876":1}}],["come",{"2":{"534":1,"841":1,"859":1,"910":1,"1041":1,"1096":1,"1106":1,"1274":1,"1276":1,"1279":1,"1352":1,"1416":1,"1421":1,"1422":1,"1574":1,"1576":2,"2388":1,"2533":1,"2688":1}}],["comes",{"2":{"390":1,"429":1,"534":1,"699":1,"832":1,"851":1,"864":1,"869":1,"880":1,"1040":1,"1078":1,"1081":1,"1281":1,"1385":1,"1394":1,"1396":1,"1402":1,"1421":1,"2310":1,"2313":1,"2733":1,"2811":1,"2839":1}}],["combines",{"2":{"1011":1,"1048":1,"1328":1,"1398":1,"1576":1,"1792":1,"2140":1,"2575":1,"2634":2}}],["combine",{"2":{"902":1,"1009":1,"1035":1,"1105":2,"1138":1,"1347":2,"1366":1,"1398":1,"1401":1,"1747":1,"1792":2,"1930":1,"2110":1,"2164":1,"2347":1,"2530":1,"2691":1,"2725":1}}],["combined",{"0":{"105":1,"420":1,"444":1,"478":1,"543":1,"574":1,"736":1,"800":1,"903":1,"1464":1},"2":{"213":1,"214":1,"258":1,"378":1,"409":1,"469":1,"541":1,"587":1,"705":1,"868":2,"872":1,"904":6,"946":1,"951":1,"1065":1,"1069":1,"1099":1,"1139":1,"1162":1,"1171":1,"1335":1,"1353":1,"1354":1,"1358":2,"1365":1,"1376":1,"1378":1,"1407":1,"1577":1,"1740":1,"1745":2,"2207":1,"2277":1,"2278":1,"2288":1,"2333":1,"2346":2,"2459":1,"2506":1,"2549":1,"2589":1,"2621":1,"2662":1,"2767":2}}],["combining",{"0":{"902":1,"1163":1,"2207":1},"1":{"903":1},"2":{"1010":1,"1192":1,"1351":1,"1766":1,"2429":1,"2515":1}}],["combinations",{"2":{"2435":1,"2871":1}}],["combination",{"2":{"40":1,"877":1,"1067":1,"1114":1,"2407":1,"2413":1}}],["com",{"2":{"75":1,"128":2,"206":1,"207":1,"208":1,"209":2,"211":2,"212":1,"213":3,"214":5,"215":1,"297":2,"333":2,"390":1,"394":1,"423":4,"430":1,"436":3,"442":2,"444":2,"446":9,"449":1,"452":2,"455":1,"488":1,"489":2,"493":2,"531":1,"533":1,"611":1,"612":1,"695":1,"700":1,"845":1,"851":1,"878":1,"913":1,"921":1,"938":2,"947":1,"970":1,"976":1,"977":3,"979":3,"986":3,"988":3,"989":1,"994":1,"1010":1,"1017":2,"1018":2,"1019":2,"1023":2,"1026":2,"1030":1,"1032":1,"1033":1,"1034":3,"1048":1,"1051":3,"1074":2,"1078":2,"1105":3,"1117":1,"1118":1,"1119":1,"1173":3,"1176":3,"1177":5,"1183":1,"1207":1,"1225":2,"1233":1,"1302":1,"1328":1,"1347":1,"1348":1,"1352":1,"1369":1,"1380":1,"1386":4,"1391":1,"1393":1,"1398":1,"1423":1,"1426":1,"1430":1,"1431":1,"1449":4,"1614":3,"1616":1,"1627":3,"1629":6,"1640":2,"1646":2,"1691":3,"1692":4,"1693":3,"1694":3,"1695":3,"1697":3,"1709":3,"1711":2,"1714":1,"1726":1,"1730":3,"1731":2,"1733":2,"1736":1,"1738":1,"1740":3,"1742":1,"1743":1,"1792":48,"1833":1,"1875":2,"1900":2,"1907":1,"1911":1,"1920":2,"1921":1,"1924":1,"1926":1,"1931":1,"2162":1,"2180":1,"2254":1,"2257":4,"2264":3,"2266":3,"2283":1,"2286":1,"2288":3,"2290":1,"2308":1,"2425":2,"2429":2,"2434":1,"2450":1,"2483":1,"2502":1,"2526":2,"2529":1,"2530":1,"2549":3,"2580":1,"2633":3,"2634":1,"2739":1,"2760":1,"2762":3,"2764":2,"2765":1,"2766":2,"2768":2,"2781":1,"2782":1,"2783":1,"2784":1,"2792":2,"2810":1,"2811":6,"2842":1,"2860":2,"2865":1,"2869":2,"2873":1}}],["compilation",{"2":{"1005":1,"1026":1,"2245":3,"2600":1,"2776":1,"2789":1,"2792":2}}],["compiled",{"2":{"982":1,"1013":1,"1417":1,"1420":1,"2576":1,"2711":1,"2744":1,"2776":1,"2790":1,"2792":2}}],["compile",{"0":{"1001":1},"2":{"856":1,"872":1,"875":1,"978":1,"1004":1,"1009":1,"1037":1,"1406":1,"1409":1,"1422":1,"2168":1}}],["compiles",{"2":{"852":1,"1406":2,"1420":1,"1422":1,"2461":1}}],["compilers",{"2":{"1005":1}}],["compiler",{"2":{"843":1,"852":1,"872":1,"975":1,"2328":1}}],["comprehensive",{"0":{"2615":1},"2":{"1255":1,"2255":1,"2257":1,"2278":1}}],["compressing",{"2":{"2626":2}}],["compressible",{"2":{"1943":1,"2626":1}}],["compressionlevel",{"2":{"1792":1,"1936":1,"1937":1,"1944":2}}],["compression",{"0":{"1935":1,"1938":1,"1939":1,"1942":1,"2626":1,"2746":1},"1":{"1936":1,"1937":1,"1938":1,"1939":1,"1940":2,"1941":2,"1942":1,"1943":1,"1944":1,"1945":1,"1946":1},"2":{"848":1,"868":1,"869":1,"873":1,"953":1,"1101":2,"1790":2,"1792":8,"1797":2,"1935":1,"1937":7,"1938":5,"1940":2,"1941":1,"1942":1,"1944":2,"1994":1,"2235":1,"2626":1,"2627":1,"2746":1}}],["compress",{"2":{"873":1}}],["compresses",{"2":{"869":1}}],["compressed",{"2":{"848":1,"953":1}}],["compromised",{"2":{"946":1,"1185":2}}],["compromise",{"2":{"945":1}}],["compromises",{"2":{"940":1}}],["complicates",{"2":{"1393":1}}],["complicated",{"2":{"919":1}}],["compliant",{"2":{"1064":1}}],["compliance",{"2":{"902":1,"1792":1}}],["completing",{"2":{"2169":1,"2393":1}}],["completion",{"0":{"1167":1,"1215":1,"2087":1},"2":{"1167":2,"1214":1,"1215":1,"1216":1,"1232":1,"1233":1,"1241":1,"1252":1,"1792":5,"1882":1,"1890":1,"2086":2,"2087":1}}],["completions",{"2":{"1105":1}}],["completed",{"2":{"1385":1,"1792":4,"2362":2,"2766":1,"2802":1}}],["completeaddexistingusercommand",{"0":{"1237":1,"1887":1},"2":{"1221":1,"1238":2,"1792":5,"1871":1,"1893":1}}],["completeauthenticatecommand",{"0":{"1239":1,"1888":1},"2":{"1217":1,"1222":1,"1236":1,"1792":2,"1872":1,"1886":1,"1893":1}}],["completeregistrationcommand",{"0":{"1238":1,"1887":1},"2":{"1217":1,"1220":1,"1792":1,"1870":1,"1893":1}}],["completely",{"0":{"2752":1,"2801":1},"2":{"845":1,"848":2,"852":2,"863":1,"864":1,"1105":1,"1139":1,"1385":1,"1386":1,"1399":2,"1802":1,"2092":1,"2157":1,"2337":1,"2405":1,"2543":1,"2544":1}}],["completes",{"2":{"285":1,"1060":1,"1416":1,"1684":1,"2559":1}}],["complete",{"0":{"364":1,"997":1,"1083":1,"1207":1,"1212":1,"1223":1,"1249":1,"1339":1,"1461":1,"1483":1,"1505":1,"1529":1,"1548":1,"1633":1,"1663":1,"1698":1,"1734":1,"1810":1,"1863":1,"1891":1,"1907":1,"1931":1,"1960":1,"1975":1,"1995":1,"2080":1,"2132":1,"2146":1,"2187":1,"2815":1,"2836":1},"1":{"365":1,"366":1,"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1462":1,"1463":1,"1464":1,"1735":1,"1736":1,"1737":1,"1892":1,"1893":1},"2":{"35":1,"73":1,"98":1,"121":1,"141":1,"220":1,"251":1,"327":1,"364":1,"430":1,"455":1,"577":1,"646":1,"658":1,"659":1,"684":1,"791":1,"834":1,"860":1,"919":1,"921":1,"947":1,"972":1,"976":1,"988":1,"1037":5,"1047":1,"1048":1,"1062":1,"1064":1,"1065":1,"1071":1,"1081":1,"1082":1,"1084":2,"1086":1,"1094":1,"1127":1,"1177":1,"1182":1,"1183":1,"1206":1,"1211":1,"1215":1,"1216":1,"1217":3,"1218":1,"1220":3,"1221":2,"1222":2,"1226":3,"1239":1,"1252":1,"1317":1,"1339":1,"1366":1,"1369":1,"1371":1,"1381":1,"1382":1,"1386":1,"1399":1,"1596":1,"1616":1,"1629":1,"1633":1,"1674":1,"1678":1,"1785":2,"1792":6,"1834":1,"1868":1,"1876":3,"1893":3,"1894":2,"1984":1,"2040":1,"2134":1,"2170":1,"2187":1,"2210":1,"2219":1,"2372":1,"2476":1,"2527":1,"2531":1,"2537":1,"2577":1,"2701":1,"2705":1,"2706":2,"2729":1,"2737":1,"2754":1,"2774":1,"2793":2,"2806":1,"2826":3,"2827":1,"2841":1,"2860":2,"2873":1,"2882":1}}],["complementary",{"0":{"836":1},"2":{"1136":1}}],["complexity",{"2":{"845":1,"1013":1,"1084":1,"1108":1,"1121":1,"1206":1,"1281":1,"1385":1}}],["complex",{"0":{"900":1,"1303":1,"1394":1},"1":{"1395":1,"1396":1},"2":{"137":1,"841":2,"843":1,"845":1,"908":1,"920":3,"1064":1,"1084":2,"1086":1,"1094":1,"1097":1,"1121":1,"1127":1,"1205":1,"1247":1,"1351":1,"1378":1,"1394":1,"1396":2,"1398":1,"1399":1,"1401":1,"1405":1,"1792":1,"2164":1,"2270":1,"2586":1,"2588":1}}],["compelling",{"2":{"1404":1}}],["compensate",{"2":{"865":1}}],["compensating",{"2":{"865":1}}],["competitive",{"2":{"1091":1}}],["competitors",{"2":{"831":1,"1262":1}}],["competent",{"2":{"876":1}}],["compete",{"2":{"831":1,"1078":1,"1205":1}}],["computation",{"2":{"852":3,"866":1}}],["computevisualizationurl",{"2":{"1416":1,"1572":1}}],["computevisualization",{"2":{"1416":1}}],["computeurl",{"2":{"1416":1,"1581":1}}],["computes",{"2":{"859":1,"1738":1}}],["computer",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1},"2":{"840":1}}],["computed",{"2":{"212":1,"215":1,"362":1,"395":1,"527":1,"534":1,"1255":1,"1738":1,"2621":3,"2622":1}}],["compute",{"2":{"106":1,"396":1,"852":1,"860":2,"861":2,"868":3,"869":1,"1067":5,"1094":1,"1150":2,"1424":1,"1529":3,"1572":2,"1573":1,"2463":1,"2762":1,"2768":1,"2771":1}}],["compounded",{"2":{"2402":1}}],["compound",{"2":{"873":1,"2398":1}}],["compounding",{"2":{"872":1}}],["compounds",{"2":{"871":1,"2398":1}}],["component",{"2":{"833":1,"834":3,"835":2,"871":1,"946":1,"1027":1,"1064":1,"1322":1,"1350":1,"1366":1,"1409":1,"1414":1,"1419":1,"1436":1}}],["components",{"2":{"833":2,"834":2,"837":1,"867":1,"2785":1,"2788":1}}],["composed",{"2":{"1088":1,"1961":1}}],["compose",{"0":{"1775":1},"2":{"1078":1,"1096":2,"1119":2,"1127":1,"1192":1,"1193":1,"1398":1,"1399":1,"2433":1,"2532":1,"2537":1,"2717":1,"2767":1,"2804":1}}],["composes",{"0":{"349":1},"2":{"1792":1,"2097":1,"2344":1,"2346":1,"2868":1,"2869":1,"2877":1}}],["composition",{"0":{"1190":1,"1745":1,"2433":1},"1":{"1191":1,"1192":1,"1193":1},"2":{"1037":1,"1122":1,"1125":1,"1187":1,"1207":1,"1398":2,"2164":1,"2329":1,"2346":1,"2347":1,"2435":1}}],["compositefieldbuffer",{"2":{"2614":1}}],["compositefielddescriptors",{"2":{"2611":1}}],["compositefieldnames",{"2":{"2611":1}}],["compositecolumninfo",{"2":{"2370":1}}],["compositetypecache",{"2":{"2370":1}}],["composites",{"0":{"2397":1},"1":{"2398":1},"2":{"334":2,"337":2,"919":1,"1792":1,"1967":1,"1974":7,"2010":1,"2226":1,"2356":1,"2398":1,"2586":1,"2588":1,"2603":1,"2607":9,"2611":2}}],["composite",{"0":{"333":1,"334":1,"335":1,"383":1,"1192":1,"1725":1,"1973":1,"1974":1,"2217":1,"2325":1,"2348":1,"2356":1,"2370":1,"2504":1,"2585":1,"2586":1,"2587":1,"2590":1,"2607":1,"2611":1,"2618":1},"1":{"2586":1,"2587":1},"2":{"74":2,"75":1,"201":1,"202":1,"203":1,"206":1,"210":1,"223":1,"227":1,"238":1,"266":1,"328":2,"330":3,"334":2,"335":2,"336":1,"337":4,"383":8,"581":1,"582":2,"583":1,"584":1,"587":1,"869":2,"871":1,"912":1,"914":2,"916":1,"919":1,"1016":1,"1019":1,"1031":1,"1092":1,"1097":14,"1104":1,"1105":2,"1187":1,"1190":2,"1192":2,"1193":2,"1375":1,"1376":1,"1377":1,"1398":1,"1426":1,"1720":1,"1722":1,"1723":1,"1725":1,"1727":1,"1728":1,"1732":1,"1792":8,"1824":2,"1967":2,"1973":1,"1974":10,"2000":2,"2010":3,"2156":1,"2164":1,"2165":1,"2217":1,"2222":1,"2228":1,"2236":2,"2258":1,"2264":6,"2267":1,"2270":1,"2324":1,"2325":2,"2329":2,"2330":2,"2337":5,"2348":9,"2356":1,"2369":1,"2370":2,"2372":1,"2388":1,"2397":3,"2398":2,"2435":1,"2481":1,"2484":1,"2498":1,"2504":1,"2512":1,"2518":2,"2522":1,"2542":1,"2585":1,"2586":11,"2587":5,"2588":1,"2589":3,"2590":7,"2597":1,"2600":1,"2603":2,"2607":11,"2608":1,"2611":4,"2614":1,"2618":1,"2641":2,"2725":1,"2759":1,"2760":1,"2762":1,"2769":1,"2854":2,"2856":1}}],["composing",{"0":{"2767":1},"2":{"264":1,"1746":1,"2347":1,"2759":1}}],["compact",{"2":{"1386":1}}],["company",{"2":{"1079":1,"2394":5,"2868":1}}],["companion",{"2":{"1067":1,"1434":1}}],["companies",{"2":{"876":1}}],["compat",{"2":{"918":1,"1792":1}}],["compatibility",{"0":{"1066":1,"1102":1,"1850":1,"2382":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1851":1,"1852":1},"2":{"330":1,"917":1,"1013":1,"1037":1,"1066":1,"1070":1,"1101":1,"1102":2,"1181":1,"1792":3,"1850":1,"1856":1,"1974":1,"2002":1,"2359":1,"2423":1,"2455":1,"2481":1,"2587":1,"2600":1,"2607":2}}],["compatible",{"0":{"954":1},"2":{"309":1,"696":1,"1522":1,"1751":1,"1792":2,"1958":1,"2047":1,"2054":1,"2077":1,"2371":1,"2380":1,"2394":2,"2461":1,"2470":1,"2477":1,"2496":1,"2635":2,"2652":1}}],["comparing",{"0":{"1260":1}}],["comparisons",{"2":{"831":1}}],["comparison",{"0":{"869":1,"906":1,"945":1,"969":1,"1083":1,"1085":1,"1093":1,"1108":1,"1116":1,"1206":1,"1281":1,"1319":1,"1350":1,"1366":1},"1":{"907":1,"908":1,"909":1,"1084":1,"1085":1,"1086":2,"1087":2,"1088":2,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":2,"1095":2,"1096":2,"1097":2,"1098":2,"1099":2,"1100":2,"1101":2,"1102":2,"1103":2,"1104":2,"1105":2,"1106":2,"1107":2,"1108":2,"1109":2,"1110":2,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":2,"1118":2,"1119":2,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1,"1320":1,"1321":1},"2":{"831":1,"1037":3,"1049":1,"1083":2,"1181":1,"1260":1,"1366":2,"1368":1,"1383":1,"1894":1,"2177":1,"2245":1,"2372":1,"2713":1}}],["compares",{"0":{"534":1},"2":{"1738":1}}],["compare",{"0":{"2713":1},"2":{"308":1,"994":1,"1006":1,"1057":1,"1064":1,"1133":1,"1254":1,"1387":1}}],["compared",{"0":{"1254":1},"1":{"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1},"2":{"74":1,"1004":1,"1266":1,"1382":1,"2245":1,"2518":1,"2621":1}}],["communicates",{"2":{"1211":1,"1868":1}}],["communication",{"2":{"1007":1,"1015":1,"1035":1,"1685":1}}],["community",{"2":{"863":1,"876":1,"1127":1,"1385":2}}],["commas",{"2":{"2400":1,"2533":1}}],["comma",{"0":{"601":1},"2":{"687":1,"704":1,"709":1,"714":1,"767":1,"781":2,"786":1,"809":1,"902":1,"1189":1,"1358":1,"1627":1,"1792":3,"2097":1,"2125":1,"2200":2,"2266":1,"2537":1,"2575":1,"2648":1,"2870":1}}],["commandlineconfigurationprovider",{"2":{"2824":1,"2825":1}}],["commandtext",{"2":{"2614":1}}],["commandtextbuilder",{"2":{"2614":1}}],["commandtimeout=30",{"2":{"2694":1,"2695":1,"2700":1}}],["commandtimeout",{"2":{"134":1,"139":1,"141":1,"279":1,"1672":1,"1792":2,"1836":1,"1837":1,"1863":1,"2253":2,"2255":1,"2687":1,"2701":1}}],["commandcallbackasync",{"2":{"2466":2}}],["commandbehavior",{"2":{"2324":1}}],["commandretrystrategy",{"2":{"1217":1,"1224":1,"1792":1,"1874":1}}],["commandretryoptions",{"2":{"570":1,"575":1,"577":1,"1153":1,"1154":1,"1177":1,"1587":1,"1597":1,"1598":1,"1792":2,"2551":1,"2701":1}}],["commands",{"0":{"1231":1,"1881":1,"2168":1,"2414":1,"2582":1,"2667":1,"2785":1},"1":{"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"2415":1,"2416":1,"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"238":1,"310":1,"347":2,"565":1,"567":2,"568":1,"614":1,"624":3,"625":2,"830":1,"845":1,"868":2,"869":2,"901":1,"1102":3,"1217":1,"1223":1,"1382":1,"1386":2,"1391":1,"1394":4,"1399":1,"1655":1,"1792":4,"1850":2,"2225":1,"2232":1,"2258":1,"2319":1,"2320":3,"2339":1,"2340":1,"2342":4,"2357":3,"2383":1,"2432":1,"2496":4,"2531":1,"2534":1,"2614":1,"2667":2,"2679":1,"2750":1,"2772":1,"2774":1,"2785":1,"2798":1,"2875":1}}],["command>",{"2":{"30":2}}],["command",{"0":{"29":1,"31":1,"37":1,"38":1,"39":1,"40":1,"50":1,"60":1,"132":1,"277":1,"614":1,"760":1,"761":1,"765":1,"770":1,"771":1,"826":1,"827":1,"907":1,"1153":1,"1370":1,"1473":1,"1480":1,"1481":1,"1501":1,"1586":1,"1686":1,"1689":1,"1806":1,"2129":1,"2131":1,"2319":1,"2320":1,"2340":1,"2357":1,"2403":1,"2496":1,"2577":1,"2691":1,"2692":1,"2699":1,"2780":1,"2785":1,"2798":1,"2842":1,"2850":1},"1":{"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1,"1154":1,"1587":1,"1588":1,"1589":1,"1590":1,"1591":1,"1592":1,"1593":1,"1594":1,"1595":1,"1596":1,"1597":1,"1598":1,"1599":1,"1600":1,"1601":1,"1602":1,"1687":1,"1688":1,"1689":1,"2692":1,"2781":1,"2782":1,"2783":1,"2784":1,"2843":1,"2851":1,"2852":1,"2853":1},"2":{"29":3,"30":3,"31":1,"32":1,"37":6,"38":5,"39":3,"40":7,"41":4,"42":1,"50":2,"51":1,"54":1,"56":1,"60":2,"63":1,"66":1,"90":1,"133":2,"138":1,"140":2,"152":1,"165":1,"170":1,"218":2,"225":1,"231":1,"238":2,"281":1,"310":1,"384":1,"559":2,"565":3,"567":1,"568":2,"577":1,"578":1,"579":1,"580":1,"586":2,"587":2,"588":1,"596":1,"614":3,"615":1,"616":2,"617":1,"618":2,"625":2,"626":2,"746":2,"759":1,"760":2,"761":3,"762":1,"763":1,"764":2,"767":1,"768":3,"769":1,"770":2,"771":3,"772":1,"773":1,"774":2,"775":1,"776":3,"786":3,"787":1,"788":3,"789":1,"827":1,"829":1,"830":1,"868":1,"886":1,"891":2,"892":2,"899":1,"900":1,"902":2,"903":1,"904":2,"907":1,"1037":1,"1060":1,"1086":1,"1095":2,"1101":1,"1111":1,"1125":1,"1151":1,"1153":1,"1177":1,"1180":1,"1181":2,"1182":2,"1217":1,"1219":1,"1220":1,"1221":1,"1222":1,"1231":1,"1232":1,"1233":1,"1238":1,"1250":1,"1385":1,"1386":9,"1391":1,"1398":2,"1419":1,"1435":1,"1472":2,"1480":1,"1482":1,"1499":1,"1504":2,"1506":2,"1508":2,"1586":1,"1588":1,"1651":2,"1669":1,"1670":1,"1672":4,"1679":1,"1680":1,"1684":2,"1686":1,"1688":1,"1689":1,"1785":1,"1790":2,"1792":51,"1797":2,"1805":1,"1837":2,"1844":2,"1864":1,"1869":1,"1870":1,"1871":1,"1872":1,"1882":1,"1887":1,"2000":1,"2007":1,"2008":1,"2009":2,"2011":3,"2013":1,"2094":1,"2111":7,"2128":2,"2129":1,"2130":2,"2131":1,"2153":1,"2155":1,"2168":1,"2208":1,"2223":1,"2228":2,"2253":1,"2255":4,"2258":2,"2289":1,"2319":1,"2320":4,"2321":1,"2322":1,"2323":1,"2328":1,"2330":3,"2337":2,"2338":2,"2339":5,"2340":1,"2342":1,"2354":3,"2357":4,"2372":4,"2403":1,"2414":1,"2498":1,"2532":6,"2534":2,"2537":1,"2540":1,"2541":1,"2545":2,"2546":1,"2558":1,"2572":3,"2577":1,"2597":3,"2614":3,"2649":1,"2663":1,"2664":1,"2673":1,"2679":2,"2680":1,"2681":3,"2682":1,"2684":1,"2687":2,"2691":2,"2693":1,"2696":1,"2697":1,"2719":1,"2756":2,"2772":1,"2774":1,"2794":1,"2795":2,"2803":2,"2823":2,"2824":1,"2825":2,"2841":1,"2845":1,"2855":1,"2859":1,"2871":3,"2872":1,"2873":1,"2874":6,"2875":3}}],["commitasync",{"2":{"2615":1}}],["committed",{"2":{"2533":1,"2881":1}}],["commits",{"2":{"864":1,"1079":2,"1324":1,"1325":1,"1382":1,"2741":1,"2868":1}}],["commit",{"2":{"584":1,"622":2,"624":1,"860":1,"865":1,"874":1,"879":1,"1070":3,"1076":1,"1078":1,"1079":2,"1102":1,"1324":1,"1325":1,"1366":1,"1376":2,"1395":1,"1396":2,"1400":1,"1792":2,"1850":1,"1851":1,"1852":1,"2337":1,"2342":2,"2382":2,"2383":1,"2527":1,"2533":1,"2545":1,"2741":1,"2841":1,"2851":1,"2855":1,"2862":1,"2868":3}}],["commons",{"2":{"879":1}}],["commonly",{"2":{"357":1,"726":1,"1859":1}}],["common",{"0":{"546":1,"576":1,"994":1,"1972":1,"2125":1,"2213":1,"2699":1},"1":{"2214":1,"2215":1,"2216":1,"2217":1,"2218":1},"2":{"75":1,"92":1,"133":1,"215":1,"414":1,"624":1,"650":1,"663":1,"748":1,"836":1,"914":1,"916":1,"993":1,"1067":1,"1152":1,"1162":1,"1176":1,"1193":1,"1210":1,"1324":1,"1366":1,"1459":1,"1524":1,"1616":1,"1624":1,"1694":2,"1708":1,"1792":6,"1925":1,"1943":1,"1959":1,"2014":1,"2020":1,"2095":1,"2300":1,"2375":1,"2381":1,"2438":1,"2471":1,"2529":1,"2540":1,"2632":2,"2680":1,"2721":1,"2804":1,"2865":1}}],["commentlineresult",{"2":{"2482":1}}],["commentparsers",{"2":{"2419":1}}],["commentparamretyped",{"2":{"2372":1}}],["commentparamrenamed",{"2":{"2372":1}}],["commentparamnotexistscantrename",{"2":{"2372":1}}],["commenting",{"2":{"2406":1}}],["commented",{"0":{"2677":1},"2":{"1792":1,"2231":1,"2682":1,"2700":1}}],["commentheaderincludecomments",{"2":{"1553":1,"1556":1,"1581":1,"1752":1,"1753":1,"1758":1,"1792":2}}],["commentheader",{"2":{"1553":1,"1556":1,"1581":1,"1752":1,"1753":2,"1758":2,"1792":4}}],["comment",{"0":{"323":1,"1389":1,"1556":1,"1557":1,"1726":1,"1755":1,"2190":1,"2252":1,"2339":1,"2358":1,"2366":1,"2432":1,"2581":1},"1":{"1557":1,"2191":1,"2192":1,"2193":1,"2194":1,"2195":1,"2196":1,"2197":1,"2198":1,"2199":1,"2200":1,"2201":1,"2202":1,"2203":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2211":1,"2212":1,"2213":1,"2214":1,"2215":1,"2216":1,"2217":1,"2218":1,"2219":1},"2":{"7":1,"9":2,"11":1,"16":1,"17":3,"18":1,"19":1,"20":1,"21":1,"26":1,"37":1,"38":1,"39":1,"40":1,"42":1,"48":1,"50":1,"53":1,"60":1,"61":1,"62":1,"65":1,"71":1,"72":1,"76":1,"89":1,"98":1,"104":1,"115":1,"116":1,"117":1,"119":1,"122":1,"128":1,"130":1,"136":1,"137":1,"141":1,"151":1,"157":1,"164":1,"175":1,"184":1,"186":2,"187":2,"189":1,"198":1,"202":1,"203":1,"206":1,"207":1,"208":1,"209":2,"211":2,"213":3,"214":4,"215":1,"217":1,"220":3,"244":1,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"259":1,"263":2,"264":2,"277":2,"278":2,"281":1,"288":1,"289":1,"290":1,"291":1,"292":1,"293":1,"298":1,"309":1,"312":1,"313":1,"317":1,"318":1,"319":9,"320":3,"322":1,"326":2,"327":1,"332":1,"333":1,"334":1,"335":1,"338":1,"345":1,"351":1,"352":1,"354":1,"356":1,"360":1,"361":1,"365":1,"366":1,"367":1,"374":1,"385":1,"386":2,"396":1,"401":1,"405":1,"406":1,"408":2,"410":1,"415":1,"417":1,"418":1,"419":1,"420":1,"421":1,"423":1,"426":1,"427":1,"428":1,"436":1,"438":1,"439":1,"441":1,"442":1,"443":1,"444":1,"445":1,"449":1,"451":2,"452":1,"453":1,"454":1,"466":1,"467":1,"468":1,"469":1,"470":1,"471":1,"481":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"495":1,"503":1,"505":1,"510":1,"511":1,"513":1,"520":1,"521":1,"522":2,"523":1,"525":1,"527":1,"531":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"547":1,"556":1,"557":1,"568":1,"577":2,"578":1,"588":1,"592":1,"593":1,"594":1,"596":1,"605":1,"611":1,"617":1,"626":1,"634":1,"646":1,"647":1,"658":1,"659":2,"664":2,"670":1,"677":1,"679":1,"680":1,"683":1,"685":1,"686":1,"687":1,"706":1,"722":1,"723":1,"724":1,"725":1,"733":1,"734":1,"735":1,"736":1,"740":1,"750":1,"751":1,"752":1,"755":1,"756":1,"764":1,"774":1,"777":1,"790":1,"797":1,"798":1,"799":1,"804":1,"811":1,"812":1,"813":1,"814":1,"815":1,"820":1,"830":1,"835":2,"876":1,"886":1,"904":1,"914":2,"915":2,"916":3,"917":1,"918":1,"934":1,"935":1,"936":1,"957":1,"960":2,"979":2,"980":1,"988":1,"990":1,"1010":2,"1016":1,"1017":1,"1019":3,"1021":1,"1032":1,"1033":1,"1034":3,"1036":1,"1040":3,"1055":1,"1057":1,"1073":1,"1078":1,"1086":2,"1095":1,"1098":1,"1105":7,"1113":1,"1127":1,"1135":3,"1138":2,"1139":1,"1141":1,"1142":1,"1149":1,"1150":3,"1154":2,"1163":3,"1176":1,"1179":2,"1189":1,"1308":1,"1309":1,"1310":1,"1313":2,"1321":1,"1331":1,"1332":1,"1337":1,"1338":1,"1339":1,"1345":2,"1347":1,"1348":1,"1358":4,"1362":1,"1368":1,"1378":3,"1386":4,"1387":1,"1389":3,"1390":1,"1393":1,"1396":1,"1398":7,"1399":1,"1401":2,"1405":1,"1406":1,"1407":1,"1408":1,"1422":1,"1423":1,"1426":2,"1427":1,"1431":2,"1434":1,"1443":1,"1465":1,"1484":1,"1495":1,"1506":1,"1529":4,"1530":1,"1535":1,"1547":1,"1549":1,"1556":1,"1557":2,"1567":1,"1583":1,"1599":3,"1600":1,"1610":1,"1632":1,"1634":1,"1647":1,"1664":2,"1665":1,"1679":1,"1699":1,"1723":2,"1726":1,"1728":1,"1731":2,"1733":1,"1736":2,"1738":1,"1740":3,"1742":1,"1743":1,"1744":2,"1748":2,"1750":1,"1753":1,"1755":2,"1760":1,"1792":33,"1811":1,"1824":1,"1834":1,"1840":4,"1843":1,"1844":1,"1855":1,"1862":1,"1864":1,"1908":1,"1913":1,"1920":1,"1921":1,"1924":1,"1926":1,"1930":2,"1932":1,"1945":1,"1962":1,"1976":1,"1996":1,"2000":1,"2004":1,"2005":1,"2043":1,"2076":1,"2078":1,"2079":1,"2081":1,"2090":1,"2106":1,"2120":1,"2133":1,"2139":1,"2147":1,"2150":1,"2156":1,"2169":1,"2176":1,"2183":1,"2184":1,"2186":1,"2187":3,"2190":3,"2191":1,"2192":1,"2193":5,"2194":1,"2196":5,"2199":3,"2200":3,"2202":1,"2204":2,"2205":3,"2206":2,"2214":1,"2215":1,"2237":1,"2247":2,"2249":1,"2251":1,"2252":2,"2253":1,"2255":3,"2257":3,"2258":1,"2264":8,"2277":4,"2282":1,"2283":3,"2287":1,"2288":3,"2290":1,"2291":1,"2292":1,"2293":2,"2294":2,"2303":1,"2304":1,"2314":5,"2323":2,"2332":1,"2338":1,"2339":2,"2344":2,"2346":4,"2358":2,"2365":3,"2366":4,"2380":1,"2389":2,"2391":2,"2393":1,"2419":1,"2430":1,"2432":2,"2437":1,"2438":1,"2452":1,"2471":1,"2481":6,"2482":5,"2487":3,"2498":1,"2502":1,"2529":5,"2533":2,"2542":1,"2546":1,"2549":10,"2558":1,"2575":2,"2580":2,"2581":4,"2587":2,"2591":3,"2596":1,"2629":1,"2641":1,"2665":2,"2671":1,"2678":1,"2695":1,"2706":1,"2721":2,"2727":1,"2759":1,"2760":1,"2762":6,"2764":2,"2766":3,"2767":3,"2768":1,"2773":1,"2775":2,"2785":1,"2793":1,"2797":1,"2809":1,"2810":1,"2812":1,"2813":1,"2815":1,"2822":2,"2824":3,"2825":1,"2826":1,"2829":1,"2834":2,"2836":2,"2839":1,"2841":1,"2865":3,"2878":1}}],["commentscope",{"0":{"2005":1},"1":{"2006":1},"2":{"324":1,"1792":1,"1999":1,"2000":1,"2006":2,"2323":1,"2330":2,"2841":1}}],["commentsmode",{"0":{"244":1,"2004":1},"2":{"244":1,"659":1,"1792":2,"1836":1,"1840":2,"1863":2,"1999":1,"2000":1,"2195":1,"2209":1,"2223":1,"2330":2,"2367":1,"2369":1,"2482":1,"2701":1,"2721":2,"2722":1,"2841":2}}],["comments",{"0":{"1113":1,"1840":1,"2209":1,"2323":1},"2":{"3":1,"220":2,"239":1,"244":1,"324":1,"378":1,"694":1,"703":1,"704":1,"706":1,"709":1,"711":1,"713":1,"714":1,"837":1,"845":1,"868":1,"995":1,"1002":1,"1037":1,"1073":1,"1078":1,"1084":1,"1086":2,"1108":1,"1109":1,"1113":2,"1125":1,"1127":1,"1135":1,"1378":1,"1386":6,"1392":1,"1556":1,"1608":1,"1632":1,"1753":1,"1792":6,"1840":1,"2000":1,"2004":2,"2005":2,"2006":2,"2147":1,"2164":1,"2190":3,"2191":2,"2247":1,"2272":1,"2318":1,"2323":6,"2330":1,"2358":1,"2359":1,"2365":1,"2528":1,"2529":1,"2531":1,"2533":1,"2540":1,"2677":1,"2678":1,"2686":1,"2694":1,"2695":1,"2706":1,"2772":1,"2793":1,"2826":1,"2840":1,"2841":1,"2845":1,"2863":1,"2869":1,"2870":1}}],["congratulations",{"2":{"2823":1}}],["conjunctively",{"2":{"2433":1}}],["conjunctive",{"2":{"1909":1}}],["conn",{"2":{"1333":1}}],["connecteventsource",{"2":{"1318":1}}],["connected",{"2":{"636":1,"638":1,"645":1,"650":2,"1044":1,"1103":1,"1183":1,"1303":1,"1304":1,"1305":2,"1309":2,"1318":2,"1320":1,"1372":1,"1386":1,"1704":1,"1792":1,"1857":1,"2362":1,"2391":1,"2393":1,"2490":2,"2818":1,"2823":1,"2827":1,"2828":2,"2829":1,"2831":2,"2836":1}}],["connecting",{"0":{"1202":1},"2":{"1441":1,"2828":1}}],["connectivity",{"2":{"1100":1,"1609":1,"1764":1,"1767":1,"1770":1,"1792":4,"2634":5,"2785":1}}],["connectiontest",{"2":{"2415":1}}],["connectionname",{"0":{"2098":1},"2":{"693":1,"697":1,"705":2,"967":1,"1217":1,"1224":1,"1630":1,"1631":1,"1633":1,"1763":1,"1764":1,"1771":1,"1792":8,"1836":1,"1837":1,"1863":1,"1874":1,"2046":1,"2047":1,"2063":1,"2093":1,"2094":1,"2098":1,"2111":5,"2112":1,"2532":5,"2534":4,"2537":1,"2635":1,"2701":1,"2740":1,"2871":3,"2872":4,"2873":5,"2876":1}}],["connection|",{"2":{"663":1}}],["connectionstring",{"2":{"1792":4,"2254":1,"2714":1}}],["connectionstrings",{"2":{"150":1,"693":1,"695":1,"937":1,"1173":1,"1176":1,"1177":1,"1606":2,"1607":1,"1613":2,"1614":1,"1615":1,"1627":1,"1629":1,"1631":1,"1633":1,"1771":1,"1792":9,"1837":2,"2063":1,"2094":1,"2098":2,"2111":1,"2256":3,"2266":2,"2532":2,"2533":1,"2534":2,"2537":1,"2686":1,"2687":1,"2689":1,"2690":2,"2691":1,"2699":1,"2701":1,"2718":1,"2823":2,"2824":2,"2825":1,"2871":1,"2872":1,"2873":1}}],["connectionsettings",{"2":{"1152":2,"1174":1,"1176":1,"1177":1,"1617":2,"1622":1,"1625":1,"1628":1,"1629":1,"1633":1,"1792":1,"1848":1,"2256":2,"2266":1,"2486":1,"2701":1}}],["connections",{"0":{"1173":1,"1331":1,"1614":1,"1632":1,"2362":1},"2":{"151":2,"153":1,"576":1,"669":1,"818":1,"845":1,"986":1,"1014":1,"1015":1,"1037":1,"1101":1,"1151":1,"1152":1,"1155":1,"1169":2,"1174":2,"1176":2,"1178":2,"1180":2,"1250":1,"1303":1,"1305":1,"1323":1,"1325":3,"1329":1,"1349":3,"1466":1,"1536":1,"1594":1,"1601":1,"1613":1,"1614":1,"1616":2,"1619":1,"1621":1,"1624":1,"1626":1,"1627":1,"1630":1,"1631":1,"1700":1,"1749":1,"1792":5,"1812":1,"1837":1,"1865":1,"1991":3,"1997":1,"2091":1,"2111":1,"2149":1,"2221":2,"2266":3,"2362":1,"2407":1,"2533":1,"2615":1,"2701":1,"2706":1,"2876":1}}],["connection",{"0":{"144":1,"147":1,"693":1,"1102":1,"1152":1,"1329":1,"1528":1,"1593":1,"1612":1,"1613":1,"1616":1,"1617":1,"1619":1,"1621":1,"1626":1,"1627":1,"1630":1,"1631":1,"1771":1,"1837":1,"1850":1,"1990":1,"2063":1,"2266":1,"2382":1,"2393":1,"2486":1,"2533":1},"1":{"145":1,"146":1,"147":1,"148":1,"149":1,"150":1,"151":1,"152":1,"153":1,"694":1,"695":1,"696":1,"697":1,"1613":1,"1614":2,"1615":2,"1616":2,"1617":1,"1618":2,"1619":2,"1620":2,"1621":2,"1622":1,"1623":1,"1624":1,"1625":1,"1626":1,"1627":2,"1628":2,"1629":2,"1630":1,"1631":2,"1632":2,"1633":1,"1634":1,"1635":1,"1636":1,"1851":1,"1852":1,"1991":1},"2":{"144":2,"145":4,"147":1,"148":1,"149":1,"150":2,"151":1,"153":1,"159":3,"236":2,"239":2,"424":2,"436":1,"438":1,"447":1,"529":1,"576":2,"650":2,"666":1,"668":1,"669":4,"693":4,"694":2,"695":1,"696":4,"705":2,"706":1,"707":2,"711":1,"714":2,"715":1,"716":2,"717":2,"807":1,"876":1,"922":1,"926":1,"937":1,"948":1,"967":1,"1014":2,"1037":1,"1067":2,"1070":3,"1076":2,"1078":3,"1079":1,"1086":1,"1101":4,"1102":2,"1104":1,"1105":2,"1107":1,"1117":3,"1137":1,"1141":2,"1150":1,"1151":1,"1152":6,"1153":1,"1155":1,"1169":1,"1172":1,"1173":1,"1174":2,"1175":1,"1176":11,"1177":1,"1179":3,"1180":1,"1181":5,"1182":4,"1217":1,"1224":1,"1250":1,"1276":1,"1281":1,"1303":2,"1305":1,"1309":2,"1317":1,"1320":2,"1325":4,"1326":2,"1328":4,"1329":3,"1331":1,"1337":2,"1342":1,"1349":1,"1363":2,"1385":1,"1394":1,"1398":2,"1441":2,"1466":1,"1500":2,"1511":1,"1514":1,"1522":2,"1528":1,"1536":1,"1593":3,"1601":2,"1608":1,"1611":2,"1612":4,"1613":2,"1615":1,"1616":9,"1617":1,"1618":5,"1619":1,"1620":2,"1621":2,"1622":1,"1623":1,"1624":10,"1627":1,"1628":2,"1630":1,"1631":1,"1632":4,"1633":1,"1634":2,"1636":2,"1700":1,"1746":1,"1749":1,"1753":1,"1764":2,"1771":2,"1783":2,"1787":2,"1792":57,"1794":2,"1812":1,"1818":1,"1837":1,"1844":1,"1850":1,"1851":2,"1865":1,"1874":1,"1920":1,"1925":1,"1928":1,"1957":1,"1982":1,"1984":1,"1990":1,"1997":1,"2047":2,"2071":2,"2091":1,"2094":1,"2098":4,"2099":1,"2109":1,"2110":2,"2111":2,"2114":1,"2121":2,"2137":1,"2156":1,"2167":2,"2221":1,"2226":1,"2239":1,"2247":3,"2256":10,"2266":10,"2284":1,"2307":2,"2347":1,"2364":1,"2379":1,"2380":1,"2382":2,"2393":1,"2459":4,"2463":1,"2466":2,"2486":1,"2517":1,"2526":1,"2527":4,"2530":3,"2531":2,"2532":2,"2533":6,"2534":3,"2537":2,"2542":1,"2545":1,"2549":1,"2559":1,"2575":1,"2591":1,"2635":2,"2669":1,"2686":1,"2691":1,"2699":1,"2701":1,"2706":1,"2717":1,"2718":2,"2721":1,"2739":1,"2763":1,"2802":1,"2807":2,"2809":1,"2810":1,"2813":1,"2819":1,"2823":5,"2824":2,"2825":3,"2828":1,"2830":1,"2834":1,"2862":4,"2866":1,"2869":2,"2870":2,"2871":3,"2872":1,"2873":1,"2874":2,"2875":1,"2876":1,"2882":1}}],["connectretry=3",{"2":{"1067":1,"1510":1,"1514":1,"1792":1}}],["connecttimeout=10000",{"2":{"1067":1,"1510":1,"1514":1,"1792":1}}],["connects",{"2":{"832":1,"937":1,"1086":1,"1173":1,"1177":1,"1203":2,"2672":1,"2709":1,"2828":1,"2876":1}}],["connect",{"0":{"2718":1},"2":{"576":1,"650":1,"664":1,"1107":1,"1184":1,"1202":1,"1207":1,"1318":1,"1320":2,"1373":1,"1406":1,"1595":1,"1624":2,"1792":2,"2020":1,"2029":1,"2834":1}}],["concat",{"2":{"2614":1,"2622":2}}],["concatenate",{"2":{"2265":1}}],["concatenated",{"2":{"488":1,"494":2,"528":1,"2712":1}}],["concatenation",{"2":{"528":1}}],["concludes",{"2":{"1134":1}}],["concluded",{"2":{"1075":1}}],["conclusion",{"0":{"838":1,"910":1,"920":1,"946":1,"971":1,"1005":1,"1036":1,"1064":1,"1127":1,"1208":1,"1253":1,"1280":1,"1327":1},"1":{"911":1,"1065":1,"1281":1},"2":{"847":1,"1075":1,"1254":1,"1405":1}}],["concrete",{"2":{"873":1,"926":1}}],["concentrated",{"2":{"2419":1}}],["concentrates",{"2":{"874":1}}],["concession",{"2":{"859":1,"861":1}}],["conceded",{"2":{"856":1,"864":1}}],["concedes",{"2":{"855":1,"864":1}}],["concern",{"2":{"868":1,"869":2,"1303":1,"1420":1}}],["concerned",{"2":{"849":1}}],["concerns",{"2":{"837":1,"869":1,"873":1,"874":1,"876":1,"1015":1,"1385":1,"1420":1}}],["concepts",{"2":{"843":1,"1084":1,"2160":1,"2482":1}}],["concept",{"2":{"841":3,"848":1,"864":1,"918":1}}],["conceptual",{"2":{"841":1,"2170":1,"2759":1,"2806":1,"2827":1}}],["concurrency",{"0":{"862":1,"1161":1,"1263":1,"1954":1},"1":{"863":1,"864":1,"865":1},"2":{"480":1,"841":1,"844":1,"854":1,"855":1,"864":1,"865":1,"868":2,"1101":1,"1161":4,"1163":2,"1169":2,"1170":1,"1179":2,"1254":1,"1255":1,"1258":1,"1266":1,"1267":1,"1280":1,"1285":1,"1324":1,"1792":4,"1950":1,"1954":4,"1960":2,"2088":1,"2089":1,"2257":4,"2258":1,"2398":1,"2442":1,"2443":1,"2498":1,"2551":2,"2789":1}}],["concurrentdictionary",{"2":{"2462":1}}],["concurrently",{"2":{"351":3,"993":1,"1105":1,"1161":1,"1792":1,"2094":1,"2099":1,"2546":1,"2766":1}}],["concurrent",{"0":{"1090":1},"2":{"214":1,"844":1,"864":2,"919":1,"1007":1,"1090":1,"1147":2,"1164":1,"1165":1,"1167":1,"1168":2,"1169":2,"1171":1,"1263":1,"1430":1,"1447":1,"1515":1,"1743":1,"1792":3,"1950":1,"1954":2,"2274":1,"2459":1,"2461":1,"2462":1,"2463":2,"2464":1,"2465":2,"2466":3,"2498":2,"2502":1,"2534":1,"2537":1,"2765":1}}],["conveyance",{"2":{"1792":1}}],["convenient",{"2":{"1382":1}}],["convenience",{"2":{"975":1,"1382":1,"2779":1}}],["conventionally",{"2":{"2461":1}}],["conventional",{"2":{"871":1,"872":3,"874":1,"875":1,"876":1,"1409":1,"2481":1}}],["convention",{"0":{"1480":1,"1481":1},"2":{"448":1,"976":1,"1792":2,"2193":2,"2438":1,"2451":1,"2537":1,"2689":1}}],["conventions",{"0":{"379":1,"1688":1},"2":{"245":1,"259":1,"374":1,"410":1,"1060":1,"1480":1,"1686":2,"1787":1,"1794":1,"2197":1,"2333":1,"2438":1,"2538":1,"2581":1,"2848":1}}],["converse",{"2":{"1792":1}}],["conversions",{"2":{"968":1}}],["conversion",{"0":{"2397":1},"1":{"2398":1},"2":{"370":1,"379":1,"2226":1,"2270":2,"2333":1,"2397":1,"2452":1}}],["converter",{"2":{"2327":1,"2540":1,"2546":1,"2723":1,"2845":1}}],["convertedname",{"2":{"1523":1,"2372":1}}],["converted",{"0":{"814":1},"2":{"74":2,"75":1,"258":1,"299":1,"384":1,"388":1,"407":1,"528":1,"529":1,"809":1,"814":1,"818":1,"995":1,"1481":1,"1567":1,"1792":2,"1856":2,"2221":1,"2222":1,"2224":1,"2277":1,"2284":1,"2454":1,"2455":1,"2518":3,"2519":1,"2523":1,"2575":1,"2588":1,"2723":1,"2842":1}}],["converts",{"2":{"786":1,"2451":2,"2724":1}}],["convert",{"2":{"263":3,"768":1,"891":1,"952":1,"1792":2,"1841":2,"1930":1,"2128":1,"2344":2,"2451":1}}],["conditions",{"0":{"1440":1},"2":{"639":1,"1089":1,"1180":1,"1181":1,"1440":1,"1441":1,"1442":1,"1519":1,"1792":1,"2380":1,"2576":1}}],["condition",{"0":{"1524":1},"2":{"230":1,"1101":1,"1121":1,"1386":1,"1402":1,"1523":1,"2363":1,"2380":2,"2597":1}}],["conditionals",{"2":{"2621":1}}],["conditionally",{"2":{"176":1,"181":1}}],["conditional",{"0":{"175":1,"291":1,"1067":1},"2":{"101":1,"104":1,"123":1,"177":1,"868":1,"971":1,"1037":1,"1101":3,"1121":1,"1127":1,"1150":1,"1385":1,"1394":1,"1521":1,"1792":1,"2380":1}}],["confusing",{"2":{"2242":1}}],["confused",{"0":{"395":1},"2":{"387":1}}],["conformance",{"2":{"2492":1}}],["conform",{"2":{"1824":1,"2481":1}}],["conf",{"2":{"967":1,"1118":1,"1792":3,"2049":2,"2635":3}}],["confession",{"2":{"854":1}}],["conference",{"2":{"851":1}}],["confidence",{"2":{"1336":1,"1339":7}}],["confidently",{"2":{"947":1}}],["confirmation",{"2":{"1220":1,"1870":1}}],["confirmed",{"2":{"852":1,"1386":1}}],["confirms",{"2":{"841":1}}],["confirm",{"2":{"565":3,"2320":2,"2357":1,"2774":1}}],["config=",{"2":{"2872":1}}],["config=timeout",{"2":{"2678":1,"2695":1,"2700":1}}],["configtests",{"2":{"2417":1,"2447":1,"2448":1,"2472":1}}],["configvalidationtests",{"2":{"2417":1,"2447":1,"2448":1,"2472":1}}],["configvalid",{"2":{"2415":3}}],["configdefaults",{"2":{"2409":1,"2440":1,"2448":1,"2666":2}}],["config`",{"2":{"1792":2}}],["configs",{"2":{"1157":1,"1792":1,"1840":1,"1908":1,"1911":1,"1958":1,"2426":1,"2470":1,"2477":1,"2482":1}}],["config",{"0":{"349":1,"354":1,"1603":1,"2414":1,"2425":1,"2431":1,"2434":1,"2577":1,"2662":1,"2670":1,"2673":1,"2677":1,"2678":1,"2705":1},"1":{"1604":1,"1605":1,"1606":1,"1607":1,"1608":1,"1609":1,"1610":1,"1611":1,"2415":1,"2416":1,"2426":1,"2427":1,"2428":1,"2429":1},"2":{"67":1,"96":1,"115":2,"118":1,"219":1,"278":1,"300":1,"304":1,"305":2,"306":1,"349":2,"356":1,"395":2,"432":1,"457":1,"499":1,"542":4,"583":1,"584":2,"586":1,"682":1,"695":1,"705":1,"710":3,"739":1,"822":1,"826":3,"827":1,"849":1,"867":1,"868":4,"869":10,"889":1,"958":1,"976":1,"1044":1,"1049":1,"1059":1,"1070":4,"1071":1,"1074":1,"1079":1,"1080":2,"1086":1,"1101":1,"1102":7,"1125":1,"1135":3,"1138":1,"1141":2,"1181":7,"1197":1,"1199":1,"1207":2,"1281":2,"1343":2,"1350":1,"1356":1,"1367":1,"1372":4,"1378":1,"1379":1,"1382":1,"1394":1,"1395":2,"1417":2,"1418":5,"1420":2,"1449":1,"1464":1,"1532":2,"1582":1,"1603":2,"1604":1,"1605":1,"1608":1,"1609":2,"1787":1,"1788":1,"1792":4,"1794":1,"1813":1,"1849":1,"1850":2,"1851":1,"1852":3,"1908":1,"1911":1,"1948":1,"1961":1,"2040":1,"2092":4,"2096":1,"2097":1,"2110":1,"2111":1,"2120":1,"2155":4,"2156":1,"2181":2,"2205":1,"2223":1,"2224":1,"2225":2,"2231":2,"2272":2,"2336":1,"2337":3,"2338":3,"2352":1,"2364":1,"2376":1,"2378":1,"2382":2,"2383":4,"2389":3,"2409":1,"2412":1,"2414":3,"2415":3,"2416":3,"2417":1,"2419":2,"2425":1,"2428":2,"2429":1,"2430":2,"2434":2,"2440":1,"2441":1,"2448":1,"2457":1,"2468":1,"2472":1,"2476":1,"2477":1,"2481":2,"2484":1,"2486":1,"2497":2,"2498":1,"2522":2,"2526":1,"2534":1,"2537":7,"2539":1,"2542":1,"2543":1,"2545":1,"2546":2,"2558":3,"2577":1,"2611":1,"2659":2,"2660":2,"2662":3,"2666":1,"2670":2,"2673":2,"2677":2,"2678":3,"2679":8,"2682":3,"2688":1,"2689":1,"2693":2,"2694":5,"2695":5,"2696":1,"2700":3,"2701":1,"2705":2,"2719":1,"2734":1,"2736":1,"2739":1,"2747":1,"2754":1,"2757":1,"2785":1,"2847":1,"2852":1,"2855":2,"2857":1,"2858":1,"2861":1,"2872":2,"2877":2,"2878":2,"2880":3}}],["configuring",{"0":{"1166":1},"2":{"296":1,"1825":1}}],["configurability",{"2":{"1111":1}}],["configurable",{"0":{"2273":1},"2":{"33":1,"210":1,"213":1,"300":1,"309":1,"448":1,"480":1,"567":1,"700":1,"739":1,"1097":1,"1098":2,"1100":1,"1101":4,"1105":2,"1106":1,"1109":1,"1111":5,"1127":1,"1250":1,"1255":2,"1341":1,"1386":2,"1739":1,"1802":1,"1926":1,"1928":2,"2014":1,"2264":1,"2265":1,"2266":1,"2287":1,"2320":1,"2372":1,"2392":1,"2424":1,"2438":1,"2500":1,"2530":1,"2536":1,"2549":3,"2632":3,"2763":1,"2795":1,"2810":1,"2842":1,"2866":1}}],["configurations",{"0":{"1578":1,"1710":1,"1777":1,"2026":1,"2065":1},"1":{"1579":1,"1580":1,"1581":1,"1582":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1778":1,"1779":1,"1780":1,"1781":1,"2027":1,"2028":1,"2029":1,"2066":1,"2067":1,"2068":1,"2069":1},"2":{"1588":1,"2413":1,"2420":1,"2423":1}}],["configuration",{"0":{"121":1,"226":1,"227":1,"279":1,"336":1,"430":1,"455":1,"470":1,"556":1,"577":1,"889":1,"937":1,"962":1,"967":1,"998":1,"1022":1,"1034":1,"1062":1,"1112":1,"1114":1,"1168":1,"1177":1,"1196":1,"1198":1,"1217":1,"1223":1,"1240":1,"1340":1,"1416":1,"1494":1,"1497":1,"1534":1,"1555":1,"1560":1,"1598":1,"1609":1,"1625":1,"1646":1,"1678":1,"1735":1,"1758":1,"1773":1,"1778":1,"1785":1,"1792":1,"1859":1,"1889":1,"1892":1,"1893":1,"1944":1,"1979":1,"1984":1,"1985":1,"2042":1,"2089":1,"2118":1,"2148":1,"2203":1,"2272":1,"2279":1,"2308":1,"2330":1,"2349":1,"2350":1,"2406":1,"2436":1,"2486":1,"2537":1,"2551":1,"2594":1,"2659":1,"2661":1,"2677":1,"2678":1,"2680":1,"2681":1,"2683":1,"2684":1,"2685":1,"2686":1,"2693":1,"2694":1,"2696":1,"2697":1,"2700":1,"2701":1,"2703":1,"2719":1,"2754":1,"2769":1,"2814":1,"2825":1,"2835":1,"2841":1},"1":{"963":1,"964":1,"1063":1,"1113":1,"1114":1,"1115":1,"1199":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1498":1,"1499":1,"1500":1,"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"1786":1,"1787":1,"1788":1,"1789":1,"1790":1,"1791":1,"1793":1,"1794":1,"1795":1,"1796":1,"1797":1,"1798":1,"1980":1,"1981":1,"1982":1,"1983":1,"1985":1,"1986":2,"1987":2,"1988":2,"1989":2,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1,"2204":1,"2350":1,"2351":1,"2352":1,"2353":1,"2354":1,"2595":1,"2596":1,"2681":1,"2682":1,"2683":1,"2684":2,"2685":2,"2686":2,"2687":1,"2688":1,"2689":1,"2690":1,"2691":1,"2692":1,"2693":1,"2694":2,"2695":2,"2696":2,"2697":1,"2698":1,"2699":1,"2700":1,"2701":1,"2702":1,"2703":1,"2704":1,"2705":1,"2706":1},"2":{"1":1,"11":4,"26":4,"42":3,"52":1,"53":3,"64":1,"65":3,"76":3,"89":3,"98":3,"101":1,"110":1,"111":1,"121":3,"122":3,"130":2,"134":1,"139":1,"140":1,"141":4,"150":2,"151":4,"154":1,"164":4,"170":1,"182":1,"189":1,"197":1,"198":3,"216":1,"217":2,"244":1,"259":3,"279":1,"293":4,"306":1,"307":1,"315":1,"327":1,"336":1,"345":2,"356":1,"367":3,"410":3,"417":1,"419":1,"430":2,"431":1,"436":1,"441":1,"443":1,"455":2,"456":1,"471":3,"473":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":3,"495":2,"505":3,"513":3,"525":3,"545":1,"547":3,"557":3,"567":1,"568":1,"570":1,"575":1,"577":2,"578":3,"588":1,"596":3,"605":2,"624":1,"626":1,"634":3,"647":3,"655":1,"670":3,"672":1,"673":2,"680":3,"697":1,"702":2,"704":1,"707":1,"712":1,"717":1,"718":2,"725":3,"740":3,"779":1,"790":3,"801":1,"804":3,"809":1,"816":1,"817":1,"820":3,"837":1,"867":1,"868":2,"873":1,"877":1,"902":1,"908":1,"914":1,"937":2,"976":1,"998":1,"1010":1,"1022":1,"1026":2,"1027":1,"1047":1,"1054":1,"1062":1,"1064":2,"1065":1,"1066":1,"1080":1,"1082":1,"1086":1,"1094":1,"1098":1,"1100":2,"1102":1,"1105":1,"1109":2,"1111":2,"1113":2,"1114":1,"1115":1,"1125":1,"1127":1,"1135":1,"1141":1,"1168":1,"1171":1,"1177":1,"1181":4,"1182":3,"1196":2,"1197":1,"1207":1,"1217":4,"1223":1,"1252":3,"1255":1,"1280":1,"1281":1,"1320":2,"1329":1,"1340":1,"1351":1,"1356":1,"1361":1,"1380":1,"1385":4,"1386":1,"1396":1,"1408":1,"1416":2,"1417":1,"1421":1,"1465":2,"1468":1,"1482":2,"1483":1,"1484":3,"1485":1,"1487":1,"1495":2,"1499":1,"1502":1,"1504":1,"1505":1,"1506":3,"1509":1,"1514":1,"1534":2,"1535":2,"1548":1,"1549":4,"1550":1,"1552":1,"1581":1,"1583":2,"1594":1,"1598":1,"1600":2,"1603":1,"1604":2,"1606":1,"1607":2,"1608":1,"1609":5,"1610":2,"1611":1,"1612":1,"1615":1,"1618":1,"1620":1,"1629":1,"1633":1,"1634":2,"1637":1,"1646":2,"1647":2,"1661":1,"1663":2,"1665":2,"1668":1,"1678":2,"1679":2,"1689":1,"1690":1,"1696":1,"1698":1,"1699":2,"1711":1,"1718":2,"1720":1,"1732":1,"1748":2,"1751":1,"1760":2,"1785":5,"1787":2,"1788":3,"1789":1,"1790":2,"1791":3,"1792":24,"1794":2,"1795":2,"1797":2,"1798":3,"1799":1,"1801":1,"1802":2,"1810":1,"1811":2,"1813":1,"1835":1,"1836":8,"1863":2,"1864":2,"1867":1,"1894":1,"1896":1,"1907":1,"1913":2,"1915":1,"1931":1,"1932":2,"1944":1,"1945":2,"1947":1,"1960":1,"1962":2,"1973":1,"1976":2,"1977":1,"1978":1,"1980":1,"1984":1,"1995":1,"1996":2,"1998":1,"2030":1,"2032":1,"2034":1,"2043":2,"2049":1,"2056":1,"2080":1,"2081":2,"2084":1,"2089":1,"2090":2,"2092":2,"2094":1,"2106":1,"2111":2,"2113":1,"2115":1,"2120":3,"2121":1,"2122":1,"2132":1,"2133":2,"2137":1,"2146":1,"2150":2,"2153":2,"2157":2,"2158":1,"2159":1,"2169":2,"2170":1,"2172":1,"2177":1,"2187":1,"2189":1,"2197":1,"2209":1,"2218":1,"2221":1,"2223":1,"2231":2,"2232":1,"2249":1,"2251":2,"2253":2,"2254":2,"2255":4,"2256":2,"2257":2,"2259":2,"2264":2,"2265":3,"2266":1,"2267":2,"2270":1,"2272":1,"2273":1,"2274":1,"2279":1,"2291":1,"2297":2,"2308":1,"2329":1,"2342":1,"2351":1,"2354":1,"2369":1,"2389":3,"2396":1,"2410":3,"2414":1,"2441":2,"2479":1,"2481":1,"2486":2,"2502":1,"2525":1,"2532":2,"2535":1,"2537":1,"2541":2,"2542":1,"2543":3,"2545":1,"2546":2,"2549":2,"2551":3,"2554":2,"2558":1,"2565":1,"2572":1,"2575":2,"2577":5,"2587":1,"2588":1,"2594":1,"2627":2,"2628":1,"2632":1,"2633":1,"2634":1,"2635":1,"2641":1,"2645":2,"2648":1,"2655":1,"2659":2,"2660":1,"2661":1,"2669":1,"2670":1,"2677":1,"2679":1,"2680":2,"2681":5,"2682":3,"2684":4,"2685":1,"2686":1,"2687":4,"2689":2,"2691":2,"2693":2,"2694":2,"2696":1,"2697":2,"2700":2,"2701":3,"2705":3,"2706":2,"2719":1,"2742":2,"2754":1,"2756":1,"2759":2,"2771":1,"2772":2,"2773":1,"2774":1,"2779":2,"2785":3,"2788":1,"2793":2,"2794":1,"2795":2,"2805":1,"2806":2,"2817":1,"2822":1,"2824":1,"2825":3,"2826":3,"2827":1,"2832":1,"2841":1,"2857":1,"2859":2,"2860":2,"2871":1,"2872":1,"2878":2,"2882":1}}],["configures",{"2":{"305":1,"868":1,"886":1,"1053":1,"1622":1,"1984":1,"2171":1}}],["configured",{"0":{"739":1},"2":{"25":1,"31":1,"41":1,"60":1,"63":3,"108":1,"134":1,"139":1,"214":1,"245":1,"299":1,"302":2,"305":1,"317":1,"347":1,"436":1,"447":1,"473":1,"480":1,"504":1,"524":2,"639":1,"656":1,"659":1,"662":1,"668":1,"734":1,"737":2,"745":1,"747":2,"762":1,"765":1,"772":1,"801":1,"818":1,"819":1,"835":1,"868":2,"905":1,"934":2,"1044":1,"1105":1,"1113":2,"1123":1,"1217":1,"1219":1,"1224":1,"1305":1,"1331":1,"1386":5,"1398":1,"1406":1,"1422":1,"1452":1,"1454":2,"1456":1,"1518":1,"1543":1,"1618":1,"1619":1,"1620":1,"1621":1,"1690":2,"1696":3,"1741":1,"1792":9,"1825":1,"1833":1,"1858":1,"1869":1,"1874":1,"1921":1,"1957":1,"1983":1,"2007":1,"2016":1,"2020":1,"2056":1,"2147":1,"2149":1,"2156":1,"2178":1,"2181":1,"2191":1,"2218":1,"2222":1,"2257":1,"2265":1,"2289":1,"2317":1,"2318":1,"2328":1,"2372":1,"2376":1,"2379":1,"2384":1,"2392":1,"2394":1,"2422":1,"2432":1,"2476":1,"2481":4,"2531":1,"2542":1,"2543":1,"2554":1,"2632":1,"2664":1,"2674":1,"2680":1,"2745":1,"2802":1,"2815":1,"2823":1,"2840":1,"2857":1}}],["configure",{"0":{"958":1,"1356":1,"2088":1,"2172":1},"1":{"2173":1,"2174":1,"2175":1},"2":{"11":2,"26":2,"28":2,"42":1,"53":1,"65":1,"67":1,"76":1,"89":1,"98":1,"100":1,"122":1,"124":1,"141":1,"151":1,"153":1,"164":4,"170":2,"191":1,"198":1,"200":1,"217":1,"219":1,"237":1,"259":1,"293":2,"295":2,"315":1,"367":1,"410":1,"432":1,"449":1,"457":1,"471":1,"481":1,"483":1,"505":1,"513":1,"525":1,"535":1,"545":1,"547":1,"557":1,"568":1,"580":1,"596":1,"634":1,"647":1,"670":1,"682":1,"727":1,"740":1,"742":1,"790":1,"793":1,"804":1,"806":1,"820":1,"822":1,"868":2,"879":1,"911":1,"958":1,"1031":1,"1052":1,"1056":1,"1086":1,"1123":1,"1155":1,"1176":1,"1199":1,"1240":1,"1252":1,"1281":1,"1385":2,"1386":2,"1434":1,"1465":2,"1466":5,"1484":2,"1485":4,"1496":2,"1502":1,"1506":1,"1507":3,"1536":2,"1538":1,"1549":1,"1550":3,"1584":3,"1601":2,"1628":1,"1635":1,"1648":2,"1656":1,"1657":1,"1666":2,"1672":1,"1680":2,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1700":3,"1718":1,"1719":1,"1749":2,"1761":2,"1784":1,"1792":12,"1802":1,"1812":2,"1825":1,"1830":1,"1857":1,"1865":4,"1895":2,"1899":1,"1914":2,"1918":1,"1927":1,"1933":2,"1934":1,"1946":2,"1963":2,"1981":1,"1985":1,"1990":1,"1992":1,"1993":1,"1997":1,"2013":1,"2031":2,"2044":2,"2070":2,"2071":1,"2082":2,"2091":2,"2115":1,"2135":2,"2148":1,"2151":2,"2170":1,"2171":1,"2180":1,"2190":1,"2219":3,"2254":1,"2257":1,"2549":1,"2628":1,"2632":1,"2702":1,"2706":3,"2757":1,"2773":1,"2793":1,"2826":2,"2869":1,"2870":1}}],["conflicting",{"2":{"2321":1}}],["conflicts",{"2":{"384":1,"576":1,"784":1,"1014":1,"2007":1,"2328":1,"2359":1}}],["conflict",{"2":{"184":2,"898":1,"1054":1,"1111":2,"1153":1,"1339":1,"1624":1,"1655":1,"1664":1,"1678":1,"1689":1,"2292":1,"2389":1,"2815":1}}],["consolidation",{"2":{"1205":1}}],["consoleminimumlevel",{"2":{"1792":1,"1800":1,"1803":2,"1810":1,"2804":2}}],["consoleconsolesqlfilesource",{"2":{"1386":1}}],["consoleconsole$",{"2":{"1080":2}}],["console",{"0":{"1803":1},"2":{"894":1,"1024":2,"1110":1,"1218":2,"1254":1,"1320":2,"1342":3,"1361":1,"1409":2,"1410":2,"1691":1,"1792":5,"1799":1,"1803":2,"2094":1,"2102":1,"2104":1,"2526":1,"2535":2,"2537":1,"2794":1,"2800":1,"2804":2,"2824":1,"2825":1,"2830":1,"2880":2}}],["consciousness",{"2":{"913":1}}],["consequence",{"2":{"2590":1}}],["consent",{"2":{"1825":1,"2869":1}}],["consensus",{"2":{"844":1}}],["conservative",{"2":{"872":1,"1181":1}}],["conservatively",{"2":{"869":1,"872":2}}],["consulting",{"2":{"1443":1,"2677":1,"2693":1,"2792":1}}],["consulted",{"2":{"480":1,"1961":1,"2438":1}}],["consumption",{"2":{"918":1,"1516":1,"2265":1,"2615":1,"2667":1}}],["consume",{"2":{"535":1,"559":1,"618":1,"625":1,"833":1,"916":1,"1183":1,"1328":1,"1413":1,"1792":1,"2102":1,"2342":1,"2389":1,"2817":1}}],["consumes",{"2":{"378":1,"1169":1,"1208":1,"1329":1,"1727":1,"1974":1,"2438":1,"2607":1,"2827":1}}],["consumed",{"2":{"300":1,"679":1,"720":1,"723":1,"1413":1,"2656":1}}],["consumers",{"2":{"1043":1,"1127":1,"1190":1,"1193":1,"2371":1,"2385":1,"2415":1}}],["consumer",{"2":{"182":1,"837":1,"872":1,"875":1,"916":1,"1100":1,"1203":1,"1228":1,"1409":1,"1410":1,"1415":1,"1664":1,"1792":1,"1878":1,"2291":1,"2393":1}}],["consuming",{"2":{"206":1,"917":1,"1014":1,"1183":1,"2771":1}}],["consts",{"2":{"2399":1}}],["constrained",{"2":{"1944":1,"1974":1,"2607":1}}],["constraint",{"2":{"845":1,"864":2,"992":2,"1041":1,"2167":1,"2545":1,"2868":1}}],["constraints",{"0":{"992":1,"1594":1,"2868":1},"2":{"845":2,"852":2,"865":3,"876":1,"986":1,"992":5,"1005":2,"1075":2,"1079":3,"1082":1,"1107":1,"1155":1,"1792":1,"2670":1,"2741":2,"2868":4,"2869":1}}],["constructs",{"2":{"1394":4}}],["constructor",{"2":{"916":1,"1026":2,"1573":1}}],["construct",{"2":{"849":1,"1572":1,"2586":1,"2712":1}}],["constructed",{"2":{"652":1,"1792":2,"2677":1}}],["construction",{"0":{"652":1},"1":{"653":1,"654":1},"2":{"696":1,"864":1,"873":1,"875":1,"1410":1,"2476":1,"2621":2,"2712":1}}],["const",{"2":{"429":1,"723":1,"894":3,"938":1,"961":4,"995":3,"996":10,"1024":2,"1026":16,"1063":3,"1107":4,"1218":3,"1317":2,"1318":3,"1320":10,"1321":1,"1326":3,"1335":9,"1342":3,"1361":2,"1366":1,"1386":1,"1408":2,"1409":2,"1410":3,"1413":1,"1416":2,"1431":3,"1567":1,"1568":1,"1569":1,"1571":1,"1572":2,"1573":1,"1574":3,"1575":1,"2247":2,"2310":1,"2313":1,"2648":1,"2655":1,"2830":2,"2836":2}}],["constants",{"0":{"1572":1,"2399":1},"2":{"1416":1,"1563":1,"1581":2,"1792":1}}],["constant",{"0":{"953":1},"2":{"214":1,"720":2,"872":1,"928":1,"929":1,"947":1,"957":1,"961":1,"968":1,"971":1,"1037":1,"1099":1,"1255":1,"1415":1,"1560":1,"1574":1,"1792":1,"2182":1,"2502":1,"2655":1,"2656":2}}],["constantly",{"2":{"106":1}}],["considered",{"2":{"2540":1}}],["considers",{"2":{"1823":1}}],["consideriation",{"2":{"919":1}}],["considerable",{"2":{"849":1,"1403":1}}],["consideration",{"2":{"307":1,"1792":4,"1942":1,"2177":1,"2634":2,"2635":2}}],["considerations",{"0":{"88":1,"1124":1,"1204":1,"1242":1,"1717":1},"1":{"1125":1,"1126":1,"1243":1,"1244":1,"1245":1},"2":{"1169":1,"1974":1,"2607":1}}],["consider",{"2":{"747":1,"845":1,"918":1,"982":1,"1035":1,"1181":1,"1204":1,"1205":1,"1206":2,"1448":1,"1792":1,"2088":1,"2588":1,"2697":1}}],["consistent",{"0":{"2271":1},"2":{"388":1,"863":2,"974":1,"995":1,"1190":1,"1193":4,"1407":1,"1410":1,"1567":1,"2271":1,"2314":1,"2332":1,"2493":1,"2520":1,"2638":1}}],["consistently",{"2":{"74":1,"974":1,"1923":1,"2222":1,"2271":1,"2508":1,"2513":1}}],["consistency",{"2":{"375":1,"844":1,"863":2,"864":1,"865":1,"871":2,"1193":2,"2267":1,"2279":1,"2440":1,"2486":1,"2558":1,"2581":1}}],["continuously",{"2":{"1080":1}}],["continue",{"0":{"1271":1},"2":{"851":1,"1150":1,"1386":1,"1520":1,"1792":3,"2007":1,"2297":1,"2330":1,"2380":1,"2384":1,"2389":1,"2435":1,"2444":1,"2513":1,"2615":1,"2840":1}}],["continues",{"2":{"301":2,"706":1,"973":1,"994":1,"1254":1,"1280":1,"1609":1,"2193":1,"2328":1,"2375":1,"2414":1,"2423":1,"2437":1,"2581":1,"2615":1,"2659":1,"2841":1}}],["contiguous",{"2":{"845":1}}],["contact",{"2":{"333":5}}],["containerport",{"2":{"1773":1}}],["container",{"2":{"1076":1,"1108":1,"1343":2,"1438":1,"1654":1,"1715":1,"1762":1,"1768":1,"1774":1,"1792":2,"2450":1,"2452":1,"2550":1,"2634":3,"2717":2,"2791":1,"2875":2}}],["containers",{"2":{"994":1,"1075":2,"1119":1,"1255":1,"1773":1,"2804":1}}],["contained",{"2":{"832":2,"1086":1,"1343":1,"2608":1,"2709":1,"2711":1,"2792":1,"2881":1}}],["contain",{"2":{"334":1,"395":1,"458":1,"528":1,"979":1,"980":1,"986":1,"988":1,"990":1,"1232":1,"1233":1,"1238":1,"1733":1,"1792":6,"1974":1,"2052":1,"2187":1,"2264":1,"2445":1,"2589":1,"2603":2,"2607":1,"2635":1}}],["containskey",{"2":{"2614":1}}],["contains",{"0":{"2495":1},"2":{"309":1,"314":1,"319":1,"388":1,"706":1,"762":1,"772":1,"782":1,"784":1,"786":1,"903":2,"924":1,"932":1,"1105":1,"1236":1,"1429":1,"1630":1,"1792":8,"1847":1,"1974":1,"2181":1,"2187":1,"2194":1,"2223":1,"2256":3,"2264":1,"2442":1,"2495":1,"2537":1,"2607":1,"2611":1,"2721":1,"2760":1,"2822":1,"2869":1}}],["containing",{"2":{"245":1,"880":1,"1052":1,"1235":1,"1394":1,"1406":1,"1472":1,"1489":1,"1651":1,"1655":1,"1661":1,"1792":4,"2004":1,"2197":1,"2397":1,"2586":1,"2611":2,"2772":1,"2774":1,"2841":1}}],["contends",{"2":{"2869":1}}],["contention",{"2":{"1155":1,"1324":1,"1325":1}}],["contentsecuritypolicy",{"2":{"1792":1,"2015":1,"2016":1,"2020":1,"2028":1,"2029":1,"2632":1}}],["contents",{"2":{"1054":1,"1202":1,"1366":4}}],["contenttype",{"2":{"748":1,"762":2,"763":1,"772":2,"773":2,"883":1,"887":1,"893":2,"894":1,"903":2,"1357":2,"1359":1,"1360":1,"1364":1,"1366":3,"1410":1,"1792":1,"2093":1,"2109":1,"2537":1}}],["content",{"0":{"539":1,"2017":1,"2020":1,"2037":1,"2038":1,"2495":1},"1":{"2038":1,"2039":1,"2040":1,"2041":1},"2":{"21":2,"73":2,"128":2,"188":1,"207":1,"208":1,"209":1,"210":3,"214":1,"227":1,"308":1,"386":4,"387":1,"392":2,"396":1,"415":1,"428":1,"439":1,"447":3,"489":1,"490":1,"492":2,"493":4,"494":2,"496":1,"521":1,"539":3,"540":1,"543":2,"544":3,"545":1,"546":2,"551":3,"554":1,"586":1,"587":1,"616":2,"691":2,"695":1,"700":2,"705":1,"747":1,"782":4,"784":5,"786":2,"823":1,"826":1,"829":1,"880":1,"915":1,"977":2,"980":3,"982":2,"985":4,"990":5,"992":1,"995":6,"996":5,"1030":1,"1031":3,"1078":1,"1079":1,"1100":2,"1105":6,"1107":1,"1189":4,"1340":1,"1341":3,"1355":2,"1357":1,"1362":3,"1363":1,"1366":2,"1373":4,"1384":1,"1386":3,"1408":4,"1410":2,"1412":2,"1416":2,"1432":1,"1452":1,"1456":1,"1471":1,"1493":1,"1567":1,"1643":1,"1646":1,"1655":1,"1674":1,"1721":1,"1722":3,"1725":1,"1730":1,"1732":3,"1736":1,"1743":1,"1754":1,"1792":25,"1824":2,"1855":2,"1916":3,"1917":2,"1918":3,"1921":1,"1922":3,"1924":1,"1931":2,"1940":1,"1943":1,"2016":2,"2017":1,"2020":2,"2021":1,"2032":1,"2034":1,"2038":3,"2040":2,"2041":1,"2042":1,"2075":2,"2093":1,"2109":2,"2110":1,"2125":1,"2126":2,"2127":2,"2193":1,"2202":1,"2207":2,"2223":1,"2224":1,"2247":1,"2255":4,"2264":6,"2296":1,"2303":1,"2337":1,"2338":1,"2339":1,"2360":1,"2381":1,"2474":1,"2476":1,"2477":1,"2481":1,"2493":1,"2495":2,"2502":1,"2506":1,"2512":1,"2529":2,"2530":4,"2531":1,"2537":1,"2549":7,"2566":1,"2580":1,"2589":1,"2596":2,"2626":1,"2627":1,"2632":8,"2726":2,"2763":2,"2769":2,"2810":2,"2814":3,"2823":1,"2824":1,"2853":1,"2859":1,"2865":2,"2866":2,"2868":3,"2869":1,"2873":1}}],["contemplated",{"2":{"1403":1}}],["contexts",{"2":{"2190":1,"2794":1}}],["contextkeyclaimsmapping",{"0":{"1476":1},"2":{"306":2,"453":1,"737":1,"1058":1,"1062":1,"1469":1,"1475":1,"1483":1,"1539":1,"1540":1,"1541":1,"1542":1,"1548":1,"1792":2,"1926":1,"2184":2,"2549":1,"2812":1}}],["context",{"0":{"236":1,"453":1,"502":1,"728":1,"732":1,"738":1,"739":1,"752":1,"766":1,"800":1,"803":1,"1058":1,"1348":1,"1475":1,"1540":2,"1541":1,"1542":1,"1926":1,"2184":1,"2481":1,"2572":1},"1":{"729":1,"730":1,"731":1,"732":1,"733":1,"734":1,"735":1,"736":1,"737":1,"738":1,"739":1,"740":1,"741":1,"742":1,"1476":1,"1541":2,"1542":2,"1543":2},"2":{"223":1,"236":2,"306":5,"315":1,"316":2,"317":1,"453":4,"456":2,"499":2,"502":2,"504":1,"638":1,"641":1,"645":1,"728":1,"729":2,"730":1,"732":1,"733":5,"734":1,"735":1,"736":4,"737":3,"738":1,"739":1,"740":1,"741":2,"742":1,"752":3,"766":2,"800":1,"803":1,"805":2,"841":1,"863":1,"871":1,"907":1,"1037":1,"1038":1,"1039":1,"1058":1,"1064":1,"1070":1,"1098":2,"1102":2,"1105":1,"1169":1,"1170":1,"1181":1,"1192":1,"1193":1,"1214":2,"1215":5,"1216":2,"1220":1,"1221":1,"1222":1,"1232":2,"1233":1,"1236":2,"1237":1,"1238":1,"1239":3,"1240":1,"1314":1,"1348":2,"1366":2,"1475":8,"1483":1,"1484":1,"1485":1,"1506":1,"1507":1,"1528":1,"1538":1,"1540":8,"1542":1,"1548":1,"1549":2,"1551":2,"1788":1,"1789":1,"1792":32,"1802":2,"1806":1,"1813":2,"1823":1,"1848":2,"1849":3,"1850":2,"1882":1,"1886":1,"1887":1,"1888":1,"1926":5,"1928":2,"1932":2,"2089":1,"2124":2,"2166":1,"2170":1,"2184":4,"2185":2,"2188":1,"2189":2,"2258":1,"2336":1,"2383":2,"2421":1,"2423":2,"2479":1,"2481":1,"2527":1,"2549":8,"2572":2,"2591":1,"2614":1,"2734":1,"2803":1,"2806":1,"2812":1,"2817":1,"2828":1,"2831":1,"2847":1}}],["contribute",{"2":{"2531":1,"2776":1}}],["controllable",{"2":{"2425":1}}],["controlling",{"2":{"1637":1}}],["controller",{"2":{"869":1,"871":2,"873":2,"876":1,"1006":2,"1008":1,"1027":1,"1037":1,"1181":1,"1382":1,"1405":6,"1406":1}}],["controllers",{"2":{"867":1,"869":1,"873":1,"1026":1,"1401":1,"1409":1,"1435":1}}],["controlled",{"2":{"64":1,"334":1,"837":1,"843":3,"877":1,"1086":1,"1113":1,"1127":1,"1160":1,"1185":1,"1253":1,"1405":1,"1743":1,"2040":1,"2072":1,"2389":1,"2544":1,"2650":1,"2659":1,"2688":1}}],["controls",{"2":{"33":1,"39":1,"87":1,"197":1,"300":1,"301":1,"320":2,"324":1,"335":1,"337":1,"458":1,"957":1,"1037":1,"1108":1,"1185":1,"1199":1,"1228":1,"1229":1,"1230":1,"1311":1,"1358":1,"1513":1,"1603":1,"1617":1,"1645":1,"1792":8,"1878":1,"1879":1,"1880":1,"2004":1,"2005":1,"2007":1,"2010":1,"2011":1,"2016":4,"2018":1,"2019":1,"2021":1,"2023":1,"2209":1,"2323":1,"2354":1,"2406":1,"2430":1,"2481":2,"2535":1,"2628":2,"2632":9,"2705":1,"2802":1}}],["control",{"0":{"542":1,"622":1,"1048":1,"1057":1,"1203":1,"1244":1,"1249":1,"1411":1,"2654":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":2,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1412":1,"1413":1,"1414":1,"1415":1,"2655":1,"2656":1},"2":{"33":1,"41":1,"77":1,"142":1,"160":1,"161":1,"163":1,"167":1,"226":1,"260":1,"297":2,"300":1,"307":1,"308":1,"385":1,"472":1,"497":1,"515":1,"540":1,"542":1,"543":1,"544":1,"545":3,"546":1,"549":1,"617":1,"624":1,"625":1,"636":1,"673":1,"681":1,"718":1,"726":1,"756":1,"780":1,"792":1,"876":1,"907":1,"911":1,"933":1,"963":2,"994":1,"1037":1,"1048":2,"1064":2,"1088":1,"1094":1,"1096":1,"1098":2,"1107":1,"1108":1,"1137":1,"1138":5,"1139":2,"1140":1,"1143":1,"1174":1,"1176":1,"1184":1,"1203":1,"1211":1,"1244":1,"1247":1,"1304":1,"1323":1,"1327":1,"1354":1,"1362":2,"1363":1,"1385":1,"1404":1,"1406":1,"1583":1,"1585":1,"1630":1,"1649":1,"1688":1,"1792":3,"1840":1,"1860":1,"1864":1,"1868":2,"1908":1,"1947":1,"1973":1,"2033":1,"2037":1,"2041":1,"2164":1,"2177":1,"2191":1,"2198":1,"2202":1,"2203":1,"2207":2,"2225":1,"2233":1,"2322":1,"2342":1,"2359":1,"2490":1,"2505":1,"2527":1,"2551":1,"2628":1,"2629":1,"2654":1,"2726":1,"2775":2,"2841":1,"2851":1,"2862":1,"2867":1}}],["contracts",{"2":{"1005":1,"1193":1,"1382":1,"1405":2,"1435":1,"1443":1}}],["contract",{"0":{"978":1,"983":1},"1":{"979":1,"980":1},"2":{"1":1,"304":1,"835":2,"836":1,"854":1,"873":1,"974":1,"975":1,"978":1,"983":4,"1000":1,"1020":1,"1039":1,"1096":1,"1382":1,"1405":1,"1435":3,"1437":1,"2498":1,"2504":1,"2533":1}}],["p99",{"2":{"2398":2}}],["p2`",{"2":{"1792":1}}],["p2",{"2":{"1521":1,"2380":2,"2494":1}}],["p1",{"2":{"1521":1,"1792":1,"2380":2,"2494":1}}],["pfx",{"0":{"1986":1},"2":{"1199":2,"1207":1,"1651":1,"1661":2,"1792":3,"1984":1,"1986":1,"1989":1,"1995":1,"2297":1,"2565":2}}],["pformat=pdf",{"2":{"423":2,"2304":2}}],["pt404",{"2":{"1111":1}}],["pt",{"2":{"1111":1,"1333":2}}],["p0004",{"2":{"1071":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":1,"2255":4,"2384":1,"2528":1,"2864":1}}],["p0001",{"2":{"1071":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":1,"2255":5,"2384":1}}],["p>your",{"2":{"2039":1}}],["p>welcome",{"2":{"2039":1}}],["p>`",{"2":{"996":2,"1409":1}}],["p>error",{"2":{"996":2,"1409":1}}],["p>",{"2":{"996":1,"2039":2}}],["p>$",{"2":{"996":1}}],["pw",{"2":{"949":2}}],["psql",{"2":{"907":1,"1080":1,"1407":1,"1442":1,"1792":1,"2156":1,"2528":1,"2531":4,"2542":1,"2545":1,"2546":1,"2742":1,"2863":1,"2869":1,"2874":2,"2878":1}}],["pytest",{"2":{"876":1,"1076":1}}],["pythonpython",{"2":{"1366":1}}],["python",{"0":{"909":1,"1271":1},"2":{"866":1,"867":1,"869":3,"873":1,"876":1,"1037":1,"1106":1,"1255":2,"1335":1,"1366":1,"1423":1,"1972":1,"2874":1}}],["py",{"2":{"869":1,"2874":1}}],["pydantic",{"2":{"869":1}}],["pp",{"2":{"852":1}}],["pem",{"0":{"1987":1},"2":{"1792":1,"1987":1}}],["peak",{"2":{"969":1,"1277":1,"1278":2,"2466":1}}],["penalty",{"2":{"1166":1}}],["pentaho",{"0":{"908":1}}],["pending",{"2":{"844":1,"849":4,"860":1,"2168":1}}],["peek",{"2":{"844":1}}],["people",{"2":{"843":1,"844":1,"849":2,"860":1,"861":1,"877":1,"1075":1,"1076":1,"1393":1,"1401":1,"1402":2,"1403":2,"1404":1}}],["perl",{"2":{"1972":1}}],["pertesttimeout",{"0":{"2101":1},"2":{"1792":1,"2093":1,"2094":1,"2537":1}}],["perceived",{"2":{"1704":1}}],["percentage",{"2":{"2094":1}}],["percent",{"2":{"894":2,"1401":2,"1410":2,"1925":2,"2397":1,"2517":2}}],["permanent",{"2":{"1792":1,"2109":1,"2110":1,"2530":1,"2537":1,"2866":1}}],["permanently",{"2":{"1664":1,"2297":1}}],["permission",{"0":{"2755":1},"2":{"1792":1,"2016":1,"2024":1,"2608":2,"2632":1}}],["permissionspolicy",{"2":{"1792":1,"2015":1,"2016":1,"2021":2,"2029":1,"2632":1}}],["permissions",{"0":{"2021":1},"2":{"868":1,"926":1,"941":1,"943":1,"945":1,"1100":1,"1674":1,"1771":1,"1792":2,"2021":1,"2258":1,"2632":3,"2876":1}}],["permissive",{"2":{"1428":1,"1824":1}}],["permitlimt",{"2":{"2444":1}}],["permitlimit",{"2":{"476":1,"478":1,"479":1,"1069":2,"1158":1,"1159":1,"1161":1,"1162":2,"1177":1,"1245":1,"1792":5,"1893":1,"1951":2,"1952":2,"1954":2,"1955":2,"1958":1,"1959":1,"1960":4,"2061":1,"2257":3,"2378":2,"2379":2,"2441":1,"2443":3,"2470":1,"2471":1,"2551":1}}],["permits",{"2":{"1951":1,"1952":1}}],["permitted",{"2":{"1225":1,"1464":1,"1642":1,"1643":1,"1792":2,"1875":1,"2376":1}}],["peripherals",{"2":{"1044":4}}],["periodic",{"2":{"2532":1}}],["periodically",{"2":{"531":1,"2438":1}}],["periodseconds",{"2":{"1773":2}}],["periods",{"2":{"845":1,"863":1,"864":4,"1171":1}}],["period",{"2":{"843":1,"851":1,"864":1,"926":1,"1179":1,"1259":1,"1403":1,"1775":1,"1953":1,"1983":1,"2385":1}}],["perhaps",{"2":{"847":1,"851":1,"974":1,"1140":1}}],["person",{"2":{"1401":1,"1792":1}}],["personal",{"2":{"1210":2,"1251":2,"1384":1,"2052":1,"2798":1}}],["personally",{"2":{"845":1,"859":1,"912":1,"1073":1,"1384":1}}],["personalized",{"2":{"732":1,"1094":1}}],["persists",{"2":{"989":1}}],["persistent",{"2":{"848":1,"1303":1,"1458":1,"2296":1,"2375":1,"2757":1}}],["persistence",{"2":{"840":1,"845":2,"849":4,"851":1,"1146":1,"1303":1,"1325":1,"1664":1}}],["persisted",{"2":{"844":1,"851":1,"1792":1,"2297":2}}],["persist",{"2":{"841":1,"849":1,"921":1,"985":1,"1054":1,"1653":1,"1654":1,"1792":1}}],["perf",{"2":{"1255":12,"1284":1,"1285":5,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"2398":1}}],["perfectly",{"2":{"843":1,"860":1,"1385":1,"1394":1,"1400":1,"1402":1}}],["perfect",{"2":{"694":1,"913":1,"959":1,"1079":1,"1082":1,"1312":1,"1399":1,"2156":1,"2167":1,"2542":1,"2740":1}}],["performer",{"2":{"1266":1}}],["performers",{"0":{"1264":1}}],["performed",{"2":{"807":1,"1423":1,"1609":1,"2137":1,"2575":1}}],["performant",{"2":{"1065":1}}],["performance",{"0":{"88":1,"231":1,"874":1,"1007":1,"1014":1,"1089":1,"1101":1,"1135":1,"1265":1,"1270":1,"1363":1,"1439":1,"1790":1,"1797":1,"2270":1,"2309":1,"2396":1,"2559":1,"2604":1,"2614":1,"2743":1},"1":{"1090":1,"1091":1,"1092":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"2397":1,"2398":1,"2399":1,"2400":1,"2744":1,"2745":1,"2746":1,"2747":1},"2":{"87":1,"88":2,"258":1,"803":1,"856":1,"859":2,"868":1,"872":2,"874":2,"919":1,"920":1,"946":1,"947":1,"966":1,"974":1,"1011":1,"1037":4,"1083":1,"1084":2,"1091":1,"1096":1,"1100":1,"1101":1,"1121":1,"1127":1,"1130":1,"1132":1,"1135":1,"1164":1,"1170":1,"1182":1,"1254":4,"1255":1,"1258":1,"1264":1,"1265":1,"1266":2,"1268":1,"1272":1,"1275":1,"1278":1,"1280":3,"1281":2,"1283":1,"1382":2,"1398":1,"1399":2,"1439":1,"1440":1,"1441":1,"1442":1,"1511":1,"1516":1,"1746":1,"1792":17,"1974":1,"2084":1,"2236":1,"2257":4,"2265":2,"2270":1,"2277":1,"2347":1,"2576":1,"2607":1,"2635":3,"2701":1,"2776":2,"2789":1,"2790":1}}],["performs",{"2":{"414":1,"851":1,"981":1,"1243":1,"1266":1,"1271":1,"1792":1,"2300":1,"2759":1,"2760":1,"2807":1}}],["perform",{"2":{"288":1,"387":1,"621":1,"664":1,"851":1,"922":1,"994":1,"1106":1,"1130":1,"1272":1,"2343":1,"2834":3}}],["per",{"0":{"479":1,"1066":1,"1069":1,"1090":1,"1162":1,"1411":1,"1415":1,"1459":1,"1577":1,"1955":1,"1958":1,"2056":1,"2079":1,"2379":1,"2427":1,"2432":1,"2462":1,"2470":1,"2504":1,"2533":1,"2653":1,"2654":1,"2870":1,"2872":1,"2873":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1412":1,"1413":1,"1414":1,"1415":1,"1956":1,"1957":1,"1959":1,"2655":1,"2656":1},"2":{"88":1,"101":2,"106":1,"110":1,"160":1,"161":1,"163":1,"175":1,"214":5,"215":1,"347":1,"388":1,"390":2,"436":1,"453":1,"470":1,"479":3,"480":4,"531":1,"534":2,"556":1,"614":1,"616":1,"636":1,"650":1,"663":1,"666":1,"669":1,"694":1,"696":1,"715":1,"716":2,"763":1,"773":1,"778":1,"780":1,"819":1,"835":6,"837":1,"857":2,"860":3,"861":3,"863":1,"865":1,"866":1,"868":4,"869":5,"871":1,"872":2,"873":3,"874":2,"887":2,"903":1,"905":1,"907":1,"911":1,"916":1,"917":2,"919":4,"947":1,"951":1,"964":1,"969":1,"1007":1,"1037":4,"1043":1,"1066":1,"1068":2,"1069":7,"1070":1,"1075":1,"1079":1,"1082":2,"1098":1,"1101":9,"1102":3,"1104":1,"1105":3,"1107":1,"1109":6,"1111":2,"1113":1,"1121":1,"1127":4,"1129":1,"1150":2,"1154":1,"1158":1,"1160":2,"1162":6,"1169":1,"1177":1,"1181":2,"1182":4,"1255":1,"1259":1,"1277":1,"1283":1,"1285":1,"1305":2,"1309":1,"1316":1,"1326":2,"1340":1,"1349":1,"1359":1,"1385":1,"1394":1,"1406":3,"1407":2,"1414":3,"1416":1,"1429":1,"1430":1,"1458":2,"1459":1,"1460":2,"1486":1,"1506":3,"1508":3,"1519":2,"1521":1,"1529":1,"1535":1,"1540":1,"1544":1,"1549":2,"1554":1,"1579":1,"1581":1,"1583":1,"1585":1,"1602":1,"1608":1,"1618":1,"1620":1,"1634":1,"1636":1,"1672":1,"1679":1,"1681":1,"1722":1,"1738":2,"1743":2,"1746":1,"1756":1,"1758":1,"1788":1,"1792":27,"1795":1,"1802":1,"1822":1,"1824":2,"1825":1,"1827":1,"1837":1,"1843":1,"1855":1,"1858":1,"1860":2,"1861":1,"1908":1,"1910":1,"1911":2,"1913":1,"1928":1,"1951":5,"1952":5,"1953":5,"1954":4,"1955":2,"1959":2,"1960":1,"1973":1,"1974":2,"1977":1,"2000":1,"2008":1,"2009":1,"2010":1,"2040":3,"2047":1,"2056":1,"2079":1,"2081":1,"2094":3,"2101":1,"2109":1,"2151":1,"2166":1,"2167":4,"2192":1,"2221":3,"2222":2,"2223":2,"2224":2,"2225":2,"2233":1,"2252":1,"2255":1,"2266":2,"2270":1,"2272":1,"2306":1,"2320":1,"2330":2,"2339":2,"2347":1,"2357":1,"2359":1,"2364":2,"2366":5,"2372":4,"2375":5,"2379":2,"2380":1,"2389":2,"2392":1,"2393":1,"2397":4,"2410":1,"2413":2,"2417":1,"2419":1,"2421":2,"2422":1,"2426":1,"2430":1,"2433":1,"2434":1,"2435":3,"2438":6,"2442":1,"2443":1,"2463":2,"2465":2,"2466":3,"2468":1,"2471":2,"2472":1,"2474":2,"2476":1,"2477":1,"2481":3,"2482":1,"2483":1,"2490":3,"2496":1,"2498":2,"2502":5,"2504":6,"2512":1,"2522":1,"2526":1,"2528":1,"2529":1,"2532":1,"2533":2,"2534":2,"2535":3,"2536":2,"2537":3,"2540":1,"2545":4,"2546":1,"2549":1,"2587":1,"2596":1,"2604":1,"2607":2,"2622":3,"2653":1,"2654":1,"2674":1,"2728":1,"2729":1,"2731":1,"2740":1,"2744":1,"2745":2,"2747":1,"2756":1,"2762":1,"2765":2,"2767":1,"2768":1,"2804":1,"2808":1,"2812":1,"2814":1,"2828":3,"2833":1,"2835":1,"2844":1,"2850":1,"2858":1,"2864":1,"2865":1,"2866":1,"2867":1,"2869":1,"2873":1,"2880":2,"2881":1}}],["phantom",{"2":{"2391":1}}],["phase",{"2":{"1259":1,"1324":1,"1802":1,"2007":1,"2751":1,"2795":1,"2799":1}}],["phished",{"2":{"1867":1}}],["phishing",{"2":{"1792":1,"1866":1,"2625":1}}],["phish",{"2":{"1209":1}}],["philosophies",{"2":{"849":1,"1075":1}}],["philosophy",{"0":{"1403":1},"2":{"832":1,"838":1,"859":1,"1037":1,"1383":1,"1385":1,"1402":1,"1403":3,"2388":1}}],["php",{"0":{"1262":1,"1274":1},"2":{"1007":1,"1090":1,"1091":2,"1255":1,"1257":2,"1262":1,"1264":2,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1277":1,"1278":1,"1279":2,"1280":1,"1281":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1}}],["phrase",{"2":{"872":1}}],["physical",{"2":{"841":5,"845":1,"848":3,"1079":1,"1403":1,"2099":1,"2527":2,"2862":2}}],["physically",{"2":{"841":1,"848":1}}],["phone",{"2":{"333":2,"817":4,"888":3,"1193":1,"1232":1,"1792":3,"1882":1,"2144":2,"2146":2,"2148":2,"2446":1,"2575":2}}],["photographed",{"2":{"852":1}}],["photograph",{"2":{"852":1}}],["photo",{"2":{"157":2,"1359":2}}],["png",{"2":{"747":1,"1099":1,"1412":1,"1792":1,"2123":1,"2125":1}}],["pgdev",{"2":{"2667":1}}],["pgdatabase=example",{"2":{"1608":1,"2272":1}}],["pgdatabase",{"2":{"937":1,"1607":1,"1615":1,"1633":2,"1792":1}}],["pgtupletojsonobject",{"2":{"2397":2}}],["pgtap",{"2":{"860":1,"867":1,"1094":1,"2535":1}}],["pgcompositearraytojsonarray",{"2":{"2397":2,"2402":1,"2604":1}}],["pgconverters",{"2":{"2372":1,"2621":1}}],["pgcrypto",{"2":{"182":1,"308":2,"924":2,"1048":1,"1049":3,"1098":2,"1100":1,"2177":2,"2291":1}}],["pgunknowntojsonarray",{"2":{"2270":1,"2397":1}}],["pguser=postgres",{"2":{"1608":1,"2272":1}}],["pguser",{"2":{"1607":1,"1615":1,"1633":2,"1792":1}}],["pgarraytojsonarray",{"2":{"2270":1,"2397":3}}],["pgpassword=postgres",{"2":{"1608":1,"2272":1}}],["pgpassword",{"2":{"1607":1,"1615":1,"1633":2,"1792":1}}],["pgport=5432",{"2":{"1608":1,"2272":1}}],["pgport",{"2":{"937":1,"1607":1,"1615":1,"1633":2}}],["pgx",{"2":{"1255":1}}],["pgbouncer",{"0":{"1066":1,"1070":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1},"2":{"1037":2,"1066":1,"1070":1,"1101":1,"1102":2,"1121":1,"1528":1,"1792":1,"1851":1,"2382":1}}],["pghost=localhost",{"2":{"1608":1}}],["pghost",{"2":{"937":1,"1607":1,"1615":1,"1633":2,"1792":1,"2534":3}}],["pgmigrations",{"2":{"926":1}}],["pgsql",{"2":{"868":1,"874":1,"876":4,"1012":1,"1096":1,"1106":1,"1378":1,"1394":4,"1972":1,"2255":2,"2802":1}}],["pg",{"2":{"531":1,"584":1,"621":1,"874":1,"876":1,"907":1,"933":2,"934":2,"935":2,"936":2,"949":2,"966":4,"967":2,"1055":2,"1056":4,"1057":3,"1058":7,"1060":1,"1104":3,"1105":2,"1106":1,"1107":2,"1108":5,"1126":1,"1174":1,"1176":2,"1184":2,"1185":2,"1188":2,"1192":2,"1281":1,"1325":1,"1333":1,"1353":1,"1365":1,"1527":1,"1619":1,"1620":1,"1792":14,"1839":1,"1974":2,"2045":4,"2049":2,"2050":1,"2051":1,"2052":1,"2111":3,"2324":2,"2337":1,"2343":1,"2532":2,"2534":1,"2607":3,"2635":13,"2710":1,"2751":1,"2758":1,"2795":1,"2799":1,"2803":1,"2854":1,"2875":4}}],["pdfs",{"2":{"1104":1}}],["pdf",{"0":{"426":1},"2":{"392":1,"414":1,"415":1,"426":6,"1105":2,"1107":5,"1360":2,"1396":1,"2132":1,"2300":1,"2303":1}}],["pbkdf2",{"2":{"307":1,"309":1,"363":1,"366":1,"1049":3,"1098":2,"2177":1}}],["pi",{"2":{"2576":1,"2779":1,"2783":1,"2790":1}}],["pixelratio",{"2":{"1792":1}}],["pissed",{"2":{"1403":1}}],["pii",{"2":{"1100":1,"1664":1,"2291":1}}],["pinning",{"2":{"2437":1}}],["pinned",{"2":{"214":1,"946":1,"1743":1,"2502":1}}],["pings",{"2":{"1337":1}}],["ping",{"2":{"1320":1,"1399":1,"1569":2,"1824":1,"2481":1}}],["pin",{"2":{"1209":1,"1210":1,"1217":1,"1220":1,"1221":1,"1222":1,"1228":2,"1792":4,"1867":1,"1878":2,"2459":1}}],["pins",{"2":{"1098":1,"1792":1,"1866":1,"2625":1}}],["pile",{"2":{"849":1,"1324":1}}],["pitch",{"2":{"848":1,"876":1,"1037":1,"1381":1}}],["pitfall",{"2":{"650":1}}],["pieces",{"2":{"861":1,"1048":1,"2170":1,"2419":1,"2836":1,"2871":1}}],["piece",{"2":{"831":1,"852":2,"916":1,"1133":1}}],["piped",{"2":{"2535":1,"2667":1,"2673":1,"2678":1,"2679":1,"2694":1,"2785":1,"2880":1}}],["pipewriter",{"0":{"2400":1},"2":{"949":1,"2372":1}}],["pipe",{"0":{"491":1,"603":1},"2":{"603":1}}],["pipelines",{"2":{"1013":1,"1108":1,"1203":1,"1208":1}}],["pipeline",{"0":{"426":1,"1407":1},"2":{"696":1,"867":1,"868":2,"869":1,"872":1,"878":1,"1009":1,"1041":1,"1044":1,"1045":1,"1078":1,"1094":1,"1105":1,"1126":1,"1398":1,"1422":1,"1460":1,"1703":1,"1792":1,"1824":1,"2092":1,"2157":1,"2375":1,"2481":1,"2527":1,"2543":1,"2545":1,"2576":1,"2615":1,"2627":1,"2633":1,"2739":1,"2860":1}}],["picked",{"0":{"2799":1},"2":{"1401":1,"2476":1,"2529":1}}],["picker",{"2":{"1229":1,"1792":1,"1879":1}}],["picks",{"2":{"1183":1,"1352":1,"1418":1,"1792":1,"2178":1,"2385":1,"2421":1,"2443":1}}],["pick",{"2":{"307":1,"534":1,"833":1,"1135":1,"1385":1,"2533":1,"2537":1,"2833":1}}],["picture",{"0":{"2171":1},"2":{"296":1,"854":1,"863":1,"864":1,"865":1,"872":1,"2170":1,"2750":1}}],["pid=1",{"2":{"2555":1}}],["pid=123",{"2":{"423":2,"2304":2}}],["pid",{"2":{"258":1,"1575":2,"2277":2,"2543":1,"2555":1}}],["popups",{"2":{"1792":2,"2016":1,"2023":3,"2632":3}}],["popular",{"2":{"1147":1,"1682":1}}],["population",{"2":{"1399":1,"2435":1}}],["populating",{"2":{"992":1,"2868":1}}],["populate",{"2":{"851":1,"1733":1,"2264":1}}],["populated",{"2":{"210":1,"454":1,"1015":1,"1016":1,"1023":1,"1105":1,"1188":1,"1359":1,"1398":1,"1723":1,"1732":1,"1827":1,"2264":1,"2760":2}}],["populates",{"2":{"202":1,"452":1,"1016":1,"1023":1,"1355":1,"1723":1,"2264":1,"2463":1}}],["pong",{"2":{"1320":1,"1399":1}}],["poison",{"2":{"1067":1,"2402":1}}],["pointer",{"2":{"2399":1}}],["pointed",{"2":{"868":1,"1443":1}}],["point",{"0":{"2027":1},"2":{"650":1,"663":1,"669":1,"694":1,"836":1,"844":1,"847":1,"848":1,"849":1,"851":1,"856":1,"861":1,"867":1,"876":1,"956":1,"976":1,"1008":1,"1047":1,"1074":1,"1172":1,"1205":1,"1208":1,"1209":1,"1252":2,"1303":1,"1305":1,"1381":1,"1382":1,"1385":1,"1396":1,"1398":2,"1399":1,"1403":2,"1429":1,"1431":1,"1439":1,"1574":1,"1792":2,"1825":1,"1868":1,"2098":1,"2450":1,"2577":1,"2717":1,"2760":1,"2828":1,"2861":1}}],["points",{"0":{"2482":1},"2":{"280":1,"937":1,"996":1,"1055":1,"1169":1,"1231":1,"1654":1,"1670":1,"1792":1,"1881":1,"2223":1,"2255":1,"2479":1,"2481":1,"2533":1,"2534":1,"2740":1,"2828":1}}],["pointing",{"2":{"264":1,"1176":1,"1205":1,"1676":1,"2328":1}}],["potentially",{"2":{"1147":1,"2615":1}}],["potential",{"2":{"1008":1,"1281":1,"1401":1,"1825":1,"2265":1,"2576":1}}],["poolers",{"2":{"1102":1,"1792":1,"1851":1,"2382":1}}],["pooler",{"0":{"1070":1,"1102":1,"1528":1,"1850":1,"2382":1},"1":{"1851":1,"1852":1},"2":{"1066":1,"1070":4,"1101":1,"1102":3,"1121":2,"1398":1,"1528":2,"1792":2,"1850":1,"1851":2,"2382":2}}],["pooled",{"2":{"694":1,"716":1,"1079":1,"1792":4,"2099":1,"2156":1,"2221":1,"2380":1,"2527":1,"2542":1,"2862":1}}],["pooling=true",{"2":{"1177":2,"1633":2}}],["pooling",{"0":{"1522":1},"2":{"1007":1,"1014":1,"1067":1,"1101":1,"1102":1,"1150":1,"1276":2,"1616":2,"2236":1,"2614":1}}],["pool",{"0":{"1164":1,"1170":1,"1329":1,"2084":1},"1":{"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"2085":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1},"2":{"948":1,"1037":1,"1067":1,"1070":2,"1102":2,"1137":1,"1141":1,"1164":2,"1165":3,"1166":2,"1167":1,"1169":1,"1177":2,"1180":1,"1181":1,"1182":2,"1281":1,"1328":2,"1329":2,"1337":1,"1349":1,"1363":2,"1398":1,"1616":4,"1633":2,"1790":2,"1792":5,"1797":2,"1851":2,"2084":1,"2086":2,"2088":3,"2089":1,"2382":2,"2397":1,"2402":2,"2459":2,"2614":1}}],["poetic",{"2":{"913":1}}],["powershell",{"2":{"2781":1}}],["powershellpowershell",{"2":{"2781":1}}],["powers",{"2":{"1664":1}}],["powerquerypowerquerylet",{"2":{"1202":1}}],["powered",{"2":{"1099":1,"1100":1}}],["power",{"0":{"885":1,"1201":1,"1203":1,"1405":1},"1":{"1202":1,"1203":1,"1204":1,"1205":1},"2":{"871":1,"1037":2,"1183":1,"1184":2,"1202":1,"1207":1,"1247":1,"1385":1,"1405":2}}],["powerful",{"2":{"859":1,"913":1,"916":1,"1076":1,"1385":1}}],["poller",{"2":{"2546":1}}],["polled",{"2":{"1080":1}}],["polls",{"2":{"2156":1,"2542":1,"2878":1}}],["poll",{"2":{"1792":2,"2154":1,"2156":1,"2542":1}}],["polly",{"2":{"1181":1,"2289":1}}],["polling",{"0":{"2542":1},"2":{"868":1,"1035":1,"1036":1,"1082":1,"2106":1,"2154":1,"2155":1,"2156":2,"2157":4,"2541":1,"2542":1,"2543":3,"2546":1}}],["polp",{"0":{"922":1,"2876":1},"2":{"922":1,"926":1,"932":1,"941":1,"1185":2}}],["polish",{"2":{"2438":1}}],["political",{"2":{"913":1,"919":2}}],["policies",{"0":{"235":1,"1163":1,"1673":1,"2022":1,"2378":1,"2441":1},"1":{"1674":1,"2023":1,"2024":1,"2025":1,"2442":1,"2443":1,"2444":1},"2":{"198":1,"200":1,"476":1,"477":1,"478":1,"479":1,"480":2,"481":1,"483":1,"835":1,"1069":3,"1084":1,"1098":2,"1109":3,"1111":6,"1114":1,"1127":1,"1150":1,"1157":2,"1158":1,"1159":1,"1160":1,"1161":1,"1162":2,"1163":1,"1177":1,"1181":1,"1182":1,"1230":1,"1245":1,"1519":1,"1670":1,"1673":1,"1790":1,"1792":5,"1797":1,"1880":1,"1893":1,"1947":1,"1948":2,"1949":2,"1951":1,"1952":1,"1953":1,"1954":1,"1955":2,"1957":1,"1958":1,"1960":2,"1961":1,"1962":1,"2047":1,"2061":1,"2070":1,"2225":1,"2255":1,"2257":3,"2378":3,"2379":3,"2380":1,"2440":1,"2441":2,"2442":1,"2443":1,"2444":1,"2447":1,"2470":3,"2634":1,"2635":1,"2747":1}}],["policyscheme",{"2":{"2422":1}}],["policy",{"0":{"192":1,"195":1,"473":1,"476":1,"477":1,"1950":1,"1951":1,"1952":1,"1953":1,"1954":1,"1958":1,"1959":1,"2019":1,"2020":1,"2021":1,"2023":1,"2024":1,"2025":1,"2379":1,"2470":1,"2471":1},"1":{"193":1,"194":1,"195":1,"196":1,"197":1,"198":1,"199":1,"200":1,"474":1,"475":1,"476":1,"477":1,"478":1,"479":1,"480":1,"481":1,"482":1,"483":1,"1959":1},"2":{"192":2,"193":3,"195":1,"196":1,"197":1,"235":4,"473":4,"474":3,"476":1,"479":1,"480":6,"579":1,"835":3,"868":4,"869":1,"873":1,"1067":1,"1069":2,"1100":3,"1101":3,"1111":4,"1113":2,"1114":1,"1156":1,"1157":1,"1158":1,"1161":2,"1162":1,"1163":3,"1179":2,"1181":1,"1182":1,"1217":1,"1224":1,"1244":1,"1402":1,"1493":1,"1645":1,"1670":3,"1679":2,"1681":2,"1792":35,"1822":4,"1824":1,"1874":1,"1947":1,"1948":2,"1949":4,"1950":1,"1951":6,"1952":4,"1953":4,"1954":4,"1955":1,"1957":2,"1958":5,"1959":2,"1961":4,"1962":1,"1964":1,"2020":2,"2021":1,"2047":2,"2061":1,"2218":2,"2224":2,"2225":2,"2253":1,"2255":10,"2257":10,"2378":2,"2379":3,"2421":1,"2422":2,"2423":1,"2425":1,"2435":2,"2442":1,"2443":1,"2444":3,"2468":3,"2470":5,"2471":1,"2472":4,"2481":1,"2632":13,"2634":3,"2635":2,"2747":1}}],["portion",{"2":{"2453":1}}],["porting",{"2":{"852":1}}],["ports",{"2":{"1773":1}}],["portal",{"2":{"1694":1,"1792":1}}],["portability",{"2":{"860":1}}],["portable",{"2":{"859":2,"860":1,"876":1}}],["port=54329",{"2":{"2875":1}}],["port=5432",{"2":{"1613":1,"1792":1,"2718":1,"2823":2,"2824":3,"2825":2}}],["port=",{"2":{"937":1,"1607":1,"1615":1,"1633":2}}],["ported",{"2":{"852":1}}],["port",{"0":{"1167":1,"2087":1},"2":{"852":3,"1167":2,"1335":3,"1616":2,"1773":2,"1792":4,"2019":1,"2086":2,"2087":1,"2157":2,"2161":1,"2543":2,"2788":1}}],["poke",{"2":{"843":1}}],["pods",{"2":{"1774":1}}],["pod",{"2":{"390":2,"1427":1,"1767":1,"1768":1,"1792":2,"2040":2,"2224":1,"2450":1,"2474":1,"2476":1,"2483":1,"2634":2}}],["possession",{"2":{"1228":1,"1792":2,"1878":1}}],["possibly",{"2":{"1133":1}}],["possible",{"2":{"984":1,"1071":1,"1102":1,"1254":2,"1396":1,"1400":1,"1401":1,"1792":7,"2112":1,"2438":1,"2833":1}}],["position",{"0":{"1390":1},"2":{"310":1,"448":1,"772":1,"859":1,"1101":1,"1175":1,"1378":1,"1390":3,"1792":1,"1922":1,"2005":1,"2006":1,"2284":1,"2323":1,"2328":1,"2505":1,"2530":1,"2537":1}}],["positionally",{"2":{"2340":1,"2734":1}}],["positional",{"0":{"372":1,"614":1,"2337":1,"2340":1,"2731":1,"2846":1,"2852":1},"2":{"165":1,"310":1,"369":1,"372":1,"376":2,"377":1,"382":1,"384":1,"385":1,"529":1,"560":2,"581":1,"587":1,"614":1,"615":1,"619":2,"1142":1,"1378":1,"1379":1,"1386":2,"1392":1,"1396":1,"1792":5,"2284":1,"2320":1,"2321":1,"2323":3,"2332":1,"2333":1,"2334":1,"2337":1,"2339":1,"2340":1,"2343":1,"2540":5,"2731":1,"2733":1,"2803":1,"2844":1,"2845":1,"2846":2,"2848":1,"2852":1,"2854":1}}],["posting",{"2":{"1792":1}}],["posture",{"2":{"1382":1,"2427":1,"2438":1,"2486":1}}],["postmapping",{"2":{"1366":1}}],["postgraphile",{"2":{"2744":1}}],["postgrrest",{"2":{"1385":8}}],["postgresnoticelevels",{"2":{"2251":2}}],["postgresminimumlevel",{"2":{"1792":1,"1800":1,"1805":2,"1810":1,"2803":2,"2804":2}}],["postgrescommand",{"2":{"1792":1,"1800":1,"1805":2,"1806":1,"1810":1,"2803":1,"2804":1}}],["postgres",{"2":{"848":1,"851":1,"918":1,"1071":1,"1255":1,"1370":1,"1382":1,"1464":1,"1792":6,"2111":2,"2161":2,"2376":1,"2377":1,"2451":1,"2452":1,"2459":1,"2465":1,"2532":2,"2534":1,"2850":1,"2875":3}}],["postgrest",{"0":{"1083":1,"1087":1,"1106":1,"1114":1,"1118":1,"1122":1,"1125":1,"2713":1},"1":{"1084":1,"1085":1,"1086":1,"1087":1,"1088":1,"1089":1,"1090":1,"1091":1,"1092":1,"1093":1,"1094":1,"1095":1,"1096":1,"1097":1,"1098":1,"1099":1,"1100":1,"1101":1,"1102":1,"1103":1,"1104":1,"1105":1,"1106":1,"1107":1,"1108":1,"1109":1,"1110":1,"1111":1,"1112":1,"1113":1,"1114":1,"1115":1,"1116":1,"1117":1,"1118":1,"1119":1,"1120":1,"1121":1,"1122":1,"1123":1,"1124":1,"1125":1,"1126":1,"1127":1},"2":{"831":1,"1037":2,"1083":2,"1084":2,"1087":1,"1088":2,"1090":3,"1091":2,"1092":1,"1094":3,"1095":2,"1096":3,"1097":4,"1098":6,"1099":3,"1100":6,"1101":4,"1102":5,"1103":1,"1104":2,"1106":6,"1107":1,"1108":2,"1109":1,"1110":1,"1111":5,"1113":1,"1114":1,"1118":8,"1119":1,"1121":1,"1122":3,"1125":1,"1126":1,"1127":6,"1255":1,"1257":1,"1265":1,"1266":1,"1267":1,"1269":2,"1270":1,"1279":1,"1281":2,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1382":1,"1383":1,"1894":1,"2713":1,"2744":1}}],["postgresqlerrorcodetohttpstatuscodemapping",{"2":{"2255":2}}],["postgresql",{"0":{"611":1,"878":1,"912":1,"921":1,"947":1,"972":1,"974":1,"982":1,"987":1,"1010":1,"1012":1,"1038":1,"1092":1,"1128":1,"1155":1,"1183":1,"1254":1,"1302":1,"1304":1,"1324":1,"1328":1,"1336":1,"1352":1,"1423":1,"1435":1,"1540":1,"1543":1,"1805":1,"1806":1,"1972":1,"2045":1,"2184":1,"2635":1,"2710":1,"2775":1,"2802":1,"2803":1,"2822":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"985":1,"986":1,"987":1,"988":2,"989":2,"990":2,"991":2,"992":2,"993":2,"994":2,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"1011":1,"1012":1,"1013":2,"1014":2,"1015":2,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1129":1,"1130":1,"1131":1,"1132":1,"1133":1,"1134":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":2,"1326":1,"1327":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1541":1,"1542":1,"1543":1,"1806":1,"2046":1,"2047":1,"2048":1,"2049":1,"2050":1,"2051":1,"2052":1,"2053":1,"2054":1,"2055":1,"2056":1,"2057":1,"2058":1,"2059":1,"2060":1,"2061":1,"2062":1,"2063":1,"2064":1,"2065":1,"2066":1,"2067":1,"2068":1,"2069":1,"2070":1,"2071":1},"2":{"1":1,"29":1,"108":1,"165":1,"166":1,"175":2,"182":1,"184":1,"187":1,"188":1,"197":1,"201":1,"202":1,"216":1,"218":1,"220":1,"237":1,"241":1,"258":1,"299":1,"307":1,"308":1,"316":1,"327":1,"370":1,"372":1,"377":1,"382":1,"383":2,"384":1,"388":2,"407":1,"408":3,"409":1,"412":1,"414":3,"415":1,"426":1,"428":1,"429":1,"435":1,"439":2,"447":1,"453":1,"458":1,"497":1,"499":1,"528":1,"575":2,"576":1,"581":1,"582":3,"585":1,"587":2,"627":1,"629":1,"646":1,"668":1,"684":2,"728":1,"737":2,"746":3,"748":1,"749":1,"752":1,"759":1,"768":1,"769":1,"776":1,"782":1,"801":1,"803":1,"805":1,"809":1,"818":1,"831":1,"834":1,"836":2,"837":2,"838":1,"839":1,"845":1,"848":6,"866":1,"868":1,"869":1,"871":1,"874":2,"875":2,"876":3,"878":1,"902":1,"903":2,"907":1,"909":1,"912":2,"916":1,"918":3,"919":6,"920":2,"921":2,"933":1,"942":1,"945":1,"947":2,"949":2,"952":2,"956":2,"966":1,"967":1,"971":1,"972":1,"973":1,"974":2,"975":4,"977":1,"980":1,"981":1,"982":2,"983":1,"984":1,"990":1,"993":1,"994":2,"995":3,"996":1,"997":1,"1000":1,"1002":1,"1005":5,"1006":1,"1007":2,"1009":1,"1010":2,"1012":1,"1013":2,"1014":1,"1015":4,"1016":2,"1023":1,"1036":1,"1037":22,"1038":3,"1044":1,"1048":1,"1058":1,"1067":1,"1072":1,"1074":1,"1075":3,"1078":1,"1079":3,"1080":1,"1082":1,"1083":1,"1084":1,"1086":3,"1087":1,"1088":1,"1089":1,"1092":1,"1096":2,"1097":1,"1098":7,"1099":3,"1100":6,"1101":1,"1103":3,"1104":2,"1105":7,"1106":4,"1107":3,"1108":1,"1110":2,"1111":1,"1114":1,"1119":1,"1121":1,"1123":1,"1125":2,"1126":2,"1127":2,"1128":2,"1129":2,"1132":1,"1134":1,"1135":1,"1150":1,"1155":1,"1169":1,"1172":1,"1183":2,"1184":2,"1185":1,"1197":1,"1203":1,"1205":1,"1206":3,"1208":2,"1209":1,"1211":2,"1219":1,"1247":1,"1255":2,"1274":1,"1276":2,"1279":2,"1280":1,"1302":2,"1304":2,"1305":2,"1322":1,"1324":2,"1327":1,"1328":3,"1331":1,"1332":1,"1333":1,"1334":1,"1337":1,"1342":1,"1346":1,"1352":2,"1353":1,"1368":2,"1378":1,"1381":1,"1382":1,"1383":1,"1384":1,"1385":3,"1386":5,"1390":1,"1391":1,"1394":6,"1401":1,"1403":2,"1404":1,"1405":3,"1406":1,"1407":1,"1408":1,"1410":1,"1412":1,"1414":3,"1416":1,"1419":1,"1422":1,"1423":2,"1428":1,"1431":1,"1432":1,"1434":1,"1435":2,"1447":1,"1451":1,"1454":3,"1475":1,"1477":1,"1499":1,"1503":1,"1509":1,"1511":2,"1521":1,"1523":1,"1538":1,"1540":2,"1554":1,"1567":1,"1576":1,"1581":1,"1589":1,"1591":1,"1596":1,"1616":1,"1619":1,"1620":1,"1623":1,"1626":1,"1632":2,"1655":1,"1664":1,"1668":1,"1670":1,"1671":2,"1673":1,"1674":2,"1684":1,"1686":1,"1720":1,"1723":2,"1733":1,"1741":1,"1764":2,"1769":1,"1770":1,"1784":1,"1787":1,"1789":1,"1791":1,"1792":54,"1794":1,"1796":1,"1799":2,"1805":3,"1810":1,"1811":1,"1813":1,"1834":1,"1838":1,"1841":2,"1844":1,"1848":2,"1851":1,"1857":2,"1858":1,"1868":3,"1869":1,"1898":2,"1907":1,"1909":1,"1915":1,"1920":1,"1921":1,"1922":1,"1928":1,"1933":1,"1965":1,"1967":2,"1972":1,"1974":2,"2007":2,"2009":1,"2045":2,"2047":3,"2049":3,"2060":1,"2062":1,"2072":1,"2075":1,"2077":1,"2111":1,"2122":1,"2126":1,"2128":2,"2130":2,"2147":1,"2160":1,"2161":1,"2164":8,"2165":2,"2166":1,"2177":2,"2183":1,"2184":1,"2190":1,"2205":1,"2223":1,"2234":1,"2239":1,"2242":1,"2251":1,"2253":2,"2255":11,"2256":1,"2261":1,"2264":3,"2266":1,"2267":2,"2270":2,"2277":1,"2282":1,"2287":1,"2289":2,"2291":1,"2292":1,"2294":1,"2296":1,"2297":1,"2300":3,"2302":1,"2303":1,"2310":1,"2317":2,"2318":1,"2319":1,"2321":1,"2322":1,"2323":1,"2324":1,"2328":2,"2333":1,"2334":1,"2336":2,"2337":3,"2339":2,"2348":1,"2350":2,"2351":1,"2367":1,"2380":2,"2382":1,"2388":1,"2389":2,"2397":1,"2398":2,"2402":1,"2431":1,"2450":1,"2479":2,"2481":2,"2493":1,"2496":2,"2498":1,"2531":2,"2532":1,"2540":3,"2546":1,"2549":4,"2575":1,"2585":1,"2586":3,"2588":3,"2589":1,"2590":2,"2597":1,"2603":2,"2607":5,"2611":1,"2634":5,"2635":8,"2650":1,"2665":3,"2709":1,"2710":2,"2712":1,"2724":1,"2734":1,"2741":1,"2759":1,"2762":1,"2770":2,"2772":3,"2774":2,"2775":1,"2792":1,"2794":1,"2795":2,"2802":1,"2803":1,"2804":1,"2806":1,"2816":1,"2818":1,"2819":1,"2820":1,"2822":1,"2824":1,"2825":2,"2827":2,"2832":1,"2835":1,"2840":1,"2844":1,"2845":2,"2847":2,"2854":1,"2855":3,"2868":1,"2869":1,"2871":1,"2874":1,"2875":1}}],["postgis",{"2":{"834":1}}],["post",{"0":{"208":1,"523":1,"1030":1,"1065":1,"1270":1,"1294":1,"1379":1,"2300":1},"1":{"1295":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1},"2":{"9":1,"21":1,"71":2,"72":1,"75":1,"138":1,"157":1,"184":4,"187":1,"195":1,"204":1,"208":1,"209":1,"243":1,"245":1,"248":2,"252":1,"257":2,"258":2,"288":2,"289":1,"290":1,"291":3,"292":1,"297":1,"298":2,"309":1,"312":2,"313":1,"324":1,"338":1,"351":3,"352":1,"360":1,"361":1,"365":1,"366":1,"414":1,"415":2,"418":2,"420":2,"421":2,"423":3,"426":1,"427":1,"428":2,"443":2,"444":2,"445":2,"446":3,"456":1,"477":1,"503":2,"510":2,"511":1,"523":3,"545":1,"565":1,"572":1,"574":1,"577":1,"586":1,"592":2,"593":1,"594":1,"614":1,"621":1,"622":1,"623":1,"631":1,"632":1,"633":1,"641":2,"642":1,"643":1,"644":1,"645":1,"646":1,"658":1,"659":1,"660":1,"661":1,"662":2,"664":1,"665":1,"691":2,"695":1,"700":1,"705":1,"750":1,"764":1,"774":1,"783":1,"785":1,"787":1,"789":1,"813":1,"814":1,"817":1,"826":1,"827":1,"828":1,"835":1,"847":1,"866":1,"868":1,"871":1,"874":1,"881":1,"886":2,"894":1,"899":1,"900":1,"902":1,"904":1,"910":1,"911":1,"912":1,"915":3,"920":1,"921":1,"934":1,"935":1,"938":1,"946":1,"971":1,"972":1,"975":1,"977":2,"980":1,"990":5,"992":2,"996":12,"1030":2,"1036":1,"1038":1,"1045":2,"1048":2,"1049":1,"1055":1,"1065":1,"1078":1,"1079":3,"1081":1,"1104":2,"1105":5,"1107":3,"1111":1,"1135":1,"1154":2,"1157":1,"1161":2,"1163":1,"1179":1,"1182":1,"1183":1,"1208":1,"1220":4,"1221":4,"1222":4,"1226":1,"1232":1,"1233":1,"1234":1,"1236":1,"1237":1,"1238":1,"1239":1,"1253":1,"1254":1,"1255":4,"1258":1,"1264":1,"1285":4,"1295":1,"1308":1,"1309":1,"1321":1,"1326":1,"1327":1,"1332":2,"1335":3,"1338":4,"1339":2,"1342":1,"1351":1,"1358":3,"1366":2,"1367":1,"1368":2,"1371":2,"1372":1,"1384":3,"1394":1,"1399":1,"1400":1,"1401":1,"1404":3,"1406":1,"1409":1,"1410":2,"1423":1,"1431":5,"1434":1,"1452":1,"1456":1,"1458":1,"1487":1,"1491":1,"1492":1,"1504":1,"1599":2,"1642":1,"1646":1,"1664":2,"1729":1,"1730":1,"1792":20,"1817":1,"1824":1,"1846":1,"1847":1,"1870":2,"1871":2,"1872":2,"1876":1,"1894":1,"1924":1,"1925":1,"1929":1,"2011":1,"2106":1,"2147":2,"2164":1,"2166":1,"2176":2,"2185":1,"2186":1,"2187":2,"2196":1,"2197":1,"2204":1,"2214":1,"2216":1,"2218":1,"2229":1,"2264":1,"2277":5,"2292":3,"2294":1,"2302":1,"2303":1,"2304":1,"2306":1,"2319":4,"2320":1,"2321":2,"2338":1,"2339":1,"2342":1,"2346":2,"2347":1,"2354":1,"2391":2,"2394":1,"2395":1,"2398":1,"2429":1,"2432":1,"2438":1,"2479":1,"2481":2,"2506":1,"2511":1,"2513":2,"2517":1,"2523":1,"2529":2,"2537":1,"2540":1,"2549":3,"2554":1,"2575":2,"2625":1,"2655":1,"2713":1,"2723":1,"2737":1,"2744":1,"2762":1,"2774":1,"2811":3,"2813":2,"2815":2,"2828":1,"2829":2,"2830":1,"2834":2,"2836":1,"2843":4,"2865":2,"2868":4,"2873":1,"2876":1,"2879":1}}],["postssection",{"2":{"996":4}}],["posts",{"0":{"791":1,"980":1,"1037":1,"2134":1},"2":{"0":1,"212":2,"297":1,"415":1,"423":1,"836":1,"866":1,"976":2,"977":3,"980":7,"982":2,"985":1,"990":14,"991":3,"992":2,"995":2,"996":5,"1076":4,"1079":1,"1403":1,"1404":1,"1408":4,"1564":1,"1792":1,"2171":1,"2303":1,"2304":1,"2328":1,"2358":1,"2813":1,"2840":1,"2868":3}}],["p",{"2":{"186":5,"254":3,"255":3,"256":3,"257":3,"258":1,"306":2,"408":6,"423":3,"469":5,"835":3,"851":2,"930":2,"980":3,"982":3,"990":3,"1046":1,"1113":3,"1117":1,"1118":1,"1138":4,"1139":2,"1179":2,"1236":6,"1243":2,"1305":2,"1343":1,"1408":3,"1427":4,"1429":11,"1531":2,"1575":1,"1792":3,"1968":3,"2111":1,"2277":13,"2293":5,"2304":3,"2481":1,"2532":1,"2555":3,"2622":1,"2665":4,"2717":1,"2723":1,"2762":6,"2788":1,"2789":1,"2790":1,"2791":1,"2875":1}}],["pltcl",{"2":{"1972":1}}],["plpython",{"2":{"2759":1}}],["plpython3u",{"2":{"1971":1,"1972":1}}],["plperl",{"2":{"1972":1}}],["plpgsql",{"2":{"184":1,"186":1,"207":1,"208":1,"209":1,"263":1,"264":1,"292":1,"310":2,"313":1,"415":1,"423":1,"426":1,"427":1,"428":1,"439":1,"449":1,"452":1,"454":1,"646":1,"658":1,"664":2,"750":1,"751":1,"752":1,"755":1,"756":1,"764":1,"765":1,"766":1,"774":1,"777":1,"812":1,"813":1,"814":1,"815":1,"883":1,"884":1,"888":1,"904":1,"928":1,"929":1,"1021":1,"1029":1,"1056":2,"1060":1,"1105":3,"1179":2,"1197":1,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1235":1,"1236":1,"1239":1,"1309":1,"1321":1,"1332":1,"1338":1,"1339":1,"1347":1,"1348":1,"1427":1,"1431":1,"1504":1,"1689":1,"1727":1,"1736":1,"1742":1,"1745":1,"1921":1,"1924":1,"1926":1,"1970":1,"1972":1,"1975":1,"2147":1,"2264":1,"2283":1,"2290":1,"2292":1,"2293":1,"2303":1,"2304":1,"2344":1,"2346":1,"2549":3,"2572":1,"2575":1,"2580":2,"2762":1,"2766":1,"2802":1,"2809":1,"2810":1,"2812":1,"2815":1,"2829":1,"2834":2,"2836":1}}],["plv8",{"2":{"1394":1}}],["please",{"2":{"1157":1,"1404":1,"1792":3,"1948":1,"1949":1,"1958":2,"1959":1,"1960":1,"2257":1,"2470":2,"2471":1}}],["pleases",{"2":{"1132":1}}],["pl",{"2":{"868":1,"874":1,"876":3,"1096":1,"1378":1,"1394":4,"1792":1,"1972":4,"2255":2,"2635":1,"2802":1}}],["plug",{"2":{"1049":1}}],["pluggable",{"0":{"2650":1},"1":{"2651":1,"2652":1,"2653":1},"2":{"848":1,"1048":1,"1049":2,"2072":1,"2233":1,"2650":1}}],["plugins",{"0":{"2555":1},"2":{"876":1,"1385":3,"1401":1,"2370":1,"2389":1,"2419":1,"2489":1,"2555":1,"2714":1,"2795":1}}],["plugin",{"0":{"2317":1,"2481":1,"2482":1,"2562":1,"2566":1,"2590":1},"1":{"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2323":1,"2324":1,"2325":1,"2326":1,"2327":1,"2328":1,"2329":1,"2330":1},"2":{"317":2,"347":1,"1039":1,"1096":2,"1135":1,"1386":2,"1401":2,"1792":1,"1813":1,"1840":3,"2165":1,"2195":1,"2223":3,"2226":1,"2228":1,"2254":2,"2318":1,"2370":1,"2389":3,"2430":1,"2432":1,"2435":2,"2438":1,"2479":1,"2481":3,"2482":8,"2487":2,"2555":2,"2590":1,"2721":1,"2729":1,"2841":1}}],["plumbing",{"2":{"834":1,"845":1,"849":1,"869":3,"872":1,"873":2,"1302":1,"1382":1,"2438":1}}],["plus",{"2":{"304":1,"388":1,"753":1,"757":1,"768":1,"776":1,"834":1,"860":3,"867":3,"868":2,"869":2,"871":1,"872":2,"873":1,"876":1,"920":1,"921":1,"1036":2,"1080":1,"1098":1,"1102":1,"1350":1,"1382":3,"1407":1,"1423":2,"1432":1,"1433":1,"1792":1,"1822":1,"1824":1,"2107":1,"2389":1,"2394":1,"2419":1,"2422":1,"2430":1,"2435":1,"2440":1,"2498":1,"2515":1,"2543":1,"2546":3,"2726":1,"2751":1,"2794":1,"2812":1,"2834":1,"2874":1,"2878":1}}],["play",{"2":{"1382":1}}],["playing",{"0":{"1268":1}}],["plays",{"2":{"857":1}}],["planning",{"2":{"2324":1}}],["planned",{"2":{"1130":1}}],["planner",{"2":{"857":1,"860":1,"861":1,"1130":5,"1132":3,"1133":4}}],["plans",{"2":{"1401":1}}],["plan",{"2":{"860":2,"861":1,"1133":1,"1254":1}}],["platforms",{"2":{"1088":1,"1102":1,"1127":1,"1662":1,"2776":1}}],["platform",{"0":{"1094":1},"2":{"428":1,"835":1,"866":1,"977":1,"980":1,"990":1,"1083":1,"1084":4,"1086":1,"1088":1,"1094":3,"1121":1,"1127":2,"1582":1,"1653":1,"1694":1,"1792":4,"2438":1,"2668":1,"2776":1,"2804":1}}],["placeorder",{"2":{"1043":1}}],["place",{"2":{"562":1,"563":1,"706":1,"836":1,"843":1,"859":1,"861":1,"864":1,"865":1,"873":1,"921":1,"988":1,"1043":1,"1044":1,"1184":1,"1203":1,"1385":1,"1416":1,"1431":1,"1439":1,"1440":1,"1792":1,"1942":1,"2317":1,"2426":1,"2431":1,"2532":1,"2774":1,"2869":2}}],["placed",{"2":{"560":1,"619":1,"659":1,"689":1,"694":1,"704":1,"709":1,"714":1,"1044":1,"1703":1,"1792":1,"1924":1,"1925":1,"2191":1,"2222":1,"2320":1,"2340":1,"2444":1,"2505":1,"2529":1,"2533":1,"2633":1}}],["places",{"2":{"395":1,"973":1,"1008":1,"2190":1}}],["placement",{"0":{"623":1,"1924":1},"2":{"214":1,"448":1,"560":1,"619":1,"712":1,"1135":2,"2222":1,"2340":1,"2509":2,"2511":1,"2513":1}}],["placements",{"2":{"203":1,"2222":1,"2505":1}}],["placeholder$",{"2":{"1792":3}}],["placeholder=",{"2":{"938":2,"1061":2}}],["placeholder",{"0":{"212":1,"388":1,"1605":1,"1733":1},"1":{"389":1},"2":{"167":2,"215":1,"216":1,"386":1,"388":3,"448":1,"527":1,"529":2,"533":1,"534":1,"957":1,"961":1,"998":1,"1019":1,"1067":1,"1104":1,"1105":2,"1379":1,"1577":1,"1605":1,"1733":2,"1738":3,"1792":2,"1862":1,"2119":1,"2185":1,"2221":2,"2264":3,"2282":1,"2284":1,"2286":1,"2322":1,"2493":2,"2497":1,"2540":4,"2546":2,"2645":1,"2731":1,"2795":1,"2845":2,"2849":1}}],["placeholders",{"0":{"167":1,"207":1,"2119":1,"2185":1,"2493":1,"2497":1,"2688":1,"2764":1},"2":{"165":2,"170":1,"212":2,"214":2,"388":2,"390":1,"393":1,"394":1,"395":1,"396":1,"404":1,"452":1,"528":1,"529":1,"674":1,"679":1,"784":2,"834":1,"926":1,"1016":1,"1017":1,"1023":1,"1086":1,"1094":1,"1105":1,"1374":1,"1398":1,"1604":1,"1615":1,"1685":1,"1696":1,"1723":1,"1733":3,"1743":1,"1792":3,"1808":1,"2079":1,"2117":1,"2119":1,"2141":1,"2170":1,"2223":2,"2264":4,"2284":3,"2322":2,"2483":1,"2493":2,"2502":2,"2534":1,"2540":1,"2575":1,"2645":1,"2653":1,"2688":1,"2702":1,"2704":2,"2719":1,"2759":1,"2764":1,"2765":1,"2771":1,"2849":1}}],["plaintext",{"2":{"182":1,"184":1,"1100":1,"1664":1,"2052":1,"2291":1,"2292":1}}],["plain",{"0":{"1368":1,"2726":1,"2774":1},"1":{"1369":1,"1370":1,"1371":1,"1372":1,"1373":1,"1374":1,"1375":1,"1376":1,"1377":1,"1378":1,"1379":1,"1380":1},"2":{"31":1,"361":1,"362":1,"487":4,"494":1,"549":1,"567":1,"609":1,"874":1,"911":1,"961":1,"971":1,"1037":2,"1042":1,"1074":1,"1077":1,"1094":1,"1125":1,"1189":1,"1195":1,"1199":1,"1373":1,"1377":1,"1379":1,"1386":1,"1419":1,"1424":1,"1502":1,"1571":1,"1782":1,"1792":4,"1853":1,"1936":1,"1943":1,"1972":1,"2092":1,"2101":1,"2156":1,"2167":1,"2221":1,"2271":1,"2391":1,"2461":1,"2484":1,"2525":1,"2529":1,"2535":1,"2537":1,"2542":1,"2545":1,"2596":1,"2662":1,"2667":1,"2673":1,"2678":1,"2694":1,"2709":1,"2729":1,"2739":1,"2772":1,"2785":1,"2802":1,"2809":1,"2820":1,"2826":1,"2836":1,"2858":1,"2859":1,"2860":1,"2871":1,"2874":1}}],["puserid",{"2":{"2723":1}}],["pushing",{"2":{"974":1}}],["push",{"2":{"848":1,"2388":1}}],["pushes",{"2":{"668":1,"856":1}}],["pulled",{"2":{"2788":1}}],["pulls",{"2":{"1429":1}}],["pull",{"2":{"1343":1,"1428":1,"2788":2,"2789":2,"2790":2,"2791":2}}],["pubkeycredparams",{"2":{"1220":1}}],["pub",{"2":{"922":2,"1185":3,"1302":1,"1303":1,"1320":3,"1322":1}}],["publishing",{"2":{"1792":1,"2407":1}}],["publishaot=true",{"2":{"1046":1,"2481":1}}],["publishaot",{"2":{"954":1}}],["publishes",{"2":{"1305":1,"1714":1}}],["publisher",{"0":{"668":1,"2829":1},"2":{"1861":1,"2226":1,"2407":1,"2827":1,"2828":2,"2829":1,"2830":1,"2835":2,"2836":2,"2838":1}}],["published",{"2":{"325":1,"840":1,"1128":1,"2380":1,"2385":1,"2398":2,"2490":1,"2754":1}}],["publish",{"0":{"2391":1,"2392":1,"2834":1},"2":{"663":1,"1046":1,"1320":1,"1792":1,"1861":1,"2226":2,"2391":3,"2392":4,"2393":2,"2406":1,"2481":1,"2498":1,"2792":3,"2827":1}}],["public|api",{"2":{"2069":1}}],["public|myapp",{"2":{"1792":1,"2062":1,"2635":1}}],["publicapitypes",{"2":{"1577":1}}],["publicapi",{"2":{"1577":1}}],["publicly",{"2":{"1403":1,"1792":1,"2634":1}}],["publickeyalgorithmcolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["publickeycolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["public",{"0":{"7":1,"9":1,"925":1,"2214":1,"2370":1},"2":{"7":3,"10":1,"223":1,"261":1,"263":1,"264":1,"320":1,"352":3,"476":1,"542":1,"582":1,"724":1,"785":1,"836":1,"837":1,"867":1,"922":2,"925":4,"926":3,"932":2,"934":3,"935":3,"936":3,"937":5,"938":2,"941":1,"943":1,"946":1,"976":1,"998":1,"1010":1,"1018":1,"1042":1,"1060":1,"1138":4,"1139":1,"1158":1,"1163":2,"1184":2,"1185":2,"1187":1,"1188":2,"1189":1,"1192":10,"1193":5,"1196":1,"1200":1,"1202":1,"1203":1,"1207":2,"1209":1,"1210":3,"1213":3,"1215":7,"1222":1,"1232":1,"1234":1,"1236":7,"1237":2,"1240":2,"1243":2,"1251":2,"1315":1,"1362":1,"1363":1,"1366":2,"1398":1,"1423":1,"1441":1,"1567":2,"1576":1,"1577":1,"1661":1,"1704":1,"1747":1,"1792":8,"1839":1,"1867":2,"1868":1,"1886":3,"1887":2,"1889":2,"1930":2,"2038":1,"2040":2,"2062":1,"2201":1,"2202":1,"2223":1,"2228":1,"2251":1,"2255":6,"2256":4,"2257":1,"2265":4,"2266":1,"2267":1,"2344":2,"2370":3,"2396":1,"2432":1,"2461":1,"2477":1,"2481":1,"2487":1,"2489":1,"2635":1,"2721":1,"2728":1,"2775":1,"2797":1,"2824":3,"2825":2}}],["punchline",{"2":{"861":1}}],["purely",{"2":{"873":1,"1107":1,"1150":1,"1402":1,"1406":1,"2193":1,"2455":1}}],["pure",{"0":{"949":1,"1209":1,"1269":1,"1292":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1293":1},"2":{"669":1,"848":1,"851":1,"852":2,"872":1,"1037":1,"1254":1,"1255":1,"1264":1,"1266":1,"1269":2,"1271":1,"1280":1,"1281":1,"1403":1,"1559":1,"1570":1,"1792":2,"2164":1,"2360":1,"2380":1,"2741":1,"2807":1,"2828":1}}],["purposes",{"2":{"624":1,"666":1,"926":1,"1386":1,"1614":1,"2824":1}}],["purpose",{"0":{"2249":1},"2":{"33":1,"300":1,"546":1,"863":1,"1167":1,"1255":1,"1309":1,"1385":1,"1705":1,"2259":1,"2421":1,"2436":2,"2495":1}}],["putting",{"0":{"1179":1},"2":{"683":1,"851":1,"859":1,"876":1,"1078":1,"2809":1}}],["puts",{"2":{"448":1,"852":1,"873":1,"1326":1,"1403":1,"1421":1,"2455":1}}],["put",{"2":{"204":1,"243":1,"258":2,"390":1,"419":3,"423":1,"452":1,"812":1,"815":1,"831":1,"835":1,"849":1,"873":1,"880":1,"1104":1,"1105":1,"1107":1,"1253":1,"1423":1,"1487":1,"1642":1,"1646":1,"1729":1,"1792":2,"1846":1,"1925":1,"2264":1,"2277":3,"2305":2,"2319":2,"2321":1,"2429":1,"2438":1,"2481":1,"2517":1,"2529":1,"2531":1,"2723":1,"2762":1,"2843":2,"2861":1,"2865":1,"2873":1}}],["packaging",{"2":{"2569":1}}],["package",{"0":{"2386":1},"2":{"864":2,"869":1,"1026":1,"1096":1,"1418":1,"2254":1,"2385":1,"2389":1,"2716":1,"2786":1}}],["packages",{"2":{"863":1,"869":1,"1088":1,"2567":1,"2714":1}}],["pacific",{"2":{"2453":1}}],["pause",{"2":{"1254":1}}],["paul",{"2":{"913":1}}],["pagination",{"2":{"1096":2,"1122":1,"1127":1}}],["paged",{"2":{"861":1}}],["page",{"0":{"1430":1},"2":{"3":1,"221":1,"267":1,"296":1,"386":2,"387":1,"395":2,"534":1,"539":3,"833":3,"834":2,"1037":2,"1043":1,"1047":1,"1068":1,"1400":1,"1414":1,"1416":1,"1423":3,"1424":1,"1427":2,"1431":2,"1444":1,"1612":1,"1684":2,"1685":2,"1792":7,"1925":1,"1978":1,"2016":1,"2018":2,"2040":1,"2164":2,"2166":1,"2202":1,"2474":1,"2479":1,"2517":1,"2632":1,"2759":1,"2779":2,"2792":1,"2816":1}}],["pages",{"2":{"0":1,"835":1,"1094":1,"1430":1,"1432":3,"1792":2,"1836":1,"2170":1,"2806":1,"2827":1,"2870":1}}],["pandas",{"0":{"909":1},"2":{"879":1,"909":1}}],["panel",{"2":{"834":1,"836":1,"1047":1,"2166":1}}],["padding",{"2":{"965":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["pad",{"2":{"872":1,"1044":2}}],["papercut",{"2":{"2453":1}}],["papered",{"2":{"2452":1}}],["paper",{"2":{"852":1}}],["papers",{"2":{"840":1}}],["pascal",{"2":{"1792":1}}],["paste",{"2":{"920":1,"959":1,"965":2,"966":1,"1792":2,"2047":1,"2054":1,"2075":1,"2221":1,"2531":1,"2635":2,"2651":1}}],["pasted",{"2":{"699":1,"2531":3,"2533":1,"2537":1,"2678":1,"2695":1,"2869":1}}],["past",{"2":{"877":1,"1180":1}}],["passkeyauth",{"0":{"2625":1},"2":{"1217":2,"1223":1,"1240":1,"1241":1,"1245":1,"1252":1,"1792":1,"1867":1,"1889":1,"1890":1,"1892":1,"1893":1,"2235":1,"2486":1}}],["passkeys",{"0":{"1209":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1},"2":{"1037":1,"1098":1,"1209":1,"1210":1,"1213":3,"1215":1,"1216":1,"1218":1,"1220":1,"1232":2,"1234":1,"1236":1,"1239":1,"1252":1,"1253":1,"1867":2,"1870":1,"2625":1,"2628":1,"2736":1}}],["passkey",{"0":{"1220":1,"1221":1,"1866":1,"1870":1,"1871":1,"2486":1,"2492":1},"1":{"1867":1,"1868":1,"1869":1,"1870":1,"1871":1,"1872":1,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1,"1892":1,"1893":1,"1894":1,"1895":1},"2":{"835":1,"868":1,"869":1,"1037":1,"1086":1,"1098":6,"1126":1,"1127":1,"1209":2,"1211":2,"1212":1,"1213":2,"1214":3,"1215":2,"1216":1,"1217":7,"1218":1,"1219":1,"1220":12,"1221":9,"1222":9,"1224":2,"1226":8,"1232":4,"1233":2,"1234":4,"1235":3,"1236":2,"1237":2,"1238":1,"1239":3,"1245":3,"1250":1,"1252":2,"1445":2,"1465":1,"1466":1,"1788":1,"1792":29,"1866":2,"1867":1,"1868":1,"1869":1,"1870":6,"1871":3,"1872":3,"1874":2,"1876":8,"1882":1,"1883":1,"1884":1,"1893":10,"1894":3,"1895":1,"2164":3,"2189":1,"2223":1,"2420":1,"2423":2,"2492":1,"2496":1,"2625":1}}],["passthrough",{"0":{"438":1,"1331":1,"1337":1,"1920":1,"2313":1,"2809":1},"2":{"435":1,"436":1,"439":1,"453":2,"876":1,"1104":1,"1105":1,"1333":1,"1337":1,"1342":1,"1349":3,"1351":1,"1396":1,"1431":2,"1915":1,"1928":1,"2229":1,"2313":4,"2463":2,"2510":1,"2549":3,"2580":1,"2806":1,"2807":2,"2809":4,"2817":1}}],["passes",{"2":{"383":1,"408":1,"435":1,"448":1,"942":1,"1159":1,"1332":1,"1338":1,"1359":1,"1376":1,"1396":1,"1422":1,"2348":1,"2482":1,"2528":2,"2665":1,"2864":2,"2881":1}}],["passed",{"2":{"40":1,"41":1,"50":1,"51":1,"156":1,"188":1,"213":1,"389":2,"390":1,"394":1,"460":1,"462":1,"497":1,"512":1,"528":1,"760":1,"761":1,"762":1,"770":1,"771":1,"772":1,"775":1,"801":2,"865":1,"883":1,"930":1,"1070":1,"1074":1,"1102":1,"1105":1,"1214":1,"1232":1,"1236":1,"1477":2,"1569":1,"1741":1,"1792":7,"1852":1,"1882":1,"1886":1,"1921":1,"1928":2,"2094":1,"2104":1,"2107":1,"2113":1,"2147":1,"2282":1,"2287":1,"2289":1,"2291":1,"2296":1,"2321":1,"2348":1,"2383":1,"2446":2,"2496":1,"2526":1,"2528":1,"2531":1,"2535":3,"2537":2,"2549":3,"2572":1,"2575":1,"2615":1,"2860":1,"2864":1,"2880":1}}],["passing",{"2":{"226":1,"514":1,"794":1,"881":1,"915":2,"1097":1,"1241":1,"1475":1,"1572":1,"1792":2,"1864":1,"1890":1,"1961":1,"2094":1,"2104":1,"2107":1,"2178":1,"2258":2,"2495":1,"2535":1,"2537":3,"2802":1,"2825":1,"2855":1}}],["pass2",{"2":{"62":2}}],["pass1",{"2":{"62":2}}],["pass",{"0":{"502":1,"503":1},"2":{"38":4,"167":1,"309":2,"408":1,"458":1,"463":1,"464":1,"469":1,"499":2,"741":1,"776":1,"812":1,"816":1,"851":1,"864":1,"865":1,"872":1,"874":1,"892":1,"994":1,"1063":1,"1074":1,"1105":1,"1326":1,"1328":1,"1340":2,"1398":3,"1458":4,"1469":1,"1472":1,"1711":1,"1792":7,"1823":1,"1909":1,"2097":1,"2124":2,"2130":1,"2140":1,"2149":1,"2184":1,"2322":1,"2375":3,"2396":1,"2407":1,"2416":1,"2431":1,"2433":1,"2435":1,"2446":1,"2456":1,"2482":1,"2513":1,"2526":1,"2535":1,"2575":1,"2665":1,"2687":1,"2695":1,"2800":1,"2830":1,"2860":1,"2875":1,"2880":2,"2881":1}}],["passwordhashlocation",{"2":{"1792":1}}],["password789",{"2":{"1061":1}}],["passwordless",{"2":{"1037":1,"1098":2,"1209":1,"1217":1,"1229":3,"1445":1,"1465":1,"1466":1,"1651":1,"1788":1,"1792":3,"1866":1,"1879":3,"2164":1,"2189":1,"2565":1,"2625":1}}],["password456",{"2":{"938":1,"1051":1,"1061":1,"1307":2,"1502":1}}],["password123",{"2":{"938":2,"970":1,"1051":1,"1061":1,"1307":2}}],["password=test",{"2":{"2875":1}}],["password=pass",{"2":{"2686":1,"2699":1}}],["password=postgres",{"2":{"1792":1,"2111":1,"2532":1,"2718":1,"2823":2,"2824":2,"2825":1}}],["password=mypassword",{"2":{"1613":1}}],["password=secret",{"2":{"1173":1,"1176":2,"1177":2,"1614":3,"1627":1,"1629":2,"2266":1}}],["password=",{"2":{"937":1,"1067":1,"1534":1,"1607":1,"1615":1,"1633":2,"1792":1,"2687":1,"2824":1,"2825":1,"2872":2}}],["passwordverificationsucceededcommand",{"2":{"310":1,"1056":1,"1062":1,"1469":1,"1472":1,"1473":1,"1792":1}}],["passwordverificationfailedcommand",{"2":{"310":1,"1056":1,"1062":1,"1469":1,"1472":1,"1473":1,"1792":1}}],["passwordparameternamecontains",{"2":{"309":2,"1052":2,"1469":1,"1472":1,"1483":1,"1792":3}}],["passwords",{"2":{"63":1,"308":1,"316":1,"357":1,"362":1,"364":1,"366":1,"368":2,"868":1,"922":1,"927":1,"928":2,"942":1,"944":1,"1048":1,"1049":2,"1059":1,"1062":2,"1185":1,"1195":1,"1204":1,"1499":1,"1502":2,"1867":1,"2052":1,"2164":1,"2165":2,"2188":1,"2645":1,"2798":1}}],["password",{"0":{"38":1,"57":1,"307":1,"363":1,"592":1,"930":1,"1049":1,"1051":1,"1055":1,"1056":1,"1195":1,"1472":1,"1473":1,"2177":1},"1":{"308":1,"309":1,"310":1,"1056":1,"1473":1},"2":{"31":2,"33":1,"37":4,"38":9,"39":1,"40":2,"41":1,"48":2,"49":1,"56":3,"57":3,"58":1,"61":2,"63":4,"190":1,"297":2,"298":13,"300":1,"307":2,"308":21,"309":8,"310":9,"312":4,"313":4,"360":6,"361":9,"362":1,"363":3,"364":1,"365":3,"366":2,"367":1,"592":10,"593":1,"700":1,"813":4,"817":4,"834":1,"835":1,"921":1,"922":2,"924":3,"926":4,"927":2,"928":2,"929":2,"930":37,"934":4,"937":1,"938":7,"940":1,"942":1,"944":1,"945":2,"1037":1,"1048":5,"1049":3,"1050":4,"1051":1,"1052":3,"1055":7,"1056":11,"1061":6,"1062":3,"1064":2,"1067":2,"1068":2,"1078":1,"1098":4,"1185":3,"1196":1,"1197":6,"1199":2,"1202":1,"1207":1,"1209":1,"1213":1,"1221":1,"1251":1,"1307":3,"1308":4,"1371":3,"1458":2,"1466":1,"1468":1,"1471":2,"1472":8,"1480":1,"1482":1,"1483":1,"1497":1,"1499":2,"1501":3,"1504":10,"1507":1,"1534":1,"1550":1,"1616":2,"1651":1,"1661":2,"1700":1,"1792":36,"1984":2,"1986":2,"1987":2,"1989":2,"1995":2,"2040":1,"2145":1,"2146":2,"2147":9,"2164":1,"2165":1,"2167":1,"2176":10,"2177":17,"2178":1,"2187":6,"2188":2,"2216":1,"2477":1,"2540":3,"2565":2,"2575":4,"2655":1}}],["painful",{"2":{"1075":1}}],["pairing",{"2":{"2481":1}}],["pairs",{"2":{"1608":1,"2040":1,"2272":1,"2452":1,"2476":1}}],["pair",{"2":{"531":1,"1210":1,"1456":1,"1792":1,"2177":1,"2538":1,"2554":1}}],["paid",{"2":{"2":1,"844":1,"1033":3,"1206":1,"1824":2}}],["payoff",{"2":{"1409":1}}],["pays",{"2":{"873":1,"874":1,"1007":1,"1139":1,"1382":1,"1401":1}}],["payment",{"0":{"594":1},"2":{"577":1,"594":2,"865":1,"1035":1,"1068":1,"1098":1,"1154":4,"1458":1,"1599":1,"1792":1,"2375":1}}],["pay",{"2":{"388":1,"861":1,"864":1,"1792":1,"1861":1}}],["payloads",{"0":{"1091":1,"1268":1},"2":{"1091":1,"1262":1,"1511":1,"1792":1,"2496":1}}],["payload",{"0":{"1262":1,"1263":1,"1298":1,"1299":1},"1":{"1299":1},"2":{"71":8,"414":1,"533":1,"1030":1,"1098":1,"1105":1,"1161":1,"1255":4,"1258":1,"1263":1,"1266":1,"1270":1,"1280":1,"1285":3,"1299":1,"1305":1,"1309":1,"1823":1,"1856":1,"1925":1,"2286":1,"2300":1,"2398":2,"2492":2,"2517":1,"2813":1,"2829":1}}],["patrn",{"2":{"2446":1}}],["patters",{"2":{"1792":2}}],["patterns",{"0":{"895":1,"2036":1,"2213":1},"1":{"896":1,"897":1,"898":1,"899":1,"900":1,"2214":1,"2215":1,"2216":1,"2217":1,"2218":1},"2":{"215":1,"338":1,"747":2,"781":2,"782":2,"784":2,"786":2,"788":2,"840":3,"841":1,"849":1,"851":1,"880":1,"885":1,"920":1,"948":1,"987":1,"1037":1,"1066":1,"1069":1,"1101":1,"1267":1,"1372":1,"1792":5,"1956":1,"2034":1,"2038":1,"2059":1,"2092":1,"2125":2,"2270":1,"2364":1,"2379":1,"2389":1,"2395":1,"2635":1,"2867":1}}],["pattern",{"0":{"9":1,"106":1,"451":1,"663":1,"856":1,"1076":1,"1345":1,"1524":1,"1525":1,"1526":1,"2144":1,"2371":1},"1":{"664":1,"665":1,"666":1},"2":{"298":1,"414":1,"650":1,"663":1,"686":1,"812":1,"816":1,"817":1,"851":2,"863":1,"864":1,"876":1,"920":2,"959":1,"967":1,"986":1,"988":1,"1006":1,"1037":1,"1068":1,"1076":2,"1077":2,"1097":1,"1106":1,"1139":1,"1162":1,"1176":1,"1305":1,"1376":1,"1386":1,"1393":1,"1398":3,"1405":1,"1416":1,"1574":1,"1684":1,"1738":1,"1753":1,"1792":12,"1838":4,"1839":1,"1852":1,"1898":2,"1909":1,"2000":1,"2002":3,"2004":1,"2007":1,"2036":2,"2047":1,"2062":1,"2109":1,"2140":1,"2141":2,"2142":1,"2144":3,"2146":3,"2148":1,"2228":1,"2270":1,"2289":1,"2300":1,"2318":1,"2328":1,"2330":1,"2371":2,"2381":1,"2383":1,"2400":1,"2427":1,"2431":1,"2435":1,"2436":2,"2446":2,"2482":1,"2490":1,"2521":1,"2537":1,"2539":1,"2575":5,"2635":1,"2666":1,"2840":1,"2868":1,"2869":1}}],["patch",{"2":{"204":1,"243":1,"1104":1,"1105":1,"1107":1,"1419":1,"1729":1,"1792":1,"1925":1,"2264":1,"2440":1,"2450":1,"2459":1,"2461":1,"2468":1,"2474":1,"2508":1,"2515":1,"2517":1,"2762":1}}],["patientid=1",{"2":{"186":1,"2293":1}}],["patientid",{"2":{"184":1,"2292":1}}],["patients",{"2":{"184":2,"186":1,"2292":1,"2293":1}}],["patient",{"2":{"184":7,"186":5,"2291":1,"2292":5,"2293":5}}],["pathparam",{"2":{"2277":1}}],["path`",{"2":{"1792":1}}],["path=",{"2":{"1792":1,"2256":1}}],["path=str",{"2":{"1366":1}}],["pathname",{"2":{"1335":1,"1792":2}}],["pathology",{"2":{"852":1,"856":1}}],["paths",{"0":{"1226":1,"1479":1,"1744":1,"1776":1,"1876":1,"1929":1,"2064":1,"2215":1,"2463":1},"1":{"1480":1,"1481":1,"1745":1,"1746":1,"1747":1,"1930":1},"2":{"253":1,"261":1,"387":1,"404":1,"409":1,"446":1,"879":1,"1007":1,"1067":1,"1086":1,"1095":1,"1114":1,"1121":1,"1167":1,"1226":1,"1275":1,"1366":1,"1398":3,"1416":1,"1458":1,"1460":1,"1562":1,"1576":1,"1744":1,"1776":1,"1778":1,"1780":1,"1792":6,"1841":1,"1876":1,"1929":1,"1961":3,"2036":1,"2040":1,"2258":1,"2267":1,"2277":1,"2329":1,"2344":1,"2346":2,"2347":1,"2372":1,"2375":2,"2384":1,"2398":3,"2414":1,"2415":1,"2423":1,"2437":1,"2438":1,"2466":1,"2493":1,"2532":1,"2559":1,"2621":1,"2622":1,"2645":1,"2648":1}}],["path>",{"2":{"243":2,"399":1,"651":2,"2832":2}}],["path",{"0":{"106":1,"249":1,"250":1,"252":1,"253":1,"254":1,"255":1,"256":1,"257":1,"258":1,"397":1,"401":1,"402":1,"404":1,"405":1,"406":1,"408":1,"421":1,"423":1,"445":1,"469":1,"652":1,"653":1,"654":1,"662":1,"933":1,"1070":1,"1575":1,"2036":1,"2277":1,"2327":1,"2346":1,"2404":1,"2555":1,"2665":1,"2727":1},"1":{"254":1,"255":1,"256":1,"257":1,"258":1,"398":1,"399":1,"400":1,"401":1,"402":1,"403":1,"404":1,"405":2,"406":2,"407":2,"408":2,"409":1,"410":1,"411":1,"653":1,"654":1},"2":{"31":1,"37":1,"38":4,"39":1,"40":1,"41":1,"157":7,"162":2,"212":1,"223":2,"243":3,"244":1,"245":1,"252":1,"253":2,"258":1,"260":2,"386":1,"387":1,"393":3,"395":3,"397":2,"398":2,"399":1,"401":2,"402":1,"403":1,"404":1,"405":1,"406":1,"408":4,"409":4,"411":1,"414":3,"421":1,"422":1,"423":9,"436":10,"445":1,"446":8,"448":1,"452":2,"469":4,"533":1,"649":2,"650":2,"652":3,"653":4,"654":4,"659":1,"662":2,"668":1,"669":2,"690":1,"748":1,"756":7,"757":8,"758":1,"784":6,"785":2,"835":1,"868":1,"871":1,"875":1,"876":1,"933":6,"934":2,"935":1,"936":1,"946":1,"983":1,"1037":1,"1055":1,"1056":2,"1057":1,"1058":1,"1060":1,"1065":1,"1066":1,"1067":1,"1070":6,"1073":1,"1078":1,"1094":1,"1095":1,"1102":4,"1113":1,"1121":1,"1125":1,"1127":1,"1185":3,"1188":2,"1192":1,"1199":2,"1207":1,"1335":5,"1355":4,"1357":1,"1358":4,"1366":11,"1367":1,"1386":3,"1447":1,"1449":1,"1451":1,"1452":1,"1454":1,"1456":1,"1460":3,"1479":2,"1501":1,"1511":1,"1554":1,"1575":3,"1576":2,"1604":1,"1618":3,"1651":2,"1661":1,"1684":2,"1763":1,"1764":4,"1773":2,"1776":1,"1780":1,"1792":56,"1804":1,"1817":1,"1824":1,"1831":2,"1833":1,"1852":2,"1864":2,"1898":1,"1929":2,"1961":1,"1984":2,"1986":2,"1987":3,"1989":2,"1995":1,"2011":1,"2034":1,"2040":1,"2047":4,"2094":2,"2096":2,"2102":1,"2110":1,"2112":1,"2127":1,"2185":3,"2196":4,"2197":1,"2221":1,"2232":1,"2239":1,"2249":5,"2251":1,"2252":15,"2254":1,"2256":4,"2265":1,"2267":1,"2270":2,"2272":1,"2277":9,"2278":2,"2286":2,"2309":1,"2318":1,"2323":1,"2327":4,"2328":1,"2346":1,"2347":3,"2354":1,"2358":2,"2372":3,"2375":4,"2383":2,"2389":2,"2391":1,"2400":1,"2402":1,"2411":1,"2428":1,"2429":1,"2435":3,"2437":1,"2438":1,"2455":1,"2463":2,"2465":2,"2476":1,"2481":1,"2489":1,"2493":1,"2498":1,"2529":6,"2530":1,"2531":4,"2532":2,"2533":1,"2535":1,"2537":2,"2539":1,"2542":1,"2546":2,"2554":2,"2555":11,"2558":1,"2559":1,"2565":2,"2589":3,"2614":3,"2634":4,"2635":4,"2665":3,"2666":2,"2672":1,"2690":1,"2723":3,"2727":4,"2729":1,"2758":1,"2781":1,"2782":1,"2783":1,"2784":1,"2795":1,"2807":1,"2811":4,"2830":1,"2832":1,"2840":2,"2865":5,"2869":2,"2877":1,"2880":1,"2881":2}}],["parity",{"0":{"2329":1},"2":{"2498":1}}],["paring",{"2":{"2258":1}}],["parent",{"2":{"1097":1,"1973":1,"1977":1,"2157":1,"2532":2,"2543":1,"2678":1,"2679":1,"2695":1}}],["park",{"2":{"851":1}}],["parquet",{"2":{"848":1}}],["paragraph",{"0":{"1039":1}}],["parallelism",{"2":{"861":1,"1130":2,"2536":1}}],["parallel",{"0":{"1130":1,"1745":1,"2766":1},"2":{"860":1,"871":1,"872":1,"920":1,"928":1,"986":1,"993":1,"1015":1,"1023":1,"1026":1,"1029":1,"1037":1,"1079":1,"1104":1,"1105":2,"1108":1,"1130":7,"1376":2,"1398":4,"1399":2,"1418":1,"1458":1,"1741":2,"1745":2,"1746":1,"2099":1,"2167":1,"2221":1,"2289":3,"2329":1,"2346":4,"2347":3,"2375":1,"2463":1,"2527":1,"2545":1,"2546":1,"2759":1,"2766":2,"2767":2,"2770":1,"2858":1,"2862":1,"2869":1,"2872":1,"2873":1}}],["paradoxical",{"2":{"1324":1}}],["paradigm",{"2":{"874":1}}],["paradigms",{"2":{"841":1}}],["parade",{"2":{"840":1}}],["paramindex",{"2":{"2622":3}}],["paramtests",{"2":{"2457":2}}],["paramtype",{"2":{"2277":1}}],["paramater",{"2":{"1792":1}}],["param3",{"2":{"1792":1}}],["param3>",{"2":{"113":1}}],["param2",{"2":{"915":2,"1792":1}}],["param2>",{"2":{"113":1}}],["param1",{"2":{"915":2,"1792":1}}],["param1>",{"2":{"113":1}}],["param=null",{"2":{"460":1}}],["param=",{"2":{"460":1}}],["param>",{"2":{"358":4}}],["param",{"0":{"165":1,"369":1,"515":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2849":1},"1":{"166":1,"167":1,"168":1,"169":1,"170":1,"370":1,"371":1,"372":1,"373":1,"374":1,"375":1,"376":1,"377":1,"378":1,"379":1,"380":1,"381":1,"382":1,"383":1,"384":1,"385":1,"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"525":1,"526":1},"2":{"37":1,"61":1,"68":1,"69":1,"71":2,"77":1,"136":1,"155":2,"156":2,"157":2,"165":1,"166":2,"167":3,"168":7,"169":3,"170":2,"184":2,"206":1,"215":1,"226":1,"237":1,"238":1,"253":1,"258":1,"260":1,"298":2,"306":1,"312":3,"357":1,"358":3,"360":4,"361":1,"364":2,"365":1,"369":3,"370":16,"372":3,"373":2,"374":2,"375":5,"376":2,"377":1,"378":14,"380":3,"382":5,"383":3,"385":2,"389":1,"395":1,"404":1,"407":1,"408":1,"409":1,"415":1,"466":6,"472":1,"493":1,"503":2,"507":1,"508":1,"510":2,"515":3,"516":2,"520":4,"521":1,"522":4,"523":1,"527":2,"534":1,"544":1,"565":1,"568":1,"584":2,"586":2,"592":2,"612":1,"613":1,"614":1,"621":1,"622":3,"623":1,"659":1,"665":2,"674":1,"722":1,"750":2,"751":1,"755":1,"756":1,"758":1,"764":1,"767":1,"774":1,"775":1,"777":1,"783":1,"785":1,"787":1,"789":1,"797":3,"811":1,"826":2,"827":2,"886":1,"902":1,"904":1,"1038":2,"1040":1,"1045":2,"1073":2,"1105":1,"1142":2,"1150":3,"1154":1,"1179":3,"1358":4,"1368":1,"1371":5,"1372":3,"1373":1,"1374":4,"1375":1,"1376":6,"1378":1,"1379":1,"1386":4,"1387":1,"1391":1,"1392":1,"1395":2,"1396":1,"1398":11,"1405":1,"1410":2,"1412":2,"1413":3,"1458":2,"1472":1,"1504":3,"1547":5,"1567":3,"1664":3,"1689":5,"1727":3,"1738":1,"1792":1,"1864":1,"1924":1,"2010":2,"2012":1,"2013":1,"2147":3,"2176":2,"2193":4,"2204":2,"2221":1,"2228":1,"2247":4,"2277":2,"2319":2,"2320":1,"2321":4,"2322":5,"2323":9,"2329":1,"2332":10,"2333":17,"2334":6,"2335":7,"2336":2,"2337":2,"2338":2,"2339":2,"2342":1,"2348":3,"2372":1,"2511":1,"2513":1,"2540":6,"2575":2,"2591":4,"2665":1,"2723":1,"2731":1,"2732":3,"2733":1,"2734":2,"2762":1,"2763":1,"2774":2,"2813":1,"2829":3,"2834":2,"2845":3,"2846":4,"2847":3,"2848":3,"2849":2,"2859":2}}],["params",{"0":{"1300":1,"2495":1},"1":{"1301":1},"2":{"37":2,"38":1,"39":1,"40":1,"41":1,"50":1,"60":1,"61":2,"62":1,"63":1,"105":1,"395":1,"454":3,"794":1,"795":1,"797":5,"798":3,"799":1,"815":1,"835":1,"1026":1,"1189":2,"1196":1,"1255":2,"1285":4,"1301":1,"1547":2,"1738":1,"1792":1,"1923":1,"1924":1,"2222":1,"2284":2,"2332":1,"2338":1,"2380":1,"2523":1,"2549":2,"2572":2,"2807":2}}],["parameterparsers",{"2":{"2451":1,"2457":1,"2621":1}}],["parameterhandler",{"2":{"2372":1}}],["parameterdescription",{"2":{"2324":1}}],["parameterless",{"2":{"2319":1}}],["parametername",{"2":{"1792":1}}],["parameternameclaimsmapping",{"0":{"1478":1},"2":{"306":2,"690":1,"801":1,"937":1,"1469":1,"1477":1,"1483":1,"1539":1,"1544":1,"1545":1,"1546":1,"1548":1,"1792":2,"2183":2,"2187":1,"2394":2,"2529":1}}],["parameterized",{"2":{"1102":2,"1738":1,"1792":1,"1852":1,"2283":1,"2284":1,"2319":1,"2383":1}}],["parameter",{"0":{"68":1,"71":1,"72":1,"116":1,"156":1,"167":1,"215":1,"237":1,"254":1,"258":1,"357":1,"386":1,"393":1,"405":1,"407":1,"449":1,"503":1,"507":1,"510":1,"511":1,"751":1,"763":1,"773":1,"802":1,"812":1,"814":1,"1033":1,"1052":1,"1142":1,"1545":1,"1546":1,"1738":1,"1918":1,"1923":1,"1968":1,"2203":1,"2282":1,"2332":1,"2493":1,"2494":1,"2518":1,"2519":1,"2575":1,"2724":1,"2732":1,"2734":1},"1":{"69":1,"70":1,"71":1,"72":1,"73":1,"74":1,"75":1,"76":1,"77":1,"157":1,"358":1,"359":1,"360":1,"361":1,"362":1,"363":1,"364":1,"365":1,"366":1,"367":1,"368":1,"387":1,"388":1,"389":1,"390":1,"391":1,"392":1,"393":1,"394":1,"395":1,"396":1,"508":1,"509":1,"510":1,"511":1,"512":1,"513":1,"514":1,"1924":1,"1925":1,"1926":1,"2204":1,"2283":1,"2284":1,"2285":1,"2286":1},"2":{"31":1,"38":1,"41":1,"50":1,"51":2,"68":1,"69":1,"71":2,"72":1,"73":2,"74":4,"75":1,"76":1,"77":1,"79":1,"101":2,"106":1,"107":3,"109":1,"133":1,"156":3,"158":1,"165":2,"166":1,"167":2,"168":7,"169":1,"170":1,"182":1,"184":1,"188":1,"190":1,"202":1,"212":4,"215":2,"226":5,"237":3,"253":1,"258":2,"260":1,"302":1,"306":2,"309":4,"316":1,"357":2,"358":3,"362":2,"368":1,"369":4,"370":4,"372":1,"373":1,"376":3,"377":1,"382":1,"383":3,"384":6,"385":2,"386":1,"388":5,"389":2,"390":5,"395":4,"404":2,"407":1,"408":4,"436":2,"447":3,"448":3,"452":1,"454":1,"462":2,"466":1,"467":1,"468":2,"469":1,"472":1,"499":2,"503":2,"504":2,"506":2,"507":2,"508":1,"510":4,"511":2,"512":3,"513":1,"524":1,"525":1,"526":2,"527":2,"528":5,"529":4,"534":3,"535":2,"544":2,"570":1,"587":2,"589":1,"595":1,"596":1,"637":1,"639":1,"646":1,"675":1,"680":1,"690":1,"718":1,"720":4,"725":1,"726":1,"737":1,"751":2,"756":2,"761":1,"762":1,"771":1,"772":1,"775":1,"779":1,"781":1,"782":1,"784":1,"786":1,"788":1,"790":1,"801":1,"802":1,"804":1,"806":1,"809":4,"812":1,"814":2,"817":1,"818":1,"819":1,"868":4,"869":1,"876":1,"882":1,"885":1,"887":1,"904":2,"915":6,"919":1,"957":4,"1016":3,"1017":2,"1019":1,"1037":1,"1052":1,"1055":1,"1067":2,"1070":1,"1074":1,"1097":3,"1100":1,"1102":4,"1105":4,"1109":1,"1110":1,"1133":2,"1139":3,"1150":3,"1185":1,"1188":1,"1189":1,"1204":1,"1237":1,"1239":1,"1341":2,"1357":1,"1358":3,"1362":1,"1379":1,"1386":1,"1395":3,"1396":1,"1398":4,"1399":2,"1410":1,"1412":2,"1415":3,"1426":1,"1431":3,"1470":1,"1472":2,"1473":1,"1477":7,"1501":1,"1518":1,"1519":1,"1520":1,"1521":1,"1523":3,"1524":1,"1525":2,"1526":1,"1527":3,"1529":2,"1543":1,"1544":5,"1546":1,"1561":2,"1567":1,"1569":4,"1573":1,"1616":1,"1664":1,"1687":1,"1723":3,"1727":1,"1733":4,"1738":3,"1759":3,"1788":1,"1792":100,"1795":1,"1806":1,"1836":1,"1841":1,"1844":1,"1846":1,"1848":3,"1849":3,"1852":2,"1862":1,"1863":1,"1887":1,"1888":1,"1901":1,"1912":3,"1917":1,"1918":7,"1921":1,"1922":3,"1923":4,"1924":4,"1925":2,"1967":1,"1968":2,"2007":1,"2034":1,"2047":1,"2056":1,"2079":1,"2124":2,"2129":1,"2131":1,"2137":1,"2138":1,"2139":1,"2140":5,"2141":3,"2142":4,"2144":3,"2145":2,"2146":6,"2148":3,"2149":2,"2156":1,"2164":1,"2177":2,"2178":1,"2183":2,"2184":1,"2185":2,"2216":1,"2221":2,"2222":7,"2223":1,"2224":1,"2228":1,"2230":1,"2237":1,"2247":1,"2250":1,"2252":2,"2258":3,"2261":1,"2264":6,"2265":4,"2267":4,"2277":4,"2282":1,"2283":1,"2284":2,"2291":1,"2296":1,"2318":1,"2320":1,"2321":1,"2322":4,"2323":3,"2324":1,"2328":1,"2332":2,"2333":1,"2334":1,"2336":3,"2364":1,"2372":1,"2380":11,"2383":2,"2394":4,"2395":4,"2435":2,"2445":1,"2450":1,"2451":1,"2481":1,"2483":2,"2491":1,"2493":3,"2494":3,"2495":2,"2496":2,"2498":3,"2504":3,"2508":1,"2509":7,"2510":1,"2511":1,"2513":1,"2515":1,"2517":1,"2518":5,"2519":4,"2520":4,"2521":1,"2522":1,"2523":2,"2527":1,"2529":1,"2540":5,"2542":1,"2546":2,"2549":1,"2551":1,"2572":1,"2575":13,"2580":1,"2597":3,"2614":1,"2615":3,"2621":1,"2622":3,"2649":1,"2653":1,"2654":1,"2664":1,"2665":4,"2674":1,"2701":1,"2712":1,"2723":3,"2731":2,"2732":1,"2733":2,"2734":1,"2739":1,"2745":1,"2750":1,"2759":1,"2760":2,"2762":2,"2764":3,"2768":1,"2771":1,"2774":1,"2798":1,"2809":1,"2810":1,"2812":1,"2814":1,"2816":1,"2833":1,"2840":1,"2845":2,"2847":2,"2848":1,"2849":1,"2851":1,"2860":1}}],["parameters",{"0":{"31":1,"117":1,"154":1,"158":1,"183":1,"253":1,"255":1,"256":1,"257":1,"372":1,"374":1,"376":1,"377":1,"383":1,"394":1,"404":1,"406":1,"408":1,"447":1,"448":1,"454":1,"469":1,"520":1,"521":1,"527":1,"532":1,"544":1,"675":1,"720":1,"756":1,"761":1,"771":1,"780":1,"781":1,"794":1,"797":1,"813":1,"882":1,"915":1,"1300":1,"1341":1,"1392":1,"1395":1,"1473":1,"1477":1,"1501":1,"1544":1,"1547":1,"1561":1,"1575":1,"1616":1,"1687":1,"1759":1,"1806":1,"1846":1,"1912":1,"1922":1,"2129":1,"2131":1,"2183":1,"2277":1,"2285":1,"2286":1,"2292":1,"2321":1,"2322":1,"2332":1,"2333":1,"2348":1,"2394":1,"2496":1,"2520":1,"2540":1,"2555":1,"2591":1,"2653":1,"2665":1,"2730":1,"2731":1,"2844":1,"2845":1,"2846":1,"2849":1},"1":{"155":1,"156":1,"157":1,"158":1,"159":2,"160":2,"161":2,"162":2,"163":2,"164":1,"184":1,"254":1,"255":1,"256":1,"257":1,"258":1,"378":1,"379":1,"380":1,"381":1,"405":1,"406":1,"407":1,"408":1,"448":1,"449":1,"528":1,"529":1,"530":1,"531":1,"532":1,"533":1,"534":1,"535":1,"781":1,"782":1,"783":1,"784":1,"785":1,"786":1,"787":1,"788":1,"789":1,"795":1,"796":1,"797":1,"798":1,"799":1,"800":1,"801":1,"802":1,"803":1,"804":1,"805":1,"806":1,"883":1,"884":1,"1301":1,"1478":1,"1545":1,"1546":1,"1547":1,"1847":1,"2731":1,"2732":1,"2733":1,"2734":1,"2845":1,"2846":1,"2847":1,"2848":1,"2849":1},"2":{"31":1,"43":2,"66":2,"105":1,"106":1,"107":1,"108":1,"113":1,"120":1,"155":1,"156":1,"159":1,"160":2,"164":1,"165":6,"167":1,"168":4,"170":4,"184":2,"188":1,"190":2,"209":1,"215":1,"216":2,"226":1,"228":1,"236":2,"237":1,"238":1,"240":1,"253":1,"258":3,"306":4,"310":1,"315":1,"316":2,"322":1,"362":1,"369":1,"372":1,"374":1,"376":5,"377":2,"380":4,"381":2,"382":1,"384":2,"385":2,"387":2,"388":2,"390":3,"393":1,"394":1,"396":3,"408":2,"409":3,"423":1,"436":2,"438":1,"439":2,"447":1,"448":5,"452":1,"453":1,"454":1,"456":2,"458":1,"469":2,"515":1,"517":2,"524":1,"527":1,"528":2,"529":3,"535":3,"568":1,"595":1,"674":1,"679":1,"680":1,"690":1,"692":1,"725":1,"741":2,"760":1,"770":1,"780":1,"790":1,"794":1,"795":1,"800":1,"801":4,"803":2,"807":2,"821":2,"835":2,"876":1,"914":1,"915":4,"936":2,"937":3,"968":1,"1010":1,"1019":1,"1023":3,"1040":1,"1049":1,"1052":2,"1058":2,"1067":5,"1070":3,"1073":1,"1077":1,"1082":1,"1092":3,"1095":1,"1097":2,"1098":3,"1102":2,"1104":1,"1105":4,"1108":1,"1109":1,"1129":4,"1134":1,"1139":1,"1142":4,"1148":1,"1150":3,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1255":3,"1258":2,"1285":1,"1331":1,"1332":2,"1337":1,"1338":1,"1339":1,"1341":1,"1371":4,"1372":4,"1373":1,"1374":1,"1378":2,"1379":1,"1386":2,"1392":2,"1395":1,"1396":1,"1398":4,"1407":1,"1431":1,"1435":1,"1477":3,"1482":1,"1484":1,"1485":1,"1506":1,"1507":1,"1511":1,"1516":2,"1518":1,"1520":2,"1521":2,"1522":1,"1529":2,"1531":3,"1533":1,"1538":1,"1544":3,"1547":1,"1548":1,"1549":2,"1551":2,"1557":1,"1559":1,"1567":3,"1569":4,"1575":3,"1616":2,"1651":2,"1655":2,"1687":1,"1733":1,"1738":1,"1753":2,"1755":1,"1759":4,"1788":1,"1792":58,"1806":1,"1824":1,"1852":4,"1856":1,"1862":2,"1864":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1898":3,"1912":6,"1920":1,"1921":1,"1922":3,"1923":2,"1924":5,"1928":2,"1932":2,"1968":1,"2013":1,"2079":1,"2081":1,"2124":1,"2137":2,"2147":1,"2150":1,"2152":1,"2167":2,"2170":1,"2183":5,"2184":2,"2185":4,"2187":2,"2188":1,"2189":2,"2193":2,"2203":1,"2204":2,"2205":1,"2221":1,"2222":2,"2223":2,"2226":1,"2228":1,"2232":1,"2239":1,"2247":2,"2258":3,"2264":3,"2265":2,"2267":2,"2277":9,"2278":4,"2282":2,"2284":5,"2285":1,"2292":2,"2296":1,"2304":1,"2319":2,"2320":1,"2321":2,"2322":4,"2329":1,"2332":3,"2333":8,"2334":1,"2348":4,"2380":7,"2383":4,"2394":2,"2398":1,"2445":1,"2481":1,"2483":3,"2493":2,"2494":2,"2496":1,"2504":1,"2508":1,"2509":4,"2510":2,"2511":2,"2512":2,"2515":2,"2518":1,"2520":5,"2525":1,"2527":1,"2529":1,"2540":3,"2545":1,"2546":1,"2549":5,"2555":10,"2558":1,"2572":1,"2575":3,"2580":1,"2591":5,"2595":1,"2597":1,"2614":1,"2653":1,"2663":1,"2665":1,"2666":2,"2672":2,"2712":1,"2723":1,"2731":1,"2733":4,"2766":1,"2771":1,"2798":1,"2803":1,"2805":1,"2807":4,"2809":1,"2810":3,"2812":3,"2814":1,"2817":1,"2829":2,"2836":1,"2844":2,"2845":2,"2846":1,"2848":3,"2849":1,"2851":1,"2855":3,"2858":1,"2859":2,"2862":1}}],["parsepatterns",{"2":{"2371":1}}],["parseerrormode",{"2":{"2328":2}}],["parseenvironmentvariables",{"2":{"1603":1,"1604":1,"1605":1,"1607":1,"1608":2,"1615":1,"1792":2,"2040":1,"2272":2,"2477":1,"2497":1,"2645":2,"2688":1,"2719":1}}],["parseable",{"2":{"2271":1,"2491":1}}],["parseall",{"2":{"244":1,"1792":1,"1840":1,"1863":1,"2004":2,"2209":1,"2330":2,"2721":1,"2841":1}}],["parsecontentoptions",{"0":{"2476":1},"2":{"1792":1,"2033":1,"2034":1,"2037":1,"2040":1,"2042":1,"2224":1,"2371":1,"2476":1,"2483":1,"2551":1}}],["parses",{"2":{"746":2,"759":1,"769":1,"881":2,"1016":1,"1023":1,"1080":1,"1102":2,"1243":1,"1408":1,"1409":1,"1423":1,"1427":1,"1723":1,"1792":1,"1974":1,"2223":1,"2487":1,"2607":1,"2841":2}}],["parsequery",{"2":{"723":1,"894":1,"961":1,"1366":1,"1410":1,"1413":1,"1416":1,"1560":1,"1567":1,"1568":1,"1572":1,"1574":3,"1581":2,"1792":1,"2278":1}}],["parseurl",{"2":{"720":1,"1415":1,"1561":1}}],["parse",{"0":{"1427":1,"1431":1,"2038":1},"2":{"719":2,"720":2,"879":1,"894":2,"904":1,"1037":1,"1218":1,"1318":1,"1320":1,"1321":1,"1338":1,"1339":1,"1366":2,"1385":1,"1386":4,"1394":1,"1410":2,"1415":2,"1423":1,"1424":1,"1558":1,"1604":1,"1792":6,"1840":1,"2000":2,"2005":2,"2007":2,"2037":1,"2038":2,"2164":2,"2209":1,"2224":1,"2273":1,"2278":2,"2324":1,"2328":1,"2330":1,"2451":1,"2453":2,"2482":1,"2491":2,"2494":1,"2506":1,"2536":1,"2566":1,"2649":1,"2762":2,"2763":1,"2770":1,"2830":1,"2836":1,"2840":1}}],["parsed",{"2":{"244":1,"251":1,"324":1,"384":1,"646":1,"650":1,"760":1,"761":1,"807":1,"952":1,"1279":1,"1792":9,"1856":2,"1927":1,"2004":1,"2005":1,"2006":4,"2038":2,"2041":1,"2129":1,"2131":1,"2137":1,"2208":1,"2318":1,"2323":2,"2455":1,"2490":1,"2505":1,"2549":1,"2575":1,"2588":1,"2589":1,"2687":1,"2840":1}}],["parsertests",{"2":{"2546":1}}],["parsers",{"0":{"2451":1},"2":{"1856":2,"2224":1,"2450":2,"2451":5,"2453":1,"2455":2,"2456":2,"2457":1,"2546":1}}],["parserequest",{"2":{"720":1,"1063":4,"1415":1,"1561":1}}],["parser",{"2":{"1":1,"369":1,"378":1,"886":1,"904":1,"1073":1,"1080":1,"1248":1,"1378":1,"1385":1,"1386":4,"1388":1,"1428":1,"1431":1,"2191":1,"2194":1,"2336":1,"2371":1,"2453":1,"2456":1,"2476":1,"2621":2,"2816":1}}],["parsing",{"0":{"379":1,"1270":1,"1294":1,"1607":1,"2037":1},"1":{"1295":1,"2038":1,"2039":1,"2040":1,"2041":1},"2":{"133":1,"277":1,"779":1,"788":3,"834":1,"879":2,"880":1,"891":1,"910":2,"1086":1,"1094":2,"1098":1,"1102":1,"1127":2,"1211":2,"1218":1,"1254":1,"1255":3,"1258":2,"1264":1,"1270":2,"1275":1,"1279":1,"1285":1,"1386":1,"1396":1,"1431":2,"1433":1,"1608":1,"1746":1,"1792":5,"1868":2,"2032":1,"2034":1,"2038":2,"2042":1,"2108":1,"2149":1,"2212":1,"2267":1,"2270":2,"2272":1,"2273":1,"2333":1,"2347":1,"2398":1,"2402":1,"2435":1,"2474":1,"2490":1,"2566":1,"2600":1,"2627":1,"2629":1,"2688":1,"2705":1,"2795":1}}],["party",{"0":{"1225":1,"1875":1},"2":{"864":1,"868":1,"876":1,"1035":1,"1097":1,"1098":2,"1099":1,"1104":2,"1209":1,"1225":1,"1243":1,"1329":1,"1335":1,"1572":1,"1792":2,"1875":1,"2427":1,"2759":1}}],["partly",{"2":{"849":5,"860":1}}],["particular",{"2":{"1403":1}}],["particularly",{"2":{"859":1,"974":1,"1073":1,"1516":1,"2265":1,"2271":1,"2338":1}}],["participates",{"2":{"533":1,"1792":1,"2795":1}}],["participate",{"2":{"384":1,"699":1,"2286":1,"2531":1,"2869":1}}],["participating",{"2":{"165":1}}],["partial",{"2":{"857":1,"879":1,"901":1,"918":1,"1097":1,"1792":1,"2007":2,"2107":1,"2320":1,"2328":1,"2372":1,"2537":1}}],["partitioning",{"2":{"1162":1}}],["partitions",{"0":{"1162":1},"2":{"848":1}}],["partition",{"0":{"1955":1,"1956":1,"2379":1},"1":{"1956":1,"1957":1},"2":{"479":1,"480":1,"860":1,"1069":4,"1101":2,"1121":1,"1127":1,"1162":4,"1792":7,"1951":2,"1952":2,"1953":2,"1954":2,"1955":3,"1956":2,"1957":3,"1958":1,"1959":1,"1960":1,"2379":6,"2438":2,"2441":1,"2443":5,"2447":1,"2470":1,"2471":1}}],["partitioned",{"2":{"479":1,"835":1,"1101":3,"1792":2,"1955":1,"1957":1,"1959":1,"2379":3,"2438":1,"2471":1}}],["parts",{"2":{"387":1,"843":2,"873":1,"1115":1,"1303":1,"1402":1,"1435":1,"2171":1}}],["partners",{"2":{"836":1,"837":1,"2419":1,"2434":1,"2438":1}}],["partner",{"0":{"1911":1,"2430":1,"2434":1,"2438":1},"1":{"2431":1,"2432":1,"2433":1,"2434":1},"2":{"348":3,"352":11,"354":6,"1792":3,"1909":2,"1911":8,"2419":1,"2430":3,"2432":6,"2434":9,"2438":8}}],["part",{"0":{"1377":1},"2":{"1":1,"113":1,"165":1,"167":1,"448":1,"836":1,"843":1,"851":5,"860":1,"861":1,"873":1,"916":1,"917":1,"920":2,"947":1,"1035":1,"1076":1,"1080":1,"1135":1,"1209":1,"1403":2,"1472":1,"1568":1,"2171":1,"2322":1,"2435":1,"2486":1,"2830":1}}],["prm",{"2":{"1792":2,"1825":2,"1828":1,"1830":1,"1833":1,"2481":1,"2498":1}}],["pragma",{"2":{"1792":1,"2033":1,"2037":1,"2041":1,"2551":1}}],["practical",{"2":{"988":1,"1102":1,"1133":1,"1135":1,"1393":1,"1792":2,"2537":1}}],["practice",{"0":{"1421":1},"2":{"175":1,"209":1,"847":1,"861":1,"871":1,"873":1,"876":1,"933":1,"936":1,"948":1,"1096":1,"1208":1,"1368":1,"1393":1,"2389":1,"2424":1}}],["pruned",{"2":{"1722":1,"1792":2,"2502":1,"2769":1}}],["prune",{"2":{"1511":1,"2803":1}}],["prs",{"2":{"1254":1}}],["preamble",{"2":{"2868":1}}],["preachy",{"2":{"1404":1}}],["preauthorize",{"2":{"1366":1}}],["pretty",{"2":{"1401":1,"1435":1}}],["pretend",{"2":{"844":1}}],["premises",{"2":{"1094":1}}],["premium",{"2":{"1033":4}}],["prejudice",{"2":{"913":1,"918":2}}],["preliminary",{"2":{"854":1}}],["pressing",{"2":{"2362":1}}],["pressure",{"2":{"919":1,"920":1,"951":1,"2397":1,"2398":2,"2604":1}}],["prescribes",{"2":{"1045":1}}],["presumably",{"2":{"844":1}}],["preserving",{"2":{"917":1,"1141":1,"1351":1,"2435":1}}],["preservation",{"2":{"903":1,"2435":1,"2498":1}}],["preserves",{"2":{"2678":1,"2695":1}}],["preserved",{"2":{"1102":1,"1408":1,"1605":1,"2405":1,"2435":2,"2497":1,"2504":1,"2587":1,"2588":1,"2589":1,"2607":1,"2688":1}}],["preserve",{"2":{"348":1,"1097":1,"1792":1,"1851":1,"1973":1,"2382":1,"2432":1,"2580":1}}],["presence",{"2":{"369":1,"439":1,"448":1}}],["present",{"0":{"2424":1},"2":{"101":1,"106":1,"214":1,"301":1,"309":1,"319":2,"324":1,"436":1,"869":1,"986":1,"1078":1,"1470":1,"1743":1,"1792":14,"1823":1,"1824":2,"2002":2,"2371":1,"2416":1,"2421":1,"2424":1,"2435":1,"2481":3,"2490":1,"2491":1,"2541":1,"2551":1,"2811":1,"2815":1}}],["precondition",{"2":{"876":1}}],["precompiled",{"2":{"874":1}}],["precise",{"2":{"852":1,"863":1,"864":1,"868":1,"1419":1,"1422":1,"1428":1,"2394":1}}],["precisely",{"2":{"851":1,"1402":1}}],["precision",{"2":{"585":1,"956":1}}],["precedence",{"0":{"319":1,"2424":1,"2697":1},"2":{"1785":1,"1792":2,"1862":1,"2040":1,"2380":1,"2476":1,"2481":1,"2483":1,"2680":1,"2681":1,"2691":1}}],["preprocessing",{"2":{"1343":1}}],["prepended",{"2":{"446":1,"1929":1}}],["preparecommand",{"2":{"2559":1,"2615":1}}],["prepared",{"2":{"427":1,"1130":1,"2099":1,"2527":1,"2534":1,"2862":1}}],["prepare",{"2":{"426":1,"428":1}}],["prepares",{"2":{"414":1,"1105":1,"2300":1,"2419":1}}],["predicting",{"2":{"1401":1}}],["predictable",{"2":{"1096":1,"1255":1,"2393":1}}],["predict",{"2":{"427":4}}],["predefined",{"2":{"159":1,"1013":1,"1390":1}}],["prefs",{"2":{"1029":1,"1347":3}}],["preflightmaxageseconds",{"2":{"1638":1,"1639":1,"1645":1,"1646":2,"1792":1}}],["preflight",{"0":{"1645":1},"2":{"545":1,"1639":1,"1645":2,"1792":1,"2429":1,"2627":1}}],["prefers",{"2":{"2858":1}}],["preferstandby",{"2":{"1174":1,"1176":1,"1177":1,"1628":1,"1629":1,"1792":1,"2266":2}}],["preference",{"2":{"1792":1}}],["preferences",{"2":{"251":1,"452":2,"1029":4,"1347":2}}],["preferred",{"2":{"1217":2,"1228":1,"1229":1,"1792":5,"1878":1,"1879":1,"2486":1}}],["preferprimary",{"2":{"1174":1,"1177":1,"1628":1,"1629":1,"1792":1,"2266":2}}],["prefer",{"2":{"1094":1,"1096":1,"1121":1,"1122":1,"1123":1,"1127":1,"1616":1,"1792":1,"1823":1,"2177":1,"2193":1,"2769":1}}],["preferably",{"2":{"319":1,"920":1}}],["prefixing",{"2":{"1415":1}}],["prefixes",{"2":{"259":1,"374":1,"410":1,"1576":1,"1787":1,"1794":1,"2270":1}}],["prefixed",{"2":{"108":1,"768":1,"776":1,"915":1,"1522":1,"2380":1}}],["prefix",{"0":{"2193":1,"2365":1,"2581":1,"2591":1},"2":{"4":1,"13":1,"29":1,"44":1,"55":1,"68":1,"78":1,"91":1,"125":1,"132":1,"144":1,"155":3,"165":1,"192":1,"211":1,"245":1,"261":1,"282":1,"296":1,"328":1,"339":1,"357":1,"369":1,"412":1,"433":1,"449":1,"458":1,"473":1,"484":1,"497":1,"507":1,"515":1,"549":1,"567":1,"569":1,"589":1,"598":1,"607":1,"618":1,"627":1,"636":1,"649":1,"683":1,"794":1,"823":1,"915":1,"924":1,"976":2,"985":1,"988":1,"1005":1,"1067":1,"1522":1,"1555":2,"1731":1,"1792":5,"1841":1,"2000":1,"2008":1,"2193":10,"2197":1,"2207":1,"2237":1,"2279":1,"2301":1,"2320":1,"2330":1,"2365":3,"2370":1,"2380":1,"2435":1,"2529":1,"2581":5,"2591":5,"2723":1,"2841":1,"2865":1}}],["previews",{"2":{"959":1}}],["preview",{"2":{"959":1,"961":1,"1257":4,"1336":1,"1338":1,"1339":1}}],["previously",{"2":{"892":1,"904":1,"1066":1,"1071":1,"1792":1,"1851":1,"1925":1,"1948":1,"1958":1,"2282":1,"2287":1,"2297":1,"2313":1,"2314":1,"2352":1,"2362":1,"2367":1,"2378":1,"2382":1,"2402":1,"2405":1,"2413":1,"2419":1,"2430":1,"2446":1,"2468":1,"2484":1,"2491":1,"2492":1,"2497":1,"2505":1,"2510":1,"2517":1,"2518":2,"2532":1,"2539":1,"2544":1,"2580":1,"2588":1,"2641":1,"2645":1,"2664":1,"2801":1}}],["previous",{"0":{"1260":1},"2":{"214":1,"563":1,"760":1,"761":1,"770":1,"771":1,"851":1,"854":1,"860":1,"861":2,"880":1,"882":1,"883":1,"885":1,"901":1,"1048":1,"1049":1,"1256":1,"1257":1,"1260":1,"1342":1,"1792":2,"2129":1,"2131":1,"2160":1,"2258":1,"2267":1,"2354":1,"2454":1,"2537":1,"2551":1,"2615":1}}],["prev",{"2":{"760":1,"764":2,"765":2,"766":2,"770":1,"774":2,"883":2,"884":2,"885":5,"888":2,"904":2,"2572":1}}],["prevented",{"2":{"2626":1}}],["prevention",{"2":{"2546":1}}],["preventing",{"2":{"1149":1,"1159":1,"2284":1,"2352":1}}],["preventdefault",{"2":{"961":1}}],["prevents",{"2":{"737":1,"1147":1,"1180":1,"1185":1,"1515":2,"1792":6,"2016":2,"2017":1,"2018":1,"2024":1,"2216":1,"2274":1,"2380":1,"2607":1,"2615":1,"2632":4,"2634":1,"2841":1}}],["prevent",{"2":{"595":1,"1138":1,"1160":1,"1232":1,"1250":1,"1398":1,"1493":1,"1709":1,"1717":2,"1769":2,"1792":7,"2016":1,"2060":1,"2061":1,"2127":1,"2265":1,"2372":1,"2558":1,"2632":1,"2633":1,"2634":1,"2635":1}}],["prevalent",{"2":{"1384":1}}],["preval",{"2":{"38":2}}],["prerequisites",{"0":{"2161":1,"2819":1}}],["prerequisite",{"2":{"182":1,"1596":1,"1792":1,"2291":1}}],["pre",{"0":{"38":1},"2":{"31":1,"308":1,"868":1,"951":1,"1015":1,"1051":1,"1069":1,"1102":8,"1104":1,"1106":2,"1123":1,"1166":1,"1168":1,"1197":1,"1343":1,"1522":1,"1609":1,"1690":2,"1696":3,"1792":1,"1850":1,"1856":1,"2177":1,"2224":1,"2372":1,"2381":1,"2422":1,"2435":3,"2455":2,"2461":1,"2482":2,"2550":1,"2621":1,"2669":1,"2785":1,"2791":1,"2792":1}}],["privacy",{"0":{"1251":1},"2":{"1792":1}}],["private",{"0":{"324":1,"855":1},"2":{"3":1,"695":1,"705":1,"715":1,"852":1,"855":1,"857":2,"864":2,"1138":3,"1210":3,"1312":2,"1435":1,"1571":1,"1661":1,"1708":1,"1792":1,"1987":1,"2207":1,"2372":1,"2484":1,"2490":1,"2559":1,"2633":1,"2873":1}}],["privileged",{"2":{"932":1,"1045":2,"2834":2}}],["privileges",{"2":{"922":1,"926":2,"932":3,"933":2,"2608":1,"2721":1}}],["privilege",{"0":{"922":1,"1185":1,"2876":1},"2":{"921":1,"922":1,"1037":1,"1065":1,"1111":1,"1185":1,"1441":1,"1443":1,"1669":1,"1673":1,"1674":1,"1678":1,"1792":1,"2164":1,"2255":2,"2608":1,"2755":1}}],["prioritize",{"2":{"933":1}}],["priority",{"2":{"52":1,"319":2,"422":1,"446":1,"1929":1,"2266":1,"2481":1}}],["prior",{"2":{"919":1,"2506":1}}],["pride",{"2":{"913":1,"918":2}}],["primitive",{"2":{"2424":1,"2586":1,"2588":1}}],["primitives",{"2":{"874":1,"1075":1,"1082":1}}],["primary|standby|any|preferprimary|preferstandby|readwrite|readonly",{"2":{"1792":1}}],["primary",{"2":{"764":1,"774":1,"852":1,"913":3,"924":1,"977":2,"1050":1,"1054":1,"1075":1,"1086":1,"1098":1,"1173":1,"1174":4,"1176":2,"1177":1,"1178":2,"1213":3,"1307":2,"1336":1,"1355":1,"1385":3,"1414":1,"1628":4,"1629":2,"1655":1,"1792":5,"2020":1,"2195":1,"2266":5,"2438":1,"2774":1,"2836":1,"2868":1}}],["primarily",{"2":{"0":1,"669":1}}],["prices",{"2":{"1010":1,"1011":1,"1018":1,"1020":2,"1021":2,"1024":3,"1376":1,"1431":4}}],["price",{"0":{"1425":1},"1":{"1426":1,"1427":1,"1428":1},"2":{"814":3,"845":1,"863":1,"864":3,"876":1,"898":3,"1019":3,"1021":2,"1023":1,"1026":1,"1038":4,"1044":1,"1187":1,"1188":1,"1189":1,"1192":2,"1373":1,"1376":3,"1398":7,"1423":2,"1427":9,"1429":15,"1430":1,"1431":5,"1435":1,"1436":1,"1437":2,"1442":1,"1571":1,"2164":1,"2214":1,"2726":1,"2760":2,"2762":8,"2766":5}}],["printing",{"2":{"2878":1}}],["prints",{"2":{"2104":1,"2110":1,"2415":1,"2530":1,"2537":1,"2543":1,"2800":1,"2857":1}}],["print",{"2":{"1792":1,"2416":1,"2754":1}}],["printed",{"2":{"1792":1,"2102":1,"2800":1}}],["printable",{"2":{"747":2,"753":2,"757":2,"776":1,"782":3,"784":3,"786":3,"1792":2,"2125":1}}],["principle",{"0":{"922":1,"1185":1},"2":{"921":1,"922":1,"1037":1,"1065":1,"1185":1,"1385":1,"1441":1,"2164":1}}],["principals",{"2":{"2221":1}}],["principal",{"2":{"239":1,"305":1,"689":1,"690":2,"691":1,"692":1,"1792":5,"1825":2,"1827":1,"1832":1,"1961":1,"2181":1,"2183":1,"2423":1,"2438":1,"2481":1,"2529":2,"2733":1,"2865":2}}],["proud",{"2":{"1404":1}}],["proudly",{"2":{"865":1}}],["proclaim",{"2":{"1393":1}}],["proceeds",{"2":{"2415":1}}],["proceed",{"2":{"1214":1,"1228":1,"1232":1,"1234":1,"1236":1,"1386":2,"1792":1,"1882":1,"1884":1,"1886":1}}],["procedural",{"2":{"876":1,"1377":1,"1378":2,"1394":5,"1396":3,"1972":1,"2855":1,"2858":1}}],["procedures",{"0":{"2775":1},"2":{"175":2,"265":1,"338":1,"369":1,"615":1,"650":1,"663":1,"668":1,"684":2,"829":1,"835":1,"994":1,"1037":1,"1086":1,"1095":1,"1113":1,"1305":1,"1385":4,"1405":1,"1435":2,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1558":1,"1792":3,"1965":1,"1967":1,"2106":1,"2156":2,"2164":1,"2190":1,"2273":1,"2317":1,"2325":1,"2332":1,"2338":1,"2344":1,"2350":1,"2388":1,"2389":2,"2391":1,"2395":1,"2542":2,"2709":1,"2713":1,"2772":1,"2774":1,"2775":1,"2802":1}}],["procedure",{"0":{"663":1},"1":{"664":1,"665":1,"666":1},"2":{"173":1,"175":1,"179":1,"220":1,"241":1,"296":1,"310":3,"374":1,"650":9,"663":4,"664":6,"667":1,"668":2,"669":3,"684":2,"828":1,"1054":1,"1056":2,"1073":1,"1077":1,"1128":1,"1302":1,"1305":4,"1309":4,"1312":1,"1313":2,"1314":1,"1315":1,"1321":2,"1396":1,"1655":2,"1792":1,"1973":1,"2010":1,"2047":1,"2329":1,"2332":1,"2391":5,"2392":2,"2394":1,"2407":2,"2504":1,"2635":2,"2712":1,"2803":2,"2827":1,"2828":1,"2829":2,"2830":2,"2834":4,"2836":2}}],["processpath",{"2":{"2543":1}}],["processed",{"0":{"1705":1},"2":{"772":1,"773":1,"886":1,"903":1,"1335":1,"1339":2,"1603":1,"1686":1,"1688":2,"1703":1,"1706":1,"1792":4,"1844":1,"1915":1,"1927":1,"2549":2,"2633":1,"2705":1}}],["processes",{"0":{"1418":1},"2":{"746":2,"759":1,"769":1,"788":1,"844":1,"868":1,"886":1,"902":1,"1105":1,"1106":1,"1324":2,"1358":1,"1359":1,"1376":2,"1418":1,"1706":1,"2128":1,"2130":1,"2157":1,"2543":1,"2611":1,"2833":1}}],["processorder",{"2":{"2357":1}}],["processors",{"2":{"1035":1}}],["processor",{"2":{"421":2,"423":1,"760":1,"770":1,"1792":1,"2094":1,"2099":1,"2527":1,"2537":1,"2862":1}}],["processing",{"0":{"594":1,"878":1,"899":1,"2270":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1},"2":{"160":1,"414":2,"435":1,"438":1,"565":2,"747":2,"768":1,"771":1,"780":1,"781":4,"782":2,"784":2,"786":4,"788":3,"791":1,"878":1,"903":1,"907":1,"1037":2,"1099":2,"1100":1,"1105":3,"1154":1,"1167":1,"1179":1,"1328":1,"1334":1,"1358":1,"1396":1,"1684":1,"1701":1,"1787":1,"1788":1,"1794":1,"1802":1,"1917":1,"1921":1,"1974":1,"2120":1,"2125":2,"2128":1,"2134":1,"2164":1,"2165":1,"2239":1,"2270":2,"2300":2,"2320":2,"2607":1,"2633":1,"2645":1,"2648":1,"2649":1,"2664":1,"2774":1,"2795":1,"2813":1,"2814":1}}],["process",{"0":{"902":1,"1332":1},"1":{"903":1},"2":{"71":4,"138":1,"214":1,"239":1,"277":1,"390":1,"421":1,"439":1,"445":1,"480":1,"510":3,"565":2,"577":1,"594":2,"614":1,"621":1,"631":1,"632":1,"633":1,"646":3,"650":2,"658":5,"659":1,"662":6,"689":1,"691":1,"764":1,"768":1,"774":1,"776":3,"779":1,"788":1,"828":1,"844":2,"852":6,"855":1,"864":3,"865":1,"868":1,"874":1,"876":1,"888":3,"891":1,"892":3,"899":1,"900":1,"902":1,"911":1,"922":1,"986":1,"1014":1,"1021":2,"1037":1,"1049":1,"1073":1,"1074":2,"1078":1,"1080":1,"1081":1,"1094":1,"1121":1,"1130":1,"1154":3,"1179":2,"1200":1,"1305":1,"1379":1,"1398":1,"1401":1,"1404":1,"1406":1,"1599":1,"1703":1,"1743":1,"1746":1,"1768":2,"1774":1,"1781":1,"1792":20,"1856":1,"1911":1,"1927":1,"1928":1,"1929":1,"1954":1,"1961":1,"2007":1,"2092":1,"2106":2,"2112":1,"2123":2,"2128":3,"2130":5,"2153":1,"2157":1,"2158":1,"2167":1,"2221":3,"2270":1,"2274":1,"2320":2,"2328":1,"2330":1,"2337":1,"2346":1,"2347":1,"2367":1,"2372":2,"2402":1,"2434":1,"2438":2,"2450":1,"2466":2,"2476":1,"2502":1,"2525":1,"2527":2,"2529":1,"2532":2,"2533":1,"2537":3,"2541":1,"2542":1,"2543":1,"2549":3,"2572":1,"2633":1,"2634":2,"2649":1,"2664":1,"2679":2,"2739":1,"2742":1,"2767":1,"2774":1,"2795":1,"2800":1,"2811":1,"2828":2,"2832":1,"2860":1,"2865":1,"2874":1,"2878":1,"2881":1}}],["prone",{"2":{"1378":1}}],["profiling",{"2":{"2088":1}}],["profile`",{"2":{"1792":2}}],["profiled",{"2":{"1421":1}}],["profiles`",{"2":{"1792":1}}],["profiles",{"0":{"1066":1,"1150":1,"1519":1,"2380":2,"2445":1},"1":{"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1520":1,"1521":1,"1522":1,"1523":1,"1524":1,"1525":1,"1526":1,"1527":1,"1528":1,"1529":1,"2381":2},"2":{"101":1,"102":1,"104":1,"106":1,"107":1,"108":2,"110":1,"815":1,"869":1,"1037":1,"1067":4,"1101":2,"1150":6,"1510":1,"1511":3,"1519":1,"1520":1,"1521":1,"1522":2,"1528":1,"1529":1,"1792":6,"1948":1,"2153":1,"2225":1,"2378":1,"2380":8,"2381":2,"2440":1,"2442":1,"2445":1,"2532":1,"2541":1}}],["profile",{"0":{"101":1,"711":1,"969":1,"1521":1,"1533":1,"2380":1},"1":{"102":1,"103":1,"104":1,"105":1,"106":1,"107":1,"108":1,"109":1,"110":1,"111":1,"2381":1},"2":{"16":3,"19":2,"101":4,"102":3,"104":3,"105":5,"106":2,"107":1,"108":8,"109":5,"110":2,"111":1,"116":2,"123":2,"230":2,"251":3,"374":4,"376":2,"706":2,"711":4,"800":1,"815":2,"860":2,"868":2,"869":1,"873":1,"1067":5,"1101":1,"1142":3,"1150":11,"1398":8,"1399":5,"1406":1,"1511":2,"1519":1,"1520":4,"1521":1,"1522":5,"1523":1,"1525":1,"1527":9,"1529":5,"1533":5,"1535":2,"1691":1,"1695":1,"1792":11,"1842":2,"2036":1,"2097":1,"2106":1,"2167":1,"2187":4,"2205":1,"2225":1,"2314":1,"2327":2,"2380":15,"2381":6,"2400":1,"2445":3,"2531":1,"2533":1,"2537":3,"2545":1,"2546":1,"2580":2,"2869":2,"2873":1}}],["proficient",{"2":{"1405":1}}],["profit",{"2":{"1203":1}}],["profound",{"2":{"913":1,"919":2}}],["prod",{"0":{"1417":1},"1":{"1418":1,"1419":1,"1420":1,"1421":1},"2":{"868":2,"873":1,"1037":1,"1418":1,"1420":1}}],["producing",{"2":{"1792":1,"1917":1,"2222":1,"2517":1,"2588":1,"2696":1,"2812":1}}],["produced",{"2":{"864":1,"874":1,"1417":1,"1567":1,"1825":1,"2170":1,"2171":1,"2384":1,"2415":1}}],["produce",{"2":{"386":1,"567":2,"587":1,"871":1,"930":2,"1135":1,"1193":1,"1373":1,"1412":1,"1792":2,"1925":1,"2265":1,"2297":1,"2337":1,"2340":1,"2348":1,"2456":1,"2589":2,"2679":1,"2696":1}}],["produces",{"2":{"304":1,"308":1,"348":1,"586":1,"872":1,"971":1,"1406":2,"1431":1,"1856":1,"2180":1,"2319":2,"2358":1,"2391":1,"2529":1,"2648":1,"2842":1}}],["productid",{"2":{"1044":1,"1045":1}}],["productivity",{"0":{"871":1},"1":{"872":1},"2":{"871":1,"1382":1,"1385":1,"1386":1,"1404":1}}],["production",{"0":{"1177":1,"1204":1,"1420":1,"1581":1,"1779":1,"2067":1,"2804":1},"2":{"2":1,"121":1,"307":1,"690":1,"837":1,"838":1,"864":1,"865":1,"866":1,"867":2,"868":1,"872":1,"875":1,"877":2,"926":1,"947":1,"967":1,"972":1,"983":1,"997":1,"1001":1,"1005":1,"1009":1,"1037":5,"1049":1,"1054":1,"1064":1,"1065":1,"1066":1,"1067":1,"1071":1,"1111":1,"1135":1,"1146":1,"1172":1,"1179":1,"1198":1,"1204":1,"1205":2,"1206":1,"1220":1,"1335":1,"1381":2,"1382":2,"1383":1,"1402":2,"1412":1,"1414":1,"1417":1,"1420":5,"1448":1,"1457":1,"1483":1,"1502":1,"1505":1,"1534":1,"1598":1,"1615":1,"1633":1,"1641":1,"1646":1,"1658":1,"1663":1,"1664":1,"1678":1,"1716":1,"1792":5,"1799":1,"1810":1,"1811":1,"1863":1,"1870":1,"1875":1,"1900":1,"1907":2,"1911":1,"1931":1,"1944":1,"1983":1,"1995":1,"2007":1,"2052":1,"2059":1,"2080":1,"2089":1,"2116":1,"2117":2,"2132":1,"2157":1,"2177":1,"2254":1,"2297":1,"2328":1,"2346":1,"2351":1,"2384":2,"2393":1,"2434":1,"2452":1,"2492":1,"2529":1,"2543":1,"2635":1,"2684":2,"2701":1,"2702":2,"2750":1,"2772":1,"2798":1,"2804":2,"2868":1,"2876":1}}],["productname",{"2":{"814":2}}],["products",{"2":{"9":1,"250":6,"254":2,"255":2,"256":2,"257":2,"814":1,"837":1,"898":1,"1038":3,"1042":1,"1044":1,"1045":1,"1138":2,"1139":6,"1179":1,"1429":1,"1531":3,"1532":2,"1575":2,"2214":1,"2277":10,"2555":3,"2726":1}}],["product",{"2":{"1":1,"9":1,"254":2,"256":2,"257":2,"324":2,"814":5,"836":1,"852":1,"866":1,"872":1,"877":1,"948":1,"1037":1,"1038":1,"1044":1,"1045":3,"1138":3,"1179":2,"1187":1,"1188":1,"1189":1,"1191":3,"1192":2,"1373":1,"1381":1,"1382":7,"1383":1,"1424":1,"1427":1,"1429":1,"1432":1,"1531":1,"2164":1,"2214":1,"2277":6,"2389":1}}],["proof",{"2":{"857":1,"877":1,"1792":1}}],["prominently",{"2":{"1823":1,"2533":1}}],["promised",{"2":{"859":1}}],["promise",{"2":{"429":2,"615":2,"894":2,"938":2,"995":2,"1024":1,"1026":1,"1108":1,"1317":1,"1342":1,"1366":2,"1386":1,"1408":2,"1410":2,"1416":2,"1567":1,"1568":1,"1569":1,"1571":1,"2247":2,"2310":2,"2313":3,"2339":2,"2357":1,"2359":2,"2389":1}}],["promoteallscalars=true",{"2":{"1202":1}}],["promoteheaders",{"2":{"1202":1}}],["promotedheaders",{"2":{"1202":2}}],["promoted",{"2":{"851":1}}],["prompts",{"2":{"1202":1,"1401":1}}],["prompted",{"2":{"1200":1,"1832":1,"1833":1}}],["prompting",{"2":{"1068":1,"1400":1}}],["prompt",{"2":{"1044":1,"1105":2,"1792":1}}],["provoking",{"2":{"913":1,"919":2}}],["proving",{"2":{"2490":1,"2545":1,"2546":1}}],["provisioning",{"2":{"1170":1}}],["provisional",{"2":{"855":1}}],["providing",{"2":{"463":1,"464":1,"986":1,"1185":1,"1188":1,"1866":1}}],["provider",{"0":{"1696":1},"1":{"1697":1},"2":{"1050":1,"1056":1,"1058":1,"1059":1,"1060":9,"1088":1,"1204":1,"1684":1,"1685":3,"1687":2,"1689":7,"1690":2,"1696":4,"1697":1,"1792":4,"2496":1}}],["providers",{"0":{"1048":1,"1059":1,"1690":1,"1697":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":2,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1},"2":{"11":1,"25":1,"26":1,"28":1,"295":1,"1037":1,"1048":2,"1050":1,"1060":1,"1064":1,"1098":4,"1445":1,"1466":1,"1682":1,"1684":1,"1690":2,"1696":4,"1697":1,"1792":2,"1795":1,"1895":1,"2164":1,"2175":1,"2189":1,"2219":1,"2736":1}}],["provide",{"2":{"319":1,"364":1,"527":1,"587":1,"851":1,"975":1,"1096":1,"1229":1,"1335":1,"1394":1,"1515":1,"1664":1,"1792":1,"1879":1,"2274":1}}],["provides",{"2":{"308":1,"363":1,"369":1,"375":1,"436":1,"534":1,"841":2,"873":1,"918":1,"994":1,"1015":1,"1100":4,"1107":1,"1140":1,"1147":1,"1151":1,"1211":1,"1218":1,"1223":1,"1335":1,"1343":1,"1398":1,"1446":1,"1450":1,"1453":1,"1511":1,"1620":1,"1765":1,"1792":5,"1840":1,"1868":1,"1940":1,"1974":1,"2045":1,"2160":1,"2164":1,"2168":1,"2177":1,"2332":1,"2531":1,"2581":1,"2607":1,"2614":1,"2634":1,"2635":1,"2701":1,"2772":1}}],["provided",{"2":{"63":1,"377":1,"408":1,"422":1,"462":2,"639":1,"646":2,"864":1,"869":1,"1234":2,"1252":1,"1620":1,"1884":1,"2247":2,"2333":1,"2665":1,"2848":1}}],["proves",{"2":{"1228":1,"1792":2,"1878":1,"2876":1}}],["proven",{"2":{"1122":1,"2167":1}}],["prove",{"2":{"844":1,"851":1}}],["probe",{"0":{"1767":1,"1768":1,"1774":1},"2":{"1764":2,"1770":1,"1774":1,"1792":5,"2459":1,"2461":1,"2466":1,"2634":7}}],["probes",{"2":{"868":2,"869":1,"1351":1,"1774":1,"1781":1,"1791":1,"1792":3,"1898":1,"2431":1,"2434":1,"2634":1}}],["probablybinary",{"2":{"1360":1}}],["probably",{"2":{"841":1,"868":1,"1073":1,"1129":1,"1395":1,"1396":1,"1401":1,"1403":1,"1405":1,"2391":1}}],["problemdetails",{"2":{"2558":1}}],["problems",{"2":{"857":1,"918":1,"1001":1,"1014":1,"1041":1,"1079":1,"1096":1,"1394":1,"1402":1,"1404":1,"2384":1}}],["problem",{"0":{"973":1,"1011":1,"1165":1,"1191":1,"1325":1,"1329":1},"2":{"109":1,"840":1,"841":6,"851":4,"852":3,"859":1,"861":1,"865":1,"879":1,"947":1,"1109":1,"1111":3,"1218":1,"1324":1,"1405":1,"1527":1,"1671":1,"1792":1,"2240":1,"2255":2,"2271":1,"2384":1,"2868":1}}],["programmatically",{"2":{"2148":1,"2389":1}}],["programmatic",{"0":{"2148":1},"2":{"1148":1,"1259":1,"2055":1,"2265":1,"2575":1,"2667":1}}],["programming",{"2":{"841":6,"843":2,"860":2,"1394":1}}],["program",{"2":{"867":1,"868":1,"869":1,"873":1,"922":1,"1403":1,"2409":1}}],["programs",{"2":{"848":1,"2438":1}}],["progressively",{"2":{"2160":1}}],["progressbar",{"2":{"894":1,"1361":1,"1410":1}}],["progress",{"2":{"641":1,"646":1,"658":1,"659":1,"868":1,"878":1,"880":1,"894":6,"907":1,"1037":2,"1352":1,"1361":1,"1366":7,"1367":1,"1410":8,"2164":1,"2833":1}}],["propagation",{"0":{"2615":1},"2":{"1181":1,"2236":1,"2576":1,"2615":3}}],["propagate",{"0":{"984":1},"2":{"1005":1,"1203":1,"1436":1,"2615":1}}],["propagated",{"2":{"974":1}}],["proprietary",{"2":{"1054":1,"1098":1,"1445":1,"1450":1,"1457":1,"1792":1,"2554":1}}],["prophetic",{"2":{"913":1,"919":2}}],["proportional",{"2":{"1974":2,"2607":2}}],["proposing",{"2":{"1101":1}}],["propose",{"2":{"851":1}}],["proposed",{"2":{"851":3}}],["proposes",{"2":{"1":1,"855":1}}],["proponent",{"2":{"859":1}}],["properly",{"2":{"863":2,"919":1,"959":1,"1279":1,"1717":1,"2265":1,"2372":1,"2555":2,"2572":1,"2586":1,"2588":1,"2589":3,"2611":2,"2615":2,"2641":1,"2645":1}}],["property",{"2":{"748":2,"762":1,"763":1,"772":1,"773":1,"841":1,"845":1,"856":1,"864":1,"975":1,"985":1,"996":2,"1005":1,"1386":2,"1409":1,"1419":1,"1618":1,"1620":2,"1792":12,"2141":1,"2222":1,"2254":2,"2257":1,"2266":2,"2357":1,"2378":1,"2380":1,"2482":2,"2519":1,"2527":1,"2532":1,"2558":1,"2575":1,"2621":1,"2635":1,"2642":2,"2670":1,"2862":1}}],["properties",{"0":{"2141":1},"2":{"748":2,"852":2,"996":4,"1069":1,"1386":1,"1567":1,"1569":1,"1792":2,"1948":1,"2141":1,"2399":1,"2487":1,"2575":1,"2621":2,"2666":1}}],["proper",{"2":{"334":1,"859":1,"917":1,"918":1,"919":1,"965":1,"993":1,"1096":1,"1097":1,"1189":1,"1204":1,"1373":1,"1391":1,"1403":2,"1928":1,"1967":1,"1974":2,"2247":1,"2267":2,"2520":1,"2549":1,"2588":1,"2590":1,"2603":1,"2607":2,"2611":1,"2615":3,"2855":1}}],["prose",{"0":{"323":1},"2":{"318":1,"319":3,"323":1,"324":1,"326":2,"706":1,"913":1,"1040":2,"1042":1,"1824":1,"2481":3,"2482":1}}],["proxied",{"2":{"394":1,"396":1,"1106":1,"1328":1,"1329":1}}],["proxies",{"0":{"1707":1},"2":{"263":2,"1014":1,"1100":1,"1323":1,"1334":1,"1703":2,"1706":1,"1707":1,"1713":1,"1717":2,"1792":3,"1930":1,"2633":3,"2815":1}}],["proxyrequesthandler",{"2":{"2615":1}}],["proxyhttptypeprobetest",{"2":{"2513":1,"2523":1}}],["proxytests",{"2":{"2513":1,"2523":1}}],["proxying",{"2":{"837":1,"1351":1,"2809":1}}],["proxyoptions",{"2":{"417":1,"422":3,"423":1,"430":3,"436":4,"441":1,"446":4,"448":1,"449":1,"455":1,"1105":1,"1340":1,"1341":1,"1431":1,"1792":1,"1916":1,"1927":1,"1929":1,"1931":1,"2222":1,"2306":1,"2308":3,"2346":1,"2517":1,"2522":1,"2549":2,"2808":1,"2811":2,"2814":2,"2815":1,"2817":1}}],["proxy",{"0":{"75":1,"263":1,"412":1,"416":1,"417":1,"418":1,"419":1,"421":1,"433":1,"440":1,"441":1,"442":1,"443":1,"445":1,"453":1,"454":1,"1070":1,"1105":1,"1328":1,"1330":1,"1337":1,"1338":1,"1341":1,"1344":1,"1351":1,"1915":1,"1919":1,"2300":2,"2310":1,"2313":1,"2346":1,"2404":1,"2517":1,"2549":1,"2580":1,"2806":1,"2808":1,"2813":1},"1":{"413":1,"414":1,"415":1,"416":1,"417":2,"418":2,"419":2,"420":2,"421":2,"422":1,"423":1,"424":1,"425":1,"426":1,"427":1,"428":1,"429":1,"430":1,"431":1,"432":1,"434":1,"435":1,"436":1,"437":1,"438":1,"439":1,"440":1,"441":2,"442":2,"443":2,"444":2,"445":2,"446":1,"447":1,"448":1,"449":1,"450":1,"451":1,"452":1,"453":1,"454":1,"455":1,"456":1,"457":1,"1329":1,"1330":1,"1331":2,"1332":2,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":2,"1346":2,"1347":2,"1348":2,"1349":1,"1350":1,"1351":1,"1916":1,"1917":1,"1918":1,"1919":1,"1920":2,"1921":2,"1922":1,"1923":1,"1924":1,"1925":1,"1926":1,"1927":1,"1928":1,"1929":1,"1930":1,"1931":1,"1932":1,"1933":1,"1934":1,"2301":2,"2302":2,"2303":2,"2304":2,"2305":2,"2306":2,"2307":2,"2308":2,"2309":2,"2807":1,"2808":1,"2809":1,"2810":1,"2811":1,"2812":1,"2813":1,"2814":1,"2815":1,"2816":1,"2817":1},"2":{"75":3,"77":1,"223":4,"261":2,"263":1,"266":4,"412":1,"413":4,"414":7,"415":2,"417":2,"418":2,"419":2,"420":2,"421":2,"422":1,"423":3,"424":1,"426":1,"427":1,"428":1,"429":3,"430":2,"431":5,"432":2,"433":2,"434":4,"435":2,"436":6,"438":4,"439":13,"441":2,"442":2,"443":2,"444":2,"445":3,"446":9,"447":7,"448":4,"449":2,"451":2,"452":6,"453":4,"454":7,"455":2,"456":5,"457":2,"480":1,"807":1,"835":1,"876":2,"1011":1,"1037":2,"1066":1,"1070":1,"1100":3,"1101":2,"1102":2,"1104":4,"1105":15,"1106":2,"1121":1,"1126":3,"1127":1,"1328":3,"1330":1,"1331":4,"1332":15,"1333":3,"1335":1,"1337":2,"1338":14,"1339":9,"1340":2,"1341":6,"1343":1,"1345":2,"1347":5,"1348":3,"1349":2,"1350":2,"1351":2,"1368":1,"1377":1,"1396":4,"1431":5,"1433":3,"1475":2,"1477":1,"1528":1,"1701":2,"1703":1,"1704":5,"1705":1,"1706":1,"1708":1,"1711":4,"1717":1,"1718":1,"1784":1,"1788":1,"1789":2,"1792":42,"1824":1,"1851":1,"1915":2,"1916":6,"1917":4,"1918":7,"1920":3,"1921":12,"1922":6,"1923":1,"1924":10,"1925":2,"1926":7,"1927":2,"1928":1,"1929":2,"1930":3,"1931":1,"1932":3,"1934":4,"1961":4,"2031":1,"2107":1,"2137":1,"2164":6,"2165":3,"2185":1,"2222":3,"2229":4,"2237":1,"2238":1,"2300":2,"2301":2,"2302":2,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2310":3,"2313":8,"2344":3,"2346":3,"2347":1,"2382":1,"2404":1,"2463":2,"2466":1,"2489":1,"2508":1,"2509":4,"2510":1,"2513":2,"2515":2,"2517":2,"2518":1,"2521":1,"2523":2,"2529":1,"2537":1,"2549":53,"2550":2,"2575":1,"2580":6,"2581":1,"2615":1,"2633":11,"2771":1,"2791":2,"2806":9,"2807":8,"2809":5,"2810":17,"2811":5,"2812":1,"2813":4,"2814":7,"2815":11,"2816":7,"2817":4,"2835":1,"2856":2,"2865":1,"2879":1,"2881":1}}],["prototyping",{"2":{"1403":1}}],["prototype",{"2":{"1123":1}}],["proto",{"2":{"1100":1,"1705":1,"1711":1,"1792":1,"2633":2}}],["protocols",{"2":{"844":1,"1323":1}}],["protocol",{"0":{"2324":1,"2481":1},"2":{"223":1,"317":1,"848":1,"868":2,"869":1,"1007":1,"1037":1,"1038":1,"1039":2,"1098":1,"1126":1,"1276":1,"1593":1,"1701":1,"1704":1,"1705":1,"1789":1,"1792":6,"1807":2,"1813":2,"1824":2,"1868":1,"2166":1,"2318":1,"2479":1,"2481":4,"2498":1,"2633":2,"2712":1,"2774":1,"2840":1}}],["prot",{"2":{"922":2,"1185":2}}],["protects",{"2":{"921":1,"1156":1,"1180":1,"1185":1,"1487":1,"1792":1}}],["protecting",{"0":{"933":1},"2":{"835":1,"843":1,"1162":1,"1448":1,"2175":1,"2189":1}}],["protection",{"0":{"944":1,"1054":1,"1649":1,"2029":1,"2291":1,"2462":1},"1":{"1650":1,"1651":1,"1652":1,"1653":1,"1654":1,"1655":1,"1656":1,"1657":1,"1658":1,"1659":1,"1660":1,"1661":1,"1662":1,"1663":1,"1664":1,"1665":1,"1666":1,"1667":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1},"2":{"182":2,"184":1,"188":1,"189":1,"191":1,"214":1,"868":2,"869":1,"877":1,"1054":9,"1065":1,"1098":4,"1100":1,"1101":2,"1147":3,"1177":1,"1180":1,"1181":1,"1185":1,"1213":1,"1217":1,"1430":1,"1493":1,"1496":1,"1511":1,"1515":4,"1649":1,"1650":3,"1651":4,"1655":7,"1656":1,"1657":1,"1659":1,"1662":1,"1663":2,"1664":1,"1743":1,"1788":2,"1792":15,"1795":2,"1823":1,"1874":1,"2030":1,"2044":1,"2222":1,"2224":1,"2230":1,"2274":3,"2291":2,"2292":1,"2296":1,"2297":4,"2421":1,"2459":3,"2461":1,"2462":1,"2481":1,"2495":1,"2502":1,"2551":4,"2565":1,"2580":1,"2757":2,"2765":1}}],["protect",{"2":{"182":1,"314":1,"316":1,"792":1,"841":1,"843":2,"845":2,"849":1,"851":1,"934":1,"1135":1,"1188":1,"1224":1,"1251":1,"1252":1,"1470":1,"1651":1,"1792":3,"1990":1,"2014":1,"2035":1,"2295":1,"2297":1,"2565":1,"2632":2}}],["protectedresourcemetadatapath",{"0":{"1831":1},"2":{"1792":1,"1814":1,"2481":1}}],["protected",{"0":{"9":1,"314":1,"924":1,"1833":1,"2214":1},"2":{"23":1,"24":1,"37":3,"41":1,"48":3,"51":1,"182":1,"306":1,"453":1,"478":1,"843":1,"922":1,"924":1,"1045":1,"1184":1,"1185":2,"1207":1,"1210":1,"1436":1,"1662":2,"1792":5,"1828":1,"1829":1,"1831":3,"2042":1,"2187":2,"2199":1,"2223":1,"2295":1,"2481":2}}],["pro",{"2":{"107":2,"1189":1,"1192":1,"1218":1,"1792":1,"2380":1,"2397":1}}],["projection=",{"2":{"1692":1,"1792":1}}],["projections",{"2":{"1046":1}}],["projection",{"2":{"852":1,"2482":1}}],["projects",{"2":{"175":1,"683":1,"920":1,"1076":1,"1382":1,"1385":2,"1403":1,"1566":1,"1582":1,"1792":1,"2389":1,"2392":2,"2590":1,"2860":1}}],["project",{"0":{"976":1,"2538":1},"2":{"1":1,"852":1,"856":1,"867":1,"868":1,"869":1,"871":1,"872":3,"873":2,"874":1,"876":3,"920":1,"977":1,"980":1,"990":1,"1107":1,"1254":1,"1385":1,"1400":1,"1401":2,"1402":1,"1404":1,"1406":1,"1409":1,"1414":1,"1421":1,"1792":1,"2030":1,"2156":1,"2258":2,"2389":1,"2392":1,"2406":1,"2479":1,"2542":1,"2632":1,"2776":1,"2786":1,"2872":1}}],["tcl",{"2":{"1972":1}}],["tcp",{"2":{"1254":1,"1259":1,"1746":1,"2347":2}}],["tmp",{"2":{"1792":1,"2123":1,"2127":2}}],["tmpfs",{"2":{"848":1}}],["tz=america",{"2":{"2456":1}}],["tz=utc",{"2":{"2452":1}}],["tz",{"0":{"2451":1},"2":{"1792":2,"1856":2,"2224":1,"2450":2,"2451":4,"2452":1,"2453":1,"2455":1,"2456":3}}],["t>",{"2":{"1386":1,"1408":1,"2359":1}}],["txt",{"2":{"1792":2,"1800":1,"1804":2}}],["tx",{"2":{"1154":1}}],["tls",{"2":{"1109":1,"1198":1,"1500":1,"1787":1,"1792":1,"1794":1,"2809":1}}],["tl",{"0":{"1074":1}}],["tbody",{"2":{"996":4}}],["tb",{"2":{"881":1,"922":1,"1088":1,"1184":1,"1185":1,"1220":2,"1221":2,"1222":2,"1255":1,"1305":1,"1333":1}}],["tue",{"2":{"1023":1}}],["tuple",{"0":{"2589":1},"2":{"916":1,"919":2,"966":1,"1097":1,"1792":2,"1967":1,"1974":3,"2050":1,"2270":2,"2397":1,"2586":2,"2588":1,"2589":1,"2603":3,"2607":3,"2611":1,"2635":2}}],["tuples",{"2":{"852":1,"2051":1}}],["tune",{"2":{"919":1,"2835":1}}],["tuned",{"2":{"868":1,"1382":1}}],["tuning",{"2":{"874":1,"958":1,"1180":1,"1181":1,"1792":1}}],["turing",{"2":{"860":1}}],["turning",{"2":{"831":1,"1373":1}}],["turn",{"0":{"1038":1,"1183":1,"2724":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1},"2":{"327":1,"1016":1,"1037":2,"1083":1,"1095":1,"1208":1,"1383":1,"1384":3,"1386":1,"1792":2,"1834":1,"2154":1,"2479":1,"2544":1,"2806":1}}],["turned",{"2":{"300":1,"852":1,"868":1,"876":2,"2765":1,"2828":1}}],["turns",{"2":{"297":1,"832":1,"861":1,"863":1,"868":1,"947":1,"1037":1,"1038":1,"1099":1,"1302":1,"1327":1,"1382":1,"1431":1,"2107":1,"2113":1,"2155":1,"2171":1,"2176":1,"2535":1,"2537":1,"2713":2,"2766":1,"2802":1,"2879":1}}],["tutorials",{"0":{"1037":1},"2":{"849":1,"1405":1}}],["tutorial",{"2":{"791":1,"878":1,"1010":1,"1302":1,"1328":1,"1894":1,"2134":1,"2770":2,"2774":1,"2816":1,"2826":1,"2837":1}}],["twitter",{"2":{"1393":1}}],["twice",{"2":{"690":1,"845":4,"864":1,"865":1,"1082":1,"2529":1,"2815":1}}],["twelve",{"2":{"848":1}}],["twenty",{"2":{"840":2,"851":1,"857":1,"859":1}}],["two",{"0":{"831":2,"959":1,"1043":1,"1418":1,"2391":1},"1":{"832":2,"833":2,"834":2,"835":2,"836":2,"837":2,"838":2,"960":1},"2":{"1":1,"108":2,"121":1,"306":1,"307":2,"347":1,"560":1,"619":1,"622":1,"650":1,"663":1,"665":1,"667":1,"690":1,"699":1,"701":1,"841":9,"844":1,"845":4,"847":1,"849":1,"851":1,"852":2,"855":1,"856":1,"857":2,"860":1,"864":1,"865":2,"868":2,"871":1,"874":2,"876":1,"915":1,"916":1,"920":1,"927":1,"977":1,"1010":1,"1018":1,"1021":1,"1036":1,"1037":1,"1038":2,"1043":2,"1046":1,"1067":1,"1070":1,"1079":1,"1080":1,"1097":1,"1101":1,"1102":1,"1136":1,"1151":1,"1167":1,"1309":1,"1330":1,"1359":1,"1370":1,"1376":1,"1382":1,"1385":1,"1386":2,"1387":2,"1391":1,"1400":1,"1401":2,"1405":2,"1406":1,"1408":2,"1418":1,"1422":1,"1423":2,"1428":1,"1440":1,"1459":1,"1490":1,"1522":1,"1605":1,"1651":1,"1655":2,"1745":1,"1792":3,"1840":1,"1911":1,"2040":1,"2095":1,"2153":1,"2156":1,"2166":1,"2167":1,"2177":1,"2188":1,"2190":1,"2221":1,"2256":1,"2258":1,"2273":1,"2291":1,"2340":1,"2346":2,"2347":1,"2359":1,"2360":1,"2369":1,"2375":2,"2380":3,"2384":1,"2398":1,"2409":1,"2414":1,"2425":1,"2427":1,"2432":1,"2436":1,"2438":1,"2440":1,"2442":1,"2451":1,"2456":1,"2465":1,"2476":1,"2479":1,"2481":1,"2493":1,"2497":1,"2504":3,"2506":1,"2529":1,"2530":1,"2531":1,"2533":1,"2538":1,"2541":1,"2542":1,"2545":1,"2546":1,"2585":1,"2629":1,"2654":1,"2688":1,"2722":1,"2742":1,"2750":1,"2762":1,"2765":1,"2766":1,"2767":1,"2768":1,"2770":1,"2773":1,"2798":1,"2802":1,"2867":1,"2868":1,"2869":1,"2876":1}}],["t=hello",{"2":{"462":1,"463":1,"464":1,"466":1}}],["t=null",{"2":{"462":1,"463":1,"464":2}}],["t=",{"2":{"462":1,"463":1,"464":1,"466":1,"2438":1}}],["typing",{"2":{"704":1,"872":3,"1384":1,"1792":1,"2111":1,"2359":1,"2532":1,"2871":1}}],["typical",{"2":{"310":1,"869":1,"871":2,"918":1,"973":1,"1064":1,"1416":1,"1459":1,"1792":1,"2111":1,"2375":1,"2421":1,"2429":1,"2438":1,"2532":1,"2880":1}}],["typically",{"2":{"302":1,"316":1,"362":1,"512":1,"624":1,"679":1,"841":1,"968":1,"1011":1,"1015":1,"1064":1,"1132":1,"1165":1,"1206":1,"1219":1,"1236":1,"1412":1,"1447":1,"1685":1,"1792":6,"1801":1,"1869":1,"1870":1,"2075":1,"2313":1,"2430":1,"2438":1,"2854":1,"2878":1}}],["typo",{"0":{"2493":1},"2":{"390":1,"1080":1,"2414":1,"2416":2,"2417":1,"2447":1,"2529":1,"2754":1,"2865":1,"2881":1}}],["typos",{"2":{"388":1,"1609":1,"1792":1,"2225":1,"2380":1,"2389":1,"2413":1,"2416":1,"2428":1,"2444":1,"2445":1,"2446":2,"2493":1,"2659":1,"2696":1}}],["typecategorylookup",{"2":{"2621":1}}],["typecategory",{"2":{"2621":2}}],["type1",{"2":{"1792":2,"1968":2}}],["type=code",{"2":{"1691":1,"1692":1,"1694":1,"1695":1,"1697":1,"1792":4}}],["type=file",{"2":{"1366":1}}],["type=",{"2":{"938":2,"1061":5,"1491":1,"1792":1,"2039":1}}],["type=text",{"2":{"386":1,"493":1,"544":1}}],["typedescriptor",{"2":{"2370":1,"2504":1,"2611":4,"2621":2}}],["typed",{"2":{"583":1,"585":1,"833":1,"834":1,"835":3,"836":2,"837":1,"959":1,"995":1,"1020":1,"1024":1,"1037":2,"1038":2,"1043":3,"1104":1,"1105":1,"1126":1,"1342":1,"1381":1,"1382":1,"1406":1,"1407":1,"1410":2,"1581":1,"1605":1,"1924":1,"2166":1,"2223":1,"2310":1,"2337":2,"2357":4,"2496":1,"2497":2,"2498":1,"2509":1,"2513":1,"2590":3,"2611":1,"2688":1,"2826":1,"2830":1,"2836":1,"2838":1}}],["type>",{"2":{"370":6,"516":2,"582":1,"745":1,"1792":1}}],["type",{"0":{"75":1,"169":1,"373":1,"383":1,"515":1,"539":1,"585":1,"758":1,"952":1,"972":1,"981":1,"983":1,"984":1,"997":1,"1004":1,"1008":1,"1017":1,"1020":1,"1092":1,"1187":1,"1190":1,"1192":1,"1279":1,"1284":1,"1286":1,"1352":1,"1406":1,"1409":1,"1426":1,"1436":1,"1459":1,"1559":1,"1570":1,"1676":1,"1724":1,"1725":1,"1968":1,"2017":1,"2273":1,"2287":1,"2325":1,"2336":1,"2337":1,"2348":1,"2356":1,"2359":1,"2370":1,"2394":1,"2410":1,"2441":1,"2502":1,"2504":1,"2505":1,"2518":1,"2585":1,"2587":1,"2590":1,"2607":1,"2611":1,"2618":1,"2621":1,"2734":1,"2762":1,"2847":1},"1":{"516":1,"517":1,"518":1,"519":1,"520":1,"521":1,"522":1,"523":1,"524":1,"525":1,"526":1,"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":2,"983":2,"984":2,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"1191":1,"1192":1,"1193":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"1725":1,"1726":1,"1727":1,"2288":1,"2289":1,"2290":1,"2411":1,"2412":1,"2413":1,"2442":1,"2443":1,"2444":1,"2586":1,"2587":1},"2":{"31":1,"33":1,"73":1,"74":2,"75":1,"77":3,"106":1,"107":1,"121":3,"128":2,"140":1,"166":3,"169":2,"201":1,"202":2,"203":1,"206":5,"207":3,"208":3,"209":6,"210":4,"211":4,"212":4,"213":6,"214":9,"215":1,"216":2,"223":1,"226":1,"227":1,"238":1,"260":1,"264":3,"266":2,"304":1,"310":1,"322":1,"328":1,"330":3,"332":4,"333":2,"334":8,"335":2,"336":1,"337":2,"370":2,"373":1,"375":1,"379":1,"383":9,"384":2,"385":1,"386":4,"387":1,"388":1,"390":2,"392":1,"394":1,"395":1,"396":1,"408":1,"415":1,"429":1,"436":1,"439":1,"447":3,"472":1,"476":1,"477":1,"478":1,"479":4,"480":2,"489":1,"490":1,"492":1,"493":5,"494":2,"496":1,"512":1,"515":3,"516":2,"520":2,"521":2,"522":4,"523":1,"524":1,"527":2,"529":1,"531":1,"532":1,"533":1,"539":3,"540":1,"543":1,"544":4,"545":1,"546":2,"576":1,"581":1,"582":7,"584":6,"587":3,"625":1,"683":1,"690":1,"691":2,"695":1,"696":1,"700":2,"705":1,"720":1,"747":3,"748":5,"758":2,"761":1,"762":5,"763":2,"768":1,"771":1,"772":5,"773":3,"776":1,"779":1,"781":2,"782":3,"784":3,"786":3,"788":2,"801":2,"803":1,"816":1,"817":2,"834":1,"835":1,"843":1,"845":2,"851":2,"871":2,"872":5,"875":1,"876":1,"880":2,"882":2,"887":3,"893":2,"894":1,"903":4,"904":1,"914":5,"915":9,"916":15,"917":3,"918":2,"920":1,"946":1,"947":1,"952":2,"968":2,"972":3,"974":1,"975":3,"976":2,"979":1,"981":1,"982":3,"983":1,"984":4,"985":2,"995":2,"996":4,"997":1,"998":4,"1005":2,"1009":2,"1010":2,"1016":3,"1017":1,"1019":6,"1020":3,"1023":3,"1027":1,"1029":2,"1030":3,"1031":4,"1032":1,"1033":1,"1034":3,"1036":2,"1037":9,"1065":1,"1067":3,"1068":2,"1069":4,"1073":2,"1078":1,"1080":3,"1084":1,"1086":2,"1092":1,"1094":1,"1096":1,"1097":4,"1098":2,"1100":1,"1102":1,"1104":1,"1105":13,"1107":1,"1111":1,"1121":2,"1126":1,"1127":1,"1141":1,"1145":1,"1146":1,"1147":2,"1150":7,"1158":1,"1159":1,"1160":1,"1161":1,"1162":6,"1167":1,"1168":1,"1174":1,"1175":1,"1177":2,"1187":4,"1189":3,"1190":3,"1192":5,"1193":14,"1207":1,"1232":2,"1234":2,"1236":1,"1237":2,"1239":3,"1245":1,"1255":1,"1280":1,"1341":3,"1350":1,"1355":6,"1357":1,"1359":1,"1360":2,"1362":6,"1364":1,"1366":2,"1368":1,"1373":2,"1375":1,"1376":1,"1378":2,"1382":2,"1386":5,"1390":2,"1391":1,"1394":1,"1395":1,"1396":3,"1398":11,"1399":8,"1402":1,"1403":2,"1405":3,"1406":2,"1407":1,"1408":3,"1409":3,"1410":2,"1412":2,"1413":1,"1416":2,"1418":1,"1419":3,"1423":1,"1424":1,"1426":5,"1430":1,"1431":4,"1432":1,"1434":1,"1436":4,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1447":1,"1451":1,"1452":1,"1454":1,"1456":1,"1458":3,"1459":2,"1460":1,"1470":2,"1471":2,"1472":1,"1473":1,"1474":4,"1475":1,"1477":3,"1479":1,"1480":2,"1489":1,"1499":1,"1501":1,"1510":1,"1511":6,"1513":1,"1514":1,"1515":2,"1516":1,"1520":4,"1521":2,"1522":2,"1523":1,"1527":1,"1529":4,"1534":2,"1540":2,"1544":2,"1554":2,"1555":1,"1556":1,"1558":2,"1559":4,"1560":1,"1561":1,"1562":1,"1563":1,"1564":1,"1565":1,"1568":1,"1569":2,"1570":2,"1571":3,"1580":1,"1588":1,"1589":1,"1604":1,"1605":1,"1618":1,"1623":1,"1628":1,"1631":1,"1639":1,"1643":1,"1646":1,"1651":1,"1669":5,"1670":3,"1671":3,"1672":1,"1673":4,"1676":2,"1677":1,"1678":7,"1684":1,"1686":1,"1687":1,"1688":1,"1696":1,"1703":1,"1721":1,"1722":5,"1723":3,"1725":3,"1726":2,"1727":3,"1728":1,"1730":4,"1731":2,"1732":5,"1733":3,"1736":4,"1738":1,"1740":6,"1741":1,"1742":2,"1743":5,"1744":5,"1747":1,"1748":2,"1750":2,"1753":1,"1759":1,"1764":1,"1792":119,"1802":1,"1803":1,"1804":1,"1805":1,"1806":1,"1807":1,"1809":1,"1816":1,"1817":1,"1818":1,"1819":1,"1820":1,"1821":1,"1822":1,"1823":1,"1824":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1837":1,"1838":1,"1840":1,"1841":1,"1843":1,"1844":1,"1846":1,"1848":1,"1849":1,"1850":1,"1852":1,"1853":1,"1856":1,"1857":1,"1861":1,"1862":3,"1864":1,"1874":1,"1875":1,"1877":1,"1882":1,"1884":1,"1886":1,"1887":2,"1888":2,"1893":1,"1898":1,"1902":1,"1903":1,"1904":1,"1905":1,"1906":6,"1907":2,"1911":1,"1912":1,"1916":1,"1917":1,"1918":3,"1921":1,"1922":3,"1923":2,"1924":3,"1925":2,"1937":1,"1949":1,"1951":3,"1952":3,"1953":3,"1954":3,"1955":6,"1956":1,"1957":3,"1958":3,"1959":2,"1960":8,"1961":2,"1967":3,"1968":1,"1973":4,"1974":21,"1980":1,"2000":2,"2007":1,"2010":3,"2016":2,"2017":1,"2034":1,"2038":1,"2047":1,"2074":1,"2075":1,"2077":1,"2086":1,"2093":1,"2094":1,"2098":1,"2109":2,"2110":1,"2117":1,"2124":1,"2125":3,"2126":2,"2127":2,"2128":1,"2129":1,"2130":1,"2131":1,"2138":1,"2139":1,"2140":1,"2141":2,"2142":4,"2144":3,"2145":2,"2146":8,"2148":3,"2154":1,"2164":9,"2165":3,"2184":1,"2185":1,"2193":1,"2202":1,"2204":2,"2207":2,"2217":1,"2221":2,"2222":7,"2225":1,"2226":1,"2228":1,"2230":1,"2236":3,"2247":2,"2253":1,"2254":5,"2255":10,"2257":4,"2258":4,"2259":1,"2264":20,"2267":2,"2270":1,"2271":2,"2273":1,"2274":1,"2279":1,"2282":1,"2283":2,"2285":1,"2286":1,"2287":1,"2288":6,"2289":1,"2290":2,"2303":1,"2310":1,"2321":4,"2322":3,"2323":3,"2324":3,"2325":1,"2328":1,"2329":3,"2330":2,"2333":1,"2335":1,"2336":4,"2337":10,"2346":7,"2348":7,"2356":1,"2359":5,"2360":1,"2364":1,"2369":1,"2370":3,"2372":2,"2375":7,"2378":4,"2379":9,"2380":9,"2381":2,"2383":1,"2394":5,"2398":3,"2410":3,"2412":7,"2413":4,"2417":3,"2421":1,"2422":1,"2427":3,"2431":1,"2434":1,"2436":1,"2440":1,"2441":2,"2443":8,"2444":2,"2445":2,"2446":1,"2447":2,"2457":1,"2470":3,"2471":2,"2483":3,"2484":2,"2489":1,"2493":2,"2495":1,"2496":2,"2497":1,"2500":1,"2502":7,"2504":10,"2506":1,"2509":2,"2510":2,"2511":1,"2512":2,"2513":2,"2515":1,"2517":1,"2518":2,"2519":1,"2520":2,"2521":1,"2522":1,"2523":3,"2529":4,"2530":4,"2534":1,"2537":1,"2540":5,"2543":1,"2546":3,"2549":5,"2555":1,"2575":9,"2580":1,"2586":4,"2587":7,"2590":5,"2597":1,"2600":1,"2607":21,"2608":1,"2621":7,"2632":3,"2656":1,"2665":1,"2687":1,"2688":1,"2702":1,"2722":1,"2726":2,"2734":2,"2759":3,"2760":4,"2762":9,"2763":5,"2764":2,"2765":2,"2766":5,"2767":4,"2768":3,"2769":2,"2775":1,"2810":3,"2812":1,"2814":1,"2815":1,"2816":1,"2824":1,"2826":1,"2840":1,"2845":3,"2846":1,"2847":3,"2849":1,"2854":6,"2855":1,"2857":1,"2858":1,"2865":2,"2866":2,"2868":1,"2872":1,"2873":1}}],["typescripttypescripttype",{"2":{"1386":1,"2359":1}}],["typescripttypescriptconst",{"2":{"1024":1,"1410":1}}],["typescripttypescriptif",{"2":{"1364":1}}],["typescripttypescriptinterface",{"2":{"920":1,"1024":1,"1567":1,"2357":1,"2590":1,"2611":2}}],["typescripttypescriptimport",{"2":{"894":1,"961":1,"996":1,"1318":1,"1321":1,"1342":1,"1361":1,"1409":1}}],["typescripttypescriptexport",{"2":{"723":1,"1410":1,"1413":1,"1416":1,"1568":1,"1569":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"2655":1}}],["typescripttypescript",{"2":{"429":1,"894":1,"938":1,"961":1,"995":1,"1063":1,"1107":1,"1193":1,"1218":1,"1317":1,"1326":1,"1335":1,"1342":1,"1366":1,"1408":1,"1569":1,"1570":1,"1574":1,"2247":1,"2310":1,"2313":1,"2359":1}}],["typescript",{"0":{"163":1,"429":1,"894":1,"961":1,"972":1,"995":1,"1024":1,"1317":1,"1342":1,"1352":1,"1406":1,"2519":1},"1":{"973":1,"974":1,"975":1,"976":1,"977":1,"978":1,"979":1,"980":1,"981":1,"982":1,"983":1,"984":1,"985":1,"986":1,"987":1,"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"995":1,"996":1,"997":1,"998":1,"999":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1005":1,"1006":1,"1007":1,"1008":1,"1009":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"74":1,"163":1,"164":1,"381":1,"429":1,"615":1,"679":1,"681":1,"718":2,"720":2,"722":1,"725":1,"727":1,"792":1,"833":1,"834":1,"835":1,"837":1,"867":2,"868":2,"869":2,"871":3,"872":2,"873":2,"875":1,"876":1,"878":1,"879":1,"880":1,"894":1,"909":1,"910":1,"912":1,"914":1,"915":1,"917":1,"918":2,"920":1,"938":1,"957":1,"968":1,"972":3,"975":5,"978":1,"981":1,"982":1,"984":2,"985":2,"996":3,"997":1,"1000":1,"1005":3,"1006":3,"1009":1,"1020":1,"1026":2,"1027":1,"1036":1,"1037":8,"1042":1,"1043":1,"1080":1,"1086":1,"1094":3,"1095":1,"1097":4,"1104":1,"1107":2,"1108":3,"1115":1,"1121":1,"1127":1,"1193":1,"1211":1,"1218":1,"1302":1,"1304":1,"1317":1,"1322":1,"1352":2,"1355":2,"1361":1,"1366":4,"1367":1,"1368":1,"1378":1,"1379":1,"1381":1,"1385":1,"1386":3,"1391":1,"1394":1,"1401":1,"1405":1,"1406":3,"1407":2,"1408":2,"1409":3,"1411":1,"1412":1,"1414":1,"1419":1,"1421":1,"1422":2,"1436":1,"1552":1,"1558":1,"1559":1,"1566":1,"1567":2,"1580":2,"1582":1,"1583":1,"1585":1,"1789":1,"1792":4,"1796":1,"1868":1,"2081":1,"2157":1,"2162":1,"2164":2,"2165":1,"2168":3,"2221":1,"2222":2,"2247":3,"2267":1,"2273":2,"2278":2,"2310":1,"2313":1,"2333":1,"2339":1,"2356":1,"2357":1,"2358":1,"2359":2,"2360":1,"2364":1,"2391":2,"2484":1,"2489":1,"2515":1,"2519":1,"2520":1,"2527":1,"2543":1,"2562":1,"2566":1,"2590":5,"2611":2,"2641":1,"2648":1,"2654":1,"2655":1,"2714":1,"2723":1,"2742":1,"2772":1,"2775":1,"2826":2,"2830":1,"2836":1,"2838":1,"2848":1,"2857":1,"2862":1,"2878":1}}],["types",{"0":{"201":1,"264":1,"334":1,"335":1,"746":1,"912":1,"915":1,"982":1,"1010":1,"1016":1,"1019":1,"1035":1,"1097":1,"1144":1,"1375":1,"1423":1,"1474":1,"1512":1,"1580":1,"1723":1,"1765":1,"1943":1,"1950":1,"1957":1,"1973":1,"1974":1,"2140":1,"2217":1,"2264":1,"2345":1,"2346":1,"2359":1,"2586":1,"2759":1},"1":{"202":1,"203":1,"204":1,"205":1,"206":1,"207":1,"208":1,"209":1,"210":1,"211":1,"212":1,"213":1,"214":1,"215":1,"216":1,"217":1,"218":1,"219":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1,"1145":1,"1146":1,"1147":1,"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1,"1513":1,"1514":1,"1515":1,"1766":1,"1767":1,"1768":1,"2346":1,"2347":1,"2348":1,"2760":1,"2761":1,"2762":1,"2763":1,"2764":1,"2765":1,"2766":1,"2767":1,"2768":1,"2769":1,"2770":1,"2771":1},"2":{"22":1,"34":1,"41":1,"119":1,"120":1,"121":1,"186":1,"188":2,"202":2,"212":1,"215":1,"216":1,"217":1,"223":2,"258":1,"261":2,"266":1,"315":1,"334":2,"335":1,"337":3,"338":1,"369":1,"373":1,"378":1,"383":2,"384":1,"387":2,"388":1,"390":1,"394":1,"396":1,"535":1,"583":2,"585":2,"587":2,"746":1,"747":4,"753":4,"757":4,"758":1,"781":2,"782":2,"784":2,"786":2,"788":2,"801":1,"829":1,"835":1,"845":1,"868":1,"871":1,"873":1,"875":2,"876":1,"879":3,"910":1,"912":6,"914":5,"915":5,"916":8,"917":3,"918":5,"919":1,"920":2,"951":1,"952":1,"968":1,"971":1,"973":1,"974":1,"978":1,"982":1,"983":3,"984":1,"985":1,"995":1,"1000":1,"1004":1,"1005":2,"1006":2,"1008":2,"1009":1,"1010":1,"1015":1,"1016":2,"1019":1,"1022":1,"1026":1,"1029":1,"1030":1,"1035":1,"1036":2,"1037":6,"1068":1,"1069":1,"1080":1,"1092":3,"1097":13,"1099":1,"1104":2,"1105":3,"1108":2,"1126":1,"1127":1,"1129":1,"1156":1,"1167":1,"1192":1,"1193":10,"1255":2,"1279":1,"1322":1,"1338":1,"1358":4,"1366":1,"1375":1,"1376":2,"1377":2,"1378":1,"1385":1,"1386":2,"1390":2,"1391":1,"1394":1,"1398":3,"1399":1,"1401":1,"1404":1,"1405":1,"1407":1,"1408":1,"1419":1,"1423":1,"1434":2,"1436":1,"1437":1,"1458":1,"1460":1,"1477":1,"1517":1,"1522":1,"1559":7,"1571":4,"1686":1,"1720":2,"1722":2,"1723":1,"1739":1,"1745":1,"1748":1,"1765":1,"1787":1,"1789":1,"1792":36,"1794":1,"1796":1,"1824":1,"1901":1,"1933":1,"1937":2,"1943":1,"1950":1,"1967":1,"1968":1,"1973":1,"1974":7,"2038":1,"2125":1,"2140":1,"2156":2,"2164":7,"2165":3,"2183":1,"2189":1,"2223":1,"2228":1,"2239":1,"2247":2,"2254":1,"2255":1,"2257":1,"2258":1,"2261":2,"2264":11,"2265":1,"2267":2,"2274":1,"2277":1,"2282":1,"2287":1,"2293":1,"2296":2,"2314":1,"2318":1,"2321":2,"2324":4,"2325":1,"2329":2,"2332":1,"2333":1,"2338":1,"2344":2,"2346":2,"2347":1,"2348":1,"2357":2,"2359":1,"2371":1,"2375":2,"2394":1,"2397":1,"2421":2,"2422":1,"2450":1,"2484":5,"2500":2,"2504":2,"2506":1,"2542":2,"2543":1,"2575":1,"2585":1,"2586":5,"2588":2,"2589":3,"2590":2,"2597":1,"2603":2,"2607":8,"2608":3,"2611":3,"2618":1,"2621":1,"2626":1,"2634":1,"2641":3,"2670":1,"2759":3,"2761":1,"2769":1,"2770":1,"2771":1,"2773":1,"2774":1,"2817":1,"2840":1,"2856":2,"2857":1,"2858":1}}],["td>",{"2":{"996":4}}],["td>$",{"2":{"996":4}}],["tdd",{"0":{"1081":1},"2":{"994":1,"1005":1,"1076":1,"1081":1,"1393":1}}],["td",{"2":{"297":1,"836":1,"965":1,"1792":1,"2073":1,"2075":1,"2080":1,"2171":1,"2760":1,"2807":1,"2828":1}}],["tsts",{"2":{"1431":1}}],["tstsconst",{"2":{"666":1}}],["tsqlt",{"2":{"1075":1}}],["tsc",{"2":{"1005":1,"1407":1,"1419":1,"1422":1}}],["tsclienttests",{"2":{"2523":1}}],["tsclienttestproxypassthrough",{"2":{"2313":1}}],["tsclientoptions",{"2":{"2520":1}}],["tsclient",{"0":{"718":1,"1411":1,"2247":1,"2273":1,"2278":1,"2310":1,"2313":1,"2355":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2484":1,"2562":1,"2566":1,"2590":1,"2611":1,"2654":1,"2655":1,"2656":1},"1":{"719":1,"720":1,"721":1,"722":1,"723":1,"724":1,"725":1,"726":1,"727":1,"1412":1,"1413":1,"1414":1,"1415":1,"2356":1,"2357":1,"2358":1,"2359":1,"2360":1,"2655":1,"2656":1},"2":{"163":1,"381":1,"429":1,"679":3,"681":2,"719":8,"720":9,"722":3,"723":2,"724":3,"726":2,"792":1,"868":1,"957":2,"961":1,"1037":1,"1374":1,"1406":1,"1411":1,"1412":3,"1413":2,"1414":4,"1415":6,"1583":1,"1585":1,"2081":2,"2228":1,"2229":1,"2233":1,"2240":1,"2247":3,"2273":1,"2277":1,"2278":3,"2310":1,"2313":2,"2333":1,"2357":1,"2364":2,"2484":1,"2489":1,"2523":1,"2555":1,"2569":1,"2590":2,"2611":1,"2641":1,"2655":1,"2656":2}}],["ts",{"2":{"836":1,"868":1,"894":1,"937":1,"961":1,"976":2,"985":2,"995":1,"996":2,"997":4,"998":2,"1026":2,"1043":1,"1044":1,"1062":1,"1107":2,"1108":1,"1209":1,"1211":1,"1218":1,"1252":1,"1255":1,"1318":1,"1321":1,"1335":1,"1342":1,"1361":1,"1406":1,"1407":2,"1408":2,"1409":3,"1414":3,"1416":4,"1417":2,"1418":2,"1419":2,"1420":2,"1422":2,"1431":1,"1559":4,"1570":4,"1571":6,"1574":1,"1577":5,"1579":1,"1580":1,"1581":4,"1582":1,"1792":4,"1868":1,"2247":1,"2391":1,"2407":1,"2484":3,"2519":1,"2520":1}}],["tsv",{"0":{"129":1,"602":1},"2":{"129":1,"490":2,"602":1}}],["ttls",{"2":{"1101":1,"1121":1,"1519":1,"1525":1}}],["ttl",{"0":{"107":1,"1525":1},"2":{"105":1,"108":1,"110":1,"123":1,"214":5,"230":1,"852":1,"1067":3,"1098":1,"1101":1,"1150":2,"1430":1,"1523":1,"1525":1,"1743":4,"1792":2,"2222":1,"2380":2,"2381":1,"2494":1,"2498":1,"2502":4,"2506":1,"2765":1}}],["tampered",{"2":{"2405":1}}],["taking",{"2":{"1645":1,"2459":1,"2543":1}}],["take",{"0":{"1025":1},"1":{"1026":1},"2":{"841":1,"844":1,"845":1,"849":1,"852":1,"860":1,"947":1,"1065":1,"1104":1,"1165":1,"1253":1,"1281":1,"1384":2,"2266":1,"2380":1,"2487":1}}],["takeaway",{"2":{"838":1}}],["takes",{"2":{"415":1,"422":1,"843":1,"852":1,"859":1,"861":1,"865":1,"1010":1,"1059":1,"1064":1,"1096":1,"1098":1,"1378":1,"1402":1,"1410":1,"1426":1,"1569":1,"1655":1,"1792":2,"1862":1,"2040":1,"2303":1,"2476":1,"2483":1,"2540":1,"2795":1,"2845":1,"2869":1}}],["taken",{"2":{"323":1,"386":1,"852":1,"866":1,"1368":1,"1408":1,"1566":1,"2309":1}}],["taught",{"2":{"1402":1}}],["tackling",{"2":{"1132":1}}],["tar",{"2":{"1118":3}}],["targeting",{"0":{"2833":1},"2":{"1792":1,"1961":1,"2634":1,"2827":1,"2831":1}}],["targets",{"2":{"1110":1,"1174":1,"1431":1,"1961":1,"2711":1,"2792":1}}],["targetarray",{"2":{"1026":2}}],["targetcurrencies",{"2":{"1026":5}}],["targetcurrenciescsv",{"2":{"1024":2}}],["targetcurrenciescsv=eur",{"2":{"1023":1}}],["targeted",{"2":{"77":1,"2490":1,"2523":1,"2834":1}}],["target",{"0":{"436":1,"1174":1,"1628":1,"2246":1,"2811":1},"2":{"75":1,"358":3,"362":1,"364":1,"421":1,"422":1,"423":1,"445":1,"446":3,"664":2,"665":3,"666":1,"836":1,"1019":1,"1021":6,"1174":1,"1175":1,"1176":2,"1313":1,"1376":8,"1628":1,"1792":4,"1830":1,"1961":3,"2223":1,"2240":1,"2246":1,"2266":2,"2395":1,"2437":1,"2489":1,"2496":1,"2504":1,"2513":1,"2523":1,"2806":1,"2808":1,"2811":3,"2834":5}}],["taht",{"2":{"920":1}}],["taylor",{"2":{"913":1}}],["talend",{"0":{"908":1}}],["talks",{"2":{"1039":1}}],["talk",{"2":{"844":1,"847":1,"851":1,"1075":1,"1382":1,"1386":2,"1392":1,"1399":1,"1402":1}}],["talking",{"2":{"841":4,"844":1,"1401":1}}],["tailored",{"2":{"916":2}}],["tail",{"2":{"872":1,"2398":2}}],["taxonomies",{"2":{"2381":1}}],["taxonomy",{"2":{"2167":1,"2545":1}}],["tax",{"0":{"856":1},"2":{"861":5,"1382":2}}],["tasks",{"2":{"851":1}}],["task",{"2":{"574":1,"641":3,"646":1,"660":3,"871":2,"872":1,"1105":1,"1366":1,"1376":1,"1398":2,"1403":1,"1741":1,"1745":1,"1799":1,"1811":1,"2013":1,"2289":1,"2346":1,"2347":1,"2462":1,"2502":1,"2559":1,"2794":1}}],["tabs",{"2":{"2589":1}}],["tabular",{"2":{"971":1}}],["tab",{"0":{"490":1,"602":1},"2":{"490":1,"599":1,"767":1,"1068":1}}],["tableau",{"2":{"1206":1}}],["tableformatoptions",{"2":{"673":1,"958":1,"963":1,"964":1,"965":1,"1792":1,"2073":1,"2075":1,"2077":1,"2080":1,"2651":1,"2652":1}}],["tablesstatspath",{"2":{"1792":1,"2046":1,"2047":1,"2064":1,"2635":1}}],["tablespace",{"2":{"848":3}}],["tables",{"0":{"991":1,"1283":1,"2050":2,"2729":1},"1":{"1284":1,"1285":1},"2":{"238":1,"583":1,"673":1,"701":1,"834":1,"836":1,"848":2,"864":1,"865":1,"868":1,"874":1,"913":1,"918":1,"919":1,"922":2,"926":1,"945":1,"956":1,"966":2,"974":1,"976":3,"977":2,"991":1,"992":1,"993":1,"994":1,"1070":1,"1075":1,"1079":1,"1095":2,"1096":8,"1098":1,"1100":1,"1122":1,"1125":1,"1127":1,"1130":1,"1185":1,"1213":1,"1252":1,"1391":1,"1394":1,"1395":1,"1396":1,"1432":1,"1435":4,"1437":1,"1792":5,"1967":1,"2045":1,"2046":1,"2047":1,"2050":1,"2056":1,"2064":1,"2072":1,"2110":1,"2156":2,"2317":1,"2337":1,"2344":1,"2351":1,"2388":1,"2389":1,"2530":1,"2542":1,"2546":2,"2635":5,"2650":1,"2674":1,"2713":1,"2726":1,"2836":1,"2840":1,"2854":1,"2859":1,"2866":1,"2878":1}}],["table",{"0":{"161":1,"228":1,"673":1,"677":1,"958":1,"965":1,"1096":1,"2072":1,"2075":1,"2077":1,"2530":1,"2650":1,"2651":1,"2652":1,"2866":1},"1":{"674":1,"675":1,"676":1,"677":1,"678":1,"679":1,"680":1,"681":1,"682":1,"2073":1,"2074":1,"2075":1,"2076":2,"2077":1,"2078":2,"2079":2,"2080":1,"2081":1,"2082":1,"2083":1,"2651":1,"2652":1,"2653":1},"2":{"37":1,"38":1,"39":1,"119":1,"128":1,"136":1,"161":2,"164":2,"167":4,"186":1,"187":1,"212":1,"215":2,"228":2,"239":1,"298":1,"299":1,"309":1,"312":1,"313":1,"332":1,"333":1,"334":1,"335":1,"337":1,"366":1,"488":1,"489":1,"490":2,"491":1,"492":1,"493":1,"531":1,"583":1,"584":2,"587":1,"611":1,"673":5,"674":1,"675":7,"677":3,"678":1,"679":4,"680":1,"681":1,"682":1,"691":1,"692":1,"698":1,"699":1,"700":1,"701":1,"702":1,"720":1,"723":3,"726":2,"733":1,"734":1,"735":1,"736":1,"764":2,"774":2,"797":1,"798":1,"799":1,"833":1,"841":2,"844":1,"848":15,"851":1,"852":6,"860":1,"864":1,"865":1,"868":4,"872":2,"881":1,"912":1,"913":4,"914":2,"915":2,"916":8,"918":2,"922":1,"924":2,"932":1,"934":3,"936":1,"940":1,"941":1,"947":1,"949":2,"956":1,"957":4,"959":2,"960":3,"961":2,"964":1,"965":3,"966":4,"970":1,"971":1,"975":1,"977":2,"979":1,"980":2,"982":4,"983":3,"990":1,"991":2,"995":2,"996":5,"1037":1,"1050":2,"1054":1,"1055":1,"1057":1,"1060":1,"1064":1,"1065":1,"1068":2,"1071":1,"1076":2,"1078":1,"1084":1,"1096":1,"1099":2,"1105":1,"1107":1,"1110":1,"1111":1,"1122":1,"1125":1,"1126":2,"1127":1,"1133":1,"1149":1,"1154":1,"1184":1,"1185":2,"1190":1,"1191":3,"1192":1,"1193":4,"1197":2,"1202":1,"1203":1,"1213":5,"1214":1,"1215":1,"1216":1,"1232":1,"1234":1,"1236":1,"1239":1,"1254":1,"1255":1,"1258":1,"1307":4,"1308":1,"1310":1,"1336":1,"1355":3,"1367":1,"1368":1,"1374":3,"1376":5,"1378":2,"1385":5,"1387":1,"1395":2,"1396":6,"1412":1,"1413":2,"1427":1,"1431":1,"1436":2,"1437":1,"1441":2,"1458":3,"1504":1,"1547":1,"1567":1,"1632":3,"1655":2,"1664":1,"1688":1,"1689":1,"1742":1,"1789":2,"1792":21,"1929":1,"1973":1,"1974":1,"2007":1,"2047":1,"2050":1,"2054":1,"2059":1,"2072":4,"2074":2,"2075":7,"2076":4,"2077":3,"2078":2,"2079":3,"2081":2,"2083":1,"2094":1,"2099":1,"2109":3,"2110":4,"2156":1,"2164":2,"2165":3,"2167":1,"2176":1,"2183":1,"2184":1,"2187":2,"2195":1,"2221":1,"2233":1,"2247":1,"2283":1,"2290":1,"2293":1,"2294":1,"2322":4,"2323":1,"2328":1,"2329":2,"2337":2,"2339":1,"2372":1,"2375":3,"2389":1,"2438":1,"2498":1,"2527":1,"2529":1,"2530":5,"2537":2,"2542":2,"2546":1,"2572":1,"2586":4,"2587":1,"2588":1,"2607":1,"2621":2,"2635":5,"2650":2,"2651":2,"2652":1,"2653":2,"2656":2,"2726":1,"2729":1,"2768":1,"2775":1,"2802":1,"2803":1,"2810":1,"2815":1,"2836":1,"2839":1,"2849":1,"2855":2,"2856":1,"2858":1,"2860":1,"2862":1,"2865":1,"2866":2,"2868":1,"2869":6}}],["tag=smoke",{"2":{"710":2,"1792":1,"2092":1,"2097":1,"2537":1,"2877":1}}],["tagged",{"2":{"352":1,"668":1,"711":1,"1326":1,"1414":1,"1856":1,"2455":1,"2536":1}}],["tags",{"0":{"353":1,"683":1,"684":1,"711":1,"2877":1},"1":{"684":1,"685":1,"686":1,"687":1,"688":1},"2":{"175":6,"176":1,"179":5,"181":1,"223":4,"347":1,"348":2,"353":1,"355":2,"356":2,"683":2,"684":3,"685":1,"708":2,"709":1,"711":1,"1416":1,"1424":1,"1428":1,"1429":1,"1792":5,"1913":1,"2037":1,"2038":1,"2039":1,"2094":2,"2097":2,"2224":1,"2432":1,"2451":1,"2533":1,"2537":5,"2545":1,"2546":1,"2550":1,"2576":1,"2789":1,"2790":1,"2791":1}}],["tag2>",{"2":{"175":1,"179":1,"685":1}}],["tag1>",{"2":{"175":1,"179":1,"685":1}}],["tag",{"0":{"175":1,"352":1,"708":1,"2039":1,"2097":1},"1":{"709":1,"710":1,"711":1,"712":1},"2":{"175":3,"176":2,"177":1,"181":1,"223":1,"239":3,"320":3,"325":1,"327":1,"347":1,"348":5,"352":4,"355":3,"356":1,"378":1,"683":2,"684":1,"687":1,"688":2,"706":1,"708":1,"709":3,"710":1,"711":2,"712":1,"1042":2,"1792":11,"1834":1,"1913":2,"2040":1,"2075":1,"2093":1,"2094":1,"2097":3,"2107":1,"2114":1,"2167":2,"2221":3,"2223":1,"2225":1,"2323":1,"2333":1,"2367":2,"2419":1,"2430":2,"2432":5,"2435":4,"2476":1,"2481":3,"2482":3,"2487":1,"2533":2,"2537":7,"2545":1,"2546":2,"2869":1,"2870":3,"2873":1,"2877":2,"2879":1,"2882":1}}],["troubleshooting",{"0":{"2707":1,"2753":1,"2881":1},"1":{"2708":1,"2709":1,"2710":1,"2711":1,"2712":1,"2713":1,"2714":1,"2715":1,"2716":1,"2717":1,"2718":1,"2719":1,"2720":1,"2721":1,"2722":1,"2723":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2729":1,"2730":1,"2731":1,"2732":1,"2733":1,"2734":1,"2735":1,"2736":1,"2737":1,"2738":1,"2739":1,"2740":1,"2741":1,"2742":1,"2743":1,"2744":1,"2745":1,"2746":1,"2747":1,"2748":1,"2749":1,"2750":1,"2751":1,"2752":1,"2753":1,"2754":2,"2755":2,"2756":2,"2757":2,"2758":2}}],["tr>",{"2":{"996":2}}],["tr",{"2":{"996":1,"1333":2}}],["trygetvalue",{"2":{"2614":1}}],["trygetitem",{"2":{"2482":1}}],["tryparsetimetz",{"2":{"2451":1}}],["tryparsetime",{"2":{"2451":1}}],["tryparsetimestamptz",{"2":{"2451":1}}],["tryparsetimestamp",{"2":{"2451":1}}],["tryparsedate",{"0":{"2453":1},"2":{"2224":1,"2453":1}}],["tryparse",{"2":{"1792":1,"2451":2,"2452":1,"2453":2,"2455":1}}],["trying",{"2":{"1403":1,"1428":1}}],["try",{"0":{"1047":1,"1433":1,"2402":1},"2":{"844":1,"970":1,"1026":2,"1061":1,"1076":2,"1079":1,"1132":1,"1157":1,"1320":1,"1366":2,"1404":1,"1416":1,"1440":1,"1628":2,"1792":6,"1948":1,"1949":1,"1958":1,"1959":1,"1960":1,"2226":1,"2247":1,"2257":1,"2266":2,"2404":1,"2470":1,"2471":1,"2533":1}}],["tremendous",{"2":{"1403":1,"1404":1}}],["trees",{"2":{"848":1,"860":1,"954":1}}],["tree",{"2":{"843":1,"848":1,"852":2,"1382":1,"1409":1,"1792":1,"2095":2,"2105":1,"2106":1,"2153":1,"2157":1,"2159":1,"2537":3,"2538":1,"2541":1,"2543":2,"2545":1,"2878":1}}],["treatment",{"2":{"2440":1}}],["treating",{"2":{"876":1,"2411":1,"2494":1}}],["treat",{"2":{"859":1,"863":1,"921":1,"946":1,"1079":1,"1139":1,"1208":1,"1792":1,"2040":1,"2105":1,"2438":1,"2477":1,"2798":1}}],["treats",{"2":{"297":1,"788":1,"843":1,"1567":1,"2451":1,"2767":1}}],["treated",{"2":{"252":1,"383":1,"388":1,"528":1,"587":1,"706":1,"1139":1,"1923":1,"2252":1,"2283":1,"2414":1,"2420":1,"2444":1,"2505":1,"2648":1}}],["tri",{"2":{"1792":1,"2107":1}}],["tried",{"2":{"1075":1,"1402":1,"2608":1}}],["tries",{"2":{"904":2,"941":1,"1147":1,"1173":1,"2183":1}}],["trial",{"2":{"913":1}}],["triage",{"2":{"323":2}}],["trims",{"2":{"2861":1}}],["trimming",{"2":{"954":1}}],["trim",{"0":{"954":1},"2":{"888":2,"1021":1,"1026":1,"1318":1,"1376":1,"2528":1,"2861":1,"2864":1}}],["tripped",{"2":{"2468":1}}],["tripping",{"2":{"1041":1}}],["trips",{"2":{"854":1,"1440":1,"2456":2}}],["trip",{"2":{"852":2,"861":1,"874":1,"1070":2,"1076":1,"1102":2,"1137":1,"1370":1,"1439":1,"1792":1,"1850":1,"1852":1,"2282":1,"2320":1,"2383":2,"2398":1,"2496":1,"2498":1,"2546":1,"2767":1,"2774":1,"2811":1,"2850":1}}],["tricked",{"2":{"1185":1}}],["tricks",{"2":{"1079":1,"1792":1}}],["trick",{"2":{"851":1,"860":1}}],["trivial",{"2":{"832":1,"849":1,"1063":1,"1405":1}}],["triggering",{"2":{"2648":1}}],["triggered",{"2":{"1792":1,"1844":1}}],["triggers",{"2":{"663":3,"934":1,"983":1,"1035":1,"1106":1,"1107":1,"2106":1,"2156":1,"2184":1,"2537":1,"2542":1,"2767":1,"2802":1,"2830":1}}],["trigger",{"2":{"213":1,"390":1,"575":1,"663":4,"779":1,"961":1,"1104":1,"1108":1,"1332":1,"1337":1,"1338":1,"1413":1,"1589":1,"1609":1,"1623":1,"1741":1,"1768":1,"1792":4,"1925":1,"2156":1,"2289":1,"2542":1,"2634":1,"2661":1}}],["truly",{"2":{"1170":1,"1326":1}}],["truncating",{"2":{"994":1}}],["truncated",{"2":{"1620":1,"2491":1,"2498":1}}],["truncate",{"2":{"993":1}}],["truncates",{"2":{"308":1,"2177":1}}],["truthful",{"2":{"875":1,"1081":2}}],["truth",{"0":{"975":1,"1000":1},"2":{"844":4,"852":1,"872":1,"873":1,"875":1,"975":1,"1005":1,"1008":1,"1038":1,"1046":1,"1193":1,"1382":2,"1422":1}}],["trusts",{"2":{"1708":1}}],["trusting",{"2":{"1068":1}}],["trusted",{"2":{"933":1,"946":1,"1100":2,"1204":2,"1394":1,"1707":1,"1717":2,"1792":1,"2633":1}}],["trust",{"0":{"1716":1},"2":{"453":1,"1199":2,"1388":1,"1712":1,"1792":1,"2183":2,"2633":1,"2812":1}}],["true`",{"2":{"1792":3}}],["true|false>",{"2":{"719":7}}],["true",{"0":{"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1576":1,"1577":1,"2342":1},"2":{"10":1,"31":1,"33":1,"38":2,"102":1,"106":2,"107":1,"121":2,"150":1,"210":1,"216":1,"256":1,"301":1,"306":1,"334":1,"336":2,"378":1,"430":2,"436":1,"447":1,"449":1,"455":1,"476":2,"477":2,"478":2,"479":2,"502":1,"577":1,"584":3,"586":1,"592":1,"624":1,"673":1,"679":2,"718":1,"720":3,"723":2,"733":6,"734":1,"735":1,"736":3,"737":2,"747":3,"752":1,"763":1,"766":1,"768":2,"770":1,"771":2,"773":3,"774":1,"775":2,"781":2,"782":2,"783":1,"784":4,"785":3,"786":3,"788":2,"789":1,"812":1,"813":1,"814":1,"815":1,"826":2,"827":1,"841":1,"852":1,"860":1,"876":1,"887":4,"889":6,"891":2,"900":2,"903":3,"904":1,"917":1,"919":1,"929":1,"937":4,"956":2,"957":2,"958":3,"961":1,"963":2,"965":1,"966":1,"967":2,"977":4,"979":3,"980":1,"982":1,"986":3,"988":3,"989":1,"990":1,"994":2,"1021":2,"1022":1,"1023":2,"1026":2,"1031":1,"1041":1,"1053":3,"1054":1,"1057":2,"1058":6,"1059":2,"1062":8,"1063":1,"1067":6,"1068":2,"1069":3,"1070":4,"1075":1,"1086":1,"1102":4,"1127":1,"1135":1,"1141":1,"1145":1,"1146":1,"1147":3,"1148":2,"1150":5,"1152":2,"1153":1,"1154":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":2,"1162":5,"1176":1,"1177":8,"1178":1,"1179":1,"1193":1,"1196":2,"1197":1,"1199":1,"1217":5,"1220":2,"1221":1,"1227":1,"1229":2,"1258":1,"1320":1,"1338":1,"1339":1,"1340":3,"1341":1,"1356":8,"1357":1,"1358":10,"1359":1,"1360":3,"1369":1,"1372":3,"1374":3,"1376":2,"1386":3,"1391":1,"1393":1,"1395":1,"1408":3,"1410":3,"1413":2,"1415":3,"1416":6,"1417":7,"1446":3,"1447":2,"1448":1,"1449":4,"1450":1,"1453":5,"1454":4,"1458":5,"1459":1,"1462":2,"1463":3,"1464":4,"1469":2,"1470":2,"1482":1,"1483":3,"1489":2,"1493":1,"1494":2,"1498":1,"1499":1,"1501":1,"1502":1,"1503":2,"1504":2,"1505":2,"1511":3,"1513":1,"1514":1,"1515":4,"1516":2,"1517":1,"1518":3,"1520":4,"1521":1,"1525":1,"1529":6,"1534":2,"1543":6,"1548":2,"1553":8,"1554":4,"1555":1,"1556":1,"1558":4,"1559":5,"1563":1,"1567":1,"1569":2,"1570":1,"1571":3,"1576":1,"1579":1,"1580":3,"1581":10,"1582":1,"1587":1,"1588":1,"1597":1,"1598":1,"1603":1,"1604":1,"1606":1,"1607":1,"1608":2,"1616":1,"1617":3,"1618":2,"1619":1,"1620":1,"1621":1,"1622":1,"1623":1,"1625":1,"1632":1,"1633":4,"1640":1,"1641":2,"1644":2,"1646":3,"1651":1,"1661":1,"1662":3,"1663":2,"1669":1,"1670":1,"1671":1,"1678":1,"1683":1,"1689":2,"1690":2,"1697":2,"1698":4,"1706":1,"1707":1,"1708":1,"1709":1,"1711":1,"1712":1,"1713":1,"1714":1,"1715":1,"1716":1,"1721":1,"1722":2,"1732":1,"1735":1,"1736":1,"1752":2,"1753":2,"1758":5,"1759":3,"1763":1,"1764":1,"1767":1,"1769":1,"1770":3,"1771":1,"1776":1,"1778":1,"1779":2,"1780":1,"1781":1,"1792":148,"1800":2,"1803":2,"1804":2,"1810":4,"1824":1,"1825":1,"1827":1,"1832":1,"1836":4,"1841":2,"1842":1,"1843":1,"1844":4,"1850":1,"1851":2,"1852":3,"1856":3,"1861":1,"1863":8,"1867":1,"1877":1,"1879":2,"1892":2,"1893":4,"1897":2,"1898":4,"1899":1,"1900":1,"1907":3,"1909":2,"1911":2,"1912":3,"1916":2,"1917":2,"1918":1,"1922":1,"1927":1,"1931":3,"1936":2,"1937":2,"1940":1,"1941":1,"1942":1,"1944":8,"1951":3,"1952":3,"1953":3,"1954":3,"1955":4,"1956":1,"1958":3,"1959":2,"1960":11,"1966":2,"1967":4,"1973":2,"1974":2,"1975":1,"1979":2,"1980":2,"1981":4,"1982":1,"1983":1,"1994":4,"1995":3,"1999":1,"2000":2,"2001":1,"2003":1,"2009":2,"2010":3,"2011":3,"2012":1,"2017":1,"2018":1,"2019":1,"2020":1,"2021":1,"2027":1,"2028":1,"2029":1,"2033":1,"2035":1,"2037":1,"2038":1,"2039":1,"2040":2,"2042":3,"2047":1,"2054":1,"2055":1,"2058":2,"2059":2,"2060":1,"2061":2,"2062":1,"2063":1,"2064":1,"2066":1,"2067":2,"2068":2,"2069":2,"2073":2,"2075":3,"2077":3,"2080":3,"2094":1,"2106":1,"2107":1,"2109":1,"2111":2,"2112":1,"2113":1,"2123":10,"2124":1,"2126":2,"2127":6,"2128":8,"2130":2,"2131":1,"2132":10,"2138":1,"2139":1,"2142":1,"2146":1,"2147":2,"2173":1,"2174":1,"2175":2,"2183":2,"2184":9,"2187":3,"2193":2,"2223":1,"2254":3,"2255":8,"2257":6,"2264":1,"2265":5,"2267":1,"2272":2,"2273":3,"2274":2,"2277":1,"2297":2,"2308":2,"2330":4,"2333":1,"2336":1,"2337":2,"2338":2,"2342":3,"2350":1,"2351":2,"2353":2,"2354":2,"2360":1,"2375":6,"2377":2,"2378":2,"2379":5,"2380":3,"2381":2,"2382":3,"2383":3,"2389":1,"2392":1,"2406":1,"2410":1,"2415":3,"2423":1,"2426":1,"2427":1,"2429":4,"2431":1,"2434":2,"2441":1,"2451":1,"2453":1,"2455":1,"2470":1,"2471":1,"2476":3,"2481":1,"2484":2,"2486":4,"2502":1,"2528":1,"2530":2,"2532":3,"2535":2,"2537":2,"2549":4,"2551":4,"2554":7,"2555":2,"2565":4,"2575":2,"2586":1,"2587":2,"2591":2,"2607":2,"2618":1,"2629":2,"2634":4,"2635":1,"2655":1,"2656":2,"2664":1,"2689":2,"2692":1,"2697":1,"2701":2,"2734":1,"2737":1,"2746":1,"2750":3,"2758":1,"2761":1,"2763":1,"2769":4,"2798":2,"2802":1,"2803":1,"2804":5,"2808":1,"2810":1,"2814":6,"2815":1,"2821":1,"2825":1,"2829":2,"2830":1,"2835":1,"2841":3,"2842":1,"2847":1,"2848":1,"2851":1,"2852":1,"2855":2,"2864":1,"2871":1,"2879":1,"2880":1,"2881":1}}],["trained",{"2":{"1403":1}}],["trailing",{"2":{"2529":1,"2531":1}}],["trail",{"2":{"1183":1,"1185":1,"1188":1,"1204":1,"1207":1,"2492":1}}],["trails",{"2":{"905":1}}],["tracing",{"2":{"1110":2}}],["traces",{"2":{"2384":1}}],["traced",{"2":{"1409":1}}],["traceid",{"0":{"1677":1},"2":{"1109":1,"1111":3,"1670":2,"1677":2,"1678":1,"1792":1,"2255":1,"2558":2}}],["trace",{"0":{"2405":1},"2":{"995":1,"1111":1,"1792":1,"1845":1,"2226":1,"2242":1,"2364":4,"2366":1,"2394":1,"2405":2,"2527":1,"2663":1,"2750":1,"2798":1,"2860":1}}],["tracks",{"2":{"2546":1}}],["tracked",{"2":{"2422":1}}],["tracker",{"2":{"849":1,"852":1}}],["track",{"2":{"879":1,"967":2,"1249":1,"1254":1,"1303":1,"1355":1,"1388":1,"1792":4,"2049":3,"2635":3}}],["trackingid",{"2":{"2040":1}}],["tracking",{"2":{"878":1,"880":1,"894":1,"905":1,"907":1,"1037":1,"1110":1,"1259":1,"1352":1,"1361":1,"1366":1,"1460":1,"1620":1,"1680":1,"1848":1,"2040":2,"2164":1,"2375":1,"2422":1,"2476":1}}],["traversal",{"2":{"860":1}}],["travels",{"2":{"852":1,"1925":1,"2517":1}}],["travel",{"2":{"75":1,"306":1,"974":1,"2182":1,"2521":1,"2537":1}}],["traditionally",{"2":{"1064":1}}],["traditional",{"0":{"879":1,"945":1,"968":1,"973":1,"1025":1,"1026":1,"1303":1,"1319":1,"1320":1,"1366":1},"1":{"969":1,"1026":1,"1320":1,"1321":1},"2":{"849":1,"879":1,"910":1,"911":1,"922":1,"941":1,"945":1,"948":1,"961":1,"968":2,"969":1,"1006":2,"1007":1,"1008":1,"1011":1,"1027":1,"1036":1,"1076":1,"1098":1,"1181":1,"1191":1,"1200":1,"1203":1,"1205":1,"1206":1,"1303":1,"1322":1,"1350":1,"1366":1,"1382":1,"1445":1,"2200":1}}],["tradeoffs",{"0":{"876":1},"2":{"876":2}}],["trade",{"2":{"88":1,"841":1,"1382":1,"1404":1,"2534":1}}],["traffic",{"0":{"1171":1},"2":{"307":1,"480":1,"1014":1,"1141":1,"1159":1,"1164":1,"1180":1,"1255":1,"1327":1,"1349":1,"1363":1,"1430":1,"1767":2,"1792":3,"1822":1,"1824":1,"2063":1,"2088":1,"2177":1,"2422":1,"2634":2}}],["transmission",{"2":{"1207":1}}],["transmitted",{"2":{"515":1,"2203":1}}],["transmits",{"2":{"64":1,"1198":1,"2451":1}}],["transit",{"2":{"1185":1}}],["transiently",{"2":{"1032":1,"1739":1,"2287":1}}],["transient",{"2":{"213":1,"214":1,"569":1,"575":1,"845":1,"857":1,"868":1,"1101":1,"1135":1,"1151":1,"1152":1,"1177":1,"1180":1,"1217":1,"1224":1,"1250":1,"1586":1,"1622":1,"1624":1,"1743":1,"1874":1,"2502":1}}],["transports",{"2":{"1213":1,"1215":3,"1220":1,"1221":1,"1234":2,"1237":1,"1792":3,"1887":1}}],["transport",{"0":{"1983":1},"2":{"874":1,"1047":1,"1237":1,"1792":4,"1823":1,"1824":1,"1825":1,"1887":1,"1980":1,"1983":1,"2347":1,"2481":1,"2498":1}}],["transparently",{"2":{"187":1,"1180":1,"2294":1,"2649":1}}],["transparent",{"2":{"182":1,"868":1,"1100":1,"1254":1,"1338":1,"1664":1,"2291":1}}],["translators",{"2":{"2712":1}}],["translations",{"2":{"1422":1}}],["translation",{"2":{"871":1,"1909":1}}],["translating",{"2":{"852":1}}],["translates",{"2":{"1422":2,"2398":1}}],["translate",{"2":{"849":1,"1401":1,"1403":1}}],["transfers",{"2":{"965":1,"1323":1,"1351":1}}],["transfer",{"2":{"622":1,"849":6,"854":1,"860":1,"918":1,"1255":1,"1258":1,"1340":1,"1516":1,"1792":2,"1916":2,"1917":2,"1931":2,"2265":1,"2549":2,"2802":2,"2814":2,"2824":1}}],["transforms",{"2":{"2772":1}}],["transformers",{"2":{"1335":1}}],["transform",{"0":{"439":1,"1332":1,"1338":1,"1339":1,"1921":1,"2810":1},"2":{"435":1,"439":4,"448":1,"449":2,"453":1,"876":1,"911":1,"1037":2,"1104":1,"1105":1,"1203":1,"1328":1,"1332":2,"1333":1,"1338":1,"1341":1,"1342":1,"1349":1,"1351":1,"1415":2,"1915":1,"1921":2,"1928":1,"2164":1,"2549":5,"2806":1,"2807":2,"2809":1,"2814":1,"2815":1,"2816":1,"2817":1}}],["transformations",{"2":{"1205":1}}],["transformation",{"2":{"304":1,"876":1,"907":1,"1011":1,"1343":1,"1351":2}}],["transactions",{"0":{"2867":1},"2":{"845":1,"851":1,"860":1,"864":2,"865":1,"899":1,"909":1,"910":2,"992":1,"993":2,"1079":1,"1081":1,"1130":1,"1174":2,"1324":1,"1391":1,"1628":2,"1792":2,"2114":1,"2266":2}}],["transactionally",{"2":{"852":1}}],["transactional",{"2":{"695":1,"779":1,"863":2,"864":1,"1075":1,"1079":2,"1082":1,"1354":1,"1632":1,"2167":1,"2867":1,"2869":1}}],["transaction",{"0":{"622":1,"901":1},"2":{"576":1,"624":1,"625":1,"849":1,"852":2,"855":1,"860":5,"863":3,"864":4,"865":2,"876":1,"879":1,"880":1,"881":2,"899":1,"901":2,"902":1,"903":2,"907":1,"909":1,"974":1,"986":1,"992":2,"993":1,"994":1,"1005":1,"1037":1,"1070":4,"1073":1,"1074":3,"1078":2,"1079":2,"1094":1,"1099":1,"1101":1,"1102":10,"1106":1,"1121":1,"1324":1,"1376":1,"1395":2,"1442":1,"1528":2,"1592":1,"1593":1,"1624":1,"1792":9,"1850":1,"1851":5,"2267":1,"2342":1,"2382":6,"2525":1,"2527":3,"2528":2,"2531":1,"2533":3,"2739":1,"2841":1,"2851":1,"2855":1,"2860":1,"2862":3,"2863":1,"2867":2,"2868":1,"2869":2}}],["tiny",{"2":{"1435":1}}],["ticket",{"2":{"1193":3,"1403":1}}],["tickets",{"2":{"323":4,"1193":1}}],["timing",{"2":{"872":1,"1792":7}}],["timetz",{"2":{"1792":1,"1856":2,"2224":1,"2450":1,"2456":2,"2457":1}}],["timers",{"2":{"1792":1,"2024":1,"2632":1}}],["timezone",{"2":{"1060":1,"1687":1,"1792":2,"2456":1}}],["timeless",{"2":{"913":1}}],["timelines",{"2":{"834":1}}],["timed",{"2":{"140":1,"1669":1,"1672":1,"1678":1,"1792":1,"2101":1,"2253":1,"2255":2}}],["timescaledb",{"2":{"848":1}}],["timestamps",{"2":{"1856":2,"2050":1,"2224":1,"2453":1,"2454":1,"2455":1}}],["timestamptz",{"2":{"585":1,"1213":5,"1307":1,"1309":1,"1310":1,"1321":1,"1336":2,"1355":1,"1372":1,"1792":1,"1806":1,"1856":2,"2224":1,"2450":1,"2456":2,"2457":1,"2803":2,"2829":2,"2836":2,"2845":2}}],["timestamp",{"0":{"1856":1},"2":{"585":1,"913":1,"952":1,"956":5,"961":1,"977":1,"980":6,"982":1,"990":6,"995":1,"1050":1,"1056":1,"1139":1,"1150":1,"1191":1,"1216":1,"1370":3,"1374":4,"1565":1,"1792":5,"1800":1,"1806":1,"1809":2,"1810":1,"1852":1,"1856":1,"2224":1,"2383":1,"2450":1,"2451":1,"2456":1,"2803":1,"2850":3}}],["timespan",{"2":{"211":1,"1034":1,"1731":1,"2253":1,"2461":1}}],["times",{"2":{"140":1,"845":1,"847":1,"848":1,"872":1,"948":1,"966":1,"1032":1,"1082":1,"1139":1,"1255":1,"1382":1,"1403":1,"1792":1,"2459":1,"2504":1,"2533":1,"2540":1,"2580":1,"2622":1,"2635":2,"2789":1,"2845":1}}],["timeseries",{"2":{"106":3,"1067":5,"1150":7,"1520":1,"1529":3}}],["time",{"0":{"872":1,"1001":1,"1103":1,"1181":1,"1302":1,"1304":1,"1320":1,"1372":1,"2210":1,"2376":1,"2377":1,"2836":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"2211":1,"2212":1},"2":{"91":1,"108":1,"110":1,"120":1,"122":1,"123":1,"214":1,"217":1,"232":1,"267":1,"268":1,"278":1,"386":1,"388":1,"395":3,"480":1,"527":1,"575":1,"776":2,"788":3,"835":1,"841":1,"843":1,"848":1,"849":1,"851":1,"852":3,"856":1,"859":2,"860":1,"863":1,"864":5,"866":1,"869":1,"871":1,"872":4,"873":1,"874":1,"877":1,"892":2,"911":2,"920":1,"949":1,"956":5,"969":1,"975":1,"978":1,"982":2,"995":1,"997":2,"1004":1,"1009":1,"1021":1,"1026":1,"1037":3,"1050":1,"1064":1,"1066":1,"1067":1,"1070":1,"1071":1,"1076":1,"1092":1,"1094":1,"1098":1,"1102":1,"1103":2,"1107":1,"1121":1,"1123":1,"1127":1,"1132":1,"1150":2,"1158":1,"1159":1,"1165":1,"1181":1,"1205":2,"1206":1,"1235":1,"1254":3,"1255":1,"1259":1,"1281":2,"1302":3,"1303":2,"1309":2,"1322":1,"1323":1,"1324":1,"1325":1,"1327":1,"1372":1,"1374":4,"1376":1,"1382":1,"1384":2,"1385":1,"1386":2,"1390":1,"1396":1,"1398":1,"1400":3,"1401":3,"1402":2,"1403":2,"1405":1,"1406":1,"1407":1,"1409":1,"1420":1,"1422":1,"1440":1,"1464":1,"1519":1,"1521":1,"1525":1,"1535":1,"1743":1,"1792":8,"1852":2,"1856":3,"1857":1,"1861":1,"1950":2,"1951":1,"1952":1,"1955":1,"1956":1,"1958":1,"1974":1,"2049":2,"2112":1,"2116":1,"2117":1,"2119":3,"2130":1,"2164":3,"2165":2,"2175":1,"2210":1,"2223":1,"2224":1,"2245":1,"2247":2,"2297":1,"2376":1,"2377":1,"2379":2,"2383":2,"2386":1,"2388":1,"2397":1,"2398":1,"2399":1,"2410":1,"2450":1,"2451":4,"2454":1,"2456":3,"2464":1,"2466":2,"2470":1,"2481":2,"2493":2,"2494":1,"2500":1,"2607":1,"2621":2,"2701":1,"2702":1,"2704":3,"2764":1,"2776":1,"2789":1,"2792":1,"2815":1,"2818":1,"2825":1,"2827":2,"2830":1,"2835":1,"2836":1,"2837":1,"2838":1,"2868":1}}],["timeouts",{"0":{"2765":1},"2":{"213":1,"232":1,"424":1,"1032":1,"1105":2,"1109":1,"1111":1,"1182":1,"1329":1,"1672":2,"1739":1,"1741":1,"2253":1,"2287":1,"2289":1,"2307":1,"2759":1,"2813":1}}],["timeouterrormapping",{"2":{"139":1,"140":2,"141":1,"1669":1,"1670":1,"1672":1,"1678":1,"1792":1,"2253":1,"2255":2}}],["timeout",{"0":{"132":1,"136":1,"140":1,"211":1,"277":2,"574":1,"1034":1,"1672":1,"1731":1,"2253":1,"2756":2},"1":{"133":1,"134":1,"135":1,"136":1,"137":1,"138":1,"139":1,"140":1,"141":1,"142":1,"143":1},"2":{"90":2,"132":2,"133":5,"134":1,"136":2,"137":1,"138":1,"139":2,"140":3,"143":1,"152":2,"203":3,"207":1,"208":1,"211":9,"213":4,"214":2,"218":2,"231":2,"277":6,"281":2,"424":1,"447":1,"574":1,"579":2,"1011":1,"1017":2,"1019":4,"1026":6,"1030":1,"1032":1,"1033":1,"1034":4,"1104":1,"1105":1,"1109":1,"1111":3,"1113":1,"1127":1,"1220":1,"1222":1,"1340":1,"1398":1,"1426":1,"1430":1,"1431":1,"1616":2,"1670":1,"1672":3,"1679":2,"1680":1,"1726":1,"1731":8,"1733":1,"1736":1,"1740":4,"1741":1,"1743":1,"1775":1,"1792":12,"1837":2,"1864":2,"1917":1,"1928":3,"1991":2,"2094":1,"2101":1,"2113":1,"2193":3,"2210":1,"2212":10,"2222":1,"2253":2,"2255":6,"2258":1,"2264":7,"2287":1,"2288":4,"2289":1,"2307":1,"2320":1,"2323":1,"2502":2,"2505":2,"2535":1,"2537":1,"2549":3,"2581":1,"2591":3,"2687":1,"2695":1,"2756":2,"2762":3,"2763":1,"2764":1,"2765":3,"2766":2,"2810":1,"2814":1}}],["tired",{"2":{"861":1}}],["tidies",{"2":{"2830":1}}],["tidy",{"2":{"1435":1}}],["tidb",{"2":{"848":2}}],["tidvxenk9gsqapyi82xduquaigq5odbiecfrtq7wfwtht3ffx2s+noijvfcaw90z",{"2":{"62":2}}],["tiff",{"2":{"747":1,"1792":1,"2123":1,"2125":1}}],["tightened",{"2":{"2457":1}}],["tightens",{"2":{"2427":1}}],["tighter",{"0":{"2400":1}}],["tight",{"0":{"1418":1},"2":{"531":1,"847":1,"1081":2,"1386":1,"1401":1}}],["tip",{"2":{"369":1,"1132":1,"1448":1,"1609":1,"1708":1,"1792":1,"1973":1,"2004":1,"2007":1,"2010":1,"2024":1,"2059":1,"2193":1}}],["tied",{"2":{"1209":1,"1867":2}}],["tie",{"2":{"1014":1,"1127":1}}],["ties",{"2":{"306":1,"937":1}}],["tiers",{"0":{"1265":1}}],["tiered",{"0":{"107":1},"2":{"1792":1,"2380":1}}],["tier",{"2":{"2":1,"107":10,"307":1,"874":1,"876":1,"1014":1,"1015":2,"1121":1,"1127":1,"1193":1,"1265":1,"1266":1,"1278":1,"1280":2,"1281":1,"1792":2,"2177":2,"2380":2,"2760":1}}],["title>",{"2":{"1685":1,"1792":1}}],["title>talking",{"2":{"1685":1,"1792":1}}],["titles",{"2":{"1111":1}}],["title",{"2":{"140":1,"335":3,"372":1,"415":2,"819":1,"834":3,"894":1,"913":2,"918":7,"919":4,"920":2,"938":2,"995":5,"996":2,"1073":2,"1111":5,"1342":1,"1366":1,"1386":1,"1408":1,"1409":1,"1429":5,"1442":1,"1553":1,"1558":1,"1567":2,"1669":5,"1671":2,"1672":1,"1673":4,"1676":1,"1677":1,"1678":7,"1792":9,"1818":1,"1898":1,"2253":1,"2254":1,"2255":14,"2271":1,"2273":2,"2303":1,"2319":2,"2359":2,"2586":3,"2590":1,"2611":2,"2731":1,"2813":2,"2845":1,"2846":1}}],["t",{"0":{"877":1,"910":1,"1210":1,"1396":1,"1410":1,"1432":1,"2721":1,"2722":1,"2799":1},"1":{"911":1},"2":{"23":1,"33":1,"101":1,"129":1,"136":5,"168":3,"175":2,"177":1,"214":4,"302":1,"303":2,"304":1,"307":1,"308":2,"319":2,"351":1,"354":1,"372":1,"373":1,"374":1,"376":1,"389":1,"390":1,"415":4,"454":1,"462":4,"463":3,"464":4,"466":5,"480":2,"490":1,"499":1,"524":1,"528":1,"583":1,"584":1,"587":1,"599":1,"602":1,"639":1,"663":1,"684":1,"691":1,"737":1,"767":1,"784":1,"831":1,"838":1,"841":2,"843":2,"844":1,"845":2,"847":3,"848":5,"849":2,"851":1,"852":5,"856":1,"857":1,"859":2,"860":1,"868":1,"869":2,"871":3,"872":1,"873":3,"874":2,"876":4,"877":3,"884":1,"896":1,"901":1,"903":1,"904":2,"911":1,"918":2,"933":1,"934":2,"947":1,"948":1,"953":1,"960":1,"961":2,"975":1,"984":1,"992":1,"994":1,"1005":1,"1006":1,"1036":1,"1040":1,"1046":1,"1050":1,"1065":1,"1067":2,"1068":1,"1073":2,"1074":1,"1075":1,"1076":3,"1077":3,"1078":1,"1079":5,"1081":1,"1104":1,"1129":2,"1130":1,"1134":1,"1137":1,"1139":1,"1140":2,"1145":2,"1150":3,"1166":1,"1170":1,"1171":1,"1205":1,"1210":1,"1228":1,"1238":1,"1243":1,"1251":1,"1281":1,"1326":2,"1328":2,"1337":1,"1351":1,"1358":1,"1370":1,"1372":1,"1374":1,"1376":1,"1382":2,"1385":6,"1386":8,"1388":2,"1390":1,"1391":2,"1393":3,"1394":2,"1395":2,"1396":3,"1398":1,"1399":1,"1400":2,"1401":4,"1402":7,"1403":4,"1404":1,"1405":2,"1408":3,"1409":1,"1410":1,"1411":1,"1412":1,"1414":1,"1415":1,"1421":1,"1422":1,"1428":1,"1429":1,"1431":1,"1435":1,"1437":3,"1441":3,"1442":1,"1443":1,"1511":1,"1522":1,"1527":2,"1543":1,"1572":1,"1580":1,"1609":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1768":1,"1770":1,"1774":1,"1792":15,"1801":1,"1823":1,"1825":3,"1832":1,"1833":1,"1849":1,"1878":1,"1941":1,"1974":1,"1983":1,"2016":1,"2024":1,"2127":1,"2156":1,"2157":1,"2160":1,"2177":2,"2180":1,"2183":1,"2193":1,"2195":1,"2207":1,"2250":1,"2297":1,"2303":2,"2336":1,"2337":1,"2342":1,"2359":1,"2380":1,"2389":1,"2392":1,"2394":2,"2398":2,"2402":1,"2404":1,"2406":1,"2415":1,"2421":1,"2425":1,"2428":1,"2432":1,"2433":1,"2434":1,"2438":3,"2452":3,"2481":1,"2483":1,"2489":1,"2533":1,"2534":1,"2543":1,"2586":1,"2589":1,"2590":1,"2607":1,"2608":1,"2618":1,"2632":1,"2634":1,"2661":1,"2685":2,"2734":1,"2741":1,"2751":1,"2768":1,"2815":1,"2820":1,"2827":1,"2830":1,"2835":4,"2836":1,"2840":1,"2847":1,"2850":1,"2854":1,"2867":2,"2868":1}}],["terrible",{"0":{"948":1}}],["teradata",{"2":{"918":1}}],["terminate",{"2":{"2362":1}}],["terminals",{"2":{"2535":1}}],["terminal",{"2":{"848":1,"1418":2,"1419":1,"1433":2,"1792":1,"1957":1,"2379":1,"2535":1,"2667":1,"2673":1,"2678":1,"2694":1,"2695":1,"2785":1}}],["terms",{"2":{"1073":1,"1132":1,"2678":1,"2695":1}}],["term",{"2":{"847":1,"848":2,"868":1,"1529":2}}],["techstart",{"2":{"1189":1}}],["techempower",{"2":{"874":1}}],["techniques",{"2":{"1443":1}}],["technique",{"0":{"1139":1},"2":{"1139":2,"2741":1,"2868":1}}],["technical",{"2":{"843":1,"847":1,"1368":1,"1380":1,"1386":2,"1404":1,"2625":1}}],["technology",{"0":{"2776":1},"2":{"859":1,"1403":1}}],["technologies",{"2":{"841":1}}],["tells",{"2":{"873":1,"934":1,"935":1,"979":1,"1077":1,"1134":1,"1138":1,"1216":1,"1233":1}}],["telling",{"2":{"868":1,"1948":1,"2378":1}}],["tell",{"2":{"851":1,"1041":1,"1396":2,"1400":1,"1402":1,"2540":1}}],["telemetry",{"2":{"2":1}}],["tens",{"2":{"1076":1,"2878":1}}],["tension",{"2":{"841":2}}],["tenancy",{"0":{"1102":1},"2":{"1066":1}}],["tenant",{"0":{"106":1,"1070":1},"2":{"1037":1,"1066":1,"1070":7,"1102":4,"1121":1,"1127":1,"1542":2,"1546":2,"1792":3,"1852":2,"2383":2,"2438":1}}],["tend",{"2":{"859":1,"873":1,"1133":1,"1403":1}}],["ten",{"2":{"851":1,"860":2,"871":1,"2389":1,"2400":1}}],["tenets",{"2":{"843":1}}],["tedious",{"2":{"1378":1,"1390":1}}],["ted",{"2":{"840":1}}],["tears",{"2":{"2742":1,"2881":1}}],["teardown",{"0":{"713":1,"2112":1,"2532":1,"2533":1,"2871":1},"1":{"714":1,"715":1,"716":1,"717":1},"2":{"239":1,"695":1,"697":1,"705":2,"706":1,"707":1,"711":1,"714":1,"715":1,"716":2,"1789":1,"1792":8,"2093":1,"2094":4,"2098":1,"2103":1,"2106":1,"2110":1,"2111":3,"2112":3,"2114":1,"2157":1,"2158":1,"2159":1,"2167":2,"2221":3,"2530":1,"2531":2,"2532":7,"2533":10,"2534":5,"2536":2,"2537":7,"2543":1,"2545":1,"2546":3,"2740":1,"2869":1,"2870":1,"2871":3,"2872":2,"2873":2,"2875":2,"2878":1,"2882":1}}],["teaches",{"2":{"851":1,"861":1}}],["teamchat",{"2":{"1326":1}}],["teams",{"2":{"874":1,"876":1,"947":1,"948":1,"1127":1}}],["team",{"2":{"641":5,"864":2,"865":1,"866":1,"868":2,"869":1,"871":1,"872":4,"873":1,"877":1,"1123":1,"1314":1,"1326":3,"1382":3,"2430":1,"2438":1,"2858":1}}],["temporary",{"0":{"2740":1},"2":{"851":1,"1032":1,"1391":1,"1394":1,"1395":2,"1396":4,"1739":1,"2287":1}}],["temporarily",{"2":{"174":1,"2869":1}}],["templated",{"2":{"1792":1,"2038":1,"2040":1,"2476":1,"2477":1}}],["templates",{"2":{"1581":1,"1792":1,"2038":4,"2040":1,"2224":1,"2270":2,"2286":1,"2476":1}}],["template",{"0":{"1809":1,"2185":1,"2873":1},"2":{"493":1,"544":1,"696":1,"697":1,"705":2,"834":1,"1065":1,"1075":1,"1079":1,"1082":2,"1086":1,"1094":2,"1127":2,"1218":1,"1575":1,"1684":1,"1685":1,"1773":1,"1792":1,"1809":2,"2040":1,"2092":1,"2114":1,"2167":2,"2170":1,"2270":2,"2277":1,"2372":1,"2389":1,"2474":1,"2498":1,"2522":1,"2533":2,"2534":4,"2545":2,"2546":3,"2740":1,"2795":1,"2873":9}}],["templating",{"2":{"452":1,"868":1,"2040":1,"2474":1}}],["temp",{"0":{"2530":1},"2":{"238":1,"239":1,"583":2,"584":2,"587":1,"698":1,"874":1,"919":1,"933":1,"934":1,"935":1,"936":1,"1055":1,"1056":2,"1057":1,"1058":1,"1076":1,"1078":1,"1079":1,"1130":1,"1185":1,"1188":1,"1192":1,"1376":5,"1378":1,"1395":1,"1396":2,"1792":4,"2094":1,"2099":1,"2109":1,"2110":2,"2156":1,"2221":1,"2337":3,"2527":1,"2529":1,"2530":3,"2542":1,"2546":1,"2840":1,"2854":1,"2855":2,"2858":1,"2859":1,"2860":1,"2862":1,"2866":2,"2878":1}}],["textnonprintablethreshold",{"2":{"1792":1,"2123":1,"2125":1}}],["textual",{"2":{"1792":1,"2125":1,"2482":1}}],["texttestbuffersize",{"2":{"1792":1,"2123":1,"2125":1}}],["texttextsrc",{"2":{"1570":1,"1571":1,"1577":1}}],["textresponsenullhandling",{"0":{"1855":1,"2596":1},"2":{"556":1,"1792":1,"1836":1,"1853":1,"2594":1,"2596":1,"2597":1,"2666":1}}],["text>",{"2":{"318":3}}],["text",{"0":{"1334":1,"2394":1,"2496":1,"2726":1},"1":{"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1},"2":{"9":1,"21":2,"31":5,"33":3,"37":7,"38":9,"39":6,"40":1,"50":1,"60":3,"61":3,"62":3,"71":3,"73":1,"90":1,"106":2,"107":1,"117":2,"119":1,"128":4,"157":4,"166":1,"167":1,"169":2,"184":5,"186":4,"187":8,"188":1,"206":3,"207":5,"208":5,"209":7,"210":4,"212":1,"215":1,"229":1,"247":1,"248":2,"250":2,"251":1,"254":1,"255":1,"256":1,"257":3,"258":1,"263":4,"264":1,"289":2,"290":2,"291":3,"298":7,"299":1,"303":1,"308":4,"309":8,"310":9,"312":11,"313":9,"319":4,"322":3,"324":1,"326":3,"332":4,"333":3,"334":2,"335":2,"352":3,"360":6,"361":7,"362":1,"365":6,"366":7,"370":1,"378":2,"379":1,"383":3,"386":6,"388":2,"389":1,"392":1,"393":1,"408":4,"423":2,"429":1,"439":6,"447":6,"449":2,"452":4,"454":4,"458":1,"466":3,"467":3,"468":3,"469":2,"484":1,"487":5,"488":2,"489":3,"490":4,"491":3,"492":2,"493":7,"494":1,"503":2,"510":2,"511":2,"512":1,"520":2,"521":2,"522":2,"523":1,"527":1,"531":2,"532":3,"539":4,"541":1,"543":2,"544":6,"548":1,"549":2,"550":1,"555":1,"582":1,"584":3,"585":1,"586":2,"592":4,"593":4,"594":4,"611":2,"617":1,"664":2,"665":2,"677":1,"679":8,"686":1,"700":2,"706":1,"723":8,"733":6,"734":2,"735":2,"736":2,"747":5,"752":1,"753":2,"756":4,"757":2,"760":2,"761":2,"762":1,"763":1,"764":3,"765":1,"766":1,"768":2,"770":2,"771":2,"774":4,"776":2,"782":3,"783":1,"784":3,"785":1,"797":11,"798":9,"799":3,"801":2,"812":2,"813":6,"814":2,"815":4,"817":3,"826":4,"827":2,"830":1,"848":1,"880":2,"881":1,"882":2,"883":1,"884":1,"887":1,"888":4,"892":1,"893":1,"903":2,"904":2,"911":1,"913":7,"914":2,"915":3,"924":3,"928":5,"929":3,"930":2,"934":7,"935":1,"936":9,"938":1,"952":1,"956":9,"957":3,"977":3,"980":2,"982":4,"983":1,"990":2,"994":2,"995":4,"1019":2,"1020":4,"1021":6,"1029":1,"1031":3,"1033":4,"1038":1,"1040":3,"1042":1,"1046":1,"1050":5,"1051":1,"1054":5,"1055":11,"1056":6,"1058":1,"1060":7,"1061":1,"1067":2,"1068":5,"1092":1,"1096":3,"1105":5,"1139":2,"1142":1,"1149":1,"1150":4,"1187":3,"1188":1,"1189":4,"1191":9,"1192":4,"1193":10,"1195":1,"1196":1,"1197":4,"1213":6,"1214":9,"1215":4,"1216":3,"1232":15,"1234":14,"1235":3,"1236":4,"1237":2,"1239":4,"1255":1,"1279":2,"1307":5,"1308":6,"1309":11,"1310":4,"1320":4,"1321":13,"1328":1,"1332":8,"1334":1,"1335":12,"1336":9,"1338":19,"1339":20,"1341":3,"1342":3,"1347":2,"1348":1,"1355":7,"1357":1,"1358":6,"1360":2,"1362":2,"1368":2,"1370":2,"1371":1,"1372":14,"1373":3,"1374":7,"1376":12,"1387":2,"1390":1,"1394":1,"1395":5,"1396":1,"1398":8,"1399":2,"1410":1,"1413":3,"1426":4,"1427":5,"1429":4,"1430":1,"1431":4,"1458":19,"1473":3,"1477":2,"1480":3,"1501":4,"1502":1,"1504":5,"1529":2,"1547":15,"1567":9,"1651":2,"1655":7,"1664":8,"1687":5,"1689":9,"1725":3,"1727":2,"1732":4,"1733":2,"1736":5,"1738":1,"1742":3,"1743":1,"1744":2,"1782":1,"1792":52,"1806":5,"1821":1,"1823":1,"1824":6,"1849":1,"1852":1,"1853":1,"1855":2,"1882":7,"1884":5,"1885":1,"1886":1,"1887":2,"1921":6,"1922":6,"1924":8,"1926":2,"1930":2,"1936":5,"1940":1,"1943":6,"1968":1,"1973":4,"1974":3,"2010":3,"2011":3,"2076":1,"2078":1,"2079":7,"2109":2,"2111":1,"2125":1,"2126":1,"2127":1,"2129":3,"2130":1,"2131":3,"2132":1,"2145":1,"2147":6,"2176":7,"2177":4,"2181":1,"2183":11,"2184":4,"2185":1,"2187":13,"2192":2,"2194":1,"2202":1,"2204":3,"2206":2,"2207":1,"2214":1,"2216":2,"2223":1,"2226":1,"2247":1,"2264":11,"2271":1,"2277":7,"2283":2,"2285":3,"2290":3,"2292":5,"2293":4,"2294":8,"2296":1,"2304":2,"2310":1,"2313":1,"2322":2,"2332":1,"2333":3,"2335":2,"2336":1,"2337":3,"2338":4,"2339":1,"2344":4,"2346":2,"2348":3,"2354":3,"2370":1,"2375":18,"2380":2,"2383":1,"2394":4,"2395":2,"2397":1,"2415":1,"2432":1,"2468":1,"2481":6,"2493":1,"2496":2,"2498":2,"2528":1,"2530":2,"2535":3,"2540":1,"2549":19,"2572":1,"2575":7,"2580":2,"2586":3,"2587":4,"2588":1,"2589":3,"2596":3,"2597":1,"2607":3,"2614":2,"2621":1,"2626":1,"2653":3,"2655":2,"2656":1,"2665":2,"2678":1,"2712":1,"2726":1,"2734":3,"2762":11,"2763":4,"2764":3,"2766":8,"2768":1,"2775":2,"2803":9,"2810":8,"2812":4,"2815":12,"2822":1,"2829":17,"2830":1,"2834":3,"2836":14,"2848":1,"2849":2,"2850":2,"2864":1,"2866":2,"2868":1,"2871":1}}],["testcase",{"2":{"2535":1}}],["testconnectionstrings",{"2":{"1177":1,"1617":1,"1618":1,"1621":1,"1633":1,"1792":1,"2223":1,"2486":1}}],["testfixture",{"2":{"2435":1}}],["test`",{"2":{"1792":1}}],["testability",{"0":{"1393":1},"2":{"860":1,"1037":1,"1378":1,"1393":1,"2258":1}}],["testable",{"2":{"859":1,"860":1,"1247":1,"1378":1,"1792":1,"2107":1}}],["testrunnertests",{"2":{"2546":1}}],["testrunner",{"0":{"2537":1},"2":{"693":1,"699":1,"703":1,"704":1,"705":1,"708":1,"710":4,"713":1,"714":1,"716":1,"1792":6,"1802":1,"2092":3,"2093":1,"2094":2,"2095":2,"2096":1,"2097":2,"2098":1,"2102":1,"2106":1,"2110":1,"2111":2,"2112":1,"2532":2,"2534":2,"2536":1,"2537":6,"2740":1,"2795":1,"2800":1,"2861":1,"2871":3,"2872":1,"2873":1,"2875":1,"2876":1,"2877":3,"2880":1}}],["test",{"0":{"239":1,"689":1,"693":1,"698":1,"703":1,"708":1,"713":1,"988":1,"989":1,"992":1,"1044":1,"1074":1,"1258":1,"1442":1,"2092":1,"2158":1,"2167":1,"2407":1,"2465":1,"2472":1,"2526":2,"2528":1,"2534":1,"2739":1,"2741":1,"2758":1,"2800":1,"2863":1,"2872":1,"2873":1},"1":{"690":1,"691":1,"692":1,"694":1,"695":1,"696":1,"697":1,"699":1,"700":1,"701":1,"702":1,"704":1,"705":1,"706":1,"707":1,"709":1,"710":1,"711":1,"712":1,"714":1,"715":1,"716":1,"717":1,"2093":1,"2094":1,"2095":1,"2096":1,"2097":1,"2098":1,"2099":1,"2100":1,"2101":1,"2102":1,"2103":1,"2104":1,"2105":1,"2106":1,"2107":1,"2108":1,"2109":1,"2110":1,"2111":1,"2112":1,"2113":1,"2114":1,"2527":2,"2528":2,"2529":2,"2530":2,"2531":2,"2532":2,"2533":2,"2534":2,"2535":2,"2536":2,"2537":2,"2538":2,"2864":1,"2865":1,"2866":1,"2867":1,"2868":1},"2":{"221":1,"239":13,"689":4,"691":2,"692":1,"693":6,"694":1,"695":4,"696":1,"697":3,"698":4,"701":1,"702":2,"703":5,"705":1,"706":1,"707":3,"708":5,"710":4,"711":1,"712":2,"713":5,"716":1,"717":3,"747":2,"753":2,"757":2,"758":1,"776":1,"782":2,"784":2,"786":2,"835":1,"837":1,"860":2,"865":3,"872":1,"874":3,"875":3,"876":3,"913":3,"915":1,"920":1,"930":20,"938":1,"979":1,"986":5,"987":1,"988":3,"989":10,"990":4,"991":3,"992":5,"993":1,"994":6,"1037":1,"1073":5,"1074":7,"1075":3,"1076":2,"1078":6,"1079":8,"1080":3,"1081":1,"1082":4,"1086":1,"1094":7,"1095":1,"1254":3,"1255":6,"1277":1,"1284":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1305":1,"1307":1,"1318":1,"1379":2,"1385":1,"1393":7,"1401":1,"1402":3,"1403":1,"1405":1,"1419":2,"1423":2,"1442":3,"1443":1,"1775":1,"1789":5,"1792":42,"1796":1,"1802":3,"2089":1,"2092":8,"2094":3,"2095":8,"2096":2,"2097":3,"2098":6,"2099":2,"2101":1,"2102":2,"2103":1,"2105":1,"2106":8,"2107":2,"2109":2,"2110":6,"2111":9,"2113":1,"2114":2,"2153":5,"2155":5,"2156":1,"2157":1,"2158":2,"2159":3,"2167":11,"2221":6,"2231":1,"2247":9,"2278":1,"2313":1,"2346":1,"2435":2,"2452":1,"2456":1,"2465":2,"2498":3,"2525":4,"2526":6,"2527":12,"2528":5,"2529":2,"2530":6,"2531":6,"2532":11,"2533":5,"2534":10,"2535":6,"2536":1,"2537":24,"2538":2,"2539":5,"2541":8,"2542":2,"2543":1,"2544":1,"2545":6,"2546":5,"2679":1,"2722":3,"2739":5,"2740":2,"2741":3,"2742":4,"2752":1,"2758":2,"2772":1,"2775":1,"2785":1,"2794":1,"2795":5,"2800":1,"2802":1,"2823":1,"2824":1,"2857":1,"2859":4,"2860":8,"2861":8,"2862":12,"2863":1,"2864":3,"2865":1,"2866":1,"2867":4,"2868":6,"2869":9,"2871":4,"2872":12,"2873":7,"2874":3,"2875":5,"2876":3,"2877":3,"2878":5,"2879":2,"2880":5,"2881":3,"2882":1}}],["testing",{"0":{"930":1,"986":1,"987":1,"990":1,"991":1,"993":1,"1003":1,"1075":1,"1621":1,"2486":1,"2738":1,"2860":1,"2876":1},"1":{"988":1,"989":1,"990":1,"991":1,"992":1,"993":1,"994":1,"2739":1,"2740":1,"2741":1,"2742":1,"2861":1,"2862":1,"2863":1,"2864":1,"2865":1,"2866":1,"2867":1,"2868":1,"2869":1,"2870":1,"2871":1,"2872":1,"2873":1,"2874":1,"2875":1,"2876":1,"2877":1,"2878":1,"2879":1,"2880":1,"2881":1,"2882":1},"2":{"58":1,"692":1,"697":1,"702":1,"707":1,"712":1,"717":1,"782":1,"784":1,"786":1,"860":1,"872":1,"879":1,"986":1,"987":2,"990":1,"992":1,"993":3,"994":3,"1005":1,"1009":1,"1037":1,"1072":1,"1073":1,"1074":1,"1075":5,"1077":3,"1079":2,"1080":2,"1082":2,"1086":1,"1089":1,"1094":2,"1181":1,"1247":1,"1255":1,"1258":1,"1269":1,"1270":1,"1324":1,"1377":1,"1378":1,"1385":1,"1386":2,"1393":3,"1617":1,"1757":1,"1792":1,"2092":1,"2114":1,"2125":1,"2158":1,"2159":1,"2167":3,"2258":1,"2428":1,"2545":3,"2740":1,"2741":1,"2775":1,"2792":1,"2800":1,"2805":1,"2857":1,"2858":1,"2859":1,"2860":3,"2872":1,"2873":1}}],["tested",{"0":{"1255":1},"2":{"2":1,"856":1,"876":1,"988":1,"1076":1,"1077":2,"1078":1,"1255":3,"1386":1,"1393":1,"1402":1,"1419":1,"1792":1,"2465":1,"2486":1,"2607":1,"2882":1}}],["testsse",{"2":{"2247":1}}],["tests",{"0":{"988":1,"1072":1,"1078":1,"1286":1,"2417":1,"2435":1,"2447":1,"2456":1,"2498":1,"2506":1,"2513":1,"2523":1,"2546":1,"2627":1,"2740":1},"1":{"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1},"2":{"1":1,"695":1,"701":1,"710":1,"711":2,"865":1,"867":1,"874":1,"875":1,"876":1,"877":1,"930":2,"988":2,"989":1,"993":2,"1003":1,"1005":4,"1006":2,"1009":1,"1037":2,"1074":2,"1076":2,"1079":1,"1080":1,"1081":2,"1094":2,"1181":2,"1254":6,"1255":4,"1258":4,"1259":3,"1266":1,"1281":2,"1379":1,"1399":1,"1402":5,"1405":3,"1789":1,"1792":11,"2092":1,"2094":3,"2095":4,"2098":1,"2100":2,"2104":2,"2105":1,"2113":1,"2221":2,"2258":1,"2278":1,"2372":1,"2393":1,"2407":1,"2417":2,"2435":8,"2447":1,"2448":1,"2452":1,"2456":2,"2457":1,"2490":1,"2498":3,"2506":3,"2513":1,"2525":2,"2526":2,"2529":2,"2531":1,"2532":1,"2534":3,"2535":5,"2537":6,"2538":2,"2542":1,"2545":1,"2546":4,"2627":4,"2669":1,"2679":1,"2739":1,"2742":1,"2802":1,"2858":1,"2860":4,"2861":1,"2865":1,"2867":1,"2869":3,"2872":2,"2873":3,"2875":1,"2876":1,"2878":1,"2879":1,"2880":3}}],["thu",{"2":{"2823":1,"2824":1}}],["thundering",{"2":{"1515":1}}],["thumbnails",{"2":{"1381":1}}],["thumb",{"2":{"1378":1}}],["thus",{"2":{"863":1}}],["th>active",{"2":{"996":1}}],["th>email",{"2":{"996":1}}],["th>username",{"2":{"996":1}}],["th>",{"2":{"996":4}}],["th>id",{"2":{"996":1}}],["th",{"2":{"965":1,"1792":2,"2073":2,"2075":2,"2080":2}}],["thoroughly",{"2":{"2089":1}}],["thousands",{"2":{"873":1,"918":1,"1064":1,"1164":1,"1168":1,"1974":1,"2607":1,"2744":1}}],["thousand",{"2":{"860":1,"876":1}}],["thought",{"2":{"843":1,"863":1,"864":1,"913":1,"919":2,"1402":1}}],["though",{"2":{"667":1,"843":1,"1414":1,"1569":1,"2156":1,"2414":1,"2470":1,"2496":1,"2520":1,"2542":1,"2833":1}}],["those",{"2":{"304":1,"319":1,"387":1,"448":1,"449":1,"641":1,"650":1,"841":1,"848":1,"849":3,"852":4,"859":1,"868":1,"869":3,"872":1,"873":3,"912":1,"916":1,"919":1,"922":1,"932":3,"1036":1,"1126":1,"1208":1,"1254":1,"1305":1,"1385":1,"1386":2,"1388":1,"1394":3,"1396":1,"1398":1,"1401":1,"1405":1,"1432":1,"1435":2,"1436":1,"1441":1,"1442":1,"1567":1,"1792":1,"2186":1,"2267":1,"2380":1,"2407":1,"2455":1,"2465":1,"2470":1,"2490":1,"2527":1,"2765":1,"2807":1,"2862":1}}],["threat",{"2":{"2438":2}}],["threading",{"2":{"2184":1,"2701":1}}],["threaded",{"2":{"1266":1}}],["threadpool",{"2":{"1166":1,"1168":1,"1171":1,"1792":1,"2085":1,"2089":1,"2701":1}}],["threads",{"0":{"1166":1,"1167":2,"2087":2},"2":{"1164":2,"1165":3,"1166":2,"1167":5,"1168":2,"1169":3,"1170":2,"1792":4,"2086":4,"2087":2}}],["thread",{"0":{"1164":1,"1165":1,"1170":1,"2084":1},"1":{"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"2085":1,"2086":1,"2087":1,"2088":1,"2089":1,"2090":1,"2091":1},"2":{"306":1,"1164":3,"1165":5,"1166":3,"1167":1,"1169":1,"1170":1,"1180":1,"1181":1,"1182":2,"1790":2,"1792":3,"1797":2,"2084":1,"2086":2,"2088":3,"2089":1,"2614":1}}],["threw",{"2":{"2402":1}}],["threshold",{"2":{"747":1,"753":2,"757":2,"776":1,"782":3,"784":3,"786":3,"1511":2,"1516":1,"1792":3,"2107":1,"2221":1,"2265":3,"2537":1,"2546":1}}],["three",{"0":{"1219":1,"1869":1},"1":{"1220":1,"1221":1,"1222":1,"1870":1,"1871":1,"1872":1},"2":{"22":1,"212":1,"305":1,"310":1,"534":1,"540":1,"831":1,"845":1,"859":2,"860":1,"872":1,"1053":1,"1064":1,"1067":1,"1083":1,"1092":1,"1097":2,"1098":1,"1101":1,"1102":1,"1104":1,"1105":2,"1127":1,"1144":1,"1218":1,"1219":1,"1309":1,"1357":1,"1366":1,"1367":1,"1371":1,"1382":1,"1398":3,"1411":1,"1422":1,"1429":1,"1435":1,"1458":1,"1464":1,"1521":1,"1523":1,"1609":1,"1765":1,"1792":6,"1869":1,"1961":1,"2171":1,"2181":1,"2182":1,"2314":1,"2337":1,"2352":1,"2372":1,"2375":1,"2380":1,"2397":1,"2400":1,"2405":1,"2412":1,"2414":1,"2419":1,"2421":2,"2435":3,"2447":1,"2448":2,"2486":1,"2496":1,"2498":1,"2520":2,"2523":1,"2528":1,"2534":1,"2546":1,"2554":1,"2634":1,"2659":1,"2765":1,"2794":1,"2836":1,"2852":1,"2854":1,"2860":1,"2864":1,"2869":1,"2873":1}}],["throttling",{"2":{"1955":1,"2379":1}}],["throttled",{"2":{"1792":1,"1961":1}}],["throttle`",{"2":{"1792":1}}],["throttle",{"0":{"1959":1,"2471":1},"2":{"1069":1,"1101":1,"1162":2,"1792":3,"1822":1,"1824":1,"1955":1,"1956":1,"1958":2,"1959":3,"2224":1,"2379":2,"2441":3,"2442":1,"2468":1,"2470":1,"2471":3,"2481":1}}],["throws",{"2":{"1605":1,"2403":1,"2412":1,"2497":1,"2498":1,"2688":1}}],["throw",{"2":{"1366":1,"1460":2,"1568":1,"1792":1,"2375":2,"2497":1}}],["throwaway",{"2":{"1075":1,"1079":1,"1081":1,"1094":1,"2534":1}}],["thrown",{"2":{"188":1,"2242":1,"2296":1}}],["throughs",{"2":{"1150":1}}],["throughout",{"2":{"267":1,"1328":1,"2615":1,"2769":1}}],["through",{"2":{"108":1,"212":1,"296":1,"389":2,"390":1,"452":1,"636":1,"650":1,"666":1,"695":1,"840":1,"843":1,"851":1,"864":1,"865":1,"868":2,"872":1,"876":2,"878":1,"926":1,"972":1,"993":2,"996":1,"1005":1,"1009":1,"1014":1,"1037":1,"1038":2,"1041":1,"1045":1,"1048":1,"1057":1,"1083":1,"1099":1,"1103":1,"1107":1,"1113":1,"1126":1,"1135":1,"1139":1,"1162":1,"1170":1,"1175":1,"1193":1,"1203":1,"1214":1,"1220":1,"1232":1,"1253":1,"1323":1,"1328":1,"1329":1,"1359":1,"1363":1,"1381":1,"1385":2,"1386":1,"1388":1,"1393":1,"1405":1,"1406":1,"1409":1,"1412":1,"1436":1,"1525":1,"1566":1,"1792":6,"1824":1,"1870":1,"1882":1,"1956":1,"1961":1,"2045":1,"2106":1,"2184":1,"2347":1,"2379":1,"2380":1,"2413":1,"2427":1,"2435":1,"2437":1,"2461":1,"2462":1,"2463":1,"2472":1,"2481":1,"2483":1,"2491":1,"2522":1,"2531":1,"2536":1,"2537":2,"2540":1,"2545":1,"2546":2,"2607":1,"2614":1,"2635":1,"2680":1,"2763":2,"2801":1,"2806":2,"2818":1,"2845":1,"2867":1}}],["throughput",{"0":{"85":1,"1168":1},"2":{"88":1,"1164":1,"1270":1,"1271":1,"1324":2,"2270":1,"2388":1,"2398":1,"2466":1,"2621":1,"2789":1}}],["thief",{"2":{"1441":1}}],["thirty",{"2":{"1079":1}}],["third",{"2":{"565":1,"614":1,"863":1,"876":1,"1011":1,"1035":1,"1097":1,"1098":2,"1099":1,"1104":2,"1150":1,"1152":1,"1209":1,"1329":1,"1335":1,"1382":2,"1572":1,"1590":1,"2274":1,"2317":1,"2339":1,"2554":1,"2759":1}}],["thin",{"2":{"1039":1}}],["think",{"2":{"1":1,"851":1,"859":1,"1037":1,"1400":1,"1401":2,"1402":2,"1404":1}}],["things",{"2":{"650":1,"836":1,"857":1,"864":1,"948":1,"1150":1,"1382":1,"1386":2,"1393":1,"1396":1,"1401":2,"1404":1,"1405":1,"1406":1,"1408":1,"1422":1,"1423":1,"2184":1,"2868":1}}],["thing",{"2":{"1":1,"436":1,"841":1,"848":3,"849":1,"852":5,"871":1,"874":1,"876":1,"918":1,"1044":1,"1075":1,"1076":1,"1078":1,"1081":1,"1161":1,"1382":2,"1384":1,"1386":1,"1391":1,"1398":1,"1399":1,"1400":1,"1404":1,"1406":1,"2414":1,"2531":1,"2729":1,"2797":1,"2807":1}}],["this",{"0":{"0":1,"221":1,"877":1,"939":1,"950":1,"968":1,"999":1,"1006":1,"1046":1,"1065":1,"1246":1,"1256":1,"1325":1,"1379":1,"1421":1,"1432":1,"2452":1,"2465":1},"1":{"1":1,"2":1,"3":1,"940":1,"941":1,"942":1,"943":1,"944":1,"951":1,"952":1,"953":1,"954":1,"969":1,"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1007":1,"1008":1,"1009":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1257":1,"1258":1,"1259":1,"1260":1},"2":{"0":1,"18":1,"19":1,"20":1,"32":1,"101":1,"105":1,"119":1,"139":1,"165":1,"168":3,"215":1,"267":1,"286":2,"292":1,"296":1,"298":1,"302":1,"304":1,"308":1,"309":1,"316":1,"320":1,"335":1,"337":1,"349":1,"357":1,"362":1,"363":1,"369":1,"376":1,"377":1,"386":3,"387":1,"390":1,"395":2,"408":1,"414":1,"423":1,"436":1,"453":2,"458":1,"470":1,"480":1,"527":1,"534":1,"556":1,"559":2,"560":1,"567":1,"581":1,"582":1,"619":1,"624":1,"636":2,"646":5,"650":2,"655":1,"656":1,"668":1,"669":1,"683":1,"686":1,"689":1,"693":2,"694":1,"696":1,"698":2,"703":2,"705":2,"708":1,"713":2,"716":1,"720":1,"761":1,"771":1,"773":1,"826":2,"831":2,"834":2,"841":5,"843":2,"844":3,"845":4,"847":3,"848":2,"849":7,"851":8,"852":3,"855":2,"856":1,"857":2,"859":7,"860":2,"861":3,"863":1,"866":3,"868":2,"869":3,"871":4,"872":6,"873":2,"874":3,"875":1,"876":5,"877":1,"878":1,"885":2,"886":1,"894":10,"901":1,"902":2,"904":2,"910":2,"911":3,"912":2,"913":2,"915":4,"916":5,"917":3,"918":6,"919":2,"920":4,"921":2,"924":2,"926":5,"927":2,"928":2,"932":1,"933":3,"934":3,"936":1,"943":1,"945":1,"946":3,"948":2,"956":1,"961":1,"971":4,"972":1,"973":1,"974":1,"975":2,"976":1,"977":2,"979":1,"980":2,"982":2,"983":1,"985":1,"986":2,"987":1,"990":2,"994":3,"1006":2,"1009":1,"1010":1,"1011":1,"1026":8,"1029":1,"1036":2,"1038":2,"1042":2,"1045":1,"1048":1,"1049":1,"1053":1,"1055":1,"1057":3,"1058":2,"1063":2,"1064":4,"1065":3,"1067":1,"1068":2,"1073":5,"1074":1,"1075":1,"1076":5,"1077":1,"1078":1,"1079":3,"1080":1,"1081":3,"1083":1,"1100":1,"1102":1,"1105":1,"1111":2,"1113":1,"1129":2,"1130":2,"1132":3,"1133":4,"1134":2,"1135":2,"1137":1,"1139":2,"1141":1,"1149":1,"1150":1,"1157":1,"1164":1,"1165":1,"1166":1,"1174":1,"1176":1,"1178":1,"1182":1,"1183":1,"1185":4,"1187":1,"1190":1,"1192":4,"1198":1,"1200":1,"1202":1,"1203":3,"1205":1,"1206":1,"1208":2,"1220":1,"1223":1,"1233":1,"1234":1,"1238":1,"1247":1,"1253":1,"1254":2,"1255":1,"1256":1,"1277":1,"1302":1,"1304":1,"1305":1,"1309":2,"1316":3,"1324":2,"1325":1,"1326":1,"1327":2,"1328":2,"1331":1,"1335":2,"1337":1,"1338":1,"1351":2,"1358":1,"1363":1,"1366":12,"1367":2,"1368":4,"1370":2,"1372":1,"1373":1,"1376":1,"1378":2,"1382":5,"1384":6,"1385":4,"1386":19,"1388":2,"1389":2,"1390":4,"1391":2,"1392":1,"1393":3,"1394":5,"1395":4,"1396":7,"1398":8,"1399":11,"1400":7,"1401":6,"1402":3,"1403":3,"1404":5,"1405":8,"1406":1,"1410":7,"1412":1,"1414":1,"1415":2,"1416":2,"1417":1,"1421":2,"1423":2,"1431":2,"1432":1,"1434":1,"1443":1,"1444":1,"1450":1,"1458":1,"1472":1,"1475":2,"1477":2,"1511":6,"1516":1,"1517":1,"1519":1,"1521":1,"1523":1,"1566":1,"1567":2,"1568":1,"1580":1,"1581":1,"1609":1,"1612":1,"1618":1,"1619":1,"1620":1,"1621":1,"1632":1,"1644":1,"1664":1,"1678":1,"1685":2,"1690":1,"1696":1,"1701":1,"1704":1,"1706":1,"1708":1,"1714":1,"1745":1,"1792":144,"1802":1,"1823":1,"1825":2,"1830":2,"1832":1,"1838":4,"1840":1,"1851":1,"1852":1,"1858":2,"1884":1,"1910":1,"1917":1,"1925":1,"1951":3,"1952":3,"1953":3,"1954":3,"1956":1,"1958":1,"1973":1,"1978":1,"1982":1,"2001":1,"2006":2,"2009":1,"2010":1,"2011":2,"2016":1,"2018":1,"2020":1,"2040":3,"2094":1,"2104":1,"2106":1,"2109":1,"2113":1,"2125":1,"2154":1,"2156":1,"2157":1,"2159":1,"2160":1,"2170":2,"2175":2,"2176":1,"2177":1,"2180":1,"2183":1,"2184":1,"2185":1,"2190":1,"2193":1,"2201":1,"2207":1,"2242":1,"2245":1,"2247":2,"2251":1,"2254":3,"2256":5,"2257":1,"2258":2,"2265":5,"2266":2,"2267":1,"2277":1,"2282":1,"2291":1,"2297":1,"2300":1,"2304":1,"2314":1,"2319":1,"2332":1,"2333":1,"2336":1,"2338":2,"2340":1,"2342":1,"2346":1,"2347":1,"2354":2,"2363":1,"2365":1,"2366":1,"2367":1,"2371":1,"2375":1,"2378":1,"2379":1,"2380":3,"2381":2,"2382":1,"2383":1,"2385":1,"2388":1,"2389":1,"2391":1,"2392":2,"2394":1,"2395":1,"2397":1,"2414":1,"2419":1,"2424":2,"2425":1,"2432":1,"2433":1,"2435":1,"2437":1,"2438":1,"2440":1,"2446":1,"2452":2,"2455":2,"2459":1,"2461":1,"2463":2,"2465":1,"2466":4,"2470":1,"2474":1,"2477":2,"2479":1,"2486":1,"2492":1,"2497":1,"2500":1,"2509":1,"2517":1,"2518":2,"2520":1,"2525":1,"2529":1,"2531":2,"2533":4,"2535":2,"2537":2,"2539":2,"2540":1,"2542":1,"2550":1,"2554":1,"2576":1,"2581":2,"2590":1,"2603":1,"2611":1,"2615":1,"2625":1,"2628":1,"2632":9,"2633":2,"2634":4,"2635":1,"2642":1,"2649":1,"2659":1,"2664":1,"2665":1,"2666":1,"2677":1,"2681":1,"2684":1,"2693":1,"2695":1,"2697":1,"2701":1,"2733":1,"2759":2,"2762":1,"2766":2,"2767":1,"2769":1,"2773":1,"2779":2,"2785":1,"2788":1,"2794":1,"2802":1,"2806":3,"2807":1,"2815":1,"2818":1,"2821":1,"2822":2,"2823":3,"2824":1,"2827":2,"2828":1,"2829":1,"2830":1,"2831":1,"2839":1,"2840":1,"2841":1,"2848":1,"2850":2,"2853":1,"2854":1,"2855":2,"2856":1,"2868":3,"2869":3,"2870":3,"2871":1,"2872":2,"2873":2}}],["thanks",{"2":{"1385":1}}],["thank",{"2":{"848":1}}],["than",{"2":{"1":2,"87":1,"214":1,"216":1,"302":1,"307":1,"325":1,"666":1,"701":1,"723":1,"831":1,"841":2,"847":1,"851":1,"859":1,"860":1,"868":3,"869":2,"872":3,"873":2,"874":1,"875":2,"876":4,"904":1,"908":1,"993":1,"994":1,"1075":1,"1076":2,"1090":1,"1102":1,"1103":1,"1121":1,"1129":1,"1133":1,"1150":1,"1159":1,"1161":1,"1176":1,"1254":1,"1269":1,"1270":1,"1272":1,"1305":1,"1326":1,"1363":1,"1385":4,"1402":2,"1403":1,"1413":1,"1416":1,"1428":1,"1464":1,"1511":5,"1527":1,"1607":1,"1792":8,"1824":1,"1856":1,"1917":1,"1925":2,"1940":1,"2175":2,"2177":1,"2220":1,"2222":1,"2265":3,"2376":1,"2377":1,"2380":1,"2384":1,"2405":1,"2421":1,"2440":1,"2454":1,"2463":1,"2466":2,"2481":1,"2511":1,"2513":1,"2517":1,"2528":1,"2531":2,"2535":1,"2603":1,"2656":1,"2789":1,"2802":1,"2812":1,"2864":1}}],["that",{"0":{"39":1,"324":1,"870":1,"978":1,"1007":1,"1381":1,"2724":1},"1":{"871":1,"872":1,"873":1,"874":1,"875":1,"979":1,"980":1,"1382":1,"1383":1},"2":{"0":1,"1":2,"3":1,"30":1,"33":1,"37":1,"106":1,"109":1,"119":1,"159":1,"162":1,"165":3,"167":1,"168":1,"170":1,"174":1,"177":1,"188":1,"202":1,"214":1,"215":1,"220":1,"263":1,"297":2,"301":1,"302":1,"304":1,"306":1,"308":1,"309":1,"312":1,"319":2,"320":1,"352":1,"357":1,"358":1,"368":1,"380":1,"386":2,"388":1,"390":1,"395":1,"414":1,"423":1,"429":1,"433":1,"436":1,"507":1,"531":1,"535":1,"560":1,"575":1,"583":1,"586":1,"619":1,"636":1,"646":1,"650":1,"654":1,"656":1,"663":2,"664":1,"665":1,"666":1,"668":1,"673":1,"684":1,"687":1,"691":1,"694":2,"697":1,"706":1,"722":1,"749":1,"831":3,"833":1,"834":1,"835":1,"838":1,"840":1,"841":20,"843":5,"844":10,"845":10,"847":5,"848":9,"849":3,"851":19,"852":13,"854":3,"856":2,"857":3,"859":8,"860":7,"861":5,"863":8,"864":6,"865":4,"868":3,"869":3,"871":10,"872":15,"873":7,"874":4,"875":4,"876":6,"877":2,"878":1,"879":1,"880":3,"904":1,"913":1,"914":1,"915":2,"916":3,"917":1,"918":2,"919":1,"920":4,"921":3,"922":1,"924":1,"926":1,"928":2,"932":2,"933":2,"946":3,"948":2,"951":1,"953":1,"954":1,"961":1,"974":3,"975":3,"979":1,"981":1,"982":1,"983":1,"986":3,"987":1,"991":1,"992":1,"993":1,"1005":5,"1006":1,"1007":1,"1009":1,"1010":1,"1012":1,"1017":1,"1037":3,"1042":1,"1043":1,"1044":1,"1048":2,"1049":1,"1050":1,"1053":1,"1063":1,"1064":1,"1065":2,"1066":2,"1067":2,"1068":4,"1069":1,"1073":6,"1075":6,"1077":3,"1078":4,"1079":4,"1081":1,"1082":1,"1086":1,"1096":2,"1098":3,"1101":2,"1102":4,"1104":1,"1105":1,"1106":1,"1107":1,"1111":1,"1115":1,"1125":1,"1126":1,"1127":1,"1128":1,"1130":2,"1133":4,"1134":2,"1137":2,"1139":1,"1140":1,"1141":1,"1142":1,"1150":1,"1162":2,"1171":1,"1176":1,"1180":3,"1183":2,"1185":1,"1187":1,"1190":1,"1200":1,"1203":1,"1205":2,"1208":1,"1209":1,"1210":1,"1211":1,"1214":2,"1219":1,"1239":1,"1254":4,"1281":1,"1303":1,"1305":1,"1324":1,"1326":1,"1327":2,"1328":3,"1329":1,"1351":1,"1366":1,"1367":1,"1374":1,"1376":1,"1382":8,"1384":2,"1385":18,"1386":20,"1388":5,"1389":2,"1390":3,"1391":5,"1393":7,"1394":8,"1395":2,"1396":5,"1397":3,"1398":6,"1399":8,"1400":1,"1401":11,"1402":15,"1403":22,"1404":7,"1405":12,"1406":2,"1407":1,"1409":3,"1410":2,"1412":2,"1413":1,"1414":1,"1419":2,"1421":2,"1423":2,"1424":1,"1427":1,"1428":2,"1429":1,"1431":5,"1432":2,"1435":3,"1440":2,"1441":2,"1442":1,"1445":1,"1453":1,"1458":1,"1459":1,"1460":5,"1511":1,"1515":1,"1516":1,"1527":1,"1544":2,"1566":1,"1569":1,"1571":1,"1574":1,"1576":1,"1582":1,"1589":1,"1609":1,"1623":1,"1630":1,"1664":1,"1686":1,"1703":1,"1720":1,"1727":1,"1733":1,"1738":1,"1743":1,"1744":1,"1759":1,"1764":1,"1792":75,"1801":3,"1802":2,"1813":3,"1817":1,"1823":2,"1825":3,"1840":1,"1844":1,"1856":1,"1868":1,"1869":1,"1898":1,"1912":1,"1923":1,"1925":1,"1930":1,"1941":1,"1958":3,"1961":2,"1974":1,"2005":1,"2016":2,"2020":1,"2024":1,"2040":2,"2054":1,"2072":2,"2098":1,"2109":1,"2110":2,"2125":1,"2139":1,"2157":2,"2159":1,"2166":1,"2171":1,"2175":1,"2177":1,"2182":2,"2185":2,"2187":1,"2191":1,"2193":1,"2200":1,"2247":1,"2255":2,"2256":5,"2264":2,"2265":2,"2267":1,"2291":1,"2296":1,"2300":2,"2304":1,"2310":1,"2313":1,"2314":1,"2321":2,"2322":2,"2332":1,"2333":1,"2336":2,"2337":1,"2338":1,"2339":1,"2340":1,"2344":1,"2346":4,"2356":1,"2369":1,"2375":7,"2380":3,"2381":1,"2384":1,"2385":1,"2389":5,"2391":1,"2392":3,"2393":1,"2394":1,"2395":3,"2397":1,"2398":3,"2401":1,"2402":1,"2405":1,"2409":1,"2411":1,"2412":1,"2414":1,"2421":2,"2423":1,"2425":1,"2428":1,"2430":1,"2433":1,"2435":1,"2437":1,"2438":4,"2440":2,"2442":1,"2443":1,"2451":1,"2452":2,"2455":2,"2456":1,"2459":2,"2466":3,"2468":3,"2470":5,"2474":1,"2477":2,"2479":2,"2481":3,"2482":1,"2489":2,"2492":1,"2493":1,"2494":2,"2495":2,"2497":1,"2502":1,"2505":1,"2508":1,"2509":1,"2513":1,"2515":1,"2517":2,"2519":1,"2520":1,"2527":2,"2528":1,"2529":2,"2530":4,"2531":2,"2532":3,"2533":3,"2534":3,"2535":1,"2536":2,"2537":3,"2539":1,"2540":2,"2542":1,"2543":2,"2544":1,"2545":1,"2580":2,"2586":1,"2589":1,"2597":1,"2603":1,"2607":2,"2608":2,"2611":1,"2615":1,"2632":2,"2633":1,"2634":1,"2650":1,"2655":1,"2659":2,"2669":1,"2674":1,"2678":1,"2709":1,"2710":1,"2712":2,"2717":1,"2725":1,"2729":1,"2740":1,"2741":2,"2755":1,"2758":1,"2759":1,"2760":2,"2772":1,"2775":1,"2785":1,"2795":1,"2800":1,"2804":1,"2806":1,"2812":1,"2815":1,"2823":1,"2826":1,"2828":1,"2830":3,"2831":1,"2832":1,"2833":2,"2834":2,"2835":2,"2836":1,"2840":1,"2841":1,"2849":1,"2850":1,"2854":1,"2855":1,"2856":1,"2858":1,"2862":2,"2864":1,"2865":2,"2868":4,"2869":3,"2871":2,"2872":1,"2873":2,"2879":1,"2881":2}}],["theft",{"2":{"2438":1}}],["thead>",{"2":{"996":2}}],["therapy",{"2":{"840":1}}],["therefore",{"2":{"696":1,"847":1,"864":1,"868":1,"872":1,"1079":1,"1129":1,"1133":1,"2504":1,"2803":1}}],["there",{"0":{"1080":1,"2741":1,"2742":1},"2":{"167":1,"307":1,"325":1,"389":1,"390":1,"452":1,"453":1,"458":1,"746":1,"773":1,"833":2,"835":1,"841":3,"843":1,"844":2,"845":6,"847":1,"851":1,"852":1,"857":1,"859":2,"867":1,"868":2,"871":5,"872":2,"874":1,"875":2,"876":1,"912":1,"913":1,"916":1,"917":1,"919":1,"920":3,"949":1,"973":1,"974":1,"975":1,"982":1,"988":1,"994":1,"1075":2,"1076":3,"1077":1,"1079":1,"1082":1,"1137":1,"1157":1,"1169":1,"1209":1,"1254":1,"1384":1,"1385":2,"1386":6,"1390":1,"1392":1,"1394":4,"1396":2,"1398":1,"1399":1,"1400":1,"1401":8,"1402":3,"1403":1,"1405":1,"1406":1,"1409":1,"1422":1,"1432":1,"1792":3,"2171":2,"2183":1,"2256":1,"2381":1,"2383":1,"2438":1,"2544":1,"2590":1,"2712":1,"2744":1,"2760":1,"2795":1,"2828":1,"2848":1,"2868":1}}],["their",{"0":{"2249":1},"2":{"210":1,"244":1,"300":1,"328":1,"337":1,"348":1,"352":1,"386":1,"439":1,"479":1,"565":1,"567":1,"636":1,"650":1,"668":1,"696":1,"801":1,"841":1,"851":1,"859":1,"865":1,"904":1,"912":1,"917":1,"918":3,"919":1,"933":1,"1054":1,"1068":1,"1074":1,"1079":1,"1096":1,"1098":2,"1100":1,"1101":2,"1106":1,"1108":1,"1111":1,"1115":1,"1127":1,"1162":1,"1206":1,"1220":1,"1232":1,"1254":1,"1324":1,"1382":3,"1385":1,"1396":1,"1398":1,"1432":2,"1574":1,"1588":1,"1605":1,"1690":1,"1714":1,"1732":1,"1792":5,"1840":1,"1871":1,"1908":1,"1955":1,"1973":1,"1974":1,"2217":1,"2264":1,"2285":1,"2320":1,"2330":1,"2342":1,"2379":1,"2397":1,"2398":1,"2456":1,"2466":2,"2495":1,"2497":1,"2532":1,"2533":1,"2536":1,"2607":1,"2625":1,"2688":1,"2694":1,"2799":1,"2870":1,"2873":1}}],["theme",{"2":{"2535":3,"2880":1}}],["theme=dark",{"2":{"541":2,"2202":1}}],["themself",{"2":{"1254":1}}],["themselves",{"2":{"841":1,"1792":1,"1908":1,"2297":1,"2353":1,"2415":1,"2543":1}}],["them",{"0":{"868":1},"2":{"165":1,"167":1,"302":1,"305":1,"306":2,"308":1,"309":1,"362":1,"364":1,"369":1,"372":1,"376":1,"390":1,"447":1,"448":3,"614":1,"684":1,"841":4,"843":1,"845":1,"848":1,"851":3,"852":1,"855":1,"856":2,"857":1,"860":2,"865":2,"868":3,"869":2,"871":3,"872":2,"877":1,"912":1,"932":3,"933":2,"936":1,"942":1,"1010":1,"1038":3,"1056":1,"1065":1,"1105":1,"1128":2,"1133":1,"1134":1,"1150":1,"1169":1,"1190":1,"1196":1,"1208":2,"1215":1,"1220":1,"1304":1,"1305":1,"1325":1,"1327":2,"1362":1,"1371":1,"1375":1,"1377":1,"1382":2,"1386":4,"1392":1,"1393":1,"1394":3,"1398":5,"1400":1,"1401":3,"1402":1,"1405":1,"1409":1,"1416":1,"1419":1,"1423":2,"1427":1,"1429":1,"1435":2,"1437":1,"1449":1,"1519":1,"1569":1,"1571":3,"1574":2,"1792":5,"1832":1,"1833":1,"1840":1,"1912":1,"1922":1,"1961":1,"2092":1,"2097":1,"2111":1,"2156":1,"2182":2,"2183":1,"2184":1,"2190":1,"2193":1,"2209":1,"2282":1,"2322":1,"2339":1,"2347":1,"2358":1,"2378":1,"2380":1,"2389":1,"2410":1,"2415":1,"2421":1,"2422":1,"2476":1,"2481":3,"2484":1,"2486":1,"2509":1,"2530":1,"2536":1,"2537":1,"2540":1,"2572":1,"2603":1,"2666":1,"2747":1,"2749":1,"2758":1,"2766":1,"2827":1,"2834":1,"2852":1,"2860":1,"2881":1}}],["then",{"0":{"1080":1},"2":{"106":1,"107":3,"207":1,"208":1,"209":2,"215":1,"223":1,"310":1,"313":1,"378":1,"395":1,"412":1,"423":1,"436":1,"439":1,"446":1,"448":1,"449":2,"452":1,"453":1,"454":1,"456":1,"470":1,"556":1,"683":1,"745":1,"840":1,"841":1,"843":1,"844":2,"845":2,"847":1,"848":1,"849":6,"851":5,"859":1,"861":1,"864":1,"865":1,"888":1,"896":1,"897":2,"916":1,"918":1,"928":1,"929":3,"1021":3,"1039":1,"1044":2,"1060":1,"1067":3,"1075":1,"1078":1,"1081":1,"1105":4,"1129":1,"1133":1,"1150":3,"1232":1,"1234":2,"1236":1,"1239":1,"1254":1,"1332":2,"1338":2,"1339":2,"1376":3,"1385":2,"1386":7,"1393":2,"1394":1,"1395":3,"1396":2,"1398":5,"1399":2,"1401":4,"1403":1,"1404":1,"1405":2,"1417":1,"1418":1,"1427":2,"1428":1,"1429":2,"1431":2,"1439":3,"1504":1,"1520":1,"1523":1,"1524":1,"1525":2,"1526":1,"1527":1,"1529":2,"1689":1,"1727":2,"1736":1,"1738":1,"1742":2,"1792":8,"1823":1,"1856":1,"1921":1,"2095":1,"2106":1,"2156":1,"2164":1,"2171":1,"2257":1,"2264":1,"2290":2,"2300":1,"2304":1,"2333":1,"2378":1,"2380":7,"2393":2,"2422":1,"2424":1,"2445":1,"2451":1,"2511":1,"2521":1,"2537":2,"2542":1,"2549":1,"2669":1,"2672":1,"2681":1,"2689":1,"2728":1,"2741":1,"2762":3,"2764":1,"2766":3,"2804":1,"2810":1,"2815":3,"2816":1,"2855":2,"2876":1,"2878":1}}],["they",{"0":{"836":1,"1075":1,"2749":1},"2":{"74":1,"101":1,"184":1,"305":1,"320":1,"390":1,"395":1,"452":1,"567":1,"650":1,"666":2,"669":1,"691":1,"696":1,"831":2,"836":1,"838":1,"841":4,"844":4,"845":1,"848":3,"849":1,"851":3,"852":2,"857":2,"860":1,"864":2,"865":3,"868":3,"869":1,"872":3,"874":1,"877":1,"904":1,"912":1,"916":1,"932":1,"933":1,"940":1,"963":1,"978":1,"1005":1,"1008":1,"1012":1,"1036":1,"1037":1,"1046":1,"1049":1,"1052":1,"1054":1,"1068":1,"1071":1,"1075":2,"1076":1,"1077":1,"1079":1,"1083":2,"1107":2,"1122":1,"1145":1,"1150":1,"1166":1,"1180":1,"1206":2,"1210":2,"1221":1,"1232":1,"1254":1,"1305":1,"1325":1,"1328":1,"1371":1,"1377":1,"1382":5,"1385":3,"1386":4,"1388":1,"1394":2,"1395":1,"1396":1,"1397":1,"1398":1,"1401":2,"1403":7,"1414":1,"1435":2,"1441":2,"1559":1,"1571":1,"1792":2,"1924":1,"1961":1,"2110":1,"2156":1,"2160":1,"2181":2,"2185":1,"2190":1,"2193":1,"2250":1,"2267":1,"2282":1,"2289":1,"2292":1,"2297":1,"2320":1,"2377":1,"2383":1,"2384":1,"2398":2,"2413":1,"2415":1,"2419":1,"2423":1,"2432":1,"2438":1,"2444":1,"2483":1,"2484":1,"2486":1,"2492":1,"2511":2,"2518":1,"2522":1,"2531":1,"2532":1,"2555":1,"2588":1,"2685":1,"2722":1,"2724":1,"2739":1,"2765":1,"2820":1,"2849":1,"2852":1,"2855":1,"2868":2,"2869":1,"2876":1}}],["thesis",{"2":{"1":1}}],["these",{"0":{"1":1,"2521":1},"2":{"17":1,"105":1,"159":1,"165":1,"169":1,"239":1,"305":1,"309":2,"375":1,"387":1,"453":1,"564":1,"624":1,"684":1,"747":1,"840":1,"841":2,"843":1,"847":1,"852":1,"857":2,"865":1,"868":1,"869":1,"872":2,"893":1,"914":1,"917":1,"918":1,"926":1,"927":1,"963":1,"983":2,"985":1,"986":1,"988":1,"996":1,"1054":1,"1057":1,"1088":1,"1096":1,"1098":1,"1100":1,"1106":1,"1107":1,"1129":3,"1130":2,"1135":1,"1179":1,"1180":1,"1184":1,"1204":1,"1205":1,"1221":1,"1279":1,"1304":1,"1313":1,"1325":1,"1328":1,"1329":1,"1332":1,"1341":1,"1358":1,"1379":1,"1383":1,"1386":3,"1387":2,"1394":1,"1398":1,"1399":1,"1401":2,"1403":3,"1435":3,"1443":1,"1464":1,"1472":1,"1475":1,"1477":1,"1480":1,"1521":1,"1558":1,"1574":1,"1591":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1707":1,"1733":1,"1757":1,"1792":14,"1826":1,"1855":1,"1871":1,"1918":1,"1922":1,"1961":2,"2094":2,"2115":1,"2119":1,"2164":1,"2165":1,"2168":1,"2169":1,"2255":2,"2264":1,"2270":2,"2273":1,"2282":1,"2322":1,"2335":1,"2377":1,"2384":1,"2398":2,"2401":1,"2429":1,"2431":1,"2438":1,"2482":2,"2486":1,"2518":1,"2533":2,"2537":2,"2549":1,"2551":1,"2586":1,"2615":1,"2629":1,"2632":2,"2633":1,"2692":1,"2702":1,"2721":1,"2835":1,"2855":1,"2871":1}}],["the",{"0":{"2":1,"299":1,"304":1,"325":1,"436":1,"534":1,"666":1,"668":1,"669":1,"691":1,"832":1,"833":2,"834":1,"835":1,"839":1,"841":1,"843":1,"844":1,"845":1,"847":1,"848":1,"849":1,"851":1,"852":1,"853":1,"855":1,"856":1,"857":1,"859":1,"860":1,"861":1,"863":1,"864":1,"865":1,"867":1,"869":1,"870":1,"879":1,"880":1,"882":1,"885":1,"886":1,"902":1,"911":1,"922":1,"924":1,"925":1,"926":1,"928":1,"929":1,"930":1,"931":1,"938":1,"949":1,"957":1,"961":1,"968":1,"970":1,"973":1,"975":1,"977":1,"978":1,"981":1,"983":1,"988":1,"992":1,"995":1,"996":1,"997":1,"1009":1,"1011":1,"1015":1,"1016":1,"1017":1,"1018":1,"1020":1,"1021":1,"1024":1,"1027":1,"1044":2,"1045":1,"1055":1,"1060":1,"1061":1,"1068":1,"1076":1,"1079":1,"1137":1,"1165":1,"1184":1,"1185":1,"1188":1,"1191":1,"1192":1,"1203":1,"1206":1,"1264":1,"1268":1,"1303":1,"1304":1,"1306":1,"1309":1,"1317":1,"1318":1,"1322":1,"1329":1,"1330":1,"1334":1,"1335":1,"1342":1,"1349":1,"1355":1,"1357":1,"1358":1,"1359":1,"1361":1,"1369":1,"1377":1,"1381":1,"1382":1,"1405":1,"1407":1,"1410":1,"1420":1,"1424":1,"1426":1,"1430":1,"1924":2,"2110":1,"2171":1,"2173":1,"2177":1,"2180":1,"2188":1,"2195":1,"2389":1,"2398":1,"2413":1,"2416":1,"2423":1,"2444":1,"2484":1,"2494":2,"2505":1,"2517":1,"2530":1,"2542":1,"2727":1,"2733":1,"2741":1,"2750":1,"2751":1,"2761":1,"2763":1,"2770":1,"2795":1,"2800":1,"2808":1,"2811":1,"2816":1,"2830":1,"2837":1,"2857":1,"2866":1,"2868":1},"1":{"305":1,"306":1,"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":2,"855":2,"856":2,"857":2,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1,"871":1,"872":1,"873":1,"874":1,"875":1,"883":1,"884":1,"887":1,"903":1,"932":1,"933":1,"934":1,"935":1,"936":1,"969":1,"979":1,"980":1,"982":1,"983":1,"984":1,"1019":1,"1020":1,"1021":1,"1022":1,"1056":1,"1138":1,"1139":1,"1307":1,"1308":1,"1309":1,"1310":1,"1331":1,"1332":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1350":1,"1360":1,"1382":1,"1383":1,"2196":1,"2197":1},"2":{"0":1,"1":21,"3":2,"4":2,"10":1,"13":1,"18":1,"19":2,"20":2,"21":1,"22":2,"23":1,"29":1,"31":5,"32":3,"37":2,"38":2,"39":1,"40":1,"41":11,"44":1,"45":3,"46":1,"48":1,"50":2,"51":7,"52":2,"55":1,"56":3,"57":1,"58":1,"60":1,"63":7,"64":2,"68":1,"73":3,"74":12,"75":4,"77":2,"78":3,"80":1,"81":1,"83":1,"87":4,"101":2,"102":2,"104":1,"105":3,"106":3,"108":10,"109":1,"113":1,"119":2,"120":1,"121":2,"125":1,"132":2,"133":4,"134":1,"139":3,"140":2,"144":1,"154":1,"155":1,"156":5,"157":1,"158":2,"159":1,"162":1,"165":6,"166":1,"167":7,"168":12,"169":1,"173":3,"174":2,"175":12,"179":4,"180":3,"182":4,"184":4,"186":4,"188":2,"192":1,"197":1,"202":4,"203":4,"206":2,"210":1,"211":1,"212":4,"213":7,"214":17,"215":12,"216":3,"217":1,"220":2,"221":1,"223":2,"239":3,"244":4,"245":2,"251":3,"253":2,"258":2,"263":1,"267":2,"277":4,"279":1,"280":1,"284":2,"285":3,"286":1,"289":1,"292":1,"296":5,"297":12,"298":13,"299":5,"301":4,"302":3,"303":7,"304":3,"305":10,"306":9,"307":12,"308":13,"309":14,"310":10,"312":3,"314":1,"316":1,"317":7,"318":3,"319":14,"320":12,"322":4,"323":2,"324":7,"326":1,"327":3,"330":2,"334":2,"335":1,"336":3,"337":1,"339":1,"347":8,"349":4,"351":1,"352":1,"353":1,"354":2,"357":2,"358":3,"361":2,"362":9,"363":2,"364":4,"366":1,"369":5,"370":4,"373":1,"374":1,"375":2,"376":4,"377":3,"378":3,"380":3,"383":7,"384":8,"386":3,"387":3,"388":13,"390":13,"393":1,"394":4,"395":1,"397":2,"404":2,"408":9,"409":3,"412":3,"414":20,"415":5,"417":1,"418":1,"419":4,"421":4,"422":4,"423":15,"424":7,"429":8,"430":2,"431":1,"435":2,"436":31,"438":5,"439":14,"441":1,"442":1,"443":1,"445":3,"446":13,"447":6,"448":20,"449":3,"452":11,"453":19,"454":15,"456":1,"462":3,"464":2,"469":3,"470":2,"473":3,"480":7,"492":1,"497":1,"499":1,"504":2,"507":1,"511":1,"515":2,"523":1,"524":2,"527":7,"528":1,"529":12,"531":2,"534":5,"535":3,"536":1,"541":1,"542":1,"544":2,"545":1,"549":2,"556":2,"559":2,"560":5,"562":1,"563":5,"564":1,"567":3,"575":1,"581":2,"582":6,"583":2,"584":4,"585":2,"586":2,"587":16,"595":1,"598":1,"609":2,"613":2,"614":1,"615":3,"616":3,"618":4,"619":5,"621":3,"622":2,"624":6,"625":3,"627":1,"634":1,"636":4,"639":4,"641":2,"643":1,"644":1,"646":6,"647":1,"649":1,"650":10,"652":3,"653":5,"654":2,"655":2,"656":2,"658":3,"659":5,"660":2,"661":2,"662":6,"663":13,"664":5,"665":3,"666":13,"667":1,"668":7,"669":10,"670":1,"673":1,"674":1,"675":9,"679":4,"683":4,"684":1,"685":6,"686":3,"687":2,"689":4,"690":5,"691":1,"692":2,"693":4,"694":6,"695":2,"696":6,"697":2,"698":2,"699":3,"700":2,"701":2,"702":1,"703":2,"704":8,"705":2,"706":7,"707":4,"708":2,"709":4,"710":1,"711":3,"712":1,"713":3,"714":7,"715":4,"716":4,"717":3,"718":1,"720":10,"722":1,"723":1,"724":2,"728":1,"737":3,"745":2,"746":1,"747":3,"748":3,"751":1,"753":1,"754":1,"756":1,"760":1,"761":5,"762":4,"763":2,"765":1,"770":1,"771":6,"772":4,"773":2,"776":1,"777":3,"778":1,"779":1,"780":1,"781":2,"782":4,"784":6,"786":2,"788":2,"794":1,"809":4,"814":2,"818":2,"819":1,"826":1,"829":4,"831":4,"832":7,"833":2,"834":6,"835":7,"836":15,"838":5,"840":8,"841":26,"843":22,"844":41,"845":24,"847":19,"848":49,"849":20,"851":61,"852":77,"854":19,"855":11,"856":10,"857":19,"859":24,"860":39,"861":34,"863":20,"864":40,"865":38,"866":8,"867":3,"868":34,"869":23,"871":41,"872":73,"873":36,"874":25,"875":12,"876":29,"877":6,"878":3,"879":8,"880":8,"881":5,"882":1,"884":1,"885":6,"886":2,"887":1,"888":6,"893":1,"901":1,"902":9,"903":5,"904":13,"910":7,"911":7,"912":1,"913":7,"914":3,"915":9,"916":12,"917":10,"918":12,"919":8,"920":10,"921":7,"922":5,"924":3,"925":2,"926":9,"927":2,"929":2,"930":2,"932":13,"933":5,"934":7,"935":2,"936":3,"937":6,"938":2,"940":4,"941":1,"942":1,"943":3,"944":1,"946":5,"947":7,"948":14,"949":4,"951":1,"952":1,"953":2,"954":1,"956":4,"957":5,"958":2,"959":3,"960":1,"961":7,"963":1,"965":4,"966":5,"970":4,"971":4,"973":2,"974":10,"975":10,"976":3,"977":1,"978":5,"979":3,"980":1,"981":1,"982":6,"983":6,"984":5,"985":9,"986":10,"987":1,"988":7,"989":6,"990":1,"992":4,"993":2,"994":7,"995":5,"996":8,"997":6,"998":2,"1000":2,"1001":1,"1002":3,"1003":1,"1004":1,"1005":10,"1006":1,"1007":2,"1009":1,"1010":2,"1011":2,"1012":1,"1014":5,"1015":6,"1016":9,"1017":5,"1018":1,"1019":2,"1020":4,"1021":1,"1023":5,"1025":1,"1031":1,"1032":4,"1033":5,"1036":5,"1037":14,"1038":5,"1039":4,"1040":8,"1041":7,"1042":11,"1043":6,"1044":10,"1045":8,"1046":6,"1047":10,"1048":2,"1049":11,"1050":1,"1051":1,"1052":2,"1053":1,"1054":5,"1055":6,"1056":2,"1057":2,"1060":4,"1061":1,"1062":1,"1063":4,"1065":7,"1066":2,"1067":18,"1068":14,"1069":5,"1070":19,"1071":5,"1073":13,"1074":13,"1075":8,"1076":19,"1077":18,"1078":21,"1079":13,"1080":17,"1081":11,"1082":17,"1083":1,"1084":1,"1086":2,"1088":2,"1090":1,"1091":1,"1094":11,"1095":1,"1096":6,"1097":5,"1098":9,"1099":2,"1100":3,"1101":7,"1102":9,"1103":1,"1104":2,"1105":25,"1106":7,"1107":5,"1108":2,"1111":7,"1113":1,"1115":1,"1119":1,"1122":1,"1123":2,"1125":1,"1126":3,"1127":2,"1128":1,"1129":15,"1130":6,"1132":4,"1133":7,"1134":3,"1135":6,"1137":2,"1138":2,"1139":5,"1141":1,"1142":1,"1147":2,"1148":4,"1150":12,"1153":3,"1155":2,"1156":1,"1157":2,"1158":2,"1162":3,"1164":2,"1165":3,"1166":1,"1167":1,"1169":1,"1170":1,"1171":1,"1173":2,"1174":1,"1175":2,"1176":5,"1178":3,"1179":2,"1180":4,"1181":2,"1183":4,"1184":1,"1185":11,"1188":3,"1189":2,"1190":4,"1192":3,"1193":10,"1195":1,"1196":2,"1199":1,"1200":4,"1202":1,"1203":9,"1204":2,"1205":1,"1206":1,"1207":2,"1208":5,"1209":5,"1210":8,"1211":8,"1212":3,"1213":2,"1214":5,"1215":7,"1216":2,"1217":2,"1218":2,"1220":6,"1225":2,"1232":1,"1233":2,"1235":4,"1236":3,"1239":2,"1240":1,"1241":1,"1243":2,"1244":1,"1250":1,"1252":5,"1253":2,"1254":8,"1255":4,"1256":1,"1258":1,"1260":1,"1263":1,"1268":1,"1270":1,"1271":1,"1272":1,"1278":6,"1279":1,"1280":4,"1281":7,"1303":1,"1304":1,"1305":5,"1309":9,"1311":1,"1316":1,"1318":2,"1322":1,"1323":1,"1324":6,"1325":1,"1326":16,"1327":5,"1328":2,"1329":2,"1330":1,"1331":7,"1332":5,"1334":2,"1337":4,"1338":8,"1343":4,"1348":1,"1350":2,"1351":3,"1352":5,"1356":1,"1357":5,"1358":2,"1359":2,"1360":1,"1363":8,"1366":4,"1367":4,"1368":11,"1369":2,"1370":2,"1371":3,"1372":5,"1373":2,"1374":1,"1376":9,"1378":4,"1379":6,"1380":1,"1381":4,"1382":58,"1383":5,"1384":2,"1385":24,"1386":30,"1388":4,"1390":5,"1391":1,"1393":5,"1394":6,"1395":1,"1396":12,"1398":17,"1399":9,"1400":8,"1401":14,"1402":9,"1403":20,"1404":9,"1405":16,"1406":13,"1407":9,"1408":8,"1409":23,"1410":7,"1411":3,"1412":9,"1413":4,"1414":6,"1415":8,"1416":9,"1417":5,"1418":6,"1419":24,"1420":11,"1421":14,"1422":14,"1423":9,"1424":5,"1426":4,"1427":10,"1428":6,"1429":5,"1430":4,"1431":35,"1432":4,"1433":2,"1434":2,"1435":13,"1436":5,"1437":5,"1438":4,"1439":9,"1440":4,"1441":8,"1442":10,"1443":2,"1447":7,"1448":1,"1449":3,"1451":1,"1452":1,"1454":5,"1455":1,"1456":1,"1458":7,"1459":4,"1460":8,"1464":5,"1465":1,"1470":2,"1471":1,"1472":3,"1475":1,"1480":1,"1484":1,"1489":3,"1492":1,"1493":2,"1502":1,"1504":4,"1506":1,"1511":4,"1513":1,"1515":3,"1516":1,"1517":1,"1518":5,"1519":1,"1520":2,"1521":3,"1522":5,"1523":6,"1524":1,"1525":1,"1527":2,"1531":2,"1533":3,"1540":3,"1543":2,"1544":4,"1547":2,"1549":1,"1558":1,"1559":5,"1566":1,"1567":4,"1568":2,"1569":10,"1570":2,"1571":5,"1572":1,"1573":3,"1574":1,"1575":1,"1576":3,"1577":1,"1579":2,"1580":2,"1581":1,"1582":2,"1588":2,"1589":1,"1590":1,"1591":1,"1596":1,"1599":1,"1603":2,"1605":4,"1607":1,"1608":4,"1609":7,"1613":2,"1615":1,"1616":1,"1617":1,"1618":5,"1619":2,"1620":6,"1621":1,"1622":1,"1624":1,"1630":1,"1644":2,"1645":1,"1649":1,"1651":5,"1653":1,"1654":1,"1655":6,"1656":1,"1657":1,"1658":3,"1661":3,"1662":3,"1664":4,"1670":2,"1671":4,"1672":3,"1674":1,"1678":1,"1684":3,"1685":3,"1686":5,"1687":2,"1688":4,"1689":4,"1690":1,"1696":3,"1699":1,"1701":1,"1703":3,"1704":7,"1706":1,"1712":1,"1715":1,"1716":1,"1722":1,"1723":6,"1725":1,"1726":1,"1727":3,"1728":2,"1731":1,"1732":1,"1733":7,"1737":1,"1738":12,"1739":1,"1740":3,"1741":6,"1742":3,"1743":11,"1744":1,"1746":2,"1747":1,"1748":1,"1754":1,"1756":1,"1758":1,"1759":4,"1762":1,"1764":5,"1766":1,"1767":2,"1768":2,"1770":4,"1776":1,"1781":1,"1785":2,"1789":1,"1792":706,"1799":2,"1801":1,"1802":8,"1806":1,"1813":6,"1816":1,"1817":2,"1818":5,"1819":2,"1820":3,"1822":10,"1823":9,"1824":26,"1825":13,"1827":4,"1828":1,"1829":1,"1830":6,"1831":2,"1832":3,"1833":3,"1834":1,"1840":5,"1845":1,"1850":3,"1851":7,"1852":2,"1856":17,"1858":5,"1861":1,"1862":5,"1868":6,"1870":3,"1875":2,"1885":1,"1886":1,"1888":2,"1890":1,"1894":1,"1898":5,"1899":1,"1900":1,"1901":1,"1906":1,"1908":3,"1909":2,"1910":3,"1911":2,"1912":5,"1915":3,"1917":4,"1920":3,"1921":4,"1922":9,"1923":7,"1924":20,"1925":19,"1926":1,"1929":8,"1930":3,"1932":1,"1940":2,"1941":1,"1942":1,"1943":1,"1947":2,"1948":3,"1949":2,"1951":4,"1952":3,"1953":5,"1954":3,"1955":1,"1956":3,"1957":7,"1958":9,"1959":3,"1961":13,"1968":2,"1971":1,"1973":4,"1974":13,"1978":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":3,"1984":3,"1991":3,"1994":2,"2000":3,"2002":2,"2003":2,"2004":2,"2005":3,"2006":1,"2007":7,"2008":1,"2009":1,"2010":2,"2011":4,"2012":2,"2013":2,"2014":1,"2016":5,"2017":1,"2018":4,"2019":1,"2020":2,"2021":1,"2025":2,"2036":1,"2038":6,"2039":3,"2040":17,"2041":1,"2047":3,"2052":1,"2056":4,"2060":1,"2062":1,"2072":1,"2075":5,"2076":1,"2077":5,"2079":1,"2086":2,"2088":1,"2092":7,"2094":6,"2095":1,"2096":2,"2097":4,"2098":10,"2100":1,"2102":3,"2103":1,"2104":3,"2106":8,"2107":9,"2108":1,"2109":7,"2110":10,"2111":3,"2112":9,"2114":1,"2115":1,"2117":1,"2118":1,"2119":1,"2125":2,"2127":1,"2129":2,"2130":1,"2131":2,"2140":1,"2141":1,"2143":4,"2147":1,"2149":1,"2153":7,"2154":4,"2155":2,"2156":13,"2157":15,"2158":2,"2159":3,"2160":4,"2161":1,"2162":4,"2164":11,"2165":4,"2166":1,"2167":6,"2170":4,"2171":7,"2172":2,"2174":2,"2175":4,"2176":6,"2177":17,"2178":2,"2179":2,"2180":7,"2181":13,"2182":4,"2183":14,"2184":6,"2185":4,"2186":3,"2187":4,"2189":1,"2190":2,"2191":2,"2192":4,"2193":8,"2194":1,"2195":5,"2197":2,"2201":1,"2202":2,"2203":1,"2206":1,"2207":2,"2208":1,"2209":1,"2210":1,"2212":3,"2217":1,"2218":1,"2220":2,"2221":5,"2222":14,"2223":7,"2224":2,"2225":1,"2226":1,"2227":1,"2245":1,"2247":5,"2251":1,"2252":2,"2254":24,"2255":4,"2256":15,"2257":2,"2258":2,"2259":1,"2264":28,"2265":12,"2266":2,"2267":1,"2271":2,"2272":7,"2273":4,"2274":3,"2277":4,"2279":3,"2282":4,"2283":13,"2284":6,"2287":3,"2288":3,"2289":8,"2290":3,"2291":6,"2292":4,"2293":4,"2296":2,"2297":8,"2300":5,"2302":9,"2303":5,"2304":9,"2305":4,"2306":1,"2307":7,"2308":2,"2309":2,"2310":8,"2313":6,"2314":2,"2317":1,"2318":6,"2319":3,"2320":6,"2321":2,"2322":6,"2323":3,"2324":1,"2325":2,"2327":5,"2328":3,"2329":1,"2330":1,"2333":7,"2335":1,"2336":3,"2337":16,"2339":4,"2340":7,"2342":2,"2343":2,"2344":1,"2346":4,"2347":5,"2348":6,"2350":3,"2351":1,"2353":1,"2354":5,"2356":2,"2357":1,"2358":4,"2359":6,"2360":3,"2362":3,"2363":2,"2364":3,"2365":6,"2366":3,"2367":4,"2371":2,"2372":1,"2375":19,"2376":9,"2377":4,"2378":5,"2379":12,"2380":23,"2381":4,"2382":7,"2383":5,"2384":2,"2385":6,"2388":2,"2389":26,"2391":8,"2392":7,"2393":4,"2394":9,"2395":12,"2397":4,"2398":14,"2399":2,"2400":3,"2402":5,"2403":3,"2404":3,"2405":3,"2406":1,"2407":5,"2410":2,"2411":9,"2412":4,"2413":3,"2414":8,"2415":8,"2416":4,"2417":1,"2419":3,"2420":5,"2421":13,"2422":10,"2423":5,"2424":3,"2425":4,"2427":5,"2428":5,"2429":5,"2430":3,"2431":3,"2432":8,"2433":3,"2434":4,"2435":10,"2436":3,"2437":3,"2438":17,"2440":4,"2441":2,"2442":6,"2443":4,"2445":3,"2446":3,"2448":1,"2450":11,"2451":23,"2452":8,"2453":7,"2454":2,"2455":9,"2456":8,"2457":1,"2459":10,"2461":5,"2462":2,"2463":12,"2464":4,"2465":6,"2466":26,"2468":3,"2470":5,"2471":3,"2472":6,"2474":3,"2476":8,"2477":4,"2479":5,"2481":49,"2482":9,"2483":7,"2484":10,"2486":9,"2487":6,"2489":6,"2490":7,"2491":3,"2492":5,"2493":5,"2494":7,"2495":8,"2496":5,"2497":5,"2498":8,"2500":3,"2502":14,"2504":15,"2505":7,"2506":2,"2508":1,"2509":18,"2510":5,"2511":5,"2512":2,"2513":6,"2515":1,"2517":8,"2518":19,"2519":13,"2520":8,"2521":6,"2522":5,"2523":8,"2525":7,"2526":3,"2527":14,"2528":16,"2529":25,"2530":15,"2531":10,"2532":18,"2533":30,"2534":22,"2535":19,"2536":3,"2537":48,"2538":5,"2539":8,"2540":22,"2541":10,"2542":17,"2543":31,"2544":6,"2545":10,"2546":10,"2549":8,"2550":2,"2551":4,"2554":3,"2555":5,"2558":3,"2565":2,"2566":3,"2567":1,"2571":1,"2572":1,"2575":1,"2576":1,"2577":5,"2580":1,"2581":2,"2586":8,"2587":1,"2588":1,"2589":2,"2590":6,"2591":3,"2595":1,"2596":3,"2597":6,"2600":1,"2603":1,"2607":14,"2608":11,"2611":4,"2614":3,"2615":5,"2626":5,"2627":1,"2628":4,"2629":1,"2632":7,"2633":9,"2634":13,"2635":4,"2641":6,"2645":1,"2648":2,"2649":3,"2650":1,"2652":1,"2653":1,"2655":2,"2656":3,"2659":3,"2660":2,"2661":2,"2662":3,"2664":3,"2665":9,"2666":2,"2669":1,"2670":3,"2672":1,"2673":2,"2674":1,"2677":4,"2678":4,"2679":2,"2681":1,"2682":2,"2684":2,"2685":1,"2687":4,"2688":4,"2689":1,"2691":2,"2692":1,"2693":3,"2694":4,"2695":3,"2696":1,"2701":1,"2702":2,"2703":1,"2704":1,"2705":3,"2711":3,"2712":3,"2713":3,"2714":3,"2716":1,"2717":4,"2718":1,"2719":3,"2721":8,"2722":5,"2723":7,"2724":3,"2725":2,"2726":2,"2727":3,"2728":1,"2729":5,"2731":3,"2732":2,"2733":4,"2734":2,"2737":1,"2739":4,"2740":4,"2741":5,"2742":4,"2744":3,"2745":1,"2749":2,"2750":5,"2751":4,"2752":2,"2754":2,"2755":3,"2756":1,"2758":1,"2759":7,"2760":10,"2761":1,"2762":12,"2763":8,"2764":5,"2765":11,"2766":2,"2767":3,"2768":8,"2769":11,"2771":1,"2774":3,"2775":1,"2776":4,"2779":5,"2782":2,"2783":2,"2784":2,"2785":2,"2786":4,"2788":2,"2789":1,"2790":1,"2791":3,"2792":10,"2794":7,"2795":16,"2797":3,"2798":2,"2799":2,"2800":5,"2801":1,"2802":11,"2803":7,"2804":5,"2805":2,"2806":4,"2807":12,"2808":1,"2809":5,"2810":9,"2811":6,"2812":9,"2813":11,"2814":3,"2815":11,"2816":1,"2817":2,"2818":2,"2820":1,"2821":1,"2822":3,"2823":3,"2824":4,"2825":2,"2827":4,"2828":6,"2829":12,"2830":11,"2831":5,"2832":4,"2833":7,"2834":9,"2835":7,"2836":8,"2837":1,"2838":3,"2839":2,"2840":9,"2841":3,"2842":1,"2843":2,"2845":12,"2846":3,"2847":2,"2848":3,"2849":3,"2850":1,"2851":2,"2852":4,"2853":2,"2854":3,"2855":3,"2856":4,"2857":5,"2858":2,"2860":7,"2861":6,"2862":13,"2863":1,"2864":12,"2865":12,"2866":3,"2867":4,"2868":20,"2869":19,"2870":2,"2871":14,"2872":4,"2873":4,"2874":4,"2875":7,"2876":10,"2877":1,"2878":13,"2879":8,"2880":4,"2881":5,"2882":2}}],["torn",{"2":{"2532":1}}],["toward",{"2":{"2438":1}}],["tofile",{"2":{"1792":1,"1800":1,"1804":2,"1810":1,"2804":2}}],["toconsole",{"2":{"1792":1,"1800":1,"1803":2,"1810":1,"2804":1}}],["toctou",{"2":{"864":1}}],["toggles",{"0":{"1415":1},"2":{"2474":1}}],["together",{"0":{"1179":1,"2521":1},"2":{"101":1,"306":1,"364":1,"831":1,"841":1,"857":1,"919":1,"937":1,"988":1,"1048":1,"1179":1,"1180":1,"1372":1,"1394":1,"1396":1,"1402":1,"1423":1,"1445":1,"1464":1,"1639":1,"1644":1,"1792":1,"2170":1,"2193":1,"2409":1,"2419":1,"2435":1,"2486":1,"2533":1,"2554":1,"2581":1,"2858":1}}],["toast",{"2":{"1412":1}}],["tostring",{"2":{"1366":1,"2622":4}}],["toscrape",{"2":{"214":1,"1423":1,"1426":1,"1430":1,"1431":1,"1743":1,"2502":1,"2760":1,"2762":3}}],["tokio",{"2":{"1255":1}}],["tokenizer",{"2":{"2540":1,"2845":1}}],["tokenurl",{"2":{"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1792":5}}],["tokenvalue",{"2":{"1492":1,"1792":1}}],["tokentype",{"2":{"1455":1,"2554":1}}],["token=hacked",{"2":{"527":1,"529":1,"2284":1}}],["tokenlimit",{"2":{"477":1,"1160":1,"1792":1,"1953":2,"1960":1,"2257":1,"2443":1}}],["tokenbucket",{"2":{"477":1,"480":1,"868":1,"1160":1,"1792":2,"1950":1,"1953":2,"1958":1,"1960":1,"2257":1,"2443":1,"2444":1,"2470":1,"2551":1,"2558":1}}],["token",{"0":{"477":1,"531":1,"1063":1,"1160":1,"1450":1,"1451":1,"1452":1,"1456":1,"1457":1,"1490":1,"1902":1,"1953":1,"2174":1,"2554":1},"1":{"1451":1,"1452":1,"1491":1,"1492":1},"2":{"66":1,"133":2,"208":2,"209":4,"211":1,"212":4,"215":8,"277":2,"286":1,"297":3,"302":2,"303":2,"312":3,"378":1,"477":1,"527":8,"531":3,"532":4,"533":3,"534":1,"835":1,"854":2,"855":1,"871":2,"872":1,"1017":2,"1030":1,"1033":6,"1045":3,"1053":4,"1054":2,"1055":1,"1061":2,"1062":2,"1063":2,"1064":2,"1079":1,"1098":6,"1101":1,"1320":4,"1401":1,"1444":1,"1445":3,"1450":4,"1451":3,"1452":2,"1453":3,"1454":5,"1456":3,"1457":3,"1458":3,"1470":1,"1485":1,"1487":1,"1489":3,"1491":1,"1492":1,"1494":1,"1507":1,"1550":1,"1564":1,"1582":1,"1691":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1699":1,"1700":1,"1730":1,"1731":1,"1738":9,"1742":2,"1788":1,"1792":40,"1825":3,"1830":2,"1894":1,"1902":1,"1906":1,"1907":1,"1950":1,"1953":2,"2038":1,"2040":2,"2170":1,"2171":2,"2174":3,"2175":1,"2178":1,"2212":3,"2254":2,"2257":1,"2283":8,"2285":3,"2286":3,"2290":2,"2375":3,"2377":3,"2410":1,"2413":1,"2435":1,"2438":3,"2466":2,"2476":1,"2477":1,"2481":2,"2497":1,"2529":1,"2534":1,"2554":13,"2615":3,"2768":7}}],["tokensperperiod",{"2":{"1160":1,"1792":1,"1953":2,"1960":1,"2443":1,"2444":1,"2558":1}}],["tokens",{"2":{"10":1,"215":1,"292":2,"390":1,"396":1,"527":2,"531":2,"532":2,"696":2,"697":1,"871":1,"934":1,"1033":2,"1037":1,"1045":1,"1048":2,"1054":3,"1062":2,"1063":1,"1064":2,"1081":1,"1098":2,"1160":3,"1303":1,"1401":3,"1405":1,"1451":1,"1453":1,"1454":2,"1460":1,"1487":1,"1489":1,"1490":1,"1649":1,"1651":1,"1653":1,"1738":1,"1792":9,"1830":2,"1862":1,"1953":4,"2040":1,"2098":3,"2164":2,"2165":3,"2188":1,"2221":1,"2282":1,"2283":2,"2285":2,"2353":1,"2375":1,"2477":1,"2482":1,"2493":1,"2534":3,"2545":1,"2554":1,"2615":1,"2736":2,"2768":1,"2871":2,"2873":1}}],["to=null",{"2":{"1067":1,"1525":1}}],["to=2025",{"2":{"1067":1}}],["to=eur",{"2":{"263":1}}],["tour",{"2":{"2479":1}}],["touched",{"0":{"2448":1,"2457":1},"2":{"1402":1,"2398":1,"2872":1}}],["touches",{"2":{"868":1,"1210":1,"1409":1,"1435":1,"1436":1,"1868":1,"2537":1,"2879":1}}],["touch",{"2":{"1382":1,"1397":1,"1402":1,"2437":1,"2463":1}}],["touching",{"2":{"1075":1,"1137":1,"1328":1,"1337":1,"1382":1,"1396":1,"1410":1,"2869":1,"2878":1}}],["touppercase",{"2":{"1026":2}}],["tolerant",{"2":{"2456":1}}],["tolerance",{"2":{"1454":1,"2554":1}}],["tolerate",{"2":{"2007":1,"2328":1}}],["tolerates",{"2":{"2007":1}}],["tolocalestring",{"2":{"996":1}}],["told",{"2":{"852":1,"1384":1,"1441":1}}],["toisostring",{"2":{"961":1,"1335":1,"1792":1}}],["todate=2024",{"2":{"2845":1}}],["todate=",{"2":{"2731":1}}],["today",{"2":{"851":1,"977":1,"980":1,"990":1,"1382":1,"2381":1,"2438":3}}],["todictionary",{"2":{"2614":1}}],["todo",{"2":{"324":2,"839":1}}],["toopentelemetry",{"2":{"1792":1,"1800":1,"1807":2,"1810":1,"2804":1}}],["took",{"2":{"861":1,"1078":1,"1401":1,"1403":1,"1678":1,"2411":1}}],["too",{"0":{"1072":1,"1078":1},"1":{"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1},"2":{"374":1,"436":1,"480":1,"576":1,"664":1,"828":1,"841":1,"844":2,"845":1,"847":2,"852":1,"869":1,"873":1,"1037":1,"1042":1,"1079":1,"1082":1,"1101":1,"1152":1,"1157":1,"1169":1,"1254":1,"1281":1,"1378":1,"1385":1,"1399":1,"1401":1,"1406":1,"1421":1,"1431":1,"1435":1,"1594":1,"1624":1,"1678":1,"1792":6,"1925":2,"1948":1,"1949":1,"1958":3,"1959":1,"1960":1,"2089":1,"2104":1,"2184":1,"2200":1,"2222":1,"2257":1,"2323":1,"2332":1,"2468":1,"2470":2,"2471":1,"2493":1,"2531":1,"2537":1,"2795":1,"2830":1,"2857":1,"2869":1,"2878":1}}],["tooldescriptionsuffix",{"0":{"1821":1},"2":{"1792":1,"1814":1,"1823":1,"2481":1}}],["toolchain",{"2":{"873":1,"1419":1}}],["tool",{"0":{"322":1,"325":1,"1042":1,"2667":1},"1":{"2668":1,"2669":1,"2670":1,"2671":1,"2672":1,"2673":1},"2":{"223":1,"317":2,"318":3,"319":1,"320":6,"322":1,"325":1,"326":4,"327":1,"834":1,"837":1,"845":1,"926":1,"1036":1,"1037":2,"1038":3,"1040":4,"1041":1,"1042":3,"1043":2,"1044":1,"1045":2,"1046":4,"1203":1,"1206":3,"1255":1,"1382":2,"1385":3,"1386":1,"1388":1,"1400":1,"1401":2,"1402":2,"1403":3,"1404":1,"1409":1,"1792":4,"1821":1,"1823":5,"1824":2,"1825":2,"1827":1,"1832":1,"1833":1,"1834":2,"1961":1,"2111":2,"2166":2,"2223":2,"2232":1,"2481":13,"2482":2,"2532":3,"2871":1,"2874":1}}],["tooling",{"2":{"1":1,"876":1,"1082":1,"1111":1,"1366":1,"1378":1,"1400":1,"2874":2}}],["tools",{"0":{"320":1,"831":1,"906":1,"908":1,"1038":1,"1042":1,"1400":1,"1401":1,"1402":1},"1":{"832":1,"833":1,"834":1,"835":1,"836":1,"837":1,"838":1,"907":1,"908":1,"909":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1401":1,"1402":1},"2":{"0":1,"317":2,"320":1,"327":1,"480":1,"529":1,"831":1,"832":1,"836":2,"840":1,"851":1,"876":1,"908":1,"994":1,"1037":4,"1038":1,"1039":3,"1041":1,"1042":2,"1043":2,"1044":4,"1045":2,"1046":1,"1047":1,"1083":1,"1097":1,"1113":1,"1247":1,"1381":1,"1383":1,"1384":1,"1385":1,"1393":1,"1400":1,"1401":2,"1402":7,"1404":1,"1619":1,"1676":1,"1677":1,"1789":1,"1792":6,"1813":3,"1820":1,"1821":1,"1822":1,"1823":1,"1824":5,"1825":1,"1832":4,"1833":3,"1834":1,"1961":1,"2055":1,"2162":1,"2166":3,"2193":1,"2223":4,"2479":2,"2481":12,"2498":2,"2667":1,"2874":1}}],["totaling",{"2":{"875":1}}],["totals",{"2":{"860":1,"872":1}}],["totally",{"2":{"841":1}}],["total",{"2":{"180":1,"426":1,"427":2,"562":2,"563":1,"566":1,"834":1,"869":1,"894":4,"1027":1,"1044":1,"1064":1,"1169":1,"1187":1,"1188":1,"1189":1,"1192":2,"1193":2,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1350":1,"1361":2,"1366":3,"1373":1,"1398":2,"1410":4,"1414":1,"1824":3,"1991":1,"2049":1,"2340":2,"2364":1,"2435":2,"2621":1,"2733":1}}],["topostgres",{"2":{"1792":1,"1800":1,"1805":2,"1810":1,"2803":1,"2804":1}}],["topic",{"2":{"650":2,"669":1,"1399":1,"1400":1,"1403":1,"2700":1,"2828":1}}],["top",{"0":{"1264":1,"2115":1,"2702":1},"1":{"2116":1,"2117":1,"2118":1,"2119":1,"2120":1,"2121":1,"2703":1,"2704":1},"2":{"111":1,"436":1,"845":1,"848":1,"860":1,"868":1,"869":1,"872":1,"873":1,"874":1,"879":1,"910":1,"1067":1,"1069":1,"1265":1,"1266":1,"1278":1,"1280":3,"1281":1,"1351":1,"1400":1,"1418":1,"1435":1,"1436":1,"1520":1,"1609":1,"1658":1,"1787":1,"1792":2,"1794":1,"1825":1,"1856":1,"1924":1,"1956":1,"2092":1,"2106":1,"2117":1,"2154":1,"2175":1,"2359":1,"2379":1,"2398":2,"2438":2,"2455":1,"2509":1,"2542":1,"2661":1,"2689":1,"2702":1,"2794":1,"2812":1,"2878":1}}],["to",{"0":{"221":1,"275":1,"395":1,"534":1,"583":1,"645":1,"837":1,"910":1,"972":2,"992":1,"1035":1,"1082":1,"1120":1,"1125":1,"1126":1,"1170":1,"1202":1,"1271":1,"1323":1,"1328":1,"1351":1,"1354":1,"1378":1,"1383":1,"1406":1,"1409":1,"1427":2,"1959":1,"2088":1,"2249":2,"2363":1,"2471":1,"2490":1,"2511":1,"2591":1,"2713":1,"2718":1,"2728":1,"2867":1},"1":{"911":1,"973":2,"974":2,"975":2,"976":2,"977":2,"978":2,"979":2,"980":2,"981":2,"982":2,"983":2,"984":2,"985":2,"986":2,"987":2,"988":2,"989":2,"990":2,"991":2,"992":2,"993":2,"994":2,"995":2,"996":2,"997":2,"998":2,"999":2,"1000":2,"1001":2,"1002":2,"1003":2,"1004":2,"1005":2,"1006":2,"1007":2,"1008":2,"1009":2,"1121":1,"1122":1,"1123":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"0":1,"3":1,"4":1,"16":2,"19":1,"20":1,"27":1,"29":1,"31":1,"35":1,"40":1,"41":1,"43":1,"50":1,"51":3,"57":1,"58":1,"60":2,"61":1,"62":1,"63":1,"66":1,"73":2,"74":1,"78":1,"83":1,"87":3,"102":1,"106":6,"116":1,"119":1,"120":1,"121":1,"133":2,"134":1,"136":2,"150":1,"156":1,"157":1,"158":1,"160":1,"165":2,"167":1,"168":2,"169":1,"170":2,"175":1,"177":1,"182":2,"184":3,"186":4,"188":1,"197":1,"201":3,"208":1,"209":1,"213":1,"215":1,"220":2,"223":1,"227":1,"237":2,"238":1,"239":2,"260":1,"261":1,"263":1,"264":2,"268":1,"277":1,"286":2,"291":1,"296":1,"297":1,"298":2,"300":1,"301":1,"303":1,"304":1,"305":1,"306":1,"307":3,"308":1,"309":1,"310":4,"313":1,"315":1,"317":1,"319":1,"324":1,"334":2,"336":1,"337":1,"347":1,"357":1,"364":2,"372":3,"373":1,"376":2,"380":1,"384":4,"387":2,"390":6,"394":1,"395":1,"397":1,"401":1,"408":2,"412":2,"414":6,"415":4,"419":1,"421":2,"423":6,"424":2,"426":1,"427":1,"428":2,"429":1,"431":1,"433":1,"435":2,"436":10,"438":2,"439":5,"445":2,"446":2,"447":1,"448":3,"449":1,"451":1,"452":4,"453":2,"454":8,"456":1,"458":2,"462":1,"463":2,"464":2,"473":1,"476":1,"477":1,"478":1,"480":1,"494":3,"497":1,"499":2,"515":1,"527":1,"529":1,"531":1,"545":1,"559":2,"560":3,"562":1,"565":2,"581":1,"582":1,"583":1,"586":1,"587":2,"589":1,"595":1,"614":1,"615":1,"618":1,"619":3,"622":3,"624":1,"625":1,"636":1,"641":1,"643":1,"646":4,"650":4,"652":1,"653":1,"654":1,"656":1,"663":1,"666":3,"668":4,"669":6,"673":2,"675":3,"679":2,"683":1,"684":1,"686":1,"689":1,"690":2,"693":1,"694":1,"695":1,"701":1,"703":1,"704":1,"708":1,"713":1,"720":2,"722":1,"724":1,"726":1,"737":1,"747":14,"753":2,"757":3,"760":2,"761":1,"762":1,"764":1,"768":3,"770":2,"771":2,"772":1,"774":1,"776":2,"780":1,"781":4,"782":3,"783":1,"784":4,"785":1,"786":7,"788":4,"801":2,"809":1,"811":2,"814":1,"821":1,"823":1,"826":2,"831":1,"832":2,"833":2,"834":1,"835":2,"837":1,"840":1,"841":14,"843":7,"844":3,"845":8,"847":9,"848":15,"849":15,"851":29,"852":13,"854":6,"857":4,"859":7,"860":8,"861":6,"863":1,"864":6,"865":4,"866":2,"867":1,"868":7,"869":3,"871":8,"872":6,"873":8,"874":1,"875":4,"876":9,"877":2,"879":3,"881":1,"883":1,"886":1,"888":3,"891":3,"892":4,"894":1,"901":1,"902":3,"904":8,"909":1,"911":3,"912":2,"913":3,"914":2,"915":5,"916":12,"917":3,"918":11,"919":8,"920":6,"921":2,"922":4,"926":6,"930":1,"932":1,"933":4,"934":3,"935":2,"937":1,"942":1,"946":2,"948":5,"949":2,"952":1,"958":3,"961":2,"967":4,"968":1,"969":1,"970":1,"971":1,"972":2,"973":1,"974":3,"975":1,"979":1,"981":1,"982":3,"984":2,"985":5,"988":2,"989":2,"991":1,"993":1,"994":3,"995":3,"996":3,"997":1,"1004":1,"1006":2,"1008":2,"1009":4,"1010":1,"1011":3,"1013":2,"1014":2,"1019":2,"1020":1,"1021":3,"1026":1,"1036":1,"1037":12,"1038":1,"1039":1,"1041":1,"1042":2,"1044":3,"1045":3,"1046":1,"1048":1,"1049":1,"1051":1,"1052":1,"1053":2,"1054":1,"1055":2,"1056":1,"1057":1,"1058":2,"1059":1,"1063":3,"1064":1,"1065":3,"1066":2,"1067":10,"1068":1,"1069":1,"1070":2,"1071":2,"1073":6,"1074":3,"1075":4,"1076":4,"1077":2,"1079":3,"1081":2,"1082":3,"1086":4,"1087":1,"1090":2,"1095":2,"1096":2,"1097":2,"1098":5,"1099":1,"1100":2,"1101":1,"1102":3,"1103":2,"1104":2,"1105":10,"1106":4,"1107":3,"1108":2,"1110":2,"1111":6,"1113":1,"1119":1,"1122":1,"1123":1,"1125":2,"1127":5,"1128":2,"1129":2,"1130":5,"1132":2,"1133":3,"1134":3,"1135":4,"1138":3,"1139":5,"1142":3,"1147":1,"1148":2,"1150":7,"1154":1,"1157":1,"1158":2,"1160":3,"1161":2,"1162":2,"1165":2,"1166":1,"1167":1,"1168":2,"1169":2,"1171":1,"1173":1,"1174":1,"1176":4,"1177":4,"1181":2,"1184":1,"1185":2,"1189":1,"1191":2,"1193":3,"1197":2,"1202":1,"1203":3,"1205":1,"1207":2,"1208":1,"1209":3,"1213":1,"1214":2,"1216":1,"1217":1,"1218":1,"1220":2,"1221":1,"1224":4,"1225":1,"1226":4,"1227":1,"1229":1,"1230":1,"1232":4,"1233":1,"1234":1,"1236":2,"1237":1,"1241":2,"1247":1,"1249":3,"1250":1,"1251":1,"1252":1,"1253":2,"1254":17,"1255":1,"1258":3,"1266":3,"1280":1,"1281":1,"1302":1,"1303":1,"1304":1,"1305":1,"1309":2,"1315":1,"1316":2,"1318":2,"1320":1,"1322":2,"1323":1,"1324":2,"1325":2,"1326":1,"1328":3,"1329":2,"1331":3,"1332":3,"1334":1,"1335":2,"1338":5,"1339":1,"1340":3,"1345":1,"1348":2,"1351":2,"1352":1,"1355":1,"1357":3,"1358":5,"1359":1,"1360":1,"1362":1,"1363":1,"1366":6,"1367":3,"1372":2,"1374":1,"1376":1,"1377":1,"1378":2,"1381":2,"1382":7,"1384":5,"1385":22,"1386":18,"1388":7,"1389":3,"1390":8,"1391":3,"1392":3,"1393":4,"1394":5,"1395":6,"1396":10,"1397":1,"1398":11,"1399":6,"1400":4,"1401":18,"1402":14,"1403":22,"1404":5,"1405":5,"1406":7,"1408":4,"1409":8,"1410":4,"1412":1,"1413":2,"1414":1,"1415":3,"1417":2,"1419":5,"1420":2,"1421":3,"1422":5,"1427":2,"1428":1,"1430":2,"1431":6,"1432":3,"1433":1,"1434":1,"1435":3,"1436":2,"1437":2,"1439":2,"1441":4,"1442":1,"1443":1,"1447":3,"1448":1,"1451":3,"1452":2,"1454":8,"1456":2,"1458":1,"1460":1,"1464":2,"1470":1,"1471":1,"1475":7,"1477":7,"1481":2,"1485":1,"1493":2,"1507":1,"1511":6,"1515":3,"1516":1,"1517":1,"1518":1,"1520":3,"1521":1,"1523":3,"1524":2,"1525":3,"1529":3,"1531":1,"1538":1,"1540":5,"1542":1,"1543":2,"1544":3,"1546":1,"1551":2,"1554":2,"1558":1,"1559":1,"1560":2,"1562":4,"1564":1,"1567":1,"1568":1,"1570":1,"1571":2,"1572":3,"1573":1,"1574":2,"1576":1,"1577":1,"1582":3,"1588":2,"1593":1,"1599":1,"1600":1,"1601":1,"1604":2,"1605":2,"1607":1,"1609":1,"1615":1,"1618":3,"1620":1,"1624":1,"1628":3,"1631":2,"1632":3,"1641":1,"1642":1,"1643":1,"1644":1,"1651":6,"1654":2,"1661":2,"1662":3,"1664":2,"1668":1,"1670":4,"1671":3,"1672":1,"1673":1,"1676":1,"1678":1,"1679":1,"1684":5,"1685":4,"1687":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1703":3,"1706":1,"1709":1,"1717":3,"1720":2,"1723":2,"1728":1,"1733":1,"1738":3,"1739":1,"1741":1,"1744":1,"1745":2,"1747":1,"1757":1,"1759":1,"1762":1,"1764":2,"1767":5,"1768":2,"1769":4,"1771":1,"1774":1,"1785":1,"1789":1,"1792":506,"1801":1,"1802":2,"1803":1,"1804":2,"1805":2,"1818":1,"1819":1,"1820":1,"1821":1,"1822":2,"1823":2,"1824":5,"1825":1,"1832":1,"1833":2,"1837":1,"1838":8,"1840":3,"1841":2,"1843":1,"1844":1,"1848":6,"1849":2,"1850":1,"1851":4,"1856":5,"1857":2,"1858":1,"1859":1,"1862":2,"1867":3,"1870":2,"1871":2,"1874":2,"1875":1,"1876":3,"1877":1,"1879":1,"1880":1,"1882":2,"1884":1,"1886":3,"1890":2,"1894":1,"1898":4,"1900":1,"1909":2,"1911":2,"1915":2,"1917":6,"1920":1,"1921":1,"1922":2,"1923":1,"1924":2,"1925":6,"1926":1,"1927":2,"1928":3,"1929":2,"1930":2,"1937":2,"1941":1,"1942":1,"1947":2,"1948":2,"1949":3,"1951":2,"1952":2,"1953":4,"1954":2,"1956":2,"1957":1,"1959":1,"1961":1,"1962":1,"1964":1,"1967":4,"1968":1,"1970":1,"1971":1,"1973":2,"1974":7,"1980":1,"1981":2,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":2,"1989":1,"1990":1,"1994":2,"2000":2,"2004":2,"2007":5,"2009":1,"2014":2,"2016":8,"2017":1,"2018":2,"2021":1,"2024":1,"2033":1,"2034":1,"2035":1,"2036":1,"2038":5,"2040":4,"2042":1,"2045":1,"2047":5,"2049":1,"2056":1,"2060":4,"2061":1,"2062":1,"2072":3,"2075":5,"2077":1,"2089":1,"2094":1,"2095":2,"2098":3,"2102":1,"2103":1,"2106":2,"2107":2,"2109":2,"2110":1,"2111":1,"2114":1,"2117":1,"2125":7,"2127":2,"2128":2,"2130":2,"2141":1,"2143":1,"2147":2,"2149":2,"2150":1,"2152":1,"2153":1,"2155":2,"2156":2,"2157":2,"2160":2,"2162":2,"2164":3,"2167":1,"2168":1,"2170":1,"2171":1,"2175":1,"2176":2,"2177":3,"2178":1,"2180":2,"2181":3,"2182":1,"2183":2,"2184":2,"2185":1,"2186":1,"2187":1,"2189":1,"2190":2,"2193":1,"2195":1,"2198":1,"2200":1,"2203":1,"2208":3,"2209":1,"2212":2,"2220":1,"2221":2,"2222":1,"2223":4,"2224":6,"2242":1,"2245":2,"2246":1,"2247":5,"2249":4,"2252":3,"2254":10,"2255":19,"2256":8,"2257":3,"2258":7,"2259":3,"2261":1,"2264":5,"2265":16,"2266":4,"2267":2,"2270":3,"2272":1,"2273":2,"2274":2,"2279":2,"2282":4,"2283":2,"2284":2,"2287":3,"2289":2,"2291":3,"2292":3,"2293":4,"2296":1,"2297":3,"2300":2,"2302":5,"2303":3,"2304":5,"2305":1,"2307":2,"2310":1,"2313":1,"2314":2,"2317":1,"2319":2,"2320":3,"2322":2,"2323":1,"2324":1,"2328":3,"2329":2,"2330":1,"2333":1,"2337":2,"2338":4,"2339":1,"2340":3,"2342":1,"2343":1,"2344":2,"2346":5,"2347":1,"2348":1,"2350":2,"2351":1,"2353":1,"2358":1,"2362":3,"2363":1,"2364":4,"2366":5,"2369":3,"2370":2,"2372":2,"2375":3,"2376":1,"2377":2,"2378":3,"2379":3,"2380":13,"2381":1,"2382":6,"2383":1,"2384":4,"2385":1,"2389":4,"2391":4,"2392":4,"2393":2,"2394":3,"2395":1,"2397":1,"2398":13,"2399":1,"2400":2,"2401":2,"2403":1,"2404":1,"2405":2,"2407":3,"2409":1,"2411":2,"2415":3,"2416":2,"2419":3,"2420":1,"2421":1,"2422":2,"2423":2,"2425":3,"2427":2,"2428":2,"2430":3,"2431":1,"2432":1,"2433":1,"2434":2,"2435":2,"2436":2,"2437":3,"2438":6,"2440":1,"2442":1,"2443":1,"2444":2,"2448":1,"2450":3,"2451":5,"2452":1,"2453":2,"2454":2,"2455":3,"2456":3,"2457":1,"2459":1,"2462":2,"2463":1,"2466":3,"2468":1,"2471":1,"2474":1,"2476":2,"2477":1,"2479":1,"2481":6,"2482":3,"2483":2,"2484":1,"2486":2,"2490":2,"2491":1,"2494":2,"2495":2,"2496":1,"2497":2,"2498":1,"2500":1,"2504":1,"2506":1,"2508":1,"2509":2,"2510":1,"2511":1,"2513":2,"2515":1,"2517":4,"2518":4,"2521":2,"2525":1,"2526":1,"2528":1,"2529":2,"2530":5,"2531":5,"2532":3,"2533":3,"2535":1,"2536":1,"2537":11,"2539":4,"2540":3,"2541":1,"2542":3,"2543":4,"2544":2,"2546":3,"2549":10,"2550":1,"2551":1,"2554":3,"2555":2,"2558":4,"2559":1,"2562":2,"2565":5,"2566":2,"2567":1,"2571":2,"2572":7,"2575":1,"2576":1,"2577":1,"2580":1,"2581":2,"2586":1,"2587":1,"2588":1,"2589":4,"2590":2,"2591":1,"2597":3,"2600":2,"2604":1,"2607":7,"2608":3,"2611":3,"2614":3,"2615":6,"2621":1,"2625":1,"2626":1,"2627":1,"2628":1,"2629":2,"2632":24,"2633":4,"2634":13,"2635":10,"2638":1,"2641":1,"2642":2,"2649":4,"2650":1,"2654":1,"2660":1,"2662":1,"2664":3,"2665":2,"2666":4,"2672":2,"2677":1,"2678":1,"2681":2,"2682":2,"2685":1,"2687":1,"2688":2,"2689":3,"2692":2,"2694":3,"2695":2,"2700":1,"2702":1,"2706":1,"2709":1,"2712":2,"2717":1,"2721":3,"2723":3,"2724":2,"2725":1,"2726":1,"2729":1,"2731":1,"2733":2,"2749":1,"2750":1,"2751":2,"2754":1,"2755":2,"2757":1,"2759":1,"2761":1,"2763":2,"2764":2,"2765":1,"2767":2,"2768":1,"2769":2,"2771":1,"2772":2,"2773":2,"2774":1,"2776":3,"2781":2,"2782":1,"2783":1,"2784":1,"2785":1,"2791":1,"2792":1,"2793":1,"2794":1,"2795":2,"2797":3,"2798":1,"2802":4,"2804":1,"2806":4,"2807":3,"2809":3,"2810":1,"2811":2,"2812":2,"2813":8,"2814":2,"2815":4,"2816":1,"2818":1,"2820":1,"2821":2,"2823":2,"2824":7,"2825":4,"2826":3,"2827":2,"2828":2,"2829":2,"2831":1,"2833":3,"2834":3,"2835":1,"2836":2,"2840":2,"2841":1,"2842":1,"2845":3,"2846":2,"2847":1,"2849":1,"2852":1,"2853":2,"2854":1,"2855":2,"2857":1,"2858":1,"2861":2,"2866":2,"2868":4,"2869":4,"2873":1,"2879":1}}],["wb",{"2":{"1366":1}}],["wget",{"2":{"1117":1,"1118":1,"2782":1,"2783":1}}],["w4itb24mvxg8r9rc906c0",{"2":{"1051":2}}],["w0rd",{"2":{"930":2}}],["wrn",{"2":{"1609":1,"2659":1}}],["wright",{"2":{"913":1}}],["writeasync",{"2":{"2615":1}}],["writeresponseasync",{"2":{"2615":1}}],["writer",{"2":{"844":1,"852":3,"864":5,"865":1,"949":1}}],["writers",{"2":{"844":2,"852":2}}],["writes",{"0":{"1381":1,"2400":1},"1":{"1382":1,"1383":1},"2":{"88":1,"531":1,"666":1,"843":1,"848":1,"851":2,"860":1,"951":1,"1037":1,"1043":1,"1174":1,"1382":3,"1406":1,"1408":1,"1792":2,"2184":1,"2400":1,"2435":1,"2615":2,"2867":2,"2881":1}}],["write",{"0":{"9":1,"854":1,"910":1,"956":1,"2176":1,"2214":1},"1":{"911":1,"2177":1,"2178":1},"2":{"33":1,"81":1,"83":1,"303":3,"370":1,"669":1,"706":1,"831":1,"835":1,"848":2,"854":1,"855":1,"860":3,"864":1,"869":1,"871":3,"873":1,"876":2,"879":2,"894":1,"901":1,"910":1,"920":2,"948":1,"968":3,"971":1,"994":1,"1006":1,"1036":1,"1037":1,"1038":2,"1040":1,"1065":2,"1073":4,"1080":2,"1081":3,"1086":1,"1094":1,"1095":1,"1096":2,"1098":1,"1102":1,"1121":1,"1126":1,"1127":1,"1128":1,"1174":2,"1176":1,"1179":1,"1366":2,"1367":1,"1382":2,"1384":1,"1385":2,"1386":2,"1401":8,"1403":5,"1405":2,"1410":1,"1417":1,"1422":1,"1439":2,"1524":1,"1582":1,"1628":2,"1792":5,"1833":1,"2092":1,"2102":1,"2107":1,"2170":1,"2171":2,"2221":1,"2266":2,"2389":1,"2400":1,"2466":1,"2525":1,"2533":1,"2537":1,"2739":1,"2772":1,"2803":1,"2827":1,"2836":1,"2860":1,"2879":1,"2881":1}}],["writing",{"0":{"951":1,"2729":1},"2":{"1":1,"81":2,"87":2,"782":1,"784":1,"856":1,"859":1,"860":1,"868":2,"879":1,"947":1,"966":1,"1096":1,"1101":1,"1102":1,"1150":1,"1281":1,"1384":1,"1400":2,"1401":1,"1402":2,"1403":1,"1523":1,"1792":3,"2380":1,"2394":1,"2626":1,"2857":1}}],["written",{"2":{"0":2,"1":1,"108":1,"188":1,"383":1,"636":1,"704":1,"706":1,"711":1,"714":1,"716":2,"832":1,"854":1,"860":2,"861":1,"866":1,"867":3,"872":4,"873":2,"910":1,"912":1,"916":1,"952":1,"976":1,"996":1,"1004":1,"1006":1,"1073":1,"1083":1,"1107":1,"1122":1,"1209":1,"1382":2,"1384":1,"1386":1,"1404":1,"1406":1,"1420":1,"1422":1,"1522":1,"1792":6,"1969":1,"1970":1,"2075":2,"2094":2,"2097":1,"2110":1,"2112":1,"2296":1,"2360":2,"2380":1,"2381":1,"2451":1,"2530":1,"2532":1,"2533":1,"2537":2,"2540":1,"2726":1,"2836":1,"2845":1,"2869":1,"2870":1,"2871":1}}],["wrote",{"2":{"859":1,"879":1,"1046":1,"1403":1,"2348":1,"2712":1}}],["wrongpassword",{"2":{"930":1}}],["wrong",{"2":{"298":1,"663":1,"848":1,"855":1,"865":1,"874":1,"930":2,"986":1,"1036":1,"1037":2,"1064":1,"1075":1,"1076":1,"1385":1,"1386":1,"1390":1,"1391":1,"1442":1,"1704":1,"2176":1,"2258":1,"2414":1,"2441":1,"2498":1,"2597":1,"2611":1,"2666":1}}],["wraps",{"2":{"1070":1,"1102":2,"1375":1,"1567":1,"2466":1}}],["wrapintransaction",{"0":{"1851":1,"2382":1},"2":{"1070":1,"1102":1,"1528":1,"1792":1,"1850":2,"1851":2,"1852":1,"2382":2,"2383":1,"2527":1,"2862":1}}],["wrapper",{"0":{"1410":1},"2":{"871":1,"872":2,"965":1,"1181":1,"1193":2,"1407":1,"1410":2,"1411":1,"1412":1,"1413":1,"1415":1,"1422":1,"1568":1,"2106":1,"2372":1,"2489":1,"2537":1,"2678":1,"2714":1}}],["wrappers",{"2":{"848":1,"1406":1,"1412":1,"2389":1,"2878":1}}],["wrapped",{"2":{"864":1,"1279":1,"1391":1,"1408":1,"1792":1,"1850":1,"2010":1,"2382":1,"2810":1}}],["wrapping",{"2":{"487":1,"615":1,"829":1,"1102":1,"1368":1,"1376":1,"1412":1,"2339":1,"2358":1}}],["wrap",{"0":{"1404":1},"2":{"716":1,"847":1,"1111":1,"1193":1,"1395":1,"2810":3,"2867":1,"2881":1}}],["w",{"2":{"269":1,"922":1,"2211":1}}],["wolf",{"2":{"2385":1}}],["woff2",{"2":{"1792":2,"1936":2,"1943":2}}],["woff",{"2":{"1792":2,"1936":2,"1943":2}}],["woolf",{"2":{"913":1}}],["wondering",{"2":{"877":1}}],["wonderful",{"2":{"852":1,"1403":1}}],["wonder",{"2":{"841":1,"1402":1}}],["won",{"2":{"354":1,"480":1,"903":1,"992":1,"1130":1,"1145":1,"1382":1,"1609":1,"2195":1,"2425":1,"2590":1,"2661":1,"2685":1}}],["worry",{"2":{"1390":1}}],["worst",{"2":{"947":1,"1382":2,"2459":1,"2464":1}}],["worse",{"2":{"838":1,"861":1,"865":2,"948":1}}],["worth",{"2":{"841":1,"864":1,"871":1,"874":1,"1074":1,"1386":1,"1389":1,"1396":1,"1397":1,"2867":1}}],["words",{"2":{"1335":1,"1339":2,"1383":1,"1404":3}}],["word",{"2":{"379":1,"851":3,"852":1,"865":1,"2333":1,"2533":1}}],["worldview",{"2":{"859":1}}],["worlds",{"2":{"840":1,"865":1,"1515":1}}],["world",{"0":{"1416":1,"1417":1},"1":{"1418":1,"1419":1,"1420":1,"1421":1},"2":{"338":1,"487":3,"851":1,"864":1,"865":1,"916":1,"956":1,"977":1,"980":1,"990":1,"1037":2,"1078":1,"1370":2,"1374":1,"1385":1,"1401":1,"1403":2,"1414":1,"1581":1,"2326":1,"2333":1,"2589":3,"2621":1,"2821":1,"2822":1,"2824":1,"2850":2,"2868":1}}],["workdir",{"2":{"1420":2}}],["workflow",{"0":{"997":1,"1417":1,"1422":1},"1":{"1418":1,"1419":1,"1420":1,"1421":1},"2":{"984":1,"1037":1,"1328":1,"1351":1,"1384":1,"2110":1,"2112":1,"2530":1,"2545":1,"2546":1}}],["workflows",{"2":{"973":1,"1005":1,"2534":1,"2858":1}}],["workarounds",{"2":{"918":1,"1066":1,"1097":1,"1378":2,"1396":1,"2855":1,"2858":1}}],["workaround",{"0":{"920":1},"2":{"918":1,"920":1,"1049":1,"1111":2,"1394":2,"1395":2}}],["workloads",{"2":{"1154":1,"1205":1,"1272":1,"1280":2,"1412":1,"1632":1,"2088":1,"2789":1}}],["workload",{"2":{"868":1,"869":1,"1084":1}}],["worker",{"0":{"1167":1,"2087":1},"2":{"1106":1,"1167":2,"1792":2,"2086":2,"2087":1}}],["workers",{"2":{"860":1,"1106":1,"1130":2}}],["worked",{"0":{"2187":1},"2":{"306":1,"844":1,"919":1,"1368":1,"1385":1,"2170":1,"2580":1,"2824":1}}],["workbook",{"2":{"776":1,"892":1,"948":2,"949":1,"968":2,"969":1,"971":1,"1183":1,"1203":1,"1207":1,"1208":1,"2130":1}}],["workingdirectory",{"2":{"1792":3,"2111":3,"2532":2,"2871":2,"2874":1}}],["working",{"0":{"2365":1},"2":{"221":1,"834":2,"857":1,"876":2,"879":1,"919":2,"920":1,"921":1,"947":1,"976":1,"977":1,"980":1,"990":1,"1045":1,"1048":2,"1054":1,"1068":1,"1081":1,"1183":2,"1207":1,"1382":1,"1386":1,"1390":1,"1399":1,"1404":1,"1405":1,"1653":1,"1792":1,"1802":1,"1894":1,"2397":1,"2540":1,"2545":1,"2645":1,"2684":1,"2795":1,"2818":1,"2825":1,"2860":1}}],["workshops",{"2":{"1443":1}}],["worksheets",{"2":{"788":1}}],["worksheet",{"0":{"964":1},"2":{"675":1,"788":1,"957":1,"964":1,"1792":1,"2077":1,"2079":1,"2653":1}}],["works",{"0":{"297":1,"387":1,"881":1,"1305":1,"1359":1,"1432":1,"1824":1,"1868":1,"2283":1,"2302":1,"2318":1,"2527":1,"2760":1,"2807":1,"2828":1,"2840":1,"2862":1},"1":{"1360":1},"2":{"11":1,"25":1,"26":1,"42":1,"53":1,"65":1,"76":1,"89":1,"98":1,"119":1,"120":1,"122":1,"130":1,"141":1,"151":1,"164":1,"186":1,"198":1,"217":1,"258":1,"259":1,"263":1,"265":1,"293":1,"305":1,"320":1,"334":1,"337":1,"345":1,"364":1,"367":1,"369":1,"374":1,"408":1,"410":1,"436":1,"469":1,"471":1,"481":1,"495":1,"505":1,"513":1,"525":1,"529":1,"547":1,"557":1,"578":1,"596":1,"605":1,"615":1,"625":1,"634":1,"647":1,"670":1,"680":1,"725":1,"740":1,"790":1,"803":1,"804":1,"815":1,"820":1,"827":1,"828":1,"829":1,"840":1,"841":1,"860":1,"869":1,"871":1,"873":1,"877":1,"884":1,"911":1,"918":1,"920":1,"934":1,"946":1,"954":1,"971":1,"994":1,"1009":1,"1015":1,"1036":1,"1048":1,"1061":1,"1065":1,"1068":1,"1070":1,"1081":1,"1083":1,"1086":1,"1103":1,"1117":1,"1126":1,"1135":1,"1149":1,"1176":1,"1205":1,"1208":1,"1304":1,"1322":1,"1323":2,"1327":1,"1351":1,"1367":2,"1378":1,"1385":1,"1386":1,"1394":1,"1395":2,"1396":2,"1398":2,"1400":1,"1416":1,"1421":1,"1465":1,"1472":1,"1484":1,"1495":1,"1506":1,"1515":1,"1517":1,"1518":1,"1535":1,"1549":1,"1583":1,"1600":1,"1634":1,"1647":1,"1665":1,"1679":1,"1699":1,"1747":1,"1748":1,"1760":1,"1785":1,"1792":3,"1811":1,"1864":1,"1913":1,"1930":1,"1932":1,"1945":1,"1962":1,"1974":1,"1976":1,"1996":1,"2043":1,"2081":1,"2090":1,"2095":1,"2133":1,"2150":1,"2157":1,"2170":1,"2183":1,"2193":2,"2200":1,"2265":2,"2274":1,"2277":1,"2284":1,"2293":1,"2332":2,"2335":2,"2338":1,"2339":1,"2344":1,"2389":1,"2391":1,"2406":1,"2419":2,"2425":1,"2430":1,"2438":1,"2481":1,"2521":1,"2533":1,"2535":1,"2543":2,"2545":1,"2581":1,"2586":2,"2588":1,"2591":1,"2607":2,"2627":1,"2665":1,"2681":1,"2687":1,"2695":1,"2727":1,"2779":1,"2795":2,"2827":1,"2835":1,"2871":1,"2874":1}}],["work",{"0":{"1723":1,"2179":1,"2191":1,"2856":1},"1":{"2180":1,"2181":1,"2192":1,"2193":1,"2194":1},"2":{"11":1,"26":1,"42":1,"53":1,"65":1,"76":1,"89":1,"98":1,"122":1,"130":1,"141":1,"151":1,"155":1,"164":1,"189":1,"198":1,"212":1,"217":1,"220":2,"259":1,"261":1,"281":1,"293":1,"307":1,"308":1,"319":1,"327":1,"338":1,"345":1,"356":1,"367":1,"376":1,"385":1,"388":1,"396":1,"410":1,"448":1,"453":1,"471":1,"481":1,"495":1,"505":1,"513":1,"515":1,"525":1,"547":1,"557":1,"568":1,"578":1,"588":1,"596":1,"605":1,"617":1,"626":1,"634":1,"647":1,"658":2,"659":2,"665":1,"670":1,"680":1,"683":1,"725":1,"740":1,"790":1,"801":1,"804":1,"818":1,"820":1,"830":1,"832":1,"840":1,"841":2,"844":1,"849":2,"852":1,"861":2,"864":1,"868":1,"871":1,"872":2,"874":2,"876":1,"919":2,"930":1,"932":1,"952":1,"967":1,"973":1,"992":1,"994":1,"1009":1,"1012":1,"1042":1,"1049":1,"1074":1,"1075":2,"1125":1,"1167":1,"1170":1,"1179":1,"1180":1,"1182":1,"1193":1,"1200":1,"1211":1,"1309":1,"1368":1,"1371":1,"1372":1,"1378":1,"1382":1,"1394":1,"1400":1,"1402":2,"1405":1,"1449":1,"1460":1,"1465":1,"1484":1,"1495":1,"1506":1,"1535":1,"1549":1,"1583":1,"1600":1,"1610":2,"1634":1,"1647":1,"1655":1,"1665":1,"1679":1,"1686":1,"1699":1,"1748":1,"1760":1,"1792":1,"1811":1,"1825":1,"1834":1,"1864":1,"1909":1,"1913":1,"1932":1,"1945":1,"1962":1,"1976":1,"1996":1,"2043":1,"2081":1,"2087":1,"2088":1,"2090":1,"2120":2,"2133":1,"2150":1,"2170":1,"2177":1,"2190":3,"2193":3,"2200":1,"2274":1,"2319":1,"2323":2,"2332":1,"2375":2,"2380":1,"2398":3,"2407":2,"2430":1,"2461":1,"2466":1,"2496":1,"2529":2,"2531":1,"2532":1,"2533":1,"2534":1,"2545":1,"2581":2,"2759":1,"2769":1,"2806":1,"2814":1,"2846":1,"2855":1,"2856":1,"2865":1}}],["wouldn",{"2":{"1412":1}}],["would",{"0":{"1025":1},"1":{"1026":1},"2":{"75":1,"167":2,"177":1,"209":1,"320":1,"349":1,"354":1,"376":1,"587":1,"829":1,"841":2,"849":1,"851":4,"852":1,"857":1,"859":2,"860":1,"861":1,"865":1,"867":1,"868":1,"869":1,"872":3,"873":1,"915":2,"918":7,"919":2,"956":1,"985":2,"1065":1,"1254":1,"1335":1,"1382":1,"1386":1,"1400":2,"1401":4,"1402":2,"1403":3,"1404":1,"1412":1,"1413":1,"1428":1,"1431":1,"1449":1,"1527":1,"1569":1,"1609":1,"1759":1,"1774":1,"1792":2,"1851":1,"1912":1,"1923":1,"1925":1,"1958":1,"1968":1,"2107":1,"2313":1,"2338":1,"2362":1,"2367":1,"2375":1,"2376":2,"2378":1,"2382":1,"2394":1,"2414":2,"2420":1,"2438":2,"2468":1,"2494":1,"2509":1,"2520":1,"2532":1,"2533":1,"2537":1,"2539":1,"2540":1,"2551":1,"2577":1,"2597":1,"2600":1,"2608":1,"2659":1,"2666":1,"2722":1,"2823":1,"2868":2,"2873":1,"2876":1}}],["waving",{"2":{"1435":1}}],["waves",{"2":{"1162":1}}],["waanted",{"2":{"1254":1}}],["wa",{"2":{"1220":1,"1221":1,"1222":1}}],["wakes",{"2":{"859":1}}],["wall",{"2":{"2451":3}}],["walled",{"2":{"848":1}}],["walked",{"2":{"1069":1,"1956":1,"2379":1,"2442":1}}],["walks",{"2":{"860":2,"878":1,"972":1,"1038":1,"1048":1,"1135":1,"1409":1,"1566":1,"2422":1,"2818":1}}],["walking",{"2":{"856":1}}],["walk",{"0":{"856":1},"2":{"857":1,"860":1,"1381":1,"2424":1}}],["walkthroughs",{"2":{"1066":1}}],["walkthrough",{"0":{"1212":1,"1406":1},"1":{"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1},"2":{"217":1,"296":1,"315":1,"327":1,"431":1,"436":1,"456":1,"634":1,"647":1,"670":1,"1037":1,"1073":1,"1074":1,"1082":1,"1465":1,"1484":1,"1506":1,"1549":1,"1699":1,"1748":1,"1799":1,"1811":1,"1834":1,"1894":1,"1932":1,"2013":1,"2092":1,"2114":1,"2159":1,"2170":1,"2479":1,"2737":1,"2759":1,"2794":1,"2806":1,"2826":1,"2827":1,"2837":1}}],["warmup",{"2":{"1254":1,"1259":2,"2744":1}}],["warm",{"2":{"1076":1,"1141":1,"2465":1}}],["warehouses",{"2":{"1206":1}}],["warehouse",{"2":{"837":1,"1203":1,"1206":1,"1208":1}}],["warnunboundserversenteventsnotices=false",{"2":{"2392":1}}],["warnunboundserversenteventsnotices",{"2":{"1792":1,"1861":1,"2392":1,"2406":1,"2835":1}}],["warns",{"2":{"1743":1,"2392":1,"2502":1}}],["warn",{"2":{"1388":1,"2380":1}}],["warned",{"2":{"388":1}}],["warningsarefatal",{"2":{"2415":2}}],["warnings",{"0":{"2428":1},"2":{"952":1,"1199":1,"1609":2,"1792":2,"1801":1,"1861":1,"2392":2,"2415":5,"2416":1,"2537":1,"2659":1,"2661":1}}],["warning",{"0":{"633":1,"661":1,"1861":1,"2392":1,"2395":1,"2493":1,"2754":1},"2":{"64":2,"109":3,"214":2,"319":1,"382":2,"383":1,"384":1,"388":2,"390":1,"395":1,"629":2,"631":1,"632":1,"633":2,"651":1,"653":2,"656":3,"661":2,"675":1,"859":1,"1056":1,"1060":1,"1071":1,"1103":1,"1192":2,"1193":1,"1199":2,"1305":1,"1325":1,"1386":1,"1449":1,"1493":1,"1499":1,"1500":3,"1527":6,"1603":1,"1604":1,"1609":2,"1620":1,"1743":1,"1774":1,"1792":19,"1800":2,"1801":1,"1802":4,"1806":1,"1810":3,"1822":2,"1825":1,"1857":1,"1858":1,"1861":1,"1875":1,"1917":1,"1925":1,"1957":2,"1961":1,"1983":1,"2007":1,"2018":1,"2052":1,"2110":1,"2222":1,"2223":1,"2226":2,"2252":6,"2328":1,"2334":2,"2348":1,"2353":1,"2363":2,"2379":2,"2380":2,"2384":1,"2392":2,"2395":2,"2406":1,"2407":1,"2414":4,"2415":2,"2416":2,"2417":1,"2428":2,"2481":3,"2492":2,"2493":1,"2502":1,"2517":1,"2529":1,"2530":1,"2533":2,"2536":2,"2628":2,"2633":1,"2659":2,"2794":1,"2795":4,"2802":1,"2803":2,"2804":4,"2812":1,"2828":1,"2832":3,"2835":2,"2865":1}}],["watchable",{"2":{"2156":1,"2542":1}}],["watching",{"0":{"2542":1},"2":{"1792":1,"2153":1,"2157":1,"2544":1,"2752":1,"2857":1}}],["watcher",{"2":{"2157":1,"2543":1,"2546":1}}],["watcher=1",{"2":{"2157":1,"2543":1}}],["watched",{"2":{"1080":2,"1407":1,"2537":1,"2857":1}}],["watches",{"2":{"1080":1,"2153":1,"2157":1,"2541":1,"2543":1,"2742":1,"2878":1}}],["watch",{"0":{"1080":1,"2106":1,"2153":1,"2157":1,"2158":1,"2541":2,"2543":1,"2742":1,"2800":1,"2857":1,"2878":1},"1":{"2154":1,"2155":1,"2156":1,"2157":1,"2158":1,"2159":1,"2542":2,"2543":2},"2":{"706":1,"863":1,"876":1,"986":1,"1037":1,"1072":1,"1073":2,"1080":3,"1081":1,"1082":1,"1094":3,"1379":2,"1401":1,"1407":2,"1418":4,"1419":2,"1789":2,"1792":6,"2092":1,"2106":12,"2113":1,"2153":6,"2154":4,"2155":13,"2156":4,"2157":1,"2159":1,"2167":1,"2168":2,"2221":4,"2525":2,"2537":10,"2541":10,"2542":9,"2543":4,"2546":3,"2742":3,"2857":3,"2878":8,"2880":1}}],["wanted",{"2":{"912":1,"919":1,"1073":1,"1254":4,"1384":2,"1394":1,"1399":1,"1402":2,"1403":2}}],["wants",{"2":{"650":1,"1428":1,"2393":1,"2729":1}}],["want",{"2":{"88":1,"286":1,"304":1,"307":1,"319":1,"334":1,"390":1,"449":1,"583":1,"587":1,"624":1,"650":1,"837":4,"843":1,"847":1,"848":1,"851":2,"852":2,"866":1,"869":1,"904":2,"916":3,"918":1,"971":1,"1065":2,"1068":3,"1069":1,"1094":1,"1096":1,"1121":5,"1122":2,"1123":2,"1127":1,"1145":1,"1150":1,"1160":1,"1161":1,"1162":1,"1181":1,"1206":1,"1220":1,"1221":1,"1305":1,"1338":1,"1354":2,"1366":1,"1382":2,"1385":1,"1386":1,"1390":1,"1395":1,"1398":1,"1399":1,"1403":2,"1404":1,"1405":1,"1431":1,"1435":1,"1437":1,"1443":1,"1515":1,"1572":1,"1576":1,"1580":1,"1625":1,"1771":1,"1792":5,"1870":1,"1871":1,"2004":1,"2180":1,"2406":1,"2430":1,"2533":1,"2635":1,"2729":1,"2771":1,"2834":1}}],["waiter",{"2":{"2466":2}}],["waiters",{"2":{"2463":1,"2466":3}}],["waits",{"2":{"1014":1,"1165":1,"1170":1,"2157":1,"2407":1}}],["waiting",{"2":{"852":1,"922":1,"1014":1,"1078":1,"1148":1,"1324":2,"2393":1,"2532":1,"2543":1}}],["wait",{"2":{"84":1,"575":1,"871":1,"917":1,"966":1,"1147":1,"1158":1,"1166":1,"1254":1,"1259":1,"1329":1,"1623":1,"1792":2,"1958":1,"1959":1,"2052":1,"2247":2,"2470":1,"2471":1,"2534":1,"2635":2}}],["waste",{"2":{"1171":1}}],["wastes",{"2":{"1170":1}}],["was",{"0":{"2452":1,"2491":1},"2":{"74":1,"188":1,"347":1,"453":1,"469":1,"748":1,"841":1,"848":5,"851":2,"852":1,"855":1,"857":1,"860":3,"861":1,"864":2,"865":1,"866":1,"872":2,"919":1,"1037":1,"1069":1,"1073":2,"1075":2,"1076":1,"1078":3,"1079":1,"1082":2,"1239":1,"1254":3,"1326":1,"1332":1,"1384":1,"1385":9,"1400":4,"1401":6,"1402":9,"1403":6,"1404":4,"1442":2,"1453":1,"1504":1,"1518":2,"1569":1,"1609":1,"1674":1,"1701":1,"1759":1,"1762":1,"1792":1,"1866":1,"1912":1,"1925":2,"1948":1,"2014":1,"2056":1,"2182":1,"2223":1,"2242":1,"2258":1,"2265":2,"2267":2,"2287":1,"2296":1,"2313":1,"2314":1,"2351":1,"2352":2,"2353":1,"2360":2,"2363":1,"2372":2,"2378":1,"2392":1,"2395":2,"2402":1,"2410":1,"2414":1,"2416":1,"2420":2,"2421":2,"2437":1,"2441":1,"2442":2,"2444":1,"2445":1,"2446":1,"2448":1,"2450":1,"2451":1,"2452":1,"2453":2,"2486":2,"2490":4,"2493":1,"2494":1,"2495":2,"2505":2,"2506":1,"2510":1,"2517":1,"2518":1,"2519":5,"2544":1,"2551":1,"2597":1,"2622":1,"2626":2,"2648":3,"2721":1,"2722":1,"2758":1,"2799":2,"2801":1,"2868":1,"2881":2}}],["ways",{"2":{"306":1,"307":1,"534":1,"560":1,"619":1,"1043":1,"1073":1,"1490":1,"1985":1,"2182":1,"2340":1,"2768":1}}],["way",{"0":{"968":2,"2741":1},"1":{"969":2},"2":{"1":1,"260":1,"310":1,"390":1,"414":1,"458":1,"840":1,"841":2,"843":1,"851":2,"852":1,"857":1,"863":1,"872":1,"874":1,"904":1,"910":1,"915":1,"916":1,"946":1,"974":1,"983":1,"1020":1,"1056":1,"1073":1,"1075":2,"1076":1,"1077":2,"1081":1,"1082":1,"1086":1,"1127":1,"1128":1,"1176":1,"1254":1,"1303":1,"1368":1,"1385":3,"1386":1,"1390":1,"1393":1,"1394":1,"1398":5,"1399":1,"1400":1,"1403":5,"1433":1,"1441":1,"1832":1,"2160":1,"2195":1,"2325":1,"2423":1,"2430":1,"2481":1,"2510":1,"2531":1,"2544":1,"2545":1,"2590":1,"2727":1,"2772":1,"2774":1,"2827":1}}],["wwwroot",{"2":{"1792":1,"2033":1,"2034":1,"2042":1}}],["www",{"2":{"45":1,"51":1,"63":1,"209":1,"845":1,"1691":1,"1692":2,"1695":1,"1709":1,"1711":1,"1714":1,"1792":13,"1825":1,"1827":1,"2255":4,"2481":1,"2632":1,"2633":1}}],["why",{"0":{"691":1,"939":1,"948":1,"974":1,"985":1,"993":1,"1006":1,"1012":1,"1046":1,"1049":1,"1273":1,"1324":1,"1428":1,"1704":1,"2452":1,"2510":1,"2521":1,"2723":1,"2724":1,"2725":1,"2797":1,"2799":1},"1":{"940":1,"941":1,"942":1,"943":1,"944":1,"1007":1,"1008":1,"1009":1,"1013":1,"1014":1,"1015":1,"1274":1,"1275":1,"1276":1,"1325":1},"2":{"636":1,"841":4,"848":2,"851":1,"861":1,"864":1,"866":1,"948":3,"1037":2,"1049":1,"1075":1,"1324":1,"1325":1,"1385":1,"1386":1,"1396":1,"1397":1,"1400":1,"1402":1,"1405":2,"2389":1,"2540":1,"2545":1,"2799":1,"2838":1}}],["whatnot",{"2":{"1400":1}}],["whatsoever",{"2":{"851":1}}],["what",{"0":{"666":1,"841":1,"867":1,"868":1,"877":1,"910":1,"950":1,"1023":1,"1025":1,"1039":1,"1210":2,"1243":1,"1244":1,"1255":1,"1256":1,"1266":1,"1379":1,"1421":1,"1435":1,"1566":1,"2390":1,"2412":1,"2415":1,"2422":1,"2438":1,"2443":1,"2460":1,"2469":1,"2475":1,"2509":1,"2516":1,"2709":1,"2710":1,"2711":1,"2736":1,"2749":1},"1":{"911":1,"951":1,"952":1,"953":1,"954":1,"1026":1,"1257":1,"1258":1,"1259":1,"1260":1,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":1,"1442":1,"1443":1,"1567":1,"1568":1,"1569":1,"1570":1,"1571":1,"1572":1,"1573":1,"1574":1,"1575":1,"1576":1,"1577":1,"2391":1,"2392":1,"2393":1,"2394":1,"2395":1,"2461":1,"2462":1,"2463":1,"2470":1,"2471":1,"2476":1,"2477":1,"2510":1,"2511":1,"2512":1,"2517":1,"2518":1,"2519":1,"2520":1,"2521":1},"2":{"386":1,"387":1,"390":1,"436":1,"439":2,"448":1,"458":1,"666":2,"668":1,"669":1,"692":1,"832":1,"833":1,"834":1,"841":3,"843":1,"844":5,"845":1,"848":1,"849":4,"852":9,"853":1,"855":1,"857":4,"860":4,"861":1,"864":4,"865":1,"866":1,"867":1,"868":2,"877":1,"879":1,"918":1,"926":1,"932":1,"937":1,"940":1,"943":2,"957":1,"961":1,"966":1,"968":1,"987":1,"997":1,"1002":1,"1037":3,"1039":1,"1043":1,"1044":2,"1064":1,"1069":1,"1077":1,"1079":2,"1081":1,"1082":2,"1084":1,"1096":2,"1098":1,"1107":1,"1108":1,"1111":1,"1129":1,"1150":1,"1162":1,"1165":1,"1180":1,"1181":3,"1185":2,"1206":1,"1208":1,"1210":1,"1231":1,"1244":1,"1249":2,"1254":1,"1302":1,"1304":1,"1335":1,"1366":1,"1382":4,"1384":1,"1385":2,"1386":1,"1396":2,"1399":1,"1401":1,"1402":2,"1403":3,"1404":4,"1405":3,"1406":2,"1419":1,"1435":4,"1436":1,"1437":1,"1438":1,"1439":1,"1440":1,"1441":3,"1442":1,"1458":1,"1566":1,"1567":1,"1579":1,"1792":1,"1832":1,"1961":1,"2112":1,"2171":1,"2179":1,"2377":1,"2388":1,"2389":2,"2391":1,"2395":1,"2398":1,"2415":1,"2434":1,"2531":1,"2532":1,"2539":1,"2540":1,"2577":2,"2677":1,"2721":1,"2755":1,"2765":1,"2795":1,"2799":2,"2807":1,"2824":1,"2829":1,"2833":1,"2855":1,"2867":1,"2868":1,"2869":1}}],["whatever",{"2":{"304":1,"436":1,"650":1,"843":1,"864":1,"880":1,"882":1,"885":1,"911":1,"956":1,"1044":1,"1220":1,"1386":1,"1402":1,"1405":1,"1435":1,"1437":1,"1825":1,"1922":1,"2769":1}}],["whitelist",{"2":{"2442":1,"2446":2}}],["white",{"2":{"768":1,"786":2,"891":1,"913":1,"919":2}}],["whitespace",{"2":{"704":1,"709":1,"714":1,"768":1,"786":1,"891":1,"1527":1,"1792":2,"2097":1,"2128":1,"2380":1,"2533":1,"2537":1,"2589":1,"2870":1}}],["while",{"2":{"174":1,"414":1,"453":1,"587":1,"772":1,"841":1,"843":1,"844":3,"860":1,"864":1,"903":1,"907":1,"916":1,"926":1,"994":1,"1014":1,"1054":1,"1088":1,"1091":1,"1098":1,"1100":1,"1127":1,"1147":1,"1160":1,"1211":1,"1270":1,"1280":1,"1324":1,"1351":1,"1385":1,"1403":2,"1404":1,"1407":1,"1410":1,"1459":1,"1792":2,"1868":1,"1925":1,"2153":1,"2207":1,"2270":1,"2302":1,"2342":1,"2362":1,"2372":1,"2375":1,"2419":1,"2482":1,"2493":1,"2504":1,"2521":1,"2543":1,"2544":1,"2615":1,"2621":1,"2629":1,"2752":1,"2857":3,"2876":1}}],["whichever",{"2":{"1081":1,"1135":1,"2193":1}}],["which",{"0":{"1378":1,"2731":1,"2749":1,"2797":1,"2832":1},"2":{"51":2,"68":1,"74":1,"106":1,"215":1,"298":2,"300":1,"307":1,"324":2,"372":1,"390":1,"419":1,"423":1,"436":2,"470":1,"528":1,"556":1,"636":1,"650":2,"669":1,"693":1,"695":1,"696":1,"708":1,"831":1,"836":1,"837":1,"841":1,"844":2,"845":1,"848":3,"849":2,"852":3,"856":1,"860":2,"864":1,"865":2,"873":1,"874":2,"876":1,"886":1,"904":1,"918":2,"919":1,"920":2,"934":1,"943":2,"978":1,"980":1,"982":1,"1013":1,"1052":1,"1053":1,"1054":1,"1055":1,"1067":2,"1069":1,"1073":1,"1075":1,"1077":1,"1079":1,"1080":1,"1097":1,"1098":1,"1103":1,"1111":2,"1133":1,"1134":1,"1139":1,"1142":1,"1165":1,"1174":2,"1176":3,"1216":1,"1244":1,"1254":1,"1355":1,"1358":2,"1385":2,"1386":3,"1390":1,"1392":1,"1399":1,"1400":1,"1402":2,"1403":3,"1410":1,"1412":1,"1421":1,"1437":1,"1440":2,"1458":1,"1531":1,"1628":1,"1632":1,"1640":1,"1642":1,"1643":1,"1664":1,"1706":1,"1709":1,"1722":1,"1738":1,"1792":20,"1823":1,"1838":1,"1840":1,"1851":2,"1858":1,"1908":1,"1918":1,"1957":1,"1969":1,"2000":1,"2005":1,"2016":1,"2021":1,"2040":1,"2052":1,"2112":1,"2175":1,"2176":2,"2178":1,"2184":1,"2195":1,"2208":1,"2252":1,"2255":1,"2264":1,"2302":1,"2305":1,"2313":2,"2323":1,"2351":1,"2353":1,"2360":1,"2363":1,"2365":2,"2375":1,"2376":1,"2379":1,"2382":2,"2384":1,"2394":1,"2397":1,"2421":2,"2424":1,"2426":1,"2438":1,"2442":1,"2450":1,"2451":1,"2452":1,"2459":1,"2463":2,"2465":1,"2468":1,"2477":1,"2482":1,"2492":1,"2495":2,"2496":1,"2509":1,"2518":1,"2519":1,"2532":2,"2533":1,"2535":1,"2540":1,"2545":2,"2626":1,"2632":1,"2635":1,"2645":1,"2648":1,"2656":1,"2666":1,"2742":1,"2749":1,"2795":2,"2801":1,"2802":1,"2827":1,"2828":1,"2831":1,"2840":1,"2854":1,"2868":2}}],["whether",{"2":{"22":1,"335":1,"337":1,"669":1,"748":1,"864":1,"872":1,"877":1,"904":1,"953":1,"1055":1,"1056":1,"1150":1,"1228":1,"1230":1,"1237":1,"1567":1,"1767":1,"1768":1,"1792":6,"1878":1,"1880":1,"1887":1,"1927":1,"2011":1,"2016":1,"2018":1,"2354":1,"2541":1,"2549":1,"2597":1,"2632":1,"2806":1,"2807":1}}],["wherever",{"2":{"527":1,"704":1,"1792":1,"2111":1,"2532":1,"2537":1,"2871":1}}],["whereas",{"2":{"307":1,"414":1,"1127":1}}],["where",{"0":{"387":1,"834":1,"835":1,"1082":1,"1383":1,"2811":1},"2":{"16":2,"18":1,"19":1,"20":1,"35":1,"37":1,"75":1,"88":1,"115":2,"116":1,"136":2,"156":1,"158":1,"165":1,"167":1,"168":1,"175":1,"186":1,"215":1,"250":1,"288":2,"292":1,"298":4,"302":1,"304":1,"307":1,"309":1,"310":3,"312":2,"313":3,"366":1,"372":1,"373":1,"374":1,"376":1,"377":1,"395":1,"401":1,"414":1,"415":2,"426":2,"427":1,"428":1,"452":1,"520":2,"527":3,"531":1,"532":2,"534":1,"535":1,"542":1,"565":3,"566":2,"592":1,"611":1,"612":1,"613":1,"614":3,"621":1,"622":4,"623":2,"686":1,"722":2,"748":1,"784":1,"811":2,"812":1,"815":1,"831":1,"835":1,"836":2,"838":2,"841":1,"843":1,"844":1,"845":1,"847":2,"848":2,"849":2,"851":2,"852":1,"854":1,"855":2,"857":1,"859":2,"861":1,"864":4,"873":3,"874":2,"903":2,"914":2,"916":3,"918":3,"926":1,"934":1,"975":1,"978":1,"979":1,"980":2,"982":1,"986":1,"988":1,"989":1,"990":5,"994":1,"996":1,"1009":1,"1015":1,"1033":1,"1036":1,"1038":1,"1042":1,"1045":1,"1055":1,"1056":1,"1058":1,"1060":3,"1065":1,"1068":1,"1069":1,"1070":1,"1073":2,"1075":2,"1076":2,"1079":1,"1080":1,"1091":1,"1105":3,"1108":2,"1113":1,"1127":1,"1129":1,"1135":2,"1138":2,"1141":2,"1142":2,"1145":1,"1146":1,"1150":1,"1152":1,"1154":1,"1160":1,"1161":1,"1162":2,"1179":3,"1193":1,"1197":1,"1203":1,"1216":2,"1232":2,"1234":2,"1235":1,"1236":1,"1239":2,"1280":1,"1308":1,"1328":1,"1332":1,"1338":2,"1339":2,"1343":1,"1347":1,"1357":1,"1358":1,"1363":1,"1368":2,"1371":1,"1375":1,"1379":1,"1384":1,"1386":5,"1387":2,"1390":1,"1391":2,"1393":1,"1394":1,"1395":1,"1396":2,"1398":3,"1402":1,"1405":1,"1408":1,"1410":1,"1412":1,"1415":1,"1431":1,"1440":1,"1442":1,"1458":5,"1504":2,"1516":1,"1574":1,"1664":1,"1689":1,"1738":1,"1792":15,"1924":1,"1961":2,"2010":3,"2012":1,"2105":1,"2110":1,"2157":1,"2171":1,"2176":2,"2178":1,"2184":1,"2187":1,"2221":1,"2245":1,"2265":1,"2270":1,"2277":1,"2283":2,"2285":2,"2293":1,"2300":1,"2303":1,"2319":1,"2320":3,"2321":1,"2322":1,"2333":1,"2338":1,"2339":4,"2342":2,"2350":1,"2375":4,"2380":1,"2389":1,"2394":1,"2395":1,"2398":3,"2413":1,"2430":1,"2438":1,"2452":1,"2489":1,"2495":1,"2520":1,"2530":1,"2531":1,"2540":4,"2543":1,"2550":1,"2551":1,"2572":1,"2611":1,"2626":1,"2689":1,"2731":1,"2733":1,"2758":1,"2768":1,"2774":4,"2775":1,"2791":1,"2794":1,"2802":1,"2806":1,"2813":2,"2815":1,"2845":3,"2846":1,"2869":1}}],["whenall",{"2":{"1105":1,"1376":1,"1398":2,"1745":1,"2346":1,"2347":1}}],["whenever",{"2":{"319":1,"841":2,"848":1,"879":1,"910":1,"1823":1,"2422":1,"2490":1}}],["when",{"0":{"583":1,"653":1,"654":1,"686":1,"739":1,"837":1,"1035":1,"1120":1,"1121":1,"1122":1,"1123":1,"1170":1,"1205":1,"1323":1,"1351":1,"1354":1,"1360":1,"1378":1,"1410":1,"1432":2,"1523":1,"2088":1,"2392":1,"2394":1,"2395":1,"2424":1,"2867":1},"1":{"1121":1,"1122":1,"1123":1,"1524":1,"1525":1,"1526":1},"2":{"1":1,"25":1,"38":1,"40":1,"51":2,"60":1,"63":2,"64":1,"73":1,"87":1,"101":3,"105":1,"106":2,"107":1,"108":2,"109":1,"110":1,"119":1,"140":1,"168":2,"169":1,"175":1,"179":1,"180":1,"188":1,"202":1,"213":1,"214":1,"245":2,"284":1,"286":1,"297":1,"298":1,"301":1,"302":1,"303":1,"307":1,"308":2,"309":2,"317":2,"319":4,"320":1,"330":1,"334":2,"347":1,"349":2,"354":1,"373":2,"374":1,"376":1,"377":1,"382":1,"383":1,"388":4,"389":1,"390":2,"395":1,"408":1,"409":1,"422":1,"435":1,"436":2,"446":1,"447":1,"463":1,"464":1,"469":1,"480":2,"507":1,"512":1,"518":1,"524":1,"531":1,"534":1,"549":1,"575":1,"583":3,"587":2,"609":1,"613":1,"616":1,"624":1,"639":2,"646":2,"653":1,"654":1,"656":1,"666":1,"675":2,"679":1,"683":2,"685":1,"696":2,"713":1,"720":2,"734":1,"737":1,"747":4,"762":1,"771":1,"772":1,"773":1,"775":1,"781":2,"782":2,"784":4,"786":3,"788":2,"801":1,"819":1,"826":1,"837":3,"841":1,"843":1,"844":1,"848":2,"849":5,"852":1,"859":1,"860":3,"865":1,"866":1,"868":1,"869":1,"872":2,"877":1,"880":2,"888":1,"902":1,"903":1,"904":1,"911":3,"914":2,"915":2,"917":2,"918":2,"919":4,"926":1,"932":1,"933":2,"946":1,"974":3,"982":1,"983":3,"988":1,"991":2,"992":1,"994":1,"1009":1,"1016":1,"1023":1,"1037":1,"1040":1,"1042":1,"1049":1,"1050":1,"1060":1,"1067":4,"1068":1,"1070":1,"1079":1,"1096":1,"1101":2,"1105":1,"1127":3,"1128":1,"1129":1,"1130":2,"1132":1,"1133":1,"1137":1,"1139":4,"1140":1,"1147":2,"1150":5,"1152":1,"1158":1,"1164":1,"1165":1,"1167":1,"1170":1,"1178":1,"1190":1,"1193":2,"1200":1,"1203":2,"1205":1,"1220":1,"1231":1,"1232":2,"1233":1,"1234":2,"1235":1,"1236":2,"1237":3,"1238":1,"1239":1,"1279":1,"1318":2,"1323":1,"1324":1,"1326":1,"1328":2,"1329":1,"1331":1,"1332":1,"1337":1,"1341":1,"1353":1,"1354":3,"1355":2,"1359":1,"1360":3,"1377":1,"1378":2,"1382":1,"1385":1,"1390":1,"1396":1,"1399":1,"1402":2,"1403":1,"1410":1,"1414":1,"1415":1,"1416":1,"1417":1,"1419":1,"1421":1,"1426":1,"1429":1,"1447":2,"1448":1,"1455":1,"1460":1,"1470":2,"1475":4,"1477":4,"1489":2,"1493":1,"1500":1,"1502":1,"1511":8,"1515":6,"1516":2,"1517":1,"1518":1,"1519":1,"1520":1,"1521":3,"1523":2,"1524":1,"1525":1,"1526":2,"1527":3,"1529":2,"1543":2,"1547":1,"1554":1,"1558":3,"1559":2,"1569":2,"1570":1,"1571":3,"1572":2,"1575":2,"1576":1,"1580":1,"1588":1,"1605":3,"1606":1,"1607":1,"1608":1,"1613":1,"1615":1,"1618":2,"1619":1,"1620":1,"1621":1,"1632":1,"1640":1,"1644":1,"1651":3,"1653":1,"1654":1,"1671":3,"1672":2,"1676":1,"1677":1,"1701":1,"1704":1,"1708":1,"1722":1,"1723":1,"1727":1,"1739":1,"1741":1,"1743":1,"1753":2,"1759":2,"1764":1,"1767":2,"1768":1,"1770":1,"1771":1,"1792":194,"1802":1,"1804":1,"1816":1,"1818":1,"1820":2,"1821":1,"1822":4,"1827":2,"1828":1,"1830":2,"1831":1,"1832":2,"1833":2,"1844":1,"1847":3,"1848":2,"1850":1,"1851":1,"1856":3,"1858":1,"1859":1,"1861":1,"1875":1,"1882":2,"1883":1,"1884":2,"1885":1,"1886":2,"1887":2,"1888":1,"1898":4,"1912":2,"1915":1,"1917":2,"1920":1,"1921":1,"1922":1,"1924":2,"1925":1,"1926":1,"1928":2,"1929":2,"1937":2,"1940":1,"1941":1,"1949":2,"1951":3,"1952":3,"1953":3,"1954":3,"1956":1,"1967":2,"1968":1,"1973":1,"1974":3,"1982":1,"1983":1,"2000":3,"2002":2,"2007":3,"2009":1,"2011":1,"2015":1,"2016":2,"2019":4,"2027":1,"2029":1,"2038":2,"2039":1,"2040":1,"2047":1,"2062":1,"2074":1,"2077":3,"2088":1,"2092":1,"2094":2,"2097":1,"2106":1,"2107":1,"2109":2,"2112":1,"2124":1,"2129":1,"2131":1,"2148":1,"2156":1,"2177":3,"2183":1,"2187":1,"2195":1,"2197":2,"2200":1,"2208":1,"2224":1,"2226":3,"2242":1,"2247":4,"2251":2,"2254":1,"2255":4,"2256":3,"2258":4,"2264":2,"2265":9,"2266":1,"2267":7,"2273":2,"2274":4,"2278":1,"2282":1,"2284":1,"2287":1,"2289":1,"2296":1,"2297":1,"2319":1,"2328":1,"2330":2,"2333":1,"2334":1,"2335":1,"2336":2,"2346":1,"2352":1,"2353":1,"2354":2,"2359":1,"2360":3,"2362":1,"2365":2,"2367":1,"2371":2,"2372":2,"2375":1,"2379":1,"2380":5,"2382":2,"2391":1,"2392":3,"2395":2,"2405":1,"2411":1,"2412":1,"2414":1,"2421":3,"2424":1,"2429":2,"2431":1,"2432":1,"2435":1,"2436":2,"2438":2,"2443":1,"2444":1,"2445":3,"2447":1,"2453":3,"2455":1,"2466":1,"2476":1,"2481":2,"2482":1,"2484":2,"2486":2,"2487":1,"2491":1,"2492":1,"2495":1,"2497":3,"2498":3,"2502":1,"2509":2,"2512":1,"2517":1,"2520":3,"2531":1,"2532":1,"2533":2,"2534":1,"2535":1,"2536":1,"2537":6,"2539":1,"2541":1,"2549":5,"2551":1,"2554":1,"2558":4,"2559":1,"2565":2,"2566":1,"2586":3,"2587":1,"2589":1,"2590":2,"2596":1,"2597":4,"2600":1,"2603":1,"2607":3,"2608":3,"2611":3,"2614":2,"2615":3,"2629":2,"2632":8,"2633":3,"2634":5,"2635":4,"2641":1,"2645":1,"2648":2,"2649":1,"2656":1,"2662":1,"2664":1,"2665":1,"2667":2,"2673":1,"2678":2,"2679":1,"2688":3,"2694":1,"2695":2,"2719":1,"2751":1,"2760":1,"2763":1,"2764":1,"2766":2,"2769":1,"2771":1,"2785":1,"2795":2,"2797":1,"2814":2,"2815":1,"2829":1,"2833":1,"2834":1,"2840":1,"2847":1,"2854":2,"2855":1,"2858":2,"2868":2,"2871":1,"2876":1,"2878":1,"2879":1,"2880":1}}],["whoami",{"2":{"938":2,"1061":1,"1063":1,"1567":2,"1568":1,"1576":2}}],["whoever",{"2":{"926":1}}],["whose",{"2":{"77":1,"309":1,"314":1,"320":1,"358":1,"389":1,"447":1,"624":1,"664":1,"871":1,"1075":1,"1105":1,"1326":1,"1426":1,"1569":1,"1792":6,"1823":2,"1832":1,"1861":1,"1898":1,"1925":1,"2106":1,"2380":1,"2392":1,"2394":1,"2411":1,"2422":1,"2423":1,"2431":1,"2455":1,"2476":1,"2482":1,"2504":1,"2505":1,"2517":1,"2529":3,"2537":1,"2539":1,"2540":1,"2733":1,"2760":1,"2813":1,"2831":1,"2835":1,"2865":3,"2878":1}}],["who",{"0":{"936":1,"1371":1,"2831":1},"2":{"1":1,"636":1,"844":2,"848":1,"851":1,"879":1,"922":1,"927":1,"932":2,"936":3,"944":1,"1058":2,"1059":1,"1064":1,"1106":1,"1185":2,"1188":2,"1221":1,"1304":1,"1311":1,"1327":1,"1371":2,"1382":3,"1384":1,"1401":1,"1402":1,"1403":1,"1567":4,"1568":1,"1576":2,"1871":1,"2179":1,"2183":2,"2184":2,"2351":1,"2353":1,"2366":9,"2454":2,"2455":1,"2794":1,"2795":1,"2812":1,"2827":1,"2828":1,"2831":1,"2833":1,"2838":1}}],["wholesale",{"2":{"1111":1}}],["whole",{"0":{"2868":1},"2":{"1":1,"214":2,"694":1,"711":1,"716":1,"834":1,"836":1,"845":1,"848":3,"851":1,"852":2,"854":2,"865":1,"922":1,"1037":1,"1039":1,"1042":1,"1044":1,"1076":1,"1080":1,"1382":2,"1427":1,"1436":1,"1743":2,"1792":3,"1822":1,"1824":1,"1961":1,"2098":1,"2112":1,"2477":1,"2481":1,"2492":1,"2502":2,"2528":1,"2532":1,"2534":2,"2542":1,"2740":1,"2861":1,"2863":1,"2869":1,"2875":1}}],["went",{"2":{"1441":1,"2510":1}}],["weakest",{"2":{"1209":1}}],["weak",{"2":{"876":1}}],["wearing",{"2":{"853":1,"857":1}}],["weather",{"2":{"207":5,"212":1,"322":8,"390":4,"394":2,"452":6,"531":5,"1105":3,"1347":6,"1725":1,"1726":2,"1727":7,"1733":6,"1792":1,"1823":2,"2264":12,"2483":2,"2763":1,"2768":3}}],["well",{"2":{"840":1,"841":2,"843":1,"844":3,"845":1,"848":1,"851":1,"875":1,"916":1,"918":1,"920":1,"933":1,"994":1,"1048":1,"1073":1,"1079":1,"1081":1,"1096":1,"1122":1,"1132":1,"1170":1,"1205":1,"1385":1,"1386":2,"1389":1,"1391":1,"1394":1,"1395":1,"1396":1,"1398":2,"1399":1,"1400":1,"1402":3,"1403":3,"1404":2,"1405":1,"1419":1,"1424":1,"1427":1,"1428":1,"1576":1,"1792":2,"1831":3,"1833":1,"2088":1,"2422":1,"2481":1,"2795":1,"2803":1}}],["welcome",{"2":{"313":1,"428":4,"1254":1}}],["weighting",{"2":{"1429":1}}],["weighted",{"2":{"1423":1,"2164":1}}],["weight",{"2":{"686":1,"976":1,"1792":1,"2073":1,"2075":1,"2080":1}}],["weeks",{"2":{"133":1,"269":2,"271":1,"272":1,"280":1,"851":1,"865":1,"872":1,"1064":1,"1181":1,"1385":1,"2211":2}}],["week",{"2":{"92":1,"269":1,"859":1,"1143":1,"1193":2,"1792":1,"2211":1}}],["weekends",{"2":{"1":1}}],["webrequest",{"2":{"2781":1}}],["webapplicationfactory",{"2":{"2372":1}}],["webauthn",{"0":{"1209":1,"1227":1,"1877":1,"2492":1,"2625":1},"1":{"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":2,"1229":2,"1230":2,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1878":1,"1879":1,"1880":1},"2":{"868":2,"869":3,"873":1,"877":1,"1037":1,"1086":1,"1098":4,"1126":1,"1127":1,"1209":3,"1211":2,"1214":1,"1218":1,"1220":1,"1221":1,"1222":1,"1232":1,"1237":1,"1243":1,"1445":1,"1465":1,"1466":1,"1788":1,"1792":6,"1866":1,"1868":2,"1882":1,"1887":1,"2164":1,"2235":1,"2492":2,"2625":1,"2736":1}}],["webhostbuilderkestrelextensions",{"2":{"1792":1}}],["webhooks",{"2":{"1101":1,"1104":1,"1107":1,"1108":1}}],["webhook",{"2":{"72":2,"1030":2,"1035":1,"1106":1}}],["webcam",{"2":{"1044":2}}],["webscraper",{"2":{"1423":1}}],["websocket",{"2":{"1094":1,"1121":1,"1302":1,"1303":3,"1304":1,"1320":1,"1322":2,"2020":1}}],["websockets",{"0":{"1323":1},"2":{"1035":1,"1037":1,"1103":2,"1127":1,"1323":1,"1327":1,"1351":1,"1372":1,"1991":1}}],["website",{"0":{"0":1},"1":{"1":1,"2":1,"3":1},"2":{"0":1,"1":1,"815":3,"1400":1,"1404":1,"2267":1}}],["webp",{"2":{"747":1,"1099":1,"1792":1,"2123":1,"2125":1}}],["web",{"0":{"1423":1,"2429":1,"2554":1},"1":{"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1},"2":{"290":3,"831":1,"834":1,"921":1,"922":1,"938":1,"1037":2,"1043":1,"1047":2,"1061":1,"1064":1,"1098":2,"1115":1,"1202":1,"1354":1,"1363":1,"1373":1,"1404":1,"1423":2,"1445":1,"1453":1,"1466":1,"1582":1,"1635":1,"1648":1,"1666":1,"1718":1,"1787":1,"1792":6,"1794":1,"1812":1,"1946":1,"1963":1,"1978":1,"1984":1,"2014":1,"2091":1,"2164":5,"2166":1,"2175":1,"2419":1,"2479":1,"2527":1,"2632":4,"2706":1,"2762":1,"2770":2,"2772":1,"2776":2,"2862":1}}],["were",{"0":{"2490":2,"2505":1},"2":{"31":1,"188":1,"317":1,"408":1,"423":1,"848":1,"852":2,"856":1,"857":1,"859":1,"872":3,"912":1,"919":1,"1037":1,"1077":1,"1082":1,"1254":2,"1377":1,"1385":1,"1441":1,"1464":1,"1741":1,"1813":1,"1924":2,"1958":1,"2045":1,"2222":1,"2223":2,"2258":1,"2289":1,"2296":1,"2304":1,"2360":1,"2362":2,"2365":2,"2367":1,"2384":2,"2414":1,"2421":1,"2423":1,"2437":1,"2442":1,"2444":1,"2450":1,"2454":1,"2468":1,"2482":1,"2486":1,"2487":1,"2489":1,"2490":1,"2491":1,"2493":1,"2494":2,"2496":1,"2504":1,"2510":2,"2544":1,"2551":2,"2572":1,"2588":1,"2597":1,"2645":1,"2749":1}}],["we",{"0":{"1255":1},"2":{"1":2,"840":5,"841":15,"843":3,"844":9,"845":10,"847":4,"848":6,"849":4,"851":12,"852":3,"854":1,"856":1,"859":3,"861":2,"863":1,"864":1,"866":1,"913":1,"914":7,"915":4,"916":12,"917":7,"918":12,"919":4,"920":2,"922":1,"926":1,"932":1,"981":1,"992":2,"994":1,"996":1,"1031":1,"1048":1,"1075":1,"1258":2,"1318":1,"1335":1,"1337":1,"1338":2,"1382":13,"1385":5,"1386":15,"1388":3,"1389":2,"1390":8,"1391":5,"1392":4,"1394":7,"1395":8,"1396":6,"1397":1,"1398":5,"1399":10,"1401":2,"1403":1,"1404":1,"1405":6,"1426":1,"1428":1,"1435":1,"1441":2,"2438":1,"2452":1,"2823":1,"2824":4,"2825":1}}],["wildcard",{"2":{"1792":1}}],["wildcards",{"2":{"1792":4,"2002":1,"2096":2,"2270":1,"2537":3,"2877":1}}],["wild",{"2":{"1404":1}}],["wilson",{"2":{"913":1}}],["will",{"2":{"39":1,"40":2,"48":1,"60":1,"157":1,"173":1,"244":1,"336":1,"358":2,"408":1,"492":1,"653":1,"654":1,"656":2,"658":1,"660":1,"661":1,"662":2,"724":1,"745":1,"747":1,"761":1,"767":1,"771":1,"778":1,"784":1,"844":2,"845":3,"848":1,"851":2,"852":1,"857":1,"859":1,"860":1,"864":1,"869":1,"911":1,"912":1,"914":1,"916":1,"932":1,"982":1,"994":1,"996":2,"1009":1,"1052":1,"1073":2,"1076":2,"1078":1,"1084":1,"1129":6,"1130":2,"1132":3,"1133":2,"1196":1,"1203":1,"1318":1,"1326":1,"1382":1,"1386":18,"1388":1,"1390":1,"1391":2,"1394":1,"1395":1,"1398":6,"1399":7,"1402":1,"1404":2,"1409":1,"1464":1,"1511":4,"1517":2,"1608":1,"1620":1,"1621":1,"1641":1,"1653":1,"1697":1,"1703":1,"1733":1,"1792":95,"1840":1,"1940":1,"1948":1,"2208":1,"2247":4,"2250":1,"2252":2,"2254":2,"2255":1,"2256":5,"2258":2,"2264":1,"2265":4,"2266":1,"2267":1,"2272":1,"2376":1,"2378":1,"2389":1,"2454":1,"2533":1,"2577":1,"2633":1,"2634":1,"2642":1,"2665":1,"2687":1,"2788":1,"2792":2,"2821":1,"2823":1,"2881":1}}],["witty",{"2":{"913":1}}],["withactiverequests",{"2":{"1792":1,"1992":1}}],["withmessage",{"2":{"1026":4}}],["within",{"2":{"214":1,"646":1,"841":1,"863":1,"901":1,"902":1,"917":2,"918":1,"1098":1,"1129":1,"1158":1,"1204":1,"1208":1,"1343":1,"1418":1,"1430":1,"1741":1,"1743":1,"1792":9,"1951":1,"2002":1,"2257":1,"2264":6,"2289":1,"2397":2,"2463":1,"2464":1,"2465":1,"2482":1,"2500":1,"2525":1,"2550":1,"2588":1,"2633":1,"2791":1}}],["without",{"0":{"40":1,"60":1,"1045":1,"1781":1,"2729":1,"2868":1},"2":{"4":1,"10":1,"13":1,"29":1,"40":1,"41":1,"44":1,"55":1,"60":3,"68":1,"78":1,"91":1,"125":1,"132":1,"133":2,"144":1,"155":1,"165":4,"167":3,"168":1,"171":1,"174":1,"179":1,"180":1,"184":1,"186":1,"188":1,"192":1,"211":1,"212":1,"213":1,"214":1,"215":1,"244":1,"258":1,"261":1,"277":1,"282":1,"296":1,"320":1,"328":1,"332":1,"336":1,"339":1,"357":1,"362":1,"369":2,"378":1,"388":1,"390":1,"412":1,"433":1,"438":2,"458":1,"473":1,"484":1,"497":1,"507":1,"515":1,"549":1,"565":1,"567":1,"569":1,"589":1,"598":1,"607":1,"609":1,"618":1,"627":1,"636":2,"649":1,"650":1,"653":2,"664":1,"668":1,"699":1,"745":1,"794":1,"801":1,"803":1,"816":1,"823":1,"843":2,"851":1,"864":1,"868":3,"872":1,"874":2,"877":1,"888":1,"933":2,"936":1,"947":1,"952":1,"966":1,"985":1,"986":1,"992":1,"995":1,"1032":1,"1037":1,"1045":1,"1057":1,"1069":1,"1070":2,"1075":1,"1080":1,"1083":1,"1094":1,"1100":1,"1101":1,"1102":1,"1105":1,"1121":1,"1130":1,"1137":1,"1147":2,"1150":1,"1162":1,"1192":1,"1193":2,"1196":1,"1205":1,"1228":1,"1269":1,"1305":1,"1325":1,"1328":2,"1337":1,"1349":1,"1350":1,"1363":1,"1374":1,"1384":1,"1386":2,"1396":1,"1399":2,"1400":2,"1402":1,"1403":1,"1404":1,"1408":1,"1410":1,"1416":1,"1449":2,"1460":1,"1500":1,"1520":2,"1522":1,"1547":1,"1562":2,"1660":1,"1704":1,"1716":1,"1741":1,"1792":18,"1822":1,"1823":1,"1825":1,"1840":1,"1920":1,"1955":1,"1957":2,"1961":1,"2024":1,"2056":1,"2096":1,"2106":1,"2165":1,"2184":1,"2185":1,"2193":3,"2195":1,"2212":2,"2221":2,"2270":2,"2277":1,"2289":1,"2292":1,"2293":1,"2296":1,"2301":1,"2318":1,"2320":2,"2322":4,"2326":1,"2327":1,"2332":1,"2333":1,"2340":1,"2342":1,"2346":1,"2347":1,"2351":1,"2353":1,"2365":1,"2367":2,"2375":1,"2379":2,"2380":2,"2381":3,"2415":1,"2422":1,"2425":2,"2428":1,"2451":2,"2474":1,"2482":2,"2490":1,"2494":1,"2532":1,"2535":1,"2537":1,"2539":1,"2540":1,"2541":1,"2550":1,"2580":1,"2581":2,"2591":2,"2615":1,"2625":1,"2633":1,"2634":1,"2635":1,"2662":1,"2677":1,"2693":1,"2723":1,"2728":1,"2732":1,"2742":1,"2749":1,"2759":1,"2779":1,"2791":1,"2797":1,"2812":1,"2825":1,"2840":1,"2843":1,"2845":1,"2846":1,"2849":1,"2851":1,"2872":1,"2878":1,"2881":1}}],["with",{"0":{"38":1,"50":1,"61":1,"105":1,"118":1,"128":1,"129":1,"207":1,"208":1,"251":1,"256":1,"257":1,"263":1,"264":1,"273":1,"286":1,"292":1,"324":1,"349":1,"361":1,"373":1,"376":1,"395":1,"402":1,"417":1,"418":1,"419":1,"441":1,"442":1,"443":1,"453":1,"454":1,"478":1,"488":1,"523":1,"543":1,"574":1,"642":1,"643":1,"660":1,"736":1,"751":1,"756":1,"798":1,"800":1,"815":1,"878":1,"906":1,"918":1,"921":1,"945":1,"973":1,"986":1,"989":1,"1030":1,"1042":1,"1055":1,"1058":1,"1063":1,"1070":1,"1135":1,"1194":1,"1197":1,"1209":1,"1220":1,"1260":1,"1302":1,"1309":1,"1316":1,"1328":1,"1338":1,"1352":1,"1372":1,"1373":1,"1401":1,"1411":1,"1423":1,"1582":1,"1664":1,"1715":1,"1779":1,"1870":1,"1987":1,"2029":1,"2147":1,"2215":1,"2362":1,"2365":1,"2484":1,"2544":1,"2550":1,"2764":1},"1":{"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":1,"904":1,"905":1,"906":1,"907":2,"908":2,"909":2,"910":1,"911":1,"919":1,"920":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1056":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"1195":1,"1196":1,"1197":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1329":1,"1330":1,"1331":1,"1332":1,"1333":1,"1334":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1341":1,"1342":1,"1343":1,"1344":1,"1345":1,"1346":1,"1347":1,"1348":1,"1349":1,"1350":1,"1351":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1412":1,"1413":1,"1414":1,"1415":1,"1424":1,"1425":1,"1426":1,"1427":1,"1428":1,"1429":1,"1430":1,"1431":1,"1432":1,"1433":1,"1434":1},"2":{"0":1,"4":1,"13":1,"18":1,"19":1,"20":1,"25":1,"29":1,"34":1,"35":1,"38":3,"40":1,"44":1,"55":1,"61":1,"62":1,"63":1,"64":2,"68":1,"78":1,"87":1,"91":1,"102":2,"108":2,"109":3,"120":2,"125":1,"132":1,"144":1,"155":1,"157":1,"165":1,"168":1,"170":1,"179":1,"184":1,"187":2,"192":2,"202":2,"209":1,"211":1,"213":4,"214":4,"215":1,"221":1,"243":1,"244":2,"245":2,"257":1,"258":2,"261":2,"266":2,"282":1,"284":1,"296":1,"298":1,"301":2,"306":4,"308":2,"309":1,"310":2,"313":1,"314":1,"316":1,"320":2,"322":1,"327":2,"328":1,"330":1,"332":2,"337":1,"338":1,"339":1,"347":1,"348":2,"352":1,"355":2,"357":1,"361":1,"363":1,"364":1,"368":1,"369":3,"372":1,"374":1,"375":2,"376":1,"377":1,"380":1,"381":2,"382":4,"386":2,"387":1,"388":2,"389":1,"394":1,"395":1,"408":1,"409":2,"412":1,"415":1,"419":1,"421":2,"422":1,"423":2,"433":1,"436":4,"439":1,"445":2,"446":3,"448":4,"452":1,"453":1,"454":4,"458":1,"469":3,"473":1,"480":1,"484":1,"494":1,"497":1,"503":3,"507":1,"515":1,"527":1,"528":1,"529":1,"531":1,"537":1,"541":1,"549":1,"551":2,"554":1,"559":1,"565":1,"567":1,"569":1,"584":1,"585":1,"587":1,"589":1,"595":1,"598":1,"607":1,"609":1,"618":1,"624":1,"625":1,"627":1,"636":2,"638":1,"641":1,"642":1,"646":2,"649":1,"659":1,"663":1,"668":1,"679":3,"690":1,"698":1,"699":2,"701":1,"704":1,"705":2,"708":1,"714":1,"720":2,"726":1,"748":1,"761":1,"763":1,"765":1,"766":1,"771":1,"775":1,"778":1,"791":1,"794":1,"801":1,"803":1,"823":1,"826":1,"829":1,"833":1,"834":3,"835":3,"837":2,"838":1,"840":1,"841":4,"843":2,"844":1,"845":4,"848":5,"849":3,"851":5,"852":4,"856":1,"857":2,"859":2,"860":3,"861":2,"863":5,"864":5,"865":1,"866":2,"867":1,"868":5,"869":4,"871":1,"872":7,"873":1,"874":2,"875":1,"876":2,"877":1,"878":1,"879":2,"880":1,"884":1,"887":2,"894":1,"900":1,"902":2,"903":2,"904":1,"905":1,"910":1,"911":1,"913":1,"914":5,"915":4,"916":8,"917":3,"918":5,"919":5,"920":7,"922":1,"924":2,"926":3,"928":1,"932":3,"933":2,"934":1,"937":1,"941":1,"946":3,"947":2,"951":2,"952":1,"954":1,"964":1,"965":1,"969":1,"970":1,"971":2,"972":1,"973":1,"974":2,"975":1,"982":1,"983":2,"985":2,"986":2,"987":1,"988":1,"989":3,"990":1,"991":1,"992":3,"994":2,"995":1,"996":2,"997":1,"998":1,"1002":1,"1005":1,"1006":1,"1008":1,"1009":1,"1010":2,"1013":3,"1015":3,"1016":3,"1017":2,"1023":2,"1029":1,"1030":1,"1032":1,"1034":1,"1035":1,"1036":1,"1037":24,"1038":2,"1039":2,"1040":1,"1041":1,"1042":1,"1044":1,"1046":2,"1048":3,"1049":5,"1050":2,"1051":1,"1054":1,"1057":4,"1060":3,"1061":1,"1064":6,"1065":2,"1066":1,"1067":3,"1068":1,"1069":1,"1070":2,"1071":1,"1073":2,"1074":1,"1075":2,"1076":1,"1078":2,"1080":1,"1082":1,"1084":2,"1086":6,"1088":2,"1091":2,"1094":4,"1095":3,"1096":3,"1098":4,"1099":5,"1100":3,"1101":11,"1102":7,"1104":2,"1105":5,"1106":1,"1107":1,"1108":1,"1110":2,"1111":4,"1113":3,"1115":1,"1119":1,"1121":3,"1122":1,"1123":1,"1125":3,"1126":8,"1127":6,"1132":2,"1134":1,"1135":4,"1137":1,"1139":3,"1140":1,"1142":1,"1150":4,"1155":1,"1156":1,"1157":2,"1164":1,"1170":2,"1171":2,"1176":1,"1177":2,"1181":1,"1182":1,"1183":3,"1184":1,"1185":2,"1190":1,"1191":1,"1192":1,"1193":1,"1200":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":2,"1208":1,"1209":2,"1210":2,"1211":3,"1213":1,"1214":1,"1218":1,"1220":3,"1221":1,"1222":1,"1224":1,"1228":1,"1233":1,"1237":1,"1247":2,"1250":1,"1254":3,"1255":6,"1258":1,"1260":1,"1262":2,"1263":1,"1268":1,"1271":2,"1278":1,"1279":1,"1280":1,"1281":1,"1302":1,"1304":1,"1307":1,"1309":1,"1314":1,"1317":2,"1323":1,"1325":1,"1326":1,"1327":1,"1328":1,"1334":1,"1335":1,"1338":2,"1342":1,"1343":1,"1347":2,"1349":2,"1351":1,"1357":1,"1360":1,"1361":1,"1365":2,"1366":3,"1367":3,"1368":1,"1370":1,"1373":1,"1377":3,"1378":6,"1379":1,"1380":1,"1382":3,"1384":1,"1385":12,"1386":16,"1387":1,"1388":1,"1389":3,"1390":4,"1391":3,"1392":2,"1393":2,"1394":8,"1395":1,"1396":3,"1398":6,"1399":4,"1400":2,"1401":5,"1402":3,"1403":4,"1405":9,"1406":1,"1407":3,"1409":2,"1410":5,"1414":1,"1419":4,"1420":1,"1422":2,"1424":3,"1427":1,"1428":1,"1430":1,"1431":3,"1432":2,"1435":4,"1438":1,"1441":1,"1449":1,"1453":1,"1458":4,"1460":4,"1464":2,"1483":1,"1494":1,"1497":1,"1505":1,"1511":4,"1515":3,"1516":4,"1518":2,"1519":1,"1520":1,"1521":1,"1522":1,"1525":1,"1527":1,"1529":1,"1534":2,"1540":2,"1544":2,"1547":1,"1548":1,"1559":2,"1567":1,"1569":1,"1571":1,"1573":1,"1575":1,"1576":2,"1577":2,"1581":1,"1588":1,"1598":1,"1604":1,"1605":3,"1608":2,"1609":1,"1618":1,"1626":1,"1627":1,"1630":1,"1639":1,"1641":2,"1643":1,"1644":1,"1646":1,"1651":1,"1655":1,"1658":1,"1663":2,"1664":2,"1670":1,"1678":3,"1682":1,"1684":1,"1685":1,"1689":1,"1698":1,"1723":4,"1725":1,"1727":1,"1730":2,"1731":2,"1733":7,"1739":1,"1740":2,"1741":1,"1743":3,"1745":1,"1747":1,"1751":1,"1758":1,"1759":1,"1766":2,"1771":1,"1792":106,"1802":1,"1807":1,"1810":1,"1813":1,"1823":2,"1824":2,"1825":3,"1827":1,"1830":2,"1834":2,"1837":1,"1840":4,"1842":1,"1847":2,"1849":2,"1851":2,"1852":3,"1855":2,"1856":1,"1861":1,"1863":1,"1868":1,"1870":2,"1872":1,"1874":1,"1878":1,"1883":1,"1894":1,"1907":1,"1911":1,"1912":1,"1917":2,"1923":1,"1924":2,"1925":2,"1926":2,"1927":1,"1928":2,"1929":2,"1930":1,"1931":1,"1938":1,"1948":3,"1951":1,"1952":1,"1957":1,"1958":2,"1959":2,"1960":1,"1968":1,"1973":1,"1974":5,"1995":1,"2000":1,"2004":1,"2006":1,"2010":3,"2016":2,"2019":1,"2020":1,"2023":1,"2024":1,"2032":1,"2037":1,"2038":1,"2039":3,"2040":1,"2042":1,"2047":1,"2055":1,"2059":1,"2062":1,"2063":1,"2080":1,"2081":1,"2092":1,"2094":1,"2095":1,"2096":1,"2097":2,"2103":1,"2106":2,"2109":1,"2110":3,"2111":4,"2114":1,"2117":1,"2119":1,"2125":1,"2132":1,"2134":1,"2141":1,"2146":1,"2155":2,"2156":2,"2157":1,"2158":1,"2160":1,"2161":1,"2164":17,"2165":8,"2167":4,"2171":1,"2175":1,"2176":1,"2177":3,"2178":1,"2182":1,"2183":3,"2184":3,"2186":1,"2187":1,"2188":1,"2192":1,"2193":6,"2195":1,"2196":4,"2197":3,"2198":1,"2202":2,"2205":1,"2206":1,"2209":1,"2217":1,"2221":6,"2222":3,"2223":2,"2228":1,"2238":1,"2245":2,"2247":1,"2251":1,"2254":1,"2255":2,"2256":2,"2257":1,"2258":5,"2261":1,"2264":11,"2265":10,"2266":3,"2267":3,"2270":4,"2272":2,"2274":2,"2277":5,"2283":1,"2284":1,"2287":2,"2288":2,"2289":1,"2292":1,"2294":2,"2301":1,"2303":1,"2304":1,"2305":1,"2314":1,"2318":1,"2319":2,"2320":5,"2321":4,"2322":1,"2325":1,"2326":1,"2327":2,"2328":1,"2329":3,"2330":1,"2332":2,"2333":4,"2334":4,"2335":2,"2337":1,"2338":1,"2339":1,"2342":1,"2344":1,"2346":1,"2347":2,"2348":2,"2353":1,"2357":2,"2360":2,"2365":1,"2367":1,"2369":1,"2372":3,"2375":8,"2376":2,"2377":1,"2378":3,"2379":1,"2380":5,"2381":2,"2382":2,"2383":3,"2384":1,"2389":4,"2391":2,"2394":4,"2395":1,"2398":1,"2410":1,"2413":1,"2414":2,"2415":1,"2416":1,"2421":1,"2422":1,"2427":1,"2428":1,"2429":1,"2430":2,"2432":1,"2435":2,"2438":2,"2441":1,"2442":1,"2450":2,"2451":2,"2452":1,"2456":6,"2459":2,"2465":2,"2468":2,"2471":2,"2472":2,"2481":13,"2482":2,"2486":1,"2487":1,"2490":2,"2492":1,"2493":1,"2496":2,"2497":3,"2498":1,"2502":4,"2505":1,"2509":1,"2515":2,"2517":1,"2519":1,"2521":2,"2527":1,"2528":3,"2529":5,"2530":8,"2531":4,"2532":4,"2533":6,"2534":4,"2535":3,"2536":1,"2537":7,"2538":1,"2540":4,"2541":2,"2542":2,"2543":3,"2545":3,"2546":1,"2549":10,"2550":3,"2554":1,"2555":1,"2558":1,"2559":1,"2565":2,"2571":1,"2575":1,"2576":1,"2577":1,"2580":3,"2581":3,"2586":4,"2587":3,"2588":1,"2589":2,"2591":3,"2596":2,"2597":3,"2600":4,"2603":2,"2607":6,"2608":3,"2614":3,"2615":1,"2621":1,"2622":2,"2625":1,"2632":4,"2634":2,"2635":5,"2638":1,"2659":1,"2662":2,"2663":1,"2665":1,"2666":1,"2669":1,"2671":1,"2677":2,"2678":1,"2679":2,"2680":1,"2682":2,"2686":1,"2688":2,"2691":1,"2694":2,"2695":1,"2696":1,"2697":1,"2700":1,"2702":1,"2704":1,"2705":1,"2719":1,"2721":1,"2722":2,"2723":2,"2725":2,"2726":1,"2728":1,"2733":3,"2739":2,"2741":1,"2742":1,"2745":1,"2747":1,"2756":1,"2758":3,"2759":1,"2760":3,"2762":2,"2763":1,"2764":1,"2765":2,"2767":2,"2768":1,"2770":2,"2774":1,"2775":1,"2776":1,"2779":1,"2785":2,"2788":2,"2789":4,"2790":1,"2791":4,"2792":1,"2795":2,"2797":1,"2799":1,"2802":1,"2804":2,"2805":1,"2806":1,"2810":2,"2811":2,"2812":1,"2815":2,"2816":1,"2819":1,"2824":4,"2825":3,"2826":1,"2827":2,"2828":2,"2829":1,"2830":1,"2831":1,"2832":1,"2833":1,"2835":1,"2837":1,"2840":3,"2842":1,"2845":4,"2848":2,"2850":2,"2851":1,"2855":5,"2857":1,"2858":1,"2859":2,"2860":4,"2862":1,"2863":1,"2864":2,"2865":2,"2866":1,"2868":1,"2869":4,"2871":1,"2872":1,"2873":2,"2877":2,"2879":2,"2880":2,"2881":2}}],["widget",{"2":{"1189":2,"1192":1}}],["width",{"2":{"894":1,"1361":1,"1410":1,"1792":2}}],["widespread",{"2":{"1075":1}}],["widely",{"2":{"918":1}}],["wider",{"2":{"868":1,"1792":1}}],["wide",{"2":{"650":2,"872":1,"1305":1,"1385":1,"1792":1,"1823":1,"2040":1,"2474":1,"2828":1,"2832":1}}],["widens",{"2":{"320":1,"1042":1,"2481":1}}],["wiring",{"2":{"873":2,"2472":1}}],["wiremock",{"2":{"2506":1,"2513":1,"2523":1}}],["wires",{"2":{"2226":1}}],["wireless",{"2":{"1044":2}}],["wire",{"0":{"2324":1},"2":{"848":1,"860":1,"861":1,"872":2,"1405":1,"1440":1,"1522":1,"2318":1,"2380":1,"2774":1,"2840":1}}],["wired",{"2":{"310":1,"868":2,"1096":1,"1406":1,"2181":1,"2389":1,"2438":1,"2522":1,"2545":1}}],["wiki",{"2":{"841":1,"1792":2}}],["wikipedia",{"2":{"841":3,"859":1}}],["win64",{"2":{"2779":1,"2781":1}}],["winning",{"2":{"1382":1,"2435":1}}],["winner",{"2":{"1127":1}}],["win",{"2":{"840":1,"852":1,"857":1,"872":1,"1378":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":2,"1393":1,"1396":1,"2040":1,"2397":1,"2440":1,"2476":1,"2792":1}}],["windowseconds",{"2":{"476":1,"478":1,"479":1,"1069":2,"1158":1,"1159":1,"1162":2,"1177":1,"1245":1,"1792":4,"1893":1,"1951":2,"1952":2,"1955":2,"1958":1,"1959":1,"1960":3,"2257":2,"2379":2,"2441":1,"2443":2,"2470":1,"2471":1}}],["windows",{"0":{"343":1,"1662":1,"1988":1,"2781":1},"2":{"340":1,"343":1,"1117":1,"1651":1,"1659":1,"1662":4,"1792":3,"2157":1,"2297":2,"2450":1,"2543":1,"2565":3,"2716":1,"2776":1,"2792":1}}],["window",{"0":{"476":1,"1158":1,"1159":1,"1951":1,"1952":1},"2":{"214":2,"476":1,"480":1,"857":1,"860":3,"876":1,"956":1,"1071":1,"1096":1,"1101":2,"1158":2,"1159":2,"1317":1,"1413":1,"1416":1,"1440":2,"1743":2,"1792":14,"1950":2,"1951":3,"1952":5,"2040":1,"2061":1,"2247":1,"2257":1,"2377":1,"2385":1,"2476":1,"2498":1,"2500":1,"2502":1}}],["wins",{"2":{"1":1,"108":1,"319":3,"349":1,"390":1,"529":1,"710":1,"831":1,"834":1,"835":1,"873":1,"1067":1,"1069":1,"1150":1,"1162":1,"1429":1,"1523":1,"1792":3,"1910":1,"1956":1,"2094":1,"2097":1,"2319":1,"2379":1,"2380":1,"2381":1,"2395":2,"2398":1,"2424":1,"2427":1,"2433":1,"2435":1,"2481":1,"2537":2,"2843":1,"2877":1}}],["axes",{"2":{"2794":1}}],["axios",{"2":{"1011":1,"1026":4,"1027":2}}],["axis",{"2":{"841":1,"859":1,"865":2}}],["aad",{"2":{"1792":1}}],["aes",{"2":{"1656":13,"1663":1,"1792":6}}],["ajax",{"2":{"1489":1,"1492":1,"1792":3}}],["aka",{"2":{"1404":1}}],["a2",{"2":{"1382":1}}],["azure",{"0":{"1713":1},"2":{"1013":1,"1094":1,"1694":1,"1701":1,"1713":1,"1792":2,"2633":2}}],["awkwardness",{"2":{"2391":1}}],["aws",{"0":{"1712":1},"2":{"1013":1,"1070":1,"1086":1,"1094":1,"1102":2,"1121":2,"1528":1,"1701":1,"1792":2,"1851":1,"2382":1,"2576":1,"2633":2,"2779":1,"2783":1,"2790":1}}],["away",{"2":{"840":1,"841":1,"845":1,"847":1,"852":1,"856":2,"859":1,"860":1,"873":1,"1382":1,"1401":1,"1792":1,"1959":1,"2016":1,"2017":1,"2471":1,"2632":1}}],["awaited",{"2":{"2267":1}}],["awaitconnectionms",{"2":{"1317":1,"1416":3,"2247":4}}],["await",{"2":{"214":1,"429":1,"894":1,"938":1,"995":6,"996":4,"1024":1,"1026":3,"1063":2,"1107":4,"1218":2,"1318":1,"1320":2,"1321":1,"1326":1,"1335":3,"1342":5,"1361":1,"1366":5,"1386":3,"1408":3,"1409":1,"1410":1,"1416":4,"1431":1,"1553":1,"1558":1,"1567":3,"1568":2,"1569":1,"1575":2,"1792":1,"2247":4,"2273":2,"2310":1,"2313":1,"2360":1,"2462":1,"2502":1,"2836":2}}],["aware",{"2":{"107":2,"502":1,"919":1,"920":1,"1040":1,"1394":1,"1405":1,"2225":1,"2380":1,"2422":1,"2481":1}}],["ahead",{"2":{"1007":1,"1957":1,"2379":1,"2776":1,"2792":1}}],["aot",{"0":{"954":1,"1272":1,"2788":1},"2":{"1046":1,"1086":1,"1090":2,"1255":1,"1263":1,"1265":1,"1266":2,"1272":3,"1277":1,"1278":1,"1279":1,"1284":1,"1285":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1396":1,"2077":1,"2157":1,"2242":1,"2245":4,"2385":1,"2481":1,"2543":1,"2600":3,"2652":1,"2711":1,"2744":1,"2776":1,"2789":3,"2792":3}}],["a1",{"2":{"775":1,"900":1,"1382":1}}],["a>",{"2":{"348":1,"961":2,"1061":1,"1200":1}}],["america",{"2":{"2453":1}}],["ambiguous",{"2":{"2540":1,"2847":1}}],["ambiguity",{"2":{"369":1,"844":1,"2332":1}}],["ambient",{"2":{"1559":1,"1571":2,"1792":1,"2484":2,"2545":1}}],["ambitious",{"2":{"1385":1}}],["amd",{"2":{"1255":1}}],["am",{"0":{"936":1,"1371":1},"2":{"852":1,"922":1,"936":3,"938":1,"971":1,"1058":2,"1061":1,"1064":1,"1371":2,"1385":1,"1399":3,"1400":1,"1401":1,"1402":2,"1403":1,"1404":2,"1421":1,"1567":4,"1568":1,"1576":2,"2183":2,"2184":2,"2366":9}}],["among",{"2":{"305":1}}],["amounts",{"2":{"1823":1}}],["amount=100",{"2":{"263":1}}],["amount",{"2":{"263":1,"378":1,"426":1,"594":1,"622":1,"677":2,"860":2,"900":1,"920":1,"1009":1,"1391":3,"1398":2,"1403":2,"1404":1,"2076":2,"2078":1,"2079":1,"2333":1,"2344":1,"2802":2}}],["amplification",{"0":{"854":1}}],["amp",{"0":{"223":1,"236":1,"1037":1,"1102":1,"1183":1,"1574":1,"1978":1,"2345":1,"2361":1,"2368":1,"2707":1,"2715":1,"2748":1,"2776":1},"1":{"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1979":1,"1980":1,"1981":1,"1982":1,"1983":1,"1984":1,"1985":1,"1986":1,"1987":1,"1988":1,"1989":1,"1990":1,"1991":1,"1992":1,"1993":1,"1994":1,"1995":1,"1996":1,"1997":1,"2346":1,"2347":1,"2348":1,"2362":1,"2363":1,"2364":1,"2365":1,"2366":1,"2367":1,"2369":1,"2370":1,"2371":1,"2372":1,"2708":1,"2709":1,"2710":1,"2711":1,"2712":1,"2713":1,"2714":1,"2715":1,"2716":2,"2717":2,"2718":2,"2719":2,"2720":1,"2721":1,"2722":1,"2723":1,"2724":1,"2725":1,"2726":1,"2727":1,"2728":1,"2729":1,"2730":1,"2731":1,"2732":1,"2733":1,"2734":1,"2735":1,"2736":1,"2737":1,"2738":1,"2739":1,"2740":1,"2741":1,"2742":1,"2743":1,"2744":1,"2745":1,"2746":1,"2747":1,"2748":1,"2749":2,"2750":2,"2751":2,"2752":2,"2753":1,"2754":1,"2755":1,"2756":1,"2757":1,"2758":1},"2":{"167":1,"263":2,"298":1,"372":2,"374":2,"386":1,"423":2,"493":1,"520":1,"527":1,"529":1,"544":1,"679":1,"832":1,"835":1,"964":1,"1023":1,"1033":1,"1067":2,"1098":1,"1374":2,"1382":1,"1412":1,"1466":1,"1611":1,"1635":1,"1648":1,"1666":1,"1691":4,"1692":5,"1693":3,"1694":4,"1695":4,"1718":1,"1733":1,"1812":1,"1946":1,"1963":1,"2091":1,"2121":1,"2164":4,"2183":1,"2188":2,"2264":1,"2284":1,"2304":2,"2319":1,"2321":1,"2322":1,"2481":1,"2540":1,"2706":1,"2731":1,"2805":1,"2816":1,"2845":2,"2846":2}}],["ab",{"2":{"2265":1}}],["abuse",{"2":{"1156":1,"1160":1,"1180":1,"1250":1,"2061":1}}],["abusing",{"2":{"933":1}}],["able",{"2":{"1077":1,"1385":1,"1388":1,"1400":1,"1792":1}}],["abcde",{"0":{"2758":1}}],["abc",{"2":{"1017":2,"1620":1,"2283":2}}],["abc123",{"2":{"187":2,"1359":1,"1677":1,"2294":2}}],["ability",{"2":{"926":1,"1399":1,"2525":1,"2628":1}}],["abandoned",{"2":{"856":1,"2615":3}}],["absurd",{"2":{"913":1}}],["absorbed",{"2":{"1382":1}}],["absorb",{"2":{"869":1}}],["absorbs",{"2":{"868":1,"869":1}}],["absolutely",{"2":{"994":1,"1076":1,"1401":1}}],["absolute",{"2":{"409":1,"422":1,"436":1,"446":1,"1917":1,"2398":1}}],["absence",{"2":{"865":1}}],["absent",{"2":{"390":1,"872":1,"1792":2,"1824":1,"2038":1,"2040":1,"2476":2,"2536":1,"2666":1,"2868":1}}],["abstracting",{"2":{"856":1}}],["abstraction",{"0":{"842":1,"846":1,"850":1,"858":1,"862":1},"1":{"843":1,"844":1,"845":1,"847":1,"848":1,"849":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"859":1,"860":1,"861":1,"863":1,"864":1,"865":1},"2":{"841":5,"848":2,"856":1,"1385":1}}],["abstractions",{"2":{"841":2,"1403":1}}],["abstracts",{"2":{"848":2,"849":1,"865":1}}],["abstract",{"2":{"847":1}}],["abomination",{"2":{"1385":1}}],["aborted",{"2":{"2615":1}}],["aborting",{"2":{"1592":1}}],["aborts",{"2":{"1232":1,"1792":1,"2414":1,"2527":1,"2528":1,"2862":1}}],["abortconnect=false",{"2":{"1067":1,"1146":1,"1147":1,"1177":1,"1510":1,"1514":1,"1515":1,"1534":1,"1792":1,"2274":1}}],["above",{"2":{"175":1,"206":1,"308":1,"370":1,"614":1,"656":1,"690":1,"840":1,"847":1,"848":1,"851":1,"860":1,"869":1,"872":2,"873":1,"915":2,"919":1,"920":1,"946":1,"987":1,"1073":1,"1077":1,"1171":1,"1208":1,"1258":1,"1377":1,"1379":1,"1390":1,"1391":1,"1392":1,"1399":1,"1743":1,"1755":1,"1792":8,"1824":1,"1825":1,"1858":1,"1922":1,"1924":1,"1925":1,"1961":2,"2165":1,"2177":2,"2258":1,"2264":1,"2339":1,"2398":1,"2406":1,"2429":1,"2431":1,"2466":1,"2533":1,"2540":1,"2541":1,"2543":1,"2544":1,"2828":1,"2830":1,"2835":1}}],["about",{"0":{"0":1,"2":1},"1":{"1":1,"2":1,"3":1},"2":{"74":1,"388":1,"436":1,"841":8,"843":2,"844":5,"847":3,"848":2,"852":4,"855":1,"857":1,"859":3,"860":1,"863":1,"864":2,"868":1,"872":1,"873":1,"875":1,"881":1,"886":1,"944":1,"987":1,"1010":1,"1036":1,"1073":1,"1077":1,"1079":2,"1080":1,"1081":1,"1132":1,"1210":1,"1384":1,"1386":4,"1390":1,"1392":1,"1399":1,"1400":1,"1401":2,"1402":1,"1403":4,"1404":5,"1421":1,"1428":1,"1792":2,"2059":1,"2156":1,"2179":1,"2388":1,"2465":1,"2542":1,"2634":1,"2635":1,"2741":1,"2868":2,"2869":2}}],["affinity",{"2":{"1078":1,"2527":1,"2862":1}}],["affected",{"2":{"567":1,"586":1,"614":1,"622":1,"624":1,"666":1,"673":1,"829":1,"1254":1,"2072":1,"2320":1,"2337":1,"2339":1,"2342":2,"2357":2,"2365":2,"2389":1,"2453":1,"2486":1,"2487":1,"2490":1,"2494":1,"2496":1,"2504":1,"2834":1,"2851":1}}],["affects",{"2":{"337":1,"650":1,"667":1,"1566":1,"1704":1,"1832":1,"2157":1,"2397":1,"2466":1,"2795":1}}],["affect",{"2":{"165":1,"384":1,"1128":1,"1528":1,"1801":1,"1840":1,"2000":1,"2004":1,"2209":1,"2459":1}}],["afterwards",{"2":{"448":1,"2110":1,"2530":1,"2862":1}}],["after",{"0":{"314":1,"563":1,"1379":1,"2110":1,"2413":1,"2416":1,"2423":1,"2444":1,"2505":1,"2757":1},"2":{"133":1,"203":1,"211":2,"239":1,"277":1,"284":1,"310":1,"319":1,"378":1,"387":1,"447":1,"560":1,"563":1,"619":1,"689":1,"701":1,"705":1,"706":2,"707":1,"713":1,"714":1,"747":2,"781":2,"807":1,"851":2,"852":2,"876":1,"927":1,"947":1,"969":1,"1068":1,"1070":1,"1080":1,"1102":1,"1139":1,"1148":1,"1152":4,"1153":1,"1170":1,"1215":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1244":1,"1358":2,"1360":1,"1363":1,"1402":1,"1403":1,"1409":1,"1443":1,"1590":4,"1684":3,"1686":1,"1731":2,"1742":1,"1792":13,"1850":1,"1885":1,"1887":1,"1888":1,"1922":1,"2006":1,"2034":1,"2075":1,"2094":1,"2100":1,"2107":2,"2110":1,"2125":1,"2137":1,"2149":1,"2156":1,"2169":1,"2182":1,"2212":1,"2222":2,"2247":1,"2267":1,"2290":1,"2320":1,"2323":1,"2340":1,"2359":1,"2378":1,"2383":1,"2397":1,"2452":1,"2498":1,"2505":2,"2528":1,"2529":3,"2530":1,"2533":3,"2537":4,"2540":1,"2542":1,"2575":1,"2577":1,"2621":1,"2762":2,"2765":1,"2852":1,"2864":1,"2865":2,"2866":1,"2870":1,"2879":1}}],["agrees",{"2":{"2486":1}}],["agree",{"2":{"1403":1}}],["again",{"2":{"851":1,"852":1,"856":1,"859":1,"863":1,"1054":1,"1076":1,"1157":1,"1191":1,"1384":2,"1394":1,"1396":2,"1440":1,"1792":4,"1948":1,"1949":1,"1958":1,"1959":1,"1960":1,"2257":1,"2470":1,"2471":1,"2824":1}}],["against",{"0":{"933":1,"991":1,"2740":1},"2":{"0":1,"1":2,"19":1,"20":1,"22":1,"41":1,"299":1,"308":1,"309":3,"388":1,"618":1,"625":1,"650":1,"666":1,"696":1,"836":1,"852":1,"864":1,"874":2,"875":1,"876":1,"930":2,"934":1,"986":1,"1005":1,"1055":1,"1073":2,"1074":1,"1075":2,"1076":2,"1077":1,"1078":1,"1080":2,"1096":2,"1127":1,"1136":1,"1149":1,"1150":1,"1168":1,"1188":1,"1196":1,"1197":1,"1224":1,"1252":1,"1255":1,"1368":1,"1378":1,"1379":1,"1382":1,"1406":2,"1408":1,"1409":1,"1412":1,"1416":1,"1419":2,"1448":1,"1487":1,"1504":1,"1523":2,"1604":1,"1609":2,"1792":8,"1898":1,"2014":1,"2020":1,"2092":1,"2094":1,"2096":1,"2098":1,"2112":1,"2141":1,"2177":1,"2314":2,"2367":1,"2389":1,"2411":1,"2413":1,"2416":1,"2420":1,"2421":1,"2431":1,"2435":1,"2442":1,"2446":1,"2459":1,"2464":1,"2465":1,"2486":1,"2495":1,"2519":1,"2527":1,"2537":3,"2546":1,"2632":2,"2659":1,"2661":1,"2669":1,"2722":1,"2744":1,"2840":1,"2872":2}}],["agnostic",{"2":{"848":1,"1039":1,"2481":1}}],["ago",{"2":{"841":1,"851":1,"855":1,"860":1,"1382":3,"1385":1,"1400":1,"1403":1,"2297":1}}],["age=42",{"2":{"2321":1}}],["age=1800",{"2":{"2207":1}}],["age=31536000",{"2":{"1139":1,"1362":1,"1363":1,"2775":1}}],["age=300",{"2":{"1138":1}}],["age=3600",{"2":{"542":1,"1138":2,"2202":1}}],["age=n",{"2":{"1138":1}}],["age=86400",{"2":{"1138":1}}],["age",{"2":{"1068":1,"1098":1,"1459":1,"1639":1,"1792":1,"2148":1,"2321":2,"2375":1}}],["agent",{"0":{"1038":1,"1044":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1},"2":{"317":1,"319":1,"320":1,"324":1,"327":2,"480":1,"529":1,"1037":3,"1038":3,"1039":2,"1042":1,"1043":1,"1044":4,"1045":1,"1047":2,"1081":5,"1381":1,"1382":2,"1383":2,"1736":1,"1792":2,"1813":1,"1820":1,"1823":1,"1832":1,"1833":1,"1834":2,"2166":2,"2479":3,"2481":1}}],["agents",{"2":{"223":1,"837":1,"1038":1,"1041":1,"1042":1,"1081":1,"1382":2,"1789":1}}],["aggegations",{"2":{"919":1}}],["aggregator",{"2":{"1929":1,"2346":1,"2766":1}}],["aggregating",{"2":{"1021":1,"1429":1,"2770":1}}],["aggregations",{"2":{"919":2,"920":1,"956":1,"1385":1}}],["aggregation",{"0":{"1011":1,"1021":1},"2":{"137":1,"919":3,"1010":1,"1035":1,"1036":1,"1105":1,"1205":1,"1259":1,"1746":1,"2347":1,"2608":1}}],["aggregated",{"0":{"2366":1},"2":{"869":2,"2366":1}}],["aggregates",{"2":{"851":2,"865":2,"1010":1,"1036":1,"1096":2,"1122":1,"1127":1}}],["aggregate",{"2":{"845":1,"849":1,"851":4,"854":1,"860":1,"861":1,"863":10,"864":3,"865":2,"918":1,"919":1,"1011":1,"1096":1,"1130":1}}],["aggressive",{"2":{"573":1,"577":3,"1129":1,"1139":1,"1154":4,"1179":2,"1597":1,"1599":2,"1625":1}}],["agg",{"2":{"264":1,"415":2,"426":1,"918":3,"919":3,"1105":1,"1138":2,"1139":1,"1179":3,"1197":1,"1232":1,"1234":1,"1427":1,"1504":2,"2303":1,"2586":1,"2588":1,"2813":2}}],["avx2",{"2":{"2270":1}}],["averaging",{"2":{"1429":1}}],["averagebookprice",{"2":{"1431":1}}],["average",{"0":{"1425":1},"1":{"1426":1,"1427":1,"1428":1},"2":{"1271":1,"1278":5,"1382":2,"1423":1,"1427":5,"1430":1,"1431":4,"1974":1,"2164":1,"2607":1,"2760":2,"2762":6}}],["avgprice",{"2":{"1427":1,"1431":2}}],["avgrating",{"2":{"916":2,"917":2,"920":1}}],["avg",{"2":{"427":2,"916":3,"1277":2,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1293":1,"1295":1,"1297":1,"1299":1,"1301":1,"1427":2,"1431":1,"2762":3}}],["availability",{"0":{"1135":1,"1172":1,"1177":1},"1":{"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":2,"1174":2,"1175":2,"1176":2,"1177":2,"1178":2,"1179":1,"1180":1,"1181":1,"1182":1},"2":{"844":1,"1037":1,"1135":2,"1152":1,"1625":1}}],["availableclaims",{"2":{"1792":1,"2033":1,"2037":1,"2038":1,"2039":1,"2040":1,"2042":1,"2476":2}}],["availableenvvars",{"0":{"2476":1},"2":{"390":3,"534":1,"1792":2,"1862":1,"2033":1,"2037":1,"2038":1,"2040":3,"2223":1,"2224":1,"2476":2,"2477":1,"2483":2,"2768":1}}],["available",{"0":{"684":1,"2048":1,"2163":1,"2168":1},"1":{"2049":1,"2050":1,"2051":1,"2052":1,"2164":1,"2165":1,"2166":1,"2167":1},"2":{"19":1,"20":1,"22":1,"63":1,"121":1,"175":2,"179":1,"216":1,"412":1,"448":1,"452":1,"529":1,"559":1,"581":1,"618":1,"746":1,"747":1,"752":1,"765":1,"768":1,"776":1,"781":1,"816":1,"823":1,"868":3,"892":1,"904":1,"905":1,"921":1,"934":1,"947":1,"976":1,"1013":1,"1029":1,"1048":1,"1049":1,"1069":1,"1096":1,"1104":1,"1107":1,"1111":1,"1155":1,"1165":1,"1166":1,"1173":1,"1174":3,"1183":1,"1217":1,"1220":1,"1228":1,"1278":1,"1281":1,"1349":1,"1377":1,"1385":1,"1386":2,"1398":3,"1401":1,"1403":1,"1416":1,"1475":1,"1477":1,"1569":1,"1596":1,"1608":1,"1613":1,"1624":1,"1631":1,"1662":2,"1759":1,"1792":13,"1801":1,"1820":1,"1837":1,"1840":1,"1862":1,"1878":1,"1912":1,"1950":1,"2010":1,"2012":1,"2094":1,"2109":1,"2130":1,"2140":1,"2142":1,"2160":1,"2169":1,"2184":2,"2254":1,"2256":1,"2272":1,"2329":1,"2366":1,"2372":1,"2550":1,"2572":1,"2575":1,"2577":1,"2664":1,"2693":1,"2789":1,"2790":1,"2791":3,"2792":1,"2823":1}}],["avatar",{"2":{"393":1,"2185":1}}],["avoiding",{"2":{"920":1,"951":1}}],["avoids",{"0":{"1325":1},"2":{"915":1,"1031":1,"1199":1}}],["avoided",{"2":{"872":4}}],["avoid",{"2":{"133":1,"277":1,"784":1,"967":1,"1037":1,"1055":1,"1134":1,"1174":1,"1338":1,"1403":1,"1543":1,"1554":1,"1661":1,"1792":1,"2212":1,"2247":1,"2256":1,"2324":1,"2372":1,"2422":1,"2614":1,"2825":1}}],["arabic",{"2":{"2607":1}}],["arial",{"2":{"1792":1,"2073":1,"2075":1,"2080":1}}],["ar",{"2":{"1185":2}}],["arm",{"2":{"2270":1,"2385":1,"2576":4,"2790":6}}],["army",{"2":{"1441":1}}],["arm64",{"0":{"2576":1,"2783":1,"2784":1,"2790":1},"2":{"1117":1,"2237":1,"2576":7,"2779":2,"2783":2,"2784":1,"2790":8,"2792":2}}],["arms",{"2":{"913":1}}],["args",{"2":{"2679":1}}],["arg",{"2":{"2523":1}}],["argon2",{"2":{"1049":2}}],["argued",{"2":{"869":1,"876":1}}],["argue",{"2":{"866":1}}],["arguably",{"2":{"843":1,"1078":1}}],["argument",{"2":{"308":1,"847":1,"852":2,"857":1,"860":1,"873":1,"1040":1,"1046":1,"1403":1,"1569":1,"2092":1,"2164":1,"2184":1,"2252":2,"2520":1,"2522":1,"2540":1,"2662":1,"2678":1,"2695":1,"2823":1,"2824":1}}],["arguments",{"0":{"2691":1},"1":{"2692":1},"2":{"109":1,"184":1,"186":1,"188":1,"653":2,"872":1,"1039":2,"1431":1,"1785":1,"1792":1,"2181":1,"2292":1,"2293":1,"2296":1,"2481":1,"2498":1,"2680":1,"2681":3,"2684":1,"2687":1,"2691":1,"2694":1,"2772":1,"2825":2}}],["artifacts",{"0":{"2489":1},"2":{"2489":1,"2527":1,"2576":1,"2862":1}}],["artifact",{"2":{"872":1,"1406":1,"1421":1,"2102":1}}],["article>",{"2":{"1427":2}}],["articles",{"2":{"1427":4}}],["article",{"2":{"859":1,"863":1,"996":5,"1424":1,"1427":1}}],["arbitration",{"0":{"855":1},"2":{"852":2,"855":1}}],["arbitrary",{"2":{"390":1,"852":1,"857":1,"1097":1,"1132":1,"2383":1,"2389":1}}],["archive",{"2":{"848":1,"1437":1,"1438":1,"2062":1,"2220":1}}],["architect",{"2":{"847":1}}],["architects",{"2":{"845":1}}],["architectures",{"2":{"840":1,"876":1,"1250":1}}],["architecture",{"0":{"923":1,"939":1,"1085":1,"1184":1,"1211":1,"1276":1,"1320":1,"1333":1},"1":{"924":1,"925":1,"926":1,"940":1,"941":1,"942":1,"943":1,"944":1,"1086":1,"1087":1,"1088":1},"2":{"836":1,"839":2,"840":4,"847":1,"848":1,"851":2,"861":1,"866":1,"871":2,"874":1,"876":4,"877":2,"946":1,"948":1,"1007":1,"1015":1,"1035":1,"1037":1,"1083":1,"1185":1,"1382":2,"1402":2,"1403":2,"1405":2,"1435":1,"2790":1}}],["architectural",{"0":{"1108":1},"2":{"307":1,"871":1,"873":1,"1014":1,"1098":1,"1101":2}}],["arranges",{"2":{"2860":1}}],["arrange",{"2":{"1074":1,"1076":1,"1393":1,"2526":1,"2528":1,"2860":1,"2864":1}}],["arraycompositefielddescriptors",{"2":{"2611":1}}],["arraycompositefieldnames",{"2":{"2611":1}}],["arraycompositecolumninfo",{"2":{"2370":1}}],["arraypool",{"0":{"2402":1},"2":{"2226":1,"2402":1,"2604":1}}],["arraybuffer",{"2":{"1107":1}}],["arrayagg",{"2":{"918":1}}],["arrays",{"0":{"335":1,"2397":1,"2586":1,"2589":1},"1":{"2398":1},"2":{"87":1,"286":1,"334":1,"335":2,"337":2,"918":3,"919":1,"1092":1,"1097":3,"1255":1,"1370":1,"1605":1,"1792":2,"1967":1,"1974":1,"2000":2,"2009":2,"2226":1,"2236":1,"2270":2,"2325":1,"2330":2,"2357":2,"2398":2,"2492":1,"2497":1,"2532":1,"2533":1,"2586":6,"2588":9,"2589":4,"2590":3,"2603":1,"2607":3,"2611":3,"2688":1,"2725":1,"2841":2,"2842":1}}],["array",{"0":{"1279":1,"1526":1,"2378":1,"2588":1,"2725":1},"2":{"81":1,"227":1,"290":1,"335":1,"390":1,"567":2,"575":1,"607":1,"609":1,"615":1,"738":1,"760":1,"761":1,"763":1,"770":1,"771":1,"773":1,"776":1,"778":1,"798":1,"802":1,"835":1,"880":1,"881":1,"882":1,"883":1,"885":1,"887":1,"892":1,"903":3,"918":6,"919":5,"924":1,"928":2,"929":6,"930":2,"948":2,"951":1,"1021":2,"1026":1,"1051":3,"1069":1,"1152":1,"1157":2,"1197":1,"1232":1,"1234":1,"1279":4,"1339":3,"1357":2,"1370":1,"1376":2,"1386":1,"1399":1,"1410":2,"1427":3,"1504":3,"1523":2,"1560":1,"1562":4,"1565":1,"1574":1,"1589":3,"1590":1,"1639":4,"1640":1,"1670":1,"1703":3,"1792":22,"1824":1,"1838":4,"1850":1,"1862":2,"1882":1,"1884":1,"1898":2,"1917":2,"1937":2,"1948":2,"1956":1,"1967":2,"1974":7,"2034":1,"2038":7,"2040":1,"2047":2,"2055":1,"2094":2,"2129":1,"2130":1,"2131":1,"2187":1,"2236":1,"2258":1,"2265":2,"2270":2,"2320":1,"2339":3,"2357":2,"2370":1,"2372":1,"2378":2,"2380":2,"2388":1,"2397":4,"2398":5,"2415":1,"2476":1,"2483":1,"2491":1,"2496":1,"2498":1,"2531":1,"2535":1,"2540":1,"2586":7,"2588":3,"2589":2,"2590":4,"2600":1,"2603":1,"2607":7,"2608":1,"2611":2,"2614":1,"2621":2,"2635":2,"2671":1,"2725":1,"2785":1,"2824":1,"2842":1,"2845":1,"2851":2,"2852":1,"2869":1}}],["arr",{"2":{"1021":3,"1376":3}}],["arrows",{"2":{"1381":1}}],["arrow",{"2":{"1008":1}}],["arrive",{"2":{"868":2,"1067":1,"1165":1,"2183":1}}],["arrives",{"2":{"435":1,"2830":1}}],["arriving",{"2":{"847":1,"1961":1}}],["around",{"2":{"174":1,"697":1,"835":1,"851":1,"852":1,"865":1,"912":1,"919":1,"1073":1,"1075":1,"1122":1,"1280":1,"1401":1,"1402":1,"1414":1,"1419":1,"2112":1,"2258":3,"2409":1,"2532":1,"2535":1,"2714":1,"2855":1}}],["aren",{"2":{"319":1,"372":1,"868":1,"1251":1,"1328":1,"1385":1}}],["areas",{"2":{"1068":1,"1098":1,"1458":1,"2375":1}}],["area",{"2":{"49":2,"1083":1,"1414":1,"1458":1,"1792":1,"2042":1,"2375":1}}],["are",{"0":{"1":1,"448":1,"948":1,"985":1,"1041":1,"1072":1,"1078":1,"1395":1,"2180":1,"2384":1,"2410":1,"2424":1,"2451":1,"2489":1,"2492":1,"2710":1,"2724":1,"2736":1,"2749":1},"1":{"1073":1,"1074":1,"1075":1,"1076":1,"1077":1,"1078":1,"1079":1,"1080":1,"1081":1,"1082":1,"2411":1,"2412":1,"2413":1},"2":{"1":3,"3":1,"14":1,"17":1,"25":1,"41":2,"63":3,"87":1,"101":1,"108":2,"113":1,"119":1,"121":1,"155":1,"156":1,"159":1,"165":1,"169":1,"170":1,"175":3,"177":1,"179":1,"182":2,"184":1,"186":1,"188":6,"197":1,"202":1,"203":1,"210":2,"212":2,"214":4,"216":2,"220":1,"244":2,"253":1,"262":1,"269":2,"280":1,"286":5,"299":2,"300":3,"305":2,"306":1,"307":1,"308":1,"309":3,"320":1,"324":2,"327":1,"330":2,"334":1,"335":2,"337":3,"347":1,"352":1,"370":1,"375":1,"376":1,"378":1,"380":1,"381":1,"382":3,"384":1,"387":1,"388":1,"389":1,"390":1,"395":1,"404":1,"414":2,"423":3,"424":1,"436":2,"446":1,"448":5,"452":2,"460":1,"462":1,"480":1,"494":2,"497":1,"512":1,"515":1,"528":1,"529":1,"537":1,"541":1,"544":1,"549":1,"564":1,"567":1,"584":1,"585":1,"595":1,"622":1,"625":1,"637":1,"639":3,"641":1,"643":1,"656":1,"663":2,"668":2,"669":1,"673":2,"679":1,"684":1,"687":1,"691":1,"695":2,"696":2,"704":1,"709":1,"720":3,"722":1,"737":1,"746":1,"747":2,"765":1,"766":1,"768":1,"776":1,"777":1,"779":1,"801":3,"807":1,"816":1,"817":1,"818":3,"819":1,"823":1,"826":1,"829":1,"831":1,"832":1,"840":1,"841":11,"843":2,"844":5,"845":3,"847":2,"848":5,"849":4,"851":5,"852":5,"853":1,"855":1,"856":1,"857":3,"859":2,"860":3,"861":1,"863":1,"864":1,"865":4,"866":1,"867":2,"868":6,"869":3,"871":3,"872":5,"873":3,"874":2,"875":1,"877":1,"881":1,"901":1,"911":1,"915":4,"916":6,"917":2,"918":3,"919":3,"920":1,"926":2,"928":1,"932":2,"933":1,"936":2,"937":2,"938":1,"946":1,"948":1,"949":1,"952":1,"963":1,"973":1,"974":1,"975":2,"978":2,"980":1,"987":1,"990":1,"992":2,"995":1,"996":2,"1004":1,"1014":2,"1015":2,"1029":2,"1034":1,"1035":2,"1037":1,"1038":1,"1041":1,"1042":1,"1046":2,"1049":1,"1054":3,"1056":1,"1063":1,"1067":2,"1069":1,"1070":1,"1071":2,"1074":1,"1076":1,"1077":1,"1079":3,"1080":2,"1081":3,"1082":1,"1086":1,"1087":1,"1088":1,"1095":1,"1096":1,"1097":1,"1098":2,"1100":2,"1102":1,"1105":2,"1106":3,"1107":3,"1127":2,"1128":1,"1129":1,"1132":1,"1142":1,"1149":1,"1150":2,"1151":1,"1152":1,"1160":2,"1164":1,"1165":1,"1169":1,"1170":1,"1172":1,"1178":1,"1192":1,"1214":1,"1220":1,"1225":1,"1226":1,"1244":1,"1249":1,"1254":1,"1281":1,"1285":1,"1305":1,"1323":2,"1324":1,"1325":1,"1341":1,"1354":2,"1355":1,"1359":1,"1363":1,"1365":1,"1376":1,"1378":1,"1382":3,"1385":6,"1386":7,"1387":1,"1390":1,"1391":2,"1392":2,"1393":2,"1394":8,"1396":4,"1397":3,"1398":3,"1401":6,"1402":3,"1403":11,"1405":1,"1406":1,"1410":1,"1414":2,"1417":2,"1419":1,"1420":2,"1423":1,"1429":2,"1431":1,"1433":1,"1435":3,"1437":1,"1453":1,"1472":1,"1475":4,"1477":6,"1502":1,"1511":3,"1513":1,"1516":1,"1521":1,"1522":2,"1523":2,"1531":2,"1538":1,"1566":1,"1567":2,"1569":1,"1570":1,"1571":2,"1575":1,"1582":1,"1608":1,"1621":3,"1627":1,"1642":1,"1643":1,"1651":1,"1653":1,"1660":1,"1662":2,"1664":3,"1690":1,"1706":1,"1707":1,"1708":3,"1709":1,"1722":3,"1732":1,"1733":1,"1741":1,"1743":3,"1757":1,"1759":1,"1769":2,"1792":108,"1816":1,"1820":1,"1823":1,"1824":3,"1825":1,"1827":1,"1830":1,"1838":1,"1840":1,"1851":1,"1852":2,"1853":2,"1854":1,"1856":6,"1858":2,"1862":1,"1876":1,"1898":2,"1908":2,"1909":1,"1910":1,"1912":1,"1915":1,"1917":1,"1922":2,"1923":2,"1924":4,"1926":1,"1927":1,"1950":1,"1953":1,"1957":1,"1961":1,"1967":2,"1968":1,"1969":2,"1973":1,"1974":3,"1982":1,"2000":2,"2004":2,"2005":1,"2006":2,"2007":1,"2008":1,"2010":3,"2011":1,"2016":1,"2036":1,"2039":2,"2040":2,"2056":1,"2060":1,"2062":1,"2072":1,"2074":1,"2095":1,"2098":2,"2100":1,"2106":1,"2107":1,"2110":1,"2112":1,"2137":1,"2140":1,"2142":1,"2147":1,"2149":2,"2157":1,"2160":1,"2165":1,"2167":1,"2179":1,"2181":1,"2184":2,"2185":1,"2187":1,"2191":2,"2192":2,"2193":3,"2202":1,"2203":1,"2208":1,"2212":1,"2222":1,"2223":1,"2224":3,"2242":1,"2250":1,"2253":1,"2256":1,"2258":1,"2264":3,"2265":4,"2267":1,"2270":1,"2272":1,"2273":1,"2277":1,"2278":1,"2282":3,"2284":2,"2289":1,"2291":2,"2292":1,"2293":1,"2296":6,"2297":2,"2302":1,"2307":1,"2318":2,"2320":1,"2321":2,"2322":1,"2323":4,"2325":1,"2328":1,"2330":2,"2332":1,"2333":2,"2334":3,"2335":1,"2337":1,"2338":2,"2340":1,"2342":2,"2347":1,"2348":1,"2352":1,"2353":1,"2354":1,"2357":2,"2359":2,"2362":2,"2363":1,"2364":1,"2366":2,"2367":1,"2372":2,"2376":1,"2379":1,"2380":5,"2382":1,"2383":2,"2384":1,"2385":1,"2394":1,"2398":2,"2399":2,"2403":1,"2407":2,"2410":2,"2413":2,"2415":2,"2419":1,"2422":1,"2427":1,"2428":1,"2430":1,"2431":2,"2432":1,"2433":1,"2438":2,"2444":2,"2445":1,"2450":1,"2454":4,"2455":1,"2459":1,"2465":1,"2466":2,"2474":1,"2476":2,"2481":1,"2482":4,"2483":1,"2484":1,"2486":3,"2491":2,"2492":1,"2493":1,"2495":3,"2496":2,"2502":6,"2505":3,"2509":2,"2511":1,"2512":3,"2523":1,"2527":1,"2528":4,"2529":5,"2530":2,"2531":1,"2532":1,"2533":4,"2534":4,"2535":3,"2537":8,"2538":1,"2539":2,"2540":4,"2543":1,"2545":2,"2546":1,"2549":4,"2555":7,"2558":1,"2572":2,"2575":3,"2581":1,"2586":1,"2587":1,"2588":2,"2589":1,"2590":2,"2591":2,"2595":1,"2597":1,"2607":4,"2615":2,"2621":2,"2632":1,"2633":2,"2634":4,"2635":3,"2656":1,"2674":1,"2678":1,"2680":1,"2684":1,"2692":2,"2695":1,"2710":1,"2711":1,"2712":1,"2714":1,"2722":1,"2723":1,"2724":1,"2726":1,"2729":1,"2731":1,"2741":1,"2744":1,"2745":1,"2754":1,"2760":1,"2763":2,"2769":2,"2774":1,"2802":2,"2803":1,"2810":2,"2812":5,"2813":1,"2820":1,"2822":1,"2824":1,"2833":1,"2836":1,"2840":4,"2841":1,"2842":1,"2845":3,"2849":1,"2852":1,"2854":1,"2855":1,"2856":1,"2857":1,"2861":1,"2864":2,"2865":2,"2867":1,"2868":1,"2869":6,"2870":1,"2871":4,"2872":1,"2873":1,"2875":1,"2878":2,"2879":1,"2880":1}}],["adoption",{"2":{"1382":1}}],["adopted",{"2":{"838":1}}],["advsimd",{"2":{"2270":1}}],["advertising",{"2":{"1825":1}}],["advertises",{"2":{"1824":1}}],["advertised",{"2":{"322":1,"351":1,"1043":1,"1792":2,"1828":1,"1829":1,"2434":1,"2459":1,"2461":1,"2489":1}}],["adverse",{"2":{"1180":1}}],["advantages",{"0":{"1246":1},"1":{"1247":1,"1248":1,"1249":1,"1250":1,"1251":1},"2":{"1378":2,"1386":1,"1392":1,"1396":1}}],["advantage",{"0":{"1015":1,"1365":1},"2":{"944":1}}],["advanced",{"0":{"895":1,"1028":1,"1109":1,"1326":1},"1":{"896":1,"897":1,"898":1,"899":1,"900":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1},"2":{"1114":1,"1376":1,"1386":1,"1405":1,"2160":1}}],["advance",{"2":{"878":1,"1381":1,"1409":1,"2400":1,"2533":1}}],["admit",{"2":{"1385":1}}],["admitted",{"2":{"857":1,"861":1}}],["admirably",{"2":{"863":1}}],["admission",{"2":{"851":1}}],["adminapi",{"2":{"1414":1}}],["administers",{"2":{"926":1}}],["admins",{"2":{"646":1,"1313":1,"1316":1,"2833":1}}],["admin",{"2":{"14":1,"18":2,"21":1,"22":1,"23":1,"34":1,"48":2,"49":3,"107":2,"305":1,"314":2,"351":3,"637":1,"642":4,"644":1,"646":2,"691":3,"695":1,"700":1,"705":2,"714":1,"724":3,"834":1,"835":1,"836":1,"837":1,"868":1,"869":1,"877":1,"967":1,"1051":2,"1057":8,"1061":3,"1065":1,"1066":1,"1068":1,"1073":2,"1078":1,"1088":1,"1098":1,"1100":1,"1113":1,"1179":1,"1189":4,"1193":4,"1195":1,"1196":1,"1220":1,"1313":2,"1316":2,"1373":1,"1414":4,"1458":10,"1502":1,"1504":1,"1595":1,"1646":1,"1792":11,"1909":1,"1911":1,"2006":1,"2035":1,"2036":2,"2042":2,"2059":2,"2067":1,"2098":1,"2111":4,"2167":1,"2179":1,"2181":4,"2187":9,"2199":3,"2200":3,"2207":1,"2214":1,"2314":2,"2319":1,"2375":10,"2410":1,"2413":1,"2432":1,"2434":2,"2532":5,"2534":3,"2540":1,"2635":2,"2728":1,"2774":1,"2775":4,"2797":1,"2831":2,"2833":1,"2871":2,"2872":4,"2873":5,"2876":3}}],["adjusttouniversal",{"2":{"2451":2,"2457":1}}],["adjusting",{"2":{"2088":1}}],["adjust",{"2":{"845":1,"1792":1,"2111":1,"2532":1,"2721":1,"2756":1}}],["adams",{"2":{"913":1}}],["adapters",{"2":{"1343":1}}],["adapts",{"2":{"1066":1}}],["adapt",{"2":{"860":1,"861":1,"1065":1,"1403":1}}],["ada",{"2":{"695":2,"700":1,"705":1,"1078":2,"2873":2}}],["addorupdate",{"2":{"2459":1,"2622":1}}],["addfixedwindowlimiter",{"2":{"1822":1}}],["addpolicy",{"2":{"1822":1}}],["addpasskeypath",{"2":{"1226":1,"1792":1,"1876":1}}],["addpasskeyoptionspath",{"2":{"1226":1,"1792":2,"1876":1}}],["addcurrentserver",{"2":{"1792":1,"1897":1,"1898":1,"1900":1,"1907":1,"2254":1}}],["addcookie",{"2":{"869":1}}],["addenvironmentvariables",{"2":{"1603":1,"1604":1,"1606":1,"1608":2,"1792":2,"2272":2,"2689":2}}],["addeventlistener",{"2":{"894":1,"961":1,"1366":1,"1410":1}}],["added",{"0":{"2582":1,"2627":1},"2":{"203":1,"286":1,"317":1,"347":1,"408":1,"469":1,"663":1,"854":1,"868":1,"869":1,"912":1,"1160":1,"1237":1,"1258":1,"1385":1,"1397":1,"1453":1,"1564":1,"1569":1,"1609":1,"1701":1,"1759":1,"1762":1,"1767":1,"1792":11,"1813":1,"1821":1,"1848":1,"1852":1,"1857":1,"1866":1,"1912":1,"1924":1,"1925":1,"1953":1,"2014":1,"2016":1,"2045":1,"2056":1,"2254":3,"2255":4,"2257":1,"2258":1,"2259":1,"2261":2,"2265":5,"2266":2,"2267":2,"2270":1,"2272":1,"2273":1,"2274":1,"2277":1,"2278":1,"2353":1,"2383":1,"2410":1,"2442":1,"2482":1,"2509":1,"2549":1,"2550":1,"2554":2,"2555":3,"2565":1,"2567":1,"2572":1,"2576":1,"2580":1,"2581":1,"2585":1,"2588":1,"2594":1,"2600":1,"2608":1,"2614":2,"2615":2,"2625":1,"2626":1,"2627":1,"2628":1,"2629":1,"2632":2,"2633":1,"2634":1,"2635":1,"2642":1,"2649":1,"2650":1,"2654":1,"2659":1,"2666":2,"2667":1,"2678":1,"2679":1,"2779":1,"2835":1}}],["addhealthchecks",{"2":{"869":1}}],["addratelimiter",{"2":{"869":1,"1792":1,"1822":1}}],["addressing",{"0":{"994":1}}],["addresses",{"2":{"918":1,"1107":1,"1225":1,"1328":1,"1703":1,"1707":2,"1792":3,"1875":1,"2346":1,"2372":1,"2633":1}}],["addressed",{"2":{"869":1}}],["address",{"0":{"735":1},"2":{"332":7,"436":1,"669":1,"735":2,"738":2,"799":3,"802":2,"819":1,"1239":1,"1241":1,"1469":2,"1475":3,"1477":3,"1483":2,"1539":2,"1540":2,"1543":2,"1544":2,"1546":1,"1547":3,"1548":2,"1569":1,"1687":1,"1701":1,"1759":1,"1792":15,"1890":1,"1912":1,"1923":1,"1924":1,"1973":6,"2010":7,"2142":1,"2146":1,"2184":1,"2217":1,"2346":1,"2509":1,"2520":1,"2549":1,"2575":1,"2587":8,"2590":1,"2633":1,"2869":1}}],["addauthentication",{"2":{"869":1,"2422":1}}],["additively",{"2":{"2430":1}}],["additive",{"0":{"2461":1},"2":{"1150":1,"1924":1,"2509":1,"2545":1}}],["additions",{"2":{"868":1,"872":2}}],["addition",{"2":{"868":1,"1017":1,"1385":1,"1398":1,"2317":1}}],["additional",{"0":{"739":1,"1458":1,"1971":1,"1994":1,"2375":1,"2622":1},"1":{"1459":1,"1460":1},"2":{"816":1,"914":1,"919":1,"948":1,"1027":2,"1068":1,"1086":1,"1098":2,"1105":1,"1108":3,"1111":1,"1127":1,"1158":1,"1161":1,"1165":1,"1169":1,"1181":1,"1190":1,"1192":1,"1220":1,"1279":1,"1322":2,"1325":1,"1385":1,"1393":1,"1401":1,"1458":2,"1460":2,"1511":1,"1542":1,"1546":1,"1630":1,"1659":1,"1660":1,"1792":8,"1870":1,"1898":1,"1971":1,"1974":1,"2227":1,"2254":1,"2256":1,"2265":1,"2375":3,"2534":1,"2607":1,"2648":1,"2684":1,"2722":1,"2779":1,"2829":1}}],["additionally",{"2":{"453":1,"2463":1,"2532":1,"2535":1,"2880":1}}],["adding",{"0":{"2143":1},"1":{"2144":1,"2145":1},"2":{"62":1,"336":1,"868":1,"869":1,"871":3,"878":1,"879":1,"1013":1,"1164":1,"1193":1,"1218":1,"1226":2,"1237":1,"1302":2,"1304":1,"1351":1,"1792":6,"1876":2,"2369":1,"2806":1,"2824":1}}],["add",{"0":{"957":1,"1221":1,"1358":1,"1726":1,"1871":1},"2":{"236":1,"297":1,"439":1,"689":1,"826":1,"845":1,"852":1,"855":1,"864":2,"871":1,"873":1,"915":1,"946":1,"971":1,"1012":1,"1049":1,"1063":2,"1065":2,"1073":2,"1086":1,"1098":1,"1106":2,"1111":1,"1125":2,"1139":1,"1191":1,"1192":1,"1193":4,"1203":1,"1206":1,"1220":2,"1221":8,"1226":2,"1232":3,"1235":1,"1237":1,"1252":1,"1253":1,"1254":1,"1366":2,"1367":2,"1377":1,"1393":1,"1402":1,"1405":1,"1415":2,"1437":1,"1440":1,"1557":2,"1582":1,"1711":1,"1719":1,"1726":1,"1755":2,"1792":18,"1825":1,"1870":2,"1871":3,"1876":2,"1882":1,"1893":2,"1900":1,"1953":1,"1980":1,"1994":1,"2143":1,"2156":1,"2247":1,"2254":1,"2264":1,"2338":1,"2389":1,"2392":1,"2420":1,"2423":2,"2542":1,"2543":1,"2642":1,"2721":1,"2733":1,"2734":1,"2765":1,"2781":1,"2806":1,"2821":1,"2823":1,"2824":1,"2829":1,"2835":1,"2847":1}}],["addserverheader",{"2":{"1792":1,"1994":2}}],["adds",{"2":{"213":1,"453":1,"869":1,"1032":1,"1037":1,"1135":1,"1174":1,"1193":3,"1241":1,"1254":1,"1270":1,"1350":1,"1493":1,"1739":1,"1792":8,"1890":1,"2014":1,"2110":1,"2287":1,"2309":1,"2430":1,"2479":1,"2523":1,"2529":1,"2530":1,"2632":2,"2795":1}}],["auckland",{"2":{"2453":1}}],["aud",{"2":{"1454":1,"1792":2,"1830":1,"2481":1}}],["audience",{"0":{"1830":1},"2":{"836":1,"837":1,"1454":2,"1792":4,"1814":1,"1825":3,"1830":2,"1911":1,"2223":1,"2434":1,"2438":2,"2481":3,"2498":1,"2554":2,"2833":1}}],["audittimestamp",{"2":{"1193":1}}],["auditedby",{"2":{"1193":1}}],["audited",{"2":{"1191":1,"1193":1}}],["audits",{"2":{"943":1,"1181":1}}],["auditable",{"0":{"943":1}}],["audit",{"2":{"41":1,"292":1,"902":1,"905":1,"945":2,"948":1,"1056":1,"1064":1,"1183":1,"1184":1,"1185":1,"1188":1,"1191":3,"1204":2,"1207":1,"1239":1,"1244":1,"1253":1,"1351":1,"1382":1,"1437":1,"1792":1,"2438":1,"2486":1,"2492":1}}],["augment",{"2":{"1035":1}}],["austen",{"2":{"913":2,"916":3,"917":2,"918":3}}],["autocompletion",{"2":{"2754":1}}],["autocomplete",{"2":{"2670":1}}],["autocommit",{"2":{"2110":1,"2528":1,"2530":1,"2863":1}}],["autoreplenishment",{"2":{"1792":5,"1951":2,"1952":2,"1953":2,"1959":1,"1960":4,"2257":3,"2443":3,"2471":1}}],["autonomously",{"2":{"1038":1}}],["autogenerated",{"2":{"872":2,"995":1,"1408":1,"1416":1,"1417":1,"1553":1,"1565":1,"1570":1,"1574":1,"1581":1,"1792":2,"2164":1}}],["automated",{"2":{"845":3,"1009":1,"1393":2,"1405":2,"2465":2,"2627":1}}],["automatic",{"0":{"1002":1,"1040":1,"1052":1,"1759":1,"1912":1,"1923":1,"2520":1},"1":{"1041":1,"1924":1,"1925":1,"1926":1},"2":{"213":1,"317":1,"384":1,"436":2,"777":1,"868":2,"901":1,"908":1,"909":1,"918":1,"968":1,"972":1,"973":1,"1009":1,"1032":1,"1037":3,"1096":1,"1101":1,"1180":1,"1354":1,"1365":1,"1405":3,"1431":1,"1489":1,"1540":1,"1544":1,"1569":3,"1622":1,"1623":2,"1739":1,"1753":1,"1759":2,"1792":6,"1898":1,"1912":2,"1917":1,"1923":1,"1924":3,"1925":1,"2164":2,"2165":1,"2222":1,"2266":1,"2270":1,"2287":1,"2350":1,"2481":1,"2508":1,"2509":1,"2510":1,"2511":1,"2520":2,"2585":1,"2586":1,"2588":1,"2673":1,"2689":1,"2729":1,"2772":1,"2812":1}}],["automatically",{"2":{"168":1,"202":1,"335":1,"337":1,"364":1,"376":1,"380":1,"383":1,"454":1,"575":1,"624":1,"747":1,"801":1,"835":2,"837":1,"868":1,"894":1,"904":2,"914":1,"920":1,"936":2,"937":1,"938":1,"975":2,"988":1,"995":1,"1008":1,"1010":1,"1016":2,"1024":1,"1027":1,"1049":1,"1052":2,"1055":2,"1063":1,"1067":1,"1094":1,"1096":1,"1097":2,"1098":1,"1105":1,"1129":1,"1148":1,"1173":1,"1188":1,"1190":1,"1192":1,"1203":2,"1241":1,"1304":1,"1308":1,"1309":1,"1323":1,"1354":1,"1365":1,"1366":2,"1370":1,"1382":1,"1385":1,"1386":1,"1398":1,"1410":1,"1416":1,"1423":1,"1435":1,"1460":1,"1475":2,"1477":2,"1493":1,"1516":1,"1518":1,"1613":1,"1627":1,"1703":1,"1712":1,"1723":1,"1732":1,"1733":1,"1742":1,"1792":12,"1802":1,"1813":1,"1890":1,"1951":1,"1952":1,"1953":1,"1982":1,"2004":1,"2156":1,"2253":1,"2255":1,"2264":3,"2265":3,"2290":1,"2291":1,"2297":1,"2317":1,"2333":1,"2342":1,"2375":1,"2391":1,"2527":1,"2535":1,"2537":1,"2586":1,"2632":1,"2649":1,"2664":1,"2687":1,"2709":1,"2763":1,"2772":2,"2774":1,"2786":1,"2812":1,"2821":1,"2823":1,"2861":1,"2862":1,"2879":1,"2880":1,"2881":1}}],["auto",{"0":{"1317":1,"2389":1,"2395":1,"2517":1},"2":{"165":1,"168":1,"175":3,"179":2,"376":1,"383":1,"409":1,"436":1,"529":1,"535":1,"565":1,"684":2,"867":2,"868":1,"876":1,"878":1,"880":1,"894":1,"938":1,"961":1,"968":1,"976":1,"1024":1,"1027":2,"1049":1,"1096":3,"1121":1,"1125":1,"1126":1,"1225":2,"1304":1,"1317":2,"1322":1,"1323":1,"1342":2,"1350":1,"1366":1,"1372":1,"1381":1,"1385":1,"1403":1,"1405":1,"1470":1,"1569":1,"1684":1,"1792":4,"1875":1,"1923":1,"1924":1,"1925":1,"2226":1,"2265":1,"2284":1,"2319":2,"2322":1,"2340":1,"2346":1,"2348":1,"2351":1,"2372":1,"2388":1,"2389":2,"2395":3,"2407":1,"2509":1,"2510":1,"2515":1,"2546":1,"2648":1,"2814":1,"2841":1,"2851":1}}],["authintervalnotationtests",{"2":{"2435":1}}],["authlegacyfieldfailfasttests",{"2":{"2435":1}}],["authschemelogintests",{"2":{"2435":1}}],["authschemeregistrationtests",{"2":{"2435":1}}],["authcookiesamesitesecuretests",{"2":{"2435":1}}],["authpolicyschemetests",{"2":{"2435":1}}],["authtests",{"2":{"2435":2,"2472":1}}],["authtoken",{"2":{"1063":2}}],["authurl",{"2":{"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1792":5}}],["authentic",{"2":{"1404":1}}],["authenticators",{"2":{"1227":1,"1792":2,"1877":1}}],["authenticatordata",{"2":{"1222":1,"1792":1}}],["authenticator",{"2":{"1217":1,"1225":1,"1229":1,"1230":2,"1232":2,"1792":9,"1875":1,"1880":2,"1882":2}}],["authenticating",{"2":{"453":1,"1050":1,"1222":1,"1872":1}}],["authenticationtype",{"2":{"2423":1}}],["authenticationschemes",{"2":{"2424":1,"2437":1,"2438":1}}],["authenticationscheme",{"2":{"1447":1,"1451":1,"1454":1}}],["authenticationoptionspath",{"2":{"1792":1}}],["authenticationoptions",{"2":{"33":1,"300":1,"305":2,"309":2,"310":1,"937":1,"1052":1,"1056":1,"1058":1,"1062":1,"1196":1,"1197":1,"1199":1,"1469":1,"1482":1,"1483":1,"1498":1,"1502":1,"1503":1,"1505":1,"1539":1,"1548":1,"1792":2,"1836":2,"2181":1,"2183":1,"2184":1,"2187":1,"2701":1}}],["authentication",{"0":{"34":1,"35":1,"225":1,"905":1,"921":1,"931":1,"1048":1,"1053":1,"1098":1,"1194":1,"1197":1,"1216":1,"1219":1,"1348":1,"1371":1,"1444":1,"1446":1,"1450":1,"1453":1,"1458":1,"1462":1,"1463":1,"1464":1,"1468":1,"1482":1,"1503":1,"1682":1,"1825":1,"1866":1,"1869":1,"1902":1,"1903":1,"1904":1,"2058":1,"2170":1,"2172":1,"2199":1,"2375":1,"2554":1,"2735":1,"2736":1,"2737":1},"1":{"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":2,"933":2,"934":2,"935":2,"936":2,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":2,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1195":1,"1196":1,"1197":1,"1220":1,"1221":1,"1222":1,"1445":1,"1446":1,"1447":2,"1448":2,"1449":2,"1450":1,"1451":2,"1452":2,"1453":1,"1454":2,"1455":2,"1456":2,"1457":2,"1458":1,"1459":2,"1460":2,"1461":1,"1462":1,"1463":1,"1464":1,"1465":1,"1466":1,"1467":1,"1469":1,"1470":1,"1471":1,"1472":1,"1473":1,"1474":1,"1475":1,"1476":1,"1477":1,"1478":1,"1479":1,"1480":1,"1481":1,"1482":1,"1483":1,"1484":1,"1485":1,"1486":1,"1504":1,"1683":1,"1684":1,"1685":1,"1686":1,"1687":1,"1688":1,"1689":1,"1690":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1696":1,"1697":1,"1698":1,"1699":1,"1700":1,"1826":1,"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"1833":1,"1867":1,"1868":1,"1869":1,"1870":2,"1871":2,"1872":2,"1873":1,"1874":1,"1875":1,"1876":1,"1877":1,"1878":1,"1879":1,"1880":1,"1881":1,"1882":1,"1883":1,"1884":1,"1885":1,"1886":1,"1887":1,"1888":1,"1889":1,"1890":1,"1891":1,"1892":1,"1893":1,"1894":1,"1895":1,"2171":1,"2172":1,"2173":2,"2174":2,"2175":2,"2176":1,"2177":1,"2178":1,"2179":1,"2180":1,"2181":1,"2182":1,"2183":1,"2184":1,"2185":1,"2186":1,"2187":1,"2188":1,"2189":1,"2736":1,"2737":1},"2":{"10":1,"11":3,"12":1,"13":1,"25":1,"26":5,"27":1,"28":4,"29":2,"33":2,"34":1,"41":3,"42":1,"43":2,"44":2,"45":1,"48":1,"51":2,"53":1,"54":2,"55":2,"56":1,"63":2,"64":1,"65":2,"66":2,"67":1,"209":1,"224":1,"225":2,"260":1,"285":1,"286":2,"288":1,"289":1,"293":4,"294":1,"295":4,"296":1,"300":1,"302":1,"303":1,"306":1,"310":1,"315":3,"364":1,"367":1,"368":2,"456":1,"482":1,"597":1,"691":1,"710":1,"741":1,"801":1,"805":1,"821":1,"832":1,"868":1,"879":1,"880":1,"907":1,"909":1,"921":2,"934":4,"935":2,"936":1,"937":2,"938":1,"957":1,"1026":1,"1037":4,"1048":5,"1053":2,"1054":5,"1055":1,"1060":2,"1061":1,"1063":2,"1064":4,"1065":2,"1068":1,"1086":2,"1088":1,"1094":1,"1098":11,"1102":1,"1105":1,"1113":2,"1118":1,"1127":2,"1148":1,"1185":1,"1189":1,"1192":1,"1197":2,"1198":1,"1209":4,"1211":1,"1213":2,"1216":3,"1217":3,"1221":1,"1222":2,"1224":1,"1226":1,"1234":2,"1235":1,"1239":5,"1244":1,"1247":1,"1248":1,"1249":1,"1252":1,"1303":1,"1304":1,"1320":2,"1377":1,"1403":1,"1444":1,"1445":10,"1446":2,"1447":3,"1450":2,"1451":2,"1453":4,"1454":2,"1455":1,"1458":2,"1464":1,"1465":4,"1466":3,"1467":1,"1468":1,"1470":1,"1471":1,"1473":1,"1480":2,"1482":1,"1484":2,"1485":3,"1486":1,"1496":2,"1497":1,"1499":3,"1503":1,"1505":1,"1506":4,"1507":3,"1518":1,"1547":1,"1549":4,"1550":4,"1635":2,"1648":2,"1649":1,"1651":1,"1653":1,"1666":2,"1682":1,"1686":1,"1699":4,"1700":3,"1788":7,"1792":62,"1795":4,"1825":5,"1836":1,"1866":2,"1867":1,"1868":1,"1871":1,"1874":1,"1876":1,"1885":1,"1888":1,"1893":1,"1894":3,"1895":2,"1898":1,"1901":3,"1902":1,"1903":1,"1904":1,"1907":2,"1933":2,"1977":2,"1997":2,"2031":2,"2034":1,"2035":1,"2044":2,"2047":1,"2070":2,"2151":2,"2164":3,"2165":1,"2170":4,"2171":1,"2172":1,"2175":2,"2181":1,"2189":4,"2194":1,"2219":2,"2227":1,"2238":1,"2254":3,"2265":1,"2291":1,"2353":1,"2363":1,"2375":1,"2386":1,"2421":1,"2437":1,"2438":1,"2481":3,"2492":1,"2527":1,"2554":5,"2559":1,"2567":1,"2625":3,"2628":1,"2634":1,"2635":1,"2706":2,"2736":1,"2836":1,"2838":1,"2856":1,"2862":1}}],["authenticatepath",{"2":{"1792":3}}],["authenticatetoken",{"2":{"1026":2}}],["authenticates",{"2":{"453":1,"1222":1,"1792":2,"2423":1}}],["authenticate",{"0":{"2420":1},"1":{"2421":1,"2422":1,"2423":1,"2424":1},"2":{"45":1,"48":1,"51":1,"63":1,"593":2,"1045":1,"1059":1,"1098":1,"1216":1,"1217":2,"1222":2,"1236":1,"1239":1,"1244":1,"1792":7,"1825":1,"1827":1,"1832":1,"1833":1,"1893":2,"2225":1,"2419":1,"2420":1,"2421":1,"2423":1,"2481":1,"2625":1,"2634":1}}],["authenticatedatacommand",{"0":{"1236":1,"1886":1},"2":{"1217":1,"1239":1,"1792":2,"1888":1,"1893":1}}],["authenticated",{"0":{"16":1,"453":1,"2733":1},"2":{"9":1,"41":1,"43":1,"63":1,"66":1,"165":1,"168":2,"320":1,"376":2,"380":1,"453":1,"454":1,"478":3,"479":1,"529":1,"664":1,"690":1,"737":1,"801":1,"905":1,"936":1,"1057":1,"1069":1,"1074":2,"1094":1,"1098":1,"1101":1,"1105":2,"1138":1,"1140":1,"1162":2,"1163":1,"1185":1,"1188":1,"1189":1,"1208":1,"1232":1,"1309":2,"1312":1,"1348":1,"1371":1,"1372":1,"1475":3,"1477":3,"1538":1,"1540":1,"1544":1,"1547":1,"1620":1,"1792":18,"1824":1,"1825":3,"1827":1,"1830":1,"1871":1,"1882":2,"1911":2,"1928":1,"1955":1,"2037":1,"2047":1,"2058":1,"2125":1,"2183":1,"2199":1,"2284":1,"2333":1,"2379":2,"2394":1,"2430":1,"2434":2,"2436":1,"2481":1,"2490":1,"2498":2,"2526":1,"2529":1,"2549":1,"2572":1,"2634":1,"2635":1,"2733":1,"2739":1,"2812":1,"2817":1,"2829":1,"2831":1,"2860":1}}],["auth0",{"2":{"1045":1,"1825":1}}],["auth",{"0":{"29":1,"44":1,"55":1,"60":1,"61":1,"1064":1,"1066":1,"1183":1,"1373":1,"1497":1,"2363":1,"2375":1,"2376":1,"2377":1,"2410":1,"2427":1},"1":{"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":1,"52":1,"53":1,"54":1,"56":1,"57":1,"58":1,"59":1,"60":1,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"1065":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1498":1,"1499":1,"1500":1,"1501":1,"1502":1,"1503":1,"1504":1,"1505":1,"1506":1,"1507":1,"1508":1,"2411":1,"2412":1,"2413":1},"2":{"11":1,"26":1,"30":1,"37":5,"38":9,"39":4,"40":7,"43":2,"45":1,"48":2,"49":2,"50":1,"53":1,"54":2,"56":2,"58":2,"60":4,"61":7,"62":9,"65":2,"66":2,"67":1,"209":8,"225":3,"279":1,"289":1,"290":1,"291":3,"293":1,"296":1,"298":3,"302":1,"305":1,"310":1,"313":1,"365":1,"366":1,"368":1,"709":2,"710":3,"724":1,"803":1,"834":1,"835":2,"836":1,"837":1,"867":1,"869":1,"872":1,"883":1,"910":1,"921":1,"937":5,"1026":1,"1037":4,"1050":1,"1053":2,"1054":3,"1059":1,"1062":1,"1063":1,"1066":1,"1067":1,"1068":3,"1071":1,"1081":1,"1083":1,"1086":1,"1098":9,"1114":1,"1121":2,"1125":1,"1126":1,"1127":3,"1183":2,"1184":3,"1185":2,"1188":1,"1189":2,"1195":1,"1196":2,"1197":5,"1199":3,"1202":1,"1207":2,"1208":1,"1217":2,"1221":1,"1226":6,"1239":1,"1247":1,"1304":1,"1320":2,"1322":1,"1323":1,"1327":1,"1368":1,"1371":2,"1373":3,"1385":1,"1386":1,"1407":1,"1445":1,"1446":1,"1449":2,"1450":1,"1453":1,"1457":1,"1458":2,"1459":1,"1462":1,"1463":1,"1464":10,"1470":2,"1482":1,"1483":2,"1484":1,"1485":1,"1486":2,"1500":1,"1501":3,"1503":1,"1504":3,"1505":1,"1506":3,"1508":4,"1549":1,"1550":1,"1605":2,"1683":1,"1684":4,"1685":1,"1690":1,"1691":1,"1697":3,"1698":1,"1788":1,"1792":30,"1802":1,"1825":3,"1867":1,"1871":1,"1876":2,"1889":1,"1890":1,"1892":1,"1893":1,"1906":1,"2097":1,"2164":5,"2165":5,"2167":1,"2171":2,"2172":1,"2173":2,"2174":1,"2175":3,"2183":1,"2184":1,"2187":2,"2188":2,"2225":4,"2227":1,"2254":4,"2353":2,"2363":1,"2375":3,"2376":9,"2377":2,"2389":1,"2394":1,"2409":1,"2410":4,"2412":1,"2419":3,"2420":1,"2421":1,"2422":2,"2423":1,"2425":1,"2426":1,"2427":3,"2428":1,"2429":2,"2435":3,"2436":4,"2438":3,"2440":1,"2481":2,"2486":1,"2496":2,"2497":2,"2545":1,"2551":1,"2554":1,"2558":1,"2591":1,"2615":1,"2679":1,"2687":1,"2688":2,"2690":2,"2701":1,"2728":1,"2736":2,"2737":2,"2739":1,"2795":1,"2806":1,"2809":1,"2812":1,"2836":1}}],["authorname",{"2":{"2586":2,"2590":1}}],["authored",{"2":{"1382":1,"1400":1,"2496":1}}],["authorauthorid",{"2":{"915":3,"920":2}}],["authorlastname",{"2":{"915":3,"920":2,"1097":1}}],["authorfirstname",{"2":{"915":4,"920":2,"1097":1}}],["authors",{"2":{"847":1,"913":5,"914":5,"915":6,"916":20,"917":3,"918":12,"919":1,"1375":2,"2586":1}}],["authorship",{"2":{"1":1}}],["authority",{"2":{"852":3}}],["authoritative",{"2":{"318":1,"319":1,"326":1,"843":1,"844":2,"852":1,"978":1,"1040":1,"2324":1,"2481":1}}],["authorid=1",{"2":{"914":2}}],["authorid",{"2":{"370":2,"914":1,"915":1,"916":6,"917":4,"918":6,"919":2,"920":13,"1375":2,"2586":4,"2590":2}}],["authorizationservers",{"0":{"1828":1},"2":{"1792":2,"1814":1,"1825":1,"2481":1}}],["authorization",{"0":{"58":1,"224":1,"478":1,"815":1,"1045":1,"1826":1,"1843":1,"2035":1,"2198":1},"1":{"1827":1,"1828":1,"1829":1,"1830":1,"1831":1,"1832":1,"2036":1,"2199":1,"2200":1,"2201":1},"2":{"13":1,"17":1,"31":2,"38":1,"40":1,"58":2,"61":1,"62":2,"63":2,"66":1,"207":1,"208":1,"209":1,"211":1,"212":2,"215":2,"390":1,"394":1,"531":1,"533":1,"641":1,"648":1,"663":1,"669":1,"690":1,"807":1,"868":1,"905":1,"1017":2,"1030":1,"1033":2,"1037":1,"1045":2,"1047":1,"1057":1,"1063":4,"1064":1,"1074":1,"1086":1,"1094":2,"1098":2,"1105":1,"1115":1,"1221":1,"1385":1,"1482":1,"1497":1,"1518":1,"1639":1,"1643":1,"1644":1,"1646":1,"1692":1,"1696":1,"1726":1,"1730":1,"1731":1,"1733":2,"1738":3,"1742":1,"1792":13,"1814":1,"1825":7,"1826":1,"1827":1,"1828":1,"1832":1,"1833":3,"1843":1,"1898":1,"1910":1,"1931":1,"2032":1,"2034":1,"2137":1,"2166":1,"2167":1,"2174":1,"2188":1,"2198":1,"2223":1,"2254":1,"2264":3,"2265":1,"2267":2,"2271":1,"2282":1,"2283":2,"2286":1,"2290":1,"2314":1,"2329":1,"2363":1,"2366":1,"2481":10,"2483":1,"2486":1,"2490":1,"2498":3,"2527":1,"2529":1,"2545":1,"2575":1,"2638":1,"2672":1,"2699":1,"2768":3,"2774":1,"2775":1,"2823":1,"2824":2,"2860":1}}],["authorizepaths",{"2":{"1792":1,"2033":1,"2034":1,"2035":1,"2042":1,"2371":1}}],["authorizedroles",{"2":{"967":2,"1792":1,"2046":1,"2047":1,"2059":1,"2067":1,"2068":1,"2635":1}}],["authorized",{"2":{"13":1,"17":1,"638":1,"641":1,"689":1,"1305":1,"1309":1,"2828":1,"2829":1,"2836":1}}],["authorize",{"0":{"13":1,"19":1,"20":1,"23":1,"24":1,"642":1,"643":1,"1312":1,"1313":1,"2314":1},"1":{"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1},"2":{"9":1,"12":1,"14":3,"16":2,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"66":1,"137":1,"168":2,"224":1,"260":1,"288":2,"289":1,"290":1,"291":1,"292":1,"294":1,"305":3,"314":2,"316":1,"320":1,"327":1,"347":1,"351":2,"352":2,"353":1,"354":1,"356":1,"376":1,"380":1,"453":1,"454":1,"456":1,"478":1,"479":1,"482":1,"543":1,"544":1,"592":2,"594":1,"597":1,"637":2,"638":1,"642":1,"643":1,"644":1,"646":2,"648":1,"650":1,"664":6,"665":5,"666":1,"690":2,"692":1,"732":1,"733":2,"734":1,"735":1,"736":1,"741":1,"792":1,"797":2,"799":1,"800":1,"805":1,"815":1,"821":1,"835":1,"868":1,"873":1,"904":1,"935":1,"936":1,"957":2,"1021":1,"1045":2,"1057":6,"1065":1,"1068":1,"1069":1,"1073":2,"1077":1,"1098":1,"1103":1,"1113":1,"1125":1,"1138":1,"1150":1,"1163":2,"1179":3,"1305":1,"1309":4,"1310":1,"1312":1,"1313":2,"1316":1,"1321":2,"1332":1,"1338":1,"1339":1,"1348":1,"1366":1,"1371":3,"1372":2,"1374":1,"1376":1,"1401":1,"1406":1,"1413":1,"1465":1,"1467":1,"1486":1,"1547":2,"1567":2,"1693":1,"1694":1,"1697":1,"1699":1,"1792":3,"1824":1,"1825":1,"1827":1,"1832":1,"1834":1,"1924":1,"1926":1,"1961":1,"2006":1,"2012":1,"2171":1,"2181":3,"2183":1,"2184":1,"2185":1,"2186":1,"2187":3,"2189":1,"2193":3,"2194":2,"2199":3,"2200":3,"2207":1,"2214":1,"2216":1,"2223":1,"2229":1,"2247":1,"2250":1,"2252":4,"2314":9,"2319":1,"2322":1,"2323":1,"2329":1,"2333":1,"2366":1,"2391":1,"2420":1,"2423":2,"2424":1,"2432":2,"2437":2,"2438":2,"2481":3,"2490":4,"2498":1,"2529":1,"2540":1,"2549":2,"2581":4,"2728":3,"2733":1,"2766":1,"2767":1,"2768":1,"2774":1,"2775":1,"2795":1,"2797":1,"2810":1,"2812":1,"2815":1,"2829":5,"2831":4,"2833":3,"2834":10,"2836":4,"2838":1,"2845":1,"2856":1}}],["author",{"0":{"2":1},"2":{"335":2,"370":2,"913":5,"914":20,"915":18,"916":25,"917":4,"918":19,"919":2,"920":6,"1096":2,"1375":6,"1383":1,"1421":1,"1442":1,"2540":1,"2586":5,"2845":1}}],["acquire",{"2":{"1624":1}}],["acquires",{"2":{"1324":1}}],["acquisition",{"0":{"1596":1},"2":{"1792":1}}],["ac",{"2":{"1338":5}}],["achieved",{"2":{"1386":1}}],["achieves",{"2":{"1007":1,"1262":1,"1263":1,"1269":1,"1278":1}}],["achieve",{"2":{"918":1,"1037":1,"1385":1,"1391":2}}],["acronym",{"2":{"864":1}}],["across",{"0":{"1414":1},"2":{"156":1,"214":1,"384":1,"544":1,"615":1,"663":1,"761":1,"771":1,"848":1,"860":1,"861":1,"868":1,"869":1,"871":2,"872":4,"873":2,"874":1,"876":1,"915":1,"948":1,"956":1,"974":1,"1013":1,"1036":1,"1054":2,"1067":1,"1069":1,"1146":1,"1147":1,"1162":1,"1172":1,"1175":1,"1178":1,"1180":1,"1181":2,"1187":1,"1190":1,"1193":1,"1209":1,"1254":1,"1266":1,"1302":1,"1324":1,"1382":1,"1398":1,"1409":1,"1414":1,"1416":1,"1440":1,"1449":1,"1460":2,"1515":2,"1567":1,"1574":1,"1581":1,"1654":1,"1743":1,"1746":1,"1792":7,"1823":1,"1867":1,"2221":1,"2228":1,"2274":3,"2314":1,"2321":2,"2339":1,"2347":1,"2375":2,"2380":1,"2400":1,"2419":1,"2429":1,"2435":1,"2453":1,"2456":1,"2466":1,"2495":1,"2498":4,"2502":1,"2534":1,"2540":1,"2546":1,"2607":1,"2614":1,"2695":1,"2710":1,"2731":1,"2845":1}}],["acid",{"2":{"851":1,"852":1}}],["acknowledged",{"2":{"1824":1}}],["acknowledge",{"2":{"845":1,"847":1,"876":1}}],["acme",{"2":{"333":4,"1038":1,"1044":1,"1189":1,"1192":1,"1792":1,"1823":3,"1911":2,"2166":1,"2434":2,"2479":1}}],["acts",{"2":{"1045":1,"1825":1,"2297":1}}],["act",{"2":{"691":1,"864":1,"865":1,"1074":1,"1076":1,"1385":1,"1393":1,"2419":1,"2528":1,"2529":1,"2860":1,"2864":1,"2865":1}}],["actix",{"2":{"1090":1,"1255":1}}],["activated",{"2":{"1792":2,"2075":1,"2077":1}}],["activate",{"2":{"1792":2,"2380":1}}],["activities",{"2":{"848":1}}],["activityquery",{"2":{"2638":1}}],["activitypath",{"2":{"1792":1,"2046":1,"2047":1,"2064":1,"2635":1}}],["activity",{"0":{"2052":2},"2":{"320":1,"351":1,"868":1,"869":1,"966":2,"1100":1,"1619":1,"1620":1,"1792":4,"2045":1,"2046":1,"2047":2,"2052":2,"2635":7}}],["activereviews",{"2":{"916":2,"917":2,"920":1}}],["active=true",{"2":{"520":1,"1368":1,"2012":1}}],["active",{"0":{"2362":1},"2":{"297":1,"303":1,"373":2,"378":1,"467":1,"520":5,"584":2,"868":1,"916":1,"966":1,"977":2,"979":2,"980":2,"982":1,"986":1,"988":2,"989":1,"990":3,"994":1,"995":2,"996":3,"1101":1,"1138":2,"1179":1,"1368":8,"1369":2,"1386":10,"1387":3,"1390":1,"1391":2,"1393":2,"1396":1,"1408":3,"1792":2,"2012":2,"2052":1,"2059":1,"2194":1,"2333":1,"2342":4,"2362":1,"2635":3,"2732":1,"2842":2,"2847":1,"2848":1}}],["acting",{"2":{"239":1,"689":1,"851":3,"1005":1,"1825":1,"2529":1,"2865":1}}],["action=",{"2":{"1491":1}}],["actions",{"2":{"865":1,"938":1,"1061":1,"1181":1,"2102":1,"2576":1,"2880":1}}],["action",{"0":{"1409":1},"2":{"21":1,"292":1,"326":1,"348":1,"355":1,"523":3,"1413":1,"1523":1,"1572":1,"1774":1,"2495":1,"2834":2}}],["actualname",{"2":{"1523":1,"2372":1,"2522":1}}],["actually",{"0":{"2420":1},"1":{"2421":1,"2422":1,"2423":1,"2424":1},"2":{"388":1,"636":1,"650":1,"664":1,"860":2,"864":4,"865":1,"866":1,"868":1,"869":1,"872":2,"873":1,"874":1,"932":1,"982":1,"994":1,"1009":1,"1037":2,"1064":1,"1077":1,"1081":1,"1141":1,"1185":1,"1210":1,"1324":1,"1358":1,"1384":1,"1385":1,"1401":1,"1403":1,"1405":1,"1410":1,"1421":1,"1435":1,"1443":1,"1522":1,"1566":1,"1922":1,"2224":1,"2225":1,"2389":1,"2392":1,"2395":1,"2419":1,"2421":1,"2459":1,"2466":1,"2537":1,"2763":1,"2869":1}}],["actual",{"2":{"74":1,"156":1,"388":1,"429":1,"436":1,"528":2,"587":1,"772":2,"845":2,"848":1,"857":1,"860":1,"872":1,"874":1,"884":1,"893":1,"930":1,"1005":1,"1044":1,"1096":1,"1169":1,"1393":1,"1403":1,"1575":1,"1671":1,"1792":1,"1802":1,"2222":1,"2255":1,"2264":1,"2283":1,"2310":1,"2313":1,"2356":1,"2412":1,"2506":1,"2518":1,"2519":1,"2641":1,"2795":1,"2840":1}}],["accidental",{"2":{"2040":1,"2476":1,"2841":1}}],["accidentally",{"2":{"1408":1}}],["accident",{"2":{"1040":1,"2454":1}}],["accross",{"2":{"914":1}}],["accordingly",{"2":{"917":1,"918":1,"919":1,"2252":1}}],["according",{"2":{"801":1,"841":1,"918":1}}],["accounting",{"2":{"2666":1}}],["accountability",{"2":{"1185":1}}],["accounts",{"2":{"20":1,"622":4,"1218":1,"1220":3,"1253":1,"1691":1,"1792":2,"1870":3}}],["account",{"2":{"20":2,"107":1,"310":1,"320":3,"622":4,"1056":1,"1064":1,"1220":2,"1221":1,"1229":1,"1232":1,"1233":1,"1249":1,"1662":1,"1792":4,"1870":2,"1871":1,"1879":1,"2314":1,"2422":1}}],["accumulation",{"2":{"880":1,"882":1,"885":1}}],["accumulating",{"2":{"761":1,"771":1}}],["accumulated",{"2":{"919":1}}],["accumulate",{"2":{"709":1,"860":1,"1007":1,"2533":1}}],["accurately",{"2":{"1401":1,"2558":1}}],["accurate",{"2":{"3":1,"1254":1,"1386":1,"1401":1}}],["accuracy",{"2":{"1":1}}],["accelerated",{"0":{"2270":1},"2":{"2239":1,"2270":2}}],["accelerates",{"2":{"2159":1}}],["accel",{"2":{"1859":2,"2835":2}}],["accepting",{"2":{"2648":1}}],["acceptance",{"2":{"2447":1}}],["acceptable",{"2":{"1160":1,"1628":1,"1792":1,"2266":1,"2496":1}}],["accepted",{"2":{"690":1,"701":1,"1707":1,"1708":1,"1709":1,"1792":3,"2419":1,"2427":1,"2544":1,"2633":1}}],["accept",{"0":{"2496":1},"2":{"160":1,"207":1,"267":1,"386":1,"393":1,"394":1,"747":2,"780":1,"865":1,"886":1,"888":1,"904":1,"1017":2,"1019":2,"1026":2,"1032":1,"1063":1,"1138":1,"1174":2,"1230":1,"1398":1,"1403":1,"1426":1,"1430":1,"1431":1,"1464":1,"1628":2,"1703":1,"1717":1,"1730":1,"1736":1,"1767":1,"1792":5,"1880":1,"1940":1,"1941":1,"2183":1,"2210":1,"2221":1,"2223":1,"2266":2,"2376":1,"2423":2,"2437":1,"2438":1,"2532":1,"2537":1,"2615":1,"2633":1,"2634":1,"2649":1,"2674":1,"2762":3,"2764":1,"2765":1,"2766":2,"2811":1}}],["accepts",{"2":{"74":1,"102":1,"214":1,"1067":1,"1070":1,"1148":1,"1418":1,"1460":1,"1511":2,"1590":1,"1792":9,"1802":1,"1817":1,"1875":1,"2101":1,"2118":1,"2156":1,"2200":1,"2335":1,"2375":1,"2476":1,"2492":1,"2502":1,"2518":1,"2542":1,"2544":1,"2703":1,"2752":1,"2840":1}}],["accessor",{"2":{"2545":1}}],["accessory",{"2":{"1080":1}}],["accesses`",{"2":{"1342":1}}],["accessed",{"2":{"1179":1,"1185":1,"1188":1,"1336":2,"1338":4,"1339":8,"1354":1,"1448":1,"1515":1,"2274":1,"2580":1}}],["accesstoken",{"2":{"1222":1,"1455":1,"1692":1,"1792":1,"2554":1}}],["accessing",{"0":{"765":1,"2182":1},"1":{"2183":1,"2184":1,"2185":1},"2":{"306":1,"844":1,"1098":1,"2170":1}}],["accessible",{"2":{"41":1,"223":1,"261":1,"502":1,"737":1,"766":1,"1068":1,"1204":1,"1447":1,"1747":1,"1792":2,"1840":1,"1930":2,"2344":1,"2572":1,"2634":1}}],["access",{"0":{"39":1,"733":1,"734":1,"735":1,"798":1,"799":1,"856":1,"1048":1,"1057":1,"1543":1,"2059":1,"2066":1,"2069":1,"2201":1,"2728":1},"1":{"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":2,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1},"2":{"4":1,"13":1,"18":1,"19":1,"20":1,"27":1,"41":1,"73":1,"224":1,"545":3,"741":1,"765":1,"766":1,"803":1,"805":1,"848":2,"851":1,"869":2,"905":2,"922":5,"926":2,"932":1,"934":3,"940":1,"941":1,"945":1,"967":1,"996":1,"1015":1,"1037":2,"1048":2,"1057":1,"1058":1,"1064":2,"1098":1,"1125":1,"1126":1,"1147":1,"1184":1,"1185":8,"1193":3,"1204":1,"1269":1,"1351":1,"1354":1,"1385":5,"1405":1,"1454":1,"1456":1,"1465":1,"1543":3,"1618":1,"1637":1,"1693":1,"1695":1,"1792":11,"1983":1,"2021":1,"2024":1,"2045":1,"2047":1,"2055":1,"2058":1,"2059":1,"2070":1,"2125":1,"2164":1,"2188":1,"2198":1,"2207":1,"2256":2,"2314":1,"2370":1,"2377":1,"2399":1,"2554":2,"2572":1,"2608":4,"2632":1,"2635":4}}],["april",{"2":{"1066":1,"1384":1,"1406":1}}],["apache",{"2":{"879":1,"1701":1,"1792":1,"2633":2}}],["apart",{"2":{"838":1,"865":1,"1037":1,"1046":1,"2540":1,"2545":1}}],["appconfig",{"2":{"2040":1,"2476":1}}],["apprentice",{"2":{"1403":1}}],["approximate",{"2":{"2245":1}}],["approximately",{"2":{"867":1,"2792":1}}],["approvers",{"2":{"860":1}}],["approver2",{"2":{"849":2}}],["approver1",{"2":{"849":4}}],["approved",{"2":{"849":6,"860":1,"1013":1,"1792":2,"2016":1,"2020":1,"2632":2}}],["approvals",{"2":{"849":4}}],["approval",{"2":{"849":1,"854":1,"860":1,"1220":1}}],["appropriate",{"2":{"494":1,"1155":1,"1942":1,"2353":1,"2381":1,"2687":1,"2786":1}}],["appropriately",{"2":{"429":1,"2310":1,"2313":1}}],["approaches",{"0":{"945":1},"2":{"847":1,"1008":1,"1104":1,"1191":1,"1200":1,"1220":1,"1386":1,"1387":2,"1396":1}}],["approach",{"0":{"879":1,"880":1,"949":1,"999":1,"1025":1,"1046":1,"1112":1,"1246":1,"1303":1,"1304":1,"1321":1,"1366":1,"2773":1},"1":{"1000":1,"1001":1,"1002":1,"1003":1,"1004":1,"1026":1,"1113":1,"1114":1,"1115":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"2774":1,"2775":1},"2":{"1":1,"308":1,"836":1,"847":1,"859":1,"888":1,"904":1,"907":1,"911":3,"945":1,"946":1,"948":1,"975":1,"987":1,"988":1,"1006":1,"1011":1,"1036":1,"1048":1,"1049":2,"1058":1,"1073":1,"1096":1,"1098":2,"1176":1,"1181":1,"1203":1,"1205":1,"1206":1,"1211":1,"1220":1,"1280":1,"1281":1,"1385":5,"1394":1,"1399":1,"1403":2,"1405":1,"1428":1,"1432":1,"1615":1,"1870":1,"2183":1,"2184":1,"2773":1,"2820":1}}],["apparently",{"2":{"1401":1,"1402":2}}],["appendline",{"2":{"2622":2}}],["appendix",{"2":{"1381":1,"1382":3,"1792":3,"2255":4}}],["appending",{"2":{"436":1,"652":1}}],["appendmessage",{"2":{"1318":1,"1320":1,"1321":1}}],["appendchild",{"2":{"996":5}}],["appended",{"2":{"414":1,"423":3,"436":1,"446":2,"452":1,"454":1,"654":1,"1511":1,"1792":3,"1821":1,"1917":1,"1924":2,"1925":1,"2265":1,"2304":1,"2443":1,"2509":1,"2510":1,"2517":1,"2812":1}}],["append",{"2":{"414":1,"894":1,"928":1,"1366":1,"1410":1,"2614":1,"2622":5}}],["appeared",{"2":{"869":1,"2384":1,"2520":1,"2742":1}}],["appearing",{"2":{"595":1,"1374":1,"2216":1,"2322":1,"2558":1,"2849":1}}],["appears",{"2":{"215":1,"306":1,"319":1,"353":1,"527":1,"865":1,"990":1,"1360":1,"1402":1,"1569":1,"1792":6,"1898":1,"2254":3,"2389":1,"2422":1,"2431":1,"2590":1,"2634":1}}],["appear",{"0":{"2721":1,"2722":1},"2":{"168":1,"203":1,"211":1,"319":1,"354":1,"621":1,"687":1,"963":1,"990":2,"1046":1,"1360":1,"1418":1,"1792":1,"1908":1,"2005":1,"2282":1,"2481":1,"2502":1,"2662":1,"2693":1,"2765":1}}],["appstaticfilemiddleware",{"2":{"2626":1}}],["apps",{"2":{"307":1,"869":1,"1123":1,"1217":1,"1228":1,"1230":1,"1382":1,"1401":1,"1574":1,"1773":1,"1792":6,"1861":1,"1878":1,"1880":1,"2040":1,"2173":1,"2177":1,"2474":1}}],["appsettings",{"2":{"106":1,"182":1,"395":1,"470":1,"556":1,"867":2,"868":5,"873":1,"1217":1,"1417":2,"1418":2,"1420":3,"1609":1,"1792":2,"1959":1,"2012":1,"2040":1,"2171":3,"2187":1,"2208":1,"2265":3,"2274":1,"2279":1,"2291":1,"2297":1,"2377":1,"2380":1,"2389":1,"2406":1,"2419":1,"2428":1,"2431":1,"2455":1,"2471":1,"2477":1,"2486":3,"2522":1,"2545":1,"2546":1,"2551":1,"2594":1,"2659":1,"2670":1,"2677":2,"2678":1,"2681":2,"2682":1,"2684":8,"2685":4,"2691":1,"2694":2,"2695":2,"2697":2,"2700":2,"2717":2,"2718":1,"2754":1,"2788":2,"2789":2,"2790":2,"2791":2,"2821":1,"2824":2,"2825":3,"2841":1}}],["app",{"0":{"1302":1,"1713":1,"2582":1},"1":{"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1},"2":{"115":5,"307":1,"390":1,"542":1,"584":4,"693":1,"705":3,"826":4,"833":1,"835":1,"836":3,"837":1,"868":1,"922":5,"926":7,"934":2,"937":3,"941":1,"945":4,"976":1,"985":1,"996":9,"997":2,"1026":1,"1037":3,"1079":1,"1104":1,"1135":2,"1141":5,"1185":3,"1188":1,"1287":7,"1288":7,"1289":7,"1290":7,"1291":7,"1293":7,"1295":7,"1297":7,"1299":7,"1301":7,"1343":2,"1366":1,"1382":3,"1409":2,"1416":2,"1417":1,"1418":4,"1419":1,"1420":4,"1435":1,"1441":1,"1449":2,"1453":1,"1457":1,"1463":1,"1519":1,"1529":1,"1581":2,"1582":1,"1620":2,"1640":1,"1658":1,"1663":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1701":1,"1713":1,"1770":1,"1773":2,"1774":2,"1781":1,"1792":15,"1810":1,"1852":1,"2040":2,"2095":2,"2098":2,"2111":4,"2167":2,"2173":1,"2175":2,"2177":2,"2187":1,"2257":1,"2337":4,"2338":4,"2362":1,"2375":2,"2381":1,"2383":1,"2410":1,"2425":1,"2429":1,"2459":1,"2474":1,"2526":1,"2528":2,"2532":4,"2534":5,"2554":2,"2633":2,"2634":4,"2714":2,"2717":1,"2740":1,"2758":1,"2759":1,"2760":1,"2788":1,"2789":1,"2790":1,"2791":1,"2804":2,"2829":1,"2837":1,"2852":2,"2855":2,"2860":1,"2864":2,"2871":2,"2872":4,"2873":6,"2874":2,"2876":1,"2880":1}}],["apple",{"2":{"2397":1,"2576":1,"2779":1,"2790":1}}],["applicable",{"2":{"1792":1,"2438":1}}],["applicationname=myapi",{"2":{"2689":1,"2691":1}}],["applicationname=test",{"2":{"2679":1,"2692":1}}],["applicationname>",{"2":{"1792":1}}],["applicationname",{"2":{"937":1,"998":1,"1062":1,"1225":1,"1618":1,"1619":1,"1620":2,"1651":1,"1658":1,"1792":10,"1802":1,"1808":1,"1875":1,"2116":1,"2117":1,"2119":1,"2297":1,"2679":1,"2686":1,"2690":2,"2692":1,"2697":2,"2701":1,"2702":1,"2704":1,"2795":4}}],["applicationstopping",{"2":{"2362":1}}],["applicationslistblade",{"2":{"1792":1}}],["applications",{"0":{"1193":1},"2":{"844":2,"868":1,"921":1,"922":1,"946":1,"974":1,"1098":2,"1193":2,"1405":1,"1450":1,"1658":1,"1792":2}}],["application",{"0":{"926":1,"938":1,"942":1,"996":1,"1061":1,"1619":1,"1620":1,"1658":1,"2028":1,"2116":1,"2714":1},"1":{"2117":1,"2118":1,"2119":1},"2":{"121":1,"182":1,"207":1,"208":1,"209":1,"214":1,"307":1,"392":1,"521":1,"540":1,"546":1,"691":2,"695":1,"700":1,"705":1,"758":1,"772":1,"773":1,"831":1,"840":1,"841":4,"844":3,"848":1,"849":1,"851":1,"852":1,"859":3,"861":2,"864":1,"865":1,"866":3,"867":1,"868":3,"869":2,"872":1,"873":1,"877":1,"878":1,"879":2,"880":2,"888":1,"893":1,"909":1,"911":1,"915":1,"921":1,"922":2,"926":3,"932":4,"937":2,"940":1,"941":1,"942":1,"946":2,"947":2,"948":1,"956":2,"974":3,"975":2,"976":1,"982":1,"993":1,"995":2,"996":1,"1005":1,"1015":3,"1017":2,"1019":2,"1026":2,"1030":1,"1032":1,"1037":2,"1038":1,"1049":4,"1054":2,"1065":1,"1078":1,"1098":1,"1100":4,"1105":2,"1107":1,"1136":1,"1145":1,"1150":1,"1183":1,"1184":1,"1185":2,"1191":1,"1193":2,"1208":1,"1225":1,"1247":1,"1280":1,"1302":1,"1358":1,"1360":1,"1363":1,"1367":1,"1386":2,"1398":1,"1399":2,"1403":1,"1405":1,"1408":1,"1414":1,"1416":1,"1423":1,"1435":2,"1437":2,"1439":1,"1452":1,"1456":1,"1469":1,"1471":1,"1513":1,"1515":2,"1519":1,"1568":1,"1608":1,"1609":1,"1618":2,"1620":1,"1621":1,"1649":1,"1651":1,"1664":1,"1704":4,"1706":1,"1730":2,"1736":1,"1743":1,"1762":1,"1767":1,"1768":2,"1787":1,"1792":26,"1794":1,"1800":1,"1801":1,"1807":1,"1808":3,"1875":1,"1893":1,"1899":1,"1936":5,"1942":1,"1943":5,"1974":1,"2020":1,"2021":1,"2063":1,"2084":1,"2088":1,"2115":1,"2117":1,"2119":3,"2132":1,"2164":1,"2165":1,"2177":1,"2193":1,"2207":1,"2247":1,"2257":1,"2259":1,"2272":1,"2274":1,"2291":1,"2297":3,"2362":1,"2380":1,"2386":1,"2419":1,"2481":1,"2502":1,"2529":1,"2544":1,"2551":1,"2571":1,"2577":2,"2607":1,"2628":2,"2632":1,"2633":2,"2634":3,"2659":1,"2686":1,"2701":1,"2702":2,"2704":3,"2752":1,"2764":1,"2765":1,"2766":2,"2795":1,"2804":2,"2824":2,"2825":1,"2865":1,"2866":1,"2873":1,"2876":2}}],["applies",{"2":{"87":1,"377":1,"384":1,"480":1,"512":1,"559":1,"560":3,"562":1,"581":1,"595":1,"614":1,"615":1,"619":3,"625":1,"673":2,"675":2,"686":1,"689":1,"693":1,"698":1,"703":1,"708":1,"713":1,"868":1,"919":1,"986":1,"1045":1,"1162":1,"1792":6,"1824":1,"1844":1,"1924":1,"1961":2,"1973":1,"2009":1,"2011":1,"2038":1,"2072":2,"2106":1,"2200":1,"2320":1,"2330":1,"2333":1,"2339":1,"2340":3,"2354":1,"2381":1,"2440":1,"2470":1,"2512":1,"2537":2,"2543":1,"2581":1,"2589":1,"2591":1,"2611":1,"2804":1}}],["applied",{"2":{"74":1,"349":1,"370":1,"387":1,"462":1,"480":1,"859":1,"1150":1,"1193":2,"1407":1,"1409":1,"1792":4,"1822":2,"1824":1,"1898":2,"1910":1,"1938":1,"1961":1,"2149":1,"2208":2,"2222":1,"2410":1,"2431":2,"2436":1,"2468":1,"2505":1,"2527":1,"2543":1,"2641":2,"2700":1,"2749":1,"2795":1,"2862":1}}],["applyratelimiterrejectionasync",{"2":{"2472":1}}],["applymigrations",{"2":{"2111":1,"2112":1,"2532":2,"2534":2,"2871":2,"2872":2,"2875":1}}],["applyschema",{"2":{"1792":1,"2111":1,"2532":1}}],["apply",{"2":{"109":1,"175":1,"176":1,"181":1,"239":1,"320":1,"473":1,"476":1,"477":1,"478":1,"479":1,"683":1,"684":1,"685":1,"687":1,"970":1,"1102":1,"1113":1,"1158":1,"1208":1,"1386":1,"1408":1,"1527":1,"1679":1,"1681":1,"1792":5,"1824":1,"1856":1,"1947":1,"1949":1,"1959":1,"1962":1,"1964":1,"2047":1,"2061":1,"2111":1,"2125":1,"2147":1,"2150":1,"2152":1,"2162":1,"2168":1,"2398":1,"2415":1,"2429":1,"2455":1,"2481":2,"2532":1,"2542":1,"2586":1,"2634":1,"2635":1,"2747":1,"2852":1,"2871":1}}],["apikeyauth",{"2":{"1905":1}}],["apikeylocation",{"2":{"1792":2,"1904":1,"1905":1,"1906":1,"1907":1,"2254":2}}],["apikey",{"2":{"1792":3,"1901":1,"1904":1,"1905":1,"1906":3,"1907":1,"2254":3}}],["apitypes",{"2":{"1416":1,"1418":1,"1419":1,"1581":1}}],["apiresult",{"2":{"1386":3,"1408":5,"1410":2,"1416":1,"1571":1,"2359":3}}],["apierror",{"2":{"1386":3,"1408":3,"1416":1,"2359":3}}],["api",{"0":{"209":1,"403":1,"451":1,"531":1,"835":1,"925":1,"973":1,"978":1,"1011":1,"1029":1,"1095":1,"1254":1,"1333":1,"1345":1,"1376":1,"1384":1,"1386":1,"1780":1,"1905":1,"2028":1,"2215":1,"2370":1,"2429":1,"2487":1,"2489":1},"1":{"979":1,"980":1,"1255":1,"1256":1,"1257":1,"1258":1,"1259":1,"1260":1,"1261":1,"1262":1,"1263":1,"1264":1,"1265":1,"1266":1,"1267":1,"1268":1,"1269":1,"1270":1,"1271":1,"1272":1,"1273":1,"1274":1,"1275":1,"1276":1,"1277":1,"1278":1,"1279":1,"1280":1,"1281":1,"1282":1,"1283":1,"1284":1,"1285":1,"1286":1,"1287":1,"1288":1,"1289":1,"1290":1,"1291":1,"1292":1,"1293":1,"1294":1,"1295":1,"1296":1,"1297":1,"1298":1,"1299":1,"1300":1,"1301":1,"1385":1,"1386":1,"1387":1,"1388":1,"1389":1,"1390":1,"1391":1,"1392":1,"1393":1,"1394":1,"1395":1,"1396":1,"1397":1,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":1,"1404":1},"2":{"31":1,"38":2,"40":2,"48":3,"61":1,"62":2,"74":1,"167":1,"175":1,"182":2,"184":1,"186":1,"187":4,"196":1,"206":7,"207":6,"208":4,"209":8,"211":4,"212":5,"213":6,"214":5,"215":5,"223":1,"247":1,"248":1,"263":3,"264":4,"322":2,"323":1,"348":3,"351":1,"352":5,"353":1,"354":1,"369":1,"370":1,"372":2,"374":3,"376":1,"383":2,"386":1,"390":8,"394":4,"403":1,"415":2,"421":2,"423":8,"429":1,"430":1,"436":6,"442":2,"444":2,"445":2,"446":13,"449":1,"451":6,"452":3,"453":3,"454":4,"455":1,"462":4,"463":3,"464":4,"466":2,"467":3,"468":3,"476":2,"493":1,"520":1,"521":1,"523":1,"527":5,"531":8,"532":4,"533":1,"534":1,"540":2,"544":1,"565":1,"612":1,"613":1,"650":5,"653":2,"654":2,"658":2,"660":2,"661":2,"662":4,"663":2,"666":2,"689":1,"691":2,"695":1,"700":1,"705":1,"710":2,"723":1,"746":1,"831":1,"833":2,"835":7,"836":2,"837":3,"838":1,"867":3,"868":2,"869":2,"871":1,"872":3,"874":1,"886":1,"894":1,"908":1,"914":2,"915":1,"916":5,"917":2,"918":3,"925":2,"937":1,"938":1,"940":1,"943":1,"959":2,"961":3,"964":1,"973":3,"975":1,"995":2,"998":1,"1000":1,"1006":2,"1010":3,"1011":4,"1014":1,"1016":1,"1017":3,"1018":4,"1019":12,"1021":2,"1023":3,"1026":3,"1029":11,"1030":1,"1032":2,"1033":7,"1034":6,"1035":1,"1036":2,"1037":5,"1038":2,"1042":1,"1044":1,"1045":1,"1053":2,"1062":3,"1063":2,"1067":1,"1074":1,"1078":1,"1080":1,"1082":1,"1083":1,"1084":1,"1086":4,"1088":2,"1089":1,"1094":3,"1095":1,"1096":1,"1100":3,"1101":1,"1105":10,"1106":3,"1108":1,"1111":2,"1113":3,"1114":1,"1121":2,"1122":1,"1123":2,"1126":1,"1127":3,"1135":2,"1139":2,"1148":3,"1156":1,"1158":1,"1163":1,"1164":1,"1165":1,"1171":2,"1179":1,"1180":1,"1193":1,"1200":2,"1202":1,"1207":1,"1209":1,"1211":1,"1220":4,"1221":4,"1222":4,"1226":6,"1232":1,"1233":1,"1234":1,"1236":1,"1237":1,"1238":1,"1239":1,"1255":8,"1280":1,"1281":2,"1303":1,"1305":1,"1317":1,"1322":1,"1323":1,"1325":1,"1327":1,"1328":2,"1329":1,"1334":1,"1335":1,"1345":6,"1346":1,"1347":1,"1348":3,"1351":4,"1364":2,"1366":1,"1368":1,"1369":1,"1374":1,"1376":7,"1379":1,"1381":1,"1382":3,"1383":1,"1385":7,"1386":10,"1398":20,"1399":2,"1405":6,"1406":1,"1408":2,"1409":1,"1410":1,"1412":1,"1413":1,"1414":1,"1416":3,"1417":2,"1418":3,"1419":1,"1423":1,"1426":2,"1427":2,"1430":1,"1431":4,"1434":1,"1435":2,"1436":1,"1438":1,"1442":2,"1443":1,"1449":2,"1450":1,"1451":1,"1452":1,"1453":2,"1454":1,"1456":2,"1458":4,"1463":1,"1483":2,"1491":1,"1492":1,"1518":3,"1567":1,"1568":1,"1569":1,"1570":2,"1571":2,"1572":2,"1573":1,"1574":1,"1576":1,"1577":6,"1579":1,"1580":1,"1581":4,"1582":2,"1606":1,"1662":1,"1664":2,"1692":2,"1693":1,"1697":1,"1709":1,"1725":1,"1726":3,"1727":7,"1730":6,"1731":4,"1733":10,"1736":5,"1738":7,"1740":6,"1742":5,"1743":2,"1744":6,"1745":2,"1757":1,"1775":1,"1776":3,"1787":1,"1789":2,"1792":35,"1794":1,"1796":1,"1824":1,"1836":1,"1839":2,"1840":1,"1841":1,"1842":2,"1862":1,"1863":2,"1868":1,"1870":2,"1871":2,"1872":2,"1876":6,"1898":3,"1899":2,"1900":2,"1905":2,"1907":3,"1911":3,"1920":2,"1921":1,"1924":1,"1926":1,"1929":1,"1930":2,"1931":1,"1933":1,"1958":4,"1998":1,"2011":1,"2012":1,"2013":1,"2040":1,"2064":4,"2106":2,"2126":1,"2164":2,"2165":1,"2174":1,"2187":14,"2190":1,"2221":1,"2223":1,"2228":2,"2239":1,"2247":1,"2254":2,"2258":1,"2264":18,"2265":5,"2282":2,"2283":8,"2285":3,"2286":1,"2288":6,"2290":5,"2291":2,"2292":1,"2293":1,"2294":4,"2303":2,"2304":1,"2308":1,"2310":1,"2313":1,"2317":1,"2319":1,"2320":1,"2321":3,"2322":1,"2327":2,"2329":1,"2332":1,"2344":3,"2346":11,"2347":2,"2348":2,"2354":1,"2366":5,"2375":4,"2392":1,"2394":1,"2395":1,"2396":1,"2410":1,"2413":1,"2419":1,"2420":1,"2423":2,"2425":3,"2429":1,"2430":2,"2432":3,"2434":3,"2438":8,"2445":1,"2457":1,"2461":1,"2470":2,"2477":1,"2479":1,"2483":5,"2502":1,"2518":1,"2526":1,"2529":3,"2531":1,"2536":1,"2537":5,"2540":1,"2543":2,"2546":1,"2549":3,"2554":4,"2555":3,"2562":1,"2580":2,"2611":1,"2627":1,"2655":1,"2674":2,"2686":1,"2689":1,"2691":1,"2692":3,"2697":3,"2701":2,"2709":1,"2713":2,"2723":1,"2729":1,"2731":1,"2739":1,"2759":1,"2760":1,"2762":7,"2763":1,"2764":8,"2765":2,"2766":13,"2767":12,"2768":10,"2770":1,"2772":2,"2773":2,"2774":1,"2775":1,"2797":2,"2803":1,"2806":1,"2810":1,"2811":7,"2812":1,"2815":1,"2818":2,"2821":1,"2823":1,"2824":5,"2825":3,"2827":1,"2828":3,"2830":1,"2834":1,"2839":1,"2840":1,"2842":1,"2845":2,"2846":1,"2857":1,"2860":1,"2861":1,"2865":3,"2868":1,"2869":2,"2873":1,"2876":1,"2878":1,"2879":3,"2881":2}}],["apis",{"0":{"912":1,"1010":1},"1":{"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"1011":1,"1012":1,"1013":1,"1014":1,"1015":1,"1016":1,"1017":1,"1018":1,"1019":1,"1020":1,"1021":1,"1022":1,"1023":1,"1024":1,"1025":1,"1026":1,"1027":1,"1028":1,"1029":1,"1030":1,"1031":1,"1032":1,"1033":1,"1034":1,"1035":1,"1036":1},"2":{"10":1,"201":1,"831":1,"835":1,"869":2,"876":1,"912":1,"917":1,"920":1,"1010":2,"1018":1,"1021":1,"1026":1,"1029":1,"1032":1,"1035":1,"1036":1,"1037":5,"1064":1,"1073":1,"1084":1,"1086":1,"1104":1,"1106":1,"1111":1,"1126":1,"1127":1,"1160":1,"1162":1,"1164":1,"1167":1,"1168":1,"1193":1,"1280":1,"1333":1,"1376":1,"1382":1,"1398":2,"1434":1,"1458":1,"1720":1,"1723":1,"1739":1,"1792":2,"1896":1,"2016":1,"2021":1,"2174":1,"2258":1,"2264":1,"2270":1,"2287":1,"2375":1,"2438":1,"2632":1,"2770":2,"2826":1}}],["asc",{"2":{"1310":1,"2836":1}}],["asking",{"2":{"871":1}}],["ask",{"2":{"860":1,"1105":1,"1162":1,"1384":1,"1386":1,"1401":1}}],["asked",{"2":{"849":1,"1082":2,"1385":1,"2482":1}}],["asks",{"2":{"106":1,"302":1,"838":2,"1039":1,"1082":1}}],["asynchronous",{"2":{"1106":1,"2087":1}}],["async",{"2":{"429":1,"894":1,"938":2,"995":2,"996":3,"1024":1,"1026":3,"1104":1,"1105":1,"1107":3,"1167":3,"1170":2,"1274":1,"1317":1,"1318":1,"1320":2,"1321":1,"1335":1,"1342":1,"1366":4,"1386":1,"1408":2,"1409":1,"1410":1,"1416":1,"1567":1,"1568":1,"1569":1,"1571":1,"1575":1,"2247":1,"2310":1,"2313":1,"2357":1,"2559":1,"2615":1}}],["asset",{"2":{"2576":1}}],["assets",{"2":{"1101":1,"2576":1}}],["assembly",{"2":{"2389":1,"2399":1,"2668":1}}],["asserted",{"2":{"2506":1,"2546":1}}],["asserting",{"2":{"1078":1,"2435":1,"2472":1}}],["assertions",{"0":{"986":1,"2864":1},"2":{"874":1,"986":2,"994":1,"1003":1,"1074":3,"1076":2,"1082":1,"1792":1,"2092":1,"2094":1,"2104":1,"2107":1,"2167":1,"2221":1,"2452":1,"2456":3,"2457":1,"2526":2,"2528":2,"2529":1,"2531":1,"2535":5,"2537":1,"2860":2,"2864":1,"2865":1,"2880":1,"2881":1}}],["assertion",{"2":{"715":1,"875":1,"986":1,"1078":2,"1081":2,"2102":2,"2107":1,"2113":1,"2456":1,"2526":1,"2528":3,"2529":1,"2535":4,"2537":1,"2864":3,"2865":1,"2873":1,"2880":2,"2881":2}}],["asserts",{"2":{"1074":1,"1076":1,"1792":1,"2465":1,"2528":1,"2739":1,"2860":1,"2876":1}}],["assert",{"2":{"930":11,"979":2,"980":1,"986":2,"988":1,"989":1,"990":3,"991":2,"994":1,"1074":2,"1075":1,"1076":6,"1077":1,"1078":1,"1082":1,"1214":1,"1393":3,"1419":3,"1442":3,"1674":2,"1792":2,"2109":1,"2255":3,"2384":1,"2452":1,"2525":1,"2526":1,"2527":1,"2528":5,"2529":1,"2530":1,"2858":1,"2860":1,"2862":1,"2864":4,"2865":1,"2867":1,"2881":1}}],["assumeuniversal",{"2":{"2451":2,"2457":1}}],["assume",{"2":{"877":1,"1133":1,"2398":1,"2438":1}}],["assumed",{"2":{"869":1,"1792":1,"1856":1,"2224":1,"2454":1,"2456":1}}],["assumes",{"2":{"869":1,"2504":1}}],["assumption",{"2":{"847":1,"2881":1}}],["assuming",{"2":{"206":1,"2785":1}}],["associated",{"2":{"863":1,"2340":1}}],["associate",{"2":{"192":1}}],["assistance",{"2":{"1384":1}}],["assistants",{"2":{"876":1}}],["assisted",{"2":{"0":1,"1":1,"3":1,"871":2,"1081":1}}],["assignment",{"2":{"2540":1}}],["assigns",{"2":{"684":1}}],["assign",{"2":{"569":1,"1413":1,"1599":1,"1600":1,"1602":1,"1670":1,"1949":1}}],["assigned",{"2":{"175":2,"179":1,"1588":1,"1708":1,"1792":2,"2633":1}}],["aspnet",{"2":{"1792":9,"2257":4,"2450":1,"2633":1,"2634":1}}],["aspnetcore",{"2":{"1460":1,"1489":1,"1792":7,"1904":1,"1907":1,"2254":2,"2375":1,"2386":1,"2422":1,"2435":1,"2567":1,"2634":1}}],["aspects",{"2":{"1128":1,"1254":1}}],["aspect",{"2":{"1084":1,"1108":1,"1325":1,"1401":1}}],["asp",{"0":{"869":1,"2714":1},"2":{"182":1,"188":1,"408":1,"868":3,"869":4,"871":2,"873":1,"876":1,"1054":1,"1098":1,"1100":1,"1156":1,"1366":1,"1409":1,"1421":1,"1445":1,"1447":2,"1450":2,"1457":2,"1458":1,"1460":1,"1718":1,"1783":1,"1792":5,"1802":1,"1822":1,"2257":1,"2258":1,"2291":1,"2296":1,"2375":2,"2419":1,"2421":1,"2422":1,"2424":1,"2425":1,"2426":1,"2427":1,"2428":1,"2435":1,"2436":2,"2437":1,"2438":1,"2470":1,"2481":1,"2554":2,"2645":1,"2665":1,"2795":1}}],["as",{"0":{"322":1,"492":1,"502":1,"503":1,"734":1,"775":1,"799":1,"915":1,"1326":1,"1333":1,"1386":1,"2183":1,"2184":1,"2185":1,"2494":1,"2721":1,"2722":1},"2":{"4":1,"7":1,"12":1,"13":1,"16":1,"27":2,"29":1,"32":1,"34":3,"35":4,"37":1,"41":2,"44":1,"48":1,"50":1,"51":1,"55":1,"61":1,"63":1,"68":1,"71":1,"75":1,"77":1,"78":1,"91":1,"104":1,"108":1,"115":1,"125":2,"128":1,"132":1,"136":1,"144":1,"155":1,"157":1,"165":1,"168":3,"171":1,"174":1,"176":1,"182":1,"184":2,"186":2,"188":3,"192":1,"202":1,"203":1,"206":3,"207":2,"208":2,"209":3,"210":1,"213":1,"214":1,"218":1,"223":4,"224":2,"227":2,"241":1,"244":1,"247":1,"252":1,"261":3,"263":2,"264":3,"266":1,"282":2,"286":2,"288":1,"292":1,"294":1,"296":2,"298":4,"302":1,"305":2,"306":4,"308":4,"309":3,"310":2,"312":5,"313":1,"316":3,"317":1,"318":1,"320":4,"322":2,"325":2,"326":3,"328":2,"330":1,"332":1,"333":2,"334":3,"335":3,"336":1,"337":3,"339":1,"351":2,"357":2,"360":1,"363":1,"366":1,"369":1,"370":1,"372":1,"376":3,"380":3,"383":4,"386":1,"388":1,"389":1,"390":1,"395":1,"401":1,"408":1,"412":1,"414":3,"415":2,"419":1,"422":1,"423":2,"426":2,"427":1,"428":1,"430":1,"431":1,"433":2,"435":1,"438":1,"439":1,"447":3,"448":3,"449":1,"452":1,"453":3,"454":3,"456":1,"458":2,"460":3,"462":1,"463":1,"464":1,"466":1,"473":1,"484":1,"487":1,"492":1,"494":1,"497":1,"499":2,"503":1,"507":1,"510":1,"511":1,"512":1,"515":4,"517":2,"520":1,"528":2,"539":1,"549":1,"563":2,"568":1,"569":1,"584":6,"586":1,"587":2,"588":1,"589":2,"592":1,"597":1,"598":1,"607":2,"609":2,"614":1,"616":1,"618":1,"625":1,"626":1,"627":1,"636":1,"639":1,"641":1,"646":2,"649":1,"650":1,"658":1,"659":2,"663":2,"664":2,"665":1,"669":1,"673":1,"675":2,"677":1,"679":1,"683":1,"690":2,"691":1,"699":1,"700":1,"701":1,"706":2,"711":1,"722":1,"723":1,"733":4,"739":1,"741":1,"743":1,"748":1,"750":2,"751":1,"752":1,"755":1,"756":1,"760":2,"761":2,"763":1,"764":2,"765":1,"766":1,"767":1,"770":2,"771":2,"773":1,"774":2,"775":2,"776":1,"777":1,"778":1,"786":1,"788":1,"794":2,"797":4,"801":2,"802":1,"809":1,"811":1,"812":1,"813":1,"814":1,"815":1,"823":1,"834":10,"835":3,"841":5,"843":1,"844":4,"847":2,"848":8,"849":2,"851":5,"852":2,"859":1,"860":3,"861":2,"863":2,"864":2,"865":1,"866":2,"867":1,"868":5,"869":2,"871":2,"872":3,"873":3,"874":1,"876":2,"877":4,"880":1,"881":1,"882":1,"883":3,"884":1,"885":2,"886":1,"888":2,"892":1,"900":1,"904":1,"913":3,"914":3,"915":5,"916":4,"918":6,"919":3,"920":2,"921":2,"924":1,"928":1,"929":1,"932":1,"933":2,"934":3,"937":1,"949":1,"951":1,"952":3,"959":1,"961":1,"965":1,"966":1,"971":1,"974":1,"977":2,"979":1,"988":1,"994":3,"995":4,"1005":2,"1017":1,"1019":2,"1020":1,"1021":1,"1023":1,"1029":1,"1031":1,"1035":1,"1037":1,"1038":2,"1040":2,"1041":2,"1043":1,"1045":4,"1046":1,"1049":3,"1050":1,"1056":2,"1058":4,"1060":3,"1061":2,"1065":1,"1066":1,"1067":1,"1068":2,"1070":1,"1073":7,"1076":2,"1079":1,"1080":2,"1082":1,"1083":1,"1084":2,"1094":2,"1095":7,"1096":2,"1097":4,"1098":2,"1101":2,"1102":2,"1105":9,"1107":2,"1111":1,"1115":1,"1121":2,"1125":3,"1126":2,"1135":4,"1138":1,"1139":2,"1141":1,"1142":1,"1148":1,"1154":1,"1159":1,"1161":1,"1176":1,"1179":1,"1187":1,"1188":2,"1189":3,"1192":4,"1193":5,"1197":1,"1208":2,"1209":1,"1213":1,"1214":1,"1215":1,"1216":2,"1218":2,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1238":2,"1239":1,"1252":1,"1254":6,"1266":1,"1270":1,"1279":1,"1280":1,"1307":2,"1309":3,"1321":1,"1324":1,"1326":2,"1332":1,"1334":1,"1338":1,"1339":1,"1341":1,"1347":1,"1348":2,"1355":2,"1357":3,"1361":1,"1366":1,"1368":2,"1370":5,"1371":6,"1373":2,"1374":1,"1375":3,"1376":9,"1385":7,"1386":13,"1387":1,"1388":1,"1389":1,"1390":3,"1391":1,"1392":1,"1393":3,"1394":6,"1395":4,"1396":7,"1398":17,"1399":3,"1401":2,"1402":5,"1403":7,"1404":5,"1405":5,"1406":3,"1407":1,"1408":3,"1410":5,"1412":1,"1416":2,"1420":3,"1426":2,"1427":2,"1429":8,"1430":1,"1431":3,"1436":2,"1442":2,"1458":10,"1465":2,"1467":2,"1470":1,"1475":2,"1477":3,"1484":2,"1486":1,"1504":7,"1511":2,"1515":3,"1518":1,"1531":1,"1540":1,"1543":2,"1544":1,"1547":6,"1557":1,"1563":1,"1567":7,"1568":2,"1569":1,"1570":1,"1571":2,"1574":1,"1581":1,"1582":1,"1627":1,"1632":1,"1655":1,"1664":1,"1684":1,"1686":1,"1688":1,"1689":11,"1699":2,"1706":1,"1722":1,"1725":1,"1726":1,"1727":3,"1732":1,"1736":2,"1738":1,"1740":1,"1741":1,"1742":2,"1743":2,"1744":2,"1745":1,"1747":1,"1748":1,"1755":1,"1758":1,"1789":1,"1792":61,"1802":1,"1821":1,"1822":3,"1823":1,"1824":5,"1825":3,"1833":1,"1834":1,"1838":1,"1840":3,"1850":1,"1852":2,"1854":4,"1856":3,"1861":1,"1864":1,"1868":1,"1883":1,"1888":1,"1912":1,"1915":1,"1918":1,"1920":1,"1921":1,"1922":3,"1924":6,"1925":1,"1926":2,"1928":2,"1930":1,"1937":1,"1957":1,"1958":1,"1959":1,"1961":2,"1967":2,"1968":1,"1973":1,"1974":5,"2000":2,"2004":1,"2005":1,"2006":1,"2010":3,"2018":1,"2020":1,"2038":1,"2040":4,"2072":1,"2075":1,"2076":2,"2077":1,"2083":1,"2092":1,"2094":1,"2095":2,"2096":1,"2097":1,"2101":1,"2105":1,"2107":1,"2111":1,"2129":1,"2130":1,"2131":1,"2147":2,"2148":1,"2155":1,"2156":2,"2157":2,"2164":2,"2166":2,"2170":1,"2175":2,"2176":4,"2177":2,"2178":3,"2181":1,"2183":3,"2184":3,"2185":2,"2186":1,"2187":3,"2188":2,"2193":1,"2194":1,"2195":1,"2205":1,"2206":1,"2208":1,"2217":1,"2221":1,"2222":1,"2223":1,"2224":3,"2247":2,"2252":1,"2254":2,"2258":3,"2264":5,"2265":4,"2267":1,"2274":4,"2277":4,"2282":1,"2283":3,"2288":1,"2289":1,"2290":2,"2292":1,"2293":2,"2296":3,"2297":1,"2301":1,"2302":2,"2303":1,"2304":2,"2305":1,"2308":1,"2309":1,"2318":1,"2320":2,"2323":1,"2325":1,"2326":3,"2327":1,"2330":1,"2332":1,"2333":3,"2335":1,"2337":3,"2339":2,"2344":4,"2346":4,"2348":4,"2356":1,"2357":2,"2359":1,"2360":4,"2375":6,"2377":1,"2379":2,"2380":1,"2383":3,"2389":4,"2391":1,"2393":2,"2394":1,"2405":1,"2406":1,"2411":1,"2414":3,"2415":1,"2416":1,"2419":2,"2420":1,"2422":1,"2427":1,"2431":1,"2437":1,"2438":6,"2442":1,"2444":2,"2445":1,"2451":3,"2453":1,"2454":2,"2455":2,"2456":2,"2459":3,"2461":3,"2470":1,"2471":1,"2476":2,"2477":2,"2479":1,"2481":8,"2482":1,"2483":1,"2484":4,"2486":1,"2487":2,"2491":1,"2493":2,"2494":1,"2496":1,"2498":1,"2502":2,"2505":2,"2509":1,"2511":1,"2518":2,"2519":1,"2520":1,"2523":1,"2525":1,"2527":1,"2528":4,"2529":1,"2531":3,"2533":4,"2534":1,"2535":2,"2537":5,"2538":1,"2539":4,"2540":3,"2542":2,"2543":3,"2544":1,"2545":2,"2546":1,"2549":14,"2554":1,"2572":1,"2575":2,"2580":2,"2586":6,"2587":3,"2588":3,"2589":7,"2590":3,"2591":1,"2595":4,"2607":6,"2611":1,"2641":1,"2648":1,"2650":1,"2651":1,"2652":1,"2665":1,"2668":1,"2670":1,"2671":1,"2678":1,"2682":1,"2685":1,"2694":1,"2712":1,"2722":1,"2724":1,"2725":1,"2739":1,"2740":1,"2749":1,"2755":1,"2759":1,"2762":8,"2763":3,"2764":2,"2766":3,"2767":4,"2772":1,"2785":1,"2792":1,"2795":4,"2798":1,"2802":1,"2803":3,"2805":1,"2809":1,"2810":3,"2811":1,"2812":4,"2813":2,"2815":1,"2821":1,"2824":2,"2825":1,"2829":2,"2830":2,"2833":1,"2834":2,"2835":1,"2836":3,"2837":1,"2838":1,"2840":1,"2841":1,"2850":4,"2852":2,"2857":1,"2860":1,"2861":1,"2862":1,"2864":3,"2868":1,"2869":2,"2871":1,"2874":1,"2876":3}}],["although",{"2":{"2461":1}}],["alters",{"2":{"2869":1}}],["alternation",{"2":{"2435":1}}],["alternatively",{"2":{"2822":1}}],["alternatives",{"2":{"994":1,"1035":1}}],["alternative",{"0":{"17":1,"49":1},"2":{"176":1,"260":1,"298":1,"397":1,"690":1,"701":1,"852":1,"869":1,"876":1,"1385":1,"1631":1,"2534":1,"2684":1,"2692":1}}],["alternate",{"2":{"1994":1}}],["alter",{"2":{"848":2,"864":1,"880":1,"982":1,"1193":2,"1792":2,"2156":2,"2456":1,"2542":2,"2546":1,"2635":1,"2869":7}}],["alphanumeric",{"2":{"2144":1}}],["alpine",{"2":{"1420":1}}],["alb",{"0":{"1712":1},"2":{"1701":1,"1792":1,"2633":2}}],["aleph",{"2":{"1442":1}}],["alert",{"2":{"1313":1,"1316":1}}],["alerts",{"2":{"661":2}}],["alright",{"2":{"1402":1,"1441":2}}],["already",{"2":{"38":1,"206":1,"303":1,"308":1,"327":1,"838":1,"841":5,"848":3,"851":2,"852":1,"860":2,"861":2,"864":1,"865":3,"868":1,"869":1,"874":2,"1038":1,"1046":1,"1073":1,"1076":1,"1078":2,"1082":1,"1111":1,"1122":1,"1206":1,"1221":1,"1232":1,"1304":2,"1318":1,"1377":1,"1382":1,"1386":3,"1396":1,"1401":1,"1402":1,"1403":1,"1404":1,"1420":1,"1423":1,"1431":2,"1441":1,"1443":1,"1678":1,"1792":1,"1825":1,"1834":1,"1882":1,"2018":1,"2147":1,"2314":1,"2332":1,"2398":1,"2412":1,"2416":1,"2429":1,"2437":1,"2452":1,"2476":1,"2486":2,"2504":2,"2531":1,"2540":1,"2555":1,"2559":1,"2575":1,"2614":1,"2632":1,"2666":1,"2760":1}}],["alg",{"2":{"928":2}}],["algebra",{"2":{"852":6,"1394":1}}],["algorithms",{"0":{"858":1,"1656":1,"1657":1,"1939":1},"1":{"859":1,"860":1,"861":1,"1940":1,"1941":1},"2":{"841":2,"859":2,"860":2,"865":1,"1049":1,"1064":1,"1101":2,"1403":1,"1792":2,"1935":1}}],["algorithm",{"2":{"363":1,"860":5,"861":2,"1049":2,"1213":1,"1215":4,"1236":4,"1237":2,"1240":1,"1403":2,"1651":2,"1656":2,"1657":2,"1792":7,"1886":2,"1887":2,"1889":1,"1937":1,"1950":1,"1953":1}}],["along",{"2":{"453":1,"841":1,"855":1,"857":1,"863":1,"872":1,"874":1,"915":1,"916":1,"918":1,"1082":1,"1401":1,"1419":1,"1431":1,"1792":1,"1823":1,"2024":1,"2452":1,"2487":1,"2632":1}}],["alongside",{"0":{"354":1,"1068":1},"2":{"409":1,"815":1,"926":1,"1007":1,"1066":1,"1098":1,"1106":1,"1107":1,"1121":1,"1190":1,"1416":1,"1458":3,"1533":1,"1792":6,"1862":1,"1898":2,"2274":1,"2287":1,"2319":1,"2375":2,"2420":1,"2431":2,"2436":1,"2474":1,"2502":1,"2550":1,"2554":1,"2791":1}}],["alone",{"0":{"2453":1},"2":{"436":1,"848":1,"865":1,"869":1,"872":1,"919":1,"1080":1,"1147":1,"1326":1,"1428":1,"1792":1,"2106":1,"2153":1,"2158":1,"2336":1,"2347":1,"2481":1,"2537":1,"2541":1,"2742":1,"2878":1,"2881":1}}],["alike",{"2":{"2871":1}}],["align",{"2":{"2571":1}}],["aligned",{"2":{"2314":1}}],["aligning",{"2":{"2314":1}}],["alignment",{"2":{"965":1}}],["alive",{"2":{"1991":1,"2537":1}}],["alice",{"2":{"332":3,"562":1,"609":2,"611":2,"612":2,"613":1,"614":1,"621":1,"623":1,"762":1,"772":1,"893":2,"913":1,"919":2,"938":3,"970":1,"977":2,"979":2,"980":2,"986":2,"988":2,"990":3,"1051":3,"1061":2,"1218":3,"1233":2,"1305":1,"1307":2,"1313":1,"1369":2,"1375":1,"1419":2,"1973":3,"2009":2,"2040":1,"2179":1,"2180":2,"2339":3,"2587":3,"2842":4}}],["aliased",{"2":{"2301":1}}],["aliases",{"0":{"2295":1,"2343":1},"2":{"768":1,"776":1,"1792":1,"1801":1,"2295":1,"2326":2,"2339":1,"2359":3,"2435":1,"2544":1,"2671":1,"2752":1,"2801":1}}],["alias",{"0":{"2335":1,"2359":1},"2":{"318":1,"326":1,"348":2,"460":1,"753":1,"757":2,"1581":1,"1792":2,"1840":1,"2338":1,"2359":3,"2481":1,"2482":1,"2522":1,"2813":1,"2831":1,"2841":1}}],["almost",{"2":{"214":1,"851":1,"1385":1,"1393":1,"1400":3,"1401":1,"1402":2,"2450":1,"2452":1,"2502":1,"2723":1,"2731":1,"2754":1}}],["always",{"0":{"2403":1},"2":{"39":1,"107":1,"213":1,"214":1,"239":1,"303":1,"308":1,"319":1,"337":1,"377":1,"380":1,"422":1,"446":1,"531":1,"707":1,"713":1,"716":1,"764":1,"774":1,"801":1,"863":3,"864":1,"865":1,"913":3,"918":1,"919":1,"924":1,"933":1,"960":2,"976":3,"977":2,"978":1,"1005":1,"1041":1,"1050":1,"1067":1,"1075":1,"1097":1,"1129":2,"1150":1,"1155":1,"1170":1,"1193":1,"1208":1,"1213":1,"1245":1,"1307":2,"1355":1,"1371":1,"1385":3,"1386":1,"1398":1,"1401":1,"1402":1,"1403":2,"1420":1,"1447":2,"1448":1,"1449":1,"1477":1,"1519":1,"1543":1,"1664":1,"1741":1,"1792":15,"1824":3,"1833":1,"1875":1,"1924":1,"1929":1,"1957":1,"1982":1,"2011":1,"2019":1,"2052":1,"2059":1,"2094":2,"2102":1,"2104":1,"2107":2,"2110":1,"2112":1,"2155":1,"2177":1,"2184":1,"2250":1,"2267":1,"2284":1,"2289":1,"2319":2,"2320":1,"2333":2,"2354":1,"2360":1,"2379":1,"2389":1,"2416":1,"2426":2,"2427":1,"2429":1,"2436":2,"2481":2,"2490":1,"2502":1,"2510":1,"2520":1,"2530":1,"2532":1,"2533":1,"2535":1,"2537":4,"2590":1,"2634":3,"2641":1,"2673":1,"2712":1,"2723":2,"2754":1,"2763":1,"2779":1,"2795":1,"2800":1,"2836":1,"2843":1,"2848":1,"2870":1,"2871":1,"2880":1}}],["also",{"0":{"28":1,"67":1,"100":1,"111":1,"124":1,"143":1,"153":1,"191":1,"200":1,"219":1,"295":1,"432":1,"457":1,"483":1,"580":1,"672":1,"682":1,"727":1,"742":1,"793":1,"806":1,"822":1,"1467":1,"1486":1,"1508":1,"1537":1,"1551":1,"1585":1,"1602":1,"1636":1,"1667":1,"1681":1,"1750":1,"1934":1,"1964":1,"2083":1,"2136":1,"2152":1},"2":{"4":1,"13":1,"14":1,"29":1,"44":1,"55":1,"68":1,"78":1,"91":1,"101":1,"113":1,"125":1,"132":1,"144":1,"159":1,"162":1,"165":1,"182":1,"186":1,"192":1,"212":1,"261":1,"282":1,"296":1,"306":1,"319":1,"324":1,"328":1,"334":1,"339":1,"357":1,"369":1,"385":1,"388":1,"390":1,"408":1,"411":1,"412":1,"433":1,"448":1,"452":1,"454":1,"458":1,"469":1,"473":1,"484":1,"497":1,"507":1,"515":2,"549":1,"569":1,"589":1,"598":1,"607":1,"618":1,"627":1,"636":1,"637":1,"646":1,"649":1,"683":2,"752":1,"768":1,"776":1,"794":1,"823":1,"827":1,"841":1,"843":1,"844":2,"845":1,"868":1,"876":1,"879":1,"911":1,"913":1,"914":2,"917":1,"920":3,"925":1,"946":1,"966":1,"971":1,"975":2,"1007":1,"1036":1,"1038":1,"1042":1,"1065":1,"1069":1,"1070":1,"1077":1,"1098":1,"1102":1,"1107":1,"1182":1,"1208":1,"1254":3,"1258":1,"1327":1,"1351":1,"1367":1,"1382":1,"1385":2,"1386":8,"1390":1,"1391":1,"1394":3,"1395":1,"1396":3,"1398":1,"1400":1,"1405":1,"1475":1,"1477":1,"1504":1,"1558":1,"1569":1,"1609":1,"1689":1,"1738":1,"1759":1,"1792":11,"1819":1,"1822":1,"1830":1,"1912":1,"1961":1,"2000":1,"2010":1,"2030":1,"2039":1,"2079":1,"2094":1,"2102":1,"2109":1,"2110":1,"2111":1,"2162":1,"2185":2,"2193":1,"2200":1,"2221":1,"2222":1,"2252":2,"2253":1,"2254":1,"2256":1,"2273":1,"2282":1,"2293":1,"2314":2,"2323":1,"2325":1,"2330":1,"2335":1,"2340":1,"2388":1,"2395":1,"2446":1,"2481":1,"2483":1,"2495":1,"2496":1,"2500":1,"2518":1,"2525":1,"2530":1,"2532":1,"2533":1,"2537":1,"2545":2,"2580":1,"2591":1,"2596":1,"2626":1,"2633":1,"2653":1,"2661":1,"2665":1,"2695":1,"2719":1,"2726":1,"2755":1,"2764":1,"2775":1,"2779":1,"2795":1,"2828":1,"2832":1,"2833":1,"2835":1,"2855":1,"2871":1}}],["alltogether",{"2":{"2452":1}}],["allegory",{"2":{"913":1,"919":2}}],["alley",{"2":{"841":1}}],["alloc",{"2":{"2397":2}}],["allocated",{"2":{"951":1,"1166":1}}],["allocates",{"2":{"919":1,"1168":1}}],["allocating",{"2":{"948":1,"2397":1,"2614":1}}],["allocations",{"2":{"919":1,"947":1,"951":2,"969":1,"1037":1,"1099":1,"2399":1,"2559":1,"2604":1,"2614":2,"2622":4}}],["allocation",{"0":{"947":1,"951":1,"2397":1,"2622":1},"1":{"948":1,"949":1,"950":1,"951":1,"952":1,"953":1,"954":1,"955":1,"956":1,"957":1,"958":1,"959":1,"960":1,"961":1,"962":1,"963":1,"964":1,"965":1,"966":1,"967":1,"968":1,"969":1,"970":1,"971":1,"2398":1},"2":{"874":1,"969":1,"1007":1,"1037":1,"1275":1,"2226":1,"2309":1,"2372":1,"2397":1,"2398":2,"2399":1,"2400":1,"2604":1,"2614":2}}],["allowempty",{"0":{"2105":1},"2":{"1792":1,"2093":1,"2094":1,"2113":1,"2535":1,"2537":2}}],["allowedimagetypes",{"2":{"1792":1,"2123":1,"2125":1}}],["allowedhosts",{"2":{"1702":1,"1703":1,"1709":1,"1711":1,"1717":1,"1792":1,"2633":1}}],["allowedheaders",{"2":{"1638":1,"1639":1,"1643":1,"1646":2,"1792":1,"2429":1}}],["allowedmethods",{"2":{"1638":1,"1639":1,"1642":1,"1646":2,"1792":1,"2429":1}}],["allowedorigins",{"0":{"1823":1},"2":{"1449":1,"1638":1,"1639":2,"1640":2,"1641":1,"1644":1,"1646":2,"1792":3,"1814":1,"1824":1,"2429":1,"2481":1,"2486":1,"2551":1}}],["allowed",{"0":{"1640":1,"1642":1,"1643":1,"1709":1},"1":{"1641":1},"2":{"382":1,"747":2,"1158":1,"1160":1,"1225":1,"1324":1,"1360":1,"1435":1,"1533":1,"1639":3,"1641":1,"1703":1,"1709":1,"1792":11,"1823":2,"1824":1,"1827":1,"1875":1,"1951":1,"1952":1,"2025":2,"2125":2,"2126":1,"2127":1,"2141":1,"2258":1,"2261":1,"2334":1,"2633":2}}],["allowhostheaderoverride",{"2":{"1792":1,"1994":2}}],["allowresponseheadercompression",{"2":{"1792":1,"1994":2}}],["allowalternateschemes",{"2":{"1792":1,"1994":2}}],["allowinvalid",{"2":{"1792":1,"1988":1}}],["allowing",{"2":{"1646":1,"1792":1,"2282":1,"2310":1,"2362":1,"2383":1,"2554":1,"2776":1}}],["allowcredentialscolumnname",{"2":{"1240":1,"1792":1,"1889":1}}],["allowcredentials",{"2":{"1222":1,"1449":1,"1638":1,"1639":1,"1641":2,"1644":3,"1646":2,"1792":1,"2223":1,"2429":1,"2486":1}}],["allowlist",{"2":{"390":2,"534":1,"1792":1,"1862":1,"2040":2,"2477":1,"2483":2,"2768":1}}],["allowlisted",{"0":{"2483":1},"2":{"212":1,"388":1,"390":3,"394":1,"1738":1,"1862":1,"2223":1,"2483":1,"2764":1,"2768":1}}],["allowsynchronousio",{"2":{"1609":1,"1792":1,"1994":2,"2661":1}}],["allows",{"2":{"10":1,"64":2,"150":1,"286":1,"349":1,"429":1,"463":1,"464":1,"646":1,"851":1,"918":1,"920":1,"928":1,"934":1,"1063":1,"1160":1,"1168":1,"1220":1,"1607":1,"1639":1,"1640":1,"1713":1,"1792":6,"1870":1,"2047":1,"2056":1,"2072":1,"2258":1,"2264":1,"2267":1,"2273":1,"2277":1,"2304":1,"2313":1,"2372":1,"2625":1,"2628":1,"2632":1,"2649":1,"2650":1}}],["allow",{"0":{"4":1,"1641":1,"2201":1},"1":{"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1},"2":{"4":2,"5":1,"7":2,"9":1,"27":2,"224":2,"316":1,"545":3,"747":1,"902":1,"934":1,"940":1,"1012":1,"1096":1,"1098":1,"1199":2,"1222":1,"1230":1,"1234":2,"1240":1,"1254":1,"1358":2,"1371":1,"1415":1,"1427":1,"1431":1,"1447":1,"1458":1,"1465":2,"1500":1,"1558":1,"1604":1,"1631":1,"1639":1,"1641":1,"1642":1,"1643":1,"1693":1,"1723":1,"1757":1,"1792":14,"1824":1,"1837":1,"1880":1,"1884":1,"1889":1,"1898":1,"1994":3,"2016":1,"2018":3,"2021":1,"2023":2,"2125":1,"2175":1,"2189":1,"2201":1,"2214":1,"2323":1,"2329":1,"2380":1,"2431":1,"2436":2,"2540":1,"2629":1,"2632":4,"2728":1,"2762":2,"2764":1,"2821":1,"2824":1}}],["all",{"0":{"566":1,"631":1,"645":1,"734":1,"799":1,"1179":1,"1315":1,"1390":1,"1641":1,"1716":1,"2249":1,"2664":1},"2":{"1":1,"17":1,"22":1,"25":1,"60":1,"64":1,"74":2,"81":1,"84":1,"85":1,"119":2,"168":1,"184":2,"186":2,"213":1,"216":1,"220":2,"244":2,"245":1,"249":2,"258":2,"265":1,"269":2,"285":1,"286":2,"288":1,"291":2,"292":1,"305":1,"306":1,"310":1,"320":1,"323":2,"324":1,"336":1,"369":1,"370":1,"378":1,"384":1,"388":1,"462":1,"470":2,"479":1,"518":1,"540":1,"556":2,"560":1,"585":1,"595":1,"609":1,"615":1,"619":1,"636":1,"638":2,"641":1,"645":1,"646":2,"650":1,"704":1,"737":1,"739":1,"747":2,"748":1,"753":1,"757":1,"768":1,"771":1,"773":1,"774":1,"776":4,"777":2,"779":1,"781":1,"788":3,"789":1,"801":1,"802":1,"823":1,"826":1,"829":3,"831":1,"840":1,"844":1,"845":2,"848":4,"849":2,"851":2,"852":3,"855":2,"857":2,"859":1,"861":1,"863":1,"865":1,"868":2,"871":1,"872":2,"873":1,"874":1,"876":2,"877":1,"880":1,"881":2,"886":1,"887":1,"892":3,"901":2,"904":2,"910":1,"914":1,"916":4,"917":2,"919":1,"920":1,"922":1,"930":1,"933":1,"934":1,"945":1,"947":2,"948":1,"949":2,"967":1,"974":3,"983":1,"991":1,"992":2,"994":1,"1010":1,"1014":1,"1020":1,"1026":1,"1030":1,"1037":1,"1042":1,"1046":1,"1048":1,"1054":1,"1059":1,"1061":1,"1062":1,"1064":2,"1066":2,"1069":1,"1070":2,"1073":1,"1077":2,"1079":1,"1081":1,"1082":1,"1086":1,"1088":1,"1092":1,"1096":1,"1097":3,"1101":1,"1102":1,"1103":1,"1104":1,"1105":3,"1108":1,"1111":1,"1127":2,"1129":1,"1135":1,"1137":1,"1149":2,"1150":2,"1159":1,"1162":1,"1164":1,"1165":1,"1177":1,"1178":2,"1182":1,"1192":1,"1218":1,"1223":1,"1226":1,"1235":1,"1243":1,"1252":1,"1254":1,"1255":3,"1259":1,"1260":1,"1262":1,"1266":1,"1279":1,"1280":1,"1283":1,"1305":2,"1309":2,"1315":1,"1316":1,"1318":1,"1324":2,"1325":1,"1328":1,"1337":1,"1339":1,"1356":1,"1357":3,"1358":1,"1366":1,"1367":1,"1368":2,"1370":1,"1372":1,"1376":1,"1378":1,"1380":2,"1381":1,"1385":4,"1386":5,"1388":2,"1390":1,"1391":1,"1393":1,"1394":1,"1395":1,"1396":4,"1398":3,"1399":1,"1400":1,"1401":1,"1402":1,"1403":6,"1404":1,"1405":1,"1409":1,"1410":1,"1411":1,"1414":1,"1416":1,"1431":1,"1432":1,"1437":1,"1439":1,"1460":1,"1464":1,"1470":1,"1475":2,"1477":2,"1480":1,"1521":1,"1522":1,"1531":1,"1540":2,"1543":1,"1544":2,"1547":2,"1566":1,"1580":1,"1609":1,"1612":1,"1621":1,"1642":1,"1643":1,"1646":1,"1651":1,"1655":1,"1686":1,"1688":1,"1690":2,"1697":1,"1703":1,"1704":1,"1706":1,"1708":1,"1741":1,"1745":1,"1766":1,"1774":1,"1778":1,"1785":2,"1792":43,"1825":1,"1840":2,"1841":1,"1843":1,"1846":2,"1847":1,"1850":1,"1851":1,"1876":1,"1885":1,"1909":1,"1923":1,"1924":1,"1929":1,"1949":1,"1955":2,"1961":1,"1967":1,"1974":1,"1982":1,"1989":1,"1999":1,"2000":1,"2003":3,"2004":1,"2005":1,"2006":2,"2007":1,"2010":1,"2011":1,"2014":1,"2016":1,"2036":3,"2049":2,"2062":1,"2102":1,"2113":1,"2118":1,"2125":2,"2130":1,"2160":1,"2169":1,"2182":1,"2184":1,"2190":1,"2192":1,"2193":1,"2194":1,"2209":3,"2210":1,"2219":1,"2222":2,"2223":1,"2228":1,"2232":1,"2247":1,"2250":1,"2252":2,"2257":3,"2258":1,"2265":1,"2266":1,"2271":2,"2277":2,"2278":1,"2286":1,"2289":1,"2292":2,"2293":2,"2314":2,"2320":1,"2321":1,"2323":2,"2328":1,"2329":1,"2330":4,"2332":1,"2338":3,"2339":1,"2340":1,"2344":1,"2346":1,"2347":1,"2351":1,"2352":1,"2353":1,"2360":1,"2362":1,"2365":1,"2369":1,"2371":1,"2372":1,"2375":1,"2379":1,"2380":2,"2382":2,"2384":1,"2391":2,"2392":1,"2397":1,"2415":1,"2425":1,"2430":1,"2431":1,"2433":1,"2435":3,"2436":1,"2445":1,"2446":1,"2447":1,"2451":2,"2456":1,"2463":1,"2465":1,"2481":1,"2489":1,"2490":1,"2493":1,"2494":2,"2495":1,"2496":1,"2508":1,"2509":1,"2510":1,"2515":1,"2518":3,"2520":1,"2523":1,"2525":1,"2532":1,"2533":1,"2534":1,"2535":2,"2537":1,"2540":1,"2545":1,"2546":1,"2551":1,"2554":1,"2577":3,"2581":2,"2588":1,"2589":1,"2590":1,"2591":1,"2607":2,"2611":1,"2615":3,"2622":1,"2632":3,"2633":1,"2634":3,"2635":4,"2641":1,"2645":1,"2659":1,"2664":1,"2667":1,"2668":1,"2671":1,"2672":1,"2679":1,"2682":1,"2691":1,"2692":1,"2693":1,"2696":1,"2703":1,"2706":2,"2718":1,"2723":1,"2736":1,"2741":1,"2755":1,"2763":1,"2766":1,"2769":1,"2775":1,"2776":1,"2785":3,"2788":1,"2793":2,"2810":1,"2814":1,"2826":3,"2829":1,"2831":2,"2833":1,"2836":1,"2841":2,"2851":1,"2853":1,"2856":1,"2859":2,"2866":1,"2867":1,"2868":2,"2870":1,"2871":1,"2874":1}}],["ate",{"2":{"1792":1}}],["attribution",{"2":{"2531":1}}],["attributed",{"2":{"2531":1}}],["attribute",{"2":{"869":1,"873":1,"1193":2,"1429":1,"1447":2,"1792":4,"1808":1,"2425":1,"2428":1,"2436":2,"2459":1}}],["attributes=prefer",{"2":{"1175":1}}],["attributes",{"0":{"1174":1,"1628":1,"1808":1},"2":{"841":1,"869":2,"873":3,"1176":2,"1428":1,"1572":1,"1807":1,"1808":1,"2266":2,"2419":1,"2429":2,"2804":1}}],["attack",{"2":{"2492":1}}],["attacks",{"0":{"933":1},"2":{"934":1,"1185":1,"1188":1,"1252":1,"1448":1,"1487":1,"1493":1,"1709":1,"1717":1,"1769":1,"1792":7,"1942":1,"2016":1,"2017":1,"2018":1,"2020":1,"2632":3,"2633":1,"2634":1}}],["attackers",{"2":{"1706":1}}],["attacker",{"2":{"927":1,"933":2,"940":1,"941":1,"944":1,"1185":1,"1441":1}}],["attaches",{"2":{"2533":1,"2537":1,"2873":1}}],["attached",{"2":{"841":1,"859":1,"1823":1,"2167":1,"2545":1}}],["attach",{"2":{"1822":1,"2531":1,"2533":1}}],["attaching",{"2":{"711":1,"2873":1}}],["attachment",{"2":{"386":2,"392":1,"492":1,"493":2,"543":1,"544":2,"1189":2,"1373":1}}],["attention",{"2":{"1178":1,"1404":1}}],["attempting",{"2":{"2566":1,"2608":1}}],["attempted",{"2":{"1522":1}}],["attempt",{"2":{"1056":2,"1832":1,"1833":1}}],["attempts",{"2":{"41":2,"310":8,"1152":1,"1623":1,"1792":2,"1958":2,"1959":2,"2468":1,"2470":1,"2471":2}}],["attestationobject",{"2":{"1220":1,"1221":1,"1792":2}}],["attestationconveyance",{"0":{"1230":1,"1880":1},"2":{"1217":1,"1227":1,"1792":1,"1877":1,"1893":1}}],["attestation",{"2":{"868":1,"1211":1,"1215":2,"1230":3,"1237":1,"1238":1,"1243":1,"1792":5,"1880":3,"1887":1,"2492":3}}],["attitude",{"2":{"859":1}}],["atomicity",{"2":{"865":1}}],["atomically",{"2":{"860":1,"2106":1,"2537":1}}],["atomic",{"2":{"7":1,"16":1,"18":1,"19":1,"20":1,"21":1,"37":2,"38":2,"39":2,"40":1,"48":1,"50":1,"60":1,"61":1,"62":1,"71":1,"72":1,"88":1,"104":1,"115":1,"116":1,"117":1,"119":1,"128":1,"136":1,"137":1,"157":1,"206":1,"247":1,"248":1,"249":1,"250":1,"254":1,"255":1,"256":1,"257":1,"288":1,"289":1,"290":1,"291":1,"332":1,"333":1,"334":1,"335":1,"360":1,"361":1,"365":1,"366":1,"374":1,"401":1,"405":1,"406":1,"408":2,"436":1,"438":1,"451":2,"453":1,"466":1,"467":1,"468":1,"469":1,"487":1,"488":1,"489":1,"490":1,"491":1,"492":1,"493":1,"503":1,"510":1,"511":1,"520":1,"521":1,"523":1,"539":1,"540":1,"541":1,"542":1,"543":1,"544":1,"545":1,"592":1,"593":1,"594":1,"611":1,"677":1,"679":1,"722":1,"723":1,"733":1,"734":1,"735":1,"736":1,"764":1,"774":1,"797":1,"798":1,"799":1,"811":1,"835":1,"886":1,"904":1,"914":2,"915":1,"916":3,"918":1,"934":1,"935":1,"936":1,"956":1,"974":2,"979":1,"980":1,"982":1,"988":1,"990":1,"994":1,"1054":2,"1055":1,"1057":1,"1058":1,"1113":1,"1135":1,"1138":1,"1139":1,"1141":1,"1142":1,"1149":1,"1179":1,"1188":1,"1192":1,"1308":1,"1310":1,"1331":1,"1337":1,"1345":2,"1357":1,"1362":1,"1368":1,"1438":1,"1547":1,"1632":1,"1655":2,"1920":1,"1973":1,"1974":1,"2076":1,"2078":1,"2079":1,"2775":1,"2822":1}}],["at",{"0":{"981":1,"1023":1,"1265":1,"1390":1,"1460":1,"2405":1,"2750":1,"2751":1},"1":{"982":1,"983":1,"984":1},"2":{"3":1,"21":1,"74":1,"106":1,"108":1,"109":2,"156":1,"168":1,"174":1,"175":1,"179":1,"263":1,"297":1,"308":1,"310":1,"320":1,"322":1,"324":1,"351":1,"372":2,"386":1,"388":1,"390":1,"436":1,"462":1,"527":1,"531":3,"534":1,"566":2,"582":1,"583":1,"584":1,"585":1,"587":3,"646":1,"650":1,"653":1,"654":1,"658":2,"660":2,"661":2,"662":4,"685":3,"694":1,"696":1,"704":1,"706":1,"711":1,"771":1,"817":1,"829":1,"832":1,"835":2,"841":1,"843":2,"844":1,"845":5,"847":2,"848":6,"849":1,"851":3,"852":3,"855":1,"856":3,"857":1,"860":6,"861":1,"863":2,"864":3,"865":1,"867":2,"868":1,"871":3,"872":4,"873":1,"874":1,"876":2,"888":1,"898":1,"910":1,"913":3,"921":2,"922":1,"932":1,"947":2,"948":1,"949":2,"971":1,"973":1,"975":1,"976":1,"977":2,"978":1,"980":3,"982":4,"984":1,"985":1,"989":1,"990":4,"992":1,"995":2,"997":1,"1004":1,"1007":2,"1042":1,"1043":1,"1047":1,"1048":1,"1053":1,"1059":1,"1062":1,"1064":1,"1067":2,"1070":1,"1071":1,"1073":4,"1074":1,"1077":1,"1079":2,"1090":1,"1097":1,"1098":2,"1100":1,"1101":2,"1102":1,"1106":1,"1129":1,"1137":1,"1139":1,"1150":3,"1157":1,"1159":2,"1175":1,"1177":1,"1178":1,"1183":1,"1185":1,"1190":1,"1205":1,"1208":1,"1213":5,"1214":1,"1216":1,"1231":1,"1232":1,"1234":1,"1235":1,"1239":1,"1255":3,"1262":2,"1263":1,"1266":2,"1267":1,"1268":1,"1271":2,"1280":2,"1281":1,"1307":1,"1309":5,"1310":3,"1316":1,"1321":5,"1335":1,"1336":2,"1337":1,"1338":1,"1339":4,"1355":1,"1368":1,"1372":5,"1376":1,"1378":1,"1381":1,"1382":4,"1385":3,"1386":2,"1390":1,"1391":1,"1393":1,"1395":1,"1396":4,"1398":1,"1399":1,"1400":1,"1401":1,"1402":1,"1403":2,"1405":2,"1406":3,"1408":3,"1409":1,"1420":1,"1422":3,"1431":1,"1439":1,"1442":2,"1443":1,"1449":1,"1453":1,"1454":1,"1457":1,"1480":1,"1521":2,"1522":1,"1527":2,"1553":1,"1565":1,"1575":1,"1581":1,"1604":1,"1609":1,"1621":1,"1640":1,"1659":1,"1660":1,"1664":1,"1691":1,"1692":1,"1693":1,"1694":1,"1695":1,"1714":1,"1722":1,"1792":28,"1802":4,"1823":1,"1824":1,"1825":3,"1831":1,"1833":1,"1844":2,"1851":1,"1852":1,"1856":1,"1861":1,"1862":1,"1881":1,"1924":1,"1929":1,"1955":1,"1956":1,"1957":2,"1958":1,"1961":1,"1974":2,"2003":1,"2007":3,"2012":1,"2038":1,"2040":2,"2094":1,"2097":1,"2098":2,"2107":1,"2108":2,"2110":2,"2113":2,"2140":2,"2145":1,"2146":1,"2148":1,"2164":1,"2165":1,"2171":1,"2175":2,"2177":1,"2191":1,"2192":1,"2199":1,"2224":1,"2226":1,"2258":1,"2283":1,"2291":1,"2297":2,"2318":1,"2319":2,"2324":1,"2328":5,"2337":3,"2344":1,"2347":1,"2359":1,"2360":1,"2364":2,"2366":1,"2371":1,"2372":2,"2375":1,"2377":1,"2379":4,"2380":4,"2381":1,"2382":1,"2383":1,"2384":4,"2389":1,"2392":1,"2394":3,"2405":1,"2410":1,"2412":1,"2414":1,"2415":1,"2424":1,"2425":1,"2428":2,"2443":1,"2450":1,"2452":1,"2456":2,"2463":1,"2464":1,"2466":3,"2470":1,"2474":2,"2476":2,"2481":1,"2483":1,"2486":1,"2493":1,"2494":1,"2496":1,"2510":1,"2518":1,"2530":2,"2531":1,"2532":1,"2533":1,"2534":2,"2535":2,"2537":4,"2554":2,"2565":1,"2575":2,"2577":1,"2607":2,"2621":1,"2629":1,"2705":1,"2717":1,"2721":1,"2731":1,"2737":1,"2740":1,"2741":1,"2744":1,"2750":2,"2754":1,"2764":1,"2775":1,"2794":1,"2795":1,"2798":2,"2802":2,"2803":3,"2804":5,"2809":1,"2829":10,"2830":1,"2831":1,"2832":4,"2833":1,"2836":7,"2840":6,"2841":1,"2845":3,"2846":2,"2854":2,"2860":1,"2861":1,"2867":1,"2868":3,"2871":1,"2874":1,"2875":1,"2879":1,"2880":2}}],["a",{"0":{"75":1,"297":1,"308":1,"309":1,"322":2,"324":1,"352":1,"354":1,"388":1,"393":1,"531":1,"621":1,"711":1,"1042":1,"1054":1,"1183":1,"1187":1,"1302":1,"1406":1,"1408":1,"1430":1,"1431":2,"1442":1,"1725":1,"1727":1,"1771":1,"2063":1,"2176":1,"2178":1,"2187":1,"2378":1,"2379":1,"2392":2,"2394":1,"2395":1,"2534":1,"2582":1,"2694":1,"2732":1,"2733":1,"2740":1,"2741":1,"2742":1,"2752":1,"2762":1,"2801":1,"2813":1,"2815":1,"2821":1,"2829":1,"2836":1},"1":{"389":1,"1184":1,"1185":1,"1186":1,"1187":1,"1188":1,"1189":1,"1190":1,"1191":1,"1192":1,"1193":1,"1194":1,"1195":1,"1196":1,"1197":1,"1198":1,"1199":1,"1200":1,"1201":1,"1202":1,"1203":1,"1204":1,"1205":1,"1206":1,"1207":1,"1208":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1407":1,"1408":1,"1409":1,"1410":1,"1411":1,"1412":1,"1413":1,"1414":1,"1415":1,"1416":1,"1417":1,"1418":1,"1419":1,"1420":1,"1421":1,"1422":1,"2177":1,"2178":1},"2":{"0":2,"1":2,"3":2,"7":1,"16":1,"20":3,"22":1,"25":1,"30":1,"34":1,"35":3,"37":1,"41":2,"48":1,"51":1,"57":2,"60":2,"61":1,"63":1,"64":2,"71":1,"74":1,"75":4,"87":2,"101":4,"102":2,"104":1,"106":1,"108":2,"109":2,"115":1,"119":1,"123":1,"128":1,"133":1,"136":1,"140":1,"144":1,"150":1,"156":1,"157":1,"168":4,"169":1,"171":1,"174":2,"175":1,"176":2,"177":2,"184":1,"188":2,"201":1,"202":3,"203":1,"206":3,"209":1,"212":8,"213":1,"214":16,"215":7,"223":2,"227":2,"230":1,"238":1,"239":3,"241":1,"243":1,"244":1,"247":1,"252":2,"261":1,"282":1,"286":1,"288":1,"296":2,"297":5,"298":5,"299":4,"302":2,"303":2,"304":3,"305":2,"306":3,"307":3,"308":4,"309":4,"310":1,"312":2,"313":3,"317":5,"318":1,"319":4,"320":6,"322":2,"323":1,"325":1,"326":3,"327":4,"330":2,"347":1,"348":1,"349":1,"351":1,"352":2,"355":1,"360":1,"363":1,"364":2,"376":3,"377":2,"378":1,"382":2,"383":6,"384":1,"386":4,"387":2,"388":6,"389":7,"390":16,"395":4,"396":1,"397":1,"401":1,"408":2,"414":4,"415":2,"421":1,"422":1,"423":2,"426":1,"429":1,"433":1,"435":2,"436":7,"438":2,"445":1,"446":4,"447":1,"448":2,"453":3,"454":1,"466":1,"473":2,"476":1,"477":1,"479":1,"480":3,"487":1,"491":1,"492":1,"499":1,"503":1,"510":1,"512":1,"520":1,"527":8,"528":6,"529":2,"531":4,"533":1,"534":3,"535":1,"537":1,"539":1,"551":1,"560":1,"562":1,"565":1,"567":4,"569":1,"570":1,"575":1,"581":3,"582":1,"583":1,"585":2,"586":1,"587":1,"592":1,"607":3,"609":2,"613":1,"615":2,"618":3,"619":1,"625":1,"639":1,"641":1,"646":1,"650":10,"654":1,"659":2,"663":3,"664":1,"665":1,"666":2,"669":3,"675":2,"677":1,"679":1,"683":2,"685":1,"689":1,"690":2,"691":1,"693":3,"694":2,"695":4,"698":2,"699":2,"701":2,"704":3,"705":1,"706":1,"708":1,"711":3,"720":2,"722":1,"733":1,"743":1,"747":4,"750":1,"751":1,"759":1,"763":1,"769":1,"772":1,"773":1,"778":2,"797":1,"809":1,"811":1,"817":1,"819":1,"823":1,"826":1,"831":3,"832":2,"833":4,"834":8,"835":12,"836":4,"837":3,"838":4,"840":4,"841":27,"843":5,"844":6,"845":17,"847":9,"848":23,"849":8,"851":24,"852":29,"854":3,"855":6,"856":5,"857":11,"859":6,"860":19,"861":7,"863":10,"864":10,"865":7,"866":3,"867":1,"868":8,"869":12,"871":11,"872":13,"873":14,"874":7,"875":3,"876":23,"877":8,"878":4,"879":3,"880":1,"881":1,"882":1,"885":2,"886":1,"887":1,"894":1,"901":1,"903":2,"904":3,"910":5,"912":1,"913":5,"914":4,"915":2,"916":16,"917":1,"918":6,"919":7,"920":4,"921":2,"922":2,"924":1,"926":1,"927":3,"928":1,"930":1,"932":2,"933":6,"934":1,"938":1,"946":3,"947":3,"948":5,"949":2,"951":2,"956":1,"957":2,"959":3,"961":5,"964":1,"965":1,"967":2,"968":1,"969":1,"971":4,"972":2,"973":2,"974":4,"975":1,"976":3,"977":1,"979":2,"980":2,"982":3,"983":4,"984":1,"985":1,"986":4,"988":3,"989":1,"990":4,"991":1,"992":2,"993":1,"994":3,"995":1,"996":3,"997":2,"1005":3,"1008":1,"1009":2,"1010":4,"1011":3,"1013":1,"1014":2,"1016":1,"1020":2,"1023":1,"1024":1,"1026":1,"1029":1,"1036":1,"1037":16,"1038":8,"1039":6,"1040":3,"1041":6,"1042":2,"1043":5,"1044":4,"1045":6,"1046":1,"1048":2,"1049":1,"1050":2,"1051":1,"1053":1,"1054":1,"1059":1,"1060":1,"1061":2,"1064":4,"1065":2,"1066":1,"1067":10,"1068":5,"1069":5,"1070":8,"1071":1,"1073":14,"1074":6,"1075":9,"1076":2,"1077":3,"1078":3,"1079":13,"1080":9,"1081":5,"1082":1,"1083":3,"1084":4,"1086":9,"1087":2,"1088":5,"1090":2,"1094":3,"1095":6,"1096":7,"1098":8,"1099":3,"1100":1,"1101":5,"1102":9,"1104":2,"1105":9,"1106":3,"1107":5,"1108":7,"1111":9,"1114":1,"1115":4,"1121":4,"1122":1,"1123":2,"1127":7,"1128":1,"1129":3,"1130":2,"1132":2,"1133":3,"1134":2,"1135":8,"1137":2,"1138":1,"1139":2,"1141":2,"1142":2,"1147":1,"1150":8,"1152":1,"1157":2,"1160":1,"1161":2,"1162":12,"1164":2,"1165":2,"1172":1,"1174":1,"1175":1,"1176":1,"1177":1,"1179":1,"1181":2,"1183":4,"1185":4,"1187":1,"1188":1,"1190":4,"1191":1,"1192":5,"1193":4,"1195":1,"1197":1,"1199":2,"1200":2,"1203":3,"1204":2,"1205":1,"1206":4,"1208":1,"1209":4,"1210":5,"1211":4,"1217":1,"1218":2,"1219":2,"1220":2,"1221":1,"1223":1,"1224":1,"1233":1,"1238":1,"1251":1,"1252":1,"1253":1,"1254":1,"1259":1,"1262":1,"1279":1,"1281":3,"1302":5,"1303":2,"1305":3,"1308":1,"1317":1,"1318":1,"1324":5,"1326":1,"1328":2,"1329":2,"1331":3,"1332":1,"1334":1,"1335":3,"1342":1,"1343":1,"1349":1,"1351":2,"1355":2,"1359":1,"1360":2,"1361":1,"1362":1,"1363":2,"1366":1,"1367":1,"1368":4,"1369":1,"1370":3,"1372":2,"1373":3,"1374":1,"1375":3,"1376":2,"1378":4,"1381":5,"1382":22,"1384":4,"1385":14,"1386":19,"1387":1,"1388":6,"1389":4,"1390":3,"1391":5,"1392":3,"1393":5,"1394":9,"1395":7,"1396":6,"1398":9,"1399":3,"1400":7,"1401":8,"1402":7,"1403":17,"1404":6,"1405":10,"1406":2,"1407":3,"1408":1,"1409":7,"1410":6,"1411":2,"1412":3,"1413":3,"1414":3,"1415":3,"1416":4,"1417":2,"1418":1,"1419":3,"1420":3,"1422":4,"1423":7,"1424":3,"1426":3,"1428":2,"1429":3,"1430":4,"1431":7,"1432":3,"1433":1,"1434":1,"1435":5,"1436":2,"1437":2,"1438":1,"1441":5,"1442":1,"1443":2,"1449":1,"1452":1,"1455":1,"1456":2,"1458":6,"1459":3,"1460":4,"1464":1,"1491":1,"1492":1,"1503":1,"1504":2,"1511":2,"1515":1,"1516":1,"1517":2,"1522":3,"1523":2,"1527":3,"1528":1,"1531":1,"1533":1,"1535":1,"1547":1,"1567":4,"1569":3,"1570":1,"1572":4,"1573":1,"1574":2,"1577":2,"1581":2,"1582":3,"1604":1,"1605":2,"1609":1,"1616":1,"1618":1,"1629":1,"1632":2,"1633":2,"1651":3,"1654":1,"1655":3,"1661":1,"1670":1,"1672":2,"1676":1,"1677":1,"1678":1,"1685":1,"1686":1,"1688":1,"1689":2,"1690":1,"1701":1,"1704":1,"1706":2,"1722":1,"1723":2,"1725":1,"1726":1,"1727":5,"1728":1,"1738":9,"1741":1,"1743":12,"1745":1,"1746":1,"1758":1,"1759":3,"1764":1,"1767":1,"1768":3,"1771":3,"1774":1,"1785":1,"1792":197,"1801":1,"1804":1,"1813":1,"1817":1,"1819":1,"1821":1,"1822":6,"1823":4,"1824":18,"1825":2,"1827":2,"1830":1,"1832":1,"1833":2,"1834":5,"1840":2,"1844":2,"1851":1,"1852":6,"1855":1,"1856":2,"1861":2,"1862":5,"1868":4,"1869":2,"1870":2,"1871":1,"1874":1,"1875":1,"1908":1,"1911":3,"1912":2,"1915":1,"1917":2,"1920":2,"1923":1,"1924":3,"1925":10,"1929":4,"1948":1,"1949":1,"1951":2,"1952":3,"1953":2,"1954":2,"1955":6,"1956":1,"1957":6,"1958":4,"1959":3,"1961":4,"1968":1,"1974":7,"1983":1,"1989":1,"1995":1,"2000":1,"2002":2,"2007":3,"2012":1,"2016":3,"2017":1,"2018":1,"2020":1,"2024":1,"2038":1,"2039":1,"2040":10,"2047":3,"2055":1,"2056":1,"2061":1,"2063":2,"2075":2,"2076":1,"2092":2,"2094":3,"2096":2,"2097":2,"2098":3,"2101":2,"2102":2,"2103":1,"2105":1,"2106":8,"2107":5,"2109":3,"2110":7,"2111":8,"2112":4,"2113":2,"2114":1,"2128":1,"2130":1,"2142":1,"2144":3,"2146":5,"2147":1,"2148":2,"2149":1,"2153":1,"2154":1,"2156":8,"2157":4,"2158":2,"2161":1,"2164":6,"2165":1,"2166":3,"2167":5,"2170":2,"2171":6,"2172":1,"2174":1,"2175":2,"2176":6,"2177":6,"2178":1,"2179":2,"2180":1,"2181":2,"2183":2,"2184":2,"2185":5,"2187":4,"2192":1,"2195":2,"2200":1,"2207":2,"2212":1,"2218":1,"2220":1,"2221":2,"2222":6,"2223":2,"2224":5,"2226":4,"2227":1,"2254":2,"2255":5,"2256":2,"2264":6,"2265":7,"2266":3,"2271":2,"2272":1,"2274":2,"2283":2,"2287":1,"2289":1,"2296":2,"2297":2,"2300":3,"2302":2,"2303":1,"2310":2,"2313":1,"2317":2,"2318":1,"2319":5,"2320":3,"2322":1,"2328":2,"2330":2,"2332":1,"2333":3,"2334":2,"2335":1,"2336":1,"2337":4,"2338":2,"2339":5,"2340":1,"2344":1,"2346":2,"2347":3,"2348":2,"2357":1,"2360":1,"2363":1,"2364":1,"2366":1,"2371":2,"2372":1,"2375":12,"2376":1,"2377":2,"2378":1,"2379":12,"2380":11,"2381":3,"2382":1,"2383":7,"2384":2,"2385":2,"2389":5,"2391":6,"2392":2,"2393":2,"2394":5,"2395":8,"2396":1,"2397":4,"2398":4,"2399":1,"2400":2,"2401":1,"2402":3,"2404":1,"2405":4,"2407":4,"2410":2,"2411":1,"2412":1,"2413":1,"2414":4,"2415":1,"2416":3,"2419":5,"2420":2,"2421":2,"2422":3,"2423":3,"2424":4,"2425":2,"2428":1,"2429":4,"2430":5,"2432":2,"2434":2,"2437":6,"2438":14,"2440":1,"2441":1,"2443":1,"2444":2,"2445":2,"2446":1,"2450":4,"2451":4,"2452":5,"2453":4,"2455":1,"2456":1,"2459":5,"2461":6,"2462":1,"2463":4,"2464":4,"2465":1,"2466":12,"2468":3,"2470":4,"2471":3,"2472":4,"2476":11,"2477":2,"2479":3,"2481":28,"2482":14,"2483":6,"2486":1,"2489":6,"2490":5,"2491":4,"2492":4,"2493":5,"2494":1,"2495":5,"2496":2,"2497":6,"2498":2,"2500":2,"2502":15,"2504":6,"2505":5,"2506":1,"2509":3,"2510":1,"2511":3,"2512":1,"2513":3,"2515":2,"2517":7,"2518":4,"2519":4,"2520":3,"2523":2,"2525":2,"2526":1,"2527":7,"2528":10,"2529":15,"2530":12,"2531":11,"2532":20,"2533":15,"2534":8,"2535":2,"2536":1,"2537":25,"2539":2,"2540":10,"2542":7,"2543":9,"2544":2,"2545":5,"2546":9,"2549":1,"2554":3,"2569":1,"2572":1,"2575":2,"2577":1,"2586":5,"2587":2,"2588":3,"2590":1,"2596":1,"2597":1,"2607":7,"2608":2,"2614":2,"2615":1,"2621":2,"2632":3,"2633":5,"2634":8,"2635":4,"2649":1,"2650":1,"2651":1,"2655":2,"2662":1,"2663":2,"2664":3,"2665":2,"2667":1,"2670":1,"2671":1,"2678":3,"2679":2,"2682":2,"2684":1,"2688":2,"2689":1,"2693":1,"2694":1,"2695":4,"2700":2,"2701":1,"2709":1,"2712":1,"2713":1,"2714":1,"2716":1,"2721":3,"2722":2,"2723":3,"2725":4,"2726":2,"2727":2,"2729":2,"2732":5,"2733":2,"2734":1,"2737":1,"2739":2,"2740":2,"2741":1,"2742":3,"2744":1,"2750":1,"2751":1,"2754":1,"2755":2,"2758":1,"2759":5,"2760":3,"2762":5,"2763":1,"2764":3,"2766":1,"2767":1,"2768":2,"2770":1,"2771":1,"2772":4,"2774":4,"2775":1,"2785":1,"2789":2,"2790":1,"2791":1,"2794":2,"2798":1,"2801":1,"2802":1,"2803":6,"2804":3,"2806":6,"2807":1,"2809":2,"2810":2,"2811":4,"2812":5,"2813":2,"2814":1,"2815":6,"2816":1,"2817":1,"2818":1,"2819":1,"2821":3,"2822":1,"2823":1,"2825":1,"2827":4,"2828":12,"2829":4,"2830":4,"2833":5,"2834":4,"2835":6,"2836":5,"2839":1,"2840":1,"2841":3,"2842":2,"2845":6,"2847":2,"2848":2,"2850":3,"2852":1,"2854":4,"2855":4,"2856":1,"2857":3,"2860":4,"2861":2,"2862":6,"2863":3,"2864":4,"2865":12,"2866":2,"2867":3,"2868":7,"2869":5,"2870":1,"2871":9,"2872":2,"2873":11,"2876":3,"2878":9,"2879":4,"2880":1,"2881":8}}],["angeles",{"2":{"2453":1,"2456":1}}],["analogue",{"2":{"2537":1,"2879":1}}],["analyst",{"2":{"1179":1,"1196":1}}],["analysts",{"2":{"836":1,"837":1,"1184":1}}],["analysis",{"0":{"1334":1,"1339":1},"1":{"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1},"2":{"909":1,"1206":1,"1254":1,"1259":1,"1328":1,"1332":2,"1335":4,"1336":3,"1338":4,"1339":5,"1342":1,"1403":1,"2054":1,"2635":1,"2664":1,"2815":2}}],["analyzed",{"2":{"2318":1,"2324":1}}],["analyzesentiment",{"2":{"1335":2}}],["analyze",{"2":{"841":1,"1335":1,"1339":3,"1342":2,"2050":1}}],["analyticsdata",{"2":{"1241":1,"1792":3,"1890":1}}],["analytics",{"0":{"1241":1,"1890":1},"2":{"147":2,"1060":2,"1067":1,"1150":1,"1176":3,"1216":1,"1237":2,"1239":7,"1241":1,"1244":1,"1614":1,"1632":1,"1684":2,"1687":1,"1689":2,"1704":1,"1792":8,"1887":2,"1888":2,"1890":1,"2040":1,"2474":1,"2496":1}}],["anatomy",{"0":{"2528":1,"2863":1},"1":{"2864":1,"2865":1,"2866":1,"2867":1,"2868":1},"2":{"2092":1}}],["animation",{"2":{"1685":1}}],["animal",{"2":{"335":2,"913":1,"918":2,"919":2,"2586":3}}],["anchoring",{"2":{"2435":1}}],["anchored",{"2":{"872":1,"1792":1,"1898":1,"1909":1}}],["ancestors",{"2":{"1493":1,"2028":1}}],["ancient",{"2":{"1443":1}}],["anti",{"2":{"1564":1,"1792":3}}],["anticipate",{"2":{"1080":1}}],["antiforgeryfieldname",{"2":{"1792":2,"2033":2,"2037":2,"2038":2,"2039":1,"2042":2}}],["antiforgerytoken",{"2":{"1792":3,"2033":2,"2037":2,"2038":2,"2039":1,"2042":2}}],["antiforgery",{"0":{"1487":1},"1":{"1488":1,"1489":1,"1490":1,"1491":1,"1492":1,"1493":1,"1494":1,"1495":1,"1496":1},"2":{"868":1,"869":1,"1487":1,"1488":1,"1489":2,"1490":1,"1494":2,"1649":1,"1651":1,"1788":1,"1792":18,"1795":1,"2018":2,"2030":1,"2038":2,"2044":1,"2353":2,"2438":2,"2632":5,"2701":1}}],["ant",{"2":{"1044":1}}],["anthropic",{"2":{"1044":1}}],["ansi",{"2":{"848":3,"860":1}}],["answers",{"2":{"838":2,"876":1,"1427":1,"1431":1,"2795":1,"2831":1}}],["answer",{"2":{"831":1,"838":1,"844":1,"852":1,"863":1,"1075":1,"1079":1,"1082":1,"1401":1,"1424":1,"1435":1,"2804":1}}],["anniversary",{"0":{"839":1},"1":{"840":1,"841":1,"842":1,"843":1,"844":1,"845":1,"846":1,"847":1,"848":1,"849":1,"850":1,"851":1,"852":1,"853":1,"854":1,"855":1,"856":1,"857":1,"858":1,"859":1,"860":1,"861":1,"862":1,"863":1,"864":1,"865":1}}],["announcements",{"2":{"645":1}}],["announcement",{"2":{"645":1,"1315":1,"2882":1}}],["annotate",{"2":{"663":2,"1037":1,"1095":1,"1141":1,"2183":1,"2184":1,"2725":1,"2745":1}}],["annotated",{"2":{"582":1,"587":1,"664":1,"868":1,"871":1,"1305":2,"1722":1,"1792":10,"1861":1,"2171":1,"2176":1,"2264":10,"2337":1,"2389":1,"2420":1,"2423":1,"2481":1,"2534":1,"2754":1}}],["annotations",{"0":{"12":1,"27":1,"43":1,"54":1,"66":1,"77":1,"90":1,"99":1,"123":1,"131":1,"142":1,"152":1,"190":1,"199":1,"218":1,"220":1,"237":1,"238":1,"239":1,"260":1,"276":1,"294":1,"316":1,"346":1,"368":1,"411":1,"416":1,"440":1,"472":1,"482":1,"496":1,"506":1,"514":1,"526":1,"543":1,"548":1,"558":1,"579":1,"597":1,"606":1,"635":1,"648":1,"671":1,"681":1,"726":1,"741":1,"792":1,"805":1,"821":1,"1138":1,"1189":1,"1411":1,"1530":1,"1599":1,"1664":1,"2147":1,"2190":1,"2191":1,"2198":1,"2207":1,"2208":1,"2252":1,"2291":1,"2323":1,"2331":1,"2365":1,"2391":1,"2533":1,"2581":1,"2671":1,"2852":1,"2870":1},"1":{"221":1,"222":1,"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1,"277":1,"278":1,"417":1,"418":1,"419":1,"420":1,"421":1,"441":1,"442":1,"443":1,"444":1,"445":1,"1412":1,"1413":1,"1414":1,"1415":1,"1531":1,"1532":1,"1533":1,"2191":1,"2192":2,"2193":2,"2194":2,"2195":1,"2196":1,"2197":1,"2198":1,"2199":2,"2200":2,"2201":2,"2202":1,"2203":1,"2204":1,"2205":1,"2206":1,"2207":1,"2208":1,"2209":1,"2210":1,"2211":1,"2212":1,"2213":1,"2214":1,"2215":1,"2216":1,"2217":1,"2218":1,"2219":1,"2292":1,"2293":1,"2294":1,"2295":1,"2296":1,"2297":1,"2332":1,"2333":1,"2334":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2342":1,"2343":1,"2344":1},"2":{"11":2,"23":1,"26":2,"42":2,"53":2,"62":1,"65":2,"76":2,"89":2,"98":2,"101":1,"105":1,"122":2,"130":2,"141":2,"151":2,"156":1,"158":1,"159":1,"162":1,"164":2,"176":1,"181":1,"189":2,"198":2,"217":2,"220":6,"221":1,"223":1,"259":2,"261":1,"267":1,"281":2,"293":2,"319":1,"320":1,"327":2,"338":2,"345":2,"356":2,"367":2,"378":1,"385":2,"386":2,"387":2,"396":2,"410":2,"438":1,"470":1,"471":2,"481":2,"495":2,"505":2,"513":2,"515":1,"525":2,"544":1,"547":2,"556":1,"557":2,"560":1,"568":2,"577":1,"578":2,"587":1,"588":2,"596":2,"605":2,"617":2,"619":1,"626":2,"634":2,"647":2,"659":1,"670":2,"680":2,"683":2,"687":2,"706":1,"708":1,"712":1,"718":1,"725":2,"740":2,"790":2,"804":2,"815":1,"817":1,"820":2,"830":2,"868":1,"873":1,"910":1,"964":1,"1037":1,"1065":1,"1080":1,"1086":1,"1098":1,"1100":1,"1104":1,"1105":1,"1108":1,"1125":2,"1126":2,"1135":1,"1138":1,"1174":1,"1181":1,"1195":1,"1196":1,"1208":1,"1304":1,"1309":1,"1327":1,"1358":2,"1366":1,"1371":1,"1386":2,"1398":1,"1401":3,"1406":2,"1407":1,"1411":1,"1465":2,"1484":2,"1495":2,"1506":2,"1511":1,"1530":1,"1535":2,"1549":2,"1576":1,"1580":1,"1583":2,"1600":2,"1610":2,"1634":2,"1647":2,"1649":1,"1651":1,"1664":1,"1665":3,"1679":2,"1699":2,"1748":2,"1760":2,"1792":22,"1802":1,"1811":2,"1816":1,"1834":2,"1840":3,"1843":1,"1855":1,"1864":2,"1913":2,"1917":1,"1932":2,"1945":2,"1961":1,"1962":2,"1976":2,"1996":2,"2000":2,"2004":2,"2005":1,"2043":2,"2074":1,"2079":1,"2081":2,"2090":2,"2094":1,"2111":1,"2114":1,"2120":2,"2133":2,"2139":1,"2150":2,"2156":1,"2169":2,"2185":1,"2189":1,"2190":3,"2191":1,"2192":3,"2193":2,"2198":1,"2207":2,"2208":1,"2209":3,"2210":1,"2219":2,"2221":1,"2226":1,"2230":1,"2237":1,"2249":1,"2252":5,"2255":1,"2257":2,"2267":1,"2282":1,"2291":1,"2318":1,"2323":5,"2333":1,"2340":2,"2346":1,"2348":1,"2353":2,"2365":3,"2366":1,"2378":1,"2380":1,"2388":1,"2389":1,"2391":1,"2407":1,"2481":1,"2482":1,"2487":1,"2531":1,"2533":3,"2537":1,"2540":3,"2542":1,"2549":1,"2581":3,"2586":1,"2591":1,"2596":1,"2653":1,"2654":1,"2671":2,"2679":1,"2706":3,"2731":1,"2749":1,"2772":1,"2773":1,"2785":3,"2793":3,"2814":1,"2826":3,"2840":1,"2845":3,"2852":1,"2856":1,"2869":1,"2870":1,"2873":2,"2882":1}}],["annotation",{"0":{"40":1,"222":1,"747":1,"753":1,"757":1,"768":1,"776":1,"890":1,"957":1,"960":1,"1358":1,"1862":1,"2195":1,"2295":1,"2300":1,"2314":1,"2332":1,"2337":1,"2338":1,"2339":1,"2340":1,"2341":1,"2343":1,"2344":1,"2366":1,"2380":1,"2432":1,"2483":1,"2487":1,"2591":1},"1":{"223":1,"224":1,"225":1,"226":1,"227":1,"228":1,"229":1,"230":1,"231":1,"232":1,"233":1,"234":1,"235":1,"236":1,"237":1,"238":1,"239":1,"240":1,"891":1,"892":1,"2196":1,"2197":1,"2301":1,"2302":1,"2303":1,"2304":1,"2305":1,"2306":1,"2307":1,"2308":1,"2309":1,"2342":1,"2343":1,"2381":1},"2":{"1":1,"31":3,"38":1,"41":3,"52":1,"63":2,"101":1,"102":1,"109":1,"110":1,"121":1,"133":1,"165":1,"167":1,"170":1,"215":1,"221":1,"244":3,"277":1,"296":1,"306":1,"309":1,"317":2,"319":1,"327":2,"330":1,"332":1,"335":2,"336":2,"337":1,"347":1,"356":3,"357":1,"364":2,"369":2,"386":1,"387":2,"395":1,"397":1,"409":1,"414":2,"422":2,"423":2,"430":1,"435":1,"436":3,"446":6,"452":1,"458":1,"527":1,"529":1,"534":1,"559":1,"560":1,"565":2,"567":2,"581":1,"582":1,"609":1,"616":1,"619":1,"624":1,"646":3,"650":1,"666":1,"667":1,"686":1,"693":2,"703":1,"704":1,"706":1,"708":2,"709":1,"711":2,"713":1,"714":1,"745":1,"868":3,"869":4,"876":1,"886":1,"910":1,"917":1,"934":2,"935":1,"947":1,"949":1,"957":1,"968":1,"1027":1,"1037":1,"1038":1,"1040":1,"1047":1,"1055":1,"1057":2,"1060":1,"1086":2,"1097":1,"1099":1,"1100":1,"1101":1,"1108":2,"1111":1,"1127":1,"1135":2,"1150":1,"1176":1,"1179":1,"1181":5,"1182":4,"1183":1,"1189":1,"1195":1,"1196":1,"1305":1,"1308":1,"1309":1,"1311":1,"1316":1,"1328":1,"1331":1,"1351":1,"1352":2,"1357":1,"1358":1,"1366":1,"1367":2,"1374":1,"1382":3,"1396":1,"1405":1,"1408":1,"1410":1,"1414":1,"1415":1,"1416":1,"1422":1,"1427":1,"1465":4,"1472":1,"1484":2,"1506":3,"1511":1,"1519":1,"1521":2,"1527":1,"1533":1,"1535":3,"1540":2,"1544":2,"1549":2,"1573":2,"1583":1,"1588":1,"1599":1,"1600":1,"1634":1,"1664":1,"1665":1,"1670":1,"1672":1,"1679":2,"1686":1,"1699":3,"1747":1,"1748":2,"1792":27,"1811":1,"1813":1,"1822":1,"1824":1,"1827":1,"1834":3,"1837":1,"1840":4,"1844":1,"1858":2,"1860":3,"1861":1,"1862":1,"1864":8,"1908":1,"1910":1,"1913":2,"1917":2,"1922":1,"1929":3,"1930":1,"1932":3,"1947":1,"1949":1,"1951":1,"1959":1,"1962":1,"1973":1,"2000":1,"2004":2,"2006":1,"2008":1,"2010":2,"2072":1,"2075":2,"2077":2,"2081":1,"2097":3,"2133":1,"2143":1,"2147":1,"2150":1,"2156":1,"2167":1,"2191":1,"2192":1,"2193":1,"2194":2,"2195":2,"2200":1,"2206":1,"2208":1,"2209":1,"2212":1,"2216":1,"2217":2,"2223":4,"2225":1,"2228":1,"2229":2,"2247":1,"2251":1,"2253":1,"2257":1,"2258":1,"2273":1,"2283":1,"2284":1,"2295":1,"2302":1,"2308":1,"2314":1,"2319":2,"2320":1,"2321":1,"2323":1,"2325":1,"2327":1,"2329":1,"2330":2,"2332":2,"2333":1,"2335":1,"2336":1,"2337":1,"2338":1,"2339":2,"2343":1,"2365":2,"2366":6,"2372":1,"2380":5,"2381":1,"2391":2,"2392":1,"2407":2,"2419":1,"2430":1,"2432":1,"2433":1,"2435":2,"2437":2,"2438":1,"2466":1,"2471":1,"2481":3,"2482":1,"2483":1,"2487":3,"2490":1,"2493":1,"2494":1,"2518":1,"2531":1,"2533":3,"2537":1,"2539":1,"2542":1,"2545":1,"2546":1,"2575":1,"2580":1,"2587":2,"2591":2,"2597":1,"2611":1,"2629":1,"2641":1,"2650":1,"2721":4,"2722":1,"2727":1,"2732":1,"2795":1,"2797":2,"2798":1,"2808":1,"2811":3,"2814":1,"2824":3,"2825":1,"2828":1,"2831":1,"2832":1,"2835":1,"2841":2,"2843":2,"2846":1,"2848":1,"2849":1,"2854":1,"2861":1,"2869":1,"2870":1}}],["another",{"2":{"24":1,"237":1,"357":1,"385":1,"421":1,"445":1,"707":1,"717":1,"833":1,"840":1,"841":1,"844":1,"848":1,"852":1,"864":1,"872":1,"873":1,"919":1,"948":2,"1011":1,"1133":1,"1138":1,"1196":2,"1270":1,"1385":1,"1386":1,"1390":2,"1401":2,"1402":1,"1403":1,"1409":1,"1418":1,"1429":1,"1433":1,"1571":1,"1792":3,"1929":1,"1974":1,"2202":2,"2346":1,"2527":1,"2607":1,"2759":1,"2760":1,"2767":1,"2811":1,"2830":1,"2862":1}}],["anonymize",{"2":{"1792":1}}],["anonymized",{"2":{"866":1,"1230":1,"1880":1}}],["anonymously",{"2":{"1825":1,"2481":1}}],["anonymous",{"0":{"4":1,"2201":1,"2824":1},"1":{"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1},"2":{"4":1,"5":1,"7":2,"9":1,"27":1,"224":1,"298":3,"309":1,"312":2,"313":1,"316":1,"479":1,"690":1,"710":1,"798":1,"934":2,"994":1,"996":2,"1005":1,"1045":1,"1055":1,"1069":5,"1076":1,"1077":1,"1098":1,"1101":1,"1162":3,"1308":1,"1371":1,"1394":2,"1396":1,"1427":1,"1431":1,"1458":1,"1465":1,"1620":1,"1792":5,"1827":1,"1833":1,"1898":1,"1955":1,"1956":1,"1957":1,"1960":1,"2175":1,"2176":3,"2183":1,"2187":3,"2189":1,"2201":1,"2214":1,"2319":1,"2323":1,"2329":1,"2379":4,"2420":1,"2421":1,"2422":1,"2428":1,"2481":1,"2490":1,"2498":1,"2529":1,"2540":2,"2728":1,"2762":2,"2764":1,"2821":1,"2824":3,"2865":1}}],["anon",{"2":{"4":2,"8":1,"1162":2,"1955":1,"2379":1}}],["any>",{"2":{"1574":1}}],["anywhere",{"2":{"848":1,"852":1,"994":1,"1441":1,"1607":1,"2662":1}}],["anyway",{"0":{"855":1},"2":{"841":2,"861":1,"873":1,"992":1,"1386":1,"1402":1,"2528":1}}],["anymore",{"2":{"843":1,"1386":2,"1401":1,"1405":1,"2253":1}}],["anything",{"2":{"308":1,"527":1,"841":1,"864":1,"876":2,"1011":1,"1067":1,"1073":1,"1081":1,"1095":1,"1096":1,"1139":1,"1162":1,"1386":1,"1438":1,"1441":1,"1651":1,"1655":1,"1792":3,"2040":1,"2111":1,"2171":1,"2177":1,"2180":1,"2185":1,"2395":1,"2411":1,"2450":1,"2452":1,"2477":1,"2542":1,"2728":1,"2795":1,"2874":1}}],["any",{"0":{"16":1,"2729":1},"2":{"22":1,"40":1,"74":1,"104":1,"105":1,"165":1,"168":1,"212":1,"213":2,"214":1,"301":1,"302":1,"308":2,"319":1,"334":1,"337":1,"388":1,"390":1,"408":1,"436":1,"448":2,"480":1,"528":1,"582":1,"587":1,"625":1,"643":1,"644":1,"664":1,"668":1,"669":1,"687":1,"690":1,"691":1,"760":2,"761":1,"763":1,"770":2,"771":1,"773":1,"777":1,"807":1,"829":1,"834":1,"841":1,"844":2,"845":2,"848":4,"851":2,"863":1,"864":2,"867":1,"868":1,"869":1,"871":1,"872":1,"873":1,"876":1,"882":1,"888":1,"901":1,"903":1,"918":1,"919":2,"922":1,"926":1,"928":1,"934":1,"947":2,"966":1,"971":1,"978":1,"982":2,"989":1,"996":1,"997":1,"1005":1,"1015":1,"1017":1,"1024":2,"1032":1,"1049":2,"1057":1,"1063":1,"1067":1,"1068":2,"1070":1,"1076":1,"1078":1,"1079":1,"1084":1,"1086":1,"1088":1,"1094":2,"1097":1,"1099":1,"1104":1,"1121":1,"1132":1,"1133":1,"1138":1,"1174":5,"1206":1,"1228":1,"1230":1,"1232":1,"1239":1,"1325":1,"1335":1,"1342":1,"1351":1,"1363":1,"1378":2,"1384":1,"1385":4,"1386":3,"1388":1,"1390":1,"1391":1,"1393":1,"1396":1,"1398":2,"1399":1,"1401":2,"1402":1,"1403":2,"1409":1,"1410":1,"1416":1,"1419":1,"1422":1,"1453":1,"1457":1,"1458":1,"1464":2,"1526":1,"1527":1,"1553":1,"1559":1,"1581":2,"1606":1,"1617":1,"1621":1,"1628":5,"1641":1,"1662":1,"1697":1,"1708":1,"1709":1,"1740":1,"1741":1,"1792":34,"1802":1,"1850":1,"1875":1,"1880":1,"1909":1,"1958":1,"1967":1,"1974":1,"2002":2,"2003":2,"2021":1,"2025":1,"2036":1,"2040":1,"2047":1,"2094":1,"2097":1,"2098":1,"2106":1,"2111":1,"2137":1,"2160":1,"2162":1,"2184":1,"2199":1,"2200":1,"2247":2,"2266":6,"2288":1,"2289":1,"2320":1,"2335":1,"2343":1,"2371":4,"2375":1,"2376":1,"2377":1,"2383":1,"2384":1,"2389":1,"2394":1,"2413":1,"2414":1,"2421":1,"2423":1,"2427":1,"2431":1,"2442":1,"2444":1,"2446":1,"2451":1,"2461":1,"2477":1,"2482":1,"2490":1,"2492":1,"2502":1,"2518":1,"2528":2,"2529":1,"2530":1,"2531":1,"2532":1,"2535":1,"2537":2,"2542":1,"2543":1,"2554":1,"2575":1,"2607":1,"2625":1,"2633":2,"2635":1,"2665":1,"2681":1,"2682":1,"2689":1,"2719":1,"2741":1,"2752":1,"2759":1,"2763":1,"2764":1,"2795":1,"2806":2,"2811":1,"2820":1,"2825":1,"2827":1,"2828":1,"2829":1,"2830":2,"2840":1,"2858":1,"2864":1,"2868":1,"2871":1,"2874":2,"2878":1}}],["anyone",{"2":{"9":1,"844":1,"851":2,"876":1,"977":1,"980":1,"990":1,"1220":1,"1254":1,"1403":2,"1870":1,"2490":1}}],["an",{"0":{"75":1,"351":1,"531":1,"955":1,"1038":1,"1044":1,"1724":1,"2172":1,"2378":1,"2429":1,"2544":1,"2714":1,"2721":1,"2722":1,"2723":1,"2725":1,"2728":1},"1":{"956":1,"957":1,"958":1,"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1725":1,"1726":1,"1727":1,"2173":1,"2174":1,"2175":1},"2":{"0":1,"3":1,"74":2,"75":2,"77":1,"101":1,"171":1,"174":2,"177":2,"181":1,"192":1,"197":1,"202":2,"212":1,"214":1,"215":1,"220":1,"227":1,"239":3,"241":1,"244":1,"261":2,"284":1,"296":2,"297":1,"299":1,"307":1,"317":2,"318":1,"319":3,"320":4,"322":1,"326":1,"327":1,"347":1,"388":2,"389":1,"390":6,"394":1,"395":1,"412":1,"414":1,"422":1,"426":1,"427":1,"428":1,"433":1,"435":2,"436":1,"447":1,"453":1,"463":2,"476":1,"477":1,"478":1,"528":1,"529":3,"533":1,"551":1,"567":1,"587":1,"639":1,"650":3,"663":1,"669":1,"679":2,"688":2,"689":2,"690":2,"701":1,"704":1,"706":2,"711":2,"714":1,"747":1,"777":2,"782":1,"784":1,"819":1,"823":1,"829":1,"833":2,"834":2,"835":3,"837":4,"838":1,"841":1,"844":1,"845":2,"847":2,"848":4,"851":16,"852":7,"854":1,"857":3,"859":3,"860":2,"863":2,"864":4,"865":2,"866":1,"868":1,"869":1,"871":4,"872":7,"873":1,"877":1,"903":1,"916":1,"918":2,"921":1,"928":1,"933":2,"934":2,"940":1,"944":1,"946":1,"948":2,"949":1,"961":1,"966":1,"980":1,"983":1,"986":1,"991":1,"1010":1,"1014":1,"1016":1,"1037":5,"1038":3,"1039":1,"1040":2,"1041":1,"1042":1,"1043":2,"1045":2,"1048":1,"1051":1,"1059":1,"1063":1,"1068":2,"1069":2,"1073":1,"1074":1,"1078":1,"1080":2,"1095":1,"1096":1,"1098":1,"1101":1,"1102":2,"1105":5,"1114":1,"1127":1,"1150":1,"1157":2,"1171":1,"1180":1,"1183":1,"1185":1,"1204":1,"1220":1,"1221":1,"1222":1,"1226":1,"1233":1,"1238":1,"1302":1,"1305":2,"1309":2,"1328":1,"1335":1,"1342":1,"1358":1,"1367":1,"1370":2,"1381":1,"1382":6,"1383":1,"1384":1,"1385":3,"1386":3,"1388":1,"1389":1,"1394":2,"1395":1,"1396":3,"1398":1,"1399":2,"1403":1,"1404":1,"1405":1,"1406":1,"1409":1,"1410":5,"1412":1,"1413":1,"1415":1,"1416":1,"1419":1,"1421":1,"1424":1,"1426":2,"1427":2,"1431":1,"1432":3,"1437":1,"1449":2,"1504":1,"1511":1,"1515":1,"1521":1,"1527":1,"1559":2,"1569":2,"1571":2,"1573":1,"1639":1,"1640":1,"1644":1,"1661":1,"1664":1,"1670":1,"1723":1,"1726":1,"1737":1,"1738":2,"1743":1,"1759":1,"1785":1,"1792":45,"1813":1,"1822":3,"1823":1,"1824":4,"1825":7,"1827":2,"1830":1,"1832":2,"1833":2,"1834":2,"1840":6,"1850":1,"1852":2,"1855":1,"1861":1,"1870":1,"1872":1,"1876":1,"1909":1,"1911":1,"1912":1,"1915":2,"1917":1,"1922":1,"1923":1,"1925":4,"1948":2,"1949":1,"1955":1,"1974":1,"2004":5,"2006":1,"2007":1,"2039":1,"2040":4,"2054":1,"2076":1,"2077":1,"2078":1,"2092":1,"2094":1,"2097":1,"2101":1,"2106":2,"2107":1,"2110":1,"2111":5,"2112":1,"2113":1,"2126":1,"2127":1,"2140":1,"2155":2,"2156":1,"2164":5,"2165":1,"2166":3,"2167":1,"2170":1,"2171":1,"2173":1,"2177":1,"2178":1,"2184":1,"2185":1,"2186":1,"2193":1,"2195":3,"2221":1,"2222":3,"2223":1,"2264":2,"2265":2,"2274":1,"2282":2,"2283":1,"2291":1,"2297":1,"2300":2,"2319":1,"2330":1,"2336":1,"2337":1,"2338":1,"2344":1,"2346":1,"2350":1,"2353":1,"2367":2,"2376":2,"2378":3,"2379":2,"2380":1,"2382":1,"2383":2,"2392":1,"2403":1,"2404":1,"2411":1,"2416":1,"2419":1,"2422":1,"2425":2,"2430":1,"2433":1,"2437":1,"2438":1,"2444":1,"2461":1,"2462":1,"2465":1,"2466":3,"2476":5,"2477":1,"2479":3,"2481":7,"2482":7,"2483":2,"2484":3,"2486":1,"2487":1,"2491":1,"2492":2,"2493":1,"2494":1,"2496":1,"2497":1,"2498":1,"2502":1,"2504":2,"2509":2,"2515":1,"2517":2,"2518":1,"2519":2,"2520":1,"2521":2,"2522":1,"2523":1,"2525":1,"2526":1,"2527":1,"2528":3,"2529":6,"2530":1,"2531":6,"2532":6,"2533":7,"2534":1,"2535":1,"2537":6,"2539":4,"2540":2,"2541":2,"2542":1,"2543":2,"2544":2,"2545":1,"2549":2,"2575":1,"2586":1,"2596":1,"2597":1,"2607":1,"2626":1,"2648":3,"2649":1,"2652":1,"2663":1,"2674":1,"2678":1,"2679":1,"2685":1,"2696":1,"2713":2,"2716":1,"2719":1,"2721":2,"2722":1,"2723":1,"2734":1,"2759":1,"2760":2,"2764":2,"2766":1,"2768":1,"2794":1,"2797":2,"2802":1,"2805":1,"2806":1,"2807":1,"2808":1,"2812":2,"2814":1,"2815":2,"2816":2,"2817":2,"2825":1,"2828":2,"2830":1,"2831":1,"2832":2,"2833":3,"2835":2,"2838":2,"2841":4,"2842":1,"2843":2,"2845":1,"2852":2,"2855":1,"2861":2,"2862":2,"2864":3,"2865":5,"2869":3,"2870":1,"2871":4,"2876":1,"2878":1,"2879":2,"2881":1}}],["and",{"0":{"22":1,"207":1,"250":1,"313":1,"322":1,"364":1,"420":1,"423":1,"444":1,"533":1,"777":1,"854":1,"862":1,"877":1,"878":1,"879":1,"880":1,"897":1,"902":1,"904":1,"912":1,"920":1,"921":1,"963":1,"964":1,"988":1,"1013":1,"1014":1,"1048":1,"1054":1,"1066":1,"1079":1,"1080":1,"1096":1,"1097":1,"1100":1,"1104":1,"1135":1,"1190":1,"1209":1,"1210":1,"1279":1,"1302":1,"1352":1,"1428":1,"1432":1,"1479":1,"1564":1,"1582":1,"1605":1,"1838":1,"1841":1,"1846":1,"1909":1,"1958":1,"2097":1,"2107":1,"2112":1,"2258":1,"2267":1,"2273":1,"2286":1,"2314":1,"2323":1,"2326":1,"2332":1,"2337":1,"2341":1,"2346":1,"2359":1,"2365":1,"2391":1,"2397":1,"2414":1,"2425":1,"2428":1,"2433":1,"2489":1,"2497":1,"2531":1,"2532":2,"2533":1,"2555":1,"2572":1,"2576":1,"2589":1,"2628":1,"2678":1,"2688":1,"2724":1,"2729":1,"2749":1,"2762":1,"2765":1,"2768":1,"2775":1,"2797":1,"2804":1,"2812":1,"2824":1,"2855":1,"2871":1,"2873":1,"2877":1},"1":{"365":1,"366":1,"863":1,"864":1,"865":1,"879":1,"880":1,"881":1,"882":1,"883":1,"884":1,"885":1,"886":1,"887":1,"888":1,"889":1,"890":1,"891":1,"892":1,"893":1,"894":1,"895":1,"896":1,"897":1,"898":1,"899":1,"900":1,"901":1,"902":1,"903":2,"904":1,"905":1,"906":1,"907":1,"908":1,"909":1,"910":1,"911":1,"913":1,"914":1,"915":1,"916":1,"917":1,"918":1,"919":1,"920":1,"922":1,"923":1,"924":1,"925":1,"926":1,"927":1,"928":1,"929":1,"930":1,"931":1,"932":1,"933":1,"934":1,"935":1,"936":1,"937":1,"938":1,"939":1,"940":1,"941":1,"942":1,"943":1,"944":1,"945":1,"946":1,"1049":1,"1050":1,"1051":1,"1052":1,"1053":1,"1054":1,"1055":1,"1056":1,"1057":1,"1058":1,"1059":1,"1060":1,"1061":1,"1062":1,"1063":1,"1064":1,"1065":1,"1067":1,"1068":1,"1069":1,"1070":1,"1071":1,"1105":1,"1106":1,"1107":1,"1108":1,"1136":1,"1137":1,"1138":1,"1139":1,"1140":1,"1141":1,"1142":1,"1143":1,"1144":1,"1145":1,"1146":1,"1147":1,"1148":1,"1149":1,"1150":1,"1151":1,"1152":1,"1153":1,"1154":1,"1155":1,"1156":1,"1157":1,"1158":1,"1159":1,"1160":1,"1161":1,"1162":1,"1163":1,"1164":1,"1165":1,"1166":1,"1167":1,"1168":1,"1169":1,"1170":1,"1171":1,"1172":1,"1173":1,"1174":1,"1175":1,"1176":1,"1177":1,"1178":1,"1179":1,"1180":1,"1181":1,"1182":1,"1191":1,"1192":1,"1193":1,"1210":1,"1211":1,"1212":1,"1213":1,"1214":1,"1215":1,"1216":1,"1217":1,"1218":1,"1219":1,"1220":1,"1221":1,"1222":1,"1223":1,"1224":1,"1225":1,"1226":1,"1227":1,"1228":1,"1229":1,"1230":1,"1231":1,"1232":1,"1233":1,"1234":1,"1235":1,"1236":1,"1237":1,"1238":1,"1239":1,"1240":1,"1241":1,"1242":1,"1243":1,"1244":1,"1245":1,"1246":1,"1247":1,"1248":1,"1249":1,"1250":1,"1251":1,"1252":1,"1253":1,"1303":1,"1304":1,"1305":1,"1306":1,"1307":1,"1308":1,"1309":1,"1310":1,"1311":1,"1312":1,"1313":1,"1314":1,"1315":1,"1316":1,"1317":1,"1318":1,"1319":1,"1320":1,"1321":1,"1322":1,"1323":1,"1324":1,"1325":1,"1326":1,"1327":1,"1353":1,"1354":1,"1355":1,"1356":1,"1357":1,"1358":1,"1359":1,"1360":1,"1361":1,"1362":1,"1363":1,"1364":1,"1365":1,"1366":1,"1367":1,"1480":1,"1481":1,"1839":1,"1842":1,"1847":1,"1959":1,"2259":1,"2342":1,"2343":1,"2398":1,"2415":1,"2416":1,"2426":1,"2427":1,"2428":1,"2429":1},"2":{"0":1,"1":7,"2":1,"3":2,"10":1,"11":1,"25":1,"26":1,"29":1,"30":1,"32":1,"37":1,"41":3,"45":1,"60":1,"65":1,"74":5,"75":2,"88":1,"100":1,"101":3,"102":2,"104":1,"105":1,"106":1,"110":1,"111":1,"119":1,"120":1,"124":1,"155":1,"156":1,"161":1,"168":1,"170":3,"182":1,"186":1,"187":1,"188":2,"189":1,"191":1,"202":1,"212":1,"213":3,"214":5,"215":2,"220":2,"228":1,"232":1,"237":1,"238":1,"245":1,"261":1,"265":1,"268":1,"286":2,"290":1,"293":1,"296":1,"297":1,"298":2,"299":2,"300":2,"302":2,"305":2,"306":2,"307":2,"308":5,"309":4,"310":2,"313":1,"315":2,"317":2,"319":2,"320":2,"322":2,"324":1,"325":1,"334":1,"337":1,"338":2,"347":1,"349":1,"353":1,"362":1,"364":1,"369":2,"370":1,"372":1,"373":1,"374":1,"375":1,"378":1,"383":2,"384":2,"385":1,"386":1,"387":1,"388":7,"389":1,"390":3,"393":1,"394":1,"396":1,"408":1,"414":4,"415":2,"420":1,"423":4,"424":1,"426":1,"428":1,"429":1,"432":1,"435":1,"436":5,"438":1,"439":2,"444":1,"446":2,"448":4,"449":2,"452":4,"453":3,"454":1,"457":1,"480":1,"493":1,"515":1,"518":1,"520":2,"527":4,"529":2,"535":1,"544":1,"559":1,"560":1,"568":1,"581":1,"583":1,"585":1,"587":2,"592":1,"596":1,"614":1,"615":1,"618":1,"619":1,"621":1,"622":1,"624":1,"636":1,"638":1,"641":1,"650":2,"653":1,"654":1,"656":2,"663":1,"665":1,"666":1,"667":1,"668":1,"669":4,"675":1,"680":2,"682":1,"683":2,"684":1,"687":1,"689":1,"690":1,"691":1,"694":1,"702":1,"704":1,"705":1,"709":1,"710":1,"720":2,"723":1,"725":1,"736":2,"746":2,"747":3,"756":1,"759":1,"766":1,"767":1,"769":1,"772":1,"790":2,"791":2,"793":1,"799":2,"812":2,"814":1,"818":2,"819":1,"828":1,"829":1,"831":2,"832":2,"833":4,"834":4,"835":4,"836":4,"837":3,"838":4,"840":10,"841":19,"843":9,"844":10,"845":18,"847":12,"848":18,"849":15,"851":25,"852":22,"854":5,"855":3,"856":3,"857":8,"859":10,"860":11,"861":6,"863":3,"864":9,"865":12,"866":1,"868":13,"869":6,"871":6,"872":21,"873":7,"874":8,"875":1,"876":10,"877":3,"878":4,"879":4,"880":2,"884":2,"885":1,"886":1,"889":1,"893":1,"902":1,"903":3,"904":1,"905":1,"910":4,"911":3,"912":4,"913":10,"914":5,"915":5,"916":4,"917":2,"918":17,"919":6,"920":6,"921":2,"922":1,"924":2,"926":3,"927":1,"928":1,"930":4,"932":1,"933":1,"934":2,"936":1,"937":3,"942":1,"946":3,"947":1,"948":5,"952":2,"954":1,"957":1,"959":2,"961":1,"963":1,"965":1,"970":2,"971":3,"973":2,"974":3,"975":1,"976":2,"977":1,"978":1,"980":1,"985":1,"986":5,"987":1,"988":1,"994":1,"995":1,"996":2,"997":1,"1000":1,"1005":5,"1007":2,"1008":1,"1009":1,"1010":2,"1011":2,"1012":1,"1013":2,"1014":1,"1017":1,"1019":1,"1021":1,"1029":1,"1032":2,"1033":1,"1037":30,"1038":6,"1039":2,"1041":1,"1042":4,"1043":3,"1044":2,"1045":4,"1046":3,"1047":2,"1048":2,"1049":2,"1054":4,"1056":2,"1060":2,"1061":3,"1063":1,"1064":6,"1065":3,"1066":1,"1067":7,"1068":3,"1069":2,"1070":6,"1071":2,"1073":9,"1074":6,"1075":6,"1076":7,"1077":3,"1078":5,"1079":10,"1080":7,"1081":4,"1082":3,"1083":2,"1084":1,"1086":8,"1087":1,"1088":3,"1090":1,"1094":6,"1095":2,"1096":7,"1097":2,"1098":7,"1099":2,"1100":6,"1101":3,"1102":8,"1103":1,"1104":1,"1105":11,"1106":6,"1107":6,"1108":1,"1110":1,"1111":7,"1113":4,"1114":2,"1115":3,"1117":1,"1118":2,"1119":2,"1121":1,"1123":3,"1125":1,"1126":1,"1127":6,"1128":1,"1129":6,"1130":2,"1132":2,"1133":4,"1134":1,"1135":3,"1136":1,"1137":1,"1138":2,"1139":2,"1141":1,"1142":1,"1150":5,"1151":1,"1153":1,"1156":1,"1158":1,"1162":2,"1164":1,"1165":2,"1167":1,"1169":1,"1170":2,"1171":1,"1172":1,"1174":1,"1177":3,"1179":2,"1181":3,"1182":2,"1183":2,"1184":1,"1185":1,"1187":1,"1190":2,"1191":1,"1193":4,"1200":1,"1202":2,"1203":1,"1205":1,"1206":3,"1207":2,"1208":2,"1209":1,"1210":3,"1211":3,"1213":1,"1214":1,"1215":2,"1216":2,"1218":1,"1220":2,"1221":1,"1223":1,"1231":1,"1234":1,"1235":4,"1243":4,"1244":3,"1248":1,"1249":1,"1252":3,"1253":1,"1254":12,"1255":4,"1258":2,"1259":1,"1263":1,"1268":1,"1271":1,"1272":2,"1277":1,"1279":2,"1280":2,"1281":5,"1285":1,"1302":2,"1303":1,"1304":2,"1305":3,"1308":1,"1309":1,"1323":1,"1324":2,"1325":2,"1326":3,"1327":5,"1328":2,"1331":1,"1332":1,"1337":2,"1338":1,"1339":5,"1351":2,"1352":2,"1353":1,"1354":3,"1355":2,"1359":1,"1360":1,"1363":1,"1366":5,"1367":1,"1368":2,"1371":2,"1372":2,"1373":2,"1376":5,"1377":1,"1378":5,"1379":2,"1381":3,"1382":19,"1384":4,"1385":38,"1386":32,"1387":1,"1389":1,"1390":7,"1391":4,"1392":1,"1393":10,"1394":7,"1395":5,"1396":11,"1397":1,"1398":18,"1399":12,"1400":6,"1401":21,"1402":18,"1403":29,"1404":13,"1405":14,"1406":8,"1407":6,"1408":4,"1409":7,"1410":4,"1412":2,"1413":2,"1416":3,"1417":1,"1418":3,"1419":7,"1420":1,"1421":1,"1422":2,"1423":6,"1424":1,"1426":1,"1427":3,"1428":2,"1429":6,"1430":2,"1431":5,"1432":2,"1434":1,"1435":5,"1436":4,"1437":3,"1438":3,"1439":3,"1440":3,"1441":3,"1442":3,"1443":2,"1444":1,"1448":1,"1449":1,"1453":1,"1455":1,"1456":1,"1458":2,"1460":5,"1464":1,"1466":2,"1468":1,"1470":1,"1472":1,"1473":1,"1475":2,"1477":2,"1482":1,"1483":1,"1484":1,"1485":1,"1506":1,"1507":2,"1511":2,"1517":2,"1518":1,"1519":1,"1522":1,"1523":1,"1525":2,"1527":1,"1533":1,"1538":1,"1548":1,"1550":1,"1557":1,"1558":1,"1559":3,"1567":1,"1569":5,"1571":3,"1574":1,"1577":1,"1581":4,"1586":1,"1601":1,"1604":2,"1605":1,"1608":3,"1609":8,"1611":1,"1612":1,"1617":1,"1618":3,"1621":1,"1626":1,"1628":1,"1635":1,"1639":1,"1641":1,"1644":2,"1648":1,"1649":2,"1655":1,"1661":1,"1664":3,"1665":1,"1666":1,"1667":1,"1682":1,"1686":1,"1690":4,"1698":1,"1699":1,"1700":2,"1701":1,"1704":1,"1708":1,"1718":1,"1722":2,"1733":2,"1738":3,"1740":2,"1741":1,"1742":1,"1743":3,"1751":1,"1754":1,"1755":1,"1757":1,"1759":6,"1762":1,"1787":4,"1788":3,"1789":2,"1791":2,"1792":184,"1794":4,"1795":1,"1798":1,"1799":1,"1802":3,"1810":1,"1812":1,"1813":3,"1816":1,"1822":1,"1823":1,"1824":7,"1825":2,"1827":2,"1830":1,"1832":1,"1833":2,"1835":1,"1844":2,"1845":2,"1847":1,"1848":2,"1850":3,"1851":2,"1852":4,"1856":5,"1858":2,"1861":1,"1862":1,"1868":2,"1870":1,"1894":1,"1896":1,"1898":2,"1901":1,"1908":1,"1911":1,"1912":7,"1915":1,"1921":2,"1922":1,"1924":2,"1925":2,"1935":1,"1938":1,"1942":1,"1946":1,"1948":2,"1951":1,"1952":1,"1953":1,"1954":1,"1957":3,"1958":2,"1959":2,"1961":2,"1963":1,"1965":1,"1967":2,"1969":1,"1974":2,"1978":1,"1979":1,"1981":1,"1984":1,"1990":1,"2003":1,"2007":3,"2009":1,"2011":1,"2013":1,"2016":2,"2020":2,"2021":3,"2024":1,"2032":1,"2036":1,"2037":1,"2040":2,"2042":1,"2045":2,"2049":1,"2050":2,"2051":1,"2052":1,"2054":1,"2055":1,"2056":2,"2059":1,"2075":1,"2079":1,"2080":1,"2081":2,"2087":1,"2089":1,"2091":1,"2092":1,"2094":3,"2098":3,"2100":1,"2104":2,"2106":1,"2107":2,"2108":2,"2110":4,"2111":2,"2112":5,"2114":1,"2115":1,"2121":1,"2122":1,"2125":1,"2128":1,"2130":1,"2132":1,"2134":2,"2140":1,"2149":1,"2153":1,"2156":5,"2157":3,"2158":4,"2160":1,"2162":3,"2164":16,"2165":8,"2166":3,"2167":4,"2168":1,"2170":1,"2171":5,"2174":2,"2176":4,"2177":8,"2179":1,"2181":3,"2183":2,"2184":2,"2185":1,"2187":2,"2189":2,"2190":2,"2191":1,"2192":1,"2196":2,"2197":1,"2208":1,"2210":1,"2221":8,"2222":9,"2223":2,"2224":1,"2225":4,"2226":3,"2228":2,"2229":2,"2231":2,"2235":1,"2237":1,"2245":2,"2246":1,"2247":4,"2250":3,"2251":1,"2252":1,"2254":3,"2255":2,"2256":2,"2257":1,"2258":2,"2259":2,"2264":5,"2265":8,"2266":2,"2267":4,"2270":6,"2272":3,"2273":1,"2274":1,"2278":1,"2279":1,"2282":2,"2283":2,"2286":1,"2288":2,"2289":1,"2290":1,"2291":3,"2293":1,"2294":1,"2296":2,"2297":2,"2300":1,"2302":1,"2303":2,"2307":1,"2309":1,"2310":1,"2313":2,"2314":2,"2317":4,"2318":1,"2319":1,"2320":1,"2321":1,"2322":1,"2324":1,"2325":1,"2328":2,"2330":1,"2332":4,"2335":1,"2337":1,"2338":1,"2339":2,"2340":1,"2342":1,"2344":2,"2346":1,"2347":2,"2348":1,"2350":1,"2351":1,"2353":2,"2354":1,"2356":1,"2359":2,"2360":1,"2362":1,"2364":1,"2365":1,"2369":2,"2370":1,"2371":2,"2372":3,"2375":6,"2376":2,"2378":3,"2379":4,"2380":7,"2381":1,"2382":3,"2383":4,"2385":1,"2388":4,"2389":9,"2391":3,"2392":3,"2393":1,"2394":3,"2395":1,"2397":3,"2398":2,"2399":2,"2402":1,"2403":2,"2406":1,"2407":3,"2410":1,"2411":1,"2412":1,"2413":1,"2414":2,"2415":2,"2416":1,"2417":3,"2419":3,"2420":1,"2422":3,"2423":2,"2424":3,"2425":3,"2428":1,"2435":5,"2437":1,"2438":6,"2441":1,"2442":1,"2443":1,"2446":1,"2447":1,"2450":3,"2451":4,"2452":2,"2453":2,"2454":2,"2455":3,"2456":3,"2461":2,"2462":1,"2463":3,"2465":2,"2466":3,"2468":2,"2470":3,"2471":1,"2472":3,"2474":1,"2476":2,"2477":1,"2479":3,"2481":7,"2482":1,"2483":1,"2484":2,"2486":3,"2487":2,"2489":2,"2490":2,"2491":4,"2492":1,"2493":4,"2494":1,"2495":6,"2496":3,"2497":1,"2498":11,"2500":1,"2502":5,"2504":3,"2505":3,"2506":3,"2510":3,"2511":2,"2512":1,"2513":2,"2515":1,"2518":5,"2519":2,"2520":4,"2521":1,"2522":1,"2523":4,"2525":4,"2526":2,"2527":4,"2528":5,"2529":5,"2530":6,"2531":7,"2532":6,"2533":7,"2534":7,"2535":5,"2536":1,"2537":19,"2539":3,"2540":10,"2541":3,"2542":7,"2543":11,"2544":2,"2545":4,"2546":10,"2549":5,"2554":4,"2555":3,"2562":1,"2567":1,"2572":1,"2575":2,"2576":2,"2580":1,"2581":2,"2586":2,"2588":1,"2589":2,"2591":1,"2594":1,"2597":3,"2604":2,"2607":3,"2611":1,"2614":2,"2615":7,"2621":4,"2622":2,"2627":3,"2628":1,"2632":3,"2633":4,"2634":3,"2635":9,"2638":1,"2645":1,"2649":1,"2651":1,"2653":1,"2656":2,"2659":2,"2661":5,"2662":3,"2664":1,"2665":1,"2666":3,"2667":1,"2668":1,"2669":1,"2670":1,"2671":1,"2674":1,"2678":2,"2679":1,"2680":1,"2681":1,"2684":1,"2687":1,"2688":1,"2691":1,"2693":2,"2694":1,"2695":2,"2696":1,"2702":1,"2705":1,"2706":1,"2709":2,"2710":1,"2711":1,"2721":2,"2722":2,"2723":1,"2726":2,"2728":1,"2731":2,"2732":1,"2733":1,"2736":1,"2737":1,"2739":3,"2740":2,"2741":2,"2742":3,"2744":2,"2745":1,"2747":1,"2750":1,"2751":3,"2755":1,"2758":1,"2759":7,"2760":2,"2762":1,"2765":2,"2766":2,"2767":1,"2768":2,"2770":1,"2771":1,"2772":5,"2774":4,"2775":3,"2776":3,"2785":2,"2788":1,"2789":1,"2792":1,"2794":1,"2795":5,"2798":2,"2799":3,"2800":1,"2802":2,"2803":2,"2804":2,"2805":1,"2806":5,"2807":2,"2809":2,"2810":5,"2812":4,"2813":4,"2815":2,"2817":1,"2818":1,"2820":1,"2821":1,"2823":4,"2824":2,"2826":2,"2827":2,"2828":4,"2829":3,"2830":3,"2831":1,"2833":1,"2834":1,"2835":3,"2836":2,"2837":1,"2838":1,"2840":2,"2841":1,"2845":6,"2846":2,"2848":1,"2849":1,"2854":1,"2855":1,"2857":3,"2858":2,"2860":4,"2861":1,"2862":4,"2863":2,"2864":3,"2865":1,"2867":1,"2868":8,"2869":8,"2871":4,"2872":2,"2873":1,"2875":2,"2876":2,"2877":1,"2878":9,"2879":1,"2880":3,"2881":1}}],["aid",{"2":{"1792":1,"2109":1,"2110":1,"2530":1,"2537":1}}],["aiming",{"2":{"1401":1}}],["aimed",{"2":{"835":1,"2474":1}}],["aiofiles",{"2":{"1366":1}}],["aihealth",{"2":{"1342":2}}],["aianalyze",{"2":{"1342":3}}],["ai",{"0":{"1038":1,"1044":1,"1081":1,"1334":1,"1335":1,"1400":1,"1401":1,"1402":1,"2815":1},"1":{"1039":1,"1040":1,"1041":1,"1042":1,"1043":1,"1044":1,"1045":1,"1046":1,"1047":1,"1335":1,"1336":1,"1337":1,"1338":1,"1339":1,"1340":1,"1401":1,"1402":1},"2":{"0":2,"1":2,"3":1,"223":1,"317":1,"327":1,"837":1,"871":1,"876":1,"918":2,"1037":6,"1038":2,"1039":1,"1043":1,"1081":4,"1105":1,"1254":1,"1324":1,"1328":4,"1331":4,"1332":3,"1333":3,"1334":1,"1335":7,"1337":4,"1338":8,"1339":4,"1342":3,"1343":1,"1346":1,"1381":1,"1382":7,"1383":2,"1384":3,"1385":1,"1386":1,"1399":1,"1400":3,"1401":2,"1402":8,"1404":1,"1789":1,"1813":1,"1834":1,"2164":3,"2165":3,"2166":1,"2479":2,"2481":1,"2806":1,"2815":7,"2816":3}}]],"serializationVersion":2}';export{e as default}; diff --git a/assets/chunks/VPLocalSearchBox.z2vlMY86.js b/assets/chunks/VPLocalSearchBox.z2vlMY86.js new file mode 100644 index 000000000..bc486b63a --- /dev/null +++ b/assets/chunks/VPLocalSearchBox.z2vlMY86.js @@ -0,0 +1,8 @@ +var Nt=Object.defineProperty;var Ft=(a,e,t)=>e in a?Nt(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var Ce=(a,e,t)=>Ft(a,typeof e!="symbol"?e+"":e,t);import{V as Ot,D as le,h as ge,ak as et,al as Rt,am as Ct,an as At,q as $e,ao as Mt,d as Lt,ap as tt,p as he,aq as Dt,ar as zt,s as Pt,as as Vt,v as Ae,P as fe,O as _e,aj as $t,at as jt,W as Bt,R as Wt,$ as Kt,b as Jt,o as H,j as _,a0 as qt,au as Ut,k as L,av as Gt,aw as Ht,c as Z,e as Se,n as st,B as nt,F as it,a as pe,t as ve,ax as Qt,ay as rt,a3 as Yt,a9 as Zt,ae as Xt,az as es,_ as ts}from"./framework.CgT1UzWm.js";import{a_ as ss,a$ as ns}from"./theme.kqgpP4eL.js";const is={root:()=>Ot(()=>import("./@localSearchIndexroot.CFuD74JD.js"),[])};/*! +* tabbable 6.2.0 +* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE +*/var vt=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],ke=vt.join(","),mt=typeof Element>"u",re=mt?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Ne=!mt&&Element.prototype.getRootNode?function(a){var e;return a==null||(e=a.getRootNode)===null||e===void 0?void 0:e.call(a)}:function(a){return a==null?void 0:a.ownerDocument},Fe=function a(e,t){var s;t===void 0&&(t=!0);var n=e==null||(s=e.getAttribute)===null||s===void 0?void 0:s.call(e,"inert"),r=n===""||n==="true",i=r||t&&e&&a(e.parentNode);return i},rs=function(e){var t,s=e==null||(t=e.getAttribute)===null||t===void 0?void 0:t.call(e,"contenteditable");return s===""||s==="true"},gt=function(e,t,s){if(Fe(e))return[];var n=Array.prototype.slice.apply(e.querySelectorAll(ke));return t&&re.call(e,ke)&&n.unshift(e),n=n.filter(s),n},bt=function a(e,t,s){for(var n=[],r=Array.from(e);r.length;){var i=r.shift();if(!Fe(i,!1))if(i.tagName==="SLOT"){var o=i.assignedElements(),l=o.length?o:i.children,c=a(l,!0,s);s.flatten?n.push.apply(n,c):n.push({scopeParent:i,candidates:c})}else{var h=re.call(i,ke);h&&s.filter(i)&&(t||!e.includes(i))&&n.push(i);var m=i.shadowRoot||typeof s.getShadowRoot=="function"&&s.getShadowRoot(i),f=!Fe(m,!1)&&(!s.shadowRootFilter||s.shadowRootFilter(i));if(m&&f){var b=a(m===!0?i.children:m.children,!0,s);s.flatten?n.push.apply(n,b):n.push({scopeParent:i,candidates:b})}else r.unshift.apply(r,i.children)}}return n},yt=function(e){return!isNaN(parseInt(e.getAttribute("tabindex"),10))},ie=function(e){if(!e)throw new Error("No node provided");return e.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(e.tagName)||rs(e))&&!yt(e)?0:e.tabIndex},as=function(e,t){var s=ie(e);return s<0&&t&&!yt(e)?0:s},os=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},wt=function(e){return e.tagName==="INPUT"},ls=function(e){return wt(e)&&e.type==="hidden"},cs=function(e){var t=e.tagName==="DETAILS"&&Array.prototype.slice.apply(e.children).some(function(s){return s.tagName==="SUMMARY"});return t},us=function(e,t){for(var s=0;ssummary:first-of-type"),i=r?e.parentElement:e;if(re.call(i,"details:not([open]) *"))return!0;if(!s||s==="full"||s==="legacy-full"){if(typeof n=="function"){for(var o=e;e;){var l=e.parentElement,c=Ne(e);if(l&&!l.shadowRoot&&n(l)===!0)return at(e);e.assignedSlot?e=e.assignedSlot:!l&&c!==e.ownerDocument?e=c.host:e=l}e=o}if(ps(e))return!e.getClientRects().length;if(s!=="legacy-full")return!0}else if(s==="non-zero-area")return at(e);return!1},ms=function(e){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(e.tagName))for(var t=e.parentElement;t;){if(t.tagName==="FIELDSET"&&t.disabled){for(var s=0;s=0)},bs=function a(e){var t=[],s=[];return e.forEach(function(n,r){var i=!!n.scopeParent,o=i?n.scopeParent:n,l=as(o,i),c=i?a(n.candidates):o;l===0?i?t.push.apply(t,c):t.push(o):s.push({documentOrder:r,tabIndex:l,item:n,isScope:i,content:c})}),s.sort(os).reduce(function(n,r){return r.isScope?n.push.apply(n,r.content):n.push(r.content),n},[]).concat(t)},ys=function(e,t){t=t||{};var s;return t.getShadowRoot?s=bt([e],t.includeContainer,{filter:je.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot,shadowRootFilter:gs}):s=gt(e,t.includeContainer,je.bind(null,t)),bs(s)},ws=function(e,t){t=t||{};var s;return t.getShadowRoot?s=bt([e],t.includeContainer,{filter:Oe.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):s=gt(e,t.includeContainer,Oe.bind(null,t)),s},ae=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return re.call(e,ke)===!1?!1:je(t,e)},xs=vt.concat("iframe").join(","),Me=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return re.call(e,xs)===!1?!1:Oe(t,e)};/*! +* focus-trap 7.6.5 +* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE +*/function Be(a,e){(e==null||e>a.length)&&(e=a.length);for(var t=0,s=Array(e);t0){var s=e[e.length-1];s!==t&&s._setPausedState(!0)}var n=e.indexOf(t);n===-1||e.splice(n,1),e.push(t)},deactivateTrap:function(e,t){var s=e.indexOf(t);s!==-1&&e.splice(s,1),e.length>0&&!e[e.length-1]._isManuallyPaused()&&e[e.length-1]._setPausedState(!1)}},Os=function(e){return e.tagName&&e.tagName.toLowerCase()==="input"&&typeof e.select=="function"},Rs=function(e){return(e==null?void 0:e.key)==="Escape"||(e==null?void 0:e.key)==="Esc"||(e==null?void 0:e.keyCode)===27},be=function(e){return(e==null?void 0:e.key)==="Tab"||(e==null?void 0:e.keyCode)===9},Cs=function(e){return be(e)&&!e.shiftKey},As=function(e){return be(e)&&e.shiftKey},ut=function(e){return setTimeout(e,0)},me=function(e){for(var t=arguments.length,s=new Array(t>1?t-1:0),n=1;n1&&arguments[1]!==void 0?arguments[1]:{},g=d.hasFallback,E=g===void 0?!1:g,T=d.params,F=T===void 0?[]:T,S=r[u];if(typeof S=="function"&&(S=S.apply(void 0,Is(F))),S===!0&&(S=void 0),!S){if(S===void 0||S===!1)return S;throw new Error("`".concat(u,"` was specified but was not a node, or did not return a node"))}var R=S;if(typeof S=="string"){try{R=s.querySelector(S)}catch(v){throw new Error("`".concat(u,'` appears to be an invalid selector; error="').concat(v.message,'"'))}if(!R&&!E)throw new Error("`".concat(u,"` as selector refers to no known node"))}return R},m=function(){var u=h("initialFocus",{hasFallback:!0});if(u===!1)return!1;if(u===void 0||u&&!Me(u,r.tabbableOptions))if(c(s.activeElement)>=0)u=s.activeElement;else{var d=i.tabbableGroups[0],g=d&&d.firstTabbableNode;u=g||h("fallbackFocus")}else u===null&&(u=h("fallbackFocus"));if(!u)throw new Error("Your focus-trap needs to have at least one focusable element");return u},f=function(){if(i.containerGroups=i.containers.map(function(u){var d=ys(u,r.tabbableOptions),g=ws(u,r.tabbableOptions),E=d.length>0?d[0]:void 0,T=d.length>0?d[d.length-1]:void 0,F=g.find(function(v){return ae(v)}),S=g.slice().reverse().find(function(v){return ae(v)}),R=!!d.find(function(v){return ie(v)>0});return{container:u,tabbableNodes:d,focusableNodes:g,posTabIndexesFound:R,firstTabbableNode:E,lastTabbableNode:T,firstDomTabbableNode:F,lastDomTabbableNode:S,nextTabbableNode:function(p){var I=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,O=d.indexOf(p);return O<0?I?g.slice(g.indexOf(p)+1).find(function(z){return ae(z)}):g.slice(0,g.indexOf(p)).reverse().find(function(z){return ae(z)}):d[O+(I?1:-1)]}}}),i.tabbableGroups=i.containerGroups.filter(function(u){return u.tabbableNodes.length>0}),i.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times");if(i.containerGroups.find(function(u){return u.posTabIndexesFound})&&i.containerGroups.length>1)throw new Error("At least one node with a positive tabindex was found in one of your focus-trap's multiple containers. Positive tabindexes are only supported in single-container focus-traps.")},b=function(u){var d=u.activeElement;if(d)return d.shadowRoot&&d.shadowRoot.activeElement!==null?b(d.shadowRoot):d},y=function(u){if(u!==!1&&u!==b(document)){if(!u||!u.focus){y(m());return}u.focus({preventScroll:!!r.preventScroll}),i.mostRecentlyFocusedNode=u,Os(u)&&u.select()}},x=function(u){var d=h("setReturnFocus",{params:[u]});return d||(d===!1?!1:u)},w=function(u){var d=u.target,g=u.event,E=u.isBackward,T=E===void 0?!1:E;d=d||Ee(g),f();var F=null;if(i.tabbableGroups.length>0){var S=c(d,g),R=S>=0?i.containerGroups[S]:void 0;if(S<0)T?F=i.tabbableGroups[i.tabbableGroups.length-1].lastTabbableNode:F=i.tabbableGroups[0].firstTabbableNode;else if(T){var v=i.tabbableGroups.findIndex(function(V){var k=V.firstTabbableNode;return d===k});if(v<0&&(R.container===d||Me(d,r.tabbableOptions)&&!ae(d,r.tabbableOptions)&&!R.nextTabbableNode(d,!1))&&(v=S),v>=0){var p=v===0?i.tabbableGroups.length-1:v-1,I=i.tabbableGroups[p];F=ie(d)>=0?I.lastTabbableNode:I.lastDomTabbableNode}else be(g)||(F=R.nextTabbableNode(d,!1))}else{var O=i.tabbableGroups.findIndex(function(V){var k=V.lastTabbableNode;return d===k});if(O<0&&(R.container===d||Me(d,r.tabbableOptions)&&!ae(d,r.tabbableOptions)&&!R.nextTabbableNode(d))&&(O=S),O>=0){var z=O===i.tabbableGroups.length-1?0:O+1,P=i.tabbableGroups[z];F=ie(d)>=0?P.firstTabbableNode:P.firstDomTabbableNode}else be(g)||(F=R.nextTabbableNode(d))}}else F=h("fallbackFocus");return F},C=function(u){var d=Ee(u);if(!(c(d,u)>=0)){if(me(r.clickOutsideDeactivates,u)){o.deactivate({returnFocus:r.returnFocusOnDeactivate});return}me(r.allowOutsideClick,u)||u.preventDefault()}},A=function(u){var d=Ee(u),g=c(d,u)>=0;if(g||d instanceof Document)g&&(i.mostRecentlyFocusedNode=d);else{u.stopImmediatePropagation();var E,T=!0;if(i.mostRecentlyFocusedNode)if(ie(i.mostRecentlyFocusedNode)>0){var F=c(i.mostRecentlyFocusedNode),S=i.containerGroups[F].tabbableNodes;if(S.length>0){var R=S.findIndex(function(v){return v===i.mostRecentlyFocusedNode});R>=0&&(r.isKeyForward(i.recentNavEvent)?R+1=0&&(E=S[R-1],T=!1))}}else i.containerGroups.some(function(v){return v.tabbableNodes.some(function(p){return ie(p)>0})})||(T=!1);else T=!1;T&&(E=w({target:i.mostRecentlyFocusedNode,isBackward:r.isKeyBackward(i.recentNavEvent)})),y(E||i.mostRecentlyFocusedNode||m())}i.recentNavEvent=void 0},J=function(u){var d=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;i.recentNavEvent=u;var g=w({event:u,isBackward:d});g&&(be(u)&&u.preventDefault(),y(g))},Q=function(u){(r.isKeyForward(u)||r.isKeyBackward(u))&&J(u,r.isKeyBackward(u))},W=function(u){Rs(u)&&me(r.escapeDeactivates,u)!==!1&&(u.preventDefault(),o.deactivate())},$=function(u){var d=Ee(u);c(d,u)>=0||me(r.clickOutsideDeactivates,u)||me(r.allowOutsideClick,u)||(u.preventDefault(),u.stopImmediatePropagation())},j=function(){if(i.active)return ct.activateTrap(n,o),i.delayInitialFocusTimer=r.delayInitialFocus?ut(function(){y(m())}):y(m()),s.addEventListener("focusin",A,!0),s.addEventListener("mousedown",C,{capture:!0,passive:!1}),s.addEventListener("touchstart",C,{capture:!0,passive:!1}),s.addEventListener("click",$,{capture:!0,passive:!1}),s.addEventListener("keydown",Q,{capture:!0,passive:!1}),s.addEventListener("keydown",W),o},ye=function(){if(i.active)return s.removeEventListener("focusin",A,!0),s.removeEventListener("mousedown",C,!0),s.removeEventListener("touchstart",C,!0),s.removeEventListener("click",$,!0),s.removeEventListener("keydown",Q,!0),s.removeEventListener("keydown",W),o},M=function(u){var d=u.some(function(g){var E=Array.from(g.removedNodes);return E.some(function(T){return T===i.mostRecentlyFocusedNode})});d&&y(m())},q=typeof window<"u"&&"MutationObserver"in window?new MutationObserver(M):void 0,U=function(){q&&(q.disconnect(),i.active&&!i.paused&&i.containers.map(function(u){q.observe(u,{subtree:!0,childList:!0})}))};return o={get active(){return i.active},get paused(){return i.paused},activate:function(u){if(i.active)return this;var d=l(u,"onActivate"),g=l(u,"onPostActivate"),E=l(u,"checkCanFocusTrap");E||f(),i.active=!0,i.paused=!1,i.nodeFocusedBeforeActivation=b(s),d==null||d();var T=function(){E&&f(),j(),U(),g==null||g()};return E?(E(i.containers.concat()).then(T,T),this):(T(),this)},deactivate:function(u){if(!i.active)return this;var d=lt({onDeactivate:r.onDeactivate,onPostDeactivate:r.onPostDeactivate,checkCanReturnFocus:r.checkCanReturnFocus},u);clearTimeout(i.delayInitialFocusTimer),i.delayInitialFocusTimer=void 0,ye(),i.active=!1,i.paused=!1,U(),ct.deactivateTrap(n,o);var g=l(d,"onDeactivate"),E=l(d,"onPostDeactivate"),T=l(d,"checkCanReturnFocus"),F=l(d,"returnFocus","returnFocusOnDeactivate");g==null||g();var S=function(){ut(function(){F&&y(x(i.nodeFocusedBeforeActivation)),E==null||E()})};return F&&T?(T(x(i.nodeFocusedBeforeActivation)).then(S,S),this):(S(),this)},pause:function(u){return i.active?(i.manuallyPaused=!0,this._setPausedState(!0,u)):this},unpause:function(u){return i.active?(i.manuallyPaused=!1,n[n.length-1]!==this?this:this._setPausedState(!1,u)):this},updateContainerElements:function(u){var d=[].concat(u).filter(Boolean);return i.containers=d.map(function(g){return typeof g=="string"?s.querySelector(g):g}),i.active&&f(),U(),this}},Object.defineProperties(o,{_isManuallyPaused:{value:function(){return i.manuallyPaused}},_setPausedState:{value:function(u,d){if(i.paused===u)return this;if(i.paused=u,u){var g=l(d,"onPause"),E=l(d,"onPostPause");g==null||g(),ye(),U(),E==null||E()}else{var T=l(d,"onUnpause"),F=l(d,"onPostUnpause");T==null||T(),f(),j(),U(),F==null||F()}return this}}}),o.updateContainerElements(e),o};function Ds(a,e={}){let t;const{immediate:s,...n}=e,r=le(!1),i=le(!1),o=f=>t&&t.activate(f),l=f=>t&&t.deactivate(f),c=()=>{t&&(t.pause(),i.value=!0)},h=()=>{t&&(t.unpause(),i.value=!1)},m=ge(()=>{const f=et(a);return Rt(f).map(b=>{const y=et(b);return typeof y=="string"?y:Ct(y)}).filter(At)});return $e(m,f=>{f.length&&(t=Ls(f,{...n,onActivate(){r.value=!0,e.onActivate&&e.onActivate()},onDeactivate(){r.value=!1,e.onDeactivate&&e.onDeactivate()}}),s&&o())},{flush:"post"}),Mt(()=>l()),{hasFocus:r,isPaused:i,activate:o,deactivate:l,pause:c,unpause:h}}class ce{constructor(e,t=!0,s=[],n=5e3){this.ctx=e,this.iframes=t,this.exclude=s,this.iframesTimeout=n}static matches(e,t){const s=typeof t=="string"?[t]:t,n=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(n){let r=!1;return s.every(i=>n.call(e,i)?(r=!0,!1):!0),r}else return!1}getContexts(){let e,t=[];return typeof this.ctx>"u"||!this.ctx?e=[]:NodeList.prototype.isPrototypeOf(this.ctx)?e=Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?e=this.ctx:typeof this.ctx=="string"?e=Array.prototype.slice.call(document.querySelectorAll(this.ctx)):e=[this.ctx],e.forEach(s=>{const n=t.filter(r=>r.contains(s)).length>0;t.indexOf(s)===-1&&!n&&t.push(s)}),t}getIframeContents(e,t,s=()=>{}){let n;try{const r=e.contentWindow;if(n=r.document,!r||!n)throw new Error("iframe inaccessible")}catch{s()}n&&t(n)}isIframeBlank(e){const t="about:blank",s=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&s!==t&&s}observeIframeLoad(e,t,s){let n=!1,r=null;const i=()=>{if(!n){n=!0,clearTimeout(r);try{this.isIframeBlank(e)||(e.removeEventListener("load",i),this.getIframeContents(e,t,s))}catch{s()}}};e.addEventListener("load",i),r=setTimeout(i,this.iframesTimeout)}onIframeReady(e,t,s){try{e.contentWindow.document.readyState==="complete"?this.isIframeBlank(e)?this.observeIframeLoad(e,t,s):this.getIframeContents(e,t,s):this.observeIframeLoad(e,t,s)}catch{s()}}waitForIframes(e,t){let s=0;this.forEachIframe(e,()=>!0,n=>{s++,this.waitForIframes(n.querySelector("html"),()=>{--s||t()})},n=>{n||t()})}forEachIframe(e,t,s,n=()=>{}){let r=e.querySelectorAll("iframe"),i=r.length,o=0;r=Array.prototype.slice.call(r);const l=()=>{--i<=0&&n(o)};i||l(),r.forEach(c=>{ce.matches(c,this.exclude)?l():this.onIframeReady(c,h=>{t(c)&&(o++,s(h)),l()},l)})}createIterator(e,t,s){return document.createNodeIterator(e,t,s,!1)}createInstanceOnIframe(e){return new ce(e.querySelector("html"),this.iframes)}compareNodeIframe(e,t,s){const n=e.compareDocumentPosition(s),r=Node.DOCUMENT_POSITION_PRECEDING;if(n&r)if(t!==null){const i=t.compareDocumentPosition(s),o=Node.DOCUMENT_POSITION_FOLLOWING;if(i&o)return!0}else return!0;return!1}getIteratorNode(e){const t=e.previousNode();let s;return t===null?s=e.nextNode():s=e.nextNode()&&e.nextNode(),{prevNode:t,node:s}}checkIframeFilter(e,t,s,n){let r=!1,i=!1;return n.forEach((o,l)=>{o.val===s&&(r=l,i=o.handled)}),this.compareNodeIframe(e,t,s)?(r===!1&&!i?n.push({val:s,handled:!0}):r!==!1&&!i&&(n[r].handled=!0),!0):(r===!1&&n.push({val:s,handled:!1}),!1)}handleOpenIframes(e,t,s,n){e.forEach(r=>{r.handled||this.getIframeContents(r.val,i=>{this.createInstanceOnIframe(i).forEachNode(t,s,n)})})}iterateThroughNodes(e,t,s,n,r){const i=this.createIterator(t,e,n);let o=[],l=[],c,h,m=()=>({prevNode:h,node:c}=this.getIteratorNode(i),c);for(;m();)this.iframes&&this.forEachIframe(t,f=>this.checkIframeFilter(c,h,f,o),f=>{this.createInstanceOnIframe(f).forEachNode(e,b=>l.push(b),n)}),l.push(c);l.forEach(f=>{s(f)}),this.iframes&&this.handleOpenIframes(o,e,s,n),r()}forEachNode(e,t,s,n=()=>{}){const r=this.getContexts();let i=r.length;i||n(),r.forEach(o=>{const l=()=>{this.iterateThroughNodes(e,o,t,s,()=>{--i<=0&&n()})};this.iframes?this.waitForIframes(o,l):l()})}}let zs=class{constructor(e){this.ctx=e,this.ie=!1;const t=window.navigator.userAgent;(t.indexOf("MSIE")>-1||t.indexOf("Trident")>-1)&&(this.ie=!0)}set opt(e){this._opt=Object.assign({},{element:"",className:"",exclude:[],iframes:!1,iframesTimeout:5e3,separateWordSearch:!0,diacritics:!0,synonyms:{},accuracy:"partially",acrossElements:!1,caseSensitive:!1,ignoreJoiners:!1,ignoreGroups:0,ignorePunctuation:[],wildcards:"disabled",each:()=>{},noMatch:()=>{},filter:()=>!0,done:()=>{},debug:!1,log:window.console},e)}get opt(){return this._opt}get iterator(){return new ce(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout)}log(e,t="debug"){const s=this.opt.log;this.opt.debug&&typeof s=="object"&&typeof s[t]=="function"&&s[t](`mark.js: ${e}`)}escapeStr(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}createRegExp(e){return this.opt.wildcards!=="disabled"&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),this.opt.wildcards!=="disabled"&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),e}createSynonymsRegExp(e){const t=this.opt.synonyms,s=this.opt.caseSensitive?"":"i",n=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(let r in t)if(t.hasOwnProperty(r)){const i=t[r],o=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(r):this.escapeStr(r),l=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(i):this.escapeStr(i);o!==""&&l!==""&&(e=e.replace(new RegExp(`(${this.escapeStr(o)}|${this.escapeStr(l)})`,`gm${s}`),n+`(${this.processSynomyms(o)}|${this.processSynomyms(l)})`+n))}return e}processSynomyms(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}setupWildcardsRegExp(e){return e=e.replace(/(?:\\)*\?/g,t=>t.charAt(0)==="\\"?"?":""),e.replace(/(?:\\)*\*/g,t=>t.charAt(0)==="\\"?"*":"")}createWildcardsRegExp(e){let t=this.opt.wildcards==="withSpaces";return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}setupIgnoreJoinersRegExp(e){return e.replace(/[^(|)\\]/g,(t,s,n)=>{let r=n.charAt(s+1);return/[(|)\\]/.test(r)||r===""?t:t+"\0"})}createJoinersRegExp(e){let t=[];const s=this.opt.ignorePunctuation;return Array.isArray(s)&&s.length&&t.push(this.escapeStr(s.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join(`[${t.join("")}]*`):e}createDiacriticsRegExp(e){const t=this.opt.caseSensitive?"":"i",s=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"];let n=[];return e.split("").forEach(r=>{s.every(i=>{if(i.indexOf(r)!==-1){if(n.indexOf(i)>-1)return!1;e=e.replace(new RegExp(`[${i}]`,`gm${t}`),`[${i}]`),n.push(i)}return!0})}),e}createMergedBlanksRegExp(e){return e.replace(/[\s]+/gmi,"[\\s]+")}createAccuracyRegExp(e){const t="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿";let s=this.opt.accuracy,n=typeof s=="string"?s:s.value,r=typeof s=="string"?[]:s.limiters,i="";switch(r.forEach(o=>{i+=`|${this.escapeStr(o)}`}),n){case"partially":default:return`()(${e})`;case"complementary":return i="\\s"+(i||this.escapeStr(t)),`()([^${i}]*${e}[^${i}]*)`;case"exactly":return`(^|\\s${i})(${e})(?=$|\\s${i})`}}getSeparatedKeywords(e){let t=[];return e.forEach(s=>{this.opt.separateWordSearch?s.split(" ").forEach(n=>{n.trim()&&t.indexOf(n)===-1&&t.push(n)}):s.trim()&&t.indexOf(s)===-1&&t.push(s)}),{keywords:t.sort((s,n)=>n.length-s.length),length:t.length}}isNumeric(e){return Number(parseFloat(e))==e}checkRanges(e){if(!Array.isArray(e)||Object.prototype.toString.call(e[0])!=="[object Object]")return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];const t=[];let s=0;return e.sort((n,r)=>n.start-r.start).forEach(n=>{let{start:r,end:i,valid:o}=this.callNoMatchOnInvalidRanges(n,s);o&&(n.start=r,n.length=i-r,t.push(n),s=i)}),t}callNoMatchOnInvalidRanges(e,t){let s,n,r=!1;return e&&typeof e.start<"u"?(s=parseInt(e.start,10),n=s+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&n-t>0&&n-s>0?r=!0:(this.log(`Ignoring invalid or overlapping range: ${JSON.stringify(e)}`),this.opt.noMatch(e))):(this.log(`Ignoring invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)),{start:s,end:n,valid:r}}checkWhitespaceRanges(e,t,s){let n,r=!0,i=s.length,o=t-i,l=parseInt(e.start,10)-o;return l=l>i?i:l,n=l+parseInt(e.length,10),n>i&&(n=i,this.log(`End range automatically set to the max value of ${i}`)),l<0||n-l<0||l>i||n>i?(r=!1,this.log(`Invalid range: ${JSON.stringify(e)}`),this.opt.noMatch(e)):s.substring(l,n).replace(/\s+/g,"")===""&&(r=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:l,end:n,valid:r}}getTextNodes(e){let t="",s=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,n=>{s.push({start:t.length,end:(t+=n.textContent).length,node:n})},n=>this.matchesExclude(n.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT,()=>{e({value:t,nodes:s})})}matchesExclude(e){return ce.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}wrapRangeInTextNode(e,t,s){const n=this.opt.element?this.opt.element:"mark",r=e.splitText(t),i=r.splitText(s-t);let o=document.createElement(n);return o.setAttribute("data-markjs","true"),this.opt.className&&o.setAttribute("class",this.opt.className),o.textContent=r.textContent,r.parentNode.replaceChild(o,r),i}wrapRangeInMappedTextNode(e,t,s,n,r){e.nodes.every((i,o)=>{const l=e.nodes[o+1];if(typeof l>"u"||l.start>t){if(!n(i.node))return!1;const c=t-i.start,h=(s>i.end?i.end:s)-i.start,m=e.value.substr(0,i.start),f=e.value.substr(h+i.start);if(i.node=this.wrapRangeInTextNode(i.node,c,h),e.value=m+f,e.nodes.forEach((b,y)=>{y>=o&&(e.nodes[y].start>0&&y!==o&&(e.nodes[y].start-=h),e.nodes[y].end-=h)}),s-=h,r(i.node.previousSibling,i.start),s>i.end)t=i.end;else return!1}return!0})}wrapMatches(e,t,s,n,r){const i=t===0?0:t+1;this.getTextNodes(o=>{o.nodes.forEach(l=>{l=l.node;let c;for(;(c=e.exec(l.textContent))!==null&&c[i]!=="";){if(!s(c[i],l))continue;let h=c.index;if(i!==0)for(let m=1;m{let l;for(;(l=e.exec(o.value))!==null&&l[i]!=="";){let c=l.index;if(i!==0)for(let m=1;ms(l[i],m),(m,f)=>{e.lastIndex=f,n(m)})}r()})}wrapRangeFromIndex(e,t,s,n){this.getTextNodes(r=>{const i=r.value.length;e.forEach((o,l)=>{let{start:c,end:h,valid:m}=this.checkWhitespaceRanges(o,i,r.value);m&&this.wrapRangeInMappedTextNode(r,c,h,f=>t(f,o,r.value.substring(c,h),l),f=>{s(f,o)})}),n()})}unwrapMatches(e){const t=e.parentNode;let s=document.createDocumentFragment();for(;e.firstChild;)s.appendChild(e.removeChild(e.firstChild));t.replaceChild(s,e),this.ie?this.normalizeTextNode(t):t.normalize()}normalizeTextNode(e){if(e){if(e.nodeType===3)for(;e.nextSibling&&e.nextSibling.nodeType===3;)e.nodeValue+=e.nextSibling.nodeValue,e.parentNode.removeChild(e.nextSibling);else this.normalizeTextNode(e.firstChild);this.normalizeTextNode(e.nextSibling)}}markRegExp(e,t){this.opt=t,this.log(`Searching with expression "${e}"`);let s=0,n="wrapMatches";const r=i=>{s++,this.opt.each(i)};this.opt.acrossElements&&(n="wrapMatchesAcrossElements"),this[n](e,this.opt.ignoreGroups,(i,o)=>this.opt.filter(o,i,s),r,()=>{s===0&&this.opt.noMatch(e),this.opt.done(s)})}mark(e,t){this.opt=t;let s=0,n="wrapMatches";const{keywords:r,length:i}=this.getSeparatedKeywords(typeof e=="string"?[e]:e),o=this.opt.caseSensitive?"":"i",l=c=>{let h=new RegExp(this.createRegExp(c),`gm${o}`),m=0;this.log(`Searching with expression "${h}"`),this[n](h,1,(f,b)=>this.opt.filter(b,c,s,m),f=>{m++,s++,this.opt.each(f)},()=>{m===0&&this.opt.noMatch(c),r[i-1]===c?this.opt.done(s):l(r[r.indexOf(c)+1])})};this.opt.acrossElements&&(n="wrapMatchesAcrossElements"),i===0?this.opt.done(s):l(r[0])}markRanges(e,t){this.opt=t;let s=0,n=this.checkRanges(e);n&&n.length?(this.log("Starting to mark with the following ranges: "+JSON.stringify(n)),this.wrapRangeFromIndex(n,(r,i,o,l)=>this.opt.filter(r,i,o,l),(r,i)=>{s++,this.opt.each(r,i)},()=>{this.opt.done(s)})):this.opt.done(s)}unmark(e){this.opt=e;let t=this.opt.element?this.opt.element:"*";t+="[data-markjs]",this.opt.className&&(t+=`.${this.opt.className}`),this.log(`Removal selector "${t}"`),this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,s=>{this.unwrapMatches(s)},s=>{const n=ce.matches(s,t),r=this.matchesExclude(s);return!n||r?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},this.opt.done)}};function Ps(a){const e=new zs(a);return this.mark=(t,s)=>(e.mark(t,s),this),this.markRegExp=(t,s)=>(e.markRegExp(t,s),this),this.markRanges=(t,s)=>(e.markRanges(t,s),this),this.unmark=t=>(e.unmark(t),this),this}const Vs="ENTRIES",xt="KEYS",_t="VALUES",D="";class Le{constructor(e,t){const s=e._tree,n=Array.from(s.keys());this.set=e,this._type=t,this._path=n.length>0?[{node:s,keys:n}]:[]}next(){const e=this.dive();return this.backtrack(),e}dive(){if(this._path.length===0)return{done:!0,value:void 0};const{node:e,keys:t}=oe(this._path);if(oe(t)===D)return{done:!1,value:this.result()};const s=e.get(oe(t));return this._path.push({node:s,keys:Array.from(s.keys())}),this.dive()}backtrack(){if(this._path.length===0)return;const e=oe(this._path).keys;e.pop(),!(e.length>0)&&(this._path.pop(),this.backtrack())}key(){return this.set._prefix+this._path.map(({keys:e})=>oe(e)).filter(e=>e!==D).join("")}value(){return oe(this._path).node.get(D)}result(){switch(this._type){case _t:return this.value();case xt:return this.key();default:return[this.key(),this.value()]}}[Symbol.iterator](){return this}}const oe=a=>a[a.length-1],$s=(a,e,t)=>{const s=new Map;if(e===void 0)return s;const n=e.length+1,r=n+t,i=new Uint8Array(r*n).fill(t+1);for(let o=0;o{const l=r*i;e:for(const c of a.keys())if(c===D){const h=n[l-1];h<=t&&s.set(o,[a.get(c),h])}else{let h=r;for(let m=0;mt)continue e}St(a.get(c),e,t,s,n,h,i,o+c)}};class X{constructor(e=new Map,t=""){this._size=void 0,this._tree=e,this._prefix=t}atPrefix(e){if(!e.startsWith(this._prefix))throw new Error("Mismatched prefix");const[t,s]=Re(this._tree,e.slice(this._prefix.length));if(t===void 0){const[n,r]=qe(s);for(const i of n.keys())if(i!==D&&i.startsWith(r)){const o=new Map;return o.set(i.slice(r.length),n.get(i)),new X(o,e)}}return new X(t,e)}clear(){this._size=void 0,this._tree.clear()}delete(e){return this._size=void 0,js(this._tree,e)}entries(){return new Le(this,Vs)}forEach(e){for(const[t,s]of this)e(t,s,this)}fuzzyGet(e,t){return $s(this._tree,e,t)}get(e){const t=We(this._tree,e);return t!==void 0?t.get(D):void 0}has(e){const t=We(this._tree,e);return t!==void 0&&t.has(D)}keys(){return new Le(this,xt)}set(e,t){if(typeof e!="string")throw new Error("key must be a string");return this._size=void 0,De(this._tree,e).set(D,t),this}get size(){if(this._size)return this._size;this._size=0;const e=this.entries();for(;!e.next().done;)this._size+=1;return this._size}update(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const s=De(this._tree,e);return s.set(D,t(s.get(D))),this}fetch(e,t){if(typeof e!="string")throw new Error("key must be a string");this._size=void 0;const s=De(this._tree,e);let n=s.get(D);return n===void 0&&s.set(D,n=t()),n}values(){return new Le(this,_t)}[Symbol.iterator](){return this.entries()}static from(e){const t=new X;for(const[s,n]of e)t.set(s,n);return t}static fromObject(e){return X.from(Object.entries(e))}}const Re=(a,e,t=[])=>{if(e.length===0||a==null)return[a,t];for(const s of a.keys())if(s!==D&&e.startsWith(s))return t.push([a,s]),Re(a.get(s),e.slice(s.length),t);return t.push([a,e]),Re(void 0,"",t)},We=(a,e)=>{if(e.length===0||a==null)return a;for(const t of a.keys())if(t!==D&&e.startsWith(t))return We(a.get(t),e.slice(t.length))},De=(a,e)=>{const t=e.length;e:for(let s=0;a&&s{const[t,s]=Re(a,e);if(t!==void 0){if(t.delete(D),t.size===0)Et(s);else if(t.size===1){const[n,r]=t.entries().next().value;Tt(s,n,r)}}},Et=a=>{if(a.length===0)return;const[e,t]=qe(a);if(e.delete(t),e.size===0)Et(a.slice(0,-1));else if(e.size===1){const[s,n]=e.entries().next().value;s!==D&&Tt(a.slice(0,-1),s,n)}},Tt=(a,e,t)=>{if(a.length===0)return;const[s,n]=qe(a);s.set(n+e,t),s.delete(n)},qe=a=>a[a.length-1],Ue="or",It="and",Bs="and_not";class ue{constructor(e){if((e==null?void 0:e.fields)==null)throw new Error('MiniSearch: option "fields" must be provided');const t=e.autoVacuum==null||e.autoVacuum===!0?Ve:e.autoVacuum;this._options={...Pe,...e,autoVacuum:t,searchOptions:{...dt,...e.searchOptions||{}},autoSuggestOptions:{...Us,...e.autoSuggestOptions||{}}},this._index=new X,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldIds={},this._fieldLength=new Map,this._avgFieldLength=[],this._nextId=0,this._storedFields=new Map,this._dirtCount=0,this._currentVacuum=null,this._enqueuedVacuum=null,this._enqueuedVacuumConditions=Je,this.addFields(this._options.fields)}add(e){const{extractField:t,tokenize:s,processTerm:n,fields:r,idField:i}=this._options,o=t(e,i);if(o==null)throw new Error(`MiniSearch: document does not have ID field "${i}"`);if(this._idToShortId.has(o))throw new Error(`MiniSearch: duplicate ID ${o}`);const l=this.addDocumentId(o);this.saveStoredFields(l,e);for(const c of r){const h=t(e,c);if(h==null)continue;const m=s(h.toString(),c),f=this._fieldIds[c],b=new Set(m).size;this.addFieldLength(l,f,this._documentCount-1,b);for(const y of m){const x=n(y,c);if(Array.isArray(x))for(const w of x)this.addTerm(f,l,w);else x&&this.addTerm(f,l,x)}}}addAll(e){for(const t of e)this.add(t)}addAllAsync(e,t={}){const{chunkSize:s=10}=t,n={chunk:[],promise:Promise.resolve()},{chunk:r,promise:i}=e.reduce(({chunk:o,promise:l},c,h)=>(o.push(c),(h+1)%s===0?{chunk:[],promise:l.then(()=>new Promise(m=>setTimeout(m,0))).then(()=>this.addAll(o))}:{chunk:o,promise:l}),n);return i.then(()=>this.addAll(r))}remove(e){const{tokenize:t,processTerm:s,extractField:n,fields:r,idField:i}=this._options,o=n(e,i);if(o==null)throw new Error(`MiniSearch: document does not have ID field "${i}"`);const l=this._idToShortId.get(o);if(l==null)throw new Error(`MiniSearch: cannot remove document with ID ${o}: it is not in the index`);for(const c of r){const h=n(e,c);if(h==null)continue;const m=t(h.toString(),c),f=this._fieldIds[c],b=new Set(m).size;this.removeFieldLength(l,f,this._documentCount,b);for(const y of m){const x=s(y,c);if(Array.isArray(x))for(const w of x)this.removeTerm(f,l,w);else x&&this.removeTerm(f,l,x)}}this._storedFields.delete(l),this._documentIds.delete(l),this._idToShortId.delete(o),this._fieldLength.delete(l),this._documentCount-=1}removeAll(e){if(e)for(const t of e)this.remove(t);else{if(arguments.length>0)throw new Error("Expected documents to be present. Omit the argument to remove all documents.");this._index=new X,this._documentCount=0,this._documentIds=new Map,this._idToShortId=new Map,this._fieldLength=new Map,this._avgFieldLength=[],this._storedFields=new Map,this._nextId=0}}discard(e){const t=this._idToShortId.get(e);if(t==null)throw new Error(`MiniSearch: cannot discard document with ID ${e}: it is not in the index`);this._idToShortId.delete(e),this._documentIds.delete(t),this._storedFields.delete(t),(this._fieldLength.get(t)||[]).forEach((s,n)=>{this.removeFieldLength(t,n,this._documentCount,s)}),this._fieldLength.delete(t),this._documentCount-=1,this._dirtCount+=1,this.maybeAutoVacuum()}maybeAutoVacuum(){if(this._options.autoVacuum===!1)return;const{minDirtFactor:e,minDirtCount:t,batchSize:s,batchWait:n}=this._options.autoVacuum;this.conditionalVacuum({batchSize:s,batchWait:n},{minDirtCount:t,minDirtFactor:e})}discardAll(e){const t=this._options.autoVacuum;try{this._options.autoVacuum=!1;for(const s of e)this.discard(s)}finally{this._options.autoVacuum=t}this.maybeAutoVacuum()}replace(e){const{idField:t,extractField:s}=this._options,n=s(e,t);this.discard(n),this.add(e)}vacuum(e={}){return this.conditionalVacuum(e)}conditionalVacuum(e,t){return this._currentVacuum?(this._enqueuedVacuumConditions=this._enqueuedVacuumConditions&&t,this._enqueuedVacuum!=null?this._enqueuedVacuum:(this._enqueuedVacuum=this._currentVacuum.then(()=>{const s=this._enqueuedVacuumConditions;return this._enqueuedVacuumConditions=Je,this.performVacuuming(e,s)}),this._enqueuedVacuum)):this.vacuumConditionsMet(t)===!1?Promise.resolve():(this._currentVacuum=this.performVacuuming(e),this._currentVacuum)}async performVacuuming(e,t){const s=this._dirtCount;if(this.vacuumConditionsMet(t)){const n=e.batchSize||Ke.batchSize,r=e.batchWait||Ke.batchWait;let i=1;for(const[o,l]of this._index){for(const[c,h]of l)for(const[m]of h)this._documentIds.has(m)||(h.size<=1?l.delete(c):h.delete(m));this._index.get(o).size===0&&this._index.delete(o),i%n===0&&await new Promise(c=>setTimeout(c,r)),i+=1}this._dirtCount-=s}await null,this._currentVacuum=this._enqueuedVacuum,this._enqueuedVacuum=null}vacuumConditionsMet(e){if(e==null)return!0;let{minDirtCount:t,minDirtFactor:s}=e;return t=t||Ve.minDirtCount,s=s||Ve.minDirtFactor,this.dirtCount>=t&&this.dirtFactor>=s}get isVacuuming(){return this._currentVacuum!=null}get dirtCount(){return this._dirtCount}get dirtFactor(){return this._dirtCount/(1+this._documentCount+this._dirtCount)}has(e){return this._idToShortId.has(e)}getStoredFields(e){const t=this._idToShortId.get(e);if(t!=null)return this._storedFields.get(t)}search(e,t={}){const{searchOptions:s}=this._options,n={...s,...t},r=this.executeQuery(e,t),i=[];for(const[o,{score:l,terms:c,match:h}]of r){const m=c.length||1,f={id:this._documentIds.get(o),score:l*m,terms:Object.keys(h),queryTerms:c,match:h};Object.assign(f,this._storedFields.get(o)),(n.filter==null||n.filter(f))&&i.push(f)}return e===ue.wildcard&&n.boostDocument==null||i.sort(ft),i}autoSuggest(e,t={}){t={...this._options.autoSuggestOptions,...t};const s=new Map;for(const{score:r,terms:i}of this.search(e,t)){const o=i.join(" "),l=s.get(o);l!=null?(l.score+=r,l.count+=1):s.set(o,{score:r,terms:i,count:1})}const n=[];for(const[r,{score:i,terms:o,count:l}]of s)n.push({suggestion:r,terms:o,score:i/l});return n.sort(ft),n}get documentCount(){return this._documentCount}get termCount(){return this._index.size}static loadJSON(e,t){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJS(JSON.parse(e),t)}static async loadJSONAsync(e,t){if(t==null)throw new Error("MiniSearch: loadJSON should be given the same options used when serializing the index");return this.loadJSAsync(JSON.parse(e),t)}static getDefault(e){if(Pe.hasOwnProperty(e))return ze(Pe,e);throw new Error(`MiniSearch: unknown option "${e}"`)}static loadJS(e,t){const{index:s,documentIds:n,fieldLength:r,storedFields:i,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=Te(n),l._fieldLength=Te(r),l._storedFields=Te(i);for(const[c,h]of l._documentIds)l._idToShortId.set(h,c);for(const[c,h]of s){const m=new Map;for(const f of Object.keys(h)){let b=h[f];o===1&&(b=b.ds),m.set(parseInt(f,10),Te(b))}l._index.set(c,m)}return l}static async loadJSAsync(e,t){const{index:s,documentIds:n,fieldLength:r,storedFields:i,serializationVersion:o}=e,l=this.instantiateMiniSearch(e,t);l._documentIds=await Ie(n),l._fieldLength=await Ie(r),l._storedFields=await Ie(i);for(const[h,m]of l._documentIds)l._idToShortId.set(m,h);let c=0;for(const[h,m]of s){const f=new Map;for(const b of Object.keys(m)){let y=m[b];o===1&&(y=y.ds),f.set(parseInt(b,10),await Ie(y))}++c%1e3===0&&await kt(0),l._index.set(h,f)}return l}static instantiateMiniSearch(e,t){const{documentCount:s,nextId:n,fieldIds:r,averageFieldLength:i,dirtCount:o,serializationVersion:l}=e;if(l!==1&&l!==2)throw new Error("MiniSearch: cannot deserialize an index created with an incompatible version");const c=new ue(t);return c._documentCount=s,c._nextId=n,c._idToShortId=new Map,c._fieldIds=r,c._avgFieldLength=i,c._dirtCount=o||0,c._index=new X,c}executeQuery(e,t={}){if(e===ue.wildcard)return this.executeWildcardQuery(t);if(typeof e!="string"){const f={...t,...e,queries:void 0},b=e.queries.map(y=>this.executeQuery(y,f));return this.combineResults(b,f.combineWith)}const{tokenize:s,processTerm:n,searchOptions:r}=this._options,i={tokenize:s,processTerm:n,...r,...t},{tokenize:o,processTerm:l}=i,m=o(e).flatMap(f=>l(f)).filter(f=>!!f).map(qs(i)).map(f=>this.executeQuerySpec(f,i));return this.combineResults(m,i.combineWith)}executeQuerySpec(e,t){const s={...this._options.searchOptions,...t},n=(s.fields||this._options.fields).reduce((x,w)=>({...x,[w]:ze(s.boost,w)||1}),{}),{boostDocument:r,weights:i,maxFuzzy:o,bm25:l}=s,{fuzzy:c,prefix:h}={...dt.weights,...i},m=this._index.get(e.term),f=this.termResults(e.term,e.term,1,e.termBoost,m,n,r,l);let b,y;if(e.prefix&&(b=this._index.atPrefix(e.term)),e.fuzzy){const x=e.fuzzy===!0?.2:e.fuzzy,w=x<1?Math.min(o,Math.round(e.term.length*x)):x;w&&(y=this._index.fuzzyGet(e.term,w))}if(b)for(const[x,w]of b){const C=x.length-e.term.length;if(!C)continue;y==null||y.delete(x);const A=h*x.length/(x.length+.3*C);this.termResults(e.term,x,A,e.termBoost,w,n,r,l,f)}if(y)for(const x of y.keys()){const[w,C]=y.get(x);if(!C)continue;const A=c*x.length/(x.length+C);this.termResults(e.term,x,A,e.termBoost,w,n,r,l,f)}return f}executeWildcardQuery(e){const t=new Map,s={...this._options.searchOptions,...e};for(const[n,r]of this._documentIds){const i=s.boostDocument?s.boostDocument(r,"",this._storedFields.get(n)):1;t.set(n,{score:i,terms:[],match:{}})}return t}combineResults(e,t=Ue){if(e.length===0)return new Map;const s=t.toLowerCase(),n=Ws[s];if(!n)throw new Error(`Invalid combination operator: ${t}`);return e.reduce(n)||new Map}toJSON(){const e=[];for(const[t,s]of this._index){const n={};for(const[r,i]of s)n[r]=Object.fromEntries(i);e.push([t,n])}return{documentCount:this._documentCount,nextId:this._nextId,documentIds:Object.fromEntries(this._documentIds),fieldIds:this._fieldIds,fieldLength:Object.fromEntries(this._fieldLength),averageFieldLength:this._avgFieldLength,storedFields:Object.fromEntries(this._storedFields),dirtCount:this._dirtCount,index:e,serializationVersion:2}}termResults(e,t,s,n,r,i,o,l,c=new Map){if(r==null)return c;for(const h of Object.keys(i)){const m=i[h],f=this._fieldIds[h],b=r.get(f);if(b==null)continue;let y=b.size;const x=this._avgFieldLength[f];for(const w of b.keys()){if(!this._documentIds.has(w)){this.removeTerm(f,w,t),y-=1;continue}const C=o?o(this._documentIds.get(w),t,this._storedFields.get(w)):1;if(!C)continue;const A=b.get(w),J=this._fieldLength.get(w)[f],Q=Js(A,y,this._documentCount,J,x,l),W=s*n*m*C*Q,$=c.get(w);if($){$.score+=W,Gs($.terms,e);const j=ze($.match,t);j?j.push(h):$.match[t]=[h]}else c.set(w,{score:W,terms:[e],match:{[t]:[h]}})}}return c}addTerm(e,t,s){const n=this._index.fetch(s,pt);let r=n.get(e);if(r==null)r=new Map,r.set(t,1),n.set(e,r);else{const i=r.get(t);r.set(t,(i||0)+1)}}removeTerm(e,t,s){if(!this._index.has(s)){this.warnDocumentChanged(t,e,s);return}const n=this._index.fetch(s,pt),r=n.get(e);r==null||r.get(t)==null?this.warnDocumentChanged(t,e,s):r.get(t)<=1?r.size<=1?n.delete(e):r.delete(t):r.set(t,r.get(t)-1),this._index.get(s).size===0&&this._index.delete(s)}warnDocumentChanged(e,t,s){for(const n of Object.keys(this._fieldIds))if(this._fieldIds[n]===t){this._options.logger("warn",`MiniSearch: document with ID ${this._documentIds.get(e)} has changed before removal: term "${s}" was not present in field "${n}". Removing a document after it has changed can corrupt the index!`,"version_conflict");return}}addDocumentId(e){const t=this._nextId;return this._idToShortId.set(e,t),this._documentIds.set(t,e),this._documentCount+=1,this._nextId+=1,t}addFields(e){for(let t=0;tObject.prototype.hasOwnProperty.call(a,e)?a[e]:void 0,Ws={[Ue]:(a,e)=>{for(const t of e.keys()){const s=a.get(t);if(s==null)a.set(t,e.get(t));else{const{score:n,terms:r,match:i}=e.get(t);s.score=s.score+n,s.match=Object.assign(s.match,i),ht(s.terms,r)}}return a},[It]:(a,e)=>{const t=new Map;for(const s of e.keys()){const n=a.get(s);if(n==null)continue;const{score:r,terms:i,match:o}=e.get(s);ht(n.terms,i),t.set(s,{score:n.score+r,terms:n.terms,match:Object.assign(n.match,o)})}return t},[Bs]:(a,e)=>{for(const t of e.keys())a.delete(t);return a}},Ks={k:1.2,b:.7,d:.5},Js=(a,e,t,s,n,r)=>{const{k:i,b:o,d:l}=r;return Math.log(1+(t-e+.5)/(e+.5))*(l+a*(i+1)/(a+i*(1-o+o*s/n)))},qs=a=>(e,t,s)=>{const n=typeof a.fuzzy=="function"?a.fuzzy(e,t,s):a.fuzzy||!1,r=typeof a.prefix=="function"?a.prefix(e,t,s):a.prefix===!0,i=typeof a.boostTerm=="function"?a.boostTerm(e,t,s):1;return{term:e,fuzzy:n,prefix:r,termBoost:i}},Pe={idField:"id",extractField:(a,e)=>a[e],tokenize:a=>a.split(Hs),processTerm:a=>a.toLowerCase(),fields:void 0,searchOptions:void 0,storeFields:[],logger:(a,e)=>{typeof(console==null?void 0:console[a])=="function"&&console[a](e)},autoVacuum:!0},dt={combineWith:Ue,prefix:!1,fuzzy:!1,maxFuzzy:6,boost:{},weights:{fuzzy:.45,prefix:.375},bm25:Ks},Us={combineWith:It,prefix:(a,e,t)=>e===t.length-1},Ke={batchSize:1e3,batchWait:10},Je={minDirtFactor:.1,minDirtCount:20},Ve={...Ke,...Je},Gs=(a,e)=>{a.includes(e)||a.push(e)},ht=(a,e)=>{for(const t of e)a.includes(t)||a.push(t)},ft=({score:a},{score:e})=>e-a,pt=()=>new Map,Te=a=>{const e=new Map;for(const t of Object.keys(a))e.set(parseInt(t,10),a[t]);return e},Ie=async a=>{const e=new Map;let t=0;for(const s of Object.keys(a))e.set(parseInt(s,10),a[s]),++t%1e3===0&&await kt(0);return e},kt=a=>new Promise(e=>setTimeout(e,a)),Hs=/[\n\r\p{Z}\p{P}]+/u;class Qs{constructor(e=10){Ce(this,"max");Ce(this,"cache");this.max=e,this.cache=new Map}get(e){let t=this.cache.get(e);return t!==void 0&&(this.cache.delete(e),this.cache.set(e,t)),t}set(e,t){this.cache.has(e)?this.cache.delete(e):this.cache.size===this.max&&this.cache.delete(this.first()),this.cache.set(e,t)}first(){return this.cache.keys().next().value}clear(){this.cache.clear()}}const Ys=["aria-owns"],Zs={class:"shell"},Xs=["title"],en={class:"search-actions before"},tn=["title"],sn=["aria-activedescendant","aria-controls","placeholder"],nn={class:"search-actions"},rn=["title"],an=["disabled","title"],on=["id","role","aria-labelledby"],ln=["id","aria-selected"],cn=["href","aria-label","onMouseenter","onFocusin","data-index"],un={class:"titles"},dn=["innerHTML"],hn={class:"title main"},fn=["innerHTML"],pn={key:0,class:"excerpt-wrapper"},vn={key:0,class:"excerpt",inert:""},mn=["innerHTML"],gn={key:0,class:"no-results"},bn={class:"search-keyboard-shortcuts"},yn=["aria-label"],wn=["aria-label"],xn=["aria-label"],_n=["aria-label"],Sn=Lt({__name:"VPLocalSearchBox",emits:["close"],setup(a,{emit:e}){var S,R;const t=e,s=le(),n=le(),r=le(is),i=ss(),{activate:o}=Ds(s,{immediate:!0,allowOutsideClick:!0,clickOutsideDeactivates:!0,escapeDeactivates:!0}),{localeIndex:l,theme:c}=i,h=tt(async()=>{var v,p,I,O,z,P,V,k,K;return rt(ue.loadJSON((I=await((p=(v=r.value)[l.value])==null?void 0:p.call(v)))==null?void 0:I.default,{fields:["title","titles","text"],storeFields:["title","titles"],searchOptions:{fuzzy:.2,prefix:!0,boost:{title:4,text:2,titles:1},...((O=c.value.search)==null?void 0:O.provider)==="local"&&((P=(z=c.value.search.options)==null?void 0:z.miniSearch)==null?void 0:P.searchOptions)},...((V=c.value.search)==null?void 0:V.provider)==="local"&&((K=(k=c.value.search.options)==null?void 0:k.miniSearch)==null?void 0:K.options)}))}),f=ge(()=>{var v,p;return((v=c.value.search)==null?void 0:v.provider)==="local"&&((p=c.value.search.options)==null?void 0:p.disableQueryPersistence)===!0}).value?he(""):Dt("vitepress:local-search-filter",""),b=zt("vitepress:local-search-detailed-list",((S=c.value.search)==null?void 0:S.provider)==="local"&&((R=c.value.search.options)==null?void 0:R.detailedView)===!0),y=ge(()=>{var v,p,I;return((v=c.value.search)==null?void 0:v.provider)==="local"&&(((p=c.value.search.options)==null?void 0:p.disableDetailedView)===!0||((I=c.value.search.options)==null?void 0:I.detailedView)===!1)}),x=ge(()=>{var p,I,O,z,P,V,k;const v=((p=c.value.search)==null?void 0:p.options)??c.value.algolia;return((P=(z=(O=(I=v==null?void 0:v.locales)==null?void 0:I[l.value])==null?void 0:O.translations)==null?void 0:z.button)==null?void 0:P.buttonText)||((k=(V=v==null?void 0:v.translations)==null?void 0:V.button)==null?void 0:k.buttonText)||"Search"});Pt(()=>{y.value&&(b.value=!1)});const w=le([]),C=he(!1);$e(f,()=>{C.value=!1});const A=tt(async()=>{if(n.value)return rt(new Ps(n.value))},null),J=new Qs(16);Vt(()=>[h.value,f.value,b.value],async([v,p,I],O,z)=>{var ee,we,Ge,He;(O==null?void 0:O[0])!==v&&J.clear();let P=!1;if(z(()=>{P=!0}),!v)return;w.value=v.search(p).slice(0,16),C.value=!0;const V=I?await Promise.all(w.value.map(B=>Q(B.id))):[];if(P)return;for(const{id:B,mod:te}of V){const se=B.slice(0,B.indexOf("#"));let Y=J.get(se);if(Y)continue;Y=new Map,J.set(se,Y);const G=te.default??te;if(G!=null&&G.render||G!=null&&G.setup){const ne=Yt(G);ne.config.warnHandler=()=>{},ne.provide(Zt,i),Object.defineProperties(ne.config.globalProperties,{$frontmatter:{get(){return i.frontmatter.value}},$params:{get(){return i.page.value.params}}});const Qe=document.createElement("div");ne.mount(Qe),Qe.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach(de=>{var Xe;const xe=(Xe=de.querySelector("a"))==null?void 0:Xe.getAttribute("href"),Ye=(xe==null?void 0:xe.startsWith("#"))&&xe.slice(1);if(!Ye)return;let Ze="";for(;(de=de.nextElementSibling)&&!/^h[1-6]$/i.test(de.tagName);)Ze+=de.outerHTML;Y.set(Ye,Ze)}),ne.unmount()}if(P)return}const k=new Set;if(w.value=w.value.map(B=>{const[te,se]=B.id.split("#"),Y=J.get(te),G=(Y==null?void 0:Y.get(se))??"";for(const ne in B.match)k.add(ne);return{...B,text:G}}),await fe(),P)return;await new Promise(B=>{var te;(te=A.value)==null||te.unmark({done:()=>{var se;(se=A.value)==null||se.markRegExp(T(k),{done:B})}})});const K=((ee=s.value)==null?void 0:ee.querySelectorAll(".result .excerpt"))??[];for(const B of K)(we=B.querySelector('mark[data-markjs="true"]'))==null||we.scrollIntoView({block:"center"});(He=(Ge=n.value)==null?void 0:Ge.firstElementChild)==null||He.scrollIntoView({block:"start"})},{debounce:200,immediate:!0});async function Q(v){const p=Xt(v.slice(0,v.indexOf("#")));try{if(!p)throw new Error(`Cannot find file for id: ${v}`);return{id:v,mod:await import(p)}}catch(I){return console.error(I),{id:v,mod:{}}}}const W=he(),$=ge(()=>{var v;return((v=f.value)==null?void 0:v.length)<=0});function j(v=!0){var p,I;(p=W.value)==null||p.focus(),v&&((I=W.value)==null||I.select())}Ae(()=>{j()});function ye(v){v.pointerType==="mouse"&&j()}const M=he(-1),q=he(!0);$e(w,v=>{M.value=v.length?0:-1,U()});function U(){fe(()=>{const v=document.querySelector(".result.selected");v==null||v.scrollIntoView({block:"nearest"})})}_e("ArrowUp",v=>{v.preventDefault(),M.value--,M.value<0&&(M.value=w.value.length-1),q.value=!0,U()}),_e("ArrowDown",v=>{v.preventDefault(),M.value++,M.value>=w.value.length&&(M.value=0),q.value=!0,U()});const N=$t();_e("Enter",v=>{if(v.isComposing||v.target instanceof HTMLButtonElement&&v.target.type!=="submit")return;const p=w.value[M.value];if(v.target instanceof HTMLInputElement&&!p){v.preventDefault();return}p&&(N.go(p.id),t("close"))}),_e("Escape",()=>{t("close")});const d=ns({modal:{displayDetails:"Display detailed list",resetButtonTitle:"Reset search",backButtonTitle:"Close search",noResultsText:"No results for",footer:{selectText:"to select",selectKeyAriaLabel:"enter",navigateText:"to navigate",navigateUpKeyAriaLabel:"up arrow",navigateDownKeyAriaLabel:"down arrow",closeText:"to close",closeKeyAriaLabel:"escape"}}});Ae(()=>{window.history.pushState(null,"",null)}),jt("popstate",v=>{v.preventDefault(),t("close")});const g=Bt(Wt?document.body:null);Ae(()=>{fe(()=>{g.value=!0,fe().then(()=>o())})}),Kt(()=>{g.value=!1});function E(){f.value="",fe().then(()=>j(!1))}function T(v){return new RegExp([...v].sort((p,I)=>I.length-p.length).map(p=>`(${es(p)})`).join("|"),"gi")}function F(v){var O;if(!q.value)return;const p=(O=v.target)==null?void 0:O.closest(".result"),I=Number.parseInt(p==null?void 0:p.dataset.index);I>=0&&I!==M.value&&(M.value=I),q.value=!1}return(v,p)=>{var I,O,z,P,V;return H(),Jt(Qt,{to:"body"},[_("div",{ref_key:"el",ref:s,role:"button","aria-owns":(I=w.value)!=null&&I.length?"localsearch-list":void 0,"aria-expanded":"true","aria-haspopup":"listbox","aria-labelledby":"localsearch-label",class:"VPLocalSearchBox"},[_("div",{class:"backdrop",onClick:p[0]||(p[0]=k=>v.$emit("close"))}),_("div",Zs,[_("form",{class:"search-bar",onPointerup:p[4]||(p[4]=k=>ye(k)),onSubmit:p[5]||(p[5]=qt(()=>{},["prevent"]))},[_("label",{title:x.value,id:"localsearch-label",for:"localsearch-input"},p[7]||(p[7]=[_("span",{"aria-hidden":"true",class:"vpi-search search-icon local-search-icon"},null,-1)]),8,Xs),_("div",en,[_("button",{class:"back-button",title:L(d)("modal.backButtonTitle"),onClick:p[1]||(p[1]=k=>v.$emit("close"))},p[8]||(p[8]=[_("span",{class:"vpi-arrow-left local-search-icon"},null,-1)]),8,tn)]),Ut(_("input",{ref_key:"searchInput",ref:W,"onUpdate:modelValue":p[2]||(p[2]=k=>Ht(f)?f.value=k:null),"aria-activedescendant":M.value>-1?"localsearch-item-"+M.value:void 0,"aria-autocomplete":"both","aria-controls":(O=w.value)!=null&&O.length?"localsearch-list":void 0,"aria-labelledby":"localsearch-label",autocapitalize:"off",autocomplete:"off",autocorrect:"off",class:"search-input",id:"localsearch-input",enterkeyhint:"go",maxlength:"64",placeholder:x.value,spellcheck:"false",type:"search"},null,8,sn),[[Gt,L(f)]]),_("div",nn,[y.value?Se("",!0):(H(),Z("button",{key:0,class:st(["toggle-layout-button",{"detailed-list":L(b)}]),type:"button",title:L(d)("modal.displayDetails"),onClick:p[3]||(p[3]=k=>M.value>-1&&(b.value=!L(b)))},p[9]||(p[9]=[_("span",{class:"vpi-layout-list local-search-icon"},null,-1)]),10,rn)),_("button",{class:"clear-button",type:"reset",disabled:$.value,title:L(d)("modal.resetButtonTitle"),onClick:E},p[10]||(p[10]=[_("span",{class:"vpi-delete local-search-icon"},null,-1)]),8,an)])],32),_("ul",{ref_key:"resultsEl",ref:n,id:(z=w.value)!=null&&z.length?"localsearch-list":void 0,role:(P=w.value)!=null&&P.length?"listbox":void 0,"aria-labelledby":(V=w.value)!=null&&V.length?"localsearch-label":void 0,class:"results",onMousemove:F},[(H(!0),Z(it,null,nt(w.value,(k,K)=>(H(),Z("li",{key:k.id,id:"localsearch-item-"+K,"aria-selected":M.value===K?"true":"false",role:"option"},[_("a",{href:k.id,class:st(["result",{selected:M.value===K}]),"aria-label":[...k.titles,k.title].join(" > "),onMouseenter:ee=>!q.value&&(M.value=K),onFocusin:ee=>M.value=K,onClick:p[6]||(p[6]=ee=>v.$emit("close")),"data-index":K},[_("div",null,[_("div",un,[p[12]||(p[12]=_("span",{class:"title-icon"},"#",-1)),(H(!0),Z(it,null,nt(k.titles,(ee,we)=>(H(),Z("span",{key:we,class:"title"},[_("span",{class:"text",innerHTML:ee},null,8,dn),p[11]||(p[11]=_("span",{class:"vpi-chevron-right local-search-icon"},null,-1))]))),128)),_("span",hn,[_("span",{class:"text",innerHTML:k.title},null,8,fn)])]),L(b)?(H(),Z("div",pn,[k.text?(H(),Z("div",vn,[_("div",{class:"vp-doc",innerHTML:k.text},null,8,mn)])):Se("",!0),p[13]||(p[13]=_("div",{class:"excerpt-gradient-bottom"},null,-1)),p[14]||(p[14]=_("div",{class:"excerpt-gradient-top"},null,-1))])):Se("",!0)])],42,cn)],8,ln))),128)),L(f)&&!w.value.length&&C.value?(H(),Z("li",gn,[pe(ve(L(d)("modal.noResultsText"))+' "',1),_("strong",null,ve(L(f)),1),p[15]||(p[15]=pe('" ',-1))])):Se("",!0)],40,on),_("div",bn,[_("span",null,[_("kbd",{"aria-label":L(d)("modal.footer.navigateUpKeyAriaLabel")},p[16]||(p[16]=[_("span",{class:"vpi-arrow-up navigate-icon"},null,-1)]),8,yn),_("kbd",{"aria-label":L(d)("modal.footer.navigateDownKeyAriaLabel")},p[17]||(p[17]=[_("span",{class:"vpi-arrow-down navigate-icon"},null,-1)]),8,wn),pe(" "+ve(L(d)("modal.footer.navigateText")),1)]),_("span",null,[_("kbd",{"aria-label":L(d)("modal.footer.selectKeyAriaLabel")},p[18]||(p[18]=[_("span",{class:"vpi-corner-down-left navigate-icon"},null,-1)]),8,xn),pe(" "+ve(L(d)("modal.footer.selectText")),1)]),_("span",null,[_("kbd",{"aria-label":L(d)("modal.footer.closeKeyAriaLabel")},"esc",8,_n),pe(" "+ve(L(d)("modal.footer.closeText")),1)])])])],8,Ys)])}}}),Fn=ts(Sn,[["__scopeId","data-v-ce626c7c"]]);export{Fn as default}; diff --git a/assets/chunks/architectureDiagram-VXUJARFQ.CaSL3V8c.js b/assets/chunks/architectureDiagram-VXUJARFQ.CaSL3V8c.js new file mode 100644 index 000000000..e23fcfe05 --- /dev/null +++ b/assets/chunks/architectureDiagram-VXUJARFQ.CaSL3V8c.js @@ -0,0 +1,36 @@ +import{aA as ye,aB as Ze,_ as dt,K as ke,a3 as qe,l as Re,b as Qe,a as Je,q as Ke,t as je,g as _e,s as tr,z as er,F as rr,G as ir,H as ar,c as Ee,ad as me,aC as ve,i as nr,d as or,y as sr,aD as hr,aE as lr}from"./theme.kqgpP4eL.js";import{p as fr}from"./chunk-4BX2VUAB.B6a8mhSC.js";import{p as cr}from"./treemap-KMMF4GRG.CcUr4GSN.js";import{c as Se}from"./cytoscape.esm.CyJtwmzi.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var Fe={exports:{}},ue={exports:{}},de={exports:{}},we;function gr(){return we||(we=1,function(I,D){(function(P,N){I.exports=N()})(ye,function(){return function(A){var P={};function N(u){if(P[u])return P[u].exports;var h=P[u]={i:u,l:!1,exports:{}};return A[u].call(h.exports,h,h.exports,N),h.l=!0,h.exports}return N.m=A,N.c=P,N.i=function(u){return u},N.d=function(u,h,a){N.o(u,h)||Object.defineProperty(u,h,{configurable:!1,enumerable:!0,get:a})},N.n=function(u){var h=u&&u.__esModule?function(){return u.default}:function(){return u};return N.d(h,"a",h),h},N.o=function(u,h){return Object.prototype.hasOwnProperty.call(u,h)},N.p="",N(N.s=28)}([function(A,P,N){function u(){}u.QUALITY=1,u.DEFAULT_CREATE_BENDS_AS_NEEDED=!1,u.DEFAULT_INCREMENTAL=!1,u.DEFAULT_ANIMATION_ON_LAYOUT=!0,u.DEFAULT_ANIMATION_DURING_LAYOUT=!1,u.DEFAULT_ANIMATION_PERIOD=50,u.DEFAULT_UNIFORM_LEAF_NODE_SIZES=!1,u.DEFAULT_GRAPH_MARGIN=15,u.NODE_DIMENSIONS_INCLUDE_LABELS=!1,u.SIMPLE_NODE_SIZE=40,u.SIMPLE_NODE_HALF_SIZE=u.SIMPLE_NODE_SIZE/2,u.EMPTY_COMPOUND_NODE_SIZE=40,u.MIN_EDGE_LENGTH=1,u.WORLD_BOUNDARY=1e6,u.INITIAL_WORLD_BOUNDARY=u.WORLD_BOUNDARY/1e3,u.WORLD_CENTER_X=1200,u.WORLD_CENTER_Y=900,A.exports=u},function(A,P,N){var u=N(2),h=N(8),a=N(9);function r(f,i,g){u.call(this,g),this.isOverlapingSourceAndTarget=!1,this.vGraphObject=g,this.bendpoints=[],this.source=f,this.target=i}r.prototype=Object.create(u.prototype);for(var e in u)r[e]=u[e];r.prototype.getSource=function(){return this.source},r.prototype.getTarget=function(){return this.target},r.prototype.isInterGraph=function(){return this.isInterGraph},r.prototype.getLength=function(){return this.length},r.prototype.isOverlapingSourceAndTarget=function(){return this.isOverlapingSourceAndTarget},r.prototype.getBendpoints=function(){return this.bendpoints},r.prototype.getLca=function(){return this.lca},r.prototype.getSourceInLca=function(){return this.sourceInLca},r.prototype.getTargetInLca=function(){return this.targetInLca},r.prototype.getOtherEnd=function(f){if(this.source===f)return this.target;if(this.target===f)return this.source;throw"Node is not incident with this edge"},r.prototype.getOtherEndInGraph=function(f,i){for(var g=this.getOtherEnd(f),t=i.getGraphManager().getRoot();;){if(g.getOwner()==i)return g;if(g.getOwner()==t)break;g=g.getOwner().getParent()}return null},r.prototype.updateLength=function(){var f=new Array(4);this.isOverlapingSourceAndTarget=h.getIntersection(this.target.getRect(),this.source.getRect(),f),this.isOverlapingSourceAndTarget||(this.lengthX=f[0]-f[2],this.lengthY=f[1]-f[3],Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY))},r.prototype.updateLengthSimple=function(){this.lengthX=this.target.getCenterX()-this.source.getCenterX(),this.lengthY=this.target.getCenterY()-this.source.getCenterY(),Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY)},A.exports=r},function(A,P,N){function u(h){this.vGraphObject=h}A.exports=u},function(A,P,N){var u=N(2),h=N(10),a=N(13),r=N(0),e=N(16),f=N(5);function i(t,o,s,c){s==null&&c==null&&(c=o),u.call(this,c),t.graphManager!=null&&(t=t.graphManager),this.estimatedSize=h.MIN_VALUE,this.inclusionTreeDepth=h.MAX_VALUE,this.vGraphObject=c,this.edges=[],this.graphManager=t,s!=null&&o!=null?this.rect=new a(o.x,o.y,s.width,s.height):this.rect=new a}i.prototype=Object.create(u.prototype);for(var g in u)i[g]=u[g];i.prototype.getEdges=function(){return this.edges},i.prototype.getChild=function(){return this.child},i.prototype.getOwner=function(){return this.owner},i.prototype.getWidth=function(){return this.rect.width},i.prototype.setWidth=function(t){this.rect.width=t},i.prototype.getHeight=function(){return this.rect.height},i.prototype.setHeight=function(t){this.rect.height=t},i.prototype.getCenterX=function(){return this.rect.x+this.rect.width/2},i.prototype.getCenterY=function(){return this.rect.y+this.rect.height/2},i.prototype.getCenter=function(){return new f(this.rect.x+this.rect.width/2,this.rect.y+this.rect.height/2)},i.prototype.getLocation=function(){return new f(this.rect.x,this.rect.y)},i.prototype.getRect=function(){return this.rect},i.prototype.getDiagonal=function(){return Math.sqrt(this.rect.width*this.rect.width+this.rect.height*this.rect.height)},i.prototype.getHalfTheDiagonal=function(){return Math.sqrt(this.rect.height*this.rect.height+this.rect.width*this.rect.width)/2},i.prototype.setRect=function(t,o){this.rect.x=t.x,this.rect.y=t.y,this.rect.width=o.width,this.rect.height=o.height},i.prototype.setCenter=function(t,o){this.rect.x=t-this.rect.width/2,this.rect.y=o-this.rect.height/2},i.prototype.setLocation=function(t,o){this.rect.x=t,this.rect.y=o},i.prototype.moveBy=function(t,o){this.rect.x+=t,this.rect.y+=o},i.prototype.getEdgeListToNode=function(t){var o=[],s=this;return s.edges.forEach(function(c){if(c.target==t){if(c.source!=s)throw"Incorrect edge source!";o.push(c)}}),o},i.prototype.getEdgesBetween=function(t){var o=[],s=this;return s.edges.forEach(function(c){if(!(c.source==s||c.target==s))throw"Incorrect edge source and/or target";(c.target==t||c.source==t)&&o.push(c)}),o},i.prototype.getNeighborsList=function(){var t=new Set,o=this;return o.edges.forEach(function(s){if(s.source==o)t.add(s.target);else{if(s.target!=o)throw"Incorrect incidency!";t.add(s.source)}}),t},i.prototype.withChildren=function(){var t=new Set,o,s;if(t.add(this),this.child!=null)for(var c=this.child.getNodes(),l=0;lo?(this.rect.x-=(this.labelWidth-o)/2,this.setWidth(this.labelWidth)):this.labelPosHorizontal=="right"&&this.setWidth(o+this.labelWidth)),this.labelHeight&&(this.labelPosVertical=="top"?(this.rect.y-=this.labelHeight,this.setHeight(s+this.labelHeight)):this.labelPosVertical=="center"&&this.labelHeight>s?(this.rect.y-=(this.labelHeight-s)/2,this.setHeight(this.labelHeight)):this.labelPosVertical=="bottom"&&this.setHeight(s+this.labelHeight))}}},i.prototype.getInclusionTreeDepth=function(){if(this.inclusionTreeDepth==h.MAX_VALUE)throw"assert failed";return this.inclusionTreeDepth},i.prototype.transform=function(t){var o=this.rect.x;o>r.WORLD_BOUNDARY?o=r.WORLD_BOUNDARY:o<-r.WORLD_BOUNDARY&&(o=-r.WORLD_BOUNDARY);var s=this.rect.y;s>r.WORLD_BOUNDARY?s=r.WORLD_BOUNDARY:s<-r.WORLD_BOUNDARY&&(s=-r.WORLD_BOUNDARY);var c=new f(o,s),l=t.inverseTransformPoint(c);this.setLocation(l.x,l.y)},i.prototype.getLeft=function(){return this.rect.x},i.prototype.getRight=function(){return this.rect.x+this.rect.width},i.prototype.getTop=function(){return this.rect.y},i.prototype.getBottom=function(){return this.rect.y+this.rect.height},i.prototype.getParent=function(){return this.owner==null?null:this.owner.getParent()},A.exports=i},function(A,P,N){var u=N(0);function h(){}for(var a in u)h[a]=u[a];h.MAX_ITERATIONS=2500,h.DEFAULT_EDGE_LENGTH=50,h.DEFAULT_SPRING_STRENGTH=.45,h.DEFAULT_REPULSION_STRENGTH=4500,h.DEFAULT_GRAVITY_STRENGTH=.4,h.DEFAULT_COMPOUND_GRAVITY_STRENGTH=1,h.DEFAULT_GRAVITY_RANGE_FACTOR=3.8,h.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=1.5,h.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION=!0,h.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION=!0,h.DEFAULT_COOLING_FACTOR_INCREMENTAL=.3,h.COOLING_ADAPTATION_FACTOR=.33,h.ADAPTATION_LOWER_NODE_LIMIT=1e3,h.ADAPTATION_UPPER_NODE_LIMIT=5e3,h.MAX_NODE_DISPLACEMENT_INCREMENTAL=100,h.MAX_NODE_DISPLACEMENT=h.MAX_NODE_DISPLACEMENT_INCREMENTAL*3,h.MIN_REPULSION_DIST=h.DEFAULT_EDGE_LENGTH/10,h.CONVERGENCE_CHECK_PERIOD=100,h.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=.1,h.MIN_EDGE_LENGTH=1,h.GRID_CALCULATION_CHECK_PERIOD=10,A.exports=h},function(A,P,N){function u(h,a){h==null&&a==null?(this.x=0,this.y=0):(this.x=h,this.y=a)}u.prototype.getX=function(){return this.x},u.prototype.getY=function(){return this.y},u.prototype.setX=function(h){this.x=h},u.prototype.setY=function(h){this.y=h},u.prototype.getDifference=function(h){return new DimensionD(this.x-h.x,this.y-h.y)},u.prototype.getCopy=function(){return new u(this.x,this.y)},u.prototype.translate=function(h){return this.x+=h.width,this.y+=h.height,this},A.exports=u},function(A,P,N){var u=N(2),h=N(10),a=N(0),r=N(7),e=N(3),f=N(1),i=N(13),g=N(12),t=N(11);function o(c,l,T){u.call(this,T),this.estimatedSize=h.MIN_VALUE,this.margin=a.DEFAULT_GRAPH_MARGIN,this.edges=[],this.nodes=[],this.isConnected=!1,this.parent=c,l!=null&&l instanceof r?this.graphManager=l:l!=null&&l instanceof Layout&&(this.graphManager=l.graphManager)}o.prototype=Object.create(u.prototype);for(var s in u)o[s]=u[s];o.prototype.getNodes=function(){return this.nodes},o.prototype.getEdges=function(){return this.edges},o.prototype.getGraphManager=function(){return this.graphManager},o.prototype.getParent=function(){return this.parent},o.prototype.getLeft=function(){return this.left},o.prototype.getRight=function(){return this.right},o.prototype.getTop=function(){return this.top},o.prototype.getBottom=function(){return this.bottom},o.prototype.isConnected=function(){return this.isConnected},o.prototype.add=function(c,l,T){if(l==null&&T==null){var d=c;if(this.graphManager==null)throw"Graph has no graph mgr!";if(this.getNodes().indexOf(d)>-1)throw"Node already in graph!";return d.owner=this,this.getNodes().push(d),d}else{var v=c;if(!(this.getNodes().indexOf(l)>-1&&this.getNodes().indexOf(T)>-1))throw"Source or target not in graph!";if(!(l.owner==T.owner&&l.owner==this))throw"Both owners must be this graph!";return l.owner!=T.owner?null:(v.source=l,v.target=T,v.isInterGraph=!1,this.getEdges().push(v),l.edges.push(v),T!=l&&T.edges.push(v),v)}},o.prototype.remove=function(c){var l=c;if(c instanceof e){if(l==null)throw"Node is null!";if(!(l.owner!=null&&l.owner==this))throw"Owner graph is invalid!";if(this.graphManager==null)throw"Owner graph manager is invalid!";for(var T=l.edges.slice(),d,v=T.length,L=0;L-1&&G>-1))throw"Source and/or target doesn't know this edge!";d.source.edges.splice(C,1),d.target!=d.source&&d.target.edges.splice(G,1);var S=d.source.owner.getEdges().indexOf(d);if(S==-1)throw"Not in owner's edge list!";d.source.owner.getEdges().splice(S,1)}},o.prototype.updateLeftTop=function(){for(var c=h.MAX_VALUE,l=h.MAX_VALUE,T,d,v,L=this.getNodes(),S=L.length,C=0;CT&&(c=T),l>d&&(l=d)}return c==h.MAX_VALUE?null:(L[0].getParent().paddingLeft!=null?v=L[0].getParent().paddingLeft:v=this.margin,this.left=l-v,this.top=c-v,new g(this.left,this.top))},o.prototype.updateBounds=function(c){for(var l=h.MAX_VALUE,T=-h.MAX_VALUE,d=h.MAX_VALUE,v=-h.MAX_VALUE,L,S,C,G,K,X=this.nodes,Q=X.length,O=0;OL&&(l=L),TC&&(d=C),vL&&(l=L),TC&&(d=C),v=this.nodes.length){var Q=0;T.forEach(function(O){O.owner==c&&Q++}),Q==this.nodes.length&&(this.isConnected=!0)}},A.exports=o},function(A,P,N){var u,h=N(1);function a(r){u=N(6),this.layout=r,this.graphs=[],this.edges=[]}a.prototype.addRoot=function(){var r=this.layout.newGraph(),e=this.layout.newNode(null),f=this.add(r,e);return this.setRootGraph(f),this.rootGraph},a.prototype.add=function(r,e,f,i,g){if(f==null&&i==null&&g==null){if(r==null)throw"Graph is null!";if(e==null)throw"Parent node is null!";if(this.graphs.indexOf(r)>-1)throw"Graph already in this graph mgr!";if(this.graphs.push(r),r.parent!=null)throw"Already has a parent!";if(e.child!=null)throw"Already has a child!";return r.parent=e,e.child=r,r}else{g=f,i=e,f=r;var t=i.getOwner(),o=g.getOwner();if(!(t!=null&&t.getGraphManager()==this))throw"Source not in this graph mgr!";if(!(o!=null&&o.getGraphManager()==this))throw"Target not in this graph mgr!";if(t==o)return f.isInterGraph=!1,t.add(f,i,g);if(f.isInterGraph=!0,f.source=i,f.target=g,this.edges.indexOf(f)>-1)throw"Edge already in inter-graph edge list!";if(this.edges.push(f),!(f.source!=null&&f.target!=null))throw"Edge source and/or target is null!";if(!(f.source.edges.indexOf(f)==-1&&f.target.edges.indexOf(f)==-1))throw"Edge already in source and/or target incidency list!";return f.source.edges.push(f),f.target.edges.push(f),f}},a.prototype.remove=function(r){if(r instanceof u){var e=r;if(e.getGraphManager()!=this)throw"Graph not in this graph mgr";if(!(e==this.rootGraph||e.parent!=null&&e.parent.graphManager==this))throw"Invalid parent node!";var f=[];f=f.concat(e.getEdges());for(var i,g=f.length,t=0;t=r.getRight()?e[0]+=Math.min(r.getX()-a.getX(),a.getRight()-r.getRight()):r.getX()<=a.getX()&&r.getRight()>=a.getRight()&&(e[0]+=Math.min(a.getX()-r.getX(),r.getRight()-a.getRight())),a.getY()<=r.getY()&&a.getBottom()>=r.getBottom()?e[1]+=Math.min(r.getY()-a.getY(),a.getBottom()-r.getBottom()):r.getY()<=a.getY()&&r.getBottom()>=a.getBottom()&&(e[1]+=Math.min(a.getY()-r.getY(),r.getBottom()-a.getBottom()));var g=Math.abs((r.getCenterY()-a.getCenterY())/(r.getCenterX()-a.getCenterX()));r.getCenterY()===a.getCenterY()&&r.getCenterX()===a.getCenterX()&&(g=1);var t=g*e[0],o=e[1]/g;e[0]t)return e[0]=f,e[1]=s,e[2]=g,e[3]=X,!1;if(ig)return e[0]=o,e[1]=i,e[2]=G,e[3]=t,!1;if(fg?(e[0]=l,e[1]=T,n=!0):(e[0]=c,e[1]=s,n=!0):p===y&&(f>g?(e[0]=o,e[1]=s,n=!0):(e[0]=d,e[1]=T,n=!0)),-E===y?g>f?(e[2]=K,e[3]=X,m=!0):(e[2]=G,e[3]=C,m=!0):E===y&&(g>f?(e[2]=S,e[3]=C,m=!0):(e[2]=Q,e[3]=X,m=!0)),n&&m)return!1;if(f>g?i>t?(R=this.getCardinalDirection(p,y,4),M=this.getCardinalDirection(E,y,2)):(R=this.getCardinalDirection(-p,y,3),M=this.getCardinalDirection(-E,y,1)):i>t?(R=this.getCardinalDirection(-p,y,1),M=this.getCardinalDirection(-E,y,3)):(R=this.getCardinalDirection(p,y,2),M=this.getCardinalDirection(E,y,4)),!n)switch(R){case 1:W=s,F=f+-L/y,e[0]=F,e[1]=W;break;case 2:F=d,W=i+v*y,e[0]=F,e[1]=W;break;case 3:W=T,F=f+L/y,e[0]=F,e[1]=W;break;case 4:F=l,W=i+-v*y,e[0]=F,e[1]=W;break}if(!m)switch(M){case 1:k=C,x=g+-rt/y,e[2]=x,e[3]=k;break;case 2:x=Q,k=t+O*y,e[2]=x,e[3]=k;break;case 3:k=X,x=g+rt/y,e[2]=x,e[3]=k;break;case 4:x=K,k=t+-O*y,e[2]=x,e[3]=k;break}}return!1},h.getCardinalDirection=function(a,r,e){return a>r?e:1+e%4},h.getIntersection=function(a,r,e,f){if(f==null)return this.getIntersection2(a,r,e);var i=a.x,g=a.y,t=r.x,o=r.y,s=e.x,c=e.y,l=f.x,T=f.y,d=void 0,v=void 0,L=void 0,S=void 0,C=void 0,G=void 0,K=void 0,X=void 0,Q=void 0;return L=o-g,C=i-t,K=t*g-i*o,S=T-c,G=s-l,X=l*c-s*T,Q=L*G-S*C,Q===0?null:(d=(C*X-G*K)/Q,v=(S*K-L*X)/Q,new u(d,v))},h.angleOfVector=function(a,r,e,f){var i=void 0;return a!==e?(i=Math.atan((f-r)/(e-a)),e=0){var T=(-s+Math.sqrt(s*s-4*o*c))/(2*o),d=(-s-Math.sqrt(s*s-4*o*c))/(2*o),v=null;return T>=0&&T<=1?[T]:d>=0&&d<=1?[d]:v}else return null},h.HALF_PI=.5*Math.PI,h.ONE_AND_HALF_PI=1.5*Math.PI,h.TWO_PI=2*Math.PI,h.THREE_PI=3*Math.PI,A.exports=h},function(A,P,N){function u(){}u.sign=function(h){return h>0?1:h<0?-1:0},u.floor=function(h){return h<0?Math.ceil(h):Math.floor(h)},u.ceil=function(h){return h<0?Math.floor(h):Math.ceil(h)},A.exports=u},function(A,P,N){function u(){}u.MAX_VALUE=2147483647,u.MIN_VALUE=-2147483648,A.exports=u},function(A,P,N){var u=function(){function i(g,t){for(var o=0;o"u"?"undefined":u(a);return a==null||r!="object"&&r!="function"},A.exports=h},function(A,P,N){function u(s){if(Array.isArray(s)){for(var c=0,l=Array(s.length);c0&&c;){for(L.push(C[0]);L.length>0&&c;){var G=L[0];L.splice(0,1),v.add(G);for(var K=G.getEdges(),d=0;d-1&&C.splice(rt,1)}v=new Set,S=new Map}}return s},o.prototype.createDummyNodesForBendpoints=function(s){for(var c=[],l=s.source,T=this.graphManager.calcLowestCommonAncestor(s.source,s.target),d=0;d0){for(var T=this.edgeToDummyNodes.get(l),d=0;d=0&&c.splice(X,1);var Q=S.getNeighborsList();Q.forEach(function(n){if(l.indexOf(n)<0){var m=T.get(n),p=m-1;p==1&&G.push(n),T.set(n,p)}})}l=l.concat(G),(c.length==1||c.length==2)&&(d=!0,v=c[0])}return v},o.prototype.setGraphManager=function(s){this.graphManager=s},A.exports=o},function(A,P,N){function u(){}u.seed=1,u.x=0,u.nextDouble=function(){return u.x=Math.sin(u.seed++)*1e4,u.x-Math.floor(u.x)},A.exports=u},function(A,P,N){var u=N(5);function h(a,r){this.lworldOrgX=0,this.lworldOrgY=0,this.ldeviceOrgX=0,this.ldeviceOrgY=0,this.lworldExtX=1,this.lworldExtY=1,this.ldeviceExtX=1,this.ldeviceExtY=1}h.prototype.getWorldOrgX=function(){return this.lworldOrgX},h.prototype.setWorldOrgX=function(a){this.lworldOrgX=a},h.prototype.getWorldOrgY=function(){return this.lworldOrgY},h.prototype.setWorldOrgY=function(a){this.lworldOrgY=a},h.prototype.getWorldExtX=function(){return this.lworldExtX},h.prototype.setWorldExtX=function(a){this.lworldExtX=a},h.prototype.getWorldExtY=function(){return this.lworldExtY},h.prototype.setWorldExtY=function(a){this.lworldExtY=a},h.prototype.getDeviceOrgX=function(){return this.ldeviceOrgX},h.prototype.setDeviceOrgX=function(a){this.ldeviceOrgX=a},h.prototype.getDeviceOrgY=function(){return this.ldeviceOrgY},h.prototype.setDeviceOrgY=function(a){this.ldeviceOrgY=a},h.prototype.getDeviceExtX=function(){return this.ldeviceExtX},h.prototype.setDeviceExtX=function(a){this.ldeviceExtX=a},h.prototype.getDeviceExtY=function(){return this.ldeviceExtY},h.prototype.setDeviceExtY=function(a){this.ldeviceExtY=a},h.prototype.transformX=function(a){var r=0,e=this.lworldExtX;return e!=0&&(r=this.ldeviceOrgX+(a-this.lworldOrgX)*this.ldeviceExtX/e),r},h.prototype.transformY=function(a){var r=0,e=this.lworldExtY;return e!=0&&(r=this.ldeviceOrgY+(a-this.lworldOrgY)*this.ldeviceExtY/e),r},h.prototype.inverseTransformX=function(a){var r=0,e=this.ldeviceExtX;return e!=0&&(r=this.lworldOrgX+(a-this.ldeviceOrgX)*this.lworldExtX/e),r},h.prototype.inverseTransformY=function(a){var r=0,e=this.ldeviceExtY;return e!=0&&(r=this.lworldOrgY+(a-this.ldeviceOrgY)*this.lworldExtY/e),r},h.prototype.inverseTransformPoint=function(a){var r=new u(this.inverseTransformX(a.x),this.inverseTransformY(a.y));return r},A.exports=h},function(A,P,N){function u(t){if(Array.isArray(t)){for(var o=0,s=Array(t.length);oa.ADAPTATION_LOWER_NODE_LIMIT&&(this.coolingFactor=Math.max(this.coolingFactor*a.COOLING_ADAPTATION_FACTOR,this.coolingFactor-(t-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*this.coolingFactor*(1-a.COOLING_ADAPTATION_FACTOR))),this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT_INCREMENTAL):(t>a.ADAPTATION_LOWER_NODE_LIMIT?this.coolingFactor=Math.max(a.COOLING_ADAPTATION_FACTOR,1-(t-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*(1-a.COOLING_ADAPTATION_FACTOR)):this.coolingFactor=1,this.initialCoolingFactor=this.coolingFactor,this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT),this.maxIterations=Math.max(this.getAllNodes().length*5,this.maxIterations),this.displacementThresholdPerNode=3*a.DEFAULT_EDGE_LENGTH/100,this.totalDisplacementThreshold=this.displacementThresholdPerNode*this.getAllNodes().length,this.repulsionRange=this.calcRepulsionRange()},i.prototype.calcSpringForces=function(){for(var t=this.getAllEdges(),o,s=0;s0&&arguments[0]!==void 0?arguments[0]:!0,o=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,s,c,l,T,d=this.getAllNodes(),v;if(this.useFRGridVariant)for(this.totalIterations%a.GRID_CALCULATION_CHECK_PERIOD==1&&t&&this.updateGrid(),v=new Set,s=0;sL||v>L)&&(t.gravitationForceX=-this.gravityConstant*l,t.gravitationForceY=-this.gravityConstant*T)):(L=o.getEstimatedSize()*this.compoundGravityRangeFactor,(d>L||v>L)&&(t.gravitationForceX=-this.gravityConstant*l*this.compoundGravityConstant,t.gravitationForceY=-this.gravityConstant*T*this.compoundGravityConstant))},i.prototype.isConverged=function(){var t,o=!1;return this.totalIterations>this.maxIterations/3&&(o=Math.abs(this.totalDisplacement-this.oldTotalDisplacement)<2),t=this.totalDisplacement=d.length||L>=d[0].length)){for(var S=0;Si}}]),e}();A.exports=r},function(A,P,N){function u(){}u.svd=function(h){this.U=null,this.V=null,this.s=null,this.m=0,this.n=0,this.m=h.length,this.n=h[0].length;var a=Math.min(this.m,this.n);this.s=function(Tt){for(var Ct=[];Tt-- >0;)Ct.push(0);return Ct}(Math.min(this.m+1,this.n)),this.U=function(Tt){var Ct=function Bt(bt){if(bt.length==0)return 0;for(var zt=[],St=0;St0;)Ct.push(0);return Ct}(this.n),e=function(Tt){for(var Ct=[];Tt-- >0;)Ct.push(0);return Ct}(this.m),f=!0,i=Math.min(this.m-1,this.n),g=Math.max(0,Math.min(this.n-2,this.m)),t=0;t=0;E--)if(this.s[E]!==0){for(var y=E+1;y=0;V--){if(function(Tt,Ct){return Tt&&Ct}(V0;){var q=void 0,It=void 0;for(q=n-2;q>=-1&&q!==-1;q--)if(Math.abs(r[q])<=ht+_*(Math.abs(this.s[q])+Math.abs(this.s[q+1]))){r[q]=0;break}if(q===n-2)It=4;else{var Nt=void 0;for(Nt=n-1;Nt>=q&&Nt!==q;Nt--){var vt=(Nt!==n?Math.abs(r[Nt]):0)+(Nt!==q+1?Math.abs(r[Nt-1]):0);if(Math.abs(this.s[Nt])<=ht+_*vt){this.s[Nt]=0;break}}Nt===q?It=3:Nt===n-1?It=1:(It=2,q=Nt)}switch(q++,It){case 1:{var it=r[n-2];r[n-2]=0;for(var gt=n-2;gt>=q;gt--){var mt=u.hypot(this.s[gt],it),At=this.s[gt]/mt,Ot=it/mt;this.s[gt]=mt,gt!==q&&(it=-Ot*r[gt-1],r[gt-1]=At*r[gt-1]);for(var Et=0;Et=this.s[q+1]);){var Lt=this.s[q];if(this.s[q]=this.s[q+1],this.s[q+1]=Lt,qMath.abs(a)?(r=a/h,r=Math.abs(h)*Math.sqrt(1+r*r)):a!=0?(r=h/a,r=Math.abs(a)*Math.sqrt(1+r*r)):r=0,r},A.exports=u},function(A,P,N){var u=function(){function r(e,f){for(var i=0;i2&&arguments[2]!==void 0?arguments[2]:1,g=arguments.length>3&&arguments[3]!==void 0?arguments[3]:-1,t=arguments.length>4&&arguments[4]!==void 0?arguments[4]:-1;h(this,r),this.sequence1=e,this.sequence2=f,this.match_score=i,this.mismatch_penalty=g,this.gap_penalty=t,this.iMax=e.length+1,this.jMax=f.length+1,this.grid=new Array(this.iMax);for(var o=0;o=0;e--){var f=this.listeners[e];f.event===a&&f.callback===r&&this.listeners.splice(e,1)}},h.emit=function(a,r){for(var e=0;e{var P={45:(a,r,e)=>{var f={};f.layoutBase=e(551),f.CoSEConstants=e(806),f.CoSEEdge=e(767),f.CoSEGraph=e(880),f.CoSEGraphManager=e(578),f.CoSELayout=e(765),f.CoSENode=e(991),f.ConstraintHandler=e(902),a.exports=f},806:(a,r,e)=>{var f=e(551).FDLayoutConstants;function i(){}for(var g in f)i[g]=f[g];i.DEFAULT_USE_MULTI_LEVEL_SCALING=!1,i.DEFAULT_RADIAL_SEPARATION=f.DEFAULT_EDGE_LENGTH,i.DEFAULT_COMPONENT_SEPERATION=60,i.TILE=!0,i.TILING_PADDING_VERTICAL=10,i.TILING_PADDING_HORIZONTAL=10,i.TRANSFORM_ON_CONSTRAINT_HANDLING=!0,i.ENFORCE_CONSTRAINTS=!0,i.APPLY_LAYOUT=!0,i.RELAX_MOVEMENT_ON_CONSTRAINTS=!0,i.TREE_REDUCTION_ON_INCREMENTAL=!0,i.PURE_INCREMENTAL=i.DEFAULT_INCREMENTAL,a.exports=i},767:(a,r,e)=>{var f=e(551).FDLayoutEdge;function i(t,o,s){f.call(this,t,o,s)}i.prototype=Object.create(f.prototype);for(var g in f)i[g]=f[g];a.exports=i},880:(a,r,e)=>{var f=e(551).LGraph;function i(t,o,s){f.call(this,t,o,s)}i.prototype=Object.create(f.prototype);for(var g in f)i[g]=f[g];a.exports=i},578:(a,r,e)=>{var f=e(551).LGraphManager;function i(t){f.call(this,t)}i.prototype=Object.create(f.prototype);for(var g in f)i[g]=f[g];a.exports=i},765:(a,r,e)=>{var f=e(551).FDLayout,i=e(578),g=e(880),t=e(991),o=e(767),s=e(806),c=e(902),l=e(551).FDLayoutConstants,T=e(551).LayoutConstants,d=e(551).Point,v=e(551).PointD,L=e(551).DimensionD,S=e(551).Layout,C=e(551).Integer,G=e(551).IGeometry,K=e(551).LGraph,X=e(551).Transform,Q=e(551).LinkedList;function O(){f.call(this),this.toBeTiled={},this.constraints={}}O.prototype=Object.create(f.prototype);for(var rt in f)O[rt]=f[rt];O.prototype.newGraphManager=function(){var n=new i(this);return this.graphManager=n,n},O.prototype.newGraph=function(n){return new g(null,this.graphManager,n)},O.prototype.newNode=function(n){return new t(this.graphManager,n)},O.prototype.newEdge=function(n){return new o(null,null,n)},O.prototype.initParameters=function(){f.prototype.initParameters.call(this,arguments),this.isSubLayout||(s.DEFAULT_EDGE_LENGTH<10?this.idealEdgeLength=10:this.idealEdgeLength=s.DEFAULT_EDGE_LENGTH,this.useSmartIdealEdgeLengthCalculation=s.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION,this.gravityConstant=l.DEFAULT_GRAVITY_STRENGTH,this.compoundGravityConstant=l.DEFAULT_COMPOUND_GRAVITY_STRENGTH,this.gravityRangeFactor=l.DEFAULT_GRAVITY_RANGE_FACTOR,this.compoundGravityRangeFactor=l.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR,this.prunedNodesAll=[],this.growTreeIterations=0,this.afterGrowthIterations=0,this.isTreeGrowing=!1,this.isGrowthFinished=!1)},O.prototype.initSpringEmbedder=function(){f.prototype.initSpringEmbedder.call(this),this.coolingCycle=0,this.maxCoolingCycle=this.maxIterations/l.CONVERGENCE_CHECK_PERIOD,this.finalTemperature=.04,this.coolingAdjuster=1},O.prototype.layout=function(){var n=T.DEFAULT_CREATE_BENDS_AS_NEEDED;return n&&(this.createBendpoints(),this.graphManager.resetAllEdges()),this.level=0,this.classicLayout()},O.prototype.classicLayout=function(){if(this.nodesWithGravity=this.calculateNodesToApplyGravitationTo(),this.graphManager.setAllNodesToApplyGravitation(this.nodesWithGravity),this.calcNoOfChildrenForAllNodes(),this.graphManager.calcLowestCommonAncestors(),this.graphManager.calcInclusionTreeDepths(),this.graphManager.getRoot().calcEstimatedSize(),this.calcIdealEdgeLengths(),this.incremental){if(s.TREE_REDUCTION_ON_INCREMENTAL){this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var m=new Set(this.getAllNodes()),p=this.nodesWithGravity.filter(function(R){return m.has(R)});this.graphManager.setAllNodesToApplyGravitation(p)}}else{var n=this.getFlatForest();if(n.length>0)this.positionNodesRadially(n);else{this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var m=new Set(this.getAllNodes()),p=this.nodesWithGravity.filter(function(E){return m.has(E)});this.graphManager.setAllNodesToApplyGravitation(p),this.positionNodesRandomly()}}return Object.keys(this.constraints).length>0&&(c.handleConstraints(this),this.initConstraintVariables()),this.initSpringEmbedder(),s.APPLY_LAYOUT&&this.runSpringEmbedder(),!0},O.prototype.tick=function(){if(this.totalIterations++,this.totalIterations===this.maxIterations&&!this.isTreeGrowing&&!this.isGrowthFinished)if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;if(this.totalIterations%l.CONVERGENCE_CHECK_PERIOD==0&&!this.isTreeGrowing&&!this.isGrowthFinished){if(this.isConverged())if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;this.coolingCycle++,this.layoutQuality==0?this.coolingAdjuster=this.coolingCycle:this.layoutQuality==1&&(this.coolingAdjuster=this.coolingCycle/3),this.coolingFactor=Math.max(this.initialCoolingFactor-Math.pow(this.coolingCycle,Math.log(100*(this.initialCoolingFactor-this.finalTemperature))/Math.log(this.maxCoolingCycle))/100*this.coolingAdjuster,this.finalTemperature),this.animationPeriod=Math.ceil(this.initialAnimationPeriod*Math.sqrt(this.coolingFactor))}if(this.isTreeGrowing){if(this.growTreeIterations%10==0)if(this.prunedNodesAll.length>0){this.graphManager.updateBounds(),this.updateGrid(),this.growTree(this.prunedNodesAll),this.graphManager.resetAllNodesToApplyGravitation();var n=new Set(this.getAllNodes()),m=this.nodesWithGravity.filter(function(y){return n.has(y)});this.graphManager.setAllNodesToApplyGravitation(m),this.graphManager.updateBounds(),this.updateGrid(),s.PURE_INCREMENTAL?this.coolingFactor=l.DEFAULT_COOLING_FACTOR_INCREMENTAL/2:this.coolingFactor=l.DEFAULT_COOLING_FACTOR_INCREMENTAL}else this.isTreeGrowing=!1,this.isGrowthFinished=!0;this.growTreeIterations++}if(this.isGrowthFinished){if(this.isConverged())return!0;this.afterGrowthIterations%10==0&&(this.graphManager.updateBounds(),this.updateGrid()),s.PURE_INCREMENTAL?this.coolingFactor=l.DEFAULT_COOLING_FACTOR_INCREMENTAL/2*((100-this.afterGrowthIterations)/100):this.coolingFactor=l.DEFAULT_COOLING_FACTOR_INCREMENTAL*((100-this.afterGrowthIterations)/100),this.afterGrowthIterations++}var p=!this.isTreeGrowing&&!this.isGrowthFinished,E=this.growTreeIterations%10==1&&this.isTreeGrowing||this.afterGrowthIterations%10==1&&this.isGrowthFinished;return this.totalDisplacement=0,this.graphManager.updateBounds(),this.calcSpringForces(),this.calcRepulsionForces(p,E),this.calcGravitationalForces(),this.moveNodes(),this.animate(),!1},O.prototype.getPositionsData=function(){for(var n=this.graphManager.getAllNodes(),m={},p=0;p0&&this.updateDisplacements();for(var p=0;p0&&(E.fixedNodeWeight=R)}}if(this.constraints.relativePlacementConstraint){var M=new Map,F=new Map;if(this.dummyToNodeForVerticalAlignment=new Map,this.dummyToNodeForHorizontalAlignment=new Map,this.fixedNodesOnHorizontal=new Set,this.fixedNodesOnVertical=new Set,this.fixedNodeSet.forEach(function(w){n.fixedNodesOnHorizontal.add(w),n.fixedNodesOnVertical.add(w)}),this.constraints.alignmentConstraint){if(this.constraints.alignmentConstraint.vertical)for(var W=this.constraints.alignmentConstraint.vertical,p=0;p=2*w.length/3;_--)H=Math.floor(Math.random()*(_+1)),B=w[_],w[_]=w[H],w[H]=B;return w},this.nodesInRelativeHorizontal=[],this.nodesInRelativeVertical=[],this.nodeToRelativeConstraintMapHorizontal=new Map,this.nodeToRelativeConstraintMapVertical=new Map,this.nodeToTempPositionMapHorizontal=new Map,this.nodeToTempPositionMapVertical=new Map,this.constraints.relativePlacementConstraint.forEach(function(w){if(w.left){var H=M.has(w.left)?M.get(w.left):w.left,B=M.has(w.right)?M.get(w.right):w.right;n.nodesInRelativeHorizontal.includes(H)||(n.nodesInRelativeHorizontal.push(H),n.nodeToRelativeConstraintMapHorizontal.set(H,[]),n.dummyToNodeForVerticalAlignment.has(H)?n.nodeToTempPositionMapHorizontal.set(H,n.idToNodeMap.get(n.dummyToNodeForVerticalAlignment.get(H)[0]).getCenterX()):n.nodeToTempPositionMapHorizontal.set(H,n.idToNodeMap.get(H).getCenterX())),n.nodesInRelativeHorizontal.includes(B)||(n.nodesInRelativeHorizontal.push(B),n.nodeToRelativeConstraintMapHorizontal.set(B,[]),n.dummyToNodeForVerticalAlignment.has(B)?n.nodeToTempPositionMapHorizontal.set(B,n.idToNodeMap.get(n.dummyToNodeForVerticalAlignment.get(B)[0]).getCenterX()):n.nodeToTempPositionMapHorizontal.set(B,n.idToNodeMap.get(B).getCenterX())),n.nodeToRelativeConstraintMapHorizontal.get(H).push({right:B,gap:w.gap}),n.nodeToRelativeConstraintMapHorizontal.get(B).push({left:H,gap:w.gap})}else{var _=F.has(w.top)?F.get(w.top):w.top,ht=F.has(w.bottom)?F.get(w.bottom):w.bottom;n.nodesInRelativeVertical.includes(_)||(n.nodesInRelativeVertical.push(_),n.nodeToRelativeConstraintMapVertical.set(_,[]),n.dummyToNodeForHorizontalAlignment.has(_)?n.nodeToTempPositionMapVertical.set(_,n.idToNodeMap.get(n.dummyToNodeForHorizontalAlignment.get(_)[0]).getCenterY()):n.nodeToTempPositionMapVertical.set(_,n.idToNodeMap.get(_).getCenterY())),n.nodesInRelativeVertical.includes(ht)||(n.nodesInRelativeVertical.push(ht),n.nodeToRelativeConstraintMapVertical.set(ht,[]),n.dummyToNodeForHorizontalAlignment.has(ht)?n.nodeToTempPositionMapVertical.set(ht,n.idToNodeMap.get(n.dummyToNodeForHorizontalAlignment.get(ht)[0]).getCenterY()):n.nodeToTempPositionMapVertical.set(ht,n.idToNodeMap.get(ht).getCenterY())),n.nodeToRelativeConstraintMapVertical.get(_).push({bottom:ht,gap:w.gap}),n.nodeToRelativeConstraintMapVertical.get(ht).push({top:_,gap:w.gap})}});else{var k=new Map,V=new Map;this.constraints.relativePlacementConstraint.forEach(function(w){if(w.left){var H=M.has(w.left)?M.get(w.left):w.left,B=M.has(w.right)?M.get(w.right):w.right;k.has(H)?k.get(H).push(B):k.set(H,[B]),k.has(B)?k.get(B).push(H):k.set(B,[H])}else{var _=F.has(w.top)?F.get(w.top):w.top,ht=F.has(w.bottom)?F.get(w.bottom):w.bottom;V.has(_)?V.get(_).push(ht):V.set(_,[ht]),V.has(ht)?V.get(ht).push(_):V.set(ht,[_])}});var Y=function(H,B){var _=[],ht=[],q=new Q,It=new Set,Nt=0;return H.forEach(function(vt,it){if(!It.has(it)){_[Nt]=[],ht[Nt]=!1;var gt=it;for(q.push(gt),It.add(gt),_[Nt].push(gt);q.length!=0;){gt=q.shift(),B.has(gt)&&(ht[Nt]=!0);var mt=H.get(gt);mt.forEach(function(At){It.has(At)||(q.push(At),It.add(At),_[Nt].push(At))})}Nt++}}),{components:_,isFixed:ht}},et=Y(k,n.fixedNodesOnHorizontal);this.componentsOnHorizontal=et.components,this.fixedComponentsOnHorizontal=et.isFixed;var z=Y(V,n.fixedNodesOnVertical);this.componentsOnVertical=z.components,this.fixedComponentsOnVertical=z.isFixed}}},O.prototype.updateDisplacements=function(){var n=this;if(this.constraints.fixedNodeConstraint&&this.constraints.fixedNodeConstraint.forEach(function(z){var w=n.idToNodeMap.get(z.nodeId);w.displacementX=0,w.displacementY=0}),this.constraints.alignmentConstraint){if(this.constraints.alignmentConstraint.vertical)for(var m=this.constraints.alignmentConstraint.vertical,p=0;p1){var F;for(F=0;FE&&(E=Math.floor(M.y)),R=Math.floor(M.x+s.DEFAULT_COMPONENT_SEPERATION)}this.transform(new v(T.WORLD_CENTER_X-M.x/2,T.WORLD_CENTER_Y-M.y/2))},O.radialLayout=function(n,m,p){var E=Math.max(this.maxDiagonalInTree(n),s.DEFAULT_RADIAL_SEPARATION);O.branchRadialLayout(m,null,0,359,0,E);var y=K.calculateBounds(n),R=new X;R.setDeviceOrgX(y.getMinX()),R.setDeviceOrgY(y.getMinY()),R.setWorldOrgX(p.x),R.setWorldOrgY(p.y);for(var M=0;M1;){var B=H[0];H.splice(0,1);var _=V.indexOf(B);_>=0&&V.splice(_,1),z--,Y--}m!=null?w=(V.indexOf(H[0])+1)%z:w=0;for(var ht=Math.abs(E-p)/Y,q=w;et!=Y;q=++q%z){var It=V[q].getOtherEnd(n);if(It!=m){var Nt=(p+et*ht)%360,vt=(Nt+ht)%360;O.branchRadialLayout(It,n,Nt,vt,y+R,R),et++}}},O.maxDiagonalInTree=function(n){for(var m=C.MIN_VALUE,p=0;pm&&(m=y)}return m},O.prototype.calcRepulsionRange=function(){return 2*(this.level+1)*this.idealEdgeLength},O.prototype.groupZeroDegreeMembers=function(){var n=this,m={};this.memberGroups={},this.idToDummyNode={};for(var p=[],E=this.graphManager.getAllNodes(),y=0;y"u"&&(m[F]=[]),m[F]=m[F].concat(R)}Object.keys(m).forEach(function(W){if(m[W].length>1){var x="DummyCompound_"+W;n.memberGroups[x]=m[W];var k=m[W][0].getParent(),V=new t(n.graphManager);V.id=x,V.paddingLeft=k.paddingLeft||0,V.paddingRight=k.paddingRight||0,V.paddingBottom=k.paddingBottom||0,V.paddingTop=k.paddingTop||0,n.idToDummyNode[x]=V;var Y=n.getGraphManager().add(n.newGraph(),V),et=k.getChild();et.add(V);for(var z=0;zy?(E.rect.x-=(E.labelWidth-y)/2,E.setWidth(E.labelWidth),E.labelMarginLeft=(E.labelWidth-y)/2):E.labelPosHorizontal=="right"&&E.setWidth(y+E.labelWidth)),E.labelHeight&&(E.labelPosVertical=="top"?(E.rect.y-=E.labelHeight,E.setHeight(R+E.labelHeight),E.labelMarginTop=E.labelHeight):E.labelPosVertical=="center"&&E.labelHeight>R?(E.rect.y-=(E.labelHeight-R)/2,E.setHeight(E.labelHeight),E.labelMarginTop=(E.labelHeight-R)/2):E.labelPosVertical=="bottom"&&E.setHeight(R+E.labelHeight))}})},O.prototype.repopulateCompounds=function(){for(var n=this.compoundOrder.length-1;n>=0;n--){var m=this.compoundOrder[n],p=m.id,E=m.paddingLeft,y=m.paddingTop,R=m.labelMarginLeft,M=m.labelMarginTop;this.adjustLocations(this.tiledMemberPack[p],m.rect.x,m.rect.y,E,y,R,M)}},O.prototype.repopulateZeroDegreeMembers=function(){var n=this,m=this.tiledZeroDegreePack;Object.keys(m).forEach(function(p){var E=n.idToDummyNode[p],y=E.paddingLeft,R=E.paddingTop,M=E.labelMarginLeft,F=E.labelMarginTop;n.adjustLocations(m[p],E.rect.x,E.rect.y,y,R,M,F)})},O.prototype.getToBeTiled=function(n){var m=n.id;if(this.toBeTiled[m]!=null)return this.toBeTiled[m];var p=n.getChild();if(p==null)return this.toBeTiled[m]=!1,!1;for(var E=p.getNodes(),y=0;y0)return this.toBeTiled[m]=!1,!1;if(R.getChild()==null){this.toBeTiled[R.id]=!1;continue}if(!this.getToBeTiled(R))return this.toBeTiled[m]=!1,!1}return this.toBeTiled[m]=!0,!0},O.prototype.getNodeDegree=function(n){n.id;for(var m=n.getEdges(),p=0,E=0;Ek&&(k=Y.rect.height)}p+=k+n.verticalPadding}},O.prototype.tileCompoundMembers=function(n,m){var p=this;this.tiledMemberPack=[],Object.keys(n).forEach(function(E){var y=m[E];if(p.tiledMemberPack[E]=p.tileNodes(n[E],y.paddingLeft+y.paddingRight),y.rect.width=p.tiledMemberPack[E].width,y.rect.height=p.tiledMemberPack[E].height,y.setCenter(p.tiledMemberPack[E].centerX,p.tiledMemberPack[E].centerY),y.labelMarginLeft=0,y.labelMarginTop=0,s.NODE_DIMENSIONS_INCLUDE_LABELS){var R=y.rect.width,M=y.rect.height;y.labelWidth&&(y.labelPosHorizontal=="left"?(y.rect.x-=y.labelWidth,y.setWidth(R+y.labelWidth),y.labelMarginLeft=y.labelWidth):y.labelPosHorizontal=="center"&&y.labelWidth>R?(y.rect.x-=(y.labelWidth-R)/2,y.setWidth(y.labelWidth),y.labelMarginLeft=(y.labelWidth-R)/2):y.labelPosHorizontal=="right"&&y.setWidth(R+y.labelWidth)),y.labelHeight&&(y.labelPosVertical=="top"?(y.rect.y-=y.labelHeight,y.setHeight(M+y.labelHeight),y.labelMarginTop=y.labelHeight):y.labelPosVertical=="center"&&y.labelHeight>M?(y.rect.y-=(y.labelHeight-M)/2,y.setHeight(y.labelHeight),y.labelMarginTop=(y.labelHeight-M)/2):y.labelPosVertical=="bottom"&&y.setHeight(M+y.labelHeight))}})},O.prototype.tileNodes=function(n,m){var p=this.tileNodesByFavoringDim(n,m,!0),E=this.tileNodesByFavoringDim(n,m,!1),y=this.getOrgRatio(p),R=this.getOrgRatio(E),M;return RF&&(F=z.getWidth())});var W=R/y,x=M/y,k=Math.pow(p-E,2)+4*(W+E)*(x+p)*y,V=(E-p+Math.sqrt(k))/(2*(W+E)),Y;m?(Y=Math.ceil(V),Y==V&&Y++):Y=Math.floor(V);var et=Y*(W+E)-E;return F>et&&(et=F),et+=E*2,et},O.prototype.tileNodesByFavoringDim=function(n,m,p){var E=s.TILING_PADDING_VERTICAL,y=s.TILING_PADDING_HORIZONTAL,R=s.TILING_COMPARE_BY,M={rows:[],rowWidth:[],rowHeight:[],width:0,height:m,verticalPadding:E,horizontalPadding:y,centerX:0,centerY:0};R&&(M.idealRowWidth=this.calcIdealRowWidth(n,p));var F=function(w){return w.rect.width*w.rect.height},W=function(w,H){return F(H)-F(w)};n.sort(function(z,w){var H=W;return M.idealRowWidth?(H=R,H(z.id,w.id)):H(z,w)});for(var x=0,k=0,V=0;V0&&(M+=n.horizontalPadding),n.rowWidth[p]=M,n.width0&&(F+=n.verticalPadding);var W=0;F>n.rowHeight[p]&&(W=n.rowHeight[p],n.rowHeight[p]=F,W=n.rowHeight[p]-W),n.height+=W,n.rows[p].push(m)},O.prototype.getShortestRowIndex=function(n){for(var m=-1,p=Number.MAX_VALUE,E=0;Ep&&(m=E,p=n.rowWidth[E]);return m},O.prototype.canAddHorizontal=function(n,m,p){if(n.idealRowWidth){var E=n.rows.length-1,y=n.rowWidth[E];return y+m+n.horizontalPadding<=n.idealRowWidth}var R=this.getShortestRowIndex(n);if(R<0)return!0;var M=n.rowWidth[R];if(M+n.horizontalPadding+m<=n.width)return!0;var F=0;n.rowHeight[R]0&&(F=p+n.verticalPadding-n.rowHeight[R]);var W;n.width-M>=m+n.horizontalPadding?W=(n.height+F)/(M+m+n.horizontalPadding):W=(n.height+F)/n.width,F=p+n.verticalPadding;var x;return n.widthR&&m!=p){E.splice(-1,1),n.rows[p].push(y),n.rowWidth[m]=n.rowWidth[m]-R,n.rowWidth[p]=n.rowWidth[p]+R,n.width=n.rowWidth[instance.getLongestRowIndex(n)];for(var M=Number.MIN_VALUE,F=0;FM&&(M=E[F].height);m>0&&(M+=n.verticalPadding);var W=n.rowHeight[m]+n.rowHeight[p];n.rowHeight[m]=M,n.rowHeight[p]0)for(var et=y;et<=R;et++)Y[0]+=this.grid[et][M-1].length+this.grid[et][M].length-1;if(R0)for(var et=M;et<=F;et++)Y[3]+=this.grid[y-1][et].length+this.grid[y][et].length-1;for(var z=C.MAX_VALUE,w,H,B=0;B{var f=e(551).FDLayoutNode,i=e(551).IMath;function g(o,s,c,l){f.call(this,o,s,c,l)}g.prototype=Object.create(f.prototype);for(var t in f)g[t]=f[t];g.prototype.calculateDisplacement=function(){var o=this.graphManager.getLayout();this.getChild()!=null&&this.fixedNodeWeight?(this.displacementX+=o.coolingFactor*(this.springForceX+this.repulsionForceX+this.gravitationForceX)/this.fixedNodeWeight,this.displacementY+=o.coolingFactor*(this.springForceY+this.repulsionForceY+this.gravitationForceY)/this.fixedNodeWeight):(this.displacementX+=o.coolingFactor*(this.springForceX+this.repulsionForceX+this.gravitationForceX)/this.noOfChildren,this.displacementY+=o.coolingFactor*(this.springForceY+this.repulsionForceY+this.gravitationForceY)/this.noOfChildren),Math.abs(this.displacementX)>o.coolingFactor*o.maxNodeDisplacement&&(this.displacementX=o.coolingFactor*o.maxNodeDisplacement*i.sign(this.displacementX)),Math.abs(this.displacementY)>o.coolingFactor*o.maxNodeDisplacement&&(this.displacementY=o.coolingFactor*o.maxNodeDisplacement*i.sign(this.displacementY)),this.child&&this.child.getNodes().length>0&&this.propogateDisplacementToChildren(this.displacementX,this.displacementY)},g.prototype.propogateDisplacementToChildren=function(o,s){for(var c=this.getChild().getNodes(),l,T=0;T{function f(c){if(Array.isArray(c)){for(var l=0,T=Array(c.length);l0){var Lt=0;ot.forEach(function(st){$=="horizontal"?(tt.set(st,d.has(st)?v[d.get(st)]:Z.get(st)),Lt+=tt.get(st)):(tt.set(st,d.has(st)?L[d.get(st)]:Z.get(st)),Lt+=tt.get(st))}),Lt=Lt/ot.length,lt.forEach(function(st){J.has(st)||tt.set(st,Lt)})}else{var ft=0;lt.forEach(function(st){$=="horizontal"?ft+=d.has(st)?v[d.get(st)]:Z.get(st):ft+=d.has(st)?L[d.get(st)]:Z.get(st)}),ft=ft/lt.length,lt.forEach(function(st){tt.set(st,ft)})}});for(var Mt=function(){var ot=ut.shift(),Lt=U.get(ot);Lt.forEach(function(ft){if(tt.get(ft.id)st&&(st=Zt),KtXt&&(Xt=Kt)}}catch(ee){Ct=!0,Bt=ee}finally{try{!Tt&&bt.return&&bt.return()}finally{if(Ct)throw Bt}}var he=(Lt+st)/2-(ft+Xt)/2,Qt=!0,jt=!1,_t=void 0;try{for(var Jt=lt[Symbol.iterator](),oe;!(Qt=(oe=Jt.next()).done);Qt=!0){var te=oe.value;tt.set(te,tt.get(te)+he)}}catch(ee){jt=!0,_t=ee}finally{try{!Qt&&Jt.return&&Jt.return()}finally{if(jt)throw _t}}})}return tt},rt=function(U){var $=0,J=0,Z=0,at=0;if(U.forEach(function(j){j.left?v[d.get(j.left)]-v[d.get(j.right)]>=0?$++:J++:L[d.get(j.top)]-L[d.get(j.bottom)]>=0?Z++:at++}),$>J&&Z>at)for(var ct=0;ctJ)for(var nt=0;ntat)for(var tt=0;tt1)l.fixedNodeConstraint.forEach(function(b,U){E[U]=[b.position.x,b.position.y],y[U]=[v[d.get(b.nodeId)],L[d.get(b.nodeId)]]}),R=!0;else if(l.alignmentConstraint)(function(){var b=0;if(l.alignmentConstraint.vertical){for(var U=l.alignmentConstraint.vertical,$=function(tt){var j=new Set;U[tt].forEach(function(pt){j.add(pt)});var ut=new Set([].concat(f(j)).filter(function(pt){return F.has(pt)})),Mt=void 0;ut.size>0?Mt=v[d.get(ut.values().next().value)]:Mt=Q(j).x,U[tt].forEach(function(pt){E[b]=[Mt,L[d.get(pt)]],y[b]=[v[d.get(pt)],L[d.get(pt)]],b++})},J=0;J0?Mt=v[d.get(ut.values().next().value)]:Mt=Q(j).y,Z[tt].forEach(function(pt){E[b]=[v[d.get(pt)],Mt],y[b]=[v[d.get(pt)],L[d.get(pt)]],b++})},ct=0;ctV&&(V=k[et].length,Y=et);if(V0){var Et={x:0,y:0};l.fixedNodeConstraint.forEach(function(b,U){var $={x:v[d.get(b.nodeId)],y:L[d.get(b.nodeId)]},J=b.position,Z=X(J,$);Et.x+=Z.x,Et.y+=Z.y}),Et.x/=l.fixedNodeConstraint.length,Et.y/=l.fixedNodeConstraint.length,v.forEach(function(b,U){v[U]+=Et.x}),L.forEach(function(b,U){L[U]+=Et.y}),l.fixedNodeConstraint.forEach(function(b){v[d.get(b.nodeId)]=b.position.x,L[d.get(b.nodeId)]=b.position.y})}if(l.alignmentConstraint){if(l.alignmentConstraint.vertical)for(var Dt=l.alignmentConstraint.vertical,Rt=function(U){var $=new Set;Dt[U].forEach(function(at){$.add(at)});var J=new Set([].concat(f($)).filter(function(at){return F.has(at)})),Z=void 0;J.size>0?Z=v[d.get(J.values().next().value)]:Z=Q($).x,$.forEach(function(at){F.has(at)||(v[d.get(at)]=Z)})},Ht=0;Ht0?Z=L[d.get(J.values().next().value)]:Z=Q($).y,$.forEach(function(at){F.has(at)||(L[d.get(at)]=Z)})},Ft=0;Ft{a.exports=A}},N={};function u(a){var r=N[a];if(r!==void 0)return r.exports;var e=N[a]={exports:{}};return P[a](e,e.exports,u),e.exports}var h=u(45);return h})()})}(ue)),ue.exports}(function(I,D){(function(P,N){I.exports=N(ur())})(ye,function(A){return(()=>{var P={658:a=>{a.exports=Object.assign!=null?Object.assign.bind(Object):function(r){for(var e=arguments.length,f=Array(e>1?e-1:0),i=1;i{var f=function(){function t(o,s){var c=[],l=!0,T=!1,d=void 0;try{for(var v=o[Symbol.iterator](),L;!(l=(L=v.next()).done)&&(c.push(L.value),!(s&&c.length===s));l=!0);}catch(S){T=!0,d=S}finally{try{!l&&v.return&&v.return()}finally{if(T)throw d}}return c}return function(o,s){if(Array.isArray(o))return o;if(Symbol.iterator in Object(o))return t(o,s);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=e(140).layoutBase.LinkedList,g={};g.getTopMostNodes=function(t){for(var o={},s=0;s0&&R.merge(x)});for(var M=0;M1){L=d[0],S=L.connectedEdges().length,d.forEach(function(y){y.connectedEdges().length0&&c.set("dummy"+(c.size+1),K),X},g.relocateComponent=function(t,o,s){if(!s.fixedNodeConstraint){var c=Number.POSITIVE_INFINITY,l=Number.NEGATIVE_INFINITY,T=Number.POSITIVE_INFINITY,d=Number.NEGATIVE_INFINITY;if(s.quality=="draft"){var v=!0,L=!1,S=void 0;try{for(var C=o.nodeIndexes[Symbol.iterator](),G;!(v=(G=C.next()).done);v=!0){var K=G.value,X=f(K,2),Q=X[0],O=X[1],rt=s.cy.getElementById(Q);if(rt){var n=rt.boundingBox(),m=o.xCoords[O]-n.w/2,p=o.xCoords[O]+n.w/2,E=o.yCoords[O]-n.h/2,y=o.yCoords[O]+n.h/2;ml&&(l=p),Ed&&(d=y)}}}catch(x){L=!0,S=x}finally{try{!v&&C.return&&C.return()}finally{if(L)throw S}}var R=t.x-(l+c)/2,M=t.y-(d+T)/2;o.xCoords=o.xCoords.map(function(x){return x+R}),o.yCoords=o.yCoords.map(function(x){return x+M})}else{Object.keys(o).forEach(function(x){var k=o[x],V=k.getRect().x,Y=k.getRect().x+k.getRect().width,et=k.getRect().y,z=k.getRect().y+k.getRect().height;Vl&&(l=Y),etd&&(d=z)});var F=t.x-(l+c)/2,W=t.y-(d+T)/2;Object.keys(o).forEach(function(x){var k=o[x];k.setCenter(k.getCenterX()+F,k.getCenterY()+W)})}}},g.calcBoundingBox=function(t,o,s,c){for(var l=Number.MAX_SAFE_INTEGER,T=Number.MIN_SAFE_INTEGER,d=Number.MAX_SAFE_INTEGER,v=Number.MIN_SAFE_INTEGER,L=void 0,S=void 0,C=void 0,G=void 0,K=t.descendants().not(":parent"),X=K.length,Q=0;QL&&(l=L),TC&&(d=C),v{var f=e(548),i=e(140).CoSELayout,g=e(140).CoSENode,t=e(140).layoutBase.PointD,o=e(140).layoutBase.DimensionD,s=e(140).layoutBase.LayoutConstants,c=e(140).layoutBase.FDLayoutConstants,l=e(140).CoSEConstants,T=function(v,L){var S=v.cy,C=v.eles,G=C.nodes(),K=C.edges(),X=void 0,Q=void 0,O=void 0,rt={};v.randomize&&(X=L.nodeIndexes,Q=L.xCoords,O=L.yCoords);var n=function(x){return typeof x=="function"},m=function(x,k){return n(x)?x(k):x},p=f.calcParentsWithoutChildren(S,C),E=function W(x,k,V,Y){for(var et=k.length,z=0;z0){var q=void 0;q=V.getGraphManager().add(V.newGraph(),B),W(q,H,V,Y)}}},y=function(x,k,V){for(var Y=0,et=0,z=0;z0?l.DEFAULT_EDGE_LENGTH=c.DEFAULT_EDGE_LENGTH=Y/et:n(v.idealEdgeLength)?l.DEFAULT_EDGE_LENGTH=c.DEFAULT_EDGE_LENGTH=50:l.DEFAULT_EDGE_LENGTH=c.DEFAULT_EDGE_LENGTH=v.idealEdgeLength,l.MIN_REPULSION_DIST=c.MIN_REPULSION_DIST=c.DEFAULT_EDGE_LENGTH/10,l.DEFAULT_RADIAL_SEPARATION=c.DEFAULT_EDGE_LENGTH)},R=function(x,k){k.fixedNodeConstraint&&(x.constraints.fixedNodeConstraint=k.fixedNodeConstraint),k.alignmentConstraint&&(x.constraints.alignmentConstraint=k.alignmentConstraint),k.relativePlacementConstraint&&(x.constraints.relativePlacementConstraint=k.relativePlacementConstraint)};v.nestingFactor!=null&&(l.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=c.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=v.nestingFactor),v.gravity!=null&&(l.DEFAULT_GRAVITY_STRENGTH=c.DEFAULT_GRAVITY_STRENGTH=v.gravity),v.numIter!=null&&(l.MAX_ITERATIONS=c.MAX_ITERATIONS=v.numIter),v.gravityRange!=null&&(l.DEFAULT_GRAVITY_RANGE_FACTOR=c.DEFAULT_GRAVITY_RANGE_FACTOR=v.gravityRange),v.gravityCompound!=null&&(l.DEFAULT_COMPOUND_GRAVITY_STRENGTH=c.DEFAULT_COMPOUND_GRAVITY_STRENGTH=v.gravityCompound),v.gravityRangeCompound!=null&&(l.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=c.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=v.gravityRangeCompound),v.initialEnergyOnIncremental!=null&&(l.DEFAULT_COOLING_FACTOR_INCREMENTAL=c.DEFAULT_COOLING_FACTOR_INCREMENTAL=v.initialEnergyOnIncremental),v.tilingCompareBy!=null&&(l.TILING_COMPARE_BY=v.tilingCompareBy),v.quality=="proof"?s.QUALITY=2:s.QUALITY=0,l.NODE_DIMENSIONS_INCLUDE_LABELS=c.NODE_DIMENSIONS_INCLUDE_LABELS=s.NODE_DIMENSIONS_INCLUDE_LABELS=v.nodeDimensionsIncludeLabels,l.DEFAULT_INCREMENTAL=c.DEFAULT_INCREMENTAL=s.DEFAULT_INCREMENTAL=!v.randomize,l.ANIMATE=c.ANIMATE=s.ANIMATE=v.animate,l.TILE=v.tile,l.TILING_PADDING_VERTICAL=typeof v.tilingPaddingVertical=="function"?v.tilingPaddingVertical.call():v.tilingPaddingVertical,l.TILING_PADDING_HORIZONTAL=typeof v.tilingPaddingHorizontal=="function"?v.tilingPaddingHorizontal.call():v.tilingPaddingHorizontal,l.DEFAULT_INCREMENTAL=c.DEFAULT_INCREMENTAL=s.DEFAULT_INCREMENTAL=!0,l.PURE_INCREMENTAL=!v.randomize,s.DEFAULT_UNIFORM_LEAF_NODE_SIZES=v.uniformNodeDimensions,v.step=="transformed"&&(l.TRANSFORM_ON_CONSTRAINT_HANDLING=!0,l.ENFORCE_CONSTRAINTS=!1,l.APPLY_LAYOUT=!1),v.step=="enforced"&&(l.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,l.ENFORCE_CONSTRAINTS=!0,l.APPLY_LAYOUT=!1),v.step=="cose"&&(l.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,l.ENFORCE_CONSTRAINTS=!1,l.APPLY_LAYOUT=!0),v.step=="all"&&(v.randomize?l.TRANSFORM_ON_CONSTRAINT_HANDLING=!0:l.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,l.ENFORCE_CONSTRAINTS=!0,l.APPLY_LAYOUT=!0),v.fixedNodeConstraint||v.alignmentConstraint||v.relativePlacementConstraint?l.TREE_REDUCTION_ON_INCREMENTAL=!1:l.TREE_REDUCTION_ON_INCREMENTAL=!0;var M=new i,F=M.newGraphManager();return E(F.addRoot(),f.getTopMostNodes(G),M,v),y(M,F,K),R(M,v),M.runLayout(),rt};a.exports={coseLayout:T}},212:(a,r,e)=>{var f=function(){function v(L,S){for(var C=0;C0)if(p){var R=t.getTopMostNodes(C.eles.nodes());if(O=t.connectComponents(G,C.eles,R),O.forEach(function(vt){var it=vt.boundingBox();rt.push({x:it.x1+it.w/2,y:it.y1+it.h/2})}),C.randomize&&O.forEach(function(vt){C.eles=vt,X.push(s(C))}),C.quality=="default"||C.quality=="proof"){var M=G.collection();if(C.tile){var F=new Map,W=[],x=[],k=0,V={nodeIndexes:F,xCoords:W,yCoords:x},Y=[];if(O.forEach(function(vt,it){vt.edges().length==0&&(vt.nodes().forEach(function(gt,mt){M.merge(vt.nodes()[mt]),gt.isParent()||(V.nodeIndexes.set(vt.nodes()[mt].id(),k++),V.xCoords.push(vt.nodes()[0].position().x),V.yCoords.push(vt.nodes()[0].position().y))}),Y.push(it))}),M.length>1){var et=M.boundingBox();rt.push({x:et.x1+et.w/2,y:et.y1+et.h/2}),O.push(M),X.push(V);for(var z=Y.length-1;z>=0;z--)O.splice(Y[z],1),X.splice(Y[z],1),rt.splice(Y[z],1)}}O.forEach(function(vt,it){C.eles=vt,Q.push(l(C,X[it])),t.relocateComponent(rt[it],Q[it],C)})}else O.forEach(function(vt,it){t.relocateComponent(rt[it],X[it],C)});var w=new Set;if(O.length>1){var H=[],B=K.filter(function(vt){return vt.css("display")=="none"});O.forEach(function(vt,it){var gt=void 0;if(C.quality=="draft"&&(gt=X[it].nodeIndexes),vt.nodes().not(B).length>0){var mt={};mt.edges=[],mt.nodes=[];var At=void 0;vt.nodes().not(B).forEach(function(Ot){if(C.quality=="draft")if(!Ot.isParent())At=gt.get(Ot.id()),mt.nodes.push({x:X[it].xCoords[At]-Ot.boundingbox().w/2,y:X[it].yCoords[At]-Ot.boundingbox().h/2,width:Ot.boundingbox().w,height:Ot.boundingbox().h});else{var Et=t.calcBoundingBox(Ot,X[it].xCoords,X[it].yCoords,gt);mt.nodes.push({x:Et.topLeftX,y:Et.topLeftY,width:Et.width,height:Et.height})}else Q[it][Ot.id()]&&mt.nodes.push({x:Q[it][Ot.id()].getLeft(),y:Q[it][Ot.id()].getTop(),width:Q[it][Ot.id()].getWidth(),height:Q[it][Ot.id()].getHeight()})}),vt.edges().forEach(function(Ot){var Et=Ot.source(),Dt=Ot.target();if(Et.css("display")!="none"&&Dt.css("display")!="none")if(C.quality=="draft"){var Rt=gt.get(Et.id()),Ht=gt.get(Dt.id()),Ut=[],Pt=[];if(Et.isParent()){var Ft=t.calcBoundingBox(Et,X[it].xCoords,X[it].yCoords,gt);Ut.push(Ft.topLeftX+Ft.width/2),Ut.push(Ft.topLeftY+Ft.height/2)}else Ut.push(X[it].xCoords[Rt]),Ut.push(X[it].yCoords[Rt]);if(Dt.isParent()){var Yt=t.calcBoundingBox(Dt,X[it].xCoords,X[it].yCoords,gt);Pt.push(Yt.topLeftX+Yt.width/2),Pt.push(Yt.topLeftY+Yt.height/2)}else Pt.push(X[it].xCoords[Ht]),Pt.push(X[it].yCoords[Ht]);mt.edges.push({startX:Ut[0],startY:Ut[1],endX:Pt[0],endY:Pt[1]})}else Q[it][Et.id()]&&Q[it][Dt.id()]&&mt.edges.push({startX:Q[it][Et.id()].getCenterX(),startY:Q[it][Et.id()].getCenterY(),endX:Q[it][Dt.id()].getCenterX(),endY:Q[it][Dt.id()].getCenterY()})}),mt.nodes.length>0&&(H.push(mt),w.add(it))}});var _=m.packComponents(H,C.randomize).shifts;if(C.quality=="draft")X.forEach(function(vt,it){var gt=vt.xCoords.map(function(At){return At+_[it].dx}),mt=vt.yCoords.map(function(At){return At+_[it].dy});vt.xCoords=gt,vt.yCoords=mt});else{var ht=0;w.forEach(function(vt){Object.keys(Q[vt]).forEach(function(it){var gt=Q[vt][it];gt.setCenter(gt.getCenterX()+_[ht].dx,gt.getCenterY()+_[ht].dy)}),ht++})}}}else{var E=C.eles.boundingBox();if(rt.push({x:E.x1+E.w/2,y:E.y1+E.h/2}),C.randomize){var y=s(C);X.push(y)}C.quality=="default"||C.quality=="proof"?(Q.push(l(C,X[0])),t.relocateComponent(rt[0],Q[0],C)):t.relocateComponent(rt[0],X[0],C)}var q=function(it,gt){if(C.quality=="default"||C.quality=="proof"){typeof it=="number"&&(it=gt);var mt=void 0,At=void 0,Ot=it.data("id");return Q.forEach(function(Dt){Ot in Dt&&(mt={x:Dt[Ot].getRect().getCenterX(),y:Dt[Ot].getRect().getCenterY()},At=Dt[Ot])}),C.nodeDimensionsIncludeLabels&&(At.labelWidth&&(At.labelPosHorizontal=="left"?mt.x+=At.labelWidth/2:At.labelPosHorizontal=="right"&&(mt.x-=At.labelWidth/2)),At.labelHeight&&(At.labelPosVertical=="top"?mt.y+=At.labelHeight/2:At.labelPosVertical=="bottom"&&(mt.y-=At.labelHeight/2))),mt==null&&(mt={x:it.position("x"),y:it.position("y")}),{x:mt.x,y:mt.y}}else{var Et=void 0;return X.forEach(function(Dt){var Rt=Dt.nodeIndexes.get(it.id());Rt!=null&&(Et={x:Dt.xCoords[Rt],y:Dt.yCoords[Rt]})}),Et==null&&(Et={x:it.position("x"),y:it.position("y")}),{x:Et.x,y:Et.y}}};if(C.quality=="default"||C.quality=="proof"||C.randomize){var It=t.calcParentsWithoutChildren(G,K),Nt=K.filter(function(vt){return vt.css("display")=="none"});C.eles=K.not(Nt),K.nodes().not(":parent").not(Nt).layoutPositions(S,C,q),It.length>0&&It.forEach(function(vt){vt.position(q(vt))})}else console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'.")}}]),v}();a.exports=d},657:(a,r,e)=>{var f=e(548),i=e(140).layoutBase.Matrix,g=e(140).layoutBase.SVD,t=function(s){var c=s.cy,l=s.eles,T=l.nodes(),d=l.nodes(":parent"),v=new Map,L=new Map,S=new Map,C=[],G=[],K=[],X=[],Q=[],O=[],rt=[],n=[],m=void 0,p=1e8,E=1e-9,y=s.piTol,R=s.samplingType,M=s.nodeSeparation,F=void 0,W=function(){for(var U=0,$=0,J=!1;$=at;){nt=Z[at++];for(var xt=C[nt],lt=0;ltut&&(ut=Q[Lt],Mt=Lt)}return Mt},k=function(U){var $=void 0;if(U){$=Math.floor(Math.random()*m);for(var Z=0;Z=1)break;j=tt}for(var pt=0;pt=1)break;j=tt}for(var lt=0;lt0&&($.isParent()?C[U].push(S.get($.id())):C[U].push($.id()))})});var Nt=function(U){var $=L.get(U),J=void 0;v.get(U).forEach(function(Z){c.getElementById(Z).isParent()?J=S.get(Z):J=Z,C[$].push(J),C[L.get(J)].push(U)})},vt=!0,it=!1,gt=void 0;try{for(var mt=v.keys()[Symbol.iterator](),At;!(vt=(At=mt.next()).done);vt=!0){var Ot=At.value;Nt(Ot)}}catch(b){it=!0,gt=b}finally{try{!vt&&mt.return&&mt.return()}finally{if(it)throw gt}}m=L.size;var Et=void 0;if(m>2){F=m{var f=e(212),i=function(t){t&&t("layout","fcose",f)};typeof cytoscape<"u"&&i(cytoscape),a.exports=i},140:a=>{a.exports=A}},N={};function u(a){var r=N[a];if(r!==void 0)return r.exports;var e=N[a]={exports:{}};return P[a](e,e.exports,u),e.exports}var h=u(579);return h})()})})(Fe);var dr=Fe.exports;const vr=Ze(dr);var De={L:"left",R:"right",T:"top",B:"bottom"},xe={L:dt(I=>`${I},${I/2} 0,${I} 0,0`,"L"),R:dt(I=>`0,${I/2} ${I},0 ${I},${I}`,"R"),T:dt(I=>`0,0 ${I},0 ${I/2},${I}`,"T"),B:dt(I=>`${I/2},0 ${I},${I} 0,${I}`,"B")},se={L:dt((I,D)=>I-D+2,"L"),R:dt((I,D)=>I-2,"R"),T:dt((I,D)=>I-D+2,"T"),B:dt((I,D)=>I-2,"B")},pr=dt(function(I){return Wt(I)?I==="L"?"R":"L":I==="T"?"B":"T"},"getOppositeArchitectureDirection"),Ie=dt(function(I){const D=I;return D==="L"||D==="R"||D==="T"||D==="B"},"isArchitectureDirection"),Wt=dt(function(I){const D=I;return D==="L"||D==="R"},"isArchitectureDirectionX"),qt=dt(function(I){const D=I;return D==="T"||D==="B"},"isArchitectureDirectionY"),Te=dt(function(I,D){const A=Wt(I)&&qt(D),P=qt(I)&&Wt(D);return A||P},"isArchitectureDirectionXY"),yr=dt(function(I){const D=I[0],A=I[1],P=Wt(D)&&qt(A),N=qt(D)&&Wt(A);return P||N},"isArchitecturePairXY"),Er=dt(function(I){return I!=="LL"&&I!=="RR"&&I!=="TT"&&I!=="BB"},"isValidArchitectureDirectionPair"),pe=dt(function(I,D){const A=`${I}${D}`;return Er(A)?A:void 0},"getArchitectureDirectionPair"),mr=dt(function([I,D],A){const P=A[0],N=A[1];return Wt(P)?qt(N)?[I+(P==="L"?-1:1),D+(N==="T"?1:-1)]:[I+(P==="L"?-1:1),D]:Wt(N)?[I+(N==="L"?1:-1),D+(P==="T"?1:-1)]:[I,D+(P==="T"?1:-1)]},"shiftPositionByArchitectureDirectionPair"),Tr=dt(function(I){return I==="LT"||I==="TL"?[1,1]:I==="BL"||I==="LB"?[1,-1]:I==="BR"||I==="RB"?[-1,-1]:[-1,1]},"getArchitectureDirectionXYFactors"),Nr=dt(function(I,D){return Te(I,D)?"bend":Wt(I)?"horizontal":"vertical"},"getArchitectureDirectionAlignment"),Lr=dt(function(I){return I.type==="service"},"isArchitectureService"),Cr=dt(function(I){return I.type==="junction"},"isArchitectureJunction"),be=dt(I=>I.data(),"edgeData"),ie=dt(I=>I.data(),"nodeData"),Ar=ar.architecture,ae,Pe=(ae=class{constructor(){this.nodes={},this.groups={},this.edges=[],this.registeredIds={},this.elements={},this.setAccTitle=Qe,this.getAccTitle=Je,this.setDiagramTitle=Ke,this.getDiagramTitle=je,this.getAccDescription=_e,this.setAccDescription=tr,this.clear()}clear(){this.nodes={},this.groups={},this.edges=[],this.registeredIds={},this.dataStructures=void 0,this.elements={},er()}addService({id:D,icon:A,in:P,title:N,iconText:u}){if(this.registeredIds[D]!==void 0)throw new Error(`The service id [${D}] is already in use by another ${this.registeredIds[D]}`);if(P!==void 0){if(D===P)throw new Error(`The service [${D}] cannot be placed within itself`);if(this.registeredIds[P]===void 0)throw new Error(`The service [${D}]'s parent does not exist. Please make sure the parent is created before this service`);if(this.registeredIds[P]==="node")throw new Error(`The service [${D}]'s parent is not a group`)}this.registeredIds[D]="node",this.nodes[D]={id:D,type:"service",icon:A,iconText:u,title:N,edges:[],in:P}}getServices(){return Object.values(this.nodes).filter(Lr)}addJunction({id:D,in:A}){this.registeredIds[D]="node",this.nodes[D]={id:D,type:"junction",edges:[],in:A}}getJunctions(){return Object.values(this.nodes).filter(Cr)}getNodes(){return Object.values(this.nodes)}getNode(D){return this.nodes[D]??null}addGroup({id:D,icon:A,in:P,title:N}){var u,h,a;if(((u=this.registeredIds)==null?void 0:u[D])!==void 0)throw new Error(`The group id [${D}] is already in use by another ${this.registeredIds[D]}`);if(P!==void 0){if(D===P)throw new Error(`The group [${D}] cannot be placed within itself`);if(((h=this.registeredIds)==null?void 0:h[P])===void 0)throw new Error(`The group [${D}]'s parent does not exist. Please make sure the parent is created before this group`);if(((a=this.registeredIds)==null?void 0:a[P])==="node")throw new Error(`The group [${D}]'s parent is not a group`)}this.registeredIds[D]="group",this.groups[D]={id:D,icon:A,title:N,in:P}}getGroups(){return Object.values(this.groups)}addEdge({lhsId:D,rhsId:A,lhsDir:P,rhsDir:N,lhsInto:u,rhsInto:h,lhsGroup:a,rhsGroup:r,title:e}){if(!Ie(P))throw new Error(`Invalid direction given for left hand side of edge ${D}--${A}. Expected (L,R,T,B) got ${String(P)}`);if(!Ie(N))throw new Error(`Invalid direction given for right hand side of edge ${D}--${A}. Expected (L,R,T,B) got ${String(N)}`);if(this.nodes[D]===void 0&&this.groups[D]===void 0)throw new Error(`The left-hand id [${D}] does not yet exist. Please create the service/group before declaring an edge to it.`);if(this.nodes[A]===void 0&&this.groups[A]===void 0)throw new Error(`The right-hand id [${A}] does not yet exist. Please create the service/group before declaring an edge to it.`);const f=this.nodes[D].in,i=this.nodes[A].in;if(a&&f&&i&&f==i)throw new Error(`The left-hand id [${D}] is modified to traverse the group boundary, but the edge does not pass through two groups.`);if(r&&f&&i&&f==i)throw new Error(`The right-hand id [${A}] is modified to traverse the group boundary, but the edge does not pass through two groups.`);const g={lhsId:D,lhsDir:P,lhsInto:u,lhsGroup:a,rhsId:A,rhsDir:N,rhsInto:h,rhsGroup:r,title:e};this.edges.push(g),this.nodes[D]&&this.nodes[A]&&(this.nodes[D].edges.push(this.edges[this.edges.length-1]),this.nodes[A].edges.push(this.edges[this.edges.length-1]))}getEdges(){return this.edges}getDataStructures(){if(this.dataStructures===void 0){const D={},A=Object.entries(this.nodes).reduce((r,[e,f])=>(r[e]=f.edges.reduce((i,g)=>{var s,c;const t=(s=this.getNode(g.lhsId))==null?void 0:s.in,o=(c=this.getNode(g.rhsId))==null?void 0:c.in;if(t&&o&&t!==o){const l=Nr(g.lhsDir,g.rhsDir);l!=="bend"&&(D[t]??(D[t]={}),D[t][o]=l,D[o]??(D[o]={}),D[o][t]=l)}if(g.lhsId===e){const l=pe(g.lhsDir,g.rhsDir);l&&(i[l]=g.rhsId)}else{const l=pe(g.rhsDir,g.lhsDir);l&&(i[l]=g.lhsId)}return i},{}),r),{}),P=Object.keys(A)[0],N={[P]:1},u=Object.keys(A).reduce((r,e)=>e===P?r:{...r,[e]:1},{}),h=dt(r=>{const e={[r]:[0,0]},f=[r];for(;f.length>0;){const i=f.shift();if(i){N[i]=1,delete u[i];const g=A[i],[t,o]=e[i];Object.entries(g).forEach(([s,c])=>{N[c]||(e[c]=mr([t,o],s),f.push(c))})}}return e},"BFS"),a=[h(P)];for(;Object.keys(u).length>0;)a.push(h(Object.keys(u)[0]));this.dataStructures={adjList:A,spatialMaps:a,groupAlignments:D}}return this.dataStructures}setElementForId(D,A){this.elements[D]=A}getElementById(D){return this.elements[D]}getConfig(){return rr({...Ar,...ir().architecture})}getConfigField(D){return this.getConfig()[D]}},dt(ae,"ArchitectureDB"),ae),Mr=dt((I,D)=>{fr(I,D),I.groups.map(A=>D.addGroup(A)),I.services.map(A=>D.addService({...A,type:"service"})),I.junctions.map(A=>D.addJunction({...A,type:"junction"})),I.edges.map(A=>D.addEdge(A))},"populateDb"),Ge={parser:{yy:void 0},parse:dt(async I=>{var P;const D=await cr("architecture",I);Re.debug(D);const A=(P=Ge.parser)==null?void 0:P.yy;if(!(A instanceof Pe))throw new Error("parser.parser?.yy was not a ArchitectureDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.");Mr(D,A)},"parse")},wr=dt(I=>` + .edge { + stroke-width: ${I.archEdgeWidth}; + stroke: ${I.archEdgeColor}; + fill: none; + } + + .arrow { + fill: ${I.archEdgeArrowColor}; + } + + .node-bkg { + fill: none; + stroke: ${I.archGroupBorderColor}; + stroke-width: ${I.archGroupBorderWidth}; + stroke-dasharray: 8; + } + .node-icon-text { + display: flex; + align-items: center; + } + + .node-icon-text > div { + color: #fff; + margin: 1px; + height: fit-content; + text-align: center; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + } +`,"getStyles"),Or=wr,re=dt(I=>`${I}`,"wrapIcon"),ne={prefix:"mermaid-architecture",height:80,width:80,icons:{database:{body:re('')},server:{body:re('')},disk:{body:re('')},internet:{body:re('')},cloud:{body:re('')},unknown:lr,blank:{body:re("")}}},Dr=dt(async function(I,D,A){const P=A.getConfigField("padding"),N=A.getConfigField("iconSize"),u=N/2,h=N/6,a=h/2;await Promise.all(D.edges().map(async r=>{var K,X;const{source:e,sourceDir:f,sourceArrow:i,sourceGroup:g,target:t,targetDir:o,targetArrow:s,targetGroup:c,label:l}=be(r);let{x:T,y:d}=r[0].sourceEndpoint();const{x:v,y:L}=r[0].midpoint();let{x:S,y:C}=r[0].targetEndpoint();const G=P+4;if(g&&(Wt(f)?T+=f==="L"?-G:G:d+=f==="T"?-G:G+18),c&&(Wt(o)?S+=o==="L"?-G:G:C+=o==="T"?-G:G+18),!g&&((K=A.getNode(e))==null?void 0:K.type)==="junction"&&(Wt(f)?T+=f==="L"?u:-u:d+=f==="T"?u:-u),!c&&((X=A.getNode(t))==null?void 0:X.type)==="junction"&&(Wt(o)?S+=o==="L"?u:-u:C+=o==="T"?u:-u),r[0]._private.rscratch){const Q=I.insert("g");if(Q.insert("path").attr("d",`M ${T},${d} L ${v},${L} L${S},${C} `).attr("class","edge").attr("id",sr(e,t,{prefix:"L"})),i){const O=Wt(f)?se[f](T,h):T-a,rt=qt(f)?se[f](d,h):d-a;Q.insert("polygon").attr("points",xe[f](h)).attr("transform",`translate(${O},${rt})`).attr("class","arrow")}if(s){const O=Wt(o)?se[o](S,h):S-a,rt=qt(o)?se[o](C,h):C-a;Q.insert("polygon").attr("points",xe[o](h)).attr("transform",`translate(${O},${rt})`).attr("class","arrow")}if(l){const O=Te(f,o)?"XY":Wt(f)?"X":"Y";let rt=0;O==="X"?rt=Math.abs(T-S):O==="Y"?rt=Math.abs(d-C)/1.5:rt=Math.abs(T-S)/2;const n=Q.append("g");if(await me(n,l,{useHtmlLabels:!1,width:rt,classes:"architecture-service-label"},Ee()),n.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle"),O==="X")n.attr("transform","translate("+v+", "+L+")");else if(O==="Y")n.attr("transform","translate("+v+", "+L+") rotate(-90)");else if(O==="XY"){const m=pe(f,o);if(m&&yr(m)){const p=n.node().getBoundingClientRect(),[E,y]=Tr(m);n.attr("dominant-baseline","auto").attr("transform",`rotate(${-1*E*y*45})`);const R=n.node().getBoundingClientRect();n.attr("transform",` + translate(${v}, ${L-p.height/2}) + translate(${E*R.width/2}, ${y*R.height/2}) + rotate(${-1*E*y*45}, 0, ${p.height/2}) + `)}}}}}))},"drawEdges"),xr=dt(async function(I,D,A){const N=A.getConfigField("padding")*.75,u=A.getConfigField("fontSize"),a=A.getConfigField("iconSize")/2;await Promise.all(D.nodes().map(async r=>{const e=ie(r);if(e.type==="group"){const{h:f,w:i,x1:g,y1:t}=r.boundingBox(),o=I.append("rect");o.attr("id",`group-${e.id}`).attr("x",g+a).attr("y",t+a).attr("width",i).attr("height",f).attr("class","node-bkg");const s=I.append("g");let c=g,l=t;if(e.icon){const T=s.append("g");T.html(`${await ve(e.icon,{height:N,width:N,fallbackPrefix:ne.prefix})}`),T.attr("transform","translate("+(c+a+1)+", "+(l+a+1)+")"),c+=N,l+=u/2-1-2}if(e.label){const T=s.append("g");await me(T,e.label,{useHtmlLabels:!1,width:i,classes:"architecture-service-label"},Ee()),T.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","start").attr("text-anchor","start"),T.attr("transform","translate("+(c+a+4)+", "+(l+a+2)+")")}A.setElementForId(e.id,o)}}))},"drawGroups"),Ir=dt(async function(I,D,A){const P=Ee();for(const N of A){const u=D.append("g"),h=I.getConfigField("iconSize");if(N.title){const f=u.append("g");await me(f,N.title,{useHtmlLabels:!1,width:h*1.5,classes:"architecture-service-label"},P),f.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle"),f.attr("transform","translate("+h/2+", "+h+")")}const a=u.append("g");if(N.icon)a.html(`${await ve(N.icon,{height:h,width:h,fallbackPrefix:ne.prefix})}`);else if(N.iconText){a.html(`${await ve("blank",{height:h,width:h,fallbackPrefix:ne.prefix})}`);const g=a.append("g").append("foreignObject").attr("width",h).attr("height",h).append("div").attr("class","node-icon-text").attr("style",`height: ${h}px;`).append("div").html(nr(N.iconText,P)),t=parseInt(window.getComputedStyle(g.node(),null).getPropertyValue("font-size").replace(/\D/g,""))??16;g.attr("style",`-webkit-line-clamp: ${Math.floor((h-2)/t)};`)}else a.append("path").attr("class","node-bkg").attr("id","node-"+N.id).attr("d",`M0 ${h} v${-h} q0,-5 5,-5 h${h} q5,0 5,5 v${h} H0 Z`);u.attr("id",`service-${N.id}`).attr("class","architecture-service");const{width:r,height:e}=u.node().getBBox();N.width=r,N.height=e,I.setElementForId(N.id,u)}return 0},"drawServices"),Rr=dt(function(I,D,A){A.forEach(P=>{const N=D.append("g"),u=I.getConfigField("iconSize");N.append("g").append("rect").attr("id","node-"+P.id).attr("fill-opacity","0").attr("width",u).attr("height",u),N.attr("class","architecture-junction");const{width:a,height:r}=N._groups[0][0].getBBox();N.width=a,N.height=r,I.setElementForId(P.id,N)})},"drawJunctions");hr([{name:ne.prefix,icons:ne}]);Se.use(vr);function Ue(I,D,A){I.forEach(P=>{D.add({group:"nodes",data:{type:"service",id:P.id,icon:P.icon,label:P.title,parent:P.in,width:A.getConfigField("iconSize"),height:A.getConfigField("iconSize")},classes:"node-service"})})}dt(Ue,"addServices");function Ye(I,D,A){I.forEach(P=>{D.add({group:"nodes",data:{type:"junction",id:P.id,parent:P.in,width:A.getConfigField("iconSize"),height:A.getConfigField("iconSize")},classes:"node-junction"})})}dt(Ye,"addJunctions");function Xe(I,D){D.nodes().map(A=>{const P=ie(A);if(P.type==="group")return;P.x=A.position().x,P.y=A.position().y,I.getElementById(P.id).attr("transform","translate("+(P.x||0)+","+(P.y||0)+")")})}dt(Xe,"positionNodes");function He(I,D){I.forEach(A=>{D.add({group:"nodes",data:{type:"group",id:A.id,icon:A.icon,label:A.title,parent:A.in},classes:"node-group"})})}dt(He,"addGroups");function We(I,D){I.forEach(A=>{const{lhsId:P,rhsId:N,lhsInto:u,lhsGroup:h,rhsInto:a,lhsDir:r,rhsDir:e,rhsGroup:f,title:i}=A,g=Te(A.lhsDir,A.rhsDir)?"segments":"straight",t={id:`${P}-${N}`,label:i,source:P,sourceDir:r,sourceArrow:u,sourceGroup:h,sourceEndpoint:r==="L"?"0 50%":r==="R"?"100% 50%":r==="T"?"50% 0":"50% 100%",target:N,targetDir:e,targetArrow:a,targetGroup:f,targetEndpoint:e==="L"?"0 50%":e==="R"?"100% 50%":e==="T"?"50% 0":"50% 100%"};D.add({group:"edges",data:t,classes:g})})}dt(We,"addEdges");function Ve(I,D,A){const P=dt((a,r)=>Object.entries(a).reduce((e,[f,i])=>{var o;let g=0;const t=Object.entries(i);if(t.length===1)return e[f]=t[0][1],e;for(let s=0;s{const r={},e={};return Object.entries(a).forEach(([f,[i,g]])=>{var o,s,c;const t=((o=I.getNode(f))==null?void 0:o.in)??"default";r[g]??(r[g]={}),(s=r[g])[t]??(s[t]=[]),r[g][t].push(f),e[i]??(e[i]={}),(c=e[i])[t]??(c[t]=[]),e[i][t].push(f)}),{horiz:Object.values(P(r,"horizontal")).filter(f=>f.length>1),vert:Object.values(P(e,"vertical")).filter(f=>f.length>1)}}),[u,h]=N.reduce(([a,r],{horiz:e,vert:f})=>[[...a,...e],[...r,...f]],[[],[]]);return{horizontal:u,vertical:h}}dt(Ve,"getAlignments");function ze(I,D){const A=[],P=dt(u=>`${u[0]},${u[1]}`,"posToStr"),N=dt(u=>u.split(",").map(h=>parseInt(h)),"strToPos");return I.forEach(u=>{const h=Object.fromEntries(Object.entries(u).map(([f,i])=>[P(i),f])),a=[P([0,0])],r={},e={L:[-1,0],R:[1,0],T:[0,1],B:[0,-1]};for(;a.length>0;){const f=a.shift();if(f){r[f]=1;const i=h[f];if(i){const g=N(f);Object.entries(e).forEach(([t,o])=>{const s=P([g[0]+o[0],g[1]+o[1]]),c=h[s];c&&!r[s]&&(a.push(s),A.push({[De[t]]:c,[De[pr(t)]]:i,gap:1.5*D.getConfigField("iconSize")}))})}}}}),A}dt(ze,"getRelativeConstraints");function Be(I,D,A,P,N,{spatialMaps:u,groupAlignments:h}){return new Promise(a=>{const r=or("body").append("div").attr("id","cy").attr("style","display:none"),e=Se({container:document.getElementById("cy"),style:[{selector:"edge",style:{"curve-style":"straight",label:"data(label)","source-endpoint":"data(sourceEndpoint)","target-endpoint":"data(targetEndpoint)"}},{selector:"edge.segments",style:{"curve-style":"segments","segment-weights":"0","segment-distances":[.5],"edge-distances":"endpoints","source-endpoint":"data(sourceEndpoint)","target-endpoint":"data(targetEndpoint)"}},{selector:"node",style:{"compound-sizing-wrt-labels":"include"}},{selector:"node[label]",style:{"text-valign":"bottom","text-halign":"center","font-size":`${N.getConfigField("fontSize")}px`}},{selector:".node-service",style:{label:"data(label)",width:"data(width)",height:"data(height)"}},{selector:".node-junction",style:{width:"data(width)",height:"data(height)"}},{selector:".node-group",style:{padding:`${N.getConfigField("padding")}px`}}],layout:{name:"grid",boundingBox:{x1:0,x2:100,y1:0,y2:100}}});r.remove(),He(A,e),Ue(I,e,N),Ye(D,e,N),We(P,e);const f=Ve(N,u,h),i=ze(u,N),g=e.layout({name:"fcose",quality:"proof",styleEnabled:!1,animate:!1,nodeDimensionsIncludeLabels:!1,idealEdgeLength(t){const[o,s]=t.connectedNodes(),{parent:c}=ie(o),{parent:l}=ie(s);return c===l?1.5*N.getConfigField("iconSize"):.5*N.getConfigField("iconSize")},edgeElasticity(t){const[o,s]=t.connectedNodes(),{parent:c}=ie(o),{parent:l}=ie(s);return c===l?.45:.001},alignmentConstraint:f,relativePlacementConstraint:i});g.one("layoutstop",()=>{var o;function t(s,c,l,T){let d,v;const{x:L,y:S}=s,{x:C,y:G}=c;v=(T-S+(L-l)*(S-G)/(L-C))/Math.sqrt(1+Math.pow((S-G)/(L-C),2)),d=Math.sqrt(Math.pow(T-S,2)+Math.pow(l-L,2)-Math.pow(v,2));const K=Math.sqrt(Math.pow(C-L,2)+Math.pow(G-S,2));d=d/K;let X=(C-L)*(T-S)-(G-S)*(l-L);switch(!0){case X>=0:X=1;break;case X<0:X=-1;break}let Q=(C-L)*(l-L)+(G-S)*(T-S);switch(!0){case Q>=0:Q=1;break;case Q<0:Q=-1;break}return v=Math.abs(v)*X,d=d*Q,{distances:v,weights:d}}dt(t,"getSegmentWeights"),e.startBatch();for(const s of Object.values(e.edges()))if((o=s.data)!=null&&o.call(s)){const{x:c,y:l}=s.source().position(),{x:T,y:d}=s.target().position();if(c!==T&&l!==d){const v=s.sourceEndpoint(),L=s.targetEndpoint(),{sourceDir:S}=be(s),[C,G]=qt(S)?[v.x,L.y]:[L.x,v.y],{weights:K,distances:X}=t(v,L,C,G);s.style("segment-distances",X),s.style("segment-weights",K)}}e.endBatch(),g.run()}),g.run(),e.ready(t=>{Re.info("Ready",t),a(e)})})}dt(Be,"layoutArchitecture");var Sr=dt(async(I,D,A,P)=>{const N=P.db,u=N.getServices(),h=N.getJunctions(),a=N.getGroups(),r=N.getEdges(),e=N.getDataStructures(),f=ke(D),i=f.append("g");i.attr("class","architecture-edges");const g=f.append("g");g.attr("class","architecture-services");const t=f.append("g");t.attr("class","architecture-groups"),await Ir(N,g,u),Rr(N,g,h);const o=await Be(u,h,a,r,N,e);await Dr(i,o,N),await xr(t,o,N),Xe(N,o),qe(void 0,f,N.getConfigField("padding"),N.getConfigField("useMaxWidth"))},"draw"),Fr={draw:Sr},Wr={parser:Ge,get db(){return new Pe},renderer:Fr,styles:Or};export{Wr as diagram}; diff --git a/assets/chunks/baseUniq.BHxmztwl.js b/assets/chunks/baseUniq.BHxmztwl.js new file mode 100644 index 000000000..07337f1c4 --- /dev/null +++ b/assets/chunks/baseUniq.BHxmztwl.js @@ -0,0 +1 @@ +import{b7 as L,bn as ln,ay as A,b5 as v,bo as gn,bp as dn,ax as z,bq as hn,br as W,bs as pn,bt as An,bu as m,b8 as N,bd as U,bg as T,bv as _n,bb as on,bw as wn,bx as bn,az as V,by as On,bz as I}from"./theme.kqgpP4eL.js";var vn="[object Symbol]";function x(n){return typeof n=="symbol"||L(n)&&ln(n)==vn}function yn(n,r){for(var e=-1,i=n==null?0:n.length,f=Array(i);++e-1}function M(n){return z(n)?gn(n):dn(n)}var Ln=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,xn=/^\w*$/;function $(n,r){if(A(n))return!1;var e=typeof n;return e=="number"||e=="symbol"||e=="boolean"||n==null||x(n)?!0:xn.test(n)||!Ln.test(n)||r!=null&&n in Object(r)}var Mn=500;function $n(n){var r=hn(n,function(i){return e.size===Mn&&e.clear(),i}),e=r.cache;return r}var Cn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Dn=/\\(\\)?/g,Fn=$n(function(n){var r=[];return n.charCodeAt(0)===46&&r.push(""),n.replace(Cn,function(e,i,f,t){r.push(f?t.replace(Dn,"$1"):i||e)}),r});function Gn(n){return n==null?"":k(n)}function j(n,r){return A(n)?n:$(n,r)?[n]:Fn(Gn(n))}function R(n){if(typeof n=="string"||x(n))return n;var r=n+"";return r=="0"&&1/n==-1/0?"-0":r}function nn(n,r){r=j(r,n);for(var e=0,i=r.length;n!=null&&eu))return!1;var h=t.get(n),g=t.get(r);if(h&&g)return h==r&&g==n;var l=-1,d=!0,o=e&Wn?new y:void 0;for(t.set(n,r),t.set(r,n);++l=Br){var h=r?null:Ur(n);if(h)return C(h);s=!1,f=tn,a=new y}else a=r?[]:u;n:for(;++i"u"&&(M.yylloc={});var pt=M.yylloc;c.push(pt);var oe=M.options&&M.options.ranges;typeof Q.yy.parseError=="function"?this.parseError=Q.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function he(H){p.length=p.length-2*H,S.length=S.length-H,c.length=c.length-H}d(he,"popStack");function Tt(){var H;return H=w.pop()||M.lex()||P,typeof H!="number"&&(H instanceof Array&&(w=H,H=w.pop()),H=g.symbols_[H]||H),H}d(Tt,"lex");for(var Y,$,U,ft,tt={},it,q,Ct,nt;;){if($=p[p.length-1],this.defaultActions[$]?U=this.defaultActions[$]:((Y===null||typeof Y>"u")&&(Y=Tt()),U=_[$]&&_[$][Y]),typeof U>"u"||!U.length||!U[0]){var xt="";nt=[];for(it in _[$])this.terminals_[it]&&it>X&&nt.push("'"+this.terminals_[it]+"'");M.showPosition?xt="Parse error on line "+(A+1)+`: +`+M.showPosition()+` +Expecting `+nt.join(", ")+", got '"+(this.terminals_[Y]||Y)+"'":xt="Parse error on line "+(A+1)+": Unexpected "+(Y==P?"end of input":"'"+(this.terminals_[Y]||Y)+"'"),this.parseError(xt,{text:M.match,token:this.terminals_[Y]||Y,line:M.yylineno,loc:pt,expected:nt})}if(U[0]instanceof Array&&U.length>1)throw new Error("Parse Error: multiple actions possible at state: "+$+", token: "+Y);switch(U[0]){case 1:p.push(Y),S.push(M.yytext),c.push(M.yylloc),p.push(U[1]),Y=null,O=M.yyleng,f=M.yytext,A=M.yylineno,pt=M.yylloc;break;case 2:if(q=this.productions_[U[1]][1],tt.$=S[S.length-q],tt._$={first_line:c[c.length-(q||1)].first_line,last_line:c[c.length-1].last_line,first_column:c[c.length-(q||1)].first_column,last_column:c[c.length-1].last_column},oe&&(tt._$.range=[c[c.length-(q||1)].range[0],c[c.length-1].range[1]]),ft=this.performAction.apply(tt,[f,O,A,Q.yy,U[1],S,c].concat(J)),typeof ft<"u")return ft;q&&(p=p.slice(0,-1*q*2),S=S.slice(0,-1*q),c=c.slice(0,-1*q)),p.push(this.productions_[U[1]][0]),S.push(tt.$),c.push(tt._$),Ct=_[p[p.length-2]][p[p.length-1]],p.push(Ct);break;case 3:return!0}}return!0},"parse")},T=function(){var N={EOF:1,parseError:d(function(g,p){if(this.yy.parser)this.yy.parser.parseError(g,p);else throw new Error(g)},"parseError"),setInput:d(function(x,g){return this.yy=g||this.yy||{},this._input=x,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:d(function(){var x=this._input[0];this.yytext+=x,this.yyleng++,this.offset++,this.match+=x,this.matched+=x;var g=x.match(/(?:\r\n?|\n).*/g);return g?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),x},"input"),unput:d(function(x){var g=x.length,p=x.split(/(?:\r\n?|\n)/g);this._input=x+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-g),this.offset-=g;var w=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),p.length-1&&(this.yylineno-=p.length-1);var S=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:p?(p.length===w.length?this.yylloc.first_column:0)+w[w.length-p.length].length-p[0].length:this.yylloc.first_column-g},this.options.ranges&&(this.yylloc.range=[S[0],S[0]+this.yyleng-g]),this.yyleng=this.yytext.length,this},"unput"),more:d(function(){return this._more=!0,this},"more"),reject:d(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:d(function(x){this.unput(this.match.slice(x))},"less"),pastInput:d(function(){var x=this.matched.substr(0,this.matched.length-this.match.length);return(x.length>20?"...":"")+x.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:d(function(){var x=this.match;return x.length<20&&(x+=this._input.substr(0,20-x.length)),(x.substr(0,20)+(x.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:d(function(){var x=this.pastInput(),g=new Array(x.length+1).join("-");return x+this.upcomingInput()+` +`+g+"^"},"showPosition"),test_match:d(function(x,g){var p,w,S;if(this.options.backtrack_lexer&&(S={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(S.yylloc.range=this.yylloc.range.slice(0))),w=x[0].match(/(?:\r\n?|\n).*/g),w&&(this.yylineno+=w.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:w?w[w.length-1].length-w[w.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+x[0].length},this.yytext+=x[0],this.match+=x[0],this.matches=x,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(x[0].length),this.matched+=x[0],p=this.performAction.call(this,this.yy,this,g,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),p)return p;if(this._backtrack){for(var c in S)this[c]=S[c];return!1}return!1},"test_match"),next:d(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var x,g,p,w;this._more||(this.yytext="",this.match="");for(var S=this._currentRules(),c=0;cg[0].length)){if(g=p,w=c,this.options.backtrack_lexer){if(x=this.test_match(p,S[c]),x!==!1)return x;if(this._backtrack){g=!1;continue}else return!1}else if(!this.options.flex)break}return g?(x=this.test_match(g,S[w]),x!==!1?x:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:d(function(){var g=this.next();return g||this.lex()},"lex"),begin:d(function(g){this.conditionStack.push(g)},"begin"),popState:d(function(){var g=this.conditionStack.length-1;return g>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:d(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:d(function(g){return g=this.conditionStack.length-1-Math.abs(g||0),g>=0?this.conditionStack[g]:"INITIAL"},"topState"),pushState:d(function(g){this.begin(g)},"pushState"),stateStackSize:d(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:d(function(g,p,w,S){switch(w){case 0:return g.getLogger().debug("Found block-beta"),10;case 1:return g.getLogger().debug("Found id-block"),29;case 2:return g.getLogger().debug("Found block"),10;case 3:g.getLogger().debug(".",p.yytext);break;case 4:g.getLogger().debug("_",p.yytext);break;case 5:return 5;case 6:return p.yytext=-1,28;case 7:return p.yytext=p.yytext.replace(/columns\s+/,""),g.getLogger().debug("COLUMNS (LEX)",p.yytext),28;case 8:this.pushState("md_string");break;case 9:return"MD_STR";case 10:this.popState();break;case 11:this.pushState("string");break;case 12:g.getLogger().debug("LEX: POPPING STR:",p.yytext),this.popState();break;case 13:return g.getLogger().debug("LEX: STR end:",p.yytext),"STR";case 14:return p.yytext=p.yytext.replace(/space\:/,""),g.getLogger().debug("SPACE NUM (LEX)",p.yytext),21;case 15:return p.yytext="1",g.getLogger().debug("COLUMNS (LEX)",p.yytext),21;case 16:return 42;case 17:return"LINKSTYLE";case 18:return"INTERPOLATE";case 19:return this.pushState("CLASSDEF"),39;case 20:return this.popState(),this.pushState("CLASSDEFID"),"DEFAULT_CLASSDEF_ID";case 21:return this.popState(),this.pushState("CLASSDEFID"),40;case 22:return this.popState(),41;case 23:return this.pushState("CLASS"),43;case 24:return this.popState(),this.pushState("CLASS_STYLE"),44;case 25:return this.popState(),45;case 26:return this.pushState("STYLE_STMNT"),46;case 27:return this.popState(),this.pushState("STYLE_DEFINITION"),47;case 28:return this.popState(),48;case 29:return this.pushState("acc_title"),"acc_title";case 30:return this.popState(),"acc_title_value";case 31:return this.pushState("acc_descr"),"acc_descr";case 32:return this.popState(),"acc_descr_value";case 33:this.pushState("acc_descr_multiline");break;case 34:this.popState();break;case 35:return"acc_descr_multiline_value";case 36:return 30;case 37:return this.popState(),g.getLogger().debug("Lex: (("),"NODE_DEND";case 38:return this.popState(),g.getLogger().debug("Lex: (("),"NODE_DEND";case 39:return this.popState(),g.getLogger().debug("Lex: ))"),"NODE_DEND";case 40:return this.popState(),g.getLogger().debug("Lex: (("),"NODE_DEND";case 41:return this.popState(),g.getLogger().debug("Lex: (("),"NODE_DEND";case 42:return this.popState(),g.getLogger().debug("Lex: (-"),"NODE_DEND";case 43:return this.popState(),g.getLogger().debug("Lex: -)"),"NODE_DEND";case 44:return this.popState(),g.getLogger().debug("Lex: (("),"NODE_DEND";case 45:return this.popState(),g.getLogger().debug("Lex: ]]"),"NODE_DEND";case 46:return this.popState(),g.getLogger().debug("Lex: ("),"NODE_DEND";case 47:return this.popState(),g.getLogger().debug("Lex: ])"),"NODE_DEND";case 48:return this.popState(),g.getLogger().debug("Lex: /]"),"NODE_DEND";case 49:return this.popState(),g.getLogger().debug("Lex: /]"),"NODE_DEND";case 50:return this.popState(),g.getLogger().debug("Lex: )]"),"NODE_DEND";case 51:return this.popState(),g.getLogger().debug("Lex: )"),"NODE_DEND";case 52:return this.popState(),g.getLogger().debug("Lex: ]>"),"NODE_DEND";case 53:return this.popState(),g.getLogger().debug("Lex: ]"),"NODE_DEND";case 54:return g.getLogger().debug("Lexa: -)"),this.pushState("NODE"),35;case 55:return g.getLogger().debug("Lexa: (-"),this.pushState("NODE"),35;case 56:return g.getLogger().debug("Lexa: ))"),this.pushState("NODE"),35;case 57:return g.getLogger().debug("Lexa: )"),this.pushState("NODE"),35;case 58:return g.getLogger().debug("Lex: ((("),this.pushState("NODE"),35;case 59:return g.getLogger().debug("Lexa: )"),this.pushState("NODE"),35;case 60:return g.getLogger().debug("Lexa: )"),this.pushState("NODE"),35;case 61:return g.getLogger().debug("Lexa: )"),this.pushState("NODE"),35;case 62:return g.getLogger().debug("Lexc: >"),this.pushState("NODE"),35;case 63:return g.getLogger().debug("Lexa: (["),this.pushState("NODE"),35;case 64:return g.getLogger().debug("Lexa: )"),this.pushState("NODE"),35;case 65:return this.pushState("NODE"),35;case 66:return this.pushState("NODE"),35;case 67:return this.pushState("NODE"),35;case 68:return this.pushState("NODE"),35;case 69:return this.pushState("NODE"),35;case 70:return this.pushState("NODE"),35;case 71:return this.pushState("NODE"),35;case 72:return g.getLogger().debug("Lexa: ["),this.pushState("NODE"),35;case 73:return this.pushState("BLOCK_ARROW"),g.getLogger().debug("LEX ARR START"),37;case 74:return g.getLogger().debug("Lex: NODE_ID",p.yytext),31;case 75:return g.getLogger().debug("Lex: EOF",p.yytext),8;case 76:this.pushState("md_string");break;case 77:this.pushState("md_string");break;case 78:return"NODE_DESCR";case 79:this.popState();break;case 80:g.getLogger().debug("Lex: Starting string"),this.pushState("string");break;case 81:g.getLogger().debug("LEX ARR: Starting string"),this.pushState("string");break;case 82:return g.getLogger().debug("LEX: NODE_DESCR:",p.yytext),"NODE_DESCR";case 83:g.getLogger().debug("LEX POPPING"),this.popState();break;case 84:g.getLogger().debug("Lex: =>BAE"),this.pushState("ARROW_DIR");break;case 85:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (right): dir:",p.yytext),"DIR";case 86:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (left):",p.yytext),"DIR";case 87:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (x):",p.yytext),"DIR";case 88:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (y):",p.yytext),"DIR";case 89:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (up):",p.yytext),"DIR";case 90:return p.yytext=p.yytext.replace(/^,\s*/,""),g.getLogger().debug("Lex (down):",p.yytext),"DIR";case 91:return p.yytext="]>",g.getLogger().debug("Lex (ARROW_DIR end):",p.yytext),this.popState(),this.popState(),"BLOCK_ARROW_END";case 92:return g.getLogger().debug("Lex: LINK","#"+p.yytext+"#"),15;case 93:return g.getLogger().debug("Lex: LINK",p.yytext),15;case 94:return g.getLogger().debug("Lex: LINK",p.yytext),15;case 95:return g.getLogger().debug("Lex: LINK",p.yytext),15;case 96:return g.getLogger().debug("Lex: START_LINK",p.yytext),this.pushState("LLABEL"),16;case 97:return g.getLogger().debug("Lex: START_LINK",p.yytext),this.pushState("LLABEL"),16;case 98:return g.getLogger().debug("Lex: START_LINK",p.yytext),this.pushState("LLABEL"),16;case 99:this.pushState("md_string");break;case 100:return g.getLogger().debug("Lex: Starting string"),this.pushState("string"),"LINK_LABEL";case 101:return this.popState(),g.getLogger().debug("Lex: LINK","#"+p.yytext+"#"),15;case 102:return this.popState(),g.getLogger().debug("Lex: LINK",p.yytext),15;case 103:return this.popState(),g.getLogger().debug("Lex: LINK",p.yytext),15;case 104:return g.getLogger().debug("Lex: COLON",p.yytext),p.yytext=p.yytext.slice(1),27}},"anonymous"),rules:[/^(?:block-beta\b)/,/^(?:block:)/,/^(?:block\b)/,/^(?:[\s]+)/,/^(?:[\n]+)/,/^(?:((\u000D\u000A)|(\u000A)))/,/^(?:columns\s+auto\b)/,/^(?:columns\s+[\d]+)/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:space[:]\d+)/,/^(?:space\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\s+)/,/^(?:DEFAULT\s+)/,/^(?:\w+\s+)/,/^(?:[^\n]*)/,/^(?:class\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:style\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:end\b\s*)/,/^(?:\(\(\()/,/^(?:\)\)\))/,/^(?:[\)]\))/,/^(?:\}\})/,/^(?:\})/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\()/,/^(?:\]\])/,/^(?:\()/,/^(?:\]\))/,/^(?:\\\])/,/^(?:\/\])/,/^(?:\)\])/,/^(?:[\)])/,/^(?:\]>)/,/^(?:[\]])/,/^(?:-\))/,/^(?:\(-)/,/^(?:\)\))/,/^(?:\))/,/^(?:\(\(\()/,/^(?:\(\()/,/^(?:\{\{)/,/^(?:\{)/,/^(?:>)/,/^(?:\(\[)/,/^(?:\()/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\[\\)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:\[)/,/^(?:<\[)/,/^(?:[^\(\[\n\-\)\{\}\s\<\>:]+)/,/^(?:$)/,/^(?:["][`])/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:\]>\s*\()/,/^(?:,?\s*right\s*)/,/^(?:,?\s*left\s*)/,/^(?:,?\s*x\s*)/,/^(?:,?\s*y\s*)/,/^(?:,?\s*up\s*)/,/^(?:,?\s*down\s*)/,/^(?:\)\s*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*~~[\~]+\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:["][`])/,/^(?:["])/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?::\d+)/],conditions:{STYLE_DEFINITION:{rules:[28],inclusive:!1},STYLE_STMNT:{rules:[27],inclusive:!1},CLASSDEFID:{rules:[22],inclusive:!1},CLASSDEF:{rules:[20,21],inclusive:!1},CLASS_STYLE:{rules:[25],inclusive:!1},CLASS:{rules:[24],inclusive:!1},LLABEL:{rules:[99,100,101,102,103],inclusive:!1},ARROW_DIR:{rules:[85,86,87,88,89,90,91],inclusive:!1},BLOCK_ARROW:{rules:[76,81,84],inclusive:!1},NODE:{rules:[37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,77,80],inclusive:!1},md_string:{rules:[9,10,78,79],inclusive:!1},space:{rules:[],inclusive:!1},string:{rules:[12,13,82,83],inclusive:!1},acc_descr_multiline:{rules:[34,35],inclusive:!1},acc_descr:{rules:[32],inclusive:!1},acc_title:{rules:[30],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,11,14,15,16,17,18,19,23,26,29,31,33,36,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,92,93,94,95,96,97,98,104],inclusive:!0}}};return N}();v.lexer=T;function k(){this.yy={}}return d(k,"Parser"),k.prototype=v,v.Parser=k,new k}();wt.parser=wt;var ke=wt,V=new Map,Et=[],mt=new Map,It="color",Bt="fill",De="bgFill",Kt=",",Ne=z(),ot=new Map,Te=d(e=>Le.sanitizeText(e,Ne),"sanitizeText"),Ce=d(function(e,t=""){let a=ot.get(e);a||(a={id:e,styles:[],textStyles:[]},ot.set(e,a)),t!=null&&t.split(Kt).forEach(i=>{const l=i.replace(/([^;]*);/,"$1").trim();if(RegExp(It).exec(i)){const r=l.replace(Bt,De).replace(It,Bt);a.textStyles.push(r)}a.styles.push(l)})},"addStyleClass"),Ie=d(function(e,t=""){const a=V.get(e);t!=null&&(a.styles=t.split(Kt))},"addStyle2Node"),Be=d(function(e,t){e.split(",").forEach(function(a){let i=V.get(a);if(i===void 0){const l=a.trim();i={id:l,type:"na",children:[]},V.set(l,i)}i.classes||(i.classes=[]),i.classes.push(t)})},"setCssClass"),Xt=d((e,t)=>{const a=e.flat(),i=[],l=a.find(r=>(r==null?void 0:r.type)==="column-setting"),s=(l==null?void 0:l.columns)??-1;for(const r of a){if(typeof s=="number"&&s>0&&r.type!=="column-setting"&&typeof r.widthInColumns=="number"&&r.widthInColumns>s&&m.warn(`Block ${r.id} width ${r.widthInColumns} exceeds configured column width ${s}`),r.label&&(r.label=Te(r.label)),r.type==="classDef"){Ce(r.id,r.css);continue}if(r.type==="applyClass"){Be(r.id,(r==null?void 0:r.styleClass)??"");continue}if(r.type==="applyStyles"){r!=null&&r.stylesStr&&Ie(r.id,r==null?void 0:r.stylesStr);continue}if(r.type==="column-setting")t.columns=r.columns??-1;else if(r.type==="edge"){const n=(mt.get(r.id)??0)+1;mt.set(r.id,n),r.id=n+"-"+r.id,Et.push(r)}else{r.label||(r.type==="composite"?r.label="":r.label=r.id);const n=V.get(r.id);if(n===void 0?V.set(r.id,r):(r.type!=="na"&&(n.type=r.type),r.label!==r.id&&(n.label=r.label)),r.children&&Xt(r.children,r),r.type==="space"){const o=r.width??1;for(let u=0;u{m.debug("Clear called"),ue(),at={id:"root",type:"composite",children:[],columns:-1},V=new Map([["root",at]]),_t=[],ot=new Map,Et=[],mt=new Map},"clear");function Ut(e){switch(m.debug("typeStr2Type",e),e){case"[]":return"square";case"()":return m.debug("we have a round"),"round";case"(())":return"circle";case">]":return"rect_left_inv_arrow";case"{}":return"diamond";case"{{}}":return"hexagon";case"([])":return"stadium";case"[[]]":return"subroutine";case"[()]":return"cylinder";case"((()))":return"doublecircle";case"[//]":return"lean_right";case"[\\\\]":return"lean_left";case"[/\\]":return"trapezoid";case"[\\/]":return"inv_trapezoid";case"<[]>":return"block_arrow";default:return"na"}}d(Ut,"typeStr2Type");function jt(e){switch(m.debug("typeStr2Type",e),e){case"==":return"thick";default:return"normal"}}d(jt,"edgeTypeStr2Type");function Vt(e){switch(e.replace(/^[\s-]+|[\s-]+$/g,"")){case"x":return"arrow_cross";case"o":return"arrow_circle";case">":return"arrow_point";default:return""}}d(Vt,"edgeStrToEdgeData");var Ot=0,Re=d(()=>(Ot++,"id-"+Math.random().toString(36).substr(2,12)+"-"+Ot),"generateId"),ze=d(e=>{at.children=e,Xt(e,at),_t=at.children},"setHierarchy"),Ae=d(e=>{const t=V.get(e);return t?t.columns?t.columns:t.children?t.children.length:-1:-1},"getColumns"),Me=d(()=>[...V.values()],"getBlocksFlat"),Fe=d(()=>_t||[],"getBlocks"),We=d(()=>Et,"getEdges"),Pe=d(e=>V.get(e),"getBlock"),Ye=d(e=>{V.set(e.id,e)},"setBlock"),He=d(()=>m,"getLogger"),Ke=d(function(){return ot},"getClasses"),Xe={getConfig:d(()=>st().block,"getConfig"),typeStr2Type:Ut,edgeTypeStr2Type:jt,edgeStrToEdgeData:Vt,getLogger:He,getBlocksFlat:Me,getBlocks:Fe,getEdges:We,setHierarchy:ze,getBlock:Pe,setBlock:Ye,getColumns:Ae,getClasses:Ke,clear:Oe,generateId:Re},Ue=Xe,lt=d((e,t)=>{const a=pe,i=a(e,"r"),l=a(e,"g"),s=a(e,"b");return fe(i,l,s,t)},"fade"),je=d(e=>`.label { + font-family: ${e.fontFamily}; + color: ${e.nodeTextColor||e.textColor}; + } + .cluster-label text { + fill: ${e.titleColor}; + } + .cluster-label span,p { + color: ${e.titleColor}; + } + + + + .label text,span,p { + fill: ${e.nodeTextColor||e.textColor}; + color: ${e.nodeTextColor||e.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${e.mainBkg}; + stroke: ${e.nodeBorder}; + stroke-width: 1px; + } + .flowchart-label text { + text-anchor: middle; + } + // .flowchart-label .text-outer-tspan { + // text-anchor: middle; + // } + // .flowchart-label .text-inner-tspan { + // text-anchor: start; + // } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${e.arrowheadColor}; + } + + .edgePath .path { + stroke: ${e.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${e.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${e.edgeLabelBackground}; + rect { + opacity: 0.5; + background-color: ${e.edgeLabelBackground}; + fill: ${e.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${lt(e.edgeLabelBackground,.5)}; + // background-color: + } + + .node .cluster { + // fill: ${lt(e.mainBkg,.5)}; + fill: ${lt(e.clusterBkg,.5)}; + stroke: ${lt(e.clusterBorder,.2)}; + box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; + stroke-width: 1px; + } + + .cluster text { + fill: ${e.titleColor}; + } + + .cluster span,p { + color: ${e.titleColor}; + } + /* .cluster div { + color: ${e.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${e.fontFamily}; + font-size: 12px; + background: ${e.tertiaryColor}; + border: 1px solid ${e.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${e.textColor}; + } + ${de()} +`,"getStyles"),Ve=je,Ge=d((e,t,a,i)=>{t.forEach(l=>{sr[l](e,a,i)})},"insertMarkers"),Ze=d((e,t,a)=>{m.trace("Making markers for ",a),e.append("defs").append("marker").attr("id",a+"_"+t+"-extensionStart").attr("class","marker extension "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-extensionEnd").attr("class","marker extension "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},"extension"),qe=d((e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-compositionStart").attr("class","marker composition "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-compositionEnd").attr("class","marker composition "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"composition"),Je=d((e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-aggregationStart").attr("class","marker aggregation "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-aggregationEnd").attr("class","marker aggregation "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"aggregation"),Qe=d((e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-dependencyStart").attr("class","marker dependency "+t).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",a+"_"+t+"-dependencyEnd").attr("class","marker dependency "+t).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"dependency"),$e=d((e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-lollipopStart").attr("class","marker lollipop "+t).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),e.append("defs").append("marker").attr("id",a+"_"+t+"-lollipopEnd").attr("class","marker lollipop "+t).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},"lollipop"),tr=d((e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-pointEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",6).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-pointStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"point"),er=d((e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-circleEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-circleStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"circle"),rr=d((e,t,a)=>{e.append("marker").attr("id",a+"_"+t+"-crossEnd").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),e.append("marker").attr("id",a+"_"+t+"-crossStart").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},"cross"),ar=d((e,t,a)=>{e.append("defs").append("marker").attr("id",a+"_"+t+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","strokeWidth").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"barb"),sr={extension:Ze,composition:qe,aggregation:Je,dependency:Qe,lollipop:$e,point:tr,circle:er,cross:rr,barb:ar},ir=Ge,Pt,Yt,B=((Yt=(Pt=z())==null?void 0:Pt.block)==null?void 0:Yt.padding)??8;function Gt(e,t){if(e===0||!Number.isInteger(e))throw new Error("Columns must be an integer !== 0.");if(t<0||!Number.isInteger(t))throw new Error("Position must be a non-negative integer."+t);if(e<0)return{px:t,py:0};if(e===1)return{px:0,py:t};const a=t%e,i=Math.floor(t/e);return{px:a,py:i}}d(Gt,"calculateBlockPosition");var nr=d(e=>{let t=0,a=0;for(const i of e.children){const{width:l,height:s,x:r,y:n}=i.size??{width:0,height:0,x:0,y:0};m.debug("getMaxChildSize abc95 child:",i.id,"width:",l,"height:",s,"x:",r,"y:",n,i.type),i.type!=="space"&&(l>t&&(t=l/(e.widthInColumns??1)),s>a&&(a=s))}return{width:t,height:a}},"getMaxChildSize");function ht(e,t,a=0,i=0){var r,n,o,u,h,y,b,L,E,D,v;m.debug("setBlockSizes abc95 (start)",e.id,(r=e==null?void 0:e.size)==null?void 0:r.x,"block width =",e==null?void 0:e.size,"siblingWidth",a),(n=e==null?void 0:e.size)!=null&&n.width||(e.size={width:a,height:i,x:0,y:0});let l=0,s=0;if(((o=e.children)==null?void 0:o.length)>0){for(const S of e.children)ht(S,t);const T=nr(e);l=T.width,s=T.height,m.debug("setBlockSizes abc95 maxWidth of",e.id,":s children is ",l,s);for(const S of e.children)S.size&&(m.debug(`abc95 Setting size of children of ${e.id} id=${S.id} ${l} ${s} ${JSON.stringify(S.size)}`),S.size.width=l*(S.widthInColumns??1)+B*((S.widthInColumns??1)-1),S.size.height=s,S.size.x=0,S.size.y=0,m.debug(`abc95 updating size of ${e.id} children child:${S.id} maxWidth:${l} maxHeight:${s}`));for(const S of e.children)ht(S,t,l,s);const k=e.columns??-1;let N=0;for(const S of e.children)N+=S.widthInColumns??1;let x=e.children.length;k>0&&k0?Math.min(e.children.length,k):e.children.length;if(S>0){const c=(p-S*B-B)/S;m.debug("abc95 (growing to fit) width",e.id,p,(b=e.size)==null?void 0:b.width,c);for(const _ of e.children)_.size&&(_.size.width=c)}}e.size={width:p,height:w,x:0,y:0}}m.debug("setBlockSizes abc94 (done)",e.id,(L=e==null?void 0:e.size)==null?void 0:L.x,(E=e==null?void 0:e.size)==null?void 0:E.width,(D=e==null?void 0:e.size)==null?void 0:D.y,(v=e==null?void 0:e.size)==null?void 0:v.height)}d(ht,"setBlockSizes");function kt(e,t){var i,l,s,r,n,o,u,h,y,b,L,E,D,v,T,k,N;m.debug(`abc85 layout blocks (=>layoutBlocks) ${e.id} x: ${(i=e==null?void 0:e.size)==null?void 0:i.x} y: ${(l=e==null?void 0:e.size)==null?void 0:l.y} width: ${(s=e==null?void 0:e.size)==null?void 0:s.width}`);const a=e.columns??-1;if(m.debug("layoutBlocks columns abc95",e.id,"=>",a,e),e.children&&e.children.length>0){const x=((n=(r=e==null?void 0:e.children[0])==null?void 0:r.size)==null?void 0:n.width)??0,g=e.children.length*x+(e.children.length-1)*B;m.debug("widthOfChildren 88",g,"posX");let p=0;m.debug("abc91 block?.size?.x",e.id,(o=e==null?void 0:e.size)==null?void 0:o.x);let w=(u=e==null?void 0:e.size)!=null&&u.x?((h=e==null?void 0:e.size)==null?void 0:h.x)+(-((y=e==null?void 0:e.size)==null?void 0:y.width)/2||0):-B,S=0;for(const c of e.children){const _=e;if(!c.size)continue;const{width:f,height:A}=c.size,{px:O,py:X}=Gt(a,p);if(X!=S&&(S=X,w=(b=e==null?void 0:e.size)!=null&&b.x?((L=e==null?void 0:e.size)==null?void 0:L.x)+(-((E=e==null?void 0:e.size)==null?void 0:E.width)/2||0):-B,m.debug("New row in layout for block",e.id," and child ",c.id,S)),m.debug(`abc89 layout blocks (child) id: ${c.id} Pos: ${p} (px, py) ${O},${X} (${(D=_==null?void 0:_.size)==null?void 0:D.x},${(v=_==null?void 0:_.size)==null?void 0:v.y}) parent: ${_.id} width: ${f}${B}`),_.size){const J=f/2;c.size.x=w+B+J,m.debug(`abc91 layout blocks (calc) px, pyid:${c.id} startingPos=X${w} new startingPosX${c.size.x} ${J} padding=${B} width=${f} halfWidth=${J} => x:${c.size.x} y:${c.size.y} ${c.widthInColumns} (width * (child?.w || 1)) / 2 ${f*((c==null?void 0:c.widthInColumns)??1)/2}`),w=c.size.x+J,c.size.y=_.size.y-_.size.height/2+X*(A+B)+A/2+B,m.debug(`abc88 layout blocks (calc) px, pyid:${c.id}startingPosX${w}${B}${J}=>x:${c.size.x}y:${c.size.y}${c.widthInColumns}(width * (child?.w || 1)) / 2${f*((c==null?void 0:c.widthInColumns)??1)/2}`)}c.children&&kt(c);let P=(c==null?void 0:c.widthInColumns)??1;a>0&&(P=Math.min(P,a-p%a)),p+=P,m.debug("abc88 columnsPos",c,p)}}m.debug(`layout blocks (<==layoutBlocks) ${e.id} x: ${(T=e==null?void 0:e.size)==null?void 0:T.x} y: ${(k=e==null?void 0:e.size)==null?void 0:k.y} width: ${(N=e==null?void 0:e.size)==null?void 0:N.width}`)}d(kt,"layoutBlocks");function Dt(e,{minX:t,minY:a,maxX:i,maxY:l}={minX:0,minY:0,maxX:0,maxY:0}){if(e.size&&e.id!=="root"){const{x:s,y:r,width:n,height:o}=e.size;s-n/2i&&(i=s+n/2),r+o/2>l&&(l=r+o/2)}if(e.children)for(const s of e.children)({minX:t,minY:a,maxX:i,maxY:l}=Dt(s,{minX:t,minY:a,maxX:i,maxY:l}));return{minX:t,minY:a,maxX:i,maxY:l}}d(Dt,"findBounds");function Zt(e){const t=e.getBlock("root");if(!t)return;ht(t,e,0,0),kt(t),m.debug("getBlocks",JSON.stringify(t,null,2));const{minX:a,minY:i,maxX:l,maxY:s}=Dt(t),r=s-i,n=l-a;return{x:a,y:i,width:n,height:r}}d(Zt,"layout");function Lt(e,t){t&&e.attr("style",t)}d(Lt,"applyStyle");function qt(e,t){const a=R(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),i=a.append("xhtml:div"),l=e.label,s=e.isNode?"nodeLabel":"edgeLabel",r=i.append("span");return r.html(bt(l,t)),Lt(r,e.labelStyle),r.attr("class",s),Lt(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap"),i.attr("xmlns","http://www.w3.org/1999/xhtml"),a.node()}d(qt,"addHtmlLabel");var lr=d(async(e,t,a,i)=>{let l=e||"";typeof l=="object"&&(l=l[0]);const s=z();if(Z(s.flowchart.htmlLabels)){l=l.replace(/\\n|\n/g,"
"),m.debug("vertexText"+l);const r=await Se(yt(l)),n={isNode:i,label:r,labelStyle:t.replace("fill:","color:")};return qt(n,s)}else{const r=document.createElementNS("http://www.w3.org/2000/svg","text");r.setAttribute("style",t.replace("color:","fill:"));let n=[];typeof l=="string"?n=l.split(/\\n|\n|/gi):Array.isArray(l)?n=l:n=[];for(const o of n){const u=document.createElementNS("http://www.w3.org/2000/svg","tspan");u.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),u.setAttribute("dy","1em"),u.setAttribute("x","0"),a?u.setAttribute("class","title-row"):u.setAttribute("class","row"),u.textContent=o.trim(),r.appendChild(u)}return r}},"createLabel"),j=lr,cr=d((e,t,a,i,l)=>{t.arrowTypeStart&&Rt(e,"start",t.arrowTypeStart,a,i,l),t.arrowTypeEnd&&Rt(e,"end",t.arrowTypeEnd,a,i,l)},"addEdgeMarkers"),or={arrow_cross:"cross",arrow_point:"point",arrow_barb:"barb",arrow_circle:"circle",aggregation:"aggregation",extension:"extension",composition:"composition",dependency:"dependency",lollipop:"lollipop"},Rt=d((e,t,a,i,l,s)=>{const r=or[a];if(!r){m.warn(`Unknown arrow type: ${a}`);return}const n=t==="start"?"Start":"End";e.attr(`marker-${t}`,`url(${i}#${l}_${s}-${r}${n})`)},"addEdgeMarker"),St={},W={},hr=d(async(e,t)=>{const a=z(),i=Z(a.flowchart.htmlLabels),l=t.labelType==="markdown"?Ht(e,t.label,{style:t.labelStyle,useHtmlLabels:i,addSvgBackground:!0},a):await j(t.label,t.labelStyle),s=e.insert("g").attr("class","edgeLabel"),r=s.insert("g").attr("class","label");r.node().appendChild(l);let n=l.getBBox();if(i){const u=l.children[0],h=R(l);n=u.getBoundingClientRect(),h.attr("width",n.width),h.attr("height",n.height)}r.attr("transform","translate("+-n.width/2+", "+-n.height/2+")"),St[t.id]=s,t.width=n.width,t.height=n.height;let o;if(t.startLabelLeft){const u=await j(t.startLabelLeft,t.labelStyle),h=e.insert("g").attr("class","edgeTerminals"),y=h.insert("g").attr("class","inner");o=y.node().appendChild(u);const b=u.getBBox();y.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"),W[t.id]||(W[t.id]={}),W[t.id].startLeft=h,rt(o,t.startLabelLeft)}if(t.startLabelRight){const u=await j(t.startLabelRight,t.labelStyle),h=e.insert("g").attr("class","edgeTerminals"),y=h.insert("g").attr("class","inner");o=h.node().appendChild(u),y.node().appendChild(u);const b=u.getBBox();y.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"),W[t.id]||(W[t.id]={}),W[t.id].startRight=h,rt(o,t.startLabelRight)}if(t.endLabelLeft){const u=await j(t.endLabelLeft,t.labelStyle),h=e.insert("g").attr("class","edgeTerminals"),y=h.insert("g").attr("class","inner");o=y.node().appendChild(u);const b=u.getBBox();y.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"),h.node().appendChild(u),W[t.id]||(W[t.id]={}),W[t.id].endLeft=h,rt(o,t.endLabelLeft)}if(t.endLabelRight){const u=await j(t.endLabelRight,t.labelStyle),h=e.insert("g").attr("class","edgeTerminals"),y=h.insert("g").attr("class","inner");o=y.node().appendChild(u);const b=u.getBBox();y.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"),h.node().appendChild(u),W[t.id]||(W[t.id]={}),W[t.id].endRight=h,rt(o,t.endLabelRight)}return l},"insertEdgeLabel");function rt(e,t){z().flowchart.htmlLabels&&e&&(e.style.width=t.length*9+"px",e.style.height="12px")}d(rt,"setTerminalWidth");var dr=d((e,t)=>{m.debug("Moving label abc88 ",e.id,e.label,St[e.id],t);let a=t.updatedPath?t.updatedPath:t.originalPath;const i=z(),{subGraphTitleTotalMargin:l}=me(i);if(e.label){const s=St[e.id];let r=e.x,n=e.y;if(a){const o=et.calcLabelPosition(a);m.debug("Moving label "+e.label+" from (",r,",",n,") to (",o.x,",",o.y,") abc88"),t.updatedPath&&(r=o.x,n=o.y)}s.attr("transform",`translate(${r}, ${n+l/2})`)}if(e.startLabelLeft){const s=W[e.id].startLeft;let r=e.x,n=e.y;if(a){const o=et.calcTerminalLabelPosition(e.arrowTypeStart?10:0,"start_left",a);r=o.x,n=o.y}s.attr("transform",`translate(${r}, ${n})`)}if(e.startLabelRight){const s=W[e.id].startRight;let r=e.x,n=e.y;if(a){const o=et.calcTerminalLabelPosition(e.arrowTypeStart?10:0,"start_right",a);r=o.x,n=o.y}s.attr("transform",`translate(${r}, ${n})`)}if(e.endLabelLeft){const s=W[e.id].endLeft;let r=e.x,n=e.y;if(a){const o=et.calcTerminalLabelPosition(e.arrowTypeEnd?10:0,"end_left",a);r=o.x,n=o.y}s.attr("transform",`translate(${r}, ${n})`)}if(e.endLabelRight){const s=W[e.id].endRight;let r=e.x,n=e.y;if(a){const o=et.calcTerminalLabelPosition(e.arrowTypeEnd?10:0,"end_right",a);r=o.x,n=o.y}s.attr("transform",`translate(${r}, ${n})`)}},"positionEdgeLabel"),gr=d((e,t)=>{const a=e.x,i=e.y,l=Math.abs(t.x-a),s=Math.abs(t.y-i),r=e.width/2,n=e.height/2;return l>=r||s>=n},"outsideNode"),ur=d((e,t,a)=>{m.debug(`intersection calc abc89: + outsidePoint: ${JSON.stringify(t)} + insidePoint : ${JSON.stringify(a)} + node : x:${e.x} y:${e.y} w:${e.width} h:${e.height}`);const i=e.x,l=e.y,s=Math.abs(i-a.x),r=e.width/2;let n=a.xMath.abs(i-t.x)*o){let y=a.y{m.debug("abc88 cutPathAtIntersect",e,t);let a=[],i=e[0],l=!1;return e.forEach(s=>{if(!gr(t,s)&&!l){const r=ur(t,i,s);let n=!1;a.forEach(o=>{n=n||o.x===r.x&&o.y===r.y}),a.some(o=>o.x===r.x&&o.y===r.y)||a.push(r),l=!0}else i=s,l||a.push(s)}),a},"cutPathAtIntersect"),pr=d(function(e,t,a,i,l,s,r){let n=a.points;m.debug("abc88 InsertEdge: edge=",a,"e=",t);let o=!1;const u=s.node(t.v);var h=s.node(t.w);h!=null&&h.intersect&&(u!=null&&u.intersect)&&(n=n.slice(1,a.points.length-1),n.unshift(u.intersect(n[0])),n.push(h.intersect(n[n.length-1]))),a.toCluster&&(m.debug("to cluster abc88",i[a.toCluster]),n=zt(a.points,i[a.toCluster].node),o=!0),a.fromCluster&&(m.debug("from cluster abc88",i[a.fromCluster]),n=zt(n.reverse(),i[a.fromCluster].node).reverse(),o=!0);const y=n.filter(x=>!Number.isNaN(x.y));let b=be;a.curve&&(l==="graph"||l==="flowchart")&&(b=a.curve);const{x:L,y:E}=xe(a),D=ye().x(L).y(E).curve(b);let v;switch(a.thickness){case"normal":v="edge-thickness-normal";break;case"thick":v="edge-thickness-thick";break;case"invisible":v="edge-thickness-thick";break;default:v=""}switch(a.pattern){case"solid":v+=" edge-pattern-solid";break;case"dotted":v+=" edge-pattern-dotted";break;case"dashed":v+=" edge-pattern-dashed";break}const T=e.append("path").attr("d",D(y)).attr("id",a.id).attr("class"," "+v+(a.classes?" "+a.classes:"")).attr("style",a.style);let k="";(z().flowchart.arrowMarkerAbsolute||z().state.arrowMarkerAbsolute)&&(k=we(!0)),cr(T,a,k,r,l);let N={};return o&&(N.updatedPath=n),N.originalPath=a.points,N},"insertEdge"),fr=d(e=>{const t=new Set;for(const a of e)switch(a){case"x":t.add("right"),t.add("left");break;case"y":t.add("up"),t.add("down");break;default:t.add(a);break}return t},"expandAndDeduplicateDirections"),xr=d((e,t,a)=>{const i=fr(e),l=2,s=t.height+2*a.padding,r=s/l,n=t.width+2*r+a.padding,o=a.padding/2;return i.has("right")&&i.has("left")&&i.has("up")&&i.has("down")?[{x:0,y:0},{x:r,y:0},{x:n/2,y:2*o},{x:n-r,y:0},{x:n,y:0},{x:n,y:-s/3},{x:n+2*o,y:-s/2},{x:n,y:-2*s/3},{x:n,y:-s},{x:n-r,y:-s},{x:n/2,y:-s-2*o},{x:r,y:-s},{x:0,y:-s},{x:0,y:-2*s/3},{x:-2*o,y:-s/2},{x:0,y:-s/3}]:i.has("right")&&i.has("left")&&i.has("up")?[{x:r,y:0},{x:n-r,y:0},{x:n,y:-s/2},{x:n-r,y:-s},{x:r,y:-s},{x:0,y:-s/2}]:i.has("right")&&i.has("left")&&i.has("down")?[{x:0,y:0},{x:r,y:-s},{x:n-r,y:-s},{x:n,y:0}]:i.has("right")&&i.has("up")&&i.has("down")?[{x:0,y:0},{x:n,y:-r},{x:n,y:-s+r},{x:0,y:-s}]:i.has("left")&&i.has("up")&&i.has("down")?[{x:n,y:0},{x:0,y:-r},{x:0,y:-s+r},{x:n,y:-s}]:i.has("right")&&i.has("left")?[{x:r,y:0},{x:r,y:-o},{x:n-r,y:-o},{x:n-r,y:0},{x:n,y:-s/2},{x:n-r,y:-s},{x:n-r,y:-s+o},{x:r,y:-s+o},{x:r,y:-s},{x:0,y:-s/2}]:i.has("up")&&i.has("down")?[{x:n/2,y:0},{x:0,y:-o},{x:r,y:-o},{x:r,y:-s+o},{x:0,y:-s+o},{x:n/2,y:-s},{x:n,y:-s+o},{x:n-r,y:-s+o},{x:n-r,y:-o},{x:n,y:-o}]:i.has("right")&&i.has("up")?[{x:0,y:0},{x:n,y:-r},{x:0,y:-s}]:i.has("right")&&i.has("down")?[{x:0,y:0},{x:n,y:0},{x:0,y:-s}]:i.has("left")&&i.has("up")?[{x:n,y:0},{x:0,y:-r},{x:n,y:-s}]:i.has("left")&&i.has("down")?[{x:n,y:0},{x:0,y:0},{x:n,y:-s}]:i.has("right")?[{x:r,y:-o},{x:r,y:-o},{x:n-r,y:-o},{x:n-r,y:0},{x:n,y:-s/2},{x:n-r,y:-s},{x:n-r,y:-s+o},{x:r,y:-s+o},{x:r,y:-s+o}]:i.has("left")?[{x:r,y:0},{x:r,y:-o},{x:n-r,y:-o},{x:n-r,y:-s+o},{x:r,y:-s+o},{x:r,y:-s},{x:0,y:-s/2}]:i.has("up")?[{x:r,y:-o},{x:r,y:-s+o},{x:0,y:-s+o},{x:n/2,y:-s},{x:n,y:-s+o},{x:n-r,y:-s+o},{x:n-r,y:-o}]:i.has("down")?[{x:n/2,y:0},{x:0,y:-o},{x:r,y:-o},{x:r,y:-s+o},{x:n-r,y:-s+o},{x:n-r,y:-o},{x:n,y:-o}]:[{x:0,y:0}]},"getArrowPoints");function Jt(e,t){return e.intersect(t)}d(Jt,"intersectNode");var yr=Jt;function Qt(e,t,a,i){var l=e.x,s=e.y,r=l-i.x,n=s-i.y,o=Math.sqrt(t*t*n*n+a*a*r*r),u=Math.abs(t*a*r/o);i.x0}d(vt,"sameSign");var wr=ee,mr=re;function re(e,t,a){var i=e.x,l=e.y,s=[],r=Number.POSITIVE_INFINITY,n=Number.POSITIVE_INFINITY;typeof t.forEach=="function"?t.forEach(function(E){r=Math.min(r,E.x),n=Math.min(n,E.y)}):(r=Math.min(r,t.x),n=Math.min(n,t.y));for(var o=i-e.width/2-r,u=l-e.height/2-n,h=0;h1&&s.sort(function(E,D){var v=E.x-a.x,T=E.y-a.y,k=Math.sqrt(v*v+T*T),N=D.x-a.x,x=D.y-a.y,g=Math.sqrt(N*N+x*x);return k{var a=e.x,i=e.y,l=t.x-a,s=t.y-i,r=e.width/2,n=e.height/2,o,u;return Math.abs(s)*r>Math.abs(l)*n?(s<0&&(n=-n),o=s===0?0:n*l/s,u=n):(l<0&&(r=-r),o=r,u=l===0?0:r*s/l),{x:a+o,y:i+u}},"intersectRect"),Sr=Lr,C={node:yr,circle:br,ellipse:$t,polygon:mr,rect:Sr},F=d(async(e,t,a,i)=>{const l=z();let s;const r=t.useHtmlLabels||Z(l.flowchart.htmlLabels);a?s=a:s="node default";const n=e.insert("g").attr("class",s).attr("id",t.domId||t.id),o=n.insert("g").attr("class","label").attr("style",t.labelStyle);let u;t.labelText===void 0?u="":u=typeof t.labelText=="string"?t.labelText:t.labelText[0];const h=o.node();let y;t.labelType==="markdown"?y=Ht(o,bt(yt(u),l),{useHtmlLabels:r,width:t.width||l.flowchart.wrappingWidth,classes:"markdown-node-label"},l):y=h.appendChild(await j(bt(yt(u),l),t.labelStyle,!1,i));let b=y.getBBox();const L=t.padding/2;if(Z(l.flowchart.htmlLabels)){const E=y.children[0],D=R(y),v=E.getElementsByTagName("img");if(v){const T=u.replace(/]*>/g,"").trim()==="";await Promise.all([...v].map(k=>new Promise(N=>{function x(){if(k.style.display="flex",k.style.flexDirection="column",T){const g=l.fontSize?l.fontSize:window.getComputedStyle(document.body).fontSize,w=parseInt(g,10)*5+"px";k.style.minWidth=w,k.style.maxWidth=w}else k.style.width="100%";N(k)}d(x,"setupImage"),setTimeout(()=>{k.complete&&x()}),k.addEventListener("error",x),k.addEventListener("load",x)})))}b=E.getBoundingClientRect(),D.attr("width",b.width),D.attr("height",b.height)}return r?o.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"):o.attr("transform","translate(0, "+-b.height/2+")"),t.centerLabel&&o.attr("transform","translate("+-b.width/2+", "+-b.height/2+")"),o.insert("rect",":first-child"),{shapeSvg:n,bbox:b,halfPadding:L,label:o}},"labelHelper"),I=d((e,t)=>{const a=t.node().getBBox();e.width=a.width,e.height=a.height},"updateNodeBounds");function G(e,t,a,i){return e.insert("polygon",":first-child").attr("points",i.map(function(l){return l.x+","+l.y}).join(" ")).attr("class","label-container").attr("transform","translate("+-t/2+","+a/2+")")}d(G,"insertPolygonShape");var vr=d(async(e,t)=>{t.useHtmlLabels||z().flowchart.htmlLabels||(t.centerLabel=!0);const{shapeSvg:i,bbox:l,halfPadding:s}=await F(e,t,"node "+t.classes,!0);m.info("Classes = ",t.classes);const r=i.insert("rect",":first-child");return r.attr("rx",t.rx).attr("ry",t.ry).attr("x",-l.width/2-s).attr("y",-l.height/2-s).attr("width",l.width+t.padding).attr("height",l.height+t.padding),I(t,r),t.intersect=function(n){return C.rect(t,n)},i},"note"),Er=vr,At=d(e=>e?" "+e:"","formatClass"),K=d((e,t)=>`${t||"node default"}${At(e.classes)} ${At(e.class)}`,"getClassesFromNode"),Mt=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=l+s,n=[{x:r/2,y:0},{x:r,y:-r/2},{x:r/2,y:-r},{x:0,y:-r/2}];m.info("Question main (Circle)");const o=G(a,r,r,n);return o.attr("style",t.style),I(t,o),t.intersect=function(u){return m.warn("Intersect called"),C.polygon(t,n,u)},a},"question"),_r=d((e,t)=>{const a=e.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=28,l=[{x:0,y:i/2},{x:i/2,y:0},{x:0,y:-i/2},{x:-i/2,y:0}];return a.insert("polygon",":first-child").attr("points",l.map(function(r){return r.x+","+r.y}).join(" ")).attr("class","state-start").attr("r",7).attr("width",28).attr("height",28),t.width=28,t.height=28,t.intersect=function(r){return C.circle(t,14,r)},a},"choice"),kr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=4,s=i.height+t.padding,r=s/l,n=i.width+2*r+t.padding,o=[{x:r,y:0},{x:n-r,y:0},{x:n,y:-s/2},{x:n-r,y:-s},{x:r,y:-s},{x:0,y:-s/2}],u=G(a,n,s,o);return u.attr("style",t.style),I(t,u),t.intersect=function(h){return C.polygon(t,o,h)},a},"hexagon"),Dr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,void 0,!0),l=2,s=i.height+2*t.padding,r=s/l,n=i.width+2*r+t.padding,o=xr(t.directions,i,t),u=G(a,n,s,o);return u.attr("style",t.style),I(t,u),t.intersect=function(h){return C.polygon(t,o,h)},a},"block_arrow"),Nr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:-s/2,y:0},{x:l,y:0},{x:l,y:-s},{x:-s/2,y:-s},{x:0,y:-s/2}];return G(a,l,s,r).attr("style",t.style),t.width=l+s,t.height=s,t.intersect=function(o){return C.polygon(t,r,o)},a},"rect_left_inv_arrow"),Tr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:-2*s/6,y:0},{x:l-s/6,y:0},{x:l+2*s/6,y:-s},{x:s/6,y:-s}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"lean_right"),Cr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:2*s/6,y:0},{x:l+s/6,y:0},{x:l-2*s/6,y:-s},{x:-s/6,y:-s}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"lean_left"),Ir=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:-2*s/6,y:0},{x:l+2*s/6,y:0},{x:l-s/6,y:-s},{x:s/6,y:-s}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"trapezoid"),Br=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:s/6,y:0},{x:l-s/6,y:0},{x:l+2*s/6,y:-s},{x:-2*s/6,y:-s}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"inv_trapezoid"),Or=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:0,y:0},{x:l+s/2,y:0},{x:l,y:-s/2},{x:l+s/2,y:-s},{x:0,y:-s}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"rect_right_inv_arrow"),Rr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=l/2,r=s/(2.5+l/50),n=i.height+r+t.padding,o="M 0,"+r+" a "+s+","+r+" 0,0,0 "+l+" 0 a "+s+","+r+" 0,0,0 "+-l+" 0 l 0,"+n+" a "+s+","+r+" 0,0,0 "+l+" 0 l 0,"+-n,u=a.attr("label-offset-y",r).insert("path",":first-child").attr("style",t.style).attr("d",o).attr("transform","translate("+-l/2+","+-(n/2+r)+")");return I(t,u),t.intersect=function(h){const y=C.rect(t,h),b=y.x-t.x;if(s!=0&&(Math.abs(b)t.height/2-r)){let L=r*r*(1-b*b/(s*s));L!=0&&(L=Math.sqrt(L)),L=r-L,h.y-t.y>0&&(L=-L),y.y+=L}return y},a},"cylinder"),zr=d(async(e,t)=>{const{shapeSvg:a,bbox:i,halfPadding:l}=await F(e,t,"node "+t.classes+" "+t.class,!0),s=a.insert("rect",":first-child"),r=t.positioned?t.width:i.width+t.padding,n=t.positioned?t.height:i.height+t.padding,o=t.positioned?-r/2:-i.width/2-l,u=t.positioned?-n/2:-i.height/2-l;if(s.attr("class","basic label-container").attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("x",o).attr("y",u).attr("width",r).attr("height",n),t.props){const h=new Set(Object.keys(t.props));t.props.borders&&(dt(s,t.props.borders,r,n),h.delete("borders")),h.forEach(y=>{m.warn(`Unknown node property ${y}`)})}return I(t,s),t.intersect=function(h){return C.rect(t,h)},a},"rect"),Ar=d(async(e,t)=>{const{shapeSvg:a,bbox:i,halfPadding:l}=await F(e,t,"node "+t.classes,!0),s=a.insert("rect",":first-child"),r=t.positioned?t.width:i.width+t.padding,n=t.positioned?t.height:i.height+t.padding,o=t.positioned?-r/2:-i.width/2-l,u=t.positioned?-n/2:-i.height/2-l;if(s.attr("class","basic cluster composite label-container").attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("x",o).attr("y",u).attr("width",r).attr("height",n),t.props){const h=new Set(Object.keys(t.props));t.props.borders&&(dt(s,t.props.borders,r,n),h.delete("borders")),h.forEach(y=>{m.warn(`Unknown node property ${y}`)})}return I(t,s),t.intersect=function(h){return C.rect(t,h)},a},"composite"),Mr=d(async(e,t)=>{const{shapeSvg:a}=await F(e,t,"label",!0);m.trace("Classes = ",t.class);const i=a.insert("rect",":first-child"),l=0,s=0;if(i.attr("width",l).attr("height",s),a.attr("class","label edgeLabel"),t.props){const r=new Set(Object.keys(t.props));t.props.borders&&(dt(i,t.props.borders,l,s),r.delete("borders")),r.forEach(n=>{m.warn(`Unknown node property ${n}`)})}return I(t,i),t.intersect=function(r){return C.rect(t,r)},a},"labelRect");function dt(e,t,a,i){const l=[],s=d(n=>{l.push(n,0)},"addBorder"),r=d(n=>{l.push(0,n)},"skipBorder");t.includes("t")?(m.debug("add top border"),s(a)):r(a),t.includes("r")?(m.debug("add right border"),s(i)):r(i),t.includes("b")?(m.debug("add bottom border"),s(a)):r(a),t.includes("l")?(m.debug("add left border"),s(i)):r(i),e.attr("stroke-dasharray",l.join(" "))}d(dt,"applyNodePropertyBorders");var Fr=d(async(e,t)=>{let a;t.classes?a="node "+t.classes:a="node default";const i=e.insert("g").attr("class",a).attr("id",t.domId||t.id),l=i.insert("rect",":first-child"),s=i.insert("line"),r=i.insert("g").attr("class","label"),n=t.labelText.flat?t.labelText.flat():t.labelText;let o="";typeof n=="object"?o=n[0]:o=n,m.info("Label text abc79",o,n,typeof n=="object");const u=r.node().appendChild(await j(o,t.labelStyle,!0,!0));let h={width:0,height:0};if(Z(z().flowchart.htmlLabels)){const D=u.children[0],v=R(u);h=D.getBoundingClientRect(),v.attr("width",h.width),v.attr("height",h.height)}m.info("Text 2",n);const y=n.slice(1,n.length);let b=u.getBBox();const L=r.node().appendChild(await j(y.join?y.join("
"):y,t.labelStyle,!0,!0));if(Z(z().flowchart.htmlLabels)){const D=L.children[0],v=R(L);h=D.getBoundingClientRect(),v.attr("width",h.width),v.attr("height",h.height)}const E=t.padding/2;return R(L).attr("transform","translate( "+(h.width>b.width?0:(b.width-h.width)/2)+", "+(b.height+E+5)+")"),R(u).attr("transform","translate( "+(h.width{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.height+t.padding,s=i.width+l/4+t.padding,r=a.insert("rect",":first-child").attr("style",t.style).attr("rx",l/2).attr("ry",l/2).attr("x",-s/2).attr("y",-l/2).attr("width",s).attr("height",l);return I(t,r),t.intersect=function(n){return C.rect(t,n)},a},"stadium"),Pr=d(async(e,t)=>{const{shapeSvg:a,bbox:i,halfPadding:l}=await F(e,t,K(t,void 0),!0),s=a.insert("circle",":first-child");return s.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l).attr("width",i.width+t.padding).attr("height",i.height+t.padding),m.info("Circle main"),I(t,s),t.intersect=function(r){return m.info("Circle intersect",t,i.width/2+l,r),C.circle(t,i.width/2+l,r)},a},"circle"),Yr=d(async(e,t)=>{const{shapeSvg:a,bbox:i,halfPadding:l}=await F(e,t,K(t,void 0),!0),s=5,r=a.insert("g",":first-child"),n=r.insert("circle"),o=r.insert("circle");return r.attr("class",t.class),n.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l+s).attr("width",i.width+t.padding+s*2).attr("height",i.height+t.padding+s*2),o.attr("style",t.style).attr("rx",t.rx).attr("ry",t.ry).attr("r",i.width/2+l).attr("width",i.width+t.padding).attr("height",i.height+t.padding),m.info("DoubleCircle main"),I(t,n),t.intersect=function(u){return m.info("DoubleCircle intersect",t,i.width/2+l+s,u),C.circle(t,i.width/2+l+s,u)},a},"doublecircle"),Hr=d(async(e,t)=>{const{shapeSvg:a,bbox:i}=await F(e,t,K(t,void 0),!0),l=i.width+t.padding,s=i.height+t.padding,r=[{x:0,y:0},{x:l,y:0},{x:l,y:-s},{x:0,y:-s},{x:0,y:0},{x:-8,y:0},{x:l+8,y:0},{x:l+8,y:-s},{x:-8,y:-s},{x:-8,y:0}],n=G(a,l,s,r);return n.attr("style",t.style),I(t,n),t.intersect=function(o){return C.polygon(t,r,o)},a},"subroutine"),Kr=d((e,t)=>{const a=e.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=a.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),I(t,i),t.intersect=function(l){return C.circle(t,7,l)},a},"start"),Ft=d((e,t,a)=>{const i=e.insert("g").attr("class","node default").attr("id",t.domId||t.id);let l=70,s=10;a==="LR"&&(l=10,s=70);const r=i.append("rect").attr("x",-1*l/2).attr("y",-1*s/2).attr("width",l).attr("height",s).attr("class","fork-join");return I(t,r),t.height=t.height+t.padding/2,t.width=t.width+t.padding/2,t.intersect=function(n){return C.rect(t,n)},i},"forkJoin"),Xr=d((e,t)=>{const a=e.insert("g").attr("class","node default").attr("id",t.domId||t.id),i=a.insert("circle",":first-child"),l=a.insert("circle",":first-child");return l.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),i.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),I(t,l),t.intersect=function(s){return C.circle(t,7,s)},a},"end"),Ur=d(async(e,t)=>{var S;const a=t.padding/2,i=4,l=8;let s;t.classes?s="node "+t.classes:s="node default";const r=e.insert("g").attr("class",s).attr("id",t.domId||t.id),n=r.insert("rect",":first-child"),o=r.insert("line"),u=r.insert("line");let h=0,y=i;const b=r.insert("g").attr("class","label");let L=0;const E=(S=t.classData.annotations)==null?void 0:S[0],D=t.classData.annotations[0]?"«"+t.classData.annotations[0]+"»":"",v=b.node().appendChild(await j(D,t.labelStyle,!0,!0));let T=v.getBBox();if(Z(z().flowchart.htmlLabels)){const c=v.children[0],_=R(v);T=c.getBoundingClientRect(),_.attr("width",T.width),_.attr("height",T.height)}t.classData.annotations[0]&&(y+=T.height+i,h+=T.width);let k=t.classData.label;t.classData.type!==void 0&&t.classData.type!==""&&(z().flowchart.htmlLabels?k+="<"+t.classData.type+">":k+="<"+t.classData.type+">");const N=b.node().appendChild(await j(k,t.labelStyle,!0,!0));R(N).attr("class","classTitle");let x=N.getBBox();if(Z(z().flowchart.htmlLabels)){const c=N.children[0],_=R(N);x=c.getBoundingClientRect(),_.attr("width",x.width),_.attr("height",x.height)}y+=x.height+i,x.width>h&&(h=x.width);const g=[];t.classData.members.forEach(async c=>{const _=c.getDisplayDetails();let f=_.displayText;z().flowchart.htmlLabels&&(f=f.replace(//g,">"));const A=b.node().appendChild(await j(f,_.cssStyle?_.cssStyle:t.labelStyle,!0,!0));let O=A.getBBox();if(Z(z().flowchart.htmlLabels)){const X=A.children[0],P=R(A);O=X.getBoundingClientRect(),P.attr("width",O.width),P.attr("height",O.height)}O.width>h&&(h=O.width),y+=O.height+i,g.push(A)}),y+=l;const p=[];if(t.classData.methods.forEach(async c=>{const _=c.getDisplayDetails();let f=_.displayText;z().flowchart.htmlLabels&&(f=f.replace(//g,">"));const A=b.node().appendChild(await j(f,_.cssStyle?_.cssStyle:t.labelStyle,!0,!0));let O=A.getBBox();if(Z(z().flowchart.htmlLabels)){const X=A.children[0],P=R(A);O=X.getBoundingClientRect(),P.attr("width",O.width),P.attr("height",O.height)}O.width>h&&(h=O.width),y+=O.height+i,p.push(A)}),y+=l,E){let c=(h-T.width)/2;R(v).attr("transform","translate( "+(-1*h/2+c)+", "+-1*y/2+")"),L=T.height+i}let w=(h-x.width)/2;return R(N).attr("transform","translate( "+(-1*h/2+w)+", "+(-1*y/2+L)+")"),L+=x.height+i,o.attr("class","divider").attr("x1",-h/2-a).attr("x2",h/2+a).attr("y1",-y/2-a+l+L).attr("y2",-y/2-a+l+L),L+=l,g.forEach(c=>{R(c).attr("transform","translate( "+-h/2+", "+(-1*y/2+L+l/2)+")");const _=c==null?void 0:c.getBBox();L+=((_==null?void 0:_.height)??0)+i}),L+=l,u.attr("class","divider").attr("x1",-h/2-a).attr("x2",h/2+a).attr("y1",-y/2-a+l+L).attr("y2",-y/2-a+l+L),L+=l,p.forEach(c=>{R(c).attr("transform","translate( "+-h/2+", "+(-1*y/2+L)+")");const _=c==null?void 0:c.getBBox();L+=((_==null?void 0:_.height)??0)+i}),n.attr("style",t.style).attr("class","outer title-state").attr("x",-h/2-a).attr("y",-(y/2)-a).attr("width",h+t.padding).attr("height",y+t.padding),I(t,n),t.intersect=function(c){return C.rect(t,c)},r},"class_box"),Wt={rhombus:Mt,composite:Ar,question:Mt,rect:zr,labelRect:Mr,rectWithTitle:Fr,choice:_r,circle:Pr,doublecircle:Yr,stadium:Wr,hexagon:kr,block_arrow:Dr,rect_left_inv_arrow:Nr,lean_right:Tr,lean_left:Cr,trapezoid:Ir,inv_trapezoid:Br,rect_right_inv_arrow:Or,cylinder:Rr,start:Kr,end:Xr,note:Er,subroutine:Hr,fork:Ft,join:Ft,class_box:Ur},ct={},ae=d(async(e,t,a)=>{let i,l;if(t.link){let s;z().securityLevel==="sandbox"?s="_top":t.linkTarget&&(s=t.linkTarget||"_blank"),i=e.insert("svg:a").attr("xlink:href",t.link).attr("target",s),l=await Wt[t.shape](i,t,a)}else l=await Wt[t.shape](e,t,a),i=l;return t.tooltip&&l.attr("title",t.tooltip),t.class&&l.attr("class","node default "+t.class),ct[t.id]=i,t.haveCallback&&ct[t.id].attr("class",ct[t.id].attr("class")+" clickable"),i},"insertNode"),jr=d(e=>{const t=ct[e.id];m.trace("Transforming node",e.diff,e,"translate("+(e.x-e.width/2-5)+", "+e.width/2+")");const a=8,i=e.diff||0;return e.clusterNode?t.attr("transform","translate("+(e.x+i-e.width/2)+", "+(e.y-e.height/2-a)+")"):t.attr("transform","translate("+e.x+", "+e.y+")"),i},"positionNode");function Nt(e,t,a=!1){var b,L,E;const i=e;let l="default";(((b=i==null?void 0:i.classes)==null?void 0:b.length)||0)>0&&(l=((i==null?void 0:i.classes)??[]).join(" ")),l=l+" flowchart-label";let s=0,r="",n;switch(i.type){case"round":s=5,r="rect";break;case"composite":s=0,r="composite",n=0;break;case"square":r="rect";break;case"diamond":r="question";break;case"hexagon":r="hexagon";break;case"block_arrow":r="block_arrow";break;case"odd":r="rect_left_inv_arrow";break;case"lean_right":r="lean_right";break;case"lean_left":r="lean_left";break;case"trapezoid":r="trapezoid";break;case"inv_trapezoid":r="inv_trapezoid";break;case"rect_left_inv_arrow":r="rect_left_inv_arrow";break;case"circle":r="circle";break;case"ellipse":r="ellipse";break;case"stadium":r="stadium";break;case"subroutine":r="subroutine";break;case"cylinder":r="cylinder";break;case"group":r="rect";break;case"doublecircle":r="doublecircle";break;default:r="rect"}const o=ve((i==null?void 0:i.styles)??[]),u=i.label,h=i.size??{width:0,height:0,x:0,y:0};return{labelStyle:o.labelStyle,shape:r,labelText:u,rx:s,ry:s,class:l,style:o.style,id:i.id,directions:i.directions,width:h.width,height:h.height,x:h.x,y:h.y,positioned:a,intersect:void 0,type:i.type,padding:n??((E=(L=st())==null?void 0:L.block)==null?void 0:E.padding)??0}}d(Nt,"getNodeFromBlock");async function se(e,t,a){const i=Nt(t,a,!1);if(i.type==="group")return;const l=st(),s=await ae(e,i,{config:l}),r=s.node().getBBox(),n=a.getBlock(i.id);n.size={width:r.width,height:r.height,x:0,y:0,node:s},a.setBlock(n),s.remove()}d(se,"calculateBlockSize");async function ie(e,t,a){const i=Nt(t,a,!0);if(a.getBlock(i.id).type!=="space"){const s=st();await ae(e,i,{config:s}),t.intersect=i==null?void 0:i.intersect,jr(i)}}d(ie,"insertBlockPositioned");async function gt(e,t,a,i){for(const l of t)await i(e,l,a),l.children&&await gt(e,l.children,a,i)}d(gt,"performOperations");async function ne(e,t,a){await gt(e,t,a,se)}d(ne,"calculateBlockSizes");async function le(e,t,a){await gt(e,t,a,ie)}d(le,"insertBlocks");async function ce(e,t,a,i,l){const s=new _e({multigraph:!0,compound:!0});s.setGraph({rankdir:"TB",nodesep:10,ranksep:10,marginx:8,marginy:8});for(const r of a)r.size&&s.setNode(r.id,{width:r.size.width,height:r.size.height,intersect:r.intersect});for(const r of t)if(r.start&&r.end){const n=i.getBlock(r.start),o=i.getBlock(r.end);if(n!=null&&n.size&&(o!=null&&o.size)){const u=n.size,h=o.size,y=[{x:u.x,y:u.y},{x:u.x+(h.x-u.x)/2,y:u.y+(h.y-u.y)/2},{x:h.x,y:h.y}];pr(e,{v:r.start,w:r.end,name:r.id},{...r,arrowTypeEnd:r.arrowTypeEnd,arrowTypeStart:r.arrowTypeStart,points:y,classes:"edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1"},void 0,"block",s,l),r.label&&(await hr(e,{...r,label:r.label,labelStyle:"stroke: #333; stroke-width: 1.5px;fill:none;",arrowTypeEnd:r.arrowTypeEnd,arrowTypeStart:r.arrowTypeStart,points:y,classes:"edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1"}),dr({...r,x:y[1].x,y:y[1].y},{originalPath:y}))}}}d(ce,"insertEdges");var Vr=d(function(e,t){return t.db.getClasses()},"getClasses"),Gr=d(async function(e,t,a,i){const{securityLevel:l,block:s}=st(),r=i.db;let n;l==="sandbox"&&(n=R("#i"+t));const o=l==="sandbox"?R(n.nodes()[0].contentDocument.body):R("body"),u=l==="sandbox"?o.select(`[id="${t}"]`):R(`[id="${t}"]`);ir(u,["point","circle","cross"],i.type,t);const y=r.getBlocks(),b=r.getBlocksFlat(),L=r.getEdges(),E=u.insert("g").attr("class","block");await ne(E,y,r);const D=Zt(r);if(await le(E,y,r),await ce(E,L,b,r,t),D){const v=D,T=Math.max(1,Math.round(.125*(v.width/v.height))),k=v.height+T+10,N=v.width+10,{useMaxWidth:x}=s;ge(u,k,N,!!x),m.debug("Here Bounds",D,v),u.attr("viewBox",`${v.x-5} ${v.y-5} ${v.width+10} ${v.height+10}`)}},"draw"),Zr={draw:Gr,getClasses:Vr},ra={parser:ke,db:Ue,renderer:Zr,styles:Ve};export{ra as diagram}; diff --git a/assets/chunks/c4Diagram-YG6GDRKO.Cxb4MoHr.js b/assets/chunks/c4Diagram-YG6GDRKO.Cxb4MoHr.js new file mode 100644 index 000000000..d0213046b --- /dev/null +++ b/assets/chunks/c4Diagram-YG6GDRKO.Cxb4MoHr.js @@ -0,0 +1,10 @@ +import{g as Se,d as De}from"./chunk-TZMSLE5B.CN1RMadv.js";import{_ as g,s as Pe,g as Be,a as Ie,b as Me,c as Bt,d as jt,l as de,e as Le,f as Ne,h as Tt,i as ge,j as Ye,w as je,k as $t,m as fe}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var Ft=function(){var e=g(function(_t,x,m,v){for(m=m||{},v=_t.length;v--;m[_t[v]]=x);return m},"o"),t=[1,24],s=[1,25],o=[1,26],l=[1,27],a=[1,28],r=[1,63],n=[1,64],i=[1,65],u=[1,66],d=[1,67],f=[1,68],y=[1,69],E=[1,29],O=[1,30],S=[1,31],P=[1,32],M=[1,33],U=[1,34],H=[1,35],q=[1,36],G=[1,37],K=[1,38],J=[1,39],Z=[1,40],$=[1,41],tt=[1,42],et=[1,43],at=[1,44],it=[1,45],nt=[1,46],rt=[1,47],st=[1,48],lt=[1,50],ot=[1,51],ct=[1,52],ht=[1,53],ut=[1,54],dt=[1,55],ft=[1,56],pt=[1,57],yt=[1,58],gt=[1,59],bt=[1,60],Ct=[14,42],Qt=[14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],St=[12,14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],k=[1,82],A=[1,83],C=[1,84],w=[1,85],T=[12,14,42],le=[12,14,33,42],Mt=[12,14,33,42,76,77,79,80],vt=[12,33],Ht=[34,36,37,38,39,40,41,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],qt={trace:g(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mermaidDoc:4,direction:5,direction_tb:6,direction_bt:7,direction_rl:8,direction_lr:9,graphConfig:10,C4_CONTEXT:11,NEWLINE:12,statements:13,EOF:14,C4_CONTAINER:15,C4_COMPONENT:16,C4_DYNAMIC:17,C4_DEPLOYMENT:18,otherStatements:19,diagramStatements:20,otherStatement:21,title:22,accDescription:23,acc_title:24,acc_title_value:25,acc_descr:26,acc_descr_value:27,acc_descr_multiline_value:28,boundaryStatement:29,boundaryStartStatement:30,boundaryStopStatement:31,boundaryStart:32,LBRACE:33,ENTERPRISE_BOUNDARY:34,attributes:35,SYSTEM_BOUNDARY:36,BOUNDARY:37,CONTAINER_BOUNDARY:38,NODE:39,NODE_L:40,NODE_R:41,RBRACE:42,diagramStatement:43,PERSON:44,PERSON_EXT:45,SYSTEM:46,SYSTEM_DB:47,SYSTEM_QUEUE:48,SYSTEM_EXT:49,SYSTEM_EXT_DB:50,SYSTEM_EXT_QUEUE:51,CONTAINER:52,CONTAINER_DB:53,CONTAINER_QUEUE:54,CONTAINER_EXT:55,CONTAINER_EXT_DB:56,CONTAINER_EXT_QUEUE:57,COMPONENT:58,COMPONENT_DB:59,COMPONENT_QUEUE:60,COMPONENT_EXT:61,COMPONENT_EXT_DB:62,COMPONENT_EXT_QUEUE:63,REL:64,BIREL:65,REL_U:66,REL_D:67,REL_L:68,REL_R:69,REL_B:70,REL_INDEX:71,UPDATE_EL_STYLE:72,UPDATE_REL_STYLE:73,UPDATE_LAYOUT_CONFIG:74,attribute:75,STR:76,STR_KEY:77,STR_VALUE:78,ATTRIBUTE:79,ATTRIBUTE_EMPTY:80,$accept:0,$end:1},terminals_:{2:"error",6:"direction_tb",7:"direction_bt",8:"direction_rl",9:"direction_lr",11:"C4_CONTEXT",12:"NEWLINE",14:"EOF",15:"C4_CONTAINER",16:"C4_COMPONENT",17:"C4_DYNAMIC",18:"C4_DEPLOYMENT",22:"title",23:"accDescription",24:"acc_title",25:"acc_title_value",26:"acc_descr",27:"acc_descr_value",28:"acc_descr_multiline_value",33:"LBRACE",34:"ENTERPRISE_BOUNDARY",36:"SYSTEM_BOUNDARY",37:"BOUNDARY",38:"CONTAINER_BOUNDARY",39:"NODE",40:"NODE_L",41:"NODE_R",42:"RBRACE",44:"PERSON",45:"PERSON_EXT",46:"SYSTEM",47:"SYSTEM_DB",48:"SYSTEM_QUEUE",49:"SYSTEM_EXT",50:"SYSTEM_EXT_DB",51:"SYSTEM_EXT_QUEUE",52:"CONTAINER",53:"CONTAINER_DB",54:"CONTAINER_QUEUE",55:"CONTAINER_EXT",56:"CONTAINER_EXT_DB",57:"CONTAINER_EXT_QUEUE",58:"COMPONENT",59:"COMPONENT_DB",60:"COMPONENT_QUEUE",61:"COMPONENT_EXT",62:"COMPONENT_EXT_DB",63:"COMPONENT_EXT_QUEUE",64:"REL",65:"BIREL",66:"REL_U",67:"REL_D",68:"REL_L",69:"REL_R",70:"REL_B",71:"REL_INDEX",72:"UPDATE_EL_STYLE",73:"UPDATE_REL_STYLE",74:"UPDATE_LAYOUT_CONFIG",76:"STR",77:"STR_KEY",78:"STR_VALUE",79:"ATTRIBUTE",80:"ATTRIBUTE_EMPTY"},productions_:[0,[3,1],[3,1],[5,1],[5,1],[5,1],[5,1],[4,1],[10,4],[10,4],[10,4],[10,4],[10,4],[13,1],[13,1],[13,2],[19,1],[19,2],[19,3],[21,1],[21,1],[21,2],[21,2],[21,1],[29,3],[30,3],[30,3],[30,4],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[31,1],[20,1],[20,2],[20,3],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,1],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[35,1],[35,2],[75,1],[75,2],[75,1],[75,1]],performAction:g(function(x,m,v,b,R,h,Dt){var p=h.length-1;switch(R){case 3:b.setDirection("TB");break;case 4:b.setDirection("BT");break;case 5:b.setDirection("RL");break;case 6:b.setDirection("LR");break;case 8:case 9:case 10:case 11:case 12:b.setC4Type(h[p-3]);break;case 19:b.setTitle(h[p].substring(6)),this.$=h[p].substring(6);break;case 20:b.setAccDescription(h[p].substring(15)),this.$=h[p].substring(15);break;case 21:this.$=h[p].trim(),b.setTitle(this.$);break;case 22:case 23:this.$=h[p].trim(),b.setAccDescription(this.$);break;case 28:h[p].splice(2,0,"ENTERPRISE"),b.addPersonOrSystemBoundary(...h[p]),this.$=h[p];break;case 29:h[p].splice(2,0,"SYSTEM"),b.addPersonOrSystemBoundary(...h[p]),this.$=h[p];break;case 30:b.addPersonOrSystemBoundary(...h[p]),this.$=h[p];break;case 31:h[p].splice(2,0,"CONTAINER"),b.addContainerBoundary(...h[p]),this.$=h[p];break;case 32:b.addDeploymentNode("node",...h[p]),this.$=h[p];break;case 33:b.addDeploymentNode("nodeL",...h[p]),this.$=h[p];break;case 34:b.addDeploymentNode("nodeR",...h[p]),this.$=h[p];break;case 35:b.popBoundaryParseStack();break;case 39:b.addPersonOrSystem("person",...h[p]),this.$=h[p];break;case 40:b.addPersonOrSystem("external_person",...h[p]),this.$=h[p];break;case 41:b.addPersonOrSystem("system",...h[p]),this.$=h[p];break;case 42:b.addPersonOrSystem("system_db",...h[p]),this.$=h[p];break;case 43:b.addPersonOrSystem("system_queue",...h[p]),this.$=h[p];break;case 44:b.addPersonOrSystem("external_system",...h[p]),this.$=h[p];break;case 45:b.addPersonOrSystem("external_system_db",...h[p]),this.$=h[p];break;case 46:b.addPersonOrSystem("external_system_queue",...h[p]),this.$=h[p];break;case 47:b.addContainer("container",...h[p]),this.$=h[p];break;case 48:b.addContainer("container_db",...h[p]),this.$=h[p];break;case 49:b.addContainer("container_queue",...h[p]),this.$=h[p];break;case 50:b.addContainer("external_container",...h[p]),this.$=h[p];break;case 51:b.addContainer("external_container_db",...h[p]),this.$=h[p];break;case 52:b.addContainer("external_container_queue",...h[p]),this.$=h[p];break;case 53:b.addComponent("component",...h[p]),this.$=h[p];break;case 54:b.addComponent("component_db",...h[p]),this.$=h[p];break;case 55:b.addComponent("component_queue",...h[p]),this.$=h[p];break;case 56:b.addComponent("external_component",...h[p]),this.$=h[p];break;case 57:b.addComponent("external_component_db",...h[p]),this.$=h[p];break;case 58:b.addComponent("external_component_queue",...h[p]),this.$=h[p];break;case 60:b.addRel("rel",...h[p]),this.$=h[p];break;case 61:b.addRel("birel",...h[p]),this.$=h[p];break;case 62:b.addRel("rel_u",...h[p]),this.$=h[p];break;case 63:b.addRel("rel_d",...h[p]),this.$=h[p];break;case 64:b.addRel("rel_l",...h[p]),this.$=h[p];break;case 65:b.addRel("rel_r",...h[p]),this.$=h[p];break;case 66:b.addRel("rel_b",...h[p]),this.$=h[p];break;case 67:h[p].splice(0,1),b.addRel("rel",...h[p]),this.$=h[p];break;case 68:b.updateElStyle("update_el_style",...h[p]),this.$=h[p];break;case 69:b.updateRelStyle("update_rel_style",...h[p]),this.$=h[p];break;case 70:b.updateLayoutConfig("update_layout_config",...h[p]),this.$=h[p];break;case 71:this.$=[h[p]];break;case 72:h[p].unshift(h[p-1]),this.$=h[p];break;case 73:case 75:this.$=h[p].trim();break;case 74:let Et={};Et[h[p-1].trim()]=h[p].trim(),this.$=Et;break;case 76:this.$="";break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],7:[1,6],8:[1,7],9:[1,8],10:4,11:[1,9],15:[1,10],16:[1,11],17:[1,12],18:[1,13]},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,7]},{1:[2,3]},{1:[2,4]},{1:[2,5]},{1:[2,6]},{12:[1,14]},{12:[1,15]},{12:[1,16]},{12:[1,17]},{12:[1,18]},{13:19,19:20,20:21,21:22,22:t,23:s,24:o,26:l,28:a,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{13:70,19:20,20:21,21:22,22:t,23:s,24:o,26:l,28:a,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{13:71,19:20,20:21,21:22,22:t,23:s,24:o,26:l,28:a,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{13:72,19:20,20:21,21:22,22:t,23:s,24:o,26:l,28:a,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{13:73,19:20,20:21,21:22,22:t,23:s,24:o,26:l,28:a,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{14:[1,74]},e(Ct,[2,13],{43:23,29:49,30:61,32:62,20:75,34:r,36:n,37:i,38:u,39:d,40:f,41:y,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt}),e(Ct,[2,14]),e(Qt,[2,16],{12:[1,76]}),e(Ct,[2,36],{12:[1,77]}),e(St,[2,19]),e(St,[2,20]),{25:[1,78]},{27:[1,79]},e(St,[2,23]),{35:80,75:81,76:k,77:A,79:C,80:w},{35:86,75:81,76:k,77:A,79:C,80:w},{35:87,75:81,76:k,77:A,79:C,80:w},{35:88,75:81,76:k,77:A,79:C,80:w},{35:89,75:81,76:k,77:A,79:C,80:w},{35:90,75:81,76:k,77:A,79:C,80:w},{35:91,75:81,76:k,77:A,79:C,80:w},{35:92,75:81,76:k,77:A,79:C,80:w},{35:93,75:81,76:k,77:A,79:C,80:w},{35:94,75:81,76:k,77:A,79:C,80:w},{35:95,75:81,76:k,77:A,79:C,80:w},{35:96,75:81,76:k,77:A,79:C,80:w},{35:97,75:81,76:k,77:A,79:C,80:w},{35:98,75:81,76:k,77:A,79:C,80:w},{35:99,75:81,76:k,77:A,79:C,80:w},{35:100,75:81,76:k,77:A,79:C,80:w},{35:101,75:81,76:k,77:A,79:C,80:w},{35:102,75:81,76:k,77:A,79:C,80:w},{35:103,75:81,76:k,77:A,79:C,80:w},{35:104,75:81,76:k,77:A,79:C,80:w},e(T,[2,59]),{35:105,75:81,76:k,77:A,79:C,80:w},{35:106,75:81,76:k,77:A,79:C,80:w},{35:107,75:81,76:k,77:A,79:C,80:w},{35:108,75:81,76:k,77:A,79:C,80:w},{35:109,75:81,76:k,77:A,79:C,80:w},{35:110,75:81,76:k,77:A,79:C,80:w},{35:111,75:81,76:k,77:A,79:C,80:w},{35:112,75:81,76:k,77:A,79:C,80:w},{35:113,75:81,76:k,77:A,79:C,80:w},{35:114,75:81,76:k,77:A,79:C,80:w},{35:115,75:81,76:k,77:A,79:C,80:w},{20:116,29:49,30:61,32:62,34:r,36:n,37:i,38:u,39:d,40:f,41:y,43:23,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt},{12:[1,118],33:[1,117]},{35:119,75:81,76:k,77:A,79:C,80:w},{35:120,75:81,76:k,77:A,79:C,80:w},{35:121,75:81,76:k,77:A,79:C,80:w},{35:122,75:81,76:k,77:A,79:C,80:w},{35:123,75:81,76:k,77:A,79:C,80:w},{35:124,75:81,76:k,77:A,79:C,80:w},{35:125,75:81,76:k,77:A,79:C,80:w},{14:[1,126]},{14:[1,127]},{14:[1,128]},{14:[1,129]},{1:[2,8]},e(Ct,[2,15]),e(Qt,[2,17],{21:22,19:130,22:t,23:s,24:o,26:l,28:a}),e(Ct,[2,37],{19:20,20:21,21:22,43:23,29:49,30:61,32:62,13:131,22:t,23:s,24:o,26:l,28:a,34:r,36:n,37:i,38:u,39:d,40:f,41:y,44:E,45:O,46:S,47:P,48:M,49:U,50:H,51:q,52:G,53:K,54:J,55:Z,56:$,57:tt,58:et,59:at,60:it,61:nt,62:rt,63:st,64:lt,65:ot,66:ct,67:ht,68:ut,69:dt,70:ft,71:pt,72:yt,73:gt,74:bt}),e(St,[2,21]),e(St,[2,22]),e(T,[2,39]),e(le,[2,71],{75:81,35:132,76:k,77:A,79:C,80:w}),e(Mt,[2,73]),{78:[1,133]},e(Mt,[2,75]),e(Mt,[2,76]),e(T,[2,40]),e(T,[2,41]),e(T,[2,42]),e(T,[2,43]),e(T,[2,44]),e(T,[2,45]),e(T,[2,46]),e(T,[2,47]),e(T,[2,48]),e(T,[2,49]),e(T,[2,50]),e(T,[2,51]),e(T,[2,52]),e(T,[2,53]),e(T,[2,54]),e(T,[2,55]),e(T,[2,56]),e(T,[2,57]),e(T,[2,58]),e(T,[2,60]),e(T,[2,61]),e(T,[2,62]),e(T,[2,63]),e(T,[2,64]),e(T,[2,65]),e(T,[2,66]),e(T,[2,67]),e(T,[2,68]),e(T,[2,69]),e(T,[2,70]),{31:134,42:[1,135]},{12:[1,136]},{33:[1,137]},e(vt,[2,28]),e(vt,[2,29]),e(vt,[2,30]),e(vt,[2,31]),e(vt,[2,32]),e(vt,[2,33]),e(vt,[2,34]),{1:[2,9]},{1:[2,10]},{1:[2,11]},{1:[2,12]},e(Qt,[2,18]),e(Ct,[2,38]),e(le,[2,72]),e(Mt,[2,74]),e(T,[2,24]),e(T,[2,35]),e(Ht,[2,25]),e(Ht,[2,26],{12:[1,138]}),e(Ht,[2,27])],defaultActions:{2:[2,1],3:[2,2],4:[2,7],5:[2,3],6:[2,4],7:[2,5],8:[2,6],74:[2,8],126:[2,9],127:[2,10],128:[2,11],129:[2,12]},parseError:g(function(x,m){if(m.recoverable)this.trace(x);else{var v=new Error(x);throw v.hash=m,v}},"parseError"),parse:g(function(x){var m=this,v=[0],b=[],R=[null],h=[],Dt=this.table,p="",Et=0,oe=0,we=2,ce=1,Te=h.slice.call(arguments,1),D=Object.create(this.lexer),kt={yy:{}};for(var Gt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Gt)&&(kt.yy[Gt]=this.yy[Gt]);D.setInput(x,kt.yy),kt.yy.lexer=D,kt.yy.parser=this,typeof D.yylloc>"u"&&(D.yylloc={});var Kt=D.yylloc;h.push(Kt);var Oe=D.options&&D.options.ranges;typeof kt.yy.parseError=="function"?this.parseError=kt.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Re(L){v.length=v.length-2*L,R.length=R.length-L,h.length=h.length-L}g(Re,"popStack");function he(){var L;return L=b.pop()||D.lex()||ce,typeof L!="number"&&(L instanceof Array&&(b=L,L=b.pop()),L=m.symbols_[L]||L),L}g(he,"lex");for(var I,At,N,Jt,wt={},Nt,W,ue,Yt;;){if(At=v[v.length-1],this.defaultActions[At]?N=this.defaultActions[At]:((I===null||typeof I>"u")&&(I=he()),N=Dt[At]&&Dt[At][I]),typeof N>"u"||!N.length||!N[0]){var Zt="";Yt=[];for(Nt in Dt[At])this.terminals_[Nt]&&Nt>we&&Yt.push("'"+this.terminals_[Nt]+"'");D.showPosition?Zt="Parse error on line "+(Et+1)+`: +`+D.showPosition()+` +Expecting `+Yt.join(", ")+", got '"+(this.terminals_[I]||I)+"'":Zt="Parse error on line "+(Et+1)+": Unexpected "+(I==ce?"end of input":"'"+(this.terminals_[I]||I)+"'"),this.parseError(Zt,{text:D.match,token:this.terminals_[I]||I,line:D.yylineno,loc:Kt,expected:Yt})}if(N[0]instanceof Array&&N.length>1)throw new Error("Parse Error: multiple actions possible at state: "+At+", token: "+I);switch(N[0]){case 1:v.push(I),R.push(D.yytext),h.push(D.yylloc),v.push(N[1]),I=null,oe=D.yyleng,p=D.yytext,Et=D.yylineno,Kt=D.yylloc;break;case 2:if(W=this.productions_[N[1]][1],wt.$=R[R.length-W],wt._$={first_line:h[h.length-(W||1)].first_line,last_line:h[h.length-1].last_line,first_column:h[h.length-(W||1)].first_column,last_column:h[h.length-1].last_column},Oe&&(wt._$.range=[h[h.length-(W||1)].range[0],h[h.length-1].range[1]]),Jt=this.performAction.apply(wt,[p,oe,Et,kt.yy,N[1],R,h].concat(Te)),typeof Jt<"u")return Jt;W&&(v=v.slice(0,-1*W*2),R=R.slice(0,-1*W),h=h.slice(0,-1*W)),v.push(this.productions_[N[1]][0]),R.push(wt.$),h.push(wt._$),ue=Dt[v[v.length-2]][v[v.length-1]],v.push(ue);break;case 3:return!0}}return!0},"parse")},Ce=function(){var _t={EOF:1,parseError:g(function(m,v){if(this.yy.parser)this.yy.parser.parseError(m,v);else throw new Error(m)},"parseError"),setInput:g(function(x,m){return this.yy=m||this.yy||{},this._input=x,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:g(function(){var x=this._input[0];this.yytext+=x,this.yyleng++,this.offset++,this.match+=x,this.matched+=x;var m=x.match(/(?:\r\n?|\n).*/g);return m?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),x},"input"),unput:g(function(x){var m=x.length,v=x.split(/(?:\r\n?|\n)/g);this._input=x+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-m),this.offset-=m;var b=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),v.length-1&&(this.yylineno-=v.length-1);var R=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:v?(v.length===b.length?this.yylloc.first_column:0)+b[b.length-v.length].length-v[0].length:this.yylloc.first_column-m},this.options.ranges&&(this.yylloc.range=[R[0],R[0]+this.yyleng-m]),this.yyleng=this.yytext.length,this},"unput"),more:g(function(){return this._more=!0,this},"more"),reject:g(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:g(function(x){this.unput(this.match.slice(x))},"less"),pastInput:g(function(){var x=this.matched.substr(0,this.matched.length-this.match.length);return(x.length>20?"...":"")+x.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:g(function(){var x=this.match;return x.length<20&&(x+=this._input.substr(0,20-x.length)),(x.substr(0,20)+(x.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:g(function(){var x=this.pastInput(),m=new Array(x.length+1).join("-");return x+this.upcomingInput()+` +`+m+"^"},"showPosition"),test_match:g(function(x,m){var v,b,R;if(this.options.backtrack_lexer&&(R={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(R.yylloc.range=this.yylloc.range.slice(0))),b=x[0].match(/(?:\r\n?|\n).*/g),b&&(this.yylineno+=b.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:b?b[b.length-1].length-b[b.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+x[0].length},this.yytext+=x[0],this.match+=x[0],this.matches=x,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(x[0].length),this.matched+=x[0],v=this.performAction.call(this,this.yy,this,m,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),v)return v;if(this._backtrack){for(var h in R)this[h]=R[h];return!1}return!1},"test_match"),next:g(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var x,m,v,b;this._more||(this.yytext="",this.match="");for(var R=this._currentRules(),h=0;hm[0].length)){if(m=v,b=h,this.options.backtrack_lexer){if(x=this.test_match(v,R[h]),x!==!1)return x;if(this._backtrack){m=!1;continue}else return!1}else if(!this.options.flex)break}return m?(x=this.test_match(m,R[b]),x!==!1?x:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:g(function(){var m=this.next();return m||this.lex()},"lex"),begin:g(function(m){this.conditionStack.push(m)},"begin"),popState:g(function(){var m=this.conditionStack.length-1;return m>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:g(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:g(function(m){return m=this.conditionStack.length-1-Math.abs(m||0),m>=0?this.conditionStack[m]:"INITIAL"},"topState"),pushState:g(function(m){this.begin(m)},"pushState"),stateStackSize:g(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:g(function(m,v,b,R){switch(b){case 0:return 6;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 22;case 5:return 23;case 6:return this.begin("acc_title"),24;case 7:return this.popState(),"acc_title_value";case 8:return this.begin("acc_descr"),26;case 9:return this.popState(),"acc_descr_value";case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:break;case 14:c;break;case 15:return 12;case 16:break;case 17:return 11;case 18:return 15;case 19:return 16;case 20:return 17;case 21:return 18;case 22:return this.begin("person_ext"),45;case 23:return this.begin("person"),44;case 24:return this.begin("system_ext_queue"),51;case 25:return this.begin("system_ext_db"),50;case 26:return this.begin("system_ext"),49;case 27:return this.begin("system_queue"),48;case 28:return this.begin("system_db"),47;case 29:return this.begin("system"),46;case 30:return this.begin("boundary"),37;case 31:return this.begin("enterprise_boundary"),34;case 32:return this.begin("system_boundary"),36;case 33:return this.begin("container_ext_queue"),57;case 34:return this.begin("container_ext_db"),56;case 35:return this.begin("container_ext"),55;case 36:return this.begin("container_queue"),54;case 37:return this.begin("container_db"),53;case 38:return this.begin("container"),52;case 39:return this.begin("container_boundary"),38;case 40:return this.begin("component_ext_queue"),63;case 41:return this.begin("component_ext_db"),62;case 42:return this.begin("component_ext"),61;case 43:return this.begin("component_queue"),60;case 44:return this.begin("component_db"),59;case 45:return this.begin("component"),58;case 46:return this.begin("node"),39;case 47:return this.begin("node"),39;case 48:return this.begin("node_l"),40;case 49:return this.begin("node_r"),41;case 50:return this.begin("rel"),64;case 51:return this.begin("birel"),65;case 52:return this.begin("rel_u"),66;case 53:return this.begin("rel_u"),66;case 54:return this.begin("rel_d"),67;case 55:return this.begin("rel_d"),67;case 56:return this.begin("rel_l"),68;case 57:return this.begin("rel_l"),68;case 58:return this.begin("rel_r"),69;case 59:return this.begin("rel_r"),69;case 60:return this.begin("rel_b"),70;case 61:return this.begin("rel_index"),71;case 62:return this.begin("update_el_style"),72;case 63:return this.begin("update_rel_style"),73;case 64:return this.begin("update_layout_config"),74;case 65:return"EOF_IN_STRUCT";case 66:return this.begin("attribute"),"ATTRIBUTE_EMPTY";case 67:this.begin("attribute");break;case 68:this.popState(),this.popState();break;case 69:return 80;case 70:break;case 71:return 80;case 72:this.begin("string");break;case 73:this.popState();break;case 74:return"STR";case 75:this.begin("string_kv");break;case 76:return this.begin("string_kv_key"),"STR_KEY";case 77:this.popState(),this.begin("string_kv_value");break;case 78:return"STR_VALUE";case 79:this.popState(),this.popState();break;case 80:return"STR";case 81:return"LBRACE";case 82:return"RBRACE";case 83:return"SPACE";case 84:return"EOL";case 85:return 14}},"anonymous"),rules:[/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:title\s[^#\n;]+)/,/^(?:accDescription\s[^#\n;]+)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:C4Context\b)/,/^(?:C4Container\b)/,/^(?:C4Component\b)/,/^(?:C4Dynamic\b)/,/^(?:C4Deployment\b)/,/^(?:Person_Ext\b)/,/^(?:Person\b)/,/^(?:SystemQueue_Ext\b)/,/^(?:SystemDb_Ext\b)/,/^(?:System_Ext\b)/,/^(?:SystemQueue\b)/,/^(?:SystemDb\b)/,/^(?:System\b)/,/^(?:Boundary\b)/,/^(?:Enterprise_Boundary\b)/,/^(?:System_Boundary\b)/,/^(?:ContainerQueue_Ext\b)/,/^(?:ContainerDb_Ext\b)/,/^(?:Container_Ext\b)/,/^(?:ContainerQueue\b)/,/^(?:ContainerDb\b)/,/^(?:Container\b)/,/^(?:Container_Boundary\b)/,/^(?:ComponentQueue_Ext\b)/,/^(?:ComponentDb_Ext\b)/,/^(?:Component_Ext\b)/,/^(?:ComponentQueue\b)/,/^(?:ComponentDb\b)/,/^(?:Component\b)/,/^(?:Deployment_Node\b)/,/^(?:Node\b)/,/^(?:Node_L\b)/,/^(?:Node_R\b)/,/^(?:Rel\b)/,/^(?:BiRel\b)/,/^(?:Rel_Up\b)/,/^(?:Rel_U\b)/,/^(?:Rel_Down\b)/,/^(?:Rel_D\b)/,/^(?:Rel_Left\b)/,/^(?:Rel_L\b)/,/^(?:Rel_Right\b)/,/^(?:Rel_R\b)/,/^(?:Rel_Back\b)/,/^(?:RelIndex\b)/,/^(?:UpdateElementStyle\b)/,/^(?:UpdateRelStyle\b)/,/^(?:UpdateLayoutConfig\b)/,/^(?:$)/,/^(?:[(][ ]*[,])/,/^(?:[(])/,/^(?:[)])/,/^(?:,,)/,/^(?:,)/,/^(?:[ ]*["]["])/,/^(?:[ ]*["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[ ]*[\$])/,/^(?:[^=]*)/,/^(?:[=][ ]*["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:[^,]+)/,/^(?:\{)/,/^(?:\})/,/^(?:[\s]+)/,/^(?:[\n\r]+)/,/^(?:$)/],conditions:{acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},string_kv_value:{rules:[78,79],inclusive:!1},string_kv_key:{rules:[77],inclusive:!1},string_kv:{rules:[76],inclusive:!1},string:{rules:[73,74],inclusive:!1},attribute:{rules:[68,69,70,71,72,75,80],inclusive:!1},update_layout_config:{rules:[65,66,67,68],inclusive:!1},update_rel_style:{rules:[65,66,67,68],inclusive:!1},update_el_style:{rules:[65,66,67,68],inclusive:!1},rel_b:{rules:[65,66,67,68],inclusive:!1},rel_r:{rules:[65,66,67,68],inclusive:!1},rel_l:{rules:[65,66,67,68],inclusive:!1},rel_d:{rules:[65,66,67,68],inclusive:!1},rel_u:{rules:[65,66,67,68],inclusive:!1},rel_bi:{rules:[],inclusive:!1},rel:{rules:[65,66,67,68],inclusive:!1},node_r:{rules:[65,66,67,68],inclusive:!1},node_l:{rules:[65,66,67,68],inclusive:!1},node:{rules:[65,66,67,68],inclusive:!1},index:{rules:[],inclusive:!1},rel_index:{rules:[65,66,67,68],inclusive:!1},component_ext_queue:{rules:[],inclusive:!1},component_ext_db:{rules:[65,66,67,68],inclusive:!1},component_ext:{rules:[65,66,67,68],inclusive:!1},component_queue:{rules:[65,66,67,68],inclusive:!1},component_db:{rules:[65,66,67,68],inclusive:!1},component:{rules:[65,66,67,68],inclusive:!1},container_boundary:{rules:[65,66,67,68],inclusive:!1},container_ext_queue:{rules:[65,66,67,68],inclusive:!1},container_ext_db:{rules:[65,66,67,68],inclusive:!1},container_ext:{rules:[65,66,67,68],inclusive:!1},container_queue:{rules:[65,66,67,68],inclusive:!1},container_db:{rules:[65,66,67,68],inclusive:!1},container:{rules:[65,66,67,68],inclusive:!1},birel:{rules:[65,66,67,68],inclusive:!1},system_boundary:{rules:[65,66,67,68],inclusive:!1},enterprise_boundary:{rules:[65,66,67,68],inclusive:!1},boundary:{rules:[65,66,67,68],inclusive:!1},system_ext_queue:{rules:[65,66,67,68],inclusive:!1},system_ext_db:{rules:[65,66,67,68],inclusive:!1},system_ext:{rules:[65,66,67,68],inclusive:!1},system_queue:{rules:[65,66,67,68],inclusive:!1},system_db:{rules:[65,66,67,68],inclusive:!1},system:{rules:[65,66,67,68],inclusive:!1},person_ext:{rules:[65,66,67,68],inclusive:!1},person:{rules:[65,66,67,68],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,8,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,81,82,83,84,85],inclusive:!0}}};return _t}();qt.lexer=Ce;function Lt(){this.yy={}}return g(Lt,"Parser"),Lt.prototype=qt,qt.Parser=Lt,new Lt}();Ft.parser=Ft;var Ue=Ft,V=[],xt=[""],B="global",F="",X=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],It=[],ie="",ne=!1,Vt=4,zt=2,be,Fe=g(function(){return be},"getC4Type"),Ve=g(function(e){be=ge(e,Bt())},"setC4Type"),ze=g(function(e,t,s,o,l,a,r,n,i){if(e==null||t===void 0||t===null||s===void 0||s===null||o===void 0||o===null)return;let u={};const d=It.find(f=>f.from===t&&f.to===s);if(d?u=d:It.push(u),u.type=e,u.from=t,u.to=s,u.label={text:o},l==null)u.techn={text:""};else if(typeof l=="object"){let[f,y]=Object.entries(l)[0];u[f]={text:y}}else u.techn={text:l};if(a==null)u.descr={text:""};else if(typeof a=="object"){let[f,y]=Object.entries(a)[0];u[f]={text:y}}else u.descr={text:a};if(typeof r=="object"){let[f,y]=Object.entries(r)[0];u[f]=y}else u.sprite=r;if(typeof n=="object"){let[f,y]=Object.entries(n)[0];u[f]=y}else u.tags=n;if(typeof i=="object"){let[f,y]=Object.entries(i)[0];u[f]=y}else u.link=i;u.wrap=mt()},"addRel"),Xe=g(function(e,t,s,o,l,a,r){if(t===null||s===null)return;let n={};const i=V.find(u=>u.alias===t);if(i&&t===i.alias?n=i:(n.alias=t,V.push(n)),s==null?n.label={text:""}:n.label={text:s},o==null)n.descr={text:""};else if(typeof o=="object"){let[u,d]=Object.entries(o)[0];n[u]={text:d}}else n.descr={text:o};if(typeof l=="object"){let[u,d]=Object.entries(l)[0];n[u]=d}else n.sprite=l;if(typeof a=="object"){let[u,d]=Object.entries(a)[0];n[u]=d}else n.tags=a;if(typeof r=="object"){let[u,d]=Object.entries(r)[0];n[u]=d}else n.link=r;n.typeC4Shape={text:e},n.parentBoundary=B,n.wrap=mt()},"addPersonOrSystem"),We=g(function(e,t,s,o,l,a,r,n){if(t===null||s===null)return;let i={};const u=V.find(d=>d.alias===t);if(u&&t===u.alias?i=u:(i.alias=t,V.push(i)),s==null?i.label={text:""}:i.label={text:s},o==null)i.techn={text:""};else if(typeof o=="object"){let[d,f]=Object.entries(o)[0];i[d]={text:f}}else i.techn={text:o};if(l==null)i.descr={text:""};else if(typeof l=="object"){let[d,f]=Object.entries(l)[0];i[d]={text:f}}else i.descr={text:l};if(typeof a=="object"){let[d,f]=Object.entries(a)[0];i[d]=f}else i.sprite=a;if(typeof r=="object"){let[d,f]=Object.entries(r)[0];i[d]=f}else i.tags=r;if(typeof n=="object"){let[d,f]=Object.entries(n)[0];i[d]=f}else i.link=n;i.wrap=mt(),i.typeC4Shape={text:e},i.parentBoundary=B},"addContainer"),Qe=g(function(e,t,s,o,l,a,r,n){if(t===null||s===null)return;let i={};const u=V.find(d=>d.alias===t);if(u&&t===u.alias?i=u:(i.alias=t,V.push(i)),s==null?i.label={text:""}:i.label={text:s},o==null)i.techn={text:""};else if(typeof o=="object"){let[d,f]=Object.entries(o)[0];i[d]={text:f}}else i.techn={text:o};if(l==null)i.descr={text:""};else if(typeof l=="object"){let[d,f]=Object.entries(l)[0];i[d]={text:f}}else i.descr={text:l};if(typeof a=="object"){let[d,f]=Object.entries(a)[0];i[d]=f}else i.sprite=a;if(typeof r=="object"){let[d,f]=Object.entries(r)[0];i[d]=f}else i.tags=r;if(typeof n=="object"){let[d,f]=Object.entries(n)[0];i[d]=f}else i.link=n;i.wrap=mt(),i.typeC4Shape={text:e},i.parentBoundary=B},"addComponent"),He=g(function(e,t,s,o,l){if(e===null||t===null)return;let a={};const r=X.find(n=>n.alias===e);if(r&&e===r.alias?a=r:(a.alias=e,X.push(a)),t==null?a.label={text:""}:a.label={text:t},s==null)a.type={text:"system"};else if(typeof s=="object"){let[n,i]=Object.entries(s)[0];a[n]={text:i}}else a.type={text:s};if(typeof o=="object"){let[n,i]=Object.entries(o)[0];a[n]=i}else a.tags=o;if(typeof l=="object"){let[n,i]=Object.entries(l)[0];a[n]=i}else a.link=l;a.parentBoundary=B,a.wrap=mt(),F=B,B=e,xt.push(F)},"addPersonOrSystemBoundary"),qe=g(function(e,t,s,o,l){if(e===null||t===null)return;let a={};const r=X.find(n=>n.alias===e);if(r&&e===r.alias?a=r:(a.alias=e,X.push(a)),t==null?a.label={text:""}:a.label={text:t},s==null)a.type={text:"container"};else if(typeof s=="object"){let[n,i]=Object.entries(s)[0];a[n]={text:i}}else a.type={text:s};if(typeof o=="object"){let[n,i]=Object.entries(o)[0];a[n]=i}else a.tags=o;if(typeof l=="object"){let[n,i]=Object.entries(l)[0];a[n]=i}else a.link=l;a.parentBoundary=B,a.wrap=mt(),F=B,B=e,xt.push(F)},"addContainerBoundary"),Ge=g(function(e,t,s,o,l,a,r,n){if(t===null||s===null)return;let i={};const u=X.find(d=>d.alias===t);if(u&&t===u.alias?i=u:(i.alias=t,X.push(i)),s==null?i.label={text:""}:i.label={text:s},o==null)i.type={text:"node"};else if(typeof o=="object"){let[d,f]=Object.entries(o)[0];i[d]={text:f}}else i.type={text:o};if(l==null)i.descr={text:""};else if(typeof l=="object"){let[d,f]=Object.entries(l)[0];i[d]={text:f}}else i.descr={text:l};if(typeof r=="object"){let[d,f]=Object.entries(r)[0];i[d]=f}else i.tags=r;if(typeof n=="object"){let[d,f]=Object.entries(n)[0];i[d]=f}else i.link=n;i.nodeType=e,i.parentBoundary=B,i.wrap=mt(),F=B,B=t,xt.push(F)},"addDeploymentNode"),Ke=g(function(){B=F,xt.pop(),F=xt.pop(),xt.push(F)},"popBoundaryParseStack"),Je=g(function(e,t,s,o,l,a,r,n,i,u,d){let f=V.find(y=>y.alias===t);if(!(f===void 0&&(f=X.find(y=>y.alias===t),f===void 0))){if(s!=null)if(typeof s=="object"){let[y,E]=Object.entries(s)[0];f[y]=E}else f.bgColor=s;if(o!=null)if(typeof o=="object"){let[y,E]=Object.entries(o)[0];f[y]=E}else f.fontColor=o;if(l!=null)if(typeof l=="object"){let[y,E]=Object.entries(l)[0];f[y]=E}else f.borderColor=l;if(a!=null)if(typeof a=="object"){let[y,E]=Object.entries(a)[0];f[y]=E}else f.shadowing=a;if(r!=null)if(typeof r=="object"){let[y,E]=Object.entries(r)[0];f[y]=E}else f.shape=r;if(n!=null)if(typeof n=="object"){let[y,E]=Object.entries(n)[0];f[y]=E}else f.sprite=n;if(i!=null)if(typeof i=="object"){let[y,E]=Object.entries(i)[0];f[y]=E}else f.techn=i;if(u!=null)if(typeof u=="object"){let[y,E]=Object.entries(u)[0];f[y]=E}else f.legendText=u;if(d!=null)if(typeof d=="object"){let[y,E]=Object.entries(d)[0];f[y]=E}else f.legendSprite=d}},"updateElStyle"),Ze=g(function(e,t,s,o,l,a,r){const n=It.find(i=>i.from===t&&i.to===s);if(n!==void 0){if(o!=null)if(typeof o=="object"){let[i,u]=Object.entries(o)[0];n[i]=u}else n.textColor=o;if(l!=null)if(typeof l=="object"){let[i,u]=Object.entries(l)[0];n[i]=u}else n.lineColor=l;if(a!=null)if(typeof a=="object"){let[i,u]=Object.entries(a)[0];n[i]=parseInt(u)}else n.offsetX=parseInt(a);if(r!=null)if(typeof r=="object"){let[i,u]=Object.entries(r)[0];n[i]=parseInt(u)}else n.offsetY=parseInt(r)}},"updateRelStyle"),$e=g(function(e,t,s){let o=Vt,l=zt;if(typeof t=="object"){const a=Object.values(t)[0];o=parseInt(a)}else o=parseInt(t);if(typeof s=="object"){const a=Object.values(s)[0];l=parseInt(a)}else l=parseInt(s);o>=1&&(Vt=o),l>=1&&(zt=l)},"updateLayoutConfig"),t0=g(function(){return Vt},"getC4ShapeInRow"),e0=g(function(){return zt},"getC4BoundaryInRow"),a0=g(function(){return B},"getCurrentBoundaryParse"),i0=g(function(){return F},"getParentBoundaryParse"),_e=g(function(e){return e==null?V:V.filter(t=>t.parentBoundary===e)},"getC4ShapeArray"),n0=g(function(e){return V.find(t=>t.alias===e)},"getC4Shape"),r0=g(function(e){return Object.keys(_e(e))},"getC4ShapeKeys"),xe=g(function(e){return e==null?X:X.filter(t=>t.parentBoundary===e)},"getBoundaries"),s0=xe,l0=g(function(){return It},"getRels"),o0=g(function(){return ie},"getTitle"),c0=g(function(e){ne=e},"setWrap"),mt=g(function(){return ne},"autoWrap"),h0=g(function(){V=[],X=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],F="",B="global",xt=[""],It=[],xt=[""],ie="",ne=!1,Vt=4,zt=2},"clear"),u0={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25},d0={FILLED:0,OPEN:1},f0={LEFTOF:0,RIGHTOF:1,OVER:2},p0=g(function(e){ie=ge(e,Bt())},"setTitle"),te={addPersonOrSystem:Xe,addPersonOrSystemBoundary:He,addContainer:We,addContainerBoundary:qe,addComponent:Qe,addDeploymentNode:Ge,popBoundaryParseStack:Ke,addRel:ze,updateElStyle:Je,updateRelStyle:Ze,updateLayoutConfig:$e,autoWrap:mt,setWrap:c0,getC4ShapeArray:_e,getC4Shape:n0,getC4ShapeKeys:r0,getBoundaries:xe,getBoundarys:s0,getCurrentBoundaryParse:a0,getParentBoundaryParse:i0,getRels:l0,getTitle:o0,getC4Type:Fe,getC4ShapeInRow:t0,getC4BoundaryInRow:e0,setAccTitle:Me,getAccTitle:Ie,getAccDescription:Be,setAccDescription:Pe,getConfig:g(()=>Bt().c4,"getConfig"),clear:h0,LINETYPE:u0,ARROWTYPE:d0,PLACEMENT:f0,setTitle:p0,setC4Type:Ve},re=g(function(e,t){return De(e,t)},"drawRect"),me=g(function(e,t,s,o,l,a){const r=e.append("image");r.attr("width",t),r.attr("height",s),r.attr("x",o),r.attr("y",l);let n=a.startsWith("data:image/png;base64")?a:Ye(a);r.attr("xlink:href",n)},"drawImage"),y0=g((e,t,s)=>{const o=e.append("g");let l=0;for(let a of t){let r=a.textColor?a.textColor:"#444444",n=a.lineColor?a.lineColor:"#444444",i=a.offsetX?parseInt(a.offsetX):0,u=a.offsetY?parseInt(a.offsetY):0,d="";if(l===0){let y=o.append("line");y.attr("x1",a.startPoint.x),y.attr("y1",a.startPoint.y),y.attr("x2",a.endPoint.x),y.attr("y2",a.endPoint.y),y.attr("stroke-width","1"),y.attr("stroke",n),y.style("fill","none"),a.type!=="rel_b"&&y.attr("marker-end","url("+d+"#arrowhead)"),(a.type==="birel"||a.type==="rel_b")&&y.attr("marker-start","url("+d+"#arrowend)"),l=-1}else{let y=o.append("path");y.attr("fill","none").attr("stroke-width","1").attr("stroke",n).attr("d","Mstartx,starty Qcontrolx,controly stopx,stopy ".replaceAll("startx",a.startPoint.x).replaceAll("starty",a.startPoint.y).replaceAll("controlx",a.startPoint.x+(a.endPoint.x-a.startPoint.x)/2-(a.endPoint.x-a.startPoint.x)/4).replaceAll("controly",a.startPoint.y+(a.endPoint.y-a.startPoint.y)/2).replaceAll("stopx",a.endPoint.x).replaceAll("stopy",a.endPoint.y)),a.type!=="rel_b"&&y.attr("marker-end","url("+d+"#arrowhead)"),(a.type==="birel"||a.type==="rel_b")&&y.attr("marker-start","url("+d+"#arrowend)")}let f=s.messageFont();Q(s)(a.label.text,o,Math.min(a.startPoint.x,a.endPoint.x)+Math.abs(a.endPoint.x-a.startPoint.x)/2+i,Math.min(a.startPoint.y,a.endPoint.y)+Math.abs(a.endPoint.y-a.startPoint.y)/2+u,a.label.width,a.label.height,{fill:r},f),a.techn&&a.techn.text!==""&&(f=s.messageFont(),Q(s)("["+a.techn.text+"]",o,Math.min(a.startPoint.x,a.endPoint.x)+Math.abs(a.endPoint.x-a.startPoint.x)/2+i,Math.min(a.startPoint.y,a.endPoint.y)+Math.abs(a.endPoint.y-a.startPoint.y)/2+s.messageFontSize+5+u,Math.max(a.label.width,a.techn.width),a.techn.height,{fill:r,"font-style":"italic"},f))}},"drawRels"),g0=g(function(e,t,s){const o=e.append("g");let l=t.bgColor?t.bgColor:"none",a=t.borderColor?t.borderColor:"#444444",r=t.fontColor?t.fontColor:"black",n={"stroke-width":1,"stroke-dasharray":"7.0,7.0"};t.nodeType&&(n={"stroke-width":1});let i={x:t.x,y:t.y,fill:l,stroke:a,width:t.width,height:t.height,rx:2.5,ry:2.5,attrs:n};re(o,i);let u=s.boundaryFont();u.fontWeight="bold",u.fontSize=u.fontSize+2,u.fontColor=r,Q(s)(t.label.text,o,t.x,t.y+t.label.Y,t.width,t.height,{fill:"#444444"},u),t.type&&t.type.text!==""&&(u=s.boundaryFont(),u.fontColor=r,Q(s)(t.type.text,o,t.x,t.y+t.type.Y,t.width,t.height,{fill:"#444444"},u)),t.descr&&t.descr.text!==""&&(u=s.boundaryFont(),u.fontSize=u.fontSize-2,u.fontColor=r,Q(s)(t.descr.text,o,t.x,t.y+t.descr.Y,t.width,t.height,{fill:"#444444"},u))},"drawBoundary"),b0=g(function(e,t,s){var f;let o=t.bgColor?t.bgColor:s[t.typeC4Shape.text+"_bg_color"],l=t.borderColor?t.borderColor:s[t.typeC4Shape.text+"_border_color"],a=t.fontColor?t.fontColor:"#FFFFFF",r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";switch(t.typeC4Shape.text){case"person":r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";break;case"external_person":r="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAB6ElEQVR4Xu2YLY+EMBCG9+dWr0aj0Wg0Go1Go0+j8Xdv2uTCvv1gpt0ebHKPuhDaeW4605Z9mJvx4AdXUyTUdd08z+u6flmWZRnHsWkafk9DptAwDPu+f0eAYtu2PEaGWuj5fCIZrBAC2eLBAnRCsEkkxmeaJp7iDJ2QMDdHsLg8SxKFEJaAo8lAXnmuOFIhTMpxxKATebo4UiFknuNo4OniSIXQyRxEA3YsnjGCVEjVXD7yLUAqxBGUyPv/Y4W2beMgGuS7kVQIBycH0fD+oi5pezQETxdHKmQKGk1eQEYldK+jw5GxPfZ9z7Mk0Qnhf1W1m3w//EUn5BDmSZsbR44QQLBEqrBHqOrmSKaQAxdnLArCrxZcM7A7ZKs4ioRq8LFC+NpC3WCBJsvpVw5edm9iEXFuyNfxXAgSwfrFQ1c0iNda8AdejvUgnktOtJQQxmcfFzGglc5WVCj7oDgFqU18boeFSs52CUh8LE8BIVQDT1ABrB0HtgSEYlX5doJnCwv9TXocKCaKbnwhdDKPq4lf3SwU3HLq4V/+WYhHVMa/3b4IlfyikAduCkcBc7mQ3/z/Qq/cTuikhkzB12Ae/mcJC9U+Vo8Ej1gWAtgbeGgFsAMHr50BIWOLCbezvhpBFUdY6EJuJ/QDW0XoMX60zZ0AAAAASUVORK5CYII=";break}const n=e.append("g");n.attr("class","person-man");const i=Se();switch(t.typeC4Shape.text){case"person":case"external_person":case"system":case"external_system":case"container":case"external_container":case"component":case"external_component":i.x=t.x,i.y=t.y,i.fill=o,i.width=t.width,i.height=t.height,i.stroke=l,i.rx=2.5,i.ry=2.5,i.attrs={"stroke-width":.5},re(n,i);break;case"system_db":case"external_system_db":case"container_db":case"external_container_db":case"component_db":case"external_component_db":n.append("path").attr("fill",o).attr("stroke-width","0.5").attr("stroke",l).attr("d","Mstartx,startyc0,-10 half,-10 half,-10c0,0 half,0 half,10l0,heightc0,10 -half,10 -half,10c0,0 -half,0 -half,-10l0,-height".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("half",t.width/2).replaceAll("height",t.height)),n.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",l).attr("d","Mstartx,startyc0,10 half,10 half,10c0,0 half,0 half,-10".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("half",t.width/2));break;case"system_queue":case"external_system_queue":case"container_queue":case"external_container_queue":case"component_queue":case"external_component_queue":n.append("path").attr("fill",o).attr("stroke-width","0.5").attr("stroke",l).attr("d","Mstartx,startylwidth,0c5,0 5,half 5,halfc0,0 0,half -5,halfl-width,0c-5,0 -5,-half -5,-halfc0,0 0,-half 5,-half".replaceAll("startx",t.x).replaceAll("starty",t.y).replaceAll("width",t.width).replaceAll("half",t.height/2)),n.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",l).attr("d","Mstartx,startyc-5,0 -5,half -5,halfc0,half 5,half 5,half".replaceAll("startx",t.x+t.width).replaceAll("starty",t.y).replaceAll("half",t.height/2));break}let u=w0(s,t.typeC4Shape.text);switch(n.append("text").attr("fill",a).attr("font-family",u.fontFamily).attr("font-size",u.fontSize-2).attr("font-style","italic").attr("lengthAdjust","spacing").attr("textLength",t.typeC4Shape.width).attr("x",t.x+t.width/2-t.typeC4Shape.width/2).attr("y",t.y+t.typeC4Shape.Y).text("<<"+t.typeC4Shape.text+">>"),t.typeC4Shape.text){case"person":case"external_person":me(n,48,48,t.x+t.width/2-24,t.y+t.image.Y,r);break}let d=s[t.typeC4Shape.text+"Font"]();return d.fontWeight="bold",d.fontSize=d.fontSize+2,d.fontColor=a,Q(s)(t.label.text,n,t.x,t.y+t.label.Y,t.width,t.height,{fill:a},d),d=s[t.typeC4Shape.text+"Font"](),d.fontColor=a,t.techn&&((f=t.techn)==null?void 0:f.text)!==""?Q(s)(t.techn.text,n,t.x,t.y+t.techn.Y,t.width,t.height,{fill:a,"font-style":"italic"},d):t.type&&t.type.text!==""&&Q(s)(t.type.text,n,t.x,t.y+t.type.Y,t.width,t.height,{fill:a,"font-style":"italic"},d),t.descr&&t.descr.text!==""&&(d=s.personFont(),d.fontColor=a,Q(s)(t.descr.text,n,t.x,t.y+t.descr.Y,t.width,t.height,{fill:a},d)),t.height},"drawC4Shape"),_0=g(function(e){e.append("defs").append("symbol").attr("id","database").attr("fill-rule","evenodd").attr("clip-rule","evenodd").append("path").attr("transform","scale(.5)").attr("d","M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z")},"insertDatabaseIcon"),x0=g(function(e){e.append("defs").append("symbol").attr("id","computer").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z")},"insertComputerIcon"),m0=g(function(e){e.append("defs").append("symbol").attr("id","clock").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z")},"insertClockIcon"),v0=g(function(e){e.append("defs").append("marker").attr("id","arrowhead").attr("refX",9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z")},"insertArrowHead"),E0=g(function(e){e.append("defs").append("marker").attr("id","arrowend").attr("refX",1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 10 0 L 0 5 L 10 10 z")},"insertArrowEnd"),k0=g(function(e){e.append("defs").append("marker").attr("id","filled-head").attr("refX",18).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"insertArrowFilledHead"),A0=g(function(e){e.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},"insertDynamicNumber"),C0=g(function(e){const s=e.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",16).attr("refY",4);s.append("path").attr("fill","black").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 9,2 V 6 L16,4 Z"),s.append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 0,1 L 6,7 M 6,1 L 0,7")},"insertArrowCrossHead"),w0=g((e,t)=>({fontFamily:e[t+"FontFamily"],fontSize:e[t+"FontSize"],fontWeight:e[t+"FontWeight"]}),"getC4ShapeFont"),Q=function(){function e(l,a,r,n,i,u,d){const f=a.append("text").attr("x",r+i/2).attr("y",n+u/2+5).style("text-anchor","middle").text(l);o(f,d)}g(e,"byText");function t(l,a,r,n,i,u,d,f){const{fontSize:y,fontFamily:E,fontWeight:O}=f,S=l.split($t.lineBreakRegex);for(let P=0;P=this.data.widthLimit||o>=this.data.widthLimit||this.nextData.cnt>ve)&&(s=this.nextData.startx+t.margin+_.nextLinePaddingX,l=this.nextData.stopy+t.margin*2,this.nextData.stopx=o=s+t.width,this.nextData.starty=this.nextData.stopy,this.nextData.stopy=a=l+t.height,this.nextData.cnt=1),t.x=s,t.y=l,this.updateVal(this.data,"startx",s,Math.min),this.updateVal(this.data,"starty",l,Math.min),this.updateVal(this.data,"stopx",o,Math.max),this.updateVal(this.data,"stopy",a,Math.max),this.updateVal(this.nextData,"startx",s,Math.min),this.updateVal(this.nextData,"starty",l,Math.min),this.updateVal(this.nextData,"stopx",o,Math.max),this.updateVal(this.nextData,"stopy",a,Math.max)}init(t){this.name="",this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,widthLimit:void 0},this.nextData={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,cnt:0},ae(t.db.getConfig())}bumpLastMargin(t){this.data.stopx+=t,this.data.stopy+=t}},g(Ot,"Bounds"),Ot),ae=g(function(e){Ne(_,e),e.fontFamily&&(_.personFontFamily=_.systemFontFamily=_.messageFontFamily=e.fontFamily),e.fontSize&&(_.personFontSize=_.systemFontSize=_.messageFontSize=e.fontSize),e.fontWeight&&(_.personFontWeight=_.systemFontWeight=_.messageFontWeight=e.fontWeight)},"setConf"),Pt=g((e,t)=>({fontFamily:e[t+"FontFamily"],fontSize:e[t+"FontSize"],fontWeight:e[t+"FontWeight"]}),"c4ShapeFont"),Ut=g(e=>({fontFamily:e.boundaryFontFamily,fontSize:e.boundaryFontSize,fontWeight:e.boundaryFontWeight}),"boundaryFont"),T0=g(e=>({fontFamily:e.messageFontFamily,fontSize:e.messageFontSize,fontWeight:e.messageFontWeight}),"messageFont");function j(e,t,s,o,l){if(!t[e].width)if(s)t[e].text=je(t[e].text,l,o),t[e].textLines=t[e].text.split($t.lineBreakRegex).length,t[e].width=l,t[e].height=fe(t[e].text,o);else{let a=t[e].text.split($t.lineBreakRegex);t[e].textLines=a.length;let r=0;t[e].height=0,t[e].width=0;for(const n of a)t[e].width=Math.max(Tt(n,o),t[e].width),r=fe(n,o),t[e].height=t[e].height+r}}g(j,"calcC4ShapeTextWH");var ke=g(function(e,t,s){t.x=s.data.startx,t.y=s.data.starty,t.width=s.data.stopx-s.data.startx,t.height=s.data.stopy-s.data.starty,t.label.y=_.c4ShapeMargin-35;let o=t.wrap&&_.wrap,l=Ut(_);l.fontSize=l.fontSize+2,l.fontWeight="bold";let a=Tt(t.label.text,l);j("label",t,o,l,a),z.drawBoundary(e,t,_)},"drawBoundary"),Ae=g(function(e,t,s,o){let l=0;for(const a of o){l=0;const r=s[a];let n=Pt(_,r.typeC4Shape.text);switch(n.fontSize=n.fontSize-2,r.typeC4Shape.width=Tt("«"+r.typeC4Shape.text+"»",n),r.typeC4Shape.height=n.fontSize+2,r.typeC4Shape.Y=_.c4ShapePadding,l=r.typeC4Shape.Y+r.typeC4Shape.height-4,r.image={width:0,height:0,Y:0},r.typeC4Shape.text){case"person":case"external_person":r.image.width=48,r.image.height=48,r.image.Y=l,l=r.image.Y+r.image.height;break}r.sprite&&(r.image.width=48,r.image.height=48,r.image.Y=l,l=r.image.Y+r.image.height);let i=r.wrap&&_.wrap,u=_.width-_.c4ShapePadding*2,d=Pt(_,r.typeC4Shape.text);if(d.fontSize=d.fontSize+2,d.fontWeight="bold",j("label",r,i,d,u),r.label.Y=l+8,l=r.label.Y+r.label.height,r.type&&r.type.text!==""){r.type.text="["+r.type.text+"]";let E=Pt(_,r.typeC4Shape.text);j("type",r,i,E,u),r.type.Y=l+5,l=r.type.Y+r.type.height}else if(r.techn&&r.techn.text!==""){r.techn.text="["+r.techn.text+"]";let E=Pt(_,r.techn.text);j("techn",r,i,E,u),r.techn.Y=l+5,l=r.techn.Y+r.techn.height}let f=l,y=r.label.width;if(r.descr&&r.descr.text!==""){let E=Pt(_,r.typeC4Shape.text);j("descr",r,i,E,u),r.descr.Y=l+20,l=r.descr.Y+r.descr.height,y=Math.max(r.label.width,r.descr.width),f=l-r.descr.textLines*5}y=y+_.c4ShapePadding,r.width=Math.max(r.width||_.width,y,_.width),r.height=Math.max(r.height||_.height,f,_.height),r.margin=r.margin||_.c4ShapeMargin,e.insert(r),z.drawC4Shape(t,r,_)}e.bumpLastMargin(_.c4ShapeMargin)},"drawC4ShapeArray"),Rt,Y=(Rt=class{constructor(t,s){this.x=t,this.y=s}},g(Rt,"Point"),Rt),pe=g(function(e,t){let s=e.x,o=e.y,l=t.x,a=t.y,r=s+e.width/2,n=o+e.height/2,i=Math.abs(s-l),u=Math.abs(o-a),d=u/i,f=e.height/e.width,y=null;return o==a&&sl?y=new Y(s,n):s==l&&oa&&(y=new Y(r,o)),s>l&&o=d?y=new Y(s,n+d*e.width/2):y=new Y(r-i/u*e.height/2,o+e.height):s=d?y=new Y(s+e.width,n+d*e.width/2):y=new Y(r+i/u*e.height/2,o+e.height):sa?f>=d?y=new Y(s+e.width,n-d*e.width/2):y=new Y(r+e.height/2*i/u,o):s>l&&o>a&&(f>=d?y=new Y(s,n-e.width/2*d):y=new Y(r-e.height/2*i/u,o)),y},"getIntersectPoint"),O0=g(function(e,t){let s={x:0,y:0};s.x=t.x+t.width/2,s.y=t.y+t.height/2;let o=pe(e,s);s.x=e.x+e.width/2,s.y=e.y+e.height/2;let l=pe(t,s);return{startPoint:o,endPoint:l}},"getIntersectPoints"),R0=g(function(e,t,s,o){let l=0;for(let a of t){l=l+1;let r=a.wrap&&_.wrap,n=T0(_);o.db.getC4Type()==="C4Dynamic"&&(a.label.text=l+": "+a.label.text);let u=Tt(a.label.text,n);j("label",a,r,n,u),a.techn&&a.techn.text!==""&&(u=Tt(a.techn.text,n),j("techn",a,r,n,u)),a.descr&&a.descr.text!==""&&(u=Tt(a.descr.text,n),j("descr",a,r,n,u));let d=s(a.from),f=s(a.to),y=O0(d,f);a.startPoint=y.startPoint,a.endPoint=y.endPoint}z.drawRels(e,t,_)},"drawRels");function se(e,t,s,o,l){let a=new Ee(l);a.data.widthLimit=s.data.widthLimit/Math.min(ee,o.length);for(let[r,n]of o.entries()){let i=0;n.image={width:0,height:0,Y:0},n.sprite&&(n.image.width=48,n.image.height=48,n.image.Y=i,i=n.image.Y+n.image.height);let u=n.wrap&&_.wrap,d=Ut(_);if(d.fontSize=d.fontSize+2,d.fontWeight="bold",j("label",n,u,d,a.data.widthLimit),n.label.Y=i+8,i=n.label.Y+n.label.height,n.type&&n.type.text!==""){n.type.text="["+n.type.text+"]";let O=Ut(_);j("type",n,u,O,a.data.widthLimit),n.type.Y=i+5,i=n.type.Y+n.type.height}if(n.descr&&n.descr.text!==""){let O=Ut(_);O.fontSize=O.fontSize-2,j("descr",n,u,O,a.data.widthLimit),n.descr.Y=i+20,i=n.descr.Y+n.descr.height}if(r==0||r%ee===0){let O=s.data.startx+_.diagramMarginX,S=s.data.stopy+_.diagramMarginY+i;a.setData(O,O,S,S)}else{let O=a.data.stopx!==a.data.startx?a.data.stopx+_.diagramMarginX:a.data.startx,S=a.data.starty;a.setData(O,O,S,S)}a.name=n.alias;let f=l.db.getC4ShapeArray(n.alias),y=l.db.getC4ShapeKeys(n.alias);y.length>0&&Ae(a,e,f,y),t=n.alias;let E=l.db.getBoundaries(t);E.length>0&&se(e,t,a,E,l),n.alias!=="global"&&ke(e,n,a),s.data.stopy=Math.max(a.data.stopy+_.c4ShapeMargin,s.data.stopy),s.data.stopx=Math.max(a.data.stopx+_.c4ShapeMargin,s.data.stopx),Xt=Math.max(Xt,s.data.stopx),Wt=Math.max(Wt,s.data.stopy)}}g(se,"drawInsideBoundary");var S0=g(function(e,t,s,o){_=Bt().c4;const l=Bt().securityLevel;let a;l==="sandbox"&&(a=jt("#i"+t));const r=l==="sandbox"?jt(a.nodes()[0].contentDocument.body):jt("body");let n=o.db;o.db.setWrap(_.wrap),ve=n.getC4ShapeInRow(),ee=n.getC4BoundaryInRow(),de.debug(`C:${JSON.stringify(_,null,2)}`);const i=l==="sandbox"?r.select(`[id="${t}"]`):jt(`[id="${t}"]`);z.insertComputerIcon(i),z.insertDatabaseIcon(i),z.insertClockIcon(i);let u=new Ee(o);u.setData(_.diagramMarginX,_.diagramMarginX,_.diagramMarginY,_.diagramMarginY),u.data.widthLimit=screen.availWidth,Xt=_.diagramMarginX,Wt=_.diagramMarginY;const d=o.db.getTitle();let f=o.db.getBoundaries("");se(i,"",u,f,o),z.insertArrowHead(i),z.insertArrowEnd(i),z.insertArrowCrossHead(i),z.insertArrowFilledHead(i),R0(i,o.db.getRels(),o.db.getC4Shape,o),u.data.stopx=Xt,u.data.stopy=Wt;const y=u.data;let O=y.stopy-y.starty+2*_.diagramMarginY;const P=y.stopx-y.startx+2*_.diagramMarginX;d&&i.append("text").text(d).attr("x",(y.stopx-y.startx)/2-4*_.diagramMarginX).attr("y",y.starty+_.diagramMarginY),Le(i,O,P,_.useMaxWidth);const M=d?60:0;i.attr("viewBox",y.startx-_.diagramMarginX+" -"+(_.diagramMarginY+M)+" "+P+" "+(O+M)),de.debug("models:",y)},"draw"),ye={drawPersonOrSystemArray:Ae,drawBoundary:ke,setConf:ae,draw:S0},D0=g(e=>`.person { + stroke: ${e.personBorder}; + fill: ${e.personBkg}; + } +`,"getStyles"),P0=D0,L0={parser:Ue,db:te,renderer:ye,styles:P0,init:g(({c4:e,wrap:t})=>{ye.setConf(e),te.setWrap(t)},"init")};export{L0 as diagram}; diff --git a/assets/chunks/chunk-4BX2VUAB.B6a8mhSC.js b/assets/chunks/chunk-4BX2VUAB.B6a8mhSC.js new file mode 100644 index 000000000..4789eda56 --- /dev/null +++ b/assets/chunks/chunk-4BX2VUAB.B6a8mhSC.js @@ -0,0 +1 @@ +import{_ as l}from"./theme.kqgpP4eL.js";function m(e,c){var i,t,o;e.accDescr&&((i=c.setAccDescription)==null||i.call(c,e.accDescr)),e.accTitle&&((t=c.setAccTitle)==null||t.call(c,e.accTitle)),e.title&&((o=c.setDiagramTitle)==null||o.call(c,e.title))}l(m,"populateCommonDb");export{m as p}; diff --git a/assets/chunks/chunk-55IACEB6.BKKqJU_2.js b/assets/chunks/chunk-55IACEB6.BKKqJU_2.js new file mode 100644 index 000000000..10e5b993e --- /dev/null +++ b/assets/chunks/chunk-55IACEB6.BKKqJU_2.js @@ -0,0 +1 @@ +import{_ as a,d as o}from"./theme.kqgpP4eL.js";var d=a((t,e)=>{let n;return e==="sandbox"&&(n=o("#i"+t)),(e==="sandbox"?o(n.nodes()[0].contentDocument.body):o("body")).select(`[id="${t}"]`)},"getDiagramElement");export{d as g}; diff --git a/assets/chunks/chunk-B4BG7PRW.v_eLYYkV.js b/assets/chunks/chunk-B4BG7PRW.v_eLYYkV.js new file mode 100644 index 000000000..d86b980f1 --- /dev/null +++ b/assets/chunks/chunk-B4BG7PRW.v_eLYYkV.js @@ -0,0 +1,165 @@ +import{g as et}from"./chunk-FMBD7UC4.B39tdjdc.js";import{g as tt}from"./chunk-55IACEB6.BKKqJU_2.js";import{s as st}from"./chunk-QN33PNHL.ChYgkhtD.js";import{_ as f,l as Oe,c as F,p as it,r as at,u as we,d as ee,b as nt,a as rt,s as ut,g as lt,q as ct,t as ot,k as v,z as ht,y as dt,i as pt,a0 as R}from"./theme.kqgpP4eL.js";var Ve=function(){var s=f(function(I,c,h,p){for(h=h||{},p=I.length;p--;h[I[p]]=c);return h},"o"),i=[1,18],a=[1,19],u=[1,20],l=[1,41],r=[1,42],o=[1,26],A=[1,24],g=[1,25],D=[1,32],L=[1,33],Ae=[1,34],m=[1,45],fe=[1,35],ge=[1,36],Ce=[1,37],me=[1,38],be=[1,27],Ee=[1,28],ye=[1,29],Te=[1,30],ke=[1,31],b=[1,44],E=[1,46],y=[1,43],T=[1,47],De=[1,9],d=[1,8,9],te=[1,58],se=[1,59],ie=[1,60],ae=[1,61],ne=[1,62],Fe=[1,63],Be=[1,64],z=[1,8,9,41],Pe=[1,76],P=[1,8,9,12,13,22,39,41,44,68,69,70,71,72,73,74,79,81],re=[1,8,9,12,13,18,20,22,39,41,44,50,60,68,69,70,71,72,73,74,79,81,86,100,102,103],ue=[13,60,86,100,102,103],K=[13,60,73,74,86,100,102,103],Me=[13,60,68,69,70,71,72,86,100,102,103],_e=[1,100],Y=[1,117],Q=[1,113],W=[1,109],j=[1,115],X=[1,110],q=[1,111],H=[1,112],J=[1,114],Z=[1,116],Re=[22,48,60,61,82,86,87,88,89,90],Se=[1,8,9,39,41,44],le=[1,8,9,22],Ge=[1,145],Ue=[1,8,9,61],N=[1,8,9,22,48,60,61,82,86,87,88,89,90],Ne={trace:f(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mermaidDoc:4,statements:5,graphConfig:6,CLASS_DIAGRAM:7,NEWLINE:8,EOF:9,statement:10,classLabel:11,SQS:12,STR:13,SQE:14,namespaceName:15,alphaNumToken:16,classLiteralName:17,DOT:18,className:19,GENERICTYPE:20,relationStatement:21,LABEL:22,namespaceStatement:23,classStatement:24,memberStatement:25,annotationStatement:26,clickStatement:27,styleStatement:28,cssClassStatement:29,noteStatement:30,classDefStatement:31,direction:32,acc_title:33,acc_title_value:34,acc_descr:35,acc_descr_value:36,acc_descr_multiline_value:37,namespaceIdentifier:38,STRUCT_START:39,classStatements:40,STRUCT_STOP:41,NAMESPACE:42,classIdentifier:43,STYLE_SEPARATOR:44,members:45,CLASS:46,emptyBody:47,SPACE:48,ANNOTATION_START:49,ANNOTATION_END:50,MEMBER:51,SEPARATOR:52,relation:53,NOTE_FOR:54,noteText:55,NOTE:56,CLASSDEF:57,classList:58,stylesOpt:59,ALPHA:60,COMMA:61,direction_tb:62,direction_bt:63,direction_rl:64,direction_lr:65,relationType:66,lineType:67,AGGREGATION:68,EXTENSION:69,COMPOSITION:70,DEPENDENCY:71,LOLLIPOP:72,LINE:73,DOTTED_LINE:74,CALLBACK:75,LINK:76,LINK_TARGET:77,CLICK:78,CALLBACK_NAME:79,CALLBACK_ARGS:80,HREF:81,STYLE:82,CSSCLASS:83,style:84,styleComponent:85,NUM:86,COLON:87,UNIT:88,BRKT:89,PCT:90,commentToken:91,textToken:92,graphCodeTokens:93,textNoTagsToken:94,TAGSTART:95,TAGEND:96,"==":97,"--":98,DEFAULT:99,MINUS:100,keywords:101,UNICODE_TEXT:102,BQUOTE_STR:103,$accept:0,$end:1},terminals_:{2:"error",7:"CLASS_DIAGRAM",8:"NEWLINE",9:"EOF",12:"SQS",13:"STR",14:"SQE",18:"DOT",20:"GENERICTYPE",22:"LABEL",33:"acc_title",34:"acc_title_value",35:"acc_descr",36:"acc_descr_value",37:"acc_descr_multiline_value",39:"STRUCT_START",41:"STRUCT_STOP",42:"NAMESPACE",44:"STYLE_SEPARATOR",46:"CLASS",48:"SPACE",49:"ANNOTATION_START",50:"ANNOTATION_END",51:"MEMBER",52:"SEPARATOR",54:"NOTE_FOR",56:"NOTE",57:"CLASSDEF",60:"ALPHA",61:"COMMA",62:"direction_tb",63:"direction_bt",64:"direction_rl",65:"direction_lr",68:"AGGREGATION",69:"EXTENSION",70:"COMPOSITION",71:"DEPENDENCY",72:"LOLLIPOP",73:"LINE",74:"DOTTED_LINE",75:"CALLBACK",76:"LINK",77:"LINK_TARGET",78:"CLICK",79:"CALLBACK_NAME",80:"CALLBACK_ARGS",81:"HREF",82:"STYLE",83:"CSSCLASS",86:"NUM",87:"COLON",88:"UNIT",89:"BRKT",90:"PCT",93:"graphCodeTokens",95:"TAGSTART",96:"TAGEND",97:"==",98:"--",99:"DEFAULT",100:"MINUS",101:"keywords",102:"UNICODE_TEXT",103:"BQUOTE_STR"},productions_:[0,[3,1],[3,1],[4,1],[6,4],[5,1],[5,2],[5,3],[11,3],[15,1],[15,1],[15,3],[15,2],[19,1],[19,3],[19,1],[19,2],[19,2],[19,2],[10,1],[10,2],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,2],[10,1],[23,4],[23,5],[38,2],[40,1],[40,2],[40,3],[24,1],[24,3],[24,4],[24,3],[24,6],[43,2],[43,3],[47,0],[47,2],[47,2],[26,4],[45,1],[45,2],[25,1],[25,2],[25,1],[25,1],[21,3],[21,4],[21,4],[21,5],[30,3],[30,2],[31,3],[58,1],[58,3],[32,1],[32,1],[32,1],[32,1],[53,3],[53,2],[53,2],[53,1],[66,1],[66,1],[66,1],[66,1],[66,1],[67,1],[67,1],[27,3],[27,4],[27,3],[27,4],[27,4],[27,5],[27,3],[27,4],[27,4],[27,5],[27,4],[27,5],[27,5],[27,6],[28,3],[29,3],[59,1],[59,3],[84,1],[84,2],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[91,1],[91,1],[92,1],[92,1],[92,1],[92,1],[92,1],[92,1],[92,1],[94,1],[94,1],[94,1],[94,1],[16,1],[16,1],[16,1],[16,1],[17,1],[55,1]],performAction:f(function(c,h,p,n,C,e,$){var t=e.length-1;switch(C){case 8:this.$=e[t-1];break;case 9:case 10:case 13:case 15:this.$=e[t];break;case 11:case 14:this.$=e[t-2]+"."+e[t];break;case 12:case 16:this.$=e[t-1]+e[t];break;case 17:case 18:this.$=e[t-1]+"~"+e[t]+"~";break;case 19:n.addRelation(e[t]);break;case 20:e[t-1].title=n.cleanupLabel(e[t]),n.addRelation(e[t-1]);break;case 31:this.$=e[t].trim(),n.setAccTitle(this.$);break;case 32:case 33:this.$=e[t].trim(),n.setAccDescription(this.$);break;case 34:n.addClassesToNamespace(e[t-3],e[t-1]);break;case 35:n.addClassesToNamespace(e[t-4],e[t-1]);break;case 36:this.$=e[t],n.addNamespace(e[t]);break;case 37:this.$=[e[t]];break;case 38:this.$=[e[t-1]];break;case 39:e[t].unshift(e[t-2]),this.$=e[t];break;case 41:n.setCssClass(e[t-2],e[t]);break;case 42:n.addMembers(e[t-3],e[t-1]);break;case 44:n.setCssClass(e[t-5],e[t-3]),n.addMembers(e[t-5],e[t-1]);break;case 45:this.$=e[t],n.addClass(e[t]);break;case 46:this.$=e[t-1],n.addClass(e[t-1]),n.setClassLabel(e[t-1],e[t]);break;case 50:n.addAnnotation(e[t],e[t-2]);break;case 51:case 64:this.$=[e[t]];break;case 52:e[t].push(e[t-1]),this.$=e[t];break;case 53:break;case 54:n.addMember(e[t-1],n.cleanupLabel(e[t]));break;case 55:break;case 56:break;case 57:this.$={id1:e[t-2],id2:e[t],relation:e[t-1],relationTitle1:"none",relationTitle2:"none"};break;case 58:this.$={id1:e[t-3],id2:e[t],relation:e[t-1],relationTitle1:e[t-2],relationTitle2:"none"};break;case 59:this.$={id1:e[t-3],id2:e[t],relation:e[t-2],relationTitle1:"none",relationTitle2:e[t-1]};break;case 60:this.$={id1:e[t-4],id2:e[t],relation:e[t-2],relationTitle1:e[t-3],relationTitle2:e[t-1]};break;case 61:n.addNote(e[t],e[t-1]);break;case 62:n.addNote(e[t]);break;case 63:this.$=e[t-2],n.defineClass(e[t-1],e[t]);break;case 65:this.$=e[t-2].concat([e[t]]);break;case 66:n.setDirection("TB");break;case 67:n.setDirection("BT");break;case 68:n.setDirection("RL");break;case 69:n.setDirection("LR");break;case 70:this.$={type1:e[t-2],type2:e[t],lineType:e[t-1]};break;case 71:this.$={type1:"none",type2:e[t],lineType:e[t-1]};break;case 72:this.$={type1:e[t-1],type2:"none",lineType:e[t]};break;case 73:this.$={type1:"none",type2:"none",lineType:e[t]};break;case 74:this.$=n.relationType.AGGREGATION;break;case 75:this.$=n.relationType.EXTENSION;break;case 76:this.$=n.relationType.COMPOSITION;break;case 77:this.$=n.relationType.DEPENDENCY;break;case 78:this.$=n.relationType.LOLLIPOP;break;case 79:this.$=n.lineType.LINE;break;case 80:this.$=n.lineType.DOTTED_LINE;break;case 81:case 87:this.$=e[t-2],n.setClickEvent(e[t-1],e[t]);break;case 82:case 88:this.$=e[t-3],n.setClickEvent(e[t-2],e[t-1]),n.setTooltip(e[t-2],e[t]);break;case 83:this.$=e[t-2],n.setLink(e[t-1],e[t]);break;case 84:this.$=e[t-3],n.setLink(e[t-2],e[t-1],e[t]);break;case 85:this.$=e[t-3],n.setLink(e[t-2],e[t-1]),n.setTooltip(e[t-2],e[t]);break;case 86:this.$=e[t-4],n.setLink(e[t-3],e[t-2],e[t]),n.setTooltip(e[t-3],e[t-1]);break;case 89:this.$=e[t-3],n.setClickEvent(e[t-2],e[t-1],e[t]);break;case 90:this.$=e[t-4],n.setClickEvent(e[t-3],e[t-2],e[t-1]),n.setTooltip(e[t-3],e[t]);break;case 91:this.$=e[t-3],n.setLink(e[t-2],e[t]);break;case 92:this.$=e[t-4],n.setLink(e[t-3],e[t-1],e[t]);break;case 93:this.$=e[t-4],n.setLink(e[t-3],e[t-1]),n.setTooltip(e[t-3],e[t]);break;case 94:this.$=e[t-5],n.setLink(e[t-4],e[t-2],e[t]),n.setTooltip(e[t-4],e[t-1]);break;case 95:this.$=e[t-2],n.setCssStyle(e[t-1],e[t]);break;case 96:n.setCssClass(e[t-1],e[t]);break;case 97:this.$=[e[t]];break;case 98:e[t-2].push(e[t]),this.$=e[t-2];break;case 100:this.$=e[t-1]+e[t];break}},"anonymous"),table:[{3:1,4:2,5:3,6:4,7:[1,6],10:5,16:39,17:40,19:21,21:7,23:8,24:9,25:10,26:11,27:12,28:13,29:14,30:15,31:16,32:17,33:i,35:a,37:u,38:22,42:l,43:23,46:r,49:o,51:A,52:g,54:D,56:L,57:Ae,60:m,62:fe,63:ge,64:Ce,65:me,75:be,76:Ee,78:ye,82:Te,83:ke,86:b,100:E,102:y,103:T},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,3]},s(De,[2,5],{8:[1,48]}),{8:[1,49]},s(d,[2,19],{22:[1,50]}),s(d,[2,21]),s(d,[2,22]),s(d,[2,23]),s(d,[2,24]),s(d,[2,25]),s(d,[2,26]),s(d,[2,27]),s(d,[2,28]),s(d,[2,29]),s(d,[2,30]),{34:[1,51]},{36:[1,52]},s(d,[2,33]),s(d,[2,53],{53:53,66:56,67:57,13:[1,54],22:[1,55],68:te,69:se,70:ie,71:ae,72:ne,73:Fe,74:Be}),{39:[1,65]},s(z,[2,40],{39:[1,67],44:[1,66]}),s(d,[2,55]),s(d,[2,56]),{16:68,60:m,86:b,100:E,102:y},{16:39,17:40,19:69,60:m,86:b,100:E,102:y,103:T},{16:39,17:40,19:70,60:m,86:b,100:E,102:y,103:T},{16:39,17:40,19:71,60:m,86:b,100:E,102:y,103:T},{60:[1,72]},{13:[1,73]},{16:39,17:40,19:74,60:m,86:b,100:E,102:y,103:T},{13:Pe,55:75},{58:77,60:[1,78]},s(d,[2,66]),s(d,[2,67]),s(d,[2,68]),s(d,[2,69]),s(P,[2,13],{16:39,17:40,19:80,18:[1,79],20:[1,81],60:m,86:b,100:E,102:y,103:T}),s(P,[2,15],{20:[1,82]}),{15:83,16:84,17:85,60:m,86:b,100:E,102:y,103:T},{16:39,17:40,19:86,60:m,86:b,100:E,102:y,103:T},s(re,[2,123]),s(re,[2,124]),s(re,[2,125]),s(re,[2,126]),s([1,8,9,12,13,20,22,39,41,44,68,69,70,71,72,73,74,79,81],[2,127]),s(De,[2,6],{10:5,21:7,23:8,24:9,25:10,26:11,27:12,28:13,29:14,30:15,31:16,32:17,19:21,38:22,43:23,16:39,17:40,5:87,33:i,35:a,37:u,42:l,46:r,49:o,51:A,52:g,54:D,56:L,57:Ae,60:m,62:fe,63:ge,64:Ce,65:me,75:be,76:Ee,78:ye,82:Te,83:ke,86:b,100:E,102:y,103:T}),{5:88,10:5,16:39,17:40,19:21,21:7,23:8,24:9,25:10,26:11,27:12,28:13,29:14,30:15,31:16,32:17,33:i,35:a,37:u,38:22,42:l,43:23,46:r,49:o,51:A,52:g,54:D,56:L,57:Ae,60:m,62:fe,63:ge,64:Ce,65:me,75:be,76:Ee,78:ye,82:Te,83:ke,86:b,100:E,102:y,103:T},s(d,[2,20]),s(d,[2,31]),s(d,[2,32]),{13:[1,90],16:39,17:40,19:89,60:m,86:b,100:E,102:y,103:T},{53:91,66:56,67:57,68:te,69:se,70:ie,71:ae,72:ne,73:Fe,74:Be},s(d,[2,54]),{67:92,73:Fe,74:Be},s(ue,[2,73],{66:93,68:te,69:se,70:ie,71:ae,72:ne}),s(K,[2,74]),s(K,[2,75]),s(K,[2,76]),s(K,[2,77]),s(K,[2,78]),s(Me,[2,79]),s(Me,[2,80]),{8:[1,95],24:96,40:94,43:23,46:r},{16:97,60:m,86:b,100:E,102:y},{41:[1,99],45:98,51:_e},{50:[1,101]},{13:[1,102]},{13:[1,103]},{79:[1,104],81:[1,105]},{22:Y,48:Q,59:106,60:W,82:j,84:107,85:108,86:X,87:q,88:H,89:J,90:Z},{60:[1,118]},{13:Pe,55:119},s(d,[2,62]),s(d,[2,128]),{22:Y,48:Q,59:120,60:W,61:[1,121],82:j,84:107,85:108,86:X,87:q,88:H,89:J,90:Z},s(Re,[2,64]),{16:39,17:40,19:122,60:m,86:b,100:E,102:y,103:T},s(P,[2,16]),s(P,[2,17]),s(P,[2,18]),{39:[2,36]},{15:124,16:84,17:85,18:[1,123],39:[2,9],60:m,86:b,100:E,102:y,103:T},{39:[2,10]},s(Se,[2,45],{11:125,12:[1,126]}),s(De,[2,7]),{9:[1,127]},s(le,[2,57]),{16:39,17:40,19:128,60:m,86:b,100:E,102:y,103:T},{13:[1,130],16:39,17:40,19:129,60:m,86:b,100:E,102:y,103:T},s(ue,[2,72],{66:131,68:te,69:se,70:ie,71:ae,72:ne}),s(ue,[2,71]),{41:[1,132]},{24:96,40:133,43:23,46:r},{8:[1,134],41:[2,37]},s(z,[2,41],{39:[1,135]}),{41:[1,136]},s(z,[2,43]),{41:[2,51],45:137,51:_e},{16:39,17:40,19:138,60:m,86:b,100:E,102:y,103:T},s(d,[2,81],{13:[1,139]}),s(d,[2,83],{13:[1,141],77:[1,140]}),s(d,[2,87],{13:[1,142],80:[1,143]}),{13:[1,144]},s(d,[2,95],{61:Ge}),s(Ue,[2,97],{85:146,22:Y,48:Q,60:W,82:j,86:X,87:q,88:H,89:J,90:Z}),s(N,[2,99]),s(N,[2,101]),s(N,[2,102]),s(N,[2,103]),s(N,[2,104]),s(N,[2,105]),s(N,[2,106]),s(N,[2,107]),s(N,[2,108]),s(N,[2,109]),s(d,[2,96]),s(d,[2,61]),s(d,[2,63],{61:Ge}),{60:[1,147]},s(P,[2,14]),{15:148,16:84,17:85,60:m,86:b,100:E,102:y,103:T},{39:[2,12]},s(Se,[2,46]),{13:[1,149]},{1:[2,4]},s(le,[2,59]),s(le,[2,58]),{16:39,17:40,19:150,60:m,86:b,100:E,102:y,103:T},s(ue,[2,70]),s(d,[2,34]),{41:[1,151]},{24:96,40:152,41:[2,38],43:23,46:r},{45:153,51:_e},s(z,[2,42]),{41:[2,52]},s(d,[2,50]),s(d,[2,82]),s(d,[2,84]),s(d,[2,85],{77:[1,154]}),s(d,[2,88]),s(d,[2,89],{13:[1,155]}),s(d,[2,91],{13:[1,157],77:[1,156]}),{22:Y,48:Q,60:W,82:j,84:158,85:108,86:X,87:q,88:H,89:J,90:Z},s(N,[2,100]),s(Re,[2,65]),{39:[2,11]},{14:[1,159]},s(le,[2,60]),s(d,[2,35]),{41:[2,39]},{41:[1,160]},s(d,[2,86]),s(d,[2,90]),s(d,[2,92]),s(d,[2,93],{77:[1,161]}),s(Ue,[2,98],{85:146,22:Y,48:Q,60:W,82:j,86:X,87:q,88:H,89:J,90:Z}),s(Se,[2,8]),s(z,[2,44]),s(d,[2,94])],defaultActions:{2:[2,1],3:[2,2],4:[2,3],83:[2,36],85:[2,10],124:[2,12],127:[2,4],137:[2,52],148:[2,11],152:[2,39]},parseError:f(function(c,h){if(h.recoverable)this.trace(c);else{var p=new Error(c);throw p.hash=h,p}},"parseError"),parse:f(function(c){var h=this,p=[0],n=[],C=[null],e=[],$=this.table,t="",oe=0,ze=0,He=2,Ke=1,Je=e.slice.call(arguments,1),k=Object.create(this.lexer),O={yy:{}};for(var Le in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Le)&&(O.yy[Le]=this.yy[Le]);k.setInput(c,O.yy),O.yy.lexer=k,O.yy.parser=this,typeof k.yylloc>"u"&&(k.yylloc={});var xe=k.yylloc;e.push(xe);var Ze=k.options&&k.options.ranges;typeof O.yy.parseError=="function"?this.parseError=O.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function $e(_){p.length=p.length-2*_,C.length=C.length-_,e.length=e.length-_}f($e,"popStack");function Ye(){var _;return _=n.pop()||k.lex()||Ke,typeof _!="number"&&(_ instanceof Array&&(n=_,_=n.pop()),_=h.symbols_[_]||_),_}f(Ye,"lex");for(var B,w,S,ve,M={},he,x,Qe,de;;){if(w=p[p.length-1],this.defaultActions[w]?S=this.defaultActions[w]:((B===null||typeof B>"u")&&(B=Ye()),S=$[w]&&$[w][B]),typeof S>"u"||!S.length||!S[0]){var Ie="";de=[];for(he in $[w])this.terminals_[he]&&he>He&&de.push("'"+this.terminals_[he]+"'");k.showPosition?Ie="Parse error on line "+(oe+1)+`: +`+k.showPosition()+` +Expecting `+de.join(", ")+", got '"+(this.terminals_[B]||B)+"'":Ie="Parse error on line "+(oe+1)+": Unexpected "+(B==Ke?"end of input":"'"+(this.terminals_[B]||B)+"'"),this.parseError(Ie,{text:k.match,token:this.terminals_[B]||B,line:k.yylineno,loc:xe,expected:de})}if(S[0]instanceof Array&&S.length>1)throw new Error("Parse Error: multiple actions possible at state: "+w+", token: "+B);switch(S[0]){case 1:p.push(B),C.push(k.yytext),e.push(k.yylloc),p.push(S[1]),B=null,ze=k.yyleng,t=k.yytext,oe=k.yylineno,xe=k.yylloc;break;case 2:if(x=this.productions_[S[1]][1],M.$=C[C.length-x],M._$={first_line:e[e.length-(x||1)].first_line,last_line:e[e.length-1].last_line,first_column:e[e.length-(x||1)].first_column,last_column:e[e.length-1].last_column},Ze&&(M._$.range=[e[e.length-(x||1)].range[0],e[e.length-1].range[1]]),ve=this.performAction.apply(M,[t,ze,oe,O.yy,S[1],C,e].concat(Je)),typeof ve<"u")return ve;x&&(p=p.slice(0,-1*x*2),C=C.slice(0,-1*x),e=e.slice(0,-1*x)),p.push(this.productions_[S[1]][0]),C.push(M.$),e.push(M._$),Qe=$[p[p.length-2]][p[p.length-1]],p.push(Qe);break;case 3:return!0}}return!0},"parse")},qe=function(){var I={EOF:1,parseError:f(function(h,p){if(this.yy.parser)this.yy.parser.parseError(h,p);else throw new Error(h)},"parseError"),setInput:f(function(c,h){return this.yy=h||this.yy||{},this._input=c,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:f(function(){var c=this._input[0];this.yytext+=c,this.yyleng++,this.offset++,this.match+=c,this.matched+=c;var h=c.match(/(?:\r\n?|\n).*/g);return h?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),c},"input"),unput:f(function(c){var h=c.length,p=c.split(/(?:\r\n?|\n)/g);this._input=c+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-h),this.offset-=h;var n=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),p.length-1&&(this.yylineno-=p.length-1);var C=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:p?(p.length===n.length?this.yylloc.first_column:0)+n[n.length-p.length].length-p[0].length:this.yylloc.first_column-h},this.options.ranges&&(this.yylloc.range=[C[0],C[0]+this.yyleng-h]),this.yyleng=this.yytext.length,this},"unput"),more:f(function(){return this._more=!0,this},"more"),reject:f(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:f(function(c){this.unput(this.match.slice(c))},"less"),pastInput:f(function(){var c=this.matched.substr(0,this.matched.length-this.match.length);return(c.length>20?"...":"")+c.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:f(function(){var c=this.match;return c.length<20&&(c+=this._input.substr(0,20-c.length)),(c.substr(0,20)+(c.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:f(function(){var c=this.pastInput(),h=new Array(c.length+1).join("-");return c+this.upcomingInput()+` +`+h+"^"},"showPosition"),test_match:f(function(c,h){var p,n,C;if(this.options.backtrack_lexer&&(C={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(C.yylloc.range=this.yylloc.range.slice(0))),n=c[0].match(/(?:\r\n?|\n).*/g),n&&(this.yylineno+=n.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:n?n[n.length-1].length-n[n.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+c[0].length},this.yytext+=c[0],this.match+=c[0],this.matches=c,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(c[0].length),this.matched+=c[0],p=this.performAction.call(this,this.yy,this,h,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),p)return p;if(this._backtrack){for(var e in C)this[e]=C[e];return!1}return!1},"test_match"),next:f(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var c,h,p,n;this._more||(this.yytext="",this.match="");for(var C=this._currentRules(),e=0;eh[0].length)){if(h=p,n=e,this.options.backtrack_lexer){if(c=this.test_match(p,C[e]),c!==!1)return c;if(this._backtrack){h=!1;continue}else return!1}else if(!this.options.flex)break}return h?(c=this.test_match(h,C[n]),c!==!1?c:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:f(function(){var h=this.next();return h||this.lex()},"lex"),begin:f(function(h){this.conditionStack.push(h)},"begin"),popState:f(function(){var h=this.conditionStack.length-1;return h>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:f(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:f(function(h){return h=this.conditionStack.length-1-Math.abs(h||0),h>=0?this.conditionStack[h]:"INITIAL"},"topState"),pushState:f(function(h){this.begin(h)},"pushState"),stateStackSize:f(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:f(function(h,p,n,C){switch(n){case 0:return 62;case 1:return 63;case 2:return 64;case 3:return 65;case 4:break;case 5:break;case 6:return this.begin("acc_title"),33;case 7:return this.popState(),"acc_title_value";case 8:return this.begin("acc_descr"),35;case 9:return this.popState(),"acc_descr_value";case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:return 8;case 14:break;case 15:return 7;case 16:return 7;case 17:return"EDGE_STATE";case 18:this.begin("callback_name");break;case 19:this.popState();break;case 20:this.popState(),this.begin("callback_args");break;case 21:return 79;case 22:this.popState();break;case 23:return 80;case 24:this.popState();break;case 25:return"STR";case 26:this.begin("string");break;case 27:return 82;case 28:return 57;case 29:return this.begin("namespace"),42;case 30:return this.popState(),8;case 31:break;case 32:return this.begin("namespace-body"),39;case 33:return this.popState(),41;case 34:return"EOF_IN_STRUCT";case 35:return 8;case 36:break;case 37:return"EDGE_STATE";case 38:return this.begin("class"),46;case 39:return this.popState(),8;case 40:break;case 41:return this.popState(),this.popState(),41;case 42:return this.begin("class-body"),39;case 43:return this.popState(),41;case 44:return"EOF_IN_STRUCT";case 45:return"EDGE_STATE";case 46:return"OPEN_IN_STRUCT";case 47:break;case 48:return"MEMBER";case 49:return 83;case 50:return 75;case 51:return 76;case 52:return 78;case 53:return 54;case 54:return 56;case 55:return 49;case 56:return 50;case 57:return 81;case 58:this.popState();break;case 59:return"GENERICTYPE";case 60:this.begin("generic");break;case 61:this.popState();break;case 62:return"BQUOTE_STR";case 63:this.begin("bqstring");break;case 64:return 77;case 65:return 77;case 66:return 77;case 67:return 77;case 68:return 69;case 69:return 69;case 70:return 71;case 71:return 71;case 72:return 70;case 73:return 68;case 74:return 72;case 75:return 73;case 76:return 74;case 77:return 22;case 78:return 44;case 79:return 100;case 80:return 18;case 81:return"PLUS";case 82:return 87;case 83:return 61;case 84:return 89;case 85:return 89;case 86:return 90;case 87:return"EQUALS";case 88:return"EQUALS";case 89:return 60;case 90:return 12;case 91:return 14;case 92:return"PUNCTUATION";case 93:return 86;case 94:return 102;case 95:return 48;case 96:return 48;case 97:return 9}},"anonymous"),rules:[/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:\[\*\])/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:["])/,/^(?:[^"]*)/,/^(?:["])/,/^(?:style\b)/,/^(?:classDef\b)/,/^(?:namespace\b)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:[{])/,/^(?:[}])/,/^(?:$)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:\[\*\])/,/^(?:class\b)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:[}])/,/^(?:[{])/,/^(?:[}])/,/^(?:$)/,/^(?:\[\*\])/,/^(?:[{])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:note for\b)/,/^(?:note\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:href\b)/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:~)/,/^(?:[`])/,/^(?:[^`]+)/,/^(?:[`])/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:\s*\(\))/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?::)/,/^(?:,)/,/^(?:#)/,/^(?:#)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:\[)/,/^(?:\])/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:\s)/,/^(?:$)/],conditions:{"namespace-body":{rules:[26,33,34,35,36,37,38,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},namespace:{rules:[26,29,30,31,32,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},"class-body":{rules:[26,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},class:{rules:[26,39,40,41,42,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},acc_descr_multiline:{rules:[11,12,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},acc_descr:{rules:[9,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},acc_title:{rules:[7,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},callback_args:{rules:[22,23,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},callback_name:{rules:[19,20,21,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},href:{rules:[26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},struct:{rules:[26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},generic:{rules:[26,49,50,51,52,53,54,55,56,57,58,59,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},bqstring:{rules:[26,49,50,51,52,53,54,55,56,57,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},string:{rules:[24,25,26,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,86,87,88,89,90,91,92,93,94,95,97],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,8,10,13,14,15,16,17,18,26,27,28,29,38,49,50,51,52,53,54,55,56,57,60,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97],inclusive:!0}}};return I}();Ne.lexer=qe;function ce(){this.yy={}}return f(ce,"Parser"),ce.prototype=Ne,Ne.Parser=ce,new ce}();Ve.parser=Ve;var Tt=Ve,We=["#","+","~","-",""],G,je=(G=class{constructor(i,a){this.memberType=a,this.visibility="",this.classifier="",this.text="";const u=pt(i,F());this.parseMember(u)}getDisplayDetails(){let i=this.visibility+R(this.id);this.memberType==="method"&&(i+=`(${R(this.parameters.trim())})`,this.returnType&&(i+=" : "+R(this.returnType))),i=i.trim();const a=this.parseClassifier();return{displayText:i,cssStyle:a}}parseMember(i){let a="";if(this.memberType==="method"){const r=/([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/.exec(i);if(r){const o=r[1]?r[1].trim():"";if(We.includes(o)&&(this.visibility=o),this.id=r[2],this.parameters=r[3]?r[3].trim():"",a=r[4]?r[4].trim():"",this.returnType=r[5]?r[5].trim():"",a===""){const A=this.returnType.substring(this.returnType.length-1);/[$*]/.exec(A)&&(a=A,this.returnType=this.returnType.substring(0,this.returnType.length-1))}}}else{const l=i.length,r=i.substring(0,1),o=i.substring(l-1);We.includes(r)&&(this.visibility=r),/[$*]/.exec(o)&&(a=o),this.id=i.substring(this.visibility===""?0:1,a===""?l:l-1)}this.classifier=a,this.id=this.id.startsWith(" ")?" "+this.id.trim():this.id.trim();const u=`${this.visibility?"\\"+this.visibility:""}${R(this.id)}${this.memberType==="method"?`(${R(this.parameters)})${this.returnType?" : "+R(this.returnType):""}`:""}`;this.text=u.replaceAll("<","<").replaceAll(">",">"),this.text.startsWith("\\<")&&(this.text=this.text.replace("\\<","~"))}parseClassifier(){switch(this.classifier){case"*":return"font-style:italic;";case"$":return"text-decoration:underline;";default:return""}}},f(G,"ClassMember"),G),pe="classId-",Xe=0,V=f(s=>v.sanitizeText(s,F()),"sanitizeText"),U,kt=(U=class{constructor(){this.relations=[],this.classes=new Map,this.styleClasses=new Map,this.notes=[],this.interfaces=[],this.namespaces=new Map,this.namespaceCounter=0,this.functions=[],this.lineType={LINE:0,DOTTED_LINE:1},this.relationType={AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3,LOLLIPOP:4},this.setupToolTips=f(i=>{let a=ee(".mermaidTooltip");(a._groups||a)[0][0]===null&&(a=ee("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),ee(i).select("svg").selectAll("g.node").on("mouseover",r=>{const o=ee(r.currentTarget);if(o.attr("title")===null)return;const g=this.getBoundingClientRect();a.transition().duration(200).style("opacity",".9"),a.text(o.attr("title")).style("left",window.scrollX+g.left+(g.right-g.left)/2+"px").style("top",window.scrollY+g.top-14+document.body.scrollTop+"px"),a.html(a.html().replace(/<br\/>/g,"
")),o.classed("hover",!0)}).on("mouseout",r=>{a.transition().duration(500).style("opacity",0),ee(r.currentTarget).classed("hover",!1)})},"setupToolTips"),this.direction="TB",this.setAccTitle=nt,this.getAccTitle=rt,this.setAccDescription=ut,this.getAccDescription=lt,this.setDiagramTitle=ct,this.getDiagramTitle=ot,this.getConfig=f(()=>F().class,"getConfig"),this.functions.push(this.setupToolTips.bind(this)),this.clear(),this.addRelation=this.addRelation.bind(this),this.addClassesToNamespace=this.addClassesToNamespace.bind(this),this.addNamespace=this.addNamespace.bind(this),this.setCssClass=this.setCssClass.bind(this),this.addMembers=this.addMembers.bind(this),this.addClass=this.addClass.bind(this),this.setClassLabel=this.setClassLabel.bind(this),this.addAnnotation=this.addAnnotation.bind(this),this.addMember=this.addMember.bind(this),this.cleanupLabel=this.cleanupLabel.bind(this),this.addNote=this.addNote.bind(this),this.defineClass=this.defineClass.bind(this),this.setDirection=this.setDirection.bind(this),this.setLink=this.setLink.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.clear=this.clear.bind(this),this.setTooltip=this.setTooltip.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setCssStyle=this.setCssStyle.bind(this)}splitClassNameAndType(i){const a=v.sanitizeText(i,F());let u="",l=a;if(a.indexOf("~")>0){const r=a.split("~");l=V(r[0]),u=V(r[1])}return{className:l,type:u}}setClassLabel(i,a){const u=v.sanitizeText(i,F());a&&(a=V(a));const{className:l}=this.splitClassNameAndType(u);this.classes.get(l).label=a,this.classes.get(l).text=`${a}${this.classes.get(l).type?`<${this.classes.get(l).type}>`:""}`}addClass(i){const a=v.sanitizeText(i,F()),{className:u,type:l}=this.splitClassNameAndType(a);if(this.classes.has(u))return;const r=v.sanitizeText(u,F());this.classes.set(r,{id:r,type:l,label:r,text:`${r}${l?`<${l}>`:""}`,shape:"classBox",cssClasses:"default",methods:[],members:[],annotations:[],styles:[],domId:pe+r+"-"+Xe}),Xe++}addInterface(i,a){const u={id:`interface${this.interfaces.length}`,label:i,classId:a};this.interfaces.push(u)}lookUpDomId(i){const a=v.sanitizeText(i,F());if(this.classes.has(a))return this.classes.get(a).domId;throw new Error("Class not found: "+a)}clear(){this.relations=[],this.classes=new Map,this.notes=[],this.interfaces=[],this.functions=[],this.functions.push(this.setupToolTips.bind(this)),this.namespaces=new Map,this.namespaceCounter=0,this.direction="TB",ht()}getClass(i){return this.classes.get(i)}getClasses(){return this.classes}getRelations(){return this.relations}getNotes(){return this.notes}addRelation(i){Oe.debug("Adding relation: "+JSON.stringify(i));const a=[this.relationType.LOLLIPOP,this.relationType.AGGREGATION,this.relationType.COMPOSITION,this.relationType.DEPENDENCY,this.relationType.EXTENSION];i.relation.type1===this.relationType.LOLLIPOP&&!a.includes(i.relation.type2)?(this.addClass(i.id2),this.addInterface(i.id1,i.id2),i.id1=`interface${this.interfaces.length-1}`):i.relation.type2===this.relationType.LOLLIPOP&&!a.includes(i.relation.type1)?(this.addClass(i.id1),this.addInterface(i.id2,i.id1),i.id2=`interface${this.interfaces.length-1}`):(this.addClass(i.id1),this.addClass(i.id2)),i.id1=this.splitClassNameAndType(i.id1).className,i.id2=this.splitClassNameAndType(i.id2).className,i.relationTitle1=v.sanitizeText(i.relationTitle1.trim(),F()),i.relationTitle2=v.sanitizeText(i.relationTitle2.trim(),F()),this.relations.push(i)}addAnnotation(i,a){const u=this.splitClassNameAndType(i).className;this.classes.get(u).annotations.push(a)}addMember(i,a){this.addClass(i);const u=this.splitClassNameAndType(i).className,l=this.classes.get(u);if(typeof a=="string"){const r=a.trim();r.startsWith("<<")&&r.endsWith(">>")?l.annotations.push(V(r.substring(2,r.length-2))):r.indexOf(")")>0?l.methods.push(new je(r,"method")):r&&l.members.push(new je(r,"attribute"))}}addMembers(i,a){Array.isArray(a)&&(a.reverse(),a.forEach(u=>this.addMember(i,u)))}addNote(i,a){const u={id:`note${this.notes.length}`,class:a,text:i};this.notes.push(u)}cleanupLabel(i){return i.startsWith(":")&&(i=i.substring(1)),V(i.trim())}setCssClass(i,a){i.split(",").forEach(u=>{let l=u;/\d/.exec(u[0])&&(l=pe+l);const r=this.classes.get(l);r&&(r.cssClasses+=" "+a)})}defineClass(i,a){for(const u of i){let l=this.styleClasses.get(u);l===void 0&&(l={id:u,styles:[],textStyles:[]},this.styleClasses.set(u,l)),a&&a.forEach(r=>{if(/color/.exec(r)){const o=r.replace("fill","bgFill");l.textStyles.push(o)}l.styles.push(r)}),this.classes.forEach(r=>{r.cssClasses.includes(u)&&r.styles.push(...a.flatMap(o=>o.split(",")))})}}setTooltip(i,a){i.split(",").forEach(u=>{a!==void 0&&(this.classes.get(u).tooltip=V(a))})}getTooltip(i,a){return a&&this.namespaces.has(a)?this.namespaces.get(a).classes.get(i).tooltip:this.classes.get(i).tooltip}setLink(i,a,u){const l=F();i.split(",").forEach(r=>{let o=r;/\d/.exec(r[0])&&(o=pe+o);const A=this.classes.get(o);A&&(A.link=we.formatUrl(a,l),l.securityLevel==="sandbox"?A.linkTarget="_top":typeof u=="string"?A.linkTarget=V(u):A.linkTarget="_blank")}),this.setCssClass(i,"clickable")}setClickEvent(i,a,u){i.split(",").forEach(l=>{this.setClickFunc(l,a,u),this.classes.get(l).haveCallback=!0}),this.setCssClass(i,"clickable")}setClickFunc(i,a,u){const l=v.sanitizeText(i,F());if(F().securityLevel!=="loose"||a===void 0)return;const o=l;if(this.classes.has(o)){const A=this.lookUpDomId(o);let g=[];if(typeof u=="string"){g=u.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let D=0;D{const D=document.querySelector(`[id="${A}"]`);D!==null&&D.addEventListener("click",()=>{we.runFunc(a,...g)},!1)})}}bindFunctions(i){this.functions.forEach(a=>{a(i)})}getDirection(){return this.direction}setDirection(i){this.direction=i}addNamespace(i){this.namespaces.has(i)||(this.namespaces.set(i,{id:i,classes:new Map,children:{},domId:pe+i+"-"+this.namespaceCounter}),this.namespaceCounter++)}getNamespace(i){return this.namespaces.get(i)}getNamespaces(){return this.namespaces}addClassesToNamespace(i,a){if(this.namespaces.has(i))for(const u of a){const{className:l}=this.splitClassNameAndType(u);this.classes.get(l).parent=i,this.namespaces.get(i).classes.set(l,this.classes.get(l))}}setCssStyle(i,a){const u=this.classes.get(i);if(!(!a||!u))for(const l of a)l.includes(",")?u.styles.push(...l.split(",")):u.styles.push(l)}getArrowMarker(i){let a;switch(i){case 0:a="aggregation";break;case 1:a="extension";break;case 2:a="composition";break;case 3:a="dependency";break;case 4:a="lollipop";break;default:a="none"}return a}getData(){var r;const i=[],a=[],u=F();for(const o of this.namespaces.keys()){const A=this.namespaces.get(o);if(A){const g={id:A.id,label:A.id,isGroup:!0,padding:u.class.padding??16,shape:"rect",cssStyles:["fill: none","stroke: black"],look:u.look};i.push(g)}}for(const o of this.classes.keys()){const A=this.classes.get(o);if(A){const g=A;g.parentId=A.parent,g.look=u.look,i.push(g)}}let l=0;for(const o of this.notes){l++;const A={id:o.id,label:o.text,isGroup:!1,shape:"note",padding:u.class.padding??6,cssStyles:["text-align: left","white-space: nowrap",`fill: ${u.themeVariables.noteBkgColor}`,`stroke: ${u.themeVariables.noteBorderColor}`],look:u.look};i.push(A);const g=((r=this.classes.get(o.class))==null?void 0:r.id)??"";if(g){const D={id:`edgeNote${l}`,start:o.id,end:g,type:"normal",thickness:"normal",classes:"relation",arrowTypeStart:"none",arrowTypeEnd:"none",arrowheadStyle:"",labelStyle:[""],style:["fill: none"],pattern:"dotted",look:u.look};a.push(D)}}for(const o of this.interfaces){const A={id:o.id,label:o.label,isGroup:!1,shape:"rect",cssStyles:["opacity: 0;"],look:u.look};i.push(A)}l=0;for(const o of this.relations){l++;const A={id:dt(o.id1,o.id2,{prefix:"id",counter:l}),start:o.id1,end:o.id2,type:"normal",label:o.title,labelpos:"c",thickness:"normal",classes:"relation",arrowTypeStart:this.getArrowMarker(o.relation.type1),arrowTypeEnd:this.getArrowMarker(o.relation.type2),startLabelRight:o.relationTitle1==="none"?"":o.relationTitle1,endLabelLeft:o.relationTitle2==="none"?"":o.relationTitle2,arrowheadStyle:"",labelStyle:["display: inline-block"],style:o.style||"",pattern:o.relation.lineType==1?"dashed":"solid",look:u.look};a.push(A)}return{nodes:i,edges:a,other:{},config:u,direction:this.getDirection()}}},f(U,"ClassDB"),U),At=f(s=>`g.classGroup text { + fill: ${s.nodeBorder||s.classText}; + stroke: none; + font-family: ${s.fontFamily}; + font-size: 10px; + + .title { + font-weight: bolder; + } + +} + +.nodeLabel, .edgeLabel { + color: ${s.classText}; +} +.edgeLabel .label rect { + fill: ${s.mainBkg}; +} +.label text { + fill: ${s.classText}; +} + +.labelBkg { + background: ${s.mainBkg}; +} +.edgeLabel .label span { + background: ${s.mainBkg}; +} + +.classTitle { + font-weight: bolder; +} +.node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${s.mainBkg}; + stroke: ${s.nodeBorder}; + stroke-width: 1px; + } + + +.divider { + stroke: ${s.nodeBorder}; + stroke-width: 1; +} + +g.clickable { + cursor: pointer; +} + +g.classGroup rect { + fill: ${s.mainBkg}; + stroke: ${s.nodeBorder}; +} + +g.classGroup line { + stroke: ${s.nodeBorder}; + stroke-width: 1; +} + +.classLabel .box { + stroke: none; + stroke-width: 0; + fill: ${s.mainBkg}; + opacity: 0.5; +} + +.classLabel .label { + fill: ${s.nodeBorder}; + font-size: 10px; +} + +.relation { + stroke: ${s.lineColor}; + stroke-width: 1; + fill: none; +} + +.dashed-line{ + stroke-dasharray: 3; +} + +.dotted-line{ + stroke-dasharray: 1 2; +} + +#compositionStart, .composition { + fill: ${s.lineColor} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#compositionEnd, .composition { + fill: ${s.lineColor} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#dependencyStart, .dependency { + fill: ${s.lineColor} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#dependencyStart, .dependency { + fill: ${s.lineColor} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#extensionStart, .extension { + fill: transparent !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#extensionEnd, .extension { + fill: transparent !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#aggregationStart, .aggregation { + fill: transparent !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#aggregationEnd, .aggregation { + fill: transparent !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#lollipopStart, .lollipop { + fill: ${s.mainBkg} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +#lollipopEnd, .lollipop { + fill: ${s.mainBkg} !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; +} + +.edgeTerminals { + font-size: 11px; + line-height: initial; +} + +.classTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${s.textColor}; +} + ${et()} +`,"getStyles"),Dt=At,ft=f((s,i="TB")=>{if(!s.doc)return i;let a=i;for(const u of s.doc)u.stmt==="dir"&&(a=u.value);return a},"getDir"),gt=f(function(s,i){return i.db.getClasses()},"getClasses"),Ct=f(async function(s,i,a,u){Oe.info("REF0:"),Oe.info("Drawing class diagram (v3)",i);const{securityLevel:l,state:r,layout:o}=F(),A=u.db.getData(),g=tt(i,l);A.type=u.type,A.layoutAlgorithm=it(o),A.nodeSpacing=(r==null?void 0:r.nodeSpacing)||50,A.rankSpacing=(r==null?void 0:r.rankSpacing)||50,A.markers=["aggregation","extension","composition","dependency","lollipop"],A.diagramId=i,await at(A,g);const D=8;we.insertTitle(g,"classDiagramTitleText",(r==null?void 0:r.titleTopMargin)??25,u.db.getDiagramTitle()),st(g,D,"classDiagram",(r==null?void 0:r.useMaxWidth)??!0)},"draw"),Ft={getClasses:gt,draw:Ct,getDir:ft};export{kt as C,Tt as a,Ft as c,Dt as s}; diff --git a/assets/chunks/chunk-DI55MBZ5.DJepMPeO.js b/assets/chunks/chunk-DI55MBZ5.DJepMPeO.js new file mode 100644 index 000000000..897e42164 --- /dev/null +++ b/assets/chunks/chunk-DI55MBZ5.DJepMPeO.js @@ -0,0 +1,220 @@ +import{g as te}from"./chunk-55IACEB6.BKKqJU_2.js";import{s as ee}from"./chunk-QN33PNHL.ChYgkhtD.js";import{_ as f,l as D,c as F,r as se,u as ie,a as re,b as ae,g as ne,s as oe,q as le,t as ce,a2 as he,k as z,z as ue}from"./theme.kqgpP4eL.js";var vt=function(){var e=f(function(V,o,h,n){for(h=h||{},n=V.length;n--;h[V[n]]=o);return h},"o"),t=[1,2],s=[1,3],a=[1,4],i=[2,4],l=[1,9],d=[1,11],S=[1,16],p=[1,17],T=[1,18],_=[1,19],m=[1,33],k=[1,20],A=[1,21],$=[1,22],x=[1,23],R=[1,24],u=[1,26],L=[1,27],I=[1,28],N=[1,29],G=[1,30],P=[1,31],B=[1,32],at=[1,35],nt=[1,36],ot=[1,37],lt=[1,38],K=[1,34],y=[1,4,5,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,41,45,48,51,52,53,54,57],ct=[1,4,5,14,15,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,39,40,41,45,48,51,52,53,54,57],xt=[4,5,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,41,45,48,51,52,53,54,57],gt={trace:f(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,SD:6,document:7,line:8,statement:9,classDefStatement:10,styleStatement:11,cssClassStatement:12,idStatement:13,DESCR:14,"-->":15,HIDE_EMPTY:16,scale:17,WIDTH:18,COMPOSIT_STATE:19,STRUCT_START:20,STRUCT_STOP:21,STATE_DESCR:22,AS:23,ID:24,FORK:25,JOIN:26,CHOICE:27,CONCURRENT:28,note:29,notePosition:30,NOTE_TEXT:31,direction:32,acc_title:33,acc_title_value:34,acc_descr:35,acc_descr_value:36,acc_descr_multiline_value:37,CLICK:38,STRING:39,HREF:40,classDef:41,CLASSDEF_ID:42,CLASSDEF_STYLEOPTS:43,DEFAULT:44,style:45,STYLE_IDS:46,STYLEDEF_STYLEOPTS:47,class:48,CLASSENTITY_IDS:49,STYLECLASS:50,direction_tb:51,direction_bt:52,direction_rl:53,direction_lr:54,eol:55,";":56,EDGE_STATE:57,STYLE_SEPARATOR:58,left_of:59,right_of:60,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",6:"SD",14:"DESCR",15:"-->",16:"HIDE_EMPTY",17:"scale",18:"WIDTH",19:"COMPOSIT_STATE",20:"STRUCT_START",21:"STRUCT_STOP",22:"STATE_DESCR",23:"AS",24:"ID",25:"FORK",26:"JOIN",27:"CHOICE",28:"CONCURRENT",29:"note",31:"NOTE_TEXT",33:"acc_title",34:"acc_title_value",35:"acc_descr",36:"acc_descr_value",37:"acc_descr_multiline_value",38:"CLICK",39:"STRING",40:"HREF",41:"classDef",42:"CLASSDEF_ID",43:"CLASSDEF_STYLEOPTS",44:"DEFAULT",45:"style",46:"STYLE_IDS",47:"STYLEDEF_STYLEOPTS",48:"class",49:"CLASSENTITY_IDS",50:"STYLECLASS",51:"direction_tb",52:"direction_bt",53:"direction_rl",54:"direction_lr",56:";",57:"EDGE_STATE",58:"STYLE_SEPARATOR",59:"left_of",60:"right_of"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[9,1],[9,1],[9,1],[9,1],[9,2],[9,3],[9,4],[9,1],[9,2],[9,1],[9,4],[9,3],[9,6],[9,1],[9,1],[9,1],[9,1],[9,4],[9,4],[9,1],[9,2],[9,2],[9,1],[9,5],[9,5],[10,3],[10,3],[11,3],[12,3],[32,1],[32,1],[32,1],[32,1],[55,1],[55,1],[13,1],[13,1],[13,3],[13,3],[30,1],[30,1]],performAction:f(function(o,h,n,g,E,r,Z){var c=r.length-1;switch(E){case 3:return g.setRootDoc(r[c]),r[c];case 4:this.$=[];break;case 5:r[c]!="nl"&&(r[c-1].push(r[c]),this.$=r[c-1]);break;case 6:case 7:this.$=r[c];break;case 8:this.$="nl";break;case 12:this.$=r[c];break;case 13:const tt=r[c-1];tt.description=g.trimColon(r[c]),this.$=tt;break;case 14:this.$={stmt:"relation",state1:r[c-2],state2:r[c]};break;case 15:const Tt=g.trimColon(r[c]);this.$={stmt:"relation",state1:r[c-3],state2:r[c-1],description:Tt};break;case 19:this.$={stmt:"state",id:r[c-3],type:"default",description:"",doc:r[c-1]};break;case 20:var U=r[c],X=r[c-2].trim();if(r[c].match(":")){var ut=r[c].split(":");U=ut[0],X=[X,ut[1]]}this.$={stmt:"state",id:U,type:"default",description:X};break;case 21:this.$={stmt:"state",id:r[c-3],type:"default",description:r[c-5],doc:r[c-1]};break;case 22:this.$={stmt:"state",id:r[c],type:"fork"};break;case 23:this.$={stmt:"state",id:r[c],type:"join"};break;case 24:this.$={stmt:"state",id:r[c],type:"choice"};break;case 25:this.$={stmt:"state",id:g.getDividerId(),type:"divider"};break;case 26:this.$={stmt:"state",id:r[c-1].trim(),note:{position:r[c-2].trim(),text:r[c].trim()}};break;case 29:this.$=r[c].trim(),g.setAccTitle(this.$);break;case 30:case 31:this.$=r[c].trim(),g.setAccDescription(this.$);break;case 32:this.$={stmt:"click",id:r[c-3],url:r[c-2],tooltip:r[c-1]};break;case 33:this.$={stmt:"click",id:r[c-3],url:r[c-1],tooltip:""};break;case 34:case 35:this.$={stmt:"classDef",id:r[c-1].trim(),classes:r[c].trim()};break;case 36:this.$={stmt:"style",id:r[c-1].trim(),styleClass:r[c].trim()};break;case 37:this.$={stmt:"applyClass",id:r[c-1].trim(),styleClass:r[c].trim()};break;case 38:g.setDirection("TB"),this.$={stmt:"dir",value:"TB"};break;case 39:g.setDirection("BT"),this.$={stmt:"dir",value:"BT"};break;case 40:g.setDirection("RL"),this.$={stmt:"dir",value:"RL"};break;case 41:g.setDirection("LR"),this.$={stmt:"dir",value:"LR"};break;case 44:case 45:this.$={stmt:"state",id:r[c].trim(),type:"default",description:""};break;case 46:this.$={stmt:"state",id:r[c-2].trim(),classes:[r[c].trim()],type:"default",description:""};break;case 47:this.$={stmt:"state",id:r[c-2].trim(),classes:[r[c].trim()],type:"default",description:""};break}},"anonymous"),table:[{3:1,4:t,5:s,6:a},{1:[3]},{3:5,4:t,5:s,6:a},{3:6,4:t,5:s,6:a},e([1,4,5,16,17,19,22,24,25,26,27,28,29,33,35,37,38,41,45,48,51,52,53,54,57],i,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:l,5:d,8:8,9:10,10:12,11:13,12:14,13:15,16:S,17:p,19:T,22:_,24:m,25:k,26:A,27:$,28:x,29:R,32:25,33:u,35:L,37:I,38:N,41:G,45:P,48:B,51:at,52:nt,53:ot,54:lt,57:K},e(y,[2,5]),{9:39,10:12,11:13,12:14,13:15,16:S,17:p,19:T,22:_,24:m,25:k,26:A,27:$,28:x,29:R,32:25,33:u,35:L,37:I,38:N,41:G,45:P,48:B,51:at,52:nt,53:ot,54:lt,57:K},e(y,[2,7]),e(y,[2,8]),e(y,[2,9]),e(y,[2,10]),e(y,[2,11]),e(y,[2,12],{14:[1,40],15:[1,41]}),e(y,[2,16]),{18:[1,42]},e(y,[2,18],{20:[1,43]}),{23:[1,44]},e(y,[2,22]),e(y,[2,23]),e(y,[2,24]),e(y,[2,25]),{30:45,31:[1,46],59:[1,47],60:[1,48]},e(y,[2,28]),{34:[1,49]},{36:[1,50]},e(y,[2,31]),{13:51,24:m,57:K},{42:[1,52],44:[1,53]},{46:[1,54]},{49:[1,55]},e(ct,[2,44],{58:[1,56]}),e(ct,[2,45],{58:[1,57]}),e(y,[2,38]),e(y,[2,39]),e(y,[2,40]),e(y,[2,41]),e(y,[2,6]),e(y,[2,13]),{13:58,24:m,57:K},e(y,[2,17]),e(xt,i,{7:59}),{24:[1,60]},{24:[1,61]},{23:[1,62]},{24:[2,48]},{24:[2,49]},e(y,[2,29]),e(y,[2,30]),{39:[1,63],40:[1,64]},{43:[1,65]},{43:[1,66]},{47:[1,67]},{50:[1,68]},{24:[1,69]},{24:[1,70]},e(y,[2,14],{14:[1,71]}),{4:l,5:d,8:8,9:10,10:12,11:13,12:14,13:15,16:S,17:p,19:T,21:[1,72],22:_,24:m,25:k,26:A,27:$,28:x,29:R,32:25,33:u,35:L,37:I,38:N,41:G,45:P,48:B,51:at,52:nt,53:ot,54:lt,57:K},e(y,[2,20],{20:[1,73]}),{31:[1,74]},{24:[1,75]},{39:[1,76]},{39:[1,77]},e(y,[2,34]),e(y,[2,35]),e(y,[2,36]),e(y,[2,37]),e(ct,[2,46]),e(ct,[2,47]),e(y,[2,15]),e(y,[2,19]),e(xt,i,{7:78}),e(y,[2,26]),e(y,[2,27]),{5:[1,79]},{5:[1,80]},{4:l,5:d,8:8,9:10,10:12,11:13,12:14,13:15,16:S,17:p,19:T,21:[1,81],22:_,24:m,25:k,26:A,27:$,28:x,29:R,32:25,33:u,35:L,37:I,38:N,41:G,45:P,48:B,51:at,52:nt,53:ot,54:lt,57:K},e(y,[2,32]),e(y,[2,33]),e(y,[2,21])],defaultActions:{5:[2,1],6:[2,2],47:[2,48],48:[2,49]},parseError:f(function(o,h){if(h.recoverable)this.trace(o);else{var n=new Error(o);throw n.hash=h,n}},"parseError"),parse:f(function(o){var h=this,n=[0],g=[],E=[null],r=[],Z=this.table,c="",U=0,X=0,ut=2,tt=1,Tt=r.slice.call(arguments,1),b=Object.create(this.lexer),j={yy:{}};for(var Et in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Et)&&(j.yy[Et]=this.yy[Et]);b.setInput(o,j.yy),j.yy.lexer=b,j.yy.parser=this,typeof b.yylloc>"u"&&(b.yylloc={});var _t=b.yylloc;r.push(_t);var Qt=b.options&&b.options.ranges;typeof j.yy.parseError=="function"?this.parseError=j.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Zt(O){n.length=n.length-2*O,E.length=E.length-O,r.length=r.length-O}f(Zt,"popStack");function Lt(){var O;return O=g.pop()||b.lex()||tt,typeof O!="number"&&(O instanceof Array&&(g=O,O=g.pop()),O=h.symbols_[O]||O),O}f(Lt,"lex");for(var C,H,w,mt,J={},dt,Y,Ot,ft;;){if(H=n[n.length-1],this.defaultActions[H]?w=this.defaultActions[H]:((C===null||typeof C>"u")&&(C=Lt()),w=Z[H]&&Z[H][C]),typeof w>"u"||!w.length||!w[0]){var Dt="";ft=[];for(dt in Z[H])this.terminals_[dt]&&dt>ut&&ft.push("'"+this.terminals_[dt]+"'");b.showPosition?Dt="Parse error on line "+(U+1)+`: +`+b.showPosition()+` +Expecting `+ft.join(", ")+", got '"+(this.terminals_[C]||C)+"'":Dt="Parse error on line "+(U+1)+": Unexpected "+(C==tt?"end of input":"'"+(this.terminals_[C]||C)+"'"),this.parseError(Dt,{text:b.match,token:this.terminals_[C]||C,line:b.yylineno,loc:_t,expected:ft})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+C);switch(w[0]){case 1:n.push(C),E.push(b.yytext),r.push(b.yylloc),n.push(w[1]),C=null,X=b.yyleng,c=b.yytext,U=b.yylineno,_t=b.yylloc;break;case 2:if(Y=this.productions_[w[1]][1],J.$=E[E.length-Y],J._$={first_line:r[r.length-(Y||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(Y||1)].first_column,last_column:r[r.length-1].last_column},Qt&&(J._$.range=[r[r.length-(Y||1)].range[0],r[r.length-1].range[1]]),mt=this.performAction.apply(J,[c,X,U,j.yy,w[1],E,r].concat(Tt)),typeof mt<"u")return mt;Y&&(n=n.slice(0,-1*Y*2),E=E.slice(0,-1*Y),r=r.slice(0,-1*Y)),n.push(this.productions_[w[1]][0]),E.push(J.$),r.push(J._$),Ot=Z[n[n.length-2]][n[n.length-1]],n.push(Ot);break;case 3:return!0}}return!0},"parse")},qt=function(){var V={EOF:1,parseError:f(function(h,n){if(this.yy.parser)this.yy.parser.parseError(h,n);else throw new Error(h)},"parseError"),setInput:f(function(o,h){return this.yy=h||this.yy||{},this._input=o,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:f(function(){var o=this._input[0];this.yytext+=o,this.yyleng++,this.offset++,this.match+=o,this.matched+=o;var h=o.match(/(?:\r\n?|\n).*/g);return h?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),o},"input"),unput:f(function(o){var h=o.length,n=o.split(/(?:\r\n?|\n)/g);this._input=o+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-h),this.offset-=h;var g=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var E=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===g.length?this.yylloc.first_column:0)+g[g.length-n.length].length-n[0].length:this.yylloc.first_column-h},this.options.ranges&&(this.yylloc.range=[E[0],E[0]+this.yyleng-h]),this.yyleng=this.yytext.length,this},"unput"),more:f(function(){return this._more=!0,this},"more"),reject:f(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:f(function(o){this.unput(this.match.slice(o))},"less"),pastInput:f(function(){var o=this.matched.substr(0,this.matched.length-this.match.length);return(o.length>20?"...":"")+o.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:f(function(){var o=this.match;return o.length<20&&(o+=this._input.substr(0,20-o.length)),(o.substr(0,20)+(o.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:f(function(){var o=this.pastInput(),h=new Array(o.length+1).join("-");return o+this.upcomingInput()+` +`+h+"^"},"showPosition"),test_match:f(function(o,h){var n,g,E;if(this.options.backtrack_lexer&&(E={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(E.yylloc.range=this.yylloc.range.slice(0))),g=o[0].match(/(?:\r\n?|\n).*/g),g&&(this.yylineno+=g.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:g?g[g.length-1].length-g[g.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+o[0].length},this.yytext+=o[0],this.match+=o[0],this.matches=o,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(o[0].length),this.matched+=o[0],n=this.performAction.call(this,this.yy,this,h,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var r in E)this[r]=E[r];return!1}return!1},"test_match"),next:f(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var o,h,n,g;this._more||(this.yytext="",this.match="");for(var E=this._currentRules(),r=0;rh[0].length)){if(h=n,g=r,this.options.backtrack_lexer){if(o=this.test_match(n,E[r]),o!==!1)return o;if(this._backtrack){h=!1;continue}else return!1}else if(!this.options.flex)break}return h?(o=this.test_match(h,E[g]),o!==!1?o:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:f(function(){var h=this.next();return h||this.lex()},"lex"),begin:f(function(h){this.conditionStack.push(h)},"begin"),popState:f(function(){var h=this.conditionStack.length-1;return h>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:f(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:f(function(h){return h=this.conditionStack.length-1-Math.abs(h||0),h>=0?this.conditionStack[h]:"INITIAL"},"topState"),pushState:f(function(h){this.begin(h)},"pushState"),stateStackSize:f(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:f(function(h,n,g,E){switch(g){case 0:return 38;case 1:return 40;case 2:return 39;case 3:return 44;case 4:return 51;case 5:return 52;case 6:return 53;case 7:return 54;case 8:break;case 9:break;case 10:return 5;case 11:break;case 12:break;case 13:break;case 14:break;case 15:return this.pushState("SCALE"),17;case 16:return 18;case 17:this.popState();break;case 18:return this.begin("acc_title"),33;case 19:return this.popState(),"acc_title_value";case 20:return this.begin("acc_descr"),35;case 21:return this.popState(),"acc_descr_value";case 22:this.begin("acc_descr_multiline");break;case 23:this.popState();break;case 24:return"acc_descr_multiline_value";case 25:return this.pushState("CLASSDEF"),41;case 26:return this.popState(),this.pushState("CLASSDEFID"),"DEFAULT_CLASSDEF_ID";case 27:return this.popState(),this.pushState("CLASSDEFID"),42;case 28:return this.popState(),43;case 29:return this.pushState("CLASS"),48;case 30:return this.popState(),this.pushState("CLASS_STYLE"),49;case 31:return this.popState(),50;case 32:return this.pushState("STYLE"),45;case 33:return this.popState(),this.pushState("STYLEDEF_STYLES"),46;case 34:return this.popState(),47;case 35:return this.pushState("SCALE"),17;case 36:return 18;case 37:this.popState();break;case 38:this.pushState("STATE");break;case 39:return this.popState(),n.yytext=n.yytext.slice(0,-8).trim(),25;case 40:return this.popState(),n.yytext=n.yytext.slice(0,-8).trim(),26;case 41:return this.popState(),n.yytext=n.yytext.slice(0,-10).trim(),27;case 42:return this.popState(),n.yytext=n.yytext.slice(0,-8).trim(),25;case 43:return this.popState(),n.yytext=n.yytext.slice(0,-8).trim(),26;case 44:return this.popState(),n.yytext=n.yytext.slice(0,-10).trim(),27;case 45:return 51;case 46:return 52;case 47:return 53;case 48:return 54;case 49:this.pushState("STATE_STRING");break;case 50:return this.pushState("STATE_ID"),"AS";case 51:return this.popState(),"ID";case 52:this.popState();break;case 53:return"STATE_DESCR";case 54:return 19;case 55:this.popState();break;case 56:return this.popState(),this.pushState("struct"),20;case 57:break;case 58:return this.popState(),21;case 59:break;case 60:return this.begin("NOTE"),29;case 61:return this.popState(),this.pushState("NOTE_ID"),59;case 62:return this.popState(),this.pushState("NOTE_ID"),60;case 63:this.popState(),this.pushState("FLOATING_NOTE");break;case 64:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 65:break;case 66:return"NOTE_TEXT";case 67:return this.popState(),"ID";case 68:return this.popState(),this.pushState("NOTE_TEXT"),24;case 69:return this.popState(),n.yytext=n.yytext.substr(2).trim(),31;case 70:return this.popState(),n.yytext=n.yytext.slice(0,-8).trim(),31;case 71:return 6;case 72:return 6;case 73:return 16;case 74:return 57;case 75:return 24;case 76:return n.yytext=n.yytext.trim(),14;case 77:return 15;case 78:return 28;case 79:return 58;case 80:return 5;case 81:return"INVALID"}},"anonymous"),rules:[/^(?:click\b)/i,/^(?:href\b)/i,/^(?:"[^"]*")/i,/^(?:default\b)/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:classDef\s+)/i,/^(?:DEFAULT\s+)/i,/^(?:\w+\s+)/i,/^(?:[^\n]*)/i,/^(?:class\s+)/i,/^(?:(\w+)+((,\s*\w+)*))/i,/^(?:[^\n]*)/i,/^(?:style\s+)/i,/^(?:[\w,]+\s+)/i,/^(?:[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:.*\[\[choice\]\])/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?::::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[12,13],inclusive:!1},struct:{rules:[12,13,25,29,32,38,45,46,47,48,57,58,59,60,74,75,76,77,78],inclusive:!1},FLOATING_NOTE_ID:{rules:[67],inclusive:!1},FLOATING_NOTE:{rules:[64,65,66],inclusive:!1},NOTE_TEXT:{rules:[69,70],inclusive:!1},NOTE_ID:{rules:[68],inclusive:!1},NOTE:{rules:[61,62,63],inclusive:!1},STYLEDEF_STYLEOPTS:{rules:[],inclusive:!1},STYLEDEF_STYLES:{rules:[34],inclusive:!1},STYLE_IDS:{rules:[],inclusive:!1},STYLE:{rules:[33],inclusive:!1},CLASS_STYLE:{rules:[31],inclusive:!1},CLASS:{rules:[30],inclusive:!1},CLASSDEFID:{rules:[28],inclusive:!1},CLASSDEF:{rules:[26,27],inclusive:!1},acc_descr_multiline:{rules:[23,24],inclusive:!1},acc_descr:{rules:[21],inclusive:!1},acc_title:{rules:[19],inclusive:!1},SCALE:{rules:[16,17,36,37],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[51],inclusive:!1},STATE_STRING:{rules:[52,53],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[12,13,39,40,41,42,43,44,49,50,54,55,56],inclusive:!1},ID:{rules:[12,13],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,13,14,15,18,20,22,25,29,32,35,38,56,60,71,72,73,74,75,76,77,79,80,81],inclusive:!0}}};return V}();gt.lexer=qt;function ht(){this.yy={}}return f(ht,"Parser"),ht.prototype=gt,gt.Parser=ht,new ht}();vt.parser=vt;var Be=vt,de="TB",Yt="TB",Rt="dir",Q="state",q="root",Ct="relation",fe="classDef",pe="style",Se="applyClass",it="default",Gt="divider",Bt="fill:none",Vt="fill: #333",Mt="c",Ut="text",jt="normal",bt="rect",kt="rectWithTitle",ye="stateStart",ge="stateEnd",It="divider",Nt="roundedWithTitle",Te="note",Ee="noteGroup",rt="statediagram",_e="state",me=`${rt}-${_e}`,Ht="transition",De="note",be="note-edge",ke=`${Ht} ${be}`,ve=`${rt}-${De}`,Ce="cluster",Ae=`${rt}-${Ce}`,xe="cluster-alt",Le=`${rt}-${xe}`,zt="parent",Wt="note",Oe="state",At="----",Re=`${At}${Wt}`,wt=`${At}${zt}`,Kt=f((e,t=Yt)=>{if(!e.doc)return t;let s=t;for(const a of e.doc)a.stmt==="dir"&&(s=a.value);return s},"getDir"),Ie=f(function(e,t){return t.db.getClasses()},"getClasses"),Ne=f(async function(e,t,s,a){D.info("REF0:"),D.info("Drawing state diagram (v2)",t);const{securityLevel:i,state:l,layout:d}=F();a.db.extract(a.db.getRootDocV2());const S=a.db.getData(),p=te(t,i);S.type=a.type,S.layoutAlgorithm=d,S.nodeSpacing=(l==null?void 0:l.nodeSpacing)||50,S.rankSpacing=(l==null?void 0:l.rankSpacing)||50,S.markers=["barb"],S.diagramId=t,await se(S,p);const T=8;try{(typeof a.db.getLinks=="function"?a.db.getLinks():new Map).forEach((m,k)=>{var I;const A=typeof k=="string"?k:typeof(k==null?void 0:k.id)=="string"?k.id:"";if(!A){D.warn("⚠️ Invalid or missing stateId from key:",JSON.stringify(k));return}const $=(I=p.node())==null?void 0:I.querySelectorAll("g");let x;if($==null||$.forEach(N=>{var P;((P=N.textContent)==null?void 0:P.trim())===A&&(x=N)}),!x){D.warn("⚠️ Could not find node matching text:",A);return}const R=x.parentNode;if(!R){D.warn("⚠️ Node has no parent, cannot wrap:",A);return}const u=document.createElementNS("http://www.w3.org/2000/svg","a"),L=m.url.replace(/^"+|"+$/g,"");if(u.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",L),u.setAttribute("target","_blank"),m.tooltip){const N=m.tooltip.replace(/^"+|"+$/g,"");u.setAttribute("title",N)}R.replaceChild(u,x),u.appendChild(x),D.info("🔗 Wrapped node in tag for:",A,m.url)})}catch(_){D.error("❌ Error injecting clickable links:",_)}ie.insertTitle(p,"statediagramTitleText",(l==null?void 0:l.titleTopMargin)??25,a.db.getDiagramTitle()),ee(p,T,rt,(l==null?void 0:l.useMaxWidth)??!0)},"draw"),Ve={getClasses:Ie,draw:Ne,getDir:Kt},St=new Map,M=0;function yt(e="",t=0,s="",a=At){const i=s!==null&&s.length>0?`${a}${s}`:"";return`${Oe}-${e}${i}-${t}`}f(yt,"stateDomId");var we=f((e,t,s,a,i,l,d,S)=>{D.trace("items",t),t.forEach(p=>{switch(p.stmt){case Q:st(e,p,s,a,i,l,d,S);break;case it:st(e,p,s,a,i,l,d,S);break;case Ct:{st(e,p.state1,s,a,i,l,d,S),st(e,p.state2,s,a,i,l,d,S);const T={id:"edge"+M,start:p.state1.id,end:p.state2.id,arrowhead:"normal",arrowTypeEnd:"arrow_barb",style:Bt,labelStyle:"",label:z.sanitizeText(p.description??"",F()),arrowheadStyle:Vt,labelpos:Mt,labelType:Ut,thickness:jt,classes:Ht,look:d};i.push(T),M++}break}})},"setupDoc"),$t=f((e,t=Yt)=>{let s=t;if(e.doc)for(const a of e.doc)a.stmt==="dir"&&(s=a.value);return s},"getDir");function et(e,t,s){if(!t.id||t.id===""||t.id==="")return;t.cssClasses&&(Array.isArray(t.cssCompiledStyles)||(t.cssCompiledStyles=[]),t.cssClasses.split(" ").forEach(i=>{const l=s.get(i);l&&(t.cssCompiledStyles=[...t.cssCompiledStyles??[],...l.styles])}));const a=e.find(i=>i.id===t.id);a?Object.assign(a,t):e.push(t)}f(et,"insertOrUpdateNode");function Xt(e){var t;return((t=e==null?void 0:e.classes)==null?void 0:t.join(" "))??""}f(Xt,"getClassesFromDbInfo");function Jt(e){return(e==null?void 0:e.styles)??[]}f(Jt,"getStylesFromDbInfo");var st=f((e,t,s,a,i,l,d,S)=>{var A,$,x;const p=t.id,T=s.get(p),_=Xt(T),m=Jt(T),k=F();if(D.info("dataFetcher parsedItem",t,T,m),p!=="root"){let R=bt;t.start===!0?R=ye:t.start===!1&&(R=ge),t.type!==it&&(R=t.type),St.get(p)||St.set(p,{id:p,shape:R,description:z.sanitizeText(p,k),cssClasses:`${_} ${me}`,cssStyles:m});const u=St.get(p);t.description&&(Array.isArray(u.description)?(u.shape=kt,u.description.push(t.description)):(A=u.description)!=null&&A.length&&u.description.length>0?(u.shape=kt,u.description===p?u.description=[t.description]:u.description=[u.description,t.description]):(u.shape=bt,u.description=t.description),u.description=z.sanitizeTextOrArray(u.description,k)),(($=u.description)==null?void 0:$.length)===1&&u.shape===kt&&(u.type==="group"?u.shape=Nt:u.shape=bt),!u.type&&t.doc&&(D.info("Setting cluster for XCX",p,$t(t)),u.type="group",u.isGroup=!0,u.dir=$t(t),u.shape=t.type===Gt?It:Nt,u.cssClasses=`${u.cssClasses} ${Ae} ${l?Le:""}`);const L={labelStyle:"",shape:u.shape,label:u.description,cssClasses:u.cssClasses,cssCompiledStyles:[],cssStyles:u.cssStyles,id:p,dir:u.dir,domId:yt(p,M),type:u.type,isGroup:u.type==="group",padding:8,rx:10,ry:10,look:d};if(L.shape===It&&(L.label=""),e&&e.id!=="root"&&(D.trace("Setting node ",p," to be child of its parent ",e.id),L.parentId=e.id),L.centerLabel=!0,t.note){const I={labelStyle:"",shape:Te,label:t.note.text,cssClasses:ve,cssStyles:[],cssCompiledStyles:[],id:p+Re+"-"+M,domId:yt(p,M,Wt),type:u.type,isGroup:u.type==="group",padding:(x=k.flowchart)==null?void 0:x.padding,look:d,position:t.note.position},N=p+wt,G={labelStyle:"",shape:Ee,label:t.note.text,cssClasses:u.cssClasses,cssStyles:[],id:p+wt,domId:yt(p,M,zt),type:"group",isGroup:!0,padding:16,look:d,position:t.note.position};M++,G.id=N,I.parentId=N,et(a,G,S),et(a,I,S),et(a,L,S);let P=p,B=I.id;t.note.position==="left of"&&(P=I.id,B=p),i.push({id:P+"-"+B,start:P,end:B,arrowhead:"none",arrowTypeEnd:"",style:Bt,labelStyle:"",classes:ke,arrowheadStyle:Vt,labelpos:Mt,labelType:Ut,thickness:jt,look:d})}else et(a,L,S)}t.doc&&(D.trace("Adding nodes children "),we(t,t.doc,s,a,i,!l,d,S))},"dataFetcher"),$e=f(()=>{St.clear(),M=0},"reset"),v={START_NODE:"[*]",START_TYPE:"start",END_NODE:"[*]",END_TYPE:"end",COLOR_KEYWORD:"color",FILL_KEYWORD:"fill",BG_FILL:"bgFill",STYLECLASS_SEP:","},Pt=f(()=>new Map,"newClassesList"),Ft=f(()=>({relations:[],states:new Map,documents:{}}),"newDoc"),pt=f(e=>JSON.parse(JSON.stringify(e)),"clone"),W,Me=(W=class{constructor(t){this.version=t,this.nodes=[],this.edges=[],this.rootDoc=[],this.classes=Pt(),this.documents={root:Ft()},this.currentDocument=this.documents.root,this.startEndCount=0,this.dividerCnt=0,this.links=new Map,this.getAccTitle=re,this.setAccTitle=ae,this.getAccDescription=ne,this.setAccDescription=oe,this.setDiagramTitle=le,this.getDiagramTitle=ce,this.clear(),this.setRootDoc=this.setRootDoc.bind(this),this.getDividerId=this.getDividerId.bind(this),this.setDirection=this.setDirection.bind(this),this.trimColon=this.trimColon.bind(this)}extract(t){this.clear(!0);for(const i of Array.isArray(t)?t:t.doc)switch(i.stmt){case Q:this.addState(i.id.trim(),i.type,i.doc,i.description,i.note);break;case Ct:this.addRelation(i.state1,i.state2,i.description);break;case fe:this.addStyleClass(i.id.trim(),i.classes);break;case pe:this.handleStyleDef(i);break;case Se:this.setCssClass(i.id.trim(),i.styleClass);break;case"click":this.addLink(i.id,i.url,i.tooltip);break}const s=this.getStates(),a=F();$e(),st(void 0,this.getRootDocV2(),s,this.nodes,this.edges,!0,a.look,this.classes);for(const i of this.nodes)if(Array.isArray(i.label)){if(i.description=i.label.slice(1),i.isGroup&&i.description.length>0)throw new Error(`Group nodes can only have label. Remove the additional description for node [${i.id}]`);i.label=i.label[0]}}handleStyleDef(t){const s=t.id.trim().split(","),a=t.styleClass.split(",");for(const i of s){let l=this.getState(i);if(!l){const d=i.trim();this.addState(d),l=this.getState(d)}l&&(l.styles=a.map(d=>{var S;return(S=d.replace(/;/g,""))==null?void 0:S.trim()}))}}setRootDoc(t){D.info("Setting root doc",t),this.rootDoc=t,this.version===1?this.extract(t):this.extract(this.getRootDocV2())}docTranslator(t,s,a){if(s.stmt===Ct){this.docTranslator(t,s.state1,!0),this.docTranslator(t,s.state2,!1);return}if(s.stmt===Q&&(s.id===v.START_NODE?(s.id=t.id+(a?"_start":"_end"),s.start=a):s.id=s.id.trim()),s.stmt!==q&&s.stmt!==Q||!s.doc)return;const i=[];let l=[];for(const d of s.doc)if(d.type===Gt){const S=pt(d);S.doc=pt(l),i.push(S),l=[]}else l.push(d);if(i.length>0&&l.length>0){const d={stmt:Q,id:he(),type:"divider",doc:pt(l)};i.push(pt(d)),s.doc=i}s.doc.forEach(d=>this.docTranslator(s,d,!0))}getRootDocV2(){return this.docTranslator({id:q,stmt:q},{id:q,stmt:q,doc:this.rootDoc},!0),{id:q,doc:this.rootDoc}}addState(t,s=it,a=void 0,i=void 0,l=void 0,d=void 0,S=void 0,p=void 0){const T=t==null?void 0:t.trim();if(!this.currentDocument.states.has(T))D.info("Adding state ",T,i),this.currentDocument.states.set(T,{stmt:Q,id:T,descriptions:[],type:s,doc:a,note:l,classes:[],styles:[],textStyles:[]});else{const _=this.currentDocument.states.get(T);if(!_)throw new Error(`State not found: ${T}`);_.doc||(_.doc=a),_.type||(_.type=s)}if(i&&(D.info("Setting state description",T,i),(Array.isArray(i)?i:[i]).forEach(m=>this.addDescription(T,m.trim()))),l){const _=this.currentDocument.states.get(T);if(!_)throw new Error(`State not found: ${T}`);_.note=l,_.note.text=z.sanitizeText(_.note.text,F())}d&&(D.info("Setting state classes",T,d),(Array.isArray(d)?d:[d]).forEach(m=>this.setCssClass(T,m.trim()))),S&&(D.info("Setting state styles",T,S),(Array.isArray(S)?S:[S]).forEach(m=>this.setStyle(T,m.trim()))),p&&(D.info("Setting state styles",T,S),(Array.isArray(p)?p:[p]).forEach(m=>this.setTextStyle(T,m.trim())))}clear(t){this.nodes=[],this.edges=[],this.documents={root:Ft()},this.currentDocument=this.documents.root,this.startEndCount=0,this.classes=Pt(),t||(this.links=new Map,ue())}getState(t){return this.currentDocument.states.get(t)}getStates(){return this.currentDocument.states}logDocuments(){D.info("Documents = ",this.documents)}getRelations(){return this.currentDocument.relations}addLink(t,s,a){this.links.set(t,{url:s,tooltip:a}),D.warn("Adding link",t,s,a)}getLinks(){return this.links}startIdIfNeeded(t=""){return t===v.START_NODE?(this.startEndCount++,`${v.START_TYPE}${this.startEndCount}`):t}startTypeIfNeeded(t="",s=it){return t===v.START_NODE?v.START_TYPE:s}endIdIfNeeded(t=""){return t===v.END_NODE?(this.startEndCount++,`${v.END_TYPE}${this.startEndCount}`):t}endTypeIfNeeded(t="",s=it){return t===v.END_NODE?v.END_TYPE:s}addRelationObjs(t,s,a=""){const i=this.startIdIfNeeded(t.id.trim()),l=this.startTypeIfNeeded(t.id.trim(),t.type),d=this.startIdIfNeeded(s.id.trim()),S=this.startTypeIfNeeded(s.id.trim(),s.type);this.addState(i,l,t.doc,t.description,t.note,t.classes,t.styles,t.textStyles),this.addState(d,S,s.doc,s.description,s.note,s.classes,s.styles,s.textStyles),this.currentDocument.relations.push({id1:i,id2:d,relationTitle:z.sanitizeText(a,F())})}addRelation(t,s,a){if(typeof t=="object"&&typeof s=="object")this.addRelationObjs(t,s,a);else if(typeof t=="string"&&typeof s=="string"){const i=this.startIdIfNeeded(t.trim()),l=this.startTypeIfNeeded(t),d=this.endIdIfNeeded(s.trim()),S=this.endTypeIfNeeded(s);this.addState(i,l),this.addState(d,S),this.currentDocument.relations.push({id1:i,id2:d,relationTitle:a?z.sanitizeText(a,F()):void 0})}}addDescription(t,s){var l;const a=this.currentDocument.states.get(t),i=s.startsWith(":")?s.replace(":","").trim():s;(l=a==null?void 0:a.descriptions)==null||l.push(z.sanitizeText(i,F()))}cleanupLabel(t){return t.startsWith(":")?t.slice(2).trim():t.trim()}getDividerId(){return this.dividerCnt++,`divider-id-${this.dividerCnt}`}addStyleClass(t,s=""){this.classes.has(t)||this.classes.set(t,{id:t,styles:[],textStyles:[]});const a=this.classes.get(t);s&&a&&s.split(v.STYLECLASS_SEP).forEach(i=>{const l=i.replace(/([^;]*);/,"$1").trim();if(RegExp(v.COLOR_KEYWORD).exec(i)){const S=l.replace(v.FILL_KEYWORD,v.BG_FILL).replace(v.COLOR_KEYWORD,v.FILL_KEYWORD);a.textStyles.push(S)}a.styles.push(l)})}getClasses(){return this.classes}setCssClass(t,s){t.split(",").forEach(a=>{var l;let i=this.getState(a);if(!i){const d=a.trim();this.addState(d),i=this.getState(d)}(l=i==null?void 0:i.classes)==null||l.push(s)})}setStyle(t,s){var a,i;(i=(a=this.getState(t))==null?void 0:a.styles)==null||i.push(s)}setTextStyle(t,s){var a,i;(i=(a=this.getState(t))==null?void 0:a.textStyles)==null||i.push(s)}getDirectionStatement(){return this.rootDoc.find(t=>t.stmt===Rt)}getDirection(){var t;return((t=this.getDirectionStatement())==null?void 0:t.value)??de}setDirection(t){const s=this.getDirectionStatement();s?s.value=t:this.rootDoc.unshift({stmt:Rt,value:t})}trimColon(t){return t.startsWith(":")?t.slice(1).trim():t.trim()}getData(){const t=F();return{nodes:this.nodes,edges:this.edges,other:{},config:t,direction:Kt(this.getRootDocV2())}}getConfig(){return F().state}},f(W,"StateDB"),W.relationType={AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},W),Pe=f(e=>` +defs #statediagram-barbEnd { + fill: ${e.transitionColor}; + stroke: ${e.transitionColor}; + } +g.stateGroup text { + fill: ${e.nodeBorder}; + stroke: none; + font-size: 10px; +} +g.stateGroup text { + fill: ${e.textColor}; + stroke: none; + font-size: 10px; + +} +g.stateGroup .state-title { + font-weight: bolder; + fill: ${e.stateLabelColor}; +} + +g.stateGroup rect { + fill: ${e.mainBkg}; + stroke: ${e.nodeBorder}; +} + +g.stateGroup line { + stroke: ${e.lineColor}; + stroke-width: 1; +} + +.transition { + stroke: ${e.transitionColor}; + stroke-width: 1; + fill: none; +} + +.stateGroup .composit { + fill: ${e.background}; + border-bottom: 1px +} + +.stateGroup .alt-composit { + fill: #e0e0e0; + border-bottom: 1px +} + +.state-note { + stroke: ${e.noteBorderColor}; + fill: ${e.noteBkgColor}; + + text { + fill: ${e.noteTextColor}; + stroke: none; + font-size: 10px; + } +} + +.stateLabel .box { + stroke: none; + stroke-width: 0; + fill: ${e.mainBkg}; + opacity: 0.5; +} + +.edgeLabel .label rect { + fill: ${e.labelBackgroundColor}; + opacity: 0.5; +} +.edgeLabel { + background-color: ${e.edgeLabelBackground}; + p { + background-color: ${e.edgeLabelBackground}; + } + rect { + opacity: 0.5; + background-color: ${e.edgeLabelBackground}; + fill: ${e.edgeLabelBackground}; + } + text-align: center; +} +.edgeLabel .label text { + fill: ${e.transitionLabelColor||e.tertiaryTextColor}; +} +.label div .edgeLabel { + color: ${e.transitionLabelColor||e.tertiaryTextColor}; +} + +.stateLabel text { + fill: ${e.stateLabelColor}; + font-size: 10px; + font-weight: bold; +} + +.node circle.state-start { + fill: ${e.specialStateColor}; + stroke: ${e.specialStateColor}; +} + +.node .fork-join { + fill: ${e.specialStateColor}; + stroke: ${e.specialStateColor}; +} + +.node circle.state-end { + fill: ${e.innerEndBackground}; + stroke: ${e.background}; + stroke-width: 1.5 +} +.end-state-inner { + fill: ${e.compositeBackground||e.background}; + // stroke: ${e.background}; + stroke-width: 1.5 +} + +.node rect { + fill: ${e.stateBkg||e.mainBkg}; + stroke: ${e.stateBorder||e.nodeBorder}; + stroke-width: 1px; +} +.node polygon { + fill: ${e.mainBkg}; + stroke: ${e.stateBorder||e.nodeBorder};; + stroke-width: 1px; +} +#statediagram-barbEnd { + fill: ${e.lineColor}; +} + +.statediagram-cluster rect { + fill: ${e.compositeTitleBackground}; + stroke: ${e.stateBorder||e.nodeBorder}; + stroke-width: 1px; +} + +.cluster-label, .nodeLabel { + color: ${e.stateLabelColor}; + // line-height: 1; +} + +.statediagram-cluster rect.outer { + rx: 5px; + ry: 5px; +} +.statediagram-state .divider { + stroke: ${e.stateBorder||e.nodeBorder}; +} + +.statediagram-state .title-state { + rx: 5px; + ry: 5px; +} +.statediagram-cluster.statediagram-cluster .inner { + fill: ${e.compositeBackground||e.background}; +} +.statediagram-cluster.statediagram-cluster-alt .inner { + fill: ${e.altBackground?e.altBackground:"#efefef"}; +} + +.statediagram-cluster .inner { + rx:0; + ry:0; +} + +.statediagram-state rect.basic { + rx: 5px; + ry: 5px; +} +.statediagram-state rect.divider { + stroke-dasharray: 10,10; + fill: ${e.altBackground?e.altBackground:"#efefef"}; +} + +.note-edge { + stroke-dasharray: 5; +} + +.statediagram-note rect { + fill: ${e.noteBkgColor}; + stroke: ${e.noteBorderColor}; + stroke-width: 1px; + rx: 0; + ry: 0; +} +.statediagram-note rect { + fill: ${e.noteBkgColor}; + stroke: ${e.noteBorderColor}; + stroke-width: 1px; + rx: 0; + ry: 0; +} + +.statediagram-note text { + fill: ${e.noteTextColor}; +} + +.statediagram-note .nodeLabel { + color: ${e.noteTextColor}; +} +.statediagram .edgeLabel { + color: red; // ${e.noteTextColor}; +} + +#dependencyStart, #dependencyEnd { + fill: ${e.lineColor}; + stroke: ${e.lineColor}; + stroke-width: 1; +} + +.statediagramTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${e.textColor}; +} +`,"getStyles"),Ue=Pe;export{Me as S,Be as a,Ve as b,Ue as s}; diff --git a/assets/chunks/chunk-FMBD7UC4.B39tdjdc.js b/assets/chunks/chunk-FMBD7UC4.B39tdjdc.js new file mode 100644 index 000000000..9680785a2 --- /dev/null +++ b/assets/chunks/chunk-FMBD7UC4.B39tdjdc.js @@ -0,0 +1,15 @@ +import{_ as e}from"./theme.kqgpP4eL.js";var l=e(()=>` + /* Font Awesome icon styling - consolidated */ + .label-icon { + display: inline-block; + height: 1em; + overflow: visible; + vertical-align: -0.125em; + } + + .node .label-icon path { + fill: currentColor; + stroke: revert; + stroke-width: revert; + } +`,"getIconStyles");export{l as g}; diff --git a/assets/chunks/chunk-QN33PNHL.ChYgkhtD.js b/assets/chunks/chunk-QN33PNHL.ChYgkhtD.js new file mode 100644 index 000000000..74afe9718 --- /dev/null +++ b/assets/chunks/chunk-QN33PNHL.ChYgkhtD.js @@ -0,0 +1 @@ +import{_ as a,e as w,l as x}from"./theme.kqgpP4eL.js";var d=a((e,t,i,o)=>{e.attr("class",i);const{width:r,height:h,x:n,y:c}=u(e,t);w(e,h,r,o);const s=l(n,c,r,h,t);e.attr("viewBox",s),x.debug(`viewBox configured: ${s} with padding: ${t}`)},"setupViewPortForSVG"),u=a((e,t)=>{var o;const i=((o=e.node())==null?void 0:o.getBBox())||{width:0,height:0,x:0,y:0};return{width:i.width+t*2,height:i.height+t*2,x:i.x,y:i.y}},"calculateDimensionsWithPadding"),l=a((e,t,i,o,r)=>`${e-r} ${t-r} ${i} ${o}`,"createViewBox");export{d as s}; diff --git a/assets/chunks/chunk-QZHKN3VN.SQhQYWrL.js b/assets/chunks/chunk-QZHKN3VN.SQhQYWrL.js new file mode 100644 index 000000000..14f50dfc8 --- /dev/null +++ b/assets/chunks/chunk-QZHKN3VN.SQhQYWrL.js @@ -0,0 +1 @@ +import{_ as s}from"./theme.kqgpP4eL.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I}; diff --git a/assets/chunks/chunk-TZMSLE5B.CN1RMadv.js b/assets/chunks/chunk-TZMSLE5B.CN1RMadv.js new file mode 100644 index 000000000..37a995392 --- /dev/null +++ b/assets/chunks/chunk-TZMSLE5B.CN1RMadv.js @@ -0,0 +1 @@ +import{_ as n,n as c,j as l}from"./theme.kqgpP4eL.js";var o=n((a,t)=>{const e=a.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((a,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};o(a,e).lower()},"drawBackgroundRect"),g=n((a,t)=>{const e=t.text.replace(c," "),r=a.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const s=r.append("tspan");return s.attr("x",t.x+t.textMargin*2),s.text(e),r},"drawText"),h=n((a,t,e,r)=>{const s=a.append("image");s.attr("x",t),s.attr("y",e);const i=l(r);s.attr("xlink:href",i)},"drawImage"),m=n((a,t,e,r)=>{const s=a.append("use");s.attr("x",t),s.attr("y",e);const i=l(r);s.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,o as d,h as e,g as f,y as g}; diff --git a/assets/chunks/classDiagram-2ON5EDUG.BfaWfr0K.js b/assets/chunks/classDiagram-2ON5EDUG.BfaWfr0K.js new file mode 100644 index 000000000..ffd536b37 --- /dev/null +++ b/assets/chunks/classDiagram-2ON5EDUG.BfaWfr0K.js @@ -0,0 +1 @@ +import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW.v_eLYYkV.js";import{_ as i}from"./theme.kqgpP4eL.js";import"./chunk-FMBD7UC4.B39tdjdc.js";import"./chunk-55IACEB6.BKKqJU_2.js";import"./chunk-QN33PNHL.ChYgkhtD.js";import"./framework.CgT1UzWm.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram}; diff --git a/assets/chunks/classDiagram-v2-WZHVMYZB.BfaWfr0K.js b/assets/chunks/classDiagram-v2-WZHVMYZB.BfaWfr0K.js new file mode 100644 index 000000000..ffd536b37 --- /dev/null +++ b/assets/chunks/classDiagram-v2-WZHVMYZB.BfaWfr0K.js @@ -0,0 +1 @@ +import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW.v_eLYYkV.js";import{_ as i}from"./theme.kqgpP4eL.js";import"./chunk-FMBD7UC4.B39tdjdc.js";import"./chunk-55IACEB6.BKKqJU_2.js";import"./chunk-QN33PNHL.ChYgkhtD.js";import"./framework.CgT1UzWm.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram}; diff --git a/assets/chunks/clone.BclZbdyg.js b/assets/chunks/clone.BclZbdyg.js new file mode 100644 index 000000000..392af85e2 --- /dev/null +++ b/assets/chunks/clone.BclZbdyg.js @@ -0,0 +1 @@ +import{b as r}from"./graph.CD7z0KlM.js";var e=4;function a(o){return r(o,e)}export{a as c}; diff --git a/assets/chunks/cose-bilkent-S5V4N54A.CZp12JBE.js b/assets/chunks/cose-bilkent-S5V4N54A.CZp12JBE.js new file mode 100644 index 000000000..3c6f612de --- /dev/null +++ b/assets/chunks/cose-bilkent-S5V4N54A.CZp12JBE.js @@ -0,0 +1 @@ +import{aA as $,aB as lt,_ as V,l as k,d as gt}from"./theme.kqgpP4eL.js";import{c as J}from"./cytoscape.esm.CyJtwmzi.js";import"./framework.CgT1UzWm.js";var tt={exports:{}},Z={exports:{}},Q={exports:{}},q;function ut(){return q||(q=1,function(G,b){(function(I,T){G.exports=T()})($,function(){return function(N){var I={};function T(o){if(I[o])return I[o].exports;var e=I[o]={i:o,l:!1,exports:{}};return N[o].call(e.exports,e,e.exports,T),e.l=!0,e.exports}return T.m=N,T.c=I,T.i=function(o){return o},T.d=function(o,e,t){T.o(o,e)||Object.defineProperty(o,e,{configurable:!1,enumerable:!0,get:t})},T.n=function(o){var e=o&&o.__esModule?function(){return o.default}:function(){return o};return T.d(e,"a",e),e},T.o=function(o,e){return Object.prototype.hasOwnProperty.call(o,e)},T.p="",T(T.s=26)}([function(N,I,T){function o(){}o.QUALITY=1,o.DEFAULT_CREATE_BENDS_AS_NEEDED=!1,o.DEFAULT_INCREMENTAL=!1,o.DEFAULT_ANIMATION_ON_LAYOUT=!0,o.DEFAULT_ANIMATION_DURING_LAYOUT=!1,o.DEFAULT_ANIMATION_PERIOD=50,o.DEFAULT_UNIFORM_LEAF_NODE_SIZES=!1,o.DEFAULT_GRAPH_MARGIN=15,o.NODE_DIMENSIONS_INCLUDE_LABELS=!1,o.SIMPLE_NODE_SIZE=40,o.SIMPLE_NODE_HALF_SIZE=o.SIMPLE_NODE_SIZE/2,o.EMPTY_COMPOUND_NODE_SIZE=40,o.MIN_EDGE_LENGTH=1,o.WORLD_BOUNDARY=1e6,o.INITIAL_WORLD_BOUNDARY=o.WORLD_BOUNDARY/1e3,o.WORLD_CENTER_X=1200,o.WORLD_CENTER_Y=900,N.exports=o},function(N,I,T){var o=T(2),e=T(8),t=T(9);function i(g,n,d){o.call(this,d),this.isOverlapingSourceAndTarget=!1,this.vGraphObject=d,this.bendpoints=[],this.source=g,this.target=n}i.prototype=Object.create(o.prototype);for(var l in o)i[l]=o[l];i.prototype.getSource=function(){return this.source},i.prototype.getTarget=function(){return this.target},i.prototype.isInterGraph=function(){return this.isInterGraph},i.prototype.getLength=function(){return this.length},i.prototype.isOverlapingSourceAndTarget=function(){return this.isOverlapingSourceAndTarget},i.prototype.getBendpoints=function(){return this.bendpoints},i.prototype.getLca=function(){return this.lca},i.prototype.getSourceInLca=function(){return this.sourceInLca},i.prototype.getTargetInLca=function(){return this.targetInLca},i.prototype.getOtherEnd=function(g){if(this.source===g)return this.target;if(this.target===g)return this.source;throw"Node is not incident with this edge"},i.prototype.getOtherEndInGraph=function(g,n){for(var d=this.getOtherEnd(g),r=n.getGraphManager().getRoot();;){if(d.getOwner()==n)return d;if(d.getOwner()==r)break;d=d.getOwner().getParent()}return null},i.prototype.updateLength=function(){var g=new Array(4);this.isOverlapingSourceAndTarget=e.getIntersection(this.target.getRect(),this.source.getRect(),g),this.isOverlapingSourceAndTarget||(this.lengthX=g[0]-g[2],this.lengthY=g[1]-g[3],Math.abs(this.lengthX)<1&&(this.lengthX=t.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=t.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY))},i.prototype.updateLengthSimple=function(){this.lengthX=this.target.getCenterX()-this.source.getCenterX(),this.lengthY=this.target.getCenterY()-this.source.getCenterY(),Math.abs(this.lengthX)<1&&(this.lengthX=t.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=t.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY)},N.exports=i},function(N,I,T){function o(e){this.vGraphObject=e}N.exports=o},function(N,I,T){var o=T(2),e=T(10),t=T(13),i=T(0),l=T(16),g=T(4);function n(r,h,a,p){a==null&&p==null&&(p=h),o.call(this,p),r.graphManager!=null&&(r=r.graphManager),this.estimatedSize=e.MIN_VALUE,this.inclusionTreeDepth=e.MAX_VALUE,this.vGraphObject=p,this.edges=[],this.graphManager=r,a!=null&&h!=null?this.rect=new t(h.x,h.y,a.width,a.height):this.rect=new t}n.prototype=Object.create(o.prototype);for(var d in o)n[d]=o[d];n.prototype.getEdges=function(){return this.edges},n.prototype.getChild=function(){return this.child},n.prototype.getOwner=function(){return this.owner},n.prototype.getWidth=function(){return this.rect.width},n.prototype.setWidth=function(r){this.rect.width=r},n.prototype.getHeight=function(){return this.rect.height},n.prototype.setHeight=function(r){this.rect.height=r},n.prototype.getCenterX=function(){return this.rect.x+this.rect.width/2},n.prototype.getCenterY=function(){return this.rect.y+this.rect.height/2},n.prototype.getCenter=function(){return new g(this.rect.x+this.rect.width/2,this.rect.y+this.rect.height/2)},n.prototype.getLocation=function(){return new g(this.rect.x,this.rect.y)},n.prototype.getRect=function(){return this.rect},n.prototype.getDiagonal=function(){return Math.sqrt(this.rect.width*this.rect.width+this.rect.height*this.rect.height)},n.prototype.getHalfTheDiagonal=function(){return Math.sqrt(this.rect.height*this.rect.height+this.rect.width*this.rect.width)/2},n.prototype.setRect=function(r,h){this.rect.x=r.x,this.rect.y=r.y,this.rect.width=h.width,this.rect.height=h.height},n.prototype.setCenter=function(r,h){this.rect.x=r-this.rect.width/2,this.rect.y=h-this.rect.height/2},n.prototype.setLocation=function(r,h){this.rect.x=r,this.rect.y=h},n.prototype.moveBy=function(r,h){this.rect.x+=r,this.rect.y+=h},n.prototype.getEdgeListToNode=function(r){var h=[],a=this;return a.edges.forEach(function(p){if(p.target==r){if(p.source!=a)throw"Incorrect edge source!";h.push(p)}}),h},n.prototype.getEdgesBetween=function(r){var h=[],a=this;return a.edges.forEach(function(p){if(!(p.source==a||p.target==a))throw"Incorrect edge source and/or target";(p.target==r||p.source==r)&&h.push(p)}),h},n.prototype.getNeighborsList=function(){var r=new Set,h=this;return h.edges.forEach(function(a){if(a.source==h)r.add(a.target);else{if(a.target!=h)throw"Incorrect incidency!";r.add(a.source)}}),r},n.prototype.withChildren=function(){var r=new Set,h,a;if(r.add(this),this.child!=null)for(var p=this.child.getNodes(),v=0;vh&&(this.rect.x-=(this.labelWidth-h)/2,this.setWidth(this.labelWidth)),this.labelHeight>a&&(this.labelPos=="center"?this.rect.y-=(this.labelHeight-a)/2:this.labelPos=="top"&&(this.rect.y-=this.labelHeight-a),this.setHeight(this.labelHeight))}}},n.prototype.getInclusionTreeDepth=function(){if(this.inclusionTreeDepth==e.MAX_VALUE)throw"assert failed";return this.inclusionTreeDepth},n.prototype.transform=function(r){var h=this.rect.x;h>i.WORLD_BOUNDARY?h=i.WORLD_BOUNDARY:h<-i.WORLD_BOUNDARY&&(h=-i.WORLD_BOUNDARY);var a=this.rect.y;a>i.WORLD_BOUNDARY?a=i.WORLD_BOUNDARY:a<-i.WORLD_BOUNDARY&&(a=-i.WORLD_BOUNDARY);var p=new g(h,a),v=r.inverseTransformPoint(p);this.setLocation(v.x,v.y)},n.prototype.getLeft=function(){return this.rect.x},n.prototype.getRight=function(){return this.rect.x+this.rect.width},n.prototype.getTop=function(){return this.rect.y},n.prototype.getBottom=function(){return this.rect.y+this.rect.height},n.prototype.getParent=function(){return this.owner==null?null:this.owner.getParent()},N.exports=n},function(N,I,T){function o(e,t){e==null&&t==null?(this.x=0,this.y=0):(this.x=e,this.y=t)}o.prototype.getX=function(){return this.x},o.prototype.getY=function(){return this.y},o.prototype.setX=function(e){this.x=e},o.prototype.setY=function(e){this.y=e},o.prototype.getDifference=function(e){return new DimensionD(this.x-e.x,this.y-e.y)},o.prototype.getCopy=function(){return new o(this.x,this.y)},o.prototype.translate=function(e){return this.x+=e.width,this.y+=e.height,this},N.exports=o},function(N,I,T){var o=T(2),e=T(10),t=T(0),i=T(6),l=T(3),g=T(1),n=T(13),d=T(12),r=T(11);function h(p,v,D){o.call(this,D),this.estimatedSize=e.MIN_VALUE,this.margin=t.DEFAULT_GRAPH_MARGIN,this.edges=[],this.nodes=[],this.isConnected=!1,this.parent=p,v!=null&&v instanceof i?this.graphManager=v:v!=null&&v instanceof Layout&&(this.graphManager=v.graphManager)}h.prototype=Object.create(o.prototype);for(var a in o)h[a]=o[a];h.prototype.getNodes=function(){return this.nodes},h.prototype.getEdges=function(){return this.edges},h.prototype.getGraphManager=function(){return this.graphManager},h.prototype.getParent=function(){return this.parent},h.prototype.getLeft=function(){return this.left},h.prototype.getRight=function(){return this.right},h.prototype.getTop=function(){return this.top},h.prototype.getBottom=function(){return this.bottom},h.prototype.isConnected=function(){return this.isConnected},h.prototype.add=function(p,v,D){if(v==null&&D==null){var u=p;if(this.graphManager==null)throw"Graph has no graph mgr!";if(this.getNodes().indexOf(u)>-1)throw"Node already in graph!";return u.owner=this,this.getNodes().push(u),u}else{var E=p;if(!(this.getNodes().indexOf(v)>-1&&this.getNodes().indexOf(D)>-1))throw"Source or target not in graph!";if(!(v.owner==D.owner&&v.owner==this))throw"Both owners must be this graph!";return v.owner!=D.owner?null:(E.source=v,E.target=D,E.isInterGraph=!1,this.getEdges().push(E),v.edges.push(E),D!=v&&D.edges.push(E),E)}},h.prototype.remove=function(p){var v=p;if(p instanceof l){if(v==null)throw"Node is null!";if(!(v.owner!=null&&v.owner==this))throw"Owner graph is invalid!";if(this.graphManager==null)throw"Owner graph manager is invalid!";for(var D=v.edges.slice(),u,E=D.length,y=0;y-1&&f>-1))throw"Source and/or target doesn't know this edge!";u.source.edges.splice(s,1),u.target!=u.source&&u.target.edges.splice(f,1);var O=u.source.owner.getEdges().indexOf(u);if(O==-1)throw"Not in owner's edge list!";u.source.owner.getEdges().splice(O,1)}},h.prototype.updateLeftTop=function(){for(var p=e.MAX_VALUE,v=e.MAX_VALUE,D,u,E,y=this.getNodes(),O=y.length,s=0;sD&&(p=D),v>u&&(v=u)}return p==e.MAX_VALUE?null:(y[0].getParent().paddingLeft!=null?E=y[0].getParent().paddingLeft:E=this.margin,this.left=v-E,this.top=p-E,new d(this.left,this.top))},h.prototype.updateBounds=function(p){for(var v=e.MAX_VALUE,D=-e.MAX_VALUE,u=e.MAX_VALUE,E=-e.MAX_VALUE,y,O,s,f,c,L=this.nodes,A=L.length,m=0;my&&(v=y),Ds&&(u=s),Ey&&(v=y),Ds&&(u=s),E=this.nodes.length){var A=0;D.forEach(function(m){m.owner==p&&A++}),A==this.nodes.length&&(this.isConnected=!0)}},N.exports=h},function(N,I,T){var o,e=T(1);function t(i){o=T(5),this.layout=i,this.graphs=[],this.edges=[]}t.prototype.addRoot=function(){var i=this.layout.newGraph(),l=this.layout.newNode(null),g=this.add(i,l);return this.setRootGraph(g),this.rootGraph},t.prototype.add=function(i,l,g,n,d){if(g==null&&n==null&&d==null){if(i==null)throw"Graph is null!";if(l==null)throw"Parent node is null!";if(this.graphs.indexOf(i)>-1)throw"Graph already in this graph mgr!";if(this.graphs.push(i),i.parent!=null)throw"Already has a parent!";if(l.child!=null)throw"Already has a child!";return i.parent=l,l.child=i,i}else{d=g,n=l,g=i;var r=n.getOwner(),h=d.getOwner();if(!(r!=null&&r.getGraphManager()==this))throw"Source not in this graph mgr!";if(!(h!=null&&h.getGraphManager()==this))throw"Target not in this graph mgr!";if(r==h)return g.isInterGraph=!1,r.add(g,n,d);if(g.isInterGraph=!0,g.source=n,g.target=d,this.edges.indexOf(g)>-1)throw"Edge already in inter-graph edge list!";if(this.edges.push(g),!(g.source!=null&&g.target!=null))throw"Edge source and/or target is null!";if(!(g.source.edges.indexOf(g)==-1&&g.target.edges.indexOf(g)==-1))throw"Edge already in source and/or target incidency list!";return g.source.edges.push(g),g.target.edges.push(g),g}},t.prototype.remove=function(i){if(i instanceof o){var l=i;if(l.getGraphManager()!=this)throw"Graph not in this graph mgr";if(!(l==this.rootGraph||l.parent!=null&&l.parent.graphManager==this))throw"Invalid parent node!";var g=[];g=g.concat(l.getEdges());for(var n,d=g.length,r=0;r=i.getRight()?l[0]+=Math.min(i.getX()-t.getX(),t.getRight()-i.getRight()):i.getX()<=t.getX()&&i.getRight()>=t.getRight()&&(l[0]+=Math.min(t.getX()-i.getX(),i.getRight()-t.getRight())),t.getY()<=i.getY()&&t.getBottom()>=i.getBottom()?l[1]+=Math.min(i.getY()-t.getY(),t.getBottom()-i.getBottom()):i.getY()<=t.getY()&&i.getBottom()>=t.getBottom()&&(l[1]+=Math.min(t.getY()-i.getY(),i.getBottom()-t.getBottom()));var d=Math.abs((i.getCenterY()-t.getCenterY())/(i.getCenterX()-t.getCenterX()));i.getCenterY()===t.getCenterY()&&i.getCenterX()===t.getCenterX()&&(d=1);var r=d*l[0],h=l[1]/d;l[0]r)return l[0]=g,l[1]=a,l[2]=d,l[3]=L,!1;if(nd)return l[0]=h,l[1]=n,l[2]=f,l[3]=r,!1;if(gd?(l[0]=v,l[1]=D,C=!0):(l[0]=p,l[1]=a,C=!0):S===w&&(g>d?(l[0]=h,l[1]=a,C=!0):(l[0]=u,l[1]=D,C=!0)),-Y===w?d>g?(l[2]=c,l[3]=L,M=!0):(l[2]=f,l[3]=s,M=!0):Y===w&&(d>g?(l[2]=O,l[3]=s,M=!0):(l[2]=A,l[3]=L,M=!0)),C&&M)return!1;if(g>d?n>r?(x=this.getCardinalDirection(S,w,4),F=this.getCardinalDirection(Y,w,2)):(x=this.getCardinalDirection(-S,w,3),F=this.getCardinalDirection(-Y,w,1)):n>r?(x=this.getCardinalDirection(-S,w,1),F=this.getCardinalDirection(-Y,w,3)):(x=this.getCardinalDirection(S,w,2),F=this.getCardinalDirection(Y,w,4)),!C)switch(x){case 1:P=a,U=g+-y/w,l[0]=U,l[1]=P;break;case 2:U=u,P=n+E*w,l[0]=U,l[1]=P;break;case 3:P=D,U=g+y/w,l[0]=U,l[1]=P;break;case 4:U=v,P=n+-E*w,l[0]=U,l[1]=P;break}if(!M)switch(F){case 1:X=s,_=d+-R/w,l[2]=_,l[3]=X;break;case 2:_=A,X=r+m*w,l[2]=_,l[3]=X;break;case 3:X=L,_=d+R/w,l[2]=_,l[3]=X;break;case 4:_=c,X=r+-m*w,l[2]=_,l[3]=X;break}}return!1},e.getCardinalDirection=function(t,i,l){return t>i?l:1+l%4},e.getIntersection=function(t,i,l,g){if(g==null)return this.getIntersection2(t,i,l);var n=t.x,d=t.y,r=i.x,h=i.y,a=l.x,p=l.y,v=g.x,D=g.y,u=void 0,E=void 0,y=void 0,O=void 0,s=void 0,f=void 0,c=void 0,L=void 0,A=void 0;return y=h-d,s=n-r,c=r*d-n*h,O=D-p,f=a-v,L=v*p-a*D,A=y*f-O*s,A===0?null:(u=(s*L-f*c)/A,E=(O*c-y*L)/A,new o(u,E))},e.angleOfVector=function(t,i,l,g){var n=void 0;return t!==l?(n=Math.atan((g-i)/(l-t)),l0?1:e<0?-1:0},o.floor=function(e){return e<0?Math.ceil(e):Math.floor(e)},o.ceil=function(e){return e<0?Math.floor(e):Math.ceil(e)},N.exports=o},function(N,I,T){function o(){}o.MAX_VALUE=2147483647,o.MIN_VALUE=-2147483648,N.exports=o},function(N,I,T){var o=function(){function n(d,r){for(var h=0;h"u"?"undefined":o(t);return t==null||i!="object"&&i!="function"},N.exports=e},function(N,I,T){function o(a){if(Array.isArray(a)){for(var p=0,v=Array(a.length);p0&&p;){for(y.push(s[0]);y.length>0&&p;){var f=y[0];y.splice(0,1),E.add(f);for(var c=f.getEdges(),u=0;u-1&&s.splice(R,1)}E=new Set,O=new Map}}return a},h.prototype.createDummyNodesForBendpoints=function(a){for(var p=[],v=a.source,D=this.graphManager.calcLowestCommonAncestor(a.source,a.target),u=0;u0){for(var D=this.edgeToDummyNodes.get(v),u=0;u=0&&p.splice(L,1);var A=O.getNeighborsList();A.forEach(function(C){if(v.indexOf(C)<0){var M=D.get(C),S=M-1;S==1&&f.push(C),D.set(C,S)}})}v=v.concat(f),(p.length==1||p.length==2)&&(u=!0,E=p[0])}return E},h.prototype.setGraphManager=function(a){this.graphManager=a},N.exports=h},function(N,I,T){function o(){}o.seed=1,o.x=0,o.nextDouble=function(){return o.x=Math.sin(o.seed++)*1e4,o.x-Math.floor(o.x)},N.exports=o},function(N,I,T){var o=T(4);function e(t,i){this.lworldOrgX=0,this.lworldOrgY=0,this.ldeviceOrgX=0,this.ldeviceOrgY=0,this.lworldExtX=1,this.lworldExtY=1,this.ldeviceExtX=1,this.ldeviceExtY=1}e.prototype.getWorldOrgX=function(){return this.lworldOrgX},e.prototype.setWorldOrgX=function(t){this.lworldOrgX=t},e.prototype.getWorldOrgY=function(){return this.lworldOrgY},e.prototype.setWorldOrgY=function(t){this.lworldOrgY=t},e.prototype.getWorldExtX=function(){return this.lworldExtX},e.prototype.setWorldExtX=function(t){this.lworldExtX=t},e.prototype.getWorldExtY=function(){return this.lworldExtY},e.prototype.setWorldExtY=function(t){this.lworldExtY=t},e.prototype.getDeviceOrgX=function(){return this.ldeviceOrgX},e.prototype.setDeviceOrgX=function(t){this.ldeviceOrgX=t},e.prototype.getDeviceOrgY=function(){return this.ldeviceOrgY},e.prototype.setDeviceOrgY=function(t){this.ldeviceOrgY=t},e.prototype.getDeviceExtX=function(){return this.ldeviceExtX},e.prototype.setDeviceExtX=function(t){this.ldeviceExtX=t},e.prototype.getDeviceExtY=function(){return this.ldeviceExtY},e.prototype.setDeviceExtY=function(t){this.ldeviceExtY=t},e.prototype.transformX=function(t){var i=0,l=this.lworldExtX;return l!=0&&(i=this.ldeviceOrgX+(t-this.lworldOrgX)*this.ldeviceExtX/l),i},e.prototype.transformY=function(t){var i=0,l=this.lworldExtY;return l!=0&&(i=this.ldeviceOrgY+(t-this.lworldOrgY)*this.ldeviceExtY/l),i},e.prototype.inverseTransformX=function(t){var i=0,l=this.ldeviceExtX;return l!=0&&(i=this.lworldOrgX+(t-this.ldeviceOrgX)*this.lworldExtX/l),i},e.prototype.inverseTransformY=function(t){var i=0,l=this.ldeviceExtY;return l!=0&&(i=this.lworldOrgY+(t-this.ldeviceOrgY)*this.lworldExtY/l),i},e.prototype.inverseTransformPoint=function(t){var i=new o(this.inverseTransformX(t.x),this.inverseTransformY(t.y));return i},N.exports=e},function(N,I,T){function o(r){if(Array.isArray(r)){for(var h=0,a=Array(r.length);ht.ADAPTATION_LOWER_NODE_LIMIT&&(this.coolingFactor=Math.max(this.coolingFactor*t.COOLING_ADAPTATION_FACTOR,this.coolingFactor-(r-t.ADAPTATION_LOWER_NODE_LIMIT)/(t.ADAPTATION_UPPER_NODE_LIMIT-t.ADAPTATION_LOWER_NODE_LIMIT)*this.coolingFactor*(1-t.COOLING_ADAPTATION_FACTOR))),this.maxNodeDisplacement=t.MAX_NODE_DISPLACEMENT_INCREMENTAL):(r>t.ADAPTATION_LOWER_NODE_LIMIT?this.coolingFactor=Math.max(t.COOLING_ADAPTATION_FACTOR,1-(r-t.ADAPTATION_LOWER_NODE_LIMIT)/(t.ADAPTATION_UPPER_NODE_LIMIT-t.ADAPTATION_LOWER_NODE_LIMIT)*(1-t.COOLING_ADAPTATION_FACTOR)):this.coolingFactor=1,this.initialCoolingFactor=this.coolingFactor,this.maxNodeDisplacement=t.MAX_NODE_DISPLACEMENT),this.maxIterations=Math.max(this.getAllNodes().length*5,this.maxIterations),this.totalDisplacementThreshold=this.displacementThresholdPerNode*this.getAllNodes().length,this.repulsionRange=this.calcRepulsionRange()},n.prototype.calcSpringForces=function(){for(var r=this.getAllEdges(),h,a=0;a0&&arguments[0]!==void 0?arguments[0]:!0,h=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,a,p,v,D,u=this.getAllNodes(),E;if(this.useFRGridVariant)for(this.totalIterations%t.GRID_CALCULATION_CHECK_PERIOD==1&&r&&this.updateGrid(),E=new Set,a=0;ay||E>y)&&(r.gravitationForceX=-this.gravityConstant*v,r.gravitationForceY=-this.gravityConstant*D)):(y=h.getEstimatedSize()*this.compoundGravityRangeFactor,(u>y||E>y)&&(r.gravitationForceX=-this.gravityConstant*v*this.compoundGravityConstant,r.gravitationForceY=-this.gravityConstant*D*this.compoundGravityConstant))},n.prototype.isConverged=function(){var r,h=!1;return this.totalIterations>this.maxIterations/3&&(h=Math.abs(this.totalDisplacement-this.oldTotalDisplacement)<2),r=this.totalDisplacement=u.length||y>=u[0].length)){for(var O=0;On}}]),l}();N.exports=i},function(N,I,T){var o=function(){function i(l,g){for(var n=0;n2&&arguments[2]!==void 0?arguments[2]:1,d=arguments.length>3&&arguments[3]!==void 0?arguments[3]:-1,r=arguments.length>4&&arguments[4]!==void 0?arguments[4]:-1;e(this,i),this.sequence1=l,this.sequence2=g,this.match_score=n,this.mismatch_penalty=d,this.gap_penalty=r,this.iMax=l.length+1,this.jMax=g.length+1,this.grid=new Array(this.iMax);for(var h=0;h=0;l--){var g=this.listeners[l];g.event===t&&g.callback===i&&this.listeners.splice(l,1)}},e.emit=function(t,i){for(var l=0;lg.coolingFactor*g.maxNodeDisplacement&&(this.displacementX=g.coolingFactor*g.maxNodeDisplacement*t.sign(this.displacementX)),Math.abs(this.displacementY)>g.coolingFactor*g.maxNodeDisplacement&&(this.displacementY=g.coolingFactor*g.maxNodeDisplacement*t.sign(this.displacementY)),this.child==null?this.moveBy(this.displacementX,this.displacementY):this.child.getNodes().length==0?this.moveBy(this.displacementX,this.displacementY):this.propogateDisplacementToChildren(this.displacementX,this.displacementY),g.totalDisplacement+=Math.abs(this.displacementX)+Math.abs(this.displacementY),this.springForceX=0,this.springForceY=0,this.repulsionForceX=0,this.repulsionForceY=0,this.gravitationForceX=0,this.gravitationForceY=0,this.displacementX=0,this.displacementY=0},i.prototype.propogateDisplacementToChildren=function(g,n){for(var d=this.getChild().getNodes(),r,h=0;h0)this.positionNodesRadially(s);else{this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var f=new Set(this.getAllNodes()),c=this.nodesWithGravity.filter(function(L){return f.has(L)});this.graphManager.setAllNodesToApplyGravitation(c),this.positionNodesRandomly()}}return this.initSpringEmbedder(),this.runSpringEmbedder(),!0},y.prototype.tick=function(){if(this.totalIterations++,this.totalIterations===this.maxIterations&&!this.isTreeGrowing&&!this.isGrowthFinished)if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;if(this.totalIterations%d.CONVERGENCE_CHECK_PERIOD==0&&!this.isTreeGrowing&&!this.isGrowthFinished){if(this.isConverged())if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;this.coolingCycle++,this.layoutQuality==0?this.coolingAdjuster=this.coolingCycle:this.layoutQuality==1&&(this.coolingAdjuster=this.coolingCycle/3),this.coolingFactor=Math.max(this.initialCoolingFactor-Math.pow(this.coolingCycle,Math.log(100*(this.initialCoolingFactor-this.finalTemperature))/Math.log(this.maxCoolingCycle))/100*this.coolingAdjuster,this.finalTemperature),this.animationPeriod=Math.ceil(this.initialAnimationPeriod*Math.sqrt(this.coolingFactor))}if(this.isTreeGrowing){if(this.growTreeIterations%10==0)if(this.prunedNodesAll.length>0){this.graphManager.updateBounds(),this.updateGrid(),this.growTree(this.prunedNodesAll),this.graphManager.resetAllNodesToApplyGravitation();var s=new Set(this.getAllNodes()),f=this.nodesWithGravity.filter(function(A){return s.has(A)});this.graphManager.setAllNodesToApplyGravitation(f),this.graphManager.updateBounds(),this.updateGrid(),this.coolingFactor=d.DEFAULT_COOLING_FACTOR_INCREMENTAL}else this.isTreeGrowing=!1,this.isGrowthFinished=!0;this.growTreeIterations++}if(this.isGrowthFinished){if(this.isConverged())return!0;this.afterGrowthIterations%10==0&&(this.graphManager.updateBounds(),this.updateGrid()),this.coolingFactor=d.DEFAULT_COOLING_FACTOR_INCREMENTAL*((100-this.afterGrowthIterations)/100),this.afterGrowthIterations++}var c=!this.isTreeGrowing&&!this.isGrowthFinished,L=this.growTreeIterations%10==1&&this.isTreeGrowing||this.afterGrowthIterations%10==1&&this.isGrowthFinished;return this.totalDisplacement=0,this.graphManager.updateBounds(),this.calcSpringForces(),this.calcRepulsionForces(c,L),this.calcGravitationalForces(),this.moveNodes(),this.animate(),!1},y.prototype.getPositionsData=function(){for(var s=this.graphManager.getAllNodes(),f={},c=0;c1){var C;for(C=0;CL&&(L=Math.floor(R.y)),m=Math.floor(R.x+n.DEFAULT_COMPONENT_SEPERATION)}this.transform(new a(r.WORLD_CENTER_X-R.x/2,r.WORLD_CENTER_Y-R.y/2))},y.radialLayout=function(s,f,c){var L=Math.max(this.maxDiagonalInTree(s),n.DEFAULT_RADIAL_SEPARATION);y.branchRadialLayout(f,null,0,359,0,L);var A=u.calculateBounds(s),m=new E;m.setDeviceOrgX(A.getMinX()),m.setDeviceOrgY(A.getMinY()),m.setWorldOrgX(c.x),m.setWorldOrgY(c.y);for(var R=0;R1;){var X=_[0];_.splice(0,1);var H=w.indexOf(X);H>=0&&w.splice(H,1),U--,x--}f!=null?P=(w.indexOf(_[0])+1)%U:P=0;for(var W=Math.abs(L-c)/x,B=P;F!=x;B=++B%U){var K=w[B].getOtherEnd(s);if(K!=f){var j=(c+F*W)%360,ht=(j+W)%360;y.branchRadialLayout(K,s,j,ht,A+m,m),F++}}},y.maxDiagonalInTree=function(s){for(var f=v.MIN_VALUE,c=0;cf&&(f=A)}return f},y.prototype.calcRepulsionRange=function(){return 2*(this.level+1)*this.idealEdgeLength},y.prototype.groupZeroDegreeMembers=function(){var s=this,f={};this.memberGroups={},this.idToDummyNode={};for(var c=[],L=this.graphManager.getAllNodes(),A=0;A"u"&&(f[C]=[]),f[C]=f[C].concat(m)}Object.keys(f).forEach(function(M){if(f[M].length>1){var S="DummyCompound_"+M;s.memberGroups[S]=f[M];var Y=f[M][0].getParent(),w=new l(s.graphManager);w.id=S,w.paddingLeft=Y.paddingLeft||0,w.paddingRight=Y.paddingRight||0,w.paddingBottom=Y.paddingBottom||0,w.paddingTop=Y.paddingTop||0,s.idToDummyNode[S]=w;var x=s.getGraphManager().add(s.newGraph(),w),F=Y.getChild();F.add(w);for(var U=0;U=0;s--){var f=this.compoundOrder[s],c=f.id,L=f.paddingLeft,A=f.paddingTop;this.adjustLocations(this.tiledMemberPack[c],f.rect.x,f.rect.y,L,A)}},y.prototype.repopulateZeroDegreeMembers=function(){var s=this,f=this.tiledZeroDegreePack;Object.keys(f).forEach(function(c){var L=s.idToDummyNode[c],A=L.paddingLeft,m=L.paddingTop;s.adjustLocations(f[c],L.rect.x,L.rect.y,A,m)})},y.prototype.getToBeTiled=function(s){var f=s.id;if(this.toBeTiled[f]!=null)return this.toBeTiled[f];var c=s.getChild();if(c==null)return this.toBeTiled[f]=!1,!1;for(var L=c.getNodes(),A=0;A0)return this.toBeTiled[f]=!1,!1;if(m.getChild()==null){this.toBeTiled[m.id]=!1;continue}if(!this.getToBeTiled(m))return this.toBeTiled[f]=!1,!1}return this.toBeTiled[f]=!0,!0},y.prototype.getNodeDegree=function(s){s.id;for(var f=s.getEdges(),c=0,L=0;LM&&(M=Y.rect.height)}c+=M+s.verticalPadding}},y.prototype.tileCompoundMembers=function(s,f){var c=this;this.tiledMemberPack=[],Object.keys(s).forEach(function(L){var A=f[L];c.tiledMemberPack[L]=c.tileNodes(s[L],A.paddingLeft+A.paddingRight),A.rect.width=c.tiledMemberPack[L].width,A.rect.height=c.tiledMemberPack[L].height})},y.prototype.tileNodes=function(s,f){var c=n.TILING_PADDING_VERTICAL,L=n.TILING_PADDING_HORIZONTAL,A={rows:[],rowWidth:[],rowHeight:[],width:0,height:f,verticalPadding:c,horizontalPadding:L};s.sort(function(C,M){return C.rect.width*C.rect.height>M.rect.width*M.rect.height?-1:C.rect.width*C.rect.height0&&(R+=s.horizontalPadding),s.rowWidth[c]=R,s.width0&&(C+=s.verticalPadding);var M=0;C>s.rowHeight[c]&&(M=s.rowHeight[c],s.rowHeight[c]=C,M=s.rowHeight[c]-M),s.height+=M,s.rows[c].push(f)},y.prototype.getShortestRowIndex=function(s){for(var f=-1,c=Number.MAX_VALUE,L=0;Lc&&(f=L,c=s.rowWidth[L]);return f},y.prototype.canAddHorizontal=function(s,f,c){var L=this.getShortestRowIndex(s);if(L<0)return!0;var A=s.rowWidth[L];if(A+s.horizontalPadding+f<=s.width)return!0;var m=0;s.rowHeight[L]0&&(m=c+s.verticalPadding-s.rowHeight[L]);var R;s.width-A>=f+s.horizontalPadding?R=(s.height+m)/(A+f+s.horizontalPadding):R=(s.height+m)/s.width,m=c+s.verticalPadding;var C;return s.widthm&&f!=c){L.splice(-1,1),s.rows[c].push(A),s.rowWidth[f]=s.rowWidth[f]-m,s.rowWidth[c]=s.rowWidth[c]+m,s.width=s.rowWidth[instance.getLongestRowIndex(s)];for(var R=Number.MIN_VALUE,C=0;CR&&(R=L[C].height);f>0&&(R+=s.verticalPadding);var M=s.rowHeight[f]+s.rowHeight[c];s.rowHeight[f]=R,s.rowHeight[c]0)for(var F=A;F<=m;F++)x[0]+=this.grid[F][R-1].length+this.grid[F][R].length-1;if(m0)for(var F=R;F<=C;F++)x[3]+=this.grid[A-1][F].length+this.grid[A][F].length-1;for(var U=v.MAX_VALUE,P,_,X=0;X0){var C;C=E.getGraphManager().add(E.newGraph(),c),this.processChildrenList(C,f,E)}}},a.prototype.stop=function(){return this.stopped=!0,this};var v=function(u){u("layout","cose-bilkent",a)};typeof cytoscape<"u"&&v(cytoscape),I.exports=v}])})})(tt);var ct=tt.exports;const pt=lt(ct);J.use(pt);function et(G,b){G.forEach(N=>{const I={id:N.id,labelText:N.label,height:N.height,width:N.width,padding:N.padding??0};Object.keys(N).forEach(T=>{["id","label","height","width","padding","x","y"].includes(T)||(I[T]=N[T])}),b.add({group:"nodes",data:I,position:{x:N.x??0,y:N.y??0}})})}V(et,"addNodes");function rt(G,b){G.forEach(N=>{const I={id:N.id,source:N.start,target:N.end};Object.keys(N).forEach(T=>{["id","start","end"].includes(T)||(I[T]=N[T])}),b.add({group:"edges",data:I})})}V(rt,"addEdges");function it(G){return new Promise(b=>{const N=gt("body").append("div").attr("id","cy").attr("style","display:none"),I=J({container:document.getElementById("cy"),style:[{selector:"edge",style:{"curve-style":"bezier"}}]});N.remove(),et(G.nodes,I),rt(G.edges,I),I.nodes().forEach(function(o){o.layoutDimensions=()=>{const e=o.data();return{w:e.width,h:e.height}}});const T={name:"cose-bilkent",quality:"proof",styleEnabled:!1,animate:!1};I.layout(T).run(),I.ready(o=>{k.info("Cytoscape ready",o),b(I)})})}V(it,"createCytoscapeInstance");function nt(G){return G.nodes().map(b=>{const N=b.data(),I=b.position(),T={id:N.id,x:I.x,y:I.y};return Object.keys(N).forEach(o=>{o!=="id"&&(T[o]=N[o])}),T})}V(nt,"extractPositionedNodes");function ot(G){return G.edges().map(b=>{const N=b.data(),I=b._private.rscratch,T={id:N.id,source:N.source,target:N.target,startX:I.startX,startY:I.startY,midX:I.midX,midY:I.midY,endX:I.endX,endY:I.endY};return Object.keys(N).forEach(o=>{["id","source","target"].includes(o)||(T[o]=N[o])}),T})}V(ot,"extractPositionedEdges");async function st(G,b){k.debug("Starting cose-bilkent layout algorithm");try{at(G);const N=await it(G),I=nt(N),T=ot(N);return k.debug(`Layout completed: ${I.length} nodes, ${T.length} edges`),{nodes:I,edges:T}}catch(N){throw k.error("Error in cose-bilkent layout algorithm:",N),N}}V(st,"executeCoseBilkentLayout");function at(G){if(!G)throw new Error("Layout data is required");if(!G.config)throw new Error("Configuration is required in layout data");if(!G.rootNode)throw new Error("Root node is required");if(!G.nodes||!Array.isArray(G.nodes))throw new Error("No nodes found in layout data");if(!Array.isArray(G.edges))throw new Error("Edges array is required in layout data");return!0}V(at,"validateLayoutData");var dt=V(async(G,b,{insertCluster:N,insertEdge:I,insertEdgeLabel:T,insertMarkers:o,insertNode:e,log:t,positionEdgeLabel:i},{algorithm:l})=>{const g={},n={},d=b.select("g");o(d,G.markers,G.type,G.diagramId);const r=d.insert("g").attr("class","subgraphs"),h=d.insert("g").attr("class","edgePaths"),a=d.insert("g").attr("class","edgeLabels"),p=d.insert("g").attr("class","nodes");t.debug("Inserting nodes into DOM for dimension calculation"),await Promise.all(G.nodes.map(async u=>{if(u.isGroup){const E={...u};n[u.id]=E,g[u.id]=E,await N(r,u)}else{const E={...u};g[u.id]=E;const y=await e(p,u,{config:G.config,dir:G.direction||"TB"}),O=y.node().getBBox();E.width=O.width,E.height=O.height,E.domId=y,t.debug(`Node ${u.id} dimensions: ${O.width}x${O.height}`)}})),t.debug("Running cose-bilkent layout algorithm");const v={...G,nodes:G.nodes.map(u=>{const E=g[u.id];return{...u,width:E.width,height:E.height}})},D=await st(v,G.config);t.debug("Positioning nodes based on layout results"),D.nodes.forEach(u=>{const E=g[u.id];E!=null&&E.domId&&(E.domId.attr("transform",`translate(${u.x}, ${u.y})`),E.x=u.x,E.y=u.y,t.debug(`Positioned node ${E.id} at center (${u.x}, ${u.y})`))}),D.edges.forEach(u=>{const E=G.edges.find(y=>y.id===u.id);E&&(E.points=[{x:u.startX,y:u.startY},{x:u.midX,y:u.midY},{x:u.endX,y:u.endY}])}),t.debug("Inserting and positioning edges"),await Promise.all(G.edges.map(async u=>{await T(a,u);const E=g[u.start??""],y=g[u.end??""];if(E&&y){const O=D.edges.find(s=>s.id===u.id);if(O){t.debug("APA01 positionedEdge",O);const s={...u},f=I(h,s,n,G.type,E,y,G.diagramId);i(s,f)}else{const s={...u,points:[{x:E.x||0,y:E.y||0},{x:y.x||0,y:y.y||0}]},f=I(h,s,n,G.type,E,y,G.diagramId);i(s,f)}}})),t.debug("Cose-bilkent rendering completed")},"render"),Lt=dt;export{Lt as render}; diff --git a/assets/chunks/cytoscape.esm.CyJtwmzi.js b/assets/chunks/cytoscape.esm.CyJtwmzi.js new file mode 100644 index 000000000..8572b2939 --- /dev/null +++ b/assets/chunks/cytoscape.esm.CyJtwmzi.js @@ -0,0 +1,331 @@ +function Bs(r,e){(e==null||e>r.length)&&(e=r.length);for(var t=0,a=Array(e);t=r.length?{done:!0}:{done:!1,value:r[a++]}},e:function(l){throw l},f:n}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var i,s=!0,o=!1;return{s:function(){t=t.call(r)},n:function(){var l=t.next();return s=l.done,l},e:function(l){o=!0,i=l},f:function(){try{s||t.return==null||t.return()}finally{if(o)throw i}}}}function Jl(r,e,t){return(e=jl(e))in r?Object.defineProperty(r,e,{value:t,enumerable:!0,configurable:!0,writable:!0}):r[e]=t,r}function ac(r){if(typeof Symbol<"u"&&r[Symbol.iterator]!=null||r["@@iterator"]!=null)return Array.from(r)}function nc(r,e){var t=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(t!=null){var a,n,i,s,o=[],l=!0,u=!1;try{if(i=(t=t.call(r)).next,e===0){if(Object(t)!==t)return;l=!1}else for(;!(l=(a=i.call(t)).done)&&(o.push(a.value),o.length!==e);l=!0);}catch(v){u=!0,n=v}finally{try{if(!l&&t.return!=null&&(s=t.return(),Object(s)!==s))return}finally{if(u)throw n}}return o}}function ic(){throw new TypeError(`Invalid attempt to destructure non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function sc(){throw new TypeError(`Invalid attempt to spread non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function Je(r,e){return ec(r)||nc(r,e)||Xs(r,e)||ic()}function mn(r){return rc(r)||ac(r)||Xs(r)||sc()}function oc(r,e){if(typeof r!="object"||!r)return r;var t=r[Symbol.toPrimitive];if(t!==void 0){var a=t.call(r,e);if(typeof a!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(r)}function jl(r){var e=oc(r,"string");return typeof e=="symbol"?e:e+""}function ar(r){"@babel/helpers - typeof";return ar=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},ar(r)}function Xs(r,e){if(r){if(typeof r=="string")return Bs(r,e);var t={}.toString.call(r).slice(8,-1);return t==="Object"&&r.constructor&&(t=r.constructor.name),t==="Map"||t==="Set"?Array.from(r):t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?Bs(r,e):void 0}}var rr=typeof window>"u"?null:window,To=rr?rr.navigator:null;rr&&rr.document;var uc=ar(""),ev=ar({}),lc=ar(function(){}),vc=typeof HTMLElement>"u"?"undefined":ar(HTMLElement),La=function(e){return e&&e.instanceString&&Ue(e.instanceString)?e.instanceString():null},ge=function(e){return e!=null&&ar(e)==uc},Ue=function(e){return e!=null&&ar(e)===lc},_e=function(e){return!Dr(e)&&(Array.isArray?Array.isArray(e):e!=null&&e instanceof Array)},Le=function(e){return e!=null&&ar(e)===ev&&!_e(e)&&e.constructor===Object},fc=function(e){return e!=null&&ar(e)===ev},ae=function(e){return e!=null&&ar(e)===ar(1)&&!isNaN(e)},cc=function(e){return ae(e)&&Math.floor(e)===e},bn=function(e){if(vc!=="undefined")return e!=null&&e instanceof HTMLElement},Dr=function(e){return Ia(e)||rv(e)},Ia=function(e){return La(e)==="collection"&&e._private.single},rv=function(e){return La(e)==="collection"&&!e._private.single},Ys=function(e){return La(e)==="core"},tv=function(e){return La(e)==="stylesheet"},dc=function(e){return La(e)==="event"},ut=function(e){return e==null?!0:!!(e===""||e.match(/^\s+$/))},hc=function(e){return typeof HTMLElement>"u"?!1:e instanceof HTMLElement},gc=function(e){return Le(e)&&ae(e.x1)&&ae(e.x2)&&ae(e.y1)&&ae(e.y2)},pc=function(e){return fc(e)&&Ue(e.then)},yc=function(){return To&&To.userAgent.match(/msie|trident|edge/i)},Qt=function(e,t){t||(t=function(){if(arguments.length===1)return arguments[0];if(arguments.length===0)return"undefined";for(var i=[],s=0;st?1:0},Tc=function(e,t){return-1*nv(e,t)},be=Object.assign!=null?Object.assign.bind(Object):function(r){for(var e=arguments,t=1;t1&&(g-=1),g<1/6?d+(y-d)*6*g:g<1/2?y:g<2/3?d+(y-d)*(2/3-g)*6:d}var f=new RegExp("^"+wc+"$").exec(e);if(f){if(a=parseInt(f[1]),a<0?a=(360- -1*a%360)%360:a>360&&(a=a%360),a/=360,n=parseFloat(f[2]),n<0||n>100||(n=n/100,i=parseFloat(f[3]),i<0||i>100)||(i=i/100,s=f[4],s!==void 0&&(s=parseFloat(s),s<0||s>1)))return;if(n===0)o=l=u=Math.round(i*255);else{var c=i<.5?i*(1+n):i+n-i*n,h=2*i-c;o=Math.round(255*v(h,c,a+1/3)),l=Math.round(255*v(h,c,a)),u=Math.round(255*v(h,c,a-1/3))}t=[o,l,u,s]}return t},Dc=function(e){var t,a=new RegExp("^"+mc+"$").exec(e);if(a){t=[];for(var n=[],i=1;i<=3;i++){var s=a[i];if(s[s.length-1]==="%"&&(n[i]=!0),s=parseFloat(s),n[i]&&(s=s/100*255),s<0||s>255)return;t.push(Math.floor(s))}var o=n[1]||n[2]||n[3],l=n[1]&&n[2]&&n[3];if(o&&!l)return;var u=a[4];if(u!==void 0){if(u=parseFloat(u),u<0||u>1)return;t.push(u)}}return t},Bc=function(e){return Pc[e.toLowerCase()]},iv=function(e){return(_e(e)?e:null)||Bc(e)||Sc(e)||Dc(e)||kc(e)},Pc={transparent:[0,0,0,0],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},sv=function(e){for(var t=e.map,a=e.keys,n=a.length,i=0;i=l||R<0||m&&L>=c}function T(){var A=e();if(x(A))return k(A);d=setTimeout(T,C(A))}function k(A){return d=void 0,b&&v?w(A):(v=f=void 0,h)}function D(){d!==void 0&&clearTimeout(d),g=0,v=y=f=d=void 0}function B(){return d===void 0?h:k(e())}function P(){var A=e(),R=x(A);if(v=arguments,f=this,y=A,R){if(d===void 0)return E(y);if(m)return clearTimeout(d),d=setTimeout(T,l),w(y)}return d===void 0&&(d=setTimeout(T,l)),h}return P.cancel=D,P.flush=B,P}return fi=s,fi}var Vc=Fc(),Fa=Oa(Vc),ci=rr?rr.performance:null,lv=ci&&ci.now?function(){return ci.now()}:function(){return Date.now()},qc=function(){if(rr){if(rr.requestAnimationFrame)return function(r){rr.requestAnimationFrame(r)};if(rr.mozRequestAnimationFrame)return function(r){rr.mozRequestAnimationFrame(r)};if(rr.webkitRequestAnimationFrame)return function(r){rr.webkitRequestAnimationFrame(r)};if(rr.msRequestAnimationFrame)return function(r){rr.msRequestAnimationFrame(r)}}return function(r){r&&setTimeout(function(){r(lv())},1e3/60)}}(),wn=function(e){return qc(e)},Yr=lv,Tt=9261,vv=65599,Ht=5381,fv=function(e){for(var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Tt,a=t,n;n=e.next(),!n.done;)a=a*vv+n.value|0;return a},Ca=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Tt;return t*vv+e|0},Ta=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Ht;return(t<<5)+t+e|0},_c=function(e,t){return e*2097152+t},et=function(e){return e[0]*2097152+e[1]},Xa=function(e,t){return[Ca(e[0],t[0]),Ta(e[1],t[1])]},qo=function(e,t){var a={value:0,done:!1},n=0,i=e.length,s={next:function(){return n=0;n--)e[n]===t&&e.splice(n,1)},eo=function(e){e.splice(0,e.length)},Qc=function(e,t){for(var a=0;a"u"?"undefined":ar(Set))!==jc?Set:ed,In=function(e,t){var a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0;if(e===void 0||t===void 0||!Ys(e)){$e("An element must have a core reference and parameters set");return}var n=t.group;if(n==null&&(t.data&&t.data.source!=null&&t.data.target!=null?n="edges":n="nodes"),n!=="nodes"&&n!=="edges"){$e("An element must be of type `nodes` or `edges`; you specified `"+n+"`");return}this.length=1,this[0]=this;var i=this._private={cy:e,single:!0,data:t.data||{},position:t.position||{x:0,y:0},autoWidth:void 0,autoHeight:void 0,autoPadding:void 0,compoundBoundsClean:!1,listeners:[],group:n,style:{},rstyle:{},styleCxts:[],styleKeys:{},removed:!0,selected:!!t.selected,selectable:t.selectable===void 0?!0:!!t.selectable,locked:!!t.locked,grabbed:!1,grabbable:t.grabbable===void 0?!0:!!t.grabbable,pannable:t.pannable===void 0?n==="edges":!!t.pannable,active:!1,classes:new ra,animation:{current:[],queue:[]},rscratch:{},scratch:t.scratch||{},edges:[],children:[],parent:t.parent&&t.parent.isNode()?t.parent:null,traversalCache:{},backgrounding:!1,bbCache:null,bbCacheShift:{x:0,y:0},bodyBounds:null,overlayBounds:null,labelBounds:{all:null,source:null,target:null,main:null},arrowBounds:{source:null,target:null,"mid-source":null,"mid-target":null}};if(i.position.x==null&&(i.position.x=0),i.position.y==null&&(i.position.y=0),t.renderedPosition){var s=t.renderedPosition,o=e.pan(),l=e.zoom();i.position={x:(s.x-o.x)/l,y:(s.y-o.y)/l}}var u=[];_e(t.classes)?u=t.classes:ge(t.classes)&&(u=t.classes.split(/\s+/));for(var v=0,f=u.length;vm?1:0},v=function(p,m,b,w,E){var C;if(b==null&&(b=0),E==null&&(E=a),b<0)throw new Error("lo must be non-negative");for(w==null&&(w=p.length);bD;0<=D?k++:k--)T.push(k);return T}).apply(this).reverse(),x=[],w=0,E=C.length;wB;0<=B?++T:--T)P.push(s(p,b));return P},y=function(p,m,b,w){var E,C,x;for(w==null&&(w=a),E=p[b];b>m;){if(x=b-1>>1,C=p[x],w(E,C)<0){p[b]=C,b=x;continue}break}return p[b]=E},g=function(p,m,b){var w,E,C,x,T;for(b==null&&(b=a),E=p.length,T=m,C=p[m],w=2*m+1;w0;){var C=m.pop(),x=g(C),T=C.id();if(c[T]=x,x!==1/0)for(var k=C.neighborhood().intersect(d),D=0;D0)for(O.unshift(M);f[G];){var N=f[G];O.unshift(N.edge),O.unshift(N.node),V=N.node,G=V.id()}return o.spawn(O)}}}},od={kruskal:function(e){e=e||function(b){return 1};for(var t=this.byGroup(),a=t.nodes,n=t.edges,i=a.length,s=new Array(i),o=a,l=function(w){for(var E=0;E0;){if(E(),x++,w===v){for(var T=[],k=i,D=v,B=p[D];T.unshift(k),B!=null&&T.unshift(B),k=g[D],k!=null;)D=k.id(),B=p[D];return{found:!0,distance:f[w],path:this.spawn(T),steps:x}}h[w]=!0;for(var P=b._private.edges,A=0;AB&&(d[D]=B,m[D]=k,b[D]=E),!i){var P=k*v+T;!i&&d[P]>B&&(d[P]=B,m[P]=T,b[P]=E)}}}for(var A=0;A1&&arguments[1]!==void 0?arguments[1]:s,ie=b(we),de=[],he=ie;;){if(he==null)return t.spawn();var Ee=m(he),pe=Ee.edge,Se=Ee.pred;if(de.unshift(he[0]),he.same(ye)&&de.length>0)break;pe!=null&&de.unshift(pe),he=Se}return l.spawn(de)},C=0;C=0;v--){var f=u[v],c=f[1],h=f[2];(t[c]===o&&t[h]===l||t[c]===l&&t[h]===o)&&u.splice(v,1)}for(var d=0;dn;){var i=Math.floor(Math.random()*t.length);t=gd(i,e,t),a--}return t},pd={kargerStein:function(){var e=this,t=this.byGroup(),a=t.nodes,n=t.edges;n.unmergeBy(function(O){return O.isLoop()});var i=a.length,s=n.length,o=Math.ceil(Math.pow(Math.log(i)/Math.LN2,2)),l=Math.floor(i/hd);if(i<2){$e("At least 2 nodes are required for Karger-Stein algorithm");return}for(var u=[],v=0;v1&&arguments[1]!==void 0?arguments[1]:0,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,n=1/0,i=t;i1&&arguments[1]!==void 0?arguments[1]:0,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,n=-1/0,i=t;i1&&arguments[1]!==void 0?arguments[1]:0,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,n=0,i=0,s=t;s1&&arguments[1]!==void 0?arguments[1]:0,a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,s=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0;n?e=e.slice(t,a):(a0&&e.splice(0,t));for(var o=0,l=e.length-1;l>=0;l--){var u=e[l];s?isFinite(u)||(e[l]=-1/0,o++):e.splice(l,1)}i&&e.sort(function(c,h){return c-h});var v=e.length,f=Math.floor(v/2);return v%2!==0?e[f+1+o]:(e[f-1+o]+e[f+o])/2},Ed=function(e){return Math.PI*e/180},Ya=function(e,t){return Math.atan2(t,e)-Math.PI/2},ro=Math.log2||function(r){return Math.log(r)/Math.log(2)},to=function(e){return e>0?1:e<0?-1:0},Bt=function(e,t){return Math.sqrt(Et(e,t))},Et=function(e,t){var a=t.x-e.x,n=t.y-e.y;return a*a+n*n},Cd=function(e){for(var t=e.length,a=0,n=0;n=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(e.w!=null&&e.h!=null&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},Sd=function(e){return{x1:e.x1,x2:e.x2,w:e.w,y1:e.y1,y2:e.y2,h:e.h}},kd=function(e){e.x1=1/0,e.y1=1/0,e.x2=-1/0,e.y2=-1/0,e.w=0,e.h=0},Dd=function(e,t){e.x1=Math.min(e.x1,t.x1),e.x2=Math.max(e.x2,t.x2),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,t.y1),e.y2=Math.max(e.y2,t.y2),e.h=e.y2-e.y1},mv=function(e,t,a){e.x1=Math.min(e.x1,t),e.x2=Math.max(e.x2,t),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,a),e.y2=Math.max(e.y2,a),e.h=e.y2-e.y1},un=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;return e.x1-=t,e.x2+=t,e.y1-=t,e.y2+=t,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},ln=function(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[0],a,n,i,s;if(t.length===1)a=n=i=s=t[0];else if(t.length===2)a=i=t[0],s=n=t[1];else if(t.length===4){var o=Je(t,4);a=o[0],n=o[1],i=o[2],s=o[3]}return e.x1-=s,e.x2+=n,e.y1-=a,e.y2+=i,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},Uo=function(e,t){e.x1=t.x1,e.y1=t.y1,e.x2=t.x2,e.y2=t.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1},ao=function(e,t){return!(e.x1>t.x2||t.x1>e.x2||e.x2t.y2||t.y1>e.y2)},nt=function(e,t,a){return e.x1<=t&&t<=e.x2&&e.y1<=a&&a<=e.y2},Ko=function(e,t){return nt(e,t.x,t.y)},bv=function(e,t){return nt(e,t.x1,t.y1)&&nt(e,t.x2,t.y2)},Bd=(gi=Math.hypot)!==null&&gi!==void 0?gi:function(r,e){return Math.sqrt(r*r+e*e)};function Pd(r,e){if(r.length<3)throw new Error("Need at least 3 vertices");var t=function(T,k){return{x:T.x+k.x,y:T.y+k.y}},a=function(T,k){return{x:T.x-k.x,y:T.y-k.y}},n=function(T,k){return{x:T.x*k,y:T.y*k}},i=function(T,k){return T.x*k.y-T.y*k.x},s=function(T){var k=Bd(T.x,T.y);return k===0?{x:0,y:0}:{x:T.x/k,y:T.y/k}},o=function(T){for(var k=0,D=0;D7&&arguments[7]!==void 0?arguments[7]:"auto",u=l==="auto"?vt(i,s):l,v=i/2,f=s/2;u=Math.min(u,v,f);var c=u!==v,h=u!==f,d;if(c){var y=a-v+u-o,g=n-f-o,p=a+v-u+o,m=g;if(d=it(e,t,a,n,y,g,p,m,!1),d.length>0)return d}if(h){var b=a+v+o,w=n-f+u-o,E=b,C=n+f-u+o;if(d=it(e,t,a,n,b,w,E,C,!1),d.length>0)return d}if(c){var x=a-v+u-o,T=n+f+o,k=a+v-u+o,D=T;if(d=it(e,t,a,n,x,T,k,D,!1),d.length>0)return d}if(h){var B=a-v-o,P=n-f+u-o,A=B,R=n+f-u+o;if(d=it(e,t,a,n,B,P,A,R,!1),d.length>0)return d}var L;{var I=a-v+u,M=n-f+u;if(L=ya(e,t,a,n,I,M,u+o),L.length>0&&L[0]<=I&&L[1]<=M)return[L[0],L[1]]}{var O=a+v-u,V=n-f+u;if(L=ya(e,t,a,n,O,V,u+o),L.length>0&&L[0]>=O&&L[1]<=V)return[L[0],L[1]]}{var G=a+v-u,N=n+f-u;if(L=ya(e,t,a,n,G,N,u+o),L.length>0&&L[0]>=G&&L[1]>=N)return[L[0],L[1]]}{var F=a-v+u,U=n+f-u;if(L=ya(e,t,a,n,F,U,u+o),L.length>0&&L[0]<=F&&L[1]>=U)return[L[0],L[1]]}return[]},Rd=function(e,t,a,n,i,s,o){var l=o,u=Math.min(a,i),v=Math.max(a,i),f=Math.min(n,s),c=Math.max(n,s);return u-l<=e&&e<=v+l&&f-l<=t&&t<=c+l},Md=function(e,t,a,n,i,s,o,l,u){var v={x1:Math.min(a,o,i)-u,x2:Math.max(a,o,i)+u,y1:Math.min(n,l,s)-u,y2:Math.max(n,l,s)+u};return!(ev.x2||tv.y2)},Ld=function(e,t,a,n){a-=n;var i=t*t-4*e*a;if(i<0)return[];var s=Math.sqrt(i),o=2*e,l=(-t+s)/o,u=(-t-s)/o;return[l,u]},Id=function(e,t,a,n,i){var s=1e-5;e===0&&(e=s),t/=e,a/=e,n/=e;var o,l,u,v,f,c,h,d;if(l=(3*a-t*t)/9,u=-(27*n)+t*(9*a-2*(t*t)),u/=54,o=l*l*l+u*u,i[1]=0,h=t/3,o>0){f=u+Math.sqrt(o),f=f<0?-Math.pow(-f,1/3):Math.pow(f,1/3),c=u-Math.sqrt(o),c=c<0?-Math.pow(-c,1/3):Math.pow(c,1/3),i[0]=-h+f+c,h+=(f+c)/2,i[4]=i[2]=-h,h=Math.sqrt(3)*(-c+f)/2,i[3]=h,i[5]=-h;return}if(i[5]=i[3]=0,o===0){d=u<0?-Math.pow(-u,1/3):Math.pow(u,1/3),i[0]=-h+2*d,i[4]=i[2]=-(d+h);return}l=-l,v=l*l*l,v=Math.acos(u/Math.sqrt(v)),d=2*Math.sqrt(l),i[0]=-h+d*Math.cos(v/3),i[2]=-h+d*Math.cos((v+2*Math.PI)/3),i[4]=-h+d*Math.cos((v+4*Math.PI)/3)},Od=function(e,t,a,n,i,s,o,l){var u=1*a*a-4*a*i+2*a*o+4*i*i-4*i*o+o*o+n*n-4*n*s+2*n*l+4*s*s-4*s*l+l*l,v=1*9*a*i-3*a*a-3*a*o-6*i*i+3*i*o+9*n*s-3*n*n-3*n*l-6*s*s+3*s*l,f=1*3*a*a-6*a*i+a*o-a*e+2*i*i+2*i*e-o*e+3*n*n-6*n*s+n*l-n*t+2*s*s+2*s*t-l*t,c=1*a*i-a*a+a*e-i*e+n*s-n*n+n*t-s*t,h=[];Id(u,v,f,c,h);for(var d=1e-7,y=[],g=0;g<6;g+=2)Math.abs(h[g+1])=0&&h[g]<=1&&y.push(h[g]);y.push(1),y.push(0);for(var p=-1,m,b,w,E=0;E=0?wu?(e-i)*(e-i)+(t-s)*(t-s):v-c},Sr=function(e,t,a){for(var n,i,s,o,l,u=0,v=0;v=e&&e>=s||n<=e&&e<=s)l=(e-n)/(s-n)*(o-i)+i,l>t&&u++;else continue;return u%2!==0},Zr=function(e,t,a,n,i,s,o,l,u){var v=new Array(a.length),f;l[0]!=null?(f=Math.atan(l[1]/l[0]),l[0]<0?f=f+Math.PI/2:f=-f-Math.PI/2):f=l;for(var c=Math.cos(-f),h=Math.sin(-f),d=0;d0){var g=Cn(v,-u);y=En(g)}else y=v;return Sr(e,t,y)},zd=function(e,t,a,n,i,s,o,l){for(var u=new Array(a.length*2),v=0;v=0&&g<=1&&m.push(g),p>=0&&p<=1&&m.push(p),m.length===0)return[];var b=m[0]*l[0]+e,w=m[0]*l[1]+t;if(m.length>1){if(m[0]==m[1])return[b,w];var E=m[1]*l[0]+e,C=m[1]*l[1]+t;return[b,w,E,C]}else return[b,w]},pi=function(e,t,a){return t<=e&&e<=a||a<=e&&e<=t?e:e<=t&&t<=a||a<=t&&t<=e?t:a},it=function(e,t,a,n,i,s,o,l,u){var v=e-i,f=a-e,c=o-i,h=t-s,d=n-t,y=l-s,g=c*h-y*v,p=f*h-d*v,m=y*f-c*d;if(m!==0){var b=g/m,w=p/m,E=.001,C=0-E,x=1+E;return C<=b&&b<=x&&C<=w&&w<=x?[e+b*f,t+b*d]:u?[e+b*f,t+b*d]:[]}else return g===0||p===0?pi(e,a,o)===o?[o,l]:pi(e,a,i)===i?[i,s]:pi(i,o,a)===a?[a,n]:[]:[]},Vd=function(e,t,a,n,i){var s=[],o=n/2,l=i/2,u=t,v=a;s.push({x:u+o*e[0],y:v+l*e[1]});for(var f=1;f0){var y=Cn(f,-l);h=En(y)}else h=f}else h=a;for(var g,p,m,b,w=0;w2){for(var d=[v[0],v[1]],y=Math.pow(d[0]-e,2)+Math.pow(d[1]-t,2),g=1;gv&&(v=w)},get:function(b){return u[b]}},c=0;c0?L=R.edgesTo(A)[0]:L=A.edgesTo(R)[0];var I=n(L);A=A.id(),x[A]>x[B]+I&&(x[A]=x[B]+I,T.nodes.indexOf(A)<0?T.push(A):T.updateItem(A),C[A]=0,E[A]=[]),x[A]==x[B]+I&&(C[A]=C[A]+C[B],E[A].push(B))}else for(var M=0;M0;){for(var N=w.pop(),F=0;F0&&o.push(a[l]);o.length!==0&&i.push(n.collection(o))}return i},eh=function(e,t){for(var a=0;a5&&arguments[5]!==void 0?arguments[5]:ah,o=n,l,u,v=0;v=2?va(e,t,a,0,Jo,nh):va(e,t,a,0,Qo)},squaredEuclidean:function(e,t,a){return va(e,t,a,0,Jo)},manhattan:function(e,t,a){return va(e,t,a,0,Qo)},max:function(e,t,a){return va(e,t,a,-1/0,ih)}};Jt["squared-euclidean"]=Jt.squaredEuclidean;Jt.squaredeuclidean=Jt.squaredEuclidean;function Nn(r,e,t,a,n,i){var s;return Ue(r)?s=r:s=Jt[r]||Jt.euclidean,e===0&&Ue(r)?s(n,i):s(e,t,a,n,i)}var sh=cr({k:2,m:2,sensitivityThreshold:1e-4,distance:"euclidean",maxIterations:10,attributes:[],testMode:!1,testCentroids:null}),io=function(e){return sh(e)},Tn=function(e,t,a,n,i){var s=i!=="kMedoids",o=s?function(f){return a[f]}:function(f){return n[f](a)},l=function(c){return n[c](t)},u=a,v=t;return Nn(e,n.length,o,l,u,v)},mi=function(e,t,a){for(var n=a.length,i=new Array(n),s=new Array(n),o=new Array(t),l=null,u=0;ua)return!1}return!0},lh=function(e,t,a){for(var n=0;no&&(o=t[u][v],l=v);i[l].push(e[u])}for(var f=0;f=i.threshold||i.mode==="dendrogram"&&e.length===1)return!1;var d=t[s],y=t[n[s]],g;i.mode==="dendrogram"?g={left:d,right:y,key:d.key}:g={value:d.value.concat(y.value),key:d.key},e[d.index]=g,e.splice(y.index,1),t[d.key]=g;for(var p=0;pa[y.key][m.key]&&(l=a[y.key][m.key])):i.linkage==="max"?(l=a[d.key][m.key],a[d.key][m.key]0&&n.push(i);return n},nu=function(e,t,a){for(var n=[],i=0;io&&(s=u,o=t[i*e+u])}s>0&&n.push(s)}for(var v=0;vu&&(l=v,u=f)}a[i]=s[l]}return n=nu(e,t,a),n},iu=function(e){for(var t=this.cy(),a=this.nodes(),n=xh(e),i={},s=0;s=B?(P=B,B=R,A=L):R>P&&(P=R);for(var I=0;I0?1:0;x[k%n.minIterations*o+F]=U,N+=U}if(N>0&&(k>=n.minIterations-1||k==n.maxIterations-1)){for(var Q=0,K=0;K1||C>1)&&(o=!0),f[b]=[],m.outgoers().forEach(function(T){T.isEdge()&&f[b].push(T.id())})}else c[b]=[void 0,m.target().id()]}):s.forEach(function(m){var b=m.id();if(m.isNode()){var w=m.degree(!0);w%2&&(l?u?o=!0:u=b:l=b),f[b]=[],m.connectedEdges().forEach(function(E){return f[b].push(E.id())})}else c[b]=[m.source().id(),m.target().id()]});var h={found:!1,trail:void 0};if(o)return h;if(u&&l)if(i){if(v&&u!=v)return h;v=u}else{if(v&&u!=v&&l!=v)return h;v||(v=u)}else v||(v=s[0].id());var d=function(b){for(var w=b,E=[b],C,x,T;f[w].length;)C=f[w].shift(),x=c[C][0],T=c[C][1],w!=T?(f[T]=f[T].filter(function(k){return k!=C}),w=T):!i&&w!=x&&(f[x]=f[x].filter(function(k){return k!=C}),w=x),E.unshift(C),E.unshift(w);return E},y=[],g=[];for(g=d(v);g.length!=1;)f[g[0]].length==0?(y.unshift(s.getElementById(g.shift())),y.unshift(s.getElementById(g.shift()))):g=d(g.shift()).concat(g);y.unshift(s.getElementById(g.shift()));for(var p in f)if(f[p].length)return h;return h.found=!0,h.trail=this.spawn(y,!0),h}},Qa=function(){var e=this,t={},a=0,n=0,i=[],s=[],o={},l=function(c,h){for(var d=s.length-1,y=[],g=e.spawn();s[d].x!=c||s[d].y!=h;)y.push(s.pop().edge),d--;y.push(s.pop().edge),y.forEach(function(p){var m=p.connectedNodes().intersection(e);g.merge(p),m.forEach(function(b){var w=b.id(),E=b.connectedEdges().intersection(e);g.merge(b),t[w].cutVertex?g.merge(E.filter(function(C){return C.isLoop()})):g.merge(E)})}),i.push(g)},u=function(c,h,d){c===d&&(n+=1),t[h]={id:a,low:a++,cutVertex:!1};var y=e.getElementById(h).connectedEdges().intersection(e);if(y.size()===0)i.push(e.spawn(e.getElementById(h)));else{var g,p,m,b;y.forEach(function(w){g=w.source().id(),p=w.target().id(),m=g===h?p:g,m!==d&&(b=w.id(),o[b]||(o[b]=!0,s.push({x:h,y:m,edge:w})),m in t?t[h].low=Math.min(t[h].low,t[m].id):(u(c,m,h),t[h].low=Math.min(t[h].low,t[m].low),t[h].id<=t[m].low&&(t[h].cutVertex=!0,l(h,m))))})}};e.forEach(function(f){if(f.isNode()){var c=f.id();c in t||(n=0,u(c,c),t[c].cutVertex=n>1)}});var v=Object.keys(t).filter(function(f){return t[f].cutVertex}).map(function(f){return e.getElementById(f)});return{cut:e.spawn(v),components:i}},Ph={hopcroftTarjanBiconnected:Qa,htbc:Qa,htb:Qa,hopcroftTarjanBiconnectedComponents:Qa},Ja=function(){var e=this,t={},a=0,n=[],i=[],s=e.spawn(e),o=function(u){i.push(u),t[u]={index:a,low:a++,explored:!1};var v=e.getElementById(u).connectedEdges().intersection(e);if(v.forEach(function(y){var g=y.target().id();g!==u&&(g in t||o(g),t[g].explored||(t[u].low=Math.min(t[u].low,t[g].low)))}),t[u].index===t[u].low){for(var f=e.spawn();;){var c=i.pop();if(f.merge(e.getElementById(c)),t[c].low=t[u].index,t[c].explored=!0,c===u)break}var h=f.edgesWith(f),d=f.merge(h);n.push(d),s=s.difference(d)}};return e.forEach(function(l){if(l.isNode()){var u=l.id();u in t||o(u)}}),{cut:s,components:n}},Ah={tarjanStronglyConnected:Ja,tsc:Ja,tscc:Ja,tarjanStronglyConnectedComponents:Ja},Dv={};[Sa,sd,od,ld,fd,dd,pd,Hd,Xt,Yt,Rs,th,gh,bh,kh,Bh,Ph,Ah].forEach(function(r){be(Dv,r)});/*! +Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable +Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) +Licensed under The MIT License (http://opensource.org/licenses/MIT) +*/var Bv=0,Pv=1,Av=2,Nr=function(e){if(!(this instanceof Nr))return new Nr(e);this.id="Thenable/1.0.7",this.state=Bv,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},typeof e=="function"&&e.call(this,this.fulfill.bind(this),this.reject.bind(this))};Nr.prototype={fulfill:function(e){return su(this,Pv,"fulfillValue",e)},reject:function(e){return su(this,Av,"rejectReason",e)},then:function(e,t){var a=this,n=new Nr;return a.onFulfilled.push(uu(e,n,"fulfill")),a.onRejected.push(uu(t,n,"reject")),Rv(a),n.proxy}};var su=function(e,t,a,n){return e.state===Bv&&(e.state=t,e[a]=n,Rv(e)),e},Rv=function(e){e.state===Pv?ou(e,"onFulfilled",e.fulfillValue):e.state===Av&&ou(e,"onRejected",e.rejectReason)},ou=function(e,t,a){if(e[t].length!==0){var n=e[t];e[t]=[];var i=function(){for(var o=0;o0}},clearQueue:function(){return function(){var t=this,a=t.length!==void 0,n=a?t:[t],i=this._private.cy||this;if(!i.styleEnabled())return this;for(var s=0;s-1}return qi=e,qi}var _i,Ru;function Yh(){if(Ru)return _i;Ru=1;var r=Vn();function e(t,a){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,a])):n[i][1]=a,this}return _i=e,_i}var Gi,Mu;function Zh(){if(Mu)return Gi;Mu=1;var r=$h(),e=Uh(),t=Kh(),a=Xh(),n=Yh();function i(s){var o=-1,l=s==null?0:s.length;for(this.clear();++o-1&&a%1==0&&a0&&this.spawn(n).updateStyle().emit("class"),t},addClass:function(e){return this.toggleClass(e,!0)},hasClass:function(e){var t=this[0];return t!=null&&t._private.classes.has(e)},toggleClass:function(e,t){_e(e)||(e=e.match(/\S+/g)||[]);for(var a=this,n=t===void 0,i=[],s=0,o=a.length;s0&&this.spawn(i).updateStyle().emit("class"),a},removeClass:function(e){return this.toggleClass(e,!1)},flashClass:function(e,t){var a=this;if(t==null)t=250;else if(t===0)return a;return a.addClass(e),setTimeout(function(){a.removeClass(e)},t),a}};vn.className=vn.classNames=vn.classes;var Me={metaChar:"[\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:`"(?:\\\\"|[^"])*"|'(?:\\\\'|[^'])*'`,number:tr,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$",group:"node|edge|\\*",directedEdge:"\\s+->\\s+",undirectedEdge:"\\s+<->\\s+"};Me.variable="(?:[\\w-.]|(?:\\\\"+Me.metaChar+"))+";Me.className="(?:[\\w-]|(?:\\\\"+Me.metaChar+"))+";Me.value=Me.string+"|"+Me.number;Me.id=Me.variable;(function(){var r,e,t;for(r=Me.comparatorOp.split("|"),t=0;t=0)&&e!=="="&&(Me.comparatorOp+="|\\!"+e)})();var qe=function(){return{checks:[]}},se={GROUP:0,COLLECTION:1,FILTER:2,DATA_COMPARE:3,DATA_EXIST:4,DATA_BOOL:5,META_COMPARE:6,STATE:7,ID:8,CLASS:9,UNDIRECTED_EDGE:10,DIRECTED_EDGE:11,NODE_SOURCE:12,NODE_TARGET:13,NODE_NEIGHBOR:14,CHILD:15,DESCENDANT:16,PARENT:17,ANCESTOR:18,COMPOUND_SPLIT:19,TRUE:20},Os=[{selector:":selected",matches:function(e){return e.selected()}},{selector:":unselected",matches:function(e){return!e.selected()}},{selector:":selectable",matches:function(e){return e.selectable()}},{selector:":unselectable",matches:function(e){return!e.selectable()}},{selector:":locked",matches:function(e){return e.locked()}},{selector:":unlocked",matches:function(e){return!e.locked()}},{selector:":visible",matches:function(e){return e.visible()}},{selector:":hidden",matches:function(e){return!e.visible()}},{selector:":transparent",matches:function(e){return e.transparent()}},{selector:":grabbed",matches:function(e){return e.grabbed()}},{selector:":free",matches:function(e){return!e.grabbed()}},{selector:":removed",matches:function(e){return e.removed()}},{selector:":inside",matches:function(e){return!e.removed()}},{selector:":grabbable",matches:function(e){return e.grabbable()}},{selector:":ungrabbable",matches:function(e){return!e.grabbable()}},{selector:":animated",matches:function(e){return e.animated()}},{selector:":unanimated",matches:function(e){return!e.animated()}},{selector:":parent",matches:function(e){return e.isParent()}},{selector:":childless",matches:function(e){return e.isChildless()}},{selector:":child",matches:function(e){return e.isChild()}},{selector:":orphan",matches:function(e){return e.isOrphan()}},{selector:":nonorphan",matches:function(e){return e.isChild()}},{selector:":compound",matches:function(e){return e.isNode()?e.isParent():e.source().isParent()||e.target().isParent()}},{selector:":loop",matches:function(e){return e.isLoop()}},{selector:":simple",matches:function(e){return e.isSimple()}},{selector:":active",matches:function(e){return e.active()}},{selector:":inactive",matches:function(e){return!e.active()}},{selector:":backgrounding",matches:function(e){return e.backgrounding()}},{selector:":nonbackgrounding",matches:function(e){return!e.backgrounding()}}].sort(function(r,e){return Tc(r.selector,e.selector)}),Dg=function(){for(var r={},e,t=0;t0&&v.edgeCount>0)return Ve("The selector `"+e+"` is invalid because it uses both a compound selector and an edge selector"),!1;if(v.edgeCount>1)return Ve("The selector `"+e+"` is invalid because it uses multiple edge selectors"),!1;v.edgeCount===1&&Ve("The selector `"+e+"` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.")}return!0},Lg=function(){if(this.toStringCache!=null)return this.toStringCache;for(var e=function(v){return v??""},t=function(v){return ge(v)?'"'+v+'"':e(v)},a=function(v){return" "+v+" "},n=function(v,f){var c=v.type,h=v.value;switch(c){case se.GROUP:{var d=e(h);return d.substring(0,d.length-1)}case se.DATA_COMPARE:{var y=v.field,g=v.operator;return"["+y+a(e(g))+t(h)+"]"}case se.DATA_BOOL:{var p=v.operator,m=v.field;return"["+e(p)+m+"]"}case se.DATA_EXIST:{var b=v.field;return"["+b+"]"}case se.META_COMPARE:{var w=v.operator,E=v.field;return"[["+E+a(e(w))+t(h)+"]]"}case se.STATE:return h;case se.ID:return"#"+h;case se.CLASS:return"."+h;case se.PARENT:case se.CHILD:return i(v.parent,f)+a(">")+i(v.child,f);case se.ANCESTOR:case se.DESCENDANT:return i(v.ancestor,f)+" "+i(v.descendant,f);case se.COMPOUND_SPLIT:{var C=i(v.left,f),x=i(v.subject,f),T=i(v.right,f);return C+(C.length>0?" ":"")+x+T}case se.TRUE:return""}},i=function(v,f){return v.checks.reduce(function(c,h,d){return c+(f===v&&d===0?"$":"")+n(h,f)},"")},s="",o=0;o1&&o=0&&(t=t.replace("!",""),f=!0),t.indexOf("@")>=0&&(t=t.replace("@",""),v=!0),(i||o||v)&&(l=!i&&!s?"":""+e,u=""+a),v&&(e=l=l.toLowerCase(),a=u=u.toLowerCase()),t){case"*=":n=l.indexOf(u)>=0;break;case"$=":n=l.indexOf(u,l.length-u.length)>=0;break;case"^=":n=l.indexOf(u)===0;break;case"=":n=e===a;break;case">":c=!0,n=e>a;break;case">=":c=!0,n=e>=a;break;case"<":c=!0,n=e0;){var v=n.shift();e(v),i.add(v.id()),o&&a(n,i,v)}return r}function Vv(r,e,t){if(t.isParent())for(var a=t._private.children,n=0;n1&&arguments[1]!==void 0?arguments[1]:!0;return lo(this,r,e,Vv)};function qv(r,e,t){if(t.isChild()){var a=t._private.parent;e.has(a.id())||r.push(a)}}jt.forEachUp=function(r){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return lo(this,r,e,qv)};function _g(r,e,t){qv(r,e,t),Vv(r,e,t)}jt.forEachUpAndDown=function(r){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return lo(this,r,e,_g)};jt.ancestors=jt.parents;var Ba,_v;Ba=_v={data:Fe.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),removeData:Fe.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),scratch:Fe.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fe.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),rscratch:Fe.data({field:"rscratch",allowBinding:!1,allowSetting:!0,settingTriggersEvent:!1,allowGetting:!0}),removeRscratch:Fe.removeData({field:"rscratch",triggerEvent:!1}),id:function(){var e=this[0];if(e)return e._private.data.id}};Ba.attr=Ba.data;Ba.removeAttr=Ba.removeData;var Gg=_v,_n={};function ps(r){return function(e){var t=this;if(e===void 0&&(e=!0),t.length!==0)if(t.isNode()&&!t.removed()){for(var a=0,n=t[0],i=n._private.edges,s=0;se}),minIndegree:Nt("indegree",function(r,e){return re}),minOutdegree:Nt("outdegree",function(r,e){return re})});be(_n,{totalDegree:function(e){for(var t=0,a=this.nodes(),n=0;n0,c=f;f&&(v=v[0]);var h=c?v.position():{x:0,y:0};t!==void 0?u.position(e,t+h[e]):i!==void 0&&u.position({x:i.x+h.x,y:i.y+h.y})}else{var d=a.position(),y=o?a.parent():null,g=y&&y.length>0,p=g;g&&(y=y[0]);var m=p?y.position():{x:0,y:0};return i={x:d.x-m.x,y:d.y-m.y},e===void 0?i:i[e]}else if(!s)return;return this}};Or.modelPosition=Or.point=Or.position;Or.modelPositions=Or.points=Or.positions;Or.renderedPoint=Or.renderedPosition;Or.relativePoint=Or.relativePosition;var Hg=Gv,Zt,pt;Zt=pt={};pt.renderedBoundingBox=function(r){var e=this.boundingBox(r),t=this.cy(),a=t.zoom(),n=t.pan(),i=e.x1*a+n.x,s=e.x2*a+n.x,o=e.y1*a+n.y,l=e.y2*a+n.y;return{x1:i,x2:s,y1:o,y2:l,w:s-i,h:l-o}};pt.dirtyCompoundBoundsCache=function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,e=this.cy();return!e.styleEnabled()||!e.hasCompoundNodes()?this:(this.forEachUp(function(t){if(t.isParent()){var a=t._private;a.compoundBoundsClean=!1,a.bbCache=null,r||t.emitAndNotify("bounds")}}),this)};pt.updateCompoundBounds=function(){var r=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,e=this.cy();if(!e.styleEnabled()||!e.hasCompoundNodes())return this;if(!r&&e.batching())return this;function t(s){if(!s.isParent())return;var o=s._private,l=s.children(),u=s.pstyle("compound-sizing-wrt-labels").value==="include",v={width:{val:s.pstyle("min-width").pfValue,left:s.pstyle("min-width-bias-left"),right:s.pstyle("min-width-bias-right")},height:{val:s.pstyle("min-height").pfValue,top:s.pstyle("min-height-bias-top"),bottom:s.pstyle("min-height-bias-bottom")}},f=l.boundingBox({includeLabels:u,includeOverlays:!1,useCache:!1}),c=o.position;(f.w===0||f.h===0)&&(f={w:s.pstyle("width").pfValue,h:s.pstyle("height").pfValue},f.x1=c.x-f.w/2,f.x2=c.x+f.w/2,f.y1=c.y-f.h/2,f.y2=c.y+f.h/2);function h(k,D,B){var P=0,A=0,R=D+B;return k>0&&R>0&&(P=D/R*k,A=B/R*k),{biasDiff:P,biasComplementDiff:A}}function d(k,D,B,P){if(B.units==="%")switch(P){case"width":return k>0?B.pfValue*k:0;case"height":return D>0?B.pfValue*D:0;case"average":return k>0&&D>0?B.pfValue*(k+D)/2:0;case"min":return k>0&&D>0?k>D?B.pfValue*D:B.pfValue*k:0;case"max":return k>0&&D>0?k>D?B.pfValue*k:B.pfValue*D:0;default:return 0}else return B.units==="px"?B.pfValue:0}var y=v.width.left.value;v.width.left.units==="px"&&v.width.val>0&&(y=y*100/v.width.val);var g=v.width.right.value;v.width.right.units==="px"&&v.width.val>0&&(g=g*100/v.width.val);var p=v.height.top.value;v.height.top.units==="px"&&v.height.val>0&&(p=p*100/v.height.val);var m=v.height.bottom.value;v.height.bottom.units==="px"&&v.height.val>0&&(m=m*100/v.height.val);var b=h(v.width.val-f.w,y,g),w=b.biasDiff,E=b.biasComplementDiff,C=h(v.height.val-f.h,p,m),x=C.biasDiff,T=C.biasComplementDiff;o.autoPadding=d(f.w,f.h,s.pstyle("padding"),s.pstyle("padding-relative-to").value),o.autoWidth=Math.max(f.w,v.width.val),c.x=(-w+f.x1+f.x2+E)/2,o.autoHeight=Math.max(f.h,v.height.val),c.y=(-x+f.y1+f.y2+T)/2}for(var a=0;ae.x2?n:e.x2,e.y1=ae.y2?i:e.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1)},tt=function(e,t){return t==null?e:Ir(e,t.x1,t.y1,t.x2,t.y2)},fa=function(e,t,a){return Tr(e,t,a)},ja=function(e,t,a){if(!t.cy().headless()){var n=t._private,i=n.rstyle,s=i.arrowWidth/2,o=t.pstyle(a+"-arrow-shape").value,l,u;if(o!=="none"){a==="source"?(l=i.srcX,u=i.srcY):a==="target"?(l=i.tgtX,u=i.tgtY):(l=i.midX,u=i.midY);var v=n.arrowBounds=n.arrowBounds||{},f=v[a]=v[a]||{};f.x1=l-s,f.y1=u-s,f.x2=l+s,f.y2=u+s,f.w=f.x2-f.x1,f.h=f.y2-f.y1,un(f,1),Ir(e,f.x1,f.y1,f.x2,f.y2)}}},ys=function(e,t,a){if(!t.cy().headless()){var n;a?n=a+"-":n="";var i=t._private,s=i.rstyle,o=t.pstyle(n+"label").strValue;if(o){var l=t.pstyle("text-halign"),u=t.pstyle("text-valign"),v=fa(s,"labelWidth",a),f=fa(s,"labelHeight",a),c=fa(s,"labelX",a),h=fa(s,"labelY",a),d=t.pstyle(n+"text-margin-x").pfValue,y=t.pstyle(n+"text-margin-y").pfValue,g=t.isEdge(),p=t.pstyle(n+"text-rotation"),m=t.pstyle("text-outline-width").pfValue,b=t.pstyle("text-border-width").pfValue,w=b/2,E=t.pstyle("text-background-padding").pfValue,C=2,x=f,T=v,k=T/2,D=x/2,B,P,A,R;if(g)B=c-k,P=c+k,A=h-D,R=h+D;else{switch(l.value){case"left":B=c-T,P=c;break;case"center":B=c-k,P=c+k;break;case"right":B=c,P=c+T;break}switch(u.value){case"top":A=h-x,R=h;break;case"center":A=h-D,R=h+D;break;case"bottom":A=h,R=h+x;break}}var L=d-Math.max(m,w)-E-C,I=d+Math.max(m,w)+E+C,M=y-Math.max(m,w)-E-C,O=y+Math.max(m,w)+E+C;B+=L,P+=I,A+=M,R+=O;var V=a||"main",G=i.labelBounds,N=G[V]=G[V]||{};N.x1=B,N.y1=A,N.x2=P,N.y2=R,N.w=P-B,N.h=R-A,N.leftPad=L,N.rightPad=I,N.topPad=M,N.botPad=O;var F=g&&p.strValue==="autorotate",U=p.pfValue!=null&&p.pfValue!==0;if(F||U){var Q=F?fa(i.rstyle,"labelAngle",a):p.pfValue,K=Math.cos(Q),j=Math.sin(Q),re=(B+P)/2,ne=(A+R)/2;if(!g){switch(l.value){case"left":re=P;break;case"right":re=B;break}switch(u.value){case"top":ne=R;break;case"bottom":ne=A;break}}var J=function(Ce,we){return Ce=Ce-re,we=we-ne,{x:Ce*K-we*j+re,y:Ce*j+we*K+ne}},z=J(B,A),q=J(B,R),H=J(P,A),Y=J(P,R);B=Math.min(z.x,q.x,H.x,Y.x),P=Math.max(z.x,q.x,H.x,Y.x),A=Math.min(z.y,q.y,H.y,Y.y),R=Math.max(z.y,q.y,H.y,Y.y)}var te=V+"Rot",ce=G[te]=G[te]||{};ce.x1=B,ce.y1=A,ce.x2=P,ce.y2=R,ce.w=P-B,ce.h=R-A,Ir(e,B,A,P,R),Ir(i.labelBounds.all,B,A,P,R)}return e}},ol=function(e,t){if(!t.cy().headless()){var a=t.pstyle("outline-opacity").value,n=t.pstyle("outline-width").value,i=t.pstyle("outline-offset").value,s=n+i;Wv(e,t,a,s,"outside",s/2)}},Wv=function(e,t,a,n,i,s){if(!(a===0||n<=0||i==="inside")){var o=t.cy(),l=t.pstyle("shape").value,u=o.renderer().nodeShapes[l],v=t.position(),f=v.x,c=v.y,h=t.width(),d=t.height();if(u.hasMiterBounds){i==="center"&&(n/=2);var y=u.miterBounds(f,c,h,d,n);tt(e,y)}else s!=null&&s>0&&ln(e,[s,s,s,s])}},Wg=function(e,t){if(!t.cy().headless()){var a=t.pstyle("border-opacity").value,n=t.pstyle("border-width").pfValue,i=t.pstyle("border-position").value;Wv(e,t,a,n,i)}},$g=function(e,t){var a=e._private.cy,n=a.styleEnabled(),i=a.headless(),s=wr(),o=e._private,l=e.isNode(),u=e.isEdge(),v,f,c,h,d,y,g=o.rstyle,p=l&&n?e.pstyle("bounds-expansion").pfValue:[0],m=function(Ae){return Ae.pstyle("display").value!=="none"},b=!n||m(e)&&(!u||m(e.source())&&m(e.target()));if(b){var w=0,E=0;n&&t.includeOverlays&&(w=e.pstyle("overlay-opacity").value,w!==0&&(E=e.pstyle("overlay-padding").value));var C=0,x=0;n&&t.includeUnderlays&&(C=e.pstyle("underlay-opacity").value,C!==0&&(x=e.pstyle("underlay-padding").value));var T=Math.max(E,x),k=0,D=0;if(n&&(k=e.pstyle("width").pfValue,D=k/2),l&&t.includeNodes){var B=e.position();d=B.x,y=B.y;var P=e.outerWidth(),A=P/2,R=e.outerHeight(),L=R/2;v=d-A,f=d+A,c=y-L,h=y+L,Ir(s,v,c,f,h),n&&ol(s,e),n&&t.includeOutlines&&!i&&ol(s,e),n&&Wg(s,e)}else if(u&&t.includeEdges)if(n&&!i){var I=e.pstyle("curve-style").strValue;if(v=Math.min(g.srcX,g.midX,g.tgtX),f=Math.max(g.srcX,g.midX,g.tgtX),c=Math.min(g.srcY,g.midY,g.tgtY),h=Math.max(g.srcY,g.midY,g.tgtY),v-=D,f+=D,c-=D,h+=D,Ir(s,v,c,f,h),I==="haystack"){var M=g.haystackPts;if(M&&M.length===2){if(v=M[0].x,c=M[0].y,f=M[1].x,h=M[1].y,v>f){var O=v;v=f,f=O}if(c>h){var V=c;c=h,h=V}Ir(s,v-D,c-D,f+D,h+D)}}else if(I==="bezier"||I==="unbundled-bezier"||at(I,"segments")||at(I,"taxi")){var G;switch(I){case"bezier":case"unbundled-bezier":G=g.bezierPts;break;case"segments":case"taxi":case"round-segments":case"round-taxi":G=g.linePts;break}if(G!=null)for(var N=0;Nf){var re=v;v=f,f=re}if(c>h){var ne=c;c=h,h=ne}v-=D,f+=D,c-=D,h+=D,Ir(s,v,c,f,h)}if(n&&t.includeEdges&&u&&(ja(s,e,"mid-source"),ja(s,e,"mid-target"),ja(s,e,"source"),ja(s,e,"target")),n){var J=e.pstyle("ghost").value==="yes";if(J){var z=e.pstyle("ghost-offset-x").pfValue,q=e.pstyle("ghost-offset-y").pfValue;Ir(s,s.x1+z,s.y1+q,s.x2+z,s.y2+q)}}var H=o.bodyBounds=o.bodyBounds||{};Uo(H,s),ln(H,p),un(H,1),n&&(v=s.x1,f=s.x2,c=s.y1,h=s.y2,Ir(s,v-T,c-T,f+T,h+T));var Y=o.overlayBounds=o.overlayBounds||{};Uo(Y,s),ln(Y,p),un(Y,1);var te=o.labelBounds=o.labelBounds||{};te.all!=null?kd(te.all):te.all=wr(),n&&t.includeLabels&&(t.includeMainLabels&&ys(s,e,null),u&&(t.includeSourceLabels&&ys(s,e,"source"),t.includeTargetLabels&&ys(s,e,"target")))}return s.x1=Ar(s.x1),s.y1=Ar(s.y1),s.x2=Ar(s.x2),s.y2=Ar(s.y2),s.w=Ar(s.x2-s.x1),s.h=Ar(s.y2-s.y1),s.w>0&&s.h>0&&b&&(ln(s,p),un(s,1)),s},$v=function(e){var t=0,a=function(s){return(s?1:0)<0&&arguments[0]!==void 0?arguments[0]:sp,e=arguments.length>1?arguments[1]:void 0,t=0;t=0;o--)s(o);return this};dt.removeAllListeners=function(){return this.removeListener("*")};dt.emit=dt.trigger=function(r,e,t){var a=this.listeners,n=a.length;return this.emitting++,_e(e)||(e=[e]),op(this,function(i,s){t!=null&&(a=[{event:s.event,type:s.type,namespace:s.namespace,callback:t}],n=a.length);for(var o=function(){var v=a[l];if(v.type===s.type&&(!v.namespace||v.namespace===s.namespace||v.namespace===ip)&&i.eventMatches(i.context,v,s)){var f=[s];e!=null&&Qc(f,e),i.beforeEmit(i.context,v,s),v.conf&&v.conf.one&&(i.listeners=i.listeners.filter(function(d){return d!==v}));var c=i.callbackContext(i.context,v,s),h=v.callback.apply(c,f);i.afterEmit(i.context,v,s),h===!1&&(s.stopPropagation(),s.preventDefault())}},l=0;l1&&!s){var o=this.length-1,l=this[o],u=l._private.data.id;this[o]=void 0,this[e]=l,i.set(u,{ele:l,index:e})}return this.length--,this},unmergeOne:function(e){e=e[0];var t=this._private,a=e._private.data.id,n=t.map,i=n.get(a);if(!i)return this;var s=i.index;return this.unmergeAt(s),this},unmerge:function(e){var t=this._private.cy;if(!e)return this;if(e&&ge(e)){var a=e;e=t.mutableElements().filter(a)}for(var n=0;n=0;t--){var a=this[t];e(a)&&this.unmergeAt(t)}return this},map:function(e,t){for(var a=[],n=this,i=0;ia&&(a=l,n=o)}return{value:a,ele:n}},min:function(e,t){for(var a=1/0,n,i=this,s=0;s=0&&i"u"?"undefined":ar(Symbol))!=e&&ar(Symbol.iterator)!=e;t&&(Sn[Symbol.iterator]=function(){var a=this,n={value:void 0,done:!1},i=0,s=this.length;return Jl({next:function(){return i1&&arguments[1]!==void 0?arguments[1]:!0,a=this[0],n=a.cy();if(n.styleEnabled()&&a){a._private.styleDirty&&(a._private.styleDirty=!1,n.style().apply(a));var i=a._private.style[e];return i??(t?n.style().getDefaultProperty(e):null)}},numericStyle:function(e){var t=this[0];if(t.cy().styleEnabled()&&t){var a=t.pstyle(e);return a.pfValue!==void 0?a.pfValue:a.value}},numericStyleUnits:function(e){var t=this[0];if(t.cy().styleEnabled()&&t)return t.pstyle(e).units},renderedStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var a=this[0];if(a)return t.style().getRenderedStyle(a,e)},style:function(e,t){var a=this.cy();if(!a.styleEnabled())return this;var n=!1,i=a.style();if(Le(e)){var s=e;i.applyBypass(this,s,n),this.emitAndNotify("style")}else if(ge(e))if(t===void 0){var o=this[0];return o?i.getStylePropertyValue(o,e):void 0}else i.applyBypass(this,e,t,n),this.emitAndNotify("style");else if(e===void 0){var l=this[0];return l?i.getRawStyle(l):void 0}return this},removeStyle:function(e){var t=this.cy();if(!t.styleEnabled())return this;var a=!1,n=t.style(),i=this;if(e===void 0)for(var s=0;s0&&e.push(v[0]),e.push(o[0])}return this.spawn(e,!0).filter(r)},"neighborhood"),closedNeighborhood:function(e){return this.neighborhood().add(this).filter(e)},openNeighborhood:function(e){return this.neighborhood(e)}});gr.neighbourhood=gr.neighborhood;gr.closedNeighbourhood=gr.closedNeighborhood;gr.openNeighbourhood=gr.openNeighborhood;be(gr,{source:Rr(function(e){var t=this[0],a;return t&&(a=t._private.source||t.cy().collection()),a&&e?a.filter(e):a},"source"),target:Rr(function(e){var t=this[0],a;return t&&(a=t._private.target||t.cy().collection()),a&&e?a.filter(e):a},"target"),sources:ml({attr:"source"}),targets:ml({attr:"target"})});function ml(r){return function(t){for(var a=[],n=0;n0);return s},component:function(){var e=this[0];return e.cy().mutableElements().components(e)[0]}});gr.componentsOf=gr.components;var fr=function(e,t){var a=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!1,n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(e===void 0){$e("A collection must have a reference to the core");return}var i=new Xr,s=!1;if(!t)t=[];else if(t.length>0&&Le(t[0])&&!Ia(t[0])){s=!0;for(var o=[],l=new ra,u=0,v=t.length;u0&&arguments[0]!==void 0?arguments[0]:!0,e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,t=this,a=t.cy(),n=a._private,i=[],s=[],o,l=0,u=t.length;l0){for(var V=o.length===t.length?t:new fr(a,o),G=0;G0&&arguments[0]!==void 0?arguments[0]:!0,e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,t=this,a=[],n={},i=t._private.cy;function s(R){for(var L=R._private.edges,I=0;I0&&(r?B.emitAndNotify("remove"):e&&B.emit("remove"));for(var P=0;P0?P=R:B=R;while(Math.abs(A)>s&&++L=i?m(D,L):I===0?L:w(D,B,B+u)}var C=!1;function x(){C=!0,(r!==e||t!==a)&&b()}var T=function(B){return C||x(),r===e&&t===a?B:B===0?0:B===1?1:g(E(B),e,a)};T.getControlPoints=function(){return[{x:r,y:e},{x:t,y:a}]};var k="generateBezier("+[r,e,t,a]+")";return T.toString=function(){return k},T}/*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */var mp=function(){function r(a){return-a.tension*a.x-a.friction*a.v}function e(a,n,i){var s={x:a.x+i.dx*n,v:a.v+i.dv*n,tension:a.tension,friction:a.friction};return{dx:s.v,dv:r(s)}}function t(a,n){var i={dx:a.v,dv:r(a)},s=e(a,n*.5,i),o=e(a,n*.5,s),l=e(a,n,o),u=1/6*(i.dx+2*(s.dx+o.dx)+l.dx),v=1/6*(i.dv+2*(s.dv+o.dv)+l.dv);return a.x=a.x+u*n,a.v=a.v+v*n,a}return function a(n,i,s){var o={x:-1,v:0,tension:null,friction:null},l=[0],u=0,v=1/1e4,f=16/1e3,c,h,d;for(n=parseFloat(n)||500,i=parseFloat(i)||20,s=s||null,o.tension=n,o.friction=i,c=s!==null,c?(u=a(n,i),h=u/s*f):h=f;d=t(d||o,h),l.push(1+d.x),u+=16,Math.abs(d.x)>v&&Math.abs(d.v)>v;);return c?function(y){return l[y*(l.length-1)|0]}:u}}(),Ge=function(e,t,a,n){var i=yp(e,t,a,n);return function(s,o,l){return s+(o-s)*i(l)}},cn={linear:function(e,t,a){return e+(t-e)*a},ease:Ge(.25,.1,.25,1),"ease-in":Ge(.42,0,1,1),"ease-out":Ge(0,0,.58,1),"ease-in-out":Ge(.42,0,.58,1),"ease-in-sine":Ge(.47,0,.745,.715),"ease-out-sine":Ge(.39,.575,.565,1),"ease-in-out-sine":Ge(.445,.05,.55,.95),"ease-in-quad":Ge(.55,.085,.68,.53),"ease-out-quad":Ge(.25,.46,.45,.94),"ease-in-out-quad":Ge(.455,.03,.515,.955),"ease-in-cubic":Ge(.55,.055,.675,.19),"ease-out-cubic":Ge(.215,.61,.355,1),"ease-in-out-cubic":Ge(.645,.045,.355,1),"ease-in-quart":Ge(.895,.03,.685,.22),"ease-out-quart":Ge(.165,.84,.44,1),"ease-in-out-quart":Ge(.77,0,.175,1),"ease-in-quint":Ge(.755,.05,.855,.06),"ease-out-quint":Ge(.23,1,.32,1),"ease-in-out-quint":Ge(.86,0,.07,1),"ease-in-expo":Ge(.95,.05,.795,.035),"ease-out-expo":Ge(.19,1,.22,1),"ease-in-out-expo":Ge(1,0,0,1),"ease-in-circ":Ge(.6,.04,.98,.335),"ease-out-circ":Ge(.075,.82,.165,1),"ease-in-out-circ":Ge(.785,.135,.15,.86),spring:function(e,t,a){if(a===0)return cn.linear;var n=mp(e,t,a);return function(i,s,o){return i+(s-i)*n(o)}},"cubic-bezier":Ge};function xl(r,e,t,a,n){if(a===1||e===t)return t;var i=n(e,t,a);return r==null||((r.roundValue||r.color)&&(i=Math.round(i)),r.min!==void 0&&(i=Math.max(i,r.min)),r.max!==void 0&&(i=Math.min(i,r.max))),i}function El(r,e){return r.pfValue!=null||r.value!=null?r.pfValue!=null&&(e==null||e.type.units!=="%")?r.pfValue:r.value:r}function zt(r,e,t,a,n){var i=n!=null?n.type:null;t<0?t=0:t>1&&(t=1);var s=El(r,n),o=El(e,n);if(ae(s)&&ae(o))return xl(i,s,o,t,a);if(_e(s)&&_e(o)){for(var l=[],u=0;u0?(h==="spring"&&d.push(s.duration),s.easingImpl=cn[h].apply(null,d)):s.easingImpl=cn[h]}var y=s.easingImpl,g;if(s.duration===0?g=1:g=(t-l)/s.duration,s.applying&&(g=s.progress),g<0?g=0:g>1&&(g=1),s.delay==null){var p=s.startPosition,m=s.position;if(m&&n&&!r.locked()){var b={};da(p.x,m.x)&&(b.x=zt(p.x,m.x,g,y)),da(p.y,m.y)&&(b.y=zt(p.y,m.y,g,y)),r.position(b)}var w=s.startPan,E=s.pan,C=i.pan,x=E!=null&&a;x&&(da(w.x,E.x)&&(C.x=zt(w.x,E.x,g,y)),da(w.y,E.y)&&(C.y=zt(w.y,E.y,g,y)),r.emit("pan"));var T=s.startZoom,k=s.zoom,D=k!=null&&a;D&&(da(T,k)&&(i.zoom=ka(i.minZoom,zt(T,k,g,y),i.maxZoom)),r.emit("zoom")),(x||D)&&r.emit("viewport");var B=s.style;if(B&&B.length>0&&n){for(var P=0;P=0;x--){var T=C[x];T()}C.splice(0,C.length)},m=h.length-1;m>=0;m--){var b=h[m],w=b._private;if(w.stopped){h.splice(m,1),w.hooked=!1,w.playing=!1,w.started=!1,p(w.frames);continue}!w.playing&&!w.applying||(w.playing&&w.applying&&(w.applying=!1),w.started||wp(v,b,r),bp(v,b,r,f),w.applying&&(w.applying=!1),p(w.frames),w.step!=null&&w.step(r),b.completed()&&(h.splice(m,1),w.hooked=!1,w.playing=!1,w.started=!1,p(w.completes)),y=!0)}return!f&&h.length===0&&d.length===0&&a.push(v),y}for(var i=!1,s=0;s0?e.notify("draw",t):e.notify("draw")),t.unmerge(a),e.emit("step")}var xp={animate:Fe.animate(),animation:Fe.animation(),animated:Fe.animated(),clearQueue:Fe.clearQueue(),delay:Fe.delay(),delayAnimation:Fe.delayAnimation(),stop:Fe.stop(),addToAnimationPool:function(e){var t=this;t.styleEnabled()&&t._private.aniEles.merge(e)},stopAnimationLoop:function(){this._private.animationsRunning=!1},startAnimationLoop:function(){var e=this;if(e._private.animationsRunning=!0,!e.styleEnabled())return;function t(){e._private.animationsRunning&&wn(function(i){Cl(i,e),t()})}var a=e.renderer();a&&a.beforeRender?a.beforeRender(function(i,s){Cl(s,e)},a.beforeRenderPriorities.animations):t()}},Ep={qualifierCompare:function(e,t){return e==null||t==null?e==null&&t==null:e.sameText(t)},eventMatches:function(e,t,a){var n=t.qualifier;return n!=null?e!==a.target&&Ia(a.target)&&n.matches(a.target):!0},addEventFields:function(e,t){t.cy=e,t.target=e},callbackContext:function(e,t,a){return t.qualifier!=null?a.target:e}},tn=function(e){return ge(e)?new ft(e):e},tf={createEmitter:function(){var e=this._private;return e.emitter||(e.emitter=new Gn(Ep,this)),this},emitter:function(){return this._private.emitter},on:function(e,t,a){return this.emitter().on(e,tn(t),a),this},removeListener:function(e,t,a){return this.emitter().removeListener(e,tn(t),a),this},removeAllListeners:function(){return this.emitter().removeAllListeners(),this},one:function(e,t,a){return this.emitter().one(e,tn(t),a),this},once:function(e,t,a){return this.emitter().one(e,tn(t),a),this},emit:function(e,t){return this.emitter().emit(e,t),this},emitAndNotify:function(e,t){return this.emit(e),this.notify(e,t),this}};Fe.eventAliasesOn(tf);var zs={png:function(e){var t=this._private.renderer;return e=e||{},t.png(e)},jpg:function(e){var t=this._private.renderer;return e=e||{},e.bg=e.bg||"#fff",t.jpg(e)}};zs.jpeg=zs.jpg;var dn={layout:function(e){var t=this;if(e==null){$e("Layout options must be specified to make a layout");return}if(e.name==null){$e("A `name` must be specified to make a layout");return}var a=e.name,n=t.extension("layout",a);if(n==null){$e("No such layout `"+a+"` found. Did you forget to import it and `cytoscape.use()` it?");return}var i;ge(e.eles)?i=t.$(e.eles):i=e.eles!=null?e.eles:t.$();var s=new n(be({},e,{cy:t,eles:i}));return s}};dn.createLayout=dn.makeLayout=dn.layout;var Cp={notify:function(e,t){var a=this._private;if(this.batching()){a.batchNotifications=a.batchNotifications||{};var n=a.batchNotifications[e]=a.batchNotifications[e]||this.collection();t!=null&&n.merge(t);return}if(a.notificationsEnabled){var i=this.renderer();this.destroyed()||!i||i.notify(e,t)}},notifications:function(e){var t=this._private;return e===void 0?t.notificationsEnabled:(t.notificationsEnabled=!!e,this)},noNotifications:function(e){this.notifications(!1),e(),this.notifications(!0)},batching:function(){return this._private.batchCount>0},startBatch:function(){var e=this._private;return e.batchCount==null&&(e.batchCount=0),e.batchCount===0&&(e.batchStyleEles=this.collection(),e.batchNotifications={}),e.batchCount++,this},endBatch:function(){var e=this._private;if(e.batchCount===0)return this;if(e.batchCount--,e.batchCount===0){e.batchStyleEles.updateStyle();var t=this.renderer();Object.keys(e.batchNotifications).forEach(function(a){var n=e.batchNotifications[a];n.empty()?t.notify(a):t.notify(a,n)})}return this},batch:function(e){return this.startBatch(),e(),this.endBatch(),this},batchData:function(e){var t=this;return this.batch(function(){for(var a=Object.keys(e),n=0;n0;)t.removeChild(t.childNodes[0]);e._private.renderer=null,e.mutableElements().forEach(function(a){var n=a._private;n.rscratch={},n.rstyle={},n.animation.current=[],n.animation.queue=[]})},onRender:function(e){return this.on("render",e)},offRender:function(e){return this.off("render",e)}};Fs.invalidateDimensions=Fs.resize;var hn={collection:function(e,t){return ge(e)?this.$(e):Dr(e)?e.collection():_e(e)?(t||(t={}),new fr(this,e,t.unique,t.removed)):new fr(this)},nodes:function(e){var t=this.$(function(a){return a.isNode()});return e?t.filter(e):t},edges:function(e){var t=this.$(function(a){return a.isEdge()});return e?t.filter(e):t},$:function(e){var t=this._private.elements;return e?t.filter(e):t.spawnSelf()},mutableElements:function(){return this._private.elements}};hn.elements=hn.filter=hn.$;var ur={},wa="t",Sp="f";ur.apply=function(r){for(var e=this,t=e._private,a=t.cy,n=a.collection(),i=0;i0;if(c||f&&h){var d=void 0;c&&h||c?d=u.properties:h&&(d=u.mappedProperties);for(var y=0;y1&&(w=1),o.color){var C=a.valueMin[0],x=a.valueMax[0],T=a.valueMin[1],k=a.valueMax[1],D=a.valueMin[2],B=a.valueMax[2],P=a.valueMin[3]==null?1:a.valueMin[3],A=a.valueMax[3]==null?1:a.valueMax[3],R=[Math.round(C+(x-C)*w),Math.round(T+(k-T)*w),Math.round(D+(B-D)*w),Math.round(P+(A-P)*w)];i={bypass:a.bypass,name:a.name,value:R,strValue:"rgb("+R[0]+", "+R[1]+", "+R[2]+")"}}else if(o.number){var L=a.valueMin+(a.valueMax-a.valueMin)*w;i=this.parse(a.name,L,a.bypass,c)}else return!1;if(!i)return y(),!1;i.mapping=a,a=i;break}case s.data:{for(var I=a.field.split("."),M=f.data,O=0;O0&&i>0){for(var o={},l=!1,u=0;u0?r.delayAnimation(s).play().promise().then(b):b()}).then(function(){return r.animation({style:o,duration:i,easing:r.pstyle("transition-timing-function").value,queue:!1}).play().promise()}).then(function(){t.removeBypasses(r,n),r.emitAndNotify("style"),a.transitioning=!1})}else a.transitioning&&(this.removeBypasses(r,n),r.emitAndNotify("style"),a.transitioning=!1)};ur.checkTrigger=function(r,e,t,a,n,i){var s=this.properties[e],o=n(s);r.removed()||o!=null&&o(t,a,r)&&i(s)};ur.checkZOrderTrigger=function(r,e,t,a){var n=this;this.checkTrigger(r,e,t,a,function(i){return i.triggersZOrder},function(){n._private.cy.notify("zorder",r)})};ur.checkBoundsTrigger=function(r,e,t,a){this.checkTrigger(r,e,t,a,function(n){return n.triggersBounds},function(n){r.dirtyCompoundBoundsCache(),r.dirtyBoundingBoxCache()})};ur.checkConnectedEdgesBoundsTrigger=function(r,e,t,a){this.checkTrigger(r,e,t,a,function(n){return n.triggersBoundsOfConnectedEdges},function(n){r.connectedEdges().forEach(function(i){i.dirtyBoundingBoxCache()})})};ur.checkParallelEdgesBoundsTrigger=function(r,e,t,a){this.checkTrigger(r,e,t,a,function(n){return n.triggersBoundsOfParallelEdges},function(n){r.parallelEdges().forEach(function(i){i.dirtyBoundingBoxCache()})})};ur.checkTriggers=function(r,e,t,a){r.dirtyStyleCache(),this.checkZOrderTrigger(r,e,t,a),this.checkBoundsTrigger(r,e,t,a),this.checkConnectedEdgesBoundsTrigger(r,e,t,a),this.checkParallelEdgesBoundsTrigger(r,e,t,a)};var _a={};_a.applyBypass=function(r,e,t,a){var n=this,i=[],s=!0;if(e==="*"||e==="**"){if(t!==void 0)for(var o=0;on.length?a=a.substr(n.length):a=""}function l(){i.length>s.length?i=i.substr(s.length):i=""}for(;;){var u=a.match(/^\s*$/);if(u)break;var v=a.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!v){Ve("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+a);break}n=v[0];var f=v[1];if(f!=="core"){var c=new ft(f);if(c.invalid){Ve("Skipping parsing of block: Invalid selector found in string stylesheet: "+f),o();continue}}var h=v[2],d=!1;i=h;for(var y=[];;){var g=i.match(/^\s*$/);if(g)break;var p=i.match(/^\s*(.+?)\s*:\s*(.+?)(?:\s*;|\s*$)/);if(!p){Ve("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+h),d=!0;break}s=p[0];var m=p[1],b=p[2],w=e.properties[m];if(!w){Ve("Skipping property: Invalid property name in: "+s),l();continue}var E=t.parse(m,b);if(!E){Ve("Skipping property: Invalid property definition in: "+s),l();continue}y.push({name:m,val:b}),l()}if(d){o();break}t.selector(f);for(var C=0;C=7&&e[0]==="d"&&(v=new RegExp(o.data.regex).exec(e))){if(t)return!1;var c=o.data;return{name:r,value:v,strValue:""+e,mapped:c,field:v[1],bypass:t}}else if(e.length>=10&&e[0]==="m"&&(f=new RegExp(o.mapData.regex).exec(e))){if(t||u.multiple)return!1;var h=o.mapData;if(!(u.color||u.number))return!1;var d=this.parse(r,f[4]);if(!d||d.mapped)return!1;var y=this.parse(r,f[5]);if(!y||y.mapped)return!1;if(d.pfValue===y.pfValue||d.strValue===y.strValue)return Ve("`"+r+": "+e+"` is not a valid mapper because the output range is zero; converting to `"+r+": "+d.strValue+"`"),this.parse(r,d.strValue);if(u.color){var g=d.value,p=y.value,m=g[0]===p[0]&&g[1]===p[1]&&g[2]===p[2]&&(g[3]===p[3]||(g[3]==null||g[3]===1)&&(p[3]==null||p[3]===1));if(m)return!1}return{name:r,value:f,strValue:""+e,mapped:h,field:f[1],fieldMin:parseFloat(f[2]),fieldMax:parseFloat(f[3]),valueMin:d.value,valueMax:y.value,bypass:t}}}if(u.multiple&&a!=="multiple"){var b;if(l?b=e.split(/\s+/):_e(e)?b=e:b=[e],u.evenMultiple&&b.length%2!==0)return null;for(var w=[],E=[],C=[],x="",T=!1,k=0;k0?" ":"")+D.strValue}return u.validate&&!u.validate(w,E)?null:u.singleEnum&&T?w.length===1&&ge(w[0])?{name:r,value:w[0],strValue:w[0],bypass:t}:null:{name:r,value:w,pfValue:C,strValue:x,bypass:t,units:E}}var B=function(){for(var J=0;Ju.max||u.strictMax&&e===u.max))return null;var I={name:r,value:e,strValue:""+e+(P||""),units:P,bypass:t};return u.unitless||P!=="px"&&P!=="em"?I.pfValue=e:I.pfValue=P==="px"||!P?e:this.getEmSizeInPixels()*e,(P==="ms"||P==="s")&&(I.pfValue=P==="ms"?e:1e3*e),(P==="deg"||P==="rad")&&(I.pfValue=P==="rad"?e:Ed(e)),P==="%"&&(I.pfValue=e/100),I}else if(u.propList){var M=[],O=""+e;if(O!=="none"){for(var V=O.split(/\s*,\s*|\s+/),G=0;G0&&o>0&&!isNaN(a.w)&&!isNaN(a.h)&&a.w>0&&a.h>0){l=Math.min((s-2*t)/a.w,(o-2*t)/a.h),l=l>this._private.maxZoom?this._private.maxZoom:l,l=l=a.minZoom&&(a.maxZoom=t),this},minZoom:function(e){return e===void 0?this._private.minZoom:this.zoomRange({min:e})},maxZoom:function(e){return e===void 0?this._private.maxZoom:this.zoomRange({max:e})},getZoomedViewport:function(e){var t=this._private,a=t.pan,n=t.zoom,i,s,o=!1;if(t.zoomingEnabled||(o=!0),ae(e)?s=e:Le(e)&&(s=e.level,e.position!=null?i=On(e.position,n,a):e.renderedPosition!=null&&(i=e.renderedPosition),i!=null&&!t.panningEnabled&&(o=!0)),s=s>t.maxZoom?t.maxZoom:s,s=st.maxZoom||!t.zoomingEnabled?s=!0:(t.zoom=l,i.push("zoom"))}if(n&&(!s||!e.cancelOnFailedZoom)&&t.panningEnabled){var u=e.pan;ae(u.x)&&(t.pan.x=u.x,o=!1),ae(u.y)&&(t.pan.y=u.y,o=!1),o||i.push("pan")}return i.length>0&&(i.push("viewport"),this.emit(i.join(" ")),this.notify("viewport")),this},center:function(e){var t=this.getCenterPan(e);return t&&(this._private.pan=t,this.emit("pan viewport"),this.notify("viewport")),this},getCenterPan:function(e,t){if(this._private.panningEnabled){if(ge(e)){var a=e;e=this.mutableElements().filter(a)}else Dr(e)||(e=this.mutableElements());if(e.length!==0){var n=e.boundingBox(),i=this.width(),s=this.height();t=t===void 0?this._private.zoom:t;var o={x:(i-t*(n.x1+n.x2))/2,y:(s-t*(n.y1+n.y2))/2};return o}}},reset:function(){return!this._private.panningEnabled||!this._private.zoomingEnabled?this:(this.viewport({pan:{x:0,y:0},zoom:1}),this)},invalidateSize:function(){this._private.sizeCache=null},size:function(){var e=this._private,t=e.container,a=this;return e.sizeCache=e.sizeCache||(t?function(){var n=a.window().getComputedStyle(t),i=function(o){return parseFloat(n.getPropertyValue(o))};return{width:t.clientWidth-i("padding-left")-i("padding-right"),height:t.clientHeight-i("padding-top")-i("padding-bottom")}}():{width:1,height:1})},width:function(){return this.size().width},height:function(){return this.size().height},extent:function(){var e=this._private.pan,t=this._private.zoom,a=this.renderedExtent(),n={x1:(a.x1-e.x)/t,x2:(a.x2-e.x)/t,y1:(a.y1-e.y)/t,y2:(a.y2-e.y)/t};return n.w=n.x2-n.x1,n.h=n.y2-n.y1,n},renderedExtent:function(){var e=this.width(),t=this.height();return{x1:0,y1:0,x2:e,y2:t,w:e,h:t}},multiClickDebounceTime:function(e){if(e)this._private.multiClickDebounceTime=e;else return this._private.multiClickDebounceTime;return this}};At.centre=At.center;At.autolockNodes=At.autolock;At.autoungrabifyNodes=At.autoungrabify;var Aa={data:Fe.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeData:Fe.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),scratch:Fe.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:Fe.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0})};Aa.attr=Aa.data;Aa.removeAttr=Aa.removeData;var Ra=function(e){var t=this;e=be({},e);var a=e.container;a&&!bn(a)&&bn(a[0])&&(a=a[0]);var n=a?a._cyreg:null;n=n||{},n&&n.cy&&(n.cy.destroy(),n={});var i=n.readies=n.readies||[];a&&(a._cyreg=n),n.cy=t;var s=rr!==void 0&&a!==void 0&&!e.headless,o=e;o.layout=be({name:s?"grid":"null"},o.layout),o.renderer=be({name:s?"canvas":"null"},o.renderer);var l=function(d,y,g){return y!==void 0?y:g!==void 0?g:d},u=this._private={container:a,ready:!1,options:o,elements:new fr(this),listeners:[],aniEles:new fr(this),data:o.data||{},scratch:{},layout:null,renderer:null,destroyed:!1,notificationsEnabled:!0,minZoom:1e-50,maxZoom:1e50,zoomingEnabled:l(!0,o.zoomingEnabled),userZoomingEnabled:l(!0,o.userZoomingEnabled),panningEnabled:l(!0,o.panningEnabled),userPanningEnabled:l(!0,o.userPanningEnabled),boxSelectionEnabled:l(!0,o.boxSelectionEnabled),autolock:l(!1,o.autolock,o.autolockNodes),autoungrabify:l(!1,o.autoungrabify,o.autoungrabifyNodes),autounselectify:l(!1,o.autounselectify),styleEnabled:o.styleEnabled===void 0?s:o.styleEnabled,zoom:ae(o.zoom)?o.zoom:1,pan:{x:Le(o.pan)&&ae(o.pan.x)?o.pan.x:0,y:Le(o.pan)&&ae(o.pan.y)?o.pan.y:0},animation:{current:[],queue:[]},hasCompoundNodes:!1,multiClickDebounceTime:l(250,o.multiClickDebounceTime)};this.createEmitter(),this.selectionType(o.selectionType),this.zoomRange({min:o.minZoom,max:o.maxZoom});var v=function(d,y){var g=d.some(pc);if(g)return ta.all(d).then(y);y(d)};u.styleEnabled&&t.setStyle([]);var f=be({},o,o.renderer);t.initRenderer(f);var c=function(d,y,g){t.notifications(!1);var p=t.mutableElements();p.length>0&&p.remove(),d!=null&&(Le(d)||_e(d))&&t.add(d),t.one("layoutready",function(b){t.notifications(!0),t.emit(b),t.one("load",y),t.emitAndNotify("load")}).one("layoutstop",function(){t.one("done",g),t.emit("done")});var m=be({},t._private.options.layout);m.eles=t.elements(),t.layout(m).run()};v([o.style,o.elements],function(h){var d=h[0],y=h[1];u.styleEnabled&&t.style().append(d),c(y,function(){t.startAnimationLoop(),u.ready=!0,Ue(o.ready)&&t.on("ready",o.ready);for(var g=0;g0,o=!!r.boundingBox,l=wr(o?r.boundingBox:structuredClone(e.extent())),u;if(Dr(r.roots))u=r.roots;else if(_e(r.roots)){for(var v=[],f=0;f0;){var R=A(),L=k(R,B);if(L)R.outgoers().filter(function(ye){return ye.isNode()&&t.has(ye)}).forEach(P);else if(L===null){Ve("Detected double maximal shift for node `"+R.id()+"`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.");break}}}var I=0;if(r.avoidOverlap)for(var M=0;M0&&p[0].length<=3?pe/2:0),Re=2*Math.PI/p[he].length*Ee;return he===0&&p[0].length===1&&(Se=1),{x:H.x+Se*Math.cos(Re),y:H.y+Se*Math.sin(Re)}}else{var Oe=p[he].length,Ne=Math.max(Oe===1?0:o?(l.w-r.padding*2-Y.w)/((r.grid?ce:Oe)-1):(l.w-r.padding*2-Y.w)/((r.grid?ce:Oe)+1),I),ze={x:H.x+(Ee+1-(Oe+1)/2)*Ne,y:H.y+(he+1-(K+1)/2)*te};return ze}},Ce={downward:0,leftward:90,upward:180,rightward:-90};Object.keys(Ce).indexOf(r.direction)===-1&&$e("Invalid direction '".concat(r.direction,"' specified for breadthfirst layout. Valid values are: ").concat(Object.keys(Ce).join(", ")));var we=function(ie){return $c(Ae(ie),l,Ce[r.direction])};return t.nodes().layoutPositions(this,r,we),this};var Ap={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,radius:void 0,startAngle:3/2*Math.PI,sweep:void 0,clockwise:!0,sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function nf(r){this.options=be({},Ap,r)}nf.prototype.run=function(){var r=this.options,e=r,t=r.cy,a=e.eles,n=e.counterclockwise!==void 0?!e.counterclockwise:e.clockwise,i=a.nodes().not(":parent");e.sort&&(i=i.sort(e.sort));for(var s=wr(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:t.width(),h:t.height()}),o={x:s.x1+s.w/2,y:s.y1+s.h/2},l=e.sweep===void 0?2*Math.PI-2*Math.PI/i.length:e.sweep,u=l/Math.max(1,i.length-1),v,f=0,c=0;c1&&e.avoidOverlap){f*=1.75;var p=Math.cos(u)-Math.cos(0),m=Math.sin(u)-Math.sin(0),b=Math.sqrt(f*f/(p*p+m*m));v=Math.max(b,v)}var w=function(C,x){var T=e.startAngle+x*u*(n?1:-1),k=v*Math.cos(T),D=v*Math.sin(T),B={x:o.x+k,y:o.y+D};return B};return a.nodes().layoutPositions(this,e,w),this};var Rp={fit:!0,padding:30,startAngle:3/2*Math.PI,sweep:void 0,clockwise:!0,equidistant:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,height:void 0,width:void 0,spacingFactor:void 0,concentric:function(e){return e.degree()},levelWidth:function(e){return e.maxDegree()/4},animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function sf(r){this.options=be({},Rp,r)}sf.prototype.run=function(){for(var r=this.options,e=r,t=e.counterclockwise!==void 0?!e.counterclockwise:e.clockwise,a=r.cy,n=e.eles,i=n.nodes().not(":parent"),s=wr(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:a.width(),h:a.height()}),o={x:s.x1+s.w/2,y:s.y1+s.h/2},l=[],u=0,v=0;v0){var E=Math.abs(m[0].value-w.value);E>=g&&(m=[],p.push(m))}m.push(w)}var C=u+e.minNodeSpacing;if(!e.avoidOverlap){var x=p.length>0&&p[0].length>1,T=Math.min(s.w,s.h)/2-C,k=T/(p.length+x?1:0);C=Math.min(C,k)}for(var D=0,B=0;B1&&e.avoidOverlap){var L=Math.cos(R)-Math.cos(0),I=Math.sin(R)-Math.sin(0),M=Math.sqrt(C*C/(L*L+I*I));D=Math.max(M,D)}P.r=D,D+=C}if(e.equidistant){for(var O=0,V=0,G=0;G=r.numIter||(Fp(a,r),a.temperature=a.temperature*r.coolingFactor,a.temperature=r.animationThreshold&&i(),wn(v)}};v()}else{for(;u;)u=s(l),l++;kl(a,r),o()}return this};Kn.prototype.stop=function(){return this.stopped=!0,this.thread&&this.thread.stop(),this.emit("layoutstop"),this};Kn.prototype.destroy=function(){return this.thread&&this.thread.stop(),this};var Lp=function(e,t,a){for(var n=a.eles.edges(),i=a.eles.nodes(),s=wr(a.boundingBox?a.boundingBox:{x1:0,y1:0,w:e.width(),h:e.height()}),o={isCompound:e.hasCompoundNodes(),layoutNodes:[],idToIndex:{},nodeSize:i.size(),graphSet:[],indexToGraph:[],layoutEdges:[],edgeSize:n.size(),temperature:a.initialTemp,clientWidth:s.w,clientHeight:s.h,boundingBox:s},l=a.eles.components(),u={},v=0;v0){o.graphSet.push(T);for(var v=0;vn.count?0:n.graph},of=function(e,t,a,n){var i=n.graphSet[a];if(-10)var f=n.nodeOverlap*v,c=Math.sqrt(o*o+l*l),h=f*o/c,d=f*l/c;else var y=Dn(e,o,l),g=Dn(t,-1*o,-1*l),p=g.x-y.x,m=g.y-y.y,b=p*p+m*m,c=Math.sqrt(b),f=(e.nodeRepulsion+t.nodeRepulsion)/b,h=f*p/c,d=f*m/c;e.isLocked||(e.offsetX-=h,e.offsetY-=d),t.isLocked||(t.offsetX+=h,t.offsetY+=d)}},_p=function(e,t,a,n){if(a>0)var i=e.maxX-t.minX;else var i=t.maxX-e.minX;if(n>0)var s=e.maxY-t.minY;else var s=t.maxY-e.minY;return i>=0&&s>=0?Math.sqrt(i*i+s*s):0},Dn=function(e,t,a){var n=e.positionX,i=e.positionY,s=e.height||1,o=e.width||1,l=a/t,u=s/o,v={};return t===0&&0a?(v.x=n,v.y=i+s/2,v):0t&&-1*u<=l&&l<=u?(v.x=n-o/2,v.y=i-o*a/2/t,v):0=u)?(v.x=n+s*t/2/a,v.y=i+s/2,v):(0>a&&(l<=-1*u||l>=u)&&(v.x=n-s*t/2/a,v.y=i-s/2),v)},Gp=function(e,t){for(var a=0;aa){var g=t.gravity*h/y,p=t.gravity*d/y;c.offsetX+=g,c.offsetY+=p}}}}},Wp=function(e,t){var a=[],n=0,i=-1;for(a.push.apply(a,e.graphSet[0]),i+=e.graphSet[0].length;n<=i;){var s=a[n++],o=e.idToIndex[s],l=e.layoutNodes[o],u=l.children;if(0a)var i={x:a*e/n,y:a*t/n};else var i={x:e,y:t};return i},lf=function(e,t){var a=e.parentId;if(a!=null){var n=t.layoutNodes[t.idToIndex[a]],i=!1;if((n.maxX==null||e.maxX+n.padRight>n.maxX)&&(n.maxX=e.maxX+n.padRight,i=!0),(n.minX==null||e.minX-n.padLeftn.maxY)&&(n.maxY=e.maxY+n.padBottom,i=!0),(n.minY==null||e.minY-n.padTopp&&(d+=g+t.componentSpacing,h=0,y=0,g=0)}}},Kp={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,avoidOverlapPadding:10,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,condense:!1,rows:void 0,cols:void 0,position:function(e){},sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:function(e,t){return!0},ready:void 0,stop:void 0,transform:function(e,t){return t}};function vf(r){this.options=be({},Kp,r)}vf.prototype.run=function(){var r=this.options,e=r,t=r.cy,a=e.eles,n=a.nodes().not(":parent");e.sort&&(n=n.sort(e.sort));var i=wr(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:t.width(),h:t.height()});if(i.h===0||i.w===0)a.nodes().layoutPositions(this,e,function(U){return{x:i.x1,y:i.y1}});else{var s=n.size(),o=Math.sqrt(s*i.h/i.w),l=Math.round(o),u=Math.round(i.w/i.h*o),v=function(Q){if(Q==null)return Math.min(l,u);var K=Math.min(l,u);K==l?l=Q:u=Q},f=function(Q){if(Q==null)return Math.max(l,u);var K=Math.max(l,u);K==l?l=Q:u=Q},c=e.rows,h=e.cols!=null?e.cols:e.columns;if(c!=null&&h!=null)l=c,u=h;else if(c!=null&&h==null)l=c,u=Math.ceil(s/l);else if(c==null&&h!=null)u=h,l=Math.ceil(s/u);else if(u*l>s){var d=v(),y=f();(d-1)*y>=s?v(d-1):(y-1)*d>=s&&f(y-1)}else for(;u*l=s?f(p+1):v(g+1)}var m=i.w/u,b=i.h/l;if(e.condense&&(m=0,b=0),e.avoidOverlap)for(var w=0;w=u&&(L=0,R++)},M={},O=0;O(L=Nd(r,e,I[M],I[M+1],I[M+2],I[M+3])))return g(x,L),!0}else if(k.edgeType==="bezier"||k.edgeType==="multibezier"||k.edgeType==="self"||k.edgeType==="compound"){for(var I=k.allpts,M=0;M+5(L=Od(r,e,I[M],I[M+1],I[M+2],I[M+3],I[M+4],I[M+5])))return g(x,L),!0}for(var O=O||T.source,V=V||T.target,G=n.getArrowWidth(D,B),N=[{name:"source",x:k.arrowStartX,y:k.arrowStartY,angle:k.srcArrowAngle},{name:"target",x:k.arrowEndX,y:k.arrowEndY,angle:k.tgtArrowAngle},{name:"mid-source",x:k.midX,y:k.midY,angle:k.midsrcArrowAngle},{name:"mid-target",x:k.midX,y:k.midY,angle:k.midtgtArrowAngle}],M=0;M0&&(p(O),p(V))}function b(x,T,k){return Tr(x,T,k)}function w(x,T){var k=x._private,D=c,B;T?B=T+"-":B="",x.boundingBox();var P=k.labelBounds[T||"main"],A=x.pstyle(B+"label").value,R=x.pstyle("text-events").strValue==="yes";if(!(!R||!A)){var L=b(k.rscratch,"labelX",T),I=b(k.rscratch,"labelY",T),M=b(k.rscratch,"labelAngle",T),O=x.pstyle(B+"text-margin-x").pfValue,V=x.pstyle(B+"text-margin-y").pfValue,G=P.x1-D-O,N=P.x2+D-O,F=P.y1-D-V,U=P.y2+D-V;if(M){var Q=Math.cos(M),K=Math.sin(M),j=function(Y,te){return Y=Y-L,te=te-I,{x:Y*Q-te*K+L,y:Y*K+te*Q+I}},re=j(G,F),ne=j(G,U),J=j(N,F),z=j(N,U),q=[re.x+O,re.y+V,J.x+O,J.y+V,z.x+O,z.y+V,ne.x+O,ne.y+V];if(Sr(r,e,q))return g(x),!0}else if(nt(P,r,e))return g(x),!0}}for(var E=s.length-1;E>=0;E--){var C=s[E];C.isNode()?p(C)||w(C):m(C)||w(C)||w(C,"source")||w(C,"target")}return o};Mt.getAllInBox=function(r,e,t,a){var n=this.getCachedZSortedEles().interactive,i=this.cy.zoom(),s=2/i,o=[],l=Math.min(r,t),u=Math.max(r,t),v=Math.min(e,a),f=Math.max(e,a);r=l,t=u,e=v,a=f;var c=wr({x1:r,y1:e,x2:t,y2:a}),h=[{x:c.x1,y:c.y1},{x:c.x2,y:c.y1},{x:c.x2,y:c.y2},{x:c.x1,y:c.y2}],d=[[h[0],h[1]],[h[1],h[2]],[h[2],h[3]],[h[3],h[0]]];function y(Y,te,ce){return Tr(Y,te,ce)}function g(Y,te){var ce=Y._private,Ae=s,Ce="";Y.boundingBox();var we=ce.labelBounds.main;if(!we)return null;var ye=y(ce.rscratch,"labelX",te),ie=y(ce.rscratch,"labelY",te),de=y(ce.rscratch,"labelAngle",te),he=Y.pstyle(Ce+"text-margin-x").pfValue,Ee=Y.pstyle(Ce+"text-margin-y").pfValue,pe=we.x1-Ae-he,Se=we.x2+Ae-he,Re=we.y1-Ae-Ee,Oe=we.y2+Ae-Ee;if(de){var Ne=Math.cos(de),ze=Math.sin(de),xe=function(X,S){return X=X-ye,S=S-ie,{x:X*Ne-S*ze+ye,y:X*ze+S*Ne+ie}};return[xe(pe,Re),xe(Se,Re),xe(Se,Oe),xe(pe,Oe)]}else return[{x:pe,y:Re},{x:Se,y:Re},{x:Se,y:Oe},{x:pe,y:Oe}]}function p(Y,te,ce,Ae){function Ce(we,ye,ie){return(ie.y-we.y)*(ye.x-we.x)>(ye.y-we.y)*(ie.x-we.x)}return Ce(Y,ce,Ae)!==Ce(te,ce,Ae)&&Ce(Y,te,ce)!==Ce(Y,te,Ae)}for(var m=0;m0?-(Math.PI-e.ang):Math.PI+e.ang},jp=function(e,t,a,n,i){if(e!==Rl?Ml(t,e,Vr):Jp(Pr,Vr),Ml(t,a,Pr),Pl=Vr.nx*Pr.ny-Vr.ny*Pr.nx,Al=Vr.nx*Pr.nx-Vr.ny*-Pr.ny,Ur=Math.asin(Math.max(-1,Math.min(1,Pl))),Math.abs(Ur)<1e-6){Vs=t.x,qs=t.y,Ct=Vt=0;return}St=1,gn=!1,Al<0?Ur<0?Ur=Math.PI+Ur:(Ur=Math.PI-Ur,St=-1,gn=!0):Ur>0&&(St=-1,gn=!0),t.radius!==void 0?Vt=t.radius:Vt=n,wt=Ur/2,an=Math.min(Vr.len/2,Pr.len/2),i?(zr=Math.abs(Math.cos(wt)*Vt/Math.sin(wt)),zr>an?(zr=an,Ct=Math.abs(zr*Math.sin(wt)/Math.cos(wt))):Ct=Vt):(zr=Math.min(an,Vt),Ct=Math.abs(zr*Math.sin(wt)/Math.cos(wt))),_s=t.x+Pr.nx*zr,Gs=t.y+Pr.ny*zr,Vs=_s-Pr.ny*Ct*St,qs=Gs+Pr.nx*Ct*St,hf=t.x+Vr.nx*zr,gf=t.y+Vr.ny*zr,Rl=t};function pf(r,e){e.radius===0?r.lineTo(e.cx,e.cy):r.arc(e.cx,e.cy,e.radius,e.startAngle,e.endAngle,e.counterClockwise)}function po(r,e,t,a){var n=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0;return a===0||e.radius===0?{cx:e.x,cy:e.y,radius:0,startX:e.x,startY:e.y,stopX:e.x,stopY:e.y,startAngle:void 0,endAngle:void 0,counterClockwise:void 0}:(jp(r,e,t,a,n),{cx:Vs,cy:qs,radius:Ct,startX:hf,startY:gf,stopX:_s,stopY:Gs,startAngle:Vr.ang+Math.PI/2*St,endAngle:Pr.ang-Math.PI/2*St,counterClockwise:gn})}var Ma=.01,ey=Math.sqrt(2*Ma),yr={};yr.findMidptPtsEtc=function(r,e){var t=e.posPts,a=e.intersectionPts,n=e.vectorNormInverse,i,s=r.pstyle("source-endpoint"),o=r.pstyle("target-endpoint"),l=s.units!=null&&o.units!=null,u=function(E,C,x,T){var k=T-C,D=x-E,B=Math.sqrt(D*D+k*k);return{x:-k/B,y:D/B}},v=r.pstyle("edge-distances").value;switch(v){case"node-position":i=t;break;case"intersection":i=a;break;case"endpoints":{if(l){var f=this.manualEndptToPx(r.source()[0],s),c=Je(f,2),h=c[0],d=c[1],y=this.manualEndptToPx(r.target()[0],o),g=Je(y,2),p=g[0],m=g[1],b={x1:h,y1:d,x2:p,y2:m};n=u(h,d,p,m),i=b}else Ve("Edge ".concat(r.id()," has edge-distances:endpoints specified without manual endpoints specified via source-endpoint and target-endpoint. Falling back on edge-distances:intersection (default).")),i=a;break}}return{midptPts:i,vectorNormInverse:n}};yr.findHaystackPoints=function(r){for(var e=0;e0?Math.max(S-_,0):Math.min(S+_,0)},A=P(D,T),R=P(B,k),L=!1;m===u?p=Math.abs(A)>Math.abs(R)?n:a:m===l||m===o?(p=a,L=!0):(m===i||m===s)&&(p=n,L=!0);var I=p===a,M=I?R:A,O=I?B:D,V=to(O),G=!1;!(L&&(w||C))&&(m===o&&O<0||m===l&&O>0||m===i&&O>0||m===s&&O<0)&&(V*=-1,M=V*Math.abs(M),G=!0);var N;if(w){var F=E<0?1+E:E;N=F*M}else{var U=E<0?M:0;N=U+E*V}var Q=function(S){return Math.abs(S)=Math.abs(M)},K=Q(N),j=Q(Math.abs(M)-Math.abs(N)),re=K||j;if(re&&!G)if(I){var ne=Math.abs(O)<=c/2,J=Math.abs(D)<=h/2;if(ne){var z=(v.x1+v.x2)/2,q=v.y1,H=v.y2;t.segpts=[z,q,z,H]}else if(J){var Y=(v.y1+v.y2)/2,te=v.x1,ce=v.x2;t.segpts=[te,Y,ce,Y]}else t.segpts=[v.x1,v.y2]}else{var Ae=Math.abs(O)<=f/2,Ce=Math.abs(B)<=d/2;if(Ae){var we=(v.y1+v.y2)/2,ye=v.x1,ie=v.x2;t.segpts=[ye,we,ie,we]}else if(Ce){var de=(v.x1+v.x2)/2,he=v.y1,Ee=v.y2;t.segpts=[de,he,de,Ee]}else t.segpts=[v.x2,v.y1]}else if(I){var pe=v.y1+N+(g?c/2*V:0),Se=v.x1,Re=v.x2;t.segpts=[Se,pe,Re,pe]}else{var Oe=v.x1+N+(g?f/2*V:0),Ne=v.y1,ze=v.y2;t.segpts=[Oe,Ne,Oe,ze]}if(t.isRound){var xe=r.pstyle("taxi-radius").value,ue=r.pstyle("radius-type").value[0]==="arc-radius";t.radii=new Array(t.segpts.length/2).fill(xe),t.isArcRadius=new Array(t.segpts.length/2).fill(ue)}};yr.tryToCorrectInvalidPoints=function(r,e){var t=r._private.rscratch;if(t.edgeType==="bezier"){var a=e.srcPos,n=e.tgtPos,i=e.srcW,s=e.srcH,o=e.tgtW,l=e.tgtH,u=e.srcShape,v=e.tgtShape,f=e.srcCornerRadius,c=e.tgtCornerRadius,h=e.srcRs,d=e.tgtRs,y=!ae(t.startX)||!ae(t.startY),g=!ae(t.arrowStartX)||!ae(t.arrowStartY),p=!ae(t.endX)||!ae(t.endY),m=!ae(t.arrowEndX)||!ae(t.arrowEndY),b=3,w=this.getArrowWidth(r.pstyle("width").pfValue,r.pstyle("arrow-scale").value)*this.arrowShapeWidth,E=b*w,C=Bt({x:t.ctrlpts[0],y:t.ctrlpts[1]},{x:t.startX,y:t.startY}),x=CO.poolIndex()){var V=M;M=O,O=V}var G=A.srcPos=M.position(),N=A.tgtPos=O.position(),F=A.srcW=M.outerWidth(),U=A.srcH=M.outerHeight(),Q=A.tgtW=O.outerWidth(),K=A.tgtH=O.outerHeight(),j=A.srcShape=t.nodeShapes[e.getNodeShape(M)],re=A.tgtShape=t.nodeShapes[e.getNodeShape(O)],ne=A.srcCornerRadius=M.pstyle("corner-radius").value==="auto"?"auto":M.pstyle("corner-radius").pfValue,J=A.tgtCornerRadius=O.pstyle("corner-radius").value==="auto"?"auto":O.pstyle("corner-radius").pfValue,z=A.tgtRs=O._private.rscratch,q=A.srcRs=M._private.rscratch;A.dirCounts={north:0,west:0,south:0,east:0,northwest:0,southwest:0,northeast:0,southeast:0};for(var H=0;H=ey||(Re=Math.sqrt(Math.max(Se*Se,Ma)+Math.max(pe*pe,Ma)));var Oe=A.vector={x:Se,y:pe},Ne=A.vectorNorm={x:Oe.x/Re,y:Oe.y/Re},ze={x:-Ne.y,y:Ne.x};A.nodesOverlap=!ae(Re)||re.checkPoint(we[0],we[1],0,Q,K,N.x,N.y,J,z)||j.checkPoint(ie[0],ie[1],0,F,U,G.x,G.y,ne,q),A.vectorNormInverse=ze,R={nodesOverlap:A.nodesOverlap,dirCounts:A.dirCounts,calculatedIntersection:!0,hasBezier:A.hasBezier,hasUnbundled:A.hasUnbundled,eles:A.eles,srcPos:N,srcRs:z,tgtPos:G,tgtRs:q,srcW:Q,srcH:K,tgtW:F,tgtH:U,srcIntn:de,tgtIntn:ye,srcShape:re,tgtShape:j,posPts:{x1:Ee.x2,y1:Ee.y2,x2:Ee.x1,y2:Ee.y1},intersectionPts:{x1:he.x2,y1:he.y2,x2:he.x1,y2:he.y1},vector:{x:-Oe.x,y:-Oe.y},vectorNorm:{x:-Ne.x,y:-Ne.y},vectorNormInverse:{x:-ze.x,y:-ze.y}}}var xe=Ce?R:A;te.nodesOverlap=xe.nodesOverlap,te.srcIntn=xe.srcIntn,te.tgtIntn=xe.tgtIntn,te.isRound=ce.startsWith("round"),n&&(M.isParent()||M.isChild()||O.isParent()||O.isChild())&&(M.parents().anySame(O)||O.parents().anySame(M)||M.same(O)&&M.isParent())?e.findCompoundLoopPoints(Y,xe,H,Ae):M===O?e.findLoopPoints(Y,xe,H,Ae):ce.endsWith("segments")?e.findSegmentsPoints(Y,xe):ce.endsWith("taxi")?e.findTaxiPoints(Y,xe):ce==="straight"||!Ae&&A.eles.length%2===1&&H===Math.floor(A.eles.length/2)?e.findStraightEdgePoints(Y):e.findBezierPoints(Y,xe,H,Ae,Ce),e.findEndpoints(Y),e.tryToCorrectInvalidPoints(Y,xe),e.checkForInvalidEdgeWarning(Y),e.storeAllpts(Y),e.storeEdgeProjections(Y),e.calculateArrowAngles(Y),e.recalculateEdgeLabelProjections(Y),e.calculateLabelAngles(Y)}},x=0;x0){var we=u,ye=Et(we,Wt(s)),ie=Et(we,Wt(Ce)),de=ye;if(ie2){var he=Et(we,{x:Ce[2],y:Ce[3]});he0){var W=v,$=Et(W,Wt(s)),Z=Et(W,Wt(_)),oe=$;if(Z<$&&(s=[_[0],_[1]],oe=Z),_.length>2){var ee=Et(W,{x:_[2],y:_[3]});ee=d||x){g={cp:w,segment:C};break}}if(g)break}var T=g.cp,k=g.segment,D=(d-p)/k.length,B=k.t1-k.t0,P=h?k.t0+B*D:k.t1-B*D;P=ka(0,P,1),e=Kt(T.p0,T.p1,T.p2,P),c=ty(T.p0,T.p1,T.p2,P);break}case"straight":case"segments":case"haystack":{for(var A=0,R,L,I,M,O=a.allpts.length,V=0;V+3=d));V+=2);var G=d-L,N=G/R;N=ka(0,N,1),e=Td(I,M,N),c=bf(I,M);break}}s("labelX",f,e.x),s("labelY",f,e.y),s("labelAutoAngle",f,c)}};u("source"),u("target"),this.applyLabelDimensions(r)}};Gr.applyLabelDimensions=function(r){this.applyPrefixedLabelDimensions(r),r.isEdge()&&(this.applyPrefixedLabelDimensions(r,"source"),this.applyPrefixedLabelDimensions(r,"target"))};Gr.applyPrefixedLabelDimensions=function(r,e){var t=r._private,a=this.getLabelText(r,e),n=Dt(a,r._private.labelDimsKey);if(Tr(t.rscratch,"prefixedLabelDimsKey",e)!==n){Kr(t.rscratch,"prefixedLabelDimsKey",e,n);var i=this.calculateLabelDimensions(r,a),s=r.pstyle("line-height").pfValue,o=r.pstyle("text-wrap").strValue,l=Tr(t.rscratch,"labelWrapCachedLines",e)||[],u=o!=="wrap"?1:Math.max(l.length,1),v=i.height/u,f=v*s,c=i.width,h=i.height+(u-1)*(s-1)*v;Kr(t.rstyle,"labelWidth",e,c),Kr(t.rscratch,"labelWidth",e,c),Kr(t.rstyle,"labelHeight",e,h),Kr(t.rscratch,"labelHeight",e,h),Kr(t.rscratch,"labelLineHeight",e,f)}};Gr.getLabelText=function(r,e){var t=r._private,a=e?e+"-":"",n=r.pstyle(a+"label").strValue,i=r.pstyle("text-transform").value,s=function(U,Q){return Q?(Kr(t.rscratch,U,e,Q),Q):Tr(t.rscratch,U,e)};if(!n)return"";i=="none"||(i=="uppercase"?n=n.toUpperCase():i=="lowercase"&&(n=n.toLowerCase()));var o=r.pstyle("text-wrap").value;if(o==="wrap"){var l=s("labelKey");if(l!=null&&s("labelWrapKey")===l)return s("labelWrapCachedText");for(var u="​",v=n.split(` +`),f=r.pstyle("text-max-width").pfValue,c=r.pstyle("text-overflow-wrap").value,h=c==="anywhere",d=[],y=/[\s\u200b]+|$/g,g=0;gf){var E=p.matchAll(y),C="",x=0,T=kr(E),k;try{for(T.s();!(k=T.n()).done;){var D=k.value,B=D[0],P=p.substring(x,D.index);x=D.index+B.length;var A=C.length===0?P:C+P+B,R=this.calculateLabelDimensions(r,A),L=R.width;L<=f?C+=P+B:(C&&d.push(C),C=P+B)}}catch(F){T.e(F)}finally{T.f()}C.match(/^[\s\u200b]+$/)||d.push(C)}else d.push(p)}s("labelWrapCachedLines",d),n=s("labelWrapCachedText",d.join(` +`)),s("labelWrapKey",l)}else if(o==="ellipsis"){var I=r.pstyle("text-max-width").pfValue,M="",O="…",V=!1;if(this.calculateLabelDimensions(r,n).widthI)break;M+=n[G],G===n.length-1&&(V=!0)}return V||(M+=O),M}return n};Gr.getLabelJustification=function(r){var e=r.pstyle("text-justification").strValue,t=r.pstyle("text-halign").strValue;if(e==="auto")if(r.isNode())switch(t){case"left":return"right";case"right":return"left";default:return"center"}else return"center";else return e};Gr.calculateLabelDimensions=function(r,e){var t=this,a=t.cy.window(),n=a.document,i=0,s=r.pstyle("font-style").strValue,o=r.pstyle("font-size").pfValue,l=r.pstyle("font-family").strValue,u=r.pstyle("font-weight").strValue,v=this.labelCalcCanvas,f=this.labelCalcCanvasContext;if(!v){v=this.labelCalcCanvas=n.createElement("canvas"),f=this.labelCalcCanvasContext=v.getContext("2d");var c=v.style;c.position="absolute",c.left="-9999px",c.top="-9999px",c.zIndex="-1",c.visibility="hidden",c.pointerEvents="none"}f.font="".concat(s," ").concat(u," ").concat(o,"px ").concat(l);for(var h=0,d=0,y=e.split(` +`),g=0;g1&&arguments[1]!==void 0?arguments[1]:!0;if(e.merge(s),o)for(var l=0;l=r.desktopTapThreshold2}var lr=i(S);je&&(r.hoverData.tapholdCancelled=!0);var jr=function(){var Br=r.hoverData.dragDelta=r.hoverData.dragDelta||[];Br.length===0?(Br.push(Pe[0]),Br.push(Pe[1])):(Br[0]+=Pe[0],Br[1]+=Pe[1])};W=!0,n(De,["mousemove","vmousemove","tapdrag"],S,{x:ee[0],y:ee[1]});var Ze=function(Br){return{originalEvent:S,type:Br,position:{x:ee[0],y:ee[1]}}},Wr=function(){r.data.bgActivePosistion=void 0,r.hoverData.selecting||$.emit(Ze("boxstart")),me[4]=1,r.hoverData.selecting=!0,r.redrawHint("select",!0),r.redraw()};if(r.hoverData.which===3){if(je){var $r=Ze("cxtdrag");fe?fe.emit($r):$.emit($r),r.hoverData.cxtDragged=!0,(!r.hoverData.cxtOver||De!==r.hoverData.cxtOver)&&(r.hoverData.cxtOver&&r.hoverData.cxtOver.emit(Ze("cxtdragout")),r.hoverData.cxtOver=De,De&&De.emit(Ze("cxtdragover")))}}else if(r.hoverData.dragging){if(W=!0,$.panningEnabled()&&$.userPanningEnabled()){var It;if(r.hoverData.justStartedPan){var $a=r.hoverData.mdownPos;It={x:(ee[0]-$a[0])*Z,y:(ee[1]-$a[1])*Z},r.hoverData.justStartedPan=!1}else It={x:Pe[0]*Z,y:Pe[1]*Z};$.panBy(It),$.emit(Ze("dragpan")),r.hoverData.dragged=!0}ee=r.projectIntoViewport(S.clientX,S.clientY)}else if(me[4]==1&&(fe==null||fe.pannable())){if(je){if(!r.hoverData.dragging&&$.boxSelectionEnabled()&&(lr||!$.panningEnabled()||!$.userPanningEnabled()))Wr();else if(!r.hoverData.selecting&&$.panningEnabled()&&$.userPanningEnabled()){var bt=s(fe,r.hoverData.downs);bt&&(r.hoverData.dragging=!0,r.hoverData.justStartedPan=!0,me[4]=0,r.data.bgActivePosistion=Wt(ve),r.redrawHint("select",!0),r.redraw())}fe&&fe.pannable()&&fe.active()&&fe.unactivate()}}else{if(fe&&fe.pannable()&&fe.active()&&fe.unactivate(),(!fe||!fe.grabbed())&&De!=Te&&(Te&&n(Te,["mouseout","tapdragout"],S,{x:ee[0],y:ee[1]}),De&&n(De,["mouseover","tapdragover"],S,{x:ee[0],y:ee[1]}),r.hoverData.last=De),fe)if(je){if($.boxSelectionEnabled()&&lr)fe&&fe.grabbed()&&(p(Be),fe.emit(Ze("freeon")),Be.emit(Ze("free")),r.dragData.didDrag&&(fe.emit(Ze("dragfreeon")),Be.emit(Ze("dragfree")))),Wr();else if(fe&&fe.grabbed()&&r.nodeIsDraggable(fe)){var Er=!r.dragData.didDrag;Er&&r.redrawHint("eles",!0),r.dragData.didDrag=!0,r.hoverData.draggingEles||y(Be,{inDragLayer:!0});var hr={x:0,y:0};if(ae(Pe[0])&&ae(Pe[1])&&(hr.x+=Pe[0],hr.y+=Pe[1],Er)){var Cr=r.hoverData.dragDelta;Cr&&ae(Cr[0])&&ae(Cr[1])&&(hr.x+=Cr[0],hr.y+=Cr[1])}r.hoverData.draggingEles=!0,Be.silentShift(hr).emit(Ze("position")).emit(Ze("drag")),r.redrawHint("drag",!0),r.redraw()}}else jr();W=!0}if(me[2]=ee[0],me[3]=ee[1],W)return S.stopPropagation&&S.stopPropagation(),S.preventDefault&&S.preventDefault(),!1}},!1);var P,A,R;r.registerBinding(e,"mouseup",function(S){if(!(r.hoverData.which===1&&S.which!==1&&r.hoverData.capture)){var _=r.hoverData.capture;if(_){r.hoverData.capture=!1;var W=r.cy,$=r.projectIntoViewport(S.clientX,S.clientY),Z=r.selection,oe=r.findNearestElement($[0],$[1],!0,!1),ee=r.dragData.possibleDragElements,ve=r.hoverData.down,le=i(S);r.data.bgActivePosistion&&(r.redrawHint("select",!0),r.redraw()),r.hoverData.tapholdCancelled=!0,r.data.bgActivePosistion=void 0,ve&&ve.unactivate();var me=function(Ke){return{originalEvent:S,type:Ke,position:{x:$[0],y:$[1]}}};if(r.hoverData.which===3){var De=me("cxttapend");if(ve?ve.emit(De):W.emit(De),!r.hoverData.cxtDragged){var Te=me("cxttap");ve?ve.emit(Te):W.emit(Te)}r.hoverData.cxtDragged=!1,r.hoverData.which=null}else if(r.hoverData.which===1){if(n(oe,["mouseup","tapend","vmouseup"],S,{x:$[0],y:$[1]}),!r.dragData.didDrag&&!r.hoverData.dragged&&!r.hoverData.selecting&&!r.hoverData.isOverThresholdDrag&&(n(ve,["click","tap","vclick"],S,{x:$[0],y:$[1]}),A=!1,S.timeStamp-R<=W.multiClickDebounceTime()?(P&&clearTimeout(P),A=!0,R=null,n(ve,["dblclick","dbltap","vdblclick"],S,{x:$[0],y:$[1]})):(P=setTimeout(function(){A||n(ve,["oneclick","onetap","voneclick"],S,{x:$[0],y:$[1]})},W.multiClickDebounceTime()),R=S.timeStamp)),ve==null&&!r.dragData.didDrag&&!r.hoverData.selecting&&!r.hoverData.dragged&&!i(S)&&(W.$(t).unselect(["tapunselect"]),ee.length>0&&r.redrawHint("eles",!0),r.dragData.possibleDragElements=ee=W.collection()),oe==ve&&!r.dragData.didDrag&&!r.hoverData.selecting&&oe!=null&&oe._private.selectable&&(r.hoverData.dragging||(W.selectionType()==="additive"||le?oe.selected()?oe.unselect(["tapunselect"]):oe.select(["tapselect"]):le||(W.$(t).unmerge(oe).unselect(["tapunselect"]),oe.select(["tapselect"]))),r.redrawHint("eles",!0)),r.hoverData.selecting){var fe=W.collection(r.getAllInBox(Z[0],Z[1],Z[2],Z[3]));r.redrawHint("select",!0),fe.length>0&&r.redrawHint("eles",!0),W.emit(me("boxend"));var Pe=function(Ke){return Ke.selectable()&&!Ke.selected()};W.selectionType()==="additive"||le||W.$(t).unmerge(fe).unselect(),fe.emit(me("box")).stdFilter(Pe).select().emit(me("boxselect")),r.redraw()}if(r.hoverData.dragging&&(r.hoverData.dragging=!1,r.redrawHint("select",!0),r.redrawHint("eles",!0),r.redraw()),!Z[4]){r.redrawHint("drag",!0),r.redrawHint("eles",!0);var Be=ve&&ve.grabbed();p(ee),Be&&(ve.emit(me("freeon")),ee.emit(me("free")),r.dragData.didDrag&&(ve.emit(me("dragfreeon")),ee.emit(me("dragfree"))))}}Z[4]=0,r.hoverData.down=null,r.hoverData.cxtStarted=!1,r.hoverData.draggingEles=!1,r.hoverData.selecting=!1,r.hoverData.isOverThresholdDrag=!1,r.dragData.didDrag=!1,r.hoverData.dragged=!1,r.hoverData.dragDelta=[],r.hoverData.mdownPos=null,r.hoverData.mdownGPos=null,r.hoverData.which=null}}},!1);var L=[],I=4,M,O=1e5,V=function(S,_){for(var W=0;W=I){var $=L;if(M=V($,5),!M){var Z=Math.abs($[0]);M=G($)&&Z>5}if(M)for(var oe=0;oe<$.length;oe++)O=Math.min(Math.abs($[oe]),O)}else L.push(W),_=!0;else M&&(O=Math.min(Math.abs(W),O));if(!r.scrollingPage){var ee=r.cy,ve=ee.zoom(),le=ee.pan(),me=r.projectIntoViewport(S.clientX,S.clientY),De=[me[0]*ve+le.x,me[1]*ve+le.y];if(r.hoverData.draggingEles||r.hoverData.dragging||r.hoverData.cxtStarted||k()){S.preventDefault();return}if(ee.panningEnabled()&&ee.userPanningEnabled()&&ee.zoomingEnabled()&&ee.userZoomingEnabled()){S.preventDefault(),r.data.wheelZooming=!0,clearTimeout(r.data.wheelTimeout),r.data.wheelTimeout=setTimeout(function(){r.data.wheelZooming=!1,r.redrawHint("eles",!0),r.redraw()},150);var Te;_&&Math.abs(W)>5&&(W=to(W)*5),Te=W/-250,M&&(Te/=O,Te*=3),Te=Te*r.wheelSensitivity;var fe=S.deltaMode===1;fe&&(Te*=33);var Pe=ee.zoom()*Math.pow(10,Te);S.type==="gesturechange"&&(Pe=r.gestureStartZoom*S.scale),ee.zoom({level:Pe,renderedPosition:{x:De[0],y:De[1]}}),ee.emit({type:S.type==="gesturechange"?"pinchzoom":"scrollzoom",originalEvent:S,position:{x:me[0],y:me[1]}})}}}};r.registerBinding(r.container,"wheel",N,!0),r.registerBinding(e,"scroll",function(S){r.scrollingPage=!0,clearTimeout(r.scrollingPageTimeout),r.scrollingPageTimeout=setTimeout(function(){r.scrollingPage=!1},250)},!0),r.registerBinding(r.container,"gesturestart",function(S){r.gestureStartZoom=r.cy.zoom(),r.hasTouchStarted||S.preventDefault()},!0),r.registerBinding(r.container,"gesturechange",function(X){r.hasTouchStarted||N(X)},!0),r.registerBinding(r.container,"mouseout",function(S){var _=r.projectIntoViewport(S.clientX,S.clientY);r.cy.emit({originalEvent:S,type:"mouseout",position:{x:_[0],y:_[1]}})},!1),r.registerBinding(r.container,"mouseover",function(S){var _=r.projectIntoViewport(S.clientX,S.clientY);r.cy.emit({originalEvent:S,type:"mouseover",position:{x:_[0],y:_[1]}})},!1);var F,U,Q,K,j,re,ne,J,z,q,H,Y,te,ce=function(S,_,W,$){return Math.sqrt((W-S)*(W-S)+($-_)*($-_))},Ae=function(S,_,W,$){return(W-S)*(W-S)+($-_)*($-_)},Ce;r.registerBinding(r.container,"touchstart",Ce=function(S){if(r.hasTouchStarted=!0,!!D(S)){b(),r.touchData.capture=!0,r.data.bgActivePosistion=void 0;var _=r.cy,W=r.touchData.now,$=r.touchData.earlier;if(S.touches[0]){var Z=r.projectIntoViewport(S.touches[0].clientX,S.touches[0].clientY);W[0]=Z[0],W[1]=Z[1]}if(S.touches[1]){var Z=r.projectIntoViewport(S.touches[1].clientX,S.touches[1].clientY);W[2]=Z[0],W[3]=Z[1]}if(S.touches[2]){var Z=r.projectIntoViewport(S.touches[2].clientX,S.touches[2].clientY);W[4]=Z[0],W[5]=Z[1]}var oe=function(lr){return{originalEvent:S,type:lr,position:{x:W[0],y:W[1]}}};if(S.touches[1]){r.touchData.singleTouchMoved=!0,p(r.dragData.touchDragEles);var ee=r.findContainerClientCoords();z=ee[0],q=ee[1],H=ee[2],Y=ee[3],F=S.touches[0].clientX-z,U=S.touches[0].clientY-q,Q=S.touches[1].clientX-z,K=S.touches[1].clientY-q,te=0<=F&&F<=H&&0<=Q&&Q<=H&&0<=U&&U<=Y&&0<=K&&K<=Y;var ve=_.pan(),le=_.zoom();j=ce(F,U,Q,K),re=Ae(F,U,Q,K),ne=[(F+Q)/2,(U+K)/2],J=[(ne[0]-ve.x)/le,(ne[1]-ve.y)/le];var me=200,De=me*me;if(re=1){for(var mr=r.touchData.startPosition=[null,null,null,null,null,null],Ye=0;Ye=r.touchTapThreshold2}if(_&&r.touchData.cxt){S.preventDefault();var Ye=S.touches[0].clientX-z,ir=S.touches[0].clientY-q,er=S.touches[1].clientX-z,lr=S.touches[1].clientY-q,jr=Ae(Ye,ir,er,lr),Ze=jr/re,Wr=150,$r=Wr*Wr,It=1.5,$a=It*It;if(Ze>=$a||jr>=$r){r.touchData.cxt=!1,r.data.bgActivePosistion=void 0,r.redrawHint("select",!0);var bt=le("cxttapend");r.touchData.start?(r.touchData.start.unactivate().emit(bt),r.touchData.start=null):$.emit(bt)}}if(_&&r.touchData.cxt){var bt=le("cxtdrag");r.data.bgActivePosistion=void 0,r.redrawHint("select",!0),r.touchData.start?r.touchData.start.emit(bt):$.emit(bt),r.touchData.start&&(r.touchData.start._private.grabbed=!1),r.touchData.cxtDragged=!0;var Er=r.findNearestElement(Z[0],Z[1],!0,!0);(!r.touchData.cxtOver||Er!==r.touchData.cxtOver)&&(r.touchData.cxtOver&&r.touchData.cxtOver.emit(le("cxtdragout")),r.touchData.cxtOver=Er,Er&&Er.emit(le("cxtdragover")))}else if(_&&S.touches[2]&&$.boxSelectionEnabled())S.preventDefault(),r.data.bgActivePosistion=void 0,this.lastThreeTouch=+new Date,r.touchData.selecting||$.emit(le("boxstart")),r.touchData.selecting=!0,r.touchData.didSelect=!0,W[4]=1,!W||W.length===0||W[0]===void 0?(W[0]=(Z[0]+Z[2]+Z[4])/3,W[1]=(Z[1]+Z[3]+Z[5])/3,W[2]=(Z[0]+Z[2]+Z[4])/3+1,W[3]=(Z[1]+Z[3]+Z[5])/3+1):(W[2]=(Z[0]+Z[2]+Z[4])/3,W[3]=(Z[1]+Z[3]+Z[5])/3),r.redrawHint("select",!0),r.redraw();else if(_&&S.touches[1]&&!r.touchData.didSelect&&$.zoomingEnabled()&&$.panningEnabled()&&$.userZoomingEnabled()&&$.userPanningEnabled()){S.preventDefault(),r.data.bgActivePosistion=void 0,r.redrawHint("select",!0);var hr=r.dragData.touchDragEles;if(hr){r.redrawHint("drag",!0);for(var Cr=0;Cr0&&!r.hoverData.draggingEles&&!r.swipePanning&&r.data.bgActivePosistion!=null&&(r.data.bgActivePosistion=void 0,r.redrawHint("select",!0),r.redraw())}},!1);var ye;r.registerBinding(e,"touchcancel",ye=function(S){var _=r.touchData.start;r.touchData.capture=!1,_&&_.unactivate()});var ie,de,he,Ee;if(r.registerBinding(e,"touchend",ie=function(S){var _=r.touchData.start,W=r.touchData.capture;if(W)S.touches.length===0&&(r.touchData.capture=!1),S.preventDefault();else return;var $=r.selection;r.swipePanning=!1,r.hoverData.draggingEles=!1;var Z=r.cy,oe=Z.zoom(),ee=r.touchData.now,ve=r.touchData.earlier;if(S.touches[0]){var le=r.projectIntoViewport(S.touches[0].clientX,S.touches[0].clientY);ee[0]=le[0],ee[1]=le[1]}if(S.touches[1]){var le=r.projectIntoViewport(S.touches[1].clientX,S.touches[1].clientY);ee[2]=le[0],ee[3]=le[1]}if(S.touches[2]){var le=r.projectIntoViewport(S.touches[2].clientX,S.touches[2].clientY);ee[4]=le[0],ee[5]=le[1]}var me=function($r){return{originalEvent:S,type:$r,position:{x:ee[0],y:ee[1]}}};_&&_.unactivate();var De;if(r.touchData.cxt){if(De=me("cxttapend"),_?_.emit(De):Z.emit(De),!r.touchData.cxtDragged){var Te=me("cxttap");_?_.emit(Te):Z.emit(Te)}r.touchData.start&&(r.touchData.start._private.grabbed=!1),r.touchData.cxt=!1,r.touchData.start=null,r.redraw();return}if(!S.touches[2]&&Z.boxSelectionEnabled()&&r.touchData.selecting){r.touchData.selecting=!1;var fe=Z.collection(r.getAllInBox($[0],$[1],$[2],$[3]));$[0]=void 0,$[1]=void 0,$[2]=void 0,$[3]=void 0,$[4]=0,r.redrawHint("select",!0),Z.emit(me("boxend"));var Pe=function($r){return $r.selectable()&&!$r.selected()};fe.emit(me("box")).stdFilter(Pe).select().emit(me("boxselect")),fe.nonempty()&&r.redrawHint("eles",!0),r.redraw()}if(_!=null&&_.unactivate(),S.touches[2])r.data.bgActivePosistion=void 0,r.redrawHint("select",!0);else if(!S.touches[1]){if(!S.touches[0]){if(!S.touches[0]){r.data.bgActivePosistion=void 0,r.redrawHint("select",!0);var Be=r.dragData.touchDragEles;if(_!=null){var je=_._private.grabbed;p(Be),r.redrawHint("drag",!0),r.redrawHint("eles",!0),je&&(_.emit(me("freeon")),Be.emit(me("free")),r.dragData.didDrag&&(_.emit(me("dragfreeon")),Be.emit(me("dragfree")))),n(_,["touchend","tapend","vmouseup","tapdragout"],S,{x:ee[0],y:ee[1]}),_.unactivate(),r.touchData.start=null}else{var Ke=r.findNearestElement(ee[0],ee[1],!0,!0);n(Ke,["touchend","tapend","vmouseup","tapdragout"],S,{x:ee[0],y:ee[1]})}var mr=r.touchData.startPosition[0]-ee[0],Ye=mr*mr,ir=r.touchData.startPosition[1]-ee[1],er=ir*ir,lr=Ye+er,jr=lr*oe*oe;r.touchData.singleTouchMoved||(_||Z.$(":selected").unselect(["tapunselect"]),n(_,["tap","vclick"],S,{x:ee[0],y:ee[1]}),de=!1,S.timeStamp-Ee<=Z.multiClickDebounceTime()?(he&&clearTimeout(he),de=!0,Ee=null,n(_,["dbltap","vdblclick"],S,{x:ee[0],y:ee[1]})):(he=setTimeout(function(){de||n(_,["onetap","voneclick"],S,{x:ee[0],y:ee[1]})},Z.multiClickDebounceTime()),Ee=S.timeStamp)),_!=null&&!r.dragData.didDrag&&_._private.selectable&&jr"u"){var pe=[],Se=function(S){return{clientX:S.clientX,clientY:S.clientY,force:1,identifier:S.pointerId,pageX:S.pageX,pageY:S.pageY,radiusX:S.width/2,radiusY:S.height/2,screenX:S.screenX,screenY:S.screenY,target:S.target}},Re=function(S){return{event:S,touch:Se(S)}},Oe=function(S){pe.push(Re(S))},Ne=function(S){for(var _=0;_0)return F[0]}return null},d=Object.keys(c),y=0;y0?h:wv(i,s,e,t,a,n,o,l)},checkPoint:function(e,t,a,n,i,s,o,l){l=l==="auto"?vt(n,i):l;var u=2*l;if(Zr(e,t,this.points,s,o,n,i-u,[0,-1],a)||Zr(e,t,this.points,s,o,n-u,i,[0,-1],a))return!0;var v=n/2+2*a,f=i/2+2*a,c=[s-v,o-f,s-v,o,s+v,o,s+v,o-f];return!!(Sr(e,t,c)||kt(e,t,u,u,s+n/2-l,o+i/2-l,a)||kt(e,t,u,u,s-n/2+l,o+i/2-l,a))}}};Qr.registerNodeShapes=function(){var r=this.nodeShapes={},e=this;this.generateEllipse(),this.generatePolygon("triangle",br(3,0)),this.generateRoundPolygon("round-triangle",br(3,0)),this.generatePolygon("rectangle",br(4,0)),r.square=r.rectangle,this.generateRoundRectangle(),this.generateCutRectangle(),this.generateBarrel(),this.generateBottomRoundrectangle();{var t=[0,1,1,0,0,-1,-1,0];this.generatePolygon("diamond",t),this.generateRoundPolygon("round-diamond",t)}this.generatePolygon("pentagon",br(5,0)),this.generateRoundPolygon("round-pentagon",br(5,0)),this.generatePolygon("hexagon",br(6,0)),this.generateRoundPolygon("round-hexagon",br(6,0)),this.generatePolygon("heptagon",br(7,0)),this.generateRoundPolygon("round-heptagon",br(7,0)),this.generatePolygon("octagon",br(8,0)),this.generateRoundPolygon("round-octagon",br(8,0));var a=new Array(20);{var n=Ps(5,0),i=Ps(5,Math.PI/5),s=.5*(3-Math.sqrt(5));s*=1.57;for(var o=0;o=e.deqFastCost*w)break}else if(u){if(m>=e.deqCost*h||m>=e.deqAvgCost*c)break}else if(b>=e.deqNoDrawCost*ws)break;var E=e.deq(a,g,y);if(E.length>0)for(var C=0;C0&&(e.onDeqd(a,d),!u&&e.shouldRedraw(a,d,g,y)&&i())},o=e.priority||js;n.beforeRender(s,o(a))}}}},ny=function(){function r(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:xn;ht(this,r),this.idsByKey=new Xr,this.keyForId=new Xr,this.cachesByLvl=new Xr,this.lvls=[],this.getKey=e,this.doesEleInvalidateKey=t}return gt(r,[{key:"getIdsFor",value:function(t){t==null&&$e("Can not get id list for null key");var a=this.idsByKey,n=this.idsByKey.get(t);return n||(n=new ra,a.set(t,n)),n}},{key:"addIdForKey",value:function(t,a){t!=null&&this.getIdsFor(t).add(a)}},{key:"deleteIdForKey",value:function(t,a){t!=null&&this.getIdsFor(t).delete(a)}},{key:"getNumberOfIdsForKey",value:function(t){return t==null?0:this.getIdsFor(t).size}},{key:"updateKeyMappingFor",value:function(t){var a=t.id(),n=this.keyForId.get(a),i=this.getKey(t);this.deleteIdForKey(n,a),this.addIdForKey(i,a),this.keyForId.set(a,i)}},{key:"deleteKeyMappingFor",value:function(t){var a=t.id(),n=this.keyForId.get(a);this.deleteIdForKey(n,a),this.keyForId.delete(a)}},{key:"keyHasChangedFor",value:function(t){var a=t.id(),n=this.keyForId.get(a),i=this.getKey(t);return n!==i}},{key:"isInvalid",value:function(t){return this.keyHasChangedFor(t)||this.doesEleInvalidateKey(t)}},{key:"getCachesAt",value:function(t){var a=this.cachesByLvl,n=this.lvls,i=a.get(t);return i||(i=new Xr,a.set(t,i),n.push(t)),i}},{key:"getCache",value:function(t,a){return this.getCachesAt(a).get(t)}},{key:"get",value:function(t,a){var n=this.getKey(t),i=this.getCache(n,a);return i!=null&&this.updateKeyMappingFor(t),i}},{key:"getForCachedKey",value:function(t,a){var n=this.keyForId.get(t.id()),i=this.getCache(n,a);return i}},{key:"hasCache",value:function(t,a){return this.getCachesAt(a).has(t)}},{key:"has",value:function(t,a){var n=this.getKey(t);return this.hasCache(n,a)}},{key:"setCache",value:function(t,a,n){n.key=t,this.getCachesAt(a).set(t,n)}},{key:"set",value:function(t,a,n){var i=this.getKey(t);this.setCache(i,a,n),this.updateKeyMappingFor(t)}},{key:"deleteCache",value:function(t,a){this.getCachesAt(a).delete(t)}},{key:"delete",value:function(t,a){var n=this.getKey(t);this.deleteCache(n,a)}},{key:"invalidateKey",value:function(t){var a=this;this.lvls.forEach(function(n){return a.deleteCache(t,n)})}},{key:"invalidate",value:function(t){var a=t.id(),n=this.keyForId.get(a);this.deleteKeyMappingFor(t);var i=this.doesEleInvalidateKey(t);return i&&this.invalidateKey(n),i||this.getNumberOfIdsForKey(n)===0}}])}(),Nl=25,nn=50,pn=-4,Hs=3,Sf=7.99,iy=8,sy=1024,oy=1024,uy=1024,ly=.2,vy=.8,fy=10,cy=.15,dy=.1,hy=.9,gy=.9,py=100,yy=1,Ut={dequeue:"dequeue",downscale:"downscale",highQuality:"highQuality"},my=cr({getKey:null,doesEleInvalidateKey:xn,drawElement:null,getBoundingBox:null,getRotationPoint:null,getRotationOffset:null,isVisible:dv,allowEdgeTxrCaching:!0,allowParentTxrCaching:!0}),ba=function(e,t){var a=this;a.renderer=e,a.onDequeues=[];var n=my(t);be(a,n),a.lookup=new ny(n.getKey,n.doesEleInvalidateKey),a.setupDequeueing()},nr=ba.prototype;nr.reasons=Ut;nr.getTextureQueue=function(r){var e=this;return e.eleImgCaches=e.eleImgCaches||{},e.eleImgCaches[r]=e.eleImgCaches[r]||[]};nr.getRetiredTextureQueue=function(r){var e=this,t=e.eleImgCaches.retired=e.eleImgCaches.retired||{},a=t[r]=t[r]||[];return a};nr.getElementQueue=function(){var r=this,e=r.eleCacheQueue=r.eleCacheQueue||new Va(function(t,a){return a.reqs-t.reqs});return e};nr.getElementKeyToQueue=function(){var r=this,e=r.eleKeyToCacheQueue=r.eleKeyToCacheQueue||{};return e};nr.getElement=function(r,e,t,a,n){var i=this,s=this.renderer,o=s.cy.zoom(),l=this.lookup;if(!e||e.w===0||e.h===0||isNaN(e.w)||isNaN(e.h)||!r.visible()||r.removed()||!i.allowEdgeTxrCaching&&r.isEdge()||!i.allowParentTxrCaching&&r.isParent())return null;if(a==null&&(a=Math.ceil(ro(o*t))),a=Sf||a>Hs)return null;var u=Math.pow(2,a),v=e.h*u,f=e.w*u,c=s.eleTextBiggerThanMin(r,u);if(!this.isVisible(r,c))return null;var h=l.get(r,a);if(h&&h.invalidated&&(h.invalidated=!1,h.texture.invalidatedWidth-=h.width),h)return h;var d;if(v<=Nl?d=Nl:v<=nn?d=nn:d=Math.ceil(v/nn)*nn,v>uy||f>oy)return null;var y=i.getTextureQueue(d),g=y[y.length-2],p=function(){return i.recycleTexture(d,f)||i.addTexture(d,f)};g||(g=y[y.length-1]),g||(g=p()),g.width-g.usedWidtha;B--)k=i.getElement(r,e,t,B,Ut.downscale);D()}else return i.queueElement(r,C.level-1),C;else{var P;if(!b&&!w&&!E)for(var A=a-1;A>=pn;A--){var R=l.get(r,A);if(R){P=R;break}}if(m(P))return i.queueElement(r,a),P;g.context.translate(g.usedWidth,0),g.context.scale(u,u),this.drawElement(g.context,r,e,c,!1),g.context.scale(1/u,1/u),g.context.translate(-g.usedWidth,0)}return h={x:g.usedWidth,texture:g,level:a,scale:u,width:f,height:v,scaledLabelShown:c},g.usedWidth+=Math.ceil(f+iy),g.eleCaches.push(h),l.set(r,a,h),i.checkTextureFullness(g),h};nr.invalidateElements=function(r){for(var e=0;e=ly*r.width&&this.retireTexture(r)};nr.checkTextureFullness=function(r){var e=this,t=e.getTextureQueue(r.height);r.usedWidth/r.width>vy&&r.fullnessChecks>=fy?lt(t,r):r.fullnessChecks++};nr.retireTexture=function(r){var e=this,t=r.height,a=e.getTextureQueue(t),n=this.lookup;lt(a,r),r.retired=!0;for(var i=r.eleCaches,s=0;s=e)return s.retired=!1,s.usedWidth=0,s.invalidatedWidth=0,s.fullnessChecks=0,eo(s.eleCaches),s.context.setTransform(1,0,0,1,0,0),s.context.clearRect(0,0,s.width,s.height),lt(n,s),a.push(s),s}};nr.queueElement=function(r,e){var t=this,a=t.getElementQueue(),n=t.getElementKeyToQueue(),i=this.getKey(r),s=n[i];if(s)s.level=Math.max(s.level,e),s.eles.merge(r),s.reqs++,a.updateItem(s);else{var o={eles:r.spawn().merge(r),level:e,reqs:1,key:i};a.push(o),n[i]=o}};nr.dequeue=function(r){for(var e=this,t=e.getElementQueue(),a=e.getElementKeyToQueue(),n=[],i=e.lookup,s=0;s0;s++){var o=t.pop(),l=o.key,u=o.eles[0],v=i.hasCache(u,o.level);if(a[l]=null,v)continue;n.push(o);var f=e.getBoundingBox(u);e.getElement(u,f,r,o.level,Ut.dequeue)}return n};nr.removeFromQueue=function(r){var e=this,t=e.getElementQueue(),a=e.getElementKeyToQueue(),n=this.getKey(r),i=a[n];i!=null&&(i.eles.length===1?(i.reqs=Js,t.updateItem(i),t.pop(),a[n]=null):i.eles.unmerge(r))};nr.onDequeue=function(r){this.onDequeues.push(r)};nr.offDequeue=function(r){lt(this.onDequeues,r)};nr.setupDequeueing=Tf.setupDequeueing({deqRedrawThreshold:py,deqCost:cy,deqAvgCost:dy,deqNoDrawCost:hy,deqFastCost:gy,deq:function(e,t,a){return e.dequeue(t,a)},onDeqd:function(e,t){for(var a=0;a=wy||t>Pn)return null}a.validateLayersElesOrdering(t,r);var l=a.layersByLevel,u=Math.pow(2,t),v=l[t]=l[t]||[],f,c=a.levelIsComplete(t,r),h,d=function(){var D=function(L){if(a.validateLayersElesOrdering(L,r),a.levelIsComplete(L,r))return h=l[L],!0},B=function(L){if(!h)for(var I=t+L;xa<=I&&I<=Pn&&!D(I);I+=L);};B(1),B(-1);for(var P=v.length-1;P>=0;P--){var A=v[P];A.invalid&<(v,A)}};if(!c)d();else return v;var y=function(){if(!f){f=wr();for(var D=0;DFl||A>Fl)return null;var R=P*A;if(R>By)return null;var L=a.makeLayer(f,t);if(B!=null){var I=v.indexOf(B)+1;v.splice(I,0,L)}else(D.insert===void 0||D.insert)&&v.unshift(L);return L};if(a.skipping&&!o)return null;for(var p=null,m=r.length/by,b=!o,w=0;w=m||!bv(p.bb,E.boundingBox()))&&(p=g({insert:!0,after:p}),!p))return null;h||b?a.queueLayer(p,E):a.drawEleInLayer(p,E,t,e),p.eles.push(E),x[t]=p}return h||(b?null:v)};dr.getEleLevelForLayerLevel=function(r,e){return r};dr.drawEleInLayer=function(r,e,t,a){var n=this,i=this.renderer,s=r.context,o=e.boundingBox();o.w===0||o.h===0||!e.visible()||(t=n.getEleLevelForLayerLevel(t,a),i.setImgSmoothing(s,!1),i.drawCachedElement(s,e,null,null,t,Py),i.setImgSmoothing(s,!0))};dr.levelIsComplete=function(r,e){var t=this,a=t.layersByLevel[r];if(!a||a.length===0)return!1;for(var n=0,i=0;i0||s.invalid)return!1;n+=s.eles.length}return n===e.length};dr.validateLayersElesOrdering=function(r,e){var t=this.layersByLevel[r];if(t)for(var a=0;a0){e=!0;break}}return e};dr.invalidateElements=function(r){var e=this;r.length!==0&&(e.lastInvalidationTime=Yr(),!(r.length===0||!e.haveLayers())&&e.updateElementsInLayers(r,function(a,n,i){e.invalidateLayer(a)}))};dr.invalidateLayer=function(r){if(this.lastInvalidationTime=Yr(),!r.invalid){var e=r.level,t=r.eles,a=this.layersByLevel[e];lt(a,r),r.elesQueue=[],r.invalid=!0,r.replacement&&(r.replacement.invalid=!0);for(var n=0;n3&&arguments[3]!==void 0?arguments[3]:!0,n=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0,s=this,o=e._private.rscratch;if(!(i&&!e.visible())&&!(o.badLine||o.allpts==null||isNaN(o.allpts[0]))){var l;t&&(l=t,r.translate(-l.x1,-l.y1));var u=i?e.pstyle("opacity").value:1,v=i?e.pstyle("line-opacity").value:1,f=e.pstyle("curve-style").value,c=e.pstyle("line-style").value,h=e.pstyle("width").pfValue,d=e.pstyle("line-cap").value,y=e.pstyle("line-outline-width").value,g=e.pstyle("line-outline-color").value,p=u*v,m=u*v,b=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:p;f==="straight-triangle"?(s.eleStrokeStyle(r,e,L),s.drawEdgeTrianglePath(e,r,o.allpts)):(r.lineWidth=h,r.lineCap=d,s.eleStrokeStyle(r,e,L),s.drawEdgePath(e,r,o.allpts,c),r.lineCap="butt")},w=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:p;if(r.lineWidth=h+y,r.lineCap=d,y>0)s.colorStrokeStyle(r,g[0],g[1],g[2],L);else{r.lineCap="butt";return}f==="straight-triangle"?s.drawEdgeTrianglePath(e,r,o.allpts):(s.drawEdgePath(e,r,o.allpts,c),r.lineCap="butt")},E=function(){n&&s.drawEdgeOverlay(r,e)},C=function(){n&&s.drawEdgeUnderlay(r,e)},x=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:m;s.drawArrowheads(r,e,L)},T=function(){s.drawElementText(r,e,null,a)};r.lineJoin="round";var k=e.pstyle("ghost").value==="yes";if(k){var D=e.pstyle("ghost-offset-x").pfValue,B=e.pstyle("ghost-offset-y").pfValue,P=e.pstyle("ghost-opacity").value,A=p*P;r.translate(D,B),b(A),x(A),r.translate(-D,-B)}else w();C(),b(),x(),E(),T(),t&&r.translate(l.x1,l.y1)}};var Bf=function(e){if(!["overlay","underlay"].includes(e))throw new Error("Invalid state");return function(t,a){if(a.visible()){var n=a.pstyle("".concat(e,"-opacity")).value;if(n!==0){var i=this,s=i.usePaths(),o=a._private.rscratch,l=a.pstyle("".concat(e,"-padding")).pfValue,u=2*l,v=a.pstyle("".concat(e,"-color")).value;t.lineWidth=u,o.edgeType==="self"&&!s?t.lineCap="butt":t.lineCap="round",i.colorStrokeStyle(t,v[0],v[1],v[2],n),i.drawEdgePath(a,t,o.allpts,"solid")}}}};Jr.drawEdgeOverlay=Bf("overlay");Jr.drawEdgeUnderlay=Bf("underlay");Jr.drawEdgePath=function(r,e,t,a){var n=r._private.rscratch,i=e,s,o=!1,l=this.usePaths(),u=r.pstyle("line-dash-pattern").pfValue,v=r.pstyle("line-dash-offset").pfValue;if(l){var f=t.join("$"),c=n.pathCacheKey&&n.pathCacheKey===f;c?(s=e=n.pathCache,o=!0):(s=e=new Path2D,n.pathCacheKey=f,n.pathCache=s)}if(i.setLineDash)switch(a){case"dotted":i.setLineDash([1,1]);break;case"dashed":i.setLineDash(u),i.lineDashOffset=v;break;case"solid":i.setLineDash([]);break}if(!o&&!n.badLine)switch(e.beginPath&&e.beginPath(),e.moveTo(t[0],t[1]),n.edgeType){case"bezier":case"self":case"compound":case"multibezier":for(var h=2;h+35&&arguments[5]!==void 0?arguments[5]:!0,s=this;if(a==null){if(i&&!s.eleTextBiggerThanMin(e))return}else if(a===!1)return;if(e.isNode()){var o=e.pstyle("label");if(!o||!o.value)return;var l=s.getLabelJustification(e);r.textAlign=l,r.textBaseline="bottom"}else{var u=e.element()._private.rscratch.badLine,v=e.pstyle("label"),f=e.pstyle("source-label"),c=e.pstyle("target-label");if(u||(!v||!v.value)&&(!f||!f.value)&&(!c||!c.value))return;r.textAlign="center",r.textBaseline="bottom"}var h=!t,d;t&&(d=t,r.translate(-d.x1,-d.y1)),n==null?(s.drawText(r,e,null,h,i),e.isEdge()&&(s.drawText(r,e,"source",h,i),s.drawText(r,e,"target",h,i))):s.drawText(r,e,n,h,i),t&&r.translate(d.x1,d.y1)};Lt.getFontCache=function(r){var e;this.fontCaches=this.fontCaches||[];for(var t=0;t2&&arguments[2]!==void 0?arguments[2]:!0,a=e.pstyle("font-style").strValue,n=e.pstyle("font-size").pfValue+"px",i=e.pstyle("font-family").strValue,s=e.pstyle("font-weight").strValue,o=t?e.effectiveOpacity()*e.pstyle("text-opacity").value:1,l=e.pstyle("text-outline-opacity").value*o,u=e.pstyle("color").value,v=e.pstyle("text-outline-color").value;r.font=a+" "+s+" "+n+" "+i,r.lineJoin="round",this.colorFillStyle(r,u[0],u[1],u[2],o),this.colorStrokeStyle(r,v[0],v[1],v[2],l)};function qy(r,e,t,a,n){var i=Math.min(a,n),s=i/2,o=e+a/2,l=t+n/2;r.beginPath(),r.arc(o,l,s,0,Math.PI*2),r.closePath()}function Gl(r,e,t,a,n){var i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:5,s=Math.min(i,a/2,n/2);r.beginPath(),r.moveTo(e+s,t),r.lineTo(e+a-s,t),r.quadraticCurveTo(e+a,t,e+a,t+s),r.lineTo(e+a,t+n-s),r.quadraticCurveTo(e+a,t+n,e+a-s,t+n),r.lineTo(e+s,t+n),r.quadraticCurveTo(e,t+n,e,t+n-s),r.lineTo(e,t+s),r.quadraticCurveTo(e,t,e+s,t),r.closePath()}Lt.getTextAngle=function(r,e){var t,a=r._private,n=a.rscratch,i=e?e+"-":"",s=r.pstyle(i+"text-rotation");if(s.strValue==="autorotate"){var o=Tr(n,"labelAngle",e);t=r.isEdge()?o:0}else s.strValue==="none"?t=0:t=s.pfValue;return t};Lt.drawText=function(r,e,t){var a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,n=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,i=e._private,s=i.rscratch,o=n?e.effectiveOpacity():1;if(!(n&&(o===0||e.pstyle("text-opacity").value===0))){t==="main"&&(t=null);var l=Tr(s,"labelX",t),u=Tr(s,"labelY",t),v,f,c=this.getLabelText(e,t);if(c!=null&&c!==""&&!isNaN(l)&&!isNaN(u)){this.setupTextStyle(r,e,n);var h=t?t+"-":"",d=Tr(s,"labelWidth",t),y=Tr(s,"labelHeight",t),g=e.pstyle(h+"text-margin-x").pfValue,p=e.pstyle(h+"text-margin-y").pfValue,m=e.isEdge(),b=e.pstyle("text-halign").value,w=e.pstyle("text-valign").value;m&&(b="center",w="center"),l+=g,u+=p;var E;switch(a?E=this.getTextAngle(e,t):E=0,E!==0&&(v=l,f=u,r.translate(v,f),r.rotate(E),l=0,u=0),w){case"top":break;case"center":u+=y/2;break;case"bottom":u+=y;break}var C=e.pstyle("text-background-opacity").value,x=e.pstyle("text-border-opacity").value,T=e.pstyle("text-border-width").pfValue,k=e.pstyle("text-background-padding").pfValue,D=e.pstyle("text-background-shape").strValue,B=D==="round-rectangle"||D==="roundrectangle",P=D==="circle",A=2;if(C>0||T>0&&x>0){var R=r.fillStyle,L=r.strokeStyle,I=r.lineWidth,M=e.pstyle("text-background-color").value,O=e.pstyle("text-border-color").value,V=e.pstyle("text-border-style").value,G=C>0,N=T>0&&x>0,F=l-k;switch(b){case"left":F-=d;break;case"center":F-=d/2;break}var U=u-y-k,Q=d+2*k,K=y+2*k;if(G&&(r.fillStyle="rgba(".concat(M[0],",").concat(M[1],",").concat(M[2],",").concat(C*o,")")),N&&(r.strokeStyle="rgba(".concat(O[0],",").concat(O[1],",").concat(O[2],",").concat(x*o,")"),r.lineWidth=T,r.setLineDash))switch(V){case"dotted":r.setLineDash([1,1]);break;case"dashed":r.setLineDash([4,2]);break;case"double":r.lineWidth=T/4,r.setLineDash([]);break;case"solid":default:r.setLineDash([]);break}if(B?(r.beginPath(),Gl(r,F,U,Q,K,A)):P?(r.beginPath(),qy(r,F,U,Q,K)):(r.beginPath(),r.rect(F,U,Q,K)),G&&r.fill(),N&&r.stroke(),N&&V==="double"){var j=T/2;r.beginPath(),B?Gl(r,F+j,U+j,Q-2*j,K-2*j,A):r.rect(F+j,U+j,Q-2*j,K-2*j),r.stroke()}r.fillStyle=R,r.strokeStyle=L,r.lineWidth=I,r.setLineDash&&r.setLineDash([])}var re=2*e.pstyle("text-outline-width").pfValue;if(re>0&&(r.lineWidth=re),e.pstyle("text-wrap").value==="wrap"){var ne=Tr(s,"labelWrapCachedLines",t),J=Tr(s,"labelLineHeight",t),z=d/2,q=this.getLabelJustification(e);switch(q==="auto"||(b==="left"?q==="left"?l+=-d:q==="center"&&(l+=-z):b==="center"?q==="left"?l+=-z:q==="right"&&(l+=z):b==="right"&&(q==="center"?l+=z:q==="right"&&(l+=d))),w){case"top":u-=(ne.length-1)*J;break;case"center":case"bottom":u-=(ne.length-1)*J;break}for(var H=0;H0&&r.strokeText(ne[H],l,u),r.fillText(ne[H],l,u),u+=J}else re>0&&r.strokeText(c,l,u),r.fillText(c,l,u);E!==0&&(r.rotate(-E),r.translate(-v,-f))}}};var yt={};yt.drawNode=function(r,e,t){var a=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,n=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,i=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0,s=this,o,l,u=e._private,v=u.rscratch,f=e.position();if(!(!ae(f.x)||!ae(f.y))&&!(i&&!e.visible())){var c=i?e.effectiveOpacity():1,h=s.usePaths(),d,y=!1,g=e.padding();o=e.width()+2*g,l=e.height()+2*g;var p;t&&(p=t,r.translate(-p.x1,-p.y1));for(var m=e.pstyle("background-image"),b=m.value,w=new Array(b.length),E=new Array(b.length),C=0,x=0;x0&&arguments[0]!==void 0?arguments[0]:A;s.eleFillStyle(r,e,ue)},J=function(){var ue=arguments.length>0&&arguments[0]!==void 0?arguments[0]:N;s.colorStrokeStyle(r,R[0],R[1],R[2],ue)},z=function(){var ue=arguments.length>0&&arguments[0]!==void 0?arguments[0]:K;s.colorStrokeStyle(r,U[0],U[1],U[2],ue)},q=function(ue,X,S,_){var W=s.nodePathCache=s.nodePathCache||[],$=cv(S==="polygon"?S+","+_.join(","):S,""+X,""+ue,""+re),Z=W[$],oe,ee=!1;return Z!=null?(oe=Z,ee=!0,v.pathCache=oe):(oe=new Path2D,W[$]=v.pathCache=oe),{path:oe,cacheHit:ee}},H=e.pstyle("shape").strValue,Y=e.pstyle("shape-polygon-points").pfValue;if(h){r.translate(f.x,f.y);var te=q(o,l,H,Y);d=te.path,y=te.cacheHit}var ce=function(){if(!y){var ue=f;h&&(ue={x:0,y:0}),s.nodeShapes[s.getNodeShape(e)].draw(d||r,ue.x,ue.y,o,l,re,v)}h?r.fill(d):r.fill()},Ae=function(){for(var ue=arguments.length>0&&arguments[0]!==void 0?arguments[0]:c,X=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,S=u.backgrounding,_=0,W=0;W0&&arguments[0]!==void 0?arguments[0]:!1,X=arguments.length>1&&arguments[1]!==void 0?arguments[1]:c;s.hasPie(e)&&(s.drawPie(r,e,X),ue&&(h||s.nodeShapes[s.getNodeShape(e)].draw(r,f.x,f.y,o,l,re,v)))},we=function(){var ue=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,X=arguments.length>1&&arguments[1]!==void 0?arguments[1]:c;s.hasStripe(e)&&(r.save(),h?r.clip(v.pathCache):(s.nodeShapes[s.getNodeShape(e)].draw(r,f.x,f.y,o,l,re,v),r.clip()),s.drawStripe(r,e,X),r.restore(),ue&&(h||s.nodeShapes[s.getNodeShape(e)].draw(r,f.x,f.y,o,l,re,v)))},ye=function(){var ue=arguments.length>0&&arguments[0]!==void 0?arguments[0]:c,X=(B>0?B:-B)*ue,S=B>0?0:255;B!==0&&(s.colorFillStyle(r,S,S,S,X),h?r.fill(d):r.fill())},ie=function(){if(P>0){if(r.lineWidth=P,r.lineCap=M,r.lineJoin=I,r.setLineDash)switch(L){case"dotted":r.setLineDash([1,1]);break;case"dashed":r.setLineDash(V),r.lineDashOffset=G;break;case"solid":case"double":r.setLineDash([]);break}if(O!=="center"){if(r.save(),r.lineWidth*=2,O==="inside")h?r.clip(d):r.clip();else{var ue=new Path2D;ue.rect(-o/2-P,-l/2-P,o+2*P,l+2*P),ue.addPath(d),r.clip(ue,"evenodd")}h?r.stroke(d):r.stroke(),r.restore()}else h?r.stroke(d):r.stroke();if(L==="double"){r.lineWidth=P/3;var X=r.globalCompositeOperation;r.globalCompositeOperation="destination-out",h?r.stroke(d):r.stroke(),r.globalCompositeOperation=X}r.setLineDash&&r.setLineDash([])}},de=function(){if(F>0){if(r.lineWidth=F,r.lineCap="butt",r.setLineDash)switch(Q){case"dotted":r.setLineDash([1,1]);break;case"dashed":r.setLineDash([4,2]);break;case"solid":case"double":r.setLineDash([]);break}var ue=f;h&&(ue={x:0,y:0});var X=s.getNodeShape(e),S=P;O==="inside"&&(S=0),O==="outside"&&(S*=2);var _=(o+S+(F+j))/o,W=(l+S+(F+j))/l,$=o*_,Z=l*W,oe=s.nodeShapes[X].points,ee;if(h){var ve=q($,Z,X,oe);ee=ve.path}if(X==="ellipse")s.drawEllipsePath(ee||r,ue.x,ue.y,$,Z);else if(["round-diamond","round-heptagon","round-hexagon","round-octagon","round-pentagon","round-polygon","round-triangle","round-tag"].includes(X)){var le=0,me=0,De=0;X==="round-diamond"?le=(S+j+F)*1.4:X==="round-heptagon"?(le=(S+j+F)*1.075,De=-(S/2+j+F)/35):X==="round-hexagon"?le=(S+j+F)*1.12:X==="round-pentagon"?(le=(S+j+F)*1.13,De=-(S/2+j+F)/15):X==="round-tag"?(le=(S+j+F)*1.12,me=(S/2+F+j)*.07):X==="round-triangle"&&(le=(S+j+F)*(Math.PI/2),De=-(S+j/2+F)/Math.PI),le!==0&&(_=(o+le)/o,$=o*_,["round-hexagon","round-tag"].includes(X)||(W=(l+le)/l,Z=l*W)),re=re==="auto"?Ev($,Z):re;for(var Te=$/2,fe=Z/2,Pe=re+(S+F+j)/2,Be=new Array(oe.length/2),je=new Array(oe.length/2),Ke=0;Ke0){if(n=n||a.position(),i==null||s==null){var h=a.padding();i=a.width()+2*h,s=a.height()+2*h}o.colorFillStyle(t,v[0],v[1],v[2],u),o.nodeShapes[f].draw(t,n.x,n.y,i+l*2,s+l*2,c),t.fill()}}}};yt.drawNodeOverlay=Pf("overlay");yt.drawNodeUnderlay=Pf("underlay");yt.hasPie=function(r){return r=r[0],r._private.hasPie};yt.hasStripe=function(r){return r=r[0],r._private.hasStripe};yt.drawPie=function(r,e,t,a){e=e[0],a=a||e.position();var n=e.cy().style(),i=e.pstyle("pie-size"),s=e.pstyle("pie-hole"),o=e.pstyle("pie-start-angle").pfValue,l=a.x,u=a.y,v=e.width(),f=e.height(),c=Math.min(v,f)/2,h,d=0,y=this.usePaths();if(y&&(l=0,u=0),i.units==="%"?c=c*i.pfValue:i.pfValue!==void 0&&(c=i.pfValue/2),s.units==="%"?h=c*s.pfValue:s.pfValue!==void 0&&(h=s.pfValue/2),!(h>=c))for(var g=1;g<=n.pieBackgroundN;g++){var p=e.pstyle("pie-"+g+"-background-size").value,m=e.pstyle("pie-"+g+"-background-color").value,b=e.pstyle("pie-"+g+"-background-opacity").value*t,w=p/100;w+d>1&&(w=1-d);var E=1.5*Math.PI+2*Math.PI*d;E+=o;var C=2*Math.PI*w,x=E+C;p===0||d>=1||d+w>1||(h===0?(r.beginPath(),r.moveTo(l,u),r.arc(l,u,c,E,x),r.closePath()):(r.beginPath(),r.arc(l,u,c,E,x),r.arc(l,u,h,x,E,!0),r.closePath()),this.colorFillStyle(r,m[0],m[1],m[2],b),r.fill(),d+=w)}};yt.drawStripe=function(r,e,t,a){e=e[0],a=a||e.position();var n=e.cy().style(),i=a.x,s=a.y,o=e.width(),l=e.height(),u=0,v=this.usePaths();r.save();var f=e.pstyle("stripe-direction").value,c=e.pstyle("stripe-size");switch(f){case"vertical":break;case"righward":r.rotate(-Math.PI/2);break}var h=o,d=l;c.units==="%"?(h=h*c.pfValue,d=d*c.pfValue):c.pfValue!==void 0&&(h=c.pfValue,d=c.pfValue),v&&(i=0,s=0),s-=h/2,i-=d/2;for(var y=1;y<=n.stripeBackgroundN;y++){var g=e.pstyle("stripe-"+y+"-background-size").value,p=e.pstyle("stripe-"+y+"-background-color").value,m=e.pstyle("stripe-"+y+"-background-opacity").value*t,b=g/100;b+u>1&&(b=1-u),!(g===0||u>=1||u+b>1)&&(r.beginPath(),r.rect(i,s+d*u,h,d*b),r.closePath(),this.colorFillStyle(r,p[0],p[1],p[2],m),r.fill(),u+=b)}r.restore()};var xr={},_y=100;xr.getPixelRatio=function(){var r=this.data.contexts[0];if(this.forcedPixelRatio!=null)return this.forcedPixelRatio;var e=this.cy.window(),t=r.backingStorePixelRatio||r.webkitBackingStorePixelRatio||r.mozBackingStorePixelRatio||r.msBackingStorePixelRatio||r.oBackingStorePixelRatio||r.backingStorePixelRatio||1;return(e.devicePixelRatio||1)/t};xr.paintCache=function(r){for(var e=this.paintCaches=this.paintCaches||[],t=!0,a,n=0;ne.minMbLowQualFrames&&(e.motionBlurPxRatio=e.mbPxRBlurry)),e.clearingMotionBlur&&(e.motionBlurPxRatio=1),e.textureDrawLastFrame&&!f&&(v[e.NODE]=!0,v[e.SELECT_BOX]=!0);var m=t.style(),b=t.zoom(),w=s!==void 0?s:b,E=t.pan(),C={x:E.x,y:E.y},x={zoom:b,pan:{x:E.x,y:E.y}},T=e.prevViewport,k=T===void 0||x.zoom!==T.zoom||x.pan.x!==T.pan.x||x.pan.y!==T.pan.y;!k&&!(y&&!d)&&(e.motionBlurPxRatio=1),o&&(C=o),w*=l,C.x*=l,C.y*=l;var D=e.getCachedZSortedEles();function B(J,z,q,H,Y){var te=J.globalCompositeOperation;J.globalCompositeOperation="destination-out",e.colorFillStyle(J,255,255,255,e.motionBlurTransparency),J.fillRect(z,q,H,Y),J.globalCompositeOperation=te}function P(J,z){var q,H,Y,te;!e.clearingMotionBlur&&(J===u.bufferContexts[e.MOTIONBLUR_BUFFER_NODE]||J===u.bufferContexts[e.MOTIONBLUR_BUFFER_DRAG])?(q={x:E.x*h,y:E.y*h},H=b*h,Y=e.canvasWidth*h,te=e.canvasHeight*h):(q=C,H=w,Y=e.canvasWidth,te=e.canvasHeight),J.setTransform(1,0,0,1,0,0),z==="motionBlur"?B(J,0,0,Y,te):!a&&(z===void 0||z)&&J.clearRect(0,0,Y,te),n||(J.translate(q.x,q.y),J.scale(H,H)),o&&J.translate(o.x,o.y),s&&J.scale(s,s)}if(f||(e.textureDrawLastFrame=!1),f){if(e.textureDrawLastFrame=!0,!e.textureCache){e.textureCache={},e.textureCache.bb=t.mutableElements().boundingBox(),e.textureCache.texture=e.data.bufferCanvases[e.TEXTURE_BUFFER];var A=e.data.bufferContexts[e.TEXTURE_BUFFER];A.setTransform(1,0,0,1,0,0),A.clearRect(0,0,e.canvasWidth*e.textureMult,e.canvasHeight*e.textureMult),e.render({forcedContext:A,drawOnlyNodeLayer:!0,forcedPxRatio:l*e.textureMult});var x=e.textureCache.viewport={zoom:t.zoom(),pan:t.pan(),width:e.canvasWidth,height:e.canvasHeight};x.mpan={x:(0-x.pan.x)/x.zoom,y:(0-x.pan.y)/x.zoom}}v[e.DRAG]=!1,v[e.NODE]=!1;var R=u.contexts[e.NODE],L=e.textureCache.texture,x=e.textureCache.viewport;R.setTransform(1,0,0,1,0,0),c?B(R,0,0,x.width,x.height):R.clearRect(0,0,x.width,x.height);var I=m.core("outside-texture-bg-color").value,M=m.core("outside-texture-bg-opacity").value;e.colorFillStyle(R,I[0],I[1],I[2],M),R.fillRect(0,0,x.width,x.height);var b=t.zoom();P(R,!1),R.clearRect(x.mpan.x,x.mpan.y,x.width/x.zoom/l,x.height/x.zoom/l),R.drawImage(L,x.mpan.x,x.mpan.y,x.width/x.zoom/l,x.height/x.zoom/l)}else e.textureOnViewport&&!a&&(e.textureCache=null);var O=t.extent(),V=e.pinching||e.hoverData.dragging||e.swipePanning||e.data.wheelZooming||e.hoverData.draggingEles||e.cy.animated(),G=e.hideEdgesOnViewport&&V,N=[];if(N[e.NODE]=!v[e.NODE]&&c&&!e.clearedForMotionBlur[e.NODE]||e.clearingMotionBlur,N[e.NODE]&&(e.clearedForMotionBlur[e.NODE]=!0),N[e.DRAG]=!v[e.DRAG]&&c&&!e.clearedForMotionBlur[e.DRAG]||e.clearingMotionBlur,N[e.DRAG]&&(e.clearedForMotionBlur[e.DRAG]=!0),v[e.NODE]||n||i||N[e.NODE]){var F=c&&!N[e.NODE]&&h!==1,R=a||(F?e.data.bufferContexts[e.MOTIONBLUR_BUFFER_NODE]:u.contexts[e.NODE]),U=c&&!F?"motionBlur":void 0;P(R,U),G?e.drawCachedNodes(R,D.nondrag,l,O):e.drawLayeredElements(R,D.nondrag,l,O),e.debug&&e.drawDebugPoints(R,D.nondrag),!n&&!c&&(v[e.NODE]=!1)}if(!i&&(v[e.DRAG]||n||N[e.DRAG])){var F=c&&!N[e.DRAG]&&h!==1,R=a||(F?e.data.bufferContexts[e.MOTIONBLUR_BUFFER_DRAG]:u.contexts[e.DRAG]);P(R,c&&!F?"motionBlur":void 0),G?e.drawCachedNodes(R,D.drag,l,O):e.drawCachedElements(R,D.drag,l,O),e.debug&&e.drawDebugPoints(R,D.drag),!n&&!c&&(v[e.DRAG]=!1)}if(this.drawSelectionRectangle(r,P),c&&h!==1){var Q=u.contexts[e.NODE],K=e.data.bufferCanvases[e.MOTIONBLUR_BUFFER_NODE],j=u.contexts[e.DRAG],re=e.data.bufferCanvases[e.MOTIONBLUR_BUFFER_DRAG],ne=function(z,q,H){z.setTransform(1,0,0,1,0,0),H||!p?z.clearRect(0,0,e.canvasWidth,e.canvasHeight):B(z,0,0,e.canvasWidth,e.canvasHeight);var Y=h;z.drawImage(q,0,0,e.canvasWidth*Y,e.canvasHeight*Y,0,0,e.canvasWidth,e.canvasHeight)};(v[e.NODE]||N[e.NODE])&&(ne(Q,K,N[e.NODE]),v[e.NODE]=!1),(v[e.DRAG]||N[e.DRAG])&&(ne(j,re,N[e.DRAG]),v[e.DRAG]=!1)}e.prevViewport=x,e.clearingMotionBlur&&(e.clearingMotionBlur=!1,e.motionBlurCleared=!0,e.motionBlur=!0),c&&(e.motionBlurTimeout=setTimeout(function(){e.motionBlurTimeout=null,e.clearedForMotionBlur[e.NODE]=!1,e.clearedForMotionBlur[e.DRAG]=!1,e.motionBlur=!1,e.clearingMotionBlur=!f,e.mbFrames=0,v[e.NODE]=!0,v[e.DRAG]=!0,e.redraw()},_y)),a||t.emit("render")};var ha;xr.drawSelectionRectangle=function(r,e){var t=this,a=t.cy,n=t.data,i=a.style(),s=r.drawOnlyNodeLayer,o=r.drawAllLayers,l=n.canvasNeedsRedraw,u=r.forcedContext;if(t.showFps||!s&&l[t.SELECT_BOX]&&!o){var v=u||n.contexts[t.SELECT_BOX];if(e(v),t.selection[4]==1&&(t.hoverData.selecting||t.touchData.selecting)){var f=t.cy.zoom(),c=i.core("selection-box-border-width").value/f;v.lineWidth=c,v.fillStyle="rgba("+i.core("selection-box-color").value[0]+","+i.core("selection-box-color").value[1]+","+i.core("selection-box-color").value[2]+","+i.core("selection-box-opacity").value+")",v.fillRect(t.selection[0],t.selection[1],t.selection[2]-t.selection[0],t.selection[3]-t.selection[1]),c>0&&(v.strokeStyle="rgba("+i.core("selection-box-border-color").value[0]+","+i.core("selection-box-border-color").value[1]+","+i.core("selection-box-border-color").value[2]+","+i.core("selection-box-opacity").value+")",v.strokeRect(t.selection[0],t.selection[1],t.selection[2]-t.selection[0],t.selection[3]-t.selection[1]))}if(n.bgActivePosistion&&!t.hoverData.selecting){var f=t.cy.zoom(),h=n.bgActivePosistion;v.fillStyle="rgba("+i.core("active-bg-color").value[0]+","+i.core("active-bg-color").value[1]+","+i.core("active-bg-color").value[2]+","+i.core("active-bg-opacity").value+")",v.beginPath(),v.arc(h.x,h.y,i.core("active-bg-size").pfValue/f,0,2*Math.PI),v.fill()}var d=t.lastRedrawTime;if(t.showFps&&d){d=Math.round(d);var y=Math.round(1e3/d),g="1 frame = "+d+" ms = "+y+" fps";if(v.setTransform(1,0,0,1,0,0),v.fillStyle="rgba(255, 0, 0, 0.75)",v.strokeStyle="rgba(255, 0, 0, 0.75)",v.font="30px Arial",!ha){var p=v.measureText(g);ha=p.actualBoundingBoxAscent}v.fillText(g,0,ha);var m=60;v.strokeRect(0,ha+10,250,20),v.fillRect(0,ha+10,250*Math.min(y/m,1),20)}o||(l[t.SELECT_BOX]=!1)}};function Hl(r,e,t){var a=r.createShader(e);if(r.shaderSource(a,t),r.compileShader(a),!r.getShaderParameter(a,r.COMPILE_STATUS))throw new Error(r.getShaderInfoLog(a));return a}function Gy(r,e,t){var a=Hl(r,r.VERTEX_SHADER,e),n=Hl(r,r.FRAGMENT_SHADER,t),i=r.createProgram();if(r.attachShader(i,a),r.attachShader(i,n),r.linkProgram(i),!r.getProgramParameter(i,r.LINK_STATUS))throw new Error("Could not initialize shaders");return i}function Hy(r,e,t){t===void 0&&(t=e);var a=r.makeOffscreenCanvas(e,t),n=a.context=a.getContext("2d");return a.clear=function(){return n.clearRect(0,0,a.width,a.height)},a.clear(),a}function bo(r){var e=r.pixelRatio,t=r.cy.zoom(),a=r.cy.pan();return{zoom:t*e,pan:{x:a.x*e,y:a.y*e}}}function Wy(r){var e=r.pixelRatio,t=r.cy.zoom();return t*e}function $y(r,e,t,a,n){var i=a*t+e.x,s=n*t+e.y;return s=Math.round(r.canvasHeight-s),[i,s]}function Uy(r){return r.pstyle("background-fill").value!=="solid"||r.pstyle("background-image").strValue!=="none"?!1:r.pstyle("border-width").value===0||r.pstyle("border-opacity").value===0?!0:r.pstyle("border-style").value==="solid"}function Ky(r,e){if(r.length!==e.length)return!1;for(var t=0;t>0&255)/255,t[1]=(r>>8&255)/255,t[2]=(r>>16&255)/255,t[3]=(r>>24&255)/255,t}function Xy(r){return r[0]+(r[1]<<8)+(r[2]<<16)+(r[3]<<24)}function Yy(r,e){var t=r.createTexture();return t.buffer=function(a){r.bindTexture(r.TEXTURE_2D,t),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_S,r.CLAMP_TO_EDGE),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_WRAP_T,r.CLAMP_TO_EDGE),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MAG_FILTER,r.LINEAR),r.texParameteri(r.TEXTURE_2D,r.TEXTURE_MIN_FILTER,r.LINEAR_MIPMAP_NEAREST),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),r.texImage2D(r.TEXTURE_2D,0,r.RGBA,r.RGBA,r.UNSIGNED_BYTE,a),r.generateMipmap(r.TEXTURE_2D),r.bindTexture(r.TEXTURE_2D,null)},t.deleteTexture=function(){r.deleteTexture(t)},t}function Af(r,e){switch(e){case"float":return[1,r.FLOAT,4];case"vec2":return[2,r.FLOAT,4];case"vec3":return[3,r.FLOAT,4];case"vec4":return[4,r.FLOAT,4];case"int":return[1,r.INT,4];case"ivec2":return[2,r.INT,4]}}function Rf(r,e,t){switch(e){case r.FLOAT:return new Float32Array(t);case r.INT:return new Int32Array(t)}}function Zy(r,e,t,a,n,i){switch(e){case r.FLOAT:return new Float32Array(t.buffer,i*a,n);case r.INT:return new Int32Array(t.buffer,i*a,n)}}function Qy(r,e,t,a){var n=Af(r,e),i=Je(n,2),s=i[0],o=i[1],l=Rf(r,o,a),u=r.createBuffer();return r.bindBuffer(r.ARRAY_BUFFER,u),r.bufferData(r.ARRAY_BUFFER,l,r.STATIC_DRAW),o===r.FLOAT?r.vertexAttribPointer(t,s,o,!1,0,0):o===r.INT&&r.vertexAttribIPointer(t,s,o,0,0),r.enableVertexAttribArray(t),r.bindBuffer(r.ARRAY_BUFFER,null),u}function Fr(r,e,t,a){var n=Af(r,t),i=Je(n,3),s=i[0],o=i[1],l=i[2],u=Rf(r,o,e*s),v=s*l,f=r.createBuffer();r.bindBuffer(r.ARRAY_BUFFER,f),r.bufferData(r.ARRAY_BUFFER,e*v,r.DYNAMIC_DRAW),r.enableVertexAttribArray(a),o===r.FLOAT?r.vertexAttribPointer(a,s,o,!1,v,0):o===r.INT&&r.vertexAttribIPointer(a,s,o,v,0),r.vertexAttribDivisor(a,1),r.bindBuffer(r.ARRAY_BUFFER,null);for(var c=new Array(e),h=0;hs&&(o=s/a,l=a*o,u=n*o),{scale:o,texW:l,texH:u}}},{key:"draw",value:function(t,a,n){var i=this;if(this.locked)throw new Error("can't draw, atlas is locked");var s=this.texSize,o=this.texRows,l=this.texHeight,u=this.getScale(a),v=u.scale,f=u.texW,c=u.texH,h=function(b,w){if(n&&w){var E=w.context,C=b.x,x=b.row,T=C,k=l*x;E.save(),E.translate(T,k),E.scale(v,v),n(E,a),E.restore()}},d=[null,null],y=function(){h(i.freePointer,i.canvas),d[0]={x:i.freePointer.x,y:i.freePointer.row*l,w:f,h:c},d[1]={x:i.freePointer.x+f,y:i.freePointer.row*l,w:0,h:c},i.freePointer.x+=f,i.freePointer.x==s&&(i.freePointer.x=0,i.freePointer.row++)},g=function(){var b=i.scratch,w=i.canvas;b.clear(),h({x:0,row:0},b);var E=s-i.freePointer.x,C=f-E,x=l;{var T=i.freePointer.x,k=i.freePointer.row*l,D=E;w.context.drawImage(b,0,0,D,x,T,k,D,x),d[0]={x:T,y:k,w:D,h:c}}{var B=E,P=(i.freePointer.row+1)*l,A=C;w&&w.context.drawImage(b,B,0,A,x,0,P,A,x),d[1]={x:0,y:P,w:A,h:c}}i.freePointer.x=C,i.freePointer.row++},p=function(){i.freePointer.x=0,i.freePointer.row++};if(this.freePointer.x+f<=s)y();else{if(this.freePointer.row>=o-1)return!1;this.freePointer.x===s?(p(),y()):this.enableWrapping?g():(p(),y())}return this.keyToLocation.set(t,d),this.needsBuffer=!0,d}},{key:"getOffsets",value:function(t){return this.keyToLocation.get(t)}},{key:"isEmpty",value:function(){return this.freePointer.x===0&&this.freePointer.row===0}},{key:"canFit",value:function(t){if(this.locked)return!1;var a=this.texSize,n=this.texRows,i=this.getScale(t),s=i.texW;return this.freePointer.x+s>a?this.freePointer.row1&&arguments[1]!==void 0?arguments[1]:{},i=n.forceRedraw,s=i===void 0?!1:i,o=n.filterEle,l=o===void 0?function(){return!0}:o,u=n.filterType,v=u===void 0?function(){return!0}:u,f=!1,c=!1,h=kr(t),d;try{for(h.s();!(d=h.n()).done;){var y=d.value;if(l(y)){var g=kr(this.renderTypes.values()),p;try{var m=function(){var w=p.value,E=w.type;if(v(E)){var C=a.collections.get(w.collection),x=w.getKey(y),T=Array.isArray(x)?x:[x];if(s)T.forEach(function(P){return C.markKeyForGC(P)}),c=!0;else{var k=w.getID?w.getID(y):y.id(),D=a._key(E,k),B=a.typeAndIdToKey.get(D);B!==void 0&&!Ky(T,B)&&(f=!0,a.typeAndIdToKey.delete(D),B.forEach(function(P){return C.markKeyForGC(P)}))}}};for(g.s();!(p=g.n()).done;)m()}catch(b){g.e(b)}finally{g.f()}}}}catch(b){h.e(b)}finally{h.f()}return c&&(this.gc(),f=!1),f}},{key:"gc",value:function(){var t=kr(this.collections.values()),a;try{for(t.s();!(a=t.n()).done;){var n=a.value;n.gc()}}catch(i){t.e(i)}finally{t.f()}}},{key:"getOrCreateAtlas",value:function(t,a,n,i){var s=this.renderTypes.get(a),o=this.collections.get(s.collection),l=!1,u=o.draw(i,n,function(c){s.drawClipped?(c.save(),c.beginPath(),c.rect(0,0,n.w,n.h),c.clip(),s.drawElement(c,t,n,!0,!0),c.restore()):s.drawElement(c,t,n,!0,!0),l=!0});if(l){var v=s.getID?s.getID(t):t.id(),f=this._key(a,v);this.typeAndIdToKey.has(f)?this.typeAndIdToKey.get(f).push(i):this.typeAndIdToKey.set(f,[i])}return u}},{key:"getAtlasInfo",value:function(t,a){var n=this,i=this.renderTypes.get(a),s=i.getKey(t),o=Array.isArray(s)?s:[s];return o.map(function(l){var u=i.getBoundingBox(t,l),v=n.getOrCreateAtlas(t,a,u,l),f=v.getOffsets(l),c=Je(f,2),h=c[0],d=c[1];return{atlas:v,tex:h,tex1:h,tex2:d,bb:u}})}},{key:"getDebugInfo",value:function(){var t=[],a=kr(this.collections),n;try{for(a.s();!(n=a.n()).done;){var i=Je(n.value,2),s=i[0],o=i[1],l=o.getCounts(),u=l.keyCount,v=l.atlasCount;t.push({type:s,keyCount:u,atlasCount:v})}}catch(f){a.e(f)}finally{a.f()}return t}}])}(),sm=function(){function r(e){ht(this,r),this.globalOptions=e,this.atlasSize=e.webglTexSize,this.maxAtlasesPerBatch=e.webglTexPerBatch,this.batchAtlases=[]}return gt(r,[{key:"getMaxAtlasesPerBatch",value:function(){return this.maxAtlasesPerBatch}},{key:"getAtlasSize",value:function(){return this.atlasSize}},{key:"getIndexArray",value:function(){return Array.from({length:this.maxAtlasesPerBatch},function(t,a){return a})}},{key:"startBatch",value:function(){this.batchAtlases=[]}},{key:"getAtlasCount",value:function(){return this.batchAtlases.length}},{key:"getAtlases",value:function(){return this.batchAtlases}},{key:"canAddToCurrentBatch",value:function(t){return this.batchAtlases.length===this.maxAtlasesPerBatch?this.batchAtlases.includes(t):!0}},{key:"getAtlasIndexForBatch",value:function(t){var a=this.batchAtlases.indexOf(t);if(a<0){if(this.batchAtlases.length===this.maxAtlasesPerBatch)throw new Error("cannot add more atlases to batch");this.batchAtlases.push(t),a=this.batchAtlases.length-1}return a}}])}(),om=` + float circleSD(vec2 p, float r) { + return distance(vec2(0), p) - r; // signed distance + } +`,um=` + float rectangleSD(vec2 p, vec2 b) { + vec2 d = abs(p)-b; + return distance(vec2(0),max(d,0.0)) + min(max(d.x,d.y),0.0); + } +`,lm=` + float roundRectangleSD(vec2 p, vec2 b, vec4 cr) { + cr.xy = (p.x > 0.0) ? cr.xy : cr.zw; + cr.x = (p.y > 0.0) ? cr.x : cr.y; + vec2 q = abs(p) - b + cr.x; + return min(max(q.x, q.y), 0.0) + distance(vec2(0), max(q, 0.0)) - cr.x; + } +`,vm=` + float ellipseSD(vec2 p, vec2 ab) { + p = abs( p ); // symmetry + + // find root with Newton solver + vec2 q = ab*(p-ab); + float w = (q.x1.0) ? d : -d; + } +`,Ea={SCREEN:{name:"screen",screen:!0},PICKING:{name:"picking",picking:!0}},An={IGNORE:1,USE_BB:2},Cs=0,Kl=1,Xl=2,Ts=3,_t=4,sn=5,ga=6,pa=7,fm=function(){function r(e,t,a){ht(this,r),this.r=e,this.gl=t,this.maxInstances=a.webglBatchSize,this.atlasSize=a.webglTexSize,this.bgColor=a.bgColor,this.debug=a.webglDebug,this.batchDebugInfo=[],a.enableWrapping=!0,a.createTextureCanvas=Hy,this.atlasManager=new im(e,a),this.batchManager=new sm(a),this.simpleShapeOptions=new Map,this.program=this._createShaderProgram(Ea.SCREEN),this.pickingProgram=this._createShaderProgram(Ea.PICKING),this.vao=this._createVAO()}return gt(r,[{key:"addAtlasCollection",value:function(t,a){this.atlasManager.addAtlasCollection(t,a)}},{key:"addTextureAtlasRenderType",value:function(t,a){this.atlasManager.addRenderType(t,a)}},{key:"addSimpleShapeRenderType",value:function(t,a){this.simpleShapeOptions.set(t,a)}},{key:"invalidate",value:function(t){var a=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},n=a.type,i=this.atlasManager;return n?i.invalidate(t,{filterType:function(o){return o===n},forceRedraw:!0}):i.invalidate(t)}},{key:"gc",value:function(){this.atlasManager.gc()}},{key:"_createShaderProgram",value:function(t){var a=this.gl,n=`#version 300 es + precision highp float; + + uniform mat3 uPanZoomMatrix; + uniform int uAtlasSize; + + // instanced + in vec2 aPosition; // a vertex from the unit square + + in mat3 aTransform; // used to transform verticies, eg into a bounding box + in int aVertType; // the type of thing we are rendering + + // the z-index that is output when using picking mode + in vec4 aIndex; + + // For textures + in int aAtlasId; // which shader unit/atlas to use + in vec4 aTex; // x/y/w/h of texture in atlas + + // for edges + in vec4 aPointAPointB; + in vec4 aPointCPointD; + in vec2 aLineWidth; // also used for node border width + + // simple shapes + in vec4 aCornerRadius; // for round-rectangle [top-right, bottom-right, top-left, bottom-left] + in vec4 aColor; // also used for edges + in vec4 aBorderColor; // aLineWidth is used for border width + + // output values passed to the fragment shader + out vec2 vTexCoord; + out vec4 vColor; + out vec2 vPosition; + // flat values are not interpolated + flat out int vAtlasId; + flat out int vVertType; + flat out vec2 vTopRight; + flat out vec2 vBotLeft; + flat out vec4 vCornerRadius; + flat out vec4 vBorderColor; + flat out vec2 vBorderWidth; + flat out vec4 vIndex; + + void main(void) { + int vid = gl_VertexID; + vec2 position = aPosition; // TODO make this a vec3, simplifies some code below + + if(aVertType == `.concat(Cs,`) { + float texX = aTex.x; // texture coordinates + float texY = aTex.y; + float texW = aTex.z; + float texH = aTex.w; + + if(vid == 1 || vid == 2 || vid == 4) { + texX += texW; + } + if(vid == 2 || vid == 4 || vid == 5) { + texY += texH; + } + + float d = float(uAtlasSize); + vTexCoord = vec2(texX / d, texY / d); // tex coords must be between 0 and 1 + + gl_Position = vec4(uPanZoomMatrix * aTransform * vec3(position, 1.0), 1.0); + } + else if(aVertType == `).concat(_t," || aVertType == ").concat(pa,` + || aVertType == `).concat(sn," || aVertType == ").concat(ga,`) { // simple shapes + + // the bounding box is needed by the fragment shader + vBotLeft = (aTransform * vec3(0, 0, 1)).xy; // flat + vTopRight = (aTransform * vec3(1, 1, 1)).xy; // flat + vPosition = (aTransform * vec3(position, 1)).xy; // will be interpolated + + // calculations are done in the fragment shader, just pass these along + vColor = aColor; + vCornerRadius = aCornerRadius; + vBorderColor = aBorderColor; + vBorderWidth = aLineWidth; + + gl_Position = vec4(uPanZoomMatrix * aTransform * vec3(position, 1.0), 1.0); + } + else if(aVertType == `).concat(Kl,`) { + vec2 source = aPointAPointB.xy; + vec2 target = aPointAPointB.zw; + + // adjust the geometry so that the line is centered on the edge + position.y = position.y - 0.5; + + // stretch the unit square into a long skinny rectangle + vec2 xBasis = target - source; + vec2 yBasis = normalize(vec2(-xBasis.y, xBasis.x)); + vec2 point = source + xBasis * position.x + yBasis * aLineWidth[0] * position.y; + + gl_Position = vec4(uPanZoomMatrix * vec3(point, 1.0), 1.0); + vColor = aColor; + } + else if(aVertType == `).concat(Xl,`) { + vec2 pointA = aPointAPointB.xy; + vec2 pointB = aPointAPointB.zw; + vec2 pointC = aPointCPointD.xy; + vec2 pointD = aPointCPointD.zw; + + // adjust the geometry so that the line is centered on the edge + position.y = position.y - 0.5; + + vec2 p0, p1, p2, pos; + if(position.x == 0.0) { // The left side of the unit square + p0 = pointA; + p1 = pointB; + p2 = pointC; + pos = position; + } else { // The right side of the unit square, use same approach but flip the geometry upside down + p0 = pointD; + p1 = pointC; + p2 = pointB; + pos = vec2(0.0, -position.y); + } + + vec2 p01 = p1 - p0; + vec2 p12 = p2 - p1; + vec2 p21 = p1 - p2; + + // Find the normal vector. + vec2 tangent = normalize(normalize(p12) + normalize(p01)); + vec2 normal = vec2(-tangent.y, tangent.x); + + // Find the vector perpendicular to p0 -> p1. + vec2 p01Norm = normalize(vec2(-p01.y, p01.x)); + + // Determine the bend direction. + float sigma = sign(dot(p01 + p21, normal)); + float width = aLineWidth[0]; + + if(sign(pos.y) == -sigma) { + // This is an intersecting vertex. Adjust the position so that there's no overlap. + vec2 point = 0.5 * width * normal * -sigma / dot(normal, p01Norm); + gl_Position = vec4(uPanZoomMatrix * vec3(p1 + point, 1.0), 1.0); + } else { + // This is a non-intersecting vertex. Treat it like a mitre join. + vec2 point = 0.5 * width * normal * sigma * dot(normal, p01Norm); + gl_Position = vec4(uPanZoomMatrix * vec3(p1 + point, 1.0), 1.0); + } + + vColor = aColor; + } + else if(aVertType == `).concat(Ts,` && vid < 3) { + // massage the first triangle into an edge arrow + if(vid == 0) + position = vec2(-0.15, -0.3); + if(vid == 1) + position = vec2( 0.0, 0.0); + if(vid == 2) + position = vec2( 0.15, -0.3); + + gl_Position = vec4(uPanZoomMatrix * aTransform * vec3(position, 1.0), 1.0); + vColor = aColor; + } + else { + gl_Position = vec4(2.0, 0.0, 0.0, 1.0); // discard vertex by putting it outside webgl clip space + } + + vAtlasId = aAtlasId; + vVertType = aVertType; + vIndex = aIndex; + } + `),i=this.batchManager.getIndexArray(),s=`#version 300 es + precision highp float; + + // declare texture unit for each texture atlas in the batch + `.concat(i.map(function(u){return"uniform sampler2D uTexture".concat(u,";")}).join(` + `),` + + uniform vec4 uBGColor; + uniform float uZoom; + + in vec2 vTexCoord; + in vec4 vColor; + in vec2 vPosition; // model coordinates + + flat in int vAtlasId; + flat in vec4 vIndex; + flat in int vVertType; + flat in vec2 vTopRight; + flat in vec2 vBotLeft; + flat in vec4 vCornerRadius; + flat in vec4 vBorderColor; + flat in vec2 vBorderWidth; + + out vec4 outColor; + + `).concat(om,` + `).concat(um,` + `).concat(lm,` + `).concat(vm,` + + vec4 blend(vec4 top, vec4 bot) { // blend colors with premultiplied alpha + return vec4( + top.rgb + (bot.rgb * (1.0 - top.a)), + top.a + (bot.a * (1.0 - top.a)) + ); + } + + vec4 distInterp(vec4 cA, vec4 cB, float d) { // interpolate color using Signed Distance + // scale to the zoom level so that borders don't look blurry when zoomed in + // note 1.5 is an aribitrary value chosen because it looks good + return mix(cA, cB, 1.0 - smoothstep(0.0, 1.5 / uZoom, abs(d))); + } + + void main(void) { + if(vVertType == `).concat(Cs,`) { + // look up the texel from the texture unit + `).concat(i.map(function(u){return"if(vAtlasId == ".concat(u,") outColor = texture(uTexture").concat(u,", vTexCoord);")}).join(` + else `),` + } + else if(vVertType == `).concat(Ts,`) { + // mimics how canvas renderer uses context.globalCompositeOperation = 'destination-out'; + outColor = blend(vColor, uBGColor); + outColor.a = 1.0; // make opaque, masks out line under arrow + } + else if(vVertType == `).concat(_t,` && vBorderWidth == vec2(0.0)) { // simple rectangle with no border + outColor = vColor; // unit square is already transformed to the rectangle, nothing else needs to be done + } + else if(vVertType == `).concat(_t," || vVertType == ").concat(pa,` + || vVertType == `).concat(sn," || vVertType == ").concat(ga,`) { // use SDF + + float outerBorder = vBorderWidth[0]; + float innerBorder = vBorderWidth[1]; + float borderPadding = outerBorder * 2.0; + float w = vTopRight.x - vBotLeft.x - borderPadding; + float h = vTopRight.y - vBotLeft.y - borderPadding; + vec2 b = vec2(w/2.0, h/2.0); // half width, half height + vec2 p = vPosition - vec2(vTopRight.x - b[0] - outerBorder, vTopRight.y - b[1] - outerBorder); // translate to center + + float d; // signed distance + if(vVertType == `).concat(_t,`) { + d = rectangleSD(p, b); + } else if(vVertType == `).concat(pa,` && w == h) { + d = circleSD(p, b.x); // faster than ellipse + } else if(vVertType == `).concat(pa,`) { + d = ellipseSD(p, b); + } else { + d = roundRectangleSD(p, b, vCornerRadius.wzyx); + } + + // use the distance to interpolate a color to smooth the edges of the shape, doesn't need multisampling + // we must smooth colors inwards, because we can't change pixels outside the shape's bounding box + if(d > 0.0) { + if(d > outerBorder) { + discard; + } else { + outColor = distInterp(vBorderColor, vec4(0), d - outerBorder); + } + } else { + if(d > innerBorder) { + vec4 outerColor = outerBorder == 0.0 ? vec4(0) : vBorderColor; + vec4 innerBorderColor = blend(vBorderColor, vColor); + outColor = distInterp(innerBorderColor, outerColor, d); + } + else { + vec4 outerColor; + if(innerBorder == 0.0 && outerBorder == 0.0) { + outerColor = vec4(0); + } else if(innerBorder == 0.0) { + outerColor = vBorderColor; + } else { + outerColor = blend(vBorderColor, vColor); + } + outColor = distInterp(vColor, outerColor, d - innerBorder); + } + } + } + else { + outColor = vColor; + } + + `).concat(t.picking?`if(outColor.a == 0.0) discard; + else outColor = vIndex;`:"",` + } + `),o=Gy(a,n,s);o.aPosition=a.getAttribLocation(o,"aPosition"),o.aIndex=a.getAttribLocation(o,"aIndex"),o.aVertType=a.getAttribLocation(o,"aVertType"),o.aTransform=a.getAttribLocation(o,"aTransform"),o.aAtlasId=a.getAttribLocation(o,"aAtlasId"),o.aTex=a.getAttribLocation(o,"aTex"),o.aPointAPointB=a.getAttribLocation(o,"aPointAPointB"),o.aPointCPointD=a.getAttribLocation(o,"aPointCPointD"),o.aLineWidth=a.getAttribLocation(o,"aLineWidth"),o.aColor=a.getAttribLocation(o,"aColor"),o.aCornerRadius=a.getAttribLocation(o,"aCornerRadius"),o.aBorderColor=a.getAttribLocation(o,"aBorderColor"),o.uPanZoomMatrix=a.getUniformLocation(o,"uPanZoomMatrix"),o.uAtlasSize=a.getUniformLocation(o,"uAtlasSize"),o.uBGColor=a.getUniformLocation(o,"uBGColor"),o.uZoom=a.getUniformLocation(o,"uZoom"),o.uTextures=[];for(var l=0;l1&&arguments[1]!==void 0?arguments[1]:Ea.SCREEN;this.panZoomMatrix=t,this.renderTarget=a,this.batchDebugInfo=[],this.wrappedCount=0,this.simpleCount=0,this.startBatch()}},{key:"startBatch",value:function(){this.instanceCount=0,this.batchManager.startBatch()}},{key:"endFrame",value:function(){this.endBatch()}},{key:"_isVisible",value:function(t,a){return t.visible()?a&&a.isVisible?a.isVisible(t):!0:!1}},{key:"drawTexture",value:function(t,a,n){var i=this.atlasManager,s=this.batchManager,o=i.getRenderTypeOpts(n);if(this._isVisible(t,o)&&!(t.isEdge()&&!this._isValidEdge(t))){if(this.renderTarget.picking&&o.getTexPickingMode){var l=o.getTexPickingMode(t);if(l===An.IGNORE)return;if(l==An.USE_BB){this.drawPickingRectangle(t,a,n);return}}var u=i.getAtlasInfo(t,n),v=kr(u),f;try{for(v.s();!(f=v.n()).done;){var c=f.value,h=c.atlas,d=c.tex1,y=c.tex2;s.canAddToCurrentBatch(h)||this.endBatch();for(var g=s.getAtlasIndexForBatch(h),p=0,m=[[d,!0],[y,!1]];p=this.maxInstances&&this.endBatch()}}}}catch(B){v.e(B)}finally{v.f()}}}},{key:"setTransformMatrix",value:function(t,a,n,i){var s=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,o=0;if(n.shapeProps&&n.shapeProps.padding&&(o=t.pstyle(n.shapeProps.padding).pfValue),i){var l=i.bb,u=i.tex1,v=i.tex2,f=u.w/(u.w+v.w);s||(f=1-f);var c=this._getAdjustedBB(l,o,s,f);this._applyTransformMatrix(a,c,n,t)}else{var h=n.getBoundingBox(t),d=this._getAdjustedBB(h,o,!0,1);this._applyTransformMatrix(a,d,n,t)}}},{key:"_applyTransformMatrix",value:function(t,a,n,i){var s,o;$l(t);var l=n.getRotation?n.getRotation(i):0;if(l!==0){var u=n.getRotationPoint(i),v=u.x,f=u.y;yn(t,t,[v,f]),Ul(t,t,l);var c=n.getRotationOffset(i);s=c.x+(a.xOffset||0),o=c.y+(a.yOffset||0)}else s=a.x1,o=a.y1;yn(t,t,[s,o]),Ws(t,t,[a.w,a.h])}},{key:"_getAdjustedBB",value:function(t,a,n,i){var s=t.x1,o=t.y1,l=t.w,u=t.h,v=t.yOffset;a&&(s-=a,o-=a,l+=2*a,u+=2*a);var f=0,c=l*i;return n&&i<1?l=c:!n&&i<1&&(f=l-c,s+=f,l=c),{x1:s,y1:o,w:l,h:u,xOffset:f,yOffset:v}}},{key:"drawPickingRectangle",value:function(t,a,n){var i=this.atlasManager.getRenderTypeOpts(n),s=this.instanceCount;this.vertTypeBuffer.getView(s)[0]=_t;var o=this.indexBuffer.getView(s);qt(a,o);var l=this.colorBuffer.getView(s);xt([0,0,0],1,l);var u=this.transformBuffer.getMatrixView(s);this.setTransformMatrix(t,u,i),this.simpleCount++,this.instanceCount++,this.instanceCount>=this.maxInstances&&this.endBatch()}},{key:"drawNode",value:function(t,a,n){var i=this.simpleShapeOptions.get(n);if(this._isVisible(t,i)){var s=i.shapeProps,o=this._getVertTypeForShape(t,s.shape);if(o===void 0||i.isSimple&&!i.isSimple(t)){this.drawTexture(t,a,n);return}var l=this.instanceCount;if(this.vertTypeBuffer.getView(l)[0]=o,o===sn||o===ga){var u=i.getBoundingBox(t),v=this._getCornerRadius(t,s.radius,u),f=this.cornerRadiusBuffer.getView(l);f[0]=v,f[1]=v,f[2]=v,f[3]=v,o===ga&&(f[0]=0,f[2]=0)}var c=this.indexBuffer.getView(l);qt(a,c);var h=t.pstyle(s.color).value,d=t.pstyle(s.opacity).value,y=this.colorBuffer.getView(l);xt(h,d,y);var g=this.lineWidthBuffer.getView(l);if(g[0]=0,g[1]=0,s.border){var p=t.pstyle("border-width").value;if(p>0){var m=t.pstyle("border-color").value,b=t.pstyle("border-opacity").value,w=this.borderColorBuffer.getView(l);xt(m,b,w);var E=t.pstyle("border-position").value;if(E==="inside")g[0]=0,g[1]=-p;else if(E==="outside")g[0]=p,g[1]=0;else{var C=p/2;g[0]=C,g[1]=-C}}}var x=this.transformBuffer.getMatrixView(l);this.setTransformMatrix(t,x,i),this.simpleCount++,this.instanceCount++,this.instanceCount>=this.maxInstances&&this.endBatch()}}},{key:"_getVertTypeForShape",value:function(t,a){var n=t.pstyle(a).value;switch(n){case"rectangle":return _t;case"ellipse":return pa;case"roundrectangle":case"round-rectangle":return sn;case"bottom-round-rectangle":return ga;default:return}}},{key:"_getCornerRadius",value:function(t,a,n){var i=n.w,s=n.h;if(t.pstyle(a).value==="auto")return vt(i,s);var o=t.pstyle(a).pfValue,l=i/2,u=s/2;return Math.min(o,u,l)}},{key:"drawEdgeArrow",value:function(t,a,n){if(t.visible()){var i=t._private.rscratch,s,o,l;if(n==="source"?(s=i.arrowStartX,o=i.arrowStartY,l=i.srcArrowAngle):(s=i.arrowEndX,o=i.arrowEndY,l=i.tgtArrowAngle),!(isNaN(s)||s==null||isNaN(o)||o==null||isNaN(l)||l==null)){var u=t.pstyle(n+"-arrow-shape").value;if(u!=="none"){var v=t.pstyle(n+"-arrow-color").value,f=t.pstyle("opacity").value,c=t.pstyle("line-opacity").value,h=f*c,d=t.pstyle("width").pfValue,y=t.pstyle("arrow-scale").value,g=this.r.getArrowWidth(d,y),p=this.instanceCount,m=this.transformBuffer.getMatrixView(p);$l(m),yn(m,m,[s,o]),Ws(m,m,[g,g]),Ul(m,m,l),this.vertTypeBuffer.getView(p)[0]=Ts;var b=this.indexBuffer.getView(p);qt(a,b);var w=this.colorBuffer.getView(p);xt(v,h,w),this.instanceCount++,this.instanceCount>=this.maxInstances&&this.endBatch()}}}}},{key:"drawEdgeLine",value:function(t,a){if(t.visible()){var n=this._getEdgePoints(t);if(n){var i=t.pstyle("opacity").value,s=t.pstyle("line-opacity").value,o=t.pstyle("width").pfValue,l=t.pstyle("line-color").value,u=i*s;if(n.length/2+this.instanceCount>this.maxInstances&&this.endBatch(),n.length==4){var v=this.instanceCount;this.vertTypeBuffer.getView(v)[0]=Kl;var f=this.indexBuffer.getView(v);qt(a,f);var c=this.colorBuffer.getView(v);xt(l,u,c);var h=this.lineWidthBuffer.getView(v);h[0]=o;var d=this.pointAPointBBuffer.getView(v);d[0]=n[0],d[1]=n[1],d[2]=n[2],d[3]=n[3],this.instanceCount++,this.instanceCount>=this.maxInstances&&this.endBatch()}else for(var y=0;y=this.maxInstances&&this.endBatch()}}}}},{key:"_isValidEdge",value:function(t){var a=t._private.rscratch;return!(a.badLine||a.allpts==null||isNaN(a.allpts[0]))}},{key:"_getEdgePoints",value:function(t){var a=t._private.rscratch;if(this._isValidEdge(t)){var n=a.allpts;if(n.length==4)return n;var i=this._getNumSegments(t);return this._getCurveSegmentPoints(n,i)}}},{key:"_getNumSegments",value:function(t){var a=15;return Math.min(Math.max(a,5),this.maxInstances)}},{key:"_getCurveSegmentPoints",value:function(t,a){if(t.length==4)return t;for(var n=Array((a+1)*2),i=0;i<=a;i++)if(i==0)n[0]=t[0],n[1]=t[1];else if(i==a)n[i*2]=t[t.length-2],n[i*2+1]=t[t.length-1];else{var s=i/a;this._setCurvePoint(t,s,n,i*2)}return n}},{key:"_setCurvePoint",value:function(t,a,n,i){if(t.length<=2)n[i]=t[0],n[i+1]=t[1];else{for(var s=Array(t.length-2),o=0;o0}},o=function(f){var c=f.pstyle("text-events").strValue==="yes";return c?An.USE_BB:An.IGNORE},l=function(f){var c=f.position(),h=c.x,d=c.y,y=f.outerWidth(),g=f.outerHeight();return{w:y,h:g,x1:h-y/2,y1:d-g/2}};t.drawing.addAtlasCollection("node",{texRows:r.webglTexRowsNodes}),t.drawing.addAtlasCollection("label",{texRows:r.webglTexRows}),t.drawing.addTextureAtlasRenderType("node-body",{collection:"node",getKey:e.getStyleKey,getBoundingBox:e.getElementBox,drawElement:e.drawElement}),t.drawing.addSimpleShapeRenderType("node-body",{getBoundingBox:l,isSimple:Uy,shapeProps:{shape:"shape",color:"background-color",opacity:"background-opacity",radius:"corner-radius",border:!0}}),t.drawing.addSimpleShapeRenderType("node-overlay",{getBoundingBox:l,isVisible:s("overlay"),shapeProps:{shape:"overlay-shape",color:"overlay-color",opacity:"overlay-opacity",padding:"overlay-padding",radius:"overlay-corner-radius"}}),t.drawing.addSimpleShapeRenderType("node-underlay",{getBoundingBox:l,isVisible:s("underlay"),shapeProps:{shape:"underlay-shape",color:"underlay-color",opacity:"underlay-opacity",padding:"underlay-padding",radius:"underlay-corner-radius"}}),t.drawing.addTextureAtlasRenderType("label",{collection:"label",getTexPickingMode:o,getKey:Ss(e.getLabelKey,null),getBoundingBox:ks(e.getLabelBox,null),drawClipped:!0,drawElement:e.drawLabel,getRotation:n(null),getRotationPoint:e.getLabelRotationPoint,getRotationOffset:e.getLabelRotationOffset,isVisible:i("label")}),t.drawing.addTextureAtlasRenderType("edge-source-label",{collection:"label",getTexPickingMode:o,getKey:Ss(e.getSourceLabelKey,"source"),getBoundingBox:ks(e.getSourceLabelBox,"source"),drawClipped:!0,drawElement:e.drawSourceLabel,getRotation:n("source"),getRotationPoint:e.getSourceLabelRotationPoint,getRotationOffset:e.getSourceLabelRotationOffset,isVisible:i("source-label")}),t.drawing.addTextureAtlasRenderType("edge-target-label",{collection:"label",getTexPickingMode:o,getKey:Ss(e.getTargetLabelKey,"target"),getBoundingBox:ks(e.getTargetLabelBox,"target"),drawClipped:!0,drawElement:e.drawTargetLabel,getRotation:n("target"),getRotationPoint:e.getTargetLabelRotationPoint,getRotationOffset:e.getTargetLabelRotationOffset,isVisible:i("target-label")});var u=Fa(function(){console.log("garbage collect flag set"),t.data.gc=!0},1e4);t.onUpdateEleCalcs(function(v,f){var c=!1;f&&f.length>0&&(c|=t.drawing.invalidate(f)),c&&u()}),dm(t)};function cm(r){var e=r.cy.container(),t=e&&e.style&&e.style.backgroundColor||"white";return iv(t)}function Lf(r,e){var t=r._private.rscratch;return Tr(t,"labelWrapCachedLines",e)||[]}var Ss=function(e,t){return function(a){var n=e(a),i=Lf(a,t);return i.length>1?i.map(function(s,o){return"".concat(n,"_").concat(o)}):n}},ks=function(e,t){return function(a,n){var i=e(a);if(typeof n=="string"){var s=n.indexOf("_");if(s>0){var o=Number(n.substring(s+1)),l=Lf(a,t),u=i.h/l.length,v=u*o,f=i.y1+v;return{x1:i.x1,w:i.w,y1:f,h:u,yOffset:v}}}return i}};function dm(r){{var e=r.render;r.render=function(i){i=i||{};var s=r.cy;r.webgl&&(s.zoom()>Sf?(hm(r),e.call(r,i)):(gm(r),Of(r,i,Ea.SCREEN)))}}{var t=r.matchCanvasSize;r.matchCanvasSize=function(i){t.call(r,i),r.pickingFrameBuffer.setFramebufferAttachmentSizes(r.canvasWidth,r.canvasHeight),r.pickingFrameBuffer.needsDraw=!0}}r.findNearestElements=function(i,s,o,l){return xm(r,i,s)};{var a=r.invalidateCachedZSortedEles;r.invalidateCachedZSortedEles=function(){a.call(r),r.pickingFrameBuffer.needsDraw=!0}}{var n=r.notify;r.notify=function(i,s){n.call(r,i,s),i==="viewport"||i==="bounds"?r.pickingFrameBuffer.needsDraw=!0:i==="background"&&r.drawing.invalidate(s,{type:"node-body"})}}}function hm(r){var e=r.data.contexts[r.WEBGL];e.clear(e.COLOR_BUFFER_BIT|e.DEPTH_BUFFER_BIT)}function gm(r){var e=function(a){a.save(),a.setTransform(1,0,0,1,0,0),a.clearRect(0,0,r.canvasWidth,r.canvasHeight),a.restore()};e(r.data.contexts[r.NODE]),e(r.data.contexts[r.DRAG])}function pm(r){var e=r.canvasWidth,t=r.canvasHeight,a=bo(r),n=a.pan,i=a.zoom,s=Es();yn(s,s,[n.x,n.y]),Ws(s,s,[i,i]);var o=Es();rm(o,e,t);var l=Es();return em(l,o,s),l}function If(r,e){var t=r.canvasWidth,a=r.canvasHeight,n=bo(r),i=n.pan,s=n.zoom;e.setTransform(1,0,0,1,0,0),e.clearRect(0,0,t,a),e.translate(i.x,i.y),e.scale(s,s)}function ym(r,e){r.drawSelectionRectangle(e,function(t){return If(r,t)})}function mm(r){var e=r.data.contexts[r.NODE];e.save(),If(r,e),e.strokeStyle="rgba(0, 0, 0, 0.3)",e.beginPath(),e.moveTo(-1e3,0),e.lineTo(1e3,0),e.stroke(),e.beginPath(),e.moveTo(0,-1e3),e.lineTo(0,1e3),e.stroke(),e.restore()}function bm(r){var e=function(n,i,s){for(var o=n.atlasManager.getAtlasCollection(i),l=r.data.contexts[r.NODE],u=o.atlases,v=0;v=0&&w.add(x)}return w}function xm(r,e,t){var a=wm(r,e,t),n=r.getCachedZSortedEles(),i,s,o=kr(a),l;try{for(o.s();!(l=o.n()).done;){var u=l.value,v=n[u];if(!i&&v.isNode()&&(i=v),!s&&v.isEdge()&&(s=v),i&&s)break}}catch(f){o.e(f)}finally{o.f()}return[i,s].filter(Boolean)}function Ds(r,e,t){var a=r.drawing;e+=1,t.isNode()?(a.drawNode(t,e,"node-underlay"),a.drawNode(t,e,"node-body"),a.drawTexture(t,e,"label"),a.drawNode(t,e,"node-overlay")):(a.drawEdgeLine(t,e),a.drawEdgeArrow(t,e,"source"),a.drawEdgeArrow(t,e,"target"),a.drawTexture(t,e,"label"),a.drawTexture(t,e,"edge-source-label"),a.drawTexture(t,e,"edge-target-label"))}function Of(r,e,t){var a;r.webglDebug&&(a=performance.now());var n=r.drawing,i=0;if(t.screen&&r.data.canvasNeedsRedraw[r.SELECT_BOX]&&ym(r,e),r.data.canvasNeedsRedraw[r.NODE]||t.picking){var s=r.data.contexts[r.WEBGL];t.screen?(s.clearColor(0,0,0,0),s.enable(s.BLEND),s.blendFunc(s.ONE,s.ONE_MINUS_SRC_ALPHA)):s.disable(s.BLEND),s.clear(s.COLOR_BUFFER_BIT|s.DEPTH_BUFFER_BIT),s.viewport(0,0,s.canvas.width,s.canvas.height);var o=pm(r),l=r.getCachedZSortedEles();if(i=l.length,n.startFrame(o,t),t.screen){for(var u=0;u0&&s>0){h.clearRect(0,0,i,s),h.globalCompositeOperation="source-over";var d=this.getCachedZSortedEles();if(r.full)h.translate(-a.x1*u,-a.y1*u),h.scale(u,u),this.drawElements(h,d),h.scale(1/u,1/u),h.translate(a.x1*u,a.y1*u);else{var y=e.pan(),g={x:y.x*u,y:y.y*u};u*=e.zoom(),h.translate(g.x,g.y),h.scale(u,u),this.drawElements(h,d),h.scale(1/u,1/u),h.translate(-g.x,-g.y)}r.bg&&(h.globalCompositeOperation="destination-over",h.fillStyle=r.bg,h.rect(0,0,i,s),h.fill())}return c};function Em(r,e){for(var t=atob(r),a=new ArrayBuffer(t.length),n=new Uint8Array(a),i=0;i"u"?"undefined":ar(OffscreenCanvas))!=="undefined")t=new OffscreenCanvas(r,e);else{var a=this.cy.window(),n=a.document;t=n.createElement("canvas"),t.width=r,t.height=e}return t};[Df,Hr,Jr,mo,Lt,yt,xr,Mf,mt,Wa,Ff].forEach(function(r){be(ke,r)});var Sm=[{name:"null",impl:df},{name:"base",impl:Cf},{name:"canvas",impl:Cm}],km=[{type:"layout",extensions:Qp},{type:"renderer",extensions:Sm}],qf={},_f={};function Gf(r,e,t){var a=t,n=function(T){Ve("Can not register `"+e+"` for `"+r+"` since `"+T+"` already exists in the prototype and can not be overridden")};if(r==="core"){if(Ra.prototype[e])return n(e);Ra.prototype[e]=t}else if(r==="collection"){if(fr.prototype[e])return n(e);fr.prototype[e]=t}else if(r==="layout"){for(var i=function(T){this.options=T,t.call(this,T),Le(this._private)||(this._private={}),this._private.cy=T.cy,this._private.listeners=[],this.createEmitter()},s=i.prototype=Object.create(t.prototype),o=[],l=0;l{b.clear(),J.clear(),f.clear()},"clear"),O=X((e,t)=>{const n=b.get(t)||[];return i.trace("In isDescendant",t," ",e," = ",n.includes(e)),n.includes(e)},"isDescendant"),se=X((e,t)=>{const n=b.get(t)||[];return i.info("Descendants of ",t," is ",n),i.info("Edge is ",e),e.v===t||e.w===t?!1:n?n.includes(e.v)||O(e.v,t)||O(e.w,t)||n.includes(e.w):(i.debug("Tilt, ",t,",not in descendants"),!1)},"edgeInCluster"),G=X((e,t,n,o)=>{i.warn("Copying children of ",e,"root",o,"data",t.node(e),o);const c=t.children(e)||[];e!==o&&c.push(e),i.warn("Copying (nodes) clusterId",e,"nodes",c),c.forEach(a=>{if(t.children(a).length>0)G(a,t,n,o);else{const r=t.node(a);i.info("cp ",a," to ",o," with parent ",e),n.setNode(a,r),o!==t.parent(a)&&(i.warn("Setting parent",a,t.parent(a)),n.setParent(a,t.parent(a))),e!==o&&a!==e?(i.debug("Setting parent",a,e),n.setParent(a,e)):(i.info("In copy ",e,"root",o,"data",t.node(e),o),i.debug("Not Setting parent for node=",a,"cluster!==rootId",e!==o,"node!==clusterId",a!==e));const u=t.edges(a);i.debug("Copying Edges",u),u.forEach(l=>{i.info("Edge",l);const h=t.edge(l.v,l.w,l.name);i.info("Edge data",h,o);try{se(l,o)?(i.info("Copying as ",l.v,l.w,h,l.name),n.setEdge(l.v,l.w,h,l.name),i.info("newGraph edges ",n.edges(),n.edge(n.edges()[0]))):i.info("Skipping copy of edge ",l.v,"-->",l.w," rootId: ",o," clusterId:",e)}catch(C){i.error(C)}})}i.debug("Removing node",a),t.removeNode(a)})},"copy"),R=X((e,t)=>{const n=t.children(e);let o=[...n];for(const c of n)J.set(c,e),o=[...o,...R(c,t)];return o},"extractDescendants"),ie=X((e,t,n)=>{const o=e.edges().filter(l=>l.v===t||l.w===t),c=e.edges().filter(l=>l.v===n||l.w===n),a=o.map(l=>({v:l.v===t?n:l.v,w:l.w===t?t:l.w})),r=c.map(l=>({v:l.v,w:l.w}));return a.filter(l=>r.some(h=>l.v===h.v&&l.w===h.w))},"findCommonEdges"),D=X((e,t,n)=>{const o=t.children(e);if(i.trace("Searching children of id ",e,o),o.length<1)return e;let c;for(const a of o){const r=D(a,t,n),u=ie(t,n,r);if(r)if(u.length>0)c=r;else return r}return c},"findNonClusterChild"),k=X(e=>!f.has(e)||!f.get(e).externalConnections?e:f.has(e)?f.get(e).id:e,"getAnchorId"),re=X((e,t)=>{if(!e||t>10){i.debug("Opting out, no graph ");return}else i.debug("Opting in, graph ");e.nodes().forEach(function(n){e.children(n).length>0&&(i.warn("Cluster identified",n," Replacement id in edges: ",D(n,e,n)),b.set(n,R(n,e)),f.set(n,{id:D(n,e,n),clusterData:e.node(n)}))}),e.nodes().forEach(function(n){const o=e.children(n),c=e.edges();o.length>0?(i.debug("Cluster identified",n,b),c.forEach(a=>{const r=O(a.v,n),u=O(a.w,n);r^u&&(i.warn("Edge: ",a," leaves cluster ",n),i.warn("Descendants of XXX ",n,": ",b.get(n)),f.get(n).externalConnections=!0)})):i.debug("Not a cluster ",n,b)});for(let n of f.keys()){const o=f.get(n).id,c=e.parent(o);c!==n&&f.has(c)&&!f.get(c).externalConnections&&(f.get(n).id=c)}e.edges().forEach(function(n){const o=e.edge(n);i.warn("Edge "+n.v+" -> "+n.w+": "+JSON.stringify(n)),i.warn("Edge "+n.v+" -> "+n.w+": "+JSON.stringify(e.edge(n)));let c=n.v,a=n.w;if(i.warn("Fix XXX",f,"ids:",n.v,n.w,"Translating: ",f.get(n.v)," --- ",f.get(n.w)),f.get(n.v)||f.get(n.w)){if(i.warn("Fixing and trying - removing XXX",n.v,n.w,n.name),c=k(n.v),a=k(n.w),e.removeEdge(n.v,n.w,n.name),c!==n.v){const r=e.parent(c);f.get(r).externalConnections=!0,o.fromCluster=n.v}if(a!==n.w){const r=e.parent(a);f.get(r).externalConnections=!0,o.toCluster=n.w}i.warn("Fix Replacing with XXX",c,a,n.name),e.setEdge(c,a,o,n.name)}}),i.warn("Adjusted Graph",E(e)),T(e,0),i.trace(f)},"adjustClustersAndEdges"),T=X((e,t)=>{var c,a;if(i.warn("extractor - ",t,E(e),e.children("D")),t>10){i.error("Bailing out");return}let n=e.nodes(),o=!1;for(const r of n){const u=e.children(r);o=o||u.length>0}if(!o){i.debug("Done, no node has children",e.nodes());return}i.debug("Nodes = ",n,t);for(const r of n)if(i.debug("Extracting node",r,f,f.has(r)&&!f.get(r).externalConnections,!e.parent(r),e.node(r),e.children("D")," Depth ",t),!f.has(r))i.debug("Not a cluster",r,t);else if(!f.get(r).externalConnections&&e.children(r)&&e.children(r).length>0){i.warn("Cluster without external connections, without a parent and with children",r,t);let l=e.graph().rankdir==="TB"?"LR":"TB";(a=(c=f.get(r))==null?void 0:c.clusterData)!=null&&a.dir&&(l=f.get(r).clusterData.dir,i.warn("Fixing dir",f.get(r).clusterData.dir,l));const h=new B({multigraph:!0,compound:!0}).setGraph({rankdir:l,nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}});i.warn("Old graph before copy",E(e)),G(r,e,h,r),e.setNode(r,{clusterNode:!0,id:r,clusterData:f.get(r).clusterData,label:f.get(r).label,graph:h}),i.warn("New graph after copy node: (",r,")",E(h)),i.debug("Old graph after copy",E(e))}else i.warn("Cluster ** ",r," **not meeting the criteria !externalConnections:",!f.get(r).externalConnections," no parent: ",!e.parent(r)," children ",e.children(r)&&e.children(r).length>0,e.children("D"),t),i.debug(f);n=e.nodes(),i.warn("New list of nodes",n);for(const r of n){const u=e.node(r);i.warn(" Now next level",r,u),u!=null&&u.clusterNode&&T(u.graph,t+1)}},"extractor"),M=X((e,t)=>{if(t.length===0)return[];let n=Object.assign([],t);return t.forEach(o=>{const c=e.children(o),a=M(e,c);n=[...n,...a]}),n},"sorter"),oe=X(e=>M(e,e.children()),"sortNodesByHierarchy"),j=X(async(e,t,n,o,c,a)=>{i.warn("Graph in recursive render:XAX",E(t),c);const r=t.graph().rankdir;i.trace("Dir in recursive render - dir:",r);const u=e.insert("g").attr("class","root");t.nodes()?i.info("Recursive render XXX",t.nodes()):i.info("No nodes found for",t),t.edges().length>0&&i.info("Recursive edges",t.edge(t.edges()[0]));const l=u.insert("g").attr("class","clusters"),h=u.insert("g").attr("class","edgePaths"),C=u.insert("g").attr("class","edgeLabels"),g=u.insert("g").attr("class","nodes");await Promise.all(t.nodes().map(async function(d){const s=t.node(d);if(c!==void 0){const w=JSON.parse(JSON.stringify(c.clusterData));i.trace(`Setting data for parent cluster XXX + Node.id = `,d,` + data=`,w.height,` +Parent cluster`,c.height),t.setNode(c.id,w),t.parent(d)||(i.trace("Setting parent",d,c.id),t.setParent(d,c.id,w))}if(i.info("(Insert) Node XXX"+d+": "+JSON.stringify(t.node(d))),s!=null&&s.clusterNode){i.info("Cluster identified XBX",d,s.width,t.node(d));const{ranksep:w,nodesep:m}=t.graph();s.graph.setGraph({...s.graph.graph(),ranksep:w+25,nodesep:m});const N=await j(g,s.graph,n,o,t.node(d),a),S=N.elem;q(s,S),s.diff=N.diff||0,i.info("New compound node after recursive render XAX",d,"width",s.width,"height",s.height),U(S,s)}else t.children(d).length>0?(i.trace("Cluster - the non recursive path XBX",d,s.id,s,s.width,"Graph:",t),i.trace(D(s.id,t)),f.set(s.id,{id:D(s.id,t),node:s})):(i.trace("Node - the non recursive path XAX",d,g,t.node(d),r),await $(g,t.node(d),{config:a,dir:r}))})),await X(async()=>{const d=t.edges().map(async function(s){const w=t.edge(s.v,s.w,s.name);i.info("Edge "+s.v+" -> "+s.w+": "+JSON.stringify(s)),i.info("Edge "+s.v+" -> "+s.w+": ",s," ",JSON.stringify(t.edge(s))),i.info("Fix",f,"ids:",s.v,s.w,"Translating: ",f.get(s.v),f.get(s.w)),await Z(C,w)});await Promise.all(d)},"processEdges")(),i.info("Graph before layout:",JSON.stringify(E(t))),i.info("############################################# XXX"),i.info("### Layout ### XXX"),i.info("############################################# XXX"),I(t),i.info("Graph after layout:",JSON.stringify(E(t)));let y=0,{subGraphTitleTotalMargin:p}=z(a);return await Promise.all(oe(t).map(async function(d){var w;const s=t.node(d);if(i.info("Position XBX => "+d+": ("+s.x,","+s.y,") width: ",s.width," height: ",s.height),s!=null&&s.clusterNode)s.y+=p,i.info("A tainted cluster node XBX1",d,s.id,s.width,s.height,s.x,s.y,t.parent(d)),f.get(s.id).node=s,P(s);else if(t.children(d).length>0){i.info("A pure cluster node XBX1",d,s.id,s.x,s.y,s.width,s.height,t.parent(d)),s.height+=p,t.node(s.parentId);const m=(s==null?void 0:s.padding)/2||0,N=((w=s==null?void 0:s.labelBBox)==null?void 0:w.height)||0,S=N-m||0;i.debug("OffsetY",S,"labelHeight",N,"halfPadding",m),await K(l,s),f.get(s.id).node=s}else{const m=t.node(s.parentId);s.y+=p/2,i.info("A regular node XBX1 - using the padding",s.id,"parent",s.parentId,s.width,s.height,s.x,s.y,"offsetY",s.offsetY,"parent",m,m==null?void 0:m.offsetY,s),P(s)}})),t.edges().forEach(function(d){const s=t.edge(d);i.info("Edge "+d.v+" -> "+d.w+": "+JSON.stringify(s),s),s.points.forEach(S=>S.y+=p/2);const w=t.node(d.v);var m=t.node(d.w);const N=Q(h,s,f,n,w,m,o);W(s,N)}),t.nodes().forEach(function(d){const s=t.node(d);i.info(d,s.type,s.diff),s.isGroup&&(y=s.diff)}),i.warn("Returning from recursive render XAX",u,y),{elem:u,diff:y}},"recursiveRender"),we=X(async(e,t)=>{var a,r,u,l,h,C;const n=new B({multigraph:!0,compound:!0}).setGraph({rankdir:e.direction,nodesep:((a=e.config)==null?void 0:a.nodeSpacing)||((u=(r=e.config)==null?void 0:r.flowchart)==null?void 0:u.nodeSpacing)||e.nodeSpacing,ranksep:((l=e.config)==null?void 0:l.rankSpacing)||((C=(h=e.config)==null?void 0:h.flowchart)==null?void 0:C.rankSpacing)||e.rankSpacing,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}}),o=t.select("g");F(o,e.markers,e.type,e.diagramId),Y(),_(),H(),te(),e.nodes.forEach(g=>{n.setNode(g.id,{...g}),g.parentId&&n.setParent(g.id,g.parentId)}),i.debug("Edges:",e.edges),e.edges.forEach(g=>{if(g.start===g.end){const v=g.start,y=v+"---"+v+"---1",p=v+"---"+v+"---2",d=n.node(v);n.setNode(y,{domId:y,id:y,parentId:d.parentId,labelStyle:"",label:"",padding:0,shape:"labelRect",style:"",width:10,height:10}),n.setParent(y,d.parentId),n.setNode(p,{domId:p,id:p,parentId:d.parentId,labelStyle:"",padding:0,shape:"labelRect",label:"",style:"",width:10,height:10}),n.setParent(p,d.parentId);const s=structuredClone(g),w=structuredClone(g),m=structuredClone(g);s.label="",s.arrowTypeEnd="none",s.id=v+"-cyclic-special-1",w.arrowTypeStart="none",w.arrowTypeEnd="none",w.id=v+"-cyclic-special-mid",m.label="",d.isGroup&&(s.fromCluster=v,m.toCluster=v),m.id=v+"-cyclic-special-2",m.arrowTypeStart="none",n.setEdge(v,y,s,v+"-cyclic-special-0"),n.setEdge(y,p,w,v+"-cyclic-special-1"),n.setEdge(p,v,m,v+"-cyc{ge(n)&&(s!=null&&s.textStyles?s.textStyles.push(n):s.textStyles=[n]),s!=null&&s.styles?s.styles.push(n):s.styles=[n]}),this.classes.set(a,s)}getClasses(){return this.classes}getStylesForClass(a){var o;return((o=this.classes.get(a))==null?void 0:o.styles)??[]}clear(){Se(),this.nodes=[],this.levels=new Map,this.outerNodes=[],this.classes=new Map,this.root=void 0}},h(F,"TreeMapDB"),F);function J(d){if(!d.length)return[];const a=[],o=[];return d.forEach(s=>{const c={name:s.name,children:s.type==="Leaf"?void 0:[]};for(c.classSelector=s==null?void 0:s.classSelector,s!=null&&s.cssCompiledStyles&&(c.cssCompiledStyles=[s.cssCompiledStyles]),s.type==="Leaf"&&s.value!==void 0&&(c.value=s.value);o.length>0&&o[o.length-1].level>=s.level;)o.pop();if(o.length===0)a.push(c);else{const n=o[o.length-1].node;n.children?n.children.push(c):n.children=[c]}s.type!=="Leaf"&&o.push({node:c,level:s.level})}),a}h(J,"buildHierarchy");var Ce=h((d,a)=>{be(d,a);const o=[];for(const n of d.TreemapRows??[])n.$type==="ClassDefStatement"&&a.addClass(n.className??"",n.styleText??"");for(const n of d.TreemapRows??[]){const p=n.item;if(!p)continue;const f=n.indent?parseInt(n.indent):0,V=we(p),l=p.classSelector?a.getStylesForClass(p.classSelector):[],z=l.length>0?l.join(";"):void 0,b={level:f,name:V,type:p.$type,value:p.value,classSelector:p.classSelector,cssCompiledStyles:z};o.push(b)}const s=J(o),c=h((n,p)=>{for(const f of n)a.addNode(f,p),f.children&&f.children.length>0&&c(f.children,p+1)},"addNodesRecursively");c(s,0)},"populate"),we=h(d=>d.name?String(d.name):"","getItemName"),Q={parser:{yy:void 0},parse:h(async d=>{var a;try{const s=await ve("treemap",d);I.debug("Treemap AST:",s);const c=(a=Q.parser)==null?void 0:a.yy;if(!(c instanceof U))throw new Error("parser.parser?.yy was not a TreemapDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.");Ce(s,c)}catch(o){throw I.error("Error parsing treemap:",o),o}},"parse")},Te=10,$=10,M=25,Le=h((d,a,o,s)=>{const c=s.db,n=c.getConfig(),p=n.padding??Te,f=c.getDiagramTitle(),V=c.getRoot(),{themeVariables:l}=K();if(!V)return;const z=f?30:0,b=re(a),G=n.nodeWidth?n.nodeWidth*$:960,O=n.nodeHeight?n.nodeHeight*$:500,H=G,X=O+z;b.attr("viewBox",`0 0 ${H} ${X}`),oe(b,X,H,n.useMaxWidth);let v;try{const e=n.valueFormat||",";if(e==="$0,0")v=h(t=>"$"+D(",")(t),"valueFormat");else if(e.startsWith("$")&&e.includes(",")){const t=/\.\d+/.exec(e),r=t?t[0]:"";v=h(u=>"$"+D(","+r)(u),"valueFormat")}else if(e.startsWith("$")){const t=e.substring(1);v=h(r=>"$"+D(t||"")(r),"valueFormat")}else v=D(e)}catch(e){I.error("Error creating format function:",e),v=D(",")}const N=B().range(["transparent",l.cScale0,l.cScale1,l.cScale2,l.cScale3,l.cScale4,l.cScale5,l.cScale6,l.cScale7,l.cScale8,l.cScale9,l.cScale10,l.cScale11]),Z=B().range(["transparent",l.cScalePeer0,l.cScalePeer1,l.cScalePeer2,l.cScalePeer3,l.cScalePeer4,l.cScalePeer5,l.cScalePeer6,l.cScalePeer7,l.cScalePeer8,l.cScalePeer9,l.cScalePeer10,l.cScalePeer11]),W=B().range([l.cScaleLabel0,l.cScaleLabel1,l.cScaleLabel2,l.cScaleLabel3,l.cScaleLabel4,l.cScaleLabel5,l.cScaleLabel6,l.cScaleLabel7,l.cScaleLabel8,l.cScaleLabel9,l.cScaleLabel10,l.cScaleLabel11]);f&&b.append("text").attr("x",H/2).attr("y",z/2).attr("class","treemapTitle").attr("text-anchor","middle").attr("dominant-baseline","middle").text(f);const j=b.append("g").attr("transform",`translate(0, ${z})`).attr("class","treemapContainer"),ee=ie(V).sum(e=>e.value??0).sort((e,t)=>(t.value??0)-(e.value??0)),Y=ce().size([G,O]).paddingTop(e=>e.children&&e.children.length>0?M+$:0).paddingInner(p).paddingLeft(e=>e.children&&e.children.length>0?$:0).paddingRight(e=>e.children&&e.children.length>0?$:0).paddingBottom(e=>e.children&&e.children.length>0?$:0).round(!0)(ee),te=Y.descendants().filter(e=>e.children&&e.children.length>0),A=j.selectAll(".treemapSection").data(te).enter().append("g").attr("class","treemapSection").attr("transform",e=>`translate(${e.x0},${e.y0})`);A.append("rect").attr("width",e=>e.x1-e.x0).attr("height",M).attr("class","treemapSectionHeader").attr("fill","none").attr("fill-opacity",.6).attr("stroke-width",.6).attr("style",e=>e.depth===0?"display: none;":""),A.append("clipPath").attr("id",(e,t)=>`clip-section-${a}-${t}`).append("rect").attr("width",e=>Math.max(0,e.x1-e.x0-12)).attr("height",M),A.append("rect").attr("width",e=>e.x1-e.x0).attr("height",e=>e.y1-e.y0).attr("class",(e,t)=>`treemapSection section${t}`).attr("fill",e=>N(e.data.name)).attr("fill-opacity",.6).attr("stroke",e=>Z(e.data.name)).attr("stroke-width",2).attr("stroke-opacity",.4).attr("style",e=>{if(e.depth===0)return"display: none;";const t=L({cssCompiledStyles:e.data.cssCompiledStyles});return t.nodeStyles+";"+t.borderStyles.join(";")}),A.append("text").attr("class","treemapSectionLabel").attr("x",6).attr("y",M/2).attr("dominant-baseline","middle").text(e=>e.depth===0?"":e.data.name).attr("font-weight","bold").attr("style",e=>{if(e.depth===0)return"display: none;";const t="dominant-baseline: middle; font-size: 12px; fill:"+W(e.data.name)+"; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;",r=L({cssCompiledStyles:e.data.cssCompiledStyles});return t+r.labelStyles.replace("color:","fill:")}).each(function(e){if(e.depth===0)return;const t=_(this),r=e.data.name;t.text(r);const u=e.x1-e.x0,g=6;let S;n.showValues!==!1&&e.value?S=u-10-30-10-g:S=u-g-6;const x=Math.max(15,S),i=t.node();if(i.getComputedTextLength()>x){const m="...";let y=r;for(;y.length>0;){if(y=r.substring(0,y.length-1),y.length===0){t.text(m),i.getComputedTextLength()>x&&t.text("");break}if(t.text(y+m),i.getComputedTextLength()<=x)break}}}),n.showValues!==!1&&A.append("text").attr("class","treemapSectionValue").attr("x",e=>e.x1-e.x0-10).attr("y",M/2).attr("text-anchor","end").attr("dominant-baseline","middle").text(e=>e.value?v(e.value):"").attr("font-style","italic").attr("style",e=>{if(e.depth===0)return"display: none;";const t="text-anchor: end; dominant-baseline: middle; font-size: 10px; fill:"+W(e.data.name)+"; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;",r=L({cssCompiledStyles:e.data.cssCompiledStyles});return t+r.labelStyles.replace("color:","fill:")});const ae=Y.leaves(),E=j.selectAll(".treemapLeafGroup").data(ae).enter().append("g").attr("class",(e,t)=>`treemapNode treemapLeafGroup leaf${t}${e.data.classSelector?` ${e.data.classSelector}`:""}x`).attr("transform",e=>`translate(${e.x0},${e.y0})`);E.append("rect").attr("width",e=>e.x1-e.x0).attr("height",e=>e.y1-e.y0).attr("class","treemapLeaf").attr("fill",e=>e.parent?N(e.parent.data.name):N(e.data.name)).attr("style",e=>L({cssCompiledStyles:e.data.cssCompiledStyles}).nodeStyles).attr("fill-opacity",.3).attr("stroke",e=>e.parent?N(e.parent.data.name):N(e.data.name)).attr("stroke-width",3),E.append("clipPath").attr("id",(e,t)=>`clip-${a}-${t}`).append("rect").attr("width",e=>Math.max(0,e.x1-e.x0-4)).attr("height",e=>Math.max(0,e.y1-e.y0-4)),E.append("text").attr("class","treemapLabel").attr("x",e=>(e.x1-e.x0)/2).attr("y",e=>(e.y1-e.y0)/2).attr("style",e=>{const t="text-anchor: middle; dominant-baseline: middle; font-size: 38px;fill:"+W(e.data.name)+";",r=L({cssCompiledStyles:e.data.cssCompiledStyles});return t+r.labelStyles.replace("color:","fill:")}).attr("clip-path",(e,t)=>`url(#clip-${a}-${t})`).text(e=>e.data.name).each(function(e){const t=_(this),r=e.x1-e.x0,u=e.y1-e.y0,g=t.node(),S=4,T=r-2*S,x=u-2*S;if(T<10||x<10){t.style("display","none");return}let i=parseInt(t.style("font-size"),10);const C=8,m=28,y=.6,w=6,k=2;for(;g.getComputedTextLength()>T&&i>C;)i--,t.style("font-size",`${i}px`);let P=Math.max(w,Math.min(m,Math.round(i*y))),R=i+k+P;for(;R>x&&i>C&&(i--,P=Math.max(w,Math.min(m,Math.round(i*y))),!(PT||i(t.x1-t.x0)/2).attr("y",function(t){return(t.y1-t.y0)/2}).attr("style",t=>{const r="text-anchor: middle; dominant-baseline: hanging; font-size: 28px;fill:"+W(t.data.name)+";",u=L({cssCompiledStyles:t.data.cssCompiledStyles});return r+u.labelStyles.replace("color:","fill:")}).attr("clip-path",(t,r)=>`url(#clip-${a}-${r})`).text(t=>t.value?v(t.value):"").each(function(t){const r=_(this),u=this.parentNode;if(!u){r.style("display","none");return}const g=_(u).select(".treemapLabel");if(g.empty()||g.style("display")==="none"){r.style("display","none");return}const S=parseFloat(g.style("font-size")),T=28,x=.6,i=6,C=2,m=Math.max(i,Math.min(T,Math.round(S*x)));r.style("font-size",`${m}px`);const w=(t.y1-t.y0)/2+S/2+C;r.attr("y",w);const k=t.x1-t.x0,se=t.y1-t.y0-4,ne=k-2*4;r.node().getComputedTextLength()>ne||w+m>se||m{const a=q(ze,d);return` + .treemapNode.section { + stroke: ${a.sectionStrokeColor}; + stroke-width: ${a.sectionStrokeWidth}; + fill: ${a.sectionFillColor}; + } + .treemapNode.leaf { + stroke: ${a.leafStrokeColor}; + stroke-width: ${a.leafStrokeWidth}; + fill: ${a.leafFillColor}; + } + .treemapLabel { + fill: ${a.labelColor}; + font-size: ${a.labelFontSize}; + } + .treemapValue { + fill: ${a.valueColor}; + font-size: ${a.valueFontSize}; + } + .treemapTitle { + fill: ${a.titleColor}; + font-size: ${a.titleFontSize}; + } + `},"getStyles"),Ae=Ne,Re={parser:Q,get db(){return new U},renderer:Fe,styles:Ae};export{Re as diagram}; diff --git a/assets/chunks/diagram-QEK2KX5R.CRLQ07ic.js b/assets/chunks/diagram-QEK2KX5R.CRLQ07ic.js new file mode 100644 index 000000000..babdfe137 --- /dev/null +++ b/assets/chunks/diagram-QEK2KX5R.CRLQ07ic.js @@ -0,0 +1,43 @@ +import{_ as l,s as k,g as R,t as F,q as I,a as _,b as E,K as D,z as G,F as y,G as C,H as z,l as P,Q as H}from"./theme.kqgpP4eL.js";import{p as V}from"./chunk-4BX2VUAB.B6a8mhSC.js";import{p as W}from"./treemap-KMMF4GRG.CcUr4GSN.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var h={showLegend:!0,ticks:5,max:null,min:0,graticule:"circle"},w={axes:[],curves:[],options:h},m=structuredClone(w),B=z.radar,j=l(()=>y({...B,...C().radar}),"getConfig"),b=l(()=>m.axes,"getAxes"),q=l(()=>m.curves,"getCurves"),K=l(()=>m.options,"getOptions"),N=l(a=>{m.axes=a.map(t=>({name:t.name,label:t.label??t.name}))},"setAxes"),Q=l(a=>{m.curves=a.map(t=>({name:t.name,label:t.label??t.name,entries:U(t.entries)}))},"setCurves"),U=l(a=>{if(a[0].axis==null)return a.map(e=>e.value);const t=b();if(t.length===0)throw new Error("Axes must be populated before curves for reference entries");return t.map(e=>{const r=a.find(s=>{var o;return((o=s.axis)==null?void 0:o.$refText)===e.name});if(r===void 0)throw new Error("Missing entry for axis "+e.label);return r.value})},"computeCurveEntries"),X=l(a=>{var e,r,s,o,i;const t=a.reduce((n,c)=>(n[c.name]=c,n),{});m.options={showLegend:((e=t.showLegend)==null?void 0:e.value)??h.showLegend,ticks:((r=t.ticks)==null?void 0:r.value)??h.ticks,max:((s=t.max)==null?void 0:s.value)??h.max,min:((o=t.min)==null?void 0:o.value)??h.min,graticule:((i=t.graticule)==null?void 0:i.value)??h.graticule}},"setOptions"),Y=l(()=>{G(),m=structuredClone(w)},"clear"),$={getAxes:b,getCurves:q,getOptions:K,setAxes:N,setCurves:Q,setOptions:X,getConfig:j,clear:Y,setAccTitle:E,getAccTitle:_,setDiagramTitle:I,getDiagramTitle:F,getAccDescription:R,setAccDescription:k},Z=l(a=>{V(a,$);const{axes:t,curves:e,options:r}=a;$.setAxes(t),$.setCurves(e),$.setOptions(r)},"populate"),J={parse:l(async a=>{const t=await W("radar",a);P.debug(t),Z(t)},"parse")},tt=l((a,t,e,r)=>{const s=r.db,o=s.getAxes(),i=s.getCurves(),n=s.getOptions(),c=s.getConfig(),d=s.getDiagramTitle(),u=D(t),p=et(u,c),g=n.max??Math.max(...i.map(f=>Math.max(...f.entries))),x=n.min,v=Math.min(c.width,c.height)/2;at(p,o,v,n.ticks,n.graticule),rt(p,o,v,c),M(p,o,i,x,g,n.graticule,c),T(p,i,n.showLegend,c),p.append("text").attr("class","radarTitle").text(d).attr("x",0).attr("y",-c.height/2-c.marginTop)},"draw"),et=l((a,t)=>{const e=t.width+t.marginLeft+t.marginRight,r=t.height+t.marginTop+t.marginBottom,s={x:t.marginLeft+t.width/2,y:t.marginTop+t.height/2};return a.attr("viewbox",`0 0 ${e} ${r}`).attr("width",e).attr("height",r),a.append("g").attr("transform",`translate(${s.x}, ${s.y})`)},"drawFrame"),at=l((a,t,e,r,s)=>{if(s==="circle")for(let o=0;o{const p=2*u*Math.PI/o-Math.PI/2,g=n*Math.cos(p),x=n*Math.sin(p);return`${g},${x}`}).join(" ");a.append("polygon").attr("points",c).attr("class","radarGraticule")}}},"drawGraticule"),rt=l((a,t,e,r)=>{const s=t.length;for(let o=0;o{if(d.entries.length!==n)return;const p=d.entries.map((g,x)=>{const v=2*Math.PI*x/n-Math.PI/2,f=A(g,r,s,c),O=f*Math.cos(v),S=f*Math.sin(v);return{x:O,y:S}});o==="circle"?a.append("path").attr("d",L(p,i.curveTension)).attr("class",`radarCurve-${u}`):o==="polygon"&&a.append("polygon").attr("points",p.map(g=>`${g.x},${g.y}`).join(" ")).attr("class",`radarCurve-${u}`)})}l(M,"drawCurves");function A(a,t,e,r){const s=Math.min(Math.max(a,t),e);return r*(s-t)/(e-t)}l(A,"relativeRadius");function L(a,t){const e=a.length;let r=`M${a[0].x},${a[0].y}`;for(let s=0;s{const d=a.append("g").attr("transform",`translate(${s}, ${o+c*i})`);d.append("rect").attr("width",12).attr("height",12).attr("class",`radarLegendBox-${c}`),d.append("text").attr("x",16).attr("y",0).attr("class","radarLegendText").text(n.label)})}l(T,"drawLegend");var st={draw:tt},nt=l((a,t)=>{let e="";for(let r=0;r{const t=H(),e=C(),r=y(t,e.themeVariables),s=y(r.radar,a);return{themeVariables:r,radarOptions:s}},"buildRadarStyleOptions"),it=l(({radar:a}={})=>{const{themeVariables:t,radarOptions:e}=ot(a);return` + .radarTitle { + font-size: ${t.fontSize}; + color: ${t.titleColor}; + dominant-baseline: hanging; + text-anchor: middle; + } + .radarAxisLine { + stroke: ${e.axisColor}; + stroke-width: ${e.axisStrokeWidth}; + } + .radarAxisLabel { + dominant-baseline: middle; + text-anchor: middle; + font-size: ${e.axisLabelFontSize}px; + color: ${e.axisColor}; + } + .radarGraticule { + fill: ${e.graticuleColor}; + fill-opacity: ${e.graticuleOpacity}; + stroke: ${e.graticuleColor}; + stroke-width: ${e.graticuleStrokeWidth}; + } + .radarLegendText { + text-anchor: start; + font-size: ${e.legendFontSize}px; + dominant-baseline: hanging; + } + ${nt(t,e)} + `},"styles"),mt={parser:J,db:$,renderer:st,styles:it};export{mt as diagram}; diff --git a/assets/chunks/diagram-S2PKOQOG.BvMBeI2b.js b/assets/chunks/diagram-S2PKOQOG.BvMBeI2b.js new file mode 100644 index 000000000..9f89f4fb4 --- /dev/null +++ b/assets/chunks/diagram-S2PKOQOG.BvMBeI2b.js @@ -0,0 +1,24 @@ +import{_ as b,F as m,K as B,e as C,l as w,b as S,a as D,q as T,t as z,g as F,s as P,G as E,H as A,z as W}from"./theme.kqgpP4eL.js";import{p as _}from"./chunk-4BX2VUAB.B6a8mhSC.js";import{p as N}from"./treemap-KMMF4GRG.CcUr4GSN.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var L=A.packet,u,v=(u=class{constructor(){this.packet=[],this.setAccTitle=S,this.getAccTitle=D,this.setDiagramTitle=T,this.getDiagramTitle=z,this.getAccDescription=F,this.setAccDescription=P}getConfig(){const t=m({...L,...E().packet});return t.showBits&&(t.paddingY+=10),t}getPacket(){return this.packet}pushWord(t){t.length>0&&this.packet.push(t)}clear(){W(),this.packet=[]}},b(u,"PacketDB"),u),M=1e4,Y=b((e,t)=>{_(e,t);let o=-1,r=[],n=1;const{bitsPerRow:l}=t.getConfig();for(let{start:a,end:i,bits:d,label:c}of e.blocks){if(a!==void 0&&i!==void 0&&i{if(e.start===void 0)throw new Error("start should have been set during first phase");if(e.end===void 0)throw new Error("end should have been set during first phase");if(e.start>e.end)throw new Error(`Block start ${e.start} is greater than block end ${e.end}.`);if(e.end+1<=t*o)return[e,void 0];const r=t*o-1,n=t*o;return[{start:e.start,end:r,label:e.label,bits:r-e.start},{start:n,end:e.end,label:e.label,bits:e.end-n}]},"getNextFittingBlock"),x={parser:{yy:void 0},parse:b(async e=>{var r;const t=await N("packet",e),o=(r=x.parser)==null?void 0:r.yy;if(!(o instanceof v))throw new Error("parser.parser?.yy was not a PacketDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.");w.debug(t),Y(t,o)},"parse")},H=b((e,t,o,r)=>{const n=r.db,l=n.getConfig(),{rowHeight:a,paddingY:i,bitWidth:d,bitsPerRow:c}=l,p=n.getPacket(),s=n.getDiagramTitle(),h=a+i,g=h*(p.length+1)-(s?0:a),k=d*c+2,f=B(t);f.attr("viewbox",`0 0 ${k} ${g}`),C(f,g,k,l.useMaxWidth);for(const[y,$]of p.entries())I(f,$,y,l);f.append("text").text(s).attr("x",k/2).attr("y",g-h/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),I=b((e,t,o,{rowHeight:r,paddingX:n,paddingY:l,bitWidth:a,bitsPerRow:i,showBits:d})=>{const c=e.append("g"),p=o*(r+l)+l;for(const s of t){const h=s.start%i*a+1,g=(s.end-s.start+1)*a-n;if(c.append("rect").attr("x",h).attr("y",p).attr("width",g).attr("height",r).attr("class","packetBlock"),c.append("text").attr("x",h+g/2).attr("y",p+r/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(s.label),!d)continue;const k=s.end===s.start,f=p-2;c.append("text").attr("x",h+(k?g/2:0)).attr("y",f).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",k?"middle":"start").text(s.start),k||c.append("text").attr("x",h+g).attr("y",f).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(s.end)}},"drawWord"),K={draw:H},O={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},j=b(({packet:e}={})=>{const t=m(O,e);return` + .packetByte { + font-size: ${t.byteFontSize}; + } + .packetByte.start { + fill: ${t.startByteColor}; + } + .packetByte.end { + fill: ${t.endByteColor}; + } + .packetLabel { + fill: ${t.labelColor}; + font-size: ${t.labelFontSize}; + } + .packetTitle { + fill: ${t.titleColor}; + font-size: ${t.titleFontSize}; + } + .packetBlock { + stroke: ${t.blockStrokeColor}; + stroke-width: ${t.blockStrokeWidth}; + fill: ${t.blockFillColor}; + } + `},"styles"),V={parser:x,get db(){return new v},renderer:K,styles:j};export{V as diagram}; diff --git a/assets/chunks/erDiagram-Q2GNP2WA.B8pTQkdS.js b/assets/chunks/erDiagram-Q2GNP2WA.B8pTQkdS.js new file mode 100644 index 000000000..dfed68df0 --- /dev/null +++ b/assets/chunks/erDiagram-Q2GNP2WA.B8pTQkdS.js @@ -0,0 +1,60 @@ +import{g as Dt}from"./chunk-55IACEB6.BKKqJU_2.js";import{s as wt}from"./chunk-QN33PNHL.ChYgkhtD.js";import{_ as u,b as Vt,a as Lt,s as Mt,g as Bt,q as Ft,t as Yt,c as tt,l as D,z as Pt,y as zt,B as Gt,C as Kt,D as Zt,p as Ut,r as jt,d as Wt,u as Qt}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var dt=function(){var s=u(function(R,n,a,c){for(a=a||{},c=R.length;c--;a[R[c]]=n);return a},"o"),i=[6,8,10,22,24,26,28,33,34,35,36,37,40,43,44,50],h=[1,10],d=[1,11],o=[1,12],l=[1,13],f=[1,20],_=[1,21],E=[1,22],V=[1,23],Z=[1,24],S=[1,19],et=[1,25],U=[1,26],T=[1,18],L=[1,33],st=[1,34],it=[1,35],rt=[1,36],nt=[1,37],pt=[6,8,10,13,15,17,20,21,22,24,26,28,33,34,35,36,37,40,43,44,50,63,64,65,66,67],O=[1,42],A=[1,43],M=[1,52],B=[40,50,68,69],F=[1,63],Y=[1,61],N=[1,58],P=[1,62],z=[1,64],j=[6,8,10,13,17,22,24,26,28,33,34,35,36,37,40,41,42,43,44,48,49,50,63,64,65,66,67],yt=[63,64,65,66,67],ft=[1,81],_t=[1,80],gt=[1,78],bt=[1,79],mt=[6,10,42,47],v=[6,10,13,41,42,47,48,49],W=[1,89],Q=[1,88],X=[1,87],G=[19,56],Et=[1,98],kt=[1,97],at=[19,56,58,60],ct={trace:u(function(){},"trace"),yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,entityName:11,relSpec:12,COLON:13,role:14,STYLE_SEPARATOR:15,idList:16,BLOCK_START:17,attributes:18,BLOCK_STOP:19,SQS:20,SQE:21,title:22,title_value:23,acc_title:24,acc_title_value:25,acc_descr:26,acc_descr_value:27,acc_descr_multiline_value:28,direction:29,classDefStatement:30,classStatement:31,styleStatement:32,direction_tb:33,direction_bt:34,direction_rl:35,direction_lr:36,CLASSDEF:37,stylesOpt:38,separator:39,UNICODE_TEXT:40,STYLE_TEXT:41,COMMA:42,CLASS:43,STYLE:44,style:45,styleComponent:46,SEMI:47,NUM:48,BRKT:49,ENTITY_NAME:50,attribute:51,attributeType:52,attributeName:53,attributeKeyTypeList:54,attributeComment:55,ATTRIBUTE_WORD:56,attributeKeyType:57,",":58,ATTRIBUTE_KEY:59,COMMENT:60,cardinality:61,relType:62,ZERO_OR_ONE:63,ZERO_OR_MORE:64,ONE_OR_MORE:65,ONLY_ONE:66,MD_PARENT:67,NON_IDENTIFYING:68,IDENTIFYING:69,WORD:70,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",8:"SPACE",10:"NEWLINE",13:"COLON",15:"STYLE_SEPARATOR",17:"BLOCK_START",19:"BLOCK_STOP",20:"SQS",21:"SQE",22:"title",23:"title_value",24:"acc_title",25:"acc_title_value",26:"acc_descr",27:"acc_descr_value",28:"acc_descr_multiline_value",33:"direction_tb",34:"direction_bt",35:"direction_rl",36:"direction_lr",37:"CLASSDEF",40:"UNICODE_TEXT",41:"STYLE_TEXT",42:"COMMA",43:"CLASS",44:"STYLE",47:"SEMI",48:"NUM",49:"BRKT",50:"ENTITY_NAME",56:"ATTRIBUTE_WORD",58:",",59:"ATTRIBUTE_KEY",60:"COMMENT",63:"ZERO_OR_ONE",64:"ZERO_OR_MORE",65:"ONE_OR_MORE",66:"ONLY_ONE",67:"MD_PARENT",68:"NON_IDENTIFYING",69:"IDENTIFYING",70:"WORD"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,5],[9,9],[9,7],[9,7],[9,4],[9,6],[9,3],[9,5],[9,1],[9,3],[9,7],[9,9],[9,6],[9,8],[9,4],[9,6],[9,2],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[9,1],[29,1],[29,1],[29,1],[29,1],[30,4],[16,1],[16,1],[16,3],[16,3],[31,3],[32,4],[38,1],[38,3],[45,1],[45,2],[39,1],[39,1],[39,1],[46,1],[46,1],[46,1],[46,1],[11,1],[11,1],[18,1],[18,2],[51,2],[51,3],[51,3],[51,4],[52,1],[53,1],[54,1],[54,3],[57,1],[55,1],[12,3],[61,1],[61,1],[61,1],[61,1],[61,1],[62,1],[62,1],[14,1],[14,1],[14,1]],performAction:u(function(n,a,c,r,p,t,K){var e=t.length-1;switch(p){case 1:break;case 2:this.$=[];break;case 3:t[e-1].push(t[e]),this.$=t[e-1];break;case 4:case 5:this.$=t[e];break;case 6:case 7:this.$=[];break;case 8:r.addEntity(t[e-4]),r.addEntity(t[e-2]),r.addRelationship(t[e-4],t[e],t[e-2],t[e-3]);break;case 9:r.addEntity(t[e-8]),r.addEntity(t[e-4]),r.addRelationship(t[e-8],t[e],t[e-4],t[e-5]),r.setClass([t[e-8]],t[e-6]),r.setClass([t[e-4]],t[e-2]);break;case 10:r.addEntity(t[e-6]),r.addEntity(t[e-2]),r.addRelationship(t[e-6],t[e],t[e-2],t[e-3]),r.setClass([t[e-6]],t[e-4]);break;case 11:r.addEntity(t[e-6]),r.addEntity(t[e-4]),r.addRelationship(t[e-6],t[e],t[e-4],t[e-5]),r.setClass([t[e-4]],t[e-2]);break;case 12:r.addEntity(t[e-3]),r.addAttributes(t[e-3],t[e-1]);break;case 13:r.addEntity(t[e-5]),r.addAttributes(t[e-5],t[e-1]),r.setClass([t[e-5]],t[e-3]);break;case 14:r.addEntity(t[e-2]);break;case 15:r.addEntity(t[e-4]),r.setClass([t[e-4]],t[e-2]);break;case 16:r.addEntity(t[e]);break;case 17:r.addEntity(t[e-2]),r.setClass([t[e-2]],t[e]);break;case 18:r.addEntity(t[e-6],t[e-4]),r.addAttributes(t[e-6],t[e-1]);break;case 19:r.addEntity(t[e-8],t[e-6]),r.addAttributes(t[e-8],t[e-1]),r.setClass([t[e-8]],t[e-3]);break;case 20:r.addEntity(t[e-5],t[e-3]);break;case 21:r.addEntity(t[e-7],t[e-5]),r.setClass([t[e-7]],t[e-2]);break;case 22:r.addEntity(t[e-3],t[e-1]);break;case 23:r.addEntity(t[e-5],t[e-3]),r.setClass([t[e-5]],t[e]);break;case 24:case 25:this.$=t[e].trim(),r.setAccTitle(this.$);break;case 26:case 27:this.$=t[e].trim(),r.setAccDescription(this.$);break;case 32:r.setDirection("TB");break;case 33:r.setDirection("BT");break;case 34:r.setDirection("RL");break;case 35:r.setDirection("LR");break;case 36:this.$=t[e-3],r.addClass(t[e-2],t[e-1]);break;case 37:case 38:case 56:case 64:this.$=[t[e]];break;case 39:case 40:this.$=t[e-2].concat([t[e]]);break;case 41:this.$=t[e-2],r.setClass(t[e-1],t[e]);break;case 42:this.$=t[e-3],r.addCssStyles(t[e-2],t[e-1]);break;case 43:this.$=[t[e]];break;case 44:t[e-2].push(t[e]),this.$=t[e-2];break;case 46:this.$=t[e-1]+t[e];break;case 54:case 76:case 77:this.$=t[e].replace(/"/g,"");break;case 55:case 78:this.$=t[e];break;case 57:t[e].push(t[e-1]),this.$=t[e];break;case 58:this.$={type:t[e-1],name:t[e]};break;case 59:this.$={type:t[e-2],name:t[e-1],keys:t[e]};break;case 60:this.$={type:t[e-2],name:t[e-1],comment:t[e]};break;case 61:this.$={type:t[e-3],name:t[e-2],keys:t[e-1],comment:t[e]};break;case 62:case 63:case 66:this.$=t[e];break;case 65:t[e-2].push(t[e]),this.$=t[e-2];break;case 67:this.$=t[e].replace(/"/g,"");break;case 68:this.$={cardA:t[e],relType:t[e-1],cardB:t[e-2]};break;case 69:this.$=r.Cardinality.ZERO_OR_ONE;break;case 70:this.$=r.Cardinality.ZERO_OR_MORE;break;case 71:this.$=r.Cardinality.ONE_OR_MORE;break;case 72:this.$=r.Cardinality.ONLY_ONE;break;case 73:this.$=r.Cardinality.MD_PARENT;break;case 74:this.$=r.Identification.NON_IDENTIFYING;break;case 75:this.$=r.Identification.IDENTIFYING;break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},s(i,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:9,22:h,24:d,26:o,28:l,29:14,30:15,31:16,32:17,33:f,34:_,35:E,36:V,37:Z,40:S,43:et,44:U,50:T},s(i,[2,7],{1:[2,1]}),s(i,[2,3]),{9:27,11:9,22:h,24:d,26:o,28:l,29:14,30:15,31:16,32:17,33:f,34:_,35:E,36:V,37:Z,40:S,43:et,44:U,50:T},s(i,[2,5]),s(i,[2,6]),s(i,[2,16],{12:28,61:32,15:[1,29],17:[1,30],20:[1,31],63:L,64:st,65:it,66:rt,67:nt}),{23:[1,38]},{25:[1,39]},{27:[1,40]},s(i,[2,27]),s(i,[2,28]),s(i,[2,29]),s(i,[2,30]),s(i,[2,31]),s(pt,[2,54]),s(pt,[2,55]),s(i,[2,32]),s(i,[2,33]),s(i,[2,34]),s(i,[2,35]),{16:41,40:O,41:A},{16:44,40:O,41:A},{16:45,40:O,41:A},s(i,[2,4]),{11:46,40:S,50:T},{16:47,40:O,41:A},{18:48,19:[1,49],51:50,52:51,56:M},{11:53,40:S,50:T},{62:54,68:[1,55],69:[1,56]},s(B,[2,69]),s(B,[2,70]),s(B,[2,71]),s(B,[2,72]),s(B,[2,73]),s(i,[2,24]),s(i,[2,25]),s(i,[2,26]),{13:F,38:57,41:Y,42:N,45:59,46:60,48:P,49:z},s(j,[2,37]),s(j,[2,38]),{16:65,40:O,41:A,42:N},{13:F,38:66,41:Y,42:N,45:59,46:60,48:P,49:z},{13:[1,67],15:[1,68]},s(i,[2,17],{61:32,12:69,17:[1,70],42:N,63:L,64:st,65:it,66:rt,67:nt}),{19:[1,71]},s(i,[2,14]),{18:72,19:[2,56],51:50,52:51,56:M},{53:73,56:[1,74]},{56:[2,62]},{21:[1,75]},{61:76,63:L,64:st,65:it,66:rt,67:nt},s(yt,[2,74]),s(yt,[2,75]),{6:ft,10:_t,39:77,42:gt,47:bt},{40:[1,82],41:[1,83]},s(mt,[2,43],{46:84,13:F,41:Y,48:P,49:z}),s(v,[2,45]),s(v,[2,50]),s(v,[2,51]),s(v,[2,52]),s(v,[2,53]),s(i,[2,41],{42:N}),{6:ft,10:_t,39:85,42:gt,47:bt},{14:86,40:W,50:Q,70:X},{16:90,40:O,41:A},{11:91,40:S,50:T},{18:92,19:[1,93],51:50,52:51,56:M},s(i,[2,12]),{19:[2,57]},s(G,[2,58],{54:94,55:95,57:96,59:Et,60:kt}),s([19,56,59,60],[2,63]),s(i,[2,22],{15:[1,100],17:[1,99]}),s([40,50],[2,68]),s(i,[2,36]),{13:F,41:Y,45:101,46:60,48:P,49:z},s(i,[2,47]),s(i,[2,48]),s(i,[2,49]),s(j,[2,39]),s(j,[2,40]),s(v,[2,46]),s(i,[2,42]),s(i,[2,8]),s(i,[2,76]),s(i,[2,77]),s(i,[2,78]),{13:[1,102],42:N},{13:[1,104],15:[1,103]},{19:[1,105]},s(i,[2,15]),s(G,[2,59],{55:106,58:[1,107],60:kt}),s(G,[2,60]),s(at,[2,64]),s(G,[2,67]),s(at,[2,66]),{18:108,19:[1,109],51:50,52:51,56:M},{16:110,40:O,41:A},s(mt,[2,44],{46:84,13:F,41:Y,48:P,49:z}),{14:111,40:W,50:Q,70:X},{16:112,40:O,41:A},{14:113,40:W,50:Q,70:X},s(i,[2,13]),s(G,[2,61]),{57:114,59:Et},{19:[1,115]},s(i,[2,20]),s(i,[2,23],{17:[1,116],42:N}),s(i,[2,11]),{13:[1,117],42:N},s(i,[2,10]),s(at,[2,65]),s(i,[2,18]),{18:118,19:[1,119],51:50,52:51,56:M},{14:120,40:W,50:Q,70:X},{19:[1,121]},s(i,[2,21]),s(i,[2,9]),s(i,[2,19])],defaultActions:{52:[2,62],72:[2,57]},parseError:u(function(n,a){if(a.recoverable)this.trace(n);else{var c=new Error(n);throw c.hash=a,c}},"parseError"),parse:u(function(n){var a=this,c=[0],r=[],p=[null],t=[],K=this.table,e="",H=0,St=0,It=2,Tt=1,xt=t.slice.call(arguments,1),y=Object.create(this.lexer),I={yy:{}};for(var lt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,lt)&&(I.yy[lt]=this.yy[lt]);y.setInput(n,I.yy),I.yy.lexer=y,I.yy.parser=this,typeof y.yylloc>"u"&&(y.yylloc={});var ot=y.yylloc;t.push(ot);var vt=y.options&&y.options.ranges;typeof I.yy.parseError=="function"?this.parseError=I.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Ct(b){c.length=c.length-2*b,p.length=p.length-b,t.length=t.length-b}u(Ct,"popStack");function Ot(){var b;return b=r.pop()||y.lex()||Tt,typeof b!="number"&&(b instanceof Array&&(r=b,b=r.pop()),b=a.symbols_[b]||b),b}u(Ot,"lex");for(var g,x,m,ht,C={},J,k,At,$;;){if(x=c[c.length-1],this.defaultActions[x]?m=this.defaultActions[x]:((g===null||typeof g>"u")&&(g=Ot()),m=K[x]&&K[x][g]),typeof m>"u"||!m.length||!m[0]){var ut="";$=[];for(J in K[x])this.terminals_[J]&&J>It&&$.push("'"+this.terminals_[J]+"'");y.showPosition?ut="Parse error on line "+(H+1)+`: +`+y.showPosition()+` +Expecting `+$.join(", ")+", got '"+(this.terminals_[g]||g)+"'":ut="Parse error on line "+(H+1)+": Unexpected "+(g==Tt?"end of input":"'"+(this.terminals_[g]||g)+"'"),this.parseError(ut,{text:y.match,token:this.terminals_[g]||g,line:y.yylineno,loc:ot,expected:$})}if(m[0]instanceof Array&&m.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+g);switch(m[0]){case 1:c.push(g),p.push(y.yytext),t.push(y.yylloc),c.push(m[1]),g=null,St=y.yyleng,e=y.yytext,H=y.yylineno,ot=y.yylloc;break;case 2:if(k=this.productions_[m[1]][1],C.$=p[p.length-k],C._$={first_line:t[t.length-(k||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(k||1)].first_column,last_column:t[t.length-1].last_column},vt&&(C._$.range=[t[t.length-(k||1)].range[0],t[t.length-1].range[1]]),ht=this.performAction.apply(C,[e,St,H,I.yy,m[1],p,t].concat(xt)),typeof ht<"u")return ht;k&&(c=c.slice(0,-1*k*2),p=p.slice(0,-1*k),t=t.slice(0,-1*k)),c.push(this.productions_[m[1]][0]),p.push(C.$),t.push(C._$),At=K[c[c.length-2]][c[c.length-1]],c.push(At);break;case 3:return!0}}return!0},"parse")},Rt=function(){var R={EOF:1,parseError:u(function(a,c){if(this.yy.parser)this.yy.parser.parseError(a,c);else throw new Error(a)},"parseError"),setInput:u(function(n,a){return this.yy=a||this.yy||{},this._input=n,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:u(function(){var n=this._input[0];this.yytext+=n,this.yyleng++,this.offset++,this.match+=n,this.matched+=n;var a=n.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),n},"input"),unput:u(function(n){var a=n.length,c=n.split(/(?:\r\n?|\n)/g);this._input=n+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),c.length-1&&(this.yylineno-=c.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:c?(c.length===r.length?this.yylloc.first_column:0)+r[r.length-c.length].length-c[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:u(function(){return this._more=!0,this},"more"),reject:u(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:u(function(n){this.unput(this.match.slice(n))},"less"),pastInput:u(function(){var n=this.matched.substr(0,this.matched.length-this.match.length);return(n.length>20?"...":"")+n.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:u(function(){var n=this.match;return n.length<20&&(n+=this._input.substr(0,20-n.length)),(n.substr(0,20)+(n.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:u(function(){var n=this.pastInput(),a=new Array(n.length+1).join("-");return n+this.upcomingInput()+` +`+a+"^"},"showPosition"),test_match:u(function(n,a){var c,r,p;if(this.options.backtrack_lexer&&(p={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(p.yylloc.range=this.yylloc.range.slice(0))),r=n[0].match(/(?:\r\n?|\n).*/g),r&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+n[0].length},this.yytext+=n[0],this.match+=n[0],this.matches=n,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(n[0].length),this.matched+=n[0],c=this.performAction.call(this,this.yy,this,a,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),c)return c;if(this._backtrack){for(var t in p)this[t]=p[t];return!1}return!1},"test_match"),next:u(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var n,a,c,r;this._more||(this.yytext="",this.match="");for(var p=this._currentRules(),t=0;ta[0].length)){if(a=c,r=t,this.options.backtrack_lexer){if(n=this.test_match(c,p[t]),n!==!1)return n;if(this._backtrack){a=!1;continue}else return!1}else if(!this.options.flex)break}return a?(n=this.test_match(a,p[r]),n!==!1?n:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:u(function(){var a=this.next();return a||this.lex()},"lex"),begin:u(function(a){this.conditionStack.push(a)},"begin"),popState:u(function(){var a=this.conditionStack.length-1;return a>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:u(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:u(function(a){return a=this.conditionStack.length-1-Math.abs(a||0),a>=0?this.conditionStack[a]:"INITIAL"},"topState"),pushState:u(function(a){this.begin(a)},"pushState"),stateStackSize:u(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:u(function(a,c,r,p){switch(r){case 0:return this.begin("acc_title"),24;case 1:return this.popState(),"acc_title_value";case 2:return this.begin("acc_descr"),26;case 3:return this.popState(),"acc_descr_value";case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:return 33;case 8:return 34;case 9:return 35;case 10:return 36;case 11:return 10;case 12:break;case 13:return 8;case 14:return 50;case 15:return 70;case 16:return 4;case 17:return this.begin("block"),17;case 18:return 49;case 19:return 49;case 20:return 42;case 21:return 15;case 22:return 13;case 23:break;case 24:return 59;case 25:return 56;case 26:return 56;case 27:return 60;case 28:break;case 29:return this.popState(),19;case 30:return c.yytext[0];case 31:return 20;case 32:return 21;case 33:return this.begin("style"),44;case 34:return this.popState(),10;case 35:break;case 36:return 13;case 37:return 42;case 38:return 49;case 39:return this.begin("style"),37;case 40:return 43;case 41:return 63;case 42:return 65;case 43:return 65;case 44:return 65;case 45:return 63;case 46:return 63;case 47:return 64;case 48:return 64;case 49:return 64;case 50:return 64;case 51:return 64;case 52:return 65;case 53:return 64;case 54:return 65;case 55:return 66;case 56:return 66;case 57:return 66;case 58:return 66;case 59:return 63;case 60:return 64;case 61:return 65;case 62:return 67;case 63:return 68;case 64:return 69;case 65:return 69;case 66:return 68;case 67:return 68;case 68:return 68;case 69:return 41;case 70:return 47;case 71:return 40;case 72:return 48;case 73:return c.yytext[0];case 74:return 6}},"anonymous"),rules:[/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"%\r\n\v\b\\]+")/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:#)/i,/^(?:#)/i,/^(?:,)/i,/^(?::::)/i,/^(?::)/i,/^(?:\s+)/i,/^(?:\b((?:PK)|(?:FK)|(?:UK))\b)/i,/^(?:([^\s]*)[~].*[~]([^\s]*))/i,/^(?:([\*A-Za-z_\u00C0-\uFFFF][A-Za-z0-9\-\_\[\]\(\)\u00C0-\uFFFF\*]*))/i,/^(?:"[^"]*")/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\[)/i,/^(?:\])/i,/^(?:style\b)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?::)/i,/^(?:,)/i,/^(?:#)/i,/^(?:classDef\b)/i,/^(?:class\b)/i,/^(?:one or zero\b)/i,/^(?:one or more\b)/i,/^(?:one or many\b)/i,/^(?:1\+)/i,/^(?:\|o\b)/i,/^(?:zero or one\b)/i,/^(?:zero or more\b)/i,/^(?:zero or many\b)/i,/^(?:0\+)/i,/^(?:\}o\b)/i,/^(?:many\(0\))/i,/^(?:many\(1\))/i,/^(?:many\b)/i,/^(?:\}\|)/i,/^(?:one\b)/i,/^(?:only one\b)/i,/^(?:1\b)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\s*u\b)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:to\b)/i,/^(?:optionally to\b)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:([^\x00-\x7F]|\w|-|\*)+)/i,/^(?:;)/i,/^(?:([^\x00-\x7F]|\w|-|\*)+)/i,/^(?:[0-9])/i,/^(?:.)/i,/^(?:$)/i],conditions:{style:{rules:[34,35,36,37,38,69,70],inclusive:!1},acc_descr_multiline:{rules:[5,6],inclusive:!1},acc_descr:{rules:[3],inclusive:!1},acc_title:{rules:[1],inclusive:!1},block:{rules:[23,24,25,26,27,28,29,30],inclusive:!1},INITIAL:{rules:[0,2,4,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,31,32,33,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,71,72,73,74],inclusive:!0}}};return R}();ct.lexer=Rt;function q(){this.yy={}}return u(q,"Parser"),q.prototype=ct,ct.Parser=q,new q}();dt.parser=dt;var Xt=dt,w,qt=(w=class{constructor(){this.entities=new Map,this.relationships=[],this.classes=new Map,this.direction="TB",this.Cardinality={ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE",MD_PARENT:"MD_PARENT"},this.Identification={NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},this.setAccTitle=Vt,this.getAccTitle=Lt,this.setAccDescription=Mt,this.getAccDescription=Bt,this.setDiagramTitle=Ft,this.getDiagramTitle=Yt,this.getConfig=u(()=>tt().er,"getConfig"),this.clear(),this.addEntity=this.addEntity.bind(this),this.addAttributes=this.addAttributes.bind(this),this.addRelationship=this.addRelationship.bind(this),this.setDirection=this.setDirection.bind(this),this.addCssStyles=this.addCssStyles.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.setAccTitle=this.setAccTitle.bind(this),this.setAccDescription=this.setAccDescription.bind(this)}addEntity(i,h=""){var d;return this.entities.has(i)?!((d=this.entities.get(i))!=null&&d.alias)&&h&&(this.entities.get(i).alias=h,D.info(`Add alias '${h}' to entity '${i}'`)):(this.entities.set(i,{id:`entity-${i}-${this.entities.size}`,label:i,attributes:[],alias:h,shape:"erBox",look:tt().look??"default",cssClasses:"default",cssStyles:[]}),D.info("Added new entity :",i)),this.entities.get(i)}getEntity(i){return this.entities.get(i)}getEntities(){return this.entities}getClasses(){return this.classes}addAttributes(i,h){const d=this.addEntity(i);let o;for(o=h.length-1;o>=0;o--)h[o].keys||(h[o].keys=[]),h[o].comment||(h[o].comment=""),d.attributes.push(h[o]),D.debug("Added attribute ",h[o].name)}addRelationship(i,h,d,o){const l=this.entities.get(i),f=this.entities.get(d);if(!l||!f)return;const _={entityA:l.id,roleA:h,entityB:f.id,relSpec:o};this.relationships.push(_),D.debug("Added new relationship :",_)}getRelationships(){return this.relationships}getDirection(){return this.direction}setDirection(i){this.direction=i}getCompiledStyles(i){let h=[];for(const d of i){const o=this.classes.get(d);o!=null&&o.styles&&(h=[...h,...o.styles??[]].map(l=>l.trim())),o!=null&&o.textStyles&&(h=[...h,...o.textStyles??[]].map(l=>l.trim()))}return h}addCssStyles(i,h){for(const d of i){const o=this.entities.get(d);if(!h||!o)return;for(const l of h)o.cssStyles.push(l)}}addClass(i,h){i.forEach(d=>{let o=this.classes.get(d);o===void 0&&(o={id:d,styles:[],textStyles:[]},this.classes.set(d,o)),h&&h.forEach(function(l){if(/color/.exec(l)){const f=l.replace("fill","bgFill");o.textStyles.push(f)}o.styles.push(l)})})}setClass(i,h){for(const d of i){const o=this.entities.get(d);if(o)for(const l of h)o.cssClasses+=" "+l}}clear(){this.entities=new Map,this.classes=new Map,this.relationships=[],Pt()}getData(){const i=[],h=[],d=tt();for(const l of this.entities.keys()){const f=this.entities.get(l);f&&(f.cssCompiledStyles=this.getCompiledStyles(f.cssClasses.split(" ")),i.push(f))}let o=0;for(const l of this.relationships){const f={id:zt(l.entityA,l.entityB,{prefix:"id",counter:o++}),type:"normal",curve:"basis",start:l.entityA,end:l.entityB,label:l.roleA,labelpos:"c",thickness:"normal",classes:"relationshipLine",arrowTypeStart:l.relSpec.cardB.toLowerCase(),arrowTypeEnd:l.relSpec.cardA.toLowerCase(),pattern:l.relSpec.relType=="IDENTIFYING"?"solid":"dashed",look:d.look};h.push(f)}return{nodes:i,edges:h,other:{},config:d,direction:"TB"}}},u(w,"ErDB"),w),Nt={};Zt(Nt,{draw:()=>Ht});var Ht=u(async function(s,i,h,d){D.info("REF0:"),D.info("Drawing er diagram (unified)",i);const{securityLevel:o,er:l,layout:f}=tt(),_=d.db.getData(),E=Dt(i,o);_.type=d.type,_.layoutAlgorithm=Ut(f),_.config.flowchart.nodeSpacing=(l==null?void 0:l.nodeSpacing)||140,_.config.flowchart.rankSpacing=(l==null?void 0:l.rankSpacing)||80,_.direction=d.db.getDirection(),_.markers=["only_one","zero_or_one","one_or_more","zero_or_more"],_.diagramId=i,await jt(_,E),_.layoutAlgorithm==="elk"&&E.select(".edges").lower();const V=E.selectAll('[id*="-background"]');Array.from(V).length>0&&V.each(function(){const S=Wt(this),U=S.attr("id").replace("-background",""),T=E.select(`#${CSS.escape(U)}`);if(!T.empty()){const L=T.attr("transform");S.attr("transform",L)}});const Z=8;Qt.insertTitle(E,"erDiagramTitleText",(l==null?void 0:l.titleTopMargin)??25,d.db.getDiagramTitle()),wt(E,Z,"erDiagram",(l==null?void 0:l.useMaxWidth)??!0)},"draw"),Jt=u((s,i)=>{const h=Gt,d=h(s,"r"),o=h(s,"g"),l=h(s,"b");return Kt(d,o,l,i)},"fade"),$t=u(s=>` + .entityBox { + fill: ${s.mainBkg}; + stroke: ${s.nodeBorder}; + } + + .relationshipLabelBox { + fill: ${s.tertiaryColor}; + opacity: 0.7; + background-color: ${s.tertiaryColor}; + rect { + opacity: 0.5; + } + } + + .labelBkg { + background-color: ${Jt(s.tertiaryColor,.5)}; + } + + .edgeLabel .label { + fill: ${s.nodeBorder}; + font-size: 14px; + } + + .label { + font-family: ${s.fontFamily}; + color: ${s.nodeTextColor||s.textColor}; + } + + .edge-pattern-dashed { + stroke-dasharray: 8,8; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon + { + fill: ${s.mainBkg}; + stroke: ${s.nodeBorder}; + stroke-width: 1px; + } + + .relationshipLine { + stroke: ${s.lineColor}; + stroke-width: 1; + fill: none; + } + + .marker { + fill: none !important; + stroke: ${s.lineColor} !important; + stroke-width: 1; + } +`,"getStyles"),te=$t,ne={parser:Xt,get db(){return new qt},renderer:Nt,styles:te};export{ne as diagram}; diff --git a/assets/chunks/flowDiagram-NV44I4VS.NRN3ub33.js b/assets/chunks/flowDiagram-NV44I4VS.NRN3ub33.js new file mode 100644 index 000000000..6b2c2ed71 --- /dev/null +++ b/assets/chunks/flowDiagram-NV44I4VS.NRN3ub33.js @@ -0,0 +1,162 @@ +import{g as qt}from"./chunk-FMBD7UC4.B39tdjdc.js";import{_ as m,o as Ot,l as t1,c as b1,d as S1,p as Ht,r as Xt,u as it,b as Qt,s as Jt,q as Zt,a as $t,g as te,t as ee,k as se,v as ie,J as re,x as ae,y as st,z as ne,A as ue,B as oe,C as le}from"./theme.kqgpP4eL.js";import{g as ce}from"./chunk-55IACEB6.BKKqJU_2.js";import{s as he}from"./chunk-QN33PNHL.ChYgkhtD.js";import"./framework.CgT1UzWm.js";var de="flowchart-",P1,pe=(P1=class{constructor(){this.vertexCounter=0,this.config=b1(),this.vertices=new Map,this.edges=[],this.classes=new Map,this.subGraphs=[],this.subGraphLookup=new Map,this.tooltips=new Map,this.subCount=0,this.firstGraphFlag=!0,this.secCount=-1,this.posCrossRef=[],this.funs=[],this.setAccTitle=Qt,this.setAccDescription=Jt,this.setDiagramTitle=Zt,this.getAccTitle=$t,this.getAccDescription=te,this.getDiagramTitle=ee,this.funs.push(this.setupToolTips.bind(this)),this.addVertex=this.addVertex.bind(this),this.firstGraph=this.firstGraph.bind(this),this.setDirection=this.setDirection.bind(this),this.addSubGraph=this.addSubGraph.bind(this),this.addLink=this.addLink.bind(this),this.setLink=this.setLink.bind(this),this.updateLink=this.updateLink.bind(this),this.addClass=this.addClass.bind(this),this.setClass=this.setClass.bind(this),this.destructLink=this.destructLink.bind(this),this.setClickEvent=this.setClickEvent.bind(this),this.setTooltip=this.setTooltip.bind(this),this.updateLinkInterpolate=this.updateLinkInterpolate.bind(this),this.setClickFun=this.setClickFun.bind(this),this.bindFunctions=this.bindFunctions.bind(this),this.lex={firstGraph:this.firstGraph.bind(this)},this.clear(),this.setGen("gen-2")}sanitizeText(i){return se.sanitizeText(i,this.config)}lookUpDomId(i){for(const a of this.vertices.values())if(a.id===i)return a.domId;return i}addVertex(i,a,n,u,o,f,c={},A){var L,C;if(!i||i.trim().length===0)return;let r;if(A!==void 0){let d;A.includes(` +`)?d=A+` +`:d=`{ +`+A+` +}`,r=ie(d,{schema:re})}const k=this.edges.find(d=>d.id===i);if(k){const d=r;(d==null?void 0:d.animate)!==void 0&&(k.animate=d.animate),(d==null?void 0:d.animation)!==void 0&&(k.animation=d.animation),(d==null?void 0:d.curve)!==void 0&&(k.interpolate=d.curve);return}let E,b=this.vertices.get(i);if(b===void 0&&(b={id:i,labelType:"text",domId:de+i+"-"+this.vertexCounter,styles:[],classes:[]},this.vertices.set(i,b)),this.vertexCounter++,a!==void 0?(this.config=b1(),E=this.sanitizeText(a.text.trim()),b.labelType=a.type,E.startsWith('"')&&E.endsWith('"')&&(E=E.substring(1,E.length-1)),b.text=E):b.text===void 0&&(b.text=i),n!==void 0&&(b.type=n),u!=null&&u.forEach(d=>{b.styles.push(d)}),o!=null&&o.forEach(d=>{b.classes.push(d)}),f!==void 0&&(b.dir=f),b.props===void 0?b.props=c:c!==void 0&&Object.assign(b.props,c),r!==void 0){if(r.shape){if(r.shape!==r.shape.toLowerCase()||r.shape.includes("_"))throw new Error(`No such shape: ${r.shape}. Shape names should be lowercase.`);if(!ae(r.shape))throw new Error(`No such shape: ${r.shape}.`);b.type=r==null?void 0:r.shape}r!=null&&r.label&&(b.text=r==null?void 0:r.label),r!=null&&r.icon&&(b.icon=r==null?void 0:r.icon,!((L=r.label)!=null&&L.trim())&&b.text===i&&(b.text="")),r!=null&&r.form&&(b.form=r==null?void 0:r.form),r!=null&&r.pos&&(b.pos=r==null?void 0:r.pos),r!=null&&r.img&&(b.img=r==null?void 0:r.img,!((C=r.label)!=null&&C.trim())&&b.text===i&&(b.text="")),r!=null&&r.constraint&&(b.constraint=r.constraint),r.w&&(b.assetWidth=Number(r.w)),r.h&&(b.assetHeight=Number(r.h))}}addSingleLink(i,a,n,u){const c={start:i,end:a,type:void 0,text:"",labelType:"text",classes:[],isUserDefinedId:!1,interpolate:this.edges.defaultInterpolate};t1.info("abc78 Got edge...",c);const A=n.text;if(A!==void 0&&(c.text=this.sanitizeText(A.text.trim()),c.text.startsWith('"')&&c.text.endsWith('"')&&(c.text=c.text.substring(1,c.text.length-1)),c.labelType=A.type),n!==void 0&&(c.type=n.type,c.stroke=n.stroke,c.length=n.length>10?10:n.length),u&&!this.edges.some(r=>r.id===u))c.id=u,c.isUserDefinedId=!0;else{const r=this.edges.filter(k=>k.start===c.start&&k.end===c.end);r.length===0?c.id=st(c.start,c.end,{counter:0,prefix:"L"}):c.id=st(c.start,c.end,{counter:r.length+1,prefix:"L"})}if(this.edges.length<(this.config.maxEdges??500))t1.info("Pushing edge..."),this.edges.push(c);else throw new Error(`Edge limit exceeded. ${this.edges.length} edges found, but the limit is ${this.config.maxEdges}. + +Initialize mermaid with maxEdges set to a higher number to allow more edges. +You cannot set this config via configuration inside the diagram as it is a secure config. +You have to call mermaid.initialize.`)}isLinkData(i){return i!==null&&typeof i=="object"&&"id"in i&&typeof i.id=="string"}addLink(i,a,n){const u=this.isLinkData(n)?n.id.replace("@",""):void 0;t1.info("addLink",i,a,u);for(const o of i)for(const f of a){const c=o===i[i.length-1],A=f===a[0];c&&A?this.addSingleLink(o,f,n,u):this.addSingleLink(o,f,n,void 0)}}updateLinkInterpolate(i,a){i.forEach(n=>{n==="default"?this.edges.defaultInterpolate=a:this.edges[n].interpolate=a})}updateLink(i,a){i.forEach(n=>{var u,o,f,c,A,r;if(typeof n=="number"&&n>=this.edges.length)throw new Error(`The index ${n} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${this.edges.length-1}. (Help: Ensure that the index is within the range of existing edges.)`);n==="default"?this.edges.defaultStyle=a:(this.edges[n].style=a,(((o=(u=this.edges[n])==null?void 0:u.style)==null?void 0:o.length)??0)>0&&!((c=(f=this.edges[n])==null?void 0:f.style)!=null&&c.some(k=>k==null?void 0:k.startsWith("fill")))&&((r=(A=this.edges[n])==null?void 0:A.style)==null||r.push("fill:none")))})}addClass(i,a){const n=a.join().replace(/\\,/g,"§§§").replace(/,/g,";").replace(/§§§/g,",").split(";");i.split(",").forEach(u=>{let o=this.classes.get(u);o===void 0&&(o={id:u,styles:[],textStyles:[]},this.classes.set(u,o)),n!=null&&n.forEach(f=>{if(/color/.exec(f)){const c=f.replace("fill","bgFill");o.textStyles.push(c)}o.styles.push(f)})})}setDirection(i){this.direction=i.trim(),/.*/.exec(this.direction)&&(this.direction="LR"),/.*v/.exec(this.direction)&&(this.direction="TB"),this.direction==="TD"&&(this.direction="TB")}setClass(i,a){for(const n of i.split(",")){const u=this.vertices.get(n);u&&u.classes.push(a);const o=this.edges.find(c=>c.id===n);o&&o.classes.push(a);const f=this.subGraphLookup.get(n);f&&f.classes.push(a)}}setTooltip(i,a){if(a!==void 0){a=this.sanitizeText(a);for(const n of i.split(","))this.tooltips.set(this.version==="gen-1"?this.lookUpDomId(n):n,a)}}setClickFun(i,a,n){const u=this.lookUpDomId(i);if(b1().securityLevel!=="loose"||a===void 0)return;let o=[];if(typeof n=="string"){o=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let c=0;c{const c=document.querySelector(`[id="${u}"]`);c!==null&&c.addEventListener("click",()=>{it.runFunc(a,...o)},!1)}))}setLink(i,a,n){i.split(",").forEach(u=>{const o=this.vertices.get(u);o!==void 0&&(o.link=it.formatUrl(a,this.config),o.linkTarget=n)}),this.setClass(i,"clickable")}getTooltip(i){return this.tooltips.get(i)}setClickEvent(i,a,n){i.split(",").forEach(u=>{this.setClickFun(u,a,n)}),this.setClass(i,"clickable")}bindFunctions(i){this.funs.forEach(a=>{a(i)})}getDirection(){var i;return(i=this.direction)==null?void 0:i.trim()}getVertices(){return this.vertices}getEdges(){return this.edges}getClasses(){return this.classes}setupToolTips(i){let a=S1(".mermaidTooltip");(a._groups||a)[0][0]===null&&(a=S1("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),S1(i).select("svg").selectAll("g.node").on("mouseover",o=>{var r;const f=S1(o.currentTarget);if(f.attr("title")===null)return;const A=(r=o.currentTarget)==null?void 0:r.getBoundingClientRect();a.transition().duration(200).style("opacity",".9"),a.text(f.attr("title")).style("left",window.scrollX+A.left+(A.right-A.left)/2+"px").style("top",window.scrollY+A.bottom+"px"),a.html(a.html().replace(/<br\/>/g,"
")),f.classed("hover",!0)}).on("mouseout",o=>{a.transition().duration(500).style("opacity",0),S1(o.currentTarget).classed("hover",!1)})}clear(i="gen-2"){this.vertices=new Map,this.classes=new Map,this.edges=[],this.funs=[this.setupToolTips.bind(this)],this.subGraphs=[],this.subGraphLookup=new Map,this.subCount=0,this.tooltips=new Map,this.firstGraphFlag=!0,this.version=i,this.config=b1(),ne()}setGen(i){this.version=i||"gen-2"}defaultStyle(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"}addSubGraph(i,a,n){let u=i.text.trim(),o=n.text;i===n&&/\s/.exec(n.text)&&(u=void 0);const c=m(b=>{const L={boolean:{},number:{},string:{}},C=[];let d;return{nodeList:b.filter(function(W){const Z=typeof W;return W.stmt&&W.stmt==="dir"?(d=W.value,!1):W.trim()===""?!1:Z in L?L[Z].hasOwnProperty(W)?!1:L[Z][W]=!0:C.includes(W)?!1:C.push(W)}),dir:d}},"uniq")(a.flat()),A=c.nodeList;let r=c.dir;const k=b1().flowchart??{};if(r=r??(k.inheritDir?this.getDirection()??b1().direction??void 0:void 0),this.version==="gen-1")for(let b=0;b2e3)return{result:!1,count:0};if(this.posCrossRef[this.secCount]=a,this.subGraphs[a].id===i)return{result:!0,count:0};let u=0,o=1;for(;u=0){const c=this.indexNodes2(i,f);if(c.result)return{result:!0,count:o+c.count};o=o+c.count}u=u+1}return{result:!1,count:o}}getDepthFirstPos(i){return this.posCrossRef[i]}indexNodes(){this.secCount=-1,this.subGraphs.length>0&&this.indexNodes2("none",this.subGraphs.length-1)}getSubGraphs(){return this.subGraphs}firstGraph(){return this.firstGraphFlag?(this.firstGraphFlag=!1,!0):!1}destructStartLink(i){let a=i.trim(),n="arrow_open";switch(a[0]){case"<":n="arrow_point",a=a.slice(1);break;case"x":n="arrow_cross",a=a.slice(1);break;case"o":n="arrow_circle",a=a.slice(1);break}let u="normal";return a.includes("=")&&(u="thick"),a.includes(".")&&(u="dotted"),{type:n,stroke:u}}countChar(i,a){const n=a.length;let u=0;for(let o=0;o":u="arrow_point",a.startsWith("<")&&(u="double_"+u,n=n.slice(1));break;case"o":u="arrow_circle",a.startsWith("o")&&(u="double_"+u,n=n.slice(1));break}let o="normal",f=n.length-1;n.startsWith("=")&&(o="thick"),n.startsWith("~")&&(o="invisible");const c=this.countChar(".",n);return c&&(o="dotted",f=c),{type:u,stroke:o,length:f}}destructLink(i,a){const n=this.destructEndLink(i);let u;if(a){if(u=this.destructStartLink(a),u.stroke!==n.stroke)return{type:"INVALID",stroke:"INVALID"};if(u.type==="arrow_open")u.type=n.type;else{if(u.type!==n.type)return{type:"INVALID",stroke:"INVALID"};u.type="double_"+u.type}return u.type==="double_arrow"&&(u.type="double_arrow_point"),u.length=n.length,u}return n}exists(i,a){for(const n of i)if(n.nodes.includes(a))return!0;return!1}makeUniq(i,a){const n=[];return i.nodes.forEach((u,o)=>{this.exists(a,u)||n.push(i.nodes[o])}),{nodes:n}}getTypeFromVertex(i){if(i.img)return"imageSquare";if(i.icon)return i.form==="circle"?"iconCircle":i.form==="square"?"iconSquare":i.form==="rounded"?"iconRounded":"icon";switch(i.type){case"square":case void 0:return"squareRect";case"round":return"roundedRect";case"ellipse":return"ellipse";default:return i.type}}findNode(i,a){return i.find(n=>n.id===a)}destructEdgeType(i){let a="none",n="arrow_point";switch(i){case"arrow_point":case"arrow_circle":case"arrow_cross":n=i;break;case"double_arrow_point":case"double_arrow_circle":case"double_arrow_cross":a=i.replace("double_",""),n=a;break}return{arrowTypeStart:a,arrowTypeEnd:n}}addNodeFromVertex(i,a,n,u,o,f){var k;const c=n.get(i.id),A=u.get(i.id)??!1,r=this.findNode(a,i.id);if(r)r.cssStyles=i.styles,r.cssCompiledStyles=this.getCompiledStyles(i.classes),r.cssClasses=i.classes.join(" ");else{const E={id:i.id,label:i.text,labelStyle:"",parentId:c,padding:((k=o.flowchart)==null?void 0:k.padding)||8,cssStyles:i.styles,cssCompiledStyles:this.getCompiledStyles(["default","node",...i.classes]),cssClasses:"default "+i.classes.join(" "),dir:i.dir,domId:i.domId,look:f,link:i.link,linkTarget:i.linkTarget,tooltip:this.getTooltip(i.id),icon:i.icon,pos:i.pos,img:i.img,assetWidth:i.assetWidth,assetHeight:i.assetHeight,constraint:i.constraint};A?a.push({...E,isGroup:!0,shape:"rect"}):a.push({...E,isGroup:!1,shape:this.getTypeFromVertex(i)})}}getCompiledStyles(i){let a=[];for(const n of i){const u=this.classes.get(n);u!=null&&u.styles&&(a=[...a,...u.styles??[]].map(o=>o.trim())),u!=null&&u.textStyles&&(a=[...a,...u.textStyles??[]].map(o=>o.trim()))}return a}getData(){const i=b1(),a=[],n=[],u=this.getSubGraphs(),o=new Map,f=new Map;for(let r=u.length-1;r>=0;r--){const k=u[r];k.nodes.length>0&&f.set(k.id,!0);for(const E of k.nodes)o.set(E,k.id)}for(let r=u.length-1;r>=0;r--){const k=u[r];a.push({id:k.id,label:k.title,labelStyle:"",parentId:o.get(k.id),padding:8,cssCompiledStyles:this.getCompiledStyles(k.classes),cssClasses:k.classes.join(" "),shape:"rect",dir:k.dir,isGroup:!0,look:i.look})}this.getVertices().forEach(r=>{this.addNodeFromVertex(r,a,o,f,i,i.look||"classic")});const A=this.getEdges();return A.forEach((r,k)=>{var d;const{arrowTypeStart:E,arrowTypeEnd:b}=this.destructEdgeType(r.type),L=[...A.defaultStyle??[]];r.style&&L.push(...r.style);const C={id:st(r.start,r.end,{counter:k,prefix:"L"},r.id),isUserDefinedId:r.isUserDefinedId,start:r.start,end:r.end,type:r.type??"normal",label:r.text,labelpos:"c",thickness:r.stroke,minlen:r.length,classes:(r==null?void 0:r.stroke)==="invisible"?"":"edge-thickness-normal edge-pattern-solid flowchart-link",arrowTypeStart:(r==null?void 0:r.stroke)==="invisible"||(r==null?void 0:r.type)==="arrow_open"?"none":E,arrowTypeEnd:(r==null?void 0:r.stroke)==="invisible"||(r==null?void 0:r.type)==="arrow_open"?"none":b,arrowheadStyle:"fill: #333",cssCompiledStyles:this.getCompiledStyles(r.classes),labelStyle:L,style:L,pattern:r.stroke,look:i.look,animate:r.animate,animation:r.animation,curve:r.interpolate||this.edges.defaultInterpolate||((d=i.flowchart)==null?void 0:d.curve)};n.push(C)}),{nodes:a,edges:n,other:{},config:i}}defaultConfig(){return ue.flowchart}},m(P1,"FlowDB"),P1),fe=m(function(s,i){return i.db.getClasses()},"getClasses"),ge=m(async function(s,i,a,n){var L;t1.info("REF0:"),t1.info("Drawing state diagram (v2)",i);const{securityLevel:u,flowchart:o,layout:f}=b1();let c;u==="sandbox"&&(c=S1("#i"+i));const A=u==="sandbox"?c.nodes()[0].contentDocument:document;t1.debug("Before getData: ");const r=n.db.getData();t1.debug("Data: ",r);const k=ce(i,u),E=n.db.getDirection();r.type=n.type,r.layoutAlgorithm=Ht(f),r.layoutAlgorithm==="dagre"&&f==="elk"&&t1.warn("flowchart-elk was moved to an external package in Mermaid v11. Please refer [release notes](https://github.com/mermaid-js/mermaid/releases/tag/v11.0.0) for more details. This diagram will be rendered using `dagre` layout as a fallback."),r.direction=E,r.nodeSpacing=(o==null?void 0:o.nodeSpacing)||50,r.rankSpacing=(o==null?void 0:o.rankSpacing)||50,r.markers=["point","circle","cross"],r.diagramId=i,t1.debug("REF1:",r),await Xt(r,k);const b=((L=r.config.flowchart)==null?void 0:L.diagramPadding)??8;it.insertTitle(k,"flowchartTitleText",(o==null?void 0:o.titleTopMargin)||0,n.db.getDiagramTitle()),he(k,b,"flowchart",(o==null?void 0:o.useMaxWidth)||!1);for(const C of r.nodes){const d=S1(`#${i} [id="${C.id}"]`);if(!d||!C.link)continue;const J=A.createElementNS("http://www.w3.org/2000/svg","a");J.setAttributeNS("http://www.w3.org/2000/svg","class",C.cssClasses),J.setAttributeNS("http://www.w3.org/2000/svg","rel","noopener"),u==="sandbox"?J.setAttributeNS("http://www.w3.org/2000/svg","target","_top"):C.linkTarget&&J.setAttributeNS("http://www.w3.org/2000/svg","target",C.linkTarget);const W=d.insert(function(){return J},":first-child"),Z=d.select(".label-container");Z&&W.append(function(){return Z.node()});const A1=d.select(".label");A1&&W.append(function(){return A1.node()})}},"draw"),be={getClasses:fe,draw:ge},rt=function(){var s=m(function(g1,h,p,g){for(p=p||{},g=g1.length;g--;p[g1[g]]=h);return p},"o"),i=[1,4],a=[1,3],n=[1,5],u=[1,8,9,10,11,27,34,36,38,44,60,84,85,86,87,88,89,102,105,106,109,111,114,115,116,121,122,123,124],o=[2,2],f=[1,13],c=[1,14],A=[1,15],r=[1,16],k=[1,23],E=[1,25],b=[1,26],L=[1,27],C=[1,49],d=[1,48],J=[1,29],W=[1,30],Z=[1,31],A1=[1,32],M1=[1,33],V=[1,44],I=[1,46],w=[1,42],R=[1,47],N=[1,43],G=[1,50],P=[1,45],O=[1,51],M=[1,52],U1=[1,34],W1=[1,35],z1=[1,36],j1=[1,37],p1=[1,57],y=[1,8,9,10,11,27,32,34,36,38,44,60,84,85,86,87,88,89,102,105,106,109,111,114,115,116,121,122,123,124],e1=[1,61],s1=[1,60],i1=[1,62],D1=[8,9,11,75,77,78],at=[1,78],x1=[1,91],T1=[1,96],E1=[1,95],y1=[1,92],F1=[1,88],_1=[1,94],B1=[1,90],v1=[1,97],L1=[1,93],V1=[1,98],I1=[1,89],k1=[8,9,10,11,40,75,77,78],z=[8,9,10,11,40,46,75,77,78],q=[8,9,10,11,29,40,44,46,48,50,52,54,56,58,60,63,65,67,68,70,75,77,78,89,102,105,106,109,111,114,115,116],nt=[8,9,11,44,60,75,77,78,89,102,105,106,109,111,114,115,116],w1=[44,60,89,102,105,106,109,111,114,115,116],ut=[1,121],ot=[1,122],K1=[1,124],Y1=[1,123],lt=[44,60,62,74,89,102,105,106,109,111,114,115,116],ct=[1,133],ht=[1,147],dt=[1,148],pt=[1,149],ft=[1,150],gt=[1,135],bt=[1,137],At=[1,141],kt=[1,142],mt=[1,143],Ct=[1,144],St=[1,145],Dt=[1,146],xt=[1,151],Tt=[1,152],Et=[1,131],yt=[1,132],Ft=[1,139],_t=[1,134],Bt=[1,138],vt=[1,136],Q1=[8,9,10,11,27,32,34,36,38,44,60,84,85,86,87,88,89,102,105,106,109,111,114,115,116,121,122,123,124],Lt=[1,154],Vt=[1,156],B=[8,9,11],H=[8,9,10,11,14,44,60,89,105,106,109,111,114,115,116],S=[1,176],j=[1,172],K=[1,173],D=[1,177],x=[1,174],T=[1,175],R1=[77,116,119],F=[8,9,10,11,12,14,27,29,32,44,60,75,84,85,86,87,88,89,90,105,109,111,114,115,116],It=[10,106],f1=[31,49,51,53,55,57,62,64,66,67,69,71,116,117,118],r1=[1,247],a1=[1,245],n1=[1,249],u1=[1,243],o1=[1,244],l1=[1,246],c1=[1,248],h1=[1,250],N1=[1,268],wt=[8,9,11,106],$=[8,9,10,11,60,84,105,106,109,110,111,112],J1={trace:m(function(){},"trace"),yy:{},symbols_:{error:2,start:3,graphConfig:4,document:5,line:6,statement:7,SEMI:8,NEWLINE:9,SPACE:10,EOF:11,GRAPH:12,NODIR:13,DIR:14,FirstStmtSeparator:15,ending:16,endToken:17,spaceList:18,spaceListNewline:19,vertexStatement:20,separator:21,styleStatement:22,linkStyleStatement:23,classDefStatement:24,classStatement:25,clickStatement:26,subgraph:27,textNoTags:28,SQS:29,text:30,SQE:31,end:32,direction:33,acc_title:34,acc_title_value:35,acc_descr:36,acc_descr_value:37,acc_descr_multiline_value:38,shapeData:39,SHAPE_DATA:40,link:41,node:42,styledVertex:43,AMP:44,vertex:45,STYLE_SEPARATOR:46,idString:47,DOUBLECIRCLESTART:48,DOUBLECIRCLEEND:49,PS:50,PE:51,"(-":52,"-)":53,STADIUMSTART:54,STADIUMEND:55,SUBROUTINESTART:56,SUBROUTINEEND:57,VERTEX_WITH_PROPS_START:58,"NODE_STRING[field]":59,COLON:60,"NODE_STRING[value]":61,PIPE:62,CYLINDERSTART:63,CYLINDEREND:64,DIAMOND_START:65,DIAMOND_STOP:66,TAGEND:67,TRAPSTART:68,TRAPEND:69,INVTRAPSTART:70,INVTRAPEND:71,linkStatement:72,arrowText:73,TESTSTR:74,START_LINK:75,edgeText:76,LINK:77,LINK_ID:78,edgeTextToken:79,STR:80,MD_STR:81,textToken:82,keywords:83,STYLE:84,LINKSTYLE:85,CLASSDEF:86,CLASS:87,CLICK:88,DOWN:89,UP:90,textNoTagsToken:91,stylesOpt:92,"idString[vertex]":93,"idString[class]":94,CALLBACKNAME:95,CALLBACKARGS:96,HREF:97,LINK_TARGET:98,"STR[link]":99,"STR[tooltip]":100,alphaNum:101,DEFAULT:102,numList:103,INTERPOLATE:104,NUM:105,COMMA:106,style:107,styleComponent:108,NODE_STRING:109,UNIT:110,BRKT:111,PCT:112,idStringToken:113,MINUS:114,MULT:115,UNICODE_TEXT:116,TEXT:117,TAGSTART:118,EDGE_TEXT:119,alphaNumToken:120,direction_tb:121,direction_bt:122,direction_rl:123,direction_lr:124,$accept:0,$end:1},terminals_:{2:"error",8:"SEMI",9:"NEWLINE",10:"SPACE",11:"EOF",12:"GRAPH",13:"NODIR",14:"DIR",27:"subgraph",29:"SQS",31:"SQE",32:"end",34:"acc_title",35:"acc_title_value",36:"acc_descr",37:"acc_descr_value",38:"acc_descr_multiline_value",40:"SHAPE_DATA",44:"AMP",46:"STYLE_SEPARATOR",48:"DOUBLECIRCLESTART",49:"DOUBLECIRCLEEND",50:"PS",51:"PE",52:"(-",53:"-)",54:"STADIUMSTART",55:"STADIUMEND",56:"SUBROUTINESTART",57:"SUBROUTINEEND",58:"VERTEX_WITH_PROPS_START",59:"NODE_STRING[field]",60:"COLON",61:"NODE_STRING[value]",62:"PIPE",63:"CYLINDERSTART",64:"CYLINDEREND",65:"DIAMOND_START",66:"DIAMOND_STOP",67:"TAGEND",68:"TRAPSTART",69:"TRAPEND",70:"INVTRAPSTART",71:"INVTRAPEND",74:"TESTSTR",75:"START_LINK",77:"LINK",78:"LINK_ID",80:"STR",81:"MD_STR",84:"STYLE",85:"LINKSTYLE",86:"CLASSDEF",87:"CLASS",88:"CLICK",89:"DOWN",90:"UP",93:"idString[vertex]",94:"idString[class]",95:"CALLBACKNAME",96:"CALLBACKARGS",97:"HREF",98:"LINK_TARGET",99:"STR[link]",100:"STR[tooltip]",102:"DEFAULT",104:"INTERPOLATE",105:"NUM",106:"COMMA",109:"NODE_STRING",110:"UNIT",111:"BRKT",112:"PCT",114:"MINUS",115:"MULT",116:"UNICODE_TEXT",117:"TEXT",118:"TAGSTART",119:"EDGE_TEXT",121:"direction_tb",122:"direction_bt",123:"direction_rl",124:"direction_lr"},productions_:[0,[3,2],[5,0],[5,2],[6,1],[6,1],[6,1],[6,1],[6,1],[4,2],[4,2],[4,2],[4,3],[16,2],[16,1],[17,1],[17,1],[17,1],[15,1],[15,1],[15,2],[19,2],[19,2],[19,1],[19,1],[18,2],[18,1],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,9],[7,6],[7,4],[7,1],[7,2],[7,2],[7,1],[21,1],[21,1],[21,1],[39,2],[39,1],[20,4],[20,3],[20,4],[20,2],[20,2],[20,1],[42,1],[42,6],[42,5],[43,1],[43,3],[45,4],[45,4],[45,6],[45,4],[45,4],[45,4],[45,8],[45,4],[45,4],[45,4],[45,6],[45,4],[45,4],[45,4],[45,4],[45,4],[45,1],[41,2],[41,3],[41,3],[41,1],[41,3],[41,4],[76,1],[76,2],[76,1],[76,1],[72,1],[72,2],[73,3],[30,1],[30,2],[30,1],[30,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[28,1],[28,2],[28,1],[28,1],[24,5],[25,5],[26,2],[26,4],[26,3],[26,5],[26,3],[26,5],[26,5],[26,7],[26,2],[26,4],[26,2],[26,4],[26,4],[26,6],[22,5],[23,5],[23,5],[23,9],[23,9],[23,7],[23,7],[103,1],[103,3],[92,1],[92,3],[107,1],[107,2],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[113,1],[82,1],[82,1],[82,1],[82,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[91,1],[79,1],[79,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[120,1],[47,1],[47,2],[101,1],[101,2],[33,1],[33,1],[33,1],[33,1]],performAction:m(function(h,p,g,l,_,t,O1){var e=t.length-1;switch(_){case 2:this.$=[];break;case 3:(!Array.isArray(t[e])||t[e].length>0)&&t[e-1].push(t[e]),this.$=t[e-1];break;case 4:case 183:this.$=t[e];break;case 11:l.setDirection("TB"),this.$="TB";break;case 12:l.setDirection(t[e-1]),this.$=t[e-1];break;case 27:this.$=t[e-1].nodes;break;case 28:case 29:case 30:case 31:case 32:this.$=[];break;case 33:this.$=l.addSubGraph(t[e-6],t[e-1],t[e-4]);break;case 34:this.$=l.addSubGraph(t[e-3],t[e-1],t[e-3]);break;case 35:this.$=l.addSubGraph(void 0,t[e-1],void 0);break;case 37:this.$=t[e].trim(),l.setAccTitle(this.$);break;case 38:case 39:this.$=t[e].trim(),l.setAccDescription(this.$);break;case 43:this.$=t[e-1]+t[e];break;case 44:this.$=t[e];break;case 45:l.addVertex(t[e-1][t[e-1].length-1],void 0,void 0,void 0,void 0,void 0,void 0,t[e]),l.addLink(t[e-3].stmt,t[e-1],t[e-2]),this.$={stmt:t[e-1],nodes:t[e-1].concat(t[e-3].nodes)};break;case 46:l.addLink(t[e-2].stmt,t[e],t[e-1]),this.$={stmt:t[e],nodes:t[e].concat(t[e-2].nodes)};break;case 47:l.addLink(t[e-3].stmt,t[e-1],t[e-2]),this.$={stmt:t[e-1],nodes:t[e-1].concat(t[e-3].nodes)};break;case 48:this.$={stmt:t[e-1],nodes:t[e-1]};break;case 49:l.addVertex(t[e-1][t[e-1].length-1],void 0,void 0,void 0,void 0,void 0,void 0,t[e]),this.$={stmt:t[e-1],nodes:t[e-1],shapeData:t[e]};break;case 50:this.$={stmt:t[e],nodes:t[e]};break;case 51:this.$=[t[e]];break;case 52:l.addVertex(t[e-5][t[e-5].length-1],void 0,void 0,void 0,void 0,void 0,void 0,t[e-4]),this.$=t[e-5].concat(t[e]);break;case 53:this.$=t[e-4].concat(t[e]);break;case 54:this.$=t[e];break;case 55:this.$=t[e-2],l.setClass(t[e-2],t[e]);break;case 56:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"square");break;case 57:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"doublecircle");break;case 58:this.$=t[e-5],l.addVertex(t[e-5],t[e-2],"circle");break;case 59:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"ellipse");break;case 60:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"stadium");break;case 61:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"subroutine");break;case 62:this.$=t[e-7],l.addVertex(t[e-7],t[e-1],"rect",void 0,void 0,void 0,Object.fromEntries([[t[e-5],t[e-3]]]));break;case 63:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"cylinder");break;case 64:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"round");break;case 65:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"diamond");break;case 66:this.$=t[e-5],l.addVertex(t[e-5],t[e-2],"hexagon");break;case 67:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"odd");break;case 68:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"trapezoid");break;case 69:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"inv_trapezoid");break;case 70:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"lean_right");break;case 71:this.$=t[e-3],l.addVertex(t[e-3],t[e-1],"lean_left");break;case 72:this.$=t[e],l.addVertex(t[e]);break;case 73:t[e-1].text=t[e],this.$=t[e-1];break;case 74:case 75:t[e-2].text=t[e-1],this.$=t[e-2];break;case 76:this.$=t[e];break;case 77:var v=l.destructLink(t[e],t[e-2]);this.$={type:v.type,stroke:v.stroke,length:v.length,text:t[e-1]};break;case 78:var v=l.destructLink(t[e],t[e-2]);this.$={type:v.type,stroke:v.stroke,length:v.length,text:t[e-1],id:t[e-3]};break;case 79:this.$={text:t[e],type:"text"};break;case 80:this.$={text:t[e-1].text+""+t[e],type:t[e-1].type};break;case 81:this.$={text:t[e],type:"string"};break;case 82:this.$={text:t[e],type:"markdown"};break;case 83:var v=l.destructLink(t[e]);this.$={type:v.type,stroke:v.stroke,length:v.length};break;case 84:var v=l.destructLink(t[e]);this.$={type:v.type,stroke:v.stroke,length:v.length,id:t[e-1]};break;case 85:this.$=t[e-1];break;case 86:this.$={text:t[e],type:"text"};break;case 87:this.$={text:t[e-1].text+""+t[e],type:t[e-1].type};break;case 88:this.$={text:t[e],type:"string"};break;case 89:case 104:this.$={text:t[e],type:"markdown"};break;case 101:this.$={text:t[e],type:"text"};break;case 102:this.$={text:t[e-1].text+""+t[e],type:t[e-1].type};break;case 103:this.$={text:t[e],type:"text"};break;case 105:this.$=t[e-4],l.addClass(t[e-2],t[e]);break;case 106:this.$=t[e-4],l.setClass(t[e-2],t[e]);break;case 107:case 115:this.$=t[e-1],l.setClickEvent(t[e-1],t[e]);break;case 108:case 116:this.$=t[e-3],l.setClickEvent(t[e-3],t[e-2]),l.setTooltip(t[e-3],t[e]);break;case 109:this.$=t[e-2],l.setClickEvent(t[e-2],t[e-1],t[e]);break;case 110:this.$=t[e-4],l.setClickEvent(t[e-4],t[e-3],t[e-2]),l.setTooltip(t[e-4],t[e]);break;case 111:this.$=t[e-2],l.setLink(t[e-2],t[e]);break;case 112:this.$=t[e-4],l.setLink(t[e-4],t[e-2]),l.setTooltip(t[e-4],t[e]);break;case 113:this.$=t[e-4],l.setLink(t[e-4],t[e-2],t[e]);break;case 114:this.$=t[e-6],l.setLink(t[e-6],t[e-4],t[e]),l.setTooltip(t[e-6],t[e-2]);break;case 117:this.$=t[e-1],l.setLink(t[e-1],t[e]);break;case 118:this.$=t[e-3],l.setLink(t[e-3],t[e-2]),l.setTooltip(t[e-3],t[e]);break;case 119:this.$=t[e-3],l.setLink(t[e-3],t[e-2],t[e]);break;case 120:this.$=t[e-5],l.setLink(t[e-5],t[e-4],t[e]),l.setTooltip(t[e-5],t[e-2]);break;case 121:this.$=t[e-4],l.addVertex(t[e-2],void 0,void 0,t[e]);break;case 122:this.$=t[e-4],l.updateLink([t[e-2]],t[e]);break;case 123:this.$=t[e-4],l.updateLink(t[e-2],t[e]);break;case 124:this.$=t[e-8],l.updateLinkInterpolate([t[e-6]],t[e-2]),l.updateLink([t[e-6]],t[e]);break;case 125:this.$=t[e-8],l.updateLinkInterpolate(t[e-6],t[e-2]),l.updateLink(t[e-6],t[e]);break;case 126:this.$=t[e-6],l.updateLinkInterpolate([t[e-4]],t[e]);break;case 127:this.$=t[e-6],l.updateLinkInterpolate(t[e-4],t[e]);break;case 128:case 130:this.$=[t[e]];break;case 129:case 131:t[e-2].push(t[e]),this.$=t[e-2];break;case 133:this.$=t[e-1]+t[e];break;case 181:this.$=t[e];break;case 182:this.$=t[e-1]+""+t[e];break;case 184:this.$=t[e-1]+""+t[e];break;case 185:this.$={stmt:"dir",value:"TB"};break;case 186:this.$={stmt:"dir",value:"BT"};break;case 187:this.$={stmt:"dir",value:"RL"};break;case 188:this.$={stmt:"dir",value:"LR"};break}},"anonymous"),table:[{3:1,4:2,9:i,10:a,12:n},{1:[3]},s(u,o,{5:6}),{4:7,9:i,10:a,12:n},{4:8,9:i,10:a,12:n},{13:[1,9],14:[1,10]},{1:[2,1],6:11,7:12,8:f,9:c,10:A,11:r,20:17,22:18,23:19,24:20,25:21,26:22,27:k,33:24,34:E,36:b,38:L,42:28,43:38,44:C,45:39,47:40,60:d,84:J,85:W,86:Z,87:A1,88:M1,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M,121:U1,122:W1,123:z1,124:j1},s(u,[2,9]),s(u,[2,10]),s(u,[2,11]),{8:[1,54],9:[1,55],10:p1,15:53,18:56},s(y,[2,3]),s(y,[2,4]),s(y,[2,5]),s(y,[2,6]),s(y,[2,7]),s(y,[2,8]),{8:e1,9:s1,11:i1,21:58,41:59,72:63,75:[1,64],77:[1,66],78:[1,65]},{8:e1,9:s1,11:i1,21:67},{8:e1,9:s1,11:i1,21:68},{8:e1,9:s1,11:i1,21:69},{8:e1,9:s1,11:i1,21:70},{8:e1,9:s1,11:i1,21:71},{8:e1,9:s1,10:[1,72],11:i1,21:73},s(y,[2,36]),{35:[1,74]},{37:[1,75]},s(y,[2,39]),s(D1,[2,50],{18:76,39:77,10:p1,40:at}),{10:[1,79]},{10:[1,80]},{10:[1,81]},{10:[1,82]},{14:x1,44:T1,60:E1,80:[1,86],89:y1,95:[1,83],97:[1,84],101:85,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1,120:87},s(y,[2,185]),s(y,[2,186]),s(y,[2,187]),s(y,[2,188]),s(k1,[2,51]),s(k1,[2,54],{46:[1,99]}),s(z,[2,72],{113:112,29:[1,100],44:C,48:[1,101],50:[1,102],52:[1,103],54:[1,104],56:[1,105],58:[1,106],60:d,63:[1,107],65:[1,108],67:[1,109],68:[1,110],70:[1,111],89:V,102:I,105:w,106:R,109:N,111:G,114:P,115:O,116:M}),s(q,[2,181]),s(q,[2,142]),s(q,[2,143]),s(q,[2,144]),s(q,[2,145]),s(q,[2,146]),s(q,[2,147]),s(q,[2,148]),s(q,[2,149]),s(q,[2,150]),s(q,[2,151]),s(q,[2,152]),s(u,[2,12]),s(u,[2,18]),s(u,[2,19]),{9:[1,113]},s(nt,[2,26],{18:114,10:p1}),s(y,[2,27]),{42:115,43:38,44:C,45:39,47:40,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},s(y,[2,40]),s(y,[2,41]),s(y,[2,42]),s(w1,[2,76],{73:116,62:[1,118],74:[1,117]}),{76:119,79:120,80:ut,81:ot,116:K1,119:Y1},{75:[1,125],77:[1,126]},s(lt,[2,83]),s(y,[2,28]),s(y,[2,29]),s(y,[2,30]),s(y,[2,31]),s(y,[2,32]),{10:ct,12:ht,14:dt,27:pt,28:127,32:ft,44:gt,60:bt,75:At,80:[1,129],81:[1,130],83:140,84:kt,85:mt,86:Ct,87:St,88:Dt,89:xt,90:Tt,91:128,105:Et,109:yt,111:Ft,114:_t,115:Bt,116:vt},s(Q1,o,{5:153}),s(y,[2,37]),s(y,[2,38]),s(D1,[2,48],{44:Lt}),s(D1,[2,49],{18:155,10:p1,40:Vt}),s(k1,[2,44]),{44:C,47:157,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},{102:[1,158],103:159,105:[1,160]},{44:C,47:161,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},{44:C,47:162,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},s(B,[2,107],{10:[1,163],96:[1,164]}),{80:[1,165]},s(B,[2,115],{120:167,10:[1,166],14:x1,44:T1,60:E1,89:y1,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1}),s(B,[2,117],{10:[1,168]}),s(H,[2,183]),s(H,[2,170]),s(H,[2,171]),s(H,[2,172]),s(H,[2,173]),s(H,[2,174]),s(H,[2,175]),s(H,[2,176]),s(H,[2,177]),s(H,[2,178]),s(H,[2,179]),s(H,[2,180]),{44:C,47:169,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},{30:170,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:178,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:180,50:[1,179],67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:181,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:182,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:183,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{109:[1,184]},{30:185,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:186,65:[1,187],67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:188,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:189,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{30:190,67:S,80:j,81:K,82:171,116:D,117:x,118:T},s(q,[2,182]),s(u,[2,20]),s(nt,[2,25]),s(D1,[2,46],{39:191,18:192,10:p1,40:at}),s(w1,[2,73],{10:[1,193]}),{10:[1,194]},{30:195,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{77:[1,196],79:197,116:K1,119:Y1},s(R1,[2,79]),s(R1,[2,81]),s(R1,[2,82]),s(R1,[2,168]),s(R1,[2,169]),{76:198,79:120,80:ut,81:ot,116:K1,119:Y1},s(lt,[2,84]),{8:e1,9:s1,10:ct,11:i1,12:ht,14:dt,21:200,27:pt,29:[1,199],32:ft,44:gt,60:bt,75:At,83:140,84:kt,85:mt,86:Ct,87:St,88:Dt,89:xt,90:Tt,91:201,105:Et,109:yt,111:Ft,114:_t,115:Bt,116:vt},s(F,[2,101]),s(F,[2,103]),s(F,[2,104]),s(F,[2,157]),s(F,[2,158]),s(F,[2,159]),s(F,[2,160]),s(F,[2,161]),s(F,[2,162]),s(F,[2,163]),s(F,[2,164]),s(F,[2,165]),s(F,[2,166]),s(F,[2,167]),s(F,[2,90]),s(F,[2,91]),s(F,[2,92]),s(F,[2,93]),s(F,[2,94]),s(F,[2,95]),s(F,[2,96]),s(F,[2,97]),s(F,[2,98]),s(F,[2,99]),s(F,[2,100]),{6:11,7:12,8:f,9:c,10:A,11:r,20:17,22:18,23:19,24:20,25:21,26:22,27:k,32:[1,202],33:24,34:E,36:b,38:L,42:28,43:38,44:C,45:39,47:40,60:d,84:J,85:W,86:Z,87:A1,88:M1,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M,121:U1,122:W1,123:z1,124:j1},{10:p1,18:203},{44:[1,204]},s(k1,[2,43]),{10:[1,205],44:C,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:112,114:P,115:O,116:M},{10:[1,206]},{10:[1,207],106:[1,208]},s(It,[2,128]),{10:[1,209],44:C,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:112,114:P,115:O,116:M},{10:[1,210],44:C,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:112,114:P,115:O,116:M},{80:[1,211]},s(B,[2,109],{10:[1,212]}),s(B,[2,111],{10:[1,213]}),{80:[1,214]},s(H,[2,184]),{80:[1,215],98:[1,216]},s(k1,[2,55],{113:112,44:C,60:d,89:V,102:I,105:w,106:R,109:N,111:G,114:P,115:O,116:M}),{31:[1,217],67:S,82:218,116:D,117:x,118:T},s(f1,[2,86]),s(f1,[2,88]),s(f1,[2,89]),s(f1,[2,153]),s(f1,[2,154]),s(f1,[2,155]),s(f1,[2,156]),{49:[1,219],67:S,82:218,116:D,117:x,118:T},{30:220,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{51:[1,221],67:S,82:218,116:D,117:x,118:T},{53:[1,222],67:S,82:218,116:D,117:x,118:T},{55:[1,223],67:S,82:218,116:D,117:x,118:T},{57:[1,224],67:S,82:218,116:D,117:x,118:T},{60:[1,225]},{64:[1,226],67:S,82:218,116:D,117:x,118:T},{66:[1,227],67:S,82:218,116:D,117:x,118:T},{30:228,67:S,80:j,81:K,82:171,116:D,117:x,118:T},{31:[1,229],67:S,82:218,116:D,117:x,118:T},{67:S,69:[1,230],71:[1,231],82:218,116:D,117:x,118:T},{67:S,69:[1,233],71:[1,232],82:218,116:D,117:x,118:T},s(D1,[2,45],{18:155,10:p1,40:Vt}),s(D1,[2,47],{44:Lt}),s(w1,[2,75]),s(w1,[2,74]),{62:[1,234],67:S,82:218,116:D,117:x,118:T},s(w1,[2,77]),s(R1,[2,80]),{77:[1,235],79:197,116:K1,119:Y1},{30:236,67:S,80:j,81:K,82:171,116:D,117:x,118:T},s(Q1,o,{5:237}),s(F,[2,102]),s(y,[2,35]),{43:238,44:C,45:39,47:40,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},{10:p1,18:239},{10:r1,60:a1,84:n1,92:240,105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},{10:r1,60:a1,84:n1,92:251,104:[1,252],105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},{10:r1,60:a1,84:n1,92:253,104:[1,254],105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},{105:[1,255]},{10:r1,60:a1,84:n1,92:256,105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},{44:C,47:257,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},s(B,[2,108]),{80:[1,258]},{80:[1,259],98:[1,260]},s(B,[2,116]),s(B,[2,118],{10:[1,261]}),s(B,[2,119]),s(z,[2,56]),s(f1,[2,87]),s(z,[2,57]),{51:[1,262],67:S,82:218,116:D,117:x,118:T},s(z,[2,64]),s(z,[2,59]),s(z,[2,60]),s(z,[2,61]),{109:[1,263]},s(z,[2,63]),s(z,[2,65]),{66:[1,264],67:S,82:218,116:D,117:x,118:T},s(z,[2,67]),s(z,[2,68]),s(z,[2,70]),s(z,[2,69]),s(z,[2,71]),s([10,44,60,89,102,105,106,109,111,114,115,116],[2,85]),s(w1,[2,78]),{31:[1,265],67:S,82:218,116:D,117:x,118:T},{6:11,7:12,8:f,9:c,10:A,11:r,20:17,22:18,23:19,24:20,25:21,26:22,27:k,32:[1,266],33:24,34:E,36:b,38:L,42:28,43:38,44:C,45:39,47:40,60:d,84:J,85:W,86:Z,87:A1,88:M1,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M,121:U1,122:W1,123:z1,124:j1},s(k1,[2,53]),{43:267,44:C,45:39,47:40,60:d,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M},s(B,[2,121],{106:N1}),s(wt,[2,130],{108:269,10:r1,60:a1,84:n1,105:u1,109:o1,110:l1,111:c1,112:h1}),s($,[2,132]),s($,[2,134]),s($,[2,135]),s($,[2,136]),s($,[2,137]),s($,[2,138]),s($,[2,139]),s($,[2,140]),s($,[2,141]),s(B,[2,122],{106:N1}),{10:[1,270]},s(B,[2,123],{106:N1}),{10:[1,271]},s(It,[2,129]),s(B,[2,105],{106:N1}),s(B,[2,106],{113:112,44:C,60:d,89:V,102:I,105:w,106:R,109:N,111:G,114:P,115:O,116:M}),s(B,[2,110]),s(B,[2,112],{10:[1,272]}),s(B,[2,113]),{98:[1,273]},{51:[1,274]},{62:[1,275]},{66:[1,276]},{8:e1,9:s1,11:i1,21:277},s(y,[2,34]),s(k1,[2,52]),{10:r1,60:a1,84:n1,105:u1,107:278,108:242,109:o1,110:l1,111:c1,112:h1},s($,[2,133]),{14:x1,44:T1,60:E1,89:y1,101:279,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1,120:87},{14:x1,44:T1,60:E1,89:y1,101:280,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1,120:87},{98:[1,281]},s(B,[2,120]),s(z,[2,58]),{30:282,67:S,80:j,81:K,82:171,116:D,117:x,118:T},s(z,[2,66]),s(Q1,o,{5:283}),s(wt,[2,131],{108:269,10:r1,60:a1,84:n1,105:u1,109:o1,110:l1,111:c1,112:h1}),s(B,[2,126],{120:167,10:[1,284],14:x1,44:T1,60:E1,89:y1,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1}),s(B,[2,127],{120:167,10:[1,285],14:x1,44:T1,60:E1,89:y1,105:F1,106:_1,109:B1,111:v1,114:L1,115:V1,116:I1}),s(B,[2,114]),{31:[1,286],67:S,82:218,116:D,117:x,118:T},{6:11,7:12,8:f,9:c,10:A,11:r,20:17,22:18,23:19,24:20,25:21,26:22,27:k,32:[1,287],33:24,34:E,36:b,38:L,42:28,43:38,44:C,45:39,47:40,60:d,84:J,85:W,86:Z,87:A1,88:M1,89:V,102:I,105:w,106:R,109:N,111:G,113:41,114:P,115:O,116:M,121:U1,122:W1,123:z1,124:j1},{10:r1,60:a1,84:n1,92:288,105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},{10:r1,60:a1,84:n1,92:289,105:u1,107:241,108:242,109:o1,110:l1,111:c1,112:h1},s(z,[2,62]),s(y,[2,33]),s(B,[2,124],{106:N1}),s(B,[2,125],{106:N1})],defaultActions:{},parseError:m(function(h,p){if(p.recoverable)this.trace(h);else{var g=new Error(h);throw g.hash=p,g}},"parseError"),parse:m(function(h){var p=this,g=[0],l=[],_=[null],t=[],O1=this.table,e="",v=0,Rt=0,zt=2,Nt=1,jt=t.slice.call(arguments,1),U=Object.create(this.lexer),m1={yy:{}};for(var Z1 in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Z1)&&(m1.yy[Z1]=this.yy[Z1]);U.setInput(h,m1.yy),m1.yy.lexer=U,m1.yy.parser=this,typeof U.yylloc>"u"&&(U.yylloc={});var $1=U.yylloc;t.push($1);var Kt=U.options&&U.options.ranges;typeof m1.yy.parseError=="function"?this.parseError=m1.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Yt(X){g.length=g.length-2*X,_.length=_.length-X,t.length=t.length-X}m(Yt,"popStack");function Gt(){var X;return X=l.pop()||U.lex()||Nt,typeof X!="number"&&(X instanceof Array&&(l=X,X=l.pop()),X=p.symbols_[X]||X),X}m(Gt,"lex");for(var Y,C1,Q,tt,G1={},H1,d1,Pt,X1;;){if(C1=g[g.length-1],this.defaultActions[C1]?Q=this.defaultActions[C1]:((Y===null||typeof Y>"u")&&(Y=Gt()),Q=O1[C1]&&O1[C1][Y]),typeof Q>"u"||!Q.length||!Q[0]){var et="";X1=[];for(H1 in O1[C1])this.terminals_[H1]&&H1>zt&&X1.push("'"+this.terminals_[H1]+"'");U.showPosition?et="Parse error on line "+(v+1)+`: +`+U.showPosition()+` +Expecting `+X1.join(", ")+", got '"+(this.terminals_[Y]||Y)+"'":et="Parse error on line "+(v+1)+": Unexpected "+(Y==Nt?"end of input":"'"+(this.terminals_[Y]||Y)+"'"),this.parseError(et,{text:U.match,token:this.terminals_[Y]||Y,line:U.yylineno,loc:$1,expected:X1})}if(Q[0]instanceof Array&&Q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+C1+", token: "+Y);switch(Q[0]){case 1:g.push(Y),_.push(U.yytext),t.push(U.yylloc),g.push(Q[1]),Y=null,Rt=U.yyleng,e=U.yytext,v=U.yylineno,$1=U.yylloc;break;case 2:if(d1=this.productions_[Q[1]][1],G1.$=_[_.length-d1],G1._$={first_line:t[t.length-(d1||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(d1||1)].first_column,last_column:t[t.length-1].last_column},Kt&&(G1._$.range=[t[t.length-(d1||1)].range[0],t[t.length-1].range[1]]),tt=this.performAction.apply(G1,[e,Rt,v,m1.yy,Q[1],_,t].concat(jt)),typeof tt<"u")return tt;d1&&(g=g.slice(0,-1*d1*2),_=_.slice(0,-1*d1),t=t.slice(0,-1*d1)),g.push(this.productions_[Q[1]][0]),_.push(G1.$),t.push(G1._$),Pt=O1[g[g.length-2]][g[g.length-1]],g.push(Pt);break;case 3:return!0}}return!0},"parse")},Wt=function(){var g1={EOF:1,parseError:m(function(p,g){if(this.yy.parser)this.yy.parser.parseError(p,g);else throw new Error(p)},"parseError"),setInput:m(function(h,p){return this.yy=p||this.yy||{},this._input=h,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:m(function(){var h=this._input[0];this.yytext+=h,this.yyleng++,this.offset++,this.match+=h,this.matched+=h;var p=h.match(/(?:\r\n?|\n).*/g);return p?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),h},"input"),unput:m(function(h){var p=h.length,g=h.split(/(?:\r\n?|\n)/g);this._input=h+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-p),this.offset-=p;var l=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var _=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===l.length?this.yylloc.first_column:0)+l[l.length-g.length].length-g[0].length:this.yylloc.first_column-p},this.options.ranges&&(this.yylloc.range=[_[0],_[0]+this.yyleng-p]),this.yyleng=this.yytext.length,this},"unput"),more:m(function(){return this._more=!0,this},"more"),reject:m(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:m(function(h){this.unput(this.match.slice(h))},"less"),pastInput:m(function(){var h=this.matched.substr(0,this.matched.length-this.match.length);return(h.length>20?"...":"")+h.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:m(function(){var h=this.match;return h.length<20&&(h+=this._input.substr(0,20-h.length)),(h.substr(0,20)+(h.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:m(function(){var h=this.pastInput(),p=new Array(h.length+1).join("-");return h+this.upcomingInput()+` +`+p+"^"},"showPosition"),test_match:m(function(h,p){var g,l,_;if(this.options.backtrack_lexer&&(_={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(_.yylloc.range=this.yylloc.range.slice(0))),l=h[0].match(/(?:\r\n?|\n).*/g),l&&(this.yylineno+=l.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:l?l[l.length-1].length-l[l.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+h[0].length},this.yytext+=h[0],this.match+=h[0],this.matches=h,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(h[0].length),this.matched+=h[0],g=this.performAction.call(this,this.yy,this,p,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),g)return g;if(this._backtrack){for(var t in _)this[t]=_[t];return!1}return!1},"test_match"),next:m(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var h,p,g,l;this._more||(this.yytext="",this.match="");for(var _=this._currentRules(),t=0;t<_.length;t++)if(g=this._input.match(this.rules[_[t]]),g&&(!p||g[0].length>p[0].length)){if(p=g,l=t,this.options.backtrack_lexer){if(h=this.test_match(g,_[t]),h!==!1)return h;if(this._backtrack){p=!1;continue}else return!1}else if(!this.options.flex)break}return p?(h=this.test_match(p,_[l]),h!==!1?h:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:m(function(){var p=this.next();return p||this.lex()},"lex"),begin:m(function(p){this.conditionStack.push(p)},"begin"),popState:m(function(){var p=this.conditionStack.length-1;return p>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:m(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:m(function(p){return p=this.conditionStack.length-1-Math.abs(p||0),p>=0?this.conditionStack[p]:"INITIAL"},"topState"),pushState:m(function(p){this.begin(p)},"pushState"),stateStackSize:m(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:m(function(p,g,l,_){switch(l){case 0:return this.begin("acc_title"),34;case 1:return this.popState(),"acc_title_value";case 2:return this.begin("acc_descr"),36;case 3:return this.popState(),"acc_descr_value";case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:return this.pushState("shapeData"),g.yytext="",40;case 8:return this.pushState("shapeDataStr"),40;case 9:return this.popState(),40;case 10:const t=/\n\s*/g;return g.yytext=g.yytext.replace(t,"
"),40;case 11:return 40;case 12:this.popState();break;case 13:this.begin("callbackname");break;case 14:this.popState();break;case 15:this.popState(),this.begin("callbackargs");break;case 16:return 95;case 17:this.popState();break;case 18:return 96;case 19:return"MD_STR";case 20:this.popState();break;case 21:this.begin("md_string");break;case 22:return"STR";case 23:this.popState();break;case 24:this.pushState("string");break;case 25:return 84;case 26:return 102;case 27:return 85;case 28:return 104;case 29:return 86;case 30:return 87;case 31:return 97;case 32:this.begin("click");break;case 33:this.popState();break;case 34:return 88;case 35:return p.lex.firstGraph()&&this.begin("dir"),12;case 36:return p.lex.firstGraph()&&this.begin("dir"),12;case 37:return p.lex.firstGraph()&&this.begin("dir"),12;case 38:return 27;case 39:return 32;case 40:return 98;case 41:return 98;case 42:return 98;case 43:return 98;case 44:return this.popState(),13;case 45:return this.popState(),14;case 46:return this.popState(),14;case 47:return this.popState(),14;case 48:return this.popState(),14;case 49:return this.popState(),14;case 50:return this.popState(),14;case 51:return this.popState(),14;case 52:return this.popState(),14;case 53:return this.popState(),14;case 54:return this.popState(),14;case 55:return 121;case 56:return 122;case 57:return 123;case 58:return 124;case 59:return 78;case 60:return 105;case 61:return 111;case 62:return 46;case 63:return 60;case 64:return 44;case 65:return 8;case 66:return 106;case 67:return 115;case 68:return this.popState(),77;case 69:return this.pushState("edgeText"),75;case 70:return 119;case 71:return this.popState(),77;case 72:return this.pushState("thickEdgeText"),75;case 73:return 119;case 74:return this.popState(),77;case 75:return this.pushState("dottedEdgeText"),75;case 76:return 119;case 77:return 77;case 78:return this.popState(),53;case 79:return"TEXT";case 80:return this.pushState("ellipseText"),52;case 81:return this.popState(),55;case 82:return this.pushState("text"),54;case 83:return this.popState(),57;case 84:return this.pushState("text"),56;case 85:return 58;case 86:return this.pushState("text"),67;case 87:return this.popState(),64;case 88:return this.pushState("text"),63;case 89:return this.popState(),49;case 90:return this.pushState("text"),48;case 91:return this.popState(),69;case 92:return this.popState(),71;case 93:return 117;case 94:return this.pushState("trapText"),68;case 95:return this.pushState("trapText"),70;case 96:return 118;case 97:return 67;case 98:return 90;case 99:return"SEP";case 100:return 89;case 101:return 115;case 102:return 111;case 103:return 44;case 104:return 109;case 105:return 114;case 106:return 116;case 107:return this.popState(),62;case 108:return this.pushState("text"),62;case 109:return this.popState(),51;case 110:return this.pushState("text"),50;case 111:return this.popState(),31;case 112:return this.pushState("text"),29;case 113:return this.popState(),66;case 114:return this.pushState("text"),65;case 115:return"TEXT";case 116:return"QUOTE";case 117:return 9;case 118:return 10;case 119:return 11}},"anonymous"),rules:[/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:@\{)/,/^(?:["])/,/^(?:["])/,/^(?:[^\"]+)/,/^(?:[^}^"]+)/,/^(?:\})/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["][`])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:["])/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s])/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:flowchart-elk\b)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[^\s\"]+@(?=[^\{\"]))/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:[^-]|-(?!-)+)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:[^=]|=(?!))/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:[^\.]|\.(?!))/,/^(?:\s*~~[\~]+\s*)/,/^(?:[-/\)][\)])/,/^(?:[^\(\)\[\]\{\}]|!\)+)/,/^(?:\(-)/,/^(?:\]\))/,/^(?:\(\[)/,/^(?:\]\])/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:>)/,/^(?:\)\])/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\(\(\()/,/^(?:[\\(?=\])][\]])/,/^(?:\/(?=\])\])/,/^(?:\/(?!\])|\\(?!\])|[^\\\[\]\(\)\{\}\/]+)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:\*)/,/^(?:#)/,/^(?:&)/,/^(?:([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|-(?=[^\>\-\.])|(?!))+)/,/^(?:-)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\|)/,/^(?:\))/,/^(?:\()/,/^(?:\])/,/^(?:\[)/,/^(?:(\}))/,/^(?:\{)/,/^(?:[^\[\]\(\)\{\}\|\"]+)/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{shapeDataEndBracket:{rules:[21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},shapeDataStr:{rules:[9,10,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},shapeData:{rules:[8,11,12,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},callbackargs:{rules:[17,18,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},callbackname:{rules:[14,15,16,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},href:{rules:[21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},click:{rules:[21,24,33,34,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},dottedEdgeText:{rules:[21,24,74,76,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},thickEdgeText:{rules:[21,24,71,73,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},edgeText:{rules:[21,24,68,70,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},trapText:{rules:[21,24,77,80,82,84,88,90,91,92,93,94,95,108,110,112,114],inclusive:!1},ellipseText:{rules:[21,24,77,78,79,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},text:{rules:[21,24,77,80,81,82,83,84,87,88,89,90,94,95,107,108,109,110,111,112,113,114,115],inclusive:!1},vertex:{rules:[21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},dir:{rules:[21,24,44,45,46,47,48,49,50,51,52,53,54,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},acc_descr_multiline:{rules:[5,6,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},acc_descr:{rules:[3,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},acc_title:{rules:[1,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},md_string:{rules:[19,20,21,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},string:{rules:[21,22,23,24,77,80,82,84,88,90,94,95,108,110,112,114],inclusive:!1},INITIAL:{rules:[0,2,4,7,13,21,24,25,26,27,28,29,30,31,32,35,36,37,38,39,40,41,42,43,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,71,72,74,75,77,80,82,84,85,86,88,90,94,95,96,97,98,99,100,101,102,103,104,105,106,108,110,112,114,116,117,118,119],inclusive:!0}}};return g1}();J1.lexer=Wt;function q1(){this.yy={}}return m(q1,"Parser"),q1.prototype=J1,J1.Parser=q1,new q1}();rt.parser=rt;var Mt=rt,Ut=Object.assign({},Mt);Ut.parse=s=>{const i=s.replace(/}\s*\n/g,`} +`);return Mt.parse(i)};var Ae=Ut,ke=m((s,i)=>{const a=oe,n=a(s,"r"),u=a(s,"g"),o=a(s,"b");return le(n,u,o,i)},"fade"),me=m(s=>`.label { + font-family: ${s.fontFamily}; + color: ${s.nodeTextColor||s.textColor}; + } + .cluster-label text { + fill: ${s.titleColor}; + } + .cluster-label span { + color: ${s.titleColor}; + } + .cluster-label span p { + background-color: transparent; + } + + .label text,span { + fill: ${s.nodeTextColor||s.textColor}; + color: ${s.nodeTextColor||s.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${s.mainBkg}; + stroke: ${s.nodeBorder}; + stroke-width: 1px; + } + .rough-node .label text , .node .label text, .image-shape .label, .icon-shape .label { + text-anchor: middle; + } + // .flowchart-label .text-outer-tspan { + // text-anchor: middle; + // } + // .flowchart-label .text-inner-tspan { + // text-anchor: start; + // } + + .node .katex path { + fill: #000; + stroke: #000; + stroke-width: 1px; + } + + .rough-node .label,.node .label, .image-shape .label, .icon-shape .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + + .root .anchor path { + fill: ${s.lineColor} !important; + stroke-width: 0; + stroke: ${s.lineColor}; + } + + .arrowheadPath { + fill: ${s.arrowheadColor}; + } + + .edgePath .path { + stroke: ${s.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${s.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${s.edgeLabelBackground}; + p { + background-color: ${s.edgeLabelBackground}; + } + rect { + opacity: 0.5; + background-color: ${s.edgeLabelBackground}; + fill: ${s.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${ke(s.edgeLabelBackground,.5)}; + // background-color: + } + + .cluster rect { + fill: ${s.clusterBkg}; + stroke: ${s.clusterBorder}; + stroke-width: 1px; + } + + .cluster text { + fill: ${s.titleColor}; + } + + .cluster span { + color: ${s.titleColor}; + } + /* .cluster div { + color: ${s.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${s.fontFamily}; + font-size: 12px; + background: ${s.tertiaryColor}; + border: 1px solid ${s.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${s.textColor}; + } + + rect.text { + fill: none; + stroke-width: 0; + } + + .icon-shape, .image-shape { + background-color: ${s.edgeLabelBackground}; + p { + background-color: ${s.edgeLabelBackground}; + padding: 2px; + } + rect { + opacity: 0.5; + background-color: ${s.edgeLabelBackground}; + fill: ${s.edgeLabelBackground}; + } + text-align: center; + } + ${qt()} +`,"getStyles"),Ce=me,ye={parser:Ae,get db(){return new pe},renderer:be,styles:Ce,init:m(s=>{s.flowchart||(s.flowchart={}),s.layout&&Ot({layout:s.layout}),s.flowchart.arrowMarkerAbsolute=s.arrowMarkerAbsolute,Ot({flowchart:{arrowMarkerAbsolute:s.arrowMarkerAbsolute}})},"init")};export{ye as diagram}; diff --git a/assets/chunks/framework.CgT1UzWm.js b/assets/chunks/framework.CgT1UzWm.js new file mode 100644 index 000000000..377819919 --- /dev/null +++ b/assets/chunks/framework.CgT1UzWm.js @@ -0,0 +1,18 @@ +/** +* @vue/shared v3.5.18 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function js(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const ee={},Ot=[],Be=()=>{},Qo=()=>!1,sn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Vs=e=>e.startsWith("onUpdate:"),ue=Object.assign,ks=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Zo=Object.prototype.hasOwnProperty,Q=(e,t)=>Zo.call(e,t),K=Array.isArray,Pt=e=>Dn(e)==="[object Map]",di=e=>Dn(e)==="[object Set]",q=e=>typeof e=="function",le=e=>typeof e=="string",Ze=e=>typeof e=="symbol",se=e=>e!==null&&typeof e=="object",hi=e=>(se(e)||q(e))&&q(e.then)&&q(e.catch),pi=Object.prototype.toString,Dn=e=>pi.call(e),el=e=>Dn(e).slice(8,-1),gi=e=>Dn(e)==="[object Object]",Us=e=>le(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Lt=js(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),$n=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},tl=/-(\w)/g,Ne=$n(e=>e.replace(tl,(t,n)=>n?n.toUpperCase():"")),nl=/\B([A-Z])/g,at=$n(e=>e.replace(nl,"-$1").toLowerCase()),jn=$n(e=>e.charAt(0).toUpperCase()+e.slice(1)),Sn=$n(e=>e?`on${jn(e)}`:""),it=(e,t)=>!Object.is(e,t),Tn=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},xs=e=>{const t=parseFloat(e);return isNaN(t)?e:t},sl=e=>{const t=le(e)?Number(e):NaN;return isNaN(t)?e:t};let gr;const Vn=()=>gr||(gr=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Ws(e){if(K(e)){const t={};for(let n=0;n{if(n){const s=n.split(il);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function Bs(e){let t="";if(le(e))t=e;else if(K(e))for(let n=0;n!!(e&&e.__v_isRef===!0),fl=e=>le(e)?e:e==null?"":K(e)||se(e)&&(e.toString===pi||!q(e.toString))?vi(e)?fl(e.value):JSON.stringify(e,yi,2):String(e),yi=(e,t)=>vi(t)?yi(e,t.value):Pt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[es(s,i)+" =>"]=r,n),{})}:di(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>es(n))}:Ze(t)?es(t):se(t)&&!K(t)&&!gi(t)?String(t):t,es=(e,t="")=>{var n;return Ze(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.5.18 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let ve;class ul{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.parent=ve,!t&&ve&&(this.index=(ve.scopes||(ve.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t0&&--this._on===0&&(ve=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let n,s;for(n=0,s=this.effects.length;n0)return;if(Bt){let t=Bt;for(Bt=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Wt;){let t=Wt;for(Wt=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(s){e||(e=s)}t=n}}if(e)throw e}function Ti(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function xi(e){let t,n=e.depsTail,s=n;for(;s;){const r=s.prevDep;s.version===-1?(s===n&&(n=r),Gs(s),hl(s)):t=s,s.dep.activeLink=s.prevActiveLink,s.prevActiveLink=void 0,s=r}e.deps=t,e.depsTail=n}function Es(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Ei(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Ei(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Jt)||(e.globalVersion=Jt,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Es(e))))return;e.flags|=2;const t=e.dep,n=ne,s=He;ne=e,He=!0;try{Ti(e);const r=e.fn(e._value);(t.version===0||it(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{ne=n,He=s,xi(e),e.flags&=-3}}function Gs(e,t=!1){const{dep:n,prevSub:s,nextSub:r}=e;if(s&&(s.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=s,e.nextSub=void 0),n.subs===e&&(n.subs=s,!s&&n.computed)){n.computed.flags&=-5;for(let i=n.computed.deps;i;i=i.nextDep)Gs(i,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function hl(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let He=!0;const Ci=[];function Je(){Ci.push(He),He=!1}function ze(){const e=Ci.pop();He=e===void 0?!0:e}function mr(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=ne;ne=void 0;try{t()}finally{ne=n}}}let Jt=0;class pl{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class kn{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!ne||!He||ne===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==ne)n=this.activeLink=new pl(ne,this),ne.deps?(n.prevDep=ne.depsTail,ne.depsTail.nextDep=n,ne.depsTail=n):ne.deps=ne.depsTail=n,Ai(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const s=n.nextDep;s.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=s),n.prevDep=ne.depsTail,n.nextDep=void 0,ne.depsTail.nextDep=n,ne.depsTail=n,ne.deps===n&&(ne.deps=s)}return n}trigger(t){this.version++,Jt++,this.notify(t)}notify(t){Ks();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{qs()}}}function Ai(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let s=t.deps;s;s=s.nextDep)Ai(s)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const Mn=new WeakMap,mt=Symbol(""),Cs=Symbol(""),zt=Symbol("");function _e(e,t,n){if(He&&ne){let s=Mn.get(e);s||Mn.set(e,s=new Map);let r=s.get(n);r||(s.set(n,r=new kn),r.map=s,r.key=n),r.track()}}function Xe(e,t,n,s,r,i){const o=Mn.get(e);if(!o){Jt++;return}const l=c=>{c&&c.trigger()};if(Ks(),t==="clear")o.forEach(l);else{const c=K(e),f=c&&Us(n);if(c&&n==="length"){const a=Number(s);o.forEach((d,m)=>{(m==="length"||m===zt||!Ze(m)&&m>=a)&&l(d)})}else switch((n!==void 0||o.has(void 0))&&l(o.get(n)),f&&l(o.get(zt)),t){case"add":c?f&&l(o.get("length")):(l(o.get(mt)),Pt(e)&&l(o.get(Cs)));break;case"delete":c||(l(o.get(mt)),Pt(e)&&l(o.get(Cs)));break;case"set":Pt(e)&&l(o.get(mt));break}}qs()}function gl(e,t){const n=Mn.get(e);return n&&n.get(t)}function Et(e){const t=z(e);return t===e?t:(_e(t,"iterate",zt),Le(e)?t:t.map(de))}function Un(e){return _e(e=z(e),"iterate",zt),e}const ml={__proto__:null,[Symbol.iterator](){return ns(this,Symbol.iterator,de)},concat(...e){return Et(this).concat(...e.map(t=>K(t)?Et(t):t))},entries(){return ns(this,"entries",e=>(e[1]=de(e[1]),e))},every(e,t){return Ke(this,"every",e,t,void 0,arguments)},filter(e,t){return Ke(this,"filter",e,t,n=>n.map(de),arguments)},find(e,t){return Ke(this,"find",e,t,de,arguments)},findIndex(e,t){return Ke(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return Ke(this,"findLast",e,t,de,arguments)},findLastIndex(e,t){return Ke(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return Ke(this,"forEach",e,t,void 0,arguments)},includes(...e){return ss(this,"includes",e)},indexOf(...e){return ss(this,"indexOf",e)},join(e){return Et(this).join(e)},lastIndexOf(...e){return ss(this,"lastIndexOf",e)},map(e,t){return Ke(this,"map",e,t,void 0,arguments)},pop(){return Vt(this,"pop")},push(...e){return Vt(this,"push",e)},reduce(e,...t){return vr(this,"reduce",e,t)},reduceRight(e,...t){return vr(this,"reduceRight",e,t)},shift(){return Vt(this,"shift")},some(e,t){return Ke(this,"some",e,t,void 0,arguments)},splice(...e){return Vt(this,"splice",e)},toReversed(){return Et(this).toReversed()},toSorted(e){return Et(this).toSorted(e)},toSpliced(...e){return Et(this).toSpliced(...e)},unshift(...e){return Vt(this,"unshift",e)},values(){return ns(this,"values",de)}};function ns(e,t,n){const s=Un(e),r=s[t]();return s!==e&&!Le(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.value&&(i.value=n(i.value)),i}),r}const vl=Array.prototype;function Ke(e,t,n,s,r,i){const o=Un(e),l=o!==e&&!Le(e),c=o[t];if(c!==vl[t]){const d=c.apply(e,i);return l?de(d):d}let f=n;o!==e&&(l?f=function(d,m){return n.call(this,de(d),m,e)}:n.length>2&&(f=function(d,m){return n.call(this,d,m,e)}));const a=c.call(o,f,s);return l&&r?r(a):a}function vr(e,t,n,s){const r=Un(e);let i=n;return r!==e&&(Le(e)?n.length>3&&(i=function(o,l,c){return n.call(this,o,l,c,e)}):i=function(o,l,c){return n.call(this,o,de(l),c,e)}),r[t](i,...s)}function ss(e,t,n){const s=z(e);_e(s,"iterate",zt);const r=s[t](...n);return(r===-1||r===!1)&&Js(n[0])?(n[0]=z(n[0]),s[t](...n)):r}function Vt(e,t,n=[]){Je(),Ks();const s=z(e)[t].apply(e,n);return qs(),ze(),s}const yl=js("__proto__,__v_isRef,__isVue"),Ri=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ze));function _l(e){Ze(e)||(e=String(e));const t=z(this);return _e(t,"has",e),t.hasOwnProperty(e)}class Mi{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){if(n==="__v_skip")return t.__v_skip;const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?Ml:Ii:i?Li:Pi).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=K(t);if(!r){let c;if(o&&(c=ml[n]))return c;if(n==="hasOwnProperty")return _l}const l=Reflect.get(t,n,fe(t)?t:s);return(Ze(n)?Ri.has(n):yl(n))||(r||_e(t,"get",n),i)?l:fe(l)?o&&Us(n)?l:l.value:se(l)?r?Wn(l):Ft(l):l}}class Oi extends Mi{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];if(!this._isShallow){const c=ot(i);if(!Le(s)&&!ot(s)&&(i=z(i),s=z(s)),!K(t)&&fe(i)&&!fe(s))return c?!1:(i.value=s,!0)}const o=K(t)&&Us(n)?Number(n)e,un=e=>Reflect.getPrototypeOf(e);function xl(e,t,n){return function(...s){const r=this.__v_raw,i=z(r),o=Pt(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,f=r[e](...s),a=n?As:t?On:de;return!t&&_e(i,"iterate",c?Cs:mt),{next(){const{value:d,done:m}=f.next();return m?{value:d,done:m}:{value:l?[a(d[0]),a(d[1])]:a(d),done:m}},[Symbol.iterator](){return this}}}}function dn(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function El(e,t){const n={get(r){const i=this.__v_raw,o=z(i),l=z(r);e||(it(r,l)&&_e(o,"get",r),_e(o,"get",l));const{has:c}=un(o),f=t?As:e?On:de;if(c.call(o,r))return f(i.get(r));if(c.call(o,l))return f(i.get(l));i!==o&&i.get(r)},get size(){const r=this.__v_raw;return!e&&_e(z(r),"iterate",mt),Reflect.get(r,"size",r)},has(r){const i=this.__v_raw,o=z(i),l=z(r);return e||(it(r,l)&&_e(o,"has",r),_e(o,"has",l)),r===l?i.has(r):i.has(r)||i.has(l)},forEach(r,i){const o=this,l=o.__v_raw,c=z(l),f=t?As:e?On:de;return!e&&_e(c,"iterate",mt),l.forEach((a,d)=>r.call(i,f(a),f(d),o))}};return ue(n,e?{add:dn("add"),set:dn("set"),delete:dn("delete"),clear:dn("clear")}:{add(r){!t&&!Le(r)&&!ot(r)&&(r=z(r));const i=z(this);return un(i).has.call(i,r)||(i.add(r),Xe(i,"add",r,r)),this},set(r,i){!t&&!Le(i)&&!ot(i)&&(i=z(i));const o=z(this),{has:l,get:c}=un(o);let f=l.call(o,r);f||(r=z(r),f=l.call(o,r));const a=c.call(o,r);return o.set(r,i),f?it(i,a)&&Xe(o,"set",r,i):Xe(o,"add",r,i),this},delete(r){const i=z(this),{has:o,get:l}=un(i);let c=o.call(i,r);c||(r=z(r),c=o.call(i,r)),l&&l.call(i,r);const f=i.delete(r);return c&&Xe(i,"delete",r,void 0),f},clear(){const r=z(this),i=r.size!==0,o=r.clear();return i&&Xe(r,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=xl(r,e,t)}),n}function Xs(e,t){const n=El(e,t);return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(Q(n,r)&&r in s?n:s,r,i)}const Cl={get:Xs(!1,!1)},Al={get:Xs(!1,!0)},Rl={get:Xs(!0,!1)};const Pi=new WeakMap,Li=new WeakMap,Ii=new WeakMap,Ml=new WeakMap;function Ol(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Pl(e){return e.__v_skip||!Object.isExtensible(e)?0:Ol(el(e))}function Ft(e){return ot(e)?e:Ys(e,!1,wl,Cl,Pi)}function Ll(e){return Ys(e,!1,Tl,Al,Li)}function Wn(e){return Ys(e,!0,Sl,Rl,Ii)}function Ys(e,t,n,s,r){if(!se(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=Pl(e);if(i===0)return e;const o=r.get(e);if(o)return o;const l=new Proxy(e,i===2?s:n);return r.set(e,l),l}function vt(e){return ot(e)?vt(e.__v_raw):!!(e&&e.__v_isReactive)}function ot(e){return!!(e&&e.__v_isReadonly)}function Le(e){return!!(e&&e.__v_isShallow)}function Js(e){return e?!!e.__v_raw:!1}function z(e){const t=e&&e.__v_raw;return t?z(t):e}function xn(e){return!Q(e,"__v_skip")&&Object.isExtensible(e)&&Ts(e,"__v_skip",!0),e}const de=e=>se(e)?Ft(e):e,On=e=>se(e)?Wn(e):e;function fe(e){return e?e.__v_isRef===!0:!1}function De(e){return Ni(e,!1)}function Ee(e){return Ni(e,!0)}function Ni(e,t){return fe(e)?e:new Il(e,t)}class Il{constructor(t,n){this.dep=new kn,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:z(t),this._value=n?t:de(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,s=this.__v_isShallow||Le(t)||ot(t);t=s?t:z(t),it(t,n)&&(this._rawValue=t,this._value=s?t:de(t),this.dep.trigger())}}function zs(e){return fe(e)?e.value:e}function ce(e){return q(e)?e():zs(e)}const Nl={get:(e,t,n)=>t==="__v_raw"?e:zs(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return fe(r)&&!fe(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function Fi(e){return vt(e)?e:new Proxy(e,Nl)}class Fl{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new kn,{get:s,set:r}=t(n.track.bind(n),n.trigger.bind(n));this._get=s,this._set=r}get value(){return this._value=this._get()}set value(t){this._set(t)}}function Hl(e){return new Fl(e)}class Dl{constructor(t,n,s){this._object=t,this._key=n,this._defaultValue=s,this.__v_isRef=!0,this._value=void 0}get value(){const t=this._object[this._key];return this._value=t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return gl(z(this._object),this._key)}}class $l{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function jl(e,t,n){return fe(e)?e:q(e)?new $l(e):se(e)&&arguments.length>1?Vl(e,t,n):De(e)}function Vl(e,t,n){const s=e[t];return fe(s)?s:new Dl(e,t,n)}class kl{constructor(t,n,s){this.fn=t,this.setter=n,this._value=void 0,this.dep=new kn(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Jt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=s}notify(){if(this.flags|=16,!(this.flags&8)&&ne!==this)return Si(this,!0),!0}get value(){const t=this.dep.track();return Ei(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Ul(e,t,n=!1){let s,r;return q(e)?s=e:(s=e.get,r=e.set),new kl(s,r,n)}const hn={},Pn=new WeakMap;let pt;function Wl(e,t=!1,n=pt){if(n){let s=Pn.get(n);s||Pn.set(n,s=[]),s.push(e)}}function Bl(e,t,n=ee){const{immediate:s,deep:r,once:i,scheduler:o,augmentJob:l,call:c}=n,f=g=>r?g:Le(g)||r===!1||r===0?Ye(g,1):Ye(g);let a,d,m,v,b=!1,_=!1;if(fe(e)?(d=()=>e.value,b=Le(e)):vt(e)?(d=()=>f(e),b=!0):K(e)?(_=!0,b=e.some(g=>vt(g)||Le(g)),d=()=>e.map(g=>{if(fe(g))return g.value;if(vt(g))return f(g);if(q(g))return c?c(g,2):g()})):q(e)?t?d=c?()=>c(e,2):e:d=()=>{if(m){Je();try{m()}finally{ze()}}const g=pt;pt=a;try{return c?c(e,3,[v]):e(v)}finally{pt=g}}:d=Be,t&&r){const g=d,R=r===!0?1/0:r;d=()=>Ye(g(),R)}const U=_i(),L=()=>{a.stop(),U&&U.active&&ks(U.effects,a)};if(i&&t){const g=t;t=(...R)=>{g(...R),L()}}let D=_?new Array(e.length).fill(hn):hn;const p=g=>{if(!(!(a.flags&1)||!a.dirty&&!g))if(t){const R=a.run();if(r||b||(_?R.some(($,M)=>it($,D[M])):it(R,D))){m&&m();const $=pt;pt=a;try{const M=[R,D===hn?void 0:_&&D[0]===hn?[]:D,v];D=R,c?c(t,3,M):t(...M)}finally{pt=$}}}else a.run()};return l&&l(p),a=new bi(d),a.scheduler=o?()=>o(p,!1):p,v=g=>Wl(g,!1,a),m=a.onStop=()=>{const g=Pn.get(a);if(g){if(c)c(g,4);else for(const R of g)R();Pn.delete(a)}},t?s?p(!0):D=a.run():o?o(p.bind(null,!0),!0):a.run(),L.pause=a.pause.bind(a),L.resume=a.resume.bind(a),L.stop=L,L}function Ye(e,t=1/0,n){if(t<=0||!se(e)||e.__v_skip||(n=n||new Set,n.has(e)))return e;if(n.add(e),t--,fe(e))Ye(e.value,t,n);else if(K(e))for(let s=0;s{Ye(s,t,n)});else if(gi(e)){for(const s in e)Ye(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ye(e[s],t,n)}return e}/** +* @vue/runtime-core v3.5.18 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function rn(e,t,n,s){try{return s?e(...s):e()}catch(r){on(r,t,n)}}function $e(e,t,n,s){if(q(e)){const r=rn(e,t,n,s);return r&&hi(r)&&r.catch(i=>{on(i,t,n)}),r}if(K(e)){const r=[];for(let i=0;i>>1,r=Se[s],i=Qt(r);i=Qt(n)?Se.push(e):Se.splice(ql(t),0,e),e.flags|=1,Di()}}function Di(){Ln||(Ln=Hi.then($i))}function Gl(e){K(e)?It.push(...e):nt&&e.id===-1?nt.splice(At+1,0,e):e.flags&1||(It.push(e),e.flags|=1),Di()}function yr(e,t,n=Ue+1){for(;nQt(n)-Qt(s));if(It.length=0,nt){nt.push(...t);return}for(nt=t,At=0;Ate.id==null?e.flags&2?-1:1/0:e.id;function $i(e){try{for(Ue=0;Ue{s._d&&Ir(-1);const i=Nn(t);let o;try{o=e(...r)}finally{Nn(i),s._d&&Ir(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function Lf(e,t){if(ge===null)return e;const n=Yn(ge),s=e.dirs||(e.dirs=[]);for(let r=0;re.__isTeleport,Kt=e=>e&&(e.disabled||e.disabled===""),_r=e=>e&&(e.defer||e.defer===""),br=e=>typeof SVGElement<"u"&&e instanceof SVGElement,wr=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,Rs=(e,t)=>{const n=e&&e.to;return le(n)?t?t(n):null:n},Ui={name:"Teleport",__isTeleport:!0,process(e,t,n,s,r,i,o,l,c,f){const{mc:a,pc:d,pbc:m,o:{insert:v,querySelector:b,createText:_,createComment:U}}=f,L=Kt(t.props);let{shapeFlag:D,children:p,dynamicChildren:g}=t;if(e==null){const R=t.el=_(""),$=t.anchor=_("");v(R,n,s),v($,n,s);const M=(T,O)=>{D&16&&(r&&r.isCE&&(r.ce._teleportTarget=T),a(p,T,O,r,i,o,l,c))},V=()=>{const T=t.target=Rs(t.props,b),O=Wi(T,t,_,v);T&&(o!=="svg"&&br(T)?o="svg":o!=="mathml"&&wr(T)&&(o="mathml"),L||(M(T,O),En(t,!1)))};L&&(M(n,$),En(t,!0)),_r(t.props)?(t.el.__isMounted=!1,we(()=>{V(),delete t.el.__isMounted},i)):V()}else{if(_r(t.props)&&e.el.__isMounted===!1){we(()=>{Ui.process(e,t,n,s,r,i,o,l,c,f)},i);return}t.el=e.el,t.targetStart=e.targetStart;const R=t.anchor=e.anchor,$=t.target=e.target,M=t.targetAnchor=e.targetAnchor,V=Kt(e.props),T=V?n:$,O=V?R:M;if(o==="svg"||br($)?o="svg":(o==="mathml"||wr($))&&(o="mathml"),g?(m(e.dynamicChildren,g,T,r,i,o,l),sr(e,t,!0)):c||d(e,t,T,O,r,i,o,l,!1),L)V?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):pn(t,n,R,f,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const A=t.target=Rs(t.props,b);A&&pn(t,A,null,f,0)}else V&&pn(t,$,M,f,1);En(t,L)}},remove(e,t,n,{um:s,o:{remove:r}},i){const{shapeFlag:o,children:l,anchor:c,targetStart:f,targetAnchor:a,target:d,props:m}=e;if(d&&(r(f),r(a)),i&&r(c),o&16){const v=i||!Kt(m);for(let b=0;b{e.isMounted=!0}),Ji(()=>{e.isUnmounting=!0}),e}const Me=[Function,Array],Bi={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Me,onEnter:Me,onAfterEnter:Me,onEnterCancelled:Me,onBeforeLeave:Me,onLeave:Me,onAfterLeave:Me,onLeaveCancelled:Me,onBeforeAppear:Me,onAppear:Me,onAfterAppear:Me,onAppearCancelled:Me},Ki=e=>{const t=e.subTree;return t.component?Ki(t.component):t},zl={name:"BaseTransition",props:Bi,setup(e,{slots:t}){const n=Tt(),s=Jl();return()=>{const r=t.default&&Xi(t.default(),!0);if(!r||!r.length)return;const i=qi(r),o=z(e),{mode:l}=o;if(s.isLeaving)return rs(i);const c=Sr(i);if(!c)return rs(i);let f=Ms(c,o,s,n,d=>f=d);c.type!==he&&Zt(c,f);let a=n.subTree&&Sr(n.subTree);if(a&&a.type!==he&&!gt(c,a)&&Ki(n).type!==he){let d=Ms(a,o,s,n);if(Zt(a,d),l==="out-in"&&c.type!==he)return s.isLeaving=!0,d.afterLeave=()=>{s.isLeaving=!1,n.job.flags&8||n.update(),delete d.afterLeave,a=void 0},rs(i);l==="in-out"&&c.type!==he?d.delayLeave=(m,v,b)=>{const _=Gi(s,a);_[String(a.key)]=a,m[st]=()=>{v(),m[st]=void 0,delete f.delayedLeave,a=void 0},f.delayedLeave=()=>{b(),delete f.delayedLeave,a=void 0}}:a=void 0}else a&&(a=void 0);return i}}};function qi(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==he){t=n;break}}return t}const Ql=zl;function Gi(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function Ms(e,t,n,s,r){const{appear:i,mode:o,persisted:l=!1,onBeforeEnter:c,onEnter:f,onAfterEnter:a,onEnterCancelled:d,onBeforeLeave:m,onLeave:v,onAfterLeave:b,onLeaveCancelled:_,onBeforeAppear:U,onAppear:L,onAfterAppear:D,onAppearCancelled:p}=t,g=String(e.key),R=Gi(n,e),$=(T,O)=>{T&&$e(T,s,9,O)},M=(T,O)=>{const A=O[1];$(T,O),K(T)?T.every(w=>w.length<=1)&&A():T.length<=1&&A()},V={mode:o,persisted:l,beforeEnter(T){let O=c;if(!n.isMounted)if(i)O=U||c;else return;T[st]&&T[st](!0);const A=R[g];A&>(e,A)&&A.el[st]&&A.el[st](),$(O,[T])},enter(T){let O=f,A=a,w=d;if(!n.isMounted)if(i)O=L||f,A=D||a,w=p||d;else return;let F=!1;const Y=T[gn]=ie=>{F||(F=!0,ie?$(w,[T]):$(A,[T]),V.delayedLeave&&V.delayedLeave(),T[gn]=void 0)};O?M(O,[T,Y]):Y()},leave(T,O){const A=String(e.key);if(T[gn]&&T[gn](!0),n.isUnmounting)return O();$(m,[T]);let w=!1;const F=T[st]=Y=>{w||(w=!0,O(),Y?$(_,[T]):$(b,[T]),T[st]=void 0,R[A]===e&&delete R[A])};R[A]=e,v?M(v,[T,F]):F()},clone(T){const O=Ms(T,t,n,s,r);return r&&r(O),O}};return V}function rs(e){if(ln(e))return e=lt(e),e.children=null,e}function Sr(e){if(!ln(e))return ki(e.type)&&e.children?qi(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&q(n.default))return n.default()}}function Zt(e,t){e.shapeFlag&6&&e.component?(e.transition=t,Zt(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Xi(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;iNt(b,t&&(K(t)?t[_]:t),n,s,r));return}if(yt(s)&&!r){s.shapeFlag&512&&s.type.__asyncResolved&&s.component.subTree.component&&Nt(e,t,n,s.component.subTree);return}const i=s.shapeFlag&4?Yn(s.component):s.el,o=r?null:i,{i:l,r:c}=e,f=t&&t.r,a=l.refs===ee?l.refs={}:l.refs,d=l.setupState,m=z(d),v=d===ee?()=>!1:b=>Q(m,b);if(f!=null&&f!==c&&(le(f)?(a[f]=null,v(f)&&(d[f]=null)):fe(f)&&(f.value=null)),q(c))rn(c,l,12,[o,a]);else{const b=le(c),_=fe(c);if(b||_){const U=()=>{if(e.f){const L=b?v(c)?d[c]:a[c]:c.value;r?K(L)&&ks(L,i):K(L)?L.includes(i)||L.push(i):b?(a[c]=[i],v(c)&&(d[c]=a[c])):(c.value=[i],e.k&&(a[e.k]=c.value))}else b?(a[c]=o,v(c)&&(d[c]=o)):_&&(c.value=o,e.k&&(a[e.k]=o))};o?(U.id=-1,we(U,n)):U()}}}let Tr=!1;const Ct=()=>{Tr||(console.error("Hydration completed but contains mismatches."),Tr=!0)},Zl=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",ec=e=>e.namespaceURI.includes("MathML"),mn=e=>{if(e.nodeType===1){if(Zl(e))return"svg";if(ec(e))return"mathml"}},Mt=e=>e.nodeType===8;function tc(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:l,insert:c,createComment:f}}=e,a=(p,g)=>{if(!g.hasChildNodes()){n(null,p,g),In(),g._vnode=p;return}d(g.firstChild,p,null,null,null),In(),g._vnode=p},d=(p,g,R,$,M,V=!1)=>{V=V||!!g.dynamicChildren;const T=Mt(p)&&p.data==="[",O=()=>_(p,g,R,$,M,T),{type:A,ref:w,shapeFlag:F,patchFlag:Y}=g;let ie=p.nodeType;g.el=p,Y===-2&&(V=!1,g.dynamicChildren=null);let W=null;switch(A){case wt:ie!==3?g.children===""?(c(g.el=r(""),o(p),p),W=p):W=O():(p.data!==g.children&&(Ct(),p.data=g.children),W=i(p));break;case he:D(p)?(W=i(p),L(g.el=p.content.firstChild,p,R)):ie!==8||T?W=O():W=i(p);break;case Gt:if(T&&(p=i(p),ie=p.nodeType),ie===1||ie===3){W=p;const X=!g.children.length;for(let j=0;j{V=V||!!g.dynamicChildren;const{type:T,props:O,patchFlag:A,shapeFlag:w,dirs:F,transition:Y}=g,ie=T==="input"||T==="option";if(ie||A!==-1){F&&We(g,null,R,"created");let W=!1;if(D(p)){W=go(null,Y)&&R&&R.vnode.props&&R.vnode.props.appear;const j=p.content.firstChild;if(W){const oe=j.getAttribute("class");oe&&(j.$cls=oe),Y.beforeEnter(j)}L(j,p,R),g.el=p=j}if(w&16&&!(O&&(O.innerHTML||O.textContent))){let j=v(p.firstChild,g,p,R,$,M,V);for(;j;){vn(p,1)||Ct();const oe=j;j=j.nextSibling,l(oe)}}else if(w&8){let j=g.children;j[0]===` +`&&(p.tagName==="PRE"||p.tagName==="TEXTAREA")&&(j=j.slice(1)),p.textContent!==j&&(vn(p,0)||Ct(),p.textContent=g.children)}if(O){if(ie||!V||A&48){const j=p.tagName.includes("-");for(const oe in O)(ie&&(oe.endsWith("value")||oe==="indeterminate")||sn(oe)&&!Lt(oe)||oe[0]==="."||j)&&s(p,oe,null,O[oe],void 0,R)}else if(O.onClick)s(p,"onClick",null,O.onClick,void 0,R);else if(A&4&&vt(O.style))for(const j in O.style)O.style[j]}let X;(X=O&&O.onVnodeBeforeMount)&&Oe(X,R,g),F&&We(g,null,R,"beforeMount"),((X=O&&O.onVnodeMounted)||F||W)&&wo(()=>{X&&Oe(X,R,g),W&&Y.enter(p),F&&We(g,null,R,"mounted")},$)}return p.nextSibling},v=(p,g,R,$,M,V,T)=>{T=T||!!g.dynamicChildren;const O=g.children,A=O.length;for(let w=0;w{const{slotScopeIds:T}=g;T&&(M=M?M.concat(T):T);const O=o(p),A=v(i(p),g,O,R,$,M,V);return A&&Mt(A)&&A.data==="]"?i(g.anchor=A):(Ct(),c(g.anchor=f("]"),O,A),A)},_=(p,g,R,$,M,V)=>{if(vn(p.parentElement,1)||Ct(),g.el=null,V){const A=U(p);for(;;){const w=i(p);if(w&&w!==A)l(w);else break}}const T=i(p),O=o(p);return l(p),n(null,g,O,T,R,$,mn(O),M),R&&(R.vnode.el=g.el,_o(R,g.el)),T},U=(p,g="[",R="]")=>{let $=0;for(;p;)if(p=i(p),p&&Mt(p)&&(p.data===g&&$++,p.data===R)){if($===0)return i(p);$--}return p},L=(p,g,R)=>{const $=g.parentNode;$&&$.replaceChild(p,g);let M=R;for(;M;)M.vnode.el===g&&(M.vnode.el=M.subTree.el=p),M=M.parent},D=p=>p.nodeType===1&&p.tagName==="TEMPLATE";return[a,d]}const xr="data-allow-mismatch",nc={0:"text",1:"children",2:"class",3:"style",4:"attribute"};function vn(e,t){if(t===0||t===1)for(;e&&!e.hasAttribute(xr);)e=e.parentElement;const n=e&&e.getAttribute(xr);if(n==null)return!1;if(n==="")return!0;{const s=n.split(",");return t===0&&s.includes("children")?!0:s.includes(nc[t])}}Vn().requestIdleCallback;Vn().cancelIdleCallback;function sc(e,t){if(Mt(e)&&e.data==="["){let n=1,s=e.nextSibling;for(;s;){if(s.nodeType===1){if(t(s)===!1)break}else if(Mt(s))if(s.data==="]"){if(--n===0)break}else s.data==="["&&n++;s=s.nextSibling}}else t(e)}const yt=e=>!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function Nf(e){q(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:s,delay:r=200,hydrate:i,timeout:o,suspensible:l=!0,onError:c}=e;let f=null,a,d=0;const m=()=>(d++,f=null,v()),v=()=>{let b;return f||(b=f=t().catch(_=>{if(_=_ instanceof Error?_:new Error(String(_)),c)return new Promise((U,L)=>{c(_,()=>U(m()),()=>L(_),d+1)});throw _}).then(_=>b!==f&&f?f:(_&&(_.__esModule||_[Symbol.toStringTag]==="Module")&&(_=_.default),a=_,_)))};return Zs({name:"AsyncComponentWrapper",__asyncLoader:v,__asyncHydrate(b,_,U){let L=!1;(_.bu||(_.bu=[])).push(()=>L=!0);const D=()=>{L||U()},p=i?()=>{const g=i(D,R=>sc(b,R));g&&(_.bum||(_.bum=[])).push(g)}:D;a?p():v().then(()=>!_.isUnmounted&&p())},get __asyncResolved(){return a},setup(){const b=pe;if(er(b),a)return()=>is(a,b);const _=p=>{f=null,on(p,b,13,!s)};if(l&&b.suspense||Ht)return v().then(p=>()=>is(p,b)).catch(p=>(_(p),()=>s?ae(s,{error:p}):null));const U=De(!1),L=De(),D=De(!!r);return r&&setTimeout(()=>{D.value=!1},r),o!=null&&setTimeout(()=>{if(!U.value&&!L.value){const p=new Error(`Async component timed out after ${o}ms.`);_(p),L.value=p}},o),v().then(()=>{U.value=!0,b.parent&&ln(b.parent.vnode)&&b.parent.update()}).catch(p=>{_(p),L.value=p}),()=>{if(U.value&&a)return is(a,b);if(L.value&&s)return ae(s,{error:L.value});if(n&&!D.value)return ae(n)}}})}function is(e,t){const{ref:n,props:s,children:r,ce:i}=t.vnode,o=ae(e,s,r);return o.ref=n,o.ce=i,delete t.vnode.ce,o}const ln=e=>e.type.__isKeepAlive;function rc(e,t){Yi(e,"a",t)}function ic(e,t){Yi(e,"da",t)}function Yi(e,t,n=pe){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Kn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)ln(r.parent.vnode)&&oc(s,t,n,r),r=r.parent}}function oc(e,t,n,s){const r=Kn(t,e,s,!0);qn(()=>{ks(s[t],r)},n)}function Kn(e,t,n=pe,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{Je();const l=cn(n),c=$e(t,n,e,o);return l(),ze(),c});return s?r.unshift(i):r.push(i),i}}const et=e=>(t,n=pe)=>{(!Ht||e==="sp")&&Kn(e,(...s)=>t(...s),n)},lc=et("bm"),Dt=et("m"),cc=et("bu"),ac=et("u"),Ji=et("bum"),qn=et("um"),fc=et("sp"),uc=et("rtg"),dc=et("rtc");function hc(e,t=pe){Kn("ec",e,t)}const zi="components";function Ff(e,t){return Zi(zi,e,!0,t)||e}const Qi=Symbol.for("v-ndc");function Hf(e){return le(e)?Zi(zi,e,!1)||e:e||Qi}function Zi(e,t,n=!0,s=!1){const r=ge||pe;if(r){const i=r.type;{const l=zc(i,!1);if(l&&(l===t||l===Ne(t)||l===jn(Ne(t))))return i}const o=Er(r[e]||i[e],t)||Er(r.appContext[e],t);return!o&&s?i:o}}function Er(e,t){return e&&(e[t]||e[Ne(t)]||e[jn(Ne(t))])}function Df(e,t,n,s){let r;const i=n,o=K(e);if(o||le(e)){const l=o&&vt(e);let c=!1,f=!1;l&&(c=!Le(e),f=ot(e),e=Un(e)),r=new Array(e.length);for(let a=0,d=e.length;at(l,c,void 0,i));else{const l=Object.keys(e);r=new Array(l.length);for(let c=0,f=l.length;ctn(t)?!(t.type===he||t.type===Te&&!eo(t.children)):!0)?e:null}function jf(e,t){const n={};for(const s in e)n[/[A-Z]/.test(s)?`on:${s}`:Sn(s)]=e[s];return n}const Os=e=>e?Co(e)?Yn(e):Os(e.parent):null,qt=ue(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Os(e.parent),$root:e=>Os(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>no(e),$forceUpdate:e=>e.f||(e.f=()=>{Qs(e.update)}),$nextTick:e=>e.n||(e.n=Bn.bind(e.proxy)),$watch:e=>Fc.bind(e)}),os=(e,t)=>e!==ee&&!e.__isScriptSetup&&Q(e,t),pc={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:l,appContext:c}=e;let f;if(t[0]!=="$"){const v=o[t];if(v!==void 0)switch(v){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(os(s,t))return o[t]=1,s[t];if(r!==ee&&Q(r,t))return o[t]=2,r[t];if((f=e.propsOptions[0])&&Q(f,t))return o[t]=3,i[t];if(n!==ee&&Q(n,t))return o[t]=4,n[t];Ps&&(o[t]=0)}}const a=qt[t];let d,m;if(a)return t==="$attrs"&&_e(e.attrs,"get",""),a(e);if((d=l.__cssModules)&&(d=d[t]))return d;if(n!==ee&&Q(n,t))return o[t]=4,n[t];if(m=c.config.globalProperties,Q(m,t))return m[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return os(r,t)?(r[t]=n,!0):s!==ee&&Q(s,t)?(s[t]=n,!0):Q(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},o){let l;return!!n[o]||e!==ee&&Q(e,o)||os(t,o)||(l=i[0])&&Q(l,o)||Q(s,o)||Q(qt,o)||Q(r.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Q(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Vf(){return gc().slots}function gc(e){const t=Tt();return t.setupContext||(t.setupContext=Ro(t))}function Cr(e){return K(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Ps=!0;function mc(e){const t=no(e),n=e.proxy,s=e.ctx;Ps=!1,t.beforeCreate&&Ar(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:f,created:a,beforeMount:d,mounted:m,beforeUpdate:v,updated:b,activated:_,deactivated:U,beforeDestroy:L,beforeUnmount:D,destroyed:p,unmounted:g,render:R,renderTracked:$,renderTriggered:M,errorCaptured:V,serverPrefetch:T,expose:O,inheritAttrs:A,components:w,directives:F,filters:Y}=t;if(f&&vc(f,s,null),o)for(const X in o){const j=o[X];q(j)&&(s[X]=j.bind(n))}if(r){const X=r.call(n,n);se(X)&&(e.data=Ft(X))}if(Ps=!0,i)for(const X in i){const j=i[X],oe=q(j)?j.bind(n,n):q(j.get)?j.get.bind(n,n):Be,an=!q(j)&&q(j.set)?j.set.bind(n):Be,ft=re({get:oe,set:an});Object.defineProperty(s,X,{enumerable:!0,configurable:!0,get:()=>ft.value,set:Ve=>ft.value=Ve})}if(l)for(const X in l)to(l[X],s,n,X);if(c){const X=q(c)?c.call(n):c;Reflect.ownKeys(X).forEach(j=>{Tc(j,X[j])})}a&&Ar(a,e,"c");function W(X,j){K(j)?j.forEach(oe=>X(oe.bind(n))):j&&X(j.bind(n))}if(W(lc,d),W(Dt,m),W(cc,v),W(ac,b),W(rc,_),W(ic,U),W(hc,V),W(dc,$),W(uc,M),W(Ji,D),W(qn,g),W(fc,T),K(O))if(O.length){const X=e.exposed||(e.exposed={});O.forEach(j=>{Object.defineProperty(X,j,{get:()=>n[j],set:oe=>n[j]=oe,enumerable:!0})})}else e.exposed||(e.exposed={});R&&e.render===Be&&(e.render=R),A!=null&&(e.inheritAttrs=A),w&&(e.components=w),F&&(e.directives=F),T&&er(e)}function vc(e,t,n=Be){K(e)&&(e=Ls(e));for(const s in e){const r=e[s];let i;se(r)?"default"in r?i=bt(r.from||s,r.default,!0):i=bt(r.from||s):i=bt(r),fe(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function Ar(e,t,n){$e(K(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function to(e,t,n,s){let r=s.includes(".")?vo(n,s):()=>n[s];if(le(e)){const i=t[e];q(i)&&Ie(r,i)}else if(q(e))Ie(r,e.bind(n));else if(se(e))if(K(e))e.forEach(i=>to(i,t,n,s));else{const i=q(e.handler)?e.handler.bind(n):t[e.handler];q(i)&&Ie(r,i,e)}}function no(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!n&&!s?c=t:(c={},r.length&&r.forEach(f=>Fn(c,f,o,!0)),Fn(c,t,o)),se(t)&&i.set(t,c),c}function Fn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&Fn(e,i,n,!0),r&&r.forEach(o=>Fn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const l=yc[o]||n&&n[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const yc={data:Rr,props:Mr,emits:Mr,methods:Ut,computed:Ut,beforeCreate:be,created:be,beforeMount:be,mounted:be,beforeUpdate:be,updated:be,beforeDestroy:be,beforeUnmount:be,destroyed:be,unmounted:be,activated:be,deactivated:be,errorCaptured:be,serverPrefetch:be,components:Ut,directives:Ut,watch:bc,provide:Rr,inject:_c};function Rr(e,t){return t?e?function(){return ue(q(e)?e.call(this,this):e,q(t)?t.call(this,this):t)}:t:e}function _c(e,t){return Ut(Ls(e),Ls(t))}function Ls(e){if(K(e)){const t={};for(let n=0;n1)return n&&q(t)?t.call(s&&s.proxy):t}}function ro(){return!!(Tt()||_t)}const io={},oo=()=>Object.create(io),lo=e=>Object.getPrototypeOf(e)===io;function xc(e,t,n,s=!1){const r={},i=oo();e.propsDefaults=Object.create(null),co(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:Ll(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function Ec(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=z(r),[c]=e.propsOptions;let f=!1;if((s||o>0)&&!(o&16)){if(o&8){const a=e.vnode.dynamicProps;for(let d=0;d{c=!0;const[m,v]=ao(d,t,!0);ue(o,m),v&&l.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}if(!i&&!c)return se(e)&&s.set(e,Ot),Ot;if(K(i))for(let a=0;ae==="_"||e==="__"||e==="_ctx"||e==="$stable",nr=e=>K(e)?e.map(Pe):[Pe(e)],Ac=(e,t,n)=>{if(t._n)return t;const s=Xl((...r)=>nr(t(...r)),n);return s._c=!1,s},fo=(e,t,n)=>{const s=e._ctx;for(const r in e){if(tr(r))continue;const i=e[r];if(q(i))t[r]=Ac(r,i,s);else if(i!=null){const o=nr(i);t[r]=()=>o}}},uo=(e,t)=>{const n=nr(t);e.slots.default=()=>n},ho=(e,t,n)=>{for(const s in t)(n||!tr(s))&&(e[s]=t[s])},Rc=(e,t,n)=>{const s=e.slots=oo();if(e.vnode.shapeFlag&32){const r=t.__;r&&Ts(s,"__",r,!0);const i=t._;i?(ho(s,t,n),n&&Ts(s,"_",i,!0)):fo(t,s)}else t&&uo(e,t)},Mc=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=ee;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:ho(r,t,n):(i=!t.$stable,fo(t,r)),o=t}else t&&(uo(e,t),o={default:1});if(i)for(const l in r)!tr(l)&&o[l]==null&&delete r[l]},we=wo;function Oc(e){return po(e)}function Pc(e){return po(e,tc)}function po(e,t){const n=Vn();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:f,setElementText:a,parentNode:d,nextSibling:m,setScopeId:v=Be,insertStaticContent:b}=e,_=(u,h,y,E=null,S=null,x=null,N=void 0,I=null,P=!!h.dynamicChildren)=>{if(u===h)return;u&&!gt(u,h)&&(E=fn(u),Ve(u,S,x,!0),u=null),h.patchFlag===-2&&(P=!1,h.dynamicChildren=null);const{type:C,ref:B,shapeFlag:H}=h;switch(C){case wt:U(u,h,y,E);break;case he:L(u,h,y,E);break;case Gt:u==null&&D(h,y,E,N);break;case Te:w(u,h,y,E,S,x,N,I,P);break;default:H&1?R(u,h,y,E,S,x,N,I,P):H&6?F(u,h,y,E,S,x,N,I,P):(H&64||H&128)&&C.process(u,h,y,E,S,x,N,I,P,xt)}B!=null&&S?Nt(B,u&&u.ref,x,h||u,!h):B==null&&u&&u.ref!=null&&Nt(u.ref,null,x,u,!0)},U=(u,h,y,E)=>{if(u==null)s(h.el=l(h.children),y,E);else{const S=h.el=u.el;h.children!==u.children&&f(S,h.children)}},L=(u,h,y,E)=>{u==null?s(h.el=c(h.children||""),y,E):h.el=u.el},D=(u,h,y,E)=>{[u.el,u.anchor]=b(u.children,h,y,E,u.el,u.anchor)},p=({el:u,anchor:h},y,E)=>{let S;for(;u&&u!==h;)S=m(u),s(u,y,E),u=S;s(h,y,E)},g=({el:u,anchor:h})=>{let y;for(;u&&u!==h;)y=m(u),r(u),u=y;r(h)},R=(u,h,y,E,S,x,N,I,P)=>{h.type==="svg"?N="svg":h.type==="math"&&(N="mathml"),u==null?$(h,y,E,S,x,N,I,P):T(u,h,S,x,N,I,P)},$=(u,h,y,E,S,x,N,I)=>{let P,C;const{props:B,shapeFlag:H,transition:k,dirs:G}=u;if(P=u.el=o(u.type,x,B&&B.is,B),H&8?a(P,u.children):H&16&&V(u.children,P,null,E,S,ls(u,x),N,I),G&&We(u,null,E,"created"),M(P,u,u.scopeId,N,E),B){for(const te in B)te!=="value"&&!Lt(te)&&i(P,te,null,B[te],x,E);"value"in B&&i(P,"value",null,B.value,x),(C=B.onVnodeBeforeMount)&&Oe(C,E,u)}G&&We(u,null,E,"beforeMount");const J=go(S,k);J&&k.beforeEnter(P),s(P,h,y),((C=B&&B.onVnodeMounted)||J||G)&&we(()=>{C&&Oe(C,E,u),J&&k.enter(P),G&&We(u,null,E,"mounted")},S)},M=(u,h,y,E,S)=>{if(y&&v(u,y),E)for(let x=0;x{for(let C=P;C{const I=h.el=u.el;let{patchFlag:P,dynamicChildren:C,dirs:B}=h;P|=u.patchFlag&16;const H=u.props||ee,k=h.props||ee;let G;if(y&&ut(y,!1),(G=k.onVnodeBeforeUpdate)&&Oe(G,y,h,u),B&&We(h,u,y,"beforeUpdate"),y&&ut(y,!0),(H.innerHTML&&k.innerHTML==null||H.textContent&&k.textContent==null)&&a(I,""),C?O(u.dynamicChildren,C,I,y,E,ls(h,S),x):N||j(u,h,I,null,y,E,ls(h,S),x,!1),P>0){if(P&16)A(I,H,k,y,S);else if(P&2&&H.class!==k.class&&i(I,"class",null,k.class,S),P&4&&i(I,"style",H.style,k.style,S),P&8){const J=h.dynamicProps;for(let te=0;te{G&&Oe(G,y,h,u),B&&We(h,u,y,"updated")},E)},O=(u,h,y,E,S,x,N)=>{for(let I=0;I{if(h!==y){if(h!==ee)for(const x in h)!Lt(x)&&!(x in y)&&i(u,x,h[x],null,S,E);for(const x in y){if(Lt(x))continue;const N=y[x],I=h[x];N!==I&&x!=="value"&&i(u,x,I,N,S,E)}"value"in y&&i(u,"value",h.value,y.value,S)}},w=(u,h,y,E,S,x,N,I,P)=>{const C=h.el=u?u.el:l(""),B=h.anchor=u?u.anchor:l("");let{patchFlag:H,dynamicChildren:k,slotScopeIds:G}=h;G&&(I=I?I.concat(G):G),u==null?(s(C,y,E),s(B,y,E),V(h.children||[],y,B,S,x,N,I,P)):H>0&&H&64&&k&&u.dynamicChildren?(O(u.dynamicChildren,k,y,S,x,N,I),(h.key!=null||S&&h===S.subTree)&&sr(u,h,!0)):j(u,h,y,B,S,x,N,I,P)},F=(u,h,y,E,S,x,N,I,P)=>{h.slotScopeIds=I,u==null?h.shapeFlag&512?S.ctx.activate(h,y,E,N,P):Y(h,y,E,S,x,N,P):ie(u,h,P)},Y=(u,h,y,E,S,x,N)=>{const I=u.component=Gc(u,E,S);if(ln(u)&&(I.ctx.renderer=xt),Xc(I,!1,N),I.asyncDep){if(S&&S.registerDep(I,W,N),!u.el){const P=I.subTree=ae(he);L(null,P,h,y),u.placeholder=P.el}}else W(I,u,h,y,S,x,N)},ie=(u,h,y)=>{const E=h.component=u.component;if(Vc(u,h,y))if(E.asyncDep&&!E.asyncResolved){X(E,h,y);return}else E.next=h,E.update();else h.el=u.el,E.vnode=h},W=(u,h,y,E,S,x,N)=>{const I=()=>{if(u.isMounted){let{next:H,bu:k,u:G,parent:J,vnode:te}=u;{const Ce=mo(u);if(Ce){H&&(H.el=te.el,X(u,H,N)),Ce.asyncDep.then(()=>{u.isUnmounted||I()});return}}let Z=H,xe;ut(u,!1),H?(H.el=te.el,X(u,H,N)):H=te,k&&Tn(k),(xe=H.props&&H.props.onVnodeBeforeUpdate)&&Oe(xe,J,H,te),ut(u,!0);const me=cs(u),Fe=u.subTree;u.subTree=me,_(Fe,me,d(Fe.el),fn(Fe),u,S,x),H.el=me.el,Z===null&&_o(u,me.el),G&&we(G,S),(xe=H.props&&H.props.onVnodeUpdated)&&we(()=>Oe(xe,J,H,te),S)}else{let H;const{el:k,props:G}=h,{bm:J,m:te,parent:Z,root:xe,type:me}=u,Fe=yt(h);if(ut(u,!1),J&&Tn(J),!Fe&&(H=G&&G.onVnodeBeforeMount)&&Oe(H,Z,h),ut(u,!0),k&&Zn){const Ce=()=>{u.subTree=cs(u),Zn(k,u.subTree,u,S,null)};Fe&&me.__asyncHydrate?me.__asyncHydrate(k,u,Ce):Ce()}else{xe.ce&&xe.ce._def.shadowRoot!==!1&&xe.ce._injectChildStyle(me);const Ce=u.subTree=cs(u);_(null,Ce,y,E,u,S,x),h.el=Ce.el}if(te&&we(te,S),!Fe&&(H=G&&G.onVnodeMounted)){const Ce=h;we(()=>Oe(H,Z,Ce),S)}(h.shapeFlag&256||Z&&yt(Z.vnode)&&Z.vnode.shapeFlag&256)&&u.a&&we(u.a,S),u.isMounted=!0,h=y=E=null}};u.scope.on();const P=u.effect=new bi(I);u.scope.off();const C=u.update=P.run.bind(P),B=u.job=P.runIfDirty.bind(P);B.i=u,B.id=u.uid,P.scheduler=()=>Qs(B),ut(u,!0),C()},X=(u,h,y)=>{h.component=u;const E=u.vnode.props;u.vnode=h,u.next=null,Ec(u,h.props,E,y),Mc(u,h.children,y),Je(),yr(u),ze()},j=(u,h,y,E,S,x,N,I,P=!1)=>{const C=u&&u.children,B=u?u.shapeFlag:0,H=h.children,{patchFlag:k,shapeFlag:G}=h;if(k>0){if(k&128){an(C,H,y,E,S,x,N,I,P);return}else if(k&256){oe(C,H,y,E,S,x,N,I,P);return}}G&8?(B&16&&$t(C,S,x),H!==C&&a(y,H)):B&16?G&16?an(C,H,y,E,S,x,N,I,P):$t(C,S,x,!0):(B&8&&a(y,""),G&16&&V(H,y,E,S,x,N,I,P))},oe=(u,h,y,E,S,x,N,I,P)=>{u=u||Ot,h=h||Ot;const C=u.length,B=h.length,H=Math.min(C,B);let k;for(k=0;kB?$t(u,S,x,!0,!1,H):V(h,y,E,S,x,N,I,P,H)},an=(u,h,y,E,S,x,N,I,P)=>{let C=0;const B=h.length;let H=u.length-1,k=B-1;for(;C<=H&&C<=k;){const G=u[C],J=h[C]=P?rt(h[C]):Pe(h[C]);if(gt(G,J))_(G,J,y,null,S,x,N,I,P);else break;C++}for(;C<=H&&C<=k;){const G=u[H],J=h[k]=P?rt(h[k]):Pe(h[k]);if(gt(G,J))_(G,J,y,null,S,x,N,I,P);else break;H--,k--}if(C>H){if(C<=k){const G=k+1,J=Gk)for(;C<=H;)Ve(u[C],S,x,!0),C++;else{const G=C,J=C,te=new Map;for(C=J;C<=k;C++){const Ae=h[C]=P?rt(h[C]):Pe(h[C]);Ae.key!=null&&te.set(Ae.key,C)}let Z,xe=0;const me=k-J+1;let Fe=!1,Ce=0;const jt=new Array(me);for(C=0;C=me){Ve(Ae,S,x,!0);continue}let ke;if(Ae.key!=null)ke=te.get(Ae.key);else for(Z=J;Z<=k;Z++)if(jt[Z-J]===0&>(Ae,h[Z])){ke=Z;break}ke===void 0?Ve(Ae,S,x,!0):(jt[ke-J]=C+1,ke>=Ce?Ce=ke:Fe=!0,_(Ae,h[ke],y,null,S,x,N,I,P),xe++)}const dr=Fe?Lc(jt):Ot;for(Z=dr.length-1,C=me-1;C>=0;C--){const Ae=J+C,ke=h[Ae],hr=h[Ae+1],pr=Ae+1{const{el:x,type:N,transition:I,children:P,shapeFlag:C}=u;if(C&6){ft(u.component.subTree,h,y,E);return}if(C&128){u.suspense.move(h,y,E);return}if(C&64){N.move(u,h,y,xt);return}if(N===Te){s(x,h,y);for(let H=0;HI.enter(x),S);else{const{leave:H,delayLeave:k,afterLeave:G}=I,J=()=>{u.ctx.isUnmounted?r(x):s(x,h,y)},te=()=>{H(x,()=>{J(),G&&G()})};k?k(x,J,te):te()}else s(x,h,y)},Ve=(u,h,y,E=!1,S=!1)=>{const{type:x,props:N,ref:I,children:P,dynamicChildren:C,shapeFlag:B,patchFlag:H,dirs:k,cacheIndex:G}=u;if(H===-2&&(S=!1),I!=null&&(Je(),Nt(I,null,y,u,!0),ze()),G!=null&&(h.renderCache[G]=void 0),B&256){h.ctx.deactivate(u);return}const J=B&1&&k,te=!yt(u);let Z;if(te&&(Z=N&&N.onVnodeBeforeUnmount)&&Oe(Z,h,u),B&6)zo(u.component,y,E);else{if(B&128){u.suspense.unmount(y,E);return}J&&We(u,null,h,"beforeUnmount"),B&64?u.type.remove(u,h,y,xt,E):C&&!C.hasOnce&&(x!==Te||H>0&&H&64)?$t(C,h,y,!1,!0):(x===Te&&H&384||!S&&B&16)&&$t(P,h,y),E&&fr(u)}(te&&(Z=N&&N.onVnodeUnmounted)||J)&&we(()=>{Z&&Oe(Z,h,u),J&&We(u,null,h,"unmounted")},y)},fr=u=>{const{type:h,el:y,anchor:E,transition:S}=u;if(h===Te){Jo(y,E);return}if(h===Gt){g(u);return}const x=()=>{r(y),S&&!S.persisted&&S.afterLeave&&S.afterLeave()};if(u.shapeFlag&1&&S&&!S.persisted){const{leave:N,delayLeave:I}=S,P=()=>N(y,x);I?I(u.el,x,P):P()}else x()},Jo=(u,h)=>{let y;for(;u!==h;)y=m(u),r(u),u=y;r(h)},zo=(u,h,y)=>{const{bum:E,scope:S,job:x,subTree:N,um:I,m:P,a:C,parent:B,slots:{__:H}}=u;Pr(P),Pr(C),E&&Tn(E),B&&K(H)&&H.forEach(k=>{B.renderCache[k]=void 0}),S.stop(),x&&(x.flags|=8,Ve(N,u,h,y)),I&&we(I,h),we(()=>{u.isUnmounted=!0},h),h&&h.pendingBranch&&!h.isUnmounted&&u.asyncDep&&!u.asyncResolved&&u.suspenseId===h.pendingId&&(h.deps--,h.deps===0&&h.resolve())},$t=(u,h,y,E=!1,S=!1,x=0)=>{for(let N=x;N{if(u.shapeFlag&6)return fn(u.component.subTree);if(u.shapeFlag&128)return u.suspense.next();const h=m(u.anchor||u.el),y=h&&h[Vi];return y?m(y):h};let zn=!1;const ur=(u,h,y)=>{u==null?h._vnode&&Ve(h._vnode,null,null,!0):_(h._vnode||null,u,h,null,null,null,y),h._vnode=u,zn||(zn=!0,yr(),In(),zn=!1)},xt={p:_,um:Ve,m:ft,r:fr,mt:Y,mc:V,pc:j,pbc:O,n:fn,o:e};let Qn,Zn;return t&&([Qn,Zn]=t(xt)),{render:ur,hydrate:Qn,createApp:Sc(ur,Qn)}}function ls({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function ut({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function go(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function sr(e,t,n=!1){const s=e.children,r=t.children;if(K(s)&&K(r))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function mo(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:mo(t)}function Pr(e){if(e)for(let t=0;tbt(Ic);function rr(e,t){return Gn(e,null,t)}function kf(e,t){return Gn(e,null,{flush:"post"})}function Ie(e,t,n){return Gn(e,t,n)}function Gn(e,t,n=ee){const{immediate:s,deep:r,flush:i,once:o}=n,l=ue({},n),c=t&&s||!t&&i!=="post";let f;if(Ht){if(i==="sync"){const v=Nc();f=v.__watcherHandles||(v.__watcherHandles=[])}else if(!c){const v=()=>{};return v.stop=Be,v.resume=Be,v.pause=Be,v}}const a=pe;l.call=(v,b,_)=>$e(v,a,b,_);let d=!1;i==="post"?l.scheduler=v=>{we(v,a&&a.suspense)}:i!=="sync"&&(d=!0,l.scheduler=(v,b)=>{b?v():Qs(v)}),l.augmentJob=v=>{t&&(v.flags|=4),d&&(v.flags|=2,a&&(v.id=a.uid,v.i=a))};const m=Bl(e,t,l);return Ht&&(f?f.push(m):c&&m()),m}function Fc(e,t,n){const s=this.proxy,r=le(e)?e.includes(".")?vo(s,e):()=>s[e]:e.bind(s,s);let i;q(t)?i=t:(i=t.handler,n=t);const o=cn(this),l=Gn(r,i.bind(s),n);return o(),l}function vo(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;rt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Ne(t)}Modifiers`]||e[`${at(t)}Modifiers`];function Dc(e,t,...n){if(e.isUnmounted)return;const s=e.vnode.props||ee;let r=n;const i=t.startsWith("update:"),o=i&&Hc(s,t.slice(7));o&&(o.trim&&(r=n.map(a=>le(a)?a.trim():a)),o.number&&(r=n.map(xs)));let l,c=s[l=Sn(t)]||s[l=Sn(Ne(t))];!c&&i&&(c=s[l=Sn(at(t))]),c&&$e(c,e,6,r);const f=s[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,$e(f,e,6,r)}}function yo(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!q(e)){const c=f=>{const a=yo(f,t,!0);a&&(l=!0,ue(o,a))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(se(e)&&s.set(e,null),null):(K(i)?i.forEach(c=>o[c]=null):ue(o,i),se(e)&&s.set(e,o),o)}function Xn(e,t){return!e||!sn(t)?!1:(t=t.slice(2).replace(/Once$/,""),Q(e,t[0].toLowerCase()+t.slice(1))||Q(e,at(t))||Q(e,t))}function cs(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:f,renderCache:a,props:d,data:m,setupState:v,ctx:b,inheritAttrs:_}=e,U=Nn(e);let L,D;try{if(n.shapeFlag&4){const g=r||s,R=g;L=Pe(f.call(R,g,a,d,v,m,b)),D=l}else{const g=t;L=Pe(g.length>1?g(d,{attrs:l,slots:o,emit:c}):g(d,null)),D=t.props?l:$c(l)}}catch(g){Xt.length=0,on(g,e,1),L=ae(he)}let p=L;if(D&&_!==!1){const g=Object.keys(D),{shapeFlag:R}=p;g.length&&R&7&&(i&&g.some(Vs)&&(D=jc(D,i)),p=lt(p,D,!1,!0))}return n.dirs&&(p=lt(p,null,!1,!0),p.dirs=p.dirs?p.dirs.concat(n.dirs):n.dirs),n.transition&&Zt(p,n.transition),L=p,Nn(U),L}const $c=e=>{let t;for(const n in e)(n==="class"||n==="style"||sn(n))&&((t||(t={}))[n]=e[n]);return t},jc=(e,t)=>{const n={};for(const s in e)(!Vs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function Vc(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,f=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?Lr(s,o,f):!!o;if(c&8){const a=t.dynamicProps;for(let d=0;de.__isSuspense;function wo(e,t){t&&t.pendingBranch?K(e)?t.effects.push(...e):t.effects.push(e):Gl(e)}const Te=Symbol.for("v-fgt"),wt=Symbol.for("v-txt"),he=Symbol.for("v-cmt"),Gt=Symbol.for("v-stc"),Xt=[];let Re=null;function Ns(e=!1){Xt.push(Re=e?null:[])}function kc(){Xt.pop(),Re=Xt[Xt.length-1]||null}let en=1;function Ir(e,t=!1){en+=e,e<0&&Re&&t&&(Re.hasOnce=!0)}function So(e){return e.dynamicChildren=en>0?Re||Ot:null,kc(),en>0&&Re&&Re.push(e),e}function Uf(e,t,n,s,r,i){return So(xo(e,t,n,s,r,i,!0))}function Fs(e,t,n,s,r){return So(ae(e,t,n,s,r,!0))}function tn(e){return e?e.__v_isVNode===!0:!1}function gt(e,t){return e.type===t.type&&e.key===t.key}const To=({key:e})=>e??null,Cn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?le(e)||fe(e)||q(e)?{i:ge,r:e,k:t,f:!!n}:e:null);function xo(e,t=null,n=null,s=0,r=null,i=e===Te?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&To(t),ref:t&&Cn(t),scopeId:ji,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:ge};return l?(ir(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=le(n)?8:16),en>0&&!o&&Re&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&Re.push(c),c}const ae=Uc;function Uc(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===Qi)&&(e=he),tn(e)){const l=lt(e,t,!0);return n&&ir(l,n),en>0&&!i&&Re&&(l.shapeFlag&6?Re[Re.indexOf(e)]=l:Re.push(l)),l.patchFlag=-2,l}if(Qc(e)&&(e=e.__vccOpts),t){t=Wc(t);let{class:l,style:c}=t;l&&!le(l)&&(t.class=Bs(l)),se(c)&&(Js(c)&&!K(c)&&(c=ue({},c)),t.style=Ws(c))}const o=le(e)?1:bo(e)?128:ki(e)?64:se(e)?4:q(e)?2:0;return xo(e,t,n,s,r,o,i,!0)}function Wc(e){return e?Js(e)||lo(e)?ue({},e):e:null}function lt(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,f=t?Bc(r||{},t):r,a={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&To(f),ref:t&&t.ref?n&&i?K(i)?i.concat(Cn(t)):[i,Cn(t)]:Cn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Te?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&<(e.ssContent),ssFallback:e.ssFallback&<(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&s&&Zt(a,c.clone(a)),a}function Eo(e=" ",t=0){return ae(wt,null,e,t)}function Wf(e,t){const n=ae(Gt,null,e);return n.staticCount=t,n}function Bf(e="",t=!1){return t?(Ns(),Fs(he,null,e)):ae(he,null,e)}function Pe(e){return e==null||typeof e=="boolean"?ae(he):K(e)?ae(Te,null,e.slice()):tn(e)?rt(e):ae(wt,null,String(e))}function rt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:lt(e)}function ir(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(K(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),ir(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!lo(t)?t._ctx=ge:r===3&&ge&&(ge.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else q(t)?(t={default:t,_ctx:ge},n=32):(t=String(t),s&64?(n=16,t=[Eo(t)]):n=8);e.children=t,e.shapeFlag|=n}function Bc(...e){const t={};for(let n=0;npe||ge;let Hn,Hs;{const e=Vn(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};Hn=t("__VUE_INSTANCE_SETTERS__",n=>pe=n),Hs=t("__VUE_SSR_SETTERS__",n=>Ht=n)}const cn=e=>{const t=pe;return Hn(e),e.scope.on(),()=>{e.scope.off(),Hn(t)}},Nr=()=>{pe&&pe.scope.off(),Hn(null)};function Co(e){return e.vnode.shapeFlag&4}let Ht=!1;function Xc(e,t=!1,n=!1){t&&Hs(t);const{props:s,children:r}=e.vnode,i=Co(e);xc(e,s,i,t),Rc(e,r,n||t);const o=i?Yc(e,t):void 0;return t&&Hs(!1),o}function Yc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,pc);const{setup:s}=n;if(s){Je();const r=e.setupContext=s.length>1?Ro(e):null,i=cn(e),o=rn(s,e,0,[e.props,r]),l=hi(o);if(ze(),i(),(l||e.sp)&&!yt(e)&&er(e),l){if(o.then(Nr,Nr),t)return o.then(c=>{Fr(e,c)}).catch(c=>{on(c,e,0)});e.asyncDep=o}else Fr(e,o)}else Ao(e)}function Fr(e,t,n){q(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:se(t)&&(e.setupState=Fi(t)),Ao(e)}function Ao(e,t,n){const s=e.type;e.render||(e.render=s.render||Be);{const r=cn(e);Je();try{mc(e)}finally{ze(),r()}}}const Jc={get(e,t){return _e(e,"get",""),e[t]}};function Ro(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Jc),slots:e.slots,emit:e.emit,expose:t}}function Yn(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(Fi(xn(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in qt)return qt[n](e)},has(t,n){return n in t||n in qt}})):e.proxy}function zc(e,t=!0){return q(e)?e.displayName||e.name:e.name||t&&e.__name}function Qc(e){return q(e)&&"__vccOpts"in e}const re=(e,t)=>Ul(e,t,Ht);function Ds(e,t,n){const s=arguments.length;return s===2?se(t)&&!K(t)?tn(t)?ae(e,null,[t]):ae(e,t):ae(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&tn(n)&&(n=[n]),ae(e,t,n))}const Zc="3.5.18";/** +* @vue/runtime-dom v3.5.18 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let $s;const Hr=typeof window<"u"&&window.trustedTypes;if(Hr)try{$s=Hr.createPolicy("vue",{createHTML:e=>e})}catch{}const Mo=$s?e=>$s.createHTML(e):e=>e,ea="http://www.w3.org/2000/svg",ta="http://www.w3.org/1998/Math/MathML",Ge=typeof document<"u"?document:null,Dr=Ge&&Ge.createElement("template"),na={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?Ge.createElementNS(ea,e):t==="mathml"?Ge.createElementNS(ta,e):n?Ge.createElement(e,{is:n}):Ge.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Ge.createTextNode(e),createComment:e=>Ge.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ge.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{Dr.innerHTML=Mo(s==="svg"?`${e}`:s==="mathml"?`${e}`:e);const l=Dr.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},tt="transition",kt="animation",nn=Symbol("_vtc"),Oo={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},sa=ue({},Bi,Oo),ra=e=>(e.displayName="Transition",e.props=sa,e),Kf=ra((e,{slots:t})=>Ds(Ql,ia(e),t)),dt=(e,t=[])=>{K(e)?e.forEach(n=>n(...t)):e&&e(...t)},$r=e=>e?K(e)?e.some(t=>t.length>1):e.length>1:!1;function ia(e){const t={};for(const w in e)w in Oo||(t[w]=e[w]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:f=o,appearToClass:a=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:m=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,b=oa(r),_=b&&b[0],U=b&&b[1],{onBeforeEnter:L,onEnter:D,onEnterCancelled:p,onLeave:g,onLeaveCancelled:R,onBeforeAppear:$=L,onAppear:M=D,onAppearCancelled:V=p}=t,T=(w,F,Y,ie)=>{w._enterCancelled=ie,ht(w,F?a:l),ht(w,F?f:o),Y&&Y()},O=(w,F)=>{w._isLeaving=!1,ht(w,d),ht(w,v),ht(w,m),F&&F()},A=w=>(F,Y)=>{const ie=w?M:D,W=()=>T(F,w,Y);dt(ie,[F,W]),jr(()=>{ht(F,w?c:i),qe(F,w?a:l),$r(ie)||Vr(F,s,_,W)})};return ue(t,{onBeforeEnter(w){dt(L,[w]),qe(w,i),qe(w,o)},onBeforeAppear(w){dt($,[w]),qe(w,c),qe(w,f)},onEnter:A(!1),onAppear:A(!0),onLeave(w,F){w._isLeaving=!0;const Y=()=>O(w,F);qe(w,d),w._enterCancelled?(qe(w,m),Wr()):(Wr(),qe(w,m)),jr(()=>{w._isLeaving&&(ht(w,d),qe(w,v),$r(g)||Vr(w,s,U,Y))}),dt(g,[w,Y])},onEnterCancelled(w){T(w,!1,void 0,!0),dt(p,[w])},onAppearCancelled(w){T(w,!0,void 0,!0),dt(V,[w])},onLeaveCancelled(w){O(w),dt(R,[w])}})}function oa(e){if(e==null)return null;if(se(e))return[as(e.enter),as(e.leave)];{const t=as(e);return[t,t]}}function as(e){return sl(e)}function qe(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[nn]||(e[nn]=new Set)).add(t)}function ht(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[nn];n&&(n.delete(t),n.size||(e[nn]=void 0))}function jr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let la=0;function Vr(e,t,n,s){const r=e._endId=++la,i=()=>{r===e._endId&&s()};if(n!=null)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=ca(e,t);if(!o)return s();const f=o+"end";let a=0;const d=()=>{e.removeEventListener(f,m),i()},m=v=>{v.target===e&&++a>=c&&d()};setTimeout(()=>{a(n[b]||"").split(", "),r=s(`${tt}Delay`),i=s(`${tt}Duration`),o=kr(r,i),l=s(`${kt}Delay`),c=s(`${kt}Duration`),f=kr(l,c);let a=null,d=0,m=0;t===tt?o>0&&(a=tt,d=o,m=i.length):t===kt?f>0&&(a=kt,d=f,m=c.length):(d=Math.max(o,f),a=d>0?o>f?tt:kt:null,m=a?a===tt?i.length:c.length:0);const v=a===tt&&/\b(transform|all)(,|$)/.test(s(`${tt}Property`).toString());return{type:a,timeout:d,propCount:m,hasTransform:v}}function kr(e,t){for(;e.lengthUr(n)+Ur(e[s])))}function Ur(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Wr(){return document.body.offsetHeight}function aa(e,t,n){const s=e[nn];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Br=Symbol("_vod"),fa=Symbol("_vsh"),ua=Symbol(""),da=/(^|;)\s*display\s*:/;function ha(e,t,n){const s=e.style,r=le(n);let i=!1;if(n&&!r){if(t)if(le(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();n[l]==null&&An(s,l,"")}else for(const o in t)n[o]==null&&An(s,o,"");for(const o in n)o==="display"&&(i=!0),An(s,o,n[o])}else if(r){if(t!==n){const o=s[ua];o&&(n+=";"+o),s.cssText=n,i=da.test(n)}}else t&&e.removeAttribute("style");Br in e&&(e[Br]=i?s.display:"",e[fa]&&(s.display="none"))}const Kr=/\s*!important$/;function An(e,t,n){if(K(n))n.forEach(s=>An(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=pa(e,t);Kr.test(n)?e.setProperty(at(s),n.replace(Kr,""),"important"):e[s]=n}}const qr=["Webkit","Moz","ms"],fs={};function pa(e,t){const n=fs[t];if(n)return n;let s=Ne(t);if(s!=="filter"&&s in e)return fs[t]=s;s=jn(s);for(let r=0;rus||(ya.then(()=>us=0),us=Date.now());function ba(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;$e(wa(s,n.value),t,5,[s])};return n.value=e,n.attached=_a(),n}function wa(e,t){if(K(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Qr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Sa=(e,t,n,s,r,i)=>{const o=r==="svg";t==="class"?aa(e,s,o):t==="style"?ha(e,n,s):sn(t)?Vs(t)||ma(e,t,n,s,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Ta(e,t,s,o))?(Yr(e,t,s),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Xr(e,t,s,o,i,t!=="value")):e._isVueCE&&(/[A-Z]/.test(t)||!le(s))?Yr(e,Ne(t),s,i,t):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Xr(e,t,s,o))};function Ta(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&Qr(t)&&q(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Qr(t)&&le(n)?!1:t in e}const Zr=e=>{const t=e.props["onUpdate:modelValue"]||!1;return K(t)?n=>Tn(t,n):t};function xa(e){e.target.composing=!0}function ei(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const ds=Symbol("_assign"),qf={created(e,{modifiers:{lazy:t,trim:n,number:s}},r){e[ds]=Zr(r);const i=s||r.props&&r.props.type==="number";Rt(e,t?"change":"input",o=>{if(o.target.composing)return;let l=e.value;n&&(l=l.trim()),i&&(l=xs(l)),e[ds](l)}),n&&Rt(e,"change",()=>{e.value=e.value.trim()}),t||(Rt(e,"compositionstart",xa),Rt(e,"compositionend",ei),Rt(e,"change",ei))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:r,number:i}},o){if(e[ds]=Zr(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?xs(e.value):e.value,c=t??"";l!==c&&(document.activeElement===e&&e.type!=="range"&&(s&&t===n||r&&e.value.trim()===c)||(e.value=c))}},Ea=["ctrl","shift","alt","meta"],Ca={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Ea.some(n=>e[`${n}Key`]&&!t.includes(n))},Gf=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(r,...i)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=r=>{if(!("key"in r))return;const i=at(r.key);if(t.some(o=>o===i||Aa[o]===i))return e(r)})},Po=ue({patchProp:Sa},na);let Yt,ti=!1;function Ra(){return Yt||(Yt=Oc(Po))}function Ma(){return Yt=ti?Yt:Pc(Po),ti=!0,Yt}const Yf=(...e)=>{const t=Ra().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=Io(s);if(!r)return;const i=t._component;!q(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=n(r,!1,Lo(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t},Jf=(...e)=>{const t=Ma().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=Io(s);if(r)return n(r,!0,Lo(r))},t};function Lo(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Io(e){return le(e)?document.querySelector(e):e}const Oa=window.__VP_SITE_DATA__;function No(e){return _i()?(dl(e),!0):!1}const hs=new WeakMap,Pa=(...e)=>{var t;const n=e[0],s=(t=Tt())==null?void 0:t.proxy;if(s==null&&!ro())throw new Error("injectLocal must be called in setup");return s&&hs.has(s)&&n in hs.get(s)?hs.get(s)[n]:bt(...e)},Fo=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const zf=e=>e!=null,La=Object.prototype.toString,Ia=e=>La.call(e)==="[object Object]",ct=()=>{},ni=Na();function Na(){var e,t;return Fo&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function or(e,t){function n(...s){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(r).catch(i)})}return n}const Ho=e=>e();function Do(e,t={}){let n,s,r=ct;const i=c=>{clearTimeout(c),r(),r=ct};let o;return c=>{const f=ce(e),a=ce(t.maxWait);return n&&i(n),f<=0||a!==void 0&&a<=0?(s&&(i(s),s=null),Promise.resolve(c())):new Promise((d,m)=>{r=t.rejectOnCancel?m:d,o=c,a&&!s&&(s=setTimeout(()=>{n&&i(n),s=null,d(o())},a)),n=setTimeout(()=>{s&&i(s),s=null,d(c())},f)})}}function Fa(...e){let t=0,n,s=!0,r=ct,i,o,l,c,f;!fe(e[0])&&typeof e[0]=="object"?{delay:o,trailing:l=!0,leading:c=!0,rejectOnCancel:f=!1}=e[0]:[o,l=!0,c=!0,f=!1]=e;const a=()=>{n&&(clearTimeout(n),n=void 0,r(),r=ct)};return m=>{const v=ce(o),b=Date.now()-t,_=()=>i=m();return a(),v<=0?(t=Date.now(),_()):(b>v&&(c||!s)?(t=Date.now(),_()):l&&(i=new Promise((U,L)=>{r=f?L:U,n=setTimeout(()=>{t=Date.now(),s=!0,U(_()),a()},Math.max(0,v-b))})),!c&&!n&&(n=setTimeout(()=>s=!0,v)),s=!1,i)}}function Ha(e=Ho,t={}){const{initialState:n="active"}=t,s=lr(n==="active");function r(){s.value=!1}function i(){s.value=!0}const o=(...l)=>{s.value&&e(...l)};return{isActive:Wn(s),pause:r,resume:i,eventFilter:o}}function si(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function Da(e){return Tt()}function ps(e){return Array.isArray(e)?e:[e]}function lr(...e){if(e.length!==1)return jl(...e);const t=e[0];return typeof t=="function"?Wn(Hl(()=>({get:t,set:ct}))):De(t)}function $a(e,t=200,n={}){return or(Do(t,n),e)}function ja(e,t=200,n=!1,s=!0,r=!1){return or(Fa(t,n,s,r),e)}function $o(e,t,n={}){const{eventFilter:s=Ho,...r}=n;return Ie(e,or(s,t),r)}function Va(e,t,n={}){const{eventFilter:s,initialState:r="active",...i}=n,{eventFilter:o,pause:l,resume:c,isActive:f}=Ha(s,{initialState:r});return{stop:$o(e,t,{...i,eventFilter:o}),pause:l,resume:c,isActive:f}}function Jn(e,t=!0,n){Da()?Dt(e,n):t?e():Bn(e)}function Qf(e,t,n={}){const{debounce:s=0,maxWait:r=void 0,...i}=n;return $o(e,t,{...i,eventFilter:Do(s,{maxWait:r})})}function ka(e,t,n){return Ie(e,t,{...n,immediate:!0})}function Zf(e,t,n){let s;fe(n)?s={evaluating:n}:s={};const{lazy:r=!1,evaluating:i=void 0,shallow:o=!0,onError:l=ct}=s,c=Ee(!r),f=o?Ee(t):De(t);let a=0;return rr(async d=>{if(!c.value)return;a++;const m=a;let v=!1;i&&Promise.resolve().then(()=>{i.value=!0});try{const b=await e(_=>{d(()=>{i&&(i.value=!1),v||_()})});m===a&&(f.value=b)}catch(b){l(b)}finally{i&&m===a&&(i.value=!1),v=!0}}),r?re(()=>(c.value=!0,f.value)):f}const je=Fo?window:void 0;function cr(e){var t;const n=ce(e);return(t=n==null?void 0:n.$el)!=null?t:n}function Qe(...e){const t=[],n=()=>{t.forEach(l=>l()),t.length=0},s=(l,c,f,a)=>(l.addEventListener(c,f,a),()=>l.removeEventListener(c,f,a)),r=re(()=>{const l=ps(ce(e[0])).filter(c=>c!=null);return l.every(c=>typeof c!="string")?l:void 0}),i=ka(()=>{var l,c;return[(c=(l=r.value)==null?void 0:l.map(f=>cr(f)))!=null?c:[je].filter(f=>f!=null),ps(ce(r.value?e[1]:e[0])),ps(zs(r.value?e[2]:e[1])),ce(r.value?e[3]:e[2])]},([l,c,f,a])=>{if(n(),!(l!=null&&l.length)||!(c!=null&&c.length)||!(f!=null&&f.length))return;const d=Ia(a)?{...a}:a;t.push(...l.flatMap(m=>c.flatMap(v=>f.map(b=>s(m,v,b,d)))))},{flush:"post"}),o=()=>{i(),n()};return No(n),o}function Ua(){const e=Ee(!1),t=Tt();return t&&Dt(()=>{e.value=!0},t),e}function Wa(e){const t=Ua();return re(()=>(t.value,!!e()))}function Ba(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function eu(...e){let t,n,s={};e.length===3?(t=e[0],n=e[1],s=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,n=e[0],s=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);const{target:r=je,eventName:i="keydown",passive:o=!1,dedupe:l=!1}=s,c=Ba(t);return Qe(r,i,a=>{a.repeat&&ce(l)||c(a)&&n(a)},o)}const Ka=Symbol("vueuse-ssr-width");function qa(){const e=ro()?Pa(Ka,null):null;return typeof e=="number"?e:void 0}function jo(e,t={}){const{window:n=je,ssrWidth:s=qa()}=t,r=Wa(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function"),i=Ee(typeof s=="number"),o=Ee(),l=Ee(!1),c=f=>{l.value=f.matches};return rr(()=>{if(i.value){i.value=!r.value;const f=ce(e).split(",");l.value=f.some(a=>{const d=a.includes("not all"),m=a.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),v=a.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/);let b=!!(m||v);return m&&b&&(b=s>=si(m[1])),v&&b&&(b=s<=si(v[1])),d?!b:b});return}r.value&&(o.value=n.matchMedia(ce(e)),l.value=o.value.matches)}),Qe(o,"change",c,{passive:!0}),re(()=>l.value)}const yn=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},_n="__vueuse_ssr_handlers__",Ga=Xa();function Xa(){return _n in yn||(yn[_n]=yn[_n]||{}),yn[_n]}function Vo(e,t){return Ga[e]||t}function ko(e){return jo("(prefers-color-scheme: dark)",e)}function Ya(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Ja={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},ri="vueuse-storage";function ar(e,t,n,s={}){var r;const{flush:i="pre",deep:o=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:f=!1,shallow:a,window:d=je,eventFilter:m,onError:v=A=>{console.error(A)},initOnMounted:b}=s,_=(a?Ee:De)(typeof t=="function"?t():t),U=re(()=>ce(e));if(!n)try{n=Vo("getDefaultStorage",()=>{var A;return(A=je)==null?void 0:A.localStorage})()}catch(A){v(A)}if(!n)return _;const L=ce(t),D=Ya(L),p=(r=s.serializer)!=null?r:Ja[D],{pause:g,resume:R}=Va(_,()=>M(_.value),{flush:i,deep:o,eventFilter:m});Ie(U,()=>T(),{flush:i}),d&&l&&Jn(()=>{n instanceof Storage?Qe(d,"storage",T,{passive:!0}):Qe(d,ri,O),b&&T()}),b||T();function $(A,w){if(d){const F={key:U.value,oldValue:A,newValue:w,storageArea:n};d.dispatchEvent(n instanceof Storage?new StorageEvent("storage",F):new CustomEvent(ri,{detail:F}))}}function M(A){try{const w=n.getItem(U.value);if(A==null)$(w,null),n.removeItem(U.value);else{const F=p.write(A);w!==F&&(n.setItem(U.value,F),$(w,F))}}catch(w){v(w)}}function V(A){const w=A?A.newValue:n.getItem(U.value);if(w==null)return c&&L!=null&&n.setItem(U.value,p.write(L)),L;if(!A&&f){const F=p.read(w);return typeof f=="function"?f(F,L):D==="object"&&!Array.isArray(F)?{...L,...F}:F}else return typeof w!="string"?w:p.read(w)}function T(A){if(!(A&&A.storageArea!==n)){if(A&&A.key==null){_.value=L;return}if(!(A&&A.key!==U.value)){g();try{(A==null?void 0:A.newValue)!==p.write(_.value)&&(_.value=V(A))}catch(w){v(w)}finally{A?Bn(R):R()}}}}function O(A){T(A.detail)}return _}const za="*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}";function Qa(e={}){const{selector:t="html",attribute:n="class",initialValue:s="auto",window:r=je,storage:i,storageKey:o="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:f,disableTransition:a=!0}=e,d={auto:"",light:"light",dark:"dark",...e.modes||{}},m=ko({window:r}),v=re(()=>m.value?"dark":"light"),b=c||(o==null?lr(s):ar(o,s,i,{window:r,listenToStorageChanges:l})),_=re(()=>b.value==="auto"?v.value:b.value),U=Vo("updateHTMLAttrs",(g,R,$)=>{const M=typeof g=="string"?r==null?void 0:r.document.querySelector(g):cr(g);if(!M)return;const V=new Set,T=new Set;let O=null;if(R==="class"){const w=$.split(/\s/g);Object.values(d).flatMap(F=>(F||"").split(/\s/g)).filter(Boolean).forEach(F=>{w.includes(F)?V.add(F):T.add(F)})}else O={key:R,value:$};if(V.size===0&&T.size===0&&O===null)return;let A;a&&(A=r.document.createElement("style"),A.appendChild(document.createTextNode(za)),r.document.head.appendChild(A));for(const w of V)M.classList.add(w);for(const w of T)M.classList.remove(w);O&&M.setAttribute(O.key,O.value),a&&(r.getComputedStyle(A).opacity,document.head.removeChild(A))});function L(g){var R;U(t,n,(R=d[g])!=null?R:g)}function D(g){e.onChanged?e.onChanged(g,L):L(g)}Ie(_,D,{flush:"post",immediate:!0}),Jn(()=>D(_.value));const p=re({get(){return f?b.value:_.value},set(g){b.value=g}});return Object.assign(p,{store:b,system:v,state:_})}function Za(e={}){const{valueDark:t="dark",valueLight:n=""}=e,s=Qa({...e,onChanged:(o,l)=>{var c;e.onChanged?(c=e.onChanged)==null||c.call(e,o==="dark",l,o):l(o)},modes:{dark:t,light:n}}),r=re(()=>s.system.value);return re({get(){return s.value==="dark"},set(o){const l=o?"dark":"light";r.value===l?s.value="auto":s.value=l}})}function gs(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}const ii=1;function ef(e,t={}){const{throttle:n=0,idle:s=200,onStop:r=ct,onScroll:i=ct,offset:o={left:0,right:0,top:0,bottom:0},eventListenerOptions:l={capture:!1,passive:!0},behavior:c="auto",window:f=je,onError:a=M=>{console.error(M)}}=t,d=Ee(0),m=Ee(0),v=re({get(){return d.value},set(M){_(M,void 0)}}),b=re({get(){return m.value},set(M){_(void 0,M)}});function _(M,V){var T,O,A,w;if(!f)return;const F=ce(e);if(!F)return;(A=F instanceof Document?f.document.body:F)==null||A.scrollTo({top:(T=ce(V))!=null?T:b.value,left:(O=ce(M))!=null?O:v.value,behavior:ce(c)});const Y=((w=F==null?void 0:F.document)==null?void 0:w.documentElement)||(F==null?void 0:F.documentElement)||F;v!=null&&(d.value=Y.scrollLeft),b!=null&&(m.value=Y.scrollTop)}const U=Ee(!1),L=Ft({left:!0,right:!1,top:!0,bottom:!1}),D=Ft({left:!1,right:!1,top:!1,bottom:!1}),p=M=>{U.value&&(U.value=!1,D.left=!1,D.right=!1,D.top=!1,D.bottom=!1,r(M))},g=$a(p,n+s),R=M=>{var V;if(!f)return;const T=((V=M==null?void 0:M.document)==null?void 0:V.documentElement)||(M==null?void 0:M.documentElement)||cr(M),{display:O,flexDirection:A,direction:w}=getComputedStyle(T),F=w==="rtl"?-1:1,Y=T.scrollLeft;D.left=Yd.value;const ie=Math.abs(Y*F)<=(o.left||0),W=Math.abs(Y*F)+T.clientWidth>=T.scrollWidth-(o.right||0)-ii;O==="flex"&&A==="row-reverse"?(L.left=W,L.right=ie):(L.left=ie,L.right=W),d.value=Y;let X=T.scrollTop;M===f.document&&!X&&(X=f.document.body.scrollTop),D.top=Xm.value;const j=Math.abs(X)<=(o.top||0),oe=Math.abs(X)+T.clientHeight>=T.scrollHeight-(o.bottom||0)-ii;O==="flex"&&A==="column-reverse"?(L.top=oe,L.bottom=j):(L.top=j,L.bottom=oe),m.value=X},$=M=>{var V;if(!f)return;const T=(V=M.target.documentElement)!=null?V:M.target;R(T),U.value=!0,g(M),i(M)};return Qe(e,"scroll",n?ja($,n,!0,!1):$,l),Jn(()=>{try{const M=ce(e);if(!M)return;R(M)}catch(M){a(M)}}),Qe(e,"scrollend",p,l),{x:v,y:b,isScrolling:U,arrivedState:L,directions:D,measure(){const M=ce(e);f&&M&&R(M)}}}function tu(e,t,n={}){const{window:s=je}=n;return ar(e,t,s==null?void 0:s.localStorage,n)}function Uo(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const ms=new WeakMap;function nu(e,t=!1){const n=Ee(t);let s=null,r="";Ie(lr(e),l=>{const c=gs(ce(l));if(c){const f=c;if(ms.get(f)||ms.set(f,f.style.overflow),f.style.overflow!=="hidden"&&(r=f.style.overflow),f.style.overflow==="hidden")return n.value=!0;if(n.value)return f.style.overflow="hidden"}},{immediate:!0});const i=()=>{const l=gs(ce(e));!l||n.value||(ni&&(s=Qe(l,"touchmove",c=>{tf(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{const l=gs(ce(e));!l||!n.value||(ni&&(s==null||s()),l.style.overflow=r,ms.delete(l),n.value=!1)};return No(o),re({get(){return n.value},set(l){l?i():o()}})}function su(e,t,n={}){const{window:s=je}=n;return ar(e,t,s==null?void 0:s.sessionStorage,n)}function ru(e={}){const{window:t=je,...n}=e;return ef(t,n)}function iu(e={}){const{window:t=je,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:s=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:i=!0,type:o="inner"}=e,l=Ee(n),c=Ee(s),f=()=>{if(t)if(o==="outer")l.value=t.outerWidth,c.value=t.outerHeight;else if(o==="visual"&&t.visualViewport){const{width:d,height:m,scale:v}=t.visualViewport;l.value=Math.round(d*v),c.value=Math.round(m*v)}else i?(l.value=t.innerWidth,c.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,c.value=t.document.documentElement.clientHeight)};f(),Jn(f);const a={passive:!0};if(Qe("resize",f,a),t&&o==="visual"&&t.visualViewport&&Qe(t.visualViewport,"resize",f,a),r){const d=jo("(orientation: portrait)");Ie(d,()=>f())}return{width:l,height:c}}const vs={};var ys={};const Wo=/^(?:[a-z]+:|\/\/)/i,nf="vitepress-theme-appearance",sf=/#.*$/,rf=/[?#].*$/,of=/(?:(^|\/)index)?\.(?:md|html)$/,ye=typeof document<"u",Bo={relativePath:"404.md",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function lf(e,t,n=!1){if(t===void 0)return!1;if(e=oi(`/${e}`),n)return new RegExp(t).test(e);if(oi(t)!==e)return!1;const s=t.match(sf);return s?(ye?location.hash:"")===s[0]:!0}function oi(e){return decodeURI(e).replace(rf,"").replace(of,"$1")}function cf(e){return Wo.test(e)}function af(e,t){return Object.keys((e==null?void 0:e.locales)||{}).find(n=>n!=="root"&&!cf(n)&&lf(t,`/${n}/`,!0))||"root"}function ff(e,t){var s,r,i,o,l,c,f;const n=af(e,t);return Object.assign({},e,{localeIndex:n,lang:((s=e.locales[n])==null?void 0:s.lang)??e.lang,dir:((r=e.locales[n])==null?void 0:r.dir)??e.dir,title:((i=e.locales[n])==null?void 0:i.title)??e.title,titleTemplate:((o=e.locales[n])==null?void 0:o.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:qo(e.head,((c=e.locales[n])==null?void 0:c.head)??[]),themeConfig:{...e.themeConfig,...(f=e.locales[n])==null?void 0:f.themeConfig}})}function Ko(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=uf(e.title,s);return n===r.slice(3)?n:`${n}${r}`}function uf(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function df(e,t){const[n,s]=t;if(n!=="meta")return!1;const r=Object.entries(s)[0];return r==null?!1:e.some(([i,o])=>i===n&&o[r[0]]===r[1])}function qo(e,t){return[...e.filter(n=>!df(t,n)),...t]}const hf=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,pf=/^[a-z]:/i;function li(e){const t=pf.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(hf,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const _s=new Set;function gf(e){if(_s.size===0){const n=typeof process=="object"&&(ys==null?void 0:ys.VITE_EXTRA_EXTENSIONS)||(vs==null?void 0:vs.VITE_EXTRA_EXTENSIONS)||"";("3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip"+(n&&typeof n=="string"?","+n:"")).split(",").forEach(s=>_s.add(s))}const t=e.split(".").pop();return t==null||!_s.has(t.toLowerCase())}function ou(e){return e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}const mf=Symbol(),St=Ee(Oa);function lu(e){const t=re(()=>ff(St.value,e.data.relativePath)),n=t.value.appearance,s=n==="force-dark"?De(!0):n==="force-auto"?ko():n?Za({storageKey:nf,initialValue:()=>n==="dark"?"dark":"auto",...typeof n=="object"?n:{}}):De(!1),r=De(ye?location.hash:"");return ye&&window.addEventListener("hashchange",()=>{r.value=location.hash}),Ie(()=>e.data,()=>{r.value=ye?location.hash:""}),{site:t,theme:re(()=>t.value.themeConfig),page:re(()=>e.data),frontmatter:re(()=>e.data.frontmatter),params:re(()=>e.data.params),lang:re(()=>t.value.lang),dir:re(()=>e.data.frontmatter.dir||t.value.dir),localeIndex:re(()=>t.value.localeIndex||"root"),title:re(()=>Ko(t.value,e.data)),description:re(()=>e.data.description||t.value.description),isDark:s,hash:re(()=>r.value)}}function vf(){const e=bt(mf);if(!e)throw new Error("vitepress data not properly injected in app");return e}function yf(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function ci(e){return Wo.test(e)||!e.startsWith("/")?e:yf(St.value.base,e)}function _f(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),ye){const n="/";t=li(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];if(s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),!s)return null;t=`${n}assets/${t}.${s}.js`}else t=`./${li(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let Rn=[];function cu(e){Rn.push(e),qn(()=>{Rn=Rn.filter(t=>t!==e)})}function bf(){let e=St.value.scrollOffset,t=0,n=24;if(typeof e=="object"&&"padding"in e&&(n=e.padding,e=e.selector),typeof e=="number")t=e;else if(typeof e=="string")t=ai(e,n);else if(Array.isArray(e))for(const s of e){const r=ai(s,n);if(r){t=r;break}}return t}function ai(e,t){const n=document.querySelector(e);if(!n)return 0;const s=n.getBoundingClientRect().bottom;return s<0?0:s+t}const wf=Symbol(),Go="http://a.com",Sf=()=>({path:"/",component:null,data:Bo});function au(e,t){const n=Ft(Sf()),s={route:n,go:r};async function r(l=ye?location.href:"/"){var c,f;l=bs(l),await((c=s.onBeforeRouteChange)==null?void 0:c.call(s,l))!==!1&&(ye&&l!==bs(location.href)&&(history.replaceState({scrollPosition:window.scrollY},""),history.pushState({},"",l)),await o(l),await((f=s.onAfterRouteChange??s.onAfterRouteChanged)==null?void 0:f(l)))}let i=null;async function o(l,c=0,f=!1){var m,v;if(await((m=s.onBeforePageLoad)==null?void 0:m.call(s,l))===!1)return;const a=new URL(l,Go),d=i=a.pathname;try{let b=await e(d);if(!b)throw new Error(`Page not found: ${d}`);if(i===d){i=null;const{default:_,__pageData:U}=b;if(!_)throw new Error(`Invalid route component: ${_}`);await((v=s.onAfterPageLoad)==null?void 0:v.call(s,l)),n.path=ye?d:ci(d),n.component=xn(_),n.data=xn(U),ye&&Bn(()=>{let L=St.value.base+U.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!St.value.cleanUrls&&!L.endsWith("/")&&(L+=".html"),L!==a.pathname&&(a.pathname=L,l=L+a.search+a.hash,history.replaceState({},"",l)),a.hash&&!c){let D=null;try{D=document.getElementById(decodeURIComponent(a.hash).slice(1))}catch(p){console.warn(p)}if(D){fi(D,a.hash);return}}window.scrollTo(0,c)})}}catch(b){if(!/fetch|Page not found/.test(b.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(b),!f)try{const _=await fetch(St.value.base+"hashmap.json");window.__VP_HASH_MAP__=await _.json(),await o(l,c,!0);return}catch{}if(i===d){i=null,n.path=ye?d:ci(d),n.component=t?xn(t):null;const _=ye?d.replace(/(^|\/)$/,"$1index").replace(/(\.html)?$/,".md").replace(/^\//,""):"404.md";n.data={...Bo,relativePath:_}}}}return ye&&(history.state===null&&history.replaceState({},""),window.addEventListener("click",l=>{if(l.defaultPrevented||!(l.target instanceof Element)||l.target.closest("button")||l.button!==0||l.ctrlKey||l.shiftKey||l.altKey||l.metaKey)return;const c=l.target.closest("a");if(!c||c.closest(".vp-raw")||c.hasAttribute("download")||c.hasAttribute("target"))return;const f=c.getAttribute("href")??(c instanceof SVGAElement?c.getAttribute("xlink:href"):null);if(f==null)return;const{href:a,origin:d,pathname:m,hash:v,search:b}=new URL(f,c.baseURI),_=new URL(location.href);d===_.origin&&gf(m)&&(l.preventDefault(),m===_.pathname&&b===_.search?(v!==_.hash&&(history.pushState({},"",a),window.dispatchEvent(new HashChangeEvent("hashchange",{oldURL:_.href,newURL:a}))),v?fi(c,v,c.classList.contains("header-anchor")):window.scrollTo(0,0)):r(a))},{capture:!0}),window.addEventListener("popstate",async l=>{var f;if(l.state===null)return;const c=bs(location.href);await o(c,l.state&&l.state.scrollPosition||0),await((f=s.onAfterRouteChange??s.onAfterRouteChanged)==null?void 0:f(c))}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function Tf(){const e=bt(wf);if(!e)throw new Error("useRouter() is called without provider.");return e}function Xo(){return Tf().route}function fi(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(r){console.warn(r)}if(s){let r=function(){!n||Math.abs(o-window.scrollY)>window.innerHeight?window.scrollTo(0,o):window.scrollTo({left:0,top:o,behavior:"smooth"})};const i=parseInt(window.getComputedStyle(s).paddingTop,10),o=window.scrollY+s.getBoundingClientRect().top-bf()+i;requestAnimationFrame(r)}}function bs(e){const t=new URL(e,Go);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),St.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const bn=()=>Rn.forEach(e=>e()),fu=Zs({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=Xo(),{frontmatter:n,site:s}=vf();return Ie(n,bn,{deep:!0,flush:"post"}),()=>Ds(e.as,s.value.contentProps??{style:{position:"relative"}},[t.component?Ds(t.component,{onVnodeMounted:bn,onVnodeUpdated:bn,onVnodeUnmounted:bn}):"404 Page Not Found"])}}),uu=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},xf="modulepreload",Ef=function(e){return"/"+e},ui={},du=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),l=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));r=Promise.allSettled(n.map(c=>{if(c=Ef(c),c in ui)return;ui[c]=!0;const f=c.endsWith(".css"),a=f?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${a}`))return;const d=document.createElement("link");if(d.rel=f?"stylesheet":xf,f||(d.as="script"),d.crossOrigin="",d.href=c,l&&d.setAttribute("nonce",l),document.head.appendChild(d),f)return new Promise((m,v)=>{d.addEventListener("load",m),d.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${c}`)))})}))}function i(o){const l=new Event("vite:preloadError",{cancelable:!0});if(l.payload=o,window.dispatchEvent(l),!l.defaultPrevented)throw o}return r.then(o=>{for(const l of o||[])l.status==="rejected"&&i(l.reason);return t().catch(i)})},hu=Zs({setup(e,{slots:t}){const n=De(!1);return Dt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function pu(){ye&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const s=(n=t.parentElement)==null?void 0:n.parentElement;if(!s)return;const r=Array.from(s.querySelectorAll("input")).indexOf(t);if(r<0)return;const i=s.querySelector(".blocks");if(!i)return;const o=Array.from(i.children).find(f=>f.classList.contains("active"));if(!o)return;const l=i.children[r];if(!l||o===l)return;o.classList.remove("active"),l.classList.add("active");const c=s==null?void 0:s.querySelector(`label[for="${t.id}"]`);c==null||c.scrollIntoView({block:"nearest"})}})}function gu(){if(ye){const e=new WeakMap;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const r=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!r||!i)return;const o=/language-(shellscript|shell|bash|sh|zsh)/.test(r.className),l=[".vp-copy-ignore",".diff.remove"],c=i.cloneNode(!0);c.querySelectorAll(l.join(",")).forEach(a=>a.remove());let f=c.textContent||"";o&&(f=f.replace(/^ *(\$|>) /gm,"").trim()),Cf(f).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const a=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,a)})}})}}async function Cf(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function mu(e,t){let n=!0,s=[];const r=i=>{if(n){n=!1,i.forEach(l=>{const c=ws(l);for(const f of document.head.children)if(f.isEqualNode(c)){s.push(f);return}});return}const o=i.map(ws);s.forEach((l,c)=>{const f=o.findIndex(a=>a==null?void 0:a.isEqualNode(l??null));f!==-1?delete o[f]:(l==null||l.remove(),delete s[c])}),o.forEach(l=>l&&document.head.appendChild(l)),s=[...s,...o].filter(Boolean)};rr(()=>{const i=e.data,o=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[],f=Ko(o,i);f!==document.title&&(document.title=f);const a=l||o.description;let d=document.querySelector("meta[name=description]");d?d.getAttribute("content")!==a&&d.setAttribute("content",a):ws(["meta",{name:"description",content:a}]),r(qo(o.head,Rf(c)))})}function ws([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),e==="script"&&t.async==null&&(s.async=!1),s}function Af(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function Rf(e){return e.filter(t=>!Af(t))}const Ss=new Set,Yo=()=>document.createElement("link"),Mf=e=>{const t=Yo();t.rel="prefetch",t.href=e,document.head.appendChild(t)},Of=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let wn;const Pf=ye&&(wn=Yo())&&wn.relList&&wn.relList.supports&&wn.relList.supports("prefetch")?Mf:Of;function vu(){if(!ye||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(o=>{if(o.isIntersecting){const l=o.target;n.unobserve(l);const{pathname:c}=l;if(!Ss.has(c)){Ss.add(c);const f=_f(c);f&&Pf(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{hostname:o,pathname:l}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||i.target!=="_blank"&&o===location.hostname&&(l!==location.pathname?n.observe(i):Ss.add(l))})})};Dt(s);const r=Xo();Ie(()=>r.path,s),qn(()=>{n&&n.disconnect()})}export{Ji as $,bf as A,Df as B,Ff as C,Ee as D,cu as E,Te as F,ae as G,Hf as H,Wo as I,Xo as J,Bc as K,bt as L,iu as M,Ws as N,eu as O,Bn as P,ru as Q,ye as R,Wn as S,Kf as T,Nf as U,du as V,nu as W,Tc as X,jf as Y,Xf as Z,uu as _,Eo as a,Gf as a0,Vf as a1,Ds as a2,Yf as a3,Tt as a4,Wf as a5,mu as a6,wf as a7,lu as a8,mf as a9,fu as aa,hu as ab,St as ac,au as ad,_f as ae,Jf as af,vu as ag,gu as ah,pu as ai,Tf as aj,ce as ak,ps as al,cr as am,zf as an,No as ao,Zf as ap,su as aq,tu as ar,Qf as as,Qe as at,Lf as au,qf as av,fe as aw,If as ax,xn as ay,ou as az,Fs as b,Uf as c,Zs as d,Bf as e,gf as f,ci as g,re as h,cf as i,xo as j,zs as k,lf as l,jo as m,Bs as n,Ns as o,De as p,Ie as q,$f as r,rr as s,fl as t,vf as u,Dt as v,Xl as w,qn as x,kf as y,ac as z}; diff --git a/assets/chunks/ganttDiagram-JELNMOA3.CLnTOziW.js b/assets/chunks/ganttDiagram-JELNMOA3.CLnTOziW.js new file mode 100644 index 000000000..20a0160a1 --- /dev/null +++ b/assets/chunks/ganttDiagram-JELNMOA3.CLnTOziW.js @@ -0,0 +1,267 @@ +import{aA as wt,aB as _t,_ as c,g as ue,s as de,t as fe,q as he,a as me,b as ke,c as lt,d as vt,aF as ye,aG as ge,aH as ve,e as pe,S as Te,aI as xe,aJ as X,l as nt,aK as be,aL as Rt,aM as Ht,aN as we,aO as _e,aP as De,aQ as Se,aR as Me,aS as Ce,aT as Ee,aU as Bt,aV as Gt,aW as jt,aX as Xt,aY as Ut,aZ as Ie,k as Ye,j as Ae,z as $e,u as Fe}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var Kt={exports:{}};(function(t,a){(function(r,i){t.exports=i()})(wt,function(){var r="day";return function(i,n,k){var y=function(F){return F.add(4-F.isoWeekday(),r)},_=n.prototype;_.isoWeekYear=function(){return y(this).year()},_.isoWeek=function(F){if(!this.$utils().u(F))return this.add(7*(F-this.isoWeek()),r);var b,L,V,N,z=y(this),C=(b=this.isoWeekYear(),L=this.$u,V=(L?k.utc:k)().year(b).startOf("year"),N=4-V.isoWeekday(),V.isoWeekday()>4&&(N+=7),V.add(N,r));return z.diff(C,"week")+1},_.isoWeekday=function(F){return this.$utils().u(F)?this.day()||7:this.day(this.day()%7?F:F-7)};var W=_.startOf;_.startOf=function(F,b){var L=this.$utils(),V=!!L.u(b)||b;return L.p(F)==="isoweek"?V?this.date(this.date()-(this.isoWeekday()-1)).startOf("day"):this.date(this.date()-1-(this.isoWeekday()-1)+7).endOf("day"):W.bind(this)(F,b)}}})})(Kt);var Le=Kt.exports;const We=_t(Le);var Jt={exports:{}};(function(t,a){(function(r,i){t.exports=i()})(wt,function(){var r={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},i=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,n=/\d/,k=/\d\d/,y=/\d\d?/,_=/\d*[^-_:/,()\s\d]+/,W={},F=function(D){return(D=+D)+(D>68?1900:2e3)},b=function(D){return function(S){this[D]=+S}},L=[/[+-]\d\d:?(\d\d)?|Z/,function(D){(this.zone||(this.zone={})).offset=function(S){if(!S||S==="Z")return 0;var O=S.match(/([+-]|\d\d)/g),A=60*O[1]+(+O[2]||0);return A===0?0:O[0]==="+"?-A:A}(D)}],V=function(D){var S=W[D];return S&&(S.indexOf?S:S.s.concat(S.f))},N=function(D,S){var O,A=W.meridiem;if(A){for(var R=1;R<=24;R+=1)if(D.indexOf(A(R,0,S))>-1){O=R>12;break}}else O=D===(S?"pm":"PM");return O},z={A:[_,function(D){this.afternoon=N(D,!1)}],a:[_,function(D){this.afternoon=N(D,!0)}],Q:[n,function(D){this.month=3*(D-1)+1}],S:[n,function(D){this.milliseconds=100*+D}],SS:[k,function(D){this.milliseconds=10*+D}],SSS:[/\d{3}/,function(D){this.milliseconds=+D}],s:[y,b("seconds")],ss:[y,b("seconds")],m:[y,b("minutes")],mm:[y,b("minutes")],H:[y,b("hours")],h:[y,b("hours")],HH:[y,b("hours")],hh:[y,b("hours")],D:[y,b("day")],DD:[k,b("day")],Do:[_,function(D){var S=W.ordinal,O=D.match(/\d+/);if(this.day=O[0],S)for(var A=1;A<=31;A+=1)S(A).replace(/\[|\]/g,"")===D&&(this.day=A)}],w:[y,b("week")],ww:[k,b("week")],M:[y,b("month")],MM:[k,b("month")],MMM:[_,function(D){var S=V("months"),O=(V("monthsShort")||S.map(function(A){return A.slice(0,3)})).indexOf(D)+1;if(O<1)throw new Error;this.month=O%12||O}],MMMM:[_,function(D){var S=V("months").indexOf(D)+1;if(S<1)throw new Error;this.month=S%12||S}],Y:[/[+-]?\d+/,b("year")],YY:[k,function(D){this.year=F(D)}],YYYY:[/\d{4}/,b("year")],Z:L,ZZ:L};function C(D){var S,O;S=D,O=W&&W.formats;for(var A=(D=S.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(p,v,g){var f=g&&g.toUpperCase();return v||O[g]||r[g]||O[f].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(o,l,h){return l||h.slice(1)})})).match(i),R=A.length,G=0;G-1)return new Date((x==="X"?1e3:1)*m);var e=C(x)(m),w=e.year,$=e.month,Y=e.day,I=e.hours,j=e.minutes,M=e.seconds,Q=e.milliseconds,st=e.zone,ot=e.week,ft=new Date,ht=Y||(w||$?1:ft.getDate()),ct=w||ft.getFullYear(),H=0;w&&!$||(H=$>0?$-1:ft.getMonth());var Z,U=I||0,rt=j||0,K=M||0,it=Q||0;return st?new Date(Date.UTC(ct,H,ht,U,rt,K,it+60*st.offset*1e3)):s?new Date(Date.UTC(ct,H,ht,U,rt,K,it)):(Z=new Date(ct,H,ht,U,rt,K,it),ot&&(Z=P(Z).week(ot).toDate()),Z)}catch{return new Date("")}}(E,u,T,O),this.init(),f&&f!==!0&&(this.$L=this.locale(f).$L),g&&E!=this.format(u)&&(this.$d=new Date("")),W={}}else if(u instanceof Array)for(var o=u.length,l=1;l<=o;l+=1){d[1]=u[l-1];var h=O.apply(this,d);if(h.isValid()){this.$d=h.$d,this.$L=h.$L,this.init();break}l===o&&(this.$d=new Date(""))}else R.call(this,G)}}})})(Jt);var Oe=Jt.exports;const Pe=_t(Oe);var te={exports:{}};(function(t,a){(function(r,i){t.exports=i()})(wt,function(){return function(r,i){var n=i.prototype,k=n.format;n.format=function(y){var _=this,W=this.$locale();if(!this.isValid())return k.bind(this)(y);var F=this.$utils(),b=(y||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(L){switch(L){case"Q":return Math.ceil((_.$M+1)/3);case"Do":return W.ordinal(_.$D);case"gggg":return _.weekYear();case"GGGG":return _.isoWeekYear();case"wo":return W.ordinal(_.week(),"W");case"w":case"ww":return F.s(_.week(),L==="w"?1:2,"0");case"W":case"WW":return F.s(_.isoWeek(),L==="W"?1:2,"0");case"k":case"kk":return F.s(String(_.$H===0?24:_.$H),L==="k"?1:2,"0");case"X":return Math.floor(_.$d.getTime()/1e3);case"x":return _.$d.getTime();case"z":return"["+_.offsetName()+"]";case"zzz":return"["+_.offsetName("long")+"]";default:return L}});return k.bind(this)(b)}}})})(te);var Ve=te.exports;const Ne=_t(Ve);var ee={exports:{}};(function(t,a){(function(r,i){t.exports=i()})(wt,function(){var r,i,n=1e3,k=6e4,y=36e5,_=864e5,W=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,F=31536e6,b=2628e6,L=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/,V={years:F,months:b,days:_,hours:y,minutes:k,seconds:n,milliseconds:1,weeks:6048e5},N=function(E){return E instanceof R},z=function(E,T,d){return new R(E,d,T.$l)},C=function(E){return i.p(E)+"s"},D=function(E){return E<0},S=function(E){return D(E)?Math.ceil(E):Math.floor(E)},O=function(E){return Math.abs(E)},A=function(E,T){return E?D(E)?{negative:!0,format:""+O(E)+T}:{negative:!1,format:""+E+T}:{negative:!1,format:""}},R=function(){function E(d,u,p){var v=this;if(this.$d={},this.$l=p,d===void 0&&(this.$ms=0,this.parseFromMilliseconds()),u)return z(d*V[C(u)],this);if(typeof d=="number")return this.$ms=d,this.parseFromMilliseconds(),this;if(typeof d=="object")return Object.keys(d).forEach(function(o){v.$d[C(o)]=d[o]}),this.calMilliseconds(),this;if(typeof d=="string"){var g=d.match(L);if(g){var f=g.slice(2).map(function(o){return o!=null?Number(o):0});return this.$d.years=f[0],this.$d.months=f[1],this.$d.weeks=f[2],this.$d.days=f[3],this.$d.hours=f[4],this.$d.minutes=f[5],this.$d.seconds=f[6],this.calMilliseconds(),this}}return this}var T=E.prototype;return T.calMilliseconds=function(){var d=this;this.$ms=Object.keys(this.$d).reduce(function(u,p){return u+(d.$d[p]||0)*V[p]},0)},T.parseFromMilliseconds=function(){var d=this.$ms;this.$d.years=S(d/F),d%=F,this.$d.months=S(d/b),d%=b,this.$d.days=S(d/_),d%=_,this.$d.hours=S(d/y),d%=y,this.$d.minutes=S(d/k),d%=k,this.$d.seconds=S(d/n),d%=n,this.$d.milliseconds=d},T.toISOString=function(){var d=A(this.$d.years,"Y"),u=A(this.$d.months,"M"),p=+this.$d.days||0;this.$d.weeks&&(p+=7*this.$d.weeks);var v=A(p,"D"),g=A(this.$d.hours,"H"),f=A(this.$d.minutes,"M"),o=this.$d.seconds||0;this.$d.milliseconds&&(o+=this.$d.milliseconds/1e3,o=Math.round(1e3*o)/1e3);var l=A(o,"S"),h=d.negative||u.negative||v.negative||g.negative||f.negative||l.negative,m=g.format||f.format||l.format?"T":"",x=(h?"-":"")+"P"+d.format+u.format+v.format+m+g.format+f.format+l.format;return x==="P"||x==="-P"?"P0D":x},T.toJSON=function(){return this.toISOString()},T.format=function(d){var u=d||"YYYY-MM-DDTHH:mm:ss",p={Y:this.$d.years,YY:i.s(this.$d.years,2,"0"),YYYY:i.s(this.$d.years,4,"0"),M:this.$d.months,MM:i.s(this.$d.months,2,"0"),D:this.$d.days,DD:i.s(this.$d.days,2,"0"),H:this.$d.hours,HH:i.s(this.$d.hours,2,"0"),m:this.$d.minutes,mm:i.s(this.$d.minutes,2,"0"),s:this.$d.seconds,ss:i.s(this.$d.seconds,2,"0"),SSS:i.s(this.$d.milliseconds,3,"0")};return u.replace(W,function(v,g){return g||String(p[v])})},T.as=function(d){return this.$ms/V[C(d)]},T.get=function(d){var u=this.$ms,p=C(d);return p==="milliseconds"?u%=1e3:u=p==="weeks"?S(u/V[p]):this.$d[p],u||0},T.add=function(d,u,p){var v;return v=u?d*V[C(u)]:N(d)?d.$ms:z(d,this).$ms,z(this.$ms+v*(p?-1:1),this)},T.subtract=function(d,u){return this.add(d,u,!0)},T.locale=function(d){var u=this.clone();return u.$l=d,u},T.clone=function(){return z(this.$ms,this)},T.humanize=function(d){return r().add(this.$ms,"ms").locale(this.$l).fromNow(!d)},T.valueOf=function(){return this.asMilliseconds()},T.milliseconds=function(){return this.get("milliseconds")},T.asMilliseconds=function(){return this.as("milliseconds")},T.seconds=function(){return this.get("seconds")},T.asSeconds=function(){return this.as("seconds")},T.minutes=function(){return this.get("minutes")},T.asMinutes=function(){return this.as("minutes")},T.hours=function(){return this.get("hours")},T.asHours=function(){return this.as("hours")},T.days=function(){return this.get("days")},T.asDays=function(){return this.as("days")},T.weeks=function(){return this.get("weeks")},T.asWeeks=function(){return this.as("weeks")},T.months=function(){return this.get("months")},T.asMonths=function(){return this.as("months")},T.years=function(){return this.get("years")},T.asYears=function(){return this.as("years")},E}(),G=function(E,T,d){return E.add(T.years()*d,"y").add(T.months()*d,"M").add(T.days()*d,"d").add(T.hours()*d,"h").add(T.minutes()*d,"m").add(T.seconds()*d,"s").add(T.milliseconds()*d,"ms")};return function(E,T,d){r=d,i=d().$utils(),d.duration=function(v,g){var f=d.locale();return z(v,{$l:f},g)},d.isDuration=N;var u=T.prototype.add,p=T.prototype.subtract;T.prototype.add=function(v,g){return N(v)?G(this,v,1):u.bind(this)(v,g)},T.prototype.subtract=function(v,g){return N(v)?G(this,v,-1):p.bind(this)(v,g)}}})})(ee);var ze=ee.exports;const Re=_t(ze);var Mt=function(){var t=c(function(f,o,l,h){for(l=l||{},h=f.length;h--;l[f[h]]=o);return l},"o"),a=[6,8,10,12,13,14,15,16,17,18,20,21,22,23,24,25,26,27,28,29,30,31,33,35,36,38,40],r=[1,26],i=[1,27],n=[1,28],k=[1,29],y=[1,30],_=[1,31],W=[1,32],F=[1,33],b=[1,34],L=[1,9],V=[1,10],N=[1,11],z=[1,12],C=[1,13],D=[1,14],S=[1,15],O=[1,16],A=[1,19],R=[1,20],G=[1,21],E=[1,22],T=[1,23],d=[1,25],u=[1,35],p={trace:c(function(){},"trace"),yy:{},symbols_:{error:2,start:3,gantt:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NL:10,weekday:11,weekday_monday:12,weekday_tuesday:13,weekday_wednesday:14,weekday_thursday:15,weekday_friday:16,weekday_saturday:17,weekday_sunday:18,weekend:19,weekend_friday:20,weekend_saturday:21,dateFormat:22,inclusiveEndDates:23,topAxis:24,axisFormat:25,tickInterval:26,excludes:27,includes:28,todayMarker:29,title:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,section:36,clickStatement:37,taskTxt:38,taskData:39,click:40,callbackname:41,callbackargs:42,href:43,clickStatementDebug:44,$accept:0,$end:1},terminals_:{2:"error",4:"gantt",6:"EOF",8:"SPACE",10:"NL",12:"weekday_monday",13:"weekday_tuesday",14:"weekday_wednesday",15:"weekday_thursday",16:"weekday_friday",17:"weekday_saturday",18:"weekday_sunday",20:"weekend_friday",21:"weekend_saturday",22:"dateFormat",23:"inclusiveEndDates",24:"topAxis",25:"axisFormat",26:"tickInterval",27:"excludes",28:"includes",29:"todayMarker",30:"title",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",36:"section",38:"taskTxt",39:"taskData",40:"click",41:"callbackname",42:"callbackargs",43:"href"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[19,1],[19,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,2],[37,2],[37,3],[37,3],[37,4],[37,3],[37,4],[37,2],[44,2],[44,3],[44,3],[44,4],[44,3],[44,4],[44,2]],performAction:c(function(o,l,h,m,x,s,P){var e=s.length-1;switch(x){case 1:return s[e-1];case 2:this.$=[];break;case 3:s[e-1].push(s[e]),this.$=s[e-1];break;case 4:case 5:this.$=s[e];break;case 6:case 7:this.$=[];break;case 8:m.setWeekday("monday");break;case 9:m.setWeekday("tuesday");break;case 10:m.setWeekday("wednesday");break;case 11:m.setWeekday("thursday");break;case 12:m.setWeekday("friday");break;case 13:m.setWeekday("saturday");break;case 14:m.setWeekday("sunday");break;case 15:m.setWeekend("friday");break;case 16:m.setWeekend("saturday");break;case 17:m.setDateFormat(s[e].substr(11)),this.$=s[e].substr(11);break;case 18:m.enableInclusiveEndDates(),this.$=s[e].substr(18);break;case 19:m.TopAxis(),this.$=s[e].substr(8);break;case 20:m.setAxisFormat(s[e].substr(11)),this.$=s[e].substr(11);break;case 21:m.setTickInterval(s[e].substr(13)),this.$=s[e].substr(13);break;case 22:m.setExcludes(s[e].substr(9)),this.$=s[e].substr(9);break;case 23:m.setIncludes(s[e].substr(9)),this.$=s[e].substr(9);break;case 24:m.setTodayMarker(s[e].substr(12)),this.$=s[e].substr(12);break;case 27:m.setDiagramTitle(s[e].substr(6)),this.$=s[e].substr(6);break;case 28:this.$=s[e].trim(),m.setAccTitle(this.$);break;case 29:case 30:this.$=s[e].trim(),m.setAccDescription(this.$);break;case 31:m.addSection(s[e].substr(8)),this.$=s[e].substr(8);break;case 33:m.addTask(s[e-1],s[e]),this.$="task";break;case 34:this.$=s[e-1],m.setClickEvent(s[e-1],s[e],null);break;case 35:this.$=s[e-2],m.setClickEvent(s[e-2],s[e-1],s[e]);break;case 36:this.$=s[e-2],m.setClickEvent(s[e-2],s[e-1],null),m.setLink(s[e-2],s[e]);break;case 37:this.$=s[e-3],m.setClickEvent(s[e-3],s[e-2],s[e-1]),m.setLink(s[e-3],s[e]);break;case 38:this.$=s[e-2],m.setClickEvent(s[e-2],s[e],null),m.setLink(s[e-2],s[e-1]);break;case 39:this.$=s[e-3],m.setClickEvent(s[e-3],s[e-1],s[e]),m.setLink(s[e-3],s[e-2]);break;case 40:this.$=s[e-1],m.setLink(s[e-1],s[e]);break;case 41:case 47:this.$=s[e-1]+" "+s[e];break;case 42:case 43:case 45:this.$=s[e-2]+" "+s[e-1]+" "+s[e];break;case 44:case 46:this.$=s[e-3]+" "+s[e-2]+" "+s[e-1]+" "+s[e];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(a,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:17,12:r,13:i,14:n,15:k,16:y,17:_,18:W,19:18,20:F,21:b,22:L,23:V,24:N,25:z,26:C,27:D,28:S,29:O,30:A,31:R,33:G,35:E,36:T,37:24,38:d,40:u},t(a,[2,7],{1:[2,1]}),t(a,[2,3]),{9:36,11:17,12:r,13:i,14:n,15:k,16:y,17:_,18:W,19:18,20:F,21:b,22:L,23:V,24:N,25:z,26:C,27:D,28:S,29:O,30:A,31:R,33:G,35:E,36:T,37:24,38:d,40:u},t(a,[2,5]),t(a,[2,6]),t(a,[2,17]),t(a,[2,18]),t(a,[2,19]),t(a,[2,20]),t(a,[2,21]),t(a,[2,22]),t(a,[2,23]),t(a,[2,24]),t(a,[2,25]),t(a,[2,26]),t(a,[2,27]),{32:[1,37]},{34:[1,38]},t(a,[2,30]),t(a,[2,31]),t(a,[2,32]),{39:[1,39]},t(a,[2,8]),t(a,[2,9]),t(a,[2,10]),t(a,[2,11]),t(a,[2,12]),t(a,[2,13]),t(a,[2,14]),t(a,[2,15]),t(a,[2,16]),{41:[1,40],43:[1,41]},t(a,[2,4]),t(a,[2,28]),t(a,[2,29]),t(a,[2,33]),t(a,[2,34],{42:[1,42],43:[1,43]}),t(a,[2,40],{41:[1,44]}),t(a,[2,35],{43:[1,45]}),t(a,[2,36]),t(a,[2,38],{42:[1,46]}),t(a,[2,37]),t(a,[2,39])],defaultActions:{},parseError:c(function(o,l){if(l.recoverable)this.trace(o);else{var h=new Error(o);throw h.hash=l,h}},"parseError"),parse:c(function(o){var l=this,h=[0],m=[],x=[null],s=[],P=this.table,e="",w=0,$=0,Y=2,I=1,j=s.slice.call(arguments,1),M=Object.create(this.lexer),Q={yy:{}};for(var st in this.yy)Object.prototype.hasOwnProperty.call(this.yy,st)&&(Q.yy[st]=this.yy[st]);M.setInput(o,Q.yy),Q.yy.lexer=M,Q.yy.parser=this,typeof M.yylloc>"u"&&(M.yylloc={});var ot=M.yylloc;s.push(ot);var ft=M.options&&M.options.ranges;typeof Q.yy.parseError=="function"?this.parseError=Q.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ht(q){h.length=h.length-2*q,x.length=x.length-q,s.length=s.length-q}c(ht,"popStack");function ct(){var q;return q=m.pop()||M.lex()||I,typeof q!="number"&&(q instanceof Array&&(m=q,q=m.pop()),q=l.symbols_[q]||q),q}c(ct,"lex");for(var H,Z,U,rt,K={},it,J,zt,gt;;){if(Z=h[h.length-1],this.defaultActions[Z]?U=this.defaultActions[Z]:((H===null||typeof H>"u")&&(H=ct()),U=P[Z]&&P[Z][H]),typeof U>"u"||!U.length||!U[0]){var Dt="";gt=[];for(it in P[Z])this.terminals_[it]&&it>Y&>.push("'"+this.terminals_[it]+"'");M.showPosition?Dt="Parse error on line "+(w+1)+`: +`+M.showPosition()+` +Expecting `+gt.join(", ")+", got '"+(this.terminals_[H]||H)+"'":Dt="Parse error on line "+(w+1)+": Unexpected "+(H==I?"end of input":"'"+(this.terminals_[H]||H)+"'"),this.parseError(Dt,{text:M.match,token:this.terminals_[H]||H,line:M.yylineno,loc:ot,expected:gt})}if(U[0]instanceof Array&&U.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Z+", token: "+H);switch(U[0]){case 1:h.push(H),x.push(M.yytext),s.push(M.yylloc),h.push(U[1]),H=null,$=M.yyleng,e=M.yytext,w=M.yylineno,ot=M.yylloc;break;case 2:if(J=this.productions_[U[1]][1],K.$=x[x.length-J],K._$={first_line:s[s.length-(J||1)].first_line,last_line:s[s.length-1].last_line,first_column:s[s.length-(J||1)].first_column,last_column:s[s.length-1].last_column},ft&&(K._$.range=[s[s.length-(J||1)].range[0],s[s.length-1].range[1]]),rt=this.performAction.apply(K,[e,$,w,Q.yy,U[1],x,s].concat(j)),typeof rt<"u")return rt;J&&(h=h.slice(0,-1*J*2),x=x.slice(0,-1*J),s=s.slice(0,-1*J)),h.push(this.productions_[U[1]][0]),x.push(K.$),s.push(K._$),zt=P[h[h.length-2]][h[h.length-1]],h.push(zt);break;case 3:return!0}}return!0},"parse")},v=function(){var f={EOF:1,parseError:c(function(l,h){if(this.yy.parser)this.yy.parser.parseError(l,h);else throw new Error(l)},"parseError"),setInput:c(function(o,l){return this.yy=l||this.yy||{},this._input=o,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:c(function(){var o=this._input[0];this.yytext+=o,this.yyleng++,this.offset++,this.match+=o,this.matched+=o;var l=o.match(/(?:\r\n?|\n).*/g);return l?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),o},"input"),unput:c(function(o){var l=o.length,h=o.split(/(?:\r\n?|\n)/g);this._input=o+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-l),this.offset-=l;var m=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),h.length-1&&(this.yylineno-=h.length-1);var x=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:h?(h.length===m.length?this.yylloc.first_column:0)+m[m.length-h.length].length-h[0].length:this.yylloc.first_column-l},this.options.ranges&&(this.yylloc.range=[x[0],x[0]+this.yyleng-l]),this.yyleng=this.yytext.length,this},"unput"),more:c(function(){return this._more=!0,this},"more"),reject:c(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:c(function(o){this.unput(this.match.slice(o))},"less"),pastInput:c(function(){var o=this.matched.substr(0,this.matched.length-this.match.length);return(o.length>20?"...":"")+o.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:c(function(){var o=this.match;return o.length<20&&(o+=this._input.substr(0,20-o.length)),(o.substr(0,20)+(o.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:c(function(){var o=this.pastInput(),l=new Array(o.length+1).join("-");return o+this.upcomingInput()+` +`+l+"^"},"showPosition"),test_match:c(function(o,l){var h,m,x;if(this.options.backtrack_lexer&&(x={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(x.yylloc.range=this.yylloc.range.slice(0))),m=o[0].match(/(?:\r\n?|\n).*/g),m&&(this.yylineno+=m.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:m?m[m.length-1].length-m[m.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+o[0].length},this.yytext+=o[0],this.match+=o[0],this.matches=o,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(o[0].length),this.matched+=o[0],h=this.performAction.call(this,this.yy,this,l,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),h)return h;if(this._backtrack){for(var s in x)this[s]=x[s];return!1}return!1},"test_match"),next:c(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var o,l,h,m;this._more||(this.yytext="",this.match="");for(var x=this._currentRules(),s=0;sl[0].length)){if(l=h,m=s,this.options.backtrack_lexer){if(o=this.test_match(h,x[s]),o!==!1)return o;if(this._backtrack){l=!1;continue}else return!1}else if(!this.options.flex)break}return l?(o=this.test_match(l,x[m]),o!==!1?o:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:c(function(){var l=this.next();return l||this.lex()},"lex"),begin:c(function(l){this.conditionStack.push(l)},"begin"),popState:c(function(){var l=this.conditionStack.length-1;return l>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:c(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:c(function(l){return l=this.conditionStack.length-1-Math.abs(l||0),l>=0?this.conditionStack[l]:"INITIAL"},"topState"),pushState:c(function(l){this.begin(l)},"pushState"),stateStackSize:c(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:c(function(l,h,m,x){switch(m){case 0:return this.begin("open_directive"),"open_directive";case 1:return this.begin("acc_title"),31;case 2:return this.popState(),"acc_title_value";case 3:return this.begin("acc_descr"),33;case 4:return this.popState(),"acc_descr_value";case 5:this.begin("acc_descr_multiline");break;case 6:this.popState();break;case 7:return"acc_descr_multiline_value";case 8:break;case 9:break;case 10:break;case 11:return 10;case 12:break;case 13:break;case 14:this.begin("href");break;case 15:this.popState();break;case 16:return 43;case 17:this.begin("callbackname");break;case 18:this.popState();break;case 19:this.popState(),this.begin("callbackargs");break;case 20:return 41;case 21:this.popState();break;case 22:return 42;case 23:this.begin("click");break;case 24:this.popState();break;case 25:return 40;case 26:return 4;case 27:return 22;case 28:return 23;case 29:return 24;case 30:return 25;case 31:return 26;case 32:return 28;case 33:return 27;case 34:return 29;case 35:return 12;case 36:return 13;case 37:return 14;case 38:return 15;case 39:return 16;case 40:return 17;case 41:return 18;case 42:return 20;case 43:return 21;case 44:return"date";case 45:return 30;case 46:return"accDescription";case 47:return 36;case 48:return 38;case 49:return 39;case 50:return":";case 51:return 6;case 52:return"INVALID"}},"anonymous"),rules:[/^(?:%%\{)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:topAxis\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:tickInterval\s[^#\n;]+)/i,/^(?:includes\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:weekday\s+monday\b)/i,/^(?:weekday\s+tuesday\b)/i,/^(?:weekday\s+wednesday\b)/i,/^(?:weekday\s+thursday\b)/i,/^(?:weekday\s+friday\b)/i,/^(?:weekday\s+saturday\b)/i,/^(?:weekday\s+sunday\b)/i,/^(?:weekend\s+friday\b)/i,/^(?:weekend\s+saturday\b)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^\n]+)/i,/^(?:accDescription\s[^#\n;]+)/i,/^(?:section\s[^\n]+)/i,/^(?:[^:\n]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[6,7],inclusive:!1},acc_descr:{rules:[4],inclusive:!1},acc_title:{rules:[2],inclusive:!1},callbackargs:{rules:[21,22],inclusive:!1},callbackname:{rules:[18,19,20],inclusive:!1},href:{rules:[15,16],inclusive:!1},click:{rules:[24,25],inclusive:!1},INITIAL:{rules:[0,1,3,5,8,9,10,11,12,13,14,17,23,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52],inclusive:!0}}};return f}();p.lexer=v;function g(){this.yy={}}return c(g,"Parser"),g.prototype=p,p.Parser=g,new g}();Mt.parser=Mt;var He=Mt;X.extend(We);X.extend(Pe);X.extend(Ne);var qt={friday:5,saturday:6},tt="",Yt="",At=void 0,$t="",mt=[],kt=[],Ft=new Map,Lt=[],xt=[],dt="",Wt="",se=["active","done","crit","milestone","vert"],Ot=[],yt=!1,Pt=!1,Vt="sunday",bt="saturday",Ct=0,Be=c(function(){Lt=[],xt=[],dt="",Ot=[],pt=0,It=void 0,Tt=void 0,B=[],tt="",Yt="",Wt="",At=void 0,$t="",mt=[],kt=[],yt=!1,Pt=!1,Ct=0,Ft=new Map,$e(),Vt="sunday",bt="saturday"},"clear"),Ge=c(function(t){Yt=t},"setAxisFormat"),je=c(function(){return Yt},"getAxisFormat"),Xe=c(function(t){At=t},"setTickInterval"),Ue=c(function(){return At},"getTickInterval"),qe=c(function(t){$t=t},"setTodayMarker"),Ze=c(function(){return $t},"getTodayMarker"),Qe=c(function(t){tt=t},"setDateFormat"),Ke=c(function(){yt=!0},"enableInclusiveEndDates"),Je=c(function(){return yt},"endDatesAreInclusive"),ts=c(function(){Pt=!0},"enableTopAxis"),es=c(function(){return Pt},"topAxisEnabled"),ss=c(function(t){Wt=t},"setDisplayMode"),is=c(function(){return Wt},"getDisplayMode"),rs=c(function(){return tt},"getDateFormat"),ns=c(function(t){mt=t.toLowerCase().split(/[\s,]+/)},"setIncludes"),as=c(function(){return mt},"getIncludes"),os=c(function(t){kt=t.toLowerCase().split(/[\s,]+/)},"setExcludes"),cs=c(function(){return kt},"getExcludes"),ls=c(function(){return Ft},"getLinks"),us=c(function(t){dt=t,Lt.push(t)},"addSection"),ds=c(function(){return Lt},"getSections"),fs=c(function(){let t=Zt();const a=10;let r=0;for(;!t&&r{const W=_.trim();return W==="x"||W==="X"},"isTimestampFormat")(a)&&/^\d+$/.test(r))return new Date(Number(r));const k=/^after\s+(?[\d\w- ]+)/.exec(r);if(k!==null){let _=null;for(const F of k.groups.ids.split(" ")){let b=at(F);b!==void 0&&(!_||b.endTime>_.endTime)&&(_=b)}if(_)return _.endTime;const W=new Date;return W.setHours(0,0,0,0),W}let y=X(r,a.trim(),!0);if(y.isValid())return y.toDate();{nt.debug("Invalid date:"+r),nt.debug("With date format:"+a.trim());const _=new Date(r);if(_===void 0||isNaN(_.getTime())||_.getFullYear()<-1e4||_.getFullYear()>1e4)throw new Error("Invalid date:"+r);return _}},"getStartDate"),ne=c(function(t){const a=/^(\d+(?:\.\d+)?)([Mdhmswy]|ms)$/.exec(t.trim());return a!==null?[Number.parseFloat(a[1]),a[2]]:[NaN,"ms"]},"parseDuration"),ae=c(function(t,a,r,i=!1){r=r.trim();const k=/^until\s+(?[\d\w- ]+)/.exec(r);if(k!==null){let b=null;for(const V of k.groups.ids.split(" ")){let N=at(V);N!==void 0&&(!b||N.startTime{window.open(r,"_self")}),Ft.set(i,r))}),ce(t,"clickable")},"setLink"),ce=c(function(t,a){t.split(",").forEach(function(r){let i=at(r);i!==void 0&&i.classes.push(a)})},"setClass"),bs=c(function(t,a,r){if(lt().securityLevel!=="loose"||a===void 0)return;let i=[];if(typeof r=="string"){i=r.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let k=0;k{Fe.runFunc(a,...i)})},"setClickFun"),le=c(function(t,a){Ot.push(function(){const r=document.querySelector(`[id="${t}"]`);r!==null&&r.addEventListener("click",function(){a()})},function(){const r=document.querySelector(`[id="${t}-text"]`);r!==null&&r.addEventListener("click",function(){a()})})},"pushFun"),ws=c(function(t,a,r){t.split(",").forEach(function(i){bs(i,a,r)}),ce(t,"clickable")},"setClickEvent"),_s=c(function(t){Ot.forEach(function(a){a(t)})},"bindFunctions"),Ds={getConfig:c(()=>lt().gantt,"getConfig"),clear:Be,setDateFormat:Qe,getDateFormat:rs,enableInclusiveEndDates:Ke,endDatesAreInclusive:Je,enableTopAxis:ts,topAxisEnabled:es,setAxisFormat:Ge,getAxisFormat:je,setTickInterval:Xe,getTickInterval:Ue,setTodayMarker:qe,getTodayMarker:Ze,setAccTitle:ke,getAccTitle:me,setDiagramTitle:he,getDiagramTitle:fe,setDisplayMode:ss,getDisplayMode:is,setAccDescription:de,getAccDescription:ue,addSection:us,getSections:ds,getTasks:fs,addTask:ps,findTaskById:at,addTaskOrg:Ts,setIncludes:ns,getIncludes:as,setExcludes:os,getExcludes:cs,setClickEvent:ws,setLink:xs,getLinks:ls,bindFunctions:_s,parseDuration:ne,isInvalidDate:ie,setWeekday:hs,getWeekday:ms,setWeekend:ks};function Nt(t,a,r){let i=!0;for(;i;)i=!1,r.forEach(function(n){const k="^\\s*"+n+"\\s*$",y=new RegExp(k);t[0].match(y)&&(a[n]=!0,t.shift(1),i=!0)})}c(Nt,"getTaskTags");X.extend(Re);var Ss=c(function(){nt.debug("Something is calling, setConf, remove the call")},"setConf"),Qt={monday:Ee,tuesday:Ce,wednesday:Me,thursday:Se,friday:De,saturday:_e,sunday:we},Ms=c((t,a)=>{let r=[...t].map(()=>-1/0),i=[...t].sort((k,y)=>k.startTime-y.startTime||k.order-y.order),n=0;for(const k of i)for(let y=0;y=r[y]){r[y]=k.endTime,k.order=y+a,y>n&&(n=y);break}return n},"getMaxIntersections"),et,St=1e4,Cs=c(function(t,a,r,i){const n=lt().gantt,k=lt().securityLevel;let y;k==="sandbox"&&(y=vt("#i"+a));const _=k==="sandbox"?vt(y.nodes()[0].contentDocument.body):vt("body"),W=k==="sandbox"?y.nodes()[0].contentDocument:document,F=W.getElementById(a);et=F.parentElement.offsetWidth,et===void 0&&(et=1200),n.useWidth!==void 0&&(et=n.useWidth);const b=i.db.getTasks();let L=[];for(const u of b)L.push(u.type);L=d(L);const V={};let N=2*n.topPadding;if(i.db.getDisplayMode()==="compact"||n.displayMode==="compact"){const u={};for(const v of b)u[v.section]===void 0?u[v.section]=[v]:u[v.section].push(v);let p=0;for(const v of Object.keys(u)){const g=Ms(u[v],p)+1;p+=g,N+=g*(n.barHeight+n.barGap),V[v]=g}}else{N+=b.length*(n.barHeight+n.barGap);for(const u of L)V[u]=b.filter(p=>p.type===u).length}F.setAttribute("viewBox","0 0 "+et+" "+N);const z=_.select(`[id="${a}"]`),C=ye().domain([ge(b,function(u){return u.startTime}),ve(b,function(u){return u.endTime})]).rangeRound([0,et-n.leftPadding-n.rightPadding]);function D(u,p){const v=u.startTime,g=p.startTime;let f=0;return v>g?f=1:ve.vert===w.vert?0:e.vert?1:-1);const m=[...new Set(u.map(e=>e.order))].map(e=>u.find(w=>w.order===e));z.append("g").selectAll("rect").data(m).enter().append("rect").attr("x",0).attr("y",function(e,w){return w=e.order,w*p+v-2}).attr("width",function(){return l-n.rightPadding/2}).attr("height",p).attr("class",function(e){for(const[w,$]of L.entries())if(e.type===$)return"section section"+w%n.numberSectionStyles;return"section section0"}).enter();const x=z.append("g").selectAll("rect").data(u).enter(),s=i.db.getLinks();if(x.append("rect").attr("id",function(e){return e.id}).attr("rx",3).attr("ry",3).attr("x",function(e){return e.milestone?C(e.startTime)+g+.5*(C(e.endTime)-C(e.startTime))-.5*f:C(e.startTime)+g}).attr("y",function(e,w){return w=e.order,e.vert?n.gridLineStartPadding:w*p+v}).attr("width",function(e){return e.milestone?f:e.vert?.08*f:C(e.renderEndTime||e.endTime)-C(e.startTime)}).attr("height",function(e){return e.vert?b.length*(n.barHeight+n.barGap)+n.barHeight*2:f}).attr("transform-origin",function(e,w){return w=e.order,(C(e.startTime)+g+.5*(C(e.endTime)-C(e.startTime))).toString()+"px "+(w*p+v+.5*f).toString()+"px"}).attr("class",function(e){const w="task";let $="";e.classes.length>0&&($=e.classes.join(" "));let Y=0;for(const[j,M]of L.entries())e.type===M&&(Y=j%n.numberSectionStyles);let I="";return e.active?e.crit?I+=" activeCrit":I=" active":e.done?e.crit?I=" doneCrit":I=" done":e.crit&&(I+=" crit"),I.length===0&&(I=" task"),e.milestone&&(I=" milestone "+I),e.vert&&(I=" vert "+I),I+=Y,I+=" "+$,w+I}),x.append("text").attr("id",function(e){return e.id+"-text"}).text(function(e){return e.task}).attr("font-size",n.fontSize).attr("x",function(e){let w=C(e.startTime),$=C(e.renderEndTime||e.endTime);if(e.milestone&&(w+=.5*(C(e.endTime)-C(e.startTime))-.5*f,$=w+f),e.vert)return C(e.startTime)+g;const Y=this.getBBox().width;return Y>$-w?$+Y+1.5*n.leftPadding>l?w+g-5:$+g+5:($-w)/2+w+g}).attr("y",function(e,w){return e.vert?n.gridLineStartPadding+b.length*(n.barHeight+n.barGap)+60:(w=e.order,w*p+n.barHeight/2+(n.fontSize/2-2)+v)}).attr("text-height",f).attr("class",function(e){const w=C(e.startTime);let $=C(e.endTime);e.milestone&&($=w+f);const Y=this.getBBox().width;let I="";e.classes.length>0&&(I=e.classes.join(" "));let j=0;for(const[Q,st]of L.entries())e.type===st&&(j=Q%n.numberSectionStyles);let M="";return e.active&&(e.crit?M="activeCritText"+j:M="activeText"+j),e.done?e.crit?M=M+" doneCritText"+j:M=M+" doneText"+j:e.crit&&(M=M+" critText"+j),e.milestone&&(M+=" milestoneText"),e.vert&&(M+=" vertText"),Y>$-w?$+Y+1.5*n.leftPadding>l?I+" taskTextOutsideLeft taskTextOutside"+j+" "+M:I+" taskTextOutsideRight taskTextOutside"+j+" "+M+" width-"+Y:I+" taskText taskText"+j+" "+M+" width-"+Y}),lt().securityLevel==="sandbox"){let e;e=vt("#i"+a);const w=e.nodes()[0].contentDocument;x.filter(function($){return s.has($.id)}).each(function($){var Y=w.querySelector("#"+$.id),I=w.querySelector("#"+$.id+"-text");const j=Y.parentNode;var M=w.createElement("a");M.setAttribute("xlink:href",s.get($.id)),M.setAttribute("target","_top"),j.appendChild(M),M.appendChild(Y),M.appendChild(I)})}}c(O,"drawRects");function A(u,p,v,g,f,o,l,h){if(l.length===0&&h.length===0)return;let m,x;for(const{startTime:Y,endTime:I}of o)(m===void 0||Yx)&&(x=I);if(!m||!x)return;if(X(x).diff(X(m),"year")>5){nt.warn("The difference between the min and max time is more than 5 years. This will cause performance issues. Skipping drawing exclude days.");return}const s=i.db.getDateFormat(),P=[];let e=null,w=X(m);for(;w.valueOf()<=x;)i.db.isInvalidDate(w,s,l,h)?e?e.end=w:e={start:w,end:w}:e&&(P.push(e),e=null),w=w.add(1,"d");z.append("g").selectAll("rect").data(P).enter().append("rect").attr("id",Y=>"exclude-"+Y.start.format("YYYY-MM-DD")).attr("x",Y=>C(Y.start.startOf("day"))+v).attr("y",n.gridLineStartPadding).attr("width",Y=>C(Y.end.endOf("day"))-C(Y.start.startOf("day"))).attr("height",f-p-n.gridLineStartPadding).attr("transform-origin",function(Y,I){return(C(Y.start)+v+.5*(C(Y.end)-C(Y.start))).toString()+"px "+(I*u+.5*f).toString()+"px"}).attr("class","exclude-range")}c(A,"drawExcludeDays");function R(u,p,v,g){if(v<=0||u>p)return 1/0;const f=p-u,o=X.duration({[g??"day"]:v}).asMilliseconds();return o<=0?1/0:Math.ceil(f/o)}c(R,"getEstimatedTickCount");function G(u,p,v,g){const f=i.db.getDateFormat(),o=i.db.getAxisFormat();let l;o?l=o:f==="D"?l="%d":l=n.axisFormat??"%Y-%m-%d";let h=be(C).tickSize(-g+p+n.gridLineStartPadding).tickFormat(Rt(l));const x=/^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/.exec(i.db.getTickInterval()||n.tickInterval);if(x!==null){const s=parseInt(x[1],10);if(isNaN(s)||s<=0)nt.warn(`Invalid tick interval value: "${x[1]}". Skipping custom tick interval.`);else{const P=x[2],e=i.db.getWeekday()||n.weekday,w=C.domain(),$=w[0],Y=w[1],I=R($,Y,s,P);if(I>St)nt.warn(`The tick interval "${s}${P}" would generate ${I} ticks, which exceeds the maximum allowed (${St}). This may indicate an invalid date or time range. Skipping custom tick interval.`);else switch(P){case"millisecond":h.ticks(Ut.every(s));break;case"second":h.ticks(Xt.every(s));break;case"minute":h.ticks(jt.every(s));break;case"hour":h.ticks(Gt.every(s));break;case"day":h.ticks(Bt.every(s));break;case"week":h.ticks(Qt[e].every(s));break;case"month":h.ticks(Ht.every(s));break}}}if(z.append("g").attr("class","grid").attr("transform","translate("+u+", "+(g-50)+")").call(h).selectAll("text").style("text-anchor","middle").attr("fill","#000").attr("stroke","none").attr("font-size",10).attr("dy","1em"),i.db.topAxisEnabled()||n.topAxis){let s=Ie(C).tickSize(-g+p+n.gridLineStartPadding).tickFormat(Rt(l));if(x!==null){const P=parseInt(x[1],10);if(isNaN(P)||P<=0)nt.warn(`Invalid tick interval value: "${x[1]}". Skipping custom tick interval.`);else{const e=x[2],w=i.db.getWeekday()||n.weekday,$=C.domain(),Y=$[0],I=$[1];if(R(Y,I,P,e)<=St)switch(e){case"millisecond":s.ticks(Ut.every(P));break;case"second":s.ticks(Xt.every(P));break;case"minute":s.ticks(jt.every(P));break;case"hour":s.ticks(Gt.every(P));break;case"day":s.ticks(Bt.every(P));break;case"week":s.ticks(Qt[w].every(P));break;case"month":s.ticks(Ht.every(P));break}}}z.append("g").attr("class","grid").attr("transform","translate("+u+", "+p+")").call(s).selectAll("text").style("text-anchor","middle").attr("fill","#000").attr("stroke","none").attr("font-size",10)}}c(G,"makeGrid");function E(u,p){let v=0;const g=Object.keys(V).map(f=>[f,V[f]]);z.append("g").selectAll("text").data(g).enter().append(function(f){const o=f[0].split(Ye.lineBreakRegex),l=-(o.length-1)/2,h=W.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("dy",l+"em");for(const[m,x]of o.entries()){const s=W.createElementNS("http://www.w3.org/2000/svg","tspan");s.setAttribute("alignment-baseline","central"),s.setAttribute("x","10"),m>0&&s.setAttribute("dy","1em"),s.textContent=x,h.appendChild(s)}return h}).attr("x",10).attr("y",function(f,o){if(o>0)for(let l=0;l` + .mermaid-main-font { + font-family: ${t.fontFamily}; + } + + .exclude-range { + fill: ${t.excludeBkgColor}; + } + + .section { + stroke: none; + opacity: 0.2; + } + + .section0 { + fill: ${t.sectionBkgColor}; + } + + .section2 { + fill: ${t.sectionBkgColor2}; + } + + .section1, + .section3 { + fill: ${t.altSectionBkgColor}; + opacity: 0.2; + } + + .sectionTitle0 { + fill: ${t.titleColor}; + } + + .sectionTitle1 { + fill: ${t.titleColor}; + } + + .sectionTitle2 { + fill: ${t.titleColor}; + } + + .sectionTitle3 { + fill: ${t.titleColor}; + } + + .sectionTitle { + text-anchor: start; + font-family: ${t.fontFamily}; + } + + + /* Grid and axis */ + + .grid .tick { + stroke: ${t.gridColor}; + opacity: 0.8; + shape-rendering: crispEdges; + } + + .grid .tick text { + font-family: ${t.fontFamily}; + fill: ${t.textColor}; + } + + .grid path { + stroke-width: 0; + } + + + /* Today line */ + + .today { + fill: none; + stroke: ${t.todayLineColor}; + stroke-width: 2px; + } + + + /* Task styling */ + + /* Default task */ + + .task { + stroke-width: 2; + } + + .taskText { + text-anchor: middle; + font-family: ${t.fontFamily}; + } + + .taskTextOutsideRight { + fill: ${t.taskTextDarkColor}; + text-anchor: start; + font-family: ${t.fontFamily}; + } + + .taskTextOutsideLeft { + fill: ${t.taskTextDarkColor}; + text-anchor: end; + } + + + /* Special case clickable */ + + .task.clickable { + cursor: pointer; + } + + .taskText.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + .taskTextOutsideLeft.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + .taskTextOutsideRight.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + + /* Specific task settings for the sections*/ + + .taskText0, + .taskText1, + .taskText2, + .taskText3 { + fill: ${t.taskTextColor}; + } + + .task0, + .task1, + .task2, + .task3 { + fill: ${t.taskBkgColor}; + stroke: ${t.taskBorderColor}; + } + + .taskTextOutside0, + .taskTextOutside2 + { + fill: ${t.taskTextOutsideColor}; + } + + .taskTextOutside1, + .taskTextOutside3 { + fill: ${t.taskTextOutsideColor}; + } + + + /* Active task */ + + .active0, + .active1, + .active2, + .active3 { + fill: ${t.activeTaskBkgColor}; + stroke: ${t.activeTaskBorderColor}; + } + + .activeText0, + .activeText1, + .activeText2, + .activeText3 { + fill: ${t.taskTextDarkColor} !important; + } + + + /* Completed task */ + + .done0, + .done1, + .done2, + .done3 { + stroke: ${t.doneTaskBorderColor}; + fill: ${t.doneTaskBkgColor}; + stroke-width: 2; + } + + .doneText0, + .doneText1, + .doneText2, + .doneText3 { + fill: ${t.taskTextDarkColor} !important; + } + + + /* Tasks on the critical line */ + + .crit0, + .crit1, + .crit2, + .crit3 { + stroke: ${t.critBorderColor}; + fill: ${t.critBkgColor}; + stroke-width: 2; + } + + .activeCrit0, + .activeCrit1, + .activeCrit2, + .activeCrit3 { + stroke: ${t.critBorderColor}; + fill: ${t.activeTaskBkgColor}; + stroke-width: 2; + } + + .doneCrit0, + .doneCrit1, + .doneCrit2, + .doneCrit3 { + stroke: ${t.critBorderColor}; + fill: ${t.doneTaskBkgColor}; + stroke-width: 2; + cursor: pointer; + shape-rendering: crispEdges; + } + + .milestone { + transform: rotate(45deg) scale(0.8,0.8); + } + + .milestoneText { + font-style: italic; + } + .doneCritText0, + .doneCritText1, + .doneCritText2, + .doneCritText3 { + fill: ${t.taskTextDarkColor} !important; + } + + .vert { + stroke: ${t.vertLineColor}; + } + + .vertText { + font-size: 15px; + text-anchor: middle; + fill: ${t.vertLineColor} !important; + } + + .activeCritText0, + .activeCritText1, + .activeCritText2, + .activeCritText3 { + fill: ${t.taskTextDarkColor} !important; + } + + .titleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.titleColor||t.textColor}; + font-family: ${t.fontFamily}; + } +`,"getStyles"),Ys=Is,Fs={parser:He,db:Ds,renderer:Es,styles:Ys};export{Fs as diagram}; diff --git a/assets/chunks/giscus-Ci9LqPcC.BNebfDgq.js b/assets/chunks/giscus-Ci9LqPcC.BNebfDgq.js new file mode 100644 index 000000000..630544a28 --- /dev/null +++ b/assets/chunks/giscus-Ci9LqPcC.BNebfDgq.js @@ -0,0 +1,66 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const x=globalThis,G=x.ShadowRoot&&(x.ShadyCSS===void 0||x.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,B=Symbol(),q=new WeakMap;let at=class{constructor(s,t,e){if(this._$cssResult$=!0,e!==B)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=s,this.t=t}get styleSheet(){let s=this.o;const t=this.t;if(G&&s===void 0){const e=t!==void 0&&t.length===1;e&&(s=q.get(t)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),e&&q.set(t,s))}return s}toString(){return this.cssText}};const pt=s=>new at(typeof s=="string"?s:s+"",void 0,B),ft=(s,...t)=>{const e=s.length===1?s[0]:t.reduce((i,r,o)=>i+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+s[o+1],s[0]);return new at(e,s,B)},_t=(s,t)=>{if(G)s.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const e of t){const i=document.createElement("style"),r=x.litNonce;r!==void 0&&i.setAttribute("nonce",r),i.textContent=e.cssText,s.appendChild(i)}},K=G?s=>s:s=>s instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return pt(e)})(s):s;/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const{is:$t,defineProperty:gt,getOwnPropertyDescriptor:mt,getOwnPropertyNames:vt,getOwnPropertySymbols:At,getPrototypeOf:yt}=Object,E=globalThis,F=E.trustedTypes,St=F?F.emptyScript:"",J=E.reactiveElementPolyfillSupport,U=(s,t)=>s,H={toAttribute(s,t){switch(t){case Boolean:s=s?St:null;break;case Object:case Array:s=s==null?s:JSON.stringify(s)}return s},fromAttribute(s,t){let e=s;switch(t){case Boolean:e=s!==null;break;case Number:e=s===null?null:Number(s);break;case Object:case Array:try{e=JSON.parse(s)}catch{e=null}}return e}},W=(s,t)=>!$t(s,t),Q={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:W};Symbol.metadata??(Symbol.metadata=Symbol("metadata")),E.litPropertyMetadata??(E.litPropertyMetadata=new WeakMap);class S extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??(this.l=[])).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=Q){if(e.state&&(e.attribute=!1),this._$Ei(),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),r=this.getPropertyDescriptor(t,i,e);r!==void 0&>(this.prototype,t,r)}}static getPropertyDescriptor(t,e,i){const{get:r,set:o}=mt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get(){return r==null?void 0:r.call(this)},set(n){const h=r==null?void 0:r.call(this);o.call(this,n),this.requestUpdate(t,h,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??Q}static _$Ei(){if(this.hasOwnProperty(U("elementProperties")))return;const t=yt(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(U("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(U("properties"))){const e=this.properties,i=[...vt(e),...At(e)];for(const r of i)this.createProperty(r,e[r])}const t=this[Symbol.metadata];if(t!==null){const e=litPropertyMetadata.get(t);if(e!==void 0)for(const[i,r]of e)this.elementProperties.set(i,r)}this._$Eh=new Map;for(const[e,i]of this.elementProperties){const r=this._$Eu(e,i);r!==void 0&&this._$Eh.set(r,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const r of i)e.unshift(K(r))}else t!==void 0&&e.push(K(t));return e}static _$Eu(t,e){const i=e.attribute;return i===!1?void 0:typeof i=="string"?i:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){var t;this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),(t=this.constructor.l)==null||t.forEach(e=>e(this))}addController(t){var e;(this._$EO??(this._$EO=new Set)).add(t),this.renderRoot!==void 0&&this.isConnected&&((e=t.hostConnected)==null||e.call(t))}removeController(t){var e;(e=this._$EO)==null||e.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return _t(t,this.constructor.elementStyles),t}connectedCallback(){var t;this.renderRoot??(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),(t=this._$EO)==null||t.forEach(e=>{var i;return(i=e.hostConnected)==null?void 0:i.call(e)})}enableUpdating(t){}disconnectedCallback(){var t;(t=this._$EO)==null||t.forEach(e=>{var i;return(i=e.hostDisconnected)==null?void 0:i.call(e)})}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EC(t,e){var i;const r=this.constructor.elementProperties.get(t),o=this.constructor._$Eu(t,r);if(o!==void 0&&r.reflect===!0){const n=(((i=r.converter)==null?void 0:i.toAttribute)!==void 0?r.converter:H).toAttribute(e,r.type);this._$Em=t,n==null?this.removeAttribute(o):this.setAttribute(o,n),this._$Em=null}}_$AK(t,e){var i;const r=this.constructor,o=r._$Eh.get(t);if(o!==void 0&&this._$Em!==o){const n=r.getPropertyOptions(o),h=typeof n.converter=="function"?{fromAttribute:n.converter}:((i=n.converter)==null?void 0:i.fromAttribute)!==void 0?n.converter:H;this._$Em=o,this[o]=h.fromAttribute(e,n.type),this._$Em=null}}requestUpdate(t,e,i){if(t!==void 0){if(i??(i=this.constructor.getPropertyOptions(t)),!(i.hasChanged??W)(this[t],e))return;this.P(t,e,i)}this.isUpdatePending===!1&&(this._$ES=this._$ET())}P(t,e,i){this._$AL.has(t)||this._$AL.set(t,e),i.reflect===!0&&this._$Em!==t&&(this._$Ej??(this._$Ej=new Set)).add(t)}async _$ET(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??(this.renderRoot=this.createRenderRoot()),this._$Ep){for(const[o,n]of this._$Ep)this[o]=n;this._$Ep=void 0}const r=this.constructor.elementProperties;if(r.size>0)for(const[o,n]of r)n.wrapped!==!0||this._$AL.has(o)||this[o]===void 0||this.P(o,this[o],n)}let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),(t=this._$EO)==null||t.forEach(r=>{var o;return(o=r.hostUpdate)==null?void 0:o.call(r)}),this.update(i)):this._$EU()}catch(r){throw e=!1,this._$EU(),r}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;(e=this._$EO)==null||e.forEach(i=>{var r;return(r=i.hostUpdated)==null?void 0:r.call(i)}),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EU(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Ej&&(this._$Ej=this._$Ej.forEach(e=>this._$EC(e,this[e]))),this._$EU()}updated(t){}firstUpdated(t){}}S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[U("elementProperties")]=new Map,S[U("finalized")]=new Map,J==null||J({ReactiveElement:S}),(E.reactiveElementVersions??(E.reactiveElementVersions=[])).push("2.0.4");/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const L=globalThis,k=L.trustedTypes,Z=k?k.createPolicy("lit-html",{createHTML:s=>s}):void 0,ht="$lit$",m=`lit$${Math.random().toFixed(9).slice(2)}$`,lt="?"+m,Et=`<${lt}>`,y=document,M=()=>y.createComment(""),T=s=>s===null||typeof s!="object"&&typeof s!="function",V=Array.isArray,bt=s=>V(s)||typeof(s==null?void 0:s[Symbol.iterator])=="function",z=`[ +\f\r]`,w=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,X=/-->/g,tt=/>/g,v=RegExp(`>|${z}(?:([^\\s"'>=/]+)(${z}*=${z}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),et=/'/g,st=/"/g,ct=/^(?:script|style|textarea|title)$/i,Ct=s=>(t,...e)=>({_$litType$:s,strings:t,values:e}),wt=Ct(1),b=Symbol.for("lit-noChange"),c=Symbol.for("lit-nothing"),it=new WeakMap,A=y.createTreeWalker(y,129);function dt(s,t){if(!V(s)||!s.hasOwnProperty("raw"))throw Error("invalid template strings array");return Z!==void 0?Z.createHTML(t):t}const Ut=(s,t)=>{const e=s.length-1,i=[];let r,o=t===2?"":t===3?"":"",n=w;for(let h=0;h"?(n=r??w,l=-1):p[1]===void 0?l=-2:(l=n.lastIndex-p[2].length,d=p[1],n=p[3]===void 0?v:p[3]==='"'?st:et):n===st||n===et?n=v:n===X||n===tt?n=w:(n=v,r=void 0);const g=n===v&&s[h+1].startsWith("/>")?" ":"";o+=n===w?a+Et:l>=0?(i.push(d),a.slice(0,l)+ht+a.slice(l)+m+g):a+m+(l===-2?h:g)}return[dt(s,o+(s[e]||"")+(t===2?"":t===3?"":"")),i]};class N{constructor({strings:t,_$litType$:e},i){let r;this.parts=[];let o=0,n=0;const h=t.length-1,a=this.parts,[d,p]=Ut(t,e);if(this.el=N.createElement(d,i),A.currentNode=this.el.content,e===2||e===3){const l=this.el.content.firstChild;l.replaceWith(...l.childNodes)}for(;(r=A.nextNode())!==null&&a.length0){r.textContent=k?k.emptyScript:"";for(let g=0;g<$;g++)r.append(l[g],M()),A.nextNode(),a.push({type:2,index:++o});r.append(l[$],M())}}}else if(r.nodeType===8)if(r.data===lt)a.push({type:2,index:o});else{let l=-1;for(;(l=r.data.indexOf(m,l+1))!==-1;)a.push({type:7,index:o}),l+=m.length-1}o++}}static createElement(t,e){const i=y.createElement("template");return i.innerHTML=t,i}}function C(s,t,e=s,i){var r,o;if(t===b)return t;let n=i!==void 0?(r=e._$Co)==null?void 0:r[i]:e._$Cl;const h=T(t)?void 0:t._$litDirective$;return(n==null?void 0:n.constructor)!==h&&((o=n==null?void 0:n._$AO)==null||o.call(n,!1),h===void 0?n=void 0:(n=new h(s),n._$AT(s,e,i)),i!==void 0?(e._$Co??(e._$Co=[]))[i]=n:e._$Cl=n),n!==void 0&&(t=C(s,n._$AS(s,t.values),n,i)),t}class Pt{constructor(t,e){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:e},parts:i}=this._$AD,r=((t==null?void 0:t.creationScope)??y).importNode(e,!0);A.currentNode=r;let o=A.nextNode(),n=0,h=0,a=i[0];for(;a!==void 0;){if(n===a.index){let d;a.type===2?d=new R(o,o.nextSibling,this,t):a.type===1?d=new a.ctor(o,a.name,a.strings,this,t):a.type===6&&(d=new Nt(o,this,t)),this._$AV.push(d),a=i[++h]}n!==(a==null?void 0:a.index)&&(o=A.nextNode(),n++)}return A.currentNode=y,r}p(t){let e=0;for(const i of this._$AV)i!==void 0&&(i.strings!==void 0?(i._$AI(t,i,e),e+=i.strings.length-2):i._$AI(t[e])),e++}}class R{get _$AU(){var t;return((t=this._$AM)==null?void 0:t._$AU)??this._$Cv}constructor(t,e,i,r){this.type=2,this._$AH=c,this._$AN=void 0,this._$AA=t,this._$AB=e,this._$AM=i,this.options=r,this._$Cv=(r==null?void 0:r.isConnected)??!0}get parentNode(){let t=this._$AA.parentNode;const e=this._$AM;return e!==void 0&&(t==null?void 0:t.nodeType)===11&&(t=e.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,e=this){t=C(this,t,e),T(t)?t===c||t==null||t===""?(this._$AH!==c&&this._$AR(),this._$AH=c):t!==this._$AH&&t!==b&&this._(t):t._$litType$!==void 0?this.$(t):t.nodeType!==void 0?this.T(t):bt(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==c&&T(this._$AH)?this._$AA.nextSibling.data=t:this.T(y.createTextNode(t)),this._$AH=t}$(t){var e;const{values:i,_$litType$:r}=t,o=typeof r=="number"?this._$AC(t):(r.el===void 0&&(r.el=N.createElement(dt(r.h,r.h[0]),this.options)),r);if(((e=this._$AH)==null?void 0:e._$AD)===o)this._$AH.p(i);else{const n=new Pt(o,this),h=n.u(this.options);n.p(i),this.T(h),this._$AH=n}}_$AC(t){let e=it.get(t.strings);return e===void 0&&it.set(t.strings,e=new N(t)),e}k(t){V(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,r=0;for(const o of t)r===e.length?e.push(i=new R(this.O(M()),this.O(M()),this,this.options)):i=e[r],i._$AI(o),r++;r2||i[0]!==""||i[1]!==""?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=c}_$AI(t,e=this,i,r){const o=this.strings;let n=!1;if(o===void 0)t=C(this,t,e,0),n=!T(t)||t!==this._$AH&&t!==b,n&&(this._$AH=t);else{const h=t;let a,d;for(t=o[0],a=0;a{const i=(e==null?void 0:e.renderBefore)??t;let r=i._$litPart$;if(r===void 0){const o=(e==null?void 0:e.renderBefore)??null;i._$litPart$=r=new R(t.insertBefore(M(),o),o,void 0,e??{})}return r._$AI(s),r};/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */let P=class extends S{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){var s;const t=super.createRenderRoot();return(s=this.renderOptions).renderBefore??(s.renderBefore=t.firstChild),t}update(s){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(s),this._$Do=Rt(t,this.renderRoot,this.renderOptions)}connectedCallback(){var s;super.connectedCallback(),(s=this._$Do)==null||s.setConnected(!0)}disconnectedCallback(){var s;super.disconnectedCallback(),(s=this._$Do)==null||s.setConnected(!1)}render(){return b}};var nt;P._$litElement$=!0,P.finalized=!0,(nt=globalThis.litElementHydrateSupport)==null||nt.call(globalThis,{LitElement:P});const ot=globalThis.litElementPolyfillSupport;ot==null||ot({LitElement:P});(globalThis.litElementVersions??(globalThis.litElementVersions=[])).push("4.1.1");/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const It=s=>(t,e)=>{e!==void 0?e.addInitializer(()=>{customElements.define(s,t)}):customElements.define(s,t)};/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const xt={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:W},Ht=(s=xt,t,e)=>{const{kind:i,metadata:r}=e;let o=globalThis.litPropertyMetadata.get(r);if(o===void 0&&globalThis.litPropertyMetadata.set(r,o=new Map),o.set(e.name,s),i==="accessor"){const{name:n}=e;return{set(h){const a=t.get.call(this);t.set.call(this,h),this.requestUpdate(n,a,s)},init(h){return h!==void 0&&this.P(n,void 0,s),h}}}if(i==="setter"){const{name:n}=e;return function(h){const a=this[n];t.call(this,h),this.requestUpdate(n,a,s)}}throw Error("Unsupported decorator location: "+i)};function _(s){return(t,e)=>typeof e=="object"?Ht(s,t,e):((i,r,o)=>{const n=r.hasOwnProperty(o);return r.constructor.createProperty(o,n?{...i,wrapped:!0}:i),n?Object.getOwnPropertyDescriptor(r,o):void 0})(s,t,e)}/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Lt=s=>s.strings===void 0;/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const kt={CHILD:2},Dt=s=>(...t)=>({_$litDirective$:s,values:t});let jt=class{constructor(s){}get _$AU(){return this._$AM._$AU}_$AT(s,t,e){this._$Ct=s,this._$AM=t,this._$Ci=e}_$AS(s,t){return this.update(s,t)}update(s,t){return this.render(...t)}};/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const O=(s,t)=>{var e;const i=s._$AN;if(i===void 0)return!1;for(const r of i)(e=r._$AO)==null||e.call(r,t,!1),O(r,t);return!0},D=s=>{let t,e;do{if((t=s._$AM)===void 0)break;e=t._$AN,e.delete(s),s=t}while((e==null?void 0:e.size)===0)},ut=s=>{for(let t;t=s._$AM;s=t){let e=t._$AN;if(e===void 0)t._$AN=e=new Set;else if(e.has(s))break;e.add(s),Gt(t)}};function zt(s){this._$AN!==void 0?(D(this),this._$AM=s,ut(this)):this._$AM=s}function Yt(s,t=!1,e=0){const i=this._$AH,r=this._$AN;if(r!==void 0&&r.size!==0)if(t)if(Array.isArray(i))for(let o=e;o{s.type==kt.CHILD&&(s._$AP??(s._$AP=Yt),s._$AQ??(s._$AQ=zt))};class Bt extends jt{constructor(){super(...arguments),this._$AN=void 0}_$AT(t,e,i){super._$AT(t,e,i),ut(this),this.isConnected=t._$AU}_$AO(t,e=!0){var i,r;t!==this.isConnected&&(this.isConnected=t,t?(i=this.reconnected)==null||i.call(this):(r=this.disconnected)==null||r.call(this)),e&&(O(this,t),D(this))}setValue(t){if(Lt(this._$Ct))this._$Ct._$AI(t,this);else{const e=[...this._$Ct._$AH];e[this._$Ci]=t,this._$Ct._$AI(e,this,0)}}disconnected(){}reconnected(){}}/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Wt=()=>new Vt;class Vt{}const Y=new WeakMap,qt=Dt(class extends Bt{render(s){return c}update(s,[t]){var e;const i=t!==this.Y;return i&&this.Y!==void 0&&this.rt(void 0),(i||this.lt!==this.ct)&&(this.Y=t,this.ht=(e=s.options)==null?void 0:e.host,this.rt(this.ct=s.element)),c}rt(s){if(this.isConnected||(s=void 0),typeof this.Y=="function"){const t=this.ht??globalThis;let e=Y.get(t);e===void 0&&(e=new WeakMap,Y.set(t,e)),e.get(this.Y)!==void 0&&this.Y.call(this.ht,void 0),e.set(this.Y,s),s!==void 0&&this.Y.call(this.ht,s)}else this.Y.value=s}get lt(){var s,t;return typeof this.Y=="function"?(s=Y.get(this.ht??globalThis))==null?void 0:s.get(this.Y):(t=this.Y)==null?void 0:t.value}disconnected(){this.lt===this.ct&&this.rt(void 0)}reconnected(){this.rt(this.ct)}});var Kt=Object.defineProperty,Ft=Object.getOwnPropertyDescriptor,f=(s,t,e,i)=>{for(var r=i>1?void 0:i?Ft(t,e):t,o=s.length-1,n;o>=0;o--)(n=s[o])&&(r=(i?n(t,e,r):n(r))||r);return i&&r&&Kt(t,e,r),r};function Jt(s){return customElements.get(s)?t=>t:It(s)}let u=class extends P{constructor(){super(),this.GISCUS_SESSION_KEY="giscus-session",this.GISCUS_DEFAULT_HOST="https://giscus.app",this.ERROR_SUGGESTION="Please consider reporting this error at https://github.com/giscus/giscus/issues/new.",this.__session="",this._iframeRef=Wt(),this.messageEventHandler=this.handleMessageEvent.bind(this),this.hasLoaded=!1,this.host=this.GISCUS_DEFAULT_HOST,this.strict="0",this.reactionsEnabled="1",this.emitMetadata="0",this.inputPosition="bottom",this.theme="light",this.lang="en",this.loading="eager",this.setupSession(),window.addEventListener("message",this.messageEventHandler)}get iframeRef(){var s;return(s=this._iframeRef)==null?void 0:s.value}get _host(){try{return new URL(this.host),this.host}catch{return this.GISCUS_DEFAULT_HOST}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("message",this.messageEventHandler)}_formatError(s){return`[giscus] An error occurred. Error message: "${s}".`}setupSession(){const s=location.href,t=new URL(s),e=localStorage.getItem(this.GISCUS_SESSION_KEY),i=t.searchParams.get("giscus")??"";if(this.__session="",i){localStorage.setItem(this.GISCUS_SESSION_KEY,JSON.stringify(i)),this.__session=i,t.searchParams.delete("giscus"),t.hash="",history.replaceState(void 0,document.title,t.toString());return}if(e)try{this.__session=JSON.parse(e)}catch(r){localStorage.removeItem(this.GISCUS_SESSION_KEY),console.warn(`${this._formatError(r==null?void 0:r.message)} Session has been cleared.`)}}signOut(){localStorage.removeItem(this.GISCUS_SESSION_KEY),this.__session="",this.update(new Map)}handleMessageEvent(s){if(s.origin!==this._host)return;const{data:t}=s;if(!(typeof t=="object"&&t.giscus))return;if(this.iframeRef&&t.giscus.resizeHeight&&(this.iframeRef.style.height=`${t.giscus.resizeHeight}px`),t.giscus.signOut){console.info("[giscus] User has logged out. Session has been cleared."),this.signOut();return}if(!t.giscus.error)return;const e=t.giscus.error;if(e.includes("Bad credentials")||e.includes("Invalid state value")||e.includes("State has expired")){if(localStorage.getItem(this.GISCUS_SESSION_KEY)!==null){console.warn(`${this._formatError(e)} Session has been cleared.`),this.signOut();return}console.error(`${this._formatError(e)} No session is stored initially. ${this.ERROR_SUGGESTION}`)}if(e.includes("Discussion not found")){console.warn(`[giscus] ${e}. A new discussion will be created if a comment/reaction is submitted.`);return}console.error(`${this._formatError(e)} ${this.ERROR_SUGGESTION}`)}sendMessage(s){var t;!((t=this.iframeRef)!=null&&t.contentWindow)||!this.hasLoaded||this.iframeRef.contentWindow.postMessage({giscus:s},this._host)}updateConfig(){const s={setConfig:{repo:this.repo,repoId:this.repoId,category:this.category,categoryId:this.categoryId,term:this.getTerm(),number:+this.getNumber(),strict:this.strict==="1",reactionsEnabled:this.reactionsEnabled==="1",emitMetadata:this.emitMetadata==="1",inputPosition:this.inputPosition,theme:this.theme,lang:this.lang}};this.sendMessage(s)}firstUpdated(){var s;(s=this.iframeRef)==null||s.addEventListener("load",()=>{var t;(t=this.iframeRef)==null||t.classList.remove("loading"),this.hasLoaded=!0,this.updateConfig()})}requestUpdate(s,t,e){if(!this.hasUpdated||s==="host"){super.requestUpdate(s,t,e);return}this.updateConfig()}getMetaContent(s,t=!1){const e=t?`meta[property='og:${s}'],`:"",i=document.querySelector(e+`meta[name='${s}']`);return i?i.content:""}_getCleanedUrl(){const s=new URL(location.href);return s.searchParams.delete("giscus"),s.hash="",s}getTerm(){switch(this.mapping){case"url":return this._getCleanedUrl().toString();case"title":return document.title;case"og:title":return this.getMetaContent("title",!0);case"specific":return this.term??"";case"number":return"";case"pathname":default:return location.pathname.length<2?"index":location.pathname.substring(1).replace(/\.\w+$/,"")}}getNumber(){return this.mapping==="number"?this.term??"":""}getIframeSrc(){const s=this._getCleanedUrl().toString(),t=`${s}${this.id?"#"+this.id:""}`,e=this.getMetaContent("description",!0),i=this.getMetaContent("giscus:backlink")||s,r={origin:t,session:this.__session,repo:this.repo,repoId:this.repoId??"",category:this.category??"",categoryId:this.categoryId??"",term:this.getTerm(),number:this.getNumber(),strict:this.strict,reactionsEnabled:this.reactionsEnabled,emitMetadata:this.emitMetadata,inputPosition:this.inputPosition,theme:this.theme,description:e,backLink:i},o=this._host,n=this.lang?`/${this.lang}`:"",h=new URLSearchParams(r);return`${o}${n}/widget?${h.toString()}`}render(){return wt` + + `}};u.styles=ft` + :host, + iframe { + width: 100%; + border: none; + min-height: 150px; + color-scheme: light dark; + } + + iframe.loading { + opacity: 0; + } + `;f([_({reflect:!0})],u.prototype,"host",2);f([_({reflect:!0})],u.prototype,"repo",2);f([_({reflect:!0})],u.prototype,"repoId",2);f([_({reflect:!0})],u.prototype,"category",2);f([_({reflect:!0})],u.prototype,"categoryId",2);f([_({reflect:!0})],u.prototype,"mapping",2);f([_({reflect:!0})],u.prototype,"term",2);f([_({reflect:!0})],u.prototype,"strict",2);f([_({reflect:!0})],u.prototype,"reactionsEnabled",2);f([_({reflect:!0})],u.prototype,"emitMetadata",2);f([_({reflect:!0})],u.prototype,"inputPosition",2);f([_({reflect:!0})],u.prototype,"theme",2);f([_({reflect:!0})],u.prototype,"lang",2);f([_({reflect:!0})],u.prototype,"loading",2);u=f([Jt("giscus-widget")],u);export{u as GiscusWidget}; diff --git a/assets/chunks/gitGraphDiagram-NY62KEGX.D-tkHlSx.js b/assets/chunks/gitGraphDiagram-NY62KEGX.D-tkHlSx.js new file mode 100644 index 000000000..f846228ec --- /dev/null +++ b/assets/chunks/gitGraphDiagram-NY62KEGX.D-tkHlSx.js @@ -0,0 +1,65 @@ +import{p as Z}from"./chunk-4BX2VUAB.B6a8mhSC.js";import{I as F}from"./chunk-QZHKN3VN.SQhQYWrL.js";import{_ as h,t as U,q as rr,s as er,g as tr,a as ar,b as nr,l as m,c as sr,d as or,u as cr,E as ir,z as dr,k as B,F as hr,G as lr,H as $r,I as fr}from"./theme.kqgpP4eL.js";import{p as gr}from"./treemap-KMMF4GRG.CcUr4GSN.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var u={NORMAL:0,REVERSE:1,HIGHLIGHT:2,MERGE:3,CHERRY_PICK:4},yr=$r.gitGraph,z=h(()=>hr({...yr,...lr().gitGraph}),"getConfig"),i=new F(()=>{const t=z(),r=t.mainBranchName,a=t.mainBranchOrder;return{mainBranchName:r,commits:new Map,head:null,branchConfig:new Map([[r,{name:r,order:a}]]),branches:new Map([[r,null]]),currBranch:r,direction:"LR",seq:0,options:{}}});function S(){return fr({length:7})}h(S,"getID");function N(t,r){const a=Object.create(null);return t.reduce((s,e)=>{const n=r(e);return a[n]||(a[n]=!0,s.push(e)),s},[])}h(N,"uniqBy");var xr=h(function(t){i.records.direction=t},"setDirection"),ur=h(function(t){m.debug("options str",t),t=t==null?void 0:t.trim(),t=t||"{}";try{i.records.options=JSON.parse(t)}catch(r){m.error("error while parsing gitGraph options",r.message)}},"setOptions"),pr=h(function(){return i.records.options},"getOptions"),br=h(function(t){let r=t.msg,a=t.id;const s=t.type;let e=t.tags;m.info("commit",r,a,s,e),m.debug("Entering commit:",r,a,s,e);const n=z();a=B.sanitizeText(a,n),r=B.sanitizeText(r,n),e=e==null?void 0:e.map(o=>B.sanitizeText(o,n));const c={id:a||i.records.seq+"-"+S(),message:r,seq:i.records.seq++,type:s??u.NORMAL,tags:e??[],parents:i.records.head==null?[]:[i.records.head.id],branch:i.records.currBranch};i.records.head=c,m.info("main branch",n.mainBranchName),i.records.commits.has(c.id)&&m.warn(`Commit ID ${c.id} already exists`),i.records.commits.set(c.id,c),i.records.branches.set(i.records.currBranch,c.id),m.debug("in pushCommit "+c.id)},"commit"),mr=h(function(t){let r=t.name;const a=t.order;if(r=B.sanitizeText(r,z()),i.records.branches.has(r))throw new Error(`Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ${r}")`);i.records.branches.set(r,i.records.head!=null?i.records.head.id:null),i.records.branchConfig.set(r,{name:r,order:a}),_(r),m.debug("in createBranch")},"branch"),wr=h(t=>{let r=t.branch,a=t.id;const s=t.type,e=t.tags,n=z();r=B.sanitizeText(r,n),a&&(a=B.sanitizeText(a,n));const c=i.records.branches.get(i.records.currBranch),o=i.records.branches.get(r),$=c?i.records.commits.get(c):void 0,l=o?i.records.commits.get(o):void 0;if($&&l&&$.branch===r)throw new Error(`Cannot merge branch '${r}' into itself.`);if(i.records.currBranch===r){const d=new Error('Incorrect usage of "merge". Cannot merge a branch to itself');throw d.hash={text:`merge ${r}`,token:`merge ${r}`,expected:["branch abc"]},d}if($===void 0||!$){const d=new Error(`Incorrect usage of "merge". Current branch (${i.records.currBranch})has no commits`);throw d.hash={text:`merge ${r}`,token:`merge ${r}`,expected:["commit"]},d}if(!i.records.branches.has(r)){const d=new Error('Incorrect usage of "merge". Branch to be merged ('+r+") does not exist");throw d.hash={text:`merge ${r}`,token:`merge ${r}`,expected:[`branch ${r}`]},d}if(l===void 0||!l){const d=new Error('Incorrect usage of "merge". Branch to be merged ('+r+") has no commits");throw d.hash={text:`merge ${r}`,token:`merge ${r}`,expected:['"commit"']},d}if($===l){const d=new Error('Incorrect usage of "merge". Both branches have same head');throw d.hash={text:`merge ${r}`,token:`merge ${r}`,expected:["branch abc"]},d}if(a&&i.records.commits.has(a)){const d=new Error('Incorrect usage of "merge". Commit with id:'+a+" already exists, use different custom id");throw d.hash={text:`merge ${r} ${a} ${s} ${e==null?void 0:e.join(" ")}`,token:`merge ${r} ${a} ${s} ${e==null?void 0:e.join(" ")}`,expected:[`merge ${r} ${a}_UNIQUE ${s} ${e==null?void 0:e.join(" ")}`]},d}const f=o||"",g={id:a||`${i.records.seq}-${S()}`,message:`merged branch ${r} into ${i.records.currBranch}`,seq:i.records.seq++,parents:i.records.head==null?[]:[i.records.head.id,f],branch:i.records.currBranch,type:u.MERGE,customType:s,customId:!!a,tags:e??[]};i.records.head=g,i.records.commits.set(g.id,g),i.records.branches.set(i.records.currBranch,g.id),m.debug(i.records.branches),m.debug("in mergeBranch")},"merge"),vr=h(function(t){let r=t.id,a=t.targetId,s=t.tags,e=t.parent;m.debug("Entering cherryPick:",r,a,s);const n=z();if(r=B.sanitizeText(r,n),a=B.sanitizeText(a,n),s=s==null?void 0:s.map($=>B.sanitizeText($,n)),e=B.sanitizeText(e,n),!r||!i.records.commits.has(r)){const $=new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');throw $.hash={text:`cherryPick ${r} ${a}`,token:`cherryPick ${r} ${a}`,expected:["cherry-pick abc"]},$}const c=i.records.commits.get(r);if(c===void 0||!c)throw new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');if(e&&!(Array.isArray(c.parents)&&c.parents.includes(e)))throw new Error("Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.");const o=c.branch;if(c.type===u.MERGE&&!e)throw new Error("Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.");if(!a||!i.records.commits.has(a)){if(o===i.records.currBranch){const g=new Error('Incorrect usage of "cherryPick". Source commit is already on current branch');throw g.hash={text:`cherryPick ${r} ${a}`,token:`cherryPick ${r} ${a}`,expected:["cherry-pick abc"]},g}const $=i.records.branches.get(i.records.currBranch);if($===void 0||!$){const g=new Error(`Incorrect usage of "cherry-pick". Current branch (${i.records.currBranch})has no commits`);throw g.hash={text:`cherryPick ${r} ${a}`,token:`cherryPick ${r} ${a}`,expected:["cherry-pick abc"]},g}const l=i.records.commits.get($);if(l===void 0||!l){const g=new Error(`Incorrect usage of "cherry-pick". Current branch (${i.records.currBranch})has no commits`);throw g.hash={text:`cherryPick ${r} ${a}`,token:`cherryPick ${r} ${a}`,expected:["cherry-pick abc"]},g}const f={id:i.records.seq+"-"+S(),message:`cherry-picked ${c==null?void 0:c.message} into ${i.records.currBranch}`,seq:i.records.seq++,parents:i.records.head==null?[]:[i.records.head.id,c.id],branch:i.records.currBranch,type:u.CHERRY_PICK,tags:s?s.filter(Boolean):[`cherry-pick:${c.id}${c.type===u.MERGE?`|parent:${e}`:""}`]};i.records.head=f,i.records.commits.set(f.id,f),i.records.branches.set(i.records.currBranch,f.id),m.debug(i.records.branches),m.debug("in cherryPick")}},"cherryPick"),_=h(function(t){if(t=B.sanitizeText(t,z()),i.records.branches.has(t)){i.records.currBranch=t;const r=i.records.branches.get(i.records.currBranch);r===void 0||!r?i.records.head=null:i.records.head=i.records.commits.get(r)??null}else{const r=new Error(`Trying to checkout branch which is not yet created. (Help try using "branch ${t}")`);throw r.hash={text:`checkout ${t}`,token:`checkout ${t}`,expected:[`branch ${t}`]},r}},"checkout");function A(t,r,a){const s=t.indexOf(r);s===-1?t.push(a):t.splice(s,1,a)}h(A,"upsert");function D(t){const r=t.reduce((e,n)=>e.seq>n.seq?e:n,t[0]);let a="";t.forEach(function(e){e===r?a+=" *":a+=" |"});const s=[a,r.id,r.seq];for(const e in i.records.branches)i.records.branches.get(e)===r.id&&s.push(e);if(m.debug(s.join(" ")),r.parents&&r.parents.length==2&&r.parents[0]&&r.parents[1]){const e=i.records.commits.get(r.parents[0]);A(t,r,e),r.parents[1]&&t.push(i.records.commits.get(r.parents[1]))}else{if(r.parents.length==0)return;if(r.parents[0]){const e=i.records.commits.get(r.parents[0]);A(t,r,e)}}t=N(t,e=>e.id),D(t)}h(D,"prettyPrintCommitHistory");var Cr=h(function(){m.debug(i.records.commits);const t=V()[0];D([t])},"prettyPrint"),Er=h(function(){i.reset(),dr()},"clear"),Br=h(function(){return[...i.records.branchConfig.values()].map((r,a)=>r.order!==null&&r.order!==void 0?r:{...r,order:parseFloat(`0.${a}`)}).sort((r,a)=>(r.order??0)-(a.order??0)).map(({name:r})=>({name:r}))},"getBranchesAsObjArray"),kr=h(function(){return i.records.branches},"getBranches"),Lr=h(function(){return i.records.commits},"getCommits"),V=h(function(){const t=[...i.records.commits.values()];return t.forEach(function(r){m.debug(r.id)}),t.sort((r,a)=>r.seq-a.seq),t},"getCommitsArray"),Tr=h(function(){return i.records.currBranch},"getCurrentBranch"),Mr=h(function(){return i.records.direction},"getDirection"),Rr=h(function(){return i.records.head},"getHead"),X={commitType:u,getConfig:z,setDirection:xr,setOptions:ur,getOptions:pr,commit:br,branch:mr,merge:wr,cherryPick:vr,checkout:_,prettyPrint:Cr,clear:Er,getBranchesAsObjArray:Br,getBranches:kr,getCommits:Lr,getCommitsArray:V,getCurrentBranch:Tr,getDirection:Mr,getHead:Rr,setAccTitle:nr,getAccTitle:ar,getAccDescription:tr,setAccDescription:er,setDiagramTitle:rr,getDiagramTitle:U},Ir=h((t,r)=>{Z(t,r),t.dir&&r.setDirection(t.dir);for(const a of t.statements)qr(a,r)},"populate"),qr=h((t,r)=>{const s={Commit:h(e=>r.commit(Or(e)),"Commit"),Branch:h(e=>r.branch(zr(e)),"Branch"),Merge:h(e=>r.merge(Gr(e)),"Merge"),Checkout:h(e=>r.checkout(Hr(e)),"Checkout"),CherryPicking:h(e=>r.cherryPick(Pr(e)),"CherryPicking")}[t.$type];s?s(t):m.error(`Unknown statement type: ${t.$type}`)},"parseStatement"),Or=h(t=>({id:t.id,msg:t.message??"",type:t.type!==void 0?u[t.type]:u.NORMAL,tags:t.tags??void 0}),"parseCommit"),zr=h(t=>({name:t.name,order:t.order??0}),"parseBranch"),Gr=h(t=>({branch:t.branch,id:t.id??"",type:t.type!==void 0?u[t.type]:void 0,tags:t.tags??void 0}),"parseMerge"),Hr=h(t=>t.branch,"parseCheckout"),Pr=h(t=>{var a;return{id:t.id,targetId:"",tags:((a=t.tags)==null?void 0:a.length)===0?void 0:t.tags,parent:t.parent}},"parseCherryPicking"),Wr={parse:h(async t=>{const r=await gr("gitGraph",t);m.debug(r),Ir(r,X)},"parse")},j=sr(),b=j==null?void 0:j.gitGraph,R=10,I=40,k=4,L=2,O=8,C=new Map,E=new Map,P=30,G=new Map,W=[],M=0,x="LR",Sr=h(()=>{C.clear(),E.clear(),G.clear(),M=0,W=[],x="LR"},"clear"),J=h(t=>{const r=document.createElementNS("http://www.w3.org/2000/svg","text");return(typeof t=="string"?t.split(/\\n|\n|/gi):t).forEach(s=>{const e=document.createElementNS("http://www.w3.org/2000/svg","tspan");e.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),e.setAttribute("dy","1em"),e.setAttribute("x","0"),e.setAttribute("class","row"),e.textContent=s.trim(),r.appendChild(e)}),r},"drawText"),Q=h(t=>{let r,a,s;return x==="BT"?(a=h((e,n)=>e<=n,"comparisonFunc"),s=1/0):(a=h((e,n)=>e>=n,"comparisonFunc"),s=0),t.forEach(e=>{var c,o;const n=x==="TB"||x=="BT"?(c=E.get(e))==null?void 0:c.y:(o=E.get(e))==null?void 0:o.x;n!==void 0&&a(n,s)&&(r=e,s=n)}),r},"findClosestParent"),jr=h(t=>{let r="",a=1/0;return t.forEach(s=>{const e=E.get(s).y;e<=a&&(r=s,a=e)}),r||void 0},"findClosestParentBT"),Ar=h((t,r,a)=>{let s=a,e=a;const n=[];t.forEach(c=>{const o=r.get(c);if(!o)throw new Error(`Commit not found for key ${c}`);o.parents.length?(s=Yr(o),e=Math.max(s,e)):n.push(o),Kr(o,s)}),s=e,n.forEach(c=>{Nr(c,s,a)}),t.forEach(c=>{const o=r.get(c);if(o!=null&&o.parents.length){const $=jr(o.parents);s=E.get($).y-I,s<=e&&(e=s);const l=C.get(o.branch).pos,f=s-R;E.set(o.id,{x:l,y:f})}})},"setParallelBTPos"),Dr=h(t=>{var s;const r=Q(t.parents.filter(e=>e!==null));if(!r)throw new Error(`Closest parent not found for commit ${t.id}`);const a=(s=E.get(r))==null?void 0:s.y;if(a===void 0)throw new Error(`Closest parent position not found for commit ${t.id}`);return a},"findClosestParentPos"),Yr=h(t=>Dr(t)+I,"calculateCommitPosition"),Kr=h((t,r)=>{const a=C.get(t.branch);if(!a)throw new Error(`Branch not found for commit ${t.id}`);const s=a.pos,e=r+R;return E.set(t.id,{x:s,y:e}),{x:s,y:e}},"setCommitPosition"),Nr=h((t,r,a)=>{const s=C.get(t.branch);if(!s)throw new Error(`Branch not found for commit ${t.id}`);const e=r+a,n=s.pos;E.set(t.id,{x:n,y:e})},"setRootPosition"),_r=h((t,r,a,s,e,n)=>{if(n===u.HIGHLIGHT)t.append("rect").attr("x",a.x-10).attr("y",a.y-10).attr("width",20).attr("height",20).attr("class",`commit ${r.id} commit-highlight${e%O} ${s}-outer`),t.append("rect").attr("x",a.x-6).attr("y",a.y-6).attr("width",12).attr("height",12).attr("class",`commit ${r.id} commit${e%O} ${s}-inner`);else if(n===u.CHERRY_PICK)t.append("circle").attr("cx",a.x).attr("cy",a.y).attr("r",10).attr("class",`commit ${r.id} ${s}`),t.append("circle").attr("cx",a.x-3).attr("cy",a.y+2).attr("r",2.75).attr("fill","#fff").attr("class",`commit ${r.id} ${s}`),t.append("circle").attr("cx",a.x+3).attr("cy",a.y+2).attr("r",2.75).attr("fill","#fff").attr("class",`commit ${r.id} ${s}`),t.append("line").attr("x1",a.x+3).attr("y1",a.y+1).attr("x2",a.x).attr("y2",a.y-5).attr("stroke","#fff").attr("class",`commit ${r.id} ${s}`),t.append("line").attr("x1",a.x-3).attr("y1",a.y+1).attr("x2",a.x).attr("y2",a.y-5).attr("stroke","#fff").attr("class",`commit ${r.id} ${s}`);else{const c=t.append("circle");if(c.attr("cx",a.x),c.attr("cy",a.y),c.attr("r",r.type===u.MERGE?9:10),c.attr("class",`commit ${r.id} commit${e%O}`),n===u.MERGE){const o=t.append("circle");o.attr("cx",a.x),o.attr("cy",a.y),o.attr("r",6),o.attr("class",`commit ${s} ${r.id} commit${e%O}`)}n===u.REVERSE&&t.append("path").attr("d",`M ${a.x-5},${a.y-5}L${a.x+5},${a.y+5}M${a.x-5},${a.y+5}L${a.x+5},${a.y-5}`).attr("class",`commit ${s} ${r.id} commit${e%O}`)}},"drawCommitBullet"),Vr=h((t,r,a,s)=>{var e;if(r.type!==u.CHERRY_PICK&&(r.customId&&r.type===u.MERGE||r.type!==u.MERGE)&&(b!=null&&b.showCommitLabel)){const n=t.append("g"),c=n.insert("rect").attr("class","commit-label-bkg"),o=n.append("text").attr("x",s).attr("y",a.y+25).attr("class","commit-label").text(r.id),$=(e=o.node())==null?void 0:e.getBBox();if($&&(c.attr("x",a.posWithOffset-$.width/2-L).attr("y",a.y+13.5).attr("width",$.width+2*L).attr("height",$.height+2*L),x==="TB"||x==="BT"?(c.attr("x",a.x-($.width+4*k+5)).attr("y",a.y-12),o.attr("x",a.x-($.width+4*k)).attr("y",a.y+$.height-12)):o.attr("x",a.posWithOffset-$.width/2),b.rotateCommitLabel))if(x==="TB"||x==="BT")o.attr("transform","rotate(-45, "+a.x+", "+a.y+")"),c.attr("transform","rotate(-45, "+a.x+", "+a.y+")");else{const l=-7.5-($.width+10)/25*9.5,f=10+$.width/25*8.5;n.attr("transform","translate("+l+", "+f+") rotate(-45, "+s+", "+a.y+")")}}},"drawCommitLabel"),Xr=h((t,r,a,s)=>{var e;if(r.tags.length>0){let n=0,c=0,o=0;const $=[];for(const l of r.tags.reverse()){const f=t.insert("polygon"),g=t.append("circle"),d=t.append("text").attr("y",a.y-16-n).attr("class","tag-label").text(l),y=(e=d.node())==null?void 0:e.getBBox();if(!y)throw new Error("Tag bbox not found");c=Math.max(c,y.width),o=Math.max(o,y.height),d.attr("x",a.posWithOffset-y.width/2),$.push({tag:d,hole:g,rect:f,yOffset:n}),n+=20}for(const{tag:l,hole:f,rect:g,yOffset:d}of $){const y=o/2,p=a.y-19.2-d;if(g.attr("class","tag-label-bkg").attr("points",` + ${s-c/2-k/2},${p+L} + ${s-c/2-k/2},${p-L} + ${a.posWithOffset-c/2-k},${p-y-L} + ${a.posWithOffset+c/2+k},${p-y-L} + ${a.posWithOffset+c/2+k},${p+y+L} + ${a.posWithOffset-c/2-k},${p+y+L}`),f.attr("cy",p).attr("cx",s-c/2+k/2).attr("r",1.5).attr("class","tag-hole"),x==="TB"||x==="BT"){const w=s+d;g.attr("class","tag-label-bkg").attr("points",` + ${a.x},${w+2} + ${a.x},${w-2} + ${a.x+R},${w-y-2} + ${a.x+R+c+4},${w-y-2} + ${a.x+R+c+4},${w+y+2} + ${a.x+R},${w+y+2}`).attr("transform","translate(12,12) rotate(45, "+a.x+","+s+")"),f.attr("cx",a.x+k/2).attr("cy",w).attr("transform","translate(12,12) rotate(45, "+a.x+","+s+")"),l.attr("x",a.x+5).attr("y",w+3).attr("transform","translate(14,14) rotate(45, "+a.x+","+s+")")}}}},"drawCommitTags"),Jr=h(t=>{switch(t.customType??t.type){case u.NORMAL:return"commit-normal";case u.REVERSE:return"commit-reverse";case u.HIGHLIGHT:return"commit-highlight";case u.MERGE:return"commit-merge";case u.CHERRY_PICK:return"commit-cherry-pick";default:return"commit-normal"}},"getCommitClassType"),Qr=h((t,r,a,s)=>{const e={x:0,y:0};if(t.parents.length>0){const n=Q(t.parents);if(n){const c=s.get(n)??e;return r==="TB"?c.y+I:r==="BT"?(s.get(t.id)??e).y-I:c.x+I}}else return r==="TB"?P:r==="BT"?(s.get(t.id)??e).y-I:0;return 0},"calculatePosition"),Zr=h((t,r,a)=>{var c,o;const s=x==="BT"&&a?r:r+R,e=x==="TB"||x==="BT"?s:(c=C.get(t.branch))==null?void 0:c.pos,n=x==="TB"||x==="BT"?(o=C.get(t.branch))==null?void 0:o.pos:s;if(n===void 0||e===void 0)throw new Error(`Position were undefined for commit ${t.id}`);return{x:n,y:e,posWithOffset:s}},"getCommitPosition"),K=h((t,r,a)=>{if(!b)throw new Error("GitGraph config not found");const s=t.append("g").attr("class","commit-bullets"),e=t.append("g").attr("class","commit-labels");let n=x==="TB"||x==="BT"?P:0;const c=[...r.keys()],o=(b==null?void 0:b.parallelCommits)??!1,$=h((f,g)=>{var p,w;const d=(p=r.get(f))==null?void 0:p.seq,y=(w=r.get(g))==null?void 0:w.seq;return d!==void 0&&y!==void 0?d-y:0},"sortKeys");let l=c.sort($);x==="BT"&&(o&&Ar(l,r,n),l=l.reverse()),l.forEach(f=>{var y;const g=r.get(f);if(!g)throw new Error(`Commit not found for key ${f}`);o&&(n=Qr(g,x,n,E));const d=Zr(g,n,o);if(a){const p=Jr(g),w=g.customType??g.type,q=((y=C.get(g.branch))==null?void 0:y.index)??0;_r(s,g,d,p,q,w),Vr(e,g,d,n),Xr(e,g,d,n)}x==="TB"||x==="BT"?E.set(g.id,{x:d.x,y:d.posWithOffset}):E.set(g.id,{x:d.posWithOffset,y:d.y}),n=x==="BT"&&o?n+I:n+I+R,n>M&&(M=n)})},"drawCommits"),Fr=h((t,r,a,s,e)=>{const c=(x==="TB"||x==="BT"?a.xl.branch===c,"isOnBranchToGetCurve"),$=h(l=>l.seq>t.seq&&l.seq$(l)&&o(l))},"shouldRerouteArrow"),H=h((t,r,a=0)=>{const s=t+Math.abs(t-r)/2;if(a>5)return s;if(W.every(c=>Math.abs(c-s)>=10))return W.push(s),s;const n=Math.abs(t-r);return H(t,r-n/5,a+1)},"findLane"),Ur=h((t,r,a,s)=>{var y,p,w,q,Y;const e=E.get(r.id),n=E.get(a.id);if(e===void 0||n===void 0)throw new Error(`Commit positions not found for commits ${r.id} and ${a.id}`);const c=Fr(r,a,e,n,s);let o="",$="",l=0,f=0,g=(y=C.get(a.branch))==null?void 0:y.index;a.type===u.MERGE&&r.id!==a.parents[0]&&(g=(p=C.get(r.branch))==null?void 0:p.index);let d;if(c){o="A 10 10, 0, 0, 0,",$="A 10 10, 0, 0, 1,",l=10,f=10;const T=e.yn.x&&(o="A 20 20, 0, 0, 0,",$="A 20 20, 0, 0, 1,",l=20,f=20,a.type===u.MERGE&&r.id!==a.parents[0]?d=`M ${e.x} ${e.y} L ${e.x} ${n.y-l} ${$} ${e.x-f} ${n.y} L ${n.x} ${n.y}`:d=`M ${e.x} ${e.y} L ${n.x+l} ${e.y} ${o} ${n.x} ${e.y+f} L ${n.x} ${n.y}`),e.x===n.x&&(d=`M ${e.x} ${e.y} L ${n.x} ${n.y}`)):x==="BT"?(e.xn.x&&(o="A 20 20, 0, 0, 0,",$="A 20 20, 0, 0, 1,",l=20,f=20,a.type===u.MERGE&&r.id!==a.parents[0]?d=`M ${e.x} ${e.y} L ${e.x} ${n.y+l} ${o} ${e.x-f} ${n.y} L ${n.x} ${n.y}`:d=`M ${e.x} ${e.y} L ${n.x-l} ${e.y} ${o} ${n.x} ${e.y-f} L ${n.x} ${n.y}`),e.x===n.x&&(d=`M ${e.x} ${e.y} L ${n.x} ${n.y}`)):(e.yn.y&&(a.type===u.MERGE&&r.id!==a.parents[0]?d=`M ${e.x} ${e.y} L ${n.x-l} ${e.y} ${o} ${n.x} ${e.y-f} L ${n.x} ${n.y}`:d=`M ${e.x} ${e.y} L ${e.x} ${n.y+l} ${$} ${e.x+f} ${n.y} L ${n.x} ${n.y}`),e.y===n.y&&(d=`M ${e.x} ${e.y} L ${n.x} ${n.y}`));if(d===void 0)throw new Error("Line definition not found");t.append("path").attr("d",d).attr("class","arrow arrow"+g%O)},"drawArrow"),re=h((t,r)=>{const a=t.append("g").attr("class","commit-arrows");[...r.keys()].forEach(s=>{const e=r.get(s);e.parents&&e.parents.length>0&&e.parents.forEach(n=>{Ur(a,r.get(n),e,r)})})},"drawArrows"),ee=h((t,r)=>{const a=t.append("g");r.forEach((s,e)=>{var p;const n=e%O,c=(p=C.get(s.name))==null?void 0:p.pos;if(c===void 0)throw new Error(`Position not found for branch ${s.name}`);const o=a.append("line");o.attr("x1",0),o.attr("y1",c),o.attr("x2",M),o.attr("y2",c),o.attr("class","branch branch"+n),x==="TB"?(o.attr("y1",P),o.attr("x1",c),o.attr("y2",M),o.attr("x2",c)):x==="BT"&&(o.attr("y1",M),o.attr("x1",c),o.attr("y2",P),o.attr("x2",c)),W.push(c);const $=s.name,l=J($),f=a.insert("rect"),d=a.insert("g").attr("class","branchLabel").insert("g").attr("class","label branch-label"+n);d.node().appendChild(l);const y=l.getBBox();f.attr("class","branchLabelBkg label"+n).attr("rx",4).attr("ry",4).attr("x",-y.width-4-((b==null?void 0:b.rotateCommitLabel)===!0?30:0)).attr("y",-y.height/2+8).attr("width",y.width+18).attr("height",y.height+4),d.attr("transform","translate("+(-y.width-14-((b==null?void 0:b.rotateCommitLabel)===!0?30:0))+", "+(c-y.height/2-1)+")"),x==="TB"?(f.attr("x",c-y.width/2-10).attr("y",0),d.attr("transform","translate("+(c-y.width/2-5)+", 0)")):x==="BT"?(f.attr("x",c-y.width/2-10).attr("y",M),d.attr("transform","translate("+(c-y.width/2-5)+", "+M+")")):f.attr("transform","translate(-19, "+(c-y.height/2)+")")})},"drawBranches"),te=h(function(t,r,a,s,e){return C.set(t,{pos:r,index:a}),r+=50+(e?40:0)+(x==="TB"||x==="BT"?s.width/2:0),r},"setBranchPosition"),ae=h(function(t,r,a,s){if(Sr(),m.debug("in gitgraph renderer",t+` +`,"id:",r,a),!b)throw new Error("GitGraph config not found");const e=b.rotateCommitLabel??!1,n=s.db;G=n.getCommits();const c=n.getBranchesAsObjArray();x=n.getDirection();const o=or(`[id="${r}"]`);let $=0;c.forEach((l,f)=>{var q;const g=J(l.name),d=o.append("g"),y=d.insert("g").attr("class","branchLabel"),p=y.insert("g").attr("class","label branch-label");(q=p.node())==null||q.appendChild(g);const w=g.getBBox();$=te(l.name,$,f,w,e),p.remove(),y.remove(),d.remove()}),K(o,G,!1),b.showBranches&&ee(o,c),re(o,G),K(o,G,!0),cr.insertTitle(o,"gitTitleText",b.titleTopMargin??0,n.getDiagramTitle()),ir(void 0,o,b.diagramPadding,b.useMaxWidth)},"draw"),ne={draw:ae},se=h(t=>` + .commit-id, + .commit-msg, + .branch-label { + fill: lightgrey; + color: lightgrey; + font-family: 'trebuchet ms', verdana, arial, sans-serif; + font-family: var(--mermaid-font-family); + } + ${[0,1,2,3,4,5,6,7].map(r=>` + .branch-label${r} { fill: ${t["gitBranchLabel"+r]}; } + .commit${r} { stroke: ${t["git"+r]}; fill: ${t["git"+r]}; } + .commit-highlight${r} { stroke: ${t["gitInv"+r]}; fill: ${t["gitInv"+r]}; } + .label${r} { fill: ${t["git"+r]}; } + .arrow${r} { stroke: ${t["git"+r]}; } + `).join(` +`)} + + .branch { + stroke-width: 1; + stroke: ${t.lineColor}; + stroke-dasharray: 2; + } + .commit-label { font-size: ${t.commitLabelFontSize}; fill: ${t.commitLabelColor};} + .commit-label-bkg { font-size: ${t.commitLabelFontSize}; fill: ${t.commitLabelBackground}; opacity: 0.5; } + .tag-label { font-size: ${t.tagLabelFontSize}; fill: ${t.tagLabelColor};} + .tag-label-bkg { fill: ${t.tagLabelBackground}; stroke: ${t.tagLabelBorder}; } + .tag-hole { fill: ${t.textColor}; } + + .commit-merge { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + } + .commit-reverse { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + stroke-width: 3; + } + .commit-highlight-outer { + } + .commit-highlight-inner { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + } + + .arrow { stroke-width: 8; stroke-linecap: round; fill: none} + .gitTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; + } +`,"getStyles"),oe=se,ge={parser:Wr,db:X,renderer:ne,styles:oe};export{ge as diagram}; diff --git a/assets/chunks/graph.CD7z0KlM.js b/assets/chunks/graph.CD7z0KlM.js new file mode 100644 index 000000000..431de827c --- /dev/null +++ b/assets/chunks/graph.CD7z0KlM.js @@ -0,0 +1 @@ +import{b1 as T,b2 as C,b3 as z,b4 as R,b5 as S,b6 as k,b7 as V,b8 as L,b9 as K,ba as j,bb as ee,bc as te,bd as re,be as se,bf as ne,bg as ie,bh as ae,ay as oe,bi as ue,bj as he,bk as y,bl as v,bm as $}from"./theme.kqgpP4eL.js";import{k as d,g as Y,s as de,e as ce,f as fe,h as ge,j as le,c as be,l as _e,b as pe,m,n as g,r as ye}from"./baseUniq.BHxmztwl.js";function me(t,e){return t&&T(e,d(e),t)}function je(t,e){return t&&T(e,C(e),t)}function Te(t,e){return T(t,Y(t),e)}var Oe=Object.getOwnPropertySymbols,q=Oe?function(t){for(var e=[];t;)ce(e,Y(t)),t=z(t);return e}:de;function Ae(t,e){return T(t,q(t),e)}function Ee(t){return fe(t,C,q)}var Ce=Object.prototype,Le=Ce.hasOwnProperty;function we(t){var e=t.length,r=new t.constructor(e);return e&&typeof t[0]=="string"&&Le.call(t,"index")&&(r.index=t.index,r.input=t.input),r}function Ne(t,e){var r=e?R(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}var Fe=/\w*$/;function Pe(t){var e=new t.constructor(t.source,Fe.exec(t));return e.lastIndex=t.lastIndex,e}var I=S?S.prototype:void 0,D=I?I.valueOf:void 0;function Se(t){return D?Object(D.call(t)):{}}var ve="[object Boolean]",$e="[object Date]",Ie="[object Map]",De="[object Number]",Me="[object RegExp]",Ge="[object Set]",Ue="[object String]",xe="[object Symbol]",Be="[object ArrayBuffer]",Re="[object DataView]",Ve="[object Float32Array]",Ke="[object Float64Array]",Ye="[object Int8Array]",qe="[object Int16Array]",He="[object Int32Array]",We="[object Uint8Array]",Je="[object Uint8ClampedArray]",Qe="[object Uint16Array]",Xe="[object Uint32Array]";function Ze(t,e,r){var s=t.constructor;switch(e){case Be:return R(t);case ve:case $e:return new s(+t);case Re:return Ne(t,r);case Ve:case Ke:case Ye:case qe:case He:case We:case Je:case Qe:case Xe:return k(t,r);case Ie:return new s;case De:case Ue:return new s(t);case Me:return Pe(t);case Ge:return new s;case xe:return Se(t)}}var ze="[object Map]";function ke(t){return V(t)&&L(t)==ze}var M=j&&j.isMap,et=M?K(M):ke,tt="[object Set]";function rt(t){return V(t)&&L(t)==tt}var G=j&&j.isSet,st=G?K(G):rt,nt=1,it=2,at=4,H="[object Arguments]",ot="[object Array]",ut="[object Boolean]",ht="[object Date]",dt="[object Error]",W="[object Function]",ct="[object GeneratorFunction]",ft="[object Map]",gt="[object Number]",J="[object Object]",lt="[object RegExp]",bt="[object Set]",_t="[object String]",pt="[object Symbol]",yt="[object WeakMap]",mt="[object ArrayBuffer]",jt="[object DataView]",Tt="[object Float32Array]",Ot="[object Float64Array]",At="[object Int8Array]",Et="[object Int16Array]",Ct="[object Int32Array]",Lt="[object Uint8Array]",wt="[object Uint8ClampedArray]",Nt="[object Uint16Array]",Ft="[object Uint32Array]",o={};o[H]=o[ot]=o[mt]=o[jt]=o[ut]=o[ht]=o[Tt]=o[Ot]=o[At]=o[Et]=o[Ct]=o[ft]=o[gt]=o[J]=o[lt]=o[bt]=o[_t]=o[pt]=o[Lt]=o[wt]=o[Nt]=o[Ft]=!0;o[dt]=o[W]=o[yt]=!1;function O(t,e,r,s,n,a){var i,u=e&nt,h=e&it,X=e&at;if(i!==void 0)return i;if(!ee(t))return t;var w=oe(t);if(w){if(i=we(t),!u)return te(t,i)}else{var b=L(t),N=b==W||b==ct;if(re(t))return se(t,u);if(b==J||b==H||N&&!n){if(i=h||N?{}:ne(t),!u)return h?Ae(t,je(i,t)):Te(t,me(i,t))}else{if(!o[b])return n?t:{};i=Ze(t,b,u)}}a||(a=new ie);var F=a.get(t);if(F)return F;a.set(t,i),st(t)?t.forEach(function(c){i.add(O(c,e,r,c,t,a))}):et(t)&&t.forEach(function(c,f){i.set(f,O(c,e,r,f,t,a))});var Z=X?h?Ee:ge:h?C:d,P=w?void 0:Z(t);return le(P||t,function(c,f){P&&(f=c,c=t[f]),ae(i,f,O(c,e,r,f,t,a))}),i}function Pt(t,e){return be(e,function(r){return t[r]})}function A(t){return t==null?[]:Pt(t,d(t))}function _(t){return t===void 0}var St=ue(function(t){return _e(pe(t,1,he,!0))}),vt="\0",l="\0",U="";class Q{constructor(e={}){this._isDirected=Object.prototype.hasOwnProperty.call(e,"directed")?e.directed:!0,this._isMultigraph=Object.prototype.hasOwnProperty.call(e,"multigraph")?e.multigraph:!1,this._isCompound=Object.prototype.hasOwnProperty.call(e,"compound")?e.compound:!1,this._label=void 0,this._defaultNodeLabelFn=y(void 0),this._defaultEdgeLabelFn=y(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children[l]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(e){return this._label=e,this}graph(){return this._label}setDefaultNodeLabel(e){return v(e)||(e=y(e)),this._defaultNodeLabelFn=e,this}nodeCount(){return this._nodeCount}nodes(){return d(this._nodes)}sources(){var e=this;return m(this.nodes(),function(r){return $(e._in[r])})}sinks(){var e=this;return m(this.nodes(),function(r){return $(e._out[r])})}setNodes(e,r){var s=arguments,n=this;return g(e,function(a){s.length>1?n.setNode(a,r):n.setNode(a)}),this}setNode(e,r){return Object.prototype.hasOwnProperty.call(this._nodes,e)?(arguments.length>1&&(this._nodes[e]=r),this):(this._nodes[e]=arguments.length>1?r:this._defaultNodeLabelFn(e),this._isCompound&&(this._parent[e]=l,this._children[e]={},this._children[l][e]=!0),this._in[e]={},this._preds[e]={},this._out[e]={},this._sucs[e]={},++this._nodeCount,this)}node(e){return this._nodes[e]}hasNode(e){return Object.prototype.hasOwnProperty.call(this._nodes,e)}removeNode(e){if(Object.prototype.hasOwnProperty.call(this._nodes,e)){var r=s=>this.removeEdge(this._edgeObjs[s]);delete this._nodes[e],this._isCompound&&(this._removeFromParentsChildList(e),delete this._parent[e],g(this.children(e),s=>{this.setParent(s)}),delete this._children[e]),g(d(this._in[e]),r),delete this._in[e],delete this._preds[e],g(d(this._out[e]),r),delete this._out[e],delete this._sucs[e],--this._nodeCount}return this}setParent(e,r){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(_(r))r=l;else{r+="";for(var s=r;!_(s);s=this.parent(s))if(s===e)throw new Error("Setting "+r+" as parent of "+e+" would create a cycle");this.setNode(r)}return this.setNode(e),this._removeFromParentsChildList(e),this._parent[e]=r,this._children[r][e]=!0,this}_removeFromParentsChildList(e){delete this._children[this._parent[e]][e]}parent(e){if(this._isCompound){var r=this._parent[e];if(r!==l)return r}}children(e){if(_(e)&&(e=l),this._isCompound){var r=this._children[e];if(r)return d(r)}else{if(e===l)return this.nodes();if(this.hasNode(e))return[]}}predecessors(e){var r=this._preds[e];if(r)return d(r)}successors(e){var r=this._sucs[e];if(r)return d(r)}neighbors(e){var r=this.predecessors(e);if(r)return St(r,this.successors(e))}isLeaf(e){var r;return this.isDirected()?r=this.successors(e):r=this.neighbors(e),r.length===0}filterNodes(e){var r=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});r.setGraph(this.graph());var s=this;g(this._nodes,function(i,u){e(u)&&r.setNode(u,i)}),g(this._edgeObjs,function(i){r.hasNode(i.v)&&r.hasNode(i.w)&&r.setEdge(i,s.edge(i))});var n={};function a(i){var u=s.parent(i);return u===void 0||r.hasNode(u)?(n[i]=u,u):u in n?n[u]:a(u)}return this._isCompound&&g(r.nodes(),function(i){r.setParent(i,a(i))}),r}setDefaultEdgeLabel(e){return v(e)||(e=y(e)),this._defaultEdgeLabelFn=e,this}edgeCount(){return this._edgeCount}edges(){return A(this._edgeObjs)}setPath(e,r){var s=this,n=arguments;return ye(e,function(a,i){return n.length>1?s.setEdge(a,i,r):s.setEdge(a,i),i}),this}setEdge(){var e,r,s,n,a=!1,i=arguments[0];typeof i=="object"&&i!==null&&"v"in i?(e=i.v,r=i.w,s=i.name,arguments.length===2&&(n=arguments[1],a=!0)):(e=i,r=arguments[1],s=arguments[3],arguments.length>2&&(n=arguments[2],a=!0)),e=""+e,r=""+r,_(s)||(s=""+s);var u=p(this._isDirected,e,r,s);if(Object.prototype.hasOwnProperty.call(this._edgeLabels,u))return a&&(this._edgeLabels[u]=n),this;if(!_(s)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(e),this.setNode(r),this._edgeLabels[u]=a?n:this._defaultEdgeLabelFn(e,r,s);var h=$t(this._isDirected,e,r,s);return e=h.v,r=h.w,Object.freeze(h),this._edgeObjs[u]=h,x(this._preds[r],e),x(this._sucs[e],r),this._in[r][u]=h,this._out[e][u]=h,this._edgeCount++,this}edge(e,r,s){var n=arguments.length===1?E(this._isDirected,arguments[0]):p(this._isDirected,e,r,s);return this._edgeLabels[n]}hasEdge(e,r,s){var n=arguments.length===1?E(this._isDirected,arguments[0]):p(this._isDirected,e,r,s);return Object.prototype.hasOwnProperty.call(this._edgeLabels,n)}removeEdge(e,r,s){var n=arguments.length===1?E(this._isDirected,arguments[0]):p(this._isDirected,e,r,s),a=this._edgeObjs[n];return a&&(e=a.v,r=a.w,delete this._edgeLabels[n],delete this._edgeObjs[n],B(this._preds[r],e),B(this._sucs[e],r),delete this._in[r][n],delete this._out[e][n],this._edgeCount--),this}inEdges(e,r){var s=this._in[e];if(s){var n=A(s);return r?m(n,function(a){return a.v===r}):n}}outEdges(e,r){var s=this._out[e];if(s){var n=A(s);return r?m(n,function(a){return a.w===r}):n}}nodeEdges(e,r){var s=this.inEdges(e,r);if(s)return s.concat(this.outEdges(e,r))}}Q.prototype._nodeCount=0;Q.prototype._edgeCount=0;function x(t,e){t[e]?t[e]++:t[e]=1}function B(t,e){--t[e]||delete t[e]}function p(t,e,r,s){var n=""+e,a=""+r;if(!t&&n>a){var i=n;n=a,a=i}return n+U+a+U+(_(s)?vt:s)}function $t(t,e,r,s){var n=""+e,a=""+r;if(!t&&n>a){var i=n;n=a,a=i}var u={v:n,w:a};return s&&(u.name=s),u}function E(t,e){return p(t,e.v,e.w,e.name)}export{Q as G,O as b,_ as i,A as v}; diff --git a/assets/chunks/infoDiagram-WHAUD3N6.BJpHyd3M.js b/assets/chunks/infoDiagram-WHAUD3N6.BJpHyd3M.js new file mode 100644 index 000000000..084f5f195 --- /dev/null +++ b/assets/chunks/infoDiagram-WHAUD3N6.BJpHyd3M.js @@ -0,0 +1,2 @@ +import{_ as e,l as s,K as n,e as i,L as p}from"./theme.kqgpP4eL.js";import{p as g}from"./treemap-KMMF4GRG.CcUr4GSN.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var v={parse:e(async r=>{const a=await g("info",r);s.debug(a)},"parse")},d={version:p.version+""},m=e(()=>d.version,"getVersion"),c={getVersion:m},l=e((r,a,o)=>{s.debug(`rendering info diagram +`+r);const t=n(a);i(t,100,400,!0),t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${o}`)},"draw"),f={draw:l},S={parser:v,db:c,renderer:f};export{S as diagram}; diff --git a/assets/chunks/journeyDiagram-XKPGCS4Q.DIPDU-n-.js b/assets/chunks/journeyDiagram-XKPGCS4Q.DIPDU-n-.js new file mode 100644 index 000000000..1b9d7ba55 --- /dev/null +++ b/assets/chunks/journeyDiagram-XKPGCS4Q.DIPDU-n-.js @@ -0,0 +1,139 @@ +import{a as gt,g as lt,f as mt,d as xt}from"./chunk-TZMSLE5B.CN1RMadv.js";import{g as kt}from"./chunk-FMBD7UC4.B39tdjdc.js";import{_ as n,g as _t,s as vt,a as bt,b as wt,t as Tt,q as St,c as R,d as G,e as $t,z as Mt,N as et}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var U=function(){var t=n(function(h,i,a,l){for(a=a||{},l=h.length;l--;a[h[l]]=i);return a},"o"),e=[6,8,10,11,12,14,16,17,18],s=[1,9],c=[1,10],r=[1,11],f=[1,12],u=[1,13],y=[1,14],g={trace:n(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:n(function(i,a,l,d,p,o,b){var k=o.length-1;switch(p){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:d.setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),d.setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),d.setAccDescription(this.$);break;case 12:d.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 13:d.addTask(o[k-1],o[k]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:s,12:c,14:r,16:f,17:u,18:y},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:n(function(i,a){if(a.recoverable)this.trace(i);else{var l=new Error(i);throw l.hash=a,l}},"parseError"),parse:n(function(i){var a=this,l=[0],d=[],p=[null],o=[],b=this.table,k="",C=0,K=0,dt=2,Q=1,yt=o.slice.call(arguments,1),_=Object.create(this.lexer),I={yy:{}};for(var O in this.yy)Object.prototype.hasOwnProperty.call(this.yy,O)&&(I.yy[O]=this.yy[O]);_.setInput(i,I.yy),I.yy.lexer=_,I.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var Y=_.yylloc;o.push(Y);var ft=_.options&&_.options.ranges;typeof I.yy.parseError=="function"?this.parseError=I.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pt(w){l.length=l.length-2*w,p.length=p.length-w,o.length=o.length-w}n(pt,"popStack");function D(){var w;return w=d.pop()||_.lex()||Q,typeof w!="number"&&(w instanceof Array&&(d=w,w=d.pop()),w=a.symbols_[w]||w),w}n(D,"lex");for(var v,A,T,q,F={},N,M,tt,z;;){if(A=l[l.length-1],this.defaultActions[A]?T=this.defaultActions[A]:((v===null||typeof v>"u")&&(v=D()),T=b[A]&&b[A][v]),typeof T>"u"||!T.length||!T[0]){var X="";z=[];for(N in b[A])this.terminals_[N]&&N>dt&&z.push("'"+this.terminals_[N]+"'");_.showPosition?X="Parse error on line "+(C+1)+`: +`+_.showPosition()+` +Expecting `+z.join(", ")+", got '"+(this.terminals_[v]||v)+"'":X="Parse error on line "+(C+1)+": Unexpected "+(v==Q?"end of input":"'"+(this.terminals_[v]||v)+"'"),this.parseError(X,{text:_.match,token:this.terminals_[v]||v,line:_.yylineno,loc:Y,expected:z})}if(T[0]instanceof Array&&T.length>1)throw new Error("Parse Error: multiple actions possible at state: "+A+", token: "+v);switch(T[0]){case 1:l.push(v),p.push(_.yytext),o.push(_.yylloc),l.push(T[1]),v=null,K=_.yyleng,k=_.yytext,C=_.yylineno,Y=_.yylloc;break;case 2:if(M=this.productions_[T[1]][1],F.$=p[p.length-M],F._$={first_line:o[o.length-(M||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(M||1)].first_column,last_column:o[o.length-1].last_column},ft&&(F._$.range=[o[o.length-(M||1)].range[0],o[o.length-1].range[1]]),q=this.performAction.apply(F,[k,K,C,I.yy,T[1],p,o].concat(yt)),typeof q<"u")return q;M&&(l=l.slice(0,-1*M*2),p=p.slice(0,-1*M),o=o.slice(0,-1*M)),l.push(this.productions_[T[1]][0]),p.push(F.$),o.push(F._$),tt=b[l[l.length-2]][l[l.length-1]],l.push(tt);break;case 3:return!0}}return!0},"parse")},m=function(){var h={EOF:1,parseError:n(function(a,l){if(this.yy.parser)this.yy.parser.parseError(a,l);else throw new Error(a)},"parseError"),setInput:n(function(i,a){return this.yy=a||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:n(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var a=i.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:n(function(i){var a=i.length,l=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===d.length?this.yylloc.first_column:0)+d[d.length-l.length].length-l[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:n(function(){return this._more=!0,this},"more"),reject:n(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:n(function(i){this.unput(this.match.slice(i))},"less"),pastInput:n(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:n(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:n(function(){var i=this.pastInput(),a=new Array(i.length+1).join("-");return i+this.upcomingInput()+` +`+a+"^"},"showPosition"),test_match:n(function(i,a){var l,d,p;if(this.options.backtrack_lexer&&(p={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(p.yylloc.range=this.yylloc.range.slice(0))),d=i[0].match(/(?:\r\n?|\n).*/g),d&&(this.yylineno+=d.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:d?d[d.length-1].length-d[d.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+i[0].length},this.yytext+=i[0],this.match+=i[0],this.matches=i,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(i[0].length),this.matched+=i[0],l=this.performAction.call(this,this.yy,this,a,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),l)return l;if(this._backtrack){for(var o in p)this[o]=p[o];return!1}return!1},"test_match"),next:n(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var i,a,l,d;this._more||(this.yytext="",this.match="");for(var p=this._currentRules(),o=0;oa[0].length)){if(a=l,d=o,this.options.backtrack_lexer){if(i=this.test_match(l,p[o]),i!==!1)return i;if(this._backtrack){a=!1;continue}else return!1}else if(!this.options.flex)break}return a?(i=this.test_match(a,p[d]),i!==!1?i:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:n(function(){var a=this.next();return a||this.lex()},"lex"),begin:n(function(a){this.conditionStack.push(a)},"begin"),popState:n(function(){var a=this.conditionStack.length-1;return a>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:n(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:n(function(a){return a=this.conditionStack.length-1-Math.abs(a||0),a>=0?this.conditionStack[a]:"INITIAL"},"topState"),pushState:n(function(a){this.begin(a)},"pushState"),stateStackSize:n(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:n(function(a,l,d,p){switch(d){case 0:break;case 1:break;case 2:return 10;case 3:break;case 4:break;case 5:return 4;case 6:return 11;case 7:return this.begin("acc_title"),12;case 8:return this.popState(),"acc_title_value";case 9:return this.begin("acc_descr"),14;case 10:return this.popState(),"acc_descr_value";case 11:this.begin("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 17;case 15:return 18;case 16:return 19;case 17:return":";case 18:return 6;case 19:return"INVALID"}},"anonymous"),rules:[/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,9,11,14,15,16,17,18,19],inclusive:!0}}};return h}();g.lexer=m;function x(){this.yy={}}return n(x,"Parser"),x.prototype=g,g.Parser=x,new x}();U.parser=U;var Et=U,V="",Z=[],L=[],B=[],Ct=n(function(){Z.length=0,L.length=0,V="",B.length=0,Mt()},"clear"),Pt=n(function(t){V=t,Z.push(t)},"addSection"),It=n(function(){return Z},"getSections"),At=n(function(){let t=it();const e=100;let s=0;for(;!t&&s{s.people&&t.push(...s.people)}),[...new Set(t)].sort()},"updateActors"),Vt=n(function(t,e){const s=e.substr(1).split(":");let c=0,r=[];s.length===1?(c=Number(s[0]),r=[]):(c=Number(s[0]),r=s[1].split(","));const f=r.map(y=>y.trim()),u={section:V,type:V,people:f,task:t,score:c};B.push(u)},"addTask"),Rt=n(function(t){const e={section:V,type:V,description:t,task:t,classes:[]};L.push(e)},"addTaskOrg"),it=n(function(){const t=n(function(s){return B[s].processed},"compileTask");let e=!0;for(const[s,c]of B.entries())t(s),e=e&&c.processed;return e},"compileTasks"),Lt=n(function(){return Ft()},"getActors"),rt={getConfig:n(()=>R().journey,"getConfig"),clear:Ct,setDiagramTitle:St,getDiagramTitle:Tt,setAccTitle:wt,getAccTitle:bt,setAccDescription:vt,getAccDescription:_t,addSection:Pt,getSections:It,getTasks:At,addTask:Vt,addTaskOrg:Rt,getActors:Lt},Bt=n(t=>`.label { + font-family: ${t.fontFamily}; + color: ${t.textColor}; + } + .mouth { + stroke: #666; + } + + line { + stroke: ${t.textColor} + } + + .legend { + fill: ${t.textColor}; + font-family: ${t.fontFamily}; + } + + .label text { + fill: #333; + } + .label { + color: ${t.textColor} + } + + .face { + ${t.faceColor?`fill: ${t.faceColor}`:"fill: #FFF8DC"}; + stroke: #999; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + stroke-width: 1px; + } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${t.arrowheadColor}; + } + + .edgePath .path { + stroke: ${t.lineColor}; + stroke-width: 1.5px; + } + + .flowchart-link { + stroke: ${t.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${t.edgeLabelBackground}; + rect { + opacity: 0.5; + } + text-align: center; + } + + .cluster rect { + } + + .cluster text { + fill: ${t.titleColor}; + } + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${t.fontFamily}; + font-size: 12px; + background: ${t.tertiaryColor}; + border: 1px solid ${t.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .task-type-0, .section-type-0 { + ${t.fillType0?`fill: ${t.fillType0}`:""}; + } + .task-type-1, .section-type-1 { + ${t.fillType0?`fill: ${t.fillType1}`:""}; + } + .task-type-2, .section-type-2 { + ${t.fillType0?`fill: ${t.fillType2}`:""}; + } + .task-type-3, .section-type-3 { + ${t.fillType0?`fill: ${t.fillType3}`:""}; + } + .task-type-4, .section-type-4 { + ${t.fillType0?`fill: ${t.fillType4}`:""}; + } + .task-type-5, .section-type-5 { + ${t.fillType0?`fill: ${t.fillType5}`:""}; + } + .task-type-6, .section-type-6 { + ${t.fillType0?`fill: ${t.fillType6}`:""}; + } + .task-type-7, .section-type-7 { + ${t.fillType0?`fill: ${t.fillType7}`:""}; + } + + .actor-0 { + ${t.actor0?`fill: ${t.actor0}`:""}; + } + .actor-1 { + ${t.actor1?`fill: ${t.actor1}`:""}; + } + .actor-2 { + ${t.actor2?`fill: ${t.actor2}`:""}; + } + .actor-3 { + ${t.actor3?`fill: ${t.actor3}`:""}; + } + .actor-4 { + ${t.actor4?`fill: ${t.actor4}`:""}; + } + .actor-5 { + ${t.actor5?`fill: ${t.actor5}`:""}; + } + ${kt()} +`,"getStyles"),jt=Bt,J=n(function(t,e){return xt(t,e)},"drawRect"),Nt=n(function(t,e){const c=t.append("circle").attr("cx",e.cx).attr("cy",e.cy).attr("class","face").attr("r",15).attr("stroke-width",2).attr("overflow","visible"),r=t.append("g");r.append("circle").attr("cx",e.cx-15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),r.append("circle").attr("cx",e.cx+15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666");function f(g){const m=et().startAngle(Math.PI/2).endAngle(3*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);g.append("path").attr("class","mouth").attr("d",m).attr("transform","translate("+e.cx+","+(e.cy+2)+")")}n(f,"smile");function u(g){const m=et().startAngle(3*Math.PI/2).endAngle(5*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);g.append("path").attr("class","mouth").attr("d",m).attr("transform","translate("+e.cx+","+(e.cy+7)+")")}n(u,"sad");function y(g){g.append("line").attr("class","mouth").attr("stroke",2).attr("x1",e.cx-5).attr("y1",e.cy+7).attr("x2",e.cx+5).attr("y2",e.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}return n(y,"ambivalent"),e.score>3?f(r):e.score<3?u(r):y(r),c},"drawFace"),ot=n(function(t,e){const s=t.append("circle");return s.attr("cx",e.cx),s.attr("cy",e.cy),s.attr("class","actor-"+e.pos),s.attr("fill",e.fill),s.attr("stroke",e.stroke),s.attr("r",e.r),s.class!==void 0&&s.attr("class",s.class),e.title!==void 0&&s.append("title").text(e.title),s},"drawCircle"),ct=n(function(t,e){return mt(t,e)},"drawText"),zt=n(function(t,e){function s(r,f,u,y,g){return r+","+f+" "+(r+u)+","+f+" "+(r+u)+","+(f+y-g)+" "+(r+u-g*1.2)+","+(f+y)+" "+r+","+(f+y)}n(s,"genPoints");const c=t.append("polygon");c.attr("points",s(e.x,e.y,50,20,7)),c.attr("class","labelBox"),e.y=e.y+e.labelMargin,e.x=e.x+.5*e.labelMargin,ct(t,e)},"drawLabel"),Wt=n(function(t,e,s){const c=t.append("g"),r=lt();r.x=e.x,r.y=e.y,r.fill=e.fill,r.width=s.width*e.taskCount+s.diagramMarginX*(e.taskCount-1),r.height=s.height,r.class="journey-section section-type-"+e.num,r.rx=3,r.ry=3,J(c,r),ht(s)(e.text,c,r.x,r.y,r.width,r.height,{class:"journey-section section-type-"+e.num},s,e.colour)},"drawSection"),nt=-1,Ot=n(function(t,e,s){const c=e.x+s.width/2,r=t.append("g");nt++;const f=300+5*30;r.append("line").attr("id","task"+nt).attr("x1",c).attr("y1",e.y).attr("x2",c).attr("y2",f).attr("class","task-line").attr("stroke-width","1px").attr("stroke-dasharray","4 2").attr("stroke","#666"),Nt(r,{cx:c,cy:300+(5-e.score)*30,score:e.score});const u=lt();u.x=e.x,u.y=e.y,u.fill=e.fill,u.width=s.width,u.height=s.height,u.class="task task-type-"+e.num,u.rx=3,u.ry=3,J(r,u);let y=e.x+14;e.people.forEach(g=>{const m=e.actors[g].color,x={cx:y,cy:e.y,r:7,fill:m,stroke:"#000",title:g,pos:e.actors[g].position};ot(r,x),y+=10}),ht(s)(e.task,r,u.x,u.y,u.width,u.height,{class:"task"},s,e.colour)},"drawTask"),Yt=n(function(t,e){gt(t,e)},"drawBackgroundRect"),ht=function(){function t(r,f,u,y,g,m,x,h){const i=f.append("text").attr("x",u+g/2).attr("y",y+m/2+5).style("font-color",h).style("text-anchor","middle").text(r);c(i,x)}n(t,"byText");function e(r,f,u,y,g,m,x,h,i){const{taskFontSize:a,taskFontFamily:l}=h,d=r.split(//gi);for(let p=0;p{const f=E[r].color,u={cx:20,cy:c,r:7,fill:f,stroke:"#000",pos:E[r].position};j.drawCircle(t,u);let y=t.append("text").attr("visibility","hidden").text(r);const g=y.node().getBoundingClientRect().width;y.remove();let m=[];if(g<=s)m=[r];else{const x=r.split(" ");let h="";y=t.append("text").attr("visibility","hidden"),x.forEach(i=>{const a=h?`${h} ${i}`:i;if(y.text(a),y.node().getBoundingClientRect().width>s){if(h&&m.push(h),h=i,y.text(i),y.node().getBoundingClientRect().width>s){let d="";for(const p of i)d+=p,y.text(d+"-"),y.node().getBoundingClientRect().width>s&&(m.push(d.slice(0,-1)+"-"),d=p);h=d}}else h=a}),h&&m.push(h),y.remove()}m.forEach((x,h)=>{const i={x:40,y:c+7+h*20,fill:"#666",text:x,textMargin:e.boxTextMargin??5},l=j.drawText(t,i).node().getBoundingClientRect().width;l>W&&l>e.leftMargin-l&&(W=l)}),c+=Math.max(20,m.length*20)})}n(ut,"drawActorLegend");var $=R().journey,P=0,Gt=n(function(t,e,s,c){const r=R(),f=r.journey.titleColor,u=r.journey.titleFontSize,y=r.journey.titleFontFamily,g=r.securityLevel;let m;g==="sandbox"&&(m=G("#i"+e));const x=g==="sandbox"?G(m.nodes()[0].contentDocument.body):G("body");S.init();const h=x.select("#"+e);j.initGraphics(h);const i=c.db.getTasks(),a=c.db.getDiagramTitle(),l=c.db.getActors();for(const C in E)delete E[C];let d=0;l.forEach(C=>{E[C]={color:$.actorColours[d%$.actorColours.length],position:d},d++}),ut(h),P=$.leftMargin+W,S.insert(0,0,P,Object.keys(E).length*50),Ht(h,i,0);const p=S.getBounds();a&&h.append("text").text(a).attr("x",P).attr("font-size",u).attr("font-weight","bold").attr("y",25).attr("fill",f).attr("font-family",y);const o=p.stopy-p.starty+2*$.diagramMarginY,b=P+p.stopx+2*$.diagramMarginX;$t(h,o,b,$.useMaxWidth),h.append("line").attr("x1",P).attr("y1",$.height*4).attr("x2",b-P-4).attr("y2",$.height*4).attr("stroke-width",4).attr("stroke","black").attr("marker-end","url(#arrowhead)");const k=a?70:0;h.attr("viewBox",`${p.startx} -25 ${b} ${o+k}`),h.attr("preserveAspectRatio","xMinYMin meet"),h.attr("height",o+k+25)},"draw"),S={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],init:n(function(){this.sequenceItems=[],this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0},"init"),updateVal:n(function(t,e,s,c){t[e]===void 0?t[e]=s:t[e]=c(s,t[e])},"updateVal"),updateBounds:n(function(t,e,s,c){const r=R().journey,f=this;let u=0;function y(g){return n(function(x){u++;const h=f.sequenceItems.length-u+1;f.updateVal(x,"starty",e-h*r.boxMargin,Math.min),f.updateVal(x,"stopy",c+h*r.boxMargin,Math.max),f.updateVal(S.data,"startx",t-h*r.boxMargin,Math.min),f.updateVal(S.data,"stopx",s+h*r.boxMargin,Math.max),g!=="activation"&&(f.updateVal(x,"startx",t-h*r.boxMargin,Math.min),f.updateVal(x,"stopx",s+h*r.boxMargin,Math.max),f.updateVal(S.data,"starty",e-h*r.boxMargin,Math.min),f.updateVal(S.data,"stopy",c+h*r.boxMargin,Math.max))},"updateItemBounds")}n(y,"updateFn"),this.sequenceItems.forEach(y())},"updateBounds"),insert:n(function(t,e,s,c){const r=Math.min(t,s),f=Math.max(t,s),u=Math.min(e,c),y=Math.max(e,c);this.updateVal(S.data,"startx",r,Math.min),this.updateVal(S.data,"starty",u,Math.min),this.updateVal(S.data,"stopx",f,Math.max),this.updateVal(S.data,"stopy",y,Math.max),this.updateBounds(r,u,f,y)},"insert"),bumpVerticalPos:n(function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},"bumpVerticalPos"),getVerticalPos:n(function(){return this.verticalPos},"getVerticalPos"),getBounds:n(function(){return this.data},"getBounds")},H=$.sectionFills,st=$.sectionColours,Ht=n(function(t,e,s){const c=R().journey;let r="";const f=c.height*2+c.diagramMarginY,u=s+f;let y=0,g="#CCC",m="black",x=0;for(const[h,i]of e.entries()){if(r!==i.section){g=H[y%H.length],x=y%H.length,m=st[y%st.length];let l=0;const d=i.section;for(let o=h;o(E[d]&&(l[d]=E[d]),l),{});i.x=h*c.taskMargin+h*c.width+P,i.y=u,i.width=c.diagramMarginX,i.height=c.diagramMarginY,i.colour=m,i.fill=g,i.num=x,i.actors=a,j.drawTask(t,i,c),S.insert(i.x,i.y,i.x+i.width+c.taskMargin,300+5*30)}},"drawTasks"),at={setConf:Xt,draw:Gt},Qt={parser:Et,db:rt,renderer:at,styles:jt,init:n(t=>{at.setConf(t.journey),rt.clear()},"init")};export{Qt as diagram}; diff --git a/assets/chunks/kanban-definition-3W4ZIXB7.DFxkbzml.js b/assets/chunks/kanban-definition-3W4ZIXB7.DFxkbzml.js new file mode 100644 index 000000000..daa463208 --- /dev/null +++ b/assets/chunks/kanban-definition-3W4ZIXB7.DFxkbzml.js @@ -0,0 +1,89 @@ +import{_ as c,l as te,c as W,K as fe,a8 as ye,a9 as be,aa as me,a3 as _e,H as Y,i as G,v as Ee,J as ke,a4 as Se,a5 as le,a6 as ce}from"./theme.kqgpP4eL.js";import{g as Ne}from"./chunk-FMBD7UC4.B39tdjdc.js";import"./framework.CgT1UzWm.js";var $=function(){var t=c(function(_,s,n,a){for(n=n||{},a=_.length;a--;n[_[a]]=s);return n},"o"),g=[1,4],d=[1,13],r=[1,12],p=[1,15],E=[1,16],f=[1,20],h=[1,19],L=[6,7,8],C=[1,26],w=[1,24],N=[1,25],i=[6,7,11],H=[1,31],x=[6,7,11,24],P=[1,6,13,16,17,20,23],M=[1,35],U=[1,36],A=[1,6,7,11,13,16,17,20,23],j=[1,38],V={trace:c(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,KANBAN:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,shapeData:15,ICON:16,CLASS:17,nodeWithId:18,nodeWithoutId:19,NODE_DSTART:20,NODE_DESCR:21,NODE_DEND:22,NODE_ID:23,SHAPE_DATA:24,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"KANBAN",11:"EOF",13:"SPACELIST",16:"ICON",17:"CLASS",20:"NODE_DSTART",21:"NODE_DESCR",22:"NODE_DEND",23:"NODE_ID",24:"SHAPE_DATA"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,3],[12,2],[12,2],[12,2],[12,1],[12,2],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[19,3],[18,1],[18,4],[15,2],[15,1]],performAction:c(function(s,n,a,o,u,e,B){var l=e.length-1;switch(u){case 6:case 7:return o;case 8:o.getLogger().trace("Stop NL ");break;case 9:o.getLogger().trace("Stop EOF ");break;case 11:o.getLogger().trace("Stop NL2 ");break;case 12:o.getLogger().trace("Stop EOF2 ");break;case 15:o.getLogger().info("Node: ",e[l-1].id),o.addNode(e[l-2].length,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 16:o.getLogger().info("Node: ",e[l].id),o.addNode(e[l-1].length,e[l].id,e[l].descr,e[l].type);break;case 17:o.getLogger().trace("Icon: ",e[l]),o.decorateNode({icon:e[l]});break;case 18:case 23:o.decorateNode({class:e[l]});break;case 19:o.getLogger().trace("SPACELIST");break;case 20:o.getLogger().trace("Node: ",e[l-1].id),o.addNode(0,e[l-1].id,e[l-1].descr,e[l-1].type,e[l]);break;case 21:o.getLogger().trace("Node: ",e[l].id),o.addNode(0,e[l].id,e[l].descr,e[l].type);break;case 22:o.decorateNode({icon:e[l]});break;case 27:o.getLogger().trace("node found ..",e[l-2]),this.$={id:e[l-1],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 28:this.$={id:e[l],descr:e[l],type:0};break;case 29:o.getLogger().trace("node found ..",e[l-3]),this.$={id:e[l-3],descr:e[l-1],type:o.getType(e[l-2],e[l])};break;case 30:this.$=e[l-1]+e[l];break;case 31:this.$=e[l];break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:g},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:g},{6:d,7:[1,10],9:9,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(L,[2,3]),{1:[2,2]},t(L,[2,4]),t(L,[2,5]),{1:[2,6],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:d,9:22,12:11,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},{6:C,7:w,10:23,11:N},t(i,[2,24],{18:17,19:18,14:27,16:[1,28],17:[1,29],20:f,23:h}),t(i,[2,19]),t(i,[2,21],{15:30,24:H}),t(i,[2,22]),t(i,[2,23]),t(x,[2,25]),t(x,[2,26]),t(x,[2,28],{20:[1,32]}),{21:[1,33]},{6:C,7:w,10:34,11:N},{1:[2,7],6:d,12:21,13:r,14:14,16:p,17:E,18:17,19:18,20:f,23:h},t(P,[2,14],{7:M,11:U}),t(A,[2,8]),t(A,[2,9]),t(A,[2,10]),t(i,[2,16],{15:37,24:H}),t(i,[2,17]),t(i,[2,18]),t(i,[2,20],{24:j}),t(x,[2,31]),{21:[1,39]},{22:[1,40]},t(P,[2,13],{7:M,11:U}),t(A,[2,11]),t(A,[2,12]),t(i,[2,15],{24:j}),t(x,[2,30]),{22:[1,41]},t(x,[2,27]),t(x,[2,29])],defaultActions:{2:[2,1],6:[2,2]},parseError:c(function(s,n){if(n.recoverable)this.trace(s);else{var a=new Error(s);throw a.hash=n,a}},"parseError"),parse:c(function(s){var n=this,a=[0],o=[],u=[null],e=[],B=this.table,l="",z=0,ie=0,ue=2,re=1,ge=e.slice.call(arguments,1),b=Object.create(this.lexer),T={yy:{}};for(var J in this.yy)Object.prototype.hasOwnProperty.call(this.yy,J)&&(T.yy[J]=this.yy[J]);b.setInput(s,T.yy),T.yy.lexer=b,T.yy.parser=this,typeof b.yylloc>"u"&&(b.yylloc={});var q=b.yylloc;e.push(q);var de=b.options&&b.options.ranges;typeof T.yy.parseError=="function"?this.parseError=T.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function pe(S){a.length=a.length-2*S,u.length=u.length-S,e.length=e.length-S}c(pe,"popStack");function ae(){var S;return S=o.pop()||b.lex()||re,typeof S!="number"&&(S instanceof Array&&(o=S,S=o.pop()),S=n.symbols_[S]||S),S}c(ae,"lex");for(var k,R,v,Q,F={},K,I,oe,X;;){if(R=a[a.length-1],this.defaultActions[R]?v=this.defaultActions[R]:((k===null||typeof k>"u")&&(k=ae()),v=B[R]&&B[R][k]),typeof v>"u"||!v.length||!v[0]){var Z="";X=[];for(K in B[R])this.terminals_[K]&&K>ue&&X.push("'"+this.terminals_[K]+"'");b.showPosition?Z="Parse error on line "+(z+1)+`: +`+b.showPosition()+` +Expecting `+X.join(", ")+", got '"+(this.terminals_[k]||k)+"'":Z="Parse error on line "+(z+1)+": Unexpected "+(k==re?"end of input":"'"+(this.terminals_[k]||k)+"'"),this.parseError(Z,{text:b.match,token:this.terminals_[k]||k,line:b.yylineno,loc:q,expected:X})}if(v[0]instanceof Array&&v.length>1)throw new Error("Parse Error: multiple actions possible at state: "+R+", token: "+k);switch(v[0]){case 1:a.push(k),u.push(b.yytext),e.push(b.yylloc),a.push(v[1]),k=null,ie=b.yyleng,l=b.yytext,z=b.yylineno,q=b.yylloc;break;case 2:if(I=this.productions_[v[1]][1],F.$=u[u.length-I],F._$={first_line:e[e.length-(I||1)].first_line,last_line:e[e.length-1].last_line,first_column:e[e.length-(I||1)].first_column,last_column:e[e.length-1].last_column},de&&(F._$.range=[e[e.length-(I||1)].range[0],e[e.length-1].range[1]]),Q=this.performAction.apply(F,[l,ie,z,T.yy,v[1],u,e].concat(ge)),typeof Q<"u")return Q;I&&(a=a.slice(0,-1*I*2),u=u.slice(0,-1*I),e=e.slice(0,-1*I)),a.push(this.productions_[v[1]][0]),u.push(F.$),e.push(F._$),oe=B[a[a.length-2]][a[a.length-1]],a.push(oe);break;case 3:return!0}}return!0},"parse")},m=function(){var _={EOF:1,parseError:c(function(n,a){if(this.yy.parser)this.yy.parser.parseError(n,a);else throw new Error(n)},"parseError"),setInput:c(function(s,n){return this.yy=n||this.yy||{},this._input=s,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:c(function(){var s=this._input[0];this.yytext+=s,this.yyleng++,this.offset++,this.match+=s,this.matched+=s;var n=s.match(/(?:\r\n?|\n).*/g);return n?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),s},"input"),unput:c(function(s){var n=s.length,a=s.split(/(?:\r\n?|\n)/g);this._input=s+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-n),this.offset-=n;var o=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),a.length-1&&(this.yylineno-=a.length-1);var u=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:a?(a.length===o.length?this.yylloc.first_column:0)+o[o.length-a.length].length-a[0].length:this.yylloc.first_column-n},this.options.ranges&&(this.yylloc.range=[u[0],u[0]+this.yyleng-n]),this.yyleng=this.yytext.length,this},"unput"),more:c(function(){return this._more=!0,this},"more"),reject:c(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:c(function(s){this.unput(this.match.slice(s))},"less"),pastInput:c(function(){var s=this.matched.substr(0,this.matched.length-this.match.length);return(s.length>20?"...":"")+s.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:c(function(){var s=this.match;return s.length<20&&(s+=this._input.substr(0,20-s.length)),(s.substr(0,20)+(s.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:c(function(){var s=this.pastInput(),n=new Array(s.length+1).join("-");return s+this.upcomingInput()+` +`+n+"^"},"showPosition"),test_match:c(function(s,n){var a,o,u;if(this.options.backtrack_lexer&&(u={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(u.yylloc.range=this.yylloc.range.slice(0))),o=s[0].match(/(?:\r\n?|\n).*/g),o&&(this.yylineno+=o.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:o?o[o.length-1].length-o[o.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+s[0].length},this.yytext+=s[0],this.match+=s[0],this.matches=s,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(s[0].length),this.matched+=s[0],a=this.performAction.call(this,this.yy,this,n,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a)return a;if(this._backtrack){for(var e in u)this[e]=u[e];return!1}return!1},"test_match"),next:c(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var s,n,a,o;this._more||(this.yytext="",this.match="");for(var u=this._currentRules(),e=0;en[0].length)){if(n=a,o=e,this.options.backtrack_lexer){if(s=this.test_match(a,u[e]),s!==!1)return s;if(this._backtrack){n=!1;continue}else return!1}else if(!this.options.flex)break}return n?(s=this.test_match(n,u[o]),s!==!1?s:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:c(function(){var n=this.next();return n||this.lex()},"lex"),begin:c(function(n){this.conditionStack.push(n)},"begin"),popState:c(function(){var n=this.conditionStack.length-1;return n>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:c(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:c(function(n){return n=this.conditionStack.length-1-Math.abs(n||0),n>=0?this.conditionStack[n]:"INITIAL"},"topState"),pushState:c(function(n){this.begin(n)},"pushState"),stateStackSize:c(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:c(function(n,a,o,u){switch(o){case 0:return this.pushState("shapeData"),a.yytext="",24;case 1:return this.pushState("shapeDataStr"),24;case 2:return this.popState(),24;case 3:const e=/\n\s*/g;return a.yytext=a.yytext.replace(e,"
"),24;case 4:return 24;case 5:this.popState();break;case 6:return n.getLogger().trace("Found comment",a.yytext),6;case 7:return 8;case 8:this.begin("CLASS");break;case 9:return this.popState(),17;case 10:this.popState();break;case 11:n.getLogger().trace("Begin icon"),this.begin("ICON");break;case 12:return n.getLogger().trace("SPACELINE"),6;case 13:return 7;case 14:return 16;case 15:n.getLogger().trace("end icon"),this.popState();break;case 16:return n.getLogger().trace("Exploding node"),this.begin("NODE"),20;case 17:return n.getLogger().trace("Cloud"),this.begin("NODE"),20;case 18:return n.getLogger().trace("Explosion Bang"),this.begin("NODE"),20;case 19:return n.getLogger().trace("Cloud Bang"),this.begin("NODE"),20;case 20:return this.begin("NODE"),20;case 21:return this.begin("NODE"),20;case 22:return this.begin("NODE"),20;case 23:return this.begin("NODE"),20;case 24:return 13;case 25:return 23;case 26:return 11;case 27:this.begin("NSTR2");break;case 28:return"NODE_DESCR";case 29:this.popState();break;case 30:n.getLogger().trace("Starting NSTR"),this.begin("NSTR");break;case 31:return n.getLogger().trace("description:",a.yytext),"NODE_DESCR";case 32:this.popState();break;case 33:return this.popState(),n.getLogger().trace("node end ))"),"NODE_DEND";case 34:return this.popState(),n.getLogger().trace("node end )"),"NODE_DEND";case 35:return this.popState(),n.getLogger().trace("node end ...",a.yytext),"NODE_DEND";case 36:return this.popState(),n.getLogger().trace("node end (("),"NODE_DEND";case 37:return this.popState(),n.getLogger().trace("node end (-"),"NODE_DEND";case 38:return this.popState(),n.getLogger().trace("node end (-"),"NODE_DEND";case 39:return this.popState(),n.getLogger().trace("node end (("),"NODE_DEND";case 40:return this.popState(),n.getLogger().trace("node end (("),"NODE_DEND";case 41:return n.getLogger().trace("Long description:",a.yytext),21;case 42:return n.getLogger().trace("Long description:",a.yytext),21}},"anonymous"),rules:[/^(?:@\{)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^\"]+)/i,/^(?:[^}^"]+)/i,/^(?:\})/i,/^(?:\s*%%.*)/i,/^(?:kanban\b)/i,/^(?::::)/i,/^(?:.+)/i,/^(?:\n)/i,/^(?:::icon\()/i,/^(?:[\s]+[\n])/i,/^(?:[\n]+)/i,/^(?:[^\)]+)/i,/^(?:\))/i,/^(?:-\))/i,/^(?:\(-)/i,/^(?:\)\))/i,/^(?:\))/i,/^(?:\(\()/i,/^(?:\{\{)/i,/^(?:\()/i,/^(?:\[)/i,/^(?:[\s]+)/i,/^(?:[^\(\[\n\)\{\}@]+)/i,/^(?:$)/i,/^(?:["][`])/i,/^(?:[^`"]+)/i,/^(?:[`]["])/i,/^(?:["])/i,/^(?:[^"]+)/i,/^(?:["])/i,/^(?:[\)]\))/i,/^(?:[\)])/i,/^(?:[\]])/i,/^(?:\}\})/i,/^(?:\(-)/i,/^(?:-\))/i,/^(?:\(\()/i,/^(?:\()/i,/^(?:[^\)\]\(\}]+)/i,/^(?:.+(?!\(\())/i],conditions:{shapeDataEndBracket:{rules:[],inclusive:!1},shapeDataStr:{rules:[2,3],inclusive:!1},shapeData:{rules:[1,4,5],inclusive:!1},CLASS:{rules:[9,10],inclusive:!1},ICON:{rules:[14,15],inclusive:!1},NSTR2:{rules:[28,29],inclusive:!1},NSTR:{rules:[31,32],inclusive:!1},NODE:{rules:[27,30,33,34,35,36,37,38,39,40,41,42],inclusive:!1},INITIAL:{rules:[0,6,7,8,11,12,13,16,17,18,19,20,21,22,23,24,25,26],inclusive:!0}}};return _}();V.lexer=m;function O(){this.yy={}}return c(O,"Parser"),O.prototype=V,V.Parser=O,new O}();$.parser=$;var xe=$,D=[],ne=[],ee=0,se={},ve=c(()=>{D=[],ne=[],ee=0,se={}},"clear"),De=c(t=>{if(D.length===0)return null;const g=D[0].level;let d=null;for(let r=D.length-1;r>=0;r--)if(D[r].level===g&&!d&&(d=D[r]),D[r].levelh.parentId===p.id);for(const h of f){const L={id:h.id,parentId:p.id,label:G(h.label??"",r),isGroup:!1,ticket:h==null?void 0:h.ticket,priority:h==null?void 0:h.priority,assigned:h==null?void 0:h.assigned,icon:h==null?void 0:h.icon,shape:"kanbanItem",level:h.level,rx:5,ry:5,cssStyles:["text-align: left"]};g.push(L)}}return{nodes:g,edges:t,other:{},config:W()}},"getData"),Oe=c((t,g,d,r,p)=>{var C,w;const E=W();let f=((C=E.mindmap)==null?void 0:C.padding)??Y.mindmap.padding;switch(r){case y.ROUNDED_RECT:case y.RECT:case y.HEXAGON:f*=2}const h={id:G(g,E)||"kbn"+ee++,level:t,label:G(d,E),width:((w=E.mindmap)==null?void 0:w.maxNodeWidth)??Y.mindmap.maxNodeWidth,padding:f,isGroup:!1};if(p!==void 0){let N;p.includes(` +`)?N=p+` +`:N=`{ +`+p+` +}`;const i=Ee(N,{schema:ke});if(i.shape&&(i.shape!==i.shape.toLowerCase()||i.shape.includes("_")))throw new Error(`No such shape: ${i.shape}. Shape names should be lowercase.`);i!=null&&i.shape&&i.shape==="kanbanItem"&&(h.shape=i==null?void 0:i.shape),i!=null&&i.label&&(h.label=i==null?void 0:i.label),i!=null&&i.icon&&(h.icon=i==null?void 0:i.icon.toString()),i!=null&&i.assigned&&(h.assigned=i==null?void 0:i.assigned.toString()),i!=null&&i.ticket&&(h.ticket=i==null?void 0:i.ticket.toString()),i!=null&&i.priority&&(h.priority=i==null?void 0:i.priority)}const L=De(t);L?h.parentId=L.id||"kbn"+ee++:ne.push(h),D.push(h)},"addNode"),y={DEFAULT:0,NO_BORDER:0,ROUNDED_RECT:1,RECT:2,CIRCLE:3,CLOUD:4,BANG:5,HEXAGON:6},Ie=c((t,g)=>{switch(te.debug("In get type",t,g),t){case"[":return y.RECT;case"(":return g===")"?y.ROUNDED_RECT:y.CLOUD;case"((":return y.CIRCLE;case")":return y.CLOUD;case"))":return y.BANG;case"{{":return y.HEXAGON;default:return y.DEFAULT}},"getType"),Ce=c((t,g)=>{se[t]=g},"setElementForId"),we=c(t=>{if(!t)return;const g=W(),d=D[D.length-1];t.icon&&(d.icon=G(t.icon,g)),t.class&&(d.cssClasses=G(t.class,g))},"decorateNode"),Ae=c(t=>{switch(t){case y.DEFAULT:return"no-border";case y.RECT:return"rect";case y.ROUNDED_RECT:return"rounded-rect";case y.CIRCLE:return"circle";case y.CLOUD:return"cloud";case y.BANG:return"bang";case y.HEXAGON:return"hexgon";default:return"no-border"}},"type2Str"),Te=c(()=>te,"getLogger"),Re=c(t=>se[t],"getElementById"),Pe={clear:ve,addNode:Oe,getSections:he,getData:Le,nodeType:y,getType:Ie,setElementForId:Ce,decorateNode:we,type2Str:Ae,getLogger:Te,getElementById:Re},Ve=Pe,Be=c(async(t,g,d,r)=>{var M,U,A,j,V;te.debug(`Rendering kanban diagram +`+t);const E=r.db.getData(),f=W();f.htmlLabels=!1;const h=fe(g),L=h.append("g");L.attr("class","sections");const C=h.append("g");C.attr("class","items");const w=E.nodes.filter(m=>m.isGroup);let N=0;const i=10,H=[];let x=25;for(const m of w){const O=((M=f==null?void 0:f.kanban)==null?void 0:M.sectionWidth)||200;N=N+1,m.x=O*N+(N-1)*i/2,m.width=O,m.y=0,m.height=O*3,m.rx=5,m.ry=5,m.cssClasses=m.cssClasses+" section-"+N;const _=await ye(L,m);x=Math.max(x,(U=_==null?void 0:_.labelBBox)==null?void 0:U.height),H.push(_)}let P=0;for(const m of w){const O=H[P];P=P+1;const _=((A=f==null?void 0:f.kanban)==null?void 0:A.sectionWidth)||200,s=-_*3/2+x;let n=s;const a=E.nodes.filter(e=>e.parentId===m.id);for(const e of a){if(e.isGroup)throw new Error("Groups within groups are not allowed in Kanban diagrams");e.x=m.x,e.width=_-1.5*i;const l=(await be(C,e,{config:f})).node().getBBox();e.y=n+l.height/2,await me(e),n=e.y+l.height/2+i/2}const o=O.cluster.select("rect"),u=Math.max(n-s+3*i,50)+(x-25);o.attr("height",u)}_e(void 0,h,((j=f.mindmap)==null?void 0:j.padding)??Y.kanban.padding,((V=f.mindmap)==null?void 0:V.useMaxWidth)??Y.kanban.useMaxWidth)},"draw"),Fe={draw:Be},Ge=c(t=>{let g="";for(let r=0;rt.darkMode?ce(r,p):le(r,p),"adjuster");for(let r=0;r` + .edge { + stroke-width: 3; + } + ${Ge(t)} + .section-root rect, .section-root path, .section-root circle, .section-root polygon { + fill: ${t.git0}; + } + .section-root text { + fill: ${t.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .cluster-label, .label { + color: ${t.textColor}; + fill: ${t.textColor}; + } + .kanban-label { + dy: 1em; + alignment-baseline: middle; + text-anchor: middle; + dominant-baseline: middle; + text-align: center; + } + ${Ne()} +`,"getStyles"),Me=He,ze={db:Ve,renderer:Fe,parser:xe,styles:Me};export{ze as diagram}; diff --git a/assets/chunks/katex.CBSAILhF.js b/assets/chunks/katex.CBSAILhF.js new file mode 100644 index 000000000..91e6b6d0a --- /dev/null +++ b/assets/chunks/katex.CBSAILhF.js @@ -0,0 +1,261 @@ +class u0{constructor(e,t,a){this.lexer=void 0,this.start=void 0,this.end=void 0,this.lexer=e,this.start=t,this.end=a}static range(e,t){return t?!e||!e.loc||!t.loc||e.loc.lexer!==t.loc.lexer?null:new u0(e.loc.lexer,e.loc.start,t.loc.end):e&&e.loc}}class m0{constructor(e,t){this.text=void 0,this.loc=void 0,this.noexpand=void 0,this.treatAsRelax=void 0,this.text=e,this.loc=t}range(e,t){return new m0(t,u0.range(this,e))}}class M{constructor(e,t){this.name=void 0,this.position=void 0,this.length=void 0,this.rawMessage=void 0;var a="KaTeX parse error: "+e,n,s,u=t&&t.loc;if(u&&u.start<=u.end){var h=u.lexer.input;n=u.start,s=u.end,n===h.length?a+=" at end of input: ":a+=" at position "+(n+1)+": ";var c=h.slice(n,s).replace(/[^]/g,"$&̲"),p;n>15?p="…"+h.slice(n-15,n):p=h.slice(0,n);var g;s+15":">","<":"<",'"':""","'":"'"},ba=/[&><"']/g;function ya(r){return String(r).replace(ba,e=>ga[e])}var vr=function r(e){return e.type==="ordgroup"||e.type==="color"?e.body.length===1?r(e.body[0]):e:e.type==="font"?r(e.body):e},xa=function(e){var t=vr(e);return t.type==="mathord"||t.type==="textord"||t.type==="atom"},wa=function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e},ka=function(e){var t=/^[\x00-\x20]*([^\\/#?]*?)(:|�*58|�*3a|&colon)/i.exec(e);return t?t[2]!==":"||!/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(t[1])?null:t[1].toLowerCase():"_relative"},V={deflt:fa,escape:ya,hyphenate:va,getBaseElem:vr,isCharacterBox:xa,protocolFromUrl:ka},ze={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.",cli:"-d, --display-mode"},output:{type:{enum:["htmlAndMathml","html","mathml"]},description:"Determines the markup language of the output.",cli:"-F, --format "},leqno:{type:"boolean",description:"Render display math in leqno style (left-justified tags)."},fleqn:{type:"boolean",description:"Render display math flush left."},throwOnError:{type:"boolean",default:!0,cli:"-t, --no-throw-on-error",cliDescription:"Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error."},errorColor:{type:"string",default:"#cc0000",cli:"-c, --error-color ",cliDescription:"A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.",cliProcessor:r=>"#"+r},macros:{type:"object",cli:"-m, --macro ",cliDescription:"Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).",cliDefault:[],cliProcessor:(r,e)=>(e.push(r),e)},minRuleThickness:{type:"number",description:"Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.",processor:r=>Math.max(0,r),cli:"--min-rule-thickness ",cliProcessor:parseFloat},colorIsTextColor:{type:"boolean",description:"Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.",cli:"-b, --color-is-text-color"},strict:{type:[{enum:["warn","ignore","error"]},"boolean","function"],description:"Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.",cli:"-S, --strict",cliDefault:!1},trust:{type:["boolean","function"],description:"Trust the input, enabling all HTML features such as \\url.",cli:"-T, --trust"},maxSize:{type:"number",default:1/0,description:"If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large",processor:r=>Math.max(0,r),cli:"-s, --max-size ",cliProcessor:parseInt},maxExpand:{type:"number",default:1e3,description:"Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.",processor:r=>Math.max(0,r),cli:"-e, --max-expand ",cliProcessor:r=>r==="Infinity"?1/0:parseInt(r)},globalGroup:{type:"boolean",cli:!1}};function Sa(r){if(r.default)return r.default;var e=r.type,t=Array.isArray(e)?e[0]:e;if(typeof t!="string")return t.enum[0];switch(t){case"boolean":return!1;case"string":return"";case"number":return 0;case"object":return{}}}class dt{constructor(e){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.minRuleThickness=void 0,this.colorIsTextColor=void 0,this.strict=void 0,this.trust=void 0,this.maxSize=void 0,this.maxExpand=void 0,this.globalGroup=void 0,e=e||{};for(var t in ze)if(ze.hasOwnProperty(t)){var a=ze[t];this[t]=e[t]!==void 0?a.processor?a.processor(e[t]):e[t]:Sa(a)}}reportNonstrict(e,t,a){var n=this.strict;if(typeof n=="function"&&(n=n(e,t,a)),!(!n||n==="ignore")){if(n===!0||n==="error")throw new M("LaTeX-incompatible input and strict mode is set to 'error': "+(t+" ["+e+"]"),a);n==="warn"?typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")):typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]"))}}useStrictBehavior(e,t,a){var n=this.strict;if(typeof n=="function")try{n=n(e,t,a)}catch{n="error"}return!n||n==="ignore"?!1:n===!0||n==="error"?!0:n==="warn"?(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(t+" ["+e+"]")),!1):(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+n+"': "+t+" ["+e+"]")),!1)}isTrusted(e){if(e.url&&!e.protocol){var t=V.protocolFromUrl(e.url);if(t==null)return!1;e.protocol=t}var a=typeof this.trust=="function"?this.trust(e):this.trust;return!!a}}class O0{constructor(e,t,a){this.id=void 0,this.size=void 0,this.cramped=void 0,this.id=e,this.size=t,this.cramped=a}sup(){return y0[Ma[this.id]]}sub(){return y0[za[this.id]]}fracNum(){return y0[Aa[this.id]]}fracDen(){return y0[Ta[this.id]]}cramp(){return y0[Ba[this.id]]}text(){return y0[Da[this.id]]}isTight(){return this.size>=2}}var ft=0,Te=1,ee=2,B0=3,le=4,f0=5,te=6,n0=7,y0=[new O0(ft,0,!1),new O0(Te,0,!0),new O0(ee,1,!1),new O0(B0,1,!0),new O0(le,2,!1),new O0(f0,2,!0),new O0(te,3,!1),new O0(n0,3,!0)],Ma=[le,f0,le,f0,te,n0,te,n0],za=[f0,f0,f0,f0,n0,n0,n0,n0],Aa=[ee,B0,le,f0,te,n0,te,n0],Ta=[B0,B0,f0,f0,n0,n0,n0,n0],Ba=[Te,Te,B0,B0,f0,f0,n0,n0],Da=[ft,Te,ee,B0,ee,B0,ee,B0],R={DISPLAY:y0[ft],TEXT:y0[ee],SCRIPT:y0[le],SCRIPTSCRIPT:y0[te]},nt=[{name:"latin",blocks:[[256,591],[768,879]]},{name:"cyrillic",blocks:[[1024,1279]]},{name:"armenian",blocks:[[1328,1423]]},{name:"brahmic",blocks:[[2304,4255]]},{name:"georgian",blocks:[[4256,4351]]},{name:"cjk",blocks:[[12288,12543],[19968,40879],[65280,65376]]},{name:"hangul",blocks:[[44032,55215]]}];function Ca(r){for(var e=0;e=n[0]&&r<=n[1])return t.name}return null}var Ae=[];nt.forEach(r=>r.blocks.forEach(e=>Ae.push(...e)));function gr(r){for(var e=0;e=Ae[e]&&r<=Ae[e+1])return!0;return!1}var _0=80,Na=function(e,t){return"M95,"+(622+e+t)+` +c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14 +c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54 +c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10 +s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429 +c69,-144,104.5,-217.7,106.5,-221 +l`+e/2.075+" -"+e+` +c5.3,-9.3,12,-14,20,-14 +H400000v`+(40+e)+`H845.2724 +s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 +c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z +M`+(834+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},qa=function(e,t){return"M263,"+(601+e+t)+`c0.7,0,18,39.7,52,119 +c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120 +c340,-704.7,510.7,-1060.3,512,-1067 +l`+e/2.084+" -"+e+` +c4.7,-7.3,11,-11,19,-11 +H40000v`+(40+e)+`H1012.3 +s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232 +c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1 +s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26 +c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z +M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},Ra=function(e,t){return"M983 "+(10+e+t)+` +l`+e/3.13+" -"+e+` +c4,-6.7,10,-10,18,-10 H400000v`+(40+e)+` +H1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7 +s-12,0,-12,0c-1.3,-3.3,-3.7,-11.7,-7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744 +c-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30 +c26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722 +c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5 +c53.7,-170.3,84.5,-266.8,92.5,-289.5z +M`+(1001+e)+" "+t+"h400000v"+(40+e)+"h-400000z"},Ea=function(e,t){return"M424,"+(2398+e+t)+` +c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514 +c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20 +s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121 +s209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081 +l`+e/4.223+" -"+e+`c4,-6.7,10,-10,18,-10 H400000 +v`+(40+e)+`H1014.6 +s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185 +c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2z M`+(1001+e)+" "+t+` +h400000v`+(40+e)+"h-400000z"},Ia=function(e,t){return"M473,"+(2713+e+t)+` +c339.3,-1799.3,509.3,-2700,510,-2702 l`+e/5.298+" -"+e+` +c3.3,-7.3,9.3,-11,18,-11 H400000v`+(40+e)+`H1017.7 +s-90.5,478,-276.2,1466c-185.7,988,-279.5,1483,-281.5,1485c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32,-16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200 +c0,-1.3,-5.3,8.7,-16,30c-10.7,21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26 +s76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104, +606zM`+(1001+e)+" "+t+"h400000v"+(40+e)+"H1017.7z"},Fa=function(e){var t=e/2;return"M400000 "+e+" H0 L"+t+" 0 l65 45 L145 "+(e-80)+" H400000z"},Oa=function(e,t,a){var n=a-54-t-e;return"M702 "+(e+t)+"H400000"+(40+e)+` +H742v`+n+`l-4 4-4 4c-.667.7 -2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1 +h-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170 +c-4-3.333-8.333-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667 +219 661 l218 661zM702 `+t+"H400000v"+(40+e)+"H742z"},Ha=function(e,t,a){t=1e3*t;var n="";switch(e){case"sqrtMain":n=Na(t,_0);break;case"sqrtSize1":n=qa(t,_0);break;case"sqrtSize2":n=Ra(t,_0);break;case"sqrtSize3":n=Ea(t,_0);break;case"sqrtSize4":n=Ia(t,_0);break;case"sqrtTall":n=Oa(t,_0,a)}return n},La=function(e,t){switch(e){case"⎜":return"M291 0 H417 V"+t+" H291z M291 0 H417 V"+t+" H291z";case"∣":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z";case"∥":return"M145 0 H188 V"+t+" H145z M145 0 H188 V"+t+" H145z"+("M367 0 H410 V"+t+" H367z M367 0 H410 V"+t+" H367z");case"⎟":return"M457 0 H583 V"+t+" H457z M457 0 H583 V"+t+" H457z";case"⎢":return"M319 0 H403 V"+t+" H319z M319 0 H403 V"+t+" H319z";case"⎥":return"M263 0 H347 V"+t+" H263z M263 0 H347 V"+t+" H263z";case"⎪":return"M384 0 H504 V"+t+" H384z M384 0 H504 V"+t+" H384z";case"⏐":return"M312 0 H355 V"+t+" H312z M312 0 H355 V"+t+" H312z";case"‖":return"M257 0 H300 V"+t+" H257z M257 0 H300 V"+t+" H257z"+("M478 0 H521 V"+t+" H478z M478 0 H521 V"+t+" H478z");default:return""}},Ft={doubleleftarrow:`M262 157 +l10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3 + 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28 + 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5 +c2 1.7 6.3 3.5 13 5.5 68 17.3 128.2 47.8 180.5 91.5 52.3 43.7 93.8 96.2 124.5 + 157.5 9.3 8 15.3 12.3 18 13h6c12-.7 18-4 18-10 0-2-1.7-7-5-15-23.3-46-52-87 +-86-123l-10-10h399738v-40H218c328 0 0 0 0 0l-10-8c-26.7-20-65.7-43-117-69 2.7 +-2 6-3.7 10-5 36.7-16 72.3-37.3 107-64l10-8h399782v-40z +m8 0v40h399730v-40zm0 194v40h399730v-40z`,doublerightarrow:`M399738 392l +-10 10c-34 36-62.7 77-86 123-3.3 8-5 13.3-5 16 0 5.3 6.7 8 20 8 7.3 0 12.2-.5 + 14.5-1.5 2.3-1 4.8-4.5 7.5-10.5 49.3-97.3 121.7-169.3 217-216 28-14 57.3-25 88 +-33 6.7-2 11-3.8 13-5.5 2-1.7 3-4.2 3-7.5s-1-5.8-3-7.5c-2-1.7-6.3-3.5-13-5.5-68 +-17.3-128.2-47.8-180.5-91.5-52.3-43.7-93.8-96.2-124.5-157.5-9.3-8-15.3-12.3-18 +-13h-6c-12 .7-18 4-18 10 0 2 1.7 7 5 15 23.3 46 52 87 86 123l10 10H0v40h399782 +c-328 0 0 0 0 0l10 8c26.7 20 65.7 43 117 69-2.7 2-6 3.7-10 5-36.7 16-72.3 37.3 +-107 64l-10 8H0v40zM0 157v40h399730v-40zm0 194v40h399730v-40z`,leftarrow:`M400000 241H110l3-3c68.7-52.7 113.7-120 + 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8 +-5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247 +c-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208 + 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3 + 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202 + l-3-3h399890zM100 241v40h399900v-40z`,leftbrace:`M6 548l-6-6v-35l6-11c56-104 135.3-181.3 238-232 57.3-28.7 117 +-45 179-50h399577v120H403c-43.3 7-81 15-113 26-100.7 33-179.7 91-237 174-2.7 + 5-6 9-10 13-.7 1-7.3 1-20 1H6z`,leftbraceunder:`M0 6l6-6h17c12.688 0 19.313.3 20 1 4 4 7.313 8.3 10 13 + 35.313 51.3 80.813 93.8 136.5 127.5 55.688 33.7 117.188 55.8 184.5 66.5.688 + 0 2 .3 4 1 18.688 2.7 76 4.3 172 5h399450v120H429l-6-1c-124.688-8-235-61.7 +-331-161C60.687 138.7 32.312 99.3 7 54L0 41V6z`,leftgroup:`M400000 80 +H435C64 80 168.3 229.4 21 260c-5.9 1.2-18 0-18 0-2 0-3-1-3-3v-38C76 61 257 0 + 435 0h399565z`,leftgroupunder:`M400000 262 +H435C64 262 168.3 112.6 21 82c-5.9-1.2-18 0-18 0-2 0-3 1-3 3v38c76 158 257 219 + 435 219h399565z`,leftharpoon:`M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3 +-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5 +-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7 +-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40z`,leftharpoonplus:`M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5 + 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3 +-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7 +-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40zM0 435v40h400000v-40z +m0 0v40h400000v-40z`,leftharpoondown:`M7 241c-4 4-6.333 8.667-7 14 0 5.333.667 9 2 11s5.333 + 5.333 12 10c90.667 54 156 130 196 228 3.333 10.667 6.333 16.333 9 17 2 .667 5 + 1 9 1h5c10.667 0 16.667-2 18-6 2-2.667 1-9.667-3-21-32-87.333-82.667-157.667 +-152-211l-3-3h399907v-40zM93 281 H400000 v-40L7 241z`,leftharpoondownplus:`M7 435c-4 4-6.3 8.7-7 14 0 5.3.7 9 2 11s5.3 5.3 12 + 10c90.7 54 156 130 196 228 3.3 10.7 6.3 16.3 9 17 2 .7 5 1 9 1h5c10.7 0 16.7 +-2 18-6 2-2.7 1-9.7-3-21-32-87.3-82.7-157.7-152-211l-3-3h399907v-40H7zm93 0 +v40h399900v-40zM0 241v40h399900v-40zm0 0v40h399900v-40z`,lefthook:`M400000 281 H103s-33-11.2-61-33.5S0 197.3 0 164s14.2-61.2 42.5 +-83.5C70.8 58.2 104 47 142 47 c16.7 0 25 6.7 25 20 0 12-8.7 18.7-26 20-40 3.3 +-68.7 15.7-86 37-10 12-15 25.3-15 40 0 22.7 9.8 40.7 29.5 54 19.7 13.3 43.5 21 + 71.5 23h399859zM103 281v-40h399897v40z`,leftlinesegment:`M40 281 V428 H0 V94 H40 V241 H400000 v40z +M40 281 V428 H0 V94 H40 V241 H400000 v40z`,leftmapsto:`M40 281 V448H0V74H40V241H400000v40z +M40 281 V448H0V74H40V241H400000v40z`,leftToFrom:`M0 147h400000v40H0zm0 214c68 40 115.7 95.7 143 167h22c15.3 0 23 +-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h399905v-40H95l7-8 +c28.7-32 52-65.7 70-101 10.7-23.3 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 265.3 + 68 321 0 361zm0-174v-40h399900v40zm100 154v40h399900v-40z`,longequal:`M0 50 h400000 v40H0z m0 194h40000v40H0z +M0 50 h400000 v40H0z m0 194h40000v40H0z`,midbrace:`M200428 334 +c-100.7-8.3-195.3-44-280-108-55.3-42-101.7-93-139-153l-9-14c-2.7 4-5.7 8.7-9 14 +-53.3 86.7-123.7 153-211 199-66.7 36-137.3 56.3-212 62H0V214h199568c178.3-11.7 + 311.7-78.3 403-201 6-8 9.7-12 11-12 .7-.7 6.7-1 18-1s17.3.3 18 1c1.3 0 5 4 11 + 12 44.7 59.3 101.3 106.3 170 141s145.3 54.3 229 60h199572v120z`,midbraceunder:`M199572 214 +c100.7 8.3 195.3 44 280 108 55.3 42 101.7 93 139 153l9 14c2.7-4 5.7-8.7 9-14 + 53.3-86.7 123.7-153 211-199 66.7-36 137.3-56.3 212-62h199568v120H200432c-178.3 + 11.7-311.7 78.3-403 201-6 8-9.7 12-11 12-.7.7-6.7 1-18 1s-17.3-.3-18-1c-1.3 0 +-5-4-11-12-44.7-59.3-101.3-106.3-170-141s-145.3-54.3-229-60H0V214z`,oiintSize1:`M512.6 71.6c272.6 0 320.3 106.8 320.3 178.2 0 70.8-47.7 177.6 +-320.3 177.6S193.1 320.6 193.1 249.8c0-71.4 46.9-178.2 319.5-178.2z +m368.1 178.2c0-86.4-60.9-215.4-368.1-215.4-306.4 0-367.3 129-367.3 215.4 0 85.8 +60.9 214.8 367.3 214.8 307.2 0 368.1-129 368.1-214.8z`,oiintSize2:`M757.8 100.1c384.7 0 451.1 137.6 451.1 230 0 91.3-66.4 228.8 +-451.1 228.8-386.3 0-452.7-137.5-452.7-228.8 0-92.4 66.4-230 452.7-230z +m502.4 230c0-111.2-82.4-277.2-502.4-277.2s-504 166-504 277.2 +c0 110 84 276 504 276s502.4-166 502.4-276z`,oiiintSize1:`M681.4 71.6c408.9 0 480.5 106.8 480.5 178.2 0 70.8-71.6 177.6 +-480.5 177.6S202.1 320.6 202.1 249.8c0-71.4 70.5-178.2 479.3-178.2z +m525.8 178.2c0-86.4-86.8-215.4-525.7-215.4-437.9 0-524.7 129-524.7 215.4 0 +85.8 86.8 214.8 524.7 214.8 438.9 0 525.7-129 525.7-214.8z`,oiiintSize2:`M1021.2 53c603.6 0 707.8 165.8 707.8 277.2 0 110-104.2 275.8 +-707.8 275.8-606 0-710.2-165.8-710.2-275.8C311 218.8 415.2 53 1021.2 53z +m770.4 277.1c0-131.2-126.4-327.6-770.5-327.6S248.4 198.9 248.4 330.1 +c0 130 128.8 326.4 772.7 326.4s770.5-196.4 770.5-326.4z`,rightarrow:`M0 241v40h399891c-47.3 35.3-84 78-110 128 +-16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 + 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 + 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85 +-40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5 +-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 + 151.7 139 205zm0 0v40h399900v-40z`,rightbrace:`M400000 542l +-6 6h-17c-12.7 0-19.3-.3-20-1-4-4-7.3-8.3-10-13-35.3-51.3-80.8-93.8-136.5-127.5 +s-117.2-55.8-184.5-66.5c-.7 0-2-.3-4-1-18.7-2.7-76-4.3-172-5H0V214h399571l6 1 +c124.7 8 235 61.7 331 161 31.3 33.3 59.7 72.7 85 118l7 13v35z`,rightbraceunder:`M399994 0l6 6v35l-6 11c-56 104-135.3 181.3-238 232-57.3 + 28.7-117 45-179 50H-300V214h399897c43.3-7 81-15 113-26 100.7-33 179.7-91 237 +-174 2.7-5 6-9 10-13 .7-1 7.3-1 20-1h17z`,rightgroup:`M0 80h399565c371 0 266.7 149.4 414 180 5.9 1.2 18 0 18 0 2 0 + 3-1 3-3v-38c-76-158-257-219-435-219H0z`,rightgroupunder:`M0 262h399565c371 0 266.7-149.4 414-180 5.9-1.2 18 0 18 + 0 2 0 3 1 3 3v38c-76 158-257 219-435 219H0z`,rightharpoon:`M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3 +-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2 +-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 + 69.2 92 94.5zm0 0v40h399900v-40z`,rightharpoonplus:`M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11 +-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7 + 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5z +m0 0v40h399900v-40z m100 194v40h399900v-40zm0 0v40h399900v-40z`,rightharpoondown:`M399747 511c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8 + 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5 +-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95 +-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 241v40h399900v-40z`,rightharpoondownplus:`M399747 705c0 7.3 6.7 11 20 11 8 0 13-.8 + 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 + 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3 +-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 435v40h399900v-40z +m0-194v40h400000v-40zm0 0v40h400000v-40z`,righthook:`M399859 241c-764 0 0 0 0 0 40-3.3 68.7-15.7 86-37 10-12 15-25.3 + 15-40 0-22.7-9.8-40.7-29.5-54-19.7-13.3-43.5-21-71.5-23-17.3-1.3-26-8-26-20 0 +-13.3 8.7-20 26-20 38 0 71 11.2 99 33.5 0 0 7 5.6 21 16.7 14 11.2 21 33.5 21 + 66.8s-14 61.2-42 83.5c-28 22.3-61 33.5-99 33.5L0 241z M0 281v-40h399859v40z`,rightlinesegment:`M399960 241 V94 h40 V428 h-40 V281 H0 v-40z +M399960 241 V94 h40 V428 h-40 V281 H0 v-40z`,rightToFrom:`M400000 167c-70.7-42-118-97.7-142-167h-23c-15.3 0-23 .3-23 + 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8H0v40h399905l-7 8c-28.7 32 +-52 65.7-70 101-10.7 23.3-16 35.7-16 37 0 .7 7.7 1 23 1h23c24-69.3 71.3-125 142 +-167z M100 147v40h399900v-40zM0 341v40h399900v-40z`,twoheadleftarrow:`M0 167c68 40 + 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69 +-70-101l-7-8h125l9 7c50.7 39.3 85 86 103 140h46c0-4.7-6.3-18.7-19-42-18-35.3 +-40-67.3-66-96l-9-9h399716v-40H284l9-9c26-28.7 48-60.7 66-96 12.7-23.333 19 +-37.333 19-42h-46c-18 54-52.3 100.7-103 140l-9 7H95l7-8c28.7-32 52-65.7 70-101 + 10.7-23.333 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 71.3 68 127 0 167z`,twoheadrightarrow:`M400000 167 +c-68-40-115.7-95.7-143-167h-22c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3 + 41.3 69 70 101l7 8h-125l-9-7c-50.7-39.3-85-86-103-140h-46c0 4.7 6.3 18.7 19 42 + 18 35.3 40 67.3 66 96l9 9H0v40h399716l-9 9c-26 28.7-48 60.7-66 96-12.7 23.333 +-19 37.333-19 42h46c18-54 52.3-100.7 103-140l9-7h125l-7 8c-28.7 32-52 65.7-70 + 101-10.7 23.333-16 35.7-16 37 0 .7 7.7 1 23 1h22c27.3-71.3 75-127 143-167z`,tilde1:`M200 55.538c-77 0-168 73.953-177 73.953-3 0-7 +-2.175-9-5.437L2 97c-1-2-2-4-2-6 0-4 2-7 5-9l20-12C116 12 171 0 207 0c86 0 + 114 68 191 68 78 0 168-68 177-68 4 0 7 2 9 5l12 19c1 2.175 2 4.35 2 6.525 0 + 4.35-2 7.613-5 9.788l-19 13.05c-92 63.077-116.937 75.308-183 76.128 +-68.267.847-113-73.952-191-73.952z`,tilde2:`M344 55.266c-142 0-300.638 81.316-311.5 86.418 +-8.01 3.762-22.5 10.91-23.5 5.562L1 120c-1-2-1-3-1-4 0-5 3-9 8-10l18.4-9C160.9 + 31.9 283 0 358 0c148 0 188 122 331 122s314-97 326-97c4 0 8 2 10 7l7 21.114 +c1 2.14 1 3.21 1 4.28 0 5.347-3 9.626-7 10.696l-22.3 12.622C852.6 158.372 751 + 181.476 676 181.476c-149 0-189-126.21-332-126.21z`,tilde3:`M786 59C457 59 32 175.242 13 175.242c-6 0-10-3.457 +-11-10.37L.15 138c-1-7 3-12 10-13l19.2-6.4C378.4 40.7 634.3 0 804.3 0c337 0 + 411.8 157 746.8 157 328 0 754-112 773-112 5 0 10 3 11 9l1 14.075c1 8.066-.697 + 16.595-6.697 17.492l-21.052 7.31c-367.9 98.146-609.15 122.696-778.15 122.696 + -338 0-409-156.573-744-156.573z`,tilde4:`M786 58C457 58 32 177.487 13 177.487c-6 0-10-3.345 +-11-10.035L.15 143c-1-7 3-12 10-13l22-6.7C381.2 35 637.15 0 807.15 0c337 0 409 + 177 744 177 328 0 754-127 773-127 5 0 10 3 11 9l1 14.794c1 7.805-3 13.38-9 + 14.495l-20.7 5.574c-366.85 99.79-607.3 139.372-776.3 139.372-338 0-409 + -175.236-744-175.236z`,vec:`M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5 +3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11 +10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63 +-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1 +-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59 +H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359 +c-16-25.333-24-45-24-59z`,widehat1:`M529 0h5l519 115c5 1 9 5 9 10 0 1-1 2-1 3l-4 22 +c-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z`,widehat2:`M1181 0h2l1171 176c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 220h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widehat3:`M1181 0h2l1171 236c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 280h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widehat4:`M1181 0h2l1171 296c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widecheck1:`M529,159h5l519,-115c5,-1,9,-5,9,-10c0,-1,-1,-2,-1,-3l-4,-22c-1, +-5,-5,-9,-11,-9h-2l-512,92l-513,-92h-2c-5,0,-9,4,-11,9l-5,22c-1,6,2,12,8,13z`,widecheck2:`M1181,220h2l1171,-176c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,153l-1167,-153h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,widecheck3:`M1181,280h2l1171,-236c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,213l-1167,-213h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,widecheck4:`M1181,340h2l1171,-296c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,273l-1167,-273h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,baraboveleftarrow:`M400000 620h-399890l3 -3c68.7 -52.7 113.7 -120 135 -202 +c4 -14.7 6 -23 6 -25c0 -7.3 -7 -11 -21 -11c-8 0 -13.2 0.8 -15.5 2.5 +c-2.3 1.7 -4.2 5.8 -5.5 12.5c-1.3 4.7 -2.7 10.3 -4 17c-12 48.7 -34.8 92 -68.5 130 +s-74.2 66.3 -121.5 85c-10 4 -16 7.7 -18 11c0 8.7 6 14.3 18 17c47.3 18.7 87.8 47 +121.5 85s56.5 81.3 68.5 130c0.7 2 1.3 5 2 9s1.2 6.7 1.5 8c0.3 1.3 1 3.3 2 6 +s2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21 -3.7 21 -11 +c0 -2 -2 -10.3 -6 -25c-20 -79.3 -65 -146.7 -135 -202l-3 -3h399890z +M100 620v40h399900v-40z M0 241v40h399900v-40zM0 241v40h399900v-40z`,rightarrowabovebar:`M0 241v40h399891c-47.3 35.3-84 78-110 128-16.7 32 +-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0 +13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39 +-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85-40.5 +-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5 +-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 +151.7 139 205zm96 379h399894v40H0zm0 0h399904v40H0z`,baraboveshortleftharpoon:`M507,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11 +c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17 +c2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21 +c-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399351,0l0,-40 +c-398570,0,-399437,0,-399437,0z M593 435 v40 H399500 v-40z +M0 281 v-40 H399908 v40z M0 281 v-40 H399908 v40z`,rightharpoonaboveshortbar:`M0,241 l0,40c399126,0,399993,0,399993,0 +c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, +-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6 +c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z +M0 241 v40 H399908 v-40z M0 475 v-40 H399500 v40z M0 475 v-40 H399500 v40z`,shortbaraboveleftharpoon:`M7,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11 +c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9, +1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7, +-152,-211c0,0,-3,-3,-3,-3l399907,0l0,-40c-399126,0,-399993,0,-399993,0z +M93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z`,shortrightharpoonabovebar:`M53,241l0,40c398570,0,399437,0,399437,0 +c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, +-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6 +c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z +M500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z`},Pa=function(e,t){switch(e){case"lbrack":return"M403 1759 V84 H666 V0 H319 V1759 v"+t+` v1759 h347 v-84 +H403z M403 1759 V0 H319 V1759 v`+t+" v1759 h84z";case"rbrack":return"M347 1759 V0 H0 V84 H263 V1759 v"+t+` v1759 H0 v84 H347z +M347 1759 V0 H263 V1759 v`+t+" v1759 h84z";case"vert":return"M145 15 v585 v"+t+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-t+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v`+t+" v585 h43z";case"doublevert":return"M145 15 v585 v"+t+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-t+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v`+t+` v585 h43z +M367 15 v585 v`+t+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-t+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M410 15 H367 v585 v`+t+" v585 h43z";case"lfloor":return"M319 602 V0 H403 V602 v"+t+` v1715 h263 v84 H319z +MM319 602 V0 H403 V602 v`+t+" v1715 H319z";case"rfloor":return"M319 602 V0 H403 V602 v"+t+` v1799 H0 v-84 H319z +MM319 602 V0 H403 V602 v`+t+" v1715 H319z";case"lceil":return"M403 1759 V84 H666 V0 H319 V1759 v"+t+` v602 h84z +M403 1759 V0 H319 V1759 v`+t+" v602 h84z";case"rceil":return"M347 1759 V0 H0 V84 H263 V1759 v"+t+` v602 h84z +M347 1759 V0 h-84 V1759 v`+t+" v602 h84z";case"lparen":return`M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1 +c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349, +-36,557 l0,`+(t+84)+`c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210, +949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9 +c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5, +-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189 +l0,-`+(t+92)+`c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3, +-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z`;case"rparen":return`M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3, +63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5 +c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,`+(t+9)+` +c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664 +c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11 +c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17 +c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558 +l0,-`+(t+144)+`c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7, +-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z`;default:throw new Error("Unknown stretchy delimiter.")}};class oe{constructor(e){this.children=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.children=e,this.classes=[],this.height=0,this.depth=0,this.maxFontSize=0,this.style={}}hasClass(e){return this.classes.includes(e)}toNode(){for(var e=document.createDocumentFragment(),t=0;tt.toText();return this.children.map(e).join("")}}var x0={"AMS-Regular":{32:[0,0,0,0,.25],65:[0,.68889,0,0,.72222],66:[0,.68889,0,0,.66667],67:[0,.68889,0,0,.72222],68:[0,.68889,0,0,.72222],69:[0,.68889,0,0,.66667],70:[0,.68889,0,0,.61111],71:[0,.68889,0,0,.77778],72:[0,.68889,0,0,.77778],73:[0,.68889,0,0,.38889],74:[.16667,.68889,0,0,.5],75:[0,.68889,0,0,.77778],76:[0,.68889,0,0,.66667],77:[0,.68889,0,0,.94445],78:[0,.68889,0,0,.72222],79:[.16667,.68889,0,0,.77778],80:[0,.68889,0,0,.61111],81:[.16667,.68889,0,0,.77778],82:[0,.68889,0,0,.72222],83:[0,.68889,0,0,.55556],84:[0,.68889,0,0,.66667],85:[0,.68889,0,0,.72222],86:[0,.68889,0,0,.72222],87:[0,.68889,0,0,1],88:[0,.68889,0,0,.72222],89:[0,.68889,0,0,.72222],90:[0,.68889,0,0,.66667],107:[0,.68889,0,0,.55556],160:[0,0,0,0,.25],165:[0,.675,.025,0,.75],174:[.15559,.69224,0,0,.94666],240:[0,.68889,0,0,.55556],295:[0,.68889,0,0,.54028],710:[0,.825,0,0,2.33334],732:[0,.9,0,0,2.33334],770:[0,.825,0,0,2.33334],771:[0,.9,0,0,2.33334],989:[.08167,.58167,0,0,.77778],1008:[0,.43056,.04028,0,.66667],8245:[0,.54986,0,0,.275],8463:[0,.68889,0,0,.54028],8487:[0,.68889,0,0,.72222],8498:[0,.68889,0,0,.55556],8502:[0,.68889,0,0,.66667],8503:[0,.68889,0,0,.44445],8504:[0,.68889,0,0,.66667],8513:[0,.68889,0,0,.63889],8592:[-.03598,.46402,0,0,.5],8594:[-.03598,.46402,0,0,.5],8602:[-.13313,.36687,0,0,1],8603:[-.13313,.36687,0,0,1],8606:[.01354,.52239,0,0,1],8608:[.01354,.52239,0,0,1],8610:[.01354,.52239,0,0,1.11111],8611:[.01354,.52239,0,0,1.11111],8619:[0,.54986,0,0,1],8620:[0,.54986,0,0,1],8621:[-.13313,.37788,0,0,1.38889],8622:[-.13313,.36687,0,0,1],8624:[0,.69224,0,0,.5],8625:[0,.69224,0,0,.5],8630:[0,.43056,0,0,1],8631:[0,.43056,0,0,1],8634:[.08198,.58198,0,0,.77778],8635:[.08198,.58198,0,0,.77778],8638:[.19444,.69224,0,0,.41667],8639:[.19444,.69224,0,0,.41667],8642:[.19444,.69224,0,0,.41667],8643:[.19444,.69224,0,0,.41667],8644:[.1808,.675,0,0,1],8646:[.1808,.675,0,0,1],8647:[.1808,.675,0,0,1],8648:[.19444,.69224,0,0,.83334],8649:[.1808,.675,0,0,1],8650:[.19444,.69224,0,0,.83334],8651:[.01354,.52239,0,0,1],8652:[.01354,.52239,0,0,1],8653:[-.13313,.36687,0,0,1],8654:[-.13313,.36687,0,0,1],8655:[-.13313,.36687,0,0,1],8666:[.13667,.63667,0,0,1],8667:[.13667,.63667,0,0,1],8669:[-.13313,.37788,0,0,1],8672:[-.064,.437,0,0,1.334],8674:[-.064,.437,0,0,1.334],8705:[0,.825,0,0,.5],8708:[0,.68889,0,0,.55556],8709:[.08167,.58167,0,0,.77778],8717:[0,.43056,0,0,.42917],8722:[-.03598,.46402,0,0,.5],8724:[.08198,.69224,0,0,.77778],8726:[.08167,.58167,0,0,.77778],8733:[0,.69224,0,0,.77778],8736:[0,.69224,0,0,.72222],8737:[0,.69224,0,0,.72222],8738:[.03517,.52239,0,0,.72222],8739:[.08167,.58167,0,0,.22222],8740:[.25142,.74111,0,0,.27778],8741:[.08167,.58167,0,0,.38889],8742:[.25142,.74111,0,0,.5],8756:[0,.69224,0,0,.66667],8757:[0,.69224,0,0,.66667],8764:[-.13313,.36687,0,0,.77778],8765:[-.13313,.37788,0,0,.77778],8769:[-.13313,.36687,0,0,.77778],8770:[-.03625,.46375,0,0,.77778],8774:[.30274,.79383,0,0,.77778],8776:[-.01688,.48312,0,0,.77778],8778:[.08167,.58167,0,0,.77778],8782:[.06062,.54986,0,0,.77778],8783:[.06062,.54986,0,0,.77778],8785:[.08198,.58198,0,0,.77778],8786:[.08198,.58198,0,0,.77778],8787:[.08198,.58198,0,0,.77778],8790:[0,.69224,0,0,.77778],8791:[.22958,.72958,0,0,.77778],8796:[.08198,.91667,0,0,.77778],8806:[.25583,.75583,0,0,.77778],8807:[.25583,.75583,0,0,.77778],8808:[.25142,.75726,0,0,.77778],8809:[.25142,.75726,0,0,.77778],8812:[.25583,.75583,0,0,.5],8814:[.20576,.70576,0,0,.77778],8815:[.20576,.70576,0,0,.77778],8816:[.30274,.79383,0,0,.77778],8817:[.30274,.79383,0,0,.77778],8818:[.22958,.72958,0,0,.77778],8819:[.22958,.72958,0,0,.77778],8822:[.1808,.675,0,0,.77778],8823:[.1808,.675,0,0,.77778],8828:[.13667,.63667,0,0,.77778],8829:[.13667,.63667,0,0,.77778],8830:[.22958,.72958,0,0,.77778],8831:[.22958,.72958,0,0,.77778],8832:[.20576,.70576,0,0,.77778],8833:[.20576,.70576,0,0,.77778],8840:[.30274,.79383,0,0,.77778],8841:[.30274,.79383,0,0,.77778],8842:[.13597,.63597,0,0,.77778],8843:[.13597,.63597,0,0,.77778],8847:[.03517,.54986,0,0,.77778],8848:[.03517,.54986,0,0,.77778],8858:[.08198,.58198,0,0,.77778],8859:[.08198,.58198,0,0,.77778],8861:[.08198,.58198,0,0,.77778],8862:[0,.675,0,0,.77778],8863:[0,.675,0,0,.77778],8864:[0,.675,0,0,.77778],8865:[0,.675,0,0,.77778],8872:[0,.69224,0,0,.61111],8873:[0,.69224,0,0,.72222],8874:[0,.69224,0,0,.88889],8876:[0,.68889,0,0,.61111],8877:[0,.68889,0,0,.61111],8878:[0,.68889,0,0,.72222],8879:[0,.68889,0,0,.72222],8882:[.03517,.54986,0,0,.77778],8883:[.03517,.54986,0,0,.77778],8884:[.13667,.63667,0,0,.77778],8885:[.13667,.63667,0,0,.77778],8888:[0,.54986,0,0,1.11111],8890:[.19444,.43056,0,0,.55556],8891:[.19444,.69224,0,0,.61111],8892:[.19444,.69224,0,0,.61111],8901:[0,.54986,0,0,.27778],8903:[.08167,.58167,0,0,.77778],8905:[.08167,.58167,0,0,.77778],8906:[.08167,.58167,0,0,.77778],8907:[0,.69224,0,0,.77778],8908:[0,.69224,0,0,.77778],8909:[-.03598,.46402,0,0,.77778],8910:[0,.54986,0,0,.76042],8911:[0,.54986,0,0,.76042],8912:[.03517,.54986,0,0,.77778],8913:[.03517,.54986,0,0,.77778],8914:[0,.54986,0,0,.66667],8915:[0,.54986,0,0,.66667],8916:[0,.69224,0,0,.66667],8918:[.0391,.5391,0,0,.77778],8919:[.0391,.5391,0,0,.77778],8920:[.03517,.54986,0,0,1.33334],8921:[.03517,.54986,0,0,1.33334],8922:[.38569,.88569,0,0,.77778],8923:[.38569,.88569,0,0,.77778],8926:[.13667,.63667,0,0,.77778],8927:[.13667,.63667,0,0,.77778],8928:[.30274,.79383,0,0,.77778],8929:[.30274,.79383,0,0,.77778],8934:[.23222,.74111,0,0,.77778],8935:[.23222,.74111,0,0,.77778],8936:[.23222,.74111,0,0,.77778],8937:[.23222,.74111,0,0,.77778],8938:[.20576,.70576,0,0,.77778],8939:[.20576,.70576,0,0,.77778],8940:[.30274,.79383,0,0,.77778],8941:[.30274,.79383,0,0,.77778],8994:[.19444,.69224,0,0,.77778],8995:[.19444,.69224,0,0,.77778],9416:[.15559,.69224,0,0,.90222],9484:[0,.69224,0,0,.5],9488:[0,.69224,0,0,.5],9492:[0,.37788,0,0,.5],9496:[0,.37788,0,0,.5],9585:[.19444,.68889,0,0,.88889],9586:[.19444,.74111,0,0,.88889],9632:[0,.675,0,0,.77778],9633:[0,.675,0,0,.77778],9650:[0,.54986,0,0,.72222],9651:[0,.54986,0,0,.72222],9654:[.03517,.54986,0,0,.77778],9660:[0,.54986,0,0,.72222],9661:[0,.54986,0,0,.72222],9664:[.03517,.54986,0,0,.77778],9674:[.11111,.69224,0,0,.66667],9733:[.19444,.69224,0,0,.94445],10003:[0,.69224,0,0,.83334],10016:[0,.69224,0,0,.83334],10731:[.11111,.69224,0,0,.66667],10846:[.19444,.75583,0,0,.61111],10877:[.13667,.63667,0,0,.77778],10878:[.13667,.63667,0,0,.77778],10885:[.25583,.75583,0,0,.77778],10886:[.25583,.75583,0,0,.77778],10887:[.13597,.63597,0,0,.77778],10888:[.13597,.63597,0,0,.77778],10889:[.26167,.75726,0,0,.77778],10890:[.26167,.75726,0,0,.77778],10891:[.48256,.98256,0,0,.77778],10892:[.48256,.98256,0,0,.77778],10901:[.13667,.63667,0,0,.77778],10902:[.13667,.63667,0,0,.77778],10933:[.25142,.75726,0,0,.77778],10934:[.25142,.75726,0,0,.77778],10935:[.26167,.75726,0,0,.77778],10936:[.26167,.75726,0,0,.77778],10937:[.26167,.75726,0,0,.77778],10938:[.26167,.75726,0,0,.77778],10949:[.25583,.75583,0,0,.77778],10950:[.25583,.75583,0,0,.77778],10955:[.28481,.79383,0,0,.77778],10956:[.28481,.79383,0,0,.77778],57350:[.08167,.58167,0,0,.22222],57351:[.08167,.58167,0,0,.38889],57352:[.08167,.58167,0,0,.77778],57353:[0,.43056,.04028,0,.66667],57356:[.25142,.75726,0,0,.77778],57357:[.25142,.75726,0,0,.77778],57358:[.41951,.91951,0,0,.77778],57359:[.30274,.79383,0,0,.77778],57360:[.30274,.79383,0,0,.77778],57361:[.41951,.91951,0,0,.77778],57366:[.25142,.75726,0,0,.77778],57367:[.25142,.75726,0,0,.77778],57368:[.25142,.75726,0,0,.77778],57369:[.25142,.75726,0,0,.77778],57370:[.13597,.63597,0,0,.77778],57371:[.13597,.63597,0,0,.77778]},"Caligraphic-Regular":{32:[0,0,0,0,.25],65:[0,.68333,0,.19445,.79847],66:[0,.68333,.03041,.13889,.65681],67:[0,.68333,.05834,.13889,.52653],68:[0,.68333,.02778,.08334,.77139],69:[0,.68333,.08944,.11111,.52778],70:[0,.68333,.09931,.11111,.71875],71:[.09722,.68333,.0593,.11111,.59487],72:[0,.68333,.00965,.11111,.84452],73:[0,.68333,.07382,0,.54452],74:[.09722,.68333,.18472,.16667,.67778],75:[0,.68333,.01445,.05556,.76195],76:[0,.68333,0,.13889,.68972],77:[0,.68333,0,.13889,1.2009],78:[0,.68333,.14736,.08334,.82049],79:[0,.68333,.02778,.11111,.79611],80:[0,.68333,.08222,.08334,.69556],81:[.09722,.68333,0,.11111,.81667],82:[0,.68333,0,.08334,.8475],83:[0,.68333,.075,.13889,.60556],84:[0,.68333,.25417,0,.54464],85:[0,.68333,.09931,.08334,.62583],86:[0,.68333,.08222,0,.61278],87:[0,.68333,.08222,.08334,.98778],88:[0,.68333,.14643,.13889,.7133],89:[.09722,.68333,.08222,.08334,.66834],90:[0,.68333,.07944,.13889,.72473],160:[0,0,0,0,.25]},"Fraktur-Regular":{32:[0,0,0,0,.25],33:[0,.69141,0,0,.29574],34:[0,.69141,0,0,.21471],38:[0,.69141,0,0,.73786],39:[0,.69141,0,0,.21201],40:[.24982,.74947,0,0,.38865],41:[.24982,.74947,0,0,.38865],42:[0,.62119,0,0,.27764],43:[.08319,.58283,0,0,.75623],44:[0,.10803,0,0,.27764],45:[.08319,.58283,0,0,.75623],46:[0,.10803,0,0,.27764],47:[.24982,.74947,0,0,.50181],48:[0,.47534,0,0,.50181],49:[0,.47534,0,0,.50181],50:[0,.47534,0,0,.50181],51:[.18906,.47534,0,0,.50181],52:[.18906,.47534,0,0,.50181],53:[.18906,.47534,0,0,.50181],54:[0,.69141,0,0,.50181],55:[.18906,.47534,0,0,.50181],56:[0,.69141,0,0,.50181],57:[.18906,.47534,0,0,.50181],58:[0,.47534,0,0,.21606],59:[.12604,.47534,0,0,.21606],61:[-.13099,.36866,0,0,.75623],63:[0,.69141,0,0,.36245],65:[0,.69141,0,0,.7176],66:[0,.69141,0,0,.88397],67:[0,.69141,0,0,.61254],68:[0,.69141,0,0,.83158],69:[0,.69141,0,0,.66278],70:[.12604,.69141,0,0,.61119],71:[0,.69141,0,0,.78539],72:[.06302,.69141,0,0,.7203],73:[0,.69141,0,0,.55448],74:[.12604,.69141,0,0,.55231],75:[0,.69141,0,0,.66845],76:[0,.69141,0,0,.66602],77:[0,.69141,0,0,1.04953],78:[0,.69141,0,0,.83212],79:[0,.69141,0,0,.82699],80:[.18906,.69141,0,0,.82753],81:[.03781,.69141,0,0,.82699],82:[0,.69141,0,0,.82807],83:[0,.69141,0,0,.82861],84:[0,.69141,0,0,.66899],85:[0,.69141,0,0,.64576],86:[0,.69141,0,0,.83131],87:[0,.69141,0,0,1.04602],88:[0,.69141,0,0,.71922],89:[.18906,.69141,0,0,.83293],90:[.12604,.69141,0,0,.60201],91:[.24982,.74947,0,0,.27764],93:[.24982,.74947,0,0,.27764],94:[0,.69141,0,0,.49965],97:[0,.47534,0,0,.50046],98:[0,.69141,0,0,.51315],99:[0,.47534,0,0,.38946],100:[0,.62119,0,0,.49857],101:[0,.47534,0,0,.40053],102:[.18906,.69141,0,0,.32626],103:[.18906,.47534,0,0,.5037],104:[.18906,.69141,0,0,.52126],105:[0,.69141,0,0,.27899],106:[0,.69141,0,0,.28088],107:[0,.69141,0,0,.38946],108:[0,.69141,0,0,.27953],109:[0,.47534,0,0,.76676],110:[0,.47534,0,0,.52666],111:[0,.47534,0,0,.48885],112:[.18906,.52396,0,0,.50046],113:[.18906,.47534,0,0,.48912],114:[0,.47534,0,0,.38919],115:[0,.47534,0,0,.44266],116:[0,.62119,0,0,.33301],117:[0,.47534,0,0,.5172],118:[0,.52396,0,0,.5118],119:[0,.52396,0,0,.77351],120:[.18906,.47534,0,0,.38865],121:[.18906,.47534,0,0,.49884],122:[.18906,.47534,0,0,.39054],160:[0,0,0,0,.25],8216:[0,.69141,0,0,.21471],8217:[0,.69141,0,0,.21471],58112:[0,.62119,0,0,.49749],58113:[0,.62119,0,0,.4983],58114:[.18906,.69141,0,0,.33328],58115:[.18906,.69141,0,0,.32923],58116:[.18906,.47534,0,0,.50343],58117:[0,.69141,0,0,.33301],58118:[0,.62119,0,0,.33409],58119:[0,.47534,0,0,.50073]},"Main-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.35],34:[0,.69444,0,0,.60278],35:[.19444,.69444,0,0,.95833],36:[.05556,.75,0,0,.575],37:[.05556,.75,0,0,.95833],38:[0,.69444,0,0,.89444],39:[0,.69444,0,0,.31944],40:[.25,.75,0,0,.44722],41:[.25,.75,0,0,.44722],42:[0,.75,0,0,.575],43:[.13333,.63333,0,0,.89444],44:[.19444,.15556,0,0,.31944],45:[0,.44444,0,0,.38333],46:[0,.15556,0,0,.31944],47:[.25,.75,0,0,.575],48:[0,.64444,0,0,.575],49:[0,.64444,0,0,.575],50:[0,.64444,0,0,.575],51:[0,.64444,0,0,.575],52:[0,.64444,0,0,.575],53:[0,.64444,0,0,.575],54:[0,.64444,0,0,.575],55:[0,.64444,0,0,.575],56:[0,.64444,0,0,.575],57:[0,.64444,0,0,.575],58:[0,.44444,0,0,.31944],59:[.19444,.44444,0,0,.31944],60:[.08556,.58556,0,0,.89444],61:[-.10889,.39111,0,0,.89444],62:[.08556,.58556,0,0,.89444],63:[0,.69444,0,0,.54305],64:[0,.69444,0,0,.89444],65:[0,.68611,0,0,.86944],66:[0,.68611,0,0,.81805],67:[0,.68611,0,0,.83055],68:[0,.68611,0,0,.88194],69:[0,.68611,0,0,.75555],70:[0,.68611,0,0,.72361],71:[0,.68611,0,0,.90416],72:[0,.68611,0,0,.9],73:[0,.68611,0,0,.43611],74:[0,.68611,0,0,.59444],75:[0,.68611,0,0,.90138],76:[0,.68611,0,0,.69166],77:[0,.68611,0,0,1.09166],78:[0,.68611,0,0,.9],79:[0,.68611,0,0,.86388],80:[0,.68611,0,0,.78611],81:[.19444,.68611,0,0,.86388],82:[0,.68611,0,0,.8625],83:[0,.68611,0,0,.63889],84:[0,.68611,0,0,.8],85:[0,.68611,0,0,.88472],86:[0,.68611,.01597,0,.86944],87:[0,.68611,.01597,0,1.18888],88:[0,.68611,0,0,.86944],89:[0,.68611,.02875,0,.86944],90:[0,.68611,0,0,.70277],91:[.25,.75,0,0,.31944],92:[.25,.75,0,0,.575],93:[.25,.75,0,0,.31944],94:[0,.69444,0,0,.575],95:[.31,.13444,.03194,0,.575],97:[0,.44444,0,0,.55902],98:[0,.69444,0,0,.63889],99:[0,.44444,0,0,.51111],100:[0,.69444,0,0,.63889],101:[0,.44444,0,0,.52708],102:[0,.69444,.10903,0,.35139],103:[.19444,.44444,.01597,0,.575],104:[0,.69444,0,0,.63889],105:[0,.69444,0,0,.31944],106:[.19444,.69444,0,0,.35139],107:[0,.69444,0,0,.60694],108:[0,.69444,0,0,.31944],109:[0,.44444,0,0,.95833],110:[0,.44444,0,0,.63889],111:[0,.44444,0,0,.575],112:[.19444,.44444,0,0,.63889],113:[.19444,.44444,0,0,.60694],114:[0,.44444,0,0,.47361],115:[0,.44444,0,0,.45361],116:[0,.63492,0,0,.44722],117:[0,.44444,0,0,.63889],118:[0,.44444,.01597,0,.60694],119:[0,.44444,.01597,0,.83055],120:[0,.44444,0,0,.60694],121:[.19444,.44444,.01597,0,.60694],122:[0,.44444,0,0,.51111],123:[.25,.75,0,0,.575],124:[.25,.75,0,0,.31944],125:[.25,.75,0,0,.575],126:[.35,.34444,0,0,.575],160:[0,0,0,0,.25],163:[0,.69444,0,0,.86853],168:[0,.69444,0,0,.575],172:[0,.44444,0,0,.76666],176:[0,.69444,0,0,.86944],177:[.13333,.63333,0,0,.89444],184:[.17014,0,0,0,.51111],198:[0,.68611,0,0,1.04166],215:[.13333,.63333,0,0,.89444],216:[.04861,.73472,0,0,.89444],223:[0,.69444,0,0,.59722],230:[0,.44444,0,0,.83055],247:[.13333,.63333,0,0,.89444],248:[.09722,.54167,0,0,.575],305:[0,.44444,0,0,.31944],338:[0,.68611,0,0,1.16944],339:[0,.44444,0,0,.89444],567:[.19444,.44444,0,0,.35139],710:[0,.69444,0,0,.575],711:[0,.63194,0,0,.575],713:[0,.59611,0,0,.575],714:[0,.69444,0,0,.575],715:[0,.69444,0,0,.575],728:[0,.69444,0,0,.575],729:[0,.69444,0,0,.31944],730:[0,.69444,0,0,.86944],732:[0,.69444,0,0,.575],733:[0,.69444,0,0,.575],915:[0,.68611,0,0,.69166],916:[0,.68611,0,0,.95833],920:[0,.68611,0,0,.89444],923:[0,.68611,0,0,.80555],926:[0,.68611,0,0,.76666],928:[0,.68611,0,0,.9],931:[0,.68611,0,0,.83055],933:[0,.68611,0,0,.89444],934:[0,.68611,0,0,.83055],936:[0,.68611,0,0,.89444],937:[0,.68611,0,0,.83055],8211:[0,.44444,.03194,0,.575],8212:[0,.44444,.03194,0,1.14999],8216:[0,.69444,0,0,.31944],8217:[0,.69444,0,0,.31944],8220:[0,.69444,0,0,.60278],8221:[0,.69444,0,0,.60278],8224:[.19444,.69444,0,0,.51111],8225:[.19444,.69444,0,0,.51111],8242:[0,.55556,0,0,.34444],8407:[0,.72444,.15486,0,.575],8463:[0,.69444,0,0,.66759],8465:[0,.69444,0,0,.83055],8467:[0,.69444,0,0,.47361],8472:[.19444,.44444,0,0,.74027],8476:[0,.69444,0,0,.83055],8501:[0,.69444,0,0,.70277],8592:[-.10889,.39111,0,0,1.14999],8593:[.19444,.69444,0,0,.575],8594:[-.10889,.39111,0,0,1.14999],8595:[.19444,.69444,0,0,.575],8596:[-.10889,.39111,0,0,1.14999],8597:[.25,.75,0,0,.575],8598:[.19444,.69444,0,0,1.14999],8599:[.19444,.69444,0,0,1.14999],8600:[.19444,.69444,0,0,1.14999],8601:[.19444,.69444,0,0,1.14999],8636:[-.10889,.39111,0,0,1.14999],8637:[-.10889,.39111,0,0,1.14999],8640:[-.10889,.39111,0,0,1.14999],8641:[-.10889,.39111,0,0,1.14999],8656:[-.10889,.39111,0,0,1.14999],8657:[.19444,.69444,0,0,.70277],8658:[-.10889,.39111,0,0,1.14999],8659:[.19444,.69444,0,0,.70277],8660:[-.10889,.39111,0,0,1.14999],8661:[.25,.75,0,0,.70277],8704:[0,.69444,0,0,.63889],8706:[0,.69444,.06389,0,.62847],8707:[0,.69444,0,0,.63889],8709:[.05556,.75,0,0,.575],8711:[0,.68611,0,0,.95833],8712:[.08556,.58556,0,0,.76666],8715:[.08556,.58556,0,0,.76666],8722:[.13333,.63333,0,0,.89444],8723:[.13333,.63333,0,0,.89444],8725:[.25,.75,0,0,.575],8726:[.25,.75,0,0,.575],8727:[-.02778,.47222,0,0,.575],8728:[-.02639,.47361,0,0,.575],8729:[-.02639,.47361,0,0,.575],8730:[.18,.82,0,0,.95833],8733:[0,.44444,0,0,.89444],8734:[0,.44444,0,0,1.14999],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.31944],8741:[.25,.75,0,0,.575],8743:[0,.55556,0,0,.76666],8744:[0,.55556,0,0,.76666],8745:[0,.55556,0,0,.76666],8746:[0,.55556,0,0,.76666],8747:[.19444,.69444,.12778,0,.56875],8764:[-.10889,.39111,0,0,.89444],8768:[.19444,.69444,0,0,.31944],8771:[.00222,.50222,0,0,.89444],8773:[.027,.638,0,0,.894],8776:[.02444,.52444,0,0,.89444],8781:[.00222,.50222,0,0,.89444],8801:[.00222,.50222,0,0,.89444],8804:[.19667,.69667,0,0,.89444],8805:[.19667,.69667,0,0,.89444],8810:[.08556,.58556,0,0,1.14999],8811:[.08556,.58556,0,0,1.14999],8826:[.08556,.58556,0,0,.89444],8827:[.08556,.58556,0,0,.89444],8834:[.08556,.58556,0,0,.89444],8835:[.08556,.58556,0,0,.89444],8838:[.19667,.69667,0,0,.89444],8839:[.19667,.69667,0,0,.89444],8846:[0,.55556,0,0,.76666],8849:[.19667,.69667,0,0,.89444],8850:[.19667,.69667,0,0,.89444],8851:[0,.55556,0,0,.76666],8852:[0,.55556,0,0,.76666],8853:[.13333,.63333,0,0,.89444],8854:[.13333,.63333,0,0,.89444],8855:[.13333,.63333,0,0,.89444],8856:[.13333,.63333,0,0,.89444],8857:[.13333,.63333,0,0,.89444],8866:[0,.69444,0,0,.70277],8867:[0,.69444,0,0,.70277],8868:[0,.69444,0,0,.89444],8869:[0,.69444,0,0,.89444],8900:[-.02639,.47361,0,0,.575],8901:[-.02639,.47361,0,0,.31944],8902:[-.02778,.47222,0,0,.575],8968:[.25,.75,0,0,.51111],8969:[.25,.75,0,0,.51111],8970:[.25,.75,0,0,.51111],8971:[.25,.75,0,0,.51111],8994:[-.13889,.36111,0,0,1.14999],8995:[-.13889,.36111,0,0,1.14999],9651:[.19444,.69444,0,0,1.02222],9657:[-.02778,.47222,0,0,.575],9661:[.19444,.69444,0,0,1.02222],9667:[-.02778,.47222,0,0,.575],9711:[.19444,.69444,0,0,1.14999],9824:[.12963,.69444,0,0,.89444],9825:[.12963,.69444,0,0,.89444],9826:[.12963,.69444,0,0,.89444],9827:[.12963,.69444,0,0,.89444],9837:[0,.75,0,0,.44722],9838:[.19444,.69444,0,0,.44722],9839:[.19444,.69444,0,0,.44722],10216:[.25,.75,0,0,.44722],10217:[.25,.75,0,0,.44722],10815:[0,.68611,0,0,.9],10927:[.19667,.69667,0,0,.89444],10928:[.19667,.69667,0,0,.89444],57376:[.19444,.69444,0,0,0]},"Main-BoldItalic":{32:[0,0,0,0,.25],33:[0,.69444,.11417,0,.38611],34:[0,.69444,.07939,0,.62055],35:[.19444,.69444,.06833,0,.94444],37:[.05556,.75,.12861,0,.94444],38:[0,.69444,.08528,0,.88555],39:[0,.69444,.12945,0,.35555],40:[.25,.75,.15806,0,.47333],41:[.25,.75,.03306,0,.47333],42:[0,.75,.14333,0,.59111],43:[.10333,.60333,.03306,0,.88555],44:[.19444,.14722,0,0,.35555],45:[0,.44444,.02611,0,.41444],46:[0,.14722,0,0,.35555],47:[.25,.75,.15806,0,.59111],48:[0,.64444,.13167,0,.59111],49:[0,.64444,.13167,0,.59111],50:[0,.64444,.13167,0,.59111],51:[0,.64444,.13167,0,.59111],52:[.19444,.64444,.13167,0,.59111],53:[0,.64444,.13167,0,.59111],54:[0,.64444,.13167,0,.59111],55:[.19444,.64444,.13167,0,.59111],56:[0,.64444,.13167,0,.59111],57:[0,.64444,.13167,0,.59111],58:[0,.44444,.06695,0,.35555],59:[.19444,.44444,.06695,0,.35555],61:[-.10889,.39111,.06833,0,.88555],63:[0,.69444,.11472,0,.59111],64:[0,.69444,.09208,0,.88555],65:[0,.68611,0,0,.86555],66:[0,.68611,.0992,0,.81666],67:[0,.68611,.14208,0,.82666],68:[0,.68611,.09062,0,.87555],69:[0,.68611,.11431,0,.75666],70:[0,.68611,.12903,0,.72722],71:[0,.68611,.07347,0,.89527],72:[0,.68611,.17208,0,.8961],73:[0,.68611,.15681,0,.47166],74:[0,.68611,.145,0,.61055],75:[0,.68611,.14208,0,.89499],76:[0,.68611,0,0,.69777],77:[0,.68611,.17208,0,1.07277],78:[0,.68611,.17208,0,.8961],79:[0,.68611,.09062,0,.85499],80:[0,.68611,.0992,0,.78721],81:[.19444,.68611,.09062,0,.85499],82:[0,.68611,.02559,0,.85944],83:[0,.68611,.11264,0,.64999],84:[0,.68611,.12903,0,.7961],85:[0,.68611,.17208,0,.88083],86:[0,.68611,.18625,0,.86555],87:[0,.68611,.18625,0,1.15999],88:[0,.68611,.15681,0,.86555],89:[0,.68611,.19803,0,.86555],90:[0,.68611,.14208,0,.70888],91:[.25,.75,.1875,0,.35611],93:[.25,.75,.09972,0,.35611],94:[0,.69444,.06709,0,.59111],95:[.31,.13444,.09811,0,.59111],97:[0,.44444,.09426,0,.59111],98:[0,.69444,.07861,0,.53222],99:[0,.44444,.05222,0,.53222],100:[0,.69444,.10861,0,.59111],101:[0,.44444,.085,0,.53222],102:[.19444,.69444,.21778,0,.4],103:[.19444,.44444,.105,0,.53222],104:[0,.69444,.09426,0,.59111],105:[0,.69326,.11387,0,.35555],106:[.19444,.69326,.1672,0,.35555],107:[0,.69444,.11111,0,.53222],108:[0,.69444,.10861,0,.29666],109:[0,.44444,.09426,0,.94444],110:[0,.44444,.09426,0,.64999],111:[0,.44444,.07861,0,.59111],112:[.19444,.44444,.07861,0,.59111],113:[.19444,.44444,.105,0,.53222],114:[0,.44444,.11111,0,.50167],115:[0,.44444,.08167,0,.48694],116:[0,.63492,.09639,0,.385],117:[0,.44444,.09426,0,.62055],118:[0,.44444,.11111,0,.53222],119:[0,.44444,.11111,0,.76777],120:[0,.44444,.12583,0,.56055],121:[.19444,.44444,.105,0,.56166],122:[0,.44444,.13889,0,.49055],126:[.35,.34444,.11472,0,.59111],160:[0,0,0,0,.25],168:[0,.69444,.11473,0,.59111],176:[0,.69444,0,0,.94888],184:[.17014,0,0,0,.53222],198:[0,.68611,.11431,0,1.02277],216:[.04861,.73472,.09062,0,.88555],223:[.19444,.69444,.09736,0,.665],230:[0,.44444,.085,0,.82666],248:[.09722,.54167,.09458,0,.59111],305:[0,.44444,.09426,0,.35555],338:[0,.68611,.11431,0,1.14054],339:[0,.44444,.085,0,.82666],567:[.19444,.44444,.04611,0,.385],710:[0,.69444,.06709,0,.59111],711:[0,.63194,.08271,0,.59111],713:[0,.59444,.10444,0,.59111],714:[0,.69444,.08528,0,.59111],715:[0,.69444,0,0,.59111],728:[0,.69444,.10333,0,.59111],729:[0,.69444,.12945,0,.35555],730:[0,.69444,0,0,.94888],732:[0,.69444,.11472,0,.59111],733:[0,.69444,.11472,0,.59111],915:[0,.68611,.12903,0,.69777],916:[0,.68611,0,0,.94444],920:[0,.68611,.09062,0,.88555],923:[0,.68611,0,0,.80666],926:[0,.68611,.15092,0,.76777],928:[0,.68611,.17208,0,.8961],931:[0,.68611,.11431,0,.82666],933:[0,.68611,.10778,0,.88555],934:[0,.68611,.05632,0,.82666],936:[0,.68611,.10778,0,.88555],937:[0,.68611,.0992,0,.82666],8211:[0,.44444,.09811,0,.59111],8212:[0,.44444,.09811,0,1.18221],8216:[0,.69444,.12945,0,.35555],8217:[0,.69444,.12945,0,.35555],8220:[0,.69444,.16772,0,.62055],8221:[0,.69444,.07939,0,.62055]},"Main-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.12417,0,.30667],34:[0,.69444,.06961,0,.51444],35:[.19444,.69444,.06616,0,.81777],37:[.05556,.75,.13639,0,.81777],38:[0,.69444,.09694,0,.76666],39:[0,.69444,.12417,0,.30667],40:[.25,.75,.16194,0,.40889],41:[.25,.75,.03694,0,.40889],42:[0,.75,.14917,0,.51111],43:[.05667,.56167,.03694,0,.76666],44:[.19444,.10556,0,0,.30667],45:[0,.43056,.02826,0,.35778],46:[0,.10556,0,0,.30667],47:[.25,.75,.16194,0,.51111],48:[0,.64444,.13556,0,.51111],49:[0,.64444,.13556,0,.51111],50:[0,.64444,.13556,0,.51111],51:[0,.64444,.13556,0,.51111],52:[.19444,.64444,.13556,0,.51111],53:[0,.64444,.13556,0,.51111],54:[0,.64444,.13556,0,.51111],55:[.19444,.64444,.13556,0,.51111],56:[0,.64444,.13556,0,.51111],57:[0,.64444,.13556,0,.51111],58:[0,.43056,.0582,0,.30667],59:[.19444,.43056,.0582,0,.30667],61:[-.13313,.36687,.06616,0,.76666],63:[0,.69444,.1225,0,.51111],64:[0,.69444,.09597,0,.76666],65:[0,.68333,0,0,.74333],66:[0,.68333,.10257,0,.70389],67:[0,.68333,.14528,0,.71555],68:[0,.68333,.09403,0,.755],69:[0,.68333,.12028,0,.67833],70:[0,.68333,.13305,0,.65277],71:[0,.68333,.08722,0,.77361],72:[0,.68333,.16389,0,.74333],73:[0,.68333,.15806,0,.38555],74:[0,.68333,.14028,0,.525],75:[0,.68333,.14528,0,.76888],76:[0,.68333,0,0,.62722],77:[0,.68333,.16389,0,.89666],78:[0,.68333,.16389,0,.74333],79:[0,.68333,.09403,0,.76666],80:[0,.68333,.10257,0,.67833],81:[.19444,.68333,.09403,0,.76666],82:[0,.68333,.03868,0,.72944],83:[0,.68333,.11972,0,.56222],84:[0,.68333,.13305,0,.71555],85:[0,.68333,.16389,0,.74333],86:[0,.68333,.18361,0,.74333],87:[0,.68333,.18361,0,.99888],88:[0,.68333,.15806,0,.74333],89:[0,.68333,.19383,0,.74333],90:[0,.68333,.14528,0,.61333],91:[.25,.75,.1875,0,.30667],93:[.25,.75,.10528,0,.30667],94:[0,.69444,.06646,0,.51111],95:[.31,.12056,.09208,0,.51111],97:[0,.43056,.07671,0,.51111],98:[0,.69444,.06312,0,.46],99:[0,.43056,.05653,0,.46],100:[0,.69444,.10333,0,.51111],101:[0,.43056,.07514,0,.46],102:[.19444,.69444,.21194,0,.30667],103:[.19444,.43056,.08847,0,.46],104:[0,.69444,.07671,0,.51111],105:[0,.65536,.1019,0,.30667],106:[.19444,.65536,.14467,0,.30667],107:[0,.69444,.10764,0,.46],108:[0,.69444,.10333,0,.25555],109:[0,.43056,.07671,0,.81777],110:[0,.43056,.07671,0,.56222],111:[0,.43056,.06312,0,.51111],112:[.19444,.43056,.06312,0,.51111],113:[.19444,.43056,.08847,0,.46],114:[0,.43056,.10764,0,.42166],115:[0,.43056,.08208,0,.40889],116:[0,.61508,.09486,0,.33222],117:[0,.43056,.07671,0,.53666],118:[0,.43056,.10764,0,.46],119:[0,.43056,.10764,0,.66444],120:[0,.43056,.12042,0,.46389],121:[.19444,.43056,.08847,0,.48555],122:[0,.43056,.12292,0,.40889],126:[.35,.31786,.11585,0,.51111],160:[0,0,0,0,.25],168:[0,.66786,.10474,0,.51111],176:[0,.69444,0,0,.83129],184:[.17014,0,0,0,.46],198:[0,.68333,.12028,0,.88277],216:[.04861,.73194,.09403,0,.76666],223:[.19444,.69444,.10514,0,.53666],230:[0,.43056,.07514,0,.71555],248:[.09722,.52778,.09194,0,.51111],338:[0,.68333,.12028,0,.98499],339:[0,.43056,.07514,0,.71555],710:[0,.69444,.06646,0,.51111],711:[0,.62847,.08295,0,.51111],713:[0,.56167,.10333,0,.51111],714:[0,.69444,.09694,0,.51111],715:[0,.69444,0,0,.51111],728:[0,.69444,.10806,0,.51111],729:[0,.66786,.11752,0,.30667],730:[0,.69444,0,0,.83129],732:[0,.66786,.11585,0,.51111],733:[0,.69444,.1225,0,.51111],915:[0,.68333,.13305,0,.62722],916:[0,.68333,0,0,.81777],920:[0,.68333,.09403,0,.76666],923:[0,.68333,0,0,.69222],926:[0,.68333,.15294,0,.66444],928:[0,.68333,.16389,0,.74333],931:[0,.68333,.12028,0,.71555],933:[0,.68333,.11111,0,.76666],934:[0,.68333,.05986,0,.71555],936:[0,.68333,.11111,0,.76666],937:[0,.68333,.10257,0,.71555],8211:[0,.43056,.09208,0,.51111],8212:[0,.43056,.09208,0,1.02222],8216:[0,.69444,.12417,0,.30667],8217:[0,.69444,.12417,0,.30667],8220:[0,.69444,.1685,0,.51444],8221:[0,.69444,.06961,0,.51444],8463:[0,.68889,0,0,.54028]},"Main-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.27778],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.77778],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.19444,.10556,0,0,.27778],45:[0,.43056,0,0,.33333],46:[0,.10556,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.64444,0,0,.5],49:[0,.64444,0,0,.5],50:[0,.64444,0,0,.5],51:[0,.64444,0,0,.5],52:[0,.64444,0,0,.5],53:[0,.64444,0,0,.5],54:[0,.64444,0,0,.5],55:[0,.64444,0,0,.5],56:[0,.64444,0,0,.5],57:[0,.64444,0,0,.5],58:[0,.43056,0,0,.27778],59:[.19444,.43056,0,0,.27778],60:[.0391,.5391,0,0,.77778],61:[-.13313,.36687,0,0,.77778],62:[.0391,.5391,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.77778],65:[0,.68333,0,0,.75],66:[0,.68333,0,0,.70834],67:[0,.68333,0,0,.72222],68:[0,.68333,0,0,.76389],69:[0,.68333,0,0,.68056],70:[0,.68333,0,0,.65278],71:[0,.68333,0,0,.78472],72:[0,.68333,0,0,.75],73:[0,.68333,0,0,.36111],74:[0,.68333,0,0,.51389],75:[0,.68333,0,0,.77778],76:[0,.68333,0,0,.625],77:[0,.68333,0,0,.91667],78:[0,.68333,0,0,.75],79:[0,.68333,0,0,.77778],80:[0,.68333,0,0,.68056],81:[.19444,.68333,0,0,.77778],82:[0,.68333,0,0,.73611],83:[0,.68333,0,0,.55556],84:[0,.68333,0,0,.72222],85:[0,.68333,0,0,.75],86:[0,.68333,.01389,0,.75],87:[0,.68333,.01389,0,1.02778],88:[0,.68333,0,0,.75],89:[0,.68333,.025,0,.75],90:[0,.68333,0,0,.61111],91:[.25,.75,0,0,.27778],92:[.25,.75,0,0,.5],93:[.25,.75,0,0,.27778],94:[0,.69444,0,0,.5],95:[.31,.12056,.02778,0,.5],97:[0,.43056,0,0,.5],98:[0,.69444,0,0,.55556],99:[0,.43056,0,0,.44445],100:[0,.69444,0,0,.55556],101:[0,.43056,0,0,.44445],102:[0,.69444,.07778,0,.30556],103:[.19444,.43056,.01389,0,.5],104:[0,.69444,0,0,.55556],105:[0,.66786,0,0,.27778],106:[.19444,.66786,0,0,.30556],107:[0,.69444,0,0,.52778],108:[0,.69444,0,0,.27778],109:[0,.43056,0,0,.83334],110:[0,.43056,0,0,.55556],111:[0,.43056,0,0,.5],112:[.19444,.43056,0,0,.55556],113:[.19444,.43056,0,0,.52778],114:[0,.43056,0,0,.39167],115:[0,.43056,0,0,.39445],116:[0,.61508,0,0,.38889],117:[0,.43056,0,0,.55556],118:[0,.43056,.01389,0,.52778],119:[0,.43056,.01389,0,.72222],120:[0,.43056,0,0,.52778],121:[.19444,.43056,.01389,0,.52778],122:[0,.43056,0,0,.44445],123:[.25,.75,0,0,.5],124:[.25,.75,0,0,.27778],125:[.25,.75,0,0,.5],126:[.35,.31786,0,0,.5],160:[0,0,0,0,.25],163:[0,.69444,0,0,.76909],167:[.19444,.69444,0,0,.44445],168:[0,.66786,0,0,.5],172:[0,.43056,0,0,.66667],176:[0,.69444,0,0,.75],177:[.08333,.58333,0,0,.77778],182:[.19444,.69444,0,0,.61111],184:[.17014,0,0,0,.44445],198:[0,.68333,0,0,.90278],215:[.08333,.58333,0,0,.77778],216:[.04861,.73194,0,0,.77778],223:[0,.69444,0,0,.5],230:[0,.43056,0,0,.72222],247:[.08333,.58333,0,0,.77778],248:[.09722,.52778,0,0,.5],305:[0,.43056,0,0,.27778],338:[0,.68333,0,0,1.01389],339:[0,.43056,0,0,.77778],567:[.19444,.43056,0,0,.30556],710:[0,.69444,0,0,.5],711:[0,.62847,0,0,.5],713:[0,.56778,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.66786,0,0,.27778],730:[0,.69444,0,0,.75],732:[0,.66786,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.68333,0,0,.625],916:[0,.68333,0,0,.83334],920:[0,.68333,0,0,.77778],923:[0,.68333,0,0,.69445],926:[0,.68333,0,0,.66667],928:[0,.68333,0,0,.75],931:[0,.68333,0,0,.72222],933:[0,.68333,0,0,.77778],934:[0,.68333,0,0,.72222],936:[0,.68333,0,0,.77778],937:[0,.68333,0,0,.72222],8211:[0,.43056,.02778,0,.5],8212:[0,.43056,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5],8224:[.19444,.69444,0,0,.44445],8225:[.19444,.69444,0,0,.44445],8230:[0,.123,0,0,1.172],8242:[0,.55556,0,0,.275],8407:[0,.71444,.15382,0,.5],8463:[0,.68889,0,0,.54028],8465:[0,.69444,0,0,.72222],8467:[0,.69444,0,.11111,.41667],8472:[.19444,.43056,0,.11111,.63646],8476:[0,.69444,0,0,.72222],8501:[0,.69444,0,0,.61111],8592:[-.13313,.36687,0,0,1],8593:[.19444,.69444,0,0,.5],8594:[-.13313,.36687,0,0,1],8595:[.19444,.69444,0,0,.5],8596:[-.13313,.36687,0,0,1],8597:[.25,.75,0,0,.5],8598:[.19444,.69444,0,0,1],8599:[.19444,.69444,0,0,1],8600:[.19444,.69444,0,0,1],8601:[.19444,.69444,0,0,1],8614:[.011,.511,0,0,1],8617:[.011,.511,0,0,1.126],8618:[.011,.511,0,0,1.126],8636:[-.13313,.36687,0,0,1],8637:[-.13313,.36687,0,0,1],8640:[-.13313,.36687,0,0,1],8641:[-.13313,.36687,0,0,1],8652:[.011,.671,0,0,1],8656:[-.13313,.36687,0,0,1],8657:[.19444,.69444,0,0,.61111],8658:[-.13313,.36687,0,0,1],8659:[.19444,.69444,0,0,.61111],8660:[-.13313,.36687,0,0,1],8661:[.25,.75,0,0,.61111],8704:[0,.69444,0,0,.55556],8706:[0,.69444,.05556,.08334,.5309],8707:[0,.69444,0,0,.55556],8709:[.05556,.75,0,0,.5],8711:[0,.68333,0,0,.83334],8712:[.0391,.5391,0,0,.66667],8715:[.0391,.5391,0,0,.66667],8722:[.08333,.58333,0,0,.77778],8723:[.08333,.58333,0,0,.77778],8725:[.25,.75,0,0,.5],8726:[.25,.75,0,0,.5],8727:[-.03472,.46528,0,0,.5],8728:[-.05555,.44445,0,0,.5],8729:[-.05555,.44445,0,0,.5],8730:[.2,.8,0,0,.83334],8733:[0,.43056,0,0,.77778],8734:[0,.43056,0,0,1],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.27778],8741:[.25,.75,0,0,.5],8743:[0,.55556,0,0,.66667],8744:[0,.55556,0,0,.66667],8745:[0,.55556,0,0,.66667],8746:[0,.55556,0,0,.66667],8747:[.19444,.69444,.11111,0,.41667],8764:[-.13313,.36687,0,0,.77778],8768:[.19444,.69444,0,0,.27778],8771:[-.03625,.46375,0,0,.77778],8773:[-.022,.589,0,0,.778],8776:[-.01688,.48312,0,0,.77778],8781:[-.03625,.46375,0,0,.77778],8784:[-.133,.673,0,0,.778],8801:[-.03625,.46375,0,0,.77778],8804:[.13597,.63597,0,0,.77778],8805:[.13597,.63597,0,0,.77778],8810:[.0391,.5391,0,0,1],8811:[.0391,.5391,0,0,1],8826:[.0391,.5391,0,0,.77778],8827:[.0391,.5391,0,0,.77778],8834:[.0391,.5391,0,0,.77778],8835:[.0391,.5391,0,0,.77778],8838:[.13597,.63597,0,0,.77778],8839:[.13597,.63597,0,0,.77778],8846:[0,.55556,0,0,.66667],8849:[.13597,.63597,0,0,.77778],8850:[.13597,.63597,0,0,.77778],8851:[0,.55556,0,0,.66667],8852:[0,.55556,0,0,.66667],8853:[.08333,.58333,0,0,.77778],8854:[.08333,.58333,0,0,.77778],8855:[.08333,.58333,0,0,.77778],8856:[.08333,.58333,0,0,.77778],8857:[.08333,.58333,0,0,.77778],8866:[0,.69444,0,0,.61111],8867:[0,.69444,0,0,.61111],8868:[0,.69444,0,0,.77778],8869:[0,.69444,0,0,.77778],8872:[.249,.75,0,0,.867],8900:[-.05555,.44445,0,0,.5],8901:[-.05555,.44445,0,0,.27778],8902:[-.03472,.46528,0,0,.5],8904:[.005,.505,0,0,.9],8942:[.03,.903,0,0,.278],8943:[-.19,.313,0,0,1.172],8945:[-.1,.823,0,0,1.282],8968:[.25,.75,0,0,.44445],8969:[.25,.75,0,0,.44445],8970:[.25,.75,0,0,.44445],8971:[.25,.75,0,0,.44445],8994:[-.14236,.35764,0,0,1],8995:[-.14236,.35764,0,0,1],9136:[.244,.744,0,0,.412],9137:[.244,.745,0,0,.412],9651:[.19444,.69444,0,0,.88889],9657:[-.03472,.46528,0,0,.5],9661:[.19444,.69444,0,0,.88889],9667:[-.03472,.46528,0,0,.5],9711:[.19444,.69444,0,0,1],9824:[.12963,.69444,0,0,.77778],9825:[.12963,.69444,0,0,.77778],9826:[.12963,.69444,0,0,.77778],9827:[.12963,.69444,0,0,.77778],9837:[0,.75,0,0,.38889],9838:[.19444,.69444,0,0,.38889],9839:[.19444,.69444,0,0,.38889],10216:[.25,.75,0,0,.38889],10217:[.25,.75,0,0,.38889],10222:[.244,.744,0,0,.412],10223:[.244,.745,0,0,.412],10229:[.011,.511,0,0,1.609],10230:[.011,.511,0,0,1.638],10231:[.011,.511,0,0,1.859],10232:[.024,.525,0,0,1.609],10233:[.024,.525,0,0,1.638],10234:[.024,.525,0,0,1.858],10236:[.011,.511,0,0,1.638],10815:[0,.68333,0,0,.75],10927:[.13597,.63597,0,0,.77778],10928:[.13597,.63597,0,0,.77778],57376:[.19444,.69444,0,0,0]},"Math-BoldItalic":{32:[0,0,0,0,.25],48:[0,.44444,0,0,.575],49:[0,.44444,0,0,.575],50:[0,.44444,0,0,.575],51:[.19444,.44444,0,0,.575],52:[.19444,.44444,0,0,.575],53:[.19444,.44444,0,0,.575],54:[0,.64444,0,0,.575],55:[.19444,.44444,0,0,.575],56:[0,.64444,0,0,.575],57:[.19444,.44444,0,0,.575],65:[0,.68611,0,0,.86944],66:[0,.68611,.04835,0,.8664],67:[0,.68611,.06979,0,.81694],68:[0,.68611,.03194,0,.93812],69:[0,.68611,.05451,0,.81007],70:[0,.68611,.15972,0,.68889],71:[0,.68611,0,0,.88673],72:[0,.68611,.08229,0,.98229],73:[0,.68611,.07778,0,.51111],74:[0,.68611,.10069,0,.63125],75:[0,.68611,.06979,0,.97118],76:[0,.68611,0,0,.75555],77:[0,.68611,.11424,0,1.14201],78:[0,.68611,.11424,0,.95034],79:[0,.68611,.03194,0,.83666],80:[0,.68611,.15972,0,.72309],81:[.19444,.68611,0,0,.86861],82:[0,.68611,.00421,0,.87235],83:[0,.68611,.05382,0,.69271],84:[0,.68611,.15972,0,.63663],85:[0,.68611,.11424,0,.80027],86:[0,.68611,.25555,0,.67778],87:[0,.68611,.15972,0,1.09305],88:[0,.68611,.07778,0,.94722],89:[0,.68611,.25555,0,.67458],90:[0,.68611,.06979,0,.77257],97:[0,.44444,0,0,.63287],98:[0,.69444,0,0,.52083],99:[0,.44444,0,0,.51342],100:[0,.69444,0,0,.60972],101:[0,.44444,0,0,.55361],102:[.19444,.69444,.11042,0,.56806],103:[.19444,.44444,.03704,0,.5449],104:[0,.69444,0,0,.66759],105:[0,.69326,0,0,.4048],106:[.19444,.69326,.0622,0,.47083],107:[0,.69444,.01852,0,.6037],108:[0,.69444,.0088,0,.34815],109:[0,.44444,0,0,1.0324],110:[0,.44444,0,0,.71296],111:[0,.44444,0,0,.58472],112:[.19444,.44444,0,0,.60092],113:[.19444,.44444,.03704,0,.54213],114:[0,.44444,.03194,0,.5287],115:[0,.44444,0,0,.53125],116:[0,.63492,0,0,.41528],117:[0,.44444,0,0,.68102],118:[0,.44444,.03704,0,.56666],119:[0,.44444,.02778,0,.83148],120:[0,.44444,0,0,.65903],121:[.19444,.44444,.03704,0,.59028],122:[0,.44444,.04213,0,.55509],160:[0,0,0,0,.25],915:[0,.68611,.15972,0,.65694],916:[0,.68611,0,0,.95833],920:[0,.68611,.03194,0,.86722],923:[0,.68611,0,0,.80555],926:[0,.68611,.07458,0,.84125],928:[0,.68611,.08229,0,.98229],931:[0,.68611,.05451,0,.88507],933:[0,.68611,.15972,0,.67083],934:[0,.68611,0,0,.76666],936:[0,.68611,.11653,0,.71402],937:[0,.68611,.04835,0,.8789],945:[0,.44444,0,0,.76064],946:[.19444,.69444,.03403,0,.65972],947:[.19444,.44444,.06389,0,.59003],948:[0,.69444,.03819,0,.52222],949:[0,.44444,0,0,.52882],950:[.19444,.69444,.06215,0,.50833],951:[.19444,.44444,.03704,0,.6],952:[0,.69444,.03194,0,.5618],953:[0,.44444,0,0,.41204],954:[0,.44444,0,0,.66759],955:[0,.69444,0,0,.67083],956:[.19444,.44444,0,0,.70787],957:[0,.44444,.06898,0,.57685],958:[.19444,.69444,.03021,0,.50833],959:[0,.44444,0,0,.58472],960:[0,.44444,.03704,0,.68241],961:[.19444,.44444,0,0,.6118],962:[.09722,.44444,.07917,0,.42361],963:[0,.44444,.03704,0,.68588],964:[0,.44444,.13472,0,.52083],965:[0,.44444,.03704,0,.63055],966:[.19444,.44444,0,0,.74722],967:[.19444,.44444,0,0,.71805],968:[.19444,.69444,.03704,0,.75833],969:[0,.44444,.03704,0,.71782],977:[0,.69444,0,0,.69155],981:[.19444,.69444,0,0,.7125],982:[0,.44444,.03194,0,.975],1009:[.19444,.44444,0,0,.6118],1013:[0,.44444,0,0,.48333],57649:[0,.44444,0,0,.39352],57911:[.19444,.44444,0,0,.43889]},"Math-Italic":{32:[0,0,0,0,.25],48:[0,.43056,0,0,.5],49:[0,.43056,0,0,.5],50:[0,.43056,0,0,.5],51:[.19444,.43056,0,0,.5],52:[.19444,.43056,0,0,.5],53:[.19444,.43056,0,0,.5],54:[0,.64444,0,0,.5],55:[.19444,.43056,0,0,.5],56:[0,.64444,0,0,.5],57:[.19444,.43056,0,0,.5],65:[0,.68333,0,.13889,.75],66:[0,.68333,.05017,.08334,.75851],67:[0,.68333,.07153,.08334,.71472],68:[0,.68333,.02778,.05556,.82792],69:[0,.68333,.05764,.08334,.7382],70:[0,.68333,.13889,.08334,.64306],71:[0,.68333,0,.08334,.78625],72:[0,.68333,.08125,.05556,.83125],73:[0,.68333,.07847,.11111,.43958],74:[0,.68333,.09618,.16667,.55451],75:[0,.68333,.07153,.05556,.84931],76:[0,.68333,0,.02778,.68056],77:[0,.68333,.10903,.08334,.97014],78:[0,.68333,.10903,.08334,.80347],79:[0,.68333,.02778,.08334,.76278],80:[0,.68333,.13889,.08334,.64201],81:[.19444,.68333,0,.08334,.79056],82:[0,.68333,.00773,.08334,.75929],83:[0,.68333,.05764,.08334,.6132],84:[0,.68333,.13889,.08334,.58438],85:[0,.68333,.10903,.02778,.68278],86:[0,.68333,.22222,0,.58333],87:[0,.68333,.13889,0,.94445],88:[0,.68333,.07847,.08334,.82847],89:[0,.68333,.22222,0,.58056],90:[0,.68333,.07153,.08334,.68264],97:[0,.43056,0,0,.52859],98:[0,.69444,0,0,.42917],99:[0,.43056,0,.05556,.43276],100:[0,.69444,0,.16667,.52049],101:[0,.43056,0,.05556,.46563],102:[.19444,.69444,.10764,.16667,.48959],103:[.19444,.43056,.03588,.02778,.47697],104:[0,.69444,0,0,.57616],105:[0,.65952,0,0,.34451],106:[.19444,.65952,.05724,0,.41181],107:[0,.69444,.03148,0,.5206],108:[0,.69444,.01968,.08334,.29838],109:[0,.43056,0,0,.87801],110:[0,.43056,0,0,.60023],111:[0,.43056,0,.05556,.48472],112:[.19444,.43056,0,.08334,.50313],113:[.19444,.43056,.03588,.08334,.44641],114:[0,.43056,.02778,.05556,.45116],115:[0,.43056,0,.05556,.46875],116:[0,.61508,0,.08334,.36111],117:[0,.43056,0,.02778,.57246],118:[0,.43056,.03588,.02778,.48472],119:[0,.43056,.02691,.08334,.71592],120:[0,.43056,0,.02778,.57153],121:[.19444,.43056,.03588,.05556,.49028],122:[0,.43056,.04398,.05556,.46505],160:[0,0,0,0,.25],915:[0,.68333,.13889,.08334,.61528],916:[0,.68333,0,.16667,.83334],920:[0,.68333,.02778,.08334,.76278],923:[0,.68333,0,.16667,.69445],926:[0,.68333,.07569,.08334,.74236],928:[0,.68333,.08125,.05556,.83125],931:[0,.68333,.05764,.08334,.77986],933:[0,.68333,.13889,.05556,.58333],934:[0,.68333,0,.08334,.66667],936:[0,.68333,.11,.05556,.61222],937:[0,.68333,.05017,.08334,.7724],945:[0,.43056,.0037,.02778,.6397],946:[.19444,.69444,.05278,.08334,.56563],947:[.19444,.43056,.05556,0,.51773],948:[0,.69444,.03785,.05556,.44444],949:[0,.43056,0,.08334,.46632],950:[.19444,.69444,.07378,.08334,.4375],951:[.19444,.43056,.03588,.05556,.49653],952:[0,.69444,.02778,.08334,.46944],953:[0,.43056,0,.05556,.35394],954:[0,.43056,0,0,.57616],955:[0,.69444,0,0,.58334],956:[.19444,.43056,0,.02778,.60255],957:[0,.43056,.06366,.02778,.49398],958:[.19444,.69444,.04601,.11111,.4375],959:[0,.43056,0,.05556,.48472],960:[0,.43056,.03588,0,.57003],961:[.19444,.43056,0,.08334,.51702],962:[.09722,.43056,.07986,.08334,.36285],963:[0,.43056,.03588,0,.57141],964:[0,.43056,.1132,.02778,.43715],965:[0,.43056,.03588,.02778,.54028],966:[.19444,.43056,0,.08334,.65417],967:[.19444,.43056,0,.05556,.62569],968:[.19444,.69444,.03588,.11111,.65139],969:[0,.43056,.03588,0,.62245],977:[0,.69444,0,.08334,.59144],981:[.19444,.69444,0,.08334,.59583],982:[0,.43056,.02778,0,.82813],1009:[.19444,.43056,0,.08334,.51702],1013:[0,.43056,0,.05556,.4059],57649:[0,.43056,0,.02778,.32246],57911:[.19444,.43056,0,.08334,.38403]},"SansSerif-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.36667],34:[0,.69444,0,0,.55834],35:[.19444,.69444,0,0,.91667],36:[.05556,.75,0,0,.55],37:[.05556,.75,0,0,1.02912],38:[0,.69444,0,0,.83056],39:[0,.69444,0,0,.30556],40:[.25,.75,0,0,.42778],41:[.25,.75,0,0,.42778],42:[0,.75,0,0,.55],43:[.11667,.61667,0,0,.85556],44:[.10556,.13056,0,0,.30556],45:[0,.45833,0,0,.36667],46:[0,.13056,0,0,.30556],47:[.25,.75,0,0,.55],48:[0,.69444,0,0,.55],49:[0,.69444,0,0,.55],50:[0,.69444,0,0,.55],51:[0,.69444,0,0,.55],52:[0,.69444,0,0,.55],53:[0,.69444,0,0,.55],54:[0,.69444,0,0,.55],55:[0,.69444,0,0,.55],56:[0,.69444,0,0,.55],57:[0,.69444,0,0,.55],58:[0,.45833,0,0,.30556],59:[.10556,.45833,0,0,.30556],61:[-.09375,.40625,0,0,.85556],63:[0,.69444,0,0,.51945],64:[0,.69444,0,0,.73334],65:[0,.69444,0,0,.73334],66:[0,.69444,0,0,.73334],67:[0,.69444,0,0,.70278],68:[0,.69444,0,0,.79445],69:[0,.69444,0,0,.64167],70:[0,.69444,0,0,.61111],71:[0,.69444,0,0,.73334],72:[0,.69444,0,0,.79445],73:[0,.69444,0,0,.33056],74:[0,.69444,0,0,.51945],75:[0,.69444,0,0,.76389],76:[0,.69444,0,0,.58056],77:[0,.69444,0,0,.97778],78:[0,.69444,0,0,.79445],79:[0,.69444,0,0,.79445],80:[0,.69444,0,0,.70278],81:[.10556,.69444,0,0,.79445],82:[0,.69444,0,0,.70278],83:[0,.69444,0,0,.61111],84:[0,.69444,0,0,.73334],85:[0,.69444,0,0,.76389],86:[0,.69444,.01528,0,.73334],87:[0,.69444,.01528,0,1.03889],88:[0,.69444,0,0,.73334],89:[0,.69444,.0275,0,.73334],90:[0,.69444,0,0,.67223],91:[.25,.75,0,0,.34306],93:[.25,.75,0,0,.34306],94:[0,.69444,0,0,.55],95:[.35,.10833,.03056,0,.55],97:[0,.45833,0,0,.525],98:[0,.69444,0,0,.56111],99:[0,.45833,0,0,.48889],100:[0,.69444,0,0,.56111],101:[0,.45833,0,0,.51111],102:[0,.69444,.07639,0,.33611],103:[.19444,.45833,.01528,0,.55],104:[0,.69444,0,0,.56111],105:[0,.69444,0,0,.25556],106:[.19444,.69444,0,0,.28611],107:[0,.69444,0,0,.53056],108:[0,.69444,0,0,.25556],109:[0,.45833,0,0,.86667],110:[0,.45833,0,0,.56111],111:[0,.45833,0,0,.55],112:[.19444,.45833,0,0,.56111],113:[.19444,.45833,0,0,.56111],114:[0,.45833,.01528,0,.37222],115:[0,.45833,0,0,.42167],116:[0,.58929,0,0,.40417],117:[0,.45833,0,0,.56111],118:[0,.45833,.01528,0,.5],119:[0,.45833,.01528,0,.74445],120:[0,.45833,0,0,.5],121:[.19444,.45833,.01528,0,.5],122:[0,.45833,0,0,.47639],126:[.35,.34444,0,0,.55],160:[0,0,0,0,.25],168:[0,.69444,0,0,.55],176:[0,.69444,0,0,.73334],180:[0,.69444,0,0,.55],184:[.17014,0,0,0,.48889],305:[0,.45833,0,0,.25556],567:[.19444,.45833,0,0,.28611],710:[0,.69444,0,0,.55],711:[0,.63542,0,0,.55],713:[0,.63778,0,0,.55],728:[0,.69444,0,0,.55],729:[0,.69444,0,0,.30556],730:[0,.69444,0,0,.73334],732:[0,.69444,0,0,.55],733:[0,.69444,0,0,.55],915:[0,.69444,0,0,.58056],916:[0,.69444,0,0,.91667],920:[0,.69444,0,0,.85556],923:[0,.69444,0,0,.67223],926:[0,.69444,0,0,.73334],928:[0,.69444,0,0,.79445],931:[0,.69444,0,0,.79445],933:[0,.69444,0,0,.85556],934:[0,.69444,0,0,.79445],936:[0,.69444,0,0,.85556],937:[0,.69444,0,0,.79445],8211:[0,.45833,.03056,0,.55],8212:[0,.45833,.03056,0,1.10001],8216:[0,.69444,0,0,.30556],8217:[0,.69444,0,0,.30556],8220:[0,.69444,0,0,.55834],8221:[0,.69444,0,0,.55834]},"SansSerif-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.05733,0,.31945],34:[0,.69444,.00316,0,.5],35:[.19444,.69444,.05087,0,.83334],36:[.05556,.75,.11156,0,.5],37:[.05556,.75,.03126,0,.83334],38:[0,.69444,.03058,0,.75834],39:[0,.69444,.07816,0,.27778],40:[.25,.75,.13164,0,.38889],41:[.25,.75,.02536,0,.38889],42:[0,.75,.11775,0,.5],43:[.08333,.58333,.02536,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,.01946,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,.13164,0,.5],48:[0,.65556,.11156,0,.5],49:[0,.65556,.11156,0,.5],50:[0,.65556,.11156,0,.5],51:[0,.65556,.11156,0,.5],52:[0,.65556,.11156,0,.5],53:[0,.65556,.11156,0,.5],54:[0,.65556,.11156,0,.5],55:[0,.65556,.11156,0,.5],56:[0,.65556,.11156,0,.5],57:[0,.65556,.11156,0,.5],58:[0,.44444,.02502,0,.27778],59:[.125,.44444,.02502,0,.27778],61:[-.13,.37,.05087,0,.77778],63:[0,.69444,.11809,0,.47222],64:[0,.69444,.07555,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,.08293,0,.66667],67:[0,.69444,.11983,0,.63889],68:[0,.69444,.07555,0,.72223],69:[0,.69444,.11983,0,.59722],70:[0,.69444,.13372,0,.56945],71:[0,.69444,.11983,0,.66667],72:[0,.69444,.08094,0,.70834],73:[0,.69444,.13372,0,.27778],74:[0,.69444,.08094,0,.47222],75:[0,.69444,.11983,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,.08094,0,.875],78:[0,.69444,.08094,0,.70834],79:[0,.69444,.07555,0,.73611],80:[0,.69444,.08293,0,.63889],81:[.125,.69444,.07555,0,.73611],82:[0,.69444,.08293,0,.64584],83:[0,.69444,.09205,0,.55556],84:[0,.69444,.13372,0,.68056],85:[0,.69444,.08094,0,.6875],86:[0,.69444,.1615,0,.66667],87:[0,.69444,.1615,0,.94445],88:[0,.69444,.13372,0,.66667],89:[0,.69444,.17261,0,.66667],90:[0,.69444,.11983,0,.61111],91:[.25,.75,.15942,0,.28889],93:[.25,.75,.08719,0,.28889],94:[0,.69444,.0799,0,.5],95:[.35,.09444,.08616,0,.5],97:[0,.44444,.00981,0,.48056],98:[0,.69444,.03057,0,.51667],99:[0,.44444,.08336,0,.44445],100:[0,.69444,.09483,0,.51667],101:[0,.44444,.06778,0,.44445],102:[0,.69444,.21705,0,.30556],103:[.19444,.44444,.10836,0,.5],104:[0,.69444,.01778,0,.51667],105:[0,.67937,.09718,0,.23889],106:[.19444,.67937,.09162,0,.26667],107:[0,.69444,.08336,0,.48889],108:[0,.69444,.09483,0,.23889],109:[0,.44444,.01778,0,.79445],110:[0,.44444,.01778,0,.51667],111:[0,.44444,.06613,0,.5],112:[.19444,.44444,.0389,0,.51667],113:[.19444,.44444,.04169,0,.51667],114:[0,.44444,.10836,0,.34167],115:[0,.44444,.0778,0,.38333],116:[0,.57143,.07225,0,.36111],117:[0,.44444,.04169,0,.51667],118:[0,.44444,.10836,0,.46111],119:[0,.44444,.10836,0,.68334],120:[0,.44444,.09169,0,.46111],121:[.19444,.44444,.10836,0,.46111],122:[0,.44444,.08752,0,.43472],126:[.35,.32659,.08826,0,.5],160:[0,0,0,0,.25],168:[0,.67937,.06385,0,.5],176:[0,.69444,0,0,.73752],184:[.17014,0,0,0,.44445],305:[0,.44444,.04169,0,.23889],567:[.19444,.44444,.04169,0,.26667],710:[0,.69444,.0799,0,.5],711:[0,.63194,.08432,0,.5],713:[0,.60889,.08776,0,.5],714:[0,.69444,.09205,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,.09483,0,.5],729:[0,.67937,.07774,0,.27778],730:[0,.69444,0,0,.73752],732:[0,.67659,.08826,0,.5],733:[0,.69444,.09205,0,.5],915:[0,.69444,.13372,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,.07555,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,.12816,0,.66667],928:[0,.69444,.08094,0,.70834],931:[0,.69444,.11983,0,.72222],933:[0,.69444,.09031,0,.77778],934:[0,.69444,.04603,0,.72222],936:[0,.69444,.09031,0,.77778],937:[0,.69444,.08293,0,.72222],8211:[0,.44444,.08616,0,.5],8212:[0,.44444,.08616,0,1],8216:[0,.69444,.07816,0,.27778],8217:[0,.69444,.07816,0,.27778],8220:[0,.69444,.14205,0,.5],8221:[0,.69444,.00316,0,.5]},"SansSerif-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.31945],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.75834],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,0,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.65556,0,0,.5],49:[0,.65556,0,0,.5],50:[0,.65556,0,0,.5],51:[0,.65556,0,0,.5],52:[0,.65556,0,0,.5],53:[0,.65556,0,0,.5],54:[0,.65556,0,0,.5],55:[0,.65556,0,0,.5],56:[0,.65556,0,0,.5],57:[0,.65556,0,0,.5],58:[0,.44444,0,0,.27778],59:[.125,.44444,0,0,.27778],61:[-.13,.37,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,0,0,.66667],67:[0,.69444,0,0,.63889],68:[0,.69444,0,0,.72223],69:[0,.69444,0,0,.59722],70:[0,.69444,0,0,.56945],71:[0,.69444,0,0,.66667],72:[0,.69444,0,0,.70834],73:[0,.69444,0,0,.27778],74:[0,.69444,0,0,.47222],75:[0,.69444,0,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,0,0,.875],78:[0,.69444,0,0,.70834],79:[0,.69444,0,0,.73611],80:[0,.69444,0,0,.63889],81:[.125,.69444,0,0,.73611],82:[0,.69444,0,0,.64584],83:[0,.69444,0,0,.55556],84:[0,.69444,0,0,.68056],85:[0,.69444,0,0,.6875],86:[0,.69444,.01389,0,.66667],87:[0,.69444,.01389,0,.94445],88:[0,.69444,0,0,.66667],89:[0,.69444,.025,0,.66667],90:[0,.69444,0,0,.61111],91:[.25,.75,0,0,.28889],93:[.25,.75,0,0,.28889],94:[0,.69444,0,0,.5],95:[.35,.09444,.02778,0,.5],97:[0,.44444,0,0,.48056],98:[0,.69444,0,0,.51667],99:[0,.44444,0,0,.44445],100:[0,.69444,0,0,.51667],101:[0,.44444,0,0,.44445],102:[0,.69444,.06944,0,.30556],103:[.19444,.44444,.01389,0,.5],104:[0,.69444,0,0,.51667],105:[0,.67937,0,0,.23889],106:[.19444,.67937,0,0,.26667],107:[0,.69444,0,0,.48889],108:[0,.69444,0,0,.23889],109:[0,.44444,0,0,.79445],110:[0,.44444,0,0,.51667],111:[0,.44444,0,0,.5],112:[.19444,.44444,0,0,.51667],113:[.19444,.44444,0,0,.51667],114:[0,.44444,.01389,0,.34167],115:[0,.44444,0,0,.38333],116:[0,.57143,0,0,.36111],117:[0,.44444,0,0,.51667],118:[0,.44444,.01389,0,.46111],119:[0,.44444,.01389,0,.68334],120:[0,.44444,0,0,.46111],121:[.19444,.44444,.01389,0,.46111],122:[0,.44444,0,0,.43472],126:[.35,.32659,0,0,.5],160:[0,0,0,0,.25],168:[0,.67937,0,0,.5],176:[0,.69444,0,0,.66667],184:[.17014,0,0,0,.44445],305:[0,.44444,0,0,.23889],567:[.19444,.44444,0,0,.26667],710:[0,.69444,0,0,.5],711:[0,.63194,0,0,.5],713:[0,.60889,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.67937,0,0,.27778],730:[0,.69444,0,0,.66667],732:[0,.67659,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.69444,0,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,0,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,0,0,.66667],928:[0,.69444,0,0,.70834],931:[0,.69444,0,0,.72222],933:[0,.69444,0,0,.77778],934:[0,.69444,0,0,.72222],936:[0,.69444,0,0,.77778],937:[0,.69444,0,0,.72222],8211:[0,.44444,.02778,0,.5],8212:[0,.44444,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5]},"Script-Regular":{32:[0,0,0,0,.25],65:[0,.7,.22925,0,.80253],66:[0,.7,.04087,0,.90757],67:[0,.7,.1689,0,.66619],68:[0,.7,.09371,0,.77443],69:[0,.7,.18583,0,.56162],70:[0,.7,.13634,0,.89544],71:[0,.7,.17322,0,.60961],72:[0,.7,.29694,0,.96919],73:[0,.7,.19189,0,.80907],74:[.27778,.7,.19189,0,1.05159],75:[0,.7,.31259,0,.91364],76:[0,.7,.19189,0,.87373],77:[0,.7,.15981,0,1.08031],78:[0,.7,.3525,0,.9015],79:[0,.7,.08078,0,.73787],80:[0,.7,.08078,0,1.01262],81:[0,.7,.03305,0,.88282],82:[0,.7,.06259,0,.85],83:[0,.7,.19189,0,.86767],84:[0,.7,.29087,0,.74697],85:[0,.7,.25815,0,.79996],86:[0,.7,.27523,0,.62204],87:[0,.7,.27523,0,.80532],88:[0,.7,.26006,0,.94445],89:[0,.7,.2939,0,.70961],90:[0,.7,.24037,0,.8212],160:[0,0,0,0,.25]},"Size1-Regular":{32:[0,0,0,0,.25],40:[.35001,.85,0,0,.45834],41:[.35001,.85,0,0,.45834],47:[.35001,.85,0,0,.57778],91:[.35001,.85,0,0,.41667],92:[.35001,.85,0,0,.57778],93:[.35001,.85,0,0,.41667],123:[.35001,.85,0,0,.58334],125:[.35001,.85,0,0,.58334],160:[0,0,0,0,.25],710:[0,.72222,0,0,.55556],732:[0,.72222,0,0,.55556],770:[0,.72222,0,0,.55556],771:[0,.72222,0,0,.55556],8214:[-99e-5,.601,0,0,.77778],8593:[1e-5,.6,0,0,.66667],8595:[1e-5,.6,0,0,.66667],8657:[1e-5,.6,0,0,.77778],8659:[1e-5,.6,0,0,.77778],8719:[.25001,.75,0,0,.94445],8720:[.25001,.75,0,0,.94445],8721:[.25001,.75,0,0,1.05556],8730:[.35001,.85,0,0,1],8739:[-.00599,.606,0,0,.33333],8741:[-.00599,.606,0,0,.55556],8747:[.30612,.805,.19445,0,.47222],8748:[.306,.805,.19445,0,.47222],8749:[.306,.805,.19445,0,.47222],8750:[.30612,.805,.19445,0,.47222],8896:[.25001,.75,0,0,.83334],8897:[.25001,.75,0,0,.83334],8898:[.25001,.75,0,0,.83334],8899:[.25001,.75,0,0,.83334],8968:[.35001,.85,0,0,.47222],8969:[.35001,.85,0,0,.47222],8970:[.35001,.85,0,0,.47222],8971:[.35001,.85,0,0,.47222],9168:[-99e-5,.601,0,0,.66667],10216:[.35001,.85,0,0,.47222],10217:[.35001,.85,0,0,.47222],10752:[.25001,.75,0,0,1.11111],10753:[.25001,.75,0,0,1.11111],10754:[.25001,.75,0,0,1.11111],10756:[.25001,.75,0,0,.83334],10758:[.25001,.75,0,0,.83334]},"Size2-Regular":{32:[0,0,0,0,.25],40:[.65002,1.15,0,0,.59722],41:[.65002,1.15,0,0,.59722],47:[.65002,1.15,0,0,.81111],91:[.65002,1.15,0,0,.47222],92:[.65002,1.15,0,0,.81111],93:[.65002,1.15,0,0,.47222],123:[.65002,1.15,0,0,.66667],125:[.65002,1.15,0,0,.66667],160:[0,0,0,0,.25],710:[0,.75,0,0,1],732:[0,.75,0,0,1],770:[0,.75,0,0,1],771:[0,.75,0,0,1],8719:[.55001,1.05,0,0,1.27778],8720:[.55001,1.05,0,0,1.27778],8721:[.55001,1.05,0,0,1.44445],8730:[.65002,1.15,0,0,1],8747:[.86225,1.36,.44445,0,.55556],8748:[.862,1.36,.44445,0,.55556],8749:[.862,1.36,.44445,0,.55556],8750:[.86225,1.36,.44445,0,.55556],8896:[.55001,1.05,0,0,1.11111],8897:[.55001,1.05,0,0,1.11111],8898:[.55001,1.05,0,0,1.11111],8899:[.55001,1.05,0,0,1.11111],8968:[.65002,1.15,0,0,.52778],8969:[.65002,1.15,0,0,.52778],8970:[.65002,1.15,0,0,.52778],8971:[.65002,1.15,0,0,.52778],10216:[.65002,1.15,0,0,.61111],10217:[.65002,1.15,0,0,.61111],10752:[.55001,1.05,0,0,1.51112],10753:[.55001,1.05,0,0,1.51112],10754:[.55001,1.05,0,0,1.51112],10756:[.55001,1.05,0,0,1.11111],10758:[.55001,1.05,0,0,1.11111]},"Size3-Regular":{32:[0,0,0,0,.25],40:[.95003,1.45,0,0,.73611],41:[.95003,1.45,0,0,.73611],47:[.95003,1.45,0,0,1.04445],91:[.95003,1.45,0,0,.52778],92:[.95003,1.45,0,0,1.04445],93:[.95003,1.45,0,0,.52778],123:[.95003,1.45,0,0,.75],125:[.95003,1.45,0,0,.75],160:[0,0,0,0,.25],710:[0,.75,0,0,1.44445],732:[0,.75,0,0,1.44445],770:[0,.75,0,0,1.44445],771:[0,.75,0,0,1.44445],8730:[.95003,1.45,0,0,1],8968:[.95003,1.45,0,0,.58334],8969:[.95003,1.45,0,0,.58334],8970:[.95003,1.45,0,0,.58334],8971:[.95003,1.45,0,0,.58334],10216:[.95003,1.45,0,0,.75],10217:[.95003,1.45,0,0,.75]},"Size4-Regular":{32:[0,0,0,0,.25],40:[1.25003,1.75,0,0,.79167],41:[1.25003,1.75,0,0,.79167],47:[1.25003,1.75,0,0,1.27778],91:[1.25003,1.75,0,0,.58334],92:[1.25003,1.75,0,0,1.27778],93:[1.25003,1.75,0,0,.58334],123:[1.25003,1.75,0,0,.80556],125:[1.25003,1.75,0,0,.80556],160:[0,0,0,0,.25],710:[0,.825,0,0,1.8889],732:[0,.825,0,0,1.8889],770:[0,.825,0,0,1.8889],771:[0,.825,0,0,1.8889],8730:[1.25003,1.75,0,0,1],8968:[1.25003,1.75,0,0,.63889],8969:[1.25003,1.75,0,0,.63889],8970:[1.25003,1.75,0,0,.63889],8971:[1.25003,1.75,0,0,.63889],9115:[.64502,1.155,0,0,.875],9116:[1e-5,.6,0,0,.875],9117:[.64502,1.155,0,0,.875],9118:[.64502,1.155,0,0,.875],9119:[1e-5,.6,0,0,.875],9120:[.64502,1.155,0,0,.875],9121:[.64502,1.155,0,0,.66667],9122:[-99e-5,.601,0,0,.66667],9123:[.64502,1.155,0,0,.66667],9124:[.64502,1.155,0,0,.66667],9125:[-99e-5,.601,0,0,.66667],9126:[.64502,1.155,0,0,.66667],9127:[1e-5,.9,0,0,.88889],9128:[.65002,1.15,0,0,.88889],9129:[.90001,0,0,0,.88889],9130:[0,.3,0,0,.88889],9131:[1e-5,.9,0,0,.88889],9132:[.65002,1.15,0,0,.88889],9133:[.90001,0,0,0,.88889],9143:[.88502,.915,0,0,1.05556],10216:[1.25003,1.75,0,0,.80556],10217:[1.25003,1.75,0,0,.80556],57344:[-.00499,.605,0,0,1.05556],57345:[-.00499,.605,0,0,1.05556],57680:[0,.12,0,0,.45],57681:[0,.12,0,0,.45],57682:[0,.12,0,0,.45],57683:[0,.12,0,0,.45]},"Typewriter-Regular":{32:[0,0,0,0,.525],33:[0,.61111,0,0,.525],34:[0,.61111,0,0,.525],35:[0,.61111,0,0,.525],36:[.08333,.69444,0,0,.525],37:[.08333,.69444,0,0,.525],38:[0,.61111,0,0,.525],39:[0,.61111,0,0,.525],40:[.08333,.69444,0,0,.525],41:[.08333,.69444,0,0,.525],42:[0,.52083,0,0,.525],43:[-.08056,.53055,0,0,.525],44:[.13889,.125,0,0,.525],45:[-.08056,.53055,0,0,.525],46:[0,.125,0,0,.525],47:[.08333,.69444,0,0,.525],48:[0,.61111,0,0,.525],49:[0,.61111,0,0,.525],50:[0,.61111,0,0,.525],51:[0,.61111,0,0,.525],52:[0,.61111,0,0,.525],53:[0,.61111,0,0,.525],54:[0,.61111,0,0,.525],55:[0,.61111,0,0,.525],56:[0,.61111,0,0,.525],57:[0,.61111,0,0,.525],58:[0,.43056,0,0,.525],59:[.13889,.43056,0,0,.525],60:[-.05556,.55556,0,0,.525],61:[-.19549,.41562,0,0,.525],62:[-.05556,.55556,0,0,.525],63:[0,.61111,0,0,.525],64:[0,.61111,0,0,.525],65:[0,.61111,0,0,.525],66:[0,.61111,0,0,.525],67:[0,.61111,0,0,.525],68:[0,.61111,0,0,.525],69:[0,.61111,0,0,.525],70:[0,.61111,0,0,.525],71:[0,.61111,0,0,.525],72:[0,.61111,0,0,.525],73:[0,.61111,0,0,.525],74:[0,.61111,0,0,.525],75:[0,.61111,0,0,.525],76:[0,.61111,0,0,.525],77:[0,.61111,0,0,.525],78:[0,.61111,0,0,.525],79:[0,.61111,0,0,.525],80:[0,.61111,0,0,.525],81:[.13889,.61111,0,0,.525],82:[0,.61111,0,0,.525],83:[0,.61111,0,0,.525],84:[0,.61111,0,0,.525],85:[0,.61111,0,0,.525],86:[0,.61111,0,0,.525],87:[0,.61111,0,0,.525],88:[0,.61111,0,0,.525],89:[0,.61111,0,0,.525],90:[0,.61111,0,0,.525],91:[.08333,.69444,0,0,.525],92:[.08333,.69444,0,0,.525],93:[.08333,.69444,0,0,.525],94:[0,.61111,0,0,.525],95:[.09514,0,0,0,.525],96:[0,.61111,0,0,.525],97:[0,.43056,0,0,.525],98:[0,.61111,0,0,.525],99:[0,.43056,0,0,.525],100:[0,.61111,0,0,.525],101:[0,.43056,0,0,.525],102:[0,.61111,0,0,.525],103:[.22222,.43056,0,0,.525],104:[0,.61111,0,0,.525],105:[0,.61111,0,0,.525],106:[.22222,.61111,0,0,.525],107:[0,.61111,0,0,.525],108:[0,.61111,0,0,.525],109:[0,.43056,0,0,.525],110:[0,.43056,0,0,.525],111:[0,.43056,0,0,.525],112:[.22222,.43056,0,0,.525],113:[.22222,.43056,0,0,.525],114:[0,.43056,0,0,.525],115:[0,.43056,0,0,.525],116:[0,.55358,0,0,.525],117:[0,.43056,0,0,.525],118:[0,.43056,0,0,.525],119:[0,.43056,0,0,.525],120:[0,.43056,0,0,.525],121:[.22222,.43056,0,0,.525],122:[0,.43056,0,0,.525],123:[.08333,.69444,0,0,.525],124:[.08333,.69444,0,0,.525],125:[.08333,.69444,0,0,.525],126:[0,.61111,0,0,.525],127:[0,.61111,0,0,.525],160:[0,0,0,0,.525],176:[0,.61111,0,0,.525],184:[.19445,0,0,0,.525],305:[0,.43056,0,0,.525],567:[.22222,.43056,0,0,.525],711:[0,.56597,0,0,.525],713:[0,.56555,0,0,.525],714:[0,.61111,0,0,.525],715:[0,.61111,0,0,.525],728:[0,.61111,0,0,.525],730:[0,.61111,0,0,.525],770:[0,.61111,0,0,.525],771:[0,.61111,0,0,.525],776:[0,.61111,0,0,.525],915:[0,.61111,0,0,.525],916:[0,.61111,0,0,.525],920:[0,.61111,0,0,.525],923:[0,.61111,0,0,.525],926:[0,.61111,0,0,.525],928:[0,.61111,0,0,.525],931:[0,.61111,0,0,.525],933:[0,.61111,0,0,.525],934:[0,.61111,0,0,.525],936:[0,.61111,0,0,.525],937:[0,.61111,0,0,.525],8216:[0,.61111,0,0,.525],8217:[0,.61111,0,0,.525],8242:[0,.61111,0,0,.525],9251:[.11111,.21944,0,0,.525]}},ve={slant:[.25,.25,.25],space:[0,0,0],stretch:[0,0,0],shrink:[0,0,0],xHeight:[.431,.431,.431],quad:[1,1.171,1.472],extraSpace:[0,0,0],num1:[.677,.732,.925],num2:[.394,.384,.387],num3:[.444,.471,.504],denom1:[.686,.752,1.025],denom2:[.345,.344,.532],sup1:[.413,.503,.504],sup2:[.363,.431,.404],sup3:[.289,.286,.294],sub1:[.15,.143,.2],sub2:[.247,.286,.4],supDrop:[.386,.353,.494],subDrop:[.05,.071,.1],delim1:[2.39,1.7,1.98],delim2:[1.01,1.157,1.42],axisHeight:[.25,.25,.25],defaultRuleThickness:[.04,.049,.049],bigOpSpacing1:[.111,.111,.111],bigOpSpacing2:[.166,.166,.166],bigOpSpacing3:[.2,.2,.2],bigOpSpacing4:[.6,.611,.611],bigOpSpacing5:[.1,.143,.143],sqrtRuleThickness:[.04,.04,.04],ptPerEm:[10,10,10],doubleRuleSep:[.2,.2,.2],arrayRuleWidth:[.04,.04,.04],fboxsep:[.3,.3,.3],fboxrule:[.04,.04,.04]},Ot={Å:"A",Ð:"D",Þ:"o",å:"a",ð:"d",þ:"o",А:"A",Б:"B",В:"B",Г:"F",Д:"A",Е:"E",Ж:"K",З:"3",И:"N",Й:"N",К:"K",Л:"N",М:"M",Н:"H",О:"O",П:"N",Р:"P",С:"C",Т:"T",У:"y",Ф:"O",Х:"X",Ц:"U",Ч:"h",Ш:"W",Щ:"W",Ъ:"B",Ы:"X",Ь:"B",Э:"3",Ю:"X",Я:"R",а:"a",б:"b",в:"a",г:"r",д:"y",е:"e",ж:"m",з:"e",и:"n",й:"n",к:"n",л:"n",м:"m",н:"n",о:"o",п:"n",р:"p",с:"c",т:"o",у:"y",ф:"b",х:"x",ц:"n",ч:"n",ш:"w",щ:"w",ъ:"a",ы:"m",ь:"a",э:"e",ю:"m",я:"r"};function Va(r,e){x0[r]=e}function pt(r,e,t){if(!x0[e])throw new Error("Font metrics not found for font: "+e+".");var a=r.charCodeAt(0),n=x0[e][a];if(!n&&r[0]in Ot&&(a=Ot[r[0]].charCodeAt(0),n=x0[e][a]),!n&&t==="text"&&gr(a)&&(n=x0[e][77]),n)return{depth:n[0],height:n[1],italic:n[2],skew:n[3],width:n[4]}}var Ue={};function Ga(r){var e;if(r>=5?e=0:r>=3?e=1:e=2,!Ue[e]){var t=Ue[e]={cssEmPerMu:ve.quad[e]/18};for(var a in ve)ve.hasOwnProperty(a)&&(t[a]=ve[a][e])}return Ue[e]}var Ua=[[1,1,1],[2,1,1],[3,1,1],[4,2,1],[5,2,1],[6,3,1],[7,4,2],[8,6,3],[9,7,6],[10,8,7],[11,10,9]],Ht=[.5,.6,.7,.8,.9,1,1.2,1.44,1.728,2.074,2.488],Lt=function(e,t){return t.size<2?e:Ua[e-1][t.size-1]};class T0{constructor(e){this.style=void 0,this.color=void 0,this.size=void 0,this.textSize=void 0,this.phantom=void 0,this.font=void 0,this.fontFamily=void 0,this.fontWeight=void 0,this.fontShape=void 0,this.sizeMultiplier=void 0,this.maxSize=void 0,this.minRuleThickness=void 0,this._fontMetrics=void 0,this.style=e.style,this.color=e.color,this.size=e.size||T0.BASESIZE,this.textSize=e.textSize||this.size,this.phantom=!!e.phantom,this.font=e.font||"",this.fontFamily=e.fontFamily||"",this.fontWeight=e.fontWeight||"",this.fontShape=e.fontShape||"",this.sizeMultiplier=Ht[this.size-1],this.maxSize=e.maxSize,this.minRuleThickness=e.minRuleThickness,this._fontMetrics=void 0}extend(e){var t={style:this.style,size:this.size,textSize:this.textSize,color:this.color,phantom:this.phantom,font:this.font,fontFamily:this.fontFamily,fontWeight:this.fontWeight,fontShape:this.fontShape,maxSize:this.maxSize,minRuleThickness:this.minRuleThickness};for(var a in e)e.hasOwnProperty(a)&&(t[a]=e[a]);return new T0(t)}havingStyle(e){return this.style===e?this:this.extend({style:e,size:Lt(this.textSize,e)})}havingCrampedStyle(){return this.havingStyle(this.style.cramp())}havingSize(e){return this.size===e&&this.textSize===e?this:this.extend({style:this.style.text(),size:e,textSize:e,sizeMultiplier:Ht[e-1]})}havingBaseStyle(e){e=e||this.style.text();var t=Lt(T0.BASESIZE,e);return this.size===t&&this.textSize===T0.BASESIZE&&this.style===e?this:this.extend({style:e,size:t})}havingBaseSizing(){var e;switch(this.style.id){case 4:case 5:e=3;break;case 6:case 7:e=1;break;default:e=6}return this.extend({style:this.style.text(),size:e})}withColor(e){return this.extend({color:e})}withPhantom(){return this.extend({phantom:!0})}withFont(e){return this.extend({font:e})}withTextFontFamily(e){return this.extend({fontFamily:e,font:""})}withTextFontWeight(e){return this.extend({fontWeight:e,font:""})}withTextFontShape(e){return this.extend({fontShape:e,font:""})}sizingClasses(e){return e.size!==this.size?["sizing","reset-size"+e.size,"size"+this.size]:[]}baseSizingClasses(){return this.size!==T0.BASESIZE?["sizing","reset-size"+this.size,"size"+T0.BASESIZE]:[]}fontMetrics(){return this._fontMetrics||(this._fontMetrics=Ga(this.size)),this._fontMetrics}getColor(){return this.phantom?"transparent":this.color}}T0.BASESIZE=6;var it={pt:1,mm:7227/2540,cm:7227/254,in:72.27,bp:803/800,pc:12,dd:1238/1157,cc:14856/1157,nd:685/642,nc:1370/107,sp:1/65536,px:803/800},Ya={ex:!0,em:!0,mu:!0},br=function(e){return typeof e!="string"&&(e=e.unit),e in it||e in Ya||e==="ex"},K=function(e,t){var a;if(e.unit in it)a=it[e.unit]/t.fontMetrics().ptPerEm/t.sizeMultiplier;else if(e.unit==="mu")a=t.fontMetrics().cssEmPerMu;else{var n;if(t.style.isTight()?n=t.havingStyle(t.style.text()):n=t,e.unit==="ex")a=n.fontMetrics().xHeight;else if(e.unit==="em")a=n.fontMetrics().quad;else throw new M("Invalid unit: '"+e.unit+"'");n!==t&&(a*=n.sizeMultiplier/t.sizeMultiplier)}return Math.min(e.number*a,t.maxSize)},A=function(e){return+e.toFixed(4)+"em"},P0=function(e){return e.filter(t=>t).join(" ")},yr=function(e,t,a){if(this.classes=e||[],this.attributes={},this.height=0,this.depth=0,this.maxFontSize=0,this.style=a||{},t){t.style.isTight()&&this.classes.push("mtight");var n=t.getColor();n&&(this.style.color=n)}},xr=function(e){var t=document.createElement(e);t.className=P0(this.classes);for(var a in this.style)this.style.hasOwnProperty(a)&&(t.style[a]=this.style[a]);for(var n in this.attributes)this.attributes.hasOwnProperty(n)&&t.setAttribute(n,this.attributes[n]);for(var s=0;s/=\x00-\x1f]/,wr=function(e){var t="<"+e;this.classes.length&&(t+=' class="'+V.escape(P0(this.classes))+'"');var a="";for(var n in this.style)this.style.hasOwnProperty(n)&&(a+=V.hyphenate(n)+":"+this.style[n]+";");a&&(t+=' style="'+V.escape(a)+'"');for(var s in this.attributes)if(this.attributes.hasOwnProperty(s)){if(Xa.test(s))throw new M("Invalid attribute name '"+s+"'");t+=" "+s+'="'+V.escape(this.attributes[s])+'"'}t+=">";for(var u=0;u",t};class he{constructor(e,t,a,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.width=void 0,this.maxFontSize=void 0,this.style=void 0,yr.call(this,e,a,n),this.children=t||[]}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return xr.call(this,"span")}toMarkup(){return wr.call(this,"span")}}class vt{constructor(e,t,a,n){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,yr.call(this,t,n),this.children=a||[],this.setAttribute("href",e)}setAttribute(e,t){this.attributes[e]=t}hasClass(e){return this.classes.includes(e)}toNode(){return xr.call(this,"a")}toMarkup(){return wr.call(this,"a")}}class $a{constructor(e,t,a){this.src=void 0,this.alt=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.alt=t,this.src=e,this.classes=["mord"],this.style=a}hasClass(e){return this.classes.includes(e)}toNode(){var e=document.createElement("img");e.src=this.src,e.alt=this.alt,e.className="mord";for(var t in this.style)this.style.hasOwnProperty(t)&&(e.style[t]=this.style[t]);return e}toMarkup(){var e=''+V.escape(this.alt)+'0&&(t=document.createElement("span"),t.style.marginRight=A(this.italic)),this.classes.length>0&&(t=t||document.createElement("span"),t.className=P0(this.classes));for(var a in this.style)this.style.hasOwnProperty(a)&&(t=t||document.createElement("span"),t.style[a]=this.style[a]);return t?(t.appendChild(e),t):e}toMarkup(){var e=!1,t="0&&(a+="margin-right:"+this.italic+"em;");for(var n in this.style)this.style.hasOwnProperty(n)&&(a+=V.hyphenate(n)+":"+this.style[n]+";");a&&(e=!0,t+=' style="'+V.escape(a)+'"');var s=V.escape(this.text);return e?(t+=">",t+=s,t+="",t):s}}class C0{constructor(e,t){this.children=void 0,this.attributes=void 0,this.children=e||[],this.attributes=t||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"svg");for(var a in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,a)&&t.setAttribute(a,this.attributes[a]);for(var n=0;n':''}}class st{constructor(e){this.attributes=void 0,this.attributes=e||{}}toNode(){var e="http://www.w3.org/2000/svg",t=document.createElementNS(e,"line");for(var a in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,a)&&t.setAttribute(a,this.attributes[a]);return t}toMarkup(){var e=" but got "+String(r)+".")}var Za={bin:1,close:1,inner:1,open:1,punct:1,rel:1},Ka={"accent-token":1,mathord:1,"op-token":1,spacing:1,textord:1},$={math:{},text:{}};function i(r,e,t,a,n,s){$[r][n]={font:e,group:t,replace:a},s&&a&&($[r][a]=$[r][n])}var l="math",k="text",o="main",d="ams",W="accent-token",D="bin",i0="close",re="inner",q="mathord",_="op-token",c0="open",qe="punct",f="rel",R0="spacing",v="textord";i(l,o,f,"≡","\\equiv",!0);i(l,o,f,"≺","\\prec",!0);i(l,o,f,"≻","\\succ",!0);i(l,o,f,"∼","\\sim",!0);i(l,o,f,"⊥","\\perp");i(l,o,f,"⪯","\\preceq",!0);i(l,o,f,"⪰","\\succeq",!0);i(l,o,f,"≃","\\simeq",!0);i(l,o,f,"∣","\\mid",!0);i(l,o,f,"≪","\\ll",!0);i(l,o,f,"≫","\\gg",!0);i(l,o,f,"≍","\\asymp",!0);i(l,o,f,"∥","\\parallel");i(l,o,f,"⋈","\\bowtie",!0);i(l,o,f,"⌣","\\smile",!0);i(l,o,f,"⊑","\\sqsubseteq",!0);i(l,o,f,"⊒","\\sqsupseteq",!0);i(l,o,f,"≐","\\doteq",!0);i(l,o,f,"⌢","\\frown",!0);i(l,o,f,"∋","\\ni",!0);i(l,o,f,"∝","\\propto",!0);i(l,o,f,"⊢","\\vdash",!0);i(l,o,f,"⊣","\\dashv",!0);i(l,o,f,"∋","\\owns");i(l,o,qe,".","\\ldotp");i(l,o,qe,"⋅","\\cdotp");i(l,o,v,"#","\\#");i(k,o,v,"#","\\#");i(l,o,v,"&","\\&");i(k,o,v,"&","\\&");i(l,o,v,"ℵ","\\aleph",!0);i(l,o,v,"∀","\\forall",!0);i(l,o,v,"ℏ","\\hbar",!0);i(l,o,v,"∃","\\exists",!0);i(l,o,v,"∇","\\nabla",!0);i(l,o,v,"♭","\\flat",!0);i(l,o,v,"ℓ","\\ell",!0);i(l,o,v,"♮","\\natural",!0);i(l,o,v,"♣","\\clubsuit",!0);i(l,o,v,"℘","\\wp",!0);i(l,o,v,"♯","\\sharp",!0);i(l,o,v,"♢","\\diamondsuit",!0);i(l,o,v,"ℜ","\\Re",!0);i(l,o,v,"♡","\\heartsuit",!0);i(l,o,v,"ℑ","\\Im",!0);i(l,o,v,"♠","\\spadesuit",!0);i(l,o,v,"§","\\S",!0);i(k,o,v,"§","\\S");i(l,o,v,"¶","\\P",!0);i(k,o,v,"¶","\\P");i(l,o,v,"†","\\dag");i(k,o,v,"†","\\dag");i(k,o,v,"†","\\textdagger");i(l,o,v,"‡","\\ddag");i(k,o,v,"‡","\\ddag");i(k,o,v,"‡","\\textdaggerdbl");i(l,o,i0,"⎱","\\rmoustache",!0);i(l,o,c0,"⎰","\\lmoustache",!0);i(l,o,i0,"⟯","\\rgroup",!0);i(l,o,c0,"⟮","\\lgroup",!0);i(l,o,D,"∓","\\mp",!0);i(l,o,D,"⊖","\\ominus",!0);i(l,o,D,"⊎","\\uplus",!0);i(l,o,D,"⊓","\\sqcap",!0);i(l,o,D,"∗","\\ast");i(l,o,D,"⊔","\\sqcup",!0);i(l,o,D,"◯","\\bigcirc",!0);i(l,o,D,"∙","\\bullet",!0);i(l,o,D,"‡","\\ddagger");i(l,o,D,"≀","\\wr",!0);i(l,o,D,"⨿","\\amalg");i(l,o,D,"&","\\And");i(l,o,f,"⟵","\\longleftarrow",!0);i(l,o,f,"⇐","\\Leftarrow",!0);i(l,o,f,"⟸","\\Longleftarrow",!0);i(l,o,f,"⟶","\\longrightarrow",!0);i(l,o,f,"⇒","\\Rightarrow",!0);i(l,o,f,"⟹","\\Longrightarrow",!0);i(l,o,f,"↔","\\leftrightarrow",!0);i(l,o,f,"⟷","\\longleftrightarrow",!0);i(l,o,f,"⇔","\\Leftrightarrow",!0);i(l,o,f,"⟺","\\Longleftrightarrow",!0);i(l,o,f,"↦","\\mapsto",!0);i(l,o,f,"⟼","\\longmapsto",!0);i(l,o,f,"↗","\\nearrow",!0);i(l,o,f,"↩","\\hookleftarrow",!0);i(l,o,f,"↪","\\hookrightarrow",!0);i(l,o,f,"↘","\\searrow",!0);i(l,o,f,"↼","\\leftharpoonup",!0);i(l,o,f,"⇀","\\rightharpoonup",!0);i(l,o,f,"↙","\\swarrow",!0);i(l,o,f,"↽","\\leftharpoondown",!0);i(l,o,f,"⇁","\\rightharpoondown",!0);i(l,o,f,"↖","\\nwarrow",!0);i(l,o,f,"⇌","\\rightleftharpoons",!0);i(l,d,f,"≮","\\nless",!0);i(l,d,f,"","\\@nleqslant");i(l,d,f,"","\\@nleqq");i(l,d,f,"⪇","\\lneq",!0);i(l,d,f,"≨","\\lneqq",!0);i(l,d,f,"","\\@lvertneqq");i(l,d,f,"⋦","\\lnsim",!0);i(l,d,f,"⪉","\\lnapprox",!0);i(l,d,f,"⊀","\\nprec",!0);i(l,d,f,"⋠","\\npreceq",!0);i(l,d,f,"⋨","\\precnsim",!0);i(l,d,f,"⪹","\\precnapprox",!0);i(l,d,f,"≁","\\nsim",!0);i(l,d,f,"","\\@nshortmid");i(l,d,f,"∤","\\nmid",!0);i(l,d,f,"⊬","\\nvdash",!0);i(l,d,f,"⊭","\\nvDash",!0);i(l,d,f,"⋪","\\ntriangleleft");i(l,d,f,"⋬","\\ntrianglelefteq",!0);i(l,d,f,"⊊","\\subsetneq",!0);i(l,d,f,"","\\@varsubsetneq");i(l,d,f,"⫋","\\subsetneqq",!0);i(l,d,f,"","\\@varsubsetneqq");i(l,d,f,"≯","\\ngtr",!0);i(l,d,f,"","\\@ngeqslant");i(l,d,f,"","\\@ngeqq");i(l,d,f,"⪈","\\gneq",!0);i(l,d,f,"≩","\\gneqq",!0);i(l,d,f,"","\\@gvertneqq");i(l,d,f,"⋧","\\gnsim",!0);i(l,d,f,"⪊","\\gnapprox",!0);i(l,d,f,"⊁","\\nsucc",!0);i(l,d,f,"⋡","\\nsucceq",!0);i(l,d,f,"⋩","\\succnsim",!0);i(l,d,f,"⪺","\\succnapprox",!0);i(l,d,f,"≆","\\ncong",!0);i(l,d,f,"","\\@nshortparallel");i(l,d,f,"∦","\\nparallel",!0);i(l,d,f,"⊯","\\nVDash",!0);i(l,d,f,"⋫","\\ntriangleright");i(l,d,f,"⋭","\\ntrianglerighteq",!0);i(l,d,f,"","\\@nsupseteqq");i(l,d,f,"⊋","\\supsetneq",!0);i(l,d,f,"","\\@varsupsetneq");i(l,d,f,"⫌","\\supsetneqq",!0);i(l,d,f,"","\\@varsupsetneqq");i(l,d,f,"⊮","\\nVdash",!0);i(l,d,f,"⪵","\\precneqq",!0);i(l,d,f,"⪶","\\succneqq",!0);i(l,d,f,"","\\@nsubseteqq");i(l,d,D,"⊴","\\unlhd");i(l,d,D,"⊵","\\unrhd");i(l,d,f,"↚","\\nleftarrow",!0);i(l,d,f,"↛","\\nrightarrow",!0);i(l,d,f,"⇍","\\nLeftarrow",!0);i(l,d,f,"⇏","\\nRightarrow",!0);i(l,d,f,"↮","\\nleftrightarrow",!0);i(l,d,f,"⇎","\\nLeftrightarrow",!0);i(l,d,f,"△","\\vartriangle");i(l,d,v,"ℏ","\\hslash");i(l,d,v,"▽","\\triangledown");i(l,d,v,"◊","\\lozenge");i(l,d,v,"Ⓢ","\\circledS");i(l,d,v,"®","\\circledR");i(k,d,v,"®","\\circledR");i(l,d,v,"∡","\\measuredangle",!0);i(l,d,v,"∄","\\nexists");i(l,d,v,"℧","\\mho");i(l,d,v,"Ⅎ","\\Finv",!0);i(l,d,v,"⅁","\\Game",!0);i(l,d,v,"‵","\\backprime");i(l,d,v,"▲","\\blacktriangle");i(l,d,v,"▼","\\blacktriangledown");i(l,d,v,"■","\\blacksquare");i(l,d,v,"⧫","\\blacklozenge");i(l,d,v,"★","\\bigstar");i(l,d,v,"∢","\\sphericalangle",!0);i(l,d,v,"∁","\\complement",!0);i(l,d,v,"ð","\\eth",!0);i(k,o,v,"ð","ð");i(l,d,v,"╱","\\diagup");i(l,d,v,"╲","\\diagdown");i(l,d,v,"□","\\square");i(l,d,v,"□","\\Box");i(l,d,v,"◊","\\Diamond");i(l,d,v,"¥","\\yen",!0);i(k,d,v,"¥","\\yen",!0);i(l,d,v,"✓","\\checkmark",!0);i(k,d,v,"✓","\\checkmark");i(l,d,v,"ℶ","\\beth",!0);i(l,d,v,"ℸ","\\daleth",!0);i(l,d,v,"ℷ","\\gimel",!0);i(l,d,v,"ϝ","\\digamma",!0);i(l,d,v,"ϰ","\\varkappa");i(l,d,c0,"┌","\\@ulcorner",!0);i(l,d,i0,"┐","\\@urcorner",!0);i(l,d,c0,"└","\\@llcorner",!0);i(l,d,i0,"┘","\\@lrcorner",!0);i(l,d,f,"≦","\\leqq",!0);i(l,d,f,"⩽","\\leqslant",!0);i(l,d,f,"⪕","\\eqslantless",!0);i(l,d,f,"≲","\\lesssim",!0);i(l,d,f,"⪅","\\lessapprox",!0);i(l,d,f,"≊","\\approxeq",!0);i(l,d,D,"⋖","\\lessdot");i(l,d,f,"⋘","\\lll",!0);i(l,d,f,"≶","\\lessgtr",!0);i(l,d,f,"⋚","\\lesseqgtr",!0);i(l,d,f,"⪋","\\lesseqqgtr",!0);i(l,d,f,"≑","\\doteqdot");i(l,d,f,"≓","\\risingdotseq",!0);i(l,d,f,"≒","\\fallingdotseq",!0);i(l,d,f,"∽","\\backsim",!0);i(l,d,f,"⋍","\\backsimeq",!0);i(l,d,f,"⫅","\\subseteqq",!0);i(l,d,f,"⋐","\\Subset",!0);i(l,d,f,"⊏","\\sqsubset",!0);i(l,d,f,"≼","\\preccurlyeq",!0);i(l,d,f,"⋞","\\curlyeqprec",!0);i(l,d,f,"≾","\\precsim",!0);i(l,d,f,"⪷","\\precapprox",!0);i(l,d,f,"⊲","\\vartriangleleft");i(l,d,f,"⊴","\\trianglelefteq");i(l,d,f,"⊨","\\vDash",!0);i(l,d,f,"⊪","\\Vvdash",!0);i(l,d,f,"⌣","\\smallsmile");i(l,d,f,"⌢","\\smallfrown");i(l,d,f,"≏","\\bumpeq",!0);i(l,d,f,"≎","\\Bumpeq",!0);i(l,d,f,"≧","\\geqq",!0);i(l,d,f,"⩾","\\geqslant",!0);i(l,d,f,"⪖","\\eqslantgtr",!0);i(l,d,f,"≳","\\gtrsim",!0);i(l,d,f,"⪆","\\gtrapprox",!0);i(l,d,D,"⋗","\\gtrdot");i(l,d,f,"⋙","\\ggg",!0);i(l,d,f,"≷","\\gtrless",!0);i(l,d,f,"⋛","\\gtreqless",!0);i(l,d,f,"⪌","\\gtreqqless",!0);i(l,d,f,"≖","\\eqcirc",!0);i(l,d,f,"≗","\\circeq",!0);i(l,d,f,"≜","\\triangleq",!0);i(l,d,f,"∼","\\thicksim");i(l,d,f,"≈","\\thickapprox");i(l,d,f,"⫆","\\supseteqq",!0);i(l,d,f,"⋑","\\Supset",!0);i(l,d,f,"⊐","\\sqsupset",!0);i(l,d,f,"≽","\\succcurlyeq",!0);i(l,d,f,"⋟","\\curlyeqsucc",!0);i(l,d,f,"≿","\\succsim",!0);i(l,d,f,"⪸","\\succapprox",!0);i(l,d,f,"⊳","\\vartriangleright");i(l,d,f,"⊵","\\trianglerighteq");i(l,d,f,"⊩","\\Vdash",!0);i(l,d,f,"∣","\\shortmid");i(l,d,f,"∥","\\shortparallel");i(l,d,f,"≬","\\between",!0);i(l,d,f,"⋔","\\pitchfork",!0);i(l,d,f,"∝","\\varpropto");i(l,d,f,"◀","\\blacktriangleleft");i(l,d,f,"∴","\\therefore",!0);i(l,d,f,"∍","\\backepsilon");i(l,d,f,"▶","\\blacktriangleright");i(l,d,f,"∵","\\because",!0);i(l,d,f,"⋘","\\llless");i(l,d,f,"⋙","\\gggtr");i(l,d,D,"⊲","\\lhd");i(l,d,D,"⊳","\\rhd");i(l,d,f,"≂","\\eqsim",!0);i(l,o,f,"⋈","\\Join");i(l,d,f,"≑","\\Doteq",!0);i(l,d,D,"∔","\\dotplus",!0);i(l,d,D,"∖","\\smallsetminus");i(l,d,D,"⋒","\\Cap",!0);i(l,d,D,"⋓","\\Cup",!0);i(l,d,D,"⩞","\\doublebarwedge",!0);i(l,d,D,"⊟","\\boxminus",!0);i(l,d,D,"⊞","\\boxplus",!0);i(l,d,D,"⋇","\\divideontimes",!0);i(l,d,D,"⋉","\\ltimes",!0);i(l,d,D,"⋊","\\rtimes",!0);i(l,d,D,"⋋","\\leftthreetimes",!0);i(l,d,D,"⋌","\\rightthreetimes",!0);i(l,d,D,"⋏","\\curlywedge",!0);i(l,d,D,"⋎","\\curlyvee",!0);i(l,d,D,"⊝","\\circleddash",!0);i(l,d,D,"⊛","\\circledast",!0);i(l,d,D,"⋅","\\centerdot");i(l,d,D,"⊺","\\intercal",!0);i(l,d,D,"⋒","\\doublecap");i(l,d,D,"⋓","\\doublecup");i(l,d,D,"⊠","\\boxtimes",!0);i(l,d,f,"⇢","\\dashrightarrow",!0);i(l,d,f,"⇠","\\dashleftarrow",!0);i(l,d,f,"⇇","\\leftleftarrows",!0);i(l,d,f,"⇆","\\leftrightarrows",!0);i(l,d,f,"⇚","\\Lleftarrow",!0);i(l,d,f,"↞","\\twoheadleftarrow",!0);i(l,d,f,"↢","\\leftarrowtail",!0);i(l,d,f,"↫","\\looparrowleft",!0);i(l,d,f,"⇋","\\leftrightharpoons",!0);i(l,d,f,"↶","\\curvearrowleft",!0);i(l,d,f,"↺","\\circlearrowleft",!0);i(l,d,f,"↰","\\Lsh",!0);i(l,d,f,"⇈","\\upuparrows",!0);i(l,d,f,"↿","\\upharpoonleft",!0);i(l,d,f,"⇃","\\downharpoonleft",!0);i(l,o,f,"⊶","\\origof",!0);i(l,o,f,"⊷","\\imageof",!0);i(l,d,f,"⊸","\\multimap",!0);i(l,d,f,"↭","\\leftrightsquigarrow",!0);i(l,d,f,"⇉","\\rightrightarrows",!0);i(l,d,f,"⇄","\\rightleftarrows",!0);i(l,d,f,"↠","\\twoheadrightarrow",!0);i(l,d,f,"↣","\\rightarrowtail",!0);i(l,d,f,"↬","\\looparrowright",!0);i(l,d,f,"↷","\\curvearrowright",!0);i(l,d,f,"↻","\\circlearrowright",!0);i(l,d,f,"↱","\\Rsh",!0);i(l,d,f,"⇊","\\downdownarrows",!0);i(l,d,f,"↾","\\upharpoonright",!0);i(l,d,f,"⇂","\\downharpoonright",!0);i(l,d,f,"⇝","\\rightsquigarrow",!0);i(l,d,f,"⇝","\\leadsto");i(l,d,f,"⇛","\\Rrightarrow",!0);i(l,d,f,"↾","\\restriction");i(l,o,v,"‘","`");i(l,o,v,"$","\\$");i(k,o,v,"$","\\$");i(k,o,v,"$","\\textdollar");i(l,o,v,"%","\\%");i(k,o,v,"%","\\%");i(l,o,v,"_","\\_");i(k,o,v,"_","\\_");i(k,o,v,"_","\\textunderscore");i(l,o,v,"∠","\\angle",!0);i(l,o,v,"∞","\\infty",!0);i(l,o,v,"′","\\prime");i(l,o,v,"△","\\triangle");i(l,o,v,"Γ","\\Gamma",!0);i(l,o,v,"Δ","\\Delta",!0);i(l,o,v,"Θ","\\Theta",!0);i(l,o,v,"Λ","\\Lambda",!0);i(l,o,v,"Ξ","\\Xi",!0);i(l,o,v,"Π","\\Pi",!0);i(l,o,v,"Σ","\\Sigma",!0);i(l,o,v,"Υ","\\Upsilon",!0);i(l,o,v,"Φ","\\Phi",!0);i(l,o,v,"Ψ","\\Psi",!0);i(l,o,v,"Ω","\\Omega",!0);i(l,o,v,"A","Α");i(l,o,v,"B","Β");i(l,o,v,"E","Ε");i(l,o,v,"Z","Ζ");i(l,o,v,"H","Η");i(l,o,v,"I","Ι");i(l,o,v,"K","Κ");i(l,o,v,"M","Μ");i(l,o,v,"N","Ν");i(l,o,v,"O","Ο");i(l,o,v,"P","Ρ");i(l,o,v,"T","Τ");i(l,o,v,"X","Χ");i(l,o,v,"¬","\\neg",!0);i(l,o,v,"¬","\\lnot");i(l,o,v,"⊤","\\top");i(l,o,v,"⊥","\\bot");i(l,o,v,"∅","\\emptyset");i(l,d,v,"∅","\\varnothing");i(l,o,q,"α","\\alpha",!0);i(l,o,q,"β","\\beta",!0);i(l,o,q,"γ","\\gamma",!0);i(l,o,q,"δ","\\delta",!0);i(l,o,q,"ϵ","\\epsilon",!0);i(l,o,q,"ζ","\\zeta",!0);i(l,o,q,"η","\\eta",!0);i(l,o,q,"θ","\\theta",!0);i(l,o,q,"ι","\\iota",!0);i(l,o,q,"κ","\\kappa",!0);i(l,o,q,"λ","\\lambda",!0);i(l,o,q,"μ","\\mu",!0);i(l,o,q,"ν","\\nu",!0);i(l,o,q,"ξ","\\xi",!0);i(l,o,q,"ο","\\omicron",!0);i(l,o,q,"π","\\pi",!0);i(l,o,q,"ρ","\\rho",!0);i(l,o,q,"σ","\\sigma",!0);i(l,o,q,"τ","\\tau",!0);i(l,o,q,"υ","\\upsilon",!0);i(l,o,q,"ϕ","\\phi",!0);i(l,o,q,"χ","\\chi",!0);i(l,o,q,"ψ","\\psi",!0);i(l,o,q,"ω","\\omega",!0);i(l,o,q,"ε","\\varepsilon",!0);i(l,o,q,"ϑ","\\vartheta",!0);i(l,o,q,"ϖ","\\varpi",!0);i(l,o,q,"ϱ","\\varrho",!0);i(l,o,q,"ς","\\varsigma",!0);i(l,o,q,"φ","\\varphi",!0);i(l,o,D,"∗","*",!0);i(l,o,D,"+","+");i(l,o,D,"−","-",!0);i(l,o,D,"⋅","\\cdot",!0);i(l,o,D,"∘","\\circ",!0);i(l,o,D,"÷","\\div",!0);i(l,o,D,"±","\\pm",!0);i(l,o,D,"×","\\times",!0);i(l,o,D,"∩","\\cap",!0);i(l,o,D,"∪","\\cup",!0);i(l,o,D,"∖","\\setminus",!0);i(l,o,D,"∧","\\land");i(l,o,D,"∨","\\lor");i(l,o,D,"∧","\\wedge",!0);i(l,o,D,"∨","\\vee",!0);i(l,o,v,"√","\\surd");i(l,o,c0,"⟨","\\langle",!0);i(l,o,c0,"∣","\\lvert");i(l,o,c0,"∥","\\lVert");i(l,o,i0,"?","?");i(l,o,i0,"!","!");i(l,o,i0,"⟩","\\rangle",!0);i(l,o,i0,"∣","\\rvert");i(l,o,i0,"∥","\\rVert");i(l,o,f,"=","=");i(l,o,f,":",":");i(l,o,f,"≈","\\approx",!0);i(l,o,f,"≅","\\cong",!0);i(l,o,f,"≥","\\ge");i(l,o,f,"≥","\\geq",!0);i(l,o,f,"←","\\gets");i(l,o,f,">","\\gt",!0);i(l,o,f,"∈","\\in",!0);i(l,o,f,"","\\@not");i(l,o,f,"⊂","\\subset",!0);i(l,o,f,"⊃","\\supset",!0);i(l,o,f,"⊆","\\subseteq",!0);i(l,o,f,"⊇","\\supseteq",!0);i(l,d,f,"⊈","\\nsubseteq",!0);i(l,d,f,"⊉","\\nsupseteq",!0);i(l,o,f,"⊨","\\models");i(l,o,f,"←","\\leftarrow",!0);i(l,o,f,"≤","\\le");i(l,o,f,"≤","\\leq",!0);i(l,o,f,"<","\\lt",!0);i(l,o,f,"→","\\rightarrow",!0);i(l,o,f,"→","\\to");i(l,d,f,"≱","\\ngeq",!0);i(l,d,f,"≰","\\nleq",!0);i(l,o,R0," ","\\ ");i(l,o,R0," ","\\space");i(l,o,R0," ","\\nobreakspace");i(k,o,R0," ","\\ ");i(k,o,R0," "," ");i(k,o,R0," ","\\space");i(k,o,R0," ","\\nobreakspace");i(l,o,R0,null,"\\nobreak");i(l,o,R0,null,"\\allowbreak");i(l,o,qe,",",",");i(l,o,qe,";",";");i(l,d,D,"⊼","\\barwedge",!0);i(l,d,D,"⊻","\\veebar",!0);i(l,o,D,"⊙","\\odot",!0);i(l,o,D,"⊕","\\oplus",!0);i(l,o,D,"⊗","\\otimes",!0);i(l,o,v,"∂","\\partial",!0);i(l,o,D,"⊘","\\oslash",!0);i(l,d,D,"⊚","\\circledcirc",!0);i(l,d,D,"⊡","\\boxdot",!0);i(l,o,D,"△","\\bigtriangleup");i(l,o,D,"▽","\\bigtriangledown");i(l,o,D,"†","\\dagger");i(l,o,D,"⋄","\\diamond");i(l,o,D,"⋆","\\star");i(l,o,D,"◃","\\triangleleft");i(l,o,D,"▹","\\triangleright");i(l,o,c0,"{","\\{");i(k,o,v,"{","\\{");i(k,o,v,"{","\\textbraceleft");i(l,o,i0,"}","\\}");i(k,o,v,"}","\\}");i(k,o,v,"}","\\textbraceright");i(l,o,c0,"{","\\lbrace");i(l,o,i0,"}","\\rbrace");i(l,o,c0,"[","\\lbrack",!0);i(k,o,v,"[","\\lbrack",!0);i(l,o,i0,"]","\\rbrack",!0);i(k,o,v,"]","\\rbrack",!0);i(l,o,c0,"(","\\lparen",!0);i(l,o,i0,")","\\rparen",!0);i(k,o,v,"<","\\textless",!0);i(k,o,v,">","\\textgreater",!0);i(l,o,c0,"⌊","\\lfloor",!0);i(l,o,i0,"⌋","\\rfloor",!0);i(l,o,c0,"⌈","\\lceil",!0);i(l,o,i0,"⌉","\\rceil",!0);i(l,o,v,"\\","\\backslash");i(l,o,v,"∣","|");i(l,o,v,"∣","\\vert");i(k,o,v,"|","\\textbar",!0);i(l,o,v,"∥","\\|");i(l,o,v,"∥","\\Vert");i(k,o,v,"∥","\\textbardbl");i(k,o,v,"~","\\textasciitilde");i(k,o,v,"\\","\\textbackslash");i(k,o,v,"^","\\textasciicircum");i(l,o,f,"↑","\\uparrow",!0);i(l,o,f,"⇑","\\Uparrow",!0);i(l,o,f,"↓","\\downarrow",!0);i(l,o,f,"⇓","\\Downarrow",!0);i(l,o,f,"↕","\\updownarrow",!0);i(l,o,f,"⇕","\\Updownarrow",!0);i(l,o,_,"∐","\\coprod");i(l,o,_,"⋁","\\bigvee");i(l,o,_,"⋀","\\bigwedge");i(l,o,_,"⨄","\\biguplus");i(l,o,_,"⋂","\\bigcap");i(l,o,_,"⋃","\\bigcup");i(l,o,_,"∫","\\int");i(l,o,_,"∫","\\intop");i(l,o,_,"∬","\\iint");i(l,o,_,"∭","\\iiint");i(l,o,_,"∏","\\prod");i(l,o,_,"∑","\\sum");i(l,o,_,"⨂","\\bigotimes");i(l,o,_,"⨁","\\bigoplus");i(l,o,_,"⨀","\\bigodot");i(l,o,_,"∮","\\oint");i(l,o,_,"∯","\\oiint");i(l,o,_,"∰","\\oiiint");i(l,o,_,"⨆","\\bigsqcup");i(l,o,_,"∫","\\smallint");i(k,o,re,"…","\\textellipsis");i(l,o,re,"…","\\mathellipsis");i(k,o,re,"…","\\ldots",!0);i(l,o,re,"…","\\ldots",!0);i(l,o,re,"⋯","\\@cdots",!0);i(l,o,re,"⋱","\\ddots",!0);i(l,o,v,"⋮","\\varvdots");i(k,o,v,"⋮","\\varvdots");i(l,o,W,"ˊ","\\acute");i(l,o,W,"ˋ","\\grave");i(l,o,W,"¨","\\ddot");i(l,o,W,"~","\\tilde");i(l,o,W,"ˉ","\\bar");i(l,o,W,"˘","\\breve");i(l,o,W,"ˇ","\\check");i(l,o,W,"^","\\hat");i(l,o,W,"⃗","\\vec");i(l,o,W,"˙","\\dot");i(l,o,W,"˚","\\mathring");i(l,o,q,"","\\@imath");i(l,o,q,"","\\@jmath");i(l,o,v,"ı","ı");i(l,o,v,"ȷ","ȷ");i(k,o,v,"ı","\\i",!0);i(k,o,v,"ȷ","\\j",!0);i(k,o,v,"ß","\\ss",!0);i(k,o,v,"æ","\\ae",!0);i(k,o,v,"œ","\\oe",!0);i(k,o,v,"ø","\\o",!0);i(k,o,v,"Æ","\\AE",!0);i(k,o,v,"Œ","\\OE",!0);i(k,o,v,"Ø","\\O",!0);i(k,o,W,"ˊ","\\'");i(k,o,W,"ˋ","\\`");i(k,o,W,"ˆ","\\^");i(k,o,W,"˜","\\~");i(k,o,W,"ˉ","\\=");i(k,o,W,"˘","\\u");i(k,o,W,"˙","\\.");i(k,o,W,"¸","\\c");i(k,o,W,"˚","\\r");i(k,o,W,"ˇ","\\v");i(k,o,W,"¨",'\\"');i(k,o,W,"˝","\\H");i(k,o,W,"◯","\\textcircled");var kr={"--":!0,"---":!0,"``":!0,"''":!0};i(k,o,v,"–","--",!0);i(k,o,v,"–","\\textendash");i(k,o,v,"—","---",!0);i(k,o,v,"—","\\textemdash");i(k,o,v,"‘","`",!0);i(k,o,v,"‘","\\textquoteleft");i(k,o,v,"’","'",!0);i(k,o,v,"’","\\textquoteright");i(k,o,v,"“","``",!0);i(k,o,v,"“","\\textquotedblleft");i(k,o,v,"”","''",!0);i(k,o,v,"”","\\textquotedblright");i(l,o,v,"°","\\degree",!0);i(k,o,v,"°","\\degree");i(k,o,v,"°","\\textdegree",!0);i(l,o,v,"£","\\pounds");i(l,o,v,"£","\\mathsterling",!0);i(k,o,v,"£","\\pounds");i(k,o,v,"£","\\textsterling",!0);i(l,d,v,"✠","\\maltese");i(k,d,v,"✠","\\maltese");var Vt='0123456789/@."';for(var Ye=0;Ye0)return b0(s,p,n,t,u.concat(g));if(c){var y,x;if(c==="boldsymbol"){var w=_a(s,n,t,u,a);y=w.fontName,x=[w.fontClass]}else h?(y=zr[c].fontName,x=[c]):(y=xe(c,t.fontWeight,t.fontShape),x=[c,t.fontWeight,t.fontShape]);if(Re(s,y,n).metrics)return b0(s,y,n,t,u.concat(x));if(kr.hasOwnProperty(s)&&y.slice(0,10)==="Typewriter"){for(var z=[],T=0;T{if(P0(r.classes)!==P0(e.classes)||r.skew!==e.skew||r.maxFontSize!==e.maxFontSize)return!1;if(r.classes.length===1){var t=r.classes[0];if(t==="mbin"||t==="mord")return!1}for(var a in r.style)if(r.style.hasOwnProperty(a)&&r.style[a]!==e.style[a])return!1;for(var n in e.style)if(e.style.hasOwnProperty(n)&&r.style[n]!==e.style[n])return!1;return!0},r1=r=>{for(var e=0;et&&(t=u.height),u.depth>a&&(a=u.depth),u.maxFontSize>n&&(n=u.maxFontSize)}e.height=t,e.depth=a,e.maxFontSize=n},l0=function(e,t,a,n){var s=new he(e,t,a,n);return gt(s),s},Sr=(r,e,t,a)=>new he(r,e,t,a),a1=function(e,t,a){var n=l0([e],[],t);return n.height=Math.max(a||t.fontMetrics().defaultRuleThickness,t.minRuleThickness),n.style.borderBottomWidth=A(n.height),n.maxFontSize=1,n},n1=function(e,t,a,n){var s=new vt(e,t,a,n);return gt(s),s},Mr=function(e){var t=new oe(e);return gt(t),t},i1=function(e,t){return e instanceof oe?l0([],[e],t):e},s1=function(e){if(e.positionType==="individualShift"){for(var t=e.children,a=[t[0]],n=-t[0].shift-t[0].elem.depth,s=n,u=1;u{var t=l0(["mspace"],[],e),a=K(r,e);return t.style.marginRight=A(a),t},xe=function(e,t,a){var n="";switch(e){case"amsrm":n="AMS";break;case"textrm":n="Main";break;case"textsf":n="SansSerif";break;case"texttt":n="Typewriter";break;default:n=e}var s;return t==="textbf"&&a==="textit"?s="BoldItalic":t==="textbf"?s="Bold":t==="textit"?s="Italic":s="Regular",n+"-"+s},zr={mathbf:{variant:"bold",fontName:"Main-Bold"},mathrm:{variant:"normal",fontName:"Main-Regular"},textit:{variant:"italic",fontName:"Main-Italic"},mathit:{variant:"italic",fontName:"Main-Italic"},mathnormal:{variant:"italic",fontName:"Math-Italic"},mathsfit:{variant:"sans-serif-italic",fontName:"SansSerif-Italic"},mathbb:{variant:"double-struck",fontName:"AMS-Regular"},mathcal:{variant:"script",fontName:"Caligraphic-Regular"},mathfrak:{variant:"fraktur",fontName:"Fraktur-Regular"},mathscr:{variant:"script",fontName:"Script-Regular"},mathsf:{variant:"sans-serif",fontName:"SansSerif-Regular"},mathtt:{variant:"monospace",fontName:"Typewriter-Regular"}},Ar={vec:["vec",.471,.714],oiintSize1:["oiintSize1",.957,.499],oiintSize2:["oiintSize2",1.472,.659],oiiintSize1:["oiiintSize1",1.304,.499],oiiintSize2:["oiiintSize2",1.98,.659]},o1=function(e,t){var[a,n,s]=Ar[e],u=new V0(a),h=new C0([u],{width:A(n),height:A(s),style:"width:"+A(n),viewBox:"0 0 "+1e3*n+" "+1e3*s,preserveAspectRatio:"xMinYMin"}),c=Sr(["overlay"],[h],t);return c.height=s,c.style.height=A(s),c.style.width=A(n),c},b={fontMap:zr,makeSymbol:b0,mathsym:Qa,makeSpan:l0,makeSvgSpan:Sr,makeLineSpan:a1,makeAnchor:n1,makeFragment:Mr,wrapFragment:i1,makeVList:l1,makeOrd:e1,makeGlue:u1,staticSvg:o1,svgData:Ar,tryCombineChars:r1},Z={number:3,unit:"mu"},$0={number:4,unit:"mu"},A0={number:5,unit:"mu"},h1={mord:{mop:Z,mbin:$0,mrel:A0,minner:Z},mop:{mord:Z,mop:Z,mrel:A0,minner:Z},mbin:{mord:$0,mop:$0,mopen:$0,minner:$0},mrel:{mord:A0,mop:A0,mopen:A0,minner:A0},mopen:{},mclose:{mop:Z,mbin:$0,mrel:A0,minner:Z},mpunct:{mord:Z,mop:Z,mrel:A0,mopen:Z,mclose:Z,mpunct:Z,minner:Z},minner:{mord:Z,mop:Z,mbin:$0,mrel:A0,mopen:Z,mpunct:Z,minner:Z}},m1={mord:{mop:Z},mop:{mord:Z,mop:Z},mbin:{},mrel:{},mopen:{},mclose:{mop:Z},mpunct:{},minner:{mop:Z}},Tr={},De={},Ce={};function B(r){for(var{type:e,names:t,props:a,handler:n,htmlBuilder:s,mathmlBuilder:u}=r,h={type:e,numArgs:a.numArgs,argTypes:a.argTypes,allowedInArgument:!!a.allowedInArgument,allowedInText:!!a.allowedInText,allowedInMath:a.allowedInMath===void 0?!0:a.allowedInMath,numOptionalArgs:a.numOptionalArgs||0,infix:!!a.infix,primitive:!!a.primitive,handler:n},c=0;c{var C=T.classes[0],N=z.classes[0];C==="mbin"&&d1.includes(N)?T.classes[0]="mord":N==="mbin"&&c1.includes(C)&&(z.classes[0]="mord")},{node:y},x,w),$t(s,(z,T)=>{var C=ut(T),N=ut(z),I=C&&N?z.hasClass("mtight")?m1[C][N]:h1[C][N]:null;if(I)return b.makeGlue(I,p)},{node:y},x,w),s},$t=function r(e,t,a,n,s){n&&e.push(n);for(var u=0;ux=>{e.splice(y+1,0,x),u++})(u)}n&&e.pop()},Br=function(e){return e instanceof oe||e instanceof vt||e instanceof he&&e.hasClass("enclosing")?e:null},v1=function r(e,t){var a=Br(e);if(a){var n=a.children;if(n.length){if(t==="right")return r(n[n.length-1],"right");if(t==="left")return r(n[0],"left")}}return e},ut=function(e,t){return e?(t&&(e=v1(e,t)),p1[e.classes[0]]||null):null},ue=function(e,t){var a=["nulldelimiter"].concat(e.baseSizingClasses());return N0(t.concat(a))},L=function(e,t,a){if(!e)return N0();if(De[e.type]){var n=De[e.type](e,t);if(a&&t.size!==a.size){n=N0(t.sizingClasses(a),[n],t);var s=t.sizeMultiplier/a.sizeMultiplier;n.height*=s,n.depth*=s}return n}else throw new M("Got group of unknown type: '"+e.type+"'")};function we(r,e){var t=N0(["base"],r,e),a=N0(["strut"]);return a.style.height=A(t.height+t.depth),t.depth&&(a.style.verticalAlign=A(-t.depth)),t.children.unshift(a),t}function ot(r,e){var t=null;r.length===1&&r[0].type==="tag"&&(t=r[0].tag,r=r[0].body);var a=t0(r,e,"root"),n;a.length===2&&a[1].hasClass("tag")&&(n=a.pop());for(var s=[],u=[],h=0;h0&&(s.push(we(u,e)),u=[]),s.push(a[h]));u.length>0&&s.push(we(u,e));var p;t?(p=we(t0(t,e,!0)),p.classes=["tag"],s.push(p)):n&&s.push(n);var g=N0(["katex-html"],s);if(g.setAttribute("aria-hidden","true"),p){var y=p.children[0];y.style.height=A(g.height+g.depth),g.depth&&(y.style.verticalAlign=A(-g.depth))}return g}function Dr(r){return new oe(r)}class h0{constructor(e,t,a){this.type=void 0,this.attributes=void 0,this.children=void 0,this.classes=void 0,this.type=e,this.attributes={},this.children=t||[],this.classes=a||[]}setAttribute(e,t){this.attributes[e]=t}getAttribute(e){return this.attributes[e]}toNode(){var e=document.createElementNS("http://www.w3.org/1998/Math/MathML",this.type);for(var t in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,t)&&e.setAttribute(t,this.attributes[t]);this.classes.length>0&&(e.className=P0(this.classes));for(var a=0;a0&&(e+=' class ="'+V.escape(P0(this.classes))+'"'),e+=">";for(var a=0;a",e}toText(){return this.children.map(e=>e.toText()).join("")}}class w0{constructor(e){this.text=void 0,this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return V.escape(this.toText())}toText(){return this.text}}class g1{constructor(e){this.width=void 0,this.character=void 0,this.width=e,e>=.05555&&e<=.05556?this.character=" ":e>=.1666&&e<=.1667?this.character=" ":e>=.2222&&e<=.2223?this.character=" ":e>=.2777&&e<=.2778?this.character="  ":e>=-.05556&&e<=-.05555?this.character=" ⁣":e>=-.1667&&e<=-.1666?this.character=" ⁣":e>=-.2223&&e<=-.2222?this.character=" ⁣":e>=-.2778&&e<=-.2777?this.character=" ⁣":this.character=null}toNode(){if(this.character)return document.createTextNode(this.character);var e=document.createElementNS("http://www.w3.org/1998/Math/MathML","mspace");return e.setAttribute("width",A(this.width)),e}toMarkup(){return this.character?""+this.character+"":''}toText(){return this.character?this.character:" "}}var S={MathNode:h0,TextNode:w0,SpaceNode:g1,newDocumentFragment:Dr},v0=function(e,t,a){return $[t][e]&&$[t][e].replace&&e.charCodeAt(0)!==55349&&!(kr.hasOwnProperty(e)&&a&&(a.fontFamily&&a.fontFamily.slice(4,6)==="tt"||a.font&&a.font.slice(4,6)==="tt"))&&(e=$[t][e].replace),new S.TextNode(e)},bt=function(e){return e.length===1?e[0]:new S.MathNode("mrow",e)},yt=function(e,t){if(t.fontFamily==="texttt")return"monospace";if(t.fontFamily==="textsf")return t.fontShape==="textit"&&t.fontWeight==="textbf"?"sans-serif-bold-italic":t.fontShape==="textit"?"sans-serif-italic":t.fontWeight==="textbf"?"bold-sans-serif":"sans-serif";if(t.fontShape==="textit"&&t.fontWeight==="textbf")return"bold-italic";if(t.fontShape==="textit")return"italic";if(t.fontWeight==="textbf")return"bold";var a=t.font;if(!a||a==="mathnormal")return null;var n=e.mode;if(a==="mathit")return"italic";if(a==="boldsymbol")return e.type==="textord"?"bold":"bold-italic";if(a==="mathbf")return"bold";if(a==="mathbb")return"double-struck";if(a==="mathsfit")return"sans-serif-italic";if(a==="mathfrak")return"fraktur";if(a==="mathscr"||a==="mathcal")return"script";if(a==="mathsf")return"sans-serif";if(a==="mathtt")return"monospace";var s=e.text;if(["\\imath","\\jmath"].includes(s))return null;$[n][s]&&$[n][s].replace&&(s=$[n][s].replace);var u=b.fontMap[a].fontName;return pt(s,u,n)?b.fontMap[a].variant:null};function je(r){if(!r)return!1;if(r.type==="mi"&&r.children.length===1){var e=r.children[0];return e instanceof w0&&e.text==="."}else if(r.type==="mo"&&r.children.length===1&&r.getAttribute("separator")==="true"&&r.getAttribute("lspace")==="0em"&&r.getAttribute("rspace")==="0em"){var t=r.children[0];return t instanceof w0&&t.text===","}else return!1}var o0=function(e,t,a){if(e.length===1){var n=X(e[0],t);return a&&n instanceof h0&&n.type==="mo"&&(n.setAttribute("lspace","0em"),n.setAttribute("rspace","0em")),[n]}for(var s=[],u,h=0;h=1&&(u.type==="mn"||je(u))){var p=c.children[0];p instanceof h0&&p.type==="mn"&&(p.children=[...u.children,...p.children],s.pop())}else if(u.type==="mi"&&u.children.length===1){var g=u.children[0];if(g instanceof w0&&g.text==="̸"&&(c.type==="mo"||c.type==="mi"||c.type==="mn")){var y=c.children[0];y instanceof w0&&y.text.length>0&&(y.text=y.text.slice(0,1)+"̸"+y.text.slice(1),s.pop())}}}s.push(c),u=c}return s},G0=function(e,t,a){return bt(o0(e,t,a))},X=function(e,t){if(!e)return new S.MathNode("mrow");if(Ce[e.type]){var a=Ce[e.type](e,t);return a}else throw new M("Got group of unknown type: '"+e.type+"'")};function Wt(r,e,t,a,n){var s=o0(r,t),u;s.length===1&&s[0]instanceof h0&&["mrow","mtable"].includes(s[0].type)?u=s[0]:u=new S.MathNode("mrow",s);var h=new S.MathNode("annotation",[new S.TextNode(e)]);h.setAttribute("encoding","application/x-tex");var c=new S.MathNode("semantics",[u,h]),p=new S.MathNode("math",[c]);p.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),a&&p.setAttribute("display","block");var g=n?"katex":"katex-mathml";return b.makeSpan([g],[p])}var Cr=function(e){return new T0({style:e.displayMode?R.DISPLAY:R.TEXT,maxSize:e.maxSize,minRuleThickness:e.minRuleThickness})},Nr=function(e,t){if(t.displayMode){var a=["katex-display"];t.leqno&&a.push("leqno"),t.fleqn&&a.push("fleqn"),e=b.makeSpan(a,[e])}return e},b1=function(e,t,a){var n=Cr(a),s;if(a.output==="mathml")return Wt(e,t,n,a.displayMode,!0);if(a.output==="html"){var u=ot(e,n);s=b.makeSpan(["katex"],[u])}else{var h=Wt(e,t,n,a.displayMode,!1),c=ot(e,n);s=b.makeSpan(["katex"],[h,c])}return Nr(s,a)},y1=function(e,t,a){var n=Cr(a),s=ot(e,n),u=b.makeSpan(["katex"],[s]);return Nr(u,a)},x1={widehat:"^",widecheck:"ˇ",widetilde:"~",utilde:"~",overleftarrow:"←",underleftarrow:"←",xleftarrow:"←",overrightarrow:"→",underrightarrow:"→",xrightarrow:"→",underbrace:"⏟",overbrace:"⏞",overgroup:"⏠",undergroup:"⏡",overleftrightarrow:"↔",underleftrightarrow:"↔",xleftrightarrow:"↔",Overrightarrow:"⇒",xRightarrow:"⇒",overleftharpoon:"↼",xleftharpoonup:"↼",overrightharpoon:"⇀",xrightharpoonup:"⇀",xLeftarrow:"⇐",xLeftrightarrow:"⇔",xhookleftarrow:"↩",xhookrightarrow:"↪",xmapsto:"↦",xrightharpoondown:"⇁",xleftharpoondown:"↽",xrightleftharpoons:"⇌",xleftrightharpoons:"⇋",xtwoheadleftarrow:"↞",xtwoheadrightarrow:"↠",xlongequal:"=",xtofrom:"⇄",xrightleftarrows:"⇄",xrightequilibrium:"⇌",xleftequilibrium:"⇋","\\cdrightarrow":"→","\\cdleftarrow":"←","\\cdlongequal":"="},w1=function(e){var t=new S.MathNode("mo",[new S.TextNode(x1[e.replace(/^\\/,"")])]);return t.setAttribute("stretchy","true"),t},k1={overrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],overleftarrow:[["leftarrow"],.888,522,"xMinYMin"],underrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],underleftarrow:[["leftarrow"],.888,522,"xMinYMin"],xrightarrow:[["rightarrow"],1.469,522,"xMaxYMin"],"\\cdrightarrow":[["rightarrow"],3,522,"xMaxYMin"],xleftarrow:[["leftarrow"],1.469,522,"xMinYMin"],"\\cdleftarrow":[["leftarrow"],3,522,"xMinYMin"],Overrightarrow:[["doublerightarrow"],.888,560,"xMaxYMin"],xRightarrow:[["doublerightarrow"],1.526,560,"xMaxYMin"],xLeftarrow:[["doubleleftarrow"],1.526,560,"xMinYMin"],overleftharpoon:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoonup:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoondown:[["leftharpoondown"],.888,522,"xMinYMin"],overrightharpoon:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoonup:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoondown:[["rightharpoondown"],.888,522,"xMaxYMin"],xlongequal:[["longequal"],.888,334,"xMinYMin"],"\\cdlongequal":[["longequal"],3,334,"xMinYMin"],xtwoheadleftarrow:[["twoheadleftarrow"],.888,334,"xMinYMin"],xtwoheadrightarrow:[["twoheadrightarrow"],.888,334,"xMaxYMin"],overleftrightarrow:[["leftarrow","rightarrow"],.888,522],overbrace:[["leftbrace","midbrace","rightbrace"],1.6,548],underbrace:[["leftbraceunder","midbraceunder","rightbraceunder"],1.6,548],underleftrightarrow:[["leftarrow","rightarrow"],.888,522],xleftrightarrow:[["leftarrow","rightarrow"],1.75,522],xLeftrightarrow:[["doubleleftarrow","doublerightarrow"],1.75,560],xrightleftharpoons:[["leftharpoondownplus","rightharpoonplus"],1.75,716],xleftrightharpoons:[["leftharpoonplus","rightharpoondownplus"],1.75,716],xhookleftarrow:[["leftarrow","righthook"],1.08,522],xhookrightarrow:[["lefthook","rightarrow"],1.08,522],overlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],underlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],overgroup:[["leftgroup","rightgroup"],.888,342],undergroup:[["leftgroupunder","rightgroupunder"],.888,342],xmapsto:[["leftmapsto","rightarrow"],1.5,522],xtofrom:[["leftToFrom","rightToFrom"],1.75,528],xrightleftarrows:[["baraboveleftarrow","rightarrowabovebar"],1.75,901],xrightequilibrium:[["baraboveshortleftharpoon","rightharpoonaboveshortbar"],1.75,716],xleftequilibrium:[["shortbaraboveleftharpoon","shortrightharpoonabovebar"],1.75,716]},S1=function(e){return e.type==="ordgroup"?e.body.length:1},M1=function(e,t){function a(){var h=4e5,c=e.label.slice(1);if(["widehat","widecheck","widetilde","utilde"].includes(c)){var p=e,g=S1(p.base),y,x,w;if(g>5)c==="widehat"||c==="widecheck"?(y=420,h=2364,w=.42,x=c+"4"):(y=312,h=2340,w=.34,x="tilde4");else{var z=[1,1,2,2,3,3][g];c==="widehat"||c==="widecheck"?(h=[0,1062,2364,2364,2364][z],y=[0,239,300,360,420][z],w=[0,.24,.3,.3,.36,.42][z],x=c+z):(h=[0,600,1033,2339,2340][z],y=[0,260,286,306,312][z],w=[0,.26,.286,.3,.306,.34][z],x="tilde"+z)}var T=new V0(x),C=new C0([T],{width:"100%",height:A(w),viewBox:"0 0 "+h+" "+y,preserveAspectRatio:"none"});return{span:b.makeSvgSpan([],[C],t),minWidth:0,height:w}}else{var N=[],I=k1[c],[F,G,H]=I,U=H/1e3,P=F.length,j,Y;if(P===1){var z0=I[3];j=["hide-tail"],Y=[z0]}else if(P===2)j=["halfarrow-left","halfarrow-right"],Y=["xMinYMin","xMaxYMin"];else if(P===3)j=["brace-left","brace-center","brace-right"],Y=["xMinYMin","xMidYMin","xMaxYMin"];else throw new Error(`Correct katexImagesData or update code here to support + `+P+" children.");for(var r0=0;r00&&(n.style.minWidth=A(s)),n},z1=function(e,t,a,n,s){var u,h=e.height+e.depth+a+n;if(/fbox|color|angl/.test(t)){if(u=b.makeSpan(["stretchy",t],[],s),t==="fbox"){var c=s.color&&s.getColor();c&&(u.style.borderColor=c)}}else{var p=[];/^[bx]cancel$/.test(t)&&p.push(new st({x1:"0",y1:"0",x2:"100%",y2:"100%","stroke-width":"0.046em"})),/^x?cancel$/.test(t)&&p.push(new st({x1:"0",y1:"100%",x2:"100%",y2:"0","stroke-width":"0.046em"}));var g=new C0(p,{width:"100%",height:A(h)});u=b.makeSvgSpan([],[g],s)}return u.height=h,u.style.height=A(h),u},q0={encloseSpan:z1,mathMLnode:w1,svgSpan:M1};function O(r,e){if(!r||r.type!==e)throw new Error("Expected node of type "+e+", but got "+(r?"node of type "+r.type:String(r)));return r}function xt(r){var e=Ee(r);if(!e)throw new Error("Expected node of symbol group type, but got "+(r?"node of type "+r.type:String(r)));return e}function Ee(r){return r&&(r.type==="atom"||Ka.hasOwnProperty(r.type))?r:null}var wt=(r,e)=>{var t,a,n;r&&r.type==="supsub"?(a=O(r.base,"accent"),t=a.base,r.base=t,n=ja(L(r,e)),r.base=a):(a=O(r,"accent"),t=a.base);var s=L(t,e.havingCrampedStyle()),u=a.isShifty&&V.isCharacterBox(t),h=0;if(u){var c=V.getBaseElem(t),p=L(c,e.havingCrampedStyle());h=Pt(p).skew}var g=a.label==="\\c",y=g?s.height+s.depth:Math.min(s.height,e.fontMetrics().xHeight),x;if(a.isStretchy)x=q0.svgSpan(a,e),x=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"elem",elem:x,wrapperClasses:["svg-align"],wrapperStyle:h>0?{width:"calc(100% - "+A(2*h)+")",marginLeft:A(2*h)}:void 0}]},e);else{var w,z;a.label==="\\vec"?(w=b.staticSvg("vec",e),z=b.svgData.vec[1]):(w=b.makeOrd({mode:a.mode,text:a.label},e,"textord"),w=Pt(w),w.italic=0,z=w.width,g&&(y+=w.depth)),x=b.makeSpan(["accent-body"],[w]);var T=a.label==="\\textcircled";T&&(x.classes.push("accent-full"),y=s.height);var C=h;T||(C-=z/2),x.style.left=A(C),a.label==="\\textcircled"&&(x.style.top=".2em"),x=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:-y},{type:"elem",elem:x}]},e)}var N=b.makeSpan(["mord","accent"],[x],e);return n?(n.children[0]=N,n.height=Math.max(N.height,n.height),n.classes[0]="mord",n):N},qr=(r,e)=>{var t=r.isStretchy?q0.mathMLnode(r.label):new S.MathNode("mo",[v0(r.label,r.mode)]),a=new S.MathNode("mover",[X(r.base,e),t]);return a.setAttribute("accent","true"),a},A1=new RegExp(["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring"].map(r=>"\\"+r).join("|"));B({type:"accent",names:["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon"],props:{numArgs:1},handler:(r,e)=>{var t=Ne(e[0]),a=!A1.test(r.funcName),n=!a||r.funcName==="\\widehat"||r.funcName==="\\widetilde"||r.funcName==="\\widecheck";return{type:"accent",mode:r.parser.mode,label:r.funcName,isStretchy:a,isShifty:n,base:t}},htmlBuilder:wt,mathmlBuilder:qr});B({type:"accent",names:["\\'","\\`","\\^","\\~","\\=","\\u","\\.",'\\"',"\\c","\\r","\\H","\\v","\\textcircled"],props:{numArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["primitive"]},handler:(r,e)=>{var t=e[0],a=r.parser.mode;return a==="math"&&(r.parser.settings.reportNonstrict("mathVsTextAccents","LaTeX's accent "+r.funcName+" works only in text mode"),a="text"),{type:"accent",mode:a,label:r.funcName,isStretchy:!1,isShifty:!0,base:t}},htmlBuilder:wt,mathmlBuilder:qr});B({type:"accentUnder",names:["\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde"],props:{numArgs:1},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0];return{type:"accentUnder",mode:t.mode,label:a,base:n}},htmlBuilder:(r,e)=>{var t=L(r.base,e),a=q0.svgSpan(r,e),n=r.label==="\\utilde"?.12:0,s=b.makeVList({positionType:"top",positionData:t.height,children:[{type:"elem",elem:a,wrapperClasses:["svg-align"]},{type:"kern",size:n},{type:"elem",elem:t}]},e);return b.makeSpan(["mord","accentunder"],[s],e)},mathmlBuilder:(r,e)=>{var t=q0.mathMLnode(r.label),a=new S.MathNode("munder",[X(r.base,e),t]);return a.setAttribute("accentunder","true"),a}});var ke=r=>{var e=new S.MathNode("mpadded",r?[r]:[]);return e.setAttribute("width","+0.6em"),e.setAttribute("lspace","0.3em"),e};B({type:"xArrow",names:["\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium","\\\\cdrightarrow","\\\\cdleftarrow","\\\\cdlongequal"],props:{numArgs:1,numOptionalArgs:1},handler(r,e,t){var{parser:a,funcName:n}=r;return{type:"xArrow",mode:a.mode,label:n,body:e[0],below:t[0]}},htmlBuilder(r,e){var t=e.style,a=e.havingStyle(t.sup()),n=b.wrapFragment(L(r.body,a,e),e),s=r.label.slice(0,2)==="\\x"?"x":"cd";n.classes.push(s+"-arrow-pad");var u;r.below&&(a=e.havingStyle(t.sub()),u=b.wrapFragment(L(r.below,a,e),e),u.classes.push(s+"-arrow-pad"));var h=q0.svgSpan(r,e),c=-e.fontMetrics().axisHeight+.5*h.height,p=-e.fontMetrics().axisHeight-.5*h.height-.111;(n.depth>.25||r.label==="\\xleftequilibrium")&&(p-=n.depth);var g;if(u){var y=-e.fontMetrics().axisHeight+u.height+.5*h.height+.111;g=b.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:p},{type:"elem",elem:h,shift:c},{type:"elem",elem:u,shift:y}]},e)}else g=b.makeVList({positionType:"individualShift",children:[{type:"elem",elem:n,shift:p},{type:"elem",elem:h,shift:c}]},e);return g.children[0].children[0].children[1].classes.push("svg-align"),b.makeSpan(["mrel","x-arrow"],[g],e)},mathmlBuilder(r,e){var t=q0.mathMLnode(r.label);t.setAttribute("minsize",r.label.charAt(0)==="x"?"1.75em":"3.0em");var a;if(r.body){var n=ke(X(r.body,e));if(r.below){var s=ke(X(r.below,e));a=new S.MathNode("munderover",[t,s,n])}else a=new S.MathNode("mover",[t,n])}else if(r.below){var u=ke(X(r.below,e));a=new S.MathNode("munder",[t,u])}else a=ke(),a=new S.MathNode("mover",[t,a]);return a}});var T1=b.makeSpan;function Rr(r,e){var t=t0(r.body,e,!0);return T1([r.mclass],t,e)}function Er(r,e){var t,a=o0(r.body,e);return r.mclass==="minner"?t=new S.MathNode("mpadded",a):r.mclass==="mord"?r.isCharacterBox?(t=a[0],t.type="mi"):t=new S.MathNode("mi",a):(r.isCharacterBox?(t=a[0],t.type="mo"):t=new S.MathNode("mo",a),r.mclass==="mbin"?(t.attributes.lspace="0.22em",t.attributes.rspace="0.22em"):r.mclass==="mpunct"?(t.attributes.lspace="0em",t.attributes.rspace="0.17em"):r.mclass==="mopen"||r.mclass==="mclose"?(t.attributes.lspace="0em",t.attributes.rspace="0em"):r.mclass==="minner"&&(t.attributes.lspace="0.0556em",t.attributes.width="+0.1111em")),t}B({type:"mclass",names:["\\mathord","\\mathbin","\\mathrel","\\mathopen","\\mathclose","\\mathpunct","\\mathinner"],props:{numArgs:1,primitive:!0},handler(r,e){var{parser:t,funcName:a}=r,n=e[0];return{type:"mclass",mode:t.mode,mclass:"m"+a.slice(5),body:Q(n),isCharacterBox:V.isCharacterBox(n)}},htmlBuilder:Rr,mathmlBuilder:Er});var Ie=r=>{var e=r.type==="ordgroup"&&r.body.length?r.body[0]:r;return e.type==="atom"&&(e.family==="bin"||e.family==="rel")?"m"+e.family:"mord"};B({type:"mclass",names:["\\@binrel"],props:{numArgs:2},handler(r,e){var{parser:t}=r;return{type:"mclass",mode:t.mode,mclass:Ie(e[0]),body:Q(e[1]),isCharacterBox:V.isCharacterBox(e[1])}}});B({type:"mclass",names:["\\stackrel","\\overset","\\underset"],props:{numArgs:2},handler(r,e){var{parser:t,funcName:a}=r,n=e[1],s=e[0],u;a!=="\\stackrel"?u=Ie(n):u="mrel";var h={type:"op",mode:n.mode,limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!1,symbol:!1,suppressBaseShift:a!=="\\stackrel",body:Q(n)},c={type:"supsub",mode:s.mode,base:h,sup:a==="\\underset"?null:s,sub:a==="\\underset"?s:null};return{type:"mclass",mode:t.mode,mclass:u,body:[c],isCharacterBox:V.isCharacterBox(c)}},htmlBuilder:Rr,mathmlBuilder:Er});B({type:"pmb",names:["\\pmb"],props:{numArgs:1,allowedInText:!0},handler(r,e){var{parser:t}=r;return{type:"pmb",mode:t.mode,mclass:Ie(e[0]),body:Q(e[0])}},htmlBuilder(r,e){var t=t0(r.body,e,!0),a=b.makeSpan([r.mclass],t,e);return a.style.textShadow="0.02em 0.01em 0.04px",a},mathmlBuilder(r,e){var t=o0(r.body,e),a=new S.MathNode("mstyle",t);return a.setAttribute("style","text-shadow: 0.02em 0.01em 0.04px"),a}});var B1={">":"\\\\cdrightarrow","<":"\\\\cdleftarrow","=":"\\\\cdlongequal",A:"\\uparrow",V:"\\downarrow","|":"\\Vert",".":"no arrow"},jt=()=>({type:"styling",body:[],mode:"math",style:"display"}),Zt=r=>r.type==="textord"&&r.text==="@",D1=(r,e)=>(r.type==="mathord"||r.type==="atom")&&r.text===e;function C1(r,e,t){var a=B1[r];switch(a){case"\\\\cdrightarrow":case"\\\\cdleftarrow":return t.callFunction(a,[e[0]],[e[1]]);case"\\uparrow":case"\\downarrow":{var n=t.callFunction("\\\\cdleft",[e[0]],[]),s={type:"atom",text:a,mode:"math",family:"rel"},u=t.callFunction("\\Big",[s],[]),h=t.callFunction("\\\\cdright",[e[1]],[]),c={type:"ordgroup",mode:"math",body:[n,u,h]};return t.callFunction("\\\\cdparent",[c],[])}case"\\\\cdlongequal":return t.callFunction("\\\\cdlongequal",[],[]);case"\\Vert":{var p={type:"textord",text:"\\Vert",mode:"math"};return t.callFunction("\\Big",[p],[])}default:return{type:"textord",text:" ",mode:"math"}}}function N1(r){var e=[];for(r.gullet.beginGroup(),r.gullet.macros.set("\\cr","\\\\\\relax"),r.gullet.beginGroup();;){e.push(r.parseExpression(!1,"\\\\")),r.gullet.endGroup(),r.gullet.beginGroup();var t=r.fetch().text;if(t==="&"||t==="\\\\")r.consume();else if(t==="\\end"){e[e.length-1].length===0&&e.pop();break}else throw new M("Expected \\\\ or \\cr or \\end",r.nextToken)}for(var a=[],n=[a],s=0;s-1))if("<>AV".indexOf(p)>-1)for(var y=0;y<2;y++){for(var x=!0,w=c+1;wAV=|." after @',u[c]);var z=C1(p,g,r),T={type:"styling",body:[z],mode:"math",style:"display"};a.push(T),h=jt()}s%2===0?a.push(h):a.shift(),a=[],n.push(a)}r.gullet.endGroup(),r.gullet.endGroup();var C=new Array(n[0].length).fill({type:"align",align:"c",pregap:.25,postgap:.25});return{type:"array",mode:"math",body:n,arraystretch:1,addJot:!0,rowGaps:[null],cols:C,colSeparationType:"CD",hLinesBeforeRow:new Array(n.length+1).fill([])}}B({type:"cdlabel",names:["\\\\cdleft","\\\\cdright"],props:{numArgs:1},handler(r,e){var{parser:t,funcName:a}=r;return{type:"cdlabel",mode:t.mode,side:a.slice(4),label:e[0]}},htmlBuilder(r,e){var t=e.havingStyle(e.style.sup()),a=b.wrapFragment(L(r.label,t,e),e);return a.classes.push("cd-label-"+r.side),a.style.bottom=A(.8-a.depth),a.height=0,a.depth=0,a},mathmlBuilder(r,e){var t=new S.MathNode("mrow",[X(r.label,e)]);return t=new S.MathNode("mpadded",[t]),t.setAttribute("width","0"),r.side==="left"&&t.setAttribute("lspace","-1width"),t.setAttribute("voffset","0.7em"),t=new S.MathNode("mstyle",[t]),t.setAttribute("displaystyle","false"),t.setAttribute("scriptlevel","1"),t}});B({type:"cdlabelparent",names:["\\\\cdparent"],props:{numArgs:1},handler(r,e){var{parser:t}=r;return{type:"cdlabelparent",mode:t.mode,fragment:e[0]}},htmlBuilder(r,e){var t=b.wrapFragment(L(r.fragment,e),e);return t.classes.push("cd-vert-arrow"),t},mathmlBuilder(r,e){return new S.MathNode("mrow",[X(r.fragment,e)])}});B({type:"textord",names:["\\@char"],props:{numArgs:1,allowedInText:!0},handler(r,e){for(var{parser:t}=r,a=O(e[0],"ordgroup"),n=a.body,s="",u=0;u=1114111)throw new M("\\@char with invalid code point "+s);return c<=65535?p=String.fromCharCode(c):(c-=65536,p=String.fromCharCode((c>>10)+55296,(c&1023)+56320)),{type:"textord",mode:t.mode,text:p}}});var Ir=(r,e)=>{var t=t0(r.body,e.withColor(r.color),!1);return b.makeFragment(t)},Fr=(r,e)=>{var t=o0(r.body,e.withColor(r.color)),a=new S.MathNode("mstyle",t);return a.setAttribute("mathcolor",r.color),a};B({type:"color",names:["\\textcolor"],props:{numArgs:2,allowedInText:!0,argTypes:["color","original"]},handler(r,e){var{parser:t}=r,a=O(e[0],"color-token").color,n=e[1];return{type:"color",mode:t.mode,color:a,body:Q(n)}},htmlBuilder:Ir,mathmlBuilder:Fr});B({type:"color",names:["\\color"],props:{numArgs:1,allowedInText:!0,argTypes:["color"]},handler(r,e){var{parser:t,breakOnTokenText:a}=r,n=O(e[0],"color-token").color;t.gullet.macros.set("\\current@color",n);var s=t.parseExpression(!0,a);return{type:"color",mode:t.mode,color:n,body:s}},htmlBuilder:Ir,mathmlBuilder:Fr});B({type:"cr",names:["\\\\"],props:{numArgs:0,numOptionalArgs:0,allowedInText:!0},handler(r,e,t){var{parser:a}=r,n=a.gullet.future().text==="["?a.parseSizeGroup(!0):null,s=!a.settings.displayMode||!a.settings.useStrictBehavior("newLineInDisplayMode","In LaTeX, \\\\ or \\newline does nothing in display mode");return{type:"cr",mode:a.mode,newLine:s,size:n&&O(n,"size").value}},htmlBuilder(r,e){var t=b.makeSpan(["mspace"],[],e);return r.newLine&&(t.classes.push("newline"),r.size&&(t.style.marginTop=A(K(r.size,e)))),t},mathmlBuilder(r,e){var t=new S.MathNode("mspace");return r.newLine&&(t.setAttribute("linebreak","newline"),r.size&&t.setAttribute("height",A(K(r.size,e)))),t}});var ht={"\\global":"\\global","\\long":"\\\\globallong","\\\\globallong":"\\\\globallong","\\def":"\\gdef","\\gdef":"\\gdef","\\edef":"\\xdef","\\xdef":"\\xdef","\\let":"\\\\globallet","\\futurelet":"\\\\globalfuture"},Or=r=>{var e=r.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(e))throw new M("Expected a control sequence",r);return e},q1=r=>{var e=r.gullet.popToken();return e.text==="="&&(e=r.gullet.popToken(),e.text===" "&&(e=r.gullet.popToken())),e},Hr=(r,e,t,a)=>{var n=r.gullet.macros.get(t.text);n==null&&(t.noexpand=!0,n={tokens:[t],numArgs:0,unexpandable:!r.gullet.isExpandable(t.text)}),r.gullet.macros.set(e,n,a)};B({type:"internal",names:["\\global","\\long","\\\\globallong"],props:{numArgs:0,allowedInText:!0},handler(r){var{parser:e,funcName:t}=r;e.consumeSpaces();var a=e.fetch();if(ht[a.text])return(t==="\\global"||t==="\\\\globallong")&&(a.text=ht[a.text]),O(e.parseFunction(),"internal");throw new M("Invalid token after macro prefix",a)}});B({type:"internal",names:["\\def","\\gdef","\\edef","\\xdef"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(r){var{parser:e,funcName:t}=r,a=e.gullet.popToken(),n=a.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(n))throw new M("Expected a control sequence",a);for(var s=0,u,h=[[]];e.gullet.future().text!=="{";)if(a=e.gullet.popToken(),a.text==="#"){if(e.gullet.future().text==="{"){u=e.gullet.future(),h[s].push("{");break}if(a=e.gullet.popToken(),!/^[1-9]$/.test(a.text))throw new M('Invalid argument number "'+a.text+'"');if(parseInt(a.text)!==s+1)throw new M('Argument number "'+a.text+'" out of order');s++,h.push([])}else{if(a.text==="EOF")throw new M("Expected a macro definition");h[s].push(a.text)}var{tokens:c}=e.gullet.consumeArg();return u&&c.unshift(u),(t==="\\edef"||t==="\\xdef")&&(c=e.gullet.expandTokens(c),c.reverse()),e.gullet.macros.set(n,{tokens:c,numArgs:s,delimiters:h},t===ht[t]),{type:"internal",mode:e.mode}}});B({type:"internal",names:["\\let","\\\\globallet"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(r){var{parser:e,funcName:t}=r,a=Or(e.gullet.popToken());e.gullet.consumeSpaces();var n=q1(e);return Hr(e,a,n,t==="\\\\globallet"),{type:"internal",mode:e.mode}}});B({type:"internal",names:["\\futurelet","\\\\globalfuture"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(r){var{parser:e,funcName:t}=r,a=Or(e.gullet.popToken()),n=e.gullet.popToken(),s=e.gullet.popToken();return Hr(e,a,s,t==="\\\\globalfuture"),e.gullet.pushToken(s),e.gullet.pushToken(n),{type:"internal",mode:e.mode}}});var ie=function(e,t,a){var n=$.math[e]&&$.math[e].replace,s=pt(n||e,t,a);if(!s)throw new Error("Unsupported symbol "+e+" and font size "+t+".");return s},kt=function(e,t,a,n){var s=a.havingBaseStyle(t),u=b.makeSpan(n.concat(s.sizingClasses(a)),[e],a),h=s.sizeMultiplier/a.sizeMultiplier;return u.height*=h,u.depth*=h,u.maxFontSize=s.sizeMultiplier,u},Lr=function(e,t,a){var n=t.havingBaseStyle(a),s=(1-t.sizeMultiplier/n.sizeMultiplier)*t.fontMetrics().axisHeight;e.classes.push("delimcenter"),e.style.top=A(s),e.height-=s,e.depth+=s},R1=function(e,t,a,n,s,u){var h=b.makeSymbol(e,"Main-Regular",s,n),c=kt(h,t,n,u);return a&&Lr(c,n,t),c},E1=function(e,t,a,n){return b.makeSymbol(e,"Size"+t+"-Regular",a,n)},Pr=function(e,t,a,n,s,u){var h=E1(e,t,s,n),c=kt(b.makeSpan(["delimsizing","size"+t],[h],n),R.TEXT,n,u);return a&&Lr(c,n,R.TEXT),c},Ze=function(e,t,a){var n;t==="Size1-Regular"?n="delim-size1":n="delim-size4";var s=b.makeSpan(["delimsizinginner",n],[b.makeSpan([],[b.makeSymbol(e,t,a)])]);return{type:"elem",elem:s}},Ke=function(e,t,a){var n=x0["Size4-Regular"][e.charCodeAt(0)]?x0["Size4-Regular"][e.charCodeAt(0)][4]:x0["Size1-Regular"][e.charCodeAt(0)][4],s=new V0("inner",La(e,Math.round(1e3*t))),u=new C0([s],{width:A(n),height:A(t),style:"width:"+A(n),viewBox:"0 0 "+1e3*n+" "+Math.round(1e3*t),preserveAspectRatio:"xMinYMin"}),h=b.makeSvgSpan([],[u],a);return h.height=t,h.style.height=A(t),h.style.width=A(n),{type:"elem",elem:h}},mt=.008,Se={type:"kern",size:-1*mt},I1=["|","\\lvert","\\rvert","\\vert"],F1=["\\|","\\lVert","\\rVert","\\Vert"],Vr=function(e,t,a,n,s,u){var h,c,p,g,y="",x=0;h=p=g=e,c=null;var w="Size1-Regular";e==="\\uparrow"?p=g="⏐":e==="\\Uparrow"?p=g="‖":e==="\\downarrow"?h=p="⏐":e==="\\Downarrow"?h=p="‖":e==="\\updownarrow"?(h="\\uparrow",p="⏐",g="\\downarrow"):e==="\\Updownarrow"?(h="\\Uparrow",p="‖",g="\\Downarrow"):I1.includes(e)?(p="∣",y="vert",x=333):F1.includes(e)?(p="∥",y="doublevert",x=556):e==="["||e==="\\lbrack"?(h="⎡",p="⎢",g="⎣",w="Size4-Regular",y="lbrack",x=667):e==="]"||e==="\\rbrack"?(h="⎤",p="⎥",g="⎦",w="Size4-Regular",y="rbrack",x=667):e==="\\lfloor"||e==="⌊"?(p=h="⎢",g="⎣",w="Size4-Regular",y="lfloor",x=667):e==="\\lceil"||e==="⌈"?(h="⎡",p=g="⎢",w="Size4-Regular",y="lceil",x=667):e==="\\rfloor"||e==="⌋"?(p=h="⎥",g="⎦",w="Size4-Regular",y="rfloor",x=667):e==="\\rceil"||e==="⌉"?(h="⎤",p=g="⎥",w="Size4-Regular",y="rceil",x=667):e==="("||e==="\\lparen"?(h="⎛",p="⎜",g="⎝",w="Size4-Regular",y="lparen",x=875):e===")"||e==="\\rparen"?(h="⎞",p="⎟",g="⎠",w="Size4-Regular",y="rparen",x=875):e==="\\{"||e==="\\lbrace"?(h="⎧",c="⎨",g="⎩",p="⎪",w="Size4-Regular"):e==="\\}"||e==="\\rbrace"?(h="⎫",c="⎬",g="⎭",p="⎪",w="Size4-Regular"):e==="\\lgroup"||e==="⟮"?(h="⎧",g="⎩",p="⎪",w="Size4-Regular"):e==="\\rgroup"||e==="⟯"?(h="⎫",g="⎭",p="⎪",w="Size4-Regular"):e==="\\lmoustache"||e==="⎰"?(h="⎧",g="⎭",p="⎪",w="Size4-Regular"):(e==="\\rmoustache"||e==="⎱")&&(h="⎫",g="⎩",p="⎪",w="Size4-Regular");var z=ie(h,w,s),T=z.height+z.depth,C=ie(p,w,s),N=C.height+C.depth,I=ie(g,w,s),F=I.height+I.depth,G=0,H=1;if(c!==null){var U=ie(c,w,s);G=U.height+U.depth,H=2}var P=T+F+G,j=Math.max(0,Math.ceil((t-P)/(H*N))),Y=P+j*H*N,z0=n.fontMetrics().axisHeight;a&&(z0*=n.sizeMultiplier);var r0=Y/2-z0,e0=[];if(y.length>0){var Y0=Y-T-F,s0=Math.round(Y*1e3),g0=Pa(y,Math.round(Y0*1e3)),E0=new V0(y,g0),j0=(x/1e3).toFixed(3)+"em",Z0=(s0/1e3).toFixed(3)+"em",Le=new C0([E0],{width:j0,height:Z0,viewBox:"0 0 "+x+" "+s0}),I0=b.makeSvgSpan([],[Le],n);I0.height=s0/1e3,I0.style.width=j0,I0.style.height=Z0,e0.push({type:"elem",elem:I0})}else{if(e0.push(Ze(g,w,s)),e0.push(Se),c===null){var F0=Y-T-F+2*mt;e0.push(Ke(p,F0,n))}else{var d0=(Y-T-F-G)/2+2*mt;e0.push(Ke(p,d0,n)),e0.push(Se),e0.push(Ze(c,w,s)),e0.push(Se),e0.push(Ke(p,d0,n))}e0.push(Se),e0.push(Ze(h,w,s))}var ne=n.havingBaseStyle(R.TEXT),Pe=b.makeVList({positionType:"bottom",positionData:r0,children:e0},ne);return kt(b.makeSpan(["delimsizing","mult"],[Pe],ne),R.TEXT,n,u)},Je=80,Qe=.08,_e=function(e,t,a,n,s){var u=Ha(e,n,a),h=new V0(e,u),c=new C0([h],{width:"400em",height:A(t),viewBox:"0 0 400000 "+a,preserveAspectRatio:"xMinYMin slice"});return b.makeSvgSpan(["hide-tail"],[c],s)},O1=function(e,t){var a=t.havingBaseSizing(),n=Xr("\\surd",e*a.sizeMultiplier,Yr,a),s=a.sizeMultiplier,u=Math.max(0,t.minRuleThickness-t.fontMetrics().sqrtRuleThickness),h,c=0,p=0,g=0,y;return n.type==="small"?(g=1e3+1e3*u+Je,e<1?s=1:e<1.4&&(s=.7),c=(1+u+Qe)/s,p=(1+u)/s,h=_e("sqrtMain",c,g,u,t),h.style.minWidth="0.853em",y=.833/s):n.type==="large"?(g=(1e3+Je)*se[n.size],p=(se[n.size]+u)/s,c=(se[n.size]+u+Qe)/s,h=_e("sqrtSize"+n.size,c,g,u,t),h.style.minWidth="1.02em",y=1/s):(c=e+u+Qe,p=e+u,g=Math.floor(1e3*e+u)+Je,h=_e("sqrtTall",c,g,u,t),h.style.minWidth="0.742em",y=1.056),h.height=p,h.style.height=A(c),{span:h,advanceWidth:y,ruleWidth:(t.fontMetrics().sqrtRuleThickness+u)*s}},Gr=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","⌊","⌋","\\lceil","\\rceil","⌈","⌉","\\surd"],H1=["\\uparrow","\\downarrow","\\updownarrow","\\Uparrow","\\Downarrow","\\Updownarrow","|","\\|","\\vert","\\Vert","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","⟮","⟯","\\lmoustache","\\rmoustache","⎰","⎱"],Ur=["<",">","\\langle","\\rangle","/","\\backslash","\\lt","\\gt"],se=[0,1.2,1.8,2.4,3],L1=function(e,t,a,n,s){if(e==="<"||e==="\\lt"||e==="⟨"?e="\\langle":(e===">"||e==="\\gt"||e==="⟩")&&(e="\\rangle"),Gr.includes(e)||Ur.includes(e))return Pr(e,t,!1,a,n,s);if(H1.includes(e))return Vr(e,se[t],!1,a,n,s);throw new M("Illegal delimiter: '"+e+"'")},P1=[{type:"small",style:R.SCRIPTSCRIPT},{type:"small",style:R.SCRIPT},{type:"small",style:R.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4}],V1=[{type:"small",style:R.SCRIPTSCRIPT},{type:"small",style:R.SCRIPT},{type:"small",style:R.TEXT},{type:"stack"}],Yr=[{type:"small",style:R.SCRIPTSCRIPT},{type:"small",style:R.SCRIPT},{type:"small",style:R.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4},{type:"stack"}],G1=function(e){if(e.type==="small")return"Main-Regular";if(e.type==="large")return"Size"+e.size+"-Regular";if(e.type==="stack")return"Size4-Regular";throw new Error("Add support for delim type '"+e.type+"' here.")},Xr=function(e,t,a,n){for(var s=Math.min(2,3-n.style.size),u=s;ut)return a[u]}return a[a.length-1]},$r=function(e,t,a,n,s,u){e==="<"||e==="\\lt"||e==="⟨"?e="\\langle":(e===">"||e==="\\gt"||e==="⟩")&&(e="\\rangle");var h;Ur.includes(e)?h=P1:Gr.includes(e)?h=Yr:h=V1;var c=Xr(e,t,h,n);return c.type==="small"?R1(e,c.style,a,n,s,u):c.type==="large"?Pr(e,c.size,a,n,s,u):Vr(e,t,a,n,s,u)},U1=function(e,t,a,n,s,u){var h=n.fontMetrics().axisHeight*n.sizeMultiplier,c=901,p=5/n.fontMetrics().ptPerEm,g=Math.max(t-h,a+h),y=Math.max(g/500*c,2*g-p);return $r(e,y,!0,n,s,u)},D0={sqrtImage:O1,sizedDelim:L1,sizeToMaxHeight:se,customSizedDelim:$r,leftRightDelim:U1},Kt={"\\bigl":{mclass:"mopen",size:1},"\\Bigl":{mclass:"mopen",size:2},"\\biggl":{mclass:"mopen",size:3},"\\Biggl":{mclass:"mopen",size:4},"\\bigr":{mclass:"mclose",size:1},"\\Bigr":{mclass:"mclose",size:2},"\\biggr":{mclass:"mclose",size:3},"\\Biggr":{mclass:"mclose",size:4},"\\bigm":{mclass:"mrel",size:1},"\\Bigm":{mclass:"mrel",size:2},"\\biggm":{mclass:"mrel",size:3},"\\Biggm":{mclass:"mrel",size:4},"\\big":{mclass:"mord",size:1},"\\Big":{mclass:"mord",size:2},"\\bigg":{mclass:"mord",size:3},"\\Bigg":{mclass:"mord",size:4}},Y1=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","⌊","⌋","\\lceil","\\rceil","⌈","⌉","<",">","\\langle","⟨","\\rangle","⟩","\\lt","\\gt","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","⟮","⟯","\\lmoustache","\\rmoustache","⎰","⎱","/","\\backslash","|","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","."];function Fe(r,e){var t=Ee(r);if(t&&Y1.includes(t.text))return t;throw t?new M("Invalid delimiter '"+t.text+"' after '"+e.funcName+"'",r):new M("Invalid delimiter type '"+r.type+"'",r)}B({type:"delimsizing",names:["\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg"],props:{numArgs:1,argTypes:["primitive"]},handler:(r,e)=>{var t=Fe(e[0],r);return{type:"delimsizing",mode:r.parser.mode,size:Kt[r.funcName].size,mclass:Kt[r.funcName].mclass,delim:t.text}},htmlBuilder:(r,e)=>r.delim==="."?b.makeSpan([r.mclass]):D0.sizedDelim(r.delim,r.size,e,r.mode,[r.mclass]),mathmlBuilder:r=>{var e=[];r.delim!=="."&&e.push(v0(r.delim,r.mode));var t=new S.MathNode("mo",e);r.mclass==="mopen"||r.mclass==="mclose"?t.setAttribute("fence","true"):t.setAttribute("fence","false"),t.setAttribute("stretchy","true");var a=A(D0.sizeToMaxHeight[r.size]);return t.setAttribute("minsize",a),t.setAttribute("maxsize",a),t}});function Jt(r){if(!r.body)throw new Error("Bug: The leftright ParseNode wasn't fully parsed.")}B({type:"leftright-right",names:["\\right"],props:{numArgs:1,primitive:!0},handler:(r,e)=>{var t=r.parser.gullet.macros.get("\\current@color");if(t&&typeof t!="string")throw new M("\\current@color set to non-string in \\right");return{type:"leftright-right",mode:r.parser.mode,delim:Fe(e[0],r).text,color:t}}});B({type:"leftright",names:["\\left"],props:{numArgs:1,primitive:!0},handler:(r,e)=>{var t=Fe(e[0],r),a=r.parser;++a.leftrightDepth;var n=a.parseExpression(!1);--a.leftrightDepth,a.expect("\\right",!1);var s=O(a.parseFunction(),"leftright-right");return{type:"leftright",mode:a.mode,body:n,left:t.text,right:s.delim,rightColor:s.color}},htmlBuilder:(r,e)=>{Jt(r);for(var t=t0(r.body,e,!0,["mopen","mclose"]),a=0,n=0,s=!1,u=0;u{Jt(r);var t=o0(r.body,e);if(r.left!=="."){var a=new S.MathNode("mo",[v0(r.left,r.mode)]);a.setAttribute("fence","true"),t.unshift(a)}if(r.right!=="."){var n=new S.MathNode("mo",[v0(r.right,r.mode)]);n.setAttribute("fence","true"),r.rightColor&&n.setAttribute("mathcolor",r.rightColor),t.push(n)}return bt(t)}});B({type:"middle",names:["\\middle"],props:{numArgs:1,primitive:!0},handler:(r,e)=>{var t=Fe(e[0],r);if(!r.parser.leftrightDepth)throw new M("\\middle without preceding \\left",t);return{type:"middle",mode:r.parser.mode,delim:t.text}},htmlBuilder:(r,e)=>{var t;if(r.delim===".")t=ue(e,[]);else{t=D0.sizedDelim(r.delim,1,e,r.mode,[]);var a={delim:r.delim,options:e};t.isMiddle=a}return t},mathmlBuilder:(r,e)=>{var t=r.delim==="\\vert"||r.delim==="|"?v0("|","text"):v0(r.delim,r.mode),a=new S.MathNode("mo",[t]);return a.setAttribute("fence","true"),a.setAttribute("lspace","0.05em"),a.setAttribute("rspace","0.05em"),a}});var St=(r,e)=>{var t=b.wrapFragment(L(r.body,e),e),a=r.label.slice(1),n=e.sizeMultiplier,s,u=0,h=V.isCharacterBox(r.body);if(a==="sout")s=b.makeSpan(["stretchy","sout"]),s.height=e.fontMetrics().defaultRuleThickness/n,u=-.5*e.fontMetrics().xHeight;else if(a==="phase"){var c=K({number:.6,unit:"pt"},e),p=K({number:.35,unit:"ex"},e),g=e.havingBaseSizing();n=n/g.sizeMultiplier;var y=t.height+t.depth+c+p;t.style.paddingLeft=A(y/2+c);var x=Math.floor(1e3*y*n),w=Fa(x),z=new C0([new V0("phase",w)],{width:"400em",height:A(x/1e3),viewBox:"0 0 400000 "+x,preserveAspectRatio:"xMinYMin slice"});s=b.makeSvgSpan(["hide-tail"],[z],e),s.style.height=A(y),u=t.depth+c+p}else{/cancel/.test(a)?h||t.classes.push("cancel-pad"):a==="angl"?t.classes.push("anglpad"):t.classes.push("boxpad");var T=0,C=0,N=0;/box/.test(a)?(N=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness),T=e.fontMetrics().fboxsep+(a==="colorbox"?0:N),C=T):a==="angl"?(N=Math.max(e.fontMetrics().defaultRuleThickness,e.minRuleThickness),T=4*N,C=Math.max(0,.25-t.depth)):(T=h?.2:0,C=T),s=q0.encloseSpan(t,a,T,C,e),/fbox|boxed|fcolorbox/.test(a)?(s.style.borderStyle="solid",s.style.borderWidth=A(N)):a==="angl"&&N!==.049&&(s.style.borderTopWidth=A(N),s.style.borderRightWidth=A(N)),u=t.depth+C,r.backgroundColor&&(s.style.backgroundColor=r.backgroundColor,r.borderColor&&(s.style.borderColor=r.borderColor))}var I;if(r.backgroundColor)I=b.makeVList({positionType:"individualShift",children:[{type:"elem",elem:s,shift:u},{type:"elem",elem:t,shift:0}]},e);else{var F=/cancel|phase/.test(a)?["svg-align"]:[];I=b.makeVList({positionType:"individualShift",children:[{type:"elem",elem:t,shift:0},{type:"elem",elem:s,shift:u,wrapperClasses:F}]},e)}return/cancel/.test(a)&&(I.height=t.height,I.depth=t.depth),/cancel/.test(a)&&!h?b.makeSpan(["mord","cancel-lap"],[I],e):b.makeSpan(["mord"],[I],e)},Mt=(r,e)=>{var t=0,a=new S.MathNode(r.label.indexOf("colorbox")>-1?"mpadded":"menclose",[X(r.body,e)]);switch(r.label){case"\\cancel":a.setAttribute("notation","updiagonalstrike");break;case"\\bcancel":a.setAttribute("notation","downdiagonalstrike");break;case"\\phase":a.setAttribute("notation","phasorangle");break;case"\\sout":a.setAttribute("notation","horizontalstrike");break;case"\\fbox":a.setAttribute("notation","box");break;case"\\angl":a.setAttribute("notation","actuarial");break;case"\\fcolorbox":case"\\colorbox":if(t=e.fontMetrics().fboxsep*e.fontMetrics().ptPerEm,a.setAttribute("width","+"+2*t+"pt"),a.setAttribute("height","+"+2*t+"pt"),a.setAttribute("lspace",t+"pt"),a.setAttribute("voffset",t+"pt"),r.label==="\\fcolorbox"){var n=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness);a.setAttribute("style","border: "+n+"em solid "+String(r.borderColor))}break;case"\\xcancel":a.setAttribute("notation","updiagonalstrike downdiagonalstrike");break}return r.backgroundColor&&a.setAttribute("mathbackground",r.backgroundColor),a};B({type:"enclose",names:["\\colorbox"],props:{numArgs:2,allowedInText:!0,argTypes:["color","text"]},handler(r,e,t){var{parser:a,funcName:n}=r,s=O(e[0],"color-token").color,u=e[1];return{type:"enclose",mode:a.mode,label:n,backgroundColor:s,body:u}},htmlBuilder:St,mathmlBuilder:Mt});B({type:"enclose",names:["\\fcolorbox"],props:{numArgs:3,allowedInText:!0,argTypes:["color","color","text"]},handler(r,e,t){var{parser:a,funcName:n}=r,s=O(e[0],"color-token").color,u=O(e[1],"color-token").color,h=e[2];return{type:"enclose",mode:a.mode,label:n,backgroundColor:u,borderColor:s,body:h}},htmlBuilder:St,mathmlBuilder:Mt});B({type:"enclose",names:["\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler(r,e){var{parser:t}=r;return{type:"enclose",mode:t.mode,label:"\\fbox",body:e[0]}}});B({type:"enclose",names:["\\cancel","\\bcancel","\\xcancel","\\sout","\\phase"],props:{numArgs:1},handler(r,e){var{parser:t,funcName:a}=r,n=e[0];return{type:"enclose",mode:t.mode,label:a,body:n}},htmlBuilder:St,mathmlBuilder:Mt});B({type:"enclose",names:["\\angl"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!1},handler(r,e){var{parser:t}=r;return{type:"enclose",mode:t.mode,label:"\\angl",body:e[0]}}});var Wr={};function k0(r){for(var{type:e,names:t,props:a,handler:n,htmlBuilder:s,mathmlBuilder:u}=r,h={type:e,numArgs:a.numArgs||0,allowedInText:!1,numOptionalArgs:0,handler:n},c=0;c{var e=r.parser.settings;if(!e.displayMode)throw new M("{"+r.envName+"} can be used only in display mode.")};function zt(r){if(r.indexOf("ed")===-1)return r.indexOf("*")===-1}function U0(r,e,t){var{hskipBeforeAndAfter:a,addJot:n,cols:s,arraystretch:u,colSeparationType:h,autoTag:c,singleRow:p,emptySingleRow:g,maxNumCols:y,leqno:x}=e;if(r.gullet.beginGroup(),p||r.gullet.macros.set("\\cr","\\\\\\relax"),!u){var w=r.gullet.expandMacroAsText("\\arraystretch");if(w==null)u=1;else if(u=parseFloat(w),!u||u<0)throw new M("Invalid \\arraystretch: "+w)}r.gullet.beginGroup();var z=[],T=[z],C=[],N=[],I=c!=null?[]:void 0;function F(){c&&r.gullet.macros.set("\\@eqnsw","1",!0)}function G(){I&&(r.gullet.macros.get("\\df@tag")?(I.push(r.subparse([new m0("\\df@tag")])),r.gullet.macros.set("\\df@tag",void 0,!0)):I.push(!!c&&r.gullet.macros.get("\\@eqnsw")==="1"))}for(F(),N.push(Qt(r));;){var H=r.parseExpression(!1,p?"\\end":"\\\\");r.gullet.endGroup(),r.gullet.beginGroup(),H={type:"ordgroup",mode:r.mode,body:H},t&&(H={type:"styling",mode:r.mode,style:t,body:[H]}),z.push(H);var U=r.fetch().text;if(U==="&"){if(y&&z.length===y){if(p||h)throw new M("Too many tab characters: &",r.nextToken);r.settings.reportNonstrict("textEnv","Too few columns specified in the {array} column argument.")}r.consume()}else if(U==="\\end"){G(),z.length===1&&H.type==="styling"&&H.body[0].body.length===0&&(T.length>1||!g)&&T.pop(),N.length0&&(F+=.25),p.push({pos:F,isDashed:fe[pe]})}for(G(u[0]),a=0;a0&&(r0+=I,Pfe))for(a=0;a=h)){var J0=void 0;(n>0||e.hskipBeforeAndAfter)&&(J0=V.deflt(d0.pregap,x),J0!==0&&(g0=b.makeSpan(["arraycolsep"],[]),g0.style.width=A(J0),s0.push(g0)));var Q0=[];for(a=0;a0){for(var ca=b.makeLineSpan("hline",t,g),da=b.makeLineSpan("hdashline",t,g),Ve=[{type:"elem",elem:c,shift:0}];p.length>0;){var Et=p.pop(),It=Et.pos-e0;Et.isDashed?Ve.push({type:"elem",elem:da,shift:It}):Ve.push({type:"elem",elem:ca,shift:It})}c=b.makeVList({positionType:"individualShift",children:Ve},t)}if(j0.length===0)return b.makeSpan(["mord"],[c],t);var Ge=b.makeVList({positionType:"individualShift",children:j0},t);return Ge=b.makeSpan(["tag"],[Ge],t),b.makeFragment([c,Ge])},X1={c:"center ",l:"left ",r:"right "},M0=function(e,t){for(var a=[],n=new S.MathNode("mtd",[],["mtr-glue"]),s=new S.MathNode("mtd",[],["mml-eqn-num"]),u=0;u0){var z=e.cols,T="",C=!1,N=0,I=z.length;z[0].type==="separator"&&(x+="top ",N=1),z[z.length-1].type==="separator"&&(x+="bottom ",I-=1);for(var F=N;F0?"left ":"",x+=j[j.length-1].length>0?"right ":"";for(var Y=1;Y-1?"alignat":"align",s=e.envName==="split",u=U0(e.parser,{cols:a,addJot:!0,autoTag:s?void 0:zt(e.envName),emptySingleRow:!0,colSeparationType:n,maxNumCols:s?2:void 0,leqno:e.parser.settings.leqno},"display"),h,c=0,p={type:"ordgroup",mode:e.mode,body:[]};if(t[0]&&t[0].type==="ordgroup"){for(var g="",y=0;y0&&w&&(C=1),a[z]={type:"align",align:T,pregap:C,postgap:0}}return u.colSeparationType=w?"align":"alignat",u};k0({type:"array",names:["array","darray"],props:{numArgs:1},handler(r,e){var t=Ee(e[0]),a=t?[e[0]]:O(e[0],"ordgroup").body,n=a.map(function(u){var h=xt(u),c=h.text;if("lcr".indexOf(c)!==-1)return{type:"align",align:c};if(c==="|")return{type:"separator",separator:"|"};if(c===":")return{type:"separator",separator:":"};throw new M("Unknown column alignment: "+c,u)}),s={cols:n,hskipBeforeAndAfter:!0,maxNumCols:n.length};return U0(r.parser,s,At(r.envName))},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","matrix*","pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"],props:{numArgs:0},handler(r){var e={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[r.envName.replace("*","")],t="c",a={hskipBeforeAndAfter:!1,cols:[{type:"align",align:t}]};if(r.envName.charAt(r.envName.length-1)==="*"){var n=r.parser;if(n.consumeSpaces(),n.fetch().text==="["){if(n.consume(),n.consumeSpaces(),t=n.fetch().text,"lcr".indexOf(t)===-1)throw new M("Expected l or c or r",n.nextToken);n.consume(),n.consumeSpaces(),n.expect("]"),n.consume(),a.cols=[{type:"align",align:t}]}}var s=U0(r.parser,a,At(r.envName)),u=Math.max(0,...s.body.map(h=>h.length));return s.cols=new Array(u).fill({type:"align",align:t}),e?{type:"leftright",mode:r.mode,body:[s],left:e[0],right:e[1],rightColor:void 0}:s},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["smallmatrix"],props:{numArgs:0},handler(r){var e={arraystretch:.5},t=U0(r.parser,e,"script");return t.colSeparationType="small",t},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["subarray"],props:{numArgs:1},handler(r,e){var t=Ee(e[0]),a=t?[e[0]]:O(e[0],"ordgroup").body,n=a.map(function(u){var h=xt(u),c=h.text;if("lc".indexOf(c)!==-1)return{type:"align",align:c};throw new M("Unknown column alignment: "+c,u)});if(n.length>1)throw new M("{subarray} can contain only one column");var s={cols:n,hskipBeforeAndAfter:!1,arraystretch:.5};if(s=U0(r.parser,s,"script"),s.body.length>0&&s.body[0].length>1)throw new M("{subarray} can contain only one column");return s},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(r){var e={arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},t=U0(r.parser,e,At(r.envName));return{type:"leftright",mode:r.mode,body:[t],left:r.envName.indexOf("r")>-1?".":"\\{",right:r.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:Zr,htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(r){["gather","gather*"].includes(r.envName)&&Oe(r);var e={cols:[{type:"align",align:"c"}],addJot:!0,colSeparationType:"gather",autoTag:zt(r.envName),emptySingleRow:!0,leqno:r.parser.settings.leqno};return U0(r.parser,e,"display")},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:Zr,htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(r){Oe(r);var e={autoTag:zt(r.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,leqno:r.parser.settings.leqno};return U0(r.parser,e,"display")},htmlBuilder:S0,mathmlBuilder:M0});k0({type:"array",names:["CD"],props:{numArgs:0},handler(r){return Oe(r),N1(r.parser)},htmlBuilder:S0,mathmlBuilder:M0});m("\\nonumber","\\gdef\\@eqnsw{0}");m("\\notag","\\nonumber");B({type:"text",names:["\\hline","\\hdashline"],props:{numArgs:0,allowedInText:!0,allowedInMath:!0},handler(r,e){throw new M(r.funcName+" valid only within array environment")}});var _t=Wr;B({type:"environment",names:["\\begin","\\end"],props:{numArgs:1,argTypes:["text"]},handler(r,e){var{parser:t,funcName:a}=r,n=e[0];if(n.type!=="ordgroup")throw new M("Invalid environment name",n);for(var s="",u=0;u{var t=r.font,a=e.withFont(t);return L(r.body,a)},Jr=(r,e)=>{var t=r.font,a=e.withFont(t);return X(r.body,a)},er={"\\Bbb":"\\mathbb","\\bold":"\\mathbf","\\frak":"\\mathfrak","\\bm":"\\boldsymbol"};B({type:"font",names:["\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathsfit","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak"],props:{numArgs:1,allowedInArgument:!0},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=Ne(e[0]),s=a;return s in er&&(s=er[s]),{type:"font",mode:t.mode,font:s.slice(1),body:n}},htmlBuilder:Kr,mathmlBuilder:Jr});B({type:"mclass",names:["\\boldsymbol","\\bm"],props:{numArgs:1},handler:(r,e)=>{var{parser:t}=r,a=e[0],n=V.isCharacterBox(a);return{type:"mclass",mode:t.mode,mclass:Ie(a),body:[{type:"font",mode:t.mode,font:"boldsymbol",body:a}],isCharacterBox:n}}});B({type:"font",names:["\\rm","\\sf","\\tt","\\bf","\\it","\\cal"],props:{numArgs:0,allowedInText:!0},handler:(r,e)=>{var{parser:t,funcName:a,breakOnTokenText:n}=r,{mode:s}=t,u=t.parseExpression(!0,n),h="math"+a.slice(1);return{type:"font",mode:s,font:h,body:{type:"ordgroup",mode:t.mode,body:u}}},htmlBuilder:Kr,mathmlBuilder:Jr});var Qr=(r,e)=>{var t=e;return r==="display"?t=t.id>=R.SCRIPT.id?t.text():R.DISPLAY:r==="text"&&t.size===R.DISPLAY.size?t=R.TEXT:r==="script"?t=R.SCRIPT:r==="scriptscript"&&(t=R.SCRIPTSCRIPT),t},Tt=(r,e)=>{var t=Qr(r.size,e.style),a=t.fracNum(),n=t.fracDen(),s;s=e.havingStyle(a);var u=L(r.numer,s,e);if(r.continued){var h=8.5/e.fontMetrics().ptPerEm,c=3.5/e.fontMetrics().ptPerEm;u.height=u.height0?z=3*x:z=7*x,T=e.fontMetrics().denom1):(y>0?(w=e.fontMetrics().num2,z=x):(w=e.fontMetrics().num3,z=3*x),T=e.fontMetrics().denom2);var C;if(g){var I=e.fontMetrics().axisHeight;w-u.depth-(I+.5*y){var t=new S.MathNode("mfrac",[X(r.numer,e),X(r.denom,e)]);if(!r.hasBarLine)t.setAttribute("linethickness","0px");else if(r.barSize){var a=K(r.barSize,e);t.setAttribute("linethickness",A(a))}var n=Qr(r.size,e.style);if(n.size!==e.style.size){t=new S.MathNode("mstyle",[t]);var s=n.size===R.DISPLAY.size?"true":"false";t.setAttribute("displaystyle",s),t.setAttribute("scriptlevel","0")}if(r.leftDelim!=null||r.rightDelim!=null){var u=[];if(r.leftDelim!=null){var h=new S.MathNode("mo",[new S.TextNode(r.leftDelim.replace("\\",""))]);h.setAttribute("fence","true"),u.push(h)}if(u.push(t),r.rightDelim!=null){var c=new S.MathNode("mo",[new S.TextNode(r.rightDelim.replace("\\",""))]);c.setAttribute("fence","true"),u.push(c)}return bt(u)}return t};B({type:"genfrac",names:["\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0],s=e[1],u,h=null,c=null,p="auto";switch(a){case"\\dfrac":case"\\frac":case"\\tfrac":u=!0;break;case"\\\\atopfrac":u=!1;break;case"\\dbinom":case"\\binom":case"\\tbinom":u=!1,h="(",c=")";break;case"\\\\bracefrac":u=!1,h="\\{",c="\\}";break;case"\\\\brackfrac":u=!1,h="[",c="]";break;default:throw new Error("Unrecognized genfrac command")}switch(a){case"\\dfrac":case"\\dbinom":p="display";break;case"\\tfrac":case"\\tbinom":p="text";break}return{type:"genfrac",mode:t.mode,continued:!1,numer:n,denom:s,hasBarLine:u,leftDelim:h,rightDelim:c,size:p,barSize:null}},htmlBuilder:Tt,mathmlBuilder:Bt});B({type:"genfrac",names:["\\cfrac"],props:{numArgs:2},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0],s=e[1];return{type:"genfrac",mode:t.mode,continued:!0,numer:n,denom:s,hasBarLine:!0,leftDelim:null,rightDelim:null,size:"display",barSize:null}}});B({type:"infix",names:["\\over","\\choose","\\atop","\\brace","\\brack"],props:{numArgs:0,infix:!0},handler(r){var{parser:e,funcName:t,token:a}=r,n;switch(t){case"\\over":n="\\frac";break;case"\\choose":n="\\binom";break;case"\\atop":n="\\\\atopfrac";break;case"\\brace":n="\\\\bracefrac";break;case"\\brack":n="\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:e.mode,replaceWith:n,token:a}}});var tr=["display","text","script","scriptscript"],rr=function(e){var t=null;return e.length>0&&(t=e,t=t==="."?null:t),t};B({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler(r,e){var{parser:t}=r,a=e[4],n=e[5],s=Ne(e[0]),u=s.type==="atom"&&s.family==="open"?rr(s.text):null,h=Ne(e[1]),c=h.type==="atom"&&h.family==="close"?rr(h.text):null,p=O(e[2],"size"),g,y=null;p.isBlank?g=!0:(y=p.value,g=y.number>0);var x="auto",w=e[3];if(w.type==="ordgroup"){if(w.body.length>0){var z=O(w.body[0],"textord");x=tr[Number(z.text)]}}else w=O(w,"textord"),x=tr[Number(w.text)];return{type:"genfrac",mode:t.mode,numer:a,denom:n,continued:!1,hasBarLine:g,barSize:y,leftDelim:u,rightDelim:c,size:x}},htmlBuilder:Tt,mathmlBuilder:Bt});B({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler(r,e){var{parser:t,funcName:a,token:n}=r;return{type:"infix",mode:t.mode,replaceWith:"\\\\abovefrac",size:O(e[0],"size").value,token:n}}});B({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0],s=wa(O(e[1],"infix").size),u=e[2],h=s.number>0;return{type:"genfrac",mode:t.mode,numer:n,denom:u,continued:!1,hasBarLine:h,barSize:s,leftDelim:null,rightDelim:null,size:"auto"}},htmlBuilder:Tt,mathmlBuilder:Bt});var _r=(r,e)=>{var t=e.style,a,n;r.type==="supsub"?(a=r.sup?L(r.sup,e.havingStyle(t.sup()),e):L(r.sub,e.havingStyle(t.sub()),e),n=O(r.base,"horizBrace")):n=O(r,"horizBrace");var s=L(n.base,e.havingBaseStyle(R.DISPLAY)),u=q0.svgSpan(n,e),h;if(n.isOver?(h=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:u}]},e),h.children[0].children[0].children[1].classes.push("svg-align")):(h=b.makeVList({positionType:"bottom",positionData:s.depth+.1+u.height,children:[{type:"elem",elem:u},{type:"kern",size:.1},{type:"elem",elem:s}]},e),h.children[0].children[0].children[0].classes.push("svg-align")),a){var c=b.makeSpan(["mord",n.isOver?"mover":"munder"],[h],e);n.isOver?h=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:c},{type:"kern",size:.2},{type:"elem",elem:a}]},e):h=b.makeVList({positionType:"bottom",positionData:c.depth+.2+a.height+a.depth,children:[{type:"elem",elem:a},{type:"kern",size:.2},{type:"elem",elem:c}]},e)}return b.makeSpan(["mord",n.isOver?"mover":"munder"],[h],e)},$1=(r,e)=>{var t=q0.mathMLnode(r.label);return new S.MathNode(r.isOver?"mover":"munder",[X(r.base,e),t])};B({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler(r,e){var{parser:t,funcName:a}=r;return{type:"horizBrace",mode:t.mode,label:a,isOver:/^\\over/.test(a),base:e[0]}},htmlBuilder:_r,mathmlBuilder:$1});B({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:(r,e)=>{var{parser:t}=r,a=e[1],n=O(e[0],"url").url;return t.settings.isTrusted({command:"\\href",url:n})?{type:"href",mode:t.mode,href:n,body:Q(a)}:t.formatUnsupportedCmd("\\href")},htmlBuilder:(r,e)=>{var t=t0(r.body,e,!1);return b.makeAnchor(r.href,[],t,e)},mathmlBuilder:(r,e)=>{var t=G0(r.body,e);return t instanceof h0||(t=new h0("mrow",[t])),t.setAttribute("href",r.href),t}});B({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:(r,e)=>{var{parser:t}=r,a=O(e[0],"url").url;if(!t.settings.isTrusted({command:"\\url",url:a}))return t.formatUnsupportedCmd("\\url");for(var n=[],s=0;s{var{parser:t,funcName:a,token:n}=r,s=O(e[0],"raw").string,u=e[1];t.settings.strict&&t.settings.reportNonstrict("htmlExtension","HTML extension is disabled on strict mode");var h,c={};switch(a){case"\\htmlClass":c.class=s,h={command:"\\htmlClass",class:s};break;case"\\htmlId":c.id=s,h={command:"\\htmlId",id:s};break;case"\\htmlStyle":c.style=s,h={command:"\\htmlStyle",style:s};break;case"\\htmlData":{for(var p=s.split(","),g=0;g{var t=t0(r.body,e,!1),a=["enclosing"];r.attributes.class&&a.push(...r.attributes.class.trim().split(/\s+/));var n=b.makeSpan(a,t,e);for(var s in r.attributes)s!=="class"&&r.attributes.hasOwnProperty(s)&&n.setAttribute(s,r.attributes[s]);return n},mathmlBuilder:(r,e)=>G0(r.body,e)});B({type:"htmlmathml",names:["\\html@mathml"],props:{numArgs:2,allowedInText:!0},handler:(r,e)=>{var{parser:t}=r;return{type:"htmlmathml",mode:t.mode,html:Q(e[0]),mathml:Q(e[1])}},htmlBuilder:(r,e)=>{var t=t0(r.html,e,!1);return b.makeFragment(t)},mathmlBuilder:(r,e)=>G0(r.mathml,e)});var et=function(e){if(/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e))return{number:+e,unit:"bp"};var t=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e);if(!t)throw new M("Invalid size: '"+e+"' in \\includegraphics");var a={number:+(t[1]+t[2]),unit:t[3]};if(!br(a))throw new M("Invalid unit: '"+a.unit+"' in \\includegraphics.");return a};B({type:"includegraphics",names:["\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:(r,e,t)=>{var{parser:a}=r,n={number:0,unit:"em"},s={number:.9,unit:"em"},u={number:0,unit:"em"},h="";if(t[0])for(var c=O(t[0],"raw").string,p=c.split(","),g=0;g{var t=K(r.height,e),a=0;r.totalheight.number>0&&(a=K(r.totalheight,e)-t);var n=0;r.width.number>0&&(n=K(r.width,e));var s={height:A(t+a)};n>0&&(s.width=A(n)),a>0&&(s.verticalAlign=A(-a));var u=new $a(r.src,r.alt,s);return u.height=t,u.depth=a,u},mathmlBuilder:(r,e)=>{var t=new S.MathNode("mglyph",[]);t.setAttribute("alt",r.alt);var a=K(r.height,e),n=0;if(r.totalheight.number>0&&(n=K(r.totalheight,e)-a,t.setAttribute("valign",A(-n))),t.setAttribute("height",A(a+n)),r.width.number>0){var s=K(r.width,e);t.setAttribute("width",A(s))}return t.setAttribute("src",r.src),t}});B({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler(r,e){var{parser:t,funcName:a}=r,n=O(e[0],"size");if(t.settings.strict){var s=a[1]==="m",u=n.value.unit==="mu";s?(u||t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+a+" supports only mu units, "+("not "+n.value.unit+" units")),t.mode!=="math"&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+a+" works only in math mode")):u&&t.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+a+" doesn't support mu units")}return{type:"kern",mode:t.mode,dimension:n.value}},htmlBuilder(r,e){return b.makeGlue(r.dimension,e)},mathmlBuilder(r,e){var t=K(r.dimension,e);return new S.SpaceNode(t)}});B({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0];return{type:"lap",mode:t.mode,alignment:a.slice(5),body:n}},htmlBuilder:(r,e)=>{var t;r.alignment==="clap"?(t=b.makeSpan([],[L(r.body,e)]),t=b.makeSpan(["inner"],[t],e)):t=b.makeSpan(["inner"],[L(r.body,e)]);var a=b.makeSpan(["fix"],[]),n=b.makeSpan([r.alignment],[t,a],e),s=b.makeSpan(["strut"]);return s.style.height=A(n.height+n.depth),n.depth&&(s.style.verticalAlign=A(-n.depth)),n.children.unshift(s),n=b.makeSpan(["thinbox"],[n],e),b.makeSpan(["mord","vbox"],[n],e)},mathmlBuilder:(r,e)=>{var t=new S.MathNode("mpadded",[X(r.body,e)]);if(r.alignment!=="rlap"){var a=r.alignment==="llap"?"-1":"-0.5";t.setAttribute("lspace",a+"width")}return t.setAttribute("width","0px"),t}});B({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(r,e){var{funcName:t,parser:a}=r,n=a.mode;a.switchMode("math");var s=t==="\\("?"\\)":"$",u=a.parseExpression(!1,s);return a.expect(s),a.switchMode(n),{type:"styling",mode:a.mode,style:"text",body:u}}});B({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(r,e){throw new M("Mismatched "+r.funcName)}});var ar=(r,e)=>{switch(e.style.size){case R.DISPLAY.size:return r.display;case R.TEXT.size:return r.text;case R.SCRIPT.size:return r.script;case R.SCRIPTSCRIPT.size:return r.scriptscript;default:return r.text}};B({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:(r,e)=>{var{parser:t}=r;return{type:"mathchoice",mode:t.mode,display:Q(e[0]),text:Q(e[1]),script:Q(e[2]),scriptscript:Q(e[3])}},htmlBuilder:(r,e)=>{var t=ar(r,e),a=t0(t,e,!1);return b.makeFragment(a)},mathmlBuilder:(r,e)=>{var t=ar(r,e);return G0(t,e)}});var ea=(r,e,t,a,n,s,u)=>{r=b.makeSpan([],[r]);var h=t&&V.isCharacterBox(t),c,p;if(e){var g=L(e,a.havingStyle(n.sup()),a);p={elem:g,kern:Math.max(a.fontMetrics().bigOpSpacing1,a.fontMetrics().bigOpSpacing3-g.depth)}}if(t){var y=L(t,a.havingStyle(n.sub()),a);c={elem:y,kern:Math.max(a.fontMetrics().bigOpSpacing2,a.fontMetrics().bigOpSpacing4-y.height)}}var x;if(p&&c){var w=a.fontMetrics().bigOpSpacing5+c.elem.height+c.elem.depth+c.kern+r.depth+u;x=b.makeVList({positionType:"bottom",positionData:w,children:[{type:"kern",size:a.fontMetrics().bigOpSpacing5},{type:"elem",elem:c.elem,marginLeft:A(-s)},{type:"kern",size:c.kern},{type:"elem",elem:r},{type:"kern",size:p.kern},{type:"elem",elem:p.elem,marginLeft:A(s)},{type:"kern",size:a.fontMetrics().bigOpSpacing5}]},a)}else if(c){var z=r.height-u;x=b.makeVList({positionType:"top",positionData:z,children:[{type:"kern",size:a.fontMetrics().bigOpSpacing5},{type:"elem",elem:c.elem,marginLeft:A(-s)},{type:"kern",size:c.kern},{type:"elem",elem:r}]},a)}else if(p){var T=r.depth+u;x=b.makeVList({positionType:"bottom",positionData:T,children:[{type:"elem",elem:r},{type:"kern",size:p.kern},{type:"elem",elem:p.elem,marginLeft:A(s)},{type:"kern",size:a.fontMetrics().bigOpSpacing5}]},a)}else return r;var C=[x];if(c&&s!==0&&!h){var N=b.makeSpan(["mspace"],[],a);N.style.marginRight=A(s),C.unshift(N)}return b.makeSpan(["mop","op-limits"],C,a)},ta=["\\smallint"],ae=(r,e)=>{var t,a,n=!1,s;r.type==="supsub"?(t=r.sup,a=r.sub,s=O(r.base,"op"),n=!0):s=O(r,"op");var u=e.style,h=!1;u.size===R.DISPLAY.size&&s.symbol&&!ta.includes(s.name)&&(h=!0);var c;if(s.symbol){var p=h?"Size2-Regular":"Size1-Regular",g="";if((s.name==="\\oiint"||s.name==="\\oiiint")&&(g=s.name.slice(1),s.name=g==="oiint"?"\\iint":"\\iiint"),c=b.makeSymbol(s.name,p,"math",e,["mop","op-symbol",h?"large-op":"small-op"]),g.length>0){var y=c.italic,x=b.staticSvg(g+"Size"+(h?"2":"1"),e);c=b.makeVList({positionType:"individualShift",children:[{type:"elem",elem:c,shift:0},{type:"elem",elem:x,shift:h?.08:0}]},e),s.name="\\"+g,c.classes.unshift("mop"),c.italic=y}}else if(s.body){var w=t0(s.body,e,!0);w.length===1&&w[0]instanceof p0?(c=w[0],c.classes[0]="mop"):c=b.makeSpan(["mop"],w,e)}else{for(var z=[],T=1;T{var t;if(r.symbol)t=new h0("mo",[v0(r.name,r.mode)]),ta.includes(r.name)&&t.setAttribute("largeop","false");else if(r.body)t=new h0("mo",o0(r.body,e));else{t=new h0("mi",[new w0(r.name.slice(1))]);var a=new h0("mo",[v0("⁡","text")]);r.parentIsSupSub?t=new h0("mrow",[t,a]):t=Dr([t,a])}return t},W1={"∏":"\\prod","∐":"\\coprod","∑":"\\sum","⋀":"\\bigwedge","⋁":"\\bigvee","⋂":"\\bigcap","⋃":"\\bigcup","⨀":"\\bigodot","⨁":"\\bigoplus","⨂":"\\bigotimes","⨄":"\\biguplus","⨆":"\\bigsqcup"};B({type:"op",names:["\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","∏","∐","∑","⋀","⋁","⋂","⋃","⨀","⨁","⨂","⨄","⨆"],props:{numArgs:0},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=a;return n.length===1&&(n=W1[n]),{type:"op",mode:t.mode,limits:!0,parentIsSupSub:!1,symbol:!0,name:n}},htmlBuilder:ae,mathmlBuilder:me});B({type:"op",names:["\\mathop"],props:{numArgs:1,primitive:!0},handler:(r,e)=>{var{parser:t}=r,a=e[0];return{type:"op",mode:t.mode,limits:!1,parentIsSupSub:!1,symbol:!1,body:Q(a)}},htmlBuilder:ae,mathmlBuilder:me});var j1={"∫":"\\int","∬":"\\iint","∭":"\\iiint","∮":"\\oint","∯":"\\oiint","∰":"\\oiiint"};B({type:"op",names:["\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th"],props:{numArgs:0},handler(r){var{parser:e,funcName:t}=r;return{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:ae,mathmlBuilder:me});B({type:"op",names:["\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup"],props:{numArgs:0},handler(r){var{parser:e,funcName:t}=r;return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!1,name:t}},htmlBuilder:ae,mathmlBuilder:me});B({type:"op",names:["\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","∫","∬","∭","∮","∯","∰"],props:{numArgs:0,allowedInArgument:!0},handler(r){var{parser:e,funcName:t}=r,a=t;return a.length===1&&(a=j1[a]),{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!0,name:a}},htmlBuilder:ae,mathmlBuilder:me});var ra=(r,e)=>{var t,a,n=!1,s;r.type==="supsub"?(t=r.sup,a=r.sub,s=O(r.base,"operatorname"),n=!0):s=O(r,"operatorname");var u;if(s.body.length>0){for(var h=s.body.map(y=>{var x=y.text;return typeof x=="string"?{type:"textord",mode:y.mode,text:x}:y}),c=t0(h,e.withFont("mathrm"),!0),p=0;p{for(var t=o0(r.body,e.withFont("mathrm")),a=!0,n=0;ng.toText()).join("");t=[new S.TextNode(h)]}var c=new S.MathNode("mi",t);c.setAttribute("mathvariant","normal");var p=new S.MathNode("mo",[v0("⁡","text")]);return r.parentIsSupSub?new S.MathNode("mrow",[c,p]):S.newDocumentFragment([c,p])};B({type:"operatorname",names:["\\operatorname@","\\operatornamewithlimits"],props:{numArgs:1},handler:(r,e)=>{var{parser:t,funcName:a}=r,n=e[0];return{type:"operatorname",mode:t.mode,body:Q(n),alwaysHandleSupSub:a==="\\operatornamewithlimits",limits:!1,parentIsSupSub:!1}},htmlBuilder:ra,mathmlBuilder:Z1});m("\\operatorname","\\@ifstar\\operatornamewithlimits\\operatorname@");W0({type:"ordgroup",htmlBuilder(r,e){return r.semisimple?b.makeFragment(t0(r.body,e,!1)):b.makeSpan(["mord"],t0(r.body,e,!0),e)},mathmlBuilder(r,e){return G0(r.body,e,!0)}});B({type:"overline",names:["\\overline"],props:{numArgs:1},handler(r,e){var{parser:t}=r,a=e[0];return{type:"overline",mode:t.mode,body:a}},htmlBuilder(r,e){var t=L(r.body,e.havingCrampedStyle()),a=b.makeLineSpan("overline-line",e),n=e.fontMetrics().defaultRuleThickness,s=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t},{type:"kern",size:3*n},{type:"elem",elem:a},{type:"kern",size:n}]},e);return b.makeSpan(["mord","overline"],[s],e)},mathmlBuilder(r,e){var t=new S.MathNode("mo",[new S.TextNode("‾")]);t.setAttribute("stretchy","true");var a=new S.MathNode("mover",[X(r.body,e),t]);return a.setAttribute("accent","true"),a}});B({type:"phantom",names:["\\phantom"],props:{numArgs:1,allowedInText:!0},handler:(r,e)=>{var{parser:t}=r,a=e[0];return{type:"phantom",mode:t.mode,body:Q(a)}},htmlBuilder:(r,e)=>{var t=t0(r.body,e.withPhantom(),!1);return b.makeFragment(t)},mathmlBuilder:(r,e)=>{var t=o0(r.body,e);return new S.MathNode("mphantom",t)}});B({type:"hphantom",names:["\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:(r,e)=>{var{parser:t}=r,a=e[0];return{type:"hphantom",mode:t.mode,body:a}},htmlBuilder:(r,e)=>{var t=b.makeSpan([],[L(r.body,e.withPhantom())]);if(t.height=0,t.depth=0,t.children)for(var a=0;a{var t=o0(Q(r.body),e),a=new S.MathNode("mphantom",t),n=new S.MathNode("mpadded",[a]);return n.setAttribute("height","0px"),n.setAttribute("depth","0px"),n}});B({type:"vphantom",names:["\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:(r,e)=>{var{parser:t}=r,a=e[0];return{type:"vphantom",mode:t.mode,body:a}},htmlBuilder:(r,e)=>{var t=b.makeSpan(["inner"],[L(r.body,e.withPhantom())]),a=b.makeSpan(["fix"],[]);return b.makeSpan(["mord","rlap"],[t,a],e)},mathmlBuilder:(r,e)=>{var t=o0(Q(r.body),e),a=new S.MathNode("mphantom",t),n=new S.MathNode("mpadded",[a]);return n.setAttribute("width","0px"),n}});B({type:"raisebox",names:["\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler(r,e){var{parser:t}=r,a=O(e[0],"size").value,n=e[1];return{type:"raisebox",mode:t.mode,dy:a,body:n}},htmlBuilder(r,e){var t=L(r.body,e),a=K(r.dy,e);return b.makeVList({positionType:"shift",positionData:-a,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(r,e){var t=new S.MathNode("mpadded",[X(r.body,e)]),a=r.dy.number+r.dy.unit;return t.setAttribute("voffset",a),t}});B({type:"internal",names:["\\relax"],props:{numArgs:0,allowedInText:!0,allowedInArgument:!0},handler(r){var{parser:e}=r;return{type:"internal",mode:e.mode}}});B({type:"rule",names:["\\rule"],props:{numArgs:2,numOptionalArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["size","size","size"]},handler(r,e,t){var{parser:a}=r,n=t[0],s=O(e[0],"size"),u=O(e[1],"size");return{type:"rule",mode:a.mode,shift:n&&O(n,"size").value,width:s.value,height:u.value}},htmlBuilder(r,e){var t=b.makeSpan(["mord","rule"],[],e),a=K(r.width,e),n=K(r.height,e),s=r.shift?K(r.shift,e):0;return t.style.borderRightWidth=A(a),t.style.borderTopWidth=A(n),t.style.bottom=A(s),t.width=a,t.height=n+s,t.depth=-s,t.maxFontSize=n*1.125*e.sizeMultiplier,t},mathmlBuilder(r,e){var t=K(r.width,e),a=K(r.height,e),n=r.shift?K(r.shift,e):0,s=e.color&&e.getColor()||"black",u=new S.MathNode("mspace");u.setAttribute("mathbackground",s),u.setAttribute("width",A(t)),u.setAttribute("height",A(a));var h=new S.MathNode("mpadded",[u]);return n>=0?h.setAttribute("height",A(n)):(h.setAttribute("height",A(n)),h.setAttribute("depth",A(-n))),h.setAttribute("voffset",A(n)),h}});function aa(r,e,t){for(var a=t0(r,e,!1),n=e.sizeMultiplier/t.sizeMultiplier,s=0;s{var t=e.havingSize(r.size);return aa(r.body,t,e)};B({type:"sizing",names:nr,props:{numArgs:0,allowedInText:!0},handler:(r,e)=>{var{breakOnTokenText:t,funcName:a,parser:n}=r,s=n.parseExpression(!1,t);return{type:"sizing",mode:n.mode,size:nr.indexOf(a)+1,body:s}},htmlBuilder:K1,mathmlBuilder:(r,e)=>{var t=e.havingSize(r.size),a=o0(r.body,t),n=new S.MathNode("mstyle",a);return n.setAttribute("mathsize",A(t.sizeMultiplier)),n}});B({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:(r,e,t)=>{var{parser:a}=r,n=!1,s=!1,u=t[0]&&O(t[0],"ordgroup");if(u)for(var h="",c=0;c{var t=b.makeSpan([],[L(r.body,e)]);if(!r.smashHeight&&!r.smashDepth)return t;if(r.smashHeight&&(t.height=0,t.children))for(var a=0;a{var t=new S.MathNode("mpadded",[X(r.body,e)]);return r.smashHeight&&t.setAttribute("height","0px"),r.smashDepth&&t.setAttribute("depth","0px"),t}});B({type:"sqrt",names:["\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler(r,e,t){var{parser:a}=r,n=t[0],s=e[0];return{type:"sqrt",mode:a.mode,body:s,index:n}},htmlBuilder(r,e){var t=L(r.body,e.havingCrampedStyle());t.height===0&&(t.height=e.fontMetrics().xHeight),t=b.wrapFragment(t,e);var a=e.fontMetrics(),n=a.defaultRuleThickness,s=n;e.style.idt.height+t.depth+u&&(u=(u+y-t.height-t.depth)/2);var x=c.height-t.height-u-p;t.style.paddingLeft=A(g);var w=b.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:t,wrapperClasses:["svg-align"]},{type:"kern",size:-(t.height+x)},{type:"elem",elem:c},{type:"kern",size:p}]},e);if(r.index){var z=e.havingStyle(R.SCRIPTSCRIPT),T=L(r.index,z,e),C=.6*(w.height-w.depth),N=b.makeVList({positionType:"shift",positionData:-C,children:[{type:"elem",elem:T}]},e),I=b.makeSpan(["root"],[N]);return b.makeSpan(["mord","sqrt"],[I,w],e)}else return b.makeSpan(["mord","sqrt"],[w],e)},mathmlBuilder(r,e){var{body:t,index:a}=r;return a?new S.MathNode("mroot",[X(t,e),X(a,e)]):new S.MathNode("msqrt",[X(t,e)])}});var ir={display:R.DISPLAY,text:R.TEXT,script:R.SCRIPT,scriptscript:R.SCRIPTSCRIPT};B({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(r,e){var{breakOnTokenText:t,funcName:a,parser:n}=r,s=n.parseExpression(!0,t),u=a.slice(1,a.length-5);return{type:"styling",mode:n.mode,style:u,body:s}},htmlBuilder(r,e){var t=ir[r.style],a=e.havingStyle(t).withFont("");return aa(r.body,a,e)},mathmlBuilder(r,e){var t=ir[r.style],a=e.havingStyle(t),n=o0(r.body,a),s=new S.MathNode("mstyle",n),u={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]},h=u[r.style];return s.setAttribute("scriptlevel",h[0]),s.setAttribute("displaystyle",h[1]),s}});var J1=function(e,t){var a=e.base;if(a)if(a.type==="op"){var n=a.limits&&(t.style.size===R.DISPLAY.size||a.alwaysHandleSupSub);return n?ae:null}else if(a.type==="operatorname"){var s=a.alwaysHandleSupSub&&(t.style.size===R.DISPLAY.size||a.limits);return s?ra:null}else{if(a.type==="accent")return V.isCharacterBox(a.base)?wt:null;if(a.type==="horizBrace"){var u=!e.sub;return u===a.isOver?_r:null}else return null}else return null};W0({type:"supsub",htmlBuilder(r,e){var t=J1(r,e);if(t)return t(r,e);var{base:a,sup:n,sub:s}=r,u=L(a,e),h,c,p=e.fontMetrics(),g=0,y=0,x=a&&V.isCharacterBox(a);if(n){var w=e.havingStyle(e.style.sup());h=L(n,w,e),x||(g=u.height-w.fontMetrics().supDrop*w.sizeMultiplier/e.sizeMultiplier)}if(s){var z=e.havingStyle(e.style.sub());c=L(s,z,e),x||(y=u.depth+z.fontMetrics().subDrop*z.sizeMultiplier/e.sizeMultiplier)}var T;e.style===R.DISPLAY?T=p.sup1:e.style.cramped?T=p.sup3:T=p.sup2;var C=e.sizeMultiplier,N=A(.5/p.ptPerEm/C),I=null;if(c){var F=r.base&&r.base.type==="op"&&r.base.name&&(r.base.name==="\\oiint"||r.base.name==="\\oiiint");(u instanceof p0||F)&&(I=A(-u.italic))}var G;if(h&&c){g=Math.max(g,T,h.depth+.25*p.xHeight),y=Math.max(y,p.sub2);var H=p.defaultRuleThickness,U=4*H;if(g-h.depth-(c.height-y)0&&(g+=P,y-=P)}var j=[{type:"elem",elem:c,shift:y,marginRight:N,marginLeft:I},{type:"elem",elem:h,shift:-g,marginRight:N}];G=b.makeVList({positionType:"individualShift",children:j},e)}else if(c){y=Math.max(y,p.sub1,c.height-.8*p.xHeight);var Y=[{type:"elem",elem:c,marginLeft:I,marginRight:N}];G=b.makeVList({positionType:"shift",positionData:y,children:Y},e)}else if(h)g=Math.max(g,T,h.depth+.25*p.xHeight),G=b.makeVList({positionType:"shift",positionData:-g,children:[{type:"elem",elem:h,marginRight:N}]},e);else throw new Error("supsub must have either sup or sub.");var z0=ut(u,"right")||"mord";return b.makeSpan([z0],[u,b.makeSpan(["msupsub"],[G])],e)},mathmlBuilder(r,e){var t=!1,a,n;r.base&&r.base.type==="horizBrace"&&(n=!!r.sup,n===r.base.isOver&&(t=!0,a=r.base.isOver)),r.base&&(r.base.type==="op"||r.base.type==="operatorname")&&(r.base.parentIsSupSub=!0);var s=[X(r.base,e)];r.sub&&s.push(X(r.sub,e)),r.sup&&s.push(X(r.sup,e));var u;if(t)u=a?"mover":"munder";else if(r.sub)if(r.sup){var p=r.base;p&&p.type==="op"&&p.limits&&e.style===R.DISPLAY||p&&p.type==="operatorname"&&p.alwaysHandleSupSub&&(e.style===R.DISPLAY||p.limits)?u="munderover":u="msubsup"}else{var c=r.base;c&&c.type==="op"&&c.limits&&(e.style===R.DISPLAY||c.alwaysHandleSupSub)||c&&c.type==="operatorname"&&c.alwaysHandleSupSub&&(c.limits||e.style===R.DISPLAY)?u="munder":u="msub"}else{var h=r.base;h&&h.type==="op"&&h.limits&&(e.style===R.DISPLAY||h.alwaysHandleSupSub)||h&&h.type==="operatorname"&&h.alwaysHandleSupSub&&(h.limits||e.style===R.DISPLAY)?u="mover":u="msup"}return new S.MathNode(u,s)}});W0({type:"atom",htmlBuilder(r,e){return b.mathsym(r.text,r.mode,e,["m"+r.family])},mathmlBuilder(r,e){var t=new S.MathNode("mo",[v0(r.text,r.mode)]);if(r.family==="bin"){var a=yt(r,e);a==="bold-italic"&&t.setAttribute("mathvariant",a)}else r.family==="punct"?t.setAttribute("separator","true"):(r.family==="open"||r.family==="close")&&t.setAttribute("stretchy","false");return t}});var na={mi:"italic",mn:"normal",mtext:"normal"};W0({type:"mathord",htmlBuilder(r,e){return b.makeOrd(r,e,"mathord")},mathmlBuilder(r,e){var t=new S.MathNode("mi",[v0(r.text,r.mode,e)]),a=yt(r,e)||"italic";return a!==na[t.type]&&t.setAttribute("mathvariant",a),t}});W0({type:"textord",htmlBuilder(r,e){return b.makeOrd(r,e,"textord")},mathmlBuilder(r,e){var t=v0(r.text,r.mode,e),a=yt(r,e)||"normal",n;return r.mode==="text"?n=new S.MathNode("mtext",[t]):/[0-9]/.test(r.text)?n=new S.MathNode("mn",[t]):r.text==="\\prime"?n=new S.MathNode("mo",[t]):n=new S.MathNode("mi",[t]),a!==na[n.type]&&n.setAttribute("mathvariant",a),n}});var tt={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},rt={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};W0({type:"spacing",htmlBuilder(r,e){if(rt.hasOwnProperty(r.text)){var t=rt[r.text].className||"";if(r.mode==="text"){var a=b.makeOrd(r,e,"textord");return a.classes.push(t),a}else return b.makeSpan(["mspace",t],[b.mathsym(r.text,r.mode,e)],e)}else{if(tt.hasOwnProperty(r.text))return b.makeSpan(["mspace",tt[r.text]],[],e);throw new M('Unknown type of space "'+r.text+'"')}},mathmlBuilder(r,e){var t;if(rt.hasOwnProperty(r.text))t=new S.MathNode("mtext",[new S.TextNode(" ")]);else{if(tt.hasOwnProperty(r.text))return new S.MathNode("mspace");throw new M('Unknown type of space "'+r.text+'"')}return t}});var sr=()=>{var r=new S.MathNode("mtd",[]);return r.setAttribute("width","50%"),r};W0({type:"tag",mathmlBuilder(r,e){var t=new S.MathNode("mtable",[new S.MathNode("mtr",[sr(),new S.MathNode("mtd",[G0(r.body,e)]),sr(),new S.MathNode("mtd",[G0(r.tag,e)])])]);return t.setAttribute("width","100%"),t}});var lr={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},ur={"\\textbf":"textbf","\\textmd":"textmd"},Q1={"\\textit":"textit","\\textup":"textup"},or=(r,e)=>{var t=r.font;if(t){if(lr[t])return e.withTextFontFamily(lr[t]);if(ur[t])return e.withTextFontWeight(ur[t]);if(t==="\\emph")return e.fontShape==="textit"?e.withTextFontShape("textup"):e.withTextFontShape("textit")}else return e;return e.withTextFontShape(Q1[t])};B({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup","\\emph"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler(r,e){var{parser:t,funcName:a}=r,n=e[0];return{type:"text",mode:t.mode,body:Q(n),font:a}},htmlBuilder(r,e){var t=or(r,e),a=t0(r.body,t,!0);return b.makeSpan(["mord","text"],a,t)},mathmlBuilder(r,e){var t=or(r,e);return G0(r.body,t)}});B({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler(r,e){var{parser:t}=r;return{type:"underline",mode:t.mode,body:e[0]}},htmlBuilder(r,e){var t=L(r.body,e),a=b.makeLineSpan("underline-line",e),n=e.fontMetrics().defaultRuleThickness,s=b.makeVList({positionType:"top",positionData:t.height,children:[{type:"kern",size:n},{type:"elem",elem:a},{type:"kern",size:3*n},{type:"elem",elem:t}]},e);return b.makeSpan(["mord","underline"],[s],e)},mathmlBuilder(r,e){var t=new S.MathNode("mo",[new S.TextNode("‾")]);t.setAttribute("stretchy","true");var a=new S.MathNode("munder",[X(r.body,e),t]);return a.setAttribute("accentunder","true"),a}});B({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler(r,e){var{parser:t}=r;return{type:"vcenter",mode:t.mode,body:e[0]}},htmlBuilder(r,e){var t=L(r.body,e),a=e.fontMetrics().axisHeight,n=.5*(t.height-a-(t.depth+a));return b.makeVList({positionType:"shift",positionData:n,children:[{type:"elem",elem:t}]},e)},mathmlBuilder(r,e){return new S.MathNode("mpadded",[X(r.body,e)],["vcenter"])}});B({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler(r,e,t){throw new M("\\verb ended by end of line instead of matching delimiter")},htmlBuilder(r,e){for(var t=hr(r),a=[],n=e.havingStyle(e.style.text()),s=0;sr.body.replace(/ /g,r.star?"␣":" "),L0=Tr,ia=`[ \r + ]`,_1="\\\\[a-zA-Z@]+",e4="\\\\[^\uD800-\uDFFF]",t4="("+_1+")"+ia+"*",r4=`\\\\( +|[ \r ]+ +?)[ \r ]*`,ct="[̀-ͯ]",a4=new RegExp(ct+"+$"),n4="("+ia+"+)|"+(r4+"|")+"([!-\\[\\]-‧‪-퟿豈-￿]"+(ct+"*")+"|[\uD800-\uDBFF][\uDC00-\uDFFF]"+(ct+"*")+"|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5"+("|"+t4)+("|"+e4+")");class mr{constructor(e,t){this.input=void 0,this.settings=void 0,this.tokenRegex=void 0,this.catcodes=void 0,this.input=e,this.settings=t,this.tokenRegex=new RegExp(n4,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,t){this.catcodes[e]=t}lex(){var e=this.input,t=this.tokenRegex.lastIndex;if(t===e.length)return new m0("EOF",new u0(this,t,t));var a=this.tokenRegex.exec(e);if(a===null||a.index!==t)throw new M("Unexpected character: '"+e[t]+"'",new m0(e[t],new u0(this,t,t+1)));var n=a[6]||a[3]||(a[2]?"\\ ":" ");if(this.catcodes[n]===14){var s=e.indexOf(` +`,this.tokenRegex.lastIndex);return s===-1?(this.tokenRegex.lastIndex=e.length,this.settings.reportNonstrict("commentAtEnd","% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")):this.tokenRegex.lastIndex=s+1,this.lex()}return new m0(n,new u0(this,t,this.tokenRegex.lastIndex))}}class i4{constructor(e,t){e===void 0&&(e={}),t===void 0&&(t={}),this.current=void 0,this.builtins=void 0,this.undefStack=void 0,this.current=t,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push({})}endGroup(){if(this.undefStack.length===0)throw new M("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");var e=this.undefStack.pop();for(var t in e)e.hasOwnProperty(t)&&(e[t]==null?delete this.current[t]:this.current[t]=e[t])}endGroups(){for(;this.undefStack.length>0;)this.endGroup()}has(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)}get(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]}set(e,t,a){if(a===void 0&&(a=!1),a){for(var n=0;n0&&(this.undefStack[this.undefStack.length-1][e]=t)}else{var s=this.undefStack[this.undefStack.length-1];s&&!s.hasOwnProperty(e)&&(s[e]=this.current[e])}t==null?delete this.current[e]:this.current[e]=t}}var s4=jr;m("\\noexpand",function(r){var e=r.popToken();return r.isExpandable(e.text)&&(e.noexpand=!0,e.treatAsRelax=!0),{tokens:[e],numArgs:0}});m("\\expandafter",function(r){var e=r.popToken();return r.expandOnce(!0),{tokens:[e],numArgs:0}});m("\\@firstoftwo",function(r){var e=r.consumeArgs(2);return{tokens:e[0],numArgs:0}});m("\\@secondoftwo",function(r){var e=r.consumeArgs(2);return{tokens:e[1],numArgs:0}});m("\\@ifnextchar",function(r){var e=r.consumeArgs(3);r.consumeSpaces();var t=r.future();return e[0].length===1&&e[0][0].text===t.text?{tokens:e[1],numArgs:0}:{tokens:e[2],numArgs:0}});m("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}");m("\\TextOrMath",function(r){var e=r.consumeArgs(2);return r.mode==="text"?{tokens:e[0],numArgs:0}:{tokens:e[1],numArgs:0}});var cr={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};m("\\char",function(r){var e=r.popToken(),t,a="";if(e.text==="'")t=8,e=r.popToken();else if(e.text==='"')t=16,e=r.popToken();else if(e.text==="`")if(e=r.popToken(),e.text[0]==="\\")a=e.text.charCodeAt(1);else{if(e.text==="EOF")throw new M("\\char` missing argument");a=e.text.charCodeAt(0)}else t=10;if(t){if(a=cr[e.text],a==null||a>=t)throw new M("Invalid base-"+t+" digit "+e.text);for(var n;(n=cr[r.future().text])!=null&&n{var n=r.consumeArg().tokens;if(n.length!==1)throw new M("\\newcommand's first argument must be a macro name");var s=n[0].text,u=r.isDefined(s);if(u&&!e)throw new M("\\newcommand{"+s+"} attempting to redefine "+(s+"; use \\renewcommand"));if(!u&&!t)throw new M("\\renewcommand{"+s+"} when command "+s+" does not yet exist; use \\newcommand");var h=0;if(n=r.consumeArg().tokens,n.length===1&&n[0].text==="["){for(var c="",p=r.expandNextToken();p.text!=="]"&&p.text!=="EOF";)c+=p.text,p=r.expandNextToken();if(!c.match(/^\s*[0-9]+\s*$/))throw new M("Invalid number of arguments: "+c);h=parseInt(c),n=r.consumeArg().tokens}return u&&a||r.macros.set(s,{tokens:n,numArgs:h}),""};m("\\newcommand",r=>Dt(r,!1,!0,!1));m("\\renewcommand",r=>Dt(r,!0,!1,!1));m("\\providecommand",r=>Dt(r,!0,!0,!0));m("\\message",r=>{var e=r.consumeArgs(1)[0];return console.log(e.reverse().map(t=>t.text).join("")),""});m("\\errmessage",r=>{var e=r.consumeArgs(1)[0];return console.error(e.reverse().map(t=>t.text).join("")),""});m("\\show",r=>{var e=r.popToken(),t=e.text;return console.log(e,r.macros.get(t),L0[t],$.math[t],$.text[t]),""});m("\\bgroup","{");m("\\egroup","}");m("~","\\nobreakspace");m("\\lq","`");m("\\rq","'");m("\\aa","\\r a");m("\\AA","\\r A");m("\\textcopyright","\\html@mathml{\\textcircled{c}}{\\char`©}");m("\\copyright","\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}");m("\\textregistered","\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`®}");m("ℬ","\\mathscr{B}");m("ℰ","\\mathscr{E}");m("ℱ","\\mathscr{F}");m("ℋ","\\mathscr{H}");m("ℐ","\\mathscr{I}");m("ℒ","\\mathscr{L}");m("ℳ","\\mathscr{M}");m("ℛ","\\mathscr{R}");m("ℭ","\\mathfrak{C}");m("ℌ","\\mathfrak{H}");m("ℨ","\\mathfrak{Z}");m("\\Bbbk","\\Bbb{k}");m("·","\\cdotp");m("\\llap","\\mathllap{\\textrm{#1}}");m("\\rlap","\\mathrlap{\\textrm{#1}}");m("\\clap","\\mathclap{\\textrm{#1}}");m("\\mathstrut","\\vphantom{(}");m("\\underbar","\\underline{\\text{#1}}");m("\\not",'\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}');m("\\neq","\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`≠}}");m("\\ne","\\neq");m("≠","\\neq");m("\\notin","\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`∉}}");m("∉","\\notin");m("≘","\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`≘}}");m("≙","\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`≘}}");m("≚","\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`≚}}");m("≛","\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`≛}}");m("≝","\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`≝}}");m("≞","\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`≞}}");m("≟","\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`≟}}");m("⟂","\\perp");m("‼","\\mathclose{!\\mkern-0.8mu!}");m("∌","\\notni");m("⌜","\\ulcorner");m("⌝","\\urcorner");m("⌞","\\llcorner");m("⌟","\\lrcorner");m("©","\\copyright");m("®","\\textregistered");m("️","\\textregistered");m("\\ulcorner",'\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}');m("\\urcorner",'\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}');m("\\llcorner",'\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}');m("\\lrcorner",'\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}');m("\\vdots","{\\varvdots\\rule{0pt}{15pt}}");m("⋮","\\vdots");m("\\varGamma","\\mathit{\\Gamma}");m("\\varDelta","\\mathit{\\Delta}");m("\\varTheta","\\mathit{\\Theta}");m("\\varLambda","\\mathit{\\Lambda}");m("\\varXi","\\mathit{\\Xi}");m("\\varPi","\\mathit{\\Pi}");m("\\varSigma","\\mathit{\\Sigma}");m("\\varUpsilon","\\mathit{\\Upsilon}");m("\\varPhi","\\mathit{\\Phi}");m("\\varPsi","\\mathit{\\Psi}");m("\\varOmega","\\mathit{\\Omega}");m("\\substack","\\begin{subarray}{c}#1\\end{subarray}");m("\\colon","\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax");m("\\boxed","\\fbox{$\\displaystyle{#1}$}");m("\\iff","\\DOTSB\\;\\Longleftrightarrow\\;");m("\\implies","\\DOTSB\\;\\Longrightarrow\\;");m("\\impliedby","\\DOTSB\\;\\Longleftarrow\\;");m("\\dddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ...}}{#1}}");m("\\ddddot","{\\overset{\\raisebox{-0.1ex}{\\normalsize ....}}{#1}}");var dr={",":"\\dotsc","\\not":"\\dotsb","+":"\\dotsb","=":"\\dotsb","<":"\\dotsb",">":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};m("\\dots",function(r){var e="\\dotso",t=r.expandAfterFuture().text;return t in dr?e=dr[t]:(t.slice(0,4)==="\\not"||t in $.math&&["bin","rel"].includes($.math[t].group))&&(e="\\dotsb"),e});var Ct={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};m("\\dotso",function(r){var e=r.future().text;return e in Ct?"\\ldots\\,":"\\ldots"});m("\\dotsc",function(r){var e=r.future().text;return e in Ct&&e!==","?"\\ldots\\,":"\\ldots"});m("\\cdots",function(r){var e=r.future().text;return e in Ct?"\\@cdots\\,":"\\@cdots"});m("\\dotsb","\\cdots");m("\\dotsm","\\cdots");m("\\dotsi","\\!\\cdots");m("\\dotsx","\\ldots\\,");m("\\DOTSI","\\relax");m("\\DOTSB","\\relax");m("\\DOTSX","\\relax");m("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax");m("\\,","\\tmspace+{3mu}{.1667em}");m("\\thinspace","\\,");m("\\>","\\mskip{4mu}");m("\\:","\\tmspace+{4mu}{.2222em}");m("\\medspace","\\:");m("\\;","\\tmspace+{5mu}{.2777em}");m("\\thickspace","\\;");m("\\!","\\tmspace-{3mu}{.1667em}");m("\\negthinspace","\\!");m("\\negmedspace","\\tmspace-{4mu}{.2222em}");m("\\negthickspace","\\tmspace-{5mu}{.277em}");m("\\enspace","\\kern.5em ");m("\\enskip","\\hskip.5em\\relax");m("\\quad","\\hskip1em\\relax");m("\\qquad","\\hskip2em\\relax");m("\\tag","\\@ifstar\\tag@literal\\tag@paren");m("\\tag@paren","\\tag@literal{({#1})}");m("\\tag@literal",r=>{if(r.macros.get("\\df@tag"))throw new M("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"});m("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}");m("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)");m("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}");m("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1");m("\\newline","\\\\\\relax");m("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");var sa=A(x0["Main-Regular"][84][1]-.7*x0["Main-Regular"][65][1]);m("\\LaTeX","\\textrm{\\html@mathml{"+("L\\kern-.36em\\raisebox{"+sa+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{LaTeX}}");m("\\KaTeX","\\textrm{\\html@mathml{"+("K\\kern-.17em\\raisebox{"+sa+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{KaTeX}}");m("\\hspace","\\@ifstar\\@hspacer\\@hspace");m("\\@hspace","\\hskip #1\\relax");m("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax");m("\\ordinarycolon",":");m("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}");m("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}');m("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}');m("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}');m("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}');m("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}');m("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}');m("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}');m("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}');m("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}');m("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}');m("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}');m("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}');m("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}');m("∷","\\dblcolon");m("∹","\\eqcolon");m("≔","\\coloneqq");m("≕","\\eqqcolon");m("⩴","\\Coloneqq");m("\\ratio","\\vcentcolon");m("\\coloncolon","\\dblcolon");m("\\colonequals","\\coloneqq");m("\\coloncolonequals","\\Coloneqq");m("\\equalscolon","\\eqqcolon");m("\\equalscoloncolon","\\Eqqcolon");m("\\colonminus","\\coloneq");m("\\coloncolonminus","\\Coloneq");m("\\minuscolon","\\eqcolon");m("\\minuscoloncolon","\\Eqcolon");m("\\coloncolonapprox","\\Colonapprox");m("\\coloncolonsim","\\Colonsim");m("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}");m("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}");m("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}");m("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}");m("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`∌}}");m("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}");m("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}");m("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}");m("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}");m("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}");m("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}");m("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}");m("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}");m("\\gvertneqq","\\html@mathml{\\@gvertneqq}{≩}");m("\\lvertneqq","\\html@mathml{\\@lvertneqq}{≨}");m("\\ngeqq","\\html@mathml{\\@ngeqq}{≱}");m("\\ngeqslant","\\html@mathml{\\@ngeqslant}{≱}");m("\\nleqq","\\html@mathml{\\@nleqq}{≰}");m("\\nleqslant","\\html@mathml{\\@nleqslant}{≰}");m("\\nshortmid","\\html@mathml{\\@nshortmid}{∤}");m("\\nshortparallel","\\html@mathml{\\@nshortparallel}{∦}");m("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{⊈}");m("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{⊉}");m("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{⊊}");m("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{⫋}");m("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{⊋}");m("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{⫌}");m("\\imath","\\html@mathml{\\@imath}{ı}");m("\\jmath","\\html@mathml{\\@jmath}{ȷ}");m("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`⟦}}");m("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`⟧}}");m("⟦","\\llbracket");m("⟧","\\rrbracket");m("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`⦃}}");m("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`⦄}}");m("⦃","\\lBrace");m("⦄","\\rBrace");m("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`⦵}}");m("⦵","\\minuso");m("\\darr","\\downarrow");m("\\dArr","\\Downarrow");m("\\Darr","\\Downarrow");m("\\lang","\\langle");m("\\rang","\\rangle");m("\\uarr","\\uparrow");m("\\uArr","\\Uparrow");m("\\Uarr","\\Uparrow");m("\\N","\\mathbb{N}");m("\\R","\\mathbb{R}");m("\\Z","\\mathbb{Z}");m("\\alef","\\aleph");m("\\alefsym","\\aleph");m("\\Alpha","\\mathrm{A}");m("\\Beta","\\mathrm{B}");m("\\bull","\\bullet");m("\\Chi","\\mathrm{X}");m("\\clubs","\\clubsuit");m("\\cnums","\\mathbb{C}");m("\\Complex","\\mathbb{C}");m("\\Dagger","\\ddagger");m("\\diamonds","\\diamondsuit");m("\\empty","\\emptyset");m("\\Epsilon","\\mathrm{E}");m("\\Eta","\\mathrm{H}");m("\\exist","\\exists");m("\\harr","\\leftrightarrow");m("\\hArr","\\Leftrightarrow");m("\\Harr","\\Leftrightarrow");m("\\hearts","\\heartsuit");m("\\image","\\Im");m("\\infin","\\infty");m("\\Iota","\\mathrm{I}");m("\\isin","\\in");m("\\Kappa","\\mathrm{K}");m("\\larr","\\leftarrow");m("\\lArr","\\Leftarrow");m("\\Larr","\\Leftarrow");m("\\lrarr","\\leftrightarrow");m("\\lrArr","\\Leftrightarrow");m("\\Lrarr","\\Leftrightarrow");m("\\Mu","\\mathrm{M}");m("\\natnums","\\mathbb{N}");m("\\Nu","\\mathrm{N}");m("\\Omicron","\\mathrm{O}");m("\\plusmn","\\pm");m("\\rarr","\\rightarrow");m("\\rArr","\\Rightarrow");m("\\Rarr","\\Rightarrow");m("\\real","\\Re");m("\\reals","\\mathbb{R}");m("\\Reals","\\mathbb{R}");m("\\Rho","\\mathrm{P}");m("\\sdot","\\cdot");m("\\sect","\\S");m("\\spades","\\spadesuit");m("\\sub","\\subset");m("\\sube","\\subseteq");m("\\supe","\\supseteq");m("\\Tau","\\mathrm{T}");m("\\thetasym","\\vartheta");m("\\weierp","\\wp");m("\\Zeta","\\mathrm{Z}");m("\\argmin","\\DOTSB\\operatorname*{arg\\,min}");m("\\argmax","\\DOTSB\\operatorname*{arg\\,max}");m("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits");m("\\bra","\\mathinner{\\langle{#1}|}");m("\\ket","\\mathinner{|{#1}\\rangle}");m("\\braket","\\mathinner{\\langle{#1}\\rangle}");m("\\Bra","\\left\\langle#1\\right|");m("\\Ket","\\left|#1\\right\\rangle");var la=r=>e=>{var t=e.consumeArg().tokens,a=e.consumeArg().tokens,n=e.consumeArg().tokens,s=e.consumeArg().tokens,u=e.macros.get("|"),h=e.macros.get("\\|");e.macros.beginGroup();var c=y=>x=>{r&&(x.macros.set("|",u),n.length&&x.macros.set("\\|",h));var w=y;if(!y&&n.length){var z=x.future();z.text==="|"&&(x.popToken(),w=!0)}return{tokens:w?n:a,numArgs:0}};e.macros.set("|",c(!1)),n.length&&e.macros.set("\\|",c(!0));var p=e.consumeArg().tokens,g=e.expandTokens([...s,...p,...t]);return e.macros.endGroup(),{tokens:g.reverse(),numArgs:0}};m("\\bra@ket",la(!1));m("\\bra@set",la(!0));m("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");m("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");m("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");m("\\angln","{\\angl n}");m("\\blue","\\textcolor{##6495ed}{#1}");m("\\orange","\\textcolor{##ffa500}{#1}");m("\\pink","\\textcolor{##ff00af}{#1}");m("\\red","\\textcolor{##df0030}{#1}");m("\\green","\\textcolor{##28ae7b}{#1}");m("\\gray","\\textcolor{gray}{#1}");m("\\purple","\\textcolor{##9d38bd}{#1}");m("\\blueA","\\textcolor{##ccfaff}{#1}");m("\\blueB","\\textcolor{##80f6ff}{#1}");m("\\blueC","\\textcolor{##63d9ea}{#1}");m("\\blueD","\\textcolor{##11accd}{#1}");m("\\blueE","\\textcolor{##0c7f99}{#1}");m("\\tealA","\\textcolor{##94fff5}{#1}");m("\\tealB","\\textcolor{##26edd5}{#1}");m("\\tealC","\\textcolor{##01d1c1}{#1}");m("\\tealD","\\textcolor{##01a995}{#1}");m("\\tealE","\\textcolor{##208170}{#1}");m("\\greenA","\\textcolor{##b6ffb0}{#1}");m("\\greenB","\\textcolor{##8af281}{#1}");m("\\greenC","\\textcolor{##74cf70}{#1}");m("\\greenD","\\textcolor{##1fab54}{#1}");m("\\greenE","\\textcolor{##0d923f}{#1}");m("\\goldA","\\textcolor{##ffd0a9}{#1}");m("\\goldB","\\textcolor{##ffbb71}{#1}");m("\\goldC","\\textcolor{##ff9c39}{#1}");m("\\goldD","\\textcolor{##e07d10}{#1}");m("\\goldE","\\textcolor{##a75a05}{#1}");m("\\redA","\\textcolor{##fca9a9}{#1}");m("\\redB","\\textcolor{##ff8482}{#1}");m("\\redC","\\textcolor{##f9685d}{#1}");m("\\redD","\\textcolor{##e84d39}{#1}");m("\\redE","\\textcolor{##bc2612}{#1}");m("\\maroonA","\\textcolor{##ffbde0}{#1}");m("\\maroonB","\\textcolor{##ff92c6}{#1}");m("\\maroonC","\\textcolor{##ed5fa6}{#1}");m("\\maroonD","\\textcolor{##ca337c}{#1}");m("\\maroonE","\\textcolor{##9e034e}{#1}");m("\\purpleA","\\textcolor{##ddd7ff}{#1}");m("\\purpleB","\\textcolor{##c6b9fc}{#1}");m("\\purpleC","\\textcolor{##aa87ff}{#1}");m("\\purpleD","\\textcolor{##7854ab}{#1}");m("\\purpleE","\\textcolor{##543b78}{#1}");m("\\mintA","\\textcolor{##f5f9e8}{#1}");m("\\mintB","\\textcolor{##edf2df}{#1}");m("\\mintC","\\textcolor{##e0e5cc}{#1}");m("\\grayA","\\textcolor{##f6f7f7}{#1}");m("\\grayB","\\textcolor{##f0f1f2}{#1}");m("\\grayC","\\textcolor{##e3e5e6}{#1}");m("\\grayD","\\textcolor{##d6d8da}{#1}");m("\\grayE","\\textcolor{##babec2}{#1}");m("\\grayF","\\textcolor{##888d93}{#1}");m("\\grayG","\\textcolor{##626569}{#1}");m("\\grayH","\\textcolor{##3b3e40}{#1}");m("\\grayI","\\textcolor{##21242c}{#1}");m("\\kaBlue","\\textcolor{##314453}{#1}");m("\\kaGreen","\\textcolor{##71B307}{#1}");var ua={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0};class l4{constructor(e,t,a){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=t,this.expansionCount=0,this.feed(e),this.macros=new i4(s4,t.macros),this.mode=a,this.stack=[]}feed(e){this.lexer=new mr(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}endGroups(){this.macros.endGroups()}future(){return this.stack.length===0&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){var t,a,n;if(e){if(this.consumeSpaces(),this.future().text!=="[")return null;t=this.popToken(),{tokens:n,end:a}=this.consumeArg(["]"])}else({tokens:n,start:t,end:a}=this.consumeArg());return this.pushToken(new m0("EOF",a.loc)),this.pushTokens(n),new m0("",u0.range(t,a))}consumeSpaces(){for(;;){var e=this.future();if(e.text===" ")this.stack.pop();else break}}consumeArg(e){var t=[],a=e&&e.length>0;a||this.consumeSpaces();var n=this.future(),s,u=0,h=0;do{if(s=this.popToken(),t.push(s),s.text==="{")++u;else if(s.text==="}"){if(--u,u===-1)throw new M("Extra }",s)}else if(s.text==="EOF")throw new M("Unexpected end of input in a macro argument, expected '"+(e&&a?e[h]:"}")+"'",s);if(e&&a)if((u===0||u===1&&e[h]==="{")&&s.text===e[h]){if(++h,h===e.length){t.splice(-h,h);break}}else h=0}while(u!==0||a);return n.text==="{"&&t[t.length-1].text==="}"&&(t.pop(),t.shift()),t.reverse(),{tokens:t,start:n,end:s}}consumeArgs(e,t){if(t){if(t.length!==e+1)throw new M("The length of delimiters doesn't match the number of args!");for(var a=t[0],n=0;nthis.settings.maxExpand)throw new M("Too many expansions: infinite loop or need to increase maxExpand setting")}expandOnce(e){var t=this.popToken(),a=t.text,n=t.noexpand?null:this._getExpansion(a);if(n==null||e&&n.unexpandable){if(e&&n==null&&a[0]==="\\"&&!this.isDefined(a))throw new M("Undefined control sequence: "+a);return this.pushToken(t),!1}this.countExpansion(1);var s=n.tokens,u=this.consumeArgs(n.numArgs,n.delimiters);if(n.numArgs){s=s.slice();for(var h=s.length-1;h>=0;--h){var c=s[h];if(c.text==="#"){if(h===0)throw new M("Incomplete placeholder at end of macro body",c);if(c=s[--h],c.text==="#")s.splice(h+1,1);else if(/^[1-9]$/.test(c.text))s.splice(h,2,...u[+c.text-1]);else throw new M("Not a valid argument number",c)}}}return this.pushTokens(s),s.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(this.expandOnce()===!1){var e=this.stack.pop();return e.treatAsRelax&&(e.text="\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new m0(e)]):void 0}expandTokens(e){var t=[],a=this.stack.length;for(this.pushTokens(e);this.stack.length>a;)if(this.expandOnce(!0)===!1){var n=this.stack.pop();n.treatAsRelax&&(n.noexpand=!1,n.treatAsRelax=!1),t.push(n)}return this.countExpansion(t.length),t}expandMacroAsText(e){var t=this.expandMacro(e);return t&&t.map(a=>a.text).join("")}_getExpansion(e){var t=this.macros.get(e);if(t==null)return t;if(e.length===1){var a=this.lexer.catcodes[e];if(a!=null&&a!==13)return}var n=typeof t=="function"?t(this):t;if(typeof n=="string"){var s=0;if(n.indexOf("#")!==-1)for(var u=n.replace(/##/g,"");u.indexOf("#"+(s+1))!==-1;)++s;for(var h=new mr(n,this.settings),c=[],p=h.lex();p.text!=="EOF";)c.push(p),p=h.lex();c.reverse();var g={tokens:c,numArgs:s};return g}return n}isDefined(e){return this.macros.has(e)||L0.hasOwnProperty(e)||$.math.hasOwnProperty(e)||$.text.hasOwnProperty(e)||ua.hasOwnProperty(e)}isExpandable(e){var t=this.macros.get(e);return t!=null?typeof t=="string"||typeof t=="function"||!t.unexpandable:L0.hasOwnProperty(e)&&!L0[e].primitive}}var fr=/^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/,Me=Object.freeze({"₊":"+","₋":"-","₌":"=","₍":"(","₎":")","₀":"0","₁":"1","₂":"2","₃":"3","₄":"4","₅":"5","₆":"6","₇":"7","₈":"8","₉":"9","ₐ":"a","ₑ":"e","ₕ":"h","ᵢ":"i","ⱼ":"j","ₖ":"k","ₗ":"l","ₘ":"m","ₙ":"n","ₒ":"o","ₚ":"p","ᵣ":"r","ₛ":"s","ₜ":"t","ᵤ":"u","ᵥ":"v","ₓ":"x","ᵦ":"β","ᵧ":"γ","ᵨ":"ρ","ᵩ":"ϕ","ᵪ":"χ","⁺":"+","⁻":"-","⁼":"=","⁽":"(","⁾":")","⁰":"0","¹":"1","²":"2","³":"3","⁴":"4","⁵":"5","⁶":"6","⁷":"7","⁸":"8","⁹":"9","ᴬ":"A","ᴮ":"B","ᴰ":"D","ᴱ":"E","ᴳ":"G","ᴴ":"H","ᴵ":"I","ᴶ":"J","ᴷ":"K","ᴸ":"L","ᴹ":"M","ᴺ":"N","ᴼ":"O","ᴾ":"P","ᴿ":"R","ᵀ":"T","ᵁ":"U","ⱽ":"V","ᵂ":"W","ᵃ":"a","ᵇ":"b","ᶜ":"c","ᵈ":"d","ᵉ":"e","ᶠ":"f","ᵍ":"g",ʰ:"h","ⁱ":"i",ʲ:"j","ᵏ":"k",ˡ:"l","ᵐ":"m",ⁿ:"n","ᵒ":"o","ᵖ":"p",ʳ:"r",ˢ:"s","ᵗ":"t","ᵘ":"u","ᵛ":"v",ʷ:"w",ˣ:"x",ʸ:"y","ᶻ":"z","ᵝ":"β","ᵞ":"γ","ᵟ":"δ","ᵠ":"ϕ","ᵡ":"χ","ᶿ":"θ"}),at={"́":{text:"\\'",math:"\\acute"},"̀":{text:"\\`",math:"\\grave"},"̈":{text:'\\"',math:"\\ddot"},"̃":{text:"\\~",math:"\\tilde"},"̄":{text:"\\=",math:"\\bar"},"̆":{text:"\\u",math:"\\breve"},"̌":{text:"\\v",math:"\\check"},"̂":{text:"\\^",math:"\\hat"},"̇":{text:"\\.",math:"\\dot"},"̊":{text:"\\r",math:"\\mathring"},"̋":{text:"\\H"},"̧":{text:"\\c"}},pr={á:"á",à:"à",ä:"ä",ǟ:"ǟ",ã:"ã",ā:"ā",ă:"ă",ắ:"ắ",ằ:"ằ",ẵ:"ẵ",ǎ:"ǎ",â:"â",ấ:"ấ",ầ:"ầ",ẫ:"ẫ",ȧ:"ȧ",ǡ:"ǡ",å:"å",ǻ:"ǻ",ḃ:"ḃ",ć:"ć",ḉ:"ḉ",č:"č",ĉ:"ĉ",ċ:"ċ",ç:"ç",ď:"ď",ḋ:"ḋ",ḑ:"ḑ",é:"é",è:"è",ë:"ë",ẽ:"ẽ",ē:"ē",ḗ:"ḗ",ḕ:"ḕ",ĕ:"ĕ",ḝ:"ḝ",ě:"ě",ê:"ê",ế:"ế",ề:"ề",ễ:"ễ",ė:"ė",ȩ:"ȩ",ḟ:"ḟ",ǵ:"ǵ",ḡ:"ḡ",ğ:"ğ",ǧ:"ǧ",ĝ:"ĝ",ġ:"ġ",ģ:"ģ",ḧ:"ḧ",ȟ:"ȟ",ĥ:"ĥ",ḣ:"ḣ",ḩ:"ḩ",í:"í",ì:"ì",ï:"ï",ḯ:"ḯ",ĩ:"ĩ",ī:"ī",ĭ:"ĭ",ǐ:"ǐ",î:"î",ǰ:"ǰ",ĵ:"ĵ",ḱ:"ḱ",ǩ:"ǩ",ķ:"ķ",ĺ:"ĺ",ľ:"ľ",ļ:"ļ",ḿ:"ḿ",ṁ:"ṁ",ń:"ń",ǹ:"ǹ",ñ:"ñ",ň:"ň",ṅ:"ṅ",ņ:"ņ",ó:"ó",ò:"ò",ö:"ö",ȫ:"ȫ",õ:"õ",ṍ:"ṍ",ṏ:"ṏ",ȭ:"ȭ",ō:"ō",ṓ:"ṓ",ṑ:"ṑ",ŏ:"ŏ",ǒ:"ǒ",ô:"ô",ố:"ố",ồ:"ồ",ỗ:"ỗ",ȯ:"ȯ",ȱ:"ȱ",ő:"ő",ṕ:"ṕ",ṗ:"ṗ",ŕ:"ŕ",ř:"ř",ṙ:"ṙ",ŗ:"ŗ",ś:"ś",ṥ:"ṥ",š:"š",ṧ:"ṧ",ŝ:"ŝ",ṡ:"ṡ",ş:"ş",ẗ:"ẗ",ť:"ť",ṫ:"ṫ",ţ:"ţ",ú:"ú",ù:"ù",ü:"ü",ǘ:"ǘ",ǜ:"ǜ",ǖ:"ǖ",ǚ:"ǚ",ũ:"ũ",ṹ:"ṹ",ū:"ū",ṻ:"ṻ",ŭ:"ŭ",ǔ:"ǔ",û:"û",ů:"ů",ű:"ű",ṽ:"ṽ",ẃ:"ẃ",ẁ:"ẁ",ẅ:"ẅ",ŵ:"ŵ",ẇ:"ẇ",ẘ:"ẘ",ẍ:"ẍ",ẋ:"ẋ",ý:"ý",ỳ:"ỳ",ÿ:"ÿ",ỹ:"ỹ",ȳ:"ȳ",ŷ:"ŷ",ẏ:"ẏ",ẙ:"ẙ",ź:"ź",ž:"ž",ẑ:"ẑ",ż:"ż",Á:"Á",À:"À",Ä:"Ä",Ǟ:"Ǟ",Ã:"Ã",Ā:"Ā",Ă:"Ă",Ắ:"Ắ",Ằ:"Ằ",Ẵ:"Ẵ",Ǎ:"Ǎ",Â:"Â",Ấ:"Ấ",Ầ:"Ầ",Ẫ:"Ẫ",Ȧ:"Ȧ",Ǡ:"Ǡ",Å:"Å",Ǻ:"Ǻ",Ḃ:"Ḃ",Ć:"Ć",Ḉ:"Ḉ",Č:"Č",Ĉ:"Ĉ",Ċ:"Ċ",Ç:"Ç",Ď:"Ď",Ḋ:"Ḋ",Ḑ:"Ḑ",É:"É",È:"È",Ë:"Ë",Ẽ:"Ẽ",Ē:"Ē",Ḗ:"Ḗ",Ḕ:"Ḕ",Ĕ:"Ĕ",Ḝ:"Ḝ",Ě:"Ě",Ê:"Ê",Ế:"Ế",Ề:"Ề",Ễ:"Ễ",Ė:"Ė",Ȩ:"Ȩ",Ḟ:"Ḟ",Ǵ:"Ǵ",Ḡ:"Ḡ",Ğ:"Ğ",Ǧ:"Ǧ",Ĝ:"Ĝ",Ġ:"Ġ",Ģ:"Ģ",Ḧ:"Ḧ",Ȟ:"Ȟ",Ĥ:"Ĥ",Ḣ:"Ḣ",Ḩ:"Ḩ",Í:"Í",Ì:"Ì",Ï:"Ï",Ḯ:"Ḯ",Ĩ:"Ĩ",Ī:"Ī",Ĭ:"Ĭ",Ǐ:"Ǐ",Î:"Î",İ:"İ",Ĵ:"Ĵ",Ḱ:"Ḱ",Ǩ:"Ǩ",Ķ:"Ķ",Ĺ:"Ĺ",Ľ:"Ľ",Ļ:"Ļ",Ḿ:"Ḿ",Ṁ:"Ṁ",Ń:"Ń",Ǹ:"Ǹ",Ñ:"Ñ",Ň:"Ň",Ṅ:"Ṅ",Ņ:"Ņ",Ó:"Ó",Ò:"Ò",Ö:"Ö",Ȫ:"Ȫ",Õ:"Õ",Ṍ:"Ṍ",Ṏ:"Ṏ",Ȭ:"Ȭ",Ō:"Ō",Ṓ:"Ṓ",Ṑ:"Ṑ",Ŏ:"Ŏ",Ǒ:"Ǒ",Ô:"Ô",Ố:"Ố",Ồ:"Ồ",Ỗ:"Ỗ",Ȯ:"Ȯ",Ȱ:"Ȱ",Ő:"Ő",Ṕ:"Ṕ",Ṗ:"Ṗ",Ŕ:"Ŕ",Ř:"Ř",Ṙ:"Ṙ",Ŗ:"Ŗ",Ś:"Ś",Ṥ:"Ṥ",Š:"Š",Ṧ:"Ṧ",Ŝ:"Ŝ",Ṡ:"Ṡ",Ş:"Ş",Ť:"Ť",Ṫ:"Ṫ",Ţ:"Ţ",Ú:"Ú",Ù:"Ù",Ü:"Ü",Ǘ:"Ǘ",Ǜ:"Ǜ",Ǖ:"Ǖ",Ǚ:"Ǚ",Ũ:"Ũ",Ṹ:"Ṹ",Ū:"Ū",Ṻ:"Ṻ",Ŭ:"Ŭ",Ǔ:"Ǔ",Û:"Û",Ů:"Ů",Ű:"Ű",Ṽ:"Ṽ",Ẃ:"Ẃ",Ẁ:"Ẁ",Ẅ:"Ẅ",Ŵ:"Ŵ",Ẇ:"Ẇ",Ẍ:"Ẍ",Ẋ:"Ẋ",Ý:"Ý",Ỳ:"Ỳ",Ÿ:"Ÿ",Ỹ:"Ỹ",Ȳ:"Ȳ",Ŷ:"Ŷ",Ẏ:"Ẏ",Ź:"Ź",Ž:"Ž",Ẑ:"Ẑ",Ż:"Ż",ά:"ά",ὰ:"ὰ",ᾱ:"ᾱ",ᾰ:"ᾰ",έ:"έ",ὲ:"ὲ",ή:"ή",ὴ:"ὴ",ί:"ί",ὶ:"ὶ",ϊ:"ϊ",ΐ:"ΐ",ῒ:"ῒ",ῑ:"ῑ",ῐ:"ῐ",ό:"ό",ὸ:"ὸ",ύ:"ύ",ὺ:"ὺ",ϋ:"ϋ",ΰ:"ΰ",ῢ:"ῢ",ῡ:"ῡ",ῠ:"ῠ",ώ:"ώ",ὼ:"ὼ",Ύ:"Ύ",Ὺ:"Ὺ",Ϋ:"Ϋ",Ῡ:"Ῡ",Ῠ:"Ῠ",Ώ:"Ώ",Ὼ:"Ὼ"};class He{constructor(e,t){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new l4(e,t,this.mode),this.settings=t,this.leftrightDepth=0}expect(e,t){if(t===void 0&&(t=!0),this.fetch().text!==e)throw new M("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());t&&this.consume()}consume(){this.nextToken=null}fetch(){return this.nextToken==null&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}}subparse(e){var t=this.nextToken;this.consume(),this.gullet.pushToken(new m0("}")),this.gullet.pushTokens(e);var a=this.parseExpression(!1);return this.expect("}"),this.nextToken=t,a}parseExpression(e,t){for(var a=[];;){this.mode==="math"&&this.consumeSpaces();var n=this.fetch();if(He.endOfExpression.indexOf(n.text)!==-1||t&&n.text===t||e&&L0[n.text]&&L0[n.text].infix)break;var s=this.parseAtom(t);if(s){if(s.type==="internal")continue}else break;a.push(s)}return this.mode==="text"&&this.formLigatures(a),this.handleInfixNodes(a)}handleInfixNodes(e){for(var t=-1,a,n=0;n=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+t[0]+'" used in math mode',e);var h=$[this.mode][t].group,c=u0.range(e),p;if(Za.hasOwnProperty(h)){var g=h;p={type:"atom",mode:this.mode,family:g,loc:c,text:t}}else p={type:h,mode:this.mode,loc:c,text:t};u=p}else if(t.charCodeAt(0)>=128)this.settings.strict&&(gr(t.charCodeAt(0))?this.mode==="math"&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+t[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+t[0]+'"'+(" ("+t.charCodeAt(0)+")"),e)),u={type:"textord",mode:"text",loc:u0.range(e),text:t};else return null;if(this.consume(),s)for(var y=0;y2?n[2]:void 0;for(i&&S(n[0],n[1],i)&&(t=1);++r-1?i[o?n[a]:a]:void 0}}var On=Math.max;function Nn(e,n,r){var t=e==null?0:e.length;if(!t)return-1;var i=r==null?0:wn(r);return i<0&&(i=On(t+i,0)),$e(e,C(n),i)}var U=kn(Nn);function Ln(e,n){return e==null?e:en(e,ve(n),ye)}function Pn(e,n){return e&&pe(e,ve(n))}function _n(e,n){return e>n}var Cn=Object.prototype,In=Cn.hasOwnProperty;function Rn(e,n){return e!=null&&In.call(e,n)}function Ne(e,n){return e!=null&&De(e,n,Rn)}function j(e,n){var r={};return n=C(n),pe(e,function(t,i,o){nn(r,i,n(t,i,o))}),r}function y(e){return e&&e.length?me(e,xe,_n):void 0}function J(e,n){return e&&e.length?me(e,C(n),He):void 0}function Tn(e,n,r,t){if(!M(e))return e;n=we(n,e);for(var i=-1,o=n.length,a=o-1,u=e;u!=null&&++in||o&&a&&d&&!u&&!s||t&&a&&d||!r&&d||!i)return 1;if(!t&&!o&&!s&&e=u)return d;var s=r[t];return d*(s=="desc"?-1:1)}}return e.index-n.index}function Bn(e,n,r){n.length?n=V(n,function(o){return ke(o)?function(a){return be(a,o.length===1?o[0]:o)}:o}):n=[xe];var t=-1;n=V(n,tn(C));var i=Ue(e,function(o,a,u){var d=V(n,function(s){return s(o)});return{criteria:d,index:++t,value:o}});return Sn(i,function(o,a){return An(o,a,r)})}function jn(e,n){return Mn(e,n,function(r,t){return We(e,t)})}var A=bn(function(e,n){return e==null?{}:jn(e,n)}),Gn=Math.ceil,Vn=Math.max;function Yn(e,n,r,t){for(var i=-1,o=Vn(Gn((n-e)/(r||1)),0),a=Array(o);o--;)a[++i]=e,e+=r;return a}function $n(e){return function(n,r,t){return t&&typeof t!="number"&&S(n,r,t)&&(r=t=void 0),n=T(n),r===void 0?(r=n,n=0):r=T(r),t=t===void 0?n1&&S(e,n[0],n[1])?n=[]:r>2&&S(n[0],n[1],n[2])&&(n=[n[0]]),Bn(e,ze(n),[])}),Dn=0;function K(e){var n=++Dn;return Xe(e)+n}function qn(e,n,r){for(var t=-1,i=e.length,o=n.length,a={};++t0;--u)if(a=n[u].dequeue(),a){t=t.concat(Y(e,n,r,a,!0));break}}}return t}function Y(e,n,r,t,i){var o=i?[]:void 0;return f(e.inEdges(t.v),function(a){var u=e.edge(a),d=e.node(a.v);i&&o.push({v:a.v,w:a.w}),d.out-=u,H(n,r,d)}),f(e.outEdges(t.v),function(a){var u=e.edge(a),d=a.w,s=e.node(d);s.in-=u,H(n,r,s)}),e.removeNode(t.v),o}function Kn(e,n){var r=new g,t=0,i=0;f(e.nodes(),function(u){r.setNode(u,{v:u,in:0,out:0})}),f(e.edges(),function(u){var d=r.edge(u.v,u.w)||0,s=n(u),c=d+s;r.setEdge(u.v,u.w,c),i=Math.max(i,r.node(u.v).out+=s),t=Math.max(t,r.node(u.w).in+=s)});var o=k(i+t+3).map(function(){return new zn}),a=t+1;return f(r.nodes(),function(u){H(o,a,r.node(u))}),{graph:r,buckets:o,zeroIdx:a}}function H(e,n,r){r.out?r.in?e[r.out-r.in+n].enqueue(r):e[e.length-1].enqueue(r):e[0].enqueue(r)}function Zn(e){var n=e.graph().acyclicer==="greedy"?Un(e,r(e)):Qn(e);f(n,function(t){var i=e.edge(t);e.removeEdge(t),i.forwardName=t.name,i.reversed=!0,e.setEdge(t.w,t.v,i,K("rev"))});function r(t){return function(i){return t.edge(i).weight}}}function Qn(e){var n=[],r={},t={};function i(o){Object.prototype.hasOwnProperty.call(t,o)||(t[o]=!0,r[o]=!0,f(e.outEdges(o),function(a){Object.prototype.hasOwnProperty.call(r,a.w)?n.push(a):i(a.w)}),delete r[o])}return f(e.nodes(),i),n}function er(e){f(e.edges(),function(n){var r=e.edge(n);if(r.reversed){e.removeEdge(n);var t=r.forwardName;delete r.reversed,delete r.forwardName,e.setEdge(n.w,n.v,r,t)}})}function N(e,n,r,t){var i;do i=K(t);while(e.hasNode(i));return r.dummy=n,e.setNode(i,r),i}function nr(e){var n=new g().setGraph(e.graph());return f(e.nodes(),function(r){n.setNode(r,e.node(r))}),f(e.edges(),function(r){var t=n.edge(r.v,r.w)||{weight:0,minlen:1},i=e.edge(r);n.setEdge(r.v,r.w,{weight:t.weight+i.weight,minlen:Math.max(t.minlen,i.minlen)})}),n}function Le(e){var n=new g({multigraph:e.isMultigraph()}).setGraph(e.graph());return f(e.nodes(),function(r){e.children(r).length||n.setNode(r,e.node(r))}),f(e.edges(),function(r){n.setEdge(r,e.edge(r))}),n}function ae(e,n){var r=e.x,t=e.y,i=n.x-r,o=n.y-t,a=e.width/2,u=e.height/2;if(!i&&!o)throw new Error("Not possible to find intersection inside of the rectangle");var d,s;return Math.abs(o)*a>Math.abs(i)*u?(o<0&&(u=-u),d=u*i/o,s=u):(i<0&&(a=-a),d=a,s=a*o/i),{x:r+d,y:t+s}}function G(e){var n=w(k(Pe(e)+1),function(){return[]});return f(e.nodes(),function(r){var t=e.node(r),i=t.rank;m(i)||(n[i][t.order]=r)}),n}function rr(e){var n=P(w(e.nodes(),function(r){return e.node(r).rank}));f(e.nodes(),function(r){var t=e.node(r);Ne(t,"rank")&&(t.rank-=n)})}function tr(e){var n=P(w(e.nodes(),function(o){return e.node(o).rank})),r=[];f(e.nodes(),function(o){var a=e.node(o).rank-n;r[a]||(r[a]=[]),r[a].push(o)});var t=0,i=e.graph().nodeRankFactor;f(r,function(o,a){m(o)&&a%i!==0?--t:t&&f(o,function(u){e.node(u).rank+=t})})}function oe(e,n,r,t){var i={width:0,height:0};return arguments.length>=4&&(i.rank=r,i.order=t),N(e,"border",i,n)}function Pe(e){return y(w(e.nodes(),function(n){var r=e.node(n).rank;if(!m(r))return r}))}function ir(e,n){var r={lhs:[],rhs:[]};return f(e,function(t){n(t)?r.lhs.push(t):r.rhs.push(t)}),r}function ar(e,n){return n()}function or(e){function n(r){var t=e.children(r),i=e.node(r);if(t.length&&f(t,n),Object.prototype.hasOwnProperty.call(i,"minRank")){i.borderLeft=[],i.borderRight=[];for(var o=i.minRank,a=i.maxRank+1;oa.lim&&(u=a,d=!0);var s=I(n.edges(),function(c){return d===se(e,e.node(c.v),u)&&d!==se(e,e.node(c.w),u)});return J(s,function(c){return _(n,c)})}function Ae(e,n,r,t){var i=r.v,o=r.w;e.removeEdge(i,o),e.setEdge(t.v,t.w,{}),ee(e),Q(e,n),xr(e,n)}function xr(e,n){var r=U(e.nodes(),function(i){return!n.node(i).parent}),t=gr(e,r);t=t.slice(1),f(t,function(i){var o=e.node(i).parent,a=n.edge(i,o),u=!1;a||(a=n.edge(o,i),u=!0),n.node(i).rank=n.node(o).rank+(u?a.minlen:-a.minlen)})}function Er(e,n,r){return e.hasEdge(n,r)}function se(e,n,r){return r.low<=n.lim&&n.lim<=r.lim}function kr(e){switch(e.graph().ranker){case"network-simplex":fe(e);break;case"tight-tree":Nr(e);break;case"longest-path":Or(e);break;default:fe(e)}}var Or=Z;function Nr(e){Z(e),Ce(e)}function fe(e){x(e)}function Lr(e){var n=N(e,"root",{},"_root"),r=Pr(e),t=y(E(r))-1,i=2*t+1;e.graph().nestingRoot=n,f(e.edges(),function(a){e.edge(a).minlen*=i});var o=_r(e)+1;f(e.children(),function(a){Be(e,n,i,o,t,r,a)}),e.graph().nodeRankFactor=i}function Be(e,n,r,t,i,o,a){var u=e.children(a);if(!u.length){a!==n&&e.setEdge(n,a,{weight:0,minlen:r});return}var d=oe(e,"_bt"),s=oe(e,"_bb"),c=e.node(a);e.setParent(d,a),c.borderTop=d,e.setParent(s,a),c.borderBottom=s,f(u,function(l){Be(e,n,r,t,i,o,l);var h=e.node(l),v=h.borderTop?h.borderTop:l,p=h.borderBottom?h.borderBottom:l,b=h.borderTop?t:2*t,L=v!==p?1:i-o[a]+1;e.setEdge(d,v,{weight:b,minlen:L,nestingEdge:!0}),e.setEdge(p,s,{weight:b,minlen:L,nestingEdge:!0})}),e.parent(a)||e.setEdge(n,d,{weight:0,minlen:i+o[a]})}function Pr(e){var n={};function r(t,i){var o=e.children(t);o&&o.length&&f(o,function(a){r(a,i+1)}),n[t]=i}return f(e.children(),function(t){r(t,1)}),n}function _r(e){return B(e.edges(),function(n,r){return n+e.edge(r).weight},0)}function Cr(e){var n=e.graph();e.removeNode(n.nestingRoot),delete n.nestingRoot,f(e.edges(),function(r){var t=e.edge(r);t.nestingEdge&&e.removeEdge(r)})}function Ir(e,n,r){var t={},i;f(r,function(o){for(var a=e.parent(o),u,d;a;){if(u=e.parent(a),u?(d=t[u],t[u]=a):(d=i,i=a),d&&d!==a){n.setEdge(d,a);return}a=u}})}function Rr(e,n,r){var t=Tr(e),i=new g({compound:!0}).setGraph({root:t}).setDefaultNodeLabel(function(o){return e.node(o)});return f(e.nodes(),function(o){var a=e.node(o),u=e.parent(o);(a.rank===n||a.minRank<=n&&n<=a.maxRank)&&(i.setNode(o),i.setParent(o,u||t),f(e[r](o),function(d){var s=d.v===o?d.w:d.v,c=i.edge(s,o),l=m(c)?0:c.weight;i.setEdge(s,o,{weight:e.edge(d).weight+l})}),Object.prototype.hasOwnProperty.call(a,"minRank")&&i.setNode(o,{borderLeft:a.borderLeft[n],borderRight:a.borderRight[n]}))}),i}function Tr(e){for(var n;e.hasNode(n=K("_root")););return n}function Mr(e,n){for(var r=0,t=1;t0;)c%2&&(l+=u[c+1]),c=c-1>>1,u[c]+=s.weight;d+=s.weight*l})),d}function Fr(e){var n={},r=I(e.nodes(),function(u){return!e.children(u).length}),t=y(w(r,function(u){return e.node(u).rank})),i=w(k(t+1),function(){return[]});function o(u){if(!Ne(n,u)){n[u]=!0;var d=e.node(u);i[d.rank].push(u),f(e.successors(u),o)}}var a=R(r,function(u){return e.node(u).rank});return f(a,o),i}function Ar(e,n){return w(n,function(r){var t=e.inEdges(r);if(t.length){var i=B(t,function(o,a){var u=e.edge(a),d=e.node(a.v);return{sum:o.sum+u.weight*d.order,weight:o.weight+u.weight}},{sum:0,weight:0});return{v:r,barycenter:i.sum/i.weight,weight:i.weight}}else return{v:r}})}function Br(e,n){var r={};f(e,function(i,o){var a=r[i.v]={indegree:0,in:[],out:[],vs:[i.v],i:o};m(i.barycenter)||(a.barycenter=i.barycenter,a.weight=i.weight)}),f(n.edges(),function(i){var o=r[i.v],a=r[i.w];!m(o)&&!m(a)&&(a.indegree++,o.out.push(r[i.w]))});var t=I(r,function(i){return!i.indegree});return jr(t)}function jr(e){var n=[];function r(o){return function(a){a.merged||(m(a.barycenter)||m(o.barycenter)||a.barycenter>=o.barycenter)&&Gr(o,a)}}function t(o){return function(a){a.in.push(o),--a.indegree===0&&e.push(a)}}for(;e.length;){var i=e.pop();n.push(i),f(i.in.reverse(),r(i)),f(i.out,t(i))}return w(I(n,function(o){return!o.merged}),function(o){return A(o,["vs","i","barycenter","weight"])})}function Gr(e,n){var r=0,t=0;e.weight&&(r+=e.barycenter*e.weight,t+=e.weight),n.weight&&(r+=n.barycenter*n.weight,t+=n.weight),e.vs=n.vs.concat(e.vs),e.barycenter=r/t,e.weight=t,e.i=Math.min(n.i,e.i),n.merged=!0}function Vr(e,n){var r=ir(e,function(c){return Object.prototype.hasOwnProperty.call(c,"barycenter")}),t=r.lhs,i=R(r.rhs,function(c){return-c.i}),o=[],a=0,u=0,d=0;t.sort(Yr(!!n)),d=ce(o,i,d),f(t,function(c){d+=c.vs.length,o.push(c.vs),a+=c.barycenter*c.weight,u+=c.weight,d=ce(o,i,d)});var s={vs:O(o)};return u&&(s.barycenter=a/u,s.weight=u),s}function ce(e,n,r){for(var t;n.length&&(t=F(n)).i<=r;)n.pop(),e.push(t.vs),r++;return r}function Yr(e){return function(n,r){return n.barycenterr.barycenter?1:e?r.i-n.i:n.i-r.i}}function je(e,n,r,t){var i=e.children(n),o=e.node(n),a=o?o.borderLeft:void 0,u=o?o.borderRight:void 0,d={};a&&(i=I(i,function(p){return p!==a&&p!==u}));var s=Ar(e,i);f(s,function(p){if(e.children(p.v).length){var b=je(e,p.v,r,t);d[p.v]=b,Object.prototype.hasOwnProperty.call(b,"barycenter")&&Dr(p,b)}});var c=Br(s,r);$r(c,d);var l=Vr(c,t);if(a&&(l.vs=O([a,l.vs,u]),e.predecessors(a).length)){var h=e.node(e.predecessors(a)[0]),v=e.node(e.predecessors(u)[0]);Object.prototype.hasOwnProperty.call(l,"barycenter")||(l.barycenter=0,l.weight=0),l.barycenter=(l.barycenter*l.weight+h.order+v.order)/(l.weight+2),l.weight+=2}return l}function $r(e,n){f(e,function(r){r.vs=O(r.vs.map(function(t){return n[t]?n[t].vs:t}))})}function Dr(e,n){m(e.barycenter)?(e.barycenter=n.barycenter,e.weight=n.weight):(e.barycenter=(e.barycenter*e.weight+n.barycenter*n.weight)/(e.weight+n.weight),e.weight+=n.weight)}function qr(e){var n=Pe(e),r=le(e,k(1,n+1),"inEdges"),t=le(e,k(n-1,-1,-1),"outEdges"),i=Fr(e);he(e,i);for(var o=Number.POSITIVE_INFINITY,a,u=0,d=0;d<4;++u,++d){Wr(u%2?r:t,u%4>=2),i=G(e);var s=Mr(e,i);sa||u>n[d].lim));for(s=d,d=t;(d=e.parent(d))!==s;)o.push(d);return{path:i.concat(o.reverse()),lca:s}}function Hr(e){var n={},r=0;function t(i){var o=r;f(e.children(i),t),n[i]={low:o,lim:r++}}return f(e.children(),t),n}function Ur(e,n){var r={};function t(i,o){var a=0,u=0,d=i.length,s=F(o);return f(o,function(c,l){var h=Kr(e,c),v=h?e.node(h).order:d;(h||c===s)&&(f(o.slice(u,l+1),function(p){f(e.predecessors(p),function(b){var L=e.node(b),ne=L.order;(nes)&&Ge(r,h,c)})})}function i(o,a){var u=-1,d,s=0;return f(a,function(c,l){if(e.node(c).dummy==="border"){var h=e.predecessors(c);h.length&&(d=e.node(h[0]).order,t(a,s,l,u,d),s=l,u=d)}t(a,s,a.length,d,o.length)}),a}return B(n,i),r}function Kr(e,n){if(e.node(n).dummy)return U(e.predecessors(n),function(r){return e.node(r).dummy})}function Ge(e,n,r){if(n>r){var t=n;n=r,r=t}Object.prototype.hasOwnProperty.call(e,n)||Object.defineProperty(e,n,{enumerable:!0,configurable:!0,value:{},writable:!0});var i=e[n];Object.defineProperty(i,r,{enumerable:!0,configurable:!0,value:!0,writable:!0})}function Zr(e,n,r){if(n>r){var t=n;n=r,r=t}return!!e[n]&&Object.prototype.hasOwnProperty.call(e[n],r)}function Qr(e,n,r,t){var i={},o={},a={};return f(n,function(u){f(u,function(d,s){i[d]=d,o[d]=d,a[d]=s})}),f(n,function(u){var d=-1;f(u,function(s){var c=t(s);if(c.length){c=R(c,function(b){return a[b]});for(var l=(c.length-1)/2,h=Math.floor(l),v=Math.ceil(l);h<=v;++h){var p=c[h];o[s]===s&&d{var t=r(" buildLayoutGraph",()=>yt(e));r(" runLayout",()=>ft(t,r)),r(" updateInputGraph",()=>ct(e,t))})}function ft(e,n){n(" makeSpaceForEdgeLabels",()=>xt(e)),n(" removeSelfEdges",()=>It(e)),n(" acyclic",()=>Zn(e)),n(" nestingGraph.run",()=>Lr(e)),n(" rank",()=>kr(Le(e))),n(" injectEdgeLabelProxies",()=>Et(e)),n(" removeEmptyRanks",()=>tr(e)),n(" nestingGraph.cleanup",()=>Cr(e)),n(" normalizeRanks",()=>rr(e)),n(" assignRankMinMax",()=>kt(e)),n(" removeEdgeLabelProxies",()=>Ot(e)),n(" normalize.run",()=>cr(e)),n(" parentDummyChains",()=>zr(e)),n(" addBorderSegments",()=>or(e)),n(" order",()=>qr(e)),n(" insertSelfEdges",()=>Rt(e)),n(" adjustCoordinateSystem",()=>ur(e)),n(" position",()=>dt(e)),n(" positionSelfEdges",()=>Tt(e)),n(" removeBorderNodes",()=>Ct(e)),n(" normalize.undo",()=>hr(e)),n(" fixupEdgeLabelCoords",()=>Pt(e)),n(" undoCoordinateSystem",()=>dr(e)),n(" translateGraph",()=>Nt(e)),n(" assignNodeIntersects",()=>Lt(e)),n(" reversePoints",()=>_t(e)),n(" acyclic.undo",()=>er(e))}function ct(e,n){f(e.nodes(),function(r){var t=e.node(r),i=n.node(r);t&&(t.x=i.x,t.y=i.y,n.children(r).length&&(t.width=i.width,t.height=i.height))}),f(e.edges(),function(r){var t=e.edge(r),i=n.edge(r);t.points=i.points,Object.prototype.hasOwnProperty.call(i,"x")&&(t.x=i.x,t.y=i.y)}),e.graph().width=n.graph().width,e.graph().height=n.graph().height}var lt=["nodesep","edgesep","ranksep","marginx","marginy"],ht={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},vt=["acyclicer","ranker","rankdir","align"],pt=["width","height"],wt={width:0,height:0},bt=["minlen","weight","width","height","labeloffset"],mt={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},gt=["labelpos"];function yt(e){var n=new g({multigraph:!0,compound:!0}),r=W(e.graph());return n.setGraph(X({},ht,q(r,lt),A(r,vt))),f(e.nodes(),function(t){var i=W(e.node(t));n.setNode(t,En(q(i,pt),wt)),n.setParent(t,e.parent(t))}),f(e.edges(),function(t){var i=W(e.edge(t));n.setEdge(t,X({},mt,q(i,bt),A(i,gt)))}),n}function xt(e){var n=e.graph();n.ranksep/=2,f(e.edges(),function(r){var t=e.edge(r);t.minlen*=2,t.labelpos.toLowerCase()!=="c"&&(n.rankdir==="TB"||n.rankdir==="BT"?t.width+=t.labeloffset:t.height+=t.labeloffset)})}function Et(e){f(e.edges(),function(n){var r=e.edge(n);if(r.width&&r.height){var t=e.node(n.v),i=e.node(n.w),o={rank:(i.rank-t.rank)/2+t.rank,e:n};N(e,"edge-proxy",o,"_ep")}})}function kt(e){var n=0;f(e.nodes(),function(r){var t=e.node(r);t.borderTop&&(t.minRank=e.node(t.borderTop).rank,t.maxRank=e.node(t.borderBottom).rank,n=y(n,t.maxRank))}),e.graph().maxRank=n}function Ot(e){f(e.nodes(),function(n){var r=e.node(n);r.dummy==="edge-proxy"&&(e.edge(r.e).labelRank=r.rank,e.removeNode(n))})}function Nt(e){var n=Number.POSITIVE_INFINITY,r=0,t=Number.POSITIVE_INFINITY,i=0,o=e.graph(),a=o.marginx||0,u=o.marginy||0;function d(s){var c=s.x,l=s.y,h=s.width,v=s.height;n=Math.min(n,c-h/2),r=Math.max(r,c+h/2),t=Math.min(t,l-v/2),i=Math.max(i,l+v/2)}f(e.nodes(),function(s){d(e.node(s))}),f(e.edges(),function(s){var c=e.edge(s);Object.prototype.hasOwnProperty.call(c,"x")&&d(c)}),n-=a,t-=u,f(e.nodes(),function(s){var c=e.node(s);c.x-=n,c.y-=t}),f(e.edges(),function(s){var c=e.edge(s);f(c.points,function(l){l.x-=n,l.y-=t}),Object.prototype.hasOwnProperty.call(c,"x")&&(c.x-=n),Object.prototype.hasOwnProperty.call(c,"y")&&(c.y-=t)}),o.width=r-n+a,o.height=i-t+u}function Lt(e){f(e.edges(),function(n){var r=e.edge(n),t=e.node(n.v),i=e.node(n.w),o,a;r.points?(o=r.points[0],a=r.points[r.points.length-1]):(r.points=[],o=i,a=t),r.points.unshift(ae(t,o)),r.points.push(ae(i,a))})}function Pt(e){f(e.edges(),function(n){var r=e.edge(n);if(Object.prototype.hasOwnProperty.call(r,"x"))switch((r.labelpos==="l"||r.labelpos==="r")&&(r.width-=r.labeloffset),r.labelpos){case"l":r.x-=r.width/2+r.labeloffset;break;case"r":r.x+=r.width/2+r.labeloffset;break}})}function _t(e){f(e.edges(),function(n){var r=e.edge(n);r.reversed&&r.points.reverse()})}function Ct(e){f(e.nodes(),function(n){if(e.children(n).length){var r=e.node(n),t=e.node(r.borderTop),i=e.node(r.borderBottom),o=e.node(F(r.borderLeft)),a=e.node(F(r.borderRight));r.width=Math.abs(a.x-o.x),r.height=Math.abs(i.y-t.y),r.x=o.x+r.width/2,r.y=t.y+r.height/2}}),f(e.nodes(),function(n){e.node(n).dummy==="border"&&e.removeNode(n)})}function It(e){f(e.edges(),function(n){if(n.v===n.w){var r=e.node(n.v);r.selfEdges||(r.selfEdges=[]),r.selfEdges.push({e:n,label:e.edge(n)}),e.removeEdge(n)}})}function Rt(e){var n=G(e);f(n,function(r){var t=0;f(r,function(i,o){var a=e.node(i);a.order=o+t,f(a.selfEdges,function(u){N(e,"selfedge",{width:u.label.width,height:u.label.height,rank:a.rank,order:o+ ++t,e:u.e,label:u.label},"_se")}),delete a.selfEdges})})}function Tt(e){f(e.nodes(),function(n){var r=e.node(n);if(r.dummy==="selfedge"){var t=e.node(r.e.v),i=t.x+t.width/2,o=t.y,a=r.x-i,u=t.height/2;e.setEdge(r.e,r.label),e.removeNode(n),r.label.points=[{x:i+2*a/3,y:o-u},{x:i+5*a/6,y:o-u},{x:i+a,y:o},{x:i+5*a/6,y:o+u},{x:i+2*a/3,y:o+u}],r.label.x=r.x,r.label.y=r.y}})}function q(e,n){return j(A(e,n),Number)}function W(e){var n={};return f(e,function(r,t){n[t.toLowerCase()]=r}),n}export{Bt as l}; diff --git a/assets/chunks/min.fO5GJb76.js b/assets/chunks/min.fO5GJb76.js new file mode 100644 index 000000000..7b498e644 --- /dev/null +++ b/assets/chunks/min.fO5GJb76.js @@ -0,0 +1 @@ +import{b,a as m,c as d,d as h,i as l}from"./baseUniq.BHxmztwl.js";import{ax as g,ay as o,az as p}from"./theme.kqgpP4eL.js";function L(a){var n=a==null?0:a.length;return n?b(a):[]}function v(a,n){var s=-1,t=g(a)?Array(a.length):[];return m(a,function(f,i,e){t[++s]=n(f,i,e)}),t}function M(a,n){var s=o(a)?d:v;return s(a,h(n))}function x(a,n){return a"u"||!crypto.getRandomValues)throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");Y=crypto.getRandomValues.bind(crypto)}return Y(Ee)}const be=typeof crypto<"u"&&crypto.randomUUID&&crypto.randomUUID.bind(crypto),ne={randomUUID:be};function Se(t,e,n){var u;if(ne.randomUUID&&!t)return ne.randomUUID();t=t||{};const c=t.random??((u=t.rng)==null?void 0:u.call(t))??_e();if(c.length<16)throw new Error("Random bytes length must be >= 16");return c[6]=c[6]&15|64,c[8]=c[8]&63|128,me(c)}var q=function(){var t=l(function(v,s,i,a){for(i=i||{},a=v.length;a--;i[v[a]]=s);return i},"o"),e=[1,4],n=[1,13],c=[1,12],u=[1,15],h=[1,16],p=[1,20],y=[1,19],_=[6,7,8],b=[1,26],m=[1,24],w=[1,25],D=[6,7,11],J=[1,6,13,15,16,19,22],K=[1,33],Q=[1,34],A=[1,6,7,11,13,15,16,19,22],G={trace:l(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,MINDMAP:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,ICON:15,CLASS:16,nodeWithId:17,nodeWithoutId:18,NODE_DSTART:19,NODE_DESCR:20,NODE_DEND:21,NODE_ID:22,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"MINDMAP",11:"EOF",13:"SPACELIST",15:"ICON",16:"CLASS",19:"NODE_DSTART",20:"NODE_DESCR",21:"NODE_DEND",22:"NODE_ID"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,2],[12,2],[12,2],[12,1],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[18,3],[17,1],[17,4]],performAction:l(function(s,i,a,o,g,r,U){var d=r.length-1;switch(g){case 6:case 7:return o;case 8:o.getLogger().trace("Stop NL ");break;case 9:o.getLogger().trace("Stop EOF ");break;case 11:o.getLogger().trace("Stop NL2 ");break;case 12:o.getLogger().trace("Stop EOF2 ");break;case 15:o.getLogger().info("Node: ",r[d].id),o.addNode(r[d-1].length,r[d].id,r[d].descr,r[d].type);break;case 16:o.getLogger().trace("Icon: ",r[d]),o.decorateNode({icon:r[d]});break;case 17:case 21:o.decorateNode({class:r[d]});break;case 18:o.getLogger().trace("SPACELIST");break;case 19:o.getLogger().trace("Node: ",r[d].id),o.addNode(0,r[d].id,r[d].descr,r[d].type);break;case 20:o.decorateNode({icon:r[d]});break;case 25:o.getLogger().trace("node found ..",r[d-2]),this.$={id:r[d-1],descr:r[d-1],type:o.getType(r[d-2],r[d])};break;case 26:this.$={id:r[d],descr:r[d],type:o.nodeType.DEFAULT};break;case 27:o.getLogger().trace("node found ..",r[d-3]),this.$={id:r[d-3],descr:r[d-1],type:o.getType(r[d-2],r[d])};break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:e},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:e},{6:n,7:[1,10],9:9,12:11,13:c,14:14,15:u,16:h,17:17,18:18,19:p,22:y},t(_,[2,3]),{1:[2,2]},t(_,[2,4]),t(_,[2,5]),{1:[2,6],6:n,12:21,13:c,14:14,15:u,16:h,17:17,18:18,19:p,22:y},{6:n,9:22,12:11,13:c,14:14,15:u,16:h,17:17,18:18,19:p,22:y},{6:b,7:m,10:23,11:w},t(D,[2,22],{17:17,18:18,14:27,15:[1,28],16:[1,29],19:p,22:y}),t(D,[2,18]),t(D,[2,19]),t(D,[2,20]),t(D,[2,21]),t(D,[2,23]),t(D,[2,24]),t(D,[2,26],{19:[1,30]}),{20:[1,31]},{6:b,7:m,10:32,11:w},{1:[2,7],6:n,12:21,13:c,14:14,15:u,16:h,17:17,18:18,19:p,22:y},t(J,[2,14],{7:K,11:Q}),t(A,[2,8]),t(A,[2,9]),t(A,[2,10]),t(D,[2,15]),t(D,[2,16]),t(D,[2,17]),{20:[1,35]},{21:[1,36]},t(J,[2,13],{7:K,11:Q}),t(A,[2,11]),t(A,[2,12]),{21:[1,37]},t(D,[2,25]),t(D,[2,27])],defaultActions:{2:[2,1],6:[2,2]},parseError:l(function(s,i){if(i.recoverable)this.trace(s);else{var a=new Error(s);throw a.hash=i,a}},"parseError"),parse:l(function(s){var i=this,a=[0],o=[],g=[null],r=[],U=this.table,d="",M=0,Z=0,re=2,ee=1,ae=r.slice.call(arguments,1),f=Object.create(this.lexer),T={yy:{}};for(var H in this.yy)Object.prototype.hasOwnProperty.call(this.yy,H)&&(T.yy[H]=this.yy[H]);f.setInput(s,T.yy),T.yy.lexer=f,T.yy.parser=this,typeof f.yylloc>"u"&&(f.yylloc={});var $=f.yylloc;r.push($);var oe=f.options&&f.options.ranges;typeof T.yy.parseError=="function"?this.parseError=T.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ce(N){a.length=a.length-2*N,g.length=g.length-N,r.length=r.length-N}l(ce,"popStack");function te(){var N;return N=o.pop()||f.lex()||ee,typeof N!="number"&&(N instanceof Array&&(o=N,N=o.pop()),N=i.symbols_[N]||N),N}l(te,"lex");for(var S,O,k,z,I={},V,L,ie,B;;){if(O=a[a.length-1],this.defaultActions[O]?k=this.defaultActions[O]:((S===null||typeof S>"u")&&(S=te()),k=U[O]&&U[O][S]),typeof k>"u"||!k.length||!k[0]){var W="";B=[];for(V in U[O])this.terminals_[V]&&V>re&&B.push("'"+this.terminals_[V]+"'");f.showPosition?W="Parse error on line "+(M+1)+`: +`+f.showPosition()+` +Expecting `+B.join(", ")+", got '"+(this.terminals_[S]||S)+"'":W="Parse error on line "+(M+1)+": Unexpected "+(S==ee?"end of input":"'"+(this.terminals_[S]||S)+"'"),this.parseError(W,{text:f.match,token:this.terminals_[S]||S,line:f.yylineno,loc:$,expected:B})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+O+", token: "+S);switch(k[0]){case 1:a.push(S),g.push(f.yytext),r.push(f.yylloc),a.push(k[1]),S=null,Z=f.yyleng,d=f.yytext,M=f.yylineno,$=f.yylloc;break;case 2:if(L=this.productions_[k[1]][1],I.$=g[g.length-L],I._$={first_line:r[r.length-(L||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(L||1)].first_column,last_column:r[r.length-1].last_column},oe&&(I._$.range=[r[r.length-(L||1)].range[0],r[r.length-1].range[1]]),z=this.performAction.apply(I,[d,Z,M,T.yy,k[1],g,r].concat(ae)),typeof z<"u")return z;L&&(a=a.slice(0,-1*L*2),g=g.slice(0,-1*L),r=r.slice(0,-1*L)),a.push(this.productions_[k[1]][0]),g.push(I.$),r.push(I._$),ie=U[a[a.length-2]][a[a.length-1]],a.push(ie);break;case 3:return!0}}return!0},"parse")},se=function(){var v={EOF:1,parseError:l(function(i,a){if(this.yy.parser)this.yy.parser.parseError(i,a);else throw new Error(i)},"parseError"),setInput:l(function(s,i){return this.yy=i||this.yy||{},this._input=s,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:l(function(){var s=this._input[0];this.yytext+=s,this.yyleng++,this.offset++,this.match+=s,this.matched+=s;var i=s.match(/(?:\r\n?|\n).*/g);return i?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),s},"input"),unput:l(function(s){var i=s.length,a=s.split(/(?:\r\n?|\n)/g);this._input=s+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-i),this.offset-=i;var o=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),a.length-1&&(this.yylineno-=a.length-1);var g=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:a?(a.length===o.length?this.yylloc.first_column:0)+o[o.length-a.length].length-a[0].length:this.yylloc.first_column-i},this.options.ranges&&(this.yylloc.range=[g[0],g[0]+this.yyleng-i]),this.yyleng=this.yytext.length,this},"unput"),more:l(function(){return this._more=!0,this},"more"),reject:l(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:l(function(s){this.unput(this.match.slice(s))},"less"),pastInput:l(function(){var s=this.matched.substr(0,this.matched.length-this.match.length);return(s.length>20?"...":"")+s.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:l(function(){var s=this.match;return s.length<20&&(s+=this._input.substr(0,20-s.length)),(s.substr(0,20)+(s.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:l(function(){var s=this.pastInput(),i=new Array(s.length+1).join("-");return s+this.upcomingInput()+` +`+i+"^"},"showPosition"),test_match:l(function(s,i){var a,o,g;if(this.options.backtrack_lexer&&(g={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(g.yylloc.range=this.yylloc.range.slice(0))),o=s[0].match(/(?:\r\n?|\n).*/g),o&&(this.yylineno+=o.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:o?o[o.length-1].length-o[o.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+s[0].length},this.yytext+=s[0],this.match+=s[0],this.matches=s,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(s[0].length),this.matched+=s[0],a=this.performAction.call(this,this.yy,this,i,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),a)return a;if(this._backtrack){for(var r in g)this[r]=g[r];return!1}return!1},"test_match"),next:l(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var s,i,a,o;this._more||(this.yytext="",this.match="");for(var g=this._currentRules(),r=0;ri[0].length)){if(i=a,o=r,this.options.backtrack_lexer){if(s=this.test_match(a,g[r]),s!==!1)return s;if(this._backtrack){i=!1;continue}else return!1}else if(!this.options.flex)break}return i?(s=this.test_match(i,g[o]),s!==!1?s:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:l(function(){var i=this.next();return i||this.lex()},"lex"),begin:l(function(i){this.conditionStack.push(i)},"begin"),popState:l(function(){var i=this.conditionStack.length-1;return i>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:l(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:l(function(i){return i=this.conditionStack.length-1-Math.abs(i||0),i>=0?this.conditionStack[i]:"INITIAL"},"topState"),pushState:l(function(i){this.begin(i)},"pushState"),stateStackSize:l(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:l(function(i,a,o,g){switch(o){case 0:return i.getLogger().trace("Found comment",a.yytext),6;case 1:return 8;case 2:this.begin("CLASS");break;case 3:return this.popState(),16;case 4:this.popState();break;case 5:i.getLogger().trace("Begin icon"),this.begin("ICON");break;case 6:return i.getLogger().trace("SPACELINE"),6;case 7:return 7;case 8:return 15;case 9:i.getLogger().trace("end icon"),this.popState();break;case 10:return i.getLogger().trace("Exploding node"),this.begin("NODE"),19;case 11:return i.getLogger().trace("Cloud"),this.begin("NODE"),19;case 12:return i.getLogger().trace("Explosion Bang"),this.begin("NODE"),19;case 13:return i.getLogger().trace("Cloud Bang"),this.begin("NODE"),19;case 14:return this.begin("NODE"),19;case 15:return this.begin("NODE"),19;case 16:return this.begin("NODE"),19;case 17:return this.begin("NODE"),19;case 18:return 13;case 19:return 22;case 20:return 11;case 21:this.begin("NSTR2");break;case 22:return"NODE_DESCR";case 23:this.popState();break;case 24:i.getLogger().trace("Starting NSTR"),this.begin("NSTR");break;case 25:return i.getLogger().trace("description:",a.yytext),"NODE_DESCR";case 26:this.popState();break;case 27:return this.popState(),i.getLogger().trace("node end ))"),"NODE_DEND";case 28:return this.popState(),i.getLogger().trace("node end )"),"NODE_DEND";case 29:return this.popState(),i.getLogger().trace("node end ...",a.yytext),"NODE_DEND";case 30:return this.popState(),i.getLogger().trace("node end (("),"NODE_DEND";case 31:return this.popState(),i.getLogger().trace("node end (-"),"NODE_DEND";case 32:return this.popState(),i.getLogger().trace("node end (-"),"NODE_DEND";case 33:return this.popState(),i.getLogger().trace("node end (("),"NODE_DEND";case 34:return this.popState(),i.getLogger().trace("node end (("),"NODE_DEND";case 35:return i.getLogger().trace("Long description:",a.yytext),20;case 36:return i.getLogger().trace("Long description:",a.yytext),20}},"anonymous"),rules:[/^(?:\s*%%.*)/i,/^(?:mindmap\b)/i,/^(?::::)/i,/^(?:.+)/i,/^(?:\n)/i,/^(?:::icon\()/i,/^(?:[\s]+[\n])/i,/^(?:[\n]+)/i,/^(?:[^\)]+)/i,/^(?:\))/i,/^(?:-\))/i,/^(?:\(-)/i,/^(?:\)\))/i,/^(?:\))/i,/^(?:\(\()/i,/^(?:\{\{)/i,/^(?:\()/i,/^(?:\[)/i,/^(?:[\s]+)/i,/^(?:[^\(\[\n\)\{\}]+)/i,/^(?:$)/i,/^(?:["][`])/i,/^(?:[^`"]+)/i,/^(?:[`]["])/i,/^(?:["])/i,/^(?:[^"]+)/i,/^(?:["])/i,/^(?:[\)]\))/i,/^(?:[\)])/i,/^(?:[\]])/i,/^(?:\}\})/i,/^(?:\(-)/i,/^(?:-\))/i,/^(?:\(\()/i,/^(?:\()/i,/^(?:[^\)\]\(\}]+)/i,/^(?:.+(?!\(\())/i],conditions:{CLASS:{rules:[3,4],inclusive:!1},ICON:{rules:[8,9],inclusive:!1},NSTR2:{rules:[22,23],inclusive:!1},NSTR:{rules:[25,26],inclusive:!1},NODE:{rules:[21,24,27,28,29,30,31,32,33,34,35,36],inclusive:!1},INITIAL:{rules:[0,1,2,5,6,7,10,11,12,13,14,15,16,17,18,19,20],inclusive:!0}}};return v}();G.lexer=se;function P(){this.yy={}}return l(P,"Parser"),P.prototype=G,G.Parser=P,new P}();q.parser=q;var De=q,x={DEFAULT:0,NO_BORDER:0,ROUNDED_RECT:1,RECT:2,CIRCLE:3,CLOUD:4,BANG:5,HEXAGON:6},R,Ne=(R=class{constructor(){this.nodes=[],this.count=0,this.elements={},this.getLogger=this.getLogger.bind(this),this.nodeType=x,this.clear(),this.getType=this.getType.bind(this),this.getElementById=this.getElementById.bind(this),this.getParent=this.getParent.bind(this),this.getMindmap=this.getMindmap.bind(this),this.addNode=this.addNode.bind(this),this.decorateNode=this.decorateNode.bind(this)}clear(){this.nodes=[],this.count=0,this.elements={},this.baseLevel=void 0}getParent(e){for(let n=this.nodes.length-1;n>=0;n--)if(this.nodes[n].level0?this.nodes[0]:null}addNode(e,n,c,u){var m,w;C.info("addNode",e,n,c,u);let h=!1;this.nodes.length===0?(this.baseLevel=e,e=0,h=!0):this.baseLevel!==void 0&&(e=e-this.baseLevel,h=!1);const p=X();let y=((m=p.mindmap)==null?void 0:m.padding)??j.mindmap.padding;switch(u){case this.nodeType.ROUNDED_RECT:case this.nodeType.RECT:case this.nodeType.HEXAGON:y*=2;break}const _={id:this.count++,nodeId:F(n,p),level:e,descr:F(c,p),type:u,children:[],width:((w=p.mindmap)==null?void 0:w.maxNodeWidth)??j.mindmap.maxNodeWidth,padding:y,isRoot:h},b=this.getParent(e);if(b)b.children.push(_),this.nodes.push(_);else if(h)this.nodes.push(_);else throw new Error(`There can be only one root. No parent could be found for ("${_.descr}")`)}getType(e,n){switch(C.debug("In get type",e,n),e){case"[":return this.nodeType.RECT;case"(":return n===")"?this.nodeType.ROUNDED_RECT:this.nodeType.CLOUD;case"((":return this.nodeType.CIRCLE;case")":return this.nodeType.CLOUD;case"))":return this.nodeType.BANG;case"{{":return this.nodeType.HEXAGON;default:return this.nodeType.DEFAULT}}setElementForId(e,n){this.elements[e]=n}getElementById(e){return this.elements[e]}decorateNode(e){if(!e)return;const n=X(),c=this.nodes[this.nodes.length-1];e.icon&&(c.icon=F(e.icon,n)),e.class&&(c.class=F(e.class,n))}type2Str(e){switch(e){case this.nodeType.DEFAULT:return"no-border";case this.nodeType.RECT:return"rect";case this.nodeType.ROUNDED_RECT:return"rounded-rect";case this.nodeType.CIRCLE:return"circle";case this.nodeType.CLOUD:return"cloud";case this.nodeType.BANG:return"bang";case this.nodeType.HEXAGON:return"hexgon";default:return"no-border"}}assignSections(e,n){if(e.level===0?e.section=void 0:e.section=n,e.children)for(const[c,u]of e.children.entries()){const h=e.level===0?c:n;this.assignSections(u,h)}}flattenNodes(e,n){const c=["mindmap-node"];e.isRoot===!0?c.push("section-root","section--1"):e.section!==void 0&&c.push(`section-${e.section}`),e.class&&c.push(e.class);const u=c.join(" "),h=l(y=>{switch(y){case x.CIRCLE:return"mindmapCircle";case x.RECT:return"rect";case x.ROUNDED_RECT:return"rounded";case x.CLOUD:return"cloud";case x.BANG:return"bang";case x.HEXAGON:return"hexagon";case x.DEFAULT:return"defaultMindmapNode";case x.NO_BORDER:default:return"rect"}},"getShapeFromType"),p={id:e.id.toString(),domId:"node_"+e.id.toString(),label:e.descr,isGroup:!1,shape:h(e.type),width:e.width,height:e.height??0,padding:e.padding,cssClasses:u,cssStyles:[],look:"default",icon:e.icon,x:e.x,y:e.y,level:e.level,nodeId:e.nodeId,type:e.type,section:e.section};if(n.push(p),e.children)for(const y of e.children)this.flattenNodes(y,n)}generateEdges(e,n){if(e.children)for(const c of e.children){let u="edge";c.section!==void 0&&(u+=` section-edge-${c.section}`);const h=e.level+1;u+=` edge-depth-${h}`;const p={id:`edge_${e.id}_${c.id}`,start:e.id.toString(),end:c.id.toString(),type:"normal",curve:"basis",thickness:"normal",look:"default",classes:u,depth:e.level,section:c.section};n.push(p),this.generateEdges(c,n)}}getData(){const e=this.getMindmap(),n=X(),u=ue().layout!==void 0,h=n;if(u||(h.layout="cose-bilkent"),!e)return{nodes:[],edges:[],config:h};C.debug("getData: mindmapRoot",e,n),this.assignSections(e);const p=[],y=[];this.flattenNodes(e,p),this.generateEdges(e,y),C.debug(`getData: processed ${p.length} nodes and ${y.length} edges`);const _=new Map;for(const b of p)_.set(b.id,{shape:b.shape,width:b.width,height:b.height,padding:b.padding});return{nodes:p,edges:y,config:h,rootNode:e,markers:["point"],direction:"TB",nodeSpacing:50,rankSpacing:50,shapes:Object.fromEntries(_),type:"mindmap",diagramId:"mindmap-"+Se()}}getLogger(){return C}},l(R,"MindmapDB"),R),ke=l(async(t,e,n,c)=>{var _,b;C.debug(`Rendering mindmap diagram +`+t);const u=c.db,h=u.getData(),p=le(e,h.config.securityLevel);h.type=c.type,h.layoutAlgorithm=de(h.config.layout,{fallback:"cose-bilkent"}),h.diagramId=e,u.getMindmap()&&(h.nodes.forEach(m=>{m.shape==="rounded"?(m.radius=15,m.taper=15,m.stroke="none",m.width=0,m.padding=15):m.shape==="circle"?m.padding=10:m.shape==="rect"&&(m.width=0,m.padding=10)}),await ge(h,p),he(p,((_=h.config.mindmap)==null?void 0:_.padding)??j.mindmap.padding,"mindmapDiagram",((b=h.config.mindmap)==null?void 0:b.useMaxWidth)??j.mindmap.useMaxWidth))},"draw"),Le={draw:ke},xe=l(t=>{let e="";for(let n=0;n` + .edge { + stroke-width: 3; + } + ${xe(t)} + .section-root rect, .section-root path, .section-root circle, .section-root polygon { + fill: ${t.git0}; + } + .section-root text { + fill: ${t.gitBranchLabel0}; + } + .section-root span { + color: ${t.gitBranchLabel0}; + } + .section-2 span { + color: ${t.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .mindmap-node-label { + dy: 1em; + alignment-baseline: middle; + text-anchor: middle; + dominant-baseline: middle; + text-align: center; + } +`,"getStyles"),Te=ve,we={get db(){return new Ne},renderer:Le,parser:De,styles:Te};export{we as diagram}; diff --git a/assets/chunks/pieDiagram-ADFJNKIX.BdoKephD.js b/assets/chunks/pieDiagram-ADFJNKIX.BdoKephD.js new file mode 100644 index 000000000..563190cf3 --- /dev/null +++ b/assets/chunks/pieDiagram-ADFJNKIX.BdoKephD.js @@ -0,0 +1,30 @@ +import{_ as s,g as U,s as q,a as H,b as K,t as V,q as Z,l as w,c as j,F as J,K as Q,M as X,N as G,O as Y,e as ee,z as te,P as ae,H as re}from"./theme.kqgpP4eL.js";import{p as ie}from"./chunk-4BX2VUAB.B6a8mhSC.js";import{p as se}from"./treemap-KMMF4GRG.CcUr4GSN.js";import"./framework.CgT1UzWm.js";import"./min.fO5GJb76.js";import"./baseUniq.BHxmztwl.js";var le=re.pie,D={sections:new Map,showData:!1},g=D.sections,C=D.showData,oe=structuredClone(le),ne=s(()=>structuredClone(oe),"getConfig"),ce=s(()=>{g=new Map,C=D.showData,te()},"clear"),pe=s(({label:e,value:a})=>{if(a<0)throw new Error(`"${e}" has invalid value: ${a}. Negative values are not allowed in pie charts. All slice values must be >= 0.`);g.has(e)||(g.set(e,a),w.debug(`added new section: ${e}, with value: ${a}`))},"addSection"),de=s(()=>g,"getSections"),ge=s(e=>{C=e},"setShowData"),ue=s(()=>C,"getShowData"),M={getConfig:ne,clear:ce,setDiagramTitle:Z,getDiagramTitle:V,setAccTitle:K,getAccTitle:H,setAccDescription:q,getAccDescription:U,addSection:pe,getSections:de,setShowData:ge,getShowData:ue},fe=s((e,a)=>{ie(e,a),a.setShowData(e.showData),e.sections.map(a.addSection)},"populateDb"),he={parse:s(async e=>{const a=await se("pie",e);w.debug(a),fe(a,M)},"parse")},me=s(e=>` + .pieCircle{ + stroke: ${e.pieStrokeColor}; + stroke-width : ${e.pieStrokeWidth}; + opacity : ${e.pieOpacity}; + } + .pieOuterCircle{ + stroke: ${e.pieOuterStrokeColor}; + stroke-width: ${e.pieOuterStrokeWidth}; + fill: none; + } + .pieTitleText { + text-anchor: middle; + font-size: ${e.pieTitleTextSize}; + fill: ${e.pieTitleTextColor}; + font-family: ${e.fontFamily}; + } + .slice { + font-family: ${e.fontFamily}; + fill: ${e.pieSectionTextColor}; + font-size:${e.pieSectionTextSize}; + // fill: white; + } + .legend text { + fill: ${e.pieLegendTextColor}; + font-family: ${e.fontFamily}; + font-size: ${e.pieLegendTextSize}; + } +`,"getStyles"),ve=me,Se=s(e=>{const a=[...e.values()].reduce((r,l)=>r+l,0),$=[...e.entries()].map(([r,l])=>({label:r,value:l})).filter(r=>r.value/a*100>=1).sort((r,l)=>l.value-r.value);return ae().value(r=>r.value)($)},"createPieArcs"),xe=s((e,a,$,y)=>{w.debug(`rendering pie chart +`+e);const r=y.db,l=j(),T=J(r.getConfig(),l.pie),A=40,o=18,p=4,c=450,u=c,f=Q(a),n=f.append("g");n.attr("transform","translate("+u/2+","+c/2+")");const{themeVariables:i}=l;let[b]=X(i.pieOuterStrokeWidth);b??(b=2);const _=T.textPosition,d=Math.min(u,c)/2-A,O=G().innerRadius(0).outerRadius(d),P=G().innerRadius(d*_).outerRadius(d*_);n.append("circle").attr("cx",0).attr("cy",0).attr("r",d+b/2).attr("class","pieOuterCircle");const h=r.getSections(),W=Se(h),N=[i.pie1,i.pie2,i.pie3,i.pie4,i.pie5,i.pie6,i.pie7,i.pie8,i.pie9,i.pie10,i.pie11,i.pie12];let m=0;h.forEach(t=>{m+=t});const E=W.filter(t=>(t.data.value/m*100).toFixed(0)!=="0"),v=Y(N);n.selectAll("mySlices").data(E).enter().append("path").attr("d",O).attr("fill",t=>v(t.data.label)).attr("class","pieCircle"),n.selectAll("mySlices").data(E).enter().append("text").text(t=>(t.data.value/m*100).toFixed(0)+"%").attr("transform",t=>"translate("+P.centroid(t)+")").style("text-anchor","middle").attr("class","slice"),n.append("text").text(r.getDiagramTitle()).attr("x",0).attr("y",-400/2).attr("class","pieTitleText");const k=[...h.entries()].map(([t,x])=>({label:t,value:x})),S=n.selectAll(".legend").data(k).enter().append("g").attr("class","legend").attr("transform",(t,x)=>{const F=o+p,I=F*k.length/2,L=12*o,B=x*F-I;return"translate("+L+","+B+")"});S.append("rect").attr("width",o).attr("height",o).style("fill",t=>v(t.label)).style("stroke",t=>v(t.label)),S.append("text").attr("x",o+p).attr("y",o-p).text(t=>r.getShowData()?`${t.label} [${t.value}]`:t.label);const R=Math.max(...S.selectAll("text").nodes().map(t=>(t==null?void 0:t.getBoundingClientRect().width)??0)),z=u+A+o+p+R;f.attr("viewBox",`0 0 ${z} ${c}`),ee(f,c,z,T.useMaxWidth)},"draw"),we={draw:xe},be={parser:he,db:M,renderer:we,styles:ve};export{be as diagram}; diff --git a/assets/chunks/presentationSlides.D90XTQsY.js b/assets/chunks/presentationSlides.D90XTQsY.js new file mode 100644 index 000000000..006e84f84 --- /dev/null +++ b/assets/chunks/presentationSlides.D90XTQsY.js @@ -0,0 +1 @@ +const e=[{src:"/presentation/slide-1.webp",alt:"The backend that writes itself — title",notes:"One sentence: I built a tool that turns a Postgres database into a complete, production-grade backend — and I've been running a real product on it for 9 months."},{src:"/presentation/slide-2.webp",alt:"The problem — change one column, touch five files",notes:"Change one column and you touch five files in three languages. Every team on earth pays this tax. It has nothing to do with their product."},{src:"/presentation/slide-3.webp",alt:"The solution — a single native binary",notes:"A single native binary. You point it at your database, it builds the API at startup, and writes the typed frontend client into your source tree. The contract can't drift — it's generated from the schema minutes ago."},{src:"/presentation/slide-4.webp",alt:"An endpoint is a SQL file",notes:"The same endpoint in a traditional stack: route, controller, validation, DTO, service, repository, client type, client function — 5 to 7 files, 2 to 3 languages, plus a code review for each. Here, an endpoint is a SQL file."},{src:"/presentation/slide-5.webp",alt:"The type-system collapse — one source of truth",notes:"This is the type-system collapse: one source of truth. In the case-study product, 154 API contracts exist — and exactly zero are maintained by a human."},{src:"/presentation/slide-6.webp",alt:"The declarative holy grail",notes:"Declarative systems are the holy grail: you state intent and the machine guarantees execution. Everyone who chased it built a new language and died on the adoption curve. We didn't invent a language — we removed everything except the declarative language that won 50 years ago. And declaring intent is exactly what AI models are best at."},{src:"/presentation/slide-7.webp",alt:"Not a demo. A product. — measured figures",notes:"Every number on this slide is measured from the repo and reproducible — there's an appendix with the commands. The backend of this product is PostgreSQL plus one JSON file."},{src:"/presentation/slide-8.webp",alt:"Making change cheap — 108 absorbed changes",notes:"The origin story is requirements churn: stakeholders who only recognize what they want when they see it working. The only winning strategy is making change cheap. The generated client files changed 108 times — 108 rounds of 'they changed their mind' absorbed automatically."},{src:"/presentation/slide-9.webp",alt:"Security, caching, real-time — declarative and auditable",notes:"Security, caching, real-time — the checklist every business app needs — is declarative. You can audit the security posture of the whole API with grep."},{src:"/presentation/slide-10.webp",alt:"Performance — the convenient thing is also the fast thing",notes:"The usual trade is convenience vs performance. Here the convenient thing is also the fast thing, because we removed the layers instead of optimizing them. And note what we're compared against: hand-tuned raw-driver code no real team ships. Real projects use ORMs — the EF Core row, 30% slower."},{src:"/presentation/slide-11.webp",alt:"vs PostgREST and Supabase",notes:"PostgREST and Supabase validated the category. We differ on who writes the query: they let the client specify it, capping you at their URL syntax and exposing your whole schema. We say write SQL — the most expressive data language ever made — and expose exactly that. Plus we're 2.6× faster and one binary instead of seven services."},{src:"/presentation/slide-12.webp",alt:"Enterprise features, each an annotation",notes:"Deliver as a 20-second flyover — don't read the bullets. These are the unglamorous things enterprise deals hang on: upload an Excel file, encrypt SSNs, route reads to replicas. Each is normally a sprint; here each is an annotation. Longer list in the appendix."},{src:"/presentation/slide-13.webp",alt:"The worst-case measurement — still 28%",notes:"We measured the worst case for this architecture and still got 28%. The average business app does better, because the average business app is mostly the plumbing we delete."},{src:"/presentation/slide-14.webp",alt:"We removed the glue code",notes:"Everyone is making AI write the glue code faster. We removed the glue code. The agent reads two files, declares intent in SQL, and machines verify the rest. A third of commits AI-co-authored, with a human owning design and review — the realistic shape of AI development today, and this architecture is built for it."},{src:"/presentation/slide-15.webp",alt:"Letting AI agents touch your data safely",notes:"Every company is asking how to let AI agents touch their data safely. Our answer: the same governed, role-checked, rate-limited endpoints you already have — one annotation away. The database becomes the single source of truth for apps, frontends, AND agents. Note: it's still the same file."},{src:"/presentation/slide-16.webp",alt:"An AI-infrastructure play",notes:"Five years ago this would have been a developer-productivity tool. Today it's an AI-infrastructure play: the architecture that makes both AI-written code and AI-called APIs cheap and safe."},{src:"/presentation/slide-17.webp",alt:"The loop — one product, then multiply",notes:"End on the loop: we measured what this did for one product — a third of the code never written, one developer doing a team's work. Now multiply that across every product we build, and every AI agent that will want to talk to them."},{src:"/presentation/slide-18.webp",alt:"Appendix A1 — reproducible measurements",notes:"Reference slide. Every measured value is from the production repo and reproducible — exact commands in the case-study raw-data appendix."},{src:"/presentation/slide-19.webp",alt:"Appendix A2 — the full feature list",notes:"Don't read it — scroll it. The message is the LENGTH of the list, and that each line is an annotation or config block, not a sprint. PostgREST's answer to most of these is 'use a gateway'; Supabase's is 'use another service'; ours is 'it's already in the binary.'"}];export{e as p}; diff --git a/assets/chunks/quadrantDiagram-AYHSOK5B.QCp9GEfl.js b/assets/chunks/quadrantDiagram-AYHSOK5B.QCp9GEfl.js new file mode 100644 index 000000000..642260b03 --- /dev/null +++ b/assets/chunks/quadrantDiagram-AYHSOK5B.QCp9GEfl.js @@ -0,0 +1,7 @@ +import{_ as o,s as _e,g as Ae,t as ie,q as ke,a as Fe,b as Pe,c as wt,l as At,d as zt,e as ve,z as Ce,H as D,Q as Le,S as ee,i as Ee}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var Vt=function(){var t=o(function(j,r,l,g){for(l=l||{},g=j.length;g--;l[j[g]]=r);return l},"o"),n=[1,3],u=[1,4],c=[1,5],h=[1,6],p=[1,7],y=[1,4,5,10,12,13,14,18,25,35,37,39,41,42,48,50,51,52,53,54,55,56,57,60,61,63,64,65,66,67],S=[1,4,5,10,12,13,14,18,25,28,35,37,39,41,42,48,50,51,52,53,54,55,56,57,60,61,63,64,65,66,67],a=[55,56,57],A=[2,36],d=[1,37],T=[1,36],q=[1,38],m=[1,35],b=[1,43],x=[1,41],O=[1,14],Y=[1,23],G=[1,18],yt=[1,19],Tt=[1,20],dt=[1,21],Ft=[1,22],ut=[1,24],xt=[1,25],ft=[1,26],gt=[1,27],i=[1,28],Bt=[1,29],W=[1,32],Q=[1,33],k=[1,34],F=[1,39],P=[1,40],v=[1,42],C=[1,44],H=[1,62],X=[1,61],L=[4,5,8,10,12,13,14,18,44,47,49,55,56,57,63,64,65,66,67],Rt=[1,65],Nt=[1,66],Wt=[1,67],Qt=[1,68],Ut=[1,69],Ot=[1,70],Ht=[1,71],Xt=[1,72],Mt=[1,73],Yt=[1,74],jt=[1,75],Gt=[1,76],I=[4,5,6,7,8,9,10,11,12,13,14,15,18],J=[1,90],$=[1,91],tt=[1,92],et=[1,99],it=[1,93],at=[1,96],nt=[1,94],st=[1,95],rt=[1,97],ot=[1,98],Pt=[1,102],Kt=[10,55,56,57],R=[4,5,6,8,10,11,13,17,18,19,20,55,56,57],vt={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,idStringToken:3,ALPHA:4,NUM:5,NODE_STRING:6,DOWN:7,MINUS:8,DEFAULT:9,COMMA:10,COLON:11,AMP:12,BRKT:13,MULT:14,UNICODE_TEXT:15,styleComponent:16,UNIT:17,SPACE:18,STYLE:19,PCT:20,idString:21,style:22,stylesOpt:23,classDefStatement:24,CLASSDEF:25,start:26,eol:27,QUADRANT:28,document:29,line:30,statement:31,axisDetails:32,quadrantDetails:33,points:34,title:35,title_value:36,acc_title:37,acc_title_value:38,acc_descr:39,acc_descr_value:40,acc_descr_multiline_value:41,section:42,text:43,point_start:44,point_x:45,point_y:46,class_name:47,"X-AXIS":48,"AXIS-TEXT-DELIMITER":49,"Y-AXIS":50,QUADRANT_1:51,QUADRANT_2:52,QUADRANT_3:53,QUADRANT_4:54,NEWLINE:55,SEMI:56,EOF:57,alphaNumToken:58,textNoTagsToken:59,STR:60,MD_STR:61,alphaNum:62,PUNCTUATION:63,PLUS:64,EQUALS:65,DOT:66,UNDERSCORE:67,$accept:0,$end:1},terminals_:{2:"error",4:"ALPHA",5:"NUM",6:"NODE_STRING",7:"DOWN",8:"MINUS",9:"DEFAULT",10:"COMMA",11:"COLON",12:"AMP",13:"BRKT",14:"MULT",15:"UNICODE_TEXT",17:"UNIT",18:"SPACE",19:"STYLE",20:"PCT",25:"CLASSDEF",28:"QUADRANT",35:"title",36:"title_value",37:"acc_title",38:"acc_title_value",39:"acc_descr",40:"acc_descr_value",41:"acc_descr_multiline_value",42:"section",44:"point_start",45:"point_x",46:"point_y",47:"class_name",48:"X-AXIS",49:"AXIS-TEXT-DELIMITER",50:"Y-AXIS",51:"QUADRANT_1",52:"QUADRANT_2",53:"QUADRANT_3",54:"QUADRANT_4",55:"NEWLINE",56:"SEMI",57:"EOF",60:"STR",61:"MD_STR",63:"PUNCTUATION",64:"PLUS",65:"EQUALS",66:"DOT",67:"UNDERSCORE"},productions_:[0,[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[21,1],[21,2],[22,1],[22,2],[23,1],[23,3],[24,5],[26,2],[26,2],[26,2],[29,0],[29,2],[30,2],[31,0],[31,1],[31,2],[31,1],[31,1],[31,1],[31,2],[31,2],[31,2],[31,1],[31,1],[34,4],[34,5],[34,5],[34,6],[32,4],[32,3],[32,2],[32,4],[32,3],[32,2],[33,2],[33,2],[33,2],[33,2],[27,1],[27,1],[27,1],[43,1],[43,2],[43,1],[43,1],[62,1],[62,2],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[59,1],[59,1],[59,1]],performAction:o(function(r,l,g,f,_,e,pt){var s=e.length-1;switch(_){case 23:this.$=e[s];break;case 24:this.$=e[s-1]+""+e[s];break;case 26:this.$=e[s-1]+e[s];break;case 27:this.$=[e[s].trim()];break;case 28:e[s-2].push(e[s].trim()),this.$=e[s-2];break;case 29:this.$=e[s-4],f.addClass(e[s-2],e[s]);break;case 37:this.$=[];break;case 42:this.$=e[s].trim(),f.setDiagramTitle(this.$);break;case 43:this.$=e[s].trim(),f.setAccTitle(this.$);break;case 44:case 45:this.$=e[s].trim(),f.setAccDescription(this.$);break;case 46:f.addSection(e[s].substr(8)),this.$=e[s].substr(8);break;case 47:f.addPoint(e[s-3],"",e[s-1],e[s],[]);break;case 48:f.addPoint(e[s-4],e[s-3],e[s-1],e[s],[]);break;case 49:f.addPoint(e[s-4],"",e[s-2],e[s-1],e[s]);break;case 50:f.addPoint(e[s-5],e[s-4],e[s-2],e[s-1],e[s]);break;case 51:f.setXAxisLeftText(e[s-2]),f.setXAxisRightText(e[s]);break;case 52:e[s-1].text+=" ⟶ ",f.setXAxisLeftText(e[s-1]);break;case 53:f.setXAxisLeftText(e[s]);break;case 54:f.setYAxisBottomText(e[s-2]),f.setYAxisTopText(e[s]);break;case 55:e[s-1].text+=" ⟶ ",f.setYAxisBottomText(e[s-1]);break;case 56:f.setYAxisBottomText(e[s]);break;case 57:f.setQuadrant1Text(e[s]);break;case 58:f.setQuadrant2Text(e[s]);break;case 59:f.setQuadrant3Text(e[s]);break;case 60:f.setQuadrant4Text(e[s]);break;case 64:this.$={text:e[s],type:"text"};break;case 65:this.$={text:e[s-1].text+""+e[s],type:e[s-1].type};break;case 66:this.$={text:e[s],type:"text"};break;case 67:this.$={text:e[s],type:"markdown"};break;case 68:this.$=e[s];break;case 69:this.$=e[s-1]+""+e[s];break}},"anonymous"),table:[{18:n,26:1,27:2,28:u,55:c,56:h,57:p},{1:[3]},{18:n,26:8,27:2,28:u,55:c,56:h,57:p},{18:n,26:9,27:2,28:u,55:c,56:h,57:p},t(y,[2,33],{29:10}),t(S,[2,61]),t(S,[2,62]),t(S,[2,63]),{1:[2,30]},{1:[2,31]},t(a,A,{30:11,31:12,24:13,32:15,33:16,34:17,43:30,58:31,1:[2,32],4:d,5:T,10:q,12:m,13:b,14:x,18:O,25:Y,35:G,37:yt,39:Tt,41:dt,42:Ft,48:ut,50:xt,51:ft,52:gt,53:i,54:Bt,60:W,61:Q,63:k,64:F,65:P,66:v,67:C}),t(y,[2,34]),{27:45,55:c,56:h,57:p},t(a,[2,37]),t(a,A,{24:13,32:15,33:16,34:17,43:30,58:31,31:46,4:d,5:T,10:q,12:m,13:b,14:x,18:O,25:Y,35:G,37:yt,39:Tt,41:dt,42:Ft,48:ut,50:xt,51:ft,52:gt,53:i,54:Bt,60:W,61:Q,63:k,64:F,65:P,66:v,67:C}),t(a,[2,39]),t(a,[2,40]),t(a,[2,41]),{36:[1,47]},{38:[1,48]},{40:[1,49]},t(a,[2,45]),t(a,[2,46]),{18:[1,50]},{4:d,5:T,10:q,12:m,13:b,14:x,43:51,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,10:q,12:m,13:b,14:x,43:52,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,10:q,12:m,13:b,14:x,43:53,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,10:q,12:m,13:b,14:x,43:54,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,10:q,12:m,13:b,14:x,43:55,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,10:q,12:m,13:b,14:x,43:56,58:31,60:W,61:Q,63:k,64:F,65:P,66:v,67:C},{4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,44:[1,57],47:[1,58],58:60,59:59,63:k,64:F,65:P,66:v,67:C},t(L,[2,64]),t(L,[2,66]),t(L,[2,67]),t(L,[2,70]),t(L,[2,71]),t(L,[2,72]),t(L,[2,73]),t(L,[2,74]),t(L,[2,75]),t(L,[2,76]),t(L,[2,77]),t(L,[2,78]),t(L,[2,79]),t(L,[2,80]),t(y,[2,35]),t(a,[2,38]),t(a,[2,42]),t(a,[2,43]),t(a,[2,44]),{3:64,4:Rt,5:Nt,6:Wt,7:Qt,8:Ut,9:Ot,10:Ht,11:Xt,12:Mt,13:Yt,14:jt,15:Gt,21:63},t(a,[2,53],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,49:[1,77],63:k,64:F,65:P,66:v,67:C}),t(a,[2,56],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,49:[1,78],63:k,64:F,65:P,66:v,67:C}),t(a,[2,57],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),t(a,[2,58],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),t(a,[2,59],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),t(a,[2,60],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),{45:[1,79]},{44:[1,80]},t(L,[2,65]),t(L,[2,81]),t(L,[2,82]),t(L,[2,83]),{3:82,4:Rt,5:Nt,6:Wt,7:Qt,8:Ut,9:Ot,10:Ht,11:Xt,12:Mt,13:Yt,14:jt,15:Gt,18:[1,81]},t(I,[2,23]),t(I,[2,1]),t(I,[2,2]),t(I,[2,3]),t(I,[2,4]),t(I,[2,5]),t(I,[2,6]),t(I,[2,7]),t(I,[2,8]),t(I,[2,9]),t(I,[2,10]),t(I,[2,11]),t(I,[2,12]),t(a,[2,52],{58:31,43:83,4:d,5:T,10:q,12:m,13:b,14:x,60:W,61:Q,63:k,64:F,65:P,66:v,67:C}),t(a,[2,55],{58:31,43:84,4:d,5:T,10:q,12:m,13:b,14:x,60:W,61:Q,63:k,64:F,65:P,66:v,67:C}),{46:[1,85]},{45:[1,86]},{4:J,5:$,6:tt,8:et,11:it,13:at,16:89,17:nt,18:st,19:rt,20:ot,22:88,23:87},t(I,[2,24]),t(a,[2,51],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),t(a,[2,54],{59:59,58:60,4:d,5:T,8:H,10:q,12:m,13:b,14:x,18:X,63:k,64:F,65:P,66:v,67:C}),t(a,[2,47],{22:88,16:89,23:100,4:J,5:$,6:tt,8:et,11:it,13:at,17:nt,18:st,19:rt,20:ot}),{46:[1,101]},t(a,[2,29],{10:Pt}),t(Kt,[2,27],{16:103,4:J,5:$,6:tt,8:et,11:it,13:at,17:nt,18:st,19:rt,20:ot}),t(R,[2,25]),t(R,[2,13]),t(R,[2,14]),t(R,[2,15]),t(R,[2,16]),t(R,[2,17]),t(R,[2,18]),t(R,[2,19]),t(R,[2,20]),t(R,[2,21]),t(R,[2,22]),t(a,[2,49],{10:Pt}),t(a,[2,48],{22:88,16:89,23:104,4:J,5:$,6:tt,8:et,11:it,13:at,17:nt,18:st,19:rt,20:ot}),{4:J,5:$,6:tt,8:et,11:it,13:at,16:89,17:nt,18:st,19:rt,20:ot,22:105},t(R,[2,26]),t(a,[2,50],{10:Pt}),t(Kt,[2,28],{16:103,4:J,5:$,6:tt,8:et,11:it,13:at,17:nt,18:st,19:rt,20:ot})],defaultActions:{8:[2,30],9:[2,31]},parseError:o(function(r,l){if(l.recoverable)this.trace(r);else{var g=new Error(r);throw g.hash=l,g}},"parseError"),parse:o(function(r){var l=this,g=[0],f=[],_=[null],e=[],pt=this.table,s="",mt=0,Zt=0,qe=2,Jt=1,me=e.slice.call(arguments,1),E=Object.create(this.lexer),K={yy:{}};for(var Ct in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Ct)&&(K.yy[Ct]=this.yy[Ct]);E.setInput(r,K.yy),K.yy.lexer=E,K.yy.parser=this,typeof E.yylloc>"u"&&(E.yylloc={});var Lt=E.yylloc;e.push(Lt);var be=E.options&&E.options.ranges;typeof K.yy.parseError=="function"?this.parseError=K.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Se(B){g.length=g.length-2*B,_.length=_.length-B,e.length=e.length-B}o(Se,"popStack");function $t(){var B;return B=f.pop()||E.lex()||Jt,typeof B!="number"&&(B instanceof Array&&(f=B,B=f.pop()),B=l.symbols_[B]||B),B}o($t,"lex");for(var w,Z,N,Et,lt={},bt,M,te,St;;){if(Z=g[g.length-1],this.defaultActions[Z]?N=this.defaultActions[Z]:((w===null||typeof w>"u")&&(w=$t()),N=pt[Z]&&pt[Z][w]),typeof N>"u"||!N.length||!N[0]){var Dt="";St=[];for(bt in pt[Z])this.terminals_[bt]&&bt>qe&&St.push("'"+this.terminals_[bt]+"'");E.showPosition?Dt="Parse error on line "+(mt+1)+`: +`+E.showPosition()+` +Expecting `+St.join(", ")+", got '"+(this.terminals_[w]||w)+"'":Dt="Parse error on line "+(mt+1)+": Unexpected "+(w==Jt?"end of input":"'"+(this.terminals_[w]||w)+"'"),this.parseError(Dt,{text:E.match,token:this.terminals_[w]||w,line:E.yylineno,loc:Lt,expected:St})}if(N[0]instanceof Array&&N.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Z+", token: "+w);switch(N[0]){case 1:g.push(w),_.push(E.yytext),e.push(E.yylloc),g.push(N[1]),w=null,Zt=E.yyleng,s=E.yytext,mt=E.yylineno,Lt=E.yylloc;break;case 2:if(M=this.productions_[N[1]][1],lt.$=_[_.length-M],lt._$={first_line:e[e.length-(M||1)].first_line,last_line:e[e.length-1].last_line,first_column:e[e.length-(M||1)].first_column,last_column:e[e.length-1].last_column},be&&(lt._$.range=[e[e.length-(M||1)].range[0],e[e.length-1].range[1]]),Et=this.performAction.apply(lt,[s,Zt,mt,K.yy,N[1],_,e].concat(me)),typeof Et<"u")return Et;M&&(g=g.slice(0,-1*M*2),_=_.slice(0,-1*M),e=e.slice(0,-1*M)),g.push(this.productions_[N[1]][0]),_.push(lt.$),e.push(lt._$),te=pt[g[g.length-2]][g[g.length-1]],g.push(te);break;case 3:return!0}}return!0},"parse")},Te=function(){var j={EOF:1,parseError:o(function(l,g){if(this.yy.parser)this.yy.parser.parseError(l,g);else throw new Error(l)},"parseError"),setInput:o(function(r,l){return this.yy=l||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var l=r.match(/(?:\r\n?|\n).*/g);return l?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:o(function(r){var l=r.length,g=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-l),this.offset-=l;var f=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var _=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===f.length?this.yylloc.first_column:0)+f[f.length-g.length].length-g[0].length:this.yylloc.first_column-l},this.options.ranges&&(this.yylloc.range=[_[0],_[0]+this.yyleng-l]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(r){this.unput(this.match.slice(r))},"less"),pastInput:o(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var r=this.pastInput(),l=new Array(r.length+1).join("-");return r+this.upcomingInput()+` +`+l+"^"},"showPosition"),test_match:o(function(r,l){var g,f,_;if(this.options.backtrack_lexer&&(_={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(_.yylloc.range=this.yylloc.range.slice(0))),f=r[0].match(/(?:\r\n?|\n).*/g),f&&(this.yylineno+=f.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:f?f[f.length-1].length-f[f.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+r[0].length},this.yytext+=r[0],this.match+=r[0],this.matches=r,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(r[0].length),this.matched+=r[0],g=this.performAction.call(this,this.yy,this,l,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),g)return g;if(this._backtrack){for(var e in _)this[e]=_[e];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var r,l,g,f;this._more||(this.yytext="",this.match="");for(var _=this._currentRules(),e=0;e<_.length;e++)if(g=this._input.match(this.rules[_[e]]),g&&(!l||g[0].length>l[0].length)){if(l=g,f=e,this.options.backtrack_lexer){if(r=this.test_match(g,_[e]),r!==!1)return r;if(this._backtrack){l=!1;continue}else return!1}else if(!this.options.flex)break}return l?(r=this.test_match(l,_[f]),r!==!1?r:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var l=this.next();return l||this.lex()},"lex"),begin:o(function(l){this.conditionStack.push(l)},"begin"),popState:o(function(){var l=this.conditionStack.length-1;return l>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(l){return l=this.conditionStack.length-1-Math.abs(l||0),l>=0?this.conditionStack[l]:"INITIAL"},"topState"),pushState:o(function(l){this.begin(l)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(l,g,f,_){switch(f){case 0:break;case 1:break;case 2:return 55;case 3:break;case 4:return this.begin("title"),35;case 5:return this.popState(),"title_value";case 6:return this.begin("acc_title"),37;case 7:return this.popState(),"acc_title_value";case 8:return this.begin("acc_descr"),39;case 9:return this.popState(),"acc_descr_value";case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:return 48;case 14:return 50;case 15:return 49;case 16:return 51;case 17:return 52;case 18:return 53;case 19:return 54;case 20:return 25;case 21:this.begin("md_string");break;case 22:return"MD_STR";case 23:this.popState();break;case 24:this.begin("string");break;case 25:this.popState();break;case 26:return"STR";case 27:this.begin("class_name");break;case 28:return this.popState(),47;case 29:return this.begin("point_start"),44;case 30:return this.begin("point_x"),45;case 31:this.popState();break;case 32:this.popState(),this.begin("point_y");break;case 33:return this.popState(),46;case 34:return 28;case 35:return 4;case 36:return 11;case 37:return 64;case 38:return 10;case 39:return 65;case 40:return 65;case 41:return 14;case 42:return 13;case 43:return 67;case 44:return 66;case 45:return 12;case 46:return 8;case 47:return 5;case 48:return 18;case 49:return 56;case 50:return 63;case 51:return 57}},"anonymous"),rules:[/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?: *x-axis *)/i,/^(?: *y-axis *)/i,/^(?: *--+> *)/i,/^(?: *quadrant-1 *)/i,/^(?: *quadrant-2 *)/i,/^(?: *quadrant-3 *)/i,/^(?: *quadrant-4 *)/i,/^(?:classDef\b)/i,/^(?:["][`])/i,/^(?:[^`"]+)/i,/^(?:[`]["])/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?::::)/i,/^(?:^\w+)/i,/^(?:\s*:\s*\[\s*)/i,/^(?:(1)|(0(.\d+)?))/i,/^(?:\s*\] *)/i,/^(?:\s*,\s*)/i,/^(?:(1)|(0(.\d+)?))/i,/^(?: *quadrantChart *)/i,/^(?:[A-Za-z]+)/i,/^(?::)/i,/^(?:\+)/i,/^(?:,)/i,/^(?:=)/i,/^(?:=)/i,/^(?:\*)/i,/^(?:#)/i,/^(?:[\_])/i,/^(?:\.)/i,/^(?:&)/i,/^(?:-)/i,/^(?:[0-9]+)/i,/^(?:\s)/i,/^(?:;)/i,/^(?:[!"#$%&'*+,-.`?\\_/])/i,/^(?:$)/i],conditions:{class_name:{rules:[28],inclusive:!1},point_y:{rules:[33],inclusive:!1},point_x:{rules:[32],inclusive:!1},point_start:{rules:[30,31],inclusive:!1},acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},title:{rules:[5],inclusive:!1},md_string:{rules:[22,23],inclusive:!1},string:{rules:[25,26],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,6,8,10,13,14,15,16,17,18,19,20,21,24,27,29,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],inclusive:!0}}};return j}();vt.lexer=Te;function qt(){this.yy={}}return o(qt,"Parser"),qt.prototype=vt,vt.Parser=qt,new qt}();Vt.parser=Vt;var De=Vt,V=Le(),ht,ze=(ht=class{constructor(){this.classes=new Map,this.config=this.getDefaultConfig(),this.themeConfig=this.getDefaultThemeConfig(),this.data=this.getDefaultData()}getDefaultData(){return{titleText:"",quadrant1Text:"",quadrant2Text:"",quadrant3Text:"",quadrant4Text:"",xAxisLeftText:"",xAxisRightText:"",yAxisBottomText:"",yAxisTopText:"",points:[]}}getDefaultConfig(){var n,u,c,h,p,y,S,a,A,d,T,q,m,b,x,O,Y,G;return{showXAxis:!0,showYAxis:!0,showTitle:!0,chartHeight:((n=D.quadrantChart)==null?void 0:n.chartWidth)||500,chartWidth:((u=D.quadrantChart)==null?void 0:u.chartHeight)||500,titlePadding:((c=D.quadrantChart)==null?void 0:c.titlePadding)||10,titleFontSize:((h=D.quadrantChart)==null?void 0:h.titleFontSize)||20,quadrantPadding:((p=D.quadrantChart)==null?void 0:p.quadrantPadding)||5,xAxisLabelPadding:((y=D.quadrantChart)==null?void 0:y.xAxisLabelPadding)||5,yAxisLabelPadding:((S=D.quadrantChart)==null?void 0:S.yAxisLabelPadding)||5,xAxisLabelFontSize:((a=D.quadrantChart)==null?void 0:a.xAxisLabelFontSize)||16,yAxisLabelFontSize:((A=D.quadrantChart)==null?void 0:A.yAxisLabelFontSize)||16,quadrantLabelFontSize:((d=D.quadrantChart)==null?void 0:d.quadrantLabelFontSize)||16,quadrantTextTopPadding:((T=D.quadrantChart)==null?void 0:T.quadrantTextTopPadding)||5,pointTextPadding:((q=D.quadrantChart)==null?void 0:q.pointTextPadding)||5,pointLabelFontSize:((m=D.quadrantChart)==null?void 0:m.pointLabelFontSize)||12,pointRadius:((b=D.quadrantChart)==null?void 0:b.pointRadius)||5,xAxisPosition:((x=D.quadrantChart)==null?void 0:x.xAxisPosition)||"top",yAxisPosition:((O=D.quadrantChart)==null?void 0:O.yAxisPosition)||"left",quadrantInternalBorderStrokeWidth:((Y=D.quadrantChart)==null?void 0:Y.quadrantInternalBorderStrokeWidth)||1,quadrantExternalBorderStrokeWidth:((G=D.quadrantChart)==null?void 0:G.quadrantExternalBorderStrokeWidth)||2}}getDefaultThemeConfig(){return{quadrant1Fill:V.quadrant1Fill,quadrant2Fill:V.quadrant2Fill,quadrant3Fill:V.quadrant3Fill,quadrant4Fill:V.quadrant4Fill,quadrant1TextFill:V.quadrant1TextFill,quadrant2TextFill:V.quadrant2TextFill,quadrant3TextFill:V.quadrant3TextFill,quadrant4TextFill:V.quadrant4TextFill,quadrantPointFill:V.quadrantPointFill,quadrantPointTextFill:V.quadrantPointTextFill,quadrantXAxisTextFill:V.quadrantXAxisTextFill,quadrantYAxisTextFill:V.quadrantYAxisTextFill,quadrantTitleFill:V.quadrantTitleFill,quadrantInternalBorderStrokeFill:V.quadrantInternalBorderStrokeFill,quadrantExternalBorderStrokeFill:V.quadrantExternalBorderStrokeFill}}clear(){this.config=this.getDefaultConfig(),this.themeConfig=this.getDefaultThemeConfig(),this.data=this.getDefaultData(),this.classes=new Map,At.info("clear called")}setData(n){this.data={...this.data,...n}}addPoints(n){this.data.points=[...n,...this.data.points]}addClass(n,u){this.classes.set(n,u)}setConfig(n){At.trace("setConfig called with: ",n),this.config={...this.config,...n}}setThemeConfig(n){At.trace("setThemeConfig called with: ",n),this.themeConfig={...this.themeConfig,...n}}calculateSpace(n,u,c,h){const p=this.config.xAxisLabelPadding*2+this.config.xAxisLabelFontSize,y={top:n==="top"&&u?p:0,bottom:n==="bottom"&&u?p:0},S=this.config.yAxisLabelPadding*2+this.config.yAxisLabelFontSize,a={left:this.config.yAxisPosition==="left"&&c?S:0,right:this.config.yAxisPosition==="right"&&c?S:0},A=this.config.titleFontSize+this.config.titlePadding*2,d={top:h?A:0},T=this.config.quadrantPadding+a.left,q=this.config.quadrantPadding+y.top+d.top,m=this.config.chartWidth-this.config.quadrantPadding*2-a.left-a.right,b=this.config.chartHeight-this.config.quadrantPadding*2-y.top-y.bottom-d.top,x=m/2,O=b/2;return{xAxisSpace:y,yAxisSpace:a,titleSpace:d,quadrantSpace:{quadrantLeft:T,quadrantTop:q,quadrantWidth:m,quadrantHalfWidth:x,quadrantHeight:b,quadrantHalfHeight:O}}}getAxisLabels(n,u,c,h){const{quadrantSpace:p,titleSpace:y}=h,{quadrantHalfHeight:S,quadrantHeight:a,quadrantLeft:A,quadrantHalfWidth:d,quadrantTop:T,quadrantWidth:q}=p,m=!!this.data.xAxisRightText,b=!!this.data.yAxisTopText,x=[];return this.data.xAxisLeftText&&u&&x.push({text:this.data.xAxisLeftText,fill:this.themeConfig.quadrantXAxisTextFill,x:A+(m?d/2:0),y:n==="top"?this.config.xAxisLabelPadding+y.top:this.config.xAxisLabelPadding+T+a+this.config.quadrantPadding,fontSize:this.config.xAxisLabelFontSize,verticalPos:m?"center":"left",horizontalPos:"top",rotation:0}),this.data.xAxisRightText&&u&&x.push({text:this.data.xAxisRightText,fill:this.themeConfig.quadrantXAxisTextFill,x:A+d+(m?d/2:0),y:n==="top"?this.config.xAxisLabelPadding+y.top:this.config.xAxisLabelPadding+T+a+this.config.quadrantPadding,fontSize:this.config.xAxisLabelFontSize,verticalPos:m?"center":"left",horizontalPos:"top",rotation:0}),this.data.yAxisBottomText&&c&&x.push({text:this.data.yAxisBottomText,fill:this.themeConfig.quadrantYAxisTextFill,x:this.config.yAxisPosition==="left"?this.config.yAxisLabelPadding:this.config.yAxisLabelPadding+A+q+this.config.quadrantPadding,y:T+a-(b?S/2:0),fontSize:this.config.yAxisLabelFontSize,verticalPos:b?"center":"left",horizontalPos:"top",rotation:-90}),this.data.yAxisTopText&&c&&x.push({text:this.data.yAxisTopText,fill:this.themeConfig.quadrantYAxisTextFill,x:this.config.yAxisPosition==="left"?this.config.yAxisLabelPadding:this.config.yAxisLabelPadding+A+q+this.config.quadrantPadding,y:T+S-(b?S/2:0),fontSize:this.config.yAxisLabelFontSize,verticalPos:b?"center":"left",horizontalPos:"top",rotation:-90}),x}getQuadrants(n){const{quadrantSpace:u}=n,{quadrantHalfHeight:c,quadrantLeft:h,quadrantHalfWidth:p,quadrantTop:y}=u,S=[{text:{text:this.data.quadrant1Text,fill:this.themeConfig.quadrant1TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:h+p,y,width:p,height:c,fill:this.themeConfig.quadrant1Fill},{text:{text:this.data.quadrant2Text,fill:this.themeConfig.quadrant2TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:h,y,width:p,height:c,fill:this.themeConfig.quadrant2Fill},{text:{text:this.data.quadrant3Text,fill:this.themeConfig.quadrant3TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:h,y:y+c,width:p,height:c,fill:this.themeConfig.quadrant3Fill},{text:{text:this.data.quadrant4Text,fill:this.themeConfig.quadrant4TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:h+p,y:y+c,width:p,height:c,fill:this.themeConfig.quadrant4Fill}];for(const a of S)a.text.x=a.x+a.width/2,this.data.points.length===0?(a.text.y=a.y+a.height/2,a.text.horizontalPos="middle"):(a.text.y=a.y+this.config.quadrantTextTopPadding,a.text.horizontalPos="top");return S}getQuadrantPoints(n){const{quadrantSpace:u}=n,{quadrantHeight:c,quadrantLeft:h,quadrantTop:p,quadrantWidth:y}=u,S=ee().domain([0,1]).range([h,y+h]),a=ee().domain([0,1]).range([c+p,p]);return this.data.points.map(d=>{const T=this.classes.get(d.className);return T&&(d={...T,...d}),{x:S(d.x),y:a(d.y),fill:d.color??this.themeConfig.quadrantPointFill,radius:d.radius??this.config.pointRadius,text:{text:d.text,fill:this.themeConfig.quadrantPointTextFill,x:S(d.x),y:a(d.y)+this.config.pointTextPadding,verticalPos:"center",horizontalPos:"top",fontSize:this.config.pointLabelFontSize,rotation:0},strokeColor:d.strokeColor??this.themeConfig.quadrantPointFill,strokeWidth:d.strokeWidth??"0px"}})}getBorders(n){const u=this.config.quadrantExternalBorderStrokeWidth/2,{quadrantSpace:c}=n,{quadrantHalfHeight:h,quadrantHeight:p,quadrantLeft:y,quadrantHalfWidth:S,quadrantTop:a,quadrantWidth:A}=c;return[{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:y-u,y1:a,x2:y+A+u,y2:a},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:y+A,y1:a+u,x2:y+A,y2:a+p-u},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:y-u,y1:a+p,x2:y+A+u,y2:a+p},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:y,y1:a+u,x2:y,y2:a+p-u},{strokeFill:this.themeConfig.quadrantInternalBorderStrokeFill,strokeWidth:this.config.quadrantInternalBorderStrokeWidth,x1:y+S,y1:a+u,x2:y+S,y2:a+p-u},{strokeFill:this.themeConfig.quadrantInternalBorderStrokeFill,strokeWidth:this.config.quadrantInternalBorderStrokeWidth,x1:y+u,y1:a+h,x2:y+A-u,y2:a+h}]}getTitle(n){if(n)return{text:this.data.titleText,fill:this.themeConfig.quadrantTitleFill,fontSize:this.config.titleFontSize,horizontalPos:"top",verticalPos:"center",rotation:0,y:this.config.titlePadding,x:this.config.chartWidth/2}}build(){const n=this.config.showXAxis&&!!(this.data.xAxisLeftText||this.data.xAxisRightText),u=this.config.showYAxis&&!!(this.data.yAxisTopText||this.data.yAxisBottomText),c=this.config.showTitle&&!!this.data.titleText,h=this.data.points.length>0?"bottom":this.config.xAxisPosition,p=this.calculateSpace(h,n,u,c);return{points:this.getQuadrantPoints(p),quadrants:this.getQuadrants(p),axisLabels:this.getAxisLabels(h,n,u,p),borderLines:this.getBorders(p),title:this.getTitle(c)}}},o(ht,"QuadrantBuilder"),ht),ct,_t=(ct=class extends Error{constructor(n,u,c){super(`value for ${n} ${u} is invalid, please use a valid ${c}`),this.name="InvalidStyleError"}},o(ct,"InvalidStyleError"),ct);function It(t){return!/^#?([\dA-Fa-f]{6}|[\dA-Fa-f]{3})$/.test(t)}o(It,"validateHexCode");function ae(t){return!/^\d+$/.test(t)}o(ae,"validateNumber");function ne(t){return!/^\d+px$/.test(t)}o(ne,"validateSizeInPixels");var Ve=wt();function U(t){return Ee(t.trim(),Ve)}o(U,"textSanitizer");var z=new ze;function se(t){z.setData({quadrant1Text:U(t.text)})}o(se,"setQuadrant1Text");function re(t){z.setData({quadrant2Text:U(t.text)})}o(re,"setQuadrant2Text");function oe(t){z.setData({quadrant3Text:U(t.text)})}o(oe,"setQuadrant3Text");function le(t){z.setData({quadrant4Text:U(t.text)})}o(le,"setQuadrant4Text");function he(t){z.setData({xAxisLeftText:U(t.text)})}o(he,"setXAxisLeftText");function ce(t){z.setData({xAxisRightText:U(t.text)})}o(ce,"setXAxisRightText");function de(t){z.setData({yAxisTopText:U(t.text)})}o(de,"setYAxisTopText");function ue(t){z.setData({yAxisBottomText:U(t.text)})}o(ue,"setYAxisBottomText");function kt(t){const n={};for(const u of t){const[c,h]=u.trim().split(/\s*:\s*/);if(c==="radius"){if(ae(h))throw new _t(c,h,"number");n.radius=parseInt(h)}else if(c==="color"){if(It(h))throw new _t(c,h,"hex code");n.color=h}else if(c==="stroke-color"){if(It(h))throw new _t(c,h,"hex code");n.strokeColor=h}else if(c==="stroke-width"){if(ne(h))throw new _t(c,h,"number of pixels (eg. 10px)");n.strokeWidth=h}else throw new Error(`style named ${c} is not supported.`)}return n}o(kt,"parseStyles");function xe(t,n,u,c,h){const p=kt(h);z.addPoints([{x:u,y:c,text:U(t.text),className:n,...p}])}o(xe,"addPoint");function fe(t,n){z.addClass(t,kt(n))}o(fe,"addClass");function ge(t){z.setConfig({chartWidth:t})}o(ge,"setWidth");function pe(t){z.setConfig({chartHeight:t})}o(pe,"setHeight");function ye(){const t=wt(),{themeVariables:n,quadrantChart:u}=t;return u&&z.setConfig(u),z.setThemeConfig({quadrant1Fill:n.quadrant1Fill,quadrant2Fill:n.quadrant2Fill,quadrant3Fill:n.quadrant3Fill,quadrant4Fill:n.quadrant4Fill,quadrant1TextFill:n.quadrant1TextFill,quadrant2TextFill:n.quadrant2TextFill,quadrant3TextFill:n.quadrant3TextFill,quadrant4TextFill:n.quadrant4TextFill,quadrantPointFill:n.quadrantPointFill,quadrantPointTextFill:n.quadrantPointTextFill,quadrantXAxisTextFill:n.quadrantXAxisTextFill,quadrantYAxisTextFill:n.quadrantYAxisTextFill,quadrantExternalBorderStrokeFill:n.quadrantExternalBorderStrokeFill,quadrantInternalBorderStrokeFill:n.quadrantInternalBorderStrokeFill,quadrantTitleFill:n.quadrantTitleFill}),z.setData({titleText:ie()}),z.build()}o(ye,"getQuadrantData");var Ie=o(function(){z.clear(),Ce()},"clear"),we={setWidth:ge,setHeight:pe,setQuadrant1Text:se,setQuadrant2Text:re,setQuadrant3Text:oe,setQuadrant4Text:le,setXAxisLeftText:he,setXAxisRightText:ce,setYAxisTopText:de,setYAxisBottomText:ue,parseStyles:kt,addPoint:xe,addClass:fe,getQuadrantData:ye,clear:Ie,setAccTitle:Pe,getAccTitle:Fe,setDiagramTitle:ke,getDiagramTitle:ie,getAccDescription:Ae,setAccDescription:_e},Be=o((t,n,u,c)=>{var xt,ft,gt;function h(i){return i==="top"?"hanging":"middle"}o(h,"getDominantBaseLine");function p(i){return i==="left"?"start":"middle"}o(p,"getTextAnchor");function y(i){return`translate(${i.x}, ${i.y}) rotate(${i.rotation||0})`}o(y,"getTransformation");const S=wt();At.debug(`Rendering quadrant chart +`+t);const a=S.securityLevel;let A;a==="sandbox"&&(A=zt("#i"+n));const T=(a==="sandbox"?zt(A.nodes()[0].contentDocument.body):zt("body")).select(`[id="${n}"]`),q=T.append("g").attr("class","main"),m=((xt=S.quadrantChart)==null?void 0:xt.chartWidth)??500,b=((ft=S.quadrantChart)==null?void 0:ft.chartHeight)??500;ve(T,b,m,((gt=S.quadrantChart)==null?void 0:gt.useMaxWidth)??!0),T.attr("viewBox","0 0 "+m+" "+b),c.db.setHeight(b),c.db.setWidth(m);const x=c.db.getQuadrantData(),O=q.append("g").attr("class","quadrants"),Y=q.append("g").attr("class","border"),G=q.append("g").attr("class","data-points"),yt=q.append("g").attr("class","labels"),Tt=q.append("g").attr("class","title");x.title&&Tt.append("text").attr("x",0).attr("y",0).attr("fill",x.title.fill).attr("font-size",x.title.fontSize).attr("dominant-baseline",h(x.title.horizontalPos)).attr("text-anchor",p(x.title.verticalPos)).attr("transform",y(x.title)).text(x.title.text),x.borderLines&&Y.selectAll("line").data(x.borderLines).enter().append("line").attr("x1",i=>i.x1).attr("y1",i=>i.y1).attr("x2",i=>i.x2).attr("y2",i=>i.y2).style("stroke",i=>i.strokeFill).style("stroke-width",i=>i.strokeWidth);const dt=O.selectAll("g.quadrant").data(x.quadrants).enter().append("g").attr("class","quadrant");dt.append("rect").attr("x",i=>i.x).attr("y",i=>i.y).attr("width",i=>i.width).attr("height",i=>i.height).attr("fill",i=>i.fill),dt.append("text").attr("x",0).attr("y",0).attr("fill",i=>i.text.fill).attr("font-size",i=>i.text.fontSize).attr("dominant-baseline",i=>h(i.text.horizontalPos)).attr("text-anchor",i=>p(i.text.verticalPos)).attr("transform",i=>y(i.text)).text(i=>i.text.text),yt.selectAll("g.label").data(x.axisLabels).enter().append("g").attr("class","label").append("text").attr("x",0).attr("y",0).text(i=>i.text).attr("fill",i=>i.fill).attr("font-size",i=>i.fontSize).attr("dominant-baseline",i=>h(i.horizontalPos)).attr("text-anchor",i=>p(i.verticalPos)).attr("transform",i=>y(i));const ut=G.selectAll("g.data-point").data(x.points).enter().append("g").attr("class","data-point");ut.append("circle").attr("cx",i=>i.x).attr("cy",i=>i.y).attr("r",i=>i.radius).attr("fill",i=>i.fill).attr("stroke",i=>i.strokeColor).attr("stroke-width",i=>i.strokeWidth),ut.append("text").attr("x",0).attr("y",0).text(i=>i.text.text).attr("fill",i=>i.text.fill).attr("font-size",i=>i.text.fontSize).attr("dominant-baseline",i=>h(i.text.horizontalPos)).attr("text-anchor",i=>p(i.text.verticalPos)).attr("transform",i=>y(i.text))},"draw"),Re={draw:Be},Qe={parser:De,db:we,renderer:Re,styles:o(()=>"","styles")};export{Qe as diagram}; diff --git a/assets/chunks/requirementDiagram-UZGBJVZJ.CyKYuSjS.js b/assets/chunks/requirementDiagram-UZGBJVZJ.CyKYuSjS.js new file mode 100644 index 000000000..c06972534 --- /dev/null +++ b/assets/chunks/requirementDiagram-UZGBJVZJ.CyKYuSjS.js @@ -0,0 +1,64 @@ +import{g as ze}from"./chunk-55IACEB6.BKKqJU_2.js";import{s as Ge}from"./chunk-QN33PNHL.ChYgkhtD.js";import{_ as m,b as Xe,a as Je,s as Ze,g as et,q as tt,t as st,c as Ne,l as qe,z as it,D as rt,p as nt,r as at,u as lt}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var Ae=function(){var e=m(function(P,i,r,l){for(r=r||{},l=P.length;l--;r[P[l]]=i);return r},"o"),a=[1,3],u=[1,4],o=[1,5],f=[1,6],c=[5,6,8,9,11,13,21,22,23,24,41,42,43,44,45,46,54,72,74,77,89,90],p=[1,22],R=[2,7],h=[1,26],E=[1,27],I=[1,28],k=[1,29],A=[1,33],C=[1,34],V=[1,35],v=[1,36],x=[1,37],L=[1,38],D=[1,24],O=[1,31],w=[1,32],M=[1,30],g=[1,39],_=[1,40],y=[5,8,9,11,13,21,22,23,24,41,42,43,44,45,46,54,72,74,77,89,90],$=[1,61],X=[89,90],Ce=[5,8,9,11,13,21,22,23,24,27,29,41,42,43,44,45,46,54,61,63,72,74,75,76,77,80,81,82,83,84,85,86,87,88,89,90],de=[27,29],Ve=[1,70],ve=[1,71],xe=[1,72],Le=[1,73],De=[1,74],Oe=[1,75],we=[1,76],ee=[1,83],U=[1,80],te=[1,84],se=[1,85],ie=[1,86],re=[1,87],ne=[1,88],ae=[1,89],le=[1,90],ce=[1,91],oe=[1,92],pe=[5,8,9,11,13,21,22,23,24,27,41,42,43,44,45,46,54,72,74,75,76,77,80,81,82,83,84,85,86,87,88,89,90],Y=[63,64],Me=[1,101],Fe=[5,8,9,11,13,21,22,23,24,41,42,43,44,45,46,54,72,74,76,77,89,90],N=[5,8,9,11,13,21,22,23,24,41,42,43,44,45,46,54,72,74,75,76,77,80,81,82,83,84,85,86,87,88,89,90],B=[1,110],Q=[1,106],H=[1,107],K=[1,108],W=[1,109],j=[1,111],he=[1,116],ue=[1,117],me=[1,114],fe=[1,115],Se={trace:m(function(){},"trace"),yy:{},symbols_:{error:2,start:3,directive:4,NEWLINE:5,RD:6,diagram:7,EOF:8,acc_title:9,acc_title_value:10,acc_descr:11,acc_descr_value:12,acc_descr_multiline_value:13,requirementDef:14,elementDef:15,relationshipDef:16,direction:17,styleStatement:18,classDefStatement:19,classStatement:20,direction_tb:21,direction_bt:22,direction_rl:23,direction_lr:24,requirementType:25,requirementName:26,STRUCT_START:27,requirementBody:28,STYLE_SEPARATOR:29,idList:30,ID:31,COLONSEP:32,id:33,TEXT:34,text:35,RISK:36,riskLevel:37,VERIFYMTHD:38,verifyType:39,STRUCT_STOP:40,REQUIREMENT:41,FUNCTIONAL_REQUIREMENT:42,INTERFACE_REQUIREMENT:43,PERFORMANCE_REQUIREMENT:44,PHYSICAL_REQUIREMENT:45,DESIGN_CONSTRAINT:46,LOW_RISK:47,MED_RISK:48,HIGH_RISK:49,VERIFY_ANALYSIS:50,VERIFY_DEMONSTRATION:51,VERIFY_INSPECTION:52,VERIFY_TEST:53,ELEMENT:54,elementName:55,elementBody:56,TYPE:57,type:58,DOCREF:59,ref:60,END_ARROW_L:61,relationship:62,LINE:63,END_ARROW_R:64,CONTAINS:65,COPIES:66,DERIVES:67,SATISFIES:68,VERIFIES:69,REFINES:70,TRACES:71,CLASSDEF:72,stylesOpt:73,CLASS:74,ALPHA:75,COMMA:76,STYLE:77,style:78,styleComponent:79,NUM:80,COLON:81,UNIT:82,SPACE:83,BRKT:84,PCT:85,MINUS:86,LABEL:87,SEMICOLON:88,unqString:89,qString:90,$accept:0,$end:1},terminals_:{2:"error",5:"NEWLINE",6:"RD",8:"EOF",9:"acc_title",10:"acc_title_value",11:"acc_descr",12:"acc_descr_value",13:"acc_descr_multiline_value",21:"direction_tb",22:"direction_bt",23:"direction_rl",24:"direction_lr",27:"STRUCT_START",29:"STYLE_SEPARATOR",31:"ID",32:"COLONSEP",34:"TEXT",36:"RISK",38:"VERIFYMTHD",40:"STRUCT_STOP",41:"REQUIREMENT",42:"FUNCTIONAL_REQUIREMENT",43:"INTERFACE_REQUIREMENT",44:"PERFORMANCE_REQUIREMENT",45:"PHYSICAL_REQUIREMENT",46:"DESIGN_CONSTRAINT",47:"LOW_RISK",48:"MED_RISK",49:"HIGH_RISK",50:"VERIFY_ANALYSIS",51:"VERIFY_DEMONSTRATION",52:"VERIFY_INSPECTION",53:"VERIFY_TEST",54:"ELEMENT",57:"TYPE",59:"DOCREF",61:"END_ARROW_L",63:"LINE",64:"END_ARROW_R",65:"CONTAINS",66:"COPIES",67:"DERIVES",68:"SATISFIES",69:"VERIFIES",70:"REFINES",71:"TRACES",72:"CLASSDEF",74:"CLASS",75:"ALPHA",76:"COMMA",77:"STYLE",80:"NUM",81:"COLON",82:"UNIT",83:"SPACE",84:"BRKT",85:"PCT",86:"MINUS",87:"LABEL",88:"SEMICOLON",89:"unqString",90:"qString"},productions_:[0,[3,3],[3,2],[3,4],[4,2],[4,2],[4,1],[7,0],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[17,1],[17,1],[17,1],[17,1],[14,5],[14,7],[28,5],[28,5],[28,5],[28,5],[28,2],[28,1],[25,1],[25,1],[25,1],[25,1],[25,1],[25,1],[37,1],[37,1],[37,1],[39,1],[39,1],[39,1],[39,1],[15,5],[15,7],[56,5],[56,5],[56,2],[56,1],[16,5],[16,5],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[62,1],[19,3],[20,3],[20,3],[30,1],[30,3],[30,1],[30,3],[18,3],[73,1],[73,3],[78,1],[78,2],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[26,1],[26,1],[33,1],[33,1],[35,1],[35,1],[55,1],[55,1],[58,1],[58,1],[60,1],[60,1]],performAction:m(function(i,r,l,s,d,t,Ee){var n=t.length-1;switch(d){case 4:this.$=t[n].trim(),s.setAccTitle(this.$);break;case 5:case 6:this.$=t[n].trim(),s.setAccDescription(this.$);break;case 7:this.$=[];break;case 17:s.setDirection("TB");break;case 18:s.setDirection("BT");break;case 19:s.setDirection("RL");break;case 20:s.setDirection("LR");break;case 21:s.addRequirement(t[n-3],t[n-4]);break;case 22:s.addRequirement(t[n-5],t[n-6]),s.setClass([t[n-5]],t[n-3]);break;case 23:s.setNewReqId(t[n-2]);break;case 24:s.setNewReqText(t[n-2]);break;case 25:s.setNewReqRisk(t[n-2]);break;case 26:s.setNewReqVerifyMethod(t[n-2]);break;case 29:this.$=s.RequirementType.REQUIREMENT;break;case 30:this.$=s.RequirementType.FUNCTIONAL_REQUIREMENT;break;case 31:this.$=s.RequirementType.INTERFACE_REQUIREMENT;break;case 32:this.$=s.RequirementType.PERFORMANCE_REQUIREMENT;break;case 33:this.$=s.RequirementType.PHYSICAL_REQUIREMENT;break;case 34:this.$=s.RequirementType.DESIGN_CONSTRAINT;break;case 35:this.$=s.RiskLevel.LOW_RISK;break;case 36:this.$=s.RiskLevel.MED_RISK;break;case 37:this.$=s.RiskLevel.HIGH_RISK;break;case 38:this.$=s.VerifyType.VERIFY_ANALYSIS;break;case 39:this.$=s.VerifyType.VERIFY_DEMONSTRATION;break;case 40:this.$=s.VerifyType.VERIFY_INSPECTION;break;case 41:this.$=s.VerifyType.VERIFY_TEST;break;case 42:s.addElement(t[n-3]);break;case 43:s.addElement(t[n-5]),s.setClass([t[n-5]],t[n-3]);break;case 44:s.setNewElementType(t[n-2]);break;case 45:s.setNewElementDocRef(t[n-2]);break;case 48:s.addRelationship(t[n-2],t[n],t[n-4]);break;case 49:s.addRelationship(t[n-2],t[n-4],t[n]);break;case 50:this.$=s.Relationships.CONTAINS;break;case 51:this.$=s.Relationships.COPIES;break;case 52:this.$=s.Relationships.DERIVES;break;case 53:this.$=s.Relationships.SATISFIES;break;case 54:this.$=s.Relationships.VERIFIES;break;case 55:this.$=s.Relationships.REFINES;break;case 56:this.$=s.Relationships.TRACES;break;case 57:this.$=t[n-2],s.defineClass(t[n-1],t[n]);break;case 58:s.setClass(t[n-1],t[n]);break;case 59:s.setClass([t[n-2]],t[n]);break;case 60:case 62:this.$=[t[n]];break;case 61:case 63:this.$=t[n-2].concat([t[n]]);break;case 64:this.$=t[n-2],s.setCssStyle(t[n-1],t[n]);break;case 65:this.$=[t[n]];break;case 66:t[n-2].push(t[n]),this.$=t[n-2];break;case 68:this.$=t[n-1]+t[n];break}},"anonymous"),table:[{3:1,4:2,6:a,9:u,11:o,13:f},{1:[3]},{3:8,4:2,5:[1,7],6:a,9:u,11:o,13:f},{5:[1,9]},{10:[1,10]},{12:[1,11]},e(c,[2,6]),{3:12,4:2,6:a,9:u,11:o,13:f},{1:[2,2]},{4:17,5:p,7:13,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},e(c,[2,4]),e(c,[2,5]),{1:[2,1]},{8:[1,41]},{4:17,5:p,7:42,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:43,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:44,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:45,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:46,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:47,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:48,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:49,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{4:17,5:p,7:50,8:R,9:u,11:o,13:f,14:14,15:15,16:16,17:18,18:19,19:20,20:21,21:h,22:E,23:I,24:k,25:23,33:25,41:A,42:C,43:V,44:v,45:x,46:L,54:D,72:O,74:w,77:M,89:g,90:_},{26:51,89:[1,52],90:[1,53]},{55:54,89:[1,55],90:[1,56]},{29:[1,59],61:[1,57],63:[1,58]},e(y,[2,17]),e(y,[2,18]),e(y,[2,19]),e(y,[2,20]),{30:60,33:62,75:$,89:g,90:_},{30:63,33:62,75:$,89:g,90:_},{30:64,33:62,75:$,89:g,90:_},e(X,[2,29]),e(X,[2,30]),e(X,[2,31]),e(X,[2,32]),e(X,[2,33]),e(X,[2,34]),e(Ce,[2,81]),e(Ce,[2,82]),{1:[2,3]},{8:[2,8]},{8:[2,9]},{8:[2,10]},{8:[2,11]},{8:[2,12]},{8:[2,13]},{8:[2,14]},{8:[2,15]},{8:[2,16]},{27:[1,65],29:[1,66]},e(de,[2,79]),e(de,[2,80]),{27:[1,67],29:[1,68]},e(de,[2,85]),e(de,[2,86]),{62:69,65:Ve,66:ve,67:xe,68:Le,69:De,70:Oe,71:we},{62:77,65:Ve,66:ve,67:xe,68:Le,69:De,70:Oe,71:we},{30:78,33:62,75:$,89:g,90:_},{73:79,75:ee,76:U,78:81,79:82,80:te,81:se,82:ie,83:re,84:ne,85:ae,86:le,87:ce,88:oe},e(pe,[2,60]),e(pe,[2,62]),{73:93,75:ee,76:U,78:81,79:82,80:te,81:se,82:ie,83:re,84:ne,85:ae,86:le,87:ce,88:oe},{30:94,33:62,75:$,76:U,89:g,90:_},{5:[1,95]},{30:96,33:62,75:$,89:g,90:_},{5:[1,97]},{30:98,33:62,75:$,89:g,90:_},{63:[1,99]},e(Y,[2,50]),e(Y,[2,51]),e(Y,[2,52]),e(Y,[2,53]),e(Y,[2,54]),e(Y,[2,55]),e(Y,[2,56]),{64:[1,100]},e(y,[2,59],{76:U}),e(y,[2,64],{76:Me}),{33:103,75:[1,102],89:g,90:_},e(Fe,[2,65],{79:104,75:ee,80:te,81:se,82:ie,83:re,84:ne,85:ae,86:le,87:ce,88:oe}),e(N,[2,67]),e(N,[2,69]),e(N,[2,70]),e(N,[2,71]),e(N,[2,72]),e(N,[2,73]),e(N,[2,74]),e(N,[2,75]),e(N,[2,76]),e(N,[2,77]),e(N,[2,78]),e(y,[2,57],{76:Me}),e(y,[2,58],{76:U}),{5:B,28:105,31:Q,34:H,36:K,38:W,40:j},{27:[1,112],76:U},{5:he,40:ue,56:113,57:me,59:fe},{27:[1,118],76:U},{33:119,89:g,90:_},{33:120,89:g,90:_},{75:ee,78:121,79:82,80:te,81:se,82:ie,83:re,84:ne,85:ae,86:le,87:ce,88:oe},e(pe,[2,61]),e(pe,[2,63]),e(N,[2,68]),e(y,[2,21]),{32:[1,122]},{32:[1,123]},{32:[1,124]},{32:[1,125]},{5:B,28:126,31:Q,34:H,36:K,38:W,40:j},e(y,[2,28]),{5:[1,127]},e(y,[2,42]),{32:[1,128]},{32:[1,129]},{5:he,40:ue,56:130,57:me,59:fe},e(y,[2,47]),{5:[1,131]},e(y,[2,48]),e(y,[2,49]),e(Fe,[2,66],{79:104,75:ee,80:te,81:se,82:ie,83:re,84:ne,85:ae,86:le,87:ce,88:oe}),{33:132,89:g,90:_},{35:133,89:[1,134],90:[1,135]},{37:136,47:[1,137],48:[1,138],49:[1,139]},{39:140,50:[1,141],51:[1,142],52:[1,143],53:[1,144]},e(y,[2,27]),{5:B,28:145,31:Q,34:H,36:K,38:W,40:j},{58:146,89:[1,147],90:[1,148]},{60:149,89:[1,150],90:[1,151]},e(y,[2,46]),{5:he,40:ue,56:152,57:me,59:fe},{5:[1,153]},{5:[1,154]},{5:[2,83]},{5:[2,84]},{5:[1,155]},{5:[2,35]},{5:[2,36]},{5:[2,37]},{5:[1,156]},{5:[2,38]},{5:[2,39]},{5:[2,40]},{5:[2,41]},e(y,[2,22]),{5:[1,157]},{5:[2,87]},{5:[2,88]},{5:[1,158]},{5:[2,89]},{5:[2,90]},e(y,[2,43]),{5:B,28:159,31:Q,34:H,36:K,38:W,40:j},{5:B,28:160,31:Q,34:H,36:K,38:W,40:j},{5:B,28:161,31:Q,34:H,36:K,38:W,40:j},{5:B,28:162,31:Q,34:H,36:K,38:W,40:j},{5:he,40:ue,56:163,57:me,59:fe},{5:he,40:ue,56:164,57:me,59:fe},e(y,[2,23]),e(y,[2,24]),e(y,[2,25]),e(y,[2,26]),e(y,[2,44]),e(y,[2,45])],defaultActions:{8:[2,2],12:[2,1],41:[2,3],42:[2,8],43:[2,9],44:[2,10],45:[2,11],46:[2,12],47:[2,13],48:[2,14],49:[2,15],50:[2,16],134:[2,83],135:[2,84],137:[2,35],138:[2,36],139:[2,37],141:[2,38],142:[2,39],143:[2,40],144:[2,41],147:[2,87],148:[2,88],150:[2,89],151:[2,90]},parseError:m(function(i,r){if(r.recoverable)this.trace(i);else{var l=new Error(i);throw l.hash=r,l}},"parseError"),parse:m(function(i){var r=this,l=[0],s=[],d=[null],t=[],Ee=this.table,n="",ye=0,Pe=0,He=2,$e=1,Ke=t.slice.call(arguments,1),S=Object.create(this.lexer),z={yy:{}};for(var Ie in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Ie)&&(z.yy[Ie]=this.yy[Ie]);S.setInput(i,z.yy),z.yy.lexer=S,z.yy.parser=this,typeof S.yylloc>"u"&&(S.yylloc={});var be=S.yylloc;t.push(be);var We=S.options&&S.options.ranges;typeof z.yy.parseError=="function"?this.parseError=z.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function je(T){l.length=l.length-2*T,d.length=d.length-T,t.length=t.length-T}m(je,"popStack");function Ue(){var T;return T=s.pop()||S.lex()||$e,typeof T!="number"&&(T instanceof Array&&(s=T,T=s.pop()),T=r.symbols_[T]||T),T}m(Ue,"lex");for(var b,G,q,Te,J={},ge,F,Ye,_e;;){if(G=l[l.length-1],this.defaultActions[G]?q=this.defaultActions[G]:((b===null||typeof b>"u")&&(b=Ue()),q=Ee[G]&&Ee[G][b]),typeof q>"u"||!q.length||!q[0]){var ke="";_e=[];for(ge in Ee[G])this.terminals_[ge]&&ge>He&&_e.push("'"+this.terminals_[ge]+"'");S.showPosition?ke="Parse error on line "+(ye+1)+`: +`+S.showPosition()+` +Expecting `+_e.join(", ")+", got '"+(this.terminals_[b]||b)+"'":ke="Parse error on line "+(ye+1)+": Unexpected "+(b==$e?"end of input":"'"+(this.terminals_[b]||b)+"'"),this.parseError(ke,{text:S.match,token:this.terminals_[b]||b,line:S.yylineno,loc:be,expected:_e})}if(q[0]instanceof Array&&q.length>1)throw new Error("Parse Error: multiple actions possible at state: "+G+", token: "+b);switch(q[0]){case 1:l.push(b),d.push(S.yytext),t.push(S.yylloc),l.push(q[1]),b=null,Pe=S.yyleng,n=S.yytext,ye=S.yylineno,be=S.yylloc;break;case 2:if(F=this.productions_[q[1]][1],J.$=d[d.length-F],J._$={first_line:t[t.length-(F||1)].first_line,last_line:t[t.length-1].last_line,first_column:t[t.length-(F||1)].first_column,last_column:t[t.length-1].last_column},We&&(J._$.range=[t[t.length-(F||1)].range[0],t[t.length-1].range[1]]),Te=this.performAction.apply(J,[n,Pe,ye,z.yy,q[1],d,t].concat(Ke)),typeof Te<"u")return Te;F&&(l=l.slice(0,-1*F*2),d=d.slice(0,-1*F),t=t.slice(0,-1*F)),l.push(this.productions_[q[1]][0]),d.push(J.$),t.push(J._$),Ye=Ee[l[l.length-2]][l[l.length-1]],l.push(Ye);break;case 3:return!0}}return!0},"parse")},Qe=function(){var P={EOF:1,parseError:m(function(r,l){if(this.yy.parser)this.yy.parser.parseError(r,l);else throw new Error(r)},"parseError"),setInput:m(function(i,r){return this.yy=r||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:m(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var r=i.match(/(?:\r\n?|\n).*/g);return r?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:m(function(i){var r=i.length,l=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-r),this.offset-=r;var s=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var d=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===s.length?this.yylloc.first_column:0)+s[s.length-l.length].length-l[0].length:this.yylloc.first_column-r},this.options.ranges&&(this.yylloc.range=[d[0],d[0]+this.yyleng-r]),this.yyleng=this.yytext.length,this},"unput"),more:m(function(){return this._more=!0,this},"more"),reject:m(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:m(function(i){this.unput(this.match.slice(i))},"less"),pastInput:m(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:m(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:m(function(){var i=this.pastInput(),r=new Array(i.length+1).join("-");return i+this.upcomingInput()+` +`+r+"^"},"showPosition"),test_match:m(function(i,r){var l,s,d;if(this.options.backtrack_lexer&&(d={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(d.yylloc.range=this.yylloc.range.slice(0))),s=i[0].match(/(?:\r\n?|\n).*/g),s&&(this.yylineno+=s.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:s?s[s.length-1].length-s[s.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+i[0].length},this.yytext+=i[0],this.match+=i[0],this.matches=i,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(i[0].length),this.matched+=i[0],l=this.performAction.call(this,this.yy,this,r,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),l)return l;if(this._backtrack){for(var t in d)this[t]=d[t];return!1}return!1},"test_match"),next:m(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var i,r,l,s;this._more||(this.yytext="",this.match="");for(var d=this._currentRules(),t=0;tr[0].length)){if(r=l,s=t,this.options.backtrack_lexer){if(i=this.test_match(l,d[t]),i!==!1)return i;if(this._backtrack){r=!1;continue}else return!1}else if(!this.options.flex)break}return r?(i=this.test_match(r,d[s]),i!==!1?i:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:m(function(){var r=this.next();return r||this.lex()},"lex"),begin:m(function(r){this.conditionStack.push(r)},"begin"),popState:m(function(){var r=this.conditionStack.length-1;return r>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:m(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:m(function(r){return r=this.conditionStack.length-1-Math.abs(r||0),r>=0?this.conditionStack[r]:"INITIAL"},"topState"),pushState:m(function(r){this.begin(r)},"pushState"),stateStackSize:m(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:m(function(r,l,s,d){switch(s){case 0:return"title";case 1:return this.begin("acc_title"),9;case 2:return this.popState(),"acc_title_value";case 3:return this.begin("acc_descr"),11;case 4:return this.popState(),"acc_descr_value";case 5:this.begin("acc_descr_multiline");break;case 6:this.popState();break;case 7:return"acc_descr_multiline_value";case 8:return 21;case 9:return 22;case 10:return 23;case 11:return 24;case 12:return 5;case 13:break;case 14:break;case 15:break;case 16:return 8;case 17:return 6;case 18:return 27;case 19:return 40;case 20:return 29;case 21:return 32;case 22:return 31;case 23:return 34;case 24:return 36;case 25:return 38;case 26:return 41;case 27:return 42;case 28:return 43;case 29:return 44;case 30:return 45;case 31:return 46;case 32:return 47;case 33:return 48;case 34:return 49;case 35:return 50;case 36:return 51;case 37:return 52;case 38:return 53;case 39:return 54;case 40:return 65;case 41:return 66;case 42:return 67;case 43:return 68;case 44:return 69;case 45:return 70;case 46:return 71;case 47:return 57;case 48:return 59;case 49:return this.begin("style"),77;case 50:return 75;case 51:return 81;case 52:return 88;case 53:return"PERCENT";case 54:return 86;case 55:return 84;case 56:break;case 57:this.begin("string");break;case 58:this.popState();break;case 59:return this.begin("style"),72;case 60:return this.begin("style"),74;case 61:return 61;case 62:return 64;case 63:return 63;case 64:this.begin("string");break;case 65:this.popState();break;case 66:return"qString";case 67:return l.yytext=l.yytext.trim(),89;case 68:return 75;case 69:return 80;case 70:return 76}},"anonymous"),rules:[/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:$)/i,/^(?:requirementDiagram\b)/i,/^(?:\{)/i,/^(?:\})/i,/^(?::{3})/i,/^(?::)/i,/^(?:id\b)/i,/^(?:text\b)/i,/^(?:risk\b)/i,/^(?:verifyMethod\b)/i,/^(?:requirement\b)/i,/^(?:functionalRequirement\b)/i,/^(?:interfaceRequirement\b)/i,/^(?:performanceRequirement\b)/i,/^(?:physicalRequirement\b)/i,/^(?:designConstraint\b)/i,/^(?:low\b)/i,/^(?:medium\b)/i,/^(?:high\b)/i,/^(?:analysis\b)/i,/^(?:demonstration\b)/i,/^(?:inspection\b)/i,/^(?:test\b)/i,/^(?:element\b)/i,/^(?:contains\b)/i,/^(?:copies\b)/i,/^(?:derives\b)/i,/^(?:satisfies\b)/i,/^(?:verifies\b)/i,/^(?:refines\b)/i,/^(?:traces\b)/i,/^(?:type\b)/i,/^(?:docref\b)/i,/^(?:style\b)/i,/^(?:\w+)/i,/^(?::)/i,/^(?:;)/i,/^(?:%)/i,/^(?:-)/i,/^(?:#)/i,/^(?: )/i,/^(?:["])/i,/^(?:\n)/i,/^(?:classDef\b)/i,/^(?:class\b)/i,/^(?:<-)/i,/^(?:->)/i,/^(?:-)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[\w][^:,\r\n\{\<\>\-\=]*)/i,/^(?:\w+)/i,/^(?:[0-9]+)/i,/^(?:,)/i],conditions:{acc_descr_multiline:{rules:[6,7,68,69,70],inclusive:!1},acc_descr:{rules:[4,68,69,70],inclusive:!1},acc_title:{rules:[2,68,69,70],inclusive:!1},style:{rules:[50,51,52,53,54,55,56,57,58,68,69,70],inclusive:!1},unqString:{rules:[68,69,70],inclusive:!1},token:{rules:[68,69,70],inclusive:!1},string:{rules:[65,66,68,69,70],inclusive:!1},INITIAL:{rules:[0,1,3,5,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,59,60,61,62,63,64,67,68,69,70],inclusive:!0}}};return P}();Se.lexer=Qe;function Re(){this.yy={}}return m(Re,"Parser"),Re.prototype=Se,Se.Parser=Re,new Re}();Ae.parser=Ae;var ct=Ae,Z,ot=(Z=class{constructor(){this.relations=[],this.latestRequirement=this.getInitialRequirement(),this.requirements=new Map,this.latestElement=this.getInitialElement(),this.elements=new Map,this.classes=new Map,this.direction="TB",this.RequirementType={REQUIREMENT:"Requirement",FUNCTIONAL_REQUIREMENT:"Functional Requirement",INTERFACE_REQUIREMENT:"Interface Requirement",PERFORMANCE_REQUIREMENT:"Performance Requirement",PHYSICAL_REQUIREMENT:"Physical Requirement",DESIGN_CONSTRAINT:"Design Constraint"},this.RiskLevel={LOW_RISK:"Low",MED_RISK:"Medium",HIGH_RISK:"High"},this.VerifyType={VERIFY_ANALYSIS:"Analysis",VERIFY_DEMONSTRATION:"Demonstration",VERIFY_INSPECTION:"Inspection",VERIFY_TEST:"Test"},this.Relationships={CONTAINS:"contains",COPIES:"copies",DERIVES:"derives",SATISFIES:"satisfies",VERIFIES:"verifies",REFINES:"refines",TRACES:"traces"},this.setAccTitle=Xe,this.getAccTitle=Je,this.setAccDescription=Ze,this.getAccDescription=et,this.setDiagramTitle=tt,this.getDiagramTitle=st,this.getConfig=m(()=>Ne().requirement,"getConfig"),this.clear(),this.setDirection=this.setDirection.bind(this),this.addRequirement=this.addRequirement.bind(this),this.setNewReqId=this.setNewReqId.bind(this),this.setNewReqRisk=this.setNewReqRisk.bind(this),this.setNewReqText=this.setNewReqText.bind(this),this.setNewReqVerifyMethod=this.setNewReqVerifyMethod.bind(this),this.addElement=this.addElement.bind(this),this.setNewElementType=this.setNewElementType.bind(this),this.setNewElementDocRef=this.setNewElementDocRef.bind(this),this.addRelationship=this.addRelationship.bind(this),this.setCssStyle=this.setCssStyle.bind(this),this.setClass=this.setClass.bind(this),this.defineClass=this.defineClass.bind(this),this.setAccTitle=this.setAccTitle.bind(this),this.setAccDescription=this.setAccDescription.bind(this)}getDirection(){return this.direction}setDirection(a){this.direction=a}resetLatestRequirement(){this.latestRequirement=this.getInitialRequirement()}resetLatestElement(){this.latestElement=this.getInitialElement()}getInitialRequirement(){return{requirementId:"",text:"",risk:"",verifyMethod:"",name:"",type:"",cssStyles:[],classes:["default"]}}getInitialElement(){return{name:"",type:"",docRef:"",cssStyles:[],classes:["default"]}}addRequirement(a,u){return this.requirements.has(a)||this.requirements.set(a,{name:a,type:u,requirementId:this.latestRequirement.requirementId,text:this.latestRequirement.text,risk:this.latestRequirement.risk,verifyMethod:this.latestRequirement.verifyMethod,cssStyles:[],classes:["default"]}),this.resetLatestRequirement(),this.requirements.get(a)}getRequirements(){return this.requirements}setNewReqId(a){this.latestRequirement!==void 0&&(this.latestRequirement.requirementId=a)}setNewReqText(a){this.latestRequirement!==void 0&&(this.latestRequirement.text=a)}setNewReqRisk(a){this.latestRequirement!==void 0&&(this.latestRequirement.risk=a)}setNewReqVerifyMethod(a){this.latestRequirement!==void 0&&(this.latestRequirement.verifyMethod=a)}addElement(a){return this.elements.has(a)||(this.elements.set(a,{name:a,type:this.latestElement.type,docRef:this.latestElement.docRef,cssStyles:[],classes:["default"]}),qe.info("Added new element: ",a)),this.resetLatestElement(),this.elements.get(a)}getElements(){return this.elements}setNewElementType(a){this.latestElement!==void 0&&(this.latestElement.type=a)}setNewElementDocRef(a){this.latestElement!==void 0&&(this.latestElement.docRef=a)}addRelationship(a,u,o){this.relations.push({type:a,src:u,dst:o})}getRelationships(){return this.relations}clear(){this.relations=[],this.resetLatestRequirement(),this.requirements=new Map,this.resetLatestElement(),this.elements=new Map,this.classes=new Map,it()}setCssStyle(a,u){for(const o of a){const f=this.requirements.get(o)??this.elements.get(o);if(!u||!f)return;for(const c of u)c.includes(",")?f.cssStyles.push(...c.split(",")):f.cssStyles.push(c)}}setClass(a,u){var o;for(const f of a){const c=this.requirements.get(f)??this.elements.get(f);if(c)for(const p of u){c.classes.push(p);const R=(o=this.classes.get(p))==null?void 0:o.styles;R&&c.cssStyles.push(...R)}}}defineClass(a,u){for(const o of a){let f=this.classes.get(o);f===void 0&&(f={id:o,styles:[],textStyles:[]},this.classes.set(o,f)),u&&u.forEach(function(c){if(/color/.exec(c)){const p=c.replace("fill","bgFill");f.textStyles.push(p)}f.styles.push(c)}),this.requirements.forEach(c=>{c.classes.includes(o)&&c.cssStyles.push(...u.flatMap(p=>p.split(",")))}),this.elements.forEach(c=>{c.classes.includes(o)&&c.cssStyles.push(...u.flatMap(p=>p.split(",")))})}}getClasses(){return this.classes}getData(){var f,c,p,R;const a=Ne(),u=[],o=[];for(const h of this.requirements.values()){const E=h;E.id=h.name,E.cssStyles=h.cssStyles,E.cssClasses=h.classes.join(" "),E.shape="requirementBox",E.look=a.look,u.push(E)}for(const h of this.elements.values()){const E=h;E.shape="requirementBox",E.look=a.look,E.id=h.name,E.cssStyles=h.cssStyles,E.cssClasses=h.classes.join(" "),u.push(E)}for(const h of this.relations){let E=0;const I=h.type===this.Relationships.CONTAINS,k={id:`${h.src}-${h.dst}-${E}`,start:((f=this.requirements.get(h.src))==null?void 0:f.name)??((c=this.elements.get(h.src))==null?void 0:c.name),end:((p=this.requirements.get(h.dst))==null?void 0:p.name)??((R=this.elements.get(h.dst))==null?void 0:R.name),label:`<<${h.type}>>`,classes:"relationshipLine",style:["fill:none",I?"":"stroke-dasharray: 10,7"],labelpos:"c",thickness:"normal",type:"normal",pattern:I?"normal":"dashed",arrowTypeStart:I?"requirement_contains":"",arrowTypeEnd:I?"":"requirement_arrow",look:a.look};o.push(k),E++}return{nodes:u,edges:o,other:{},config:a,direction:this.getDirection()}}},m(Z,"RequirementDB"),Z),ht=m(e=>` + + marker { + fill: ${e.relationColor}; + stroke: ${e.relationColor}; + } + + marker.cross { + stroke: ${e.lineColor}; + } + + svg { + font-family: ${e.fontFamily}; + font-size: ${e.fontSize}; + } + + .reqBox { + fill: ${e.requirementBackground}; + fill-opacity: 1.0; + stroke: ${e.requirementBorderColor}; + stroke-width: ${e.requirementBorderSize}; + } + + .reqTitle, .reqLabel{ + fill: ${e.requirementTextColor}; + } + .reqLabelBox { + fill: ${e.relationLabelBackground}; + fill-opacity: 1.0; + } + + .req-title-line { + stroke: ${e.requirementBorderColor}; + stroke-width: ${e.requirementBorderSize}; + } + .relationshipLine { + stroke: ${e.relationColor}; + stroke-width: 1; + } + .relationshipLabel { + fill: ${e.relationLabelColor}; + } + .divider { + stroke: ${e.nodeBorder}; + stroke-width: 1; + } + .label { + font-family: ${e.fontFamily}; + color: ${e.nodeTextColor||e.textColor}; + } + .label text,span { + fill: ${e.nodeTextColor||e.textColor}; + color: ${e.nodeTextColor||e.textColor}; + } + .labelBkg { + background-color: ${e.edgeLabelBackground}; + } + +`,"getStyles"),ut=ht,Be={};rt(Be,{draw:()=>mt});var mt=m(async function(e,a,u,o){qe.info("REF0:"),qe.info("Drawing requirement diagram (unified)",a);const{securityLevel:f,state:c,layout:p}=Ne(),R=o.db.getData(),h=ze(a,f);R.type=o.type,R.layoutAlgorithm=nt(p),R.nodeSpacing=(c==null?void 0:c.nodeSpacing)??50,R.rankSpacing=(c==null?void 0:c.rankSpacing)??50,R.markers=["requirement_contains","requirement_arrow"],R.diagramId=a,await at(R,h);const E=8;lt.insertTitle(h,"requirementDiagramTitleText",(c==null?void 0:c.titleTopMargin)??25,o.db.getDiagramTitle()),Ge(h,E,"requirementDiagram",(c==null?void 0:c.useMaxWidth)??!0)},"draw"),Rt={parser:ct,get db(){return new ot},renderer:Be,styles:ut};export{Rt as diagram}; diff --git a/assets/chunks/sankeyDiagram-TZEHDZUN.qimGZH9q.js b/assets/chunks/sankeyDiagram-TZEHDZUN.qimGZH9q.js new file mode 100644 index 000000000..8ba5a90b1 --- /dev/null +++ b/assets/chunks/sankeyDiagram-TZEHDZUN.qimGZH9q.js @@ -0,0 +1,10 @@ +import{_ as p,q as _t,t as xt,s as vt,g as bt,b as St,a as wt,c as lt,A as Lt,d as H,O as Et,b0 as At,a3 as Tt,z as Mt,k as Nt}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";function ct(t,n){let s;if(n===void 0)for(const a of t)a!=null&&(s=a)&&(s=a);else{let a=-1;for(let h of t)(h=n(h,++a,t))!=null&&(s=h)&&(s=h)}return s}function pt(t,n){let s;if(n===void 0)for(const a of t)a!=null&&(s>a||s===void 0&&a>=a)&&(s=a);else{let a=-1;for(let h of t)(h=n(h,++a,t))!=null&&(s>h||s===void 0&&h>=h)&&(s=h)}return s}function nt(t,n){let s=0;if(n===void 0)for(let a of t)(a=+a)&&(s+=a);else{let a=-1;for(let h of t)(h=+n(h,++a,t))&&(s+=h)}return s}function It(t){return t.target.depth}function Pt(t){return t.depth}function Ct(t,n){return n-1-t.height}function mt(t,n){return t.sourceLinks.length?t.depth:n-1}function Ot(t){return t.targetLinks.length?t.depth:t.sourceLinks.length?pt(t.sourceLinks,It)-1:0}function X(t){return function(){return t}}function ut(t,n){return Q(t.source,n.source)||t.index-n.index}function ht(t,n){return Q(t.target,n.target)||t.index-n.index}function Q(t,n){return t.y0-n.y0}function it(t){return t.value}function zt(t){return t.index}function Dt(t){return t.nodes}function $t(t){return t.links}function ft(t,n){const s=t.get(n);if(!s)throw new Error("missing: "+n);return s}function yt({nodes:t}){for(const n of t){let s=n.y0,a=s;for(const h of n.sourceLinks)h.y0=s+h.width/2,s+=h.width;for(const h of n.targetLinks)h.y1=a+h.width/2,a+=h.width}}function jt(){let t=0,n=0,s=1,a=1,h=24,d=8,m,_=zt,i=mt,o,l,x=Dt,v=$t,y=6;function b(){const e={nodes:x.apply(null,arguments),links:v.apply(null,arguments)};return M(e),T(e),N(e),C(e),w(e),yt(e),e}b.update=function(e){return yt(e),e},b.nodeId=function(e){return arguments.length?(_=typeof e=="function"?e:X(e),b):_},b.nodeAlign=function(e){return arguments.length?(i=typeof e=="function"?e:X(e),b):i},b.nodeSort=function(e){return arguments.length?(o=e,b):o},b.nodeWidth=function(e){return arguments.length?(h=+e,b):h},b.nodePadding=function(e){return arguments.length?(d=m=+e,b):d},b.nodes=function(e){return arguments.length?(x=typeof e=="function"?e:X(e),b):x},b.links=function(e){return arguments.length?(v=typeof e=="function"?e:X(e),b):v},b.linkSort=function(e){return arguments.length?(l=e,b):l},b.size=function(e){return arguments.length?(t=n=0,s=+e[0],a=+e[1],b):[s-t,a-n]},b.extent=function(e){return arguments.length?(t=+e[0][0],s=+e[1][0],n=+e[0][1],a=+e[1][1],b):[[t,n],[s,a]]},b.iterations=function(e){return arguments.length?(y=+e,b):y};function M({nodes:e,links:f}){for(const[c,r]of e.entries())r.index=c,r.sourceLinks=[],r.targetLinks=[];const u=new Map(e.map((c,r)=>[_(c,r,e),c]));for(const[c,r]of f.entries()){r.index=c;let{source:k,target:S}=r;typeof k!="object"&&(k=r.source=ft(u,k)),typeof S!="object"&&(S=r.target=ft(u,S)),k.sourceLinks.push(r),S.targetLinks.push(r)}if(l!=null)for(const{sourceLinks:c,targetLinks:r}of e)c.sort(l),r.sort(l)}function T({nodes:e}){for(const f of e)f.value=f.fixedValue===void 0?Math.max(nt(f.sourceLinks,it),nt(f.targetLinks,it)):f.fixedValue}function N({nodes:e}){const f=e.length;let u=new Set(e),c=new Set,r=0;for(;u.size;){for(const k of u){k.depth=r;for(const{target:S}of k.sourceLinks)c.add(S)}if(++r>f)throw new Error("circular link");u=c,c=new Set}}function C({nodes:e}){const f=e.length;let u=new Set(e),c=new Set,r=0;for(;u.size;){for(const k of u){k.height=r;for(const{source:S}of k.targetLinks)c.add(S)}if(++r>f)throw new Error("circular link");u=c,c=new Set}}function D({nodes:e}){const f=ct(e,r=>r.depth)+1,u=(s-t-h)/(f-1),c=new Array(f);for(const r of e){const k=Math.max(0,Math.min(f-1,Math.floor(i.call(null,r,f))));r.layer=k,r.x0=t+k*u,r.x1=r.x0+h,c[k]?c[k].push(r):c[k]=[r]}if(o)for(const r of c)r.sort(o);return c}function R(e){const f=pt(e,u=>(a-n-(u.length-1)*m)/nt(u,it));for(const u of e){let c=n;for(const r of u){r.y0=c,r.y1=c+r.value*f,c=r.y1+m;for(const k of r.sourceLinks)k.width=k.value*f}c=(a-c+m)/(u.length+1);for(let r=0;ru.length)-1)),R(f);for(let u=0;u0))continue;let G=(L/F-S.y0)*f;S.y0+=G,S.y1+=G,E(S)}o===void 0&&k.sort(Q),O(k,u)}}function B(e,f,u){for(let c=e.length,r=c-2;r>=0;--r){const k=e[r];for(const S of k){let L=0,F=0;for(const{target:Y,value:et}of S.sourceLinks){let q=et*(Y.layer-S.layer);L+=I(S,Y)*q,F+=q}if(!(F>0))continue;let G=(L/F-S.y0)*f;S.y0+=G,S.y1+=G,E(S)}o===void 0&&k.sort(Q),O(k,u)}}function O(e,f){const u=e.length>>1,c=e[u];g(e,c.y0-m,u-1,f),z(e,c.y1+m,u+1,f),g(e,a,e.length-1,f),z(e,n,0,f)}function z(e,f,u,c){for(;u1e-6&&(r.y0+=k,r.y1+=k),f=r.y1+m}}function g(e,f,u,c){for(;u>=0;--u){const r=e[u],k=(r.y1-f)*c;k>1e-6&&(r.y0-=k,r.y1-=k),f=r.y0-m}}function E({sourceLinks:e,targetLinks:f}){if(l===void 0){for(const{source:{sourceLinks:u}}of f)u.sort(ht);for(const{target:{targetLinks:u}}of e)u.sort(ut)}}function A(e){if(l===void 0)for(const{sourceLinks:f,targetLinks:u}of e)f.sort(ht),u.sort(ut)}function $(e,f){let u=e.y0-(e.sourceLinks.length-1)*m/2;for(const{target:c,width:r}of e.sourceLinks){if(c===f)break;u+=r+m}for(const{source:c,width:r}of f.targetLinks){if(c===e)break;u-=r}return u}function I(e,f){let u=f.y0-(f.targetLinks.length-1)*m/2;for(const{source:c,width:r}of f.targetLinks){if(c===e)break;u+=r+m}for(const{target:c,width:r}of e.sourceLinks){if(c===f)break;u-=r}return u}return b}var st=Math.PI,rt=2*st,V=1e-6,Bt=rt-V;function ot(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function kt(){return new ot}ot.prototype=kt.prototype={constructor:ot,moveTo:function(t,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)},closePath:function(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(t,n){this._+="L"+(this._x1=+t)+","+(this._y1=+n)},quadraticCurveTo:function(t,n,s,a){this._+="Q"+ +t+","+ +n+","+(this._x1=+s)+","+(this._y1=+a)},bezierCurveTo:function(t,n,s,a,h,d){this._+="C"+ +t+","+ +n+","+ +s+","+ +a+","+(this._x1=+h)+","+(this._y1=+d)},arcTo:function(t,n,s,a,h){t=+t,n=+n,s=+s,a=+a,h=+h;var d=this._x1,m=this._y1,_=s-t,i=a-n,o=d-t,l=m-n,x=o*o+l*l;if(h<0)throw new Error("negative radius: "+h);if(this._x1===null)this._+="M"+(this._x1=t)+","+(this._y1=n);else if(x>V)if(!(Math.abs(l*_-i*o)>V)||!h)this._+="L"+(this._x1=t)+","+(this._y1=n);else{var v=s-d,y=a-m,b=_*_+i*i,M=v*v+y*y,T=Math.sqrt(b),N=Math.sqrt(x),C=h*Math.tan((st-Math.acos((b+x-M)/(2*T*N)))/2),D=C/N,R=C/T;Math.abs(D-1)>V&&(this._+="L"+(t+D*o)+","+(n+D*l)),this._+="A"+h+","+h+",0,0,"+ +(l*v>o*y)+","+(this._x1=t+R*_)+","+(this._y1=n+R*i)}},arc:function(t,n,s,a,h,d){t=+t,n=+n,s=+s,d=!!d;var m=s*Math.cos(a),_=s*Math.sin(a),i=t+m,o=n+_,l=1^d,x=d?a-h:h-a;if(s<0)throw new Error("negative radius: "+s);this._x1===null?this._+="M"+i+","+o:(Math.abs(this._x1-i)>V||Math.abs(this._y1-o)>V)&&(this._+="L"+i+","+o),s&&(x<0&&(x=x%rt+rt),x>Bt?this._+="A"+s+","+s+",0,1,"+l+","+(t-m)+","+(n-_)+"A"+s+","+s+",0,1,"+l+","+(this._x1=i)+","+(this._y1=o):x>V&&(this._+="A"+s+","+s+",0,"+ +(x>=st)+","+l+","+(this._x1=t+s*Math.cos(h))+","+(this._y1=n+s*Math.sin(h))))},rect:function(t,n,s,a){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +s+"v"+ +a+"h"+-s+"Z"},toString:function(){return this._}};function dt(t){return function(){return t}}function Rt(t){return t[0]}function Ft(t){return t[1]}var Vt=Array.prototype.slice;function Wt(t){return t.source}function Ut(t){return t.target}function Gt(t){var n=Wt,s=Ut,a=Rt,h=Ft,d=null;function m(){var _,i=Vt.call(arguments),o=n.apply(this,i),l=s.apply(this,i);if(d||(d=_=kt()),t(d,+a.apply(this,(i[0]=o,i)),+h.apply(this,i),+a.apply(this,(i[0]=l,i)),+h.apply(this,i)),_)return d=null,_+""||null}return m.source=function(_){return arguments.length?(n=_,m):n},m.target=function(_){return arguments.length?(s=_,m):s},m.x=function(_){return arguments.length?(a=typeof _=="function"?_:dt(+_),m):a},m.y=function(_){return arguments.length?(h=typeof _=="function"?_:dt(+_),m):h},m.context=function(_){return arguments.length?(d=_??null,m):d},m}function Yt(t,n,s,a,h){t.moveTo(n,s),t.bezierCurveTo(n=(n+a)/2,s,n,h,a,h)}function qt(){return Gt(Yt)}function Ht(t){return[t.source.x1,t.y0]}function Xt(t){return[t.target.x0,t.y1]}function Qt(){return qt().source(Ht).target(Xt)}var at=function(){var t=p(function(_,i,o,l){for(o=o||{},l=_.length;l--;o[_[l]]=i);return o},"o"),n=[1,9],s=[1,10],a=[1,5,10,12],h={trace:p(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SANKEY:4,NEWLINE:5,csv:6,opt_eof:7,record:8,csv_tail:9,EOF:10,"field[source]":11,COMMA:12,"field[target]":13,"field[value]":14,field:15,escaped:16,non_escaped:17,DQUOTE:18,ESCAPED_TEXT:19,NON_ESCAPED_TEXT:20,$accept:0,$end:1},terminals_:{2:"error",4:"SANKEY",5:"NEWLINE",10:"EOF",11:"field[source]",12:"COMMA",13:"field[target]",14:"field[value]",18:"DQUOTE",19:"ESCAPED_TEXT",20:"NON_ESCAPED_TEXT"},productions_:[0,[3,4],[6,2],[9,2],[9,0],[7,1],[7,0],[8,5],[15,1],[15,1],[16,3],[17,1]],performAction:p(function(i,o,l,x,v,y,b){var M=y.length-1;switch(v){case 7:const T=x.findOrCreateNode(y[M-4].trim().replaceAll('""','"')),N=x.findOrCreateNode(y[M-2].trim().replaceAll('""','"')),C=parseFloat(y[M].trim());x.addLink(T,N,C);break;case 8:case 9:case 11:this.$=y[M];break;case 10:this.$=y[M-1];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3]},{6:4,8:5,15:6,16:7,17:8,18:n,20:s},{1:[2,6],7:11,10:[1,12]},t(s,[2,4],{9:13,5:[1,14]}),{12:[1,15]},t(a,[2,8]),t(a,[2,9]),{19:[1,16]},t(a,[2,11]),{1:[2,1]},{1:[2,5]},t(s,[2,2]),{6:17,8:5,15:6,16:7,17:8,18:n,20:s},{15:18,16:7,17:8,18:n,20:s},{18:[1,19]},t(s,[2,3]),{12:[1,20]},t(a,[2,10]),{15:21,16:7,17:8,18:n,20:s},t([1,5,10],[2,7])],defaultActions:{11:[2,1],12:[2,5]},parseError:p(function(i,o){if(o.recoverable)this.trace(i);else{var l=new Error(i);throw l.hash=o,l}},"parseError"),parse:p(function(i){var o=this,l=[0],x=[],v=[null],y=[],b=this.table,M="",T=0,N=0,C=2,D=1,R=y.slice.call(arguments,1),w=Object.create(this.lexer),P={yy:{}};for(var B in this.yy)Object.prototype.hasOwnProperty.call(this.yy,B)&&(P.yy[B]=this.yy[B]);w.setInput(i,P.yy),P.yy.lexer=w,P.yy.parser=this,typeof w.yylloc>"u"&&(w.yylloc={});var O=w.yylloc;y.push(O);var z=w.options&&w.options.ranges;typeof P.yy.parseError=="function"?this.parseError=P.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function g(L){l.length=l.length-2*L,v.length=v.length-L,y.length=y.length-L}p(g,"popStack");function E(){var L;return L=x.pop()||w.lex()||D,typeof L!="number"&&(L instanceof Array&&(x=L,L=x.pop()),L=o.symbols_[L]||L),L}p(E,"lex");for(var A,$,I,e,f={},u,c,r,k;;){if($=l[l.length-1],this.defaultActions[$]?I=this.defaultActions[$]:((A===null||typeof A>"u")&&(A=E()),I=b[$]&&b[$][A]),typeof I>"u"||!I.length||!I[0]){var S="";k=[];for(u in b[$])this.terminals_[u]&&u>C&&k.push("'"+this.terminals_[u]+"'");w.showPosition?S="Parse error on line "+(T+1)+`: +`+w.showPosition()+` +Expecting `+k.join(", ")+", got '"+(this.terminals_[A]||A)+"'":S="Parse error on line "+(T+1)+": Unexpected "+(A==D?"end of input":"'"+(this.terminals_[A]||A)+"'"),this.parseError(S,{text:w.match,token:this.terminals_[A]||A,line:w.yylineno,loc:O,expected:k})}if(I[0]instanceof Array&&I.length>1)throw new Error("Parse Error: multiple actions possible at state: "+$+", token: "+A);switch(I[0]){case 1:l.push(A),v.push(w.yytext),y.push(w.yylloc),l.push(I[1]),A=null,N=w.yyleng,M=w.yytext,T=w.yylineno,O=w.yylloc;break;case 2:if(c=this.productions_[I[1]][1],f.$=v[v.length-c],f._$={first_line:y[y.length-(c||1)].first_line,last_line:y[y.length-1].last_line,first_column:y[y.length-(c||1)].first_column,last_column:y[y.length-1].last_column},z&&(f._$.range=[y[y.length-(c||1)].range[0],y[y.length-1].range[1]]),e=this.performAction.apply(f,[M,N,T,P.yy,I[1],v,y].concat(R)),typeof e<"u")return e;c&&(l=l.slice(0,-1*c*2),v=v.slice(0,-1*c),y=y.slice(0,-1*c)),l.push(this.productions_[I[1]][0]),v.push(f.$),y.push(f._$),r=b[l[l.length-2]][l[l.length-1]],l.push(r);break;case 3:return!0}}return!0},"parse")},d=function(){var _={EOF:1,parseError:p(function(o,l){if(this.yy.parser)this.yy.parser.parseError(o,l);else throw new Error(o)},"parseError"),setInput:p(function(i,o){return this.yy=o||this.yy||{},this._input=i,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:p(function(){var i=this._input[0];this.yytext+=i,this.yyleng++,this.offset++,this.match+=i,this.matched+=i;var o=i.match(/(?:\r\n?|\n).*/g);return o?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),i},"input"),unput:p(function(i){var o=i.length,l=i.split(/(?:\r\n?|\n)/g);this._input=i+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-o),this.offset-=o;var x=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),l.length-1&&(this.yylineno-=l.length-1);var v=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:l?(l.length===x.length?this.yylloc.first_column:0)+x[x.length-l.length].length-l[0].length:this.yylloc.first_column-o},this.options.ranges&&(this.yylloc.range=[v[0],v[0]+this.yyleng-o]),this.yyleng=this.yytext.length,this},"unput"),more:p(function(){return this._more=!0,this},"more"),reject:p(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:p(function(i){this.unput(this.match.slice(i))},"less"),pastInput:p(function(){var i=this.matched.substr(0,this.matched.length-this.match.length);return(i.length>20?"...":"")+i.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:p(function(){var i=this.match;return i.length<20&&(i+=this._input.substr(0,20-i.length)),(i.substr(0,20)+(i.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:p(function(){var i=this.pastInput(),o=new Array(i.length+1).join("-");return i+this.upcomingInput()+` +`+o+"^"},"showPosition"),test_match:p(function(i,o){var l,x,v;if(this.options.backtrack_lexer&&(v={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(v.yylloc.range=this.yylloc.range.slice(0))),x=i[0].match(/(?:\r\n?|\n).*/g),x&&(this.yylineno+=x.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:x?x[x.length-1].length-x[x.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+i[0].length},this.yytext+=i[0],this.match+=i[0],this.matches=i,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(i[0].length),this.matched+=i[0],l=this.performAction.call(this,this.yy,this,o,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),l)return l;if(this._backtrack){for(var y in v)this[y]=v[y];return!1}return!1},"test_match"),next:p(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var i,o,l,x;this._more||(this.yytext="",this.match="");for(var v=this._currentRules(),y=0;yo[0].length)){if(o=l,x=y,this.options.backtrack_lexer){if(i=this.test_match(l,v[y]),i!==!1)return i;if(this._backtrack){o=!1;continue}else return!1}else if(!this.options.flex)break}return o?(i=this.test_match(o,v[x]),i!==!1?i:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:p(function(){var o=this.next();return o||this.lex()},"lex"),begin:p(function(o){this.conditionStack.push(o)},"begin"),popState:p(function(){var o=this.conditionStack.length-1;return o>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:p(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:p(function(o){return o=this.conditionStack.length-1-Math.abs(o||0),o>=0?this.conditionStack[o]:"INITIAL"},"topState"),pushState:p(function(o){this.begin(o)},"pushState"),stateStackSize:p(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:p(function(o,l,x,v){switch(x){case 0:return this.pushState("csv"),4;case 1:return this.pushState("csv"),4;case 2:return 10;case 3:return 5;case 4:return 12;case 5:return this.pushState("escaped_text"),18;case 6:return 20;case 7:return this.popState("escaped_text"),18;case 8:return 19}},"anonymous"),rules:[/^(?:sankey-beta\b)/i,/^(?:sankey\b)/i,/^(?:$)/i,/^(?:((\u000D\u000A)|(\u000A)))/i,/^(?:(\u002C))/i,/^(?:(\u0022))/i,/^(?:([\u0020-\u0021\u0023-\u002B\u002D-\u007E])*)/i,/^(?:(\u0022)(?!(\u0022)))/i,/^(?:(([\u0020-\u0021\u0023-\u002B\u002D-\u007E])|(\u002C)|(\u000D)|(\u000A)|(\u0022)(\u0022))*)/i],conditions:{csv:{rules:[2,3,4,5,6,7,8],inclusive:!1},escaped_text:{rules:[7,8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8],inclusive:!0}}};return _}();h.lexer=d;function m(){this.yy={}}return p(m,"Parser"),m.prototype=h,h.Parser=m,new m}();at.parser=at;var K=at,J=[],tt=[],Z=new Map,Kt=p(()=>{J=[],tt=[],Z=new Map,Mt()},"clear"),W,Zt=(W=class{constructor(n,s,a=0){this.source=n,this.target=s,this.value=a}},p(W,"SankeyLink"),W),Jt=p((t,n,s)=>{J.push(new Zt(t,n,s))},"addLink"),U,te=(U=class{constructor(n){this.ID=n}},p(U,"SankeyNode"),U),ee=p(t=>{t=Nt.sanitizeText(t,lt());let n=Z.get(t);return n===void 0&&(n=new te(t),Z.set(t,n),tt.push(n)),n},"findOrCreateNode"),ne=p(()=>tt,"getNodes"),ie=p(()=>J,"getLinks"),se=p(()=>({nodes:tt.map(t=>({id:t.ID})),links:J.map(t=>({source:t.source.ID,target:t.target.ID,value:t.value}))}),"getGraph"),re={nodesMap:Z,getConfig:p(()=>lt().sankey,"getConfig"),getNodes:ne,getLinks:ie,getGraph:se,addLink:Jt,findOrCreateNode:ee,getAccTitle:wt,setAccTitle:St,getAccDescription:bt,setAccDescription:vt,getDiagramTitle:xt,setDiagramTitle:_t,clear:Kt},j,gt=(j=class{static next(n){return new j(n+ ++j.count)}constructor(n){this.id=n,this.href=`#${n}`}toString(){return"url("+this.href+")"}},p(j,"Uid"),j.count=0,j),oe={left:Pt,right:Ct,center:Ot,justify:mt},ae=p(function(t,n,s,a){const{securityLevel:h,sankey:d}=lt(),m=Lt.sankey;let _;h==="sandbox"&&(_=H("#i"+n));const i=h==="sandbox"?H(_.nodes()[0].contentDocument.body):H("body"),o=h==="sandbox"?i.select(`[id="${n}"]`):H(`[id="${n}"]`),l=(d==null?void 0:d.width)??m.width,x=(d==null?void 0:d.height)??m.width,v=(d==null?void 0:d.useMaxWidth)??m.useMaxWidth,y=(d==null?void 0:d.nodeAlignment)??m.nodeAlignment,b=(d==null?void 0:d.prefix)??m.prefix,M=(d==null?void 0:d.suffix)??m.suffix,T=(d==null?void 0:d.showValues)??m.showValues,N=a.db.getGraph(),C=oe[y];jt().nodeId(g=>g.id).nodeWidth(10).nodePadding(10+(T?15:0)).nodeAlign(C).extent([[0,0],[l,x]])(N);const w=Et(At);o.append("g").attr("class","nodes").selectAll(".node").data(N.nodes).join("g").attr("class","node").attr("id",g=>(g.uid=gt.next("node-")).id).attr("transform",function(g){return"translate("+g.x0+","+g.y0+")"}).attr("x",g=>g.x0).attr("y",g=>g.y0).append("rect").attr("height",g=>g.y1-g.y0).attr("width",g=>g.x1-g.x0).attr("fill",g=>w(g.id));const P=p(({id:g,value:E})=>T?`${g} +${b}${Math.round(E*100)/100}${M}`:g,"getText");o.append("g").attr("class","node-labels").attr("font-size",14).selectAll("text").data(N.nodes).join("text").attr("x",g=>g.x0(g.y1+g.y0)/2).attr("dy",`${T?"0":"0.35"}em`).attr("text-anchor",g=>g.x0(E.uid=gt.next("linearGradient-")).id).attr("gradientUnits","userSpaceOnUse").attr("x1",E=>E.source.x1).attr("x2",E=>E.target.x0);g.append("stop").attr("offset","0%").attr("stop-color",E=>w(E.source.id)),g.append("stop").attr("offset","100%").attr("stop-color",E=>w(E.target.id))}let z;switch(O){case"gradient":z=p(g=>g.uid,"coloring");break;case"source":z=p(g=>w(g.source.id),"coloring");break;case"target":z=p(g=>w(g.target.id),"coloring");break;default:z=O}B.append("path").attr("d",Qt()).attr("stroke",z).attr("stroke-width",g=>Math.max(1,g.width)),Tt(void 0,o,0,v)},"draw"),le={draw:ae},ce=p(t=>t.replaceAll(/^[^\S\n\r]+|[^\S\n\r]+$/g,"").replaceAll(/([\n\r])+/g,` +`).trim(),"prepareTextForParsing"),ue=p(t=>`.label { + font-family: ${t.fontFamily}; + }`,"getStyles"),he=ue,fe=K.parse.bind(K);K.parse=t=>fe(ce(t));var ge={styles:he,parser:K,db:re,renderer:le};export{ge as diagram}; diff --git a/assets/chunks/sequenceDiagram-WL72ISMW.D-QuC8xB.js b/assets/chunks/sequenceDiagram-WL72ISMW.D-QuC8xB.js new file mode 100644 index 000000000..83540f28b --- /dev/null +++ b/assets/chunks/sequenceDiagram-WL72ISMW.D-QuC8xB.js @@ -0,0 +1,145 @@ +import{a as we,b as Xt,g as ct,d as ve,c as Jt,e as Qt}from"./chunk-TZMSLE5B.CN1RMadv.js";import{_ as f,o as Ie,c as st,d as St,l as Q,j as re,e as Le,f as _e,k as I,b as se,s as Ae,q as ke,a as Pe,g as Ne,t as Se,v as Me,J as Re,z as De,i as Mt,u as F,W as z,X as _t,M as ie,Z as Ce,Y as Oe,$ as ne,G as Ht}from"./theme.kqgpP4eL.js";import{I as Be}from"./chunk-QZHKN3VN.SQhQYWrL.js";import"./framework.CgT1UzWm.js";var Ut=function(){var e=f(function(pt,v,A,L){for(A=A||{},L=pt.length;L--;A[pt[L]]=v);return A},"o"),t=[1,2],n=[1,3],s=[1,4],r=[2,4],i=[1,9],c=[1,11],h=[1,13],o=[1,14],a=[1,16],p=[1,17],g=[1,18],x=[1,24],y=[1,25],b=[1,26],w=[1,27],k=[1,28],N=[1,29],S=[1,30],M=[1,31],C=[1,32],Y=[1,33],H=[1,34],Z=[1,35],at=[1,36],U=[1,37],G=[1,38],q=[1,39],O=[1,41],$=[1,42],K=[1,43],j=[1,44],rt=[1,45],D=[1,46],E=[1,4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,47,48,49,50,52,53,55,60,61,62,63,71],_=[2,71],X=[4,5,16,50,52,53],tt=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,50,52,53,55,60,61,62,63,71],R=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,49,50,52,53,55,60,61,62,63,71],Vt=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,48,50,52,53,55,60,61,62,63,71],Zt=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,47,50,52,53,55,60,61,62,63,71],ot=[69,70,71],lt=[1,127],Yt={trace:f(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,SD:6,document:7,line:8,statement:9,box_section:10,box_line:11,participant_statement:12,create:13,box:14,restOfLine:15,end:16,signal:17,autonumber:18,NUM:19,off:20,activate:21,actor:22,deactivate:23,note_statement:24,links_statement:25,link_statement:26,properties_statement:27,details_statement:28,title:29,legacy_title:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,loop:36,rect:37,opt:38,alt:39,else_sections:40,par:41,par_sections:42,par_over:43,critical:44,option_sections:45,break:46,option:47,and:48,else:49,participant:50,AS:51,participant_actor:52,destroy:53,actor_with_config:54,note:55,placement:56,text2:57,over:58,actor_pair:59,links:60,link:61,properties:62,details:63,spaceList:64,",":65,left_of:66,right_of:67,signaltype:68,"+":69,"-":70,ACTOR:71,config_object:72,CONFIG_START:73,CONFIG_CONTENT:74,CONFIG_END:75,SOLID_OPEN_ARROW:76,DOTTED_OPEN_ARROW:77,SOLID_ARROW:78,BIDIRECTIONAL_SOLID_ARROW:79,DOTTED_ARROW:80,BIDIRECTIONAL_DOTTED_ARROW:81,SOLID_CROSS:82,DOTTED_CROSS:83,SOLID_POINT:84,DOTTED_POINT:85,TXT:86,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",6:"SD",13:"create",14:"box",15:"restOfLine",16:"end",18:"autonumber",19:"NUM",20:"off",21:"activate",23:"deactivate",29:"title",30:"legacy_title",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",36:"loop",37:"rect",38:"opt",39:"alt",41:"par",43:"par_over",44:"critical",46:"break",47:"option",48:"and",49:"else",50:"participant",51:"AS",52:"participant_actor",53:"destroy",55:"note",58:"over",60:"links",61:"link",62:"properties",63:"details",65:",",66:"left_of",67:"right_of",69:"+",70:"-",71:"ACTOR",73:"CONFIG_START",74:"CONFIG_CONTENT",75:"CONFIG_END",76:"SOLID_OPEN_ARROW",77:"DOTTED_OPEN_ARROW",78:"SOLID_ARROW",79:"BIDIRECTIONAL_SOLID_ARROW",80:"DOTTED_ARROW",81:"BIDIRECTIONAL_DOTTED_ARROW",82:"SOLID_CROSS",83:"DOTTED_CROSS",84:"SOLID_POINT",85:"DOTTED_POINT",86:"TXT"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[10,0],[10,2],[11,2],[11,1],[11,1],[9,1],[9,2],[9,4],[9,2],[9,4],[9,3],[9,3],[9,2],[9,3],[9,3],[9,2],[9,2],[9,2],[9,2],[9,2],[9,1],[9,1],[9,2],[9,2],[9,1],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[45,1],[45,4],[42,1],[42,4],[40,1],[40,4],[12,5],[12,3],[12,5],[12,3],[12,3],[12,3],[24,4],[24,4],[25,3],[26,3],[27,3],[28,3],[64,2],[64,1],[59,3],[59,1],[56,1],[56,1],[17,5],[17,5],[17,4],[54,2],[72,3],[22,1],[68,1],[68,1],[68,1],[68,1],[68,1],[68,1],[68,1],[68,1],[68,1],[68,1],[57,1]],performAction:f(function(v,A,L,m,B,d,It){var u=d.length-1;switch(B){case 3:return m.apply(d[u]),d[u];case 4:case 9:this.$=[];break;case 5:case 10:d[u-1].push(d[u]),this.$=d[u-1];break;case 6:case 7:case 11:case 12:this.$=d[u];break;case 8:case 13:this.$=[];break;case 15:d[u].type="createParticipant",this.$=d[u];break;case 16:d[u-1].unshift({type:"boxStart",boxData:m.parseBoxData(d[u-2])}),d[u-1].push({type:"boxEnd",boxText:d[u-2]}),this.$=d[u-1];break;case 18:this.$={type:"sequenceIndex",sequenceIndex:Number(d[u-2]),sequenceIndexStep:Number(d[u-1]),sequenceVisible:!0,signalType:m.LINETYPE.AUTONUMBER};break;case 19:this.$={type:"sequenceIndex",sequenceIndex:Number(d[u-1]),sequenceIndexStep:1,sequenceVisible:!0,signalType:m.LINETYPE.AUTONUMBER};break;case 20:this.$={type:"sequenceIndex",sequenceVisible:!1,signalType:m.LINETYPE.AUTONUMBER};break;case 21:this.$={type:"sequenceIndex",sequenceVisible:!0,signalType:m.LINETYPE.AUTONUMBER};break;case 22:this.$={type:"activeStart",signalType:m.LINETYPE.ACTIVE_START,actor:d[u-1].actor};break;case 23:this.$={type:"activeEnd",signalType:m.LINETYPE.ACTIVE_END,actor:d[u-1].actor};break;case 29:m.setDiagramTitle(d[u].substring(6)),this.$=d[u].substring(6);break;case 30:m.setDiagramTitle(d[u].substring(7)),this.$=d[u].substring(7);break;case 31:this.$=d[u].trim(),m.setAccTitle(this.$);break;case 32:case 33:this.$=d[u].trim(),m.setAccDescription(this.$);break;case 34:d[u-1].unshift({type:"loopStart",loopText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.LOOP_START}),d[u-1].push({type:"loopEnd",loopText:d[u-2],signalType:m.LINETYPE.LOOP_END}),this.$=d[u-1];break;case 35:d[u-1].unshift({type:"rectStart",color:m.parseMessage(d[u-2]),signalType:m.LINETYPE.RECT_START}),d[u-1].push({type:"rectEnd",color:m.parseMessage(d[u-2]),signalType:m.LINETYPE.RECT_END}),this.$=d[u-1];break;case 36:d[u-1].unshift({type:"optStart",optText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.OPT_START}),d[u-1].push({type:"optEnd",optText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.OPT_END}),this.$=d[u-1];break;case 37:d[u-1].unshift({type:"altStart",altText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.ALT_START}),d[u-1].push({type:"altEnd",signalType:m.LINETYPE.ALT_END}),this.$=d[u-1];break;case 38:d[u-1].unshift({type:"parStart",parText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.PAR_START}),d[u-1].push({type:"parEnd",signalType:m.LINETYPE.PAR_END}),this.$=d[u-1];break;case 39:d[u-1].unshift({type:"parStart",parText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.PAR_OVER_START}),d[u-1].push({type:"parEnd",signalType:m.LINETYPE.PAR_END}),this.$=d[u-1];break;case 40:d[u-1].unshift({type:"criticalStart",criticalText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.CRITICAL_START}),d[u-1].push({type:"criticalEnd",signalType:m.LINETYPE.CRITICAL_END}),this.$=d[u-1];break;case 41:d[u-1].unshift({type:"breakStart",breakText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.BREAK_START}),d[u-1].push({type:"breakEnd",optText:m.parseMessage(d[u-2]),signalType:m.LINETYPE.BREAK_END}),this.$=d[u-1];break;case 43:this.$=d[u-3].concat([{type:"option",optionText:m.parseMessage(d[u-1]),signalType:m.LINETYPE.CRITICAL_OPTION},d[u]]);break;case 45:this.$=d[u-3].concat([{type:"and",parText:m.parseMessage(d[u-1]),signalType:m.LINETYPE.PAR_AND},d[u]]);break;case 47:this.$=d[u-3].concat([{type:"else",altText:m.parseMessage(d[u-1]),signalType:m.LINETYPE.ALT_ELSE},d[u]]);break;case 48:d[u-3].draw="participant",d[u-3].type="addParticipant",d[u-3].description=m.parseMessage(d[u-1]),this.$=d[u-3];break;case 49:d[u-1].draw="participant",d[u-1].type="addParticipant",this.$=d[u-1];break;case 50:d[u-3].draw="actor",d[u-3].type="addParticipant",d[u-3].description=m.parseMessage(d[u-1]),this.$=d[u-3];break;case 51:d[u-1].draw="actor",d[u-1].type="addParticipant",this.$=d[u-1];break;case 52:d[u-1].type="destroyParticipant",this.$=d[u-1];break;case 53:d[u-1].draw="participant",d[u-1].type="addParticipant",this.$=d[u-1];break;case 54:this.$=[d[u-1],{type:"addNote",placement:d[u-2],actor:d[u-1].actor,text:d[u]}];break;case 55:d[u-2]=[].concat(d[u-1],d[u-1]).slice(0,2),d[u-2][0]=d[u-2][0].actor,d[u-2][1]=d[u-2][1].actor,this.$=[d[u-1],{type:"addNote",placement:m.PLACEMENT.OVER,actor:d[u-2].slice(0,2),text:d[u]}];break;case 56:this.$=[d[u-1],{type:"addLinks",actor:d[u-1].actor,text:d[u]}];break;case 57:this.$=[d[u-1],{type:"addALink",actor:d[u-1].actor,text:d[u]}];break;case 58:this.$=[d[u-1],{type:"addProperties",actor:d[u-1].actor,text:d[u]}];break;case 59:this.$=[d[u-1],{type:"addDetails",actor:d[u-1].actor,text:d[u]}];break;case 62:this.$=[d[u-2],d[u]];break;case 63:this.$=d[u];break;case 64:this.$=m.PLACEMENT.LEFTOF;break;case 65:this.$=m.PLACEMENT.RIGHTOF;break;case 66:this.$=[d[u-4],d[u-1],{type:"addMessage",from:d[u-4].actor,to:d[u-1].actor,signalType:d[u-3],msg:d[u],activate:!0},{type:"activeStart",signalType:m.LINETYPE.ACTIVE_START,actor:d[u-1].actor}];break;case 67:this.$=[d[u-4],d[u-1],{type:"addMessage",from:d[u-4].actor,to:d[u-1].actor,signalType:d[u-3],msg:d[u]},{type:"activeEnd",signalType:m.LINETYPE.ACTIVE_END,actor:d[u-4].actor}];break;case 68:this.$=[d[u-3],d[u-1],{type:"addMessage",from:d[u-3].actor,to:d[u-1].actor,signalType:d[u-2],msg:d[u]}];break;case 69:this.$={type:"addParticipant",actor:d[u-1],config:d[u]};break;case 70:this.$=d[u-1].trim();break;case 71:this.$={type:"addParticipant",actor:d[u]};break;case 72:this.$=m.LINETYPE.SOLID_OPEN;break;case 73:this.$=m.LINETYPE.DOTTED_OPEN;break;case 74:this.$=m.LINETYPE.SOLID;break;case 75:this.$=m.LINETYPE.BIDIRECTIONAL_SOLID;break;case 76:this.$=m.LINETYPE.DOTTED;break;case 77:this.$=m.LINETYPE.BIDIRECTIONAL_DOTTED;break;case 78:this.$=m.LINETYPE.SOLID_CROSS;break;case 79:this.$=m.LINETYPE.DOTTED_CROSS;break;case 80:this.$=m.LINETYPE.SOLID_POINT;break;case 81:this.$=m.LINETYPE.DOTTED_POINT;break;case 82:this.$=m.parseMessage(d[u].trim().substring(1));break}},"anonymous"),table:[{3:1,4:t,5:n,6:s},{1:[3]},{3:5,4:t,5:n,6:s},{3:6,4:t,5:n,6:s},e([1,4,5,13,14,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,50,52,53,55,60,61,62,63,71],r,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:i,5:c,8:8,9:10,12:12,13:h,14:o,17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},e(E,[2,5]),{9:47,12:12,13:h,14:o,17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},e(E,[2,7]),e(E,[2,8]),e(E,[2,14]),{12:48,50:U,52:G,53:q},{15:[1,49]},{5:[1,50]},{5:[1,53],19:[1,51],20:[1,52]},{22:54,71:D},{22:55,71:D},{5:[1,56]},{5:[1,57]},{5:[1,58]},{5:[1,59]},{5:[1,60]},e(E,[2,29]),e(E,[2,30]),{32:[1,61]},{34:[1,62]},e(E,[2,33]),{15:[1,63]},{15:[1,64]},{15:[1,65]},{15:[1,66]},{15:[1,67]},{15:[1,68]},{15:[1,69]},{15:[1,70]},{22:71,54:72,71:[1,73]},{22:74,71:D},{22:75,71:D},{68:76,76:[1,77],77:[1,78],78:[1,79],79:[1,80],80:[1,81],81:[1,82],82:[1,83],83:[1,84],84:[1,85],85:[1,86]},{56:87,58:[1,88],66:[1,89],67:[1,90]},{22:91,71:D},{22:92,71:D},{22:93,71:D},{22:94,71:D},e([5,51,65,76,77,78,79,80,81,82,83,84,85,86],_),e(E,[2,6]),e(E,[2,15]),e(X,[2,9],{10:95}),e(E,[2,17]),{5:[1,97],19:[1,96]},{5:[1,98]},e(E,[2,21]),{5:[1,99]},{5:[1,100]},e(E,[2,24]),e(E,[2,25]),e(E,[2,26]),e(E,[2,27]),e(E,[2,28]),e(E,[2,31]),e(E,[2,32]),e(tt,r,{7:101}),e(tt,r,{7:102}),e(tt,r,{7:103}),e(R,r,{40:104,7:105}),e(Vt,r,{42:106,7:107}),e(Vt,r,{7:107,42:108}),e(Zt,r,{45:109,7:110}),e(tt,r,{7:111}),{5:[1,113],51:[1,112]},{5:[1,114]},e([5,51],_,{72:115,73:[1,116]}),{5:[1,118],51:[1,117]},{5:[1,119]},{22:122,69:[1,120],70:[1,121],71:D},e(ot,[2,72]),e(ot,[2,73]),e(ot,[2,74]),e(ot,[2,75]),e(ot,[2,76]),e(ot,[2,77]),e(ot,[2,78]),e(ot,[2,79]),e(ot,[2,80]),e(ot,[2,81]),{22:123,71:D},{22:125,59:124,71:D},{71:[2,64]},{71:[2,65]},{57:126,86:lt},{57:128,86:lt},{57:129,86:lt},{57:130,86:lt},{4:[1,133],5:[1,135],11:132,12:134,16:[1,131],50:U,52:G,53:q},{5:[1,136]},e(E,[2,19]),e(E,[2,20]),e(E,[2,22]),e(E,[2,23]),{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[1,137],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[1,138],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[1,139],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{16:[1,140]},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[2,46],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,49:[1,141],50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{16:[1,142]},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[2,44],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,48:[1,143],50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{16:[1,144]},{16:[1,145]},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[2,42],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,47:[1,146],50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{4:i,5:c,8:8,9:10,12:12,13:h,14:o,16:[1,147],17:15,18:a,21:p,22:40,23:g,24:19,25:20,26:21,27:22,28:23,29:x,30:y,31:b,33:w,35:k,36:N,37:S,38:M,39:C,41:Y,43:H,44:Z,46:at,50:U,52:G,53:q,55:O,60:$,61:K,62:j,63:rt,71:D},{15:[1,148]},e(E,[2,49]),e(E,[2,53]),{5:[2,69]},{74:[1,149]},{15:[1,150]},e(E,[2,51]),e(E,[2,52]),{22:151,71:D},{22:152,71:D},{57:153,86:lt},{57:154,86:lt},{57:155,86:lt},{65:[1,156],86:[2,63]},{5:[2,56]},{5:[2,82]},{5:[2,57]},{5:[2,58]},{5:[2,59]},e(E,[2,16]),e(X,[2,10]),{12:157,50:U,52:G,53:q},e(X,[2,12]),e(X,[2,13]),e(E,[2,18]),e(E,[2,34]),e(E,[2,35]),e(E,[2,36]),e(E,[2,37]),{15:[1,158]},e(E,[2,38]),{15:[1,159]},e(E,[2,39]),e(E,[2,40]),{15:[1,160]},e(E,[2,41]),{5:[1,161]},{75:[1,162]},{5:[1,163]},{57:164,86:lt},{57:165,86:lt},{5:[2,68]},{5:[2,54]},{5:[2,55]},{22:166,71:D},e(X,[2,11]),e(R,r,{7:105,40:167}),e(Vt,r,{7:107,42:168}),e(Zt,r,{7:110,45:169}),e(E,[2,48]),{5:[2,70]},e(E,[2,50]),{5:[2,66]},{5:[2,67]},{86:[2,62]},{16:[2,47]},{16:[2,45]},{16:[2,43]}],defaultActions:{5:[2,1],6:[2,2],89:[2,64],90:[2,65],115:[2,69],126:[2,56],127:[2,82],128:[2,57],129:[2,58],130:[2,59],153:[2,68],154:[2,54],155:[2,55],162:[2,70],164:[2,66],165:[2,67],166:[2,62],167:[2,47],168:[2,45],169:[2,43]},parseError:f(function(v,A){if(A.recoverable)this.trace(v);else{var L=new Error(v);throw L.hash=A,L}},"parseError"),parse:f(function(v){var A=this,L=[0],m=[],B=[null],d=[],It=this.table,u="",kt=0,$t=0,Te=2,jt=1,Ee=d.slice.call(arguments,1),W=Object.create(this.lexer),ft={yy:{}};for(var Wt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Wt)&&(ft.yy[Wt]=this.yy[Wt]);W.setInput(v,ft.yy),ft.yy.lexer=W,ft.yy.parser=this,typeof W.yylloc>"u"&&(W.yylloc={});var Ft=W.yylloc;d.push(Ft);var be=W.options&&W.options.ranges;typeof ft.yy.parseError=="function"?this.parseError=ft.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function me(et){L.length=L.length-2*et,B.length=B.length-et,d.length=d.length-et}f(me,"popStack");function te(){var et;return et=m.pop()||W.lex()||jt,typeof et!="number"&&(et instanceof Array&&(m=et,et=m.pop()),et=A.symbols_[et]||et),et}f(te,"lex");for(var J,yt,it,qt,bt={},Pt,ht,ee,Nt;;){if(yt=L[L.length-1],this.defaultActions[yt]?it=this.defaultActions[yt]:((J===null||typeof J>"u")&&(J=te()),it=It[yt]&&It[yt][J]),typeof it>"u"||!it.length||!it[0]){var zt="";Nt=[];for(Pt in It[yt])this.terminals_[Pt]&&Pt>Te&&Nt.push("'"+this.terminals_[Pt]+"'");W.showPosition?zt="Parse error on line "+(kt+1)+`: +`+W.showPosition()+` +Expecting `+Nt.join(", ")+", got '"+(this.terminals_[J]||J)+"'":zt="Parse error on line "+(kt+1)+": Unexpected "+(J==jt?"end of input":"'"+(this.terminals_[J]||J)+"'"),this.parseError(zt,{text:W.match,token:this.terminals_[J]||J,line:W.yylineno,loc:Ft,expected:Nt})}if(it[0]instanceof Array&&it.length>1)throw new Error("Parse Error: multiple actions possible at state: "+yt+", token: "+J);switch(it[0]){case 1:L.push(J),B.push(W.yytext),d.push(W.yylloc),L.push(it[1]),J=null,$t=W.yyleng,u=W.yytext,kt=W.yylineno,Ft=W.yylloc;break;case 2:if(ht=this.productions_[it[1]][1],bt.$=B[B.length-ht],bt._$={first_line:d[d.length-(ht||1)].first_line,last_line:d[d.length-1].last_line,first_column:d[d.length-(ht||1)].first_column,last_column:d[d.length-1].last_column},be&&(bt._$.range=[d[d.length-(ht||1)].range[0],d[d.length-1].range[1]]),qt=this.performAction.apply(bt,[u,$t,kt,ft.yy,it[1],B,d].concat(Ee)),typeof qt<"u")return qt;ht&&(L=L.slice(0,-1*ht*2),B=B.slice(0,-1*ht),d=d.slice(0,-1*ht)),L.push(this.productions_[it[1]][0]),B.push(bt.$),d.push(bt._$),ee=It[L[L.length-2]][L[L.length-1]],L.push(ee);break;case 3:return!0}}return!0},"parse")},ye=function(){var pt={EOF:1,parseError:f(function(A,L){if(this.yy.parser)this.yy.parser.parseError(A,L);else throw new Error(A)},"parseError"),setInput:f(function(v,A){return this.yy=A||this.yy||{},this._input=v,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:f(function(){var v=this._input[0];this.yytext+=v,this.yyleng++,this.offset++,this.match+=v,this.matched+=v;var A=v.match(/(?:\r\n?|\n).*/g);return A?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),v},"input"),unput:f(function(v){var A=v.length,L=v.split(/(?:\r\n?|\n)/g);this._input=v+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-A),this.offset-=A;var m=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),L.length-1&&(this.yylineno-=L.length-1);var B=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:L?(L.length===m.length?this.yylloc.first_column:0)+m[m.length-L.length].length-L[0].length:this.yylloc.first_column-A},this.options.ranges&&(this.yylloc.range=[B[0],B[0]+this.yyleng-A]),this.yyleng=this.yytext.length,this},"unput"),more:f(function(){return this._more=!0,this},"more"),reject:f(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:f(function(v){this.unput(this.match.slice(v))},"less"),pastInput:f(function(){var v=this.matched.substr(0,this.matched.length-this.match.length);return(v.length>20?"...":"")+v.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:f(function(){var v=this.match;return v.length<20&&(v+=this._input.substr(0,20-v.length)),(v.substr(0,20)+(v.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:f(function(){var v=this.pastInput(),A=new Array(v.length+1).join("-");return v+this.upcomingInput()+` +`+A+"^"},"showPosition"),test_match:f(function(v,A){var L,m,B;if(this.options.backtrack_lexer&&(B={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(B.yylloc.range=this.yylloc.range.slice(0))),m=v[0].match(/(?:\r\n?|\n).*/g),m&&(this.yylineno+=m.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:m?m[m.length-1].length-m[m.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+v[0].length},this.yytext+=v[0],this.match+=v[0],this.matches=v,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(v[0].length),this.matched+=v[0],L=this.performAction.call(this,this.yy,this,A,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),L)return L;if(this._backtrack){for(var d in B)this[d]=B[d];return!1}return!1},"test_match"),next:f(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var v,A,L,m;this._more||(this.yytext="",this.match="");for(var B=this._currentRules(),d=0;dA[0].length)){if(A=L,m=d,this.options.backtrack_lexer){if(v=this.test_match(L,B[d]),v!==!1)return v;if(this._backtrack){A=!1;continue}else return!1}else if(!this.options.flex)break}return A?(v=this.test_match(A,B[m]),v!==!1?v:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:f(function(){var A=this.next();return A||this.lex()},"lex"),begin:f(function(A){this.conditionStack.push(A)},"begin"),popState:f(function(){var A=this.conditionStack.length-1;return A>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:f(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:f(function(A){return A=this.conditionStack.length-1-Math.abs(A||0),A>=0?this.conditionStack[A]:"INITIAL"},"topState"),pushState:f(function(A){this.begin(A)},"pushState"),stateStackSize:f(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:f(function(A,L,m,B){switch(m){case 0:return 5;case 1:break;case 2:break;case 3:break;case 4:break;case 5:break;case 6:return 19;case 7:return this.begin("CONFIG"),73;case 8:return 74;case 9:return this.popState(),this.popState(),75;case 10:return L.yytext=L.yytext.trim(),71;case 11:return L.yytext=L.yytext.trim(),this.begin("ALIAS"),71;case 12:return this.begin("LINE"),14;case 13:return this.begin("ID"),50;case 14:return this.begin("ID"),52;case 15:return 13;case 16:return this.begin("ID"),53;case 17:return L.yytext=L.yytext.trim(),this.begin("ALIAS"),71;case 18:return this.popState(),this.popState(),this.begin("LINE"),51;case 19:return this.popState(),this.popState(),5;case 20:return this.begin("LINE"),36;case 21:return this.begin("LINE"),37;case 22:return this.begin("LINE"),38;case 23:return this.begin("LINE"),39;case 24:return this.begin("LINE"),49;case 25:return this.begin("LINE"),41;case 26:return this.begin("LINE"),43;case 27:return this.begin("LINE"),48;case 28:return this.begin("LINE"),44;case 29:return this.begin("LINE"),47;case 30:return this.begin("LINE"),46;case 31:return this.popState(),15;case 32:return 16;case 33:return 66;case 34:return 67;case 35:return 60;case 36:return 61;case 37:return 62;case 38:return 63;case 39:return 58;case 40:return 55;case 41:return this.begin("ID"),21;case 42:return this.begin("ID"),23;case 43:return 29;case 44:return 30;case 45:return this.begin("acc_title"),31;case 46:return this.popState(),"acc_title_value";case 47:return this.begin("acc_descr"),33;case 48:return this.popState(),"acc_descr_value";case 49:this.begin("acc_descr_multiline");break;case 50:this.popState();break;case 51:return"acc_descr_multiline_value";case 52:return 6;case 53:return 18;case 54:return 20;case 55:return 65;case 56:return 5;case 57:return L.yytext=L.yytext.trim(),71;case 58:return 78;case 59:return 79;case 60:return 80;case 61:return 81;case 62:return 76;case 63:return 77;case 64:return 82;case 65:return 83;case 66:return 84;case 67:return 85;case 68:return 86;case 69:return 86;case 70:return 69;case 71:return 70;case 72:return 5;case 73:return"INVALID"}},"anonymous"),rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[0-9]+(?=[ \n]+))/i,/^(?:@\{)/i,/^(?:[^\}]+)/i,/^(?:\})/i,/^(?:[^\<->\->:\n,;@\s]+(?=@\{))/i,/^(?:[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:box\b)/i,/^(?:participant\b)/i,/^(?:actor\b)/i,/^(?:create\b)/i,/^(?:destroy\b)/i,/^(?:[^<\->\->:\n,;]+?([\-]*[^<\->\->:\n,;]+?)*?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:par_over\b)/i,/^(?:and\b)/i,/^(?:critical\b)/i,/^(?:option\b)/i,/^(?:break\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:links\b)/i,/^(?:link\b)/i,/^(?:properties\b)/i,/^(?:details\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:title:\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:off\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^+<\->\->:\n,;]+((?!(-x|--x|-\)|--\)))[\-]*[^\+<\->\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:<<->>)/i,/^(?:-->>)/i,/^(?:<<-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?:-[\)])/i,/^(?:--[\)])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]*)/i,/^(?::)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[50,51],inclusive:!1},acc_descr:{rules:[48],inclusive:!1},acc_title:{rules:[46],inclusive:!1},ID:{rules:[2,3,7,10,11,17],inclusive:!1},ALIAS:{rules:[2,3,18,19],inclusive:!1},LINE:{rules:[2,3,31],inclusive:!1},CONFIG:{rules:[8,9],inclusive:!1},CONFIG_DATA:{rules:[],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,6,12,13,14,15,16,20,21,22,23,24,25,26,27,28,29,30,32,33,34,35,36,37,38,39,40,41,42,43,44,45,47,49,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73],inclusive:!0}}};return pt}();Yt.lexer=ye;function At(){this.yy={}}return f(At,"Parser"),At.prototype=Yt,Yt.Parser=At,new At}();Ut.parser=Ut;var Ve=Ut,Ye={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25,AUTONUMBER:26,CRITICAL_START:27,CRITICAL_OPTION:28,CRITICAL_END:29,BREAK_START:30,BREAK_END:31,PAR_OVER_START:32,BIDIRECTIONAL_SOLID:33,BIDIRECTIONAL_DOTTED:34},We={FILLED:0,OPEN:1},Fe={LEFTOF:0,RIGHTOF:1,OVER:2},Rt={ACTOR:"actor",CONTROL:"control",DATABASE:"database",ENTITY:"entity"},wt,qe=(wt=class{constructor(){this.state=new Be(()=>({prevActor:void 0,actors:new Map,createdActors:new Map,destroyedActors:new Map,boxes:[],messages:[],notes:[],sequenceNumbersEnabled:!1,wrapEnabled:void 0,currentBox:void 0,lastCreated:void 0,lastDestroyed:void 0})),this.setAccTitle=se,this.setAccDescription=Ae,this.setDiagramTitle=ke,this.getAccTitle=Pe,this.getAccDescription=Ne,this.getDiagramTitle=Se,this.apply=this.apply.bind(this),this.parseBoxData=this.parseBoxData.bind(this),this.parseMessage=this.parseMessage.bind(this),this.clear(),this.setWrap(st().wrap),this.LINETYPE=Ye,this.ARROWTYPE=We,this.PLACEMENT=Fe}addBox(t){this.state.records.boxes.push({name:t.text,wrap:t.wrap??this.autoWrap(),fill:t.color,actorKeys:[]}),this.state.records.currentBox=this.state.records.boxes.slice(-1)[0]}addActor(t,n,s,r,i){let c=this.state.records.currentBox,h;if(i!==void 0){let a;i.includes(` +`)?a=i+` +`:a=`{ +`+i+` +}`,h=Me(a,{schema:Re})}r=(h==null?void 0:h.type)??r;const o=this.state.records.actors.get(t);if(o){if(this.state.records.currentBox&&o.box&&this.state.records.currentBox!==o.box)throw new Error(`A same participant should only be defined in one Box: ${o.name} can't be in '${o.box.name}' and in '${this.state.records.currentBox.name}' at the same time.`);if(c=o.box?o.box:this.state.records.currentBox,o.box=c,o&&n===o.name&&s==null)return}if((s==null?void 0:s.text)==null&&(s={text:n,type:r}),(r==null||s.text==null)&&(s={text:n,type:r}),this.state.records.actors.set(t,{box:c,name:n,description:s.text,wrap:s.wrap??this.autoWrap(),prevActor:this.state.records.prevActor,links:{},properties:{},actorCnt:null,rectData:null,type:r??"participant"}),this.state.records.prevActor){const a=this.state.records.actors.get(this.state.records.prevActor);a&&(a.nextActor=t)}this.state.records.currentBox&&this.state.records.currentBox.actorKeys.push(t),this.state.records.prevActor=t}activationCount(t){let n,s=0;if(!t)return 0;for(n=0;n>-",token:"->>-",line:"1",loc:{first_line:1,last_line:1,first_column:1,last_column:1},expected:["'ACTIVE_PARTICIPANT'"]},h}return this.state.records.messages.push({id:this.state.records.messages.length.toString(),from:t,to:n,message:(s==null?void 0:s.text)??"",wrap:(s==null?void 0:s.wrap)??this.autoWrap(),type:r,activate:i}),!0}hasAtLeastOneBox(){return this.state.records.boxes.length>0}hasAtLeastOneBoxWithTitle(){return this.state.records.boxes.some(t=>t.name)}getMessages(){return this.state.records.messages}getBoxes(){return this.state.records.boxes}getActors(){return this.state.records.actors}getCreatedActors(){return this.state.records.createdActors}getDestroyedActors(){return this.state.records.destroyedActors}getActor(t){return this.state.records.actors.get(t)}getActorKeys(){return[...this.state.records.actors.keys()]}enableSequenceNumbers(){this.state.records.sequenceNumbersEnabled=!0}disableSequenceNumbers(){this.state.records.sequenceNumbersEnabled=!1}showSequenceNumbers(){return this.state.records.sequenceNumbersEnabled}setWrap(t){this.state.records.wrapEnabled=t}extractWrap(t){if(t===void 0)return{};t=t.trim();const n=/^:?wrap:/.exec(t)!==null?!0:/^:?nowrap:/.exec(t)!==null?!1:void 0;return{cleanedText:(n===void 0?t:t.replace(/^:?(?:no)?wrap:/,"")).trim(),wrap:n}}autoWrap(){var t;return this.state.records.wrapEnabled!==void 0?this.state.records.wrapEnabled:((t=st().sequence)==null?void 0:t.wrap)??!1}clear(){this.state.reset(),De()}parseMessage(t){const n=t.trim(),{wrap:s,cleanedText:r}=this.extractWrap(n),i={text:r,wrap:s};return Q.debug(`parseMessage: ${JSON.stringify(i)}`),i}parseBoxData(t){const n=/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/.exec(t);let s=n!=null&&n[1]?n[1].trim():"transparent",r=n!=null&&n[2]?n[2].trim():void 0;if(window!=null&&window.CSS)window.CSS.supports("color",s)||(s="transparent",r=t.trim());else{const h=new Option().style;h.color=s,h.color!==s&&(s="transparent",r=t.trim())}const{wrap:i,cleanedText:c}=this.extractWrap(r);return{text:c?Mt(c,st()):void 0,color:s,wrap:i}}addNote(t,n,s){const r={actor:t,placement:n,message:s.text,wrap:s.wrap??this.autoWrap()},i=[].concat(t,t);this.state.records.notes.push(r),this.state.records.messages.push({id:this.state.records.messages.length.toString(),from:i[0],to:i[1],message:s.text,wrap:s.wrap??this.autoWrap(),type:this.LINETYPE.NOTE,placement:n})}addLinks(t,n){const s=this.getActor(t);try{let r=Mt(n.text,st());r=r.replace(/=/g,"="),r=r.replace(/&/g,"&");const i=JSON.parse(r);this.insertLinks(s,i)}catch(r){Q.error("error while parsing actor link text",r)}}addALink(t,n){const s=this.getActor(t);try{const r={};let i=Mt(n.text,st());const c=i.indexOf("@");i=i.replace(/=/g,"="),i=i.replace(/&/g,"&");const h=i.slice(0,c-1).trim(),o=i.slice(c+1).trim();r[h]=o,this.insertLinks(s,r)}catch(r){Q.error("error while parsing actor link text",r)}}insertLinks(t,n){if(t.links==null)t.links=n;else for(const s in n)t.links[s]=n[s]}addProperties(t,n){const s=this.getActor(t);try{const r=Mt(n.text,st()),i=JSON.parse(r);this.insertProperties(s,i)}catch(r){Q.error("error while parsing actor properties text",r)}}insertProperties(t,n){if(t.properties==null)t.properties=n;else for(const s in n)t.properties[s]=n[s]}boxEnd(){this.state.records.currentBox=void 0}addDetails(t,n){const s=this.getActor(t),r=document.getElementById(n.text);try{const i=r.innerHTML,c=JSON.parse(i);c.properties&&this.insertProperties(s,c.properties),c.links&&this.insertLinks(s,c.links)}catch(i){Q.error("error while parsing actor details text",i)}}getActorProperty(t,n){if((t==null?void 0:t.properties)!==void 0)return t.properties[n]}apply(t){if(Array.isArray(t))t.forEach(n=>{this.apply(n)});else switch(t.type){case"sequenceIndex":this.state.records.messages.push({id:this.state.records.messages.length.toString(),from:void 0,to:void 0,message:{start:t.sequenceIndex,step:t.sequenceIndexStep,visible:t.sequenceVisible},wrap:!1,type:t.signalType});break;case"addParticipant":this.addActor(t.actor,t.actor,t.description,t.draw,t.config);break;case"createParticipant":if(this.state.records.actors.has(t.actor))throw new Error("It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior");this.state.records.lastCreated=t.actor,this.addActor(t.actor,t.actor,t.description,t.draw,t.config),this.state.records.createdActors.set(t.actor,this.state.records.messages.length);break;case"destroyParticipant":this.state.records.lastDestroyed=t.actor,this.state.records.destroyedActors.set(t.actor,this.state.records.messages.length);break;case"activeStart":this.addSignal(t.actor,void 0,void 0,t.signalType);break;case"activeEnd":this.addSignal(t.actor,void 0,void 0,t.signalType);break;case"addNote":this.addNote(t.actor,t.placement,t.text);break;case"addLinks":this.addLinks(t.actor,t.text);break;case"addALink":this.addALink(t.actor,t.text);break;case"addProperties":this.addProperties(t.actor,t.text);break;case"addDetails":this.addDetails(t.actor,t.text);break;case"addMessage":if(this.state.records.lastCreated){if(t.to!==this.state.records.lastCreated)throw new Error("The created participant "+this.state.records.lastCreated.name+" does not have an associated creating message after its declaration. Please check the sequence diagram.");this.state.records.lastCreated=void 0}else if(this.state.records.lastDestroyed){if(t.to!==this.state.records.lastDestroyed&&t.from!==this.state.records.lastDestroyed)throw new Error("The destroyed participant "+this.state.records.lastDestroyed.name+" does not have an associated destroying message after its declaration. Please check the sequence diagram.");this.state.records.lastDestroyed=void 0}this.addSignal(t.from,t.to,t.msg,t.signalType,t.activate);break;case"boxStart":this.addBox(t.boxData);break;case"boxEnd":this.boxEnd();break;case"loopStart":this.addSignal(void 0,void 0,t.loopText,t.signalType);break;case"loopEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"rectStart":this.addSignal(void 0,void 0,t.color,t.signalType);break;case"rectEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"optStart":this.addSignal(void 0,void 0,t.optText,t.signalType);break;case"optEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"altStart":this.addSignal(void 0,void 0,t.altText,t.signalType);break;case"else":this.addSignal(void 0,void 0,t.altText,t.signalType);break;case"altEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"setAccTitle":se(t.text);break;case"parStart":this.addSignal(void 0,void 0,t.parText,t.signalType);break;case"and":this.addSignal(void 0,void 0,t.parText,t.signalType);break;case"parEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"criticalStart":this.addSignal(void 0,void 0,t.criticalText,t.signalType);break;case"option":this.addSignal(void 0,void 0,t.optionText,t.signalType);break;case"criticalEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break;case"breakStart":this.addSignal(void 0,void 0,t.breakText,t.signalType);break;case"breakEnd":this.addSignal(void 0,void 0,void 0,t.signalType);break}}getConfig(){return st().sequence}},f(wt,"SequenceDB"),wt),ze=f(e=>`.actor { + stroke: ${e.actorBorder}; + fill: ${e.actorBkg}; + } + + text.actor > tspan { + fill: ${e.actorTextColor}; + stroke: none; + } + + .actor-line { + stroke: ${e.actorLineColor}; + } + + .innerArc { + stroke-width: 1.5; + stroke-dasharray: none; + } + + .messageLine0 { + stroke-width: 1.5; + stroke-dasharray: none; + stroke: ${e.signalColor}; + } + + .messageLine1 { + stroke-width: 1.5; + stroke-dasharray: 2, 2; + stroke: ${e.signalColor}; + } + + #arrowhead path { + fill: ${e.signalColor}; + stroke: ${e.signalColor}; + } + + .sequenceNumber { + fill: ${e.sequenceNumberColor}; + } + + #sequencenumber { + fill: ${e.signalColor}; + } + + #crosshead path { + fill: ${e.signalColor}; + stroke: ${e.signalColor}; + } + + .messageText { + fill: ${e.signalTextColor}; + stroke: none; + } + + .labelBox { + stroke: ${e.labelBoxBorderColor}; + fill: ${e.labelBoxBkgColor}; + } + + .labelText, .labelText > tspan { + fill: ${e.labelTextColor}; + stroke: none; + } + + .loopText, .loopText > tspan { + fill: ${e.loopTextColor}; + stroke: none; + } + + .loopLine { + stroke-width: 2px; + stroke-dasharray: 2, 2; + stroke: ${e.labelBoxBorderColor}; + fill: ${e.labelBoxBorderColor}; + } + + .note { + //stroke: #decc93; + stroke: ${e.noteBorderColor}; + fill: ${e.noteBkgColor}; + } + + .noteText, .noteText > tspan { + fill: ${e.noteTextColor}; + stroke: none; + } + + .activation0 { + fill: ${e.activationBkgColor}; + stroke: ${e.activationBorderColor}; + } + + .activation1 { + fill: ${e.activationBkgColor}; + stroke: ${e.activationBorderColor}; + } + + .activation2 { + fill: ${e.activationBkgColor}; + stroke: ${e.activationBorderColor}; + } + + .actorPopupMenu { + position: absolute; + } + + .actorPopupMenuPanel { + position: absolute; + fill: ${e.actorBkg}; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); +} + .actor-man line { + stroke: ${e.actorBorder}; + fill: ${e.actorBkg}; + } + .actor-man circle, line { + stroke: ${e.actorBorder}; + fill: ${e.actorBkg}; + stroke-width: 2px; + } + +`,"getStyles"),He=ze,Tt=18*2,gt="actor-top",xt="actor-bottom",Ct="actor-box",ut="actor-man",Lt=f(function(e,t){return ve(e,t)},"drawRect"),Ue=f(function(e,t,n,s,r){if(t.links===void 0||t.links===null||Object.keys(t.links).length===0)return{height:0,width:0};const i=t.links,c=t.actorCnt,h=t.rectData;var o="none";r&&(o="block !important");const a=e.append("g");a.attr("id","actor"+c+"_popup"),a.attr("class","actorPopupMenu"),a.attr("display",o);var p="";h.class!==void 0&&(p=" "+h.class);let g=h.width>n?h.width:n;const x=a.append("rect");if(x.attr("class","actorPopupMenuPanel"+p),x.attr("x",h.x),x.attr("y",h.height),x.attr("fill",h.fill),x.attr("stroke",h.stroke),x.attr("width",g),x.attr("height",h.height),x.attr("rx",h.rx),x.attr("ry",h.ry),i!=null){var y=20;for(let k in i){var b=a.append("a"),w=re(i[k]);b.attr("xlink:href",w),b.attr("target","_blank"),us(s)(k,b,h.x+10,h.height+y,g,20,{class:"actor"},s),y+=30}}return x.attr("height",y),{height:h.height+y,width:g}},"drawPopup"),Ot=f(function(e){return"var pu = document.getElementById('"+e+"'); if (pu != null) { pu.style.display = pu.style.display == 'block' ? 'none' : 'block'; }"},"popupMenuToggle"),Dt=f(async function(e,t,n=null){let s=e.append("foreignObject");const r=await ne(t.text,Ht()),c=s.append("xhtml:div").attr("style","width: fit-content;").attr("xmlns","http://www.w3.org/1999/xhtml").html(r).node().getBoundingClientRect();if(s.attr("height",Math.round(c.height)).attr("width",Math.round(c.width)),t.class==="noteText"){const h=e.node().firstChild;h.setAttribute("height",c.height+2*t.textMargin);const o=h.getBBox();s.attr("x",Math.round(o.x+o.width/2-c.width/2)).attr("y",Math.round(o.y+o.height/2-c.height/2))}else if(n){let{startx:h,stopx:o,starty:a}=n;if(h>o){const p=h;h=o,o=p}s.attr("x",Math.round(h+Math.abs(h-o)/2-c.width/2)),t.class==="loopText"?s.attr("y",Math.round(a)):s.attr("y",Math.round(a-c.height))}return[s]},"drawKatex"),vt=f(function(e,t){let n=0,s=0;const r=t.text.split(I.lineBreakRegex),[i,c]=ie(t.fontSize);let h=[],o=0,a=f(()=>t.y,"yfunc");if(t.valign!==void 0&&t.textMargin!==void 0&&t.textMargin>0)switch(t.valign){case"top":case"start":a=f(()=>Math.round(t.y+t.textMargin),"yfunc");break;case"middle":case"center":a=f(()=>Math.round(t.y+(n+s+t.textMargin)/2),"yfunc");break;case"bottom":case"end":a=f(()=>Math.round(t.y+(n+s+2*t.textMargin)-t.textMargin),"yfunc");break}if(t.anchor!==void 0&&t.textMargin!==void 0&&t.width!==void 0)switch(t.anchor){case"left":case"start":t.x=Math.round(t.x+t.textMargin),t.anchor="start",t.dominantBaseline="middle",t.alignmentBaseline="middle";break;case"middle":case"center":t.x=Math.round(t.x+t.width/2),t.anchor="middle",t.dominantBaseline="middle",t.alignmentBaseline="middle";break;case"right":case"end":t.x=Math.round(t.x+t.width-t.textMargin),t.anchor="end",t.dominantBaseline="middle",t.alignmentBaseline="middle";break}for(let[p,g]of r.entries()){t.textMargin!==void 0&&t.textMargin===0&&i!==void 0&&(o=p*i);const x=e.append("text");x.attr("x",t.x),x.attr("y",a()),t.anchor!==void 0&&x.attr("text-anchor",t.anchor).attr("dominant-baseline",t.dominantBaseline).attr("alignment-baseline",t.alignmentBaseline),t.fontFamily!==void 0&&x.style("font-family",t.fontFamily),c!==void 0&&x.style("font-size",c),t.fontWeight!==void 0&&x.style("font-weight",t.fontWeight),t.fill!==void 0&&x.attr("fill",t.fill),t.class!==void 0&&x.attr("class",t.class),t.dy!==void 0?x.attr("dy",t.dy):o!==0&&x.attr("dy",o);const y=g||Ce;if(t.tspan){const b=x.append("tspan");b.attr("x",t.x),t.fill!==void 0&&b.attr("fill",t.fill),b.text(y)}else x.text(y);t.valign!==void 0&&t.textMargin!==void 0&&t.textMargin>0&&(s+=(x._groups||x)[0][0].getBBox().height,n=s),h.push(x)}return h},"drawText"),oe=f(function(e,t){function n(r,i,c,h,o){return r+","+i+" "+(r+c)+","+i+" "+(r+c)+","+(i+h-o)+" "+(r+c-o*1.2)+","+(i+h)+" "+r+","+(i+h)}f(n,"genPoints");const s=e.append("polygon");return s.attr("points",n(t.x,t.y,t.width,t.height,7)),s.attr("class","labelBox"),t.y=t.y+t.height/2,vt(e,t),s},"drawLabel"),P=-1,ce=f((e,t,n,s)=>{e.select&&n.forEach(r=>{const i=t.get(r),c=e.select("#actor"+i.actorCnt);!s.mirrorActors&&i.stopy?c.attr("y2",i.stopy+i.height/2):s.mirrorActors&&c.attr("y2",i.stopy)})},"fixLifeLineHeights"),Ge=f(function(e,t,n,s){var y,b;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+t.height,h=e.append("g").lower();var o=h;s||(P++,Object.keys(t.links||{}).length&&!n.forceMenus&&o.attr("onclick",Ot(`actor${P}_popup`)).attr("cursor","pointer"),o.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),o=h.append("g"),t.actorCnt=P,t.links!=null&&o.attr("id","root-"+P));const a=ct();var p="actor";(y=t.properties)!=null&&y.class?p=t.properties.class:a.fill="#eaeaea",s?p+=` ${xt}`:p+=` ${gt}`,a.x=t.x,a.y=r,a.width=t.width,a.height=t.height,a.class=p,a.rx=3,a.ry=3,a.name=t.name;const g=Lt(o,a);if(t.rectData=a,(b=t.properties)!=null&&b.icon){const w=t.properties.icon.trim();w.charAt(0)==="@"?Jt(o,a.x+a.width-20,a.y+10,w.substr(1)):Qt(o,a.x+a.width-20,a.y+10,w)}dt(n,z(t.description))(t.description,o,a.x,a.y,a.width,a.height,{class:`actor ${Ct}`},n);let x=t.height;if(g.node){const w=g.node().getBBox();t.height=w.height,x=w.height}return x},"drawActorTypeParticipant"),Ke=f(function(e,t,n,s){var w,k;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+t.height,h=e.append("g").lower();var o=h;s||(P++,Object.keys(t.links||{}).length&&!n.forceMenus&&o.attr("onclick",Ot(`actor${P}_popup`)).attr("cursor","pointer"),o.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),o=h.append("g"),t.actorCnt=P,t.links!=null&&o.attr("id","root-"+P));const a=ct();var p="actor";(w=t.properties)!=null&&w.class?p=t.properties.class:a.fill="#eaeaea",s?p+=` ${xt}`:p+=` ${gt}`,a.x=t.x,a.y=r,a.width=t.width,a.height=t.height,a.class=p,a.name=t.name;const g=6,x={...a,x:a.x+-g,y:a.y+ +g,class:"actor"},y=Lt(o,a);if(Lt(o,x),t.rectData=a,(k=t.properties)!=null&&k.icon){const N=t.properties.icon.trim();N.charAt(0)==="@"?Jt(o,a.x+a.width-20,a.y+10,N.substr(1)):Qt(o,a.x+a.width-20,a.y+10,N)}dt(n,z(t.description))(t.description,o,a.x-g,a.y+g,a.width,a.height,{class:`actor ${Ct}`},n);let b=t.height;if(y.node){const N=y.node().getBBox();t.height=N.height,b=N.height}return b},"drawActorTypeCollections"),Xe=f(function(e,t,n,s){var N,S;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+t.height,h=e.append("g").lower();let o=h;s||(P++,Object.keys(t.links||{}).length&&!n.forceMenus&&o.attr("onclick",Ot(`actor${P}_popup`)).attr("cursor","pointer"),o.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),o=h.append("g"),t.actorCnt=P,t.links!=null&&o.attr("id","root-"+P));const a=ct();let p="actor";(N=t.properties)!=null&&N.class?p=t.properties.class:a.fill="#eaeaea",s?p+=` ${xt}`:p+=` ${gt}`,a.x=t.x,a.y=r,a.width=t.width,a.height=t.height,a.class=p,a.name=t.name;const g=a.height/2,x=g/(2.5+a.height/50),y=o.append("g"),b=o.append("g");if(y.append("path").attr("d",`M ${a.x},${a.y+g} + a ${x},${g} 0 0 0 0,${a.height} + h ${a.width-2*x} + a ${x},${g} 0 0 0 0,-${a.height} + Z + `).attr("class",p),b.append("path").attr("d",`M ${a.x},${a.y+g} + a ${x},${g} 0 0 0 0,${a.height}`).attr("stroke","#666").attr("stroke-width","1px").attr("class",p),y.attr("transform",`translate(${x}, ${-(a.height/2)})`),b.attr("transform",`translate(${a.width-x}, ${-a.height/2})`),t.rectData=a,(S=t.properties)!=null&&S.icon){const M=t.properties.icon.trim(),C=a.x+a.width-20,Y=a.y+10;M.charAt(0)==="@"?Jt(o,C,Y,M.substr(1)):Qt(o,C,Y,M)}dt(n,z(t.description))(t.description,o,a.x,a.y,a.width,a.height,{class:`actor ${Ct}`},n);let w=t.height;const k=y.select("path:last-child");if(k.node()){const M=k.node().getBBox();t.height=M.height,w=M.height}return w},"drawActorTypeQueue"),Je=f(function(e,t,n,s){var w;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+75,h=e.append("g").lower();s||(P++,h.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),t.actorCnt=P);const o=e.append("g");let a=ut;s?a+=` ${xt}`:a+=` ${gt}`,o.attr("class",a),o.attr("name",t.name);const p=ct();p.x=t.x,p.y=r,p.fill="#eaeaea",p.width=t.width,p.height=t.height,p.class="actor";const g=t.x+t.width/2,x=r+30,y=18;o.append("defs").append("marker").attr("id","filled-head-control").attr("refX",11).attr("refY",5.8).attr("markerWidth",20).attr("markerHeight",28).attr("orient","172.5").append("path").attr("d","M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z"),o.append("circle").attr("cx",g).attr("cy",x).attr("r",y).attr("fill","#eaeaf7").attr("stroke","#666").attr("stroke-width",1.2),o.append("line").attr("marker-end","url(#filled-head-control)").attr("transform",`translate(${g}, ${x-y})`);const b=o.node().getBBox();return t.height=b.height+2*(((w=n==null?void 0:n.sequence)==null?void 0:w.labelBoxHeight)??0),dt(n,z(t.description))(t.description,o,p.x,p.y+y+(s?5:10),p.width,p.height,{class:`actor ${ut}`},n),t.height},"drawActorTypeControl"),Qe=f(function(e,t,n,s){var w;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+75,h=e.append("g").lower(),o=e.append("g");let a=ut;s?a+=` ${xt}`:a+=` ${gt}`,o.attr("class",a),o.attr("name",t.name);const p=ct();p.x=t.x,p.y=r,p.fill="#eaeaea",p.width=t.width,p.height=t.height,p.class="actor";const g=t.x+t.width/2,x=r+(s?10:25),y=18;o.append("circle").attr("cx",g).attr("cy",x).attr("r",y).attr("width",t.width).attr("height",t.height),o.append("line").attr("x1",g-y).attr("x2",g+y).attr("y1",x+y).attr("y2",x+y).attr("stroke","#333").attr("stroke-width",2);const b=o.node().getBBox();return t.height=b.height+(((w=n==null?void 0:n.sequence)==null?void 0:w.labelBoxHeight)??0),s||(P++,h.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),t.actorCnt=P),dt(n,z(t.description))(t.description,o,p.x,p.y+(s?(x-r+y-5)/2:(x+y-r)/2),p.width,p.height,{class:`actor ${ut}`},n),s?o.attr("transform",`translate(0, ${y/2})`):o.attr("transform",`translate(0, ${y/2})`),t.height},"drawActorTypeEntity"),Ze=f(function(e,t,n,s){var S;const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+t.height+2*n.boxTextMargin,h=e.append("g").lower();let o=h;s||(P++,Object.keys(t.links||{}).length&&!n.forceMenus&&o.attr("onclick",Ot(`actor${P}_popup`)).attr("cursor","pointer"),o.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),o=h.append("g"),t.actorCnt=P,t.links!=null&&o.attr("id","root-"+P));const a=ct();let p="actor";(S=t.properties)!=null&&S.class?p=t.properties.class:a.fill="#eaeaea",s?p+=` ${xt}`:p+=` ${gt}`,a.x=t.x,a.y=r,a.width=t.width,a.height=t.height,a.class=p,a.name=t.name,a.x=t.x,a.y=r;const g=a.width/4,x=a.width/4,y=g/2,b=y/(2.5+g/50),w=o.append("g"),k=` + M ${a.x},${a.y+b} + a ${y},${b} 0 0 0 ${g},0 + a ${y},${b} 0 0 0 -${g},0 + l 0,${x-2*b} + a ${y},${b} 0 0 0 ${g},0 + l 0,-${x-2*b} +`;w.append("path").attr("d",k).attr("fill","#eaeaea").attr("stroke","#000").attr("stroke-width",1).attr("class",p),s?w.attr("transform",`translate(${g*1.5}, ${a.height/4-2*b})`):w.attr("transform",`translate(${g*1.5}, ${(a.height+b)/4})`),t.rectData=a,dt(n,z(t.description))(t.description,o,a.x,a.y+(s?(a.height+x)/4:(a.height+b)/2),a.width,a.height,{class:`actor ${Ct}`},n);const N=w.select("path:last-child");if(N.node()){const M=N.node().getBBox();t.height=M.height+(n.sequence.labelBoxHeight??0)}return t.height},"drawActorTypeDatabase"),$e=f(function(e,t,n,s){const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+80,h=30,o=e.append("g").lower();s||(P++,o.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),t.actorCnt=P);const a=e.append("g");let p=ut;s?p+=` ${xt}`:p+=` ${gt}`,a.attr("class",p),a.attr("name",t.name);const g=ct();g.x=t.x,g.y=r,g.fill="#eaeaea",g.width=t.width,g.height=t.height,g.class="actor",a.append("line").attr("id","actor-man-torso"+P).attr("x1",t.x+t.width/2-h*2.5).attr("y1",r+10).attr("x2",t.x+t.width/2-15).attr("y2",r+10),a.append("line").attr("id","actor-man-arms"+P).attr("x1",t.x+t.width/2-h*2.5).attr("y1",r+0).attr("x2",t.x+t.width/2-h*2.5).attr("y2",r+20),a.append("circle").attr("cx",t.x+t.width/2).attr("cy",r+10).attr("r",h);const x=a.node().getBBox();return t.height=x.height+(n.sequence.labelBoxHeight??0),dt(n,z(t.description))(t.description,a,g.x,g.y+(s?h/2-4:h/2+3),g.width,g.height,{class:`actor ${ut}`},n),s?a.attr("transform",`translate(0,${h/2+7})`):a.attr("transform",`translate(0,${h/2+7})`),t.height},"drawActorTypeBoundary"),je=f(function(e,t,n,s){const r=s?t.stopy:t.starty,i=t.x+t.width/2,c=r+80,h=e.append("g").lower();s||(P++,h.append("line").attr("id","actor"+P).attr("x1",i).attr("y1",c).attr("x2",i).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",t.name),t.actorCnt=P);const o=e.append("g");let a=ut;s?a+=` ${xt}`:a+=` ${gt}`,o.attr("class",a),o.attr("name",t.name);const p=ct();p.x=t.x,p.y=r,p.fill="#eaeaea",p.width=t.width,p.height=t.height,p.class="actor",p.rx=3,p.ry=3,o.append("line").attr("id","actor-man-torso"+P).attr("x1",i).attr("y1",r+25).attr("x2",i).attr("y2",r+45),o.append("line").attr("id","actor-man-arms"+P).attr("x1",i-Tt/2).attr("y1",r+33).attr("x2",i+Tt/2).attr("y2",r+33),o.append("line").attr("x1",i-Tt/2).attr("y1",r+60).attr("x2",i).attr("y2",r+45),o.append("line").attr("x1",i).attr("y1",r+45).attr("x2",i+Tt/2-2).attr("y2",r+60);const g=o.append("circle");g.attr("cx",t.x+t.width/2),g.attr("cy",r+10),g.attr("r",15),g.attr("width",t.width),g.attr("height",t.height);const x=o.node().getBBox();return t.height=x.height,dt(n,z(t.description))(t.description,o,p.x,p.y+35,p.width,p.height,{class:`actor ${ut}`},n),t.height},"drawActorTypeActor"),ts=f(async function(e,t,n,s){switch(t.type){case"actor":return await je(e,t,n,s);case"participant":return await Ge(e,t,n,s);case"boundary":return await $e(e,t,n,s);case"control":return await Je(e,t,n,s);case"entity":return await Qe(e,t,n,s);case"database":return await Ze(e,t,n,s);case"collections":return await Ke(e,t,n,s);case"queue":return await Xe(e,t,n,s)}},"drawActor"),es=f(function(e,t,n){const r=e.append("g");le(r,t),t.name&&dt(n)(t.name,r,t.x,t.y+n.boxTextMargin+(t.textMaxHeight||0)/2,t.width,0,{class:"text"},n),r.lower()},"drawBox"),ss=f(function(e){return e.append("g")},"anchorElement"),as=f(function(e,t,n,s,r){const i=ct(),c=t.anchored;i.x=t.startx,i.y=t.starty,i.class="activation"+r%3,i.width=t.stopx-t.startx,i.height=n-t.starty,Lt(c,i)},"drawActivation"),rs=f(async function(e,t,n,s){const{boxMargin:r,boxTextMargin:i,labelBoxHeight:c,labelBoxWidth:h,messageFontFamily:o,messageFontSize:a,messageFontWeight:p}=s,g=e.append("g"),x=f(function(w,k,N,S){return g.append("line").attr("x1",w).attr("y1",k).attr("x2",N).attr("y2",S).attr("class","loopLine")},"drawLoopLine");x(t.startx,t.starty,t.stopx,t.starty),x(t.stopx,t.starty,t.stopx,t.stopy),x(t.startx,t.stopy,t.stopx,t.stopy),x(t.startx,t.starty,t.startx,t.stopy),t.sections!==void 0&&t.sections.forEach(function(w){x(t.startx,w.y,t.stopx,w.y).style("stroke-dasharray","3, 3")});let y=Xt();y.text=n,y.x=t.startx,y.y=t.starty,y.fontFamily=o,y.fontSize=a,y.fontWeight=p,y.anchor="middle",y.valign="middle",y.tspan=!1,y.width=h||50,y.height=c||20,y.textMargin=i,y.class="labelText",oe(g,y),y=he(),y.text=t.title,y.x=t.startx+h/2+(t.stopx-t.startx)/2,y.y=t.starty+r+i,y.anchor="middle",y.valign="middle",y.textMargin=i,y.class="loopText",y.fontFamily=o,y.fontSize=a,y.fontWeight=p,y.wrap=!0;let b=z(y.text)?await Dt(g,y,t):vt(g,y);if(t.sectionTitles!==void 0){for(const[w,k]of Object.entries(t.sectionTitles))if(k.message){y.text=k.message,y.x=t.startx+(t.stopx-t.startx)/2,y.y=t.sections[w].y+r+i,y.class="loopText",y.anchor="middle",y.valign="middle",y.tspan=!1,y.fontFamily=o,y.fontSize=a,y.fontWeight=p,y.wrap=t.wrap,z(y.text)?(t.starty=t.sections[w].y,await Dt(g,y,t)):vt(g,y);let N=Math.round(b.map(S=>(S._groups||S)[0][0].getBBox().height).reduce((S,M)=>S+M));t.sections[w].height+=N-(r+i)}}return t.height=Math.round(t.stopy-t.starty),g},"drawLoop"),le=f(function(e,t){we(e,t)},"drawBackgroundRect"),is=f(function(e){e.append("defs").append("symbol").attr("id","database").attr("fill-rule","evenodd").attr("clip-rule","evenodd").append("path").attr("transform","scale(.5)").attr("d","M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z")},"insertDatabaseIcon"),ns=f(function(e){e.append("defs").append("symbol").attr("id","computer").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z")},"insertComputerIcon"),os=f(function(e){e.append("defs").append("symbol").attr("id","clock").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z")},"insertClockIcon"),cs=f(function(e){e.append("defs").append("marker").attr("id","arrowhead").attr("refX",7.9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto-start-reverse").append("path").attr("d","M -1 0 L 10 5 L 0 10 z")},"insertArrowHead"),ls=f(function(e){e.append("defs").append("marker").attr("id","filled-head").attr("refX",15.5).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"insertArrowFilledHead"),hs=f(function(e){e.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},"insertSequenceNumber"),ds=f(function(e){e.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",4).attr("refY",4.5).append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1pt").attr("d","M 1,2 L 6,7 M 6,2 L 1,7")},"insertArrowCrossHead"),he=f(function(){return{x:0,y:0,fill:void 0,anchor:void 0,style:"#666",width:void 0,height:void 0,textMargin:0,rx:0,ry:0,tspan:!0,valign:void 0}},"getTextObj"),ps=f(function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},"getNoteRect"),dt=function(){function e(i,c,h,o,a,p,g){const x=c.append("text").attr("x",h+a/2).attr("y",o+p/2+5).style("text-anchor","middle").text(i);r(x,g)}f(e,"byText");function t(i,c,h,o,a,p,g,x){const{actorFontSize:y,actorFontFamily:b,actorFontWeight:w}=x,[k,N]=ie(y),S=i.split(I.lineBreakRegex);for(let M=0;Me.height||0))+(this.loops.length===0?0:this.loops.map(e=>e.height||0).reduce((e,t)=>e+t))+(this.messages.length===0?0:this.messages.map(e=>e.height||0).reduce((e,t)=>e+t))+(this.notes.length===0?0:this.notes.map(e=>e.height||0).reduce((e,t)=>e+t))},"getHeight"),clear:f(function(){this.actors=[],this.boxes=[],this.loops=[],this.messages=[],this.notes=[]},"clear"),addBox:f(function(e){this.boxes.push(e)},"addBox"),addActor:f(function(e){this.actors.push(e)},"addActor"),addLoop:f(function(e){this.loops.push(e)},"addLoop"),addMessage:f(function(e){this.messages.push(e)},"addMessage"),addNote:f(function(e){this.notes.push(e)},"addNote"),lastActor:f(function(){return this.actors[this.actors.length-1]},"lastActor"),lastLoop:f(function(){return this.loops[this.loops.length-1]},"lastLoop"),lastMessage:f(function(){return this.messages[this.messages.length-1]},"lastMessage"),lastNote:f(function(){return this.notes[this.notes.length-1]},"lastNote"),actors:[],boxes:[],loops:[],messages:[],notes:[]},init:f(function(){this.sequenceItems=[],this.activations=[],this.models.clear(),this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0,ue(st())},"init"),updateVal:f(function(e,t,n,s){e[t]===void 0?e[t]=n:e[t]=s(n,e[t])},"updateVal"),updateBounds:f(function(e,t,n,s){const r=this;let i=0;function c(h){return f(function(a){i++;const p=r.sequenceItems.length-i+1;r.updateVal(a,"starty",t-p*l.boxMargin,Math.min),r.updateVal(a,"stopy",s+p*l.boxMargin,Math.max),r.updateVal(T.data,"startx",e-p*l.boxMargin,Math.min),r.updateVal(T.data,"stopx",n+p*l.boxMargin,Math.max),h!=="activation"&&(r.updateVal(a,"startx",e-p*l.boxMargin,Math.min),r.updateVal(a,"stopx",n+p*l.boxMargin,Math.max),r.updateVal(T.data,"starty",t-p*l.boxMargin,Math.min),r.updateVal(T.data,"stopy",s+p*l.boxMargin,Math.max))},"updateItemBounds")}f(c,"updateFn"),this.sequenceItems.forEach(c()),this.activations.forEach(c("activation"))},"updateBounds"),insert:f(function(e,t,n,s){const r=I.getMin(e,n),i=I.getMax(e,n),c=I.getMin(t,s),h=I.getMax(t,s);this.updateVal(T.data,"startx",r,Math.min),this.updateVal(T.data,"starty",c,Math.min),this.updateVal(T.data,"stopx",i,Math.max),this.updateVal(T.data,"stopy",h,Math.max),this.updateBounds(r,c,i,h)},"insert"),newActivation:f(function(e,t,n){const s=n.get(e.from),r=Bt(e.from).length||0,i=s.x+s.width/2+(r-1)*l.activationWidth/2;this.activations.push({startx:i,starty:this.verticalPos+2,stopx:i+l.activationWidth,stopy:void 0,actor:e.from,anchored:V.anchorElement(t)})},"newActivation"),endActivation:f(function(e){const t=this.activations.map(function(n){return n.actor}).lastIndexOf(e.from);return this.activations.splice(t,1)[0]},"endActivation"),createLoop:f(function(e={message:void 0,wrap:!1,width:void 0},t){return{startx:void 0,starty:this.verticalPos,stopx:void 0,stopy:void 0,title:e.message,wrap:e.wrap,width:e.width,height:0,fill:t}},"createLoop"),newLoop:f(function(e={message:void 0,wrap:!1,width:void 0},t){this.sequenceItems.push(this.createLoop(e,t))},"newLoop"),endLoop:f(function(){return this.sequenceItems.pop()},"endLoop"),isLoopOverlap:f(function(){return this.sequenceItems.length?this.sequenceItems[this.sequenceItems.length-1].overlap:!1},"isLoopOverlap"),addSectionToLoop:f(function(e){const t=this.sequenceItems.pop();t.sections=t.sections||[],t.sectionTitles=t.sectionTitles||[],t.sections.push({y:T.getVerticalPos(),height:0}),t.sectionTitles.push(e),this.sequenceItems.push(t)},"addSectionToLoop"),saveVerticalPos:f(function(){this.isLoopOverlap()&&(this.savedVerticalPos=this.verticalPos)},"saveVerticalPos"),resetVerticalPos:f(function(){this.isLoopOverlap()&&(this.verticalPos=this.savedVerticalPos)},"resetVerticalPos"),bumpVerticalPos:f(function(e){this.verticalPos=this.verticalPos+e,this.data.stopy=I.getMax(this.data.stopy,this.verticalPos)},"bumpVerticalPos"),getVerticalPos:f(function(){return this.verticalPos},"getVerticalPos"),getBounds:f(function(){return{bounds:this.data,models:this.models}},"getBounds")},gs=f(async function(e,t){T.bumpVerticalPos(l.boxMargin),t.height=l.boxMargin,t.starty=T.getVerticalPos();const n=ct();n.x=t.startx,n.y=t.starty,n.width=t.width||l.width,n.class="note";const s=e.append("g"),r=V.drawRect(s,n),i=Xt();i.x=t.startx,i.y=t.starty,i.width=n.width,i.dy="1em",i.text=t.message,i.class="noteText",i.fontFamily=l.noteFontFamily,i.fontSize=l.noteFontSize,i.fontWeight=l.noteFontWeight,i.anchor=l.noteAlign,i.textMargin=l.noteMargin,i.valign="center";const c=z(i.text)?await Dt(s,i):vt(s,i),h=Math.round(c.map(o=>(o._groups||o)[0][0].getBBox().height).reduce((o,a)=>o+a));r.attr("height",h+2*l.noteMargin),t.height+=h+2*l.noteMargin,T.bumpVerticalPos(h+2*l.noteMargin),t.stopy=t.starty+h+2*l.noteMargin,t.stopx=t.startx+n.width,T.insert(t.startx,t.starty,t.stopx,t.stopy),T.models.addNote(t)},"drawNote"),Et=f(e=>({fontFamily:e.messageFontFamily,fontSize:e.messageFontSize,fontWeight:e.messageFontWeight}),"messageFont"),mt=f(e=>({fontFamily:e.noteFontFamily,fontSize:e.noteFontSize,fontWeight:e.noteFontWeight}),"noteFont"),Gt=f(e=>({fontFamily:e.actorFontFamily,fontSize:e.actorFontSize,fontWeight:e.actorFontWeight}),"actorFont");async function de(e,t){T.bumpVerticalPos(10);const{startx:n,stopx:s,message:r}=t,i=I.splitBreaks(r).length,c=z(r),h=c?await _t(r,st()):F.calculateTextDimensions(r,Et(l));if(!c){const g=h.height/i;t.height+=g,T.bumpVerticalPos(g)}let o,a=h.height-10;const p=h.width;if(n===s){o=T.getVerticalPos()+a,l.rightAngles||(a+=l.boxMargin,o=T.getVerticalPos()+a),a+=30;const g=I.getMax(p/2,l.width/2);T.insert(n-g,T.getVerticalPos()-10+a,s+g,T.getVerticalPos()+30+a)}else a+=l.boxMargin,o=T.getVerticalPos()+a,T.insert(n,o-10,s,o);return T.bumpVerticalPos(a),t.height+=a,t.stopy=t.starty+t.height,T.insert(t.fromBounds,t.starty,t.toBounds,t.stopy),o}f(de,"boundMessage");var xs=f(async function(e,t,n,s){const{startx:r,stopx:i,starty:c,message:h,type:o,sequenceIndex:a,sequenceVisible:p}=t,g=F.calculateTextDimensions(h,Et(l)),x=Xt();x.x=r,x.y=c+10,x.width=i-r,x.class="messageText",x.dy="1em",x.text=h,x.fontFamily=l.messageFontFamily,x.fontSize=l.messageFontSize,x.fontWeight=l.messageFontWeight,x.anchor=l.messageAlign,x.valign="center",x.textMargin=l.wrapPadding,x.tspan=!1,z(x.text)?await Dt(e,x,{startx:r,stopx:i,starty:n}):vt(e,x);const y=g.width;let b;r===i?l.rightAngles?b=e.append("path").attr("d",`M ${r},${n} H ${r+I.getMax(l.width/2,y/2)} V ${n+25} H ${r}`):b=e.append("path").attr("d","M "+r+","+n+" C "+(r+60)+","+(n-10)+" "+(r+60)+","+(n+30)+" "+r+","+(n+20)):(b=e.append("line"),b.attr("x1",r),b.attr("y1",n),b.attr("x2",i),b.attr("y2",n)),o===s.db.LINETYPE.DOTTED||o===s.db.LINETYPE.DOTTED_CROSS||o===s.db.LINETYPE.DOTTED_POINT||o===s.db.LINETYPE.DOTTED_OPEN||o===s.db.LINETYPE.BIDIRECTIONAL_DOTTED?(b.style("stroke-dasharray","3, 3"),b.attr("class","messageLine1")):b.attr("class","messageLine0");let w="";l.arrowMarkerAbsolute&&(w=Oe(!0)),b.attr("stroke-width",2),b.attr("stroke","none"),b.style("fill","none"),(o===s.db.LINETYPE.SOLID||o===s.db.LINETYPE.DOTTED)&&b.attr("marker-end","url("+w+"#arrowhead)"),(o===s.db.LINETYPE.BIDIRECTIONAL_SOLID||o===s.db.LINETYPE.BIDIRECTIONAL_DOTTED)&&(b.attr("marker-start","url("+w+"#arrowhead)"),b.attr("marker-end","url("+w+"#arrowhead)")),(o===s.db.LINETYPE.SOLID_POINT||o===s.db.LINETYPE.DOTTED_POINT)&&b.attr("marker-end","url("+w+"#filled-head)"),(o===s.db.LINETYPE.SOLID_CROSS||o===s.db.LINETYPE.DOTTED_CROSS)&&b.attr("marker-end","url("+w+"#crosshead)"),(p||l.showSequenceNumbers)&&((o===s.db.LINETYPE.BIDIRECTIONAL_SOLID||o===s.db.LINETYPE.BIDIRECTIONAL_DOTTED)&&(rr&&(r=a.height),a.width+h.x>i&&(i=a.width+h.x)}return{maxHeight:r,maxWidth:i}},"drawActorsPopup"),ue=f(function(e){_e(l,e),e.fontFamily&&(l.actorFontFamily=l.noteFontFamily=l.messageFontFamily=e.fontFamily),e.fontSize&&(l.actorFontSize=l.noteFontSize=l.messageFontSize=e.fontSize),e.fontWeight&&(l.actorFontWeight=l.noteFontWeight=l.messageFontWeight=e.fontWeight)},"setConf"),Bt=f(function(e){return T.activations.filter(function(t){return t.actor===e})},"actorActivations"),ae=f(function(e,t){const n=t.get(e),s=Bt(e),r=s.reduce(function(c,h){return I.getMin(c,h.startx)},n.x+n.width/2-1),i=s.reduce(function(c,h){return I.getMax(c,h.stopx)},n.x+n.width/2+1);return[r,i]},"activationBounds");function nt(e,t,n,s,r){T.bumpVerticalPos(n);let i=s;if(t.id&&t.message&&e[t.id]){const c=e[t.id].width,h=Et(l);t.message=F.wrapLabel(`[${t.message}]`,c-2*l.wrapPadding,h),t.width=c,t.wrap=!0;const o=F.calculateTextDimensions(t.message,h),a=I.getMax(o.height,l.labelBoxHeight);i=s+a,Q.debug(`${a} - ${t.message}`)}r(t),T.bumpVerticalPos(i)}f(nt,"adjustLoopHeightForWrap");function ge(e,t,n,s,r,i,c){function h(p,g){p.x{E.add(_.from),E.add(_.to)}),b=b.filter(_=>E.has(_))}fs(a,p,g,b,0,w,!1);const C=await ms(w,p,M,s);V.insertArrowHead(a),V.insertArrowCrossHead(a),V.insertArrowFilledHead(a),V.insertSequenceNumber(a);function Y(E,_){const X=T.endActivation(E);X.starty+18>_&&(X.starty=_-6,_+=12),V.drawActivation(a,X,_,l,Bt(E.from).length),T.insert(X.startx,_-10,X.stopx,_)}f(Y,"activeEnd");let H=1,Z=1;const at=[],U=[];let G=0;for(const E of w){let _,X,tt;switch(E.type){case s.db.LINETYPE.NOTE:T.resetVerticalPos(),X=E.noteModel,await gs(a,X);break;case s.db.LINETYPE.ACTIVE_START:T.newActivation(E,a,p);break;case s.db.LINETYPE.ACTIVE_END:Y(E,T.getVerticalPos());break;case s.db.LINETYPE.LOOP_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R));break;case s.db.LINETYPE.LOOP_END:_=T.endLoop(),await V.drawLoop(a,_,"loop",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;case s.db.LINETYPE.RECT_START:nt(C,E,l.boxMargin,l.boxMargin,R=>T.newLoop(void 0,R.message));break;case s.db.LINETYPE.RECT_END:_=T.endLoop(),U.push(_),T.models.addLoop(_),T.bumpVerticalPos(_.stopy-T.getVerticalPos());break;case s.db.LINETYPE.OPT_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R));break;case s.db.LINETYPE.OPT_END:_=T.endLoop(),await V.drawLoop(a,_,"opt",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;case s.db.LINETYPE.ALT_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R));break;case s.db.LINETYPE.ALT_ELSE:nt(C,E,l.boxMargin+l.boxTextMargin,l.boxMargin,R=>T.addSectionToLoop(R));break;case s.db.LINETYPE.ALT_END:_=T.endLoop(),await V.drawLoop(a,_,"alt",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;case s.db.LINETYPE.PAR_START:case s.db.LINETYPE.PAR_OVER_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R)),T.saveVerticalPos();break;case s.db.LINETYPE.PAR_AND:nt(C,E,l.boxMargin+l.boxTextMargin,l.boxMargin,R=>T.addSectionToLoop(R));break;case s.db.LINETYPE.PAR_END:_=T.endLoop(),await V.drawLoop(a,_,"par",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;case s.db.LINETYPE.AUTONUMBER:H=E.message.start||H,Z=E.message.step||Z,E.message.visible?s.db.enableSequenceNumbers():s.db.disableSequenceNumbers();break;case s.db.LINETYPE.CRITICAL_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R));break;case s.db.LINETYPE.CRITICAL_OPTION:nt(C,E,l.boxMargin+l.boxTextMargin,l.boxMargin,R=>T.addSectionToLoop(R));break;case s.db.LINETYPE.CRITICAL_END:_=T.endLoop(),await V.drawLoop(a,_,"critical",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;case s.db.LINETYPE.BREAK_START:nt(C,E,l.boxMargin,l.boxMargin+l.boxTextMargin,R=>T.newLoop(R));break;case s.db.LINETYPE.BREAK_END:_=T.endLoop(),await V.drawLoop(a,_,"break",l),T.bumpVerticalPos(_.stopy-T.getVerticalPos()),T.models.addLoop(_);break;default:try{tt=E.msgModel,tt.starty=T.getVerticalPos(),tt.sequenceIndex=H,tt.sequenceVisible=s.db.showSequenceNumbers();const R=await de(a,tt);ge(E,tt,R,G,p,g,x),at.push({messageModel:tt,lineStartY:R}),T.models.addMessage(tt)}catch(R){Q.error("error while drawing message",R)}}[s.db.LINETYPE.SOLID_OPEN,s.db.LINETYPE.DOTTED_OPEN,s.db.LINETYPE.SOLID,s.db.LINETYPE.DOTTED,s.db.LINETYPE.SOLID_CROSS,s.db.LINETYPE.DOTTED_CROSS,s.db.LINETYPE.SOLID_POINT,s.db.LINETYPE.DOTTED_POINT,s.db.LINETYPE.BIDIRECTIONAL_SOLID,s.db.LINETYPE.BIDIRECTIONAL_DOTTED].includes(E.type)&&(H=H+Z),G++}Q.debug("createdActors",g),Q.debug("destroyedActors",x),await Kt(a,p,b,!1);for(const E of at)await xs(a,E.messageModel,E.lineStartY,s);l.mirrorActors&&await Kt(a,p,b,!0),U.forEach(E=>V.drawBackgroundRect(a,E)),ce(a,p,b,l);for(const E of T.models.boxes){E.height=T.getVerticalPos()-E.y,T.insert(E.x,E.y,E.x+E.width,E.height);const _=l.boxMargin*2;E.startx=E.x-_,E.starty=E.y-_*.25,E.stopx=E.startx+E.width+2*_,E.stopy=E.starty+E.height+_*.75,E.stroke="rgb(0,0,0, 0.5)",V.drawBox(a,E,l)}N&&T.bumpVerticalPos(l.boxMargin);const q=pe(a,p,b,o),{bounds:O}=T.getBounds();O.startx===void 0&&(O.startx=0),O.starty===void 0&&(O.starty=0),O.stopx===void 0&&(O.stopx=0),O.stopy===void 0&&(O.stopy=0);let $=O.stopy-O.starty;${const c=Et(l);let h=i.actorKeys.reduce((g,x)=>g+=e.get(x).width+(e.get(x).margin||0),0);const o=l.boxMargin*8;h+=o,h-=2*l.boxTextMargin,i.wrap&&(i.name=F.wrapLabel(i.name,h-2*l.wrapPadding,c));const a=F.calculateTextDimensions(i.name,c);r=I.getMax(a.height,r);const p=I.getMax(h,a.width+2*l.wrapPadding);if(i.margin=l.boxTextMargin,hi.textMaxHeight=r),I.getMax(s,l.height)}f(fe,"calculateActorMargins");var Es=f(async function(e,t,n){const s=t.get(e.from),r=t.get(e.to),i=s.x,c=r.x,h=e.wrap&&e.message;let o=z(e.message)?await _t(e.message,st()):F.calculateTextDimensions(h?F.wrapLabel(e.message,l.width,mt(l)):e.message,mt(l));const a={width:h?l.width:I.getMax(l.width,o.width+2*l.noteMargin),height:0,startx:s.x,stopx:0,starty:0,stopy:0,message:e.message};return e.placement===n.db.PLACEMENT.RIGHTOF?(a.width=h?I.getMax(l.width,o.width):I.getMax(s.width/2+r.width/2,o.width+2*l.noteMargin),a.startx=i+(s.width+l.actorMargin)/2):e.placement===n.db.PLACEMENT.LEFTOF?(a.width=h?I.getMax(l.width,o.width+2*l.noteMargin):I.getMax(s.width/2+r.width/2,o.width+2*l.noteMargin),a.startx=i-a.width+(s.width-l.actorMargin)/2):e.to===e.from?(o=F.calculateTextDimensions(h?F.wrapLabel(e.message,I.getMax(l.width,s.width),mt(l)):e.message,mt(l)),a.width=h?I.getMax(l.width,s.width):I.getMax(s.width,l.width,o.width+2*l.noteMargin),a.startx=i+(s.width-a.width)/2):(a.width=Math.abs(i+s.width/2-(c+r.width/2))+l.actorMargin,a.startx=i2,g=f(w=>h?-w:w,"adjustValue");e.from===e.to?a=o:(e.activate&&!p&&(a+=g(l.activationWidth/2-1)),[n.db.LINETYPE.SOLID_OPEN,n.db.LINETYPE.DOTTED_OPEN].includes(e.type)||(a+=g(3)),[n.db.LINETYPE.BIDIRECTIONAL_SOLID,n.db.LINETYPE.BIDIRECTIONAL_DOTTED].includes(e.type)&&(o-=g(3)));const x=[s,r,i,c],y=Math.abs(o-a);e.wrap&&e.message&&(e.message=F.wrapLabel(e.message,I.getMax(y+2*l.wrapPadding,l.width),Et(l)));const b=F.calculateTextDimensions(e.message,Et(l));return{width:I.getMax(e.wrap?0:b.width+2*l.wrapPadding,y+2*l.wrapPadding,l.width),height:0,startx:o,stopx:a,starty:0,stopy:0,message:e.message,type:e.type,wrap:e.wrap,fromBounds:Math.min.apply(null,x),toBounds:Math.max.apply(null,x)}},"buildMessageModel"),ms=f(async function(e,t,n,s){const r={},i=[];let c,h,o;for(const a of e){switch(a.type){case s.db.LINETYPE.LOOP_START:case s.db.LINETYPE.ALT_START:case s.db.LINETYPE.OPT_START:case s.db.LINETYPE.PAR_START:case s.db.LINETYPE.PAR_OVER_START:case s.db.LINETYPE.CRITICAL_START:case s.db.LINETYPE.BREAK_START:i.push({id:a.id,msg:a.message,from:Number.MAX_SAFE_INTEGER,to:Number.MIN_SAFE_INTEGER,width:0});break;case s.db.LINETYPE.ALT_ELSE:case s.db.LINETYPE.PAR_AND:case s.db.LINETYPE.CRITICAL_OPTION:a.message&&(c=i.pop(),r[c.id]=c,r[a.id]=c,i.push(c));break;case s.db.LINETYPE.LOOP_END:case s.db.LINETYPE.ALT_END:case s.db.LINETYPE.OPT_END:case s.db.LINETYPE.PAR_END:case s.db.LINETYPE.CRITICAL_END:case s.db.LINETYPE.BREAK_END:c=i.pop(),r[c.id]=c;break;case s.db.LINETYPE.ACTIVE_START:{const g=t.get(a.from?a.from:a.to.actor),x=Bt(a.from?a.from:a.to.actor).length,y=g.x+g.width/2+(x-1)*l.activationWidth/2,b={startx:y,stopx:y+l.activationWidth,actor:a.from,enabled:!0};T.activations.push(b)}break;case s.db.LINETYPE.ACTIVE_END:{const g=T.activations.map(x=>x.actor).lastIndexOf(a.from);T.activations.splice(g,1).splice(0,1)}break}a.placement!==void 0?(h=await Es(a,t,s),a.noteModel=h,i.forEach(g=>{c=g,c.from=I.getMin(c.from,h.startx),c.to=I.getMax(c.to,h.startx+h.width),c.width=I.getMax(c.width,Math.abs(c.from-c.to))-l.labelBoxWidth})):(o=bs(a,t,s),a.msgModel=o,o.startx&&o.stopx&&i.length>0&&i.forEach(g=>{if(c=g,o.startx===o.stopx){const x=t.get(a.from),y=t.get(a.to);c.from=I.getMin(x.x-o.width/2,x.x-x.width/2,c.from),c.to=I.getMax(y.x+o.width/2,y.x+x.width/2,c.to),c.width=I.getMax(c.width,Math.abs(c.to-c.from))-l.labelBoxWidth}else c.from=I.getMin(o.startx,c.from),c.to=I.getMax(o.stopx,c.to),c.width=I.getMax(c.width,o.width)-l.labelBoxWidth}))}return T.activations=[],Q.debug("Loop type widths:",r),r},"calculateLoopBounds"),ws={bounds:T,drawActors:Kt,drawActorsPopup:pe,setConf:ue,draw:ys},As={parser:Ve,get db(){return new qe},renderer:ws,styles:He,init:f(e=>{e.sequence||(e.sequence={}),e.wrap&&(e.sequence.wrap=e.wrap,Ie({sequence:{wrap:e.wrap}}))},"init")};export{As as diagram}; diff --git a/assets/chunks/stateDiagram-FKZM4ZOC.DbXCcajc.js b/assets/chunks/stateDiagram-FKZM4ZOC.DbXCcajc.js new file mode 100644 index 000000000..08fe7de4a --- /dev/null +++ b/assets/chunks/stateDiagram-FKZM4ZOC.DbXCcajc.js @@ -0,0 +1 @@ +import{s as R,a as W,S as N}from"./chunk-DI55MBZ5.DJepMPeO.js";import{_ as f,c as t,d as H,l as S,e as P,k as z,V as _,a1 as U,Y as C,u as F}from"./theme.kqgpP4eL.js";import{G as O}from"./graph.CD7z0KlM.js";import{l as J}from"./layout.CmsRkK9P.js";import"./chunk-55IACEB6.BKKqJU_2.js";import"./chunk-QN33PNHL.ChYgkhtD.js";import"./framework.CgT1UzWm.js";import"./baseUniq.BHxmztwl.js";import"./min.fO5GJb76.js";var X=f(e=>e.append("circle").attr("class","start-state").attr("r",t().state.sizeUnit).attr("cx",t().state.padding+t().state.sizeUnit).attr("cy",t().state.padding+t().state.sizeUnit),"drawStartState"),Y=f(e=>e.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",t().state.textHeight).attr("class","divider").attr("x2",t().state.textHeight*2).attr("y1",0).attr("y2",0),"drawDivider"),D=f((e,i)=>{const d=e.append("text").attr("x",2*t().state.padding).attr("y",t().state.textHeight+2*t().state.padding).attr("font-size",t().state.fontSize).attr("class","state-title").text(i.id),c=d.node().getBBox();return e.insert("rect",":first-child").attr("x",t().state.padding).attr("y",t().state.padding).attr("width",c.width+2*t().state.padding).attr("height",c.height+2*t().state.padding).attr("rx",t().state.radius),d},"drawSimpleState"),I=f((e,i)=>{const d=f(function(g,B,m){const E=g.append("tspan").attr("x",2*t().state.padding).text(B);m||E.attr("dy",t().state.textHeight)},"addTspan"),n=e.append("text").attr("x",2*t().state.padding).attr("y",t().state.textHeight+1.3*t().state.padding).attr("font-size",t().state.fontSize).attr("class","state-title").text(i.descriptions[0]).node().getBBox(),l=n.height,x=e.append("text").attr("x",t().state.padding).attr("y",l+t().state.padding*.4+t().state.dividerMargin+t().state.textHeight).attr("class","state-description");let a=!0,s=!0;i.descriptions.forEach(function(g){a||(d(x,g,s),s=!1),a=!1});const w=e.append("line").attr("x1",t().state.padding).attr("y1",t().state.padding+l+t().state.dividerMargin/2).attr("y2",t().state.padding+l+t().state.dividerMargin/2).attr("class","descr-divider"),p=x.node().getBBox(),o=Math.max(p.width,n.width);return w.attr("x2",o+3*t().state.padding),e.insert("rect",":first-child").attr("x",t().state.padding).attr("y",t().state.padding).attr("width",o+2*t().state.padding).attr("height",p.height+l+2*t().state.padding).attr("rx",t().state.radius),e},"drawDescrState"),$=f((e,i,d)=>{const c=t().state.padding,n=2*t().state.padding,l=e.node().getBBox(),x=l.width,a=l.x,s=e.append("text").attr("x",0).attr("y",t().state.titleShift).attr("font-size",t().state.fontSize).attr("class","state-title").text(i.id),p=s.node().getBBox().width+n;let o=Math.max(p,x);o===x&&(o=o+n);let g;const B=e.node().getBBox();i.doc,g=a-c,p>x&&(g=(x-o)/2+c),Math.abs(a-B.x)x&&(g=a-(p-x)/2);const m=1-t().state.textHeight;return e.insert("rect",":first-child").attr("x",g).attr("y",m).attr("class",d?"alt-composit":"composit").attr("width",o).attr("height",B.height+t().state.textHeight+t().state.titleShift+1).attr("rx","0"),s.attr("x",g+c),p<=x&&s.attr("x",a+(o-n)/2-p/2+c),e.insert("rect",":first-child").attr("x",g).attr("y",t().state.titleShift-t().state.textHeight-t().state.padding).attr("width",o).attr("height",t().state.textHeight*3).attr("rx",t().state.radius),e.insert("rect",":first-child").attr("x",g).attr("y",t().state.titleShift-t().state.textHeight-t().state.padding).attr("width",o).attr("height",B.height+3+2*t().state.textHeight).attr("rx",t().state.radius),e},"addTitleAndBox"),q=f(e=>(e.append("circle").attr("class","end-state-outer").attr("r",t().state.sizeUnit+t().state.miniPadding).attr("cx",t().state.padding+t().state.sizeUnit+t().state.miniPadding).attr("cy",t().state.padding+t().state.sizeUnit+t().state.miniPadding),e.append("circle").attr("class","end-state-inner").attr("r",t().state.sizeUnit).attr("cx",t().state.padding+t().state.sizeUnit+2).attr("cy",t().state.padding+t().state.sizeUnit+2)),"drawEndState"),V=f((e,i)=>{let d=t().state.forkWidth,c=t().state.forkHeight;if(i.parentId){let n=d;d=c,c=n}return e.append("rect").style("stroke","black").style("fill","black").attr("width",d).attr("height",c).attr("x",t().state.padding).attr("y",t().state.padding)},"drawForkJoinState"),Z=f((e,i,d,c)=>{let n=0;const l=c.append("text");l.style("text-anchor","start"),l.attr("class","noteText");let x=e.replace(/\r\n/g,"
");x=x.replace(/\n/g,"
");const a=x.split(z.lineBreakRegex);let s=1.25*t().state.noteMargin;for(const w of a){const p=w.trim();if(p.length>0){const o=l.append("tspan");if(o.text(p),s===0){const g=o.node().getBBox();s+=g.height}n+=s,o.attr("x",i+t().state.noteMargin),o.attr("y",d+n+1.25*t().state.noteMargin)}}return{textWidth:l.node().getBBox().width,textHeight:n}},"_drawLongText"),j=f((e,i)=>{i.attr("class","state-note");const d=i.append("rect").attr("x",0).attr("y",t().state.padding),c=i.append("g"),{textWidth:n,textHeight:l}=Z(e,0,0,c);return d.attr("height",l+2*t().state.noteMargin),d.attr("width",n+t().state.noteMargin*2),d},"drawNote"),L=f(function(e,i){const d=i.id,c={id:d,label:i.id,width:0,height:0},n=e.append("g").attr("id",d).attr("class","stateGroup");i.type==="start"&&X(n),i.type==="end"&&q(n),(i.type==="fork"||i.type==="join")&&V(n,i),i.type==="note"&&j(i.note.text,n),i.type==="divider"&&Y(n),i.type==="default"&&i.descriptions.length===0&&D(n,i),i.type==="default"&&i.descriptions.length>0&&I(n,i);const l=n.node().getBBox();return c.width=l.width+2*t().state.padding,c.height=l.height+2*t().state.padding,c},"drawState"),A=0,K=f(function(e,i,d){const c=f(function(s){switch(s){case N.relationType.AGGREGATION:return"aggregation";case N.relationType.EXTENSION:return"extension";case N.relationType.COMPOSITION:return"composition";case N.relationType.DEPENDENCY:return"dependency"}},"getRelationType");i.points=i.points.filter(s=>!Number.isNaN(s.y));const n=i.points,l=_().x(function(s){return s.x}).y(function(s){return s.y}).curve(U),x=e.append("path").attr("d",l(n)).attr("id","edge"+A).attr("class","transition");let a="";if(t().state.arrowMarkerAbsolute&&(a=C(!0)),x.attr("marker-end","url("+a+"#"+c(N.relationType.DEPENDENCY)+"End)"),d.title!==void 0){const s=e.append("g").attr("class","stateLabel"),{x:w,y:p}=F.calcLabelPosition(i.points),o=z.getRows(d.title);let g=0;const B=[];let m=0,E=0;for(let u=0;u<=o.length;u++){const h=s.append("text").attr("text-anchor","middle").text(o[u]).attr("x",w).attr("y",p+g),y=h.node().getBBox();m=Math.max(m,y.width),E=Math.min(E,y.x),S.info(y.x,w,p+g),g===0&&(g=h.node().getBBox().height,S.info("Title height",g,p)),B.push(h)}let k=g*o.length;if(o.length>1){const u=(o.length-1)*g*.5;B.forEach((h,y)=>h.attr("y",p+y*g-u)),k=g*o.length}const r=s.node().getBBox();s.insert("rect",":first-child").attr("class","box").attr("x",w-m/2-t().state.padding/2).attr("y",p-k/2-t().state.padding/2-3.5).attr("width",m+t().state.padding).attr("height",k+t().state.padding),S.info(r)}A++},"drawEdge"),b,T={},Q=f(function(){},"setConf"),tt=f(function(e){e.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"insertMarkers"),et=f(function(e,i,d,c){b=t().state;const n=t().securityLevel;let l;n==="sandbox"&&(l=H("#i"+i));const x=n==="sandbox"?H(l.nodes()[0].contentDocument.body):H("body"),a=n==="sandbox"?l.nodes()[0].contentDocument:document;S.debug("Rendering diagram "+e);const s=x.select(`[id='${i}']`);tt(s);const w=c.db.getRootDoc();G(w,s,void 0,!1,x,a,c);const p=b.padding,o=s.node().getBBox(),g=o.width+p*2,B=o.height+p*2,m=g*1.75;P(s,B,m,b.useMaxWidth),s.attr("viewBox",`${o.x-b.padding} ${o.y-b.padding} `+g+" "+B)},"draw"),at=f(e=>e?e.length*b.fontSizeFactor:1,"getLabelWidth"),G=f((e,i,d,c,n,l,x)=>{const a=new O({compound:!0,multigraph:!0});let s,w=!0;for(s=0;s{const y=h.parentElement;let v=0,M=0;y&&(y.parentElement&&(v=y.parentElement.getBBox().width),M=parseInt(y.getAttribute("data-x-shift"),10),Number.isNaN(M)&&(M=0)),h.setAttribute("x1",0-M+8),h.setAttribute("x2",v-M-8)})):S.debug("No Node "+r+": "+JSON.stringify(a.node(r)))});let E=m.getBBox();a.edges().forEach(function(r){r!==void 0&&a.edge(r)!==void 0&&(S.debug("Edge "+r.v+" -> "+r.w+": "+JSON.stringify(a.edge(r))),K(i,a.edge(r),a.edge(r).relation))}),E=m.getBBox();const k={id:d||"root",label:d||"root",width:0,height:0};return k.width=E.width+2*b.padding,k.height=E.height+2*b.padding,S.debug("Doc rendered",k,a),k},"renderDoc"),it={setConf:Q,draw:et},pt={parser:W,get db(){return new N(1)},renderer:it,styles:R,init:f(e=>{e.state||(e.state={}),e.state.arrowMarkerAbsolute=e.arrowMarkerAbsolute},"init")};export{pt as diagram}; diff --git a/assets/chunks/stateDiagram-v2-4FDKWEC3.zkSEVl0q.js b/assets/chunks/stateDiagram-v2-4FDKWEC3.zkSEVl0q.js new file mode 100644 index 000000000..6dfd3df7c --- /dev/null +++ b/assets/chunks/stateDiagram-v2-4FDKWEC3.zkSEVl0q.js @@ -0,0 +1 @@ +import{s as e,b as r,a,S as s}from"./chunk-DI55MBZ5.DJepMPeO.js";import{_ as i}from"./theme.kqgpP4eL.js";import"./chunk-55IACEB6.BKKqJU_2.js";import"./chunk-QN33PNHL.ChYgkhtD.js";import"./framework.CgT1UzWm.js";var p={parser:a,get db(){return new s(2)},renderer:r,styles:e,init:i(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{p as diagram}; diff --git a/assets/chunks/theme.kqgpP4eL.js b/assets/chunks/theme.kqgpP4eL.js new file mode 100644 index 000000000..e486be02c --- /dev/null +++ b/assets/chunks/theme.kqgpP4eL.js @@ -0,0 +1,256 @@ +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/chunks/VPLocalSearchBox.z2vlMY86.js","assets/chunks/framework.CgT1UzWm.js","assets/chunks/dagre-6UL2VRFP.Er--WOqv.js","assets/chunks/graph.CD7z0KlM.js","assets/chunks/baseUniq.BHxmztwl.js","assets/chunks/layout.CmsRkK9P.js","assets/chunks/min.fO5GJb76.js","assets/chunks/clone.BclZbdyg.js","assets/chunks/cose-bilkent-S5V4N54A.CZp12JBE.js","assets/chunks/cytoscape.esm.CyJtwmzi.js","assets/chunks/c4Diagram-YG6GDRKO.Cxb4MoHr.js","assets/chunks/chunk-TZMSLE5B.CN1RMadv.js","assets/chunks/flowDiagram-NV44I4VS.NRN3ub33.js","assets/chunks/chunk-FMBD7UC4.B39tdjdc.js","assets/chunks/chunk-55IACEB6.BKKqJU_2.js","assets/chunks/chunk-QN33PNHL.ChYgkhtD.js","assets/chunks/erDiagram-Q2GNP2WA.B8pTQkdS.js","assets/chunks/gitGraphDiagram-NY62KEGX.D-tkHlSx.js","assets/chunks/chunk-4BX2VUAB.B6a8mhSC.js","assets/chunks/chunk-QZHKN3VN.SQhQYWrL.js","assets/chunks/treemap-KMMF4GRG.CcUr4GSN.js","assets/chunks/ganttDiagram-JELNMOA3.CLnTOziW.js","assets/chunks/infoDiagram-WHAUD3N6.BJpHyd3M.js","assets/chunks/pieDiagram-ADFJNKIX.BdoKephD.js","assets/chunks/quadrantDiagram-AYHSOK5B.QCp9GEfl.js","assets/chunks/xychartDiagram-PRI3JC2R.CYHK3ubw.js","assets/chunks/requirementDiagram-UZGBJVZJ.CyKYuSjS.js","assets/chunks/sequenceDiagram-WL72ISMW.D-QuC8xB.js","assets/chunks/classDiagram-2ON5EDUG.BfaWfr0K.js","assets/chunks/chunk-B4BG7PRW.v_eLYYkV.js","assets/chunks/classDiagram-v2-WZHVMYZB.BfaWfr0K.js","assets/chunks/stateDiagram-FKZM4ZOC.DbXCcajc.js","assets/chunks/chunk-DI55MBZ5.DJepMPeO.js","assets/chunks/stateDiagram-v2-4FDKWEC3.zkSEVl0q.js","assets/chunks/journeyDiagram-XKPGCS4Q.DIPDU-n-.js","assets/chunks/timeline-definition-IT6M3QCI.tSV0dAf1.js","assets/chunks/mindmap-definition-VGOIOE7T.BtOuNEFY.js","assets/chunks/kanban-definition-3W4ZIXB7.DFxkbzml.js","assets/chunks/sankeyDiagram-TZEHDZUN.qimGZH9q.js","assets/chunks/diagram-S2PKOQOG.BvMBeI2b.js","assets/chunks/diagram-QEK2KX5R.CRLQ07ic.js","assets/chunks/blockDiagram-VD42YOAC.BZKpDMvR.js","assets/chunks/architectureDiagram-VXUJARFQ.CaSL3V8c.js","assets/chunks/diagram-PSM6KHXK.COu_zksi.js"])))=>i.map(i=>d[i]); +var Wb=Object.defineProperty;var Hb=(e,t,r)=>t in e?Wb(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var Lt=(e,t,r)=>Hb(e,typeof t!="symbol"?t+"":t,r);import{d as nt,c as A,r as H,n as At,o as S,a as fr,t as yt,b as mt,w as j,T as co,e as U,_ as ut,u as ho,i as Vb,f as Ub,g as Yc,h as rt,j as T,k as P,l as fi,m as Il,p as tt,q as Ne,s as uo,v as _e,x as gn,y as Gc,z as jb,A as Yb,F as Mt,B as Vt,C as wi,D as Bf,E as fo,G as dt,H as cr,I as Ef,J as Pr,K as Wr,L as po,M as Gb,N as ya,O as Ol,P as mn,Q as Ff,R as go,S as Xb,U as Zb,V as Rt,W as Pf,X as Df,Y as Kb,Z as Qb,$ as mo,a0 as Jb,a1 as tv,a2 as nr,a3 as ev,a4 as rv,a5 as ps}from"./framework.CgT1UzWm.js";const iv=nt({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(e){return(t,r)=>(S(),A("span",{class:At(["VPBadge",t.type])},[H(t.$slots,"default",{},()=>[fr(yt(t.text),1)])],2))}}),nv={key:0,class:"VPBackdrop"},av=nt({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(e){return(t,r)=>(S(),mt(co,{name:"fade"},{default:j(()=>[t.show?(S(),A("div",nv)):U("",!0)]),_:1}))}}),sv=ut(av,[["__scopeId","data-v-c79a1216"]]),Bt=ho;function ov(e,t){let r,i=!1;return()=>{r&&clearTimeout(r),i?r=setTimeout(e,t):(e(),(i=!0)&&setTimeout(()=>i=!1,t))}}function Rl(e){return e.startsWith("/")?e:`/${e}`}function Xc(e){const{pathname:t,search:r,hash:i,protocol:n}=new URL(e,"http://a.com");if(Vb(e)||e.startsWith("#")||!n.startsWith("http")||!Ub(t))return e;const{site:a}=Bt(),o=t.endsWith("/")||t.endsWith(".html")?e:e.replace(/(?:(^\.+)\/)?.*$/,`$1${t.replace(/(\.md)?$/,a.value.cleanUrls?"":".html")}${r}${i}`);return Yc(o)}function ba({correspondingLink:e=!1}={}){const{site:t,localeIndex:r,page:i,theme:n,hash:a}=Bt(),o=rt(()=>{var l,c;return{label:(l=t.value.locales[r.value])==null?void 0:l.label,link:((c=t.value.locales[r.value])==null?void 0:c.link)||(r.value==="root"?"/":`/${r.value}/`)}});return{localeLinks:rt(()=>Object.entries(t.value.locales).flatMap(([l,c])=>o.value.label===c.label?[]:{text:c.label,link:lv(c.link||(l==="root"?"/":`/${l}/`),n.value.i18nRouting!==!1&&e,i.value.relativePath.slice(o.value.link.length-1),!t.value.cleanUrls)+a.value})),currentLang:o}}function lv(e,t,r,i){return t?e.replace(/\/$/,"")+Rl(r.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,i?".html":"")):e}const cv={class:"NotFound"},hv={class:"code"},uv={class:"title"},dv={class:"quote"},fv={class:"action"},pv=["href","aria-label"],gv=nt({__name:"NotFound",setup(e){const{theme:t}=Bt(),{currentLang:r}=ba();return(i,n)=>{var a,o,s,l,c;return S(),A("div",cv,[T("p",hv,yt(((a=P(t).notFound)==null?void 0:a.code)??"404"),1),T("h1",uv,yt(((o=P(t).notFound)==null?void 0:o.title)??"PAGE NOT FOUND"),1),n[0]||(n[0]=T("div",{class:"divider"},null,-1)),T("blockquote",dv,yt(((s=P(t).notFound)==null?void 0:s.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),T("div",fv,[T("a",{class:"link",href:P(Yc)(P(r).link),"aria-label":((l=P(t).notFound)==null?void 0:l.linkLabel)??"go to home"},yt(((c=P(t).notFound)==null?void 0:c.linkText)??"Take me home"),9,pv)])])}}}),mv=ut(gv,[["__scopeId","data-v-d6be1790"]]);function If(e,t){if(Array.isArray(e))return Za(e);if(e==null)return[];t=Rl(t);const r=Object.keys(e).sort((n,a)=>a.split("/").length-n.split("/").length).find(n=>t.startsWith(Rl(n))),i=r?e[r]:[];return Array.isArray(i)?Za(i):Za(i.items,i.base)}function yv(e){const t=[];let r=0;for(const i in e){const n=e[i];if(n.items){r=t.push(n);continue}t[r]||t.push({items:[]}),t[r].items.push(n)}return t}function bv(e){const t=[];function r(i){for(const n of i)n.text&&n.link&&t.push({text:n.text,link:n.link,docFooterText:n.docFooterText}),n.items&&r(n.items)}return r(e),t}function Nl(e,t){return Array.isArray(t)?t.some(r=>Nl(e,r)):fi(e,t.link)?!0:t.items?Nl(e,t.items):!1}function Za(e,t){return[...e].map(r=>{const i={...r},n=i.base||t;return n&&i.link&&(i.link=n+i.link),i.items&&(i.items=Za(i.items,n)),i})}function Dr(){const{frontmatter:e,page:t,theme:r}=Bt(),i=Il("(min-width: 960px)"),n=tt(!1),a=rt(()=>{const g=r.value.sidebar,y=t.value.relativePath;return g?If(g,y):[]}),o=tt(a.value);Ne(a,(g,y)=>{JSON.stringify(g)!==JSON.stringify(y)&&(o.value=a.value)});const s=rt(()=>e.value.sidebar!==!1&&o.value.length>0&&e.value.layout!=="home"),l=rt(()=>c?e.value.aside==null?r.value.aside==="left":e.value.aside==="left":!1),c=rt(()=>e.value.layout==="home"?!1:e.value.aside!=null?!!e.value.aside:r.value.aside!==!1),h=rt(()=>s.value&&i.value),u=rt(()=>s.value?yv(o.value):[]);function d(){n.value=!0}function f(){n.value=!1}function p(){n.value?f():d()}return{isOpen:n,sidebar:o,sidebarGroups:u,hasSidebar:s,hasAside:c,leftAside:l,isSidebarEnabled:h,open:d,close:f,toggle:p}}function vv(e,t){let r;uo(()=>{r=e.value?document.activeElement:void 0}),_e(()=>{window.addEventListener("keyup",i)}),gn(()=>{window.removeEventListener("keyup",i)});function i(n){n.key==="Escape"&&e.value&&(t(),r==null||r.focus())}}function xv(e){const{page:t,hash:r}=Bt(),i=tt(!1),n=rt(()=>e.value.collapsed!=null),a=rt(()=>!!e.value.link),o=tt(!1),s=()=>{o.value=fi(t.value.relativePath,e.value.link)};Ne([t,e,r],s),_e(s);const l=rt(()=>o.value?!0:e.value.items?Nl(t.value.relativePath,e.value.items):!1),c=rt(()=>!!(e.value.items&&e.value.items.length));uo(()=>{i.value=!!(n.value&&e.value.collapsed)}),Gc(()=>{(o.value||l.value)&&(i.value=!1)});function h(){n.value&&(i.value=!i.value)}return{collapsed:i,collapsible:n,isLink:a,isActiveLink:o,hasActiveLink:l,hasChildren:c,toggle:h}}function _v(){const{hasSidebar:e}=Dr(),t=Il("(min-width: 960px)"),r=Il("(min-width: 1280px)");return{isAsideEnabled:rt(()=>!r.value&&!t.value?!1:e.value?r.value:t.value)}}const kv=/\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/,zl=[];function Of(e){return typeof e.outline=="object"&&!Array.isArray(e.outline)&&e.outline.label||e.outlineTitle||"On this page"}function Zc(e){const t=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(r=>r.id&&r.hasChildNodes()).map(r=>{const i=Number(r.tagName[1]);return{element:r,title:wv(r),link:"#"+r.id,level:i}});return Cv(t,e)}function wv(e){let t="";for(const r of e.childNodes)if(r.nodeType===1){if(kv.test(r.className))continue;t+=r.textContent}else r.nodeType===3&&(t+=r.textContent);return t.trim()}function Cv(e,t){if(t===!1)return[];const r=(typeof t=="object"&&!Array.isArray(t)?t.level:t)||2,[i,n]=typeof r=="number"?[r,r]:r==="deep"?[2,6]:r;return Mv(e,i,n)}function Sv(e,t){const{isAsideEnabled:r}=_v(),i=ov(a,100);let n=null;_e(()=>{requestAnimationFrame(a),window.addEventListener("scroll",i)}),jb(()=>{o(location.hash)}),gn(()=>{window.removeEventListener("scroll",i)});function a(){if(!r.value)return;const s=window.scrollY,l=window.innerHeight,c=document.body.offsetHeight,h=Math.abs(s+l-c)<1,u=zl.map(({element:f,link:p})=>({link:p,top:Tv(f)})).filter(({top:f})=>!Number.isNaN(f)).sort((f,p)=>f.top-p.top);if(!u.length){o(null);return}if(s<1){o(null);return}if(h){o(u[u.length-1].link);return}let d=null;for(const{link:f,top:p}of u){if(p>s+Yb()+4)break;d=f}o(d)}function o(s){n&&n.classList.remove("active"),s==null?n=null:n=e.value.querySelector(`a[href="${decodeURIComponent(s)}"]`);const l=n;l?(l.classList.add("active"),t.value.style.top=l.offsetTop+39+"px",t.value.style.opacity="1"):(t.value.style.top="33px",t.value.style.opacity="0")}}function Tv(e){let t=0;for(;e!==document.body;){if(e===null)return NaN;t+=e.offsetTop,e=e.offsetParent}return t}function Mv(e,t,r){zl.length=0;const i=[],n=[];return e.forEach(a=>{const o={...a,children:[]};let s=n[n.length-1];for(;s&&s.level>=o.level;)n.pop(),s=n[n.length-1];if(o.element.classList.contains("ignore-header")||s&&"shouldIgnore"in s){n.push({level:o.level,shouldIgnore:!0});return}o.level>r||o.level{const n=wi("VPDocOutlineItem",!0);return S(),A("ul",{class:At(["VPDocOutlineItem",r.root?"root":"nested"])},[(S(!0),A(Mt,null,Vt(r.headers,({children:a,link:o,title:s})=>(S(),A("li",null,[T("a",{class:"outline-link",href:o,onClick:t,title:s},yt(s),9,$v),a!=null&&a.length?(S(),mt(n,{key:0,headers:a},null,8,["headers"])):U("",!0)]))),256))],2)}}}),Rf=ut(Lv,[["__scopeId","data-v-b933a997"]]),Av={class:"content"},Bv={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},Ev=nt({__name:"VPDocAsideOutline",setup(e){const{frontmatter:t,theme:r}=Bt(),i=Bf([]);fo(()=>{i.value=Zc(t.value.outline??r.value.outline)});const n=tt(),a=tt();return Sv(n,a),(o,s)=>(S(),A("nav",{"aria-labelledby":"doc-outline-aria-label",class:At(["VPDocAsideOutline",{"has-outline":i.value.length>0}]),ref_key:"container",ref:n},[T("div",Av,[T("div",{class:"outline-marker",ref_key:"marker",ref:a},null,512),T("div",Bv,yt(P(Of)(P(r))),1),dt(Rf,{headers:i.value,root:!0},null,8,["headers"])])],2))}}),Fv=ut(Ev,[["__scopeId","data-v-a5bbad30"]]),Pv={class:"VPDocAsideCarbonAds"},Dv=nt({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(e){const t=()=>null;return(r,i)=>(S(),A("div",Pv,[dt(P(t),{"carbon-ads":r.carbonAds},null,8,["carbon-ads"])]))}}),Iv={class:"VPDocAside"},Ov=nt({__name:"VPDocAside",setup(e){const{theme:t}=Bt();return(r,i)=>(S(),A("div",Iv,[H(r.$slots,"aside-top",{},void 0,!0),H(r.$slots,"aside-outline-before",{},void 0,!0),dt(Fv),H(r.$slots,"aside-outline-after",{},void 0,!0),i[0]||(i[0]=T("div",{class:"spacer"},null,-1)),H(r.$slots,"aside-ads-before",{},void 0,!0),P(t).carbonAds?(S(),mt(Dv,{key:0,"carbon-ads":P(t).carbonAds},null,8,["carbon-ads"])):U("",!0),H(r.$slots,"aside-ads-after",{},void 0,!0),H(r.$slots,"aside-bottom",{},void 0,!0)]))}}),Rv=ut(Ov,[["__scopeId","data-v-3f215769"]]);function Nv(){const{theme:e,page:t}=Bt();return rt(()=>{const{text:r="Edit this page",pattern:i=""}=e.value.editLink||{};let n;return typeof i=="function"?n=i(t.value):n=i.replace(/:path/g,t.value.filePath),{url:n,text:r}})}function zv(){const{page:e,theme:t,frontmatter:r}=Bt();return rt(()=>{var c,h,u,d,f,p,g,y;const i=If(t.value.sidebar,e.value.relativePath),n=bv(i),a=qv(n,b=>b.link.replace(/[?#].*$/,"")),o=a.findIndex(b=>fi(e.value.relativePath,b.link)),s=((c=t.value.docFooter)==null?void 0:c.prev)===!1&&!r.value.prev||r.value.prev===!1,l=((h=t.value.docFooter)==null?void 0:h.next)===!1&&!r.value.next||r.value.next===!1;return{prev:s?void 0:{text:(typeof r.value.prev=="string"?r.value.prev:typeof r.value.prev=="object"?r.value.prev.text:void 0)??((u=a[o-1])==null?void 0:u.docFooterText)??((d=a[o-1])==null?void 0:d.text),link:(typeof r.value.prev=="object"?r.value.prev.link:void 0)??((f=a[o-1])==null?void 0:f.link)},next:l?void 0:{text:(typeof r.value.next=="string"?r.value.next:typeof r.value.next=="object"?r.value.next.text:void 0)??((p=a[o+1])==null?void 0:p.docFooterText)??((g=a[o+1])==null?void 0:g.text),link:(typeof r.value.next=="object"?r.value.next.link:void 0)??((y=a[o+1])==null?void 0:y.link)}}})}function qv(e,t){const r=new Set;return e.filter(i=>{const n=t(i);return r.has(n)?!1:r.add(n)})}const hr=nt({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(e){const t=e,r=rt(()=>t.tag??(t.href?"a":"span")),i=rt(()=>t.href&&Ef.test(t.href)||t.target==="_blank");return(n,a)=>(S(),mt(cr(r.value),{class:At(["VPLink",{link:n.href,"vp-external-link-icon":i.value,"no-icon":n.noIcon}]),href:n.href?P(Xc)(n.href):void 0,target:n.target??(i.value?"_blank":void 0),rel:n.rel??(i.value?"noreferrer":void 0)},{default:j(()=>[H(n.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Wv={class:"VPLastUpdated"},Hv=["datetime"],Vv=nt({__name:"VPDocFooterLastUpdated",setup(e){const{theme:t,page:r,lang:i}=Bt(),n=rt(()=>new Date(r.value.lastUpdated)),a=rt(()=>n.value.toISOString()),o=tt("");return _e(()=>{uo(()=>{var s,l,c;o.value=new Intl.DateTimeFormat((l=(s=t.value.lastUpdated)==null?void 0:s.formatOptions)!=null&&l.forceLocale?i.value:void 0,((c=t.value.lastUpdated)==null?void 0:c.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(n.value)})}),(s,l)=>{var c;return S(),A("p",Wv,[fr(yt(((c=P(t).lastUpdated)==null?void 0:c.text)||P(t).lastUpdatedText||"Last updated")+": ",1),T("time",{datetime:a.value},yt(o.value),9,Hv)])}}}),Uv=ut(Vv,[["__scopeId","data-v-e98dd255"]]),jv={key:0,class:"VPDocFooter"},Yv={key:0,class:"edit-info"},Gv={key:0,class:"edit-link"},Xv={key:1,class:"last-updated"},Zv={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Kv={class:"pager"},Qv=["innerHTML"],Jv=["innerHTML"],tx={class:"pager"},ex=["innerHTML"],rx=["innerHTML"],ix=nt({__name:"VPDocFooter",setup(e){const{theme:t,page:r,frontmatter:i}=Bt(),n=Nv(),a=zv(),o=rt(()=>t.value.editLink&&i.value.editLink!==!1),s=rt(()=>r.value.lastUpdated),l=rt(()=>o.value||s.value||a.value.prev||a.value.next);return(c,h)=>{var u,d,f,p;return l.value?(S(),A("footer",jv,[H(c.$slots,"doc-footer-before",{},void 0,!0),o.value||s.value?(S(),A("div",Yv,[o.value?(S(),A("div",Gv,[dt(hr,{class:"edit-link-button",href:P(n).url,"no-icon":!0},{default:j(()=>[h[0]||(h[0]=T("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),fr(" "+yt(P(n).text),1)]),_:1,__:[0]},8,["href"])])):U("",!0),s.value?(S(),A("div",Xv,[dt(Uv)])):U("",!0)])):U("",!0),(u=P(a).prev)!=null&&u.link||(d=P(a).next)!=null&&d.link?(S(),A("nav",Zv,[h[1]||(h[1]=T("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),T("div",Kv,[(f=P(a).prev)!=null&&f.link?(S(),mt(hr,{key:0,class:"pager-link prev",href:P(a).prev.link},{default:j(()=>{var g;return[T("span",{class:"desc",innerHTML:((g=P(t).docFooter)==null?void 0:g.prev)||"Previous page"},null,8,Qv),T("span",{class:"title",innerHTML:P(a).prev.text},null,8,Jv)]}),_:1},8,["href"])):U("",!0)]),T("div",tx,[(p=P(a).next)!=null&&p.link?(S(),mt(hr,{key:0,class:"pager-link next",href:P(a).next.link},{default:j(()=>{var g;return[T("span",{class:"desc",innerHTML:((g=P(t).docFooter)==null?void 0:g.next)||"Next page"},null,8,ex),T("span",{class:"title",innerHTML:P(a).next.text},null,8,rx)]}),_:1},8,["href"])):U("",!0)])])):U("",!0)])):U("",!0)}}}),nx=ut(ix,[["__scopeId","data-v-e257564d"]]),ax={class:"container"},sx={class:"aside-container"},ox={class:"aside-content"},lx={class:"content"},cx={class:"content-container"},hx={class:"main"},ux=nt({__name:"VPDoc",setup(e){const{theme:t}=Bt(),r=Pr(),{hasSidebar:i,hasAside:n,leftAside:a}=Dr(),o=rt(()=>r.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(s,l)=>{const c=wi("Content");return S(),A("div",{class:At(["VPDoc",{"has-sidebar":P(i),"has-aside":P(n)}])},[H(s.$slots,"doc-top",{},void 0,!0),T("div",ax,[P(n)?(S(),A("div",{key:0,class:At(["aside",{"left-aside":P(a)}])},[l[0]||(l[0]=T("div",{class:"aside-curtain"},null,-1)),T("div",sx,[T("div",ox,[dt(Rv,null,{"aside-top":j(()=>[H(s.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":j(()=>[H(s.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":j(()=>[H(s.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":j(()=>[H(s.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":j(()=>[H(s.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":j(()=>[H(s.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):U("",!0),T("div",lx,[T("div",cx,[H(s.$slots,"doc-before",{},void 0,!0),T("main",hx,[dt(c,{class:At(["vp-doc",[o.value,P(t).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),dt(nx,null,{"doc-footer-before":j(()=>[H(s.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),H(s.$slots,"doc-after",{},void 0,!0)])])]),H(s.$slots,"doc-bottom",{},void 0,!0)],2)}}}),dx=ut(ux,[["__scopeId","data-v-39a288b8"]]),fx=nt({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(e){const t=e,r=rt(()=>t.href&&Ef.test(t.href)),i=rt(()=>t.tag||(t.href?"a":"button"));return(n,a)=>(S(),mt(cr(i.value),{class:At(["VPButton",[n.size,n.theme]]),href:n.href?P(Xc)(n.href):void 0,target:t.target??(r.value?"_blank":void 0),rel:t.rel??(r.value?"noreferrer":void 0)},{default:j(()=>[fr(yt(n.text),1)]),_:1},8,["class","href","target","rel"]))}}),px=ut(fx,[["__scopeId","data-v-fa7799d5"]]),gx=["src","alt"],mx=nt({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(e){return(t,r)=>{const i=wi("VPImage",!0);return t.image?(S(),A(Mt,{key:0},[typeof t.image=="string"||"src"in t.image?(S(),A("img",Wr({key:0,class:"VPImage"},typeof t.image=="string"?t.$attrs:{...t.image,...t.$attrs},{src:P(Yc)(typeof t.image=="string"?t.image:t.image.src),alt:t.alt??(typeof t.image=="string"?"":t.image.alt||"")}),null,16,gx)):(S(),A(Mt,{key:1},[dt(i,Wr({class:"dark",image:t.image.dark,alt:t.image.alt},t.$attrs),null,16,["image","alt"]),dt(i,Wr({class:"light",image:t.image.light,alt:t.image.alt},t.$attrs),null,16,["image","alt"])],64))],64)):U("",!0)}}}),gs=ut(mx,[["__scopeId","data-v-8426fc1a"]]),yx={class:"container"},bx={class:"main"},vx={class:"heading"},xx=["innerHTML"],_x=["innerHTML"],kx=["innerHTML"],wx={key:0,class:"actions"},Cx={key:0,class:"image"},Sx={class:"image-container"},Tx=nt({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(e){const t=po("hero-image-slot-exists");return(r,i)=>(S(),A("div",{class:At(["VPHero",{"has-image":r.image||P(t)}])},[T("div",yx,[T("div",bx,[H(r.$slots,"home-hero-info-before",{},void 0,!0),H(r.$slots,"home-hero-info",{},()=>[T("h1",vx,[r.name?(S(),A("span",{key:0,innerHTML:r.name,class:"name clip"},null,8,xx)):U("",!0),r.text?(S(),A("span",{key:1,innerHTML:r.text,class:"text"},null,8,_x)):U("",!0)]),r.tagline?(S(),A("p",{key:0,innerHTML:r.tagline,class:"tagline"},null,8,kx)):U("",!0)],!0),H(r.$slots,"home-hero-info-after",{},void 0,!0),r.actions?(S(),A("div",wx,[(S(!0),A(Mt,null,Vt(r.actions,n=>(S(),A("div",{key:n.link,class:"action"},[dt(px,{tag:"a",size:"medium",theme:n.theme,text:n.text,href:n.link,target:n.target,rel:n.rel},null,8,["theme","text","href","target","rel"])]))),128))])):U("",!0),H(r.$slots,"home-hero-actions-after",{},void 0,!0)]),r.image||P(t)?(S(),A("div",Cx,[T("div",Sx,[i[0]||(i[0]=T("div",{class:"image-bg"},null,-1)),H(r.$slots,"home-hero-image",{},()=>[r.image?(S(),mt(gs,{key:0,class:"image-src",image:r.image},null,8,["image"])):U("",!0)],!0)])])):U("",!0)])],2))}}),Mx=ut(Tx,[["__scopeId","data-v-4f9c455b"]]),$x=nt({__name:"VPHomeHero",setup(e){const{frontmatter:t}=Bt();return(r,i)=>P(t).hero?(S(),mt(Mx,{key:0,class:"VPHomeHero",name:P(t).hero.name,text:P(t).hero.text,tagline:P(t).hero.tagline,image:P(t).hero.image,actions:P(t).hero.actions},{"home-hero-info-before":j(()=>[H(r.$slots,"home-hero-info-before")]),"home-hero-info":j(()=>[H(r.$slots,"home-hero-info")]),"home-hero-info-after":j(()=>[H(r.$slots,"home-hero-info-after")]),"home-hero-actions-after":j(()=>[H(r.$slots,"home-hero-actions-after")]),"home-hero-image":j(()=>[H(r.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):U("",!0)}}),Lx={class:"box"},Ax={key:0,class:"icon"},Bx=["innerHTML"],Ex=["innerHTML"],Fx=["innerHTML"],Px={key:4,class:"link-text"},Dx={class:"link-text-value"},Ix=nt({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(e){return(t,r)=>(S(),mt(hr,{class:"VPFeature",href:t.link,rel:t.rel,target:t.target,"no-icon":!0,tag:t.link?"a":"div"},{default:j(()=>[T("article",Lx,[typeof t.icon=="object"&&t.icon.wrap?(S(),A("div",Ax,[dt(gs,{image:t.icon,alt:t.icon.alt,height:t.icon.height||48,width:t.icon.width||48},null,8,["image","alt","height","width"])])):typeof t.icon=="object"?(S(),mt(gs,{key:1,image:t.icon,alt:t.icon.alt,height:t.icon.height||48,width:t.icon.width||48},null,8,["image","alt","height","width"])):t.icon?(S(),A("div",{key:2,class:"icon",innerHTML:t.icon},null,8,Bx)):U("",!0),T("h2",{class:"title",innerHTML:t.title},null,8,Ex),t.details?(S(),A("p",{key:3,class:"details",innerHTML:t.details},null,8,Fx)):U("",!0),t.linkText?(S(),A("div",Px,[T("p",Dx,[fr(yt(t.linkText)+" ",1),r[0]||(r[0]=T("span",{class:"vpi-arrow-right link-text-icon"},null,-1))])])):U("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),Ox=ut(Ix,[["__scopeId","data-v-a3976bdc"]]),Rx={key:0,class:"VPFeatures"},Nx={class:"container"},zx={class:"items"},qx=nt({__name:"VPFeatures",props:{features:{}},setup(e){const t=e,r=rt(()=>{const i=t.features.length;if(i){if(i===2)return"grid-2";if(i===3)return"grid-3";if(i%3===0)return"grid-6";if(i>3)return"grid-4"}else return});return(i,n)=>i.features?(S(),A("div",Rx,[T("div",Nx,[T("div",zx,[(S(!0),A(Mt,null,Vt(i.features,a=>(S(),A("div",{key:a.title,class:At(["item",[r.value]])},[dt(Ox,{icon:a.icon,title:a.title,details:a.details,link:a.link,"link-text":a.linkText,rel:a.rel,target:a.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):U("",!0)}}),Wx=ut(qx,[["__scopeId","data-v-a6181336"]]),Hx=nt({__name:"VPHomeFeatures",setup(e){const{frontmatter:t}=Bt();return(r,i)=>P(t).features?(S(),mt(Wx,{key:0,class:"VPHomeFeatures",features:P(t).features},null,8,["features"])):U("",!0)}}),Vx=nt({__name:"VPHomeContent",setup(e){const{width:t}=Gb({initialWidth:0,includeScrollbar:!1});return(r,i)=>(S(),A("div",{class:"vp-doc container",style:ya(P(t)?{"--vp-offset":`calc(50% - ${P(t)/2}px)`}:{})},[H(r.$slots,"default",{},void 0,!0)],4))}}),Ux=ut(Vx,[["__scopeId","data-v-8e2d4988"]]),jx=nt({__name:"VPHome",setup(e){const{frontmatter:t,theme:r}=Bt();return(i,n)=>{const a=wi("Content");return S(),A("div",{class:At(["VPHome",{"external-link-icon-enabled":P(r).externalLinkIcon}])},[H(i.$slots,"home-hero-before",{},void 0,!0),dt($x,null,{"home-hero-info-before":j(()=>[H(i.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":j(()=>[H(i.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":j(()=>[H(i.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":j(()=>[H(i.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":j(()=>[H(i.$slots,"home-hero-image",{},void 0,!0)]),_:3}),H(i.$slots,"home-hero-after",{},void 0,!0),H(i.$slots,"home-features-before",{},void 0,!0),dt(Hx),H(i.$slots,"home-features-after",{},void 0,!0),P(t).markdownStyles!==!1?(S(),mt(Ux,{key:0},{default:j(()=>[dt(a)]),_:1})):(S(),mt(a,{key:1}))],2)}}}),Yx=ut(jx,[["__scopeId","data-v-8b561e3d"]]),Gx={},Xx={class:"VPPage"};function Zx(e,t){const r=wi("Content");return S(),A("div",Xx,[H(e.$slots,"page-top"),dt(r),H(e.$slots,"page-bottom")])}const Kx=ut(Gx,[["render",Zx]]),Qx=nt({__name:"VPContent",setup(e){const{page:t,frontmatter:r}=Bt(),{hasSidebar:i}=Dr();return(n,a)=>(S(),A("div",{class:At(["VPContent",{"has-sidebar":P(i),"is-home":P(r).layout==="home"}]),id:"VPContent"},[P(t).isNotFound?H(n.$slots,"not-found",{key:0},()=>[dt(mv)],!0):P(r).layout==="page"?(S(),mt(Kx,{key:1},{"page-top":j(()=>[H(n.$slots,"page-top",{},void 0,!0)]),"page-bottom":j(()=>[H(n.$slots,"page-bottom",{},void 0,!0)]),_:3})):P(r).layout==="home"?(S(),mt(Yx,{key:2},{"home-hero-before":j(()=>[H(n.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":j(()=>[H(n.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":j(()=>[H(n.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":j(()=>[H(n.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":j(()=>[H(n.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":j(()=>[H(n.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":j(()=>[H(n.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":j(()=>[H(n.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":j(()=>[H(n.$slots,"home-features-after",{},void 0,!0)]),_:3})):P(r).layout&&P(r).layout!=="doc"?(S(),mt(cr(P(r).layout),{key:3})):(S(),mt(dx,{key:4},{"doc-top":j(()=>[H(n.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":j(()=>[H(n.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":j(()=>[H(n.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":j(()=>[H(n.$slots,"doc-before",{},void 0,!0)]),"doc-after":j(()=>[H(n.$slots,"doc-after",{},void 0,!0)]),"aside-top":j(()=>[H(n.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":j(()=>[H(n.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":j(()=>[H(n.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":j(()=>[H(n.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":j(()=>[H(n.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":j(()=>[H(n.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),Jx=ut(Qx,[["__scopeId","data-v-1428d186"]]),t2={class:"container"},e2=["innerHTML"],r2=["innerHTML"],i2=nt({__name:"VPFooter",setup(e){const{theme:t,frontmatter:r}=Bt(),{hasSidebar:i}=Dr();return(n,a)=>P(t).footer&&P(r).footer!==!1?(S(),A("footer",{key:0,class:At(["VPFooter",{"has-sidebar":P(i)}])},[T("div",t2,[P(t).footer.message?(S(),A("p",{key:0,class:"message",innerHTML:P(t).footer.message},null,8,e2)):U("",!0),P(t).footer.copyright?(S(),A("p",{key:1,class:"copyright",innerHTML:P(t).footer.copyright},null,8,r2)):U("",!0)])],2)):U("",!0)}}),n2=ut(i2,[["__scopeId","data-v-e315a0ad"]]);function a2(){const{theme:e,frontmatter:t}=Bt(),r=Bf([]),i=rt(()=>r.value.length>0);return fo(()=>{r.value=Zc(t.value.outline??e.value.outline)}),{headers:r,hasLocalNav:i}}const s2={class:"menu-text"},o2={class:"header"},l2={class:"outline"},c2=nt({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(e){const t=e,{theme:r}=Bt(),i=tt(!1),n=tt(0),a=tt(),o=tt();function s(u){var d;(d=a.value)!=null&&d.contains(u.target)||(i.value=!1)}Ne(i,u=>{if(u){document.addEventListener("click",s);return}document.removeEventListener("click",s)}),Ol("Escape",()=>{i.value=!1}),fo(()=>{i.value=!1});function l(){i.value=!i.value,n.value=window.innerHeight+Math.min(window.scrollY-t.navHeight,0)}function c(u){u.target.classList.contains("outline-link")&&(o.value&&(o.value.style.transition="none"),mn(()=>{i.value=!1}))}function h(){i.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(u,d)=>(S(),A("div",{class:"VPLocalNavOutlineDropdown",style:ya({"--vp-vh":n.value+"px"}),ref_key:"main",ref:a},[u.headers.length>0?(S(),A("button",{key:0,onClick:l,class:At({open:i.value})},[T("span",s2,yt(P(Of)(P(r))),1),d[0]||(d[0]=T("span",{class:"vpi-chevron-right icon"},null,-1))],2)):(S(),A("button",{key:1,onClick:h},yt(P(r).returnToTopLabel||"Return to top"),1)),dt(co,{name:"flyout"},{default:j(()=>[i.value?(S(),A("div",{key:0,ref_key:"items",ref:o,class:"items",onClick:c},[T("div",o2,[T("a",{class:"top-link",href:"#",onClick:h},yt(P(r).returnToTopLabel||"Return to top"),1)]),T("div",l2,[dt(Rf,{headers:u.headers},null,8,["headers"])])],512)):U("",!0)]),_:1})],4))}}),h2=ut(c2,[["__scopeId","data-v-8a42e2b4"]]),u2={class:"container"},d2=["aria-expanded"],f2={class:"menu-text"},p2=nt({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(e){const{theme:t,frontmatter:r}=Bt(),{hasSidebar:i}=Dr(),{headers:n}=a2(),{y:a}=Ff(),o=tt(0);_e(()=>{o.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),fo(()=>{n.value=Zc(r.value.outline??t.value.outline)});const s=rt(()=>n.value.length===0),l=rt(()=>s.value&&!i.value),c=rt(()=>({VPLocalNav:!0,"has-sidebar":i.value,empty:s.value,fixed:l.value}));return(h,u)=>P(r).layout!=="home"&&(!l.value||P(a)>=o.value)?(S(),A("div",{key:0,class:At(c.value)},[T("div",u2,[P(i)?(S(),A("button",{key:0,class:"menu","aria-expanded":h.open,"aria-controls":"VPSidebarNav",onClick:u[0]||(u[0]=d=>h.$emit("open-menu"))},[u[1]||(u[1]=T("span",{class:"vpi-align-left menu-icon"},null,-1)),T("span",f2,yt(P(t).sidebarMenuLabel||"Menu"),1)],8,d2)):U("",!0),dt(h2,{headers:P(n),navHeight:o.value},null,8,["headers","navHeight"])])],2)):U("",!0)}}),g2=ut(p2,[["__scopeId","data-v-a6f0e41e"]]);function m2(){const e=tt(!1);function t(){e.value=!0,window.addEventListener("resize",n)}function r(){e.value=!1,window.removeEventListener("resize",n)}function i(){e.value?r():t()}function n(){window.outerWidth>=768&&r()}const a=Pr();return Ne(()=>a.path,r),{isScreenOpen:e,openScreen:t,closeScreen:r,toggleScreen:i}}const y2={},b2={class:"VPSwitch",type:"button",role:"switch"},v2={class:"check"},x2={key:0,class:"icon"};function _2(e,t){return S(),A("button",b2,[T("span",v2,[e.$slots.default?(S(),A("span",x2,[H(e.$slots,"default",{},void 0,!0)])):U("",!0)])])}const k2=ut(y2,[["render",_2],["__scopeId","data-v-1d5665e3"]]),w2=nt({__name:"VPSwitchAppearance",setup(e){const{isDark:t,theme:r}=Bt(),i=po("toggle-appearance",()=>{t.value=!t.value}),n=tt("");return Gc(()=>{n.value=t.value?r.value.lightModeSwitchTitle||"Switch to light theme":r.value.darkModeSwitchTitle||"Switch to dark theme"}),(a,o)=>(S(),mt(k2,{title:n.value,class:"VPSwitchAppearance","aria-checked":P(t),onClick:P(i)},{default:j(()=>o[0]||(o[0]=[T("span",{class:"vpi-sun sun"},null,-1),T("span",{class:"vpi-moon moon"},null,-1)])),_:1,__:[0]},8,["title","aria-checked","onClick"]))}}),Kc=ut(w2,[["__scopeId","data-v-5337faa4"]]),C2={key:0,class:"VPNavBarAppearance"},S2=nt({__name:"VPNavBarAppearance",setup(e){const{site:t}=Bt();return(r,i)=>P(t).appearance&&P(t).appearance!=="force-dark"&&P(t).appearance!=="force-auto"?(S(),A("div",C2,[dt(Kc)])):U("",!0)}}),T2=ut(S2,[["__scopeId","data-v-6c893767"]]),Qc=tt();let Nf=!1,el=0;function M2(e){const t=tt(!1);if(go){!Nf&&$2(),el++;const r=Ne(Qc,i=>{var n,a,o;i===e.el.value||(n=e.el.value)!=null&&n.contains(i)?(t.value=!0,(a=e.onFocus)==null||a.call(e)):(t.value=!1,(o=e.onBlur)==null||o.call(e))});gn(()=>{r(),el--,el||L2()})}return Xb(t)}function $2(){document.addEventListener("focusin",zf),Nf=!0,Qc.value=document.activeElement}function L2(){document.removeEventListener("focusin",zf)}function zf(){Qc.value=document.activeElement}const A2={class:"VPMenuLink"},B2=["innerHTML"],E2=nt({__name:"VPMenuLink",props:{item:{}},setup(e){const{page:t}=Bt();return(r,i)=>(S(),A("div",A2,[dt(hr,{class:At({active:P(fi)(P(t).relativePath,r.item.activeMatch||r.item.link,!!r.item.activeMatch)}),href:r.item.link,target:r.item.target,rel:r.item.rel,"no-icon":r.item.noIcon},{default:j(()=>[T("span",{innerHTML:r.item.text},null,8,B2)]),_:1},8,["class","href","target","rel","no-icon"])]))}}),yo=ut(E2,[["__scopeId","data-v-35975db6"]]),F2={class:"VPMenuGroup"},P2={key:0,class:"title"},D2=nt({__name:"VPMenuGroup",props:{text:{},items:{}},setup(e){return(t,r)=>(S(),A("div",F2,[t.text?(S(),A("p",P2,yt(t.text),1)):U("",!0),(S(!0),A(Mt,null,Vt(t.items,i=>(S(),A(Mt,null,["link"in i?(S(),mt(yo,{key:0,item:i},null,8,["item"])):U("",!0)],64))),256))]))}}),I2=ut(D2,[["__scopeId","data-v-69e747b5"]]),O2={class:"VPMenu"},R2={key:0,class:"items"},N2=nt({__name:"VPMenu",props:{items:{}},setup(e){return(t,r)=>(S(),A("div",O2,[t.items?(S(),A("div",R2,[(S(!0),A(Mt,null,Vt(t.items,i=>(S(),A(Mt,{key:JSON.stringify(i)},["link"in i?(S(),mt(yo,{key:0,item:i},null,8,["item"])):"component"in i?(S(),mt(cr(i.component),Wr({key:1,ref_for:!0},i.props),null,16)):(S(),mt(I2,{key:2,text:i.text,items:i.items},null,8,["text","items"]))],64))),128))])):U("",!0),H(t.$slots,"default",{},void 0,!0)]))}}),z2=ut(N2,[["__scopeId","data-v-b98bc113"]]),q2=["aria-expanded","aria-label"],W2={key:0,class:"text"},H2=["innerHTML"],V2={key:1,class:"vpi-more-horizontal icon"},U2={class:"menu"},j2=nt({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(e){const t=tt(!1),r=tt();M2({el:r,onBlur:i});function i(){t.value=!1}return(n,a)=>(S(),A("div",{class:"VPFlyout",ref_key:"el",ref:r,onMouseenter:a[1]||(a[1]=o=>t.value=!0),onMouseleave:a[2]||(a[2]=o=>t.value=!1)},[T("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":t.value,"aria-label":n.label,onClick:a[0]||(a[0]=o=>t.value=!t.value)},[n.button||n.icon?(S(),A("span",W2,[n.icon?(S(),A("span",{key:0,class:At([n.icon,"option-icon"])},null,2)):U("",!0),n.button?(S(),A("span",{key:1,innerHTML:n.button},null,8,H2)):U("",!0),a[3]||(a[3]=T("span",{class:"vpi-chevron-down text-icon"},null,-1))])):(S(),A("span",V2))],8,q2),T("div",U2,[dt(z2,{items:n.items},{default:j(()=>[H(n.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),Jc=ut(j2,[["__scopeId","data-v-cf11d7a2"]]),Y2=["href","aria-label","innerHTML"],G2=nt({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(e){const t=e,r=tt();_e(async()=>{var a;await mn();const n=(a=r.value)==null?void 0:a.children[0];n instanceof HTMLElement&&n.className.startsWith("vpi-social-")&&(getComputedStyle(n).maskImage||getComputedStyle(n).webkitMaskImage)==="none"&&n.style.setProperty("--icon",`url('https://api.iconify.design/simple-icons/${t.icon}.svg')`)});const i=rt(()=>typeof t.icon=="object"?t.icon.svg:``);return(n,a)=>(S(),A("a",{ref_key:"el",ref:r,class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:i.value},null,8,Y2))}}),X2=ut(G2,[["__scopeId","data-v-bd121fe5"]]),Z2={class:"VPSocialLinks"},K2=nt({__name:"VPSocialLinks",props:{links:{}},setup(e){return(t,r)=>(S(),A("div",Z2,[(S(!0),A(Mt,null,Vt(t.links,({link:i,icon:n,ariaLabel:a})=>(S(),mt(X2,{key:i,icon:n,link:i,ariaLabel:a},null,8,["icon","link","ariaLabel"]))),128))]))}}),th=ut(K2,[["__scopeId","data-v-7bc22406"]]),Q2={key:0,class:"group translations"},J2={class:"trans-title"},t_={key:1,class:"group"},e_={class:"item appearance"},r_={class:"label"},i_={class:"appearance-action"},n_={key:2,class:"group"},a_={class:"item social-links"},s_=nt({__name:"VPNavBarExtra",setup(e){const{site:t,theme:r}=Bt(),{localeLinks:i,currentLang:n}=ba({correspondingLink:!0}),a=rt(()=>i.value.length&&n.value.label||t.value.appearance||r.value.socialLinks);return(o,s)=>a.value?(S(),mt(Jc,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:j(()=>[P(i).length&&P(n).label?(S(),A("div",Q2,[T("p",J2,yt(P(n).label),1),(S(!0),A(Mt,null,Vt(P(i),l=>(S(),mt(yo,{key:l.link,item:l},null,8,["item"]))),128))])):U("",!0),P(t).appearance&&P(t).appearance!=="force-dark"&&P(t).appearance!=="force-auto"?(S(),A("div",t_,[T("div",e_,[T("p",r_,yt(P(r).darkModeSwitchLabel||"Appearance"),1),T("div",i_,[dt(Kc)])])])):U("",!0),P(r).socialLinks?(S(),A("div",n_,[T("div",a_,[dt(th,{class:"social-links-list",links:P(r).socialLinks},null,8,["links"])])])):U("",!0)]),_:1})):U("",!0)}}),o_=ut(s_,[["__scopeId","data-v-bb2aa2f0"]]),l_=["aria-expanded"],c_=nt({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(e){return(t,r)=>(S(),A("button",{type:"button",class:At(["VPNavBarHamburger",{active:t.active}]),"aria-label":"mobile navigation","aria-expanded":t.active,"aria-controls":"VPNavScreen",onClick:r[0]||(r[0]=i=>t.$emit("click"))},r[1]||(r[1]=[T("span",{class:"container"},[T("span",{class:"top"}),T("span",{class:"middle"}),T("span",{class:"bottom"})],-1)]),10,l_))}}),h_=ut(c_,[["__scopeId","data-v-e5dd9c1c"]]),u_=["innerHTML"],d_=nt({__name:"VPNavBarMenuLink",props:{item:{}},setup(e){const{page:t}=Bt();return(r,i)=>(S(),mt(hr,{class:At({VPNavBarMenuLink:!0,active:P(fi)(P(t).relativePath,r.item.activeMatch||r.item.link,!!r.item.activeMatch)}),href:r.item.link,target:r.item.target,rel:r.item.rel,"no-icon":r.item.noIcon,tabindex:"0"},{default:j(()=>[T("span",{innerHTML:r.item.text},null,8,u_)]),_:1},8,["class","href","target","rel","no-icon"]))}}),f_=ut(d_,[["__scopeId","data-v-e56f3d57"]]),p_=nt({__name:"VPNavBarMenuGroup",props:{item:{}},setup(e){const t=e,{page:r}=Bt(),i=a=>"component"in a?!1:"link"in a?fi(r.value.relativePath,a.link,!!t.item.activeMatch):a.items.some(i),n=rt(()=>i(t.item));return(a,o)=>(S(),mt(Jc,{class:At({VPNavBarMenuGroup:!0,active:P(fi)(P(r).relativePath,a.item.activeMatch,!!a.item.activeMatch)||n.value}),button:a.item.text,items:a.item.items},null,8,["class","button","items"]))}}),g_={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},m_=nt({__name:"VPNavBarMenu",setup(e){const{theme:t}=Bt();return(r,i)=>P(t).nav?(S(),A("nav",g_,[i[0]||(i[0]=T("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),(S(!0),A(Mt,null,Vt(P(t).nav,n=>(S(),A(Mt,{key:JSON.stringify(n)},["link"in n?(S(),mt(f_,{key:0,item:n},null,8,["item"])):"component"in n?(S(),mt(cr(n.component),Wr({key:1,ref_for:!0},n.props),null,16)):(S(),mt(p_,{key:2,item:n},null,8,["item"]))],64))),128))])):U("",!0)}}),y_=ut(m_,[["__scopeId","data-v-dc692963"]]);function b_(e){const{localeIndex:t,theme:r}=Bt();function i(n){var p,g,y;const a=n.split("."),o=(p=r.value.search)==null?void 0:p.options,s=o&&typeof o=="object",l=s&&((y=(g=o.locales)==null?void 0:g[t.value])==null?void 0:y.translations)||null,c=s&&o.translations||null;let h=l,u=c,d=e;const f=a.pop();for(const b of a){let x=null;const _=d==null?void 0:d[b];_&&(x=d=_);const w=u==null?void 0:u[b];w&&(x=u=w);const C=h==null?void 0:h[b];C&&(x=h=C),_||(d=x),w||(u=x),C||(h=x)}return(h==null?void 0:h[f])??(u==null?void 0:u[f])??(d==null?void 0:d[f])??""}return i}const v_=["aria-label"],x_={class:"DocSearch-Button-Container"},__={class:"DocSearch-Button-Placeholder"},wu=nt({__name:"VPNavBarSearchButton",setup(e){const r=b_({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(i,n)=>(S(),A("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":P(r)("button.buttonAriaLabel")},[T("span",x_,[n[0]||(n[0]=T("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1)),T("span",__,yt(P(r)("button.buttonText")),1)]),n[1]||(n[1]=T("span",{class:"DocSearch-Button-Keys"},[T("kbd",{class:"DocSearch-Button-Key"}),T("kbd",{class:"DocSearch-Button-Key"},"K")],-1))],8,v_))}}),k_={class:"VPNavBarSearch"},w_={id:"local-search"},C_={key:1,id:"docsearch"},S_=nt({__name:"VPNavBarSearch",setup(e){const t=Zb(()=>Rt(()=>import("./VPLocalSearchBox.z2vlMY86.js"),__vite__mapDeps([0,1]))),r=()=>null,{theme:i}=Bt(),n=tt(!1),a=tt(!1);_e(()=>{});function o(){n.value||(n.value=!0,setTimeout(s,16))}function s(){const u=new Event("keydown");u.key="k",u.metaKey=!0,window.dispatchEvent(u),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||s()},16)}function l(u){const d=u.target,f=d.tagName;return d.isContentEditable||f==="INPUT"||f==="SELECT"||f==="TEXTAREA"}const c=tt(!1);Ol("k",u=>{(u.ctrlKey||u.metaKey)&&(u.preventDefault(),c.value=!0)}),Ol("/",u=>{l(u)||(u.preventDefault(),c.value=!0)});const h="local";return(u,d)=>{var f;return S(),A("div",k_,[P(h)==="local"?(S(),A(Mt,{key:0},[c.value?(S(),mt(P(t),{key:0,onClose:d[0]||(d[0]=p=>c.value=!1)})):U("",!0),T("div",w_,[dt(wu,{onClick:d[1]||(d[1]=p=>c.value=!0)})])],64)):P(h)==="algolia"?(S(),A(Mt,{key:1},[n.value?(S(),mt(P(r),{key:0,algolia:((f=P(i).search)==null?void 0:f.options)??P(i).algolia,onVnodeBeforeMount:d[2]||(d[2]=p=>a.value=!0)},null,8,["algolia"])):U("",!0),a.value?U("",!0):(S(),A("div",C_,[dt(wu,{onClick:o})]))],64)):U("",!0)])}}}),T_=nt({__name:"VPNavBarSocialLinks",setup(e){const{theme:t}=Bt();return(r,i)=>P(t).socialLinks?(S(),mt(th,{key:0,class:"VPNavBarSocialLinks",links:P(t).socialLinks},null,8,["links"])):U("",!0)}}),M_=ut(T_,[["__scopeId","data-v-0394ad82"]]),$_=["href","rel","target"],L_=["innerHTML"],A_={key:2},B_=nt({__name:"VPNavBarTitle",setup(e){const{site:t,theme:r}=Bt(),{hasSidebar:i}=Dr(),{currentLang:n}=ba(),a=rt(()=>{var l;return typeof r.value.logoLink=="string"?r.value.logoLink:(l=r.value.logoLink)==null?void 0:l.link}),o=rt(()=>{var l;return typeof r.value.logoLink=="string"||(l=r.value.logoLink)==null?void 0:l.rel}),s=rt(()=>{var l;return typeof r.value.logoLink=="string"||(l=r.value.logoLink)==null?void 0:l.target});return(l,c)=>(S(),A("div",{class:At(["VPNavBarTitle",{"has-sidebar":P(i)}])},[T("a",{class:"title",href:a.value??P(Xc)(P(n).link),rel:o.value,target:s.value},[H(l.$slots,"nav-bar-title-before",{},void 0,!0),P(r).logo?(S(),mt(gs,{key:0,class:"logo",image:P(r).logo},null,8,["image"])):U("",!0),P(r).siteTitle?(S(),A("span",{key:1,innerHTML:P(r).siteTitle},null,8,L_)):P(r).siteTitle===void 0?(S(),A("span",A_,yt(P(t).title),1)):U("",!0),H(l.$slots,"nav-bar-title-after",{},void 0,!0)],8,$_)],2))}}),E_=ut(B_,[["__scopeId","data-v-1168a8e4"]]),F_={class:"items"},P_={class:"title"},D_=nt({__name:"VPNavBarTranslations",setup(e){const{theme:t}=Bt(),{localeLinks:r,currentLang:i}=ba({correspondingLink:!0});return(n,a)=>P(r).length&&P(i).label?(S(),mt(Jc,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:P(t).langMenuLabel||"Change language"},{default:j(()=>[T("div",F_,[T("p",P_,yt(P(i).label),1),(S(!0),A(Mt,null,Vt(P(r),o=>(S(),mt(yo,{key:o.link,item:o},null,8,["item"]))),128))])]),_:1},8,["label"])):U("",!0)}}),I_=ut(D_,[["__scopeId","data-v-88af2de4"]]),O_={class:"wrapper"},R_={class:"container"},N_={class:"title"},z_={class:"content"},q_={class:"content-body"},W_=nt({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(e){const t=e,{y:r}=Ff(),{hasSidebar:i}=Dr(),{frontmatter:n}=Bt(),a=tt({});return Gc(()=>{a.value={"has-sidebar":i.value,home:n.value.layout==="home",top:r.value===0,"screen-open":t.isScreenOpen}}),(o,s)=>(S(),A("div",{class:At(["VPNavBar",a.value])},[T("div",O_,[T("div",R_,[T("div",N_,[dt(E_,null,{"nav-bar-title-before":j(()=>[H(o.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":j(()=>[H(o.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),T("div",z_,[T("div",q_,[H(o.$slots,"nav-bar-content-before",{},void 0,!0),dt(S_,{class:"search"}),dt(y_,{class:"menu"}),dt(I_,{class:"translations"}),dt(T2,{class:"appearance"}),dt(M_,{class:"social-links"}),dt(o_,{class:"extra"}),H(o.$slots,"nav-bar-content-after",{},void 0,!0),dt(h_,{class:"hamburger",active:o.isScreenOpen,onClick:s[0]||(s[0]=l=>o.$emit("toggle-screen"))},null,8,["active"])])])])]),s[1]||(s[1]=T("div",{class:"divider"},[T("div",{class:"divider-line"})],-1))],2))}}),H_=ut(W_,[["__scopeId","data-v-6aa21345"]]),V_={key:0,class:"VPNavScreenAppearance"},U_={class:"text"},j_=nt({__name:"VPNavScreenAppearance",setup(e){const{site:t,theme:r}=Bt();return(i,n)=>P(t).appearance&&P(t).appearance!=="force-dark"&&P(t).appearance!=="force-auto"?(S(),A("div",V_,[T("p",U_,yt(P(r).darkModeSwitchLabel||"Appearance"),1),dt(Kc)])):U("",!0)}}),Y_=ut(j_,[["__scopeId","data-v-b44890b2"]]),G_=["innerHTML"],X_=nt({__name:"VPNavScreenMenuLink",props:{item:{}},setup(e){const t=po("close-screen");return(r,i)=>(S(),mt(hr,{class:"VPNavScreenMenuLink",href:r.item.link,target:r.item.target,rel:r.item.rel,"no-icon":r.item.noIcon,onClick:P(t)},{default:j(()=>[T("span",{innerHTML:r.item.text},null,8,G_)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),Z_=ut(X_,[["__scopeId","data-v-df37e6dd"]]),K_=["innerHTML"],Q_=nt({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(e){const t=po("close-screen");return(r,i)=>(S(),mt(hr,{class:"VPNavScreenMenuGroupLink",href:r.item.link,target:r.item.target,rel:r.item.rel,"no-icon":r.item.noIcon,onClick:P(t)},{default:j(()=>[T("span",{innerHTML:r.item.text},null,8,K_)]),_:1},8,["href","target","rel","no-icon","onClick"]))}}),qf=ut(Q_,[["__scopeId","data-v-3e9c20e4"]]),J_={class:"VPNavScreenMenuGroupSection"},tk={key:0,class:"title"},ek=nt({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(e){return(t,r)=>(S(),A("div",J_,[t.text?(S(),A("p",tk,yt(t.text),1)):U("",!0),(S(!0),A(Mt,null,Vt(t.items,i=>(S(),mt(qf,{key:i.text,item:i},null,8,["item"]))),128))]))}}),rk=ut(ek,[["__scopeId","data-v-8133b170"]]),ik=["aria-controls","aria-expanded"],nk=["innerHTML"],ak=["id"],sk={key:0,class:"item"},ok={key:1,class:"item"},lk={key:2,class:"group"},ck=nt({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(e){const t=e,r=tt(!1),i=rt(()=>`NavScreenGroup-${t.text.replace(" ","-").toLowerCase()}`);function n(){r.value=!r.value}return(a,o)=>(S(),A("div",{class:At(["VPNavScreenMenuGroup",{open:r.value}])},[T("button",{class:"button","aria-controls":i.value,"aria-expanded":r.value,onClick:n},[T("span",{class:"button-text",innerHTML:a.text},null,8,nk),o[0]||(o[0]=T("span",{class:"vpi-plus button-icon"},null,-1))],8,ik),T("div",{id:i.value,class:"items"},[(S(!0),A(Mt,null,Vt(a.items,s=>(S(),A(Mt,{key:JSON.stringify(s)},["link"in s?(S(),A("div",sk,[dt(qf,{item:s},null,8,["item"])])):"component"in s?(S(),A("div",ok,[(S(),mt(cr(s.component),Wr({ref_for:!0},s.props,{"screen-menu":""}),null,16))])):(S(),A("div",lk,[dt(rk,{text:s.text,items:s.items},null,8,["text","items"])]))],64))),128))],8,ak)],2))}}),hk=ut(ck,[["__scopeId","data-v-b9ab8c58"]]),uk={key:0,class:"VPNavScreenMenu"},dk=nt({__name:"VPNavScreenMenu",setup(e){const{theme:t}=Bt();return(r,i)=>P(t).nav?(S(),A("nav",uk,[(S(!0),A(Mt,null,Vt(P(t).nav,n=>(S(),A(Mt,{key:JSON.stringify(n)},["link"in n?(S(),mt(Z_,{key:0,item:n},null,8,["item"])):"component"in n?(S(),mt(cr(n.component),Wr({key:1,ref_for:!0},n.props,{"screen-menu":""}),null,16)):(S(),mt(hk,{key:2,text:n.text||"",items:n.items},null,8,["text","items"]))],64))),128))])):U("",!0)}}),fk=nt({__name:"VPNavScreenSocialLinks",setup(e){const{theme:t}=Bt();return(r,i)=>P(t).socialLinks?(S(),mt(th,{key:0,class:"VPNavScreenSocialLinks",links:P(t).socialLinks},null,8,["links"])):U("",!0)}}),pk={class:"list"},gk=nt({__name:"VPNavScreenTranslations",setup(e){const{localeLinks:t,currentLang:r}=ba({correspondingLink:!0}),i=tt(!1);function n(){i.value=!i.value}return(a,o)=>P(t).length&&P(r).label?(S(),A("div",{key:0,class:At(["VPNavScreenTranslations",{open:i.value}])},[T("button",{class:"title",onClick:n},[o[0]||(o[0]=T("span",{class:"vpi-languages icon lang"},null,-1)),fr(" "+yt(P(r).label)+" ",1),o[1]||(o[1]=T("span",{class:"vpi-chevron-down icon chevron"},null,-1))]),T("ul",pk,[(S(!0),A(Mt,null,Vt(P(t),s=>(S(),A("li",{key:s.link,class:"item"},[dt(hr,{class:"link",href:s.link},{default:j(()=>[fr(yt(s.text),1)]),_:2},1032,["href"])]))),128))])],2)):U("",!0)}}),mk=ut(gk,[["__scopeId","data-v-858fe1a4"]]),yk={class:"container"},bk=nt({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(e){const t=tt(null),r=Pf(go?document.body:null);return(i,n)=>(S(),mt(co,{name:"fade",onEnter:n[0]||(n[0]=a=>r.value=!0),onAfterLeave:n[1]||(n[1]=a=>r.value=!1)},{default:j(()=>[i.open?(S(),A("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:t,id:"VPNavScreen"},[T("div",yk,[H(i.$slots,"nav-screen-content-before",{},void 0,!0),dt(dk,{class:"menu"}),dt(mk,{class:"translations"}),dt(Y_,{class:"appearance"}),dt(fk,{class:"social-links"}),H(i.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):U("",!0)]),_:3}))}}),vk=ut(bk,[["__scopeId","data-v-f2779853"]]),xk={key:0,class:"VPNav"},_k=nt({__name:"VPNav",setup(e){const{isScreenOpen:t,closeScreen:r,toggleScreen:i}=m2(),{frontmatter:n}=Bt(),a=rt(()=>n.value.navbar!==!1);return Df("close-screen",r),uo(()=>{go&&document.documentElement.classList.toggle("hide-nav",!a.value)}),(o,s)=>a.value?(S(),A("header",xk,[dt(H_,{"is-screen-open":P(t),onToggleScreen:P(i)},{"nav-bar-title-before":j(()=>[H(o.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":j(()=>[H(o.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":j(()=>[H(o.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":j(()=>[H(o.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),dt(vk,{open:P(t)},{"nav-screen-content-before":j(()=>[H(o.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":j(()=>[H(o.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):U("",!0)}}),kk=ut(_k,[["__scopeId","data-v-ae24b3ad"]]),wk=["role","tabindex"],Ck={key:1,class:"items"},Sk=nt({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(e){const t=e,{collapsed:r,collapsible:i,isLink:n,isActiveLink:a,hasActiveLink:o,hasChildren:s,toggle:l}=xv(rt(()=>t.item)),c=rt(()=>s.value?"section":"div"),h=rt(()=>n.value?"a":"div"),u=rt(()=>s.value?t.depth+2===7?"p":`h${t.depth+2}`:"p"),d=rt(()=>n.value?void 0:"button"),f=rt(()=>[[`level-${t.depth}`],{collapsible:i.value},{collapsed:r.value},{"is-link":n.value},{"is-active":a.value},{"has-active":o.value}]);function p(y){"key"in y&&y.key!=="Enter"||!t.item.link&&l()}function g(){t.item.link&&l()}return(y,b)=>{const x=wi("VPSidebarItem",!0);return S(),mt(cr(c.value),{class:At(["VPSidebarItem",f.value])},{default:j(()=>[y.item.text?(S(),A("div",Wr({key:0,class:"item",role:d.value},Kb(y.item.items?{click:p,keydown:p}:{},!0),{tabindex:y.item.items&&0}),[b[1]||(b[1]=T("div",{class:"indicator"},null,-1)),y.item.link?(S(),mt(hr,{key:0,tag:h.value,class:"link",href:y.item.link,rel:y.item.rel,target:y.item.target},{default:j(()=>[(S(),mt(cr(u.value),{class:"text",innerHTML:y.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(S(),mt(cr(u.value),{key:1,class:"text",innerHTML:y.item.text},null,8,["innerHTML"])),y.item.collapsed!=null&&y.item.items&&y.item.items.length?(S(),A("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:g,onKeydown:Qb(g,["enter"]),tabindex:"0"},b[0]||(b[0]=[T("span",{class:"vpi-chevron-right caret-icon"},null,-1)]),32)):U("",!0)],16,wk)):U("",!0),y.item.items&&y.item.items.length?(S(),A("div",Ck,[y.depth<5?(S(!0),A(Mt,{key:0},Vt(y.item.items,_=>(S(),mt(x,{key:_.text,item:_,depth:y.depth+1},null,8,["item","depth"]))),128)):U("",!0)])):U("",!0)]),_:1},8,["class"])}}}),Tk=ut(Sk,[["__scopeId","data-v-b3fd67f8"]]),Mk=nt({__name:"VPSidebarGroup",props:{items:{}},setup(e){const t=tt(!0);let r=null;return _e(()=>{r=setTimeout(()=>{r=null,t.value=!1},300)}),mo(()=>{r!=null&&(clearTimeout(r),r=null)}),(i,n)=>(S(!0),A(Mt,null,Vt(i.items,a=>(S(),A("div",{key:a.text,class:At(["group",{"no-transition":t.value}])},[dt(Tk,{item:a,depth:0},null,8,["item"])],2))),128))}}),$k=ut(Mk,[["__scopeId","data-v-c40bc020"]]),Lk={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Ak=nt({__name:"VPSidebar",props:{open:{type:Boolean}},setup(e){const{sidebarGroups:t,hasSidebar:r}=Dr(),i=e,n=tt(null),a=Pf(go?document.body:null);Ne([i,n],()=>{var s;i.open?(a.value=!0,(s=n.value)==null||s.focus()):a.value=!1},{immediate:!0,flush:"post"});const o=tt(0);return Ne(t,()=>{o.value+=1},{deep:!0}),(s,l)=>P(r)?(S(),A("aside",{key:0,class:At(["VPSidebar",{open:s.open}]),ref_key:"navEl",ref:n,onClick:l[0]||(l[0]=Jb(()=>{},["stop"]))},[l[2]||(l[2]=T("div",{class:"curtain"},null,-1)),T("nav",Lk,[l[1]||(l[1]=T("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),H(s.$slots,"sidebar-nav-before",{},void 0,!0),(S(),mt($k,{items:P(t),key:o.value},null,8,["items"])),H(s.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):U("",!0)}}),Bk=ut(Ak,[["__scopeId","data-v-319d5ca6"]]),Ek=nt({__name:"VPSkipLink",setup(e){const{theme:t}=Bt(),r=Pr(),i=tt();Ne(()=>r.path,()=>i.value.focus());function n({target:a}){const o=document.getElementById(decodeURIComponent(a.hash).slice(1));if(o){const s=()=>{o.removeAttribute("tabindex"),o.removeEventListener("blur",s)};o.setAttribute("tabindex","-1"),o.addEventListener("blur",s),o.focus(),window.scrollTo(0,0)}}return(a,o)=>(S(),A(Mt,null,[T("span",{ref_key:"backToTop",ref:i,tabindex:"-1"},null,512),T("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n},yt(P(t).skipToContentLabel||"Skip to content"),1)],64))}}),Fk=ut(Ek,[["__scopeId","data-v-0b0ada53"]]),Pk=nt({__name:"Layout",setup(e){const{isOpen:t,open:r,close:i}=Dr(),n=Pr();Ne(()=>n.path,i),vv(t,i);const{frontmatter:a}=Bt(),o=tv(),s=rt(()=>!!o["home-hero-image"]);return Df("hero-image-slot-exists",s),(l,c)=>{const h=wi("Content");return P(a).layout!==!1?(S(),A("div",{key:0,class:At(["Layout",P(a).pageClass])},[H(l.$slots,"layout-top",{},void 0,!0),dt(Fk),dt(sv,{class:"backdrop",show:P(t),onClick:P(i)},null,8,["show","onClick"]),dt(kk,null,{"nav-bar-title-before":j(()=>[H(l.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":j(()=>[H(l.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":j(()=>[H(l.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":j(()=>[H(l.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":j(()=>[H(l.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":j(()=>[H(l.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),dt(g2,{open:P(t),onOpenMenu:P(r)},null,8,["open","onOpenMenu"]),dt(Bk,{open:P(t)},{"sidebar-nav-before":j(()=>[H(l.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":j(()=>[H(l.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),dt(Jx,null,{"page-top":j(()=>[H(l.$slots,"page-top",{},void 0,!0)]),"page-bottom":j(()=>[H(l.$slots,"page-bottom",{},void 0,!0)]),"not-found":j(()=>[H(l.$slots,"not-found",{},void 0,!0)]),"home-hero-before":j(()=>[H(l.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":j(()=>[H(l.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":j(()=>[H(l.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":j(()=>[H(l.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":j(()=>[H(l.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":j(()=>[H(l.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":j(()=>[H(l.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":j(()=>[H(l.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":j(()=>[H(l.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":j(()=>[H(l.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":j(()=>[H(l.$slots,"doc-before",{},void 0,!0)]),"doc-after":j(()=>[H(l.$slots,"doc-after",{},void 0,!0)]),"doc-top":j(()=>[H(l.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":j(()=>[H(l.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":j(()=>[H(l.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":j(()=>[H(l.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":j(()=>[H(l.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":j(()=>[H(l.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":j(()=>[H(l.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":j(()=>[H(l.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),dt(n2),H(l.$slots,"layout-bottom",{},void 0,!0)],2)):(S(),mt(h,{key:1}))}}}),Dk=ut(Pk,[["__scopeId","data-v-5d98c3a5"]]),Cu={Layout:Dk,enhanceApp:({app:e})=>{e.component("Badge",iv)}};var Su={name:"mermaid",version:"11.12.2",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",type:"module",module:"./dist/mermaid.core.mjs",types:"./dist/mermaid.d.ts",exports:{".":{types:"./dist/mermaid.d.ts",import:"./dist/mermaid.core.mjs",default:"./dist/mermaid.core.mjs"},"./*":"./*"},keywords:["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph","mindmap","packet diagram","c4 diagram","er diagram","pie chart","pie diagram","quadrant chart","requirement diagram","graph"],scripts:{clean:"rimraf dist",dev:"pnpm -w dev","docs:code":"typedoc src/defaultConfig.ts src/config.ts src/mermaid.ts && prettier --write ./src/docs/config/setup","docs:build":"rimraf ../../docs && pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts","docs:verify":"pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts --verify","docs:pre:vitepress":"pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts","docs:build:vitepress":"pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing","docs:dev":'pnpm docs:pre:vitepress && concurrently "pnpm --filter ./src/vitepress dev" "tsx scripts/docs.cli.mts --watch --vitepress"',"docs:dev:docker":'pnpm docs:pre:vitepress && concurrently "pnpm --filter ./src/vitepress dev:docker" "tsx scripts/docs.cli.mts --watch --vitepress"',"docs:serve":"pnpm docs:build:vitepress && vitepress serve src/vitepress","docs:spellcheck":'cspell "src/docs/**/*.md"',"docs:release-version":"tsx scripts/update-release-version.mts","docs:verify-version":"tsx scripts/update-release-version.mts --verify","types:build-config":"tsx scripts/create-types-from-json-schema.mts","types:verify-config":"tsx scripts/create-types-from-json-schema.mts --verify",checkCircle:"npx madge --circular ./src",prepublishOnly:"pnpm docs:verify-version"},repository:{type:"git",url:"https://github.com/mermaid-js/mermaid"},author:"Knut Sveidqvist",license:"MIT",standard:{ignore:["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],globals:["page"]},dependencies:{"@braintree/sanitize-url":"^7.1.1","@iconify/utils":"^3.0.1","@mermaid-js/parser":"workspace:^","@types/d3":"^7.4.3",cytoscape:"^3.29.3","cytoscape-cose-bilkent":"^4.1.0","cytoscape-fcose":"^2.2.0",d3:"^7.9.0","d3-sankey":"^0.12.3","dagre-d3-es":"7.0.13",dayjs:"^1.11.18",dompurify:"^3.2.5",katex:"^0.16.22",khroma:"^2.1.0","lodash-es":"^4.17.21",marked:"^16.2.1",roughjs:"^4.6.6",stylis:"^4.3.6","ts-dedent":"^2.2.0",uuid:"^11.1.0"},devDependencies:{"@adobe/jsonschema2md":"^8.0.5","@iconify/types":"^2.0.0","@types/cytoscape":"^3.21.9","@types/cytoscape-fcose":"^2.2.4","@types/d3-sankey":"^0.12.4","@types/d3-scale":"^4.0.9","@types/d3-scale-chromatic":"^3.1.0","@types/d3-selection":"^3.0.11","@types/d3-shape":"^3.1.7","@types/jsdom":"^21.1.7","@types/katex":"^0.16.7","@types/lodash-es":"^4.17.12","@types/micromatch":"^4.0.9","@types/stylis":"^4.2.7","@types/uuid":"^10.0.0",ajv:"^8.17.1",canvas:"^3.1.2",chokidar:"3.6.0",concurrently:"^9.1.2","csstree-validator":"^4.0.1",globby:"^14.1.0",jison:"^0.4.18","js-base64":"^3.7.8",jsdom:"^26.1.0","json-schema-to-typescript":"^15.0.4",micromatch:"^4.0.8","path-browserify":"^1.0.1",prettier:"^3.5.3",remark:"^15.0.1","remark-frontmatter":"^5.0.0","remark-gfm":"^4.0.1",rimraf:"^6.0.1","start-server-and-test":"^2.0.13","type-fest":"^4.35.0",typedoc:"^0.28.12","typedoc-plugin-markdown":"^4.8.1",typescript:"~5.7.3","unist-util-flatmap":"^1.0.0","unist-util-visit":"^5.0.0",vitepress:"^1.6.4","vitepress-plugin-search":"1.0.4-alpha.22"},files:["dist/","README.md"],publishConfig:{access:"public"}},Ik=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Ok(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var Wf={exports:{}};(function(e,t){(function(r,i){e.exports=i()})(Ik,function(){var r=1e3,i=6e4,n=36e5,a="millisecond",o="second",s="minute",l="hour",c="day",h="week",u="month",d="quarter",f="year",p="date",g="Invalid Date",y=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,b=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,x={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(L){var B=["th","st","nd","rd"],F=L%100;return"["+L+(B[(F-20)%10]||B[F]||B[0])+"]"}},_=function(L,B,F){var R=String(L);return!R||R.length>=B?L:""+Array(B+1-R.length).join(F)+L},w={s:_,z:function(L){var B=-L.utcOffset(),F=Math.abs(B),R=Math.floor(F/60),I=F%60;return(B<=0?"+":"-")+_(R,2,"0")+":"+_(I,2,"0")},m:function L(B,F){if(B.date()1)return L(Z[0])}else{var J=B.name;v[J]=B,I=J}return!R&&I&&(C=I),I||!R&&C},W=function(L,B){if($(L))return L.clone();var F=typeof B=="object"?B:{};return F.date=L,F.args=arguments,new N(F)},O=w;O.l=z,O.i=$,O.w=function(L,B){return W(L,{locale:B.$L,utc:B.$u,x:B.$x,$offset:B.$offset})};var N=function(){function L(F){this.$L=z(F.locale,null,!0),this.parse(F),this.$x=this.$x||F.x||{},this[k]=!0}var B=L.prototype;return B.parse=function(F){this.$d=function(R){var I=R.date,X=R.utc;if(I===null)return new Date(NaN);if(O.u(I))return new Date;if(I instanceof Date)return new Date(I);if(typeof I=="string"&&!/Z$/i.test(I)){var Z=I.match(y);if(Z){var J=Z[2]-1||0,Tt=(Z[7]||"0").substring(0,3);return X?new Date(Date.UTC(Z[1],J,Z[3]||1,Z[4]||0,Z[5]||0,Z[6]||0,Tt)):new Date(Z[1],J,Z[3]||1,Z[4]||0,Z[5]||0,Z[6]||0,Tt)}}return new Date(I)}(F),this.init()},B.init=function(){var F=this.$d;this.$y=F.getFullYear(),this.$M=F.getMonth(),this.$D=F.getDate(),this.$W=F.getDay(),this.$H=F.getHours(),this.$m=F.getMinutes(),this.$s=F.getSeconds(),this.$ms=F.getMilliseconds()},B.$utils=function(){return O},B.isValid=function(){return this.$d.toString()!==g},B.isSame=function(F,R){var I=W(F);return this.startOf(R)<=I&&I<=this.endOf(R)},B.isAfter=function(F,R){return W(F)Hf(e,"name",{value:t,configurable:!0}),zk=(e,t)=>{for(var r in t)Hf(e,r,{get:t[r],enumerable:!0})},_r={trace:0,debug:1,info:2,warn:3,error:4,fatal:5},V={trace:m((...e)=>{},"trace"),debug:m((...e)=>{},"debug"),info:m((...e)=>{},"info"),warn:m((...e)=>{},"warn"),error:m((...e)=>{},"error"),fatal:m((...e)=>{},"fatal")},eh=m(function(e="fatal"){let t=_r.fatal;typeof e=="string"?e.toLowerCase()in _r&&(t=_r[e]):typeof e=="number"&&(t=e),V.trace=()=>{},V.debug=()=>{},V.info=()=>{},V.warn=()=>{},V.error=()=>{},V.fatal=()=>{},t<=_r.fatal&&(V.fatal=console.error?console.error.bind(console,Oe("FATAL"),"color: orange"):console.log.bind(console,"\x1B[35m",Oe("FATAL"))),t<=_r.error&&(V.error=console.error?console.error.bind(console,Oe("ERROR"),"color: orange"):console.log.bind(console,"\x1B[31m",Oe("ERROR"))),t<=_r.warn&&(V.warn=console.warn?console.warn.bind(console,Oe("WARN"),"color: orange"):console.log.bind(console,"\x1B[33m",Oe("WARN"))),t<=_r.info&&(V.info=console.info?console.info.bind(console,Oe("INFO"),"color: lightblue"):console.log.bind(console,"\x1B[34m",Oe("INFO"))),t<=_r.debug&&(V.debug=console.debug?console.debug.bind(console,Oe("DEBUG"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Oe("DEBUG"))),t<=_r.trace&&(V.trace=console.debug?console.debug.bind(console,Oe("TRACE"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Oe("TRACE")))},"setLogLevel"),Oe=m(e=>`%c${Nk().format("ss.SSS")} : ${e} : `,"format");const Ka={min:{r:0,g:0,b:0,s:0,l:0,a:0},max:{r:255,g:255,b:255,h:360,s:100,l:100,a:1},clamp:{r:e=>e>=255?255:e<0?0:e,g:e=>e>=255?255:e<0?0:e,b:e=>e>=255?255:e<0?0:e,h:e=>e%360,s:e=>e>=100?100:e<0?0:e,l:e=>e>=100?100:e<0?0:e,a:e=>e>=1?1:e<0?0:e},toLinear:e=>{const t=e/255;return e>.03928?Math.pow((t+.055)/1.055,2.4):t/12.92},hue2rgb:(e,t,r)=>(r<0&&(r+=1),r>1&&(r-=1),r<1/6?e+(t-e)*6*r:r<1/2?t:r<2/3?e+(t-e)*(2/3-r)*6:e),hsl2rgb:({h:e,s:t,l:r},i)=>{if(!t)return r*2.55;e/=360,t/=100,r/=100;const n=r<.5?r*(1+t):r+t-r*t,a=2*r-n;switch(i){case"r":return Ka.hue2rgb(a,n,e+1/3)*255;case"g":return Ka.hue2rgb(a,n,e)*255;case"b":return Ka.hue2rgb(a,n,e-1/3)*255}},rgb2hsl:({r:e,g:t,b:r},i)=>{e/=255,t/=255,r/=255;const n=Math.max(e,t,r),a=Math.min(e,t,r),o=(n+a)/2;if(i==="l")return o*100;if(n===a)return 0;const s=n-a,l=o>.5?s/(2-n-a):s/(n+a);if(i==="s")return l*100;switch(n){case e:return((t-r)/s+(tt>r?Math.min(t,Math.max(r,e)):Math.min(r,Math.max(t,e)),round:e=>Math.round(e*1e10)/1e10},Wk={dec2hex:e=>{const t=Math.round(e).toString(16);return t.length>1?t:`0${t}`}},Ct={channel:Ka,lang:qk,unit:Wk},Nr={};for(let e=0;e<=255;e++)Nr[e]=Ct.unit.dec2hex(e);const fe={ALL:0,RGB:1,HSL:2};class Hk{constructor(){this.type=fe.ALL}get(){return this.type}set(t){if(this.type&&this.type!==t)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=t}reset(){this.type=fe.ALL}is(t){return this.type===t}}class Vk{constructor(t,r){this.color=r,this.changed=!1,this.data=t,this.type=new Hk}set(t,r){return this.color=r,this.changed=!1,this.data=t,this.type.type=fe.ALL,this}_ensureHSL(){const t=this.data,{h:r,s:i,l:n}=t;r===void 0&&(t.h=Ct.channel.rgb2hsl(t,"h")),i===void 0&&(t.s=Ct.channel.rgb2hsl(t,"s")),n===void 0&&(t.l=Ct.channel.rgb2hsl(t,"l"))}_ensureRGB(){const t=this.data,{r,g:i,b:n}=t;r===void 0&&(t.r=Ct.channel.hsl2rgb(t,"r")),i===void 0&&(t.g=Ct.channel.hsl2rgb(t,"g")),n===void 0&&(t.b=Ct.channel.hsl2rgb(t,"b"))}get r(){const t=this.data,r=t.r;return!this.type.is(fe.HSL)&&r!==void 0?r:(this._ensureHSL(),Ct.channel.hsl2rgb(t,"r"))}get g(){const t=this.data,r=t.g;return!this.type.is(fe.HSL)&&r!==void 0?r:(this._ensureHSL(),Ct.channel.hsl2rgb(t,"g"))}get b(){const t=this.data,r=t.b;return!this.type.is(fe.HSL)&&r!==void 0?r:(this._ensureHSL(),Ct.channel.hsl2rgb(t,"b"))}get h(){const t=this.data,r=t.h;return!this.type.is(fe.RGB)&&r!==void 0?r:(this._ensureRGB(),Ct.channel.rgb2hsl(t,"h"))}get s(){const t=this.data,r=t.s;return!this.type.is(fe.RGB)&&r!==void 0?r:(this._ensureRGB(),Ct.channel.rgb2hsl(t,"s"))}get l(){const t=this.data,r=t.l;return!this.type.is(fe.RGB)&&r!==void 0?r:(this._ensureRGB(),Ct.channel.rgb2hsl(t,"l"))}get a(){return this.data.a}set r(t){this.type.set(fe.RGB),this.changed=!0,this.data.r=t}set g(t){this.type.set(fe.RGB),this.changed=!0,this.data.g=t}set b(t){this.type.set(fe.RGB),this.changed=!0,this.data.b=t}set h(t){this.type.set(fe.HSL),this.changed=!0,this.data.h=t}set s(t){this.type.set(fe.HSL),this.changed=!0,this.data.s=t}set l(t){this.type.set(fe.HSL),this.changed=!0,this.data.l=t}set a(t){this.changed=!0,this.data.a=t}}const bo=new Vk({r:0,g:0,b:0,a:0},"transparent"),Vi={re:/^#((?:[a-f0-9]{2}){2,4}|[a-f0-9]{3})$/i,parse:e=>{if(e.charCodeAt(0)!==35)return;const t=e.match(Vi.re);if(!t)return;const r=t[1],i=parseInt(r,16),n=r.length,a=n%4===0,o=n>4,s=o?1:17,l=o?8:4,c=a?0:-1,h=o?255:15;return bo.set({r:(i>>l*(c+3)&h)*s,g:(i>>l*(c+2)&h)*s,b:(i>>l*(c+1)&h)*s,a:a?(i&h)*s/255:1},e)},stringify:e=>{const{r:t,g:r,b:i,a:n}=e;return n<1?`#${Nr[Math.round(t)]}${Nr[Math.round(r)]}${Nr[Math.round(i)]}${Nr[Math.round(n*255)]}`:`#${Nr[Math.round(t)]}${Nr[Math.round(r)]}${Nr[Math.round(i)]}`}},si={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:e=>{const t=e.match(si.hueRe);if(t){const[,r,i]=t;switch(i){case"grad":return Ct.channel.clamp.h(parseFloat(r)*.9);case"rad":return Ct.channel.clamp.h(parseFloat(r)*180/Math.PI);case"turn":return Ct.channel.clamp.h(parseFloat(r)*360)}}return Ct.channel.clamp.h(parseFloat(e))},parse:e=>{const t=e.charCodeAt(0);if(t!==104&&t!==72)return;const r=e.match(si.re);if(!r)return;const[,i,n,a,o,s]=r;return bo.set({h:si._hue2deg(i),s:Ct.channel.clamp.s(parseFloat(n)),l:Ct.channel.clamp.l(parseFloat(a)),a:o?Ct.channel.clamp.a(s?parseFloat(o)/100:parseFloat(o)):1},e)},stringify:e=>{const{h:t,s:r,l:i,a:n}=e;return n<1?`hsla(${Ct.lang.round(t)}, ${Ct.lang.round(r)}%, ${Ct.lang.round(i)}%, ${n})`:`hsl(${Ct.lang.round(t)}, ${Ct.lang.round(r)}%, ${Ct.lang.round(i)}%)`}},Kn={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:e=>{e=e.toLowerCase();const t=Kn.colors[e];if(t)return Vi.parse(t)},stringify:e=>{const t=Vi.stringify(e);for(const r in Kn.colors)if(Kn.colors[r]===t)return r}},Wn={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:e=>{const t=e.charCodeAt(0);if(t!==114&&t!==82)return;const r=e.match(Wn.re);if(!r)return;const[,i,n,a,o,s,l,c,h]=r;return bo.set({r:Ct.channel.clamp.r(n?parseFloat(i)*2.55:parseFloat(i)),g:Ct.channel.clamp.g(o?parseFloat(a)*2.55:parseFloat(a)),b:Ct.channel.clamp.b(l?parseFloat(s)*2.55:parseFloat(s)),a:c?Ct.channel.clamp.a(h?parseFloat(c)/100:parseFloat(c)):1},e)},stringify:e=>{const{r:t,g:r,b:i,a:n}=e;return n<1?`rgba(${Ct.lang.round(t)}, ${Ct.lang.round(r)}, ${Ct.lang.round(i)}, ${Ct.lang.round(n)})`:`rgb(${Ct.lang.round(t)}, ${Ct.lang.round(r)}, ${Ct.lang.round(i)})`}},Qe={format:{keyword:Kn,hex:Vi,rgb:Wn,rgba:Wn,hsl:si,hsla:si},parse:e=>{if(typeof e!="string")return e;const t=Vi.parse(e)||Wn.parse(e)||si.parse(e)||Kn.parse(e);if(t)return t;throw new Error(`Unsupported color format: "${e}"`)},stringify:e=>!e.changed&&e.color?e.color:e.type.is(fe.HSL)||e.data.r===void 0?si.stringify(e):e.a<1||!Number.isInteger(e.r)||!Number.isInteger(e.g)||!Number.isInteger(e.b)?Wn.stringify(e):Vi.stringify(e)},Vf=(e,t)=>{const r=Qe.parse(e);for(const i in t)r[i]=Ct.channel.clamp[i](t[i]);return Qe.stringify(r)},Qn=(e,t,r=0,i=1)=>{if(typeof e!="number")return Vf(e,{a:t});const n=bo.set({r:Ct.channel.clamp.r(e),g:Ct.channel.clamp.g(t),b:Ct.channel.clamp.b(r),a:Ct.channel.clamp.a(i)});return Qe.stringify(n)},LO=(e,t)=>Ct.lang.round(Qe.parse(e)[t]),Uk=e=>{const{r:t,g:r,b:i}=Qe.parse(e),n=.2126*Ct.channel.toLinear(t)+.7152*Ct.channel.toLinear(r)+.0722*Ct.channel.toLinear(i);return Ct.lang.round(n)},jk=e=>Uk(e)>=.5,va=e=>!jk(e),Uf=(e,t,r)=>{const i=Qe.parse(e),n=i[t],a=Ct.channel.clamp[t](n+r);return n!==a&&(i[t]=a),Qe.stringify(i)},it=(e,t)=>Uf(e,"l",t),gt=(e,t)=>Uf(e,"l",-t),E=(e,t)=>{const r=Qe.parse(e),i={};for(const n in t)t[n]&&(i[n]=r[n]+t[n]);return Vf(e,i)},Yk=(e,t,r=50)=>{const{r:i,g:n,b:a,a:o}=Qe.parse(e),{r:s,g:l,b:c,a:h}=Qe.parse(t),u=r/100,d=u*2-1,f=o-h,g=((d*f===-1?d:(d+f)/(1+d*f))+1)/2,y=1-g,b=i*g+s*y,x=n*g+l*y,_=a*g+c*y,w=o*u+h*(1-u);return Qn(b,x,_,w)},K=(e,t=100)=>{const r=Qe.parse(e);return r.r=255-r.r,r.g=255-r.g,r.b=255-r.b,Yk(r,e,t)};/*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE */const{entries:jf,setPrototypeOf:Tu,isFrozen:Gk,getPrototypeOf:Xk,getOwnPropertyDescriptor:Zk}=Object;let{freeze:$e,seal:ze,create:ql}=Object,{apply:Wl,construct:Hl}=typeof Reflect<"u"&&Reflect;$e||($e=function(t){return t});ze||(ze=function(t){return t});Wl||(Wl=function(t,r){for(var i=arguments.length,n=new Array(i>2?i-2:0),a=2;a1?r-1:0),n=1;n1?r-1:0),n=1;n2&&arguments[2]!==void 0?arguments[2]:Qa;Tu&&Tu(e,null);let i=t.length;for(;i--;){let n=t[i];if(typeof n=="string"){const a=r(n);a!==n&&(Gk(t)||(t[i]=a),n=a)}e[n]=!0}return e}function rw(e){for(let t=0;t/gm),ow=ze(/\$\{[\w\W]*/gm),lw=ze(/^data-[\-\w.\u00B7-\uFFFF]+$/),cw=ze(/^aria-[\-\w]+$/),Yf=ze(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),hw=ze(/^(?:\w+script|data):/i),uw=ze(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Gf=ze(/^html$/i),dw=ze(/^[a-z][.\w]*(-[.\w]+)+$/i);var Eu=Object.freeze({__proto__:null,ARIA_ATTR:cw,ATTR_WHITESPACE:uw,CUSTOM_ELEMENT:dw,DATA_ATTR:lw,DOCTYPE_NAME:Gf,ERB_EXPR:sw,IS_ALLOWED_URI:Yf,IS_SCRIPT_OR_DATA:hw,MUSTACHE_EXPR:aw,TMPLIT_EXPR:ow});const Ln={element:1,text:3,progressingInstruction:7,comment:8,document:9},fw=function(){return typeof window>"u"?null:window},pw=function(t,r){if(typeof t!="object"||typeof t.createPolicy!="function")return null;let i=null;const n="data-tt-policy-suffix";r&&r.hasAttribute(n)&&(i=r.getAttribute(n));const a="dompurify"+(i?"#"+i:"");try{return t.createPolicy(a,{createHTML(o){return o},createScriptURL(o){return o}})}catch{return console.warn("TrustedTypes policy "+a+" could not be created."),null}},Fu=function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}};function Xf(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:fw();const t=pt=>Xf(pt);if(t.version="3.3.1",t.removed=[],!e||!e.document||e.document.nodeType!==Ln.document||!e.Element)return t.isSupported=!1,t;let{document:r}=e;const i=r,n=i.currentScript,{DocumentFragment:a,HTMLTemplateElement:o,Node:s,Element:l,NodeFilter:c,NamedNodeMap:h=e.NamedNodeMap||e.MozNamedAttrMap,HTMLFormElement:u,DOMParser:d,trustedTypes:f}=e,p=l.prototype,g=$n(p,"cloneNode"),y=$n(p,"remove"),b=$n(p,"nextSibling"),x=$n(p,"childNodes"),_=$n(p,"parentNode");if(typeof o=="function"){const pt=r.createElement("template");pt.content&&pt.content.ownerDocument&&(r=pt.content.ownerDocument)}let w,C="";const{implementation:v,createNodeIterator:k,createDocumentFragment:$,getElementsByTagName:z}=r,{importNode:W}=i;let O=Fu();t.isSupported=typeof jf=="function"&&typeof _=="function"&&v&&v.createHTMLDocument!==void 0;const{MUSTACHE_EXPR:N,ERB_EXPR:D,TMPLIT_EXPR:L,DATA_ATTR:B,ARIA_ATTR:F,IS_SCRIPT_OR_DATA:R,ATTR_WHITESPACE:I,CUSTOM_ELEMENT:X}=Eu;let{IS_ALLOWED_URI:Z}=Eu,J=null;const Tt=Et({},[...$u,...nl,...al,...sl,...Lu]);let vt=null;const Pt=Et({},[...Au,...ol,...Bu,...Da]);let xt=Object.seal(ql(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),kt=null,St=null;const It=Object.seal(ql(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let Y=!0,Q=!0,ht=!1,q=!0,Dt=!1,$t=!0,Yt=!1,Qt=!1,oe=!1,Jt=!1,Ei=!1,Qr=!1,Jr=!0,yr=!1;const Or="user-content-";let Rr=!0,tr=!1,He={},we=null;const wn=Et({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]);let lu=null;const cu=Et({},["audio","video","img","source","image","track"]);let Xo=null;const hu=Et({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Aa="http://www.w3.org/1998/Math/MathML",Ba="http://www.w3.org/2000/svg",br="http://www.w3.org/1999/xhtml";let Fi=br,Zo=!1,Ko=null;const Db=Et({},[Aa,Ba,br],rl);let Ea=Et({},["mi","mo","mn","ms","mtext"]),Fa=Et({},["annotation-xml"]);const Ib=Et({},["title","style","font","a","script"]);let Cn=null;const Ob=["application/xhtml+xml","text/html"],Rb="text/html";let te=null,Pi=null;const Nb=r.createElement("form"),uu=function(M){return M instanceof RegExp||M instanceof Function},Qo=function(){let M=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(Pi&&Pi===M)){if((!M||typeof M!="object")&&(M={}),M=ar(M),Cn=Ob.indexOf(M.PARSER_MEDIA_TYPE)===-1?Rb:M.PARSER_MEDIA_TYPE,te=Cn==="application/xhtml+xml"?rl:Qa,J=Ve(M,"ALLOWED_TAGS")?Et({},M.ALLOWED_TAGS,te):Tt,vt=Ve(M,"ALLOWED_ATTR")?Et({},M.ALLOWED_ATTR,te):Pt,Ko=Ve(M,"ALLOWED_NAMESPACES")?Et({},M.ALLOWED_NAMESPACES,rl):Db,Xo=Ve(M,"ADD_URI_SAFE_ATTR")?Et(ar(hu),M.ADD_URI_SAFE_ATTR,te):hu,lu=Ve(M,"ADD_DATA_URI_TAGS")?Et(ar(cu),M.ADD_DATA_URI_TAGS,te):cu,we=Ve(M,"FORBID_CONTENTS")?Et({},M.FORBID_CONTENTS,te):wn,kt=Ve(M,"FORBID_TAGS")?Et({},M.FORBID_TAGS,te):ar({}),St=Ve(M,"FORBID_ATTR")?Et({},M.FORBID_ATTR,te):ar({}),He=Ve(M,"USE_PROFILES")?M.USE_PROFILES:!1,Y=M.ALLOW_ARIA_ATTR!==!1,Q=M.ALLOW_DATA_ATTR!==!1,ht=M.ALLOW_UNKNOWN_PROTOCOLS||!1,q=M.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Dt=M.SAFE_FOR_TEMPLATES||!1,$t=M.SAFE_FOR_XML!==!1,Yt=M.WHOLE_DOCUMENT||!1,Jt=M.RETURN_DOM||!1,Ei=M.RETURN_DOM_FRAGMENT||!1,Qr=M.RETURN_TRUSTED_TYPE||!1,oe=M.FORCE_BODY||!1,Jr=M.SANITIZE_DOM!==!1,yr=M.SANITIZE_NAMED_PROPS||!1,Rr=M.KEEP_CONTENT!==!1,tr=M.IN_PLACE||!1,Z=M.ALLOWED_URI_REGEXP||Yf,Fi=M.NAMESPACE||br,Ea=M.MATHML_TEXT_INTEGRATION_POINTS||Ea,Fa=M.HTML_INTEGRATION_POINTS||Fa,xt=M.CUSTOM_ELEMENT_HANDLING||{},M.CUSTOM_ELEMENT_HANDLING&&uu(M.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(xt.tagNameCheck=M.CUSTOM_ELEMENT_HANDLING.tagNameCheck),M.CUSTOM_ELEMENT_HANDLING&&uu(M.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(xt.attributeNameCheck=M.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),M.CUSTOM_ELEMENT_HANDLING&&typeof M.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(xt.allowCustomizedBuiltInElements=M.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Dt&&(Q=!1),Ei&&(Jt=!0),He&&(J=Et({},Lu),vt=[],He.html===!0&&(Et(J,$u),Et(vt,Au)),He.svg===!0&&(Et(J,nl),Et(vt,ol),Et(vt,Da)),He.svgFilters===!0&&(Et(J,al),Et(vt,ol),Et(vt,Da)),He.mathMl===!0&&(Et(J,sl),Et(vt,Bu),Et(vt,Da))),M.ADD_TAGS&&(typeof M.ADD_TAGS=="function"?It.tagCheck=M.ADD_TAGS:(J===Tt&&(J=ar(J)),Et(J,M.ADD_TAGS,te))),M.ADD_ATTR&&(typeof M.ADD_ATTR=="function"?It.attributeCheck=M.ADD_ATTR:(vt===Pt&&(vt=ar(vt)),Et(vt,M.ADD_ATTR,te))),M.ADD_URI_SAFE_ATTR&&Et(Xo,M.ADD_URI_SAFE_ATTR,te),M.FORBID_CONTENTS&&(we===wn&&(we=ar(we)),Et(we,M.FORBID_CONTENTS,te)),M.ADD_FORBID_CONTENTS&&(we===wn&&(we=ar(we)),Et(we,M.ADD_FORBID_CONTENTS,te)),Rr&&(J["#text"]=!0),Yt&&Et(J,["html","head","body"]),J.table&&(Et(J,["tbody"]),delete kt.tbody),M.TRUSTED_TYPES_POLICY){if(typeof M.TRUSTED_TYPES_POLICY.createHTML!="function")throw Mn('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof M.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw Mn('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');w=M.TRUSTED_TYPES_POLICY,C=w.createHTML("")}else w===void 0&&(w=pw(f,n)),w!==null&&typeof C=="string"&&(C=w.createHTML(""));$e&&$e(M),Pi=M}},du=Et({},[...nl,...al,...iw]),fu=Et({},[...sl,...nw]),zb=function(M){let G=_(M);(!G||!G.tagName)&&(G={namespaceURI:Fi,tagName:"template"});const ct=Qa(M.tagName),Ut=Qa(G.tagName);return Ko[M.namespaceURI]?M.namespaceURI===Ba?G.namespaceURI===br?ct==="svg":G.namespaceURI===Aa?ct==="svg"&&(Ut==="annotation-xml"||Ea[Ut]):!!du[ct]:M.namespaceURI===Aa?G.namespaceURI===br?ct==="math":G.namespaceURI===Ba?ct==="math"&&Fa[Ut]:!!fu[ct]:M.namespaceURI===br?G.namespaceURI===Ba&&!Fa[Ut]||G.namespaceURI===Aa&&!Ea[Ut]?!1:!fu[ct]&&(Ib[ct]||!du[ct]):!!(Cn==="application/xhtml+xml"&&Ko[M.namespaceURI]):!1},er=function(M){Sn(t.removed,{element:M});try{_(M).removeChild(M)}catch{y(M)}},ti=function(M,G){try{Sn(t.removed,{attribute:G.getAttributeNode(M),from:G})}catch{Sn(t.removed,{attribute:null,from:G})}if(G.removeAttribute(M),M==="is")if(Jt||Ei)try{er(G)}catch{}else try{G.setAttribute(M,"")}catch{}},pu=function(M){let G=null,ct=null;if(oe)M=""+M;else{const Zt=il(M,/^[\r\n\t ]+/);ct=Zt&&Zt[0]}Cn==="application/xhtml+xml"&&Fi===br&&(M=''+M+"");const Ut=w?w.createHTML(M):M;if(Fi===br)try{G=new d().parseFromString(Ut,Cn)}catch{}if(!G||!G.documentElement){G=v.createDocument(Fi,"template",null);try{G.documentElement.innerHTML=Zo?C:Ut}catch{}}const ue=G.body||G.documentElement;return M&&ct&&ue.insertBefore(r.createTextNode(ct),ue.childNodes[0]||null),Fi===br?z.call(G,Yt?"html":"body")[0]:Yt?G.documentElement:ue},gu=function(M){return k.call(M.ownerDocument||M,M,c.SHOW_ELEMENT|c.SHOW_COMMENT|c.SHOW_TEXT|c.SHOW_PROCESSING_INSTRUCTION|c.SHOW_CDATA_SECTION,null)},Jo=function(M){return M instanceof u&&(typeof M.nodeName!="string"||typeof M.textContent!="string"||typeof M.removeChild!="function"||!(M.attributes instanceof h)||typeof M.removeAttribute!="function"||typeof M.setAttribute!="function"||typeof M.namespaceURI!="string"||typeof M.insertBefore!="function"||typeof M.hasChildNodes!="function")},mu=function(M){return typeof s=="function"&&M instanceof s};function vr(pt,M,G){Pa(pt,ct=>{ct.call(t,M,G,Pi)})}const yu=function(M){let G=null;if(vr(O.beforeSanitizeElements,M,null),Jo(M))return er(M),!0;const ct=te(M.nodeName);if(vr(O.uponSanitizeElement,M,{tagName:ct,allowedTags:J}),$t&&M.hasChildNodes()&&!mu(M.firstElementChild)&&Ce(/<[/\w!]/g,M.innerHTML)&&Ce(/<[/\w!]/g,M.textContent)||M.nodeType===Ln.progressingInstruction||$t&&M.nodeType===Ln.comment&&Ce(/<[/\w]/g,M.data))return er(M),!0;if(!(It.tagCheck instanceof Function&&It.tagCheck(ct))&&(!J[ct]||kt[ct])){if(!kt[ct]&&vu(ct)&&(xt.tagNameCheck instanceof RegExp&&Ce(xt.tagNameCheck,ct)||xt.tagNameCheck instanceof Function&&xt.tagNameCheck(ct)))return!1;if(Rr&&!we[ct]){const Ut=_(M)||M.parentNode,ue=x(M)||M.childNodes;if(ue&&Ut){const Zt=ue.length;for(let Ae=Zt-1;Ae>=0;--Ae){const xr=g(ue[Ae],!0);xr.__removalCount=(M.__removalCount||0)+1,Ut.insertBefore(xr,b(M))}}}return er(M),!0}return M instanceof l&&!zb(M)||(ct==="noscript"||ct==="noembed"||ct==="noframes")&&Ce(/<\/no(script|embed|frames)/i,M.innerHTML)?(er(M),!0):(Dt&&M.nodeType===Ln.text&&(G=M.textContent,Pa([N,D,L],Ut=>{G=Tn(G,Ut," ")}),M.textContent!==G&&(Sn(t.removed,{element:M.cloneNode()}),M.textContent=G)),vr(O.afterSanitizeElements,M,null),!1)},bu=function(M,G,ct){if(Jr&&(G==="id"||G==="name")&&(ct in r||ct in Nb))return!1;if(!(Q&&!St[G]&&Ce(B,G))){if(!(Y&&Ce(F,G))){if(!(It.attributeCheck instanceof Function&&It.attributeCheck(G,M))){if(!vt[G]||St[G]){if(!(vu(M)&&(xt.tagNameCheck instanceof RegExp&&Ce(xt.tagNameCheck,M)||xt.tagNameCheck instanceof Function&&xt.tagNameCheck(M))&&(xt.attributeNameCheck instanceof RegExp&&Ce(xt.attributeNameCheck,G)||xt.attributeNameCheck instanceof Function&&xt.attributeNameCheck(G,M))||G==="is"&&xt.allowCustomizedBuiltInElements&&(xt.tagNameCheck instanceof RegExp&&Ce(xt.tagNameCheck,ct)||xt.tagNameCheck instanceof Function&&xt.tagNameCheck(ct))))return!1}else if(!Xo[G]){if(!Ce(Z,Tn(ct,I,""))){if(!((G==="src"||G==="xlink:href"||G==="href")&&M!=="script"&&Jk(ct,"data:")===0&&lu[M])){if(!(ht&&!Ce(R,Tn(ct,I,"")))){if(ct)return!1}}}}}}}return!0},vu=function(M){return M!=="annotation-xml"&&il(M,X)},xu=function(M){vr(O.beforeSanitizeAttributes,M,null);const{attributes:G}=M;if(!G||Jo(M))return;const ct={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:vt,forceKeepAttr:void 0};let Ut=G.length;for(;Ut--;){const ue=G[Ut],{name:Zt,namespaceURI:Ae,value:xr}=ue,Di=te(Zt),tl=xr;let le=Zt==="value"?tl:tw(tl);if(ct.attrName=Di,ct.attrValue=le,ct.keepAttr=!0,ct.forceKeepAttr=void 0,vr(O.uponSanitizeAttribute,M,ct),le=ct.attrValue,yr&&(Di==="id"||Di==="name")&&(ti(Zt,M),le=Or+le),$t&&Ce(/((--!?|])>)|<\/(style|title|textarea)/i,le)){ti(Zt,M);continue}if(Di==="attributename"&&il(le,"href")){ti(Zt,M);continue}if(ct.forceKeepAttr)continue;if(!ct.keepAttr){ti(Zt,M);continue}if(!q&&Ce(/\/>/i,le)){ti(Zt,M);continue}Dt&&Pa([N,D,L],ku=>{le=Tn(le,ku," ")});const _u=te(M.nodeName);if(!bu(_u,Di,le)){ti(Zt,M);continue}if(w&&typeof f=="object"&&typeof f.getAttributeType=="function"&&!Ae)switch(f.getAttributeType(_u,Di)){case"TrustedHTML":{le=w.createHTML(le);break}case"TrustedScriptURL":{le=w.createScriptURL(le);break}}if(le!==tl)try{Ae?M.setAttributeNS(Ae,Zt,le):M.setAttribute(Zt,le),Jo(M)?er(M):Mu(t.removed)}catch{ti(Zt,M)}}vr(O.afterSanitizeAttributes,M,null)},qb=function pt(M){let G=null;const ct=gu(M);for(vr(O.beforeSanitizeShadowDOM,M,null);G=ct.nextNode();)vr(O.uponSanitizeShadowNode,G,null),yu(G),xu(G),G.content instanceof a&&pt(G.content);vr(O.afterSanitizeShadowDOM,M,null)};return t.sanitize=function(pt){let M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},G=null,ct=null,Ut=null,ue=null;if(Zo=!pt,Zo&&(pt=""),typeof pt!="string"&&!mu(pt))if(typeof pt.toString=="function"){if(pt=pt.toString(),typeof pt!="string")throw Mn("dirty is not a string, aborting")}else throw Mn("toString is not a function");if(!t.isSupported)return pt;if(Qt||Qo(M),t.removed=[],typeof pt=="string"&&(tr=!1),tr){if(pt.nodeName){const xr=te(pt.nodeName);if(!J[xr]||kt[xr])throw Mn("root node is forbidden and cannot be sanitized in-place")}}else if(pt instanceof s)G=pu(""),ct=G.ownerDocument.importNode(pt,!0),ct.nodeType===Ln.element&&ct.nodeName==="BODY"||ct.nodeName==="HTML"?G=ct:G.appendChild(ct);else{if(!Jt&&!Dt&&!Yt&&pt.indexOf("<")===-1)return w&&Qr?w.createHTML(pt):pt;if(G=pu(pt),!G)return Jt?null:Qr?C:""}G&&oe&&er(G.firstChild);const Zt=gu(tr?pt:G);for(;Ut=Zt.nextNode();)yu(Ut),xu(Ut),Ut.content instanceof a&&qb(Ut.content);if(tr)return pt;if(Jt){if(Ei)for(ue=$.call(G.ownerDocument);G.firstChild;)ue.appendChild(G.firstChild);else ue=G;return(vt.shadowroot||vt.shadowrootmode)&&(ue=W.call(i,ue,!0)),ue}let Ae=Yt?G.outerHTML:G.innerHTML;return Yt&&J["!doctype"]&&G.ownerDocument&&G.ownerDocument.doctype&&G.ownerDocument.doctype.name&&Ce(Gf,G.ownerDocument.doctype.name)&&(Ae=" +`+Ae),Dt&&Pa([N,D,L],xr=>{Ae=Tn(Ae,xr," ")}),w&&Qr?w.createHTML(Ae):Ae},t.setConfig=function(){let pt=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};Qo(pt),Qt=!0},t.clearConfig=function(){Pi=null,Qt=!1},t.isValidAttribute=function(pt,M,G){Pi||Qo({});const ct=te(pt),Ut=te(M);return bu(ct,Ut,G)},t.addHook=function(pt,M){typeof M=="function"&&Sn(O[pt],M)},t.removeHook=function(pt,M){if(M!==void 0){const G=Kk(O[pt],M);return G===-1?void 0:Qk(O[pt],G,1)[0]}return Mu(O[pt])},t.removeHooks=function(pt){O[pt]=[]},t.removeAllHooks=function(){O=Fu()},t}var rn=Xf(),Zf=/^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s,Jn=/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi,gw=/\s*%%.*\n/gm,Gi,Kf=(Gi=class extends Error{constructor(t){super(t),this.name="UnknownDiagramError"}},m(Gi,"UnknownDiagramError"),Gi),pi={},rh=m(function(e,t){e=e.replace(Zf,"").replace(Jn,"").replace(gw,` +`);for(const[r,{detector:i}]of Object.entries(pi))if(i(e,t))return r;throw new Kf(`No diagram type detected matching given configuration for text: ${e}`)},"detectType"),Vl=m((...e)=>{for(const{id:t,detector:r,loader:i}of e)Qf(t,r,i)},"registerLazyLoadedDiagrams"),Qf=m((e,t,r)=>{pi[e]&&V.warn(`Detector with key ${e} already exists. Overwriting.`),pi[e]={detector:t,loader:r},V.debug(`Detector with key ${e} added${r?" with loader":""}`)},"addDetector"),mw=m(e=>pi[e].loader,"getDiagramLoader"),Ul=m((e,t,{depth:r=2,clobber:i=!1}={})=>{const n={depth:r,clobber:i};return Array.isArray(t)&&!Array.isArray(e)?(t.forEach(a=>Ul(e,a,n)),e):Array.isArray(t)&&Array.isArray(e)?(t.forEach(a=>{e.includes(a)||e.push(a)}),e):e===void 0||r<=0?e!=null&&typeof e=="object"&&typeof t=="object"?Object.assign(e,t):t:(t!==void 0&&typeof e=="object"&&typeof t=="object"&&Object.keys(t).forEach(a=>{typeof t[a]=="object"&&(e[a]===void 0||typeof e[a]=="object")?(e[a]===void 0&&(e[a]=Array.isArray(t[a])?[]:{}),e[a]=Ul(e[a],t[a],{depth:r-1,clobber:i})):(i||typeof e[a]!="object"&&typeof t[a]!="object")&&(e[a]=t[a])}),e)},"assignWithDepth"),ee=Ul,vo="#ffffff",xo="#f2f2f2",Se=m((e,t)=>t?E(e,{s:-40,l:10}):E(e,{s:-40,l:-10}),"mkBorder"),Xi,yw=(Xi=class{constructor(){this.background="#f4f4f4",this.primaryColor="#fff4dd",this.noteBkgColor="#fff5ad",this.noteTextColor="#333",this.THEME_COLOR_LIMIT=12,this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px"}updateColors(){var r,i,n,a,o,s,l,c,h,u,d,f,p,g,y,b,x,_,w,C,v;if(this.primaryTextColor=this.primaryTextColor||(this.darkMode?"#eee":"#333"),this.secondaryColor=this.secondaryColor||E(this.primaryColor,{h:-120}),this.tertiaryColor=this.tertiaryColor||E(this.primaryColor,{h:180,l:5}),this.primaryBorderColor=this.primaryBorderColor||Se(this.primaryColor,this.darkMode),this.secondaryBorderColor=this.secondaryBorderColor||Se(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=this.tertiaryBorderColor||Se(this.tertiaryColor,this.darkMode),this.noteBorderColor=this.noteBorderColor||Se(this.noteBkgColor,this.darkMode),this.noteBkgColor=this.noteBkgColor||"#fff5ad",this.noteTextColor=this.noteTextColor||"#333",this.secondaryTextColor=this.secondaryTextColor||K(this.secondaryColor),this.tertiaryTextColor=this.tertiaryTextColor||K(this.tertiaryColor),this.lineColor=this.lineColor||K(this.background),this.arrowheadColor=this.arrowheadColor||K(this.background),this.textColor=this.textColor||this.primaryTextColor,this.border2=this.border2||this.tertiaryBorderColor,this.nodeBkg=this.nodeBkg||this.primaryColor,this.mainBkg=this.mainBkg||this.primaryColor,this.nodeBorder=this.nodeBorder||this.primaryBorderColor,this.clusterBkg=this.clusterBkg||this.tertiaryColor,this.clusterBorder=this.clusterBorder||this.tertiaryBorderColor,this.defaultLinkColor=this.defaultLinkColor||this.lineColor,this.titleColor=this.titleColor||this.tertiaryTextColor,this.edgeLabelBackground=this.edgeLabelBackground||(this.darkMode?gt(this.secondaryColor,30):this.secondaryColor),this.nodeTextColor=this.nodeTextColor||this.primaryTextColor,this.actorBorder=this.actorBorder||this.primaryBorderColor,this.actorBkg=this.actorBkg||this.mainBkg,this.actorTextColor=this.actorTextColor||this.primaryTextColor,this.actorLineColor=this.actorLineColor||this.actorBorder,this.labelBoxBkgColor=this.labelBoxBkgColor||this.actorBkg,this.signalColor=this.signalColor||this.textColor,this.signalTextColor=this.signalTextColor||this.textColor,this.labelBoxBorderColor=this.labelBoxBorderColor||this.actorBorder,this.labelTextColor=this.labelTextColor||this.actorTextColor,this.loopTextColor=this.loopTextColor||this.actorTextColor,this.activationBorderColor=this.activationBorderColor||gt(this.secondaryColor,10),this.activationBkgColor=this.activationBkgColor||this.secondaryColor,this.sequenceNumberColor=this.sequenceNumberColor||K(this.lineColor),this.sectionBkgColor=this.sectionBkgColor||this.tertiaryColor,this.altSectionBkgColor=this.altSectionBkgColor||"white",this.sectionBkgColor=this.sectionBkgColor||this.secondaryColor,this.sectionBkgColor2=this.sectionBkgColor2||this.primaryColor,this.excludeBkgColor=this.excludeBkgColor||"#eeeeee",this.taskBorderColor=this.taskBorderColor||this.primaryBorderColor,this.taskBkgColor=this.taskBkgColor||this.primaryColor,this.activeTaskBorderColor=this.activeTaskBorderColor||this.primaryColor,this.activeTaskBkgColor=this.activeTaskBkgColor||it(this.primaryColor,23),this.gridColor=this.gridColor||"lightgrey",this.doneTaskBkgColor=this.doneTaskBkgColor||"lightgrey",this.doneTaskBorderColor=this.doneTaskBorderColor||"grey",this.critBorderColor=this.critBorderColor||"#ff8888",this.critBkgColor=this.critBkgColor||"red",this.todayLineColor=this.todayLineColor||"red",this.vertLineColor=this.vertLineColor||"navy",this.taskTextColor=this.taskTextColor||this.textColor,this.taskTextOutsideColor=this.taskTextOutsideColor||this.textColor,this.taskTextLightColor=this.taskTextLightColor||this.textColor,this.taskTextColor=this.taskTextColor||this.primaryTextColor,this.taskTextDarkColor=this.taskTextDarkColor||this.textColor,this.taskTextClickableColor=this.taskTextClickableColor||"#003163",this.personBorder=this.personBorder||this.primaryBorderColor,this.personBkg=this.personBkg||this.mainBkg,this.darkMode?(this.rowOdd=this.rowOdd||gt(this.mainBkg,5)||"#ffffff",this.rowEven=this.rowEven||gt(this.mainBkg,10)):(this.rowOdd=this.rowOdd||it(this.mainBkg,75)||"#ffffff",this.rowEven=this.rowEven||it(this.mainBkg,5)),this.transitionColor=this.transitionColor||this.lineColor,this.transitionLabelColor=this.transitionLabelColor||this.textColor,this.stateLabelColor=this.stateLabelColor||this.stateBkg||this.primaryTextColor,this.stateBkg=this.stateBkg||this.mainBkg,this.labelBackgroundColor=this.labelBackgroundColor||this.stateBkg,this.compositeBackground=this.compositeBackground||this.background||this.tertiaryColor,this.altBackground=this.altBackground||this.tertiaryColor,this.compositeTitleBackground=this.compositeTitleBackground||this.mainBkg,this.compositeBorder=this.compositeBorder||this.nodeBorder,this.innerEndBackground=this.nodeBorder,this.errorBkgColor=this.errorBkgColor||this.tertiaryColor,this.errorTextColor=this.errorTextColor||this.tertiaryTextColor,this.transitionColor=this.transitionColor||this.lineColor,this.specialStateColor=this.lineColor,this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||E(this.primaryColor,{h:30}),this.cScale4=this.cScale4||E(this.primaryColor,{h:60}),this.cScale5=this.cScale5||E(this.primaryColor,{h:90}),this.cScale6=this.cScale6||E(this.primaryColor,{h:120}),this.cScale7=this.cScale7||E(this.primaryColor,{h:150}),this.cScale8=this.cScale8||E(this.primaryColor,{h:210,l:150}),this.cScale9=this.cScale9||E(this.primaryColor,{h:270}),this.cScale10=this.cScale10||E(this.primaryColor,{h:300}),this.cScale11=this.cScale11||E(this.primaryColor,{h:330}),this.darkMode)for(let k=0;k{this[i]=t[i]}),this.updateColors(),r.forEach(i=>{this[i]=t[i]})}},m(Xi,"Theme"),Xi),bw=m(e=>{const t=new yw;return t.calculate(e),t},"getThemeVariables"),Zi,vw=(Zi=class{constructor(){this.background="#333",this.primaryColor="#1f2020",this.secondaryColor=it(this.primaryColor,16),this.tertiaryColor=E(this.primaryColor,{h:-160}),this.primaryBorderColor=K(this.background),this.secondaryBorderColor=Se(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=Se(this.tertiaryColor,this.darkMode),this.primaryTextColor=K(this.primaryColor),this.secondaryTextColor=K(this.secondaryColor),this.tertiaryTextColor=K(this.tertiaryColor),this.lineColor=K(this.background),this.textColor=K(this.background),this.mainBkg="#1f2020",this.secondBkg="calculated",this.mainContrastColor="lightgrey",this.darkTextColor=it(K("#323D47"),10),this.lineColor="calculated",this.border1="#ccc",this.border2=Qn(255,255,255,.25),this.arrowheadColor="calculated",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="#181818",this.textColor="#ccc",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#F9FFFE",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="calculated",this.activationBkgColor="calculated",this.sequenceNumberColor="black",this.sectionBkgColor=gt("#EAE8D9",30),this.altSectionBkgColor="calculated",this.sectionBkgColor2="#EAE8D9",this.excludeBkgColor=gt(this.sectionBkgColor,10),this.taskBorderColor=Qn(255,255,255,70),this.taskBkgColor="calculated",this.taskTextColor="calculated",this.taskTextLightColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor=Qn(255,255,255,50),this.activeTaskBkgColor="#81B1DB",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="grey",this.critBorderColor="#E83737",this.critBkgColor="#E83737",this.taskTextDarkColor="calculated",this.todayLineColor="#DB5757",this.vertLineColor="#00BFFF",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.rowOdd=this.rowOdd||it(this.mainBkg,5)||"#ffffff",this.rowEven=this.rowEven||gt(this.mainBkg,10),this.labelColor="calculated",this.errorBkgColor="#a44141",this.errorTextColor="#ddd"}updateColors(){var t,r,i,n,a,o,s,l,c,h,u,d,f,p,g,y,b,x,_,w,C;this.secondBkg=it(this.mainBkg,16),this.lineColor=this.mainContrastColor,this.arrowheadColor=this.mainContrastColor,this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.edgeLabelBackground=it(this.labelBackground,25),this.actorBorder=this.border1,this.actorBkg=this.mainBkg,this.actorTextColor=this.mainContrastColor,this.actorLineColor=this.actorBorder,this.signalColor=this.mainContrastColor,this.signalTextColor=this.mainContrastColor,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.mainContrastColor,this.loopTextColor=this.mainContrastColor,this.noteBorderColor=this.secondaryBorderColor,this.noteBkgColor=this.secondBkg,this.noteTextColor=this.secondaryTextColor,this.activationBorderColor=this.border1,this.activationBkgColor=this.secondBkg,this.altSectionBkgColor=this.background,this.taskBkgColor=it(this.mainBkg,23),this.taskTextColor=this.darkTextColor,this.taskTextLightColor=this.mainContrastColor,this.taskTextOutsideColor=this.taskTextLightColor,this.gridColor=this.mainContrastColor,this.doneTaskBkgColor=this.mainContrastColor,this.taskTextDarkColor=this.darkTextColor,this.archEdgeColor=this.lineColor,this.archEdgeArrowColor=this.lineColor,this.transitionColor=this.transitionColor||this.lineColor,this.transitionLabelColor=this.transitionLabelColor||this.textColor,this.stateLabelColor=this.stateLabelColor||this.stateBkg||this.primaryTextColor,this.stateBkg=this.stateBkg||this.mainBkg,this.labelBackgroundColor=this.labelBackgroundColor||this.stateBkg,this.compositeBackground=this.compositeBackground||this.background||this.tertiaryColor,this.altBackground=this.altBackground||"#555",this.compositeTitleBackground=this.compositeTitleBackground||this.mainBkg,this.compositeBorder=this.compositeBorder||this.nodeBorder,this.innerEndBackground=this.primaryBorderColor,this.specialStateColor="#f4f4f4",this.errorBkgColor=this.errorBkgColor||this.tertiaryColor,this.errorTextColor=this.errorTextColor||this.tertiaryTextColor,this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=E(this.primaryColor,{h:64}),this.fillType3=E(this.secondaryColor,{h:64}),this.fillType4=E(this.primaryColor,{h:-64}),this.fillType5=E(this.secondaryColor,{h:-64}),this.fillType6=E(this.primaryColor,{h:128}),this.fillType7=E(this.secondaryColor,{h:128}),this.cScale1=this.cScale1||"#0b0000",this.cScale2=this.cScale2||"#4d1037",this.cScale3=this.cScale3||"#3f5258",this.cScale4=this.cScale4||"#4f2f1b",this.cScale5=this.cScale5||"#6e0a0a",this.cScale6=this.cScale6||"#3b0048",this.cScale7=this.cScale7||"#995a01",this.cScale8=this.cScale8||"#154706",this.cScale9=this.cScale9||"#161722",this.cScale10=this.cScale10||"#00296f",this.cScale11=this.cScale11||"#01629c",this.cScale12=this.cScale12||"#010029",this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||E(this.primaryColor,{h:30}),this.cScale4=this.cScale4||E(this.primaryColor,{h:60}),this.cScale5=this.cScale5||E(this.primaryColor,{h:90}),this.cScale6=this.cScale6||E(this.primaryColor,{h:120}),this.cScale7=this.cScale7||E(this.primaryColor,{h:150}),this.cScale8=this.cScale8||E(this.primaryColor,{h:210}),this.cScale9=this.cScale9||E(this.primaryColor,{h:270}),this.cScale10=this.cScale10||E(this.primaryColor,{h:300}),this.cScale11=this.cScale11||E(this.primaryColor,{h:330});for(let v=0;v{this[i]=t[i]}),this.updateColors(),r.forEach(i=>{this[i]=t[i]})}},m(Zi,"Theme"),Zi),xw=m(e=>{const t=new vw;return t.calculate(e),t},"getThemeVariables"),Ki,_w=(Ki=class{constructor(){this.background="#f4f4f4",this.primaryColor="#ECECFF",this.secondaryColor=E(this.primaryColor,{h:120}),this.secondaryColor="#ffffde",this.tertiaryColor=E(this.primaryColor,{h:-160}),this.primaryBorderColor=Se(this.primaryColor,this.darkMode),this.secondaryBorderColor=Se(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=Se(this.tertiaryColor,this.darkMode),this.primaryTextColor=K(this.primaryColor),this.secondaryTextColor=K(this.secondaryColor),this.tertiaryTextColor=K(this.tertiaryColor),this.lineColor=K(this.background),this.textColor=K(this.background),this.background="white",this.mainBkg="#ECECFF",this.secondBkg="#ffffde",this.lineColor="#333333",this.border1="#9370DB",this.border2="#aaaa33",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="rgba(232,232,232, 0.8)",this.textColor="#333",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="calculated",this.sectionBkgColor2="calculated",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="calculated",this.taskTextColor=this.taskTextLightColor,this.taskTextDarkColor="calculated",this.taskTextOutsideColor=this.taskTextDarkColor,this.taskTextClickableColor="calculated",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBorderColor="calculated",this.critBkgColor="calculated",this.todayLineColor="calculated",this.vertLineColor="calculated",this.sectionBkgColor=Qn(102,102,255,.49),this.altSectionBkgColor="white",this.sectionBkgColor2="#fff400",this.taskBorderColor="#534fbc",this.taskBkgColor="#8a90dd",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="#534fbc",this.activeTaskBkgColor="#bfc7ff",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.vertLineColor="navy",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.rowOdd="calculated",this.rowEven="calculated",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222",this.updateColors()}updateColors(){var t,r,i,n,a,o,s,l,c,h,u,d,f,p,g,y,b,x,_,w,C;this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||E(this.primaryColor,{h:30}),this.cScale4=this.cScale4||E(this.primaryColor,{h:60}),this.cScale5=this.cScale5||E(this.primaryColor,{h:90}),this.cScale6=this.cScale6||E(this.primaryColor,{h:120}),this.cScale7=this.cScale7||E(this.primaryColor,{h:150}),this.cScale8=this.cScale8||E(this.primaryColor,{h:210}),this.cScale9=this.cScale9||E(this.primaryColor,{h:270}),this.cScale10=this.cScale10||E(this.primaryColor,{h:300}),this.cScale11=this.cScale11||E(this.primaryColor,{h:330}),this.cScalePeer1=this.cScalePeer1||gt(this.secondaryColor,45),this.cScalePeer2=this.cScalePeer2||gt(this.tertiaryColor,40);for(let v=0;v{this[i]==="calculated"&&(this[i]=void 0)}),typeof t!="object"){this.updateColors();return}const r=Object.keys(t);r.forEach(i=>{this[i]=t[i]}),this.updateColors(),r.forEach(i=>{this[i]=t[i]})}},m(Ki,"Theme"),Ki),kw=m(e=>{const t=new _w;return t.calculate(e),t},"getThemeVariables"),Qi,ww=(Qi=class{constructor(){this.background="#f4f4f4",this.primaryColor="#cde498",this.secondaryColor="#cdffb2",this.background="white",this.mainBkg="#cde498",this.secondBkg="#cdffb2",this.lineColor="green",this.border1="#13540c",this.border2="#6eaa49",this.arrowheadColor="green",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.tertiaryColor=it("#cde498",10),this.primaryBorderColor=Se(this.primaryColor,this.darkMode),this.secondaryBorderColor=Se(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=Se(this.tertiaryColor,this.darkMode),this.primaryTextColor=K(this.primaryColor),this.secondaryTextColor=K(this.secondaryColor),this.tertiaryTextColor=K(this.primaryColor),this.lineColor=K(this.background),this.textColor=K(this.background),this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#333",this.edgeLabelBackground="#e8e8e8",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="calculated",this.signalColor="#333",this.signalTextColor="#333",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="#326932",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="#6eaa49",this.altSectionBkgColor="white",this.sectionBkgColor2="#6eaa49",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="#487e3a",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.vertLineColor="#00BFFF",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}updateColors(){var t,r,i,n,a,o,s,l,c,h,u,d,f,p,g,y,b,x,_,w,C;this.actorBorder=gt(this.mainBkg,20),this.actorBkg=this.mainBkg,this.labelBoxBkgColor=this.actorBkg,this.labelTextColor=this.actorTextColor,this.loopTextColor=this.actorTextColor,this.noteBorderColor=this.border2,this.noteTextColor=this.actorTextColor,this.actorLineColor=this.actorBorder,this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||E(this.primaryColor,{h:30}),this.cScale4=this.cScale4||E(this.primaryColor,{h:60}),this.cScale5=this.cScale5||E(this.primaryColor,{h:90}),this.cScale6=this.cScale6||E(this.primaryColor,{h:120}),this.cScale7=this.cScale7||E(this.primaryColor,{h:150}),this.cScale8=this.cScale8||E(this.primaryColor,{h:210}),this.cScale9=this.cScale9||E(this.primaryColor,{h:270}),this.cScale10=this.cScale10||E(this.primaryColor,{h:300}),this.cScale11=this.cScale11||E(this.primaryColor,{h:330}),this.cScalePeer1=this.cScalePeer1||gt(this.secondaryColor,45),this.cScalePeer2=this.cScalePeer2||gt(this.tertiaryColor,40);for(let v=0;v{this[i]=t[i]}),this.updateColors(),r.forEach(i=>{this[i]=t[i]})}},m(Qi,"Theme"),Qi),Cw=m(e=>{const t=new ww;return t.calculate(e),t},"getThemeVariables"),Ji,Sw=(Ji=class{constructor(){this.primaryColor="#eee",this.contrast="#707070",this.secondaryColor=it(this.contrast,55),this.background="#ffffff",this.tertiaryColor=E(this.primaryColor,{h:-160}),this.primaryBorderColor=Se(this.primaryColor,this.darkMode),this.secondaryBorderColor=Se(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=Se(this.tertiaryColor,this.darkMode),this.primaryTextColor=K(this.primaryColor),this.secondaryTextColor=K(this.secondaryColor),this.tertiaryTextColor=K(this.tertiaryColor),this.lineColor=K(this.background),this.textColor=K(this.background),this.mainBkg="#eee",this.secondBkg="calculated",this.lineColor="#666",this.border1="#999",this.border2="calculated",this.note="#ffa",this.text="#333",this.critical="#d42",this.done="#bbb",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="white",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor=this.actorBorder,this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="calculated",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="white",this.sectionBkgColor2="calculated",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBkgColor="calculated",this.critBorderColor="calculated",this.todayLineColor="calculated",this.vertLineColor="calculated",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.rowOdd=this.rowOdd||it(this.mainBkg,75)||"#ffffff",this.rowEven=this.rowEven||"#f4f4f4",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}updateColors(){var t,r,i,n,a,o,s,l,c,h,u,d,f,p,g,y,b,x,_,w,C;this.secondBkg=it(this.contrast,55),this.border2=this.contrast,this.actorBorder=it(this.border1,23),this.actorBkg=this.mainBkg,this.actorTextColor=this.text,this.actorLineColor=this.actorBorder,this.signalColor=this.text,this.signalTextColor=this.text,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.text,this.loopTextColor=this.text,this.noteBorderColor="#999",this.noteBkgColor="#666",this.noteTextColor="#fff",this.cScale0=this.cScale0||"#555",this.cScale1=this.cScale1||"#F4F4F4",this.cScale2=this.cScale2||"#555",this.cScale3=this.cScale3||"#BBB",this.cScale4=this.cScale4||"#777",this.cScale5=this.cScale5||"#999",this.cScale6=this.cScale6||"#DDD",this.cScale7=this.cScale7||"#FFF",this.cScale8=this.cScale8||"#DDD",this.cScale9=this.cScale9||"#BBB",this.cScale10=this.cScale10||"#999",this.cScale11=this.cScale11||"#777";for(let v=0;v{this[i]=t[i]}),this.updateColors(),r.forEach(i=>{this[i]=t[i]})}},m(Ji,"Theme"),Ji),Tw=m(e=>{const t=new Sw;return t.calculate(e),t},"getThemeVariables"),$r={base:{getThemeVariables:bw},dark:{getThemeVariables:xw},default:{getThemeVariables:kw},forest:{getThemeVariables:Cw},neutral:{getThemeVariables:Tw}},rr={flowchart:{useMaxWidth:!0,titleTopMargin:25,subGraphTitleMargin:{top:0,bottom:0},diagramPadding:8,htmlLabels:!0,nodeSpacing:50,rankSpacing:50,curve:"basis",padding:15,defaultRenderer:"dagre-wrapper",wrappingWidth:200,inheritDir:!1},sequence:{useMaxWidth:!0,hideUnusedParticipants:!1,activationWidth:10,diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",mirrorActors:!0,forceMenus:!1,bottomMarginAdj:1,rightAngles:!1,showSequenceNumbers:!1,actorFontSize:14,actorFontFamily:'"Open Sans", sans-serif',actorFontWeight:400,noteFontSize:14,noteFontFamily:'"trebuchet ms", verdana, arial, sans-serif',noteFontWeight:400,noteAlign:"center",messageFontSize:16,messageFontFamily:'"trebuchet ms", verdana, arial, sans-serif',messageFontWeight:400,wrap:!1,wrapPadding:10,labelBoxWidth:50,labelBoxHeight:20},gantt:{useMaxWidth:!0,titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,rightPadding:75,leftPadding:75,gridLineStartPadding:35,fontSize:11,sectionFontSize:11,numberSectionStyles:4,axisFormat:"%Y-%m-%d",topAxis:!1,displayMode:"",weekday:"sunday"},journey:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,leftMargin:150,maxLabelWidth:360,width:150,height:50,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,rightAngles:!1,taskFontSize:14,taskFontFamily:'"Open Sans", sans-serif',taskMargin:50,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"],titleColor:"",titleFontFamily:'"trebuchet ms", verdana, arial, sans-serif',titleFontSize:"4ex"},class:{useMaxWidth:!0,titleTopMargin:25,arrowMarkerAbsolute:!1,dividerMargin:10,padding:5,textHeight:10,defaultRenderer:"dagre-wrapper",htmlLabels:!1,hideEmptyMembersBox:!1},state:{useMaxWidth:!0,titleTopMargin:25,dividerMargin:10,sizeUnit:5,padding:8,textHeight:10,titleShift:-15,noteMargin:10,forkWidth:70,forkHeight:7,miniPadding:2,fontSizeFactor:5.02,fontSize:24,labelHeight:16,edgeLengthFactor:"20",compositTitleSize:35,radius:5,defaultRenderer:"dagre-wrapper"},er:{useMaxWidth:!0,titleTopMargin:25,diagramPadding:20,layoutDirection:"TB",minEntityWidth:100,minEntityHeight:75,entityPadding:15,nodeSpacing:140,rankSpacing:80,stroke:"gray",fill:"honeydew",fontSize:12},pie:{useMaxWidth:!0,textPosition:.75},quadrantChart:{useMaxWidth:!0,chartWidth:500,chartHeight:500,titleFontSize:20,titlePadding:10,quadrantPadding:5,xAxisLabelPadding:5,yAxisLabelPadding:5,xAxisLabelFontSize:16,yAxisLabelFontSize:16,quadrantLabelFontSize:16,quadrantTextTopPadding:5,pointTextPadding:5,pointLabelFontSize:12,pointRadius:5,xAxisPosition:"top",yAxisPosition:"left",quadrantInternalBorderStrokeWidth:1,quadrantExternalBorderStrokeWidth:2},xyChart:{useMaxWidth:!0,width:700,height:500,titleFontSize:20,titlePadding:10,showDataLabel:!1,showTitle:!0,xAxis:{$ref:"#/$defs/XYChartAxisConfig",showLabel:!0,labelFontSize:14,labelPadding:5,showTitle:!0,titleFontSize:16,titlePadding:5,showTick:!0,tickLength:5,tickWidth:2,showAxisLine:!0,axisLineWidth:2},yAxis:{$ref:"#/$defs/XYChartAxisConfig",showLabel:!0,labelFontSize:14,labelPadding:5,showTitle:!0,titleFontSize:16,titlePadding:5,showTick:!0,tickLength:5,tickWidth:2,showAxisLine:!0,axisLineWidth:2},chartOrientation:"vertical",plotReservedSpacePercent:50},requirement:{useMaxWidth:!0,rect_fill:"#f9f9f9",text_color:"#333",rect_border_size:"0.5px",rect_border_color:"#bbb",rect_min_width:200,rect_min_height:200,fontSize:14,rect_padding:10,line_height:20},mindmap:{useMaxWidth:!0,padding:10,maxNodeWidth:200,layoutAlgorithm:"cose-bilkent"},kanban:{useMaxWidth:!0,padding:8,sectionWidth:200,ticketBaseUrl:""},timeline:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,leftMargin:150,width:150,height:50,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,rightAngles:!1,taskFontSize:14,taskFontFamily:'"Open Sans", sans-serif',taskMargin:50,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"],disableMulticolor:!1},gitGraph:{useMaxWidth:!0,titleTopMargin:25,diagramPadding:8,nodeLabel:{width:75,height:100,x:-25,y:0},mainBranchName:"main",mainBranchOrder:0,showCommitLabel:!0,showBranches:!0,rotateCommitLabel:!0,parallelCommits:!1,arrowMarkerAbsolute:!1},c4:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,c4ShapeMargin:50,c4ShapePadding:20,width:216,height:60,boxMargin:10,c4ShapeInRow:4,nextLinePaddingX:0,c4BoundaryInRow:2,personFontSize:14,personFontFamily:'"Open Sans", sans-serif',personFontWeight:"normal",external_personFontSize:14,external_personFontFamily:'"Open Sans", sans-serif',external_personFontWeight:"normal",systemFontSize:14,systemFontFamily:'"Open Sans", sans-serif',systemFontWeight:"normal",external_systemFontSize:14,external_systemFontFamily:'"Open Sans", sans-serif',external_systemFontWeight:"normal",system_dbFontSize:14,system_dbFontFamily:'"Open Sans", sans-serif',system_dbFontWeight:"normal",external_system_dbFontSize:14,external_system_dbFontFamily:'"Open Sans", sans-serif',external_system_dbFontWeight:"normal",system_queueFontSize:14,system_queueFontFamily:'"Open Sans", sans-serif',system_queueFontWeight:"normal",external_system_queueFontSize:14,external_system_queueFontFamily:'"Open Sans", sans-serif',external_system_queueFontWeight:"normal",boundaryFontSize:14,boundaryFontFamily:'"Open Sans", sans-serif',boundaryFontWeight:"normal",messageFontSize:12,messageFontFamily:'"Open Sans", sans-serif',messageFontWeight:"normal",containerFontSize:14,containerFontFamily:'"Open Sans", sans-serif',containerFontWeight:"normal",external_containerFontSize:14,external_containerFontFamily:'"Open Sans", sans-serif',external_containerFontWeight:"normal",container_dbFontSize:14,container_dbFontFamily:'"Open Sans", sans-serif',container_dbFontWeight:"normal",external_container_dbFontSize:14,external_container_dbFontFamily:'"Open Sans", sans-serif',external_container_dbFontWeight:"normal",container_queueFontSize:14,container_queueFontFamily:'"Open Sans", sans-serif',container_queueFontWeight:"normal",external_container_queueFontSize:14,external_container_queueFontFamily:'"Open Sans", sans-serif',external_container_queueFontWeight:"normal",componentFontSize:14,componentFontFamily:'"Open Sans", sans-serif',componentFontWeight:"normal",external_componentFontSize:14,external_componentFontFamily:'"Open Sans", sans-serif',external_componentFontWeight:"normal",component_dbFontSize:14,component_dbFontFamily:'"Open Sans", sans-serif',component_dbFontWeight:"normal",external_component_dbFontSize:14,external_component_dbFontFamily:'"Open Sans", sans-serif',external_component_dbFontWeight:"normal",component_queueFontSize:14,component_queueFontFamily:'"Open Sans", sans-serif',component_queueFontWeight:"normal",external_component_queueFontSize:14,external_component_queueFontFamily:'"Open Sans", sans-serif',external_component_queueFontWeight:"normal",wrap:!0,wrapPadding:10,person_bg_color:"#08427B",person_border_color:"#073B6F",external_person_bg_color:"#686868",external_person_border_color:"#8A8A8A",system_bg_color:"#1168BD",system_border_color:"#3C7FC0",system_db_bg_color:"#1168BD",system_db_border_color:"#3C7FC0",system_queue_bg_color:"#1168BD",system_queue_border_color:"#3C7FC0",external_system_bg_color:"#999999",external_system_border_color:"#8A8A8A",external_system_db_bg_color:"#999999",external_system_db_border_color:"#8A8A8A",external_system_queue_bg_color:"#999999",external_system_queue_border_color:"#8A8A8A",container_bg_color:"#438DD5",container_border_color:"#3C7FC0",container_db_bg_color:"#438DD5",container_db_border_color:"#3C7FC0",container_queue_bg_color:"#438DD5",container_queue_border_color:"#3C7FC0",external_container_bg_color:"#B3B3B3",external_container_border_color:"#A6A6A6",external_container_db_bg_color:"#B3B3B3",external_container_db_border_color:"#A6A6A6",external_container_queue_bg_color:"#B3B3B3",external_container_queue_border_color:"#A6A6A6",component_bg_color:"#85BBF0",component_border_color:"#78A8D8",component_db_bg_color:"#85BBF0",component_db_border_color:"#78A8D8",component_queue_bg_color:"#85BBF0",component_queue_border_color:"#78A8D8",external_component_bg_color:"#CCCCCC",external_component_border_color:"#BFBFBF",external_component_db_bg_color:"#CCCCCC",external_component_db_border_color:"#BFBFBF",external_component_queue_bg_color:"#CCCCCC",external_component_queue_border_color:"#BFBFBF"},sankey:{useMaxWidth:!0,width:600,height:400,linkColor:"gradient",nodeAlignment:"justify",showValues:!0,prefix:"",suffix:""},block:{useMaxWidth:!0,padding:8},packet:{useMaxWidth:!0,rowHeight:32,bitWidth:32,bitsPerRow:32,showBits:!0,paddingX:5,paddingY:5},architecture:{useMaxWidth:!0,padding:40,iconSize:80,fontSize:16},radar:{useMaxWidth:!0,width:600,height:600,marginTop:50,marginRight:50,marginBottom:50,marginLeft:50,axisScaleFactor:1,axisLabelFactor:1.05,curveTension:.17},theme:"default",look:"classic",handDrawnSeed:0,layout:"dagre",maxTextSize:5e4,maxEdges:500,darkMode:!1,fontFamily:'"trebuchet ms", verdana, arial, sans-serif;',logLevel:5,securityLevel:"strict",startOnLoad:!0,arrowMarkerAbsolute:!1,secure:["secure","securityLevel","startOnLoad","maxTextSize","suppressErrorRendering","maxEdges"],legacyMathML:!1,forceLegacyMathML:!1,deterministicIds:!1,fontSize:16,markdownAutoWrap:!0,suppressErrorRendering:!1},Jf={...rr,deterministicIDSeed:void 0,elk:{mergeEdges:!1,nodePlacementStrategy:"BRANDES_KOEPF",forceNodeModelOrder:!1,considerModelOrder:"NODES_AND_EDGES"},themeCSS:void 0,themeVariables:$r.default.getThemeVariables(),sequence:{...rr.sequence,messageFont:m(function(){return{fontFamily:this.messageFontFamily,fontSize:this.messageFontSize,fontWeight:this.messageFontWeight}},"messageFont"),noteFont:m(function(){return{fontFamily:this.noteFontFamily,fontSize:this.noteFontSize,fontWeight:this.noteFontWeight}},"noteFont"),actorFont:m(function(){return{fontFamily:this.actorFontFamily,fontSize:this.actorFontSize,fontWeight:this.actorFontWeight}},"actorFont")},class:{hideEmptyMembersBox:!1},gantt:{...rr.gantt,tickInterval:void 0,useWidth:void 0},c4:{...rr.c4,useWidth:void 0,personFont:m(function(){return{fontFamily:this.personFontFamily,fontSize:this.personFontSize,fontWeight:this.personFontWeight}},"personFont"),flowchart:{...rr.flowchart,inheritDir:!1},external_personFont:m(function(){return{fontFamily:this.external_personFontFamily,fontSize:this.external_personFontSize,fontWeight:this.external_personFontWeight}},"external_personFont"),systemFont:m(function(){return{fontFamily:this.systemFontFamily,fontSize:this.systemFontSize,fontWeight:this.systemFontWeight}},"systemFont"),external_systemFont:m(function(){return{fontFamily:this.external_systemFontFamily,fontSize:this.external_systemFontSize,fontWeight:this.external_systemFontWeight}},"external_systemFont"),system_dbFont:m(function(){return{fontFamily:this.system_dbFontFamily,fontSize:this.system_dbFontSize,fontWeight:this.system_dbFontWeight}},"system_dbFont"),external_system_dbFont:m(function(){return{fontFamily:this.external_system_dbFontFamily,fontSize:this.external_system_dbFontSize,fontWeight:this.external_system_dbFontWeight}},"external_system_dbFont"),system_queueFont:m(function(){return{fontFamily:this.system_queueFontFamily,fontSize:this.system_queueFontSize,fontWeight:this.system_queueFontWeight}},"system_queueFont"),external_system_queueFont:m(function(){return{fontFamily:this.external_system_queueFontFamily,fontSize:this.external_system_queueFontSize,fontWeight:this.external_system_queueFontWeight}},"external_system_queueFont"),containerFont:m(function(){return{fontFamily:this.containerFontFamily,fontSize:this.containerFontSize,fontWeight:this.containerFontWeight}},"containerFont"),external_containerFont:m(function(){return{fontFamily:this.external_containerFontFamily,fontSize:this.external_containerFontSize,fontWeight:this.external_containerFontWeight}},"external_containerFont"),container_dbFont:m(function(){return{fontFamily:this.container_dbFontFamily,fontSize:this.container_dbFontSize,fontWeight:this.container_dbFontWeight}},"container_dbFont"),external_container_dbFont:m(function(){return{fontFamily:this.external_container_dbFontFamily,fontSize:this.external_container_dbFontSize,fontWeight:this.external_container_dbFontWeight}},"external_container_dbFont"),container_queueFont:m(function(){return{fontFamily:this.container_queueFontFamily,fontSize:this.container_queueFontSize,fontWeight:this.container_queueFontWeight}},"container_queueFont"),external_container_queueFont:m(function(){return{fontFamily:this.external_container_queueFontFamily,fontSize:this.external_container_queueFontSize,fontWeight:this.external_container_queueFontWeight}},"external_container_queueFont"),componentFont:m(function(){return{fontFamily:this.componentFontFamily,fontSize:this.componentFontSize,fontWeight:this.componentFontWeight}},"componentFont"),external_componentFont:m(function(){return{fontFamily:this.external_componentFontFamily,fontSize:this.external_componentFontSize,fontWeight:this.external_componentFontWeight}},"external_componentFont"),component_dbFont:m(function(){return{fontFamily:this.component_dbFontFamily,fontSize:this.component_dbFontSize,fontWeight:this.component_dbFontWeight}},"component_dbFont"),external_component_dbFont:m(function(){return{fontFamily:this.external_component_dbFontFamily,fontSize:this.external_component_dbFontSize,fontWeight:this.external_component_dbFontWeight}},"external_component_dbFont"),component_queueFont:m(function(){return{fontFamily:this.component_queueFontFamily,fontSize:this.component_queueFontSize,fontWeight:this.component_queueFontWeight}},"component_queueFont"),external_component_queueFont:m(function(){return{fontFamily:this.external_component_queueFontFamily,fontSize:this.external_component_queueFontSize,fontWeight:this.external_component_queueFontWeight}},"external_component_queueFont"),boundaryFont:m(function(){return{fontFamily:this.boundaryFontFamily,fontSize:this.boundaryFontSize,fontWeight:this.boundaryFontWeight}},"boundaryFont"),messageFont:m(function(){return{fontFamily:this.messageFontFamily,fontSize:this.messageFontSize,fontWeight:this.messageFontWeight}},"messageFont")},pie:{...rr.pie,useWidth:984},xyChart:{...rr.xyChart,useWidth:void 0},requirement:{...rr.requirement,useWidth:void 0},packet:{...rr.packet},radar:{...rr.radar},treemap:{useMaxWidth:!0,padding:10,diagramPadding:8,showValues:!0,nodeWidth:100,nodeHeight:40,borderWidth:1,valueFontSize:12,labelFontSize:14,valueFormat:","}},tp=m((e,t="")=>Object.keys(e).reduce((r,i)=>Array.isArray(e[i])?r:typeof e[i]=="object"&&e[i]!==null?[...r,t+i,...tp(e[i],"")]:[...r,t+i],[]),"keyify"),Mw=new Set(tp(Jf,"")),ep=Jf,ms=m(e=>{if(V.debug("sanitizeDirective called with",e),!(typeof e!="object"||e==null)){if(Array.isArray(e)){e.forEach(t=>ms(t));return}for(const t of Object.keys(e)){if(V.debug("Checking key",t),t.startsWith("__")||t.includes("proto")||t.includes("constr")||!Mw.has(t)||e[t]==null){V.debug("sanitize deleting key: ",t),delete e[t];continue}if(typeof e[t]=="object"){V.debug("sanitizing object",t),ms(e[t]);continue}const r=["themeCSS","fontFamily","altFontFamily"];for(const i of r)t.includes(i)&&(V.debug("sanitizing css option",t),e[t]=$w(e[t]))}if(e.themeVariables)for(const t of Object.keys(e.themeVariables)){const r=e.themeVariables[t];r!=null&&r.match&&!r.match(/^[\d "#%(),.;A-Za-z]+$/)&&(e.themeVariables[t]="")}V.debug("After sanitization",e)}},"sanitizeDirective"),$w=m(e=>{let t=0,r=0;for(const i of e){if(t{let r=ee({},e),i={};for(const n of t)np(n),i=ee(i,n);if(r=ee(r,i),i.theme&&i.theme in $r){const n=ee({},ys),a=ee(n.themeVariables||{},i.themeVariables);r.theme&&r.theme in $r&&(r.themeVariables=$r[r.theme].getThemeVariables(a))}return ta=r,ap(ta),ta},"updateCurrentConfig"),Lw=m(e=>(Ee=ee({},nn),Ee=ee(Ee,e),e.theme&&$r[e.theme]&&(Ee.themeVariables=$r[e.theme].getThemeVariables(e.themeVariables)),_o(Ee,gi),Ee),"setSiteConfig"),Aw=m(e=>{ys=ee({},e)},"saveConfigFromInitialize"),Bw=m(e=>(Ee=ee(Ee,e),_o(Ee,gi),Ee),"updateSiteConfig"),rp=m(()=>ee({},Ee),"getSiteConfig"),ip=m(e=>(ap(e),ee(ta,e),be()),"setConfig"),be=m(()=>ee({},ta),"getConfig"),np=m(e=>{e&&(["secure",...Ee.secure??[]].forEach(t=>{Object.hasOwn(e,t)&&(V.debug(`Denied attempt to modify a secure key ${t}`,e[t]),delete e[t])}),Object.keys(e).forEach(t=>{t.startsWith("__")&&delete e[t]}),Object.keys(e).forEach(t=>{typeof e[t]=="string"&&(e[t].includes("<")||e[t].includes(">")||e[t].includes("url(data:"))&&delete e[t],typeof e[t]=="object"&&np(e[t])}))},"sanitize"),Ew=m(e=>{var t;ms(e),e.fontFamily&&!((t=e.themeVariables)!=null&&t.fontFamily)&&(e.themeVariables={...e.themeVariables,fontFamily:e.fontFamily}),gi.push(e),_o(Ee,gi)},"addDirective"),bs=m((e=Ee)=>{gi=[],_o(e,gi)},"reset"),Fw={LAZY_LOAD_DEPRECATED:"The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead."},Pu={},Pw=m(e=>{Pu[e]||(V.warn(Fw[e]),Pu[e]=!0)},"issueWarning"),ap=m(e=>{e&&(e.lazyLoadedDiagrams||e.loadExternalDiagramsAtStartup)&&Pw("LAZY_LOAD_DEPRECATED")},"checkConfig"),AO=m(()=>{let e={};ys&&(e=ee(e,ys));for(const t of gi)e=ee(e,t);return e},"getUserDefinedConfig"),xa=//gi,Dw=m(e=>e?lp(e).replace(/\\n/g,"#br#").split("#br#"):[""],"getRows"),Iw=(()=>{let e=!1;return()=>{e||(sp(),e=!0)}})();function sp(){const e="data-temp-href-target";rn.addHook("beforeSanitizeAttributes",t=>{t.tagName==="A"&&t.hasAttribute("target")&&t.setAttribute(e,t.getAttribute("target")??"")}),rn.addHook("afterSanitizeAttributes",t=>{t.tagName==="A"&&t.hasAttribute(e)&&(t.setAttribute("target",t.getAttribute(e)??""),t.removeAttribute(e),t.getAttribute("target")==="_blank"&&t.setAttribute("rel","noopener"))})}m(sp,"setupDompurifyHooks");var op=m(e=>(Iw(),rn.sanitize(e)),"removeScript"),Du=m((e,t)=>{var r;if(((r=t.flowchart)==null?void 0:r.htmlLabels)!==!1){const i=t.securityLevel;i==="antiscript"||i==="strict"?e=op(e):i!=="loose"&&(e=lp(e),e=e.replace(//g,">"),e=e.replace(/=/g,"="),e=zw(e))}return e},"sanitizeMore"),qe=m((e,t)=>e&&(t.dompurifyConfig?e=rn.sanitize(Du(e,t),t.dompurifyConfig).toString():e=rn.sanitize(Du(e,t),{FORBID_TAGS:["style"]}).toString(),e),"sanitizeText"),Ow=m((e,t)=>typeof e=="string"?qe(e,t):e.flat().map(r=>qe(r,t)),"sanitizeTextOrArray"),Rw=m(e=>xa.test(e),"hasBreaks"),Nw=m(e=>e.split(xa),"splitBreaks"),zw=m(e=>e.replace(/#br#/g,"
"),"placeholderToBreak"),lp=m(e=>e.replace(xa,"#br#"),"breakToPlaceholder"),qw=m(e=>{let t="";return e&&(t=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,t=CSS.escape(t)),t},"getUrl"),ie=m(e=>!(e===!1||["false","null","0"].includes(String(e).trim().toLowerCase())),"evaluate"),Ww=m(function(...e){const t=e.filter(r=>!isNaN(r));return Math.max(...t)},"getMax"),Hw=m(function(...e){const t=e.filter(r=>!isNaN(r));return Math.min(...t)},"getMin"),Iu=m(function(e){const t=e.split(/(,)/),r=[];for(let i=0;i0&&i+1Math.max(0,e.split(t).length-1),"countOccurrence"),Vw=m((e,t)=>{const r=jl(e,"~"),i=jl(t,"~");return r===1&&i===1},"shouldCombineSets"),Uw=m(e=>{const t=jl(e,"~");let r=!1;if(t<=1)return e;t%2!==0&&e.startsWith("~")&&(e=e.substring(1),r=!0);const i=[...e];let n=i.indexOf("~"),a=i.lastIndexOf("~");for(;n!==-1&&a!==-1&&n!==a;)i[n]="<",i[a]=">",n=i.indexOf("~"),a=i.lastIndexOf("~");return r&&i.unshift("~"),i.join("")},"processSet"),Ou=m(()=>window.MathMLElement!==void 0,"isMathMLSupported"),Yl=/\$\$(.*)\$\$/g,an=m(e=>{var t;return(((t=e.match(Yl))==null?void 0:t.length)??0)>0},"hasKatex"),BO=m(async(e,t)=>{const r=document.createElement("div");r.innerHTML=await ih(e,t),r.id="katex-temp",r.style.visibility="hidden",r.style.position="absolute",r.style.top="0";const i=document.querySelector("body");i==null||i.insertAdjacentElement("beforeend",r);const n={width:r.clientWidth,height:r.clientHeight};return r.remove(),n},"calculateMathMLDimensions"),jw=m(async(e,t)=>{if(!an(e))return e;if(!(Ou()||t.legacyMathML||t.forceLegacyMathML))return e.replace(Yl,"MathML is unsupported in this environment.");{const{default:r}=await Rt(async()=>{const{default:n}=await import("./katex.CBSAILhF.js");return{default:n}},[]),i=t.forceLegacyMathML||!Ou()&&t.legacyMathML?"htmlAndMathml":"mathml";return e.split(xa).map(n=>an(n)?`
${n}
`:`
${n}
`).join("").replace(Yl,(n,a)=>r.renderToString(a,{throwOnError:!0,displayMode:!0,output:i}).replace(/\n/g," ").replace(//g,""))}},"renderKatexUnsanitized"),ih=m(async(e,t)=>qe(await jw(e,t),t),"renderKatexSanitized"),yn={getRows:Dw,sanitizeText:qe,sanitizeTextOrArray:Ow,hasBreaks:Rw,splitBreaks:Nw,lineBreakRegex:xa,removeScript:op,getUrl:qw,evaluate:ie,getMax:Ww,getMin:Hw},Yw=m(function(e,t){for(let r of t)e.attr(r[0],r[1])},"d3Attrs"),Gw=m(function(e,t,r){let i=new Map;return r?(i.set("width","100%"),i.set("style",`max-width: ${t}px;`)):(i.set("height",e),i.set("width",t)),i},"calculateSvgSizeAttrs"),cp=m(function(e,t,r,i){const n=Gw(t,r,i);Yw(e,n)},"configureSvgSize"),Xw=m(function(e,t,r,i){const n=t.node().getBBox(),a=n.width,o=n.height;V.info(`SVG bounds: ${a}x${o}`,n);let s=0,l=0;V.info(`Graph bounds: ${s}x${l}`,e),s=a+r*2,l=o+r*2,V.info(`Calculated bounds: ${s}x${l}`),cp(t,l,s,i);const c=`${n.x-r} ${n.y-r} ${n.width+2*r} ${n.height+2*r}`;t.attr("viewBox",c)},"setupGraphViewbox"),Ja={},Zw=m((e,t,r)=>{let i="";return e in Ja&&Ja[e]?i=Ja[e](r):V.warn(`No theme found for ${e}`),` & { + font-family: ${r.fontFamily}; + font-size: ${r.fontSize}; + fill: ${r.textColor} + } + @keyframes edge-animation-frame { + from { + stroke-dashoffset: 0; + } + } + @keyframes dash { + to { + stroke-dashoffset: 0; + } + } + & .edge-animation-slow { + stroke-dasharray: 9,5 !important; + stroke-dashoffset: 900; + animation: dash 50s linear infinite; + stroke-linecap: round; + } + & .edge-animation-fast { + stroke-dasharray: 9,5 !important; + stroke-dashoffset: 900; + animation: dash 20s linear infinite; + stroke-linecap: round; + } + /* Classes common for multiple diagrams */ + + & .error-icon { + fill: ${r.errorBkgColor}; + } + & .error-text { + fill: ${r.errorTextColor}; + stroke: ${r.errorTextColor}; + } + + & .edge-thickness-normal { + stroke-width: 1px; + } + & .edge-thickness-thick { + stroke-width: 3.5px + } + & .edge-pattern-solid { + stroke-dasharray: 0; + } + & .edge-thickness-invisible { + stroke-width: 0; + fill: none; + } + & .edge-pattern-dashed{ + stroke-dasharray: 3; + } + .edge-pattern-dotted { + stroke-dasharray: 2; + } + + & .marker { + fill: ${r.lineColor}; + stroke: ${r.lineColor}; + } + & .marker.cross { + stroke: ${r.lineColor}; + } + + & svg { + font-family: ${r.fontFamily}; + font-size: ${r.fontSize}; + } + & p { + margin: 0 + } + + ${i} + + ${t} +`},"getStyles"),Kw=m((e,t)=>{t!==void 0&&(Ja[e]=t)},"addStylesForDiagram"),Qw=Zw,hp={};zk(hp,{clear:()=>Jw,getAccDescription:()=>iC,getAccTitle:()=>eC,getDiagramTitle:()=>aC,setAccDescription:()=>rC,setAccTitle:()=>tC,setDiagramTitle:()=>nC});var nh="",ah="",sh="",oh=m(e=>qe(e,be()),"sanitizeText"),Jw=m(()=>{nh="",sh="",ah=""},"clear"),tC=m(e=>{nh=oh(e).replace(/^\s+/g,"")},"setAccTitle"),eC=m(()=>nh,"getAccTitle"),rC=m(e=>{sh=oh(e).replace(/\n\s+/g,` +`)},"setAccDescription"),iC=m(()=>sh,"getAccDescription"),nC=m(e=>{ah=oh(e)},"setDiagramTitle"),aC=m(()=>ah,"getDiagramTitle"),Ru=V,sC=eh,qt=be,EO=ip,FO=nn,lh=m(e=>qe(e,qt()),"sanitizeText"),oC=Xw,lC=m(()=>hp,"getCommonDb"),vs={},xs=m((e,t,r)=>{var i;vs[e]&&Ru.warn(`Diagram with id ${e} already registered. Overwriting.`),vs[e]=t,r&&Qf(e,r),Kw(e,t.styles),(i=t.injectUtils)==null||i.call(t,Ru,sC,qt,lh,oC,lC(),()=>{})},"registerDiagram"),Gl=m(e=>{if(e in vs)return vs[e];throw new cC(e)},"getDiagram"),tn,cC=(tn=class extends Error{constructor(t){super(`Diagram ${t} not found.`)}},m(tn,"DiagramNotFoundError"),tn);function ts(e,t){return e==null||t==null?NaN:et?1:e>=t?0:NaN}function hC(e,t){return e==null||t==null?NaN:te?1:t>=e?0:NaN}function ch(e){let t,r,i;e.length!==2?(t=ts,r=(s,l)=>ts(e(s),l),i=(s,l)=>e(s)-l):(t=e===ts||e===hC?e:uC,r=e,i=e);function n(s,l,c=0,h=s.length){if(c>>1;r(s[u],l)<0?c=u+1:h=u}while(c>>1;r(s[u],l)<=0?c=u+1:h=u}while(cc&&i(s[u-1],l)>-i(s[u],l)?u-1:u}return{left:n,center:o,right:a}}function uC(){return 0}function dC(e){return e===null?NaN:+e}const fC=ch(ts),pC=fC.right;ch(dC).center;class Nu extends Map{constructor(t,r=yC){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),t!=null)for(const[i,n]of t)this.set(i,n)}get(t){return super.get(zu(this,t))}has(t){return super.has(zu(this,t))}set(t,r){return super.set(gC(this,t),r)}delete(t){return super.delete(mC(this,t))}}function zu({_intern:e,_key:t},r){const i=t(r);return e.has(i)?e.get(i):r}function gC({_intern:e,_key:t},r){const i=t(r);return e.has(i)?e.get(i):(e.set(i,r),r)}function mC({_intern:e,_key:t},r){const i=t(r);return e.has(i)&&(r=e.get(r),e.delete(i)),r}function yC(e){return e!==null&&typeof e=="object"?e.valueOf():e}const bC=Math.sqrt(50),vC=Math.sqrt(10),xC=Math.sqrt(2);function _s(e,t,r){const i=(t-e)/Math.max(0,r),n=Math.floor(Math.log10(i)),a=i/Math.pow(10,n),o=a>=bC?10:a>=vC?5:a>=xC?2:1;let s,l,c;return n<0?(c=Math.pow(10,-n)/o,s=Math.round(e*c),l=Math.round(t*c),s/ct&&--l,c=-c):(c=Math.pow(10,n)*o,s=Math.round(e/c),l=Math.round(t/c),s*ct&&--l),l0))return[];if(e===t)return[e];const i=t=n))return[];const s=a-n+1,l=new Array(s);if(i)if(o<0)for(let c=0;c=i)&&(r=i);else{let i=-1;for(let n of e)(n=t(n,++i,e))!=null&&(r=n)&&(r=n)}return r}function DO(e,t){let r;if(t===void 0)for(const i of e)i!=null&&(r>i||r===void 0&&i>=i)&&(r=i);else{let i=-1;for(let n of e)(n=t(n,++i,e))!=null&&(r>n||r===void 0&&n>=n)&&(r=n)}return r}function kC(e,t,r){e=+e,t=+t,r=(n=arguments.length)<2?(t=e,e=0,1):n<3?1:+r;for(var i=-1,n=Math.max(0,Math.ceil((t-e)/r))|0,a=new Array(n);++i+e(t)}function MC(e,t){return t=Math.max(0,e.bandwidth()-t*2)/2,e.round()&&(t=Math.round(t)),r=>+e(r)+t}function $C(){return!this.__axis}function up(e,t){var r=[],i=null,n=null,a=6,o=6,s=3,l=typeof window<"u"&&window.devicePixelRatio>1?0:.5,c=e===es||e===Ia?-1:1,h=e===Ia||e===ll?"x":"y",u=e===es||e===Kl?CC:SC;function d(f){var p=i??(t.ticks?t.ticks.apply(t,r):t.domain()),g=n??(t.tickFormat?t.tickFormat.apply(t,r):wC),y=Math.max(a,0)+s,b=t.range(),x=+b[0]+l,_=+b[b.length-1]+l,w=(t.bandwidth?MC:TC)(t.copy(),l),C=f.selection?f.selection():f,v=C.selectAll(".domain").data([null]),k=C.selectAll(".tick").data(p,t).order(),$=k.exit(),z=k.enter().append("g").attr("class","tick"),W=k.select("line"),O=k.select("text");v=v.merge(v.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),k=k.merge(z),W=W.merge(z.append("line").attr("stroke","currentColor").attr(h+"2",c*a)),O=O.merge(z.append("text").attr("fill","currentColor").attr(h,c*y).attr("dy",e===es?"0em":e===Kl?"0.71em":"0.32em")),f!==C&&(v=v.transition(f),k=k.transition(f),W=W.transition(f),O=O.transition(f),$=$.transition(f).attr("opacity",qu).attr("transform",function(N){return isFinite(N=w(N))?u(N+l):this.getAttribute("transform")}),z.attr("opacity",qu).attr("transform",function(N){var D=this.parentNode.__axis;return u((D&&isFinite(D=D(N))?D:w(N))+l)})),$.remove(),v.attr("d",e===Ia||e===ll?o?"M"+c*o+","+x+"H"+l+"V"+_+"H"+c*o:"M"+l+","+x+"V"+_:o?"M"+x+","+c*o+"V"+l+"H"+_+"V"+c*o:"M"+x+","+l+"H"+_),k.attr("opacity",1).attr("transform",function(N){return u(w(N)+l)}),W.attr(h+"2",c*a),O.attr(h,c*y).text(g),C.filter($C).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",e===ll?"start":e===Ia?"end":"middle"),C.each(function(){this.__axis=w})}return d.scale=function(f){return arguments.length?(t=f,d):t},d.ticks=function(){return r=Array.from(arguments),d},d.tickArguments=function(f){return arguments.length?(r=f==null?[]:Array.from(f),d):r.slice()},d.tickValues=function(f){return arguments.length?(i=f==null?null:Array.from(f),d):i&&i.slice()},d.tickFormat=function(f){return arguments.length?(n=f,d):n},d.tickSize=function(f){return arguments.length?(a=o=+f,d):a},d.tickSizeInner=function(f){return arguments.length?(a=+f,d):a},d.tickSizeOuter=function(f){return arguments.length?(o=+f,d):o},d.tickPadding=function(f){return arguments.length?(s=+f,d):s},d.offset=function(f){return arguments.length?(l=+f,d):l},d}function IO(e){return up(es,e)}function OO(e){return up(Kl,e)}var LC={value:()=>{}};function dp(){for(var e=0,t=arguments.length,r={},i;e=0&&(i=r.slice(n+1),r=r.slice(0,n)),r&&!t.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:i}})}rs.prototype=dp.prototype={constructor:rs,on:function(e,t){var r=this._,i=AC(e+"",r),n,a=-1,o=i.length;if(arguments.length<2){for(;++a0)for(var r=new Array(n),i=0,n,a;i=0&&(t=e.slice(0,r))!=="xmlns"&&(e=e.slice(r+1)),Hu.hasOwnProperty(t)?{space:Hu[t],local:e}:e}function EC(e){return function(){var t=this.ownerDocument,r=this.namespaceURI;return r===Ql&&t.documentElement.namespaceURI===Ql?t.createElement(e):t.createElementNS(r,e)}}function FC(e){return function(){return this.ownerDocument.createElementNS(e.space,e.local)}}function fp(e){var t=ko(e);return(t.local?FC:EC)(t)}function PC(){}function hh(e){return e==null?PC:function(){return this.querySelector(e)}}function DC(e){typeof e!="function"&&(e=hh(e));for(var t=this._groups,r=t.length,i=new Array(r),n=0;n=_&&(_=x+1);!(C=y[_])&&++_=0;)(o=i[n])&&(a&&o.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(o,a),a=o);return this}function sS(e){e||(e=oS);function t(u,d){return u&&d?e(u.__data__,d.__data__):!u-!d}for(var r=this._groups,i=r.length,n=new Array(i),a=0;at?1:e>=t?0:NaN}function lS(){var e=arguments[0];return arguments[0]=this,e.apply(null,arguments),this}function cS(){return Array.from(this)}function hS(){for(var e=this._groups,t=0,r=e.length;t1?this.each((t==null?_S:typeof t=="function"?wS:kS)(e,t,r??"")):sn(this.node(),e)}function sn(e,t){return e.style.getPropertyValue(t)||bp(e).getComputedStyle(e,null).getPropertyValue(t)}function SS(e){return function(){delete this[e]}}function TS(e,t){return function(){this[e]=t}}function MS(e,t){return function(){var r=t.apply(this,arguments);r==null?delete this[e]:this[e]=r}}function $S(e,t){return arguments.length>1?this.each((t==null?SS:typeof t=="function"?MS:TS)(e,t)):this.node()[e]}function vp(e){return e.trim().split(/^|\s+/)}function uh(e){return e.classList||new xp(e)}function xp(e){this._node=e,this._names=vp(e.getAttribute("class")||"")}xp.prototype={add:function(e){var t=this._names.indexOf(e);t<0&&(this._names.push(e),this._node.setAttribute("class",this._names.join(" ")))},remove:function(e){var t=this._names.indexOf(e);t>=0&&(this._names.splice(t,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(e){return this._names.indexOf(e)>=0}};function _p(e,t){for(var r=uh(e),i=-1,n=t.length;++i=0&&(r=t.slice(i+1),t=t.slice(0,i)),{type:t,name:r}})}function rT(e){return function(){var t=this.__on;if(t){for(var r=0,i=-1,n=t.length,a;r>8&15|t>>4&240,t>>4&15|t&240,(t&15)<<4|t&15,1):r===8?Oa(t>>24&255,t>>16&255,t>>8&255,(t&255)/255):r===4?Oa(t>>12&15|t>>8&240,t>>8&15|t>>4&240,t>>4&15|t&240,((t&15)<<4|t&15)/255):null):(t=uT.exec(e))?new ye(t[1],t[2],t[3],1):(t=dT.exec(e))?new ye(t[1]*255/100,t[2]*255/100,t[3]*255/100,1):(t=fT.exec(e))?Oa(t[1],t[2],t[3],t[4]):(t=pT.exec(e))?Oa(t[1]*255/100,t[2]*255/100,t[3]*255/100,t[4]):(t=gT.exec(e))?Zu(t[1],t[2]/100,t[3]/100,1):(t=mT.exec(e))?Zu(t[1],t[2]/100,t[3]/100,t[4]):Vu.hasOwnProperty(e)?Yu(Vu[e]):e==="transparent"?new ye(NaN,NaN,NaN,0):null}function Yu(e){return new ye(e>>16&255,e>>8&255,e&255,1)}function Oa(e,t,r,i){return i<=0&&(e=t=r=NaN),new ye(e,t,r,i)}function Sp(e){return e instanceof Ci||(e=mi(e)),e?(e=e.rgb(),new ye(e.r,e.g,e.b,e.opacity)):new ye}function Jl(e,t,r,i){return arguments.length===1?Sp(e):new ye(e,t,r,i??1)}function ye(e,t,r,i){this.r=+e,this.g=+t,this.b=+r,this.opacity=+i}ka(ye,Jl,wo(Ci,{brighter(e){return e=e==null?ws:Math.pow(ws,e),new ye(this.r*e,this.g*e,this.b*e,this.opacity)},darker(e){return e=e==null?na:Math.pow(na,e),new ye(this.r*e,this.g*e,this.b*e,this.opacity)},rgb(){return this},clamp(){return new ye(ui(this.r),ui(this.g),ui(this.b),Cs(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Gu,formatHex:Gu,formatHex8:vT,formatRgb:Xu,toString:Xu}));function Gu(){return`#${oi(this.r)}${oi(this.g)}${oi(this.b)}`}function vT(){return`#${oi(this.r)}${oi(this.g)}${oi(this.b)}${oi((isNaN(this.opacity)?1:this.opacity)*255)}`}function Xu(){const e=Cs(this.opacity);return`${e===1?"rgb(":"rgba("}${ui(this.r)}, ${ui(this.g)}, ${ui(this.b)}${e===1?")":`, ${e})`}`}function Cs(e){return isNaN(e)?1:Math.max(0,Math.min(1,e))}function ui(e){return Math.max(0,Math.min(255,Math.round(e)||0))}function oi(e){return e=ui(e),(e<16?"0":"")+e.toString(16)}function Zu(e,t,r,i){return i<=0?e=t=r=NaN:r<=0||r>=1?e=t=NaN:t<=0&&(e=NaN),new Ge(e,t,r,i)}function Tp(e){if(e instanceof Ge)return new Ge(e.h,e.s,e.l,e.opacity);if(e instanceof Ci||(e=mi(e)),!e)return new Ge;if(e instanceof Ge)return e;e=e.rgb();var t=e.r/255,r=e.g/255,i=e.b/255,n=Math.min(t,r,i),a=Math.max(t,r,i),o=NaN,s=a-n,l=(a+n)/2;return s?(t===a?o=(r-i)/s+(r0&&l<1?0:o,new Ge(o,s,l,e.opacity)}function xT(e,t,r,i){return arguments.length===1?Tp(e):new Ge(e,t,r,i??1)}function Ge(e,t,r,i){this.h=+e,this.s=+t,this.l=+r,this.opacity=+i}ka(Ge,xT,wo(Ci,{brighter(e){return e=e==null?ws:Math.pow(ws,e),new Ge(this.h,this.s,this.l*e,this.opacity)},darker(e){return e=e==null?na:Math.pow(na,e),new Ge(this.h,this.s,this.l*e,this.opacity)},rgb(){var e=this.h%360+(this.h<0)*360,t=isNaN(e)||isNaN(this.s)?0:this.s,r=this.l,i=r+(r<.5?r:1-r)*t,n=2*r-i;return new ye(cl(e>=240?e-240:e+120,n,i),cl(e,n,i),cl(e<120?e+240:e-120,n,i),this.opacity)},clamp(){return new Ge(Ku(this.h),Ra(this.s),Ra(this.l),Cs(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const e=Cs(this.opacity);return`${e===1?"hsl(":"hsla("}${Ku(this.h)}, ${Ra(this.s)*100}%, ${Ra(this.l)*100}%${e===1?")":`, ${e})`}`}}));function Ku(e){return e=(e||0)%360,e<0?e+360:e}function Ra(e){return Math.max(0,Math.min(1,e||0))}function cl(e,t,r){return(e<60?t+(r-t)*e/60:e<180?r:e<240?t+(r-t)*(240-e)/60:t)*255}const _T=Math.PI/180,kT=180/Math.PI,Ss=18,Mp=.96422,$p=1,Lp=.82521,Ap=4/29,ji=6/29,Bp=3*ji*ji,wT=ji*ji*ji;function Ep(e){if(e instanceof dr)return new dr(e.l,e.a,e.b,e.opacity);if(e instanceof Sr)return Fp(e);e instanceof ye||(e=Sp(e));var t=fl(e.r),r=fl(e.g),i=fl(e.b),n=hl((.2225045*t+.7168786*r+.0606169*i)/$p),a,o;return t===r&&r===i?a=o=n:(a=hl((.4360747*t+.3850649*r+.1430804*i)/Mp),o=hl((.0139322*t+.0971045*r+.7141733*i)/Lp)),new dr(116*n-16,500*(a-n),200*(n-o),e.opacity)}function CT(e,t,r,i){return arguments.length===1?Ep(e):new dr(e,t,r,i??1)}function dr(e,t,r,i){this.l=+e,this.a=+t,this.b=+r,this.opacity=+i}ka(dr,CT,wo(Ci,{brighter(e){return new dr(this.l+Ss*(e??1),this.a,this.b,this.opacity)},darker(e){return new dr(this.l-Ss*(e??1),this.a,this.b,this.opacity)},rgb(){var e=(this.l+16)/116,t=isNaN(this.a)?e:e+this.a/500,r=isNaN(this.b)?e:e-this.b/200;return t=Mp*ul(t),e=$p*ul(e),r=Lp*ul(r),new ye(dl(3.1338561*t-1.6168667*e-.4906146*r),dl(-.9787684*t+1.9161415*e+.033454*r),dl(.0719453*t-.2289914*e+1.4052427*r),this.opacity)}}));function hl(e){return e>wT?Math.pow(e,1/3):e/Bp+Ap}function ul(e){return e>ji?e*e*e:Bp*(e-Ap)}function dl(e){return 255*(e<=.0031308?12.92*e:1.055*Math.pow(e,1/2.4)-.055)}function fl(e){return(e/=255)<=.04045?e/12.92:Math.pow((e+.055)/1.055,2.4)}function ST(e){if(e instanceof Sr)return new Sr(e.h,e.c,e.l,e.opacity);if(e instanceof dr||(e=Ep(e)),e.a===0&&e.b===0)return new Sr(NaN,0()=>e;function Pp(e,t){return function(r){return e+r*t}}function TT(e,t,r){return e=Math.pow(e,r),t=Math.pow(t,r)-e,r=1/r,function(i){return Math.pow(e+i*t,r)}}function MT(e,t){var r=t-e;return r?Pp(e,r>180||r<-180?r-360*Math.round(r/360):r):Co(isNaN(e)?t:e)}function $T(e){return(e=+e)==1?ea:function(t,r){return r-t?TT(t,r,e):Co(isNaN(t)?r:t)}}function ea(e,t){var r=t-e;return r?Pp(e,r):Co(isNaN(e)?t:e)}const Ts=function e(t){var r=$T(t);function i(n,a){var o=r((n=Jl(n)).r,(a=Jl(a)).r),s=r(n.g,a.g),l=r(n.b,a.b),c=ea(n.opacity,a.opacity);return function(h){return n.r=o(h),n.g=s(h),n.b=l(h),n.opacity=c(h),n+""}}return i.gamma=e,i}(1);function LT(e,t){t||(t=[]);var r=e?Math.min(t.length,e.length):0,i=t.slice(),n;return function(a){for(n=0;nr&&(a=t.slice(r,a),s[o]?s[o]+=a:s[++o]=a),(i=i[0])===(n=n[0])?s[o]?s[o]+=n:s[++o]=n:(s[++o]=null,l.push({i:o,x:Ue(i,n)})),r=pl.lastIndex;return r180?h+=360:h-c>180&&(c+=360),d.push({i:u.push(n(u)+"rotate(",null,i)-2,x:Ue(c,h)})):h&&u.push(n(u)+"rotate("+h+i)}function s(c,h,u,d){c!==h?d.push({i:u.push(n(u)+"skewX(",null,i)-2,x:Ue(c,h)}):h&&u.push(n(u)+"skewX("+h+i)}function l(c,h,u,d,f,p){if(c!==u||h!==d){var g=f.push(n(f)+"scale(",null,",",null,")");p.push({i:g-4,x:Ue(c,u)},{i:g-2,x:Ue(h,d)})}else(u!==1||d!==1)&&f.push(n(f)+"scale("+u+","+d+")")}return function(c,h){var u=[],d=[];return c=e(c),h=e(h),a(c.translateX,c.translateY,h.translateX,h.translateY,u,d),o(c.rotate,h.rotate,u,d),s(c.skewX,h.skewX,u,d),l(c.scaleX,c.scaleY,h.scaleX,h.scaleY,u,d),c=h=null,function(f){for(var p=-1,g=d.length,y;++p=0&&e._call.call(void 0,t),e=e._next;--on}function Ju(){yi=($s=sa.now())+So,on=Hn=0;try{HT()}finally{on=0,UT(),yi=0}}function VT(){var e=sa.now(),t=e-$s;t>Rp&&(So-=t,$s=e)}function UT(){for(var e,t=Ms,r,i=1/0;t;)t._call?(i>t._time&&(i=t._time),e=t,t=t._next):(r=t._next,t._next=null,t=e?e._next=r:Ms=r);Vn=e,ic(i)}function ic(e){if(!on){Hn&&(Hn=clearTimeout(Hn));var t=e-yi;t>24?(e<1/0&&(Hn=setTimeout(Ju,e-sa.now()-So)),An&&(An=clearInterval(An))):(An||($s=sa.now(),An=setInterval(VT,Rp)),on=1,Np(Ju))}}function td(e,t,r){var i=new Ls;return t=t==null?0:+t,i.restart(n=>{i.stop(),e(n+t)},t,r),i}var jT=dp("start","end","cancel","interrupt"),YT=[],qp=0,ed=1,nc=2,is=3,rd=4,ac=5,ns=6;function To(e,t,r,i,n,a){var o=e.__transition;if(!o)e.__transition={};else if(r in o)return;GT(e,r,{name:t,index:i,group:n,on:jT,tween:YT,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:qp})}function ph(e,t){var r=Je(e,t);if(r.state>qp)throw new Error("too late; already scheduled");return r}function gr(e,t){var r=Je(e,t);if(r.state>is)throw new Error("too late; already running");return r}function Je(e,t){var r=e.__transition;if(!r||!(r=r[t]))throw new Error("transition not found");return r}function GT(e,t,r){var i=e.__transition,n;i[t]=r,r.timer=zp(a,0,r.time);function a(c){r.state=ed,r.timer.restart(o,r.delay,r.time),r.delay<=c&&o(c-r.delay)}function o(c){var h,u,d,f;if(r.state!==ed)return l();for(h in i)if(f=i[h],f.name===r.name){if(f.state===is)return td(o);f.state===rd?(f.state=ns,f.timer.stop(),f.on.call("interrupt",e,e.__data__,f.index,f.group),delete i[h]):+hnc&&i.state=0&&(t=t.slice(0,r)),!t||t==="start"})}function SM(e,t,r){var i,n,a=CM(t)?ph:gr;return function(){var o=a(this,e),s=o.on;s!==i&&(n=(i=s).copy()).on(t,r),o.on=n}}function TM(e,t){var r=this._id;return arguments.length<2?Je(this.node(),r).on.on(e):this.each(SM(r,e,t))}function MM(e){return function(){var t=this.parentNode;for(var r in this.__transition)if(+r!==e)return;t&&t.removeChild(this)}}function $M(){return this.on("end.remove",MM(this._id))}function LM(e){var t=this._name,r=this._id;typeof e!="function"&&(e=hh(e));for(var i=this._groups,n=i.length,a=new Array(n),o=0;o=0))throw new Error(`invalid digits: ${e}`);if(t>15)return Up;const r=10**t;return function(i){this._+=i[0];for(let n=1,a=i.length;nii)if(!(Math.abs(u*l-c*h)>ii)||!a)this._append`L${this._x1=t},${this._y1=r}`;else{let f=i-o,p=n-s,g=l*l+c*c,y=f*f+p*p,b=Math.sqrt(g),x=Math.sqrt(d),_=a*Math.tan((sc-Math.acos((g+d-y)/(2*b*x)))/2),w=_/x,C=_/b;Math.abs(w-1)>ii&&this._append`L${t+w*h},${r+w*u}`,this._append`A${a},${a},0,0,${+(u*f>h*p)},${this._x1=t+C*l},${this._y1=r+C*c}`}}arc(t,r,i,n,a,o){if(t=+t,r=+r,i=+i,o=!!o,i<0)throw new Error(`negative radius: ${i}`);let s=i*Math.cos(n),l=i*Math.sin(n),c=t+s,h=r+l,u=1^o,d=o?n-a:a-n;this._x1===null?this._append`M${c},${h}`:(Math.abs(this._x1-c)>ii||Math.abs(this._y1-h)>ii)&&this._append`L${c},${h}`,i&&(d<0&&(d=d%oc+oc),d>t$?this._append`A${i},${i},0,1,${u},${t-s},${r-l}A${i},${i},0,1,${u},${this._x1=c},${this._y1=h}`:d>ii&&this._append`A${i},${i},0,${+(d>=sc)},${u},${this._x1=t+i*Math.cos(a)},${this._y1=r+i*Math.sin(a)}`)}rect(t,r,i,n){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+r}h${i=+i}v${+n}h${-i}Z`}toString(){return this._}}function i$(e){return Math.abs(e=Math.round(e))>=1e21?e.toLocaleString("en").replace(/,/g,""):e.toString(10)}function As(e,t){if(!isFinite(e)||e===0)return null;var r=(e=t?e.toExponential(t-1):e.toExponential()).indexOf("e"),i=e.slice(0,r);return[i.length>1?i[0]+i.slice(2):i,+e.slice(r+1)]}function ln(e){return e=As(Math.abs(e)),e?e[1]:NaN}function n$(e,t){return function(r,i){for(var n=r.length,a=[],o=0,s=e[0],l=0;n>0&&s>0&&(l+s+1>i&&(s=Math.max(1,i-l)),a.push(r.substring(n-=s,n+s)),!((l+=s+1)>i));)s=e[o=(o+1)%e.length];return a.reverse().join(t)}}function a$(e){return function(t){return t.replace(/[0-9]/g,function(r){return e[+r]})}}var s$=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Bs(e){if(!(t=s$.exec(e)))throw new Error("invalid format: "+e);var t;return new mh({fill:t[1],align:t[2],sign:t[3],symbol:t[4],zero:t[5],width:t[6],comma:t[7],precision:t[8]&&t[8].slice(1),trim:t[9],type:t[10]})}Bs.prototype=mh.prototype;function mh(e){this.fill=e.fill===void 0?" ":e.fill+"",this.align=e.align===void 0?">":e.align+"",this.sign=e.sign===void 0?"-":e.sign+"",this.symbol=e.symbol===void 0?"":e.symbol+"",this.zero=!!e.zero,this.width=e.width===void 0?void 0:+e.width,this.comma=!!e.comma,this.precision=e.precision===void 0?void 0:+e.precision,this.trim=!!e.trim,this.type=e.type===void 0?"":e.type+""}mh.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function o$(e){t:for(var t=e.length,r=1,i=-1,n;r0&&(i=0);break}return i>0?e.slice(0,i)+e.slice(n+1):e}var Es;function l$(e,t){var r=As(e,t);if(!r)return Es=void 0,e.toPrecision(t);var i=r[0],n=r[1],a=n-(Es=Math.max(-8,Math.min(8,Math.floor(n/3)))*3)+1,o=i.length;return a===o?i:a>o?i+new Array(a-o+1).join("0"):a>0?i.slice(0,a)+"."+i.slice(a):"0."+new Array(1-a).join("0")+As(e,Math.max(0,t+a-1))[0]}function id(e,t){var r=As(e,t);if(!r)return e+"";var i=r[0],n=r[1];return n<0?"0."+new Array(-n).join("0")+i:i.length>n+1?i.slice(0,n+1)+"."+i.slice(n+1):i+new Array(n-i.length+2).join("0")}const nd={"%":(e,t)=>(e*100).toFixed(t),b:e=>Math.round(e).toString(2),c:e=>e+"",d:i$,e:(e,t)=>e.toExponential(t),f:(e,t)=>e.toFixed(t),g:(e,t)=>e.toPrecision(t),o:e=>Math.round(e).toString(8),p:(e,t)=>id(e*100,t),r:id,s:l$,X:e=>Math.round(e).toString(16).toUpperCase(),x:e=>Math.round(e).toString(16)};function ad(e){return e}var sd=Array.prototype.map,od=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function c$(e){var t=e.grouping===void 0||e.thousands===void 0?ad:n$(sd.call(e.grouping,Number),e.thousands+""),r=e.currency===void 0?"":e.currency[0]+"",i=e.currency===void 0?"":e.currency[1]+"",n=e.decimal===void 0?".":e.decimal+"",a=e.numerals===void 0?ad:a$(sd.call(e.numerals,String)),o=e.percent===void 0?"%":e.percent+"",s=e.minus===void 0?"−":e.minus+"",l=e.nan===void 0?"NaN":e.nan+"";function c(u,d){u=Bs(u);var f=u.fill,p=u.align,g=u.sign,y=u.symbol,b=u.zero,x=u.width,_=u.comma,w=u.precision,C=u.trim,v=u.type;v==="n"?(_=!0,v="g"):nd[v]||(w===void 0&&(w=12),C=!0,v="g"),(b||f==="0"&&p==="=")&&(b=!0,f="0",p="=");var k=(d&&d.prefix!==void 0?d.prefix:"")+(y==="$"?r:y==="#"&&/[boxX]/.test(v)?"0"+v.toLowerCase():""),$=(y==="$"?i:/[%p]/.test(v)?o:"")+(d&&d.suffix!==void 0?d.suffix:""),z=nd[v],W=/[defgprs%]/.test(v);w=w===void 0?6:/[gprs]/.test(v)?Math.max(1,Math.min(21,w)):Math.max(0,Math.min(20,w));function O(N){var D=k,L=$,B,F,R;if(v==="c")L=z(N)+L,N="";else{N=+N;var I=N<0||1/N<0;if(N=isNaN(N)?l:z(Math.abs(N),w),C&&(N=o$(N)),I&&+N==0&&g!=="+"&&(I=!1),D=(I?g==="("?g:s:g==="-"||g==="("?"":g)+D,L=(v==="s"&&!isNaN(N)&&Es!==void 0?od[8+Es/3]:"")+L+(I&&g==="("?")":""),W){for(B=-1,F=N.length;++BR||R>57){L=(R===46?n+N.slice(B+1):N.slice(B))+L,N=N.slice(0,B);break}}}_&&!b&&(N=t(N,1/0));var X=D.length+N.length+L.length,Z=X>1)+D+N+L+Z.slice(X);break;default:N=Z+D+N+L;break}return a(N)}return O.toString=function(){return u+""},O}function h(u,d){var f=Math.max(-8,Math.min(8,Math.floor(ln(d)/3)))*3,p=Math.pow(10,-f),g=c((u=Bs(u),u.type="f",u),{suffix:od[8+f/3]});return function(y){return g(p*y)}}return{format:c,formatPrefix:h}}var za,jp,Yp;h$({thousands:",",grouping:[3],currency:["$",""]});function h$(e){return za=c$(e),jp=za.format,Yp=za.formatPrefix,za}function u$(e){return Math.max(0,-ln(Math.abs(e)))}function d$(e,t){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(ln(t)/3)))*3-ln(Math.abs(e)))}function f$(e,t){return e=Math.abs(e),t=Math.abs(t)-e,Math.max(0,ln(t)-ln(e))+1}function p$(e){var t=0,r=e.children,i=r&&r.length;if(!i)t=1;else for(;--i>=0;)t+=r[i].value;e.value=t}function g$(){return this.eachAfter(p$)}function m$(e,t){let r=-1;for(const i of this)e.call(t,i,++r,this);return this}function y$(e,t){for(var r=this,i=[r],n,a,o=-1;r=i.pop();)if(e.call(t,r,++o,this),n=r.children)for(a=n.length-1;a>=0;--a)i.push(n[a]);return this}function b$(e,t){for(var r=this,i=[r],n=[],a,o,s,l=-1;r=i.pop();)if(n.push(r),a=r.children)for(o=0,s=a.length;o=0;)r+=i[n].value;t.value=r})}function _$(e){return this.eachBefore(function(t){t.children&&t.children.sort(e)})}function k$(e){for(var t=this,r=w$(t,e),i=[t];t!==r;)t=t.parent,i.push(t);for(var n=i.length;e!==r;)i.splice(n,0,e),e=e.parent;return i}function w$(e,t){if(e===t)return e;var r=e.ancestors(),i=t.ancestors(),n=null;for(e=r.pop(),t=i.pop();e===t;)n=e,e=r.pop(),t=i.pop();return n}function C$(){for(var e=this,t=[e];e=e.parent;)t.push(e);return t}function S$(){return Array.from(this)}function T$(){var e=[];return this.eachBefore(function(t){t.children||e.push(t)}),e}function M$(){var e=this,t=[];return e.each(function(r){r!==e&&t.push({source:r.parent,target:r})}),t}function*$$(){var e=this,t,r=[e],i,n,a;do for(t=r.reverse(),r=[];e=t.pop();)if(yield e,i=e.children)for(n=0,a=i.length;n=0;--s)n.push(a=o[s]=new Fs(o[s])),a.parent=i,a.depth=i.depth+1;return r.eachBefore(F$)}function L$(){return Gp(this).eachBefore(E$)}function A$(e){return e.children}function B$(e){return Array.isArray(e)?e[1]:null}function E$(e){e.data.value!==void 0&&(e.value=e.data.value),e.data=e.data.data}function F$(e){var t=0;do e.height=t;while((e=e.parent)&&e.height<++t)}function Fs(e){this.data=e,this.depth=this.height=0,this.parent=null}Fs.prototype=Gp.prototype={constructor:Fs,count:g$,each:m$,eachAfter:b$,eachBefore:y$,find:v$,sum:x$,sort:_$,path:k$,ancestors:C$,descendants:S$,leaves:T$,links:M$,copy:L$,[Symbol.iterator]:$$};function P$(e){if(typeof e!="function")throw new Error;return e}function Bn(){return 0}function En(e){return function(){return e}}function D$(e){e.x0=Math.round(e.x0),e.y0=Math.round(e.y0),e.x1=Math.round(e.x1),e.y1=Math.round(e.y1)}function I$(e,t,r,i,n){for(var a=e.children,o,s=-1,l=a.length,c=e.value&&(i-t)/e.value;++sx&&(x=c),v=y*y*C,_=Math.max(x/v,v/b),_>w){y-=c;break}w=_}o.push(l={value:y,dice:f1?i:1)},r}(R$);function NO(){var e=z$,t=!1,r=1,i=1,n=[0],a=Bn,o=Bn,s=Bn,l=Bn,c=Bn;function h(d){return d.x0=d.y0=0,d.x1=r,d.y1=i,d.eachBefore(u),n=[0],t&&d.eachBefore(D$),d}function u(d){var f=n[d.depth],p=d.x0+f,g=d.y0+f,y=d.x1-f,b=d.y1-f;yt&&(r=e,e=t,t=r),function(i){return Math.max(e,Math.min(t,i))}}function U$(e,t,r){var i=e[0],n=e[1],a=t[0],o=t[1];return n2?j$:U$,l=c=null,u}function u(d){return d==null||isNaN(d=+d)?a:(l||(l=s(e.map(i),t,r)))(i(o(d)))}return u.invert=function(d){return o(n((c||(c=s(t,e.map(i),Ue)))(d)))},u.domain=function(d){return arguments.length?(e=Array.from(d,H$),h()):e.slice()},u.range=function(d){return arguments.length?(t=Array.from(d),h()):t.slice()},u.rangeRound=function(d){return t=Array.from(d),r=IT,h()},u.clamp=function(d){return arguments.length?(o=d?!0:zi,h()):o!==zi},u.interpolate=function(d){return arguments.length?(r=d,h()):r},u.unknown=function(d){return arguments.length?(a=d,u):a},function(d,f){return i=d,n=f,h()}}function Kp(){return Y$()(zi,zi)}function G$(e,t,r,i){var n=Zl(e,t,r),a;switch(i=Bs(i??",f"),i.type){case"s":{var o=Math.max(Math.abs(e),Math.abs(t));return i.precision==null&&!isNaN(a=d$(n,o))&&(i.precision=a),Yp(i,o)}case"":case"e":case"g":case"p":case"r":{i.precision==null&&!isNaN(a=f$(n,Math.max(Math.abs(e),Math.abs(t))))&&(i.precision=a-(i.type==="e"));break}case"f":case"%":{i.precision==null&&!isNaN(a=u$(n))&&(i.precision=a-(i.type==="%")*2);break}}return jp(i)}function X$(e){var t=e.domain;return e.ticks=function(r){var i=t();return _C(i[0],i[i.length-1],r??10)},e.tickFormat=function(r,i){var n=t();return G$(n[0],n[n.length-1],r??10,i)},e.nice=function(r){r==null&&(r=10);var i=t(),n=0,a=i.length-1,o=i[n],s=i[a],l,c,h=10;for(s0;){if(c=Xl(o,s,r),c===l)return i[n]=o,i[a]=s,t(i);if(c>0)o=Math.floor(o/c)*c,s=Math.ceil(s/c)*c;else if(c<0)o=Math.ceil(o*c)/c,s=Math.floor(s*c)/c;else break;l=c}return e},e}function Z$(){var e=Kp();return e.copy=function(){return Zp(e,Z$())},Mo.apply(e,arguments),X$(e)}function K$(e,t){e=e.slice();var r=0,i=e.length-1,n=e[r],a=e[i],o;return a(e(a=new Date(+a)),a),n.ceil=a=>(e(a=new Date(a-1)),t(a,1),e(a),a),n.round=a=>{const o=n(a),s=n.ceil(a);return a-o(t(a=new Date(+a),o==null?1:Math.floor(o)),a),n.range=(a,o,s)=>{const l=[];if(a=n.ceil(a),s=s==null?1:Math.floor(s),!(a0))return l;let c;do l.push(c=new Date(+a)),t(a,s),e(a);while(cne(o=>{if(o>=o)for(;e(o),!a(o);)o.setTime(o-1)},(o,s)=>{if(o>=o)if(s<0)for(;++s<=0;)for(;t(o,-1),!a(o););else for(;--s>=0;)for(;t(o,1),!a(o););}),r&&(n.count=(a,o)=>(gl.setTime(+a),ml.setTime(+o),e(gl),e(ml),Math.floor(r(gl,ml))),n.every=a=>(a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?n.filter(i?o=>i(o)%a===0:o=>n.count(0,o)%a===0):n)),n}const Ps=ne(()=>{},(e,t)=>{e.setTime(+e+t)},(e,t)=>t-e);Ps.every=e=>(e=Math.floor(e),!isFinite(e)||!(e>0)?null:e>1?ne(t=>{t.setTime(Math.floor(t/e)*e)},(t,r)=>{t.setTime(+t+r*e)},(t,r)=>(r-t)/e):Ps);Ps.range;const Tr=1e3,Re=Tr*60,Mr=Re*60,Br=Mr*24,yh=Br*7,hd=Br*30,yl=Br*365,qi=ne(e=>{e.setTime(e-e.getMilliseconds())},(e,t)=>{e.setTime(+e+t*Tr)},(e,t)=>(t-e)/Tr,e=>e.getUTCSeconds());qi.range;const bh=ne(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*Tr)},(e,t)=>{e.setTime(+e+t*Re)},(e,t)=>(t-e)/Re,e=>e.getMinutes());bh.range;const Q$=ne(e=>{e.setUTCSeconds(0,0)},(e,t)=>{e.setTime(+e+t*Re)},(e,t)=>(t-e)/Re,e=>e.getUTCMinutes());Q$.range;const vh=ne(e=>{e.setTime(e-e.getMilliseconds()-e.getSeconds()*Tr-e.getMinutes()*Re)},(e,t)=>{e.setTime(+e+t*Mr)},(e,t)=>(t-e)/Mr,e=>e.getHours());vh.range;const J$=ne(e=>{e.setUTCMinutes(0,0,0)},(e,t)=>{e.setTime(+e+t*Mr)},(e,t)=>(t-e)/Mr,e=>e.getUTCHours());J$.range;const wa=ne(e=>e.setHours(0,0,0,0),(e,t)=>e.setDate(e.getDate()+t),(e,t)=>(t-e-(t.getTimezoneOffset()-e.getTimezoneOffset())*Re)/Br,e=>e.getDate()-1);wa.range;const xh=ne(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Br,e=>e.getUTCDate()-1);xh.range;const tL=ne(e=>{e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCDate(e.getUTCDate()+t)},(e,t)=>(t-e)/Br,e=>Math.floor(e/Br));tL.range;function Si(e){return ne(t=>{t.setDate(t.getDate()-(t.getDay()+7-e)%7),t.setHours(0,0,0,0)},(t,r)=>{t.setDate(t.getDate()+r*7)},(t,r)=>(r-t-(r.getTimezoneOffset()-t.getTimezoneOffset())*Re)/yh)}const $o=Si(0),Ds=Si(1),eL=Si(2),rL=Si(3),cn=Si(4),iL=Si(5),nL=Si(6);$o.range;Ds.range;eL.range;rL.range;cn.range;iL.range;nL.range;function Ti(e){return ne(t=>{t.setUTCDate(t.getUTCDate()-(t.getUTCDay()+7-e)%7),t.setUTCHours(0,0,0,0)},(t,r)=>{t.setUTCDate(t.getUTCDate()+r*7)},(t,r)=>(r-t)/yh)}const Qp=Ti(0),Is=Ti(1),aL=Ti(2),sL=Ti(3),hn=Ti(4),oL=Ti(5),lL=Ti(6);Qp.range;Is.range;aL.range;sL.range;hn.range;oL.range;lL.range;const _h=ne(e=>{e.setDate(1),e.setHours(0,0,0,0)},(e,t)=>{e.setMonth(e.getMonth()+t)},(e,t)=>t.getMonth()-e.getMonth()+(t.getFullYear()-e.getFullYear())*12,e=>e.getMonth());_h.range;const cL=ne(e=>{e.setUTCDate(1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCMonth(e.getUTCMonth()+t)},(e,t)=>t.getUTCMonth()-e.getUTCMonth()+(t.getUTCFullYear()-e.getUTCFullYear())*12,e=>e.getUTCMonth());cL.range;const Er=ne(e=>{e.setMonth(0,1),e.setHours(0,0,0,0)},(e,t)=>{e.setFullYear(e.getFullYear()+t)},(e,t)=>t.getFullYear()-e.getFullYear(),e=>e.getFullYear());Er.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:ne(t=>{t.setFullYear(Math.floor(t.getFullYear()/e)*e),t.setMonth(0,1),t.setHours(0,0,0,0)},(t,r)=>{t.setFullYear(t.getFullYear()+r*e)});Er.range;const bi=ne(e=>{e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,t)=>{e.setUTCFullYear(e.getUTCFullYear()+t)},(e,t)=>t.getUTCFullYear()-e.getUTCFullYear(),e=>e.getUTCFullYear());bi.every=e=>!isFinite(e=Math.floor(e))||!(e>0)?null:ne(t=>{t.setUTCFullYear(Math.floor(t.getUTCFullYear()/e)*e),t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,r)=>{t.setUTCFullYear(t.getUTCFullYear()+r*e)});bi.range;function hL(e,t,r,i,n,a){const o=[[qi,1,Tr],[qi,5,5*Tr],[qi,15,15*Tr],[qi,30,30*Tr],[a,1,Re],[a,5,5*Re],[a,15,15*Re],[a,30,30*Re],[n,1,Mr],[n,3,3*Mr],[n,6,6*Mr],[n,12,12*Mr],[i,1,Br],[i,2,2*Br],[r,1,yh],[t,1,hd],[t,3,3*hd],[e,1,yl]];function s(c,h,u){const d=hy).right(o,d);if(f===o.length)return e.every(Zl(c/yl,h/yl,u));if(f===0)return Ps.every(Math.max(Zl(c,h,u),1));const[p,g]=o[d/o[f-1][2]53)return null;"w"in q||(q.w=1),"Z"in q?($t=vl(Fn(q.y,0,1)),Yt=$t.getUTCDay(),$t=Yt>4||Yt===0?Is.ceil($t):Is($t),$t=xh.offset($t,(q.V-1)*7),q.y=$t.getUTCFullYear(),q.m=$t.getUTCMonth(),q.d=$t.getUTCDate()+(q.w+6)%7):($t=bl(Fn(q.y,0,1)),Yt=$t.getDay(),$t=Yt>4||Yt===0?Ds.ceil($t):Ds($t),$t=wa.offset($t,(q.V-1)*7),q.y=$t.getFullYear(),q.m=$t.getMonth(),q.d=$t.getDate()+(q.w+6)%7)}else("W"in q||"U"in q)&&("w"in q||(q.w="u"in q?q.u%7:"W"in q?1:0),Yt="Z"in q?vl(Fn(q.y,0,1)).getUTCDay():bl(Fn(q.y,0,1)).getDay(),q.m=0,q.d="W"in q?(q.w+6)%7+q.W*7-(Yt+5)%7:q.w+q.U*7-(Yt+6)%7);return"Z"in q?(q.H+=q.Z/100|0,q.M+=q.Z%100,vl(q)):bl(q)}}function $(Y,Q,ht,q){for(var Dt=0,$t=Q.length,Yt=ht.length,Qt,oe;Dt<$t;){if(q>=Yt)return-1;if(Qt=Q.charCodeAt(Dt++),Qt===37){if(Qt=Q.charAt(Dt++),oe=C[Qt in ud?Q.charAt(Dt++):Qt],!oe||(q=oe(Y,ht,q))<0)return-1}else if(Qt!=ht.charCodeAt(q++))return-1}return q}function z(Y,Q,ht){var q=c.exec(Q.slice(ht));return q?(Y.p=h.get(q[0].toLowerCase()),ht+q[0].length):-1}function W(Y,Q,ht){var q=f.exec(Q.slice(ht));return q?(Y.w=p.get(q[0].toLowerCase()),ht+q[0].length):-1}function O(Y,Q,ht){var q=u.exec(Q.slice(ht));return q?(Y.w=d.get(q[0].toLowerCase()),ht+q[0].length):-1}function N(Y,Q,ht){var q=b.exec(Q.slice(ht));return q?(Y.m=x.get(q[0].toLowerCase()),ht+q[0].length):-1}function D(Y,Q,ht){var q=g.exec(Q.slice(ht));return q?(Y.m=y.get(q[0].toLowerCase()),ht+q[0].length):-1}function L(Y,Q,ht){return $(Y,t,Q,ht)}function B(Y,Q,ht){return $(Y,r,Q,ht)}function F(Y,Q,ht){return $(Y,i,Q,ht)}function R(Y){return o[Y.getDay()]}function I(Y){return a[Y.getDay()]}function X(Y){return l[Y.getMonth()]}function Z(Y){return s[Y.getMonth()]}function J(Y){return n[+(Y.getHours()>=12)]}function Tt(Y){return 1+~~(Y.getMonth()/3)}function vt(Y){return o[Y.getUTCDay()]}function Pt(Y){return a[Y.getUTCDay()]}function xt(Y){return l[Y.getUTCMonth()]}function kt(Y){return s[Y.getUTCMonth()]}function St(Y){return n[+(Y.getUTCHours()>=12)]}function It(Y){return 1+~~(Y.getUTCMonth()/3)}return{format:function(Y){var Q=v(Y+="",_);return Q.toString=function(){return Y},Q},parse:function(Y){var Q=k(Y+="",!1);return Q.toString=function(){return Y},Q},utcFormat:function(Y){var Q=v(Y+="",w);return Q.toString=function(){return Y},Q},utcParse:function(Y){var Q=k(Y+="",!0);return Q.toString=function(){return Y},Q}}}var ud={"-":"",_:" ",0:"0"},se=/^\s*\d+/,pL=/^%/,gL=/[\\^$*+?|[\]().{}]/g;function Nt(e,t,r){var i=e<0?"-":"",n=(i?-e:e)+"",a=n.length;return i+(a[t.toLowerCase(),r]))}function yL(e,t,r){var i=se.exec(t.slice(r,r+1));return i?(e.w=+i[0],r+i[0].length):-1}function bL(e,t,r){var i=se.exec(t.slice(r,r+1));return i?(e.u=+i[0],r+i[0].length):-1}function vL(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.U=+i[0],r+i[0].length):-1}function xL(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.V=+i[0],r+i[0].length):-1}function _L(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.W=+i[0],r+i[0].length):-1}function dd(e,t,r){var i=se.exec(t.slice(r,r+4));return i?(e.y=+i[0],r+i[0].length):-1}function fd(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.y=+i[0]+(+i[0]>68?1900:2e3),r+i[0].length):-1}function kL(e,t,r){var i=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(t.slice(r,r+6));return i?(e.Z=i[1]?0:-(i[2]+(i[3]||"00")),r+i[0].length):-1}function wL(e,t,r){var i=se.exec(t.slice(r,r+1));return i?(e.q=i[0]*3-3,r+i[0].length):-1}function CL(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.m=i[0]-1,r+i[0].length):-1}function pd(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.d=+i[0],r+i[0].length):-1}function SL(e,t,r){var i=se.exec(t.slice(r,r+3));return i?(e.m=0,e.d=+i[0],r+i[0].length):-1}function gd(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.H=+i[0],r+i[0].length):-1}function TL(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.M=+i[0],r+i[0].length):-1}function ML(e,t,r){var i=se.exec(t.slice(r,r+2));return i?(e.S=+i[0],r+i[0].length):-1}function $L(e,t,r){var i=se.exec(t.slice(r,r+3));return i?(e.L=+i[0],r+i[0].length):-1}function LL(e,t,r){var i=se.exec(t.slice(r,r+6));return i?(e.L=Math.floor(i[0]/1e3),r+i[0].length):-1}function AL(e,t,r){var i=pL.exec(t.slice(r,r+1));return i?r+i[0].length:-1}function BL(e,t,r){var i=se.exec(t.slice(r));return i?(e.Q=+i[0],r+i[0].length):-1}function EL(e,t,r){var i=se.exec(t.slice(r));return i?(e.s=+i[0],r+i[0].length):-1}function md(e,t){return Nt(e.getDate(),t,2)}function FL(e,t){return Nt(e.getHours(),t,2)}function PL(e,t){return Nt(e.getHours()%12||12,t,2)}function DL(e,t){return Nt(1+wa.count(Er(e),e),t,3)}function Jp(e,t){return Nt(e.getMilliseconds(),t,3)}function IL(e,t){return Jp(e,t)+"000"}function OL(e,t){return Nt(e.getMonth()+1,t,2)}function RL(e,t){return Nt(e.getMinutes(),t,2)}function NL(e,t){return Nt(e.getSeconds(),t,2)}function zL(e){var t=e.getDay();return t===0?7:t}function qL(e,t){return Nt($o.count(Er(e)-1,e),t,2)}function tg(e){var t=e.getDay();return t>=4||t===0?cn(e):cn.ceil(e)}function WL(e,t){return e=tg(e),Nt(cn.count(Er(e),e)+(Er(e).getDay()===4),t,2)}function HL(e){return e.getDay()}function VL(e,t){return Nt(Ds.count(Er(e)-1,e),t,2)}function UL(e,t){return Nt(e.getFullYear()%100,t,2)}function jL(e,t){return e=tg(e),Nt(e.getFullYear()%100,t,2)}function YL(e,t){return Nt(e.getFullYear()%1e4,t,4)}function GL(e,t){var r=e.getDay();return e=r>=4||r===0?cn(e):cn.ceil(e),Nt(e.getFullYear()%1e4,t,4)}function XL(e){var t=e.getTimezoneOffset();return(t>0?"-":(t*=-1,"+"))+Nt(t/60|0,"0",2)+Nt(t%60,"0",2)}function yd(e,t){return Nt(e.getUTCDate(),t,2)}function ZL(e,t){return Nt(e.getUTCHours(),t,2)}function KL(e,t){return Nt(e.getUTCHours()%12||12,t,2)}function QL(e,t){return Nt(1+xh.count(bi(e),e),t,3)}function eg(e,t){return Nt(e.getUTCMilliseconds(),t,3)}function JL(e,t){return eg(e,t)+"000"}function tA(e,t){return Nt(e.getUTCMonth()+1,t,2)}function eA(e,t){return Nt(e.getUTCMinutes(),t,2)}function rA(e,t){return Nt(e.getUTCSeconds(),t,2)}function iA(e){var t=e.getUTCDay();return t===0?7:t}function nA(e,t){return Nt(Qp.count(bi(e)-1,e),t,2)}function rg(e){var t=e.getUTCDay();return t>=4||t===0?hn(e):hn.ceil(e)}function aA(e,t){return e=rg(e),Nt(hn.count(bi(e),e)+(bi(e).getUTCDay()===4),t,2)}function sA(e){return e.getUTCDay()}function oA(e,t){return Nt(Is.count(bi(e)-1,e),t,2)}function lA(e,t){return Nt(e.getUTCFullYear()%100,t,2)}function cA(e,t){return e=rg(e),Nt(e.getUTCFullYear()%100,t,2)}function hA(e,t){return Nt(e.getUTCFullYear()%1e4,t,4)}function uA(e,t){var r=e.getUTCDay();return e=r>=4||r===0?hn(e):hn.ceil(e),Nt(e.getUTCFullYear()%1e4,t,4)}function dA(){return"+0000"}function bd(){return"%"}function vd(e){return+e}function xd(e){return Math.floor(+e/1e3)}var Ii,ig;fA({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function fA(e){return Ii=fL(e),ig=Ii.format,Ii.parse,Ii.utcFormat,Ii.utcParse,Ii}function pA(e){return new Date(e)}function gA(e){return e instanceof Date?+e:+new Date(+e)}function ng(e,t,r,i,n,a,o,s,l,c){var h=Kp(),u=h.invert,d=h.domain,f=c(".%L"),p=c(":%S"),g=c("%I:%M"),y=c("%I %p"),b=c("%a %d"),x=c("%b %d"),_=c("%B"),w=c("%Y");function C(v){return(l(v)1?0:e<-1?oa:Math.acos(e)}function kd(e){return e>=1?Os:e<=-1?-Os:Math.asin(e)}function ag(e){let t=3;return e.digits=function(r){if(!arguments.length)return t;if(r==null)t=null;else{const i=Math.floor(r);if(!(i>=0))throw new RangeError(`invalid digits: ${r}`);t=i}return e},()=>new r$(t)}function vA(e){return e.innerRadius}function xA(e){return e.outerRadius}function _A(e){return e.startAngle}function kA(e){return e.endAngle}function wA(e){return e&&e.padAngle}function CA(e,t,r,i,n,a,o,s){var l=r-e,c=i-t,h=o-n,u=s-a,d=u*l-h*c;if(!(d*dL*L+B*B&&($=W,z=O),{cx:$,cy:z,x01:-h,y01:-u,x11:$*(n/C-1),y11:z*(n/C-1)}}function WO(){var e=vA,t=xA,r=Gt(0),i=null,n=_A,a=kA,o=wA,s=null,l=ag(c);function c(){var h,u,d=+e.apply(this,arguments),f=+t.apply(this,arguments),p=n.apply(this,arguments)-Os,g=a.apply(this,arguments)-Os,y=_d(g-p),b=g>p;if(s||(s=h=l()),fpe))s.moveTo(0,0);else if(y>as-pe)s.moveTo(f*ei(p),f*ir(p)),s.arc(0,0,f,p,g,!b),d>pe&&(s.moveTo(d*ei(g),d*ir(g)),s.arc(0,0,d,g,p,b));else{var x=p,_=g,w=p,C=g,v=y,k=y,$=o.apply(this,arguments)/2,z=$>pe&&(i?+i.apply(this,arguments):Wi(d*d+f*f)),W=xl(_d(f-d)/2,+r.apply(this,arguments)),O=W,N=W,D,L;if(z>pe){var B=kd(z/d*ir($)),F=kd(z/f*ir($));(v-=B*2)>pe?(B*=b?1:-1,w+=B,C-=B):(v=0,w=C=(p+g)/2),(k-=F*2)>pe?(F*=b?1:-1,x+=F,_-=F):(k=0,x=_=(p+g)/2)}var R=f*ei(x),I=f*ir(x),X=d*ei(C),Z=d*ir(C);if(W>pe){var J=f*ei(_),Tt=f*ir(_),vt=d*ei(w),Pt=d*ir(w),xt;if(ype?N>pe?(D=qa(vt,Pt,R,I,f,N,b),L=qa(J,Tt,X,Z,f,N,b),s.moveTo(D.cx+D.x01,D.cy+D.y01),Npe)||!(v>pe)?s.lineTo(X,Z):O>pe?(D=qa(X,Z,J,Tt,d,-O,b),L=qa(R,I,vt,Pt,d,-O,b),s.lineTo(D.cx+D.x01,D.cy+D.y01),Oe?1:t>=e?0:NaN}function LA(e){return e}function HO(){var e=LA,t=$A,r=null,i=Gt(0),n=Gt(as),a=Gt(0);function o(s){var l,c=(s=sg(s)).length,h,u,d=0,f=new Array(c),p=new Array(c),g=+i.apply(this,arguments),y=Math.min(as,Math.max(-as,n.apply(this,arguments)-g)),b,x=Math.min(Math.abs(y)/c,a.apply(this,arguments)),_=x*(y<0?-1:1),w;for(l=0;l0&&(d+=w);for(t!=null?f.sort(function(C,v){return t(p[C],p[v])}):r!=null&&f.sort(function(C,v){return r(s[C],s[v])}),l=0,u=d?(y-c*_)/d:0;l0?w*u:0)+_,p[h]={data:s[h],index:l,value:w,startAngle:g,endAngle:b,padAngle:x};return p}return o.value=function(s){return arguments.length?(e=typeof s=="function"?s:Gt(+s),o):e},o.sortValues=function(s){return arguments.length?(t=s,r=null,o):t},o.sort=function(s){return arguments.length?(r=s,t=null,o):r},o.startAngle=function(s){return arguments.length?(i=typeof s=="function"?s:Gt(+s),o):i},o.endAngle=function(s){return arguments.length?(n=typeof s=="function"?s:Gt(+s),o):n},o.padAngle=function(s){return arguments.length?(a=typeof s=="function"?s:Gt(+s),o):a},o}class lg{constructor(t,r){this._context=t,this._x=r}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(t,r){switch(t=+t,r=+r,this._point){case 0:{this._point=1,this._line?this._context.lineTo(t,r):this._context.moveTo(t,r);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,r,t,r):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+r)/2,t,this._y0,t,r);break}}this._x0=t,this._y0=r}}function cg(e){return new lg(e,!0)}function hg(e){return new lg(e,!1)}function Vr(){}function Ns(e,t,r){e._context.bezierCurveTo((2*e._x0+e._x1)/3,(2*e._y0+e._y1)/3,(e._x0+2*e._x1)/3,(e._y0+2*e._y1)/3,(e._x0+4*e._x1+t)/6,(e._y0+4*e._y1+r)/6)}function Lo(e){this._context=e}Lo.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Ns(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Ns(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function ss(e){return new Lo(e)}function ug(e){this._context=e}ug.prototype={areaStart:Vr,areaEnd:Vr,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x2=e,this._y2=t;break;case 1:this._point=2,this._x3=e,this._y3=t;break;case 2:this._point=3,this._x4=e,this._y4=t,this._context.moveTo((this._x0+4*this._x1+e)/6,(this._y0+4*this._y1+t)/6);break;default:Ns(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function AA(e){return new ug(e)}function dg(e){this._context=e}dg.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+e)/6,i=(this._y0+4*this._y1+t)/6;this._line?this._context.lineTo(r,i):this._context.moveTo(r,i);break;case 3:this._point=4;default:Ns(this,e,t);break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t}};function BA(e){return new dg(e)}function fg(e,t){this._basis=new Lo(e),this._beta=t}fg.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var e=this._x,t=this._y,r=e.length-1;if(r>0)for(var i=e[0],n=t[0],a=e[r]-i,o=t[r]-n,s=-1,l;++s<=r;)l=s/r,this._basis.point(this._beta*e[s]+(1-this._beta)*(i+l*a),this._beta*t[s]+(1-this._beta)*(n+l*o));this._x=this._y=null,this._basis.lineEnd()},point:function(e,t){this._x.push(+e),this._y.push(+t)}};const EA=function e(t){function r(i){return t===1?new Lo(i):new fg(i,t)}return r.beta=function(i){return e(+i)},r}(.85);function zs(e,t,r){e._context.bezierCurveTo(e._x1+e._k*(e._x2-e._x0),e._y1+e._k*(e._y2-e._y0),e._x2+e._k*(e._x1-t),e._y2+e._k*(e._y1-r),e._x2,e._y2)}function kh(e,t){this._context=e,this._k=(1-t)/6}kh.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:zs(this,this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2,this._x1=e,this._y1=t;break;case 2:this._point=3;default:zs(this,e,t);break}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const pg=function e(t){function r(i){return new kh(i,t)}return r.tension=function(i){return e(+i)},r}(0);function wh(e,t){this._context=e,this._k=(1-t)/6}wh.prototype={areaStart:Vr,areaEnd:Vr,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:zs(this,e,t);break}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const FA=function e(t){function r(i){return new wh(i,t)}return r.tension=function(i){return e(+i)},r}(0);function Ch(e,t){this._context=e,this._k=(1-t)/6}Ch.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:zs(this,e,t);break}this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const PA=function e(t){function r(i){return new Ch(i,t)}return r.tension=function(i){return e(+i)},r}(0);function Sh(e,t,r){var i=e._x1,n=e._y1,a=e._x2,o=e._y2;if(e._l01_a>pe){var s=2*e._l01_2a+3*e._l01_a*e._l12_a+e._l12_2a,l=3*e._l01_a*(e._l01_a+e._l12_a);i=(i*s-e._x0*e._l12_2a+e._x2*e._l01_2a)/l,n=(n*s-e._y0*e._l12_2a+e._y2*e._l01_2a)/l}if(e._l23_a>pe){var c=2*e._l23_2a+3*e._l23_a*e._l12_a+e._l12_2a,h=3*e._l23_a*(e._l23_a+e._l12_a);a=(a*c+e._x1*e._l23_2a-t*e._l12_2a)/h,o=(o*c+e._y1*e._l23_2a-r*e._l12_2a)/h}e._context.bezierCurveTo(i,n,a,o,e._x2,e._y2)}function gg(e,t){this._context=e,this._alpha=t}gg.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var r=this._x2-e,i=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+i*i,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3;default:Sh(this,e,t);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const mg=function e(t){function r(i){return t?new gg(i,t):new kh(i,0)}return r.alpha=function(i){return e(+i)},r}(.5);function yg(e,t){this._context=e,this._alpha=t}yg.prototype={areaStart:Vr,areaEnd:Vr,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},point:function(e,t){if(e=+e,t=+t,this._point){var r=this._x2-e,i=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+i*i,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=e,this._y3=t;break;case 1:this._point=2,this._context.moveTo(this._x4=e,this._y4=t);break;case 2:this._point=3,this._x5=e,this._y5=t;break;default:Sh(this,e,t);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const DA=function e(t){function r(i){return t?new yg(i,t):new wh(i,0)}return r.alpha=function(i){return e(+i)},r}(.5);function bg(e,t){this._context=e,this._alpha=t}bg.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){if(e=+e,t=+t,this._point){var r=this._x2-e,i=this._y2-t;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+i*i,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Sh(this,e,t);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=e,this._y0=this._y1,this._y1=this._y2,this._y2=t}};const IA=function e(t){function r(i){return t?new bg(i,t):new Ch(i,0)}return r.alpha=function(i){return e(+i)},r}(.5);function vg(e){this._context=e}vg.prototype={areaStart:Vr,areaEnd:Vr,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(e,t){e=+e,t=+t,this._point?this._context.lineTo(e,t):(this._point=1,this._context.moveTo(e,t))}};function OA(e){return new vg(e)}function wd(e){return e<0?-1:1}function Cd(e,t,r){var i=e._x1-e._x0,n=t-e._x1,a=(e._y1-e._y0)/(i||n<0&&-0),o=(r-e._y1)/(n||i<0&&-0),s=(a*n+o*i)/(i+n);return(wd(a)+wd(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function Sd(e,t){var r=e._x1-e._x0;return r?(3*(e._y1-e._y0)/r-t)/2:t}function _l(e,t,r){var i=e._x0,n=e._y0,a=e._x1,o=e._y1,s=(a-i)/3;e._context.bezierCurveTo(i+s,n+s*t,a-s,o-s*r,a,o)}function qs(e){this._context=e}qs.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:_l(this,this._t0,Sd(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(e,t){var r=NaN;if(e=+e,t=+t,!(e===this._x1&&t===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;break;case 2:this._point=3,_l(this,Sd(this,r=Cd(this,e,t)),r);break;default:_l(this,this._t0,r=Cd(this,e,t));break}this._x0=this._x1,this._x1=e,this._y0=this._y1,this._y1=t,this._t0=r}}};function xg(e){this._context=new _g(e)}(xg.prototype=Object.create(qs.prototype)).point=function(e,t){qs.prototype.point.call(this,t,e)};function _g(e){this._context=e}_g.prototype={moveTo:function(e,t){this._context.moveTo(t,e)},closePath:function(){this._context.closePath()},lineTo:function(e,t){this._context.lineTo(t,e)},bezierCurveTo:function(e,t,r,i,n,a){this._context.bezierCurveTo(t,e,i,r,a,n)}};function kg(e){return new qs(e)}function wg(e){return new xg(e)}function Cg(e){this._context=e}Cg.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var e=this._x,t=this._y,r=e.length;if(r)if(this._line?this._context.lineTo(e[0],t[0]):this._context.moveTo(e[0],t[0]),r===2)this._context.lineTo(e[1],t[1]);else for(var i=Td(e),n=Td(t),a=0,o=1;o=0;--t)n[t]=(o[t]-n[t+1])/a[t];for(a[r-1]=(e[r]+n[r-1])/2,t=0;t=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(e,t){switch(e=+e,t=+t,this._point){case 0:this._point=1,this._line?this._context.lineTo(e,t):this._context.moveTo(e,t);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,t),this._context.lineTo(e,t);else{var r=this._x*(1-this._t)+e*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,t)}break}}this._x=e,this._y=t}};function Tg(e){return new Ao(e,.5)}function Mg(e){return new Ao(e,0)}function $g(e){return new Ao(e,1)}function Un(e,t,r){this.k=e,this.x=t,this.y=r}Un.prototype={constructor:Un,scale:function(e){return e===1?this:new Un(this.k*e,this.x,this.y)},translate:function(e,t){return e===0&t===0?this:new Un(this.k,this.x+this.k*e,this.y+this.k*t)},apply:function(e){return[e[0]*this.k+this.x,e[1]*this.k+this.y]},applyX:function(e){return e*this.k+this.x},applyY:function(e){return e*this.k+this.y},invert:function(e){return[(e[0]-this.x)/this.k,(e[1]-this.y)/this.k]},invertX:function(e){return(e-this.x)/this.k},invertY:function(e){return(e-this.y)/this.k},rescaleX:function(e){return e.copy().domain(e.range().map(this.invertX,this).map(e.invert,e))},rescaleY:function(e){return e.copy().domain(e.range().map(this.invertY,this).map(e.invert,e))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};Un.prototype;var RA=m(e=>{var n;const{securityLevel:t}=qt();let r=Ot("body");if(t==="sandbox"){const o=((n=Ot(`#i${e}`).node())==null?void 0:n.contentDocument)??document;r=Ot(o.body)}return r.select(`#${e}`)},"selectSvgElement");function Th(e){return typeof e>"u"||e===null}m(Th,"isNothing");function Lg(e){return typeof e=="object"&&e!==null}m(Lg,"isObject");function Ag(e){return Array.isArray(e)?e:Th(e)?[]:[e]}m(Ag,"toArray");function Bg(e,t){var r,i,n,a;if(t)for(a=Object.keys(t),r=0,i=a.length;rs&&(a=" ... ",t=i-s+a.length),r-i>s&&(o=" ...",r=i+s-o.length),{str:a+e.slice(t,r).replace(/\t/g,"→")+o,pos:i-t+a.length}}m(os,"getLine");function ls(e,t){return re.repeat(" ",t-e.length)+e}m(ls,"padStart");function Pg(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),typeof t.indent!="number"&&(t.indent=1),typeof t.linesBefore!="number"&&(t.linesBefore=3),typeof t.linesAfter!="number"&&(t.linesAfter=2);for(var r=/\r?\n|\r|\0/g,i=[0],n=[],a,o=-1;a=r.exec(e.buffer);)n.push(a.index),i.push(a.index+a[0].length),e.position<=a.index&&o<0&&(o=i.length-2);o<0&&(o=i.length-1);var s="",l,c,h=Math.min(e.line+t.linesAfter,n.length).toString().length,u=t.maxLength-(t.indent+h+3);for(l=1;l<=t.linesBefore&&!(o-l<0);l++)c=os(e.buffer,i[o-l],n[o-l],e.position-(i[o]-i[o-l]),u),s=re.repeat(" ",t.indent)+ls((e.line-l+1).toString(),h)+" | "+c.str+` +`+s;for(c=os(e.buffer,i[o],n[o],e.position,u),s+=re.repeat(" ",t.indent)+ls((e.line+1).toString(),h)+" | "+c.str+` +`,s+=re.repeat("-",t.indent+h+3+c.pos)+`^ +`,l=1;l<=t.linesAfter&&!(o+l>=n.length);l++)c=os(e.buffer,i[o+l],n[o+l],e.position-(i[o]-i[o+l]),u),s+=re.repeat(" ",t.indent)+ls((e.line+l+1).toString(),h)+" | "+c.str+` +`;return s.replace(/\n$/,"")}m(Pg,"makeSnippet");var UA=Pg,jA=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],YA=["scalar","sequence","mapping"];function Dg(e){var t={};return e!==null&&Object.keys(e).forEach(function(r){e[r].forEach(function(i){t[String(i)]=r})}),t}m(Dg,"compileStyleAliases");function Ig(e,t){if(t=t||{},Object.keys(t).forEach(function(r){if(jA.indexOf(r)===-1)throw new Fe('Unknown option "'+r+'" is met in definition of "'+e+'" YAML type.')}),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(r){return r},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=Dg(t.styleAliases||null),YA.indexOf(this.kind)===-1)throw new Fe('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}m(Ig,"Type$1");var ve=Ig;function cc(e,t){var r=[];return e[t].forEach(function(i){var n=r.length;r.forEach(function(a,o){a.tag===i.tag&&a.kind===i.kind&&a.multi===i.multi&&(n=o)}),r[n]=i}),r}m(cc,"compileList");function Og(){var e={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}},t,r;function i(n){n.multi?(e.multi[n.kind].push(n),e.multi.fallback.push(n)):e[n.kind][n.tag]=e.fallback[n.tag]=n}for(m(i,"collectType"),t=0,r=arguments.length;t=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},"binary"),octal:m(function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},"octal"),decimal:m(function(e){return e.toString(10)},"decimal"),hexadecimal:m(function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)},"hexadecimal")},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),r3=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function Zg(e){return!(e===null||!r3.test(e)||e[e.length-1]==="_")}m(Zg,"resolveYamlFloat");function Kg(e){var t,r;return t=e.replace(/_/g,"").toLowerCase(),r=t[0]==="-"?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),t===".inf"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:t===".nan"?NaN:r*parseFloat(t,10)}m(Kg,"constructYamlFloat");var i3=/^[-+]?[0-9]+e/;function Qg(e,t){var r;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(re.isNegativeZero(e))return"-0.0";return r=e.toString(10),i3.test(r)?r.replace("e",".e"):r}m(Qg,"representYamlFloat");function Jg(e){return Object.prototype.toString.call(e)==="[object Number]"&&(e%1!==0||re.isNegativeZero(e))}m(Jg,"isFloat");var n3=new ve("tag:yaml.org,2002:float",{kind:"scalar",resolve:Zg,construct:Kg,predicate:Jg,represent:Qg,defaultStyle:"lowercase"}),tm=QA.extend({implicit:[JA,t3,e3,n3]}),a3=tm,em=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),rm=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function im(e){return e===null?!1:em.exec(e)!==null||rm.exec(e)!==null}m(im,"resolveYamlTimestamp");function nm(e){var t,r,i,n,a,o,s,l=0,c=null,h,u,d;if(t=em.exec(e),t===null&&(t=rm.exec(e)),t===null)throw new Error("Date resolve error");if(r=+t[1],i=+t[2]-1,n=+t[3],!t[4])return new Date(Date.UTC(r,i,n));if(a=+t[4],o=+t[5],s=+t[6],t[7]){for(l=t[7].slice(0,3);l.length<3;)l+="0";l=+l}return t[9]&&(h=+t[10],u=+(t[11]||0),c=(h*60+u)*6e4,t[9]==="-"&&(c=-c)),d=new Date(Date.UTC(r,i,n,a,o,s,l)),c&&d.setTime(d.getTime()-c),d}m(nm,"constructYamlTimestamp");function am(e){return e.toISOString()}m(am,"representYamlTimestamp");var s3=new ve("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:im,construct:nm,instanceOf:Date,represent:am});function sm(e){return e==="<<"||e===null}m(sm,"resolveYamlMerge");var o3=new ve("tag:yaml.org,2002:merge",{kind:"scalar",resolve:sm}),$h=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= +\r`;function om(e){if(e===null)return!1;var t,r,i=0,n=e.length,a=$h;for(r=0;r64)){if(t<0)return!1;i+=6}return i%8===0}m(om,"resolveYamlBinary");function lm(e){var t,r,i=e.replace(/[\r\n=]/g,""),n=i.length,a=$h,o=0,s=[];for(t=0;t>16&255),s.push(o>>8&255),s.push(o&255)),o=o<<6|a.indexOf(i.charAt(t));return r=n%4*6,r===0?(s.push(o>>16&255),s.push(o>>8&255),s.push(o&255)):r===18?(s.push(o>>10&255),s.push(o>>2&255)):r===12&&s.push(o>>4&255),new Uint8Array(s)}m(lm,"constructYamlBinary");function cm(e){var t="",r=0,i,n,a=e.length,o=$h;for(i=0;i>18&63],t+=o[r>>12&63],t+=o[r>>6&63],t+=o[r&63]),r=(r<<8)+e[i];return n=a%3,n===0?(t+=o[r>>18&63],t+=o[r>>12&63],t+=o[r>>6&63],t+=o[r&63]):n===2?(t+=o[r>>10&63],t+=o[r>>4&63],t+=o[r<<2&63],t+=o[64]):n===1&&(t+=o[r>>2&63],t+=o[r<<4&63],t+=o[64],t+=o[64]),t}m(cm,"representYamlBinary");function hm(e){return Object.prototype.toString.call(e)==="[object Uint8Array]"}m(hm,"isBinary");var l3=new ve("tag:yaml.org,2002:binary",{kind:"scalar",resolve:om,construct:lm,predicate:hm,represent:cm}),c3=Object.prototype.hasOwnProperty,h3=Object.prototype.toString;function um(e){if(e===null)return!0;var t=[],r,i,n,a,o,s=e;for(r=0,i=s.length;r>10)+55296,(e-65536&1023)+56320)}m(Sm,"charFromCodepoint");var Tm=new Array(256),Mm=new Array(256);for(ri=0;ri<256;ri++)Tm[ri]=uc(ri)?1:0,Mm[ri]=uc(ri);var ri;function $m(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||ym,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}m($m,"State$1");function Lh(e,t){var r={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return r.snippet=UA(r),new Fe(t,r)}m(Lh,"generateError");function ft(e,t){throw Lh(e,t)}m(ft,"throwError");function la(e,t){e.onWarning&&e.onWarning.call(null,Lh(e,t))}m(la,"throwWarning");var $d={YAML:m(function(t,r,i){var n,a,o;t.version!==null&&ft(t,"duplication of %YAML directive"),i.length!==1&&ft(t,"YAML directive accepts exactly one argument"),n=/^([0-9]+)\.([0-9]+)$/.exec(i[0]),n===null&&ft(t,"ill-formed argument of the YAML directive"),a=parseInt(n[1],10),o=parseInt(n[2],10),a!==1&&ft(t,"unacceptable YAML version of the document"),t.version=i[0],t.checkLineBreaks=o<2,o!==1&&o!==2&&la(t,"unsupported YAML version of the document")},"handleYamlDirective"),TAG:m(function(t,r,i){var n,a;i.length!==2&&ft(t,"TAG directive accepts exactly two arguments"),n=i[0],a=i[1],xm.test(n)||ft(t,"ill-formed tag handle (first argument) of the TAG directive"),Ur.call(t.tagMap,n)&&ft(t,'there is a previously declared suffix for "'+n+'" tag handle'),_m.test(a)||ft(t,"ill-formed tag prefix (second argument) of the TAG directive");try{a=decodeURIComponent(a)}catch{ft(t,"tag prefix is malformed: "+a)}t.tagMap[n]=a},"handleTagDirective")};function Lr(e,t,r,i){var n,a,o,s;if(t1&&(e.result+=re.repeat(` +`,t-1))}m(Eo,"writeFoldedLines");function Lm(e,t,r){var i,n,a,o,s,l,c,h,u=e.kind,d=e.result,f;if(f=e.input.charCodeAt(e.position),Te(f)||li(f)||f===35||f===38||f===42||f===33||f===124||f===62||f===39||f===34||f===37||f===64||f===96||(f===63||f===45)&&(n=e.input.charCodeAt(e.position+1),Te(n)||r&&li(n)))return!1;for(e.kind="scalar",e.result="",a=o=e.position,s=!1;f!==0;){if(f===58){if(n=e.input.charCodeAt(e.position+1),Te(n)||r&&li(n))break}else if(f===35){if(i=e.input.charCodeAt(e.position-1),Te(i))break}else{if(e.position===e.lineStart&&Ca(e)||r&&li(f))break;if(Ze(f))if(l=e.line,c=e.lineStart,h=e.lineIndent,Xt(e,!1,-1),e.lineIndent>=t){s=!0,f=e.input.charCodeAt(e.position);continue}else{e.position=o,e.line=l,e.lineStart=c,e.lineIndent=h;break}}s&&(Lr(e,a,o,!1),Eo(e,e.line-l),a=o=e.position,s=!1),Hr(f)||(o=e.position+1),f=e.input.charCodeAt(++e.position)}return Lr(e,a,o,!1),e.result?!0:(e.kind=u,e.result=d,!1)}m(Lm,"readPlainScalar");function Am(e,t){var r,i,n;if(r=e.input.charCodeAt(e.position),r!==39)return!1;for(e.kind="scalar",e.result="",e.position++,i=n=e.position;(r=e.input.charCodeAt(e.position))!==0;)if(r===39)if(Lr(e,i,e.position,!0),r=e.input.charCodeAt(++e.position),r===39)i=e.position,e.position++,n=e.position;else return!0;else Ze(r)?(Lr(e,i,n,!0),Eo(e,Xt(e,!1,t)),i=n=e.position):e.position===e.lineStart&&Ca(e)?ft(e,"unexpected end of the document within a single quoted scalar"):(e.position++,n=e.position);ft(e,"unexpected end of the stream within a single quoted scalar")}m(Am,"readSingleQuotedScalar");function Bm(e,t){var r,i,n,a,o,s;if(s=e.input.charCodeAt(e.position),s!==34)return!1;for(e.kind="scalar",e.result="",e.position++,r=i=e.position;(s=e.input.charCodeAt(e.position))!==0;){if(s===34)return Lr(e,r,e.position,!0),e.position++,!0;if(s===92){if(Lr(e,r,e.position,!0),s=e.input.charCodeAt(++e.position),Ze(s))Xt(e,!1,t);else if(s<256&&Tm[s])e.result+=Mm[s],e.position++;else if((o=wm(s))>0){for(n=o,a=0;n>0;n--)s=e.input.charCodeAt(++e.position),(o=km(s))>=0?a=(a<<4)+o:ft(e,"expected hexadecimal character");e.result+=Sm(a),e.position++}else ft(e,"unknown escape sequence");r=i=e.position}else Ze(s)?(Lr(e,r,i,!0),Eo(e,Xt(e,!1,t)),r=i=e.position):e.position===e.lineStart&&Ca(e)?ft(e,"unexpected end of the document within a double quoted scalar"):(e.position++,i=e.position)}ft(e,"unexpected end of the stream within a double quoted scalar")}m(Bm,"readDoubleQuotedScalar");function Em(e,t){var r=!0,i,n,a,o=e.tag,s,l=e.anchor,c,h,u,d,f,p=Object.create(null),g,y,b,x;if(x=e.input.charCodeAt(e.position),x===91)h=93,f=!1,s=[];else if(x===123)h=125,f=!0,s={};else return!1;for(e.anchor!==null&&(e.anchorMap[e.anchor]=s),x=e.input.charCodeAt(++e.position);x!==0;){if(Xt(e,!0,t),x=e.input.charCodeAt(e.position),x===h)return e.position++,e.tag=o,e.anchor=l,e.kind=f?"mapping":"sequence",e.result=s,!0;r?x===44&&ft(e,"expected the node content, but found ','"):ft(e,"missed comma between flow collection entries"),y=g=b=null,u=d=!1,x===63&&(c=e.input.charCodeAt(e.position+1),Te(c)&&(u=d=!0,e.position++,Xt(e,!0,t))),i=e.line,n=e.lineStart,a=e.position,vi(e,t,Hs,!1,!0),y=e.tag,g=e.result,Xt(e,!0,t),x=e.input.charCodeAt(e.position),(d||e.line===i)&&x===58&&(u=!0,x=e.input.charCodeAt(++e.position),Xt(e,!0,t),vi(e,t,Hs,!1,!0),b=e.result),f?ci(e,s,p,y,g,b,i,n,a):u?s.push(ci(e,null,p,y,g,b,i,n,a)):s.push(g),Xt(e,!0,t),x=e.input.charCodeAt(e.position),x===44?(r=!0,x=e.input.charCodeAt(++e.position)):r=!1}ft(e,"unexpected end of the stream within a flow collection")}m(Em,"readFlowCollection");function Fm(e,t){var r,i,n=kl,a=!1,o=!1,s=t,l=0,c=!1,h,u;if(u=e.input.charCodeAt(e.position),u===124)i=!1;else if(u===62)i=!0;else return!1;for(e.kind="scalar",e.result="";u!==0;)if(u=e.input.charCodeAt(++e.position),u===43||u===45)kl===n?n=u===43?Md:m3:ft(e,"repeat of a chomping mode identifier");else if((h=Cm(u))>=0)h===0?ft(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):o?ft(e,"repeat of an indentation width identifier"):(s=t+h-1,o=!0);else break;if(Hr(u)){do u=e.input.charCodeAt(++e.position);while(Hr(u));if(u===35)do u=e.input.charCodeAt(++e.position);while(!Ze(u)&&u!==0)}for(;u!==0;){for(Bo(e),e.lineIndent=0,u=e.input.charCodeAt(e.position);(!o||e.lineIndents&&(s=e.lineIndent),Ze(u)){l++;continue}if(e.lineIndentt)&&l!==0)ft(e,"bad indentation of a sequence entry");else if(e.lineIndentt)&&(y&&(o=e.line,s=e.lineStart,l=e.position),vi(e,t,Vs,!0,n)&&(y?p=e.result:g=e.result),y||(ci(e,u,d,f,p,g,o,s,l),f=p=g=null),Xt(e,!0,-1),x=e.input.charCodeAt(e.position)),(e.line===a||e.lineIndent>t)&&x!==0)ft(e,"bad indentation of a mapping entry");else if(e.lineIndentt?l=1:e.lineIndent===t?l=0:e.lineIndentt?l=1:e.lineIndent===t?l=0:e.lineIndent tag; it should be "scalar", not "'+e.kind+'"'),u=0,d=e.implicitTypes.length;u"),e.result!==null&&p.kind!==e.kind&&ft(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+p.kind+'", not "'+e.kind+'"'),p.resolve(e.result,e.tag)?(e.result=p.construct(e.result,e.tag),e.anchor!==null&&(e.anchorMap[e.anchor]=e.result)):ft(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return e.listener!==null&&e.listener("close",e),e.tag!==null||e.anchor!==null||h}m(vi,"composeNode");function Rm(e){var t=e.position,r,i,n,a=!1,o;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);(o=e.input.charCodeAt(e.position))!==0&&(Xt(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||o!==37));){for(a=!0,o=e.input.charCodeAt(++e.position),r=e.position;o!==0&&!Te(o);)o=e.input.charCodeAt(++e.position);for(i=e.input.slice(r,e.position),n=[],i.length<1&&ft(e,"directive name must not be less than one character in length");o!==0;){for(;Hr(o);)o=e.input.charCodeAt(++e.position);if(o===35){do o=e.input.charCodeAt(++e.position);while(o!==0&&!Ze(o));break}if(Ze(o))break;for(r=e.position;o!==0&&!Te(o);)o=e.input.charCodeAt(++e.position);n.push(e.input.slice(r,e.position))}o!==0&&Bo(e),Ur.call($d,i)?$d[i](e,i,n):la(e,'unknown document directive "'+i+'"')}if(Xt(e,!0,-1),e.lineIndent===0&&e.input.charCodeAt(e.position)===45&&e.input.charCodeAt(e.position+1)===45&&e.input.charCodeAt(e.position+2)===45?(e.position+=3,Xt(e,!0,-1)):a&&ft(e,"directives end mark is expected"),vi(e,e.lineIndent-1,Vs,!1,!0),Xt(e,!0,-1),e.checkLineBreaks&&b3.test(e.input.slice(t,e.position))&&la(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&Ca(e)){e.input.charCodeAt(e.position)===46&&(e.position+=3,Xt(e,!0,-1));return}if(e.position"u"&&(r=t,t=null);var i=Ah(e,r);if(typeof t!="function")return i;for(var n=0,a=i.length;n=55296&&r<=56319&&t+1=56320&&i<=57343)?(r-55296)*1024+i-56320+65536:r}m(Hi,"codePointAt");function Eh(e){var t=/^\n* /;return t.test(e)}m(Eh,"needIndentIndicator");var Jm=1,bc=2,t0=3,e0=4,Ri=5;function r0(e,t,r,i,n,a,o,s){var l,c=0,h=null,u=!1,d=!1,f=i!==-1,p=-1,g=Km(Hi(e,0))&&Qm(Hi(e,e.length-1));if(t||o)for(l=0;l=65536?l+=2:l++){if(c=Hi(e,l),!dn(c))return Ri;g=g&&yc(c,h,s),h=c}else{for(l=0;l=65536?l+=2:l++){if(c=Hi(e,l),c===ca)u=!0,f&&(d=d||l-p-1>i&&e[p+1]!==" ",p=l);else if(!dn(c))return Ri;g=g&&yc(c,h,s),h=c}d=d||f&&l-p-1>i&&e[p+1]!==" "}return!u&&!d?g&&!o&&!n(e)?Jm:a===ha?Ri:bc:r>9&&Eh(e)?Ri:o?a===ha?Ri:bc:d?e0:t0}m(r0,"chooseScalarStyle");function i0(e,t,r,i,n){e.dump=function(){if(t.length===0)return e.quotingType===ha?'""':"''";if(!e.noCompatMode&&(N3.indexOf(t)!==-1||z3.test(t)))return e.quotingType===ha?'"'+t+'"':"'"+t+"'";var a=e.indent*Math.max(1,r),o=e.lineWidth===-1?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-a),s=i||e.flowLevel>-1&&r>=e.flowLevel;function l(c){return Zm(e,c)}switch(m(l,"testAmbiguity"),r0(t,s,e.indent,o,l,e.quotingType,e.forceQuotes&&!i,n)){case Jm:return t;case bc:return"'"+t.replace(/'/g,"''")+"'";case t0:return"|"+vc(t,e.indent)+xc(gc(t,a));case e0:return">"+vc(t,e.indent)+xc(gc(n0(t,o),a));case Ri:return'"'+a0(t)+'"';default:throw new Fe("impossible error: invalid scalar style")}}()}m(i0,"writeScalar");function vc(e,t){var r=Eh(e)?String(t):"",i=e[e.length-1]===` +`,n=i&&(e[e.length-2]===` +`||e===` +`),a=n?"+":i?"":"-";return r+a+` +`}m(vc,"blockHeader");function xc(e){return e[e.length-1]===` +`?e.slice(0,-1):e}m(xc,"dropEndingNewline");function n0(e,t){for(var r=/(\n+)([^\n]*)/g,i=function(){var c=e.indexOf(` +`);return c=c!==-1?c:e.length,r.lastIndex=c,_c(e.slice(0,c),t)}(),n=e[0]===` +`||e[0]===" ",a,o;o=r.exec(e);){var s=o[1],l=o[2];a=l[0]===" ",i+=s+(!n&&!a&&l!==""?` +`:"")+_c(l,t),n=a}return i}m(n0,"foldString");function _c(e,t){if(e===""||e[0]===" ")return e;for(var r=/ [^ ]/g,i,n=0,a,o=0,s=0,l="";i=r.exec(e);)s=i.index,s-n>t&&(a=o>n?o:s,l+=` +`+e.slice(n,a),n=a+1),o=s;return l+=` +`,e.length-n>t&&o>n?l+=e.slice(n,o)+` +`+e.slice(o+1):l+=e.slice(n),l.slice(1)}m(_c,"foldLine");function a0(e){for(var t="",r=0,i,n=0;n=65536?n+=2:n++)r=Hi(e,n),i=ke[r],!i&&dn(r)?(t+=e[n],r>=65536&&(t+=e[n+1])):t+=i||Gm(r);return t}m(a0,"escapeString");function s0(e,t,r){var i="",n=e.tag,a,o,s;for(a=0,o=r.length;a"u"&&pr(e,t,null,!1,!1))&&(i!==""&&(i+=","+(e.condenseFlow?"":" ")),i+=e.dump);e.tag=n,e.dump="["+i+"]"}m(s0,"writeFlowSequence");function kc(e,t,r,i){var n="",a=e.tag,o,s,l;for(o=0,s=r.length;o"u"&&pr(e,t+1,null,!0,!0,!1,!0))&&((!i||n!=="")&&(n+=js(e,t)),e.dump&&ca===e.dump.charCodeAt(0)?n+="-":n+="- ",n+=e.dump);e.tag=a,e.dump=n||"[]"}m(kc,"writeBlockSequence");function o0(e,t,r){var i="",n=e.tag,a=Object.keys(r),o,s,l,c,h;for(o=0,s=a.length;o1024&&(h+="? "),h+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),pr(e,t,c,!1,!1)&&(h+=e.dump,i+=h));e.tag=n,e.dump="{"+i+"}"}m(o0,"writeFlowMapping");function l0(e,t,r,i){var n="",a=e.tag,o=Object.keys(r),s,l,c,h,u,d;if(e.sortKeys===!0)o.sort();else if(typeof e.sortKeys=="function")o.sort(e.sortKeys);else if(e.sortKeys)throw new Fe("sortKeys must be a boolean or a function");for(s=0,l=o.length;s1024,u&&(e.dump&&ca===e.dump.charCodeAt(0)?d+="?":d+="? "),d+=e.dump,u&&(d+=js(e,t)),pr(e,t+1,h,!0,u)&&(e.dump&&ca===e.dump.charCodeAt(0)?d+=":":d+=": ",d+=e.dump,n+=d));e.tag=a,e.dump=n||"{}"}m(l0,"writeBlockMapping");function wc(e,t,r){var i,n,a,o,s,l;for(n=r?e.explicitTypes:e.implicitTypes,a=0,o=n.length;a tag resolver accepts not "'+l+'" style');e.dump=i}return!0}return!1}m(wc,"detectType");function pr(e,t,r,i,n,a,o){e.tag=null,e.dump=r,wc(e,r,!1)||wc(e,r,!0);var s=zm.call(e.dump),l=i,c;i&&(i=e.flowLevel<0||e.flowLevel>t);var h=s==="[object Object]"||s==="[object Array]",u,d;if(h&&(u=e.duplicates.indexOf(r),d=u!==-1),(e.tag!==null&&e.tag!=="?"||d||e.indent!==2&&t>0)&&(n=!1),d&&e.usedDuplicates[u])e.dump="*ref_"+u;else{if(h&&d&&!e.usedDuplicates[u]&&(e.usedDuplicates[u]=!0),s==="[object Object]")i&&Object.keys(e.dump).length!==0?(l0(e,t,e.dump,n),d&&(e.dump="&ref_"+u+e.dump)):(o0(e,t,e.dump),d&&(e.dump="&ref_"+u+" "+e.dump));else if(s==="[object Array]")i&&e.dump.length!==0?(e.noArrayIndent&&!o&&t>0?kc(e,t-1,e.dump,n):kc(e,t,e.dump,n),d&&(e.dump="&ref_"+u+e.dump)):(s0(e,t,e.dump),d&&(e.dump="&ref_"+u+" "+e.dump));else if(s==="[object String]")e.tag!=="?"&&i0(e,e.dump,t,a,l);else{if(s==="[object Undefined]")return!1;if(e.skipInvalid)return!1;throw new Fe("unacceptable kind of an object to dump "+s)}e.tag!==null&&e.tag!=="?"&&(c=encodeURI(e.tag[0]==="!"?e.tag.slice(1):e.tag).replace(/!/g,"%21"),e.tag[0]==="!"?c="!"+c:c.slice(0,18)==="tag:yaml.org,2002:"?c="!!"+c.slice(18):c="!<"+c+">",e.dump=c+" "+e.dump)}return!0}m(pr,"writeNode");function c0(e,t){var r=[],i=[],n,a;for(Ys(e,r,i),n=0,a=i.length;nArray.isArray(e)?{x:e[0],y:e[1]}:e,"pointTransformer"),j3=m(e=>({x:m(function(t,r,i){let n=0;const a=jt(i[0]).x=0?1:-1)}else if(r===i.length-1&&Object.hasOwn(me,e.arrowTypeEnd)){const{angle:f,deltaX:p}=jn(i[i.length-1],i[i.length-2]);n=me[e.arrowTypeEnd]*Math.cos(f)*(p>=0?1:-1)}const o=Math.abs(jt(t).x-jt(i[i.length-1]).x),s=Math.abs(jt(t).y-jt(i[i.length-1]).y),l=Math.abs(jt(t).x-jt(i[0]).x),c=Math.abs(jt(t).y-jt(i[0]).y),h=me[e.arrowTypeStart],u=me[e.arrowTypeEnd],d=1;if(o0&&s0&&c=0?1:-1)}else if(r===i.length-1&&Object.hasOwn(me,e.arrowTypeEnd)){const{angle:f,deltaY:p}=jn(i[i.length-1],i[i.length-2]);n=me[e.arrowTypeEnd]*Math.abs(Math.sin(f))*(p>=0?1:-1)}const o=Math.abs(jt(t).y-jt(i[i.length-1]).y),s=Math.abs(jt(t).x-jt(i[i.length-1]).x),l=Math.abs(jt(t).y-jt(i[0]).y),c=Math.abs(jt(t).x-jt(i[0]).x),h=me[e.arrowTypeStart],u=me[e.arrowTypeEnd],d=1;if(o0&&s0&&c{var n,a;const t=((n=e==null?void 0:e.subGraphTitleMargin)==null?void 0:n.top)??0,r=((a=e==null?void 0:e.subGraphTitleMargin)==null?void 0:a.bottom)??0,i=t+r;return{subGraphTitleTopMargin:t,subGraphTitleBottomMargin:r,subGraphTitleTotalMargin:i}},"getSubGraphTitleMargins"),Y3=m(e=>{const{handDrawnSeed:t}=qt();return{fill:e,hachureAngle:120,hachureGap:4,fillWeight:2,roughness:.7,stroke:e,seed:t}},"solidStateFill"),bn=m(e=>{const t=G3([...e.cssCompiledStyles||[],...e.cssStyles||[],...e.labelStyle||[]]);return{stylesMap:t,stylesArray:[...t]}},"compileStyles"),G3=m(e=>{const t=new Map;return e.forEach(r=>{const[i,n]=r.split(":");t.set(i.trim(),n==null?void 0:n.trim())}),t},"styles2Map"),h0=m(e=>e==="color"||e==="font-size"||e==="font-family"||e==="font-weight"||e==="font-style"||e==="text-decoration"||e==="text-align"||e==="text-transform"||e==="line-height"||e==="letter-spacing"||e==="word-spacing"||e==="text-shadow"||e==="text-overflow"||e==="white-space"||e==="word-wrap"||e==="word-break"||e==="overflow-wrap"||e==="hyphens","isLabelStyle"),ot=m(e=>{const{stylesArray:t}=bn(e),r=[],i=[],n=[],a=[];return t.forEach(o=>{const s=o[0];h0(s)?r.push(o.join(":")+" !important"):(i.push(o.join(":")+" !important"),s.includes("stroke")&&n.push(o.join(":")+" !important"),s==="fill"&&a.push(o.join(":")+" !important"))}),{labelStyles:r.join(";"),nodeStyles:i.join(";"),stylesArray:t,borderStyles:n,backgroundStyles:a}},"styles2String"),st=m((e,t)=>{var l;const{themeVariables:r,handDrawnSeed:i}=qt(),{nodeBorder:n,mainBkg:a}=r,{stylesMap:o}=bn(e);return Object.assign({roughness:.7,fill:o.get("fill")||a,fillStyle:"hachure",fillWeight:4,hachureGap:5.2,stroke:o.get("stroke")||n,seed:i,strokeWidth:((l=o.get("stroke-width"))==null?void 0:l.replace("px",""))||1.3,fillLineDash:[0,0],strokeLineDash:X3(o.get("stroke-dasharray"))},t)},"userNodeOverrides"),X3=m(e=>{if(!e)return[0,0];const t=e.trim().split(/\s+/).map(Number);if(t.length===1){const n=isNaN(t[0])?0:t[0];return[n,n]}const r=isNaN(t[0])?0:t[0],i=isNaN(t[1])?0:t[1];return[r,i]},"getStrokeDashArray"),u0={},ae={};Object.defineProperty(ae,"__esModule",{value:!0});ae.BLANK_URL=ae.relativeFirstCharacters=ae.whitespaceEscapeCharsRegex=ae.urlSchemeRegex=ae.ctrlCharactersRegex=ae.htmlCtrlEntityRegex=ae.htmlEntitiesRegex=ae.invalidProtocolRegex=void 0;ae.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im;ae.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g;ae.htmlCtrlEntityRegex=/&(newline|tab);/gi;ae.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;ae.urlSchemeRegex=/^.+(:|:)/gim;ae.whitespaceEscapeCharsRegex=/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g;ae.relativeFirstCharacters=[".","/"];ae.BLANK_URL="about:blank";Object.defineProperty(u0,"__esModule",{value:!0});var Z3=u0.sanitizeUrl=tB,ge=ae;function K3(e){return ge.relativeFirstCharacters.indexOf(e[0])>-1}function Q3(e){var t=e.replace(ge.ctrlCharactersRegex,"");return t.replace(ge.htmlEntitiesRegex,function(r,i){return String.fromCharCode(i)})}function J3(e){return URL.canParse(e)}function Ad(e){try{return decodeURIComponent(e)}catch{return e}}function tB(e){if(!e)return ge.BLANK_URL;var t,r=Ad(e.trim());do r=Q3(r).replace(ge.htmlCtrlEntityRegex,"").replace(ge.ctrlCharactersRegex,"").replace(ge.whitespaceEscapeCharsRegex,"").trim(),r=Ad(r),t=r.match(ge.ctrlCharactersRegex)||r.match(ge.htmlEntitiesRegex)||r.match(ge.htmlCtrlEntityRegex)||r.match(ge.whitespaceEscapeCharsRegex);while(t&&t.length>0);var i=r;if(!i)return ge.BLANK_URL;if(K3(i))return i;var n=i.trimStart(),a=n.match(ge.urlSchemeRegex);if(!a)return i;var o=a[0].toLowerCase().trim();if(ge.invalidProtocolRegex.test(o))return ge.BLANK_URL;var s=n.replace(/\\/g,"/");if(o==="mailto:"||o.includes("://"))return s;if(o==="http:"||o==="https:"){if(!J3(s))return ge.BLANK_URL;var l=new URL(s);return l.protocol=l.protocol.toLowerCase(),l.hostname=l.hostname.toLowerCase(),l.toString()}return s}var d0=typeof global=="object"&&global&&global.Object===Object&&global,eB=typeof self=="object"&&self&&self.Object===Object&&self,mr=d0||eB||Function("return this")(),Gs=mr.Symbol,f0=Object.prototype,rB=f0.hasOwnProperty,iB=f0.toString,In=Gs?Gs.toStringTag:void 0;function nB(e){var t=rB.call(e,In),r=e[In];try{e[In]=void 0;var i=!0}catch{}var n=iB.call(e);return i&&(t?e[In]=r:delete e[In]),n}var aB=Object.prototype,sB=aB.toString;function oB(e){return sB.call(e)}var lB="[object Null]",cB="[object Undefined]",Bd=Gs?Gs.toStringTag:void 0;function vn(e){return e==null?e===void 0?cB:lB:Bd&&Bd in Object(e)?nB(e):oB(e)}function Mi(e){var t=typeof e;return e!=null&&(t=="object"||t=="function")}var hB="[object AsyncFunction]",uB="[object Function]",dB="[object GeneratorFunction]",fB="[object Proxy]";function Ph(e){if(!Mi(e))return!1;var t=vn(e);return t==uB||t==dB||t==hB||t==fB}var wl=mr["__core-js_shared__"],Ed=function(){var e=/[^.]+$/.exec(wl&&wl.keys&&wl.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}();function pB(e){return!!Ed&&Ed in e}var gB=Function.prototype,mB=gB.toString;function $i(e){if(e!=null){try{return mB.call(e)}catch{}try{return e+""}catch{}}return""}var yB=/[\\^$.*+?()[\]{}|]/g,bB=/^\[object .+?Constructor\]$/,vB=Function.prototype,xB=Object.prototype,_B=vB.toString,kB=xB.hasOwnProperty,wB=RegExp("^"+_B.call(kB).replace(yB,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function CB(e){if(!Mi(e)||pB(e))return!1;var t=Ph(e)?wB:bB;return t.test($i(e))}function SB(e,t){return e==null?void 0:e[t]}function Li(e,t){var r=SB(e,t);return CB(r)?r:void 0}var da=Li(Object,"create");function TB(){this.__data__=da?da(null):{},this.size=0}function MB(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}var $B="__lodash_hash_undefined__",LB=Object.prototype,AB=LB.hasOwnProperty;function BB(e){var t=this.__data__;if(da){var r=t[e];return r===$B?void 0:r}return AB.call(t,e)?t[e]:void 0}var EB=Object.prototype,FB=EB.hasOwnProperty;function PB(e){var t=this.__data__;return da?t[e]!==void 0:FB.call(t,e)}var DB="__lodash_hash_undefined__";function IB(e,t){var r=this.__data__;return this.size+=this.has(e)?0:1,r[e]=da&&t===void 0?DB:t,this}function xi(e){var t=-1,r=e==null?0:e.length;for(this.clear();++t-1}function HB(e,t){var r=this.__data__,i=Po(r,e);return i<0?(++this.size,r.push([e,t])):r[i][1]=t,this}function Ir(e){var t=-1,r=e==null?0:e.length;for(this.clear();++t-1&&e%1==0&&e<=m5}function Oo(e){return e!=null&&b0(e.length)&&!Ph(e)}function y5(e){return Ta(e)&&Oo(e)}function b5(){return!1}var v0=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Nd=v0&&typeof module=="object"&&module&&!module.nodeType&&module,v5=Nd&&Nd.exports===v0,zd=v5?mr.Buffer:void 0,x5=zd?zd.isBuffer:void 0,Ih=x5||b5,_5="[object Object]",k5=Function.prototype,w5=Object.prototype,x0=k5.toString,C5=w5.hasOwnProperty,S5=x0.call(Object);function T5(e){if(!Ta(e)||vn(e)!=_5)return!1;var t=m0(e);if(t===null)return!0;var r=C5.call(t,"constructor")&&t.constructor;return typeof r=="function"&&r instanceof r&&x0.call(r)==S5}var M5="[object Arguments]",$5="[object Array]",L5="[object Boolean]",A5="[object Date]",B5="[object Error]",E5="[object Function]",F5="[object Map]",P5="[object Number]",D5="[object Object]",I5="[object RegExp]",O5="[object Set]",R5="[object String]",N5="[object WeakMap]",z5="[object ArrayBuffer]",q5="[object DataView]",W5="[object Float32Array]",H5="[object Float64Array]",V5="[object Int8Array]",U5="[object Int16Array]",j5="[object Int32Array]",Y5="[object Uint8Array]",G5="[object Uint8ClampedArray]",X5="[object Uint16Array]",Z5="[object Uint32Array]",Ht={};Ht[W5]=Ht[H5]=Ht[V5]=Ht[U5]=Ht[j5]=Ht[Y5]=Ht[G5]=Ht[X5]=Ht[Z5]=!0;Ht[M5]=Ht[$5]=Ht[z5]=Ht[L5]=Ht[q5]=Ht[A5]=Ht[B5]=Ht[E5]=Ht[F5]=Ht[P5]=Ht[D5]=Ht[I5]=Ht[O5]=Ht[R5]=Ht[N5]=!1;function K5(e){return Ta(e)&&b0(e.length)&&!!Ht[vn(e)]}function Q5(e){return function(t){return e(t)}}var _0=typeof exports=="object"&&exports&&!exports.nodeType&&exports,ra=_0&&typeof module=="object"&&module&&!module.nodeType&&module,J5=ra&&ra.exports===_0,Cl=J5&&d0.process,qd=function(){try{var e=ra&&ra.require&&ra.require("util").types;return e||Cl&&Cl.binding&&Cl.binding("util")}catch{}}(),Wd=qd&&qd.isTypedArray,Oh=Wd?Q5(Wd):K5;function Sc(e,t){if(!(t==="constructor"&&typeof e[t]=="function")&&t!="__proto__")return e[t]}var tE=Object.prototype,eE=tE.hasOwnProperty;function rE(e,t,r){var i=e[t];(!(eE.call(e,t)&&Fo(i,r))||r===void 0&&!(t in e))&&Dh(e,t,r)}function iE(e,t,r,i){var n=!r;r||(r={});for(var a=-1,o=t.length;++a-1&&e%1==0&&e0){if(++t>=xE)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}var CE=wE(vE);function SE(e,t){return CE(yE(e,t,S0),e+"")}function TE(e,t,r){if(!Mi(r))return!1;var i=typeof t;return(i=="number"?Oo(r)&&k0(t,r.length):i=="string"&&t in r)?Fo(r[t],e):!1}function ME(e){return SE(function(t,r){var i=-1,n=r.length,a=n>1?r[n-1]:void 0,o=n>2?r[2]:void 0;for(a=e.length>3&&typeof a=="function"?(n--,a):void 0,o&&TE(r[0],r[1],o)&&(a=n<3?void 0:a,n=1),t=Object(t);++is.args);ms(o),i=ee(i,[...o])}else i=r.args;if(!i)return;let n=rh(e,t);const a="config";return i[a]!==void 0&&(n==="flowchart-v2"&&(n="flowchart"),i[n]=i[a],delete i[a]),i},"detectInit"),T0=m(function(e,t=null){var r,i;try{const n=new RegExp(`[%]{2}(?![{]${BE.source})(?=[}][%]{2}).* +`,"ig");e=e.trim().replace(n,"").replace(/'/gm,'"'),V.debug(`Detecting diagram directive${t!==null?" type:"+t:""} based on the text:${e}`);let a;const o=[];for(;(a=Jn.exec(e))!==null;)if(a.index===Jn.lastIndex&&Jn.lastIndex++,a&&!t||t&&((r=a[1])!=null&&r.match(t))||t&&((i=a[2])!=null&&i.match(t))){const s=a[1]?a[1]:a[2],l=a[3]?a[3].trim():a[4]?JSON.parse(a[4].trim()):null;o.push({type:s,args:l})}return o.length===0?{type:e,args:null}:o.length===1?o[0]:o}catch(n){return V.error(`ERROR: ${n.message} - Unable to parse directive type: '${t}' based on the text: '${e}'`),{type:void 0,args:null}}},"detectDirective"),FE=m(function(e){return e.replace(Jn,"")},"removeDirectives"),PE=m(function(e,t){for(const[r,i]of t.entries())if(i.match(e))return r;return-1},"isSubstringInArray");function Rh(e,t){if(!e)return t;const r=`curve${e.charAt(0).toUpperCase()+e.slice(1)}`;return AE[r]??t}m(Rh,"interpolateToCurve");function M0(e,t){const r=e.trim();if(r)return t.securityLevel!=="loose"?Z3(r):r}m(M0,"formatUrl");var DE=m((e,...t)=>{const r=e.split("."),i=r.length-1,n=r[i];let a=window;for(let o=0;o{r+=Nh(n,t),t=n});const i=r/2;return zh(e,i)}m($0,"traverseEdge");function L0(e){return e.length===1?e[0]:$0(e)}m(L0,"calcLabelPosition");var Vd=m((e,t=2)=>{const r=Math.pow(10,t);return Math.round(e*r)/r},"roundNumber"),zh=m((e,t)=>{let r,i=t;for(const n of e){if(r){const a=Nh(n,r);if(a===0)return r;if(a=1)return{x:n.x,y:n.y};if(o>0&&o<1)return{x:Vd((1-o)*r.x+o*n.x,5),y:Vd((1-o)*r.y+o*n.y,5)}}}r=n}throw new Error("Could not find a suitable point for the given distance")},"calculatePoint"),IE=m((e,t,r)=>{V.info(`our points ${JSON.stringify(t)}`),t[0]!==r&&(t=t.reverse());const n=zh(t,25),a=e?10:5,o=Math.atan2(t[0].y-n.y,t[0].x-n.x),s={x:0,y:0};return s.x=Math.sin(o)*a+(t[0].x+n.x)/2,s.y=-Math.cos(o)*a+(t[0].y+n.y)/2,s},"calcCardinalityPosition");function A0(e,t,r){const i=structuredClone(r);V.info("our points",i),t!=="start_left"&&t!=="start_right"&&i.reverse();const n=25+e,a=zh(i,n),o=10+e*.5,s=Math.atan2(i[0].y-a.y,i[0].x-a.x),l={x:0,y:0};return t==="start_left"?(l.x=Math.sin(s+Math.PI)*o+(i[0].x+a.x)/2,l.y=-Math.cos(s+Math.PI)*o+(i[0].y+a.y)/2):t==="end_right"?(l.x=Math.sin(s-Math.PI)*o+(i[0].x+a.x)/2-5,l.y=-Math.cos(s-Math.PI)*o+(i[0].y+a.y)/2-5):t==="end_left"?(l.x=Math.sin(s)*o+(i[0].x+a.x)/2-5,l.y=-Math.cos(s)*o+(i[0].y+a.y)/2-5):(l.x=Math.sin(s)*o+(i[0].x+a.x)/2,l.y=-Math.cos(s)*o+(i[0].y+a.y)/2),l}m(A0,"calcTerminalLabelPosition");function B0(e){let t="",r="";for(const i of e)i!==void 0&&(i.startsWith("color:")||i.startsWith("text-align:")?r=r+i+";":t=t+i+";");return{style:t,labelStyle:r}}m(B0,"getStylesFromArray");var Ud=0,OE=m(()=>(Ud++,"id-"+Math.random().toString(36).substr(2,12)+"-"+Ud),"generateId");function E0(e){let t="";const r="0123456789abcdef",i=r.length;for(let n=0;nE0(e.length),"random"),NE=m(function(){return{x:0,y:0,fill:void 0,anchor:"start",style:"#666",width:100,height:100,textMargin:0,rx:0,ry:0,valign:void 0,text:""}},"getTextObj"),zE=m(function(e,t){const r=t.text.replace(yn.lineBreakRegex," "),[,i]=Ro(t.fontSize),n=e.append("text");n.attr("x",t.x),n.attr("y",t.y),n.style("text-anchor",t.anchor),n.style("font-family",t.fontFamily),n.style("font-size",i),n.style("font-weight",t.fontWeight),n.attr("fill",t.fill),t.class!==void 0&&n.attr("class",t.class);const a=n.append("tspan");return a.attr("x",t.x+t.textMargin*2),a.attr("fill",t.fill),a.text(r),n},"drawSimpleText"),qE=Sa((e,t,r)=>{if(!e||(r=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",joinWith:"
"},r),yn.lineBreakRegex.test(e)))return e;const i=e.split(" ").filter(Boolean),n=[];let a="";return i.forEach((o,s)=>{const l=Fr(`${o} `,r),c=Fr(a,r);if(l>t){const{hyphenatedStrings:d,remainingWord:f}=WE(o,t,"-",r);n.push(a,...d),a=f}else c+l>=t?(n.push(a),a=o):a=[a,o].filter(Boolean).join(" ");s+1===i.length&&n.push(a)}),n.filter(o=>o!=="").join(r.joinWith)},(e,t,r)=>`${e}${t}${r.fontSize}${r.fontWeight}${r.fontFamily}${r.joinWith}`),WE=Sa((e,t,r="-",i)=>{i=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:0},i);const n=[...e],a=[];let o="";return n.forEach((s,l)=>{const c=`${o}${s}`;if(Fr(c,i)>=t){const u=l+1,d=n.length===u,f=`${c}${r}`;a.push(d?c:f),o=""}else o=c}),{hyphenatedStrings:a,remainingWord:o}},(e,t,r="-",i)=>`${e}${t}${r}${i.fontSize}${i.fontWeight}${i.fontFamily}`);function F0(e,t){return qh(e,t).height}m(F0,"calculateTextHeight");function Fr(e,t){return qh(e,t).width}m(Fr,"calculateTextWidth");var qh=Sa((e,t)=>{const{fontSize:r=12,fontFamily:i="Arial",fontWeight:n=400}=t;if(!e)return{width:0,height:0};const[,a]=Ro(r),o=["sans-serif",i],s=e.split(yn.lineBreakRegex),l=[],c=Ot("body");if(!c.remove)return{width:0,height:0,lineHeight:0};const h=c.append("svg");for(const d of o){let f=0;const p={width:0,height:0,lineHeight:0};for(const g of s){const y=NE();y.text=g||LE;const b=zE(h,y).style("font-size",a).style("font-weight",n).style("font-family",d),x=(b._groups||b)[0][0].getBBox();if(x.width===0&&x.height===0)throw new Error("svg element not in render tree");p.width=Math.round(Math.max(p.width,x.width)),f=Math.round(x.height),p.height+=f,p.lineHeight=Math.round(Math.max(p.lineHeight,f))}l.push(p)}h.remove();const u=isNaN(l[1].height)||isNaN(l[1].width)||isNaN(l[1].lineHeight)||l[0].height>l[1].height&&l[0].width>l[1].width&&l[0].lineHeight>l[1].lineHeight?0:1;return l[u]},(e,t)=>`${e}${t.fontSize}${t.fontWeight}${t.fontFamily}`),en,HE=(en=class{constructor(t=!1,r){this.count=0,this.count=r?r.length:0,this.next=t?()=>this.count++:()=>Date.now()}},m(en,"InitIDGenerator"),en),Wa,VE=m(function(e){return Wa=Wa||document.createElement("div"),e=escape(e).replace(/%26/g,"&").replace(/%23/g,"#").replace(/%3B/g,";"),Wa.innerHTML=e,unescape(Wa.textContent)},"entityDecode");function Wh(e){return"str"in e}m(Wh,"isDetailedError");var UE=m((e,t,r,i)=>{var a;if(!i)return;const n=(a=e.node())==null?void 0:a.getBBox();n&&e.append("text").text(i).attr("text-anchor","middle").attr("x",n.x+n.width/2).attr("y",-r).attr("class",t)},"insertTitle"),Ro=m(e=>{if(typeof e=="number")return[e,e+"px"];const t=parseInt(e??"",10);return Number.isNaN(t)?[void 0,void 0]:e===String(t)?[t,e+"px"]:[t,e]},"parseFontSize");function Hh(e,t){return $E({},e,t)}m(Hh,"cleanAndMerge");var Xe={assignWithDepth:ee,wrapLabel:qE,calculateTextHeight:F0,calculateTextWidth:Fr,calculateTextDimensions:qh,cleanAndMerge:Hh,detectInit:EE,detectDirective:T0,isSubstringInArray:PE,interpolateToCurve:Rh,calcLabelPosition:L0,calcCardinalityPosition:IE,calcTerminalLabelPosition:A0,formatUrl:M0,getStylesFromArray:B0,generateId:OE,random:RE,runFunc:DE,entityDecode:VE,insertTitle:UE,isLabelCoordinateInPath:P0,parseFontSize:Ro,InitIDGenerator:HE},jE=m(function(e){let t=e;return t=t.replace(/style.*:\S*#.*;/g,function(r){return r.substring(0,r.length-1)}),t=t.replace(/classDef.*:\S*#.*;/g,function(r){return r.substring(0,r.length-1)}),t=t.replace(/#\w+;/g,function(r){const i=r.substring(1,r.length-1);return/^\+?\d+$/.test(i)?"fl°°"+i+"¶ß":"fl°"+i+"¶ß"}),t},"encodeEntities"),Ai=m(function(e){return e.replace(/fl°°/g,"&#").replace(/fl°/g,"&").replace(/¶ß/g,";")},"decodeEntities"),VO=m((e,t,{counter:r=0,prefix:i,suffix:n},a)=>a||`${i?`${i}_`:""}${e}_${t}_${r}${n?`_${n}`:""}`,"getEdgeId");function xe(e){return e??null}m(xe,"handleUndefinedAttr");function P0(e,t){const r=Math.round(e.x),i=Math.round(e.y),n=t.replace(/(\d+\.\d+)/g,a=>Math.round(parseFloat(a)).toString());return n.includes(r.toString())||n.includes(i.toString())}m(P0,"isLabelCoordinateInPath");const YE=Object.freeze({left:0,top:0,width:16,height:16}),Qs=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),D0=Object.freeze({...YE,...Qs}),GE=Object.freeze({...D0,body:"",hidden:!1}),XE=Object.freeze({width:null,height:null}),ZE=Object.freeze({...XE,...Qs}),KE=(e,t,r,i="")=>{const n=e.split(":");if(e.slice(0,1)==="@"){if(n.length<2||n.length>3)return null;i=n.shift().slice(1)}if(n.length>3||!n.length)return null;if(n.length>1){const s=n.pop(),l=n.pop(),c={provider:n.length>0?n[0]:i,prefix:l,name:s};return Sl(c)?c:null}const a=n[0],o=a.split("-");if(o.length>1){const s={provider:i,prefix:o.shift(),name:o.join("-")};return Sl(s)?s:null}if(r&&i===""){const s={provider:i,prefix:"",name:a};return Sl(s,r)?s:null}return null},Sl=(e,t)=>e?!!((t&&e.prefix===""||e.prefix)&&e.name):!1;function QE(e,t){const r={};!e.hFlip!=!t.hFlip&&(r.hFlip=!0),!e.vFlip!=!t.vFlip&&(r.vFlip=!0);const i=((e.rotate||0)+(t.rotate||0))%4;return i&&(r.rotate=i),r}function jd(e,t){const r=QE(e,t);for(const i in GE)i in Qs?i in e&&!(i in r)&&(r[i]=Qs[i]):i in t?r[i]=t[i]:i in e&&(r[i]=e[i]);return r}function JE(e,t){const r=e.icons,i=e.aliases||Object.create(null),n=Object.create(null);function a(o){if(r[o])return n[o]=[];if(!(o in n)){n[o]=null;const s=i[o]&&i[o].parent,l=s&&a(s);l&&(n[o]=[s].concat(l))}return n[o]}return(t||Object.keys(r).concat(Object.keys(i))).forEach(a),n}function Yd(e,t,r){const i=e.icons,n=e.aliases||Object.create(null);let a={};function o(s){a=jd(i[s]||n[s],a)}return o(t),r.forEach(o),jd(e,a)}function tF(e,t){if(e.icons[t])return Yd(e,t,[]);const r=JE(e,[t])[t];return r?Yd(e,t,r):null}const eF=/(-?[0-9.]*[0-9]+[0-9.]*)/g,rF=/^-?[0-9.]*[0-9]+[0-9.]*$/g;function Gd(e,t,r){if(t===1)return e;if(r=r||100,typeof e=="number")return Math.ceil(e*t*r)/r;if(typeof e!="string")return e;const i=e.split(eF);if(i===null||!i.length)return e;const n=[];let a=i.shift(),o=rF.test(a);for(;;){if(o){const s=parseFloat(a);isNaN(s)?n.push(a):n.push(Math.ceil(s*t*r)/r)}else n.push(a);if(a=i.shift(),a===void 0)return n.join("");o=!o}}function iF(e,t="defs"){let r="";const i=e.indexOf("<"+t);for(;i>=0;){const n=e.indexOf(">",i),a=e.indexOf("",a);if(o===-1)break;r+=e.slice(n+1,a).trim(),e=e.slice(0,i).trim()+e.slice(o+1)}return{defs:r,content:e}}function nF(e,t){return e?""+e+""+t:t}function aF(e,t,r){const i=iF(e);return nF(i.defs,t+i.content+r)}const sF=e=>e==="unset"||e==="undefined"||e==="none";function oF(e,t){const r={...D0,...e},i={...ZE,...t},n={left:r.left,top:r.top,width:r.width,height:r.height};let a=r.body;[r,i].forEach(g=>{const y=[],b=g.hFlip,x=g.vFlip;let _=g.rotate;b?x?_+=2:(y.push("translate("+(n.width+n.left).toString()+" "+(0-n.top).toString()+")"),y.push("scale(-1 1)"),n.top=n.left=0):x&&(y.push("translate("+(0-n.left).toString()+" "+(n.height+n.top).toString()+")"),y.push("scale(1 -1)"),n.top=n.left=0);let w;switch(_<0&&(_-=Math.floor(_/4)*4),_=_%4,_){case 1:w=n.height/2+n.top,y.unshift("rotate(90 "+w.toString()+" "+w.toString()+")");break;case 2:y.unshift("rotate(180 "+(n.width/2+n.left).toString()+" "+(n.height/2+n.top).toString()+")");break;case 3:w=n.width/2+n.left,y.unshift("rotate(-90 "+w.toString()+" "+w.toString()+")");break}_%2===1&&(n.left!==n.top&&(w=n.left,n.left=n.top,n.top=w),n.width!==n.height&&(w=n.width,n.width=n.height,n.height=w)),y.length&&(a=aF(a,'',""))});const o=i.width,s=i.height,l=n.width,c=n.height;let h,u;o===null?(u=s===null?"1em":s==="auto"?c:s,h=Gd(u,l/c)):(h=o==="auto"?l:o,u=s===null?Gd(h,c/l):s==="auto"?c:s);const d={},f=(g,y)=>{sF(y)||(d[g]=y.toString())};f("width",h),f("height",u);const p=[n.left,n.top,l,c];return d.viewBox=p.join(" "),{attributes:d,viewBox:p,body:a}}const lF=/\sid="(\S+)"/g,Xd=new Map;function cF(e){e=e.replace(/[0-9]+$/,"")||"a";const t=Xd.get(e)||0;return Xd.set(e,t+1),t?`${e}${t}`:e}function hF(e){const t=[];let r;for(;r=lF.exec(e);)t.push(r[1]);if(!t.length)return e;const i="suffix"+(Math.random()*16777216|Date.now()).toString(16);return t.forEach(n=>{const a=cF(n),o=n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");e=e.replace(new RegExp('([#;"])('+o+')([")]|\\.[a-z])',"g"),"$1"+a+i+"$3")}),e=e.replace(new RegExp(i,"g"),""),e}function uF(e,t){let r=e.indexOf("xlink:")===-1?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(const i in t)r+=" "+i+'="'+t[i]+'"';return'"+e+""}function Vh(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var Bi=Vh();function I0(e){Bi=e}var ia={exec:()=>null};function zt(e,t=""){let r=typeof e=="string"?e:e.source,i={replace:(n,a)=>{let o=typeof a=="string"?a:a.source;return o=o.replace(Me.caret,"$1"),r=r.replace(n,o),i},getRegex:()=>new RegExp(r,t)};return i}var dF=(()=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^
/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},fF=/^(?:[ \t]*(?:\n|$))+/,pF=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,gF=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,Ma=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,mF=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,Uh=/(?:[*+-]|\d{1,9}[.)])/,O0=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,R0=zt(O0).replace(/bull/g,Uh).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),yF=zt(O0).replace(/bull/g,Uh).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),jh=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,bF=/^[^\n]+/,Yh=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,vF=zt(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",Yh).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),xF=zt(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,Uh).getRegex(),No="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Gh=/|$))/,_F=zt("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",Gh).replace("tag",No).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),N0=zt(jh).replace("hr",Ma).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",No).getRegex(),kF=zt(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",N0).getRegex(),Xh={blockquote:kF,code:pF,def:vF,fences:gF,heading:mF,hr:Ma,html:_F,lheading:R0,list:xF,newline:fF,paragraph:N0,table:ia,text:bF},Zd=zt("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",Ma).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",No).getRegex(),wF={...Xh,lheading:yF,table:Zd,paragraph:zt(jh).replace("hr",Ma).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Zd).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",No).getRegex()},CF={...Xh,html:zt(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",Gh).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:ia,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:zt(jh).replace("hr",Ma).replace("heading",` *#{1,6} *[^ +]`).replace("lheading",R0).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},SF=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,TF=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,z0=/^( {2,}|\\)\n(?!\s*$)/,MF=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",dF?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),H0=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,EF=zt(H0,"u").replace(/punct/g,zo).getRegex(),FF=zt(H0,"u").replace(/punct/g,W0).getRegex(),V0="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",PF=zt(V0,"gu").replace(/notPunctSpace/g,q0).replace(/punctSpace/g,Zh).replace(/punct/g,zo).getRegex(),DF=zt(V0,"gu").replace(/notPunctSpace/g,AF).replace(/punctSpace/g,LF).replace(/punct/g,W0).getRegex(),IF=zt("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,q0).replace(/punctSpace/g,Zh).replace(/punct/g,zo).getRegex(),OF=zt(/\\(punct)/,"gu").replace(/punct/g,zo).getRegex(),RF=zt(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),NF=zt(Gh).replace("(?:-->|$)","-->").getRegex(),zF=zt("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",NF).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),Js=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+[^`]*?`+(?!`)|[^\[\]\\`])*?/,qF=zt(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",Js).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),U0=zt(/^!?\[(label)\]\[(ref)\]/).replace("label",Js).replace("ref",Yh).getRegex(),j0=zt(/^!?\[(ref)\](?:\[\])?/).replace("ref",Yh).getRegex(),WF=zt("reflink|nolink(?!\\()","g").replace("reflink",U0).replace("nolink",j0).getRegex(),Kd=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,Kh={_backpedal:ia,anyPunctuation:OF,autolink:RF,blockSkip:BF,br:z0,code:TF,del:ia,emStrongLDelim:EF,emStrongRDelimAst:PF,emStrongRDelimUnd:IF,escape:SF,link:qF,nolink:j0,punctuation:$F,reflink:U0,reflinkSearch:WF,tag:zF,text:MF,url:ia},HF={...Kh,link:zt(/^!?\[(label)\]\((.*?)\)/).replace("label",Js).getRegex(),reflink:zt(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Js).getRegex()},Tc={...Kh,emStrongRDelimAst:DF,emStrongLDelim:FF,url:zt(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",Kd).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:zt(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},Qd=e=>UF[e];function sr(e,t){if(t){if(Me.escapeTest.test(e))return e.replace(Me.escapeReplace,Qd)}else if(Me.escapeTestNoEncode.test(e))return e.replace(Me.escapeReplaceNoEncode,Qd);return e}function Jd(e){try{e=encodeURI(e).replace(Me.percentDecode,"%")}catch{return null}return e}function tf(e,t){var a;let r=e.replace(Me.findPipe,(o,s,l)=>{let c=!1,h=s;for(;--h>=0&&l[h]==="\\";)c=!c;return c?"|":" |"}),i=r.split(Me.splitPipe),n=0;if(i[0].trim()||i.shift(),i.length>0&&!((a=i.at(-1))!=null&&a.trim())&&i.pop(),t)if(i.length>t)i.splice(t);else for(;i.length0?-2:-1}function ef(e,t,r,i,n){let a=t.href,o=t.title||null,s=e[1].replace(n.other.outputLinkReplace,"$1");i.state.inLink=!0;let l={type:e[0].charAt(0)==="!"?"image":"link",raw:r,href:a,title:o,text:s,tokens:i.inlineTokens(s)};return i.state.inLink=!1,l}function YF(e,t,r){let i=e.match(r.other.indentCodeCompensation);if(i===null)return t;let n=i[1];return t.split(` +`).map(a=>{let o=a.match(r.other.beginningSpace);if(o===null)return a;let[s]=o;return s.length>=n.length?a.slice(n.length):a}).join(` +`)}var to=class{constructor(t){Lt(this,"options");Lt(this,"rules");Lt(this,"lexer");this.options=t||Bi}space(t){let r=this.rules.block.newline.exec(t);if(r&&r[0].length>0)return{type:"space",raw:r[0]}}code(t){let r=this.rules.block.code.exec(t);if(r){let i=r[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:r[0],codeBlockStyle:"indented",text:this.options.pedantic?i:Rn(i,` +`)}}}fences(t){let r=this.rules.block.fences.exec(t);if(r){let i=r[0],n=YF(i,r[3]||"",this.rules);return{type:"code",raw:i,lang:r[2]?r[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):r[2],text:n}}}heading(t){let r=this.rules.block.heading.exec(t);if(r){let i=r[2].trim();if(this.rules.other.endingHash.test(i)){let n=Rn(i,"#");(this.options.pedantic||!n||this.rules.other.endingSpaceChar.test(n))&&(i=n.trim())}return{type:"heading",raw:r[0],depth:r[1].length,text:i,tokens:this.lexer.inline(i)}}}hr(t){let r=this.rules.block.hr.exec(t);if(r)return{type:"hr",raw:Rn(r[0],` +`)}}blockquote(t){let r=this.rules.block.blockquote.exec(t);if(r){let i=Rn(r[0],` +`).split(` +`),n="",a="",o=[];for(;i.length>0;){let s=!1,l=[],c;for(c=0;c1,a={type:"list",raw:"",ordered:n,start:n?+i.slice(0,-1):"",loose:!1,items:[]};i=n?`\\d{1,9}\\${i.slice(-1)}`:`\\${i}`,this.options.pedantic&&(i=n?i:"[*+-]");let o=this.rules.other.listItemRegex(i),s=!1;for(;t;){let c=!1,h="",u="";if(!(r=o.exec(t))||this.rules.block.hr.test(t))break;h=r[0],t=t.substring(h.length);let d=r[2].split(` +`,1)[0].replace(this.rules.other.listReplaceTabs,x=>" ".repeat(3*x.length)),f=t.split(` +`,1)[0],p=!d.trim(),g=0;if(this.options.pedantic?(g=2,u=d.trimStart()):p?g=r[1].length+1:(g=r[2].search(this.rules.other.nonSpaceChar),g=g>4?1:g,u=d.slice(g),g+=r[1].length),p&&this.rules.other.blankLine.test(f)&&(h+=f+` +`,t=t.substring(f.length+1),c=!0),!c){let x=this.rules.other.nextBulletRegex(g),_=this.rules.other.hrRegex(g),w=this.rules.other.fencesBeginRegex(g),C=this.rules.other.headingBeginRegex(g),v=this.rules.other.htmlBeginRegex(g);for(;t;){let k=t.split(` +`,1)[0],$;if(f=k,this.options.pedantic?(f=f.replace(this.rules.other.listReplaceNesting," "),$=f):$=f.replace(this.rules.other.tabCharGlobal," "),w.test(f)||C.test(f)||v.test(f)||x.test(f)||_.test(f))break;if($.search(this.rules.other.nonSpaceChar)>=g||!f.trim())u+=` +`+$.slice(g);else{if(p||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||w.test(d)||C.test(d)||_.test(d))break;u+=` +`+f}!p&&!f.trim()&&(p=!0),h+=k+` +`,t=t.substring(k.length+1),d=$.slice(g)}}a.loose||(s?a.loose=!0:this.rules.other.doubleBlankLine.test(h)&&(s=!0));let y=null,b;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(b=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),a.items.push({type:"list_item",raw:h,task:!!y,checked:b,loose:!1,text:u,tokens:[]}),a.raw+=h}let l=a.items.at(-1);if(l)l.raw=l.raw.trimEnd(),l.text=l.text.trimEnd();else return;a.raw=a.raw.trimEnd();for(let c=0;cd.type==="space"),u=h.length>0&&h.some(d=>this.rules.other.anyLine.test(d.raw));a.loose=u}if(a.loose)for(let c=0;c({text:c,tokens:this.lexer.inline(c),header:!1,align:o.align[h]})));return o}}lheading(t){let r=this.rules.block.lheading.exec(t);if(r)return{type:"heading",raw:r[0],depth:r[2].charAt(0)==="="?1:2,text:r[1],tokens:this.lexer.inline(r[1])}}paragraph(t){let r=this.rules.block.paragraph.exec(t);if(r){let i=r[1].charAt(r[1].length-1)===` +`?r[1].slice(0,-1):r[1];return{type:"paragraph",raw:r[0],text:i,tokens:this.lexer.inline(i)}}}text(t){let r=this.rules.block.text.exec(t);if(r)return{type:"text",raw:r[0],text:r[0],tokens:this.lexer.inline(r[0])}}escape(t){let r=this.rules.inline.escape.exec(t);if(r)return{type:"escape",raw:r[0],text:r[1]}}tag(t){let r=this.rules.inline.tag.exec(t);if(r)return!this.lexer.state.inLink&&this.rules.other.startATag.test(r[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(r[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(r[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(r[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:r[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:r[0]}}link(t){let r=this.rules.inline.link.exec(t);if(r){let i=r[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(i)){if(!this.rules.other.endAngleBracket.test(i))return;let o=Rn(i.slice(0,-1),"\\");if((i.length-o.length)%2===0)return}else{let o=jF(r[2],"()");if(o===-2)return;if(o>-1){let s=(r[0].indexOf("!")===0?5:4)+r[1].length+o;r[2]=r[2].substring(0,o),r[0]=r[0].substring(0,s).trim(),r[3]=""}}let n=r[2],a="";if(this.options.pedantic){let o=this.rules.other.pedanticHrefTitle.exec(n);o&&(n=o[1],a=o[3])}else a=r[3]?r[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(i)?n=n.slice(1):n=n.slice(1,-1)),ef(r,{href:n&&n.replace(this.rules.inline.anyPunctuation,"$1"),title:a&&a.replace(this.rules.inline.anyPunctuation,"$1")},r[0],this.lexer,this.rules)}}reflink(t,r){let i;if((i=this.rules.inline.reflink.exec(t))||(i=this.rules.inline.nolink.exec(t))){let n=(i[2]||i[1]).replace(this.rules.other.multipleSpaceGlobal," "),a=r[n.toLowerCase()];if(!a){let o=i[0].charAt(0);return{type:"text",raw:o,text:o}}return ef(i,a,i[0],this.lexer,this.rules)}}emStrong(t,r,i=""){let n=this.rules.inline.emStrongLDelim.exec(t);if(!(!n||n[3]&&i.match(this.rules.other.unicodeAlphaNumeric))&&(!(n[1]||n[2])||!i||this.rules.inline.punctuation.exec(i))){let a=[...n[0]].length-1,o,s,l=a,c=0,h=n[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(h.lastIndex=0,r=r.slice(-1*t.length+a);(n=h.exec(r))!=null;){if(o=n[1]||n[2]||n[3]||n[4]||n[5]||n[6],!o)continue;if(s=[...o].length,n[3]||n[4]){l+=s;continue}else if((n[5]||n[6])&&a%3&&!((a+s)%3)){c+=s;continue}if(l-=s,l>0)continue;s=Math.min(s,s+l+c);let u=[...n[0]][0].length,d=t.slice(0,a+n.index+u+s);if(Math.min(a,s)%2){let p=d.slice(1,-1);return{type:"em",raw:d,text:p,tokens:this.lexer.inlineTokens(p)}}let f=d.slice(2,-2);return{type:"strong",raw:d,text:f,tokens:this.lexer.inlineTokens(f)}}}}codespan(t){let r=this.rules.inline.code.exec(t);if(r){let i=r[2].replace(this.rules.other.newLineCharGlobal," "),n=this.rules.other.nonSpaceChar.test(i),a=this.rules.other.startingSpaceChar.test(i)&&this.rules.other.endingSpaceChar.test(i);return n&&a&&(i=i.substring(1,i.length-1)),{type:"codespan",raw:r[0],text:i}}}br(t){let r=this.rules.inline.br.exec(t);if(r)return{type:"br",raw:r[0]}}del(t){let r=this.rules.inline.del.exec(t);if(r)return{type:"del",raw:r[0],text:r[2],tokens:this.lexer.inlineTokens(r[2])}}autolink(t){let r=this.rules.inline.autolink.exec(t);if(r){let i,n;return r[2]==="@"?(i=r[1],n="mailto:"+i):(i=r[1],n=i),{type:"link",raw:r[0],text:i,href:n,tokens:[{type:"text",raw:i,text:i}]}}}url(t){var i;let r;if(r=this.rules.inline.url.exec(t)){let n,a;if(r[2]==="@")n=r[0],a="mailto:"+n;else{let o;do o=r[0],r[0]=((i=this.rules.inline._backpedal.exec(r[0]))==null?void 0:i[0])??"";while(o!==r[0]);n=r[0],r[1]==="www."?a="http://"+r[0]:a=r[0]}return{type:"link",raw:r[0],text:n,href:a,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(t){let r=this.rules.inline.text.exec(t);if(r){let i=this.lexer.state.inRawBlock;return{type:"text",raw:r[0],text:r[0],escaped:i}}}},je=class Mc{constructor(t){Lt(this,"tokens");Lt(this,"options");Lt(this,"state");Lt(this,"tokenizer");Lt(this,"inlineQueue");this.tokens=[],this.tokens.links=Object.create(null),this.options=t||Bi,this.options.tokenizer=this.options.tokenizer||new to,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let r={other:Me,block:Ha.normal,inline:On.normal};this.options.pedantic?(r.block=Ha.pedantic,r.inline=On.pedantic):this.options.gfm&&(r.block=Ha.gfm,this.options.breaks?r.inline=On.breaks:r.inline=On.gfm),this.tokenizer.rules=r}static get rules(){return{block:Ha,inline:On}}static lex(t,r){return new Mc(r).lex(t)}static lexInline(t,r){return new Mc(r).inlineTokens(t)}lex(t){t=t.replace(Me.carriageReturn,` +`),this.blockTokens(t,this.tokens);for(let r=0;r(s=c.call({lexer:this},t,r))?(t=t.substring(s.raw.length),r.push(s),!0):!1))continue;if(s=this.tokenizer.space(t)){t=t.substring(s.raw.length);let c=r.at(-1);s.raw.length===1&&c!==void 0?c.raw+=` +`:r.push(s);continue}if(s=this.tokenizer.code(t)){t=t.substring(s.raw.length);let c=r.at(-1);(c==null?void 0:c.type)==="paragraph"||(c==null?void 0:c.type)==="text"?(c.raw+=(c.raw.endsWith(` +`)?"":` +`)+s.raw,c.text+=` +`+s.text,this.inlineQueue.at(-1).src=c.text):r.push(s);continue}if(s=this.tokenizer.fences(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.heading(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.hr(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.blockquote(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.list(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.html(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.def(t)){t=t.substring(s.raw.length);let c=r.at(-1);(c==null?void 0:c.type)==="paragraph"||(c==null?void 0:c.type)==="text"?(c.raw+=(c.raw.endsWith(` +`)?"":` +`)+s.raw,c.text+=` +`+s.raw,this.inlineQueue.at(-1).src=c.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title},r.push(s));continue}if(s=this.tokenizer.table(t)){t=t.substring(s.raw.length),r.push(s);continue}if(s=this.tokenizer.lheading(t)){t=t.substring(s.raw.length),r.push(s);continue}let l=t;if((o=this.options.extensions)!=null&&o.startBlock){let c=1/0,h=t.slice(1),u;this.options.extensions.startBlock.forEach(d=>{u=d.call({lexer:this},h),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(l=t.substring(0,c+1))}if(this.state.top&&(s=this.tokenizer.paragraph(l))){let c=r.at(-1);i&&(c==null?void 0:c.type)==="paragraph"?(c.raw+=(c.raw.endsWith(` +`)?"":` +`)+s.raw,c.text+=` +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=c.text):r.push(s),i=l.length!==t.length,t=t.substring(s.raw.length);continue}if(s=this.tokenizer.text(t)){t=t.substring(s.raw.length);let c=r.at(-1);(c==null?void 0:c.type)==="text"?(c.raw+=(c.raw.endsWith(` +`)?"":` +`)+s.raw,c.text+=` +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=c.text):r.push(s);continue}if(t){let c="Infinite loop on byte: "+t.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return this.state.top=!0,r}inline(t,r=[]){return this.inlineQueue.push({src:t,tokens:r}),r}inlineTokens(t,r=[]){var l,c,h,u,d;let i=t,n=null;if(this.tokens.links){let f=Object.keys(this.tokens.links);if(f.length>0)for(;(n=this.tokenizer.rules.inline.reflinkSearch.exec(i))!=null;)f.includes(n[0].slice(n[0].lastIndexOf("[")+1,-1))&&(i=i.slice(0,n.index)+"["+"a".repeat(n[0].length-2)+"]"+i.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(n=this.tokenizer.rules.inline.anyPunctuation.exec(i))!=null;)i=i.slice(0,n.index)+"++"+i.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let a;for(;(n=this.tokenizer.rules.inline.blockSkip.exec(i))!=null;)a=n[2]?n[2].length:0,i=i.slice(0,n.index+a)+"["+"a".repeat(n[0].length-a-2)+"]"+i.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);i=((c=(l=this.options.hooks)==null?void 0:l.emStrongMask)==null?void 0:c.call({lexer:this},i))??i;let o=!1,s="";for(;t;){o||(s=""),o=!1;let f;if((u=(h=this.options.extensions)==null?void 0:h.inline)!=null&&u.some(g=>(f=g.call({lexer:this},t,r))?(t=t.substring(f.raw.length),r.push(f),!0):!1))continue;if(f=this.tokenizer.escape(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.tag(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.link(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.reflink(t,this.tokens.links)){t=t.substring(f.raw.length);let g=r.at(-1);f.type==="text"&&(g==null?void 0:g.type)==="text"?(g.raw+=f.raw,g.text+=f.text):r.push(f);continue}if(f=this.tokenizer.emStrong(t,i,s)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.codespan(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.br(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.del(t)){t=t.substring(f.raw.length),r.push(f);continue}if(f=this.tokenizer.autolink(t)){t=t.substring(f.raw.length),r.push(f);continue}if(!this.state.inLink&&(f=this.tokenizer.url(t))){t=t.substring(f.raw.length),r.push(f);continue}let p=t;if((d=this.options.extensions)!=null&&d.startInline){let g=1/0,y=t.slice(1),b;this.options.extensions.startInline.forEach(x=>{b=x.call({lexer:this},y),typeof b=="number"&&b>=0&&(g=Math.min(g,b))}),g<1/0&&g>=0&&(p=t.substring(0,g+1))}if(f=this.tokenizer.inlineText(p)){t=t.substring(f.raw.length),f.raw.slice(-1)!=="_"&&(s=f.raw.slice(-1)),o=!0;let g=r.at(-1);(g==null?void 0:g.type)==="text"?(g.raw+=f.raw,g.text+=f.text):r.push(f);continue}if(t){let g="Infinite loop on byte: "+t.charCodeAt(0);if(this.options.silent){console.error(g);break}else throw new Error(g)}}return r}},eo=class{constructor(t){Lt(this,"options");Lt(this,"parser");this.options=t||Bi}space(t){return""}code({text:t,lang:r,escaped:i}){var o;let n=(o=(r||"").match(Me.notSpaceStart))==null?void 0:o[0],a=t.replace(Me.endingNewline,"")+` +`;return n?'
'+(i?a:sr(a,!0))+`
+`:"
"+(i?a:sr(a,!0))+`
+`}blockquote({tokens:t}){return`
+${this.parser.parse(t)}
+`}html({text:t}){return t}def(t){return""}heading({tokens:t,depth:r}){return`${this.parser.parseInline(t)} +`}hr(t){return`
+`}list(t){let r=t.ordered,i=t.start,n="";for(let s=0;s +`+n+" +`}listitem(t){var i;let r="";if(t.task){let n=this.checkbox({checked:!!t.checked});t.loose?((i=t.tokens[0])==null?void 0:i.type)==="paragraph"?(t.tokens[0].text=n+" "+t.tokens[0].text,t.tokens[0].tokens&&t.tokens[0].tokens.length>0&&t.tokens[0].tokens[0].type==="text"&&(t.tokens[0].tokens[0].text=n+" "+sr(t.tokens[0].tokens[0].text),t.tokens[0].tokens[0].escaped=!0)):t.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):r+=n+" "}return r+=this.parser.parse(t.tokens,!!t.loose),`
  • ${r}
  • +`}checkbox({checked:t}){return"'}paragraph({tokens:t}){return`

    ${this.parser.parseInline(t)}

    +`}table(t){let r="",i="";for(let a=0;a${n}`),` + +`+r+` +`+n+`
    +`}tablerow({text:t}){return` +${t} +`}tablecell(t){let r=this.parser.parseInline(t.tokens),i=t.header?"th":"td";return(t.align?`<${i} align="${t.align}">`:`<${i}>`)+r+` +`}strong({tokens:t}){return`${this.parser.parseInline(t)}`}em({tokens:t}){return`${this.parser.parseInline(t)}`}codespan({text:t}){return`${sr(t,!0)}`}br(t){return"
    "}del({tokens:t}){return`${this.parser.parseInline(t)}`}link({href:t,title:r,tokens:i}){let n=this.parser.parseInline(i),a=Jd(t);if(a===null)return n;t=a;let o='
    ",o}image({href:t,title:r,text:i,tokens:n}){n&&(i=this.parser.parseInline(n,this.parser.textRenderer));let a=Jd(t);if(a===null)return sr(i);t=a;let o=`${i}{let c=s[l].flat(1/0);i=i.concat(this.walkTokens(c,r))}):s.tokens&&(i=i.concat(this.walkTokens(s.tokens,r)))}}return i}use(...t){let r=this.defaults.extensions||{renderers:{},childTokens:{}};return t.forEach(i=>{let n={...i};if(n.async=this.defaults.async||n.async||!1,i.extensions&&(i.extensions.forEach(a=>{if(!a.name)throw new Error("extension name required");if("renderer"in a){let o=r.renderers[a.name];o?r.renderers[a.name]=function(...s){let l=a.renderer.apply(this,s);return l===!1&&(l=o.apply(this,s)),l}:r.renderers[a.name]=a.renderer}if("tokenizer"in a){if(!a.level||a.level!=="block"&&a.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let o=r[a.level];o?o.unshift(a.tokenizer):r[a.level]=[a.tokenizer],a.start&&(a.level==="block"?r.startBlock?r.startBlock.push(a.start):r.startBlock=[a.start]:a.level==="inline"&&(r.startInline?r.startInline.push(a.start):r.startInline=[a.start]))}"childTokens"in a&&a.childTokens&&(r.childTokens[a.name]=a.childTokens)}),n.extensions=r),i.renderer){let a=this.defaults.renderer||new eo(this.defaults);for(let o in i.renderer){if(!(o in a))throw new Error(`renderer '${o}' does not exist`);if(["options","parser"].includes(o))continue;let s=o,l=i.renderer[s],c=a[s];a[s]=(...h)=>{let u=l.apply(a,h);return u===!1&&(u=c.apply(a,h)),u||""}}n.renderer=a}if(i.tokenizer){let a=this.defaults.tokenizer||new to(this.defaults);for(let o in i.tokenizer){if(!(o in a))throw new Error(`tokenizer '${o}' does not exist`);if(["options","rules","lexer"].includes(o))continue;let s=o,l=i.tokenizer[s],c=a[s];a[s]=(...h)=>{let u=l.apply(a,h);return u===!1&&(u=c.apply(a,h)),u}}n.tokenizer=a}if(i.hooks){let a=this.defaults.hooks||new Yn;for(let o in i.hooks){if(!(o in a))throw new Error(`hook '${o}' does not exist`);if(["options","block"].includes(o))continue;let s=o,l=i.hooks[s],c=a[s];Yn.passThroughHooks.has(o)?a[s]=h=>{if(this.defaults.async&&Yn.passThroughHooksRespectAsync.has(o))return(async()=>{let d=await l.call(a,h);return c.call(a,d)})();let u=l.call(a,h);return c.call(a,u)}:a[s]=(...h)=>{if(this.defaults.async)return(async()=>{let d=await l.apply(a,h);return d===!1&&(d=await c.apply(a,h)),d})();let u=l.apply(a,h);return u===!1&&(u=c.apply(a,h)),u}}n.hooks=a}if(i.walkTokens){let a=this.defaults.walkTokens,o=i.walkTokens;n.walkTokens=function(s){let l=[];return l.push(o.call(this,s)),a&&(l=l.concat(a.call(this,s))),l}}this.defaults={...this.defaults,...n}}),this}setOptions(t){return this.defaults={...this.defaults,...t},this}lexer(t,r){return je.lex(t,r??this.defaults)}parser(t,r){return Ye.parse(t,r??this.defaults)}parseMarkdown(t){return(r,i)=>{let n={...i},a={...this.defaults,...n},o=this.onError(!!a.silent,!!a.async);if(this.defaults.async===!0&&n.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof r>"u"||r===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof r!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(r)+", string expected"));if(a.hooks&&(a.hooks.options=a,a.hooks.block=t),a.async)return(async()=>{let s=a.hooks?await a.hooks.preprocess(r):r,l=await(a.hooks?await a.hooks.provideLexer():t?je.lex:je.lexInline)(s,a),c=a.hooks?await a.hooks.processAllTokens(l):l;a.walkTokens&&await Promise.all(this.walkTokens(c,a.walkTokens));let h=await(a.hooks?await a.hooks.provideParser():t?Ye.parse:Ye.parseInline)(c,a);return a.hooks?await a.hooks.postprocess(h):h})().catch(o);try{a.hooks&&(r=a.hooks.preprocess(r));let s=(a.hooks?a.hooks.provideLexer():t?je.lex:je.lexInline)(r,a);a.hooks&&(s=a.hooks.processAllTokens(s)),a.walkTokens&&this.walkTokens(s,a.walkTokens);let l=(a.hooks?a.hooks.provideParser():t?Ye.parse:Ye.parseInline)(s,a);return a.hooks&&(l=a.hooks.postprocess(l)),l}catch(s){return o(s)}}}onError(t,r){return i=>{if(i.message+=` +Please report this to https://github.com/markedjs/marked.`,t){let n="

    An error occurred:

    "+sr(i.message+"",!0)+"
    ";return r?Promise.resolve(n):n}if(r)return Promise.reject(i);throw i}}},_i=new GF;function Wt(e,t){return _i.parse(e,t)}Wt.options=Wt.setOptions=function(e){return _i.setOptions(e),Wt.defaults=_i.defaults,I0(Wt.defaults),Wt};Wt.getDefaults=Vh;Wt.defaults=Bi;Wt.use=function(...e){return _i.use(...e),Wt.defaults=_i.defaults,I0(Wt.defaults),Wt};Wt.walkTokens=function(e,t){return _i.walkTokens(e,t)};Wt.parseInline=_i.parseInline;Wt.Parser=Ye;Wt.parser=Ye.parse;Wt.Renderer=eo;Wt.TextRenderer=Qh;Wt.Lexer=je;Wt.lexer=je.lex;Wt.Tokenizer=to;Wt.Hooks=Yn;Wt.parse=Wt;Wt.options;Wt.setOptions;Wt.use;Wt.walkTokens;Wt.parseInline;Ye.parse;je.lex;function Y0(e){for(var t=[],r=1;r?',height:80,width:80},Lc=new Map,G0=new Map,ZF=m(e=>{for(const t of e){if(!t.name)throw new Error('Invalid icon loader. Must have a "name" property with non-empty string value.');if(V.debug("Registering icon pack:",t.name),"loader"in t)G0.set(t.name,t.loader);else if("icons"in t)Lc.set(t.name,t.icons);else throw V.error("Invalid icon loader:",t),new Error('Invalid icon loader. Must have either "icons" or "loader" property.')}},"registerIconPacks"),X0=m(async(e,t)=>{const r=KE(e,!0,t!==void 0);if(!r)throw new Error(`Invalid icon name: ${e}`);const i=r.prefix||t;if(!i)throw new Error(`Icon name must contain a prefix: ${e}`);let n=Lc.get(i);if(!n){const o=G0.get(i);if(!o)throw new Error(`Icon set not found: ${r.prefix}`);try{n={...await o(),prefix:i},Lc.set(i,n)}catch(s){throw V.error(s),new Error(`Failed to load icon set: ${r.prefix}`)}}const a=tF(n,r.name);if(!a)throw new Error(`Icon not found: ${e}`);return a},"getRegisteredIconData"),KF=m(async e=>{try{return await X0(e),!0}catch{return!1}},"isIconAvailable"),$a=m(async(e,t,r)=>{let i;try{i=await X0(e,t==null?void 0:t.fallbackPrefix)}catch(o){V.error(o),i=XF}const n=oF(i,t),a=uF(hF(n.body),{...n.attributes,...r});return qe(a,be())},"getIconSVG");function Z0(e,{markdownAutoWrap:t}){const i=e.replace(//g,` +`).replace(/\n{2,}/g,` +`),n=Y0(i);return t===!1?n.replace(/ /g," "):n}m(Z0,"preprocessMarkdown");function K0(e,t={}){const r=Z0(e,t),i=Wt.lexer(r),n=[[]];let a=0;function o(s,l="normal"){s.type==="text"?s.text.split(` +`).forEach((h,u)=>{u!==0&&(a++,n.push([])),h.split(" ").forEach(d=>{d=d.replace(/'/g,"'"),d&&n[a].push({content:d,type:l})})}):s.type==="strong"||s.type==="em"?s.tokens.forEach(c=>{o(c,s.type)}):s.type==="html"&&n[a].push({content:s.text,type:"normal"})}return m(o,"processNode"),i.forEach(s=>{var l;s.type==="paragraph"?(l=s.tokens)==null||l.forEach(c=>{o(c)}):s.type==="html"?n[a].push({content:s.text,type:"normal"}):n[a].push({content:s.raw,type:"normal"})}),n}m(K0,"markdownToLines");function Q0(e,{markdownAutoWrap:t}={}){const r=Wt.lexer(e);function i(n){var a,o,s;return n.type==="text"?t===!1?n.text.replace(/\n */g,"
    ").replace(/ /g," "):n.text.replace(/\n */g,"
    "):n.type==="strong"?`${(a=n.tokens)==null?void 0:a.map(i).join("")}`:n.type==="em"?`${(o=n.tokens)==null?void 0:o.map(i).join("")}`:n.type==="paragraph"?`

    ${(s=n.tokens)==null?void 0:s.map(i).join("")}

    `:n.type==="space"?"":n.type==="html"?`${n.text}`:n.type==="escape"?n.text:(V.warn(`Unsupported markdown: ${n.type}`),n.raw)}return m(i,"output"),r.map(i).join("")}m(Q0,"markdownToHTML");function J0(e){return Intl.Segmenter?[...new Intl.Segmenter().segment(e)].map(t=>t.segment):[...e]}m(J0,"splitTextToChars");function ty(e,t){const r=J0(t.content);return Jh(e,[],r,t.type)}m(ty,"splitWordToFitWidth");function Jh(e,t,r,i){if(r.length===0)return[{content:t.join(""),type:i},{content:"",type:i}];const[n,...a]=r,o=[...t,n];return e([{content:o.join(""),type:i}])?Jh(e,o,a,i):(t.length===0&&n&&(t.push(n),r.shift()),[{content:t.join(""),type:i},{content:r.join(""),type:i}])}m(Jh,"splitWordToFitWidthRecursion");function ey(e,t){if(e.some(({content:r})=>r.includes(` +`)))throw new Error("splitLineToFitWidth does not support newlines in the line");return ro(e,t)}m(ey,"splitLineToFitWidth");function ro(e,t,r=[],i=[]){if(e.length===0)return i.length>0&&r.push(i),r.length>0?r:[];let n="";e[0].content===" "&&(n=" ",e.shift());const a=e.shift()??{content:" ",type:"normal"},o=[...i];if(n!==""&&o.push({content:n,type:"normal"}),o.push(a),t(o))return ro(e,t,r,o);if(i.length>0)r.push(i),e.unshift(a);else if(a.content){const[s,l]=ty(t,a);r.push([s]),l.content&&e.unshift(l)}return ro(e,t,r)}m(ro,"splitLineToFitWidthRecursion");function Ac(e,t){t&&e.attr("style",t)}m(Ac,"applyStyle");async function ry(e,t,r,i,n=!1,a=be()){const o=e.append("foreignObject");o.attr("width",`${10*r}px`),o.attr("height",`${10*r}px`);const s=o.append("xhtml:div"),l=an(t.label)?await ih(t.label.replace(yn.lineBreakRegex,` +`),a):qe(t.label,a),c=t.isNode?"nodeLabel":"edgeLabel",h=s.append("span");h.html(l),Ac(h,t.labelStyle),h.attr("class",`${c} ${i}`),Ac(s,t.labelStyle),s.style("display","table-cell"),s.style("white-space","nowrap"),s.style("line-height","1.5"),s.style("max-width",r+"px"),s.style("text-align","center"),s.attr("xmlns","http://www.w3.org/1999/xhtml"),n&&s.attr("class","labelBkg");let u=s.node().getBoundingClientRect();return u.width===r&&(s.style("display","table"),s.style("white-space","break-spaces"),s.style("width",r+"px"),u=s.node().getBoundingClientRect()),o.node()}m(ry,"addHtmlSpan");function qo(e,t,r){return e.append("tspan").attr("class","text-outer-tspan").attr("x",0).attr("y",t*r-.1+"em").attr("dy",r+"em")}m(qo,"createTspan");function iy(e,t,r){const i=e.append("text"),n=qo(i,1,t);Wo(n,r);const a=n.node().getComputedTextLength();return i.remove(),a}m(iy,"computeWidthOfText");function QF(e,t,r){var o;const i=e.append("text"),n=qo(i,1,t);Wo(n,[{content:r,type:"normal"}]);const a=(o=n.node())==null?void 0:o.getBoundingClientRect();return a&&i.remove(),a}m(QF,"computeDimensionOfText");function ny(e,t,r,i=!1){const a=t.append("g"),o=a.insert("rect").attr("class","background").attr("style","stroke: none"),s=a.append("text").attr("y","-10.1");let l=0;for(const c of r){const h=m(d=>iy(a,1.1,d)<=e,"checkWidth"),u=h(c)?[c]:ey(c,h);for(const d of u){const f=qo(s,l,1.1);Wo(f,d),l++}}if(i){const c=s.node().getBBox(),h=2;return o.attr("x",c.x-h).attr("y",c.y-h).attr("width",c.width+2*h).attr("height",c.height+2*h),a.node()}else return s.node()}m(ny,"createFormattedText");function Wo(e,t){e.text(""),t.forEach((r,i)=>{const n=e.append("tspan").attr("font-style",r.type==="em"?"italic":"normal").attr("class","text-inner-tspan").attr("font-weight",r.type==="strong"?"bold":"normal");i===0?n.text(r.content):n.text(" "+r.content)})}m(Wo,"updateTextContentAndStyles");async function ay(e,t={}){const r=[];e.replace(/(fa[bklrs]?):fa-([\w-]+)/g,(n,a,o)=>(r.push((async()=>{const s=`${a}:${o}`;return await KF(s)?await $a(s,void 0,{class:"label-icon"}):``})()),n));const i=await Promise.all(r);return e.replace(/(fa[bklrs]?):fa-([\w-]+)/g,()=>i.shift()??"")}m(ay,"replaceIconSubstring");var Xr=m(async(e,t="",{style:r="",isTitle:i=!1,classes:n="",useHtmlLabels:a=!0,isNode:o=!0,width:s=200,addSvgBackground:l=!1}={},c)=>{if(V.debug("XYZ createText",t,r,i,n,a,o,"addSvgBackground: ",l),a){const h=Q0(t,c),u=await ay(Ai(h),c),d=t.replace(/\\\\/g,"\\"),f={isNode:o,label:an(t)?d:u,labelStyle:r.replace("fill:","color:")};return await ry(e,f,s,n,l,c)}else{const h=t.replace(//g,"
    "),u=K0(h.replace("
    ","
    "),c),d=ny(s,e,u,t?l:!1);if(o){/stroke:/.exec(r)&&(r=r.replace("stroke:","lineColor:"));const f=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/color:/g,"fill:");Ot(d).attr("style",f)}else{const f=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/background:/g,"fill:");Ot(d).select("rect").attr("style",f.replace(/background:/g,"fill:"));const p=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/color:/g,"fill:");Ot(d).select("text").attr("style",p)}return d}},"createText");function Tl(e,t,r){if(e&&e.length){const[i,n]=t,a=Math.PI/180*r,o=Math.cos(a),s=Math.sin(a);for(const l of e){const[c,h]=l;l[0]=(c-i)*o-(h-n)*s+i,l[1]=(c-i)*s+(h-n)*o+n}}}function JF(e,t){return e[0]===t[0]&&e[1]===t[1]}function t4(e,t,r,i=1){const n=r,a=Math.max(t,.1),o=e[0]&&e[0][0]&&typeof e[0][0]=="number"?[e]:e,s=[0,0];if(n)for(const c of o)Tl(c,s,n);const l=function(c,h,u){const d=[];for(const x of c){const _=[...x];JF(_[0],_[_.length-1])||_.push([_[0][0],_[0][1]]),_.length>2&&d.push(_)}const f=[];h=Math.max(h,.1);const p=[];for(const x of d)for(let _=0;_x.ymin<_.ymin?-1:x.ymin>_.ymin?1:x.x<_.x?-1:x.x>_.x?1:x.ymax===_.ymax?0:(x.ymax-_.ymax)/Math.abs(x.ymax-_.ymax)),!p.length)return f;let g=[],y=p[0].ymin,b=0;for(;g.length||p.length;){if(p.length){let x=-1;for(let _=0;_y);_++)x=_;p.splice(0,x+1).forEach(_=>{g.push({s:y,edge:_})})}if(g=g.filter(x=>!(x.edge.ymax<=y)),g.sort((x,_)=>x.edge.x===_.edge.x?0:(x.edge.x-_.edge.x)/Math.abs(x.edge.x-_.edge.x)),(u!==1||b%h==0)&&g.length>1)for(let x=0;x=g.length)break;const w=g[x].edge,C=g[_].edge;f.push([[Math.round(w.x),y],[Math.round(C.x),y]])}y+=u,g.forEach(x=>{x.edge.x=x.edge.x+u*x.edge.islope}),b++}return f}(o,a,i);if(n){for(const c of o)Tl(c,s,-n);(function(c,h,u){const d=[];c.forEach(f=>d.push(...f)),Tl(d,h,u)})(l,s,-n)}return l}function La(e,t){var r;const i=t.hachureAngle+90;let n=t.hachureGap;n<0&&(n=4*t.strokeWidth),n=Math.round(Math.max(n,.1));let a=1;return t.roughness>=1&&(((r=t.randomizer)===null||r===void 0?void 0:r.next())||Math.random())>.7&&(a=n),t4(e,n,i,a||1)}class tu{constructor(t){this.helper=t}fillPolygons(t,r){return this._fillPolygons(t,r)}_fillPolygons(t,r){const i=La(t,r);return{type:"fillSketch",ops:this.renderLines(i,r)}}renderLines(t,r){const i=[];for(const n of t)i.push(...this.helper.doubleLineOps(n[0][0],n[0][1],n[1][0],n[1][1],r));return i}}function Ho(e){const t=e[0],r=e[1];return Math.sqrt(Math.pow(t[0]-r[0],2)+Math.pow(t[1]-r[1],2))}class e4 extends tu{fillPolygons(t,r){let i=r.hachureGap;i<0&&(i=4*r.strokeWidth),i=Math.max(i,.1);const n=La(t,Object.assign({},r,{hachureGap:i})),a=Math.PI/180*r.hachureAngle,o=[],s=.5*i*Math.cos(a),l=.5*i*Math.sin(a);for(const[c,h]of n)Ho([c,h])&&o.push([[c[0]-s,c[1]+l],[...h]],[[c[0]+s,c[1]-l],[...h]]);return{type:"fillSketch",ops:this.renderLines(o,r)}}}class r4 extends tu{fillPolygons(t,r){const i=this._fillPolygons(t,r),n=Object.assign({},r,{hachureAngle:r.hachureAngle+90}),a=this._fillPolygons(t,n);return i.ops=i.ops.concat(a.ops),i}}class i4{constructor(t){this.helper=t}fillPolygons(t,r){const i=La(t,r=Object.assign({},r,{hachureAngle:0}));return this.dotsOnLines(i,r)}dotsOnLines(t,r){const i=[];let n=r.hachureGap;n<0&&(n=4*r.strokeWidth),n=Math.max(n,.1);let a=r.fillWeight;a<0&&(a=r.strokeWidth/2);const o=n/4;for(const s of t){const l=Ho(s),c=l/n,h=Math.ceil(c)-1,u=l-h*n,d=(s[0][0]+s[1][0])/2-n/4,f=Math.min(s[0][1],s[1][1]);for(let p=0;p{const s=Ho(o),l=Math.floor(s/(i+n)),c=(s+n-l*(i+n))/2;let h=o[0],u=o[1];h[0]>u[0]&&(h=o[1],u=o[0]);const d=Math.atan((u[1]-h[1])/(u[0]-h[0]));for(let f=0;f{const o=Ho(a),s=Math.round(o/(2*r));let l=a[0],c=a[1];l[0]>c[0]&&(l=a[1],c=a[0]);const h=Math.atan((c[1]-l[1])/(c[0]-l[0]));for(let u=0;uh%2?c+r:c+t);a.push({key:"C",data:l}),t=l[4],r=l[5];break}case"Q":a.push({key:"Q",data:[...s]}),t=s[2],r=s[3];break;case"q":{const l=s.map((c,h)=>h%2?c+r:c+t);a.push({key:"Q",data:l}),t=l[2],r=l[3];break}case"A":a.push({key:"A",data:[...s]}),t=s[5],r=s[6];break;case"a":t+=s[5],r+=s[6],a.push({key:"A",data:[s[0],s[1],s[2],s[3],s[4],t,r]});break;case"H":a.push({key:"H",data:[...s]}),t=s[0];break;case"h":t+=s[0],a.push({key:"H",data:[t]});break;case"V":a.push({key:"V",data:[...s]}),r=s[0];break;case"v":r+=s[0],a.push({key:"V",data:[r]});break;case"S":a.push({key:"S",data:[...s]}),t=s[2],r=s[3];break;case"s":{const l=s.map((c,h)=>h%2?c+r:c+t);a.push({key:"S",data:l}),t=l[2],r=l[3];break}case"T":a.push({key:"T",data:[...s]}),t=s[0],r=s[1];break;case"t":t+=s[0],r+=s[1],a.push({key:"T",data:[t,r]});break;case"Z":case"z":a.push({key:"Z",data:[]}),t=i,r=n}return a}function oy(e){const t=[];let r="",i=0,n=0,a=0,o=0,s=0,l=0;for(const{key:c,data:h}of e){switch(c){case"M":t.push({key:"M",data:[...h]}),[i,n]=h,[a,o]=h;break;case"C":t.push({key:"C",data:[...h]}),i=h[4],n=h[5],s=h[2],l=h[3];break;case"L":t.push({key:"L",data:[...h]}),[i,n]=h;break;case"H":i=h[0],t.push({key:"L",data:[i,n]});break;case"V":n=h[0],t.push({key:"L",data:[i,n]});break;case"S":{let u=0,d=0;r==="C"||r==="S"?(u=i+(i-s),d=n+(n-l)):(u=i,d=n),t.push({key:"C",data:[u,d,...h]}),s=h[0],l=h[1],i=h[2],n=h[3];break}case"T":{const[u,d]=h;let f=0,p=0;r==="Q"||r==="T"?(f=i+(i-s),p=n+(n-l)):(f=i,p=n);const g=i+2*(f-i)/3,y=n+2*(p-n)/3,b=u+2*(f-u)/3,x=d+2*(p-d)/3;t.push({key:"C",data:[g,y,b,x,u,d]}),s=f,l=p,i=u,n=d;break}case"Q":{const[u,d,f,p]=h,g=i+2*(u-i)/3,y=n+2*(d-n)/3,b=f+2*(u-f)/3,x=p+2*(d-p)/3;t.push({key:"C",data:[g,y,b,x,f,p]}),s=u,l=d,i=f,n=p;break}case"A":{const u=Math.abs(h[0]),d=Math.abs(h[1]),f=h[2],p=h[3],g=h[4],y=h[5],b=h[6];u===0||d===0?(t.push({key:"C",data:[i,n,y,b,y,b]}),i=y,n=b):(i!==y||n!==b)&&(ly(i,n,y,b,u,d,f,p,g).forEach(function(x){t.push({key:"C",data:x})}),i=y,n=b);break}case"Z":t.push({key:"Z",data:[]}),i=a,n=o}r=c}return t}function Nn(e,t,r){return[e*Math.cos(r)-t*Math.sin(r),e*Math.sin(r)+t*Math.cos(r)]}function ly(e,t,r,i,n,a,o,s,l,c){const h=(u=o,Math.PI*u/180);var u;let d=[],f=0,p=0,g=0,y=0;if(c)[f,p,g,y]=c;else{[e,t]=Nn(e,t,-h),[r,i]=Nn(r,i,-h);const D=(e-r)/2,L=(t-i)/2;let B=D*D/(n*n)+L*L/(a*a);B>1&&(B=Math.sqrt(B),n*=B,a*=B);const F=n*n,R=a*a,I=F*R-F*L*L-R*D*D,X=F*L*L+R*D*D,Z=(s===l?-1:1)*Math.sqrt(Math.abs(I/X));g=Z*n*L/a+(e+r)/2,y=Z*-a*D/n+(t+i)/2,f=Math.asin(parseFloat(((t-y)/a).toFixed(9))),p=Math.asin(parseFloat(((i-y)/a).toFixed(9))),ep&&(f-=2*Math.PI),!l&&p>f&&(p-=2*Math.PI)}let b=p-f;if(Math.abs(b)>120*Math.PI/180){const D=p,L=r,B=i;p=l&&p>f?f+120*Math.PI/180*1:f+120*Math.PI/180*-1,d=ly(r=g+n*Math.cos(p),i=y+a*Math.sin(p),L,B,n,a,o,0,l,[p,D,g,y])}b=p-f;const x=Math.cos(f),_=Math.sin(f),w=Math.cos(p),C=Math.sin(p),v=Math.tan(b/4),k=4/3*n*v,$=4/3*a*v,z=[e,t],W=[e+k*_,t-$*x],O=[r+k*C,i-$*w],N=[r,i];if(W[0]=2*z[0]-W[0],W[1]=2*z[1]-W[1],c)return[W,O,N].concat(d);{d=[W,O,N].concat(d);const D=[];for(let L=0;L2){const n=[];for(let a=0;a2*Math.PI&&(f=0,p=2*Math.PI);const g=2*Math.PI/l.curveStepCount,y=Math.min(g/2,(p-f)/2),b=cf(y,c,h,u,d,f,p,1,l);if(!l.disableMultiStroke){const x=cf(y,c,h,u,d,f,p,1.5,l);b.push(...x)}return o&&(s?b.push(...jr(c,h,c+u*Math.cos(f),h+d*Math.sin(f),l),...jr(c,h,c+u*Math.cos(p),h+d*Math.sin(p),l)):b.push({op:"lineTo",data:[c,h]},{op:"lineTo",data:[c+u*Math.cos(f),h+d*Math.sin(f)]})),{type:"path",ops:b}}function sf(e,t){const r=oy(sy(eu(e))),i=[];let n=[0,0],a=[0,0];for(const{key:o,data:s}of r)switch(o){case"M":a=[s[0],s[1]],n=[s[0],s[1]];break;case"L":i.push(...jr(a[0],a[1],s[0],s[1],t)),a=[s[0],s[1]];break;case"C":{const[l,c,h,u,d,f]=s;i.push(...h4(l,c,h,u,d,f,a,t)),a=[d,f];break}case"Z":i.push(...jr(a[0],a[1],n[0],n[1],t)),a=[n[0],n[1]]}return{type:"path",ops:i}}function Ll(e,t){const r=[];for(const i of e)if(i.length){const n=t.maxRandomnessOffset||0,a=i.length;if(a>2){r.push({op:"move",data:[i[0][0]+wt(n,t),i[0][1]+wt(n,t)]});for(let o=1;o500?.4:-.0016668*l+1.233334;let h=n.maxRandomnessOffset||0;h*h*100>s&&(h=l/10);const u=h/2,d=.2+.2*uy(n);let f=n.bowing*n.maxRandomnessOffset*(i-t)/200,p=n.bowing*n.maxRandomnessOffset*(e-r)/200;f=wt(f,n,c),p=wt(p,n,c);const g=[],y=()=>wt(u,n,c),b=()=>wt(h,n,c),x=n.preserveVertices;return o?g.push({op:"move",data:[e+(x?0:y()),t+(x?0:y())]}):g.push({op:"move",data:[e+(x?0:wt(h,n,c)),t+(x?0:wt(h,n,c))]}),o?g.push({op:"bcurveTo",data:[f+e+(r-e)*d+y(),p+t+(i-t)*d+y(),f+e+2*(r-e)*d+y(),p+t+2*(i-t)*d+y(),r+(x?0:y()),i+(x?0:y())]}):g.push({op:"bcurveTo",data:[f+e+(r-e)*d+b(),p+t+(i-t)*d+b(),f+e+2*(r-e)*d+b(),p+t+2*(i-t)*d+b(),r+(x?0:b()),i+(x?0:b())]}),g}function Ua(e,t,r){if(!e.length)return[];const i=[];i.push([e[0][0]+wt(t,r),e[0][1]+wt(t,r)]),i.push([e[0][0]+wt(t,r),e[0][1]+wt(t,r)]);for(let n=1;n3){const a=[],o=1-r.curveTightness;n.push({op:"move",data:[e[1][0],e[1][1]]});for(let s=1;s+21&&n.push(s)):n.push(s),n.push(e[t+3])}else{const l=e[t+0],c=e[t+1],h=e[t+2],u=e[t+3],d=ni(l,c,.5),f=ni(c,h,.5),p=ni(h,u,.5),g=ni(d,f,.5),y=ni(f,p,.5),b=ni(g,y,.5);Fc([l,d,g,b],0,r,n),Fc([b,y,p,u],0,r,n)}var a,o;return n}function d4(e,t){return ao(e,0,e.length,t)}function ao(e,t,r,i,n){const a=n||[],o=e[t],s=e[r-1];let l=0,c=1;for(let h=t+1;hl&&(l=u,c=h)}return Math.sqrt(l)>i?(ao(e,t,c+1,i,a),ao(e,c,r,i,a)):(a.length||a.push(o),a.push(s)),a}function Al(e,t=.15,r){const i=[],n=(e.length-1)/3;for(let a=0;a0?ao(i,0,i.length,r):i}const Pe="none";let so=class{constructor(t){this.defaultOptions={maxRandomnessOffset:2,roughness:1,bowing:1,stroke:"#000",strokeWidth:1,curveTightness:0,curveFitting:.95,curveStepCount:9,fillStyle:"hachure",fillWeight:-1,hachureAngle:-41,hachureGap:-1,dashOffset:-1,dashGap:-1,zigzagOffset:-1,seed:0,disableMultiStroke:!1,disableMultiStrokeFill:!1,preserveVertices:!1,fillShapeRoughnessGain:.8},this.config=t||{},this.config.options&&(this.defaultOptions=this._o(this.config.options))}static newSeed(){return Math.floor(Math.random()*2**31)}_o(t){return t?Object.assign({},this.defaultOptions,t):this.defaultOptions}_d(t,r,i){return{shape:t,sets:r||[],options:i||this.defaultOptions}}line(t,r,i,n,a){const o=this._o(a);return this._d("line",[cy(t,r,i,n,o)],o)}rectangle(t,r,i,n,a){const o=this._o(a),s=[],l=c4(t,r,i,n,o);if(o.fill){const c=[[t,r],[t+i,r],[t+i,r+n],[t,r+n]];o.fillStyle==="solid"?s.push(Ll([c],o)):s.push(Oi([c],o))}return o.stroke!==Pe&&s.push(l),this._d("rectangle",s,o)}ellipse(t,r,i,n,a){const o=this._o(a),s=[],l=hy(i,n,o),c=Bc(t,r,o,l);if(o.fill)if(o.fillStyle==="solid"){const h=Bc(t,r,o,l).opset;h.type="fillPath",s.push(h)}else s.push(Oi([c.estimatedPoints],o));return o.stroke!==Pe&&s.push(c.opset),this._d("ellipse",s,o)}circle(t,r,i,n){const a=this.ellipse(t,r,i,i,n);return a.shape="circle",a}linearPath(t,r){const i=this._o(r);return this._d("linearPath",[cs(t,!1,i)],i)}arc(t,r,i,n,a,o,s=!1,l){const c=this._o(l),h=[],u=af(t,r,i,n,a,o,s,!0,c);if(s&&c.fill)if(c.fillStyle==="solid"){const d=Object.assign({},c);d.disableMultiStroke=!0;const f=af(t,r,i,n,a,o,!0,!1,d);f.type="fillPath",h.push(f)}else h.push(function(d,f,p,g,y,b,x){const _=d,w=f;let C=Math.abs(p/2),v=Math.abs(g/2);C+=wt(.01*C,x),v+=wt(.01*v,x);let k=y,$=b;for(;k<0;)k+=2*Math.PI,$+=2*Math.PI;$-k>2*Math.PI&&(k=0,$=2*Math.PI);const z=($-k)/x.curveStepCount,W=[];for(let O=k;O<=$;O+=z)W.push([_+C*Math.cos(O),w+v*Math.sin(O)]);return W.push([_+C*Math.cos($),w+v*Math.sin($)]),W.push([_,w]),Oi([W],x)}(t,r,i,n,a,o,c));return c.stroke!==Pe&&h.push(u),this._d("arc",h,c)}curve(t,r){const i=this._o(r),n=[],a=nf(t,i);if(i.fill&&i.fill!==Pe)if(i.fillStyle==="solid"){const o=nf(t,Object.assign(Object.assign({},i),{disableMultiStroke:!0,roughness:i.roughness?i.roughness+i.fillShapeRoughnessGain:0}));n.push({type:"fillPath",ops:this._mergedShape(o.ops)})}else{const o=[],s=t;if(s.length){const l=typeof s[0][0]=="number"?[s]:s;for(const c of l)c.length<3?o.push(...c):c.length===3?o.push(...Al(hf([c[0],c[0],c[1],c[2]]),10,(1+i.roughness)/2)):o.push(...Al(hf(c),10,(1+i.roughness)/2))}o.length&&n.push(Oi([o],i))}return i.stroke!==Pe&&n.push(a),this._d("curve",n,i)}polygon(t,r){const i=this._o(r),n=[],a=cs(t,!0,i);return i.fill&&(i.fillStyle==="solid"?n.push(Ll([t],i)):n.push(Oi([t],i))),i.stroke!==Pe&&n.push(a),this._d("polygon",n,i)}path(t,r){const i=this._o(r),n=[];if(!t)return this._d("path",n,i);t=(t||"").replace(/\n/g," ").replace(/(-\s)/g,"-").replace("/(ss)/g"," ");const a=i.fill&&i.fill!=="transparent"&&i.fill!==Pe,o=i.stroke!==Pe,s=!!(i.simplification&&i.simplification<1),l=function(h,u,d){const f=oy(sy(eu(h))),p=[];let g=[],y=[0,0],b=[];const x=()=>{b.length>=4&&g.push(...Al(b,u)),b=[]},_=()=>{x(),g.length&&(p.push(g),g=[])};for(const{key:C,data:v}of f)switch(C){case"M":_(),y=[v[0],v[1]],g.push(y);break;case"L":x(),g.push([v[0],v[1]]);break;case"C":if(!b.length){const k=g.length?g[g.length-1]:y;b.push([k[0],k[1]])}b.push([v[0],v[1]]),b.push([v[2],v[3]]),b.push([v[4],v[5]]);break;case"Z":x(),g.push([y[0],y[1]])}if(_(),!d)return p;const w=[];for(const C of p){const v=d4(C,d);v.length&&w.push(v)}return w}(t,1,s?4-4*(i.simplification||1):(1+i.roughness)/2),c=sf(t,i);if(a)if(i.fillStyle==="solid")if(l.length===1){const h=sf(t,Object.assign(Object.assign({},i),{disableMultiStroke:!0,roughness:i.roughness?i.roughness+i.fillShapeRoughnessGain:0}));n.push({type:"fillPath",ops:this._mergedShape(h.ops)})}else n.push(Ll(l,i));else n.push(Oi(l,i));return o&&(s?l.forEach(h=>{n.push(cs(h,!1,i))}):n.push(c)),this._d("path",n,i)}opsToPath(t,r){let i="";for(const n of t.ops){const a=typeof r=="number"&&r>=0?n.data.map(o=>+o.toFixed(r)):n.data;switch(n.op){case"move":i+=`M${a[0]} ${a[1]} `;break;case"bcurveTo":i+=`C${a[0]} ${a[1]}, ${a[2]} ${a[3]}, ${a[4]} ${a[5]} `;break;case"lineTo":i+=`L${a[0]} ${a[1]} `}}return i.trim()}toPaths(t){const r=t.sets||[],i=t.options||this.defaultOptions,n=[];for(const a of r){let o=null;switch(a.type){case"path":o={d:this.opsToPath(a),stroke:i.stroke,strokeWidth:i.strokeWidth,fill:Pe};break;case"fillPath":o={d:this.opsToPath(a),stroke:Pe,strokeWidth:0,fill:i.fill||Pe};break;case"fillSketch":o=this.fillSketch(a,i)}o&&n.push(o)}return n}fillSketch(t,r){let i=r.fillWeight;return i<0&&(i=r.strokeWidth/2),{d:this.opsToPath(t),stroke:r.fill||Pe,strokeWidth:i,fill:Pe}}_mergedShape(t){return t.filter((r,i)=>i===0||r.op!=="move")}};class f4{constructor(t,r){this.canvas=t,this.ctx=this.canvas.getContext("2d"),this.gen=new so(r)}draw(t){const r=t.sets||[],i=t.options||this.getDefaultOptions(),n=this.ctx,a=t.options.fixedDecimalPlaceDigits;for(const o of r)switch(o.type){case"path":n.save(),n.strokeStyle=i.stroke==="none"?"transparent":i.stroke,n.lineWidth=i.strokeWidth,i.strokeLineDash&&n.setLineDash(i.strokeLineDash),i.strokeLineDashOffset&&(n.lineDashOffset=i.strokeLineDashOffset),this._drawToContext(n,o,a),n.restore();break;case"fillPath":{n.save(),n.fillStyle=i.fill||"";const s=t.shape==="curve"||t.shape==="polygon"||t.shape==="path"?"evenodd":"nonzero";this._drawToContext(n,o,a,s),n.restore();break}case"fillSketch":this.fillSketch(n,o,i)}}fillSketch(t,r,i){let n=i.fillWeight;n<0&&(n=i.strokeWidth/2),t.save(),i.fillLineDash&&t.setLineDash(i.fillLineDash),i.fillLineDashOffset&&(t.lineDashOffset=i.fillLineDashOffset),t.strokeStyle=i.fill||"",t.lineWidth=n,this._drawToContext(t,r,i.fixedDecimalPlaceDigits),t.restore()}_drawToContext(t,r,i,n="nonzero"){t.beginPath();for(const a of r.ops){const o=typeof i=="number"&&i>=0?a.data.map(s=>+s.toFixed(i)):a.data;switch(a.op){case"move":t.moveTo(o[0],o[1]);break;case"bcurveTo":t.bezierCurveTo(o[0],o[1],o[2],o[3],o[4],o[5]);break;case"lineTo":t.lineTo(o[0],o[1])}}r.type==="fillPath"?t.fill(n):t.stroke()}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}line(t,r,i,n,a){const o=this.gen.line(t,r,i,n,a);return this.draw(o),o}rectangle(t,r,i,n,a){const o=this.gen.rectangle(t,r,i,n,a);return this.draw(o),o}ellipse(t,r,i,n,a){const o=this.gen.ellipse(t,r,i,n,a);return this.draw(o),o}circle(t,r,i,n){const a=this.gen.circle(t,r,i,n);return this.draw(a),a}linearPath(t,r){const i=this.gen.linearPath(t,r);return this.draw(i),i}polygon(t,r){const i=this.gen.polygon(t,r);return this.draw(i),i}arc(t,r,i,n,a,o,s=!1,l){const c=this.gen.arc(t,r,i,n,a,o,s,l);return this.draw(c),c}curve(t,r){const i=this.gen.curve(t,r);return this.draw(i),i}path(t,r){const i=this.gen.path(t,r);return this.draw(i),i}}const ja="http://www.w3.org/2000/svg";let p4=class{constructor(t,r){this.svg=t,this.gen=new so(r)}draw(t){const r=t.sets||[],i=t.options||this.getDefaultOptions(),n=this.svg.ownerDocument||window.document,a=n.createElementNS(ja,"g"),o=t.options.fixedDecimalPlaceDigits;for(const s of r){let l=null;switch(s.type){case"path":l=n.createElementNS(ja,"path"),l.setAttribute("d",this.opsToPath(s,o)),l.setAttribute("stroke",i.stroke),l.setAttribute("stroke-width",i.strokeWidth+""),l.setAttribute("fill","none"),i.strokeLineDash&&l.setAttribute("stroke-dasharray",i.strokeLineDash.join(" ").trim()),i.strokeLineDashOffset&&l.setAttribute("stroke-dashoffset",`${i.strokeLineDashOffset}`);break;case"fillPath":l=n.createElementNS(ja,"path"),l.setAttribute("d",this.opsToPath(s,o)),l.setAttribute("stroke","none"),l.setAttribute("stroke-width","0"),l.setAttribute("fill",i.fill||""),t.shape!=="curve"&&t.shape!=="polygon"||l.setAttribute("fill-rule","evenodd");break;case"fillSketch":l=this.fillSketch(n,s,i)}l&&a.appendChild(l)}return a}fillSketch(t,r,i){let n=i.fillWeight;n<0&&(n=i.strokeWidth/2);const a=t.createElementNS(ja,"path");return a.setAttribute("d",this.opsToPath(r,i.fixedDecimalPlaceDigits)),a.setAttribute("stroke",i.fill||""),a.setAttribute("stroke-width",n+""),a.setAttribute("fill","none"),i.fillLineDash&&a.setAttribute("stroke-dasharray",i.fillLineDash.join(" ").trim()),i.fillLineDashOffset&&a.setAttribute("stroke-dashoffset",`${i.fillLineDashOffset}`),a}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}opsToPath(t,r){return this.gen.opsToPath(t,r)}line(t,r,i,n,a){const o=this.gen.line(t,r,i,n,a);return this.draw(o)}rectangle(t,r,i,n,a){const o=this.gen.rectangle(t,r,i,n,a);return this.draw(o)}ellipse(t,r,i,n,a){const o=this.gen.ellipse(t,r,i,n,a);return this.draw(o)}circle(t,r,i,n){const a=this.gen.circle(t,r,i,n);return this.draw(a)}linearPath(t,r){const i=this.gen.linearPath(t,r);return this.draw(i)}polygon(t,r){const i=this.gen.polygon(t,r);return this.draw(i)}arc(t,r,i,n,a,o,s=!1,l){const c=this.gen.arc(t,r,i,n,a,o,s,l);return this.draw(c)}curve(t,r){const i=this.gen.curve(t,r);return this.draw(i)}path(t,r){const i=this.gen.path(t,r);return this.draw(i)}};var at={canvas:(e,t)=>new f4(e,t),svg:(e,t)=>new p4(e,t),generator:e=>new so(e),newSeed:()=>so.newSeed()},_t=m(async(e,t,r)=>{var u,d;let i;const n=t.useHtmlLabels||ie((u=qt())==null?void 0:u.htmlLabels);r?i=r:i="node default";const a=e.insert("g").attr("class",i).attr("id",t.domId||t.id),o=a.insert("g").attr("class","label").attr("style",xe(t.labelStyle));let s;t.label===void 0?s="":s=typeof t.label=="string"?t.label:t.label[0];const l=await Xr(o,qe(Ai(s),qt()),{useHtmlLabels:n,width:t.width||((d=qt().flowchart)==null?void 0:d.wrappingWidth),cssClasses:"markdown-node-label",style:t.labelStyle,addSvgBackground:!!t.icon||!!t.img});let c=l.getBBox();const h=((t==null?void 0:t.padding)??0)/2;if(n){const f=l.children[0],p=Ot(l),g=f.getElementsByTagName("img");if(g){const y=s.replace(/]*>/g,"").trim()==="";await Promise.all([...g].map(b=>new Promise(x=>{function _(){if(b.style.display="flex",b.style.flexDirection="column",y){const w=qt().fontSize?qt().fontSize:window.getComputedStyle(document.body).fontSize,C=5,[v=ep.fontSize]=Ro(w),k=v*C+"px";b.style.minWidth=k,b.style.maxWidth=k}else b.style.width="100%";x(b)}m(_,"setupImage"),setTimeout(()=>{b.complete&&_()}),b.addEventListener("error",_),b.addEventListener("load",_)})))}c=f.getBoundingClientRect(),p.attr("width",c.width),p.attr("height",c.height)}return n?o.attr("transform","translate("+-c.width/2+", "+-c.height/2+")"):o.attr("transform","translate(0, "+-c.height/2+")"),t.centerLabel&&o.attr("transform","translate("+-c.width/2+", "+-c.height/2+")"),o.insert("rect",":first-child"),{shapeSvg:a,bbox:c,halfPadding:h,label:o}},"labelHelper"),Bl=m(async(e,t,r)=>{var l,c,h,u,d,f;const i=r.useHtmlLabels||ie((c=(l=qt())==null?void 0:l.flowchart)==null?void 0:c.htmlLabels),n=e.insert("g").attr("class","label").attr("style",r.labelStyle||""),a=await Xr(n,qe(Ai(t),qt()),{useHtmlLabels:i,width:r.width||((u=(h=qt())==null?void 0:h.flowchart)==null?void 0:u.wrappingWidth),style:r.labelStyle,addSvgBackground:!!r.icon||!!r.img});let o=a.getBBox();const s=r.padding/2;if(ie((f=(d=qt())==null?void 0:d.flowchart)==null?void 0:f.htmlLabels)){const p=a.children[0],g=Ot(a);o=p.getBoundingClientRect(),g.attr("width",o.width),g.attr("height",o.height)}return i?n.attr("transform","translate("+-o.width/2+", "+-o.height/2+")"):n.attr("transform","translate(0, "+-o.height/2+")"),r.centerLabel&&n.attr("transform","translate("+-o.width/2+", "+-o.height/2+")"),n.insert("rect",":first-child"),{shapeSvg:e,bbox:o,halfPadding:s,label:n}},"insertLabel"),lt=m((e,t)=>{const r=t.node().getBBox();e.width=r.width,e.height=r.height},"updateNodeBounds"),bt=m((e,t)=>(e.look==="handDrawn"?"rough-node":"node")+" "+e.cssClasses+" "+(t||""),"getNodeClasses");function Ft(e){const t=e.map((r,i)=>`${i===0?"M":"L"}${r.x},${r.y}`);return t.push("Z"),t.join(" ")}m(Ft,"createPathFromPoints");function Yr(e,t,r,i,n,a){const o=[],l=r-e,c=i-t,h=l/a,u=2*Math.PI/h,d=t+c/2;for(let f=0;f<=50;f++){const p=f/50,g=e+p*l,y=d+n*Math.sin(u*(g-e));o.push({x:g,y})}return o}m(Yr,"generateFullSineWavePoints");function pa(e,t,r,i,n,a){const o=[],s=n*Math.PI/180,h=(a*Math.PI/180-s)/(i-1);for(let u=0;u{var r=e.x,i=e.y,n=t.x-r,a=t.y-i,o=e.width/2,s=e.height/2,l,c;return Math.abs(a)*o>Math.abs(n)*s?(a<0&&(s=-s),l=a===0?0:s*n/a,c=s):(n<0&&(o=-o),l=o,c=n===0?0:o*a/n),{x:r+l,y:i+c}},"intersectRect"),_n=g4;function dy(e,t){t&&e.attr("style",t)}m(dy,"applyStyle");async function fy(e){const t=Ot(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),r=t.append("xhtml:div"),i=qt();let n=e.label;e.label&&an(e.label)&&(n=await ih(e.label.replace(yn.lineBreakRegex,` +`),i));const o='"+n+"";return r.html(qe(o,i)),dy(r,e.labelStyle),r.style("display","inline-block"),r.style("padding-right","1px"),r.style("white-space","nowrap"),r.attr("xmlns","http://www.w3.org/1999/xhtml"),t.node()}m(fy,"addHtmlLabel");var m4=m(async(e,t,r,i)=>{let n=e||"";if(typeof n=="object"&&(n=n[0]),ie(qt().flowchart.htmlLabels)){n=n.replace(/\\n|\n/g,"
    "),V.info("vertexText"+n);const a={isNode:i,label:Ai(n).replace(/fa[blrs]?:fa-[\w-]+/g,s=>``),labelStyle:t&&t.replace("fill:","color:")};return await fy(a)}else{const a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",t.replace("color:","fill:"));let o=[];typeof n=="string"?o=n.split(/\\n|\n|/gi):Array.isArray(n)?o=n:o=[];for(const s of o){const l=document.createElementNS("http://www.w3.org/2000/svg","tspan");l.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),l.setAttribute("dy","1em"),l.setAttribute("x","0"),r?l.setAttribute("class","title-row"):l.setAttribute("class","row"),l.textContent=s.trim(),a.appendChild(l)}return a}},"createLabel"),hi=m4,Zr=m((e,t,r,i,n)=>["M",e+n,t,"H",e+r-n,"A",n,n,0,0,1,e+r,t+n,"V",t+i-n,"A",n,n,0,0,1,e+r-n,t+i,"H",e+n,"A",n,n,0,0,1,e,t+i-n,"V",t+n,"A",n,n,0,0,1,e+n,t,"Z"].join(" "),"createRoundedRectPathD"),py=m(async(e,t)=>{V.info("Creating subgraph rect for ",t.id,t);const r=qt(),{themeVariables:i,handDrawnSeed:n}=r,{clusterBkg:a,clusterBorder:o}=i,{labelStyles:s,nodeStyles:l,borderStyles:c,backgroundStyles:h}=ot(t),u=e.insert("g").attr("class","cluster "+t.cssClasses).attr("id",t.id).attr("data-look",t.look),d=ie(r.flowchart.htmlLabels),f=u.insert("g").attr("class","cluster-label "),p=await Xr(f,t.label,{style:t.labelStyle,useHtmlLabels:d,isNode:!0});let g=p.getBBox();if(ie(r.flowchart.htmlLabels)){const k=p.children[0],$=Ot(p);g=k.getBoundingClientRect(),$.attr("width",g.width),$.attr("height",g.height)}const y=t.width<=g.width+t.padding?g.width+t.padding:t.width;t.width<=g.width+t.padding?t.diff=(y-t.width)/2-t.padding:t.diff=-t.padding;const b=t.height,x=t.x-y/2,_=t.y-b/2;V.trace("Data ",t,JSON.stringify(t));let w;if(t.look==="handDrawn"){const k=at.svg(u),$=st(t,{roughness:.7,fill:a,stroke:o,fillWeight:3,seed:n}),z=k.path(Zr(x,_,y,b,0),$);w=u.insert(()=>(V.debug("Rough node insert CXC",z),z),":first-child"),w.select("path:nth-child(2)").attr("style",c.join(";")),w.select("path").attr("style",h.join(";").replace("fill","stroke"))}else w=u.insert("rect",":first-child"),w.attr("style",l).attr("rx",t.rx).attr("ry",t.ry).attr("x",x).attr("y",_).attr("width",y).attr("height",b);const{subGraphTitleTopMargin:C}=Fh(r);if(f.attr("transform",`translate(${t.x-g.width/2}, ${t.y-t.height/2+C})`),s){const k=f.select("span");k&&k.attr("style",s)}const v=w.node().getBBox();return t.offsetX=0,t.width=v.width,t.height=v.height,t.offsetY=g.height-t.padding/2,t.intersect=function(k){return _n(t,k)},{cluster:u,labelBBox:g}},"rect"),y4=m((e,t)=>{const r=e.insert("g").attr("class","note-cluster").attr("id",t.id),i=r.insert("rect",":first-child"),n=0*t.padding,a=n/2;i.attr("rx",t.rx).attr("ry",t.ry).attr("x",t.x-t.width/2-a).attr("y",t.y-t.height/2-a).attr("width",t.width+n).attr("height",t.height+n).attr("fill","none");const o=i.node().getBBox();return t.width=o.width,t.height=o.height,t.intersect=function(s){return _n(t,s)},{cluster:r,labelBBox:{width:0,height:0}}},"noteGroup"),b4=m(async(e,t)=>{const r=qt(),{themeVariables:i,handDrawnSeed:n}=r,{altBackground:a,compositeBackground:o,compositeTitleBackground:s,nodeBorder:l}=i,c=e.insert("g").attr("class",t.cssClasses).attr("id",t.id).attr("data-id",t.id).attr("data-look",t.look),h=c.insert("g",":first-child"),u=c.insert("g").attr("class","cluster-label");let d=c.append("rect");const f=u.node().appendChild(await hi(t.label,t.labelStyle,void 0,!0));let p=f.getBBox();if(ie(r.flowchart.htmlLabels)){const z=f.children[0],W=Ot(f);p=z.getBoundingClientRect(),W.attr("width",p.width),W.attr("height",p.height)}const g=0*t.padding,y=g/2,b=(t.width<=p.width+t.padding?p.width+t.padding:t.width)+g;t.width<=p.width+t.padding?t.diff=(b-t.width)/2-t.padding:t.diff=-t.padding;const x=t.height+g,_=t.height+g-p.height-6,w=t.x-b/2,C=t.y-x/2;t.width=b;const v=t.y-t.height/2-y+p.height+2;let k;if(t.look==="handDrawn"){const z=t.cssClasses.includes("statediagram-cluster-alt"),W=at.svg(c),O=t.rx||t.ry?W.path(Zr(w,C,b,x,10),{roughness:.7,fill:s,fillStyle:"solid",stroke:l,seed:n}):W.rectangle(w,C,b,x,{seed:n});k=c.insert(()=>O,":first-child");const N=W.rectangle(w,v,b,_,{fill:z?a:o,fillStyle:z?"hachure":"solid",stroke:l,seed:n});k=c.insert(()=>O,":first-child"),d=c.insert(()=>N)}else k=h.insert("rect",":first-child"),k.attr("class","outer").attr("x",w).attr("y",C).attr("width",b).attr("height",x).attr("data-look",t.look),d.attr("class","inner").attr("x",w).attr("y",v).attr("width",b).attr("height",_);u.attr("transform",`translate(${t.x-p.width/2}, ${C+1-(ie(r.flowchart.htmlLabels)?0:3)})`);const $=k.node().getBBox();return t.height=$.height,t.offsetX=0,t.offsetY=p.height-t.padding/2,t.labelBBox=p,t.intersect=function(z){return _n(t,z)},{cluster:c,labelBBox:p}},"roundedWithTitle"),v4=m(async(e,t)=>{V.info("Creating subgraph rect for ",t.id,t);const r=qt(),{themeVariables:i,handDrawnSeed:n}=r,{clusterBkg:a,clusterBorder:o}=i,{labelStyles:s,nodeStyles:l,borderStyles:c,backgroundStyles:h}=ot(t),u=e.insert("g").attr("class","cluster "+t.cssClasses).attr("id",t.id).attr("data-look",t.look),d=ie(r.flowchart.htmlLabels),f=u.insert("g").attr("class","cluster-label "),p=await Xr(f,t.label,{style:t.labelStyle,useHtmlLabels:d,isNode:!0,width:t.width});let g=p.getBBox();if(ie(r.flowchart.htmlLabels)){const k=p.children[0],$=Ot(p);g=k.getBoundingClientRect(),$.attr("width",g.width),$.attr("height",g.height)}const y=t.width<=g.width+t.padding?g.width+t.padding:t.width;t.width<=g.width+t.padding?t.diff=(y-t.width)/2-t.padding:t.diff=-t.padding;const b=t.height,x=t.x-y/2,_=t.y-b/2;V.trace("Data ",t,JSON.stringify(t));let w;if(t.look==="handDrawn"){const k=at.svg(u),$=st(t,{roughness:.7,fill:a,stroke:o,fillWeight:4,seed:n}),z=k.path(Zr(x,_,y,b,t.rx),$);w=u.insert(()=>(V.debug("Rough node insert CXC",z),z),":first-child"),w.select("path:nth-child(2)").attr("style",c.join(";")),w.select("path").attr("style",h.join(";").replace("fill","stroke"))}else w=u.insert("rect",":first-child"),w.attr("style",l).attr("rx",t.rx).attr("ry",t.ry).attr("x",x).attr("y",_).attr("width",y).attr("height",b);const{subGraphTitleTopMargin:C}=Fh(r);if(f.attr("transform",`translate(${t.x-g.width/2}, ${t.y-t.height/2+C})`),s){const k=f.select("span");k&&k.attr("style",s)}const v=w.node().getBBox();return t.offsetX=0,t.width=v.width,t.height=v.height,t.offsetY=g.height-t.padding/2,t.intersect=function(k){return _n(t,k)},{cluster:u,labelBBox:g}},"kanbanSection"),x4=m((e,t)=>{const r=qt(),{themeVariables:i,handDrawnSeed:n}=r,{nodeBorder:a}=i,o=e.insert("g").attr("class",t.cssClasses).attr("id",t.id).attr("data-look",t.look),s=o.insert("g",":first-child"),l=0*t.padding,c=t.width+l;t.diff=-t.padding;const h=t.height+l,u=t.x-c/2,d=t.y-h/2;t.width=c;let f;if(t.look==="handDrawn"){const y=at.svg(o).rectangle(u,d,c,h,{fill:"lightgrey",roughness:.5,strokeLineDash:[5],stroke:a,seed:n});f=o.insert(()=>y,":first-child")}else f=s.insert("rect",":first-child"),f.attr("class","divider").attr("x",u).attr("y",d).attr("width",c).attr("height",h).attr("data-look",t.look);const p=f.node().getBBox();return t.height=p.height,t.offsetX=0,t.offsetY=0,t.intersect=function(g){return _n(t,g)},{cluster:o,labelBBox:{}}},"divider"),_4=py,k4={rect:py,squareRect:_4,roundedWithTitle:b4,noteGroup:y4,divider:x4,kanbanSection:v4},gy=new Map,w4=m(async(e,t)=>{const r=t.shape||"rect",i=await k4[r](e,t);return gy.set(t.id,i),i},"insertCluster"),JO=m(()=>{gy=new Map},"clear");function my(e,t){return e.intersect(t)}m(my,"intersectNode");var C4=my;function yy(e,t,r,i){var n=e.x,a=e.y,o=n-i.x,s=a-i.y,l=Math.sqrt(t*t*s*s+r*r*o*o),c=Math.abs(t*r*o/l);i.x0}m(Pc,"sameSign");var T4=xy;function _y(e,t,r){let i=e.x,n=e.y,a=[],o=Number.POSITIVE_INFINITY,s=Number.POSITIVE_INFINITY;typeof t.forEach=="function"?t.forEach(function(h){o=Math.min(o,h.x),s=Math.min(s,h.y)}):(o=Math.min(o,t.x),s=Math.min(s,t.y));let l=i-e.width/2-o,c=n-e.height/2-s;for(let h=0;h1&&a.sort(function(h,u){let d=h.x-r.x,f=h.y-r.y,p=Math.sqrt(d*d+f*f),g=u.x-r.x,y=u.y-r.y,b=Math.sqrt(g*g+y*y);return ph,":first-child");return u.attr("class","anchor").attr("style",xe(s)),lt(t,u),t.intersect=function(d){return V.info("Circle intersect",t,o,d),et.circle(t,o,d)},a}m(ky,"anchor");function Dc(e,t,r,i,n,a,o){const l=(e+r)/2,c=(t+i)/2,h=Math.atan2(i-t,r-e),u=(r-e)/2,d=(i-t)/2,f=u/n,p=d/a,g=Math.sqrt(f**2+p**2);if(g>1)throw new Error("The given radii are too small to create an arc between the points.");const y=Math.sqrt(1-g**2),b=l+y*a*Math.sin(h)*(o?-1:1),x=c-y*n*Math.cos(h)*(o?-1:1),_=Math.atan2((t-x)/a,(e-b)/n);let C=Math.atan2((i-x)/a,(r-b)/n)-_;o&&C<0&&(C+=2*Math.PI),!o&&C>0&&(C-=2*Math.PI);const v=[];for(let k=0;k<20;k++){const $=k/19,z=_+$*C,W=b+n*Math.cos(z),O=x+a*Math.sin(z);v.push({x:W,y:O})}return v}m(Dc,"generateArcPoints");async function wy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.width+t.padding+20,s=a.height+t.padding,l=s/2,c=l/(2.5+s/50),{cssStyles:h}=t,u=[{x:o/2,y:-s/2},{x:-o/2,y:-s/2},...Dc(-o/2,-s/2,-o/2,s/2,c,l,!1),{x:o/2,y:s/2},...Dc(o/2,s/2,o/2,-s/2,c,l,!0)],d=at.svg(n),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=Ft(u),g=d.path(p,f),y=n.insert(()=>g,":first-child");return y.attr("class","basic label-container"),h&&t.look!=="handDrawn"&&y.selectAll("path").attr("style",h),i&&t.look!=="handDrawn"&&y.selectAll("path").attr("style",i),y.attr("transform",`translate(${c/2}, 0)`),lt(t,y),t.intersect=function(b){return et.polygon(t,u,b)},n}m(wy,"bowTieRect");function Kr(e,t,r,i){return e.insert("polygon",":first-child").attr("points",i.map(function(n){return n.x+","+n.y}).join(" ")).attr("class","label-container").attr("transform","translate("+-t/2+","+r/2+")")}m(Kr,"insertPolygonShape");async function Cy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.height+t.padding,s=12,l=a.width+t.padding+s,c=0,h=l,u=-o,d=0,f=[{x:c+s,y:u},{x:h,y:u},{x:h,y:d},{x:c,y:d},{x:c,y:u+s},{x:c+s,y:u}];let p;const{cssStyles:g}=t;if(t.look==="handDrawn"){const y=at.svg(n),b=st(t,{}),x=Ft(f),_=y.path(x,b);p=n.insert(()=>_,":first-child").attr("transform",`translate(${-l/2}, ${o/2})`),g&&p.attr("style",g)}else p=Kr(n,l,o,f);return i&&p.attr("style",i),lt(t,p),t.intersect=function(y){return et.polygon(t,f,y)},n}m(Cy,"card");function Sy(e,t){const{nodeStyles:r}=ot(t);t.label="";const i=e.insert("g").attr("class",bt(t)).attr("id",t.domId??t.id),{cssStyles:n}=t,a=Math.max(28,t.width??0),o=[{x:0,y:a/2},{x:a/2,y:0},{x:0,y:-a/2},{x:-a/2,y:0}],s=at.svg(i),l=st(t,{});t.look!=="handDrawn"&&(l.roughness=0,l.fillStyle="solid");const c=Ft(o),h=s.path(c,l),u=i.insert(()=>h,":first-child");return n&&t.look!=="handDrawn"&&u.selectAll("path").attr("style",n),r&&t.look!=="handDrawn"&&u.selectAll("path").attr("style",r),t.width=28,t.height=28,t.intersect=function(d){return et.polygon(t,o,d)},i}m(Sy,"choice");async function ru(e,t,r){const{labelStyles:i,nodeStyles:n}=ot(t);t.labelStyle=i;const{shapeSvg:a,bbox:o,halfPadding:s}=await _t(e,t,bt(t)),l=(r==null?void 0:r.padding)??s,c=o.width/2+l;let h;const{cssStyles:u}=t;if(t.look==="handDrawn"){const d=at.svg(a),f=st(t,{}),p=d.circle(0,0,c*2,f);h=a.insert(()=>p,":first-child"),h.attr("class","basic label-container").attr("style",xe(u))}else h=a.insert("circle",":first-child").attr("class","basic label-container").attr("style",n).attr("r",c).attr("cx",0).attr("cy",0);return lt(t,h),t.calcIntersect=function(d,f){const p=d.width/2;return et.circle(d,p,f)},t.intersect=function(d){return V.info("Circle intersect",t,c,d),et.circle(t,c,d)},a}m(ru,"circle");function Ty(e){const t=Math.cos(Math.PI/4),r=Math.sin(Math.PI/4),i=e*2,n={x:i/2*t,y:i/2*r},a={x:-(i/2)*t,y:i/2*r},o={x:-(i/2)*t,y:-(i/2)*r},s={x:i/2*t,y:-(i/2)*r};return`M ${a.x},${a.y} L ${s.x},${s.y} + M ${n.x},${n.y} L ${o.x},${o.y}`}m(Ty,"createLine");function My(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r,t.label="";const n=e.insert("g").attr("class",bt(t)).attr("id",t.domId??t.id),a=Math.max(30,(t==null?void 0:t.width)??0),{cssStyles:o}=t,s=at.svg(n),l=st(t,{});t.look!=="handDrawn"&&(l.roughness=0,l.fillStyle="solid");const c=s.circle(0,0,a*2,l),h=Ty(a),u=s.path(h,l),d=n.insert(()=>c,":first-child");return d.insert(()=>u),o&&t.look!=="handDrawn"&&d.selectAll("path").attr("style",o),i&&t.look!=="handDrawn"&&d.selectAll("path").attr("style",i),lt(t,d),t.intersect=function(f){return V.info("crossedCircle intersect",t,{radius:a,point:f}),et.circle(t,a,f)},n}m(My,"crossedCircle");function wr(e,t,r,i=100,n=0,a=180){const o=[],s=n*Math.PI/180,h=(a*Math.PI/180-s)/(i-1);for(let u=0;u_,":first-child").attr("stroke-opacity",0),w.insert(()=>b,":first-child"),w.attr("class","text"),h&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",h),i&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",i),w.attr("transform",`translate(${c}, 0)`),o.attr("transform",`translate(${-s/2+c-(a.x-(a.left??0))},${-l/2+(t.padding??0)/2-(a.y-(a.top??0))})`),lt(t,w),t.intersect=function(C){return et.polygon(t,d,C)},n}m($y,"curlyBraceLeft");function Cr(e,t,r,i=100,n=0,a=180){const o=[],s=n*Math.PI/180,h=(a*Math.PI/180-s)/(i-1);for(let u=0;u_,":first-child").attr("stroke-opacity",0),w.insert(()=>b,":first-child"),w.attr("class","text"),h&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",h),i&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",i),w.attr("transform",`translate(${-c}, 0)`),o.attr("transform",`translate(${-s/2+(t.padding??0)/2-(a.x-(a.left??0))},${-l/2+(t.padding??0)/2-(a.y-(a.top??0))})`),lt(t,w),t.intersect=function(C){return et.polygon(t,d,C)},n}m(Ly,"curlyBraceRight");function ce(e,t,r,i=100,n=0,a=180){const o=[],s=n*Math.PI/180,h=(a*Math.PI/180-s)/(i-1);for(let u=0;uk,":first-child").attr("stroke-opacity",0),$.insert(()=>x,":first-child"),$.insert(()=>C,":first-child"),$.attr("class","text"),h&&t.look!=="handDrawn"&&$.selectAll("path").attr("style",h),i&&t.look!=="handDrawn"&&$.selectAll("path").attr("style",i),$.attr("transform",`translate(${c-c/4}, 0)`),o.attr("transform",`translate(${-s/2+(t.padding??0)/2-(a.x-(a.left??0))},${-l/2+(t.padding??0)/2-(a.y-(a.top??0))})`),lt(t,$),t.intersect=function(z){return et.polygon(t,f,z)},n}m(Ay,"curlyBraces");async function By(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=80,s=20,l=Math.max(o,(a.width+(t.padding??0)*2)*1.25,(t==null?void 0:t.width)??0),c=Math.max(s,a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),h=c/2,{cssStyles:u}=t,d=at.svg(n),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=l,g=c,y=p-h,b=g/4,x=[{x:y,y:0},{x:b,y:0},{x:0,y:g/2},{x:b,y:g},{x:y,y:g},...pa(-y,-g/2,h,50,270,90)],_=Ft(x),w=d.path(_,f),C=n.insert(()=>w,":first-child");return C.attr("class","basic label-container"),u&&t.look!=="handDrawn"&&C.selectChildren("path").attr("style",u),i&&t.look!=="handDrawn"&&C.selectChildren("path").attr("style",i),C.attr("transform",`translate(${-l/2}, ${-c/2})`),lt(t,C),t.intersect=function(v){return et.polygon(t,x,v)},n}m(By,"curvedTrapezoid");var $4=m((e,t,r,i,n,a)=>[`M${e},${t+a}`,`a${n},${a} 0,0,0 ${r},0`,`a${n},${a} 0,0,0 ${-r},0`,`l0,${i}`,`a${n},${a} 0,0,0 ${r},0`,`l0,${-i}`].join(" "),"createCylinderPathD"),L4=m((e,t,r,i,n,a)=>[`M${e},${t+a}`,`M${e+r},${t+a}`,`a${n},${a} 0,0,0 ${-r},0`,`l0,${i}`,`a${n},${a} 0,0,0 ${r},0`,`l0,${-i}`].join(" "),"createOuterCylinderPathD"),A4=m((e,t,r,i,n,a)=>[`M${e-r/2},${-i/2}`,`a${n},${a} 0,0,0 ${r},0`].join(" "),"createInnerCylinderPathD");async function Ey(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+t.padding,t.width??0),l=s/2,c=l/(2.5+s/50),h=Math.max(a.height+c+t.padding,t.height??0);let u;const{cssStyles:d}=t;if(t.look==="handDrawn"){const f=at.svg(n),p=L4(0,0,s,h,l,c),g=A4(0,c,s,h,l,c),y=f.path(p,st(t,{})),b=f.path(g,st(t,{fill:"none"}));u=n.insert(()=>b,":first-child"),u=n.insert(()=>y,":first-child"),u.attr("class","basic label-container"),d&&u.attr("style",d)}else{const f=$4(0,0,s,h,l,c);u=n.insert("path",":first-child").attr("d",f).attr("class","basic label-container").attr("style",xe(d)).attr("style",i)}return u.attr("label-offset-y",c),u.attr("transform",`translate(${-s/2}, ${-(h/2+c)})`),lt(t,u),o.attr("transform",`translate(${-(a.width/2)-(a.x-(a.left??0))}, ${-(a.height/2)+(t.padding??0)/1.5-(a.y-(a.top??0))})`),t.intersect=function(f){const p=et.rect(t,f),g=p.x-(t.x??0);if(l!=0&&(Math.abs(g)<(t.width??0)/2||Math.abs(g)==(t.width??0)/2&&Math.abs(p.y-(t.y??0))>(t.height??0)/2-c)){let y=c*c*(1-g*g/(l*l));y>0&&(y=Math.sqrt(y)),y=c-y,f.y-(t.y??0)>0&&(y=-y),p.y+=y}return p},n}m(Ey,"cylinder");async function Fy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=a.width+t.padding,l=a.height+t.padding,c=l*.2,h=-s/2,u=-l/2-c/2,{cssStyles:d}=t,f=at.svg(n),p=st(t,{});t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const g=[{x:h,y:u+c},{x:-h,y:u+c},{x:-h,y:-u},{x:h,y:-u},{x:h,y:u},{x:-h,y:u},{x:-h,y:u+c}],y=f.polygon(g.map(x=>[x.x,x.y]),p),b=n.insert(()=>y,":first-child");return b.attr("class","basic label-container"),d&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",d),i&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",i),o.attr("transform",`translate(${h+(t.padding??0)/2-(a.x-(a.left??0))}, ${u+c+(t.padding??0)/2-(a.y-(a.top??0))})`),lt(t,b),t.intersect=function(x){return et.rect(t,x)},n}m(Fy,"dividedRectangle");async function Py(e,t){var d,f;const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,halfPadding:o}=await _t(e,t,bt(t)),l=a.width/2+o+5,c=a.width/2+o;let h;const{cssStyles:u}=t;if(t.look==="handDrawn"){const p=at.svg(n),g=st(t,{roughness:.2,strokeWidth:2.5}),y=st(t,{roughness:.2,strokeWidth:1.5}),b=p.circle(0,0,l*2,g),x=p.circle(0,0,c*2,y);h=n.insert("g",":first-child"),h.attr("class",xe(t.cssClasses)).attr("style",xe(u)),(d=h.node())==null||d.appendChild(b),(f=h.node())==null||f.appendChild(x)}else{h=n.insert("g",":first-child");const p=h.insert("circle",":first-child"),g=h.insert("circle");h.attr("class","basic label-container").attr("style",i),p.attr("class","outer-circle").attr("style",i).attr("r",l).attr("cx",0).attr("cy",0),g.attr("class","inner-circle").attr("style",i).attr("r",c).attr("cx",0).attr("cy",0)}return lt(t,h),t.intersect=function(p){return V.info("DoubleCircle intersect",t,l,p),et.circle(t,l,p)},n}m(Py,"doublecircle");function Dy(e,t,{config:{themeVariables:r}}){const{labelStyles:i,nodeStyles:n}=ot(t);t.label="",t.labelStyle=i;const a=e.insert("g").attr("class",bt(t)).attr("id",t.domId??t.id),o=7,{cssStyles:s}=t,l=at.svg(a),{nodeBorder:c}=r,h=st(t,{fillStyle:"solid"});t.look!=="handDrawn"&&(h.roughness=0);const u=l.circle(0,0,o*2,h),d=a.insert(()=>u,":first-child");return d.selectAll("path").attr("style",`fill: ${c} !important;`),s&&s.length>0&&t.look!=="handDrawn"&&d.selectAll("path").attr("style",s),n&&t.look!=="handDrawn"&&d.selectAll("path").attr("style",n),lt(t,d),t.intersect=function(f){return V.info("filledCircle intersect",t,{radius:o,point:f}),et.circle(t,o,f)},a}m(Dy,"filledCircle");async function Iy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=a.width+(t.padding??0),l=s+a.height,c=s+a.height,h=[{x:0,y:-l},{x:c,y:-l},{x:c/2,y:0}],{cssStyles:u}=t,d=at.svg(n),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=Ft(h),g=d.path(p,f),y=n.insert(()=>g,":first-child").attr("transform",`translate(${-l/2}, ${l/2})`);return u&&t.look!=="handDrawn"&&y.selectChildren("path").attr("style",u),i&&t.look!=="handDrawn"&&y.selectChildren("path").attr("style",i),t.width=s,t.height=l,lt(t,y),o.attr("transform",`translate(${-a.width/2-(a.x-(a.left??0))}, ${-l/2+(t.padding??0)/2+(a.y-(a.top??0))})`),t.intersect=function(b){return V.info("Triangle intersect",t,h,b),et.polygon(t,h,b)},n}m(Iy,"flippedTriangle");function Oy(e,t,{dir:r,config:{state:i,themeVariables:n}}){const{nodeStyles:a}=ot(t);t.label="";const o=e.insert("g").attr("class",bt(t)).attr("id",t.domId??t.id),{cssStyles:s}=t;let l=Math.max(70,(t==null?void 0:t.width)??0),c=Math.max(10,(t==null?void 0:t.height)??0);r==="LR"&&(l=Math.max(10,(t==null?void 0:t.width)??0),c=Math.max(70,(t==null?void 0:t.height)??0));const h=-1*l/2,u=-1*c/2,d=at.svg(o),f=st(t,{stroke:n.lineColor,fill:n.lineColor});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=d.rectangle(h,u,l,c,f),g=o.insert(()=>p,":first-child");s&&t.look!=="handDrawn"&&g.selectAll("path").attr("style",s),a&&t.look!=="handDrawn"&&g.selectAll("path").attr("style",a),lt(t,g);const y=(i==null?void 0:i.padding)??0;return t.width&&t.height&&(t.width+=y/2||0,t.height+=y/2||0),t.intersect=function(b){return et.rect(t,b)},o}m(Oy,"forkJoin");async function Ry(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const n=80,a=50,{shapeSvg:o,bbox:s}=await _t(e,t,bt(t)),l=Math.max(n,s.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),c=Math.max(a,s.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),h=c/2,{cssStyles:u}=t,d=at.svg(o),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=[{x:-l/2,y:-c/2},{x:l/2-h,y:-c/2},...pa(-l/2+h,0,h,50,90,270),{x:l/2-h,y:c/2},{x:-l/2,y:c/2}],g=Ft(p),y=d.path(g,f),b=o.insert(()=>y,":first-child");return b.attr("class","basic label-container"),u&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",u),i&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",i),lt(t,b),t.intersect=function(x){return V.info("Pill intersect",t,{radius:h,point:x}),et.polygon(t,p,x)},o}m(Ry,"halfRoundedRectangle");async function Ny(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.height+(t.padding??0),s=a.width+(t.padding??0)*2.5,{cssStyles:l}=t,c=at.svg(n),h=st(t,{});t.look!=="handDrawn"&&(h.roughness=0,h.fillStyle="solid");let u=s/2;const d=u/6;u=u+d;const f=o/2,p=f/2,g=u-p,y=[{x:-g,y:-f},{x:0,y:-f},{x:g,y:-f},{x:u,y:0},{x:g,y:f},{x:0,y:f},{x:-g,y:f},{x:-u,y:0}],b=Ft(y),x=c.path(b,h),_=n.insert(()=>x,":first-child");return _.attr("class","basic label-container"),l&&t.look!=="handDrawn"&&_.selectChildren("path").attr("style",l),i&&t.look!=="handDrawn"&&_.selectChildren("path").attr("style",i),t.width=s,t.height=o,lt(t,_),t.intersect=function(w){return et.polygon(t,y,w)},n}m(Ny,"hexagon");async function zy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.label="",t.labelStyle=r;const{shapeSvg:n}=await _t(e,t,bt(t)),a=Math.max(30,(t==null?void 0:t.width)??0),o=Math.max(30,(t==null?void 0:t.height)??0),{cssStyles:s}=t,l=at.svg(n),c=st(t,{});t.look!=="handDrawn"&&(c.roughness=0,c.fillStyle="solid");const h=[{x:0,y:0},{x:a,y:0},{x:0,y:o},{x:a,y:o}],u=Ft(h),d=l.path(u,c),f=n.insert(()=>d,":first-child");return f.attr("class","basic label-container"),s&&t.look!=="handDrawn"&&f.selectChildren("path").attr("style",s),i&&t.look!=="handDrawn"&&f.selectChildren("path").attr("style",i),f.attr("transform",`translate(${-a/2}, ${-o/2})`),lt(t,f),t.intersect=function(p){return V.info("Pill intersect",t,{points:h}),et.polygon(t,h,p)},n}m(zy,"hourglass");async function qy(e,t,{config:{themeVariables:r,flowchart:i}}){const{labelStyles:n}=ot(t);t.labelStyle=n;const a=t.assetHeight??48,o=t.assetWidth??48,s=Math.max(a,o),l=i==null?void 0:i.wrappingWidth;t.width=Math.max(s,l??0);const{shapeSvg:c,bbox:h,label:u}=await _t(e,t,"icon-shape default"),d=t.pos==="t",f=s,p=s,{nodeBorder:g}=r,{stylesMap:y}=bn(t),b=-p/2,x=-f/2,_=t.label?8:0,w=at.svg(c),C=st(t,{stroke:"none",fill:"none"});t.look!=="handDrawn"&&(C.roughness=0,C.fillStyle="solid");const v=w.rectangle(b,x,p,f,C),k=Math.max(p,h.width),$=f+h.height+_,z=w.rectangle(-k/2,-$/2,k,$,{...C,fill:"transparent",stroke:"none"}),W=c.insert(()=>v,":first-child"),O=c.insert(()=>z);if(t.icon){const N=c.append("g");N.html(`${await $a(t.icon,{height:s,width:s,fallbackPrefix:""})}`);const D=N.node().getBBox(),L=D.width,B=D.height,F=D.x,R=D.y;N.attr("transform",`translate(${-L/2-F},${d?h.height/2+_/2-B/2-R:-h.height/2-_/2-B/2-R})`),N.attr("style",`color: ${y.get("stroke")??g};`)}return u.attr("transform",`translate(${-h.width/2-(h.x-(h.left??0))},${d?-$/2:$/2-h.height})`),W.attr("transform",`translate(0,${d?h.height/2+_/2:-h.height/2-_/2})`),lt(t,O),t.intersect=function(N){if(V.info("iconSquare intersect",t,N),!t.label)return et.rect(t,N);const D=t.x??0,L=t.y??0,B=t.height??0;let F=[];return d?F=[{x:D-h.width/2,y:L-B/2},{x:D+h.width/2,y:L-B/2},{x:D+h.width/2,y:L-B/2+h.height+_},{x:D+p/2,y:L-B/2+h.height+_},{x:D+p/2,y:L+B/2},{x:D-p/2,y:L+B/2},{x:D-p/2,y:L-B/2+h.height+_},{x:D-h.width/2,y:L-B/2+h.height+_}]:F=[{x:D-p/2,y:L-B/2},{x:D+p/2,y:L-B/2},{x:D+p/2,y:L-B/2+f},{x:D+h.width/2,y:L-B/2+f},{x:D+h.width/2/2,y:L+B/2},{x:D-h.width/2,y:L+B/2},{x:D-h.width/2,y:L-B/2+f},{x:D-p/2,y:L-B/2+f}],et.polygon(t,F,N)},c}m(qy,"icon");async function Wy(e,t,{config:{themeVariables:r,flowchart:i}}){const{labelStyles:n}=ot(t);t.labelStyle=n;const a=t.assetHeight??48,o=t.assetWidth??48,s=Math.max(a,o),l=i==null?void 0:i.wrappingWidth;t.width=Math.max(s,l??0);const{shapeSvg:c,bbox:h,label:u}=await _t(e,t,"icon-shape default"),d=20,f=t.label?8:0,p=t.pos==="t",{nodeBorder:g,mainBkg:y}=r,{stylesMap:b}=bn(t),x=at.svg(c),_=st(t,{});t.look!=="handDrawn"&&(_.roughness=0,_.fillStyle="solid");const w=b.get("fill");_.stroke=w??y;const C=c.append("g");t.icon&&C.html(`${await $a(t.icon,{height:s,width:s,fallbackPrefix:""})}`);const v=C.node().getBBox(),k=v.width,$=v.height,z=v.x,W=v.y,O=Math.max(k,$)*Math.SQRT2+d*2,N=x.circle(0,0,O,_),D=Math.max(O,h.width),L=O+h.height+f,B=x.rectangle(-D/2,-L/2,D,L,{..._,fill:"transparent",stroke:"none"}),F=c.insert(()=>N,":first-child"),R=c.insert(()=>B);return C.attr("transform",`translate(${-k/2-z},${p?h.height/2+f/2-$/2-W:-h.height/2-f/2-$/2-W})`),C.attr("style",`color: ${b.get("stroke")??g};`),u.attr("transform",`translate(${-h.width/2-(h.x-(h.left??0))},${p?-L/2:L/2-h.height})`),F.attr("transform",`translate(0,${p?h.height/2+f/2:-h.height/2-f/2})`),lt(t,R),t.intersect=function(I){return V.info("iconSquare intersect",t,I),et.rect(t,I)},c}m(Wy,"iconCircle");async function Hy(e,t,{config:{themeVariables:r,flowchart:i}}){const{labelStyles:n}=ot(t);t.labelStyle=n;const a=t.assetHeight??48,o=t.assetWidth??48,s=Math.max(a,o),l=i==null?void 0:i.wrappingWidth;t.width=Math.max(s,l??0);const{shapeSvg:c,bbox:h,halfPadding:u,label:d}=await _t(e,t,"icon-shape default"),f=t.pos==="t",p=s+u*2,g=s+u*2,{nodeBorder:y,mainBkg:b}=r,{stylesMap:x}=bn(t),_=-g/2,w=-p/2,C=t.label?8:0,v=at.svg(c),k=st(t,{});t.look!=="handDrawn"&&(k.roughness=0,k.fillStyle="solid");const $=x.get("fill");k.stroke=$??b;const z=v.path(Zr(_,w,g,p,5),k),W=Math.max(g,h.width),O=p+h.height+C,N=v.rectangle(-W/2,-O/2,W,O,{...k,fill:"transparent",stroke:"none"}),D=c.insert(()=>z,":first-child").attr("class","icon-shape2"),L=c.insert(()=>N);if(t.icon){const B=c.append("g");B.html(`${await $a(t.icon,{height:s,width:s,fallbackPrefix:""})}`);const F=B.node().getBBox(),R=F.width,I=F.height,X=F.x,Z=F.y;B.attr("transform",`translate(${-R/2-X},${f?h.height/2+C/2-I/2-Z:-h.height/2-C/2-I/2-Z})`),B.attr("style",`color: ${x.get("stroke")??y};`)}return d.attr("transform",`translate(${-h.width/2-(h.x-(h.left??0))},${f?-O/2:O/2-h.height})`),D.attr("transform",`translate(0,${f?h.height/2+C/2:-h.height/2-C/2})`),lt(t,L),t.intersect=function(B){if(V.info("iconSquare intersect",t,B),!t.label)return et.rect(t,B);const F=t.x??0,R=t.y??0,I=t.height??0;let X=[];return f?X=[{x:F-h.width/2,y:R-I/2},{x:F+h.width/2,y:R-I/2},{x:F+h.width/2,y:R-I/2+h.height+C},{x:F+g/2,y:R-I/2+h.height+C},{x:F+g/2,y:R+I/2},{x:F-g/2,y:R+I/2},{x:F-g/2,y:R-I/2+h.height+C},{x:F-h.width/2,y:R-I/2+h.height+C}]:X=[{x:F-g/2,y:R-I/2},{x:F+g/2,y:R-I/2},{x:F+g/2,y:R-I/2+p},{x:F+h.width/2,y:R-I/2+p},{x:F+h.width/2/2,y:R+I/2},{x:F-h.width/2,y:R+I/2},{x:F-h.width/2,y:R-I/2+p},{x:F-g/2,y:R-I/2+p}],et.polygon(t,X,B)},c}m(Hy,"iconRounded");async function Vy(e,t,{config:{themeVariables:r,flowchart:i}}){const{labelStyles:n}=ot(t);t.labelStyle=n;const a=t.assetHeight??48,o=t.assetWidth??48,s=Math.max(a,o),l=i==null?void 0:i.wrappingWidth;t.width=Math.max(s,l??0);const{shapeSvg:c,bbox:h,halfPadding:u,label:d}=await _t(e,t,"icon-shape default"),f=t.pos==="t",p=s+u*2,g=s+u*2,{nodeBorder:y,mainBkg:b}=r,{stylesMap:x}=bn(t),_=-g/2,w=-p/2,C=t.label?8:0,v=at.svg(c),k=st(t,{});t.look!=="handDrawn"&&(k.roughness=0,k.fillStyle="solid");const $=x.get("fill");k.stroke=$??b;const z=v.path(Zr(_,w,g,p,.1),k),W=Math.max(g,h.width),O=p+h.height+C,N=v.rectangle(-W/2,-O/2,W,O,{...k,fill:"transparent",stroke:"none"}),D=c.insert(()=>z,":first-child"),L=c.insert(()=>N);if(t.icon){const B=c.append("g");B.html(`${await $a(t.icon,{height:s,width:s,fallbackPrefix:""})}`);const F=B.node().getBBox(),R=F.width,I=F.height,X=F.x,Z=F.y;B.attr("transform",`translate(${-R/2-X},${f?h.height/2+C/2-I/2-Z:-h.height/2-C/2-I/2-Z})`),B.attr("style",`color: ${x.get("stroke")??y};`)}return d.attr("transform",`translate(${-h.width/2-(h.x-(h.left??0))},${f?-O/2:O/2-h.height})`),D.attr("transform",`translate(0,${f?h.height/2+C/2:-h.height/2-C/2})`),lt(t,L),t.intersect=function(B){if(V.info("iconSquare intersect",t,B),!t.label)return et.rect(t,B);const F=t.x??0,R=t.y??0,I=t.height??0;let X=[];return f?X=[{x:F-h.width/2,y:R-I/2},{x:F+h.width/2,y:R-I/2},{x:F+h.width/2,y:R-I/2+h.height+C},{x:F+g/2,y:R-I/2+h.height+C},{x:F+g/2,y:R+I/2},{x:F-g/2,y:R+I/2},{x:F-g/2,y:R-I/2+h.height+C},{x:F-h.width/2,y:R-I/2+h.height+C}]:X=[{x:F-g/2,y:R-I/2},{x:F+g/2,y:R-I/2},{x:F+g/2,y:R-I/2+p},{x:F+h.width/2,y:R-I/2+p},{x:F+h.width/2/2,y:R+I/2},{x:F-h.width/2,y:R+I/2},{x:F-h.width/2,y:R-I/2+p},{x:F-g/2,y:R-I/2+p}],et.polygon(t,X,B)},c}m(Vy,"iconSquare");async function Uy(e,t,{config:{flowchart:r}}){const i=new Image;i.src=(t==null?void 0:t.img)??"",await i.decode();const n=Number(i.naturalWidth.toString().replace("px","")),a=Number(i.naturalHeight.toString().replace("px",""));t.imageAspectRatio=n/a;const{labelStyles:o}=ot(t);t.labelStyle=o;const s=r==null?void 0:r.wrappingWidth;t.defaultWidth=r==null?void 0:r.wrappingWidth;const l=Math.max(t.label?s??0:0,(t==null?void 0:t.assetWidth)??n),c=t.constraint==="on"&&t!=null&&t.assetHeight?t.assetHeight*t.imageAspectRatio:l,h=t.constraint==="on"?c/t.imageAspectRatio:(t==null?void 0:t.assetHeight)??a;t.width=Math.max(c,s??0);const{shapeSvg:u,bbox:d,label:f}=await _t(e,t,"image-shape default"),p=t.pos==="t",g=-c/2,y=-h/2,b=t.label?8:0,x=at.svg(u),_=st(t,{});t.look!=="handDrawn"&&(_.roughness=0,_.fillStyle="solid");const w=x.rectangle(g,y,c,h,_),C=Math.max(c,d.width),v=h+d.height+b,k=x.rectangle(-C/2,-v/2,C,v,{..._,fill:"none",stroke:"none"}),$=u.insert(()=>w,":first-child"),z=u.insert(()=>k);if(t.img){const W=u.append("image");W.attr("href",t.img),W.attr("width",c),W.attr("height",h),W.attr("preserveAspectRatio","none"),W.attr("transform",`translate(${-c/2},${p?v/2-h:-v/2})`)}return f.attr("transform",`translate(${-d.width/2-(d.x-(d.left??0))},${p?-h/2-d.height/2-b/2:h/2-d.height/2+b/2})`),$.attr("transform",`translate(0,${p?d.height/2+b/2:-d.height/2-b/2})`),lt(t,z),t.intersect=function(W){if(V.info("iconSquare intersect",t,W),!t.label)return et.rect(t,W);const O=t.x??0,N=t.y??0,D=t.height??0;let L=[];return p?L=[{x:O-d.width/2,y:N-D/2},{x:O+d.width/2,y:N-D/2},{x:O+d.width/2,y:N-D/2+d.height+b},{x:O+c/2,y:N-D/2+d.height+b},{x:O+c/2,y:N+D/2},{x:O-c/2,y:N+D/2},{x:O-c/2,y:N-D/2+d.height+b},{x:O-d.width/2,y:N-D/2+d.height+b}]:L=[{x:O-c/2,y:N-D/2},{x:O+c/2,y:N-D/2},{x:O+c/2,y:N-D/2+h},{x:O+d.width/2,y:N-D/2+h},{x:O+d.width/2/2,y:N+D/2},{x:O-d.width/2,y:N+D/2},{x:O-d.width/2,y:N-D/2+h},{x:O-c/2,y:N-D/2+h}],et.polygon(t,L,W)},u}m(Uy,"imageSquare");async function jy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),s=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),l=[{x:0,y:0},{x:o,y:0},{x:o+3*s/6,y:-s},{x:-3*s/6,y:-s}];let c;const{cssStyles:h}=t;if(t.look==="handDrawn"){const u=at.svg(n),d=st(t,{}),f=Ft(l),p=u.path(f,d);c=n.insert(()=>p,":first-child").attr("transform",`translate(${-o/2}, ${s/2})`),h&&c.attr("style",h)}else c=Kr(n,o,s,l);return i&&c.attr("style",i),t.width=o,t.height=s,lt(t,c),t.intersect=function(u){return et.polygon(t,l,u)},n}m(jy,"inv_trapezoid");async function Vo(e,t,r){const{labelStyles:i,nodeStyles:n}=ot(t);t.labelStyle=i;const{shapeSvg:a,bbox:o}=await _t(e,t,bt(t)),s=Math.max(o.width+r.labelPaddingX*2,(t==null?void 0:t.width)||0),l=Math.max(o.height+r.labelPaddingY*2,(t==null?void 0:t.height)||0),c=-s/2,h=-l/2;let u,{rx:d,ry:f}=t;const{cssStyles:p}=t;if(r!=null&&r.rx&&r.ry&&(d=r.rx,f=r.ry),t.look==="handDrawn"){const g=at.svg(a),y=st(t,{}),b=d||f?g.path(Zr(c,h,s,l,d||0),y):g.rectangle(c,h,s,l,y);u=a.insert(()=>b,":first-child"),u.attr("class","basic label-container").attr("style",xe(p))}else u=a.insert("rect",":first-child"),u.attr("class","basic label-container").attr("style",n).attr("rx",xe(d)).attr("ry",xe(f)).attr("x",c).attr("y",h).attr("width",s).attr("height",l);return lt(t,u),t.calcIntersect=function(g,y){return et.rect(g,y)},t.intersect=function(g){return et.rect(t,g)},a}m(Vo,"drawRect");async function Yy(e,t){const{shapeSvg:r,bbox:i,label:n}=await _t(e,t,"label"),a=r.insert("rect",":first-child");return a.attr("width",.1).attr("height",.1),r.attr("class","label edgeLabel"),n.attr("transform",`translate(${-(i.width/2)-(i.x-(i.left??0))}, ${-(i.height/2)-(i.y-(i.top??0))})`),lt(t,a),t.intersect=function(l){return et.rect(t,l)},r}m(Yy,"labelRect");async function Gy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=Math.max(a.width+(t.padding??0),(t==null?void 0:t.width)??0),s=Math.max(a.height+(t.padding??0),(t==null?void 0:t.height)??0),l=[{x:0,y:0},{x:o+3*s/6,y:0},{x:o,y:-s},{x:-(3*s)/6,y:-s}];let c;const{cssStyles:h}=t;if(t.look==="handDrawn"){const u=at.svg(n),d=st(t,{}),f=Ft(l),p=u.path(f,d);c=n.insert(()=>p,":first-child").attr("transform",`translate(${-o/2}, ${s/2})`),h&&c.attr("style",h)}else c=Kr(n,o,s,l);return i&&c.attr("style",i),t.width=o,t.height=s,lt(t,c),t.intersect=function(u){return et.polygon(t,l,u)},n}m(Gy,"lean_left");async function Xy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=Math.max(a.width+(t.padding??0),(t==null?void 0:t.width)??0),s=Math.max(a.height+(t.padding??0),(t==null?void 0:t.height)??0),l=[{x:-3*s/6,y:0},{x:o,y:0},{x:o+3*s/6,y:-s},{x:0,y:-s}];let c;const{cssStyles:h}=t;if(t.look==="handDrawn"){const u=at.svg(n),d=st(t,{}),f=Ft(l),p=u.path(f,d);c=n.insert(()=>p,":first-child").attr("transform",`translate(${-o/2}, ${s/2})`),h&&c.attr("style",h)}else c=Kr(n,o,s,l);return i&&c.attr("style",i),t.width=o,t.height=s,lt(t,c),t.intersect=function(u){return et.polygon(t,l,u)},n}m(Xy,"lean_right");function Zy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.label="",t.labelStyle=r;const n=e.insert("g").attr("class",bt(t)).attr("id",t.domId??t.id),{cssStyles:a}=t,o=Math.max(35,(t==null?void 0:t.width)??0),s=Math.max(35,(t==null?void 0:t.height)??0),l=7,c=[{x:o,y:0},{x:0,y:s+l/2},{x:o-2*l,y:s+l/2},{x:0,y:2*s},{x:o,y:s-l/2},{x:2*l,y:s-l/2}],h=at.svg(n),u=st(t,{});t.look!=="handDrawn"&&(u.roughness=0,u.fillStyle="solid");const d=Ft(c),f=h.path(d,u),p=n.insert(()=>f,":first-child");return a&&t.look!=="handDrawn"&&p.selectAll("path").attr("style",a),i&&t.look!=="handDrawn"&&p.selectAll("path").attr("style",i),p.attr("transform",`translate(-${o/2},${-s})`),lt(t,p),t.intersect=function(g){return V.info("lightningBolt intersect",t,g),et.polygon(t,c,g)},n}m(Zy,"lightningBolt");var B4=m((e,t,r,i,n,a,o)=>[`M${e},${t+a}`,`a${n},${a} 0,0,0 ${r},0`,`a${n},${a} 0,0,0 ${-r},0`,`l0,${i}`,`a${n},${a} 0,0,0 ${r},0`,`l0,${-i}`,`M${e},${t+a+o}`,`a${n},${a} 0,0,0 ${r},0`].join(" "),"createCylinderPathD"),E4=m((e,t,r,i,n,a,o)=>[`M${e},${t+a}`,`M${e+r},${t+a}`,`a${n},${a} 0,0,0 ${-r},0`,`l0,${i}`,`a${n},${a} 0,0,0 ${r},0`,`l0,${-i}`,`M${e},${t+a+o}`,`a${n},${a} 0,0,0 ${r},0`].join(" "),"createOuterCylinderPathD"),F4=m((e,t,r,i,n,a)=>[`M${e-r/2},${-i/2}`,`a${n},${a} 0,0,0 ${r},0`].join(" "),"createInnerCylinderPathD");async function Ky(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0),t.width??0),l=s/2,c=l/(2.5+s/50),h=Math.max(a.height+c+(t.padding??0),t.height??0),u=h*.1;let d;const{cssStyles:f}=t;if(t.look==="handDrawn"){const p=at.svg(n),g=E4(0,0,s,h,l,c,u),y=F4(0,c,s,h,l,c),b=st(t,{}),x=p.path(g,b),_=p.path(y,b);n.insert(()=>_,":first-child").attr("class","line"),d=n.insert(()=>x,":first-child"),d.attr("class","basic label-container"),f&&d.attr("style",f)}else{const p=B4(0,0,s,h,l,c,u);d=n.insert("path",":first-child").attr("d",p).attr("class","basic label-container").attr("style",xe(f)).attr("style",i)}return d.attr("label-offset-y",c),d.attr("transform",`translate(${-s/2}, ${-(h/2+c)})`),lt(t,d),o.attr("transform",`translate(${-(a.width/2)-(a.x-(a.left??0))}, ${-(a.height/2)+c-(a.y-(a.top??0))})`),t.intersect=function(p){const g=et.rect(t,p),y=g.x-(t.x??0);if(l!=0&&(Math.abs(y)<(t.width??0)/2||Math.abs(y)==(t.width??0)/2&&Math.abs(g.y-(t.y??0))>(t.height??0)/2-c)){let b=c*c*(1-y*y/(l*l));b>0&&(b=Math.sqrt(b)),b=c-b,p.y-(t.y??0)>0&&(b=-b),g.y+=b}return g},n}m(Ky,"linedCylinder");async function Qy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=l/4,h=l+c,{cssStyles:u}=t,d=at.svg(n),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=[{x:-s/2-s/2*.1,y:-h/2},{x:-s/2-s/2*.1,y:h/2},...Yr(-s/2-s/2*.1,h/2,s/2+s/2*.1,h/2,c,.8),{x:s/2+s/2*.1,y:-h/2},{x:-s/2-s/2*.1,y:-h/2},{x:-s/2,y:-h/2},{x:-s/2,y:h/2*1.1},{x:-s/2,y:-h/2}],g=d.polygon(p.map(b=>[b.x,b.y]),f),y=n.insert(()=>g,":first-child");return y.attr("class","basic label-container"),u&&t.look!=="handDrawn"&&y.selectAll("path").attr("style",u),i&&t.look!=="handDrawn"&&y.selectAll("path").attr("style",i),y.attr("transform",`translate(0,${-c/2})`),o.attr("transform",`translate(${-s/2+(t.padding??0)+s/2*.1/2-(a.x-(a.left??0))},${-l/2+(t.padding??0)-c/2-(a.y-(a.top??0))})`),lt(t,y),t.intersect=function(b){return et.polygon(t,p,b)},n}m(Qy,"linedWaveEdgedRect");async function Jy(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=5,h=-s/2,u=-l/2,{cssStyles:d}=t,f=at.svg(n),p=st(t,{}),g=[{x:h-c,y:u+c},{x:h-c,y:u+l+c},{x:h+s-c,y:u+l+c},{x:h+s-c,y:u+l},{x:h+s,y:u+l},{x:h+s,y:u+l-c},{x:h+s+c,y:u+l-c},{x:h+s+c,y:u-c},{x:h+c,y:u-c},{x:h+c,y:u},{x:h,y:u},{x:h,y:u+c}],y=[{x:h,y:u+c},{x:h+s-c,y:u+c},{x:h+s-c,y:u+l},{x:h+s,y:u+l},{x:h+s,y:u},{x:h,y:u}];t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const b=Ft(g),x=f.path(b,p),_=Ft(y),w=f.path(_,{...p,fill:"none"}),C=n.insert(()=>w,":first-child");return C.insert(()=>x,":first-child"),C.attr("class","basic label-container"),d&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",d),i&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",i),o.attr("transform",`translate(${-(a.width/2)-c-(a.x-(a.left??0))}, ${-(a.height/2)+c-(a.y-(a.top??0))})`),lt(t,C),t.intersect=function(v){return et.polygon(t,g,v)},n}m(Jy,"multiRect");async function t1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=l/4,h=l+c,u=-s/2,d=-h/2,f=5,{cssStyles:p}=t,g=Yr(u-f,d+h+f,u+s-f,d+h+f,c,.8),y=g==null?void 0:g[g.length-1],b=[{x:u-f,y:d+f},{x:u-f,y:d+h+f},...g,{x:u+s-f,y:y.y-f},{x:u+s,y:y.y-f},{x:u+s,y:y.y-2*f},{x:u+s+f,y:y.y-2*f},{x:u+s+f,y:d-f},{x:u+f,y:d-f},{x:u+f,y:d},{x:u,y:d},{x:u,y:d+f}],x=[{x:u,y:d+f},{x:u+s-f,y:d+f},{x:u+s-f,y:y.y-f},{x:u+s,y:y.y-f},{x:u+s,y:d},{x:u,y:d}],_=at.svg(n),w=st(t,{});t.look!=="handDrawn"&&(w.roughness=0,w.fillStyle="solid");const C=Ft(b),v=_.path(C,w),k=Ft(x),$=_.path(k,w),z=n.insert(()=>v,":first-child");return z.insert(()=>$),z.attr("class","basic label-container"),p&&t.look!=="handDrawn"&&z.selectAll("path").attr("style",p),i&&t.look!=="handDrawn"&&z.selectAll("path").attr("style",i),z.attr("transform",`translate(0,${-c/2})`),o.attr("transform",`translate(${-(a.width/2)-f-(a.x-(a.left??0))}, ${-(a.height/2)+f-c/2-(a.y-(a.top??0))})`),lt(t,z),t.intersect=function(W){return et.polygon(t,b,W)},n}m(t1,"multiWaveEdgedRectangle");async function e1(e,t,{config:{themeVariables:r}}){var x;const{labelStyles:i,nodeStyles:n}=ot(t);t.labelStyle=i,t.useHtmlLabels||((x=be().flowchart)==null?void 0:x.htmlLabels)!==!1||(t.centerLabel=!0);const{shapeSvg:o,bbox:s,label:l}=await _t(e,t,bt(t)),c=Math.max(s.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),h=Math.max(s.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),u=-c/2,d=-h/2,{cssStyles:f}=t,p=at.svg(o),g=st(t,{fill:r.noteBkgColor,stroke:r.noteBorderColor});t.look!=="handDrawn"&&(g.roughness=0,g.fillStyle="solid");const y=p.rectangle(u,d,c,h,g),b=o.insert(()=>y,":first-child");return b.attr("class","basic label-container"),f&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",f),n&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",n),l.attr("transform",`translate(${-s.width/2-(s.x-(s.left??0))}, ${-(s.height/2)-(s.y-(s.top??0))})`),lt(t,b),t.intersect=function(_){return et.rect(t,_)},o}m(e1,"note");var P4=m((e,t,r)=>[`M${e+r/2},${t}`,`L${e+r},${t-r/2}`,`L${e+r/2},${t-r}`,`L${e},${t-r/2}`,"Z"].join(" "),"createDecisionBoxPathD");async function r1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.width+t.padding,s=a.height+t.padding,l=o+s,c=.5,h=[{x:l/2,y:0},{x:l,y:-l/2},{x:l/2,y:-l},{x:0,y:-l/2}];let u;const{cssStyles:d}=t;if(t.look==="handDrawn"){const f=at.svg(n),p=st(t,{}),g=P4(0,0,l),y=f.path(g,p);u=n.insert(()=>y,":first-child").attr("transform",`translate(${-l/2+c}, ${l/2})`),d&&u.attr("style",d)}else u=Kr(n,l,l,h),u.attr("transform",`translate(${-l/2+c}, ${l/2})`);return i&&u.attr("style",i),lt(t,u),t.calcIntersect=function(f,p){const g=f.width,y=[{x:g/2,y:0},{x:g,y:-g/2},{x:g/2,y:-g},{x:0,y:-g/2}],b=et.polygon(f,y,p);return{x:b.x-.5,y:b.y-.5}},t.intersect=function(f){return this.calcIntersect(t,f)},n}m(r1,"question");async function i1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0),(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0),(t==null?void 0:t.height)??0),c=-s/2,h=-l/2,u=h/2,d=[{x:c+u,y:h},{x:c,y:0},{x:c+u,y:-h},{x:-c,y:-h},{x:-c,y:h}],{cssStyles:f}=t,p=at.svg(n),g=st(t,{});t.look!=="handDrawn"&&(g.roughness=0,g.fillStyle="solid");const y=Ft(d),b=p.path(y,g),x=n.insert(()=>b,":first-child");return x.attr("class","basic label-container"),f&&t.look!=="handDrawn"&&x.selectAll("path").attr("style",f),i&&t.look!=="handDrawn"&&x.selectAll("path").attr("style",i),x.attr("transform",`translate(${-u/2},0)`),o.attr("transform",`translate(${-u/2-a.width/2-(a.x-(a.left??0))}, ${-(a.height/2)-(a.y-(a.top??0))})`),lt(t,x),t.intersect=function(_){return et.polygon(t,d,_)},n}m(i1,"rect_left_inv_arrow");async function n1(e,t){var $,z;const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;let n;t.cssClasses?n="node "+t.cssClasses:n="node default";const a=e.insert("g").attr("class",n).attr("id",t.domId||t.id),o=a.insert("g"),s=a.insert("g").attr("class","label").attr("style",i),l=t.description,c=t.label,h=s.node().appendChild(await hi(c,t.labelStyle,!0,!0));let u={width:0,height:0};if(ie((z=($=qt())==null?void 0:$.flowchart)==null?void 0:z.htmlLabels)){const W=h.children[0],O=Ot(h);u=W.getBoundingClientRect(),O.attr("width",u.width),O.attr("height",u.height)}V.info("Text 2",l);const d=l||[],f=h.getBBox(),p=s.node().appendChild(await hi(d.join?d.join("
    "):d,t.labelStyle,!0,!0)),g=p.children[0],y=Ot(p);u=g.getBoundingClientRect(),y.attr("width",u.width),y.attr("height",u.height);const b=(t.padding||0)/2;Ot(p).attr("transform","translate( "+(u.width>f.width?0:(f.width-u.width)/2)+", "+(f.height+b+5)+")"),Ot(h).attr("transform","translate( "+(u.width(V.debug("Rough node insert CXC",N),D),":first-child"),v=a.insert(()=>(V.debug("Rough node insert CXC",N),N),":first-child")}else v=o.insert("rect",":first-child"),k=o.insert("line"),v.attr("class","outer title-state").attr("style",i).attr("x",-u.width/2-b).attr("y",-u.height/2-b).attr("width",u.width+(t.padding||0)).attr("height",u.height+(t.padding||0)),k.attr("class","divider").attr("x1",-u.width/2-b).attr("x2",u.width/2+b).attr("y1",-u.height/2-b+f.height+b).attr("y2",-u.height/2-b+f.height+b);return lt(t,v),t.intersect=function(W){return et.rect(t,W)},a}m(n1,"rectWithTitle");function Gn(e,t,r,i,n,a,o){const l=(e+r)/2,c=(t+i)/2,h=Math.atan2(i-t,r-e),u=(r-e)/2,d=(i-t)/2,f=u/n,p=d/a,g=Math.sqrt(f**2+p**2);if(g>1)throw new Error("The given radii are too small to create an arc between the points.");const y=Math.sqrt(1-g**2),b=l+y*a*Math.sin(h)*(o?-1:1),x=c-y*n*Math.cos(h)*(o?-1:1),_=Math.atan2((t-x)/a,(e-b)/n);let C=Math.atan2((i-x)/a,(r-b)/n)-_;o&&C<0&&(C+=2*Math.PI),!o&&C>0&&(C-=2*Math.PI);const v=[];for(let k=0;k<20;k++){const $=k/19,z=_+$*C,W=b+n*Math.cos(z),O=x+a*Math.sin(z);v.push({x:W,y:O})}return v}m(Gn,"generateArcPoints");async function a1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=(t==null?void 0:t.padding)??0,s=(t==null?void 0:t.padding)??0,l=(t!=null&&t.width?t==null?void 0:t.width:a.width)+o*2,c=(t!=null&&t.height?t==null?void 0:t.height:a.height)+s*2,h=t.radius||5,u=t.taper||5,{cssStyles:d}=t,f=at.svg(n),p=st(t,{});t.stroke&&(p.stroke=t.stroke),t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const g=[{x:-l/2+u,y:-c/2},{x:l/2-u,y:-c/2},...Gn(l/2-u,-c/2,l/2,-c/2+u,h,h,!0),{x:l/2,y:-c/2+u},{x:l/2,y:c/2-u},...Gn(l/2,c/2-u,l/2-u,c/2,h,h,!0),{x:l/2-u,y:c/2},{x:-l/2+u,y:c/2},...Gn(-l/2+u,c/2,-l/2,c/2-u,h,h,!0),{x:-l/2,y:c/2-u},{x:-l/2,y:-c/2+u},...Gn(-l/2,-c/2+u,-l/2+u,-c/2,h,h,!0)],y=Ft(g),b=f.path(y,p),x=n.insert(()=>b,":first-child");return x.attr("class","basic label-container outer-path"),d&&t.look!=="handDrawn"&&x.selectChildren("path").attr("style",d),i&&t.look!=="handDrawn"&&x.selectChildren("path").attr("style",i),lt(t,x),t.intersect=function(_){return et.polygon(t,g,_)},n}m(a1,"roundedRect");async function s1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=(t==null?void 0:t.padding)??0,l=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),c=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),h=-a.width/2-s,u=-a.height/2-s,{cssStyles:d}=t,f=at.svg(n),p=st(t,{});t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const g=[{x:h,y:u},{x:h+l+8,y:u},{x:h+l+8,y:u+c},{x:h-8,y:u+c},{x:h-8,y:u},{x:h,y:u},{x:h,y:u+c}],y=f.polygon(g.map(x=>[x.x,x.y]),p),b=n.insert(()=>y,":first-child");return b.attr("class","basic label-container").attr("style",xe(d)),i&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",i),d&&t.look!=="handDrawn"&&b.selectAll("path").attr("style",i),o.attr("transform",`translate(${-l/2+4+(t.padding??0)-(a.x-(a.left??0))},${-c/2+(t.padding??0)-(a.y-(a.top??0))})`),lt(t,b),t.intersect=function(x){return et.rect(t,x)},n}m(s1,"shadedProcess");async function o1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=-s/2,h=-l/2,{cssStyles:u}=t,d=at.svg(n),f=st(t,{});t.look!=="handDrawn"&&(f.roughness=0,f.fillStyle="solid");const p=[{x:c,y:h},{x:c,y:h+l},{x:c+s,y:h+l},{x:c+s,y:h-l/2}],g=Ft(p),y=d.path(g,f),b=n.insert(()=>y,":first-child");return b.attr("class","basic label-container"),u&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",u),i&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",i),b.attr("transform",`translate(0, ${l/4})`),o.attr("transform",`translate(${-s/2+(t.padding??0)-(a.x-(a.left??0))}, ${-l/4+(t.padding??0)-(a.y-(a.top??0))})`),lt(t,b),t.intersect=function(x){return et.polygon(t,p,x)},n}m(o1,"slopedRect");async function l1(e,t){const r={rx:0,ry:0,labelPaddingX:t.labelPaddingX??((t==null?void 0:t.padding)||0)*2,labelPaddingY:((t==null?void 0:t.padding)||0)*1};return Vo(e,t,r)}m(l1,"squareRect");async function c1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.height+t.padding,s=a.width+o/4+t.padding,l=o/2,{cssStyles:c}=t,h=at.svg(n),u=st(t,{});t.look!=="handDrawn"&&(u.roughness=0,u.fillStyle="solid");const d=[{x:-s/2+l,y:-o/2},{x:s/2-l,y:-o/2},...pa(-s/2+l,0,l,50,90,270),{x:s/2-l,y:o/2},...pa(s/2-l,0,l,50,270,450)],f=Ft(d),p=h.path(f,u),g=n.insert(()=>p,":first-child");return g.attr("class","basic label-container outer-path"),c&&t.look!=="handDrawn"&&g.selectChildren("path").attr("style",c),i&&t.look!=="handDrawn"&&g.selectChildren("path").attr("style",i),lt(t,g),t.intersect=function(y){return et.polygon(t,d,y)},n}m(c1,"stadium");async function h1(e,t){return Vo(e,t,{rx:5,ry:5})}m(h1,"state");function u1(e,t,{config:{themeVariables:r}}){const{labelStyles:i,nodeStyles:n}=ot(t);t.labelStyle=i;const{cssStyles:a}=t,{lineColor:o,stateBorder:s,nodeBorder:l}=r,c=e.insert("g").attr("class","node default").attr("id",t.domId||t.id),h=at.svg(c),u=st(t,{});t.look!=="handDrawn"&&(u.roughness=0,u.fillStyle="solid");const d=h.circle(0,0,14,{...u,stroke:o,strokeWidth:2}),f=s??l,p=h.circle(0,0,5,{...u,fill:f,stroke:f,strokeWidth:2,fillStyle:"solid"}),g=c.insert(()=>d,":first-child");return g.insert(()=>p),a&&g.selectAll("path").attr("style",a),n&&g.selectAll("path").attr("style",n),lt(t,g),t.intersect=function(y){return et.circle(t,7,y)},c}m(u1,"stateEnd");function d1(e,t,{config:{themeVariables:r}}){const{lineColor:i}=r,n=e.insert("g").attr("class","node default").attr("id",t.domId||t.id);let a;if(t.look==="handDrawn"){const s=at.svg(n).circle(0,0,14,Y3(i));a=n.insert(()=>s),a.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14)}else a=n.insert("circle",":first-child"),a.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14);return lt(t,a),t.intersect=function(o){return et.circle(t,7,o)},n}m(d1,"stateStart");async function f1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=((t==null?void 0:t.padding)||0)/2,s=a.width+t.padding,l=a.height+t.padding,c=-a.width/2-o,h=-a.height/2-o,u=[{x:0,y:0},{x:s,y:0},{x:s,y:-l},{x:0,y:-l},{x:0,y:0},{x:-8,y:0},{x:s+8,y:0},{x:s+8,y:-l},{x:-8,y:-l},{x:-8,y:0}];if(t.look==="handDrawn"){const d=at.svg(n),f=st(t,{}),p=d.rectangle(c-8,h,s+16,l,f),g=d.line(c,h,c,h+l,f),y=d.line(c+s,h,c+s,h+l,f);n.insert(()=>g,":first-child"),n.insert(()=>y,":first-child");const b=n.insert(()=>p,":first-child"),{cssStyles:x}=t;b.attr("class","basic label-container").attr("style",xe(x)),lt(t,b)}else{const d=Kr(n,s,l,u);i&&d.attr("style",i),lt(t,d)}return t.intersect=function(d){return et.polygon(t,u,d)},n}m(f1,"subroutine");async function p1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),s=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),l=-o/2,c=-s/2,h=.2*s,u=.2*s,{cssStyles:d}=t,f=at.svg(n),p=st(t,{}),g=[{x:l-h/2,y:c},{x:l+o+h/2,y:c},{x:l+o+h/2,y:c+s},{x:l-h/2,y:c+s}],y=[{x:l+o-h/2,y:c+s},{x:l+o+h/2,y:c+s},{x:l+o+h/2,y:c+s-u}];t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const b=Ft(g),x=f.path(b,p),_=Ft(y),w=f.path(_,{...p,fillStyle:"solid"}),C=n.insert(()=>w,":first-child");return C.insert(()=>x,":first-child"),C.attr("class","basic label-container"),d&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",d),i&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",i),lt(t,C),t.intersect=function(v){return et.polygon(t,g,v)},n}m(p1,"taggedRect");async function g1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=l/4,h=.2*s,u=.2*l,d=l+c,{cssStyles:f}=t,p=at.svg(n),g=st(t,{});t.look!=="handDrawn"&&(g.roughness=0,g.fillStyle="solid");const y=[{x:-s/2-s/2*.1,y:d/2},...Yr(-s/2-s/2*.1,d/2,s/2+s/2*.1,d/2,c,.8),{x:s/2+s/2*.1,y:-d/2},{x:-s/2-s/2*.1,y:-d/2}],b=-s/2+s/2*.1,x=-d/2-u*.4,_=[{x:b+s-h,y:(x+l)*1.4},{x:b+s,y:x+l-u},{x:b+s,y:(x+l)*.9},...Yr(b+s,(x+l)*1.3,b+s-h,(x+l)*1.5,-l*.03,.5)],w=Ft(y),C=p.path(w,g),v=Ft(_),k=p.path(v,{...g,fillStyle:"solid"}),$=n.insert(()=>k,":first-child");return $.insert(()=>C,":first-child"),$.attr("class","basic label-container"),f&&t.look!=="handDrawn"&&$.selectAll("path").attr("style",f),i&&t.look!=="handDrawn"&&$.selectAll("path").attr("style",i),$.attr("transform",`translate(0,${-c/2})`),o.attr("transform",`translate(${-s/2+(t.padding??0)-(a.x-(a.left??0))},${-l/2+(t.padding??0)-c/2-(a.y-(a.top??0))})`),lt(t,$),t.intersect=function(z){return et.polygon(t,y,z)},n}m(g1,"taggedWaveEdgedRectangle");async function m1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=Math.max(a.width+t.padding,(t==null?void 0:t.width)||0),s=Math.max(a.height+t.padding,(t==null?void 0:t.height)||0),l=-o/2,c=-s/2,h=n.insert("rect",":first-child");return h.attr("class","text").attr("style",i).attr("rx",0).attr("ry",0).attr("x",l).attr("y",c).attr("width",o).attr("height",s),lt(t,h),t.intersect=function(u){return et.rect(t,u)},n}m(m1,"text");var D4=m((e,t,r,i,n,a)=>`M${e},${t} + a${n},${a} 0,0,1 0,${-i} + l${r},0 + a${n},${a} 0,0,1 0,${i} + M${r},${-i} + a${n},${a} 0,0,0 0,${i} + l${-r},0`,"createCylinderPathD"),I4=m((e,t,r,i,n,a)=>[`M${e},${t}`,`M${e+r},${t}`,`a${n},${a} 0,0,0 0,${-i}`,`l${-r},0`,`a${n},${a} 0,0,0 0,${i}`,`l${r},0`].join(" "),"createOuterCylinderPathD"),O4=m((e,t,r,i,n,a)=>[`M${e+r/2},${-i/2}`,`a${n},${a} 0,0,0 0,${i}`].join(" "),"createInnerCylinderPathD");async function y1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o,halfPadding:s}=await _t(e,t,bt(t)),l=t.look==="neo"?s*2:s,c=a.height+l,h=c/2,u=h/(2.5+c/50),d=a.width+u+l,{cssStyles:f}=t;let p;if(t.look==="handDrawn"){const g=at.svg(n),y=I4(0,0,d,c,u,h),b=O4(0,0,d,c,u,h),x=g.path(y,st(t,{})),_=g.path(b,st(t,{fill:"none"}));p=n.insert(()=>_,":first-child"),p=n.insert(()=>x,":first-child"),p.attr("class","basic label-container"),f&&p.attr("style",f)}else{const g=D4(0,0,d,c,u,h);p=n.insert("path",":first-child").attr("d",g).attr("class","basic label-container").attr("style",xe(f)).attr("style",i),p.attr("class","basic label-container"),f&&p.selectAll("path").attr("style",f),i&&p.selectAll("path").attr("style",i)}return p.attr("label-offset-x",u),p.attr("transform",`translate(${-d/2}, ${c/2} )`),o.attr("transform",`translate(${-(a.width/2)-u-(a.x-(a.left??0))}, ${-(a.height/2)-(a.y-(a.top??0))})`),lt(t,p),t.intersect=function(g){const y=et.rect(t,g),b=y.y-(t.y??0);if(h!=0&&(Math.abs(b)<(t.height??0)/2||Math.abs(b)==(t.height??0)/2&&Math.abs(y.x-(t.x??0))>(t.width??0)/2-u)){let x=u*u*(1-b*b/(h*h));x!=0&&(x=Math.sqrt(Math.abs(x))),x=u-x,g.x-(t.x??0)>0&&(x=-x),y.x+=x}return y},n}m(y1,"tiltedCylinder");async function b1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=a.width+t.padding,s=a.height+t.padding,l=[{x:-3*s/6,y:0},{x:o+3*s/6,y:0},{x:o,y:-s},{x:0,y:-s}];let c;const{cssStyles:h}=t;if(t.look==="handDrawn"){const u=at.svg(n),d=st(t,{}),f=Ft(l),p=u.path(f,d);c=n.insert(()=>p,":first-child").attr("transform",`translate(${-o/2}, ${s/2})`),h&&c.attr("style",h)}else c=Kr(n,o,s,l);return i&&c.attr("style",i),t.width=o,t.height=s,lt(t,c),t.intersect=function(u){return et.polygon(t,l,u)},n}m(b1,"trapezoid");async function v1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=60,s=20,l=Math.max(o,a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),c=Math.max(s,a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),{cssStyles:h}=t,u=at.svg(n),d=st(t,{});t.look!=="handDrawn"&&(d.roughness=0,d.fillStyle="solid");const f=[{x:-l/2*.8,y:-c/2},{x:l/2*.8,y:-c/2},{x:l/2,y:-c/2*.6},{x:l/2,y:c/2},{x:-l/2,y:c/2},{x:-l/2,y:-c/2*.6}],p=Ft(f),g=u.path(p,d),y=n.insert(()=>g,":first-child");return y.attr("class","basic label-container"),h&&t.look!=="handDrawn"&&y.selectChildren("path").attr("style",h),i&&t.look!=="handDrawn"&&y.selectChildren("path").attr("style",i),lt(t,y),t.intersect=function(b){return et.polygon(t,f,b)},n}m(v1,"trapezoidalPentagon");async function x1(e,t){var x;const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=ie((x=qt().flowchart)==null?void 0:x.htmlLabels),l=a.width+(t.padding??0),c=l+a.height,h=l+a.height,u=[{x:0,y:0},{x:h,y:0},{x:h/2,y:-c}],{cssStyles:d}=t,f=at.svg(n),p=st(t,{});t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const g=Ft(u),y=f.path(g,p),b=n.insert(()=>y,":first-child").attr("transform",`translate(${-c/2}, ${c/2})`);return d&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",d),i&&t.look!=="handDrawn"&&b.selectChildren("path").attr("style",i),t.width=l,t.height=c,lt(t,b),o.attr("transform",`translate(${-a.width/2-(a.x-(a.left??0))}, ${c/2-(a.height+(t.padding??0)/(s?2:1)-(a.y-(a.top??0)))})`),t.intersect=function(_){return V.info("Triangle intersect",t,u,_),et.polygon(t,u,_)},n}m(x1,"triangle");async function _1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=l/8,h=l+c,{cssStyles:u}=t,f=70-s,p=f>0?f/2:0,g=at.svg(n),y=st(t,{});t.look!=="handDrawn"&&(y.roughness=0,y.fillStyle="solid");const b=[{x:-s/2-p,y:h/2},...Yr(-s/2-p,h/2,s/2+p,h/2,c,.8),{x:s/2+p,y:-h/2},{x:-s/2-p,y:-h/2}],x=Ft(b),_=g.path(x,y),w=n.insert(()=>_,":first-child");return w.attr("class","basic label-container"),u&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",u),i&&t.look!=="handDrawn"&&w.selectAll("path").attr("style",i),w.attr("transform",`translate(0,${-c/2})`),o.attr("transform",`translate(${-s/2+(t.padding??0)-(a.x-(a.left??0))},${-l/2+(t.padding??0)-c-(a.y-(a.top??0))})`),lt(t,w),t.intersect=function(C){return et.polygon(t,b,C)},n}m(_1,"waveEdgedRectangle");async function k1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a}=await _t(e,t,bt(t)),o=100,s=50,l=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),c=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),h=l/c;let u=l,d=c;u>d*h?d=u/h:u=d*h,u=Math.max(u,o),d=Math.max(d,s);const f=Math.min(d*.2,d/4),p=d+f*2,{cssStyles:g}=t,y=at.svg(n),b=st(t,{});t.look!=="handDrawn"&&(b.roughness=0,b.fillStyle="solid");const x=[{x:-u/2,y:p/2},...Yr(-u/2,p/2,u/2,p/2,f,1),{x:u/2,y:-p/2},...Yr(u/2,-p/2,-u/2,-p/2,f,-1)],_=Ft(x),w=y.path(_,b),C=n.insert(()=>w,":first-child");return C.attr("class","basic label-container"),g&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",g),i&&t.look!=="handDrawn"&&C.selectAll("path").attr("style",i),lt(t,C),t.intersect=function(v){return et.polygon(t,x,v)},n}m(k1,"waveRectangle");async function w1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,label:o}=await _t(e,t,bt(t)),s=Math.max(a.width+(t.padding??0)*2,(t==null?void 0:t.width)??0),l=Math.max(a.height+(t.padding??0)*2,(t==null?void 0:t.height)??0),c=5,h=-s/2,u=-l/2,{cssStyles:d}=t,f=at.svg(n),p=st(t,{}),g=[{x:h-c,y:u-c},{x:h-c,y:u+l},{x:h+s,y:u+l},{x:h+s,y:u-c}],y=`M${h-c},${u-c} L${h+s},${u-c} L${h+s},${u+l} L${h-c},${u+l} L${h-c},${u-c} + M${h-c},${u} L${h+s},${u} + M${h},${u-c} L${h},${u+l}`;t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const b=f.path(y,p),x=n.insert(()=>b,":first-child");return x.attr("transform",`translate(${c/2}, ${c/2})`),x.attr("class","basic label-container"),d&&t.look!=="handDrawn"&&x.selectAll("path").attr("style",d),i&&t.look!=="handDrawn"&&x.selectAll("path").attr("style",i),o.attr("transform",`translate(${-(a.width/2)+c/2-(a.x-(a.left??0))}, ${-(a.height/2)+c/2-(a.y-(a.top??0))})`),lt(t,x),t.intersect=function(_){return et.polygon(t,g,_)},n}m(w1,"windowPane");async function iu(e,t){var Tt,vt,Pt,xt;const r=t;if(r.alias&&(t.label=r.alias),t.look==="handDrawn"){const{themeVariables:kt}=be(),{background:St}=kt,It={...t,id:t.id+"-background",look:"default",cssStyles:["stroke: none",`fill: ${St}`]};await iu(e,It)}const i=be();t.useHtmlLabels=i.htmlLabels;let n=((Tt=i.er)==null?void 0:Tt.diagramPadding)??10,a=((vt=i.er)==null?void 0:vt.entityPadding)??6;const{cssStyles:o}=t,{labelStyles:s,nodeStyles:l}=ot(t);if(r.attributes.length===0&&t.label){const kt={rx:0,ry:0,labelPaddingX:n,labelPaddingY:n*1.5};Fr(t.label,i)+kt.labelPaddingX*20){const kt=u.width+n*2-(g+y+b+x);g+=kt/C,y+=kt/C,b>0&&(b+=kt/C),x>0&&(x+=kt/C)}const k=g+y+b+x,$=at.svg(h),z=st(t,{});t.look!=="handDrawn"&&(z.roughness=0,z.fillStyle="solid");let W=0;p.length>0&&(W=p.reduce((kt,St)=>kt+((St==null?void 0:St.rowHeight)??0),0));const O=Math.max(v.width+n*2,(t==null?void 0:t.width)||0,k),N=Math.max((W??0)+u.height,(t==null?void 0:t.height)||0),D=-O/2,L=-N/2;h.selectAll("g:not(:first-child)").each((kt,St,It)=>{const Y=Ot(It[St]),Q=Y.attr("transform");let ht=0,q=0;if(Q){const $t=RegExp(/translate\(([^,]+),([^)]+)\)/).exec(Q);$t&&(ht=parseFloat($t[1]),q=parseFloat($t[2]),Y.attr("class").includes("attribute-name")?ht+=g:Y.attr("class").includes("attribute-keys")?ht+=g+y:Y.attr("class").includes("attribute-comment")&&(ht+=g+y+b))}Y.attr("transform",`translate(${D+n/2+ht}, ${q+L+u.height+a/2})`)}),h.select(".name").attr("transform","translate("+-u.width/2+", "+(L+a/2)+")");const B=$.rectangle(D,L,O,N,z),F=h.insert(()=>B,":first-child").attr("style",o.join("")),{themeVariables:R}=be(),{rowEven:I,rowOdd:X,nodeBorder:Z}=R;f.push(0);for(const[kt,St]of p.entries()){const Y=(kt+1)%2===0&&St.yOffset!==0,Q=$.rectangle(D,u.height+L+(St==null?void 0:St.yOffset),O,St==null?void 0:St.rowHeight,{...z,fill:Y?I:X,stroke:Z});h.insert(()=>Q,"g.label").attr("style",o.join("")).attr("class",`row-rect-${Y?"even":"odd"}`)}let J=$.line(D,u.height+L,O+D,u.height+L,z);h.insert(()=>J).attr("class","divider"),J=$.line(g+D,u.height+L,g+D,N+L,z),h.insert(()=>J).attr("class","divider"),_&&(J=$.line(g+y+D,u.height+L,g+y+D,N+L,z),h.insert(()=>J).attr("class","divider")),w&&(J=$.line(g+y+b+D,u.height+L,g+y+b+D,N+L,z),h.insert(()=>J).attr("class","divider"));for(const kt of f)J=$.line(D,u.height+L+kt,O+D,u.height+L+kt,z),h.insert(()=>J).attr("class","divider");if(lt(t,F),l&&t.look!=="handDrawn"){const kt=l.split(";"),St=(xt=kt==null?void 0:kt.filter(It=>It.includes("stroke")))==null?void 0:xt.map(It=>`${It}`).join("; ");h.selectAll("path").attr("style",St??""),h.selectAll(".row-rect-even path").attr("style",l)}return t.intersect=function(kt){return et.rect(t,kt)},h}m(iu,"erBox");async function Ni(e,t,r,i=0,n=0,a=[],o=""){const s=e.insert("g").attr("class",`label ${a.join(" ")}`).attr("transform",`translate(${i}, ${n})`).attr("style",o);t!==Iu(t)&&(t=Iu(t),t=t.replaceAll("<","<").replaceAll(">",">"));const l=s.node().appendChild(await Xr(s,t,{width:Fr(t,r)+100,style:o,useHtmlLabels:r.htmlLabels},r));if(t.includes("<")||t.includes(">")){let h=l.children[0];for(h.textContent=h.textContent.replaceAll("<","<").replaceAll(">",">");h.childNodes[0];)h=h.childNodes[0],h.textContent=h.textContent.replaceAll("<","<").replaceAll(">",">")}let c=l.getBBox();if(ie(r.htmlLabels)){const h=l.children[0];h.style.textAlign="start";const u=Ot(l);c=h.getBoundingClientRect(),u.attr("width",c.width),u.attr("height",c.height)}return c}m(Ni,"addText");async function C1(e,t,r,i,n=r.class.padding??12){const a=i?0:3,o=e.insert("g").attr("class",bt(t)).attr("id",t.domId||t.id);let s=null,l=null,c=null,h=null,u=0,d=0,f=0;if(s=o.insert("g").attr("class","annotation-group text"),t.annotations.length>0){const x=t.annotations[0];await Xn(s,{text:`«${x}»`},0),u=s.node().getBBox().height}l=o.insert("g").attr("class","label-group text"),await Xn(l,t,0,["font-weight: bolder"]);const p=l.node().getBBox();d=p.height,c=o.insert("g").attr("class","members-group text");let g=0;for(const x of t.members){const _=await Xn(c,x,g,[x.parseClassifier()]);g+=_+a}f=c.node().getBBox().height,f<=0&&(f=n/2),h=o.insert("g").attr("class","methods-group text");let y=0;for(const x of t.methods){const _=await Xn(h,x,y,[x.parseClassifier()]);y+=_+a}let b=o.node().getBBox();if(s!==null){const x=s.node().getBBox();s.attr("transform",`translate(${-x.width/2})`)}return l.attr("transform",`translate(${-p.width/2}, ${u})`),b=o.node().getBBox(),c.attr("transform",`translate(0, ${u+d+n*2})`),b=o.node().getBBox(),h.attr("transform",`translate(0, ${u+d+(f?f+n*4:n*2)})`),b=o.node().getBBox(),{shapeSvg:o,bbox:b}}m(C1,"textHelper");async function Xn(e,t,r,i=[]){const n=e.insert("g").attr("class","label").attr("style",i.join("; ")),a=be();let o="useHtmlLabels"in t?t.useHtmlLabels:ie(a.htmlLabels)??!0,s="";"text"in t?s=t.text:s=t.label,!o&&s.startsWith("\\")&&(s=s.substring(1)),an(s)&&(o=!0);const l=await Xr(n,lh(Ai(s)),{width:Fr(s,a)+50,classes:"markdown-node-label",useHtmlLabels:o},a);let c,h=1;if(o){const u=l.children[0],d=Ot(l);h=u.innerHTML.split("
    ").length,u.innerHTML.includes("")&&(h+=u.innerHTML.split("").length-1);const f=u.getElementsByTagName("img");if(f){const p=s.replace(/]*>/g,"").trim()==="";await Promise.all([...f].map(g=>new Promise(y=>{function b(){var x;if(g.style.display="flex",g.style.flexDirection="column",p){const _=((x=a.fontSize)==null?void 0:x.toString())??window.getComputedStyle(document.body).fontSize,C=parseInt(_,10)*5+"px";g.style.minWidth=C,g.style.maxWidth=C}else g.style.width="100%";y(g)}m(b,"setupImage"),setTimeout(()=>{g.complete&&b()}),g.addEventListener("error",b),g.addEventListener("load",b)})))}c=u.getBoundingClientRect(),d.attr("width",c.width),d.attr("height",c.height)}else{i.includes("font-weight: bolder")&&Ot(l).selectAll("tspan").attr("font-weight",""),h=l.children.length;const u=l.children[0];(l.textContent===""||l.textContent.includes(">"))&&(u.textContent=s[0]+s.substring(1).replaceAll(">",">").replaceAll("<","<").trim(),s[1]===" "&&(u.textContent=u.textContent[0]+" "+u.textContent.substring(1))),u.textContent==="undefined"&&(u.textContent=""),c=l.getBBox()}return n.attr("transform","translate(0,"+(-c.height/(2*h)+r)+")"),c.height}m(Xn,"addText");async function S1(e,t){var z,W;const r=qt(),i=r.class.padding??12,n=i,a=t.useHtmlLabels??ie(r.htmlLabels)??!0,o=t;o.annotations=o.annotations??[],o.members=o.members??[],o.methods=o.methods??[];const{shapeSvg:s,bbox:l}=await C1(e,t,r,a,n),{labelStyles:c,nodeStyles:h}=ot(t);t.labelStyle=c,t.cssStyles=o.styles||"";const u=((z=o.styles)==null?void 0:z.join(";"))||h||"";t.cssStyles||(t.cssStyles=u.replaceAll("!important","").split(";"));const d=o.members.length===0&&o.methods.length===0&&!((W=r.class)!=null&&W.hideEmptyMembersBox),f=at.svg(s),p=st(t,{});t.look!=="handDrawn"&&(p.roughness=0,p.fillStyle="solid");const g=l.width;let y=l.height;o.members.length===0&&o.methods.length===0?y+=n:o.members.length>0&&o.methods.length===0&&(y+=n*2);const b=-g/2,x=-y/2,_=f.rectangle(b-i,x-i-(d?i:o.members.length===0&&o.methods.length===0?-i/2:0),g+2*i,y+2*i+(d?i*2:o.members.length===0&&o.methods.length===0?-i:0),p),w=s.insert(()=>_,":first-child");w.attr("class","basic label-container");const C=w.node().getBBox();s.selectAll(".text").each((O,N,D)=>{var X;const L=Ot(D[N]),B=L.attr("transform");let F=0;if(B){const J=RegExp(/translate\(([^,]+),([^)]+)\)/).exec(B);J&&(F=parseFloat(J[2]))}let R=F+x+i-(d?i:o.members.length===0&&o.methods.length===0?-i/2:0);a||(R-=4);let I=b;(L.attr("class").includes("label-group")||L.attr("class").includes("annotation-group"))&&(I=-((X=L.node())==null?void 0:X.getBBox().width)/2||0,s.selectAll("text").each(function(Z,J,Tt){window.getComputedStyle(Tt[J]).textAnchor==="middle"&&(I=0)})),L.attr("transform",`translate(${I}, ${R})`)});const v=s.select(".annotation-group").node().getBBox().height-(d?i/2:0)||0,k=s.select(".label-group").node().getBBox().height-(d?i/2:0)||0,$=s.select(".members-group").node().getBBox().height-(d?i/2:0)||0;if(o.members.length>0||o.methods.length>0||d){const O=f.line(C.x,v+k+x+i,C.x+C.width,v+k+x+i,p);s.insert(()=>O).attr("class","divider").attr("style",u)}if(d||o.members.length>0||o.methods.length>0){const O=f.line(C.x,v+k+$+x+n*2+i,C.x+C.width,v+k+$+x+i+n*2,p);s.insert(()=>O).attr("class","divider").attr("style",u)}if(o.look!=="handDrawn"&&s.selectAll("path").attr("style",u),w.select(":nth-child(2)").attr("style",u),s.selectAll(".divider").select("path").attr("style",u),t.labelStyle?s.selectAll("span").attr("style",t.labelStyle):s.selectAll("span").attr("style",u),!a){const O=RegExp(/color\s*:\s*([^;]*)/),N=O.exec(u);if(N){const D=N[0].replace("color","fill");s.selectAll("tspan").attr("style",D)}else if(c){const D=O.exec(c);if(D){const L=D[0].replace("color","fill");s.selectAll("tspan").attr("style",L)}}}return lt(t,w),t.intersect=function(O){return et.rect(t,O)},s}m(S1,"classBox");async function T1(e,t){var v,k;const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const n=t,a=t,o=20,s=20,l="verifyMethod"in t,c=bt(t),h=e.insert("g").attr("class",c).attr("id",t.domId??t.id);let u;l?u=await or(h,`<<${n.type}>>`,0,t.labelStyle):u=await or(h,"<<Element>>",0,t.labelStyle);let d=u;const f=await or(h,n.name,d,t.labelStyle+"; font-weight: bold;");if(d+=f+s,l){const $=await or(h,`${n.requirementId?`ID: ${n.requirementId}`:""}`,d,t.labelStyle);d+=$;const z=await or(h,`${n.text?`Text: ${n.text}`:""}`,d,t.labelStyle);d+=z;const W=await or(h,`${n.risk?`Risk: ${n.risk}`:""}`,d,t.labelStyle);d+=W,await or(h,`${n.verifyMethod?`Verification: ${n.verifyMethod}`:""}`,d,t.labelStyle)}else{const $=await or(h,`${a.type?`Type: ${a.type}`:""}`,d,t.labelStyle);d+=$,await or(h,`${a.docRef?`Doc Ref: ${a.docRef}`:""}`,d,t.labelStyle)}const p=(((v=h.node())==null?void 0:v.getBBox().width)??200)+o,g=(((k=h.node())==null?void 0:k.getBBox().height)??200)+o,y=-p/2,b=-g/2,x=at.svg(h),_=st(t,{});t.look!=="handDrawn"&&(_.roughness=0,_.fillStyle="solid");const w=x.rectangle(y,b,p,g,_),C=h.insert(()=>w,":first-child");if(C.attr("class","basic label-container").attr("style",i),h.selectAll(".label").each(($,z,W)=>{const O=Ot(W[z]),N=O.attr("transform");let D=0,L=0;if(N){const I=RegExp(/translate\(([^,]+),([^)]+)\)/).exec(N);I&&(D=parseFloat(I[1]),L=parseFloat(I[2]))}const B=L-g/2;let F=y+o/2;(z===0||z===1)&&(F=D),O.attr("transform",`translate(${F}, ${B+o})`)}),d>u+f+s){const $=x.line(y,b+u+f+s,y+p,b+u+f+s,_);h.insert(()=>$).attr("style",i)}return lt(t,C),t.intersect=function($){return et.rect(t,$)},h}m(T1,"requirementBox");async function or(e,t,r,i=""){if(t==="")return 0;const n=e.insert("g").attr("class","label").attr("style",i),a=qt(),o=a.htmlLabels??!0,s=await Xr(n,lh(Ai(t)),{width:Fr(t,a)+50,classes:"markdown-node-label",useHtmlLabels:o,style:i},a);let l;if(o){const c=s.children[0],h=Ot(s);l=c.getBoundingClientRect(),h.attr("width",l.width),h.attr("height",l.height)}else{const c=s.children[0];for(const h of c.children)h.textContent=h.textContent.replaceAll(">",">").replaceAll("<","<"),i&&h.setAttribute("style",i);l=s.getBBox(),l.height+=6}return n.attr("transform",`translate(${-l.width/2},${-l.height/2+r})`),l.height}m(or,"addText");var R4=m(e=>{switch(e){case"Very High":return"red";case"High":return"orange";case"Medium":return null;case"Low":return"blue";case"Very Low":return"lightblue"}},"colorFromPriority");async function M1(e,t,{config:r}){var N,D;const{labelStyles:i,nodeStyles:n}=ot(t);t.labelStyle=i||"";const a=10,o=t.width;t.width=(t.width??200)-10;const{shapeSvg:s,bbox:l,label:c}=await _t(e,t,bt(t)),h=t.padding||10;let u="",d;"ticket"in t&&t.ticket&&((N=r==null?void 0:r.kanban)!=null&&N.ticketBaseUrl)&&(u=(D=r==null?void 0:r.kanban)==null?void 0:D.ticketBaseUrl.replace("#TICKET#",t.ticket),d=s.insert("svg:a",":first-child").attr("class","kanban-ticket-link").attr("xlink:href",u).attr("target","_blank"));const f={useHtmlLabels:t.useHtmlLabels,labelStyle:t.labelStyle||"",width:t.width,img:t.img,padding:t.padding||8,centerLabel:!1};let p,g;d?{label:p,bbox:g}=await Bl(d,"ticket"in t&&t.ticket||"",f):{label:p,bbox:g}=await Bl(s,"ticket"in t&&t.ticket||"",f);const{label:y,bbox:b}=await Bl(s,"assigned"in t&&t.assigned||"",f);t.width=o;const x=10,_=(t==null?void 0:t.width)||0,w=Math.max(g.height,b.height)/2,C=Math.max(l.height+x*2,(t==null?void 0:t.height)||0)+w,v=-_/2,k=-C/2;c.attr("transform","translate("+(h-_/2)+", "+(-w-l.height/2)+")"),p.attr("transform","translate("+(h-_/2)+", "+(-w+l.height/2)+")"),y.attr("transform","translate("+(h+_/2-b.width-2*a)+", "+(-w+l.height/2)+")");let $;const{rx:z,ry:W}=t,{cssStyles:O}=t;if(t.look==="handDrawn"){const L=at.svg(s),B=st(t,{}),F=z||W?L.path(Zr(v,k,_,C,z||0),B):L.rectangle(v,k,_,C,B);$=s.insert(()=>F,":first-child"),$.attr("class","basic label-container").attr("style",O||null)}else{$=s.insert("rect",":first-child"),$.attr("class","basic label-container __APA__").attr("style",n).attr("rx",z??5).attr("ry",W??5).attr("x",v).attr("y",k).attr("width",_).attr("height",C);const L="priority"in t&&t.priority;if(L){const B=s.append("line"),F=v+2,R=k+Math.floor((z??0)/2),I=k+C-Math.floor((z??0)/2);B.attr("x1",F).attr("y1",R).attr("x2",F).attr("y2",I).attr("stroke-width","4").attr("stroke",R4(L))}}return lt(t,$),t.height=C,t.intersect=function(L){return et.rect(t,L)},s}m(M1,"kanbanItem");async function $1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,halfPadding:o,label:s}=await _t(e,t,bt(t)),l=a.width+10*o,c=a.height+8*o,h=.15*l,{cssStyles:u}=t,d=a.width+20,f=a.height+20,p=Math.max(l,d),g=Math.max(c,f);s.attr("transform",`translate(${-a.width/2}, ${-a.height/2})`);let y;const b=`M0 0 + a${h},${h} 1 0,0 ${p*.25},${-1*g*.1} + a${h},${h} 1 0,0 ${p*.25},0 + a${h},${h} 1 0,0 ${p*.25},0 + a${h},${h} 1 0,0 ${p*.25},${g*.1} + + a${h},${h} 1 0,0 ${p*.15},${g*.33} + a${h*.8},${h*.8} 1 0,0 0,${g*.34} + a${h},${h} 1 0,0 ${-1*p*.15},${g*.33} + + a${h},${h} 1 0,0 ${-1*p*.25},${g*.15} + a${h},${h} 1 0,0 ${-1*p*.25},0 + a${h},${h} 1 0,0 ${-1*p*.25},0 + a${h},${h} 1 0,0 ${-1*p*.25},${-1*g*.15} + + a${h},${h} 1 0,0 ${-1*p*.1},${-1*g*.33} + a${h*.8},${h*.8} 1 0,0 0,${-1*g*.34} + a${h},${h} 1 0,0 ${p*.1},${-1*g*.33} + H0 V0 Z`;if(t.look==="handDrawn"){const x=at.svg(n),_=st(t,{}),w=x.path(b,_);y=n.insert(()=>w,":first-child"),y.attr("class","basic label-container").attr("style",xe(u))}else y=n.insert("path",":first-child").attr("class","basic label-container").attr("style",i).attr("d",b);return y.attr("transform",`translate(${-p/2}, ${-g/2})`),lt(t,y),t.calcIntersect=function(x,_){return et.rect(x,_)},t.intersect=function(x){return V.info("Bang intersect",t,x),et.rect(t,x)},n}m($1,"bang");async function L1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,halfPadding:o,label:s}=await _t(e,t,bt(t)),l=a.width+2*o,c=a.height+2*o,h=.15*l,u=.25*l,d=.35*l,f=.2*l,{cssStyles:p}=t;let g;const y=`M0 0 + a${h},${h} 0 0,1 ${l*.25},${-1*l*.1} + a${d},${d} 1 0,1 ${l*.4},${-1*l*.1} + a${u},${u} 1 0,1 ${l*.35},${l*.2} + + a${h},${h} 1 0,1 ${l*.15},${c*.35} + a${f},${f} 1 0,1 ${-1*l*.15},${c*.65} + + a${u},${h} 1 0,1 ${-1*l*.25},${l*.15} + a${d},${d} 1 0,1 ${-1*l*.5},0 + a${h},${h} 1 0,1 ${-1*l*.25},${-1*l*.15} + + a${h},${h} 1 0,1 ${-1*l*.1},${-1*c*.35} + a${f},${f} 1 0,1 ${l*.1},${-1*c*.65} + H0 V0 Z`;if(t.look==="handDrawn"){const b=at.svg(n),x=st(t,{}),_=b.path(y,x);g=n.insert(()=>_,":first-child"),g.attr("class","basic label-container").attr("style",xe(p))}else g=n.insert("path",":first-child").attr("class","basic label-container").attr("style",i).attr("d",y);return s.attr("transform",`translate(${-a.width/2}, ${-a.height/2})`),g.attr("transform",`translate(${-l/2}, ${-c/2})`),lt(t,g),t.calcIntersect=function(b,x){return et.rect(b,x)},t.intersect=function(b){return V.info("Cloud intersect",t,b),et.rect(t,b)},n}m(L1,"cloud");async function A1(e,t){const{labelStyles:r,nodeStyles:i}=ot(t);t.labelStyle=r;const{shapeSvg:n,bbox:a,halfPadding:o,label:s}=await _t(e,t,bt(t)),l=a.width+8*o,c=a.height+2*o,h=5,u=` + M${-l/2} ${c/2-h} + v${-c+2*h} + q0,-${h} ${h},-${h} + h${l-2*h} + q${h},0 ${h},${h} + v${c-2*h} + q0,${h} -${h},${h} + h${-l+2*h} + q-${h},0 -${h},-${h} + Z + `,d=n.append("path").attr("id","node-"+t.id).attr("class","node-bkg node-"+t.type).attr("style",i).attr("d",u);return n.append("line").attr("class","node-line-").attr("x1",-l/2).attr("y1",c/2).attr("x2",l/2).attr("y2",c/2),s.attr("transform",`translate(${-a.width/2}, ${-a.height/2})`),n.append(()=>s.node()),lt(t,d),t.calcIntersect=function(f,p){return et.rect(f,p)},t.intersect=function(f){return et.rect(t,f)},n}m(A1,"defaultMindmapNode");async function B1(e,t){const r={padding:t.padding??0};return ru(e,t,r)}m(B1,"mindmapCircle");var N4=[{semanticName:"Process",name:"Rectangle",shortName:"rect",description:"Standard process shape",aliases:["proc","process","rectangle"],internalAliases:["squareRect"],handler:l1},{semanticName:"Event",name:"Rounded Rectangle",shortName:"rounded",description:"Represents an event",aliases:["event"],internalAliases:["roundedRect"],handler:a1},{semanticName:"Terminal Point",name:"Stadium",shortName:"stadium",description:"Terminal point",aliases:["terminal","pill"],handler:c1},{semanticName:"Subprocess",name:"Framed Rectangle",shortName:"fr-rect",description:"Subprocess",aliases:["subprocess","subproc","framed-rectangle","subroutine"],handler:f1},{semanticName:"Database",name:"Cylinder",shortName:"cyl",description:"Database storage",aliases:["db","database","cylinder"],handler:Ey},{semanticName:"Start",name:"Circle",shortName:"circle",description:"Starting point",aliases:["circ"],handler:ru},{semanticName:"Bang",name:"Bang",shortName:"bang",description:"Bang",aliases:["bang"],handler:$1},{semanticName:"Cloud",name:"Cloud",shortName:"cloud",description:"cloud",aliases:["cloud"],handler:L1},{semanticName:"Decision",name:"Diamond",shortName:"diam",description:"Decision-making step",aliases:["decision","diamond","question"],handler:r1},{semanticName:"Prepare Conditional",name:"Hexagon",shortName:"hex",description:"Preparation or condition step",aliases:["hexagon","prepare"],handler:Ny},{semanticName:"Data Input/Output",name:"Lean Right",shortName:"lean-r",description:"Represents input or output",aliases:["lean-right","in-out"],internalAliases:["lean_right"],handler:Xy},{semanticName:"Data Input/Output",name:"Lean Left",shortName:"lean-l",description:"Represents output or input",aliases:["lean-left","out-in"],internalAliases:["lean_left"],handler:Gy},{semanticName:"Priority Action",name:"Trapezoid Base Bottom",shortName:"trap-b",description:"Priority action",aliases:["priority","trapezoid-bottom","trapezoid"],handler:b1},{semanticName:"Manual Operation",name:"Trapezoid Base Top",shortName:"trap-t",description:"Represents a manual task",aliases:["manual","trapezoid-top","inv-trapezoid"],internalAliases:["inv_trapezoid"],handler:jy},{semanticName:"Stop",name:"Double Circle",shortName:"dbl-circ",description:"Represents a stop point",aliases:["double-circle"],internalAliases:["doublecircle"],handler:Py},{semanticName:"Text Block",name:"Text Block",shortName:"text",description:"Text block",handler:m1},{semanticName:"Card",name:"Notched Rectangle",shortName:"notch-rect",description:"Represents a card",aliases:["card","notched-rectangle"],handler:Cy},{semanticName:"Lined/Shaded Process",name:"Lined Rectangle",shortName:"lin-rect",description:"Lined process shape",aliases:["lined-rectangle","lined-process","lin-proc","shaded-process"],handler:s1},{semanticName:"Start",name:"Small Circle",shortName:"sm-circ",description:"Small starting point",aliases:["start","small-circle"],internalAliases:["stateStart"],handler:d1},{semanticName:"Stop",name:"Framed Circle",shortName:"fr-circ",description:"Stop point",aliases:["stop","framed-circle"],internalAliases:["stateEnd"],handler:u1},{semanticName:"Fork/Join",name:"Filled Rectangle",shortName:"fork",description:"Fork or join in process flow",aliases:["join"],internalAliases:["forkJoin"],handler:Oy},{semanticName:"Collate",name:"Hourglass",shortName:"hourglass",description:"Represents a collate operation",aliases:["hourglass","collate"],handler:zy},{semanticName:"Comment",name:"Curly Brace",shortName:"brace",description:"Adds a comment",aliases:["comment","brace-l"],handler:$y},{semanticName:"Comment Right",name:"Curly Brace",shortName:"brace-r",description:"Adds a comment",handler:Ly},{semanticName:"Comment with braces on both sides",name:"Curly Braces",shortName:"braces",description:"Adds a comment",handler:Ay},{semanticName:"Com Link",name:"Lightning Bolt",shortName:"bolt",description:"Communication link",aliases:["com-link","lightning-bolt"],handler:Zy},{semanticName:"Document",name:"Document",shortName:"doc",description:"Represents a document",aliases:["doc","document"],handler:_1},{semanticName:"Delay",name:"Half-Rounded Rectangle",shortName:"delay",description:"Represents a delay",aliases:["half-rounded-rectangle"],handler:Ry},{semanticName:"Direct Access Storage",name:"Horizontal Cylinder",shortName:"h-cyl",description:"Direct access storage",aliases:["das","horizontal-cylinder"],handler:y1},{semanticName:"Disk Storage",name:"Lined Cylinder",shortName:"lin-cyl",description:"Disk storage",aliases:["disk","lined-cylinder"],handler:Ky},{semanticName:"Display",name:"Curved Trapezoid",shortName:"curv-trap",description:"Represents a display",aliases:["curved-trapezoid","display"],handler:By},{semanticName:"Divided Process",name:"Divided Rectangle",shortName:"div-rect",description:"Divided process shape",aliases:["div-proc","divided-rectangle","divided-process"],handler:Fy},{semanticName:"Extract",name:"Triangle",shortName:"tri",description:"Extraction process",aliases:["extract","triangle"],handler:x1},{semanticName:"Internal Storage",name:"Window Pane",shortName:"win-pane",description:"Internal storage",aliases:["internal-storage","window-pane"],handler:w1},{semanticName:"Junction",name:"Filled Circle",shortName:"f-circ",description:"Junction point",aliases:["junction","filled-circle"],handler:Dy},{semanticName:"Loop Limit",name:"Trapezoidal Pentagon",shortName:"notch-pent",description:"Loop limit step",aliases:["loop-limit","notched-pentagon"],handler:v1},{semanticName:"Manual File",name:"Flipped Triangle",shortName:"flip-tri",description:"Manual file operation",aliases:["manual-file","flipped-triangle"],handler:Iy},{semanticName:"Manual Input",name:"Sloped Rectangle",shortName:"sl-rect",description:"Manual input step",aliases:["manual-input","sloped-rectangle"],handler:o1},{semanticName:"Multi-Document",name:"Stacked Document",shortName:"docs",description:"Multiple documents",aliases:["documents","st-doc","stacked-document"],handler:t1},{semanticName:"Multi-Process",name:"Stacked Rectangle",shortName:"st-rect",description:"Multiple processes",aliases:["procs","processes","stacked-rectangle"],handler:Jy},{semanticName:"Stored Data",name:"Bow Tie Rectangle",shortName:"bow-rect",description:"Stored data",aliases:["stored-data","bow-tie-rectangle"],handler:wy},{semanticName:"Summary",name:"Crossed Circle",shortName:"cross-circ",description:"Summary",aliases:["summary","crossed-circle"],handler:My},{semanticName:"Tagged Document",name:"Tagged Document",shortName:"tag-doc",description:"Tagged document",aliases:["tag-doc","tagged-document"],handler:g1},{semanticName:"Tagged Process",name:"Tagged Rectangle",shortName:"tag-rect",description:"Tagged process",aliases:["tagged-rectangle","tag-proc","tagged-process"],handler:p1},{semanticName:"Paper Tape",name:"Flag",shortName:"flag",description:"Paper tape",aliases:["paper-tape"],handler:k1},{semanticName:"Odd",name:"Odd",shortName:"odd",description:"Odd shape",internalAliases:["rect_left_inv_arrow"],handler:i1},{semanticName:"Lined Document",name:"Lined Document",shortName:"lin-doc",description:"Lined document",aliases:["lined-document"],handler:Qy}],z4=m(()=>{const t=[...Object.entries({state:h1,choice:Sy,note:e1,rectWithTitle:n1,labelRect:Yy,iconSquare:Vy,iconCircle:Wy,icon:qy,iconRounded:Hy,imageSquare:Uy,anchor:ky,kanbanItem:M1,mindmapCircle:B1,defaultMindmapNode:A1,classBox:S1,erBox:iu,requirementBox:T1}),...N4.flatMap(r=>[r.shortName,..."aliases"in r?r.aliases:[],..."internalAliases"in r?r.internalAliases:[]].map(n=>[n,r.handler]))];return Object.fromEntries(t)},"generateShapeMap"),E1=z4();function q4(e){return e in E1}m(q4,"isValidShape");var Uo=new Map;async function F1(e,t,r){let i,n;t.shape==="rect"&&(t.rx&&t.ry?t.shape="roundedRect":t.shape="squareRect");const a=t.shape?E1[t.shape]:void 0;if(!a)throw new Error(`No such shape: ${t.shape}. Please check your syntax.`);if(t.link){let o;r.config.securityLevel==="sandbox"?o="_top":t.linkTarget&&(o=t.linkTarget||"_blank"),i=e.insert("svg:a").attr("xlink:href",t.link).attr("target",o??null),n=await a(i,t,r)}else n=await a(e,t,r),i=n;return t.tooltip&&n.attr("title",t.tooltip),Uo.set(t.id,i),t.haveCallback&&i.attr("class",i.attr("class")+" clickable"),i}m(F1,"insertNode");var tR=m((e,t)=>{Uo.set(t.id,e)},"setNodeElem"),eR=m(()=>{Uo.clear()},"clear"),rR=m(e=>{const t=Uo.get(e.id);V.trace("Transforming node",e.diff,e,"translate("+(e.x-e.width/2-5)+", "+e.width/2+")");const r=8,i=e.diff||0;return e.clusterNode?t.attr("transform","translate("+(e.x+i-e.width/2)+", "+(e.y-e.height/2-r)+")"):t.attr("transform","translate("+e.x+", "+e.y+")"),i},"positionNode"),W4=m((e,t,r,i,n,a)=>{t.arrowTypeStart&&uf(e,"start",t.arrowTypeStart,r,i,n,a),t.arrowTypeEnd&&uf(e,"end",t.arrowTypeEnd,r,i,n,a)},"addEdgeMarkers"),H4={arrow_cross:{type:"cross",fill:!1},arrow_point:{type:"point",fill:!0},arrow_barb:{type:"barb",fill:!0},arrow_circle:{type:"circle",fill:!1},aggregation:{type:"aggregation",fill:!1},extension:{type:"extension",fill:!1},composition:{type:"composition",fill:!0},dependency:{type:"dependency",fill:!0},lollipop:{type:"lollipop",fill:!1},only_one:{type:"onlyOne",fill:!1},zero_or_one:{type:"zeroOrOne",fill:!1},one_or_more:{type:"oneOrMore",fill:!1},zero_or_more:{type:"zeroOrMore",fill:!1},requirement_arrow:{type:"requirement_arrow",fill:!1},requirement_contains:{type:"requirement_contains",fill:!1}},uf=m((e,t,r,i,n,a,o)=>{var u;const s=H4[r];if(!s){V.warn(`Unknown arrow type: ${r}`);return}const l=s.type,h=`${n}_${a}-${l}${t==="start"?"Start":"End"}`;if(o&&o.trim()!==""){const d=o.replace(/[^\dA-Za-z]/g,"_"),f=`${h}_${d}`;if(!document.getElementById(f)){const p=document.getElementById(h);if(p){const g=p.cloneNode(!0);g.id=f,g.querySelectorAll("path, circle, line").forEach(b=>{b.setAttribute("stroke",o),s.fill&&b.setAttribute("fill",o)}),(u=p.parentNode)==null||u.appendChild(g)}}e.attr(`marker-${t}`,`url(${i}#${f})`)}else e.attr(`marker-${t}`,`url(${i}#${h})`)},"addEdgeMarker"),oo=new Map,he=new Map,iR=m(()=>{oo.clear(),he.clear()},"clear"),Ya=m(e=>e?e.reduce((r,i)=>r+";"+i,""):"","getLabelStyles"),V4=m(async(e,t)=>{let r=ie(qt().flowchart.htmlLabels);const{labelStyles:i}=ot(t);t.labelStyle=i;const n=await Xr(e,t.label,{style:t.labelStyle,useHtmlLabels:r,addSvgBackground:!0,isNode:!1});V.info("abc82",t,t.labelType);const a=e.insert("g").attr("class","edgeLabel"),o=a.insert("g").attr("class","label").attr("data-id",t.id);o.node().appendChild(n);let s=n.getBBox();if(r){const c=n.children[0],h=Ot(n);s=c.getBoundingClientRect(),h.attr("width",s.width),h.attr("height",s.height)}o.attr("transform","translate("+-s.width/2+", "+-s.height/2+")"),oo.set(t.id,a),t.width=s.width,t.height=s.height;let l;if(t.startLabelLeft){const c=await hi(t.startLabelLeft,Ya(t.labelStyle)),h=e.insert("g").attr("class","edgeTerminals"),u=h.insert("g").attr("class","inner");l=u.node().appendChild(c);const d=c.getBBox();u.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),he.get(t.id)||he.set(t.id,{}),he.get(t.id).startLeft=h,Zn(l,t.startLabelLeft)}if(t.startLabelRight){const c=await hi(t.startLabelRight,Ya(t.labelStyle)),h=e.insert("g").attr("class","edgeTerminals"),u=h.insert("g").attr("class","inner");l=h.node().appendChild(c),u.node().appendChild(c);const d=c.getBBox();u.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),he.get(t.id)||he.set(t.id,{}),he.get(t.id).startRight=h,Zn(l,t.startLabelRight)}if(t.endLabelLeft){const c=await hi(t.endLabelLeft,Ya(t.labelStyle)),h=e.insert("g").attr("class","edgeTerminals"),u=h.insert("g").attr("class","inner");l=u.node().appendChild(c);const d=c.getBBox();u.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),h.node().appendChild(c),he.get(t.id)||he.set(t.id,{}),he.get(t.id).endLeft=h,Zn(l,t.endLabelLeft)}if(t.endLabelRight){const c=await hi(t.endLabelRight,Ya(t.labelStyle)),h=e.insert("g").attr("class","edgeTerminals"),u=h.insert("g").attr("class","inner");l=u.node().appendChild(c);const d=c.getBBox();u.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),h.node().appendChild(c),he.get(t.id)||he.set(t.id,{}),he.get(t.id).endRight=h,Zn(l,t.endLabelRight)}return n},"insertEdgeLabel");function Zn(e,t){qt().flowchart.htmlLabels&&e&&(e.style.width=t.length*9+"px",e.style.height="12px")}m(Zn,"setTerminalWidth");var U4=m((e,t)=>{V.debug("Moving label abc88 ",e.id,e.label,oo.get(e.id),t);let r=t.updatedPath?t.updatedPath:t.originalPath;const i=qt(),{subGraphTitleTotalMargin:n}=Fh(i);if(e.label){const a=oo.get(e.id);let o=e.x,s=e.y;if(r){const l=Xe.calcLabelPosition(r);V.debug("Moving label "+e.label+" from (",o,",",s,") to (",l.x,",",l.y,") abc88"),t.updatedPath&&(o=l.x,s=l.y)}a.attr("transform",`translate(${o}, ${s+n/2})`)}if(e.startLabelLeft){const a=he.get(e.id).startLeft;let o=e.x,s=e.y;if(r){const l=Xe.calcTerminalLabelPosition(e.arrowTypeStart?10:0,"start_left",r);o=l.x,s=l.y}a.attr("transform",`translate(${o}, ${s})`)}if(e.startLabelRight){const a=he.get(e.id).startRight;let o=e.x,s=e.y;if(r){const l=Xe.calcTerminalLabelPosition(e.arrowTypeStart?10:0,"start_right",r);o=l.x,s=l.y}a.attr("transform",`translate(${o}, ${s})`)}if(e.endLabelLeft){const a=he.get(e.id).endLeft;let o=e.x,s=e.y;if(r){const l=Xe.calcTerminalLabelPosition(e.arrowTypeEnd?10:0,"end_left",r);o=l.x,s=l.y}a.attr("transform",`translate(${o}, ${s})`)}if(e.endLabelRight){const a=he.get(e.id).endRight;let o=e.x,s=e.y;if(r){const l=Xe.calcTerminalLabelPosition(e.arrowTypeEnd?10:0,"end_right",r);o=l.x,s=l.y}a.attr("transform",`translate(${o}, ${s})`)}},"positionEdgeLabel"),j4=m((e,t)=>{const r=e.x,i=e.y,n=Math.abs(t.x-r),a=Math.abs(t.y-i),o=e.width/2,s=e.height/2;return n>=o||a>=s},"outsideNode"),Y4=m((e,t,r)=>{V.debug(`intersection calc abc89: + outsidePoint: ${JSON.stringify(t)} + insidePoint : ${JSON.stringify(r)} + node : x:${e.x} y:${e.y} w:${e.width} h:${e.height}`);const i=e.x,n=e.y,a=Math.abs(i-r.x),o=e.width/2;let s=r.xMath.abs(i-t.x)*l){let u=r.y{V.warn("abc88 cutPathAtIntersect",e,t);let r=[],i=e[0],n=!1;return e.forEach(a=>{if(V.info("abc88 checking point",a,t),!j4(t,a)&&!n){const o=Y4(t,i,a);V.debug("abc88 inside",a,i,o),V.debug("abc88 intersection",o,t);let s=!1;r.forEach(l=>{s=s||l.x===o.x&&l.y===o.y}),r.some(l=>l.x===o.x&&l.y===o.y)?V.warn("abc88 no intersect",o,r):r.push(o),n=!0}else V.warn("abc88 outside",a,i),i=a,n||r.push(a)}),V.debug("returning points",r),r},"cutPathAtIntersect");function P1(e){const t=[],r=[];for(let i=1;i5&&Math.abs(a.y-n.y)>5||n.y===a.y&&a.x===o.x&&Math.abs(a.x-n.x)>5&&Math.abs(a.y-o.y)>5)&&(t.push(a),r.push(i))}return{cornerPoints:t,cornerPointPositions:r}}m(P1,"extractCornerPoints");var ff=m(function(e,t,r){const i=t.x-e.x,n=t.y-e.y,a=Math.sqrt(i*i+n*n),o=r/a;return{x:t.x-o*i,y:t.y-o*n}},"findAdjacentPoint"),G4=m(function(e){const{cornerPointPositions:t}=P1(e),r=[];for(let i=0;i10&&Math.abs(a.y-n.y)>=10){V.debug("Corner point fixing",Math.abs(a.x-n.x),Math.abs(a.y-n.y));const f=5;o.x===s.x?d={x:c<0?s.x-f+u:s.x+f-u,y:h<0?s.y-u:s.y+u}:d={x:c<0?s.x-u:s.x+u,y:h<0?s.y-f+u:s.y+f-u}}else V.debug("Corner point skipping fixing",Math.abs(a.x-n.x),Math.abs(a.y-n.y));r.push(d,l)}else r.push(e[i]);return r},"fixCorners"),X4=m((e,t,r)=>{const i=e-t-r,n=2,a=2,o=n+a,s=Math.floor(i/o),l=Array(s).fill(`${n} ${a}`).join(" ");return`0 ${t} ${l} ${r}`},"generateDashArray"),Z4=m(function(e,t,r,i,n,a,o,s=!1){var L;const{handDrawnSeed:l}=qt();let c=t.points,h=!1;const u=n;var d=a;const f=[];for(const B in t.cssCompiledStyles)h0(B)||f.push(t.cssCompiledStyles[B]);V.debug("UIO intersect check",t.points,d.x,u.x),d.intersect&&u.intersect&&!s&&(c=c.slice(1,t.points.length-1),c.unshift(u.intersect(c[0])),V.debug("Last point UIO",t.start,"-->",t.end,c[c.length-1],d,d.intersect(c[c.length-1])),c.push(d.intersect(c[c.length-1])));const p=btoa(JSON.stringify(c));t.toCluster&&(V.info("to cluster abc88",r.get(t.toCluster)),c=df(t.points,r.get(t.toCluster).node),h=!0),t.fromCluster&&(V.debug("from cluster abc88",r.get(t.fromCluster),JSON.stringify(c,null,2)),c=df(c.reverse(),r.get(t.fromCluster).node).reverse(),h=!0);let g=c.filter(B=>!Number.isNaN(B.y));g=G4(g);let y=ss;switch(y=Rs,t.curve){case"linear":y=Rs;break;case"basis":y=ss;break;case"cardinal":y=pg;break;case"bumpX":y=cg;break;case"bumpY":y=hg;break;case"catmullRom":y=mg;break;case"monotoneX":y=kg;break;case"monotoneY":y=wg;break;case"natural":y=Sg;break;case"step":y=Tg;break;case"stepAfter":y=$g;break;case"stepBefore":y=Mg;break;default:y=ss}const{x:b,y:x}=j3(t),_=MA().x(b).y(x).curve(y);let w;switch(t.thickness){case"normal":w="edge-thickness-normal";break;case"thick":w="edge-thickness-thick";break;case"invisible":w="edge-thickness-invisible";break;default:w="edge-thickness-normal"}switch(t.pattern){case"solid":w+=" edge-pattern-solid";break;case"dotted":w+=" edge-pattern-dotted";break;case"dashed":w+=" edge-pattern-dashed";break;default:w+=" edge-pattern-solid"}let C,v=t.curve==="rounded"?D1(I1(g,t),5):_(g);const k=Array.isArray(t.style)?t.style:[t.style];let $=k.find(B=>B==null?void 0:B.startsWith("stroke:")),z=!1;if(t.look==="handDrawn"){const B=at.svg(e);Object.assign([],g);const F=B.path(v,{roughness:.3,seed:l});w+=" transition",C=Ot(F).select("path").attr("id",t.id).attr("class"," "+w+(t.classes?" "+t.classes:"")).attr("style",k?k.reduce((I,X)=>I+";"+X,""):"");let R=C.attr("d");C.attr("d",R),e.node().appendChild(C.node())}else{const B=f.join(";"),F=k?k.reduce((vt,Pt)=>vt+Pt+";",""):"";let R="";t.animate&&(R=" edge-animation-fast"),t.animation&&(R=" edge-animation-"+t.animation);const I=(B?B+";"+F+";":F)+";"+(k?k.reduce((vt,Pt)=>vt+";"+Pt,""):"");C=e.append("path").attr("d",v).attr("id",t.id).attr("class"," "+w+(t.classes?" "+t.classes:"")+(R??"")).attr("style",I),$=(L=I.match(/stroke:([^;]+)/))==null?void 0:L[1],z=t.animate===!0||!!t.animation||B.includes("animation");const X=C.node(),Z=typeof X.getTotalLength=="function"?X.getTotalLength():0,J=Ld[t.arrowTypeStart]||0,Tt=Ld[t.arrowTypeEnd]||0;if(t.look==="neo"&&!z){const Pt=`stroke-dasharray: ${t.pattern==="dotted"||t.pattern==="dashed"?X4(Z,J,Tt):`0 ${J} ${Z-J-Tt} ${Tt}`}; stroke-dashoffset: 0;`;C.attr("style",Pt+C.attr("style"))}}C.attr("data-edge",!0),C.attr("data-et","edge"),C.attr("data-id",t.id),C.attr("data-points",p),t.showPoints&&g.forEach(B=>{e.append("circle").style("stroke","red").style("fill","red").attr("r",1).attr("cx",B.x).attr("cy",B.y)});let W="";(qt().flowchart.arrowMarkerAbsolute||qt().state.arrowMarkerAbsolute)&&(W=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,W=W.replace(/\(/g,"\\(").replace(/\)/g,"\\)")),V.info("arrowTypeStart",t.arrowTypeStart),V.info("arrowTypeEnd",t.arrowTypeEnd),W4(C,t,W,o,i,$);const O=Math.floor(c.length/2),N=c[O];Xe.isLabelCoordinateInPath(N,C.attr("d"))||(h=!0);let D={};return h&&(D.updatedPath=c),D.originalPath=t.points,D},"insertEdge");function D1(e,t){if(e.length<2)return"";let r="";const i=e.length,n=1e-5;for(let a=0;a({...n}));if(e.length>=2&&me[t.arrowTypeStart]){const n=me[t.arrowTypeStart],a=e[0],o=e[1],{angle:s}=Ic(a,o),l=n*Math.cos(s),c=n*Math.sin(s);r[0].x=a.x+l,r[0].y=a.y+c}const i=e.length;if(i>=2&&me[t.arrowTypeEnd]){const n=me[t.arrowTypeEnd],a=e[i-1],o=e[i-2],{angle:s}=Ic(o,a),l=n*Math.cos(s),c=n*Math.sin(s);r[i-1].x=a.x-l,r[i-1].y=a.y-c}return r}m(I1,"applyMarkerOffsetsToPoints");var K4=m((e,t,r,i)=>{t.forEach(n=>{fP[n](e,r,i)})},"insertMarkers"),Q4=m((e,t,r)=>{V.trace("Making markers for ",r),e.append("defs").append("marker").attr("id",r+"_"+t+"-extensionStart").attr("class","marker extension "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),e.append("defs").append("marker").attr("id",r+"_"+t+"-extensionEnd").attr("class","marker extension "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},"extension"),J4=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-compositionStart").attr("class","marker composition "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",r+"_"+t+"-compositionEnd").attr("class","marker composition "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"composition"),tP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-aggregationStart").attr("class","marker aggregation "+t).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",r+"_"+t+"-aggregationEnd").attr("class","marker aggregation "+t).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"aggregation"),eP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-dependencyStart").attr("class","marker dependency "+t).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),e.append("defs").append("marker").attr("id",r+"_"+t+"-dependencyEnd").attr("class","marker dependency "+t).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"dependency"),rP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-lollipopStart").attr("class","marker lollipop "+t).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),e.append("defs").append("marker").attr("id",r+"_"+t+"-lollipopEnd").attr("class","marker lollipop "+t).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},"lollipop"),iP=m((e,t,r)=>{e.append("marker").attr("id",r+"_"+t+"-pointEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",8).attr("markerHeight",8).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",r+"_"+t+"-pointStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",8).attr("markerHeight",8).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"point"),nP=m((e,t,r)=>{e.append("marker").attr("id",r+"_"+t+"-circleEnd").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),e.append("marker").attr("id",r+"_"+t+"-circleStart").attr("class","marker "+t).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"circle"),aP=m((e,t,r)=>{e.append("marker").attr("id",r+"_"+t+"-crossEnd").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),e.append("marker").attr("id",r+"_"+t+"-crossStart").attr("class","marker cross "+t).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},"cross"),sP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","userSpaceOnUse").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"barb"),oP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-onlyOneStart").attr("class","marker onlyOne "+t).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("d","M9,0 L9,18 M15,0 L15,18"),e.append("defs").append("marker").attr("id",r+"_"+t+"-onlyOneEnd").attr("class","marker onlyOne "+t).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("d","M3,0 L3,18 M9,0 L9,18")},"only_one"),lP=m((e,t,r)=>{const i=e.append("defs").append("marker").attr("id",r+"_"+t+"-zeroOrOneStart").attr("class","marker zeroOrOne "+t).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto");i.append("circle").attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),i.append("path").attr("d","M9,0 L9,18");const n=e.append("defs").append("marker").attr("id",r+"_"+t+"-zeroOrOneEnd").attr("class","marker zeroOrOne "+t).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto");n.append("circle").attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),n.append("path").attr("d","M21,0 L21,18")},"zero_or_one"),cP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-oneOrMoreStart").attr("class","marker oneOrMore "+t).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),e.append("defs").append("marker").attr("id",r+"_"+t+"-oneOrMoreEnd").attr("class","marker oneOrMore "+t).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18")},"one_or_more"),hP=m((e,t,r)=>{const i=e.append("defs").append("marker").attr("id",r+"_"+t+"-zeroOrMoreStart").attr("class","marker zeroOrMore "+t).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto");i.append("circle").attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),i.append("path").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18");const n=e.append("defs").append("marker").attr("id",r+"_"+t+"-zeroOrMoreEnd").attr("class","marker zeroOrMore "+t).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto");n.append("circle").attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),n.append("path").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},"zero_or_more"),uP=m((e,t,r)=>{e.append("defs").append("marker").attr("id",r+"_"+t+"-requirement_arrowEnd").attr("refX",20).attr("refY",10).attr("markerWidth",20).attr("markerHeight",20).attr("orient","auto").append("path").attr("d",`M0,0 + L20,10 + M20,10 + L0,20`)},"requirement_arrow"),dP=m((e,t,r)=>{const i=e.append("defs").append("marker").attr("id",r+"_"+t+"-requirement_containsStart").attr("refX",0).attr("refY",10).attr("markerWidth",20).attr("markerHeight",20).attr("orient","auto").append("g");i.append("circle").attr("cx",10).attr("cy",10).attr("r",9).attr("fill","none"),i.append("line").attr("x1",1).attr("x2",19).attr("y1",10).attr("y2",10),i.append("line").attr("y1",1).attr("y2",19).attr("x1",10).attr("x2",10)},"requirement_contains"),fP={extension:Q4,composition:J4,aggregation:tP,dependency:eP,lollipop:rP,point:iP,circle:nP,cross:aP,barb:sP,only_one:oP,zero_or_one:lP,one_or_more:cP,zero_or_more:hP,requirement_arrow:uP,requirement_contains:dP},pP=K4,gP={common:yn,getConfig:be,insertCluster:w4,insertEdge:Z4,insertEdgeLabel:V4,insertMarkers:pP,insertNode:F1,interpolateToCurve:Rh,labelHelper:_t,log:V,positionEdgeLabel:U4},ga={},O1=m(e=>{for(const t of e)ga[t.name]=t},"registerLayoutLoaders"),mP=m(()=>{O1([{name:"dagre",loader:m(async()=>await Rt(()=>import("./dagre-6UL2VRFP.Er--WOqv.js"),__vite__mapDeps([2,3,4,5,6,7,1])),"loader")},{name:"cose-bilkent",loader:m(async()=>await Rt(()=>import("./cose-bilkent-S5V4N54A.CZp12JBE.js"),__vite__mapDeps([8,9,1])),"loader")}])},"registerDefaultLayoutLoaders");mP();var nR=m(async(e,t)=>{if(!(e.layoutAlgorithm in ga))throw new Error(`Unknown layout algorithm: ${e.layoutAlgorithm}`);const r=ga[e.layoutAlgorithm];return(await r.loader()).render(e,t,gP,{algorithm:r.algorithm})},"render"),aR=m((e="",{fallback:t="dagre"}={})=>{if(e in ga)return e;if(t in ga)return V.warn(`Layout algorithm ${e} is not registered. Using ${t} as fallback.`),t;throw new Error(`Both layout algorithms ${e} and ${t} are not registered.`)},"getRegisteredLayoutAlgorithm"),R1="comm",N1="rule",z1="decl",yP="@import",bP="@namespace",vP="@keyframes",xP="@layer",q1=Math.abs,nu=String.fromCharCode;function W1(e){return e.trim()}function us(e,t,r){return e.replace(t,r)}function _P(e,t,r){return e.indexOf(t,r)}function Yi(e,t){return e.charCodeAt(t)|0}function fn(e,t,r){return e.slice(t,r)}function lr(e){return e.length}function kP(e){return e.length}function Ga(e,t){return t.push(e),e}var jo=1,pn=1,H1=0,We=0,Kt=0,kn="";function au(e,t,r,i,n,a,o,s){return{value:e,root:t,parent:r,type:i,props:n,children:a,line:jo,column:pn,length:o,return:"",siblings:s}}function wP(){return Kt}function CP(){return Kt=We>0?Yi(kn,--We):0,pn--,Kt===10&&(pn=1,jo--),Kt}function Ke(){return Kt=We2||ma(Kt)>3?"":" "}function $P(e,t){for(;--t&&Ke()&&!(Kt<48||Kt>102||Kt>57&&Kt<65||Kt>70&&Kt<97););return Yo(e,ds()+(t<6&&qr()==32&&Ke()==32))}function Oc(e){for(;Ke();)switch(Kt){case e:return We;case 34:case 39:e!==34&&e!==39&&Oc(Kt);break;case 40:e===41&&Oc(e);break;case 92:Ke();break}return We}function LP(e,t){for(;Ke()&&e+Kt!==57;)if(e+Kt===84&&qr()===47)break;return"/*"+Yo(t,We-1)+"*"+nu(e===47?e:Ke())}function AP(e){for(;!ma(qr());)Ke();return Yo(e,We)}function BP(e){return TP(fs("",null,null,null,[""],e=SP(e),0,[0],e))}function fs(e,t,r,i,n,a,o,s,l){for(var c=0,h=0,u=o,d=0,f=0,p=0,g=1,y=1,b=1,x=0,_="",w=n,C=a,v=i,k=_;y;)switch(p=x,x=Ke()){case 40:if(p!=108&&Yi(k,u-1)==58){_P(k+=us(El(x),"&","&\f"),"&\f",q1(c?s[c-1]:0))!=-1&&(b=-1);break}case 34:case 39:case 91:k+=El(x);break;case 9:case 10:case 13:case 32:k+=MP(p);break;case 92:k+=$P(ds()-1,7);continue;case 47:switch(qr()){case 42:case 47:Ga(EP(LP(Ke(),ds()),t,r,l),l),(ma(p||1)==5||ma(qr()||1)==5)&&lr(k)&&fn(k,-1,void 0)!==" "&&(k+=" ");break;default:k+="/"}break;case 123*g:s[c++]=lr(k)*b;case 125*g:case 59:case 0:switch(x){case 0:case 125:y=0;case 59+h:b==-1&&(k=us(k,/\f/g,"")),f>0&&(lr(k)-u||g===0&&p===47)&&Ga(f>32?gf(k+";",i,r,u-1,l):gf(us(k," ","")+";",i,r,u-2,l),l);break;case 59:k+=";";default:if(Ga(v=pf(k,t,r,c,h,n,s,_,w=[],C=[],u,a),a),x===123)if(h===0)fs(k,t,v,v,w,a,u,s,C);else{switch(d){case 99:if(Yi(k,3)===110)break;case 108:if(Yi(k,2)===97)break;default:h=0;case 100:case 109:case 115:}h?fs(e,v,v,i&&Ga(pf(e,v,v,0,0,n,s,_,n,w=[],u,C),C),n,C,u,s,i?w:C):fs(k,v,v,v,[""],C,0,s,C)}}c=h=f=0,g=b=1,_=k="",u=o;break;case 58:u=1+lr(k),f=p;default:if(g<1){if(x==123)--g;else if(x==125&&g++==0&&CP()==125)continue}switch(k+=nu(x),x*g){case 38:b=h>0?1:(k+="\f",-1);break;case 44:s[c++]=(lr(k)-1)*b,b=1;break;case 64:qr()===45&&(k+=El(Ke())),d=qr(),h=u=lr(_=k+=AP(ds())),x++;break;case 45:p===45&&lr(k)==2&&(g=0)}}return a}function pf(e,t,r,i,n,a,o,s,l,c,h,u){for(var d=n-1,f=n===0?a:[""],p=kP(f),g=0,y=0,b=0;g0?f[x]+" "+_:us(_,/&\f/g,f[x])))&&(l[b++]=w);return au(e,t,r,n===0?N1:s,l,c,h,u)}function EP(e,t,r,i){return au(e,t,r,R1,nu(wP()),fn(e,2,-2),0,i)}function gf(e,t,r,i,n){return au(e,t,r,z1,fn(e,0,i),fn(e,i+1,-1),i,n)}function Rc(e,t){for(var r="",i=0;i/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/.test(e),"detector"),XP=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./c4Diagram-YG6GDRKO.Cxb4MoHr.js");return{diagram:t}},__vite__mapDeps([10,11,1]));return{id:V1,diagram:e}},"loader"),ZP={id:V1,detector:GP,loader:XP},KP=ZP,U1="flowchart",QP=m((e,t)=>{var r,i;return((r=t==null?void 0:t.flowchart)==null?void 0:r.defaultRenderer)==="dagre-wrapper"||((i=t==null?void 0:t.flowchart)==null?void 0:i.defaultRenderer)==="elk"?!1:/^\s*graph/.test(e)},"detector"),JP=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./flowDiagram-NV44I4VS.NRN3ub33.js");return{diagram:t}},__vite__mapDeps([12,13,14,15,1]));return{id:U1,diagram:e}},"loader"),tD={id:U1,detector:QP,loader:JP},eD=tD,j1="flowchart-v2",rD=m((e,t)=>{var r,i,n;return((r=t==null?void 0:t.flowchart)==null?void 0:r.defaultRenderer)==="dagre-d3"?!1:(((i=t==null?void 0:t.flowchart)==null?void 0:i.defaultRenderer)==="elk"&&(t.layout="elk"),/^\s*graph/.test(e)&&((n=t==null?void 0:t.flowchart)==null?void 0:n.defaultRenderer)==="dagre-wrapper"?!0:/^\s*flowchart/.test(e))},"detector"),iD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./flowDiagram-NV44I4VS.NRN3ub33.js");return{diagram:t}},__vite__mapDeps([12,13,14,15,1]));return{id:j1,diagram:e}},"loader"),nD={id:j1,detector:rD,loader:iD},aD=nD,Y1="er",sD=m(e=>/^\s*erDiagram/.test(e),"detector"),oD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./erDiagram-Q2GNP2WA.B8pTQkdS.js");return{diagram:t}},__vite__mapDeps([16,14,15,1]));return{id:Y1,diagram:e}},"loader"),lD={id:Y1,detector:sD,loader:oD},cD=lD,G1="gitGraph",hD=m(e=>/^\s*gitGraph/.test(e),"detector"),uD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./gitGraphDiagram-NY62KEGX.D-tkHlSx.js");return{diagram:t}},__vite__mapDeps([17,18,19,20,1,6,4]));return{id:G1,diagram:e}},"loader"),dD={id:G1,detector:hD,loader:uD},fD=dD,X1="gantt",pD=m(e=>/^\s*gantt/.test(e),"detector"),gD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./ganttDiagram-JELNMOA3.CLnTOziW.js");return{diagram:t}},__vite__mapDeps([21,1]));return{id:X1,diagram:e}},"loader"),mD={id:X1,detector:pD,loader:gD},yD=mD,Z1="info",bD=m(e=>/^\s*info/.test(e),"detector"),vD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./infoDiagram-WHAUD3N6.BJpHyd3M.js");return{diagram:t}},__vite__mapDeps([22,20,1,6,4]));return{id:Z1,diagram:e}},"loader"),xD={id:Z1,detector:bD,loader:vD},K1="pie",_D=m(e=>/^\s*pie/.test(e),"detector"),kD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./pieDiagram-ADFJNKIX.BdoKephD.js");return{diagram:t}},__vite__mapDeps([23,18,20,1,6,4]));return{id:K1,diagram:e}},"loader"),wD={id:K1,detector:_D,loader:kD},Q1="quadrantChart",CD=m(e=>/^\s*quadrantChart/.test(e),"detector"),SD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./quadrantDiagram-AYHSOK5B.QCp9GEfl.js");return{diagram:t}},__vite__mapDeps([24,1]));return{id:Q1,diagram:e}},"loader"),TD={id:Q1,detector:CD,loader:SD},MD=TD,J1="xychart",$D=m(e=>/^\s*xychart(-beta)?/.test(e),"detector"),LD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./xychartDiagram-PRI3JC2R.CYHK3ubw.js");return{diagram:t}},__vite__mapDeps([25,1]));return{id:J1,diagram:e}},"loader"),AD={id:J1,detector:$D,loader:LD},BD=AD,tb="requirement",ED=m(e=>/^\s*requirement(Diagram)?/.test(e),"detector"),FD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./requirementDiagram-UZGBJVZJ.CyKYuSjS.js");return{diagram:t}},__vite__mapDeps([26,14,15,1]));return{id:tb,diagram:e}},"loader"),PD={id:tb,detector:ED,loader:FD},DD=PD,eb="sequence",ID=m(e=>/^\s*sequenceDiagram/.test(e),"detector"),OD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./sequenceDiagram-WL72ISMW.D-QuC8xB.js");return{diagram:t}},__vite__mapDeps([27,11,19,1]));return{id:eb,diagram:e}},"loader"),RD={id:eb,detector:ID,loader:OD},ND=RD,rb="class",zD=m((e,t)=>{var r;return((r=t==null?void 0:t.class)==null?void 0:r.defaultRenderer)==="dagre-wrapper"?!1:/^\s*classDiagram/.test(e)},"detector"),qD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./classDiagram-2ON5EDUG.BfaWfr0K.js");return{diagram:t}},__vite__mapDeps([28,29,13,14,15,1]));return{id:rb,diagram:e}},"loader"),WD={id:rb,detector:zD,loader:qD},HD=WD,ib="classDiagram",VD=m((e,t)=>{var r;return/^\s*classDiagram/.test(e)&&((r=t==null?void 0:t.class)==null?void 0:r.defaultRenderer)==="dagre-wrapper"?!0:/^\s*classDiagram-v2/.test(e)},"detector"),UD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./classDiagram-v2-WZHVMYZB.BfaWfr0K.js");return{diagram:t}},__vite__mapDeps([30,29,13,14,15,1]));return{id:ib,diagram:e}},"loader"),jD={id:ib,detector:VD,loader:UD},YD=jD,nb="state",GD=m((e,t)=>{var r;return((r=t==null?void 0:t.state)==null?void 0:r.defaultRenderer)==="dagre-wrapper"?!1:/^\s*stateDiagram/.test(e)},"detector"),XD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./stateDiagram-FKZM4ZOC.DbXCcajc.js");return{diagram:t}},__vite__mapDeps([31,32,14,15,3,4,5,6,1]));return{id:nb,diagram:e}},"loader"),ZD={id:nb,detector:GD,loader:XD},KD=ZD,ab="stateDiagram",QD=m((e,t)=>{var r;return!!(/^\s*stateDiagram-v2/.test(e)||/^\s*stateDiagram/.test(e)&&((r=t==null?void 0:t.state)==null?void 0:r.defaultRenderer)==="dagre-wrapper")},"detector"),JD=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./stateDiagram-v2-4FDKWEC3.zkSEVl0q.js");return{diagram:t}},__vite__mapDeps([33,32,14,15,1]));return{id:ab,diagram:e}},"loader"),t6={id:ab,detector:QD,loader:JD},e6=t6,sb="journey",r6=m(e=>/^\s*journey/.test(e),"detector"),i6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./journeyDiagram-XKPGCS4Q.DIPDU-n-.js");return{diagram:t}},__vite__mapDeps([34,11,13,1]));return{id:sb,diagram:e}},"loader"),n6={id:sb,detector:r6,loader:i6},a6=n6,s6=m((e,t,r)=>{V.debug(`rendering svg for syntax error +`);const i=RA(t),n=i.append("g");i.attr("viewBox","0 0 2412 512"),cp(i,100,512,!0),n.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),n.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),n.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),n.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),n.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),n.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),n.append("text").attr("class","error-text").attr("x",1440).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in text"),n.append("text").attr("class","error-text").attr("x",1250).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text(`mermaid version ${r}`)},"draw"),ob={draw:s6},o6=ob,l6={db:{},renderer:ob,parser:{parse:m(()=>{},"parse")}},c6=l6,lb="flowchart-elk",h6=m((e,t={})=>{var r;return/^\s*flowchart-elk/.test(e)||/^\s*(flowchart|graph)/.test(e)&&((r=t==null?void 0:t.flowchart)==null?void 0:r.defaultRenderer)==="elk"?(t.layout="elk",!0):!1},"detector"),u6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./flowDiagram-NV44I4VS.NRN3ub33.js");return{diagram:t}},__vite__mapDeps([12,13,14,15,1]));return{id:lb,diagram:e}},"loader"),d6={id:lb,detector:h6,loader:u6},f6=d6,cb="timeline",p6=m(e=>/^\s*timeline/.test(e),"detector"),g6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./timeline-definition-IT6M3QCI.tSV0dAf1.js");return{diagram:t}},__vite__mapDeps([35,1]));return{id:cb,diagram:e}},"loader"),m6={id:cb,detector:p6,loader:g6},y6=m6,hb="mindmap",b6=m(e=>/^\s*mindmap/.test(e),"detector"),v6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./mindmap-definition-VGOIOE7T.BtOuNEFY.js");return{diagram:t}},__vite__mapDeps([36,14,15,1]));return{id:hb,diagram:e}},"loader"),x6={id:hb,detector:b6,loader:v6},_6=x6,ub="kanban",k6=m(e=>/^\s*kanban/.test(e),"detector"),w6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./kanban-definition-3W4ZIXB7.DFxkbzml.js");return{diagram:t}},__vite__mapDeps([37,13,1]));return{id:ub,diagram:e}},"loader"),C6={id:ub,detector:k6,loader:w6},S6=C6,db="sankey",T6=m(e=>/^\s*sankey(-beta)?/.test(e),"detector"),M6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./sankeyDiagram-TZEHDZUN.qimGZH9q.js");return{diagram:t}},__vite__mapDeps([38,1]));return{id:db,diagram:e}},"loader"),$6={id:db,detector:T6,loader:M6},L6=$6,fb="packet",A6=m(e=>/^\s*packet(-beta)?/.test(e),"detector"),B6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./diagram-S2PKOQOG.BvMBeI2b.js");return{diagram:t}},__vite__mapDeps([39,18,20,1,6,4]));return{id:fb,diagram:e}},"loader"),E6={id:fb,detector:A6,loader:B6},pb="radar",F6=m(e=>/^\s*radar-beta/.test(e),"detector"),P6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./diagram-QEK2KX5R.CRLQ07ic.js");return{diagram:t}},__vite__mapDeps([40,18,20,1,6,4]));return{id:pb,diagram:e}},"loader"),D6={id:pb,detector:F6,loader:P6},gb="block",I6=m(e=>/^\s*block(-beta)?/.test(e),"detector"),O6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./blockDiagram-VD42YOAC.BZKpDMvR.js");return{diagram:t}},__vite__mapDeps([41,13,7,3,4,1]));return{id:gb,diagram:e}},"loader"),R6={id:gb,detector:I6,loader:O6},N6=R6,mb="architecture",z6=m(e=>/^\s*architecture/.test(e),"detector"),q6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./architectureDiagram-VXUJARFQ.CaSL3V8c.js");return{diagram:t}},__vite__mapDeps([42,18,20,1,6,4,9]));return{id:mb,diagram:e}},"loader"),W6={id:mb,detector:z6,loader:q6},H6=W6,yb="treemap",V6=m(e=>/^\s*treemap/.test(e),"detector"),U6=m(async()=>{const{diagram:e}=await Rt(async()=>{const{diagram:t}=await import("./diagram-PSM6KHXK.COu_zksi.js");return{diagram:t}},__vite__mapDeps([43,15,18,20,1,6,4]));return{id:yb,diagram:e}},"loader"),j6={id:yb,detector:V6,loader:U6},kf=!1,Go=m(()=>{kf||(kf=!0,xs("error",c6,e=>e.toLowerCase().trim()==="error"),xs("---",{db:{clear:m(()=>{},"clear")},styles:{},renderer:{draw:m(()=>{},"draw")},parser:{parse:m(()=>{throw new Error("Diagrams beginning with --- are not valid. If you were trying to use a YAML front-matter, please ensure that you've correctly opened and closed the YAML front-matter with un-indented `---` blocks")},"parse")},init:m(()=>null,"init")},e=>e.toLowerCase().trimStart().startsWith("---")),Vl(f6,_6,H6),Vl(KP,S6,YD,HD,cD,yD,xD,wD,DD,ND,aD,eD,y6,fD,e6,KD,a6,MD,L6,E6,BD,N6,D6,j6))},"addDiagrams"),Y6=m(async()=>{V.debug("Loading registered diagrams");const t=(await Promise.allSettled(Object.entries(pi).map(async([r,{detector:i,loader:n}])=>{if(n)try{Gl(r)}catch{try{const{diagram:a,id:o}=await n();xs(o,a,i)}catch(a){throw V.error(`Failed to load external diagram with key ${r}. Removing from detectors.`),delete pi[r],a}}}))).filter(r=>r.status==="rejected");if(t.length>0){V.error(`Failed to load ${t.length} external diagrams`);for(const r of t)V.error(r);throw new Error(`Failed to load ${t.length} external diagrams`)}},"loadRegisteredDiagrams"),G6="graphics-document document";function bb(e,t){e.attr("role",G6),t!==""&&e.attr("aria-roledescription",t)}m(bb,"setA11yDiagramInfo");function vb(e,t,r,i){if(e.insert!==void 0){if(r){const n=`chart-desc-${i}`;e.attr("aria-describedby",n),e.insert("desc",":first-child").attr("id",n).text(r)}if(t){const n=`chart-title-${i}`;e.attr("aria-labelledby",n),e.insert("title",":first-child").attr("id",n).text(t)}}}m(vb,"addSVGa11yTitleDescription");var di,Hc=(di=class{constructor(t,r,i,n,a){this.type=t,this.text=r,this.db=i,this.parser=n,this.renderer=a}static async fromText(t,r={}){var c,h;const i=be(),n=rh(t,i);t=jE(t)+` +`;try{Gl(n)}catch{const u=mw(n);if(!u)throw new Kf(`Diagram ${n} not found.`);const{id:d,diagram:f}=await u();xs(d,f)}const{db:a,parser:o,renderer:s,init:l}=Gl(n);return o.parser&&(o.parser.yy=a),(c=a.clear)==null||c.call(a),l==null||l(i),r.title&&((h=a.setDiagramTitle)==null||h.call(a,r.title)),await o.parse(t),new di(n,t,a,o,s)}async render(t,r){await this.renderer.draw(this.text,t,r,this)}getParser(){return this.parser}getType(){return this.type}},m(di,"Diagram"),di),wf=[],X6=m(()=>{wf.forEach(e=>{e()}),wf=[]},"attachFunctions"),Z6=m(e=>e.replace(/^\s*%%(?!{)[^\n]+\n?/gm,"").trimStart(),"cleanupComments");function xb(e){const t=e.match(Zf);if(!t)return{text:e,metadata:{}};let r=U3(t[1],{schema:V3})??{};r=typeof r=="object"&&!Array.isArray(r)?r:{};const i={};return r.displayMode&&(i.displayMode=r.displayMode.toString()),r.title&&(i.title=r.title.toString()),r.config&&(i.config=r.config),{text:e.slice(t[0].length),metadata:i}}m(xb,"extractFrontMatter");var K6=m(e=>e.replace(/\r\n?/g,` +`).replace(/<(\w+)([^>]*)>/g,(t,r,i)=>"<"+r+i.replace(/="([^"]*)"/g,"='$1'")+">"),"cleanupText"),Q6=m(e=>{const{text:t,metadata:r}=xb(e),{displayMode:i,title:n,config:a={}}=r;return i&&(a.gantt||(a.gantt={}),a.gantt.displayMode=i),{title:n,config:a,text:t}},"processFrontmatter"),J6=m(e=>{const t=Xe.detectInit(e)??{},r=Xe.detectDirective(e,"wrap");return Array.isArray(r)?t.wrap=r.some(({type:i})=>i==="wrap"):(r==null?void 0:r.type)==="wrap"&&(t.wrap=!0),{text:FE(e),directive:t}},"processDirectives");function su(e){const t=K6(e),r=Q6(t),i=J6(r.text),n=Hh(r.config,i.directive);return e=Z6(i.text),{code:e,title:r.title,config:n}}m(su,"preprocessDiagram");function _b(e){const t=new TextEncoder().encode(e),r=Array.from(t,i=>String.fromCodePoint(i)).join("");return btoa(r)}m(_b,"toBase64");var t8=5e4,e8="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa",r8="sandbox",i8="loose",n8="http://www.w3.org/2000/svg",a8="http://www.w3.org/1999/xlink",s8="http://www.w3.org/1999/xhtml",o8="100%",l8="100%",c8="border:0;margin:0;",h8="margin:0",u8="allow-top-navigation-by-user-activation allow-popups",d8='The "iframe" tag is not supported by your browser.',f8=["foreignobject"],p8=["dominant-baseline"];function ou(e){const t=su(e);return bs(),Ew(t.config??{}),t}m(ou,"processAndSetConfigs");async function kb(e,t){Go();try{const{code:r,config:i}=ou(e);return{diagramType:(await Cb(r)).type,config:i}}catch(r){if(t!=null&&t.suppressErrors)return!1;throw r}}m(kb,"parse");var Cf=m((e,t,r=[])=>` +.${e} ${t} { ${r.join(" !important; ")} !important; }`,"cssImportantStyles"),g8=m((e,t=new Map)=>{var i;let r="";if(e.themeCSS!==void 0&&(r+=` +${e.themeCSS}`),e.fontFamily!==void 0&&(r+=` +:root { --mermaid-font-family: ${e.fontFamily}}`),e.altFontFamily!==void 0&&(r+=` +:root { --mermaid-alt-font-family: ${e.altFontFamily}}`),t instanceof Map){const s=e.htmlLabels??((i=e.flowchart)==null?void 0:i.htmlLabels)?["> *","span"]:["rect","polygon","ellipse","circle","path"];t.forEach(l=>{_f(l.styles)||s.forEach(c=>{r+=Cf(l.id,c,l.styles)}),_f(l.textStyles)||(r+=Cf(l.id,"tspan",((l==null?void 0:l.textStyles)||[]).map(c=>c.replace("color","fill"))))})}return r},"createCssStyles"),m8=m((e,t,r,i)=>{const n=g8(e,r),a=Qw(t,n,e.themeVariables);return Rc(BP(`${i}{${a}}`),FP)},"createUserStyles"),y8=m((e="",t,r)=>{let i=e;return!r&&!t&&(i=i.replace(/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,'marker-end="url(#')),i=Ai(i),i=i.replace(/
    /g,"
    "),i},"cleanUpSvgCode"),b8=m((e="",t)=>{var n,a;const r=(a=(n=t==null?void 0:t.viewBox)==null?void 0:n.baseVal)!=null&&a.height?t.viewBox.baseVal.height+"px":l8,i=_b(`${e}`);return``},"putIntoIFrame"),Sf=m((e,t,r,i,n)=>{const a=e.append("div");a.attr("id",r),i&&a.attr("style",i);const o=a.append("svg").attr("id",t).attr("width","100%").attr("xmlns",n8);return n&&o.attr("xmlns:xlink",n),o.append("g"),e},"appendDivSvgG");function Vc(e,t){return e.append("iframe").attr("id",t).attr("style","width: 100%; height: 100%;").attr("sandbox","")}m(Vc,"sandboxedIframe");var v8=m((e,t,r,i)=>{var n,a,o;(n=e.getElementById(t))==null||n.remove(),(a=e.getElementById(r))==null||a.remove(),(o=e.getElementById(i))==null||o.remove()},"removeExistingElements"),x8=m(async function(e,t,r){var N,D,L,B,F,R;Go();const i=ou(t);t=i.code;const n=be();V.debug(n),t.length>((n==null?void 0:n.maxTextSize)??t8)&&(t=e8);const a="#"+e,o="i"+e,s="#"+o,l="d"+e,c="#"+l,h=m(()=>{const X=Ot(d?s:c).node();X&&"remove"in X&&X.remove()},"removeTempElements");let u=Ot("body");const d=n.securityLevel===r8,f=n.securityLevel===i8,p=n.fontFamily;if(r!==void 0){if(r&&(r.innerHTML=""),d){const I=Vc(Ot(r),o);u=Ot(I.nodes()[0].contentDocument.body),u.node().style.margin=0}else u=Ot(r);Sf(u,e,l,`font-family: ${p}`,a8)}else{if(v8(document,e,l,o),d){const I=Vc(Ot("body"),o);u=Ot(I.nodes()[0].contentDocument.body),u.node().style.margin=0}else u=Ot("body");Sf(u,e,l)}let g,y;try{g=await Hc.fromText(t,{title:i.title})}catch(I){if(n.suppressErrorRendering)throw h(),I;g=await Hc.fromText("error"),y=I}const b=u.select(c).node(),x=g.type,_=b.firstChild,w=_.firstChild,C=(D=(N=g.renderer).getClasses)==null?void 0:D.call(N,t,g),v=m8(n,x,C,a),k=document.createElement("style");k.innerHTML=v,_.insertBefore(k,w);try{await g.renderer.draw(t,e,Su.version,g)}catch(I){throw n.suppressErrorRendering?h():o6.draw(t,e,Su.version),I}const $=u.select(`${c} svg`),z=(B=(L=g.db).getAccTitle)==null?void 0:B.call(L),W=(R=(F=g.db).getAccDescription)==null?void 0:R.call(F);Sb(x,$,z,W),u.select(`[id="${e}"]`).selectAll("foreignobject > *").attr("xmlns",s8);let O=u.select(c).node().innerHTML;if(V.debug("config.arrowMarkerAbsolute",n.arrowMarkerAbsolute),O=y8(O,d,ie(n.arrowMarkerAbsolute)),d){const I=u.select(c+" svg").node();O=b8(O,I)}else f||(O=rn.sanitize(O,{ADD_TAGS:f8,ADD_ATTR:p8,HTML_INTEGRATION_POINTS:{foreignobject:!0}}));if(X6(),y)throw y;return h(),{diagramType:x,svg:O,bindFunctions:g.db.bindFunctions}},"render");function wb(e={}){var i;const t=ee({},e);t!=null&&t.fontFamily&&!((i=t.themeVariables)!=null&&i.fontFamily)&&(t.themeVariables||(t.themeVariables={}),t.themeVariables.fontFamily=t.fontFamily),Aw(t),t!=null&&t.theme&&t.theme in $r?t.themeVariables=$r[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=$r.default.getThemeVariables(t.themeVariables));const r=typeof t=="object"?Lw(t):rp();eh(r.logLevel),Go()}m(wb,"initialize");var Cb=m((e,t={})=>{const{code:r}=su(e);return Hc.fromText(r,t)},"getDiagramFromText");function Sb(e,t,r,i){bb(t,e),vb(t,r,i,t.attr("id"))}m(Sb,"addA11yInfo");var ki=Object.freeze({render:x8,parse:kb,getDiagramFromText:Cb,initialize:wb,getConfig:be,setConfig:ip,getSiteConfig:rp,updateSiteConfig:Bw,reset:m(()=>{bs()},"reset"),globalReset:m(()=>{bs(nn)},"globalReset"),defaultConfig:nn});eh(be().logLevel);bs(be());var _8=m((e,t,r)=>{V.warn(e),Wh(e)?(r&&r(e.str,e.hash),t.push({...e,message:e.str,error:e})):(r&&r(e),e instanceof Error&&t.push({str:e.message,message:e.message,hash:e.name,error:e}))},"handleError"),Tb=m(async function(e={querySelector:".mermaid"}){try{await k8(e)}catch(t){if(Wh(t)&&V.error(t.str),De.parseError&&De.parseError(t),!e.suppressErrors)throw V.error("Use the suppressErrors option to suppress these errors"),t}},"run"),k8=m(async function({postRenderCallback:e,querySelector:t,nodes:r}={querySelector:".mermaid"}){const i=ki.getConfig();V.debug(`${e?"":"No "}Callback function found`);let n;if(r)n=r;else if(t)n=document.querySelectorAll(t);else throw new Error("Nodes and querySelector are both undefined");V.debug(`Found ${n.length} diagrams`),(i==null?void 0:i.startOnLoad)!==void 0&&(V.debug("Start On Load: "+(i==null?void 0:i.startOnLoad)),ki.updateSiteConfig({startOnLoad:i==null?void 0:i.startOnLoad}));const a=new Xe.InitIDGenerator(i.deterministicIds,i.deterministicIDSeed);let o;const s=[];for(const l of Array.from(n)){if(V.info("Rendering diagram: "+l.id),l.getAttribute("data-processed"))continue;l.setAttribute("data-processed","true");const c=`mermaid-${a.next()}`;o=l.innerHTML,o=Y0(Xe.entityDecode(o)).trim().replace(//gi,"
    ");const h=Xe.detectInit(o);h&&V.debug("Detected early reinit: ",h);try{const{svg:u,bindFunctions:d}=await Ab(c,o,l);l.innerHTML=u,e&&await e(c),d&&d(l)}catch(u){_8(u,s,De.parseError)}}if(s.length>0)throw s[0]},"runThrowsErrors"),Mb=m(function(e){ki.initialize(e)},"initialize"),w8=m(async function(e,t,r){V.warn("mermaid.init is deprecated. Please use run instead."),e&&Mb(e);const i={postRenderCallback:r,querySelector:".mermaid"};typeof t=="string"?i.querySelector=t:t&&(t instanceof HTMLElement?i.nodes=[t]:i.nodes=t),await Tb(i)},"init"),C8=m(async(e,{lazyLoad:t=!0}={})=>{Go(),Vl(...e),t===!1&&await Y6()},"registerExternalDiagrams"),$b=m(function(){if(De.startOnLoad){const{startOnLoad:e}=ki.getConfig();e&&De.run().catch(t=>V.error("Mermaid failed to initialize",t))}},"contentLoaded");typeof document<"u"&&window.addEventListener("load",$b,!1);var S8=m(function(e){De.parseError=e},"setParseErrorHandler"),lo=[],Fl=!1,Lb=m(async()=>{if(!Fl){for(Fl=!0;lo.length>0;){const e=lo.shift();if(e)try{await e()}catch(t){V.error("Error executing queue",t)}}Fl=!1}},"executeQueue"),T8=m(async(e,t)=>new Promise((r,i)=>{const n=m(()=>new Promise((a,o)=>{ki.parse(e,t).then(s=>{a(s),r(s)},s=>{var l;V.error("Error parsing",s),(l=De.parseError)==null||l.call(De,s),o(s),i(s)})}),"performCall");lo.push(n),Lb().catch(i)}),"parse"),Ab=m((e,t,r)=>new Promise((i,n)=>{const a=m(()=>new Promise((o,s)=>{ki.render(e,t,r).then(l=>{o(l),i(l)},l=>{var c;V.error("Error parsing",l),(c=De.parseError)==null||c.call(De,l),s(l),n(l)})}),"performCall");lo.push(a),Lb().catch(n)}),"render"),M8=m(()=>Object.keys(pi).map(e=>({id:e})),"getRegisteredDiagramsMetadata"),De={startOnLoad:!0,mermaidAPI:ki,parse:T8,render:Ab,init:w8,run:Tb,registerExternalDiagrams:C8,registerLayoutLoaders:O1,initialize:Mb,parseError:void 0,contentLoaded:$b,setParseErrorHandler:S8,detectType:rh,registerIconPacks:ZF,getRegisteredDiagramsMetadata:M8},Tf=De;/*! Check if previously processed *//*! + * Wait for document loaded before starting the execution + */const $8={key:1,class:"zoom-level"},L8={key:0,class:"copied-notification"},A8={class:"mobile-utility-controls"},B8={key:1,class:"zoom-level mobile-zoom-level"},E8={key:0,class:"copied-notification"},F8=nt({__name:"MermaidControls",props:{scale:{},code:{},isFullscreen:{type:Boolean},toolbar:{}},emits:["zoomIn","zoomOut","resetView","toggleFullscreen","panUp","panDown","panLeft","panRight","download"],setup(e,{expose:t,emit:r}){const i=e,n=()=>i.isFullscreen?i.toolbar.fullscreen:i.toolbar.desktop,a=()=>i.isFullscreen?i.toolbar.fullscreen:i.toolbar.mobile,o=k=>n().buttons[k]==="enabled",s=k=>a().buttons[k]==="enabled",l=r,c=tt(null),h=tt(null),u=tt(!1),d=k=>[`toolbar-vertical-${k.vertical}`,`toolbar-horizontal-${k.horizontal}`],f=rt(()=>{const k=n().positions;return d(k)}),p=rt(()=>{const k=a().positions;return d(k)}),g=k=>Object.values(k).some($=>$==="enabled"),y=rt(()=>n().zoomLevel==="enabled"),b=rt(()=>a().zoomLevel==="enabled"),x=rt(()=>{const k=n();return g(k.buttons)||y.value}),_=rt(()=>{const k=a();return g(k.buttons)||b.value}),w=async()=>{try{if(!navigator.clipboard)throw new Error("Clipboard API not available in this browser.");await navigator.clipboard.writeText(i.code),u.value=!0,setTimeout(()=>{u.value=!1},1e3)}catch(k){console.error("Failed to copy diagram code:",k),alert("Failed to copy to clipboard. Your browser might not support this feature.")}},C=()=>{l("download",i.toolbar.downloadFormat)},v=()=>{try{i.isFullscreen?(c.value&&c.value.classList.add("force-show"),h.value&&h.value.classList.add("force-show")):(c.value&&c.value.classList.remove("force-show"),h.value&&h.value.classList.remove("force-show"))}catch(k){console.error("Error updating fullscreen controls:",k)}};return _e(()=>{c.value&&(c.value.style.opacity="1",c.value.style.visibility="visible"),h.value&&(h.value.style.opacity="1",h.value.style.visibility="visible")}),t({updateFullscreenControls:v}),(k,$)=>(S(),A("div",null,[x.value?(S(),A("div",{key:0,class:At(["desktop-controls controls visible-controls",f.value]),ref_key:"controls",ref:c},[o("zoomIn")?(S(),A("button",{key:0,onClick:$[0]||($[0]=z=>k.$emit("zoomIn")),title:"Zoom In"},[...$[8]||($[8]=[ps('',1)])])):U("",!0),y.value?(S(),A("span",$8,yt(Math.round(e.scale*100))+"% ",1)):U("",!0),o("zoomOut")?(S(),A("button",{key:2,onClick:$[1]||($[1]=z=>k.$emit("zoomOut")),title:"Zoom Out"},[...$[9]||($[9]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("circle",{cx:"11",cy:"11",r:"8"}),T("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"}),T("line",{x1:"8",y1:"11",x2:"14",y2:"11"})],-1)])])):U("",!0),o("resetView")?(S(),A("button",{key:3,onClick:$[2]||($[2]=z=>k.$emit("resetView")),title:"Reset View"},[...$[10]||($[10]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M3 12a9 9 0 0 1 9-9 9 9 0 0 1 6.9 3.2L21 8"}),T("path",{d:"M21 12a9 9 0 0 1-9 9 9 9 0 0 1-6.9-3.2L3 16"})],-1)])])):U("",!0),o("copyCode")?(S(),A("button",{key:4,onClick:w,title:"Copy Code"},[$[11]||($[11]=T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),T("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})],-1)),u.value?(S(),A("span",L8,"Copied")):U("",!0)])):U("",!0),o("download")?(S(),A("button",{key:5,onClick:C,title:"Download Diagram"},[...$[12]||($[12]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}),T("polyline",{points:"7 10 12 15 17 10"}),T("line",{x1:"12",y1:"15",x2:"12",y2:"3"})],-1)])])):U("",!0),o("toggleFullscreen")?(S(),A("button",{key:6,onClick:$[3]||($[3]=z=>k.$emit("toggleFullscreen")),title:"Toggle Fullscreen"},[...$[13]||($[13]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"})],-1)])])):U("",!0)],2)):U("",!0),_.value?(S(),A("div",{key:1,class:At(["mobile-controls controls visible-controls",p.value]),ref_key:"mobileControls",ref:h},[T("div",A8,[s("zoomIn")?(S(),A("button",{key:0,onClick:$[4]||($[4]=z=>k.$emit("zoomIn")),title:"Zoom In"},[...$[14]||($[14]=[ps('',1)])])):U("",!0),b.value?(S(),A("span",B8,yt(Math.round(e.scale*100))+"% ",1)):U("",!0),s("zoomOut")?(S(),A("button",{key:2,onClick:$[5]||($[5]=z=>k.$emit("zoomOut")),title:"Zoom Out"},[...$[15]||($[15]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("circle",{cx:"11",cy:"11",r:"8"}),T("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"}),T("line",{x1:"8",y1:"11",x2:"14",y2:"11"})],-1)])])):U("",!0),s("resetView")?(S(),A("button",{key:3,onClick:$[6]||($[6]=z=>k.$emit("resetView")),title:"Reset View"},[...$[16]||($[16]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M3 12a9 9 0 0 1 9-9 9 9 0 0 1 6.9 3.2L21 8"}),T("path",{d:"M21 12a9 9 0 0 1-9 9 9 9 0 0 1-6.9-3.2L3 16"})],-1)])])):U("",!0),s("copyCode")?(S(),A("button",{key:4,onClick:w,title:"Copy Code"},[$[17]||($[17]=T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),T("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})],-1)),u.value?(S(),A("span",E8,"Copied")):U("",!0)])):U("",!0),s("download")?(S(),A("button",{key:5,onClick:C,title:"Download Diagram"},[...$[18]||($[18]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}),T("polyline",{points:"7 10 12 15 17 10"}),T("line",{x1:"12",y1:"15",x2:"12",y2:"3"})],-1)])])):U("",!0),s("toggleFullscreen")?(S(),A("button",{key:6,onClick:$[7]||($[7]=z=>k.$emit("toggleFullscreen")),title:"Toggle Fullscreen"},[...$[19]||($[19]=[T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("path",{d:"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"})],-1)])])):U("",!0)])],2)):U("",!0)]))}}),P8={key:0,class:"diagram-error"},D8={class:"error-message"},I8={key:0,class:"error-details"},O8=nt({__name:"MermaidError",props:{renderError:{type:Boolean},renderErrorDetails:{}},setup(e){const t=tt(!1),r=()=>{t.value=!t.value};return(i,n)=>e.renderError?(S(),A("div",P8,[T("div",D8,[n[0]||(n[0]=T("svg",{xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor"},[T("circle",{cx:"12",cy:"12",r:"10"}),T("line",{x1:"12",y1:"8",x2:"12",y2:"12"}),T("line",{x1:"12",y1:"16",x2:"12.01",y2:"16"})],-1)),n[1]||(n[1]=T("span",null,"Failed to render diagram",-1)),T("button",{onClick:r,class:"error-toggle-button"},yt(t.value?"Hide Details":"Show Details"),1)]),t.value?(S(),A("pre",I8,yt(e.renderErrorDetails),1)):U("",!0)])):U("",!0)}}),R8=(e,t)=>{const r=e.__vccOpts||e;for(const[i,n]of t)r[i]=n;return r},N8=R8(O8,[["__scopeId","data-v-41babc14"]]);function z8(){const e=tt(1),t=tt(0),r=tt(0),i=tt(!1),n=tt(!1),a=tt(0),o=tt(0),s=tt(0),l=tt(!1),c=tt(0),h=tt(0),u=50;return{scale:e,translateX:t,translateY:r,isPanning:i,isFullscreen:n,zoomIn:()=>{e.value=e.value*1.2},zoomOut:()=>{e.value>.2&&(e.value=e.value/1.2)},resetView:()=>{e.value=1,t.value=0,r.value=0},toggleFullscreen:d=>{try{if(document.fullscreenElement)document.exitFullscreen?document.exitFullscreen():document.webkitExitFullscreen?document.webkitExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen(),n.value=!1;else{if(d!=null&&d.requestFullscreen)d.requestFullscreen();else if(d!=null&&d.webkitRequestFullscreen)d.webkitRequestFullscreen();else if(d!=null&&d.mozRequestFullScreen)d.mozRequestFullScreen();else if(d!=null&&d.msRequestFullscreen)d.msRequestFullscreen();else throw new Error("Fullscreen API not available");n.value=!0}}catch(f){console.error("Fullscreen error:",f),alert("Fullscreen mode is not supported in this browser.")}},startPan:d=>{i.value=!0,a.value=d.clientX,o.value=d.clientY},pan:d=>{if(!i.value)return;const f=d.clientX-a.value,p=d.clientY-o.value;t.value+=f/e.value,r.value+=p/e.value,a.value=d.clientX,o.value=d.clientY},endPan:()=>{i.value=!1},handleWheel:d=>{if(!(d.ctrlKey||n.value))return;d.preventDefault();const f=-Math.sign(d.deltaY)*.1,p=e.value*(1+f);p>=.2&&p<=10&&(e.value=p)},handleTouchStart:d=>{if(d.touches.length===1)l.value=!0,c.value=d.touches[0].clientX,h.value=d.touches[0].clientY;else if(d.touches.length===2){l.value=!1;const f=d.touches[0],p=d.touches[1];s.value=Math.hypot(p.clientX-f.clientX,p.clientY-f.clientY)}},handleTouchMove:d=>{if(d.preventDefault(),l.value&&d.touches.length===1){const f=d.touches[0],p=f.clientX-c.value,g=f.clientY-h.value;t.value+=p/e.value,r.value+=g/e.value,c.value=f.clientX,h.value=f.clientY}else if(d.touches.length===2){const f=d.touches[0],p=d.touches[1],g=Math.hypot(p.clientX-f.clientX,p.clientY-f.clientY);if(s.value>0){const y=g/s.value,b=e.value*(1+(y-1)*.2);b>=.2&&b<=10&&(e.value=b),s.value=g}}},handleTouchEnd:()=>{l.value=!1,s.value=0},panUp:()=>{r.value-=u/e.value},panDown:()=>{r.value+=u/e.value},panLeft:()=>{t.value-=u/e.value},panRight:()=>{t.value+=u/e.value},updateFullscreenControls:d=>{try{document.fullscreenElement?(n.value=!0,d.controls&&d.controls.classList.add("force-show"),d.mobileControls&&d.mobileControls.classList.add("force-show")):(n.value=!1,d.controls&&d.controls.classList.remove("force-show"),d.mobileControls&&d.mobileControls.classList.remove("force-show"))}catch(f){console.error("Error updating fullscreen controls:",f)}}}}let Mf=Promise.resolve();const q8=e=>{const t=Mf.catch(()=>{}).then(()=>e());return Mf=t.catch(()=>{}),t};function W8(e={}){const t=tt(!1),r=tt(!1),i=tt(!1),n=tt(""),a=tt({width:0,height:0}),o=tt(null),s={theme:"default",securityLevel:"loose",startOnLoad:!1,flowchart:{useMaxWidth:!1,htmlLabels:!0},sequence:{diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,mirrorActors:!0,bottomMarginAdj:1,useMaxWidth:!1,rightAngles:!1,showSequenceNumbers:!1},gantt:{useMaxWidth:!1,topPadding:50,leftPadding:50,rightPadding:50,gridLineStartPadding:35,barHeight:50,barGap:40,displayMode:"compact",axisFormat:"%Y-%m-%d",topAxis:!1,tickInterval:"day",useWidth:2048},class:{arrowMarkerAbsolute:!1,useMaxWidth:!1},journey:{useMaxWidth:!1},pie:{},c4:{useMaxWidth:!1,diagramMarginX:20,diagramMarginY:20},gitGraph:{useMaxWidth:!1,rotateCommitLabel:!1,showBranches:!0,showCommitLabel:!0,mainBranchName:"main"}},l=d=>{const f={...s,...e.config,...d};Tf.initialize({...f})},c=d=>{l(d.detail);const f=o.value;f&&(r.value=!1,mn(()=>{u(f.id,f.code)}))},h=d=>{const f=d.trim().toLowerCase();return f.startsWith("c4context")||f.startsWith("c4container")||f.startsWith("c4component")||f.startsWith("c4dynamic")||f.startsWith("c4deployment")?"c4":f.startsWith("gitgraph")||f.includes("gitgraph:")?"gitgraph":f.startsWith("flowchart")||f.startsWith("graph")?"flowchart":f.startsWith("sequencediagram")||f.startsWith("sequenceDiagram")?"sequence":f.startsWith("gantt")?"gantt":"unknown"},u=async(d,f,p=0,g=3)=>{var y;try{let b=document.getElementById(d);if(!b){if(console.warn(`[Mermaid] Diagram container element not found, attempt ${p+1}/${g+1}`),psetTimeout(_,x)),u(d,f,p+1,g)}throw new Error("Failed to find diagram container element")}o.value={id:d,code:f},b.textContent=f,b.removeAttribute("data-processed"),i.value=!1,n.value="",r.value=!1,b.classList.add("mermaid-rendering"),await q8(async()=>{var _,w,C;const x=typeof window<"u";try{if(await Tf.run({nodes:[b],suppressErrors:!1}),await new Promise(v=>setTimeout(v,x?150:50)),b.firstElementChild){const v=b.querySelector("svg");if(v){if(await new Promise(k=>setTimeout(k,x?150:50)),(_=b.parentElement)==null?void 0:_.querySelector(".diagram-wrapper")){const k=h(f);if(b.classList.add(`mermaid-${k}`),k==="c4"||k==="gitgraph"){if(v.style.width="100%",v.style.height="auto",v.style.maxWidth="100%",v.style.display="block",v.removeAttribute("width"),v.removeAttribute("height"),!v.getAttribute("viewBox"))try{const $=v.getBBox();$.width&&$.height&&(v.setAttribute("viewBox",`0 0 ${$.width} ${$.height}`),v.setAttribute("preserveAspectRatio","xMidYMid meet"))}catch($){console.warn("Could not set viewBox for diagram:",$)}v.style.display="none",v.offsetHeight,v.style.display="block"}}a.value={width:v.getBoundingClientRect().width,height:v.getBoundingClientRect().height}}}r.value=!0,i.value=!1,(w=e.onRenderComplete)==null||w.call(e,{id:d,success:!0})}catch(v){console.error("Failed to render mermaid diagram:",v),i.value=!0,n.value=v instanceof Error?v.toString():"Unknown error rendering diagram",r.value=!0,(C=e.onRenderComplete)==null||C.call(e,{id:d,success:!1,error:v}),x&&p===0&&setTimeout(()=>{u(d,f,p+1,g)},1e3)}finally{b.classList.remove("mermaid-rendering")}})}catch(b){console.error("Error in diagram initialization:",b),i.value=!0,n.value=b instanceof Error?b.toString():"Unknown error initializing component",(y=e.onRenderComplete)==null||y.call(e,{id:d,success:!1,error:b})}};return _e(()=>{t.value=!0,l(),document.addEventListener("vitepress-mermaid:config-updated",c)}),gn(()=>{document.removeEventListener("vitepress-mermaid:config-updated",c)}),{mounted:t,isRendered:r,renderError:i,renderErrorDetails:n,originalDiagramSize:a,renderMermaidDiagram:u,detectDiagramType:h}}const H8=e=>{const t=e;return!!(t&&t.desktop&&typeof t.desktop=="object"&&"buttons"in t.desktop&&"positions"in t.desktop&&"zoomLevel"in t.desktop&&typeof t.showLanguageLabel=="boolean"&&typeof t.downloadFormat=="string")},qn={desktop:{buttons:{zoomIn:"enabled",zoomOut:"enabled",resetView:"enabled",copyCode:"enabled",toggleFullscreen:"enabled",download:"disabled"},positions:{vertical:"bottom",horizontal:"right"},zoomLevel:"enabled"},mobile:{buttons:{zoomIn:"disabled",zoomOut:"disabled",resetView:"enabled",copyCode:"enabled",toggleFullscreen:"enabled",download:"disabled"},positions:{vertical:"bottom",horizontal:"right"},zoomLevel:"enabled"},fullscreen:{buttons:{zoomIn:"disabled",zoomOut:"disabled",resetView:"disabled",copyCode:"disabled",toggleFullscreen:"enabled",download:"disabled"},positions:{vertical:"bottom",horizontal:"right"},zoomLevel:"enabled"},showLanguageLabel:!0,downloadFormat:"svg"},V8=(e,t)=>({vertical:(t==null?void 0:t.vertical)??e.vertical,horizontal:(t==null?void 0:t.horizontal)??e.horizontal}),Bb=e=>e==="enabled"||e==="disabled",U8=(e,t)=>{if(!t)return{...e};const r={...e};return Object.keys(t).forEach(i=>{if(i==="positions"||i==="zoomLevel")return;const n=i,a=t[n];Bb(a)&&(r[n]=a)}),r},Pl=(e,t)=>({buttons:U8(e.buttons,t),positions:V8(e.positions,t==null?void 0:t.positions),zoomLevel:t!=null&&t.zoomLevel&&Bb(t.zoomLevel)?t.zoomLevel:e.zoomLevel}),Uc=e=>{const t=(e==null?void 0:e.showLanguageLabel)??qn.showLanguageLabel,r=(e==null?void 0:e.downloadFormat)??qn.downloadFormat;return{desktop:Pl(qn.desktop,e==null?void 0:e.desktop),mobile:Pl(qn.mobile,e==null?void 0:e.mobile),fullscreen:Pl(qn.fullscreen,e==null?void 0:e.fullscreen),showLanguageLabel:t,downloadFormat:r}},j8=nt({__name:"MermaidDiagram",props:{code:{},config:{},toolbar:{}},emits:["renderComplete"],setup(e,{emit:t}){var ht;const r=t,i=e,n=rt(()=>i.toolbar&&H8(i.toolbar)?i.toolbar:Uc(i.toolbar)),a=z8(),o=W8({config:i.config,onRenderComplete:q=>r("renderComplete",q)}),{scale:s,translateX:l,translateY:c,isPanning:h,isFullscreen:u,zoomIn:d,zoomOut:f,resetView:p,toggleFullscreen:g,startPan:y,pan:b,endPan:x,handleWheel:_,handleTouchStart:w,handleTouchMove:C,handleTouchEnd:v,panUp:k,panDown:$,panLeft:z,panRight:W,updateFullscreenControls:O}=a,{mounted:N,isRendered:D,renderError:L,renderErrorDetails:B,renderMermaidDiagram:F}=o,R=tt(null),I=tt(null),X=`mermaid-${((ht=rv())==null?void 0:ht.uid)??Math.random().toString(36).slice(2)}`,Z=()=>{g(I.value)},J=async q=>{var Qr;const Dt=(Qr=document.getElementById(X))==null?void 0:Qr.querySelector("svg");if(!Dt){console.error("SVG element not found for download");return}const $t=Dt.cloneNode(!0);q!=="svg"&&($t.style.backgroundColor="white");const Yt=new XMLSerializer().serializeToString($t),Qt=new Blob([Yt],{type:"image/svg+xml;charset=utf-8"}),oe=URL.createObjectURL(Qt),Jt=document.createElement("a"),Ei="diagram";if(Jt.download=`${Ei}.${q}`,q==="svg")Jt.href=oe,document.body.appendChild(Jt),Jt.click(),document.body.removeChild(Jt),URL.revokeObjectURL(oe);else{const Jr=new Image;Jr.onload=()=>{const yr=document.createElement("canvas"),Or=Dt.viewBox.baseVal;let Rr=Or==null?void 0:Or.width,tr=Or==null?void 0:Or.height;if(!Rr||!tr){const we=Dt.getBoundingClientRect();Rr=we.width,tr=we.height}yr.width=Rr,yr.height=tr;const He=yr.getContext("2d");if(He){He.fillStyle="white",He.fillRect(0,0,Rr,tr),He.drawImage(Jr,0,0);const we=q==="png"?"image/png":"image/jpeg",wn=yr.toDataURL(we);Jt.href=wn,document.body.appendChild(Jt),Jt.click(),document.body.removeChild(Jt)}URL.revokeObjectURL(oe)},Jr.onerror=yr=>{console.error("Failed to load SVG for conversion",yr),URL.revokeObjectURL(oe)},Jr.src=oe}},Tt=q=>{y(q)},vt=q=>{b(q)},Pt=()=>{x()},xt=()=>{x()},kt=q=>{_(q)},St=q=>{w(q)},It=q=>{C(q)},Y=()=>{v()},Q=()=>{var Dt,$t;const q={controls:(Dt=R.value)==null?void 0:Dt.$refs.controls,mobileControls:($t=R.value)==null?void 0:$t.$refs.mobileControls};O(q)};return _e(async()=>{try{await mn(),await F(X,i.code),document.addEventListener("fullscreenchange",Q),document.addEventListener("webkitfullscreenchange",Q),document.addEventListener("mozfullscreenchange",Q),document.addEventListener("MSFullscreenChange",Q)}catch(q){console.error("Error in component initialization:",q)}}),gn(()=>{document.removeEventListener("fullscreenchange",Q),document.removeEventListener("webkitfullscreenchange",Q),document.removeEventListener("mozfullscreenchange",Q),document.removeEventListener("MSFullscreenChange",Q)}),(q,Dt)=>P(N)?(S(),A("div",{key:0,ref_key:"fullscreenWrapper",ref:I,class:"mermaid-container","data-fullscreen-wrapper":""},[dt(F8,{ref_key:"controlsRef",ref:R,scale:P(s),code:e.code,"is-fullscreen":P(u),onZoomIn:P(d),onZoomOut:P(f),onResetView:P(p),onToggleFullscreen:Z,onPanUp:P(k),onPanDown:P($),onPanLeft:P(z),onPanRight:P(W),onDownload:J,toolbar:n.value},null,8,["scale","code","is-fullscreen","onZoomIn","onZoomOut","onResetView","onPanUp","onPanDown","onPanLeft","onPanRight","toolbar"]),dt(N8,{"render-error":P(L),"render-error-details":P(B)},null,8,["render-error","render-error-details"]),T("div",{class:"diagram-wrapper",onMousedown:Tt,onMousemove:vt,onMouseup:Pt,onMouseleave:xt,onWheel:kt,onTouchstart:St,onTouchmove:It,onTouchend:Y},[T("div",{id:X,class:"mermaid",style:ya({opacity:P(D)?1:0,transform:`scale(${P(s)}) translate(${P(l)}px, ${P(c)}px)`,cursor:P(h)?"grabbing":"grab"})},yt(e.code),5)],32)],512)):U("",!0)}}),zr=class zr{constructor(t){Lt(this,"config");Lt(this,"toolbarConfig");Lt(this,"initialized",!1);Lt(this,"renderAttempts",0);Lt(this,"maxRenderAttempts",15);Lt(this,"retryTimeout",null);Lt(this,"renderQueue",[]);Lt(this,"isRendering",!1);Lt(this,"initialPageRenderComplete",!1);Lt(this,"hydrationComplete",!1);Lt(this,"mutationObserver",null);this.config=t?{...t}:{},this.toolbarConfig=Uc(),this.initialize()}static getInstance(t){return zr.instance?t&&zr.instance.setConfig(t):zr.instance=new zr(t),zr.instance}setConfig(t){this.config={...this.config,...t},this.dispatchConfigUpdate()}setToolbar(t){this.toolbarConfig=Uc(t)}dispatchConfigUpdate(){try{document.dispatchEvent(new CustomEvent("vitepress-mermaid:config-updated",{detail:{...this.config}}))}catch(t){console.error("Failed to dispatch Mermaid config update:",t)}}cleanupMermaidWrapper(t){const r=t.getElementsByClassName("copy");if(Array.from(r).forEach(i=>i.remove()),!this.toolbarConfig.showLanguageLabel){const i=t.getElementsByClassName("lang");Array.from(i).forEach(n=>n.remove())}}createMermaidComponent(t){try{const r=document.createElement("div");return r.id=`mermaid-wrapper-${Math.random().toString(36).slice(2)}`,r.className="mermaid-wrapper",{wrapper:r,component:nr(j8,{code:t,config:this.config,toolbar:this.toolbarConfig})}}catch(r){return console.error("Failed to create mermaid component:",r),null}}async renderNextDiagram(){if(this.renderQueue.length===0||this.isRendering)return;this.isRendering=!0;const t=this.renderQueue.shift();if(t)try{await this.renderMermaidDiagram(t)}catch(r){console.error("Failed to render diagram:",r)}this.isRendering=!1,this.renderQueue.length>0?await this.renderNextDiagram():this.initialPageRenderComplete||(this.initialPageRenderComplete=!0,this.hydrationComplete=!0)}async renderMermaidDiagram(t){var r;try{if(!t||!t.parentNode)return;const i=((r=t.textContent)==null?void 0:r.trim())||"",n=this.createMermaidComponent(i);if(!n)return;const{wrapper:a,component:o}=n;return t.parentNode.replaceChild(a,t),new Promise(s=>{ev({render:()=>o}).mount(a),setTimeout(s,200)})}catch(i){console.error("Failed to render mermaid diagram:",i)}}initialize(){if(!this.initialized)try{const t=()=>{if(!document||!document.body){console.warn("MermaidRenderer initialization failed: document or body not available");return}Promise.resolve().then(()=>{requestAnimationFrame(()=>{try{this.setupDomMutationObserver(),this.initializeRenderer()}catch(i){console.error("Failed to initialize MermaidRenderer:",i instanceof Error?i.message:"Unknown error")}})})};switch(document.readyState){case"loading":document.addEventListener("DOMContentLoaded",t,{once:!0});break;case"interactive":case"complete":t();break;default:console.warn(`MermaidRenderer: Unexpected document.readyState: ${document.readyState}`),t()}const r=()=>{try{this.handleRouteChange()}catch(i){console.error("Error handling route change:",i instanceof Error?i.message:"Unknown error")}};window.addEventListener("popstate",r),document.addEventListener("vitepress:routeChanged",r),document.addEventListener("vitepress:ready",()=>{this.renderWithRetry()},{once:!0}),typeof window<"u"&&setTimeout(()=>{this.renderWithRetry()},500),this.initialized=!0}catch(t){throw console.error("Critical error during MermaidRenderer initialization:",t instanceof Error?t.message:"Unknown error"),t}}setupDomMutationObserver(){if(typeof window>"u"||typeof MutationObserver>"u"||typeof document>"u")return;const t=document.getElementById("app")||document.querySelector(".Layout")||document.body;if(!t)return;this.mutationObserver&&this.mutationObserver.disconnect();let r=!1;this.mutationObserver=new MutationObserver(i=>{this.hasNewMermaidNodes(i)&&(r||(r=!0,requestAnimationFrame(()=>{r=!1,this.handleRouteChange()})))});try{this.mutationObserver.observe(t,{childList:!0,subtree:!0})}catch(i){console.error("Failed to observe DOM mutations for Mermaid:",i)}}hasNewMermaidNodes(t){return t.some(r=>Array.from(r.addedNodes).some(i=>this.nodeContainsMermaidCode(i)))}nodeContainsMermaidCode(t){var r;if(!t)return!1;if(t.nodeType===Node.ELEMENT_NODE){const i=t;if(i.closest(".mermaid-wrapper"))return!1;if(i.classList.contains("language-mermaid")||(r=i.matches)!=null&&r.call(i,"code.mermaid")||i.querySelector(".language-mermaid, pre.language-mermaid, code.language-mermaid, code.mermaid"))return!0}return t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.hasChildNodes()?Array.from(t.childNodes).some(i=>this.nodeContainsMermaidCode(i)):!1}initializeRenderer(){this.renderAttempts=0,this.initialPageRenderComplete=!1,this.renderWithRetry()}handleRouteChange(){this.renderAttempts=0,this.initialPageRenderComplete=!1,this.retryTimeout&&(clearTimeout(this.retryTimeout),this.retryTimeout=null),this.renderWithRetry()}renderWithRetry(){if(!this.renderMermaidDiagrams()&&this.renderAttempts{this.renderAttempts++,this.renderWithRetry()},t)}}renderMermaidDiagrams(){try{let t=document.getElementsByClassName("language-mermaid");if(t.length===0){const i=document.querySelectorAll("pre"),n=Array.from(i).filter(a=>{const o=a.querySelector("code");return!!(o&&(o.className.includes("mermaid")||o.className.includes("language-mermaid")))});n.length>0&&(t={length:n.length,item(a){return a>=0&&a({...a,[s]:o}),{})})}if(t.length===0)return!1;Array.from(t).forEach(i=>this.cleanupMermaidWrapper(i));const r=Array.from(t).map(i=>{let n=i.querySelector("pre");return!n&&i.tagName.toLowerCase()==="pre"&&(n=i),n}).filter(i=>i instanceof HTMLPreElement);return r.length>0&&(this.renderQueue.push(...r),this.isRendering||this.renderNextDiagram()),r.length>0}catch(t){return console.error("Error rendering Mermaid diagrams:",t),!1}}};Lt(zr,"instance");let jc=zr;const Y8='.mermaid-container{position:relative;min-height:20rem;max-height:50vh;width:100%;overflow:hidden!important}.controls{--control-padding: .375rem;--control-gap: .25rem;--control-radius: .375rem;--control-shadow: 0 2px 4px rgba(0, 0, 0, .1);position:absolute;bottom:.75rem;right:.75rem;top:auto;left:auto;z-index:20;padding:var(--control-padding);border-radius:var(--control-radius);box-shadow:var(--control-shadow);opacity:0;visibility:visible;pointer-events:auto;transition:all .2s ease;background:var(--vp-c-bg)}.controls.toolbar-vertical-top{top:.75rem;bottom:auto}.controls.toolbar-vertical-bottom{bottom:.75rem;top:auto}.controls.toolbar-horizontal-left{left:.75rem;right:auto}.controls.toolbar-horizontal-right{right:.75rem;left:auto}.desktop-controls{display:inline-flex;align-items:center;gap:.375rem}.desktop-controls button{position:relative;padding:.375rem;border:none;background:var(--vp-c-bg);border-radius:.25rem;cursor:pointer;display:grid;place-items:center;color:var(--vp-c-text-1);transition:all .2s ease}.desktop-controls button:hover{background:var(--vp-c-bg-soft);transform:translateY(-1px);color:var(--vp-c-brand)}.desktop-controls button:active{transform:translateY(0)}.desktop-controls button svg{width:18px;height:18px;transition:transform .2s ease}.desktop-controls button:hover svg{transform:scale(1.1)}.mobile-controls{display:none;flex-direction:row;justify-content:center;gap:.5rem;padding:.75rem;width:auto;z-index:8}.mobile-utility-controls{display:flex;align-items:center;justify-content:center;gap:.5rem}.mobile-controls button{width:40px;height:40px;background:var(--vp-c-bg);border:1px solid var(--vp-c-border);border-radius:.25rem;display:flex;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:background .2s ease,transform .2s ease}.mobile-controls button:hover{background:var(--vp-c-bg-soft);transform:translateY(-1px)}.mobile-controls button:active{transform:translateY(0)}.mobile-controls button svg{width:20px;height:20px;stroke:var(--vp-c-text-1)}.zoom-level{min-width:3.25rem;text-align:center;font-size:.75rem;font-weight:500;color:var(--vp-c-text-2);-webkit-user-select:none;user-select:none;padding:.25rem .375rem;background:var(--vp-c-bg);border-radius:.25rem}.mobile-controls .zoom-level{margin-right:.5rem;min-width:3rem}.mobile-controls .mobile-zoom-level{order:-1}@media(max-width:768px){.desktop-controls{display:none}.mobile-controls{display:flex}}@media(min-width:769px){.mobile-controls{display:none}.desktop-controls{display:inline-flex}}.mermaid-container:hover .controls:not(.force-show){opacity:1;transform:translateY(0)}.mermaid-container:fullscreen .controls{opacity:1!important;transform:translateY(0)!important}.diagram-wrapper{overflow:hidden;position:relative;width:100%;height:100%;min-height:20rem;z-index:1}.mermaid-container:fullscreen .diagram-wrapper{background:var(--vp-c-bg);color:var(--vp-c-text-1);padding:20px;display:flex;align-items:center;justify-content:center;max-height:none}.mermaid{transition:opacity .3s ease-in-out;transform-origin:center center;display:inline-block}.mermaid-rendering{opacity:.5;position:relative}.mermaid-rendering:after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:30px;height:30px;border:3px solid var(--vp-c-brand);border-top-color:transparent;border-radius:50%;animation:mermaid-spinner .8s linear infinite}@keyframes mermaid-spinner{to{transform:translate(-50%,-50%) rotate(360deg)}}.copied-notification{position:absolute;bottom:100%;left:50%;transform:translate(-50%);background:var(--vp-c-brand);color:#fff;padding:.375rem .75rem;border-radius:var(--control-radius);font-size:.75rem;font-weight:500;white-space:nowrap;margin-block-end:.5rem;opacity:0;animation:fadeInOut 2s ease-in-out;box-shadow:0 2px 4px #0000001a}@keyframes fadeInOut{0%{opacity:0;transform:translate(-50%,.5rem)}10%{opacity:1;transform:translate(-50%)}90%{opacity:1;transform:translate(-50%)}to{opacity:0;transform:translate(-50%,-.5rem)}}.visible-controls{opacity:1!important;visibility:visible!important;pointer-events:auto!important}.mobile-only{display:none!important}@media(max-width:768px){.mobile-only{display:grid!important}.controls{flex-direction:column;align-items:stretch}.zoom-level{text-align:center}}.diagram-error{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--vp-c-bg-soft);border:1px solid var(--vp-c-border);border-radius:.5rem;padding:1rem;max-width:90%;width:max-content;box-shadow:0 4px 8px #0000001a;z-index:10}.error-message{display:flex;align-items:center;gap:.75rem;color:var(--vp-c-danger);font-weight:500}.diagram-error .error-message svg{stroke:var(--vp-c-danger);flex-shrink:0}.error-toggle-button{margin-left:auto;background:var(--vp-c-bg);border:1px solid var(--vp-c-border);border-radius:.25rem;padding:.25rem .5rem;font-size:.75rem;cursor:pointer;transition:all .2s ease}.error-toggle-button:hover{background:var(--vp-c-bg-mute);transform:translateY(-1px)}.error-details{margin-top:1rem;padding:1rem;background:var(--vp-c-bg);border-radius:.25rem;white-space:pre-wrap;font-family:monospace;font-size:.85rem;overflow-x:auto;color:var(--vp-c-text-2);border:1px solid var(--vp-c-border);max-height:200px;overflow-y:auto}',$f="vitepress-mermaid-renderer-styles";let Dl=!1;const G8=()=>{if(Dl||typeof document>"u")return;if(document.getElementById($f)){Dl=!0;return}const e=document.createElement("style");e.id=$f,e.textContent=Y8,document.head.appendChild(e),Dl=!0},Eb=typeof window<"u"&&typeof document<"u",X8={setToolbar:()=>{}};Eb&&G8();const Z8=e=>Eb?jc.getInstance(e):X8,K8={__name:"Chatbot",setup(e){return _e(()=>{}),gn(()=>{}),(t,r)=>null}},Q8={key:0,class:"claude-badge"},J8={key:0,href:"https://www.linkedin.com/in/vb-software/",target:"_blank",title:"Human Written"},tI={key:1,href:"/about",title:"AI-assisted, verified against source"},eI={key:2,class:"attention-notice"},rI={__name:"ClaudeBadge",setup(e){const{frontmatter:t}=ho(),r=Pr(),i=rt(()=>t.value.badge==="human"),n=rt(()=>t.value.badge==="none"),a=rt(()=>{const s=r.path;return s.startsWith("/blog/")&&s!=="/blog/"&&s!=="/blog/index.html"}),o=rt(()=>a.value&&!i.value);return(s,l)=>n.value?U("",!0):(S(),A("div",Q8,[i.value?(S(),A("a",J8,l[0]||(l[0]=[T("img",{src:"https://img.shields.io/badge/Human-Written-blue",alt:"Human Written"},null,-1)]))):(S(),A("a",tI,l[1]||(l[1]=[T("img",{src:"https://img.shields.io/badge/AI--assisted-verified_against_source-cc785c?logo=anthropic",alt:"AI-assisted, verified against source"},null,-1)]))),o.value?(S(),A("details",eI,l[2]||(l[2]=[T("summary",null,"How this page was made",-1),T("p",null,[fr(' This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in '),T("a",{href:"/about"},"About"),fr(". ")],-1)]))):U("",!0)]))}},iI=ut(rI,[["__scopeId","data-v-344998d8"]]),Fb=[{title:"The Backend That Writes Itself (Slide Deck)",path:"/blog/the-backend-that-writes-itself-presentation"},{title:"Case Study: 74 Endpoints, Zero Backend Code",path:"/blog/case-study-zero-backend-code"},{title:"TypeScript Code Generation Walkthrough",path:"/blog/typescript-codegen-walkthrough"},{title:"NpgsqlRest 3.13.0: Production Patterns",path:"/blog/npgsqlrest-3.13-production-patterns"},{title:"SQL REST API",path:"/blog/sql-rest-api"},{title:"Excel Exports Done Right",path:"/blog/excel-export-table-format-postgresql-npgsqlrest"},{title:"Passkey SQL Auth",path:"/blog/passkey-sql-auth"},{title:"Custom Types & Multiset",path:"/blog/custom-types-multiset-rest-api"},{title:"Performance & High Availability",path:"/blog/performance-scalability-high-availability-npgsqlrest"},{title:"Benchmark 2026",path:"/blog/postgresql-rest-api-benchmark-2026"},{title:"End-to-End Type Checking",path:"/blog/end-to-end-static-type-checking-postgresql-typescript"},{title:"Database-Level Security",path:"/blog/database-level-security-postgresql-authentication"},{title:"Multiple Auth Schemes & RBAC",path:"/blog/multiple-auth-schemes-rbac-external-providers"},{title:"PostgreSQL BI Server",path:"/blog/postgresql-bi-server-excel-csv-basic-auth"},{title:"Secure Image Uploads",path:"/blog/secure-image-uploads-postgresql-typescript"},{title:"CSV & Excel Ingestion",path:"/blog/csv-excel-ingestion-postgresql-npgsqlrest"},{title:"Real-Time Chat with SSE",path:"/blog/real-time-chat-postgresql-sse-npgsqlrest"},{title:"External API Calls",path:"/blog/external-api-calls-postgresql-http-types"},{title:"Reverse Proxy & AI Service",path:"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest"},{title:"NpgsqlRest vs PostgREST vs Supabase",path:"/blog/npgsqlrest-vs-postgrest-supabase-comparison"},{title:"Optimization Labels 101",path:"/blog/optimization-labels-101"},{title:"What Have Stored Procedures Done for Us?",path:"/blog/what-have-stored-procedures-ever-done-for-us"}],nI={class:"blog-sidebar"},aI={class:"blog-sidebar-nav"},sI=["href"],oI={__name:"BlogSidebar",setup(e){const t=Pr(),r=rt(()=>t.path),i=rt(()=>Fb.filter(n=>!r.value.includes(n.path.replace("/blog/",""))));return(n,a)=>(S(),A("div",nI,[a[0]||(a[0]=T("div",{class:"blog-sidebar-title"},"More Blog Posts",-1)),T("nav",aI,[(S(!0),A(Mt,null,Vt(i.value,o=>(S(),A("a",{key:o.path,href:o.path,class:"blog-sidebar-link"},yt(o.title),9,sI))),128))])]))}},lI=ut(oI,[["__scopeId","data-v-0f020bdc"]]),cI={class:"blog-nav"},hI={key:0},uI=["href"],dI={key:1},fI=["href"],pI={key:0},gI=["href"],mI={key:0},yI={key:2},bI=["href"],vI={key:0},xI=nt({__name:"BlogNav",props:{sourceCode:{},documentation:{},getStarted:{}},setup(e){const t=Pr(),r=rt(()=>t.path),i=rt(()=>Fb.filter(n=>!r.value.includes(n.path.replace("/blog/",""))));return(n,a)=>(S(),A("div",cI,[n.sourceCode?(S(),A("p",hI,[a[0]||(a[0]=T("strong",null,"Source Code:",-1)),a[1]||(a[1]=fr()),T("a",{href:n.sourceCode,target:"_blank"},"View the complete example on GitHub",8,uI)])):U("",!0),n.documentation&&n.documentation.length?(S(),A("p",dI,[a[2]||(a[2]=T("strong",null,"Documentation:",-1)),a[3]||(a[3]=T("br",null,null,-1)),(S(!0),A(Mt,null,Vt(n.documentation,(o,s)=>(S(),A(Mt,{key:o.href},[T("a",{href:o.href},yt(o.text),9,fI),s(S(),A(Mt,{key:o.path},[T("a",{href:o.path},yt(o.title),9,gI),s(S(),A(Mt,{key:o.href},[T("a",{href:o.href},yt(o.text),9,bI),s{Rt(()=>import("./giscus-Ci9LqPcC.BNebfDgq.js"),[]).then(()=>t.value=!0)}),(r,i)=>t.value?(S(),A("giscus-widget",{key:0,id:r.id,host:r.host,repo:r.repo,repoid:r.repoId,category:r.category,categoryid:r.categoryId,mapping:r.mapping,term:r.term,strict:r.strict,reactionsenabled:r.reactionsEnabled,emitmetadata:r.emitMetadata,inputposition:r.inputPosition,theme:r.theme,lang:r.lang,loading:r.loading},null,8,kI)):U("",!0)}}),CI={id:"comments",class:"giscus-wrapper"},SI=nt({__name:"GiscusComments",setup(e){const{isDark:t}=ho(),r=Pr(),i=rt(()=>t.value?"dark":"light"),n=rt(()=>r.path);return(a,o)=>(S(),A("div",CI,[o[0]||(o[0]=T("h2",{class:"comments-heading"},"Comments",-1)),dt(P(wI),{repo:"NpgsqlRest/npgsqlrest-docs","repo-id":"R_kgDOPjTHtg",category:"Comments","category-id":"DIC_kwDOPjTHts4C05oC",mapping:"pathname",term:n.value,strict:"0","reactions-enabled":"1","emit-metadata":"0","input-position":"top",theme:i.value,lang:"en",loading:"lazy"},null,8,["term","theme"])]))}}),TI=ut(SI,[["__scopeId","data-v-5337483f"]]),MI={},$I={class:"comments-outline-link"};function LI(e,t){return S(),A("div",$I,t[0]||(t[0]=[T("a",{href:"#comments",class:"outline-link"},"Comments",-1)]))}const Lf=ut(MI,[["render",LI],["__scopeId","data-v-6357a5e2"]]),AI={},BI={class:"sponsor-footer"};function EI(e,t){return S(),A("div",BI,t[0]||(t[0]=[ps('
    ',3)]))}const FI=ut(AI,[["render",EI],["__scopeId","data-v-b93f2dbd"]]),PI={class:"hero-terminal","aria-label":"SQL file source and curl request with JSON response"},DI={class:"line"},II={class:"cmd"},OI={key:0,class:"cursor"},RI={key:0,class:"line"},NI={class:"line"},zI={class:"cmd"},qI={key:0,class:"cursor"},WI={key:0,class:"line"},Af="cat sql/users.sql",HI="curl -s 'localhost:8080/api/users?role=admin' | jq",VI={__name:"HeroTerminal",setup(e){const t=[{text:"-- HTTP GET",cls:"comment"},{text:"-- @param $1 role text",cls:"comment"},{text:"select id, name, role",cls:"sql"},{text:"from users",cls:"sql"},{text:"where $1 is null or role = $1;",cls:"sql"}],r=[{text:"[",cls:"punct"},{text:' { "id": 1, "name": "Alice", "role": "admin" },',cls:"json"},{text:' { "id": 4, "name": "Diana", "role": "admin" }',cls:"json"},{text:"]",cls:"punct"}],i=tt(1),n=tt("typing"),a=tt(""),o=tt(""),s=tt([]),l=tt([]),c=tt(!1),h=[],u=()=>{for(;h.length;)clearTimeout(h.pop())},d=(x,_)=>h.push(setTimeout(x,_));function f(x,_,w){let C=0;const v=()=>{if(C>=_.length)return w();x.value=_.slice(0,C+1),C++,d(v,30+Math.random()*28)};v()}function p(x,_,w,C){let v=0;const k=()=>{if(v>=_.length)return C();x.value=[...x.value,_[v]],v++,d(k,w)};k()}function g(x,_){c.value=!0,d(()=>{x===1?y():b()},220)}function y(){a.value="",s.value=[],i.value=1,n.value="typing",c.value=!1,f(a,Af,()=>{d(()=>{n.value="output",p(s,t,80,()=>{d(()=>{n.value="idle",d(()=>g(2),2600)},200)})},200)})}function b(){o.value="",l.value=[],i.value=2,n.value="typing",c.value=!1,f(o,HI,()=>{d(()=>{n.value="output",p(l,r,100,()=>{d(()=>{n.value="idle",d(()=>g(1),3800)},200)})},200)})}return _e(()=>{if(typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches){a.value=Af,s.value=[...t],i.value=1,n.value="idle";return}d(y,600)}),mo(()=>{u()}),(x,_)=>(S(),A("div",PI,[_[4]||(_[4]=ps('
    ~ npgsqlrest
    ',1)),T("div",{class:At(["terminal-body",{fading:c.value}])},[i.value===1?(S(),A(Mt,{key:0},[T("div",DI,[_[0]||(_[0]=T("span",{class:"prompt"},"$",-1)),T("span",II,yt(a.value),1),n.value==="typing"?(S(),A("span",OI,"▌")):U("",!0)]),(S(!0),A(Mt,null,Vt(s.value,(w,C)=>(S(),A("div",{key:"s"+C,class:At(["line response",w.cls])},yt(w.text),3))),128)),n.value==="idle"?(S(),A("div",RI,_[1]||(_[1]=[T("span",{class:"prompt"},"$",-1),T("span",{class:"cursor"},"▌",-1)]))):U("",!0)],64)):(S(),A(Mt,{key:1},[T("div",NI,[_[2]||(_[2]=T("span",{class:"prompt"},"$",-1)),T("span",zI,yt(o.value),1),n.value==="typing"?(S(),A("span",qI,"▌")):U("",!0)]),(S(!0),A(Mt,null,Vt(l.value,(w,C)=>(S(),A("div",{key:"j"+C,class:At(["line response",w.cls])},yt(w.text),3))),128)),n.value==="idle"?(S(),A("div",WI,_[3]||(_[3]=[T("span",{class:"prompt"},"$",-1),T("span",{class:"cursor"},"▌",-1)]))):U("",!0)],64))],2)]))}},UI=ut(VI,[["__scopeId","data-v-2b0df7dc"]]),jI=["aria-label"],YI={class:"terminal-chrome"},GI={class:"terminal-title"},XI={class:"line"},ZI={class:"cmd"},KI={class:"line"},QI={class:"cmd"},JI={key:0,class:"cursor"},tO={key:1,class:"line"},eO={__name:"CodeTerminal",props:{title:{type:String,default:"~ npgsqlrest"},ariaLabel:{type:String,default:"Animated terminal showing code, request, and generated client"},frames:{type:Array,required:!0},idleMs:{type:Number,default:3200},loopMs:{type:Number,default:5e3},typingMs:{type:Number,default:22},outputMs:{type:Number,default:55},blockPauseMs:{type:Number,default:700}},setup(e){const t=e,r=tt(0),i=tt([]),n=tt(""),a=tt([]),o=tt("typing"),s=tt(!1);function l(_){return _?_.blocks?_:{..._,blocks:[{command:_.command,lines:_.lines,lineMs:_.lineMs}]}:{blocks:[]}}const c=rt(()=>l(t.frames[r.value]).title||t.title),h=rt(()=>{var w;let _=0;for(const C of t.frames){const v=l(C);let k=1;for(const $ of v.blocks)k+=1+(((w=$.lines)==null?void 0:w.length)||0);k>_&&(_=k)}return _}),u=rt(()=>({minHeight:`calc(${h.value} * 1.55em + 2.2rem)`})),d=[],f=()=>{for(;d.length;)clearTimeout(d.pop())},p=(_,w)=>d.push(setTimeout(_,w));function g(_,w){let C=0;const v=()=>{if(C>=_.length)return w();n.value=_.slice(0,C+1),C++,p(v,t.typingMs+Math.random()*t.typingMs)};v()}function y(_,w,C){let v=0;const k=()=>{if(v>=_.length)return C();a.value=[...a.value,_[v]],v++,p(k,w)};k()}function b(_,w){if(_>=w.blocks.length){o.value="idle";const v=r.value===t.frames.length-1,k=v?t.loopMs:t.idleMs;p(()=>{s.value=!0,p(()=>x(v?0:r.value+1),240)},k);return}const C=w.blocks[_];n.value="",a.value=[],o.value="typing",g(C.command,()=>{p(()=>{o.value="output",y(C.lines||[],C.lineMs||t.outputMs,()=>{i.value.push({command:C.command,lines:a.value.slice()}),n.value="",a.value=[];const v=_===w.blocks.length-1;p(()=>b(_+1,w),v?200:t.blockPauseMs)})},180)})}function x(_){var C;const w=l(t.frames[_]);(C=w.blocks)!=null&&C.length&&(i.value=[],n.value="",a.value=[],s.value=!1,r.value=_,b(0,w))}return _e(()=>{if(typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches){const _=l(t.frames[0]);i.value=_.blocks.map(w=>({command:w.command,lines:[...w.lines||[]]})),o.value="idle";return}p(()=>x(0),500)}),mo(f),(_,w)=>(S(),A("div",{class:"code-terminal","aria-label":e.ariaLabel},[T("div",YI,[w[0]||(w[0]=T("span",{class:"dot dot-red"},null,-1)),w[1]||(w[1]=T("span",{class:"dot dot-yellow"},null,-1)),w[2]||(w[2]=T("span",{class:"dot dot-green"},null,-1)),T("span",GI,yt(c.value),1)]),T("div",{class:At(["terminal-body",{fading:s.value}]),style:ya(u.value)},[(S(!0),A(Mt,null,Vt(i.value,(C,v)=>(S(),A(Mt,{key:"c"+v},[T("div",XI,[w[3]||(w[3]=T("span",{class:"prompt"},"$",-1)),T("span",ZI,yt(C.command),1)]),(S(!0),A(Mt,null,Vt(C.lines,(k,$)=>(S(),A("div",{key:"cl"+v+"_"+$,class:At(["line response",k.cls])},yt(k.text||" "),3))),128))],64))),128)),o.value==="typing"||o.value==="output"?(S(),A(Mt,{key:0},[T("div",KI,[w[4]||(w[4]=T("span",{class:"prompt"},"$",-1)),T("span",QI,yt(n.value),1),o.value==="typing"?(S(),A("span",JI,"▌")):U("",!0)]),(S(!0),A(Mt,null,Vt(a.value,(C,v)=>(S(),A("div",{key:"al"+v,class:At(["line response",C.cls])},yt(C.text||" "),3))),128))],64)):U("",!0),o.value==="idle"?(S(),A("div",tO,w[5]||(w[5]=[T("span",{class:"prompt"},"$",-1),T("span",{class:"cursor"},"▌",-1)]))):U("",!0)],6)],8,jI))}},Pb=ut(eO,[["__scopeId","data-v-77e3aab4"]]),rO={__name:"SqlFileShowcase",setup(e){const t=[{title:"~ sql/users.sql",blocks:[{command:"cat sql/users.sql",lines:[{text:"/*",cls:"comment"},{text:"HTTP GET /users/",cls:"comment"},{text:"@authorize admin, user",cls:"comment"},{text:"@cached",cls:"comment"},{text:"@cache_expires_in 30sec",cls:"comment"},{text:"@timeout 5min",cls:"comment"},{text:"@param $1 department_id text",cls:"comment"},{text:"*/",cls:"comment"},{text:"select id, name, email, role",cls:"sql"},{text:"from users",cls:"sql"},{text:"where $1 is null or department_id = $1;",cls:"sql"}]},{command:"curl -s 'localhost:8080/users/?department_id=1' | jq",lineMs:75,lines:[{text:"[",cls:"punct"},{text:' { "id": 1, "name": "Alice", "email": "alice@acme.io", "role": "admin" },',cls:"json"},{text:' { "id": 4, "name": "Diana", "email": "diana@acme.io", "role": "admin" },',cls:"json"},{text:' { "id": 7, "name": "Eve", "email": "eve@acme.io", "role": "user" }',cls:"json"},{text:"]",cls:"punct"}]}]},{title:"~ src/sqlApi.ts",blocks:[{command:"cat src/sqlApi.ts",lineMs:45,lines:[{text:"// autogenerated at 2026-05-11T10:23:00+00:00",cls:"comment"},{text:"",cls:"ts"},{text:"interface IUsersRequest {",cls:"ts"},{text:" department_id?: string | null;",cls:"ts"},{text:"}",cls:"ts"},{text:"",cls:"ts"},{text:"interface IUsersResponse {",cls:"ts"},{text:" id: number | null;",cls:"ts"},{text:" name: string | null;",cls:"ts"},{text:" email: string | null;",cls:"ts"},{text:" role: string | null;",cls:"ts"},{text:"}",cls:"ts"},{text:"",cls:"ts"},{text:"export async function users(",cls:"ts"},{text:" request: IUsersRequest",cls:"ts"},{text:") : Promise> {",cls:"ts"},{text:' const response = await fetch(baseUrl + "/users/" + parseQuery(request), {',cls:"ts"},{text:' method: "GET",',cls:"ts"},{text:' headers: { "Content-Type": "application/json" },',cls:"ts"},{text:" });",cls:"ts"},{text:" return {",cls:"ts"},{text:" status: response.status,",cls:"ts"},{text:" response: response.ok ? await response.json() as IUsersResponse[] : undefined!,",cls:"ts"},{text:" error: !response.ok ? await response.json() as ApiError : undefined",cls:"ts"},{text:" };",cls:"ts"},{text:"}",cls:"ts"}]}]}];return(r,i)=>(S(),mt(Pb,{frames:t,"aria-label":"SQL file declarations turning into a REST endpoint and typed TypeScript client"}))}},iO={__name:"FunctionShowcase",setup(e){const t=[{title:"~ sql/get_users.sql",blocks:[{command:"cat sql/get_users.sql",lines:[{text:"create or replace function api.get_users(",cls:"sql"},{text:" _department_id int",cls:"sql"},{text:")",cls:"sql"},{text:"returns table (id int, name text, email text, role text)",cls:"sql"},{text:"language sql",cls:"sql"},{text:"begin atomic;",cls:"sql"},{text:"select id, name, email, role from users",cls:"sql"},{text:"where _department_id is null or department_id = _department_id;",cls:"sql"},{text:"end;",cls:"sql"},{text:"",cls:"sql"},{text:"comment on function api.get_users(int) is '",cls:"sql"},{text:"HTTP GET /users/",cls:"comment"},{text:"@authorize admin, user",cls:"comment"},{text:"@cached",cls:"comment"},{text:"@cache_expires_in 30sec",cls:"comment"},{text:"@timeout 10sec",cls:"comment"},{text:"@retry_strategy aggressive",cls:"comment"},{text:"@rate_limiter_policy authenticated_limit",cls:"comment"},{text:"@tsclient_module = users",cls:"comment"},{text:"';",cls:"sql"}]},{command:"curl -s 'localhost:8080/users/?_department_id=1' | jq",lineMs:75,lines:[{text:"[",cls:"punct"},{text:' { "id": 1, "name": "Alice", "email": "alice@acme.io", "role": "admin" },',cls:"json"},{text:' { "id": 4, "name": "Diana", "email": "diana@acme.io", "role": "admin" },',cls:"json"},{text:' { "id": 7, "name": "Eve", "email": "eve@acme.io", "role": "user" }',cls:"json"},{text:"]",cls:"punct"}]}]},{title:"~ src/users.ts",blocks:[{command:"cat src/users.ts",lineMs:45,lines:[{text:"// autogenerated at 2026-05-11T10:23:00+00:00",cls:"comment"},{text:"",cls:"ts"},{text:"interface IGetUsersRequest {",cls:"ts"},{text:" _department_id?: number | null;",cls:"ts"},{text:"}",cls:"ts"},{text:"",cls:"ts"},{text:"interface IGetUsersResponse {",cls:"ts"},{text:" id: number | null;",cls:"ts"},{text:" name: string | null;",cls:"ts"},{text:" email: string | null;",cls:"ts"},{text:" role: string | null;",cls:"ts"},{text:"}",cls:"ts"},{text:"",cls:"ts"},{text:"export async function getUsers(",cls:"ts"},{text:" request: IGetUsersRequest",cls:"ts"},{text:") : Promise> {",cls:"ts"},{text:' const response = await fetch(baseUrl + "/users/" + parseQuery(request), {',cls:"ts"},{text:' method: "GET",',cls:"ts"},{text:' headers: { "Content-Type": "application/json" },',cls:"ts"},{text:" });",cls:"ts"},{text:" return {",cls:"ts"},{text:" status: response.status,",cls:"ts"},{text:" response: response.ok ? await response.json() as IGetUsersResponse[] : undefined!,",cls:"ts"},{text:" error: !response.ok ? await response.json() as ApiError : undefined",cls:"ts"},{text:" };",cls:"ts"},{text:"}",cls:"ts"}]}]}];return(r,i)=>(S(),mt(Pb,{frames:t,"aria-label":"A PostgreSQL function turning into a REST endpoint and typed TypeScript client"}))}},nO=["aria-label"],aO={class:"sd-header"},sO={class:"sd-title"},oO={class:"sd-count"},lO={class:"sd-stage"},cO=["src","alt"],hO=["disabled"],uO=["disabled"],dO={class:"sd-progress"},fO={class:"sd-controls"},pO={class:"sd-controls-left"},gO=["disabled"],mO=["disabled"],yO=["aria-pressed","title"],bO={class:"sd-controls-right"},vO=["aria-pressed"],xO=["title"],_O={key:0,class:"sd-notes"},kO=["aria-label","aria-current","onClick"],wO=["src"],CO={class:"sd-thumb-num"},SO={__name:"SlideDeck",props:{slides:{type:Array,required:!0},title:{type:String,default:"Presentation"},autoplayMs:{type:Number,default:6e3}},setup(e){const t=e,r=tt(null),i=tt(null),n=tt(0),a=tt(!1),o=tt(!1),s=tt(!1);let l=null;const c=rt(()=>t.slides.some(v=>v.notes)),h=rt(()=>{var v;return((v=t.slides[n.value])==null?void 0:v.notes)||""});function u(v){return Math.max(0,Math.min(t.slides.length-1,v))}function d(v){n.value=u(v)}function f(){n.value{clearInterval(l),v&&(l=setInterval(f,t.autoplayMs))}),Ne(n,v=>{[v-1,v+1].forEach(k=>{const $=t.slides[k];if($){const z=new Image;z.src=$.src}}),mn(()=>{var z;const k=i.value,$=(z=k==null?void 0:k.children)==null?void 0:z[v];$&&$.scrollIntoView({behavior:"smooth",block:"nearest",inline:"center"})})});function y(v){if(!(!r.value||!(s.value||r.value.contains(document.activeElement)||b)))switch(v.key){case"ArrowRight":case" ":case"PageDown":v.preventDefault(),f();break;case"ArrowLeft":case"PageUp":v.preventDefault(),p();break;case"Home":v.preventDefault(),d(0);break;case"End":v.preventDefault(),d(t.slides.length-1);break;case"f":case"F":w();break;case"n":case"N":c.value&&(a.value=!a.value);break}}let b=!1;function x(){b=!0}function _(){b=!1}function w(){var k,$;const v=r.value;document.fullscreenElement?($=document.exitFullscreen)==null||$.call(document):(k=v.requestFullscreen)==null||k.call(v).catch(()=>{})}function C(){s.value=document.fullscreenElement===r.value}return _e(()=>{var v,k;window.addEventListener("keydown",y),document.addEventListener("fullscreenchange",C),(v=r.value)==null||v.addEventListener("mouseenter",x),(k=r.value)==null||k.addEventListener("mouseleave",_)}),mo(()=>{var v,k;clearInterval(l),window.removeEventListener("keydown",y),document.removeEventListener("fullscreenchange",C),(v=r.value)==null||v.removeEventListener("mouseenter",x),(k=r.value)==null||k.removeEventListener("mouseleave",_)}),(v,k)=>(S(),A("figure",{ref_key:"root",ref:r,class:At(["slide-deck",{"is-fullscreen":s.value}]),role:"group","aria-label":e.title||"Slide presentation"},[T("div",aO,[T("span",sO,yt(e.title),1),T("span",oO,yt(n.value+1)+" / "+yt(e.slides.length),1)]),T("div",lO,[(S(),A("img",{key:n.value,src:e.slides[n.value].src,alt:`Slide ${n.value+1}`+(e.slides[n.value].alt?": "+e.slides[n.value].alt:""),class:"sd-image",draggable:"false"},null,8,cO)),T("button",{class:"sd-edge sd-edge-prev",disabled:n.value===0,"aria-label":"Previous slide",onClick:p},k[1]||(k[1]=[T("span",null,"‹",-1)]),8,hO),T("button",{class:"sd-edge sd-edge-next",disabled:n.value===e.slides.length-1,"aria-label":"Next slide",onClick:f},k[2]||(k[2]=[T("span",null,"›",-1)]),8,uO),T("div",dO,[T("div",{class:"sd-progress-fill",style:ya({width:(n.value+1)/e.slides.length*100+"%"})},null,4)])]),T("div",fO,[T("div",pO,[T("button",{class:"sd-btn","aria-label":"Previous slide",disabled:n.value===0,onClick:p},"‹ Prev",8,gO),T("button",{class:"sd-btn","aria-label":"Next slide",disabled:n.value===e.slides.length-1,onClick:f},"Next ›",8,mO),T("button",{class:"sd-btn","aria-pressed":o.value,title:o.value?"Pause autoplay":"Play (auto-advance)",onClick:g},yt(o.value?"❚❚":"►"),9,yO)]),T("div",bO,[c.value?(S(),A("button",{key:0,class:At(["sd-btn",{"sd-btn-active":a.value}]),"aria-pressed":a.value,onClick:k[0]||(k[0]=$=>a.value=!a.value)},yt(a.value?"Hide notes":"Notes"),11,vO)):U("",!0),T("button",{class:"sd-btn",title:s.value?"Exit fullscreen":"Fullscreen",onClick:w},yt(s.value?"⤡ Exit":"⤢ Fullscreen"),9,xO)])]),dt(co,{name:"sd-fade"},{default:j(()=>[a.value&&h.value?(S(),A("div",_O,[k[3]||(k[3]=T("span",{class:"sd-notes-label"},"Speaker notes",-1)),T("p",null,yt(h.value),1)])):U("",!0)]),_:1}),T("div",{class:"sd-thumbs",ref_key:"thumbStrip",ref:i},[(S(!0),A(Mt,null,Vt(e.slides,($,z)=>(S(),A("button",{key:z,class:At(["sd-thumb",{"sd-thumb-active":z===n.value}]),"aria-label":`Go to slide ${z+1}`,"aria-current":z===n.value,onClick:W=>d(z)},[T("img",{src:$.src,alt:"",loading:"lazy"},null,8,wO),T("span",CO,yt(z+1),1)],10,kO))),128))],512)],10,nO))}},TO=ut(SO,[["__scopeId","data-v-cb7a016b"]]),sR={extends:Cu,enhanceApp({app:e}){e.component("BlogNav",_I),e.component("SqlFileShowcase",rO),e.component("FunctionShowcase",iO),e.component("SlideDeck",TO)},Layout(){const{frontmatter:e,isDark:t}=ho(),r=Pr(),i=r.path.startsWith("/blog/"),n=/^\/(annotations|changelog|config|examples|guide|reference|blog)\//.test(r.path),a=()=>{Z8({theme:t.value?"dark":"default"})};return mn(()=>a()),Ne(()=>t.value,()=>{a()}),nr(Cu.Layout,null,{"home-hero-image":()=>e.value.layout==="home"?nr(UI):null,"layout-bottom":()=>e.value.layout==="home"?nr(K8):null,"doc-before":()=>nr(iI),"aside-outline-after":()=>i?[nr(Lf),nr(lI)]:n?nr(Lf):null,"doc-after":()=>n?[nr(TI),nr(FI)]:null})}};export{ih as $,FO as A,LO as B,Qn as C,zk as D,oC as E,Hh as F,be as G,ep as H,RE as I,V3 as J,RA as K,Su as L,Ro as M,WO as N,Xp as O,HO as P,kw as Q,sR as R,Z$ as S,q$ as T,QF as U,MA as V,an as W,BO as X,qw as Y,LE as Z,m as _,eC as a,b_ as a$,Iu as a0,ss as a1,OE as a2,Xw as a3,va as a4,it as a5,gt as a6,hp as a7,w4 as a8,F1 as a9,Ik as aA,Ok as aB,$a as aC,ZF as aD,XF as aE,zO as aF,DO as aG,PO as aH,RO as aI,Nk as aJ,OO as aK,ig as aL,_h as aM,$o as aN,nL as aO,iL as aP,cn as aQ,rL as aR,eL as aS,Ds as aT,wa as aU,vh as aV,bh as aW,qi as aX,Ps as aY,IO as aZ,Bt as a_,rR as aa,j3 as ab,ie as ac,Xr as ad,Fh as ae,ay as af,Ai as ag,B0 as ah,jp as ai,Gp as aj,NO as ak,ot as al,h0 as am,pP as an,eR as ao,iR as ap,JO as aq,lt as ar,tR as as,Z4 as at,U4 as au,V4 as av,AO as aw,Oo as ax,Ks as ay,S0 as az,tC as b,qO as b0,iE as b1,w0 as b2,m0 as b3,o5 as b4,Gs as b5,l5 as b6,Ta as b7,ai as b8,Q5 as b9,CE as bA,yE as bB,TE as bC,Dh as bD,$E as bE,qd as ba,Mi as bb,c5 as bc,Ih as bd,s5 as be,d5 as bf,xn as bg,rE as bh,SE as bi,y5 as bj,bE as bk,Ph as bl,_f as bm,vn as bn,cE as bo,OP as bp,Sa as bq,Zs as br,Gr as bs,Fo as bt,Id as bu,Oh as bv,b0 as bw,k0 as bx,n5 as by,qc as bz,qt as c,Ot as d,cp as e,ee as f,iC as g,Fr as h,qe as i,Z3 as j,yn as k,V as l,F0 as m,xa as n,EO as o,aR as p,nC as q,nR as r,rC as s,aC as t,Xe as u,U3 as v,qE as w,q4 as x,VO as y,Jw as z}; diff --git a/assets/chunks/timeline-definition-IT6M3QCI.tSV0dAf1.js b/assets/chunks/timeline-definition-IT6M3QCI.tSV0dAf1.js new file mode 100644 index 000000000..2aa75ca58 --- /dev/null +++ b/assets/chunks/timeline-definition-IT6M3QCI.tSV0dAf1.js @@ -0,0 +1,61 @@ +import{_ as s,c as xt,l as E,d as q,a3 as kt,a4 as vt,a5 as _t,a6 as bt,N as nt,D as wt,a7 as St,z as Et}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var X=function(){var n=s(function(f,r,a,h){for(a=a||{},h=f.length;h--;a[f[h]]=r);return a},"o"),t=[6,8,10,11,12,14,16,17,20,21],e=[1,9],l=[1,10],i=[1,11],d=[1,12],c=[1,13],g=[1,16],m=[1,17],p={trace:s(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:s(function(r,a,h,u,y,o,S){var k=o.length-1;switch(y){case 1:return o[k-1];case 2:this.$=[];break;case 3:o[k-1].push(o[k]),this.$=o[k-1];break;case 4:case 5:this.$=o[k];break;case 6:case 7:this.$=[];break;case 8:u.getCommonDb().setDiagramTitle(o[k].substr(6)),this.$=o[k].substr(6);break;case 9:this.$=o[k].trim(),u.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=o[k].trim(),u.getCommonDb().setAccDescription(this.$);break;case 12:u.addSection(o[k].substr(8)),this.$=o[k].substr(8);break;case 15:u.addTask(o[k],0,""),this.$=o[k];break;case 16:u.addEvent(o[k].substr(2)),this.$=o[k];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},n(t,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:e,12:l,14:i,16:d,17:c,18:14,19:15,20:g,21:m},n(t,[2,7],{1:[2,1]}),n(t,[2,3]),{9:18,11:e,12:l,14:i,16:d,17:c,18:14,19:15,20:g,21:m},n(t,[2,5]),n(t,[2,6]),n(t,[2,8]),{13:[1,19]},{15:[1,20]},n(t,[2,11]),n(t,[2,12]),n(t,[2,13]),n(t,[2,14]),n(t,[2,15]),n(t,[2,16]),n(t,[2,4]),n(t,[2,9]),n(t,[2,10])],defaultActions:{},parseError:s(function(r,a){if(a.recoverable)this.trace(r);else{var h=new Error(r);throw h.hash=a,h}},"parseError"),parse:s(function(r){var a=this,h=[0],u=[],y=[null],o=[],S=this.table,k="",M=0,C=0,B=2,J=1,O=o.slice.call(arguments,1),v=Object.create(this.lexer),N={yy:{}};for(var L in this.yy)Object.prototype.hasOwnProperty.call(this.yy,L)&&(N.yy[L]=this.yy[L]);v.setInput(r,N.yy),N.yy.lexer=v,N.yy.parser=this,typeof v.yylloc>"u"&&(v.yylloc={});var b=v.yylloc;o.push(b);var $=v.options&&v.options.ranges;typeof N.yy.parseError=="function"?this.parseError=N.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function R(T){h.length=h.length-2*T,y.length=y.length-T,o.length=o.length-T}s(R,"popStack");function A(){var T;return T=u.pop()||v.lex()||J,typeof T!="number"&&(T instanceof Array&&(u=T,T=u.pop()),T=a.symbols_[T]||T),T}s(A,"lex");for(var w,H,I,K,F={},j,P,et,G;;){if(H=h[h.length-1],this.defaultActions[H]?I=this.defaultActions[H]:((w===null||typeof w>"u")&&(w=A()),I=S[H]&&S[H][w]),typeof I>"u"||!I.length||!I[0]){var Q="";G=[];for(j in S[H])this.terminals_[j]&&j>B&&G.push("'"+this.terminals_[j]+"'");v.showPosition?Q="Parse error on line "+(M+1)+`: +`+v.showPosition()+` +Expecting `+G.join(", ")+", got '"+(this.terminals_[w]||w)+"'":Q="Parse error on line "+(M+1)+": Unexpected "+(w==J?"end of input":"'"+(this.terminals_[w]||w)+"'"),this.parseError(Q,{text:v.match,token:this.terminals_[w]||w,line:v.yylineno,loc:b,expected:G})}if(I[0]instanceof Array&&I.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+w);switch(I[0]){case 1:h.push(w),y.push(v.yytext),o.push(v.yylloc),h.push(I[1]),w=null,C=v.yyleng,k=v.yytext,M=v.yylineno,b=v.yylloc;break;case 2:if(P=this.productions_[I[1]][1],F.$=y[y.length-P],F._$={first_line:o[o.length-(P||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(P||1)].first_column,last_column:o[o.length-1].last_column},$&&(F._$.range=[o[o.length-(P||1)].range[0],o[o.length-1].range[1]]),K=this.performAction.apply(F,[k,C,M,N.yy,I[1],y,o].concat(O)),typeof K<"u")return K;P&&(h=h.slice(0,-1*P*2),y=y.slice(0,-1*P),o=o.slice(0,-1*P)),h.push(this.productions_[I[1]][0]),y.push(F.$),o.push(F._$),et=S[h[h.length-2]][h[h.length-1]],h.push(et);break;case 3:return!0}}return!0},"parse")},x=function(){var f={EOF:1,parseError:s(function(a,h){if(this.yy.parser)this.yy.parser.parseError(a,h);else throw new Error(a)},"parseError"),setInput:s(function(r,a){return this.yy=a||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:s(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var a=r.match(/(?:\r\n?|\n).*/g);return a?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:s(function(r){var a=r.length,h=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-a),this.offset-=a;var u=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),h.length-1&&(this.yylineno-=h.length-1);var y=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:h?(h.length===u.length?this.yylloc.first_column:0)+u[u.length-h.length].length-h[0].length:this.yylloc.first_column-a},this.options.ranges&&(this.yylloc.range=[y[0],y[0]+this.yyleng-a]),this.yyleng=this.yytext.length,this},"unput"),more:s(function(){return this._more=!0,this},"more"),reject:s(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:s(function(r){this.unput(this.match.slice(r))},"less"),pastInput:s(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:s(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:s(function(){var r=this.pastInput(),a=new Array(r.length+1).join("-");return r+this.upcomingInput()+` +`+a+"^"},"showPosition"),test_match:s(function(r,a){var h,u,y;if(this.options.backtrack_lexer&&(y={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(y.yylloc.range=this.yylloc.range.slice(0))),u=r[0].match(/(?:\r\n?|\n).*/g),u&&(this.yylineno+=u.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:u?u[u.length-1].length-u[u.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+r[0].length},this.yytext+=r[0],this.match+=r[0],this.matches=r,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(r[0].length),this.matched+=r[0],h=this.performAction.call(this,this.yy,this,a,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),h)return h;if(this._backtrack){for(var o in y)this[o]=y[o];return!1}return!1},"test_match"),next:s(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var r,a,h,u;this._more||(this.yytext="",this.match="");for(var y=this._currentRules(),o=0;oa[0].length)){if(a=h,u=o,this.options.backtrack_lexer){if(r=this.test_match(h,y[o]),r!==!1)return r;if(this._backtrack){a=!1;continue}else return!1}else if(!this.options.flex)break}return a?(r=this.test_match(a,y[u]),r!==!1?r:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:s(function(){var a=this.next();return a||this.lex()},"lex"),begin:s(function(a){this.conditionStack.push(a)},"begin"),popState:s(function(){var a=this.conditionStack.length-1;return a>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:s(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:s(function(a){return a=this.conditionStack.length-1-Math.abs(a||0),a>=0?this.conditionStack[a]:"INITIAL"},"topState"),pushState:s(function(a){this.begin(a)},"pushState"),stateStackSize:s(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:s(function(a,h,u,y){switch(u){case 0:break;case 1:break;case 2:return 10;case 3:break;case 4:break;case 5:return 4;case 6:return 11;case 7:return this.begin("acc_title"),12;case 8:return this.popState(),"acc_title_value";case 9:return this.begin("acc_descr"),14;case 10:return this.popState(),"acc_descr_value";case 11:this.begin("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 17;case 15:return 21;case 16:return 20;case 17:return 6;case 18:return"INVALID"}},"anonymous"),rules:[/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:timeline\b)/i,/^(?:title\s[^\n]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:section\s[^:\n]+)/i,/^(?::\s(?:[^:\n]|:(?!\s))+)/i,/^(?:[^#:\n]+)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,9,11,14,15,16,17,18],inclusive:!0}}};return f}();p.lexer=x;function _(){this.yy={}}return s(_,"Parser"),_.prototype=p,p.Parser=_,new _}();X.parser=X;var Tt=X,at={};wt(at,{addEvent:()=>yt,addSection:()=>ht,addTask:()=>pt,addTaskOrg:()=>gt,clear:()=>ct,default:()=>It,getCommonDb:()=>ot,getSections:()=>dt,getTasks:()=>ut});var V="",lt=0,Y=[],U=[],W=[],ot=s(()=>St,"getCommonDb"),ct=s(function(){Y.length=0,U.length=0,V="",W.length=0,Et()},"clear"),ht=s(function(n){V=n,Y.push(n)},"addSection"),dt=s(function(){return Y},"getSections"),ut=s(function(){let n=it();const t=100;let e=0;for(;!n&&ee.id===lt-1).events.push(n)},"addEvent"),gt=s(function(n){const t={section:V,type:V,description:n,task:n,classes:[]};U.push(t)},"addTaskOrg"),it=s(function(){const n=s(function(e){return W[e].processed},"compileTask");let t=!0;for(const[e,l]of W.entries())n(e),t=t&&l.processed;return t},"compileTasks"),It={clear:ct,getCommonDb:ot,addSection:ht,getSections:dt,getTasks:ut,addTask:pt,addTaskOrg:gt,addEvent:yt},Nt=12,Z=s(function(n,t){const e=n.append("rect");return e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),e.attr("rx",t.rx),e.attr("ry",t.ry),t.class!==void 0&&e.attr("class",t.class),e},"drawRect"),Lt=s(function(n,t){const l=n.append("circle").attr("cx",t.cx).attr("cy",t.cy).attr("class","face").attr("r",15).attr("stroke-width",2).attr("overflow","visible"),i=n.append("g");i.append("circle").attr("cx",t.cx-15/3).attr("cy",t.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),i.append("circle").attr("cx",t.cx+15/3).attr("cy",t.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666");function d(m){const p=nt().startAngle(Math.PI/2).endAngle(3*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);m.append("path").attr("class","mouth").attr("d",p).attr("transform","translate("+t.cx+","+(t.cy+2)+")")}s(d,"smile");function c(m){const p=nt().startAngle(3*Math.PI/2).endAngle(5*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);m.append("path").attr("class","mouth").attr("d",p).attr("transform","translate("+t.cx+","+(t.cy+7)+")")}s(c,"sad");function g(m){m.append("line").attr("class","mouth").attr("stroke",2).attr("x1",t.cx-5).attr("y1",t.cy+7).attr("x2",t.cx+5).attr("y2",t.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}return s(g,"ambivalent"),t.score>3?d(i):t.score<3?c(i):g(i),l},"drawFace"),Mt=s(function(n,t){const e=n.append("circle");return e.attr("cx",t.cx),e.attr("cy",t.cy),e.attr("class","actor-"+t.pos),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("r",t.r),e.class!==void 0&&e.attr("class",e.class),t.title!==void 0&&e.append("title").text(t.title),e},"drawCircle"),ft=s(function(n,t){const e=t.text.replace(//gi," "),l=n.append("text");l.attr("x",t.x),l.attr("y",t.y),l.attr("class","legend"),l.style("text-anchor",t.anchor),t.class!==void 0&&l.attr("class",t.class);const i=l.append("tspan");return i.attr("x",t.x+t.textMargin*2),i.text(e),l},"drawText"),$t=s(function(n,t){function e(i,d,c,g,m){return i+","+d+" "+(i+c)+","+d+" "+(i+c)+","+(d+g-m)+" "+(i+c-m*1.2)+","+(d+g)+" "+i+","+(d+g)}s(e,"genPoints");const l=n.append("polygon");l.attr("points",e(t.x,t.y,50,20,7)),l.attr("class","labelBox"),t.y=t.y+t.labelMargin,t.x=t.x+.5*t.labelMargin,ft(n,t)},"drawLabel"),Ht=s(function(n,t,e){const l=n.append("g"),i=D();i.x=t.x,i.y=t.y,i.fill=t.fill,i.width=e.width,i.height=e.height,i.class="journey-section section-type-"+t.num,i.rx=3,i.ry=3,Z(l,i),mt(e)(t.text,l,i.x,i.y,i.width,i.height,{class:"journey-section section-type-"+t.num},e,t.colour)},"drawSection"),rt=-1,Pt=s(function(n,t,e){const l=t.x+e.width/2,i=n.append("g");rt++;const d=300+5*30;i.append("line").attr("id","task"+rt).attr("x1",l).attr("y1",t.y).attr("x2",l).attr("y2",d).attr("class","task-line").attr("stroke-width","1px").attr("stroke-dasharray","4 2").attr("stroke","#666"),Lt(i,{cx:l,cy:300+(5-t.score)*30,score:t.score});const c=D();c.x=t.x,c.y=t.y,c.fill=t.fill,c.width=e.width,c.height=e.height,c.class="task task-type-"+t.num,c.rx=3,c.ry=3,Z(i,c),mt(e)(t.task,i,c.x,c.y,c.width,c.height,{class:"task"},e,t.colour)},"drawTask"),At=s(function(n,t){Z(n,{x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,class:"rect"}).lower()},"drawBackgroundRect"),Ct=s(function(){return{x:0,y:0,fill:void 0,"text-anchor":"start",width:100,height:100,textMargin:0,rx:0,ry:0}},"getTextObj"),D=s(function(){return{x:0,y:0,width:100,anchor:"start",height:100,rx:0,ry:0}},"getNoteRect"),mt=function(){function n(i,d,c,g,m,p,x,_){const f=d.append("text").attr("x",c+m/2).attr("y",g+p/2+5).style("font-color",_).style("text-anchor","middle").text(i);l(f,x)}s(n,"byText");function t(i,d,c,g,m,p,x,_,f){const{taskFontSize:r,taskFontFamily:a}=_,h=i.split(//gi);for(let u=0;u)/).reverse(),i,d=[],c=1.1,g=e.attr("y"),m=parseFloat(e.attr("dy")),p=e.text(null).append("tspan").attr("x",0).attr("y",g).attr("dy",m+"em");for(let x=0;xt||i==="
    ")&&(d.pop(),p.text(d.join(" ").trim()),i==="
    "?d=[""]:d=[i],p=e.append("tspan").attr("x",0).attr("y",g).attr("dy",c+"em").text(i))})}s(tt,"wrap");var zt=s(function(n,t,e,l){var _;const i=e%Nt-1,d=n.append("g");t.section=i,d.attr("class",(t.class?t.class+" ":"")+"timeline-node "+("section-"+i));const c=d.append("g"),g=d.append("g"),p=g.append("text").text(t.descr).attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle").call(tt,t.width).node().getBBox(),x=(_=l.fontSize)!=null&&_.replace?l.fontSize.replace("px",""):l.fontSize;return t.height=p.height+x*1.1*.5+t.padding,t.height=Math.max(t.height,t.maxHeight),t.width=t.width+2*t.padding,g.attr("transform","translate("+t.width/2+", "+t.padding/2+")"),Vt(c,t,i,l),t},"drawNode"),Ft=s(function(n,t,e){var g;const l=n.append("g"),d=l.append("text").text(t.descr).attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle").call(tt,t.width).node().getBBox(),c=(g=e.fontSize)!=null&&g.replace?e.fontSize.replace("px",""):e.fontSize;return l.remove(),d.height+c*1.1*.5+t.padding},"getVirtualNodeHeight"),Vt=s(function(n,t,e){n.append("path").attr("id","node-"+t.id).attr("class","node-bkg node-"+t.type).attr("d",`M0 ${t.height-5} v${-t.height+2*5} q0,-5 5,-5 h${t.width-2*5} q5,0 5,5 v${t.height-5} H0 Z`),n.append("line").attr("class","node-line-"+e).attr("x1",0).attr("y1",t.height).attr("x2",t.width).attr("y2",t.height)},"defaultBkg"),z={drawRect:Z,drawCircle:Mt,drawSection:Ht,drawText:ft,drawLabel:$t,drawTask:Pt,drawBackgroundRect:At,getTextObj:Ct,getNoteRect:D,initGraphics:Rt,drawNode:zt,getVirtualNodeHeight:Ft},Wt=s(function(n,t,e,l){var O,v,N;const i=xt(),d=((O=i.timeline)==null?void 0:O.leftMargin)??50;E.debug("timeline",l.db);const c=i.securityLevel;let g;c==="sandbox"&&(g=q("#i"+t));const p=(c==="sandbox"?q(g.nodes()[0].contentDocument.body):q("body")).select("#"+t);p.append("g");const x=l.db.getTasks(),_=l.db.getCommonDb().getDiagramTitle();E.debug("task",x),z.initGraphics(p);const f=l.db.getSections();E.debug("sections",f);let r=0,a=0,h=0,u=0,y=50+d,o=50;u=50;let S=0,k=!0;f.forEach(function(L){const b={number:S,descr:L,section:S,width:150,padding:20,maxHeight:r},$=z.getVirtualNodeHeight(p,b,i);E.debug("sectionHeight before draw",$),r=Math.max(r,$+20)});let M=0,C=0;E.debug("tasks.length",x.length);for(const[L,b]of x.entries()){const $={number:L,descr:b,section:b.section,width:150,padding:20,maxHeight:a},R=z.getVirtualNodeHeight(p,$,i);E.debug("taskHeight before draw",R),a=Math.max(a,R+20),M=Math.max(M,b.events.length);let A=0;for(const w of b.events){const H={descr:w,section:b.section,number:b.section,width:150,padding:20,maxHeight:50};A+=z.getVirtualNodeHeight(p,H,i)}b.events.length>0&&(A+=(b.events.length-1)*10),C=Math.max(C,A)}E.debug("maxSectionHeight before draw",r),E.debug("maxTaskHeight before draw",a),f&&f.length>0?f.forEach(L=>{const b=x.filter(w=>w.section===L),$={number:S,descr:L,section:S,width:200*Math.max(b.length,1)-50,padding:20,maxHeight:r};E.debug("sectionNode",$);const R=p.append("g"),A=z.drawNode(R,$,S,i);E.debug("sectionNode output",A),R.attr("transform",`translate(${y}, ${u})`),o+=r+50,b.length>0&&st(p,b,S,y,o,a,i,M,C,r,!1),y+=200*Math.max(b.length,1),o=u,S++}):(k=!1,st(p,x,S,y,o,a,i,M,C,r,!0));const B=p.node().getBBox();E.debug("bounds",B),_&&p.append("text").text(_).attr("x",B.width/2-d).attr("font-size","4ex").attr("font-weight","bold").attr("y",20),h=k?r+a+150:a+100,p.append("g").attr("class","lineWrapper").append("line").attr("x1",d).attr("y1",h).attr("x2",B.width+3*d).attr("y2",h).attr("stroke-width",4).attr("stroke","black").attr("marker-end","url(#arrowhead)"),kt(void 0,p,((v=i.timeline)==null?void 0:v.padding)??50,((N=i.timeline)==null?void 0:N.useMaxWidth)??!1)},"draw"),st=s(function(n,t,e,l,i,d,c,g,m,p,x){var _;for(const f of t){const r={descr:f.task,section:e,number:e,width:150,padding:20,maxHeight:d};E.debug("taskNode",r);const a=n.append("g").attr("class","taskWrapper"),u=z.drawNode(a,r,e,c).height;if(E.debug("taskHeight after draw",u),a.attr("transform",`translate(${l}, ${i})`),d=Math.max(d,u),f.events){const y=n.append("g").attr("class","lineWrapper");let o=d;i+=100,o=o+Bt(n,f.events,e,l,i,c),i-=100,y.append("line").attr("x1",l+190/2).attr("y1",i+d).attr("x2",l+190/2).attr("y2",i+d+100+m+100).attr("stroke-width",2).attr("stroke","black").attr("marker-end","url(#arrowhead)").attr("stroke-dasharray","5,5")}l=l+200,x&&!((_=c.timeline)!=null&&_.disableMulticolor)&&e++}i=i-10},"drawTasks"),Bt=s(function(n,t,e,l,i,d){let c=0;const g=i;i=i+100;for(const m of t){const p={descr:m,section:e,number:e,width:150,padding:20,maxHeight:50};E.debug("eventNode",p);const x=n.append("g").attr("class","eventWrapper"),f=z.drawNode(x,p,e,d).height;c=c+f,x.attr("transform",`translate(${l}, ${i})`),i=i+10+f}return i=g,c},"drawEvents"),Ot={setConf:s(()=>{},"setConf"),draw:Wt},jt=s(n=>{let t="";for(let e=0;e` + .edge { + stroke-width: 3; + } + ${jt(n)} + .section-root rect, .section-root path, .section-root circle { + fill: ${n.git0}; + } + .section-root text { + fill: ${n.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .eventWrapper { + filter: brightness(120%); + } +`,"getStyles"),qt=Gt,Jt={db:at,renderer:Ot,parser:Tt,styles:qt};export{Jt as diagram}; diff --git a/assets/chunks/treemap-KMMF4GRG.CcUr4GSN.js b/assets/chunks/treemap-KMMF4GRG.CcUr4GSN.js new file mode 100644 index 000000000..b15a9e8a8 --- /dev/null +++ b/assets/chunks/treemap-KMMF4GRG.CcUr4GSN.js @@ -0,0 +1,128 @@ +var Lp=Object.defineProperty;var Mp=(t,e,n)=>e in t?Lp(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var $t=(t,e,n)=>Mp(t,typeof e!="symbol"?e+"":e,n);import{V as Bt}from"./framework.CgT1UzWm.js";import{m as ot,f as Dp,d as Fp}from"./min.fO5GJb76.js";import{b as Gp,l as Up,d as Bp,m as jp,r as Pl,n as sa}from"./baseUniq.BHxmztwl.js";import{bm as Kp}from"./theme.kqgpP4eL.js";function Hp(t,e){return Gp(ot(t,e))}function Wp(t,e){return t&&t.length?Up(t,Bp(e)):[]}function ue(t){return typeof t=="object"&&t!==null&&typeof t.$type=="string"}function ze(t){return typeof t=="object"&&t!==null&&typeof t.$refText=="string"}function zp(t){return typeof t=="object"&&t!==null&&typeof t.name=="string"&&typeof t.type=="string"&&typeof t.path=="string"}function wi(t){return typeof t=="object"&&t!==null&&ue(t.container)&&ze(t.reference)&&typeof t.message=="string"}class Uf{constructor(){this.subtypes={},this.allSubtypes={}}isInstance(e,n){return ue(e)&&this.isSubtype(e.$type,n)}isSubtype(e,n){if(e===n)return!0;let r=this.subtypes[e];r||(r=this.subtypes[e]={});const i=r[n];if(i!==void 0)return i;{const s=this.computeIsSubtype(e,n);return r[n]=s,s}}getAllSubTypes(e){const n=this.allSubtypes[e];if(n)return n;{const r=this.getAllTypes(),i=[];for(const s of r)this.isSubtype(s,e)&&i.push(s);return this.allSubtypes[e]=i,i}}}function Lr(t){return typeof t=="object"&&t!==null&&Array.isArray(t.content)}function Bf(t){return typeof t=="object"&&t!==null&&typeof t.tokenType=="object"}function jf(t){return Lr(t)&&typeof t.fullText=="string"}class ne{constructor(e,n){this.startFn=e,this.nextFn=n}iterator(){const e={state:this.startFn(),next:()=>this.nextFn(e.state),[Symbol.iterator]:()=>e};return e}[Symbol.iterator](){return this.iterator()}isEmpty(){return!!this.iterator().next().done}count(){const e=this.iterator();let n=0,r=e.next();for(;!r.done;)n++,r=e.next();return n}toArray(){const e=[],n=this.iterator();let r;do r=n.next(),r.value!==void 0&&e.push(r.value);while(!r.done);return e}toSet(){return new Set(this)}toMap(e,n){const r=this.map(i=>[e?e(i):i,n?n(i):i]);return new Map(r)}toString(){return this.join()}concat(e){return new ne(()=>({first:this.startFn(),firstDone:!1,iterator:e[Symbol.iterator]()}),n=>{let r;if(!n.firstDone){do if(r=this.nextFn(n.first),!r.done)return r;while(!r.done);n.firstDone=!0}do if(r=n.iterator.next(),!r.done)return r;while(!r.done);return Ae})}join(e=","){const n=this.iterator();let r="",i,s=!1;do i=n.next(),i.done||(s&&(r+=e),r+=Vp(i.value)),s=!0;while(!i.done);return r}indexOf(e,n=0){const r=this.iterator();let i=0,s=r.next();for(;!s.done;){if(i>=n&&s.value===e)return i;s=r.next(),i++}return-1}every(e){const n=this.iterator();let r=n.next();for(;!r.done;){if(!e(r.value))return!1;r=n.next()}return!0}some(e){const n=this.iterator();let r=n.next();for(;!r.done;){if(e(r.value))return!0;r=n.next()}return!1}forEach(e){const n=this.iterator();let r=0,i=n.next();for(;!i.done;)e(i.value,r),i=n.next(),r++}map(e){return new ne(this.startFn,n=>{const{done:r,value:i}=this.nextFn(n);return r?Ae:{done:!1,value:e(i)}})}filter(e){return new ne(this.startFn,n=>{let r;do if(r=this.nextFn(n),!r.done&&e(r.value))return r;while(!r.done);return Ae})}nonNullable(){return this.filter(e=>e!=null)}reduce(e,n){const r=this.iterator();let i=n,s=r.next();for(;!s.done;)i===void 0?i=s.value:i=e(i,s.value),s=r.next();return i}reduceRight(e,n){return this.recursiveReduce(this.iterator(),e,n)}recursiveReduce(e,n,r){const i=e.next();if(i.done)return r;const s=this.recursiveReduce(e,n,r);return s===void 0?i.value:n(s,i.value)}find(e){const n=this.iterator();let r=n.next();for(;!r.done;){if(e(r.value))return r.value;r=n.next()}}findIndex(e){const n=this.iterator();let r=0,i=n.next();for(;!i.done;){if(e(i.value))return r;i=n.next(),r++}return-1}includes(e){const n=this.iterator();let r=n.next();for(;!r.done;){if(r.value===e)return!0;r=n.next()}return!1}flatMap(e){return new ne(()=>({this:this.startFn()}),n=>{do{if(n.iterator){const s=n.iterator.next();if(s.done)n.iterator=void 0;else return s}const{done:r,value:i}=this.nextFn(n.this);if(!r){const s=e(i);if(Hi(s))n.iterator=s[Symbol.iterator]();else return{done:!1,value:s}}}while(n.iterator);return Ae})}flat(e){if(e===void 0&&(e=1),e<=0)return this;const n=e>1?this.flat(e-1):this;return new ne(()=>({this:n.startFn()}),r=>{do{if(r.iterator){const a=r.iterator.next();if(a.done)r.iterator=void 0;else return a}const{done:i,value:s}=n.nextFn(r.this);if(!i)if(Hi(s))r.iterator=s[Symbol.iterator]();else return{done:!1,value:s}}while(r.iterator);return Ae})}head(){const n=this.iterator().next();if(!n.done)return n.value}tail(e=1){return new ne(()=>{const n=this.startFn();for(let r=0;r({size:0,state:this.startFn()}),n=>(n.size++,n.size>e?Ae:this.nextFn(n.state)))}distinct(e){return new ne(()=>({set:new Set,internalState:this.startFn()}),n=>{let r;do if(r=this.nextFn(n.internalState),!r.done){const i=e?e(r.value):r.value;if(!n.set.has(i))return n.set.add(i),r}while(!r.done);return Ae})}exclude(e,n){const r=new Set;for(const i of e){const s=n?n(i):i;r.add(s)}return this.filter(i=>{const s=n?n(i):i;return!r.has(s)})}}function Vp(t){return typeof t=="string"?t:typeof t>"u"?"undefined":typeof t.toString=="function"?t.toString():Object.prototype.toString.call(t)}function Hi(t){return!!t&&typeof t[Symbol.iterator]=="function"}const qp=new ne(()=>{},()=>Ae),Ae=Object.freeze({done:!0,value:void 0});function re(...t){if(t.length===1){const e=t[0];if(e instanceof ne)return e;if(Hi(e))return new ne(()=>e[Symbol.iterator](),n=>n.next());if(typeof e.length=="number")return new ne(()=>({index:0}),n=>n.index1?new ne(()=>({collIndex:0,arrIndex:0}),e=>{do{if(e.iterator){const n=e.iterator.next();if(!n.done)return n;e.iterator=void 0}if(e.array){if(e.arrIndex({iterators:r!=null&&r.includeRoot?[[e][Symbol.iterator]()]:[n(e)[Symbol.iterator]()],pruned:!1}),i=>{for(i.pruned&&(i.iterators.pop(),i.pruned=!1);i.iterators.length>0;){const a=i.iterators[i.iterators.length-1].next();if(a.done)i.iterators.pop();else return i.iterators.push(n(a.value)[Symbol.iterator]()),a}return Ae})}iterator(){const e={state:this.startFn(),next:()=>this.nextFn(e.state),prune:()=>{e.state.pruned=!0},[Symbol.iterator]:()=>e};return e}}var Ua;(function(t){function e(s){return s.reduce((a,o)=>a+o,0)}t.sum=e;function n(s){return s.reduce((a,o)=>a*o,0)}t.product=n;function r(s){return s.reduce((a,o)=>Math.min(a,o))}t.min=r;function i(s){return s.reduce((a,o)=>Math.max(a,o))}t.max=i})(Ua||(Ua={}));function Ba(t){return new Mo(t,e=>Lr(e)?e.content:[],{includeRoot:!0})}function Yp(t,e){for(;t.container;)if(t=t.container,t===e)return!0;return!1}function ja(t){return{start:{character:t.startColumn-1,line:t.startLine-1},end:{character:t.endColumn,line:t.endLine-1}}}function Wi(t){if(!t)return;const{offset:e,end:n,range:r}=t;return{range:r,offset:e,end:n,length:n-e}}var st;(function(t){t[t.Before=0]="Before",t[t.After=1]="After",t[t.OverlapFront=2]="OverlapFront",t[t.OverlapBack=3]="OverlapBack",t[t.Inside=4]="Inside",t[t.Outside=5]="Outside"})(st||(st={}));function Xp(t,e){if(t.end.linee.end.line||t.start.line===e.end.line&&t.start.character>=e.end.character)return st.After;const n=t.start.line>e.start.line||t.start.line===e.start.line&&t.start.character>=e.start.character,r=t.end.linest.After}const Zp=/^[\w\p{L}]$/u;function Qp(t,e){if(t){const n=em(t,!0);if(n&&Ll(n,e))return n;if(jf(t)){const r=t.content.findIndex(i=>!i.hidden);for(let i=r-1;i>=0;i--){const s=t.content[i];if(Ll(s,e))return s}}}}function Ll(t,e){return Bf(t)&&e.includes(t.tokenType.name)}function em(t,e=!0){for(;t.container;){const n=t.container;let r=n.content.indexOf(t);for(;r>0;){r--;const i=n.content[r];if(e||!i.hidden)return i}t=n}}class Kf extends Error{constructor(e,n){super(e?`${n} at ${e.range.start.line}:${e.range.start.character}`:n)}}function Wr(t){throw new Error("Error! The input value was not handled.")}const si="AbstractRule",ai="AbstractType",aa="Condition",Ml="TypeDefinition",oa="ValueLiteral",Xn="AbstractElement";function tm(t){return F.isInstance(t,Xn)}const oi="ArrayLiteral",li="ArrayType",Jn="BooleanLiteral";function nm(t){return F.isInstance(t,Jn)}const Zn="Conjunction";function rm(t){return F.isInstance(t,Zn)}const Qn="Disjunction";function im(t){return F.isInstance(t,Qn)}const ui="Grammar",la="GrammarImport",er="InferredType";function Hf(t){return F.isInstance(t,er)}const tr="Interface";function Wf(t){return F.isInstance(t,tr)}const ua="NamedArgument",nr="Negation";function sm(t){return F.isInstance(t,nr)}const ci="NumberLiteral",fi="Parameter",rr="ParameterReference";function am(t){return F.isInstance(t,rr)}const ir="ParserRule";function Ne(t){return F.isInstance(t,ir)}const di="ReferenceType",Ci="ReturnType";function om(t){return F.isInstance(t,Ci)}const sr="SimpleType";function lm(t){return F.isInstance(t,sr)}const hi="StringLiteral",cn="TerminalRule";function Jt(t){return F.isInstance(t,cn)}const ar="Type";function zf(t){return F.isInstance(t,ar)}const ca="TypeAttribute",pi="UnionType",or="Action";function $s(t){return F.isInstance(t,or)}const lr="Alternatives";function Vf(t){return F.isInstance(t,lr)}const ur="Assignment";function Kt(t){return F.isInstance(t,ur)}const cr="CharacterRange";function um(t){return F.isInstance(t,cr)}const fr="CrossReference";function Do(t){return F.isInstance(t,fr)}const dr="EndOfFile";function cm(t){return F.isInstance(t,dr)}const hr="Group";function Fo(t){return F.isInstance(t,hr)}const pr="Keyword";function Ht(t){return F.isInstance(t,pr)}const mr="NegatedToken";function fm(t){return F.isInstance(t,mr)}const gr="RegexToken";function dm(t){return F.isInstance(t,gr)}const yr="RuleCall";function Wt(t){return F.isInstance(t,yr)}const Tr="TerminalAlternatives";function hm(t){return F.isInstance(t,Tr)}const vr="TerminalGroup";function pm(t){return F.isInstance(t,vr)}const $r="TerminalRuleCall";function mm(t){return F.isInstance(t,$r)}const Rr="UnorderedGroup";function qf(t){return F.isInstance(t,Rr)}const Ar="UntilToken";function gm(t){return F.isInstance(t,Ar)}const Er="Wildcard";function ym(t){return F.isInstance(t,Er)}class Yf extends Uf{getAllTypes(){return[Xn,si,ai,or,lr,oi,li,ur,Jn,cr,aa,Zn,fr,Qn,dr,ui,la,hr,er,tr,pr,ua,mr,nr,ci,fi,rr,ir,di,gr,Ci,yr,sr,hi,Tr,vr,cn,$r,ar,ca,Ml,pi,Rr,Ar,oa,Er]}computeIsSubtype(e,n){switch(e){case or:case lr:case ur:case cr:case fr:case dr:case hr:case pr:case mr:case gr:case yr:case Tr:case vr:case $r:case Rr:case Ar:case Er:return this.isSubtype(Xn,n);case oi:case ci:case hi:return this.isSubtype(oa,n);case li:case di:case sr:case pi:return this.isSubtype(Ml,n);case Jn:return this.isSubtype(aa,n)||this.isSubtype(oa,n);case Zn:case Qn:case nr:case rr:return this.isSubtype(aa,n);case er:case tr:case ar:return this.isSubtype(ai,n);case ir:return this.isSubtype(si,n)||this.isSubtype(ai,n);case cn:return this.isSubtype(si,n);default:return!1}}getReferenceType(e){const n=`${e.container.$type}:${e.property}`;switch(n){case"Action:type":case"CrossReference:type":case"Interface:superTypes":case"ParserRule:returnType":case"SimpleType:typeRef":return ai;case"Grammar:hiddenTokens":case"ParserRule:hiddenTokens":case"RuleCall:rule":return si;case"Grammar:usedGrammars":return ui;case"NamedArgument:parameter":case"ParameterReference:parameter":return fi;case"TerminalRuleCall:rule":return cn;default:throw new Error(`${n} is not a valid reference id.`)}}getTypeMetaData(e){switch(e){case Xn:return{name:Xn,properties:[{name:"cardinality"},{name:"lookahead"}]};case oi:return{name:oi,properties:[{name:"elements",defaultValue:[]}]};case li:return{name:li,properties:[{name:"elementType"}]};case Jn:return{name:Jn,properties:[{name:"true",defaultValue:!1}]};case Zn:return{name:Zn,properties:[{name:"left"},{name:"right"}]};case Qn:return{name:Qn,properties:[{name:"left"},{name:"right"}]};case ui:return{name:ui,properties:[{name:"definesHiddenTokens",defaultValue:!1},{name:"hiddenTokens",defaultValue:[]},{name:"imports",defaultValue:[]},{name:"interfaces",defaultValue:[]},{name:"isDeclared",defaultValue:!1},{name:"name"},{name:"rules",defaultValue:[]},{name:"types",defaultValue:[]},{name:"usedGrammars",defaultValue:[]}]};case la:return{name:la,properties:[{name:"path"}]};case er:return{name:er,properties:[{name:"name"}]};case tr:return{name:tr,properties:[{name:"attributes",defaultValue:[]},{name:"name"},{name:"superTypes",defaultValue:[]}]};case ua:return{name:ua,properties:[{name:"calledByName",defaultValue:!1},{name:"parameter"},{name:"value"}]};case nr:return{name:nr,properties:[{name:"value"}]};case ci:return{name:ci,properties:[{name:"value"}]};case fi:return{name:fi,properties:[{name:"name"}]};case rr:return{name:rr,properties:[{name:"parameter"}]};case ir:return{name:ir,properties:[{name:"dataType"},{name:"definesHiddenTokens",defaultValue:!1},{name:"definition"},{name:"entry",defaultValue:!1},{name:"fragment",defaultValue:!1},{name:"hiddenTokens",defaultValue:[]},{name:"inferredType"},{name:"name"},{name:"parameters",defaultValue:[]},{name:"returnType"},{name:"wildcard",defaultValue:!1}]};case di:return{name:di,properties:[{name:"referenceType"}]};case Ci:return{name:Ci,properties:[{name:"name"}]};case sr:return{name:sr,properties:[{name:"primitiveType"},{name:"stringType"},{name:"typeRef"}]};case hi:return{name:hi,properties:[{name:"value"}]};case cn:return{name:cn,properties:[{name:"definition"},{name:"fragment",defaultValue:!1},{name:"hidden",defaultValue:!1},{name:"name"},{name:"type"}]};case ar:return{name:ar,properties:[{name:"name"},{name:"type"}]};case ca:return{name:ca,properties:[{name:"defaultValue"},{name:"isOptional",defaultValue:!1},{name:"name"},{name:"type"}]};case pi:return{name:pi,properties:[{name:"types",defaultValue:[]}]};case or:return{name:or,properties:[{name:"cardinality"},{name:"feature"},{name:"inferredType"},{name:"lookahead"},{name:"operator"},{name:"type"}]};case lr:return{name:lr,properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case ur:return{name:ur,properties:[{name:"cardinality"},{name:"feature"},{name:"lookahead"},{name:"operator"},{name:"terminal"}]};case cr:return{name:cr,properties:[{name:"cardinality"},{name:"left"},{name:"lookahead"},{name:"right"}]};case fr:return{name:fr,properties:[{name:"cardinality"},{name:"deprecatedSyntax",defaultValue:!1},{name:"lookahead"},{name:"terminal"},{name:"type"}]};case dr:return{name:dr,properties:[{name:"cardinality"},{name:"lookahead"}]};case hr:return{name:hr,properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"guardCondition"},{name:"lookahead"}]};case pr:return{name:pr,properties:[{name:"cardinality"},{name:"lookahead"},{name:"value"}]};case mr:return{name:mr,properties:[{name:"cardinality"},{name:"lookahead"},{name:"terminal"}]};case gr:return{name:gr,properties:[{name:"cardinality"},{name:"lookahead"},{name:"regex"}]};case yr:return{name:yr,properties:[{name:"arguments",defaultValue:[]},{name:"cardinality"},{name:"lookahead"},{name:"rule"}]};case Tr:return{name:Tr,properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case vr:return{name:vr,properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case $r:return{name:$r,properties:[{name:"cardinality"},{name:"lookahead"},{name:"rule"}]};case Rr:return{name:Rr,properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case Ar:return{name:Ar,properties:[{name:"cardinality"},{name:"lookahead"},{name:"terminal"}]};case Er:return{name:Er,properties:[{name:"cardinality"},{name:"lookahead"}]};default:return{name:e,properties:[]}}}}const F=new Yf;function Tm(t){for(const[e,n]of Object.entries(t))e.startsWith("$")||(Array.isArray(n)?n.forEach((r,i)=>{ue(r)&&(r.$container=t,r.$containerProperty=e,r.$containerIndex=i)}):ue(n)&&(n.$container=t,n.$containerProperty=e))}function Rs(t,e){let n=t;for(;n;){if(e(n))return n;n=n.$container}}function At(t){const n=Ka(t).$document;if(!n)throw new Error("AST node has no document.");return n}function Ka(t){for(;t.$container;)t=t.$container;return t}function Go(t,e){if(!t)throw new Error("Node must be an AstNode.");const n=e==null?void 0:e.range;return new ne(()=>({keys:Object.keys(t),keyIndex:0,arrayIndex:0}),r=>{for(;r.keyIndexGo(n,e))}function dn(t,e){if(!t)throw new Error("Root node must be an AstNode.");return new Mo(t,n=>Go(n,e),{includeRoot:!0})}function Dl(t,e){var n;if(!e)return!0;const r=(n=t.$cstNode)===null||n===void 0?void 0:n.range;return r?Jp(r,e):!1}function Xf(t){return new ne(()=>({keys:Object.keys(t),keyIndex:0,arrayIndex:0}),e=>{for(;e.keyIndex=this.input.length)throw Error("Unexpected end of input");this.idx++}loc(e){return{begin:e,end:this.idx}}}class As{visitChildren(e){for(const n in e){const r=e[n];e.hasOwnProperty(n)&&(r.type!==void 0?this.visit(r):Array.isArray(r)&&r.forEach(i=>{this.visit(i)},this))}}visit(e){switch(e.type){case"Pattern":this.visitPattern(e);break;case"Flags":this.visitFlags(e);break;case"Disjunction":this.visitDisjunction(e);break;case"Alternative":this.visitAlternative(e);break;case"StartAnchor":this.visitStartAnchor(e);break;case"EndAnchor":this.visitEndAnchor(e);break;case"WordBoundary":this.visitWordBoundary(e);break;case"NonWordBoundary":this.visitNonWordBoundary(e);break;case"Lookahead":this.visitLookahead(e);break;case"NegativeLookahead":this.visitNegativeLookahead(e);break;case"Character":this.visitCharacter(e);break;case"Set":this.visitSet(e);break;case"Group":this.visitGroup(e);break;case"GroupBackReference":this.visitGroupBackReference(e);break;case"Quantifier":this.visitQuantifier(e);break}this.visitChildren(e)}visitPattern(e){}visitFlags(e){}visitDisjunction(e){}visitAlternative(e){}visitStartAnchor(e){}visitEndAnchor(e){}visitWordBoundary(e){}visitNonWordBoundary(e){}visitLookahead(e){}visitNegativeLookahead(e){}visitCharacter(e){}visitSet(e){}visitGroup(e){}visitGroupBackReference(e){}visitQuantifier(e){}}const Em=/\r?\n/gm,Sm=new Zf;class xm extends As{constructor(){super(...arguments),this.isStarting=!0,this.endRegexpStack=[],this.multiline=!1}get endRegex(){return this.endRegexpStack.join("")}reset(e){this.multiline=!1,this.regex=e,this.startRegexp="",this.isStarting=!0,this.endRegexpStack=[]}visitGroup(e){e.quantifier&&(this.isStarting=!1,this.endRegexpStack=[])}visitCharacter(e){const n=String.fromCharCode(e.value);if(!this.multiline&&n===` +`&&(this.multiline=!0),e.quantifier)this.isStarting=!1,this.endRegexpStack=[];else{const r=Es(n);this.endRegexpStack.push(r),this.isStarting&&(this.startRegexp+=r)}}visitSet(e){if(!this.multiline){const n=this.regex.substring(e.loc.begin,e.loc.end),r=new RegExp(n);this.multiline=!!` +`.match(r)}if(e.quantifier)this.isStarting=!1,this.endRegexpStack=[];else{const n=this.regex.substring(e.loc.begin,e.loc.end);this.endRegexpStack.push(n),this.isStarting&&(this.startRegexp+=n)}}visitChildren(e){e.type==="Group"&&e.quantifier||super.visitChildren(e)}}const da=new xm;function _m(t){try{return typeof t=="string"&&(t=new RegExp(t)),t=t.toString(),da.reset(t),da.visit(Sm.pattern(t)),da.multiline}catch{return!1}}const Im=`\f +\r \v              \u2028\u2029   \uFEFF`.split("");function Ha(t){const e=typeof t=="string"?new RegExp(t):t;return Im.some(n=>e.test(n))}function Es(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function wm(t){return Array.prototype.map.call(t,e=>/\w/.test(e)?`[${e.toLowerCase()}${e.toUpperCase()}]`:Es(e)).join("")}function Cm(t,e){const n=km(t),r=e.match(n);return!!r&&r[0].length>0}function km(t){typeof t=="string"&&(t=new RegExp(t));const e=t,n=t.source;let r=0;function i(){let s="",a;function o(u){s+=n.substr(r,u),r+=u}function l(u){s+="(?:"+n.substr(r,u)+"|$)",r+=u}for(;r",r)-r+1);break;default:l(2);break}break;case"[":a=/\[(?:\\.|.)*?\]/g,a.lastIndex=r,a=a.exec(n)||[],l(a[0].length);break;case"|":case"^":case"$":case"*":case"+":case"?":o(1);break;case"{":a=/\{\d+,?\d*\}/g,a.lastIndex=r,a=a.exec(n),a?o(a[0].length):l(1);break;case"(":if(n[r+1]==="?")switch(n[r+2]){case":":s+="(?:",r+=3,s+=i()+"|$)";break;case"=":s+="(?=",r+=3,s+=i()+")";break;case"!":a=r,r+=3,i(),s+=n.substr(a,r-a);break;case"<":switch(n[r+3]){case"=":case"!":a=r,r+=4,i(),s+=n.substr(a,r-a);break;default:o(n.indexOf(">",r)-r+1),s+=i()+"|$)";break}break}else o(1),s+=i()+"|$)";break;case")":return++r,s;default:l(1);break}return s}return new RegExp(i(),t.flags)}function Nm(t){return t.rules.find(e=>Ne(e)&&e.entry)}function bm(t){return t.rules.filter(e=>Jt(e)&&e.hidden)}function Qf(t,e){const n=new Set,r=Nm(t);if(!r)return new Set(t.rules);const i=[r].concat(bm(t));for(const a of i)ed(a,n,e);const s=new Set;for(const a of t.rules)(n.has(a.name)||Jt(a)&&a.hidden)&&s.add(a);return s}function ed(t,e,n){e.add(t.name),zr(t).forEach(r=>{if(Wt(r)||n){const i=r.rule.ref;i&&!e.has(i.name)&&ed(i,e,n)}})}function Om(t){if(t.terminal)return t.terminal;if(t.type.ref){const e=nd(t.type.ref);return e==null?void 0:e.terminal}}function Pm(t){return t.hidden&&!Ha(Ko(t))}function Lm(t,e){return!t||!e?[]:Uo(t,e,t.astNode,!0)}function td(t,e,n){if(!t||!e)return;const r=Uo(t,e,t.astNode,!0);if(r.length!==0)return n!==void 0?n=Math.max(0,Math.min(n,r.length-1)):n=0,r[n]}function Uo(t,e,n,r){if(!r){const i=Rs(t.grammarSource,Kt);if(i&&i.feature===e)return[t]}return Lr(t)&&t.astNode===n?t.content.flatMap(i=>Uo(i,e,n,!1)):[]}function Mm(t,e,n){if(!t)return;const r=Dm(t,e,t==null?void 0:t.astNode);if(r.length!==0)return n!==void 0?n=Math.max(0,Math.min(n,r.length-1)):n=0,r[n]}function Dm(t,e,n){if(t.astNode!==n)return[];if(Ht(t.grammarSource)&&t.grammarSource.value===e)return[t];const r=Ba(t).iterator();let i;const s=[];do if(i=r.next(),!i.done){const a=i.value;a.astNode===n?Ht(a.grammarSource)&&a.grammarSource.value===e&&s.push(a):r.prune()}while(!i.done);return s}function Fm(t){var e;const n=t.astNode;for(;n===((e=t.container)===null||e===void 0?void 0:e.astNode);){const r=Rs(t.grammarSource,Kt);if(r)return r;t=t.container}}function nd(t){let e=t;return Hf(e)&&($s(e.$container)?e=e.$container.$container:Ne(e.$container)?e=e.$container:Wr(e.$container)),rd(t,e,new Map)}function rd(t,e,n){var r;function i(s,a){let o;return Rs(s,Kt)||(o=rd(a,a,n)),n.set(t,o),o}if(n.has(t))return n.get(t);n.set(t,void 0);for(const s of zr(e)){if(Kt(s)&&s.feature.toLowerCase()==="name")return n.set(t,s),s;if(Wt(s)&&Ne(s.rule.ref))return i(s,s.rule.ref);if(lm(s)&&(!((r=s.typeRef)===null||r===void 0)&&r.ref))return i(s,s.typeRef.ref)}}function id(t){return sd(t,new Set)}function sd(t,e){if(e.has(t))return!0;e.add(t);for(const n of zr(t))if(Wt(n)){if(!n.rule.ref||Ne(n.rule.ref)&&!sd(n.rule.ref,e))return!1}else{if(Kt(n))return!1;if($s(n))return!1}return!!t.definition}function Bo(t){if(t.inferredType)return t.inferredType.name;if(t.dataType)return t.dataType;if(t.returnType){const e=t.returnType.ref;if(e){if(Ne(e))return e.name;if(Wf(e)||zf(e))return e.name}}}function jo(t){var e;if(Ne(t))return id(t)?t.name:(e=Bo(t))!==null&&e!==void 0?e:t.name;if(Wf(t)||zf(t)||om(t))return t.name;if($s(t)){const n=Gm(t);if(n)return n}else if(Hf(t))return t.name;throw new Error("Cannot get name of Unknown Type")}function Gm(t){var e;if(t.inferredType)return t.inferredType.name;if(!((e=t.type)===null||e===void 0)&&e.ref)return jo(t.type.ref)}function Um(t){var e,n,r;return Jt(t)?(n=(e=t.type)===null||e===void 0?void 0:e.name)!==null&&n!==void 0?n:"string":(r=Bo(t))!==null&&r!==void 0?r:t.name}function Ko(t){const e={s:!1,i:!1,u:!1},n=Bn(t.definition,e),r=Object.entries(e).filter(([,i])=>i).map(([i])=>i).join("");return new RegExp(n,r)}const Ho=/[\s\S]/.source;function Bn(t,e){if(hm(t))return Bm(t);if(pm(t))return jm(t);if(um(t))return Wm(t);if(mm(t)){const n=t.rule.ref;if(!n)throw new Error("Missing rule reference.");return lt(Bn(n.definition),{cardinality:t.cardinality,lookahead:t.lookahead})}else{if(fm(t))return Hm(t);if(gm(t))return Km(t);if(dm(t)){const n=t.regex.lastIndexOf("/"),r=t.regex.substring(1,n),i=t.regex.substring(n+1);return e&&(e.i=i.includes("i"),e.s=i.includes("s"),e.u=i.includes("u")),lt(r,{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1})}else{if(ym(t))return lt(Ho,{cardinality:t.cardinality,lookahead:t.lookahead});throw new Error(`Invalid terminal element: ${t==null?void 0:t.$type}`)}}}function Bm(t){return lt(t.elements.map(e=>Bn(e)).join("|"),{cardinality:t.cardinality,lookahead:t.lookahead})}function jm(t){return lt(t.elements.map(e=>Bn(e)).join(""),{cardinality:t.cardinality,lookahead:t.lookahead})}function Km(t){return lt(`${Ho}*?${Bn(t.terminal)}`,{cardinality:t.cardinality,lookahead:t.lookahead})}function Hm(t){return lt(`(?!${Bn(t.terminal)})${Ho}*?`,{cardinality:t.cardinality,lookahead:t.lookahead})}function Wm(t){return t.right?lt(`[${ha(t.left)}-${ha(t.right)}]`,{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1}):lt(ha(t.left),{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1})}function ha(t){return Es(t.value)}function lt(t,e){var n;return(e.wrap!==!1||e.lookahead)&&(t=`(${(n=e.lookahead)!==null&&n!==void 0?n:""}${t})`),e.cardinality?`${t}${e.cardinality}`:t}function zm(t){const e=[],n=t.Grammar;for(const r of n.rules)Jt(r)&&Pm(r)&&_m(Ko(r))&&e.push(r.name);return{multilineCommentRules:e,nameRegexp:Zp}}var ad=typeof global=="object"&&global&&global.Object===Object&&global,Vm=typeof self=="object"&&self&&self.Object===Object&&self,Ye=ad||Vm||Function("return this")(),be=Ye.Symbol,od=Object.prototype,qm=od.hasOwnProperty,Ym=od.toString,zn=be?be.toStringTag:void 0;function Xm(t){var e=qm.call(t,zn),n=t[zn];try{t[zn]=void 0;var r=!0}catch{}var i=Ym.call(t);return r&&(e?t[zn]=n:delete t[zn]),i}var Jm=Object.prototype,Zm=Jm.toString;function Qm(t){return Zm.call(t)}var eg="[object Null]",tg="[object Undefined]",Ul=be?be.toStringTag:void 0;function Nt(t){return t==null?t===void 0?tg:eg:Ul&&Ul in Object(t)?Xm(t):Qm(t)}function Ue(t){return t!=null&&typeof t=="object"}var ng="[object Symbol]";function Ss(t){return typeof t=="symbol"||Ue(t)&&Nt(t)==ng}function xs(t,e){for(var n=-1,r=t==null?0:t.length,i=Array(r);++n0){if(++e>=Og)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}function Dg(t){return function(){return t}}var qi=function(){try{var t=Qt(Object,"defineProperty");return t({},"",{}),t}catch{}}(),Fg=qi?function(t,e){return qi(t,"toString",{configurable:!0,enumerable:!1,value:Dg(e),writable:!0})}:bn,Gg=Mg(Fg);function ud(t,e){for(var n=-1,r=t==null?0:t.length;++n-1}var jg=9007199254740991,Kg=/^(?:0|[1-9]\d*)$/;function Is(t,e){var n=typeof t;return e=e??jg,!!e&&(n=="number"||n!="symbol"&&Kg.test(t))&&t>-1&&t%1==0&&t-1&&t%1==0&&t<=Vg}function Xe(t){return t!=null&&qo(t.length)&&!ht(t)}function dd(t,e,n){if(!Oe(n))return!1;var r=typeof e;return(r=="number"?Xe(n)&&Is(e,n.length):r=="string"&&e in n)?Vr(n[e],t):!1}function qg(t){return Vo(function(e,n){var r=-1,i=n.length,s=i>1?n[i-1]:void 0,a=i>2?n[2]:void 0;for(s=t.length>3&&typeof s=="function"?(i--,s):void 0,a&&dd(n[0],n[1],a)&&(s=i<3?void 0:s,i=1),e=Object(e);++r-1}function sT(t,e){var n=this.__data__,r=Ns(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this}function pt(t){var e=-1,n=t==null?0:t.length;for(this.clear();++ei?0:i+e),n=n>i?i:n,n<0&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var s=Array(i);++ro))return!1;var u=s.get(t),c=s.get(e);if(u&&c)return u==e&&c==t;var f=-1,d=!0,h=n&t$?new On:void 0;for(s.set(t,e),s.set(e,t);++f2?e[2]:void 0;for(i&&dd(e[0],e[1],i)&&(r=1);++n=W$&&(s=il,a=!1,e=new On(e));e:for(;++i-1?i[s?e[a]:a]:void 0}}var J$=Math.max;function Z$(t,e,n){var r=t==null?0:t.length;if(!r)return-1;var i=n==null?0:_s(n);return i<0&&(i=J$(r+i,0)),cd(t,Je(e),i)}var Ln=X$(Z$);function Be(t){return t&&t.length?t[0]:void 0}function Q$(t,e){var n=-1,r=Xe(t)?Array(t.length):[];return en(t,function(i,s,a){r[++n]=e(i,s,a)}),r}function I(t,e){var n=M(t)?xs:Q$;return n(t,Je(e))}function ke(t,e){return tl(I(t,e))}var eR=Object.prototype,tR=eR.hasOwnProperty,nR=K$(function(t,e,n){tR.call(t,n)?t[n].push(e):zo(t,n,[e])}),rR=Object.prototype,iR=rR.hasOwnProperty;function sR(t,e){return t!=null&&iR.call(t,e)}function w(t,e){return t!=null&&bd(t,e,sR)}var aR="[object String]";function je(t){return typeof t=="string"||!M(t)&&Ue(t)&&Nt(t)==aR}function oR(t,e){return xs(e,function(n){return t[n]})}function Z(t){return t==null?[]:oR(t,Pe(t))}var lR=Math.max;function ge(t,e,n,r){t=Xe(t)?t:Z(t),n=n?_s(n):0;var i=t.length;return n<0&&(n=lR(i+n,0)),je(t)?n<=i&&t.indexOf(e,n)>-1:!!i&&Wo(t,e,n)>-1}function yu(t,e,n){var r=t==null?0:t.length;if(!r)return-1;var i=0;return Wo(t,e,i)}var uR="[object Map]",cR="[object Set]",fR=Object.prototype,dR=fR.hasOwnProperty;function U(t){if(t==null)return!0;if(Xe(t)&&(M(t)||typeof t=="string"||typeof t.splice=="function"||Mr(t)||Yo(t)||Cs(t)))return!t.length;var e=Ce(t);if(e==uR||e==cR)return!t.size;if(Yr(t))return!Td(t).length;for(var n in t)if(dR.call(t,n))return!1;return!0}var hR="[object RegExp]";function pR(t){return Ue(t)&&Nt(t)==hR}var Tu=Et&&Et.isRegExp,St=Tu?ks(Tu):pR;function ct(t){return t===void 0}var mR="Expected a function";function gR(t){if(typeof t!="function")throw new TypeError(mR);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}function yR(t,e,n,r){if(!Oe(t))return t;e=Os(e,t);for(var i=-1,s=e.length,a=s-1,o=t;o!=null&&++i=xR){var u=SR(t);if(u)return sl(u);a=!1,i=il,l=new On}else l=o;e:for(;++r0){if(++e>=dA)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}function gA(t){return function(){return t}}var Xi=function(){try{var t=nn(Object,"defineProperty");return t({},"",{}),t}catch{}}(),yA=Xi?function(t,e){return Xi(t,"toString",{configurable:!0,enumerable:!1,value:gA(e),writable:!0})}:Fs,TA=mA(yA);function vA(t,e){for(var n=-1,r=t==null?0:t.length;++n-1&&t%1==0&&t-1&&t%1==0&&t<=NA}function rn(t){return t!=null&&cl(t.length)&&!Bd(t)}function bA(t,e,n){if(!ft(n))return!1;var r=typeof e;return(r=="number"?rn(n)&&Gs(e,n.length):r=="string"&&e in n)?Us(n[e],t):!1}function OA(t){return kA(function(e,n){var r=-1,i=n.length,s=i>1?n[i-1]:void 0,a=i>2?n[2]:void 0;for(s=t.length>3&&typeof s=="function"?(i--,s):void 0,a&&bA(n[0],n[1],a)&&(s=i<3?void 0:s,i=1),e=Object(e);++r-1}function zE(t,e){var n=this.__data__,r=Bs(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this}function yt(t){var e=-1,n=t==null?0:t.length;for(this.clear();++eo))return!1;var u=s.get(t),c=s.get(e);if(u&&c)return u==e&&c==t;var f=-1,d=!0,h=n&NS?new Zi:void 0;for(s.set(t,e),s.set(e,t);++f-1:!!i&&EA(t,e,n)>-1}var kx="[object RegExp]";function Nx(t){return _t(t)&&bt(t)==kx}var ju=Ji&&Ji.isRegExp,bx=ju?zd(ju):Nx;function Ox(t,e,n,r){if(!ft(t))return t;e=Ks(e,t);for(var i=-1,s=e.length,a=s-1,o=t;o!=null&&++i{n.accept(e)})}}class fe extends et{constructor(e){super([]),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}set definition(e){}get definition(){return this.referencedRule!==void 0?this.referencedRule.definition:[]}accept(e){e.visit(this)}}class jn extends et{constructor(e){super(e.definition),this.orgText="",Ze(this,Qe(e,n=>n!==void 0))}}class me extends et{constructor(e){super(e.definition),this.ignoreAmbiguities=!1,Ze(this,Qe(e,n=>n!==void 0))}}let ie=class extends et{constructor(e){super(e.definition),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}};class xe extends et{constructor(e){super(e.definition),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}}class _e extends et{constructor(e){super(e.definition),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}}class q extends et{constructor(e){super(e.definition),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}}class ye extends et{constructor(e){super(e.definition),this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}}class Te extends et{get definition(){return this._definition}set definition(e){this._definition=e}constructor(e){super(e.definition),this.idx=1,this.ignoreAmbiguities=!1,this.hasPredicates=!1,Ze(this,Qe(e,n=>n!==void 0))}}class K{constructor(e){this.idx=1,Ze(this,Qe(e,n=>n!==void 0))}accept(e){e.visit(this)}}function Gx(t){return sh(t,Ni)}function Ni(t){function e(n){return sh(n,Ni)}if(t instanceof fe){const n={type:"NonTerminal",name:t.nonTerminalName,idx:t.idx};return Qi(t.label)&&(n.label=t.label),n}else{if(t instanceof me)return{type:"Alternative",definition:e(t.definition)};if(t instanceof ie)return{type:"Option",idx:t.idx,definition:e(t.definition)};if(t instanceof xe)return{type:"RepetitionMandatory",idx:t.idx,definition:e(t.definition)};if(t instanceof _e)return{type:"RepetitionMandatoryWithSeparator",idx:t.idx,separator:Ni(new K({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof ye)return{type:"RepetitionWithSeparator",idx:t.idx,separator:Ni(new K({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof q)return{type:"Repetition",idx:t.idx,definition:e(t.definition)};if(t instanceof Te)return{type:"Alternation",idx:t.idx,definition:e(t.definition)};if(t instanceof K){const n={type:"Terminal",name:t.terminalType.name,label:Dx(t.terminalType),idx:t.idx};Qi(t.label)&&(n.terminalLabel=t.label);const r=t.terminalType.PATTERN;return t.terminalType.PATTERN&&(n.pattern=bx(r)?r.source:r),n}else{if(t instanceof jn)return{type:"Rule",name:t.name,orgText:t.orgText,definition:e(t.definition)};throw Error("non exhaustive match")}}}class Kn{visit(e){const n=e;switch(n.constructor){case fe:return this.visitNonTerminal(n);case me:return this.visitAlternative(n);case ie:return this.visitOption(n);case xe:return this.visitRepetitionMandatory(n);case _e:return this.visitRepetitionMandatoryWithSeparator(n);case ye:return this.visitRepetitionWithSeparator(n);case q:return this.visitRepetition(n);case Te:return this.visitAlternation(n);case K:return this.visitTerminal(n);case jn:return this.visitRule(n);default:throw Error("non exhaustive match")}}visitNonTerminal(e){}visitAlternative(e){}visitOption(e){}visitRepetition(e){}visitRepetitionMandatory(e){}visitRepetitionMandatoryWithSeparator(e){}visitRepetitionWithSeparator(e){}visitAlternation(e){}visitTerminal(e){}visitRule(e){}}function Ux(t){return t instanceof me||t instanceof ie||t instanceof q||t instanceof xe||t instanceof _e||t instanceof ye||t instanceof K||t instanceof jn}function es(t,e=[]){return t instanceof ie||t instanceof q||t instanceof ye?!0:t instanceof Te?Mx(t.definition,r=>es(r,e)):t instanceof fe&&Cx(e,t)?!1:t instanceof et?(t instanceof fe&&e.push(t),Ex(t.definition,r=>es(r,e))):!1}function Bx(t){return t instanceof Te}function We(t){if(t instanceof fe)return"SUBRULE";if(t instanceof ie)return"OPTION";if(t instanceof Te)return"OR";if(t instanceof xe)return"AT_LEAST_ONE";if(t instanceof _e)return"AT_LEAST_ONE_SEP";if(t instanceof ye)return"MANY_SEP";if(t instanceof q)return"MANY";if(t instanceof K)return"CONSUME";throw Error("non exhaustive match")}class zs{walk(e,n=[]){k(e.definition,(r,i)=>{const s=te(e.definition,i+1);if(r instanceof fe)this.walkProdRef(r,s,n);else if(r instanceof K)this.walkTerminal(r,s,n);else if(r instanceof me)this.walkFlat(r,s,n);else if(r instanceof ie)this.walkOption(r,s,n);else if(r instanceof xe)this.walkAtLeastOne(r,s,n);else if(r instanceof _e)this.walkAtLeastOneSep(r,s,n);else if(r instanceof ye)this.walkManySep(r,s,n);else if(r instanceof q)this.walkMany(r,s,n);else if(r instanceof Te)this.walkOr(r,s,n);else throw Error("non exhaustive match")})}walkTerminal(e,n,r){}walkProdRef(e,n,r){}walkFlat(e,n,r){const i=n.concat(r);this.walk(e,i)}walkOption(e,n,r){const i=n.concat(r);this.walk(e,i)}walkAtLeastOne(e,n,r){const i=[new ie({definition:e.definition})].concat(n,r);this.walk(e,i)}walkAtLeastOneSep(e,n,r){const i=Ku(e,n,r);this.walk(e,i)}walkMany(e,n,r){const i=[new ie({definition:e.definition})].concat(n,r);this.walk(e,i)}walkManySep(e,n,r){const i=Ku(e,n,r);this.walk(e,i)}walkOr(e,n,r){const i=n.concat(r);k(e.definition,s=>{const a=new me({definition:[s]});this.walk(a,i)})}}function Ku(t,e,n){return[new ie({definition:[new K({terminalType:t.separator})].concat(t.definition)})].concat(e,n)}function ei(t){if(t instanceof fe)return ei(t.referencedRule);if(t instanceof K)return Hx(t);if(Ux(t))return jx(t);if(Bx(t))return Kx(t);throw Error("non exhaustive match")}function jx(t){let e=[];const n=t.definition;let r=0,i=n.length>r,s,a=!0;for(;i&&a;)s=n[r],a=es(s),e=e.concat(ei(s)),r=r+1,i=n.length>r;return ll(e)}function Kx(t){const e=I(t.definition,n=>ei(n));return ll(Ge(e))}function Hx(t){return[t.terminalType]}const ah="_~IN~_";class Wx extends zs{constructor(e){super(),this.topProd=e,this.follows={}}startWalking(){return this.walk(this.topProd),this.follows}walkTerminal(e,n,r){}walkProdRef(e,n,r){const i=Vx(e.referencedRule,e.idx)+this.topProd.name,s=n.concat(r),a=new me({definition:s}),o=ei(a);this.follows[i]=o}}function zx(t){const e={};return k(t,n=>{const r=new Wx(n).startWalking();za(e,r)}),e}function Vx(t,e){return t.name+e+ah}let bi={};const qx=new Zf;function Vs(t){const e=t.toString();if(bi.hasOwnProperty(e))return bi[e];{const n=qx.pattern(e);return bi[e]=n,n}}function Yx(){bi={}}const oh="Complement Sets are not supported for first char optimization",ts=`Unable to use "first char" lexer optimizations: +`;function Xx(t,e=!1){try{const n=Vs(t);return no(n.value,{},n.flags.ignoreCase)}catch(n){if(n.message===oh)e&&Ld(`${ts} Unable to optimize: < ${t.toString()} > + Complement Sets cannot be automatically optimized. + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{let r="";e&&(r=` + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),Xa(`${ts} + Failed parsing: < ${t.toString()} > + Using the @chevrotain/regexp-to-ast library + Please open an issue at: https://github.com/chevrotain/chevrotain/issues`+r)}}return[]}function no(t,e,n){switch(t.type){case"Disjunction":for(let i=0;i{if(typeof l=="number")Ti(l,e,n);else{const u=l;if(n===!0)for(let c=u.from;c<=u.to;c++)Ti(c,e,n);else{for(let c=u.from;c<=u.to&&c=xr){const c=u.from>=xr?u.from:xr,f=u.to,d=It(c),h=It(f);for(let m=d;m<=h;m++)e[m]=m}}}});break;case"Group":no(a.value,e,n);break;default:throw Error("Non Exhaustive Match")}const o=a.quantifier!==void 0&&a.quantifier.atLeast===0;if(a.type==="Group"&&ro(a)===!1||a.type!=="Group"&&o===!1)break}break;default:throw Error("non exhaustive match!")}return Z(e)}function Ti(t,e,n){const r=It(t);e[r]=r,n===!0&&Jx(t,e)}function Jx(t,e){const n=String.fromCharCode(t),r=n.toUpperCase();if(r!==n){const i=It(r.charCodeAt(0));e[i]=i}else{const i=n.toLowerCase();if(i!==n){const s=It(i.charCodeAt(0));e[s]=s}}}function Hu(t,e){return Ln(t.value,n=>{if(typeof n=="number")return ge(e,n);{const r=n;return Ln(e,i=>r.from<=i&&i<=r.to)!==void 0}})}function ro(t){const e=t.quantifier;return e&&e.atLeast===0?!0:t.value?M(t.value)?qe(t.value,ro):ro(t.value):!1}class Zx extends As{constructor(e){super(),this.targetCharCodes=e,this.found=!1}visitChildren(e){if(this.found!==!0){switch(e.type){case"Lookahead":this.visitLookahead(e);return;case"NegativeLookahead":this.visitNegativeLookahead(e);return}super.visitChildren(e)}}visitCharacter(e){ge(this.targetCharCodes,e.value)&&(this.found=!0)}visitSet(e){e.complement?Hu(e,this.targetCharCodes)===void 0&&(this.found=!0):Hu(e,this.targetCharCodes)!==void 0&&(this.found=!0)}}function gl(t,e){if(e instanceof RegExp){const n=Vs(e),r=new Zx(t);return r.visit(n),r.found}else return Ln(e,n=>ge(t,n.charCodeAt(0)))!==void 0}const qt="PATTERN",Sr="defaultMode",vi="modes";let lh=typeof new RegExp("(?:)").sticky=="boolean";function Qx(t,e){e=ol(e,{useSticky:lh,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` +`],tracer:(R,v)=>v()});const n=e.tracer;n("initCharCodeToOptimizedIndexMap",()=>{A_()});let r;n("Reject Lexer.NA",()=>{r=Ls(t,R=>R[qt]===he.NA)});let i=!1,s;n("Transform Patterns",()=>{i=!1,s=I(r,R=>{const v=R[qt];if(St(v)){const x=v.source;return x.length===1&&x!=="^"&&x!=="$"&&x!=="."&&!v.ignoreCase?x:x.length===2&&x[0]==="\\"&&!ge(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],x[1])?x[1]:e.useSticky?zu(v):Wu(v)}else{if(ht(v))return i=!0,{exec:v};if(typeof v=="object")return i=!0,v;if(typeof v=="string"){if(v.length===1)return v;{const x=v.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),O=new RegExp(x);return e.useSticky?zu(O):Wu(O)}}else throw Error("non exhaustive match")}})});let a,o,l,u,c;n("misc mapping",()=>{a=I(r,R=>R.tokenTypeIdx),o=I(r,R=>{const v=R.GROUP;if(v!==he.SKIPPED){if(je(v))return v;if(ct(v))return!1;throw Error("non exhaustive match")}}),l=I(r,R=>{const v=R.LONGER_ALT;if(v)return M(v)?I(v,O=>yu(r,O)):[yu(r,v)]}),u=I(r,R=>R.PUSH_MODE),c=I(r,R=>w(R,"POP_MODE"))});let f;n("Line Terminator Handling",()=>{const R=fh(e.lineTerminatorCharacters);f=I(r,v=>!1),e.positionTracking!=="onlyOffset"&&(f=I(r,v=>w(v,"LINE_BREAKS")?!!v.LINE_BREAKS:ch(v,R)===!1&&gl(R,v.PATTERN)))});let d,h,m,g;n("Misc Mapping #2",()=>{d=I(r,uh),h=I(s,v_),m=Se(r,(R,v)=>{const x=v.GROUP;return je(x)&&x!==he.SKIPPED&&(R[x]=[]),R},{}),g=I(s,(R,v)=>({pattern:s[v],longerAlt:l[v],canLineTerminator:f[v],isCustom:d[v],short:h[v],group:o[v],push:u[v],pop:c[v],tokenTypeIdx:a[v],tokenType:r[v]}))});let T=!0,y=[];return e.safeMode||n("First Char Optimization",()=>{y=Se(r,(R,v,x)=>{if(typeof v.PATTERN=="string"){const O=v.PATTERN.charCodeAt(0),ae=It(O);$a(R,ae,g[x])}else if(M(v.START_CHARS_HINT)){let O;k(v.START_CHARS_HINT,ae=>{const Me=typeof ae=="string"?ae.charCodeAt(0):ae,ve=It(Me);O!==ve&&(O=ve,$a(R,ve,g[x]))})}else if(St(v.PATTERN))if(v.PATTERN.unicode)T=!1,e.ensureOptimizations&&Xa(`${ts} Unable to analyze < ${v.PATTERN.toString()} > pattern. + The regexp unicode flag is not currently supported by the regexp-to-ast library. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{const O=Xx(v.PATTERN,e.ensureOptimizations);U(O)&&(T=!1),k(O,ae=>{$a(R,ae,g[x])})}else e.ensureOptimizations&&Xa(`${ts} TokenType: <${v.name}> is using a custom token pattern without providing parameter. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),T=!1;return R},[])}),{emptyGroups:m,patternIdxToConfig:g,charCodeToPatternIdxToConfig:y,hasCustom:i,canBeOptimized:T}}function e_(t,e){let n=[];const r=n_(t);n=n.concat(r.errors);const i=r_(r.valid),s=i.valid;return n=n.concat(i.errors),n=n.concat(t_(s)),n=n.concat(f_(s)),n=n.concat(d_(s,e)),n=n.concat(h_(s)),n}function t_(t){let e=[];const n=Le(t,r=>St(r[qt]));return e=e.concat(s_(n)),e=e.concat(l_(n)),e=e.concat(u_(n)),e=e.concat(c_(n)),e=e.concat(a_(n)),e}function n_(t){const e=Le(t,i=>!w(i,qt)),n=I(e,i=>({message:"Token Type: ->"+i.name+"<- missing static 'PATTERN' property",type:Y.MISSING_PATTERN,tokenTypes:[i]})),r=Ps(t,e);return{errors:n,valid:r}}function r_(t){const e=Le(t,i=>{const s=i[qt];return!St(s)&&!ht(s)&&!w(s,"exec")&&!je(s)}),n=I(e,i=>({message:"Token Type: ->"+i.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:Y.INVALID_PATTERN,tokenTypes:[i]})),r=Ps(t,e);return{errors:n,valid:r}}const i_=/[^\\][$]/;function s_(t){class e extends As{constructor(){super(...arguments),this.found=!1}visitEndAnchor(s){this.found=!0}}const n=Le(t,i=>{const s=i.PATTERN;try{const a=Vs(s),o=new e;return o.visit(a),o.found}catch{return i_.test(s.source)}});return I(n,i=>({message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain end of input anchor '$' + See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:Y.EOI_ANCHOR_FOUND,tokenTypes:[i]}))}function a_(t){const e=Le(t,r=>r.PATTERN.test(""));return I(e,r=>({message:"Token Type: ->"+r.name+"<- static 'PATTERN' must not match an empty string",type:Y.EMPTY_MATCH_PATTERN,tokenTypes:[r]}))}const o_=/[^\\[][\^]|^\^/;function l_(t){class e extends As{constructor(){super(...arguments),this.found=!1}visitStartAnchor(s){this.found=!0}}const n=Le(t,i=>{const s=i.PATTERN;try{const a=Vs(s),o=new e;return o.visit(a),o.found}catch{return o_.test(s.source)}});return I(n,i=>({message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain start of input anchor '^' + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:Y.SOI_ANCHOR_FOUND,tokenTypes:[i]}))}function u_(t){const e=Le(t,r=>{const i=r[qt];return i instanceof RegExp&&(i.multiline||i.global)});return I(e,r=>({message:"Token Type: ->"+r.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:Y.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[r]}))}function c_(t){const e=[];let n=I(t,s=>Se(t,(a,o)=>(s.PATTERN.source===o.PATTERN.source&&!ge(e,o)&&o.PATTERN!==he.NA&&(e.push(o),a.push(o)),a),[]));n=Jr(n);const r=Le(n,s=>s.length>1);return I(r,s=>{const a=I(s,l=>l.name);return{message:`The same RegExp pattern ->${Be(s).PATTERN}<-has been used in all of the following Token Types: ${a.join(", ")} <-`,type:Y.DUPLICATE_PATTERNS_FOUND,tokenTypes:s}})}function f_(t){const e=Le(t,r=>{if(!w(r,"GROUP"))return!1;const i=r.GROUP;return i!==he.SKIPPED&&i!==he.NA&&!je(i)});return I(e,r=>({message:"Token Type: ->"+r.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:Y.INVALID_GROUP_TYPE_FOUND,tokenTypes:[r]}))}function d_(t,e){const n=Le(t,i=>i.PUSH_MODE!==void 0&&!ge(e,i.PUSH_MODE));return I(n,i=>({message:`Token Type: ->${i.name}<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->${i.PUSH_MODE}<-which does not exist`,type:Y.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[i]}))}function h_(t){const e=[],n=Se(t,(r,i,s)=>{const a=i.PATTERN;return a===he.NA||(je(a)?r.push({str:a,idx:s,tokenType:i}):St(a)&&m_(a)&&r.push({str:a.source,idx:s,tokenType:i})),r},[]);return k(t,(r,i)=>{k(n,({str:s,idx:a,tokenType:o})=>{if(i${o.name}<- can never be matched. +Because it appears AFTER the Token Type ->${r.name}<-in the lexer's definition. +See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:l,type:Y.UNREACHABLE_PATTERN,tokenTypes:[r,o]})}})}),e}function p_(t,e){if(St(e)){const n=e.exec(t);return n!==null&&n.index===0}else{if(ht(e))return e(t,0,[],{});if(w(e,"exec"))return e.exec(t,0,[],{});if(typeof e=="string")return e===t;throw Error("non exhaustive match")}}function m_(t){return Ln([".","\\","[","]","|","^","$","(",")","?","*","+","{"],n=>t.source.indexOf(n)!==-1)===void 0}function Wu(t){const e=t.ignoreCase?"i":"";return new RegExp(`^(?:${t.source})`,e)}function zu(t){const e=t.ignoreCase?"iy":"y";return new RegExp(`${t.source}`,e)}function g_(t,e,n){const r=[];return w(t,Sr)||r.push({message:"A MultiMode Lexer cannot be initialized without a <"+Sr+`> property in its definition +`,type:Y.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),w(t,vi)||r.push({message:"A MultiMode Lexer cannot be initialized without a <"+vi+`> property in its definition +`,type:Y.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),w(t,vi)&&w(t,Sr)&&!w(t.modes,t.defaultMode)&&r.push({message:`A MultiMode Lexer cannot be initialized with a ${Sr}: <${t.defaultMode}>which does not exist +`,type:Y.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),w(t,vi)&&k(t.modes,(i,s)=>{k(i,(a,o)=>{if(ct(a))r.push({message:`A Lexer cannot be initialized using an undefined Token Type. Mode:<${s}> at index: <${o}> +`,type:Y.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED});else if(w(a,"LONGER_ALT")){const l=M(a.LONGER_ALT)?a.LONGER_ALT:[a.LONGER_ALT];k(l,u=>{!ct(u)&&!ge(i,u)&&r.push({message:`A MultiMode Lexer cannot be initialized with a longer_alt <${u.name}> on token <${a.name}> outside of mode <${s}> +`,type:Y.MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE})})}})}),r}function y_(t,e,n){const r=[];let i=!1;const s=Jr(Ge(Z(t.modes))),a=Ls(s,l=>l[qt]===he.NA),o=fh(n);return e&&k(a,l=>{const u=ch(l,o);if(u!==!1){const f={message:R_(l,u),type:u.issue,tokenType:l};r.push(f)}else w(l,"LINE_BREAKS")?l.LINE_BREAKS===!0&&(i=!0):gl(o,l.PATTERN)&&(i=!0)}),e&&!i&&r.push({message:`Warning: No LINE_BREAKS Found. + This Lexer has been defined to track line and column information, + But none of the Token Types can be identified as matching a line terminator. + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS + for details.`,type:Y.NO_LINE_BREAKS_FLAGS}),r}function T_(t){const e={},n=Pe(t);return k(n,r=>{const i=t[r];if(M(i))e[r]=[];else throw Error("non exhaustive match")}),e}function uh(t){const e=t.PATTERN;if(St(e))return!1;if(ht(e))return!0;if(w(e,"exec"))return!0;if(je(e))return!1;throw Error("non exhaustive match")}function v_(t){return je(t)&&t.length===1?t.charCodeAt(0):!1}const $_={test:function(t){const e=t.length;for(let n=this.lastIndex;n Token Type + Root cause: ${e.errMsg}. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR`;if(e.issue===Y.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. + The problem is in the <${t.name}> Token Type + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK`;throw Error("non exhaustive match")}function fh(t){return I(t,n=>je(n)?n.charCodeAt(0):n)}function $a(t,e,n){t[e]===void 0?t[e]=[n]:t[e].push(n)}const xr=256;let Oi=[];function It(t){return t255?255+~~(t/255):t}}function ti(t,e){const n=t.tokenTypeIdx;return n===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[n]===!0}function ns(t,e){return t.tokenTypeIdx===e.tokenTypeIdx}let Vu=1;const dh={};function ni(t){const e=E_(t);S_(e),__(e),x_(e),k(e,n=>{n.isParent=n.categoryMatches.length>0})}function E_(t){let e=se(t),n=t,r=!0;for(;r;){n=Jr(Ge(I(n,s=>s.CATEGORIES)));const i=Ps(n,e);e=e.concat(i),U(i)?r=!1:n=i}return e}function S_(t){k(t,e=>{ph(e)||(dh[Vu]=e,e.tokenTypeIdx=Vu++),qu(e)&&!M(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),qu(e)||(e.CATEGORIES=[]),I_(e)||(e.categoryMatches=[]),w_(e)||(e.categoryMatchesMap={})})}function x_(t){k(t,e=>{e.categoryMatches=[],k(e.categoryMatchesMap,(n,r)=>{e.categoryMatches.push(dh[r].tokenTypeIdx)})})}function __(t){k(t,e=>{hh([],e)})}function hh(t,e){k(t,n=>{e.categoryMatchesMap[n.tokenTypeIdx]=!0}),k(e.CATEGORIES,n=>{const r=t.concat(e);ge(r,n)||hh(r,n)})}function ph(t){return w(t,"tokenTypeIdx")}function qu(t){return w(t,"CATEGORIES")}function I_(t){return w(t,"categoryMatches")}function w_(t){return w(t,"categoryMatchesMap")}function C_(t){return w(t,"tokenTypeIdx")}const io={buildUnableToPopLexerModeMessage(t){return`Unable to pop Lexer Mode after encountering Token ->${t.image}<- The Mode Stack is empty`},buildUnexpectedCharactersMessage(t,e,n,r,i){return`unexpected character: ->${t.charAt(e)}<- at offset: ${e}, skipped ${n} characters.`}};var Y;(function(t){t[t.MISSING_PATTERN=0]="MISSING_PATTERN",t[t.INVALID_PATTERN=1]="INVALID_PATTERN",t[t.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",t[t.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",t[t.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",t[t.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",t[t.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",t[t.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",t[t.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",t[t.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",t[t.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",t[t.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",t[t.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",t[t.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",t[t.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",t[t.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",t[t.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK",t[t.MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE=17]="MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE"})(Y||(Y={}));const _r={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` +`,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:io,traceInitPerf:!1,skipValidations:!1,recoveryEnabled:!0};Object.freeze(_r);class he{constructor(e,n=_r){if(this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},this.TRACE_INIT=(i,s)=>{if(this.traceInitPerf===!0){this.traceInitIndent++;const a=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <${i}>`);const{time:o,value:l}=Md(s),u=o>10?console.warn:console.log;return this.traceInitIndent time: ${o}ms`),this.traceInitIndent--,l}else return s()},typeof n=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. +a boolean 2nd argument is no longer supported`);this.config=za({},_r,n);const r=this.config.traceInitPerf;r===!0?(this.traceInitMaxIdent=1/0,this.traceInitPerf=!0):typeof r=="number"&&(this.traceInitMaxIdent=r,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",()=>{let i,s=!0;this.TRACE_INIT("Lexer Config handling",()=>{if(this.config.lineTerminatorsPattern===_r.lineTerminatorsPattern)this.config.lineTerminatorsPattern=$_;else if(this.config.lineTerminatorCharacters===_r.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(n.safeMode&&n.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');this.trackStartLines=/full|onlyStart/i.test(this.config.positionTracking),this.trackEndLines=/full/i.test(this.config.positionTracking),M(e)?i={modes:{defaultMode:se(e)},defaultMode:Sr}:(s=!1,i=se(e))}),this.config.skipValidations===!1&&(this.TRACE_INIT("performRuntimeChecks",()=>{this.lexerDefinitionErrors=this.lexerDefinitionErrors.concat(g_(i,this.trackStartLines,this.config.lineTerminatorCharacters))}),this.TRACE_INIT("performWarningRuntimeChecks",()=>{this.lexerDefinitionWarning=this.lexerDefinitionWarning.concat(y_(i,this.trackStartLines,this.config.lineTerminatorCharacters))})),i.modes=i.modes?i.modes:{},k(i.modes,(o,l)=>{i.modes[l]=Ls(o,u=>ct(u))});const a=Pe(i.modes);if(k(i.modes,(o,l)=>{this.TRACE_INIT(`Mode: <${l}> processing`,()=>{if(this.modes.push(l),this.config.skipValidations===!1&&this.TRACE_INIT("validatePatterns",()=>{this.lexerDefinitionErrors=this.lexerDefinitionErrors.concat(e_(o,a))}),U(this.lexerDefinitionErrors)){ni(o);let u;this.TRACE_INIT("analyzeTokenTypes",()=>{u=Qx(o,{lineTerminatorCharacters:this.config.lineTerminatorCharacters,positionTracking:n.positionTracking,ensureOptimizations:n.ensureOptimizations,safeMode:n.safeMode,tracer:this.TRACE_INIT})}),this.patternIdxToConfig[l]=u.patternIdxToConfig,this.charCodeToPatternIdxToConfig[l]=u.charCodeToPatternIdxToConfig,this.emptyGroups=za({},this.emptyGroups,u.emptyGroups),this.hasCustom=u.hasCustom||this.hasCustom,this.canModeBeOptimized[l]=u.canBeOptimized}})}),this.defaultMode=i.defaultMode,!U(this.lexerDefinitionErrors)&&!this.config.deferDefinitionErrorsHandling){const l=I(this.lexerDefinitionErrors,u=>u.message).join(`----------------------- +`);throw new Error(`Errors detected in definition of Lexer: +`+l)}k(this.lexerDefinitionWarning,o=>{Ld(o.message)}),this.TRACE_INIT("Choosing sub-methods implementations",()=>{if(lh?(this.chopInput=bn,this.match=this.matchWithTest):(this.updateLastIndex=J,this.match=this.matchWithExec),s&&(this.handleModes=J),this.trackStartLines===!1&&(this.computeNewColumn=bn),this.trackEndLines===!1&&(this.updateTokenEndLineColumnLocation=J),/full/i.test(this.config.positionTracking))this.createTokenInstance=this.createFullToken;else if(/onlyStart/i.test(this.config.positionTracking))this.createTokenInstance=this.createStartOnlyToken;else if(/onlyOffset/i.test(this.config.positionTracking))this.createTokenInstance=this.createOffsetOnlyToken;else throw Error(`Invalid config option: "${this.config.positionTracking}"`);this.hasCustom?(this.addToken=this.addTokenUsingPush,this.handlePayload=this.handlePayloadWithCustom):(this.addToken=this.addTokenUsingMemberAccess,this.handlePayload=this.handlePayloadNoCustom)}),this.TRACE_INIT("Failed Optimization Warnings",()=>{const o=Se(this.canModeBeOptimized,(l,u,c)=>(u===!1&&l.push(c),l),[]);if(n.ensureOptimizations&&!U(o))throw Error(`Lexer Modes: < ${o.join(", ")} > cannot be optimized. + Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. + Or inspect the console log for details on how to resolve these issues.`)}),this.TRACE_INIT("clearRegExpParserCache",()=>{Yx()}),this.TRACE_INIT("toFastProperties",()=>{Dd(this)})})}tokenize(e,n=this.defaultMode){if(!U(this.lexerDefinitionErrors)){const i=I(this.lexerDefinitionErrors,s=>s.message).join(`----------------------- +`);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: +`+i)}return this.tokenizeInternal(e,n)}tokenizeInternal(e,n){let r,i,s,a,o,l,u,c,f,d,h,m,g,T,y;const R=e,v=R.length;let x=0,O=0;const ae=this.hasCustom?0:Math.floor(e.length/10),Me=new Array(ae),ve=[];let He=this.trackStartLines?1:void 0,Ie=this.trackStartLines?1:void 0;const S=T_(this.emptyGroups),$=this.trackStartLines,E=this.config.lineTerminatorsPattern;let _=0,P=[],b=[];const N=[],$e=[];Object.freeze($e);let Q;function V(){return P}function Gt(oe){const we=It(oe),ln=b[we];return ln===void 0?$e:ln}const Pp=oe=>{if(N.length===1&&oe.tokenType.PUSH_MODE===void 0){const we=this.config.errorMessageProvider.buildUnableToPopLexerModeMessage(oe);ve.push({offset:oe.startOffset,line:oe.startLine,column:oe.startColumn,length:oe.image.length,message:we})}else{N.pop();const we=Pn(N);P=this.patternIdxToConfig[we],b=this.charCodeToPatternIdxToConfig[we],_=P.length;const ln=this.canModeBeOptimized[we]&&this.config.safeMode===!1;b&&ln?Q=Gt:Q=V}};function Nl(oe){N.push(oe),b=this.charCodeToPatternIdxToConfig[oe],P=this.patternIdxToConfig[oe],_=P.length,_=P.length;const we=this.canModeBeOptimized[oe]&&this.config.safeMode===!1;b&&we?Q=Gt:Q=V}Nl.call(this,n);let De;const bl=this.config.recoveryEnabled;for(;xl.length){l=a,u=c,De=nt;break}}}break}}if(l!==null){if(f=l.length,d=De.group,d!==void 0&&(h=De.tokenTypeIdx,m=this.createTokenInstance(l,x,h,De.tokenType,He,Ie,f),this.handlePayload(m,u),d===!1?O=this.addToken(Me,O,m):S[d].push(m)),e=this.chopInput(e,f),x=x+f,Ie=this.computeNewColumn(Ie,f),$===!0&&De.canLineTerminator===!0){let Re=0,tt,vt;E.lastIndex=0;do tt=E.test(l),tt===!0&&(vt=E.lastIndex-1,Re++);while(tt===!0);Re!==0&&(He=He+Re,Ie=f-vt,this.updateTokenEndLineColumnLocation(m,d,vt,Re,He,Ie,f))}this.handleModes(De,Pp,Nl,m)}else{const Re=x,tt=He,vt=Ie;let nt=bl===!1;for(;nt===!1&&x ${pn(t)} <--`:`token of type --> ${t.name} <--`} but found --> '${e.image}' <--`},buildNotAllInputParsedMessage({firstRedundant:t,ruleName:e}){return"Redundant input, expecting EOF but found: "+t.image},buildNoViableAltMessage({expectedPathsPerAlt:t,actual:e,previous:n,customUserDescription:r,ruleName:i}){const s="Expecting: ",o=` +but found: '`+Be(e).image+"'";if(r)return s+r+o;{const l=Se(t,(d,h)=>d.concat(h),[]),u=I(l,d=>`[${I(d,h=>pn(h)).join(", ")}]`),f=`one of these possible Token sequences: +${I(u,(d,h)=>` ${h+1}. ${d}`).join(` +`)}`;return s+f+o}},buildEarlyExitMessage({expectedIterationPaths:t,actual:e,customUserDescription:n,ruleName:r}){const i="Expecting: ",a=` +but found: '`+Be(e).image+"'";if(n)return i+n+a;{const l=`expecting at least one iteration which starts with one of these possible Token sequences:: + <${I(t,u=>`[${I(u,c=>pn(c)).join(",")}]`).join(" ,")}>`;return i+l+a}}};Object.freeze(fn);const b_={buildRuleNotFoundError(t,e){return"Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- +inside top level rule: ->`+t.name+"<-"}},jt={buildDuplicateFoundError(t,e){function n(c){return c instanceof K?c.terminalType.name:c instanceof fe?c.nonTerminalName:""}const r=t.name,i=Be(e),s=i.idx,a=We(i),o=n(i),l=s>0;let u=`->${a}${l?s:""}<- ${o?`with argument: ->${o}<-`:""} + appears more than once (${e.length} times) in the top level rule: ->${r}<-. + For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES + `;return u=u.replace(/[ \t]+/g," "),u=u.replace(/\s\s+/g,` +`),u},buildNamespaceConflictError(t){return`Namespace conflict found in grammar. +The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <${t.name}>. +To resolve this make sure each Terminal and Non-Terminal names are unique +This is easy to accomplish by using the convention that Terminal names start with an uppercase letter +and Non-Terminal names start with a lower case letter.`},buildAlternationPrefixAmbiguityError(t){const e=I(t.prefixPath,i=>pn(i)).join(", "),n=t.alternation.idx===0?"":t.alternation.idx;return`Ambiguous alternatives: <${t.ambiguityIndices.join(" ,")}> due to common lookahead prefix +in inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX +For Further details.`},buildAlternationAmbiguityError(t){const e=I(t.prefixPath,i=>pn(i)).join(", "),n=t.alternation.idx===0?"":t.alternation.idx;let r=`Ambiguous Alternatives Detected: <${t.ambiguityIndices.join(" ,")}> in inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +`;return r=r+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,r},buildEmptyRepetitionError(t){let e=We(t.repetition);return t.repetition.idx!==0&&(e+=t.repetition.idx),`The repetition <${e}> within Rule <${t.topLevelRule.name}> can never consume any tokens. +This could lead to an infinite loop.`},buildTokenNameError(t){return"deprecated"},buildEmptyAlternationError(t){return`Ambiguous empty alternative: <${t.emptyChoiceIdx+1}> in inside <${t.topLevelRule.name}> Rule. +Only the last alternative may be an empty alternative.`},buildTooManyAlternativesError(t){return`An Alternation cannot have more than 256 alternatives: + inside <${t.topLevelRule.name}> Rule. + has ${t.alternation.definition.length+1} alternatives.`},buildLeftRecursionError(t){const e=t.topLevelRule.name,n=I(t.leftRecursionPath,s=>s.name),r=`${e} --> ${n.concat([e]).join(" --> ")}`;return`Left Recursion found in grammar. +rule: <${e}> can be invoked from itself (directly or indirectly) +without consuming any Tokens. The grammar path that causes this is: + ${r} + To fix this refactor your grammar to remove the left recursion. +see: https://en.wikipedia.org/wiki/LL_parser#Left_factoring.`},buildInvalidRuleNameError(t){return"deprecated"},buildDuplicateRuleNameError(t){let e;return t.topLevelRule instanceof jn?e=t.topLevelRule.name:e=t.topLevelRule,`Duplicate definition, rule: ->${e}<- is already defined in the grammar: ->${t.grammarName}<-`}};function O_(t,e){const n=new P_(t,e);return n.resolveRefs(),n.errors}class P_ extends Kn{constructor(e,n){super(),this.nameToTopRule=e,this.errMsgProvider=n,this.errors=[]}resolveRefs(){k(Z(this.nameToTopRule),e=>{this.currTopLevel=e,e.accept(this)})}visitNonTerminal(e){const n=this.nameToTopRule[e.nonTerminalName];if(n)e.referencedRule=n;else{const r=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,e);this.errors.push({message:r,type:de.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:e.nonTerminalName})}}}class L_ extends zs{constructor(e,n){super(),this.topProd=e,this.path=n,this.possibleTokTypes=[],this.nextProductionName="",this.nextProductionOccurrence=0,this.found=!1,this.isAtEndOfPath=!1}startWalking(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=se(this.path.ruleStack).reverse(),this.occurrenceStack=se(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes}walk(e,n=[]){this.found||super.walk(e,n)}walkProdRef(e,n,r){if(e.referencedRule.name===this.nextProductionName&&e.idx===this.nextProductionOccurrence){const i=n.concat(r);this.updateExpectedNext(),this.walk(e.referencedRule,i)}}updateExpectedNext(){U(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())}}class M_ extends L_{constructor(e,n){super(e,n),this.path=n,this.nextTerminalName="",this.nextTerminalOccurrence=0,this.nextTerminalName=this.path.lastTok.name,this.nextTerminalOccurrence=this.path.lastTokOccurrence}walkTerminal(e,n,r){if(this.isAtEndOfPath&&e.terminalType.name===this.nextTerminalName&&e.idx===this.nextTerminalOccurrence&&!this.found){const i=n.concat(r),s=new me({definition:i});this.possibleTokTypes=ei(s),this.found=!0}}}class qs extends zs{constructor(e,n){super(),this.topRule=e,this.occurrence=n,this.result={token:void 0,occurrence:void 0,isEndOfRule:void 0}}startWalking(){return this.walk(this.topRule),this.result}}class D_ extends qs{walkMany(e,n,r){if(e.idx===this.occurrence){const i=Be(n.concat(r));this.result.isEndOfRule=i===void 0,i instanceof K&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkMany(e,n,r)}}class rc extends qs{walkManySep(e,n,r){if(e.idx===this.occurrence){const i=Be(n.concat(r));this.result.isEndOfRule=i===void 0,i instanceof K&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkManySep(e,n,r)}}class F_ extends qs{walkAtLeastOne(e,n,r){if(e.idx===this.occurrence){const i=Be(n.concat(r));this.result.isEndOfRule=i===void 0,i instanceof K&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkAtLeastOne(e,n,r)}}class ic extends qs{walkAtLeastOneSep(e,n,r){if(e.idx===this.occurrence){const i=Be(n.concat(r));this.result.isEndOfRule=i===void 0,i instanceof K&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkAtLeastOneSep(e,n,r)}}function so(t,e,n=[]){n=se(n);let r=[],i=0;function s(o){return o.concat(te(t,i+1))}function a(o){const l=so(s(o),e,n);return r.concat(l)}for(;n.length{U(l.definition)===!1&&(r=a(l.definition))}),r;if(o instanceof K)n.push(o.terminalType);else throw Error("non exhaustive match")}i++}return r.push({partialPath:n,suffixDef:te(t,i)}),r}function Th(t,e,n,r){const i="EXIT_NONE_TERMINAL",s=[i],a="EXIT_ALTERNATIVE";let o=!1;const l=e.length,u=l-r-1,c=[],f=[];for(f.push({idx:-1,def:t,ruleStack:[],occurrenceStack:[]});!U(f);){const d=f.pop();if(d===a){o&&Pn(f).idx<=u&&f.pop();continue}const h=d.def,m=d.idx,g=d.ruleStack,T=d.occurrenceStack;if(U(h))continue;const y=h[0];if(y===i){const R={idx:m,def:te(h),ruleStack:Gr(g),occurrenceStack:Gr(T)};f.push(R)}else if(y instanceof K)if(m=0;R--){const v=y.definition[R],x={idx:m,def:v.definition.concat(te(h)),ruleStack:g,occurrenceStack:T};f.push(x),f.push(a)}else if(y instanceof me)f.push({idx:m,def:y.definition.concat(te(h)),ruleStack:g,occurrenceStack:T});else if(y instanceof jn)f.push(G_(y,m,g,T));else throw Error("non exhaustive match")}return c}function G_(t,e,n,r){const i=se(n);i.push(t.name);const s=se(r);return s.push(1),{idx:e,def:t.definition,ruleStack:i,occurrenceStack:s}}var W;(function(t){t[t.OPTION=0]="OPTION",t[t.REPETITION=1]="REPETITION",t[t.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",t[t.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",t[t.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",t[t.ALTERNATION=5]="ALTERNATION"})(W||(W={}));function Tl(t){if(t instanceof ie||t==="Option")return W.OPTION;if(t instanceof q||t==="Repetition")return W.REPETITION;if(t instanceof xe||t==="RepetitionMandatory")return W.REPETITION_MANDATORY;if(t instanceof _e||t==="RepetitionMandatoryWithSeparator")return W.REPETITION_MANDATORY_WITH_SEPARATOR;if(t instanceof ye||t==="RepetitionWithSeparator")return W.REPETITION_WITH_SEPARATOR;if(t instanceof Te||t==="Alternation")return W.ALTERNATION;throw Error("non exhaustive match")}function sc(t){const{occurrence:e,rule:n,prodType:r,maxLookahead:i}=t,s=Tl(r);return s===W.ALTERNATION?Ys(e,n,i):Xs(e,n,s,i)}function U_(t,e,n,r,i,s){const a=Ys(t,e,n),o=Rh(a)?ns:ti;return s(a,r,o,i)}function B_(t,e,n,r,i,s){const a=Xs(t,e,i,n),o=Rh(a)?ns:ti;return s(a[0],o,r)}function j_(t,e,n,r){const i=t.length,s=qe(t,a=>qe(a,o=>o.length===1));if(e)return function(a){const o=I(a,l=>l.GATE);for(let l=0;lGe(l)),o=Se(a,(l,u,c)=>(k(u,f=>{w(l,f.tokenTypeIdx)||(l[f.tokenTypeIdx]=c),k(f.categoryMatches,d=>{w(l,d)||(l[d]=c)})}),l),{});return function(){const l=this.LA(1);return o[l.tokenTypeIdx]}}else return function(){for(let a=0;as.length===1),i=t.length;if(r&&!n){const s=Ge(t);if(s.length===1&&U(s[0].categoryMatches)){const o=s[0].tokenTypeIdx;return function(){return this.LA(1).tokenTypeIdx===o}}else{const a=Se(s,(o,l,u)=>(o[l.tokenTypeIdx]=!0,k(l.categoryMatches,c=>{o[c]=!0}),o),[]);return function(){const o=this.LA(1);return a[o.tokenTypeIdx]===!0}}}else return function(){e:for(let s=0;sso([a],1)),r=ac(n.length),i=I(n,a=>{const o={};return k(a,l=>{const u=Ra(l.partialPath);k(u,c=>{o[c]=!0})}),o});let s=n;for(let a=1;a<=e;a++){const o=s;s=ac(o.length);for(let l=0;l{const y=Ra(T.partialPath);k(y,R=>{i[l][R]=!0})})}}}}return r}function Ys(t,e,n,r){const i=new vh(t,W.ALTERNATION,r);return e.accept(i),$h(i.result,n)}function Xs(t,e,n,r){const i=new vh(t,n);e.accept(i);const s=i.result,o=new H_(e,t,n).startWalking(),l=new me({definition:s}),u=new me({definition:o});return $h([l,u],r)}function ao(t,e){e:for(let n=0;n{const i=e[r];return n===i||i.categoryMatchesMap[n.tokenTypeIdx]})}function Rh(t){return qe(t,e=>qe(e,n=>qe(n,r=>U(r.categoryMatches))))}function V_(t){const e=t.lookaheadStrategy.validate({rules:t.rules,tokenTypes:t.tokenTypes,grammarName:t.grammarName});return I(e,n=>Object.assign({type:de.CUSTOM_LOOKAHEAD_VALIDATION},n))}function q_(t,e,n,r){const i=ke(t,l=>Y_(l,n)),s=oI(t,e,n),a=ke(t,l=>rI(l,n)),o=ke(t,l=>Z_(l,t,r,n));return i.concat(s,a,o)}function Y_(t,e){const n=new J_;t.accept(n);const r=n.allProductions,i=nR(r,X_),s=vR(i,o=>o.length>1);return I(Z(s),o=>{const l=Be(o),u=e.buildDuplicateFoundError(t,o),c=We(l),f={message:u,type:de.DUPLICATE_PRODUCTIONS,ruleName:t.name,dslName:c,occurrence:l.idx},d=Ah(l);return d&&(f.parameter=d),f})}function X_(t){return`${We(t)}_#_${t.idx}_#_${Ah(t)}`}function Ah(t){return t instanceof K?t.terminalType.name:t instanceof fe?t.nonTerminalName:""}class J_ extends Kn{constructor(){super(...arguments),this.allProductions=[]}visitNonTerminal(e){this.allProductions.push(e)}visitOption(e){this.allProductions.push(e)}visitRepetitionWithSeparator(e){this.allProductions.push(e)}visitRepetitionMandatory(e){this.allProductions.push(e)}visitRepetitionMandatoryWithSeparator(e){this.allProductions.push(e)}visitRepetition(e){this.allProductions.push(e)}visitAlternation(e){this.allProductions.push(e)}visitTerminal(e){this.allProductions.push(e)}}function Z_(t,e,n,r){const i=[];if(Se(e,(a,o)=>o.name===t.name?a+1:a,0)>1){const a=r.buildDuplicateRuleNameError({topLevelRule:t,grammarName:n});i.push({message:a,type:de.DUPLICATE_RULE_NAME,ruleName:t.name})}return i}function Q_(t,e,n){const r=[];let i;return ge(e,t)||(i=`Invalid rule override, rule: ->${t}<- cannot be overridden in the grammar: ->${n}<-as it is not defined in any of the super grammars `,r.push({message:i,type:de.INVALID_RULE_OVERRIDE,ruleName:t})),r}function Eh(t,e,n,r=[]){const i=[],s=Pi(e.definition);if(U(s))return[];{const a=t.name;ge(s,t)&&i.push({message:n.buildLeftRecursionError({topLevelRule:t,leftRecursionPath:r}),type:de.LEFT_RECURSION,ruleName:a});const l=Ps(s,r.concat([t])),u=ke(l,c=>{const f=se(r);return f.push(c),Eh(t,c,n,f)});return i.concat(u)}}function Pi(t){let e=[];if(U(t))return e;const n=Be(t);if(n instanceof fe)e.push(n.referencedRule);else if(n instanceof me||n instanceof ie||n instanceof xe||n instanceof _e||n instanceof ye||n instanceof q)e=e.concat(Pi(n.definition));else if(n instanceof Te)e=Ge(I(n.definition,s=>Pi(s.definition)));else if(!(n instanceof K))throw Error("non exhaustive match");const r=es(n),i=t.length>1;if(r&&i){const s=te(t);return e.concat(Pi(s))}else return e}class vl extends Kn{constructor(){super(...arguments),this.alternations=[]}visitAlternation(e){this.alternations.push(e)}}function eI(t,e){const n=new vl;t.accept(n);const r=n.alternations;return ke(r,s=>{const a=Gr(s.definition);return ke(a,(o,l)=>{const u=Th([o],[],ti,1);return U(u)?[{message:e.buildEmptyAlternationError({topLevelRule:t,alternation:s,emptyChoiceIdx:l}),type:de.NONE_LAST_EMPTY_ALT,ruleName:t.name,occurrence:s.idx,alternative:l+1}]:[]})})}function tI(t,e,n){const r=new vl;t.accept(r);let i=r.alternations;return i=Ls(i,a=>a.ignoreAmbiguities===!0),ke(i,a=>{const o=a.idx,l=a.maxLookahead||e,u=Ys(o,t,l,a),c=sI(u,a,t,n),f=aI(u,a,t,n);return c.concat(f)})}class nI extends Kn{constructor(){super(...arguments),this.allProductions=[]}visitRepetitionWithSeparator(e){this.allProductions.push(e)}visitRepetitionMandatory(e){this.allProductions.push(e)}visitRepetitionMandatoryWithSeparator(e){this.allProductions.push(e)}visitRepetition(e){this.allProductions.push(e)}}function rI(t,e){const n=new vl;t.accept(n);const r=n.alternations;return ke(r,s=>s.definition.length>255?[{message:e.buildTooManyAlternativesError({topLevelRule:t,alternation:s}),type:de.TOO_MANY_ALTS,ruleName:t.name,occurrence:s.idx}]:[])}function iI(t,e,n){const r=[];return k(t,i=>{const s=new nI;i.accept(s);const a=s.allProductions;k(a,o=>{const l=Tl(o),u=o.maxLookahead||e,c=o.idx,d=Xs(c,i,l,u)[0];if(U(Ge(d))){const h=n.buildEmptyRepetitionError({topLevelRule:i,repetition:o});r.push({message:h,type:de.NO_NON_EMPTY_LOOKAHEAD,ruleName:i.name})}})}),r}function sI(t,e,n,r){const i=[],s=Se(t,(o,l,u)=>(e.definition[u].ignoreAmbiguities===!0||k(l,c=>{const f=[u];k(t,(d,h)=>{u!==h&&ao(d,c)&&e.definition[h].ignoreAmbiguities!==!0&&f.push(h)}),f.length>1&&!ao(i,c)&&(i.push(c),o.push({alts:f,path:c}))}),o),[]);return I(s,o=>{const l=I(o.alts,c=>c+1);return{message:r.buildAlternationAmbiguityError({topLevelRule:n,alternation:e,ambiguityIndices:l,prefixPath:o.path}),type:de.AMBIGUOUS_ALTS,ruleName:n.name,occurrence:e.idx,alternatives:o.alts}})}function aI(t,e,n,r){const i=Se(t,(a,o,l)=>{const u=I(o,c=>({idx:l,path:c}));return a.concat(u)},[]);return Jr(ke(i,a=>{if(e.definition[a.idx].ignoreAmbiguities===!0)return[];const l=a.idx,u=a.path,c=Le(i,d=>e.definition[d.idx].ignoreAmbiguities!==!0&&d.idx{const h=[d.idx+1,l+1],m=e.idx===0?"":e.idx;return{message:r.buildAlternationPrefixAmbiguityError({topLevelRule:n,alternation:e,ambiguityIndices:h,prefixPath:d.path}),type:de.AMBIGUOUS_PREFIX_ALTS,ruleName:n.name,occurrence:m,alternatives:h}})}))}function oI(t,e,n){const r=[],i=I(e,s=>s.name);return k(t,s=>{const a=s.name;if(ge(i,a)){const o=n.buildNamespaceConflictError(s);r.push({message:o,type:de.CONFLICT_TOKENS_RULES_NAMESPACE,ruleName:a})}}),r}function lI(t){const e=ol(t,{errMsgProvider:b_}),n={};return k(t.rules,r=>{n[r.name]=r}),O_(n,e.errMsgProvider)}function uI(t){return t=ol(t,{errMsgProvider:jt}),q_(t.rules,t.tokenTypes,t.errMsgProvider,t.grammarName)}const Sh="MismatchedTokenException",xh="NoViableAltException",_h="EarlyExitException",Ih="NotAllInputParsedException",wh=[Sh,xh,_h,Ih];Object.freeze(wh);function rs(t){return ge(wh,t.name)}class Js extends Error{constructor(e,n){super(e),this.token=n,this.resyncedTokens=[],Object.setPrototypeOf(this,new.target.prototype),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}}class Ch extends Js{constructor(e,n,r){super(e,n),this.previousToken=r,this.name=Sh}}class cI extends Js{constructor(e,n,r){super(e,n),this.previousToken=r,this.name=xh}}class fI extends Js{constructor(e,n){super(e,n),this.name=Ih}}class dI extends Js{constructor(e,n,r){super(e,n),this.previousToken=r,this.name=_h}}const Aa={},kh="InRuleRecoveryException";class hI extends Error{constructor(e){super(e),this.name=kh}}class pI{initRecoverable(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=w(e,"recoveryEnabled")?e.recoveryEnabled:dt.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=mI)}getTokenToInsert(e){const n=yl(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return n.isInsertedInRecovery=!0,n}canTokenTypeBeInsertedInRecovery(e){return!0}canTokenTypeBeDeletedInRecovery(e){return!0}tryInRepetitionRecovery(e,n,r,i){const s=this.findReSyncTokenType(),a=this.exportLexerState(),o=[];let l=!1;const u=this.LA(1);let c=this.LA(1);const f=()=>{const d=this.LA(0),h=this.errorMessageProvider.buildMismatchTokenMessage({expected:i,actual:u,previous:d,ruleName:this.getCurrRuleFullName()}),m=new Ch(h,u,this.LA(0));m.resyncedTokens=Gr(o),this.SAVE_ERROR(m)};for(;!l;)if(this.tokenMatcher(c,i)){f();return}else if(r.call(this)){f(),e.apply(this,n);return}else this.tokenMatcher(c,s)?l=!0:(c=this.SKIP_TOKEN(),this.addToResyncTokens(c,o));this.importLexerState(a)}shouldInRepetitionRecoveryBeTried(e,n,r){return!(r===!1||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,n)))}getFollowsForInRuleRecovery(e,n){const r=this.getCurrentGrammarPath(e,n);return this.getNextPossibleTokenTypes(r)}tryInRuleRecovery(e,n){if(this.canRecoverWithSingleTokenInsertion(e,n))return this.getTokenToInsert(e);if(this.canRecoverWithSingleTokenDeletion(e)){const r=this.SKIP_TOKEN();return this.consumeToken(),r}throw new hI("sad sad panda")}canPerformInRuleRecovery(e,n){return this.canRecoverWithSingleTokenInsertion(e,n)||this.canRecoverWithSingleTokenDeletion(e)}canRecoverWithSingleTokenInsertion(e,n){if(!this.canTokenTypeBeInsertedInRecovery(e)||U(n))return!1;const r=this.LA(1);return Ln(n,s=>this.tokenMatcher(r,s))!==void 0}canRecoverWithSingleTokenDeletion(e){return this.canTokenTypeBeDeletedInRecovery(e)?this.tokenMatcher(this.LA(2),e):!1}isInCurrentRuleReSyncSet(e){const n=this.getCurrFollowKey(),r=this.getFollowSetFromFollowKey(n);return ge(r,e)}findReSyncTokenType(){const e=this.flattenFollowSet();let n=this.LA(1),r=2;for(;;){const i=Ln(e,s=>yh(n,s));if(i!==void 0)return i;n=this.LA(r),r++}}getCurrFollowKey(){if(this.RULE_STACK.length===1)return Aa;const e=this.getLastExplicitRuleShortName(),n=this.getLastExplicitRuleOccurrenceIndex(),r=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:n,inRule:this.shortRuleNameToFullName(r)}}buildFullFollowKeyStack(){const e=this.RULE_STACK,n=this.RULE_OCCURRENCE_STACK;return I(e,(r,i)=>i===0?Aa:{ruleName:this.shortRuleNameToFullName(r),idxInCallingRule:n[i],inRule:this.shortRuleNameToFullName(e[i-1])})}flattenFollowSet(){const e=I(this.buildFullFollowKeyStack(),n=>this.getFollowSetFromFollowKey(n));return Ge(e)}getFollowSetFromFollowKey(e){if(e===Aa)return[wt];const n=e.ruleName+e.idxInCallingRule+ah+e.inRule;return this.resyncFollows[n]}addToResyncTokens(e,n){return this.tokenMatcher(e,wt)||n.push(e),n}reSyncTo(e){const n=[];let r=this.LA(1);for(;this.tokenMatcher(r,e)===!1;)r=this.SKIP_TOKEN(),this.addToResyncTokens(r,n);return Gr(n)}attemptInRepetitionRecovery(e,n,r,i,s,a,o){}getCurrentGrammarPath(e,n){const r=this.getHumanReadableRuleStack(),i=se(this.RULE_OCCURRENCE_STACK);return{ruleStack:r,occurrenceStack:i,lastTok:e,lastTokOccurrence:n}}getHumanReadableRuleStack(){return I(this.RULE_STACK,e=>this.shortRuleNameToFullName(e))}}function mI(t,e,n,r,i,s,a){const o=this.getKeyForAutomaticLookahead(r,i);let l=this.firstAfterRepMap[o];if(l===void 0){const d=this.getCurrRuleFullName(),h=this.getGAstProductions()[d];l=new s(h,i).startWalking(),this.firstAfterRepMap[o]=l}let u=l.token,c=l.occurrence;const f=l.isEndOfRule;this.RULE_STACK.length===1&&f&&u===void 0&&(u=wt,c=1),!(u===void 0||c===void 0)&&this.shouldInRepetitionRecoveryBeTried(u,c,a)&&this.tryInRepetitionRecovery(t,e,n,u)}const gI=4,Ot=8,Nh=1<Eh(n,n,jt))}validateEmptyOrAlternatives(e){return ke(e,n=>eI(n,jt))}validateAmbiguousAlternationAlternatives(e,n){return ke(e,r=>tI(r,n,jt))}validateSomeNonEmptyLookaheadPath(e,n){return iI(e,n,jt)}buildLookaheadForAlternation(e){return U_(e.prodOccurrence,e.rule,e.maxLookahead,e.hasPredicates,e.dynamicTokensEnabled,j_)}buildLookaheadForOptional(e){return B_(e.prodOccurrence,e.rule,e.maxLookahead,e.dynamicTokensEnabled,Tl(e.prodType),K_)}}class yI{initLooksAhead(e){this.dynamicTokensEnabled=w(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:dt.dynamicTokensEnabled,this.maxLookahead=w(e,"maxLookahead")?e.maxLookahead:dt.maxLookahead,this.lookaheadStrategy=w(e,"lookaheadStrategy")?e.lookaheadStrategy:new $l({maxLookahead:this.maxLookahead}),this.lookAheadFuncsCache=new Map}preComputeLookaheadFunctions(e){k(e,n=>{this.TRACE_INIT(`${n.name} Rule Lookahead`,()=>{const{alternation:r,repetition:i,option:s,repetitionMandatory:a,repetitionMandatoryWithSeparator:o,repetitionWithSeparator:l}=vI(n);k(r,u=>{const c=u.idx===0?"":u.idx;this.TRACE_INIT(`${We(u)}${c}`,()=>{const f=this.lookaheadStrategy.buildLookaheadForAlternation({prodOccurrence:u.idx,rule:n,maxLookahead:u.maxLookahead||this.maxLookahead,hasPredicates:u.hasPredicates,dynamicTokensEnabled:this.dynamicTokensEnabled}),d=Ea(this.fullRuleNameToShort[n.name],Nh,u.idx);this.setLaFuncCache(d,f)})}),k(i,u=>{this.computeLookaheadFunc(n,u.idx,oo,"Repetition",u.maxLookahead,We(u))}),k(s,u=>{this.computeLookaheadFunc(n,u.idx,bh,"Option",u.maxLookahead,We(u))}),k(a,u=>{this.computeLookaheadFunc(n,u.idx,lo,"RepetitionMandatory",u.maxLookahead,We(u))}),k(o,u=>{this.computeLookaheadFunc(n,u.idx,Li,"RepetitionMandatoryWithSeparator",u.maxLookahead,We(u))}),k(l,u=>{this.computeLookaheadFunc(n,u.idx,uo,"RepetitionWithSeparator",u.maxLookahead,We(u))})})})}computeLookaheadFunc(e,n,r,i,s,a){this.TRACE_INIT(`${a}${n===0?"":n}`,()=>{const o=this.lookaheadStrategy.buildLookaheadForOptional({prodOccurrence:n,rule:e,maxLookahead:s||this.maxLookahead,dynamicTokensEnabled:this.dynamicTokensEnabled,prodType:i}),l=Ea(this.fullRuleNameToShort[e.name],r,n);this.setLaFuncCache(l,o)})}getKeyForAutomaticLookahead(e,n){const r=this.getLastExplicitRuleShortName();return Ea(r,e,n)}getLaFuncFromCache(e){return this.lookAheadFuncsCache.get(e)}setLaFuncCache(e,n){this.lookAheadFuncsCache.set(e,n)}}class TI extends Kn{constructor(){super(...arguments),this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}}reset(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}}visitOption(e){this.dslMethods.option.push(e)}visitRepetitionWithSeparator(e){this.dslMethods.repetitionWithSeparator.push(e)}visitRepetitionMandatory(e){this.dslMethods.repetitionMandatory.push(e)}visitRepetitionMandatoryWithSeparator(e){this.dslMethods.repetitionMandatoryWithSeparator.push(e)}visitRepetition(e){this.dslMethods.repetition.push(e)}visitAlternation(e){this.dslMethods.alternation.push(e)}}const $i=new TI;function vI(t){$i.reset(),t.accept($i);const e=$i.dslMethods;return $i.reset(),e}function oc(t,e){isNaN(t.startOffset)===!0?(t.startOffset=e.startOffset,t.endOffset=e.endOffset):t.endOffseta.msg);throw Error(`Errors Detected in CST Visitor <${this.constructor.name}>: + ${s.join(` + +`).replace(/\n/g,` + `)}`)}}};return n.prototype=r,n.prototype.constructor=n,n._RULE_NAMES=e,n}function xI(t,e,n){const r=function(){};Oh(r,t+"BaseSemanticsWithDefaults");const i=Object.create(n.prototype);return k(e,s=>{i[s]=EI}),r.prototype=i,r.prototype.constructor=r,r}var co;(function(t){t[t.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",t[t.MISSING_METHOD=1]="MISSING_METHOD"})(co||(co={}));function _I(t,e){return II(t,e)}function II(t,e){const n=Le(e,i=>ht(t[i])===!1),r=I(n,i=>({msg:`Missing visitor method: <${i}> on ${t.constructor.name} CST Visitor.`,type:co.MISSING_METHOD,methodName:i}));return Jr(r)}class wI{initTreeBuilder(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=w(e,"nodeLocationTracking")?e.nodeLocationTracking:dt.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=J,this.cstFinallyStateUpdate=J,this.cstPostTerminal=J,this.cstPostNonTerminal=J,this.cstPostRule=J;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=lc,this.setNodeLocationFromNode=lc,this.cstPostRule=J,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=J,this.setNodeLocationFromNode=J,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=oc,this.setNodeLocationFromNode=oc,this.cstPostRule=J,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=J,this.setNodeLocationFromNode=J,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=J,this.setNodeLocationFromNode=J,this.cstPostRule=J,this.setInitialNodeLocation=J;else throw Error(`Invalid config option: "${e.nodeLocationTracking}"`)}setInitialNodeLocationOnlyOffsetRecovery(e){e.location={startOffset:NaN,endOffset:NaN}}setInitialNodeLocationOnlyOffsetRegular(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}}setInitialNodeLocationFullRecovery(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}}setInitialNodeLocationFullRegular(e){const n=this.LA(1);e.location={startOffset:n.startOffset,startLine:n.startLine,startColumn:n.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}}cstInvocationStateUpdate(e){const n={name:e,children:Object.create(null)};this.setInitialNodeLocation(n),this.CST_STACK.push(n)}cstFinallyStateUpdate(){this.CST_STACK.pop()}cstPostRuleFull(e){const n=this.LA(0),r=e.location;r.startOffset<=n.startOffset?(r.endOffset=n.endOffset,r.endLine=n.endLine,r.endColumn=n.endColumn):(r.startOffset=NaN,r.startLine=NaN,r.startColumn=NaN)}cstPostRuleOnlyOffset(e){const n=this.LA(0),r=e.location;r.startOffset<=n.startOffset?r.endOffset=n.endOffset:r.startOffset=NaN}cstPostTerminal(e,n){const r=this.CST_STACK[this.CST_STACK.length-1];$I(r,n,e),this.setNodeLocationFromToken(r.location,n)}cstPostNonTerminal(e,n){const r=this.CST_STACK[this.CST_STACK.length-1];RI(r,n,e),this.setNodeLocationFromNode(r.location,e.location)}getBaseCstVisitorConstructor(){if(ct(this.baseCstVisitorConstructor)){const e=SI(this.className,Pe(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor}getBaseCstVisitorConstructorWithDefaults(){if(ct(this.baseCstVisitorWithDefaultsConstructor)){const e=xI(this.className,Pe(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor}getLastExplicitRuleShortName(){const e=this.RULE_STACK;return e[e.length-1]}getPreviousExplicitRuleShortName(){const e=this.RULE_STACK;return e[e.length-2]}getLastExplicitRuleOccurrenceIndex(){const e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]}}class CI{initLexerAdapter(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1}set input(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length}get input(){return this.tokVector}SKIP_TOKEN(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):ss}LA(e){const n=this.currIdx+e;return n<0||this.tokVectorLength<=n?ss:this.tokVector[n]}consumeToken(){this.currIdx++}exportLexerState(){return this.currIdx}importLexerState(e){this.currIdx=e}resetLexerState(){this.currIdx=-1}moveToTerminatedState(){this.currIdx=this.tokVector.length-1}getLexerPosition(){return this.exportLexerState()}}class kI{ACTION(e){return e.call(this)}consume(e,n,r){return this.consumeInternal(n,e,r)}subrule(e,n,r){return this.subruleInternal(n,e,r)}option(e,n){return this.optionInternal(n,e)}or(e,n){return this.orInternal(n,e)}many(e,n){return this.manyInternal(e,n)}atLeastOne(e,n){return this.atLeastOneInternal(e,n)}CONSUME(e,n){return this.consumeInternal(e,0,n)}CONSUME1(e,n){return this.consumeInternal(e,1,n)}CONSUME2(e,n){return this.consumeInternal(e,2,n)}CONSUME3(e,n){return this.consumeInternal(e,3,n)}CONSUME4(e,n){return this.consumeInternal(e,4,n)}CONSUME5(e,n){return this.consumeInternal(e,5,n)}CONSUME6(e,n){return this.consumeInternal(e,6,n)}CONSUME7(e,n){return this.consumeInternal(e,7,n)}CONSUME8(e,n){return this.consumeInternal(e,8,n)}CONSUME9(e,n){return this.consumeInternal(e,9,n)}SUBRULE(e,n){return this.subruleInternal(e,0,n)}SUBRULE1(e,n){return this.subruleInternal(e,1,n)}SUBRULE2(e,n){return this.subruleInternal(e,2,n)}SUBRULE3(e,n){return this.subruleInternal(e,3,n)}SUBRULE4(e,n){return this.subruleInternal(e,4,n)}SUBRULE5(e,n){return this.subruleInternal(e,5,n)}SUBRULE6(e,n){return this.subruleInternal(e,6,n)}SUBRULE7(e,n){return this.subruleInternal(e,7,n)}SUBRULE8(e,n){return this.subruleInternal(e,8,n)}SUBRULE9(e,n){return this.subruleInternal(e,9,n)}OPTION(e){return this.optionInternal(e,0)}OPTION1(e){return this.optionInternal(e,1)}OPTION2(e){return this.optionInternal(e,2)}OPTION3(e){return this.optionInternal(e,3)}OPTION4(e){return this.optionInternal(e,4)}OPTION5(e){return this.optionInternal(e,5)}OPTION6(e){return this.optionInternal(e,6)}OPTION7(e){return this.optionInternal(e,7)}OPTION8(e){return this.optionInternal(e,8)}OPTION9(e){return this.optionInternal(e,9)}OR(e){return this.orInternal(e,0)}OR1(e){return this.orInternal(e,1)}OR2(e){return this.orInternal(e,2)}OR3(e){return this.orInternal(e,3)}OR4(e){return this.orInternal(e,4)}OR5(e){return this.orInternal(e,5)}OR6(e){return this.orInternal(e,6)}OR7(e){return this.orInternal(e,7)}OR8(e){return this.orInternal(e,8)}OR9(e){return this.orInternal(e,9)}MANY(e){this.manyInternal(0,e)}MANY1(e){this.manyInternal(1,e)}MANY2(e){this.manyInternal(2,e)}MANY3(e){this.manyInternal(3,e)}MANY4(e){this.manyInternal(4,e)}MANY5(e){this.manyInternal(5,e)}MANY6(e){this.manyInternal(6,e)}MANY7(e){this.manyInternal(7,e)}MANY8(e){this.manyInternal(8,e)}MANY9(e){this.manyInternal(9,e)}MANY_SEP(e){this.manySepFirstInternal(0,e)}MANY_SEP1(e){this.manySepFirstInternal(1,e)}MANY_SEP2(e){this.manySepFirstInternal(2,e)}MANY_SEP3(e){this.manySepFirstInternal(3,e)}MANY_SEP4(e){this.manySepFirstInternal(4,e)}MANY_SEP5(e){this.manySepFirstInternal(5,e)}MANY_SEP6(e){this.manySepFirstInternal(6,e)}MANY_SEP7(e){this.manySepFirstInternal(7,e)}MANY_SEP8(e){this.manySepFirstInternal(8,e)}MANY_SEP9(e){this.manySepFirstInternal(9,e)}AT_LEAST_ONE(e){this.atLeastOneInternal(0,e)}AT_LEAST_ONE1(e){return this.atLeastOneInternal(1,e)}AT_LEAST_ONE2(e){this.atLeastOneInternal(2,e)}AT_LEAST_ONE3(e){this.atLeastOneInternal(3,e)}AT_LEAST_ONE4(e){this.atLeastOneInternal(4,e)}AT_LEAST_ONE5(e){this.atLeastOneInternal(5,e)}AT_LEAST_ONE6(e){this.atLeastOneInternal(6,e)}AT_LEAST_ONE7(e){this.atLeastOneInternal(7,e)}AT_LEAST_ONE8(e){this.atLeastOneInternal(8,e)}AT_LEAST_ONE9(e){this.atLeastOneInternal(9,e)}AT_LEAST_ONE_SEP(e){this.atLeastOneSepFirstInternal(0,e)}AT_LEAST_ONE_SEP1(e){this.atLeastOneSepFirstInternal(1,e)}AT_LEAST_ONE_SEP2(e){this.atLeastOneSepFirstInternal(2,e)}AT_LEAST_ONE_SEP3(e){this.atLeastOneSepFirstInternal(3,e)}AT_LEAST_ONE_SEP4(e){this.atLeastOneSepFirstInternal(4,e)}AT_LEAST_ONE_SEP5(e){this.atLeastOneSepFirstInternal(5,e)}AT_LEAST_ONE_SEP6(e){this.atLeastOneSepFirstInternal(6,e)}AT_LEAST_ONE_SEP7(e){this.atLeastOneSepFirstInternal(7,e)}AT_LEAST_ONE_SEP8(e){this.atLeastOneSepFirstInternal(8,e)}AT_LEAST_ONE_SEP9(e){this.atLeastOneSepFirstInternal(9,e)}RULE(e,n,r=as){if(ge(this.definedRulesNames,e)){const a={message:jt.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),type:de.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(a)}this.definedRulesNames.push(e);const i=this.defineRule(e,n,r);return this[e]=i,i}OVERRIDE_RULE(e,n,r=as){const i=Q_(e,this.definedRulesNames,this.className);this.definitionErrors=this.definitionErrors.concat(i);const s=this.defineRule(e,n,r);return this[e]=s,s}BACKTRACK(e,n){return function(){this.isBackTrackingStack.push(1);const r=this.saveRecogState();try{return e.apply(this,n),!0}catch(i){if(rs(i))return!1;throw i}finally{this.reloadRecogState(r),this.isBackTrackingStack.pop()}}}getGAstProductions(){return this.gastProductionsCache}getSerializedGastProductions(){return Gx(Z(this.gastProductionsCache))}}class NI{initRecognizerEngine(e,n){if(this.className=this.constructor.name,this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=ns,this.subruleIdx=0,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},w(n,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 + For Further details.`);if(M(e)){if(U(e))throw Error(`A Token Vocabulary cannot be empty. + Note that the first argument for the parser constructor + is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 + For Further details.`)}if(M(e))this.tokensMap=Se(e,(s,a)=>(s[a.name]=a,s),{});else if(w(e,"modes")&&qe(Ge(Z(e.modes)),C_)){const s=Ge(Z(e.modes)),a=ll(s);this.tokensMap=Se(a,(o,l)=>(o[l.name]=l,o),{})}else if(Oe(e))this.tokensMap=se(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=wt;const r=w(e,"modes")?Ge(Z(e.modes)):Z(e),i=qe(r,s=>U(s.categoryMatches));this.tokenMatcher=i?ns:ti,ni(Z(this.tokensMap))}defineRule(e,n,r){if(this.selfAnalysisDone)throw Error(`Grammar rule <${e}> may not be defined after the 'performSelfAnalysis' method has been called' +Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);const i=w(r,"resyncEnabled")?r.resyncEnabled:as.resyncEnabled,s=w(r,"recoveryValueFunc")?r.recoveryValueFunc:as.recoveryValueFunc,a=this.ruleShortNameIdx<a.call(this)&&o.call(this)}}else s=e;if(i.call(this)===!0)return s.call(this)}atLeastOneInternal(e,n){const r=this.getKeyForAutomaticLookahead(lo,e);return this.atLeastOneInternalLogic(e,n,r)}atLeastOneInternalLogic(e,n,r){let i=this.getLaFuncFromCache(r),s;if(typeof n!="function"){s=n.DEF;const a=n.GATE;if(a!==void 0){const o=i;i=()=>a.call(this)&&o.call(this)}}else s=n;if(i.call(this)===!0){let a=this.doSingleRepetition(s);for(;i.call(this)===!0&&a===!0;)a=this.doSingleRepetition(s)}else throw this.raiseEarlyExitException(e,W.REPETITION_MANDATORY,n.ERR_MSG);this.attemptInRepetitionRecovery(this.atLeastOneInternal,[e,n],i,lo,e,F_)}atLeastOneSepFirstInternal(e,n){const r=this.getKeyForAutomaticLookahead(Li,e);this.atLeastOneSepFirstInternalLogic(e,n,r)}atLeastOneSepFirstInternalLogic(e,n,r){const i=n.DEF,s=n.SEP;if(this.getLaFuncFromCache(r).call(this)===!0){i.call(this);const o=()=>this.tokenMatcher(this.LA(1),s);for(;this.tokenMatcher(this.LA(1),s)===!0;)this.CONSUME(s),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,s,o,i,ic],o,Li,e,ic)}else throw this.raiseEarlyExitException(e,W.REPETITION_MANDATORY_WITH_SEPARATOR,n.ERR_MSG)}manyInternal(e,n){const r=this.getKeyForAutomaticLookahead(oo,e);return this.manyInternalLogic(e,n,r)}manyInternalLogic(e,n,r){let i=this.getLaFuncFromCache(r),s;if(typeof n!="function"){s=n.DEF;const o=n.GATE;if(o!==void 0){const l=i;i=()=>o.call(this)&&l.call(this)}}else s=n;let a=!0;for(;i.call(this)===!0&&a===!0;)a=this.doSingleRepetition(s);this.attemptInRepetitionRecovery(this.manyInternal,[e,n],i,oo,e,D_,a)}manySepFirstInternal(e,n){const r=this.getKeyForAutomaticLookahead(uo,e);this.manySepFirstInternalLogic(e,n,r)}manySepFirstInternalLogic(e,n,r){const i=n.DEF,s=n.SEP;if(this.getLaFuncFromCache(r).call(this)===!0){i.call(this);const o=()=>this.tokenMatcher(this.LA(1),s);for(;this.tokenMatcher(this.LA(1),s)===!0;)this.CONSUME(s),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,s,o,i,rc],o,uo,e,rc)}}repetitionSepSecondInternal(e,n,r,i,s){for(;r();)this.CONSUME(n),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,n,r,i,s],r,Li,e,s)}doSingleRepetition(e){const n=this.getLexerPosition();return e.call(this),this.getLexerPosition()>n}orInternal(e,n){const r=this.getKeyForAutomaticLookahead(Nh,n),i=M(e)?e:e.DEF,a=this.getLaFuncFromCache(r).call(this,i);if(a!==void 0)return i[a].ALT.call(this);this.raiseNoAltException(n,e.ERR_MSG)}ruleFinallyStateUpdate(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){const e=this.LA(1),n=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new fI(n,e))}}subruleInternal(e,n,r){let i;try{const s=r!==void 0?r.ARGS:void 0;return this.subruleIdx=n,i=e.apply(this,s),this.cstPostNonTerminal(i,r!==void 0&&r.LABEL!==void 0?r.LABEL:e.ruleName),i}catch(s){throw this.subruleInternalError(s,r,e.ruleName)}}subruleInternalError(e,n,r){throw rs(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,n!==void 0&&n.LABEL!==void 0?n.LABEL:r),delete e.partialCstResult),e}consumeInternal(e,n,r){let i;try{const s=this.LA(1);this.tokenMatcher(s,e)===!0?(this.consumeToken(),i=s):this.consumeInternalError(e,s,r)}catch(s){i=this.consumeInternalRecovery(e,n,s)}return this.cstPostTerminal(r!==void 0&&r.LABEL!==void 0?r.LABEL:e.name,i),i}consumeInternalError(e,n,r){let i;const s=this.LA(0);throw r!==void 0&&r.ERR_MSG?i=r.ERR_MSG:i=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:n,previous:s,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new Ch(i,n,s))}consumeInternalRecovery(e,n,r){if(this.recoveryEnabled&&r.name==="MismatchedTokenException"&&!this.isBackTracking()){const i=this.getFollowsForInRuleRecovery(e,n);try{return this.tryInRuleRecovery(e,i)}catch(s){throw s.name===kh?r:s}}else throw r}saveRecogState(){const e=this.errors,n=se(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:n,CST_STACK:this.CST_STACK}}reloadRecogState(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK}ruleInvocationStateUpdate(e,n,r){this.RULE_OCCURRENCE_STACK.push(r),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(n)}isBackTracking(){return this.isBackTrackingStack.length!==0}getCurrRuleFullName(){const e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]}shortRuleNameToFullName(e){return this.shortRuleNameToFull[e]}isAtEndOfInput(){return this.tokenMatcher(this.LA(1),wt)}reset(){this.resetLexerState(),this.subruleIdx=0,this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]}}class bI{initErrorHandler(e){this._errors=[],this.errorMessageProvider=w(e,"errorMessageProvider")?e.errorMessageProvider:dt.errorMessageProvider}SAVE_ERROR(e){if(rs(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:se(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")}get errors(){return se(this._errors)}set errors(e){this._errors=e}raiseEarlyExitException(e,n,r){const i=this.getCurrRuleFullName(),s=this.getGAstProductions()[i],o=Xs(e,s,n,this.maxLookahead)[0],l=[];for(let c=1;c<=this.maxLookahead;c++)l.push(this.LA(c));const u=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:o,actual:l,previous:this.LA(0),customUserDescription:r,ruleName:i});throw this.SAVE_ERROR(new dI(u,this.LA(1),this.LA(0)))}raiseNoAltException(e,n){const r=this.getCurrRuleFullName(),i=this.getGAstProductions()[r],s=Ys(e,i,this.maxLookahead),a=[];for(let u=1;u<=this.maxLookahead;u++)a.push(this.LA(u));const o=this.LA(0),l=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:s,actual:a,previous:o,customUserDescription:n,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new cI(l,this.LA(1),o))}}class OI{initContentAssist(){}computeContentAssist(e,n){const r=this.gastProductionsCache[e];if(ct(r))throw Error(`Rule ->${e}<- does not exist in this grammar.`);return Th([r],n,this.tokenMatcher,this.maxLookahead)}getNextPossibleTokenTypes(e){const n=Be(e.ruleStack),i=this.getGAstProductions()[n];return new M_(i,e).startWalking()}}const Zs={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(Zs);const uc=!0,cc=Math.pow(2,Ot)-1,Ph=gh({name:"RECORDING_PHASE_TOKEN",pattern:he.NA});ni([Ph]);const Lh=yl(Ph,`This IToken indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(Lh);const PI={name:`This CSTNode indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}};class LI{initGastRecorder(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1}enableRecording(){this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",()=>{for(let e=0;e<10;e++){const n=e>0?e:"";this[`CONSUME${n}`]=function(r,i){return this.consumeInternalRecord(r,e,i)},this[`SUBRULE${n}`]=function(r,i){return this.subruleInternalRecord(r,e,i)},this[`OPTION${n}`]=function(r){return this.optionInternalRecord(r,e)},this[`OR${n}`]=function(r){return this.orInternalRecord(r,e)},this[`MANY${n}`]=function(r){this.manyInternalRecord(e,r)},this[`MANY_SEP${n}`]=function(r){this.manySepFirstInternalRecord(e,r)},this[`AT_LEAST_ONE${n}`]=function(r){this.atLeastOneInternalRecord(e,r)},this[`AT_LEAST_ONE_SEP${n}`]=function(r){this.atLeastOneSepFirstInternalRecord(e,r)}}this.consume=function(e,n,r){return this.consumeInternalRecord(n,e,r)},this.subrule=function(e,n,r){return this.subruleInternalRecord(n,e,r)},this.option=function(e,n){return this.optionInternalRecord(n,e)},this.or=function(e,n){return this.orInternalRecord(n,e)},this.many=function(e,n){this.manyInternalRecord(e,n)},this.atLeastOne=function(e,n){this.atLeastOneInternalRecord(e,n)},this.ACTION=this.ACTION_RECORD,this.BACKTRACK=this.BACKTRACK_RECORD,this.LA=this.LA_RECORD})}disableRecording(){this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",()=>{const e=this;for(let n=0;n<10;n++){const r=n>0?n:"";delete e[`CONSUME${r}`],delete e[`SUBRULE${r}`],delete e[`OPTION${r}`],delete e[`OR${r}`],delete e[`MANY${r}`],delete e[`MANY_SEP${r}`],delete e[`AT_LEAST_ONE${r}`],delete e[`AT_LEAST_ONE_SEP${r}`]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})}ACTION_RECORD(e){}BACKTRACK_RECORD(e,n){return()=>!0}LA_RECORD(e){return ss}topLevelRuleRecord(e,n){try{const r=new jn({definition:[],name:e});return r.name=e,this.recordingProdStack.push(r),n.call(this),this.recordingProdStack.pop(),r}catch(r){if(r.KNOWN_RECORDER_ERROR!==!0)try{r.message=r.message+` + This error was thrown during the "grammar recording phase" For more info see: + https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch{throw r}throw r}}optionInternalRecord(e,n){return qn.call(this,ie,e,n)}atLeastOneInternalRecord(e,n){qn.call(this,xe,n,e)}atLeastOneSepFirstInternalRecord(e,n){qn.call(this,_e,n,e,uc)}manyInternalRecord(e,n){qn.call(this,q,n,e)}manySepFirstInternalRecord(e,n){qn.call(this,ye,n,e,uc)}orInternalRecord(e,n){return MI.call(this,e,n)}subruleInternalRecord(e,n,r){if(is(n),!e||w(e,"ruleName")===!1){const o=new Error(` argument is invalid expecting a Parser method reference but got: <${JSON.stringify(e)}> + inside top level rule: <${this.recordingProdStack[0].name}>`);throw o.KNOWN_RECORDER_ERROR=!0,o}const i=Pn(this.recordingProdStack),s=e.ruleName,a=new fe({idx:n,nonTerminalName:s,label:r==null?void 0:r.LABEL,referencedRule:void 0});return i.definition.push(a),this.outputCst?PI:Zs}consumeInternalRecord(e,n,r){if(is(n),!ph(e)){const a=new Error(` argument is invalid expecting a TokenType reference but got: <${JSON.stringify(e)}> + inside top level rule: <${this.recordingProdStack[0].name}>`);throw a.KNOWN_RECORDER_ERROR=!0,a}const i=Pn(this.recordingProdStack),s=new K({idx:n,terminalType:e,label:r==null?void 0:r.LABEL});return i.definition.push(s),Lh}}function qn(t,e,n,r=!1){is(n);const i=Pn(this.recordingProdStack),s=ht(e)?e:e.DEF,a=new t({definition:[],idx:n});return r&&(a.separator=e.SEP),w(e,"MAX_LOOKAHEAD")&&(a.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(a),s.call(this),i.definition.push(a),this.recordingProdStack.pop(),Zs}function MI(t,e){is(e);const n=Pn(this.recordingProdStack),r=M(t)===!1,i=r===!1?t:t.DEF,s=new Te({definition:[],idx:e,ignoreAmbiguities:r&&t.IGNORE_AMBIGUITIES===!0});w(t,"MAX_LOOKAHEAD")&&(s.maxLookahead=t.MAX_LOOKAHEAD);const a=AR(i,o=>ht(o.GATE));return s.hasPredicates=a,n.definition.push(s),k(i,o=>{const l=new me({definition:[]});s.definition.push(l),w(o,"IGNORE_AMBIGUITIES")?l.ignoreAmbiguities=o.IGNORE_AMBIGUITIES:w(o,"GATE")&&(l.ignoreAmbiguities=!0),this.recordingProdStack.push(l),o.ALT.call(this),this.recordingProdStack.pop()}),Zs}function fc(t){return t===0?"":`${t}`}function is(t){if(t<0||t>cc){const e=new Error(`Invalid DSL Method idx value: <${t}> + Idx value must be a none negative value smaller than ${cc+1}`);throw e.KNOWN_RECORDER_ERROR=!0,e}}class DI{initPerformanceTracer(e){if(w(e,"traceInitPerf")){const n=e.traceInitPerf,r=typeof n=="number";this.traceInitMaxIdent=r?n:1/0,this.traceInitPerf=r?n>0:n}else this.traceInitMaxIdent=0,this.traceInitPerf=dt.traceInitPerf;this.traceInitIndent=-1}TRACE_INIT(e,n){if(this.traceInitPerf===!0){this.traceInitIndent++;const r=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <${e}>`);const{time:i,value:s}=Md(n),a=i>10?console.warn:console.log;return this.traceInitIndent time: ${i}ms`),this.traceInitIndent--,s}else return n()}}function FI(t,e){e.forEach(n=>{const r=n.prototype;Object.getOwnPropertyNames(r).forEach(i=>{if(i==="constructor")return;const s=Object.getOwnPropertyDescriptor(r,i);s&&(s.get||s.set)?Object.defineProperty(t.prototype,i,s):t.prototype[i]=n.prototype[i]})})}const ss=yl(wt,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(ss);const dt=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:fn,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1}),as=Object.freeze({recoveryValueFunc:()=>{},resyncEnabled:!0});var de;(function(t){t[t.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",t[t.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",t[t.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",t[t.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",t[t.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",t[t.LEFT_RECURSION=5]="LEFT_RECURSION",t[t.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",t[t.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",t[t.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",t[t.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",t[t.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",t[t.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",t[t.TOO_MANY_ALTS=12]="TOO_MANY_ALTS",t[t.CUSTOM_LOOKAHEAD_VALIDATION=13]="CUSTOM_LOOKAHEAD_VALIDATION"})(de||(de={}));function dc(t=void 0){return function(){return t}}class ri{static performSelfAnalysis(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")}performSelfAnalysis(){this.TRACE_INIT("performSelfAnalysis",()=>{let e;this.selfAnalysisDone=!0;const n=this.className;this.TRACE_INIT("toFastProps",()=>{Dd(this)}),this.TRACE_INIT("Grammar Recording",()=>{try{this.enableRecording(),k(this.definedRulesNames,i=>{const a=this[i].originalGrammarAction;let o;this.TRACE_INIT(`${i} Rule`,()=>{o=this.topLevelRuleRecord(i,a)}),this.gastProductionsCache[i]=o})}finally{this.disableRecording()}});let r=[];if(this.TRACE_INIT("Grammar Resolving",()=>{r=lI({rules:Z(this.gastProductionsCache)}),this.definitionErrors=this.definitionErrors.concat(r)}),this.TRACE_INIT("Grammar Validations",()=>{if(U(r)&&this.skipValidations===!1){const i=uI({rules:Z(this.gastProductionsCache),tokenTypes:Z(this.tokensMap),errMsgProvider:jt,grammarName:n}),s=V_({lookaheadStrategy:this.lookaheadStrategy,rules:Z(this.gastProductionsCache),tokenTypes:Z(this.tokensMap),grammarName:n});this.definitionErrors=this.definitionErrors.concat(i,s)}}),U(this.definitionErrors)&&(this.recoveryEnabled&&this.TRACE_INIT("computeAllProdsFollows",()=>{const i=zx(Z(this.gastProductionsCache));this.resyncFollows=i}),this.TRACE_INIT("ComputeLookaheadFunctions",()=>{var i,s;(s=(i=this.lookaheadStrategy).initialize)===null||s===void 0||s.call(i,{rules:Z(this.gastProductionsCache)}),this.preComputeLookaheadFunctions(Z(this.gastProductionsCache))})),!ri.DEFER_DEFINITION_ERRORS_HANDLING&&!U(this.definitionErrors))throw e=I(this.definitionErrors,i=>i.message),new Error(`Parser Definition Errors detected: + ${e.join(` +------------------------------- +`)}`)})}constructor(e,n){this.definitionErrors=[],this.selfAnalysisDone=!1;const r=this;if(r.initErrorHandler(n),r.initLexerAdapter(),r.initLooksAhead(n),r.initRecognizerEngine(e,n),r.initRecoverable(n),r.initTreeBuilder(n),r.initContentAssist(),r.initGastRecorder(n),r.initPerformanceTracer(n),w(n,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. + Please use the flag on the relevant DSL method instead. + See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES + For further details.`);this.skipValidations=w(n,"skipValidations")?n.skipValidations:dt.skipValidations}}ri.DEFER_DEFINITION_ERRORS_HANDLING=!1;FI(ri,[pI,yI,wI,CI,NI,kI,bI,OI,LI,DI]);class GI extends ri{constructor(e,n=dt){const r=se(n);r.outputCst=!1,super(e,r)}}function Mn(t,e,n){return`${t.name}_${e}_${n}`}const Ct=1,UI=2,Mh=4,Dh=5,ii=7,BI=8,jI=9,KI=10,HI=11,Fh=12;class Rl{constructor(e){this.target=e}isEpsilon(){return!1}}class Al extends Rl{constructor(e,n){super(e),this.tokenType=n}}class Gh extends Rl{constructor(e){super(e)}isEpsilon(){return!0}}class El extends Rl{constructor(e,n,r){super(e),this.rule=n,this.followState=r}isEpsilon(){return!0}}function WI(t){const e={decisionMap:{},decisionStates:[],ruleToStartState:new Map,ruleToStopState:new Map,states:[]};zI(e,t);const n=t.length;for(let r=0;rUh(t,e,a));return Hn(t,e,r,n,...i)}function ZI(t,e,n){const r=ee(t,e,n,{type:Ct});Pt(t,r);const i=Hn(t,e,r,n,sn(t,e,n));return QI(t,e,n,i)}function sn(t,e,n){const r=jp(ot(n.definition,i=>Uh(t,e,i)),i=>i!==void 0);return r.length===1?r[0]:r.length===0?void 0:tw(t,r)}function Bh(t,e,n,r,i){const s=r.left,a=r.right,o=ee(t,e,n,{type:HI});Pt(t,o);const l=ee(t,e,n,{type:Fh});return s.loopback=o,l.loopback=o,t.decisionMap[Mn(e,i?"RepetitionMandatoryWithSeparator":"RepetitionMandatory",n.idx)]=o,X(a,o),i===void 0?(X(o,s),X(o,l)):(X(o,l),X(o,i.left),X(i.right,s)),{left:s,right:l}}function jh(t,e,n,r,i){const s=r.left,a=r.right,o=ee(t,e,n,{type:KI});Pt(t,o);const l=ee(t,e,n,{type:Fh}),u=ee(t,e,n,{type:jI});return o.loopback=u,l.loopback=u,X(o,s),X(o,l),X(a,u),i!==void 0?(X(u,l),X(u,i.left),X(i.right,s)):X(u,o),t.decisionMap[Mn(e,i?"RepetitionWithSeparator":"Repetition",n.idx)]=o,{left:o,right:l}}function QI(t,e,n,r){const i=r.left,s=r.right;return X(i,s),t.decisionMap[Mn(e,"Option",n.idx)]=i,r}function Pt(t,e){return t.decisionStates.push(e),e.decision=t.decisionStates.length-1,e.decision}function Hn(t,e,n,r,...i){const s=ee(t,e,r,{type:BI,start:n});n.end=s;for(const o of i)o!==void 0?(X(n,o.left),X(o.right,s)):X(n,s);const a={left:n,right:s};return t.decisionMap[Mn(e,ew(r),r.idx)]=n,a}function ew(t){if(t instanceof Te)return"Alternation";if(t instanceof ie)return"Option";if(t instanceof q)return"Repetition";if(t instanceof ye)return"RepetitionWithSeparator";if(t instanceof xe)return"RepetitionMandatory";if(t instanceof _e)return"RepetitionMandatoryWithSeparator";throw new Error("Invalid production type encountered")}function tw(t,e){const n=e.length;for(let s=0;se.alt)}get key(){let e="";for(const n in this.map)e+=n+":";return e}}function Kh(t,e=!0){return`${e?`a${t.alt}`:""}s${t.state.stateNumber}:${t.stack.map(n=>n.stateNumber.toString()).join("_")}`}function sw(t,e){const n={};return r=>{const i=r.toString();let s=n[i];return s!==void 0||(s={atnStartState:t,decision:e,states:{}},n[i]=s),s}}class Hh{constructor(){this.predicates=[]}is(e){return e>=this.predicates.length||this.predicates[e]}set(e,n){this.predicates[e]=n}toString(){let e="";const n=this.predicates.length;for(let r=0;rconsole.log(r)}initialize(e){this.atn=WI(e.rules),this.dfas=ow(this.atn)}validateAmbiguousAlternationAlternatives(){return[]}validateEmptyOrAlternatives(){return[]}buildLookaheadForAlternation(e){const{prodOccurrence:n,rule:r,hasPredicates:i,dynamicTokensEnabled:s}=e,a=this.dfas,o=this.logging,l=Mn(r,"Alternation",n),c=this.atn.decisionMap[l].decision,f=ot(sc({maxLookahead:1,occurrence:n,prodType:"Alternation",rule:r}),d=>ot(d,h=>h[0]));if(pc(f,!1)&&!s){const d=Pl(f,(h,m,g)=>(sa(m,T=>{T&&(h[T.tokenTypeIdx]=g,sa(T.categoryMatches,y=>{h[y]=g}))}),h),{});return i?function(h){var m;const g=this.LA(1),T=d[g.tokenTypeIdx];if(h!==void 0&&T!==void 0){const y=(m=h[T])===null||m===void 0?void 0:m.GATE;if(y!==void 0&&y.call(this)===!1)return}return T}:function(){const h=this.LA(1);return d[h.tokenTypeIdx]}}else return i?function(d){const h=new Hh,m=d===void 0?0:d.length;for(let T=0;Tot(d,h=>h[0]));if(pc(f)&&f[0][0]&&!s){const d=f[0],h=Dp(d);if(h.length===1&&Kp(h[0].categoryMatches)){const g=h[0].tokenTypeIdx;return function(){return this.LA(1).tokenTypeIdx===g}}else{const m=Pl(h,(g,T)=>(T!==void 0&&(g[T.tokenTypeIdx]=!0,sa(T.categoryMatches,y=>{g[y]=!0})),g),{});return function(){const g=this.LA(1);return m[g.tokenTypeIdx]===!0}}}return function(){const d=Sa.call(this,a,c,hc,o);return typeof d=="object"?!1:d===0}}}function pc(t,e=!0){const n=new Set;for(const r of t){const i=new Set;for(const s of r){if(s===void 0){if(e)break;return!1}const a=[s.tokenTypeIdx].concat(s.categoryMatches);for(const o of a)if(n.has(o)){if(!i.has(o))return!1}else n.add(o),i.add(o)}}return!0}function ow(t){const e=t.decisionStates.length,n=Array(e);for(let r=0;rpn(i)).join(", "),n=t.production.idx===0?"":t.production.idx;let r=`Ambiguous Alternatives Detected: <${t.ambiguityIndices.join(", ")}> in <${dw(t.production)}${n}> inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +`;return r=r+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,r}function dw(t){if(t instanceof fe)return"SUBRULE";if(t instanceof ie)return"OPTION";if(t instanceof Te)return"OR";if(t instanceof xe)return"AT_LEAST_ONE";if(t instanceof _e)return"AT_LEAST_ONE_SEP";if(t instanceof ye)return"MANY_SEP";if(t instanceof q)return"MANY";if(t instanceof K)return"CONSUME";throw Error("non exhaustive match")}function hw(t,e,n){const r=Hp(e.configs.elements,s=>s.state.transitions),i=Wp(r.filter(s=>s instanceof Al).map(s=>s.tokenType),s=>s.tokenTypeIdx);return{actualToken:n,possibleTokenTypes:i,tokenPath:t}}function pw(t,e){return t.edges[e.tokenTypeIdx]}function mw(t,e,n){const r=new fo,i=[];for(const a of t.elements){if(n.is(a.alt)===!1)continue;if(a.state.type===ii){i.push(a);continue}const o=a.state.transitions.length;for(let l=0;l0&&!$w(s))for(const a of i)s.add(a);return s}function gw(t,e){if(t instanceof Al&&yh(e,t.tokenType))return t.target}function yw(t,e){let n;for(const r of t.elements)if(e.is(r.alt)===!0){if(n===void 0)n=r.alt;else if(n!==r.alt)return}return n}function Wh(t){return{configs:t,edges:{},isAcceptState:!1,prediction:-1}}function mc(t,e,n,r){return r=zh(t,r),e.edges[n.tokenTypeIdx]=r,r}function zh(t,e){if(e===os)return e;const n=e.configs.key,r=t.states[n];return r!==void 0?r:(e.configs.finalize(),t.states[n]=e,e)}function Tw(t){const e=new fo,n=t.transitions.length;for(let r=0;r0){const i=[...t.stack],a={state:i.pop(),alt:t.alt,stack:i};ls(a,e)}else e.add(t);return}n.epsilonOnlyTransitions||e.add(t);const r=n.transitions.length;for(let i=0;i1)return!0;return!1}function xw(t){for(const e of Array.from(t.values()))if(Object.keys(e).length===1)return!0;return!1}var gc;(function(t){function e(n){return typeof n=="string"}t.is=e})(gc||(gc={}));var ho;(function(t){function e(n){return typeof n=="string"}t.is=e})(ho||(ho={}));var yc;(function(t){t.MIN_VALUE=-2147483648,t.MAX_VALUE=2147483647;function e(n){return typeof n=="number"&&t.MIN_VALUE<=n&&n<=t.MAX_VALUE}t.is=e})(yc||(yc={}));var us;(function(t){t.MIN_VALUE=0,t.MAX_VALUE=2147483647;function e(n){return typeof n=="number"&&t.MIN_VALUE<=n&&n<=t.MAX_VALUE}t.is=e})(us||(us={}));var D;(function(t){function e(r,i){return r===Number.MAX_VALUE&&(r=us.MAX_VALUE),i===Number.MAX_VALUE&&(i=us.MAX_VALUE),{line:r,character:i}}t.create=e;function n(r){let i=r;return p.objectLiteral(i)&&p.uinteger(i.line)&&p.uinteger(i.character)}t.is=n})(D||(D={}));var L;(function(t){function e(r,i,s,a){if(p.uinteger(r)&&p.uinteger(i)&&p.uinteger(s)&&p.uinteger(a))return{start:D.create(r,i),end:D.create(s,a)};if(D.is(r)&&D.is(i))return{start:r,end:i};throw new Error(`Range#create called with invalid arguments[${r}, ${i}, ${s}, ${a}]`)}t.create=e;function n(r){let i=r;return p.objectLiteral(i)&&D.is(i.start)&&D.is(i.end)}t.is=n})(L||(L={}));var cs;(function(t){function e(r,i){return{uri:r,range:i}}t.create=e;function n(r){let i=r;return p.objectLiteral(i)&&L.is(i.range)&&(p.string(i.uri)||p.undefined(i.uri))}t.is=n})(cs||(cs={}));var Tc;(function(t){function e(r,i,s,a){return{targetUri:r,targetRange:i,targetSelectionRange:s,originSelectionRange:a}}t.create=e;function n(r){let i=r;return p.objectLiteral(i)&&L.is(i.targetRange)&&p.string(i.targetUri)&&L.is(i.targetSelectionRange)&&(L.is(i.originSelectionRange)||p.undefined(i.originSelectionRange))}t.is=n})(Tc||(Tc={}));var po;(function(t){function e(r,i,s,a){return{red:r,green:i,blue:s,alpha:a}}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&p.numberRange(i.red,0,1)&&p.numberRange(i.green,0,1)&&p.numberRange(i.blue,0,1)&&p.numberRange(i.alpha,0,1)}t.is=n})(po||(po={}));var vc;(function(t){function e(r,i){return{range:r,color:i}}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&L.is(i.range)&&po.is(i.color)}t.is=n})(vc||(vc={}));var $c;(function(t){function e(r,i,s){return{label:r,textEdit:i,additionalTextEdits:s}}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&p.string(i.label)&&(p.undefined(i.textEdit)||Fn.is(i))&&(p.undefined(i.additionalTextEdits)||p.typedArray(i.additionalTextEdits,Fn.is))}t.is=n})($c||($c={}));var Rc;(function(t){t.Comment="comment",t.Imports="imports",t.Region="region"})(Rc||(Rc={}));var Ac;(function(t){function e(r,i,s,a,o,l){const u={startLine:r,endLine:i};return p.defined(s)&&(u.startCharacter=s),p.defined(a)&&(u.endCharacter=a),p.defined(o)&&(u.kind=o),p.defined(l)&&(u.collapsedText=l),u}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&p.uinteger(i.startLine)&&p.uinteger(i.startLine)&&(p.undefined(i.startCharacter)||p.uinteger(i.startCharacter))&&(p.undefined(i.endCharacter)||p.uinteger(i.endCharacter))&&(p.undefined(i.kind)||p.string(i.kind))}t.is=n})(Ac||(Ac={}));var mo;(function(t){function e(r,i){return{location:r,message:i}}t.create=e;function n(r){let i=r;return p.defined(i)&&cs.is(i.location)&&p.string(i.message)}t.is=n})(mo||(mo={}));var Ec;(function(t){t.Error=1,t.Warning=2,t.Information=3,t.Hint=4})(Ec||(Ec={}));var Sc;(function(t){t.Unnecessary=1,t.Deprecated=2})(Sc||(Sc={}));var xc;(function(t){function e(n){const r=n;return p.objectLiteral(r)&&p.string(r.href)}t.is=e})(xc||(xc={}));var fs;(function(t){function e(r,i,s,a,o,l){let u={range:r,message:i};return p.defined(s)&&(u.severity=s),p.defined(a)&&(u.code=a),p.defined(o)&&(u.source=o),p.defined(l)&&(u.relatedInformation=l),u}t.create=e;function n(r){var i;let s=r;return p.defined(s)&&L.is(s.range)&&p.string(s.message)&&(p.number(s.severity)||p.undefined(s.severity))&&(p.integer(s.code)||p.string(s.code)||p.undefined(s.code))&&(p.undefined(s.codeDescription)||p.string((i=s.codeDescription)===null||i===void 0?void 0:i.href))&&(p.string(s.source)||p.undefined(s.source))&&(p.undefined(s.relatedInformation)||p.typedArray(s.relatedInformation,mo.is))}t.is=n})(fs||(fs={}));var Dn;(function(t){function e(r,i,...s){let a={title:r,command:i};return p.defined(s)&&s.length>0&&(a.arguments=s),a}t.create=e;function n(r){let i=r;return p.defined(i)&&p.string(i.title)&&p.string(i.command)}t.is=n})(Dn||(Dn={}));var Fn;(function(t){function e(s,a){return{range:s,newText:a}}t.replace=e;function n(s,a){return{range:{start:s,end:s},newText:a}}t.insert=n;function r(s){return{range:s,newText:""}}t.del=r;function i(s){const a=s;return p.objectLiteral(a)&&p.string(a.newText)&&L.is(a.range)}t.is=i})(Fn||(Fn={}));var go;(function(t){function e(r,i,s){const a={label:r};return i!==void 0&&(a.needsConfirmation=i),s!==void 0&&(a.description=s),a}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&p.string(i.label)&&(p.boolean(i.needsConfirmation)||i.needsConfirmation===void 0)&&(p.string(i.description)||i.description===void 0)}t.is=n})(go||(go={}));var Gn;(function(t){function e(n){const r=n;return p.string(r)}t.is=e})(Gn||(Gn={}));var _c;(function(t){function e(s,a,o){return{range:s,newText:a,annotationId:o}}t.replace=e;function n(s,a,o){return{range:{start:s,end:s},newText:a,annotationId:o}}t.insert=n;function r(s,a){return{range:s,newText:"",annotationId:a}}t.del=r;function i(s){const a=s;return Fn.is(a)&&(go.is(a.annotationId)||Gn.is(a.annotationId))}t.is=i})(_c||(_c={}));var yo;(function(t){function e(r,i){return{textDocument:r,edits:i}}t.create=e;function n(r){let i=r;return p.defined(i)&&Ao.is(i.textDocument)&&Array.isArray(i.edits)}t.is=n})(yo||(yo={}));var To;(function(t){function e(r,i,s){let a={kind:"create",uri:r};return i!==void 0&&(i.overwrite!==void 0||i.ignoreIfExists!==void 0)&&(a.options=i),s!==void 0&&(a.annotationId=s),a}t.create=e;function n(r){let i=r;return i&&i.kind==="create"&&p.string(i.uri)&&(i.options===void 0||(i.options.overwrite===void 0||p.boolean(i.options.overwrite))&&(i.options.ignoreIfExists===void 0||p.boolean(i.options.ignoreIfExists)))&&(i.annotationId===void 0||Gn.is(i.annotationId))}t.is=n})(To||(To={}));var vo;(function(t){function e(r,i,s,a){let o={kind:"rename",oldUri:r,newUri:i};return s!==void 0&&(s.overwrite!==void 0||s.ignoreIfExists!==void 0)&&(o.options=s),a!==void 0&&(o.annotationId=a),o}t.create=e;function n(r){let i=r;return i&&i.kind==="rename"&&p.string(i.oldUri)&&p.string(i.newUri)&&(i.options===void 0||(i.options.overwrite===void 0||p.boolean(i.options.overwrite))&&(i.options.ignoreIfExists===void 0||p.boolean(i.options.ignoreIfExists)))&&(i.annotationId===void 0||Gn.is(i.annotationId))}t.is=n})(vo||(vo={}));var $o;(function(t){function e(r,i,s){let a={kind:"delete",uri:r};return i!==void 0&&(i.recursive!==void 0||i.ignoreIfNotExists!==void 0)&&(a.options=i),s!==void 0&&(a.annotationId=s),a}t.create=e;function n(r){let i=r;return i&&i.kind==="delete"&&p.string(i.uri)&&(i.options===void 0||(i.options.recursive===void 0||p.boolean(i.options.recursive))&&(i.options.ignoreIfNotExists===void 0||p.boolean(i.options.ignoreIfNotExists)))&&(i.annotationId===void 0||Gn.is(i.annotationId))}t.is=n})($o||($o={}));var Ro;(function(t){function e(n){let r=n;return r&&(r.changes!==void 0||r.documentChanges!==void 0)&&(r.documentChanges===void 0||r.documentChanges.every(i=>p.string(i.kind)?To.is(i)||vo.is(i)||$o.is(i):yo.is(i)))}t.is=e})(Ro||(Ro={}));var Ic;(function(t){function e(r){return{uri:r}}t.create=e;function n(r){let i=r;return p.defined(i)&&p.string(i.uri)}t.is=n})(Ic||(Ic={}));var wc;(function(t){function e(r,i){return{uri:r,version:i}}t.create=e;function n(r){let i=r;return p.defined(i)&&p.string(i.uri)&&p.integer(i.version)}t.is=n})(wc||(wc={}));var Ao;(function(t){function e(r,i){return{uri:r,version:i}}t.create=e;function n(r){let i=r;return p.defined(i)&&p.string(i.uri)&&(i.version===null||p.integer(i.version))}t.is=n})(Ao||(Ao={}));var Cc;(function(t){function e(r,i,s,a){return{uri:r,languageId:i,version:s,text:a}}t.create=e;function n(r){let i=r;return p.defined(i)&&p.string(i.uri)&&p.string(i.languageId)&&p.integer(i.version)&&p.string(i.text)}t.is=n})(Cc||(Cc={}));var Eo;(function(t){t.PlainText="plaintext",t.Markdown="markdown";function e(n){const r=n;return r===t.PlainText||r===t.Markdown}t.is=e})(Eo||(Eo={}));var jr;(function(t){function e(n){const r=n;return p.objectLiteral(n)&&Eo.is(r.kind)&&p.string(r.value)}t.is=e})(jr||(jr={}));var kc;(function(t){t.Text=1,t.Method=2,t.Function=3,t.Constructor=4,t.Field=5,t.Variable=6,t.Class=7,t.Interface=8,t.Module=9,t.Property=10,t.Unit=11,t.Value=12,t.Enum=13,t.Keyword=14,t.Snippet=15,t.Color=16,t.File=17,t.Reference=18,t.Folder=19,t.EnumMember=20,t.Constant=21,t.Struct=22,t.Event=23,t.Operator=24,t.TypeParameter=25})(kc||(kc={}));var Nc;(function(t){t.PlainText=1,t.Snippet=2})(Nc||(Nc={}));var bc;(function(t){t.Deprecated=1})(bc||(bc={}));var Oc;(function(t){function e(r,i,s){return{newText:r,insert:i,replace:s}}t.create=e;function n(r){const i=r;return i&&p.string(i.newText)&&L.is(i.insert)&&L.is(i.replace)}t.is=n})(Oc||(Oc={}));var Pc;(function(t){t.asIs=1,t.adjustIndentation=2})(Pc||(Pc={}));var Lc;(function(t){function e(n){const r=n;return r&&(p.string(r.detail)||r.detail===void 0)&&(p.string(r.description)||r.description===void 0)}t.is=e})(Lc||(Lc={}));var Mc;(function(t){function e(n){return{label:n}}t.create=e})(Mc||(Mc={}));var Dc;(function(t){function e(n,r){return{items:n||[],isIncomplete:!!r}}t.create=e})(Dc||(Dc={}));var ds;(function(t){function e(r){return r.replace(/[\\`*_{}[\]()#+\-.!]/g,"\\$&")}t.fromPlainText=e;function n(r){const i=r;return p.string(i)||p.objectLiteral(i)&&p.string(i.language)&&p.string(i.value)}t.is=n})(ds||(ds={}));var Fc;(function(t){function e(n){let r=n;return!!r&&p.objectLiteral(r)&&(jr.is(r.contents)||ds.is(r.contents)||p.typedArray(r.contents,ds.is))&&(n.range===void 0||L.is(n.range))}t.is=e})(Fc||(Fc={}));var Gc;(function(t){function e(n,r){return r?{label:n,documentation:r}:{label:n}}t.create=e})(Gc||(Gc={}));var Uc;(function(t){function e(n,r,...i){let s={label:n};return p.defined(r)&&(s.documentation=r),p.defined(i)?s.parameters=i:s.parameters=[],s}t.create=e})(Uc||(Uc={}));var Bc;(function(t){t.Text=1,t.Read=2,t.Write=3})(Bc||(Bc={}));var jc;(function(t){function e(n,r){let i={range:n};return p.number(r)&&(i.kind=r),i}t.create=e})(jc||(jc={}));var Kc;(function(t){t.File=1,t.Module=2,t.Namespace=3,t.Package=4,t.Class=5,t.Method=6,t.Property=7,t.Field=8,t.Constructor=9,t.Enum=10,t.Interface=11,t.Function=12,t.Variable=13,t.Constant=14,t.String=15,t.Number=16,t.Boolean=17,t.Array=18,t.Object=19,t.Key=20,t.Null=21,t.EnumMember=22,t.Struct=23,t.Event=24,t.Operator=25,t.TypeParameter=26})(Kc||(Kc={}));var Hc;(function(t){t.Deprecated=1})(Hc||(Hc={}));var Wc;(function(t){function e(n,r,i,s,a){let o={name:n,kind:r,location:{uri:s,range:i}};return a&&(o.containerName=a),o}t.create=e})(Wc||(Wc={}));var zc;(function(t){function e(n,r,i,s){return s!==void 0?{name:n,kind:r,location:{uri:i,range:s}}:{name:n,kind:r,location:{uri:i}}}t.create=e})(zc||(zc={}));var Vc;(function(t){function e(r,i,s,a,o,l){let u={name:r,detail:i,kind:s,range:a,selectionRange:o};return l!==void 0&&(u.children=l),u}t.create=e;function n(r){let i=r;return i&&p.string(i.name)&&p.number(i.kind)&&L.is(i.range)&&L.is(i.selectionRange)&&(i.detail===void 0||p.string(i.detail))&&(i.deprecated===void 0||p.boolean(i.deprecated))&&(i.children===void 0||Array.isArray(i.children))&&(i.tags===void 0||Array.isArray(i.tags))}t.is=n})(Vc||(Vc={}));var qc;(function(t){t.Empty="",t.QuickFix="quickfix",t.Refactor="refactor",t.RefactorExtract="refactor.extract",t.RefactorInline="refactor.inline",t.RefactorRewrite="refactor.rewrite",t.Source="source",t.SourceOrganizeImports="source.organizeImports",t.SourceFixAll="source.fixAll"})(qc||(qc={}));var hs;(function(t){t.Invoked=1,t.Automatic=2})(hs||(hs={}));var Yc;(function(t){function e(r,i,s){let a={diagnostics:r};return i!=null&&(a.only=i),s!=null&&(a.triggerKind=s),a}t.create=e;function n(r){let i=r;return p.defined(i)&&p.typedArray(i.diagnostics,fs.is)&&(i.only===void 0||p.typedArray(i.only,p.string))&&(i.triggerKind===void 0||i.triggerKind===hs.Invoked||i.triggerKind===hs.Automatic)}t.is=n})(Yc||(Yc={}));var Xc;(function(t){function e(r,i,s){let a={title:r},o=!0;return typeof i=="string"?(o=!1,a.kind=i):Dn.is(i)?a.command=i:a.edit=i,o&&s!==void 0&&(a.kind=s),a}t.create=e;function n(r){let i=r;return i&&p.string(i.title)&&(i.diagnostics===void 0||p.typedArray(i.diagnostics,fs.is))&&(i.kind===void 0||p.string(i.kind))&&(i.edit!==void 0||i.command!==void 0)&&(i.command===void 0||Dn.is(i.command))&&(i.isPreferred===void 0||p.boolean(i.isPreferred))&&(i.edit===void 0||Ro.is(i.edit))}t.is=n})(Xc||(Xc={}));var Jc;(function(t){function e(r,i){let s={range:r};return p.defined(i)&&(s.data=i),s}t.create=e;function n(r){let i=r;return p.defined(i)&&L.is(i.range)&&(p.undefined(i.command)||Dn.is(i.command))}t.is=n})(Jc||(Jc={}));var Zc;(function(t){function e(r,i){return{tabSize:r,insertSpaces:i}}t.create=e;function n(r){let i=r;return p.defined(i)&&p.uinteger(i.tabSize)&&p.boolean(i.insertSpaces)}t.is=n})(Zc||(Zc={}));var Qc;(function(t){function e(r,i,s){return{range:r,target:i,data:s}}t.create=e;function n(r){let i=r;return p.defined(i)&&L.is(i.range)&&(p.undefined(i.target)||p.string(i.target))}t.is=n})(Qc||(Qc={}));var ef;(function(t){function e(r,i){return{range:r,parent:i}}t.create=e;function n(r){let i=r;return p.objectLiteral(i)&&L.is(i.range)&&(i.parent===void 0||t.is(i.parent))}t.is=n})(ef||(ef={}));var tf;(function(t){t.namespace="namespace",t.type="type",t.class="class",t.enum="enum",t.interface="interface",t.struct="struct",t.typeParameter="typeParameter",t.parameter="parameter",t.variable="variable",t.property="property",t.enumMember="enumMember",t.event="event",t.function="function",t.method="method",t.macro="macro",t.keyword="keyword",t.modifier="modifier",t.comment="comment",t.string="string",t.number="number",t.regexp="regexp",t.operator="operator",t.decorator="decorator"})(tf||(tf={}));var nf;(function(t){t.declaration="declaration",t.definition="definition",t.readonly="readonly",t.static="static",t.deprecated="deprecated",t.abstract="abstract",t.async="async",t.modification="modification",t.documentation="documentation",t.defaultLibrary="defaultLibrary"})(nf||(nf={}));var rf;(function(t){function e(n){const r=n;return p.objectLiteral(r)&&(r.resultId===void 0||typeof r.resultId=="string")&&Array.isArray(r.data)&&(r.data.length===0||typeof r.data[0]=="number")}t.is=e})(rf||(rf={}));var sf;(function(t){function e(r,i){return{range:r,text:i}}t.create=e;function n(r){const i=r;return i!=null&&L.is(i.range)&&p.string(i.text)}t.is=n})(sf||(sf={}));var af;(function(t){function e(r,i,s){return{range:r,variableName:i,caseSensitiveLookup:s}}t.create=e;function n(r){const i=r;return i!=null&&L.is(i.range)&&p.boolean(i.caseSensitiveLookup)&&(p.string(i.variableName)||i.variableName===void 0)}t.is=n})(af||(af={}));var of;(function(t){function e(r,i){return{range:r,expression:i}}t.create=e;function n(r){const i=r;return i!=null&&L.is(i.range)&&(p.string(i.expression)||i.expression===void 0)}t.is=n})(of||(of={}));var lf;(function(t){function e(r,i){return{frameId:r,stoppedLocation:i}}t.create=e;function n(r){const i=r;return p.defined(i)&&L.is(r.stoppedLocation)}t.is=n})(lf||(lf={}));var So;(function(t){t.Type=1,t.Parameter=2;function e(n){return n===1||n===2}t.is=e})(So||(So={}));var xo;(function(t){function e(r){return{value:r}}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&(i.tooltip===void 0||p.string(i.tooltip)||jr.is(i.tooltip))&&(i.location===void 0||cs.is(i.location))&&(i.command===void 0||Dn.is(i.command))}t.is=n})(xo||(xo={}));var uf;(function(t){function e(r,i,s){const a={position:r,label:i};return s!==void 0&&(a.kind=s),a}t.create=e;function n(r){const i=r;return p.objectLiteral(i)&&D.is(i.position)&&(p.string(i.label)||p.typedArray(i.label,xo.is))&&(i.kind===void 0||So.is(i.kind))&&i.textEdits===void 0||p.typedArray(i.textEdits,Fn.is)&&(i.tooltip===void 0||p.string(i.tooltip)||jr.is(i.tooltip))&&(i.paddingLeft===void 0||p.boolean(i.paddingLeft))&&(i.paddingRight===void 0||p.boolean(i.paddingRight))}t.is=n})(uf||(uf={}));var cf;(function(t){function e(n){return{kind:"snippet",value:n}}t.createSnippet=e})(cf||(cf={}));var ff;(function(t){function e(n,r,i,s){return{insertText:n,filterText:r,range:i,command:s}}t.create=e})(ff||(ff={}));var df;(function(t){function e(n){return{items:n}}t.create=e})(df||(df={}));var hf;(function(t){t.Invoked=0,t.Automatic=1})(hf||(hf={}));var pf;(function(t){function e(n,r){return{range:n,text:r}}t.create=e})(pf||(pf={}));var mf;(function(t){function e(n,r){return{triggerKind:n,selectedCompletionInfo:r}}t.create=e})(mf||(mf={}));var gf;(function(t){function e(n){const r=n;return p.objectLiteral(r)&&ho.is(r.uri)&&p.string(r.name)}t.is=e})(gf||(gf={}));var yf;(function(t){function e(s,a,o,l){return new _w(s,a,o,l)}t.create=e;function n(s){let a=s;return!!(p.defined(a)&&p.string(a.uri)&&(p.undefined(a.languageId)||p.string(a.languageId))&&p.uinteger(a.lineCount)&&p.func(a.getText)&&p.func(a.positionAt)&&p.func(a.offsetAt))}t.is=n;function r(s,a){let o=s.getText(),l=i(a,(c,f)=>{let d=c.range.start.line-f.range.start.line;return d===0?c.range.start.character-f.range.start.character:d}),u=o.length;for(let c=l.length-1;c>=0;c--){let f=l[c],d=s.offsetAt(f.range.start),h=s.offsetAt(f.range.end);if(h<=u)o=o.substring(0,d)+f.newText+o.substring(h,o.length);else throw new Error("Overlapping edit");u=d}return o}t.applyEdits=r;function i(s,a){if(s.length<=1)return s;const o=s.length/2|0,l=s.slice(0,o),u=s.slice(o);i(l,a),i(u,a);let c=0,f=0,d=0;for(;c0&&e.push(n.length),this._lineOffsets=e}return this._lineOffsets}positionAt(e){e=Math.max(Math.min(e,this._content.length),0);let n=this.getLineOffsets(),r=0,i=n.length;if(i===0)return D.create(0,e);for(;re?i=a:r=a+1}let s=r-1;return D.create(s,e-n[s])}offsetAt(e){let n=this.getLineOffsets();if(e.line>=n.length)return this._content.length;if(e.line<0)return 0;let r=n[e.line],i=e.line+1"u"}t.undefined=r;function i(h){return h===!0||h===!1}t.boolean=i;function s(h){return e.call(h)==="[object String]"}t.string=s;function a(h){return e.call(h)==="[object Number]"}t.number=a;function o(h,m,g){return e.call(h)==="[object Number]"&&m<=h&&h<=g}t.numberRange=o;function l(h){return e.call(h)==="[object Number]"&&-2147483648<=h&&h<=2147483647}t.integer=l;function u(h){return e.call(h)==="[object Number]"&&0<=h&&h<=2147483647}t.uinteger=u;function c(h){return e.call(h)==="[object Function]"}t.func=c;function f(h){return h!==null&&typeof h=="object"}t.objectLiteral=f;function d(h,m){return Array.isArray(h)&&h.every(m)}t.typedArray=d})(p||(p={}));class Iw{constructor(){this.nodeStack=[]}get current(){var e;return(e=this.nodeStack[this.nodeStack.length-1])!==null&&e!==void 0?e:this.rootNode}buildRootNode(e){return this.rootNode=new qh(e),this.rootNode.root=this.rootNode,this.nodeStack=[this.rootNode],this.rootNode}buildCompositeNode(e){const n=new _l;return n.grammarSource=e,n.root=this.rootNode,this.current.content.push(n),this.nodeStack.push(n),n}buildLeafNode(e,n){const r=new _o(e.startOffset,e.image.length,ja(e),e.tokenType,!n);return r.grammarSource=n,r.root=this.rootNode,this.current.content.push(r),r}removeNode(e){const n=e.container;if(n){const r=n.content.indexOf(e);r>=0&&n.content.splice(r,1)}}addHiddenNodes(e){const n=[];for(const s of e){const a=new _o(s.startOffset,s.image.length,ja(s),s.tokenType,!0);a.root=this.rootNode,n.push(a)}let r=this.current,i=!1;if(r.content.length>0){r.content.push(...n);return}for(;r.container;){const s=r.container.content.indexOf(r);if(s>0){r.container.content.splice(s,0,...n),i=!0;break}r=r.container}i||this.rootNode.content.unshift(...n)}construct(e){const n=this.current;typeof e.$type=="string"&&(this.current.astNode=e),e.$cstNode=n;const r=this.nodeStack.pop();(r==null?void 0:r.content.length)===0&&this.removeNode(r)}}class Vh{get parent(){return this.container}get feature(){return this.grammarSource}get hidden(){return!1}get astNode(){var e,n;const r=typeof((e=this._astNode)===null||e===void 0?void 0:e.$type)=="string"?this._astNode:(n=this.container)===null||n===void 0?void 0:n.astNode;if(!r)throw new Error("This node has no associated AST element");return r}set astNode(e){this._astNode=e}get element(){return this.astNode}get text(){return this.root.fullText.substring(this.offset,this.end)}}class _o extends Vh{get offset(){return this._offset}get length(){return this._length}get end(){return this._offset+this._length}get hidden(){return this._hidden}get tokenType(){return this._tokenType}get range(){return this._range}constructor(e,n,r,i,s=!1){super(),this._hidden=s,this._offset=e,this._tokenType=i,this._length=n,this._range=r}}class _l extends Vh{constructor(){super(...arguments),this.content=new Il(this)}get children(){return this.content}get offset(){var e,n;return(n=(e=this.firstNonHiddenNode)===null||e===void 0?void 0:e.offset)!==null&&n!==void 0?n:0}get length(){return this.end-this.offset}get end(){var e,n;return(n=(e=this.lastNonHiddenNode)===null||e===void 0?void 0:e.end)!==null&&n!==void 0?n:0}get range(){const e=this.firstNonHiddenNode,n=this.lastNonHiddenNode;if(e&&n){if(this._rangeCache===void 0){const{range:r}=e,{range:i}=n;this._rangeCache={start:r.start,end:i.end.line=0;e--){const n=this.content[e];if(!n.hidden)return n}return this.content[this.content.length-1]}}class Il extends Array{constructor(e){super(),this.parent=e,Object.setPrototypeOf(this,Il.prototype)}push(...e){return this.addParents(e),super.push(...e)}unshift(...e){return this.addParents(e),super.unshift(...e)}splice(e,n,...r){return this.addParents(r),super.splice(e,n,...r)}addParents(e){for(const n of e)n.container=this.parent}}class qh extends _l{get text(){return this._text.substring(this.offset,this.end)}get fullText(){return this._text}constructor(e){super(),this._text="",this._text=e??""}}const Io=Symbol("Datatype");function xa(t){return t.$type===Io}const Tf="​",Yh=t=>t.endsWith(Tf)?t:t+Tf;class Xh{constructor(e){this._unorderedGroups=new Map,this.allRules=new Map,this.lexer=e.parser.Lexer;const n=this.lexer.definition,r=e.LanguageMetaData.mode==="production";this.wrapper=new bw(n,Object.assign(Object.assign({},e.parser.ParserConfig),{skipValidations:r,errorMessageProvider:e.parser.ParserErrorMessageProvider}))}alternatives(e,n){this.wrapper.wrapOr(e,n)}optional(e,n){this.wrapper.wrapOption(e,n)}many(e,n){this.wrapper.wrapMany(e,n)}atLeastOne(e,n){this.wrapper.wrapAtLeastOne(e,n)}getRule(e){return this.allRules.get(e)}isRecording(){return this.wrapper.IS_RECORDING}get unorderedGroups(){return this._unorderedGroups}getRuleStack(){return this.wrapper.RULE_STACK}finalize(){this.wrapper.wrapSelfAnalysis()}}class ww extends Xh{get current(){return this.stack[this.stack.length-1]}constructor(e){super(e),this.nodeBuilder=new Iw,this.stack=[],this.assignmentMap=new Map,this.linker=e.references.Linker,this.converter=e.parser.ValueConverter,this.astReflection=e.shared.AstReflection}rule(e,n){const r=this.computeRuleType(e),i=this.wrapper.DEFINE_RULE(Yh(e.name),this.startImplementation(r,n).bind(this));return this.allRules.set(e.name,i),e.entry&&(this.mainRule=i),i}computeRuleType(e){if(!e.fragment){if(id(e))return Io;{const n=Bo(e);return n??e.name}}}parse(e,n={}){this.nodeBuilder.buildRootNode(e);const r=this.lexerResult=this.lexer.tokenize(e);this.wrapper.input=r.tokens;const i=n.rule?this.allRules.get(n.rule):this.mainRule;if(!i)throw new Error(n.rule?`No rule found with name '${n.rule}'`:"No main rule available.");const s=i.call(this.wrapper,{});return this.nodeBuilder.addHiddenNodes(r.hidden),this.unorderedGroups.clear(),this.lexerResult=void 0,{value:s,lexerErrors:r.errors,lexerReport:r.report,parserErrors:this.wrapper.errors}}startImplementation(e,n){return r=>{const i=!this.isRecording()&&e!==void 0;if(i){const a={$type:e};this.stack.push(a),e===Io&&(a.value="")}let s;try{s=n(r)}catch{s=void 0}return s===void 0&&i&&(s=this.construct()),s}}extractHiddenTokens(e){const n=this.lexerResult.hidden;if(!n.length)return[];const r=e.startOffset;for(let i=0;ir)return n.splice(0,i);return n.splice(0,n.length)}consume(e,n,r){const i=this.wrapper.wrapConsume(e,n);if(!this.isRecording()&&this.isValidToken(i)){const s=this.extractHiddenTokens(i);this.nodeBuilder.addHiddenNodes(s);const a=this.nodeBuilder.buildLeafNode(i,r),{assignment:o,isCrossRef:l}=this.getAssignment(r),u=this.current;if(o){const c=Ht(r)?i.image:this.converter.convert(i.image,a);this.assign(o.operator,o.feature,c,a,l)}else if(xa(u)){let c=i.image;Ht(r)||(c=this.converter.convert(c,a).toString()),u.value+=c}}}isValidToken(e){return!e.isInsertedInRecovery&&!isNaN(e.startOffset)&&typeof e.endOffset=="number"&&!isNaN(e.endOffset)}subrule(e,n,r,i,s){let a;!this.isRecording()&&!r&&(a=this.nodeBuilder.buildCompositeNode(i));const o=this.wrapper.wrapSubrule(e,n,s);!this.isRecording()&&a&&a.length>0&&this.performSubruleAssignment(o,i,a)}performSubruleAssignment(e,n,r){const{assignment:i,isCrossRef:s}=this.getAssignment(n);if(i)this.assign(i.operator,i.feature,e,r,s);else if(!i){const a=this.current;if(xa(a))a.value+=e.toString();else if(typeof e=="object"&&e){const l=this.assignWithoutOverride(e,a);this.stack.pop(),this.stack.push(l)}}}action(e,n){if(!this.isRecording()){let r=this.current;if(n.feature&&n.operator){r=this.construct(),this.nodeBuilder.removeNode(r.$cstNode),this.nodeBuilder.buildCompositeNode(n).content.push(r.$cstNode);const s={$type:e};this.stack.push(s),this.assign(n.operator,n.feature,r,r.$cstNode,!1)}else r.$type=e}}construct(){if(this.isRecording())return;const e=this.current;return Tm(e),this.nodeBuilder.construct(e),this.stack.pop(),xa(e)?this.converter.convert(e.value,e.$cstNode):(vm(this.astReflection,e),e)}getAssignment(e){if(!this.assignmentMap.has(e)){const n=Rs(e,Kt);this.assignmentMap.set(e,{assignment:n,isCrossRef:n?Do(n.terminal):!1})}return this.assignmentMap.get(e)}assign(e,n,r,i,s){const a=this.current;let o;switch(s&&typeof r=="string"?o=this.linker.buildReference(a,n,i,r):o=r,e){case"=":{a[n]=o;break}case"?=":{a[n]=!0;break}case"+=":Array.isArray(a[n])||(a[n]=[]),a[n].push(o)}}assignWithoutOverride(e,n){for(const[i,s]of Object.entries(n)){const a=e[i];a===void 0?e[i]=s:Array.isArray(a)&&Array.isArray(s)&&(s.push(...a),e[i]=s)}const r=e.$cstNode;return r&&(r.astNode=void 0,e.$cstNode=void 0),e}get definitionErrors(){return this.wrapper.definitionErrors}}class Cw{buildMismatchTokenMessage(e){return fn.buildMismatchTokenMessage(e)}buildNotAllInputParsedMessage(e){return fn.buildNotAllInputParsedMessage(e)}buildNoViableAltMessage(e){return fn.buildNoViableAltMessage(e)}buildEarlyExitMessage(e){return fn.buildEarlyExitMessage(e)}}class Jh extends Cw{buildMismatchTokenMessage({expected:e,actual:n}){return`Expecting ${e.LABEL?"`"+e.LABEL+"`":e.name.endsWith(":KW")?`keyword '${e.name.substring(0,e.name.length-3)}'`:`token of type '${e.name}'`} but found \`${n.image}\`.`}buildNotAllInputParsedMessage({firstRedundant:e}){return`Expecting end of file but found \`${e.image}\`.`}}class kw extends Xh{constructor(){super(...arguments),this.tokens=[],this.elementStack=[],this.lastElementStack=[],this.nextTokenIndex=0,this.stackSize=0}action(){}construct(){}parse(e){this.resetState();const n=this.lexer.tokenize(e,{mode:"partial"});return this.tokens=n.tokens,this.wrapper.input=[...this.tokens],this.mainRule.call(this.wrapper,{}),this.unorderedGroups.clear(),{tokens:this.tokens,elementStack:[...this.lastElementStack],tokenIndex:this.nextTokenIndex}}rule(e,n){const r=this.wrapper.DEFINE_RULE(Yh(e.name),this.startImplementation(n).bind(this));return this.allRules.set(e.name,r),e.entry&&(this.mainRule=r),r}resetState(){this.elementStack=[],this.lastElementStack=[],this.nextTokenIndex=0,this.stackSize=0}startImplementation(e){return n=>{const r=this.keepStackSize();try{e(n)}finally{this.resetStackSize(r)}}}removeUnexpectedElements(){this.elementStack.splice(this.stackSize)}keepStackSize(){const e=this.elementStack.length;return this.stackSize=e,e}resetStackSize(e){this.removeUnexpectedElements(),this.stackSize=e}consume(e,n,r){this.wrapper.wrapConsume(e,n),this.isRecording()||(this.lastElementStack=[...this.elementStack,r],this.nextTokenIndex=this.currIdx+1)}subrule(e,n,r,i,s){this.before(i),this.wrapper.wrapSubrule(e,n,s),this.after(i)}before(e){this.isRecording()||this.elementStack.push(e)}after(e){if(!this.isRecording()){const n=this.elementStack.lastIndexOf(e);n>=0&&this.elementStack.splice(n)}}get currIdx(){return this.wrapper.currIdx}}const Nw={recoveryEnabled:!0,nodeLocationTracking:"full",skipValidations:!0,errorMessageProvider:new Jh};class bw extends GI{constructor(e,n){const r=n&&"maxLookahead"in n;super(e,Object.assign(Object.assign(Object.assign({},Nw),{lookaheadStrategy:r?new $l({maxLookahead:n.maxLookahead}):new aw({logging:n.skipValidations?()=>{}:void 0})}),n))}get IS_RECORDING(){return this.RECORDING_PHASE}DEFINE_RULE(e,n){return this.RULE(e,n)}wrapSelfAnalysis(){this.performSelfAnalysis()}wrapConsume(e,n){return this.consume(e,n)}wrapSubrule(e,n,r){return this.subrule(e,n,{ARGS:[r]})}wrapOr(e,n){this.or(e,n)}wrapOption(e,n){this.option(e,n)}wrapMany(e,n){this.many(e,n)}wrapAtLeastOne(e,n){this.atLeastOne(e,n)}}function Zh(t,e,n){return Ow({parser:e,tokens:n,ruleNames:new Map},t),e}function Ow(t,e){const n=Qf(e,!1),r=re(e.rules).filter(Ne).filter(i=>n.has(i));for(const i of r){const s=Object.assign(Object.assign({},t),{consume:1,optional:1,subrule:1,many:1,or:1});t.parser.rule(i,Yt(s,i.definition))}}function Yt(t,e,n=!1){let r;if(Ht(e))r=Uw(t,e);else if($s(e))r=Pw(t,e);else if(Kt(e))r=Yt(t,e.terminal);else if(Do(e))r=Qh(t,e);else if(Wt(e))r=Lw(t,e);else if(Vf(e))r=Dw(t,e);else if(qf(e))r=Fw(t,e);else if(Fo(e))r=Gw(t,e);else if(cm(e)){const i=t.consume++;r=()=>t.parser.consume(i,wt,e)}else throw new Kf(e.$cstNode,`Unexpected element type: ${e.$type}`);return ep(t,n?void 0:ps(e),r,e.cardinality)}function Pw(t,e){const n=jo(e);return()=>t.parser.action(n,e)}function Lw(t,e){const n=e.rule.ref;if(Ne(n)){const r=t.subrule++,i=n.fragment,s=e.arguments.length>0?Mw(n,e.arguments):()=>({});return a=>t.parser.subrule(r,tp(t,n),i,e,s(a))}else if(Jt(n)){const r=t.consume++,i=wo(t,n.name);return()=>t.parser.consume(r,i,e)}else if(n)Wr();else throw new Kf(e.$cstNode,`Undefined rule: ${e.rule.$refText}`)}function Mw(t,e){const n=e.map(r=>at(r.value));return r=>{const i={};for(let s=0;se(r)||n(r)}else if(rm(t)){const e=at(t.left),n=at(t.right);return r=>e(r)&&n(r)}else if(sm(t)){const e=at(t.value);return n=>!e(n)}else if(am(t)){const e=t.parameter.ref.name;return n=>n!==void 0&&n[e]===!0}else if(nm(t)){const e=!!t.true;return()=>e}Wr()}function Dw(t,e){if(e.elements.length===1)return Yt(t,e.elements[0]);{const n=[];for(const i of e.elements){const s={ALT:Yt(t,i,!0)},a=ps(i);a&&(s.GATE=at(a)),n.push(s)}const r=t.or++;return i=>t.parser.alternatives(r,n.map(s=>{const a={ALT:()=>s.ALT(i)},o=s.GATE;return o&&(a.GATE=()=>o(i)),a}))}}function Fw(t,e){if(e.elements.length===1)return Yt(t,e.elements[0]);const n=[];for(const o of e.elements){const l={ALT:Yt(t,o,!0)},u=ps(o);u&&(l.GATE=at(u)),n.push(l)}const r=t.or++,i=(o,l)=>{const u=l.getRuleStack().join("-");return`uGroup_${o}_${u}`},s=o=>t.parser.alternatives(r,n.map((l,u)=>{const c={ALT:()=>!0},f=t.parser;c.ALT=()=>{if(l.ALT(o),!f.isRecording()){const h=i(r,f);f.unorderedGroups.get(h)||f.unorderedGroups.set(h,[]);const m=f.unorderedGroups.get(h);typeof(m==null?void 0:m[u])>"u"&&(m[u]=!0)}};const d=l.GATE;return d?c.GATE=()=>d(o):c.GATE=()=>{const h=f.unorderedGroups.get(i(r,f));return!(h!=null&&h[u])},c})),a=ep(t,ps(e),s,"*");return o=>{a(o),t.parser.isRecording()||t.parser.unorderedGroups.delete(i(r,t.parser))}}function Gw(t,e){const n=e.elements.map(r=>Yt(t,r));return r=>n.forEach(i=>i(r))}function ps(t){if(Fo(t))return t.guardCondition}function Qh(t,e,n=e.terminal){if(n)if(Wt(n)&&Ne(n.rule.ref)){const r=n.rule.ref,i=t.subrule++;return s=>t.parser.subrule(i,tp(t,r),!1,e,s)}else if(Wt(n)&&Jt(n.rule.ref)){const r=t.consume++,i=wo(t,n.rule.ref.name);return()=>t.parser.consume(r,i,e)}else if(Ht(n)){const r=t.consume++,i=wo(t,n.value);return()=>t.parser.consume(r,i,e)}else throw new Error("Could not build cross reference parser");else{if(!e.type.ref)throw new Error("Could not resolve reference to type: "+e.type.$refText);const r=nd(e.type.ref),i=r==null?void 0:r.terminal;if(!i)throw new Error("Could not find name assignment for type: "+jo(e.type.ref));return Qh(t,e,i)}}function Uw(t,e){const n=t.consume++,r=t.tokens[e.value];if(!r)throw new Error("Could not find token for keyword: "+e.value);return()=>t.parser.consume(n,r,e)}function ep(t,e,n,r){const i=e&&at(e);if(!r)if(i){const s=t.or++;return a=>t.parser.alternatives(s,[{ALT:()=>n(a),GATE:()=>i(a)},{ALT:dc(),GATE:()=>!i(a)}])}else return n;if(r==="*"){const s=t.many++;return a=>t.parser.many(s,{DEF:()=>n(a),GATE:i?()=>i(a):void 0})}else if(r==="+"){const s=t.many++;if(i){const a=t.or++;return o=>t.parser.alternatives(a,[{ALT:()=>t.parser.atLeastOne(s,{DEF:()=>n(o)}),GATE:()=>i(o)},{ALT:dc(),GATE:()=>!i(o)}])}else return a=>t.parser.atLeastOne(s,{DEF:()=>n(a)})}else if(r==="?"){const s=t.optional++;return a=>t.parser.optional(s,{DEF:()=>n(a),GATE:i?()=>i(a):void 0})}else Wr()}function tp(t,e){const n=Bw(t,e),r=t.parser.getRule(n);if(!r)throw new Error(`Rule "${n}" not found."`);return r}function Bw(t,e){if(Ne(e))return e.name;if(t.ruleNames.has(e))return t.ruleNames.get(e);{let n=e,r=n.$container,i=e.$type;for(;!Ne(r);)(Fo(r)||Vf(r)||qf(r))&&(i=r.elements.indexOf(n).toString()+":"+i),n=r,r=r.$container;return i=r.name+":"+i,t.ruleNames.set(e,i),i}}function wo(t,e){const n=t.tokens[e];if(!n)throw new Error(`Token "${e}" not found."`);return n}function jw(t){const e=t.Grammar,n=t.parser.Lexer,r=new kw(t);return Zh(e,r,n.definition),r.finalize(),r}function Kw(t){const e=Hw(t);return e.finalize(),e}function Hw(t){const e=t.Grammar,n=t.parser.Lexer,r=new ww(t);return Zh(e,r,n.definition)}class np{constructor(){this.diagnostics=[]}buildTokens(e,n){const r=re(Qf(e,!1)),i=this.buildTerminalTokens(r),s=this.buildKeywordTokens(r,i,n);return i.forEach(a=>{const o=a.PATTERN;typeof o=="object"&&o&&"test"in o&&Ha(o)?s.unshift(a):s.push(a)}),s}flushLexingReport(e){return{diagnostics:this.popDiagnostics()}}popDiagnostics(){const e=[...this.diagnostics];return this.diagnostics=[],e}buildTerminalTokens(e){return e.filter(Jt).filter(n=>!n.fragment).map(n=>this.buildTerminalToken(n)).toArray()}buildTerminalToken(e){const n=Ko(e),r=this.requiresCustomPattern(n)?this.regexPatternFunction(n):n,i={name:e.name,PATTERN:r};return typeof r=="function"&&(i.LINE_BREAKS=!0),e.hidden&&(i.GROUP=Ha(n)?he.SKIPPED:"hidden"),i}requiresCustomPattern(e){return e.flags.includes("u")||e.flags.includes("s")?!0:!!(e.source.includes("?<=")||e.source.includes("?(n.lastIndex=i,n.exec(r))}buildKeywordTokens(e,n,r){return e.filter(Ne).flatMap(i=>zr(i).filter(Ht)).distinct(i=>i.value).toArray().sort((i,s)=>s.value.length-i.value.length).map(i=>this.buildKeywordToken(i,n,!!(r!=null&&r.caseInsensitive)))}buildKeywordToken(e,n,r){const i=this.buildKeywordPattern(e,r),s={name:e.value,PATTERN:i,LONGER_ALT:this.findLongerAlt(e,n)};return typeof i=="function"&&(s.LINE_BREAKS=!0),s}buildKeywordPattern(e,n){return n?new RegExp(wm(e.value)):e.value}findLongerAlt(e,n){return n.reduce((r,i)=>{const s=i==null?void 0:i.PATTERN;return s!=null&&s.source&&Cm("^"+s.source+"$",e.value)&&r.push(i),r},[])}}class rp{convert(e,n){let r=n.grammarSource;if(Do(r)&&(r=Om(r)),Wt(r)){const i=r.rule.ref;if(!i)throw new Error("This cst node was not parsed by a rule.");return this.runConverter(i,e,n)}return e}runConverter(e,n,r){var i;switch(e.name.toUpperCase()){case"INT":return rt.convertInt(n);case"STRING":return rt.convertString(n);case"ID":return rt.convertID(n)}switch((i=Um(e))===null||i===void 0?void 0:i.toLowerCase()){case"number":return rt.convertNumber(n);case"boolean":return rt.convertBoolean(n);case"bigint":return rt.convertBigint(n);case"date":return rt.convertDate(n);default:return n}}}var rt;(function(t){function e(u){let c="";for(let f=1;fip(e))}le.stringArray=Yw;var Un={};Object.defineProperty(Un,"__esModule",{value:!0});var ap=Un.Emitter=Un.Event=void 0;const Xw=Qs;var vf;(function(t){const e={dispose(){}};t.None=function(){return e}})(vf||(Un.Event=vf={}));class Jw{add(e,n=null,r){this._callbacks||(this._callbacks=[],this._contexts=[]),this._callbacks.push(e),this._contexts.push(n),Array.isArray(r)&&r.push({dispose:()=>this.remove(e,n)})}remove(e,n=null){if(!this._callbacks)return;let r=!1;for(let i=0,s=this._callbacks.length;i{this._callbacks||(this._callbacks=new Jw),this._options&&this._options.onFirstListenerAdd&&this._callbacks.isEmpty()&&this._options.onFirstListenerAdd(this),this._callbacks.add(e,n);const i={dispose:()=>{this._callbacks&&(this._callbacks.remove(e,n),i.dispose=ea._noop,this._options&&this._options.onLastListenerRemove&&this._callbacks.isEmpty()&&this._options.onLastListenerRemove(this))}};return Array.isArray(r)&&r.push(i),i}),this._event}fire(e){this._callbacks&&this._callbacks.invoke.call(this._callbacks,e)}dispose(){this._callbacks&&(this._callbacks.dispose(),this._callbacks=void 0)}}ap=Un.Emitter=ea;ea._noop=function(){};var z;Object.defineProperty(Kr,"__esModule",{value:!0});var wl=Kr.CancellationTokenSource=z=Kr.CancellationToken=void 0;const Zw=Qs,Qw=le,No=Un;var ms;(function(t){t.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:No.Event.None}),t.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:No.Event.None});function e(n){const r=n;return r&&(r===t.None||r===t.Cancelled||Qw.boolean(r.isCancellationRequested)&&!!r.onCancellationRequested)}t.is=e})(ms||(z=Kr.CancellationToken=ms={}));const eC=Object.freeze(function(t,e){const n=(0,Zw.default)().timer.setTimeout(t.bind(e),0);return{dispose(){n.dispose()}}});class $f{constructor(){this._isCancelled=!1}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?eC:(this._emitter||(this._emitter=new No.Emitter),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=void 0)}}class tC{get token(){return this._token||(this._token=new $f),this._token}cancel(){this._token?this._token.cancel():this._token=ms.Cancelled}dispose(){this._token?this._token instanceof $f&&this._token.dispose():this._token=ms.None}}wl=Kr.CancellationTokenSource=tC;function nC(){return new Promise(t=>{typeof setImmediate>"u"?setTimeout(t,0):setImmediate(t)})}let Mi=0,rC=10;function iC(){return Mi=performance.now(),new wl}const gs=Symbol("OperationCancelled");function ta(t){return t===gs}async function Ee(t){if(t===z.None)return;const e=performance.now();if(e-Mi>=rC&&(Mi=e,await nC(),Mi=performance.now()),t.isCancellationRequested)throw gs}class Cl{constructor(){this.promise=new Promise((e,n)=>{this.resolve=r=>(e(r),this),this.reject=r=>(n(r),this)})}}class Hr{constructor(e,n,r,i){this._uri=e,this._languageId=n,this._version=r,this._content=i,this._lineOffsets=void 0}get uri(){return this._uri}get languageId(){return this._languageId}get version(){return this._version}getText(e){if(e){const n=this.offsetAt(e.start),r=this.offsetAt(e.end);return this._content.substring(n,r)}return this._content}update(e,n){for(const r of e)if(Hr.isIncremental(r)){const i=lp(r.range),s=this.offsetAt(i.start),a=this.offsetAt(i.end);this._content=this._content.substring(0,s)+r.text+this._content.substring(a,this._content.length);const o=Math.max(i.start.line,0),l=Math.max(i.end.line,0);let u=this._lineOffsets;const c=Rf(r.text,!1,s);if(l-o===c.length)for(let d=0,h=c.length;de?i=a:r=a+1}const s=r-1;return e=this.ensureBeforeEOL(e,n[s]),{line:s,character:e-n[s]}}offsetAt(e){const n=this.getLineOffsets();if(e.line>=n.length)return this._content.length;if(e.line<0)return 0;const r=n[e.line];if(e.character<=0)return r;const i=e.line+1n&&op(this._content.charCodeAt(e-1));)e--;return e}get lineCount(){return this.getLineOffsets().length}static isIncremental(e){const n=e;return n!=null&&typeof n.text=="string"&&n.range!==void 0&&(n.rangeLength===void 0||typeof n.rangeLength=="number")}static isFull(e){const n=e;return n!=null&&typeof n.text=="string"&&n.range===void 0&&n.rangeLength===void 0}}var bo;(function(t){function e(i,s,a,o){return new Hr(i,s,a,o)}t.create=e;function n(i,s,a){if(i instanceof Hr)return i.update(s,a),i;throw new Error("TextDocument.update: document must be created by TextDocument.create")}t.update=n;function r(i,s){const a=i.getText(),o=Oo(s.map(sC),(c,f)=>{const d=c.range.start.line-f.range.start.line;return d===0?c.range.start.character-f.range.start.character:d});let l=0;const u=[];for(const c of o){const f=i.offsetAt(c.range.start);if(fl&&u.push(a.substring(l,f)),c.newText.length&&u.push(c.newText),l=i.offsetAt(c.range.end)}return u.push(a.substr(l)),u.join("")}t.applyEdits=r})(bo||(bo={}));function Oo(t,e){if(t.length<=1)return t;const n=t.length/2|0,r=t.slice(0,n),i=t.slice(n);Oo(r,e),Oo(i,e);let s=0,a=0,o=0;for(;sn.line||e.line===n.line&&e.character>n.character?{start:n,end:e}:t}function sC(t){const e=lp(t.range);return e!==t.range?{newText:t.newText,range:e}:t}var up;(()=>{var t={470:i=>{function s(l){if(typeof l!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(l))}function a(l,u){for(var c,f="",d=0,h=-1,m=0,g=0;g<=l.length;++g){if(g2){var T=f.lastIndexOf("/");if(T!==f.length-1){T===-1?(f="",d=0):d=(f=f.slice(0,T)).length-1-f.lastIndexOf("/"),h=g,m=0;continue}}else if(f.length===2||f.length===1){f="",d=0,h=g,m=0;continue}}u&&(f.length>0?f+="/..":f="..",d=2)}else f.length>0?f+="/"+l.slice(h+1,g):f=l.slice(h+1,g),d=g-h-1;h=g,m=0}else c===46&&m!==-1?++m:m=-1}return f}var o={resolve:function(){for(var l,u="",c=!1,f=arguments.length-1;f>=-1&&!c;f--){var d;f>=0?d=arguments[f]:(l===void 0&&(l=process.cwd()),d=l),s(d),d.length!==0&&(u=d+"/"+u,c=d.charCodeAt(0)===47)}return u=a(u,!c),c?u.length>0?"/"+u:"/":u.length>0?u:"."},normalize:function(l){if(s(l),l.length===0)return".";var u=l.charCodeAt(0)===47,c=l.charCodeAt(l.length-1)===47;return(l=a(l,!u)).length!==0||u||(l="."),l.length>0&&c&&(l+="/"),u?"/"+l:l},isAbsolute:function(l){return s(l),l.length>0&&l.charCodeAt(0)===47},join:function(){if(arguments.length===0)return".";for(var l,u=0;u0&&(l===void 0?l=c:l+="/"+c)}return l===void 0?".":o.normalize(l)},relative:function(l,u){if(s(l),s(u),l===u||(l=o.resolve(l))===(u=o.resolve(u)))return"";for(var c=1;cg){if(u.charCodeAt(h+y)===47)return u.slice(h+y+1);if(y===0)return u.slice(h+y)}else d>g&&(l.charCodeAt(c+y)===47?T=y:y===0&&(T=0));break}var R=l.charCodeAt(c+y);if(R!==u.charCodeAt(h+y))break;R===47&&(T=y)}var v="";for(y=c+T+1;y<=f;++y)y!==f&&l.charCodeAt(y)!==47||(v.length===0?v+="..":v+="/..");return v.length>0?v+u.slice(h+T):(h+=T,u.charCodeAt(h)===47&&++h,u.slice(h))},_makeLong:function(l){return l},dirname:function(l){if(s(l),l.length===0)return".";for(var u=l.charCodeAt(0),c=u===47,f=-1,d=!0,h=l.length-1;h>=1;--h)if((u=l.charCodeAt(h))===47){if(!d){f=h;break}}else d=!1;return f===-1?c?"/":".":c&&f===1?"//":l.slice(0,f)},basename:function(l,u){if(u!==void 0&&typeof u!="string")throw new TypeError('"ext" argument must be a string');s(l);var c,f=0,d=-1,h=!0;if(u!==void 0&&u.length>0&&u.length<=l.length){if(u.length===l.length&&u===l)return"";var m=u.length-1,g=-1;for(c=l.length-1;c>=0;--c){var T=l.charCodeAt(c);if(T===47){if(!h){f=c+1;break}}else g===-1&&(h=!1,g=c+1),m>=0&&(T===u.charCodeAt(m)?--m==-1&&(d=c):(m=-1,d=g))}return f===d?d=g:d===-1&&(d=l.length),l.slice(f,d)}for(c=l.length-1;c>=0;--c)if(l.charCodeAt(c)===47){if(!h){f=c+1;break}}else d===-1&&(h=!1,d=c+1);return d===-1?"":l.slice(f,d)},extname:function(l){s(l);for(var u=-1,c=0,f=-1,d=!0,h=0,m=l.length-1;m>=0;--m){var g=l.charCodeAt(m);if(g!==47)f===-1&&(d=!1,f=m+1),g===46?u===-1?u=m:h!==1&&(h=1):u!==-1&&(h=-1);else if(!d){c=m+1;break}}return u===-1||f===-1||h===0||h===1&&u===f-1&&u===c+1?"":l.slice(u,f)},format:function(l){if(l===null||typeof l!="object")throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof l);return function(u,c){var f=c.dir||c.root,d=c.base||(c.name||"")+(c.ext||"");return f?f===c.root?f+d:f+"/"+d:d}(0,l)},parse:function(l){s(l);var u={root:"",dir:"",base:"",ext:"",name:""};if(l.length===0)return u;var c,f=l.charCodeAt(0),d=f===47;d?(u.root="/",c=1):c=0;for(var h=-1,m=0,g=-1,T=!0,y=l.length-1,R=0;y>=c;--y)if((f=l.charCodeAt(y))!==47)g===-1&&(T=!1,g=y+1),f===46?h===-1?h=y:R!==1&&(R=1):h!==-1&&(R=-1);else if(!T){m=y+1;break}return h===-1||g===-1||R===0||R===1&&h===g-1&&h===m+1?g!==-1&&(u.base=u.name=m===0&&d?l.slice(1,g):l.slice(m,g)):(m===0&&d?(u.name=l.slice(1,h),u.base=l.slice(1,g)):(u.name=l.slice(m,h),u.base=l.slice(m,g)),u.ext=l.slice(h,g)),m>0?u.dir=l.slice(0,m-1):d&&(u.dir="/"),u},sep:"/",delimiter:":",win32:null,posix:null};o.posix=o,i.exports=o}},e={};function n(i){var s=e[i];if(s!==void 0)return s.exports;var a=e[i]={exports:{}};return t[i](a,a.exports,n),a.exports}n.d=(i,s)=>{for(var a in s)n.o(s,a)&&!n.o(i,a)&&Object.defineProperty(i,a,{enumerable:!0,get:s[a]})},n.o=(i,s)=>Object.prototype.hasOwnProperty.call(i,s),n.r=i=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(i,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(i,"__esModule",{value:!0})};var r={};(()=>{let i;n.r(r),n.d(r,{URI:()=>d,Utils:()=>Ie}),typeof process=="object"?i=process.platform==="win32":typeof navigator=="object"&&(i=navigator.userAgent.indexOf("Windows")>=0);const s=/^\w[\w\d+.-]*$/,a=/^\//,o=/^\/\//;function l(S,$){if(!S.scheme&&$)throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${S.authority}", path: "${S.path}", query: "${S.query}", fragment: "${S.fragment}"}`);if(S.scheme&&!s.test(S.scheme))throw new Error("[UriError]: Scheme contains illegal characters.");if(S.path){if(S.authority){if(!a.test(S.path))throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character')}else if(o.test(S.path))throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")')}}const u="",c="/",f=/^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;class d{constructor($,E,_,P,b,N=!1){$t(this,"scheme");$t(this,"authority");$t(this,"path");$t(this,"query");$t(this,"fragment");typeof $=="object"?(this.scheme=$.scheme||u,this.authority=$.authority||u,this.path=$.path||u,this.query=$.query||u,this.fragment=$.fragment||u):(this.scheme=function($e,Q){return $e||Q?$e:"file"}($,N),this.authority=E||u,this.path=function($e,Q){switch($e){case"https":case"http":case"file":Q?Q[0]!==c&&(Q=c+Q):Q=c}return Q}(this.scheme,_||u),this.query=P||u,this.fragment=b||u,l(this,N))}static isUri($){return $ instanceof d||!!$&&typeof $.authority=="string"&&typeof $.fragment=="string"&&typeof $.path=="string"&&typeof $.query=="string"&&typeof $.scheme=="string"&&typeof $.fsPath=="string"&&typeof $.with=="function"&&typeof $.toString=="function"}get fsPath(){return R(this)}with($){if(!$)return this;let{scheme:E,authority:_,path:P,query:b,fragment:N}=$;return E===void 0?E=this.scheme:E===null&&(E=u),_===void 0?_=this.authority:_===null&&(_=u),P===void 0?P=this.path:P===null&&(P=u),b===void 0?b=this.query:b===null&&(b=u),N===void 0?N=this.fragment:N===null&&(N=u),E===this.scheme&&_===this.authority&&P===this.path&&b===this.query&&N===this.fragment?this:new m(E,_,P,b,N)}static parse($,E=!1){const _=f.exec($);return _?new m(_[2]||u,ae(_[4]||u),ae(_[5]||u),ae(_[7]||u),ae(_[9]||u),E):new m(u,u,u,u,u)}static file($){let E=u;if(i&&($=$.replace(/\\/g,c)),$[0]===c&&$[1]===c){const _=$.indexOf(c,2);_===-1?(E=$.substring(2),$=c):(E=$.substring(2,_),$=$.substring(_)||c)}return new m("file",E,$,u,u)}static from($){const E=new m($.scheme,$.authority,$.path,$.query,$.fragment);return l(E,!0),E}toString($=!1){return v(this,$)}toJSON(){return this}static revive($){if($){if($ instanceof d)return $;{const E=new m($);return E._formatted=$.external,E._fsPath=$._sep===h?$.fsPath:null,E}}return $}}const h=i?1:void 0;class m extends d{constructor(){super(...arguments);$t(this,"_formatted",null);$t(this,"_fsPath",null)}get fsPath(){return this._fsPath||(this._fsPath=R(this)),this._fsPath}toString(E=!1){return E?v(this,!0):(this._formatted||(this._formatted=v(this,!1)),this._formatted)}toJSON(){const E={$mid:1};return this._fsPath&&(E.fsPath=this._fsPath,E._sep=h),this._formatted&&(E.external=this._formatted),this.path&&(E.path=this.path),this.scheme&&(E.scheme=this.scheme),this.authority&&(E.authority=this.authority),this.query&&(E.query=this.query),this.fragment&&(E.fragment=this.fragment),E}}const g={58:"%3A",47:"%2F",63:"%3F",35:"%23",91:"%5B",93:"%5D",64:"%40",33:"%21",36:"%24",38:"%26",39:"%27",40:"%28",41:"%29",42:"%2A",43:"%2B",44:"%2C",59:"%3B",61:"%3D",32:"%20"};function T(S,$,E){let _,P=-1;for(let b=0;b=97&&N<=122||N>=65&&N<=90||N>=48&&N<=57||N===45||N===46||N===95||N===126||$&&N===47||E&&N===91||E&&N===93||E&&N===58)P!==-1&&(_+=encodeURIComponent(S.substring(P,b)),P=-1),_!==void 0&&(_+=S.charAt(b));else{_===void 0&&(_=S.substr(0,b));const $e=g[N];$e!==void 0?(P!==-1&&(_+=encodeURIComponent(S.substring(P,b)),P=-1),_+=$e):P===-1&&(P=b)}}return P!==-1&&(_+=encodeURIComponent(S.substring(P))),_!==void 0?_:S}function y(S){let $;for(let E=0;E1&&S.scheme==="file"?`//${S.authority}${S.path}`:S.path.charCodeAt(0)===47&&(S.path.charCodeAt(1)>=65&&S.path.charCodeAt(1)<=90||S.path.charCodeAt(1)>=97&&S.path.charCodeAt(1)<=122)&&S.path.charCodeAt(2)===58?S.path[1].toLowerCase()+S.path.substr(2):S.path,i&&(E=E.replace(/\//g,"\\")),E}function v(S,$){const E=$?y:T;let _="",{scheme:P,authority:b,path:N,query:$e,fragment:Q}=S;if(P&&(_+=P,_+=":"),(b||P==="file")&&(_+=c,_+=c),b){let V=b.indexOf("@");if(V!==-1){const Gt=b.substr(0,V);b=b.substr(V+1),V=Gt.lastIndexOf(":"),V===-1?_+=E(Gt,!1,!1):(_+=E(Gt.substr(0,V),!1,!1),_+=":",_+=E(Gt.substr(V+1),!1,!0)),_+="@"}b=b.toLowerCase(),V=b.lastIndexOf(":"),V===-1?_+=E(b,!1,!0):(_+=E(b.substr(0,V),!1,!0),_+=b.substr(V))}if(N){if(N.length>=3&&N.charCodeAt(0)===47&&N.charCodeAt(2)===58){const V=N.charCodeAt(1);V>=65&&V<=90&&(N=`/${String.fromCharCode(V+32)}:${N.substr(3)}`)}else if(N.length>=2&&N.charCodeAt(1)===58){const V=N.charCodeAt(0);V>=65&&V<=90&&(N=`${String.fromCharCode(V+32)}:${N.substr(2)}`)}_+=E(N,!0,!1)}return $e&&(_+="?",_+=E($e,!1,!1)),Q&&(_+="#",_+=$?Q:T(Q,!1,!1)),_}function x(S){try{return decodeURIComponent(S)}catch{return S.length>3?S.substr(0,3)+x(S.substr(3)):S}}const O=/(%[0-9A-Za-z][0-9A-Za-z])+/g;function ae(S){return S.match(O)?S.replace(O,$=>x($)):S}var Me=n(470);const ve=Me.posix||Me,He="/";var Ie;(function(S){S.joinPath=function($,...E){return $.with({path:ve.join($.path,...E)})},S.resolvePath=function($,...E){let _=$.path,P=!1;_[0]!==He&&(_=He+_,P=!0);let b=ve.resolve(_,...E);return P&&b[0]===He&&!$.authority&&(b=b.substring(1)),$.with({path:b})},S.dirname=function($){if($.path.length===0||$.path===He)return $;let E=ve.dirname($.path);return E.length===1&&E.charCodeAt(0)===46&&(E=""),$.with({path:E})},S.basename=function($){return ve.basename($.path)},S.extname=function($){return ve.extname($.path)}})(Ie||(Ie={}))})(),up=r})();const{URI:Xt,Utils:Yn}=up;var kt;(function(t){t.basename=Yn.basename,t.dirname=Yn.dirname,t.extname=Yn.extname,t.joinPath=Yn.joinPath,t.resolvePath=Yn.resolvePath;function e(i,s){return(i==null?void 0:i.toString())===(s==null?void 0:s.toString())}t.equals=e;function n(i,s){const a=typeof i=="string"?i:i.path,o=typeof s=="string"?s:s.path,l=a.split("/").filter(h=>h.length>0),u=o.split("/").filter(h=>h.length>0);let c=0;for(;ci??(i=bo.create(e.toString(),r.getServices(e).LanguageMetaData.languageId,0,n??""))}}class oC{constructor(e){this.documentMap=new Map,this.langiumDocumentFactory=e.workspace.LangiumDocumentFactory,this.serviceRegistry=e.ServiceRegistry}get all(){return re(this.documentMap.values())}addDocument(e){const n=e.uri.toString();if(this.documentMap.has(n))throw new Error(`A document with the URI '${n}' is already present.`);this.documentMap.set(n,e)}getDocument(e){const n=e.toString();return this.documentMap.get(n)}async getOrCreateDocument(e,n){let r=this.getDocument(e);return r||(r=await this.langiumDocumentFactory.fromUri(e,n),this.addDocument(r),r)}createDocument(e,n,r){if(r)return this.langiumDocumentFactory.fromString(n,e,r).then(i=>(this.addDocument(i),i));{const i=this.langiumDocumentFactory.fromString(n,e);return this.addDocument(i),i}}hasDocument(e){return this.documentMap.has(e.toString())}invalidateDocument(e){const n=e.toString(),r=this.documentMap.get(n);return r&&(this.serviceRegistry.getServices(e).references.Linker.unlink(r),r.state=H.Changed,r.precomputedScopes=void 0,r.diagnostics=void 0),r}deleteDocument(e){const n=e.toString(),r=this.documentMap.get(n);return r&&(r.state=H.Changed,this.documentMap.delete(n)),r}}const _a=Symbol("ref_resolving");class lC{constructor(e){this.reflection=e.shared.AstReflection,this.langiumDocuments=()=>e.shared.workspace.LangiumDocuments,this.scopeProvider=e.references.ScopeProvider,this.astNodeLocator=e.workspace.AstNodeLocator}async link(e,n=z.None){for(const r of dn(e.parseResult.value))await Ee(n),Xf(r).forEach(i=>this.doLink(i,e))}doLink(e,n){var r;const i=e.reference;if(i._ref===void 0){i._ref=_a;try{const s=this.getCandidate(e);if(wi(s))i._ref=s;else if(i._nodeDescription=s,this.langiumDocuments().hasDocument(s.documentUri)){const a=this.loadAstNode(s);i._ref=a??this.createLinkingError(e,s)}else i._ref=void 0}catch(s){console.error(`An error occurred while resolving reference to '${i.$refText}':`,s);const a=(r=s.message)!==null&&r!==void 0?r:String(s);i._ref=Object.assign(Object.assign({},e),{message:`An error occurred while resolving reference to '${i.$refText}': ${a}`})}n.references.push(i)}}unlink(e){for(const n of e.references)delete n._ref,delete n._nodeDescription;e.references=[]}getCandidate(e){const r=this.scopeProvider.getScope(e).getElement(e.reference.$refText);return r??this.createLinkingError(e)}buildReference(e,n,r,i){const s=this,a={$refNode:r,$refText:i,get ref(){var o;if(ue(this._ref))return this._ref;if(zp(this._nodeDescription)){const l=s.loadAstNode(this._nodeDescription);this._ref=l??s.createLinkingError({reference:a,container:e,property:n},this._nodeDescription)}else if(this._ref===void 0){this._ref=_a;const l=Ka(e).$document,u=s.getLinkedNode({reference:a,container:e,property:n});if(u.error&&l&&l.state=e.end)return s.ref}}if(r){const i=this.nameProvider.getNameNode(r);if(i&&(i===e||Yp(e,i)))return r}}}findDeclarationNode(e){const n=this.findDeclaration(e);if(n!=null&&n.$cstNode){const r=this.nameProvider.getNameNode(n);return r??n.$cstNode}}findReferences(e,n){const r=[];if(n.includeDeclaration){const s=this.getReferenceToSelf(e);s&&r.push(s)}let i=this.index.findAllReferences(e,this.nodeLocator.getAstNodePath(e));return n.documentUri&&(i=i.filter(s=>kt.equals(s.sourceUri,n.documentUri))),r.push(...i),re(r)}getReferenceToSelf(e){const n=this.nameProvider.getNameNode(e);if(n){const r=At(e),i=this.nodeLocator.getAstNodePath(e);return{sourceUri:r.uri,sourcePath:i,targetUri:r.uri,targetPath:i,segment:Wi(n),local:!0}}}}class ys{constructor(e){if(this.map=new Map,e)for(const[n,r]of e)this.add(n,r)}get size(){return Ua.sum(re(this.map.values()).map(e=>e.length))}clear(){this.map.clear()}delete(e,n){if(n===void 0)return this.map.delete(e);{const r=this.map.get(e);if(r){const i=r.indexOf(n);if(i>=0)return r.length===1?this.map.delete(e):r.splice(i,1),!0}return!1}}get(e){var n;return(n=this.map.get(e))!==null&&n!==void 0?n:[]}has(e,n){if(n===void 0)return this.map.has(e);{const r=this.map.get(e);return r?r.indexOf(n)>=0:!1}}add(e,n){return this.map.has(e)?this.map.get(e).push(n):this.map.set(e,[n]),this}addAll(e,n){return this.map.has(e)?this.map.get(e).push(...n):this.map.set(e,Array.from(n)),this}forEach(e){this.map.forEach((n,r)=>n.forEach(i=>e(i,r,this)))}[Symbol.iterator](){return this.entries().iterator()}entries(){return re(this.map.entries()).flatMap(([e,n])=>n.map(r=>[e,r]))}keys(){return re(this.map.keys())}values(){return re(this.map.values()).flat()}entriesGroupedByKey(){return re(this.map.entries())}}class Af{get size(){return this.map.size}constructor(e){if(this.map=new Map,this.inverse=new Map,e)for(const[n,r]of e)this.set(n,r)}clear(){this.map.clear(),this.inverse.clear()}set(e,n){return this.map.set(e,n),this.inverse.set(n,e),this}get(e){return this.map.get(e)}getKey(e){return this.inverse.get(e)}delete(e){const n=this.map.get(e);return n!==void 0?(this.map.delete(e),this.inverse.delete(n),!0):!1}}class dC{constructor(e){this.nameProvider=e.references.NameProvider,this.descriptions=e.workspace.AstNodeDescriptionProvider}async computeExports(e,n=z.None){return this.computeExportsForNode(e.parseResult.value,e,void 0,n)}async computeExportsForNode(e,n,r=Go,i=z.None){const s=[];this.exportNode(e,s,n);for(const a of r(e))await Ee(i),this.exportNode(a,s,n);return s}exportNode(e,n,r){const i=this.nameProvider.getName(e);i&&n.push(this.descriptions.createDescription(e,i,r))}async computeLocalScopes(e,n=z.None){const r=e.parseResult.value,i=new ys;for(const s of zr(r))await Ee(n),this.processNode(s,e,i);return i}processNode(e,n,r){const i=e.$container;if(i){const s=this.nameProvider.getName(e);s&&r.add(i,this.descriptions.createDescription(e,s,n))}}}class Ef{constructor(e,n,r){var i;this.elements=e,this.outerScope=n,this.caseInsensitive=(i=r==null?void 0:r.caseInsensitive)!==null&&i!==void 0?i:!1}getAllElements(){return this.outerScope?this.elements.concat(this.outerScope.getAllElements()):this.elements}getElement(e){const n=this.caseInsensitive?this.elements.find(r=>r.name.toLowerCase()===e.toLowerCase()):this.elements.find(r=>r.name===e);if(n)return n;if(this.outerScope)return this.outerScope.getElement(e)}}class hC{constructor(e,n,r){var i;this.elements=new Map,this.caseInsensitive=(i=r==null?void 0:r.caseInsensitive)!==null&&i!==void 0?i:!1;for(const s of e){const a=this.caseInsensitive?s.name.toLowerCase():s.name;this.elements.set(a,s)}this.outerScope=n}getElement(e){const n=this.caseInsensitive?e.toLowerCase():e,r=this.elements.get(n);if(r)return r;if(this.outerScope)return this.outerScope.getElement(e)}getAllElements(){let e=re(this.elements.values());return this.outerScope&&(e=e.concat(this.outerScope.getAllElements())),e}}class cp{constructor(){this.toDispose=[],this.isDisposed=!1}onDispose(e){this.toDispose.push(e)}dispose(){this.throwIfDisposed(),this.clear(),this.isDisposed=!0,this.toDispose.forEach(e=>e.dispose())}throwIfDisposed(){if(this.isDisposed)throw new Error("This cache has already been disposed")}}class pC extends cp{constructor(){super(...arguments),this.cache=new Map}has(e){return this.throwIfDisposed(),this.cache.has(e)}set(e,n){this.throwIfDisposed(),this.cache.set(e,n)}get(e,n){if(this.throwIfDisposed(),this.cache.has(e))return this.cache.get(e);if(n){const r=n();return this.cache.set(e,r),r}else return}delete(e){return this.throwIfDisposed(),this.cache.delete(e)}clear(){this.throwIfDisposed(),this.cache.clear()}}class mC extends cp{constructor(e){super(),this.cache=new Map,this.converter=e??(n=>n)}has(e,n){return this.throwIfDisposed(),this.cacheForContext(e).has(n)}set(e,n,r){this.throwIfDisposed(),this.cacheForContext(e).set(n,r)}get(e,n,r){this.throwIfDisposed();const i=this.cacheForContext(e);if(i.has(n))return i.get(n);if(r){const s=r();return i.set(n,s),s}else return}delete(e,n){return this.throwIfDisposed(),this.cacheForContext(e).delete(n)}clear(e){if(this.throwIfDisposed(),e){const n=this.converter(e);this.cache.delete(n)}else this.cache.clear()}cacheForContext(e){const n=this.converter(e);let r=this.cache.get(n);return r||(r=new Map,this.cache.set(n,r)),r}}class gC extends pC{constructor(e,n){super(),n?(this.toDispose.push(e.workspace.DocumentBuilder.onBuildPhase(n,()=>{this.clear()})),this.toDispose.push(e.workspace.DocumentBuilder.onUpdate((r,i)=>{i.length>0&&this.clear()}))):this.toDispose.push(e.workspace.DocumentBuilder.onUpdate(()=>{this.clear()}))}}class yC{constructor(e){this.reflection=e.shared.AstReflection,this.nameProvider=e.references.NameProvider,this.descriptions=e.workspace.AstNodeDescriptionProvider,this.indexManager=e.shared.workspace.IndexManager,this.globalScopeCache=new gC(e.shared)}getScope(e){const n=[],r=this.reflection.getReferenceType(e),i=At(e.container).precomputedScopes;if(i){let a=e.container;do{const o=i.get(a);o.length>0&&n.push(re(o).filter(l=>this.reflection.isSubtype(l.type,r))),a=a.$container}while(a)}let s=this.getGlobalScope(r,e);for(let a=n.length-1;a>=0;a--)s=this.createScope(n[a],s);return s}createScope(e,n,r){return new Ef(re(e),n,r)}createScopeForNodes(e,n,r){const i=re(e).map(s=>{const a=this.nameProvider.getName(s);if(a)return this.descriptions.createDescription(s,a)}).nonNullable();return new Ef(i,n,r)}getGlobalScope(e,n){return this.globalScopeCache.get(e,()=>new hC(this.indexManager.allElements(e)))}}function TC(t){return typeof t.$comment=="string"}function Sf(t){return typeof t=="object"&&!!t&&("$ref"in t||"$error"in t)}class vC{constructor(e){this.ignoreProperties=new Set(["$container","$containerProperty","$containerIndex","$document","$cstNode"]),this.langiumDocuments=e.shared.workspace.LangiumDocuments,this.astNodeLocator=e.workspace.AstNodeLocator,this.nameProvider=e.references.NameProvider,this.commentProvider=e.documentation.CommentProvider}serialize(e,n){const r=n??{},i=n==null?void 0:n.replacer,s=(o,l)=>this.replacer(o,l,r),a=i?(o,l)=>i(o,l,s):s;try{return this.currentDocument=At(e),JSON.stringify(e,a,n==null?void 0:n.space)}finally{this.currentDocument=void 0}}deserialize(e,n){const r=n??{},i=JSON.parse(e);return this.linkNode(i,i,r),i}replacer(e,n,{refText:r,sourceText:i,textRegions:s,comments:a,uriConverter:o}){var l,u,c,f;if(!this.ignoreProperties.has(e))if(ze(n)){const d=n.ref,h=r?n.$refText:void 0;if(d){const m=At(d);let g="";this.currentDocument&&this.currentDocument!==m&&(o?g=o(m.uri,n):g=m.uri.toString());const T=this.astNodeLocator.getAstNodePath(d);return{$ref:`${g}#${T}`,$refText:h}}else return{$error:(u=(l=n.error)===null||l===void 0?void 0:l.message)!==null&&u!==void 0?u:"Could not resolve reference",$refText:h}}else if(ue(n)){let d;if(s&&(d=this.addAstNodeRegionWithAssignmentsTo(Object.assign({},n)),(!e||n.$document)&&(d!=null&&d.$textRegion)&&(d.$textRegion.documentURI=(c=this.currentDocument)===null||c===void 0?void 0:c.uri.toString())),i&&!e&&(d??(d=Object.assign({},n)),d.$sourceText=(f=n.$cstNode)===null||f===void 0?void 0:f.text),a){d??(d=Object.assign({},n));const h=this.commentProvider.getComment(n);h&&(d.$comment=h.replace(/\r/g,""))}return d??n}else return n}addAstNodeRegionWithAssignmentsTo(e){const n=r=>({offset:r.offset,end:r.end,length:r.length,range:r.range});if(e.$cstNode){const r=e.$textRegion=n(e.$cstNode),i=r.assignments={};return Object.keys(e).filter(s=>!s.startsWith("$")).forEach(s=>{const a=Lm(e.$cstNode,s).map(n);a.length!==0&&(i[s]=a)}),e}}linkNode(e,n,r,i,s,a){for(const[l,u]of Object.entries(e))if(Array.isArray(u))for(let c=0;c{await this.handleException(()=>e.call(n,r,i,s),"An error occurred during validation",i,r)}}async handleException(e,n,r,i){try{await e()}catch(s){if(ta(s))throw s;console.error(`${n}:`,s),s instanceof Error&&s.stack&&console.error(s.stack);const a=s instanceof Error?s.message:String(s);r("error",`${n}: ${a}`,{node:i})}}addEntry(e,n){if(e==="AstNode"){this.entries.add("AstNode",n);return}for(const r of this.reflection.getAllSubTypes(e))this.entries.add(r,n)}getChecks(e,n){let r=re(this.entries.get(e)).concat(this.entries.get("AstNode"));return n&&(r=r.filter(i=>n.includes(i.category))),r.map(i=>i.check)}registerBeforeDocument(e,n=this){this.entriesBefore.push(this.wrapPreparationException(e,"An error occurred during set-up of the validation",n))}registerAfterDocument(e,n=this){this.entriesAfter.push(this.wrapPreparationException(e,"An error occurred during tear-down of the validation",n))}wrapPreparationException(e,n,r){return async(i,s,a,o)=>{await this.handleException(()=>e.call(r,i,s,a,o),n,s,i)}}get checksBefore(){return this.entriesBefore}get checksAfter(){return this.entriesAfter}}class AC{constructor(e){this.validationRegistry=e.validation.ValidationRegistry,this.metadata=e.LanguageMetaData}async validateDocument(e,n={},r=z.None){const i=e.parseResult,s=[];if(await Ee(r),(!n.categories||n.categories.includes("built-in"))&&(this.processLexingErrors(i,s,n),n.stopAfterLexingErrors&&s.some(a=>{var o;return((o=a.data)===null||o===void 0?void 0:o.code)===Fe.LexingError})||(this.processParsingErrors(i,s,n),n.stopAfterParsingErrors&&s.some(a=>{var o;return((o=a.data)===null||o===void 0?void 0:o.code)===Fe.ParsingError}))||(this.processLinkingErrors(e,s,n),n.stopAfterLinkingErrors&&s.some(a=>{var o;return((o=a.data)===null||o===void 0?void 0:o.code)===Fe.LinkingError}))))return s;try{s.push(...await this.validateAst(i.value,n,r))}catch(a){if(ta(a))throw a;console.error("An error occurred during validation:",a)}return await Ee(r),s}processLexingErrors(e,n,r){var i,s,a;const o=[...e.lexerErrors,...(s=(i=e.lexerReport)===null||i===void 0?void 0:i.diagnostics)!==null&&s!==void 0?s:[]];for(const l of o){const u=(a=l.severity)!==null&&a!==void 0?a:"error",c={severity:Ia(u),range:{start:{line:l.line-1,character:l.column-1},end:{line:l.line-1,character:l.column+l.length-1}},message:l.message,data:SC(u),source:this.getSource()};n.push(c)}}processParsingErrors(e,n,r){for(const i of e.parserErrors){let s;if(isNaN(i.token.startOffset)){if("previousToken"in i){const a=i.previousToken;if(isNaN(a.startOffset)){const o={line:0,character:0};s={start:o,end:o}}else{const o={line:a.endLine-1,character:a.endColumn};s={start:o,end:o}}}}else s=ja(i.token);if(s){const a={severity:Ia("error"),range:s,message:i.message,data:Ir(Fe.ParsingError),source:this.getSource()};n.push(a)}}}processLinkingErrors(e,n,r){for(const i of e.references){const s=i.error;if(s){const a={node:s.container,property:s.property,index:s.index,data:{code:Fe.LinkingError,containerType:s.container.$type,property:s.property,refText:s.reference.$refText}};n.push(this.toDiagnostic("error",s.message,a))}}}async validateAst(e,n,r=z.None){const i=[],s=(a,o,l)=>{i.push(this.toDiagnostic(a,o,l))};return await this.validateAstBefore(e,n,s,r),await this.validateAstNodes(e,n,s,r),await this.validateAstAfter(e,n,s,r),i}async validateAstBefore(e,n,r,i=z.None){var s;const a=this.validationRegistry.checksBefore;for(const o of a)await Ee(i),await o(e,r,(s=n.categories)!==null&&s!==void 0?s:[],i)}async validateAstNodes(e,n,r,i=z.None){await Promise.all(dn(e).map(async s=>{await Ee(i);const a=this.validationRegistry.getChecks(s.$type,n.categories);for(const o of a)await o(s,r,i)}))}async validateAstAfter(e,n,r,i=z.None){var s;const a=this.validationRegistry.checksAfter;for(const o of a)await Ee(i),await o(e,r,(s=n.categories)!==null&&s!==void 0?s:[],i)}toDiagnostic(e,n,r){return{message:n,range:EC(r),severity:Ia(e),code:r.code,codeDescription:r.codeDescription,tags:r.tags,relatedInformation:r.relatedInformation,data:r.data,source:this.getSource()}}getSource(){return this.metadata.languageId}}function EC(t){if(t.range)return t.range;let e;return typeof t.property=="string"?e=td(t.node.$cstNode,t.property,t.index):typeof t.keyword=="string"&&(e=Mm(t.node.$cstNode,t.keyword,t.index)),e??(e=t.node.$cstNode),e?e.range:{start:{line:0,character:0},end:{line:0,character:0}}}function Ia(t){switch(t){case"error":return 1;case"warning":return 2;case"info":return 3;case"hint":return 4;default:throw new Error("Invalid diagnostic severity: "+t)}}function SC(t){switch(t){case"error":return Ir(Fe.LexingError);case"warning":return Ir(Fe.LexingWarning);case"info":return Ir(Fe.LexingInfo);case"hint":return Ir(Fe.LexingHint);default:throw new Error("Invalid diagnostic severity: "+t)}}var Fe;(function(t){t.LexingError="lexing-error",t.LexingWarning="lexing-warning",t.LexingInfo="lexing-info",t.LexingHint="lexing-hint",t.ParsingError="parsing-error",t.LinkingError="linking-error"})(Fe||(Fe={}));class xC{constructor(e){this.astNodeLocator=e.workspace.AstNodeLocator,this.nameProvider=e.references.NameProvider}createDescription(e,n,r){const i=r??At(e);n??(n=this.nameProvider.getName(e));const s=this.astNodeLocator.getAstNodePath(e);if(!n)throw new Error(`Node at path ${s} has no name.`);let a;const o=()=>{var l;return a??(a=Wi((l=this.nameProvider.getNameNode(e))!==null&&l!==void 0?l:e.$cstNode))};return{node:e,name:n,get nameSegment(){return o()},selectionSegment:Wi(e.$cstNode),type:e.$type,documentUri:i.uri,path:s}}}class _C{constructor(e){this.nodeLocator=e.workspace.AstNodeLocator}async createDescriptions(e,n=z.None){const r=[],i=e.parseResult.value;for(const s of dn(i))await Ee(n),Xf(s).filter(a=>!wi(a)).forEach(a=>{const o=this.createDescription(a);o&&r.push(o)});return r}createDescription(e){const n=e.reference.$nodeDescription,r=e.reference.$refNode;if(!n||!r)return;const i=At(e.container).uri;return{sourceUri:i,sourcePath:this.nodeLocator.getAstNodePath(e.container),targetUri:n.documentUri,targetPath:n.path,segment:Wi(r),local:kt.equals(n.documentUri,i)}}}class IC{constructor(){this.segmentSeparator="/",this.indexSeparator="@"}getAstNodePath(e){if(e.$container){const n=this.getAstNodePath(e.$container),r=this.getPathSegment(e);return n+this.segmentSeparator+r}return""}getPathSegment({$containerProperty:e,$containerIndex:n}){if(!e)throw new Error("Missing '$containerProperty' in AST node.");return n!==void 0?e+this.indexSeparator+n:e}getAstNode(e,n){return n.split(this.segmentSeparator).reduce((i,s)=>{if(!i||s.length===0)return i;const a=s.indexOf(this.indexSeparator);if(a>0){const o=s.substring(0,a),l=parseInt(s.substring(a+1)),u=i[o];return u==null?void 0:u[l]}return i[s]},e)}}class wC{constructor(e){this._ready=new Cl,this.settings={},this.workspaceConfig=!1,this.onConfigurationSectionUpdateEmitter=new ap,this.serviceRegistry=e.ServiceRegistry}get ready(){return this._ready.promise}initialize(e){var n,r;this.workspaceConfig=(r=(n=e.capabilities.workspace)===null||n===void 0?void 0:n.configuration)!==null&&r!==void 0?r:!1}async initialized(e){if(this.workspaceConfig){if(e.register){const n=this.serviceRegistry.all;e.register({section:n.map(r=>this.toSectionName(r.LanguageMetaData.languageId))})}if(e.fetchConfiguration){const n=this.serviceRegistry.all.map(i=>({section:this.toSectionName(i.LanguageMetaData.languageId)})),r=await e.fetchConfiguration(n);n.forEach((i,s)=>{this.updateSectionConfiguration(i.section,r[s])})}}this._ready.resolve()}updateConfiguration(e){e.settings&&Object.keys(e.settings).forEach(n=>{const r=e.settings[n];this.updateSectionConfiguration(n,r),this.onConfigurationSectionUpdateEmitter.fire({section:n,configuration:r})})}updateSectionConfiguration(e,n){this.settings[e]=n}async getConfiguration(e,n){await this.ready;const r=this.toSectionName(e);if(this.settings[r])return this.settings[r][n]}toSectionName(e){return`${e}`}get onConfigurationSectionUpdate(){return this.onConfigurationSectionUpdateEmitter.event}}var Pr;(function(t){function e(n){return{dispose:async()=>await n()}}t.create=e})(Pr||(Pr={}));class CC{constructor(e){this.updateBuildOptions={validation:{categories:["built-in","fast"]}},this.updateListeners=[],this.buildPhaseListeners=new ys,this.documentPhaseListeners=new ys,this.buildState=new Map,this.documentBuildWaiters=new Map,this.currentState=H.Changed,this.langiumDocuments=e.workspace.LangiumDocuments,this.langiumDocumentFactory=e.workspace.LangiumDocumentFactory,this.textDocuments=e.workspace.TextDocuments,this.indexManager=e.workspace.IndexManager,this.serviceRegistry=e.ServiceRegistry}async build(e,n={},r=z.None){var i,s;for(const a of e){const o=a.uri.toString();if(a.state===H.Validated){if(typeof n.validation=="boolean"&&n.validation)a.state=H.IndexedReferences,a.diagnostics=void 0,this.buildState.delete(o);else if(typeof n.validation=="object"){const l=this.buildState.get(o),u=(i=l==null?void 0:l.result)===null||i===void 0?void 0:i.validationChecks;if(u){const f=((s=n.validation.categories)!==null&&s!==void 0?s:Ts.all).filter(d=>!u.includes(d));f.length>0&&(this.buildState.set(o,{completed:!1,options:{validation:Object.assign(Object.assign({},n.validation),{categories:f})},result:l.result}),a.state=H.IndexedReferences)}}}else this.buildState.delete(o)}this.currentState=H.Changed,await this.emitUpdate(e.map(a=>a.uri),[]),await this.buildDocuments(e,n,r)}async update(e,n,r=z.None){this.currentState=H.Changed;for(const a of n)this.langiumDocuments.deleteDocument(a),this.buildState.delete(a.toString()),this.indexManager.remove(a);for(const a of e){if(!this.langiumDocuments.invalidateDocument(a)){const l=this.langiumDocumentFactory.fromModel({$type:"INVALID"},a);l.state=H.Changed,this.langiumDocuments.addDocument(l)}this.buildState.delete(a.toString())}const i=re(e).concat(n).map(a=>a.toString()).toSet();this.langiumDocuments.all.filter(a=>!i.has(a.uri.toString())&&this.shouldRelink(a,i)).forEach(a=>{this.serviceRegistry.getServices(a.uri).references.Linker.unlink(a),a.state=Math.min(a.state,H.ComputedScopes),a.diagnostics=void 0}),await this.emitUpdate(e,n),await Ee(r);const s=this.sortDocuments(this.langiumDocuments.all.filter(a=>{var o;return a.stater(e,n)))}sortDocuments(e){let n=0,r=e.length-1;for(;n=0&&!this.hasTextDocument(e[r]);)r--;nr.error!==void 0)?!0:this.indexManager.isAffected(e,n)}onUpdate(e){return this.updateListeners.push(e),Pr.create(()=>{const n=this.updateListeners.indexOf(e);n>=0&&this.updateListeners.splice(n,1)})}async buildDocuments(e,n,r){this.prepareBuild(e,n),await this.runCancelable(e,H.Parsed,r,s=>this.langiumDocumentFactory.update(s,r)),await this.runCancelable(e,H.IndexedContent,r,s=>this.indexManager.updateContent(s,r)),await this.runCancelable(e,H.ComputedScopes,r,async s=>{const a=this.serviceRegistry.getServices(s.uri).references.ScopeComputation;s.precomputedScopes=await a.computeLocalScopes(s,r)}),await this.runCancelable(e,H.Linked,r,s=>this.serviceRegistry.getServices(s.uri).references.Linker.link(s,r)),await this.runCancelable(e,H.IndexedReferences,r,s=>this.indexManager.updateReferences(s,r));const i=e.filter(s=>this.shouldValidate(s));await this.runCancelable(i,H.Validated,r,s=>this.validate(s,r));for(const s of e){const a=this.buildState.get(s.uri.toString());a&&(a.completed=!0)}}prepareBuild(e,n){for(const r of e){const i=r.uri.toString(),s=this.buildState.get(i);(!s||s.completed)&&this.buildState.set(i,{completed:!1,options:n,result:s==null?void 0:s.result})}}async runCancelable(e,n,r,i){const s=e.filter(o=>o.stateo.state===n);await this.notifyBuildPhase(a,n,r),this.currentState=n}onBuildPhase(e,n){return this.buildPhaseListeners.add(e,n),Pr.create(()=>{this.buildPhaseListeners.delete(e,n)})}onDocumentPhase(e,n){return this.documentPhaseListeners.add(e,n),Pr.create(()=>{this.documentPhaseListeners.delete(e,n)})}waitUntil(e,n,r){let i;if(n&&"path"in n?i=n:r=n,r??(r=z.None),i){const s=this.langiumDocuments.getDocument(i);if(s&&s.state>e)return Promise.resolve(i)}return this.currentState>=e?Promise.resolve(void 0):r.isCancellationRequested?Promise.reject(gs):new Promise((s,a)=>{const o=this.onBuildPhase(e,()=>{if(o.dispose(),l.dispose(),i){const u=this.langiumDocuments.getDocument(i);s(u==null?void 0:u.uri)}else s(void 0)}),l=r.onCancellationRequested(()=>{o.dispose(),l.dispose(),a(gs)})})}async notifyDocumentPhase(e,n,r){const s=this.documentPhaseListeners.get(n).slice();for(const a of s)try{await a(e,r)}catch(o){if(!ta(o))throw o}}async notifyBuildPhase(e,n,r){if(e.length===0)return;const s=this.buildPhaseListeners.get(n).slice();for(const a of s)await Ee(r),await a(e,r)}shouldValidate(e){return!!this.getBuildOptions(e).validation}async validate(e,n){var r,i;const s=this.serviceRegistry.getServices(e.uri).validation.DocumentValidator,a=this.getBuildOptions(e).validation,o=typeof a=="object"?a:void 0,l=await s.validateDocument(e,o,n);e.diagnostics?e.diagnostics.push(...l):e.diagnostics=l;const u=this.buildState.get(e.uri.toString());if(u){(r=u.result)!==null&&r!==void 0||(u.result={});const c=(i=o==null?void 0:o.categories)!==null&&i!==void 0?i:Ts.all;u.result.validationChecks?u.result.validationChecks.push(...c):u.result.validationChecks=[...c]}}getBuildOptions(e){var n,r;return(r=(n=this.buildState.get(e.uri.toString()))===null||n===void 0?void 0:n.options)!==null&&r!==void 0?r:{}}}class kC{constructor(e){this.symbolIndex=new Map,this.symbolByTypeIndex=new mC,this.referenceIndex=new Map,this.documents=e.workspace.LangiumDocuments,this.serviceRegistry=e.ServiceRegistry,this.astReflection=e.AstReflection}findAllReferences(e,n){const r=At(e).uri,i=[];return this.referenceIndex.forEach(s=>{s.forEach(a=>{kt.equals(a.targetUri,r)&&a.targetPath===n&&i.push(a)})}),re(i)}allElements(e,n){let r=re(this.symbolIndex.keys());return n&&(r=r.filter(i=>!n||n.has(i))),r.map(i=>this.getFileDescriptions(i,e)).flat()}getFileDescriptions(e,n){var r;return n?this.symbolByTypeIndex.get(e,n,()=>{var s;return((s=this.symbolIndex.get(e))!==null&&s!==void 0?s:[]).filter(o=>this.astReflection.isSubtype(o.type,n))}):(r=this.symbolIndex.get(e))!==null&&r!==void 0?r:[]}remove(e){const n=e.toString();this.symbolIndex.delete(n),this.symbolByTypeIndex.clear(n),this.referenceIndex.delete(n)}async updateContent(e,n=z.None){const i=await this.serviceRegistry.getServices(e.uri).references.ScopeComputation.computeExports(e,n),s=e.uri.toString();this.symbolIndex.set(s,i),this.symbolByTypeIndex.clear(s)}async updateReferences(e,n=z.None){const i=await this.serviceRegistry.getServices(e.uri).workspace.ReferenceDescriptionProvider.createDescriptions(e,n);this.referenceIndex.set(e.uri.toString(),i)}isAffected(e,n){const r=this.referenceIndex.get(e.uri.toString());return r?r.some(i=>!i.local&&n.has(i.targetUri.toString())):!1}}class NC{constructor(e){this.initialBuildOptions={},this._ready=new Cl,this.serviceRegistry=e.ServiceRegistry,this.langiumDocuments=e.workspace.LangiumDocuments,this.documentBuilder=e.workspace.DocumentBuilder,this.fileSystemProvider=e.workspace.FileSystemProvider,this.mutex=e.workspace.WorkspaceLock}get ready(){return this._ready.promise}get workspaceFolders(){return this.folders}initialize(e){var n;this.folders=(n=e.workspaceFolders)!==null&&n!==void 0?n:void 0}initialized(e){return this.mutex.write(n=>{var r;return this.initializeWorkspace((r=this.folders)!==null&&r!==void 0?r:[],n)})}async initializeWorkspace(e,n=z.None){const r=await this.performStartup(e);await Ee(n),await this.documentBuilder.build(r,this.initialBuildOptions,n)}async performStartup(e){const n=this.serviceRegistry.all.flatMap(s=>s.LanguageMetaData.fileExtensions),r=[],i=s=>{r.push(s),this.langiumDocuments.hasDocument(s.uri)||this.langiumDocuments.addDocument(s)};return await this.loadAdditionalDocuments(e,i),await Promise.all(e.map(s=>[s,this.getRootFolder(s)]).map(async s=>this.traverseFolder(...s,n,i))),this._ready.resolve(),r}loadAdditionalDocuments(e,n){return Promise.resolve()}getRootFolder(e){return Xt.parse(e.uri)}async traverseFolder(e,n,r,i){const s=await this.fileSystemProvider.readDirectory(n);await Promise.all(s.map(async a=>{if(this.includeEntry(e,a,r)){if(a.isDirectory)await this.traverseFolder(e,a.uri,r,i);else if(a.isFile){const o=await this.langiumDocuments.getOrCreateDocument(a.uri);i(o)}}}))}includeEntry(e,n,r){const i=kt.basename(n.uri);if(i.startsWith("."))return!1;if(n.isDirectory)return i!=="node_modules"&&i!=="out";if(n.isFile){const s=kt.extname(n.uri);return r.includes(s)}return!1}}class bC{buildUnexpectedCharactersMessage(e,n,r,i,s){return io.buildUnexpectedCharactersMessage(e,n,r,i,s)}buildUnableToPopLexerModeMessage(e){return io.buildUnableToPopLexerModeMessage(e)}}const OC={mode:"full"};class PC{constructor(e){this.errorMessageProvider=e.parser.LexerErrorMessageProvider,this.tokenBuilder=e.parser.TokenBuilder;const n=this.tokenBuilder.buildTokens(e.Grammar,{caseInsensitive:e.LanguageMetaData.caseInsensitive});this.tokenTypes=this.toTokenTypeDictionary(n);const r=xf(n)?Object.values(n):n,i=e.LanguageMetaData.mode==="production";this.chevrotainLexer=new he(r,{positionTracking:"full",skipValidations:i,errorMessageProvider:this.errorMessageProvider})}get definition(){return this.tokenTypes}tokenize(e,n=OC){var r,i,s;const a=this.chevrotainLexer.tokenize(e);return{tokens:a.tokens,errors:a.errors,hidden:(r=a.groups.hidden)!==null&&r!==void 0?r:[],report:(s=(i=this.tokenBuilder).flushLexingReport)===null||s===void 0?void 0:s.call(i,e)}}toTokenTypeDictionary(e){if(xf(e))return e;const n=fp(e)?Object.values(e.modes).flat():e,r={};return n.forEach(i=>r[i.name]=i),r}}function LC(t){return Array.isArray(t)&&(t.length===0||"name"in t[0])}function fp(t){return t&&"modes"in t&&"defaultMode"in t}function xf(t){return!LC(t)&&!fp(t)}function MC(t,e,n){let r,i;typeof t=="string"?(i=e,r=n):(i=t.range.start,r=e),i||(i=D.create(0,0));const s=dp(t),a=kl(r),o=GC({lines:s,position:i,options:a});return HC({index:0,tokens:o,position:i})}function DC(t,e){const n=kl(e),r=dp(t);if(r.length===0)return!1;const i=r[0],s=r[r.length-1],a=n.start,o=n.end;return!!(a!=null&&a.exec(i))&&!!(o!=null&&o.exec(s))}function dp(t){let e="";return typeof t=="string"?e=t:e=t.text,e.split(Em)}const _f=/\s*(@([\p{L}][\p{L}\p{N}]*)?)/uy,FC=/\{(@[\p{L}][\p{L}\p{N}]*)(\s*)([^\r\n}]+)?\}/gu;function GC(t){var e,n,r;const i=[];let s=t.position.line,a=t.position.character;for(let o=0;o=c.length){if(i.length>0){const h=D.create(s,a);i.push({type:"break",content:"",range:L.create(h,h)})}}else{_f.lastIndex=f;const h=_f.exec(c);if(h){const m=h[0],g=h[1],T=D.create(s,a+f),y=D.create(s,a+f+m.length);i.push({type:"tag",content:g,range:L.create(T,y)}),f+=m.length,f=Po(c,f)}if(f0&&i[i.length-1].type==="break"?i.slice(0,-1):i}function UC(t,e,n,r){const i=[];if(t.length===0){const s=D.create(n,r),a=D.create(n,r+e.length);i.push({type:"text",content:e,range:L.create(s,a)})}else{let s=0;for(const o of t){const l=o.index,u=e.substring(s,l);u.length>0&&i.push({type:"text",content:e.substring(s,l),range:L.create(D.create(n,s+r),D.create(n,l+r))});let c=u.length+1;const f=o[1];if(i.push({type:"inline-tag",content:f,range:L.create(D.create(n,s+c+r),D.create(n,s+c+f.length+r))}),c+=f.length,o.length===4){c+=o[2].length;const d=o[3];i.push({type:"text",content:d,range:L.create(D.create(n,s+c+r),D.create(n,s+c+d.length+r))})}else i.push({type:"text",content:"",range:L.create(D.create(n,s+c+r),D.create(n,s+c+r))});s=l+o[0].length}const a=e.substring(s);a.length>0&&i.push({type:"text",content:a,range:L.create(D.create(n,s+r),D.create(n,s+r+a.length))})}return i}const BC=/\S/,jC=/\s*$/;function Po(t,e){const n=t.substring(e).match(BC);return n?e+n.index:t.length}function KC(t){const e=t.match(jC);if(e&&typeof e.index=="number")return e.index}function HC(t){var e,n,r,i;const s=D.create(t.position.line,t.position.character);if(t.tokens.length===0)return new If([],L.create(s,s));const a=[];for(;t.indexn.name===e)}getTags(e){return this.getAllTags().filter(n=>n.name===e)}getAllTags(){return this.elements.filter(e=>"name"in e)}toString(){let e="";for(const n of this.elements)if(e.length===0)e=n.toString();else{const r=n.toString();e+=wf(e)+r}return e.trim()}toMarkdown(e){let n="";for(const r of this.elements)if(n.length===0)n=r.toMarkdown(e);else{const i=r.toMarkdown(e);n+=wf(n)+i}return n.trim()}}class Ca{constructor(e,n,r,i){this.name=e,this.content=n,this.inline=r,this.range=i}toString(){let e=`@${this.name}`;const n=this.content.toString();return this.content.inlines.length===1?e=`${e} ${n}`:this.content.inlines.length>1&&(e=`${e} +${n}`),this.inline?`{${e}}`:e}toMarkdown(e){var n,r;return(r=(n=e==null?void 0:e.renderTag)===null||n===void 0?void 0:n.call(e,this))!==null&&r!==void 0?r:this.toMarkdownDefault(e)}toMarkdownDefault(e){const n=this.content.toMarkdown(e);if(this.inline){const s=qC(this.name,n,e??{});if(typeof s=="string")return s}let r="";(e==null?void 0:e.tag)==="italic"||(e==null?void 0:e.tag)===void 0?r="*":(e==null?void 0:e.tag)==="bold"?r="**":(e==null?void 0:e.tag)==="bold-italic"&&(r="***");let i=`${r}@${this.name}${r}`;return this.content.inlines.length===1?i=`${i} — ${n}`:this.content.inlines.length>1&&(i=`${i} +${n}`),this.inline?`{${i}}`:i}}function qC(t,e,n){var r,i;if(t==="linkplain"||t==="linkcode"||t==="link"){const s=e.indexOf(" ");let a=e;if(s>0){const l=Po(e,s);a=e.substring(l),e=e.substring(0,s)}return(t==="linkcode"||t==="link"&&n.link==="code")&&(a=`\`${a}\``),(i=(r=n.renderLink)===null||r===void 0?void 0:r.call(n,e,a))!==null&&i!==void 0?i:YC(e,a)}}function YC(t,e){try{return Xt.parse(t,!0),`[${e}](${t})`}catch{return t}}class Lo{constructor(e,n){this.inlines=e,this.range=n}toString(){let e="";for(let n=0;nr.range.start.line&&(e+=` +`)}return e}toMarkdown(e){let n="";for(let r=0;ri.range.start.line&&(n+=` +`)}return n}}class gp{constructor(e,n){this.text=e,this.range=n}toString(){return this.text}toMarkdown(){return this.text}}function wf(t){return t.endsWith(` +`)?` +`:` + +`}class XC{constructor(e){this.indexManager=e.shared.workspace.IndexManager,this.commentProvider=e.documentation.CommentProvider}getDocumentation(e){const n=this.commentProvider.getComment(e);if(n&&DC(n))return MC(n).toMarkdown({renderLink:(i,s)=>this.documentationLinkRenderer(e,i,s),renderTag:i=>this.documentationTagRenderer(e,i)})}documentationLinkRenderer(e,n,r){var i;const s=(i=this.findNameInPrecomputedScopes(e,n))!==null&&i!==void 0?i:this.findNameInGlobalScope(e,n);if(s&&s.nameSegment){const a=s.nameSegment.range.start.line+1,o=s.nameSegment.range.start.character+1,l=s.documentUri.with({fragment:`L${a},${o}`});return`[${r}](${l.toString()})`}else return}documentationTagRenderer(e,n){}findNameInPrecomputedScopes(e,n){const i=At(e).precomputedScopes;if(!i)return;let s=e;do{const o=i.get(s).find(l=>l.name===n);if(o)return o;s=s.$container}while(s)}findNameInGlobalScope(e,n){return this.indexManager.allElements().find(i=>i.name===n)}}class JC{constructor(e){this.grammarConfig=()=>e.parser.GrammarConfig}getComment(e){var n;return TC(e)?e.$comment:(n=Qp(e.$cstNode,this.grammarConfig().multilineCommentRules))===null||n===void 0?void 0:n.text}}class ZC{constructor(e){this.syncParser=e.parser.LangiumParser}parse(e,n){return Promise.resolve(this.syncParser.parse(e))}}class QC{constructor(){this.previousTokenSource=new wl,this.writeQueue=[],this.readQueue=[],this.done=!0}write(e){this.cancelWrite();const n=iC();return this.previousTokenSource=n,this.enqueue(this.writeQueue,e,n.token)}read(e){return this.enqueue(this.readQueue,e)}enqueue(e,n,r=z.None){const i=new Cl,s={action:n,deferred:i,cancellationToken:r};return e.push(s),this.performNextOperation(),i.promise}async performNextOperation(){if(!this.done)return;const e=[];if(this.writeQueue.length>0)e.push(this.writeQueue.shift());else if(this.readQueue.length>0)e.push(...this.readQueue.splice(0,this.readQueue.length));else return;this.done=!1,await Promise.all(e.map(async({action:n,deferred:r,cancellationToken:i})=>{try{const s=await Promise.resolve().then(()=>n(i));r.resolve(s)}catch(s){ta(s)?r.resolve(void 0):r.reject(s)}})),this.done=!0,this.performNextOperation()}cancelWrite(){this.previousTokenSource.cancel()}}class ek{constructor(e){this.grammarElementIdMap=new Af,this.tokenTypeIdMap=new Af,this.grammar=e.Grammar,this.lexer=e.parser.Lexer,this.linker=e.references.Linker}dehydrate(e){return{lexerErrors:e.lexerErrors,lexerReport:e.lexerReport?this.dehydrateLexerReport(e.lexerReport):void 0,parserErrors:e.parserErrors.map(n=>Object.assign(Object.assign({},n),{message:n.message})),value:this.dehydrateAstNode(e.value,this.createDehyrationContext(e.value))}}dehydrateLexerReport(e){return e}createDehyrationContext(e){const n=new Map,r=new Map;for(const i of dn(e))n.set(i,{});if(e.$cstNode)for(const i of Ba(e.$cstNode))r.set(i,{});return{astNodes:n,cstNodes:r}}dehydrateAstNode(e,n){const r=n.astNodes.get(e);r.$type=e.$type,r.$containerIndex=e.$containerIndex,r.$containerProperty=e.$containerProperty,e.$cstNode!==void 0&&(r.$cstNode=this.dehydrateCstNode(e.$cstNode,n));for(const[i,s]of Object.entries(e))if(!i.startsWith("$"))if(Array.isArray(s)){const a=[];r[i]=a;for(const o of s)ue(o)?a.push(this.dehydrateAstNode(o,n)):ze(o)?a.push(this.dehydrateReference(o,n)):a.push(o)}else ue(s)?r[i]=this.dehydrateAstNode(s,n):ze(s)?r[i]=this.dehydrateReference(s,n):s!==void 0&&(r[i]=s);return r}dehydrateReference(e,n){const r={};return r.$refText=e.$refText,e.$refNode&&(r.$refNode=n.cstNodes.get(e.$refNode)),r}dehydrateCstNode(e,n){const r=n.cstNodes.get(e);return jf(e)?r.fullText=e.fullText:r.grammarSource=this.getGrammarElementId(e.grammarSource),r.hidden=e.hidden,r.astNode=n.astNodes.get(e.astNode),Lr(e)?r.content=e.content.map(i=>this.dehydrateCstNode(i,n)):Bf(e)&&(r.tokenType=e.tokenType.name,r.offset=e.offset,r.length=e.length,r.startLine=e.range.start.line,r.startColumn=e.range.start.character,r.endLine=e.range.end.line,r.endColumn=e.range.end.character),r}hydrate(e){const n=e.value,r=this.createHydrationContext(n);return"$cstNode"in n&&this.hydrateCstNode(n.$cstNode,r),{lexerErrors:e.lexerErrors,lexerReport:e.lexerReport,parserErrors:e.parserErrors,value:this.hydrateAstNode(n,r)}}createHydrationContext(e){const n=new Map,r=new Map;for(const s of dn(e))n.set(s,{});let i;if(e.$cstNode)for(const s of Ba(e.$cstNode)){let a;"fullText"in s?(a=new qh(s.fullText),i=a):"content"in s?a=new _l:"tokenType"in s&&(a=this.hydrateCstLeafNode(s)),a&&(r.set(s,a),a.root=i)}return{astNodes:n,cstNodes:r}}hydrateAstNode(e,n){const r=n.astNodes.get(e);r.$type=e.$type,r.$containerIndex=e.$containerIndex,r.$containerProperty=e.$containerProperty,e.$cstNode&&(r.$cstNode=n.cstNodes.get(e.$cstNode));for(const[i,s]of Object.entries(e))if(!i.startsWith("$"))if(Array.isArray(s)){const a=[];r[i]=a;for(const o of s)ue(o)?a.push(this.setParent(this.hydrateAstNode(o,n),r)):ze(o)?a.push(this.hydrateReference(o,r,i,n)):a.push(o)}else ue(s)?r[i]=this.setParent(this.hydrateAstNode(s,n),r):ze(s)?r[i]=this.hydrateReference(s,r,i,n):s!==void 0&&(r[i]=s);return r}setParent(e,n){return e.$container=n,e}hydrateReference(e,n,r,i){return this.linker.buildReference(n,r,i.cstNodes.get(e.$refNode),e.$refText)}hydrateCstNode(e,n,r=0){const i=n.cstNodes.get(e);if(typeof e.grammarSource=="number"&&(i.grammarSource=this.getGrammarElement(e.grammarSource)),i.astNode=n.astNodes.get(e.astNode),Lr(i))for(const s of e.content){const a=this.hydrateCstNode(s,n,r++);i.content.push(a)}return i}hydrateCstLeafNode(e){const n=this.getTokenType(e.tokenType),r=e.offset,i=e.length,s=e.startLine,a=e.startColumn,o=e.endLine,l=e.endColumn,u=e.hidden;return new _o(r,i,{start:{line:s,character:a},end:{line:o,character:l}},n,u)}getTokenType(e){return this.lexer.definition[e]}getGrammarElementId(e){if(e)return this.grammarElementIdMap.size===0&&this.createGrammarElementIdMap(),this.grammarElementIdMap.get(e)}getGrammarElement(e){return this.grammarElementIdMap.size===0&&this.createGrammarElementIdMap(),this.grammarElementIdMap.getKey(e)}createGrammarElementIdMap(){let e=0;for(const n of dn(this.grammar))tm(n)&&this.grammarElementIdMap.set(n,e++)}}function Lt(t){return{documentation:{CommentProvider:e=>new JC(e),DocumentationProvider:e=>new XC(e)},parser:{AsyncParser:e=>new ZC(e),GrammarConfig:e=>zm(e),LangiumParser:e=>Kw(e),CompletionParser:e=>jw(e),ValueConverter:()=>new rp,TokenBuilder:()=>new np,Lexer:e=>new PC(e),ParserErrorMessageProvider:()=>new Jh,LexerErrorMessageProvider:()=>new bC},workspace:{AstNodeLocator:()=>new IC,AstNodeDescriptionProvider:e=>new xC(e),ReferenceDescriptionProvider:e=>new _C(e)},references:{Linker:e=>new lC(e),NameProvider:()=>new cC,ScopeProvider:e=>new yC(e),ScopeComputation:e=>new dC(e),References:e=>new fC(e)},serializer:{Hydrator:e=>new ek(e),JsonSerializer:e=>new vC(e)},validation:{DocumentValidator:e=>new AC(e),ValidationRegistry:e=>new RC(e)},shared:()=>t.shared}}function Mt(t){return{ServiceRegistry:e=>new $C(e),workspace:{LangiumDocuments:e=>new oC(e),LangiumDocumentFactory:e=>new aC(e),DocumentBuilder:e=>new CC(e),IndexManager:e=>new kC(e),WorkspaceManager:e=>new NC(e),FileSystemProvider:e=>t.fileSystemProvider(e),WorkspaceLock:()=>new QC,ConfigurationProvider:e=>new wC(e)}}}var Cf;(function(t){t.merge=(e,n)=>vs(vs({},e),n)})(Cf||(Cf={}));function ce(t,e,n,r,i,s,a,o,l){const u=[t,e,n,r,i,s,a,o,l].reduce(vs,{});return yp(u)}const tk=Symbol("isProxy");function yp(t,e){const n=new Proxy({},{deleteProperty:()=>!1,set:()=>{throw new Error("Cannot set property on injected service container")},get:(r,i)=>i===tk?!0:Nf(r,i,t,e||n),getOwnPropertyDescriptor:(r,i)=>(Nf(r,i,t,e||n),Object.getOwnPropertyDescriptor(r,i)),has:(r,i)=>i in t,ownKeys:()=>[...Object.getOwnPropertyNames(t)]});return n}const kf=Symbol();function Nf(t,e,n,r){if(e in t){if(t[e]instanceof Error)throw new Error("Construction failure. Please make sure that your dependencies are constructable.",{cause:t[e]});if(t[e]===kf)throw new Error('Cycle detected. Please make "'+String(e)+'" lazy. Visit https://langium.org/docs/reference/configuration-services/#resolving-cyclic-dependencies');return t[e]}else if(e in n){const i=n[e];t[e]=kf;try{t[e]=typeof i=="function"?i(r):yp(i,r)}catch(s){throw t[e]=s instanceof Error?s:void 0,s}return t[e]}else return}function vs(t,e){if(e){for(const[n,r]of Object.entries(e))if(r!==void 0){const i=t[n];i!==null&&r!==null&&typeof i=="object"&&typeof r=="object"?t[n]=vs(i,r):t[n]=r}}return t}class nk{readFile(){throw new Error("No file system is available.")}async readDirectory(){return[]}}const Dt={fileSystemProvider:()=>new nk},rk={Grammar:()=>{},LanguageMetaData:()=>({caseInsensitive:!1,fileExtensions:[".langium"],languageId:"langium"})},ik={AstReflection:()=>new Yf};function sk(){const t=ce(Mt(Dt),ik),e=ce(Lt({shared:t}),rk);return t.ServiceRegistry.register(e),e}function an(t){var e;const n=sk(),r=n.serializer.JsonSerializer.deserialize(t);return n.shared.workspace.LangiumDocumentFactory.fromModel(r,Xt.parse(`memory://${(e=r.name)!==null&&e!==void 0?e:"grammar"}.langium`)),r}var ak=Object.defineProperty,A=(t,e)=>ak(t,"name",{value:e,configurable:!0}),bf="Statement",Di="Architecture";function ok(t){return Ke.isInstance(t,Di)}A(ok,"isArchitecture");var Ri="Axis",wr="Branch";function lk(t){return Ke.isInstance(t,wr)}A(lk,"isBranch");var Ai="Checkout",Ei="CherryPicking",ka="ClassDefStatement",Cr="Commit";function uk(t){return Ke.isInstance(t,Cr)}A(uk,"isCommit");var Na="Curve",ba="Edge",Oa="Entry",kr="GitGraph";function ck(t){return Ke.isInstance(t,kr)}A(ck,"isGitGraph");var Pa="Group",Fi="Info";function fk(t){return Ke.isInstance(t,Fi)}A(fk,"isInfo");var Si="Item",La="Junction",Nr="Merge";function dk(t){return Ke.isInstance(t,Nr)}A(dk,"isMerge");var Ma="Option",Gi="Packet";function hk(t){return Ke.isInstance(t,Gi)}A(hk,"isPacket");var Ui="PacketBlock";function pk(t){return Ke.isInstance(t,Ui)}A(pk,"isPacketBlock");var Bi="Pie";function mk(t){return Ke.isInstance(t,Bi)}A(mk,"isPie");var ji="PieSection";function gk(t){return Ke.isInstance(t,ji)}A(gk,"isPieSection");var Da="Radar",Fa="Service",Ki="Treemap";function yk(t){return Ke.isInstance(t,Ki)}A(yk,"isTreemap");var Ga="TreemapRow",xi="Direction",_i="Leaf",Ii="Section",mn,Tp=(mn=class extends Uf{getAllTypes(){return[Di,Ri,wr,Ai,Ei,ka,Cr,Na,xi,ba,Oa,kr,Pa,Fi,Si,La,_i,Nr,Ma,Gi,Ui,Bi,ji,Da,Ii,Fa,bf,Ki,Ga]}computeIsSubtype(e,n){switch(e){case wr:case Ai:case Ei:case Cr:case Nr:return this.isSubtype(bf,n);case xi:return this.isSubtype(kr,n);case _i:case Ii:return this.isSubtype(Si,n);default:return!1}}getReferenceType(e){const n=`${e.container.$type}:${e.property}`;switch(n){case"Entry:axis":return Ri;default:throw new Error(`${n} is not a valid reference id.`)}}getTypeMetaData(e){switch(e){case Di:return{name:Di,properties:[{name:"accDescr"},{name:"accTitle"},{name:"edges",defaultValue:[]},{name:"groups",defaultValue:[]},{name:"junctions",defaultValue:[]},{name:"services",defaultValue:[]},{name:"title"}]};case Ri:return{name:Ri,properties:[{name:"label"},{name:"name"}]};case wr:return{name:wr,properties:[{name:"name"},{name:"order"}]};case Ai:return{name:Ai,properties:[{name:"branch"}]};case Ei:return{name:Ei,properties:[{name:"id"},{name:"parent"},{name:"tags",defaultValue:[]}]};case ka:return{name:ka,properties:[{name:"className"},{name:"styleText"}]};case Cr:return{name:Cr,properties:[{name:"id"},{name:"message"},{name:"tags",defaultValue:[]},{name:"type"}]};case Na:return{name:Na,properties:[{name:"entries",defaultValue:[]},{name:"label"},{name:"name"}]};case ba:return{name:ba,properties:[{name:"lhsDir"},{name:"lhsGroup",defaultValue:!1},{name:"lhsId"},{name:"lhsInto",defaultValue:!1},{name:"rhsDir"},{name:"rhsGroup",defaultValue:!1},{name:"rhsId"},{name:"rhsInto",defaultValue:!1},{name:"title"}]};case Oa:return{name:Oa,properties:[{name:"axis"},{name:"value"}]};case kr:return{name:kr,properties:[{name:"accDescr"},{name:"accTitle"},{name:"statements",defaultValue:[]},{name:"title"}]};case Pa:return{name:Pa,properties:[{name:"icon"},{name:"id"},{name:"in"},{name:"title"}]};case Fi:return{name:Fi,properties:[{name:"accDescr"},{name:"accTitle"},{name:"title"}]};case Si:return{name:Si,properties:[{name:"classSelector"},{name:"name"}]};case La:return{name:La,properties:[{name:"id"},{name:"in"}]};case Nr:return{name:Nr,properties:[{name:"branch"},{name:"id"},{name:"tags",defaultValue:[]},{name:"type"}]};case Ma:return{name:Ma,properties:[{name:"name"},{name:"value",defaultValue:!1}]};case Gi:return{name:Gi,properties:[{name:"accDescr"},{name:"accTitle"},{name:"blocks",defaultValue:[]},{name:"title"}]};case Ui:return{name:Ui,properties:[{name:"bits"},{name:"end"},{name:"label"},{name:"start"}]};case Bi:return{name:Bi,properties:[{name:"accDescr"},{name:"accTitle"},{name:"sections",defaultValue:[]},{name:"showData",defaultValue:!1},{name:"title"}]};case ji:return{name:ji,properties:[{name:"label"},{name:"value"}]};case Da:return{name:Da,properties:[{name:"accDescr"},{name:"accTitle"},{name:"axes",defaultValue:[]},{name:"curves",defaultValue:[]},{name:"options",defaultValue:[]},{name:"title"}]};case Fa:return{name:Fa,properties:[{name:"icon"},{name:"iconText"},{name:"id"},{name:"in"},{name:"title"}]};case Ki:return{name:Ki,properties:[{name:"accDescr"},{name:"accTitle"},{name:"title"},{name:"TreemapRows",defaultValue:[]}]};case Ga:return{name:Ga,properties:[{name:"indent"},{name:"item"}]};case xi:return{name:xi,properties:[{name:"accDescr"},{name:"accTitle"},{name:"dir"},{name:"statements",defaultValue:[]},{name:"title"}]};case _i:return{name:_i,properties:[{name:"classSelector"},{name:"name"},{name:"value"}]};case Ii:return{name:Ii,properties:[{name:"classSelector"},{name:"name"}]};default:return{name:e,properties:[]}}}},A(mn,"MermaidAstReflection"),mn),Ke=new Tp,Of,Tk=A(()=>Of??(Of=an(`{"$type":"Grammar","isDeclared":true,"name":"Info","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"Info","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"info"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[],"cardinality":"*"},{"$type":"Group","elements":[{"$type":"Keyword","value":"showInfo"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[],"cardinality":"*"}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[],"cardinality":"?"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@7"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@8"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[],"types":[],"usedGrammars":[]}`)),"InfoGrammar"),Pf,vk=A(()=>Pf??(Pf=an(`{"$type":"Grammar","isDeclared":true,"name":"Packet","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"Packet","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"packet"},{"$type":"Keyword","value":"packet-beta"}]},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]},{"$type":"Assignment","feature":"blocks","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"PacketBlock","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Assignment","feature":"start","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"-"},{"$type":"Assignment","feature":"end","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}],"cardinality":"?"}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"+"},{"$type":"Assignment","feature":"bits","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}]}]},{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"label","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@8"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@9"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[],"types":[],"usedGrammars":[]}`)),"PacketGrammar"),Lf,$k=A(()=>Lf??(Lf=an(`{"$type":"Grammar","isDeclared":true,"name":"Pie","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"Pie","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"pie"},{"$type":"Assignment","feature":"showData","operator":"?=","terminal":{"$type":"Keyword","value":"showData"},"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]},{"$type":"Assignment","feature":"sections","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[]}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"PieSection","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"label","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]}},{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"FLOAT_PIE","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/-?[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT_PIE","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/-?(0|[1-9][0-9]*)(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER_PIE","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@2"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@3"}}]},"fragment":false,"hidden":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@11"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@12"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[],"types":[],"usedGrammars":[]}`)),"PieGrammar"),Mf,Rk=A(()=>Mf??(Mf=an(`{"$type":"Grammar","isDeclared":true,"name":"Architecture","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"Architecture","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@23"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"architecture-beta"},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@23"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"Statement","definition":{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"groups","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Assignment","feature":"services","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}},{"$type":"Assignment","feature":"junctions","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}},{"$type":"Assignment","feature":"edges","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"LeftPort","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"lhsDir","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"RightPort","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"rhsDir","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}},{"$type":"Keyword","value":":"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"Arrow","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]},{"$type":"Assignment","feature":"lhsInto","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]},"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"--"},{"$type":"Group","elements":[{"$type":"Keyword","value":"-"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@29"},"arguments":[]}},{"$type":"Keyword","value":"-"}]}]},{"$type":"Assignment","feature":"rhsInto","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Group","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"group"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Assignment","feature":"icon","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@28"},"arguments":[]},"cardinality":"?"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@29"},"arguments":[]},"cardinality":"?"},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Service","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"service"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"iconText","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@21"},"arguments":[]}},{"$type":"Assignment","feature":"icon","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@28"},"arguments":[]}}],"cardinality":"?"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@29"},"arguments":[]},"cardinality":"?"},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Junction","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"junction"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Edge","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"lhsId","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Assignment","feature":"lhsGroup","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"Assignment","feature":"rhsId","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Assignment","feature":"rhsGroup","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"ARROW_DIRECTION","definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"L"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"R"}}]},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"T"}}]},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"B"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARROW_GROUP","definition":{"$type":"RegexToken","regex":"/\\\\{group\\\\}/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARROW_INTO","definition":{"$type":"RegexToken","regex":"/<|>/"},"fragment":false,"hidden":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@23"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@18"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@19"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false},{"$type":"TerminalRule","name":"ARCH_ICON","definition":{"$type":"RegexToken","regex":"/\\\\([\\\\w-:]+\\\\)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARCH_TITLE","definition":{"$type":"RegexToken","regex":"/\\\\[[\\\\w ]+\\\\]/"},"fragment":false,"hidden":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[],"types":[],"usedGrammars":[]}`)),"ArchitectureGrammar"),Df,Ak=A(()=>Df??(Df=an(`{"$type":"Grammar","isDeclared":true,"name":"GitGraph","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"GitGraph","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"Group","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"Keyword","value":":"}]},{"$type":"Keyword","value":"gitGraph:"},{"$type":"Group","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]},{"$type":"Keyword","value":":"}]}]},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]},{"$type":"Assignment","feature":"statements","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Statement","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Direction","definition":{"$type":"Assignment","feature":"dir","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"LR"},{"$type":"Keyword","value":"TB"},{"$type":"Keyword","value":"BT"}]}},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Commit","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"commit"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"msg:","cardinality":"?"},{"$type":"Assignment","feature":"message","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"type:"},{"$type":"Assignment","feature":"type","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"NORMAL"},{"$type":"Keyword","value":"REVERSE"},{"$type":"Keyword","value":"HIGHLIGHT"}]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Branch","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"branch"},{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@24"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"order:"},{"$type":"Assignment","feature":"order","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Merge","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"merge"},{"$type":"Assignment","feature":"branch","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@24"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]}},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"type:"},{"$type":"Assignment","feature":"type","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"NORMAL"},{"$type":"Keyword","value":"REVERSE"},{"$type":"Keyword","value":"HIGHLIGHT"}]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Checkout","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"checkout"},{"$type":"Keyword","value":"switch"}]},{"$type":"Assignment","feature":"branch","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@24"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"CherryPicking","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"cherry-pick"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"parent:"},{"$type":"Assignment","feature":"parent","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@14"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@15"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false},{"$type":"TerminalRule","name":"REFERENCE","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\\\w([-\\\\./\\\\w]*[-\\\\w])?/"},"fragment":false,"hidden":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[],"types":[],"usedGrammars":[]}`)),"GitGraphGrammar"),Ff,Ek=A(()=>Ff??(Ff=an(`{"$type":"Grammar","isDeclared":true,"name":"Radar","imports":[],"rules":[{"$type":"ParserRule","entry":true,"name":"Radar","definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"radar-beta"},{"$type":"Keyword","value":"radar-beta:"},{"$type":"Group","elements":[{"$type":"Keyword","value":"radar-beta"},{"$type":"Keyword","value":":"}]}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]},{"$type":"Group","elements":[{"$type":"Keyword","value":"axis"},{"$type":"Assignment","feature":"axes","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":","},{"$type":"Assignment","feature":"axes","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}}],"cardinality":"*"}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"curve"},{"$type":"Assignment","feature":"curves","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":","},{"$type":"Assignment","feature":"curves","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}}],"cardinality":"*"}]},{"$type":"Group","elements":[{"$type":"Assignment","feature":"options","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":","},{"$type":"Assignment","feature":"options","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}}],"cardinality":"*"}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"Label","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"["},{"$type":"Assignment","feature":"label","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[]}},{"$type":"Keyword","value":"]"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Axis","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[],"cardinality":"?"}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Curve","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[],"cardinality":"?"},{"$type":"Keyword","value":"{"},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"Keyword","value":"}"}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"Entries","definition":{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Assignment","feature":"entries","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":","},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Assignment","feature":"entries","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"}]},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Assignment","feature":"entries","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":","},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"},{"$type":"Assignment","feature":"entries","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"*"}]}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"DetailedEntry","returnType":{"$ref":"#/interfaces@0"},"definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"axis","operator":"=","terminal":{"$type":"CrossReference","type":{"$ref":"#/rules@2"},"terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},"deprecatedSyntax":false}},{"$type":"Keyword","value":":","cardinality":"?"},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"NumberEntry","returnType":{"$ref":"#/interfaces@0"},"definition":{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Option","definition":{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Keyword","value":"showLegend"}},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Keyword","value":"ticks"}},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Keyword","value":"max"}},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Keyword","value":"min"}},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Keyword","value":"graticule"}},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}}]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"GRATICULE","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"circle"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"polygon"}}]},"fragment":false,"hidden":false},{"$type":"ParserRule","fragment":true,"name":"EOL","dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"FLOAT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+\\\\.[0-9]+(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*(?!\\\\.)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@15"}},{"$type":"TerminalRuleCall","rule":{"$ref":"#/rules@16"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\"([^\\"\\\\\\\\]|\\\\\\\\.)*\\"|'([^'\\\\\\\\]|\\\\\\\\.)*'/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/[\\\\w]([-\\\\w]*\\\\w)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"interfaces":[{"$type":"Interface","name":"Entry","attributes":[{"$type":"TypeAttribute","name":"axis","isOptional":true,"type":{"$type":"ReferenceType","referenceType":{"$type":"SimpleType","typeRef":{"$ref":"#/rules@2"}}}},{"$type":"TypeAttribute","name":"value","type":{"$type":"SimpleType","primitiveType":"number"},"isOptional":false}],"superTypes":[]}],"definesHiddenTokens":false,"hiddenTokens":[],"types":[],"usedGrammars":[]}`)),"RadarGrammar"),Gf,Sk=A(()=>Gf??(Gf=an(`{"$type":"Grammar","isDeclared":true,"name":"Treemap","rules":[{"$type":"ParserRule","fragment":true,"name":"TitleAndAccessibilities","definition":{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]}}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"BOOLEAN","type":{"$type":"ReturnType","name":"boolean"},"definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"true"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"false"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"ParserRule","entry":true,"name":"Treemap","returnType":{"$ref":"#/interfaces@4"},"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@0"},"arguments":[]},{"$type":"Assignment","feature":"TreemapRows","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]}}],"cardinality":"*"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"TREEMAP_KEYWORD","definition":{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"treemap-beta"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"treemap"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"CLASS_DEF","definition":{"$type":"RegexToken","regex":"/classDef\\\\s+([a-zA-Z_][a-zA-Z0-9_]+)(?:\\\\s+([^;\\\\r\\\\n]*))?(?:;)?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STYLE_SEPARATOR","definition":{"$type":"CharacterRange","left":{"$type":"Keyword","value":":::"}},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"SEPARATOR","definition":{"$type":"CharacterRange","left":{"$type":"Keyword","value":":"}},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"COMMA","definition":{"$type":"CharacterRange","left":{"$type":"Keyword","value":","}},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WS","definition":{"$type":"RegexToken","regex":"/[ \\\\t]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"ML_COMMENT","definition":{"$type":"RegexToken","regex":"/\\\\%\\\\%[^\\\\n]*/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"NL","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false},{"$type":"ParserRule","name":"TreemapRow","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"indent","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"item","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]}]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"ClassDef","dataType":"string","definition":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Item","returnType":{"$ref":"#/interfaces@0"},"definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Section","returnType":{"$ref":"#/interfaces@1"},"definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@23"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]},{"$type":"Assignment","feature":"classSelector","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}],"cardinality":"?"}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Leaf","returnType":{"$ref":"#/interfaces@2"},"definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@23"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[],"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[],"cardinality":"?"},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@22"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]},{"$type":"Assignment","feature":"classSelector","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}],"cardinality":"?"}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"INDENTATION","definition":{"$type":"RegexToken","regex":"/[ \\\\t]{1,}/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID2","definition":{"$type":"RegexToken","regex":"/[a-zA-Z_][a-zA-Z0-9_]*/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"NUMBER2","definition":{"$type":"RegexToken","regex":"/[0-9_\\\\.\\\\,]+/"},"fragment":false,"hidden":false},{"$type":"ParserRule","name":"MyNumber","dataType":"number","definition":{"$type":"RuleCall","rule":{"$ref":"#/rules@21"},"arguments":[]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"STRING2","definition":{"$type":"RegexToken","regex":"/\\"[^\\"]*\\"|'[^']*'/"},"fragment":false,"hidden":false}],"interfaces":[{"$type":"Interface","name":"Item","attributes":[{"$type":"TypeAttribute","name":"name","type":{"$type":"SimpleType","primitiveType":"string"},"isOptional":false},{"$type":"TypeAttribute","name":"classSelector","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]},{"$type":"Interface","name":"Section","superTypes":[{"$ref":"#/interfaces@0"}],"attributes":[]},{"$type":"Interface","name":"Leaf","superTypes":[{"$ref":"#/interfaces@0"}],"attributes":[{"$type":"TypeAttribute","name":"value","type":{"$type":"SimpleType","primitiveType":"number"},"isOptional":false}]},{"$type":"Interface","name":"ClassDefStatement","attributes":[{"$type":"TypeAttribute","name":"className","type":{"$type":"SimpleType","primitiveType":"string"},"isOptional":false},{"$type":"TypeAttribute","name":"styleText","type":{"$type":"SimpleType","primitiveType":"string"},"isOptional":false}],"superTypes":[]},{"$type":"Interface","name":"Treemap","attributes":[{"$type":"TypeAttribute","name":"TreemapRows","type":{"$type":"ArrayType","elementType":{"$type":"SimpleType","typeRef":{"$ref":"#/rules@14"}}},"isOptional":false},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"definesHiddenTokens":false,"hiddenTokens":[],"imports":[],"types":[],"usedGrammars":[],"$comment":"/**\\n * Treemap grammar for Langium\\n * Converted from mindmap grammar\\n *\\n * The ML_COMMENT and NL hidden terminals handle whitespace, comments, and newlines\\n * before the treemap keyword, allowing for empty lines and comments before the\\n * treemap declaration.\\n */"}`)),"TreemapGrammar"),xk={languageId:"info",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},_k={languageId:"packet",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},Ik={languageId:"pie",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},wk={languageId:"architecture",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},Ck={languageId:"gitGraph",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},kk={languageId:"radar",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},Nk={languageId:"treemap",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1,mode:"production"},on={AstReflection:A(()=>new Tp,"AstReflection")},bk={Grammar:A(()=>Tk(),"Grammar"),LanguageMetaData:A(()=>xk,"LanguageMetaData"),parser:{}},Ok={Grammar:A(()=>vk(),"Grammar"),LanguageMetaData:A(()=>_k,"LanguageMetaData"),parser:{}},Pk={Grammar:A(()=>$k(),"Grammar"),LanguageMetaData:A(()=>Ik,"LanguageMetaData"),parser:{}},Lk={Grammar:A(()=>Rk(),"Grammar"),LanguageMetaData:A(()=>wk,"LanguageMetaData"),parser:{}},Mk={Grammar:A(()=>Ak(),"Grammar"),LanguageMetaData:A(()=>Ck,"LanguageMetaData"),parser:{}},Dk={Grammar:A(()=>Ek(),"Grammar"),LanguageMetaData:A(()=>kk,"LanguageMetaData"),parser:{}},Fk={Grammar:A(()=>Sk(),"Grammar"),LanguageMetaData:A(()=>Nk,"LanguageMetaData"),parser:{}},Gk=/accDescr(?:[\t ]*:([^\n\r]*)|\s*{([^}]*)})/,Uk=/accTitle[\t ]*:([^\n\r]*)/,Bk=/title([\t ][^\n\r]*|)/,jk={ACC_DESCR:Gk,ACC_TITLE:Uk,TITLE:Bk},gn,na=(gn=class extends rp{runConverter(e,n,r){let i=this.runCommonConverter(e,n,r);return i===void 0&&(i=this.runCustomConverter(e,n,r)),i===void 0?super.runConverter(e,n,r):i}runCommonConverter(e,n,r){const i=jk[e.name];if(i===void 0)return;const s=i.exec(n);if(s!==null){if(s[1]!==void 0)return s[1].trim().replace(/[\t ]{2,}/gm," ");if(s[2]!==void 0)return s[2].replace(/^\s*/gm,"").replace(/\s+$/gm,"").replace(/[\t ]{2,}/gm," ").replace(/[\n\r]{2,}/gm,` +`)}}},A(gn,"AbstractMermaidValueConverter"),gn),yn,ra=(yn=class extends na{runCustomConverter(e,n,r){}},A(yn,"CommonValueConverter"),yn),Tn,Ft=(Tn=class extends np{constructor(e){super(),this.keywords=new Set(e)}buildKeywordTokens(e,n,r){const i=super.buildKeywordTokens(e,n,r);return i.forEach(s=>{this.keywords.has(s.name)&&s.PATTERN!==void 0&&(s.PATTERN=new RegExp(s.PATTERN.toString()+"(?:(?=%%)|(?!\\S))"))}),i}},A(Tn,"AbstractMermaidTokenBuilder"),Tn),vn;vn=class extends Ft{},A(vn,"CommonTokenBuilder");var $n,Kk=($n=class extends Ft{constructor(){super(["gitGraph"])}},A($n,"GitGraphTokenBuilder"),$n),vp={parser:{TokenBuilder:A(()=>new Kk,"TokenBuilder"),ValueConverter:A(()=>new ra,"ValueConverter")}};function $p(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Mk,vp);return e.ServiceRegistry.register(n),{shared:e,GitGraph:n}}A($p,"createGitGraphServices");var Rn,Hk=(Rn=class extends Ft{constructor(){super(["info","showInfo"])}},A(Rn,"InfoTokenBuilder"),Rn),Rp={parser:{TokenBuilder:A(()=>new Hk,"TokenBuilder"),ValueConverter:A(()=>new ra,"ValueConverter")}};function Ap(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),bk,Rp);return e.ServiceRegistry.register(n),{shared:e,Info:n}}A(Ap,"createInfoServices");var An,Wk=(An=class extends Ft{constructor(){super(["packet"])}},A(An,"PacketTokenBuilder"),An),Ep={parser:{TokenBuilder:A(()=>new Wk,"TokenBuilder"),ValueConverter:A(()=>new ra,"ValueConverter")}};function Sp(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Ok,Ep);return e.ServiceRegistry.register(n),{shared:e,Packet:n}}A(Sp,"createPacketServices");var En,zk=(En=class extends Ft{constructor(){super(["pie","showData"])}},A(En,"PieTokenBuilder"),En),Sn,Vk=(Sn=class extends na{runCustomConverter(e,n,r){if(e.name==="PIE_SECTION_LABEL")return n.replace(/"/g,"").trim()}},A(Sn,"PieValueConverter"),Sn),xp={parser:{TokenBuilder:A(()=>new zk,"TokenBuilder"),ValueConverter:A(()=>new Vk,"ValueConverter")}};function _p(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Pk,xp);return e.ServiceRegistry.register(n),{shared:e,Pie:n}}A(_p,"createPieServices");var xn,qk=(xn=class extends Ft{constructor(){super(["architecture"])}},A(xn,"ArchitectureTokenBuilder"),xn),_n,Yk=(_n=class extends na{runCustomConverter(e,n,r){if(e.name==="ARCH_ICON")return n.replace(/[()]/g,"").trim();if(e.name==="ARCH_TEXT_ICON")return n.replace(/["()]/g,"");if(e.name==="ARCH_TITLE")return n.replace(/[[\]]/g,"").trim()}},A(_n,"ArchitectureValueConverter"),_n),Ip={parser:{TokenBuilder:A(()=>new qk,"TokenBuilder"),ValueConverter:A(()=>new Yk,"ValueConverter")}};function wp(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Lk,Ip);return e.ServiceRegistry.register(n),{shared:e,Architecture:n}}A(wp,"createArchitectureServices");var In,Xk=(In=class extends Ft{constructor(){super(["radar-beta"])}},A(In,"RadarTokenBuilder"),In),Cp={parser:{TokenBuilder:A(()=>new Xk,"TokenBuilder"),ValueConverter:A(()=>new ra,"ValueConverter")}};function kp(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Dk,Cp);return e.ServiceRegistry.register(n),{shared:e,Radar:n}}A(kp,"createRadarServices");var wn,Jk=(wn=class extends Ft{constructor(){super(["treemap"])}},A(wn,"TreemapTokenBuilder"),wn),Zk=/classDef\s+([A-Z_a-z]\w+)(?:\s+([^\n\r;]*))?;?/,Cn,Qk=(Cn=class extends na{runCustomConverter(e,n,r){if(e.name==="NUMBER2")return parseFloat(n.replace(/,/g,""));if(e.name==="SEPARATOR")return n.substring(1,n.length-1);if(e.name==="STRING2")return n.substring(1,n.length-1);if(e.name==="INDENTATION")return n.length;if(e.name==="ClassDef"){if(typeof n!="string")return n;const i=Zk.exec(n);if(i)return{$type:"ClassDefStatement",className:i[1],styleText:i[2]||void 0}}}},A(Cn,"TreemapValueConverter"),Cn);function Np(t){const e=t.validation.TreemapValidator,n=t.validation.ValidationRegistry;if(n){const r={Treemap:e.checkSingleRoot.bind(e)};n.register(r,e)}}A(Np,"registerValidationChecks");var kn,eN=(kn=class{checkSingleRoot(e,n){let r;for(const i of e.TreemapRows)i.item&&(r===void 0&&i.indent===void 0?r=0:i.indent===void 0?n("error","Multiple root nodes are not allowed in a treemap.",{node:i,property:"item"}):r!==void 0&&r>=parseInt(i.indent,10)&&n("error","Multiple root nodes are not allowed in a treemap.",{node:i,property:"item"}))}},A(kn,"TreemapValidator"),kn),bp={parser:{TokenBuilder:A(()=>new Jk,"TokenBuilder"),ValueConverter:A(()=>new Qk,"ValueConverter")},validation:{TreemapValidator:A(()=>new eN,"TreemapValidator")}};function Op(t=Dt){const e=ce(Mt(t),on),n=ce(Lt({shared:e}),Fk,bp);return e.ServiceRegistry.register(n),Np(n),{shared:e,Treemap:n}}A(Op,"createTreemapServices");var it={},tN={info:A(async()=>{const{createInfoServices:t}=await Bt(async()=>{const{createInfoServices:n}=await Promise.resolve().then(()=>iN);return{createInfoServices:n}},void 0),e=t().Info.parser.LangiumParser;it.info=e},"info"),packet:A(async()=>{const{createPacketServices:t}=await Bt(async()=>{const{createPacketServices:n}=await Promise.resolve().then(()=>sN);return{createPacketServices:n}},void 0),e=t().Packet.parser.LangiumParser;it.packet=e},"packet"),pie:A(async()=>{const{createPieServices:t}=await Bt(async()=>{const{createPieServices:n}=await Promise.resolve().then(()=>aN);return{createPieServices:n}},void 0),e=t().Pie.parser.LangiumParser;it.pie=e},"pie"),architecture:A(async()=>{const{createArchitectureServices:t}=await Bt(async()=>{const{createArchitectureServices:n}=await Promise.resolve().then(()=>oN);return{createArchitectureServices:n}},void 0),e=t().Architecture.parser.LangiumParser;it.architecture=e},"architecture"),gitGraph:A(async()=>{const{createGitGraphServices:t}=await Bt(async()=>{const{createGitGraphServices:n}=await Promise.resolve().then(()=>lN);return{createGitGraphServices:n}},void 0),e=t().GitGraph.parser.LangiumParser;it.gitGraph=e},"gitGraph"),radar:A(async()=>{const{createRadarServices:t}=await Bt(async()=>{const{createRadarServices:n}=await Promise.resolve().then(()=>uN);return{createRadarServices:n}},void 0),e=t().Radar.parser.LangiumParser;it.radar=e},"radar"),treemap:A(async()=>{const{createTreemapServices:t}=await Bt(async()=>{const{createTreemapServices:n}=await Promise.resolve().then(()=>cN);return{createTreemapServices:n}},void 0),e=t().Treemap.parser.LangiumParser;it.treemap=e},"treemap")};async function nN(t,e){const n=tN[t];if(!n)throw new Error(`Unknown diagram type: ${t}`);it[t]||await n();const i=it[t].parse(e);if(i.lexerErrors.length>0||i.parserErrors.length>0)throw new rN(i);return i.value}A(nN,"parse");var Nn,rN=(Nn=class extends Error{constructor(e){const n=e.lexerErrors.map(i=>i.message).join(` +`),r=e.parserErrors.map(i=>i.message).join(` +`);super(`Parsing failed: ${n} ${r}`),this.result=e}},A(Nn,"MermaidParseError"),Nn);const iN=Object.freeze(Object.defineProperty({__proto__:null,InfoModule:Rp,createInfoServices:Ap},Symbol.toStringTag,{value:"Module"})),sN=Object.freeze(Object.defineProperty({__proto__:null,PacketModule:Ep,createPacketServices:Sp},Symbol.toStringTag,{value:"Module"})),aN=Object.freeze(Object.defineProperty({__proto__:null,PieModule:xp,createPieServices:_p},Symbol.toStringTag,{value:"Module"})),oN=Object.freeze(Object.defineProperty({__proto__:null,ArchitectureModule:Ip,createArchitectureServices:wp},Symbol.toStringTag,{value:"Module"})),lN=Object.freeze(Object.defineProperty({__proto__:null,GitGraphModule:vp,createGitGraphServices:$p},Symbol.toStringTag,{value:"Module"})),uN=Object.freeze(Object.defineProperty({__proto__:null,RadarModule:Cp,createRadarServices:kp},Symbol.toStringTag,{value:"Module"})),cN=Object.freeze(Object.defineProperty({__proto__:null,TreemapModule:bp,createTreemapServices:Op},Symbol.toStringTag,{value:"Module"}));export{nN as p}; diff --git a/assets/chunks/xychartDiagram-PRI3JC2R.CYHK3ubw.js b/assets/chunks/xychartDiagram-PRI3JC2R.CYHK3ubw.js new file mode 100644 index 000000000..b62aa752c --- /dev/null +++ b/assets/chunks/xychartDiagram-PRI3JC2R.CYHK3ubw.js @@ -0,0 +1,7 @@ +import{_ as n,s as gi,g as xi,t as Xt,q as di,a as pi,b as fi,l as Nt,K as yi,e as mi,z as bi,G as Ct,F as Yt,H as Ai,Q as wi,i as Ci,T as Bt,U as Si,S as Wt,V as zt}from"./theme.kqgpP4eL.js";import"./framework.CgT1UzWm.js";var mt=function(){var s=n(function(W,r,u,g){for(u=u||{},g=W.length;g--;u[W[g]]=r);return u},"o"),t=[1,10,12,14,16,18,19,21,23],i=[2,6],e=[1,3],a=[1,5],c=[1,6],d=[1,7],m=[1,5,10,12,14,16,18,19,21,23,34,35,36],b=[1,25],P=[1,26],I=[1,28],R=[1,29],L=[1,30],z=[1,31],F=[1,32],D=[1,33],V=[1,34],f=[1,35],C=[1,36],l=[1,37],M=[1,43],B=[1,42],U=[1,47],X=[1,50],h=[1,10,12,14,16,18,19,21,23,34,35,36],k=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],w=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],S=[1,64],$={trace:n(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:n(function(r,u,g,x,A,o,nt){var p=o.length-1;switch(A){case 5:x.setOrientation(o[p]);break;case 9:x.setDiagramTitle(o[p].text.trim());break;case 12:x.setLineData({text:"",type:"text"},o[p]);break;case 13:x.setLineData(o[p-1],o[p]);break;case 14:x.setBarData({text:"",type:"text"},o[p]);break;case 15:x.setBarData(o[p-1],o[p]);break;case 16:this.$=o[p].trim(),x.setAccTitle(this.$);break;case 17:case 18:this.$=o[p].trim(),x.setAccDescription(this.$);break;case 19:this.$=o[p-1];break;case 20:this.$=[Number(o[p-2]),...o[p]];break;case 21:this.$=[Number(o[p])];break;case 22:x.setXAxisTitle(o[p]);break;case 23:x.setXAxisTitle(o[p-1]);break;case 24:x.setXAxisTitle({type:"text",text:""});break;case 25:x.setXAxisBand(o[p]);break;case 26:x.setXAxisRangeData(Number(o[p-2]),Number(o[p]));break;case 27:this.$=o[p-1];break;case 28:this.$=[o[p-2],...o[p]];break;case 29:this.$=[o[p]];break;case 30:x.setYAxisTitle(o[p]);break;case 31:x.setYAxisTitle(o[p-1]);break;case 32:x.setYAxisTitle({type:"text",text:""});break;case 33:x.setYAxisRangeData(Number(o[p-2]),Number(o[p]));break;case 37:this.$={text:o[p],type:"text"};break;case 38:this.$={text:o[p],type:"text"};break;case 39:this.$={text:o[p],type:"markdown"};break;case 40:this.$=o[p];break;case 41:this.$=o[p-1]+""+o[p];break}},"anonymous"),table:[s(t,i,{3:1,4:2,7:4,5:e,34:a,35:c,36:d}),{1:[3]},s(t,i,{4:2,7:4,3:8,5:e,34:a,35:c,36:d}),s(t,i,{4:2,7:4,6:9,3:10,5:e,8:[1,11],34:a,35:c,36:d}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},s(m,[2,34]),s(m,[2,35]),s(m,[2,36]),{1:[2,1]},s(t,i,{4:2,7:4,3:21,5:e,34:a,35:c,36:d}),{1:[2,3]},s(m,[2,5]),s(t,[2,7],{4:22,34:a,35:c,36:d}),{11:23,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},{11:39,13:38,24:M,27:B,29:40,30:41,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},{11:45,15:44,27:U,33:46,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},{11:49,17:48,24:X,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},{11:52,17:51,24:X,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},{20:[1,53]},{22:[1,54]},s(h,[2,18]),{1:[2,2]},s(h,[2,8]),s(h,[2,9]),s(k,[2,37],{40:55,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l}),s(k,[2,38]),s(k,[2,39]),s(w,[2,40]),s(w,[2,42]),s(w,[2,43]),s(w,[2,44]),s(w,[2,45]),s(w,[2,46]),s(w,[2,47]),s(w,[2,48]),s(w,[2,49]),s(w,[2,50]),s(w,[2,51]),s(h,[2,10]),s(h,[2,22],{30:41,29:56,24:M,27:B}),s(h,[2,24]),s(h,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},s(h,[2,11]),s(h,[2,30],{33:60,27:U}),s(h,[2,32]),{31:[1,61]},s(h,[2,12]),{17:62,24:X},{25:63,27:S},s(h,[2,14]),{17:65,24:X},s(h,[2,16]),s(h,[2,17]),s(w,[2,41]),s(h,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},s(h,[2,31]),{27:[1,69]},s(h,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},s(h,[2,15]),s(h,[2,26]),s(h,[2,27]),{11:59,32:72,37:24,38:b,39:P,40:27,41:I,42:R,43:L,44:z,45:F,46:D,47:V,48:f,49:C,50:l},s(h,[2,33]),s(h,[2,19]),{25:73,27:S},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:n(function(r,u){if(u.recoverable)this.trace(r);else{var g=new Error(r);throw g.hash=u,g}},"parseError"),parse:n(function(r){var u=this,g=[0],x=[],A=[null],o=[],nt=this.table,p="",lt=0,Et=0,hi=2,It=1,li=o.slice.call(arguments,1),_=Object.create(this.lexer),Y={yy:{}};for(var dt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,dt)&&(Y.yy[dt]=this.yy[dt]);_.setInput(r,Y.yy),Y.yy.lexer=_,Y.yy.parser=this,typeof _.yylloc>"u"&&(_.yylloc={});var pt=_.yylloc;o.push(pt);var ci=_.options&&_.options.ranges;typeof Y.yy.parseError=="function"?this.parseError=Y.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ui(v){g.length=g.length-2*v,A.length=A.length-v,o.length=o.length-v}n(ui,"popStack");function Vt(){var v;return v=x.pop()||_.lex()||It,typeof v!="number"&&(v instanceof Array&&(x=v,v=x.pop()),v=u.symbols_[v]||v),v}n(Vt,"lex");for(var T,H,E,ft,q={},ct,O,Mt,ut;;){if(H=g[g.length-1],this.defaultActions[H]?E=this.defaultActions[H]:((T===null||typeof T>"u")&&(T=Vt()),E=nt[H]&&nt[H][T]),typeof E>"u"||!E.length||!E[0]){var yt="";ut=[];for(ct in nt[H])this.terminals_[ct]&&ct>hi&&ut.push("'"+this.terminals_[ct]+"'");_.showPosition?yt="Parse error on line "+(lt+1)+`: +`+_.showPosition()+` +Expecting `+ut.join(", ")+", got '"+(this.terminals_[T]||T)+"'":yt="Parse error on line "+(lt+1)+": Unexpected "+(T==It?"end of input":"'"+(this.terminals_[T]||T)+"'"),this.parseError(yt,{text:_.match,token:this.terminals_[T]||T,line:_.yylineno,loc:pt,expected:ut})}if(E[0]instanceof Array&&E.length>1)throw new Error("Parse Error: multiple actions possible at state: "+H+", token: "+T);switch(E[0]){case 1:g.push(T),A.push(_.yytext),o.push(_.yylloc),g.push(E[1]),T=null,Et=_.yyleng,p=_.yytext,lt=_.yylineno,pt=_.yylloc;break;case 2:if(O=this.productions_[E[1]][1],q.$=A[A.length-O],q._$={first_line:o[o.length-(O||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(O||1)].first_column,last_column:o[o.length-1].last_column},ci&&(q._$.range=[o[o.length-(O||1)].range[0],o[o.length-1].range[1]]),ft=this.performAction.apply(q,[p,Et,lt,Y.yy,E[1],A,o].concat(li)),typeof ft<"u")return ft;O&&(g=g.slice(0,-1*O*2),A=A.slice(0,-1*O),o=o.slice(0,-1*O)),g.push(this.productions_[E[1]][0]),A.push(q.$),o.push(q._$),Mt=nt[g[g.length-2]][g[g.length-1]],g.push(Mt);break;case 3:return!0}}return!0},"parse")},Lt=function(){var W={EOF:1,parseError:n(function(u,g){if(this.yy.parser)this.yy.parser.parseError(u,g);else throw new Error(u)},"parseError"),setInput:n(function(r,u){return this.yy=u||this.yy||{},this._input=r,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:n(function(){var r=this._input[0];this.yytext+=r,this.yyleng++,this.offset++,this.match+=r,this.matched+=r;var u=r.match(/(?:\r\n?|\n).*/g);return u?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),r},"input"),unput:n(function(r){var u=r.length,g=r.split(/(?:\r\n?|\n)/g);this._input=r+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-u),this.offset-=u;var x=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var A=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===x.length?this.yylloc.first_column:0)+x[x.length-g.length].length-g[0].length:this.yylloc.first_column-u},this.options.ranges&&(this.yylloc.range=[A[0],A[0]+this.yyleng-u]),this.yyleng=this.yytext.length,this},"unput"),more:n(function(){return this._more=!0,this},"more"),reject:n(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:n(function(r){this.unput(this.match.slice(r))},"less"),pastInput:n(function(){var r=this.matched.substr(0,this.matched.length-this.match.length);return(r.length>20?"...":"")+r.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:n(function(){var r=this.match;return r.length<20&&(r+=this._input.substr(0,20-r.length)),(r.substr(0,20)+(r.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:n(function(){var r=this.pastInput(),u=new Array(r.length+1).join("-");return r+this.upcomingInput()+` +`+u+"^"},"showPosition"),test_match:n(function(r,u){var g,x,A;if(this.options.backtrack_lexer&&(A={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(A.yylloc.range=this.yylloc.range.slice(0))),x=r[0].match(/(?:\r\n?|\n).*/g),x&&(this.yylineno+=x.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:x?x[x.length-1].length-x[x.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+r[0].length},this.yytext+=r[0],this.match+=r[0],this.matches=r,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(r[0].length),this.matched+=r[0],g=this.performAction.call(this,this.yy,this,u,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),g)return g;if(this._backtrack){for(var o in A)this[o]=A[o];return!1}return!1},"test_match"),next:n(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var r,u,g,x;this._more||(this.yytext="",this.match="");for(var A=this._currentRules(),o=0;ou[0].length)){if(u=g,x=o,this.options.backtrack_lexer){if(r=this.test_match(g,A[o]),r!==!1)return r;if(this._backtrack){u=!1;continue}else return!1}else if(!this.options.flex)break}return u?(r=this.test_match(u,A[x]),r!==!1?r:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:n(function(){var u=this.next();return u||this.lex()},"lex"),begin:n(function(u){this.conditionStack.push(u)},"begin"),popState:n(function(){var u=this.conditionStack.length-1;return u>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:n(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:n(function(u){return u=this.conditionStack.length-1-Math.abs(u||0),u>=0?this.conditionStack[u]:"INITIAL"},"topState"),pushState:n(function(u){this.begin(u)},"pushState"),stateStackSize:n(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:n(function(u,g,x,A){switch(x){case 0:break;case 1:break;case 2:return this.popState(),34;case 3:return this.popState(),34;case 4:return 34;case 5:break;case 6:return 10;case 7:return this.pushState("acc_title"),19;case 8:return this.popState(),"acc_title_value";case 9:return this.pushState("acc_descr"),21;case 10:return this.popState(),"acc_descr_value";case 11:this.pushState("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 5;case 15:return 5;case 16:return 8;case 17:return this.pushState("axis_data"),"X_AXIS";case 18:return this.pushState("axis_data"),"Y_AXIS";case 19:return this.pushState("axis_band_data"),24;case 20:return 31;case 21:return this.pushState("data"),16;case 22:return this.pushState("data"),18;case 23:return this.pushState("data_inner"),24;case 24:return 27;case 25:return this.popState(),26;case 26:this.popState();break;case 27:this.pushState("string");break;case 28:this.popState();break;case 29:return"STR";case 30:return 24;case 31:return 26;case 32:return 43;case 33:return"COLON";case 34:return 44;case 35:return 28;case 36:return 45;case 37:return 46;case 38:return 48;case 39:return 50;case 40:return 47;case 41:return 41;case 42:return 49;case 43:return 42;case 44:break;case 45:return 35;case 46:return 36}},"anonymous"),rules:[/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:(\r?\n))/i,/^(?:(\r?\n))/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:title\b)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:\{)/i,/^(?:[^\}]*)/i,/^(?:xychart-beta\b)/i,/^(?:xychart\b)/i,/^(?:(?:vertical|horizontal))/i,/^(?:x-axis\b)/i,/^(?:y-axis\b)/i,/^(?:\[)/i,/^(?:-->)/i,/^(?:line\b)/i,/^(?:bar\b)/i,/^(?:\[)/i,/^(?:[+-]?(?:\d+(?:\.\d+)?|\.\d+))/i,/^(?:\])/i,/^(?:(?:`\) \{ this\.pushState\(md_string\); \}\n\(\?:\(\?!`"\)\.\)\+ \{ return MD_STR; \}\n\(\?:`))/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:\[)/i,/^(?:\])/i,/^(?:[A-Za-z]+)/i,/^(?::)/i,/^(?:\+)/i,/^(?:,)/i,/^(?:=)/i,/^(?:\*)/i,/^(?:#)/i,/^(?:[\_])/i,/^(?:\.)/i,/^(?:&)/i,/^(?:-)/i,/^(?:[0-9]+)/i,/^(?:\s+)/i,/^(?:;)/i,/^(?:$)/i],conditions:{data_inner:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,18,21,22,24,25,26,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0},data:{rules:[0,1,3,4,5,6,7,9,11,14,15,16,17,18,21,22,23,26,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0},axis_band_data:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,18,21,22,25,26,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0},axis_data:{rules:[0,1,2,4,5,6,7,9,11,14,15,16,17,18,19,20,21,22,24,26,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0},acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},title:{rules:[],inclusive:!1},md_string:{rules:[],inclusive:!1},string:{rules:[28,29],inclusive:!1},INITIAL:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,18,21,22,26,27,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0}}};return W}();$.lexer=Lt;function N(){this.yy={}}return n(N,"Parser"),N.prototype=$,$.Parser=N,new N}();mt.parser=mt;var _i=mt;function bt(s){return s.type==="bar"}n(bt,"isBarPlot");function St(s){return s.type==="band"}n(St,"isBandAxisData");function G(s){return s.type==="linear"}n(G,"isLinearAxisData");var j,Ht=(j=class{constructor(t){this.parentGroup=t}getMaxDimension(t,i){if(!this.parentGroup)return{width:t.reduce((c,d)=>Math.max(d.length,c),0)*i,height:i};const e={width:0,height:0},a=this.parentGroup.append("g").attr("visibility","hidden").attr("font-size",i);for(const c of t){const d=Si(a,1,c),m=d?d.width:c.length*i,b=d?d.height:i;e.width=Math.max(e.width,m),e.height=Math.max(e.height,b)}return a.remove(),e}},n(j,"TextDimensionCalculatorWithFont"),j),Ft=.7,Ot=.2,Q,Ut=(Q=class{constructor(t,i,e,a){this.axisConfig=t,this.title=i,this.textDimensionCalculator=e,this.axisThemeConfig=a,this.boundingRect={x:0,y:0,width:0,height:0},this.axisPosition="left",this.showTitle=!1,this.showLabel=!1,this.showTick=!1,this.showAxisLine=!1,this.outerPadding=0,this.titleTextHeight=0,this.labelTextHeight=0,this.range=[0,10],this.boundingRect={x:0,y:0,width:0,height:0},this.axisPosition="left"}setRange(t){this.range=t,this.axisPosition==="left"||this.axisPosition==="right"?this.boundingRect.height=t[1]-t[0]:this.boundingRect.width=t[1]-t[0],this.recalculateScale()}getRange(){return[this.range[0]+this.outerPadding,this.range[1]-this.outerPadding]}setAxisPosition(t){this.axisPosition=t,this.setRange(this.range)}getTickDistance(){const t=this.getRange();return Math.abs(t[0]-t[1])/this.getTickValues().length}getAxisOuterPadding(){return this.outerPadding}getLabelDimension(){return this.textDimensionCalculator.getMaxDimension(this.getTickValues().map(t=>t.toString()),this.axisConfig.labelFontSize)}recalculateOuterPaddingToDrawBar(){Ft*this.getTickDistance()>this.outerPadding*2&&(this.outerPadding=Math.floor(Ft*this.getTickDistance()/2)),this.recalculateScale()}calculateSpaceIfDrawnHorizontally(t){let i=t.height;if(this.axisConfig.showAxisLine&&i>this.axisConfig.axisLineWidth&&(i-=this.axisConfig.axisLineWidth,this.showAxisLine=!0),this.axisConfig.showLabel){const e=this.getLabelDimension(),a=Ot*t.width;this.outerPadding=Math.min(e.width/2,a);const c=e.height+this.axisConfig.labelPadding*2;this.labelTextHeight=e.height,c<=i&&(i-=c,this.showLabel=!0)}if(this.axisConfig.showTick&&i>=this.axisConfig.tickLength&&(this.showTick=!0,i-=this.axisConfig.tickLength),this.axisConfig.showTitle&&this.title){const e=this.textDimensionCalculator.getMaxDimension([this.title],this.axisConfig.titleFontSize),a=e.height+this.axisConfig.titlePadding*2;this.titleTextHeight=e.height,a<=i&&(i-=a,this.showTitle=!0)}this.boundingRect.width=t.width,this.boundingRect.height=t.height-i}calculateSpaceIfDrawnVertical(t){let i=t.width;if(this.axisConfig.showAxisLine&&i>this.axisConfig.axisLineWidth&&(i-=this.axisConfig.axisLineWidth,this.showAxisLine=!0),this.axisConfig.showLabel){const e=this.getLabelDimension(),a=Ot*t.height;this.outerPadding=Math.min(e.height/2,a);const c=e.width+this.axisConfig.labelPadding*2;c<=i&&(i-=c,this.showLabel=!0)}if(this.axisConfig.showTick&&i>=this.axisConfig.tickLength&&(this.showTick=!0,i-=this.axisConfig.tickLength),this.axisConfig.showTitle&&this.title){const e=this.textDimensionCalculator.getMaxDimension([this.title],this.axisConfig.titleFontSize),a=e.height+this.axisConfig.titlePadding*2;this.titleTextHeight=e.height,a<=i&&(i-=a,this.showTitle=!0)}this.boundingRect.width=t.width-i,this.boundingRect.height=t.height}calculateSpace(t){return this.axisPosition==="left"||this.axisPosition==="right"?this.calculateSpaceIfDrawnVertical(t):this.calculateSpaceIfDrawnHorizontally(t),this.recalculateScale(),{width:this.boundingRect.width,height:this.boundingRect.height}}setBoundingBoxXY(t){this.boundingRect.x=t.x,this.boundingRect.y=t.y}getDrawableElementsForLeftAxis(){const t=[];if(this.showAxisLine){const i=this.boundingRect.x+this.boundingRect.width-this.axisConfig.axisLineWidth/2;t.push({type:"path",groupTexts:["left-axis","axisl-line"],data:[{path:`M ${i},${this.boundingRect.y} L ${i},${this.boundingRect.y+this.boundingRect.height} `,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&t.push({type:"text",groupTexts:["left-axis","label"],data:this.getTickValues().map(i=>({text:i.toString(),x:this.boundingRect.x+this.boundingRect.width-(this.showLabel?this.axisConfig.labelPadding:0)-(this.showTick?this.axisConfig.tickLength:0)-(this.showAxisLine?this.axisConfig.axisLineWidth:0),y:this.getScaleValue(i),fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"middle",horizontalPos:"right"}))}),this.showTick){const i=this.boundingRect.x+this.boundingRect.width-(this.showAxisLine?this.axisConfig.axisLineWidth:0);t.push({type:"path",groupTexts:["left-axis","ticks"],data:this.getTickValues().map(e=>({path:`M ${i},${this.getScaleValue(e)} L ${i-this.axisConfig.tickLength},${this.getScaleValue(e)}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&t.push({type:"text",groupTexts:["left-axis","title"],data:[{text:this.title,x:this.boundingRect.x+this.axisConfig.titlePadding,y:this.boundingRect.y+this.boundingRect.height/2,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:270,verticalPos:"top",horizontalPos:"center"}]}),t}getDrawableElementsForBottomAxis(){const t=[];if(this.showAxisLine){const i=this.boundingRect.y+this.axisConfig.axisLineWidth/2;t.push({type:"path",groupTexts:["bottom-axis","axis-line"],data:[{path:`M ${this.boundingRect.x},${i} L ${this.boundingRect.x+this.boundingRect.width},${i}`,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&t.push({type:"text",groupTexts:["bottom-axis","label"],data:this.getTickValues().map(i=>({text:i.toString(),x:this.getScaleValue(i),y:this.boundingRect.y+this.axisConfig.labelPadding+(this.showTick?this.axisConfig.tickLength:0)+(this.showAxisLine?this.axisConfig.axisLineWidth:0),fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}))}),this.showTick){const i=this.boundingRect.y+(this.showAxisLine?this.axisConfig.axisLineWidth:0);t.push({type:"path",groupTexts:["bottom-axis","ticks"],data:this.getTickValues().map(e=>({path:`M ${this.getScaleValue(e)},${i} L ${this.getScaleValue(e)},${i+this.axisConfig.tickLength}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&t.push({type:"text",groupTexts:["bottom-axis","title"],data:[{text:this.title,x:this.range[0]+(this.range[1]-this.range[0])/2,y:this.boundingRect.y+this.boundingRect.height-this.axisConfig.titlePadding-this.titleTextHeight,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}]}),t}getDrawableElementsForTopAxis(){const t=[];if(this.showAxisLine){const i=this.boundingRect.y+this.boundingRect.height-this.axisConfig.axisLineWidth/2;t.push({type:"path",groupTexts:["top-axis","axis-line"],data:[{path:`M ${this.boundingRect.x},${i} L ${this.boundingRect.x+this.boundingRect.width},${i}`,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&t.push({type:"text",groupTexts:["top-axis","label"],data:this.getTickValues().map(i=>({text:i.toString(),x:this.getScaleValue(i),y:this.boundingRect.y+(this.showTitle?this.titleTextHeight+this.axisConfig.titlePadding*2:0)+this.axisConfig.labelPadding,fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}))}),this.showTick){const i=this.boundingRect.y;t.push({type:"path",groupTexts:["top-axis","ticks"],data:this.getTickValues().map(e=>({path:`M ${this.getScaleValue(e)},${i+this.boundingRect.height-(this.showAxisLine?this.axisConfig.axisLineWidth:0)} L ${this.getScaleValue(e)},${i+this.boundingRect.height-this.axisConfig.tickLength-(this.showAxisLine?this.axisConfig.axisLineWidth:0)}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&t.push({type:"text",groupTexts:["top-axis","title"],data:[{text:this.title,x:this.boundingRect.x+this.boundingRect.width/2,y:this.boundingRect.y+this.axisConfig.titlePadding,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}]}),t}getDrawableElements(){if(this.axisPosition==="left")return this.getDrawableElementsForLeftAxis();if(this.axisPosition==="right")throw Error("Drawing of right axis is not implemented");return this.axisPosition==="bottom"?this.getDrawableElementsForBottomAxis():this.axisPosition==="top"?this.getDrawableElementsForTopAxis():[]}},n(Q,"BaseAxis"),Q),K,ki=(K=class extends Ut{constructor(t,i,e,a,c){super(t,a,c,i),this.categories=e,this.scale=Bt().domain(this.categories).range(this.getRange())}setRange(t){super.setRange(t)}recalculateScale(){this.scale=Bt().domain(this.categories).range(this.getRange()).paddingInner(1).paddingOuter(0).align(.5),Nt.trace("BandAxis axis final categories, range: ",this.categories,this.getRange())}getTickValues(){return this.categories}getScaleValue(t){return this.scale(t)??this.getRange()[0]}},n(K,"BandAxis"),K),Z,Ti=(Z=class extends Ut{constructor(t,i,e,a,c){super(t,a,c,i),this.domain=e,this.scale=Wt().domain(this.domain).range(this.getRange())}getTickValues(){return this.scale.ticks()}recalculateScale(){const t=[...this.domain];this.axisPosition==="left"&&t.reverse(),this.scale=Wt().domain(t).range(this.getRange())}getScaleValue(t){return this.scale(t)}},n(Z,"LinearAxis"),Z);function At(s,t,i,e){const a=new Ht(e);return St(s)?new ki(t,i,s.categories,s.title,a):new Ti(t,i,[s.min,s.max],s.title,a)}n(At,"getAxis");var J,Ri=(J=class{constructor(t,i,e,a){this.textDimensionCalculator=t,this.chartConfig=i,this.chartData=e,this.chartThemeConfig=a,this.boundingRect={x:0,y:0,width:0,height:0},this.showChartTitle=!1}setBoundingBoxXY(t){this.boundingRect.x=t.x,this.boundingRect.y=t.y}calculateSpace(t){const i=this.textDimensionCalculator.getMaxDimension([this.chartData.title],this.chartConfig.titleFontSize),e=Math.max(i.width,t.width),a=i.height+2*this.chartConfig.titlePadding;return i.width<=e&&i.height<=a&&this.chartConfig.showTitle&&this.chartData.title&&(this.boundingRect.width=e,this.boundingRect.height=a,this.showChartTitle=!0),{width:this.boundingRect.width,height:this.boundingRect.height}}getDrawableElements(){const t=[];return this.showChartTitle&&t.push({groupTexts:["chart-title"],type:"text",data:[{fontSize:this.chartConfig.titleFontSize,text:this.chartData.title,verticalPos:"middle",horizontalPos:"center",x:this.boundingRect.x+this.boundingRect.width/2,y:this.boundingRect.y+this.boundingRect.height/2,fill:this.chartThemeConfig.titleColor,rotation:0}]}),t}},n(J,"ChartTitle"),J);function $t(s,t,i,e){const a=new Ht(e);return new Ri(a,s,t,i)}n($t,"getChartTitleComponent");var tt,Di=(tt=class{constructor(t,i,e,a,c){this.plotData=t,this.xAxis=i,this.yAxis=e,this.orientation=a,this.plotIndex=c}getDrawableElement(){const t=this.plotData.data.map(e=>[this.xAxis.getScaleValue(e[0]),this.yAxis.getScaleValue(e[1])]);let i;return this.orientation==="horizontal"?i=zt().y(e=>e[0]).x(e=>e[1])(t):i=zt().x(e=>e[0]).y(e=>e[1])(t),i?[{groupTexts:["plot",`line-plot-${this.plotIndex}`],type:"path",data:[{path:i,strokeFill:this.plotData.strokeFill,strokeWidth:this.plotData.strokeWidth}]}]:[]}},n(tt,"LinePlot"),tt),it,vi=(it=class{constructor(t,i,e,a,c,d){this.barData=t,this.boundingRect=i,this.xAxis=e,this.yAxis=a,this.orientation=c,this.plotIndex=d}getDrawableElement(){const t=this.barData.data.map(c=>[this.xAxis.getScaleValue(c[0]),this.yAxis.getScaleValue(c[1])]),e=Math.min(this.xAxis.getAxisOuterPadding()*2,this.xAxis.getTickDistance())*(1-.05),a=e/2;return this.orientation==="horizontal"?[{groupTexts:["plot",`bar-plot-${this.plotIndex}`],type:"rect",data:t.map(c=>({x:this.boundingRect.x,y:c[0]-a,height:e,width:c[1]-this.boundingRect.x,fill:this.barData.fill,strokeWidth:0,strokeFill:this.barData.fill}))}]:[{groupTexts:["plot",`bar-plot-${this.plotIndex}`],type:"rect",data:t.map(c=>({x:c[0]-a,y:c[1],width:e,height:this.boundingRect.y+this.boundingRect.height-c[1],fill:this.barData.fill,strokeWidth:0,strokeFill:this.barData.fill}))}]}},n(it,"BarPlot"),it),et,Pi=(et=class{constructor(t,i,e){this.chartConfig=t,this.chartData=i,this.chartThemeConfig=e,this.boundingRect={x:0,y:0,width:0,height:0}}setAxes(t,i){this.xAxis=t,this.yAxis=i}setBoundingBoxXY(t){this.boundingRect.x=t.x,this.boundingRect.y=t.y}calculateSpace(t){return this.boundingRect.width=t.width,this.boundingRect.height=t.height,{width:this.boundingRect.width,height:this.boundingRect.height}}getDrawableElements(){if(!(this.xAxis&&this.yAxis))throw Error("Axes must be passed to render Plots");const t=[];for(const[i,e]of this.chartData.plots.entries())switch(e.type){case"line":{const a=new Di(e,this.xAxis,this.yAxis,this.chartConfig.chartOrientation,i);t.push(...a.getDrawableElement())}break;case"bar":{const a=new vi(e,this.boundingRect,this.xAxis,this.yAxis,this.chartConfig.chartOrientation,i);t.push(...a.getDrawableElement())}break}return t}},n(et,"BasePlot"),et);function qt(s,t,i){return new Pi(s,t,i)}n(qt,"getPlotComponent");var st,Li=(st=class{constructor(t,i,e,a){this.chartConfig=t,this.chartData=i,this.componentStore={title:$t(t,i,e,a),plot:qt(t,i,e),xAxis:At(i.xAxis,t.xAxis,{titleColor:e.xAxisTitleColor,labelColor:e.xAxisLabelColor,tickColor:e.xAxisTickColor,axisLineColor:e.xAxisLineColor},a),yAxis:At(i.yAxis,t.yAxis,{titleColor:e.yAxisTitleColor,labelColor:e.yAxisLabelColor,tickColor:e.yAxisTickColor,axisLineColor:e.yAxisLineColor},a)}}calculateVerticalSpace(){let t=this.chartConfig.width,i=this.chartConfig.height,e=0,a=0,c=Math.floor(t*this.chartConfig.plotReservedSpacePercent/100),d=Math.floor(i*this.chartConfig.plotReservedSpacePercent/100),m=this.componentStore.plot.calculateSpace({width:c,height:d});t-=m.width,i-=m.height,m=this.componentStore.title.calculateSpace({width:this.chartConfig.width,height:i}),a=m.height,i-=m.height,this.componentStore.xAxis.setAxisPosition("bottom"),m=this.componentStore.xAxis.calculateSpace({width:t,height:i}),i-=m.height,this.componentStore.yAxis.setAxisPosition("left"),m=this.componentStore.yAxis.calculateSpace({width:t,height:i}),e=m.width,t-=m.width,t>0&&(c+=t,t=0),i>0&&(d+=i,i=0),this.componentStore.plot.calculateSpace({width:c,height:d}),this.componentStore.plot.setBoundingBoxXY({x:e,y:a}),this.componentStore.xAxis.setRange([e,e+c]),this.componentStore.xAxis.setBoundingBoxXY({x:e,y:a+d}),this.componentStore.yAxis.setRange([a,a+d]),this.componentStore.yAxis.setBoundingBoxXY({x:0,y:a}),this.chartData.plots.some(b=>bt(b))&&this.componentStore.xAxis.recalculateOuterPaddingToDrawBar()}calculateHorizontalSpace(){let t=this.chartConfig.width,i=this.chartConfig.height,e=0,a=0,c=0,d=Math.floor(t*this.chartConfig.plotReservedSpacePercent/100),m=Math.floor(i*this.chartConfig.plotReservedSpacePercent/100),b=this.componentStore.plot.calculateSpace({width:d,height:m});t-=b.width,i-=b.height,b=this.componentStore.title.calculateSpace({width:this.chartConfig.width,height:i}),e=b.height,i-=b.height,this.componentStore.xAxis.setAxisPosition("left"),b=this.componentStore.xAxis.calculateSpace({width:t,height:i}),t-=b.width,a=b.width,this.componentStore.yAxis.setAxisPosition("top"),b=this.componentStore.yAxis.calculateSpace({width:t,height:i}),i-=b.height,c=e+b.height,t>0&&(d+=t,t=0),i>0&&(m+=i,i=0),this.componentStore.plot.calculateSpace({width:d,height:m}),this.componentStore.plot.setBoundingBoxXY({x:a,y:c}),this.componentStore.yAxis.setRange([a,a+d]),this.componentStore.yAxis.setBoundingBoxXY({x:a,y:e}),this.componentStore.xAxis.setRange([c,c+m]),this.componentStore.xAxis.setBoundingBoxXY({x:0,y:c}),this.chartData.plots.some(P=>bt(P))&&this.componentStore.xAxis.recalculateOuterPaddingToDrawBar()}calculateSpace(){this.chartConfig.chartOrientation==="horizontal"?this.calculateHorizontalSpace():this.calculateVerticalSpace()}getDrawableElement(){this.calculateSpace();const t=[];this.componentStore.plot.setAxes(this.componentStore.xAxis,this.componentStore.yAxis);for(const i of Object.values(this.componentStore))t.push(...i.getDrawableElements());return t}},n(st,"Orchestrator"),st),at,Ei=(at=class{static build(t,i,e,a){return new Li(t,i,e,a).getDrawableElement()}},n(at,"XYChartBuilder"),at),ot=0,Gt,rt=Tt(),ht=kt(),y=Rt(),wt=ht.plotColorPalette.split(",").map(s=>s.trim()),gt=!1,_t=!1;function kt(){const s=wi(),t=Ct();return Yt(s.xyChart,t.themeVariables.xyChart)}n(kt,"getChartDefaultThemeConfig");function Tt(){const s=Ct();return Yt(Ai.xyChart,s.xyChart)}n(Tt,"getChartDefaultConfig");function Rt(){return{yAxis:{type:"linear",title:"",min:1/0,max:-1/0},xAxis:{type:"band",title:"",categories:[]},title:"",plots:[]}}n(Rt,"getChartDefaultData");function xt(s){const t=Ct();return Ci(s.trim(),t)}n(xt,"textSanitizer");function jt(s){Gt=s}n(jt,"setTmpSVGG");function Qt(s){s==="horizontal"?rt.chartOrientation="horizontal":rt.chartOrientation="vertical"}n(Qt,"setOrientation");function Kt(s){y.xAxis.title=xt(s.text)}n(Kt,"setXAxisTitle");function Dt(s,t){y.xAxis={type:"linear",title:y.xAxis.title,min:s,max:t},gt=!0}n(Dt,"setXAxisRangeData");function Zt(s){y.xAxis={type:"band",title:y.xAxis.title,categories:s.map(t=>xt(t.text))},gt=!0}n(Zt,"setXAxisBand");function Jt(s){y.yAxis.title=xt(s.text)}n(Jt,"setYAxisTitle");function ti(s,t){y.yAxis={type:"linear",title:y.yAxis.title,min:s,max:t},_t=!0}n(ti,"setYAxisRangeData");function ii(s){const t=Math.min(...s),i=Math.max(...s),e=G(y.yAxis)?y.yAxis.min:1/0,a=G(y.yAxis)?y.yAxis.max:-1/0;y.yAxis={type:"linear",title:y.yAxis.title,min:Math.min(e,t),max:Math.max(a,i)}}n(ii,"setYAxisRangeFromPlotData");function vt(s){let t=[];if(s.length===0)return t;if(!gt){const i=G(y.xAxis)?y.xAxis.min:1/0,e=G(y.xAxis)?y.xAxis.max:-1/0;Dt(Math.min(i,1),Math.max(e,s.length))}if(_t||ii(s),St(y.xAxis)&&(t=y.xAxis.categories.map((i,e)=>[i,s[e]])),G(y.xAxis)){const i=y.xAxis.min,e=y.xAxis.max,a=(e-i)/(s.length-1),c=[];for(let d=i;d<=e;d+=a)c.push(`${d}`);t=c.map((d,m)=>[d,s[m]])}return t}n(vt,"transformDataWithoutCategory");function Pt(s){return wt[s===0?0:s%wt.length]}n(Pt,"getPlotColorFromPalette");function ei(s,t){const i=vt(t);y.plots.push({type:"line",strokeFill:Pt(ot),strokeWidth:2,data:i}),ot++}n(ei,"setLineData");function si(s,t){const i=vt(t);y.plots.push({type:"bar",fill:Pt(ot),data:i}),ot++}n(si,"setBarData");function ai(){if(y.plots.length===0)throw Error("No Plot to render, please provide a plot with some data");return y.title=Xt(),Ei.build(rt,y,ht,Gt)}n(ai,"getDrawableElem");function ni(){return ht}n(ni,"getChartThemeConfig");function oi(){return rt}n(oi,"getChartConfig");function ri(){return y}n(ri,"getXYChartData");var Ii=n(function(){bi(),ot=0,rt=Tt(),y=Rt(),ht=kt(),wt=ht.plotColorPalette.split(",").map(s=>s.trim()),gt=!1,_t=!1},"clear"),Vi={getDrawableElem:ai,clear:Ii,setAccTitle:fi,getAccTitle:pi,setDiagramTitle:di,getDiagramTitle:Xt,getAccDescription:xi,setAccDescription:gi,setOrientation:Qt,setXAxisTitle:Kt,setXAxisRangeData:Dt,setXAxisBand:Zt,setYAxisTitle:Jt,setYAxisRangeData:ti,setLineData:ei,setBarData:si,setTmpSVGG:jt,getChartThemeConfig:ni,getChartConfig:oi,getXYChartData:ri},Mi=n((s,t,i,e)=>{const a=e.db,c=a.getChartThemeConfig(),d=a.getChartConfig(),m=a.getXYChartData().plots[0].data.map(f=>f[1]);function b(f){return f==="top"?"text-before-edge":"middle"}n(b,"getDominantBaseLine");function P(f){return f==="left"?"start":f==="right"?"end":"middle"}n(P,"getTextAnchor");function I(f){return`translate(${f.x}, ${f.y}) rotate(${f.rotation||0})`}n(I,"getTextTransformation"),Nt.debug(`Rendering xychart chart +`+s);const R=yi(t),L=R.append("g").attr("class","main"),z=L.append("rect").attr("width",d.width).attr("height",d.height).attr("class","background");mi(R,d.height,d.width,!0),R.attr("viewBox",`0 0 ${d.width} ${d.height}`),z.attr("fill",c.backgroundColor),a.setTmpSVGG(R.append("g").attr("class","mermaid-tmp-group"));const F=a.getDrawableElem(),D={};function V(f){let C=L,l="";for(const[M]of f.entries()){let B=L;M>0&&D[l]&&(B=D[l]),l+=f[M],C=D[l],C||(C=D[l]=B.append("g").attr("class",f[M]))}return C}n(V,"getGroup");for(const f of F){if(f.data.length===0)continue;const C=V(f.groupTexts);switch(f.type){case"rect":if(C.selectAll("rect").data(f.data).enter().append("rect").attr("x",l=>l.x).attr("y",l=>l.y).attr("width",l=>l.width).attr("height",l=>l.height).attr("fill",l=>l.fill).attr("stroke",l=>l.strokeFill).attr("stroke-width",l=>l.strokeWidth),d.showDataLabel)if(d.chartOrientation==="horizontal"){let l=function(h,k){const{data:w,label:S}=h;return k*S.length*M<=w.width-10};n(l,"fitsHorizontally");const M=.7,B=f.data.map((h,k)=>({data:h,label:m[k].toString()})).filter(h=>h.data.width>0&&h.data.height>0),U=B.map(h=>{const{data:k}=h;let w=k.height*.7;for(;!l(h,w)&&w>0;)w-=1;return w}),X=Math.floor(Math.min(...U));C.selectAll("text").data(B).enter().append("text").attr("x",h=>h.data.x+h.data.width-10).attr("y",h=>h.data.y+h.data.height/2).attr("text-anchor","end").attr("dominant-baseline","middle").attr("fill","black").attr("font-size",`${X}px`).text(h=>h.label)}else{let l=function(h,k,w){const{data:S,label:$}=h,N=k*$.length*.7,W=S.x+S.width/2,r=W-N/2,u=W+N/2,g=r>=S.x&&u<=S.x+S.width,x=S.y+w+k<=S.y+S.height;return g&&x};n(l,"fitsInBar");const M=10,B=f.data.map((h,k)=>({data:h,label:m[k].toString()})).filter(h=>h.data.width>0&&h.data.height>0),U=B.map(h=>{const{data:k,label:w}=h;let S=k.width/(w.length*.7);for(;!l(h,S,M)&&S>0;)S-=1;return S}),X=Math.floor(Math.min(...U));C.selectAll("text").data(B).enter().append("text").attr("x",h=>h.data.x+h.data.width/2).attr("y",h=>h.data.y+M).attr("text-anchor","middle").attr("dominant-baseline","hanging").attr("fill","black").attr("font-size",`${X}px`).text(h=>h.label)}break;case"text":C.selectAll("text").data(f.data).enter().append("text").attr("x",0).attr("y",0).attr("fill",l=>l.fill).attr("font-size",l=>l.fontSize).attr("dominant-baseline",l=>b(l.verticalPos)).attr("text-anchor",l=>P(l.horizontalPos)).attr("transform",l=>I(l)).text(l=>l.text);break;case"path":C.selectAll("path").data(f.data).enter().append("path").attr("d",l=>l.path).attr("fill",l=>l.fill?l.fill:"none").attr("stroke",l=>l.strokeFill).attr("stroke-width",l=>l.strokeWidth);break}}},"draw"),Bi={draw:Mi},Fi={parser:_i,db:Vi,renderer:Bi};export{Fi as diagram}; diff --git a/assets/config_antiforgery.md.7miFkqvJ.js b/assets/config_antiforgery.md.7miFkqvJ.js new file mode 100644 index 000000000..ca9ec07dc --- /dev/null +++ b/assets/config_antiforgery.md.7miFkqvJ.js @@ -0,0 +1,24 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Antiforgery Configuration","titleTemplate":"NpgsqlRest","description":"Configure CSRF protection in NpgsqlRest. Antiforgery tokens, cookie settings, and header validation for secure state-changing requests.","frontmatter":{"outline":[2,3],"title":"Antiforgery Configuration","titleTemplate":"NpgsqlRest","description":"Configure CSRF protection in NpgsqlRest. Antiforgery tokens, cookie settings, and header validation for secure state-changing requests.","head":[["meta",{"name":"keywords","content":"npgsqlrest antiforgery, csrf protection api, cross-site request forgery, antiforgery tokens, api security csrf"}],["meta",{"property":"og:title","content":"NpgsqlRest Antiforgery Configuration"}],["meta",{"property":"og:description","content":"Configure CSRF protection with antiforgery tokens for secure state-changing requests."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/antiforgery.md","filePath":"config/antiforgery.md"}'),t={name:"config/antiforgery.md"};function l(r,s,p,h,o,k){return e(),a("div",null,s[0]||(s[0]=[n(`

    Antiforgery

    Antiforgery token configuration protects against Cross-Site Request Forgery (CSRF) attacks by validating unique tokens for state-changing requests (POST, PUT, DELETE, etc.).

    Overview

    json
    json
    {
    +  "Antiforgery": {
    +    "Enabled": false,
    +    "CookieName": null,
    +    "FormFieldName": "__RequestVerificationToken",
    +    "HeaderName": "RequestVerificationToken",
    +    "SuppressReadingTokenFromFormBody": false,
    +    "SuppressXFrameOptionsHeader": false
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable antiforgery token validation.
    CookieNamestringnullCustom cookie name. Uses default (.AspNetCore.Antiforgery.*) if null.
    FormFieldNamestring"__RequestVerificationToken"Name of the hidden form field containing the token.
    HeaderNamestring"RequestVerificationToken"HTTP header name for sending the token (useful for AJAX requests).
    SuppressReadingTokenFromFormBodyboolfalseWhen true, skips reading tokens from form body (forces header-only validation).
    SuppressXFrameOptionsHeaderboolfalseWhen true, disables automatic X-Frame-Options header generation.

    Token Submission

    Antiforgery tokens can be submitted in two ways:

    Form Field

    Include a hidden field in HTML forms:

    html
    html
    <form method="POST" action="/api/submit">
    +  <input type="hidden" name="__RequestVerificationToken" value="token-value" />
    +  <!-- form fields -->
    +</form>

    HTTP Header

    Send the token in a header (useful for AJAX/fetch requests):

    javascript
    javascript
    fetch('/api/submit', {
    +  method: 'POST',
    +  headers: {
    +    'RequestVerificationToken': tokenValue
    +  }
    +});

    X-Frame-Options Header

    When SuppressXFrameOptionsHeader is false (default), the server automatically adds the X-Frame-Options header to prevent clickjacking attacks.

    WARNING

    Only set SuppressXFrameOptionsHeader to true if you're handling frame protection elsewhere (e.g., Content-Security-Policy frame-ancestors directive).

    Example Configuration

    Enable antiforgery with custom header name:

    json
    json
    {
    +  "Antiforgery": {
    +    "Enabled": true,
    +    "HeaderName": "X-CSRF-Token",
    +    "SuppressReadingTokenFromFormBody": true
    +  }
    +}

    Next Steps

    `,24)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_antiforgery.md.7miFkqvJ.lean.js b/assets/config_antiforgery.md.7miFkqvJ.lean.js new file mode 100644 index 000000000..930849d28 --- /dev/null +++ b/assets/config_antiforgery.md.7miFkqvJ.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Antiforgery Configuration","titleTemplate":"NpgsqlRest","description":"Configure CSRF protection in NpgsqlRest. Antiforgery tokens, cookie settings, and header validation for secure state-changing requests.","frontmatter":{"outline":[2,3],"title":"Antiforgery Configuration","titleTemplate":"NpgsqlRest","description":"Configure CSRF protection in NpgsqlRest. Antiforgery tokens, cookie settings, and header validation for secure state-changing requests.","head":[["meta",{"name":"keywords","content":"npgsqlrest antiforgery, csrf protection api, cross-site request forgery, antiforgery tokens, api security csrf"}],["meta",{"property":"og:title","content":"NpgsqlRest Antiforgery Configuration"}],["meta",{"property":"og:description","content":"Configure CSRF protection with antiforgery tokens for secure state-changing requests."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/antiforgery.md","filePath":"config/antiforgery.md"}'),t={name:"config/antiforgery.md"};function l(r,s,p,h,o,k){return e(),a("div",null,s[0]||(s[0]=[n("",24)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_auth.md.BTlaq10R.js b/assets/config_auth.md.BTlaq10R.js new file mode 100644 index 000000000..26038c0ab --- /dev/null +++ b/assets/config_auth.md.BTlaq10R.js @@ -0,0 +1,162 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure authentication in NpgsqlRest: Cookie sessions, Bearer tokens, JWT tokens, and OAuth providers. Secure your PostgreSQL REST API with multiple auth methods.","frontmatter":{"outline":[2,3],"title":"Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure authentication in NpgsqlRest: Cookie sessions, Bearer tokens, JWT tokens, and OAuth providers. Secure your PostgreSQL REST API with multiple auth methods.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication, postgresql api auth, jwt postgresql, cookie auth rest api, bearer token postgresql, oauth postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Configuration"}],["meta",{"property":"og:description","content":"Configure Cookie, Bearer Token, JWT, and OAuth authentication for your PostgreSQL REST API."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/auth.md","filePath":"config/auth.md"}'),e={name:"config/auth.md"};function l(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Authentication

    This page covers Cookie, Bearer Token, and JWT authentication settings in NpgsqlRest.

    Overview

    NpgsqlRest supports multiple authentication methods that can be used together:

    • Cookie Authentication - Traditional session-based authentication
    • Microsoft Bearer Token Authentication - Proprietary encrypted token authentication (ASP.NET Core specific)
    • JWT Authentication - Industry-standard JSON Web Token authentication (RFC 7519)
    • Passkey Authentication - WebAuthn passwordless authentication (see Passkey Authentication)
    • External OAuth Providers - Google, LinkedIn, GitHub, Microsoft, Facebook (see External OAuth)
    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": false,
    +    "BearerTokenAuth": false,
    +    "JwtAuth": false,
    +    "External": {
    +      "Enabled": false
    +    }
    +  }
    +}

    Cookie authentication provides session-based authentication using HTTP cookies.

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": null,
    +    "CookieValid": "14 days",
    +    "CookieName": null,
    +    "CookiePath": null,
    +    "CookieDomain": null,
    +    "CookieMultiSessions": true,
    +    "CookieHttpOnly": true,
    +    "CookieSameSite": null,
    +    "CookieSecure": null
    +  }
    +}
    SettingTypeDefaultDescription
    CookieAuthboolfalseEnable cookie authentication.
    CookieAuthSchemestringnullAuthentication scheme name. Uses "Cookies" if null (from CookieAuthenticationDefaults.AuthenticationScheme).
    CookieValidstring"14 days"Cookie validity duration in PostgreSQL interval format (e.g., "14 days", "12 hours", "30 minutes"). Set to null to fall back to the framework default (14 days).
    CookieNamestringnullCustom name for the authentication cookie. Uses default if null.
    CookiePathstringnullPath scope for the cookie. Uses default if null.
    CookieDomainstringnullDomain scope for the cookie. Uses default if null.
    CookieMultiSessionsbooltrueAllow multiple concurrent sessions for the same user.
    CookieHttpOnlybooltrueMake cookie accessible only via HTTP (not JavaScript).
    CookieSameSitestringnullSameSite attribute on the cookie: "Strict" / "Lax" / "None" / "Unspecified". null uses ASP.NET's default (typically Lax). Use "None" for cross-origin SPA / mobile clients.
    CookieSecurestringnullWhen the cookie's Secure attribute is set: "SameAsRequest" / "Always" / "None". null uses ASP.NET's default (SameAsRequest). Required "Always" when CookieSameSite is "None" — browsers drop non-Secure SameSite=None cookies.

    When CookieHttpOnly is true (recommended), the cookie cannot be accessed by client-side JavaScript, protecting against XSS attacks.

    TIP

    For production, always use HTTPS and consider setting CookieDomain to your specific domain.

    Cross-Origin Cookies (New in 3.15.0)

    CookieSameSite and CookieSecure make cookie auth work across origins — e.g. an SPA on app.example.com calling an API on api.example.com. Without them, browsers silently drop the session cookie on cross-site requests.

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["https://app.example.com"],
    +    "AllowCredentials": true
    +  },
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieSameSite": "None",
    +    "CookieSecure":   "Always",
    +    "CookieHttpOnly": true,
    +    "CookieDomain":   ".example.com"
    +  }
    +}

    Validation: unknown CookieSameSite / CookieSecure values fail fast at startup with the offending config path. Setting CookieSameSite=None without CookieSecure=Always logs a startup warning — the cookie would be silently dropped by modern browsers.

    Microsoft Bearer Token Authentication

    Microsoft Bearer Token authentication provides stateless token-based authentication using ASP.NET Core's proprietary encrypted token format. This is suitable for single ASP.NET Core applications.

    json
    json
    {
    +  "Auth": {
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": null,
    +    "BearerTokenExpire": "1 hour",
    +    "BearerTokenRefreshPath": "/api/token/refresh"
    +  }
    +}

    Bearer Token Settings Reference

    SettingTypeDefaultDescription
    BearerTokenAuthboolfalseEnable Microsoft bearer token authentication.
    BearerTokenAuthSchemestringnullAuthentication scheme name. Uses "BearerToken" if null (from BearerTokenDefaults.AuthenticationScheme).
    BearerTokenExpirestring"1 hour"Bearer token expiration in PostgreSQL interval format (e.g., "1 hour", "30 minutes", "2 days"). Set to null to fall back to the framework default (1 hour).
    BearerTokenRefreshPathstring"/api/token/refresh"Endpoint path for refreshing tokens.

    Token Refresh

    To refresh a Microsoft bearer token, POST to the configured refresh path:

    http
    http
    POST /api/token/refresh
    +Content-Type: application/json
    +
    +{
    +  "refresh": "{{refreshToken}}"
    +}

    JWT Authentication

    New in 3.2.1

    JWT authentication was added in version 3.2.1.

    JWT (JSON Web Token) authentication provides industry-standard token-based authentication (RFC 7519). Unlike Microsoft Bearer Token authentication, JWT tokens are interoperable and can be used with any system that supports JWT.

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "your-app",
    +    "JwtAudience": "your-api",
    +    "JwtExpire": "60 minutes",
    +    "JwtRefreshExpire": "7 days",
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true,
    +    "JwtValidateLifetime": true,
    +    "JwtValidateIssuerSigningKey": true,
    +    "JwtClockSkew": "5 minutes",
    +    "JwtRefreshPath": "/api/jwt/refresh"
    +  }
    +}

    JWT Settings Reference

    SettingTypeDefaultDescription
    JwtAuthboolfalseEnable JWT authentication.
    JwtAuthSchemestringnullAuthentication scheme name. Uses "Bearer" if null (from JwtBearerDefaults.AuthenticationScheme).
    JwtSecretstringnullSecret key for signing tokens. Must be at least 32 characters for HS256.
    JwtIssuerstringnullToken issuer (iss claim).
    JwtAudiencestringnullToken audience (aud claim).
    JwtExpirestring"60 minutes"Access token expiration in PostgreSQL interval format (e.g., "60 minutes", "1 hour", "30 seconds"). Set to null to fall back to the framework default (60 minutes).
    JwtRefreshExpirestring"7 days"Refresh token expiration in PostgreSQL interval format (e.g., "7 days", "168 hours"). Set to null to fall back to the framework default (7 days).
    JwtValidateIssuerboolfalseValidate the issuer claim. Set to true if JwtIssuer is configured.
    JwtValidateAudienceboolfalseValidate the audience claim. Set to true if JwtAudience is configured.
    JwtValidateLifetimebooltrueValidate token expiration.
    JwtValidateIssuerSigningKeybooltrueValidate the signing key.
    JwtClockSkewstring"5 minutes"Clock tolerance for expiration validation. Uses PostgreSQL interval format.
    JwtRefreshPathstring"/api/jwt/refresh"Endpoint path for refreshing JWT tokens.

    Login Response

    When JWT authentication is enabled and a login endpoint returns successfully, the response includes:

    json
    json
    {
    +  "accessToken": "eyJhbG...",
    +  "refreshToken": "eyJhbG...",
    +  "tokenType": "Bearer",
    +  "expiresIn": 3600,
    +  "refreshExpiresIn": 604800
    +}

    Token Refresh

    To refresh a JWT token, POST to the configured refresh path (default: /api/jwt/refresh):

    http
    http
    POST /api/jwt/refresh
    +Content-Type: application/json
    +
    +{
    +  "refreshToken": "eyJhbG..."
    +}

    The response returns a new access token and refresh token pair.

    JWT vs Microsoft Bearer Token

    FeatureMicrosoft Bearer TokenJWT
    Token FormatProprietary, encryptedIndustry-standard (RFC 7519)
    InteroperabilityASP.NET Core onlyAny system supporting JWT
    Token InspectionOpaqueCan be decoded at jwt.io
    Use CaseSingle ASP.NET appCross-service, microservices

    Security

    Store your JwtSecret securely. Use environment variables in production:

    json
    json
    {
    +  "Auth": {
    +    "JwtSecret": "{JWT_SECRET}"
    +  }
    +}

    Additional Authentication Schemes

    New in 3.13.0

    Named additional authentication schemes registered alongside the main one.

    Auth:Schemes is a named-dict section that registers additional ASP.NET Core authentication schemes alongside the main one. Each entry is a fully-fledged scheme of any of the three supported types — Cookies, BearerToken, or Jwt — with its own options. A login function selects which scheme to use by returning the scheme's name in its scheme column.

    What this enables:

    • Short-lived sensitive sessions for admin or payment flows (Cookies scheme with shorter CookieValid + CookieMultiSessions: false).
    • Per-scope JWT signing keys so a key leak has limited blast radius (separate JwtSecret per Jwt scheme).
    • Multiple bearer-token APIs with different expirations and refresh paths.
    • Single-session cookies for areas where parallel logins must be disallowed, alongside a normal long-lived session.
    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "14 days",
    +  "JwtAuth": true,
    +  "JwtSecret": "...root-secret-32+chars...",
    +  "Schemes": {
    +    "short_session": {
    +      "Type": "Cookies",
    +      "Enabled": true,
    +      "CookieValid": "1 hour",
    +      "CookieMultiSessions": false
    +    },
    +    "api_token": {
    +      "Type": "BearerToken",
    +      "Enabled": true,
    +      "BearerTokenExpire": "30 minutes",
    +      "BearerTokenRefreshPath": "/api/api-token/refresh"
    +    },
    +    "admin_jwt": {
    +      "Type": "Jwt",
    +      "Enabled": true,
    +      "JwtSecret": "...separate-admin-secret-32+chars...",
    +      "JwtExpire": "5 minutes",
    +      "JwtRefreshPath": "/api/admin-jwt/refresh"
    +    }
    +  }
    +}

    A login function selects the scheme by returning its name in the scheme column:

    Equivalent as a SQL file endpoint (sql/login.sql):

    sql
    sql
    /*
    +HTTP POST
    +@login
    +@allow_anonymous
    +@param $1 user
    +@param $2 pass
    +*/
    +select 'Cookies' as scheme, user_id::text as name_identifier, username as name
    +from users where username = $1 and password_hash = crypt($2, password_hash);
    sql
    sql
    -- Standard login: returns 'Cookies' → 14-day persistent cookie
    +create function login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'Cookies' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Sensitive-area login: returns 'short_session' → 1-hour session-only cookie
    +create function admin_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'short_session' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Admin JWT login: returns 'admin_jwt' → 5-minute JWT signed with the admin-only secret
    +create function admin_jwt_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'admin_jwt' as scheme, user_id::text, username from users where ...
    +$$;

    Per-Type Override Fields

    TypeOverride fields
    CookiesCookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly, CookieSameSite, CookieSecure
    BearerTokenBearerTokenExpire, BearerTokenRefreshPath
    JwtJwtExpire, JwtRefreshExpire, JwtSecret, JwtIssuer, JwtAudience, JwtClockSkew, JwtRefreshPath, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey

    Common fields: Type (required, case-insensitive), Enabled (default true).

    Inheritance. A scheme that overrides only one or two fields reuses everything else from the root Auth section, so blocks stay small. Setting CookieMultiSessions: false is the typical "single-session" override — the cookie's Max-Age becomes null (browser-session-only) while ExpireTimeSpan still bounds server-side validity. JWT schemes inherit JwtSecret from the root section if not set explicitly, so a per-scheme block can be just a shorter expiration.

    Validation at Startup (Fail-Fast)

    • Scheme name must not collide with the main scheme names (CookieAuthScheme, BearerTokenAuthScheme, JwtAuthScheme).
    • Type must be one of Cookies, BearerToken, Jwt (case-insensitive). Missing or unsupported types throw with a clear message.
    • Explicit CookieName values must be distinct across all cookie schemes. When unset, ASP.NET's per-scheme .AspNetCore.<scheme> default automatically differs and is excluded from collision tracking.
    • Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across the main scheme and every scheme that defines one.
    • Jwt schemes require a secret either on the scheme or on the root section; JwtSecret must be ≥32 chars for HS256.
    • Invalid interval strings throw with the offending path and value.

    Refresh middleware per scheme. Each BearerToken/Jwt scheme that declares a refresh path gets its own middleware listening on that path, with that scheme's tokens validated under that scheme's options.

    Logout. The existing logout pipeline accepts a list of scheme names from the logout function's result columns and signs out each — additional schemes work without changes. To clear both main and additional cookies in one logout, return both scheme names from the function.

    Complete Examples

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "30 days",
    +    "CookieHttpOnly": true,
    +    "CookieMultiSessions": false
    +  }
    +}

    JWT Authentication

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "{JWT_SECRET}",
    +    "JwtIssuer": "my-app",
    +    "JwtAudience": "my-api",
    +    "JwtExpire": "60 minutes",
    +    "JwtRefreshExpire": "7 days",
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true
    +  }
    +}

    Combined Authentication

    All three authentication schemes can be used together:

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "14 days",
    +    "CookieHttpOnly": true,
    +
    +    "BearerTokenAuth": true,
    +    "BearerTokenExpire": "1 hour",
    +
    +    "JwtAuth": true,
    +    "JwtSecret": "{JWT_SECRET}",
    +    "JwtExpire": "60 minutes"
    +  }
    +}

    Breaking change in 3.13.0

    The legacy integer-based time fields under Auth were removed. If you upgrade with any of the four removed fields still in your config, startup will fail with a clear migration message.

    Removed (3.12 and earlier)Use instead (3.13.0+)
    Auth:CookieValidDays: 14Auth:CookieValid: "14 days"
    Auth:BearerTokenExpireHours: 1Auth:BearerTokenExpire: "1 hour"
    Auth:JwtExpireMinutes: 60Auth:JwtExpire: "60 minutes"
    Auth:JwtRefreshExpireDays: 7Auth:JwtRefreshExpire: "7 days"

    The new fields accept Postgres-interval syntax ("14 days", "12 hours", "30 minutes", "45 seconds", etc.) — finer-grained durations than the legacy integers permitted. Setting any of these to null falls back to the framework default.

    Next Steps

    See Also

    • AUTHORIZE - Require authentication on endpoints
    • LOGIN - Mark endpoint as sign-in
    • LOGOUT - Mark endpoint as sign-out
    `,75)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_auth.md.BTlaq10R.lean.js b/assets/config_auth.md.BTlaq10R.lean.js new file mode 100644 index 000000000..b2f96a042 --- /dev/null +++ b/assets/config_auth.md.BTlaq10R.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure authentication in NpgsqlRest: Cookie sessions, Bearer tokens, JWT tokens, and OAuth providers. Secure your PostgreSQL REST API with multiple auth methods.","frontmatter":{"outline":[2,3],"title":"Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure authentication in NpgsqlRest: Cookie sessions, Bearer tokens, JWT tokens, and OAuth providers. Secure your PostgreSQL REST API with multiple auth methods.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication, postgresql api auth, jwt postgresql, cookie auth rest api, bearer token postgresql, oauth postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Configuration"}],["meta",{"property":"og:description","content":"Configure Cookie, Bearer Token, JWT, and OAuth authentication for your PostgreSQL REST API."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/auth.md","filePath":"config/auth.md"}'),e={name:"config/auth.md"};function l(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t("",75)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_authentication-options.md.CzhPcVla.js b/assets/config_authentication-options.md.CzhPcVla.js new file mode 100644 index 000000000..4bb3ae314 --- /dev/null +++ b/assets/config_authentication-options.md.CzhPcVla.js @@ -0,0 +1,98 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Authentication Options","titleTemplate":"NpgsqlRest","description":"Configure login/logout handling in NpgsqlRest. Password hashing, session management, claim types, and authentication response formatting.","frontmatter":{"outline":[2,3],"title":"Authentication Options","titleTemplate":"NpgsqlRest","description":"Configure login/logout handling in NpgsqlRest. Password hashing, session management, claim types, and authentication response formatting.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication options, login logout api, password hashing postgresql, session management api, claim types configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Options"}],["meta",{"property":"og:description","content":"Configure login/logout, password hashing, and authentication response handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/authentication-options.md","filePath":"config/authentication-options.md"}'),e={name:"config/authentication-options.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[n(`

    Authentication Options

    Basic authentication configuration for NpgsqlRest endpoints including login/logout handling and password settings.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultAuthenticationType": null,
    +      "StatusColumnName": "status",
    +      "SchemeColumnName": "scheme",
    +      "BodyColumnName": "body",
    +      "ResponseTypeColumnName": "application/json",
    +      "HashColumnName": "hash",
    +      "PasswordParameterNameContains": "pass",
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "user_name",
    +      "DefaultRoleClaimType": "user_roles",
    +      "SerializeAuthEndpointsResponse": false,
    +      "ObfuscateAuthParameterLogValues": true,
    +      "PasswordVerificationFailedCommand": null,
    +      "PasswordVerificationSucceededCommand": null,
    +      "UseUserContext": false,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "ClaimsJsonContextKey": null,
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": false,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address",
    +      "LoginPath": null,
    +      "LogoutPath": null,
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    DefaultAuthenticationTypestringnullAuthentication type for ClaimsIdentity. Auto-detected from database name if null and login endpoint exists.
    SerializeAuthEndpointsResponseboolfalseWhen true, login endpoint returns all columns from the login routine as JSON in the response body (ignored for bearer token auth or when BodyColumnName is present).
    ObfuscateAuthParameterLogValuesbooltrueObfuscate parameter values in logs for auth endpoints to protect credentials.

    Login Response Columns

    Column names used to read values from the login routine response.

    SettingTypeDefaultDescription
    StatusColumnNamestring"status"Column for success/failure. Boolean or numeric HTTP status code (200 = success).
    SchemeColumnNamestring"scheme"Column for authentication scheme override.
    BodyColumnNamestring"body"Column for response body message.
    ResponseTypeColumnNamestring"application/json"Column for response content type.
    HashColumnNamestring"hash"Column for password hash verification. See Password Verification.

    Password Handling

    These settings are part of the built-in password verification system. For detailed information on how password verification works, including examples and the built-in password hasher, see Password Verification in the login annotation documentation.

    SettingTypeDefaultDescription
    PasswordParameterNameContainsstring"pass"Identifies password parameter (first param containing this string). See Password Parameter Detection.
    PasswordVerificationFailedCommandstringnullCommand executed on password verification failure.
    PasswordVerificationSucceededCommandstringnullCommand executed on password verification success.

    Password Verification Command Parameters

    Both PasswordVerificationFailedCommand and PasswordVerificationSucceededCommand receive:

    ParameterTypeDescription
    $1textAuthentication scheme used for login.
    $2textUser ID.
    $3textUsername.

    Default Claim Types

    SettingTypeDefaultDescription
    DefaultUserIdClaimTypestring"user_id"Claim type for user ID.
    DefaultNameClaimTypestring"user_name"Claim type for username.
    DefaultRoleClaimTypestring"user_roles"Claim type for user roles.

    User Context Settings

    Settings for automatically passing authenticated user claims to PostgreSQL via context variables.

    SettingTypeDefaultDescription
    UseUserContextboolfalseEnable setting authenticated user claims to context variables automatically. For proxy endpoints, when enabled, these values are also forwarded as HTTP headers to the upstream proxy.
    ContextKeyClaimsMappingobjectSee belowMapping of context keys to user claim names. Keys are context variable names, values are user claim names.
    ClaimsJsonContextKeystringnullContext key for all available user claims as JSON. When not null and user is authenticated, all claims are serialized to JSON and set to this context variable.
    IpAddressContextKeystring"request.ip_address"Context key for IP address. When not null, IP address is set to this context variable when UseUserContext is enabled (even for unauthenticated users).

    Default ContextKeyClaimsMapping

    json
    json
    {
    +  "request.user_id": "user_id",
    +  "request.user_name": "user_name",
    +  "request.user_roles": "user_roles"
    +}

    User Parameters Settings

    Settings for automatically mapping authenticated user claims to function parameters.

    SettingTypeDefaultDescription
    UseUserParametersboolfalseEnable mapping authenticated user claims to parameters by name automatically. For proxy endpoints, when enabled, these values are also forwarded as query string parameters.
    ParameterNameClaimsMappingobjectSee belowMapping of parameter names to user claim names. Keys are parameter names, values are user claim names.
    ClaimsJsonParameterNamestring"_user_claims"Parameter name for all available user claims. When not null and user is authenticated, all claims are serialized to JSON and set to this parameter.
    IpAddressParameterNamestring"_ip_address"Parameter name for IP address. When not null, IP address is set to this parameter when UseUserParameters is enabled (even for unauthenticated users).

    Note: Claim values are always passed as text type. For multi-value claims (like roles), values are passed as text[]. PostgreSQL handles type coercion to your parameter types.

    Default ParameterNameClaimsMapping

    json
    json
    {
    +  "_user_id": "user_id",
    +  "_user_name": "user_name",
    +  "_user_roles": "user_roles"
    +}

    Login and Logout Paths

    SettingTypeDefaultDescription
    LoginPathstringnullURL path for login endpoint. null disables login endpoint.
    LogoutPathstringnullURL path for logout endpoint. null disables logout endpoint.

    Login Command Convention

    The login command must follow these conventions:

    • Return at least one record for successful authentication
    • No records returned = 401 Unauthorized
    • All columns become user claims (column name = claim type, value = claim value)

    Special columns:

    ColumnTypeDescription
    statusbool/intSuccess indicator. Boolean or HTTP status code (200 = success).
    schemetextAuthentication scheme override.
    bodytextResponse body message.
    hashtextPassword hash for verification.

    Logout Command Convention

    • No return data = sign out default scheme
    • Returned values = scheme names to sign out (converted to string)

    Basic Authentication

    HTTP Basic Authentication settings. Expects Authorization: Basic base64(username:password) header.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    For detailed configuration options, examples, and challenge command parameters, see Basic Auth Configuration.

    Complete Example

    Production configuration with login endpoint and user context:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultAuthenticationType": "MyApp",
    +      "StatusColumnName": "status",
    +      "SchemeColumnName": "scheme",
    +      "HashColumnName": "hash",
    +      "PasswordParameterNameContains": "password",
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "user_name",
    +      "DefaultRoleClaimType": "user_roles",
    +      "ObfuscateAuthParameterLogValues": true,
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address",
    +      "LoginPath": "/api/auth/login",
    +      "LogoutPath": "/api/auth/logout"
    +    }
    +  }
    +}

    Next Steps

    See Also

    • AUTHORIZE - Require authentication on endpoints
    • LOGIN - Mark endpoint as sign-in
    • BASIC_AUTH - Enable Basic Auth per endpoint
    `,50)]))}const c=i(e,[["render",l]]);export{u as __pageData,c as default}; diff --git a/assets/config_authentication-options.md.CzhPcVla.lean.js b/assets/config_authentication-options.md.CzhPcVla.lean.js new file mode 100644 index 000000000..0df1d4b54 --- /dev/null +++ b/assets/config_authentication-options.md.CzhPcVla.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Authentication Options","titleTemplate":"NpgsqlRest","description":"Configure login/logout handling in NpgsqlRest. Password hashing, session management, claim types, and authentication response formatting.","frontmatter":{"outline":[2,3],"title":"Authentication Options","titleTemplate":"NpgsqlRest","description":"Configure login/logout handling in NpgsqlRest. Password hashing, session management, claim types, and authentication response formatting.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication options, login logout api, password hashing postgresql, session management api, claim types configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Options"}],["meta",{"property":"og:description","content":"Configure login/logout, password hashing, and authentication response handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/authentication-options.md","filePath":"config/authentication-options.md"}'),e={name:"config/authentication-options.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[n("",50)]))}const c=i(e,[["render",l]]);export{u as __pageData,c as default}; diff --git a/assets/config_basic-auth-config.md.DZCrOE7U.js b/assets/config_basic-auth-config.md.DZCrOE7U.js new file mode 100644 index 000000000..8e4a9321c --- /dev/null +++ b/assets/config_basic-auth-config.md.DZCrOE7U.js @@ -0,0 +1,100 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Basic Auth Configuration","titleTemplate":"NpgsqlRest","description":"Configure HTTP Basic Authentication in NpgsqlRest. Username/password validation, password hashing, SSL requirements, and PostgreSQL-backed authentication.","frontmatter":{"outline":[2,3],"title":"Basic Auth Configuration","titleTemplate":"NpgsqlRest","description":"Configure HTTP Basic Authentication in NpgsqlRest. Username/password validation, password hashing, SSL requirements, and PostgreSQL-backed authentication.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth, http basic authentication, authorization header api, password authentication postgresql, basic auth ssl"}],["meta",{"property":"og:title","content":"NpgsqlRest Basic Auth Configuration"}],["meta",{"property":"og:description","content":"Configure HTTP Basic Authentication with password hashing and SSL requirements."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/basic-auth-config.md","filePath":"config/basic-auth-config.md"}'),l={name:"config/basic-auth-config.md"};function e(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Basic Auth Configuration

    HTTP Basic Authentication support with Authorization: Basic base64(username:password) header.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Basic Authentication support.
    RealmstringnullAuthentication realm. Uses "NpgsqlRest" if null.
    Usersobject{}Username/password dictionary. Value is password or hash depending on UseDefaultPasswordHasher.
    SslRequirementstring"Required"SSL requirement: "Ignore", "Warning", or "Required".
    UseDefaultPasswordHasherbooltrueExpect hashed passwords in configuration.
    ChallengeCommandstringnullPostgreSQL command for authentication challenge.

    SSL Requirement Values

    ValueDescription
    IgnoreAllow Basic Auth without SSL (debug log warning).
    WarningIssue log warning when connection is not secure.
    RequiredEnforce SSL/TLS connection.

    Challenge Command Parameters

    ParameterTypeDescription
    $1textUsername from Basic Auth header.
    $2textPassword from Basic Auth header.
    $3boolPassword validation result (true/false/null if no password defined).
    $4textBasic Auth realm.
    $5textEndpoint path.

    Static Users Example

    Configure users directly in the configuration file:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": false,
    +        "Users": {
    +          "admin": "secret123",
    +          "user1": "password456"
    +        }
    +      }
    +    }
    +  }
    +}

    WARNING

    When UseDefaultPasswordHasher is false, passwords are stored in plain text. Use hashed passwords in production.

    Database Authentication Example

    Use a PostgreSQL function for authentication challenge:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
    +      }
    +    }
    +  }
    +}

    Challenge Function Example

    sql
    sql
    create function basic_auth_login(
    +    _username text,
    +    _password text,
    +    _validated bool
    +)
    +returns table (
    +    status bool,
    +    user_id int,
    +    user_name text,
    +    user_roles text[]
    +)
    +language plpgsql as $$
    +begin
    +    -- Check if password was validated by static users
    +    if _validated = true then
    +        return query
    +        select true, 1, _username, array['admin']::text[];
    +        return;
    +    end if;
    +
    +    -- Validate against database
    +    return query
    +    select
    +        u.password_hash = crypt(_password, u.password_hash),
    +        u.id,
    +        u.username,
    +        array_agg(r.role_name)
    +    from users u
    +    left join user_roles r on r.user_id = u.id
    +    where u.username = _username
    +    group by u.id, u.username, u.password_hash;
    +end;
    +$$;

    Equivalent as a SQL file challenge command (sql/basic-auth-login.sql):

    The challenge command is referenced from configuration (ChallengeCommand: "select * from basic_auth_login($1, $2, $3)"), so the call site stays the same. The implementation can also be a SQL file endpoint exposed as an internal helper:

    sql
    sql
    /*
    +HTTP POST
    +@internal
    +@param $1 username
    +@param $2 password
    +@param $3 validated boolean
    +*/
    +select
    +    u.password_hash = crypt($2, u.password_hash) as status,
    +    u.id as user_id,
    +    u.username as user_name,
    +    array_agg(r.role_name) as user_roles
    +from users u
    +left join user_roles r on r.user_id = u.id
    +where u.username = $1
    +group by u.id, u.username, u.password_hash;

    Complete Example

    Production configuration with Basic Authentication:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
    +      }
    +    }
    +  }
    +}

    Next Steps

    See Also

    `,31)]))}const u=i(l,[["render",e]]);export{c as __pageData,u as default}; diff --git a/assets/config_basic-auth-config.md.DZCrOE7U.lean.js b/assets/config_basic-auth-config.md.DZCrOE7U.lean.js new file mode 100644 index 000000000..6edf6bc9a --- /dev/null +++ b/assets/config_basic-auth-config.md.DZCrOE7U.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Basic Auth Configuration","titleTemplate":"NpgsqlRest","description":"Configure HTTP Basic Authentication in NpgsqlRest. Username/password validation, password hashing, SSL requirements, and PostgreSQL-backed authentication.","frontmatter":{"outline":[2,3],"title":"Basic Auth Configuration","titleTemplate":"NpgsqlRest","description":"Configure HTTP Basic Authentication in NpgsqlRest. Username/password validation, password hashing, SSL requirements, and PostgreSQL-backed authentication.","head":[["meta",{"name":"keywords","content":"npgsqlrest basic auth, http basic authentication, authorization header api, password authentication postgresql, basic auth ssl"}],["meta",{"property":"og:title","content":"NpgsqlRest Basic Auth Configuration"}],["meta",{"property":"og:description","content":"Configure HTTP Basic Authentication with password hashing and SSL requirements."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/basic-auth-config.md","filePath":"config/basic-auth-config.md"}'),l={name:"config/basic-auth-config.md"};function e(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",31)]))}const u=i(l,[["render",e]]);export{c as __pageData,u as default}; diff --git a/assets/config_cache-options.md.Cq5Q0JXd.js b/assets/config_cache-options.md.Cq5Q0JXd.js new file mode 100644 index 000000000..004385c0d --- /dev/null +++ b/assets/config_cache-options.md.Cq5Q0JXd.js @@ -0,0 +1,175 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Cache Options","titleTemplate":"NpgsqlRest","description":"Configure response caching for NpgsqlRest. Memory cache, Redis cache, hybrid caching, cache invalidation, and cache key configuration.","frontmatter":{"outline":[2,3],"title":"Cache Options","titleTemplate":"NpgsqlRest","description":"Configure response caching for NpgsqlRest. Memory cache, Redis cache, hybrid caching, cache invalidation, and cache key configuration.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache, postgresql api cache, redis cache api, memory cache rest api, cache invalidation, api response caching"}],["meta",{"property":"og:title","content":"NpgsqlRest Cache Options"}],["meta",{"property":"og:description","content":"Configure response caching with memory, Redis, or hybrid storage for PostgreSQL API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/cache-options.md","filePath":"config/cache-options.md"}'),t={name:"config/cache-options.md"};function l(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Cache Options

    Caching configuration for PostgreSQL routines.

    Overview

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": false,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 60,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
    +    "MaxCacheableRows": 1000,
    +    "UseHashedCacheKeys": false,
    +    "HashKeyThreshold": 256,
    +    "InvalidateCacheSuffix": null,
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": null,
    +    "HybridCacheLocalCacheExpiration": null,
    +    "Profiles": {}
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable caching for routines.
    Typestring"Memory"Cache type: "Memory", "Redis", or "Hybrid".
    MemoryCachePruneIntervalSecondsint60How often to prune expired items from memory cache (in seconds).
    RedisConfigurationstring(see below)Redis connection string. Used when Type is "Redis", or when Type is "Hybrid" with HybridCacheUseRedisBackend: true.
    MaxCacheableRowsint?1000Maximum number of rows that can be cached for set-returning functions. If a result set exceeds this limit, it will not be cached (but will still be returned). Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    UseHashedCacheKeysboolfalseWhen true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string. This reduces memory usage for long cache keys and improves Redis performance with large keys.
    HashKeyThresholdint256Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true. Keys shorter than this threshold are stored as-is for better debuggability.
    InvalidateCacheSuffixstring?nullWhen set, creates an additional invalidation endpoint for each cached endpoint. The invalidation endpoint has the same path with this suffix appended.
    HybridCacheUseRedisBackendboolfalseWhen Type is "Hybrid", enables Redis as the L2 (secondary/distributed) cache backend. When false, HybridCache uses in-memory only but still provides stampede protection.
    HybridCacheMaximumKeyLengthint1024Maximum length of cache keys in characters (Hybrid cache only). Keys longer than this will be rejected.
    HybridCacheMaximumPayloadBytesint1048576Maximum size of cached payloads in bytes (Hybrid cache only). Default is 1 MB.
    HybridCacheDefaultExpirationstring?nullDefault expiration for cached entries. Accepts PostgreSQL interval format (e.g., "5 minutes", "1 hour"). If not set, individual endpoint cache_expires annotations are used, or entries don't expire.
    HybridCacheLocalCacheExpirationstring?nullExpiration for L1 (in-memory) cache in Hybrid mode. Set shorter than HybridCacheDefaultExpiration to refresh local cache more frequently from Redis. Accepts PostgreSQL interval format.
    Profilesobject?nullNamed caching profiles. Each profile selects its own backend, default expiration, key parameters, and When rules. Endpoints opt in via the @cache_profile annotation. See Cache Profiles below.

    Cache Types

    Memory Cache

    In-memory caching on the application server:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 60
    +  }
    +}

    The MemoryCachePruneIntervalSeconds setting controls how frequently expired cache entries are removed.

    Redis Cache

    Distributed caching using Redis:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3"
    +  }
    +}

    See StackExchange.Redis Configuration for connection string options.

    Hybrid Cache

    HybridCache uses Microsoft's Microsoft.Extensions.Caching.Hybrid library to provide:

    • Stampede protection: Prevents multiple concurrent requests from hitting the database when cache expires
    • Optional Redis L2 backend: Can use Redis as a distributed secondary cache for sharing across instances
    • In-memory L1 cache: Fast local cache for frequently accessed data

    Basic HybridCache (in-memory with stampede protection):

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheDefaultExpiration": "5 minutes"
    +  }
    +}

    HybridCache with Redis backend:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": true,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false",
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "HybridCacheLocalCacheExpiration": "1 minute"
    +  }
    +}

    When HybridCacheUseRedisBackend is false (default), HybridCache works as an in-memory cache with stampede protection. When true, it uses Redis as the L2 distributed cache for sharing across multiple application instances.

    When to use HybridCache:

    • When you need stampede protection (prevents thundering herd on cache expiry)
    • When running multiple application instances that need to share cache
    • When you want the best of both worlds: fast local cache + distributed storage

    Cache Key Hashing

    For improved performance with Redis cache, especially when routines have many or large parameters, you can enable cache key hashing:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "UseHashedCacheKeys": true,
    +    "HashKeyThreshold": 256
    +  }
    +}

    When enabled, cache keys exceeding the threshold are automatically hashed to a fixed 64-character SHA256 string, reducing:

    • Memory usage for storing long cache keys
    • Network transfer overhead with Redis
    • Redis server memory consumption

    This is particularly recommended when:

    • Using Redis cache with routines that have many or large parameters
    • Caching routines with long SQL expressions
    • High cache hit rates where memory efficiency matters

    Caching Set-Returning Functions

    Caching now works for set-returning functions and record types, not just single scalar values. When a cached function returns multiple rows, the entire result set is cached and returned on subsequent calls.

    Use MaxCacheableRows to limit memory usage:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "MaxCacheableRows": 1000
    +  }
    +}

    If a result set exceeds this limit, it will still be returned but will not be cached.

    Cache Invalidation Endpoints

    NpgsqlRest can automatically create invalidation endpoints for each cached endpoint. When InvalidateCacheSuffix is configured, calling the invalidation endpoint with the same parameters removes the corresponding cache entry.

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "InvalidateCacheSuffix": "invalidate"
    +  }
    +}

    Example usage:

    code
    GET /api/get-user/?id=123           -> Returns cached user data
    +GET /api/get-user/invalidate?id=123 -> Removes cache entry, returns {"invalidated":true}
    +GET /api/get-user/?id=123           -> Fresh data (cache was cleared)

    Key features:

    • Same authentication and authorization as the original endpoint
    • Same parameter handling - no need to know the internal cache key format
    • Works correctly with hashed cache keys
    • Returns {"invalidated":true} if cache entry was removed, {"invalidated":false} if not found

    Cache Profiles

    Cache profiles let you maintain multiple distinct caching policies in one application — different backends, expirations, key shapes, or per-parameter bypass conditions — and let endpoints opt into them via the @cache_profile annotation.

    This is useful when one app needs:

    • Different cache backends for different data classes (e.g., Memory for hot per-user data, Redis for shared session data).
    • Different TTLs depending on input shape (e.g., historical queries cached for 1 hour, "until now" queries cached for 5 minutes).
    • Selective cache bypass (e.g., real-time queries with live=true always fetch fresh).

    Overview

    jsonc
    jsonc
    "CacheOptions": {
    +  "Enabled": true,
    +  "Type": "Memory",                 // root cache (used by endpoints WITHOUT @cache_profile)
    +  // ... existing top-level fields ...
    +  "Profiles": {
    +    "fast_memory": {
    +      "Enabled": true,
    +      "Type": "Memory",
    +      "Expiration": "30 seconds",
    +      "Parameters": ["user_id"]
    +    },
    +    "shared_redis": {
    +      "Enabled": true,
    +      "Type": "Redis",
    +      "Expiration": "1 hour"
    +    },
    +    "timeseries": {
    +      "Enabled": true,
    +      "Type": "Memory",
    +      "Expiration": "1 hour",
    +      "Parameters": ["from", "to"],
    +      "When": [
    +        { "Parameter": "to", "Value": null, "Then": "5 minutes" }
    +      ]
    +    }
    +  }
    +}

    Endpoints without @cache_profile continue to use the root cache. Endpoints with @cache_profile <name> use the named profile.

    Profile fields

    FieldTypeDescription
    EnabledboolDefault false. Set true to register the profile. Disabled profiles are skipped at startup with an Information log.
    Typestring"Memory", "Redis", or "Hybrid". Required when Enabled=true.
    Expirationstring?Default expiration (PostgreSQL interval format, e.g. "5 minutes", "1 hour"). Used when the endpoint has no @cache_expires annotation.
    Parametersstring[]?Default cache-key parameter list. Three semantics: null/missing → use all routine parameters; [] → URL-only cache (one entry per endpoint); ["x", "y"] → use only these. The endpoint's @cached p1, p2 annotation overrides this.
    Whenobject[]?List of conditional rules evaluated at request time (see below).

    Backend pooling

    All profiles of the same Type share a single backend instance: one Memory cache, one Redis connection, one HybridCache singleton. Backends are instantiated lazily — only types actually used (root + at least one enabled profile) get spun up. If no profile uses Redis and the root Type isn't Redis, no Redis connection is ever attempted, even if RedisConfiguration is set.

    Cache entries written under a profile are prefixed with the profile name so two profiles sharing the same Memory backend cannot collide on the same routine + parameters. Endpoints without a profile have no prefix; existing pre-3.13 cache entries remain wire-compatible.

    When rules

    When is a list of rules evaluated against the request's resolved parameter values. Each rule has three fields:

    FieldTypeDescription
    ParameterstringRoutine parameter name to inspect (matches against ActualName or ConvertedName). Required.
    Valuescalar / array / nullMatch condition. Scalar = exact match. Array = OR over entries. JSON null matches .NET null/DBNull.Value (does not match empty string). Other values are stringify-and-equal case-insensitive.
    ThenstringRequired action: the literal string "skip" to bypass the cache for this request, OR a PostgreSQL interval (e.g. "30 seconds", "5 minutes", "1 hour") to override the entry's TTL when writing.

    Rules are evaluated in declaration order; first match wins. If no rule matches, the entry is cached using the profile's Expiration.

    Pattern: skip-on-condition

    Bypass the cache entirely for some inputs:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "to", "Value": null, "Then": "skip" }
    +]

    When to is null/missing, no read or write happens — routine executes fresh. Common for "until-now" or "live" data.

    Pattern: dynamic TTL

    Different TTLs depending on input shape:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "live", "Value": true,  "Then": "skip" },
    +  { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
    +]
    • live=true → bypass entirely (real-time mode).
    • live=false and to=null → 5-minute TTL (open-ended query).
    • Otherwise → fall through to the profile's Expiration (e.g. 1 hour for historical queries with both from and to).

    Pattern: array-of-values

    Match any of several values:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "status", "Value": [null, ""], "Then": "skip" }
    +]

    Matches when status is null OR empty string.

    Validation

    Misconfiguration is caught at startup so deploy issues surface early rather than silently disabling caching at runtime:

    ProblemResult
    @cache_profile references an unknown nameStartup fails with single InvalidOperationException listing every unresolved name and the endpoints that referenced each
    Profile registered but no endpoint references itInformation log: "registered but not used by any endpoint. Did you forget a @cache_profile annotation?"
    Profile has missing/invalid TypeWarning, profile skipped
    Profile has invalid Expiration (bad PG interval)Warning, profile skipped
    Profile name is empty/whitespaceWarning, profile skipped
    When rule's Parameter isn't a routine parameterWarning, rule dropped (other rules still apply)
    When rule's Parameter isn't in the resolved cache-key listWarning, rule dropped (otherwise different rule-evaluations would share a cache entry)
    When rule has missing/invalid ThenWarning, rule dropped

    Connection pooler note

    If you're using a connection pooler in transaction mode (PgBouncer, AWS RDS Proxy in transaction mode, Supabase Pooler), see WrapInTransaction — it's required for context-injection features but does not affect profiles directly.

    Complete example

    jsonc
    jsonc
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MaxCacheableRows": 1000,
    +    "InvalidateCacheSuffix": "invalidate",
    +    "Profiles": {
    +      "user_scoped_fast": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 minute",
    +        "Parameters": ["user_id"]
    +      },
    +      "shared_long_term": {
    +        "Enabled": true,
    +        "Type": "Redis",
    +        "Expiration": "1 hour"
    +      },
    +      "timeseries_compute": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 hour",
    +        "Parameters": ["from", "to", "live"],
    +        "When": [
    +          { "Parameter": "live", "Value": true,  "Then": "skip" },
    +          { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
    +        ]
    +      }
    +    }
    +  }
    +}
    sql
    sql
    -- Uses root Memory cache (no profile)
    +comment on function get_app_settings() is 'HTTP GET
    +@cached
    +@cache_expires 1 hour';
    +
    +-- Per-user 1-minute cache via fast Memory profile
    +comment on function get_my_dashboard(user_id int) is 'HTTP GET
    +@cache_profile user_scoped_fast';
    +
    +-- Distributed Redis with 1-hour default
    +comment on function get_global_metrics() is 'HTTP GET
    +@cache_profile shared_long_term';
    +
    +-- Mixed: long-cache historical queries, short-cache open-ended,
    +-- bypass entirely when \`live\` is true
    +comment on function compute_timeseries(from text, to text default null, live boolean default false) is 'HTTP GET
    +@cache_profile timeseries_compute';

    Routine Annotations

    Enable caching for specific routines using comment annotations:

    cached

    Mark a routine as cacheable:

    sql
    sql
    comment on function get_products() is '
    +HTTP GET /products
    +@cached
    +';

    Specify which parameters to use for the cache key:

    sql
    sql
    comment on function get_product(p_id int) is '
    +HTTP GET /products
    +@cached p_id
    +';

    If no parameters are specified, all parameters are used for the cache key.

    cache_expires / cache_expires_in

    Set cache expiration using interval format:

    sql
    sql
    comment on function get_products() is '
    +HTTP GET /products
    +@cached
    +@cache_expires 5m
    +';
    sql
    sql
    comment on function get_config() is '
    +HTTP GET /config
    +@cached
    +@cache_expires_in 1h
    +';

    If no expiration is specified, cache entries never expire.

    cache_profile

    Select a named cache profile for the endpoint:

    sql
    sql
    comment on function get_dashboard() is '
    +HTTP GET
    +@cache_profile fast_memory
    +';

    @cache_profile implies caching — @cached is unnecessary alongside it but is still allowed and overrides the profile's Parameters list. See the dedicated @cache_profile annotation reference for full semantics.

    Example Configuration

    Production configuration with Redis:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "RedisConfiguration": "redis-server:6379,password={REDIS_PASSWORD},ssl=true,abortConnect=false"
    +  }
    +}

    Development configuration with memory cache:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 30
    +  }
    +}

    Next Steps

    See Also

    `,108)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_cache-options.md.Cq5Q0JXd.lean.js b/assets/config_cache-options.md.Cq5Q0JXd.lean.js new file mode 100644 index 000000000..b0ca5af88 --- /dev/null +++ b/assets/config_cache-options.md.Cq5Q0JXd.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Cache Options","titleTemplate":"NpgsqlRest","description":"Configure response caching for NpgsqlRest. Memory cache, Redis cache, hybrid caching, cache invalidation, and cache key configuration.","frontmatter":{"outline":[2,3],"title":"Cache Options","titleTemplate":"NpgsqlRest","description":"Configure response caching for NpgsqlRest. Memory cache, Redis cache, hybrid caching, cache invalidation, and cache key configuration.","head":[["meta",{"name":"keywords","content":"npgsqlrest cache, postgresql api cache, redis cache api, memory cache rest api, cache invalidation, api response caching"}],["meta",{"property":"og:title","content":"NpgsqlRest Cache Options"}],["meta",{"property":"og:description","content":"Configure response caching with memory, Redis, or hybrid storage for PostgreSQL API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/cache-options.md","filePath":"config/cache-options.md"}'),t={name:"config/cache-options.md"};function l(h,s,p,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",108)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_claims-mapping.md.DxUSHFxx.js b/assets/config_claims-mapping.md.DxUSHFxx.js new file mode 100644 index 000000000..7e7fcfca3 --- /dev/null +++ b/assets/config_claims-mapping.md.DxUSHFxx.js @@ -0,0 +1,124 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Claims Mapping Configuration","titleTemplate":"NpgsqlRest","description":"Map authenticated user claims to PostgreSQL context variables and function parameters. Pass user ID, roles, and custom claims to SQL functions automatically.","frontmatter":{"outline":[2,3],"title":"Claims Mapping Configuration","titleTemplate":"NpgsqlRest","description":"Map authenticated user claims to PostgreSQL context variables and function parameters. Pass user ID, roles, and custom claims to SQL functions automatically.","head":[["meta",{"name":"keywords","content":"npgsqlrest claims mapping, postgresql user context, jwt claims postgresql, user id parameter, role based access postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Claims Mapping Configuration"}],["meta",{"property":"og:description","content":"Map user claims to PostgreSQL context variables and function parameters automatically."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/claims-mapping.md","filePath":"config/claims-mapping.md"}'),e={name:"config/claims-mapping.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Claims Mapping

    Configure how authenticated user claims are mapped to PostgreSQL context variables and function parameters.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": false,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "ClaimsJsonContextKey": null,
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": false,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address"
    +    }
    +  }
    +}

    User Context (PostgreSQL Context Variables)

    Map authenticated user claims to PostgreSQL session context variables. Enable for specific endpoints using the user_context annotation, or enable globally with UseUserContext.

    SettingTypeDefaultDescription
    UseUserContextboolfalseEnable automatic claim-to-context mapping for all endpoints. Override per-endpoint with user_context annotation.
    ContextKeyClaimsMappingobject(see below)Map of PostgreSQL context keys to claim names. Key is the context variable name, value is the claim type.
    ClaimsJsonContextKeystringnullContext key for all claims serialized as JSON. Set to "request.user_claims" to enable.
    IpAddressContextKeystring"request.ip_address"Context key for client IP address.

    Default Context Mapping

    json
    json
    {
    +  "ContextKeyClaimsMapping": {
    +    "request.user_id": "user_id",
    +    "request.user_name": "user_name",
    +    "request.user_roles": "user_roles"
    +  }
    +}

    Custom Context Mapping Example

    Map additional claims to custom context keys:

    json
    json
    {
    +  "ContextKeyClaimsMapping": {
    +    "request.user_id": "user_id",
    +    "request.user_name": "user_name",
    +    "request.user_roles": "user_roles",
    +    "request.user_email": "email",
    +    "request.tenant_id": "tenant_id"
    +  },
    +  "ClaimsJsonContextKey": "request.user_claims"
    +}

    Access in PostgreSQL

    sql
    sql
    -- Access individual claims
    +select current_setting('request.user_id', true);
    +select current_setting('request.user_name', true);
    +select current_setting('request.user_roles', true);
    +
    +-- Access client IP address
    +select current_setting('request.ip_address', true);
    +
    +-- Access all claims as JSON (when ClaimsJsonContextKey is configured)
    +select current_setting('request.user_claims', true)::jsonb;

    TIP

    Always use true as the second parameter to current_setting() to avoid errors when the setting doesn't exist.

    User Parameters

    Map authenticated user claims to function parameters. Enable for specific endpoints using the user_parameters annotation, or enable globally with UseUserParameters.

    SettingTypeDefaultDescription
    UseUserParametersboolfalseEnable automatic claim-to-parameter mapping for all endpoints. Override per-endpoint with user_parameters annotation.
    ParameterNameClaimsMappingobject(see below)Map of function parameter names to claim names. Key is the parameter name, value is the claim type.
    ClaimsJsonParameterNamestring"_user_claims"Parameter name that receives all claims serialized as JSON.
    IpAddressParameterNamestring"_ip_address"Parameter name that receives the client IP address.

    Default Parameter Mapping

    json
    json
    {
    +  "ParameterNameClaimsMapping": {
    +    "_user_id": "user_id",
    +    "_user_name": "user_name",
    +    "_user_roles": "user_roles"
    +  }
    +}

    Custom Parameter Mapping Example

    Map additional claims to custom parameter names:

    json
    json
    {
    +  "ParameterNameClaimsMapping": {
    +    "_user_id": "user_id",
    +    "_user_name": "user_name",
    +    "_user_roles": "user_roles",
    +    "_email": "email",
    +    "_tenant": "tenant_id"
    +  },
    +  "ClaimsJsonParameterName": "_user_claims",
    +  "IpAddressParameterName": "_ip_address"
    +}

    Example Function Using Parameters

    sql
    sql
    create function get_user_data(
    +    _user_id text,
    +    _user_name text,
    +    _user_roles text[],
    +    _ip_address text,
    +    _user_claims json
    +)
    +returns table (
    +    user_id int,
    +    user_name text,
    +    roles text[],
    +    ip text,
    +    all_claims json
    +)
    +language sql
    +begin atomic;
    +select
    +    _user_id::int,
    +    _user_name,
    +    _user_roles,
    +    _ip_address,
    +    _user_claims;
    +end;
    +
    +comment on function get_user_data(text, text, text[], text, json) is '
    +@authorize
    +@user_params
    +';

    Equivalent as a SQL file endpoint (sql/get-user-data.sql):

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@user_params
    +@param $1 user_id text
    +@param $2 user_name text
    +@param $3 user_roles text[]
    +@param $4 ip_address text
    +@param $5 user_claims json
    +*/
    +select
    +    $1::int as user_id,
    +    $2 as user_name,
    +    $3 as roles,
    +    $4 as ip,
    +    $5 as all_claims;

    TIP

    Parameters with default values can be used without authentication. When the user is authenticated, claim values override the defaults.

    Complete Example

    Configuration with user context and parameters enabled:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address"
    +    }
    +  }
    +}

    Next Steps

    See Also

    `,37)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_claims-mapping.md.DxUSHFxx.lean.js b/assets/config_claims-mapping.md.DxUSHFxx.lean.js new file mode 100644 index 000000000..1e74f4485 --- /dev/null +++ b/assets/config_claims-mapping.md.DxUSHFxx.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Claims Mapping Configuration","titleTemplate":"NpgsqlRest","description":"Map authenticated user claims to PostgreSQL context variables and function parameters. Pass user ID, roles, and custom claims to SQL functions automatically.","frontmatter":{"outline":[2,3],"title":"Claims Mapping Configuration","titleTemplate":"NpgsqlRest","description":"Map authenticated user claims to PostgreSQL context variables and function parameters. Pass user ID, roles, and custom claims to SQL functions automatically.","head":[["meta",{"name":"keywords","content":"npgsqlrest claims mapping, postgresql user context, jwt claims postgresql, user id parameter, role based access postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Claims Mapping Configuration"}],["meta",{"property":"og:description","content":"Map user claims to PostgreSQL context variables and function parameters automatically."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/claims-mapping.md","filePath":"config/claims-mapping.md"}'),e={name:"config/claims-mapping.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",37)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_codegen.md.opHl7x9x.js b/assets/config_codegen.md.opHl7x9x.js new file mode 100644 index 000000000..62a4cc752 --- /dev/null +++ b/assets/config_codegen.md.opHl7x9x.js @@ -0,0 +1,225 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Code Generation Configuration","titleTemplate":"NpgsqlRest","description":"Generate TypeScript/JavaScript client code from your PostgreSQL API. Auto-create type-safe API clients with full type definitions from your database schema.","frontmatter":{"outline":[2,3],"title":"Code Generation Configuration","titleTemplate":"NpgsqlRest","description":"Generate TypeScript/JavaScript client code from your PostgreSQL API. Auto-create type-safe API clients with full type definitions from your database schema.","head":[["meta",{"name":"keywords","content":"npgsqlrest codegen, postgresql typescript, generate api client, type-safe api, postgresql to typescript, auto-generate types"}],["meta",{"property":"og:title","content":"NpgsqlRest Code Generation Configuration"}],["meta",{"property":"og:description","content":"Generate TypeScript/JavaScript client code from your PostgreSQL API with full type definitions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/codegen.md","filePath":"config/codegen.md"}'),e={name:"config/codegen.md"};function l(h,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`

    Code Generation

    Configuration for generating TypeScript/JavaScript client code for NpgsqlRest endpoints.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": false,
    +      "FilePath": null,
    +      "FileOverwrite": true,
    +      "IncludeHost": true,
    +      "CustomHost": null,
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "BySchema": true,
    +      "IncludeStatusCode": true,
    +      "CreateSeparateTypeFile": true,
    +      "ExportTypes": false,
    +      "ImportBaseUrlFrom": null,
    +      "ImportParseQueryFrom": null,
    +      "IncludeParseUrlParam": false,
    +      "IncludeParseRequestParam": false,
    +      "HeaderLines": ["// autogenerated at {0}", ""],
    +      "SkipRoutineNames": [],
    +      "SkipFunctionNames": [],
    +      "SkipPaths": [],
    +      "SkipSchemas": [],
    +      "DefaultJsonType": "any",
    +      "UseRoutineNameInsteadOfEndpoint": false,
    +      "ExportUrls": false,
    +      "SkipTypes": false,
    +      "UniqueModels": false,
    +      "XsrfTokenHeaderName": null,
    +      "ExportEventSources": true,
    +      "CustomImports": [],
    +      "CustomHeaders": {},
    +      "IncludeSchemaInNames": true,
    +      "ErrorExpression": "await response.json()",
    +      "ErrorType": "{status: number; title: string; detail?: string | null} | undefined",
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable client code generation.
    FilePathstringnullOutput file path. Use {0} for schema name when BySchema is true. null to skip.
    FileOverwritebooltrueOverwrite existing files.
    BySchemabooltrueCreate separate files per PostgreSQL schema.
    IncludeSchemaInNamesbooltrueInclude schema name in generated type names to avoid collisions.

    Host Configuration

    SettingTypeDefaultDescription
    IncludeHostbooltrueInclude current host in URL prefix.
    CustomHoststringnullCustom host prefix for URLs.

    Comment Headers

    SettingTypeDefaultDescription
    CommentHeaderstring"Simple"Comment header style: "None", "Simple", or "Full".
    CommentHeaderIncludeCommentsbooltrueInclude routine comments in header.

    Comment Header Styles

    StyleDescription
    NoneNo comment header.
    SimpleAdd routine name, parameters, and return values (default).
    FullAdd entire routine code as comment header.

    Response Options

    SettingTypeDefaultDescription
    IncludeStatusCodebooltrueInclude status code in response: {status: response.status, response: model}.
    ErrorExpressionstring"await response.json()"Expression to parse error responses. Only used when IncludeStatusCode is true.
    ErrorTypestring(see below)TypeScript type for error responses. Only used when IncludeStatusCode is true.

    Default ErrorType: "{status: number; title: string; detail?: string | null} | undefined"

    These options allow customization of error handling in generated code. Void functions and procedures also return the error object when IncludeStatusCode is true.

    Type Generation

    SettingTypeDefaultDescription
    CreateSeparateTypeFilebooltrueCreate separate {name}Types.d.ts file for global types.
    ExportTypesboolfalseEmit interfaces with the export keyword so they can be imported by other modules. When true and CreateSeparateTypeFile is true, the separate type file becomes an importable module {name}Types.ts (instead of an ambient {name}Types.d.ts) and the client file imports the named types from it. No effect when SkipTypes is true.
    DefaultJsonTypestring"any"Default TypeScript type for JSON types.
    SkipTypesboolfalseSkip type generation for pure JavaScript output (changes .ts to .js).
    UniqueModelsboolfalseMerge models with same fields/types into one (reduces generated models).
    OmitAutomaticParametersboolfalseOmit server-filled parameters from the generated request interface, query string, and body. See OmitAutomaticParameters: true.

    Import Configuration

    SettingTypeDefaultDescription
    ImportBaseUrlFromstringnullModule to import baseUrl constant from.
    ImportParseQueryFromstringnullModule to import parseQuery function from.
    CustomImportsarray[]Custom import statements (full expressions).

    Function Parameters

    SettingTypeDefaultDescription
    IncludeParseUrlParamboolfalseInclude parseUrl: (url: string) => string parameter.
    IncludeParseRequestParamboolfalseInclude parseRequest: (request: RequestInit) => RequestInit parameter.

    Skip Options

    SettingTypeDefaultDescription
    SkipRoutineNamesarray[]Routine names to skip (without schema).
    SkipFunctionNamesarray[]Generated function names to skip (without schema).
    SkipPathsarray[]URL paths to skip.
    SkipSchemasarray[]Schema names to skip.

    Export Options

    SettingTypeDefaultDescription
    ExportUrlsboolfalseExport URLs as constants.
    ExportEventSourcesbooltrueExport EventSource create functions for streaming events.
    UseRoutineNameInsteadOfEndpointboolfalseUse routine name instead of endpoint name for functions.

    Headers and Security

    SettingTypeDefaultDescription
    CustomHeadersobject{}Custom headers added to each request.
    XsrfTokenHeaderNamestringnullXSRF token header name for anti-forgery (used in upload FORM POSTs).

    File Headers

    SettingTypeDefaultDescription
    HeaderLinesarray["// autogenerated at {0}", ""]Header lines for generated files. {0} = timestamp.

    What Gets Generated

    This section walks through what the generated TypeScript actually looks like for each setting that affects output shape. All examples below are taken verbatim from real projects.

    Default Function Shape

    For a PostgreSQL function like:

    sql
    sql
    create function public.who_am_i(
    +    _user_id text default null,
    +    _username text default null,
    +    _email text default null
    +) returns table(user_id text, username text, email text)
    +language sql security definer as $$
    +  select $1, $2, $3;
    +$$;
    +
    +comment on function public.who_am_i is 'HTTP GET
    +@authorize
    +@user_parameters';

    Equivalent as a SQL file endpoint (sql/who-am-i.sql):

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@user_parameters
    +@param $1 user_id text
    +@param $2 username text
    +@param $3 email text
    +*/
    +select $1 as user_id, $2 as username, $3 as email;

    The TypeScript client generator treats both sources identically — the same IWhoAmIRequest / IWhoAmIResponse shapes and whoAmI() function are produced regardless of whether the endpoint is a function or a SQL file.

    The generated TypeScript client looks like this:

    typescript
    typescript
    interface IWhoAmIRequest {
    +    userId?: string | null;
    +    username?: string | null;
    +    email?: string | null;
    +}
    +
    +interface IWhoAmIResponse {
    +    userId: string | null;
    +    username: string | null;
    +    email: string | null;
    +}
    +
    +export async function whoAmI(
    +    request: IWhoAmIRequest
    +) : Promise<{
    +    status: number,
    +    response: IWhoAmIResponse,
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
    +        method: "GET"
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IWhoAmIResponse : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0"
    +            ? await response.json() as {status: number; title: string; detail?: string | null}
    +            : undefined
    +    };
    +}

    PostgreSQL parameter names (snake_case) are converted to camelCase. Optional parameters (those with DEFAULT) become ? properties. The IncludeStatusCode setting (default true) wraps every response in { status, response, error } — this is what makes error handling consistent across every call.

    IncludeStatusCode: false — Direct Response

    Set IncludeStatusCode: false to skip the wrapper:

    typescript
    typescript
    export async function whoAmI(request: IWhoAmIRequest): Promise<IWhoAmIResponse> {
    +    const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
    +        method: "GET"
    +    });
    +    return await response.json() as IWhoAmIResponse;
    +}

    Errors throw or surface as runtime exceptions instead of being part of the return type. Use this if you have application-level error handling middleware.

    OmitAutomaticParameters: true

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the HTTP File and OpenAPI generators). Default is false, so generated output is unchanged unless you opt in.

    Some parameters are filled by the server, so a value passed from the client would simply be ignored — emitting them as settable request properties is misleading. When true, such a parameter is dropped from the generated request interface, the query string, and the body when it is automatic and optional. "Automatic" covers:

    For a function whose only client-settable parameter is query, with an HTTP Custom Type field responseBody filled server-side:

    typescript
    typescript
    // OmitAutomaticParameters: false (default) — responseBody appears even though the server overrides it
    +interface ISearchRequest {
    +    query?: string | null;
    +    responseBody?: string | null;
    +}
    +
    +// OmitAutomaticParameters: true — only the real input remains
    +interface ISearchRequest {
    +    query?: string | null;
    +}

    When every parameter is automatic, the request shape collapses entirely — the generated function takes no request argument:

    typescript
    typescript
    export async function ping(): Promise<{ status: number; response: IPingResponse; /* ... */ }> {
    +    const response = await fetch(baseUrl + "/api/ping", { method: "GET" });
    +    // ...
    +}

    CreateSeparateTypeFile: true — Type-Only Files

    When true (default), interfaces are emitted into a sibling .d.ts file:

    text
    text
    src/api/userApi.ts        ← functions
    +src/api/userApiTypes.d.ts ← interfaces (type-only)

    The .d.ts file is pure type declarations:

    typescript
    typescript
    //
    +// autogenerated file - do not edit
    +//
    +interface IWhoAmIRequest {
    +    userId?: string | null;
    +    username?: string | null;
    +    email?: string | null;
    +}
    +
    +interface IWhoAmIResponse {
    +    user_id: string | null;
    +    username: string | null;
    +    email: string | null;
    +}

    Set CreateSeparateTypeFile: false to emit interfaces inline in the same file as the functions.

    ExportTypes: true — Importable Interfaces

    By default, interfaces are emitted as plain interface declarations. That makes them module-private when inlined (CreateSeparateTypeFile: false) and ambient/global when in the separate .d.ts file — in neither case can another module import them. Set ExportTypes: true to emit them as export interface so they can be imported.

    Inline (CreateSeparateTypeFile: false) — interfaces and functions share one file, with the interfaces now exported:

    typescript
    typescript
    export interface ISearchProductsRequest {
    +    query?: string | null;
    +    maxPrice?: number | null;
    +}
    +
    +export interface ISearchProductsResponse {
    +    id: number | null;
    +    name: string | null;
    +    price: number | null;
    +}
    +
    +export async function searchProducts(
    +    request: ISearchProductsRequest
    +) : Promise<ApiResult<ISearchProductsResponse[]>> {
    +    // ...
    +}

    Separate file (CreateSeparateTypeFile: true) — the type file becomes an importable module {name}Types.ts (not an ambient {name}Types.d.ts), and the client file imports the named types from it:

    text
    text
    src/api/searchProducts.ts        ← functions + \`import type { ... } from "./searchProductsTypes"\`
    +src/api/searchProductsTypes.ts   ← \`export interface ...\`
    typescript
    typescript
    // searchProducts.ts
    +import type { ISearchProductsRequest, ISearchProductsResponse } from "./searchProductsTypes";
    +const baseUrl = "";
    +// ...

    ExportTypes has no effect when SkipTypes is true (no types are generated). Defaulting to false keeps existing output unchanged.

    ExportUrls: true — URL Constants

    When enabled, a URL builder for each endpoint is exported:

    typescript
    typescript
    export const cancelComputeUrl = () => baseUrl + "/api/cancel-compute";
    +export const computeVisualizationUrl = (request: IComputeVisualizationRequest) =>
    +    baseUrl + "/api/compute-visualization" + parseQuery(request);

    Useful when you need to construct a URL but don't want to make the request immediately — for <a href> links, <form action> attributes, or passing to a third-party library.

    ExportEventSources: true — SSE Helpers

    For endpoints with the @sse annotation, an EventSource constructor is exported:

    typescript
    typescript
    export const createComputeVisualizationEventSource = (id: string = "") =>
    +    new EventSource(baseUrl + "/api/compute-visualization/info?" + id);

    The optional id parameter scopes the event stream to a specific execution. See the SSE annotation for usage.

    ImportBaseUrlFrom & ImportParseQueryFrom

    By default, generated files include their own baseUrl constant and parseQuery helper. To share these across files, point them to a module that exports them:

    jsonc
    jsonc
    {
    +  "ImportBaseUrlFrom": "$lib/urls",
    +  "ImportParseQueryFrom": "$lib/urls"
    +}

    Generated files now import instead of inlining:

    typescript
    typescript
    //
    +// autogenerated file - do not edit
    +//
    +import { baseUrl } from "$lib/urls";
    +import { parseQuery } from "$lib/urls";

    Where $lib/urls.ts is a file you maintain:

    typescript
    typescript
    export const baseUrl = import.meta.env.VITE_API_BASE_URL ?? "";
    +
    +export const parseQuery = (query: Record<string, any>) => "?" + Object.keys(query ?? {})
    +    .map(key => {
    +        const value = query[key] ?? "";
    +        if (Array.isArray(value)) {
    +            return value.map(s => s ? \`\${key}=\${encodeURIComponent(s)}\` : \`\${key}=\`).join("&");
    +        }
    +        return \`\${key}=\${encodeURIComponent(value as string)}\`;
    +    })
    +    .join("&");

    This is the recommended pattern for SvelteKit / Next.js / Vite apps where baseUrl should come from environment variables.

    Path Parameters

    When endpoints use path parameters (e.g., @path /products/{p_id}), template literals are used in URLs:

    typescript
    typescript
    export async function getProduct(request: { pId: number }) {
    +    const response = await fetch(\`\${baseUrl}/products/\${request.pId}\`, {
    +        method: "GET"
    +    });
    +    return { status: response.status, response: await response.json() };
    +}

    parseQuery is only emitted when at least one endpoint has actual query-string parameters. Endpoints with only path parameters skip the helper entirely.

    UseRoutineNameInsteadOfEndpoint: true

    By default, function names come from the URL path (kebab-case → camelCase): /api/who-am-iwhoAmI().

    With UseRoutineNameInsteadOfEndpoint: true, function names come from the PostgreSQL routine name instead: public.who_am_iwhoAmI().

    Useful when you customize URL paths via @path annotations but want function names that still match the SQL routine names. Combines well with IncludeSchemaInNames: false to drop schema prefixes from generated names.

    BySchema: true — One File Per Schema

    Default behavior. With FilePath: "./src/api/{0}Api.ts", the {0} placeholder is replaced with each schema name:

    text
    text
    src/api/publicApi.ts       ← from public schema
    +src/api/publicApiTypes.d.ts
    +src/api/billingApi.ts      ← from billing schema
    +src/api/billingApiTypes.d.ts

    Set BySchema: false and use a fixed filename (no {0}) to emit a single combined file.

    Example Configurations

    Minimal (Examples Repo Style)

    The simplest setup — one file per schema, everything else default:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/{0}Api.ts"
    +    }
    +  }
    +}

    This is what every example in the examples repository uses.

    Single JavaScript File (No Types)

    Use this when you don't want TypeScript:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/api/client.js",
    +      "BySchema": false,
    +      "SkipTypes": true,
    +      "IncludeSchemaInNames": false
    +    }
    +  }
    +}

    SkipTypes: true removes all TypeScript syntax (interfaces, type annotations) so the file is valid JavaScript despite the .ts.js extension.

    Production SvelteKit / Vite Setup

    Real-world configuration with shared baseUrl/parseQuery from a $lib alias, URL constants for use in templates, and routine-name-based function naming:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/app/api/{0}Api.ts",
    +      "FileOverwrite": true,
    +      "IncludeHost": true,
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "BySchema": true,
    +      "IncludeStatusCode": true,
    +      "CreateSeparateTypeFile": true,
    +      "ImportBaseUrlFrom": "$lib/urls",
    +      "ImportParseQueryFrom": "$lib/urls",
    +      "DefaultJsonType": "string",
    +      "UseRoutineNameInsteadOfEndpoint": true,
    +      "ExportUrls": true,
    +      "ExportEventSources": true,
    +      "IncludeSchemaInNames": false,
    +      "HeaderLines": [
    +        "//",
    +        "// autogenerated file - do not edit",
    +        "//"
    +      ]
    +    }
    +  }
    +}

    What this gives you:

    • One *Api.ts + one *ApiTypes.d.ts file per schema in ./src/app/api/
    • Generated files import baseUrl and parseQuery from $lib/urls (your own module)
    • JSON PostgreSQL columns typed as string instead of any — explicit casting at the call site
    • Function names match SQL routine names (good for grep / refactoring across SQL and TS)
    • URL builder constants exported (computeUrl(), loginUrl(), etc.) for use in <a href>, forms, and library integrations
    • EventSource factory functions for any @sse endpoints
    • Schema name dropped from interface names (ICancelComputeRequest, not IMathmoduleCancelComputeRequest)

    With Custom Headers and Imports

    For projects that need to add custom headers to every request or import external utilities into the generated files:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/api/{0}Api.ts",
    +      "ImportBaseUrlFrom": "@/config",
    +      "ImportParseQueryFrom": "@/utils/query",
    +      "CustomImports": [
    +        "import { handleError } from '@/utils/errors';"
    +      ],
    +      "CustomHeaders": {
    +        "X-Client-Version": "\\"1.0.0\\"",
    +        "X-Client-Platform": "\\"web\\""
    +      },
    +      "XsrfTokenHeaderName": "X-XSRF-TOKEN"
    +    }
    +  }
    +}

    Note the CustomHeaders value syntax — values are emitted as TypeScript expressions, so a literal string requires escaped quotes ("\\"1.0.0\\""). To use a dynamic value, write a JS expression: "() => localStorage.getItem('app-version')".

    Next Steps

    See Also

    • TSCLIENT - Per-endpoint TypeScript client control
    `,119)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/config_codegen.md.opHl7x9x.lean.js b/assets/config_codegen.md.opHl7x9x.lean.js new file mode 100644 index 000000000..f965433e2 --- /dev/null +++ b/assets/config_codegen.md.opHl7x9x.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Code Generation Configuration","titleTemplate":"NpgsqlRest","description":"Generate TypeScript/JavaScript client code from your PostgreSQL API. Auto-create type-safe API clients with full type definitions from your database schema.","frontmatter":{"outline":[2,3],"title":"Code Generation Configuration","titleTemplate":"NpgsqlRest","description":"Generate TypeScript/JavaScript client code from your PostgreSQL API. Auto-create type-safe API clients with full type definitions from your database schema.","head":[["meta",{"name":"keywords","content":"npgsqlrest codegen, postgresql typescript, generate api client, type-safe api, postgresql to typescript, auto-generate types"}],["meta",{"property":"og:title","content":"NpgsqlRest Code Generation Configuration"}],["meta",{"property":"og:description","content":"Generate TypeScript/JavaScript client code from your PostgreSQL API with full type definitions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/codegen.md","filePath":"config/codegen.md"}'),e={name:"config/codegen.md"};function l(h,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n("",119)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/config_command-retry.md.BgAKKMgf.js b/assets/config_command-retry.md.BgAKKMgf.js new file mode 100644 index 000000000..55170a4c0 --- /dev/null +++ b/assets/config_command-retry.md.BgAKKMgf.js @@ -0,0 +1,67 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Command Retry Configuration","titleTemplate":"NpgsqlRest","description":"Configure automatic retry for transient PostgreSQL errors. Retry strategies, error codes, backoff sequences for resilient database operations.","frontmatter":{"outline":[2,3],"title":"Command Retry Configuration","titleTemplate":"NpgsqlRest","description":"Configure automatic retry for transient PostgreSQL errors. Retry strategies, error codes, backoff sequences for resilient database operations.","head":[["meta",{"name":"keywords","content":"npgsqlrest retry, postgresql error retry, transient error handling, database resilience, connection retry api"}],["meta",{"property":"og:title","content":"NpgsqlRest Command Retry Configuration"}],["meta",{"property":"og:description","content":"Configure automatic retry strategies for transient PostgreSQL database errors."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/command-retry.md","filePath":"config/command-retry.md"}'),e={name:"config/command-retry.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[n(`

    Command Retry

    Command retry strategies and options for handling transient database errors.

    Overview

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": [
    +          "40001", "40P01",
    +          "08000", "08003", "08006", "08001", "08004", "08007", "08P01",
    +          "53000", "53100", "53200", "53300", "53400",
    +          "57P01", "57P02", "57P03", "58000", "58030",
    +          "55P03", "55006", "55000"
    +        ]
    +      }
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable command retry functionality.
    DefaultStrategystring"default"Name of the default retry strategy to use when no strategy is specified.
    Strategiesobject(see below)Named retry strategies with their configurations.

    Strategies can be assigned to endpoints using the retry_strategy annotation.

    Strategy Settings

    Each strategy has the following settings:

    SettingTypeDefaultDescription
    RetrySequenceSecondsarray[0, 1, 2, 5, 10]Retry delays in seconds. Array length determines maximum retries.
    ErrorCodesarray(see below)PostgreSQL error codes that trigger retries.

    Retry Sequence

    The RetrySequenceSeconds array defines delay between retries:

    json
    json
    {
    +  "RetrySequenceSeconds": [0, 1, 2, 5, 10]
    +}
    • First retry: immediate (0 seconds)
    • Second retry: after 1 second
    • Third retry: after 2 seconds
    • Fourth retry: after 5 seconds
    • Fifth retry: after 10 seconds

    Accepts decimal values (e.g., 0.25 for 250ms, 0.5 for 500ms).

    Default Error Codes

    The default strategy retries on these PostgreSQL error codes:

    Serialization Failures

    CodeNameDescription
    40001serialization_failureMust retry for correctness
    40P01deadlock_detectedDeadlock resolved by aborting transaction

    Connection Issues (Class 08)

    CodeName
    08000connection_exception
    08003connection_does_not_exist
    08006connection_failure
    08001sqlclient_unable_to_establish_sqlconnection
    08004sqlserver_rejected_establishment_of_sqlconnection
    08007transaction_resolution_unknown
    08P01protocol_violation

    Resource Constraints (Class 53)

    CodeName
    53000insufficient_resources
    53100disk_full
    53200out_of_memory
    53300too_many_connections
    53400configuration_limit_exceeded

    System Errors (Class 57/58)

    CodeName
    57P01admin_shutdown
    57P02crash_shutdown
    57P03cannot_connect_now
    58000system_error
    58030io_error

    Lock Acquisition Issues (Class 55)

    CodeName
    55P03lock_not_available
    55006object_in_use
    55000object_not_in_prerequisite_state

    See PostgreSQL Error Codes for the complete list.

    Multiple Strategies

    Define multiple strategies for different use cases:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
    +      },
    +      "aggressive": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
    +      },
    +      "minimal": {
    +        "RetrySequenceSeconds": [0, 1],
    +        "ErrorCodes": ["40001", "40P01"]
    +      }
    +    }
    +  }
    +}

    Example Configuration

    Production configuration with extended retries:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
    +        "ErrorCodes": [
    +          "40001", "40P01",
    +          "08000", "08003", "08006", "08001", "08004",
    +          "53300", "57P03"
    +        ]
    +      }
    +    }
    +  }
    +}

    Using Strategies in Annotations

    Assign strategies to specific endpoints using the retry_strategy annotation:

    sql
    sql
    -- Use aggressive retry for critical operations
    +comment on function process_payment() is
    +'HTTP POST
    +@retry aggressive';
    +
    +-- Use minimal retry for fast queries
    +comment on function quick_lookup() is
    +'HTTP GET
    +@retry minimal';
    +
    +-- Use default strategy explicitly
    +comment on function standard_operation() is
    +'HTTP POST
    +@retry_strategy default';

    Next Steps

    See Also

    `,43)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_command-retry.md.BgAKKMgf.lean.js b/assets/config_command-retry.md.BgAKKMgf.lean.js new file mode 100644 index 000000000..b20cce251 --- /dev/null +++ b/assets/config_command-retry.md.BgAKKMgf.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Command Retry Configuration","titleTemplate":"NpgsqlRest","description":"Configure automatic retry for transient PostgreSQL errors. Retry strategies, error codes, backoff sequences for resilient database operations.","frontmatter":{"outline":[2,3],"title":"Command Retry Configuration","titleTemplate":"NpgsqlRest","description":"Configure automatic retry for transient PostgreSQL errors. Retry strategies, error codes, backoff sequences for resilient database operations.","head":[["meta",{"name":"keywords","content":"npgsqlrest retry, postgresql error retry, transient error handling, database resilience, connection retry api"}],["meta",{"property":"og:title","content":"NpgsqlRest Command Retry Configuration"}],["meta",{"property":"og:description","content":"Configure automatic retry strategies for transient PostgreSQL database errors."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/command-retry.md","filePath":"config/command-retry.md"}'),e={name:"config/command-retry.md"};function l(h,s,p,r,k,d){return t(),a("div",null,s[0]||(s[0]=[n("",43)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_config-section.md.CwHYEVn1.js b/assets/config_config-section.md.CwHYEVn1.js new file mode 100644 index 000000000..0f4de7f5a --- /dev/null +++ b/assets/config_config-section.md.CwHYEVn1.js @@ -0,0 +1,32 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Config Section","titleTemplate":"NpgsqlRest","description":"Configure how NpgsqlRest processes configuration files. Environment variables, .env files, and configuration key validation.","frontmatter":{"outline":[2,3],"title":"Config Section","titleTemplate":"NpgsqlRest","description":"Configure how NpgsqlRest processes configuration files. Environment variables, .env files, and configuration key validation.","head":[["meta",{"name":"keywords","content":"npgsqlrest config, environment variables, env file configuration, configuration debugging, appsettings processing"}],["meta",{"property":"og:title","content":"NpgsqlRest Config Section"}],["meta",{"property":"og:description","content":"Configure environment variables, .env files, and configuration processing."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/config-section.md","filePath":"config/config-section.md"}'),t={name:"config/config-section.md"};function l(o,s,r,p,d,c){return e(),i("div",null,s[0]||(s[0]=[n(`

    Config Section

    The Config section controls how the configuration file itself is processed.

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": null,
    +    "ValidateConfigKeys": "Warning"
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    AddEnvironmentVariablesboolfalseAllow environment variables to override configuration settings.
    ParseEnvironmentVariablesbooltrueParse {ENV_VAR_NAME} (optional) and {!ENV_VAR_NAME} (required) placeholders in config values and replace with environment variable values. See below.
    EnvFilestringnullPath to a .env file for loading environment variables. See below.
    ValidateConfigKeysstring"Warning"Validate configuration keys against known defaults at startup. See below.

    Placeholder Forms: Optional and Required (3.17.0+)

    With ParseEnvironmentVariables enabled, config values support two placeholder forms, for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not set — typed reads (bool, int, …) fall back to their defaults instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.
    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)
    +"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Environment Variable Override

    When AddEnvironmentVariables is true, environment variables can override any configuration setting. Use double underscores for nested keys:

    bash
    bash
    # Override ConnectionStrings.Default
    +export ConnectionStrings__Default="Host=production-server;..."
    +
    +# Override NpgsqlRest.UrlPathPrefix
    +export NpgsqlRest__UrlPathPrefix="/v2/api"

    Environment Variable Parsing

    When ParseEnvironmentVariables is true (default), you can use {ENV_VAR} syntax anywhere in configuration values:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  }
    +}

    This allows sensitive values to be kept in environment variables rather than in the configuration file.

    Loading from .env File

    When AddEnvironmentVariables or ParseEnvironmentVariables is true and EnvFile is set, the application will load environment variables from the specified file:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": ".env"
    +  }
    +}

    The .env file format supports:

    • KEY=VALUE pairs (one per line)
    • Comments (lines starting with #)
    • Quoted values (both single and double quotes)

    Example .env file:

    code
    # Database connection settings
    +PGHOST=localhost
    +PGPORT=5432
    +PGDATABASE=example_db
    +PGUSER=postgres
    +PGPASSWORD=postgres

    The variables are loaded into the environment and made available for configuration parsing with the {ENV_VAR_NAME} syntax.

    Configuration Key Validation

    New in 3.8.0

    Configuration key validation was added in version 3.8.0.

    At startup, NpgsqlRest can validate all configuration keys in appsettings.json against the known defaults schema. This catches typos and unknown keys that would otherwise be silently ignored (e.g., LogCommand instead of LogCommands).

    The ValidateConfigKeys setting has three modes:

    ModeBehavior
    "Warning" (default)Logs warnings for unknown keys, startup continues.
    "Error"Logs errors for unknown keys and exits the application.
    "Ignore"No validation is performed.
    json
    json
    {
    +  "Config": {
    +    "ValidateConfigKeys": "Warning"
    +  }
    +}

    Example output:

    code
    [12:34:56 WRN] Unknown configuration key: NpgsqlRest:KebabCaselUrls

    Validation also covers the Kestrel section, checking against the known Kestrel schema including Limits, Http2, Http3, and top-level flags like DisableStringReuse and AllowSynchronousIO. User-defined endpoint and certificate names under Endpoints and Certificates remain open-ended and won't trigger warnings.

    TIP

    Use the --config CLI switch to inspect the current configuration with syntax highlighting, or --validate for a pre-flight check of configuration and database connectivity.

    Next Steps

    `,38)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/config_config-section.md.CwHYEVn1.lean.js b/assets/config_config-section.md.CwHYEVn1.lean.js new file mode 100644 index 000000000..4c23c7793 --- /dev/null +++ b/assets/config_config-section.md.CwHYEVn1.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Config Section","titleTemplate":"NpgsqlRest","description":"Configure how NpgsqlRest processes configuration files. Environment variables, .env files, and configuration key validation.","frontmatter":{"outline":[2,3],"title":"Config Section","titleTemplate":"NpgsqlRest","description":"Configure how NpgsqlRest processes configuration files. Environment variables, .env files, and configuration key validation.","head":[["meta",{"name":"keywords","content":"npgsqlrest config, environment variables, env file configuration, configuration debugging, appsettings processing"}],["meta",{"property":"og:title","content":"NpgsqlRest Config Section"}],["meta",{"property":"og:description","content":"Configure environment variables, .env files, and configuration processing."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/config-section.md","filePath":"config/config-section.md"}'),t={name:"config/config-section.md"};function l(o,s,r,p,d,c){return e(),i("div",null,s[0]||(s[0]=[n("",38)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/config_connection.md.OYNJshxp.js b/assets/config_connection.md.OYNJshxp.js new file mode 100644 index 000000000..c5759008f --- /dev/null +++ b/assets/config_connection.md.OYNJshxp.js @@ -0,0 +1,113 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Connection Settings","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL database connections in NpgsqlRest. Connection strings, multiple databases, pooling, and connection behavior settings.","frontmatter":{"outline":[2,3],"title":"Connection Settings","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL database connections in NpgsqlRest. Connection strings, multiple databases, pooling, and connection behavior settings.","head":[["meta",{"name":"keywords","content":"npgsqlrest connection, postgresql connection string, database connection config, npgsql pooling, postgresql multiple databases"}],["meta",{"property":"og:title","content":"NpgsqlRest Connection Settings"}],["meta",{"property":"og:description","content":"Configure PostgreSQL database connections, connection strings, pooling, and behavior settings."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/connection.md","filePath":"config/connection.md"}'),e={name:"config/connection.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Connection Settings

    This page covers all database connection configuration in NpgsqlRest, including connection strings, connection behavior settings, and NpgsqlRest-specific connection options.

    Connection Strings

    The ConnectionStrings section defines named database connections. The first available connection is used automatically when no specific connection is specified.

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypassword"
    +  }
    +}

    Multiple Connections

    You can define multiple named connections for different purposes (e.g., read replicas, different databases):

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.example.com;Database=mydb;Username=app;Password=secret",
    +    "ReadReplica": "Host=replica.example.com;Database=mydb;Username=app;Password=secret",
    +    "Analytics": "Host=analytics.example.com;Database=analytics;Username=report;Password=secret"
    +  }
    +}

    Using Environment Variables

    Connection strings support environment variable placeholders when ParseEnvironmentVariables is enabled (default):

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  }
    +}

    This is the recommended approach for production deployments to keep credentials out of configuration files.

    Connection String Parameters

    Common PostgreSQL connection string parameters:

    ParameterDescriptionExample
    HostServer hostname or IPlocalhost, db.example.com
    PortServer port5432
    DatabaseDatabase namemydb
    UsernameLogin usernamemyuser
    PasswordLogin passwordmypassword
    SSL ModeSSL connection modeRequire, Prefer, Disable
    PoolingEnable connection poolingtrue, false
    Minimum Pool SizeMinimum connections in pool0
    Maximum Pool SizeMaximum connections in pool100
    Connection Idle LifetimeSeconds before idle connection is closed300
    Connection LifetimeMaximum connection lifetime in seconds0 (unlimited)
    TimeoutConnection timeout in seconds15

    For a complete list, see the Npgsql Connection String Parameters.

    Connection Settings

    The ConnectionSettings section controls connection behavior, testing, and retry logic.

    json
    json
    {
    +  "ConnectionSettings": {
    +    "SetApplicationNameInConnection": true,
    +    "UseJsonApplicationName": false,
    +    "TestConnectionStrings": true,
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    },
    +    "MetadataQueryConnectionName": null,
    +    "MetadataQuerySchema": null,
    +    "MultiHostConnectionTargets": {
    +      "Default": "Any",
    +      "ByConnectionName": {}
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    SetApplicationNameInConnectionbooltrueSets the ApplicationName connection property to the configured application name.
    UseJsonApplicationNameboolfalseDynamically sets a JSON-formatted application name per request (see below). Note: Limited to 64 characters.
    TestConnectionStringsbooltrueValidates each connection by opening and closing it during startup.
    MetadataQueryConnectionNamestringnullConnection name used for metadata queries. Uses default connection if null.
    MetadataQuerySchemastringnullSet the search path to this schema before executing the metadata query function. When null (default), no search path is set and the server's default search path is used. Useful when using non-superuser roles with limited schema access.
    MultiHostConnectionTargetsobject(see below)Configuration for multi-host connection failover and load balancing.

    Application Name in Connection

    When SetApplicationNameInConnection is true, the configured ApplicationName is included in the database connection. This helps identify connections in PostgreSQL monitoring tools like pg_stat_activity.

    JSON Application Name

    When UseJsonApplicationName is true, the ApplicationName connection property is set dynamically on every request in the following JSON format:

    json
    json
    {"app": "MyApi", "uid": "user123", "id": "abc-123"}
    FieldDescription
    appApplication name from configuration
    uidUser ID for authenticated users, or null for anonymous requests
    idValue of the execution request header, or null if not provided

    The execution request header name can be configured in the NpgsqlRest section underExecutionIdHeaderName (default is X-NpgsqlRest-ID). See NpgsqlRest Request Headers for details.

    This provides detailed per-request tracking in PostgreSQL's pg_stat_activity.

    WARNING

    The ApplicationName connection property is limited to 64 characters. Longer values will be truncated.

    Connection Testing

    When TestConnectionStrings is true (default), NpgsqlRest validates all configured connections at startup by opening and closing each one. This ensures:

    • Connection strings are valid
    • Database servers are reachable
    • Credentials are correct

    If any connection fails, the application will not start.

    Retry Options

    The RetryOptions section configures automatic retry behavior for transient connection failures.

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    }
    +  }
    +}

    Retry Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable automatic retry for connection failures.
    RetrySequenceSecondsnumber[][1, 3, 6, 12]Wait intervals (in seconds) between retry attempts. Supports decimals like 0.25.
    ErrorCodesstring[](see below)PostgreSQL error codes that trigger automatic retries.

    Default Error Codes

    The default error codes cover common transient failures:

    CodeClassDescription
    08000Connection ExceptionGeneral connection error
    08001SQL Client Unable to Establish ConnectionClient cannot connect
    08003Connection Does Not ExistConnection lost
    08004SQL Server Rejected ConnectionServer rejected connection
    08006Connection FailureConnection failed
    55006Object In UseDatabase object is in use
    55P03Lock Not AvailableCannot acquire lock
    53300Too Many ConnectionsConnection limit reached
    57P03Cannot Connect NowServer starting up
    40001Serialization FailureTransaction serialization conflict

    Custom Retry Configuration

    For high-availability scenarios, you might want more aggressive retries:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 4, 8, 16, 32],
    +      "ErrorCodes": ["08000", "08003", "08006", "57P03"]
    +    }
    +  }
    +}

    Multi-Host Connection Support

    NpgsqlRest supports PostgreSQL multi-host connections with failover and load balancing capabilities using Npgsql's NpgsqlMultiHostDataSource.

    Multi-Host Connection Strings

    Connection strings with comma-separated hosts are automatically detected as multi-host connections:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
    +  }
    +}

    Target Session Attributes

    Configure which server type to target for each connection:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "Any",
    +      "ByConnectionName": {
    +        "readonly": "Standby",
    +        "primary": "Primary"
    +      }
    +    }
    +  }
    +}
    ValueDescription
    AnyAny successful connection is acceptable (default)
    PrimaryServer must not be in hot standby mode
    StandbyServer must be in hot standby mode
    PreferPrimaryTry primary first, fall back to any
    PreferStandbyTry standby first, fall back to any
    ReadWriteSession must accept read-write transactions
    ReadOnlySession must not accept read-write transactions

    See Npgsql Failover and Load Balancing for more details.

    Multi-Host Example

    Complete configuration for a primary-replica setup:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret",
    +    "ReadOnly": "Host=replica1.db.com,replica2.db.com,primary.db.com;Database=mydb;Username=app;Password=secret"
    +  },
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "PreferPrimary",
    +      "ByConnectionName": {
    +        "ReadOnly": "PreferStandby"
    +      }
    +    }
    +  }
    +}

    NpgsqlRest Connection Options

    The NpgsqlRest section contains additional connection-related settings that control how routines interact with database connections.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false
    +  }
    +}

    NpgsqlRest Connection Settings Reference

    SettingTypeDefaultDescription
    ConnectionNamestringnullConnection name from ConnectionStrings to use. Uses first available if null.
    UseMultipleConnectionsboolfalseAllow individual routines to specify alternative connections.

    Using Multiple Connections

    When UseMultipleConnections is true, individual PostgreSQL routines can specify which connection to use via comments. This is useful for:

    • Read replicas: Route read-only queries to replicas
    • Sharding: Route queries to different database shards
    • Resource isolation: Separate heavy analytics queries from transactional workloads

    Example PostgreSQL function using a specific connection:

    sql
    sql
    create function get_report_data()
    +returns table(...)
    +language sql
    +begin atomic;
    +  select * from large_table;
    +end;
    +
    +comment on function get_report_data() is '
    +HTTP GET /reports/data
    +@connection ReadReplica
    +';

    Equivalent as a SQL file endpoint (sql/get-report-data.sql):

    sql
    sql
    /*
    +HTTP GET /reports/data
    +@connection ReadReplica
    +*/
    +select * from large_table;

    Complete Example

    Here's a complete connection configuration for a production environment:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD};SSL Mode=Require;Pooling=true;Maximum Pool Size=100",
    +    "ReadReplica": "Host={PGHOST_REPLICA};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD};SSL Mode=Require;Pooling=true;Maximum Pool Size=50"
    +  },
    +  "ConnectionSettings": {
    +    "SetApplicationNameInConnection": true,
    +    "UseJsonApplicationName": false,
    +    "TestConnectionStrings": true,
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 5, 10],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    }
    +  },
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": true
    +  }
    +}

    Next Steps

    See Also

    `,79)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_connection.md.OYNJshxp.lean.js b/assets/config_connection.md.OYNJshxp.lean.js new file mode 100644 index 000000000..89b72aede --- /dev/null +++ b/assets/config_connection.md.OYNJshxp.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Connection Settings","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL database connections in NpgsqlRest. Connection strings, multiple databases, pooling, and connection behavior settings.","frontmatter":{"outline":[2,3],"title":"Connection Settings","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL database connections in NpgsqlRest. Connection strings, multiple databases, pooling, and connection behavior settings.","head":[["meta",{"name":"keywords","content":"npgsqlrest connection, postgresql connection string, database connection config, npgsql pooling, postgresql multiple databases"}],["meta",{"property":"og:title","content":"NpgsqlRest Connection Settings"}],["meta",{"property":"og:description","content":"Configure PostgreSQL database connections, connection strings, pooling, and behavior settings."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/connection.md","filePath":"config/connection.md"}'),e={name:"config/connection.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t("",79)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_cors.md.Cusa--1Y.js b/assets/config_cors.md.Cusa--1Y.js new file mode 100644 index 000000000..033052bba --- /dev/null +++ b/assets/config_cors.md.Cusa--1Y.js @@ -0,0 +1,53 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"CORS Configuration","titleTemplate":"NpgsqlRest","description":"Configure Cross-Origin Resource Sharing (CORS) for NpgsqlRest. Control allowed origins, methods, headers, and credentials for cross-domain API access.","frontmatter":{"outline":[2,3],"title":"CORS Configuration","titleTemplate":"NpgsqlRest","description":"Configure Cross-Origin Resource Sharing (CORS) for NpgsqlRest. Control allowed origins, methods, headers, and credentials for cross-domain API access.","head":[["meta",{"name":"keywords","content":"npgsqlrest cors, postgresql api cors, cross-origin resource sharing, rest api cors config, allowed origins api"}],["meta",{"property":"og:title","content":"NpgsqlRest CORS Configuration"}],["meta",{"property":"og:description","content":"Configure CORS for cross-domain API access. Control allowed origins, methods, headers, and credentials."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/cors.md","filePath":"config/cors.md"}'),l={name:"config/cors.md"};function t(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    CORS

    Cross-Origin Resource Sharing (CORS) configuration for controlling access from different origins.

    Overview

    json
    json
    {
    +  "Cors": {
    +    "Enabled": false,
    +    "AllowedOrigins": [],
    +    "AllowedMethods": ["*"],
    +    "AllowedHeaders": ["*"],
    +    "AllowCredentials": false,
    +    "PreflightMaxAgeSeconds": 600
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Cross-Origin Resource Sharing (CORS) support.
    AllowedOriginsarray[]List of allowed origins for CORS requests. Empty array allows no origins.
    AllowedMethodsarray["*"]List of allowed HTTP methods for CORS requests.
    AllowedHeadersarray["*"]List of allowed headers for CORS requests.
    AllowCredentialsboolfalseAllow credentials (cookies, authorization headers) in CORS requests. Disabled by default (changed in 3.17.0); enable deliberately and only together with an explicit AllowedOrigins list.
    PreflightMaxAgeSecondsint600Maximum age in seconds for preflight request caching (10 minutes).

    Allowed Origins

    Specify which origins can make cross-origin requests:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": [
    +      "https://example.com",
    +      "https://app.example.com"
    +    ]
    +  }
    +}

    WARNING

    An empty AllowedOrigins array allows no origins. You must specify at least one origin when CORS is enabled.

    Allow All Origins

    To allow requests from any origin (not recommended for production with credentials):

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["*"],
    +    "AllowCredentials": false
    +  }
    +}

    DANGER

    Using "*" for origins with AllowCredentials: true is not allowed by browsers and will cause CORS errors.

    Allowed Methods

    Specify which HTTP methods are permitted:

    json
    json
    {
    +  "Cors": {
    +    "AllowedMethods": ["GET", "POST", "PUT", "DELETE"]
    +  }
    +}

    Use ["*"] to allow all methods.

    Allowed Headers

    Specify which request headers are permitted:

    json
    json
    {
    +  "Cors": {
    +    "AllowedHeaders": ["Content-Type", "Authorization", "X-Requested-With"]
    +  }
    +}

    Use ["*"] to allow all headers.

    Credentials

    When AllowCredentials is true, the browser includes cookies and authorization headers in cross-origin requests. This requires specific origins (not "*").

    Default changed in 3.17.0

    AllowCredentials now defaults to false. Credentials in cross-origin requests must be enabled deliberately, and only together with an explicit AllowedOrigins list. If you relied on the old default, set "AllowCredentials": true explicitly.

    Preflight Caching

    The PreflightMaxAgeSeconds setting controls how long browsers cache preflight (OPTIONS) request responses. Higher values reduce preflight requests but delay CORS policy changes from taking effect.

    Example Configuration

    Production configuration with specific origins:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": [
    +      "https://myapp.com",
    +      "https://admin.myapp.com"
    +    ],
    +    "AllowedMethods": ["GET", "POST", "PUT", "DELETE"],
    +    "AllowedHeaders": ["Content-Type", "Authorization"],
    +    "AllowCredentials": true,
    +    "PreflightMaxAgeSeconds": 3600
    +  }
    +}

    Development configuration allowing all origins:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["*"],
    +    "AllowedMethods": ["*"],
    +    "AllowedHeaders": ["*"],
    +    "AllowCredentials": false,
    +    "PreflightMaxAgeSeconds": 600
    +  }
    +}

    Next Steps

    `,36)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/config_cors.md.Cusa--1Y.lean.js b/assets/config_cors.md.Cusa--1Y.lean.js new file mode 100644 index 000000000..6be558e09 --- /dev/null +++ b/assets/config_cors.md.Cusa--1Y.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"CORS Configuration","titleTemplate":"NpgsqlRest","description":"Configure Cross-Origin Resource Sharing (CORS) for NpgsqlRest. Control allowed origins, methods, headers, and credentials for cross-domain API access.","frontmatter":{"outline":[2,3],"title":"CORS Configuration","titleTemplate":"NpgsqlRest","description":"Configure Cross-Origin Resource Sharing (CORS) for NpgsqlRest. Control allowed origins, methods, headers, and credentials for cross-domain API access.","head":[["meta",{"name":"keywords","content":"npgsqlrest cors, postgresql api cors, cross-origin resource sharing, rest api cors config, allowed origins api"}],["meta",{"property":"og:title","content":"NpgsqlRest CORS Configuration"}],["meta",{"property":"og:description","content":"Configure CORS for cross-domain API access. Control allowed origins, methods, headers, and credentials."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/cors.md","filePath":"config/cors.md"}'),l={name:"config/cors.md"};function t(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",36)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/config_data-protection.md.Djdf76Tb.js b/assets/config_data-protection.md.Djdf76Tb.js new file mode 100644 index 000000000..c6c76a0a2 --- /dev/null +++ b/assets/config_data-protection.md.Djdf76Tb.js @@ -0,0 +1,130 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Data Protection Configuration","titleTemplate":"NpgsqlRest","description":"Configure data protection for NpgsqlRest. Encryption keys, key storage, certificate-based encryption, and PostgreSQL key persistence.","frontmatter":{"outline":[2,3],"title":"Data Protection Configuration","titleTemplate":"NpgsqlRest","description":"Configure data protection for NpgsqlRest. Encryption keys, key storage, certificate-based encryption, and PostgreSQL key persistence.","head":[["meta",{"name":"keywords","content":"npgsqlrest data protection, encryption keys configuration, cookie encryption, antiforgery encryption, key storage postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Data Protection Configuration"}],["meta",{"property":"og:description","content":"Configure encryption keys, storage, and protection for cookies and tokens."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/data-protection.md","filePath":"config/data-protection.md"}'),e={name:"config/data-protection.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Data Protection

    Data protection settings control encryption and decryption for authentication cookies, antiforgery tokens, and application-level column encryption via the encrypt/decrypt annotations.

    Overview

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": false,
    +    "CustomApplicationName": null,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "Default",
    +    "FileSystemPath": "./data-protection-keys",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    "EncryptionAlgorithm": null,
    +    "ValidationAlgorithm": null,
    +    "KeyEncryption": "None",
    +    "CertificatePath": null,
    +    "CertificatePassword": null,
    +    "DpapiLocalMachine": false
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable data protection. Required when using Cookie Authentication, Antiforgery tokens, or @encrypt/@decrypt annotations.
    CustomApplicationNamestringnullApplication name for encryption scope. Uses ApplicationName if null. Different names cannot decrypt each other's data.
    DefaultKeyLifetimeDaysint90Number of days before keys are rotated.
    Storagestring"Default"Key storage location: "Default", "FileSystem", or "Database".
    FileSystemPathstring"./data-protection-keys"Path for file system storage.
    GetAllElementsCommandstring"select get_data_protection_keys()"Database command to retrieve all keys. No parameters. Must return a set of rows with a single text column containing the key data.
    StoreElementCommandstring"call store_data_protection_keys($1,$2)"Database command to store a key. Receives two text parameters: $1 is the element name, $2 is the element data. Does not return anything.
    EncryptionAlgorithmstringnullEncryption algorithm. Uses default if null.
    ValidationAlgorithmstringnullValidation algorithm. Uses default if null.
    KeyEncryptionstring"None"Key encryption method: "None", "Certificate", or "Dpapi" (Windows only).
    CertificatePathstringnullPath to X.509 certificate file (.pfx) when using Certificate encryption.
    CertificatePasswordstringnullPassword for the certificate file (can be null for passwordless certificates).
    DpapiLocalMachineboolfalseWhen using DPAPI, set to true to protect keys to the local machine instead of current user.

    Storage Options

    Default Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "Default"
    +  }
    +}

    Uses the platform's default key storage location.

    Linux Users

    On Linux, Default storage does not persist keys. When keys are lost on restart, encrypted tokens (authentication cookies) will stop working. Linux deployments should use FileSystem or Database storage.

    File System Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/var/lib/npgsqlrest/keys"
    +  }
    +}

    Stores keys in the specified directory.

    Docker

    When running in Docker, ensure FileSystemPath points to a Docker volume to persist keys across container restarts.

    Database Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)"
    +  }
    +}

    Stores keys in the PostgreSQL database using custom functions. You must create the backing table, function, and procedure yourself. The two commands work as follows:

    • GetAllElementsCommand: Retrieves all stored keys. Takes no parameters. Must return a set of rows with a single text column containing the key data.
    • StoreElementCommand: Stores a single key. Receives two text parameters: $1 is the element name (unique identifier), $2 is the element data (XML key content). Does not return anything.

    Example SQL setup:

    sql
    sql
    create table data_protection_keys (
    +  name text not null primary key,
    +  data text not null
    +);
    +
    +create function get_data_protection_keys()
    +returns setof text
    +security definer
    +language sql
    +begin atomic;
    +select data from data_protection_keys;
    +end;
    +
    +create procedure store_data_protection_keys(
    +  _name text,
    +  _data text
    +)
    +security definer
    +language sql
    +begin atomic;
    +insert into data_protection_keys (name, data)
    +values (_name, _data)
    +on conflict (name) do update set data = excluded.data;
    +end;

    Encryption Algorithms

    Configure the encryption algorithm for data protection keys:

    ValueDescription
    nullUse default algorithm
    AES_128_CBCAES 128-bit CBC mode
    AES_192_CBCAES 192-bit CBC mode
    AES_256_CBCAES 256-bit CBC mode
    AES_128_GCMAES 128-bit GCM mode
    AES_192_GCMAES 192-bit GCM mode
    AES_256_GCMAES 256-bit GCM mode
    json
    json
    {
    +  "DataProtection": {
    +    "EncryptionAlgorithm": "AES_256_GCM"
    +  }
    +}

    Validation Algorithms

    Configure the validation algorithm for data protection keys:

    ValueDescription
    nullUse default algorithm
    HMACSHA256HMAC SHA-256
    HMACSHA512HMAC SHA-512
    json
    json
    {
    +  "DataProtection": {
    +    "ValidationAlgorithm": "HMACSHA512"
    +  }
    +}

    Application Name Scope

    The CustomApplicationName determines the encryption scope. Applications with different names cannot decrypt each other's data:

    json
    json
    {
    +  "DataProtection": {
    +    "CustomApplicationName": "my-app-production"
    +  }
    +}

    If null, uses the top-level ApplicationName setting.

    Key Encryption Options

    Data protection keys can be encrypted at rest using X.509 certificates or Windows DPAPI for additional security.

    No Encryption (Default)

    json
    json
    {
    +  "DataProtection": {
    +    "KeyEncryption": "None"
    +  }
    +}

    Keys are stored without additional encryption at rest.

    Certificate Encryption

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "KeyEncryption": "Certificate",
    +    "CertificatePath": "/path/to/cert.pfx",
    +    "CertificatePassword": "\${CERT_PASSWORD}"
    +  }
    +}

    Encrypts keys using an X.509 certificate. The certificate must be a .pfx file containing both the public and private key.

    Environment Variables

    Use environment variable substitution for the certificate password to avoid storing secrets in configuration files.

    DPAPI Encryption (Windows Only)

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "./keys",
    +    "KeyEncryption": "Dpapi",
    +    "DpapiLocalMachine": true
    +  }
    +}

    Uses Windows Data Protection API to encrypt keys. Only available on Windows.

    DpapiLocalMachineScope
    false (default)Keys are protected to the current user account
    trueKeys are protected to the local machine (any user on the machine can decrypt)

    Windows Only

    DPAPI encryption is only available on Windows. On other platforms, use Certificate encryption instead.

    Complete Example

    Production configuration with database storage:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "CustomApplicationName": null,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    "EncryptionAlgorithm": "AES_256_GCM",
    +    "ValidationAlgorithm": "HMACSHA512"
    +  }
    +}

    Production configuration with file system storage (Docker):

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/app/keys"
    +  }
    +}

    Column Encryption with Annotations

    Data Protection powers the encrypt and decrypt annotations, which provide transparent application-level column encryption. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client.

    sql
    sql
    -- Store with encryption
    +create function store_secret(_key text, _value text) returns void ...
    +comment on function store_secret(text, text) is '
    +HTTP POST
    +encrypt _value
    +';
    +
    +-- Retrieve with decryption
    +create function get_secret(_key text) returns table(key text, value text) ...
    +comment on function get_secret(text) is '
    +decrypt value
    +';

    Equivalent as SQL file endpoints:

    sql
    sql
    -- sql/store-secret.sql
    +/*
    +HTTP POST
    +@encrypt value
    +@param $1 key
    +@param $2 value
    +*/
    +insert into secrets (key, value) values ($1, $2)
    +on conflict (key) do update set value = excluded.value;
    sql
    sql
    -- sql/get-secret.sql
    +/*
    +HTTP GET
    +@decrypt value
    +@param $1 key
    +*/
    +select key, value from secrets where key = $1;

    The database stores ciphertext; the API consumer sees plaintext. This is useful for storing PII (SSN, medical records, credit card numbers) or other sensitive data that must be encrypted at rest but is only ever looked up by an unencrypted key (e.g., user_id).

    Key Persistence Required

    If encryption keys are lost, encrypted data is permanently unrecoverable. Always use FileSystem or Database storage in production — never rely on Default storage on Linux.

    See the ENCRYPT / DECRYPT annotation reference for full syntax, behavior notes, and examples.

    Next Steps

    See Also

    `,67)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/config_data-protection.md.Djdf76Tb.lean.js b/assets/config_data-protection.md.Djdf76Tb.lean.js new file mode 100644 index 000000000..79bc622f9 --- /dev/null +++ b/assets/config_data-protection.md.Djdf76Tb.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Data Protection Configuration","titleTemplate":"NpgsqlRest","description":"Configure data protection for NpgsqlRest. Encryption keys, key storage, certificate-based encryption, and PostgreSQL key persistence.","frontmatter":{"outline":[2,3],"title":"Data Protection Configuration","titleTemplate":"NpgsqlRest","description":"Configure data protection for NpgsqlRest. Encryption keys, key storage, certificate-based encryption, and PostgreSQL key persistence.","head":[["meta",{"name":"keywords","content":"npgsqlrest data protection, encryption keys configuration, cookie encryption, antiforgery encryption, key storage postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest Data Protection Configuration"}],["meta",{"property":"og:description","content":"Configure encryption keys, storage, and protection for cookies and tokens."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/data-protection.md","filePath":"config/data-protection.md"}'),e={name:"config/data-protection.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",67)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/config_error-handling.md.DjvdMMV4.js b/assets/config_error-handling.md.DjvdMMV4.js new file mode 100644 index 000000000..a05ec168b --- /dev/null +++ b/assets/config_error-handling.md.DjvdMMV4.js @@ -0,0 +1,88 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Error Handling Configuration","titleTemplate":"NpgsqlRest","description":"Configure error handling in NpgsqlRest. Map PostgreSQL error codes to HTTP status codes, customize error messages, and control error response format.","frontmatter":{"outline":[2,3],"title":"Error Handling Configuration","titleTemplate":"NpgsqlRest","description":"Configure error handling in NpgsqlRest. Map PostgreSQL error codes to HTTP status codes, customize error messages, and control error response format.","head":[["meta",{"name":"keywords","content":"npgsqlrest error handling, postgresql error codes, api error mapping, http error responses, postgresql exception handling"}],["meta",{"property":"og:title","content":"NpgsqlRest Error Handling Configuration"}],["meta",{"property":"og:description","content":"Map PostgreSQL error codes to HTTP status codes and customize error responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/error-handling.md","filePath":"config/error-handling.md"}'),l={name:"config/error-handling.md"};function h(k,s,p,e,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Error Handling

    Error handling configuration for mapping PostgreSQL errors to HTTP responses.

    Overview

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": true,
    +    "DefaultErrorCodePolicy": "Default",
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Command execution timed out",
    +      "Details": null,
    +      "Type": null
    +    },
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    RemoveTypeUrlboolfalseRemove Type URL from error responses. Default Type URL points to RFC documentation based on HTTP status code.
    RemoveTraceIdbooltrueRemove TraceId field from error responses. TraceId is useful for correlating logs with errors.
    DefaultErrorCodePolicystring"Default"Name of the default error code policy to use.
    TimeoutErrorMappingobject(see below)Error mapping for command timeout errors.
    ErrorCodePoliciesarray(see below)Named policies for mapping PostgreSQL error codes to HTTP responses. Assign a policy to an endpoint using the error_code_policy annotation.

    Error Mapping Object

    Each error mapping has the following fields:

    FieldTypeDescription
    StatusCodeintHTTP status code to return.
    TitlestringTitle field in response JSON. When null, the actual PostgreSQL error message is used.
    DetailsstringDetails field in response JSON. When null, the PostgreSQL error code is used.
    TypestringURI reference (RFC3986) identifying the problem type. When null, uses default. Set RemoveTypeUrl to true to disable.

    Timeout Error Mapping

    Configure the response when a command timeout occurs:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Command execution timed out",
    +      "Details": null,
    +      "Type": null
    +    }
    +  }
    +}

    This maps command timeouts to HTTP 504 Gateway Timeout. Timeouts occur when a query exceeds:

    • The global NpgsqlRest.CommandTimeout setting, or
    • The per-endpoint command_timeout annotation

    Error Code Policies

    Define named policies for mapping PostgreSQL error codes to HTTP responses:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "DefaultErrorCodePolicy": "Default",
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Default Error Code Mappings

    PostgreSQL CodeNameHTTP StatusDescription
    42501insufficient_privilege403 ForbiddenUser lacks required permissions
    57014query_canceled205 Reset ContentQuery was cancelled
    P0001raise_exception400 Bad RequestExplicit RAISE EXCEPTION in function
    P0004assert_failure400 Bad RequestAssert statement failed

    See PostgreSQL Error Codes for the complete list.

    Response Fields

    Type URL

    When RemoveTypeUrl is false (default), error responses include a Type URL pointing to RFC documentation:

    json
    json
    {
    +  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    +  "title": "Bad Request",
    +  "status": 400
    +}

    TraceId

    When RemoveTraceId is false, error responses include a TraceId for log correlation:

    json
    json
    {
    +  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    +  "title": "Bad Request",
    +  "status": 400,
    +  "traceId": "00-abc123..."
    +}

    Example Configuration

    Production configuration with custom error mappings:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": true,
    +    "DefaultErrorCodePolicy": "Default",
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Request timed out",
    +      "Details": "The database operation took too long to complete.",
    +      "Type": null
    +    },
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "23505": {"StatusCode": 409, "Title": "Conflict", "Details": "A record with this key already exists.", "Type": null},
    +          "23503": {"StatusCode": 400, "Title": "Invalid Reference", "Details": "Referenced record does not exist.", "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Development configuration with TraceId for debugging:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": false,
    +    "DefaultErrorCodePolicy": "Default"
    +  }
    +}

    Next Steps

    See Also

    `,38)]))}const F=i(l,[["render",h]]);export{g as __pageData,F as default}; diff --git a/assets/config_error-handling.md.DjvdMMV4.lean.js b/assets/config_error-handling.md.DjvdMMV4.lean.js new file mode 100644 index 000000000..56132c983 --- /dev/null +++ b/assets/config_error-handling.md.DjvdMMV4.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Error Handling Configuration","titleTemplate":"NpgsqlRest","description":"Configure error handling in NpgsqlRest. Map PostgreSQL error codes to HTTP status codes, customize error messages, and control error response format.","frontmatter":{"outline":[2,3],"title":"Error Handling Configuration","titleTemplate":"NpgsqlRest","description":"Configure error handling in NpgsqlRest. Map PostgreSQL error codes to HTTP status codes, customize error messages, and control error response format.","head":[["meta",{"name":"keywords","content":"npgsqlrest error handling, postgresql error codes, api error mapping, http error responses, postgresql exception handling"}],["meta",{"property":"og:title","content":"NpgsqlRest Error Handling Configuration"}],["meta",{"property":"og:description","content":"Map PostgreSQL error codes to HTTP status codes and customize error responses."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/error-handling.md","filePath":"config/error-handling.md"}'),l={name:"config/error-handling.md"};function h(k,s,p,e,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",38)]))}const F=i(l,[["render",h]]);export{g as __pageData,F as default}; diff --git a/assets/config_external-auth.md.bAqNP7C3.js b/assets/config_external-auth.md.bAqNP7C3.js new file mode 100644 index 000000000..7a28542c3 --- /dev/null +++ b/assets/config_external-auth.md.bAqNP7C3.js @@ -0,0 +1,112 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"External OAuth Authentication","titleTemplate":"NpgsqlRest","description":"Configure OAuth authentication with Google, GitHub, LinkedIn, Microsoft, and Facebook. Social login integration for your PostgreSQL REST API.","frontmatter":{"outline":[2,3],"title":"External OAuth Authentication","titleTemplate":"NpgsqlRest","description":"Configure OAuth authentication with Google, GitHub, LinkedIn, Microsoft, and Facebook. Social login integration for your PostgreSQL REST API.","head":[["meta",{"name":"keywords","content":"npgsqlrest oauth, postgresql google login, github authentication api, social login postgresql, oauth2 rest api, external auth providers"}],["meta",{"property":"og:title","content":"NpgsqlRest External OAuth Authentication"}],["meta",{"property":"og:description","content":"Configure OAuth with Google, GitHub, LinkedIn, Microsoft, and Facebook for social login."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/external-auth.md","filePath":"config/external-auth.md"}'),e={name:"config/external-auth.md"};function l(p,s,h,r,d,k){return n(),a("div",null,s[0]||(s[0]=[t(`

    External OAuth Authentication

    NpgsqlRest supports OAuth authentication with popular external providers including Google, LinkedIn, GitHub, Microsoft, and Facebook.

    Overview

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "SigninUrl": "/signin-{0}",
    +      "ReturnToPath": "/",
    +      "LoginCommand": "select * from external_login($1,$2,$3,$4,$5)"
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable external OAuth providers.
    BrowserSessionStatusKeystring"__external_status"sessionStorage key for auth status (HTTP status code).
    BrowserSessionMessageKeystring"__external_message"sessionStorage key for auth message.
    SigninUrlstring"/signin-{0}"Sign-in page URL pattern. {0} is replaced with provider name.
    SignInHtmlTemplatestring(see below)HTML template for the sign-in page.
    RedirectUrlstringnullURL to redirect after auth. Usually auto-detected.
    ReturnToPathstring"/"Default path to redirect after auth completes.
    ReturnToPathQueryStringKeystring"return_to"Query string key for dynamic return path.
    LoginCommandstring"select * from external_login($1,$2,$3,$4,$5)"PostgreSQL command to execute after OAuth login. Uses the same result processing logic as the login endpoint.
    ClientAnalyticsDatastring(JavaScript object)Browser analytics data sent to login command.
    ClientAnalyticsIpKeystring"ip"JSON key for client IP in analytics data.

    SignInHtmlTemplate

    HTML template for the sign-in page shown during communication with the OAuth provider. This is typically a simple loading page. You can customize this to show your own loading animation, spinner, or branding. Format placeholders:

    • {0} - Provider name (e.g., "Google", "GitHub")
    • {1} - JavaScript to redirect to the external auth provider

    Default value:

    html
    html
    <!DOCTYPE html>
    +<html>
    +<head>
    +  <meta charset="utf-8" />
    +  <title>Talking To {0}</title>
    +</head>
    +<body>
    +  Loading...
    +  {1}
    +</body>
    +</html>

    Login Command

    The LoginCommand is a PostgreSQL command that executes after successful OAuth authentication. It uses the same result set conventions as the login annotation - column names, special columns (status, scheme, body), and claim handling all work identically.

    For full details on how the result set is processed (return type requirements, special columns, claim types, status codes), see the Login Endpoint Conventions documentation.

    Parameters

    The LoginCommand receives up to five parameters:

    ParameterTypeDescription
    $1textExternal login provider name (e.g., "google", "github")
    $2textUser's email address
    $3textUser's display name
    $4text/json/jsonbRaw JSON data from the OAuth provider
    $5text/json/jsonbBrowser analytics data (screen size, timezone, etc.)

    Result Set Conventions

    The command must return a named record (table). The result is processed using the same rules as login endpoints:

    • Special columns: status, scheme, body control login behavior (see Special Columns)
    • All other columns: Become security claims (column name = claim type, column value = claim value)
    • Empty result: Returns 401 Unauthorized
    • Multiple rows: Only the first row is processed

    Example Login Command Function

    sql
    sql
    create function external_login(
    +    _provider text,
    +    _email text,
    +    _name text,
    +    _data jsonb,
    +    _analytics jsonb
    +)
    +returns table(status boolean, id int, name text, email text, provider text)
    +language plpgsql as $$
    +declare
    +    _user_id int;
    +begin
    +    -- Find or create user
    +    select id into _user_id from users where email = _email;
    +
    +    if _user_id is null then
    +        insert into users (email, name, created_via)
    +        values (_email, _name, _provider)
    +        returning id into _user_id;
    +    end if;
    +
    +    -- Return claims (same format as login endpoint)
    +    return query
    +    select
    +        true as status,
    +        _user_id as id,
    +        _name as name,
    +        _email as email,
    +        _provider as provider;
    +end;
    +$$;

    Equivalent as a SQL file (sql/external-login.sql):

    The login command is referenced from configuration (LoginCommand: "select * from external_login($1,$2,$3,$4,$5)"), so the call site stays the same. The implementation can also be a SQL file:

    sql
    sql
    /*
    +@param $1 provider text
    +@param $2 email text
    +@param $3 name text
    +@param $4 data jsonb
    +@param $5 analytics jsonb
    +*/
    +with upsert as (
    +    insert into users (email, name, created_via)
    +    values ($2, $3, $1)
    +    on conflict (email) do update set name = excluded.name
    +    returning id, name, email
    +)
    +select true as status, id, name, email, $1 as provider from upsert;

    OAuth Providers

    NpgsqlRest includes pre-configured defaults for Google, LinkedIn, GitHub, Microsoft, and Facebook. For these providers, you only need to set ClientId and ClientSecret - all URL settings have sensible defaults.

    Minimal Configuration

    For pre-configured providers, this is all you need:

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      }
    +    }
    +  }
    +}

    The AuthUrl, TokenUrl, InfoUrl, and EmailUrl settings are optional and only needed if:

    • The provider changes their endpoints
    • You need custom OAuth scopes
    • You're defining a custom provider not listed below

    Google

    Configure your app at Google Cloud Console.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={0}&redirect_uri={1}&scope=openid profile email&state={2}
    TokenUrlhttps://oauth2.googleapis.com/token
    InfoUrlhttps://www.googleapis.com/oauth2/v3/userinfo
    EmailUrlnull

    LinkedIn

    Configure your app at LinkedIn Developers.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={0}&redirect_uri={1}&state={2}&scope=r_liteprofile%20r_emailaddress
    TokenUrlhttps://www.linkedin.com/oauth/v2/accessToken
    InfoUrlhttps://api.linkedin.com/v2/me
    EmailUrlhttps://api.linkedin.com/v2/emailAddress?q=members&projection=(elements//(handle~))

    GitHub

    Configure your app at GitHub Developer Settings.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://github.com/login/oauth/authorize?client_id={0}&redirect_uri={1}&state={2}&allow_signup=false
    TokenUrlhttps://github.com/login/oauth/access_token
    InfoUrlhttps://api.github.com/user
    EmailUrlnull

    Microsoft

    Configure your app at Azure Portal. See Microsoft Identity Platform documentation.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id={0}&redirect_uri={1}&scope=openid%20profile%20email&state={2}
    TokenUrlhttps://login.microsoftonline.com/common/oauth2/v2.0/token
    InfoUrlhttps://graph.microsoft.com/oidc/userinfo
    EmailUrlnull

    Facebook

    Configure your app at Facebook Developers. See Facebook Login documentation.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://www.facebook.com/v20.0/dialog/oauth?response_type=code&client_id={0}&redirect_uri={1}&scope=public_profile%20email&state={2}
    TokenUrlhttps://graph.facebook.com/v20.0/oauth/access_token
    InfoUrlhttps://graph.facebook.com/me?fields=id,name,email
    EmailUrlnull

    Provider Settings Reference

    Each provider has the same configuration options:

    SettingTypeRequiredDescription
    EnabledboolYesEnable this provider.
    ClientIdstringYesOAuth client ID from the provider.
    ClientSecretstringYesOAuth client secret from the provider.
    AuthUrlstringNoAuthorization URL. Has sensible default for pre-configured providers. Placeholders: {0} = client ID, {1} = redirect URI, {2} = state.
    TokenUrlstringNoToken exchange URL. Has sensible default for pre-configured providers.
    InfoUrlstringNoUser info URL. Has sensible default for pre-configured providers.
    EmailUrlstringNoEmail URL (some providers require separate request). Default is null.

    Custom Providers

    You can define custom OAuth providers by specifying all URL settings. Use any key name under External:

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "MyCustomProvider": {
    +        "Enabled": true,
    +        "ClientId": "your-client-id",
    +        "ClientSecret": "your-client-secret",
    +        "AuthUrl": "https://auth.example.com/oauth/authorize?response_type=code&client_id={0}&redirect_uri={1}&state={2}",
    +        "TokenUrl": "https://auth.example.com/oauth/token",
    +        "InfoUrl": "https://api.example.com/userinfo",
    +        "EmailUrl": null
    +      }
    +    }
    +  }
    +}

    The sign-in URL will be /signin-mycustomprovider (provider name in lowercase).

    Complete Example

    Configuration with Google and GitHub OAuth:

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "30 days",
    +
    +    "External": {
    +      "Enabled": true,
    +      "ReturnToPath": "/dashboard",
    +      "LoginCommand": "select * from external_login($1, $2, $3, $4, $5)",
    +
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      },
    +
    +      "GitHub": {
    +        "Enabled": true,
    +        "ClientId": "{GITHUB_CLIENT_ID}",
    +        "ClientSecret": "{GITHUB_CLIENT_SECRET}"
    +      }
    +    }
    +  }
    +}

    Next Steps

    `,62)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_external-auth.md.bAqNP7C3.lean.js b/assets/config_external-auth.md.bAqNP7C3.lean.js new file mode 100644 index 000000000..5de070657 --- /dev/null +++ b/assets/config_external-auth.md.bAqNP7C3.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"External OAuth Authentication","titleTemplate":"NpgsqlRest","description":"Configure OAuth authentication with Google, GitHub, LinkedIn, Microsoft, and Facebook. Social login integration for your PostgreSQL REST API.","frontmatter":{"outline":[2,3],"title":"External OAuth Authentication","titleTemplate":"NpgsqlRest","description":"Configure OAuth authentication with Google, GitHub, LinkedIn, Microsoft, and Facebook. Social login integration for your PostgreSQL REST API.","head":[["meta",{"name":"keywords","content":"npgsqlrest oauth, postgresql google login, github authentication api, social login postgresql, oauth2 rest api, external auth providers"}],["meta",{"property":"og:title","content":"NpgsqlRest External OAuth Authentication"}],["meta",{"property":"og:description","content":"Configure OAuth with Google, GitHub, LinkedIn, Microsoft, and Facebook for social login."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/external-auth.md","filePath":"config/external-auth.md"}'),e={name:"config/external-auth.md"};function l(p,s,h,r,d,k){return n(),a("div",null,s[0]||(s[0]=[t("",62)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_forwarded-headers.md.CGTpUREz.js b/assets/config_forwarded-headers.md.CGTpUREz.js new file mode 100644 index 000000000..770c89ba5 --- /dev/null +++ b/assets/config_forwarded-headers.md.CGTpUREz.js @@ -0,0 +1,89 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Forwarded Headers","titleTemplate":"NpgsqlRest","description":"Configure forwarded headers middleware for NpgsqlRest. Process X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers when running behind a reverse proxy.","frontmatter":{"outline":[2,3],"title":"Forwarded Headers","titleTemplate":"NpgsqlRest","description":"Configure forwarded headers middleware for NpgsqlRest. Process X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers when running behind a reverse proxy.","head":[["meta",{"name":"keywords","content":"npgsqlrest forwarded headers, x-forwarded-for, x-forwarded-proto, reverse proxy, nginx, load balancer, client ip"}],["meta",{"property":"og:title","content":"NpgsqlRest Forwarded Headers Configuration"}],["meta",{"property":"og:description","content":"Configure proxy header processing for reverse proxy deployments."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/forwarded-headers.md","filePath":"config/forwarded-headers.md"}'),l={name:"config/forwarded-headers.md"};function t(p,s,r,h,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Forwarded Headers

    New in 3.6.0

    Forwarded Headers middleware was added in version 3.6.0.

    Support for processing proxy headers when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.). This is critical for getting the correct client IP address and protocol.

    Overview

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": false,
    +    "ForwardLimit": 1,
    +    "KnownProxies": [],
    +    "KnownNetworks": [],
    +    "AllowedHosts": []
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable forwarded headers middleware (automatically placed first in the middleware pipeline).
    ForwardLimitint1Limits the number of proxy entries that will be processed from X-Forwarded-For. Set to null to process all entries (not recommended).
    KnownProxiesarray[]List of IP addresses of known proxies to accept forwarded headers from.
    KnownNetworksarray[]List of CIDR network ranges of known proxies.
    AllowedHostsarray[]List of allowed values for the X-Forwarded-Host header.

    Why Forwarded Headers Matter

    When your application runs behind a reverse proxy, the proxy intercepts all incoming requests. Without forwarded headers:

    • Client IP: Your application sees the proxy's IP instead of the real client IP
    • Protocol: Your application sees HTTP even if the client connected via HTTPS
    • Host: Your application sees the proxy's internal hostname instead of the public domain

    This affects:

    • Rate limiting (you'd limit the proxy, not individual clients)
    • Logging and analytics (wrong IPs in logs)
    • HTTPS redirects (infinite redirect loops)
    • Cookie security (secure cookies fail on perceived HTTP)

    Processed Headers

    HeaderPurpose
    X-Forwarded-ForGets real client IP instead of proxy IP
    X-Forwarded-ProtoGets original protocol (http/https)
    X-Forwarded-HostGets original host header

    Forward Limit

    Limits how many proxy entries are processed from the X-Forwarded-For header chain.

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1
    +  }
    +}

    If you have a chain of proxies (e.g., CDN → Load Balancer → Application), increase this value:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "ForwardLimit": 2
    +  }
    +}

    WARNING

    Setting ForwardLimit to null processes all entries, which can be a security risk as attackers can inject fake X-Forwarded-For entries.

    Known Proxies

    Specify exact IP addresses of trusted proxies:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "KnownProxies": ["10.0.0.1", "192.168.1.1"]
    +  }
    +}

    Forwarded headers are only accepted from these IP addresses.

    Known Networks

    Specify CIDR network ranges when proxy IPs are dynamically assigned:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "KnownNetworks": ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"]
    +  }
    +}

    This example trusts all private network ranges (common for cloud deployments).

    TIP

    If both KnownProxies and KnownNetworks are empty, forwarded headers are accepted from any source. This is less secure but may be necessary in some environments.

    Allowed Hosts

    Restrict which host headers are accepted to prevent host header injection attacks:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "AllowedHosts": ["example.com", "www.example.com", "api.example.com"]
    +  }
    +}

    If empty, any host is allowed.

    Example Configurations

    Behind nginx

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownProxies": ["127.0.0.1"],
    +    "AllowedHosts": ["myapp.com", "www.myapp.com"]
    +  }
    +}

    nginx configuration:

    nginx
    nginx
    location / {
    +    proxy_pass http://localhost:8080;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    proxy_set_header X-Forwarded-Host $host;
    +}

    AWS ALB / ELB

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownNetworks": ["10.0.0.0/8", "172.16.0.0/12"]
    +  }
    +}

    AWS load balancers automatically set forwarded headers. Trust the VPC network range.

    Azure App Service

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 2
    +  }
    +}

    Azure App Service sits behind multiple proxies. Empty KnownProxies/KnownNetworks allows headers from Azure's infrastructure.

    Cloudflare + Origin Server

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 2,
    +    "KnownNetworks": [
    +      "173.245.48.0/20",
    +      "103.21.244.0/22",
    +      "103.22.200.0/22",
    +      "103.31.4.0/22",
    +      "141.101.64.0/18",
    +      "108.162.192.0/18",
    +      "190.93.240.0/20",
    +      "188.114.96.0/20",
    +      "197.234.240.0/22",
    +      "198.41.128.0/17",
    +      "162.158.0.0/15",
    +      "104.16.0.0/13",
    +      "104.24.0.0/14",
    +      "172.64.0.0/13",
    +      "131.0.72.0/22"
    +    ]
    +  }
    +}

    TIP

    Cloudflare publishes their IP ranges at https://www.cloudflare.com/ips/. Keep this list updated.

    Docker/Kubernetes with Internal Load Balancer

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownNetworks": ["10.0.0.0/8"]
    +  }
    +}

    Trust the container network range.

    Development (Trust All)

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1
    +  }
    +}

    DANGER

    Do not use empty KnownProxies/KnownNetworks in production without understanding the security implications. Malicious clients can spoof forwarded headers.

    Security Considerations

    1. Only enable behind trusted proxies: Forwarded headers can be spoofed by clients if not properly validated.

    2. Limit forward depth: Use ForwardLimit to prevent clients from injecting fake proxy chains.

    3. Specify known proxies: Use KnownProxies or KnownNetworks to only accept headers from trusted sources.

    4. Validate hosts: Use AllowedHosts to prevent host header injection attacks.

    Next Steps

    `,59)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/config_forwarded-headers.md.CGTpUREz.lean.js b/assets/config_forwarded-headers.md.CGTpUREz.lean.js new file mode 100644 index 000000000..a3ba7dd6d --- /dev/null +++ b/assets/config_forwarded-headers.md.CGTpUREz.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Forwarded Headers","titleTemplate":"NpgsqlRest","description":"Configure forwarded headers middleware for NpgsqlRest. Process X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers when running behind a reverse proxy.","frontmatter":{"outline":[2,3],"title":"Forwarded Headers","titleTemplate":"NpgsqlRest","description":"Configure forwarded headers middleware for NpgsqlRest. Process X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers when running behind a reverse proxy.","head":[["meta",{"name":"keywords","content":"npgsqlrest forwarded headers, x-forwarded-for, x-forwarded-proto, reverse proxy, nginx, load balancer, client ip"}],["meta",{"property":"og:title","content":"NpgsqlRest Forwarded Headers Configuration"}],["meta",{"property":"og:description","content":"Configure proxy header processing for reverse proxy deployments."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/forwarded-headers.md","filePath":"config/forwarded-headers.md"}'),l={name:"config/forwarded-headers.md"};function t(p,s,r,h,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",59)]))}const u=i(l,[["render",t]]);export{c as __pageData,u as default}; diff --git a/assets/config_health-checks.md.BpWdFu-y.js b/assets/config_health-checks.md.BpWdFu-y.js new file mode 100644 index 000000000..d22173c10 --- /dev/null +++ b/assets/config_health-checks.md.BpWdFu-y.js @@ -0,0 +1,97 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Health Checks","titleTemplate":"NpgsqlRest","description":"Configure health check endpoints for NpgsqlRest. Kubernetes readiness and liveness probes, database connectivity checks, and monitoring integration.","frontmatter":{"outline":[2,3],"title":"Health Checks","titleTemplate":"NpgsqlRest","description":"Configure health check endpoints for NpgsqlRest. Kubernetes readiness and liveness probes, database connectivity checks, and monitoring integration.","head":[["meta",{"name":"keywords","content":"npgsqlrest health checks, kubernetes probes, readiness probe, liveness probe, database health check, monitoring"}],["meta",{"property":"og:title","content":"NpgsqlRest Health Checks Configuration"}],["meta",{"property":"og:description","content":"Configure health check endpoints for container orchestration and monitoring."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/health-checks.md","filePath":"config/health-checks.md"}'),t={name:"config/health-checks.md"};function l(h,s,p,r,k,d){return n(),i("div",null,s[0]||(s[0]=[e(`

    Health Checks

    New in 3.6.0

    Health Checks middleware was added in version 3.6.0.

    Health check endpoints for container orchestration (Kubernetes, Docker Swarm) and monitoring systems to determine if the application is running correctly.

    Overview

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": false,
    +    "CacheDuration": "5 seconds",
    +    "Path": "/health",
    +    "ReadyPath": "/health/ready",
    +    "LivePath": "/health/live",
    +    "IncludeDatabaseCheck": true,
    +    "ConnectionName": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable health check endpoints.
    CacheDurationstring"5 seconds"Cache health check responses for the specified duration. PostgreSQL interval format. Set to null to disable caching.
    Pathstring"/health"Path for the main health check endpoint that reports overall status.
    ReadyPathstring"/health/ready"Path for the readiness probe endpoint.
    LivePathstring"/health/live"Path for the liveness probe endpoint.
    IncludeDatabaseCheckbooltrueInclude PostgreSQL database connectivity in health checks.
    ConnectionNamestringnullUse a specific named connection for health checks. When null, uses the default connection.

    Health Check Types

    NpgsqlRest provides three types of health check endpoints:

    Main Health (/health)

    Reports the overall health status by combining all checks.

    Response:

    • 200 OK with "Healthy" or "Degraded" status
    • 503 Service Unavailable with "Unhealthy" status

    Readiness Probe (/health/ready)

    Indicates whether the application is ready to receive traffic. Used by Kubernetes to know when a pod is ready to be added to the service load balancer.

    Includes:

    • Database connectivity check (when IncludeDatabaseCheck is true)

    Response:

    • 200 OK if ready to accept traffic
    • 503 Service Unavailable if not ready (e.g., database unreachable)

    Liveness Probe (/health/live)

    Indicates whether the application process is running. Used by Kubernetes to know when to restart a pod.

    Does NOT include:

    • Database checks (a slow database shouldn't trigger a container restart)

    Response:

    • 200 OK if the application process is responding

    Cache Duration

    Health check responses are cached server-side to prevent excessive database queries:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "CacheDuration": "5 seconds"
    +  }
    +}

    The value uses PostgreSQL interval format:

    • "5 seconds" or "5s"
    • "1 minute" or "1min"
    • "30s"

    Set to null to disable caching:

    json
    json
    {
    +  "HealthChecks": {
    +    "CacheDuration": null
    +  }
    +}

    TIP

    Query strings are ignored to prevent cache-busting attacks.

    Database Health Check

    When IncludeDatabaseCheck is true, the readiness probe verifies PostgreSQL connectivity:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "IncludeDatabaseCheck": true
    +  }
    +}

    If the database is unreachable:

    • /health/ready returns 503 Service Unavailable
    • /health/live still returns 200 OK (the app is running, just can't reach the database)

    Using a Different Connection

    Use a specific named connection for health checks:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary;Database=myapp;...",
    +    "HealthCheck": "Host=replica;Database=myapp;..."
    +  },
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "ConnectionName": "HealthCheck"
    +  }
    +}

    This is useful when you want to:

    • Use a read-only connection for health checks
    • Query a different database server
    • Use credentials with limited permissions

    Kubernetes Integration

    Deployment Configuration

    yaml
    yaml
    apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: npgsqlrest-app
    +spec:
    +  template:
    +    spec:
    +      containers:
    +      - name: app
    +        image: vbilopav/npgsqlrest:latest
    +        ports:
    +        - containerPort: 8080
    +        livenessProbe:
    +          httpGet:
    +            path: /health/live
    +            port: 8080
    +          initialDelaySeconds: 5
    +          periodSeconds: 10
    +          failureThreshold: 3
    +        readinessProbe:
    +          httpGet:
    +            path: /health/ready
    +            port: 8080
    +          initialDelaySeconds: 5
    +          periodSeconds: 5
    +          failureThreshold: 3

    Probe Behavior

    ProbeChecksFailure Action
    LivenessApp process respondingRestart container
    ReadinessApp + DatabaseRemove from load balancer

    WARNING

    Don't use /health/ready for liveness probes. A database outage would cause all pods to restart, making recovery harder.

    Docker Compose Health Check

    yaml
    yaml
    services:
    +  api:
    +    image: vbilopav/npgsqlrest:latest
    +    healthcheck:
    +      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    +      interval: 30s
    +      timeout: 10s
    +      retries: 3
    +      start_period: 10s

    Custom Paths

    Customize the health check paths:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "Path": "/api/health",
    +    "ReadyPath": "/api/health/readiness",
    +    "LivePath": "/api/health/liveness"
    +  }
    +}

    Example Configurations

    Basic Configuration

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true
    +  }
    +}

    Uses all defaults: database check enabled, 5-second cache, standard paths.

    Production with Caching

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "CacheDuration": "10 seconds",
    +    "IncludeDatabaseCheck": true
    +  }
    +}

    API Gateway Integration

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "Path": "/healthz",
    +    "ReadyPath": "/readyz",
    +    "LivePath": "/livez",
    +    "CacheDuration": "3 seconds"
    +  }
    +}

    Uses Kubernetes-style paths (/healthz, /readyz, /livez).

    Without Database Check

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "IncludeDatabaseCheck": false
    +  }
    +}

    All probes return healthy if the app process is responding. Useful if you have separate database monitoring.

    Response Format

    Health check endpoints return plain text responses:

    code
    Healthy

    Or:

    code
    Unhealthy

    With corresponding HTTP status codes:

    • 200 OK - Healthy or Degraded
    • 503 Service Unavailable - Unhealthy

    Next Steps

    `,77)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/config_health-checks.md.BpWdFu-y.lean.js b/assets/config_health-checks.md.BpWdFu-y.lean.js new file mode 100644 index 000000000..e1e6f472a --- /dev/null +++ b/assets/config_health-checks.md.BpWdFu-y.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Health Checks","titleTemplate":"NpgsqlRest","description":"Configure health check endpoints for NpgsqlRest. Kubernetes readiness and liveness probes, database connectivity checks, and monitoring integration.","frontmatter":{"outline":[2,3],"title":"Health Checks","titleTemplate":"NpgsqlRest","description":"Configure health check endpoints for NpgsqlRest. Kubernetes readiness and liveness probes, database connectivity checks, and monitoring integration.","head":[["meta",{"name":"keywords","content":"npgsqlrest health checks, kubernetes probes, readiness probe, liveness probe, database health check, monitoring"}],["meta",{"property":"og:title","content":"NpgsqlRest Health Checks Configuration"}],["meta",{"property":"og:description","content":"Configure health check endpoints for container orchestration and monitoring."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/health-checks.md","filePath":"config/health-checks.md"}'),t={name:"config/health-checks.md"};function l(h,s,p,r,k,d){return n(),i("div",null,s[0]||(s[0]=[e("",77)]))}const u=a(t,[["render",l]]);export{o as __pageData,u as default}; diff --git a/assets/config_http-client.md.dRXgMQQQ.js b/assets/config_http-client.md.dRXgMQQQ.js new file mode 100644 index 000000000..7633b7738 --- /dev/null +++ b/assets/config_http-client.md.dRXgMQQQ.js @@ -0,0 +1,166 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP Client Options","titleTemplate":"NpgsqlRest","description":"Configure HTTP Types for calling external APIs from PostgreSQL functions. Make HTTP requests to external services directly from SQL with response handling.","frontmatter":{"outline":[2,3],"title":"HTTP Client Options","titleTemplate":"NpgsqlRest","description":"Configure HTTP Types for calling external APIs from PostgreSQL functions. Make HTTP requests to external services directly from SQL with response handling.","head":[["meta",{"name":"keywords","content":"npgsqlrest http client, postgresql http request, external api postgresql, sql http call, http types postgresql, call api from sql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Client Options"}],["meta",{"property":"og:description","content":"Configure HTTP Types for calling external APIs directly from PostgreSQL functions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/http-client.md","filePath":"config/http-client.md"}'),t={name:"config/http-client.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n(`

    HTTP Client Options

    Configuration for HTTP Types - composite types that enable PostgreSQL functions to make HTTP requests to external APIs.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": false,
    +      "ResponseStatusCodeField": "status_code",
    +      "ResponseBodyField": "body",
    +      "ResponseHeadersField": "headers",
    +      "ResponseContentTypeField": "content_type",
    +      "ResponseSuccessField": "success",
    +      "ResponseErrorMessageField": "error_message",
    +      "CacheEnabled": true,
    +      "MaxCacheEntries": 10000,
    +      "CachePruneIntervalSeconds": 60
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable HTTP client functionality for annotated composite types.
    ResponseStatusCodeFieldstring"status_code"Field name for HTTP response status code.
    ResponseBodyFieldstring"body"Field name for HTTP response body content.
    ResponseHeadersFieldstring"headers"Field name for HTTP response headers (as JSON).
    ResponseContentTypeFieldstring"content_type"Field name for Content-Type header value.
    ResponseSuccessFieldstring"success"Field name for success flag (true for 2xx status codes).
    ResponseErrorMessageFieldstring"error_message"Field name for error message if request failed.
    CacheEnabledbooltrueGlobal kill switch for HTTP type response caching. When false, the @cache directive on individual types is ignored and every request fires a fresh outbound call. Caching is still opt-in per type.
    MaxCacheEntriesint10000Maximum number of distinct cached HTTP responses held in memory. Once full, new responses are not cached (existing entries are still served and expire normally).
    CachePruneIntervalSecondsint60Interval in seconds at which expired cached HTTP responses are pruned from memory.

    How HTTP Types Work

    HTTP Types allow PostgreSQL functions to make HTTP requests to external APIs. When a function parameter uses a composite type with an HTTP definition comment, NpgsqlRest automatically:

    1. Parses the HTTP definition from the type comment
    2. Substitutes placeholders with function parameter values
    3. Executes the HTTP request
    4. Populates the type fields with response data
    5. Executes the PostgreSQL function with the populated parameter

    Creating an HTTP Type

    Step 1: Create a Composite Type

    Create a composite type with fields matching the response field names:

    sql
    sql
    create type weather_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);

    Step 2: Add HTTP Definition Comment

    Add an HTTP definition as a comment on the type (RFC 7230 format):

    sql
    sql
    comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +@timeout 30s';

    Step 3: Use in a Function

    Create a function with the HTTP type as a parameter:

    sql
    sql
    create function get_weather(
    +  _city text,
    +  _api_key text,
    +  _req weather_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_req).success then
    +        return (_req).body::json;
    +    else
    +        return json_build_object('error', (_req).error_message);
    +    end if;
    +end;
    +$$;

    Equivalent as a SQL file endpoint (sql/get-weather.sql):

    The HTTP Type itself must be defined in DDL (it's a composite type). The endpoint that consumes it can be a SQL file:

    sql
    sql
    /*
    +HTTP GET
    +@param $1 city
    +@param $2 api_key
    +@param $3 req weather_api
    +*/
    +select case
    +    when ($3::weather_api).success then ($3::weather_api).body::json
    +    else json_build_object('error', ($3::weather_api).error_message)
    +end;

    HTTP Definition Format

    The comment on the composite type follows a simplified HTTP message format similar to .http files:

    code
    METHOD URL [HTTP/version]
    +Header-Name: Header-Value
    +...
    +
    +[request body]

    Supported Methods

    • GET
    • POST
    • PUT
    • PATCH
    • DELETE

    Example Definitions

    Simple GET request:

    sql
    sql
    comment on type api_response is 'GET https://api.example.com/data';

    GET with headers:

    sql
    sql
    comment on type api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}
    +Accept: application/json';

    POST with body:

    sql
    sql
    comment on type api_response is 'POST https://api.example.com/users
    +Content-Type: application/json
    +
    +{"name": "{_name}", "email": "{_email}"}';

    Timeout Directives

    Timeout can be specified before or after the request line using interval format:

    sql
    sql
    -- Before request line
    +comment on type api_response is 'timeout 30
    +GET https://api.example.com/data';
    +
    +-- After headers
    +comment on type api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}
    +@timeout 30s';

    Common timeout formats:

    FormatExampleDescription
    Seconds (integer)timeout 3030 seconds
    Seconds with suffixtimeout 30s30 seconds
    TimeSpan formattimeout 00:00:3030 seconds
    With @ prefix@timeout 2min2 minutes

    Response Fields

    The composite type fields are automatically populated based on their names:

    Field NameTypeDescription
    bodytextResponse body content
    status_codeint or textHTTP status code (e.g., 200, 404)
    headersjsonResponse headers as JSON object
    content_typetextContent-Type header value
    successbooleanTrue for 2xx status codes
    error_messagetextError message if request failed

    You can customize field names via HttpClientOptions configuration if your type uses different names.

    Placeholder Substitution

    URLs, headers, and request body can contain placeholders in the format {parameter_name}. These placeholders are automatically replaced with the values of other function parameters that share the same name.

    sql
    sql
    -- Type with placeholders
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +@timeout 30s';
    +
    +-- Function with matching parameter names
    +create function get_weather(
    +  _city text,        -- Value substitutes {_city} placeholder
    +  _api_key text,     -- Value substitutes {_api_key} placeholder
    +  _req weather_api   -- HTTP type parameter (receives response)
    +)
    +returns json
    +...

    When calling GET /api/get-weather?_city=London&_api_key=secret123, NpgsqlRest will:

    1. Substitute {_city} with London and {_api_key} with secret123
    2. Make the HTTP request to https://api.weather.com/v1/current?city=London with header Authorization: Bearer secret123
    3. Populate the _req parameter fields with the response data
    4. Execute the PostgreSQL function

    Complete Example

    Configuration

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true
    +    }
    +  }
    +}

    SQL Setup

    sql
    sql
    -- Create response type
    +create type github_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);
    +
    +-- Define HTTP request
    +comment on type github_api is 'GET https://api.github.com/users/{_username}
    +Accept: application/vnd.github.v3+json
    +User-Agent: NpgsqlRest
    +@timeout 10s';
    +
    +-- Create function
    +create function get_github_user(
    +    _username text,
    +    _response github_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_response).success then
    +        return (_response).body::json;
    +    else
    +        return json_build_object(
    +            'error', true,
    +            'status', (_response).status_code,
    +            'message', (_response).error_message
    +        );
    +    end if;
    +end;
    +$$;
    +
    +comment on function get_github_user(text, github_api) is 'HTTP GET /github/user';

    Usage

    code
    GET /github/user/octocat

    Returns the GitHub user data or an error response.

    Resolved Parameter Expressions

    A placeholder in an outgoing request (e.g. Authorization: Bearer {_token}) often needs a value the client must not supply — a DB-stored API token, a claim-derived secret. A resolved parameter expression (param = <sql> on the function) computes that value server-side per request and binds it to the parameter, which then substitutes into the URL/headers/body. It never originates from, or is overridable by, the client.

    sql
    sql
    comment on type my_api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +comment on function get_secure_data(_user_id int, _req my_api_response, _token text) is '
    +_token = select api_token from user_tokens where user_id = {_user_id}
    +';

    A call to GET /api/get-secure-data/?user_id=42 resolves _token from the database (parameterized as $1 = 42), substitutes it into the Authorization header, and makes the request — the token never leaves the server.

    A {name} placeholder can also be filled from a request parameter or an allowlisted environment variable (good for a static API key); a resolved expression is for values computed server-side per request.

    Full reference

    See Resolved Parameters for behavior (server-side only, NULL handling, multiple expressions, ordering, user_params), the DB-stored / refresh-token pattern, and how it compares to the other placeholder sources.

    Retry Logic

    When using HTTP Client Types, outgoing HTTP requests to external APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. The @retry_delay directive adds configurable automatic retries with delays.

    Syntax

    sql
    sql
    -- Retry on any failure (non-2xx status, timeout, or network error):
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s
    +GET https://api.example.com/data';
    +
    +-- Retry only on specific HTTP status codes:
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';
    +
    +-- Combined with timeout:
    +comment on type my_api_type is 'timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';

    The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1-second, 2-second, and 5-second delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

    Behavior

    • Without on filter: Retries on any non-success HTTP response, timeout, or network error.
    • With on filter: Retries only when the HTTP response status code matches one of the listed codes (e.g., 429, 503). Timeouts and network errors always trigger retry regardless of the filter.
    • Retry exhaustion: If all retries fail, the last error is passed to the PostgreSQL function — the same as if retries were not configured.
    • Unexpected exceptions: Non-HTTP errors (e.g., invalid URL) are never retried.
    • Parallel execution: Each HTTP type in a function retries independently within its own parallel task.

    Example

    sql
    sql
    create type rate_limited_api as (body json, status_code int, error_message text);
    +comment on type rate_limited_api is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_rate_limited_data(
    +    _token text,
    +    _req rate_limited_api
    +)
    +returns table (body json, status_code int, error_message text)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code, (_req).error_message;
    +end;
    +$$;

    If the external API returns 429 (rate limited), the request is automatically retried after 1s, then 2s, then 5s. If it returns 400 (bad request), no retry occurs and the error is returned immediately.

    Response Caching

    The @cache directive caches the outbound HTTP response and reuses it for matching requests within a time window, instead of calling the upstream on every request:

    sql
    sql
    create type books_api as (body text, status_code int, success boolean);
    +comment on type books_api is '@cache 5m
    +GET https://books.toscrape.com/';

    A cached type fires one outbound call for a given request shape; subsequent matching requests are served from an in-memory cache until the TTL elapses. For a type with no per-request placeholders, that means a single shared upstream call per TTL window across the whole application.

    • Opt-in, GET only. Caching happens only when @cache is present; a @cache on a non-GET method is ignored with a startup warning.
    • TTL. @cache <interval> uses the same interval format as @timeout (30s, 5m, 1h, 00:05:00, or a bare number of seconds). A bare @cache caches with no expiration (until the process restarts) and warns.
    • Successful responses only. Only 2xx responses are cached, so a transient upstream failure is never pinned for the whole TTL.
    • Stampede protection. A burst of concurrent requests for the same key coalesces into a single outbound call.
    • Cache key = method + resolved URL + resolved content-type + resolved headers + resolved body, so distinct resolved requests are cached separately.

    Caching is controlled by the CacheEnabled, MaxCacheEntries, and CachePruneIntervalSeconds settings above. See the @cache directive reference for full details.

    Self-Referencing Calls (Relative Paths)

    HTTP client type definitions support relative paths that call back to the same NpgsqlRest server instance instead of external URLs:

    sql
    sql
    create type api_users as (body text);
    +comment on type api_users is 'GET /api/users';
    +
    +create type api_orders as (body text);
    +comment on type api_orders is 'GET /api/orders';

    Parallel Query Composition

    Combined with HTTP client types executing all requests in parallel (Task.WhenAll), this enables a single endpoint to fan out to multiple internal endpoints simultaneously:

    sql
    sql
    create function get_dashboard(
    +    _users api_users,
    +    _orders api_orders
    +) returns json language plpgsql as $$
    +begin
    +    return json_build_object('users', (_users).body::json, 'orders', (_orders).body::json);
    +end;
    +$$;
    +-- One request → two parallel internal calls → combined response

    Zero HTTP Overhead

    Self-referencing calls bypass the HTTP stack entirely — the endpoint handler is invoked directly in-process via InternalRequestHandler. No TCP connection, no HTTP parsing, no serialization overhead. Performance is microseconds instead of milliseconds per internal call.

    Use cases:

    • Parallel data aggregation across multiple queries
    • Orchestrating multiple mutations in a single request
    • Composing responses from several independent data sources

    Internal-Only Endpoints

    Combine with the @internal annotation to create endpoints accessible only via self-referencing calls but not exposed as public HTTP routes:

    sql
    sql
    comment on function helper_data() is 'HTTP GET
    +@internal';
    +-- Direct HTTP call → 404. Internal call via HTTP client type → works.

    Next Steps

    See Also

    • HTTP_TYPE - HTTP Type comment format reference
    `,97)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_http-client.md.dRXgMQQQ.lean.js b/assets/config_http-client.md.dRXgMQQQ.lean.js new file mode 100644 index 000000000..803b31639 --- /dev/null +++ b/assets/config_http-client.md.dRXgMQQQ.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP Client Options","titleTemplate":"NpgsqlRest","description":"Configure HTTP Types for calling external APIs from PostgreSQL functions. Make HTTP requests to external services directly from SQL with response handling.","frontmatter":{"outline":[2,3],"title":"HTTP Client Options","titleTemplate":"NpgsqlRest","description":"Configure HTTP Types for calling external APIs from PostgreSQL functions. Make HTTP requests to external services directly from SQL with response handling.","head":[["meta",{"name":"keywords","content":"npgsqlrest http client, postgresql http request, external api postgresql, sql http call, http types postgresql, call api from sql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Client Options"}],["meta",{"property":"og:description","content":"Configure HTTP Types for calling external APIs directly from PostgreSQL functions."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/http-client.md","filePath":"config/http-client.md"}'),t={name:"config/http-client.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n("",97)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_http-files.md.B6K6vnkA.js b/assets/config_http-files.md.B6K6vnkA.js new file mode 100644 index 000000000..af8a3acc9 --- /dev/null +++ b/assets/config_http-files.md.B6K6vnkA.js @@ -0,0 +1,51 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP File Options","titleTemplate":"NpgsqlRest","description":"Generate .http files for testing NpgsqlRest APIs. Compatible with VS Code REST Client and Visual Studio HTTP file support for easy API testing.","frontmatter":{"outline":[2,3],"title":"HTTP File Options","titleTemplate":"NpgsqlRest","description":"Generate .http files for testing NpgsqlRest APIs. Compatible with VS Code REST Client and Visual Studio HTTP file support for easy API testing.","head":[["meta",{"name":"keywords","content":"npgsqlrest http files, rest client vscode, http file generator, api testing files, visual studio http, rest api testing"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP File Options"}],["meta",{"property":"og:description","content":"Generate .http files for testing APIs with VS Code REST Client and Visual Studio."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/http-files.md","filePath":"config/http-files.md"}'),n={name:"config/http-files.md"};function l(p,s,h,r,d,k){return t(),a("div",null,s[0]||(s[0]=[e(`

    HTTP File Options

    Configuration for generating HTTP files for NpgsqlRest endpoints, compatible with REST Client extensions and Visual Studio HTTP file support.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": false,
    +      "Option": "File",
    +      "Name": null,
    +      "NamePattern": "{0}_{1}",
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "FileMode": "Schema",
    +      "FileOverwrite": true,
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable HTTP file generation.
    Optionstring"File"Generation mode: "File", "Endpoint", or "Both".
    NamestringnullBase file name. Uses database name if null, or "npgsqlrest" if no connection string.
    NamePatternstring"{0}_{1}"File name pattern. {0} = database name, {1} = schema suffix (when FileMode is "Schema").
    CommentHeaderstring"Simple"Comment header style: "None", "Simple", or "Full".
    CommentHeaderIncludeCommentsbooltrueInclude routine comments in header (when CommentHeader is "Simple" or "Full").
    FileModestring"Schema"File organization: "Database" or "Schema".
    FileOverwritebooltrueOverwrite existing files.
    OmitAutomaticParametersboolfalseOmit server-filled parameters from generated requests. See Omitting automatic parameters.

    Generation Options

    OptionDescription
    FileGenerate HTTP files in the file system.
    EndpointGenerate endpoint(s) serving HTTP file content.
    BothGenerate both file system files and endpoints.

    Comment Header Styles

    StyleDescription
    NoneNo comment header above requests.
    SimpleAdd routine name, parameters, and return values (default).
    FullAdd entire routine code as comment header.

    File Mode

    ModeDescription
    DatabaseCreate one HTTP file for the entire database.
    SchemaCreate one HTTP file per schema.

    HTTP Files

    HTTP files (.http) are supported by:

    These files allow you to send HTTP requests directly from your editor for API testing and documentation.

    Example Configuration

    Generate HTTP files per schema with full routine documentation:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "File",
    +      "Name": "myapi",
    +      "NamePattern": "{0}_{1}",
    +      "CommentHeader": "Full",
    +      "CommentHeaderIncludeComments": true,
    +      "FileMode": "Schema",
    +      "FileOverwrite": true
    +    }
    +  }
    +}

    Generate a single HTTP file for the entire database:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "File",
    +      "FileMode": "Database",
    +      "CommentHeader": "Simple"
    +    }
    +  }
    +}

    Serve HTTP files as endpoints:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "Endpoint"
    +    }
    +  }
    +}

    Omitting Automatic Parameters

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the Code Generation and OpenAPI generators). Default is false, so generated output is unchanged unless you opt in.

    Some parameters are filled by the server and a client value would simply be ignored. When OmitAutomaticParameters is true, such a parameter is left out of the generated .http request (query string and request body) when it is automatic and optional. "Automatic" covers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "OmitAutomaticParameters": true
    +    }
    +  }
    +}

    When every parameter of an endpoint is omitted, the request collapses to a bare URL with no query string or body.

    Next Steps

    `,33)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_http-files.md.B6K6vnkA.lean.js b/assets/config_http-files.md.B6K6vnkA.lean.js new file mode 100644 index 000000000..1f2308abc --- /dev/null +++ b/assets/config_http-files.md.B6K6vnkA.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP File Options","titleTemplate":"NpgsqlRest","description":"Generate .http files for testing NpgsqlRest APIs. Compatible with VS Code REST Client and Visual Studio HTTP file support for easy API testing.","frontmatter":{"outline":[2,3],"title":"HTTP File Options","titleTemplate":"NpgsqlRest","description":"Generate .http files for testing NpgsqlRest APIs. Compatible with VS Code REST Client and Visual Studio HTTP file support for easy API testing.","head":[["meta",{"name":"keywords","content":"npgsqlrest http files, rest client vscode, http file generator, api testing files, visual studio http, rest api testing"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP File Options"}],["meta",{"property":"og:description","content":"Generate .http files for testing APIs with VS Code REST Client and Visual Studio."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/http-files.md","filePath":"config/http-files.md"}'),n={name:"config/http-files.md"};function l(p,s,h,r,d,k){return t(),a("div",null,s[0]||(s[0]=[e("",33)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_index.md.CXfhm2PI.js b/assets/config_index.md.CXfhm2PI.js new file mode 100644 index 000000000..abb2b759d --- /dev/null +++ b/assets/config_index.md.CXfhm2PI.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as i,a5 as r}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Configuration Reference","titleTemplate":"NpgsqlRest","description":"Complete NpgsqlRest configuration reference. All settings for connections, authentication, caching, logging, and more.","frontmatter":{"outline":[2,3],"title":"Configuration Reference","titleTemplate":"NpgsqlRest","description":"Complete NpgsqlRest configuration reference. All settings for connections, authentication, caching, logging, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest configuration, api configuration reference, postgresql rest config, appsettings reference, npgsqlrest settings"}],["meta",{"property":"og:title","content":"NpgsqlRest Configuration Reference"}],["meta",{"property":"og:description","content":"Complete configuration reference for all NpgsqlRest settings and options."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/index.md","filePath":"config/index.md"}'),n={name:"config/index.md"};function o(l,e,s,c,h,f){return i(),a("div",null,e[0]||(e[0]=[r('

    Configuration Reference

    Complete reference documentation for all NpgsqlRest configuration options.

    For an introduction to how configuration works (sources, precedence, environment variables, command-line arguments), see the Configuration Guide.

    Latest Default Configuration

    See the Latest Default Configuration for a complete reference of all default settings for version 3.19.0.

    Reference Sections

    Core Settings

    • Top-Level Settings - Application identity, URLs, and startup message
    • Config Section - Configuration file processing and environment variables
    • NpgsqlRest Options - Core API generation settings (URL prefixes, naming conventions, request handling)
    • Routine Options - PostgreSQL routine handling (language filtering, custom types)
    • Connection - Database connection strings and settings
    • Server - Kestrel web server and SSL/TLS configuration

    Security

    Features

    • SQL File Source - REST API endpoints from SQL files
    • Test Runner - SQL test runner (--test): discovery, test databases, setup/teardown, coverage
    • Watch Mode - --watch: restart the server or re-run tests on SQL file, configuration, and database routine changes
    • Proxy - Reverse proxy support for forwarding requests to upstream services
    • OpenAPI - OpenAPI/Swagger documentation generation
    • MCP - Model Context Protocol server — expose routines as MCP tools for AI agents
    • HTTP Files - HTTP test file generation
    • Code Generation - Client code generation (TypeScript, etc.)
    • Uploads - File upload handling
    • Table Format - HTML table and Excel spreadsheet rendering for function results
    • HTTP Client - HTTP Types for external API calls from PostgreSQL functions

    Performance

    Infrastructure

    ',15)]))}const p=t(n,[["render",o]]);export{g as __pageData,p as default}; diff --git a/assets/config_index.md.CXfhm2PI.lean.js b/assets/config_index.md.CXfhm2PI.lean.js new file mode 100644 index 000000000..fedfda7d4 --- /dev/null +++ b/assets/config_index.md.CXfhm2PI.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as i,a5 as r}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Configuration Reference","titleTemplate":"NpgsqlRest","description":"Complete NpgsqlRest configuration reference. All settings for connections, authentication, caching, logging, and more.","frontmatter":{"outline":[2,3],"title":"Configuration Reference","titleTemplate":"NpgsqlRest","description":"Complete NpgsqlRest configuration reference. All settings for connections, authentication, caching, logging, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest configuration, api configuration reference, postgresql rest config, appsettings reference, npgsqlrest settings"}],["meta",{"property":"og:title","content":"NpgsqlRest Configuration Reference"}],["meta",{"property":"og:description","content":"Complete configuration reference for all NpgsqlRest settings and options."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/index.md","filePath":"config/index.md"}'),n={name:"config/index.md"};function o(l,e,s,c,h,f){return i(),a("div",null,e[0]||(e[0]=[r("",15)]))}const p=t(n,[["render",o]]);export{g as __pageData,p as default}; diff --git a/assets/config_latest.md.BHuOeOrq.js b/assets/config_latest.md.BHuOeOrq.js new file mode 100644 index 000000000..fe2e5c64e --- /dev/null +++ b/assets/config_latest.md.BHuOeOrq.js @@ -0,0 +1,3137 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Latest Default Configuration","titleTemplate":"NpgsqlRest","description":"Complete default configuration reference for NpgsqlRest. All settings with their default values for the latest version.","frontmatter":{"outline":[2,3],"title":"Latest Default Configuration","titleTemplate":"NpgsqlRest","description":"Complete default configuration reference for NpgsqlRest. All settings with their default values for the latest version.","head":[["meta",{"name":"keywords","content":"npgsqlrest default config, appsettings.json reference, default settings, configuration template, npgsqlrest defaults"}],["meta",{"property":"og:title","content":"NpgsqlRest Latest Default Configuration"}],["meta",{"property":"og:description","content":"Complete default configuration reference with all settings for the latest version."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/latest.md","filePath":"config/latest.md"}'),l={name:"config/latest.md"};function e(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Latest Default Configuration Reference

    This is the latest default configuration reference for NpgsqlRest version 3.19.0.

    Downloading Configuration for Specific Versions

    To download the default configuration file for a specific version (e.g., 3.19.0):

    Replace 3.19.0 with your desired version number.

    json
    json
    {
    +  //
    +  // The application name used to set the application name property in connection string by "NpgsqlRest.SetApplicationNameInConnection" or the "NpgsqlRest.UseJsonApplicationName" settings.
    +  // It is the name of the top-level directory if set to null.
    +  //
    +  "ApplicationName": null,
    +
    +  //
    +  // Production or Development
    +  //
    +  "EnvironmentName": "Production",
    +
    +  //
    +  // Specify the urls the web host will listen on. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.hostingabstractionswebhostbuilderextensions.useurls?view=aspnetcore-8.0
    +  //
    +  "Urls": "http://localhost:8080",
    +
    +  //
    +  // Logs at startup, format placeholders:
    +  // {time} - startup time
    +  // {urls} - listening on urls
    +  // {version} - current version
    +  // {environment} - EnvironmentName
    +  // {application} - ApplicationName
    +  //
    +  // Note: This message is logged at Information level. To disable this message, set to empty string.
    +  //
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}",
    +
    +  //
    +  // Configuration settings
    +  //
    +  "Config": {
    +    //
    +    // Add the environment variables to configuration.
    +    // When enabled, environment variables will override the settings in this configuration file but can be overridden by command line arguments.
    +    // Complex hierarchical keys can be defined using double underscore as a separator. 
    +    // For example, "ConnectionStrings__Default" environment variable will override the "ConnectionStrings.Default" setting in this configuration file.
    +    //
    +    "AddEnvironmentVariables": false,
    +    //
    +    // When set, configuration values will be parsed for environment variables in the format {ENV_VAR_NAME}
    +    // and replaced with the value of the environment variable when available.
    +    //
    +    "ParseEnvironmentVariables": true,
    +    //
    +    // Path to a .env file containing environment variables.
    +    // When AddEnvironmentVariables or ParseEnvironmentVariables is true and this file exists,
    +    // variables from this file will be loaded and made available for configuration parsing.
    +    // Format: KEY=VALUE (one per line)
    +    //
    +    "EnvFile": null,
    +    //
    +    // Validate configuration keys against known defaults at startup.
    +    // "Ignore" - no validation
    +    // "Warning" - log warnings for unknown keys, continue startup (default)
    +    // "Error" - log errors for unknown keys and exit
    +    //
    +    "ValidateConfigKeys": "Warning"
    +  },
    +
    +  //
    +  // List of named connection strings to PostgreSQL databases.
    +  // The "Default" connection string is used when no connection name is specified.
    +  // For connection string definition see https://www.npgsql.org/doc/connection-string-parameters.html
    +  //
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port=5432;Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  },
    +
    +  //
    +  // Additional connection settings and options.
    +  //
    +  "ConnectionSettings": {
    +    //
    +    // Sets the ApplicationName connection property in the connection string to the value of the ApplicationName configuration.
    +    // Note: This option is ignored if the UseJsonApplicationName option is enabled.
    +    //
    +    "SetApplicationNameInConnection": true,
    +    //
    +    // Sets the ApplicationName connection property dynamically on every request in the following format: 
    +    // {"app":"<ApplicationName>","uid":"<user_id>","id":"<NpgsqlRest.ExecutionIdHeaderName>"}
    +    // Note: The ApplicationName connection property is limited to 64 characters.
    +    //
    +    "UseJsonApplicationName": false,
    +    //
    +    // Test any connection string before initializing the application and using it. The connection string is tested by opening and closing the connection.
    +    //
    +    "TestConnectionStrings": true,
    +    //
    +    // Connection open retry options.
    +    //
    +    "RetryOptions": {
    +      "Enabled": true,
    +      //
    +      // Retry sequence in seconds. Accepts decimal numbers (0.25 is quarter of a second). The length of the array determines the maximum number of retries.
    +      //
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      //
    +      // Error codes that will trigger a retry when opening a connection. See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +      //
    +      "ErrorCodes": [
    +        "08000", "08003", "08006", "08001", "08004", // Connection failure codes
    +        "55P03", // Lock not available
    +        "55006", // Object in use
    +        "53300", // Too many connections
    +        "57P03", // Cannot connect now
    +        "40001"  // Serialization failure (can be retried)
    +      ]
    +    },
    +    //
    +    // The connection name in ConnectionStrings configuration that will be used to execute the metadata query. If this value is null, the default connection string will be used.
    +    //
    +    "MetadataQueryConnectionName": null,
    +    //
    +    // Set the search path to this schema before executing the metadata query function.
    +    // When null (default), no search path is set and the server's default search path is used.
    +    //
    +    // This is needed when using non superuser connection roles with limited schema access and mapping the metadata function to a specific schema.
    +    // If the connection string contains the same "Search Path=" it will be skipped.
    +    //
    +    "MetadataQuerySchema": null,
    +    // Any: Any successful connection is acceptable.
    +    // Primary: Server must not be in hot standby mode (pg_is_in_recovery() must return false).
    +    // Standby: Server must be in hot standby mode (pg_is_in_recovery() must return true).
    +    // PreferPrimary: First try to find a primary server, but if none of the listed hosts is a primary server, try again in Any mode.
    +    // PreferStandby: First try to find a standby server, but if none of the listed hosts is a standby server, try again in Any mode.
    +    // ReadWrite: Session must accept read-write transactions by default (that is, the server must not be in hot standby mode and the default_transaction_read_only parameter must be off).
    +    // ReadOnly: Session must not accept read-write transactions by default (the converse).
    +    // see https://www.npgsql.org/doc/failover-and-load-balancing.html
    +    "MultiHostConnectionTargets": {
    +      // all connections use the same target mode
    +      "Default": "Any",
    +      // per connection overrides { "name": "Primary|Standby|Any|PreferPrimary|PreferStandby|ReadWrite|ReadOnly" } 
    +      "ByConnectionName": {  }
    +    }
    +  },
    +
    +  //
    +  // Enable to invoke UseKestrelHttpsConfiguration. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilderkestrelextensions.usekestrelhttpsconfiguration?view=aspnetcore-8.0
    +  //
    +  "Ssl": {
    +    "Enabled": false,
    +    //
    +    // Adds middleware for redirecting HTTP Requests to HTTPS. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.httpspolicybuilderextensions.usehttpsredirection?view=aspnetcore-8.0
    +    //
    +    "UseHttpsRedirection": true,
    +    //
    +    // Adds middleware for using HSTS, which adds the Strict-Transport-Security header. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.hstsbuilderextensions.usehsts?view=aspnetcore-2.1
    +    //
    +    "UseHsts": true
    +  },
    +
    +  //
    +  // Data protection settings. Encryption/decryption settings for Auth Cookies, Antiforgery tokens and custom data protection needs.
    +  //
    +  "DataProtection": {
    +    "Enabled": false,
    +    //
    +    // Set to null to use the current "ApplicationName" value.
    +    // This value determines encryption type or class. Meaning, different application names will not be able to decrypt each other's data.
    +    //
    +    "CustomApplicationName": null,
    +    //
    +    // Sets the default lifetime in days of keys created by the data protection system.
    +    // Represents a number of days how long before keys are rotated.
    +    //
    +    "DefaultKeyLifetimeDays": 90,
    +    //
    +    // Data protection location: "Default", "FileSystem" or "Database"
    +    //
    +    // Note: When running on Linux, using Default location means keys will not be persisted. 
    +    // When keys are lost on restart, encrypted tokens (auth) will also not work on restart.
    +    // Linux users should use FileSystem or Database storage.
    +    //
    +    "Storage": "Default",
    +    //
    +    // FileSystem storage path. Set to a valid path when using FileSystem.
    +    // Note: When running in Docker environment, the path must be a Docker volume path to persist the keys.
    +    //
    +    "FileSystemPath": "./data-protection-keys",
    +    //
    +    // GetAllElements database command. Expected to return rows with a single column of type text.
    +    //
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    //
    +    // StoreElement database command. Receives two parameters: name and data of type text. Doesn't return anything.
    +    //
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    //
    +    // Configure encryption algorithms for data protection keys or null to use the default algorithm.
    +    // Values: AES_128_CBC, AES_192_CBC, AES_256_CBC, AES_128_GCM, AES_192_GCM, AES_256_GCM
    +    //
    +    "EncryptionAlgorithm": null,
    +    //
    +    // Configure validation algorithms for data protection keys or null to use the default algorithm.
    +    // Values: HMACSHA256, HMACSHA512
    +    //
    +    "ValidationAlgorithm": null,
    +    //
    +    // Key encryption method: "None", "Certificate", or "Dpapi" (Windows only)
    +    // None: Keys are not encrypted at rest (default)
    +    // Certificate: Keys are encrypted using an X.509 certificate
    +    // Dpapi: Keys are encrypted using Windows Data Protection API (Windows only)
    +    //
    +    "KeyEncryption": "None",
    +    //
    +    // Path to the X.509 certificate file (.pfx) when using Certificate key encryption.
    +    //
    +    "CertificatePath": null,
    +    //
    +    // Password for the certificate file. Can be null for certificates without password.
    +    // For security, consider using environment variable reference: "\${CERT_PASSWORD}"
    +    //
    +    "CertificatePassword": null,
    +    //
    +    // When using Dpapi key encryption, set to true to protect keys to the local machine.
    +    // If false (default), keys are protected to the current user account.
    +    //
    +    "DpapiLocalMachine": false
    +  },
    +
    +  //
    +  // Uncomment to configure Kestrel web server and to add certificates
    +  // See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-9.0
    +  //
    +  "Kestrel": {
    +    //  "Endpoints": {
    +    //    "Http": {
    +    //      "Url": "http://localhost:5000"
    +    //    },
    +    //    "HttpsInlineCertFile": {
    +    //      "Url": "https://localhost:5001",
    +    //      "Certificate": {
    +    //        "Path": "<path to .pfx file>",
    +    //        "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //      }
    +    //    },
    +    //    "HttpsInlineCertAndKeyFile": {
    +    //      "Url": "https://localhost:5002",
    +    //      "Certificate": {
    +    //        "Path": "<path to .pem/.crt file>",
    +    //        "KeyPath": "<path to .key file>",
    +    //        "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //      }
    +    //    },
    +    //    "HttpsInlineCertStore": {
    +    //      "Url": "https://localhost:5003",
    +    //      "Certificate": {
    +    //        "Subject": "<subject; required>",
    +    //        "Store": "<certificate store; required>",
    +    //        "Location": "<location; defaults to CurrentUser>",
    +    //        "AllowInvalid": "<true or false; defaults to false>"
    +    //      }
    +    //    },
    +    //    "HttpsDefaultCert": {
    +    //      "Url": "https://localhost:5004"
    +    //    }
    +    //  },
    +    //  "Certificates": {
    +    //    "Default": {
    +    //      "Path": "<path to .pfx file>",
    +    //      "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //    }
    +    //  },
    +    //  "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
    +  },
    +
    +  //
    +  // Thread pool configuration settings for optimizing application performance
    +  //
    +  "ThreadPool": {
    +    //
    +    // Minimum number of worker threads in the thread pool. Set to null to use system defaults.
    +    //
    +    "MinWorkerThreads": null,
    +    //
    +    // Minimum number of completion port threads. Set to null to use system defaults.
    +    //
    +    "MinCompletionPortThreads": null,
    +    //
    +    // Maximum number of worker threads in the thread pool. Set to null to use system defaults.
    +    //
    +    "MaxWorkerThreads": null,
    +    //
    +    // Maximum number of completion port threads. Set to null to use system defaults.
    +    //
    +    "MaxCompletionPortThreads": null
    +  },
    +
    +  //
    +  // Authentication and Authorization settings
    +  //
    +  "Auth": {
    +    //
    +    // Enable Cookie Auth
    +    //
    +    "CookieAuth": false,
    +    //
    +    // Authentication scheme name for cookie authentication. Set to null to use default.
    +    //
    +    "CookieAuthScheme": "Cookies",
    +    //
    +    // Cookie validity duration in Postgres interval syntax: e.g. "14 days", "12 hours", "30 minutes".
    +    // Set to null to fall back to the framework default (14 days).
    +    //
    +    "CookieValid": "14 days",
    +    //
    +    // Custom name for the authentication cookie. Set to null to use default.
    +    //
    +    "CookieName": null,
    +    //
    +    // Path scope for the authentication cookie. Set to null to use default.
    +    //
    +    "CookiePath": null,
    +    //
    +    // Domain scope for the authentication cookie. Set to null to use default.
    +    //
    +    "CookieDomain": null,
    +    //
    +    // Allow multiple concurrent sessions for the same user.
    +    //
    +    "CookieMultiSessions": true,
    +    //
    +    // Make cookie accessible only via HTTP (not JavaScript).
    +    //
    +    "CookieHttpOnly": true,
    +    //
    +    // Controls the SameSite attribute on the authentication cookie. Accepted values:
    +    //   "Strict"      — cookie sent only on same-site requests. Most restrictive; CSRF-safe.
    +    //   "Lax"         — cookie sent on same-site requests and top-level cross-site GETs (default).
    +    //   "None"        — cookie sent on all cross-site requests. REQUIRED for cross-origin SPAs /
    +    //                   mobile clients calling this API from a different origin. Browsers drop
    +    //                   "SameSite=None" cookies without the Secure attribute, so CookieSecure
    +    //                   must be set to "Always".
    +    //   "Unspecified" — omit the SameSite attribute entirely (legacy browser behavior).
    +    // Set to null to use ASP.NET Core's default (typically "Lax").
    +    //
    +    "CookieSameSite": null,
    +    //
    +    // Controls when the cookie's Secure attribute is set. Accepted values:
    +    //   "SameAsRequest" — Secure is set only when the request itself is HTTPS (default).
    +    //   "Always"        — Secure is always set; browsers only send the cookie over HTTPS. REQUIRED
    +    //                     alongside CookieSameSite="None" for cross-origin auth.
    +    //   "None"          — Secure is never set; cookies are sent over HTTP as well as HTTPS.
    +    // Set to null to use ASP.NET Core's default ("SameAsRequest").
    +    //
    +    "CookieSecure": null,
    +    //
    +    // Enable Microsoft Bearer Token Auth (proprietary format, not JWT)
    +    //
    +    "BearerTokenAuth": false,
    +    //
    +    // Authentication scheme name for bearer token authentication. Set to null to use default.
    +    //
    +    "BearerTokenAuthScheme": "BearerToken",
    +    //
    +    // Bearer token expiration in Postgres interval syntax: e.g. "1 hour", "30 minutes", "2 days".
    +    // Set to null to fall back to the framework default (1 hour).
    +    //
    +    "BearerTokenExpire": "1 hour",
    +    // POST { "refresh": "{{refreshToken}}" }
    +    "BearerTokenRefreshPath": "/api/token/refresh",
    +    //
    +    // Enable standard JWT (JSON Web Token) Bearer Authentication
    +    //
    +    "JwtAuth": false,
    +    //
    +    // Authentication scheme name for JWT authentication. Set to null to fall back to the framework default.
    +    //
    +    "JwtAuthScheme": "Bearer",
    +    //
    +    // Secret key used to sign JWT tokens. Must be at least 32 characters for HS256.
    +    // IMPORTANT: Use a strong, unique secret in production. Store securely (e.g., environment variable).
    +    //
    +    "JwtSecret": null,
    +    //
    +    // JWT issuer (iss claim). Identifies the principal that issued the JWT.
    +    //
    +    "JwtIssuer": null,
    +    //
    +    // JWT audience (aud claim). Identifies the recipients that the JWT is intended for.
    +    //
    +    "JwtAudience": null,
    +    //
    +    // JWT access token expiration in Postgres interval syntax: e.g. "60 minutes", "1 hour", "30 seconds".
    +    // Set to null to fall back to the framework default (60 minutes).
    +    //
    +    "JwtExpire": "60 minutes",
    +    //
    +    // JWT refresh token expiration in Postgres interval syntax: e.g. "7 days", "168 hours", "1 week".
    +    // Set to null to fall back to the framework default (7 days).
    +    //
    +    "JwtRefreshExpire": "7 days",
    +    //
    +    // Validate the issuer (iss) claim. Set to true if JwtIssuer is configured.
    +    //
    +    "JwtValidateIssuer": false,
    +    //
    +    // Validate the audience (aud) claim. Set to true if JwtAudience is configured.
    +    //
    +    "JwtValidateAudience": false,
    +    //
    +    // Validate the token lifetime (exp claim). Default is true.
    +    //
    +    "JwtValidateLifetime": true,
    +    //
    +    // Validate the signing key. Default is true.
    +    //
    +    "JwtValidateIssuerSigningKey": true,
    +    //
    +    // Clock skew to apply when validating token lifetime. Format: PostgreSQL interval.
    +    // Default is 5 minutes to account for clock differences between servers.
    +    //
    +    "JwtClockSkew": "5 minutes",
    +    //
    +    // URL path for JWT token refresh endpoint. POST with { "refreshToken": "..." }
    +    // Returns new access token and refresh token pair.
    +    //
    +    "JwtRefreshPath": "/api/jwt/refresh",
    +    //
    +    // Named additional authentication schemes. Each entry registers a fully-fledged ASP.NET Core
    +    // authentication scheme alongside the main one. A login function returning a scheme name in its
    +    // \`scheme\` column signs the user in under that scheme — useful for "short-lived sensitive
    +    // session", "separate admin scope", or "different JWT signing key per scope" patterns alongside
    +    // the normal long-lived primary scheme.
    +    //
    +    // Each scheme has a \`Type\`: \`Cookies\`, \`BearerToken\`, or \`Jwt\`. Schemes inherit any unset field
    +    // from the root Auth section so blocks stay small. See the type-specific override fields below.
    +    //
    +    // Validation: scheme name must not collide with the main scheme names (CookieAuthScheme,
    +    // BearerTokenAuthScheme, JwtAuthScheme). Explicit \`CookieName\` values must be unique across all
    +    // schemes. Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across all
    +    // schemes that define one. Disabled schemes (\`Enabled: false\`) are skipped at startup.
    +    //
    +    "Schemes": {
    +      // Example: a short-lived single-session cookie for sensitive operations (admin area, payment flow).
    +      // Login functions can return \`'short_session'\` in the scheme column to sign users in under this scheme.
    +      "short_session": {
    +        "Type": "Cookies",
    +        "Enabled": false,
    +        "CookieValid": "1 hour",
    +        "CookieMultiSessions": false
    +      },
    +      // Example: a separate Microsoft bearer-token scheme with a shorter expiration than the main one.
    +      // Each scheme can declare its own refresh path; if set, it must be unique across schemes.
    +      "api_token": {
    +        "Type": "BearerToken",
    +        "Enabled": false,
    +        "BearerTokenExpire": "30 minutes",
    +        "BearerTokenRefreshPath": "/api/api-token/refresh"
    +      },
    +      // Example: a separate JWT scheme with its own signing secret (different blast radius from the
    +      // main JWT) and a much shorter access-token expiration. Inherits any unset JWT field from the
    +      // root Auth section. JwtSecret must be ≥32 characters for HS256.
    +      "admin_jwt": {
    +        "Type": "Jwt",
    +        "Enabled": false,
    +        "JwtSecret": null,
    +        "JwtIssuer": null,
    +        "JwtAudience": null,
    +        "JwtExpire": "5 minutes",
    +        "JwtRefreshExpire": "1 hour",
    +        "JwtRefreshPath": "/api/admin-jwt/refresh"
    +      }
    +    },
    +    //
    +    // Enable external auth providers
    +    //
    +    "External": {
    +      "Enabled": false,
    +      //
    +      // sessionStorage key to store the status of the external auth process returned by the signin page.
    +      // The value is HTTP status code (200 for success, 401 for unauthorized, 403 for forbidden, etc.)
    +      //
    +      "BrowserSessionStatusKey": "__external_status",
    +      //
    +      // sessionStorage key to store the message of the external auth process returned by the signin page.
    +      //
    +      "BrowserSessionMessageKey": "__external_message",
    +      //
    +      // Path to the signin page to handle the external auth process. Redirect to this page to start the external auth process.
    +      // Format placeholder {0} is the provider name in lowercase (google, linkedin, github, etc.)
    +      //
    +      "SigninUrl": "/signin-{0}",
    +      //
    +      // Sign in page template. Format placeholders {0} is the provider name, {1} is the script to redirect to the external auth provider.
    +      //
    +      "SignInHtmlTemplate": "<!DOCTYPE html><html><head><meta charset=\\"utf-8\\" /><title>Talking To {0}</title></head><body>Loading...{1}</body></html>",
    +      //
    +      // URL to redirect after the external auth process is completed. Usually this is resolved from the request automatically. Except when it's not.
    +      // 
    +      "RedirectUrl": null,
    +      //
    +      // Path to redirect after the external auth process is completed. 
    +      // 
    +      "ReturnToPath": "/",
    +      //
    +      // Query string key to store the path to redirect after the external auth process is completed.
    +      // Use this to set dynamic return path. If this query string key is not found, the ReturnToPath value is used.
    +      // 
    +      "ReturnToPathQueryStringKey": "return_to",
    +      //
    +      // Login command to execute after the external auth process is completed. There are five positional and optional parameters:
    +      //   $1 - external login provider (if parameter exists, type text).
    +      //   $2 - external login email (if parameter exists, type text).
    +      //   $3 - external login name (if parameter exists, type text).
    +      //   $4 - external login JSON data received (if parameter exists, type text, JSON or JSONB).
    +      //   $5 - client browser analytics JSON data (if parameter exists, type text, JSON or JSONB).
    +      //
    +      // The command uses the same rules as the login enabled routine. 
    +      // See: "NpgsqlRest.“LoginPath"
    +      //
    +      "LoginCommand": "select * from external_login($1,$2,$3,$4,$5)",
    +      //
    +      // Browser client analytics data that will be sent as JSON to external auth command as the 5th parameter if supplied.
    +      //
    +      "ClientAnalyticsData": "{timestamp:new Date().toISOString(),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,screen:{width:window.screen.width,height:window.screen.height,colorDepth:window.screen.colorDepth,pixelRatio:window.devicePixelRatio,orientation:screen.orientation.type},browser:{userAgent:navigator.userAgent,language:navigator.language,languages:navigator.languages,cookiesEnabled:navigator.cookieEnabled,doNotTrack:navigator.doNotTrack,onLine:navigator.onLine,platform:navigator.platform,vendor:navigator.vendor},memory:{deviceMemory:navigator.deviceMemory,hardwareConcurrency:navigator.hardwareConcurrency},window:{innerWidth:window.innerWidth,innerHeight:window.innerHeight,outerWidth:window.outerWidth,outerHeight:window.outerHeight},location:{href:window.location.href,hostname:window.location.hostname,pathname:window.location.pathname,protocol:window.location.protocol,referrer:document.referrer},performance:{navigation:{type:performance.navigation?.type,redirectCount:performance.navigation?.redirectCount},timing:performance.timing?{loadEventEnd:performance.timing.loadEventEnd,loadEventStart:performance.timing.loadEventStart,domComplete:performance.timing.domComplete,domInteractive:performance.timing.domInteractive,domContentLoadedEventEnd:performance.timing.domContentLoadedEventEnd}:null}}",
    +      //
    +      // Client IP address that will be added to the client analytics data under this JSON key.
    +      //
    +      "ClientAnalyticsIpKey": "ip",
    +      //
    +      // External providers
    +      //
    +      "Google": {
    +        //
    +        // visit https://console.cloud.google.com/apis/ to configure your Google app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={0}&redirect_uri={1}&scope=openid profile email&state={2}",
    +        "TokenUrl": "https://oauth2.googleapis.com/token",
    +        "InfoUrl": "https://www.googleapis.com/oauth2/v3/userinfo",
    +        "EmailUrl": null
    +      },
    +      "LinkedIn": {
    +        //
    +        // visit https://www.linkedin.com/developers/apps/ to configure your LinkedIn app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={0}&redirect_uri={1}&state={2}&scope=r_liteprofile%20r_emailaddress",
    +        "TokenUrl": "https://www.linkedin.com/oauth/v2/accessToken",
    +        "InfoUrl": "https://api.linkedin.com/v2/me",
    +        "EmailUrl": "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements//(handle~))"
    +      },
    +      "GitHub": {
    +        //
    +        // visit https://github.com/settings/developers/ to configure your GitHub app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://github.com/login/oauth/authorize?client_id={0}&redirect_uri={1}&state={2}&allow_signup=false",
    +        "TokenUrl": "https://github.com/login/oauth/access_token",
    +        "InfoUrl": "https://api.github.com/user",
    +        "EmailUrl": null
    +      },
    +      "Microsoft": {
    +        //
    +        // visit https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade to configure your Microsoft app and get your client id and client secret
    +        // Documentation: https://learn.microsoft.com/en-us/entra/identity-platform/
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id={0}&redirect_uri={1}&scope=openid%20profile%20email&state={2}",
    +        "TokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
    +        "InfoUrl": "https://graph.microsoft.com/oidc/userinfo",
    +        "EmailUrl": null
    +      },
    +      "Facebook": {
    +        //
    +        // visit https://developers.facebook.com/apps/ to configure your Facebook app and get your client id and client secret
    +        // Documentation: https://developers.facebook.com/docs/facebook-login/
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://www.facebook.com/v20.0/dialog/oauth?response_type=code&client_id={0}&redirect_uri={1}&scope=public_profile%20email&state={2}",
    +        "TokenUrl": "https://graph.facebook.com/v20.0/oauth/access_token",
    +        "InfoUrl": "https://graph.facebook.com/me?fields=id,name,email",
    +        "EmailUrl": null
    +      }
    +    },
    +    //
    +    // WebAuthn/FIDO2 Passkey Authentication
    +    // Provides phishing-resistant, passwordless authentication using device-native biometrics or PINs.
    +    //
    +    "PasskeyAuth": {
    +      //
    +      // Enable passkey authentication.
    +      //
    +      "Enabled": false,
    +      //
    +      // Enable registration endpoints.
    +      //
    +      "EnableRegister": false,
    +      //
    +      // Rate limiter policy name to apply to all passkey endpoints.
    +      // It is recommended to enable rate limiting on passkey endpoints to protect against brute-force attacks.
    +      // Set to the name of a configured rate limiter policy, or null to disable rate limiting.
    +      //
    +      "RateLimiterPolicy": null,
    +      //
    +      // Optional connection name for named DataSource or ConnectionString lookup.
    +      // If null, uses the default DataSource or ConnectionString from NpgsqlRest options.
    +      //
    +      "ConnectionName": null,
    +      //
    +      // Command retry strategy name from CommandRetryOptions.Strategies.
    +      // Set to null to disable command retry for passkey endpoints.
    +      //
    +      "CommandRetryStrategy": "default",
    +      //
    +      // Relying Party ID (domain name). Should match your application domain (e.g., "example.com").
    +      // If null, auto-detected from the request host.
    +      // Note: IP addresses are not permitted - use "localhost" for local development.
    +      //
    +      "RelyingPartyId": null,
    +      //
    +      // Human-readable Relying Party name displayed to users during registration and authentication.
    +      // If null, uses the ApplicationName from configuration.
    +      //
    +      "RelyingPartyName": null,
    +      //
    +      // Allowed origins for origin validation (scheme + domain + port).
    +      // Example: ["https://example.com", "https://www.example.com"]
    +      // If empty, auto-detected from the request.
    +      // Note: IP addresses are not permitted - use "http://localhost:port" for local development.
    +      //
    +      "RelyingPartyOrigins": [],
    +      //
    +      // Post path for adding a passkey to an existing authenticated user (options).
    +      // Post any additional data in the body as JSON (e.g., { "deviceName": "My Phone" }).
    +      // Requires authentication. Set to null to disable this endpoint.
    +      //
    +      "AddPasskeyOptionsPath": "/api/passkey/add/options",
    +      //
    +      // Post path for adding a passkey to an existing authenticated user (completion).
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, attestationObject, clientDataJSON, transports). 
    +      // Additional JSON body fields are userContext passed through to the CompleteAddExistingUserCommand and optional analyticsData.
    +      // Requires authentication. Set to null to disable this endpoint.
    +      //
    +      "AddPasskeyPath": "/api/passkey/add",
    +      //
    +      // Post path for registration options (new user with passkey).
    +      // Post the user registration data the body as JSON (e.g., { "user_name": "...", "user_display_name": "...", "deviceName": "My Phone"  }).
    +      // No authentication required. Set to null to disable registration.
    +      //
    +      "RegistrationOptionsPath": "/api/passkey/register/options",
    +      //
    +      // Post path for registration completion (new user with passkey).
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, attestationObject, clientDataJSON, transports). 
    +      // Additional JSON body fields are userContext passed through to the CompleteAddExistingUserCommand and optional analyticsData.
    +      // No authentication required. Set to null to disable registration.
    +      //
    +      "RegistrationPath": "/api/passkey/register",
    +      //
    +      // Post path for the login options endpoint.
    +      // Post the user login data in the body as JSON (e.g., { "user_name": "..." } ).
    +      // Posting the user_name is optional when using discoverable credentials. When discoverable credentials ate not enabled on the authenticator, user_name is required.
    +      //
    +      "LoginOptionsPath": "/api/passkey/login/options",
    +      //
    +      // Post path for the login completion endpoint.
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, authenticatorData, clientDataJSON, signature, userHandle) and optional analyticsData.
    +      //
    +      "LoginPath": "/api/passkey/login",
    +      //
    +      // Challenge timeout in minutes. Challenges not used within this time will expire.
    +      //
    +      "ChallengeTimeoutMinutes": 5,
    +      //
    +      // User verification requirement:
    +      // - "preferred": Request UV if available, but allow authentication without it
    +      // - "required": Require UV, fail if not available
    +      // - "discouraged": Don't request UV (not recommended for most use cases)
    +      //
    +      // Practical implications:
    +      // - "required": User MUST authenticate with biometric (fingerprint, face) or device PIN.
    +      //   High security - proves the person is present, not just possession of the device.
    +      // - "preferred": Browser will request biometric/PIN if available, but allows passkey
    +      //   authentication even if UV isn't supported (e.g., older security keys).
    +      // - "discouraged": Just proves device possession, no biometric/PIN prompt. Lower security.
    +      //
    +      // For most apps, use "preferred". For banking/sensitive apps, use "required".
    +      //
    +      "UserVerificationRequirement": "required",
    +      //
    +      // Resident key (discoverable credential) requirement:
    +      // - "preferred": Request discoverable credentials if supported
    +      // - "required": Require discoverable credentials, fail if not supported
    +      // - "discouraged": Request non-discoverable credentials
    +      //
    +      // Practical implications:
    +      // - "required": True passwordless. Browser shows passkey picker with all accounts at login.
    +      //   User picks account and authenticates with biometric/PIN. No username input needed.
    +      // - "preferred"/"discouraged": User enters username first, then authenticates with passkey.
    +      //
    +      // For passwordless flows (no username field), set to "required".
    +      //
    +      "ResidentKeyRequirement": "required",
    +      //
    +      // Attestation conveyance preference - controls whether the server requests the authenticator
    +      // to provide cryptographic proof of its identity (make/model) and security properties during registration.
    +      //
    +      // Options:
    +      // - "none": Don't request attestation. Accept any valid authenticator without verifying its identity.
    +      //   Best for most apps - simpler, better user privacy, wider device compatibility. (Recommended)
    +      // - "indirect": Request attestation but allow the browser/platform to anonymize it. Rarely useful.
    +      // - "direct": Request full attestation certificate chain from the authenticator.
    +      //   Use when you need to verify the authenticator vendor/model meets security requirements.
    +      // - "enterprise": Request enterprise-specific attestation for managed corporate devices
    +      //   where IT needs to verify only organization-approved hardware authenticators are used.
    +      //
    +      // When to use non-"none" values:
    +      // - Banking/financial apps requiring hardware security keys only
    +      // - Enterprise environments restricting to specific authenticator models
    +      // - Compliance requirements mandating certain security certifications (FIDO2 L1/L2)
    +      //
    +      // For most consumer applications, "none" is the correct choice - you just want the user
    +      // to authenticate securely, not audit their hardware.
    +      //
    +      "AttestationConveyance": "none",
    +      //
    +      // Whether to validate and update the signature counter (sign count).
    +      // When true, validates that the new sign count is greater than stored, and updates it after authentication.
    +      // When false, skips sign count validation and update entirely.
    +      // Set to false if authenticators don't support it or you want to simplify your database schema.
    +      //
    +      "ValidateSignCount": true,
    +      //
    +      // SQL command to create a challenge when adding a passkey to an existing authenticated user.
    +      // Parameters:
    +      //   - $1 = claims (json): JSON object with user claims from the authenticated session
    +      //   - $2 = body (json): JSON object from request body (e.g., { "deviceName": "My Phone" })
    +      // Expected return columns (by name):
    +      //   - status (int): HTTP status code. Return 200 to proceed, any other status aborts.
    +      //   - message (text): Error message when status != 200.
    +      //   - challenge (text): Base64-encoded random challenge bytes (typically 32 bytes).
    +      //   - challenge_id: Server-side identifier (uuid, int, bigint, or text).
    +      //   - user_handle (text): Base64-encoded random bytes (typically 32 bytes) for WebAuthn user.id.
    +      //   - user_name (text): Username displayed in the authenticator UI.
    +      //   - user_display_name (text): Display name shown in the authenticator UI.
    +      //   - exclude_credentials (text): JSON array of existing credentials.
    +      //   - user_context (json): Opaque JSON passed through to CompleteAddExistingUserCommand.
    +      // Called by AddPasskeyOptionsPath endpoint
    +      //
    +      "ChallengeAddExistingUserCommand": "select * from passkey_challenge_add_existing($1,$2)",
    +      //
    +      // SQL command to create a challenge for standalone registration (new user).
    +      // Parameter: $1 = JSON object from request body (e.g., { "user_name": "...", "display_name": "..." })
    +      // Expected return columns (by name): Same as ChallengeAddExistingUserCommand
    +      //   - user_context should NOT contain "id" field (distinguishes from add-existing-user flow)
    +      // Called by StandaloneRegistrationOptionsPath endpoint
    +      //
    +      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
    +      //
    +      // SQL command to create a challenge for authentication.
    +      // Parameters:
    +      //   - $1 = user_name (text, optional - null for discoverable credential flow)
    +      //   - $2 = body (json): JSON object from request body (e.g., { "deviceInfo": "..." })
    +      // Expected return columns (by name): status, message, challenge, challenge_id, allow_credentials
    +      // Called by AuthenticationOptionsPath endpoint 
    +      //
    +      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
    +      //
    +      // Used by: Flow 1, Flow 2, Flow 3 (ALL flows)
    +      // SQL command to verify and consume a challenge.
    +      // Parameters: $1 = challenge_id (uuid, int, bigint, or text), $2 = operation (text: "registration" or "authentication")
    +      // Returns: challenge (bytea) - the original challenge bytes, or NULL if not found/expired
    +      // Called by all endpoints
    +      //
    +      "VerifyChallengeCommand": "select * from passkey_verify_challenge($1,$2)",
    +      //
    +      // SQL command to get credential data for authentication.
    +      // Parameter: $1 = credential_id (bytea)
    +      // Expected return columns (by name): status, message, public_key, public_key_algorithm, sign_count, user_context
    +      // Note: user_context is passed through to CompleteAuthenticateCommand (typically contains user_id)
    +      // Called by AuthenticatePath endpoint
    +      //
    +      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
    +      //
    +      // SQL command to complete adding a passkey to an existing user account.
    +      // Parameters:
    +      //   - $1 = credential_id (bytea): Unique credential identifier from authenticator.
    +      //   - $2 = user_handle (bytea): WebAuthn user.id from registration options.
    +      //   - $3 = public_key (bytea): Public key in COSE format.
    +      //   - $4 = algorithm (int): COSE algorithm identifier (-7 for ES256, -257 for RS256).
    +      //   - $5 = transports (text[]): Transport hints (e.g., ["internal", "hybrid"]).
    +      //   - $6 = backup_eligible (boolean): Whether credential can be backed up/synced.
    +      //   - $7 = user_context (json): Opaque JSON from ChallengeAddExistingUserCommand (contains user ID).
    +      //   - $8 = analytics_data (json, optional): Client analytics with server-added IP.
    +      // Expected return columns (by name): status, message
    +      // Called by RegisterPath endpoint
    +      //
    +      "CompleteAddExistingUserCommand": "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
    +      //
    +      // SQL command to complete standalone passkey registration (creates new user).
    +      // Parameters: Same as CompleteAddExistingUserCommand
    +      //   - user_context should NOT contain "id" field (creates new user instead of linking to existing)
    +      // Expected return columns (by name): status, message
    +      // Called by RegisterPath endpoint
    +      //
    +      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
    +      //
    +      // Flow 3: Login -> AuthenticatePath endpoint (after signature validation)
    +      // SQL command to update sign count and return user claims.
    +      // Parameters:
    +      //   - $1 = credential_id (bytea)
    +      //   - $2 = new_sign_count (bigint)
    +      //   - $3 = user_context (json): Opaque JSON from AuthenticateDataCommand
    +      //   - $4 = analytics_data (json, optional): Client analytics with server-added IP
    +      // Expected return columns (by name): status, user_id, user_name, user_roles (plus any custom claims)
    +      // Called by AuthenticatePath endpoint
    +      //
    +      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)",
    +      //
    +      // The JSON key name used to add the client's IP address to the analytics data server-side.
    +      // Set to null or empty string to disable IP address collection.
    +      //
    +      "ClientAnalyticsIpKey": "ip",
    +      //
    +      // Column name configuration for database responses
    +      //
    +      "StatusColumnName": "status",
    +      "MessageColumnName": "message",
    +      "ChallengeColumnName": "challenge",
    +      "ChallengeIdColumnName": "challenge_id",
    +      "UserNameColumnName": "user_name",
    +      "UserDisplayNameColumnName": "user_display_name",
    +      "UserHandleColumnName": "user_handle",
    +      "ExcludeCredentialsColumnName": "exclude_credentials",
    +      "AllowCredentialsColumnName": "allow_credentials",
    +      "PublicKeyColumnName": "public_key",
    +      "PublicKeyAlgorithmColumnName": "public_key_algorithm",
    +      "SignCountColumnName": "sign_count"
    +    }
    +  },
    +
    +  //
    +  // Serilog settings
    +  //
    +  "Log": {
    +    //
    +    // See https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level
    +    // Verbose, Debug, Information, Warning, Error, Fatal.
    +    // Set a level to "Off" (aliases "None"/"Silent") to mute that logger entirely; use null (or omit it) to fall back to its built-in default.
    +    // Note: NpgsqlRest logger applies to main application logger, which will, by default have the name defined in the ApplicationName setting.
    +    // NpgsqlRestTest is the SQL test runner (--test) channel (see TestRunner:LoggerName): discovery/parsing at Debug, each query and HTTP call at Verbose, notices by severity.
    +    //
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    //
    +    // Enable logging to console output.
    +    //
    +    "ToConsole": true,
    +    //
    +    // Minimum log level for console output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "ConsoleMinimumLevel": "Verbose",
    +    //
    +    // Enable logging to file system.
    +    //
    +    "ToFile": false,
    +    //
    +    // File path for log files.
    +    //
    +    "FilePath": "logs/log.txt",
    +    //
    +    // Maximum size limit for log files in bytes before rolling to a new file.
    +    //
    +    "FileSizeLimitBytes": 30000000,
    +    //
    +    // Minimum log level for file output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "FileMinimumLevel": "Verbose",
    +    //
    +    // Maximum number of log files to retain.
    +    //
    +    "RetainedFileCountLimit": 30,
    +    //
    +    // Create a new log file when size limit is reached.
    +    //
    +    "RollOnFileSizeLimit": true,
    +    //
    +    // Enable logging to PostgreSQL database.
    +    //
    +    "ToPostgres": false,
    +    // $1 - log level text, $2 - message text, $3 - timestamp with tz in utc, $4 - exception text or null, $5 - source context
    +    //
    +    // PostgreSQL command to execute for database logging. Parameters: $1=level, $2=message, $3=timestamp, $4=exception, $5=source.
    +    //
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    //
    +    // Minimum log level for PostgreSQL output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "PostgresMinimumLevel": "Verbose",
    +    //
    +    // Enable OpenTelemetry protocol (OTLP) logging output. Requires an OTLP collector endpoint.
    +    //
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc", // "Grpc" or "HttpProtobuf"
    +    "OTLResourceAttributes": {
    +        "service.name": "{application}", // application name from the ApplicationName setting
    +        "service.version": "1.0", // application version, set to a static value or use a build process to update it
    +        "service.environment": "{environment}" // environment name from the EnvironmentName setting
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose",
    +    
    +    //
    +    // See https://github.com/serilog/serilog/wiki/Formatting-Output
    +    //
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  },
    +
    +  //
    +  // Response compression settings
    +  //
    +  "ResponseCompression": {
    +    //
    +    // Enable response compression for HTTP responses.
    +    //
    +    "Enabled": false,
    +    //
    +    // Enable response compression for HTTPS responses.
    +    //
    +    "EnableForHttps": false,
    +    //
    +    // Use Brotli compression algorithm when supported by client.
    +    //
    +    "UseBrotli": true,
    +    //
    +    // Use Gzip compression as fallback when Brotli is not supported.
    +    //
    +    "UseGzipFallback": true,
    +    //
    +    // Compression level: Optimal, Fastest, NoCompression, SmallestSize.
    +    //
    +    "CompressionLevel": "Optimal",
    +    //
    +    // MIME types to include for compression.
    +    //
    +    "IncludeMimeTypes": [
    +      "text/plain",
    +      "text/css",
    +      "application/javascript",
    +      "text/javascript",
    +      "text/html",
    +      "application/xml",
    +      "text/xml",
    +      "application/json",
    +      "text/json",
    +      "image/svg+xml",
    +      "font/woff",
    +      "font/woff2",
    +      "application/font-woff",
    +      "application/font-woff2"
    +    ],
    +    //
    +    // MIME types to exclude from compression.
    +    //
    +    "ExcludeMimeTypes": []
    +  },
    +
    +  //
    +  // Antiforgery Token Configuration: Protects against Cross-Site Request Forgery (CSRF/XSRF) attacks.
    +  // CSRF attacks occur when a malicious site tricks a user's browser into making unwanted requests to your application
    +  // using the user's authenticated session (cookies).
    +  //
    +  // How it works:
    +  // 1. Server generates a unique token for each session/request
    +  // 2. Token is embedded in forms (hidden field) or sent via header (for AJAX)
    +  // 3. On state-changing requests (POST, PUT, DELETE), server validates the token
    +  // 4. Requests without valid tokens are rejected (400 Bad Request)
    +  //
    +  // Usage in HTML forms:
    +  //   <form method="post">
    +  //     <input type="hidden" name="__RequestVerificationToken" value="{antiForgeryToken}" />
    +  //     ...
    +  //   </form>
    +  //
    +  // Usage in AJAX/JavaScript:
    +  //   fetch('/api/endpoint', {
    +  //     method: 'POST',
    +  //     headers: { 'RequestVerificationToken': tokenValue },
    +  //     body: JSON.stringify(data)
    +  //   });
    +  //
    +  // Note: Antiforgery automatically sets the X-Frame-Options: SAMEORIGIN header to help prevent clickjacking.
    +  // If you're using the SecurityHeaders middleware with X-Frame-Options, the Antiforgery header takes precedence
    +  // (SecurityHeaders will skip X-Frame-Options when Antiforgery is enabled).
    +  //
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery
    +  //
    +  "Antiforgery": {
    +    //
    +    // Enable antiforgery token validation for state-changing requests.
    +    //
    +    "Enabled": false,
    +    //
    +    // Name of the cookie that stores the antiforgery token.
    +    // Set to null to use the ASP.NET Core default (unique per application, starts with ".AspNetCore.Antiforgery.").
    +    // Custom names are useful when running multiple applications on the same domain.
    +    //
    +    "CookieName": null,
    +    //
    +    // Name of the hidden form field that contains the request verification token.
    +    // This must match the name used in your HTML forms.
    +    //
    +    "FormFieldName": "__RequestVerificationToken",
    +    //
    +    // Name of the HTTP header that can contain the antiforgery token.
    +    // Useful for AJAX requests where adding a form field is not possible.
    +    // JavaScript can read the token from a cookie or meta tag and send it in this header.
    +    //
    +    "HeaderName": "RequestVerificationToken",
    +    //
    +    // When true, the server will NOT look for the token in the form body.
    +    // Forces header-only validation - useful for pure API scenarios where all requests use headers.
    +    // When false (default), server checks both form field and header.
    +    //
    +    "SuppressReadingTokenFromFormBody": false,
    +    //
    +    // When true, prevents the automatic X-Frame-Options: SAMEORIGIN header from being set.
    +    // X-Frame-Options helps prevent clickjacking attacks by blocking the page from being embedded in iframes.
    +    // Only set to true if:
    +    //   - You need your pages to be embedded in iframes from other origins, OR
    +    //   - You're setting X-Frame-Options elsewhere (e.g., in SecurityHeaders or at the proxy level)
    +    // Default: false (header is set for security)
    +    //
    +    "SuppressXFrameOptionsHeader": false
    +  },
    +
    +  //
    +  // Static files settings 
    +  //
    +  "StaticFiles": {
    +    "Enabled": false,
    +    "RootPath": "wwwroot",
    +    //
    +    // List of static file patterns that will require authorization.
    +    // File paths are relative to the RootPath property and pattern matching is case-insensitive.
    +    // Pattern can include wildcards (* matches any chars, ** matches recursively including /, ? matches single char).
    +    // For example: *.html, /user/*, /admin/**/*.html
    +    //
    +    "AuthorizePaths": [],
    +    "UnauthorizedRedirectPath": "/",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      //
    +      // Enable or disable the parsing of the static files.
    +      // When enabled, the static files will be parsed and the tags will be replaced with the values from the claims collection.
    +      // The tags are in the format: {claimType} where claimType is the name of the claim that will be replaced with the value from the claims collection.
    +      //
    +      "Enabled": false,
    +      //
    +      // List of claims types used. These will be parsed to NULL if not found in the claims collection or user is not authenticated.
    +      // Accepts an array of claim names ["name","email"] or an object of name->default {"name":"guest"} where the default is used when the claim is absent.
    +      //
    +      "AvailableClaims": [],
    +      //
    +      // List of environment variable names whose values are templated into static content (the same {NAME} tag syntax as claims).
    +      // Resolved once at startup. Accepts an array ["BUILD_LABEL"] (missing -> empty string) or an object {"DEMO_FLAG":"false"} with per-name defaults.
    +      // SECURITY: every listed value is served to any client - never list a secret (DB password, API key, signing token).
    +      //
    +      "AvailableEnvVars": [],
    +      //
    +      // Set to true to cache the parsed files in memory. This will improve the performance of the static files. It only applies to parsed content.
    +      // Note: caching will occur before parsing, it applies only to templates, not parsed content.
    +      //
    +      "CacheParsedFile": true,
    +      //
    +      // Headers to be added to the response for static files. Set to null or empty array to ignore.
    +      //
    +      "Headers": [ "Cache-Control: no-store, no-cache, must-revalidate", "Pragma: no-cache", "Expires: 0" ],
    +      //
    +      // List of static file patterns that will parse the content and replace the tags with the values from the claims collection.
    +      // File paths are relative to the RootPath property and pattern matching is case-insensitive.
    +      // Pattern can include wildcards (* matches any chars, ** matches recursively including /, ? matches single char).
    +      // For example: *.html, *.htm, *.txt, /pages/**/*.html
    +      // 
    +      "FilePaths": [ "*.html" ],
    +      //
    +      // Name of the configured Antiforgery form field name to be used in the static files (see Antiforgery FormFieldName setting).
    +      //
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      //
    +      // Value of the Antiforgery token if Antiforgery is enabled.
    +      //
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  },
    +
    +  //
    +  // Cross-origin resource sharing 
    +  //
    +  "Cors": {
    +    //
    +    // Enable Cross-Origin Resource Sharing (CORS) support.
    +    //
    +    "Enabled": false,
    +    //
    +    // List of allowed origins for CORS requests. Empty array allows no origins.
    +    //
    +    "AllowedOrigins": [],
    +    //
    +    // List of allowed HTTP methods for CORS requests.
    +    //
    +    "AllowedMethods": [
    +      "*"
    +    ],
    +    //
    +    // List of allowed headers for CORS requests.
    +    //
    +    "AllowedHeaders": [
    +      "*"
    +    ],
    +    //
    +    // Allow credentials (cookies, authorization headers) in CORS requests.
    +    // Disabled by default: credentials must be enabled deliberately and only together with
    +    // an explicit AllowedOrigins list (never with wildcard origins).
    +    //
    +    "AllowCredentials": false,
    +    //
    +    // Maximum age in seconds for preflight request caching (10 minutes).
    +    //
    +    "PreflightMaxAgeSeconds": 600
    +  },
    +
    +  //
    +  // Security Headers: Adds HTTP security headers to all responses to protect against common web vulnerabilities.
    +  // These headers instruct browsers how to handle your content securely.
    +  // Note: X-Frame-Options is automatically handled by the Antiforgery middleware when enabled (see Antiforgery.SuppressXFrameOptionsHeader).
    +  // Reference: https://owasp.org/www-project-secure-headers/
    +  //
    +  "SecurityHeaders": {
    +    //
    +    // Enable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    +    //
    +    "Enabled": false,
    +    //
    +    // X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content-type.
    +    // Recommended value: "nosniff"
    +    // Set to null to not include this header.
    +    //
    +    "XContentTypeOptions": "nosniff",
    +    //
    +    // X-Frame-Options: Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>.
    +    // Values: "DENY" (never allow), "SAMEORIGIN" (allow from same origin only)
    +    // Note: This header is SKIPPED if Antiforgery is enabled (Antiforgery already sets X-Frame-Options: SAMEORIGIN by default).
    +    // Set to null to not include this header.
    +    //
    +    "XFrameOptions": "DENY",
    +    //
    +    // Referrer-Policy: Controls how much referrer information should be included with requests.
    +    // Values: "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin",
    +    //         "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"
    +    // Recommended: "strict-origin-when-cross-origin" (send origin for cross-origin requests, full URL for same-origin)
    +    // Set to null to not include this header.
    +    //
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    //
    +    // Content-Security-Policy: Defines approved sources of content that the browser may load.
    +    // Helps prevent XSS, clickjacking, and other code injection attacks.
    +    // Example: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +    // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    +    // Set to null to not include this header (recommended to configure based on your application needs).
    +    //
    +    "ContentSecurityPolicy": null,
    +    //
    +    // Permissions-Policy: Controls which browser features and APIs can be used.
    +    // Example: "geolocation=(), microphone=(), camera=()" disables these features entirely.
    +    // Example: "geolocation=(self), microphone=()" allows geolocation only from same origin.
    +    // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
    +    // Set to null to not include this header.
    +    //
    +    "PermissionsPolicy": null,
    +    //
    +    // Cross-Origin-Opener-Policy: Controls how your document is shared with cross-origin popups.
    +    // Values: "unsafe-none", "same-origin-allow-popups", "same-origin"
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginOpenerPolicy": null,
    +    //
    +    // Cross-Origin-Embedder-Policy: Prevents a document from loading cross-origin resources that don't explicitly grant permission.
    +    // Values: "unsafe-none", "require-corp", "credentialless"
    +    // Required for SharedArrayBuffer and high-resolution timers (along with COOP: same-origin).
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginEmbedderPolicy": null,
    +    //
    +    // Cross-Origin-Resource-Policy: Indicates how the resource should be shared cross-origin.
    +    // Values: "same-site", "same-origin", "cross-origin"
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginResourcePolicy": null
    +  },
    +
    +  //
    +  // Forwarded Headers: Enables the application to read proxy headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host).
    +  // CRITICAL: Required when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.)
    +  // Without this, the application sees the proxy's IP instead of the client's real IP, and HTTP instead of HTTPS.
    +  // Security Warning: Only enable if you're behind a trusted proxy. Malicious clients can spoof these headers.
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
    +  //
    +  "ForwardedHeaders": {
    +    //
    +    // Enable forwarded headers middleware (automatically placed first in the middleware pipeline).
    +    //
    +    "Enabled": false,
    +    //
    +    // Limits the number of proxy entries that will be processed from X-Forwarded-For.
    +    // Default is 1 (trust only the immediate proxy). Increase if you have multiple proxies in a chain.
    +    // Set to null to process all entries (not recommended for security).
    +    //
    +    "ForwardLimit": 1,
    +    //
    +    // List of IP addresses of known proxies to accept forwarded headers from.
    +    // Example: ["10.0.0.1", "192.168.1.1"]
    +    // If empty and KnownNetworks is also empty, forwarded headers are accepted from any source (less secure).
    +    //
    +    "KnownProxies": [],
    +    //
    +    // List of CIDR network ranges of known proxies.
    +    // Example: ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] for private networks
    +    // Useful when proxy IPs are dynamically assigned within a known range.
    +    //
    +    "KnownNetworks": [],
    +    //
    +    // List of allowed values for the X-Forwarded-Host header.
    +    // Example: ["example.com", "www.example.com"]
    +    // If empty, any host is allowed (less secure). Helps prevent host header injection attacks.
    +    //
    +    "AllowedHosts": []
    +  },
    +
    +  //
    +  // Health Checks: Provides endpoints for monitoring application health, used by container orchestrators (Kubernetes, Docker Swarm),
    +  // load balancers, and monitoring systems to determine if the application is running correctly.
    +  // Three types of checks are supported:
    +  //   - /health: Overall health status (combines all checks)
    +  //   - /health/ready: Readiness probe - is the app ready to accept traffic? (includes database connectivity)
    +  //   - /health/live: Liveness probe - is the app process running? (always returns healthy if app responds)
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
    +  //
    +  "HealthChecks": {
    +    //
    +    // Enable health check endpoints.
    +    //
    +    "Enabled": false,
    +    //
    +    // Cache health check responses server-side in memory for the specified duration.
    +    // Cached responses are served without re-executing the endpoint.
    +    // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +    // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +    //
    +    "CacheDuration": "5 seconds",
    +    //
    +    // Path for the main health check endpoint that reports overall status.
    +    // Returns "Healthy", "Degraded", or "Unhealthy" with HTTP 200 (healthy/degraded) or 503 (unhealthy).
    +    //
    +    "Path": "/health",
    +    //
    +    // Path for the readiness probe endpoint.
    +    // Kubernetes uses this to know when a pod is ready to receive traffic.
    +    // Includes database connectivity check when IncludeDatabaseCheck is true.
    +    // Returns 503 Service Unavailable if database is unreachable.
    +    //
    +    "ReadyPath": "/health/ready",
    +    //
    +    // Path for the liveness probe endpoint.
    +    // Kubernetes uses this to know when to restart a pod.
    +    // Always returns Healthy (200) if the application process is responding.
    +    // Does NOT check database - a slow database shouldn't trigger a container restart.
    +    //
    +    "LivePath": "/health/live",
    +    //
    +    // Include PostgreSQL database connectivity in health checks.
    +    // When true, the readiness probe will fail if the database is unreachable.
    +    //
    +    "IncludeDatabaseCheck": true,
    +    //
    +    // Name for the database health check (appears in detailed health reports).
    +    //
    +    "DatabaseCheckName": "postgresql",
    +    //
    +    // Require authentication for health check endpoints.
    +    // When true, all health endpoints require a valid authenticated user.
    +    // Security Consideration: Health endpoints can reveal information about your infrastructure
    +    // (database connectivity, service status). Enable this if your health endpoints are publicly accessible.
    +    // Note: Kubernetes/Docker health probes may need to authenticate if this is enabled.
    +    //
    +    "RequireAuthorization": false,
    +    //
    +    // Apply a rate limiter policy to health check endpoints.
    +    // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +    // Security Consideration: Prevents denial-of-service attacks targeting health endpoints.
    +    // Set to null to disable rate limiting on health endpoints.
    +    // Example: "fixed" or "bucket" (must match a policy name from RateLimiterOptions).
    +    //
    +    "RateLimiterPolicy": null
    +  },
    +
    +  //
    +  // PostgreSQL Statistics Endpoints
    +  // Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging.
    +  // Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.
    +  //
    +  "Stats": {
    +    //
    +    // Enable PostgreSQL statistics endpoints.
    +    //
    +    "Enabled": false,
    +    //
    +    // Cache stats responses server-side in memory for the specified duration.
    +    // Cached responses are served without re-executing the endpoint.
    +    // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +    // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +    //
    +    "CacheDuration": "5 seconds",
    +    //
    +    // Apply a rate limiter policy to stats endpoints.
    +    // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +    // Set to null to disable rate limiting on stats endpoints.
    +    //
    +    "RateLimiterPolicy": null,
    +    //
    +    // Use a specific named connection for stats queries.
    +    // When null, uses the default connection string.
    +    // Useful when you want to query stats from a different database or use read-only credentials.
    +    //
    +    "ConnectionName": null,
    +    //
    +    // Require authentication for stats endpoints.
    +    // Security Consideration: Stats endpoints can reveal sensitive information about your database
    +    // (table sizes, query patterns, active sessions). Enable this for production environments.
    +    //
    +    "RequireAuthorization": false,
    +    //
    +    // Restrict access to specific roles.
    +    // When null or empty, any authenticated user can access (if RequireAuthorization is true).
    +    // Example: ["admin", "dba"] - only users with admin or dba role can access.
    +    //
    +    "AuthorizedRoles": [],
    +    //
    +    // Output format for stats endpoints: "json" or "html".
    +    // - json: JSON array
    +    // - html: HTML table, Excel-compatible for direct browser copy-paste (default)
    +    // Can be overridden per-request with the ?format= query string parameter (e.g. ?format=json).
    +    //
    +    "OutputFormat": "html",
    +    //
    +    // Filter schemas using PostgreSQL SIMILAR TO pattern.
    +    // When null, all schemas are included.
    +    // Example: "public|myapp%" - includes 'public' and schemas starting with 'myapp'.
    +    //
    +    "SchemaSimilarTo": null,
    +    //
    +    // Path for routine (function/procedure) performance statistics.
    +    // Returns data from pg_stat_user_functions including call counts and execution times.
    +    // Note: Requires track_functions = 'pl' or 'all' in postgresql.conf.
    +    // Enable with: alter system set track_functions = 'all'; select pg_reload_conf();
    +    // Or set track_functions = 'all' directly in postgresql.conf and restart/reload.
    +    //
    +    "RoutinesStatsPath": "/stats/routines",
    +    //
    +    // Path for table statistics.
    +    // Returns data from pg_stat_user_tables including tuple counts, sizes, scan counts, and vacuum info.
    +    //
    +    "TablesStatsPath": "/stats/tables",
    +    //
    +    // Path for index statistics.
    +    // Returns data from pg_stat_user_indexes including scan counts and index definitions.
    +    //
    +    "IndexesStatsPath": "/stats/indexes",
    +    //
    +    // Path for current database activity.
    +    // Returns data from pg_stat_activity showing active sessions, queries, and wait events.
    +    // Security Consideration: Shows currently running queries which may contain sensitive data.
    +    //
    +    "ActivityPath": "/stats/activity"
    +  },
    +
    +  //
    +  // SQL test runner. Invoked with the \`--test\` command-line flag (this section is otherwise inert).
    +  // Discovers *.test.sql files, runs each in an isolated non-pooled connection, can invoke endpoints
    +  // in-process from an embedded \`/* GET /path */\` block (response captured into a temp table), and
    +  // asserts via boolean-returning SELECTs or \`do $$ ... assert ... $$;\` blocks. Exit codes:
    +  // 0 pass, 1 failures, 2 errors, 3 config/runner error, 4 no tests found.
    +  //
    +  "TestRunner": {
    +    //
    +    // Glob (same engine as SqlFileSource) selecting test files. Empty disables discovery. Two layouts:
    +    // co-located (app.sql next to app.test.sql — SqlFileSource SkipPattern keeps tests out of the endpoints)
    +    // or a separate tests tree (e.g. "./tests/**/*.test.sql").
    +    //
    +    "FilePattern": "",
    +    //
    +    // Optional filter narrowing the discovered set — the fast path for iterating on one test:
    +    //   npgsqlrest ... --test --testrunner:filter=login
    +    // Matched against each file's cwd-relative path: a value without wildcards is a substring match;
    +    // with wildcards it is the same glob engine as FilePattern. Empty = run everything discovered.
    +    //
    +    "Filter": "",
    +    //
    +    // Tag filtering (comma- or whitespace-separated lists; case-insensitive). A test file declares tags
    +    // with a \`-- @tag name [name ...]\` header annotation. "Tag" runs only files carrying at least one of
    +    // the listed tags; "ExcludeTag" skips files carrying any of them (exclude wins). Composes with Filter.
    +    //   npgsqlrest ... --test --testrunner:tag=smoke --testrunner:excludetag=slow
    +    //
    +    "Tag": "",
    +    "ExcludeTag": "",
    +    //
    +    // Optional: a ConnectionStrings entry to run the tests against instead of the app's main connection. In
    +    // test mode it becomes the connection used for endpoint type-checking (Describe) and execution, so it can
    +    // point at a dedicated test database that a Setup step creates first (it need not exist at startup).
    +    // Empty = use the main connection. Tip: {rnd1}..{rnd10} are random lowercase tokens (length = the digit),
    +    // stable for the whole run, usable in any connection string or Setup/Teardown SQL (e.g. Database=app_test_{rnd6}).
    +    // Need several distinct tokens of the same length? Indexed instances {rndN_1}..{rndN_9} are each independent.
    +    //
    +    "ConnectionName": "",
    +    //
    +    // Max test files run concurrently. 0 => processor count. Each test uses its own non-pooled connection.
    +    //
    +    "MaxParallelism": 0,
    +    //
    +    // Stop scheduling new tests after the first failure/error (in-flight tests still finish).
    +    //
    +    "FailFast": false,
    +    //
    +    // Per-test timeout. Accepts "30s", "5m", "1h", a plain number of seconds, or "hh:mm:ss". 0 disables.
    +    //
    +    "PerTestTimeout": "30s",
    +    //
    +    // Optional path to also write a JUnit XML report (console output is always printed).
    +    //
    +    "JUnitOutput": null,
    +    //
    +    // Skip Teardown so a failed run's state can be inspected.
    +    //
    +    "Keep": false,
    +    //
    +    // Detailed console REPORT: list passed assertions, print the full failing SQL statement, and show
    +    // captured \`raise notice\` output for passing tests too. This shapes the report only — for diagnostic
    +    // logging of every executed query/HTTP call, raise the log channel instead (Log:MinimalLevels + LoggerName).
    +    //
    +    "DetailedReport": false,
    +    //
    +    // Treat "no tests discovered" as success (exit 0) instead of exit 4.
    +    //
    +    "AllowEmpty": false,
    +    //
    +    // Endpoint-coverage summary after the run: exercised N of M testable endpoints + the list of untested
    +    // ones (endpoint kinds the runner rejects — SSE, upload, login/logout, outbound proxy — are excluded
    +    // from the ratio and counted separately). Tri-state: null (default) reports after FULL runs but stays
    +    // quiet when the run is narrowed by Filter/Tag (a deliberately partial run would just nag); true always
    +    // reports; false never. CoverageThreshold (0-100) always reports and fails an otherwise-passing run
    +    // with exit 2 when coverage is below it — CI gating for "every endpoint has a test".
    +    //
    +    "Coverage": null,
    +    "CoverageThreshold": null,
    +    //
    +    // SourceContext name for the runner's own log channel; set its level independently under Log:MinimalLevels.
    +    // Discovery/parsing log at Debug, each query and HTTP invocation at Verbose, \`raise notice\` by severity.
    +    //
    +    "LoggerName": "NpgsqlRestTest",
    +    //
    +    // Per-HTTP-block temp table that captures the response. Each block gets its own fresh temp table
    +    // (created without IF NOT EXISTS, so a duplicate name fails the test); writes are pg_temp-qualified.
    +    // A file with ONE HTTP block uses "Name"; a file with 2+ blocks uses "MultiNamePattern" where {n} is
    +    // the 1-based block ordinal (_response_1, _response_2, ...). Per-block override: \`# @response <name>\`.
    +    // A null/empty column name omits that column. "DebugTable" (e.g. "_responses_debug"): ALSO mirror every
    +    // response into a PERMANENT table for post-run inspection — survives rollbacks, holds the last run, one
    +    // row per HTTP block with test_file/block/method/path metadata (debugging aid; do not enable in CI).
    +    //
    +    "ResponseTempTable": {
    +      "Name": "_response",
    +      "MultiNamePattern": "_response_{n}",
    +      "DebugTable": null,
    +      "Columns": {
    +        "Status": "status",
    +        "Body": "body",
    +        "ContentType": "content_type",
    +        "Headers": "headers",
    +        "IsSuccess": "is_success"
    +      }
    +    },
    +    //
    +    // Named, reusable steps (name → step; same shape as Setup/Teardown entries). Reference them by name in
    +    // Setup/Teardown below, or from an individual test file's leading header comments:
    +    //   -- @setup StepName [StepName ...]      runs before that file
    +    //   -- @teardown StepName [StepName ...]   runs after that file (always, best-effort)
    +    //   -- @connection Name                    runs that file on a named ConnectionStrings entry
    +    // Annotations are repeatable; names may be whitespace- or comma-separated and run in the order written.
    +    // Test files can also reuse SQL scripts in place with psql-style includes: \`\\i file\` (cwd-relative) or
    +    // \`\\ir file\` (relative to the including file) — executed on the test's connection, inside its transaction.
    +    //
    +    // The entries below are disabled EXAMPLES showing every step property ("Sql", "SqlFile", "Command",
    +    // "WorkingDirectory", "ConnectionName") across the typical scenarios — flip "Enabled" to true (and adjust
    +    // names, paths, and connections) instead of typing them from scratch. A step with "Enabled": false is
    +    // simply IGNORED wherever it is referenced.
    +    //
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    },
    +    //
    +    // Run-once setup, BEFORE endpoint discovery. Steps run in the EXACT order written. Each entry is a step
    +    // NAME from "Steps" above, or an inline step object:
    +    //   { "Command": "...", "WorkingDirectory": "..." }    — runs via the OS shell
    +    //   { "Sql": "..." } | { "SqlFile": "..." }            — runs on the test connection; set "ConnectionName"
    +    //                                                         to run on another ConnectionStrings entry (e.g.
    +    //                                                         an admin connection that runs \`create database\`).
    +    //
    +    "Setup": [],
    +    //
    +    // Run-once teardown, ALWAYS (best-effort), in the EXACT order written. \`Keep\` skips it. Same entries as Setup.
    +    //
    +    "Teardown": []
    +  },
    +
    +  //
    +  // Watch mode (interactive/dev-only) — one feature, two flavors: with --test it re-runs tests on
    +  // changes (a changed test file re-runs alone; a changed endpoint file or database routine rebuilds
    +  // endpoints in-process and re-runs everything; teardown runs once, on exit); without --test it
    +  // supervises the SERVER and restarts it on SQL file source, configuration, and database routine
    +  // changes. In both flavors a broken SQL file cannot kill the session (SqlFileSource ErrorMode is
    +  // forced from Exit to Skip while watching).
    +  //
    +  "Watch": {
    +    //
    +    // Turn watch mode on. The --watch command line flag is the shorthand for this setting.
    +    //
    +    "Enabled": false,
    +    //
    +    // Poll the database for routine changes and restart the server (server watch) or rebuild endpoints and
    +    // re-run the tests (test watch). The poll runs the SAME routine discovery query the endpoint source
    +    // uses (same configured filters), hashed server-side into one value — so it detects exactly what
    +    // changes discovered endpoints: functions/procedures (create/replace/drop/alter, grants), their
    +    // COMMENT ON annotations, and the composite types and tables their signatures use; anything the
    +    // discovery does not read can never trigger. Accepts "2s", "500ms", "1m", a plain number of seconds,
    +    // or "hh:mm:ss"; 0 disables. One query per interval on a dedicated non-pooled connection.
    +    //
    +    "DatabasePollingInterval": "2s"
    +  },
    +
    +  //
    +  // Command retry strategies and options for client and middleware commands.
    +  //
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        //
    +        // Retry sequence in seconds. Accepts decimal numbers (0.25 is quarter of a second). The length of the array determines the maximum number of retries.
    +        //
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        //
    +        // Error codes that will trigger a retry when executing a command. See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +        //
    +        "ErrorCodes": [
    +          // Serialization failures (MUST retry for correctness)
    +          "40001", // serialization_failure 
    +          "40P01", // deadlock_detected
    +          // Connection issues (Class 08)
    +          "08000", // connection_exception
    +          "08003", // connection_does_not_exist
    +          "08006", // connection_failure  
    +          "08001", // sqlclient_unable_to_establish_sqlconnection
    +          "08004", // sqlserver_rejected_establishment_of_sqlconnection
    +          "08007", // transaction_resolution_unknown
    +          "08P01", // protocol_violation
    +          // Resource constraints (Class 53)
    +          "53000", // insufficient_resources
    +          "53100", // disk_full
    +          "53200", // out_of_memory
    +          "53300", // too_many_connections
    +          "53400", // configuration_limit_exceeded
    +          // System errors (Class 58) 
    +          "57P01", // admin_shutdown
    +          "57P02", // crash_shutdown  
    +          "57P03", // cannot_connect_now
    +          "58000", // system_error
    +          "58030", // io_error
    +          // Lock acquisition issues (Class 55)
    +          "55P03", // lock_not_available
    +          "55006", // object_in_use
    +          "55000"  // object_not_in_prerequisite_state
    +        ]
    +      }
    +    }
    +  },
    +  
    +  //
    +  // Caching options for routines that support caching. Currently, routines that return a single result set can be cached. Returning table or "setof" cannot be cached.
    +  // To enable caching for a routine, add the following comment annotation to the routine:
    +  // cached [ param1, param2, param3 [, ...] ] - parameters are optional, if no parameters are specified, all parameters are used for cache key.
    +  // cache_expires [ value ] or cache_expires_in [ value ] - accepts PostgreSQL interval format (for example: '5 minutes' or '5min', '1 second' or '1s', etc.). Default is forever (no expiration).
    +  // 
    +  "CacheOptions": {
    +    "Enabled": false,
    +    //
    +    // Cache type: Memory, Redis, or Hybrid
    +    // - Memory: In-process memory cache (fastest, single instance only)
    +    // - Redis: Distributed Redis cache (slower, shared across instances)
    +    // - Hybrid: Uses Microsoft.Extensions.Caching.Hybrid which provides:
    +    //   - Automatic stampede protection to prevent multiple concurrent requests from hitting the database
    +    //   - Optional Redis L2 backend (enable with HybridCacheUseRedisBackend: true) for sharing cache across instances
    +    //   - Without Redis, works as in-memory cache with stampede protection
    +    //
    +    "Type": "Memory",
    +    //
    +    // When memory cache is used, this value determines how often the cache will be pruned for expired items (in seconds).
    +    //
    +    "MemoryCachePruneIntervalSeconds": 60,
    +    //
    +    // Redis configuration string. Used when Type is "Redis", or when Type is "Hybrid" with UseRedisBackend: true.
    +    // See: https://stackexchange.github.io/StackExchange.Redis/Configuration.html
    +    //
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
    +    //
    +    // Maximum number of rows that can be cached for set-returning functions.
    +    // If a result set exceeds this limit, it will not be cached (but will still be returned).
    +    // Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    +    //
    +    "MaxCacheableRows": 1000,
    +    //
    +    // When true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string (64 characters).
    +    // This reduces memory usage for long cache keys and improves Redis performance with large keys.
    +    // Recommended for Redis cache or when caching routines with many/large parameters.
    +    //
    +    "UseHashedCacheKeys": false,
    +    //
    +    // Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true.
    +    // Keys shorter than this threshold are stored as-is for better debuggability.
    +    //
    +    "HashKeyThreshold": 256,
    +    //
    +    // When set, creates an additional invalidation endpoint for each cached endpoint.
    +    // The invalidation endpoint has the same path with this suffix appended.
    +    // For example, if a cached endpoint is /api/my-endpoint/ and this is set to "invalidate",
    +    // an invalidation endpoint /api/my-endpoint/invalidate will be created.
    +    // Calling the invalidation endpoint with the same parameters removes the cached entry.
    +    //
    +    "InvalidateCacheSuffix": null,
    +    //
    +    // --- Hybrid Cache specific options (only used when Type is "Hybrid") ---
    +    //
    +    // When true, uses Redis as the L2 (secondary/distributed) cache backend.
    +    // When false (default), HybridCache uses in-memory only but still provides stampede protection.
    +    // Stampede protection prevents multiple concurrent requests from hitting the database when cache expires.
    +    //
    +    "HybridCacheUseRedisBackend": false,
    +    //
    +    // Maximum length of cache keys in characters. Keys longer than this will be rejected.
    +    // Default: 1024
    +    //
    +    "HybridCacheMaximumKeyLength": 1024,
    +    //
    +    // Maximum size of cached payloads in bytes.
    +    // Default: 1048576 (1 MB)
    +    //
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    //
    +    // Default expiration for cached entries (both L1 and L2). Accepts PostgreSQL interval format.
    +    // Examples: '5 minutes', '1 hour', '30 seconds'
    +    // If not set, individual endpoint cache_expires annotations are used, or entries don't expire.
    +    //
    +    "HybridCacheDefaultExpiration": null,
    +    //
    +    // Expiration for L1 (in-memory) cache. If not set, uses DefaultExpiration value.
    +    // Set this shorter than DefaultExpiration to refresh local cache more frequently from Redis.
    +    // Accepts PostgreSQL interval format.
    +    //
    +    "HybridCacheLocalCacheExpiration": null,
    +    //
    +    // Named caching profiles. Endpoints opt into a profile via the \`cache_profile <name>\` comment annotation;
    +    // the profile then supplies the cache backend, default expiration, default key parameters, and per-parameter
    +    // skip-cache conditions. Endpoints WITHOUT \`cache_profile\` continue to use the root cache configured above.
    +    //
    +    // Each profile object supports:
    +    //   - "Enabled" (bool, default false): set to true to register the profile. Disabled profiles are ignored.
    +    //   - "Type": "Memory" | "Redis" | "Hybrid" (required when enabled). Backends are pooled — all profiles of the
    +    //     same type share one instance (one Memory cache, one Redis connection, one HybridCache singleton).
    +    //     A backend is only instantiated if its type is used by the root or some enabled profile.
    +    //   - "Expiration": PostgreSQL interval (e.g. "30 seconds", "5 minutes"); used as the default when the
    +    //     endpoint has no \`cache_expires\` annotation. The annotation overrides this.
    +    //   - "Parameters": cache-key parameter list with three semantics:
    +    //       null/missing  → use ALL routine parameters (different requests → different entries).
    +    //       []            → URL-only cache (one entry per endpoint, regardless of inputs).
    +    //       ["x", "y"]    → use only these named parameters as the key.
    +    //     The endpoint's \`cached p1, p2\` annotation overrides this list.
    +    //   - "When": optional list of conditional rules. Each rule has:
    +    //       - "Parameter": routine parameter name to inspect.
    +    //       - "Value": scalar (exact match) or array (OR over entries). JSON null matches .NET null/DBNull (NOT empty string).
    +    //       - "Then": "skip" → bypass the cache for this request; or a PostgreSQL interval like "30 seconds" → use this
    +    //                 as the TTL override when writing.
    +    //     Rules are evaluated in declaration order; first match wins. No match → fall through to "Expiration" above.
    +    //     A rule's Parameter must be in the profile's "Parameters" list (or in the endpoint's @cached annotation),
    +    //     otherwise the rule is dropped at startup with a Warning.
    +    //
    +    // Common patterns:
    +    //   - Skip cache when a date is null (always-fresh data):
    +    //       "When": [ { "Parameter": "to", "Value": null, "Then": "skip" } ]
    +    //   - Tiered TTL by user role:
    +    //       "When": [
    +    //         { "Parameter": "tier", "Value": "free", "Then": "5 minutes" },
    +    //         { "Parameter": "tier", "Value": "pro",  "Then": "1 hour" }
    +    //       ]
    +    //
    +    // Unknown profile names referenced by \`@cache_profile\` cause startup to fail with a single error listing all
    +    // typos and the offending endpoints. Unused profiles (registered but not referenced) log an Information warning.
    +    //
    +    // Three disabled example profiles below show all three Types and all four profile fields. Flip "Enabled": true
    +    // on the one(s) you want to use.
    +    //
    +    "Profiles": {
    +      "fast_memory": {
    +        "Enabled": false,
    +        "Type": "Memory",
    +        "Expiration": "30 seconds",
    +        "Parameters": ["user_id"]
    +      },
    +      "shared_redis": {
    +        "Enabled": false,
    +        "Type": "Redis",
    +        "Expiration": "1 hour"
    +      },
    +      "date_range_hybrid": {
    +        "Enabled": false,
    +        "Type": "Hybrid",
    +        "Expiration": "5 minutes",
    +        "Parameters": ["from", "to"],
    +        "When": [
    +          { "Parameter": "to", "Value": null, "Then": "skip" }
    +        ]
    +      }
    +    }
    +  },
    +
    +  //
    +  // Parameter validation options for validating endpoint parameters before database execution.
    +  // Validation rules can be referenced in comment annotations using "validate _param using rule_name" syntax.
    +  //
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    //
    +    // Named validation rules that can be referenced in comment annotations.
    +    // Default rules: not_null, not_empty, required, email
    +    //
    +    // Each rule can have:
    +    // - Type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    +    // - Pattern: Regular expression pattern for Regex type
    +    // - MinLength: Minimum length for MinLength type
    +    // - MaxLength: Maximum length for MaxLength type
    +    // - Message: Error message with placeholders {0}=original name, {1}=converted name, {2}=rule name
    +    // - StatusCode: HTTP status code to return (default: 400)
    +    //
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\\\s]+@[^@\\\\s]+\\\\.[^@\\\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  },
    +
    +  //
    +  // Rate Limiter settings to limit the number of requests from clients.
    +  //
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    // Global defaults for rejected (rate-limited) requests. Each policy below may override either of these
    +    // independently via its own "StatusCode"/"StatusMessage" — useful when one policy guards logins and
    +    // another guards a public API and each needs its own message. A policy that omits them inherits these.
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    //
    +    // Named rate-limiter policies. The object key is the policy name (referenced from endpoints via the
    +    // \`rate_limiter_policy <name>\` comment annotation, or used as \`DefaultPolicy\` above).
    +    //
    +    // Each policy has a \`Type\` of FixedWindow, SlidingWindow, TokenBucket, or Concurrency, and a set of
    +    // type-specific tuning fields. Set \`"Enabled": true\` to register the policy at startup.
    +    //
    +    // Each policy may also set its own \`StatusCode\` and/or \`StatusMessage\` to override the global values
    +    // above for requests rejected by that specific policy. Omit either to inherit the global default. The
    +    // "fixed" policy below shows the commented-out fields.
    +    //
    +    // **Breaking change in 3.13.0**: this section was previously an array of objects with explicit \`"Name"\`
    +    // properties. It is now an object keyed by policy name, matching \`ValidationOptions:Rules\` and
    +    // \`CacheOptions:Profiles\`. Migrate by moving each policy's \`Name\` value to be the JSON key and dropping
    +    // the \`Name\` field.
    +    //
    +    "Policies": {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#fixed
    +      "fixed": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +        // Override the global rejection response for this policy only (omit to inherit the global values):
    +        // "StatusCode": 429,
    +        // "StatusMessage": "Too many requests for this endpoint. Please slow down.",
    +        //
    +        // To partition this policy (per-user, per-IP, etc.) so each request gets its own bucket
    +        // instead of sharing a single global one, add a "Partition" block. See the "per_user"
    +        // policy below for a complete example.
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#sliding-window-limiter
    +      "sliding": {
    +        "Type": "SlidingWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "SegmentsPerWindow": 6,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#token-bucket-limiter
    +      "bucket": {
    +        "Type": "TokenBucket",
    +        "Enabled": false,
    +        "TokenLimit": 100,
    +        "TokensPerPeriod": 10,
    +        "ReplenishmentPeriodSeconds": 10,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#concurrency-limiter
    +      "concurrency": {
    +        "Type": "Concurrency",
    +        "Enabled": false,
    +        "PermitLimit": 10,
    +        "QueueLimit": 5,
    +        "OldestFirst": true
    +      },
    +      //
    +      // Example of a partitioned policy. With "Partition" set, each request resolves a partition key
    +      // (per-user, per-IP, etc.) and gets its own bucket. Without "Partition", all requests under a
    +      // policy share a single global bucket. Any of the four limiter Types above can be partitioned —
    +      // FixedWindow is shown here only because it is the most common.
    +      //
    +      // Sources (ordered) — first source returning a non-empty key wins. Fallback "unpartitioned"
    +      // is used if no source matches. Source types:
    +      //   { "Type": "Claim",     "Name": "<claim type>" }
    +      //   { "Type": "IpAddress" }
    +      //   { "Type": "Header",    "Name": "<header name>" }
    +      //   { "Type": "Static",    "Value": "<literal key>" }   ← terminal fallback
    +      //
    +      // BypassAuthenticated (bool, default false) — when true, authenticated users skip rate
    +      // limiting entirely. Evaluated before Sources, useful for "throttle anonymous only".
    +      //
    +      "per_user": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true,
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "Claim",     "Name": "name_identifier" },
    +            { "Type": "IpAddress" },
    +            { "Type": "Static",    "Value": "anonymous" }
    +          ],
    +          "BypassAuthenticated": false
    +        }
    +      },
    +      //
    +      // Ready-to-use login throttle: 10 attempts per minute per client IP, with its own rejection
    +      // message. Apply it to a login endpoint with \`rate_limiter login_throttle\` (or set it as
    +      // DefaultPolicy). Set "Enabled": true to activate.
    +      //
    +      "login_throttle": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 0,
    +        "AutoReplenishment": true,
    +        "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "IpAddress" }
    +          ],
    +          "BypassAuthenticated": false
    +        }
    +      }
    +    }
    +  },
    +  
    +  //
    +  // Error handling options for NpgsqlRest middleware
    +  //
    +  "ErrorHandlingOptions": {
    +    // Remove Type URL from error responses. Middleware automatically sets a default Type URL based on the HTTP status code that points to the RFC documentation.
    +    "RemoveTypeUrl": false,
    +    // Remove TraceId field from error responses. Useful in development and debugging scenarios to correlate logs with error responses.
    +    "RemoveTraceId": true,
    +    //
    +    // Default policy name to use from the ErrorCodePolicies section.
    +    //
    +    "DefaultErrorCodePolicy": "Default",
    +    //
    +    // Timeout error mapping when command timeout occurs (see NpgsqlRest CommandTimeout setting).
    +    //
    +    "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}, // timeout error case -> 504 Gateway Timeout
    +    //
    +    // Named policies for mapping of PostgreSQL error codes to HTTP Status Codes.
    +    //
    +    // If routine raises these PostgreSQL error codes, endpoint will return these HTTP Status Codes.
    +    // See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +    // Exception is timeout, which is not a PostgreSQL error code, but a special case when command timeout occurs.
    +    //
    +    // - StatusCode: HTTP status code to return.
    +    // - Title: Optional title field in response JSON. When null, actual error message is used.
    +    // - Details: Optional details field in response JSON. When null, PostgreSQL Error Code is used.
    +    // - Type: Optional types field in response JSON. A URI reference [RFC3986] that identifies the problem type. Set to null to use default. Or RemoveTypeUrl to true to disable.
    +    //
    +    "ErrorCodePolicies": [{
    +      "Name": "Default",
    +      "ErrorCodes": {
    +        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},   // query_canceled      -> 403 Forbidden
    +        "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},                // query_canceled      -> 205 Reset Content
    +        "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // raise_exception     -> 400 Bad Request
    +        "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}                        // assert_failure      -> 400 Bad Request
    +      }
    +    }]
    +  },
    +  
    +  //
    +  // NpgsqlRest HTTP Middleware General Configuration
    +  //
    +  "NpgsqlRest": {
    +    //
    +    // Connection name to be used from the ConnectionStrings section or NULL to use the first available connection string.
    +    //
    +    "ConnectionName": null,
    +    //
    +    // Allow using multiple connections from the ConnectionStrings section. When set to true, the connection name can be set for individual Routines.
    +    // Some routines might use the primary database connection string, while others might want to use a read-only connection string from the replica servers.
    +    //
    +    "UseMultipleConnections": false,
    +    //
    +    // Command timeout, after which the command will be cancelled and default timeout error policy will be applied. (see ErrorCodePolicies) 
    +    // Value is in PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) or \`null\` to use the default timeout of 30 seconds.
    +    //
    +    "CommandTimeout": null,
    +    //
    +    // Filter schema names similar to this parameter or \`null\` to ignore this parameter.
    +    //
    +    "SchemaSimilarTo": null,
    +    //
    +    // Filter schema names NOT similar to this parameter or \`null\` to ignore this parameter.
    +    //
    +    "SchemaNotSimilarTo": null,
    +    //
    +    // List of schema names to be included or \`null\` to ignore this parameter.
    +    //
    +    "IncludeSchemas": null,
    +    //
    +    // List of schema names to be excluded or \`null\` to ignore this parameter.
    +    //
    +    "ExcludeSchemas": null,
    +    //
    +    // Filter names similar to this parameter or \`null\` to ignore this parameter.
    +    //
    +    "NameSimilarTo": null,
    +    //
    +    // Filter names NOT similar to this parameter or \`null\` to ignore this parameter.
    +    //
    +    "NameNotSimilarTo": null,
    +    //
    +    // List of names to be included or \`null\` to ignore this parameter.
    +    //
    +    "IncludeNames": null,
    +    //
    +    // List of names to be excluded or \`null\` to ignore this parameter.
    +    //
    +    "ExcludeNames": null,
    +    //
    +    // Configure how comment annotations behave and which routines become endpoints. \`Ignore\` creates all endpoints and ignores annotations. \`ParseAll\` creates all endpoints and parses annotations. \`OnlyAnnotated\` (default) creates only routines whose comment has a recognized exposure tag — an \`HTTP\` tag, or a plugin annotation that requests an endpoint (e.g. \`mcp\`); modifier-only comments (e.g. just \`authorize\`) create nothing. \`OnlyWithHttpTag\` is a backward-compatible alias of \`OnlyAnnotated\` (identical behavior; existing configs keep working).
    +    //
    +    "CommentsMode": "OnlyAnnotated",
    +    //
    +    // The URL prefix string for every URL created by the default URL builder or \`null\` to ignore the URL prefix.
    +    //
    +    "UrlPathPrefix": "/api",
    +    //
    +    // Convert all URL paths to kebab-case from the original PostgreSQL names.
    +    //
    +    "KebabCaseUrls": true,
    +    //
    +    // Convert all parameter names to camel case from the original PostgreSQL paramater names.
    +    //
    +    "CamelCaseNames": true,
    +    //
    +    // When set to true, it will force all created endpoints to require authorization. Authorization requirements for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "RequiresAuthorization": true,
    +    //
    +    // When this value is true, all connection events are logged (depending on the level). This is usually triggered by the PostgreSQL RAISE statements. 
    +    // Set to false to turn off logging these events.
    +    //
    +    "LogConnectionNoticeEvents": true,
    +    //
    +    // MessageOnly - Log only connection messages. FirstStackFrameAndMessage - Log first stack frame and the message. FullStackAndMessage - Log full stack trace and message.
    +    //
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    //
    +    // Set this option to true to log information for every executed command and query (including parameters and parameter values) in debug level.
    +    //
    +    "LogCommands": false,
    +    //
    +    // Set this option to true to include parameter values when logging commands. This only applies when \`LogCommands\` is true.
    +    //
    +    "LogCommandParameters": false,
    +    //
    +    // Set this option to false to suppress debug-level logs when endpoints are created.
    +    // When true (default), debug logs are emitted for each endpoint creation showing URL and method.
    +    //
    +    "DebugLogEndpointCreateEvents": true,
    +    //
    +    // Set this option to false to suppress debug-level logs when comment annotations are parsed.
    +    // When true (default), debug logs are emitted for each comment annotation that is successfully processed.
    +    //
    +    "DebugLogCommentAnnotationEvents": true,
    +    //
    +    // When not null, forces a method type for all created endpoints. Method types are \`GET\`, \`PUT\`, \`POST\`, \`DELETE\`, \`HEAD\`, \`OPTIONS\`, \`TRACE\`, \`PATCH\` or \`CONNECT\`. When this value is null (default), the method type is always \`GET\` when the routine volatility option is not volatile or the routine name starts with, \`get_\`, contains \`_get_\` or ends with \`_get\` (case-insensitive). Otherwise, it is \`POST\`. This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "DefaultHttpMethod": null,
    +    //
    +    // When not null, sets the request parameter position (request parameter types) for all created endpoints. Values are \`QueryString\` (parameters are sent using query string) or \`BodyJson\` (parameters are sent using JSON request body). When this value is null (default), request parameter type is \`QueryString\` for all \`GET\` and \`DELETE\` endpoints, otherwise, request parameter type is \`BodyJson\`. This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "DefaultRequestParamType": null,
    +    //
    +    // Sets the default behavior for handling NULL values in query string parameters.
    +    // - \`Ignore\` (default): No special handling - empty strings stay as empty strings, "null" literal stays as "null" string.
    +    // - \`EmptyString\`: Empty query string values are interpreted as NULL values. This limits sending empty strings via query strings.
    +    // - \`NullLiteral\`: Literal string "null" (case insensitive) is interpreted as NULL value.
    +    // This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "QueryStringNullHandling": "Ignore",
    +    //
    +    // Sets the default behavior for plain text responses when the execution returns NULL from the database.
    +    // - \`EmptyString\` (default): Returns an empty string response with status code 200 OK.
    +    // - \`NullLiteral\`: Returns a string literal "NULL" with status code 200 OK.
    +    // - \`NoContent\`: Returns status code 204 NO CONTENT.
    +    // This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "TextResponseNullHandling": "EmptyString",
    +    //
    +    // Configure how to send request headers to PostgreSQL routines execution: 
    +    // - \`Ignore\` (default) don't send any request headers to routines. 
    +    // - \`Context\` sets a context variable for the current session \`context.headers\` containing JSON string with current request headers. This executes \`set_config('context.headers', headers, false)\` before any routine executions. 
    +    // - \`Parameter\` sends request headers to the routine parameter defined with the \`RequestHeadersParameterName\` option. Parameter with this name must exist, must be one of the JSON or text types and must have the default value defined. This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "RequestHeadersMode": "Parameter",
    +    //
    +    // Name of the context variable that will receive the request headers when RequestHeadersMode is set to Context.
    +    //
    +    "RequestHeadersContextKey": "request.headers",
    +    //
    +    // Sets a parameter name that will receive a request headers JSON when the \`Parameter\` value is used in \`RequestHeadersMode\` options. A parameter with this name must exist, must be one of the JSON or text types and must have the default value defined. This option for individual endpoints can be changed with the \`EndpointCreated\` function callback, or by using comment annotations.
    +    //
    +    "RequestHeadersParameterName": "_headers",
    +    //
    +    // When true, EVERY request is wrapped in an explicit BEGIN/COMMIT, and all \`set_config\` calls switch to the
    +    // transaction-local form (\`is_local=true\`).
    +    // Required when using a connection pooler in transaction mode (PgBouncer transaction-pool, AWS RDS Proxy in transaction mode,
    +    // Supabase Pooler) — without this, the backend can be reused across unrelated requests, allowing GUC state (and the routine
    +    // call itself) to leak or split mid-request. Default false to preserve existing behavior; safe to leave off when using Npgsql's native pool only.
    +    //
    +    "WrapInTransaction": false,
    +    //
    +    // Controls how JSON datetime strings are interpreted when bound to timestamp / timestamptz / time / timetz parameters.
    +    // When true (default since 3.16.0), Z- and offset-bearing strings are converted to UTC, and naive strings (no offset, no Z)
    +    // are assumed UTC. Stored values are then identical regardless of the host process's TZ environment.
    +    // When false, the legacy pre-3.16.0 behavior is restored: DateTime.TryParse interprets the string in the host's local time
    +    // zone, which silently shifts stored values by the host's UTC offset on non-UTC hosts. Only set to false if you have callers
    +    // that depend on host-local interpretation of naive datetime strings and you cannot update them to send Z-suffixed values.
    +    //
    +    "JsonTimestampsAreUtc": true,
    +    //
    +    // SQL commands executed after any context is set but before the main routine call. Run in the same batch as the
    +    // context \`set_config\` calls (no extra round-trip). Each entry can be either:
    +    //   - A raw SQL string (always runs, no parameters), or
    +    //   - An object with \`Enabled\`, \`Sql\`, and optional \`Parameters\`. Object entries are gated by \`Enabled\` (default false) —
    +    //     set \`"Enabled": true\` to activate.
    +    // Each parameter has a \`Source\` (Claim, RequestHeader, or IpAddress) and an optional \`Name\` (claim type or header name;
    +    // ignored for IpAddress). Values are bound at request time via parameterized SQL.
    +    // Common use case: setting \`search_path\` from a tenant claim for multi-tenant deployments.
    +    // Combine with \`WrapInTransaction = true\` for transaction-local scoping (required for connection poolers in transaction mode).
    +    //
    +    "BeforeRoutineCommands": [
    +      {
    +        "Enabled": false,
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [
    +          { "Source": "Claim", "Name": "tenant_id" }
    +        ]
    +      }
    +    ],
    +    //
    +    // Add the unique NpgsqlRest instance id request header with this name to the response or set to null to ignore.
    +    //
    +    "InstanceIdRequestHeaderName": null,
    +    //
    +    // Custom request headers dictionary that will be added to NpgsqlRest requests. Note: these values are added to the request headers dictionary before they are sent as a context or parameter to the PostgreSQL routine and as such not visible to the browser debugger.
    +    //
    +    "CustomRequestHeaders": {
    +    },
    +    //
    +    // Allowlist of environment variable names available to {name} placeholder substitution in comment annotation values (response headers, custom parameters, HTTP custom type calls), alongside the routine's parameters. Resolved once at startup (a value change requires a restart). Array form lists names (a missing variable becomes the empty string); object form maps name -> default {"WEATHER_API_KEY":""} where the default is used when the variable is absent. Names are matched case-insensitively, and a routine parameter of the same name takes precedence. SECURITY: a value used in a RESPONSE header is sent to the client - reserve secrets (API keys, tokens) for outbound HTTP custom type calls, and use response headers only for non-secret values (e.g. server/environment name).
    +    //
    +    "AvailableEnvVars": [],
    +    //
    +    // Name of the request ID header that will be used to track requests. This is used to correlate requests with server event streaming connection ids.
    +    //
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +    //
    +    // Default server-sent event notice message level: INFO, NOTICE, WARNING.
    +    // When SSE path is set, generate SSE events for PostgreSQL notice messages with this level or higher.
    +    // This can be overridden for individual endpoints using comment annotations.
    +    //
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    //
    +    // 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.
    +    //
    +    "ServerSentEventsResponseHeaders": {
    +    },
    +    //
    +    // When true (default), log a one-time warning per endpoint when a RAISE at the configured SSE notice level fires inside a routine
    +    // that has no @sse or @sse_publish annotation. Notices are logged but NOT broadcast to SSE subscribers; the warning surfaces the
    +    // likely missing annotation. Inactive when no endpoint in the build participates in SSE publishing — projects that don't use SSE
    +    // pay zero overhead and see no warnings.
    +    //
    +    "WarnUnboundServerSentEventsNotices": true,
    +    //
    +    // Options for handling PostgreSQL routines (functions and procedures)
    +    //
    +    "RoutineOptions": {
    +      //
    +      // Set to false to disable the routine source (PostgreSQL functions and procedures). Default is true.
    +      //
    +      "Enabled": true,
    +      //
    +      // Name separator for parameter names when using custom type parameters. 
    +      // Parameter names will be in the format: {ParameterName}{CustomTypeParameterSeparator}{CustomTypeFieldName}. When NULL, default underscore is used.
    +      // This is used when using custom types for parameters. For example: with "create type custom_type1 as (value text);" and parameter "_p custom_type1", this name will be merged into "_p_value"
    +      //
    +      "CustomTypeParameterSeparator": null,
    +      //
    +      // List of PostgreSQL routine language names to include. If NULL, all languages are included. Names are case-insensitive.
    +      //
    +      "IncludeLanguages": null,
    +      //
    +      // List of PostgreSQL routine language names to exclude. If NULL, "C" and "INTERNAL" are excluded by default. Names are case-insensitive.
    +      //
    +      "ExcludeLanguages": null,
    +      //
    +      // When true, composite type columns in return tables are serialized as nested JSON objects.
    +      // For example, a table column "req" of type "my_request(id int, name text)" becomes {"req": {"id": 1, "name": "test"}}
    +      // instead of the default flat structure {"id": 1, "name": "test"}.
    +      // Default is false for backward compatibility.
    +      //
    +      "NestedJsonForCompositeTypes": false,
    +      //
    +      // When true, nested composite types and arrays of composite types within composite fields
    +      // are serialized as JSON objects/arrays instead of PostgreSQL tuple strings.
    +      // For example, a nested composite "(1,x)" becomes {"id":1,"name":"x"} and
    +      // an array of composites ["(1,a)","(2,b)"] becomes [{"id":1,"name":"a"},{"id":2,"name":"b"}].
    +      // Default is true.
    +      //
    +      "ResolveNestedCompositeTypes": true
    +    },
    +
    +    //
    +    // Options for different upload handlers and general upload settings
    +    //
    +    "UploadOptions": {
    +      "Enabled": false,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      //
    +      // Handler that will be used when upload handler or handlers are not specified.
    +      //
    +      "DefaultUploadHandler": "large_object",
    +      //
    +      // Gets or sets a value indicating whether the default upload metadata parameter should be used.
    +      //
    +      "UseDefaultUploadMetadataParameter": false,
    +      //
    +      // Name of the default upload metadata parameter. This parameter is used to pass metadata to the upload handler. The metadata is passed as a JSON object.
    +      //
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      //
    +      // Gets or sets a value indicating whether the default upload metadata context key should be used.
    +      //
    +      "UseDefaultUploadMetadataContextKey": false,
    +      //
    +      // Name of the default upload metadata context key. This key is used to pass the metadata to the upload handler. The metadata is passed as a JSON object.
    +      //
    +      "DefaultUploadMetadataContextKey": "request.upload_metadata",
    +      //
    +      // Upload handlers specific settings.
    +      //
    +      "UploadHandlers": {
    +        //
    +        // General settings for all upload handlers
    +        //
    +        "StopAfterFirstSuccess": false,
    +        // csv string containing mime type patters, set to null to ignore
    +        "IncludedMimeTypePatterns": null,
    +        // csv string containing mime type patters, set to null to ignore
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 8192, // Buffer size for the upload handlers file_system and large_object, in bytes. Default is 8192 bytes (8 KB).
    +        "TextTestBufferSize": 4096, // Buffer sample size for testing textual content, in bytes. Default is 4096 bytes (4 KB).
    +        "TextNonPrintableThreshold": 5, // Threshold for non-printable characters in the text buffer. Default is 5 non-printable characters.
    +        "AllowedImageTypes": "jpeg, png, gif, bmp, tiff, webp", // Comma-separated list of allowed image types when checking images.
    +        //
    +        // When set, authenticated user claims are included in the row metadata JSON parameter ($4) under this key name.
    +        // Set to null or empty string to disable adding claims to row metadata. Example: "claims" adds {"claims": {...}} to metadata.
    +        // Access in SQL: (_meta->'claims'->>'name_identifier')
    +        //
    +        "RowCommandUserClaimsKey": "claims",
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uses PostgreSQL Large Objects API
    +        //
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false,
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uses file system
    +        //
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false,
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uploads CSV files to a row command
    +        //
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        //
    +        // $1 - row index (1-based), $2 - parsed value text array, $3 - result of previous row command, $4 - JSON metadata for upload
    +        //
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)",
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uploads Excel files to a row command
    +        //
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null, // null to use the first available
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        //
    +        // $1 - row index (1-based), $2 - parsed value text array, $3 - result of previous row command, $4 - JSON metadata for upload
    +        //
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    },
    +
    +    //
    +    // Table format handlers for custom rendering of set/record results.
    +    // When an endpoint has @table_format = <name> custom parameter, the matching handler renders the response.
    +    //
    +    "TableFormatOptions": {
    +      //
    +      // Enable or disable table format handlers. When false, @table_format annotations are ignored.
    +      //
    +      "Enabled": false,
    +      //
    +      // Built-in HTML table handler. Renders results as an HTML table for easy copy-paste to Excel.
    +      // Activated by @table_format = html annotation on PostgreSQL functions.
    +      //
    +      "HtmlEnabled": true,
    +      //
    +      // The key name used to match @table_format = <key> annotation. Default is "html".
    +      //
    +      "HtmlKey": "html",
    +      //
    +      // Content written before the HTML table. Typically a CSS style block.
    +      // Set to null to omit.
    +      //
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      //
    +      // Content written after the closing HTML table tag.
    +      // Set to null to omit.
    +      //
    +      "HtmlFooter": null,
    +      //
    +      // Built-in Excel (.xlsx) handler using SpreadCheetah. Renders results as an Excel spreadsheet download.
    +      // Activated by @table_format = excel annotation on PostgreSQL functions.
    +      //
    +      "ExcelEnabled": true,
    +      //
    +      // The key name used to match @table_format = <key> annotation. Default is "excel".
    +      //
    +      "ExcelKey": "excel",
    +      //
    +      // Worksheet name. When null, uses the routine name.
    +      //
    +      "ExcelSheetName": null,
    +      //
    +      // Excel Format Code for DateTime cells. When null, uses SpreadCheetah default (yyyy-MM-dd HH:mm:ss).
    +      // Uses Excel Format Codes (not .NET format strings). Examples: "yyyy-mm-dd", "dd/mm/yyyy hh:mm", "m/d/yy h:mm".
    +      //
    +      "ExcelDateTimeFormat": null,
    +      //
    +      // Excel Format Code for numeric cells. When null, uses Excel default (General).
    +      // Uses Excel Format Codes (not .NET format strings). Examples: "#,##0.00", "0.00", "#,##0".
    +      //
    +      "ExcelNumericFormat": null
    +    },
    +
    +    //
    +    // Authentication options for NpgsqlRest endpoints
    +    //
    +    "AuthenticationOptions": {
    +      //
    +      // Authentication type used with the Login endpoints to set the authentication type for the new \`ClaimsIdentity\` created by the login. This value must be set to non-null when using login endpoints, otherwise, the following error will raise: \`SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.\` If the value is not set and the login endpoint is present, it will automatically get the database name from the connection string.
    +      //
    +      "DefaultAuthenticationType": null,
    +
    +      //
    +      // The default column name in the data reader which will be used to read the value to determine the success or failure of the login operation. If this column is not present, the success is when the endpoint returns any records. If this column is present, it must be either a boolean to indicate success or a numeric value to indicate the HTTP Status Code to return. If this column is present and retrieves a numeric value, that value is assigned to the HTTP Status Code and the login will authenticate only when this value is 200.
    +      //
    +      "StatusColumnName": "status",
    +      //
    +      // The default column name in the data reader which will be used to read the value of the authentication scheme of the login process. If this column is not present in the login response the default authentication scheme is used. Return new value to use a different authentication scheme with the login endpoint.
    +      //
    +      "SchemeColumnName": "scheme",
    +      //
    +      // The default column name in the data reader which will return a response body message for the login operation where writing to body is possible.
    +      //
    +      "BodyColumnName": "body",
    +      //
    +      // The default column name in the data reader which will set the response content type for the login operation where writing to body is possible.
    +      //
    +      "ResponseTypeColumnName": "application/json",
    +      //
    +      // The default column name in the data reader which will be used to read the value of the hash of the password. 
    +      // If this column is present, the value will be used to verify the password from the password parameter. 
    +      // Password parameter is the first parameter which name contains the value of PasswordParameterNameContains. 
    +      // If verification fails, the login will fail and the HTTP Status Code will be set to 404 Not Found.
    +      //
    +      "HashColumnName": "hash",
    +      //
    +      // The default name of the password parameter. 
    +      // The first parameter which name contains this value will be used as the password parameter. 
    +      // This is used to verify the password from the password parameter when login endpoint returns a hash of the password (see HashColumnName).
    +      //
    +      "PasswordParameterNameContains": "pass",
    +      //
    +      // Default claim type for user id.
    +      //
    +      "DefaultUserIdClaimType": "user_id",
    +      //
    +      // Default claim type for username.
    +      //
    +      "DefaultNameClaimType": "user_name",
    +      //
    +      // Default claim type for user roles.
    +      //
    +      "DefaultRoleClaimType": "user_roles",
    +      //
    +      // Default claim type for user display name.
    +      //
    +      "DefaultDisplayNameClaimType": "display_name",
    +      //
    +      // If true, return any response from auth endpoints (login and logout) if response hasn't been written by auth handler. For cookie auth, this will return full record to response as returned by the routine. For bearer token auth, this will be ignored because bearer token auth writes its own response (with tokens). This option will also be ignored if message column is present (see BodyColumnName option).
    +      //
    +      "SerializeAuthEndpointsResponse": false,
    +      //
    +      // Don't write real parameter values when logging parameters from auth endpoints and obfuscate instead. This prevents user credentials including password from ending up in application logs.
    +      //
    +      "ObfuscateAuthParameterLogValues": true,
    +      //
    +      // Command that is executed when the password verification fails. There are three positional and optional parameters: 
    +      // - $1: Authentication scheme used for the login (if parameter exists, type text).
    +      // - $2: User id used for the login (if parameter exists, type text).
    +      // - $3: Username used for the login (if parameter exists, type text).
    +      //
    +      "PasswordVerificationFailedCommand": null,
    +      //
    +      // Command that is executed when the password verification succeeds. There are three positional and optional parameters:
    +      // - $1: authentication scheme used for the login (if parameter exists, type text).
    +      // - $2: user id used for the login (if parameter exists, type text).
    +      // - $3: username used for the login (if parameter exists, type text).
    +      //
    +      "PasswordVerificationSucceededCommand": null,
    +      //
    +      // Enable setting authenticated user claims to context variables automatically. See ContextKeyClaimsMapping and ClaimsJsonContextKey options. You can set this individually for each request by using UserContext endpoint property or user_context comment annotation.
    +      // Note: For proxy endpoints, when user_context is enabled, these values are also forwarded as HTTP headers to the upstream proxy using the context key names.
    +      //
    +      "UseUserContext": false,
    +      //
    +      // Mapping of context keys to user claim names. Keys are the context variable names and values are the user claim names. When <see cref="UseUserContext"/> is enabled, the user claims from will be automatically mapped to the context variables.
    +      //
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      //
    +      // Context key that is used to set context variable for all available user claims. When this option is not null, and user is authenticated, the user claims will be serialized to JSON value and set to the context variable.
    +      //
    +      "ClaimsJsonContextKey": null,
    +      //
    +      // IP address context key that is used to set context variable for the IP address. When this option is not null, the IP address will be set to the context variable when <see cref="UseUserContext"/> is enabled and even when user is not authenticated.
    +      //
    +      "IpAddressContextKey": "request.ip_address",
    +      //
    +      // Enable mapping authenticated user claims to parameters by name automatically. See ParameterNameClaimsMapping and ClaimsJsonParameterName options. You can set this individually for each request by using UseUserParameters endpoint property or user_parameters comment annotation.
    +      // Note: For proxy endpoints, when user_params is enabled, these values are also forwarded as query string parameters to the upstream proxy.
    +      //
    +      "UseUserParameters": false,
    +      //
    +      // Mapping of parameter names to user claim names. Keys are the parameter names and values are the user claim names. When <see cref="UseUserParameters"/> is enabled, the user claims from will be automatically mapped to the parameters.
    +      //
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      //
    +      // Parameter name that is used to set value for all available user claims. When this option is not null, and user is authenticated, the user claims will be serialized to JSON value and set to the parameter with this name.
    +      //
    +      "ClaimsJsonParameterName": "_user_claims",
    +      //
    +      // IP address parameter name that is used to set parameter value for the IP address. When this option is not null, the IP address will be set to the parameter when <see cref="UseUserContext"/> is enabled and even when user is not authenticated.
    +      //
    +      "IpAddressParameterName": "_ip_address",
    +      //
    +      // Url path that will be used for the login endpoint. If NULL, the login endpoint will not be created.
    +      // Login endpoint expects a PostgreSQL command that will be executed to authenticate the user that follow this convention:
    +      //
    +      // - Must return at least one record when authentication is successful. If no records are returned endpoint will return 401 Unauthorized.
    +      // - If record is returned, the authentication is successful, if not set in StatusColumnName column otherwise.
    +      // - All records will be added to user principal claim collection where column name is claim type and column value is claim value, 
    +      //   except for four special columns defined in StatusColumnName, SchemeColumnName, BodyColumnName and HashColumnName options:
    +      //
    +      // - If "StatusColumnName" is present in the returned record, it must be either boolean (true for success, false for failure) or numeric (HTTP Status Code, 200 for success, anything else for failure). If not present, the success is when the endpoint returns any records.
    +      // - If "SchemeColumnName" is present in the returned record, it must be text value that defines the authentication scheme to use for the login.
    +      // - If "BodyColumnName" is present in the returned record, it must be text value that defines the message to return to the client as response body where possible. This only works for authentication that doesn't write response body (cookie authentication).
    +      // - If "HashColumnName" is present in the returned record, it must be text value that defines the hash of the password. Password parameter is the first parameter which name contains the value of PasswordParameterNameContains option. If verification fails, the login will fail and the HTTP Status Code will be set to 404 Not Found.
    +      //
    +      "LoginPath": null,
    +      //
    +      // Url path that will be used for the logout endpoint. If NULL, the logout endpoint will not be created.
    +      // Login endpoint expects a PostgreSQL command that performs the logout or the sign-out operation.
    +      //
    +      // If the routine doesn't return any data, the default authorization scheme is signed out. 
    +      // Any values returned will be interpreted as scheme names (converted to string) to sign out.
    +      //
    +      "LogoutPath": null,
    +      //
    +      // Settings for basic authentication support.
    +      // Basic authentication is a simple authentication scheme built into the HTTP protocol.
    +      // It expects request header \`Authorization: Basic base64(username:password)\` where username and password are the credentials for the user.
    +      //
    +      "BasicAuth": {
    +        //
    +        // Enable or disable the Basic Authentication support.
    +        //
    +        "Enabled": false,
    +        //
    +        // The default realm for the Basic Authentication. If not set, "NpgsqlRest" will be used.
    +        //
    +        "Realm": null,
    +        //
    +        // Default users dictionary for the Basic Authentication. Key is the username and value is the password or password hash depending on the UseDefaultPasswordHasher option.
    +        // Users can be set on individual endpoints using multiple annotations: basic_authentication [ username ] [ password ]
    +        //
    +        "Users": { },
    +        //
    +        // When using Basic Authentication, set this to Required to enforce SSL/TLS connection. 
    +        // Use Warning to issue a warning in the log when connection is not secure.
    +        // Use Ignore to allow Basic Authentication (debug level log will show a warning).
    +        //
    +        "SslRequirement": "Required", // Ignore, Warning, Required
    +        //
    +        // Use default password hasher for Basic Authentication to verify the password when Password is set on endpoint or options.
    +        // When this is true, Password set in configuration, endpoint or header (depending on PasswordHashLocation) is expected to be a hashed with default hasher.
    +        //
    +        "UseDefaultPasswordHasher": true,
    +        //
    +        // PostgreSQL command executed when the Basic Authentication is challenged. 
    +        // Same convention applies as with "LoginPath" command. See "NpgsqlRest.LoginPath" option for details.
    +        // Use this command to validate the username and password and/or return user claims.
    +        // 
    +        // Positional parameters:
    +        // - $1: Username from basic authentication header (if parameter exists, type text).
    +        // - $2: Password from basic authentication header (if parameter exists, type text).
    +        // - $3: Password is valid, true or false. If endpoint or configuration has a password defined, it will be validated. 
    +        //       This the result of that validation or NULL of no password is defined. 
    +        //       This allows for password to be validated before the command and use command for additional user claims. (if parameter exists, type boolean).
    +        // - $4: Basic authentication realm (if parameter exists, type text).
    +        // - $5: Endpoint path (if parameter exists, type text).
    +        //
    +        "ChallengeCommand": null
    +      }
    +    },
    +    
    +    //
    +    // Enable or disable the generation of HTTP files for NpgsqlRest endpoints.
    +    // See more on HTTP files at: 
    +    // https://marketplace.visualstudio.com/items?itemName=humao.rest-client or 
    +    // https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0
    +    //
    +    "HttpFileOptions": {
    +      "Enabled": false,
    +      //
    +      // Options for HTTP file generation:
    +      // - File: Generate HTTP files in the file system.
    +      // - Endpoint: Generate Endpoint(s) with HTTP file(s) content.
    +      // - Both: Generate HTTP files in the file system and Endpoint(s) with HTTP file(s) content.
    +      //
    +      "Option": "File",
    +      //
    +      // File name. If not set, the database name will be used if connection string is set. 
    +      // If neither ConnectionString nor Name is set, the file name will be "npgsqlrest".
    +      //
    +      "Name": null,
    +      //
    +      // The pattern to use when generating file names. {0} is database name, {1} is schema suffix with underline when FileMode is set to Schema.
    +      // Use this property to set a custom file name.
    +      // .http extension will be added automatically.
    +      //
    +      "NamePattern": "{0}_{1}",
    +      //
    +      // Adds comment header to above request based on PostgreSQL routine.
    +      // - None: skip.
    +      // - Simple: Add name, parameters and return values to comment header. This default.
    +      // - Full: Add the entire routine code as comment header.
    +      //
    +      "CommentHeader": "Simple",
    +      //
    +      // When CommentHeader is set to Simple or Full, set to true to include routine comments in comment header.
    +      //
    +      "CommentHeaderIncludeComments": true,
    +      //
    +      // - Database: to create one http file for entire database.
    +      // - Schema: to create one http file for each schema.
    +      //
    +      "FileMode": "Schema",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from the generated HTTP file's query string and request body. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +
    +    //
    +    // Enable or disable the generation of OpenAPI files for NpgsqlRest endpoints.
    +    //
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      //
    +      // File name for the generated OpenAPI file. Set to null to skip the file generation.
    +      //
    +      "FileName": "npgsqlrest_openapi.json",
    +      //
    +      // URL path for the OpenAPI endpoint. Set to null to skip the endpoint generation.
    +      //
    +      "UrlPath": "/openapi.json",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // The title of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // If not set, the database name from the ConnectionString will be used.
    +      //
    +      "DocumentTitle": null,
    +      //
    +      // The version of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // When null, default is "1.0.0".
    +      //
    +      "DocumentVersion": "1.0.0",
    +      //
    +      // Optional description of the API. This appears in the "info" section of the OpenAPI specification.
    +      //
    +      "DocumentDescription": null,
    +      //
    +      // Include current server information in the "servers" section of the OpenAPI document.
    +      //
    +      "AddCurrentServer": true,
    +      //
    +      // Additional server entries to add to the "servers" section of the OpenAPI document.
    +      // Each server entry must have "Url" property and optional "Description" property.
    +      //
    +      "Servers": [/*{"Url": "https://api.example.com", "Description": "Production server"}*/],
    +      //
    +      // Security schemes to include in the OpenAPI document.
    +      // If not specified, a default Bearer authentication scheme will be added for endpoints requiring authorization.
    +      // Supported types: "Http" (for Bearer/Basic auth) and "ApiKey" (for Cookie/Header/Query auth).
    +      // Examples:
    +      // - Bearer token: {"Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT"}
    +      // - Cookie auth: {"Name": "cookieAuth", "Type": "ApiKey", "In": ".AspNetCore.Cookies", "ApiKeyLocation": "Cookie"}
    +      // - Basic auth: {"Name": "basicAuth", "Type": "Http", "Scheme": "Basic"}
    +      //
    +      "SecuritySchemes": [
    +        /*{
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }*/
    +      ],
    +      //
    +      // Filters that control which endpoints appear in the OpenAPI document. The HTTP endpoints
    +      // themselves are unaffected — only their inclusion in the generated spec is. Combine with
    +      // the per-routine \`openapi hide\` / \`openapi tag <name>\` comment annotations for fine-grained
    +      // control. Use case: expose a partner-facing document that hides the internal anonymous
    +      // surface (health, login, probes) and the internal-only schemas.
    +      //
    +      // Schema allow-list. When non-empty, only endpoints whose routine schema appears here are
    +      // documented. Empty array (default) = document every schema.
    +      //
    +      "IncludeSchemas": [/* "partner" */],
    +      //
    +      // Schema deny-list. Any endpoint whose routine schema appears here is skipped. Applied
    +      // alongside IncludeSchemas — both must pass. Empty array (default) = no schema exclusions.
    +      //
    +      "ExcludeSchemas": [/* "internal" */],
    +      //
    +      // PostgreSQL-style SIMILAR TO pattern matched against the routine NAME. When set, only
    +      // routines whose name matches are documented. \`_\` matches one char, \`%\` matches any
    +      // sequence; the rest of SIMILAR TO syntax (\`|\`, \`*\`, \`+\`, \`?\`, \`(...)\`, \`[...]\`) is
    +      // supported. Anchored (must cover the whole name). Default null = no name filter.
    +      //
    +      "NameSimilarTo": null,
    +      //
    +      // PostgreSQL-style SIMILAR TO pattern matched against the routine NAME for EXCLUSION.
    +      // Matches are skipped. Same syntax as NameSimilarTo. Applied alongside it — both must
    +      // pass. Default null = no name exclusion.
    +      //
    +      "NameNotSimilarTo": null,
    +      //
    +      // When true, only authenticated endpoints (those that require authorization) are
    +      // documented. Anonymous endpoints — typically health, login, probes — are omitted. Useful
    +      // for partner-facing documents. Default false = document everything.
    +      //
    +      "RequiresAuthorizationOnly": false,
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from documented query parameters and request bodies. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +    //
    +    // Enable or disable the MCP (Model Context Protocol) server endpoint. Disabled by default. Tools are NEVER auto-exposed: only routines explicitly opted in with the \`mcp\` comment annotation become MCP tools. Implements MCP specification 2025-11-25.
    +    //
    +    "McpOptions": {
    +      "Enabled": false,
    +      //
    +      // URL path for the MCP endpoint (Streamable HTTP, single JSON-RPC endpoint).
    +      //
    +      "UrlPath": "/mcp",
    +      //
    +      // serverInfo.name reported in the MCP initialize handshake. When null, the database name from the connection string is used (falling back to "NpgsqlRest").
    +      //
    +      "ServerName": null,
    +      //
    +      // serverInfo.version reported in the MCP initialize handshake.
    +      //
    +      "ServerVersion": "1.0.0",
    +      //
    +      // Optional server-level instructions returned in the MCP initialize handshake (high-level guidance for the agent).
    +      //
    +      "Instructions": null,
    +      //
    +      // Optional text appended to every MCP tool description. Null = no-op. Use for short shared context the agent should always see (e.g. "Read-only Acme CRM."); for longer server-wide guidance prefer Instructions.
    +      //
    +      "ToolDescriptionSuffix": null,
    +      //
    +      // Name of an ASP.NET rate-limiter policy applied to the whole /mcp endpoint. Null = no limiting. A routine's own rate_limiter annotation does not carry to MCP (tools/call bypasses route middleware), so this is how MCP traffic is throttled. The named policy must be registered on the host (AddRateLimiter + UseRateLimiter); an unregistered name surfaces as the framework's error when a request hits the endpoint.
    +      //
    +      "RateLimiterPolicy": null,
    +      //
    +      // Allowed values of the HTTP Origin header (DNS-rebinding protection for the Streamable HTTP transport). A request whose Origin is present but matches neither this list nor the server's own origin is rejected with 403. Requests without an Origin header (e.g. server-to-server) are allowed. Empty = only same-origin browser requests pass.
    +      //
    +      "AllowedOrigins": [],
    +      //
    +      // OAuth 2.1 Resource Server settings. Token validation reuses the host's bearer authentication; these keys configure the transport gate and the Protected Resource Metadata document (RFC 9728). NpgsqlRest is not an Authorization Server — point AuthorizationServers at an external IdP.
    +      //
    +      "Authorization": {
    +        //
    +        // When true, every MCP request requires an authenticated principal. When false (default), anonymous is allowed and a tool's own \`authorize\` annotation still gates it per call.
    +        //
    +        "RequireAuthorization": false,
    +        //
    +        // Authorization Server issuer URL(s) advertised in the Protected Resource Metadata. When empty, no PRM document is served.
    +        //
    +        "AuthorizationServers": [],
    +        //
    +        // Optional scopes advertised in the Protected Resource Metadata (scopes_supported).
    +        //
    +        "ScopesSupported": [],
    +        //
    +        // Canonical resource URI tokens must target (RFC 8707 audience) and the PRM "resource" value. Null = derived from the request (scheme + host + UrlPath).
    +        //
    +        "Audience": null,
    +        //
    +        // Path the Protected Resource Metadata document is served at. Null = the RFC 9728 well-known path derived from UrlPath.
    +        //
    +        "ProtectedResourceMetadataPath": null,
    +        //
    +        // When true, tools/list hides tools the calling principal could not run (their routine's authorize/role check would deny it). When false (default), every opted-in tool is listed (discoverable) and authorization is enforced on tools/call.
    +        //
    +        "FilterToolsByRole": false
    +      }
    +    },
    +
    +    //
    +    // Enable or disable the generation of TypeScript/Javascript client source code files for NpgsqlRest endpoints.
    +    //
    +    "ClientCodeGen": {
    +      "Enabled": false,
    +      //
    +      // File path for the generated code. Set to null to skip the code generation. Use {0} to set schema name when BySchema is true
    +      //
    +      "FilePath": null,
    +      //
    +      //  Force file overwrite.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // Include current host information in the URL prefix.
    +      //
    +      "IncludeHost": true,
    +      //
    +      // Set the custom host prefix information.
    +      //
    +      "CustomHost": null,
    +      //
    +      // Adds comment header to above request based on PostgreSQL routine
    +      // Set None to skip.
    +      // Set Simple (default) to add name, parameters and return values to comment header.
    +      // Set Full to add the entire routine code as comment header.
    +      //
    +      "CommentHeader": "Simple",
    +      //
    +      // When CommentHeader is set to Simple or Full, set to true to include routine comments in comment header.
    +      //
    +      "CommentHeaderIncludeComments": true,
    +      //
    +      // Create files by PostgreSQL schema. File name will use formatted FilePath where {0} is the schema name in pascal case.
    +      //
    +      "BySchema": true,
    +      //
    +      // Set to true to include status code in response: {status: response.status, response: model}
    +      //
    +      "IncludeStatusCode": true,
    +      //
    +      // Create separate file with global types {name}Types.d.ts
    +      //
    +      "CreateSeparateTypeFile": true,
    +      //
    +      // Emit interfaces with the \`export\` keyword so they can be imported by other modules. When true and CreateSeparateTypeFile is true, the separate type file becomes an importable module ({name}Types.ts) instead of an ambient {name}Types.d.ts, and the client file imports the named types from it. No effect when SkipTypes is true.
    +      //
    +      "ExportTypes": false,
    +      //
    +      // Module name to import "baseUrl" constant, instead of defining it in a module.
    +      //
    +      "ImportBaseUrlFrom": null,
    +      //
    +      // Module name to import "parseQuery" function, instead of defining it in a module.
    +      //
    +      "ImportParseQueryFrom": null,
    +      //
    +      // Include optional parameter \`parseUrl: (url: string) => string = url=>url\` that will parse the constructed URL.
    +      //
    +      "IncludeParseUrlParam": false,
    +      //
    +      // Include optional parameter \`parseRequest: (request: RequestInit) => RequestInit = request=>request\` that will parse the constructed request.
    +      //
    +      "IncludeParseRequestParam": false,
    +      //
    +      // Header lines on each auto-generated source file. Default is ["// autogenerated at {0}", "", ""] where {0} is the current timestamp.
    +      //
    +      "HeaderLines": [
    +        "// autogenerated at {0}",
    +        ""
    +      ],
    +      //
    +      // Array of routine names to skip (without schema)
    +      //
    +      "SkipRoutineNames": [],
    +      //
    +      // Array of generated function names to skip (without schema)
    +      //
    +      "SkipFunctionNames": [],
    +      //
    +      // Array of url paths to skip
    +      //
    +      "SkipPaths": [],
    +      //
    +      // Array of schema names to skip
    +      //
    +      "SkipSchemas": [],
    +      //
    +      // Default TypeScript type for JSON types
    +      //
    +      "DefaultJsonType": "any",
    +      //
    +      // Use routine name instead of endpoint name when generating function names.
    +      //
    +      "UseRoutineNameInsteadOfEndpoint": false,
    +      //
    +      // Export URLs as constants in the generated code.
    +      //
    +      "ExportUrls": false,
    +      //
    +      // Skip generating types and produce pure JavaScript code. Setting this to true will also change the .ts extension to .js where applicable.
    +      //
    +      "SkipTypes": false,
    +      //
    +      // Keep TypeScript models unique, meaning models with the same fields and types will be merged into one model with the name of the last model. This significantly reduces the number of generated models.
    +      //
    +      "UniqueModels": false,
    +      //
    +      // Name of the XSRF Token Header (Anti-forgery Token). This is used in FORM POSTS to the server when Anti-forgery is enabled. Currently, only Upload requests use FORM POST.
    +      //
    +      "XsrfTokenHeaderName": null,
    +      //
    +      // Export event sources create functions for streaming events.
    +      //
    +      "ExportEventSources": true,
    +      //
    +      // List of custom imports to add to the generated code. It adds line to a file. Use full expression like \`import { MyType } from './my-type';\`
    +      //
    +      "CustomImports": [],
    +      //
    +      // Dictionary of custom headers to add to each request in generated code. Header key is automatically quoted if it doesn't contain quotes.
    +      //
    +      "CustomHeaders": {},
    +      //
    +      // When true, include PostgreSQL schema name in the generated type names to avoid name collisions. Set to false to simplify type names when no name collisions are expected.
    +      //
    +      "IncludeSchemaInNames": true,
    +      //
    +      // Expression to parse error response. Only used when IncludeStatusCode is true.
    +      //
    +      "ErrorExpression": "await response.json()",
    +      //
    +      // TypeScript type for error response. Only used when IncludeStatusCode is true.
    +      //
    +      "ErrorType": "{status: number; title: string; detail?: string | null} | undefined",
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from the generated request interface, query string, and body. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +
    +    //
    +    // HTTP client functionality for annotated composite types.
    +    // Allows PostgreSQL functions to make HTTP requests by using specially annotated types as parameters.
    +    //
    +    "HttpClientOptions": {
    +      //
    +      // Enable HTTP client functionality for annotated types.
    +      //
    +      "Enabled": false,
    +      //
    +      // Default name for the response status code field within annotated types.
    +      //
    +      "ResponseStatusCodeField": "status_code",
    +      //
    +      // Default name for the response body field within annotated types.
    +      //
    +      "ResponseBodyField": "body",
    +      //
    +      // Default name for the response headers field within annotated types.
    +      //
    +      "ResponseHeadersField": "headers",
    +      //
    +      // Default name for the response content type field within annotated types.
    +      //
    +      "ResponseContentTypeField": "content_type",
    +      //
    +      // Default name for the response success field within annotated types.
    +      //
    +      "ResponseSuccessField": "success",
    +      //
    +      // Default name for the response error message field within annotated types.
    +      //
    +      "ResponseErrorMessageField": "error_message",
    +      //
    +      // Global kill switch for HTTP type response caching. When false, the '@cache' directive on
    +      // individual types is ignored and every request fires a fresh outbound call. Caching is opt-in
    +      // per type via the '@cache <interval>' type-comment directive.
    +      //
    +      "CacheEnabled": true,
    +      //
    +      // Maximum number of distinct cached HTTP responses held in memory. Once full, new responses are
    +      // not cached (existing entries are still served and expire normally).
    +      //
    +      "MaxCacheEntries": 10000,
    +      //
    +      // Interval in seconds at which expired cached HTTP responses are pruned from memory.
    +      //
    +      "CachePruneIntervalSeconds": 60
    +    },
    +
    +    //
    +    // Reverse proxy functionality for NpgsqlRest endpoints.
    +    // When an endpoint is marked with 'proxy' annotation, incoming requests are forwarded to another URL.
    +    //
    +    "ProxyOptions": {
    +      //
    +      // Enable proxy functionality for annotated endpoints.
    +      //
    +      "Enabled": false,
    +      //
    +      // Base URL (host) for proxy requests (e.g., "https://api.example.com").
    +      // When set, proxy endpoints will forward requests to this host + the original path.
    +      //
    +      "Host": null,
    +      //
    +      // Default timeout for all proxy requests. Format: "HH:MM:SS" or PostgreSQL interval.
    +      //
    +      "DefaultTimeout": "00:00:30",
    +      //
    +      // When true, original request headers are forwarded to the proxy target.
    +      //
    +      "ForwardHeaders": true,
    +      //
    +      // Headers to exclude from forwarding to the proxy target.
    +      //
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      //
    +      // When true, forward response headers from proxy back to client.
    +      //
    +      "ForwardResponseHeaders": true,
    +      //
    +      // Response headers to exclude from forwarding back to client.
    +      //
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      //
    +      // Default name for the proxy response status code parameter.
    +      //
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      //
    +      // Default name for the proxy response body parameter.
    +      //
    +      "ResponseBodyParameter": "_proxy_body",
    +      //
    +      // Default name for the proxy response headers parameter.
    +      //
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      //
    +      // Default name for the proxy response content type parameter.
    +      //
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      //
    +      // Default name for the proxy response success parameter.
    +      //
    +      "ResponseSuccessParameter": "_proxy_success",
    +      //
    +      // Default name for the proxy response error message parameter.
    +      //
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      //
    +      // When true, for upload endpoints marked as proxy, the raw multipart/form-data content is forwarded directly to the upstream proxy instead of being processed locally. This allows the upstream service to handle file uploads. When false (default), upload endpoints with proxy annotation will process uploads locally and upload metadata will not be available to the proxy.
    +      //
    +      "ForwardUploadContent": false,
    +      //
    +      // Maximum length (characters) of a single automatic parameter value appended to the proxy upstream query string. Server-filled values (claims, IP, HTTP Custom Type fields, resolved-parameter expressions) longer than this are skipped with a warning instead of producing an unusable request line (HTTP 414/431). 0 or less disables the guard. To forward a large value, use a body-carrying proxy method (POST/PUT/PATCH). Default is 2048.
    +      //
    +      "MaxForwardedQueryParamLength": 2048
    +    },
    +
    +    //
    +    // SQL file source for generating REST API endpoints from .sql files.
    +    // Each SQL file must contain exactly one statement.
    +    //
    +    "SqlFileSource": {
    +      //
    +      // Enable or disable SQL file source endpoints. Default is false.
    +      //
    +      "Enabled": false,
    +      //
    +      // Glob pattern for SQL files, e.g. "sql/**/*.sql", "queries/*.sql".
    +      // Supports * (any chars), ** (recursive, any including /), ? (single char).
    +      // Empty string disables the feature.
    +      //
    +      "FilePattern": "",
    +      //
    +      // Glob (same semantics as FilePattern) for files to EXCLUDE from endpoint discovery.
    +      // Default "*.test.sql" so co-located SQL test files (run by the test runner, see "TestRunner")
    +      // are never exposed as endpoints. Empty string disables the exclusion.
    +      //
    +      "SkipPattern": "*.test.sql",
    +      //
    +      // How comment annotations are processed for SQL file endpoints.
    +      // Possible values: Ignore, ParseAll, OnlyAnnotated, OnlyWithHttpTag.
    +      // OnlyAnnotated (default; OnlyWithHttpTag is a back-compat alias) requires an explicit
    +      // HTTP annotation (e.g., "-- HTTP GET") for a SQL file to become an endpoint.
    +      //
    +      "CommentsMode": "OnlyAnnotated",
    +      //
    +      // Which comments in the SQL file to parse as annotations.
    +      // Possible values: All (default), Header (only comments before the first statement).
    +      //
    +      "CommentScope": "All",
    +      //
    +      // Behavior when a SQL file fails to parse or describe.
    +      // Possible values: Skip (default — log error, continue), Throw (halt startup).
    +      //
    +      "ErrorMode": "Exit",
    +      //
    +      // Prefix for result keys in multi-command JSON responses.
    +      // Default keys are "result1", "result2", etc.
    +      // Override per-result with the positional @result annotation in the SQL file.
    +      //
    +      "ResultPrefix": "result",
    +      //
    +      // When true, queries returning a single column produce a flat JSON array of values
    +      // (e.g., ["a", "b", "c"]) instead of an array of objects (e.g., [{"col": "a"}, {"col": "b"}]).
    +      // This matches the behavior of PostgreSQL functions returning setof single values.
    +      //
    +      "UnnamedSingleColumnSet": true,
    +      //
    +      // When true, composite type columns in return results are serialized as nested JSON objects.
    +      // For example, a column "data" of type "my_type(id int, name text)" becomes {"data": {"id": 1, "name": "test"}}
    +      // instead of the default flat structure {"id": 1, "name": "test"}.
    +      // Default is false for backward compatibility. Can also be enabled per-endpoint with the 'nested' annotation.
    +      //
    +      "NestedJsonForCompositeTypes": false,
    +      //
    +      // When true, non-query commands (BEGIN, COMMIT, SET, DO blocks, etc.) in multi-command SQL files
    +      // are still executed but excluded from the JSON response result keys.
    +      // Default is true.
    +      //
    +      "SkipNonQueryCommands": true,
    +      //
    +      // When true, multi-command SQL file endpoints include the full SQL text in command logs.
    +      // When false (default), only the file path and statement count are logged.
    +      // Single-command SQL files always log the SQL text regardless of this setting.
    +      // This only applies when LogCommands is true.
    +      //
    +      "LogCommandText": false
    +    }
    +  }
    +}

    Core Settings

    • Top-Level Settings - Application identity, URLs, and startup message
    • Config Section - Configuration file processing and environment variables
    • NpgsqlRest Options - Core API generation settings (URL prefixes, naming conventions, request handling)
    • Routine Options - PostgreSQL routine handling (language filtering, custom types)
    • Connection - Database connection strings and settings
    • Server - Kestrel web server and SSL/TLS configuration

    Security

    Features

    • OpenAPI - OpenAPI/Swagger documentation generation
    • HTTP Files - HTTP test file generation
    • Code Generation - Client code generation (TypeScript, etc.)
    • Uploads - File upload handling
    • HTTP Client - HTTP Types for external API calls from PostgreSQL functions

    Performance

    Infrastructure

    `,15)]))}const d=i(l,[["render",e]]);export{y as __pageData,d as default}; diff --git a/assets/config_latest.md.BHuOeOrq.lean.js b/assets/config_latest.md.BHuOeOrq.lean.js new file mode 100644 index 000000000..92d89b1f8 --- /dev/null +++ b/assets/config_latest.md.BHuOeOrq.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const y=JSON.parse('{"title":"Latest Default Configuration","titleTemplate":"NpgsqlRest","description":"Complete default configuration reference for NpgsqlRest. All settings with their default values for the latest version.","frontmatter":{"outline":[2,3],"title":"Latest Default Configuration","titleTemplate":"NpgsqlRest","description":"Complete default configuration reference for NpgsqlRest. All settings with their default values for the latest version.","head":[["meta",{"name":"keywords","content":"npgsqlrest default config, appsettings.json reference, default settings, configuration template, npgsqlrest defaults"}],["meta",{"property":"og:title","content":"NpgsqlRest Latest Default Configuration"}],["meta",{"property":"og:description","content":"Complete default configuration reference with all settings for the latest version."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/latest.md","filePath":"config/latest.md"}'),l={name:"config/latest.md"};function e(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t("",15)]))}const d=i(l,[["render",e]]);export{y as __pageData,d as default}; diff --git a/assets/config_logging.md.B2g4l4DC.js b/assets/config_logging.md.B2g4l4DC.js new file mode 100644 index 000000000..db7643183 --- /dev/null +++ b/assets/config_logging.md.B2g4l4DC.js @@ -0,0 +1,99 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Logging Configuration","titleTemplate":"NpgsqlRest","description":"Configure logging in NpgsqlRest with Serilog. Output to console, files, PostgreSQL tables, and OpenTelemetry. Control log levels and formatting.","frontmatter":{"outline":[2,3],"title":"Logging Configuration","titleTemplate":"NpgsqlRest","description":"Configure logging in NpgsqlRest with Serilog. Output to console, files, PostgreSQL tables, and OpenTelemetry. Control log levels and formatting.","head":[["meta",{"name":"keywords","content":"npgsqlrest logging, serilog postgresql, api logging configuration, opentelemetry logging, postgresql log table, rest api logging"}],["meta",{"property":"og:title","content":"NpgsqlRest Logging Configuration"}],["meta",{"property":"og:description","content":"Configure logging with Serilog for console, files, PostgreSQL, and OpenTelemetry outputs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/logging.md","filePath":"config/logging.md"}'),e={name:"config/logging.md"};function l(p,s,h,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`

    Logging

    Logging configuration using Serilog for console, file, PostgreSQL database, and OpenTelemetry outputs.

    For the task-oriented walkthrough — log channels, seeing executed SQL, PostgreSQL raise messages in logs, production recipes — see the Logging Guide.

    Overview

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Verbose",
    +    "ToFile": false,
    +    "FilePath": "logs/log.txt",
    +    "FileSizeLimitBytes": 30000000,
    +    "FileMinimumLevel": "Verbose",
    +    "RetainedFileCountLimit": 30,
    +    "RollOnFileSizeLimit": true,
    +    "ToPostgres": false,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Verbose",
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.version": "1.0",
    +      "service.environment": "{environment}"
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose",
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}

    Log Levels

    Available log levels (from most to least verbose):

    LevelDescription
    VerboseMost detailed logging, typically for debugging
    DebugDebugging information
    InformationGeneral operational information
    WarningWarnings that don't stop execution
    ErrorErrors that affect specific operations
    FatalCritical errors that stop the application
    OffFully silences a channel (aliases: None, Silent; MinimalLevels entries only, since 3.19.0)

    See Serilog Configuration Basics for more details.

    Minimal Levels

    Configure minimum log levels per source context:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    NpgsqlReststring"Information"The endpoint engine: endpoint creation and annotations at Debug; discovery queries, describe phase, and (with LogCommands) executed SQL at Verbose.
    NpgsqlRestClientstring"Information"The client host: configuration processing, auth setup, startup detail. When ApplicationName is set, log lines display that name as the source context instead of NpgsqlRestClient — but this configuration key keeps working unchanged (it is mapped to the actual channel name automatically).
    NpgsqlRestTeststring"Information"The SQL test runner (--test): discovery at Debug, every test statement and HTTP invocation at Verbose. Name configurable via TestRunner.LoggerName.
    Systemstring"Warning"Log level for the .NET System namespace.
    Microsoftstring"Warning"Log level for the Microsoft namespace (ASP.NET Core, etc.).

    Any entry accepts "Off" (since 3.19.0) to silence that channel completely — see the Logging Guide.

    Console Output

    json
    json
    {
    +  "Log": {
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToConsolebooltrueEnable logging to console output.
    ConsoleMinimumLevelstring"Verbose"Minimum log level for console output.

    File Output

    json
    json
    {
    +  "Log": {
    +    "ToFile": false,
    +    "FilePath": "logs/log.txt",
    +    "FileSizeLimitBytes": 30000000,
    +    "FileMinimumLevel": "Verbose",
    +    "RetainedFileCountLimit": 30,
    +    "RollOnFileSizeLimit": true
    +  }
    +}
    SettingTypeDefaultDescription
    ToFileboolfalseEnable logging to file system.
    FilePathstring"logs/log.txt"File path for log files.
    FileSizeLimitBytesint30000000Maximum size limit for log files in bytes before rolling (30 MB).
    FileMinimumLevelstring"Verbose"Minimum log level for file output.
    RetainedFileCountLimitint30Maximum number of log files to retain.
    RollOnFileSizeLimitbooltrueCreate a new log file when size limit is reached.

    PostgreSQL Output

    json
    json
    {
    +  "Log": {
    +    "ToPostgres": false,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToPostgresboolfalseEnable logging to PostgreSQL database.
    PostgresCommandstring"call log($1,$2,$3,$4,$5)"PostgreSQL command to execute for database logging.
    PostgresMinimumLevelstring"Verbose"Minimum log level for PostgreSQL output.

    PostgreSQL Command Parameters

    The PostgresCommand receives five parameters:

    ParameterTypeDescription
    $1textLog level (Verbose, Debug, Information, Warning, Error, Fatal)
    $2textLog message
    $3timestamptzTimestamp in UTC
    $4textException text (or null if no exception)
    $5textSource context (logger name)

    OpenTelemetry Output

    json
    json
    {
    +  "Log": {
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.version": "1.0",
    +      "service.environment": "{environment}"
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToOpenTelemetryboolfalseEnable OpenTelemetry protocol (OTLP) logging output.
    OTLPEndpointstring"http://localhost:4317"OTLP collector endpoint URL.
    OTLPProtocolstring"Grpc"Protocol for OTLP: "Grpc" or "HttpProtobuf".
    OTLPResourceAttributesobject(see below)Resource attributes sent with logs.
    OTLPHeadersobject{}Custom headers for OTLP requests.
    OTLPMinimumLevelstring"Verbose"Minimum log level for OTLP output.

    Resource Attributes

    Default resource attributes use placeholders:

    AttributeDefaultDescription
    service.name"{application}"Application name from ApplicationName setting.
    service.version"1.0"Application version.
    service.environment"{environment}"Environment name from EnvironmentName setting.

    Output Template

    json
    json
    {
    +  "Log": {
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}
    SettingTypeDefaultDescription
    OutputTemplatestring"[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"Serilog output template for formatting log messages.

    See Serilog Formatting Output for template syntax.

    Complete Example

    Production configuration with file and PostgreSQL logging:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Information",
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileSizeLimitBytes": 50000000,
    +    "FileMinimumLevel": "Information",
    +    "RetainedFileCountLimit": 14,
    +    "RollOnFileSizeLimit": true,
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning",
    +    "ToOpenTelemetry": false,
    +    "OutputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}

    Next Steps

    `,43)]))}const c=i(e,[["render",l]]);export{g as __pageData,c as default}; diff --git a/assets/config_logging.md.B2g4l4DC.lean.js b/assets/config_logging.md.B2g4l4DC.lean.js new file mode 100644 index 000000000..c720c40ba --- /dev/null +++ b/assets/config_logging.md.B2g4l4DC.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Logging Configuration","titleTemplate":"NpgsqlRest","description":"Configure logging in NpgsqlRest with Serilog. Output to console, files, PostgreSQL tables, and OpenTelemetry. Control log levels and formatting.","frontmatter":{"outline":[2,3],"title":"Logging Configuration","titleTemplate":"NpgsqlRest","description":"Configure logging in NpgsqlRest with Serilog. Output to console, files, PostgreSQL tables, and OpenTelemetry. Control log levels and formatting.","head":[["meta",{"name":"keywords","content":"npgsqlrest logging, serilog postgresql, api logging configuration, opentelemetry logging, postgresql log table, rest api logging"}],["meta",{"property":"og:title","content":"NpgsqlRest Logging Configuration"}],["meta",{"property":"og:description","content":"Configure logging with Serilog for console, files, PostgreSQL, and OpenTelemetry outputs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/logging.md","filePath":"config/logging.md"}'),e={name:"config/logging.md"};function l(p,s,h,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n("",43)]))}const c=i(e,[["render",l]]);export{g as __pageData,c as default}; diff --git a/assets/config_mcp.md.D08gYjRL.js b/assets/config_mcp.md.D08gYjRL.js new file mode 100644 index 000000000..9772ba947 --- /dev/null +++ b/assets/config_mcp.md.D08gYjRL.js @@ -0,0 +1,31 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"MCP Configuration","titleTemplate":"NpgsqlRest","description":"Enable the Model Context Protocol (MCP) server endpoint for NpgsqlRest. Expose opted-in PostgreSQL routines as MCP tools for AI agents over a single Streamable HTTP JSON-RPC endpoint.","frontmatter":{"outline":[2,3],"title":"MCP Configuration","titleTemplate":"NpgsqlRest","description":"Enable the Model Context Protocol (MCP) server endpoint for NpgsqlRest. Expose opted-in PostgreSQL routines as MCP tools for AI agents over a single Streamable HTTP JSON-RPC endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest mcp, model context protocol server, postgresql mcp tools, ai agent postgresql, mcp json-rpc endpoint, streamable http mcp"}],["meta",{"property":"og:title","content":"NpgsqlRest MCP Configuration"}],["meta",{"property":"og:description","content":"Enable the MCP server endpoint and expose PostgreSQL routines as MCP tools in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/mcp.md","filePath":"config/mcp.md"}'),n={name:"config/mcp.md"};function o(l,e,r,h,d,p){return a(),i("div",null,e[0]||(e[0]=[t(`

    MCP Options

    New in 3.17.0

    The NpgsqlRest.Mcp plugin and the McpOptions config section were added in version 3.17.0. It implements the Model Context Protocol specification 2025-11-25.

    Configuration for the MCP (Model Context Protocol) server — a single Streamable-HTTP JSON-RPC endpoint that lets an AI agent discover (tools/list) and execute (tools/call) PostgreSQL routines that have been opted in with the @mcp annotation.

    The MCP server is disabled by default, and no routine is ever exposed automatically — only routines that carry @mcp become tools.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "McpOptions": {
    +      "Enabled": false,
    +      "UrlPath": "/mcp",
    +      "ServerName": null,
    +      "ServerVersion": "1.0.0",
    +      "Instructions": null,
    +      "ToolDescriptionSuffix": null,
    +      "RateLimiterPolicy": null,
    +      "AllowedOrigins": [],
    +      "Authorization": {
    +        "RequireAuthorization": false,
    +        "AuthorizationServers": [],
    +        "ScopesSupported": [],
    +        "Audience": null,
    +        "ProtectedResourceMetadataPath": null,
    +        "FilterToolsByRole": false
    +      }
    +    }
    +  }
    +}

    Options

    Enabled

    • Type: boolean
    • Default: false

    Enables or disables the MCP server endpoint. When false, no MCP endpoint is registered and @mcp annotations are ignored.

    UrlPath

    • Type: string
    • Default: /mcp

    URL path for the MCP endpoint. The endpoint is a single Streamable-HTTP JSON-RPC endpoint that accepts POST.

    ServerName

    • Type: string (nullable)
    • Default: null

    The serverInfo.name value reported in the MCP initialize handshake. When null, the database name from the connection string is used (mirroring the OpenAPI document title), falling back to "NpgsqlRest" if it cannot be resolved.

    ServerVersion

    • Type: string
    • Default: "1.0.0"

    The serverInfo.version value reported in the MCP initialize handshake. A null/blank value also falls back to "1.0.0".

    Instructions

    • Type: string (nullable)
    • Default: null

    Optional server-level instructions returned in the MCP initialize handshake — high-level guidance the agent can use when deciding how to call the available tools. When null, no instructions are sent.

    ToolDescriptionSuffix

    • Type: string (nullable)
    • Default: null

    Optional text appended (as a suffix) to every tool's description in tools/list. When null, nothing is added.

    RateLimiterPolicy

    • Type: string (nullable)
    • Default: null

    Name of an ASP.NET rate-limiter policy applied to the whole /mcp endpoint. When null (default), MCP traffic is not rate-limited.

    A routine's own @rate_limiter annotation does not carry to MCP — tools/call invokes the routine directly, bypassing the per-route middleware (NpgsqlRest logs a startup warning when an @mcp routine also has @rate_limiter). RateLimiterPolicy is how you throttle the MCP endpoint instead, covering every JSON-RPC method on it.

    The named policy must be registered on the hostAddRateLimiter(o => o.AddPolicy("name", …)) (or a built-in limiter such as AddFixedWindowLimiter) plus UseRateLimiter(). An unregistered name surfaces as the framework's error when a request reaches the endpoint. (When set, NpgsqlRest serves /mcp as a mapped endpoint so the policy can attach; on hosts without endpoint routing it logs a warning and the policy is not applied.)

    jsonc
    jsonc
    "RateLimiterPolicy": "mcp"

    AllowedOrigins

    • Type: string[]
    • Default: []

    Allowed values of the HTTP Origin header — DNS-rebinding protection required by the Streamable HTTP transport. A request whose Origin is present but matches neither this list nor the server's own origin is rejected with 403. Requests without an Origin header (e.g. server-to-server) are allowed. Empty (default) = only same-origin browser requests pass.

    Use it for short, shared context that should ride along with every tool the agent inspects — e.g. "Read-only Acme CRM." or "Amounts in USD.". Unlike Instructions (returned once at initialize, and which some clients don't surface prominently), a description suffix is attached to each tool, so the model sees it whenever it considers that tool.

    Keep it short: the suffix is repeated across every tool, so long text inflates the tools/list payload. For longer server-wide guidance, prefer Instructions.

    jsonc
    jsonc
    "ToolDescriptionSuffix": "Read-only Acme CRM."

    A tool whose own description is "Get the current weather for a city." is then reported as:

    code
    Get the current weather for a city. Read-only Acme CRM.

    How it works

    Once enabled, the endpoint is a single Streamable-HTTP JSON-RPC endpoint (POST only; GET405, no SSE). Per the transport spec it validates the Origin header (a present, untrusted origin → 403; see AllowedOrigins) and the MCP-Protocol-Version header (a present header other than 2025-11-25400; absent is allowed). It implements the MCP lifecycle and tools methods:

    • initialize — advertises the tools capability and returns serverInfo (name/version above) and the protocol version 2025-11-25. notifications/initialized is acknowledged with 202. ping returns an empty result.

    • tools/list — returns the catalog of opted-in routines. Each tool has a name (the routine name, or an @mcp_name override), a description (from @mcp <text> or the comment prose), a JSON-Schema inputSchema derived from the routine's parameters, and an outputSchema derived from the routine's return columns (matching the structuredContent shape below; leaf values allow null, and array/json/composite columns use a permissive schema so results always conform).

    • tools/call — executes the routine through the same invocation pipeline as the HTTP endpoint, forwarding the authenticated identity so @authorize role checks apply. The result carries structuredContent (always a JSON object) plus a text content block holding its serialized form:

      json
      json
      {
      +  "content": [{ "type": "text", "text": "{\\"total\\":1234,\\"status\\":\\"paid\\"}" }],
      +  "structuredContent": { "total": 1234, "status": "paid" },
      +  "isError": false
      +}

      structuredContent is mapped from the routine's return shape (per MCP 2025-11-25, it is always an object):

      Routine returnsstructuredContent
      a single value (int, text, …){ "value": 42 }
      a single record/composite (or a set with single)the object: { "total": 1234, … }
      a set of values{ "items": [1, 2, 3] }
      a set of rows{ "items": [ { … }, { … } ] }

      (Numbers/booleans/JSON are embedded as JSON; other scalar types as a string. Raw-mode and void routines emit the text block only.)

      Business failures are returned as isError: true in the result; structural failures (unknown method, unknown tool, malformed request) are returned as JSON-RPC errors (-32601, -32602, -32700).

    Rate limiting

    A routine's @rate_limiter annotation applies to its HTTP route, not to MCP calls — tools/call executes the routine directly, bypassing the route's rate-limiter. To throttle MCP traffic, set RateLimiterPolicy to a host-registered policy applied to the whole /mcp endpoint, or rate-limit the /mcp path at a reverse proxy / API gateway.

    Authentication — OAuth 2.1 Resource Server

    The /mcp endpoint acts as an OAuth 2.1 Resource Server (bring-your-own Authorization Server). Token validation reuses the host's bearer authentication — NpgsqlRest is not an Authorization Server; point AuthorizationServers at an external IdP (Keycloak, Auth0, Entra, …) or at NpgsqlRest's own JWT login acting separately.

    No built-in Authorization Server

    NpgsqlRest does not ship an Authorization Server (token / consent / authorization-code endpoints) — that is potential future work. The Resource Server role above covers every deployment that already has an IdP (or uses NpgsqlRest's own JWT). The only scenario it can't cover is fully interactive browser-login with no external IdP at all; if you don't have an IdP and don't need interactive login, use NpgsqlRest's own JWT (static-token) auth instead.

    Enabling MCP does not enable authentication

    Authentication is configured separately from MCP (via the host's Auth section — e.g. JwtAuth). The MCP Authorization settings only add the transport gate, PRM advertising, and audience binding on top of whatever principal the host's auth produced. If you set RequireAuthorization: true without configuring authentication, every /mcp request returns 401 (nothing is ever authenticated) — NpgsqlRest logs a startup warning in this case. For audience validation, configure the host JWT bearer's ValidAudience to the same value as Audience below.

    Tool execution forwards the caller's authenticated principal, so per-routine @authorize role requirements are enforced on tools/call exactly as on HTTP endpoints. A tool that needs authentication, called anonymously, returns HTTP 401 (with the PRM challenge); an authenticated caller lacking the required role gets HTTP 403 with WWW-Authenticate: Bearer error="insufficient_scope". No authorization logic is duplicated — this reuses core's check.

    Authorization options

    These live under McpOptions:Authorization.

    RequireAuthorization

    • Type: boolean
    • Default: false

    When true, every MCP request requires an authenticated principal (the host's bearer middleware must have populated the identity). An unauthenticated request is rejected with HTTP 401 and a WWW-Authenticate: Bearer resource_metadata="…" challenge (RFC 9728 §5.1) so the client can discover the Authorization Server. When false (default), anonymous requests are allowed and a tool's own @authorize annotation still gates it per call.

    AuthorizationServers

    • Type: string[]
    • Default: []

    Authorization Server issuer URL(s) advertised in the Protected Resource Metadata. When empty, no PRM document is served.

    ScopesSupported

    • Type: string[]
    • Default: []

    Optional scopes advertised in the Protected Resource Metadata (scopes_supported).

    Audience

    • Type: string (nullable)
    • Default: null

    The canonical resource URI tokens must target (RFC 8707 audience) and the resource value in the PRM document. When null, it is derived from the request (scheme + host + UrlPath).

    When set, it is also enforced: an authenticated token must carry this value in its aud claim, or the request is rejected with 401 (tokens issued for a different resource are refused). Token signature/expiry validation remains the host bearer middleware's responsibility; configure it with this same audience.

    ProtectedResourceMetadataPath

    • Type: string (nullable)
    • Default: null

    Path the Protected Resource Metadata document is served at. When null, the RFC 9728 well-known path is used: /.well-known/oauth-protected-resource + UrlPath (e.g. /.well-known/oauth-protected-resource/mcp).

    FilterToolsByRole

    • Type: boolean
    • Default: false

    When true, tools/list hides tools the calling principal could not run — i.e. tools whose routine has an @authorize requirement the caller doesn't satisfy. When false (default), every opted-in tool is listed, keeping them discoverable (so an agent can still attempt a call and be prompted to authenticate). Authorization is enforced on tools/call either way; this only affects what the listing reveals.

    Protected Resource Metadata (RFC 9728)

    When an Authorization Server is configured, NpgsqlRest serves a discovery document at the well-known path. The PRM document itself is always anonymous (it is discovery), even when RequireAuthorization is on:

    json
    json
    {
    +  "resource": "https://your-host/mcp",
    +  "authorization_servers": ["https://as.example.com"],
    +  "scopes_supported": ["mcp.read", "mcp.write"],
    +  "bearer_methods_supported": ["header"]
    +}

    Note

    By default tools/list lists every opted-in tool — keeping them discoverable so an agent can attempt a call and be prompted to authenticate — and authorization is enforced on tools/call. Set FilterToolsByRole to instead hide tools the caller can't run.

    `,75)]))}const k=s(n,[["render",o]]);export{u as __pageData,k as default}; diff --git a/assets/config_mcp.md.D08gYjRL.lean.js b/assets/config_mcp.md.D08gYjRL.lean.js new file mode 100644 index 000000000..618c3af84 --- /dev/null +++ b/assets/config_mcp.md.D08gYjRL.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"MCP Configuration","titleTemplate":"NpgsqlRest","description":"Enable the Model Context Protocol (MCP) server endpoint for NpgsqlRest. Expose opted-in PostgreSQL routines as MCP tools for AI agents over a single Streamable HTTP JSON-RPC endpoint.","frontmatter":{"outline":[2,3],"title":"MCP Configuration","titleTemplate":"NpgsqlRest","description":"Enable the Model Context Protocol (MCP) server endpoint for NpgsqlRest. Expose opted-in PostgreSQL routines as MCP tools for AI agents over a single Streamable HTTP JSON-RPC endpoint.","head":[["meta",{"name":"keywords","content":"npgsqlrest mcp, model context protocol server, postgresql mcp tools, ai agent postgresql, mcp json-rpc endpoint, streamable http mcp"}],["meta",{"property":"og:title","content":"NpgsqlRest MCP Configuration"}],["meta",{"property":"og:description","content":"Enable the MCP server endpoint and expose PostgreSQL routines as MCP tools in NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/mcp.md","filePath":"config/mcp.md"}'),n={name:"config/mcp.md"};function o(l,e,r,h,d,p){return a(),i("div",null,e[0]||(e[0]=[t("",75)]))}const k=s(n,[["render",o]]);export{u as __pageData,k as default}; diff --git a/assets/config_npgsqlrest.md.CnfMgb40.js b/assets/config_npgsqlrest.md.CnfMgb40.js new file mode 100644 index 000000000..0a83cf37e --- /dev/null +++ b/assets/config_npgsqlrest.md.CnfMgb40.js @@ -0,0 +1,119 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"NpgsqlRest Options","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest middleware options. Schema filtering, endpoint generation, URL patterns, request handling, and response formatting settings.","frontmatter":{"outline":[2,3],"title":"NpgsqlRest Options","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest middleware options. Schema filtering, endpoint generation, URL patterns, request handling, and response formatting settings.","head":[["meta",{"name":"keywords","content":"npgsqlrest options, postgresql middleware config, api endpoint generation, schema filtering, url pattern configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Options"}],["meta",{"property":"og:description","content":"Configure NpgsqlRest middleware for endpoint generation, schema filtering, and request handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/npgsqlrest.md","filePath":"config/npgsqlrest.md"}'),n={name:"config/npgsqlrest.md"};function l(h,s,p,d,r,o){return t(),a("div",null,s[0]||(s[0]=[e(`

    NpgsqlRest Options

    NpgsqlRest HTTP middleware general configuration for endpoint generation and request handling.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false,
    +    "CommandTimeout": null,
    +    "SchemaSimilarTo": null,
    +    "SchemaNotSimilarTo": null,
    +    "IncludeSchemas": null,
    +    "ExcludeSchemas": null,
    +    "NameSimilarTo": null,
    +    "NameNotSimilarTo": null,
    +    "IncludeNames": null,
    +    "ExcludeNames": null,
    +    "CommentsMode": "OnlyAnnotated",
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "RequiresAuthorization": true,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +    "DefaultHttpMethod": null,
    +    "DefaultRequestParamType": null,
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersContextKey": "request.headers",
    +    "RequestHeadersParameterName": "_headers",
    +    "InstanceIdRequestHeaderName": null,
    +    "CustomRequestHeaders": {},
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +    "QueryStringNullHandling": "Ignore",
    +    "TextResponseNullHandling": "EmptyString",
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": {},
    +    "RoutineOptions": { ... },
    +    "AuthenticationOptions": { ... },
    +    "SqlFileSource": { ... },
    +    "UploadOptions": { ... },
    +    "ClientCodeGen": { ... },
    +    "HttpFileOptions": { ... },
    +    "OpenApiOptions": { ... }
    +  }
    +}

    See related configuration pages:

    Connection Settings

    SettingTypeDefaultDescription
    ConnectionNamestringnullConnection name from ConnectionStrings section. Uses first available if null.
    UseMultipleConnectionsboolfalseAllow individual routines to use different connections from ConnectionStrings.
    CommandTimeoutstringnullCommand timeout using interval format (e.g., "30s", "1m"). Uses default 30 seconds if null. Can be overridden per endpoint with command_timeout annotation.

    Schema and Name Filtering

    Filter which PostgreSQL routines are exposed as endpoints.

    SettingTypeDefaultDescription
    SchemaSimilarTostringnullInclude schemas matching this SQL SIMILAR TO pattern.
    SchemaNotSimilarTostringnullExclude schemas matching this SQL SIMILAR TO pattern.
    IncludeSchemasarraynullList of schema names to include.
    ExcludeSchemasarraynullList of schema names to exclude.
    NameSimilarTostringnullInclude routine names matching this SQL SIMILAR TO pattern.
    NameNotSimilarTostringnullExclude routine names matching this SQL SIMILAR TO pattern.
    IncludeNamesarraynullList of routine names to include.
    ExcludeNamesarraynullList of routine names to exclude.

    Filtering Examples

    Include only specific schemas:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "IncludeSchemas": ["api", "public"]
    +  }
    +}

    Exclude internal schemas:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ExcludeSchemas": ["pg_catalog", "information_schema", "internal"]
    +  }
    +}

    Filter by name pattern:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "NameSimilarTo": "api_%",
    +    "NameNotSimilarTo": "%_internal"
    +  }
    +}

    Comments Mode

    SettingTypeDefaultDescription
    CommentsModestring"OnlyAnnotated"How comment annotations affect endpoint creation.

    Available modes:

    ModeDescription
    IgnoreCreate all endpoints, ignore comment annotations.
    ParseAllCreate all endpoints, parse comment annotations to modify them.
    OnlyWithHttpTagOnly create endpoints for routines with an HTTP annotation in comments. Kept as an identical-behavior alias of OnlyAnnotated for existing configs.
    OnlyAnnotatedOnly create endpoints for routines with an HTTP annotation or a plugin annotation that requests an endpoint (e.g. @mcp — so an MCP-only routine can exist with no HTTP route). Client default since 3.17.0.

    With the default OnlyAnnotated mode, routines without an HTTP (or endpoint-requesting plugin) annotation in their comment will not be exposed as endpoints. This provides explicit control over which database routines are accessible via the API.

    Client vs. library default

    The standalone client (npgsqlrest executable) defaults to OnlyAnnotated since 3.17.0. The C# library (NpgsqlRestOptions.CommentsMode) defaults to OnlyWithHttpTag; the two behave identically unless a plugin (such as MCP) requests endpoints.

    URL and Naming

    SettingTypeDefaultDescription
    UrlPathPrefixstring"/api"URL prefix for all generated endpoints.
    KebabCaseUrlsbooltrueConvert URL paths to kebab-case from PostgreSQL names.
    CamelCaseNamesbooltrueConvert parameter names to camelCase from PostgreSQL names.

    URL Examples

    With default settings, get_user_profile becomes /api/get-user-profile.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/v1/api",
    +    "KebabCaseUrls": true
    +  }
    +}

    Authorization

    SettingTypeDefaultDescription
    RequiresAuthorizationbooltrueForce all endpoints to require authorization. Can be overridden per endpoint via comment annotations.

    Logging

    SettingTypeDefaultDescription
    LogConnectionNoticeEventsbooltrueLog PostgreSQL connection events (triggered by RAISE statements).
    LogConnectionNoticeEventsModestring"FirstStackFrameAndMessage"How to format notice event logs.
    LogCommandsboolfalseLog every executed command and query at debug level.
    LogCommandParametersboolfalseInclude parameter values in command logs. Only applies when LogCommands is true.
    DebugLogEndpointCreateEventsbooltrueEmit a debug log for each endpoint created at startup (URL and method).
    DebugLogCommentAnnotationEventsbooltrueEmit a debug log for each comment annotation that is successfully processed.

    Notice Event Modes

    ModeDescription
    MessageOnlyLog only the message.
    FirstStackFrameAndMessageLog first stack frame and message (default).
    FullStackAndMessageLog full stack trace and message.

    HTTP Method and Parameters

    SettingTypeDefaultDescription
    DefaultHttpMethodstringnullForce HTTP method for all endpoints (GET, POST, PUT, DELETE, etc.).
    DefaultRequestParamTypestringnullForce parameter location for all endpoints (QueryString or BodyJson).

    Default Behavior

    When DefaultHttpMethod is null:

    • GET is used when routine is not volatile, or name starts with get_, contains _get_, or ends with _get
    • POST is used otherwise

    When DefaultRequestParamType is null:

    • QueryString for GET and DELETE endpoints
    • BodyJson for all other methods

    Request Headers

    SettingTypeDefaultDescription
    RequestHeadersModestring"Parameter"How to send request headers to PostgreSQL routines.
    RequestHeadersContextKeystring"request.headers"Context variable name when mode is Context.
    RequestHeadersParameterNamestring"_headers"Parameter name when mode is Parameter.
    CustomRequestHeadersobject{}Custom headers added to requests before sending to PostgreSQL.
    InstanceIdRequestHeaderNamestringnullHeader name for NpgsqlRest instance ID. Set to null to disable.
    ExecutionIdHeaderNamestring"X-NpgsqlRest-ID"Execution request header name. Used for request tracking and SSE correlation and in ConnectionSettings.UseJsonApplicationName.

    Request Headers Modes

    ModeDescription
    IgnoreDon't send request headers to routines.
    ContextSet context variable context.headers with JSON string via set_config().
    ParameterSend headers to parameter named by RequestHeadersParameterName. Parameter must be JSON/text type with default value.

    Connection Pooler Compatibility

    New in 3.13.0

    WrapInTransaction and BeforeRoutineCommands options for connection pooler compatibility and pre-routine SQL commands.

    SettingTypeDefaultDescription
    WrapInTransactionboolfalseWhen true, every request is wrapped in an explicit BEGIN ... COMMIT, and all set_config calls switch from session-scoped (is_local=false) to transaction-local (is_local=true).
    BeforeRoutineCommandsarray[]SQL commands executed after any context is set but before the main routine call. Run in the same batch as the context set_config calls (no extra round-trip).

    WrapInTransaction

    This is required for connection poolers in transaction mode — including PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, and Supabase Pooler. Previously, set_config(name, value, false) would set the GUC at the session level on the underlying PostgreSQL backend. With a transaction-mode pooler, the same backend is reused for unrelated client requests, which means session-scoped GUCs from one request could be visible to the next. With WrapInTransaction = true, GUCs are scoped to the request transaction and discarded on COMMIT.

    The default remains false to preserve existing behavior; it is safe to leave off when using Npgsql's native pool only (which issues DISCARD ALL on connection return).

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true
    +  }
    +}

    BeforeRoutineCommands

    Each entry can be either a raw SQL string (no parameters) or an object with Sql and Parameters. Each parameter has a Source (Claim, RequestHeader, or IpAddress) and an optional Name (claim type or header name). Parameter values are bound at request time from HttpContext — claim and header values are passed as parameterized SQL inputs (no string interpolation, no injection risk).

    The most useful pattern is multi-tenant search_path setup driven by a JWT/cookie claim:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true,
    +    "BeforeRoutineCommands": [
    +      "select set_config('app.request_time', clock_timestamp()::text, true)",
    +      {
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +      }
    +    ]
    +  }
    +}

    Per-request execution order with this config:

    1. BEGIN
    2. Each BeforeRoutineCommand is added as a NpgsqlBatchCommand (with parameters bound from claims/headers/IP) and dispatched in a single batch.
    3. The main routine call.
    4. COMMIT.

    Steps 1–3 share a single network round-trip.

    NULL Handling

    SettingTypeDefaultDescription
    QueryStringNullHandlingstring"Ignore"How empty or "null" query string values are interpreted.
    TextResponseNullHandlingstring"EmptyString"How NULL database results are returned in plain text responses.

    QueryStringNullHandling Values

    ValueDescription
    IgnoreNo special handling - empty strings stay as empty strings, "null" literal stays as "null" string (default).
    EmptyStringEmpty query string values are interpreted as NULL values.
    NullLiteralLiteral string "null" (case insensitive) is interpreted as NULL value.

    TextResponseNullHandling Values

    ValueDescription
    EmptyStringReturns an empty string response with status code 200 OK (default).
    NullLiteralReturns a string literal "NULL" with status code 200 OK.
    NoContentReturns status code 204 NO CONTENT.

    These settings can be overridden per-endpoint using comment annotations:

    sql
    sql
    comment on function my_func(text) is '
    +@query_string_null_handling empty_string
    +@text_response_null_handling no_content
    +';

    JSON Timestamp Handling

    SettingTypeDefaultDescription
    JsonTimestampsAreUtcbooltrueHow JSON-encoded timestamps are interpreted when parsed into timestamp, timestamptz, time, and timetz parameters.

    When true (default, recommended):

    • Z-suffixed and offset-bearing ISO strings (e.g. "2026-05-20T06:00:00Z", "2026-05-20T08:00:00+02:00") are converted to UTC.
    • Naive ISO strings with no offset and no Z (e.g. "2026-05-20T06:00:00") are assumed UTC rather than interpreted as the host's local time.

    The result is host-TZ-independent: the same JSON payload produces the same stored value regardless of the TZ environment of the process serving the request.

    When false, the parsers fall back to the pre-3.16.0 behavior:

    • Z / offset-bearing strings are converted to the host's local time zone and tagged Kind=Local.
    • Naive strings are parsed as Kind=Unspecified.
    • The timestamptz / timetz parsers then re-apply SpecifyKind(Utc) on top of the local-shifted value — silently shifting the stored value by the host's UTC offset on non-UTC hosts.

    Opt-out only — not recommended for new deployments

    JsonTimestampsAreUtc: false exists as a compatibility escape hatch for callers that genuinely depend on the legacy "naive timestamps are host-local" behavior and cannot be updated to send Z-suffixed values. It reproduces the bug class fixed in 3.16.0. Leave at the default unless you have a specific legacy reason to flip it.

    Example:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "JsonTimestampsAreUtc": true
    +  }
    +}

    Server-Sent Events

    Configure Server-Sent Events (SSE) for real-time streaming of PostgreSQL RAISE statements to connected clients.

    SettingTypeDefaultDescription
    DefaultServerSentEventsEventNoticeLevelstring"INFO"Default PostgreSQL notice level for SSE events. Valid values: INFO, NOTICE, WARNING.
    ServerSentEventsResponseHeadersobject{}Custom headers added to SSE responses.

    Notice Level Behavior

    The DefaultServerSentEventsEventNoticeLevel setting determines which PostgreSQL RAISE statements generate SSE events by default when the level is not specified in the annotation.

    Important

    SSE events are sent only for the exact level configured, not for "this level and above". For example, if set to NOTICE, only RAISE NOTICE statements generate SSE events—RAISE INFO and RAISE WARNING are ignored.

    This default can be overridden per-endpoint using the @sse annotation.

    Example Configuration

    json
    json
    {
    +  "NpgsqlRest": {
    +    "DefaultServerSentEventsEventNoticeLevel": "NOTICE",
    +    "ServerSentEventsResponseHeaders": {
    +      "X-Accel-Buffering": "no"
    +    }
    +  }
    +}

    The X-Accel-Buffering: no header is commonly needed when running behind nginx to disable response buffering for SSE streams.

    Unbound RAISE warning

    SettingTypeDefaultDescription
    WarnUnboundServerSentEventsNoticesbooltrueWhen at least one SSE endpoint exists, log a one-time warning per endpoint whose RAISE matches the SSE notice level but is not annotated as an SSE publisher (a likely missing sse_publish annotation). Apps with no SSE endpoints pay zero overhead and see no warnings.

    Environment Variables in Annotation Values

    SettingTypeDefaultDescription
    AvailableEnvVarsarray or object[]Allowlist of environment variable names available to {name} placeholder substitution in comment annotation values (response headers, custom parameters, HTTP custom type calls), alongside the routine's parameters. Array form lists names (a missing variable becomes the empty string); object form maps name → default. Resolved once at startup; matched case-insensitively; a routine parameter of the same name takes precedence.

    Security

    A value substituted into a response header is sent to the client. Reserve secrets (API keys, tokens) for outbound HTTP custom type calls, and use response headers only for non-secret values (e.g. a server/environment name). Only allowlisted names are ever read from the environment.

    Complete Example

    Production configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": true,
    +    "CommandTimeout": "30 seconds",
    +    "IncludeSchemas": ["api"],
    +    "ExcludeSchemas": ["internal"],
    +    "CommentsMode": "OnlyAnnotated",
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "RequiresAuthorization": true,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersParameterName": "_headers",
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID"
    +  }
    +}

    Development configuration with verbose logging:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CommentsMode": "ParseAll",
    +    "RequiresAuthorization": false,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FullStackAndMessage",
    +    "LogCommands": true,
    +    "LogCommandParameters": true
    +  }
    +}

    Next Steps

    `,104)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_npgsqlrest.md.CnfMgb40.lean.js b/assets/config_npgsqlrest.md.CnfMgb40.lean.js new file mode 100644 index 000000000..5acf4332a --- /dev/null +++ b/assets/config_npgsqlrest.md.CnfMgb40.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"NpgsqlRest Options","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest middleware options. Schema filtering, endpoint generation, URL patterns, request handling, and response formatting settings.","frontmatter":{"outline":[2,3],"title":"NpgsqlRest Options","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest middleware options. Schema filtering, endpoint generation, URL patterns, request handling, and response formatting settings.","head":[["meta",{"name":"keywords","content":"npgsqlrest options, postgresql middleware config, api endpoint generation, schema filtering, url pattern configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Options"}],["meta",{"property":"og:description","content":"Configure NpgsqlRest middleware for endpoint generation, schema filtering, and request handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/npgsqlrest.md","filePath":"config/npgsqlrest.md"}'),n={name:"config/npgsqlrest.md"};function l(h,s,p,d,r,o){return t(),a("div",null,s[0]||(s[0]=[e("",104)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_openapi.md.BCg7CKgl.js b/assets/config_openapi.md.BCg7CKgl.js new file mode 100644 index 000000000..fcf3323c9 --- /dev/null +++ b/assets/config_openapi.md.BCg7CKgl.js @@ -0,0 +1,160 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"OpenAPI Configuration","titleTemplate":"NpgsqlRest","description":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API. Configure document title, version, servers, and security schemes for API documentation.","frontmatter":{"outline":[2,3],"title":"OpenAPI Configuration","titleTemplate":"NpgsqlRest","description":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API. Configure document title, version, servers, and security schemes for API documentation.","head":[["meta",{"name":"keywords","content":"npgsqlrest openapi, postgresql swagger, api documentation, openapi specification, rest api docs, swagger postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest OpenAPI Configuration"}],["meta",{"property":"og:description","content":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API with customizable documentation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/openapi.md","filePath":"config/openapi.md"}'),e={name:"config/openapi.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    OpenAPI Options

    Configuration for generating OpenAPI specification files and endpoints for NpgsqlRest APIs.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      "FileName": "npgsqlrest_openapi.json",
    +      "UrlPath": "/openapi.json",
    +      "FileOverwrite": true,
    +      "DocumentTitle": null,
    +      "DocumentVersion": "1.0.0",
    +      "DocumentDescription": null,
    +      "AddCurrentServer": true,
    +      "Servers": [],
    +      "SecuritySchemes": [],
    +      "IncludeSchemas": [],
    +      "ExcludeSchemas": [],
    +      "NameSimilarTo": null,
    +      "NameNotSimilarTo": null,
    +      "RequiresAuthorizationOnly": false,
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable OpenAPI generation.
    FileNamestring"npgsqlrest_openapi.json"File name for generated OpenAPI file. null to skip file generation.
    UrlPathstring"/openapi.json"URL path for OpenAPI endpoint. null to skip endpoint generation.
    FileOverwritebooltrueOverwrite existing files.
    DocumentTitlestringnullAPI title in the info section. Uses database name if null.
    DocumentVersionstring"1.0.0"API version in the info section.
    DocumentDescriptionstringnullAPI description in the info section.
    AddCurrentServerbooltrueInclude current server in the servers section.
    Serversarray[]Additional server entries for the servers section.
    SecuritySchemesarray[]Security schemes for authentication documentation.
    IncludeSchemasstring[][]Schema allow-list. When non-empty, only endpoints whose routine schema appears here are documented.
    ExcludeSchemasstring[][]Schema deny-list. Applied alongside IncludeSchemas.
    NameSimilarTostringnullPostgreSQL SIMILAR TO pattern matched against routine names (anchored). When set, only matching routines are documented.
    NameNotSimilarTostringnullPostgreSQL SIMILAR TO pattern for exclusion. Applied alongside NameSimilarTo.
    RequiresAuthorizationOnlyboolfalseWhen true, document only endpoints that require authorization — health, login, and other anonymous probes drop out.
    OmitAutomaticParametersboolfalseWhen true, omit server-filled parameters from documented query parameters and request bodies. See Omitting automatic parameters.

    Document Info

    Configure the OpenAPI document metadata:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "DocumentTitle": "My API",
    +      "DocumentVersion": "2.0.0",
    +      "DocumentDescription": "REST API for my application"
    +    }
    +  }
    +}

    Servers

    Add server entries to the OpenAPI specification:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "AddCurrentServer": true,
    +      "Servers": [
    +        {
    +          "Url": "https://api.example.com",
    +          "Description": "Production server"
    +        },
    +        {
    +          "Url": "https://staging-api.example.com",
    +          "Description": "Staging server"
    +        }
    +      ]
    +    }
    +  }
    +}

    Security Schemes

    Define authentication schemes for the OpenAPI document. Supported types:

    • Http - For Bearer and Basic authentication
    • ApiKey - For Cookie, Header, or Query parameter authentication

    Bearer Token Authentication

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "bearerAuth",
    +      "Type": "Http",
    +      "Scheme": "Bearer",
    +      "BearerFormat": "JWT",
    +      "Description": "JWT Bearer token authentication"
    +    }
    +  ]
    +}

    Basic Authentication

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "basicAuth",
    +      "Type": "Http",
    +      "Scheme": "Basic",
    +      "Description": "HTTP Basic authentication"
    +    }
    +  ]
    +}
    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "cookieAuth",
    +      "Type": "ApiKey",
    +      "In": ".AspNetCore.Cookies",
    +      "ApiKeyLocation": "Cookie",
    +      "Description": "Cookie-based authentication"
    +    }
    +  ]
    +}

    API Key in Header

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "apiKeyAuth",
    +      "Type": "ApiKey",
    +      "In": "X-API-Key",
    +      "ApiKeyLocation": "Header",
    +      "Description": "API key in header"
    +    }
    +  ]
    +}

    Security Scheme Settings

    SettingTypeDescription
    NamestringUnique scheme identifier.
    TypestringScheme type: "Http" or "ApiKey".
    SchemestringHTTP auth scheme ("Bearer", "Basic"). For Type: "Http" only.
    BearerFormatstringBearer token format (e.g., "JWT"). Optional.
    InstringCookie/header/query name. For Type: "ApiKey" only.
    ApiKeyLocationstringLocation: "Cookie", "Header", or "Query". For Type: "ApiKey" only.
    DescriptionstringDescription of the security scheme.

    Complete Example

    Production configuration with multiple security schemes:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "FileName": "openapi.json",
    +      "UrlPath": "/openapi.json",
    +      "FileOverwrite": true,
    +      "DocumentTitle": "My REST API",
    +      "DocumentVersion": "1.0.0",
    +      "DocumentDescription": "REST API generated from PostgreSQL functions",
    +      "AddCurrentServer": true,
    +      "Servers": [
    +        {
    +          "Url": "https://api.example.com",
    +          "Description": "Production server"
    +        }
    +      ],
    +      "SecuritySchemes": [
    +        {
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }
    +      ]
    +    }
    +  }
    +}

    Filters (New in 3.15.0)

    Five config keys (and a per-routine @openapi comment annotation) control which endpoints appear in the generated document. The HTTP endpoints themselves are unaffected — only their inclusion in the spec is. Defaults are "no filter", so existing configs see no change.

    Schema and name filters

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "IncludeSchemas": ["partner"],
    +      "ExcludeSchemas": ["internal"],
    +      "NameSimilarTo": "partner_%",
    +      "NameNotSimilarTo": "%_admin",
    +      "RequiresAuthorizationOnly": true
    +    }
    +  }
    +}
    • NameSimilarTo / NameNotSimilarTo use PostgreSQL SIMILAR TO syntax — _ matches one char, % matches any sequence; |, *, +, ?, (...), [...] work via regex translation. Anchored (the pattern must cover the entire routine name).
    • All filters are conjunctive — an endpoint must pass every one to be documented.

    Filter order

    Filters are applied in this order; the first rejection short-circuits the rest:

    1. @openapi hide annotation (per-routine wins over everything)
    2. RequiresAuthorizationOnly vs. the endpoint's authorization requirement
    3. IncludeSchemas membership
    4. ExcludeSchemas membership
    5. NameSimilarTo match
    6. NameNotSimilarTo match (negative)

    Partner-facing document example

    A full "host serves one API, partner-only OpenAPI document" config:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "FileName": "openapi-partner.json",
    +      "UrlPath": "/openapi/partner.json",
    +      "DocumentTitle": "Acme Partner API",
    +      "DocumentDescription": "JWT-authenticated REST surface for partner integrations.",
    +
    +      "IncludeSchemas": ["partner"],
    +      "RequiresAuthorizationOnly": true,
    +      "NameNotSimilarTo": "%_admin",
    +
    +      "SecuritySchemes": [
    +        { "Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT" }
    +      ],
    +      "Servers": [
    +        { "Url": "https://api.acme.com", "Description": "Production" }
    +      ]
    +    }
    +  }
    +}

    The internal cookie-authenticated surface stays reachable on the same host — only the document is partner-scoped.

    One document per process

    Only one OpenAPI document is generated per host. To serve both a partner and an internal spec, run two NpgsqlRest hosts with different filter configs, or filter to a single audience.

    Omitting Automatic Parameters

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the Code Generation and HTTP File generators). Default is false, so the generated document is unchanged unless you opt in.

    Some parameters are filled by the server and a client value would simply be ignored — documenting them as settable is misleading. When OmitAutomaticParameters is true, such a parameter is left out of the generated document (query parameters and request body) when it is automatic and optional. "Automatic" covers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "OmitAutomaticParameters": true
    +    }
    +  }
    +}

    When every parameter of an endpoint is omitted, the operation is documented with no parameters and no requestBody.

    Next Steps

    `,51)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_openapi.md.BCg7CKgl.lean.js b/assets/config_openapi.md.BCg7CKgl.lean.js new file mode 100644 index 000000000..12a85b440 --- /dev/null +++ b/assets/config_openapi.md.BCg7CKgl.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"OpenAPI Configuration","titleTemplate":"NpgsqlRest","description":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API. Configure document title, version, servers, and security schemes for API documentation.","frontmatter":{"outline":[2,3],"title":"OpenAPI Configuration","titleTemplate":"NpgsqlRest","description":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API. Configure document title, version, servers, and security schemes for API documentation.","head":[["meta",{"name":"keywords","content":"npgsqlrest openapi, postgresql swagger, api documentation, openapi specification, rest api docs, swagger postgresql"}],["meta",{"property":"og:title","content":"NpgsqlRest OpenAPI Configuration"}],["meta",{"property":"og:description","content":"Generate OpenAPI/Swagger specifications for your PostgreSQL REST API with customizable documentation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/openapi.md","filePath":"config/openapi.md"}'),e={name:"config/openapi.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",51)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_passkey-auth.md.DHQzx-GX.js b/assets/config_passkey-auth.md.DHQzx-GX.js new file mode 100644 index 000000000..5cdfcdba1 --- /dev/null +++ b/assets/config_passkey-auth.md.DHQzx-GX.js @@ -0,0 +1,79 @@ +import{_ as t,c as i,o as a,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Passkey Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure WebAuthn passkey authentication in NpgsqlRest. Passwordless login using device biometrics or PINs with full SQL-based control over the authentication flow.","frontmatter":{"outline":[2,3],"title":"Passkey Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure WebAuthn passkey authentication in NpgsqlRest. Passwordless login using device biometrics or PINs with full SQL-based control over the authentication flow.","head":[["meta",{"name":"keywords","content":"npgsqlrest passkey, webauthn postgresql, passwordless authentication, fido2 postgresql, biometric login api, passkey configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Passkey Authentication Configuration"}],["meta",{"property":"og:description","content":"Configure WebAuthn passkey authentication for passwordless login with device biometrics."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/passkey-auth.md","filePath":"config/passkey-auth.md"}'),n={name:"config/passkey-auth.md"};function l(d,s,r,o,h,p){return a(),i("div",null,s[0]||(s[0]=[e(`

    Passkey Authentication

    NpgsqlRest supports WebAuthn/FIDO2 passkey authentication, providing phishing-resistant, passwordless login using device-native biometrics or PINs.

    New in 3.5.0

    Passkey authentication was added in version 3.5.0.

    Overview

    Passkeys use public-key cryptography tied to user devices. Unlike passwords, passkeys:

    • Cannot be phished (tied to origin)
    • Cannot be reused across sites
    • Cannot be stolen in database breaches (only public keys stored)
    • Require biometric or PIN verification

    Minimal configuration to enable passkey authentication:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "Enabled": true
    +    }
    +  }
    +}

    How It Works

    NpgsqlRest handles the WebAuthn protocol (CBOR parsing, signature verification) while your PostgreSQL functions control the business logic:

    mermaid
    flowchart LR
    +    A["Browser
    +    (Client Script)"] <--> B["NpgsqlRest
    +    (Endpoints + CBOR)"] <--> C["PostgreSQL
    +    (SQL Functions)"]
    • Browser: A client script that calls the browser's WebAuthn API and communicates with NpgsqlRest endpoints. See the passkey.ts example for a complete TypeScript implementation you can use as a starting point.
    • NpgsqlRest: Provides the HTTP endpoints and handles CBOR parsing/verification.
    • PostgreSQL: Your SQL functions control the entire authentication flow.

    Your database stores only public keys - no biometric data ever touches your server.

    Three Authentication Flows

    NpgsqlRest supports three distinct passkey flows. Each endpoint internally executes a configured SQL command (typically a PostgreSQL function) that you define.

    1. Registration (New User with Passkey)

    For new users signing up with a passkey. Creates both the user account and passkey.

    EndpointExecutes SQL Command
    POST /api/passkey/register/optionsChallengeRegistrationCommand
    POST /api/passkey/registerCompleteRegistrationCommand

    Registration is Disabled by Default

    Standalone registration (EnableRegister: false by default) allows anyone to create an account with just a passkey. In production, you'll typically want additional verification (email confirmation, CAPTCHA, etc.) before creating accounts.

    The recommended approach is:

    1. Create user accounts through your existing registration flow
    2. Let users add passkeys to verified accounts using the Add Passkey flow

    2. Add Passkey (Existing User)

    For authenticated users who want to add a passkey to their account. These endpoints require authentication.

    Endpoint (requires auth)Executes SQL Command
    POST /api/passkey/add/optionsChallengeAddExistingUserCommand
    POST /api/passkey/addCompleteAddExistingUserCommand

    3. Login

    For authenticating with an existing passkey.

    EndpointExecutes SQL Command
    POST /api/passkey/login/optionsChallengeAuthenticationCommand
    POST /api/passkey/loginCompleteAuthenticateCommand

    Settings Reference

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable passkey authentication.
    EnableRegisterboolfalseEnable standalone registration (new users can sign up with passkey only).
    RateLimiterPolicystringnullName of a configured rate limiter policy. Recommended for brute-force protection.
    ConnectionNamestringnullNamed connection for multi-database setups. Uses default if null.
    CommandRetryStrategystring"default"Retry strategy for transient database errors. Set to null to disable.

    Relying Party Settings

    The Relying Party (RP) identifies your application to the authenticator.

    SettingTypeDefaultDescription
    RelyingPartyIdstringnullDomain name (e.g., "example.com"). Auto-detected if null. Note: IP addresses not permitted - use "localhost" for development.
    RelyingPartyNamestringnullHuman-readable name shown during registration. Uses ApplicationName if null.
    RelyingPartyOriginsstring[][]Allowed origins (e.g., ["https://example.com"]). ⚠️ When empty, origin validation accepts ANY origin — a startup warning is logged (3.17.0+). Always set explicitly in production.

    Endpoint Paths

    All paths are POST endpoints. Set to null to disable an endpoint.

    SettingDefaultDescription
    AddPasskeyOptionsPath"/api/passkey/add/options"Get options for adding passkey to existing user (requires auth).
    AddPasskeyPath"/api/passkey/add"Complete adding passkey (requires auth).
    RegistrationOptionsPath"/api/passkey/register/options"Get options for new user registration.
    RegistrationPath"/api/passkey/register"Complete new user registration.
    LoginOptionsPath"/api/passkey/login/options"Get login challenge.
    LoginPath"/api/passkey/login"Complete authentication.

    WebAuthn Settings

    SettingTypeDefaultDescription
    ChallengeTimeoutMinutesint5How long challenges remain valid.
    ValidateSignCountbooltrueValidate signature counter to detect cloned authenticators.
    UserVerificationRequirementstring"required"See below.
    ResidentKeyRequirementstring"required"See below.
    AttestationConveyancestring"none"See below.

    UserVerificationRequirement

    Controls whether biometric/PIN verification is required:

    ValueBehaviorUse Case
    "required"User MUST verify with biometric or PINBanking, healthcare, sensitive data
    "preferred"Request verification if availableMost consumer apps
    "discouraged"Don't request verification (proves possession only)Low-security scenarios

    ResidentKeyRequirement

    Controls discoverable credentials (true passwordless):

    ValueBehaviorUse Case
    "required"Credential stored on device; browser shows account pickerTrue passwordless (no username field)
    "preferred"Request discoverable if supportedGradual migration to passwordless
    "discouraged"Server must provide credential IDUsername-first flows

    AttestationConveyance

    Controls whether to verify authenticator hardware:

    ValueBehaviorUse Case
    "none"Accept any authenticatorMost apps (recommended)
    "indirect"Allow anonymized attestationRarely useful
    "direct"Request full attestation chainVerify specific hardware models
    "enterprise"Enterprise-managed attestationCorporate device policies

    SQL Commands Reference

    NpgsqlRest calls your SQL functions at specific points in each flow.

    ChallengeAddExistingUserCommand

    When executed: User clicks "Add Passkey" (already authenticated)

    Parameters:

    • $1 = claims (json): User claims from authenticated session
    • $2 = body (json): Request body (e.g., { "deviceName": "My Phone" })

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code. Return 200 to proceed.
    messagetextError message when status ≠ 200.
    challengetextBase64-encoded random bytes (32 bytes recommended).
    challenge_idbigint/uuid/textServer-side identifier for verification.
    user_handletextBase64-encoded random bytes for WebAuthn user.id.
    user_nametextUsername shown in authenticator UI.
    user_display_nametextDisplay name shown in authenticator UI.
    exclude_credentialstextJSON array of existing credential IDs.
    user_contextjsonPassed through to completion command.

    ChallengeRegistrationCommand

    When executed: New user starts passkey-only registration

    Parameters:

    • $1 = body (json): Request body with user info

    Expected return columns: Same as ChallengeAddExistingUserCommand

    ChallengeAuthenticationCommand

    When executed: User initiates passkey login

    Parameters:

    • $1 = user_name (text): Username if provided, NULL for discoverable credentials
    • $2 = body (json): Request body

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed).
    messagetextError message when status ≠ 200.
    challengetextBase64-encoded random challenge.
    challenge_idbigint/uuid/textServer-side identifier.
    allow_credentialstextJSON array of credential IDs for this user.

    VerifyChallengeCommand

    When executed: After browser returns credential, before cryptographic verification

    Used by: ALL flows

    Parameters:

    • $1 = challenge_id: The challenge_id from options response
    • $2 = operation (text): Either "registration" or "authentication"

    Expected return: Single column challenge (bytea) - original challenge bytes, or NULL if not found/expired

    AuthenticateDataCommand

    When executed: During login, to retrieve stored credential data

    Parameters:

    • $1 = credential_id (bytea): The credential ID from browser

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed).
    messagetextError message when status ≠ 200.
    public_keybyteaStored public key for signature verification.
    public_key_algorithmintCOSE algorithm ID (-7 for ES256, -257 for RS256).
    sign_countbigintCurrent signature counter.
    user_contextjsonPassed to CompleteAuthenticateCommand.

    CompleteAddExistingUserCommand / CompleteRegistrationCommand

    When executed: After successful attestation verification

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - Unique credential identifier
    $2byteauser_handle - WebAuthn user.id
    $3byteapublic_key - Public key in COSE format
    $4intalgorithm - COSE algorithm (-7 = ES256, -257 = RS256)
    $5text[]transports - Transport hints (e.g., ["internal", "hybrid"])
    $6booleanbackup_eligible - Whether credential can be synced
    $7jsonuser_context - From challenge command
    $8jsonanalytics_data - Optional client analytics

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 = success).
    messagetextError message when status ≠ 200.

    CompleteAuthenticateCommand

    When executed: After successful signature verification during login

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - The credential used
    $2bigintnew_sign_count - Updated signature counter
    $3jsonuser_context - From AuthenticateDataCommand
    $4jsonanalytics_data - Optional client analytics

    Expected return columns: Same as login endpoint - the scheme column determines authentication type, other columns become claims.

    Column Name Configuration

    If your SQL functions use different column names:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "StatusColumnName": "status",
    +      "MessageColumnName": "message",
    +      "ChallengeColumnName": "challenge",
    +      "ChallengeIdColumnName": "challenge_id",
    +      "UserNameColumnName": "user_name",
    +      "UserDisplayNameColumnName": "user_display_name",
    +      "UserHandleColumnName": "user_handle",
    +      "ExcludeCredentialsColumnName": "exclude_credentials",
    +      "AllowCredentialsColumnName": "allow_credentials",
    +      "PublicKeyColumnName": "public_key",
    +      "PublicKeyAlgorithmColumnName": "public_key_algorithm",
    +      "SignCountColumnName": "sign_count"
    +    }
    +  }
    +}

    Analytics Data

    Collect client-side analytics by passing analyticsData in completion requests. NpgsqlRest automatically adds the client's IP address:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "ClientAnalyticsIpKey": "ip"
    +    }
    +  }
    +}

    Set to null or empty string to disable IP collection.

    Complete Example

    Minimal Configuration

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "PasskeyAuth": {
    +      "Enabled": true
    +    }
    +  }
    +}

    Full Configuration

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "PasskeyAuth": {
    +      "Enabled": true,
    +      "EnableRegister": true,
    +      "RateLimiterPolicy": "passkey-limit",
    +
    +      "RelyingPartyId": null,
    +      "RelyingPartyName": "My Application",
    +      "RelyingPartyOrigins": [],
    +
    +      "UserVerificationRequirement": "required",
    +      "ResidentKeyRequirement": "required",
    +      "AttestationConveyance": "none",
    +
    +      "ChallengeTimeoutMinutes": 5,
    +      "ValidateSignCount": true,
    +
    +      "ChallengeAddExistingUserCommand": "select * from passkey_challenge_add_existing($1,$2)",
    +      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
    +      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
    +      "VerifyChallengeCommand": "select * from passkey_verify_challenge($1,$2)",
    +      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
    +      "CompleteAddExistingUserCommand": "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
    +      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
    +      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)"
    +    }
    +  },
    +  "RateLimiting": {
    +    "Policies": {
    +      "passkey-limit": {
    +        "Type": "SlidingWindow",
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60
    +      }
    +    }
    +  }
    +}

    Next Steps

    `,103)]))}const u=t(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/config_passkey-auth.md.DHQzx-GX.lean.js b/assets/config_passkey-auth.md.DHQzx-GX.lean.js new file mode 100644 index 000000000..3c0cba109 --- /dev/null +++ b/assets/config_passkey-auth.md.DHQzx-GX.lean.js @@ -0,0 +1 @@ +import{_ as t,c as i,o as a,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Passkey Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure WebAuthn passkey authentication in NpgsqlRest. Passwordless login using device biometrics or PINs with full SQL-based control over the authentication flow.","frontmatter":{"outline":[2,3],"title":"Passkey Authentication Configuration","titleTemplate":"NpgsqlRest","description":"Configure WebAuthn passkey authentication in NpgsqlRest. Passwordless login using device biometrics or PINs with full SQL-based control over the authentication flow.","head":[["meta",{"name":"keywords","content":"npgsqlrest passkey, webauthn postgresql, passwordless authentication, fido2 postgresql, biometric login api, passkey configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Passkey Authentication Configuration"}],["meta",{"property":"og:description","content":"Configure WebAuthn passkey authentication for passwordless login with device biometrics."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/passkey-auth.md","filePath":"config/passkey-auth.md"}'),n={name:"config/passkey-auth.md"};function l(d,s,r,o,h,p){return a(),i("div",null,s[0]||(s[0]=[e("",103)]))}const u=t(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/config_proxy.md.DOTosU06.js b/assets/config_proxy.md.DOTosU06.js new file mode 100644 index 000000000..ad7621968 --- /dev/null +++ b/assets/config_proxy.md.DOTosU06.js @@ -0,0 +1,113 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Proxy Options","titleTemplate":"NpgsqlRest","description":"Configure reverse proxy for NpgsqlRest. Forward requests to upstream services, cache responses in PostgreSQL, transform API responses with SQL functions.","frontmatter":{"outline":[2,3],"title":"Proxy Options","titleTemplate":"NpgsqlRest","description":"Configure reverse proxy for NpgsqlRest. Forward requests to upstream services, cache responses in PostgreSQL, transform API responses with SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, postgresql reverse proxy, api gateway postgresql, upstream service proxy, transform mode proxy, passthrough proxy"}],["meta",{"property":"og:title","content":"NpgsqlRest Proxy Options"}],["meta",{"property":"og:description","content":"Configure reverse proxy to forward requests, cache responses, and transform API data with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/proxy.md","filePath":"config/proxy.md"}'),t={name:"config/proxy.md"};function l(r,s,p,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n(`

    Proxy Options

    Reverse proxy configuration for NpgsqlRest endpoints. When an endpoint is marked as a proxy, incoming HTTP requests are forwarded to an upstream service, and the response can either be returned directly to the client (passthrough mode) or processed by the PostgreSQL function (transform mode).

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": false,
    +      "Host": null,
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      "ResponseBodyParameter": "_proxy_body",
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      "ResponseSuccessParameter": "_proxy_success",
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      "ForwardUploadContent": false,
    +      "MaxForwardedQueryParamLength": 2048
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable proxy functionality for endpoints with proxy annotations.
    HoststringnullDefault upstream host URL. Used when the annotation has no URL. Ignored when the annotation specifies its own URL (absolute or relative). See URL Resolution.
    DefaultTimeoutstring"00:00:30"Default timeout for proxy requests. Format: "HH:MM:SS" or interval format (e.g., "30s").
    ForwardHeadersbooltrueForward request headers to upstream service.
    ExcludeHeadersarray["Host", "Content-Length", "Transfer-Encoding"]Request headers to exclude from forwarding.
    ForwardResponseHeadersbooltrueForward response headers from upstream to client.
    ExcludeResponseHeadersarray["Transfer-Encoding", "Content-Length"]Response headers to exclude from forwarding.
    ForwardUploadContentboolfalseForward raw multipart/form-data to upstream instead of processing locally.
    MaxForwardedQueryParamLengthint2048Maximum length (characters) of a single automatic parameter value appended to the proxy upstream query string. Server-filled values longer than this are skipped with a warning instead of producing an unusable request line (HTTP 414/431). 0 or less disables the guard. See Query-string length guard.

    Response Parameter Names

    These settings configure which parameter names receive proxy response data:

    SettingTypeDefaultDescription
    ResponseStatusCodeParameterstring"_proxy_status_code"Parameter name for HTTP status code from upstream.
    ResponseBodyParameterstring"_proxy_body"Parameter name for response body content.
    ResponseHeadersParameterstring"_proxy_headers"Parameter name for response headers as JSON.
    ResponseContentTypeParameterstring"_proxy_content_type"Parameter name for Content-Type header value.
    ResponseSuccessParameterstring"_proxy_success"Parameter name for success indicator (true for 2xx status).
    ResponseErrorMessageParameterstring"_proxy_error_message"Parameter name for error message if request failed.

    Proxy Modes

    Passthrough Mode

    When the PostgreSQL function has no proxy response parameters, the upstream response is returned directly to the client without opening a database connection:

    sql
    sql
    create function get_external_data()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +
    +comment on function get_external_data() is 'HTTP GET
    +@proxy https://api.example.com/data';

    Equivalent as a SQL file endpoint (sql/get-external-data.sql):

    sql
    sql
    -- HTTP GET
    +-- @proxy https://api.example.com/data
    +select;

    Transform Mode

    When the PostgreSQL function has parameters matching the configured response parameter names, the proxy response is passed to the function for processing:

    sql
    sql
    create function get_and_transform(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_headers json default null,
    +    _proxy_content_type text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', _proxy_error_message);
    +    end if;
    +    return json_build_object(
    +        'status', _proxy_status_code,
    +        'data', _proxy_body::json
    +    );
    +end;
    +$$;
    +
    +comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
    +@proxy https://api.example.com/data';

    Response Parameters

    When the PostgreSQL function has parameters matching these names (the defaults below, or whatever you set in the Response Parameter Names settings above), the upstream response data is bound to them after the request returns:

    Parameter NameTypeDescription
    _proxy_status_codeint or textHTTP status code from upstream (e.g., 200, 404). Bound as text if the parameter is declared text/varchar, otherwise as an integer.
    _proxy_bodytextResponse body content.
    _proxy_headersjsonResponse headers as JSON object.
    _proxy_content_typetextContent-Type header value.
    _proxy_successbooleanTrue for 2xx status codes.
    _proxy_error_messagetextError message if request failed.

    Parameters are matched by name (case-insensitive), not by position, and only the ones your function actually needs have to be declared. See How parameters are mapped in the annotation reference for the full rules.

    Automatic Parameter Forwarding

    Parameters that NpgsqlRest fills server-side (not supplied by the client) are forwarded to the upstream so the proxy receives the same parameter set the function would. All automatic sources are treated consistently:

    • user claims (claim-mapped parameters, user_params),
    • the IP address parameter,
    • HTTP Custom Type fields (the auto-filled responseBody / responseStatusCode / … on a routine with an HTTP Custom Type parameter),
    • resolved-parameter expressions (values looked up server-side via SQL).

    Placement follows the endpoint shape, not the HTTP verb

    Where each automatic parameter is placed mirrors how the endpoint itself receives parameters — it is decided by RequestParamType, not by the HTTP method (a POST endpoint can use param_type query):

    • The parameter designated as the body parameter (@body_parameter_name) carries the raw request body.
    • Otherwise: QueryString → appended to the proxy query string; BodyJson → merged into the proxy JSON body (typed: numbers, booleans, embedded JSON, or strings) when the proxy method can carry a body.

    Forwarding is additive — the verbatim incoming request is still forwarded; the automatic parameters are added on top. Body merging applies only when the forwarded request carries a JSON content type (multipart / non-JSON is forwarded verbatim).

    sql
    sql
    -- GET endpoint (QueryString): the auto-filled values are appended to the proxy query string.
    +create function proxy_with_claims(
    +    _user_id text default null,        -- Forwarded as ?userId=...
    +    _user_name text default null,      -- Forwarded as ?userName=...
    +    _ip_address text default null,     -- Forwarded as ?ipAddress=...
    +    _user_claims json default null,    -- Forwarded as ?userClaims=...
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('user', _user_id, 'data', _proxy_body);
    +end;
    +$$;
    +
    +comment on function proxy_with_claims(text, text, text, json, int, text) is 'HTTP GET
    +@authorize
    +@user_params
    +@proxy https://api.example.com/data';

    Behavior note (3.18.1)

    Before 3.18.1, user-claim and IP parameters were always forwarded on the query string, and HTTP Custom Type fields / resolved parameters were not forwarded at all. They are now unified under the rule above. For QueryString endpoints (the default for GET) the result is unchanged — values stay on the query string; for BodyJson endpoints the automatic parameters are now merged into the JSON body instead.

    Query-string length guard

    When an automatic parameter is placed on the proxy query string (a QueryString endpoint — see above), an oversized value would be percent-encoded into the request line and produce a URL the upstream rejects (HTTP 414 URI Too Long / 431 Request Header Fields Too Large) or that resets the connection. A common trigger is an HTTP Custom Type field whose body holds a large payload (e.g. a scraped HTML page).

    MaxForwardedQueryParamLength (default 2048) caps this. A single server-filled value longer than the limit is skipped with a warning rather than appended to the query string — the rest of the request still forwards normally. Set it to 0 (or less) to disable the guard entirely.

    To forward a large value to the upstream, move it into the request body instead of the query string:

    • use a body-carrying proxy method (POST / PUT / PATCH) so it travels in the request body, and
    • designate the field with @body_parameter_name to carry the raw body, while the remaining small fields stay on the query string under the length guard.

    New in 3.18.2

    MaxForwardedQueryParamLength was added in 3.18.2. Previously a large auto-filled value (such as an HTTP Custom Type body field) was percent-encoded into the upstream query string unconditionally.

    HTTP Headers (user_context)

    When user_context is enabled, user context values are forwarded as HTTP headers to the upstream proxy:

    sql
    sql
    create function proxy_with_context(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('status', _proxy_status_code);
    +end;
    +$$;
    +
    +comment on function proxy_with_context(int, text) is 'HTTP GET
    +@authorize
    +@user_context
    +@proxy https://api.example.com/data';
    +-- Headers forwarded: request.user_id, request.user_name, request.user_roles
    +-- (configurable via ContextKeyClaimsMapping)

    Upload Forwarding

    For upload endpoints with proxy, configure whether to process uploads locally or forward raw multipart data:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}
    ValueDescription
    false (default)Uploads are processed locally; proxy receives parsed data.
    trueRaw multipart/form-data is streamed directly to upstream (memory-efficient for large files).

    Key Features

    • Passthrough mode: No database connection opened when function has no proxy response parameters
    • Transform mode: Process upstream response in PostgreSQL before returning to client
    • User claims forwarding: Authenticated user claims passed as query parameters to upstream
    • User context headers: User context values passed as HTTP headers to upstream
    • Streaming uploads: Memory-efficient streaming for large file uploads when ForwardUploadContent is enabled
    • Timeout handling: Configurable per-request timeout with proper 504 Gateway Timeout responses
    • Header forwarding: Configurable request/response header forwarding with exclusion lists

    Self-Referencing Calls (Relative Paths)

    When a proxy annotation includes a relative path (starting with /), the request is routed to another endpoint on the same NpgsqlRest server:

    sql
    sql
    comment on function my_aggregator() is 'HTTP GET
    +@proxy POST /api/data-source';

    Self-referencing calls bypass the HTTP stack entirely — the endpoint handler is invoked directly in-process via InternalRequestHandler, with zero network overhead.

    URL Resolution

    A relative path in the annotation always creates a self-referencing internal call. The global ProxyOptions.Host setting is not prepended to relative paths — it is only used when the annotation has no URL at all. See URL Resolution for the full priority table.

    Internal-Only Endpoints

    Combine with the @internal annotation to create endpoints accessible only via proxy but not exposed as public HTTP routes:

    sql
    sql
    -- Internal helper: not accessible from outside
    +comment on function get_cached_rates() is 'HTTP GET
    +@internal';
    +
    +-- Public endpoint that proxies the internal one
    +comment on function convert_currency(numeric, text, text) is 'HTTP GET
    +@proxy GET /api/get-cached-rates';

    Direct HTTP call to /api/get-cached-rates returns 404, but the proxy call works.

    Complete Example

    Production configuration with proxy enabled:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "https://api.internal.example.com",
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding", "Authorization"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ForwardUploadContent": false,
    +      "MaxForwardedQueryParamLength": 2048
    +    }
    +  }
    +}

    Next Steps

    See Also

    • PROXY - Enable proxy for endpoints
    • PROXY_OUT - Configure outbound proxy settings
    `,64)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_proxy.md.DOTosU06.lean.js b/assets/config_proxy.md.DOTosU06.lean.js new file mode 100644 index 000000000..74a2aa73e --- /dev/null +++ b/assets/config_proxy.md.DOTosU06.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Proxy Options","titleTemplate":"NpgsqlRest","description":"Configure reverse proxy for NpgsqlRest. Forward requests to upstream services, cache responses in PostgreSQL, transform API responses with SQL functions.","frontmatter":{"outline":[2,3],"title":"Proxy Options","titleTemplate":"NpgsqlRest","description":"Configure reverse proxy for NpgsqlRest. Forward requests to upstream services, cache responses in PostgreSQL, transform API responses with SQL functions.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, postgresql reverse proxy, api gateway postgresql, upstream service proxy, transform mode proxy, passthrough proxy"}],["meta",{"property":"og:title","content":"NpgsqlRest Proxy Options"}],["meta",{"property":"og:description","content":"Configure reverse proxy to forward requests, cache responses, and transform API data with PostgreSQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/proxy.md","filePath":"config/proxy.md"}'),t={name:"config/proxy.md"};function l(r,s,p,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n("",64)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_rate-limiter.md.wLNPwvhE.js b/assets/config_rate-limiter.md.wLNPwvhE.js new file mode 100644 index 000000000..a177f6c31 --- /dev/null +++ b/assets/config_rate-limiter.md.wLNPwvhE.js @@ -0,0 +1,167 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Rate Limiter Configuration","titleTemplate":"NpgsqlRest","description":"Configure rate limiting for NpgsqlRest APIs. Control request rates with fixed window, sliding window, token bucket, and concurrency policies.","frontmatter":{"outline":[2,3],"title":"Rate Limiter Configuration","titleTemplate":"NpgsqlRest","description":"Configure rate limiting for NpgsqlRest APIs. Control request rates with fixed window, sliding window, token bucket, and concurrency policies.","head":[["meta",{"name":"keywords","content":"npgsqlrest rate limiter, api rate limiting, postgresql api throttling, request rate control, api throttle configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Rate Limiter Configuration"}],["meta",{"property":"og:description","content":"Configure rate limiting with fixed window, sliding window, token bucket, and concurrency policies."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/rate-limiter.md","filePath":"config/rate-limiter.md"}'),e={name:"config/rate-limiter.md"};function l(h,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n(`

    Rate Limiter

    Rate limiting configuration to control the number of requests from clients. Apply policies to endpoints using the rate_limiter_policy annotation.

    Overview

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    "Policies": {}
    +  }
    +}

    Breaking change in 3.13.0

    RateLimiterOptions:Policies was previously an array of objects with explicit "Name" properties. It is now an object keyed by policy name, matching ValidationOptions:Rules and CacheOptions:Profiles. Migrate by moving each policy's Name value to be the JSON key and dropping the Name field. If you upgrade with the old array form still in your config, startup will fail with a clear InvalidOperationException telling you to migrate.

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable rate limiting.
    StatusCodeint429HTTP status code returned when rate limit is exceeded.
    StatusMessagestring"Too many requests. Please try again later."Response message when rate limit is exceeded.
    DefaultPolicystringnullName of the default policy to apply to all endpoints.
    Policiesobject{}Named rate limiting policies, keyed by policy name. Assign a policy to an endpoint using the rate_limiter_policy annotation.

    Policy Types

    Four policy types are available:

    • FixedWindow - Fixed time window rate limiting
    • SlidingWindow - Sliding time window rate limiting
    • TokenBucket - Token bucket algorithm
    • Concurrency - Concurrent request limiting

    Fixed Window Policy

    Limits requests within fixed time intervals.

    json
    json
    {
    +  "Policies": {
    +    "fixed": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}

    The JSON key ("fixed") is the policy name used with the rate_limiter_policy annotation.

    SettingTypeDefaultDescription
    Typestring-Must be "FixedWindow".
    EnabledboolfalseEnable this policy.
    PermitLimitint100Maximum requests allowed per window.
    WindowSecondsint60Window duration in seconds.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish permits.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Fixed Window Limiter documentation.

    Sliding Window Policy

    Limits requests using a sliding time window with segments.

    json
    json
    {
    +  "Policies": {
    +    "sliding": {
    +      "Type": "SlidingWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "SegmentsPerWindow": 6,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "SlidingWindow".
    EnabledboolfalseEnable this policy.
    PermitLimitint100Maximum requests allowed per window.
    WindowSecondsint60Window duration in seconds.
    SegmentsPerWindowint6Number of segments dividing the window.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish permits.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Sliding Window Limiter documentation.

    Token Bucket Policy

    Limits requests using the token bucket algorithm.

    json
    json
    {
    +  "Policies": {
    +    "bucket": {
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "TokenLimit": 100,
    +      "TokensPerPeriod": 10,
    +      "ReplenishmentPeriodSeconds": 10,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "TokenBucket".
    EnabledboolfalseEnable this policy.
    TokenLimitint100Maximum tokens in the bucket.
    TokensPerPeriodint10Number of tokens to add per replenishment period.
    ReplenishmentPeriodSecondsint10How often tokens are added to the bucket.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish tokens.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Token Bucket Limiter documentation.

    Concurrency Policy

    Limits the number of concurrent requests.

    json
    json
    {
    +  "Policies": {
    +    "concurrency": {
    +      "Type": "Concurrency",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "QueueLimit": 5,
    +      "OldestFirst": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "Concurrency".
    EnabledboolfalseEnable this policy.
    PermitLimitint10Maximum concurrent requests.
    QueueLimitint5Maximum queued requests when limit is reached.
    OldestFirstbooltrueProcess queued requests oldest first.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Concurrency Limiter documentation.

    Per-User Rate Limiting (Partition)

    New in 3.13.0

    Rate-limiter policies can now be partitioned at request time, so each request gets its own bucket based on a value derived from HttpContext (a claim, an IP, a header, or a static fallback).

    The classic use case is per-user throttling: each authenticated user gets their own quota instead of all users sharing one global bucket. Without Partition, all requests under a policy share a single global bucket.

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    },
    +    "throttle_anon_only": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "BypassAuthenticated": true,
    +        "Sources": [{ "Type": "IpAddress" }]
    +      }
    +    }
    +  }
    +}

    Partition Fields

    FieldTypeDefaultDescription
    Sourcesarray-Ordered list of partition key sources. Walked top-to-bottom at request time; the first source returning a non-empty value wins. If no source resolves, partition resolution falls through to the literal key "unpartitioned".
    BypassAuthenticatedboolfalseWhen true, signed-in users skip the limiter entirely. Evaluated before Sources, so use this for "throttle anonymous only" patterns.

    Source Types

    TypeBehaviorName required?
    ClaimReads HttpContext.User.FindFirst(Name).Value.Yes (the claim type, e.g., "name_identifier").
    IpAddressReads the client IP via HttpRequest.GetClientIpAddress(), which honors X-Forwarded-For / X-Real-IP ahead of Connection.RemoteIpAddress.No
    HeaderReads HttpContext.Request.Headers[Name].Yes (the header name).
    StaticAlways returns the configured Value. Useful as a terminal fallback (e.g., everyone unmatched shares the "anonymous" bucket).Uses Value instead.

    Behavior is unchanged for policies without a Partition block. Each non-partitioned policy still uses a single global bucket.

    Each Sources entry is validated at startup — invalid entries (e.g., Claim without Name, unknown Type) are logged at Warning and skipped. If a Partition block has no usable sources and BypassAuthenticated is false, the partition is dropped (with a Warning) and the policy reverts to a single global bucket.

    Per-Policy Status Code and Message

    New in 3.16.2

    Each named policy can set its own StatusCode and/or StatusMessage, overriding the global RateLimiterOptions:StatusCode / RateLimiterOptions:StatusMessage for requests rejected by that policy. A policy that omits either field inherits the global value.

    Previously the global StatusCode / StatusMessage were the only values returned for any rejected request, so a login-specific message (e.g. "Too many login attempts…") would be returned for every rate-limited endpoint. Now the override is resolved at rejection time from the endpoint's policy, so each policy can speak for itself:

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "StatusCode": 429,                                  // global default
    +  "StatusMessage": "Too many requests. Please slow down.",
    +  "Policies": {
    +    "login_throttle": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +      "Partition": { "Sources": [ { "Type": "IpAddress" } ] }
    +    },
    +    "api": {
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "StatusCode": 503,
    +      "StatusMessage": "API capacity reached. Retry shortly."
    +    }
    +  }
    +}

    A request rejected by login_throttle returns 429 (inherited) with the login message; a request rejected by api returns 503 with the API message. This is fully backward compatible — configs that set only the global values behave exactly as before.

    Ready-to-use login_throttle policy

    The shipped appsettings.json includes a disabled login_throttle policy — 10 attempts per minute partitioned per client IP, with its own rejection message — so the common case is one flag away:

    jsonc
    jsonc
    "login_throttle": {
    +  "Type": "FixedWindow",
    +  "Enabled": false,
    +  "PermitLimit": 10,
    +  "WindowSeconds": 60,
    +  "QueueLimit": 0,
    +  "AutoReplenishment": true,
    +  "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +  "Partition": { "Sources": [ { "Type": "IpAddress" } ], "BypassAuthenticated": false }
    +}

    Set "Enabled": true and apply it to a login endpoint with the rate_limiter_policy annotation (rate_limiter login_throttle), or set it as DefaultPolicy.

    Complete Example

    Configuration with multiple policies:

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": "bucket",
    +    "Policies": {
    +      "fixed": {
    +        "Type": "FixedWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "sliding": {
    +        "Type": "SlidingWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "SegmentsPerWindow": 6,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "bucket": {
    +        "Type": "TokenBucket",
    +        "Enabled": true,
    +        "TokenLimit": 100,
    +        "TokensPerPeriod": 10,
    +        "ReplenishmentPeriodSeconds": 10,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "concurrency": {
    +        "Type": "Concurrency",
    +        "Enabled": true,
    +        "PermitLimit": 10,
    +        "QueueLimit": 5,
    +        "OldestFirst": true
    +      },
    +      "per_user": {
    +        "Type": "FixedWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true,
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "Claim", "Name": "name_identifier" },
    +            { "Type": "IpAddress" },
    +            { "Type": "Static", "Value": "anonymous" }
    +          ]
    +        }
    +      }
    +    }
    +  }
    +}

    Rate Limiting Scope

    Rate-limit policies are applied at the HTTP route level. Every request arriving over HTTP is metered by the policy of the route it hits. Three invocation paths execute endpoints in-process, without passing through the target endpoint's route — by design, the target's own rate_limiter policy is not consulted on these paths:

    Invocation pathWhat still appliesHow it is throttled
    HTTP client type self-calls (a type's URL targets the same server)the target's authorize and all execution-level annotations (the caller's principal is forwarded)by the calling endpoint's own route policy
    Proxy self-calls (@proxy / @proxy_out targeting own URL)same as aboveby the proxy endpoint's own route policy
    MCP tools/callsame as aboveby McpOptions.RateLimiterPolicy on the whole /mcp route (a routine's @rate_limiter does not carry to tool calls; a startup warning is logged for @mcp routines that also have @rate_limiter)

    In short: these paths cannot be reached by a client directly — they exist only where you explicitly composed them in SQL or config — and the outer route that the client does hit is where its rate limit applies.

    Next Steps

    • Server & SSL - Configure HTTPS and Kestrel web server
    • CORS - Configure Cross-Origin Resource Sharing

    See Also

    `,63)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_rate-limiter.md.wLNPwvhE.lean.js b/assets/config_rate-limiter.md.wLNPwvhE.lean.js new file mode 100644 index 000000000..4c11e6395 --- /dev/null +++ b/assets/config_rate-limiter.md.wLNPwvhE.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Rate Limiter Configuration","titleTemplate":"NpgsqlRest","description":"Configure rate limiting for NpgsqlRest APIs. Control request rates with fixed window, sliding window, token bucket, and concurrency policies.","frontmatter":{"outline":[2,3],"title":"Rate Limiter Configuration","titleTemplate":"NpgsqlRest","description":"Configure rate limiting for NpgsqlRest APIs. Control request rates with fixed window, sliding window, token bucket, and concurrency policies.","head":[["meta",{"name":"keywords","content":"npgsqlrest rate limiter, api rate limiting, postgresql api throttling, request rate control, api throttle configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Rate Limiter Configuration"}],["meta",{"property":"og:description","content":"Configure rate limiting with fixed window, sliding window, token bucket, and concurrency policies."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/rate-limiter.md","filePath":"config/rate-limiter.md"}'),e={name:"config/rate-limiter.md"};function l(h,s,p,k,r,d){return t(),a("div",null,s[0]||(s[0]=[n("",63)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_response-compression.md.CMmom6qe.js b/assets/config_response-compression.md.CMmom6qe.js new file mode 100644 index 000000000..8d68795e2 --- /dev/null +++ b/assets/config_response-compression.md.CMmom6qe.js @@ -0,0 +1,41 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Response Compression","titleTemplate":"NpgsqlRest","description":"Configure HTTP response compression in NpgsqlRest. Enable Brotli and Gzip compression, control compression levels, and specify MIME types to compress.","frontmatter":{"outline":[2,3],"title":"Response Compression","titleTemplate":"NpgsqlRest","description":"Configure HTTP response compression in NpgsqlRest. Enable Brotli and Gzip compression, control compression levels, and specify MIME types to compress.","head":[["meta",{"name":"keywords","content":"npgsqlrest compression, brotli compression api, gzip rest api, http response compression, api performance optimization"}],["meta",{"property":"og:title","content":"NpgsqlRest Response Compression"}],["meta",{"property":"og:description","content":"Configure Brotli and Gzip compression for HTTP responses to reduce bandwidth."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/response-compression.md","filePath":"config/response-compression.md"}'),t={name:"config/response-compression.md"};function l(p,s,r,o,h,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Response Compression

    Response compression settings for reducing HTTP response sizes using Brotli and Gzip algorithms.

    Overview

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": false,
    +    "EnableForHttps": false,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "Optimal",
    +    "IncludeMimeTypes": [
    +      "text/plain",
    +      "text/css",
    +      "application/javascript",
    +      "text/html",
    +      "application/xml",
    +      "text/xml",
    +      "application/json",
    +      "text/json",
    +      "image/svg+xml",
    +      "font/woff",
    +      "font/woff2",
    +      "application/font-woff",
    +      "application/font-woff2"
    +    ],
    +    "ExcludeMimeTypes": []
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable response compression for HTTP responses.
    EnableForHttpsboolfalseEnable response compression for HTTPS responses.
    UseBrotlibooltrueUse Brotli compression algorithm when supported by client.
    UseGzipFallbackbooltrueUse Gzip compression as fallback when Brotli is not supported.
    CompressionLevelstring"Optimal"Compression level: Optimal, Fastest, NoCompression, SmallestSize.
    IncludeMimeTypesarray(see below)MIME types to include for compression.
    ExcludeMimeTypesarray[]MIME types to exclude from compression.

    Compression Levels

    LevelDescription
    OptimalBalance between compression ratio and speed (default).
    FastestFastest compression with lower compression ratio.
    SmallestSizeBest compression ratio but slower.
    NoCompressionNo compression applied.

    Compression Algorithms

    Brotli

    Brotli provides better compression ratios than Gzip, especially for text content. When UseBrotli is true, the server will use Brotli compression if the client supports it (indicated by Accept-Encoding: br header).

    Gzip Fallback

    When UseGzipFallback is true, the server falls back to Gzip compression for clients that don't support Brotli but do support Gzip (indicated by Accept-Encoding: gzip header).

    HTTPS Compression

    Security Consideration

    Enabling compression for HTTPS responses (EnableForHttps: true) may expose your application to BREACH-style attacks. Only enable if you understand the security implications and have appropriate mitigations in place.

    Default MIME Types

    The default IncludeMimeTypes covers common compressible content:

    CategoryMIME Types
    Texttext/plain, text/css, text/html
    JavaScriptapplication/javascript
    XMLapplication/xml, text/xml
    JSONapplication/json, text/json
    SVGimage/svg+xml
    Fontsfont/woff, font/woff2, application/font-woff, application/font-woff2

    Example Configuration

    Enable compression for production:

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": true,
    +    "EnableForHttps": true,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "Optimal"
    +  }
    +}

    High-compression configuration for bandwidth-constrained environments:

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": true,
    +    "EnableForHttps": true,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "SmallestSize"
    +  }
    +}

    Next Steps

    `,27)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_response-compression.md.CMmom6qe.lean.js b/assets/config_response-compression.md.CMmom6qe.lean.js new file mode 100644 index 000000000..6f4a3c37a --- /dev/null +++ b/assets/config_response-compression.md.CMmom6qe.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Response Compression","titleTemplate":"NpgsqlRest","description":"Configure HTTP response compression in NpgsqlRest. Enable Brotli and Gzip compression, control compression levels, and specify MIME types to compress.","frontmatter":{"outline":[2,3],"title":"Response Compression","titleTemplate":"NpgsqlRest","description":"Configure HTTP response compression in NpgsqlRest. Enable Brotli and Gzip compression, control compression levels, and specify MIME types to compress.","head":[["meta",{"name":"keywords","content":"npgsqlrest compression, brotli compression api, gzip rest api, http response compression, api performance optimization"}],["meta",{"property":"og:title","content":"NpgsqlRest Response Compression"}],["meta",{"property":"og:description","content":"Configure Brotli and Gzip compression for HTTP responses to reduce bandwidth."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/response-compression.md","filePath":"config/response-compression.md"}'),t={name:"config/response-compression.md"};function l(p,s,r,o,h,d){return n(),a("div",null,s[0]||(s[0]=[e("",27)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_routine-options.md.VEEBtjYd.js b/assets/config_routine-options.md.VEEBtjYd.js new file mode 100644 index 000000000..56f404dde --- /dev/null +++ b/assets/config_routine-options.md.VEEBtjYd.js @@ -0,0 +1,75 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Routine Options","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL routine handling in NpgsqlRest. Filter by language, handle custom types, and control function/procedure endpoint generation.","frontmatter":{"outline":[2,3],"title":"Routine Options","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL routine handling in NpgsqlRest. Filter by language, handle custom types, and control function/procedure endpoint generation.","head":[["meta",{"name":"keywords","content":"npgsqlrest routines, postgresql functions api, stored procedures rest, plpgsql api, custom type parameters"}],["meta",{"property":"og:title","content":"NpgsqlRest Routine Options"}],["meta",{"property":"og:description","content":"Configure PostgreSQL function and procedure handling for REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/routine-options.md","filePath":"config/routine-options.md"}'),e={name:"config/routine-options.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Routine Options

    Options for handling PostgreSQL routines (functions and procedures).

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "Enabled": true,
    +      "CustomTypeParameterSeparator": null,
    +      "IncludeLanguages": null,
    +      "ExcludeLanguages": null,
    +      "NestedJsonForCompositeTypes": false,
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledbooltrueEnable endpoint creation from PostgreSQL functions and procedures. Set to false for SQL-files-only deployments.
    CustomTypeParameterSeparatorstringnullSeparator for custom type parameter names. Uses underscore (_) if null.
    IncludeLanguagesarraynullList of routine language names to include. Includes all if null. Case-insensitive.
    ExcludeLanguagesarraynullList of routine language names to exclude. Excludes C and INTERNAL if null. Case-insensitive.
    NestedJsonForCompositeTypesbooleanfalseWhen true, composite type columns in return tables are serialized as nested JSON objects instead of flat structure.
    ResolveNestedCompositeTypesbooleantrueWhen true, nested composite types are resolved to any depth, serializing inner composites as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Custom Type Parameter Separator

    When using custom types for parameters, field names are merged with the parameter name:

    sql
    sql
    create type custom_type1 as (value text);
    +
    +create function my_func(_p custom_type1) ...

    With default separator (_), the parameter name becomes _p_value.

    To use a different separator:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "CustomTypeParameterSeparator": "."
    +    }
    +  }
    +}

    This would result in _p.value instead.

    Language Filtering

    By default, routines written in C and INTERNAL are excluded for security reasons. You can customize which languages are included or excluded.

    Include Specific Languages

    To only expose routines written in specific languages:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "IncludeLanguages": ["plpgsql", "sql"]
    +    }
    +  }
    +}

    Exclude Additional Languages

    To exclude additional languages beyond the defaults:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ExcludeLanguages": ["C", "INTERNAL", "plpython3u"]
    +    }
    +  }
    +}

    Common PostgreSQL Languages

    LanguageDescription
    sqlPlain SQL functions
    plpgsqlPL/pgSQL procedural language
    plpython3uPL/Python (untrusted)
    plperlPL/Perl
    pltclPL/Tcl
    CC language (excluded by default)
    INTERNALInternal PostgreSQL functions (excluded by default)

    Nested JSON for Composite Types

    When returning composite types from functions, by default the fields are flattened into the parent object. Enable NestedJsonForCompositeTypes to preserve the nested structure.

    Example function:

    sql
    sql
    create type address_type as (street text, city text, zip_code text);
    +
    +create function get_user_with_address()
    +returns table(user_id int, user_name text, address address_type)
    +language sql
    +begin atomic;
    +  select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
    +end;

    Default output (NestedJsonForCompositeTypes: false):

    json
    json
    [{"userId": 1, "userName": "Alice", "street": "123 Main St", "city": "New York", "zipCode": "10001"}]

    With NestedJsonForCompositeTypes: true:

    json
    json
    [{"userId": 1, "userName": "Alice", "address": {"street": "123 Main St", "city": "New York", "zipCode": "10001"}}]

    To enable globally:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "NestedJsonForCompositeTypes": true
    +    }
    +  }
    +}

    For per-endpoint control, use the @nested annotation.

    TIP

    This setting applies to function/procedure endpoints. SQL file endpoints have their own independent NestedJsonForCompositeTypes setting in SQL File Source configuration.

    Resolve Nested Composite Types

    By default, NpgsqlRest resolves nested composite types to any depth. When a composite type contains another composite type (or an array of composites), the inner composites are serialized as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Example:

    sql
    sql
    create type inner_type as (id int, name text);
    +create type outer_type as (label text, inner_val inner_type);
    +create type with_array as (group_name text, members inner_type[]);
    +
    +create function get_nested_data()
    +returns table(data outer_type, items with_array)
    +language sql
    +begin atomic;
    +select
    +    row('outer', row(1, 'inner')::inner_type)::outer_type,
    +    row('group1', array[row(1,'a')::inner_type, row(2,'b')::inner_type])::with_array;
    +end;

    Output:

    json
    json
    [{
    +  "data": {"label":"outer","innerVal":{"id":1,"name":"inner"}},
    +  "items": {"groupName":"group1","members":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
    +}]

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Default: true - nested composites are fully resolved.

    How it works:

    At application startup, when ResolveNestedCompositeTypes is enabled:

    1. Type Cache Initialization: Queries pg_catalog to build a cache of all composite types in the database, including their field names, field types, and nested relationships.

    2. Metadata Enrichment: For each routine that returns composite types, the field descriptors are enriched with nested type information from the cache.

    3. Runtime Serialization: During request processing, the serializer checks each field's metadata. If the field is marked as a composite type (or array of composites), it recursively parses the PostgreSQL tuple string and outputs a proper JSON object/array.

    When to disable (ResolveNestedCompositeTypes: false):

    ScenarioReason
    Large schemas with thousands of composite typesReduces startup time by skipping the type cache initialization query
    No nested composites in your schemaIf your composites don't contain other composites, the cache provides no benefit
    Memory-constrained environmentsThe type cache consumes memory proportional to the number of composite types
    Backward compatibilityIf you depend on the old tuple string format "(1,x)" in your client code

    Performance considerations:

    • Startup cost: One additional query to pg_catalog at startup to build the type cache
    • Memory: Cache size is proportional to: (number of composite types) × (average fields per type)
    • Runtime: Negligible - just a dictionary lookup per composite field

    Complete Example

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "CustomTypeParameterSeparator": "_",
    +      "IncludeLanguages": ["plpgsql", "sql"],
    +      "ExcludeLanguages": null,
    +      "NestedJsonForCompositeTypes": false,
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Next Steps

    `,57)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_routine-options.md.VEEBtjYd.lean.js b/assets/config_routine-options.md.VEEBtjYd.lean.js new file mode 100644 index 000000000..b6ebbee0e --- /dev/null +++ b/assets/config_routine-options.md.VEEBtjYd.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Routine Options","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL routine handling in NpgsqlRest. Filter by language, handle custom types, and control function/procedure endpoint generation.","frontmatter":{"outline":[2,3],"title":"Routine Options","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL routine handling in NpgsqlRest. Filter by language, handle custom types, and control function/procedure endpoint generation.","head":[["meta",{"name":"keywords","content":"npgsqlrest routines, postgresql functions api, stored procedures rest, plpgsql api, custom type parameters"}],["meta",{"property":"og:title","content":"NpgsqlRest Routine Options"}],["meta",{"property":"og:description","content":"Configure PostgreSQL function and procedure handling for REST API endpoints."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/routine-options.md","filePath":"config/routine-options.md"}'),e={name:"config/routine-options.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",57)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/config_security-headers.md.D2EP-6xq.js b/assets/config_security-headers.md.D2EP-6xq.js new file mode 100644 index 000000000..e7abb8f17 --- /dev/null +++ b/assets/config_security-headers.md.D2EP-6xq.js @@ -0,0 +1,81 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Security Headers","titleTemplate":"NpgsqlRest","description":"Configure HTTP security headers for NpgsqlRest. Protect against XSS, clickjacking, MIME-sniffing, and other web vulnerabilities with X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, and more.","frontmatter":{"outline":[2,3],"title":"Security Headers","titleTemplate":"NpgsqlRest","description":"Configure HTTP security headers for NpgsqlRest. Protect against XSS, clickjacking, MIME-sniffing, and other web vulnerabilities with X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest security headers, http security headers, csp, content security policy, x-frame-options, referrer-policy, permissions-policy"}],["meta",{"property":"og:title","content":"NpgsqlRest Security Headers Configuration"}],["meta",{"property":"og:description","content":"Configure HTTP security headers to protect against common web vulnerabilities."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/security-headers.md","filePath":"config/security-headers.md"}'),t={name:"config/security-headers.md"};function l(r,s,p,h,o,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Security Headers

    New in 3.6.0

    Security Headers middleware was added in version 3.6.0.

    Configurable security headers middleware to protect against common web vulnerabilities. The middleware adds HTTP security headers to all responses.

    Overview

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": false,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    "ContentSecurityPolicy": null,
    +    "PermissionsPolicy": null,
    +    "CrossOriginOpenerPolicy": null,
    +    "CrossOriginEmbedderPolicy": null,
    +    "CrossOriginResourcePolicy": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    XContentTypeOptionsstring"nosniff"Prevents browsers from MIME-sniffing a response away from the declared content-type. Set to null to not include this header.
    XFrameOptionsstring"DENY"Controls whether the browser should allow the page to be rendered in a frame. Values: "DENY", "SAMEORIGIN". Set to null to not include.
    ReferrerPolicystring"strict-origin-when-cross-origin"Controls how much referrer information should be included with requests. Set to null to not include.
    ContentSecurityPolicystringnullDefines approved sources of content that the browser may load. Helps prevent XSS and code injection attacks.
    PermissionsPolicystringnullControls which browser features and APIs can be used.
    CrossOriginOpenerPolicystringnullControls how your document is shared with cross-origin popups.
    CrossOriginEmbedderPolicystringnullPrevents a document from loading cross-origin resources that don't explicitly grant permission.
    CrossOriginResourcePolicystringnullIndicates how the resource should be shared cross-origin.

    X-Content-Type-Options

    Prevents browsers from MIME-sniffing a response away from the declared content-type, reducing exposure to drive-by download attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff"
    +  }
    +}

    The recommended value is "nosniff".

    X-Frame-Options

    Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>. Prevents clickjacking attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XFrameOptions": "DENY"
    +  }
    +}
    ValueDescription
    DENYNever allow the page to be framed
    SAMEORIGINAllow framing from the same origin only

    WARNING

    This header is skipped if Antiforgery is enabled, as Antiforgery already sets X-Frame-Options: SAMEORIGIN by default via its SuppressXFrameOptionsHeader setting.

    Referrer-Policy

    Controls how much referrer information should be included with requests made from your site.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "ReferrerPolicy": "strict-origin-when-cross-origin"
    +  }
    +}
    ValueDescription
    no-referrerNever send referrer information
    no-referrer-when-downgradeSend full URL for same-security requests, nothing for downgrades
    originSend only the origin (scheme, host, port)
    origin-when-cross-originSend full URL for same-origin, origin only for cross-origin
    same-originSend full URL for same-origin, nothing for cross-origin
    strict-originSend origin for same-security, nothing for downgrades
    strict-origin-when-cross-originSend full URL for same-origin, origin for cross-origin same-security
    unsafe-urlAlways send full URL (not recommended)

    Content-Security-Policy

    Defines approved sources of content that the browser may load. This is the primary browser-side defense against XSS, clickjacking, and other code injection attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "ContentSecurityPolicy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +  }
    +}

    TIP

    CSP should be configured based on your specific application needs. Start with a restrictive policy and loosen as needed.

    Common directives:

    • default-src - Fallback for other directives
    • script-src - Valid sources for JavaScript
    • style-src - Valid sources for stylesheets
    • img-src - Valid sources for images
    • connect-src - Valid sources for fetch, WebSocket, etc.
    • font-src - Valid sources for fonts
    • frame-src - Valid sources for frames

    Reference: MDN Content-Security-Policy

    Permissions-Policy

    Controls which browser features and APIs can be used by your application and any embedded content.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "PermissionsPolicy": "geolocation=(), microphone=(), camera=()"
    +  }
    +}

    This example disables geolocation, microphone, and camera access entirely.

    To allow features only from the same origin:

    json
    json
    {
    +  "SecurityHeaders": {
    +    "PermissionsPolicy": "geolocation=(self), microphone=(self)"
    +  }
    +}

    Reference: MDN Permissions-Policy

    Cross-Origin Policies

    Cross-Origin-Opener-Policy

    Controls how your document is shared with cross-origin popups.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginOpenerPolicy": "same-origin"
    +  }
    +}
    ValueDescription
    unsafe-noneDefault browser behavior
    same-origin-allow-popupsIsolate from cross-origin, allow popups
    same-originFull isolation from cross-origin documents

    Cross-Origin-Embedder-Policy

    Prevents a document from loading cross-origin resources that don't explicitly grant permission.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginEmbedderPolicy": "require-corp"
    +  }
    +}
    ValueDescription
    unsafe-noneDefault browser behavior
    require-corpRequire CORP or CORS for cross-origin resources
    credentiallessLoad cross-origin resources without credentials

    TIP

    require-corp along with CrossOriginOpenerPolicy: same-origin enables access to SharedArrayBuffer and high-resolution timers.

    Cross-Origin-Resource-Policy

    Indicates how the resource should be shared cross-origin.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginResourcePolicy": "same-origin"
    +  }
    +}
    ValueDescription
    same-siteOnly same-site requests allowed
    same-originOnly same-origin requests allowed
    cross-originAny origin can load the resource

    Example Configurations

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin"
    +  }
    +}

    API-Only Application

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "no-referrer",
    +    "ContentSecurityPolicy": "default-src 'none'; frame-ancestors 'none'"
    +  }
    +}

    Full Protection with CSP

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "SAMEORIGIN",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    "ContentSecurityPolicy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'",
    +    "PermissionsPolicy": "geolocation=(), microphone=(), camera=()",
    +    "CrossOriginOpenerPolicy": "same-origin",
    +    "CrossOriginEmbedderPolicy": "require-corp",
    +    "CrossOriginResourcePolicy": "same-origin"
    +  }
    +}

    Next Steps

    `,59)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_security-headers.md.D2EP-6xq.lean.js b/assets/config_security-headers.md.D2EP-6xq.lean.js new file mode 100644 index 000000000..8b666f15b --- /dev/null +++ b/assets/config_security-headers.md.D2EP-6xq.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Security Headers","titleTemplate":"NpgsqlRest","description":"Configure HTTP security headers for NpgsqlRest. Protect against XSS, clickjacking, MIME-sniffing, and other web vulnerabilities with X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, and more.","frontmatter":{"outline":[2,3],"title":"Security Headers","titleTemplate":"NpgsqlRest","description":"Configure HTTP security headers for NpgsqlRest. Protect against XSS, clickjacking, MIME-sniffing, and other web vulnerabilities with X-Content-Type-Options, X-Frame-Options, Content-Security-Policy, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest security headers, http security headers, csp, content security policy, x-frame-options, referrer-policy, permissions-policy"}],["meta",{"property":"og:title","content":"NpgsqlRest Security Headers Configuration"}],["meta",{"property":"og:description","content":"Configure HTTP security headers to protect against common web vulnerabilities."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/security-headers.md","filePath":"config/security-headers.md"}'),t={name:"config/security-headers.md"};function l(r,s,p,h,o,d){return n(),a("div",null,s[0]||(s[0]=[e("",59)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_server.md.BCPr-8rO.js b/assets/config_server.md.BCPr-8rO.js new file mode 100644 index 000000000..5b5a01ed6 --- /dev/null +++ b/assets/config_server.md.BCPr-8rO.js @@ -0,0 +1,156 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Server & SSL Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest web server settings. SSL/HTTPS, Kestrel options, HSTS, and HTTPS redirection for secure PostgreSQL REST APIs.","frontmatter":{"outline":[2,3],"title":"Server & SSL Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest web server settings. SSL/HTTPS, Kestrel options, HSTS, and HTTPS redirection for secure PostgreSQL REST APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest ssl, postgresql api https, kestrel configuration, rest api ssl, hsts configuration, secure api server"}],["meta",{"property":"og:title","content":"NpgsqlRest Server & SSL Settings"}],["meta",{"property":"og:description","content":"Configure SSL/HTTPS, Kestrel server options, and security settings for NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/server.md","filePath":"config/server.md"}'),e={name:"config/server.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Server & SSL Settings

    This page covers the web server configuration including SSL/HTTPS settings and Kestrel server options.

    SSL Configuration

    The Ssl section enables HTTPS support and related security features.

    json
    json
    {
    +  "Ssl": {
    +    "Enabled": false,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Kestrel HTTPS configuration. See UseKestrelHttpsConfiguration.
    UseHttpsRedirectionbooltrueRedirect HTTP requests to HTTPS. See UseUseHttpsRedirection.
    UseHstsbooltrueAdd the Strict-Transport-Security header (HSTS). See UseHsts.

    Enabling HTTPS

    To enable HTTPS, set Ssl.Enabled to true and configure your certificates in the Kestrel section:

    json
    json
    {
    +  "Ssl": {
    +    "Enabled": true,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  }
    +}

    HTTPS Redirection

    When UseHttpsRedirection is true, all HTTP requests are automatically redirected to HTTPS. This ensures users always use the secure connection.

    HTTP Strict Transport Security (HSTS)

    When UseHsts is true, the server sends the Strict-Transport-Security header, instructing browsers to only access the site over HTTPS for a specified period.

    WARNING

    HSTS should only be enabled in production environments. It can cause issues during development if you don't have valid certificates configured.

    Kestrel Configuration

    The Kestrel section configures the underlying web server, including endpoints, certificates, and connection limits.

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Http": {
    +        "Url": "http://localhost:5000"
    +      },
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    For complete Kestrel configuration options, see the Microsoft documentation.

    Certificate Configuration

    Kestrel supports multiple ways to configure SSL certificates:

    PFX File

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    PEM/CRT with Key File

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pem",
    +          "KeyPath": "/path/to/private.key",
    +          "Password": "{KEY_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    Certificate Store (Windows)

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Subject": "localhost",
    +          "Store": "My",
    +          "Location": "CurrentUser",
    +          "AllowInvalid": false
    +        }
    +      }
    +    }
    +  }
    +}

    Default Certificate

    You can define a default certificate used by all HTTPS endpoints:

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001"
    +      }
    +    },
    +    "Certificates": {
    +      "Default": {
    +        "Path": "/path/to/certificate.pfx",
    +        "Password": "{CERT_PASSWORD}"
    +      }
    +    }
    +  }
    +}

    Connection Limits

    Configure connection and request limits to protect your server:

    json
    json
    {
    +  "Kestrel": {
    +    "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"
    +    }
    +  }
    +}

    Limits Reference

    SettingDefaultDescription
    MaxConcurrentConnectionsnull (unlimited)Maximum number of open connections.
    MaxConcurrentUpgradedConnectionsnull (unlimited)Maximum number of upgraded connections (e.g., WebSockets).
    MaxRequestBodySize30,000,000 (~28.6 MB)Maximum request body size in bytes.
    MaxRequestBufferSize1,048,576 (1 MB)Maximum size of the request buffer.
    MaxRequestHeaderCount100Maximum number of request headers.
    MaxRequestHeadersTotalSize32,768 (32 KB)Maximum total size of request headers.
    MaxRequestLineSize8,192 (8 KB)Maximum size of the request line.
    MaxResponseBufferSize65,536 (64 KB)Maximum size of the response buffer.
    KeepAliveTimeout2 minutesTimeout for keep-alive connections.
    RequestHeadersTimeout30 secondsTimeout for receiving request headers.

    HTTP/2 Settings

    Configure HTTP/2 specific options:

    json
    json
    {
    +  "Kestrel": {
    +    "Limits": {
    +      "Http2": {
    +        "MaxStreamsPerConnection": 100,
    +        "HeaderTableSize": 4096,
    +        "MaxFrameSize": 16384,
    +        "MaxRequestHeaderFieldSize": 8192,
    +        "InitialConnectionWindowSize": 65535,
    +        "InitialStreamWindowSize": 65535,
    +        "KeepAlivePingDelay": "00:00:30",
    +        "KeepAlivePingTimeout": "00:01:00",
    +        "KeepAlivePingPolicy": "WithActiveRequests"
    +      }
    +    }
    +  }
    +}

    HTTP/3 Settings

    Configure HTTP/3 (QUIC) specific options:

    json
    json
    {
    +  "Kestrel": {
    +    "Limits": {
    +      "Http3": {
    +        "MaxRequestHeaderFieldSize": 8192
    +      }
    +    }
    +  }
    +}

    Additional Kestrel Options

    json
    json
    {
    +  "Kestrel": {
    +    "DisableStringReuse": false,
    +    "AllowAlternateSchemes": false,
    +    "AllowSynchronousIO": false,
    +    "AllowResponseHeaderCompression": true,
    +    "AddServerHeader": true,
    +    "AllowHostHeaderOverride": false
    +  }
    +}
    SettingDefaultDescription
    DisableStringReusefalseDisable string reuse optimization for debugging.
    AllowAlternateSchemesfalseAllow alternate URI schemes in requests.
    AllowSynchronousIOfalseAllow synchronous I/O operations (not recommended).
    AllowResponseHeaderCompressiontrueEnable response header compression for HTTP/2.
    AddServerHeadertrueAdd the Server header to responses.
    AllowHostHeaderOverridefalseAllow the Host header to be overridden.

    Complete Example

    Here's a production-ready configuration with HTTPS enabled:

    json
    json
    {
    +  "Urls": "http://localhost:5000;https://localhost:5001",
    +  "Ssl": {
    +    "Enabled": true,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  },
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Http": {
    +        "Url": "http://0.0.0.0:5000"
    +      },
    +      "Https": {
    +        "Url": "https://0.0.0.0:5001",
    +        "Certificate": {
    +          "Path": "/etc/ssl/certs/myapp.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    },
    +    "Limits": {
    +      "MaxConcurrentConnections": 1000,
    +      "MaxRequestBodySize": 52428800,
    +      "KeepAliveTimeout": "00:02:00",
    +      "RequestHeadersTimeout": "00:00:30"
    +    }
    +  }
    +}

    Next Steps

    `,51)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_server.md.BCPr-8rO.lean.js b/assets/config_server.md.BCPr-8rO.lean.js new file mode 100644 index 000000000..eec68c0ff --- /dev/null +++ b/assets/config_server.md.BCPr-8rO.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Server & SSL Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest web server settings. SSL/HTTPS, Kestrel options, HSTS, and HTTPS redirection for secure PostgreSQL REST APIs.","frontmatter":{"outline":[2,3],"title":"Server & SSL Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest web server settings. SSL/HTTPS, Kestrel options, HSTS, and HTTPS redirection for secure PostgreSQL REST APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest ssl, postgresql api https, kestrel configuration, rest api ssl, hsts configuration, secure api server"}],["meta",{"property":"og:title","content":"NpgsqlRest Server & SSL Settings"}],["meta",{"property":"og:description","content":"Configure SSL/HTTPS, Kestrel server options, and security settings for NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/server.md","filePath":"config/server.md"}'),e={name:"config/server.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",51)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_sql-file-source.md.5_GwGm0C.js b/assets/config_sql-file-source.md.5_GwGm0C.js new file mode 100644 index 000000000..21d65cb5e --- /dev/null +++ b/assets/config_sql-file-source.md.5_GwGm0C.js @@ -0,0 +1,69 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SQL File Source Configuration","titleTemplate":"NpgsqlRest","description":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, error handling, and multi-command result naming.","frontmatter":{"outline":[2,3],"title":"SQL File Source Configuration","titleTemplate":"NpgsqlRest","description":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, error handling, and multi-command result naming.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql file endpoint, sql to rest api, sql file configuration, sql file source plugin"}],["meta",{"property":"og:title","content":"NpgsqlRest SQL File Source Configuration"}],["meta",{"property":"og:description","content":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, and error handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/sql-file-source.md","filePath":"config/sql-file-source.md"}'),t={name:"config/sql-file-source.md"};function l(p,s,h,r,d,o){return e(),a("div",null,s[0]||(s[0]=[n(`

    SQL File Source

    Configuration for generating REST API endpoints from .sql files.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": false,
    +      "FilePattern": "",
    +      "CommentsMode": "OnlyWithHttpTag",
    +      "CommentScope": "All",
    +      "ErrorMode": "Exit",
    +      "ResultPrefix": "result",
    +      "UnnamedSingleColumnSet": true,
    +      "NestedJsonForCompositeTypes": false
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable or disable SQL file source endpoints.
    FilePatternstring""Glob pattern for SQL files. Empty string disables the feature.
    CommentsModestring"OnlyWithHttpTag"How comment annotations affect endpoint creation.
    CommentScopestring"All"Which comments in the SQL file to parse as annotations.
    ErrorModestring"Exit"Behavior when a SQL file fails to parse or describe.
    ResultPrefixstring"result"Prefix for result keys in multi-command JSON responses.
    UnnamedSingleColumnSetbooltrueSingle-column queries return flat arrays instead of object arrays.
    NestedJsonForCompositeTypesboolfalseWhen true, composite type columns are serialized as nested JSON objects. When false (default), composite fields are flattened inline. Can also be enabled per-endpoint with the @nested annotation.

    Enabled

    Enable or disable SQL file source endpoints. Default is false — you must explicitly enable this feature.

    json
    json
    "SqlFileSource": {
    +  "Enabled": true
    +}

    FilePattern

    Glob pattern for locating SQL files. Supports the following wildcards:

    PatternDescription
    *Matches any characters within a single directory level
    **Matches any characters including / (crosses directory boundaries)
    ?Matches a single character

    When ** is present in the pattern, * stops matching / (standard glob semantics). When no ** is present, * matches / for backward compatibility.

    Examples

    json
    json
    // All .sql files in the sql/ directory (non-recursive)
    +"FilePattern": "sql/*.sql"
    +
    +// All .sql files in sql/ and all subdirectories (recursive)
    +"FilePattern": "sql/**/*.sql"
    +
    +// Any .sql file at any depth
    +"FilePattern": "**/*.sql"

    An empty string disables the feature (even if Enabled is true).

    CommentsMode

    Controls how comment annotations affect SQL file endpoint creation.

    ModeDescription
    ParseAllEvery SQL file becomes an endpoint. Comments are parsed as annotations to modify endpoint behavior.
    OnlyWithHttpTagOnly files containing an HTTP annotation become endpoints. (default)
    IgnoreEvery SQL file becomes an endpoint. All comments are ignored.

    TIP

    The default OnlyWithHttpTag means only SQL files with an explicit HTTP annotation (e.g., -- HTTP GET) become endpoints. Use ParseAll if you want every SQL file in the matched pattern to become an endpoint automatically.

    CommentScope

    Controls which comments in the SQL file are parsed as annotations.

    ScopeDescription
    AllParse every comment in the file, regardless of position. (default)
    HeaderOnly parse comments that appear before the first SQL statement.

    Example

    With CommentScope: "Header", only comments before the first statement are parsed:

    sql
    sql
    -- This IS parsed as an annotation
    +-- HTTP GET
    +-- @authorize admin
    +
    +select * from users;
    +
    +-- This is NOT parsed (after first statement)
    +-- @cached

    With CommentScope: "All" (default), all comments are parsed regardless of position.

    ErrorMode

    Controls behavior when a SQL file fails to parse or when PostgreSQL reports an error during the describe phase.

    ModeDescription
    ExitLog the error and exit the process. Fail-fast — catches SQL errors at startup. (default)
    SkipLog the error, skip the file, and continue startup. Tolerates partial failures.

    All SQL file errors are logged at Error level. In Exit mode, a Critical log explains the exit and how to switch to Skip mode.

    A warning is logged when the configured file pattern matches no files.

    Errors caught at startup include:

    • Parse errors (malformed SQL, unclosed strings/quotes)
    • Describe errors (PostgreSQL syntax errors, invalid table/column references)
    • Parameter type conflicts in multi-command files

    TIP

    Use Exit (default) during development to catch SQL errors early. Use Skip in production to tolerate partial failures.

    ResultPrefix

    Prefix for result keys in multi-command JSON responses. Default keys are result1, result2, result3, etc.

    json
    json
    // Default: result1, result2, ...
    +"ResultPrefix": "result"
    +
    +// Custom: data1, data2, ...
    +"ResultPrefix": "data"
    +
    +// Custom: query1, query2, ...
    +"ResultPrefix": "query"

    Individual result keys can be overridden per-file using the @result annotation.

    UnnamedSingleColumnSet

    When true (default), single-column queries return flat arrays instead of arrays of objects. This matches the behavior of PostgreSQL functions returning setof single values.

    sql
    sql
    -- sql/get_names.sql
    +select name from users;

    With UnnamedSingleColumnSet: true (default):

    json
    json
    ["Alice", "Bob", "Charlie"]

    With UnnamedSingleColumnSet: false:

    json
    json
    [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]

    This applies to both single-command endpoints and per-result in multi-command files.

    NestedJsonForCompositeTypes

    Controls how composite type columns are serialized in SQL file endpoint responses.

    Default (flat): Composite fields are spliced inline into the JSON row:

    sql
    sql
    -- sql/get_user_with_address.sql
    +-- HTTP GET
    +-- @param $1 user_id
    +select id, address from users where id = $1;
    +-- where address is: create type address_type as (street text, city text, zip text)
    json
    json
    {"id": 1, "street": "123 Main St", "city": "New York", "zip": "10001"}

    With NestedJsonForCompositeTypes: true or @nested annotation: Composite wrapped under column name:

    json
    json
    {"id": 1, "address": {"street": "123 Main St", "city": "New York", "zip": "10001"}}

    Enable globally for all SQL file endpoints:

    json
    json
    "SqlFileSource": {
    +  "Enabled": true,
    +  "FilePattern": "sql/**/*.sql",
    +  "NestedJsonForCompositeTypes": true
    +}

    Or per-endpoint with the @nested annotation:

    sql
    sql
    -- sql/get_user_with_address.sql
    +-- HTTP GET
    +-- @nested
    +-- @param $1 user_id
    +select id, address from users where id = $1;

    NULL composites are serialized as null in nested mode, or as individual null fields in flat mode.

    TIP

    This setting is also available in Routine Options for function/procedure endpoints. Each endpoint source has its own independent setting.

    LogCommandText

    Controls whether multi-command SQL file endpoints include the full SQL text in debug command logs.

    Default (false): Only the file path and statement count are logged:

    code
    [DBG] -- POST http://127.0.0.1:8080/api/send-message
    +-- $1 text = 'hello'
    +SQL file: sql/send-message.sql (5 statements)

    With LogCommandText: true: The full SQL body of all statements is logged.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "LogCommandText": true
    +    }
    +  }
    +}

    Single-command SQL file endpoints always log the SQL text regardless of this setting. This only applies when LogCommands is true.

    Quick Start Example

    1. Enable the SQL file source in appsettings.json:
    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}
    1. Create a SQL file:
    sql
    sql
    -- sql/get_users.sql
    +-- HTTP GET
    +-- @authorize
    +-- @param $1 active
    +select id, name, email from users where active = $1;
    1. The endpoint is available at GET /api/get-users?active=true
    `,75)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_sql-file-source.md.5_GwGm0C.lean.js b/assets/config_sql-file-source.md.5_GwGm0C.lean.js new file mode 100644 index 000000000..87fab8275 --- /dev/null +++ b/assets/config_sql-file-source.md.5_GwGm0C.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"SQL File Source Configuration","titleTemplate":"NpgsqlRest","description":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, error handling, and multi-command result naming.","frontmatter":{"outline":[2,3],"title":"SQL File Source Configuration","titleTemplate":"NpgsqlRest","description":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, error handling, and multi-command result naming.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql file endpoint, sql to rest api, sql file configuration, sql file source plugin"}],["meta",{"property":"og:title","content":"NpgsqlRest SQL File Source Configuration"}],["meta",{"property":"og:description","content":"Generate REST API endpoints directly from SQL files. Configure file patterns, comment parsing, and error handling."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/sql-file-source.md","filePath":"config/sql-file-source.md"}'),t={name:"config/sql-file-source.md"};function l(p,s,h,r,d,o){return e(),a("div",null,s[0]||(s[0]=[n("",75)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_static-files.md.CxiUeSZr.js b/assets/config_static-files.md.CxiUeSZr.js new file mode 100644 index 000000000..34d7d65aa --- /dev/null +++ b/assets/config_static-files.md.CxiUeSZr.js @@ -0,0 +1,95 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Static Files Configuration","titleTemplate":"NpgsqlRest","description":"Serve static files from NpgsqlRest. Configure file paths, authorization, content parsing, and caching for HTML, CSS, JavaScript, and other assets.","frontmatter":{"outline":[2,3],"title":"Static Files Configuration","titleTemplate":"NpgsqlRest","description":"Serve static files from NpgsqlRest. Configure file paths, authorization, content parsing, and caching for HTML, CSS, JavaScript, and other assets.","head":[["meta",{"name":"keywords","content":"npgsqlrest static files, serve static files postgresql, static file authorization, wwwroot configuration, api static assets"}],["meta",{"property":"og:title","content":"NpgsqlRest Static Files Configuration"}],["meta",{"property":"og:description","content":"Serve static files with authorization and content parsing support from NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/static-files.md","filePath":"config/static-files.md"}'),e={name:"config/static-files.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    Static Files

    Static file serving configuration with authorization and content parsing support.

    Overview

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": false,
    +    "RootPath": "wwwroot",
    +    "AuthorizePaths": [],
    +    "UnauthorizedRedirectPath": "/",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      "Enabled": false,
    +      "AvailableClaims": [],
    +      "AvailableEnvVars": [],
    +      "CacheParsedFile": true,
    +      "Headers": [
    +        "Cache-Control: no-store, no-cache, must-revalidate",
    +        "Pragma: no-cache",
    +        "Expires: 0"
    +      ],
    +      "FilePaths": ["*.html"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable static file serving.
    RootPathstring"wwwroot"Root directory for static files.
    AuthorizePathsarray[]File patterns requiring authorization.
    UnauthorizedRedirectPathstring"/"Redirect path for unauthorized requests.
    UnauthorizedReturnToQueryParameterstring"return_to"Query parameter name for return URL after authentication.
    ParseContentOptionsobject(see below)Content parsing configuration.

    Authorization

    Protect specific static files by requiring authentication:

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": true,
    +    "AuthorizePaths": [
    +      "/admin/*",
    +      "/dashboard/*.html",
    +      "/reports/*"
    +    ],
    +    "UnauthorizedRedirectPath": "/login",
    +    "UnauthorizedReturnToQueryParameter": "return_to"
    +  }
    +}

    Path Patterns

    File paths are relative to RootPath and pattern matching is case-insensitive:

    PatternDescription
    *.htmlAll HTML files in any directory
    /admin/*All files in the admin directory
    /user/profile.htmlSpecific file
    *.jsAll JavaScript files

    Content Parsing

    Parse static files and replace tags with claim values from authenticated users.

    json
    json
    {
    +  "StaticFiles": {
    +    "ParseContentOptions": {
    +      "Enabled": false,
    +      "AvailableClaims": [],
    +      "AvailableEnvVars": [],
    +      "CacheParsedFile": true,
    +      "Headers": [
    +        "Cache-Control: no-store, no-cache, must-revalidate",
    +        "Pragma: no-cache",
    +        "Expires: 0"
    +      ],
    +      "FilePaths": ["*.html"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Parse Content Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable content parsing for static files.
    AvailableClaimsarray | object[]Claim types to parse. Array form (["name"]) replaces missing claims with NULL; object form ({"name":"guest"}) uses the given default when the claim is absent.
    AvailableEnvVarsarray | object[]Environment variable names templated into static content (same {NAME} tags as claims). Array form (["BUILD_LABEL"]) yields the empty string when unset; object form ({"DEMO_FLAG":"false"}) uses the given default. Resolved once at startup. Public — never list a secret.
    CacheParsedFilebooltrueCache parsed file templates in memory. Caching applies to templates before parsing, not final content.
    Headersarray(see below)Response headers for parsed static files. Set to null or empty array to ignore.
    FilePathsarray["*.html"]File patterns to parse.
    AntiforgeryFieldNamestring"antiForgeryFieldName"Variable name for the antiforgery form field name in templates.
    AntiforgeryTokenstring"antiForgeryToken"Variable name for the antiforgery token value in templates.

    Tag Replacement

    When Enabled is true, tags in the format {claimType} are replaced with values from the user's claims:

    html
    html
    <p>Welcome, {name}!</p>
    +<p>Your email: {email}</p>
    +<input type="hidden" name="{antiForgeryFieldName}" value="{antiForgeryToken}" />

    For unauthenticated users or missing claims, values are replaced with NULL.

    You can also give a claim an explicit default with the object form:

    json
    json
    "AvailableClaims": { "name": "guest", "email": "" }

    Environment Variable Injection

    AvailableEnvVars templates app-wide, request-independent environment variable values into static content using the same {NAME} tag syntax. This is useful for Single-Page Apps deployed to Kubernetes: build the bundle once, and inject per-environment values (build label, feature flags, analytics IDs) from pod env vars at boot — no per-environment rebuild.

    json
    json
    {
    +  "StaticFiles": {
    +    "ParseContentOptions": {
    +      "Enabled": true,
    +      "FilePaths": ["/index.html"],
    +      "AvailableClaims": ["user_id", "user_name"],
    +      "AvailableEnvVars": {
    +        "BUILD_LABEL": "local",
    +        "DEMO_FLAG": "false",
    +        "TRACKING_ID": ""
    +      }
    +    }
    +  }
    +}

    Values are substituted as complete, JSON-escaped literals, so the template uses the bare {NAME} token with no surrounding quotes:

    html
    html
    <script>
    +  window.__appConfig = {
    +    userId: {user_id},          // claim → 123 or null
    +    userName: {user_name},      // claim → "alice" or null
    +    buildLabel: {BUILD_LABEL},  // env   → "demo" (or "local" default)
    +    demoMode: {DEMO_FLAG} === "true",
    +    trackingId: {TRACKING_ID}
    +  };
    +</script>

    Guarantees:

    • Two forms. An array of names (["BUILD_LABEL"]) — a missing variable becomes the empty string "". Or an object of name→default pairs ({"DEMO_FLAG":"false"}) — the default is used when the variable is absent.
    • Resolved once at startup. A value change requires a restart (a Kubernetes pod restart re-reads the values).
    • JSON-escaped. An accidental quote or backslash in a value cannot break the JS string. (The relaxed encoder does not escape </>, the same as the claim path — env values are operator-controlled, not untrusted input.)
    • Claims win on collision. If a name is both a user claim and an env var, the per-request claim value takes precedence.

    Public allowlist

    Anything you list in AvailableEnvVars is templated into static content served to any client. Templating a secret (database password, API key, signing token) into index.html leaks it to every user via browser DevTools. Treat the list as a public allowlist, never as a "make this reachable to the app" shortcut. Secrets stay in server-side code paths that read env directly. This is distinct from the server-side Config:ParseEnvironmentVariables mechanism, which substitutes {ENV} tokens into appsettings.json values that never leave the server.

    Default Headers

    The default headers disable caching for parsed content:

    code
    Cache-Control: no-store, no-cache, must-revalidate
    +Pragma: no-cache
    +Expires: 0

    Example Configuration

    Serve static files with protected admin area and content parsing:

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": true,
    +    "RootPath": "wwwroot",
    +    "AuthorizePaths": [
    +      "/admin/*",
    +      "/dashboard/*"
    +    ],
    +    "UnauthorizedRedirectPath": "/login.html",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      "Enabled": true,
    +      "AvailableClaims": ["name", "email", "role"],
    +      "CacheParsedFile": true,
    +      "FilePaths": ["*.html", "*.htm"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Next Steps

    `,41)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_static-files.md.CxiUeSZr.lean.js b/assets/config_static-files.md.CxiUeSZr.lean.js new file mode 100644 index 000000000..037884a7d --- /dev/null +++ b/assets/config_static-files.md.CxiUeSZr.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Static Files Configuration","titleTemplate":"NpgsqlRest","description":"Serve static files from NpgsqlRest. Configure file paths, authorization, content parsing, and caching for HTML, CSS, JavaScript, and other assets.","frontmatter":{"outline":[2,3],"title":"Static Files Configuration","titleTemplate":"NpgsqlRest","description":"Serve static files from NpgsqlRest. Configure file paths, authorization, content parsing, and caching for HTML, CSS, JavaScript, and other assets.","head":[["meta",{"name":"keywords","content":"npgsqlrest static files, serve static files postgresql, static file authorization, wwwroot configuration, api static assets"}],["meta",{"property":"og:title","content":"NpgsqlRest Static Files Configuration"}],["meta",{"property":"og:description","content":"Serve static files with authorization and content parsing support from NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/static-files.md","filePath":"config/static-files.md"}'),e={name:"config/static-files.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",41)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_stats.md.CXkhOJdL.js b/assets/config_stats.md.CXkhOJdL.js new file mode 100644 index 000000000..0827ad8e5 --- /dev/null +++ b/assets/config_stats.md.CXkhOJdL.js @@ -0,0 +1,109 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PostgreSQL Stats","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL statistics endpoints for NpgsqlRest. Monitor routine performance, table statistics, index usage, and active database sessions.","frontmatter":{"outline":[2,3],"title":"PostgreSQL Stats","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL statistics endpoints for NpgsqlRest. Monitor routine performance, table statistics, index usage, and active database sessions.","head":[["meta",{"name":"keywords","content":"npgsqlrest stats, postgresql statistics, pg_stat_user_functions, pg_stat_user_tables, pg_stat_activity, database monitoring"}],["meta",{"property":"og:title","content":"NpgsqlRest PostgreSQL Stats Configuration"}],["meta",{"property":"og:description","content":"Configure PostgreSQL statistics endpoints for monitoring and debugging."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/stats.md","filePath":"config/stats.md"}'),e={name:"config/stats.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t(`

    PostgreSQL Stats

    New in 3.6.0

    PostgreSQL Stats endpoints were added in version 3.6.0.

    Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging. Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.

    Overview

    json
    json
    {
    +  "Stats": {
    +    "Enabled": false,
    +    "CacheDuration": "5 seconds",
    +    "RateLimiterPolicy": null,
    +    "ConnectionName": null,
    +    "RequireAuthorization": false,
    +    "AuthorizedRoles": [],
    +    "OutputFormat": "html",
    +    "SchemaSimilarTo": null,
    +    "RoutinesStatsPath": "/stats/routines",
    +    "TablesStatsPath": "/stats/tables",
    +    "IndexesStatsPath": "/stats/indexes",
    +    "ActivityPath": "/stats/activity"
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable PostgreSQL statistics endpoints.
    CacheDurationstring"5 seconds"Cache stats responses for the specified duration. PostgreSQL interval format. Set to null to disable caching.
    RateLimiterPolicystringnullApply a rate limiter policy to stats endpoints. Specify a policy name from RateLimiterOptions.Policies.
    ConnectionNamestringnullUse a specific named connection for stats queries. When null, uses the default connection.
    RequireAuthorizationboolfalseRequire authentication for stats endpoints.
    AuthorizedRolesarray[]Restrict access to specific roles. Empty array allows any authenticated user (if RequireAuthorization is true).
    OutputFormatstring"html"Output format: "json" or "html". HTML format is Excel-compatible for easy copy-paste. Can be overridden per-request with the ?format= query string parameter.
    SchemaSimilarTostringnullFilter schemas using PostgreSQL SIMILAR TO pattern.
    RoutinesStatsPathstring"/stats/routines"Path for routine (function/procedure) statistics.
    TablesStatsPathstring"/stats/tables"Path for table statistics.
    IndexesStatsPathstring"/stats/indexes"Path for index statistics.
    ActivityPathstring"/stats/activity"Path for current database activity.

    Available Endpoints

    Routines Stats (/stats/routines)

    Returns data from pg_stat_user_functions including:

    • Call counts
    • Total execution time
    • Self execution time

    PostgreSQL Configuration Required

    Routine statistics require track_functions to be enabled in PostgreSQL:

    sql
    sql
    ALTER SYSTEM SET track_functions = 'all';
    +SELECT pg_reload_conf();

    Or set track_functions = 'all' in postgresql.conf and restart/reload.

    Tables Stats (/stats/tables)

    Returns data from pg_stat_user_tables including:

    • Tuple counts (live, dead, inserted, updated, deleted)
    • Table sizes
    • Sequential and index scan counts
    • Last vacuum and analyze timestamps

    Indexes Stats (/stats/indexes)

    Returns data from pg_stat_user_indexes including:

    • Index scan counts
    • Tuples read and fetched
    • Index definitions
    • Index sizes

    Activity (/stats/activity)

    Returns data from pg_stat_activity showing:

    • Active sessions
    • Currently running queries
    • Wait events
    • Session state and duration

    Security Warning

    The activity endpoint shows currently running queries which may contain sensitive data (passwords in plaintext queries, personal information, etc.). Always enable RequireAuthorization in production.

    Output Formats

    HTML Format (Default)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html"
    +  }
    +}

    Returns an HTML table that is Excel-compatible for direct browser copy-paste. Ideal for quick debugging and analysis.

    JSON Format

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "json"
    +  }
    +}

    Returns a JSON array suitable for programmatic access and integration with monitoring tools.

    Per-Request Format Override

    New in 3.8.0

    The format query string override was added in version 3.8.0.

    The configured OutputFormat can be overridden per-request using the format query string parameter. Valid values are html and json:

    code
    GET /stats/routines?format=json
    +GET /stats/tables?format=html

    This allows a single stats deployment to serve both human-readable HTML and machine-readable JSON without changing the server configuration.

    Security

    Require Authentication

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true
    +  }
    +}

    Any authenticated user can access stats endpoints.

    Role-Based Access

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["admin", "dba"]
    +  }
    +}

    Only users with admin or dba roles can access stats endpoints.

    TIP

    Stats endpoints can reveal sensitive information about your database including table sizes, query patterns, and active sessions. Always enable RequireAuthorization in production environments.

    Caching

    Cache responses to reduce database load:

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "CacheDuration": "10 seconds"
    +  }
    +}

    The value uses PostgreSQL interval format:

    • "5 seconds" or "5s"
    • "1 minute" or "1min"
    • "30s"

    Set to null to disable caching (queries the database on every request).

    Query strings are ignored to prevent cache-busting.

    Rate Limiting

    Apply a rate limiter policy to prevent abuse:

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "Policies": {
    +      "stats-limit": {
    +        "PermitLimit": 10,
    +        "Window": "1 minute"
    +      }
    +    }
    +  },
    +  "Stats": {
    +    "Enabled": true,
    +    "RateLimiterPolicy": "stats-limit"
    +  }
    +}

    Schema Filtering

    Filter statistics by schema using PostgreSQL SIMILAR TO pattern:

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "SchemaSimilarTo": "public|myapp%"
    +  }
    +}

    This example includes:

    • The public schema
    • Schemas starting with myapp (e.g., myapp, myapp_v1, myapp_archive)

    When null, all schemas are included.

    Using a Different Connection

    Query stats from a specific database or with different credentials:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary;Database=myapp;Username=app;...",
    +    "Stats": "Host=replica;Database=myapp;Username=readonly;..."
    +  },
    +  "Stats": {
    +    "Enabled": true,
    +    "ConnectionName": "Stats"
    +  }
    +}

    Useful for:

    • Using read-only credentials
    • Querying a read replica
    • Separating stats queries from application traffic

    Custom Paths

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RoutinesStatsPath": "/api/stats/functions",
    +    "TablesStatsPath": "/api/stats/tables",
    +    "IndexesStatsPath": "/api/stats/indexes",
    +    "ActivityPath": "/api/stats/sessions"
    +  }
    +}

    Example Configurations

    Development (Open Access)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html"
    +  }
    +}

    Production (Secured)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["admin"],
    +    "CacheDuration": "30 seconds",
    +    "OutputFormat": "json"
    +  }
    +}

    Monitoring Integration

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["monitoring"],
    +    "OutputFormat": "json",
    +    "CacheDuration": "10 seconds",
    +    "RateLimiterPolicy": "monitoring"
    +  }
    +}

    Limited Schema Access

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "SchemaSimilarTo": "public|api%",
    +    "OutputFormat": "html"
    +  }
    +}

    Next Steps

    `,78)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_stats.md.CXkhOJdL.lean.js b/assets/config_stats.md.CXkhOJdL.lean.js new file mode 100644 index 000000000..ec165a401 --- /dev/null +++ b/assets/config_stats.md.CXkhOJdL.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"PostgreSQL Stats","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL statistics endpoints for NpgsqlRest. Monitor routine performance, table statistics, index usage, and active database sessions.","frontmatter":{"outline":[2,3],"title":"PostgreSQL Stats","titleTemplate":"NpgsqlRest","description":"Configure PostgreSQL statistics endpoints for NpgsqlRest. Monitor routine performance, table statistics, index usage, and active database sessions.","head":[["meta",{"name":"keywords","content":"npgsqlrest stats, postgresql statistics, pg_stat_user_functions, pg_stat_user_tables, pg_stat_activity, database monitoring"}],["meta",{"property":"og:title","content":"NpgsqlRest PostgreSQL Stats Configuration"}],["meta",{"property":"og:description","content":"Configure PostgreSQL statistics endpoints for monitoring and debugging."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/stats.md","filePath":"config/stats.md"}'),e={name:"config/stats.md"};function l(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[t("",78)]))}const u=i(e,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_table-format.md.CKOmZ6yI.js b/assets/config_table-format.md.CKOmZ6yI.js new file mode 100644 index 000000000..2e4d9d169 --- /dev/null +++ b/assets/config_table-format.md.CKOmZ6yI.js @@ -0,0 +1,93 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Table Format Options","titleTemplate":"NpgsqlRest","description":"Configure table format rendering in NpgsqlRest. Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON.","frontmatter":{"outline":[2,3],"title":"Table Format Options","titleTemplate":"NpgsqlRest","description":"Configure table format rendering in NpgsqlRest. Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON.","head":[["meta",{"name":"keywords","content":"npgsqlrest table format, html table rendering, excel download api, spreadsheet export, postgresql report export"}],["meta",{"property":"og:title","content":"NpgsqlRest Table Format Options"}],["meta",{"property":"og:description","content":"Configure HTML table and Excel spreadsheet rendering for PostgreSQL function results."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/table-format.md","filePath":"config/table-format.md"}'),l={name:"config/table-format.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Table Format Options

    Pluggable table format rendering system that allows PostgreSQL function results to be rendered as HTML tables or Excel spreadsheet downloads instead of JSON, controlled by the @table_format annotation.

    Applies to Set-Returning Functions Only

    Table format rendering only applies to routines that return SETOF or TABLE results. Scalar-returning functions are not affected.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": false,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": null,
    +      "ExcelNumericFormat": null
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable table format handlers. When false, @table_format annotations are ignored.

    HTML Table Handler

    Renders results as a styled HTML table suitable for browser viewing and copy-paste into Excel. Activated by the @table_format = html annotation on PostgreSQL functions returning SETOF or TABLE.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    HtmlEnabledbooltrueEnable the HTML table handler.
    HtmlKeystring"html"The key name used to match @table_format = <key> annotation.
    HtmlHeaderstring(CSS style block)Content written before the HTML table. Typically a CSS style block. Set to null to omit.
    HtmlFooterstringnullContent written after the closing HTML table tag. Set to null to omit.

    Example

    sql
    sql
    create function get_report()
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report() is '
    +HTTP GET
    +@table_format = html
    +';

    Equivalent as a SQL file endpoint (sql/get-report.sql):

    sql
    sql
    /*
    +HTTP GET
    +@table_format = html
    +*/
    +select id, name, amount from reports;

    This renders the function result as an HTML table instead of JSON.

    Excel Table Handler

    Renders results as an .xlsx Excel spreadsheet download using the SpreadCheetah library (streaming, AOT-compatible). Activated by the @table_format = excel annotation on PostgreSQL functions returning SETOF or TABLE.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": null,
    +      "ExcelNumericFormat": null
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    ExcelEnabledbooltrueEnable the Excel handler.
    ExcelKeystring"excel"The key name used to match @table_format = <key> annotation.
    ExcelSheetNamestringnullWorksheet name. When null, uses the routine name.
    ExcelDateTimeFormatstringnullExcel Format Code for DateTime cells. When null, uses SpreadCheetah default (yyyy-MM-dd HH:mm:ss). Uses Excel Format Codes (not .NET format strings). Examples: yyyy-mm-dd, dd/mm/yyyy hh:mm, m/d/yy h:mm.
    ExcelNumericFormatstringnullExcel Format Code for numeric cells. When null, uses Excel default (General). Uses Excel Format Codes (not .NET format strings). Examples: #,##0.00, 0.00, #,##0.

    Example

    sql
    sql
    create function get_report()
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +';

    This returns an .xlsx file download instead of JSON.

    Per-Endpoint Overrides

    The download filename and worksheet name can be overridden per-endpoint via custom parameter annotations:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = monthly_report.xlsx
    +@excel_sheet = Report Data
    +';

    These also support dynamic placeholders resolved from function parameters:

    sql
    sql
    create function get_report(_format text, _file_name text, _sheet_name text)
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report(text, text, text) is '
    +HTTP GET
    +@table_format = {_format}
    +@excel_file_name = {_file_name}
    +@excel_sheet = {_sheet_name}
    +';

    Complete Example

    Production configuration with both HTML and Excel handlers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": "yyyy-mm-dd",
    +      "ExcelNumericFormat": "#,##0.00"
    +    }
    +  }
    +}

    Next Steps

    See Also

    `,37)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/config_table-format.md.CKOmZ6yI.lean.js b/assets/config_table-format.md.CKOmZ6yI.lean.js new file mode 100644 index 000000000..d0dd0f65b --- /dev/null +++ b/assets/config_table-format.md.CKOmZ6yI.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Table Format Options","titleTemplate":"NpgsqlRest","description":"Configure table format rendering in NpgsqlRest. Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON.","frontmatter":{"outline":[2,3],"title":"Table Format Options","titleTemplate":"NpgsqlRest","description":"Configure table format rendering in NpgsqlRest. Render PostgreSQL function results as HTML tables or Excel spreadsheet downloads instead of JSON.","head":[["meta",{"name":"keywords","content":"npgsqlrest table format, html table rendering, excel download api, spreadsheet export, postgresql report export"}],["meta",{"property":"og:title","content":"NpgsqlRest Table Format Options"}],["meta",{"property":"og:description","content":"Configure HTML table and Excel spreadsheet rendering for PostgreSQL function results."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/table-format.md","filePath":"config/table-format.md"}'),l={name:"config/table-format.md"};function t(p,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[e("",37)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/config_test-runner.md.D9PY_12B.js b/assets/config_test-runner.md.D9PY_12B.js new file mode 100644 index 000000000..9061176df --- /dev/null +++ b/assets/config_test-runner.md.D9PY_12B.js @@ -0,0 +1,82 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Test Runner Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for the NpgsqlRest SQL test runner (--test). File discovery, filtering, tags, parallelism, setup and teardown steps, test databases, watch mode, coverage, and CI output.","frontmatter":{"outline":[2,3],"title":"Test Runner Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for the NpgsqlRest SQL test runner (--test). File discovery, filtering, tags, parallelism, setup and teardown steps, test databases, watch mode, coverage, and CI output.","head":[["meta",{"name":"keywords","content":"npgsqlrest test runner, sql testing, testrunner configuration, postgresql api testing, test database, junit xml, endpoint coverage"}],["meta",{"property":"og:title","content":"NpgsqlRest Test Runner Configuration"}],["meta",{"property":"og:description","content":"Configuration reference for the NpgsqlRest SQL test runner (--test)."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/test-runner.md","filePath":"config/test-runner.md"}'),n={name:"config/test-runner.md"};function l(p,s,h,o,r,d){return e(),a("div",null,s[0]||(s[0]=[t(`

    Test Runner

    Configuration for the SQL test runner (npgsqlrest --test) — write tests for your endpoints as plain .sql files and run them against the real endpoint pipeline, in-process. For the full walkthrough (test file anatomy, HTTP blocks, assertions, isolation patterns, migrations, Docker, template databases), see the Testing Guide.

    The TestRunner section is a top-level configuration section (a sibling of NpgsqlRest, not nested inside it). It only has an effect when the client runs with the --test argument — it is completely inert during normal server operation.

    sh
    sh
    npgsqlrest ./config.json --test
    +npgsqlrest ./config.json ./test-config.json --test --watch
    +npgsqlrest ./config.json --test --testrunner:filter=login --testrunner:tag=smoke

    Overview

    json
    json
    {
    +  "TestRunner": {
    +    "FilePattern": "",
    +    "Filter": "",
    +    "Tag": "",
    +    "ExcludeTag": "",
    +    "ConnectionName": "",
    +    "MaxParallelism": 0,
    +    "FailFast": false,
    +    "PerTestTimeout": "30s",
    +    "JUnitOutput": null,
    +    "Keep": false,
    +    "DetailedReport": false,
    +    "AllowEmpty": false,
    +    "Coverage": null,
    +    "CoverageThreshold": null,
    +    "LoggerName": "NpgsqlRestTest",
    +    "ResponseTempTable": {
    +      "Name": "_response",
    +      "MultiNamePattern": "_response_{n}",
    +      "Columns": {
    +        "Status": "status",
    +        "Body": "body",
    +        "ContentType": "content_type",
    +        "Headers": "headers",
    +        "IsSuccess": "is_success"
    +      }
    +    },
    +    "Steps": {},
    +    "Setup": [],
    +    "Teardown": []
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    FilePatternstring""Glob selecting test files. Empty disables discovery.
    Filterstring""Narrow the discovered set by path (substring or glob).
    Tagstring""Run only files carrying at least one of these tags.
    ExcludeTagstring""Skip files carrying any of these tags (exclude wins).
    ConnectionNamestring""ConnectionStrings entry to run tests against instead of the main connection.
    MaxParallelismint0Max test files running concurrently. 0 = processor count.
    FailFastboolfalseStop scheduling new tests after the first failure/error.
    PerTestTimeoutstring"30s"Per-test-file timeout ("30s", "5m", seconds, or hh:mm:ss). 0 disables.
    JUnitOutputstringnullOptional path for a JUnit XML report.
    KeepboolfalseSkip Teardown so a failed run's state can be inspected.
    DetailedReportboolfalseRicher console report (passed assertions, failing SQL, notices).
    AllowEmptyboolfalse"No tests discovered" exits 0 instead of 4.
    Coveragebool?nullEndpoint-coverage summary. null = on for full runs, quiet when narrowed; true/false = always/never.
    CoverageThresholdint?nullFail an otherwise-passing run (exit 2) when coverage is below this percentage.
    LoggerNamestring"NpgsqlRestTest"SourceContext name of the runner's log channel.
    ResponseTempTableobjectsee belowNaming and columns of the per-HTTP-block response temp table.
    Stepsobject{}Named, reusable steps for Setup/Teardown and per-file annotations.
    Setuparray[]Run-once setup steps, before endpoint discovery, in written order.
    Teardownarray[]Run-once teardown steps, always (best-effort), in written order.

    Every setting is also available as a command-line override with the standard configuration syntax: --testrunner:filter=login, --testrunner:coveragethreshold=100, and so on.

    FilePattern

    Glob (same engine as SqlFileSource.FilePattern) selecting the test files. Empty disables discovery — --test then exits with code 4 (no tests found).

    Two common layouts:

    json
    json
    // Co-located: app.sql (endpoint) next to app.test.sql (test), same tree
    +{ "TestRunner": { "FilePattern": "./sql/**/*.test.sql" } }
    json
    json
    // Separate tree: endpoints in ./sql, tests in ./tests
    +{ "TestRunner": { "FilePattern": "./tests/**/*.test.sql" } }

    The co-located layout works because SqlFileSource.SkipPattern defaults to "*.test.sql", so test files are never exposed as endpoints.

    Filter

    The fast path for iterating on one test:

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:filter=login

    Matched against each file's cwd-relative path: a value without wildcards is a substring match; with wildcards it uses the same glob engine as FilePattern. Empty runs everything discovered.

    Tag and ExcludeTag

    Tag filtering (comma- or whitespace-separated lists, case-insensitive). A test file declares its tags with a header annotation:

    sql
    sql
    -- @tag smoke, auth

    Tag runs only files carrying at least one of the listed tags; ExcludeTag skips files carrying any of them — exclude wins when both match. Composes with Filter (both must pass).

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Tags declared in an included annotation profile (via \\i/\\ir in the file header) count as if written in the file. See the TEST TAG annotation.

    ConnectionName

    A ConnectionStrings entry to run the tests against instead of the app's main connection. In test mode it becomes the connection used for endpoint type-checking (Describe) and execution, so it can point at a dedicated test database that a Setup step creates first — it does not need to exist at startup.

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "...Database=app_db...",
    +    "Admin": "...Database=postgres...",
    +    "Test": "...Database=app_test_{rnd5}..."
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test"
    +  }
    +}

    Random tokens

    {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once and stable for the whole run, usable in any connection string or Setup/Teardown SQL — so app_test_{rnd5} resolves to the same name in the connection string, the create database step, and the drop database step. Need several distinct tokens of the same length? Indexed instances {rnd5_1}{rnd5_9} are each independent.

    MaxParallelism

    Maximum number of test files running concurrently; 0 means processor count. Each test file runs on its own non-pooled physical connection (fresh session — no temp-table, GUC, or prepared-statement carryover), so parallel files cannot see each other's uncommitted state.

    FailFast

    Stop scheduling new tests after the first failure or error. In-flight tests still finish and are reported.

    PerTestTimeout

    Per-test-file timeout. Accepts "30s", "5m", "1h", a plain number of seconds, or "hh:mm:ss". 0 disables. A timed-out file is reported as an error (exit code 2).

    JUnitOutput

    Optional path to also write a JUnit XML report — the standard CI artifact (GitHub Actions, GitLab, Jenkins all consume it). Console output is always printed regardless. Assertion names (the second column of a boolean-SELECT assertion) become the JUnit test-case names.

    json
    json
    { "TestRunner": { "JUnitOutput": "./test-results.xml" } }

    Keep

    Skip Teardown so a failed run's state (the test database, fixture rows) can be inspected. Remember to clean up manually — with {rnd}-named databases each kept run leaves one behind.

    DetailedReport

    Richer console report: lists passed assertions (), prints the full failing SQL statement, and shows captured raise notice output for passing tests too (notices always show under failing tests).

    This shapes the report only — for diagnostic logging of every executed query and HTTP invocation, raise the runner's log channel instead:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    AllowEmpty

    Treat "no tests discovered" as success (exit 0) instead of exit 4. Useful for repos where a test tree may legitimately be empty.

    Watch mode

    Watch mode is enabled by the top-level Watch configuration section ("Watch": { "Enabled": true }) or its CLI shorthand --watch — it is not a TestRunner setting, because the same section drives both watch flavors (test watch with --test, server watch without). In test mode: run everything once, then re-run on changes until Ctrl+C.

    • A changed test file re-runs alone (Filter still applies).
    • A changed endpoint file (matching SqlFileSource.FilePattern) triggers an in-process endpoint rebuild — sources re-read, re-described, endpoint registry swapped atomically — followed by a full rerun, with the endpoint delta reported (+ POST /api/new, - GET /api/x (endpoint dropped — check its SQL file for errors)). To make this safe, watch mode forces SqlFileSource.ErrorMode from Exit to Skip; non-watch --test keeps Exit for CI.
    • A database routine change (create/replace/drop/comment on functions or procedures, detected by polling the routine discovery query — Watch:DatabasePollingInterval, default 2s) rebuilds endpoints and re-runs everything (— change detected (database) —).
    • Any other changed .sql under the test tree (an included fixture or profile, whose dependents are unknown) re-runs everything.

    Teardown runs once, on exit — synchronously inside the SIGINT/SIGTERM handler, so the test database is dropped even when the watch process is stopped through a wrapper like bun run or npm run; a second Ctrl+C force-quits. A graceful stop exits 0 regardless of test outcomes — watch is not for CI gating.

    Coverage and CoverageThreshold

    Endpoint-coverage summary after the run: exercised N of M testable endpoints, plus the exact list of untested ones:

    code
    19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 2/2 (100%)

    Coverage is tri-state:

    ValueBehavior
    null (default)Report after full runs; stay quiet when the run is narrowed by Filter/Tag (a deliberately partial run would just nag).
    trueAlways report, including narrowed runs.
    falseNever report.

    CoverageThreshold (0–100) turns it into a CI gate — it always reports, regardless of Coverage or narrowing: an otherwise-passing run below the threshold exits 2. Set it to 100 and forgetting to write a test for a new endpoint fails the build, naming the endpoint.

    "Covered" means invoked at least once by a test — execution, not assertion depth (the same semantics as code coverage). Endpoint kinds the runner rejects (SSE, upload, login/logout, outbound proxy) are excluded from the ratio and counted separately.

    LoggerName

    SourceContext name of the runner's own log channel (default "NpgsqlRestTest"); set its level independently under Log:MinimalLevels. Discovery and parsing log at Debug, each executed query and HTTP invocation at Verbose, raise notice output by its severity.

    ResponseTempTable

    Each HTTP block's response is captured into its own temp table on the test's connection, created fresh (no IF NOT EXISTS — a duplicate name fails the test loudly).

    SettingDefaultDescription
    Name"_response"Table name when the file has one HTTP block.
    MultiNamePattern"_response_{n}"Name pattern when the file has 2+ blocks; {n} is the 1-based block ordinal.
    DebugTablenullDebugging aid: also mirror every response into this permanent table (see below).
    Columns.Status"status"int — HTTP status code.
    Columns.Body"body"text — response body (cast to ::jsonb to assert on JSON).
    Columns.ContentType"content_type"text — response content type.
    Columns.Headers"headers"jsonb — response headers.
    Columns.IsSuccess"is_success"boolean — true for 2xx.

    A null or empty column name omits that column. A per-block override is available with the # @response <name> directive inside the HTTP block.

    DebugTable — inspect responses after the run

    Temp tables vanish with the test's rollback and connection, so they cannot be examined afterwards — and re-issuing the request from an .http file cannot reproduce a response that depended on the test's uncommitted fixtures. Set DebugTable (e.g. "_responses_debug") and every captured response is also mirrored into a permanent table, written on a separate autocommit connection — immune to rollbacks, recreated at the start of every run (it always holds the last run):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:responsetemptable:debugtable=_responses_debug

    One table covers everything — each HTTP block adds one row: captured_at, test_file, block (that block's response-table name: _response, a _response_{n} ordinal, or the # @response name), method, path, status, body, content_type, headers, is_success. After the run, open a query editor:

    sql
    sql
    select test_file, block, status, body::jsonb
    +from _responses_debug
    +where status >= 400;

    The temp-table semantics are unchanged; enabling it prints a loud warning — it is a debugging aid, do not enable in CI. In the fresh-test-database workflow combine it with Keep, or teardown drops the database (and the mirror with it).

    Steps

    Named, reusable steps (name → step object, same shape as Setup/Teardown entries). Reference them by name in Setup/Teardown, or from an individual test file's header annotations (-- @setup, -- @teardown):

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase": { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up", "WorkingDirectory": "./db" },
    +      "DropDatabase": { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    }
    +  }
    +}

    A step object is one of:

    ShapeRuns
    { "Sql": "..." }SQL text, statement by statement, on the test connection — or on any named ConnectionStrings entry via "ConnectionName".
    { "SqlFile": "..." }A SQL file, statement by statement, same connection rules.
    { "Command": "...", "WorkingDirectory": "..." }An OS shell command — migration runners, Docker, pg_dump, anything.

    Referencing an unknown step name is a configuration error (exit 3).

    Every step also has an Enabled flag (default true): a disabled step is simply ignored wherever it is referenced — skipped with a debug log line, never an error. The default configuration ships disabled example steps covering the typical scenarios (create/drop a {rnd}-named test database, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) — copy one into your config, adjust names and connections, and flip Enabled to true instead of typing it from scratch:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    }
    +  }
    +}

    Setup and Teardown

    Run-once lifecycle around the whole run. Setup runs before endpoint discovery — which is what makes the create-a-fresh-database workflow possible: by the time endpoints are described against ConnectionName, the database exists and is migrated. Steps run in the exact order written; each entry is a step name from Steps or an inline step object.

    json
    json
    {
    +  "TestRunner": {
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    Teardown runs always (best-effort), even when the run fails — and it is guaranteed beyond the happy path: from Setup onward the runner intercepts SIGINT (Ctrl+C) and SIGTERM and runs Teardown synchronously in the signal handler, and a process-exit hook covers hard exits (e.g. a broken endpoint SQL file under SqlFileSource.ErrorMode: Exit). Keep: true skips Teardown deliberately.

    Exit codes

    CodeMeaning
    0All tests passed (or a graceful watch-mode stop).
    1At least one assertion failed.
    2At least one error (SQL error, timeout, unsupported endpoint, an interrupted run, or a failed coverage gate).
    3Setup or configuration error.
    4No test files found (AllowEmpty: true turns this into 0).
    • Testing Guide — the full walkthrough with scenarios: transactions, fixtures, test databases, template clones, migrations, Docker, CI
    • Test file annotations@setup, @teardown, @connection, @tag
    • SQL File Source — endpoint files and SkipPattern
    • LoggingLog:MinimalLevels, including "Off" to mute a channel
    `,85)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_test-runner.md.D9PY_12B.lean.js b/assets/config_test-runner.md.D9PY_12B.lean.js new file mode 100644 index 000000000..43b455fa3 --- /dev/null +++ b/assets/config_test-runner.md.D9PY_12B.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Test Runner Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for the NpgsqlRest SQL test runner (--test). File discovery, filtering, tags, parallelism, setup and teardown steps, test databases, watch mode, coverage, and CI output.","frontmatter":{"outline":[2,3],"title":"Test Runner Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for the NpgsqlRest SQL test runner (--test). File discovery, filtering, tags, parallelism, setup and teardown steps, test databases, watch mode, coverage, and CI output.","head":[["meta",{"name":"keywords","content":"npgsqlrest test runner, sql testing, testrunner configuration, postgresql api testing, test database, junit xml, endpoint coverage"}],["meta",{"property":"og:title","content":"NpgsqlRest Test Runner Configuration"}],["meta",{"property":"og:description","content":"Configuration reference for the NpgsqlRest SQL test runner (--test)."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/test-runner.md","filePath":"config/test-runner.md"}'),n={name:"config/test-runner.md"};function l(p,s,h,o,r,d){return e(),a("div",null,s[0]||(s[0]=[t("",85)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_thread-pool.md.CQCaprkR.js b/assets/config_thread-pool.md.CQCaprkR.js new file mode 100644 index 000000000..0b7ecc672 --- /dev/null +++ b/assets/config_thread-pool.md.CQCaprkR.js @@ -0,0 +1,15 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Thread Pool Configuration","titleTemplate":"NpgsqlRest","description":"Configure .NET thread pool settings for NpgsqlRest performance. Worker threads, completion ports, and async optimization for high-throughput APIs.","frontmatter":{"outline":[2,3],"title":"Thread Pool Configuration","titleTemplate":"NpgsqlRest","description":"Configure .NET thread pool settings for NpgsqlRest performance. Worker threads, completion ports, and async optimization for high-throughput APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest thread pool, api performance tuning, worker threads configuration, async optimization, high throughput api"}],["meta",{"property":"og:title","content":"NpgsqlRest Thread Pool Configuration"}],["meta",{"property":"og:description","content":"Configure thread pool settings for optimal API performance and throughput."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/thread-pool.md","filePath":"config/thread-pool.md"}'),n={name:"config/thread-pool.md"};function l(r,s,o,h,p,d){return e(),a("div",null,s[0]||(s[0]=[t(`

    Thread Pool

    Thread pool configuration settings for optimizing application performance.

    Overview

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": null,
    +    "MinCompletionPortThreads": null,
    +    "MaxWorkerThreads": null,
    +    "MaxCompletionPortThreads": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    MinWorkerThreadsintnullMinimum number of worker threads in the thread pool. Uses system defaults if null.
    MinCompletionPortThreadsintnullMinimum number of completion port threads. Uses system defaults if null.
    MaxWorkerThreadsintnullMaximum number of worker threads in the thread pool. Uses system defaults if null.
    MaxCompletionPortThreadsintnullMaximum number of completion port threads. Uses system defaults if null.

    Worker Threads vs Completion Port Threads

    • Worker threads execute CPU-bound work and synchronous operations
    • Completion port threads handle asynchronous I/O operations (database queries, HTTP requests)

    When to Configure

    The default thread pool settings work well for most scenarios. Consider adjusting when:

    • High-concurrency workloads cause thread pool starvation
    • Application experiences delays during burst traffic
    • Profiling indicates thread pool bottlenecks

    Example Configuration

    High-concurrency configuration:

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": 100,
    +    "MinCompletionPortThreads": 100,
    +    "MaxWorkerThreads": 500,
    +    "MaxCompletionPortThreads": 500
    +  }
    +}

    WARNING

    Setting thread pool values too high can increase memory usage and context switching overhead. Test thoroughly before deploying to production.

    Next Steps

    `,19)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_thread-pool.md.CQCaprkR.lean.js b/assets/config_thread-pool.md.CQCaprkR.lean.js new file mode 100644 index 000000000..26ca2397d --- /dev/null +++ b/assets/config_thread-pool.md.CQCaprkR.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Thread Pool Configuration","titleTemplate":"NpgsqlRest","description":"Configure .NET thread pool settings for NpgsqlRest performance. Worker threads, completion ports, and async optimization for high-throughput APIs.","frontmatter":{"outline":[2,3],"title":"Thread Pool Configuration","titleTemplate":"NpgsqlRest","description":"Configure .NET thread pool settings for NpgsqlRest performance. Worker threads, completion ports, and async optimization for high-throughput APIs.","head":[["meta",{"name":"keywords","content":"npgsqlrest thread pool, api performance tuning, worker threads configuration, async optimization, high throughput api"}],["meta",{"property":"og:title","content":"NpgsqlRest Thread Pool Configuration"}],["meta",{"property":"og:description","content":"Configure thread pool settings for optimal API performance and throughput."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/thread-pool.md","filePath":"config/thread-pool.md"}'),n={name:"config/thread-pool.md"};function l(r,s,o,h,p,d){return e(),a("div",null,s[0]||(s[0]=[t("",19)]))}const u=i(n,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/config_top-level.md.F9S_nf2B.js b/assets/config_top-level.md.F9S_nf2B.js new file mode 100644 index 000000000..0ea153068 --- /dev/null +++ b/assets/config_top-level.md.F9S_nf2B.js @@ -0,0 +1,12 @@ +import{_ as t,c as e,o as i,a5 as a}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Top-Level Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest application identity, server URLs, and startup behavior. Application name, environment, and binding configuration.","frontmatter":{"outline":[2,3],"title":"Top-Level Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest application identity, server URLs, and startup behavior. Application name, environment, and binding configuration.","head":[["meta",{"name":"keywords","content":"npgsqlrest settings, application name configuration, server url binding, startup configuration, environment settings"}],["meta",{"property":"og:title","content":"NpgsqlRest Top-Level Settings"}],["meta",{"property":"og:description","content":"Configure application identity, server URLs, and startup behavior for NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/top-level.md","filePath":"config/top-level.md"}'),n={name:"config/top-level.md"};function l(p,s,o,r,d,h){return i(),e("div",null,s[0]||(s[0]=[a(`

    Top-Level Settings

    These settings configure the application identity, server binding, and configuration behavior.

    Application Settings

    json
    json
    {
    +  "ApplicationName": null,
    +  "EnvironmentName": "Production",
    +  "Urls": "http://localhost:8080",
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}"
    +}

    Settings Reference

    SettingTypeDefaultDescription
    ApplicationNamestringnullApplication identifier. Defaults to the top-level directory name if not set.
    EnvironmentNamestring"Production"Environment designation (Development, Staging, Production).
    Urlsstring"http://localhost:8080"Server listening URLs. Separate multiple URLs with semicolons.
    StartupMessagestring(see below)Message displayed on startup. Supports placeholders.

    Default StartupMessage: "Started in {time}, listening on {urls}, version {version}"

    Urls Configuration

    The Urls setting accepts multiple URLs separated by semicolons:

    json
    json
    {
    +  "Urls": "http://localhost:8080;https://localhost:8443"
    +}

    To listen on all interfaces:

    json
    json
    {
    +  "Urls": "http://0.0.0.0:8080;https://0.0.0.0:8443"
    +}

    Startup Message Placeholders

    Customize the startup message with these placeholders:

    PlaceholderDescription
    {time}Startup time
    {urls}Listening URLs
    {version}Application version
    {environment}Environment name (from EnvironmentName)
    {application}Application name (from ApplicationName)

    Example:

    json
    json
    {
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}, env: {environment}"
    +}

    Next Steps

    `,21)]))}const k=t(n,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/config_top-level.md.F9S_nf2B.lean.js b/assets/config_top-level.md.F9S_nf2B.lean.js new file mode 100644 index 000000000..cbd710aba --- /dev/null +++ b/assets/config_top-level.md.F9S_nf2B.lean.js @@ -0,0 +1 @@ +import{_ as t,c as e,o as i,a5 as a}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Top-Level Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest application identity, server URLs, and startup behavior. Application name, environment, and binding configuration.","frontmatter":{"outline":[2,3],"title":"Top-Level Settings","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest application identity, server URLs, and startup behavior. Application name, environment, and binding configuration.","head":[["meta",{"name":"keywords","content":"npgsqlrest settings, application name configuration, server url binding, startup configuration, environment settings"}],["meta",{"property":"og:title","content":"NpgsqlRest Top-Level Settings"}],["meta",{"property":"og:description","content":"Configure application identity, server URLs, and startup behavior for NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/top-level.md","filePath":"config/top-level.md"}'),n={name:"config/top-level.md"};function l(p,s,o,r,d,h){return i(),e("div",null,s[0]||(s[0]=[a("",21)]))}const k=t(n,[["render",l]]);export{u as __pageData,k as default}; diff --git a/assets/config_uploads.md.DoJGjd4G.js b/assets/config_uploads.md.DoJGjd4G.js new file mode 100644 index 000000000..aa196fd69 --- /dev/null +++ b/assets/config_uploads.md.DoJGjd4G.js @@ -0,0 +1,134 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Upload Options","titleTemplate":"NpgsqlRest","description":"Configure file uploads in NpgsqlRest. Handle uploads via PostgreSQL Large Objects, file system storage, CSV import, and Excel file processing.","frontmatter":{"outline":[2,3],"title":"Upload Options","titleTemplate":"NpgsqlRest","description":"Configure file uploads in NpgsqlRest. Handle uploads via PostgreSQL Large Objects, file system storage, CSV import, and Excel file processing.","head":[["meta",{"name":"keywords","content":"npgsqlrest upload, postgresql file upload, large objects api, csv import postgresql, excel upload api, file upload rest api"}],["meta",{"property":"og:title","content":"NpgsqlRest Upload Options"}],["meta",{"property":"og:description","content":"Configure file uploads via PostgreSQL Large Objects, file system, CSV, and Excel handlers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/uploads.md","filePath":"config/uploads.md"}'),l={name:"config/uploads.md"};function e(p,s,h,k,d,r){return t(),a("div",null,s[0]||(s[0]=[n(`

    Upload Options

    File upload configuration for handling uploads via PostgreSQL Large Objects, file system, CSV, and Excel handlers.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": false,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      "DefaultUploadHandler": "large_object",
    +      "UseDefaultUploadMetadataParameter": false,
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      "UseDefaultUploadMetadataContextKey": false,
    +      "DefaultUploadMetadataContextKey": "request.upload_metadata",
    +      "UploadHandlers": {
    +        "StopAfterFirstSuccess": false,
    +        "IncludedMimeTypePatterns": null,
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 8192,
    +        "TextTestBufferSize": 4096,
    +        "TextNonPrintableThreshold": 5,
    +        "AllowedImageTypes": "jpeg, png, gif, bmp, tiff, webp",
    +        "RowCommandUserClaimsKey": "claims",
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false,
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false,
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)",
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null,
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable file upload handling.
    LogUploadEventbooltrueLog upload events.
    LogUploadParametersboolfalseLog upload parameters (file names, sizes, etc.).
    DefaultUploadHandlerstring"large_object"Default handler when not specified.
    UseDefaultUploadMetadataParameterboolfalsePass upload metadata via parameter.
    DefaultUploadMetadataParameterNamestring"_upload_metadata"Parameter name for upload metadata JSON.
    UseDefaultUploadMetadataContextKeyboolfalsePass upload metadata via context key.
    DefaultUploadMetadataContextKeystring"request.upload_metadata"Context key for upload metadata JSON.

    Upload Handlers Common Settings

    Settings that apply to all upload handlers.

    SettingTypeDefaultDescription
    StopAfterFirstSuccessboolfalseStop processing after first successful handler.
    IncludedMimeTypePatternsstringnullCSV of MIME type patterns to include. null to allow all.
    ExcludedMimeTypePatternsstringnullCSV of MIME type patterns to exclude. null to exclude none.
    BufferSizeint8192Buffer size in bytes for file_system and large_object handlers (8 KB).
    TextTestBufferSizeint4096Buffer sample size for testing textual content (4 KB).
    TextNonPrintableThresholdint5Maximum non-printable characters allowed in text buffer.
    AllowedImageTypesstring"jpeg, png, gif, bmp, tiff, webp"Comma-separated list of allowed image types.
    RowCommandUserClaimsKeystring"claims"For row-processing handlers (CSV, Excel), includes the authenticated user's claims in the row metadata JSON ($4) under this key. Set to null or "" to disable. Example: with "claims", access in SQL via (_meta->'claims'->>'name_identifier').

    Large Object Handler

    Uploads files using PostgreSQL Large Objects API.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    LargeObjectEnabledbooltrueEnable Large Object upload handler.
    LargeObjectKeystring"large_object"Handler key name.
    LargeObjectCheckTextboolfalseValidate uploaded content is text.
    LargeObjectCheckImageboolfalseValidate uploaded content is an allowed image type.

    File System Handler

    Uploads files to the server file system.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    FileSystemEnabledbooltrueEnable file system upload handler.
    FileSystemKeystring"file_system"Handler key name.
    FileSystemPathstring"/tmp/uploads"Directory path for uploaded files.
    FileSystemUseUniqueFileNamebooltrueGenerate unique file names to prevent overwrites.
    FileSystemCreatePathIfNotExistsbooltrueCreate upload directory if it doesn't exist.
    FileSystemCheckTextboolfalseValidate uploaded content is text.
    FileSystemCheckImageboolfalseValidate uploaded content is an allowed image type.

    CSV Upload Handler

    Uploads CSV files and processes rows via a PostgreSQL command.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    CsvUploadEnabledbooltrueEnable CSV upload handler.
    CsvUploadKeystring"csv"Handler key name.
    CsvUploadCheckFileStatusbooltrueCheck file status before processing.
    CsvUploadDelimiterCharsstring","CSV field delimiter character(s).
    CsvUploadHasFieldsEnclosedInQuotesbooltrueFields may be enclosed in quotes.
    CsvUploadSetWhiteSpaceToNullbooltrueConvert whitespace-only values to NULL.
    CsvUploadRowCommandstring"call process_csv_row($1,$2,$3,$4)"PostgreSQL command to process each row.

    CSV Row Command Parameters

    ParameterTypeDescription
    $1intRow index (1-based).
    $2text[]Parsed values as text array.
    $3textResult of previous row command.
    $4jsonUpload metadata JSON. Includes the user's claims under the RowCommandUserClaimsKey key (default "claims") when set.

    Excel Upload Handler

    Uploads Excel files and processes rows via a PostgreSQL command.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null,
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    ExcelUploadEnabledbooltrueEnable Excel upload handler.
    ExcelKeystring"excel"Handler key name.
    ExcelSheetNamestringnullSheet name to process. null for first available sheet.
    ExcelAllSheetsboolfalseProcess all sheets in the workbook.
    ExcelTimeFormatstring"HH:mm:ss"Format for time values.
    ExcelDateFormatstring"yyyy-MM-dd"Format for date values.
    ExcelDateTimeFormatstring"yyyy-MM-dd HH:mm:ss"Format for datetime values.
    ExcelRowDataAsJsonboolfalsePass row data as JSON instead of text array.
    ExcelUploadRowCommandstring"call process_excel_row($1,$2,$3,$4)"PostgreSQL command to process each row.

    Excel Row Command Parameters

    ParameterTypeDescription
    $1intRow index (1-based).
    $2text[] or jsonParsed values as text array (or JSON if ExcelRowDataAsJson is true).
    $3textResult of previous row command.
    $4jsonUpload metadata JSON. Includes the user's claims under the RowCommandUserClaimsKey key (default "claims") when set.

    Complete Example

    Production configuration with file system and CSV uploads:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": true,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      "DefaultUploadHandler": "file_system",
    +      "UseDefaultUploadMetadataParameter": true,
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      "UploadHandlers": {
    +        "StopAfterFirstSuccess": true,
    +        "IncludedMimeTypePatterns": "image/*,text/*,application/pdf",
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 16384,
    +        "LargeObjectEnabled": false,
    +        "FileSystemEnabled": true,
    +        "FileSystemPath": "/var/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckImage": true,
    +        "CsvUploadEnabled": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadRowCommand": "call import_csv_row($1,$2,$3,$4)",
    +        "ExcelUploadEnabled": true,
    +        "ExcelUploadRowCommand": "call import_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}

    Blog Posts

    Next Steps

    See Also

    • UPLOAD - Enable file upload on endpoints
    `,40)]))}const F=i(l,[["render",e]]);export{c as __pageData,F as default}; diff --git a/assets/config_uploads.md.DoJGjd4G.lean.js b/assets/config_uploads.md.DoJGjd4G.lean.js new file mode 100644 index 000000000..1e10e5ab4 --- /dev/null +++ b/assets/config_uploads.md.DoJGjd4G.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Upload Options","titleTemplate":"NpgsqlRest","description":"Configure file uploads in NpgsqlRest. Handle uploads via PostgreSQL Large Objects, file system storage, CSV import, and Excel file processing.","frontmatter":{"outline":[2,3],"title":"Upload Options","titleTemplate":"NpgsqlRest","description":"Configure file uploads in NpgsqlRest. Handle uploads via PostgreSQL Large Objects, file system storage, CSV import, and Excel file processing.","head":[["meta",{"name":"keywords","content":"npgsqlrest upload, postgresql file upload, large objects api, csv import postgresql, excel upload api, file upload rest api"}],["meta",{"property":"og:title","content":"NpgsqlRest Upload Options"}],["meta",{"property":"og:description","content":"Configure file uploads via PostgreSQL Large Objects, file system, CSV, and Excel handlers."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/uploads.md","filePath":"config/uploads.md"}'),l={name:"config/uploads.md"};function e(p,s,h,k,d,r){return t(),a("div",null,s[0]||(s[0]=[n("",40)]))}const F=i(l,[["render",e]]);export{c as __pageData,F as default}; diff --git a/assets/config_validation.md.lHNUsfic.js b/assets/config_validation.md.lHNUsfic.js new file mode 100644 index 000000000..8c42fe58c --- /dev/null +++ b/assets/config_validation.md.lHNUsfic.js @@ -0,0 +1,184 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Validation Options Configuration","titleTemplate":"NpgsqlRest","description":"Configure parameter validation for NpgsqlRest APIs. Define validation rules for endpoint parameters before database execution using NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength validators.","frontmatter":{"outline":[2,3],"title":"Validation Options Configuration","titleTemplate":"NpgsqlRest","description":"Configure parameter validation for NpgsqlRest APIs. Define validation rules for endpoint parameters before database execution using NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength validators.","head":[["meta",{"name":"keywords","content":"npgsqlrest validation, api parameter validation, postgresql api validation, request validation, input validation configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Validation Options Configuration"}],["meta",{"property":"og:description","content":"Configure parameter validation rules for NpgsqlRest endpoints before database execution."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/validation.md","filePath":"config/validation.md"}'),t={name:"config/validation.md"};function p(e,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[l(`

    Validation Options

    Parameter validation configuration for validating endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

    Overview

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable parameter validation.
    RulesobjectSee Default RulesNamed validation rules that can be referenced in comment annotations.

    Validation Types

    Six validation types are available:

    TypeDescription
    NotNullParameter value cannot be null (DBNull.Value)
    NotEmptyParameter value cannot be an empty string (null values pass)
    RequiredCombines NotNull and NotEmpty - value cannot be null or empty
    RegexParameter value must match the specified regular expression pattern
    MinLengthParameter value must have at least N characters
    MaxLengthParameter value must have at most N characters

    Rule Properties

    Each rule can have the following properties:

    PropertyRequiredDescription
    TypeYesValidation type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    PatternFor RegexRegular expression pattern to match against
    MinLengthFor MinLengthMinimum number of characters required
    MaxLengthFor MaxLengthMaximum number of characters allowed
    MessageNoError message with placeholders: {0}=original parameter name, {1}=converted parameter name, {2}=rule name. Default: "Validation failed for parameter '{0}'"
    StatusCodeNoHTTP status code returned on validation failure. Default: 400

    Default Rules

    Four validation rules are available by default:

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\\\s]+@[^@\\\\s]+\\\\.[^@\\\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Adding Custom Rules

    You can add custom validation rules to the Rules object. The key becomes the rule name used in the validate annotation.

    Regex Pattern Rule

    json
    json
    {
    +  "ValidationOptions": {
    +    "Rules": {
    +      "phone": {
    +        "Type": "Regex",
    +        "Pattern": "^\\\\+?[1-9]\\\\d{1,14}$",
    +        "Message": "Parameter '{0}' must be a valid phone number",
    +        "StatusCode": 400
    +      },
    +      "username": {
    +        "Type": "Regex",
    +        "Pattern": "^[a-zA-Z0-9_]{3,20}$",
    +        "Message": "Parameter '{0}' must be 3-20 alphanumeric characters or underscores",
    +        "StatusCode": 400
    +      },
    +      "uuid": {
    +        "Type": "Regex",
    +        "Pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
    +        "Message": "Parameter '{0}' must be a valid UUID",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Length Validation Rules

    json
    json
    {
    +  "ValidationOptions": {
    +    "Rules": {
    +      "password_length": {
    +        "Type": "MinLength",
    +        "MinLength": 8,
    +        "Message": "Parameter '{0}' must be at least 8 characters",
    +        "StatusCode": 400
    +      },
    +      "short_text": {
    +        "Type": "MaxLength",
    +        "MaxLength": 100,
    +        "Message": "Parameter '{0}' must not exceed 100 characters",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Complete Example

    Configuration with multiple custom validation rules:

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\\\s]+@[^@\\\\s]+\\\\.[^@\\\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      },
    +      "phone": {
    +        "Type": "Regex",
    +        "Pattern": "^\\\\+?[1-9]\\\\d{1,14}$",
    +        "Message": "Parameter '{0}' must be a valid phone number (E.164 format)",
    +        "StatusCode": 400
    +      },
    +      "password_min": {
    +        "Type": "MinLength",
    +        "MinLength": 8,
    +        "Message": "Password must be at least 8 characters",
    +        "StatusCode": 400
    +      },
    +      "name_max": {
    +        "Type": "MaxLength",
    +        "MaxLength": 50,
    +        "Message": "Name must not exceed 50 characters",
    +        "StatusCode": 400
    +      },
    +      "slug": {
    +        "Type": "Regex",
    +        "Pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
    +        "Message": "Parameter '{0}' must be a valid URL slug",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Usage with Annotations

    Once validation rules are configured, use the validate annotation in PostgreSQL function comments to apply validation:

    sql
    sql
    create function register_user(_email text, _password text, _name text)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    -- validation already passed, safe to use parameters
    +    insert into users (email, password_hash, name)
    +    values (_email, crypt(_password, gen_salt('bf')), _name);
    +    return json_build_object('success', true);
    +end;
    +$$;
    +
    +comment on function register_user(text, text, text) is '
    +HTTP POST
    +@validate _email using required, email
    +@validate _password using required, password_min
    +@validate _name using not_empty, name_max
    +';

    Equivalent as a SQL file endpoint (sql/register-user.sql):

    sql
    sql
    /*
    +HTTP POST
    +@validate email using required, email
    +@validate password using required, password_min
    +@validate name using not_empty, name_max
    +@param $1 email
    +@param $2 password
    +@param $3 name
    +*/
    +insert into users (email, password_hash, name)
    +values ($1, crypt($2, gen_salt('bf')), $3)
    +returning json_build_object('success', true);

    Programmatic Configuration

    When using NpgsqlRest as a library, you can configure validation options programmatically:

    csharp
    csharp
    var options = new NpgsqlRestOptions
    +{
    +    ValidationOptions = new ValidationOptions
    +    {
    +        Rules = new Dictionary<string, ValidationRule>
    +        {
    +            ["required"] = new ValidationRule
    +            {
    +                Type = ValidationType.Required,
    +                Message = "Parameter '{0}' is required",
    +                StatusCode = 400
    +            },
    +            ["phone"] = new ValidationRule
    +            {
    +                Type = ValidationType.Regex,
    +                Pattern = @"^\\+?[1-9]\\d{1,14}$",
    +                Message = "Parameter '{0}' must be a valid phone number"
    +            },
    +            ["min_age"] = new ValidationRule
    +            {
    +                Type = ValidationType.MinLength,
    +                MinLength = 2,
    +                Message = "Parameter '{0}' must be at least 2 characters"
    +            }
    +        }
    +    }
    +};

    Behavior

    • Validation runs immediately after parameter parsing, before database connections are opened
    • Multiple rules can be applied to a single parameter
    • Rules are evaluated in order; validation stops on first failure
    • Failed validation returns the configured HTTP status code (default 400)
    • Null values pass NotEmpty validation (use Required to reject nulls and empty strings)

    Next Steps

    See Also

    • VALIDATE - Apply validation rules to parameters
    `,40)]))}const y=i(t,[["render",p]]);export{g as __pageData,y as default}; diff --git a/assets/config_validation.md.lHNUsfic.lean.js b/assets/config_validation.md.lHNUsfic.lean.js new file mode 100644 index 000000000..d9dc85908 --- /dev/null +++ b/assets/config_validation.md.lHNUsfic.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Validation Options Configuration","titleTemplate":"NpgsqlRest","description":"Configure parameter validation for NpgsqlRest APIs. Define validation rules for endpoint parameters before database execution using NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength validators.","frontmatter":{"outline":[2,3],"title":"Validation Options Configuration","titleTemplate":"NpgsqlRest","description":"Configure parameter validation for NpgsqlRest APIs. Define validation rules for endpoint parameters before database execution using NotNull, NotEmpty, Required, Regex, MinLength, and MaxLength validators.","head":[["meta",{"name":"keywords","content":"npgsqlrest validation, api parameter validation, postgresql api validation, request validation, input validation configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Validation Options Configuration"}],["meta",{"property":"og:description","content":"Configure parameter validation rules for NpgsqlRest endpoints before database execution."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/validation.md","filePath":"config/validation.md"}'),t={name:"config/validation.md"};function p(e,s,h,k,r,d){return n(),a("div",null,s[0]||(s[0]=[l("",40)]))}const y=i(t,[["render",p]]);export{g as __pageData,y as default}; diff --git a/assets/config_watch.md.B_iL-Wg4.js b/assets/config_watch.md.B_iL-Wg4.js new file mode 100644 index 000000000..4b7f01768 --- /dev/null +++ b/assets/config_watch.md.B_iL-Wg4.js @@ -0,0 +1,8 @@ +import{_ as t,c as s,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Watch Mode Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for NpgsqlRest watch mode (--watch): restart the server or re-run tests on SQL file, configuration, and database routine changes.","frontmatter":{"outline":[2,3],"title":"Watch Mode Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for NpgsqlRest watch mode (--watch): restart the server or re-run tests on SQL file, configuration, and database routine changes.","head":[["meta",{"name":"keywords","content":"npgsqlrest watch mode, --watch, dev server reload, database polling, sql hot reload, postgresql rest api dev loop"}],["meta",{"property":"og:title","content":"NpgsqlRest Watch Mode Configuration"}],["meta",{"property":"og:description","content":"Configuration reference for NpgsqlRest watch mode (--watch)."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/watch.md","filePath":"config/watch.md"}'),n={name:"config/watch.md"};function o(r,e,d,l,h,c){return a(),s("div",null,e[0]||(e[0]=[i(`

    Watch Mode

    Configuration for watch mode — one feature, two flavors selected by the --test flag:

    CommandFlavorWatchesOn change
    npgsqlrest ... --test --watchTest watchtest files, included fixtures/profiles, endpoint SQL files, the databasechanged test re-runs alone; endpoint or database change rebuilds endpoints in-process and re-runs everything
    npgsqlrest ... --watchServer watchthe SQL file source tree, the configuration files, the databasethe server restarts (~1s)

    Watch is interactive/dev-only. In both flavors a broken SQL file cannot kill the session — SqlFileSource.ErrorMode is forced from Exit to Skip while watching.

    Overview

    The Watch section is top-level (a sibling of NpgsqlRest, not nested inside it), because it drives both flavors:

    json
    json
    {
    +  "Watch": {
    +    "Enabled": false,
    +    "DatabasePollingInterval": "2s"
    +  }
    +}
    SettingTypeDefaultDescription
    EnabledboolfalseTurn watch mode on. The --watch CLI flag is the shorthand for this setting.
    DatabasePollingIntervalstring"2s"Poll the database for routine changes; 0 disables polling.

    Enabled

    Turns watch mode on; --watch on the command line is equivalent. The flavor is chosen by --test:

    sh
    sh
    npgsqlrest ./config.json --watch               # server watch
    +npgsqlrest ./config.json --test --watch        # test watch
    +npgsqlrest ./config.json --watch:enabled=true  # same as --watch (standard config override syntax)

    Server watch needs something to watch: an enabled SQL file source, database polling (on by default), or both — with neither, --watch exits with an error. Test watch always has its test files to watch.

    DatabasePollingInterval

    Routine-source endpoints (functions and procedures) have no files to watch — so watch mode polls the database instead, with perfect fidelity: the poll runs the same routine discovery query the endpoint source uses, with the same configured filters (schema/name/language includes and excludes), hashed server-side into a single value on a dedicated non-pooled connection. If the hash changes, the discovered endpoints changed — by definition.

    Detected (because they change the discovery result):

    • create / create or replace / drop / alter of functions and procedures, including GRANT/REVOKE
    • COMMENT ON — i.e. annotation changes
    • changes to the composite types and tables used as parameter or return types (alter table users add column reshapes a returns setof users endpoint even though no function changed)

    Never triggers (because the discovery query doesn't read them): unrelated tables, temp objects, data changes.

    Accepts "2s", "500ms", "1m", a plain number of seconds, or "hh:mm:ss"; 0 disables polling. Polling is automatically inactive when the routine source is disabled (NpgsqlRest.RoutineOptions.Enabled: false).

    This makes a routines-only project fully watchable: run npgsqlrest ./config.json --watch, then create or replace a function in psql — the endpoint is live about two seconds later, annotations included. In test watch, a database change shows as — change detected (database) — followed by an endpoint rebuild and a full rerun; the runner re-baselines after every rerun so self-inflicted changes never re-trigger.

    Server watch behavior

    The process becomes a small supervisor that spawns itself as a child server and watches for changes; the child runs the completely normal server pipeline — including code generation (TypeScript client, HTTP files, OpenAPI regenerate on every restart), so dev is production behavior (the same model as dotnet watch).

    EventResult
    .sql change under the source treerestart (files matching SkipPattern — test files — are ignored)
    configuration file changerestart with the new configuration
    database routine change (polling)restart
    broken SQL filerestart; the error is logged, that endpoint drops, everything else keeps serving
    child crashes/exits on its ownsupervisor waits for the next change (no crash-looping)
    Ctrl+C / SIGTERM (docker stop)child stopped gracefully, both processes exit, port freed
    supervisor killed hard (SIGKILL)the child detects the vanished parent and exits by itself — no orphan holding the port

    Graceful child stop uses SIGTERM on Linux/macOS; on Windows the child is hard-killed (nothing needs teardown in a dev server). Works in every distribution: AOT executables, framework-dependent dotnet NpgsqlRestClient.dll, and both Docker image flavors.

    Docker Desktop bind mounts

    Where file events don't cross the filesystem boundary (Docker Desktop volume mounts, network shares), set the ecosystem-standard DOTNET_USE_POLLING_FILE_WATCHER=1 to switch the file watcher to a 1-second polling scan. This affects file watching only — database polling is unaffected.

    Test watch behavior

    Described in detail in the Testing Guide and the Test Runner configuration: a changed test file re-runs alone; endpoint file and database changes rebuild endpoints in-process (with a +/- endpoint delta report) and re-run everything; Teardown runs once, on exit — including on Ctrl+C and SIGTERM.

    `,28)]))}const g=t(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/config_watch.md.B_iL-Wg4.lean.js b/assets/config_watch.md.B_iL-Wg4.lean.js new file mode 100644 index 000000000..11b2e7189 --- /dev/null +++ b/assets/config_watch.md.B_iL-Wg4.lean.js @@ -0,0 +1 @@ +import{_ as t,c as s,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Watch Mode Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for NpgsqlRest watch mode (--watch): restart the server or re-run tests on SQL file, configuration, and database routine changes.","frontmatter":{"outline":[2,3],"title":"Watch Mode Configuration","titleTemplate":"NpgsqlRest","description":"Configuration reference for NpgsqlRest watch mode (--watch): restart the server or re-run tests on SQL file, configuration, and database routine changes.","head":[["meta",{"name":"keywords","content":"npgsqlrest watch mode, --watch, dev server reload, database polling, sql hot reload, postgresql rest api dev loop"}],["meta",{"property":"og:title","content":"NpgsqlRest Watch Mode Configuration"}],["meta",{"property":"og:description","content":"Configuration reference for NpgsqlRest watch mode (--watch)."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"config/watch.md","filePath":"config/watch.md"}'),n={name:"config/watch.md"};function o(r,e,d,l,h,c){return a(),s("div",null,e[0]||(e[0]=[i("",28)]))}const g=t(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/examples_index.md.D4o4oSds.js b/assets/examples_index.md.D4o4oSds.js new file mode 100644 index 000000000..1077a2368 --- /dev/null +++ b/assets/examples_index.md.D4o4oSds.js @@ -0,0 +1,8 @@ +import{_ as n,C as i,c as o,o as p,a5 as l,j as e,a as s,G as r}from"./chunks/framework.CgT1UzWm.js";const S=JSON.parse('{"title":"NpgsqlRest Examples","titleTemplate":"NpgsqlRest","description":"Hands-on examples demonstrating NpgsqlRest features. Learn PostgreSQL REST API development with TypeScript client generation, authentication, file uploads, and more.","frontmatter":{"title":"NpgsqlRest Examples","titleTemplate":"NpgsqlRest","description":"Hands-on examples demonstrating NpgsqlRest features. Learn PostgreSQL REST API development with TypeScript client generation, authentication, file uploads, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest examples, postgresql rest api tutorial, typescript api examples, database api examples, npgsqlrest tutorial"}],["meta",{"property":"og:title","content":"NpgsqlRest Examples"}],["meta",{"property":"og:description","content":"Hands-on examples demonstrating NpgsqlRest features with TypeScript client generation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"examples/index.md","filePath":"examples/index.md"}'),d={name:"examples/index.md"},c={class:"table-container"},h={class:"table-wrapper"},u={tabindex:"0"},g={id:"sql-file-examples-sqlfilesource",tabindex:"-1"},m={id:"mcp-server-sqlfilesource",tabindex:"-1"},b={id:"sql-test-runner",tabindex:"-1"};function _(f,t,q,y,x,k){const a=i("Badge");return p(),o("div",null,[t[29]||(t[29]=l(`

    Examples

    This section provides hands-on examples demonstrating NpgsqlRest features. Each example builds on the previous one, progressively introducing more advanced concepts.

    New to NpgsqlRest? Start with the SQL File examples — they're the recommended way to build endpoints and don't require any PostgreSQL function definitions.

    All examples are available in the examples repository on GitHub.

    Prerequisites

    Before running the examples, ensure you have:

    • PostgreSQL running locally (port 5432)
    • Bun runtime installed (bun.sh)
    • A database named example_db with default credentials (postgres/postgres)

    Getting Started

    1. Clone the repository:
    bash
    bash
    git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
    +cd npgsqlrest-docs/examples
    1. Install dependencies (downloads the NpgsqlRest binary and sets up required tools):
    bash
    bash
    bun install
    1. Navigate to any example directory and run it:
    bash
    bash
    cd 1_my_first_function
    +
    +# Apply database migrations
    +bun run db:up
    +
    +# Start the server (also rebuilds TypeScript and HTTP files)
    +bun run dev
    1. Visit http://127.0.0.1:8080 to see the result.

    Available Examples

    Function-Based Examples (RoutineSource)

    These examples use PostgreSQL functions and procedures as the endpoint source:

    `,18)),e("div",c,[e("div",h,[e("table",u,[t[19]||(t[19]=e("thead",null,[e("tr",null,[e("th",null,"Example"),e("th",null,"Description"),e("th",null,"Related Blog Post")])],-1)),e("tbody",null,[t[4]||(t[4]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/1_my_first_function",target:"_blank",rel:"noreferrer"},"1_my_first_function")]),e("td",null,"The basics: creating a PostgreSQL function and exposing it as an HTTP endpoint with automatic TypeScript client generation"),e("td",null,[e("a",{href:"/blog/end-to-end-static-type-checking-postgresql-typescript.html"},"End-to-End Type Checking")])],-1)),t[5]||(t[5]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking",target:"_blank",rel:"noreferrer"},"2_static_type_checking")]),e("td",null,"How NpgsqlRest's autogenerated client code provides static type safety, catching breaking changes at build time"),e("td",null,[e("a",{href:"/blog/end-to-end-static-type-checking-postgresql-typescript.html"},"End-to-End Type Checking")])],-1)),t[6]||(t[6]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/3_security_and_auth",target:"_blank",rel:"noreferrer"},"3_security_and_auth")]),e("td",null,"Database-level security with cookie-based authentication and the principle of least privilege"),e("td",null,[e("a",{href:"/blog/database-level-security-postgresql-authentication.html"},"Database-Level Security")])],-1)),t[7]||(t[7]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/4_passwords_tokens_roles",target:"_blank",rel:"noreferrer"},"4_passwords_tokens_roles")]),e("td",null,"Password verification, JWT/Bearer tokens, role-based access control (RBAC), and external OAuth providers"),e("td",null,[e("a",{href:"/blog/multiple-auth-schemes-rbac-external-providers.html"},"Multiple Auth Schemes & RBAC")])],-1)),t[8]||(t[8]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/5_csv_basic_auth",target:"_blank",rel:"noreferrer"},"5_csv_basic_auth")]),e("td",null,"CSV exports with HTTP Basic Auth, Excel integration, and type composition for BI use cases"),e("td",null,[e("a",{href:"/blog/postgresql-bi-server-excel-csv-basic-auth.html"},"PostgreSQL BI Server")])],-1)),t[9]||(t[9]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/6_image_uploads",target:"_blank",rel:"noreferrer"},"6_image_uploads")]),e("td",null,"Secure image uploads with file system storage, PostgreSQL Large Objects, and progress tracking"),e("td",null,[e("a",{href:"/blog/secure-image-uploads-postgresql-typescript.html"},"Secure Image Uploads")])],-1)),t[10]||(t[10]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/7_csv_excel_uploads",target:"_blank",rel:"noreferrer"},"7_csv_excel_uploads")]),e("td",null,"CSV and Excel file ingestion with row-by-row processing and automatic TypeScript clients"),e("td",null,[e("a",{href:"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html"},"CSV & Excel Ingestion")])],-1)),t[11]||(t[11]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/8_simple_chat_client",target:"_blank",rel:"noreferrer"},"8_simple_chat_client")]),e("td",null,"Real-time chat application using Server-Sent Events (SSE) and PostgreSQL RAISE statements"),e("td",null,[e("a",{href:"/blog/real-time-chat-postgresql-sse-npgsqlrest.html"},"Real-Time Chat with SSE")])],-1)),t[12]||(t[12]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/9_http_calls",target:"_blank",rel:"noreferrer"},"9_http_calls")]),e("td",null,"External API calls from PostgreSQL using HTTP custom types defined in type comments"),e("td",null,[e("a",{href:"/blog/external-api-calls-postgresql-http-types.html"},"External API Calls")])],-1)),t[13]||(t[13]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/10_proxy_ai_service",target:"_blank",rel:"noreferrer"},"10_proxy_ai_service")]),e("td",null,"Reverse proxy with transform mode for caching AI responses and external service integration"),e("td",null,[e("a",{href:"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html"},"Reverse Proxy & AI Service")])],-1)),t[14]||(t[14]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/12_custom_types",target:"_blank",rel:"noreferrer"},"12_custom_types")]),e("td",null,"Custom PostgreSQL composite types and multiset returns for complex nested JSON responses"),e("td",null,[e("a",{href:"/blog/custom-types-multiset-rest-api.html"},"Custom Types & Multiset")])],-1)),t[15]||(t[15]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/13_passkey",target:"_blank",rel:"noreferrer"},"13_passkey")]),e("td",null,"WebAuthn passkey authentication with pure SQL: passwordless login using device biometrics"),e("td",null,[e("a",{href:"/blog/passkey-sql-auth.html"},"Passkey SQL Auth")])],-1)),t[16]||(t[16]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/14_table_format",target:"_blank",rel:"noreferrer"},"14_table_format")]),e("td",null,"Excel export and stats endpoints with HTML table format output and cookie authentication"),e("td",null,[e("a",{href:"/blog/excel-export-table-format-postgresql-npgsqlrest.html"},"Excel Exports Done Right")])],-1)),t[17]||(t[17]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/16_scrap_demo",target:"_blank",rel:"noreferrer"},"16_scrap_demo")]),e("td",null,"Web scraping in SQL: fetch a product listing with an HTTP Custom Type, parse the HTML with PostgreSQL XPath, and return the best-value laptop by a weighted score"),e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")])],-1)),t[18]||(t[18]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/17_scrap_demo_2",target:"_blank",rel:"noreferrer"},"17_scrap_demo_2")]),e("td",null,"Web scraping in SQL: fetch a book catalog with an HTTP Custom Type, parse it with XML functions, and return the average book price on the page"),e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")])],-1)),e("tr",null,[e("td",null,[t[0]||(t[0]=e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/18_scrap_proxy_demo",target:"_blank",rel:"noreferrer"},"18_scrap_proxy_demo",-1)),t[1]||(t[1]=s()),r(a,{type:"tip",text:"v3.18.2"})]),t[2]||(t[2]=e("td",null,[s("Combine an HTTP Custom Type with a reverse proxy: fetch the page server-side, then "),e("code",null,"@proxy"),s(" the scraped HTML to an upstream service in the request body via "),e("code",null,"@body_parameter_name"),s(". "),e("code",null,"OmitAutomaticParameters"),s(" keeps the generated client a clean no-argument call")],-1)),t[3]||(t[3]=e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")],-1))])])])])]),e("h3",g,[t[20]||(t[20]=s("SQL File Examples (SqlFileSource) ",-1)),r(a,{type:"tip",text:"v3.12.0"}),t[21]||(t[21]=s()),t[22]||(t[22]=e("a",{class:"header-anchor",href:"#sql-file-examples-sqlfilesource","aria-label":'Permalink to "SQL File Examples (SqlFileSource) "'},"​",-1))]),t[30]||(t[30]=l('

    These examples use the new SQL File Source plugin — endpoints are generated directly from .sql files without needing PostgreSQL functions. Each is the SQL File equivalent of the function-based example above:

    ExampleDescriptionFunction-Based Equivalent
    1_my_first_function_sql_fileThe basics: creating an endpoint from a .sql file with automatic TypeScript client generation1_my_first_function
    2_static_type_checking_sql_fileStatic type safety with SQL File Source — catching breaking changes at build time2_static_type_checking
    3_security_and_auth_sql_fileDatabase-level security with cookie-based authentication using SQL files3_security_and_auth
    4_passwords_tokens_roles_sql_filePassword verification, JWT/Bearer tokens, and RBAC using SQL files4_passwords_tokens_roles
    5_csv_basic_auth_sql_fileCSV exports with HTTP Basic Auth using SQL files5_csv_basic_auth
    6_image_uploads_sql_fileSecure image uploads with file system storage and Large Objects using SQL files6_image_uploads
    7_csv_excel_uploads_sql_fileCSV and Excel file ingestion with row-by-row processing using SQL files7_csv_excel_uploads
    8_simple_chat_client_sql_fileReal-time chat application using SSE and SQL files8_simple_chat_client
    9_http_calls_sql_fileExternal API calls from PostgreSQL using SQL files9_http_calls
    10_proxy_ai_service_sql_fileReverse proxy with AI response caching using SQL files10_proxy_ai_service
    12_custom_types_sql_fileCustom composite types and nested JSON responses using SQL files12_custom_types
    14_table_format_sql_fileExcel export and stats endpoints with HTML table format using SQL files14_table_format
    ',2)),e("h3",m,[t[23]||(t[23]=s("MCP Server (SqlFileSource) ",-1)),r(a,{type:"tip",text:"v3.17.0"}),t[24]||(t[24]=s()),t[25]||(t[25]=e("a",{class:"header-anchor",href:"#mcp-server-sqlfilesource","aria-label":'Permalink to "MCP Server (SqlFileSource) "'},"​",-1))]),t[31]||(t[31]=l('

    Expose your .sql files as Model Context Protocol tools that an AI agent can discover and call — one source, two interfaces (REST + MCP).

    ExampleDescriptionRelated Blog Post
    15_mcp_serverAn "Acme Store" MCP server: each .sql file is both a typed REST endpoint and an @mcp tool. Includes a dual-panel web page (REST storefront + live MCP browser), a real Claude agent driving the store, MCP-only tools, and per-tool authorizationPostgreSQL as MCP Tools
    ',2)),e("h3",b,[t[26]||(t[26]=s("SQL Test Runner ",-1)),r(a,{type:"tip",text:"v3.19.0"}),t[27]||(t[27]=s()),t[28]||(t[28]=e("a",{class:"header-anchor",href:"#sql-test-runner","aria-label":'Permalink to "SQL Test Runner "'},"​",-1))]),t[32]||(t[32]=l('

    Test endpoints with plain .sql files using the built-in SQL test runner (npgsqlrest --test) — in-process endpoint invocation, transactional isolation, test databases, and endpoint coverage. Run with bun run test (or bun run test-watch) inside each example.

    ExampleDescription
    19_testing_basicThe basics: co-located layout (app.sql next to app.test.sql), boolean-SELECT and DO-block assertions, HTTP blocks with the _response table, multi-step scenario files
    20_testing_newdbA fresh test database per run: named Setup/Teardown steps (create database on an admin connection + migrations), {rnd5} unique names, one test per file, authorization + user parameters, a tag taxonomy (smoke/auth/fixtures/login), deferrable-constraint fixtures — and the login.sql endpoint uses the new named parameters (:email, :password)
    21_testing_isolationPerfect per-test isolation via a template database: migrations run once into a template, the shared run database and two per-test clones are created from it, deterministic sequence ids proven in parallel clones, a shared annotation profile attached with \\ir carrying @setup/@teardown/@connection/@tag

    Available Commands

    Each example provides these scripts:

    CommandDescription
    bun run devStart NpgsqlRest server (rebuilds TypeScript and HTTP files)
    bun run buildCompile TypeScript to JavaScript
    bun run watchWatch mode for TypeScript changes
    bun run db:upApply database migrations
    bun run db:listList pending migrations

    Next Steps

    After completing these examples, explore:

    ',8))])}const T=n(d,[["render",_]]);export{S as __pageData,T as default}; diff --git a/assets/examples_index.md.D4o4oSds.lean.js b/assets/examples_index.md.D4o4oSds.lean.js new file mode 100644 index 000000000..43a245457 --- /dev/null +++ b/assets/examples_index.md.D4o4oSds.lean.js @@ -0,0 +1 @@ +import{_ as n,C as i,c as o,o as p,a5 as l,j as e,a as s,G as r}from"./chunks/framework.CgT1UzWm.js";const S=JSON.parse('{"title":"NpgsqlRest Examples","titleTemplate":"NpgsqlRest","description":"Hands-on examples demonstrating NpgsqlRest features. Learn PostgreSQL REST API development with TypeScript client generation, authentication, file uploads, and more.","frontmatter":{"title":"NpgsqlRest Examples","titleTemplate":"NpgsqlRest","description":"Hands-on examples demonstrating NpgsqlRest features. Learn PostgreSQL REST API development with TypeScript client generation, authentication, file uploads, and more.","head":[["meta",{"name":"keywords","content":"npgsqlrest examples, postgresql rest api tutorial, typescript api examples, database api examples, npgsqlrest tutorial"}],["meta",{"property":"og:title","content":"NpgsqlRest Examples"}],["meta",{"property":"og:description","content":"Hands-on examples demonstrating NpgsqlRest features with TypeScript client generation."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"examples/index.md","filePath":"examples/index.md"}'),d={name:"examples/index.md"},c={class:"table-container"},h={class:"table-wrapper"},u={tabindex:"0"},g={id:"sql-file-examples-sqlfilesource",tabindex:"-1"},m={id:"mcp-server-sqlfilesource",tabindex:"-1"},b={id:"sql-test-runner",tabindex:"-1"};function _(f,t,q,y,x,k){const a=i("Badge");return p(),o("div",null,[t[29]||(t[29]=l("",18)),e("div",c,[e("div",h,[e("table",u,[t[19]||(t[19]=e("thead",null,[e("tr",null,[e("th",null,"Example"),e("th",null,"Description"),e("th",null,"Related Blog Post")])],-1)),e("tbody",null,[t[4]||(t[4]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/1_my_first_function",target:"_blank",rel:"noreferrer"},"1_my_first_function")]),e("td",null,"The basics: creating a PostgreSQL function and exposing it as an HTTP endpoint with automatic TypeScript client generation"),e("td",null,[e("a",{href:"/blog/end-to-end-static-type-checking-postgresql-typescript.html"},"End-to-End Type Checking")])],-1)),t[5]||(t[5]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/2_static_type_checking",target:"_blank",rel:"noreferrer"},"2_static_type_checking")]),e("td",null,"How NpgsqlRest's autogenerated client code provides static type safety, catching breaking changes at build time"),e("td",null,[e("a",{href:"/blog/end-to-end-static-type-checking-postgresql-typescript.html"},"End-to-End Type Checking")])],-1)),t[6]||(t[6]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/3_security_and_auth",target:"_blank",rel:"noreferrer"},"3_security_and_auth")]),e("td",null,"Database-level security with cookie-based authentication and the principle of least privilege"),e("td",null,[e("a",{href:"/blog/database-level-security-postgresql-authentication.html"},"Database-Level Security")])],-1)),t[7]||(t[7]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/4_passwords_tokens_roles",target:"_blank",rel:"noreferrer"},"4_passwords_tokens_roles")]),e("td",null,"Password verification, JWT/Bearer tokens, role-based access control (RBAC), and external OAuth providers"),e("td",null,[e("a",{href:"/blog/multiple-auth-schemes-rbac-external-providers.html"},"Multiple Auth Schemes & RBAC")])],-1)),t[8]||(t[8]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/5_csv_basic_auth",target:"_blank",rel:"noreferrer"},"5_csv_basic_auth")]),e("td",null,"CSV exports with HTTP Basic Auth, Excel integration, and type composition for BI use cases"),e("td",null,[e("a",{href:"/blog/postgresql-bi-server-excel-csv-basic-auth.html"},"PostgreSQL BI Server")])],-1)),t[9]||(t[9]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/6_image_uploads",target:"_blank",rel:"noreferrer"},"6_image_uploads")]),e("td",null,"Secure image uploads with file system storage, PostgreSQL Large Objects, and progress tracking"),e("td",null,[e("a",{href:"/blog/secure-image-uploads-postgresql-typescript.html"},"Secure Image Uploads")])],-1)),t[10]||(t[10]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/7_csv_excel_uploads",target:"_blank",rel:"noreferrer"},"7_csv_excel_uploads")]),e("td",null,"CSV and Excel file ingestion with row-by-row processing and automatic TypeScript clients"),e("td",null,[e("a",{href:"/blog/csv-excel-ingestion-postgresql-npgsqlrest.html"},"CSV & Excel Ingestion")])],-1)),t[11]||(t[11]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/8_simple_chat_client",target:"_blank",rel:"noreferrer"},"8_simple_chat_client")]),e("td",null,"Real-time chat application using Server-Sent Events (SSE) and PostgreSQL RAISE statements"),e("td",null,[e("a",{href:"/blog/real-time-chat-postgresql-sse-npgsqlrest.html"},"Real-Time Chat with SSE")])],-1)),t[12]||(t[12]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/9_http_calls",target:"_blank",rel:"noreferrer"},"9_http_calls")]),e("td",null,"External API calls from PostgreSQL using HTTP custom types defined in type comments"),e("td",null,[e("a",{href:"/blog/external-api-calls-postgresql-http-types.html"},"External API Calls")])],-1)),t[13]||(t[13]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/10_proxy_ai_service",target:"_blank",rel:"noreferrer"},"10_proxy_ai_service")]),e("td",null,"Reverse proxy with transform mode for caching AI responses and external service integration"),e("td",null,[e("a",{href:"/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html"},"Reverse Proxy & AI Service")])],-1)),t[14]||(t[14]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/12_custom_types",target:"_blank",rel:"noreferrer"},"12_custom_types")]),e("td",null,"Custom PostgreSQL composite types and multiset returns for complex nested JSON responses"),e("td",null,[e("a",{href:"/blog/custom-types-multiset-rest-api.html"},"Custom Types & Multiset")])],-1)),t[15]||(t[15]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/13_passkey",target:"_blank",rel:"noreferrer"},"13_passkey")]),e("td",null,"WebAuthn passkey authentication with pure SQL: passwordless login using device biometrics"),e("td",null,[e("a",{href:"/blog/passkey-sql-auth.html"},"Passkey SQL Auth")])],-1)),t[16]||(t[16]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/14_table_format",target:"_blank",rel:"noreferrer"},"14_table_format")]),e("td",null,"Excel export and stats endpoints with HTML table format output and cookie authentication"),e("td",null,[e("a",{href:"/blog/excel-export-table-format-postgresql-npgsqlrest.html"},"Excel Exports Done Right")])],-1)),t[17]||(t[17]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/16_scrap_demo",target:"_blank",rel:"noreferrer"},"16_scrap_demo")]),e("td",null,"Web scraping in SQL: fetch a product listing with an HTTP Custom Type, parse the HTML with PostgreSQL XPath, and return the best-value laptop by a weighted score"),e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")])],-1)),t[18]||(t[18]=e("tr",null,[e("td",null,[e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/17_scrap_demo_2",target:"_blank",rel:"noreferrer"},"17_scrap_demo_2")]),e("td",null,"Web scraping in SQL: fetch a book catalog with an HTTP Custom Type, parse it with XML functions, and return the average book price on the page"),e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")])],-1)),e("tr",null,[e("td",null,[t[0]||(t[0]=e("a",{href:"https://github.com/NpgsqlRest/npgsqlrest-docs/tree/main/examples/18_scrap_proxy_demo",target:"_blank",rel:"noreferrer"},"18_scrap_proxy_demo",-1)),t[1]||(t[1]=s()),r(a,{type:"tip",text:"v3.18.2"})]),t[2]||(t[2]=e("td",null,[s("Combine an HTTP Custom Type with a reverse proxy: fetch the page server-side, then "),e("code",null,"@proxy"),s(" the scraped HTML to an upstream service in the request body via "),e("code",null,"@body_parameter_name"),s(". "),e("code",null,"OmitAutomaticParameters"),s(" keeps the generated client a clean no-argument call")],-1)),t[3]||(t[3]=e("td",null,[e("a",{href:"/blog/web-scraping-postgresql-http-types-xml.html"},"Web Scraping with HTTP Types")],-1))])])])])]),e("h3",g,[t[20]||(t[20]=s("SQL File Examples (SqlFileSource) ",-1)),r(a,{type:"tip",text:"v3.12.0"}),t[21]||(t[21]=s()),t[22]||(t[22]=e("a",{class:"header-anchor",href:"#sql-file-examples-sqlfilesource","aria-label":'Permalink to "SQL File Examples (SqlFileSource) "'},"​",-1))]),t[30]||(t[30]=l("",2)),e("h3",m,[t[23]||(t[23]=s("MCP Server (SqlFileSource) ",-1)),r(a,{type:"tip",text:"v3.17.0"}),t[24]||(t[24]=s()),t[25]||(t[25]=e("a",{class:"header-anchor",href:"#mcp-server-sqlfilesource","aria-label":'Permalink to "MCP Server (SqlFileSource) "'},"​",-1))]),t[31]||(t[31]=l("",2)),e("h3",b,[t[26]||(t[26]=s("SQL Test Runner ",-1)),r(a,{type:"tip",text:"v3.19.0"}),t[27]||(t[27]=s()),t[28]||(t[28]=e("a",{class:"header-anchor",href:"#sql-test-runner","aria-label":'Permalink to "SQL Test Runner "'},"​",-1))]),t[32]||(t[32]=l("",8))])}const T=n(d,[["render",_]]);export{S as __pageData,T as default}; diff --git a/assets/guide_annotations.md.DVRiz6k_.js b/assets/guide_annotations.md.DVRiz6k_.js new file mode 100644 index 000000000..42ba6bfb8 --- /dev/null +++ b/assets/guide_annotations.md.DVRiz6k_.js @@ -0,0 +1,159 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Comment Annotations Guide","titleTemplate":"NpgsqlRest","description":"Configure REST API endpoints using PostgreSQL comments. Learn HTTP methods, authorization, caching, rate limiting, and other annotations to control your API behavior.","frontmatter":{"outline":[2,3],"title":"Comment Annotations Guide","titleTemplate":"NpgsqlRest","description":"Configure REST API endpoints using PostgreSQL comments. Learn HTTP methods, authorization, caching, rate limiting, and other annotations to control your API behavior.","head":[["meta",{"name":"keywords","content":"npgsqlrest annotations, postgresql comment api, declarative rest api, postgresql http comments, sql api configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Comment Annotations Guide"}],["meta",{"property":"og:description","content":"Configure REST API endpoints using PostgreSQL comments. HTTP methods, authorization, caching, and more."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/annotations.md","filePath":"guide/annotations.md"}'),t={name:"guide/annotations.md"};function l(p,s,r,o,c,h){return i(),n("div",null,s[0]||(s[0]=[e(`

    Comment Annotations Guide

    NpgsqlRest uses comment annotations to configure API endpoints declaratively. Annotations work in two places:

    • PostgreSQL functions/procedures — via the built-in COMMENT system (COMMENT ON FUNCTION ...)
    • SQL files — via standard SQL comments (-- line comments and /* */ block comments) directly in .sql files. See the SQL File Endpoints Guide.

    All annotations work identically in both contexts. This guide explains how they work and how to use them effectively.

    How Annotations Work

    Comment annotations are special keywords placed in comments that control how endpoints are generated and configured. The annotation parser reads comments line by line, looking for recognized keywords at the start of each line.

    Basic Rules

    1. Annotations must start at the beginning of a line - the keyword must be the first text on the line
    2. Keywords are case-insensitive - HTTP, http, and Http are all valid
    3. Unrecognized text is ignored - you can mix documentation with annotations
    4. Multiple annotations per comment - use separate lines for each annotation

    Optional @ Prefix

    NpgsqlRest-specific annotations support an optional @ prefix. This follows the .http file convention that many developers are familiar with from tools like REST Client extensions in VS Code. Using the @ prefix is recommended for better visual distinction, but both syntaxes work identically:

    sql
    sql
    -- With @ prefix (recommended, follows .http file convention)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +@cached
    +@raw';
    +
    +-- Without @ prefix (still works the same)
    +comment on function my_func() is '
    +HTTP GET
    +authorize
    +cached
    +raw';
    +
    +-- Mixed (both work together)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +cached
    +@timeout 30s';

    The @ prefix also works with annotation parameters using the key = value syntax:

    sql
    sql
    -- Both syntaxes are equivalent
    +comment on function my_func() is '
    +HTTP GET
    +raw = true
    +timeout = 30s
    +my_custom_param = custom_value
    +';
    +
    +-- With @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +@raw = true
    +@timeout = 30s
    +@my_custom_param = custom_value
    +';

    Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

    TIP

    The @ prefix is purely optional. All existing code without @ continues to work unchanged. Choose whichever style you prefer, or mix them freely.

    HTTP Headers Don't Use @

    HTTP RFC standard annotations (headers with Name: value syntax like Content-Type: application/json) do not use the @ prefix - they follow the standard HTTP header format.

    Simple Example

    sql
    sql
    comment on function get_users() is
    +'Returns all active users from the database.
    +HTTP GET
    +@authorize';

    This comment contains:

    • Documentation text (ignored by parser)
    • HTTP GET annotation - exposes as GET endpoint
    • @authorize annotation - requires authentication

    The HTTP Annotation

    The HTTP annotation is the primary way to expose a function or table as an endpoint. Without it (when using the client's default CommentsMode: OnlyAnnotated, or the library's OnlyWithHttpTag), the object won't be exposed — unless a loaded plugin annotation requests an endpoint (e.g. @mcp, which can create an MCP-only routine with no HTTP route).

    Syntax Variations

    sql
    sql
    -- Basic: expose with default method and path
    +comment on function my_func() is 'HTTP';
    +
    +-- With HTTP method
    +comment on function my_func() is 'HTTP GET';
    +comment on function my_func() is 'HTTP POST';
    +
    +-- With custom path
    +comment on function my_func() is 'HTTP /custom-path';
    +
    +-- With method and path
    +comment on function my_func() is 'HTTP GET /users/list';

    Default Behavior

    When method is not specified:

    • GET is used for non-volatile functions, or functions with names starting with get_, containing _get_, or ending with _get
    • POST is used otherwise

    When path is not specified, it's generated from the function name using the URL prefix and naming conventions from configuration.

    Authorization Annotations

    Control access to endpoints with authorization annotations.

    Require Authentication

    sql
    sql
    -- Require any authenticated user
    +comment on function protected_func() is
    +'HTTP
    +@authorize';
    +
    +-- Require specific roles
    +comment on function admin_func() is
    +'HTTP
    +@authorize admin';
    +
    +-- Multiple roles (user must have at least one)
    +comment on function staff_func() is
    +'HTTP
    +@authorize admin, manager, supervisor';

    Role List Syntax

    When specifying multiple roles, you can use either comma-separated or space-separated values - both work identically:

    sql
    sql
    -- Comma-separated (traditional)
    +comment on function staff_func() is 'HTTP
    +@authorize admin, manager, supervisor';
    +
    +-- Space-separated (also valid)
    +comment on function staff_func() is 'HTTP
    +@authorize admin manager supervisor';
    +
    +-- Mixed (works too)
    +comment on function staff_func() is 'HTTP
    +@authorize admin, manager supervisor';

    This flexibility applies to any annotation that accepts a list of values.

    Allow Anonymous Access

    sql
    sql
    comment on function public_func() is
    +'HTTP
    +@allow_anonymous';

    This overrides the global RequiresAuthorization setting for this specific endpoint.

    Response Headers

    Set custom response headers by using the Header-Name: value format:

    sql
    sql
    comment on function get_html_page() is
    +'HTTP GET
    +Content-Type: text/html
    +Cache-Control: public, max-age=3600';
    +
    +comment on function get_data() is
    +'HTTP GET
    +X-Custom-Header: custom-value
    +X-Another-Header: another-value';

    Multiple headers with the same name are supported:

    sql
    sql
    comment on function with_cookies() is
    +'HTTP
    +Set-Cookie: session=abc123
    +Set-Cookie: theme=dark';

    Request Parameter Configuration

    Control how parameters are transmitted to the endpoint.

    Query String vs Body

    sql
    sql
    -- Force query string parameters
    +comment on function search(_query text) is
    +'HTTP GET
    +@request_param_type query_string';
    +
    +-- Force JSON body parameters
    +comment on function create_user(_name text, _email text) is
    +'HTTP POST
    +@request_param_type body_json';

    Caching

    Enable response caching for scalar results:

    sql
    sql
    -- Simple caching
    +comment on function get_settings() is
    +'HTTP GET
    +@cached';
    +
    +-- Cache with specific parameters as cache key
    +comment on function get_user_profile(_user_id int) is
    +'HTTP GET
    +@cached _user_id';
    +
    +-- Set cache expiration
    +comment on function get_config() is
    +'HTTP GET
    +@cached
    +@cache_expires_in 1h';

    Cache expiration uses PostgreSQL interval format: 10s, 5m, 1h, 1d, etc.

    Raw Output Mode

    Return raw text instead of JSON:

    sql
    sql
    -- Basic raw mode
    +comment on function export_text() is
    +'HTTP GET
    +@raw';
    +
    +-- CSV export with custom formatting
    +comment on function export_csv() is
    +'HTTP GET
    +@raw
    +@separator ,
    +@new_line \\n
    +@columns';

    The @columns annotation includes column names as the first row.

    Combining Annotations

    Annotations can be combined freely. Order doesn't matter:

    sql
    sql
    comment on function get_report(_department text) is
    +'Generates a department report.
    +This is a cached endpoint requiring manager access.
    +
    +HTTP GET /reports/department
    +@authorize manager, admin
    +@cached _department
    +@cache_expires_in 30m
    +Content-Type: application/json
    +Cache-Control: private, max-age=1800';

    Note how NpgsqlRest-specific annotations use the @ prefix while HTTP headers (Content-Type, Cache-Control) use the standard RFC format.

    Debugging Annotations

    To see which annotations are applied when NpgsqlRest starts, set the logging level to Debug:

    In appsettings.json:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Debug"
    +    }
    +  }
    +}

    Via command line:

    bash
    bash
    npgsqlrest --Log:MinimalLevels:NpgsqlRest=Debug

    This will log each annotation as it's parsed and applied to endpoints.

    Comments Mode

    The CommentsMode configuration setting controls how annotations affect endpoint creation:

    ModeBehavior
    OnlyWithHttpTagOnly create endpoints for objects with HTTP annotation (default)
    ParseAllCreate all endpoints, parse annotations to modify them
    IgnoreCreate all endpoints, ignore all annotations

    Time/Duration Formats

    Several annotations accept time or duration values (e.g., @timeout, @cache_expires_in). See the complete Interval Format Reference for all supported units and syntax.

    Quick Reference

    UnitShortLong Forms
    Secondsssec, second, seconds
    Minutesmmin, minute, minutes
    Hourshhour, hours
    Daysdday, days
    Weekswweek, weeks

    Examples

    sql
    sql
    -- Using short forms (recommended)
    +@timeout 30s
    +@timeout 5min
    +@cache_expires_in 1h
    +
    +-- Decimals are supported
    +@timeout 1.5h      -- 1 hour 30 minutes
    +@timeout 500ms     -- half a second
    +
    +-- Numbers without unit default to seconds
    +@timeout 30        -- 30 seconds

    Single Token Requirement for @timeout

    The @timeout annotation reads only the first token after the keyword. Use formats without spaces to avoid parsing issues.

    sql
    sql
    -- Use single-token formats
    +@timeout 5min
    +@timeout 5m
    +@timeout 300s

    Common Patterns

    Public Read, Protected Write

    sql
    sql
    comment on function get_products() is
    +'HTTP GET
    +@allow_anonymous';
    +
    +comment on function create_product(_name text, _price numeric) is
    +'HTTP POST
    +@authorize admin';

    API Versioning with Custom Paths

    sql
    sql
    comment on function get_users_v1() is 'HTTP GET /v1/users';
    +comment on function get_users_v2() is 'HTTP GET /v2/users';

    Secure Sensitive Operations

    sql
    sql
    comment on function change_password(_old text, _new text) is
    +'HTTP POST
    +@authorize
    +@sensitive';

    The @sensitive annotation prevents parameter values from appearing in logs.

    Nested JSON for Composite Types

    sql
    sql
    comment on function get_user_with_address() is
    +'HTTP GET
    +@nested';

    The @nested annotation serializes composite type columns as nested JSON objects instead of expanding their fields into separate columns. See the NESTED annotation reference for details.

    Rate Limiting

    sql
    sql
    comment on function expensive_operation() is
    +'HTTP POST
    +@rate_limiter bucket';

    The policy name must match a policy configured in the Rate Limiter configuration.

    Next Steps

    `,93)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/guide_annotations.md.DVRiz6k_.lean.js b/assets/guide_annotations.md.DVRiz6k_.lean.js new file mode 100644 index 000000000..8d9c14fd9 --- /dev/null +++ b/assets/guide_annotations.md.DVRiz6k_.lean.js @@ -0,0 +1 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Comment Annotations Guide","titleTemplate":"NpgsqlRest","description":"Configure REST API endpoints using PostgreSQL comments. Learn HTTP methods, authorization, caching, rate limiting, and other annotations to control your API behavior.","frontmatter":{"outline":[2,3],"title":"Comment Annotations Guide","titleTemplate":"NpgsqlRest","description":"Configure REST API endpoints using PostgreSQL comments. Learn HTTP methods, authorization, caching, rate limiting, and other annotations to control your API behavior.","head":[["meta",{"name":"keywords","content":"npgsqlrest annotations, postgresql comment api, declarative rest api, postgresql http comments, sql api configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Comment Annotations Guide"}],["meta",{"property":"og:description","content":"Configure REST API endpoints using PostgreSQL comments. HTTP methods, authorization, caching, and more."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/annotations.md","filePath":"guide/annotations.md"}'),t={name:"guide/annotations.md"};function l(p,s,r,o,c,h){return i(),n("div",null,s[0]||(s[0]=[e("",93)]))}const m=a(t,[["render",l]]);export{k as __pageData,m as default}; diff --git a/assets/guide_authentication.md.BaKZBV2r.js b/assets/guide_authentication.md.BaKZBV2r.js new file mode 100644 index 000000000..6f280e559 --- /dev/null +++ b/assets/guide_authentication.md.BaKZBV2r.js @@ -0,0 +1,222 @@ +import{_ as n,c as e,o as t,a5 as a,j as s}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Authentication Guide","titleTemplate":"NpgsqlRest","description":"How authentication works in NpgsqlRest end-to-end: configure a scheme, write a login endpoint, understand claims, and read claims back as function parameters, PostgreSQL context variables, or annotation placeholders.","frontmatter":{"outline":[2,3],"title":"Authentication Guide","titleTemplate":"NpgsqlRest","description":"How authentication works in NpgsqlRest end-to-end: configure a scheme, write a login endpoint, understand claims, and read claims back as function parameters, PostgreSQL context variables, or annotation placeholders.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication, postgresql login api, sql login endpoint, claims postgresql, jwt cookie bearer auth, current_setting claims, user parameters"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Guide"}],["meta",{"property":"og:description","content":"Configure schemes, write login endpoints, and access user claims in SQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/authentication.md","filePath":"guide/authentication.md"}'),l={name:"guide/authentication.md"};function p(h,i,r,k,o,d){return t(),e("div",null,i[0]||(i[0]=[a(`

    Authentication

    This guide explains how authentication works in NpgsqlRest from end to end:

    1. The big picture — how the pieces fit together
    2. Configure an authentication scheme — cookie, bearer token, or JWT
    3. Write a login endpoint — sign users in from SQL
    4. How claims work — the user identity, produced from columns
    5. Accessing claims in your endpoints — as parameters, context variables, or template placeholders
    6. Logging out
    7. A complete worked example

    Reference pages

    This is the conceptual walkthrough. For exact options see @login, @logout, Authentication configuration, Authentication Options, and Claims Mapping.

    The big picture

    Authentication in NpgsqlRest is driven by your database. There is no separate identity service and no C# to write — you configure a scheme in appsettings.json, then write a normal SQL endpoint annotated with @login. Everything flows from there:

    mermaid
    flowchart TD
    +    CFG["appsettings.json
    +    Auth scheme
    +    cookie / bearer / jwt"]
    +    LOGIN["@login endpoint
    +    returns one row"]
    +
    +    C["1 - Client POSTs credentials"]
    +    COLS["2 - Login SQL returns columns"]
    +    CLAIMS["3 - NpgsqlRest turns columns into claims
    +    and issues a cookie / token"]
    +    REQ["4 - Every later request carries the identity
    +    NpgsqlRest checks @authorize and injects claims"]
    +
    +    C --> COLS --> CLAIMS --> REQ
    +    CFG -.->|"configures the session"| CLAIMS
    +    LOGIN -.->|"defines the columns"| COLS

    The three moving parts:

    PartWhere it livesWhat it does
    Schemeappsettings.jsonAuthDecides how the session is carried — an encrypted cookie, a bearer token, or a JWT.
    Login endpointa @login SQL routine / fileValidates credentials and returns the columns that become the user's claims.
    Claimsproduced at login, read on every requestThe user's identity (id, name, roles, and anything else you select).

    Step 1: Configure an authentication scheme

    A scheme decides how the signed-in session is carried between requests. Enable one (or several) in the Auth section. See Authentication configuration for every option.

    An encrypted, http-only cookie. Best for browser apps.

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieName": "my_app_auth",
    +    "CookieValidDays": 1
    +  }
    +}

    Bearer token

    A stateless token the client stores and sends in the Authorization: Bearer … header. Best for APIs and mobile clients.

    json
    json
    {
    +  "Auth": {
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": "token",
    +    "BearerTokenExpireHours": 1,
    +    "BearerTokenRefreshPath": "/api/token/refresh"
    +  }
    +}

    JWT

    A signed JSON Web Token, verifiable by other services that share the secret.

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtAuthScheme": "jwt",
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "my_app",
    +    "JwtAudience": "my_app",
    +    "JwtExpireMinutes": 60
    +  }
    +}

    The string you set as CookieAuthScheme / BearerTokenAuthScheme / JwtAuthScheme is the scheme name. Your login endpoint chooses which one to issue via its scheme column. You can enable more than one at the same time, as the Multiple Auth Schemes example does.

    Require auth by default

    Set NpgsqlRest.RequiresAuthorization: true so every endpoint requires authentication unless it opts out with @allow_anonymous. This is a safer default than protecting endpoints one by one.

    External OAuth providers (Google, etc.) layer on top of this — see External Authentication.

    Step 2: Write a login endpoint

    A login endpoint is a normal SQL endpoint annotated with @login. It returns one row; NpgsqlRest reads a few special columns and turns the rest into claims.

    sql
    sql
    create function login(_username text, _password text)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    email text
    +)
    +language sql
    +security definer
    +as $$
    +select
    +    'cookies' as scheme,   -- which scheme to sign in
    +    u.user_id,
    +    u.username,
    +    u.email
    +from users u
    +where u.username = _username
    +  and verify_password(_password, u.password_hash);  -- your own check
    +$$;
    +
    +comment on function login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive';

    Equivalent as a SQL file endpoint (sql/login.sql):

    sql
    sql
    /*
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive
    +@param $1 username
    +@param $2 password
    +*/
    +select
    +    'cookies' as scheme,   -- which scheme to sign in
    +    u.user_id,
    +    u.username,
    +    u.email
    +from users u
    +where u.username = $1
    +  and verify_password($2, u.password_hash);

    What happens:

    • A correct password returns one row → NpgsqlRest signs the user in and creates the claims user_id, username, email.
    • A wrong password matches nothing → empty result → 401 Unauthorized. No status column is needed for this.
    • @anonymous lets unauthenticated callers reach the endpoint; @security_sensitive keeps the password out of the logs.

    Verifying the password

    You have two options (full detail in @login → Password verification):

    • Verify in SQL (above): call your own function (e.g. verify_password) and just don't return a row when it fails. You control the hashing, but it runs on your database server.
    • Built-in hasher (more secure — recommended for production): return the stored hash in a hash column and let NpgsqlRest verify it against the password parameter, with optional success/failure callbacks. Pair it with @parameter_hash when registering users. It uses a strong, OWASP-recommended PBKDF2 configuration and — importantly — runs the CPU-intensive hashing on the NpgsqlRest application instance instead of your database. Password hashing is deliberately expensive, and the app tier is easier to scale than PostgreSQL, so offloading it is an important consideration.

    For the verify-in-SQL option you don't need anything external — PostgreSQL's built-in pgcrypto extension provides crypt(), gen_salt(), and digest(). The recommended scheme pre-hashes the password with SHA-256 + base64 before bcrypt (bcrypt truncates input at 72 bytes; the digest is a fixed 44 chars that always fits):

    sql
    sql
    create extension if not exists pgcrypto;
    +
    +create function hash_password(_password text)
    +returns text language sql as $$
    +  select crypt(encode(digest(_password, 'sha256'), 'base64'), gen_salt('bf', 12));
    +$$;
    +
    +create function verify_password(_password text, _password_hash text)
    +returns boolean language sql as $$
    +  select crypt(encode(digest(_password, 'sha256'), 'base64'), _password_hash) = _password_hash;
    +$$;

    Use hash_password() when registering a user and verify_password() in the login query above. gen_salt('bf', 12) sets the bcrypt work factor — 12 is a sensible default in 2025.

    This keeps everything in the database and is fine for small or low-traffic apps. For greater security and to offload the CPU-intensive hashing from your database to the app tier, prefer the built-in hasher — see @login → Password verification for the full comparison.

    Choosing a scheme

    The scheme column picks which configured scheme to issue. With several schemes enabled you can let the client choose by passing it as a parameter:

    sql
    sql
    -- _scheme is 'cookies', 'token' or 'jwt'; an unknown scheme is rejected (404)
    +select _scheme as scheme, u.user_id, u.username, u.roles, u.email, u.password_hash as hash
    +from users u
    +where u.username = _username;

    How claims work

    A claim is a single fact about the signed-in user — user_id = 1, username = alice, roles = {admin}. Claims are the bridge between "who logged in" and "what your SQL can see".

    Claims are just the login columns

    The rule is deliberately simple:

    Every column your login endpoint returns — except the special columns status, scheme, body, hash — becomes a claim. The column name is the claim name; the column value is the claim value.

    So this login row:

    `,46),s("div",{class:"table-container"},[s("div",{class:"table-wrapper"},[s("table",{tabindex:"0"},[s("thead",null,[s("tr",null,[s("th",null,"user_id"),s("th",null,"username"),s("th",null,"email"),s("th",null,"roles")])]),s("tbody",null,[s("tr",null,[s("td",null,"1"),s("td",null,"alice"),s("td",null,[s("a",{href:"mailto:alice@example.com",target:"_blank",rel:"noreferrer"},"alice@example.com")]),s("td",{admin:""})])])])])],-1),a(`

    produces four claims: user_id, username, email, roles. You don't configure anything to create claims — you just select the columns you want to carry.

    Identity claims

    Three claims are special: they form the canonical identity used for the signed-in principal, for role checks in @authorize, and as the arguments to the verification callbacks. They're configured in Authentication Options:

    Config optionDefaultYour login must return a column named…
    DefaultUserIdClaimTypeuser_idthe user id
    DefaultNameClaimTypeuser_namethe display name
    DefaultRoleClaimTypeuser_rolesthe roles (a text[])

    Either name your columns to match the defaults, or change the config to match your columns. For example, if your login returns username and roles instead of user_name and user_roles:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "username",
    +      "DefaultRoleClaimType": "roles"
    +    }
    +  }
    +}

    With the role claim wired up, @authorize admin checks the roles claim for the value admin:

    sql
    sql
    comment on function get_users() is '
    +HTTP GET
    +@authorize admin';   -- 403 unless the roles claim contains "admin"

    Accessing claims in your endpoints

    After login, claims travel with every request. NpgsqlRest can hand them to your SQL in three ways. The constant that links all of them is the claim name — the same string that was the login column name.

    As function parameters

    Annotate the endpoint with @user_parameters (or enable globally with UseUserParameters: true). NpgsqlRest fills matching parameters from the claims, using ParameterNameClaimsMapping (parameter name → claim name):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_username": "username",
    +        "_email": "email"
    +      }
    +    }
    +  }
    +}
    sql
    sql
    create function who_am_i(
    +    _user_id text = null,   -- filled from the user_id claim
    +    _username text = null,   -- filled from the username claim
    +    _email text = null       -- filled from the email claim
    +)
    +returns table (user_id text, username text, email text)
    +language sql
    +as $$
    +    select _user_id, _username, _email;
    +$$;
    +
    +comment on function who_am_i(text, text, text) is '
    +HTTP GET
    +@authorize';
    • Claim values arrive as text (multi-value claims like roles as text[]); PostgreSQL coerces to your parameter types.
    • Give parameters default values so the function still works for anonymous calls — the default is used when there's no claim.
    • This is the approach in the Security & Auth example.

    Never trust client-supplied identity

    Declare the identity parameters (_user_id, …) and let NpgsqlRest fill them from the authenticated principal. Don't accept a user id from the request body and trust it. With @user_parameters, a value the client tries to send is overwritten by the claim.

    As PostgreSQL context variables

    Annotate with @user_context (or enable globally with UseUserContext: true). NpgsqlRest writes each claim into a session variable before running your SQL; you read it with current_setting('key', true). The mapping is ContextKeyClaimsMapping (context key → claim name):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.username": "username",
    +        "request.email": "email",
    +        "request.roles": "roles"
    +      }
    +    }
    +  }
    +}
    sql
    sql
    create function who_am_i()
    +returns table (user_id int, username text, email text, roles text[])
    +language sql
    +as $$
    +select
    +    nullif(current_setting('request.user_id', true), '')::int,
    +    nullif(current_setting('request.username', true), ''),
    +    nullif(current_setting('request.email', true), ''),
    +    nullif(current_setting('request.roles', true), '')::text[]
    +from users
    +where user_id = nullif(current_setting('request.user_id', true), '')::int;
    +$$;
    +
    +comment on function who_am_i() is '
    +HTTP GET
    +@authorize';
    • Always pass true as the second argument to current_setting() so a missing setting returns NULL instead of raising an error.
    • The client IP is available too (IpAddressContextKey, default request.ip_address), and all claims as JSON if you set ClaimsJsonContextKey.
    • This is the approach in the Multiple Auth Schemes example.

    Parameters vs context — which one?

    Parameters are type-checked by PostgreSQL and slightly faster; great for focused endpoints. Context variables are available to any SQL the request runs (views, triggers, nested function calls, resolved-parameter expressions) without threading them through every signature — great for cross-cutting things like row-level filtering. You can enable both.

    As template placeholders

    Anything that becomes a parameter (via @user_parameters) can also be referenced as a {name} placeholder in annotations that support substitution — response headers, custom/upload parameters, and HTTP custom type calls. This lets a claim drive a header, a file path, or an outbound request without the client sending it:

    sql
    sql
    comment on function upload_avatar(_user_id int, _file text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@upload for file_system
    +@file_system_path = /var/uploads/{_user_id}';   -- claim value drives the path

    And once claims are in context variables (@user_context), they're visible to every SQL expression the request evaluates — including resolved parameter expressions. For proxy endpoints, enabling UseUserContext / UseUserParameters also forwards the claims upstream (as headers / query parameters respectively).

    Logging out

    Mark an endpoint with @logout. Returning nothing signs the user out of the default scheme; returning scheme name(s) signs out those specific schemes.

    sql
    sql
    create function logout()
    +returns void
    +language sql
    +security definer
    +as $$
    +  -- nothing to return → sign out the current user's scheme
    +$$;
    +
    +comment on function logout() is '
    +HTTP POST
    +@logout
    +@authorize';

    A complete worked example

    A minimal but complete cookie-based setup: configuration, a login endpoint, and a protected endpoint that reads the signed-in user via parameters.

    appsettings.json

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieName": "my_app_auth",
    +    "CookieValidDays": 1
    +  },
    +  "NpgsqlRest": {
    +    "IncludeSchemas": [ "api" ],
    +    "RequiresAuthorization": true,
    +    "AuthenticationOptions": {
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "username",
    +      "DefaultRoleClaimType": "roles",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_username": "username",
    +        "_roles": "roles"
    +      }
    +    }
    +  }
    +}

    login — sign in (anonymous, verifies password in SQL)

    sql
    sql
    create function api.login(_username text, _password text)
    +returns table (scheme text, user_id int, username text, roles text[])
    +language sql
    +security definer
    +as $$
    +select 'cookies', u.user_id, u.username, u.roles
    +from api.users u
    +where u.username = _username
    +  and api.verify_password(_password, u.password_hash);
    +$$;
    +
    +comment on function api.login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive';

    my_profile — protected, reads claims via parameters

    sql
    sql
    create function api.my_profile(_user_id int = null, _username text = null, _roles text[] = '{}')
    +returns table (user_id int, username text, roles text[], is_admin boolean)
    +language sql
    +as $$
    +select _user_id, _username, _roles, _roles @> array['admin'];
    +$$;
    +
    +comment on function api.my_profile(int, text, text[]) is '
    +HTTP GET
    +@authorize';

    admin_users — admin only

    sql
    sql
    create function api.admin_users()
    +returns setof api.users
    +language sql
    +as $$ select * from api.users; $$;
    +
    +comment on function api.admin_users() is '
    +HTTP GET
    +@authorize admin';   -- requires the roles claim to contain "admin"

    Flow:

    1. POST /api/login with a valid username/password → cookie set, claims user_id/username/roles created.
    2. GET /api/my-profile → NpgsqlRest fills _user_id/_username/_roles from the claims; anonymous callers are rejected (401) because of @authorize.
    3. GET /api/admin-users → only succeeds when the roles claim contains admin (otherwise 403).

    See it in the examples

    Two runnable examples demonstrate both claim-access styles:

    • Security & Auth — cookie auth, password hashing in SQL, claims read as parameters.
    • Passwords, Tokens & Roles — cookie + bearer + JWT, built-in password hasher with callbacks, external (Google) login, claims read as context variables, role-based authorization.
    `,46)]))}const g=n(l,[["render",p]]);export{u as __pageData,g as default}; diff --git a/assets/guide_authentication.md.BaKZBV2r.lean.js b/assets/guide_authentication.md.BaKZBV2r.lean.js new file mode 100644 index 000000000..fdaa1be2d --- /dev/null +++ b/assets/guide_authentication.md.BaKZBV2r.lean.js @@ -0,0 +1 @@ +import{_ as n,c as e,o as t,a5 as a,j as s}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Authentication Guide","titleTemplate":"NpgsqlRest","description":"How authentication works in NpgsqlRest end-to-end: configure a scheme, write a login endpoint, understand claims, and read claims back as function parameters, PostgreSQL context variables, or annotation placeholders.","frontmatter":{"outline":[2,3],"title":"Authentication Guide","titleTemplate":"NpgsqlRest","description":"How authentication works in NpgsqlRest end-to-end: configure a scheme, write a login endpoint, understand claims, and read claims back as function parameters, PostgreSQL context variables, or annotation placeholders.","head":[["meta",{"name":"keywords","content":"npgsqlrest authentication, postgresql login api, sql login endpoint, claims postgresql, jwt cookie bearer auth, current_setting claims, user parameters"}],["meta",{"property":"og:title","content":"NpgsqlRest Authentication Guide"}],["meta",{"property":"og:description","content":"Configure schemes, write login endpoints, and access user claims in SQL."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/authentication.md","filePath":"guide/authentication.md"}'),l={name:"guide/authentication.md"};function p(h,i,r,k,o,d){return t(),e("div",null,i[0]||(i[0]=[a("",46),s("div",{class:"table-container"},[s("div",{class:"table-wrapper"},[s("table",{tabindex:"0"},[s("thead",null,[s("tr",null,[s("th",null,"user_id"),s("th",null,"username"),s("th",null,"email"),s("th",null,"roles")])]),s("tbody",null,[s("tr",null,[s("td",null,"1"),s("td",null,"alice"),s("td",null,[s("a",{href:"mailto:alice@example.com",target:"_blank",rel:"noreferrer"},"alice@example.com")]),s("td",{admin:""})])])])])],-1),a("",46)]))}const g=n(l,[["render",p]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_index.md.B7c1xVM5.js b/assets/guide_changelog_index.md.B7c1xVM5.js new file mode 100644 index 000000000..0724ab884 --- /dev/null +++ b/assets/guide_changelog_index.md.B7c1xVM5.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/index.md","filePath":"guide/changelog/index.md"}'),d={name:"guide/changelog/index.md"};function r(l,e,n,s,c,h){return o(),a("div",null,e[0]||(e[0]=[i('

    Changelog

    Select a version below to view the full changelog.

    Note: The changelog for versions older than 3.0 can be found here: Changelog Archive


    Version 3.19 (Latest)

    VersionDate
    v3.19.02026-07-03
    • New: SQL test runner (npgsqlrest --test) — write endpoint tests as plain .sql files: boolean-SELECT and DO-block assertions, in-process endpoint invocation via embedded HTTP blocks (# @claim principals, response captured into a temp table), per-file isolated non-pooled connections running in parallel, Setup/Teardown steps with named-step registry and per-step connections, dedicated test databases with {rnd} tokens, \\i/\\ir script includes with paste semantics, per-file -- @setup/-- @teardown/-- @connection/-- @tag annotations, path filtering (Filter) and tag filtering (Tag/ExcludeTag), watch mode (--watch) with in-process endpoint rebuilds, endpoint coverage reporting (on by default for full runs) with a CI threshold gate, JUnit XML output, and guaranteed teardown on Ctrl+C/SIGTERM and hard exits
    • New: watch mode (--watch) — two modes: with --test it re-runs tests on changes (endpoint files rebuild in-process); without --test it supervises the server and restarts it on SQL file source and configuration changes, regenerating code (TypeScript client, HTTP files, OpenAPI) on every cycle
    • New: named parameters in SQL fileswhere email = :email instead of $1; the placeholder is the parameter name (camelCase-converted for the API), repeated names map to one parameter (also across statements), claim mappings hook up by placeholder name, and the new @param name type is type form retypes without renaming
    • New: SqlFileSource.SkipPattern (default "*.test.sql") — exclude files from endpoint discovery by glob
    • New: Log:MinimalLevels entries accept "Off" ("None", "Silent") to fully mute an individual logger

    Version 3.18

    VersionDate
    v3.18.22026-06-26
    v3.18.12026-06-23
    v3.18.02026-06-23
    • New: ProxyOptions.MaxForwardedQueryParamLength (default 2048) — a server-filled value too long for the proxy query string is skipped with a warning instead of producing an unusable request line (HTTP 414/431); forward large values via a body-carrying method and @body_parameter_name
    • New: OmitAutomaticParameters on the TypeScript client, HTTP file, and OpenAPI generators (default false) — omit optional server-filled parameters (HTTP Custom Type fields, resolved-parameter expressions, upload metadata, IP/claim params) from generated request shapes
    • Fix: @body_parameter_name now matches an HTTP Custom Type field by its converted, actual, or expanded signature name, case-insensitively — applied consistently by request handling and all code generators (also fixes the HTTP file and OpenAPI generators leaving the field in the query string)
    • Fix: TypeScript client generation for @body_parameter_name endpoints — no leaked ? in the body property name, the body parameter is excluded from the query string, and no fetch body is emitted for GET
    • Fix: all automatic (server-filled) proxy parameters — user claims, IP, HTTP Custom Type fields, and resolved-parameter expressions — now forward to proxy endpoints uniformly, with placement following the endpoint's RequestParamType (query string or merged into the JSON body) rather than the HTTP verb
    • New: HTTP Custom Type response caching via the @cache directive — opt-in, GET-only outbound response caching with TTL, success-only storage, and stampede protection; configured globally under HttpClientOptions (CacheEnabled, MaxCacheEntries, CachePruneIntervalSeconds)
    • Fix: an HTTP Custom Type parameter on a database-function endpoint fired one outbound call per composite field (a 6-field type → 6 identical calls); now one call per distinct type, shared from a single response
    • Fix: @timeout, @retry_delay, and @cache directives placed after the headers (as the docs showed) were silently ignored — both before-request-line and after-headers placements are now equivalent

    Version 3.17

    VersionDate
    v3.17.02026-06-10
    • New plugin NpgsqlRest.Mcp — expose opted-in PostgreSQL routines as MCP tools (tools/list / tools/call over Streamable HTTP) via the @mcp annotation; a bare @mcp with no HTTP tag is an MCP-only tool with no public route
    • MCP OAuth 2.1 resource-server authorization: Protected Resource Metadata (RFC 9728), audience binding (RFC 8707), per-tool @authorize enforcement on tools/call
    • Neutral plugin extension points on RoutineEndpoint (HandleCommentLine, Items, UnhandledCommentLines) and new CommentsMode.OnlyAnnotated (now the client default)
    • New: {name} annotation substitution can resolve allowlisted environment variables (NpgsqlRest:AvailableEnvVars); matching is now case-insensitive, unknown placeholders log a build-time warning
    • New: optional {NAME} and required {!NAME} environment-variable placeholders in config values — missing optional variables no longer crash typed reads
    • Breaking: safer configuration defaults — Cors:AllowCredentials is now false, passkey UserVerificationRequirement / ResidentKeyRequirement default to "required", TestConnectionStrings defaults to true
    • Breaking (C# API only): RoutineEndpoint.OpenApiHide / OpenApiTags removed — the OpenAPI plugin parses the @openapi annotation itself; annotation users are unaffected
    • 🔴 Security fix: SSE per-event USING HINT scopes were not enforced — hint-scoped events were delivered to every subscriber; upgrade strongly recommended for hint-based SSE scoping
    • Fix: bare @cached (no parameter list) keyed only on the routine name, serving the first cached response to all inputs
    • Fix: HybridCache silently bypassed the cache on null cached parameters (Cache key contains invalid content)
    • Fix: malformed JSON request body now returns 400 Bad Request (was 404)
    • Fix: JSON command parameters accept json, jsonb, or text target types

    Version 3.16

    VersionDate
    v3.16.32026-06-03
    v3.16.22026-06-02
    v3.16.12026-06-01
    v3.16.02026-05-20
    • New: AvailableEnvVars under StaticFiles:ParseContentOptions templates environment-variable values into served static content (same {NAME} tags as claims) — build a SPA bundle once, inject per-environment values from pod env vars at boot

    • New: rate-limiter rejection StatusCode/StatusMessage are now overridable per policy (the global values stay as defaults); ships a ready-to-use disabled login_throttle policy

    • Fix: cache stampede protection now actually fires for cached routine responses (IRoutineCache.GetOrCreateAsync); a burst of identical cold-cache requests collapses to a single database execution

    • Fix: JSON-to-parameter parsers for timestamp, timestamptz, time, and timetz are now host-TZ-independent (silent host-offset shift removed)

    • Fix: TryParseDate falls back to a DateTime parse when DateOnly rejects offset/Z-bearing inputs

    • Breaking: JSON timestamps are now interpreted as UTC by default (naive ISO strings assumed UTC, Z / offset-bearing strings converted to UTC)

    • New NpgsqlRest:JsonTimestampsAreUtc config key — opt-out escape hatch to restore the pre-3.16.0 host-local interpretation


    Version 3.15

    VersionDate
    v3.15.22026-05-11
    v3.15.12026-05-11
    v3.15.02026-05-11
    • Auth: named cookie schemes now actually authenticate requests (cookie-aware policy-scheme dispatch)
    • New Auth:CookieSameSite and Auth:CookieSecure config keys for cross-origin SPA / mobile clients (root + per-scheme)
    • OpenAPI filtering: IncludeSchemas, ExcludeSchemas, NameSimilarTo, NameNotSimilarTo, RequiresAuthorizationOnly
    • New @openapi annotation — @openapi hide and @openapi tag <name> for per-routine OpenAPI control
    • Fix: Auth:Schemes keys validated by Type, not by name — custom schemes named like the docs examples no longer fail startup (3.15.1)
    • Fix: --config and --validate CLI commands honor ValidateConfigKeys mode (3.15.1)
    • Fix: RateLimiterOptions:Policies and CacheOptions:Profiles validate by shape — custom policy / profile names no longer fail startup under ValidateConfigKeys: "Error" (3.15.2)
    • Improvement: ValidationOptions:Rules rule bodies validated for typos (3.15.2)

    Version 3.14

    VersionDate
    v3.14.02026-05-09
    • Standalone client no longer wires the NpgsqlRest.CrudSource plugin (library use unchanged)
    • New SSE annotations @sse_publish and @sse_subscribe — split publisher and subscriber roles
    • Warning when a RAISE looks like a missed @sse_publish
    • Reliable SSE connection handshake
    • Startup error when claim-mapped parameters use a non-text type
    • Warning when a request value is overridden by claim auto-bind
    • Lower-allocation JSON conversion for arrays and composites
    • Hardening: ArrayPool rentals released in try/finally, column-decryption failures logged at Trace

    Version 3.13

    VersionDate
    v3.13.02026-04-24
    • Auth Schemes — named additional authentication schemes (Cookies / BearerToken / Jwt)
    • Login functions can select a scheme via the scheme column

    Version 3.12

    VersionDate
    v3.12.02026-03-23
    • New endpoint source plugin: NpgsqlRest.SqlFileSource — generate REST API endpoints directly from .sql files
    • Multi-command SQL files with batched execution and named result sets
    • New @param / @parameter annotation for renaming and retyping parameters across all endpoint types
    • Glob pattern ** recursive matching support
    • Interface refactoring: IEndpointSource / IRoutineSource split
    • TsClient: multi-command SQL file endpoint support
    • Composite type cache public API

    Version 3.11

    VersionDate
    v3.11.12026-03-13
    v3.11.02026-03-10
    • proxy_out annotation (post-execution proxy)
    • TsClient: proxy and proxy_out passthrough endpoint support
    • authorize annotation now matches user ID and user name claims

    Version 3.10

    VersionDate
    v3.10.02026-02-25
    • Resolved parameter expressions for server-side secret handling
    • HTTP Client Type retry logic (@retry_delay)
    • Data Protection encrypt/decrypt annotations

    Version 3.9

    VersionDate
    v3.9.02026-02-23
    • Commented configuration output (--config)
    • Configuration search and filter (--config [filter])
    • CLI improvements and test suite

    Version 3.8

    VersionDate
    v3.8.02025-02-11
    • Configuration key validation
    • Optional path parameters
    • Machine-readable CLI commands for tool integration
    • Universal fallback_handler for all upload handlers

    Version 3.7

    VersionDate
    v3.7.02025-02-07
    • Pluggable table format renderers (HTML, Excel)
    • TsClient per-endpoint URL export control
    • Excel upload handler fallback_handler

    Version 3.6

    VersionDate
    v3.6.32025-02-03
    v3.6.22025-02-02
    v3.6.12025-02-02
    v3.6.02025-02-01
    • Security headers middleware
    • Forwarded headers middleware
    • Health check endpoints
    • PostgreSQL statistics endpoints

    Version 3.5

    VersionDate
    v3.5.02025-01-28
    • PasskeyAuth (WebAuthn/FIDO2)
    • Response compression fix for static files
    • Separate core and client logging

    Version 3.4

    VersionDate
    v3.4.82025-01-26
    v3.4.72025-01-21
    v3.4.62025-01-21
    v3.4.52025-01-19
    v3.4.42025-01-17
    v3.4.32025-01-16
    v3.4.22025-01-15
    v3.4.12025-01-15
    v3.4.02025-01-16
    • Composite type support (arrays, nested JSON)
    • Deep nested composite type resolution
    • Multidimensional array support
    • Performance optimizations (type category lookup, StringBuilder pooling, CancellationToken propagation)

    Version 3.3

    VersionDate
    v3.3.12025-01-14
    v3.3.02025-01-08
    • Parameter validation
    • Linux ARM64 build and Docker image
    • Proxy response caching
    • Optional @ prefix for comment annotations

    Version 3.2

    VersionDate
    v3.2.72025-01-05
    v3.2.62025-01-04
    v3.2.42025-01-03
    v3.2.32025-12-30
    v3.2.22025-12-24
    v3.2.12025-12-23
    v3.2.02025-12-22
    • Reverse proxy feature
    • JWT authentication support
    • HybridCache support
    • Docker image with Bun runtime

    Version 3.1

    VersionDate
    v3.1.32025-12-21
    v3.1.22025-12-20
    v3.1.12025-12-15
    v3.1.02025-12-13
    • HTTP Types (external API calls from PostgreSQL functions)
    • Path parameters support
    • SIMD-accelerated string processing
    • Routine caching improvements
    • Multi-host connection support

    Version 3.0

    VersionDate
    v3.0.12025-11-28
    v3.0.02025-11-27
    • .NET 10 target framework
    • Rate limiter
    • OpenAPI 3.0 support
    • Error handling improvements (RFC 7807 Problem Details)
    • TsClient improvements
    • SSE (Server-Sent Events) naming refactor
    ',83)]))}const g=t(d,[["render",r]]);export{p as __pageData,g as default}; diff --git a/assets/guide_changelog_index.md.B7c1xVM5.lean.js b/assets/guide_changelog_index.md.B7c1xVM5.lean.js new file mode 100644 index 000000000..3ff9a4215 --- /dev/null +++ b/assets/guide_changelog_index.md.B7c1xVM5.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/index.md","filePath":"guide/changelog/index.md"}'),d={name:"guide/changelog/index.md"};function r(l,e,n,s,c,h){return o(),a("div",null,e[0]||(e[0]=[i("",83)]))}const g=t(d,[["render",r]]);export{p as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.0.0.md.BCZYSJDP.js b/assets/guide_changelog_v3.0.0.md.BCZYSJDP.js new file mode 100644 index 000000000..92d530f37 --- /dev/null +++ b/assets/guide_changelog_v3.0.0.md.BCZYSJDP.js @@ -0,0 +1,315 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.0.0 (2025-11-27)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.0.0.md","filePath":"guide/changelog/v3.0.0.md"}'),l={name:"guide/changelog/v3.0.0.md"};function e(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.0.0 (2025-11-27)

    Version 3.0.0 (2025-11-27)

    Full Changelog

    Docker JIT Version

    • New Docker image with .NET 10 JIT runtime: npgsqlrest/npgsqlrest:3.0.0-jit
    • This image uses the standard .NET 10 runtime with JIT compilation instead of AOT compilation.
    • Suitable for development and scenarios where AOT compilation is not required.
    • JIT version can be faster to execute but slower startup time and larger image size compared to AOT version.

    Image Size Comparison (approximate):

    VersionSize
    AOT~80-100 MB
    JIT~200-250 MB

    .NET 10 Target Framework

    • Upgraded target framework to .NET 10.
    • Faster and more memory efficient.

    TsClient (Code Generation) Improvements

    1. When return value is JSON or JSONB, generated TypeScript type is any instead of string.
    2. New parameter annotation tsclient_module. Sets different module name for the generated TypeScript client file. For example: tsclient_module = test will create test.ts or test.js and add every and group endpoint to that module instead of the default.
    3. Fixed and improved generated JSDoc comments for better IntelliSense support in IDEs. JavaScript JSDoc invlude proper types and TypeScript JSDoc will not include types to avoid duplication. All parameters comment now include description.
    4. SSE generated parameters signature changed.

    Fetch for SSE enabled endpoint now looks like this:

    typescript
    typescript
    /**
    + * function test_sse()
    + * returns table(
    + *     id integer
    + * )
    + *
    + * @remarks
    + * comment on function test_sse is 'HTTP GET
    + * authorize
    + * upload for file_system
    + * sse
    + * tsclient_module = test';
    + *
    + * @param onMessage - Optional callback function to handle incoming SSE messages.
    + * @param id - Optional execution ID for SSE connection. When supplied, only EventSource object with this ID in query string will will receive events.
    + * @param closeAfterMs - Time in milliseconds to wait before closing the EventSource connection. Used only when onMessage callback is provided.
    + * @param awaitConnectionMs - Time in milliseconds to wait after opening the EventSource connection before sending the request. Used only when onMessage callback is provided.
    + * @returns {status: number, response: ITestSseResponse[]}
    + *
    + * @see FUNCTION test_sse
    + */
    +export async function testSse(
    +    onMessage?: (message: string) => void,
    +    id: string | undefined = undefined,
    +    closeAfterMs = 1000,
    +    awaitConnectionMs: number | undefined = 0
    +) : Promise<{status: number, response: ITestSseResponse[]}> {
    +    const executionId = id ? id : window.crypto.randomUUID();
    +    let eventSource: EventSource;
    +    if (onMessage) {
    +        eventSource = createTestSseEventSource(executionId);
    +        eventSource.onmessage = (event: MessageEvent) => {
    +            onMessage(event.data);
    +        };
    +        if (awaitConnectionMs !== undefined) {
    +            await new Promise(resolve => setTimeout(resolve, awaitConnectionMs));
    +        }
    +    }
    +    try {
    +        const response = await fetch(baseUrl + "/api/test-sse", {
    +            method: "GET",
    +            headers: {
    +                "Content-Type": "application/json",
    +                "X-test-ID": executionId
    +            },
    +        });
    +        return {
    +            status: response.status,
    +            response: response.status == 200 ? await response.json() as ITestSseResponse[] : await response.text() as any
    +        };
    +    }
    +    finally {
    +        if (onMessage) {
    +            setTimeout(() => eventSource.close(), closeAfterMs);
    +        }
    +    }
    +}

    Info Events Streaming Changes (Server-Sent Events)

    • Rename configuration key from CustomServerSentEventsResponseHeaders to ServerSentEventsResponseHeaders.
    • Option from CustomServerSentEventsResponseHeaders to SseResponseHeaders.
    • Comment annotations:
      • from info_path, info_events_path, info_streaming_path to sse, sse_path, sse_events_path
      • from info_scope, info_events_scope, info_streaming_scope to sse_scope, sse_events_scope

    Removed Self scope level

    • Removed self scope level for SSE events. Only matching, authorize, and all levels are supported now.
    • Event will always be skipped if executing id is supplied in request header and in event source query parameter, and they don't match.

    New Feature: Support for custom notice level

    • New option and configuration:
      • configuration: DefaultServerSentEventsEventNoticeLevel
      • option: public PostgresNoticeLevels DefaultSseEventNoticeLevel { get; set; } = PostgresNoticeLevels.INFO;

    Set the default notice level for SSE events when not specified in comment annotation. When SSE path is set, generate SSE events for PostgreSQL notice messages with this level or higher.

    Other Comment Annotations Changes

    • Setting SSE path (and optionally notice level) via comment annotations:
    code
    sse [ path ] [ on info | notice | warning ] 
    +sse_path [ path ] [ on info | notice | warning ]
    +sse_events_path [ path ] [ on info | notice | warning ]

    Without argument, just sse or sse_path or sse_events_path, will set the path to default, which depends on default level (info for INFO level, notice for NOTICE level, etc).

    Single argument is treated as path.

    If path is followed by on info or on notice or on warning, it will set the notice level accordingly.

    Note: you can also set sse path using parameter annotations syntax (key = value), for example sse = /my_sse_path or sse_path = /my_sse_path.

    • New comment annotations to set custom SSE event notice level per endpoint:
    code
    sse_level [ info | notice | warning ]
    +sse_events_level [ info | notice | warning ]

    Note: you can also set sse level using parameter annotations syntax (key = value), for example sse_level = info, etc.

    • Scope annotations changed name to match new SSE naming:
    code
    sse_scope [ [ matching | authorize | all ] | [ authorize [ role_or_user1, role_or_user1, role_or_user1 [, ...] ] ] ] 
    +sse_events_scope [ [ matching | authorize | all ] | [ authorize [ role_or_user1, role_or_user1, role_or_user1 [, ...] ] ] ]

    Timeout Handling

    • Timeouts are not retried automatically by NpgsqlRest anymore.
    • Timeout error policy can be set in ErrorHandlingOptions section of client configuration.
    • Default mapping for timeout errors: "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}
    • Configuration option CommandTimeout is using PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) instead of integer seconds.
    • Comment annotation is also now using PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) instead of integer seconds.
    • Option CommandTimeout is now TimeSpan? instead of int.

    OpenAPI 3.0 Support

    Added OpenAPI 3.0 support with the new NpgsqlRest.OpenApi plugin (available as a separate NuGet package as library plugin).

    Also, added new client configuration section OpenApiOptions to configure OpenAPI generation and serving.

    New configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // Enable or disable the generation of OpenAPI files for NpgsqlRest endpoints.
    +    //
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      //
    +      // File name for the generated OpenAPI file. Set to null to skip the file generation.
    +      //
    +      "FileName": "npgsqlrest_openapi.json",
    +      //
    +      // URL path for the OpenAPI endpoint. Set to null to skip the endpoint generation.
    +      //
    +      "UrlPath": "/openapi.json",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // The title of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // If not set, the database name from the ConnectionString will be used.
    +      //
    +      "DocumentTitle": null,
    +      //
    +      // The version of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // When null, default is "1.0.0".
    +      //
    +      "DocumentVersion": "1.0.0",
    +      //
    +      // Optional description of the API. This appears in the "info" section of the OpenAPI specification.
    +      //
    +      "DocumentDescription": null,
    +      //
    +      // Include current server information in the "servers" section of the OpenAPI document.
    +      //
    +      "AddCurrentServer": true,
    +      //
    +      // Additional server entries to add to the "servers" section of the OpenAPI document.
    +      // Each server entry must have "Url" property and optional "Description" property.
    +      //
    +      "Servers": [/*{"Url": "https://api.example.com", "Description": "Production server"}*/],
    +      //
    +      // Security schemes to include in the OpenAPI document.
    +      // If not specified, a default Bearer authentication scheme will be added for endpoints requiring authorization.
    +      // Supported types: "Http" (for Bearer/Basic auth) and "ApiKey" (for Cookie/Header/Query auth).
    +      // Examples:
    +      // - Bearer token: {"Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT"}
    +      // - Cookie auth: {"Name": "cookieAuth", "Type": "ApiKey", "In": ".AspNetCore.Cookies", "ApiKeyLocation": "Cookie"}
    +      // - Basic auth: {"Name": "basicAuth", "Type": "Http", "Scheme": "Basic"}
    +      //
    +      "SecuritySchemes": [
    +        /*{
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }*/
    +      ]
    +    }
    +  }
    +}

    Error Handling Improvements

    Added comprehensive error handling improvements with standardized error responses using Problem Details (RFC 7807) format.

    json
    json
    {
    +  "title": "Error message or custom title",
    +  "status": 400,
    +  "detail": "P0001"
    +}

    Old error handling options have been removed in favor of a more flexible and extensible error code policy system.

    • Removed obsolete configuration options from client configuration:
    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // Set to true to return message from NpgsqlException on response body. Default is true.
    +    //
    +    "ReturnNpgsqlExceptionMessage": true,
    +    //
    +    // Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes. Default is 57014 query_canceled to 205 Reset Content.
    +    //
    +    "PostgreSqlErrorCodeToHttpStatusCodeMapping": {
    +      "57014": 205,
    +      "P0001": 400,
    +      // PL/pgSQL raise exception
    +      "P0004": 400
    +      // PL/pgSQL assert failure
    +    }
    +  }
    +}
    • Removed options:
    csharp
    csharp
        /// <summary>
    +    /// Set to true to return message from NpgsqlException on response body. Default is true.
    +    /// </summary>
    +    public bool ReturnNpgsqlExceptionMessage { get; set; } = true;
    +
    +    /// <summary>
    +    /// Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes
    +    /// Default is 57014 query_canceled to 205 Reset Content.
    +    /// </summary>
    +    public Dictionary<string, int> PostgreSqlErrorCodeToHttpStatusCodeMapping { get; set; } = new()
    +    {
    +        { "57014", 205 }, //query_canceled -> 205 Reset Content
    +        { "P0001", 400 }, // raise_exception -> 400 Bad Request
    +        { "P0004", 400 }, // assert_failure -> 400 Bad Request
    +    };
    • Added new configuration section in client configuration:
    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    // Remove Type URL from error responses. Middleware automatically sets a default Type URL based on the HTTP status code that points to the RFC documentation.
    +    "RemoveTypeUrl": false,
    +    // Remove TraceId field from error responses. Useful in development and debugging scenarios to correlate logs with error responses.
    +    "RemoveTraceId": true,
    +    //
    +    // Default policy name to use from the ErrorCodePolicies section.
    +    //
    +    "DefaultErrorCodePolicy": "Default",
    +    //
    +    // Timeout error mapping when command timeout occurs (see NpgsqlRest CommandTimeout setting).
    +    //
    +    "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}, // timeout error case -> 504 Gateway Timeout
    +    //
    +    // Named policies for mapping of PostgreSQL error codes to HTTP Status Codes.
    +    //
    +    // If routine raises these PostgreSQL error codes, endpoint will return these HTTP Status Codes.
    +    // See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +    // Exception is timeout, which is not a PostgreSQL error code, but a special case when command timeout occurs.
    +    //
    +    // - StatusCode: HTTP status code to return.
    +    // - Title: Optional title field in response JSON. When null, actual error message is used.
    +    // - Details: Optional details field in response JSON. When null, PostgreSQL Error Code is used.
    +    // - Type: Optional types field in response JSON. A URI reference [RFC3986] that identifies the problem type. Set to null to use default. Or RemoveTypeUrl to true to disable.
    +    //
    +    "ErrorCodePolicies": [{
    +      "Name": "Default",
    +      "ErrorCodes": {
    +        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},   // query_canceled      -> 403 Forbidden
    +        "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},                // query_canceled      -> 205 Reset Content
    +        "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // raise_exception     -> 400 Bad Request
    +        "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // assert_failure      -> 400 Bad Request
    +        "42883": {"StatusCode": 404, "Title": "Not Found", "Details": null, "Type": null},                // undefined_function  -> 404 Not Found
    +      }
    +    }]
    +  }
    +}
    • Added new options:
    csharp
    csharp
        /// <summary>
    +    /// Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes
    +    /// </summary>
    +    public ErrorHandlingOptions ErrorHandlingOptions { get; set; } = new();
    csharp
    csharp
    public class ErrorHandlingOptions
    +{
    +    public string? DefaultErrorCodePolicy { get; set; } = "Default";
    +    
    +    public ErrorCodeMappingOptions? TimeoutErrorMapping { get; set; } = new()
    +    {
    +        StatusCode = 504,
    +        Title = "Command execution timed out"
    +    };
    +
    +    public Dictionary<string, Dictionary<string, ErrorCodeMappingOptions>> ErrorCodePolicies { get; set; } = new()
    +    {
    +        ["Default"] = new()
    +        {
    +            { "42501", new() { StatusCode = 403, Title = "Insufficient Privilege" } },
    +            { "57014", new() { StatusCode = 205, Title = "Cancelled" } },
    +            { "P0001", new() { StatusCode = 400 } },
    +            { "P0004", new() { StatusCode = 400 } },
    +            { "42883", new() { StatusCode = 404, Title = "Not Found" } },
    +        }
    +    };
    +}
    • Added new comment annotations to set error code policy per endpoint:
    code
    error_code_policy_name [ name ]
    +error_code_policy [ name ]
    +error_code [ name ]

    For example:

    sql
    sql
    comment on function my_function(json) is 'error_code_policy custom_policy_name';
    +-- or
    +comment on function my_function(json) is 'error_code_policy_name custom_policy_name';
    +-- or
    +comment on function my_function(json) is 'error_code custom_policy_name';

    Metadata Query Improvements

    There two new options for Metadata queries support, that are also available in client configuration:

    • MetadataQueryConnectionName: Specify a named connection from ConnectionStrings dictionary to use for metadata queries. When null, the default connection string or data source is used.
    • MetadataQuerySchema: Set the PostgreSQL search path schema for metadata query functions. Useful when using non-superuser connection roles with limited schema access.

    Options:

    csharp
    csharp
    /// <summary>
    +/// The connection name in ConnectionStrings dictionary that will be used to execute the metadata query. If this value is null, the default connection string or data source will be used.
    +/// </summary>
    +public string? MetadataQueryConnectionName { get; set; } = null;
    +
    +/// <summary>
    +/// Set the search path to this schema that contains the metadata query function. Default is \`public\`.
    +/// </summary>
    +public string? MetadataQuerySchema { get; set; } = "public";
    json
    json
    {
    +  //
    +  // Additional connection settings and options.
    +  //
    +  "ConnectionSettings": {
    +    //
    +    // other ConnectionSettings settings
    +    //
    +    
    +    //
    +    // The connection name in ConnectionStrings configuration that will be used to execute the metadata query. If this value is null, the default connection string will be used.
    +    //
    +    "MetadataQueryConnectionName": null,
    +    //
    +    // Set the search path to this schema that contains the metadata query function. Default is \`public\`. Default is \`public\`. Set to null to avoid setting metadata query search path.
    +    //
    +    // This is needed when using non superuser connection roles with limited schema access and mapping the metadata function to a specific schema. 
    +    // If the connection string contains the same "Search Path=" it will be skipped.
    +    //
    +    "MetadataQuerySchema": "public"
    +  }
    +}

    Rate Limiter

    Added comprehensive rate limiting support with integration into ASP.NET Core's built-in rate limiting middleware:

    You can:

    • Configure rate limiting policies middleware manually (for library users).
    • Set rate limiter client configuration policies (for client app users).

    And then:

    • Set default rate limiter policy for all generated endpoints.
    • Set specific endpoint rate limiter policy.
    • Use comment annotation to set endpoint rate limiter policy.

    Client configuration:

    json
    json
    {
    +  //
    +  // Rate Limiter settings to limit the number of requests from clients.
    +  //
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    // Policy types: FixedWindow, SlidingWindow, BucketWindow, Concurrency
    +    "Policies": [{
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#fixed
    +      "Type": "FixedWindow",
    +      "Enabled": false,
    +      "Name": "fixed",
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#sliding-window-limiter
    +      "Type": "SlidingWindow",
    +      "Enabled": false,
    +      "Name": "sliding",
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "SegmentsPerWindow": 6,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#token-bucket-limiter
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "Name": "bucket",
    +      "TokenLimit": 100,
    +      "ReplenishmentPeriodSeconds": 10,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#concurrency-limiter
    +      "Type": "Concurrency",
    +      "Enabled": true,
    +      "Name": "concurrency",
    +      "PermitLimit": 10,
    +      "QueueLimit": 5,
    +      "OldestFirst": true
    +    }]
    +  }
    +}
    • Option to set default policy for all endpoints:
    csharp
    csharp
    /// <summary>
    +/// Default rate limiting policy for all requests. Policy must be configured within application rate limiting options.
    +/// This can be overridden by comment annotations in the database or setting policy for specific endpoints.
    +/// </summary>
    +public string? DefaultRateLimitingPolicy { get; set; } = null;
    • Endpoint property:
    csharp
    csharp
    public string? RateLimiterPolicy { get; set; } = null;
    • Comment annotations:
    code
    rate_limiter_policy_name [ name ]
    +rate_limiter_policy [ name ]
    +rate_limiter [ name ]

    Other Changes and Fixes

    • Major refactoring: generated endpoints moved from custom middleware to minimal APIs. This allows better integration with ASP.NET Core ecosystem (rate limiter, request timeout, etc).
    • Breaking change caused by the above: API invocation to existing paths with wrong HTTP method will return 405 Method Not Allowed instead of 404 Not Found.
    • More testing (concurrency).
    • Fix: fixed excessive logging when retrying failed commands.
    • Fix: missing command logging on void routines.
    • Refactoring: static Options instead of passing Options parameter around.
    • Refactoring: static Logger instead of passing Logger parameter around.
    • NpgsqlRest core project library has set InternalsVisibleTo to NpgsqlRestTests for testability for tests using Options or Logger.
    • Refactoring: moved some files around to better structure the project.
    • Removed unnecessary type casting when routine source returns set with embedded composite type.
    • Fix: fixed incorrect handling of types with modifier (e.g. varchar(100), numeric(10,2), etc). This causes type with modifiers to be serialized as incorrect type.
    • Fix: fixed incorrect parameter logging when parameters were added from user claims as string array (roles, permissions, etc).
    • Fix: user claims mapping to parameters or context will now by default be NULL when claim is null or empty string. Previous behavior was to map empty string as empty string.
    • Remove two logging options: LogEndpointCreatedInfo and LogAnnotationSetInfo. By default, all command parameters and values are logged at Debug level.
    • Refactor comment annotation paring for better maintainability.
    • .NET10 Upgrade.

    Login Endpoint Changes

    • Changed option NpgsqlRestAuthenticationOptions.MessageColumnName to NpgsqlRestAuthenticationOptions.BodyColumnName (and corresponding client configuration option) to better reflect its purpose.
    • Default value of NpgsqlRestAuthenticationOptions.BodyColumnName is now body instead of message.
    • Added new option NpgsqlRestAuthenticationOptions.ResponseTypeColumnName (and corresponding client configuration option) to specify the response type column name for login endpoint. Default is application/json.
    `,81)]))}const y=i(l,[["render",e]]);export{c as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.0.0.md.BCZYSJDP.lean.js b/assets/guide_changelog_v3.0.0.md.BCZYSJDP.lean.js new file mode 100644 index 000000000..02e4a56de --- /dev/null +++ b/assets/guide_changelog_v3.0.0.md.BCZYSJDP.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.0.0 (2025-11-27)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.0.0.md","filePath":"guide/changelog/v3.0.0.md"}'),l={name:"guide/changelog/v3.0.0.md"};function e(h,s,p,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t("",81)]))}const y=i(l,[["render",e]]);export{c as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.0.1.md.C5QoKstF.js b/assets/guide_changelog_v3.0.1.md.C5QoKstF.js new file mode 100644 index 000000000..aadf26b15 --- /dev/null +++ b/assets/guide_changelog_v3.0.1.md.C5QoKstF.js @@ -0,0 +1 @@ +import{_ as a,c as r,o as t,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.0.1 (2025-11-28)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.0.1.md","filePath":"guide/changelog/v3.0.1.md"}'),n={name:"guide/changelog/v3.0.1.md"};function i(l,e,s,g,c,h){return t(),r("div",null,e[0]||(e[0]=[o('

    Changelog v3.0.1 (2025-11-28)

    Version 3.0.1 (2025-11-28)

    Full Changelog

    • Fix: fix missing stack trace in AOT builds when exceptions are thrown.
    • Fix: Fix failing Docker JIT image build.
    • Change: removed error mapping for PostgreSQL error code 42883 (undefined_function) from HTTP 404 Not Found. Map it to default HTTP 500 Internal Server Error instead. This was confusing.
    ',4)]))}const _=a(n,[["render",i]]);export{p as __pageData,_ as default}; diff --git a/assets/guide_changelog_v3.0.1.md.C5QoKstF.lean.js b/assets/guide_changelog_v3.0.1.md.C5QoKstF.lean.js new file mode 100644 index 000000000..1b3f8a5f9 --- /dev/null +++ b/assets/guide_changelog_v3.0.1.md.C5QoKstF.lean.js @@ -0,0 +1 @@ +import{_ as a,c as r,o as t,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.0.1 (2025-11-28)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.0.1.md","filePath":"guide/changelog/v3.0.1.md"}'),n={name:"guide/changelog/v3.0.1.md"};function i(l,e,s,g,c,h){return t(),r("div",null,e[0]||(e[0]=[o("",4)]))}const _=a(n,[["render",i]]);export{p as __pageData,_ as default}; diff --git a/assets/guide_changelog_v3.1.0.md.Xl_94wGw.js b/assets/guide_changelog_v3.1.0.md.Xl_94wGw.js new file mode 100644 index 000000000..fd1c3a924 --- /dev/null +++ b/assets/guide_changelog_v3.1.0.md.Xl_94wGw.js @@ -0,0 +1,154 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Changelog v3.1.0 (2025-12-13)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.0.md","filePath":"guide/changelog/v3.1.0.md"}'),t={name:"guide/changelog/v3.1.0.md"};function l(p,s,h,r,k,c){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.1.0 (2025-12-13)

    Version 3.1.0 (2025-12-13)

    Full Changelog

    Http Types

    New feature that enables PostgreSQL functions to make HTTP requests to external APIs by using specially annotated composite types. When a function parameter uses a composite type with an HTTP definition comment, NpgsqlRest automatically invokes the HTTP request and populates the type fields with the response data before executing the function.

    Creating an HTTP Type:

    sql
    sql
    -- Create a composite type with response fields
    +create type weather_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);
    +
    +-- Add HTTP definition as a comment (RFC 7230 format)
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +timeout 30s';

    Using the HTTP Type in a function:

    sql
    sql
    create function get_weather(
    +  _city text,
    +  _api_key text,
    +  _req weather_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_req).success then
    +        return (_req).body::json;
    +    else
    +        return json_build_object('error', (_req).error_message);
    +    end if;
    +end;
    +$$;

    HTTP Definition Format:

    The comment on the composite type follows a simplified HTTP message format similar to .http files:

    code
    METHOD URL [HTTP/version]
    +Header-Name: Header-Value
    +...
    +
    +[request body]

    Supported HTTP methods: GET, POST, PUT, PATCH, DELETE

    Timeout Directives:

    Timeout can be specified before the request line using various formats:

    code
    timeout 30
    +timeout 30s
    +timeout 00:00:30
    +@timeout 2 minutes

    Response Fields:

    The composite type fields are automatically populated based on their names (configurable via HttpClientOptions):

    Field NameTypeDescription
    bodytextResponse body content
    status_codeint or textHTTP status code (e.g., 200, 404)
    headersjsonResponse headers as JSON object
    content_typetextContent-Type header value
    successbooleanTrue for 2xx status codes
    error_messagetextError message if request failed

    Placeholder Substitution:

    URLs, headers, and request body in the type comment can contain placeholders in the format {parameter_name}. These placeholders are automatically replaced with the values of other function parameters that share the same name.

    In the example above, the function get_weather has parameters _city and _api_key. The HTTP type comment contains placeholders {_city} and {_api_key} which are substituted with the actual parameter values when the HTTP request is made:

    sql
    sql
    -- Type comment with placeholders
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +timeout 30s';
    +
    +-- Function with matching parameter names
    +create function get_weather(
    +  _city text,        -- Value substitutes {_city} placeholder
    +  _api_key text,     -- Value substitutes {_api_key} placeholder
    +  _req weather_api   -- HTTP type parameter (receives response)
    +)
    +...

    When calling GET /api/get-weather?_city=London&_api_key=secret123, NpgsqlRest will:

    1. Substitute {_city} with London and {_api_key} with secret123
    2. Make the HTTP request to https://api.weather.com/v1/current?city=London with header Authorization: Bearer secret123
    3. Populate the _req parameter fields with the response data
    4. Execute the PostgreSQL function

    Configuration Options:

    Enable HTTP Types in NpgsqlRestOptions.HttpClientOptions options or in client configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // HTTP client functionality for annotated composite types.
    +    // Allows PostgreSQL functions to make HTTP requests by using specially annotated types as parameters.
    +    //
    +    "HttpClientOptions": {
    +      //
    +      // Enable HTTP client functionality for annotated types.
    +      //
    +      "Enabled": false,
    +      //
    +      // Default name for the response status code field within annotated types.
    +      //
    +      "ResponseStatusCodeField": "status_code",
    +      //
    +      // Default name for the response body field within annotated types.
    +      //
    +      "ResponseBodyField": "body",
    +      //
    +      // Default name for the response headers field within annotated types.
    +      //
    +      "ResponseHeadersField": "headers",
    +      //
    +      // Default name for the response content type field within annotated types.
    +      //
    +      "ResponseContentTypeField": "content_type",
    +      //
    +      // Default name for the response success field within annotated types.
    +      //
    +      "ResponseSuccessField": "success",
    +      //
    +      // Default name for the response error message field within annotated types.
    +      //
    +      "ResponseErrorMessageField": "error_message"
    +    }
    +  }
    +}

    Routine Caching Improvements

    Major improvements to the routine caching system for reliability, correctness, and expanded functionality:

    Cache Key Generation Fixes:

    • Fixed potential hash collisions by switching from integer hash codes to string-based cache keys.
    • Added separator character (\\x1F) between parameter values to prevent cache key collisions when parameter values concatenate to the same string (e.g., "ab" + "c" vs "a" + "bc" now produce different cache keys).
    • Added distinct null marker (\\x00NULL\\x00) to differentiate between null values and empty strings in cache keys.
    • Fixed array parameter serialization to properly include all array elements in the cache key with separators.

    Extended Caching Support for Records and Sets:

    Caching now works for set-returning functions and record types, not just single scalar values. When a cached function returns multiple rows, the entire result set is cached and returned on subsequent calls.

    New Configuration Option:

    Added MaxCacheableRows option to CacheOptions to limit memory usage when caching large result sets:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// Maximum number of rows that can be cached for set-returning functions.
    +    /// If a result set exceeds this limit, it will not be cached (but will still be returned).
    +    /// Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    +    /// Default is 1000 rows.
    +    /// </summary>
    +    public int? MaxCacheableRows { get; set; } = 1000;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CacheOptions": {
    +      "MaxCacheableRows": 1000
    +    }
    +  }
    +}

    Cache Key Hashing for Long Keys:

    Added optional SHA256 hashing for long cache keys to improve performance, especially with Redis cache. When enabled, cache keys exceeding a configurable threshold are automatically hashed to a fixed 64-character string, reducing:

    • Memory usage for storing long cache keys
    • Network transfer overhead with Redis
    • Redis server memory consumption

    New configuration options in CacheOptions:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// When true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string.
    +    /// This reduces memory usage for long cache keys and improves Redis performance with large keys.
    +    /// Default is false (cache keys are stored as-is).
    +    /// </summary>
    +    public bool UseHashedCacheKeys { get; set; } = false;
    +
    +    /// <summary>
    +    /// Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true.
    +    /// Keys shorter than this threshold are stored as-is for better debuggability.
    +    /// Default is 256 characters.
    +    /// </summary>
    +    public int HashKeyThreshold { get; set; } = 256;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "UseHashedCacheKeys": true,
    +    "HashKeyThreshold": 256
    +  }
    +}

    This is particularly recommended when:

    • Using Redis cache with routines that have many or large parameters
    • Caching routines with long SQL expressions
    • High cache hit rates where memory efficiency matters

    Cache Invalidation Endpoints:

    Added support for programmatic cache invalidation via auto-generated invalidation endpoints. When InvalidateCacheSuffix is configured, NpgsqlRest automatically creates an invalidation endpoint for each cached endpoint.

    For example, if you have a cached endpoint /api/get-user/ and set InvalidateCacheSuffix to "invalidate", NpgsqlRest will create /api/get-user/invalidate endpoint automatically.

    Calling the invalidation endpoint with the same parameters as the cached endpoint removes the corresponding cache entry:

    code
    GET /api/get-user/?id=123           -> Returns cached user data
    +GET /api/get-user/invalidate?id=123 -> Removes cache entry, returns {"invalidated":true}
    +GET /api/get-user/?id=123           -> Fresh data (cache was cleared)

    Key Features:

    • Same authentication and authorization as the original endpoint
    • Same parameter handling - no need to know the internal cache key format
    • Works correctly with hashed cache keys
    • Returns {"invalidated":true} if cache entry was removed, {"invalidated":false} if not found

    Configuration in CacheOptions:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// When set, creates an additional invalidation endpoint for each cached endpoint.
    +    /// The invalidation endpoint has the same path with this suffix appended.
    +    /// Default is null (no invalidation endpoints created).
    +    /// </summary>
    +    public string? InvalidateCacheSuffix { get; set; } = null;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "InvalidateCacheSuffix": "invalidate"
    +  }
    +}

    Multi-Host Connection Support

    Added support for PostgreSQL multi-host connections with failover and load balancing capabilities using Npgsql's NpgsqlMultiHostDataSource.

    Features:

    • Automatic detection of multi-host connection strings (connection strings with comma-separated hosts like Host=server1,server2)
    • Configurable target session attributes per connection: Any, Primary, Standby, PreferPrimary, PreferStandby, ReadWrite, ReadOnly
    • Seamless integration with existing named connections - multi-host data sources take priority over connection strings

    Configuration:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      // Default target for all multi-host connections
    +      "Default": "Any",
    +      // Per-connection overrides
    +      "ByConnectionName": {
    +        "readonly": "Standby",
    +        "primary": "Primary"
    +      }
    +    }
    +  }
    +}

    Example Multi-Host Connection String:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
    +  }
    +}

    Target Session Attributes:

    ValueDescription
    AnyAny successful connection is acceptable (default)
    PrimaryServer must not be in hot standby mode
    StandbyServer must be in hot standby mode
    PreferPrimaryTry primary first, fall back to any
    PreferStandbyTry standby first, fall back to any
    ReadWriteSession must accept read-write transactions
    ReadOnlySession must not accept read-write transactions

    See Npgsql Failover and Load Balancing for more details.

    New Options Property:

    Added DataSources property to NpgsqlRestOptions for storing multi-host data sources:

    csharp
    csharp
    /// <summary>
    +/// Dictionary of data sources by connection name. This is used for multi-host connection support.
    +/// When a connection name is specified in a routine endpoint, the middleware will first check
    +/// this dictionary for a data source. If not found, it falls back to the ConnectionStrings dictionary.
    +/// </summary>
    +public IDictionary<string, NpgsqlDataSource>? DataSources { get; set; }

    Other Changes and Fixes

    • Fixed default value on ErrorHandlingOptions.RemoveTraceId configuration setting. Default is true as it should be.
    • Fixed PostgreSQL parameter and result type mapping when default search path is not public.
    • Fixed type on TypeScript client generation when returing error. Errors now return JSON object instead of string.
    • Removed options.md, annotations.md, client.md and login-endpoints.md documentation files because dedicated website is now live: https://npgsqlrest.github.io/
    • Added missing CsvUploadKey with value "csv" in NpgsqlRest.UploadOptions.UploadHandlers configuration.
    • Moved authorization check after parameter parsing. This allows for endpoint to return proper 404 response codes when parameter is missing, instead of 400 when authorization fails.
    • When using custom types in PostgreSQL function parameters (composite types, enums, etc), and those parameters are not supplied in the request, they will now default to NULL always. Previous behavior was 404 Not Found when parameter was missing.
    • Fixed debug logging in ErrorHandlingOptions builder.
    • Fixed default mapping in ErrorHandlingOptions builder.
    • Added guard clause that returns error if serviceProvider is null when ServiceProviderMode is set
    • Removed 5 duplicate HttpClientOptions.Enabled blocks (kept 1)
    • Replaced un-awaited transaction?.RollbackAsync() with proper shouldCommit = false and uploadHandler?.OnError() for consistency with other error paths
    `,75)]))}const y=i(t,[["render",l]]);export{o as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.1.0.md.Xl_94wGw.lean.js b/assets/guide_changelog_v3.1.0.md.Xl_94wGw.lean.js new file mode 100644 index 000000000..3807487dc --- /dev/null +++ b/assets/guide_changelog_v3.1.0.md.Xl_94wGw.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Changelog v3.1.0 (2025-12-13)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.0.md","filePath":"guide/changelog/v3.1.0.md"}'),t={name:"guide/changelog/v3.1.0.md"};function l(p,s,h,r,k,c){return n(),a("div",null,s[0]||(s[0]=[e("",75)]))}const y=i(t,[["render",l]]);export{o as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.1.1.md.CP1dTmO5.js b/assets/guide_changelog_v3.1.1.md.CP1dTmO5.js new file mode 100644 index 000000000..c35b57794 --- /dev/null +++ b/assets/guide_changelog_v3.1.1.md.CP1dTmO5.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.1.1 (2025-12-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.1.md","filePath":"guide/changelog/v3.1.1.md"}'),l={name:"guide/changelog/v3.1.1.md"};function s(i,e,n,g,d,c){return o(),t("div",null,e[0]||(e[0]=[r('

    Changelog v3.1.1 (2025-12-15)

    Version 3.1.1 (2025-12-15)

    Full Changelog

    • Fixed schema usage for types not in defaults schemas. Narrow types selection for schemas with allowed usage.
    • Improved logging of parameter values in debug mode. Using PostgreSQL literal format for better readability.
    • Added version info log on startup.
    • Added executable location to version info output (--version).
    ',4)]))}const u=a(l,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.1.1.md.CP1dTmO5.lean.js b/assets/guide_changelog_v3.1.1.md.CP1dTmO5.lean.js new file mode 100644 index 000000000..efbd7a9aa --- /dev/null +++ b/assets/guide_changelog_v3.1.1.md.CP1dTmO5.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.1.1 (2025-12-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.1.md","filePath":"guide/changelog/v3.1.1.md"}'),l={name:"guide/changelog/v3.1.1.md"};function s(i,e,n,g,d,c){return o(),t("div",null,e[0]||(e[0]=[r("",4)]))}const u=a(l,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.1.2.md.DaCWRd65.js b/assets/guide_changelog_v3.1.2.md.DaCWRd65.js new file mode 100644 index 000000000..b28313329 --- /dev/null +++ b/assets/guide_changelog_v3.1.2.md.DaCWRd65.js @@ -0,0 +1,33 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.1.2 (2025-12-20)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.2.md","filePath":"guide/changelog/v3.1.2.md"}'),t={name:"guide/changelog/v3.1.2.md"};function l(r,s,p,o,h,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.1.2 (2025-12-20)

    Version 3.1.2 (2025-12-20)

    Full Changelog

    Performance: SIMD-Accelerated String Processing

    Added SIMD (Single Instruction, Multiple Data) optimizations using SearchValues<char> for faster string processing operations. These optimizations leverage hardware vector instructions (AVX2/SSE on x64, AdvSimd on ARM) to process multiple characters simultaneously.

    Optimized operations:

    • PostgreSQL array to JSON conversion (PgArrayToJsonArray): Faster parsing of array delimiters and escape sequences.
    • Composite type/tuple to JSON conversion (PgUnknownToJsonArray): Accelerated tuple field parsing.
    • String quoting and escaping (QuoteText): Vectorized quote detection with fast-path for strings without quotes.
    • Template string formatting (FormatString): SIMD-accelerated brace detection for URL and response templates.
    • Pattern matching (IsPatternMatch): Fast-path for patterns without wildcards and early-exit for non-matching prefixes.

    Where you'll see improvements:

    • APIs returning large PostgreSQL arrays (100+ elements): ~30-50% faster serialization
    • Bulk CSV uploads with many rows: Faster delimiter detection
    • Endpoints with complex URL templates: Reduced template processing overhead
    • High-throughput scenarios: Lower CPU usage per request

    These optimizations are automatic and require no configuration changes. Performance gains scale with input size - small inputs see modest improvements (~10-20%), while large arrays and bulk operations benefit significantly (~40-60%).

    Consistent JSON Error Responses

    All error responses (401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Internal Server Error) now consistently return a JSON body using the RFC 7807 Problem Details format:

    json
    json
    {
    +  "type": null,
    +  "title": "Unauthorized",
    +  "status": 401,
    +  "detail": null
    +}

    Previously, some error responses (particularly authorization failures) returned empty bodies or plain text. Now all endpoints return a consistent, parseable JSON error format regardless of the error type.

    EnvFile Configuration Option

    Added new EnvFile option to the Config section for loading environment variables from a .env file:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": ".env"
    +  }
    +}

    When AddEnvironmentVariables or ParseEnvironmentVariables is true and the EnvFile path is set, the application will load environment variables from the specified file. The file format supports:

    • KEY=VALUE pairs (one per line)
    • Comments (lines starting with #)
    • Quoted values (both single and double quotes)

    Example .env file:

    code
    PGHOST=localhost
    +PGPORT=5432
    +PGDATABASE=example_db
    +PGUSER=postgres
    +PGPASSWORD=postgres

    The variables are loaded into the environment and made available for configuration parsing with the {ENV_VAR_NAME} syntax.

    TsClient: Configurable Error Expression and Type

    Added two new options to the TypeScript client code generator (TsClient) for customizing error handling in generated code:

    • ErrorExpression (default: "await response.json()"): The expression used to parse error responses. Allows customization for different error parsing strategies.
    • ErrorType (default: "{status: number; title: string; detail?: string | null} | undefined"): The TypeScript type annotation for error responses.

    These options are only used when IncludeStatusCode is true. Configuration example:

    json
    json
    {
    +  "ClientCodeGen": {
    +    "IncludeStatusCode": true,
    +    "ErrorExpression": "await response.json()",
    +    "ErrorType": "{status: number; title: string; detail?: string | null} | undefined"
    +  }
    +}

    Void functions and procedures now also return the error object when IncludeStatusCode is true.

    HybridCache Support

    Added HybridCache as a third caching option alongside Memory and Redis. HybridCache uses Microsoft's Microsoft.Extensions.Caching.Hybrid library to provide:

    • Stampede protection: Prevents multiple concurrent requests from hitting the database when cache expires
    • Optional Redis L2 backend: Can use Redis as a distributed secondary cache for sharing across instances
    • In-memory L1 cache: Fast local cache for frequently accessed data

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "UseRedisBackend": false,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false",
    +    "MaximumKeyLength": 1024,
    +    "MaximumPayloadBytes": 1048576,
    +    "DefaultExpiration": "5 minutes",
    +    "LocalCacheExpiration": "1 minute"
    +  }
    +}

    Cache types:

    • Memory: In-process memory cache (fastest, single instance only)
    • Redis: Distributed Redis cache (slower, shared across instances)
    • Hybrid: HybridCache with stampede protection, optionally backed by Redis

    When UseRedisBackend is false (default), HybridCache works as an in-memory cache with stampede protection. When true, it uses Redis as the L2 distributed cache for sharing across multiple application instances.

    Fixed IncludeSchemaInNames option to work correctly when UseRoutineNameInsteadOfEndpoint is false (the default).

    `,37)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.1.2.md.DaCWRd65.lean.js b/assets/guide_changelog_v3.1.2.md.DaCWRd65.lean.js new file mode 100644 index 000000000..755c10378 --- /dev/null +++ b/assets/guide_changelog_v3.1.2.md.DaCWRd65.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.1.2 (2025-12-20)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.2.md","filePath":"guide/changelog/v3.1.2.md"}'),t={name:"guide/changelog/v3.1.2.md"};function l(r,s,p,o,h,d){return n(),a("div",null,s[0]||(s[0]=[e("",37)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.js b/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.js new file mode 100644 index 000000000..0c2f78823 --- /dev/null +++ b/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.js @@ -0,0 +1,36 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Changelog v3.1.3 (2025-12-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.3.md","filePath":"guide/changelog/v3.1.3.md"}'),t={name:"guide/changelog/v3.1.3.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.1.3 (2025-12-21)

    Version 3.1.3 (2025-12-21)

    Full Changelog

    Path Parameters Support

    Added support for RESTful path parameters using the {param} syntax in URL paths. This allows defining routes like /products/{id} where parameter values are extracted directly from the URL path instead of query strings or request body.

    Usage:

    sql
    sql
    -- Single path parameter
    +create function get_product(p_id int) returns text language sql as 'select ...';
    +comment on function get_product(int) is '
    +HTTP GET /products/{p_id}
    +';
    +-- Call: GET /products/123 → p_id = 123
    +
    +-- Multiple path parameters
    +create function get_review(p_id int, review_id int) returns text language sql as 'select ...';
    +comment on function get_review(int, int) is '
    +HTTP GET /products/{p_id}/reviews/{review_id}
    +';
    +-- Call: GET /products/5/reviews/10 → p_id = 5, review_id = 10
    +
    +-- Path parameters with query string parameters
    +create function get_product_details(p_id int, include_reviews boolean default false) returns text language sql as 'select ...';
    +comment on function get_product_details(int, boolean) is '
    +HTTP GET /products/{p_id}/details
    +';
    +-- Call: GET /products/42/details?includeReviews=true → p_id = 42, include_reviews = true
    +
    +-- Path parameters with JSON body (POST/PUT)
    +create function update_product(p_id int, new_name text) returns text language sql as 'select ...';
    +comment on function update_product(int, text) is '
    +HTTP POST /products/{p_id}
    +';
    +-- Call: POST /products/7 with body {"newName": "New Name"} → p_id = 7, new_name = "New Name"

    Key features:

    • Path parameter names in {param} can use either the PostgreSQL name ({p_id}) or the converted camelCase name ({pId}), matching is case-insensitive
    • Works with all HTTP methods (GET, POST, PUT, DELETE)
    • Can be combined with query string parameters (GET/DELETE) or JSON body parameters (POST/PUT)
    • Supports all parameter types (int, text, uuid, bigint, etc.)
    • TsClient generates template literal URLs: \`\${baseUrl}/products/\${request.pId}\`
    • New ParamType.PathParam enum value for identifying path-sourced parameters
    • Zero performance impact on endpoints without path parameters

    TsClient Improvements

    • Fixed parseQuery helper being unnecessarily included in generated TypeScript files when all function parameters are path parameters (no query string parameters remain).
    • Added comprehensive test coverage for TsClient TypeScript generation including tests for: path parameters, status code responses, tsclient_parse_url, tsclient_parse_request, file upload endpoints, SSE endpoints, and combined upload+SSE endpoints.

    HybridCache Configuration Keys Renamed

    HybridCache-specific configuration keys in the CacheOptions section have been renamed to include the HybridCache prefix for better clarity and consistency:

    Old KeyNew Key
    UseRedisBackendHybridCacheUseRedisBackend
    MaximumKeyLengthHybridCacheMaximumKeyLength
    MaximumPayloadBytesHybridCacheMaximumPayloadBytes
    DefaultExpirationHybridCacheDefaultExpiration
    LocalCacheExpirationHybridCacheLocalCacheExpiration

    Migration: Update your appsettings.json to use the new key names:

    json
    json
    {
    +  "CacheOptions": {
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "HybridCacheLocalCacheExpiration": "1 minute"
    +  }
    +}

    `,17)]))}const g=i(t,[["render",l]]);export{o as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.lean.js b/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.lean.js new file mode 100644 index 000000000..cd9cf7532 --- /dev/null +++ b/assets/guide_changelog_v3.1.3.md.BMixLZ4Y.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Changelog v3.1.3 (2025-12-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.1.3.md","filePath":"guide/changelog/v3.1.3.md"}'),t={name:"guide/changelog/v3.1.3.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",17)]))}const g=i(t,[["render",l]]);export{o as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.10.0.md.CCimYYDV.js b/assets/guide_changelog_v3.10.0.md.CCimYYDV.js new file mode 100644 index 000000000..3d9176ff4 --- /dev/null +++ b/assets/guide_changelog_v3.10.0.md.CCimYYDV.js @@ -0,0 +1,100 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.10.0 (2026-02-25)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.10.0.md","filePath":"guide/changelog/v3.10.0.md"}'),t={name:"guide/changelog/v3.10.0.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n(`

    Changelog v3.10.0 (2026-02-25)

    Version 3.10.0 (2026-02-25)

    Full Changelog

    New Feature: Resolved Parameter Expressions

    When using HTTP Client Types, sensitive values like API tokens or secrets are often needed in outgoing HTTP requests (e.g., in an Authorization header). Previously, these values had to be supplied as regular HTTP parameters — exposing them to the client and requiring an insecure round-trip: database → client → server → external API.

    Resolved parameter expressions solve this by allowing function parameters to be resolved server-side via SQL expressions defined in comment annotations. The resolved values are used in HTTP Client Type placeholder substitution (headers, URL, body) and are also passed to the PostgreSQL function — but they never appear in or originate from the client HTTP request.

    How It Works

    If a comment annotation uses the existing key = value syntax and the key matches an actual function parameter name, the value is treated as a SQL expression to execute at runtime:

    sql
    sql
    create type my_api_response as (body json, status_code int);
    +comment on type my_api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_secure_data(
    +    _user_id int,
    +    _req my_api_response,
    +    _token text default null
    +)
    +returns table (body json, status_code int)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code;
    +end;
    +$$;
    +comment on function get_secure_data(int, my_api_response, text) is '
    +_token = select api_token from user_tokens where user_id = {_user_id}
    +';

    The client calls GET /api/get-secure-data/?user_id=42. The server:

    1. Fills _user_id from the query string (value 42).
    2. Executes the resolved expression: select api_token from user_tokens where user_id = $1 (parameterized, with $1 = 42).
    3. Sets _token to the result (e.g., "secret-abc").
    4. Substitutes {_token} in the outgoing HTTP request header: Authorization: Bearer secret-abc.
    5. Makes the HTTP call and returns the response.

    The token never leaves the server. The client never sees it.

    Behavior

    • Server-side only: Resolved parameters cannot be overridden by client input. Even if the client sends &token=hacked, the DB-resolved value is used.
    • NULL handling: If the SQL expression returns no rows or NULL, the parameter is set to DBNull.Value (empty string in placeholder substitution).
    • Name-based placeholders, parameterized execution: Placeholders like {_user_id} reference other function parameters by name — the value is always looked up by name, regardless of position. Internally, placeholders are converted to positional $N parameters for safe execution (preventing SQL injection).
    • Sequential execution: When multiple parameters are resolved, expressions execute one-by-one on the same connection, in annotation order.
    • Works with user_params: Resolved expressions can reference parameters auto-filled from JWT claims via user_params, enabling fully zero-parameter authenticated calls.

    Multiple Resolved Parameters

    Multiple parameters can each have their own resolved expression:

    sql
    sql
    comment on function my_func(text, my_type, text, text) is '
    +_token = select api_token from tokens where user_name = {_name}
    +_api_key = select ''static-key-'' || api_token from tokens where user_name = {_name}
    +';

    Resolved Parameters in URL, Headers, and Body

    Resolved values participate in all HTTP Client Type placeholder locations — URL path segments, headers, and request body templates:

    sql
    sql
    -- URL: GET https://api.example.com/resource/{_secret_path}
    +-- Header: Authorization: Bearer {_token}
    +-- Body: {"token": "{_token}", "data": "{_payload}"}

    New Feature: HTTP Client Type Retry Logic

    When using HTTP Client Types, outgoing HTTP requests to external APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. Previously, a single failure was passed directly to the PostgreSQL function with no opportunity to retry.

    The new @retry_delay directive adds configurable automatic retries with delays, defined in the HTTP type comment alongside existing directives like timeout.

    Syntax

    sql
    sql
    -- Retry on any failure (non-2xx status, timeout, or network error):
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s
    +GET https://api.example.com/data';
    +
    +-- Retry only on specific HTTP status codes:
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';
    +
    +-- Combined with timeout:
    +comment on type my_api_type is 'timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';

    The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1-second, 2-second, and 5-second delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

    Behavior

    • Without on filter: Retries on any non-success HTTP response, timeout, or network error.
    • With on filter: Retries only when the HTTP response status code matches one of the listed codes (e.g., 429, 503). Timeouts and network errors always trigger retry regardless of the filter, since they have no status code.
    • Retry exhaustion: If all retries fail, the last error (status code, error message) is passed to the PostgreSQL function — the same as if retries were not configured.
    • Unexpected exceptions: Non-HTTP errors (e.g., invalid URL) are never retried.
    • Parallel execution: Each HTTP type in a function retries independently within its own parallel task. No changes to the parallel execution model.
    • No external dependencies: Built-in retry loop, no Polly or other libraries required. Matches the existing PostgreSQL command retry pattern.

    Example

    sql
    sql
    create type rate_limited_api as (body json, status_code int, error_message text);
    +comment on type rate_limited_api is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_rate_limited_data(
    +    _token text,
    +    _req rate_limited_api
    +)
    +returns table (body json, status_code int, error_message text)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code, (_req).error_message;
    +end;
    +$$;

    If the external API returns 429 (rate limited), the request is automatically retried after 1s, then 2s, then 5s. If it returns 400 (bad request), no retry occurs and the error is returned immediately.

    New Feature: Data Protection Encrypt/Decrypt Annotations

    Two new comment annotations — encrypt and decrypt — enable transparent application-level column encryption using ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client. The database stores ciphertext; the API consumer sees plaintext. No pgcrypto or client-side encryption required.

    This is useful for storing PII (SSN, medical records, credit card numbers) or other sensitive data that must be encrypted at rest but is only ever looked up by an unencrypted key (e.g., user_id, patient_id).

    Prerequisite: The DataProtection section must be enabled in appsettings.json (it is by default). The DefaultDataProtector is automatically created from Data Protection configuration and passed to the NpgsqlRest authentication options.

    Encrypt Parameters

    Mark specific parameters to encrypt before they are sent to PostgreSQL:

    sql
    sql
    create function store_patient_ssn(_patient_id int, _ssn text)
    +returns void
    +language plpgsql as $$
    +begin
    +    insert into patients (id, ssn) values (_patient_id, _ssn)
    +    on conflict (id) do update set ssn = excluded.ssn;
    +end;
    +$$;
    +comment on function store_patient_ssn(int, text) is '
    +HTTP POST
    +encrypt _ssn
    +';

    The client calls POST /api/store-patient-ssn/ with {"patientId": 1, "ssn": "123-45-6789"}. The server encrypts _ssn using Data Protection before executing the SQL — the database stores ciphertext like CfDJ8N..., never the plaintext SSN.

    Use encrypt without arguments to encrypt all text parameters:

    sql
    sql
    comment on function store_all_secrets(text, text) is '
    +HTTP POST
    +encrypt
    +';

    Decrypt Result Columns

    Mark specific result columns to decrypt before returning to the client:

    sql
    sql
    create function get_patient(_patient_id int)
    +returns table(id int, ssn text, name text)
    +language plpgsql as $$
    +begin
    +    return query select p.id, p.ssn, p.name from patients p where p.id = _patient_id;
    +end;
    +$$;
    +comment on function get_patient(int) is '
    +decrypt ssn
    +';

    The client calls GET /api/get-patient/?patientId=1. The ssn column is decrypted from ciphertext back to "123-45-6789" before being included in the JSON response. The id and name columns are returned as-is.

    Use decrypt without arguments to decrypt all result columns:

    sql
    sql
    comment on function get_all_secrets(text) is '
    +decrypt
    +';

    Decrypt also works on scalar (single-value) return types:

    sql
    sql
    create function get_secret(_id int) returns text ...
    +comment on function get_secret(int) is 'decrypt';

    Full Roundtrip Example

    sql
    sql
    -- Store with encryption
    +create function store_secret(_key text, _value text) returns void ...
    +comment on function store_secret(text, text) is '
    +HTTP POST
    +encrypt _value
    +';
    +
    +-- Retrieve with decryption
    +create function get_secret(_key text) returns table(key text, value text) ...
    +comment on function get_secret(text) is '
    +decrypt value
    +';
    code
    POST /api/store-secret/  {"key": "api-key", "value": "sk-abc123"}
    +GET  /api/get-secret/?key=api-key  →  {"key": "api-key", "value": "sk-abc123"}

    The value is stored encrypted in PostgreSQL and decrypted transparently on read.

    Annotation Aliases

    AnnotationAliases
    encryptencrypted, protect, protected
    decryptdecrypted, unprotect, unprotected

    Behavior Notes

    • NULL values: NULL parameters are not encrypted (passed as DBNull). NULL columns are not decrypted (returned as JSON null).
    • Non-text types: Only string parameter values are encrypted. Integer, boolean, and other types are unaffected even when encrypt is used without arguments.
    • Decryption failures: If a column value cannot be decrypted (e.g., it was not encrypted, or keys were rotated/lost), the raw value is returned as-is — no error is thrown.
    • Key rotation: ASP.NET Data Protection maintains a key ring. Old keys still decrypt old ciphertext. Keys rotate based on DefaultKeyLifetimeDays (default: 90 days). Persistent key storage (FileSystem or Database) is strongly recommended.
    • Encrypted columns are opaque to PostgreSQL: The database cannot filter, join, sort, or index on encrypted values. Use encryption only for columns that are written and read back, never queried by content.

    Key Management

    Encrypt/decrypt relies on the existing DataProtection configuration in appsettings.json. The encryption keys must be persisted — if keys are lost, encrypted data is permanently unrecoverable.

    Key storage options (DataProtection:Storage):

    StorageDescriptionRecommendation
    "Default"OS default location. On Linux, keys are in-memory only and lost on restart.Windows only
    "FileSystem"Keys persisted to a directory (FileSystemPath). In Docker, use a volume mount.Good for single-instance
    "Database"Keys stored in PostgreSQL via GetAllElementsCommand / StoreElementCommand.Best for multi-instance

    Key rotation (DataProtection:DefaultKeyLifetimeDays, default: 90):

    Data Protection automatically rotates keys. New Protect() calls use the newest key. Old keys remain in the key ring and can still Unprotect() previously encrypted data. This means values encrypted months ago continue to decrypt correctly — the key ring grows over time, it doesn't replace old keys.

    Key encryption at rest (DataProtection:KeyEncryption):

    The keys themselves can be encrypted at rest using "Certificate" (X.509 .pfx file) or "Dpapi" (Windows only). Default is "None".

    Application name isolation (DataProtection:CustomApplicationName):

    The application name acts as an encryption isolation boundary. Different application names produce incompatible ciphertext — they cannot decrypt each other's data. When set to null (default), the current ApplicationName is used.

    Example minimal configuration for production use:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/var/lib/npgsqlrest/data-protection-keys",
    +    "DefaultKeyLifetimeDays": 90
    +  }
    +}

    Or using database storage:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)"
    +  }
    +}

    `,72)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.10.0.md.CCimYYDV.lean.js b/assets/guide_changelog_v3.10.0.md.CCimYYDV.lean.js new file mode 100644 index 000000000..ffadf22cc --- /dev/null +++ b/assets/guide_changelog_v3.10.0.md.CCimYYDV.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.10.0 (2026-02-25)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.10.0.md","filePath":"guide/changelog/v3.10.0.md"}'),t={name:"guide/changelog/v3.10.0.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n("",72)]))}const u=a(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.11.0.md.yn587hBX.js b/assets/guide_changelog_v3.11.0.md.yn587hBX.js new file mode 100644 index 000000000..3d1a1db94 --- /dev/null +++ b/assets/guide_changelog_v3.11.0.md.yn587hBX.js @@ -0,0 +1,42 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.11.0 (2026-03-10)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.11.0.md","filePath":"guide/changelog/v3.11.0.md"}'),t={name:"guide/changelog/v3.11.0.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n(`

    Changelog v3.11.0 (2026-03-10)

    Version 3.11.0 (2026-03-10)

    Full Changelog

    New Feature: proxy_out Annotation (Post-Execution Proxy)

    A new proxy mode that reverses the existing proxy flow: execute the PostgreSQL function first, then forward the function's result body to an upstream service. The upstream response is returned to the client.

    This enables a common pattern where business logic in PostgreSQL prepares a payload, and an external service performs processing that PostgreSQL cannot do — PDF rendering, image processing, ML inference, email sending, etc.

    Syntax

    code
    @proxy_out [ METHOD ] [ host_url ]

    Also aliased as forward_proxy (with or without @ prefix).

    How It Works

    code
    Client Request → NpgsqlRest
    +  → Execute PostgreSQL function
    +  → Forward function result as request body to upstream service
    +  → Forward original query string to upstream URL
    +  → Return upstream response to client

    Unlike the existing proxy annotation (which forwards the incoming request to upstream), proxy_out forwards the outgoing function result. The original request query string is forwarded to the upstream URL as-is. The client-facing HTTP method and the upstream HTTP method are independent — the client can send a GET while the upstream receives a POST.

    Basic Usage

    sql
    sql
    create function generate_report(report_id int)
    +returns json
    +language plpgsql as $$
    +begin
    +    return json_build_object(
    +        'title', 'Monthly Report',
    +        'data', (select json_agg(row_to_json(t)) from sales t where month = report_id)
    +    );
    +end;
    +$$;
    +
    +comment on function generate_report(int) is 'HTTP GET
    +@proxy_out POST https://render-service.internal/render';

    The client calls GET /api/generate-report/?reportId=3. The server:

    1. Executes generate_report(3) in PostgreSQL.
    2. Takes the returned JSON and POSTs it to https://render-service.internal/render/api/generate-report/?reportId=3 (original query string forwarded).
    3. Returns the upstream response (e.g., a rendered PDF) directly to the client with the upstream's content-type and status code.

    Query String Forwarding

    The original client query string is forwarded to the upstream service as-is. This allows the upstream to receive the same parameters that were used to invoke the function:

    sql
    sql
    create function generate_report(p_format text, p_id int)
    +returns json
    +language plpgsql as $$
    +begin
    +    return json_build_object('id', p_id, 'data', 'report');
    +end;
    +$$;
    +
    +comment on function generate_report(text, int) is 'HTTP GET
    +@proxy_out POST';

    Calling GET /api/generate-report/?pFormat=pdf&pId=123 executes the function, then POSTs the result to the upstream with ?pFormat=pdf&pId=123 appended to the URL.

    HTTP Method Override

    Specify which HTTP method to use for the upstream request:

    sql
    sql
    comment on function my_func() is 'HTTP GET
    +@proxy_out PUT';

    The client sends GET, but the upstream receives PUT with the function's result as the body.

    Custom Host

    Override the default ProxyOptions.Host per-endpoint:

    sql
    sql
    comment on function my_func() is 'HTTP GET
    +@proxy_out POST https://my-other-service.internal';

    Error Handling

    • If the function fails (database error, exception), the error is returned directly to the client — the proxy call is never made.
    • If the upstream fails (5xx, timeout, connection error), the upstream's error status and body are forwarded to the client (502 for connection errors, 504 for timeouts).

    Configuration

    Uses the same ProxyOptions configuration as the existing proxy annotation. ProxyOptions.Enabled must be true:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "https://api.example.com",
    +      "DefaultTimeout": "30 seconds"
    +    }
    +  }
    +}

    Performance

    • Zero overhead for non-proxy_out endpoints. The implementation adds only branch-not-taken boolean/null checks on the normal execution path (~4 nanoseconds).
    • Efficient byte forwarding. Function output is captured as raw bytes and forwarded directly via ByteArrayContent — no intermediate string allocation or double UTF-8 encoding.

    TsClient: proxy_out Endpoint Support

    The TypeScript client generator (NpgsqlRest.TsClient) now recognizes proxy_out endpoints and generates functions that return the raw Response object instead of a typed return value. Since the actual response comes from the upstream proxy service (not from the PostgreSQL function's return type), the generated function returns Promise<Response>, allowing the caller to handle the response appropriately (.json(), .blob(), .text(), etc.):

    typescript
    typescript
    // Generated for a proxy_out endpoint
    +export async function generateReport() : Promise<Response> {
    +    const response = await fetch(baseUrl + "/api/generate-report", {
    +        method: "GET",
    +    });
    +    return response;
    +}
    `,37)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.11.0.md.yn587hBX.lean.js b/assets/guide_changelog_v3.11.0.md.yn587hBX.lean.js new file mode 100644 index 000000000..21a87df9a --- /dev/null +++ b/assets/guide_changelog_v3.11.0.md.yn587hBX.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.11.0 (2026-03-10)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.11.0.md","filePath":"guide/changelog/v3.11.0.md"}'),t={name:"guide/changelog/v3.11.0.md"};function l(p,s,r,h,o,d){return e(),i("div",null,s[0]||(s[0]=[n("",37)]))}const u=a(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.11.1.md.DxsV24Au.js b/assets/guide_changelog_v3.11.1.md.DxsV24Au.js new file mode 100644 index 000000000..e6a51d3fa --- /dev/null +++ b/assets/guide_changelog_v3.11.1.md.DxsV24Au.js @@ -0,0 +1,17 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.11.1 (2026-03-13)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.11.1.md","filePath":"guide/changelog/v3.11.1.md"}'),t={name:"guide/changelog/v3.11.1.md"};function l(p,s,h,r,o,c){return e(),a("div",null,s[0]||(s[0]=[n(`

    Changelog v3.11.1 (2026-03-13)

    Version 3.11.1 (2026-03-13)

    Full Changelog

    TsClient: proxy Passthrough Endpoint Support

    The TypeScript client generator (NpgsqlRest.TsClient) now recognizes proxy passthrough endpoints and generates functions that return the raw Response object, matching the existing proxy_out behavior. Previously, passthrough proxy endpoints (which typically use returns void) would generate Promise<void>, which was incorrect since the actual response comes from the upstream service.

    Now, both proxy and proxy_out endpoints generate Promise<Response>:

    typescript
    typescript
    // Generated for a proxy passthrough endpoint
    +export async function tsclientTestProxyPassthrough() : Promise<Response> {
    +    const response = await fetch(baseUrl + "/api/tsclient-test/proxy-passthrough", {
    +        method: "GET",
    +    });
    +    return response;
    +}

    This allows callers to handle the upstream response appropriately (.json(), .blob(), .text(), etc.), just like proxy_out endpoints.

    authorize Annotation Now Matches User ID and User Name Claims

    The authorize comment annotation previously only matched against role claims (DefaultRoleClaimType). It now also matches against user ID (DefaultUserIdClaimType) and user name (DefaultNameClaimType) claims, aligning with the behavior that sse_scope authorize already had.

    This means you can now restrict endpoint access to specific users, not just roles:

    sql
    sql
    -- Authorize by role (existing behavior)
    +comment on function get_reports() is 'authorize admin';
    +
    +-- Authorize by user name (new)
    +comment on function get_my_profile() is 'authorize john';
    +
    +-- Authorize by user ID (new)
    +comment on function get_account() is 'authorize user123';
    +
    +-- Mix of roles and user identifiers (new)
    +comment on function get_data() is 'authorize admin, user123, jane';

    The SSE matching scope was also aligned to check all three claim types, making authorization behavior consistent across all features.


    `,14)]))}const g=i(t,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.11.1.md.DxsV24Au.lean.js b/assets/guide_changelog_v3.11.1.md.DxsV24Au.lean.js new file mode 100644 index 000000000..b1f67b35f --- /dev/null +++ b/assets/guide_changelog_v3.11.1.md.DxsV24Au.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.11.1 (2026-03-13)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.11.1.md","filePath":"guide/changelog/v3.11.1.md"}'),t={name:"guide/changelog/v3.11.1.md"};function l(p,s,h,r,o,c){return e(),a("div",null,s[0]||(s[0]=[n("",14)]))}const g=i(t,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.12.0.md.BDVbkMod.js b/assets/guide_changelog_v3.12.0.md.BDVbkMod.js new file mode 100644 index 000000000..4e4a4a9b3 --- /dev/null +++ b/assets/guide_changelog_v3.12.0.md.BDVbkMod.js @@ -0,0 +1,222 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.12.0 (2026-03-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.12.0.md","filePath":"guide/changelog/v3.12.0.md"}'),t={name:"guide/changelog/v3.12.0.md"};function l(p,s,r,o,h,d){return e(),a("div",null,s[0]||(s[0]=[n(`

    Changelog v3.12.0 (2026-03-23)

    Version 3.12.0 (2026-03-23)

    Full Changelog


    New Endpoint Source Plugin: NpgsqlRest.SqlFileSource

    In addition to the existing endpoint sources — RoutineSource (PostgreSQL functions and procedures) and CrudSource (tables and views) — NpgsqlRest now supports a third source: SQL files.

    Generate REST API endpoints directly from .sql files. Place SQL files in a configured directory, and NpgsqlRest creates endpoints automatically — no PostgreSQL functions needed.

    How It Works

    1. At startup, the plugin scans the directory matching the configured glob pattern (e.g., sql/**/*.sql)
    2. Each .sql file is parsed: comments are extracted as annotations, SQL is split into statements
    3. Each statement is analyzed via PostgreSQL's wire protocol (SchemaOnly) — parameter types and return columns are inferred without executing the query
    4. A REST endpoint is created for each file, with the URL path derived from the filename

    Single-Command Files

    A file with one SQL statement produces a standard endpoint:

    sql
    sql
    -- sql/get_reports.sql
    +-- HTTP GET
    +-- @param $1 from_date
    +-- @param $2 to_date
    +-- @authorize admin
    +SELECT id, title, created_at
    +FROM reports
    +WHERE created_at BETWEEN $1 AND $2;

    GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31[{"id": 1, "title": "Q1", "createdAt": "..."}]

    HTTP verb auto-detection (when no explicit HTTP annotation):

    SQL StatementHTTP VerbRationale
    SELECT / WITH ... SELECTGETRead-only
    INSERTPUTCreation
    UPDATEPOSTModification
    DELETEDELETERemoval
    DO $$ ... $$POSTAnonymous script
    Mixed mutationsMost destructive winsDELETE > POST > PUT

    An explicit HTTP GET, HTTP POST, etc. annotation always overrides auto-detection.

    Note: DO blocks do not support $N parameters — this is a PostgreSQL language limitation. A DO block always produces a parameterless endpoint. In multi-command files, DO blocks work alongside parameterized statements — the other commands receive the shared parameters, the DO block receives none.

    Multi-Command Files

    A file with multiple statements (separated by ;) returns a JSON object. Each key corresponds to one command's result:

    sql
    sql
    -- sql/process_order.sql
    +-- HTTP POST
    +-- @param $1 order_id
    +-- @result validate
    +SELECT count(*) FROM orders WHERE id = $1;
    +UPDATE orders SET status = 'processing' WHERE id = $1;
    +-- @result confirm
    +SELECT id, status FROM orders WHERE id = $1;

    POST /api/process-order with {"order_id": 42}

    json
    json
    {
    +  "validate": [1],
    +  "result2": 1,
    +  "confirm": [{"id": 42, "status": "processing"}]
    +}

    Result set rules:

    • Commands returning rows → JSON array of row objects (same format as single-command endpoints)
    • Void commands (INSERT/UPDATE/DELETE without RETURNING) → rows-affected count as integer
    • Multi-command endpoints are never void — they always return a JSON object

    Result naming:

    • Default keys: result1, result2, result3, ... (prefix configurable via ResultPrefix setting)
    • Override with the positional @result annotation placed before the statement it applies to, or inline after the semicolon:
      • @result validate — renames the next result to validate
      • @result is validate — same ("is" style)
      • Commands without @result keep their default name

    Execution:

    • Uses NpgsqlBatch with one NpgsqlBatchCommand per statement — single database round-trip
    • All statements share the same parameters ($1, $2, etc.) — user sends each parameter once
    • Full retry logic via ExecuteBatchReaderWithRetryAsync with error code mapping and timeout handling
    • If any command fails, the entire request fails — no partial results

    Parameters

    SQL files use PostgreSQL positional parameters ($1, $2, ...). Parameters are passed via query string (GET) or JSON body (POST/PUT/DELETE):

    code
    GET /api/my-query?$1=hello&$2=42
    +POST /api/my-mutation {"$1": "hello", "$2": 42}

    Use the @param annotation for better names:

    sql
    sql
    -- @param $1 user_name
    +-- @param $2 age
    +SELECT * FROM users WHERE name = $1 AND age > $2;

    Now: GET /api/my-query?user_name=hello&age=42

    For multi-command files: Each statement is described individually. Parameter types are merged across all statements:

    • Same $N with same type across statements → use that type
    • Same $N with conflicting types → startup error with clear message (override with @param $1 name type)
    • $N used in only some statements → type from the statement(s) that reference it

    Virtual Parameters

    Use @define_param to create HTTP parameters that are NOT bound to the PostgreSQL command. These parameters exist for HTTP request matching, custom parameter placeholders, and claim mapping — without appearing in the SQL query.

    Use case: custom parameter placeholders — pass HTTP parameters that control endpoint behavior (e.g., output format) without referencing them in SQL:

    sql
    sql
    -- sql/users_report.sql
    +-- @define_param format text
    +-- @table_format = {format}
    +-- @param $1 department_id
    +SELECT id, name, email FROM users WHERE department_id = $1;

    GET /api/users-report?department_id=5&format=html_table — the format parameter feeds into the table_format custom parameter via {format} placeholder, selecting the output format (JSON, HTML table, etc.) without being part of the SQL query.

    Use case: claim mapping — auto-fill from user claims without SQL reference:

    sql
    sql
    -- @authorize
    +-- @user_parameters
    +-- @define_param _user_id
    +SELECT * FROM user_data;

    Default type is text; specify a type with @define_param name type.

    Comments and Annotations

    All comments in the SQL file are parsed as annotations, just like COMMENT ON FUNCTION in PostgreSQL:

    sql
    sql
    -- Line comments are annotations
    +/* Block comments are annotations too */
    +SELECT * FROM table;
    +-- Comments after statements also work

    All existing NpgsqlRest annotations work: @authorize, @allow_anonymous, @tag, @sse, @request_param_type, @path, @timeout, @cached, @raw, @header, @separator, @login, @logout, @encrypt, @decrypt, etc.

    New annotations for SQL files:

    AnnotationDescriptionExample
    @param $N nameRename positional parameter-- @param $1 user_id
    @param $N name typeRename + retype parameter-- @param $1 user_id integer
    @param $N is nameRename ("is" style)-- @param $1 is user_id
    @result nameRename multi-command result key (positional)-- @result validate
    @result is nameRename result key ("is" style, positional)-- @result is validate
    @define_param name [type]Define virtual parameter (not bound to SQL)-- @define_param _user_id

    CommentScope setting controls which comments are parsed:

    • All (default) — every comment in the file, regardless of position
    • Header — only comments before the first SQL statement

    Wire Protocol Introspection

    At startup, each statement is analyzed via PostgreSQL's Parse → Describe → Sync cycle (CommandBehavior.SchemaOnly):

    • Parameter types inferred from ParameterDescription message (authoritative OIDs)
    • Return columns inferred from RowDescription message (column names and types)
    • No query planning, no execution — roughly the cost of SELECT 1
    • Uses reader.GetName() / reader.GetDataTypeName() instead of GetColumnSchema() to avoid .NET type mapping failures for custom composite types
    • Unknown type OIDs (custom types returning "-.-") resolved via pg_catalog.pg_type query

    Custom / Composite Type Support

    Composite type columns in SQL file endpoints behave the same way as routine endpoints (functions and procedures) — flat by default, nested with the @nested annotation or NestedJsonForCompositeTypes setting. Arrays of composite types are also supported.

    Unnamed and Duplicate Columns

    SQL without column aliases:

    sql
    sql
    SELECT $1, $2;

    Produces valid JSON with unique fallback names instead of duplicate ?column? keys:

    json
    json
    [{"column1": "hello", "column2": "world"}]

    Use AS aliases for meaningful names: SELECT $1 AS name, $2 AS value.

    URL Path Derivation

    The endpoint path is derived from the filename (without .sql extension) using the same NameConverter as functions. For example, with the default camelCase converter:

    • get_reports.sql/api/get-reports
    • user_profile.sql/api/user-profile

    Override with the @path annotation: -- @path /custom/path/{id}

    Error Handling

    ModeBehaviorUse Case
    ParseErrorMode.Exit (default)Logs error, exits processFail-fast — catches SQL errors at startup
    ParseErrorMode.SkipLogs error, skips file, continuesProduction — tolerate partial failures

    All SQL file errors are logged at Error level. In Exit mode, a Critical log explains the exit and how to switch to Skip mode. PostgreSQL errors include compiler-like formatting with line:column position, source line excerpt, and a caret pointing at the error location:

    code
    SqlFileSource: /path/to/get-posts.sql:
    +error 42703: column u.id does not exist
    +  at line 3, column 12
    +  select u.id, u.name from users u
    +             ^

    A warning is logged when the configured file pattern matches no files.

    Errors caught at startup:

    • Parse errors (malformed SQL, unclosed strings/quotes)
    • Describe errors (PostgreSQL syntax errors, invalid table/column references)
    • Parameter type conflicts in multi-command files

    Feature Parity

    SQL file endpoints support all features available to function/procedure endpoints:

    • Composite type expansion (flat by default, nested with @nested annotation)
    • Response caching (cached, cache_expires_in)
    • Raw mode (raw, raw_value_separator, raw_new_line_separator, raw_column_names)
    • Binary mode
    • Encryption/decryption (encrypt, decrypt)
    • Table format handlers (e.g., HTML table output)
    • SSE events
    • Authorization (authorize, allow_anonymous)
    • Custom headers (header)
    • Retry logic with error code mapping
    • Buffer rows configuration
    • HTTP client types (@param $1 name http_type_name — composite type parameters with HTTP definitions)
    • Self-referencing HTTP client types — relative paths (e.g., GET /api/endpoint) call back to the same server instance, enabling parallel internal endpoint composition

    Configuration Reference

    json
    json
    "NpgsqlRest": {
    +  "SqlFileSource": {
    +    "Enabled": true,
    +    "FilePattern": "sql/**/*.sql",
    +    "CommentsMode": "ParseAll",
    +    "CommentScope": "All",
    +    "ErrorMode": "Exit",
    +    "ResultPrefix": "result",
    +    "UnnamedSingleColumnSet": true,
    +    "NestedJsonForCompositeTypes": false
    +  }
    +}
    SettingTypeDefaultDescription
    EnabledboolfalseEnable or disable SQL file source endpoints
    FilePatternstring""Glob pattern for SQL files. Supports *, ** (recursive), ?. Empty = disabled
    CommentsModeenumOnlyWithHttpTagOnlyWithHttpTag = requires explicit HTTP annotation. ParseAll = every file becomes an endpoint
    CommentScopeenumAllAll = parse all comments. Header = only before first statement
    ErrorModeenumExitExit = log error + exit process. Skip = log error + continue
    ResultPrefixstring"result"Prefix for multi-command result keys (e.g., result1, result2)
    UnnamedSingleColumnSetbooltrueSingle-column queries return flat arrays (["a","b"]) instead of object arrays ([{"col":"a"},{"col":"b"}]). Applies to both single-command and per-result in multi-command files. Matches function behavior for setof single values
    NestedJsonForCompositeTypesboolfalseWhen true, composite type columns are serialized as nested JSON objects under their column name. When false (default), composite fields are flattened inline — matching routine behavior. Can also be enabled per-endpoint with the nested annotation

    New Annotations


    New Core Annotation: @param / @parameter — Rename and Retype Parameters

    A new comment annotation that renames and optionally retypes individual parameters. Works on all endpoint types — functions, procedures, CRUD, and SQL file endpoints.

    Positional parameters ($1, $2) already work as HTTP parameter names (?$1=value), but this annotation provides better API ergonomics:

    sql
    sql
    -- Simplest form: rename only
    +-- @param $1 user_id
    +
    +-- Simplest form: rename + retype
    +-- @param $1 user_id integer
    +
    +-- "is" style: rename only (consistent with existing @param X is hash of Y)
    +-- @param $1 is user_id
    +
    +-- "is" style: rename + retype
    +-- @param $1 is user_id integer
    +
    +-- Rename named parameters (works on function/procedure params too)
    +-- @param _old_name better_name
    +-- @param _old_name better_name text

    All forms coexist with existing @param X is hash of Y and @param X is upload metadata handlers without ambiguity. Both @param and @parameter (long form) are supported.


    @param Default Values for SQL File Parameters

    SQL file parameters can now have default values via the @param annotation. When a parameter with a default is not provided in the request, the default value is bound instead of returning 404.

    This is essential for SQL files because positional parameters ($1, $2) must always be bound — unlike PostgreSQL functions where the engine applies its own defaults.

    Syntax:

    sql
    sql
    -- Separate annotations (rename first, then set default):
    +-- @param $1 user_id
    +-- @param user_id default null
    +
    +-- Combined rename + default on a single line:
    +-- @param $1 user_id default null
    +
    +-- "is" style rename + default:
    +-- @param $1 is user_id default null
    +
    +-- Rename + retype + default:
    +-- @param $1 user_id integer default 42
    +-- @param $1 is user_id integer default 42
    +
    +-- Default without rename:
    +-- @param $1 default 'fallback'
    +
    +-- Various value types:
    +-- @param $1 status default 'active'     -- text (single-quoted)
    +-- @param $1 amount default 42           -- number
    +-- @param $1 enabled default true        -- boolean
    +-- @param $1 filter default null         -- SQL NULL (unquoted)
    +-- @param $1 tag default 'null'          -- literal text "null" (quoted)
    +-- @param $1 val default                 -- no value = NULL

    Value parsing rules (SQL conventions):

    • Unquoted null (case-insensitive) → DBNull.Value
    • Single-quoted 'text value' → string literal (supports multi-word)
    • Unquoted value → raw string (Npgsql handles type conversion via NpgsqlDbType)

    Real-world example — user identity endpoint with claim-filled parameters that fall back to NULL:

    sql
    sql
    /* HTTP GET
    +@authorize
    +@user_parameters
    +@param $1 _user_id default null
    +@param $2 _username default null
    +@param $3 _email default null
    +*/
    +select $1 as user_id, $2 as username, $3 as email;

    When authenticated, claims fill the parameters automatically. The defaults ensure the parameters are always bindable.

    Effects on generated output:

    • TsClient: Parameters with defaults get ? suffix in TypeScript interfaces (optional)
    • OpenAPI: Parameters with defaults are marked required: false

    @param Rename Validation

    Parameter names are now validated when renaming via @param. Invalid renames are rejected with a warning log instead of silently creating broken endpoints.

    Rules:

    • Must be a valid PostgreSQL identifier: starts with letter or _, followed by letters, digits, _, or $
    • Positional parameters ($1, $2) are allowed
    sql
    sql
    -- Valid:
    +-- @param $1 user_id        ✓
    +-- @param $1 _val$1         ✓
    +
    +-- Rejected (with warning log):
    +-- @param $1 1bad           ✗ starts with digit
    +-- @param $1 my-param       ✗ invalid character (hyphen)

    @param Default Value: = Alias for default

    The @param annotation now accepts = as a shorthand for default when setting default values:

    sql
    sql
    -- These are equivalent:
    +-- @param $1 _user_id text default null
    +-- @param $1 _user_id text = null
    +
    +-- Works with any value type:
    +-- @param $1 user_id integer default 42
    +-- @param $1 user_id integer = 42
    +
    +-- Also works with standalone default:
    +-- @param my_name = 'hello'
    +
    +-- And "is" style:
    +-- @param $1 is greeting = 'hey'

    @param Type Hints for SQL File Describe

    When a SQL file parameter has an explicit type in the @param annotation (e.g., @param $1 name text), that type is now used during the PostgreSQL Describe step instead of Unknown. This fixes startup errors like 42P18: could not determine data type of parameter $1 that occurred when PostgreSQL's parser couldn't infer the parameter type from context alone — for example, in select set_config('key', $1, true).


    New Positional Annotation: @returns — Skip Describe and Declare Return Type

    New positional annotation @returns that skips the PostgreSQL Describe step entirely for a statement. The SQL is never sent to PostgreSQL during startup. Supports three forms:

    • @returns <composite_type> — resolve columns from the composite type definition
    • @returns <scalar_type> — declare a single typed column (e.g., integer, text, boolean). Only the first column is used at runtime.
    • @returns void — no columns, void result

    Composite type example (temp tables created at runtime):

    sql
    sql
    -- HTTP GET
    +-- @param $1 val1 text
    +-- @param $2 val2 integer
    +begin;
    +select set_config('app.val1', $1, true); -- @skip
    +select set_config('app.val2', $2::text, true); -- @skip
    +do $$ begin
    +    create temp table _result on commit drop as
    +    select current_setting('app.val1') as val1,
    +           current_setting('app.val2')::int as val2;
    +end; $$;
    +-- @returns my_result_type
    +-- @result data
    +-- @single
    +select * from _result;
    +end;

    Without @returns, the select * from _result statement fails during startup Describe because the temp table doesn't exist yet. With @returns my_result_type, the columns are resolved from the composite type definition in pg_catalog instead.

    Scalar type example — declare a single typed column, extra columns ignored:

    sql
    sql
    -- @returns integer
    +-- @single
    +select count(*) from users;

    Returns bare 42 instead of [{"count": 42}].

    Void example — no results, skipping Describe entirely:

    sql
    sql
    -- @returns void
    +select set_config('key', $1, false);

    In multi-command files, void statements produce a rows-affected count. For single-command files, it makes the entire endpoint void (204 No Content).

    The Describe step is completely skipped for annotated statements — the SQL is never sent to PostgreSQL during startup. The composite type must exist in the database at startup. If the type is not found, an error is logged and the file is skipped or the process exits (depending on ErrorMode).


    New Annotation: @void — Force Void Response

    New comment annotation void (alias: void_result) that forces an endpoint to return 204 No Content instead of a JSON response. All statements are executed for side effects only.

    This is particularly useful for multi-command SQL files where all statements are side-effect-only (e.g., set_config calls followed by a DO block):

    sql
    sql
    /* HTTP POST
    +@void
    +@param $1 message_text text
    +@param $2 _user_id text = null
    +*/
    +select set_config('app.message', $1, true);
    +select set_config('app.user_id', $2, true);
    +do $$ begin
    +    -- use current_setting() to read params inside DO block
    +    insert into messages (user_id, text)
    +    values (current_setting('app.user_id')::int, current_setting('app.message'));
    +end; $$;

    Without @void, this multi-command endpoint would return {"result1":"...","result2":"...","result3":-1}. With @void, it returns 204 — no JSON, no need to add @skip to every statement.

    Works on all endpoint types: functions, procedures, CRUD, and SQL file endpoints.


    New Comment Annotation: @single

    New comment annotation single (aliases: single_record, single_result) that returns a single record as a JSON object instead of a JSON array.

    Works across all endpoint sources: PostgreSQL functions, SQL files, and CRUD endpoints.

    Usage:

    sql
    sql
    -- PostgreSQL function
    +CREATE FUNCTION get_user(int) RETURNS TABLE(id int, name text) ...
    +COMMENT ON FUNCTION get_user IS 'HTTP GET
    +@single';
    +
    +-- SQL file
    +-- HTTP GET
    +-- @single
    +-- @param $1 id
    +SELECT id, name FROM users WHERE id = $1;

    Without @single: [{"id": 1, "name": "alice"}] (array) With @single: {"id": 1, "name": "alice"} (object)

    Behavior:

    • Multi-column results return a JSON object (no array wrapping)
    • Single unnamed column results return a bare JSON value (e.g., "hello", 42)
    • If the query returns multiple rows, only the first row is returned (early exit from rendering loop)
    • Empty results respect the response_null annotation: empty_string (default), null_literal, or no_content (204)
    • TypeScript client generates Promise<IResponse> instead of Promise<IResponse[]>

    Per-command @single in multi-command files:

    In multi-command SQL files, @single is positional — it applies to the next statement below it:

    sql
    sql
    -- HTTP POST
    +-- @param $1 id
    +-- @single
    +SELECT id, name FROM users WHERE id = $1;
    +UPDATE orders SET status = 'done' WHERE id = $1;
    +-- @single
    +SELECT id, status FROM orders WHERE id = $1;

    Result: {"result1": {"id": 1, "name": "alice"}, "result2": 1, "result3": {"id": 1, "status": "done"}}

    • First and third commands return objects (have @single above them)
    • Second command returns rows-affected count (void, no @single)
    • Empty per-command @single results render as null

    Positional @result Annotation for Multi-Command Files

    Result keys in multi-command SQL files are named positionally. Annotations can be placed in two ways:

    Before the statement (on a separate line) — applies to the next statement below:

    sql
    sql
    -- @result users
    +SELECT id, name FROM users;
    +-- @result orders
    +SELECT id, total FROM orders;

    Inline after the semicolon (on the same line) — applies to the statement on that line:

    sql
    sql
    SELECT id, name FROM users; -- @result users
    +SELECT id, total FROM orders; -- @result orders

    Both produce: {"users": [...], "orders": [...]}

    This same placement rule applies to all positional annotations: @result, @single, and @skip.

    • @result name — names the result key for the associated statement
    • @result is name — "is" syntax also supported
    • Commands without @result get auto-generated keys: result1, result2, etc.

    SkipNonQueryCommands Setting and @skip Annotation

    SkipNonQueryCommands (default: true)

    Non-query commands in multi-command SQL files are now automatically excluded from the JSON response while still being executed. This eliminates noise like "result1": -1 from transaction control and session statements.

    Affected commands: BEGIN, COMMIT, END, ROLLBACK, SAVEPOINT, RELEASE, SET, RESET, DO blocks, DISCARD, LOCK, LISTEN, NOTIFY, DEALLOCATE.

    sql
    sql
    -- HTTP POST
    +-- @param $1 id
    +BEGIN;
    +UPDATE users SET active = true WHERE id = $1;
    +COMMIT;
    +-- @result verification
    +SELECT id, active FROM users WHERE id = $1;

    Before (without SkipNonQueryCommands):

    json
    json
    {"result1":-1,"result2":1,"result3":-1,"verification":[{"id":1,"active":true}]}

    After (with SkipNonQueryCommands, default):

    json
    json
    {"result1":1,"verification":[{"id":1,"active":true}]}

    Skipped commands don't consume result numbers — the UPDATE gets result1, not result2.

    DML commands (INSERT, UPDATE, DELETE) are NOT skipped — their rows-affected count is meaningful.

    Set "SkipNonQueryCommands": false in SqlFileSource configuration to disable.

    @skip Annotation (aliases: @skip_result, @no_result)

    For cases not covered by SkipNonQueryCommands, use the @skip positional annotation to explicitly exclude any statement from the response:

    sql
    sql
    -- @skip
    +do $$ begin perform pg_notify('channel', 'event'); end; $$;
    +-- @result data
    +SELECT id, name FROM users;

    Result: {"data": [...]}


    New Core Annotation: @internal / @internal_only

    Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but NOT exposed as a public HTTP route:

    sql
    sql
    -- Helper endpoint: returns data but is not callable from outside
    +create function get_cached_rates()
    +returns json language sql as $$
    +    select rates from exchange_rates order by fetched_at desc limit 1
    +$$;
    +comment on function get_cached_rates() is 'HTTP GET
    +@internal';
    +
    +-- Public endpoint that composes the internal one
    +create function convert_currency(_amount numeric, _from text, _to text)
    +returns json language plpgsql as $$
    +...
    +$$;
    +comment on function convert_currency(numeric, text, text) is 'HTTP GET
    +@proxy GET /api/get-cached-rates';

    Direct HTTP call to /api/get-cached-rates returns 404. But @proxy GET /api/get-cached-rates and HTTP client types with relative paths can still invoke it internally.

    Works on all endpoint sources: functions, procedures, tables/views (CRUD), and SQL files.


    HTTP Custom Types & Self-Referencing Calls


    Self-Referencing Calls: Relative Path Support for Proxy and HTTP Client Types

    Both @proxy annotations and HTTP client type definitions now support relative paths that call back to the same server instance:

    sql
    sql
    -- Proxy to another endpoint on the same server
    +comment on function my_aggregator() is 'HTTP GET
    +@proxy POST /api/data-source';
    +
    +-- HTTP client type calling a local endpoint
    +comment on type local_api as 'POST /api/process';

    Parallel query composition: Combined with HTTP client types that execute all requests in parallel (Task.WhenAll), this enables a single endpoint to fan out to multiple internal endpoints simultaneously — effectively running parallel queries without client-side orchestration:

    sql
    sql
    -- Two HTTP types calling different internal endpoints
    +create type api_users as (body text);
    +comment on type api_users is 'GET /api/users';
    +
    +create type api_orders as (body text);
    +comment on type api_orders is 'GET /api/orders';
    +
    +-- Function that composes both in parallel
    +create function get_dashboard(
    +    _users api_users,
    +    _orders api_orders
    +) returns json language plpgsql as $$
    +begin
    +    return json_build_object('users', (_users).body::json, 'orders', (_orders).body::json);
    +end;
    +$$;
    +-- One request → two parallel internal calls → combined response

    Configuration:

    • HttpClientOptions.SelfBaseUrl / ProxyOptions.SelfBaseUrl — explicit base URL for relative path resolution (auto-detected from server addresses when not set)
    • In production, relative paths resolve via loopback HTTP to the server's own address
    • In test environments, SetSelfClient() injects an in-memory handler that bypasses the network entirely

    Internal Self-Call Optimization: Zero HTTP Overhead

    Self-referencing endpoints (HTTP client types and proxy definitions with relative paths like /api/endpoint) now bypass the HTTP stack entirely. Instead of making a loopback HTTP call through TCP, the endpoint handler is invoked directly in-process via InternalRequestHandler.

    This enables efficient parallel query composition: a single endpoint can fan out to multiple internal endpoints in parallel (via Task.WhenAll), collect the results, and combine them — all without network overhead. Use cases include:

    • Parallel data aggregation across multiple queries
    • Orchestrating multiple mutations in a single request
    • Composing responses from several independent data sources

    Performance: Microseconds instead of milliseconds per internal call. No TCP connection, no HTTP parsing, no serialization/deserialization at the transport layer.

    Internal handler routing now matches by HTTP method + path (e.g., GET /api/data) instead of path alone. Two endpoints with the same path but different methods (GET vs POST) are correctly distinguished for internal calls.


    Composite Type Parameters in SQL Files — No SQL Rewriting

    Composite type parameters in SQL files are now passed as single text values instead of being expanded into multiple parameters with ROW() SQL rewriting. The SQL stays exactly as the user wrote it.

    HTTP custom type parameters (auto-filled from HTTP calls):

    sql
    sql
    -- @param $1 _response example_9.exchange_rate_api
    +select ($1::example_9.exchange_rate_api).body;

    The framework makes the HTTP call and passes the result as a single composite text value. No SQL rewriting.

    Client-sent composite type parameters:

    sql
    sql
    -- @param $1 data my_composite_type
    +select ($1::my_composite_type).field1, ($1::my_composite_type).field2;

    The client sends the composite value as PostgreSQL composite text format: ?data=("val1","val2"). The SQL casts it with $1::my_composite_type.

    Unknown types in @param annotations now produce a warning log instead of silently falling back to unknown.


    Configuration Changes


    RoutineSource: Enabled Configuration Option

    The RoutineOptions section now supports an Enabled setting (default true). Set to false to disable automatic endpoint creation from PostgreSQL functions and procedures:

    json
    json
    "RoutineOptions": {
    +  "Enabled": false
    +}

    This is useful for SQL-files-only deployments where the overhead of querying the PostgreSQL catalog for routines is unnecessary.


    CrudSource Disabled by Default

    The CrudSource:Enabled setting now defaults to false (was true).

    CrudSource auto-generates CRUD endpoints for all PostgreSQL tables and views, which is rarely desired in production without explicit configuration. Users who need CRUD endpoints should explicitly set "CrudSource": { "Enabled": true }.


    CrudSource No Longer Blocks SqlFileSource

    Previously, when CrudSource was disabled (or its config section was missing), CreateEndpointSources() returned early, preventing SqlFileSource from being registered. All three endpoint sources (RoutineSource, CrudSource, SqlFileSource) are now independently enabled/disabled.


    DataProtection Disabled by Default

    The DataProtection:Enabled setting now defaults to false (was true).

    DataProtection is only needed when using Cookie Authentication, Antiforgery tokens, or @encrypt/@decrypt annotations — all of which are themselves disabled by default. Enabling it unconditionally added unnecessary key management overhead and, on Linux/Docker with Storage: "Default", caused silent key loss on restart (invalidating auth cookies without warning).

    Users who enable Auth, Antiforgery, or encrypt/decrypt annotations should explicitly set "DataProtection": { "Enabled": true } and choose an appropriate storage mode.


    SqlFileSource:LogCommandText Setting

    New setting LogCommandText in the SqlFileSource configuration (default false) controls whether multi-command SQL file endpoints include the full SQL text in debug command logs. When false, only the file path and statement count are logged:

    code
    [DBG] -- POST http://127.0.0.1:8080/api/send-message
    +-- $1 text = 'hello'
    +SQL file: sql/send-message.sql (5 statements)

    When true, the full SQL body is logged (previous behavior). Single-command SQL file endpoints always log the SQL text regardless of this setting. This only applies when LogCommands is true.


    TsClient Improvements


    TsClient: Composite Type Support for SQL Files

    The TypeScript client generator now correctly handles composite type columns in SQL file endpoints, generating interfaces that match the actual JSON response — same behavior as routine endpoints, including flat/nested modes and recursive composites.


    TsClient: Multi-Command SQL File Support

    The TypeScript client generator now handles multi-command SQL file endpoints. For multi-command endpoints, TsClient generates a typed response interface with one property per result:

    typescript
    typescript
    interface IProcessOrderResponse {
    +    validate: number[];  // single-column → flat array (UnnamedSingleColumnSet)
    +    result2: number;  // void command → rows affected
    +    confirm: { id: number, status: string }[];
    +}
    +
    +export async function processOrder(
    +    request: IProcessOrderRequest
    +) : Promise<IProcessOrderResponse> { ... }
    • Void commands are typed as number (rows affected count)
    • Data-returning commands are typed as arrays of inline object types
    • Single-column commands with UnnamedSingleColumnSet enabled generate flat array types (e.g., string[]) instead of object arrays
    • Single-command SQL file endpoints generate standard typed functions (no change)

    TsClient: SQL File Comment Headers

    The TypeScript client generator now produces correct JSDoc comment headers for SQL file endpoints:

    • Header line shows the full file path (e.g., SQL file: /path/to/get-posts.sql) instead of just the filename
    • The @remarks section outputs SQL file comments directly instead of incorrectly wrapping them in comment on function ... syntax

    TsClient: Type Alias Extraction for Error and Result Types

    When IncludeStatusCode is enabled, the TypeScript client generator now emits reusable type aliases at the top of each generated file instead of repeating the full inline types everywhere:

    typescript
    typescript
    type ApiError = {status: number; title: string; detail?: string | null};
    +type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};

    These aliases are used in function signatures, JSDoc comments, and as casts — significantly reducing repetition and line length:

    typescript
    typescript
    // Before (repeated 3x per function):
    +) : Promise<{status: number, response: string, error: {status: number; title: string; detail?: string | null} | undefined}>
    +
    +// After:
    +) : Promise<ApiResult<string>>

    The type aliases are not exported, so importing multiple generated files causes no naming conflicts. TypeScript's structural typing ensures full compatibility.

    Two new options control the alias names:

    • ErrorTypeName (default: "ApiError") — name for the error type alias
    • ResultTypeName (default: "ApiResult") — name for the generic result type alias

    TsClient: Fix SkipTypes Generating Invalid JavaScript

    Fixed two bugs when SkipTypes is enabled (pure JavaScript output):

    1. Invalid as cast in error handling: The error expression was always generated with a TypeScript as type cast (e.g., await response.json() as {status: number; ...}), which is invalid JavaScript syntax. The as cast is now omitted when SkipTypes is true.

    2. No file output with CreateSeparateTypeFile = false: When both SkipTypes and CreateSeparateTypeFile = false were set, no file was written at all. The code-only content is now written correctly.


    Bug Fixes & Log Improvements


    Graceful Shutdown with Active SSE Connections

    The application now shuts down cleanly when SSE (Server-Sent Events) connections are active. Previously, pressing Ctrl+C while clients were connected to SSE endpoints would hang because the broadcaster channels were never completed, leaving ReadAllAsync loops blocked indefinitely.

    On ApplicationStopping, all broadcaster channels are now completed, causing SSE middleware to exit gracefully and allowing the app to terminate.


    Downgrade Basic Auth Missing Header Log to Debug

    The "No Authorization header found" log message during Basic Authentication was downgraded from Warning to Debug. This message fires on every initial browser request before credentials are sent, which is normal behavior in the HTTP Basic Auth challenge-response flow — not a warning condition.


    Improved Log Level Classification

    Moved verbose per-item logging from Debug to Trace level to reduce noise at the default Debug level:

    • Connection source logs: Per-source "Using DataSource..." messages now include the source name (e.g., RoutineSource, SqlFileSource) and are logged at Trace instead of Debug.
    • TsClient/HttpFiles file generation: Individual "Created file" messages moved to Trace. A single Debug summary reports the total count (e.g., TsClient: Created 15 TypeScript file(s)).
    • Upload handler config details: Detailed parameter dumps for each handler type (mime patterns, buffer sizes, etc.) moved to Trace.

    Fix @separator and @new_line Annotations Not Working with @ Prefix

    The @separator and @new_line comment annotations were silently ignored when using the @ prefix syntax (e.g., @separator , in /* */ block comments). This affected SQL file endpoints using block comment annotations. Line comment annotations without @ prefix (e.g., -- separator ,) were not affected.

    The root cause: the annotation matching used line.StartsWith("separator ") which failed when the line started with @separator. All other annotation handlers used StrEqualsToArray() which correctly strips the @ prefix.


    Aggregated Comment Annotation Logging

    Comment annotation debug logs are now aggregated into a single line per endpoint instead of one line per annotation. This significantly reduces log noise during development.

    Before (multiple Debug lines per endpoint):

    code
    [DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set HTTP by the comment annotation to GET /api/who-am-i
    +[DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set REQUIRED AUTHORIZATION by the comment annotation.
    +[DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set SINGLE RECORD by the comment annotation.

    After (one Debug line per endpoint):

    code
    [DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i annotations: [HTTP GET, authorize, single]

    The individual per-annotation log messages are still available at Trace level for detailed debugging.


    Fix: OnlyWithHttpTag Mode Skips Files Before Describe

    When CommentsMode is OnlyWithHttpTag (the default), SQL files without an HTTP tag are now skipped before the PostgreSQL describe step. Previously, files without an HTTP tag were still described against the database, causing errors on invalid SQL files (e.g., migration scripts, utility files) instead of being silently skipped. With ErrorMode.Exit, this would crash the process.


    Internal & Breaking Changes


    Interface Refactoring: IEndpointSource / IRoutineSource

    IRoutineSource split into two interfaces:

    • IEndpointSource — base interface with CommentsMode, NestedJsonForCompositeTypes, and Read(). Used by lightweight sources like SqlFileSource.
    • IRoutineSource : IEndpointSource — extended interface adding Query, schema/name filtering. Used by RoutineSource and CrudSource.

    NestedJsonForCompositeTypes moved from IRoutineSource to IEndpointSource so that all endpoint sources (including SqlFileSource) support composite type nesting configuration.

    Breaking: NpgsqlRestOptions.RoutineSources renamed to EndpointSources. SourcesCreated callback renamed to EndpointSourcesCreated. Custom IEndpointSource implementations must now implement NestedJsonForCompositeTypes.


    Composite Type Cache: Public API

    • CompositeTypeCache.ResolveTypeDescriptor(TypeDescriptor) — new public method for plugins to resolve composite/array-of-composite type metadata
    • Routine.CompositeColumnInfo and Routine.ArrayCompositeColumnInfo — changed from internal to public for plugin access
    • Schema-prefix fallback: public.my_type now matches cache key my_type (handles GetDataTypeName vs regtype::text format mismatch)

    Glob Pattern Enhancement: ** Recursive Matching

    Parser.IsPatternMatch now supports ** for recursive directory matching:

    • * — matches any characters (backward-compatible: matches / when no ** in pattern)
    • ** — matches any characters including / (crosses directory boundaries)
    • When ** is present in the pattern, * stops matching / (standard glob semantics)

    Examples:

    • sql/**/*.sql matches sql/file.sql, sql/dir/file.sql, sql/a/b/c/file.sql
    • **/*.sql matches any .sql file at any depth
    • dir/**/file.sql matches dir/file.sql and dir/a/b/file.sql

    This enhancement benefits all existing IsPatternMatch consumers (StaticFiles.AuthorizePaths, StaticFiles.ParseContentOptions.ParsePatterns, upload MIME types) and enables the SQL file source's recursive file scanning.


    Internal Changes

    • RoutineType.SqlFile — new enum value for SQL file endpoints (was Other), shown in log messages
    • NpgsqlRestParameter.ConvertedName / ActualNameinternal set (was private set) for @param rename support
    • ParameterHandler.HandleParameterRename — new method handling all rename/retype annotation forms
    • SqlFileParameterFormatter — static singleton, IsFormattable = false, zero per-endpoint allocation
    • Routine.MultiCommandInfo — per-command metadata array (statement SQL, column info, result names)
    • NpgsqlRetryExtensions.ExecuteBatchReaderWithRetryAsync — new retry extension for NpgsqlBatch readers
    • Multi-command rendering in NpgsqlRestEndpoint.csNpgsqlBatch execution, do/while NextResultAsync() loop, JSON object wrapper with multiCmdWriteWrapper flag (skipped in raw/binary mode), table format handler called per result set
    • JsonValueFormatter.FormatValue — shared value type dispatch for both single and multi-command rendering paths
    • Three new log messages: CommentParamNotExistsCantRename, CommentParamRenamed, CommentParamRetyped
    • NpgsqlRestEndpoint split into partial class files: NpgsqlRestEndpoint.cs (request handling + rendering, ~2866 lines) and NpgsqlRestEndpoint.Helpers.cs (helper methods, ~352 lines) for easier maintenance
    • JSON key escaping: column names, composite field names, and multi-command result keys are now properly escaped with PgConverters.SerializeString. Pre-escaped values stored in Routine.JsonColumnNames, MultiCommandInfo.JsonName/JsonColumnNames at startup to avoid per-row escaping overhead during request execution
    • HttpClientOptions.SelfBaseUrl — configurable base URL for relative-path HTTP client type definitions. Auto-detected from server addresses at runtime when not configured
    • HttpClientTypeHandler.SetSelfClient — allows injecting a custom HttpClient for self-referencing calls (used by WebApplicationFactory in tests)
    • HttpClientTypes initialization moved before Build() in NpgsqlRestBuilder so definitions are available when endpoint sources process files
    • InternalRequestHandler — direct in-process endpoint invocation for self-referencing calls. Endpoint handlers stored in FrozenDictionary keyed by path. Uses NonClosingMemoryStream to prevent PipeWriter.Complete from closing the response stream. Supports path parameter matching via segment-by-segment template comparison with route value extraction

    `,310)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.12.0.md.BDVbkMod.lean.js b/assets/guide_changelog_v3.12.0.md.BDVbkMod.lean.js new file mode 100644 index 000000000..af9def134 --- /dev/null +++ b/assets/guide_changelog_v3.12.0.md.BDVbkMod.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.12.0 (2026-03-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.12.0.md","filePath":"guide/changelog/v3.12.0.md"}'),t={name:"guide/changelog/v3.12.0.md"};function l(p,s,r,o,h,d){return e(),a("div",null,s[0]||(s[0]=[n("",310)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.13.0.md.kmKD3jrx.js b/assets/guide_changelog_v3.13.0.md.kmKD3jrx.js new file mode 100644 index 000000000..dce0cde8f --- /dev/null +++ b/assets/guide_changelog_v3.13.0.md.kmKD3jrx.js @@ -0,0 +1,143 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.13.0 (2026-04-24)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.13.0.md","filePath":"guide/changelog/v3.13.0.md"}'),t={name:"guide/changelog/v3.13.0.md"};function l(h,s,p,k,r,o){return e(),a("div",null,s[0]||(s[0]=[n(`

    Changelog v3.13.0 (2026-04-24)

    Version 3.13.0 (2026-04-24)

    Full Changelog

    New: Auth Schemes (Named Additional Authentication Schemes)

    Auth:Schemes is a named-dict section that registers additional ASP.NET Core authentication schemes alongside the main one. Each entry is a fully-fledged scheme of any of the three supported types — Cookies, BearerToken, or Jwt — with its own options. A login function selects which scheme to use by returning the scheme's name in its scheme column.

    Use cases this unlocks:

    • Short-lived sensitive sessions for admin or payment flows (Cookies scheme with shorter CookieValid + CookieMultiSessions: false).
    • Per-scope JWT signing keys so a key leak has limited blast radius (separate JwtSecret per Jwt scheme).
    • Multiple bearer-token APIs with different expirations and refresh paths.
    • Single-session cookies for areas where parallel logins must be disallowed, alongside a normal long-lived session.
    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "14 days",
    +  "JwtAuth": true,
    +  "JwtSecret": "...root-secret-32+chars...",
    +  "Schemes": {
    +    "short_session": {
    +      "Type": "Cookies",
    +      "Enabled": true,
    +      "CookieValid": "1 hour",
    +      "CookieMultiSessions": false
    +    },
    +    "api_token": {
    +      "Type": "BearerToken",
    +      "Enabled": true,
    +      "BearerTokenExpire": "30 minutes",
    +      "BearerTokenRefreshPath": "/api/api-token/refresh"
    +    },
    +    "admin_jwt": {
    +      "Type": "Jwt",
    +      "Enabled": true,
    +      "JwtSecret": "...separate-admin-secret-32+chars...",
    +      "JwtExpire": "5 minutes",
    +      "JwtRefreshPath": "/api/admin-jwt/refresh"
    +    }
    +  }
    +}
    sql
    sql
    -- Standard login: returns 'Cookies' → 14-day persistent cookie
    +create function login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'Cookies' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Sensitive-area login: returns 'short_session' → 1-hour session-only cookie
    +create function admin_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'short_session' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Admin JWT login: returns 'admin_jwt' → 5-minute JWT signed with the admin-only secret
    +create function admin_jwt_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'admin_jwt' as scheme, user_id::text, username from users where ...
    +$$;

    Per-type override fields:

    TypeOverride fields
    CookiesCookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly
    BearerTokenBearerTokenExpire, BearerTokenRefreshPath
    JwtJwtExpire, JwtRefreshExpire, JwtSecret, JwtIssuer, JwtAudience, JwtClockSkew, JwtRefreshPath, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey

    Common fields: Type (required, case-insensitive), Enabled (default true).

    Inheritance. A scheme that overrides only one or two fields reuses everything else from the root Auth section, so blocks stay small. Setting CookieMultiSessions: false is the typical "single-session" override — the cookie's Max-Age becomes null (browser-session-only) while ExpireTimeSpan still bounds server-side validity. JWT schemes inherit JwtSecret from the root section if not set explicitly, so a per-scheme block can be just a shorter expiration.

    Validation at startup (fail-fast).

    • Scheme name must not collide with the main scheme names (CookieAuthScheme, BearerTokenAuthScheme, JwtAuthScheme).
    • Type must be one of Cookies, BearerToken, Jwt (case-insensitive). Missing or unsupported types throw with a clear message.
    • Explicit CookieName values must be distinct across all cookie schemes. When unset, ASP.NET's per-scheme .AspNetCore.<scheme> default automatically differs and is excluded from collision tracking.
    • Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across the main scheme and every scheme that defines one — two app.Use middlewares listening on the same path would race.
    • Jwt schemes require a secret either on the scheme or on the root section; JwtSecret must be ≥32 chars for HS256.
    • Invalid interval strings (CookieValid, BearerTokenExpire, JwtExpire, JwtRefreshExpire, JwtClockSkew) throw with the offending path and value.

    Refresh middleware per scheme. Each BearerToken/Jwt scheme that declares a refresh path gets its own app.Use middleware listening on that path, with that scheme's tokens validated under that scheme's options. The main-scheme refresh middleware (root BearerTokenRefreshPath / JwtRefreshPath) continues to work for the main scheme.

    Logout. The existing logout pipeline accepts a list of scheme names from the logout function's result columns and signs out each — additional schemes work without changes. To clear both main and additional cookies in one logout, return both scheme names from the function.

    Breaking: legacy auth time-integer fields removed

    The four integer-based time fields under Auth are removed in 3.13.0. Use the interval-notation equivalents instead:

    Removed (3.12 and earlier)Use instead (3.13.0+)
    Auth:CookieValidDays: 14Auth:CookieValid: "14 days"
    Auth:BearerTokenExpireHours: 1Auth:BearerTokenExpire: "1 hour"
    Auth:JwtExpireMinutes: 60Auth:JwtExpire: "60 minutes"
    Auth:JwtRefreshExpireDays: 7Auth:JwtRefreshExpire: "7 days"

    The new fields accept Postgres-interval syntax ("14 days", "12 hours", "30 minutes", "45 seconds", etc.) — finer-grained durations than the legacy integers permitted.

    If you upgrade with any of the four removed fields still in your config, startup will fail with a clear migration message naming the offending field, the replacement field, and an example interval string. Failing fast is deliberate — silently ignoring the legacy field would mean an "I configured 30-day cookies" intent silently flips to the new field's default of 14 days, which would be very surprising.

    New: interval notation for auth time fields

    Each of the four time-window settings under Auth (cookie validity, bearer-token expiration, JWT access-token expiration, JWT refresh-token expiration) is expressed as a Postgres interval string:

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "12 hours",
    +  "JwtAuth": true,
    +  "JwtExpire": "5 minutes",
    +  "JwtRefreshExpire": "1 day"
    +}

    Setting any of these to null falls back to the framework default (14 days / 1 hour / 60 minutes / 7 days respectively). Invalid interval values fail at startup with a clear error, rather than silently falling back. The shipped appsettings.json includes the explicit defaults so users see exactly what they're getting.

    Breaking: RateLimiterOptions:Policies is now a dict, not an array

    This section was previously an array of objects, each with an explicit "Name" property. It is now an object keyed by policy name, matching the existing ValidationOptions:Rules and the new CacheOptions:Profiles shape. Migration is mechanical:

    jsonc
    jsonc
    // Before (3.12 and earlier):
    +"Policies": [
    +  { "Name": "fixed",   "Type": "FixedWindow",  "Enabled": true,  "PermitLimit": 100, ... },
    +  { "Name": "sliding", "Type": "SlidingWindow", "Enabled": false, ... }
    +]
    +
    +// After (3.13.0):
    +"Policies": {
    +  "fixed":   { "Type": "FixedWindow",  "Enabled": true,  "PermitLimit": 100, ... },
    +  "sliding": { "Type": "SlidingWindow", "Enabled": false, ... }
    +}

    Move each policy's Name value to be the JSON key, then drop the Name field. No other field changes; runtime behavior is identical.

    If you upgrade with the old array form still in your config, startup will fail with a clear InvalidOperationException telling you to migrate. (Failing fast is deliberate — silently registering policies under names like "0" and "1" would have made endpoint annotations referencing them stop matching, leading to silent loss of rate limiting.)

    New: Per-User Rate Limiting (Partition on a policy)

    Rate-limiter policies can now be partitioned at request time, so each request gets its own bucket based on a value derived from HttpContext (a claim, an IP, a header, or a static fallback). The classic use case is per-user throttling: each authenticated user gets their own quota instead of all users sharing one global bucket.

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    },
    +    "throttle_anon_only": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "BypassAuthenticated": true,
    +        "Sources": [{ "Type": "IpAddress" }]
    +      }
    +    }
    +  }
    +}

    Partition fields:

    • Sources — ordered list. Walked top-to-bottom at request time; the first source returning a non-empty value wins. Each source has a Type:

      • Claim — reads HttpContext.User.FindFirst(Name).Value. Name is required (the claim type, e.g., "name_identifier").
      • IpAddress — reads the client IP via HttpRequest.GetClientIpAddress(), which honors X-Forwarded-For / X-Real-IP ahead of Connection.RemoteIpAddress. No Name needed.
      • Header — reads HttpContext.Request.Headers[Name]. Name is required.
      • Static — always returns the configured Value. Useful as a terminal fallback (e.g., everyone unmatched shares the "anonymous" bucket).

      If no source resolves, partition resolution falls through to the literal key "unpartitioned" so the policy still rate-limits coherently.

    • BypassAuthenticated (bool, default false) — when true, signed-in users (HttpContext.User.Identity.IsAuthenticated) skip the limiter entirely. Evaluated before Sources, so use this for "throttle anonymous only" patterns. Authenticated users get an unlimited bucket; anonymous users still hit the partitioned limiter.

    Behavior is unchanged for policies without a Partition block. Each non-partitioned policy still uses a single global bucket, exactly as in 3.12 and earlier.

    Each Sources entry is validated at startup — invalid entries (e.g., Claim without Name, unknown Type) are logged at Warning and skipped. If a Partition block has no usable sources and BypassAuthenticated is false, the partition is dropped (with a Warning) and the policy reverts to a single global bucket.

    New: Caching Profiles (CacheOptions.Profiles + @cache_profile annotation)

    Named cache profiles allow you to maintain multiple distinct caching policies in one application — different backends, expirations, key shapes, or bypass conditions — and let endpoints opt into them via a single comment annotation.

    jsonc
    jsonc
    "CacheOptions": {
    +  "Enabled": true,
    +  "Type": "Memory",
    +  "Profiles": {
    +    "fast_memory": {
    +      "Enabled": false,
    +      "Type": "Memory",
    +      "Expiration": "30 seconds",
    +      "Parameters": ["user_id"]
    +    },
    +    "shared_redis": {
    +      "Enabled": false,
    +      "Type": "Redis",
    +      "Expiration": "1 hour"
    +    },
    +    "date_range_hybrid": {
    +      "Enabled": false,
    +      "Type": "Hybrid",
    +      "Expiration": "5 minutes",
    +      "Parameters": ["from", "to"],
    +      "When": [
    +        { "Parameter": "to", "Value": null, "Then": "skip" }
    +      ]
    +    }
    +  }
    +}
    sql
    sql
    comment on function get_orders(from text, to text) is '
    +HTTP GET
    +@cache_profile date_range_hybrid
    +';

    Profile fields:

    • Enabled (bool, default false) — disabled profiles are skipped at startup; flip to true to activate.

    • TypeMemory, Redis, or Hybrid. Backends are pooled: all profiles of the same type share one instance (one Memory cache, one Redis connection, one HybridCache singleton). A backend type is only instantiated if root or some enabled profile uses it.

    • Expiration — default expiration in PostgreSQL interval format. Used when the endpoint has no @cache_expires annotation.

    • Parameters — default cache-key parameter list:

      • null (or property omitted): use all routine parameters.
      • [] (empty array): URL-only cache (one entry per endpoint, regardless of inputs).
      • ["x", "y"]: only those named parameters as the key.

      The endpoint's @cached p1, p2 annotation overrides this.

    • When — list of conditional rules. Each rule has:

      • Parameter — the routine parameter name to inspect.
      • Value — match condition. Scalar (single match) or array (OR over entries). JSON null matches .NET null/DBNull.Value (does not match empty string). Other values are stringify-and-equal case-insensitive.
      • Then — the literal string "skip" to bypass the cache for that request, OR a PostgreSQL interval (e.g. "30 seconds") to override the entry's TTL when writing.

      Rules evaluate in declaration order; first match wins. No match → fall through to the profile's Expiration.

      This unlocks scenarios that pure skip-on-condition couldn't express:

      • Skip-on-null: [{ "Parameter": "to", "Value": null, "Then": "skip" }]
      • Tiered TTL: [{ "Parameter": "tier", "Value": "free", "Then": "5 minutes" }, { "Parameter": "tier", "Value": "pro", "Then": "1 hour" }]
      • Status-aware caching: [{ "Parameter": "status", "Value": ["draft", null], "Then": "skip" }, { "Parameter": "status", "Value": "published", "Then": "1 hour" }]

      Validation: a rule whose Parameter is not in the resolved cache-key parameter list (Parameters or the endpoint's @cached) is dropped at startup with a Warning. This prevents the surprising case where two requests with different rule-matched values share the same cache entry.

    Annotation: @cache_profile <name> selects a profile. It implies caching even without a separate @cached annotation. The existing @cached p1, p2 (overrides profile params) and @cache_expires <interval> (overrides profile expiration) annotations continue to work and take precedence over the profile's defaults.

    Misconfiguration is loud at startup. Unknown profile names referenced by @cache_profile cause startup to fail with a single InvalidOperationException listing every unresolved name and the endpoints that referenced each — so typos surface immediately rather than silently disabling caching at runtime. Profiles registered but unreferenced log an Information warning. Bad Type or Expiration values warn and skip the profile. Empty/whitespace profile names are rejected.

    Cache key isolation. Entries written under a profile are prefixed with the profile name, so two profiles sharing the same backend (e.g., two Memory profiles) cannot collide on the same routine + parameters. Endpoints without a profile have no prefix — existing cache entries stay wire-compatible across the upgrade.

    The default appsettings.json ships with three disabled example profiles covering each Type and demonstrating a When rule. Flip "Enabled": true on the one(s) you need.

    Never-expiring (infinite) cache entries

    There is no explicit "forever" or "never" literal — instead, omit the Expiration field to get never-expiring entries. This applies at every level:

    • Endpoint without @cache_expires → entry never expires (today's pre-3.13 behavior, unchanged).
    • Profile without Expiration field → entries written under that profile never expire.
    • Both @cache_expires and profile Expiration set → annotation wins (the explicit interval is used).

    If you need a mix in one app — some profiles with TTL, others never-expiring — define a dedicated profile with no Expiration:

    jsonc
    jsonc
    "Profiles": {
    +  "static_lookup_data": {
    +    "Enabled": true,
    +    "Type": "Memory"
    +    // No Expiration → entries never expire (suitable for ISO codes, taxonomies, etc.)
    +  },
    +  "session_data": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "Expiration": "1 hour"
    +  }
    +}

    Endpoints opt into the appropriate profile via @cache_profile. This pattern handles the common cases (static reference data, immutable historical content) without needing a separate "force never expire" override.

    New: WrapInTransaction Option (Connection Pooler Compatibility)

    When set to true, every request is wrapped in an explicit BEGIN ... COMMIT, and all set_config calls switch from session-scoped (is_local=false) to transaction-local (is_local=true).

    This is required for connection poolers in transaction mode — including PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, and Supabase Pooler. Previously, set_config(name, value, false) would set the GUC at the session level on the underlying PostgreSQL backend. With a transaction-mode pooler, the same backend is reused for unrelated client requests, which means session-scoped GUCs from one request could be visible to the next. With WrapInTransaction = true, GUCs are scoped to the request transaction and discarded on COMMIT.

    The default remains false to preserve existing behavior; it is safe to leave off when using Npgsql's native pool only (which issues DISCARD ALL on connection return).

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true
    +  }
    +}

    New: BeforeRoutineCommands Option

    A new option allowing arbitrary SQL commands to run after any context is set but before the main routine call. They run in the same batch as the context set_config calls, so there is no extra network round-trip.

    Each entry can be either a raw SQL string (no parameters) or an object with Sql and Parameters. Each parameter has a Source (Claim, RequestHeader, or IpAddress) and an optional Name (claim type or header name). Parameter values are bound at request time from HttpContext — claim and header values are passed as parameterized SQL inputs (no string interpolation, no injection risk).

    The most useful pattern is multi-tenant search_path setup driven by a JWT/cookie claim:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true,
    +    "BeforeRoutineCommands": [
    +      "select set_config('app.request_time', clock_timestamp()::text, true)",
    +      {
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +      }
    +    ]
    +  }
    +}

    Per-request execution order with this config:

    1. BEGIN
    2. Each BeforeRoutineCommand is added as a NpgsqlBatchCommand (with parameters bound from claims/headers/IP) and dispatched in a single batch.
    3. The main routine call.
    4. COMMIT.

    Steps 1–3 share a single network round-trip.

    Fix: 400 Bad Request responses are no longer silent in logs

    Endpoints that returned HTTP 400 were not being logged at all, making client-error problems invisible in production. Two independent paths produced silent 400s:

    1. Database exceptions mapped to 400 (P0001 raise exception, P0004 assert_failure, or any user-configured ErrorHandlingOptions mapping to 400). The exception handler in NpgsqlRestEndpoint explicitly skipped logging for status 400.
    2. Validation rule failures (ValidationOptions.Rules → 400). These were logged at Debug level, which is below the default minimum log level (Information), so they never appeared in production logs.

    Fix: 400s are now logged at Warning level — visible by default but not raised to Error, since 400 is a client-side problem rather than a server fault. Genuine server errors (500, etc.) continue to be logged at Error with full stack traces.

    Docker Images: Ubuntu 26.04 LTS Base

    The native AOT Docker images (latest, latest-arm, latest-bun) now build on Ubuntu 26.04 "Resolute Wolf" LTS, up from Ubuntu 25.04 (a 9-month interim release that is reaching end of support). This extends the security-update window for the published images to the 5-year LTS support period and picks up a newer stack (Linux 7.0 kernel, newer OpenSSL, cgroup v2). No changes are required for consumers of the images — runtime dependencies (libssl3, libgssapi-krb5-2, ca-certificates) resolve under the same package names on 26.04.

    NuGet Package Upgrades

    NpgsqlRest (main library):

    • Microsoft.SourceLink.GitHub 10.0.201 → 10.0.203 (build-time only)

    NpgsqlRestClient (client application):

    • Microsoft.AspNetCore.Authentication.JwtBearer 10.0.5 → 10.0.7
    • Microsoft.Extensions.Caching.Hybrid 10.4.0 → 10.5.0
    • Microsoft.Extensions.Caching.StackExchangeRedis 10.0.5 → 10.0.7
    • StackExchange.Redis 2.12.8 → 2.12.14
    `,78)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.13.0.md.kmKD3jrx.lean.js b/assets/guide_changelog_v3.13.0.md.kmKD3jrx.lean.js new file mode 100644 index 000000000..3ceff46e0 --- /dev/null +++ b/assets/guide_changelog_v3.13.0.md.kmKD3jrx.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as n}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.13.0 (2026-04-24)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.13.0.md","filePath":"guide/changelog/v3.13.0.md"}'),t={name:"guide/changelog/v3.13.0.md"};function l(h,s,p,k,r,o){return e(),a("div",null,s[0]||(s[0]=[n("",78)]))}const u=i(t,[["render",l]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.14.0.md.DqJvVHhw.js b/assets/guide_changelog_v3.14.0.md.DqJvVHhw.js new file mode 100644 index 000000000..982c7c846 --- /dev/null +++ b/assets/guide_changelog_v3.14.0.md.DqJvVHhw.js @@ -0,0 +1,20 @@ +import{_ as t,c as a,o as s,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.14.0 (2026-05-09)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.14.0.md","filePath":"guide/changelog/v3.14.0.md"}'),o={name:"guide/changelog/v3.14.0.md"};function r(i,e,l,d,c,p){return s(),a("div",null,e[0]||(e[0]=[n(`

    Changelog v3.14.0 (2026-05-09)

    Version 3.14.0 (2026-05-09)

    Full Changelog

    This release sharpens the philosophy of the standalone client: REST endpoints come from PostgreSQL routines (functions, procedures) and from explicit SQL files — not auto-generated CRUD on tables and views. It also makes real-time push (SSE) more honest about what your annotations mean, and gets more throughput out of array- and composite-heavy responses.

    Removed: auto-CRUD endpoint generation from the standalone client

    The NpgsqlRest.CrudSource plugin is no longer wired into the NpgsqlRestClient standalone executable. The plugin itself still ships as a NuGet package and remains fully functional for embedded use — projects that consume the NpgsqlRest library directly can continue to register a CrudSource instance against EndpointSources exactly as before. Only the NpgsqlRestClient JSON-driven path is affected.

    Why: auto-generated CRUD endpoints over arbitrary tables and views have always been a different shape from the rest of the project. The core promise is "your PostgreSQL routines become REST endpoints" — explicit, version-controlled, comment-annotated procedures and functions where the developer chose what to expose. Auto-CRUD inverts that: every table becomes ten URL patterns by default (select / insert / update / delete plus the various on conflict and returning variants), with no per-endpoint guardrails unless you opt back into them. That's a different product, and it doesn't belong in the same configuration surface.

    What this means in practice:

    • appsettings.json: the entire NpgsqlRest:CrudSource block has been removed from the default template. Any existing configuration with that block will fail key validation at startup (the same Config:ValidateConfigKeys check that catches typos) — remove the block to upgrade.
    • --config output: CrudSource no longer appears in the dumped configuration.
    • --version output: NpgsqlRest.CrudSource is no longer listed in either the human-readable or --json form, since the standalone client no longer references the assembly.
    • Library use: zero change. using NpgsqlRest.CrudSource; sources.Add(new CrudSource()) works exactly as it did in 3.13.0 and remains supported.

    If you had "CrudSource": { "Enabled": true } in your config and depend on the generated endpoints, the migration is one of:

    1. Switch to function-based wrappers — the recommended path. Write the CRUD shape you actually need as PostgreSQL functions or procedures with HTTP annotations. You get the same endpoints with explicit per-endpoint auth, validation, caching, rate limiting, and comment-driven URL shapes.
    2. Embed the library in a custom host and register CrudSource programmatically. The plugin code still ships under plugins/NpgsqlRest.CrudSource/.

    What's new

    Two new SSE annotations: @sse_publish and @sse_subscribe

    If you've ever had a manager-side procedure broadcast notifications to user-side subscribers, you've probably hit the awkwardness of the all-in-one @sse annotation: every emitter procedure ended up with a phantom /info URL nobody connected to, and your generated TypeScript client included createXEventSource() helpers for procedures that should never be subscribed to.

    3.14.0 splits the responsibilities:

    AnnotationWhat it does
    @sse_publishThis procedure's RAISE statements feed SSE subscribers. No subscribe URL is exposed.
    @sse_subscribeExposes a subscribe URL for EventSource clients. The procedure body is never executed when a client opens the stream.
    @sse [path]Same as before — shorthand for both. Unchanged.

    So a "manager broadcasts to users" flow now reads cleanly:

    sql
    sql
    -- subscriber URL, body never runs on subscribe
    +comment on function subscribe_user_events() is '
    +HTTP GET
    +@sse_subscribe
    +';
    +
    +-- emitter, no subscribe URL, no useless TS EventSource helper
    +comment on procedure broadcast_user_message(...) is '
    +HTTP POST
    +@sse_publish
    +@sse_scope authorize
    +';

    The TypeScript client generator follows automatically: @sse_publish produces a plain POST function, @sse_subscribe keeps the EventSource helper, and @sse is unchanged.

    All existing event filtering — @sse_scope, @sse_events_level, RAISE ... USING HINT, and the X-NpgsqlRest-ID execution-id header — works the same with both new annotations.

    Warning when a RAISE looks like a missed @sse_publish

    Forgetting @sse_publish on an emitter procedure used to fail silently: the RAISE ran, the notice logged, and zero events reached subscribers. NpgsqlRest now warns once per endpoint when a RAISE whose severity matches the configured SSE forwarding level fires in a procedure that has no @sse or @sse_publish:

    code
    WARN: RAISE INFO in endpoint /api/update-user-roles was not broadcast to SSE subscribers —
    +the endpoint has no @sse or @sse_publish annotation. Add @sse_publish to forward this
    +routine's notices, or set WarnUnboundServerSentEventsNotices=false to silence this warning.

    The warning only fires when the project actually uses SSE somewhere and only when the severity matches — projects that use RAISE NOTICE for unrelated logging see no warnings, and projects that don't use SSE at all see no warnings. Configurable via the new WarnUnboundServerSentEventsNotices setting (default true).

    Reliable SSE connection handshake

    SSE responses now flush a small "connected" line as soon as the broadcaster has registered the new subscriber, instead of waiting for the first real event. Browsers and EventSource clients ignore comment-only lines per spec, so no consumer behavior changes — but a client that wants to do "subscribe, then publish, then receive" can now rely on the handshake completing before its publish call. Mostly visible to integration tests; in production it makes connection states more predictable.

    Startup error when claim-mapped parameters use a non-text type

    If Auth.UseUserParameters is on and your ParameterNameClaimsMapping references a procedure parameter whose SQL type isn't text-compatible — for example _company_id int mapped to a company_id claim — every authenticated request used to crash with this error from deep inside the driver:

    code
    System.InvalidCastException: Writing values of 'System.String' is not supported
    +for parameters having NpgsqlDbType 'Integer'.

    The exception didn't mention claim mapping, so debugging meant a stack-trace hunt. NpgsqlRest now catches the misdeclaration at startup with a precise message naming the endpoint, parameter, claim, and the SQL type:

    code
    Endpoint POST /api/create-local-user parameter _company_id is mapped to claim
    +'company_id' but its type is 'int' which is not text-compatible. Claim values
    +are strings, so binding would fail at runtime with InvalidCastException.
    +Declare the parameter as text/varchar/char/json/jsonb/xml/jsonpath, or remove
    +'_company_id' from ParameterNameClaimsMapping.

    Accepted types: text, varchar, char, name, xml, json, jsonb, jsonpath, plus unknown (the SQL-file-source case where the driver resolves the type server-side). Any other type fails fast at UseNpgsqlRest. The check only runs for endpoints with UseUserParameters enabled and only for parameters that match a configured claim mapping.

    Warning when a request value is overridden by claim auto-bind

    When a parameter is auto-bound from a claim, the claim wins — that's intentional, especially for security-sensitive procedures where the caller's identity must override anything the request supplies. But if the request also sent a value for that parameter, the value used to be discarded silently. With certain UI patterns (forms that POST every field) this hid real bugs: in one case update_user_roles(_user_id text, _roles text[]) looked like it was updating a target user, but every request modified the caller's own roles because _user_id was claim-mapped.

    The auto-bind still wins (no behavior change for security), but a collision now emits a warning so the developer can see what happened:

    code
    Endpoint /api/update-user-roles parameter _user_id received a body value but
    +is auto-bound from claim 'name_identifier'. The supplied value is being ignored.

    The warning fires only when the request actually supplied a value, naming the endpoint, parameter, source (body or query), and the claim.

    Performance

    A focused pass on response rendering. No public API or configuration changes.

    Lower-allocation JSON conversion for arrays and composites

    The four PostgreSQL → JSON conversion routines used to render array, composite, and tuple values now rent their working StringBuilder buffers from the existing pool instead of allocating fresh ones on every call. This affects PgArrayToJsonArray, PgCompositeArrayToJsonArray, PgTupleToJsonObject, and PgUnknownToJsonArray — all of which fire per row × per column on responses that include array or composite types.

    Measured on a focused micro-benchmark (Apple M4 Pro, .NET 10, BenchmarkDotNet ShortRun, three iterations):

    FunctionBefore allocAfter allocΔ
    PgArrayToJsonArray (numeric, 100 elem)1.91 KB1.16 KB−39%
    PgArrayToJsonArray (text, 100 elem)19.68 KB13.86 KB−29%
    PgCompositeArrayToJsonArray (50 elem)23.02 KB11.52 KB−50%
    PgTupleToJsonObject (10 fields)1.10 KB1.27 KBwithin noise

    CPU time per call moved by single-digit percent — within or near the noise band of a short BDN run. The headline win is reduced GC pressure during sustained load: a 100-row response containing several array columns can drop ~1–2 MB of allocation per request, and a multi-row composite-array response can drop ~5–10 MB.

    Estimated impact on the PostgreSQL REST API Benchmark 2026 workloads

    These are extrapolations from the micro-benchmark above, not re-measured numbers from re-running the published benchmark. They estimate how the allocation reduction translates to throughput and tail latency under that benchmark's 100 VU sustained concurrency — where reduced GC pressure compounds. Scenarios with little or no array/composite work see essentially no change because the optimized paths don't fire.

    ScenarioBaseline (3.4.7)What firesEst. req/s ΔEst. P99 latency Δ
    Minimal Baseline16,065 req/sNothing — no arrays, no composites0%0%
    Many Parameters (20)11,504 req/sQuery-string parsing only — not touched0%0%
    POST Body (10 rec)6,101 req/sArrays in 10-row response+1 to +3%−2 to −5%
    Data Type (1 rec)4,588 req/sFew array columns × 1 row+0 to +2%−2 to −5%
    Nested JSON (depth 1, 100 rows)3,061 req/sComposite paths fire heavily+5 to +10%−5 to −15%
    Large Payload (100 KB)1,096 req/sDepends on payload shape+0 to +3%varies
    Data Type (100 rec)377 req/sArray cols × 100 rows — ~1–2 MB/req cut+3 to +7%−5 to −12%
    Data Type (500 rec)82 req/sArray cols × 500 rows — ~5–10 MB/req cut+5 to +10%−8 to −15%

    The largest absolute wins land on the high-record-count scenarios where allocation pressure is greatest. The largest relative tail-latency improvements land on the same scenarios because Gen0 stalls dominate P99 under that load shape.

    Two important caveats:

    1. The published benchmark measured 3.4.7. Current master already has months of perf work on top of that. These estimates apply on top of current state; they assume the relative shape (CPU vs. PostgreSQL vs. network) hasn't shifted dramatically since 3.4.7.
    2. End-to-end requests spend most of their time in PostgreSQL, network round-trip, and Kestrel. The optimized paths are a slice of response rendering, so the gains compound only where rendering CPU or GC is the bottleneck. For a single-row response the optimized work is microseconds out of milliseconds; for a 500-row array-heavy response it's a much larger share.

    UTF-8 literals for JSON markup constants

    Consts.Utf8OpenBrace, Utf8CloseBrace, Utf8OpenBracket, Utf8CloseBracket, Utf8Comma, Utf8Colon, and Utf8Null are now static ReadOnlySpan<byte> properties backed by "x"u8 UTF-8 string literals, instead of static readonly byte[] fields. The bytes are embedded directly in the assembly metadata, so each access is a pointer-and-length to read-only data — zero heap allocation, ever. Eliminates seven small startup-time allocations.

    Tighter PipeWriter writes

    The hot-path JSON markup writes (commas, braces, brackets, the "null" literal) have been collapsed from a three-step GetSpan / CopyTo / Advance pattern to a single IBufferWriter<byte>.Write(ReadOnlySpan<byte>) call across ten call sites in the response renderer. Same allocation profile, fewer chances to mismatch sizes.

    Hardening (silent-failure fixes)

    Each of these fixes a class of silent failure that used to require log digging or memory monitoring to detect.

    ArrayPool rent now in try/finally

    PgCompositeArrayToJsonArray rents a char[] from ArrayPool<char>.Shared for inputs over 512 chars and previously returned it only on the success path. If the parsing loop threw, the rented buffer was lost from the shared pool until process exit — a slow, silent leak that compounded over uptime. Returns now happen in finally, so a malformed PostgreSQL value can't poison the pool.

    Multi-command StringBuilder rentals always released

    The mcRowBuilder and mcCompositeBuffer StringBuilders rented inside the multi-command result-rendering loop are now lifted to method scope and released in an outer finally even if the inner reader loop throws.

    proxy_out buffer released on exception path

    The MemoryStream used by the proxy_out feature to capture function output before forwarding upstream is now disposed in an inner try/finally, so a forwarding failure can't leak the buffer.

    Column-decryption failures now logged at Trace

    Three call sites that decrypt column values via IDataProtector.Unprotect previously had silent catch { } blocks — by design, so a failed decryption falls back to the raw ciphertext rather than surfacing as a 500. The fall-back is preserved, but the failure is now logged at LogLevel.Trace:

    code
    Column decryption failed; falling back to raw value. Error: <message>

    A misconfigured key, a tampered ciphertext, or a key-rotation mismatch is now observable when Trace logging is enabled instead of being completely silent.

    Configuration

    One new optional setting:

    • WarnUnboundServerSentEventsNotices (default true) — controls the new "missed @sse_publish" warning described above. Set false if your project intentionally uses RAISE for non-SSE logging and you don't want NpgsqlRest commenting on it.

    No keys removed or renamed; existing appsettings.json works as-is.

    Test suite

    1949 tests pass on the release branch. 12 of those are new for the SSE work (URL routing under each annotation combination, end-to-end live event delivery from a publisher procedure to a subscriber on a different procedure's URL, the missed-annotation warning, TS client output for both new annotations) and 5 are new for the claim auto-bind diagnostics. A new SseTestClient helper opens streaming HTTP connections and waits for the broadcaster to register subscribers before publishing — reusable for upcoming SSE work like heartbeats and Last-Event-ID resume.

    `,72)]))}const g=t(o,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.14.0.md.DqJvVHhw.lean.js b/assets/guide_changelog_v3.14.0.md.DqJvVHhw.lean.js new file mode 100644 index 000000000..868886191 --- /dev/null +++ b/assets/guide_changelog_v3.14.0.md.DqJvVHhw.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as n}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.14.0 (2026-05-09)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.14.0.md","filePath":"guide/changelog/v3.14.0.md"}'),o={name:"guide/changelog/v3.14.0.md"};function r(i,e,l,d,c,p){return s(),a("div",null,e[0]||(e[0]=[n("",72)]))}const g=t(o,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.15.0.md.DiRcMMNc.js b/assets/guide_changelog_v3.15.0.md.DiRcMMNc.js new file mode 100644 index 000000000..dc163bda2 --- /dev/null +++ b/assets/guide_changelog_v3.15.0.md.DiRcMMNc.js @@ -0,0 +1,65 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.0.md","filePath":"guide/changelog/v3.15.0.md"}'),n={name:"guide/changelog/v3.15.0.md"};function o(l,e,r,h,p,d){return a(),i("div",null,e[0]||(e[0]=[t(`

    Changelog v3.15.0

    Version 3.15.0 (2026-05-11)

    Full Changelog

    This release prepares NpgsqlRest to act as an external Web API service for a separate partner or frontend application. Three pieces land together:

    1. Auth fix — named cookie schemes registered under Auth:Schemes (introduced in 3.13.0) now actually authenticate incoming requests; previously they signed users in but no endpoint accepted the cookie.
    2. Cookie attributesCookieSameSite and CookieSecure config knobs on both the main and named cookie schemes, so cookie-based auth works across origins (SPA on a different domain).
    3. OpenAPI filtering — config-level filters (IncludeSchemas / ExcludeSchemas / NameSimilarTo / NameNotSimilarTo / RequiresAuthorizationOnly) plus a per-routine @openapi hide / @openapi tag <name> comment annotation, so a single host can serve a curated OpenAPI document to partners while keeping internal endpoints out of the spec.

    Changes are concentrated in NpgsqlRestClient/Builder.cs, plugins/NpgsqlRest.OpenApi/, and NpgsqlRest/Defaults/CommentParsers/. No breaking changes; existing appsettings.json works as-is — every new key defaults to "no filter" or "ASP.NET default".

    Configurations using Auth:Schemes to register a named cookie scheme alongside the main CookieAuth issued the named-scheme cookie correctly on login but no endpoint would authenticate against it. A request bearing only the named-scheme cookie was treated as anonymous — framework endpoints like /api/passkey/add/options returned 401, and SQL endpoints annotated @authorize returned 401. The feature signed users in but the sign-in was functionally inert.

    Root cause

    ASP.NET's authentication middleware only runs against the default authenticate scheme. The 3.13.0 implementation:

    1. Counted only the three main auth types (cookie / bearer / jwt) when choosing the default scheme — named schemes were invisible to that calculation.
    2. Registered the policy scheme (the dispatcher that picks the right scheme per request) only when more than one of the three main types was enabled. A typical setup with cookies + a named cookie scheme skipped the dispatcher entirely.
    3. Even when the dispatcher ran, its ForwardDefaultSelector only distinguished Bearer-vs-cookie header type. For any cookie-bearing request it returned the main cookie scheme regardless of which cookie was actually present.

    The result: named-scheme cookies hit the main scheme's cookie handler, which couldn't decrypt them (different data-protection purpose strings per scheme), so context.User came out anonymous.

    What changed

    In NpgsqlRestClient/Builder.cs:

    • Pre-scan Auth:Schemes for enabled Cookie-type entries before AddAuthentication runs, so the default-scheme decision can account for them.
    • Register a policy scheme whenever the system has either (a) multiple main auth types — the existing case, unchanged — or (b) the main cookie scheme plus one or more named cookie schemes. For (b), a synthetic policy-scheme name (NpgsqlRest_PolicyScheme) is used to avoid colliding with the main cookie scheme's own registration.
    • Cookie-aware dispatchForwardDefaultSelector now walks the registered cookie schemes in order (main first, then named in registration order) and returns the first scheme whose configured cookie name appears in the request. Falls back to the main cookie scheme for cookie-less requests so anonymous traffic behaves exactly as before. Bearer/JWT header dispatch is unchanged.
    • Cookie-name tracking — every cookie scheme registration (main and named) now records its effective HTTP cookie name on Builder.CookieSchemesInOrder. Schemes without an explicit CookieName are tracked under ASP.NET's per-scheme default (.AspNetCore.<schemeName>), so the lookup is well-defined for both explicit and defaulted cookie names.

    Behavior after the fix

    • A request carrying only a named-scheme cookie authenticates under that scheme. context.User.Identity.IsAuthenticated is true, context.User.Identity.AuthenticationType matches the named scheme name.
    • /api/passkey/add/options, /api/passkey/add, bearer/JWT refresh paths, and any @authorize-annotated SQL endpoint accept named-scheme cookies the same way they accept main-scheme cookies. No endpoint changes were required.
    • @authorize <role> continues to gate by role claims — a named-scheme cookie whose principal lacks the required role is still rejected. Scheme membership is orthogonal to role membership.
    • Backward compatibility is bit-for-bit identical for single-scheme configurations (cookies only, no Auth:Schemes): no policy scheme is registered, no selector logic engages, and the default authenticate scheme remains the main cookie scheme's name.

    When a request somehow carries both a main cookie and a named-scheme cookie (rare in practice — a user is signed in under at most one scheme by SignInAsync), the walk order is main first, then named schemes in registration order, and the first match wins. This is deterministic but not configurable; if you need scheme-specific endpoint binding regardless of which cookie is present, ASP.NET's [Authorize(AuthenticationSchemes = "...")] is the right primitive and is out of scope for this release.

    Feature: CookieSameSite and CookieSecure config

    ASP.NET defaults the auth cookie's SameSite attribute to Lax and the Secure policy to SameAsRequest. That works for "browser and API on the same origin" but silently breaks the cross-origin case — an SPA on app.example.com calling an API on api.example.com won't have its session cookie sent on cross-site requests at all under Lax, and a None cookie without Secure is dropped outright by modern browsers.

    Two new config keys make this controllable without dropping to a custom host.

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",       // "Strict" | "Lax" | "None" | "Unspecified" | null
    +  "CookieSecure":   "Always"      // "SameAsRequest" | "Always" | "None" | null
    +}

    Default for both is null, which leaves ASP.NET's per-handler default in place — so existing configs see no change.

    Per-scheme override under Auth:Schemes

    The same two keys are accepted inside any Auth:Schemes:<name> Cookies-type entry, with the same inheritance pattern as the existing cookie fields (CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly): scheme-level value wins, else inherit the root Auth section's value, else fall through to ASP.NET's default.

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",
    +  "CookieSecure":   "Always",
    +  "Schemes": {
    +    // Long-lived "remember me" cookie inherits the cross-origin posture from root.
    +    "remember_me":   { "Type": "Cookies", "CookieValid": "30 days" },
    +
    +    // Short-lived sensitive-flow cookie tightens to first-party only.
    +    "short_session": {
    +      "Type": "Cookies",
    +      "CookieValid": "1 hour",
    +      "CookieSameSite": "Strict",
    +      "CookieSecure":   "SameAsRequest"
    +    }
    +  }
    +}

    Validation and warnings

    • Unknown values fail fast at startup with the offending config path included in the message — typos in security-relevant config shouldn't be silently ignored. Example: Invalid value 'Loose' for Auth:CookieSameSite. Expected one of: Unspecified, None, Lax, Strict.
    • Setting SameSite=None without Secure=Always logs a startup warning at Warning level: browsers drop SameSite=None cookies that lack the Secure attribute, and the symptom ("login succeeds but the next request is anonymous") is otherwise hard to diagnose, especially during local HTTP testing.
    • Existing appsettings.json files are unaffected — both keys default to null (use ASP.NET's default).

    Cross-origin checklist for an external Web API setup

    Combining the cookie attributes above with the already-existing CORS support, a typical "API used by a separate SPA" config looks like:

    jsonc
    jsonc
    "Cors": {
    +  "Enabled": true,
    +  "AllowedOrigins": ["https://app.example.com"],   // not "*"
    +  "AllowedMethods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    +  "AllowedHeaders": ["*"],
    +  "AllowCredentials": true                          // required for cookie auth
    +},
    +"Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",                         // cross-site
    +  "CookieSecure":   "Always",                       // required when SameSite=None
    +  "CookieHttpOnly": true,
    +  "CookieDomain":   ".example.com"                  // optional — share across subdomains
    +}

    For mobile or non-browser clients the bearer/JWT path remains the recommended route (no cookie attributes apply, no CORS preflight); these new knobs only matter when the client is a browser on a different origin.

    Feature: OpenAPI filtering for partner-facing documents

    The NpgsqlRest.OpenApi plugin previously documented every endpoint NpgsqlRest registered, with no way to suppress an endpoint or override its tag. That works for "internal API doc" but not for "API doc handed to a partner team" — where you typically want to expose only a curated subset (e.g. routines in a partner schema, only the authenticated surface, with a partner tag for nice Swagger UI grouping).

    3.15.0 adds the missing controls: five config-level filters on OpenApiOptions plus a per-routine @openapi comment annotation. All work additively — every existing config keeps its current output, since defaults are "no filter".

    Config-level filters

    KeyTypeDefaultBehavior
    IncludeSchemasstring[]empty = no filterAllow-list of schema names. Only endpoints whose routine schema appears here are documented.
    ExcludeSchemasstring[]empty = no filterDeny-list of schema names. Applied alongside IncludeSchemas — both must pass.
    NameSimilarTostringnull = no filterPostgreSQL-style SIMILAR TO pattern matched against the routine name. _ matches one char, % matches any sequence; \`
    NameNotSimilarTostringnull = no filterSame syntax as above but for exclusion. Applied alongside NameSimilarTo.
    RequiresAuthorizationOnlyboolfalseWhen true, only RequiresAuthorization-bearing endpoints are documented — health/login/probes drop out.

    These all live in the same place existing knobs do — under NpgsqlRest:OpenApiOptions in appsettings.json for the standalone client, or on OpenApiOptions for library users.

    Per-routine @openapi comment annotation

    Two sub-commands; both are no-ops when the OpenAPI plugin isn't loaded, so they're safe to leave on a routine regardless of how the host is configured.

    AnnotationEffect
    @openapi, @openapi hide, @openapi hidden, @openapi ignoreExclude this routine from the OpenAPI document. HTTP endpoint stays functional — only the spec entry is skipped.
    @openapi tag <name>, @openapi tags <a>, <b>Replace the default schema-name tag with the supplied value(s). Drives section grouping in Swagger UI / ReDoc; tag values preserve original casing.
    sql
    sql
    -- Hidden from the document; endpoint is still reachable internally.
    +comment on function refresh_materialized_views() is '
    +HTTP POST
    +@authorize admin
    +@openapi hide
    +';
    +
    +-- Grouped under "Partner API" in Swagger UI instead of the default "public" schema tag.
    +comment on function partner_get_orders(_partner_id text) is '
    +HTTP GET /api/partner/orders
    +@authorize partner
    +@openapi tag Partner API
    +';

    Filter order and composition

    Filters are checked in OpenApi.Handle() in this order. The first one that rejects short-circuits — the rest don't run. Multiple filters compose conjunctively (all must pass for an endpoint to be documented).

    1. OpenApiHide annotation on the endpoint (per-routine wins over everything)
    2. RequiresAuthorizationOnly vs. RequiresAuthorization
    3. IncludeSchemas membership
    4. ExcludeSchemas membership
    5. NameSimilarTo match
    6. NameNotSimilarTo match (negative)
    7. → endpoint documented

    Partner-facing config example

    The full "API server, partner-facing OpenAPI document, internal endpoints invisible" config:

    jsonc
    jsonc
    "NpgsqlRest": {
    +  "OpenApiOptions": {
    +    "Enabled": true,
    +    "FileName": "openapi-partner.json",
    +    "UrlPath": "/openapi/partner.json",
    +    "DocumentTitle": "Acme Partner API",
    +    "DocumentDescription": "JWT-authenticated REST surface for partner integrations.",
    +
    +    "IncludeSchemas": ["partner"],                  // only partner-namespaced routines
    +    "RequiresAuthorizationOnly": true,              // drop health, login, probes
    +    "NameNotSimilarTo": "%_admin",                  // drop partner_*_admin maintenance routines
    +
    +    "SecuritySchemes": [
    +      { "Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT" }
    +    ],
    +    "Servers": [
    +      { "Url": "https://api.acme.com", "Description": "Production" }
    +    ]
    +  }
    +}

    The same host can still serve the internal cookie-authenticated surface — only the document is partner-scoped. A later operational change (e.g. moving to a separate process per audience) doesn't break what's been advertised to partners, since the document is config-driven.

    Tests

    Three new test files, 41 new tests total. Total auth + OpenAPI test count: 145 (78 pre-existing auth + 19 cookie/policy from this release + 32 OpenAPI including 16 filter / 9 annotation / 7 pre-existing path-parameter tests).

    • NpgsqlRestTests/AuthTests/AuthPolicySchemeTests.cs (16 tests): CookieSchemesInOrder population, policy-scheme registration decisions across single/multi/named combinations, and ForwardDefaultSelector dispatch (named cookie → named scheme, main cookie → main, both → main wins per documented order, neither → main fallback, bearer header preserved, JWT three-part token preserved, named cookie in composite mode, default .AspNetCore.<scheme> cookie name).
    • NpgsqlRestTests/AuthTests/AuthCookieSameSiteSecureTests.cs (15 tests): parsing of all four SameSiteMode and three CookieSecurePolicy values (case-insensitive), invalid-value fail-fast on the root and on named schemes (with the offending path in the message), unset values preserving ASP.NET defaults, the cross-origin SameSite=None; Secure=Always pattern reaching CookieAuthenticationOptions, named-scheme inheritance from root, per-scheme override winning over root.
    • NpgsqlRestTests/OpenApiTests/OpenApiFilterTests.cs (16 tests): per-filter coverage for OpenApiHide, RequiresAuthorizationOnly, IncludeSchemas, ExcludeSchemas, NameSimilarTo (prefix %, single-char _, anchoring, alternation (get|set)_%), NameNotSimilarTo, all-filters-together composition, plus OpenApiTags override of the default schema tag. Drives the plugin directly with synthetic RoutineEndpoints, asserting against the JSON file the plugin writes.
    • NpgsqlRestTests/OpenApiTests/OpenApiAnnotationTests.cs (9 tests): end-to-end through the global TestFixture's OpenAPI handler. Verifies all four aliases for @openapi hide (bare, hide, hidden, ignore), @openapi tag single + multi, original-casing preservation for tag values, and that the default schema tag is unaffected when no @openapi annotation is present.

    Pre-existing auth-scheme tests (AuthSchemeRegistrationTests, AuthSchemeLoginTests, AuthLegacyFieldFailFastTests, AuthIntervalNotationTests) and OpenAPI path-parameter tests continue to pass unchanged.

    Configuration summary

    Two new optional Auth keys, mirrored under each Auth:Schemes:<name> Cookies-type entry:

    KeyValuesDefaultPurpose
    Auth:CookieSameSiteStrict / Lax / None / Unspecifiednull (ASP.NET default)SameSite attribute on the cookie. Use None for cross-origin SPA / mobile clients.
    Auth:CookieSecureSameAsRequest / Always / Nonenull (ASP.NET default)When the cookie's Secure attribute is set. Required Always when SameSite is None.

    Five new optional OpenAPI keys under NpgsqlRest:OpenApiOptions:

    KeyValuesDefaultPurpose
    IncludeSchemasstring[]empty = all schemasSchema allow-list for the OpenAPI document.
    ExcludeSchemasstring[]empty = no exclusionSchema deny-list. Applied alongside IncludeSchemas.
    NameSimilarTostring (SIMILAR TO)nullRoutine-name allow pattern.
    NameNotSimilarTostring (SIMILAR TO)nullRoutine-name deny pattern.
    RequiresAuthorizationOnlyboolfalseDocument only authenticated endpoints.

    No keys removed or renamed.

    Out of scope

    • The @authorize annotation continues not to accept a scheme name as a value. Pinning an endpoint to a specific authentication scheme is the job of ASP.NET's [Authorize(AuthenticationSchemes = "...")]; surfacing that through a comment annotation is a separate feature design.
    • SignOutAsync and challenge paths target a specific scheme by name in code, so no changes were needed to ForwardChallenge / ForwardSignOut selectors.
    • The sign-in path (login function returning scheme = '<name>') was already correct in 3.13.0 — this release does not touch it.

    Partner-system integration readiness — what's still missing

    3.15.0 covers the common case of partner integration: JWT Bearer auth, a curated OpenAPI document, and cross-origin cookie auth where applicable. For richer enterprise-grade external-API scenarios, the following capabilities are not yet built in and would land in a future release if there's demand:

    • Per-API-key rate limiting. The partitioned rate limiter exists, but partition keys are typically IP- or user-based today. A first-class "X-Api-Key per-tenant quota" needs custom partition logic — possible to build externally, not configurable out of the box.
    • Idempotency keys. Many partner APIs honor an Idempotency-Key request header so retried POST/PUT calls don't double-charge / double-create. NpgsqlRest has no built-in support; you'd model it in SQL (a seen_keys table consulted before the routine runs) or as custom middleware.
    • HMAC request signing. Some partner programs require body signing on top of JWT (e.g. Stripe-style Signature: t=…,v1=…). Not built in; would need a custom middleware that verifies the signature before NpgsqlRest dispatches.
    • Per-endpoint authentication-scheme binding. @authorize gates by role, not by which auth scheme issued the principal. If you need "partner JWT can hit /api/partner/* but the internal cookie session cannot," that's ASP.NET's [Authorize(AuthenticationSchemes = …)] plumbing — not yet exposed as a comment annotation.
    • Multiple OpenAPI documents per process. One host = one OpenAPI document. The new filters let you scope that document to a partner audience, but you can't serve partner.json and internal.json from the same process. Today: filter to one audience, or run two NpgsqlRest hosts. The plugin's IEndpointCreateHandler interface is single-instance.
    • API-key issuance and rotation flow. Partners rotate keys periodically. NpgsqlRest doesn't ship a key-management UX — you build the issue/rotate/revoke endpoints as ordinary SQL routines on top of the framework.
    • API versioning conventions. No built-in Accept-Version header or path-versioning convention. Today: separate routine names per version (v1_get_orders / v2_get_orders), or filter to a single version per host using the new NameSimilarTo knob.
    • Refresh-token rotation as a first-class story. Refresh paths exist for both BearerToken and JWT schemes, but "rotate on use" with sliding expiration and reuse detection is not documented as load-bearing. Treat as "works for the basic case; harden if you're under threat models that assume token theft."
    • Antiforgery posture for cookie-cross-origin. Antiforgery middleware is wired, but the interaction with SameSite=None cookies — when to require a double-submit token vs. when to rely on SameSite=Strict for state-changing routes — is not explicitly documented. If you go cookie-based cross-origin, audit this for your threat model.

    None of these block a typical "partner team consumes our JWT API + a Swagger UI we host" integration. They're the next layer of polish if NpgsqlRest evolves further toward being a primary external-API platform.

    `,67)]))}const u=s(n,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.15.0.md.DiRcMMNc.lean.js b/assets/guide_changelog_v3.15.0.md.DiRcMMNc.lean.js new file mode 100644 index 000000000..3ef68a142 --- /dev/null +++ b/assets/guide_changelog_v3.15.0.md.DiRcMMNc.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.0.md","filePath":"guide/changelog/v3.15.0.md"}'),n={name:"guide/changelog/v3.15.0.md"};function o(l,e,r,h,p,d){return a(),i("div",null,e[0]||(e[0]=[t("",67)]))}const u=s(n,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.15.1.md.EipfUUTu.js b/assets/guide_changelog_v3.15.1.md.EipfUUTu.js new file mode 100644 index 000000000..e697f0907 --- /dev/null +++ b/assets/guide_changelog_v3.15.1.md.EipfUUTu.js @@ -0,0 +1,19 @@ +import{_ as s,c as a,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.1 (2026-05-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.1.md","filePath":"guide/changelog/v3.15.1.md"}'),t={name:"guide/changelog/v3.15.1.md"};function o(l,e,d,r,h,c){return i(),a("div",null,e[0]||(e[0]=[n(`

    Changelog v3.15.1 (2026-05-11)

    Version 3.15.1 (2026-05-11)

    Full Changelog

    Two bug fixes around config-key validation that together caused legitimate Auth:Schemes setups to fail startup under default settings. No new features; no library changes — both fixes live in NpgsqlRestClient (ConfigDefaults.cs, Program.cs).

    Fix: named auth schemes are validated by Type, not by name

    A configuration like

    json
    json
    {
    +  "Auth": {
    +    "Schemes": {
    +      "short_session": {
    +        "Type": "Cookies",
    +        "CookieName": "my_app",
    +        "CookieHttpOnly": true
    +      }
    +    }
    +  }
    +}

    produced startup errors:

    code
    [ERR] Unknown configuration key: Auth:Schemes:short_session:CookieName
    +[ERR] Unknown configuration key: Auth:Schemes:short_session:CookieHttpOnly

    Both keys are documented per-type override keys for Cookies-type schemes (added in 3.15.0) and are read/applied normally at scheme registration time. The validator was flagging them because of a name collision with the docs-style example entries in Auth:Schemes defaults (short_session, api_token, admin_jwt).

    Root cause

    FindUnknownConfigKeys descended into the defaults schema by key name. When a user's scheme name matched one of the documented examples, the validator validated against that example's incomplete key set instead of treating the entry as an open-dictionary item. The same scheme renamed to anything not in the example set took the open-dict path and validated clean — so the bug surfaced only for users whose scheme names happened to match the documentation.

    What changed

    Validation under Auth:Schemes:<name> is now driven by the scheme's Type field, not its name. The validator reads Type from the actual config and selects one of three type-specific schemas:

    • Cookies: Type, Enabled, CookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly, CookieSameSite, CookieSecure.
    • BearerToken: Type, Enabled, BearerTokenExpire, BearerTokenRefreshPath.
    • Jwt: Type, Enabled, JwtSecret, JwtIssuer, JwtAudience, JwtExpire, JwtRefreshExpire, JwtClockSkew, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey, JwtRefreshPath.

    When Type is missing or unrecognized, the validator skips that scheme silently — RegisterAuthSchemes already throws a clearer error at startup, so double-reporting buys nothing.

    Behavior after the fix

    • Every named scheme — regardless of name — is validated against the same key set per its declared Type. Typos like CooieName are still caught for both example-named and custom-named schemes.
    • Cross-type keys (e.g. JwtSecret on a Cookies-type scheme) are now flagged where they previously slipped through under custom-named schemes via the open-dict shortcut.
    • Existing configurations using the docs-example names (short_session, api_token, admin_jwt) start cleanly with any combination of valid per-type override keys.

    Fix: --config and --validate CLI commands now honor ValidateConfigKeys mode

    The three call sites of ValidateConfigKeys() were inconsistent. Normal startup branched on the mode (only "Error" aborts; "Warning" logs and continues; "Ignore" skips entirely). The two CLI command paths did not — both treated any warning as a fatal validation failure regardless of mode.

    For a user running npgsqlrest --validate with the default Config:ValidateConfigKeys: "Warning", this meant:

    • Exit code 1 on the first unknown key, even though the runtime would have started up normally with the same config.
    • --config (dump current configuration as JSONC) suppressed its JSON output and exited 1 instead, even when the only thing wrong was a typo that would have shown up as a warning at runtime.

    What changed

    Both CLI paths now read the validation mode and apply the same rule as normal startup:

    • Error mode: warnings are fatal. --config prints them in red on stderr and exits 1 without dumping JSON. --validate reports configValid: false.
    • Warning mode (default): warnings are surfaced (yellow on stderr for --config, included in --validate text/JSON output) but they don't fail the run. --config proceeds to dump the JSONC. --validate reports configValid: true.
    • Ignore mode: no warnings produced at all (unchanged — the validator short-circuits earlier).

    --validate --json output gains a warningsAreFatal boolean derived from the mode, so machine consumers can decide for themselves what to do with the warnings array independent of how the binary chose to exit:

    json
    json
    {
    +  "valid": true,
    +  "configValid": true,
    +  "validationMode": "Warning",
    +  "warningsAreFatal": false,
    +  "warnings": ["SomeUnknown:Key"],
    +  "connectionTest": "ok"
    +}

    Behavior after the fix

    • npgsqlrest --validate against a config with a typo + ValidateConfigKeys: "Warning" exits 0; the typo is surfaced as a warning. Set ValidateConfigKeys: "Error" (or pass --Config:ValidateConfigKeys=Error) to keep the old fail-fast behavior.
    • npgsqlrest --config always emits the JSONC dump unless the mode is Error and an unknown key is present. Warnings still print to stderr so typos remain visible.
    • Normal startup is unchanged — it was already correct.

    Tests

    • New unit tests in NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs exercise FindUnknownConfigKeys directly: per-type validation for each example scheme name and custom names, cross-type rejection, typo detection, and missing/invalid Type handling.
    • CLI tests in NpgsqlRestTests/CliTests/CliCommandTests.cs cover the Ignore / Warning / Error matrix for both --config and --validate --json.
    `,31)]))}const u=s(t,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.15.1.md.EipfUUTu.lean.js b/assets/guide_changelog_v3.15.1.md.EipfUUTu.lean.js new file mode 100644 index 000000000..3b1319568 --- /dev/null +++ b/assets/guide_changelog_v3.15.1.md.EipfUUTu.lean.js @@ -0,0 +1 @@ +import{_ as s,c as a,o as i,a5 as n}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.1 (2026-05-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.1.md","filePath":"guide/changelog/v3.15.1.md"}'),t={name:"guide/changelog/v3.15.1.md"};function o(l,e,d,r,h,c){return i(),a("div",null,e[0]||(e[0]=[n("",31)]))}const u=s(t,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.15.2.md.B_9nMiuK.js b/assets/guide_changelog_v3.15.2.md.B_9nMiuK.js new file mode 100644 index 000000000..e561fe485 --- /dev/null +++ b/assets/guide_changelog_v3.15.2.md.B_9nMiuK.js @@ -0,0 +1,14 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.2 (2026-05-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.2.md","filePath":"guide/changelog/v3.15.2.md"}'),n={name:"guide/changelog/v3.15.2.md"};function o(l,e,d,p,r,h){return a(),i("div",null,e[0]||(e[0]=[t(`

    Changelog v3.15.2 (2026-05-11)

    Version 3.15.2 (2026-05-11)

    Full Changelog

    Patch release finishing the config-validator fix started in 3.15.1. That release made Auth:Schemes validate by Type rather than by name; this one applies the same treatment to the two sister sections (RateLimiterOptions:Policies, CacheOptions:Profiles) that share the same "name-keyed open dictionary" shape, plus a small consistency win for ValidationOptions:Rules. Library version unchanged — fixes live entirely in NpgsqlRestClient/ConfigDefaults.cs.

    Fix: RateLimiterOptions:Policies validates by Type, not by name

    A configuration like

    json
    json
    {
    +  "Config": { "ValidateConfigKeys": "Error" },
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "Policies": {
    +      "login_throttle": {
    +        "Type": "FixedWindow",
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60,
    +        "Partition": { "Sources": [{ "Type": "IpAddress" }] }
    +      }
    +    }
    +  }
    +}

    failed startup with Unknown configuration key: RateLimiterOptions:Policies:login_throttle. The rate limiter itself registered login_throttle and rejected over-limit requests correctly — only the validator was wrong.

    Root cause

    FindUnknownConfigKeys walked the user's policy name (login_throttle) against the defaults schema, which contains illustrative example names (fixed, sliding, bucket, concurrency, per_user). Any other name was flagged unknown. With ValidateConfigKeys: "Error", that killed startup.

    The 3.13.0 migration explicitly grouped RateLimiterOptions:Policies, CacheOptions:Profiles, and ValidationOptions:Rules as the same "object keyed by user-chosen name" shape, but only ValidationOptions:Rules was added to the validator's open-dictionary whitelist. The other two were missed.

    What changed

    FindUnknownConfigKeys now intercepts the descent at RateLimiterOptions:Policies:<name> and picks a per-Type schema:

    • FixedWindow: Type, Enabled, PermitLimit, WindowSeconds, QueueLimit, AutoReplenishment, Partition
    • SlidingWindow: Type, Enabled, PermitLimit, WindowSeconds, SegmentsPerWindow, QueueLimit, AutoReplenishment, Partition
    • TokenBucket: Type, Enabled, TokenLimit, TokensPerPeriod, ReplenishmentPeriodSeconds, QueueLimit, AutoReplenishment, Partition
    • Concurrency: Type, Enabled, PermitLimit, QueueLimit, OldestFirst, Partition

    The shared Partition sub-schema (Sources: [{ Type, Name, Value }], BypassAuthenticated) is appended to every type. When Type is missing/invalid the validator skips that policy silently, matching the runtime behavior in BuildRateLimiter.

    Behavior after the fix

    • Any custom policy name validates by its declared Type; example names continue to validate as before.
    • Typos inside a policy (e.g. PermitLimt) are caught — they were silently ignored when Policies was treated as an opaque dictionary.
    • Cross-type keys are caught: e.g. TokensPerPeriod placed on a FixedWindow policy is flagged, since it belongs to TokenBucket.

    Fix: CacheOptions:Profiles validates by shape

    Same root cause, same shape of fix. Custom profile names (session_cache, api_responses, etc.) failed validation when ValidateConfigKeys: "Error" was set, because the defaults contain example names (fast_memory, shared_redis, date_range_hybrid).

    All cache profiles share the same key set regardless of backend type (Memory / Redis / Hybrid) — only the backend selection varies — so a single flat schema covers every profile:

    code
    Enabled, Type, Expiration, Parameters, When

    Each When rule validates as { Parameter, Value, Then }. Typos inside a profile (e.g. Expirashun) are now caught.

    Improvement: ValidationOptions:Rules now validates rule bodies

    ValidationOptions:Rules was previously on the open-dictionary whitelist, so custom rule names (phone_number, etc.) passed validation — but typos inside a rule (e.g. Patrn instead of Pattern) also passed silently. All validation rules share the same flat key set regardless of Type (NotNull / NotEmpty / Required / Regex / MinLength / MaxLength):

    code
    Type, Pattern, MinLength, MaxLength, Message, StatusCode

    ValidationOptions:Rules has been removed from the whitelist and is now validated against this flat schema. Custom rule names still pass; typos inside any rule now surface.

    Tests

    NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs gained 15 new tests covering all three sections: custom name acceptance, example-name regression coverage, typo flagging, cross-type key detection (rate limiter), Partition sub-block validation, When-rule sub-block validation, and missing-Type skip behavior for rate-limiter policies.

    Files touched

    • NpgsqlRestClient/ConfigDefaults.cs — three new intercepts in FindUnknownConfigKeys, three new schema helpers, ValidationOptions:Rules removed from IsOpenDictionarySection.
    • NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs — 15 new tests.

    No changes to runtime config reading, no breaking changes, no library version bump (the bug was in NpgsqlRestClient only).

    `,31)]))}const u=s(n,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.15.2.md.B_9nMiuK.lean.js b/assets/guide_changelog_v3.15.2.md.B_9nMiuK.lean.js new file mode 100644 index 000000000..fd917eabb --- /dev/null +++ b/assets/guide_changelog_v3.15.2.md.B_9nMiuK.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.15.2 (2026-05-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.15.2.md","filePath":"guide/changelog/v3.15.2.md"}'),n={name:"guide/changelog/v3.15.2.md"};function o(l,e,d,p,r,h){return a(),i("div",null,e[0]||(e[0]=[t("",31)]))}const u=s(n,[["render",o]]);export{k as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.16.0.md.z16WdNnJ.js b/assets/guide_changelog_v3.16.0.md.z16WdNnJ.js new file mode 100644 index 000000000..c98fe4026 --- /dev/null +++ b/assets/guide_changelog_v3.16.0.md.z16WdNnJ.js @@ -0,0 +1 @@ +import{_ as s,c as t,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.16.0 (2026-05-20)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.0.md","filePath":"guide/changelog/v3.16.0.md"}'),i={name:"guide/changelog/v3.16.0.md"};function n(r,e,d,c,l,h){return a(),t("div",null,e[0]||(e[0]=[o('

    Changelog v3.16.0 (2026-05-20)

    Version 3.16.0 (2026-05-20)

    Full Changelog

    Minor release fixing a long-standing class of bugs in the JSON-to-parameter parsers for the timestamp, timestamptz, time, and timetz PostgreSQL types: the parsers were silently shifting incoming values by the host process's UTC offset. Bumped to minor (not patch) because the corrected behavior changes how naive ISO strings (no Z, no offset) are interpreted on non-UTC hosts — see Breaking change below. The shift was invisible on UTC hosts (the default for mcr.microsoft.com/dotnet/aspnet and almost every Linux container) and only surfaced once the same image ran somewhere with TZ set to anything else — a Windows dev box, a Kubernetes pod with TZ overridden, or a non-UTC CI runner — at which point stored values diverged from the JSON the caller sent by the host's offset.

    Fix: datetime parsers are now host-TZ-independent

    TryParseTimestamp, TryParseTimestampTz, TryParseTime, and TryParseTimeTz in NpgsqlRest/ParameterParsers.cs all relied on the parameter-less DateTime.TryParse(value) overload. That overload's default DateTimeStyles.None converts offset-bearing strings to the host's local TZ and tags the result Kind=Local. The two *Tz parsers then called DateTime.SpecifyKind(v, DateTimeKind.Utc) on the local-shifted value — but SpecifyKind only relabels the kind, it does not convert. The result was a host-local wall-clock value labelled UTC, written to Postgres with a silent shift.

    The timestamp and time parsers used the same buggy parse and sent the local-shifted value directly to Npgsql, which transmits the wall-clock verbatim for a without time zone column — the same silent shift, same size as the host's offset.

    All four parsers now use:

    csharp
    csharp
    DateTime.TryParse(\n    value,\n    CultureInfo.InvariantCulture,\n    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,\n    out var v)
    • AssumeUniversal treats naive ISO strings (no Z, no offset) as UTC — the canonical JSON-over-HTTP convention.
    • AdjustToUniversal converts any Z-bearing or offset-bearing value to UTC.

    The result is a DateTime with Kind=Utc carrying the true UTC instant regardless of the host's TZ. The *Tz parsers use it directly. The without time zone parsers strip the kind back to Unspecified so Npgsql sends the UTC clock-time as the naive wall-clock value, matching the column semantics.

    Why this was hidden so long

    Almost every production container runs TZ=UTC by default. On a UTC host, DateTime.TryParse(...)'s local-conversion is a no-op and the SpecifyKind(Utc) "lie" coincidentally matches reality. The bug only manifests once the same code is deployed where TZ is anything else. The first symptom is usually a downstream report along the lines of "we send 2026-05-20T06:00:00Z and Postgres stored 08:00" — which is exactly the host's UTC offset.

    The existing MultiParamsTests2 / MultiParamsQueryStringTests2 test pairs already hinted at this — both used Should().Match(t => t == "12:06:59..." || t == "11:06:59...") style assertions with a comment that read "integration server seems to have a different datetime alltogether". That was the bug, papered over. After this fix both tests assert single deterministic values.

    TryParseDate left alone

    DateOnly.TryParse rejects Z- and offset-bearing strings outright (verified across UTC, America/Los_Angeles, Europe/Zagreb, Pacific/Auckland) — it does not silently shift, so the date parser was not affected by the host-TZ bug class. It was however a separate papercut: callers sending full ISO timestamps (e.g. "2026-05-20T03:00:00Z") to a date column got a flat parse failure. TryParseDate now falls back to a DateTime parse and extracts the date portion when DateOnly.TryParse rejects the input, honoring the same JsonTimestampsAreUtc semantic as the other datetime parsers (UTC date when the flag is true, host-local date when false).

    Breaking change

    JSON timestamps are now interpreted as UTC:

    • Z-suffixed and offset-bearing ISO strings are converted to UTC.
    • Naive ISO strings (no offset, no Z) are assumed UTC rather than interpreted as host-local time.

    Callers who relied on the previous "JSON is host-local" behavior — usually by accident, because the host happened to be UTC — will see no change. Callers who sent Z strings expecting UTC were silently broken on non-UTC hosts and are now correct.

    Opt-out: NpgsqlRestOptions.JsonTimestampsAreUtc

    Users whose downstream code genuinely depends on the legacy "naive timestamps are host-local" behavior — and who cannot update those callers to send Z-suffixed values — can restore the pre-3.16.0 behavior by setting JsonTimestampsAreUtc to false:

    • Library: new NpgsqlRestOptions { JsonTimestampsAreUtc = false, ... }.
    • Client (appsettings.json): "NpgsqlRest": { "JsonTimestampsAreUtc": false } (default is true).

    When false, the four parsers fall back to the bare DateTime.TryParse(value) overload — Z/offset strings get host-local-converted and tagged Kind=Local, naive strings get parsed as Kind=Unspecified, and the *Tz parsers re-apply SpecifyKind(Utc) on top. That reproduces the exact pre-3.16.0 code path. Note that this is not recommended for new deployments: it puts you back in the bug class the rest of this release fixes. The flag exists purely as a compatibility escape hatch.

    Tests

    New file NpgsqlRestTests/HostTimeZoneIndependenceTests.cs covers all four parsers via echo functions and json_build_object round-trips:

    • timestamptz with Z suffix, with numeric offset, and naive (assumed UTC)
    • timestamp with Z suffix (stored as naive UTC clock-time)
    • timetz with Z suffix (round-trips as UTC)
    • time with Z suffix (UTC clock-time extracted)

    Each assertion is exact — no host-TZ-tolerant ORs. The fixture forces the database to UTC at creation (alter database … set timezone to 'UTC'), so the assertions stay deterministic across runners. To verify host-TZ independence at the parser layer, run the suite under a non-UTC TZ env var (TZ=America/Los_Angeles dotnet test, for example) — the tests must still pass.

    The two existing MultiParams* tests had their loose Should().Match(...) assertions for timestamptz and timetz replaced with single-value Should().Be(...) assertions, now that the parsers produce deterministic output.

    Files touched

    • NpgsqlRest/ParameterParsers.cs — four parsers switched to AssumeUniversal | AdjustToUniversal.
    • NpgsqlRestTests/HostTimeZoneIndependenceTests.cs — new, six tests covering the four type variants.
    • NpgsqlRestTests/ParamTests/MultiParamsTests2.cs — tightened timestamptz / timetz assertions.
    • NpgsqlRestTests/ParamTests/MultiParamsQueryStringTests2.cs — same.

    No config changes, no API surface changes.

    ',32)]))}const m=s(i,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/guide_changelog_v3.16.0.md.z16WdNnJ.lean.js b/assets/guide_changelog_v3.16.0.md.z16WdNnJ.lean.js new file mode 100644 index 000000000..3a216446a --- /dev/null +++ b/assets/guide_changelog_v3.16.0.md.z16WdNnJ.lean.js @@ -0,0 +1 @@ +import{_ as s,c as t,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.16.0 (2026-05-20)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.0.md","filePath":"guide/changelog/v3.16.0.md"}'),i={name:"guide/changelog/v3.16.0.md"};function n(r,e,d,c,l,h){return a(),t("div",null,e[0]||(e[0]=[o("",32)]))}const m=s(i,[["render",n]]);export{u as __pageData,m as default}; diff --git a/assets/guide_changelog_v3.16.1.md.twyBHeJH.js b/assets/guide_changelog_v3.16.1.md.twyBHeJH.js new file mode 100644 index 000000000..b73b435bf --- /dev/null +++ b/assets/guide_changelog_v3.16.1.md.twyBHeJH.js @@ -0,0 +1,6 @@ +import{_ as t,c as a,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.16.1 (2026-06-01)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.1.md","filePath":"guide/changelog/v3.16.1.md"}'),n={name:"guide/changelog/v3.16.1.md"};function o(r,e,c,l,h,d){return s(),a("div",null,e[0]||(e[0]=[i(`

    Changelog v3.16.1 (2026-06-01)

    Version 3.16.1 (2026-06-01)

    Full Changelog

    Patch release that makes cache stampede protection actually fire for cached routine responses. The cache-options documentation has advertised stampede protection as a HybridCache feature, but the integration used IRoutineCache as a synchronous probe (Get / AddOrUpdate) that could not carry the SQL execution as the cache factory — so the protection never engaged. A burst of identical concurrent requests against a cold cache executed the underlying query N times, each taking a connection. In the worst case this exhausted Postgres' connection pool (remaining connection slots are reserved for roles with the SUPERUSER attribute), which combined with connection-retry backoff could pin the pool long enough to affect every app sharing the database.

    What changed

    IRoutineCache gains GetOrCreateAsync (additive)

    A new method routes the cold-cache work through the cache so concurrent callers for the same key coalesce into a single execution:

    csharp
    csharp
    ValueTask<object?> GetOrCreateAsync(
    +    RoutineEndpoint endpoint,
    +    string key,
    +    Func<CancellationToken, ValueTask<object?>> factory,
    +    TimeSpan? overrideExpiration = null,
    +    CancellationToken cancellationToken = default);

    It ships as a default interface method (plain probe → factory → store, no coalescing), so any pre-existing custom IRoutineCache implementation compiles and behaves exactly as before — it simply gains no stampede protection until it overrides the method.

    Note: although this is a new public API surface (conventionally a minor bump), it is shipped as a patch because it fixes an advertised-but-broken feature and is fully backward compatible via the default implementation.

    Stampede protection per backend

    • Memory (RoutineCache) and Redis (RedisCache) — coalesce concurrent factory invocations through an in-flight ConcurrentDictionary<string, Lazy<Task>>. A burst collapses to one execution; the rest await the in-flight result.
    • HybridCache (HybridCacheWrapper) — delegates straight to HybridCache.GetOrCreateAsync, so Microsoft's built-in stampede protection now genuinely engages.

    Middleware paths

    • Scalar single-value and passthrough proxy responses (value-shaped) route through GetOrCreateAsync. The connection is opened inside the factory, so coalesced waiters never touch the database. The passthrough proxy case additionally coalesces identical upstream HTTP calls.
    • Records / sets (the streaming path) use a per-key execution gate instead. This path streams rows to the client and disables caching mid-stream once a response exceeds MaxCacheableRows (default 1000), which does not fit the "compute one value, cache it, share it" factory model. The gate serializes concurrent requests for a key: the lead executes and (within the row limit) populates the cache, so the rest get a cache hit instead of re-executing. This caps concurrent DB executions per key at one in all cases — including over-limit responses, which serialize rather than run in parallel.

    Effect

    A burst of N identical requests against a cold cache now results in one database execution (within-limit) or a single serialized execution at a time (over-limit), instead of N concurrent executions. The worst-case fan-out from one event is bounded by the number of distinct cache keys (bounded by the schema), not by the number of clients.

    Test coverage (read this honestly)

    Automated coverage (NpgsqlRestTests/RoutineCacheTests/CacheStampedeTests.cs) runs against the in-memory backend with a live Postgres and asserts execution counts directly:

    • 50 concurrent cold scalar requests → exactly 1 execution; warm-cache burst → 0 further executions; 4 distinct keys → exactly 4 executions (one per key).
    • 50 concurrent cold set requests (within limit) → exactly 1 execution; over-limit set (1001 rows) → one execution per request, never cached, all responses correct.

    The HybridCache path relies on Microsoft's own tested coalescing (Microsoft.Extensions.Caching.Hybrid) and the Redis path's coalescing is verified by inspection — neither is exercised by the test harness, which boots the core library with the default memory cache. Claims about those two backends are not backed by an automated test in this repo.

    Known limitations

    • Cross-process coalescing is out of scope. Coalescing is in-process per NpgsqlRest instance; multiple instances each execute once. (HybridCache's Redis layer still shares the cached value across instances.)
    • Over-limit sets serialize, not coalesce. Responses above MaxCacheableRows (default 1000) are never cached, so the per-key gate makes concurrent requests for such an endpoint run one-at-a-time rather than sharing a result. This is deliberate: it caps both concurrent DB executions and peak memory (only one large set renders per key at a time) — but it does reduce throughput for a cached endpoint that returns more than MaxCacheableRows rows under load. Since such an endpoint is never actually cached, the right fix when this matters is to raise MaxCacheableRows so the result caches and coalesces, or to drop the cached annotation (restoring fully concurrent, uncached execution).
    • The records/sets gate is held during the response stream. Because the gate wraps streaming to the client (not just the DB read), a slow or stalled lead client can delay other clients requesting the same key until it finishes or its request cancels. Waiters honor their own cancellation token, so a waiter that gives up is never stuck. The scalar and proxy paths are unaffected — their coalescing slot covers only the upstream call, not the client write.
    • CommandCallbackAsync short-circuit under coalescing. If a user-supplied CommandCallbackAsync short-circuits the response on a cached scalar endpoint, coalesced waiters (not the lead) may observe an empty response. This affects only that specific hook on a cached endpoint.
    • Cancellation. The shared factory runs on the lead caller's token; if the lead cancels mid-flight, waiters retry (re-probe the cache, or one becomes the new lead). A waiter may rarely observe cancellation if the lead cancels at the exact moment of coalescing. This is a deliberate safety choice — the factory uses the lead's live connection, so fully detaching the shared work risks using a disposed connection.
    `,22)]))}const g=t(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.16.1.md.twyBHeJH.lean.js b/assets/guide_changelog_v3.16.1.md.twyBHeJH.lean.js new file mode 100644 index 000000000..680495d5a --- /dev/null +++ b/assets/guide_changelog_v3.16.1.md.twyBHeJH.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.16.1 (2026-06-01)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.1.md","filePath":"guide/changelog/v3.16.1.md"}'),n={name:"guide/changelog/v3.16.1.md"};function o(r,e,c,l,h,d){return s(),a("div",null,e[0]||(e[0]=[i("",22)]))}const g=t(n,[["render",o]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.16.2.md.DJyi_HNy.js b/assets/guide_changelog_v3.16.2.md.DJyi_HNy.js new file mode 100644 index 000000000..5e01f58f7 --- /dev/null +++ b/assets/guide_changelog_v3.16.2.md.DJyi_HNy.js @@ -0,0 +1,28 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.16.2 (2026-06-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.2.md","filePath":"guide/changelog/v3.16.2.md"}'),n={name:"guide/changelog/v3.16.2.md"};function l(h,s,p,k,r,o){return t(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.16.2 (2026-06-02)

    Version 3.16.2 (2026-06-02)

    Full Changelog

    Patch release that makes the rate-limiter rejection status code and message overridable per policy. Previously RateLimiterOptions:StatusCode and RateLimiterOptions:StatusMessage were the only values returned for a rejected request, applied globally regardless of which policy tripped. A config like a login_throttle policy with the message "Too many login attempts…" would return that same login-specific text for every rate-limited endpoint, even ones that have nothing to do with logins.

    What changed

    Per-policy StatusCode / StatusMessage overrides

    Each named policy under RateLimiterOptions:Policies may now set its own StatusCode and/or StatusMessage:

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "StatusCode": 429,                                  // global default
    +  "StatusMessage": "Too many requests. Please slow down.",
    +  "Policies": {
    +    "login_throttle": {
    +      "Type": "FixedWindow",
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +      "Partition": { "Sources": [ { "Type": "IpAddress" } ] }
    +    },
    +    "api": {
    +      "Type": "TokenBucket",
    +      "StatusCode": 503,
    +      "StatusMessage": "API capacity reached. Retry shortly."
    +    }
    +  }
    +}

    A request rejected by a given policy now returns that policy's status code and message; a policy that omits either field inherits the global value. The override that applies is resolved at rejection time from the endpoint's rate-limiter policy name, so it is correct even though ASP.NET Core exposes only a single global OnRejected/RejectionStatusCode.

    This is fully backward compatible: configs that set only the global StatusCode/StatusMessage behave exactly as before — those values simply become the defaults that policies may override.

    New ready-to-use login_throttle default policy

    The shipped appsettings.json now includes a disabled ("Enabled": false) login_throttle policy — 10 attempts per minute partitioned per client IP, with its own rejection message — so the common case is one flag away:

    jsonc
    jsonc
    "login_throttle": {
    +  "Type": "FixedWindow",
    +  "Enabled": false,
    +  "PermitLimit": 10,
    +  "WindowSeconds": 60,
    +  "QueueLimit": 0,
    +  "AutoReplenishment": true,
    +  "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +  "Partition": { "Sources": [ { "Type": "IpAddress" } ], "BypassAuthenticated": false }
    +}

    Apply it to a login endpoint with the rate_limiter login_throttle comment annotation (or set it as DefaultPolicy).

    Test coverage

    NpgsqlRestTests/AuthTests/RateLimiterPerPolicyTests.cs (fixture RateLimiterPerPolicyTestFixture) boots the limiter through the same wiring BuildRateLimiter emits and drives the real Builder.ApplyRateLimiterRejectionAsync helper over HTTP, asserting:

    • a policy with a message-only override returns its own message but the global status code,
    • a policy overriding both returns its own status code (503) and message,
    • a policy with no override inherits the global status code and message.

    Config-key validation for the new per-policy StatusCode/StatusMessage keys is covered in ConfigTests/ConfigValidationTests.cs.

    `,18)]))}const c=i(n,[["render",l]]);export{g as __pageData,c as default}; diff --git a/assets/guide_changelog_v3.16.2.md.DJyi_HNy.lean.js b/assets/guide_changelog_v3.16.2.md.DJyi_HNy.lean.js new file mode 100644 index 000000000..c806855e1 --- /dev/null +++ b/assets/guide_changelog_v3.16.2.md.DJyi_HNy.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as t,a5 as e}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.16.2 (2026-06-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.2.md","filePath":"guide/changelog/v3.16.2.md"}'),n={name:"guide/changelog/v3.16.2.md"};function l(h,s,p,k,r,o){return t(),a("div",null,s[0]||(s[0]=[e("",18)]))}const c=i(n,[["render",l]]);export{g as __pageData,c as default}; diff --git a/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.js b/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.js new file mode 100644 index 000000000..12cac2635 --- /dev/null +++ b/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.js @@ -0,0 +1,19 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.16.3 (2026-06-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.3.md","filePath":"guide/changelog/v3.16.3.md"}'),t={name:"guide/changelog/v3.16.3.md"};function l(h,s,p,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.16.3 (2026-06-03)

    Version 3.16.3 (2026-06-03)

    Full Changelog

    Patch release that lets static-content parsing template environment-variable values into served files, alongside the existing per-user claim templating. This is aimed at Single-Page Apps deployed to Kubernetes: the SPA bundle is built once, and app-wide values (BUILD_LABEL, feature-flag toggles, analytics IDs) are injected from pod env vars at boot without rebuilding the bundle per environment.

    What changed

    AvailableEnvVars under StaticFiles:ParseContentOptions

    A new optional config key lists environment variable names whose values are templated into static content using the same {NAME} tag syntax the claim path already uses:

    jsonc
    jsonc
    "StaticFiles": {
    +  "Enabled": true,
    +  "ParseContentOptions": {
    +    "Enabled": true,
    +    "FilePaths": [ "/index.html" ],
    +    "AvailableClaims": [ "user_id", "user_name" ],
    +    "AvailableEnvVars": {
    +      "BUILD_LABEL": "local",
    +      "DEMO_FLAG": "false",
    +      "TRACKING_ID": ""
    +    }
    +  }
    +}
    html
    html
    <script>
    +  window.__appConfig = {
    +    userId: {user_id},          // claim → 123 or null
    +    buildLabel: {BUILD_LABEL},  // env   → "demo" (or "local" default)
    +    demoMode: {DEMO_FLAG} === "true"
    +  };
    +</script>

    Behaviour details:

    • Two forms. AvailableEnvVars accepts an array of names (["BUILD_LABEL"]; a missing variable resolves to the empty string) or an object of name→default pairs ({"DEMO_FLAG":"false"}; the default is used when the variable is absent). AvailableClaims gains the same object form, so an absent claim can resolve to a configured default instead of NULL.
    • Resolved once at startup. Env values are read at parser construction. A K8s pod restart re-reads them; changing a value in a running process is not picked up.
    • JSON-escaped, like claims. Each value is substituted as a complete, escaped JSON literal, so templates use a bare {NAME} token (no surrounding quotes) and an accidental quote/backslash in a value cannot break the JS string.
    • Claims win on collision. If a name exists both as a user claim and an env var, the per-request claim value takes precedence.

    Security note

    Anything listed in AvailableEnvVars is templated into static content served to any client — treat it as a public allowlist. Never list a secret (database password, API key, signing token). Resolution is an explicit per-name lookup; the whole environment is never exposed. This is distinct from the server-side Config:ParseEnvironmentVariables mechanism, which substitutes {ENV} tokens into appsettings.json values that never leave the server.

    This is fully backward compatible: the new key is optional, and configs that omit it behave exactly as before.

    `,14)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.lean.js b/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.lean.js new file mode 100644 index 000000000..125169de8 --- /dev/null +++ b/assets/guide_changelog_v3.16.3.md.BaS-l5LJ.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.16.3 (2026-06-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.16.3.md","filePath":"guide/changelog/v3.16.3.md"}'),t={name:"guide/changelog/v3.16.3.md"};function l(h,s,p,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e("",14)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.17.0.md.DCqJkXNp.js b/assets/guide_changelog_v3.17.0.md.DCqJkXNp.js new file mode 100644 index 000000000..e82246900 --- /dev/null +++ b/assets/guide_changelog_v3.17.0.md.DCqJkXNp.js @@ -0,0 +1 @@ +import{_ as o,c as t,o as n,a5 as a}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.17.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.17.0.md","filePath":"guide/changelog/v3.17.0.md"}'),s={name:"guide/changelog/v3.17.0.md"};function i(r,e,d,c,l,p){return n(),t("div",null,e[0]||(e[0]=[a('

    Changelog v3.17.0

    Version 3.17.0 (2026-06-13)

    Full Changelog

    The headline of this release is MCP (Model Context Protocol) support — NpgsqlRest can project explicitly opted-in PostgreSQL routines as MCP tools that an AI agent can discover and call. Supporting that, the release adds neutral plugin extension points, makes one breaking change to the OpenAPI C# API, and ships two configuration/runtime fixes.

    Walkthrough

    For a hands-on tour — an "Acme Store" built from .sql files, a dual REST + MCP web page, and a real Claude agent driving the store — see the blog post Turn PostgreSQL into MCP Tools an AI Agent Can Call.

    New Features

    MCP (Model Context Protocol) server — new NpgsqlRest.Mcp plugin

    NpgsqlRest can now expose opted-in PostgreSQL routines as MCP tools, so an AI agent can discover them (tools/list) and execute them (tools/call) over the Model Context Protocol (spec 2025-11-25). The entire MCP layer lives in the new plugin — core stays protocol-agnostic, built only on the neutral extension points below.

    Opt-in, never automatic. A routine becomes a tool only when its PostgreSQL comment carries the mcp annotation:

    • mcp — expose as a tool; description derived from the comment prose.

    • mcp <text> — expose, with <text> as an inline (explicit) description.

    • mcp_description <text> (alias mcp_desc) — explicit, authoritative description.

    • mcp_name <name> — override the tool name (default: the routine name).

    • A bare mcp with no HTTP tag is MCP-only — the tool exists with no public HTTP route. The HTTP tag controls the REST route and mcp controls the tool, independently: HTTP GET + mcp = both interfaces; mcp alone = tool only (an endpoint that exists solely because a plugin requested it defaults to internal-only, so opting into MCP never silently widens the HTTP surface); internal remains the explicit way to hide a declared HTTP route. Works identically for SQL file endpoints (a .sql file with mcp and no HTTP tag becomes an MCP-only tool; files with neither annotation are skipped as non-endpoint scripts, as before). All other annotations (authorize, parameter handling, …) apply unchanged.

      Description precedence — the highest-priority source that is present wins, regardless of the order the lines appear in the comment; an explicit description suppresses the comment-prose fallback, so unrelated comment lines never leak in: mcp_description › inline mcp <text> › comment prose › routine name.

    The endpoint. A single Streamable-HTTP JSON-RPC endpoint (default /mcp, POST only). It implements the lifecycle (initialize → protocol version + tools capability + serverInfo; notifications/initialized202; ping), tools/list (with a JSON-Schema inputSchema per tool, derived from the routine's parameters), and tools/call. Transport rules per spec: the Origin header is validated (DNS-rebinding protection — a present, untrusted origin → 403); a present MCP-Protocol-Version other than 2025-11-25400; GET405 (no SSE).

    Calling a tool. tools/call runs the routine through the same pipeline as the HTTP endpoint, forwarding the authenticated principal so authorize checks apply. Arguments map to the routine as a query string (GET/DELETE), a JSON body (POST/PUT), or path-segment substitution. The result carries:

    • structuredContent (always a JSON object): a single value → { "value": … }; a record/composite (or a set collapsed with single) → the object itself; a set → { "items": [ … ] }. The text content block carries the same JSON, serialized (backward-compatibility).
    • outputSchema (declared on the tool) derived from the routine's return columns, nullable-aware so results always conform.
    • Two error channels: business failures → isError: true in the result; structural failures (unknown method/tool, malformed request) → JSON-RPC errors.

    Authorization — OAuth 2.1 Resource Server (bring-your-own Authorization Server; token validation reuses the host's bearer authentication — NpgsqlRest is not an Authorization Server). Configured under McpOptions:Authorization:

    • Protected Resource Metadata (RFC 9728) served at /.well-known/oauth-protected-resource{UrlPath} when an Authorization Server is configured.
    • RequireAuthorization gates the endpoint → 401 with WWW-Authenticate: Bearer resource_metadata="…" (RFC 9728 §5.1) so the client can discover the AS; the PRM document itself stays anonymous.
    • Audience binding (RFC 8707): with a canonical Audience configured, a token must carry it (aud claim) or it is rejected with 401.
    • Per-tool authorization: the routine's authorize/role check runs on tools/call401 if called anonymously, 403 insufficient_scope if the role is missing (RFC 6750 §3.1; challenges include scope and resource_metadata). No authorization logic is duplicated in the plugin — it reuses core's check.

    ConfigurationNpgsqlRest:McpOptions, disabled by default, surfaced in --config, --config-schema, and the JSON schema: Enabled, UrlPath (/mcp), ServerName (null → database name → "NpgsqlRest"), ServerVersion ("1.0.0"), Instructions, ToolDescriptionSuffix, RateLimiterPolicy, AllowedOrigins, and the Authorization object (RequireAuthorization, AuthorizationServers, ScopesSupported, Audience, ProtectedResourceMetadataPath, FilterToolsByRole).

    Diagnostics & current limitations.

    • Enabling MCP does not enable authentication — it is configured separately (the host's Auth section). If RequireAuthorization is on but no authentication scheme is registered, a startup warning is logged.
    • A routine annotated mcp that also uses a feature with no MCP equivalent (login, logout, basic auth, upload, SSE) logs a build-time warning.
    • A routine's rate_limiter annotation does not carry to MCP (tools/call bypasses route middleware); pairing it with mcp logs a build-time warning. Use McpOptions:RateLimiterPolicy (a host-registered ASP.NET rate-limiter policy) to throttle the whole /mcp endpoint.
    • tools/list lists every opted-in tool by default (keeping them discoverable); set Authorization.FilterToolsByRole to hide tools the caller can't run. Authorization is enforced on tools/call regardless.
    • The JSON-RPC layer is hand-rolled over System.Text.Json.Nodes with relaxed escaping (conventional application/json output) — no reflection-based serialization, AOT-safe (verified via dotnet publish -p:PublishAot=true).

    Plugin extension points on RoutineEndpoint

    Neutral, plugin-facing hooks were added so a plugin can own its comment annotations without leaking plugin concepts into core (both MCP and the OpenAPI plugin are now built on these):

    • IEndpointCreateHandler.HandleCommentLine(...) (new default-interface method) — core offers each unrecognized comment line to handlers within its single parse pass; a handler claims it by returning a CommentLineResult (a log label + RequestsEndpoint). Tokens are pre-split by core. Non-breaking.
    • RoutineEndpoint.Items (lazy IDictionary<string, object?>) + TryGetItem — a per-endpoint property bag for plugin metadata (the HttpContext.Items pattern), namespaced by key.
    • RoutineEndpoint.UnhandledCommentLines (string[]?) — comment prose that neither core nor any handler claimed.
    • CommentsMode.OnlyAnnotated (new) — creates an endpoint when the comment has an HTTP tag or a plugin requests one. An endpoint created solely by a plugin request (no HTTP tag) defaults to internal-only — the plugin asked for a projection (an MCP tool), not a route — so a bare mcp is MCP-only (a debug log notes the defaulting). The client now defaults to OnlyAnnotated; existing OnlyWithHttpTag configs are unaffected — it is kept as an identical-behavior alias.
    • IEndpointCreateHandler.EndpointRequestingAnnotations (new default-interface property, default empty) — the annotation keywords for which the handler requests endpoints (Mcp: mcp, mcp_name, mcp_description, mcp_desc). Lets sources with a cheap textual pre-gate recognize endpoint candidates: the SQL file source passes a file whose comment carries an HTTP tag or one of these keywords, so a bare-mcp .sql file becomes an MCP-only tool while scripts with neither are still skipped without ever being described.

    {name} annotation substitution can resolve allowlisted environment variables

    The {name} placeholders in annotation values (response headers, custom parameters, HTTP custom type URL/headers/body) could only resolve request parameters. They can now also resolve allowlisted environment variables, so e.g. an outbound API key or a per-pod server name doesn't have to be routed through a request parameter:

    sql
    sql
    comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}\nAuthorization: Bearer {WEATHER_API_KEY}';
    • Opt-in allowlist NpgsqlRest:AvailableEnvVars (mirrors StaticFiles:ParseContentOptions:AvailableEnvVars): array of names, or an object of name → default. Only listed names are ever read from the environment — the allowlist is the security boundary. (C# API: NpgsqlRestOptions.SubstitutionEnvironmentVariables, a resolved name → value dictionary.)
    • Resolved once at startup, matched case-insensitively, injected as the raw value. A routine parameter of the same name takes precedence.
    • Security: a value substituted into a response header is sent to the client — reserve secrets for outbound HTTP-type calls / custom parameters, and use response headers only for non-secret values (e.g. server/environment name).

    TsClient: ExportTypes — emit request/response interfaces with the export keyword

    The TypeScript client generator (NpgsqlRest.TsClient) previously emitted its request/response (and composite) interfaces as plain interface declarations: module-private when inlined into the client file (CreateSeparateTypeFile: false), or ambient/global in the separate {name}Types.d.ts file (the default). Neither form could be imported by other modules. The new ExportTypes option (config NpgsqlRest:ClientCodeGen:ExportTypes, default false) emits them as export interface so they can be imported:

    • Inline (CreateSeparateTypeFile: false) — interfaces are emitted as export interface in the same file as the functions.
    • Separate file (CreateSeparateTypeFile: true) — the type file becomes an importable module {name}Types.ts (export interface …) instead of an ambient {name}Types.d.ts, and the generated client file gets an import type { … } from "./{name}Types"; referencing the named types.

    Has no effect when SkipTypes is true. Defaulting to false keeps existing output byte-for-byte unchanged.

    Breaking Changes

    ⚠️ Safer configuration defaults: CORS credentials, passkey requirements, connection testing

    Three configuration defaults changed as part of a security/consistency audit of the shipped appsettings.json against the in-code defaults. You are affected only if your custom configuration omits these keys — set them explicitly to keep the old behavior.

    • Cors:AllowCredentials now defaults to false (was true). Credentials (cookies, authorization headers) in cross-origin requests must now be enabled deliberately, and only together with an explicit AllowedOrigins list. This only matters when Cors:Enabled is true.
    • Auth:PasskeyAuth:UserVerificationRequirement and ResidentKeyRequirement code defaults are now "required" (were "preferred"). The shipped appsettings.json already said "required" — the in-code fallback and --config defaults disagreed; they now match the stronger, documented posture.
    • ConnectionSettings:TestConnectionStrings code default is now true (was false). Same class of fix: the shipped appsettings.json already said true; the in-code default now agrees, so connection strings are tested at startup even when the key is omitted.

    ⚠️ OpenAPI annotation handling moved out of core (C# API only)

    The public properties RoutineEndpoint.OpenApiHide and RoutineEndpoint.OpenApiTags were removed, along with the core openapi comment-annotation handler. The OpenAPI plugin now parses openapi hide / hidden / ignore / tag <…> itself (from UnhandledCommentLines).

    • No change for annotation users — the openapi … comment annotations behave exactly as before (and, as before, only take effect when the OpenAPI plugin is loaded).
    • Affected only if your code sets endpoint.OpenApiHide / endpoint.OpenApiTags directly (e.g. in an EndpointCreated callback) — use the openapi comment annotation instead.

    Fixes

    Internal-only endpoints are excluded from generated client artifacts and API docs

    Endpoints marked internal (no public HTTP route — proxy/HTTP-type-callable, or now a bare-mcp MCP-only routine) were still emitted into the generated TypeScript client (a fetch wrapper), the generated .http file (a request line), and the generated OpenAPI document (a path entry). All target a route that returns 404, so the generated artifacts advertised endpoints that don't exist. The TsClient, HttpFiles, and OpenApi plugins now skip InternalOnly endpoints. (Surfaced by the new MCP-only mode, where a bare @mcp routine has no HTTP route.)

    🔴 Security: SSE scope hints were not enforced — hint-scoped events were delivered to every subscriber

    Events published with a per-event scope override — RAISE INFO ... USING HINT = 'authorize' or USING HINT = 'authorize <role-or-user> ...' — were delivered to all connected SSE subscribers, including subscribers without the named role and unauthenticated subscribers. The hint was parsed correctly, but a control-flow bug (else if chaining) skipped the authorization checks whenever a hint was present. Endpoint-level scoping via the sse_scope annotation (no hint) was NOT affected.

    Impact: any deployment using the documented per-user/per-role USING HINT pattern (e.g. private user messages or role-targeted notifications over SSE) was broadcasting those events to every connected subscriber. Upgrade is strongly recommended for anyone using SSE with hint-based scoping.

    Fixed by decoupling scope enforcement from hint parsing so the Matching/Authorize checks always run on the effective scope. Covered by new tests proving delivery-by-ordering: a role-scoped event reaches only matching subscribers, a bare authorize event reaches only authenticated subscribers, and anonymous subscribers receive neither.

    Malformed JSON request body now returns 400 Bad Request (was 404 Not Found)

    When an endpoint expects a JSON body and the request body is present but not a parseable JSON object (truncated JSON, a bare array/string, …), the response is now 400 Bad Request. Previously the failed parse fell through to parameter matching and surfaced as a misleading 404 Not Found. Parse failures were and still are logged; valid requests and empty-body handling are unchanged.

    Passkey/WebAuthn diagnostics: CBOR decode failures are no longer silent

    • A malformed WebAuthn attestation object now logs a Warning naming the decode failure (exception + payload length — never the payload itself) instead of failing silently into a generic attestation_invalid error. This gives operators an audit trail for both debugging and attack detection.
    • Indefinite-length CBOR arrays (legal in the lax conformance mode the decoder uses) are now decoded correctly; previously they failed the whole attestation.
    • A startup warning is logged when Passkey authentication is enabled with an empty RelyingPartyOrigins list — in that state WebAuthn origin validation accepts any origin, which is not recommended for production.

    {name} parameter-value placeholders: case-insensitive matching + typo warning

    The {name} placeholders that inject a request's parameter values into annotation values (response headers incl. Content-Type, custom parameters such as upload paths, and HTTP custom type URL/headers/body) had two rough edges:

    • Case sensitivity was inconsistent. Substitution matched names case-sensitively, while the related resolved-parameter SQL expression resolver matched case-insensitively. Substitution is now case-insensitive too ({userId}, {USERID}, {userid} all resolve the same parameter), consistent with PostgreSQL identifier folding.
    • Typos were silent. An unknown placeholder is left as literal text at request time (unchanged), but a misspelling like {_fil} for {_file} shipped silently into a header/path. NpgsqlRest now logs a build-time warning naming the unknown placeholder. The check covers response headers and custom parameters and only flags identifier-shaped tokens, so {0} and JSON-like {"a":1} are never mistaken for placeholders.

    Bare @cached (no parameter list) used only the routine name as the cache key

    @cached without an explicit parameter list is documented to key on all routine parameters, but the implementation left the cache-key parameter set empty — so the key was just the routine identifier, and every call returned the first response cached for that routine regardless of inputs until the TTL expired (a search/filter endpoint would serve the first query's results to every subsequent query). Endpoints that listed parameters explicitly (@cached p1, p2) were unaffected. All cache backends (Memory, Redis, Hybrid) were affected. Fixed by treating "no list" as "every parameter" at annotation-parse time.

    HybridCache Cache key contains invalid content on nullable cached params

    When CacheOptions.Type was Hybrid and a cached routine had a nullable parameter, every call where that parameter was null logged Microsoft.Extensions.Caching.Hybrid: Cache key contains invalid content and silently bypassed the cache — the endpoint still ran against the DB and returned correct data, but lost the cache hit and stampede protection for that key. Root cause: NpgsqlRest's internal cache-key encoding used a null byte (\\x00) in its null marker, which HybridCache rejects.

    HybridCacheWrapper now hashes every key into a SHA-256 hex string before passing it to HybridCache, so keys are valid regardless of source content; the null marker source-side also no longer uses \\x00 (it is delimited by the existing \\x1F separator), which is friendlier to Redis keys and log collectors across all backends. The UseHashedCacheKeys / HashKeyThreshold options keep their original purpose (Redis-backend key length / memory) and are simply a no-op for the Hybrid backend now. No user action required — Hybrid in-memory entries are flushed on restart.

    JSON command parameters accept json, jsonb, or text

    JSON payloads passed to user-authored SQL commands were bound with a hardcoded json type, so a function declaring the receiving parameter as jsonb or text failed at runtime with PostgreSQL 42883 "function does not exist" — even though the documentation states all three are acceptable.

    The binding now uses an untyped (unknown) parameter, which PostgreSQL resolves server-side via the target type's input function. Affected commands: external-auth Auth.External.LoginCommand ($4 provider data, $5 analytics), CSV/Excel upload row commands (per-row metadata, Excel JSON data), and the Passkey/Fido2 commands. Fully backward compatiblejson-typed parameters are unchanged; jsonb and text now also work, and NULL / quoted / array values round-trip correctly.

    Optional {NAME} and required {!NAME} environment-variable placeholders

    With Config:ParseEnvironmentVariables enabled (the default), config values support two placeholder forms, for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not — so typed bool/int reads fall back to their default instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.

    This fixes a startup crash: previously a missing optional variable left an unresolved {NAME} token that a typed read (e.g. GetConfigBool) rejected. Genuinely invalid values (e.g. "maybe" for a bool) still throw.

    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)\n"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Tests

    • An MCP test suite covering the lifecycle, tools/list / tools/call, structuredContent and outputSchema across return shapes (scalar, record, set, array, custom composite), parameter mapping (query / body / path; typed, optional, null, and json arguments), authorization (PRM, 401/403, audience binding), transport rules, and protocol edge cases.
    • A binding-contract test locking the PostgreSQL/Npgsql resolution the JSON-parameter fix relies on (json-only for the old binding; json/jsonb/text for the new one, including NULL and round-trip integrity), plus end-to-end CSV upload tests for a row-command metadata parameter declared as json, jsonb, and text.
    • Config tests for optional {NAME} (resolves when set; left untouched / defaults when not — including Serilog-template preservation) and required {!NAME} (throws when unset) across GetConfigBool / GetConfigInt / GetConfigStr and the ResolveEnv resolver.
    • Malformed-JSON body tests: truncated JSON and non-object JSON → 400; valid body unchanged.
    • SSE hardening suite: hint-scope authorization (the security-fix regression test — role-scoped, authenticated-scoped, and unscoped delivery across three differently-authenticated subscribers), multi-subscriber exactly-once fan-out, per-stream publish ordering, and subscriber-disconnect resilience.
    • Cache concurrency races: TTL-expiry under concurrent bursts (exactly one execution per cache window) and concurrent invalidation + read storms (no errors, cache coherent after).
    • CRUD endpoint authorization parity: table-comment authorize/roles enforced across select/insert/update/delete variants (401 anonymous, 403 wrong role, full cycle with the right role).
    ',65)]))}const g=o(s,[["render",i]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.17.0.md.DCqJkXNp.lean.js b/assets/guide_changelog_v3.17.0.md.DCqJkXNp.lean.js new file mode 100644 index 000000000..e90277882 --- /dev/null +++ b/assets/guide_changelog_v3.17.0.md.DCqJkXNp.lean.js @@ -0,0 +1 @@ +import{_ as o,c as t,o as n,a5 as a}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.17.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.17.0.md","filePath":"guide/changelog/v3.17.0.md"}'),s={name:"guide/changelog/v3.17.0.md"};function i(r,e,d,c,l,p){return n(),t("div",null,e[0]||(e[0]=[a("",65)]))}const g=o(s,[["render",i]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.js b/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.js new file mode 100644 index 000000000..aea175810 --- /dev/null +++ b/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as o}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.18.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.0.md","filePath":"guide/changelog/v3.18.0.md"}'),n={name:"guide/changelog/v3.18.0.md"};function r(i,e,c,l,d,h){return s(),a("div",null,e[0]||(e[0]=[o('

    Changelog v3.18.0

    Version 3.18.0 (2026-06-23)

    Full Changelog

    The headline of this release is HTTP Custom Type response caching — outbound HTTP calls made by HTTP Custom Types can now be cached and reused, eliminating repeated calls to the same upstream within a configurable time window. The release also fixes a duplicate-outbound-call bug for HTTP types on database-function endpoints.

    New Features

    HTTP Custom Type response caching — @cache directive

    An HTTP Custom Type can now opt into response caching with a @cache directive in its type comment, alongside the existing @timeout and @retry_delay directives. Directives appear before the request line:

    sql
    sql
    comment on type books_api is '@cache 5m\nGET https://books.toscrape.com/';

    A cached type fires one outbound call for a given request shape; subsequent matching requests are served from the in-memory cache until the TTL elapses. For a type with no per-request placeholders (a constant URL/headers/body), that means a single shared upstream call per TTL window across the whole application — instead of one call per inbound request.

    Behavior and safety rules:

    • Opt-in, GET-only. Caching is enabled per type by @cache. A @cache directive on any non-GET method is ignored with a startup warning — caching a mutating call is almost always a mistake.
    • TTL. @cache <interval> accepts the same formats as @timeout (5m, 30s, 1h, 00:05:00, or a bare number of seconds). A bare @cache (no interval) caches with no expiration (until the process restarts) and warns.
    • Success-only. Only successful (2xx) responses are cached, so a transient upstream failure is never pinned for the whole TTL — the next request re-fetches.
    • Stampede protection. A burst of concurrent requests for the same cache key coalesces into a single outbound call; the rest await the in-flight result (same Lazy<Task> coalescing model as the routine cache).
    • Cache key = HTTP method + resolved URL + resolved content-type + resolved headers + resolved body. Placeholders are resolved first, so per-request values vary the key naturally.

    Configuration (HttpClientOptions):

    • CacheEnabled (default true) — global kill switch. When false, @cache directives are ignored and every request fires a fresh call.
    • MaxCacheEntries (default 10000) — bounds memory; once full, new responses are not cached (existing entries still serve and expire normally).
    • CachePruneIntervalSeconds (default 60) — how often expired entries are pruned.

    Fixes

    HTTP Custom Type request fired once per composite field on database-function endpoints

    An endpoint backed by a database function/procedure whose parameter is an HTTP Custom Type fired one outbound HTTP call per field of the type on every inbound request (a 4-field type → 4 identical calls; a 6-field type → 6), multiplying latency and load on the target. SQL-file endpoints were not affected.

    Cause. A composite function parameter is expanded into one parameter per field, each carrying the same TypeDescriptor.CustomType (the HTTP type name). The per-request list of HTTP types therefore held the same name N times, and the firing loop in HttpClientTypeHandler.InvokeAllAsync called InvokeAsync once per entry — while the fill loop immediately below resolves handlers by distinct type name. The design already assumes one call per distinct type; the firing loop just failed to match.

    Fix. A guard in the firing loop requests each distinct HTTP type once, reusing the dictionary the fill loop already keys on. The established contract is preserved: one call per distinct HTTP type, shared from one response — two parameters referencing the same type still share a single call, and two different types remain two separate calls.

    HTTP type directives after the headers were silently ignored

    The @timeout, @retry_delay, and @cache directives are now parsed both before the request line and after the headers. Previously only the leading position (before the request line) was recognized, so a directive placed after the headers — as the documentation and examples showed — was silently dropped (e.g. a @timeout that never applied). Both placements are now equivalent. Real HTTP headers are unaffected: a header whose name merely starts with a directive keyword (e.g. Cache-Control) is still treated as a header.

    Tests

    • Regression tests count actual outbound calls via WireMock response callbacks (the prior suite asserted content but never call counts): a 6-field type fires exactly one call (was 6), and two distinct types fire one call each.
    • Caching tests cover: cache hit reduces to one call, 6-field dedup + caching combined, error responses not cached, @cache ignored on POST, and TTL expiry. Parse-level tests cover the @cache directive forms and GET-only enforcement.
    ',22)]))}const g=t(n,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.lean.js b/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.lean.js new file mode 100644 index 000000000..0e38febf5 --- /dev/null +++ b/assets/guide_changelog_v3.18.0.md.DhzJiwQ7.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as s,a5 as o}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.18.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.0.md","filePath":"guide/changelog/v3.18.0.md"}'),n={name:"guide/changelog/v3.18.0.md"};function r(i,e,c,l,d,h){return s(),a("div",null,e[0]||(e[0]=[o("",22)]))}const g=t(n,[["render",r]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.18.1.md.BUjRzlAL.js b/assets/guide_changelog_v3.18.1.md.BUjRzlAL.js new file mode 100644 index 000000000..90029b620 --- /dev/null +++ b/assets/guide_changelog_v3.18.1.md.BUjRzlAL.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as r,a5 as o}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.18.1","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.1.md","filePath":"guide/changelog/v3.18.1.md"}'),s={name:"guide/changelog/v3.18.1.md"};function n(d,e,i,l,h,p){return r(),a("div",null,e[0]||(e[0]=[o('

    Changelog v3.18.1

    Version 3.18.1 (2026-06-23)

    Full Changelog

    Patch release that makes all automatic (server-filled) parameters forward to proxy endpoints consistently, in the endpoint's native parameter shape.

    What changed

    When an endpoint is a proxy, the parameters that NpgsqlRest fills server-side are now forwarded to the upstream uniformly, regardless of source:

    • user claims (claim-mapped parameters),
    • IP address parameter,
    • HTTP Custom Type fields (the auto-filled responseBody / responseStatusCode / … on a routine with an HTTP Custom Type parameter),
    • resolved-parameter expressions (values looked up server-side via SQL).

    All of them follow the same placement rule, which mirrors how the endpoint itself receives parameters — not the HTTP verb:

    • The parameter designated as the body parameter (@body_parameter_name) carries the raw request body.
    • Otherwise placement follows the endpoint's RequestParamType: QueryString → appended to the proxy query string; BodyJson → merged into the proxy JSON body (typed: numbers, booleans, embedded JSON, or strings), when the proxy method can carry a JSON body.

    This is additive: the verbatim incoming request is still forwarded; the automatic parameters are added on top, so the upstream receives the same parameter set the routine would have.

    Why

    Previously the behavior was inconsistent: user-claim and IP parameters were always appended to the query string, HTTP Custom Type fields and resolved parameters were not forwarded at all, and a passthrough proxy discarded the auto-filled values entirely (the outbound HTTP Custom Type call fired but its result went nowhere). Now every automatic parameter behaves the same way.

    Behavior change to note

    User-claim and IP parameters now follow RequestParamType like every other automatic parameter. For a QueryString endpoint (the default for GET) they remain in the query string, exactly as before. For a BodyJson endpoint they are now merged into the JSON body rather than forced onto the query string. Method does not decide placement — RequestParamType does (a POST endpoint can use param_type query and its parameters then go to the query string).

    Notes

    • Body merging applies only when the forwarded request carries a JSON content type; multipart and non-JSON bodies are forwarded verbatim.
    • Only the expanded per-field HTTP Custom Type parameters (DB-function shape) are forwarded; single-composite HTTP parameters (SQL-file shape) are not.

    Tests

    NpgsqlRestTests/ProxyTests/ProxyHttpTypeProbeTest.cs covers, via a WireMock proxy target that echoes the received URL / body: HTTP-type fields forwarded on the query (GET) and merged into the JSON body (POST, typed); placement following RequestParamType rather than the verb (a param_type query POST forwards to the query, not the body); and a resolved-parameter expression forwarded consistently. Existing user-claim / IP proxy tests continue to pass unchanged (GET → query string). Full suite green (2288).

    ',18)]))}const u=t(s,[["render",n]]);export{g as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.18.1.md.BUjRzlAL.lean.js b/assets/guide_changelog_v3.18.1.md.BUjRzlAL.lean.js new file mode 100644 index 000000000..7ad17f49e --- /dev/null +++ b/assets/guide_changelog_v3.18.1.md.BUjRzlAL.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as r,a5 as o}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.18.1","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.1.md","filePath":"guide/changelog/v3.18.1.md"}'),s={name:"guide/changelog/v3.18.1.md"};function n(d,e,i,l,h,p){return r(),a("div",null,e[0]||(e[0]=[o("",18)]))}const u=t(s,[["render",n]]);export{g as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.18.2.md.D9Kg93qi.js b/assets/guide_changelog_v3.18.2.md.D9Kg93qi.js new file mode 100644 index 000000000..a23a3bd17 --- /dev/null +++ b/assets/guide_changelog_v3.18.2.md.D9Kg93qi.js @@ -0,0 +1 @@ +import{_ as t,c as o,o as a,a5 as r}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Changelog v3.18.2","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.2.md","filePath":"guide/changelog/v3.18.2.md"}'),s={name:"guide/changelog/v3.18.2.md"};function n(d,e,i,c,l,p){return a(),o("div",null,e[0]||(e[0]=[r('

    Changelog v3.18.2

    Version 3.18.2 (2026-06-26)

    Full Changelog

    Patch release with fixes for proxy endpoints that forward auto-filled parameters (v3.18.1) and the generated TypeScript client, plus a new opt-in option to omit server-filled parameters from generated request shapes. All surfaced by combining a @proxy with an HTTP Custom Type parameter.

    What changed

    1. Large auto-filled values no longer break the proxy query string

    New option ProxyOptions.MaxForwardedQueryParamLength (default 2048). When a server-filled parameter is appended to the proxy upstream query string, a value longer than this limit is now skipped with a warning instead of being percent-encoded into the URL.

    Previously, an HTTP Custom Type whose body field held a large payload (e.g. a scraped HTML page) was percent-encoded into the upstream query string, producing an oversized request line that the upstream rejected (HTTP 414 / 431) or that reset the connection. To forward such a value, use a body-carrying proxy method (POST/PUT/PATCH) so it travels in the request body instead. Set the option to 0 to disable the guard.

    2. @body_parameter_name reliably matches HTTP Custom Type fields

    @body_parameter_name now matches case-insensitively and accepts any of the parameter's names. For an HTTP Custom Type field expanded out of a composite parameter, all of these now resolve to the same field:

    • the converted (API) name — e.g. responseBody
    • the expanded signature name — e.g. _response_body (the name shown in the generated signature / .http file)
    • the base composite name — e.g. _response (shared by all expanded fields; resolves to the first one)

    Previously the annotation value was force-lowercased and compared case-sensitively, so the camelCase converted name never matched, and the expanded signature name (_response_body) matched nothing at all — it is stored as neither the actual nor the converted name. This made it impossible to redirect a single expanded HTTP-type field (such as the response body) into the proxy request body.

    Body-parameter resolution is now a single shared rule on the core endpoint (RoutineEndpoint.IsBodyParameter) used by request handling and every code generator, so they no longer drift. This also fixes the HTTP file and OpenAPI generators, which previously left a @body_parameter_name field (e.g. responseBody) in the query string / query parameters instead of moving it to the request body.

    3. TypeScript client generation for @body_parameter_name

    The generated TypeScript client was broken for an endpoint with @body_parameter_name:

    • the body expression was emitted as request.responseBody? — a syntax error (the TS optional ? suffix leaked into the runtime property name);
    • the query-string exclusion key was ["responseBody?"], so the body parameter was not stripped from the query string;
    • a body was emitted even for a GET request, which fetch forbids.

    The generator now uses the parameter's bare name for the body expression and the exclusion key, only emits a fetch body for methods that can carry one (not GET), and — like the server — matches @body_parameter_name against the converted, actual, or expanded signature name of an HTTP Custom Type field (e.g. responseBody, _response, or _response_body).

    4. Opt-in: omit automatic (server-filled) parameters from generated request shapes

    New option OmitAutomaticParameters on all three generators — TsClientOptions, HttpFileOptions, and OpenApiOptions (default false, so generated output is unchanged unless you opt in).

    When enabled, a parameter is omitted from the generated request (TypeScript request interface, .http query/body, OpenAPI query parameters / request body) when it is automatic (filled server-side, so a client value would be ignored) and optional. Automatic covers: HTTP Custom Type fields, resolved-parameter expressions, upload-metadata parameters, and — on endpoints that use user parameters — IP-address and user-claim parameters. The shared rule lives on the core endpoint (RoutineEndpoint.OmitParameterFromGeneratedRequest), so the three generators stay consistent. When every parameter is omitted, the generated request collapses cleanly (no-argument TS function, bare .http URL, no OpenAPI parameters/requestBody).

    This is the proper fix for the misleading case where, e.g., an HTTP Custom Type's responseBody field appeared as a settable request parameter even though the server always overrides it.

    Why these go together

    The pattern "fetch with an HTTP Custom Type, then @proxy to an upstream" now works cleanly end to end — server and generated client: redirect the (large) body field into the upstream request body with @body_parameter_name, while the remaining small fields travel on the query string under the new length guard.

    Notes

    • ProxyOptions.MaxForwardedQueryParamLength is wired through the client config (appsettings.json, JSON schema, and the --config template).
    • No parameter ActualName semantics changed: expanded HTTP-type fields still share the composite base name so they reassemble into the single SQL argument; the per-field name is matched via an internal alias only.

    Tests

    NpgsqlRestTests/ProxyTests/ProxyHttpTypeProbeTest.cs adds three cases (WireMock proxy target echoing the received URL / body): body redirect by converted name (responseBody), body redirect by expanded signature name (_response_body), and an oversized HTTP-type body field skipped from the proxy query string. NpgsqlRestTests/TsClientTests/BodyParamGetTests.cs covers the generated client for @body_parameter_name endpoints: a GET case (no ?-suffixed name, parameter excluded from the query, no fetch body on GET) and a POST HTTP-Custom-Type case targeted by the expanded name _response_body (body emitted as request.responseBody, excluded from the query). The HTTP file and OpenAPI generators are covered for the same expanded-name body redirect (BodyParamToBodyTests). OmitAutomaticParameters is covered for each generator (TsClient: all-omitted no-arg function + mixed-params; HttpFiles and OpenAPI: query/body omission of HTTP Custom Type fields). Full suite green (2301).

    ',27)]))}const g=t(s,[["render",n]]);export{m as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.18.2.md.D9Kg93qi.lean.js b/assets/guide_changelog_v3.18.2.md.D9Kg93qi.lean.js new file mode 100644 index 000000000..cf4ff55a7 --- /dev/null +++ b/assets/guide_changelog_v3.18.2.md.D9Kg93qi.lean.js @@ -0,0 +1 @@ +import{_ as t,c as o,o as a,a5 as r}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Changelog v3.18.2","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.18.2.md","filePath":"guide/changelog/v3.18.2.md"}'),s={name:"guide/changelog/v3.18.2.md"};function n(d,e,i,c,l,p){return a(),o("div",null,e[0]||(e[0]=[r("",27)]))}const g=t(s,[["render",n]]);export{m as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.19.0.md.CnLwz7zb.js b/assets/guide_changelog_v3.19.0.md.CnLwz7zb.js new file mode 100644 index 000000000..e0a4328f0 --- /dev/null +++ b/assets/guide_changelog_v3.19.0.md.CnLwz7zb.js @@ -0,0 +1,143 @@ +import{_ as e,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.19.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.19.0.md","filePath":"guide/changelog/v3.19.0.md"}'),n={name:"guide/changelog/v3.19.0.md"};function l(o,s,r,p,h,d){return a(),i("div",null,s[0]||(s[0]=[t(`

    Changelog v3.19.0

    Version 3.19.0 (2026-07-03)

    Full Changelog

    This release introduces the SQL test runner (npgsqlrest --test) — write tests for your endpoints as plain .sql files, invoke endpoints in-process from inside a test, and assert on both the HTTP response and the database state, all within the test's own transaction. Also included: watch mode (--watch — re-run tests or restart the server on SQL and configuration changes), named parameters in SQL files (:name), a skip glob for the SQL file source, and the ability to mute an individual logger.


    1. SQL test runner (--test)

    code
    npgsqlrest ./config.json --test

    The runner discovers .sql test files, executes each on its own isolated connection, and reports per-assertion results to the console (and optionally JUnit XML for CI). A test file is ordinary SQL — arrange data, call an endpoint, assert on the result:

    sql
    sql
    -- tests/get_users_excludes_caller.test.sql
    +begin;
    +
    +insert into app.users (id, email, name) values (100, 'x@example.com', 'Fixture');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200,
    +       'authenticated caller gets 200'
    +from _response;
    +select body::jsonb @> '[{"email": "x@example.com"}]',
    +       'the fixture user is listed'
    +from _response;
    +
    +rollback;
    code
    NpgsqlRest test runner — 9 file(s)
    +PASS  tests/get_users_excludes_caller.test.sql  (2 assertions, 52ms)
    +...
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files

    How it works

    In --test mode the client builds the full endpoint middleware exactly as in normal operation (endpoints from database routines and/or SQL files, authentication, custom parameters — everything), but instead of starting the web server it runs the test files and exits with a result code. Endpoint calls made from a test are in-process: no network, no running server — the complete endpoint pipeline (routing, authorization, parameter binding, execution, serialization) runs against a synthetic HTTP context.

    The critical property is connection affinity: the in-process endpoint call runs on the test's own connection, inside the test's own transaction. A test can begin, insert fixture rows, call an endpoint that sees those uncommitted rows, assert on the response, and rollback — leaving no trace. Each test file gets its own non-pooled physical connection (fresh session: no temp-table, GUC, or prepared-statement carryover), and files run in parallel (MaxParallelism, default = processor count). If a file never rolls back, closing its physical connection aborts the open transaction — that is the safety net.

    Test-mode invariants, applied automatically:

    • WrapInTransaction is forced off (the test file owns transaction control — the runner never injects BEGIN/COMMIT/ROLLBACK).
    • Response caching is disabled (a test never sees another test's cached response).
    • Code generation (HTTP files, TypeScript client, OpenAPI) is skipped — a test run never rewrites generated artifacts.

    Test file anatomy

    A test file is a sequence of SQL statements and HTTP blocks, executed strictly in order. Files are executed statement by statement (like psql): each statement runs in autocommit unless the file opens its own transaction. Semicolon splitting understands line/block comments, string literals with '' escapes, and dollar-quoted bodies — a do $$ … $$; block stays whole.

    Assertions — a reported test is one of:

    • A boolean-returning SELECT. If the first column is boolean, the statement is an assertion: the first row's value must be true (false or null fails; zero rows passes vacuously; only the first row is examined). The optional second column is the assertion's name/message, shown in the report and used as the JUnit test-case name:
      sql
      sql
      select count(*) = 3, 'exactly three users are seeded' from app.users;
    • A do block. Passes unless it raises — assert inside a DO block raises SQLSTATE P0004, reported as a failure with the assert message. One DO block = one reported test (multiple asserts inside it are opaque to the runner):
      sql
      sql
      do $$ begin
      +    assert app.normalize_email(' X@Y.z ') = 'x@y.z', 'should trim and lowercase';
      +end $$;

    Any other statement is arrange/act — not counted as a test; it only surfaces if it errors. Any SQL error (other than an assert) is reported as an error with its SQLSTATE, message, statement text, and file:line.

    Failing behavior is fail-fast per file: after the first failed/errored assertion the rest of the file does not run (a failed DO-block assert aborts the transaction anyway). Assertions that passed before the failure are still credited.

    HTTP blocks — invoking endpoints

    An HTTP request is embedded in a block comment whose first non-comment line is a request line — a single-request subset of the standard .http file syntax:

    sql
    sql
    /*
    +POST /api/create-user
    +Content-Type: application/json
    +# @claim user_id=42
    +# @claim roles=admin
    +# @response created
    +
    +{"name": "Grace Hopper", "email": "grace@example.com"}
    +*/

    Syntax rules:

    • Request line: [HTTP] METHOD /path[?query] [HTTP/x] — the method is one of GET, POST, PUT, DELETE (the methods NpgsqlRest endpoints support); the path must start with / and must equal the endpoint's full path including UrlPathPrefix (default /api); the leading HTTP keyword and a trailing HTTP-version token are optional. A block comment whose first line is not a valid request line is an ordinary SQL comment — ignored.
    • Headers: Name: Value lines after the request line. Content-Type is picked up for the request body.
    • Directives (lines starting with # @ or // @, placed after the request line, before the body):
      • # @claim name=value — adds a claim to the acting principal. Repeatable, including the same claim type twice (e.g. two roles claims). Any # @claim makes the request authenticated; no # @claim means anonymous (an @authorize endpoint returns 401). Role checks and claim-to-parameter mappings (@user_parameters, ParameterNameClaimsMapping) work exactly as in production — tests exercise the real authorization path.
      • # @response name — capture this block's response into a temp table with the given name instead of the default.
    • Body: everything after the first blank line, verbatim. (Limitation: a literal */ inside the body ends the SQL comment early.)
    • Plain # or // lines are comments; unknown @ directives are ignored.

    An HTTP block is an act step, not an assertion — it captures the response and produces no test result of its own. The assertions are the SQL statements that follow it. One request per block; use multiple blocks for multiple calls.

    Endpoint kinds that cannot work meaningfully in-process are rejected with a clear error: SSE, upload, login/logout (inject the principal with # @claim instead), and outbound proxy/HTTP-type endpoints (tests must not call external services). A request whose path matches no endpoint still runs (a test may assert the 404 deliberately) but logs a warning — the most common cause is a path typo or a missing /api prefix.

    The response temp table

    Each HTTP block's response is captured into its own temp table on the test's connection, created fresh (no IF NOT EXISTS — a duplicate name, e.g. a repeated # @response name, fails the test loudly). Default columns:

    ColumnTypeContent
    statusintHTTP status code
    bodytextresponse body (cast to ::jsonb to assert on JSON)
    content_typetextresponse content type
    headersjsonbresponse headers
    is_successbooleantrue for 2xx

    Naming: a file with one HTTP block uses ResponseTempTable.Name (default _response); a file with two or more uses ResponseTempTable.MultiNamePattern (default _response_{n}) where {n} is the block's 1-based position — _response_1, _response_2, … A block with # @response name uses that name instead (it still counts in the numbering of the others). Column names are configurable; setting one to null/empty omits that column.

    sql
    sql
    select body::jsonb ->> 'email' = 'grace@example.com',
    +       'email is normalized to lowercase'
    +from _response;

    Debugging captured responses — temp tables vanish with the test's rollback and connection, so you cannot inspect them afterwards (and re-issuing the request from an .http file cannot reproduce a response that depended on the test's uncommitted fixtures). Set ResponseTempTable.DebugTable (e.g. "_responses_debug"; default null = off) and every captured response is also mirrored into a permanent table — written on a separate autocommit connection, immune to rollbacks, recreated at the start of every run so it always holds the last run. One table covers everything: each HTTP block adds one row, with test_file, block (that block's response-table name — _response, a _response_{n} ordinal, or the # @response name), method, path, status, body, content_type, headers, is_success, and captured_at — so after a run you can open a query editor and dig into any response with jsonb operators. The temp-table semantics are unchanged; enabling it prints a loud warning (debugging aid — do not enable in CI). In the fresh-test-database workflow combine it with Keep: true, or teardown drops the database and the mirror with it.

    Reusing scripts: \\i and \\ir includes

    Shared SQL — fixture inserts, utility scripts — can be spliced into any test with psql's include syntax on its own line:

    sql
    sql
    begin;
    +
    +\\ir fixtures/extra_users.sql    -- path relative to THIS file (psql \\ir semantics)
    +\\i  ./shared/reset_counters.sql -- path relative to the cwd, like every other configured path
    +
    +/* GET /api/get-users */
    +select jsonb_array_length(body::jsonb) = 5, 'seed + fixture users listed' from _response;
    +
    +rollback;

    An include behaves as if you pasted the file's content at that spot: SQL statements, assertions (counted as tests, attributed to the included file), HTTP blocks (they participate in _response_{n} numbering exactly as if pasted), and even header annotations-- @setup/-- @teardown/-- @connection in an included file's leading comments count when the include sits in the host file's header region. That last one enables a shared profile idiom: put an annotation set in one file and attach it with a single include line (see example 21's shared/isolated_database.sql — one \\ir line gives a test its own cloned database). Everything runs on the test's connection, inside the test's transaction — a fixture included this way rolls back with the test, so it is invisible to every other test and leaves no residue.

    Two footnotes where "pasted" is refined rather than literal:

    • An include must stand between complete statements — it cannot sit inside an unfinished statement or contribute a fragment of one (for reusable SQL fragments, use what PostgreSQL already provides: functions and views).
    • Error attribution is better than a paste: a failure inside an included file is reported with the included file's name and line (the same thing psql does), not a line number in an imaginary merged file.

    Includes nest (cycle-safe, depth-capped) and work in Setup/Teardown SqlFile steps too. A path may be single-quoted (\\ir 'my fixtures/data.sql') and a trailing ; is forgiven; a non-include backslash line is passed through to PostgreSQL untouched (no other psql meta-commands are supported).

    Setup and Teardown, and named steps

    Run-once steps around the whole test session. Setup runs before endpoint discovery (so it can create/migrate the very schema the endpoints are built from); Teardown always runs at the end — even when tests fail or Setup itself fails (best-effort; Keep: true skips it to let you inspect state). Steps execute in the exact order written; a step is one of:

    • { "Sql": "..." } — inline SQL,
    • { "SqlFile": "path" } — a SQL file (executed statement by statement, like test files; \\i/\\ir includes work),
    • { "Command": "...", "WorkingDirectory": "..." } — a shell command (e.g. docker compose up -d, an external migration tool). Non-zero exit fails Setup.

    Sql/SqlFile steps run on the test connection by default, or on any named ConnectionStrings entry via a per-step "ConnectionName" — which enables maintenance operations like create database without the runner ever issuing DDL on its own.

    Teardown is guaranteed beyond the happy path: from Setup onward the runner intercepts SIGINT (Ctrl+C) and SIGTERM (e.g. docker stop) and runs Teardown synchronously in the signal handler — before the process can be torn down by an impatient parent (bun run/npm run forward Ctrl+C and may kill their children immediately; waiting for the run loop to unwind would lose that race). A second Ctrl+C force-quits. A process-exit hook additionally covers hard exits — e.g. a broken endpoint SQL file under SqlFileSource.ErrorMode: Exit calls Environment.Exit(1), which previously leaked the just-created test database; the exit code is unchanged, but Teardown now runs first. All paths funnel into a run-once Teardown. (A parent that SIGKILLs instantly remains unsurvivable — that is what a leading drop database if exists … on a static name, or a periodic sweep, is for.)

    Steps can be defined once in the Steps registry (name → step, like ConnectionStrings or CacheOptions.Profiles) and referenced by name; Setup/Teardown arrays accept names and inline objects mixed. Referencing an unknown name is a configuration error (exit 3).

    Every step also has an Enabled flag (default true): a disabled step is simply ignored wherever it is referenced — skipped with a debug log line, never an error. The default configuration ships disabled example steps covering the typical scenarios (create/drop a {rnd}-named test database on an admin connection, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) — they show every step property in place, so instead of typing a step from scratch you copy one, adjust the names, and flip Enabled to true:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    }
    +  }
    +}
    jsonc
    jsonc
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "SqlFile": "./migrations/schema.sql" },
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    ["CreateDatabase", "ApplyMigrations"],
    +    "Teardown": ["DropDatabase"]
    +  }
    +}

    Per-file setup, teardown, and connection (header annotations)

    An individual test file can attach named steps — and pick its own connection — with leading -- comment annotations (the same annotation idiom endpoint .sql files use), placed before the first statement:

    sql
    sql
    -- @setup CreateIsolatedDb
    +-- @teardown DropIsolatedDb
    +-- @connection Isolated
    • -- @setup Name [Name …] — runs the named steps before this file (own connections, committed work — e.g. clone a database this file will use). An unknown step name fails the file with an error.
    • -- @teardown Name [Name …] — runs after this file, always (best-effort, even when the test fails or times out), after the file's connection is closed — so a drop database … with (force) teardown works. An unknown step name logs a warning.
    • -- @connection Name — runs this file, including its in-process endpoint calls, on a named ConnectionStrings entry instead of the test connection. An unknown name fails the file with an error.
    • -- @tag Name [Name …] — declares the file's tags, filtered with the Tag/ExcludeTag options (see the filtering section below).

    @setup and @teardown are repeatable, and one line may carry several names, separated by whitespace or commas (the NpgsqlRest annotation idiom — -- @setup A B, -- @setup A, B, and two -- @setup lines are all equivalent). Names accumulate and execute in exactly the order written — the same contract as the global Setup/Teardown arrays; teardown is not reversed, so write the step you want last, last. Setup is fail-fast (the first failing or unknown step stops the chain, the file body never runs, teardown still runs); each teardown step is best-effort (a failure logs a warning and the remaining steps still run).

    Annotations can also come from an include in the header region — includes behave as if pasted, so a shared annotation "profile" file attaches with one line: \\ir shared/isolated_database.sql (see the includes section above).

    Since every word after @setup/@teardown is read as a step name, don't describe these annotations in a file's header comments using their literal syntax (-- @setup CreateDb creates the db… would try to run steps named creates, the, db…). Other -- comment lines in the header are ignored as usual.

    Together these give per-test database isolation: a file's setup clones a migrated template (create database … template … — a near-instant file-level copy), @connection points the file at the clone, and teardown drops it. That is the escalation path for state that transaction rollback cannot isolate — most prominently sequences, which advance even when the transaction rolls back, so generated ids are only deterministic in a fresh clone. Per-file steps commit to shared state, so steps that mutate the shared test database should be idempotent or run under MaxParallelism: 1; in-transaction fixture reuse belongs to \\ir instead.

    A dedicated test database

    TestRunner.ConnectionName points the whole test session — endpoint type-checking (SQL-file Describe), endpoint execution, and the tests — at a named ConnectionStrings entry instead of the app's main connection. That database need not exist at startup: it is never opened before Setup, so the first Setup step can create it.

    jsonc
    jsonc
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Database=appdb;...",                     // the real app DB — untouched by tests
    +    "Admin":   "Host={PGHOST};Database=postgres;...",                  // maintenance (needs CREATEDB)
    +    "Test":    "Host={PGHOST};Database=app_test_{rnd6};..."            // the throwaway test DB
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test",
    +    "FilePattern": "./tests/**/*.test.sql",
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd6}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "SqlFile": "./migrations/schema.sql" },     // runs on "Test"
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd6} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    ["CreateDatabase", "ApplyMigrations"],
    +    "Teardown": ["DropDatabase"]
    +  }
    +}

    {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once per run and substituted everywhere {ENV} placeholders work — connection strings, Setup/Teardown SQL, and Commands. The same token yields the same value across the whole config, so the connection string, the create, and the drop all name the same database; concurrent suites on a shared server can't collide. When several distinct tokens of the same length are needed, the indexed instances {rndN_1}{rndN_9} are each independent — {rnd3}, {rnd3_1} and {rnd3_2} are three different 3-character tokens, each stable for the run. (Trade-off: a hard crash that skips Teardown orphans that run's database. A static name with a leading drop database if exists … with (force); in Setup is the self-healing alternative.)

    The same Setup/Teardown machinery covers the neighboring workflows with no additional features: clone a prepared template (create database … template app_template — near-instant; migrate the template once in Setup and clone it for the run and for -- @setup-annotated per-test databases), start a Docker Postgres (Command steps: docker run → wait for pg_isready → migrate; docker rm -f in Teardown), or run an external migrator (EF Core, Django, Flyway) as a Command.

    Reporting

    Results are per assertion (like pgTAP/xUnit — each boolean SELECT / DO block is one test), grouped per file:

    code
    PASS  tests/login_succeeds.test.sql  (3 assertions, 50ms)
    +FAIL  tests/get_users.test.sql  (49ms)
    +        ✗ the caller is excluded — 2 of 3 users listed  [tests/get_users.test.sql:17]
    +        select jsonb_array_length(body::jsonb) = 2, …
    +
    +18 passed, 1 failed, 0 error(s)  —  19 assertions in 9 files

    Failures show the assertion name, file:line, and the failing statement. DetailedReport: true additionally lists passed assertions (), full failing SQL, and captured raise notice output for passing tests (notices always show under failing tests). This shapes the console report only — it is distinct from raising the NpgsqlRestTest log level, which controls diagnostics (see Logging below). A file with no recognizable assertions is flagged rather than silently counted.

    The report's colors are matched to Serilog's Code console theme, so the test report and the log lines around it read as one output: the FAIL/ERROR labels render as the byte-identical chip the theme uses for its ERR/FTL level (red text on a dark-grey block), PASS uses the same chip grammar in the mirror green, the rest of each line stays in the terminal's normal text color, and all failure text uses the theme's error red — never the 16-color red that renders orange in some terminals. Colors are disabled automatically when output is redirected (piped/CI logs stay plain).

    JUnit XML (JUnitOutput: "path.xml"): one <testcase> per assertion (name = the assertion message, classname = the file), <failure>/<error> with message and file:line, captured notices in <system-out>, files without assertions marked <skipped> — works with any CI dashboard.

    Exit codes: 0 all passed · 1 at least one failure · 2 at least one error (SQL error, timeout, unsupported endpoint, an interrupted run) · 3 setup/configuration error · 4 no test files found (AllowEmpty: true turns this into 0).

    Logging

    The runner logs through its own channel — NpgsqlRestTest (configurable via TestRunner.LoggerName) — leveled independently under Log:MinimalLevels (defaults to Information when absent, i.e. quiet):

    • Verbose — every SQL statement and every HTTP invocation (GET /api/x → 200, captured into "_response"),
    • Debug — discovery, per-file parse results, per-file outcomes, Setup/Teardown steps, degree of parallelism,
    • Warning — a request that matches no endpoint, failed teardown steps,
    • raise notice/warning from the database — logged by their severity, tagged with the test file that emitted them.
    jsonc
    jsonc
    {
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off", "NpgsqlRestTest": "Verbose" } }
    +}

    Configuration reference (TestRunner section)

    jsonc
    jsonc
    {
    +  "TestRunner": {
    +    "FilePattern": "",                    // glob selecting test files (same engine as SqlFileSource); empty disables
    +    "Filter": "",                         // narrow the discovered set: substring, or glob when it contains wildcards
    +    "Tag": "",                            // run only files carrying at least one of these tags (-- @tag name ...)
    +    "ExcludeTag": "",                     // skip files carrying any of these tags (wins over Tag)
    +    "ConnectionName": "",                 // ConnectionStrings entry to test against; empty = the main connection
    +    "MaxParallelism": 0,                  // concurrent test files; 0 = processor count
    +    "FailFast": false,                    // stop scheduling new files after the first failure (in-flight finish)
    +    "PerTestTimeout": "30s",              // per-file timeout: "30s", "5m", "1h", plain seconds, "hh:mm:ss"; 0 disables
    +    "JUnitOutput": null,                  // optional path for a JUnit XML report
    +    "Keep": false,                        // skip Teardown (inspect state after a failed run)
    +    "DetailedReport": false,              // detailed console report: passed ✓ lines, full failing SQL, notices for passing tests
    +    "AllowEmpty": false,                  // exit 0 instead of 4 when no tests are found
    +    "Coverage": null,                     // coverage summary: null (default) = on for full runs, quiet when narrowed; true/false = always/never
    +    "CoverageThreshold": null,            // 0-100: always report + fail an otherwise-passing run (exit 2) below it
    +    "LoggerName": "NpgsqlRestTest",       // the runner's log channel (leveled via Log:MinimalLevels)
    +    "ResponseTempTable": {
    +      "Name": "_response",                // table name when a file has ONE HTTP block
    +      "MultiNamePattern": "_response_{n}",// name pattern for 2+ blocks; {n} = 1-based block position
    +      "DebugTable": null,                 // debugging aid: ALSO mirror every response into this PERMANENT table (survives rollback; last run; not for CI)
    +      "Columns": {                        // response → column mapping; null/empty omits the column
    +        "Status": "status", "Body": "body", "ContentType": "content_type",
    +        "Headers": "headers", "IsSuccess": "is_success"
    +      }
    +    },
    +    "Steps": { },                         // named, reusable steps (name → step) for Setup/Teardown and -- @setup/-- @teardown;
    +                                          // each has "Enabled" (false = ignored wherever referenced); ships disabled examples
    +    "Setup": [],                          // run-once, BEFORE endpoint discovery, in written order (step names or inline objects)
    +    "Teardown": []                        // run-once, ALWAYS, in written order (Keep skips; same entries as Setup)
    +  }
    +}

    A practical convention is to keep the TestRunner block (and quiet log levels) in a separate test-config.json layered on only for test runs: npgsqlrest ./config.json ./test-config.json --test.

    Iterating on one test: Filter narrows the run to matching files, and like every option it can be set from the command line:

    code
    npgsqlrest ./config.json ./test-config.json --test --testrunner:filter=login

    A value without wildcards is a case-insensitive substring match against each file's cwd-relative path (login runs every *login* file); a value with wildcards uses the same glob engine as FilePattern (**/get_users_shows*). Setup and Teardown still run — the filtered subset executes in the complete environment — and a filter that matches nothing exits with code 4 (AllowEmpty applies).

    Tags group tests orthogonally to the directory layout. A file declares them with a header annotation, and runs are narrowed with Tag (include — the file must carry at least one) and ExcludeTag (skip — wins over include); both accept comma- or whitespace-separated lists, case-insensitive, and compose with Filter:

    sql
    sql
    -- @tag smoke, regression
    code
    npgsqlrest ... --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Since includes behave as if pasted, tags travel through a shared profile too: a profile file carrying -- @tag isolation, slow next to its -- @setup/-- @connection annotations tags every test that attaches it — e.g. all clone-isolated tests are automatically slow, so the everyday dev loop is just --testrunner:excludetag=slow, with zero per-file bookkeeping.

    Endpoint coverage is something only an integrated runner can offer: the runner knows the entire API surface it built and records every endpoint the tests actually invoked, so after the run it reports the API-level analogue of code coverage — including the exact endpoints no test touches. It is on by default for full runs (it costs one line); a run narrowed by Filter/Tag stays quiet — a deliberately partial run would just nag — unless Coverage: true forces it, and Coverage: false silences it entirely:

    code
    19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 1/2 (50%)
    +        untested: GET /api/get-users

    Endpoint kinds the runner rejects (SSE, upload, login/logout, outbound proxy) are excluded from the ratio and counted separately, so the number is honest. CoverageThreshold (0–100) turns it into a CI gate — it always reports, regardless of the Coverage setting or run narrowing: an otherwise-passing run below the threshold exits 2 — set it to 100 and forgetting to write a test for a new endpoint fails the build, naming the endpoint. "Covered" means invoked at least once by a test — execution, not assertion depth (the same semantics as code coverage).

    Watch mode (--watch, or Watch:Enabled in configuration) keeps the process alive and re-runs on change — and because the endpoint middleware is built once at startup, re-runs are near-instant:

    code
    npgsqlrest ./config.json ./test-config.json --test --watch

    Setup runs once, then everything runs once, then the test tree — and, when the SQL file source is enabled, the endpoint source tree — is watched recursively for *.sql changes (debounced). Changes are classified per file:

    • a changed test file re-runs alone (the Filter still applies);
    • a changed endpoint file (matching SqlFileSource.FilePattern) triggers an in-process endpoint rebuild — the sources are re-read and re-described against the test database, and the endpoint registry is swapped atomically — followed by a full rerun. After each rebuild the runner prints the endpoint delta (+ POST /api/new, - GET /api/x (endpoint dropped — check its SQL file for errors)), so breaking an endpoint file mid-session is visible immediately: the endpoint drops out, its tests fail with 404 warnings, and fixing the file brings it right back — no restart. (To make this safe, watch mode forces SqlFileSource.ErrorMode from Exit to Skip — a broken file must not kill the watch session; non-watch --test keeps Exit for CI. A rebuild that fails entirely keeps the previous endpoints live.)
    • any other changed .sql under the test tree — an included fixture or profile, whose dependents are unknown — re-runs everything.

    Teardown runs once, on exit — synchronously inside the SIGINT/SIGTERM handler (see the Setup and Teardown section), so the test database is dropped even when the watch process is stopped through a wrapper like bun run; a second Ctrl+C force-quits. Interactive/dev-only: a graceful stop exits 0 regardless of test outcomes — watch is not for CI gating. Database-routine sources have no files to watch — restart to pick up catalog changes.

    Project layout

    Two equally supported conventions — the difference is just the globs:

    • Co-located: sql/get_users.sql + sql/get_users.test.sql. Pair the endpoint glob with the new SqlFileSource.SkipPattern (below) so test files are never exposed as endpoints.
    • Separate tree: endpoints in sql/, tests in tests/ (named by scenario). The globs never overlap, so no SkipPattern is needed.

    2. SqlFileSource.SkipPattern — exclude files from endpoint discovery

    New option SkipPattern on the SQL file source (config key NpgsqlRest:SqlFileSource:SkipPattern, default "*.test.sql"). Files whose full path matches this glob are excluded from endpoint discovery: a .sql file becomes an endpoint only when it matches FilePattern and does not match SkipPattern.

    This is what makes the co-located test layout safe: without it, a test file's /* GET /x */ block would be read as an HTTP annotation and exposed as an endpoint. The pattern uses the same glob engine and semantics as FilePattern (*.ext matches by suffix). Set it to an empty string ("") to disable the exclusion.

    Behavior change: the default is "*.test.sql", so files matching that suffix are no longer exposed as endpoints out of the box. If you previously relied on serving *.test.sql files, set SkipPattern to "" to restore the old behavior.

    3. Named parameters in SQL files: :name

    SQL file endpoints can now use named placeholders instead of the positional $1, $2, …:

    sql
    sql
    /*
    +HTTP POST
    +@allow_anonymous
    +@single
    +*/
    +select u.id, u.email, u.full_name as name, r.name as role
    +from users u
    +join roles r on r.id = u.role_id
    +where u.email = :email
    +  and u.password_hash = crypt(:password, u.password_hash);

    The placeholder is the parameter name: :email becomes the API parameter email (through the same NameConverter routine parameters use, so :user_iduserId with the default camelCase converter). The @param $1 email text-style annotations that existed only to name positional parameters are simply unnecessary — the file above needs none. Under the hood the SQL is rewritten to native $N before it is described and executed; PostgreSQL never sees the :name form, so type inference, Describe, and runtime behavior are identical to positional files.

    What you get:

    • Repetition collapses: the same name used multiple times — including across statements in a multi-command file — is one parameter (where :user_id = author_id or :user_id = editor_id takes a single userId value).
    • Claim mappings hook up by placeholder name: select :_user_id under @authorize + @user_parameters binds the mapped claim with zero @param annotations.
    • Annotations match by name where you still need them: @param email default null (defaults), @param :email citext (a Describe type hint), and the new retype-without-rename form @param email type is citext — renaming a parameter whose name came from its own placeholder would be nonsense, so type is changes only the type. All positional @param $N … forms keep working unchanged.
    • The tokenizer knows SQL: strings ('…', "…", dollar-quoted bodies) and comments are untouched; ::int casts, := named-argument calls, and numeric slice bounds (a[1:3]) never match. A placeholder requires an identifier character immediately after the colon — the one caveat is an array slice with a variable bound, which must be written with a space (a[1 : n]).

    One style per file: mixing $N and :name in the same file makes the ordinal assignment ambiguous and is rejected (logged; the file is skipped under ErrorMode: Skip, or exits under Exit).

    Why not ? (JDBC style)? Considered and rejected: ?, ?|, ?&, and @? are PostgreSQL's own jsonb/geometric operators — where data ? 'admin' is legal, common SQL that no rewriter can reliably tell apart from a parameter. This is the same reason the PostgreSQL JDBC driver requires ?? escapes. Anonymous-positional already exists as $N.

    4. Watch mode: --watch

    The --watch flag — shorthand for the Watch:Enabled configuration setting — runs in one of two modes, depending on whether --test is present:

    CommandModeWatchesOn change
    npgsqlrest ... --test --watchTest watchtest files, included fixtures/profiles, the endpoint SQL files (when the SQL file source is enabled), and the database catalogchanged test re-runs alone; endpoint or database change rebuilds endpoints in-process and re-runs everything
    npgsqlrest ... --watchServer watchthe SQL file source tree, the configuration files, and the database catalogthe server restarts (~1s)

    Test watch is described in the test runner section above. Server watch needs something to watch — an enabled SQL file source, database polling (on by default, below), or both; with neither, --watch without --test exits with an error.

    Watching the routine source — database polling

    Routine-source endpoints (functions and procedures) have no files to watch — so watch mode polls the database instead, and it does it with perfect fidelity: the poll runs the same routine discovery query the endpoint source uses, with the same configured filters (schema/name/language includes and excludes), hashed server-side into a single value on a dedicated non-pooled connection (default every 2s). If the hash changes, the discovered endpoints changed — by definition. That covers create/create or replace/drop/alter of functions and procedures (including GRANT/REVOKE), COMMENT ON — i.e. annotation changes, and changes to the composite types and tables used as parameter or return types (alter table users add column reshapes a returns setof users endpoint even though no function changed). Just as importantly, anything the discovery query does not read — an unrelated table, temp objects, data changes — can never cause a spurious restart. Any detected change triggers the same path a file change does: server watch restarts the server, test watch rebuilds endpoints in-process and re-runs the tests (— change detected (database) —); the test runner re-baselines after every rerun so self-inflicted changes never re-trigger.

    This makes a routines-only project fully watchable: run npgsqlrest ./config.json --watch, then create or replace a function in psql — the endpoint is live about two seconds later, annotations included.

    The whole feature lives in one top-level configuration section (the --watch flag is the shorthand for Watch:Enabled):

    json
    json
    {
    +  "Watch": {
    +    "Enabled": false,
    +    "DatabasePollingInterval": "2s"
    +  }
    +}

    DatabasePollingInterval accepts "2s", "500ms", "1m", plain seconds, or "hh:mm:ss"; 0 disables polling. Both settings apply to both watch flavors.

    Server watch

    sh
    sh
    npgsqlrest ./config.json --watch

    Run the server under a watcher: edit a SQL file and the running API restarts with the change applied (~1s) — add an endpoint and it's immediately callable, break one and the error is on screen while the rest of the API keeps serving, and any configured code generation (TypeScript client, HTTP files, OpenAPI) regenerates on every restart, so the frontend's types follow your SQL as you type.

    How it works. The process becomes a small supervisor that spawns itself as a child server (marked by an environment variable) and watches the SqlFileSource tree plus the configuration files themselves. On a debounced change it stops the child gracefully and starts a fresh one — the child runs the completely normal server pipeline, so dev is byte-for-byte production behavior (the same model as dotnet watch). One relaxation: in the watch child, SqlFileSource.ErrorMode is forced from Exit to Skip, so a broken file logs its error and drops only its own endpoint instead of taking the server down.

    Behavior:

    EventResult
    .sql change under the source treerestart (files matching SkipPattern — test files — are ignored)
    configuration file changerestart with the new configuration
    database routine change (detected by polling, above)restart — — database change detected — restarting —
    broken SQL filerestart; the error is logged, that endpoint drops, everything else serves
    child crashes/exits on its ownsupervisor prints server exited (code N) — waiting for file changes and revives on the next save (no crash-looping)
    Ctrl+C / SIGTERM (docker stop)child stopped gracefully, both processes exit, port freed
    supervisor killed hard (SIGKILL)the child detects the vanished parent and exits by itself — no orphan holding the port

    Graceful child stop uses SIGTERM on Linux/macOS; on Windows the child is hard-killed (nothing needs teardown in a dev server). For environments where file events don't cross the filesystem boundary — Docker Desktop bind mounts, network shares — set the ecosystem-standard DOTNET_USE_POLLING_FILE_WATCHER=1 to switch to a 1-second polling scan (applies to both watch modes).

    Works in every distribution: the AOT executables (the supervisor respawns Environment.ProcessPath), framework-dependent dotnet NpgsqlRestClient.dll (the dotnet host is re-invoked with the dll), and both Docker image flavors (the supervisor handles PID-1 signal and child-reaping duties).

    5. Mute an individual logger with "Off" in Log:MinimalLevels

    Each entry under Log:MinimalLevels now accepts "Off" (aliases "None" and "Silent", case-insensitive) to fully silence that logger. Previously the only accepted values were the Serilog levels Verbose…Fatal, and there was no way to turn a logger off completely.

    • "Off" / "None" / "Silent" → the logger emits nothing (implemented as a minimum level above Fatal, since Serilog's LogEventLevel has no native "off").
    • null, an omitted key, or an unrecognized value → unchanged: the logger keeps its built-in default level.

    Each named logger is controlled independently — e.g. mute the application loggers entirely while watching the test runner:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    Notes

    • The test runner's core hooks are additive and inert outside --test: an ambient-connection accessor on the endpoint pipeline (null by default) and response headers on the internal invocation result. Normal server operation is unchanged.
    • Test files run statement by statement (the client operates Npgsql with SQL rewriting disabled — one statement per command), which is also psql's default execution model; explicit begin/commit/rollback in a file work as ordinary statements. Setup/Teardown Sql/SqlFile steps execute the same way — which is why create database works as a plain step.
    • All new options are wired through the client configuration: appsettings.json, the JSON-schema descriptions, and the --config template.
    • Working examples: examples/19_testing_basic (co-located layout, multi-step scenario files), examples/20_testing_newdb (separate tests/ tree, one test per file, fresh test database per run via named steps, deferrable-constraint fixtures, authorization + user parameters, a tag taxonomy — smoke/auth/fixtures/login — on every file), and examples/21_testing_isolation (template-clone workflow; two parallel per-test isolated databases — named apart with the indexed {rnd5_1}/{rnd5_2} tokens — proving deterministic sequence ids; a shared annotation profile attached via \\ir that also carries the isolation, slow tags; a Command step mixed with named-step references in Setup).

    Tests

    Full test suite green (2394), including 63 unit tests for the test-file, HTTP-block, header-annotation, and include parsers plus the filter and tag matchers (NpgsqlRestTests/TestRunnerTests/ParserTests/), and 29 for named SQL-file parameters — 22 rewriter unit tests (casts, :=, slices, strings, dollar-quotes, jsonb-path strings, case-insensitive repetition, cross-statement sharing, mixing detection, named type hints) plus 7 end-to-end endpoint tests (auto-naming through the camelCase converter, a repeated placeholder bound from one value, required-parameter matching, name-matched defaults, type is retype, claim mapping by placeholder name, mixed-style rejection, and a multi-command file sharing :id across statements as one API parameter), plus a database-fingerprint test proving the watch poller's hash tracks the routine discovery result exactly (function create/replace/comment/drop and used-type changes fire; temp objects and unrelated tables never do). Watch mode verified live end-to-end in both flavors: file edit/break/fix cycles, config-change restarts, crash recovery, orphan prevention under SIGKILL, graceful SIGTERM teardown, polling-watcher mode, and database-driven changes (a function created in psql serving ~2s later; alter table reshaping a returns setof endpoint; unrelated tables causing zero restarts). All three documentation examples verified end-to-end against live PostgreSQL — including example 21's template-clone workflow (template migrated once; the shared run database and two parallel per-test isolated databases cloned from it concurrently; deterministic sequence ids asserted independently in both clones; everything dropped on teardown), watch mode (single-file rerun on a test change, full rerun on a fixture change, teardown on SIGINT/SIGTERM), path and tag filtering (including tags carried through a profile include), and the coverage report with a failing threshold gate. The new configuration keys are covered by the configuration round-trip tests (the --config template output matches appsettings.json).

    `,135)]))}const g=e(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.19.0.md.CnLwz7zb.lean.js b/assets/guide_changelog_v3.19.0.md.CnLwz7zb.lean.js new file mode 100644 index 000000000..5972c11e2 --- /dev/null +++ b/assets/guide_changelog_v3.19.0.md.CnLwz7zb.lean.js @@ -0,0 +1 @@ +import{_ as e,c as i,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.19.0","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.19.0.md","filePath":"guide/changelog/v3.19.0.md"}'),n={name:"guide/changelog/v3.19.0.md"};function l(o,s,r,p,h,d){return a(),i("div",null,s[0]||(s[0]=[t("",135)]))}const g=e(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.0.md.DqMIq3GM.js b/assets/guide_changelog_v3.2.0.md.DqMIq3GM.js new file mode 100644 index 000000000..dce132843 --- /dev/null +++ b/assets/guide_changelog_v3.2.0.md.DqMIq3GM.js @@ -0,0 +1,96 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.0 (2025-12-22)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.0.md","filePath":"guide/changelog/v3.2.0.md"}'),t={name:"guide/changelog/v3.2.0.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.2.0 (2025-12-22)

    Version 3.2.0 (2025-12-22)

    Full Changelog

    Reverse Proxy Feature

    Added reverse proxy support for NpgsqlRest endpoints. When an endpoint is marked as a proxy, incoming HTTP requests are forwarded to an upstream service, and the response can either be returned directly to the client (passthrough mode) or processed by the PostgreSQL function (transform mode).

    Basic Usage:

    sql
    sql
    -- Passthrough mode: forward request, return upstream response directly
    +create function get_external_data()
    +returns void
    +language sql as 'select';
    +comment on function get_external_data() is 'HTTP GET
    +proxy';
    +
    +-- Transform mode: forward request, process response in PostgreSQL
    +create function get_and_transform(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_headers json default null,
    +    _proxy_content_type text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', _proxy_error_message);
    +    end if;
    +    return json_build_object(
    +        'status', _proxy_status_code,
    +        'data', _proxy_body::json
    +    );
    +end;
    +$$;
    +comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
    +proxy';

    Proxy Annotations:

    sql
    sql
    -- Basic proxy with default host from configuration
    +comment on function my_func() is 'proxy';
    +
    +-- Proxy with custom host
    +comment on function my_func() is 'proxy https://api.example.com';
    +comment on function my_func() is 'proxy_host https://api.example.com';
    +
    +-- Proxy with custom HTTP method
    +comment on function my_func() is 'proxy POST';
    +comment on function my_func() is 'proxy_method POST';
    +
    +-- Combined host and method
    +comment on function my_func() is 'proxy https://api.example.com POST';

    Response Parameters:

    When the PostgreSQL function has parameters matching these names, the proxy response data is passed to the function:

    Parameter NameTypeDescription
    _proxy_status_codeintHTTP status code from upstream (e.g., 200, 404)
    _proxy_bodytextResponse body content
    _proxy_headersjsonResponse headers as JSON object
    _proxy_content_typetextContent-Type header value
    _proxy_successbooleanTrue for 2xx status codes
    _proxy_error_messagetextError message if request failed

    User Claims and Context Forwarding:

    When user_params is enabled, user claim values are forwarded to the upstream proxy as query string parameters:

    sql
    sql
    create function proxy_with_claims(
    +    _user_id text default null,        -- Forwarded as ?userId=...
    +    _user_name text default null,      -- Forwarded as ?userName=...
    +    _ip_address text default null,     -- Forwarded as ?ipAddress=...
    +    _user_claims json default null,    -- Forwarded as ?userClaims=...
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('user', _user_id, 'data', _proxy_body);
    +end;
    +$$;
    +comment on function proxy_with_claims(text, text, text, json, int, text) is 'HTTP GET
    +authorize
    +user_params
    +proxy';

    When user_context is enabled, user context values are forwarded as HTTP headers to the upstream proxy:

    sql
    sql
    create function proxy_with_context(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('status', _proxy_status_code);
    +end;
    +$$;
    +comment on function proxy_with_context(int, text) is 'HTTP GET
    +authorize
    +user_context
    +proxy';
    +-- Headers forwarded: request.user_id, request.user_name, request.user_roles (configurable via ContextKeyClaimsMapping)

    Upload Forwarding:

    For upload endpoints with proxy, you can configure whether to process uploads locally or forward raw multipart data:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}
    • ForwardUploadContent: false (default): Uploads are processed locally; proxy receives parsed data
    • ForwardUploadContent: true: Raw multipart/form-data is streamed directly to upstream (memory-efficient)

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": false,
    +      "Host": null,
    +      "DefaultTimeout": "30 seconds",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      "ResponseBodyParameter": "_proxy_body",
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      "ResponseSuccessParameter": "_proxy_success",
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}

    Key Features:

    • Passthrough mode: No database connection opened when function has no proxy response parameters
    • Transform mode: Process upstream response in PostgreSQL before returning to client
    • User claims forwarding: Authenticated user claims passed as query parameters to upstream
    • User context headers: User context values passed as HTTP headers to upstream
    • Streaming uploads: Memory-efficient streaming for large file uploads when ForwardUploadContent is enabled
    • Timeout handling: Configurable per-request timeout with proper 504 Gateway Timeout responses
    • Header forwarding: Configurable request/response header forwarding with exclusion lists

    Docker Image with Bun Runtime

    Added new Docker image variant with pre-installed Bun runtime: vbilopav/npgsqlrest:latest-bun

    This image includes the Bun JavaScript runtime alongside NpgsqlRest, enabling proxy endpoints to execute Bun scripts within the same container. Useful for scenarios where you need lightweight proxy handlers without external service calls.

    Available tags:

    • vbilopav/npgsqlrest:3.2.1-bun - specific version with Bun
    • vbilopav/npgsqlrest:latest-bun - latest version with Bun

    Configuration Default Fixes

    Fixed multiple configuration default mismatches where code fallback values did not match the defaults defined in appsettings.json. When configuration keys were not present, the application would use incorrect fallback values instead of the documented defaults.

    Fixed defaults:

    SectionKeyWasNow
    DataProtectionGetAllElementsCommand"select data from get_all_data_protection_elements()""select get_data_protection_keys()"
    DataProtectionStoreElementCommand"call store_data_protection_element($1,$2)""call store_data_protection_keys($1,$2)"
    CorsAllowedOrigins["*"][]
    CommandRetryOptionsEnabledfalsetrue
    RateLimiterOptions.ConcurrencyPermitLimit10010
    Auth.BasicAuthUseDefaultPasswordHasherfalsetrue
    NpgsqlRest.HttpFileOptionsNamePattern"{0}{1}""{0}_{1}"
    NpgsqlRest.OpenApiOptionsFileOverwritefalsetrue
    NpgsqlRest.CrudSourceEnabledfalsetrue
    StaticFiles.ParseContentOptionsHeadersnull["Cache-Control: no-store, no-cache, must-revalidate", "Pragma: no-cache", "Expires: 0"]
    NpgsqlRestRequestHeadersModeIgnoreParameter
    RateLimiterOptions.TokenBucketReplenishmentPeriodSeconds (log)110
    RateLimiterOptions.ConcurrencyQueueLimit105
    RateLimiterOptionsMessage (field name)"Message""StatusMessage"
    CacheOptionsUseRedisBackend (field name)"UseRedisBackend""HybridCacheUseRedisBackend"

    Note: If you were relying on the previous (incorrect) fallback behavior, you may need to explicitly set these values in your configuration.

    `,35)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.0.md.DqMIq3GM.lean.js b/assets/guide_changelog_v3.2.0.md.DqMIq3GM.lean.js new file mode 100644 index 000000000..ba685ce02 --- /dev/null +++ b/assets/guide_changelog_v3.2.0.md.DqMIq3GM.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.0 (2025-12-22)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.0.md","filePath":"guide/changelog/v3.2.0.md"}'),t={name:"guide/changelog/v3.2.0.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",35)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.1.md.jsATl_CJ.js b/assets/guide_changelog_v3.2.1.md.jsATl_CJ.js new file mode 100644 index 000000000..c6f5370cb --- /dev/null +++ b/assets/guide_changelog_v3.2.1.md.jsATl_CJ.js @@ -0,0 +1,31 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.1 (2025-12-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.1.md","filePath":"guide/changelog/v3.2.1.md"}'),t={name:"guide/changelog/v3.2.1.md"};function p(l,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.2.1 (2025-12-23)

    Version 3.2.1 (2025-12-23)

    Full Changelog

    JWT (JSON Web Token) Authentication Support

    Added standard JWT Bearer authentication as a third authentication scheme alongside Cookie and Microsoft Bearer Token authentication. All three schemes can be used together.

    Configuration:

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "your-app",
    +    "JwtAudience": "your-api",
    +    "JwtExpireMinutes": 60,
    +    "JwtRefreshExpireDays": 7,
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true,
    +    "JwtValidateLifetime": true,
    +    "JwtValidateIssuerSigningKey": true,
    +    "JwtClockSkew": "5 minutes",
    +    "JwtRefreshPath": "/api/jwt/refresh"
    +  }
    +}

    Login Response:

    When JWT authentication is enabled and a login endpoint returns successfully, the response includes:

    json
    json
    {
    +  "accessToken": "eyJhbG...",
    +  "refreshToken": "eyJhbG...",
    +  "tokenType": "Bearer",
    +  "expiresIn": 3600,
    +  "refreshExpiresIn": 604800
    +}

    Token Refresh:

    POST to the configured refresh path (default: /api/jwt/refresh) with:

    json
    json
    { "refreshToken": "eyJhbG..." }

    Returns a new access token and refresh token pair.

    Key Differences from Microsoft Bearer Token:

    FeatureMicrosoft Bearer TokenJWT
    Token FormatProprietary, encryptedIndustry-standard (RFC 7519)
    InteroperabilityASP.NET Core onlyAny system supporting JWT
    Token InspectionOpaqueCan be decoded at jwt.io
    Use CaseSingle ASP.NET appCross-service, microservices

    New Configuration Options:

    • JwtAuth - Enable JWT authentication (default: false)
    • JwtAuthScheme - Custom scheme name (default: "Bearer")
    • JwtSecret - Signing key (minimum 32 characters for HS256)
    • JwtIssuer - Token issuer claim
    • JwtAudience - Token audience claim
    • JwtExpireMinutes - Access token expiration (default: 60)
    • JwtRefreshExpireDays - Refresh token expiration (default: 7)
    • JwtValidateIssuer - Validate issuer claim (default: false)
    • JwtValidateAudience - Validate audience claim (default: false)
    • JwtValidateLifetime - Validate token expiration (default: true)
    • JwtValidateIssuerSigningKey - Validate signing key (default: true)
    • JwtClockSkew - Clock tolerance for expiration (default: 5 minutes)
    • JwtRefreshPath - Refresh endpoint path (default: "/api/jwt/refresh")

    Custom Login Handler:

    Added CustomLoginHandler callback to NpgsqlRestAuthenticationOptions allowing custom token generation during login. This enables JWT tokens to be generated and returned instead of using the default SignIn behavior.

    Path Parameters Support for HttpFiles and OpenApi Plugins

    Added path parameters support to the HttpFiles and OpenApi plugins, matching the functionality added to the core library and TsClient in version 3.1.3.

    HttpFiles Plugin:

    Path parameters are now properly handled in generated HTTP files:

    • Path parameters are excluded from query strings (they're already in the URL path)
    • Path parameters are excluded from JSON request bodies

    Before (broken):

    http
    http
    GET {host}/api/products/{p_id}?pId=1

    After (fixed):

    http
    http
    GET {host}/api/products/{p_id}

    OpenApi Plugin:

    Path parameters are now properly documented in the OpenAPI specification:

    • Path parameters are added with "in": "path" and "required": true
    • Path parameters are excluded from query parameters
    • Path parameters are excluded from request body schemas

    Example generated OpenAPI for /api/products/{p_id}:

    json
    json
    {
    +  "parameters": [
    +    {
    +      "name": "pId",
    +      "in": "path",
    +      "required": true,
    +      "schema": { "type": "integer", "format": "int32" }
    +    }
    +  ]
    +}

    `,35)]))}const u=i(t,[["render",p]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.1.md.jsATl_CJ.lean.js b/assets/guide_changelog_v3.2.1.md.jsATl_CJ.lean.js new file mode 100644 index 000000000..1a8cdc0e1 --- /dev/null +++ b/assets/guide_changelog_v3.2.1.md.jsATl_CJ.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.1 (2025-12-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.1.md","filePath":"guide/changelog/v3.2.1.md"}'),t={name:"guide/changelog/v3.2.1.md"};function p(l,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",35)]))}const u=i(t,[["render",p]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.2.md.DCWOjWem.js b/assets/guide_changelog_v3.2.2.md.DCWOjWem.js new file mode 100644 index 000000000..1f7f37f1b --- /dev/null +++ b/assets/guide_changelog_v3.2.2.md.DCWOjWem.js @@ -0,0 +1 @@ +import{_ as o,c as a,o as t,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.2.2 (2025-12-24)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.2.md","filePath":"guide/changelog/v3.2.2.md"}'),n={name:"guide/changelog/v3.2.2.md"};function i(s,e,c,l,d,h){return t(),a("div",null,e[0]||(e[0]=[r('

    Changelog v3.2.2 (2025-12-24)

    Version 3.2.2 (2025-12-24)

    Full Changelog

    Bug Fixes

    • Fixed sensitive data exposure in command logs for auth endpoints. When ObfuscateAuthParameterLogValues is enabled (default), query string parameters are now stripped from the logged URL to prevent credentials from appearing in logs.
    • Fixed traceId being included in ProblemDetails error responses when ErrorHandlingOptions config section is missing. Now correctly removes traceId by default to match the behavior when the config section exists.
    • Fixed SSL config key mismatch: renamed HttpsRedirection to UseHttpsRedirection for consistency with UseHsts.
    • Fixed missing TokensPerPeriod property in TokenBucket rate limiter configuration.
    • Fixed MetadataQuerySchema comment to accurately describe behavior (when null, no search path is set).

    Performance Improvements

    • Replaced Task with ValueTask for frequently-called private async methods to reduce heap allocations in hot paths:
      • PrepareCommand - called before every query execution
      • OpenConnectionAsync - often completes synchronously when connection is already open
      • ReturnErrorAsync - error handling path
      • Challenge (BasicAuthHandler) - authentication challenge response
    ',7)]))}const u=o(n,[["render",i]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.2.md.DCWOjWem.lean.js b/assets/guide_changelog_v3.2.2.md.DCWOjWem.lean.js new file mode 100644 index 000000000..ec60f9e55 --- /dev/null +++ b/assets/guide_changelog_v3.2.2.md.DCWOjWem.lean.js @@ -0,0 +1 @@ +import{_ as o,c as a,o as t,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.2.2 (2025-12-24)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.2.md","filePath":"guide/changelog/v3.2.2.md"}'),n={name:"guide/changelog/v3.2.2.md"};function i(s,e,c,l,d,h){return t(),a("div",null,e[0]||(e[0]=[r("",7)]))}const u=o(n,[["render",i]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.3.md.BU0QClul.js b/assets/guide_changelog_v3.2.3.md.BU0QClul.js new file mode 100644 index 000000000..1194b82e9 --- /dev/null +++ b/assets/guide_changelog_v3.2.3.md.BU0QClul.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.2.3 (2025-12-30)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.3.md","filePath":"guide/changelog/v3.2.3.md"}'),s={name:"guide/changelog/v3.2.3.md"};function n(l,e,i,c,g,h){return o(),t("div",null,e[0]||(e[0]=[r('

    Changelog v3.2.3 (2025-12-30)

    Version 3.2.3 (2025-12-30)

    Full Changelog

    TsClient Plugin

    • Changed generated TypeScript/JavaScript response checks from response.status === 200 to response.ok and response.status !== 200 to !response.ok for more idiomatic fetch API usage.
    ',5)]))}const u=a(s,[["render",n]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.3.md.BU0QClul.lean.js b/assets/guide_changelog_v3.2.3.md.BU0QClul.lean.js new file mode 100644 index 000000000..b6550ac75 --- /dev/null +++ b/assets/guide_changelog_v3.2.3.md.BU0QClul.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.2.3 (2025-12-30)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.3.md","filePath":"guide/changelog/v3.2.3.md"}'),s={name:"guide/changelog/v3.2.3.md"};function n(l,e,i,c,g,h){return o(),t("div",null,e[0]||(e[0]=[r("",5)]))}const u=a(s,[["render",n]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.2.4.md.DlHy9dKX.js b/assets/guide_changelog_v3.2.4.md.DlHy9dKX.js new file mode 100644 index 000000000..713272105 --- /dev/null +++ b/assets/guide_changelog_v3.2.4.md.DlHy9dKX.js @@ -0,0 +1,24 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.4 (2025-01-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.4.md","filePath":"guide/changelog/v3.2.4.md"}'),e={name:"guide/changelog/v3.2.4.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.2.4 (2025-01-03)

    Version 3.2.4 (2025-01-03)

    Full Changelog

    DataProtection Key Encryption Options

    Added support for encrypting data protection keys at rest using X.509 certificates or Windows DPAPI.

    New Configuration Options:

    json
    json
    {
    +  "DataProtection": {
    +    "KeyEncryption": "None",
    +    "CertificatePath": null,
    +    "CertificatePassword": null,
    +    "DpapiLocalMachine": false
    +  }
    +}

    Options:

    OptionDescription
    KeyEncryptionEncryption method: "None" (default), "Certificate", or "Dpapi" (Windows only)
    CertificatePathPath to X.509 certificate file (.pfx) when using Certificate encryption
    CertificatePasswordPassword for the certificate file (can be null for passwordless certificates)
    DpapiLocalMachineWhen using DPAPI, set to true to protect keys to the local machine instead of current user

    Example with Certificate:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "KeyEncryption": "Certificate",
    +    "CertificatePath": "/path/to/cert.pfx",
    +    "CertificatePassword": "\${CERT_PASSWORD}"
    +  }
    +}

    Example with DPAPI (Windows only):

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "./keys",
    +    "KeyEncryption": "Dpapi",
    +    "DpapiLocalMachine": true
    +  }
    +}

    TsClient Plugin

    • Fixed error parsing in generated TypeScript/JavaScript code to skip response.json() when the response has no body (e.g., 404 responses). The generated code now checks response.headers.get("content-length") !== "0" before attempting to parse the error response.

    NpgsqlRestClient

    • Added Microsoft.Extensions.Caching.StackExchangeRedis and Microsoft.AspNetCore.Authentication.JwtBearer packages to the version display output (--version / -v).
    `,17)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.4.md.DlHy9dKX.lean.js b/assets/guide_changelog_v3.2.4.md.DlHy9dKX.lean.js new file mode 100644 index 000000000..933850006 --- /dev/null +++ b/assets/guide_changelog_v3.2.4.md.DlHy9dKX.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.4 (2025-01-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.4.md","filePath":"guide/changelog/v3.2.4.md"}'),e={name:"guide/changelog/v3.2.4.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t("",17)]))}const g=i(e,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.6.md.Be7WrEBG.js b/assets/guide_changelog_v3.2.6.md.Be7WrEBG.js new file mode 100644 index 000000000..cca7005fd --- /dev/null +++ b/assets/guide_changelog_v3.2.6.md.Be7WrEBG.js @@ -0,0 +1 @@ +import{_ as a,c as t,o as r,a5 as o}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.2.6 (2025-01-04)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.6.md","filePath":"guide/changelog/v3.2.6.md"}'),n={name:"guide/changelog/v3.2.6.md"};function s(l,e,i,g,c,h){return r(),t("div",null,e[0]||(e[0]=[o('

    Changelog v3.2.6 (2025-01-04)

    Version 3.2.6 (2025-01-04)

    Full Changelog

    • Fixed issue for Upload endpoint code generation in TsClient.
    • Skipped version 3.2.5 because of a packaging issue.
    ',4)]))}const _=a(n,[["render",s]]);export{d as __pageData,_ as default}; diff --git a/assets/guide_changelog_v3.2.6.md.Be7WrEBG.lean.js b/assets/guide_changelog_v3.2.6.md.Be7WrEBG.lean.js new file mode 100644 index 000000000..61665f1ad --- /dev/null +++ b/assets/guide_changelog_v3.2.6.md.Be7WrEBG.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o as r,a5 as o}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.2.6 (2025-01-04)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.6.md","filePath":"guide/changelog/v3.2.6.md"}'),n={name:"guide/changelog/v3.2.6.md"};function s(l,e,i,g,c,h){return r(),t("div",null,e[0]||(e[0]=[o("",4)]))}const _=a(n,[["render",s]]);export{d as __pageData,_ as default}; diff --git a/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.js b/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.js new file mode 100644 index 000000000..9986045a2 --- /dev/null +++ b/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.js @@ -0,0 +1,22 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.7 (2025-01-05)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.7.md","filePath":"guide/changelog/v3.2.7.md"}'),l={name:"guide/changelog/v3.2.7.md"};function t(p,s,r,h,o,k){return n(),i("div",null,s[0]||(s[0]=[e(`

    Changelog v3.2.7 (2025-01-05)

    Version 3.2.7 (2025-01-05)

    Full Changelog

    Note: NpgsqlRest core library version jumped from 3.2.2 to 3.2.7 to align with the client application version.

    Upload Handlers: User Context and Claims Support

    Fixed issue where user_context and user_params were not properly available for CSV/Excel upload endpoints:

    • user_context: SET LOCAL session variables (e.g., request.user_id) are now set before upload, making them accessible in row_command via current_setting().
    • user_params: Claim values are now correctly bound to upload function parameters (e.g., _user_id, _user_name).

    New Feature: Added RowCommandUserClaimsKey option to include authenticated user claims in the row metadata JSON parameter ($4) passed to row_command.

    Configuration:

    json
    json
    {
    +  "UploadHandlers": {
    +    "RowCommandUserClaimsKey": "claims"
    +  }
    +}
    • Set to a key name (default: "claims") to include claims in metadata JSON
    • Set to null or empty string to disable

    SQL Usage:

    sql
    sql
    -- Access claims from metadata JSON in row_command
    +create function process_row(
    +  _index int, 
    +  _row text[], 
    +  _prev int, 
    +  _meta json
    +  )
    +returns int 
    +as $$
    +begin
    +    insert into my_table (user_id, data)
    +    values (
    +        (_meta->'claims'->>'name_identifier')::int,
    +        _row[1]
    +    );
    +    return _index;
    +end;
    +$$ language plpgsql;
    `,13)]))}const g=a(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.lean.js b/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.lean.js new file mode 100644 index 000000000..020188c3e --- /dev/null +++ b/assets/guide_changelog_v3.2.7.md.DnOXNKXQ.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.2.7 (2025-01-05)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.2.7.md","filePath":"guide/changelog/v3.2.7.md"}'),l={name:"guide/changelog/v3.2.7.md"};function t(p,s,r,h,o,k){return n(),i("div",null,s[0]||(s[0]=[e("",13)]))}const g=a(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.3.0.md.D_74S82V.js b/assets/guide_changelog_v3.3.0.md.D_74S82V.js new file mode 100644 index 000000000..885cd4902 --- /dev/null +++ b/assets/guide_changelog_v3.3.0.md.D_74S82V.js @@ -0,0 +1,69 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.3.0 (2025-01-08)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.3.0.md","filePath":"guide/changelog/v3.3.0.md"}'),l={name:"guide/changelog/v3.3.0.md"};function t(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Changelog v3.3.0 (2025-01-08)

    Version 3.3.0 (2025-01-08)

    Full Changelog

    Parameter Validation

    New feature for validating endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

    Comment Annotation Syntax:

    sql
    sql
    comment on function my_function(text) is '
    +HTTP POST
    +validate _param_name using rule_name
    +validate _param_name using rule1, rule2, rule3
    +';
    • Parameter names can use either original PostgreSQL names (_email) or converted names (email)
    • Multiple rules can be specified as comma-separated values or on separate lines
    • Rules are evaluated in order; validation stops on first failure

    Built-in Validation Types:

    TypeDescription
    NotNullParameter value cannot be null (DBNull.Value)
    NotEmptyParameter value cannot be an empty string (null values pass)
    RequiredCombines NotNull and NotEmpty - value cannot be null or empty
    RegexParameter value must match the specified regular expression pattern
    MinLengthParameter value must have at least N characters
    MaxLengthParameter value must have at most N characters

    Default Rules:

    Four validation rules are available by default: not_null, not_empty, required, and email.

    Configuration (NpgsqlRestClient):

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\\\s]+@[^@\\\\s]+\\\\.[^@\\\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Rule Properties:

    PropertyRequiredDescription
    TypeYesValidation type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    PatternFor RegexRegular expression pattern
    MinLengthFor MinLengthMinimum character length
    MaxLengthFor MaxLengthMaximum character length
    MessageNoError message with placeholders: {0}=original name, {1}=converted name, {2}=rule name. Default: "Validation failed for parameter '{0}'"
    StatusCodeNoHTTP status code on failure. Default: 400

    Programmatic Configuration:

    csharp
    csharp
    var options = new NpgsqlRestOptions
    +{
    +    ValidationOptions = new ValidationOptions
    +    {
    +        Rules = new Dictionary<string, ValidationRule>
    +        {
    +            ["required"] = new ValidationRule
    +            {
    +                Type = ValidationType.Required,
    +                Message = "Parameter '{0}' is required",
    +                StatusCode = 400
    +            },
    +            ["phone"] = new ValidationRule
    +            {
    +                Type = ValidationType.Regex,
    +                Pattern = @"^\\+?[1-9]\\d{1,14}$",
    +                Message = "Parameter '{0}' must be a valid phone number"
    +            }
    +        }
    +    }
    +};

    Example Usage:

    sql
    sql
    create function register_user(_email text, _password text, _name text)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    -- validation already passed, safe to use parameters
    +    insert into users (email, password_hash, name)
    +    values (_email, crypt(_password, gen_salt('bf')), _name);
    +    return json_build_object('success', true);
    +end;
    +$$;
    +
    +comment on function register_user(text, text, text) is '
    +HTTP POST
    +validate _email using required, email
    +validate _password using required
    +validate _name using not_empty
    +';

    Linux ARM64 Build and Docker Image

    Added Linux ARM64 native build and Docker image support:

    New Release Assets:

    • npgsqlrest-linux-arm64 - Native ARM64 executable for Linux ARM systems (Raspberry Pi, AWS Graviton, Apple Silicon Linux VMs, etc.)

    New Docker Image Tags:

    • vbilopav/npgsqlrest:3.3.0-arm - ARM64 Docker image
    • vbilopav/npgsqlrest:latest-arm - Latest ARM64 Docker image

    The ARM64 build is compiled natively on GitHub's ARM64 runners for optimal performance on ARM-based systems.

    Docker Build Improvements:

    Refactored Docker build pipeline to use GitHub Actions artifacts instead of downloading binaries from release URLs. This eliminates potential race conditions with release asset propagation and removes hardcoded version numbers from Dockerfiles.

    Config Command Shows Default Values

    The --config command now displays the complete configuration including all default values, not just explicitly set values.

    Before: Only showed values explicitly set in configuration files, leaving users to guess what defaults the application would use.

    After: Shows the full merged configuration with all defaults visible, making it useful for:

    • Understanding what values the application will use at runtime
    • Creating a starting point configuration file
    • Debugging configuration issues
    • Self-documenting reference of all available options
    `,34)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.3.0.md.D_74S82V.lean.js b/assets/guide_changelog_v3.3.0.md.D_74S82V.lean.js new file mode 100644 index 000000000..9d9d7e987 --- /dev/null +++ b/assets/guide_changelog_v3.3.0.md.D_74S82V.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.3.0 (2025-01-08)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.3.0.md","filePath":"guide/changelog/v3.3.0.md"}'),l={name:"guide/changelog/v3.3.0.md"};function t(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",34)]))}const g=i(l,[["render",t]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.3.1.md.DBVEhTOX.js b/assets/guide_changelog_v3.3.1.md.DBVEhTOX.js new file mode 100644 index 000000000..00047d81a --- /dev/null +++ b/assets/guide_changelog_v3.3.1.md.DBVEhTOX.js @@ -0,0 +1,41 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.3.1 (2025-01-14)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.3.1.md","filePath":"guide/changelog/v3.3.1.md"}'),l={name:"guide/changelog/v3.3.1.md"};function p(t,s,r,h,c,o){return i(),n("div",null,s[0]||(s[0]=[e(`

    Changelog v3.3.1 (2025-01-14)

    Version 3.3.1 (2025-01-14)

    Full Changelog

    Proxy Response Caching

    Added support for caching responses from passthrough proxy endpoints. Previously, caching only worked with endpoints that executed database functions. Now, proxy endpoints that forward requests to upstream services can also leverage the caching system.

    Usage:

    sql
    sql
    create function get_external_data()
    +returns void
    +language plpgsql as $$ begin null; end; $$;
    +
    +comment on function get_external_data() is '
    +HTTP GET
    +proxy
    +cached
    +cache_expires_in 5 minutes
    +';

    Features:

    • Cache lookup happens before proxy request is sent
    • On cache hit, response is returned immediately without calling upstream service
    • Cached proxy responses preserve: status code, body, content type, and headers
    • Supports cache key parameters for parameter-based caching
    • Supports cache expiration with cache_expires_in annotation

    Example with cache key:

    sql
    sql
    create function get_user_profile(_user_id text)
    +returns void
    +language plpgsql as $$ begin null; end; $$;
    +
    +comment on function get_user_profile(text) is '
    +HTTP GET
    +proxy https://api.example.com/users
    +cached _user_id
    +cache_expires_in 1 hour
    +';

    This is useful for:

    • Reducing load on upstream services
    • Improving response times for frequently accessed data
    • Rate limiting protection for external API calls

    Optional @ Prefix for Comment Annotations

    Added support for optional @ prefix on all NpgsqlRest-specific comment annotations. This provides better visual distinction and consistency with .http file conventions.

    Both syntaxes are equivalent and can be mixed freely:

    sql
    sql
    -- Without @ prefix (existing syntax - still works)
    +comment on function my_func() is '
    +HTTP GET
    +authorize
    +cached
    +raw
    +';
    +
    +-- With @ prefix (new syntax)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +@cached
    +@raw
    +';
    +
    +-- Mixed (both work together)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +cached
    +@timeout 30s
    +';

    Notes:

    • The @ prefix is optional - existing code without @ continues to work unchanged
    • HTTP RFC standard annotations (headers with Name: value syntax) do not use the @ prefix
    • This applies to all NpgsqlRest-specific annotations: authorize, cached, raw, disabled, login, logout, proxy, upload, validate, etc.

    Added a logo on client app commands

    `,22)]))}const g=a(l,[["render",p]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.3.1.md.DBVEhTOX.lean.js b/assets/guide_changelog_v3.3.1.md.DBVEhTOX.lean.js new file mode 100644 index 000000000..fd471ae03 --- /dev/null +++ b/assets/guide_changelog_v3.3.1.md.DBVEhTOX.lean.js @@ -0,0 +1 @@ +import{_ as a,c as n,o as i,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.3.1 (2025-01-14)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.3.1.md","filePath":"guide/changelog/v3.3.1.md"}'),l={name:"guide/changelog/v3.3.1.md"};function p(t,s,r,h,c,o){return i(),n("div",null,s[0]||(s[0]=[e("",22)]))}const g=a(l,[["render",p]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.0.md.DWaLq7jU.js b/assets/guide_changelog_v3.4.0.md.DWaLq7jU.js new file mode 100644 index 000000000..ebaffbe31 --- /dev/null +++ b/assets/guide_changelog_v3.4.0.md.DWaLq7jU.js @@ -0,0 +1,91 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.0 (2025-01-16)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.0.md","filePath":"guide/changelog/v3.4.0.md"}'),e={name:"guide/changelog/v3.4.0.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.4.0 (2025-01-16)

    Version 3.4.0 (2025-01-16)

    Full Changelog

    Composite Type Support

    Added automatic JSON serialization support for PostgreSQL composite types in two scenarios:

    1. Arrays of Composite Types

    When a function returns a column that is an array of a composite type (or table type), the array elements are now automatically serialized as JSON arrays of objects instead of PostgreSQL's text representation.

    Example:

    sql
    sql
    create type book_item as (
    +    book_id int,
    +    title text,
    +    author_id int
    +);
    +
    +create function get_authors_with_books()
    +returns table(
    +    author_id int,
    +    author_name text,
    +    books book_item[]
    +)
    +language sql as $$
    +select * from (values
    +    (1, 'George Orwell', array[
    +        row(1, '1984', 1)::book_item,
    +        row(2, 'Animal Farm', 1)::book_item
    +    ])
    +) as t(author_id, author_name, books);
    +$$;

    Previous behavior:

    json
    json
    [{"authorId":1,"authorName":"George Orwell","books":["(1,1984,1)","(2,Animal Farm,1)"]}]

    New behavior:

    json
    json
    [{"authorId":1,"authorName":"George Orwell","books":[{"bookId":1,"title":"1984","authorId":1},{"bookId":2,"title":"Animal Farm","authorId":1}]}]

    This feature is automatic and requires no annotations. It works with:

    • Custom composite types (create type)
    • Table types (arrays of table row types)
    • Composite types containing NULL values
    • Empty arrays and NULL arrays
    • Multiple array columns in the same result set
    • Primitive arrays inside composite types (e.g., int[] field) - properly serialized as JSON arrays

    Limitations (with ResolveNestedCompositeTypes: false):

    When ResolveNestedCompositeTypes is disabled, the array composite serialization works for one level only. Nested structures have the following behavior:

    ScenarioOutput
    Nested composite (composite inside composite)Inner composite serialized as PostgreSQL tuple string: "(1,x)" instead of {"id":1,"name":"x"}
    Array of composites inside compositeArray of tuple strings: ["(1,a)","(2,b)"] instead of [{"id":1,"name":"a"},...]

    Note: These limitations do not apply when ResolveNestedCompositeTypes: true (the default). See the ResolveNestedCompositeTypes documentation in version 3.4.4 for full nested composite support.

    For complex nested structures with the option disabled, use PostgreSQL's json_build_object/json_agg functions to construct the JSON directly in your query.

    2. Nested JSON for Composite Type Columns (Opt-in)

    When a function returns a composite type column, by default the composite type fields are expanded into separate columns (existing behavior preserved for backward compatibility).

    To serialize composite type columns as nested JSON objects, you can either:

    1. Enable globally via configuration option NestedJsonForCompositeTypes (default is false):
    json
    json
    {
    +  "NpgsqlRest": {
    +      "RoutineOptions": {
    +        "NestedJsonForCompositeTypes": true
    +    }
    +  }
    +}
    1. Enable per-endpoint via comment annotation (nested, nested_json, or nested_composite):
    sql
    sql
    comment on function get_user_with_address() is 'nested';
    +-- or: 'nested_json'
    +-- or: 'nested_composite'

    Example:

    sql
    sql
    create type address_type as (
    +    street text,
    +    city text,
    +    zip_code text
    +);
    +
    +create function get_user_with_address()
    +returns table(
    +    user_id int,
    +    user_name text,
    +    address address_type
    +)
    +language sql as $$
    +select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
    +$$;
    +
    +comment on function get_user_with_address() is 'nested';

    Default behavior (expanded columns):

    json
    json
    [{"userId":1,"userName":"Alice","street":"123 Main St","city":"New York","zipCode":"10001"}]

    With nested annotation or NestedJsonForCompositeTypes: true:

    json
    json
    [{"userId":1,"userName":"Alice","address":{"street":"123 Main St","city":"New York","zipCode":"10001"}}]

    Multidimensional Array Support

    Added proper JSON serialization for multidimensional PostgreSQL arrays. Previously, multidimensional arrays were serialized incorrectly, producing invalid JSON. Now they are properly converted to nested JSON arrays.

    Example:

    sql
    sql
    create function get_2d_int_array()
    +returns table(
    +    matrix int[][]
    +)
    +language sql as $$
    +select array[[1,2,3],[4,5,6]];
    +$$;

    Previous behavior (invalid JSON):

    code
    [{"matrix":[{1,2,3,{4,5,6]}]

    New behavior:

    json
    json
    [{"matrix":[[1,2,3],[4,5,6]]}]

    This feature is automatic and requires no configuration. It works with:

    • 2D arrays: {{1,2},{3,4}}[[1,2],[3,4]]
    • 3D arrays: {{{1,2},{3,4}},{{5,6},{7,8}}}[[[1,2],[3,4]],[[5,6],[7,8]]]
    • Higher dimensional arrays
    • All primitive types (int, text, boolean, numeric, etc.)
    • NULL values within multidimensional arrays

    Limitation: Multidimensional arrays of composite types are serialized as nested arrays of PostgreSQL tuple strings, not as fully expanded JSON objects. For example, a 2D array of composites {{"(1,a)","(2,b)"},{"(3,c)","(4,d)"}} becomes [["(1,a)","(2,b)"],["(3,c)","(4,d)"]]. The data is preserved but not fully parsed. For complex nested structures, consider using PostgreSQL's json_build_object/json_agg functions instead.

    JSON Escaping Fix for Arrays and Tuple Strings

    Fixed JSON escaping issues that could produce invalid JSON output when PostgreSQL arrays or composite types contain special characters. The fix ensures all special characters are properly escaped:

    Characters now properly escaped:

    • Quotes (") - escaped as \\"
    • Backslashes (\\) - escaped as \\\\
    • Newlines - escaped as \\n
    • Tabs - escaped as \\t
    • Carriage returns - escaped as \\r
    • Combined special characters in the same string

    Example:

    sql
    sql
    create function get_text_array()
    +returns text[]
    +language sql as $$
    +select array['hello "world"', 'path\\to\\file', E'line1\\nline2'];
    +$$;

    Previous behavior (could produce invalid JSON):

    json
    json
    ["hello \\"world\\"", "path\\to\\file", "line1
    +line2"]

    New behavior (valid JSON):

    json
    json
    ["hello \\"world\\"","path\\\\to\\\\file","line1\\nline2"]

    This fix applies to:

    • Simple text arrays with special characters
    • Multidimensional arrays (2D, 3D, etc.)
    • Nested composite types serialized as tuple strings
    • Arrays of composite types with special characters in field values
    • Unicode characters and emoji (preserved correctly)
    • Empty strings and whitespace-only strings
    • JSON-like string content (properly escaped, not parsed)

    TsClient Plugin: Composite Type Interface Generation

    The TsClient plugin now generates proper TypeScript interfaces for composite types:

    Generated TypeScript:

    typescript
    typescript
    interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    authorId: number | null;
    +}
    +
    +interface IAddress {
    +    street: string | null;
    +    city: string | null;
    +    zipCode: string | null;
    +}
    +
    +interface IGetAuthorsWithBooksResponse {
    +    authorId: number | null;
    +    authorName: string | null;
    +    books: IBooks[] | null;  // Array of composite type
    +}
    +
    +interface IGetUserWithAddressResponse {
    +    userId: number | null;
    +    userName: string | null;
    +    address: IAddress | null;  // Nested composite type
    +}

    Features:

    • Separate interfaces generated for each unique composite type structure
    • Array composite columns typed as InterfaceName[]
    • Nested composite columns typed as InterfaceName
    • Interfaces are deduplicated when the same composite structure appears in multiple functions

    TsClient Limitation - Multidimensional Arrays:

    PostgreSQL normalizes multidimensional array types (int[][], int[][][]) to single-dimensional (integer[]) in all catalog views. This is a PostgreSQL limitation—there is no way to retrieve the original array dimensionality from metadata.

    Consequence: Multidimensional arrays are typed as single-dimensional in TypeScript:

    • int[][]number[] (instead of number[][])
    • int[][][]number[] (instead of number[][][])

    The runtime JSON is always correct (e.g., [[1,2],[3,4]]), but the TypeScript type won't match. For strict TypeScript projects, manually cast the response type when using multidimensional arrays.

    Optional @ Prefix Extended to Annotation Parameters

    The optional @ prefix for comment annotations (introduced in 3.3.1) now also works with annotation parameters using the key = value syntax.

    Both syntaxes are now equivalent:

    sql
    sql
    -- Without @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +raw = true
    +timeout = 30s
    +my_custom_param = custom_value
    +';
    +
    +-- With @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +@raw = true
    +@timeout = 30s
    +@my_custom_param = custom_value
    +';

    This applies to all annotation parameters including raw, timeout, buffer, connection, user_context, user_parameters, SSE settings, basic auth settings, and custom parameters.

    Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

    `,73)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.4.0.md.DWaLq7jU.lean.js b/assets/guide_changelog_v3.4.0.md.DWaLq7jU.lean.js new file mode 100644 index 000000000..7505cf664 --- /dev/null +++ b/assets/guide_changelog_v3.4.0.md.DWaLq7jU.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.0 (2025-01-16)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.0.md","filePath":"guide/changelog/v3.4.0.md"}'),e={name:"guide/changelog/v3.4.0.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[t("",73)]))}const y=i(e,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.4.1.md.BjkyM2ol.js b/assets/guide_changelog_v3.4.1.md.BjkyM2ol.js new file mode 100644 index 000000000..95e9c9993 --- /dev/null +++ b/assets/guide_changelog_v3.4.1.md.BjkyM2ol.js @@ -0,0 +1,12 @@ +import{_ as e,c as n,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.4.1 (2025-01-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.1.md","filePath":"guide/changelog/v3.4.1.md"}'),l={name:"guide/changelog/v3.4.1.md"};function t(r,s,o,p,d,h){return a(),n("div",null,s[0]||(s[0]=[i(`

    Changelog v3.4.1 (2025-01-15)

    Version 3.4.1 (2025-01-15)

    Full Changelog

    Configuration Options for Null Handling

    Added global configuration options for QueryStringNullHandling and TextResponseNullHandling in appsettings.json.

    QueryStringNullHandling

    Sets the default behavior for handling NULL values in query string parameters:

    • Ignore (default): No special handling - empty strings stay as empty strings, "null" literal stays as "null" string.
    • EmptyString: Empty query string values are interpreted as NULL values.
    • NullLiteral: Literal string "null" (case insensitive) is interpreted as NULL value.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "QueryStringNullHandling": "EmptyString"
    +  }
    +}

    TextResponseNullHandling

    Sets the default behavior for plain text responses when the execution returns NULL from the database:

    • EmptyString (default): Returns an empty string response with status code 200 OK.
    • NullLiteral: Returns a string literal "NULL" with status code 200 OK.
    • NoContent: Returns status code 204 NO CONTENT.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "TextResponseNullHandling": "NoContent"
    +  }
    +}

    Both options can also be overridden per-endpoint using comment annotations:

    sql
    sql
    comment on function my_func(text) is '
    +query_string_null_handling empty_string
    +text_response_null_handling no_content
    +';

    Bug Fixes

    • Fixed logging condition in QueryStringNullHandlingHandler that was incorrectly checking TextResponseNullHandling instead of QueryStringNullHandling when determining whether to log annotation changes.
    • Fixed overloaded function resolution not updating the SQL command text. When multiple PostgreSQL functions with the same name but different parameter types (e.g., one with int and one with a custom composite type) were mapped to the same endpoint, selecting an overload based on parameter count would use the wrong SQL expression, causing syntax errors.
    • Fixed error logging to include command parameters. When command execution failed, the error log now includes the request URL and parameter values (when LogCommands and LogCommandParameters are enabled) for easier debugging.

    `,18)]))}const g=e(l,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.1.md.BjkyM2ol.lean.js b/assets/guide_changelog_v3.4.1.md.BjkyM2ol.lean.js new file mode 100644 index 000000000..3645c0162 --- /dev/null +++ b/assets/guide_changelog_v3.4.1.md.BjkyM2ol.lean.js @@ -0,0 +1 @@ +import{_ as e,c as n,o as a,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.4.1 (2025-01-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.1.md","filePath":"guide/changelog/v3.4.1.md"}'),l={name:"guide/changelog/v3.4.1.md"};function t(r,s,o,p,d,h){return a(),n("div",null,s[0]||(s[0]=[i("",18)]))}const g=e(l,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.2.md.CwgXLNC8.js b/assets/guide_changelog_v3.4.2.md.CwgXLNC8.js new file mode 100644 index 000000000..b91af9d31 --- /dev/null +++ b/assets/guide_changelog_v3.4.2.md.CwgXLNC8.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.4.2 (2025-01-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.2.md","filePath":"guide/changelog/v3.4.2.md"}'),r={name:"guide/changelog/v3.4.2.md"};function s(n,e,l,d,c,g){return o(),t("div",null,e[0]||(e[0]=[i('

    Changelog v3.4.2 (2025-01-15)

    Version 3.4.2 (2025-01-15)

    Full Changelog

    Bug Fixes

    • Fixed AOT compatibility issue with JSON deserialization. When running with Native AOT or with reflection-based serialization disabled, parsing composite type metadata for nested array columns would fail with InvalidOperationException: Reflection-based serialization has been disabled. Added string[][] to the source-generated NpgsqlRestSerializerContext to support AOT compilation.

    ',6)]))}const u=a(r,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.4.2.md.CwgXLNC8.lean.js b/assets/guide_changelog_v3.4.2.md.CwgXLNC8.lean.js new file mode 100644 index 000000000..af4dd6bd1 --- /dev/null +++ b/assets/guide_changelog_v3.4.2.md.CwgXLNC8.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.4.2 (2025-01-15)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.2.md","filePath":"guide/changelog/v3.4.2.md"}'),r={name:"guide/changelog/v3.4.2.md"};function s(n,e,l,d,c,g){return o(),t("div",null,e[0]||(e[0]=[i("",6)]))}const u=a(r,[["render",s]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.js b/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.js new file mode 100644 index 000000000..f198a259b --- /dev/null +++ b/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.4.3 (2025-01-16)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.3.md","filePath":"guide/changelog/v3.4.3.md"}'),s={name:"guide/changelog/v3.4.3.md"};function i(n,e,l,c,d,h){return o(),t("div",null,e[0]||(e[0]=[r('

    Changelog v3.4.3 (2025-01-16)

    Version 3.4.3 (2025-01-16)

    Full Changelog

    Bug Fixes

    • Fixed double-escaping bug in PostgreSQL tuple string serialization. When composite types contain arrays of other composites (e.g., nested composite types with array fields), the JSON output now correctly escapes quotes instead of double-escaping them (\\" instead of \\\\\\"). This fix ensures that decoded tuple strings contain proper PostgreSQL tuple format with doubled quotes ("") for literal quote characters, rather than backslash-escaped quotes.

    Performance Improvements

    • Optimized PgCompositeArrayToJsonArray to use stack allocation (stackalloc) for small inputs (≤512 chars) and ArrayPool<char> for larger inputs, eliminating per-element StringBuilder allocations and reducing GC pressure.

    ',8)]))}const p=a(s,[["render",i]]);export{g as __pageData,p as default}; diff --git a/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.lean.js b/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.lean.js new file mode 100644 index 000000000..45d7a440c --- /dev/null +++ b/assets/guide_changelog_v3.4.3.md.CwIJ6zQw.lean.js @@ -0,0 +1 @@ +import{_ as a,c as t,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.4.3 (2025-01-16)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.3.md","filePath":"guide/changelog/v3.4.3.md"}'),s={name:"guide/changelog/v3.4.3.md"};function i(n,e,l,c,d,h){return o(),t("div",null,e[0]||(e[0]=[r("",8)]))}const p=a(s,[["render",i]]);export{g as __pageData,p as default}; diff --git a/assets/guide_changelog_v3.4.4.md.Dun3dnAP.js b/assets/guide_changelog_v3.4.4.md.Dun3dnAP.js new file mode 100644 index 000000000..ec6131ee2 --- /dev/null +++ b/assets/guide_changelog_v3.4.4.md.Dun3dnAP.js @@ -0,0 +1,20 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.4 (2025-01-17)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.4.md","filePath":"guide/changelog/v3.4.4.md"}'),n={name:"guide/changelog/v3.4.4.md"};function l(p,s,h,r,o,k){return e(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.4.4 (2025-01-17)

    Version 3.4.4 (2025-01-17)

    Full Changelog

    Deep Nested Composite Type Resolution (ResolveNestedCompositeTypes)

    By default, NpgsqlRest resolves nested composite types to any depth. When a composite type contains another composite type (or an array of composites), the inner composites are serialized as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Example:

    sql
    sql
    create type inner_type as (id int, name text);
    +create type outer_type as (label text, inner_val inner_type);
    +create type with_array as (group_name text, members inner_type[]);
    +
    +create function get_nested_data()
    +returns table(data outer_type, items with_array)
    +language sql as $$
    +select
    +    row('outer', row(1, 'inner')::inner_type)::outer_type,
    +    row('group1', array[row(1,'a')::inner_type, row(2,'b')::inner_type])::with_array;
    +$$;

    Output:

    json
    json
    [{
    +  "data": {"label":"outer","innerVal":{"id":1,"name":"inner"}},
    +  "items": {"groupName":"group1","members":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
    +}]

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Default: true - nested composites are fully resolved.

    How it works:

    At application startup, when ResolveNestedCompositeTypes is enabled:

    1. Type Cache Initialization: Queries pg_catalog to build a cache of all composite types in the database, including their field names, field types, and nested relationships.

    2. Metadata Enrichment: For each routine that returns composite types, the field descriptors are enriched with nested type information from the cache.

    3. Runtime Serialization: During request processing, the serializer checks each field's metadata. If the field is marked as a composite type (or array of composites), it recursively parses the PostgreSQL tuple string and outputs a proper JSON object/array.

    When to disable (ResolveNestedCompositeTypes: false):

    ScenarioReason
    Large schemas with thousands of composite typesReduces startup time by skipping the type cache initialization query
    No nested composites in your schemaIf your composites don't contain other composites, the cache provides no benefit
    Memory-constrained environmentsThe type cache consumes memory proportional to the number of composite types
    Backward compatibilityIf you depend on the old tuple string format "(1,x)" in your client code

    Performance considerations:

    • Startup cost: One additional query to pg_catalog at startup to build the type cache
    • Memory: Cache size is proportional to: (number of composite types) × (average fields per type)
    • Runtime: Negligible - just a dictionary lookup per composite field

    PostgreSQL version compatibility:

    Tested and works on PostgreSQL 13 through 17. The feature uses standard pg_catalog views that are stable across PostgreSQL versions.

    Edge cases handled:

    • Empty arrays of composites → []
    • NULL composite elements in arrays → [{"id":1},null,{"id":2}]
    • Composites with all NULL fields → {"id":null,"name":null}
    • Empty string vs NULL distinction → "" vs null
    • Unicode characters (emoji, Chinese, Arabic) → preserved correctly
    • Deeply nested structures (4+ levels) → fully resolved
    • Self-referencing types → cycle detection prevents infinite loops

    Bug Fixes

    • Fixed "permission denied for schema" error in the metadata query when a database user with limited privileges runs the routine discovery. The error occurred when a user with only USAGE permission on specific schemas tried to discover routines, but the database contained other schemas with composite types that the user couldn't access. The ::regtype cast in the metadata query would fail when attempting to resolve type names from unauthorized schemas. Added has_schema_privilege checks to filter out:
      • Array element types from schemas the user cannot access
      • Schemas the user cannot access from the schema aggregation
      • Routines that return types from schemas the user cannot access

    `,26)]))}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.4.md.Dun3dnAP.lean.js b/assets/guide_changelog_v3.4.4.md.Dun3dnAP.lean.js new file mode 100644 index 000000000..8518416ff --- /dev/null +++ b/assets/guide_changelog_v3.4.4.md.Dun3dnAP.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.4 (2025-01-17)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.4.md","filePath":"guide/changelog/v3.4.4.md"}'),n={name:"guide/changelog/v3.4.4.md"};function l(p,s,h,r,o,k){return e(),a("div",null,s[0]||(s[0]=[t("",26)]))}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.5.md.CCdH9iK4.js b/assets/guide_changelog_v3.4.5.md.CCdH9iK4.js new file mode 100644 index 000000000..a56efafb5 --- /dev/null +++ b/assets/guide_changelog_v3.4.5.md.CCdH9iK4.js @@ -0,0 +1,17 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.5 (2025-01-19)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.5.md","filePath":"guide/changelog/v3.4.5.md"}'),n={name:"guide/changelog/v3.4.5.md"};function l(p,s,h,r,k,o){return e(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.4.5 (2025-01-19)

    Version 3.4.5 (2025-01-19)

    Full Changelog

    NpgsqlRest.TsClient: Deep Nested Composite Type Support

    Fixed the TypeScript client generator (NpgsqlRest.TsClient) to properly handle deeply nested composite types when NestedJsonForCompositeTypes is enabled.

    Before (incorrect):

    typescript
    typescript
    interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    reviews: string[] | null;  // Wrong: should be IReviews[]
    +}

    After (correct):

    typescript
    typescript
    interface IReviews {
    +    reviewId: number | null;
    +    bookId: number | null;
    +    reviewerName: string | null;
    +    rating: number | null;
    +    reviewText: string | null;
    +}
    +
    +interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    reviews: IReviews[] | null;  // Correct: properly typed array
    +}

    Supported scenarios:

    • Arrays of composites containing arrays: books[] where each book has reviews[]
    • Deep nesting (4+ levels): level4 → level3 → level2 → level1
    • Mixed nesting: Composite containing nested composite that contains array of composites

    The fix recursively processes TypeDescriptor.CompositeFieldNames, TypeDescriptor.CompositeFieldDescriptors, TypeDescriptor.ArrayCompositeFieldNames, and TypeDescriptor.ArrayCompositeFieldDescriptors to generate proper TypeScript interfaces for all nested types.

    Note: This only applies when NestedJsonForCompositeTypes is enabled (via nested annotation or global config). When disabled, arrays of composite types correctly remain as string[] to match the PostgreSQL tuple string format returned by the API.


    `,14)]))}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.5.md.CCdH9iK4.lean.js b/assets/guide_changelog_v3.4.5.md.CCdH9iK4.lean.js new file mode 100644 index 000000000..f0fc56fd3 --- /dev/null +++ b/assets/guide_changelog_v3.4.5.md.CCdH9iK4.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as e,a5 as t}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.4.5 (2025-01-19)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.5.md","filePath":"guide/changelog/v3.4.5.md"}'),n={name:"guide/changelog/v3.4.5.md"};function l(p,s,h,r,k,o){return e(),a("div",null,s[0]||(s[0]=[t("",14)]))}const g=i(n,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.6.md.DfISyzwO.js b/assets/guide_changelog_v3.4.6.md.DfISyzwO.js new file mode 100644 index 000000000..44b659d4b --- /dev/null +++ b/assets/guide_changelog_v3.4.6.md.DfISyzwO.js @@ -0,0 +1 @@ +import{_ as o,c as n,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.4.6 (2025-01-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.6.md","filePath":"guide/changelog/v3.4.6.md"}'),r={name:"guide/changelog/v3.4.6.md"};function i(d,e,c,l,s,p){return a(),n("div",null,e[0]||(e[0]=[t('

    Changelog v3.4.6 (2025-01-21)

    Version 3.4.6 (2025-01-21)

    Full Changelog

    Endpoint Execution Performance Optimizations

    Reduced memory allocations and CPU overhead in the hot path of endpoint execution through several optimizations:

    StringBuilder Pooling

    Added a thread-safe StringBuilderPool to reuse StringBuilder instances across requests instead of allocating new ones:

    • cmdLog - command logging
    • cacheKeys - cache key building
    • rowBuilder - response row building
    • compositeFieldBuffer - nested JSON composite handling
    • commandTextBuilder - SQL command text building

    The pool maintains up to 64 instances with lock-free rent/return operations.

    Avoid Query String Dictionary Allocation

    Changed from context.Request.Query.ToDictionary() to using IQueryCollection directly, eliminating a dictionary allocation on every request. The IQueryCollection interface already provides TryGetValue(), Count, and ContainsKey() methods.

    StringBuilder for Command Text Building

    Replaced ~18 string.Concat(commandText, ...) calls with StringBuilder.Append() operations, reducing intermediate string allocations when building SQL commands for non-formattable routines.

    HashSet for Path Parameter Lookup

    Added FindMatchingPathParameter() method with lazy-initialized HashSet<string> for O(1) case-insensitive lookups instead of O(n) array iteration when matching path parameters.


    Comprehensive CancellationToken Propagation

    Improved cancellation token propagation throughout the entire request pipeline. The CancellationToken parameter is now properly passed to all async operations, enabling proper request cancellation and resource cleanup when clients disconnect or requests are aborted.

    Changes:

    • NpgsqlRestEndpoint: Fixed missing cancellation token propagation to ReadToEndAsync, ReadAsync, WriteAsync, FlushAsync, BeginTransactionAsync, CommitAsync, and helper methods (PrepareCommand, OpenConnectionAsync, ValidateParametersAsync, ReturnErrorAsync).

    • Auth Handlers: Added CancellationToken parameter to BasicAuthHandler.HandleAsync, LoginHandler.HandleAsync, and LogoutHandler.HandleAsync. All database operations and response writes now respect cancellation.

    • Upload Handlers: Updated IUploadHandler.UploadAsync interface and all implementations (DefaultUploadHandler, FileSystemUploadHandler, LargeObjectUploadHandler, CsvUploadHandler, ExcelUploadHandler) to accept and propagate cancellation tokens to file I/O and database operations.

    • Proxy Handler: Added CancellationToken parameter to ProxyRequestHandler.WriteResponseAsync for cancellable response body writes.

    Benefits:

    • Immediate cleanup when HTTP clients disconnect mid-request
    • Proper cancellation of long-running database queries
    • Reduced resource consumption from abandoned requests
    • Better handling of upload/download operations that can be cancelled
    • Prevents request storms: When users repeatedly refresh the browser during slow endpoint execution, each refresh creates a new request while the previous one continues running. Without proper cancellation token propagation, these abandoned requests continue executing database queries, potentially choking the database. With this fix, abandoned requests are properly cancelled, freeing up database connections immediately.

    ',23)]))}const h=o(r,[["render",i]]);export{u as __pageData,h as default}; diff --git a/assets/guide_changelog_v3.4.6.md.DfISyzwO.lean.js b/assets/guide_changelog_v3.4.6.md.DfISyzwO.lean.js new file mode 100644 index 000000000..74283d356 --- /dev/null +++ b/assets/guide_changelog_v3.4.6.md.DfISyzwO.lean.js @@ -0,0 +1 @@ +import{_ as o,c as n,o as a,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.4.6 (2025-01-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.6.md","filePath":"guide/changelog/v3.4.6.md"}'),r={name:"guide/changelog/v3.4.6.md"};function i(d,e,c,l,s,p){return a(),n("div",null,e[0]||(e[0]=[t("",23)]))}const h=o(r,[["render",i]]);export{u as __pageData,h as default}; diff --git a/assets/guide_changelog_v3.4.7.md.PS46_sBY.js b/assets/guide_changelog_v3.4.7.md.PS46_sBY.js new file mode 100644 index 000000000..dd0f27fa5 --- /dev/null +++ b/assets/guide_changelog_v3.4.7.md.PS46_sBY.js @@ -0,0 +1,2 @@ +import{_ as s,c as t,o as a,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.4.7 (2025-01-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.7.md","filePath":"guide/changelog/v3.4.7.md"}'),n={name:"guide/changelog/v3.4.7.md"};function l(o,i,p,r,h,d){return a(),t("div",null,i[0]||(i[0]=[e(`

    Changelog v3.4.7 (2025-01-21)

    Version 3.4.7 (2025-01-21)

    Full Changelog

    Type Category Lookup Optimization

    Introduced TypeCategory flags enum and pre-computed lookup table for O(1) type dispatch, replacing sequential if-chain conditionals in hot paths.

    New Files:

    • TypeCategory.cs - Flags enum (Numeric, Boolean, Json, Text, DateTime, Date, NeedsEscape, CastToText, Binary, Time) and TypeCategoryLookup static class with 128-element array for instant type classification
    • ParameterParsers.cs - Delegate array for O(1) parameter parser lookup by NpgsqlDbType

    Changes:

    • TypeDescriptor now has a Category property computed once at construction via lookup table
    • Boolean properties (IsNumeric, IsJson, IsText, etc.) are now computed from Category using bitwise operations
    • NpgsqlRestEndpoint.cs and PgConverters.cs use bitwise category checks for type dispatch

    Benchmark Results:

    OperationBeforeAfterImprovement
    Type category lookup (18 types)22.6 ns6.6 ns70% faster
    TypeDescriptor construction232.8 ns164.2 ns29% faster
    Parser delegate lookup7.6 ns5.9 ns23% faster
    Combined type check (bitwise vs properties)7.97 ns4.94 ns38% faster
    Serialization type check (1000 rows)5,572 ns4,060 ns27% faster

    Note: While micro-benchmarks show significant improvements, real-world endpoint throughput gains are modest (1-5%) since type dispatch is a small fraction of total request time compared to database I/O and serialization.

    Additional Allocation Optimizations

    Parameter Logging String Allocations

    Replaced string.Concat() with paramIndex.ToString() in 8 logging paths with direct StringBuilder.Append(int) calls, eliminating intermediate string allocations for each logged parameter.

    Before:

    csharp
    csharp
    cmdLog!.AppendLine(string.Concat("-- $", paramIndex.ToString(), " ", ...));

    After:

    csharp
    csharp
    cmdLog!.Append("-- $").Append(paramIndex).Append(' ').Append(...).AppendLine(p);

    Cache Key String Reuse

    Cache key string (cacheKeys.ToString()) was being called 3-6 times per cached request. Now computed once and reused:

    csharp
    csharp
    string? cacheKeyString = cacheKeys?.ToString();
    +// Reused in all cache Get/AddOrUpdate calls

    Impact: Eliminates 8+ string allocations per parameter-heavy request (logging) and 2-5 allocations per cached request (cache keys).


    `,24)]))}const g=s(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.7.md.PS46_sBY.lean.js b/assets/guide_changelog_v3.4.7.md.PS46_sBY.lean.js new file mode 100644 index 000000000..49425d8fd --- /dev/null +++ b/assets/guide_changelog_v3.4.7.md.PS46_sBY.lean.js @@ -0,0 +1 @@ +import{_ as s,c as t,o as a,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Changelog v3.4.7 (2025-01-21)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.7.md","filePath":"guide/changelog/v3.4.7.md"}'),n={name:"guide/changelog/v3.4.7.md"};function l(o,i,p,r,h,d){return a(),t("div",null,i[0]||(i[0]=[e("",24)]))}const g=s(n,[["render",l]]);export{k as __pageData,g as default}; diff --git a/assets/guide_changelog_v3.4.8.md.C6Us0ANY.js b/assets/guide_changelog_v3.4.8.md.C6Us0ANY.js new file mode 100644 index 000000000..44ee030de --- /dev/null +++ b/assets/guide_changelog_v3.4.8.md.C6Us0ANY.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.4.8 (2025-01-26)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.8.md","filePath":"guide/changelog/v3.4.8.md"}'),s={name:"guide/changelog/v3.4.8.md"};function n(i,e,l,g,c,h){return o(),a("div",null,e[0]||(e[0]=[r('

    Changelog v3.4.8 (2025-01-26)

    Version 3.4.8 (2025-01-26)

    Full Changelog

    Fix: Single-Field Composite Type Returns

    Fixed functions returning single-field composite types returning {"status":"(t)"} instead of {"status":true}.


    ',6)]))}const u=t(s,[["render",n]]);export{d as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.4.8.md.C6Us0ANY.lean.js b/assets/guide_changelog_v3.4.8.md.C6Us0ANY.lean.js new file mode 100644 index 000000000..bab15f77e --- /dev/null +++ b/assets/guide_changelog_v3.4.8.md.C6Us0ANY.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as r}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.4.8 (2025-01-26)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.4.8.md","filePath":"guide/changelog/v3.4.8.md"}'),s={name:"guide/changelog/v3.4.8.md"};function n(i,e,l,g,c,h){return o(),a("div",null,e[0]||(e[0]=[r("",6)]))}const u=t(s,[["render",n]]);export{d as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.5.0.md.COe0eWbp.js b/assets/guide_changelog_v3.5.0.md.COe0eWbp.js new file mode 100644 index 000000000..2d7544ee3 --- /dev/null +++ b/assets/guide_changelog_v3.5.0.md.COe0eWbp.js @@ -0,0 +1,6 @@ +import{_ as s,c as i,o as t,a5 as a}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.5.0 (2025-01-28)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.5.0.md","filePath":"guide/changelog/v3.5.0.md"}'),n={name:"guide/changelog/v3.5.0.md"};function o(l,e,r,p,h,d){return t(),i("div",null,e[0]||(e[0]=[a(`

    Changelog v3.5.0 (2025-01-28)

    Version 3.5.0 (2025-01-28)

    Full Changelog

    New Feature: PasskeyAuth (WebAuthn/FIDO2)

    Added WebAuthn/FIDO2 Passkey Authentication support, enabling phishing-resistant, passwordless authentication using device-native biometrics or PINs. This feature allows users to authenticate using passkeys stored on their devices without requiring any external authentication libraries.

    Bugfix: Response Compression for Static Files

    Fixed an issue where ResponseCompression middleware was not compressing static files served by AppStaticFileMiddleware. The middleware was setting Content-Length header before writing the response body, which prevented the compression middleware from compressing the response. Also added text/javascript to the default list of compressible MIME types.

    Added Client Integration Tests

    Added automated integration tests for NpgsqlRestClient configuration features to catch configuration bugs in the CI/CD pipeline:

    • ResponseCompression Tests - Verify compression works correctly for static files and API responses
    • CORS Tests - Verify CORS headers, preflight requests, and origin validation
    • StaticFiles Tests - Verify content parsing, claims replacement, and file serving

    Separate Core and Client Logging

    Added ability to configure separate log levels for the core NpgsqlRest library and the NpgsqlRestClient application. This allows fine-grained control over logging verbosity:

    json
    json
    "MinimalLevels": {
    +  "NpgsqlRest": "Information",
    +  "NpgsqlRestClient": "Debug",
    +  "System": "Warning",
    +  "Microsoft": "Warning"
    +}
    • NpgsqlRest - Controls log level for the core library (endpoint creation, SQL execution, etc.)
    • NpgsqlRestClient - Controls log level for the client application (configuration, authentication setup, passkeys, etc.)

    Debug Log Filtering Options

    Added two new boolean options to control debug-level logging verbosity:

    • DebugLogEndpointCreateEvents (default: true) - When false, suppresses "Created endpoint" debug logs
    • DebugLogCommentAnnotationEvents (default: true) - When false, suppresses comment annotation parsing debug logs

    These options allow users to reduce log noise while keeping the log level at Debug for other important information.


    `,20)]))}const u=s(n,[["render",o]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.5.0.md.COe0eWbp.lean.js b/assets/guide_changelog_v3.5.0.md.COe0eWbp.lean.js new file mode 100644 index 000000000..df7942ef9 --- /dev/null +++ b/assets/guide_changelog_v3.5.0.md.COe0eWbp.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as t,a5 as a}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Changelog v3.5.0 (2025-01-28)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.5.0.md","filePath":"guide/changelog/v3.5.0.md"}'),n={name:"guide/changelog/v3.5.0.md"};function o(l,e,r,p,h,d){return t(),i("div",null,e[0]||(e[0]=[a("",20)]))}const u=s(n,[["render",o]]);export{c as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.js b/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.js new file mode 100644 index 000000000..922f4461d --- /dev/null +++ b/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.js @@ -0,0 +1,246 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.6.0 (2025-02-01)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.0.md","filePath":"guide/changelog/v3.6.0.md"}'),l={name:"guide/changelog/v3.6.0.md"};function e(h,s,p,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t(`

    Changelog v3.6.0 (2025-02-01)

    Version 3.6.0 (2025-02-01)

    Full Changelog

    New Feature: Security Headers Middleware

    Added configurable security headers middleware to protect against common web vulnerabilities. The middleware adds HTTP security headers to all responses:

    • X-Content-Type-Options - Prevents MIME-sniffing attacks (default: nosniff)
    • X-Frame-Options - Prevents clickjacking attacks (default: DENY, skipped if Antiforgery is enabled)
    • Referrer-Policy - Controls referrer information (default: strict-origin-when-cross-origin)
    • Content-Security-Policy - Defines approved content sources (configurable)
    • Permissions-Policy - Controls browser feature access (configurable)
    • Cross-Origin-Opener-Policy - Controls document sharing with popups
    • Cross-Origin-Embedder-Policy - Controls cross-origin resource loading
    • Cross-Origin-Resource-Policy - Controls resource sharing cross-origin

    Configuration:

    jsonc
    jsonc
    //
    +// Security Headers: Adds HTTP security headers to all responses to protect against common web vulnerabilities.
    +// These headers instruct browsers how to handle your content securely.
    +// Note: X-Frame-Options is automatically handled by the Antiforgery middleware when enabled (see Antiforgery.SuppressXFrameOptionsHeader).
    +// Reference: https://owasp.org/www-project-secure-headers/
    +//
    +"SecurityHeaders": {
    +  //
    +  // Enable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    +  //
    +  "Enabled": false,
    +  //
    +  // X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content-type.
    +  // Recommended value: "nosniff"
    +  // Set to null to not include this header.
    +  //
    +  "XContentTypeOptions": "nosniff",
    +  //
    +  // X-Frame-Options: Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>.
    +  // Values: "DENY" (never allow), "SAMEORIGIN" (allow from same origin only)
    +  // Note: This header is SKIPPED if Antiforgery is enabled (Antiforgery already sets X-Frame-Options: SAMEORIGIN by default).
    +  // Set to null to not include this header.
    +  //
    +  "XFrameOptions": "DENY",
    +  //
    +  // Referrer-Policy: Controls how much referrer information should be included with requests.
    +  // Values: "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin",
    +  //         "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"
    +  // Recommended: "strict-origin-when-cross-origin" (send origin for cross-origin requests, full URL for same-origin)
    +  // Set to null to not include this header.
    +  //
    +  "ReferrerPolicy": "strict-origin-when-cross-origin",
    +  //
    +  // Content-Security-Policy: Defines approved sources of content that the browser may load.
    +  // Helps prevent XSS, clickjacking, and other code injection attacks.
    +  // Example: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +  // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    +  // Set to null to not include this header (recommended to configure based on your application needs).
    +  //
    +  "ContentSecurityPolicy": null,
    +  //
    +  // Permissions-Policy: Controls which browser features and APIs can be used.
    +  // Example: "geolocation=(), microphone=(), camera=()" disables these features entirely.
    +  // Example: "geolocation=(self), microphone=()" allows geolocation only from same origin.
    +  // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
    +  // Set to null to not include this header.
    +  //
    +  "PermissionsPolicy": null,
    +  //
    +  // Cross-Origin-Opener-Policy: Controls how your document is shared with cross-origin popups.
    +  // Values: "unsafe-none", "same-origin-allow-popups", "same-origin"
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginOpenerPolicy": null,
    +  //
    +  // Cross-Origin-Embedder-Policy: Prevents a document from loading cross-origin resources that don't explicitly grant permission.
    +  // Values: "unsafe-none", "require-corp", "credentialless"
    +  // Required for SharedArrayBuffer and high-resolution timers (along with COOP: same-origin).
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginEmbedderPolicy": null,
    +  //
    +  // Cross-Origin-Resource-Policy: Indicates how the resource should be shared cross-origin.
    +  // Values: "same-site", "same-origin", "cross-origin"
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginResourcePolicy": null
    +}

    New Feature: Forwarded Headers Middleware

    Added support for processing proxy headers when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.). This is critical for getting the correct client IP address and protocol.

    • X-Forwarded-For - Gets real client IP instead of proxy IP
    • X-Forwarded-Proto - Gets original protocol (http/https)
    • X-Forwarded-Host - Gets original host header

    Configuration:

    jsonc
    jsonc
    //
    +// Forwarded Headers: Enables the application to read proxy headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host).
    +// CRITICAL: Required when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.)
    +// Without this, the application sees the proxy's IP instead of the client's real IP, and HTTP instead of HTTPS.
    +// Security Warning: Only enable if you're behind a trusted proxy. Malicious clients can spoof these headers.
    +// Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
    +//
    +"ForwardedHeaders": {
    +  //
    +  // Enable forwarded headers middleware. Must be placed FIRST in the middleware pipeline.
    +  //
    +  "Enabled": false,
    +  //
    +  // Limits the number of proxy entries that will be processed from X-Forwarded-For.
    +  // Default is 1 (trust only the immediate proxy). Increase if you have multiple proxies in a chain.
    +  // Set to null to process all entries (not recommended for security).
    +  //
    +  "ForwardLimit": 1,
    +  //
    +  // List of IP addresses of known proxies to accept forwarded headers from.
    +  // Example: ["10.0.0.1", "192.168.1.1"]
    +  // If empty and KnownNetworks is also empty, forwarded headers are accepted from any source (less secure).
    +  //
    +  "KnownProxies": [],
    +  //
    +  // List of CIDR network ranges of known proxies.
    +  // Example: ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] for private networks
    +  // Useful when proxy IPs are dynamically assigned within a known range.
    +  //
    +  "KnownNetworks": [],
    +  //
    +  // List of allowed values for the X-Forwarded-Host header.
    +  // Example: ["example.com", "www.example.com"]
    +  // If empty, any host is allowed (less secure). Helps prevent host header injection attacks.
    +  //
    +  "AllowedHosts": []
    +}

    New Feature: Health Check Endpoints

    Added health check endpoints for container orchestration (Kubernetes, Docker Swarm) and monitoring systems:

    • /health - Overall health status (combines all checks)
    • /health/ready - Readiness probe with optional PostgreSQL connectivity check
    • /health/live - Liveness probe (always returns healthy if app is running)

    Configuration:

    jsonc
    jsonc
    //
    +// Health Checks: Provides endpoints for monitoring application health, used by container orchestrators (Kubernetes, Docker Swarm),
    +// load balancers, and monitoring systems to determine if the application is running correctly.
    +// Three types of checks are supported:
    +//   - /health: Overall health status (combines all checks)
    +//   - /health/ready: Readiness probe - is the app ready to accept traffic? (includes database connectivity)
    +//   - /health/live: Liveness probe - is the app process running? (always returns healthy if app responds)
    +// Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
    +//
    +"HealthChecks": {
    +  //
    +  // Enable health check endpoints.
    +  //
    +  "Enabled": false,
    +  //
    +  // Cache health check responses server-side in memory for the specified duration.
    +  // Cached responses are served without re-executing the endpoint. 
    +  // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +  // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +  //
    +  "CacheDuration": "5 seconds",
    +  //
    +  // Path for the main health check endpoint that reports overall status.
    +  // Returns "Healthy", "Degraded", or "Unhealthy" with HTTP 200 (healthy/degraded) or 503 (unhealthy).
    +  //
    +  "Path": "/health",
    +  //
    +  // Path for the readiness probe endpoint.
    +  // Kubernetes uses this to know when a pod is ready to receive traffic.
    +  // Includes database connectivity check when IncludeDatabaseCheck is true.
    +  // Returns 503 Service Unavailable if database is unreachable.
    +  //
    +  "ReadyPath": "/health/ready",
    +  //
    +  // Path for the liveness probe endpoint.
    +  // Kubernetes uses this to know when to restart a pod.
    +  // Always returns Healthy (200) if the application process is responding.
    +  // Does NOT check database - a slow database shouldn't trigger a container restart.
    +  //
    +  "LivePath": "/health/live",
    +  //
    +  // Include PostgreSQL database connectivity in health checks.
    +  // When true, the readiness probe will fail if the database is unreachable.
    +  //
    +  "IncludeDatabaseCheck": true,
    +  //
    +  // Name for the database health check (appears in detailed health reports).
    +  //
    +  "DatabaseCheckName": "postgresql",
    +  //
    +  // Require authentication for health check endpoints.
    +  // When true, all health endpoints require a valid authenticated user.
    +  // Security Consideration: Health endpoints can reveal information about your infrastructure
    +  // (database connectivity, service status). Enable this if your health endpoints are publicly accessible.
    +  // Note: Kubernetes/Docker health probes may need to authenticate if this is enabled.
    +  //
    +  "RequireAuthorization": false,
    +  //
    +  // Apply a rate limiter policy to health check endpoints.
    +  // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +  // Security Consideration: Prevents denial-of-service attacks targeting health endpoints.
    +  // Set to null to disable rate limiting on health endpoints.
    +  // Example: "fixed" or "bucket" (must match a policy name from RateLimiterOptions).
    +  //
    +  "RateLimiterPolicy": null
    +}

    Added new dependency: AspNetCore.HealthChecks.NpgSql for PostgreSQL health checks.

    New Feature: PostgreSQL Statistics Endpoints

    Added HTTP endpoints for monitoring PostgreSQL database statistics, useful for debugging, performance analysis, and operational monitoring:

    • /stats/routines - Function/procedure performance statistics from pg_stat_user_functions (call counts, execution times)
    • /stats/tables - Table statistics from pg_stat_user_tables (tuple counts, sizes, scan counts, vacuum info)
    • /stats/indexes - Index statistics from pg_stat_user_indexes (scan counts, definitions)
    • /stats/activity - Current database activity from pg_stat_activity (active sessions, queries, wait events)

    Output formats:

    • HTML (default) - HTML table with Excel-compatible formatting for direct browser copy-paste
    • JSON - JSON array with camelCase property names

    Configuration:

    jsonc
    jsonc
    //
    +// PostgreSQL Statistics Endpoints
    +// Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging.
    +// Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.
    +//
    +"Stats": {
    +  //
    +  // Enable PostgreSQL statistics endpoints.
    +  //
    +  "Enabled": false,
    +  //
    +  // Cache stats responses server-side in memory for the specified duration.
    +  // Cached responses are served without re-executing the endpoint.
    +  // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +  // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +  //
    +  "CacheDuration": "5 seconds",
    +  //
    +  // Apply a rate limiter policy to stats endpoints.
    +  // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +  // Set to null to disable rate limiting on stats endpoints.
    +  //
    +  "RateLimiterPolicy": null,
    +  //
    +  // Use a specific named connection for stats queries.
    +  // When null, uses the default connection string.
    +  // Useful when you want to query stats from a different database or use read-only credentials.
    +  //
    +  "ConnectionName": null,
    +  //
    +  // Require authentication for stats endpoints.
    +  // Security Consideration: Stats endpoints can reveal sensitive information about your database
    +  // (table sizes, query patterns, active sessions). Enable this for production environments.
    +  //
    +  "RequireAuthorization": false,
    +  //
    +  // Restrict access to specific roles.
    +  // When null or empty, any authenticated user can access (if RequireAuthorization is true).
    +  // Example: ["admin", "dba"] - only users with admin or dba role can access.
    +  //
    +  "AuthorizedRoles": [],
    +  //
    +  // Output format for stats endpoints: "json" or "html".
    +  // - json: JSON array
    +  // - html: HTML table, Excel-compatible for direct browser copy-paste (default)
    +  //
    +  "OutputFormat": "html",
    +  //
    +  // Filter schemas using PostgreSQL SIMILAR TO pattern.
    +  // When null, all schemas are included.
    +  // Example: "public|myapp%" - includes 'public' and schemas starting with 'myapp'.
    +  //
    +  "SchemaSimilarTo": null,
    +  //
    +  // Path for routine (function/procedure) performance statistics.
    +  // Returns data from pg_stat_user_functions including call counts and execution times.
    +  // Note: Requires track_functions = 'pl' or 'all' in postgresql.conf.
    +  // Enable with: alter system set track_functions = 'all'; select pg_reload_conf();
    +  // Or set track_functions = 'all' directly in postgresql.conf and restart/reload.
    +  //
    +  "RoutinesStatsPath": "/stats/routines",
    +  //
    +  // Path for table statistics.
    +  // Returns data from pg_stat_user_tables including tuple counts, sizes, scan counts, and vacuum info.
    +  //
    +  "TablesStatsPath": "/stats/tables",
    +  //
    +  // Path for index statistics.
    +  // Returns data from pg_stat_user_indexes including scan counts and index definitions.
    +  //
    +  "IndexesStatsPath": "/stats/indexes",
    +  //
    +  // Path for current database activity.
    +  // Returns data from pg_stat_activity showing active sessions, queries, and wait events.
    +  // Security Consideration: Shows currently running queries which may contain sensitive data.
    +  //
    +  "ActivityPath": "/stats/activity"
    +}
    `,26)]))}const y=i(l,[["render",e]]);export{d as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.lean.js b/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.lean.js new file mode 100644 index 000000000..93154807c --- /dev/null +++ b/assets/guide_changelog_v3.6.0.md.p7Q-OsI9.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as t}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Changelog v3.6.0 (2025-02-01)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.0.md","filePath":"guide/changelog/v3.6.0.md"}'),l={name:"guide/changelog/v3.6.0.md"};function e(h,s,p,r,k,o){return n(),a("div",null,s[0]||(s[0]=[t("",26)]))}const y=i(l,[["render",e]]);export{d as __pageData,y as default}; diff --git a/assets/guide_changelog_v3.6.1.md.CCDqHGJF.js b/assets/guide_changelog_v3.6.1.md.CCDqHGJF.js new file mode 100644 index 000000000..040918ec1 --- /dev/null +++ b/assets/guide_changelog_v3.6.1.md.CCDqHGJF.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.6.1 (2025-02-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.1.md","filePath":"guide/changelog/v3.6.1.md"}'),r={name:"guide/changelog/v3.6.1.md"};function n(s,e,l,h,c,d){return o(),a("div",null,e[0]||(e[0]=[i('

    Changelog v3.6.1 (2025-02-02)

    Version 3.6.1 (2025-02-02)

    Full Changelog

    Fixes

    • Fixed RequireAuthorization on Stats and Health endpoints to use manual authorization check consistent with NpgsqlRest endpoints.
    • Fixed ActivityQuery in Stats endpoints.
    • Fixed OutputFormat default value in Stats endpoints.

    ',6)]))}const p=t(r,[["render",n]]);export{u as __pageData,p as default}; diff --git a/assets/guide_changelog_v3.6.1.md.CCDqHGJF.lean.js b/assets/guide_changelog_v3.6.1.md.CCDqHGJF.lean.js new file mode 100644 index 000000000..bb9b268f4 --- /dev/null +++ b/assets/guide_changelog_v3.6.1.md.CCDqHGJF.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.6.1 (2025-02-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.1.md","filePath":"guide/changelog/v3.6.1.md"}'),r={name:"guide/changelog/v3.6.1.md"};function n(s,e,l,h,c,d){return o(),a("div",null,e[0]||(e[0]=[i("",6)]))}const p=t(r,[["render",n]]);export{u as __pageData,p as default}; diff --git a/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.js b/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.js new file mode 100644 index 000000000..44b54f3d8 --- /dev/null +++ b/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.js @@ -0,0 +1 @@ +import{_ as o,c as t,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.6.2 (2025-02-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.2.md","filePath":"guide/changelog/v3.6.2.md"}'),r={name:"guide/changelog/v3.6.2.md"};function s(i,e,l,c,d,p){return a(),t("div",null,e[0]||(e[0]=[n('

    Changelog v3.6.2 (2025-02-02)

    Version 3.6.2 (2025-02-02)

    Full Changelog

    Fixes

    • Fixed NestedJsonForCompositeTypes option from RoutineOptions not being applied to endpoints. Previously, only the nested comment annotation could enable nested JSON serialization for composite types. Now the global configuration option is properly applied as the default for all endpoints.

    • Fixed TypeScript client (NpgsqlRest.TsClient) generating incorrect types for composite columns when NestedJsonForCompositeTypes is false (the default). The client now correctly generates flat field types matching the actual JSON response structure, instead of always generating nested interfaces.

    Breaking Changes

    • Added NestedJsonForCompositeTypes property to IRoutineSource interface. Custom implementations of IRoutineSource will need to add this property.

    ',8)]))}const u=o(r,[["render",s]]);export{g as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.lean.js b/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.lean.js new file mode 100644 index 000000000..265d5f2fc --- /dev/null +++ b/assets/guide_changelog_v3.6.2.md.Lg_0h_OI.lean.js @@ -0,0 +1 @@ +import{_ as o,c as t,o as a,a5 as n}from"./chunks/framework.CgT1UzWm.js";const g=JSON.parse('{"title":"Changelog v3.6.2 (2025-02-02)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.2.md","filePath":"guide/changelog/v3.6.2.md"}'),r={name:"guide/changelog/v3.6.2.md"};function s(i,e,l,c,d,p){return a(),t("div",null,e[0]||(e[0]=[n("",8)]))}const u=o(r,[["render",s]]);export{g as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.6.3.md.CcLrwLcH.js b/assets/guide_changelog_v3.6.3.md.CcLrwLcH.js new file mode 100644 index 000000000..5201a9097 --- /dev/null +++ b/assets/guide_changelog_v3.6.3.md.CcLrwLcH.js @@ -0,0 +1 @@ +import{_ as a,c as r,o as t,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.6.3 (2025-02-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.3.md","filePath":"guide/changelog/v3.6.3.md"}'),s={name:"guide/changelog/v3.6.3.md"};function n(i,e,l,c,h,g){return t(),r("div",null,e[0]||(e[0]=[o('

    Changelog v3.6.3 (2025-02-03)

    Version 3.6.3 (2025-02-03)

    Full Changelog

    Fixes

    • Fixed ParseEnvironmentVariables feature not working for Kestrel configuration values. Previously, environment variable placeholders (e.g., {MY_HOST}) in Kestrel settings like Endpoints URLs, Certificate paths/passwords, and Limits were not being replaced because Kestrel uses ASP.NET Core's direct binding which bypassed the custom placeholder processing. Now all Kestrel configuration values properly support environment variable replacement when ParseEnvironmentVariables is enabled.

    ',6)]))}const u=a(s,[["render",n]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.6.3.md.CcLrwLcH.lean.js b/assets/guide_changelog_v3.6.3.md.CcLrwLcH.lean.js new file mode 100644 index 000000000..b6d0afda2 --- /dev/null +++ b/assets/guide_changelog_v3.6.3.md.CcLrwLcH.lean.js @@ -0,0 +1 @@ +import{_ as a,c as r,o as t,a5 as o}from"./chunks/framework.CgT1UzWm.js";const p=JSON.parse('{"title":"Changelog v3.6.3 (2025-02-03)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.6.3.md","filePath":"guide/changelog/v3.6.3.md"}'),s={name:"guide/changelog/v3.6.3.md"};function n(i,e,l,c,h,g){return t(),r("div",null,e[0]||(e[0]=[o("",6)]))}const u=a(s,[["render",n]]);export{p as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.js b/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.js new file mode 100644 index 000000000..2066e914c --- /dev/null +++ b/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.js @@ -0,0 +1,29 @@ +import{_ as e,c as a,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Changelog v3.7.0 (2025-02-07)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.7.0.md","filePath":"guide/changelog/v3.7.0.md"}'),l={name:"guide/changelog/v3.7.0.md"};function t(p,s,r,o,c,d){return n(),a("div",null,s[0]||(s[0]=[i(`

    Changelog v3.7.0 (2025-02-07)

    Version 3.7.0 (2025-02-07)

    Full Changelog

    Fixes

    • Fixed comma separator bug in Excel Upload Handler error response when processing multiple files. The fileId counter was not incremented on error, causing malformed JSON output when an invalid file was followed by additional files.

    • Fixed CustomHost configuration in ClientCodeGen not accepting an empty string value. Setting "CustomHost": "" was treated the same as null (triggering host auto-detection) because GetConfigStr uses string.IsNullOrEmpty. Now an explicit empty string correctly produces const baseUrl = ""; in generated TypeScript, which is useful for relative URL paths.

    New Features

    • Added fallback_handler parameter to the Excel Upload Handler. When set (e.g., fallback_handler = csv), if ExcelDataReader fails to parse an uploaded file (invalid Excel format), the handler automatically delegates processing to the named fallback handler. This allows a single upload endpoint to accept both Excel and CSV files transparently:
    sql
    sql
    comment on function my_upload(json) is '
    +@upload for excel
    +@fallback_handler = csv
    +@row_command = select process_row($1,$2)
    +';

    New Feature: Pluggable Table Format Renderers

    Added a pluggable table format rendering system that allows PostgreSQL function results to be rendered as HTML tables or Excel spreadsheet downloads instead of JSON, controlled by the @table_format annotation.

    HTML Table Format

    Renders results as a styled HTML table suitable for browser viewing and copy-paste into Excel:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = html
    +';

    Configuration options in TableFormatOptions: HtmlEnabled, HtmlKey, HtmlHeader, HtmlFooter.

    Excel Table Format

    Renders results as an .xlsx Excel spreadsheet download using the SpreadCheetah library (streaming, AOT-compatible):

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +';

    Configuration options in TableFormatOptions: ExcelEnabled, ExcelKey, ExcelSheetName, ExcelDateTimeFormat, ExcelNumericFormat.

    • ExcelDateTimeFormat — Excel Format Code for DateTime cells (default: yyyy-MM-dd HH:mm:ss). Examples: yyyy-mm-dd, dd/mm/yyyy hh:mm.
    • ExcelNumericFormat — Excel Format Code for numeric cells (default: General). Examples: #,##0.00, 0.00.

    Per-Endpoint Custom Parameters

    The download filename and worksheet name can be overridden per-endpoint via custom parameter annotations:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = monthly_report.xlsx
    +@excel_sheet = Report Data
    +';

    These also support dynamic placeholders resolved from function parameters:

    sql
    sql
    comment on function get_report(_format text, _file_name text, _sheet_name text) is '
    +HTTP GET
    +@table_format = {_format}
    +@excel_file_name = {_file_name}
    +@excel_sheet = {_sheet_name}
    +';

    TsClient: Per-Endpoint URL Export Control

    Added two new custom parameter annotations to control TypeScript client code generation per-endpoint:

    tsclient_export_url

    Overrides the global ExportUrls configuration setting for a specific endpoint:

    sql
    sql
    comment on function login(_username text, _password text) is '
    +HTTP POST
    +@login
    +@tsclient_export_url = true
    +';

    When enabled, the generated TypeScript exports a URL constant for that endpoint:

    typescript
    typescript
    export const loginUrl = () => baseUrl + "/api/login";

    tsclient_url_only

    When set, only the URL constant is exported — the fetch function and response type interface are skipped entirely. Implies tsclient_export_url = true:

    sql
    sql
    comment on function get_data(_format text) is '
    +HTTP GET
    +@table_format = {_format}
    +@tsclient_url_only = true
    +';

    This generates only the URL constant and request interface, which is useful for endpoints consumed via browser navigation (e.g., table format downloads) rather than fetch calls.


    `,36)]))}const u=e(l,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.lean.js b/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.lean.js new file mode 100644 index 000000000..fd063ec88 --- /dev/null +++ b/assets/guide_changelog_v3.7.0.md.BsBWtBOJ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const m=JSON.parse('{"title":"Changelog v3.7.0 (2025-02-07)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.7.0.md","filePath":"guide/changelog/v3.7.0.md"}'),l={name:"guide/changelog/v3.7.0.md"};function t(p,s,r,o,c,d){return n(),a("div",null,s[0]||(s[0]=[i("",36)]))}const u=e(l,[["render",t]]);export{m as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.8.0.md.D7bLiXbw.js b/assets/guide_changelog_v3.8.0.md.D7bLiXbw.js new file mode 100644 index 000000000..459c2b392 --- /dev/null +++ b/assets/guide_changelog_v3.8.0.md.D7bLiXbw.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.8.0 (2025-02-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.8.0.md","filePath":"guide/changelog/v3.8.0.md"}'),t={name:"guide/changelog/v3.8.0.md"};function l(o,a,r,d,p,c){return n(),s("div",null,a[0]||(a[0]=[i('

    Changelog v3.8.0 (2025-02-11)

    Version 3.8.0 (2025-02-11)

    Full Changelog

    New Feature: Configuration Key Validation

    Added startup validation that checks all configuration keys in appsettings.json against the known defaults schema. This catches typos and unknown keys that would otherwise be silently ignored (e.g., LogCommand instead of LogCommands).

    Controlled by the new Config:ValidateConfigKeys setting with three modes:

    • "Warning" (default) — logs warnings for unknown keys, startup continues.
    • "Error" — logs errors for unknown keys and exits the application.
    • "Ignore" — no validation.
    json
    json
    "Config": {\n  "ValidateConfigKeys": "Warning"\n}

    Example output:

    code
    [12:34:56 WRN] Unknown configuration key: NpgsqlRest:KebabCaselUrls

    Removed

    • Removed the Config:ExposeAsEndpoint option. Use the --config CLI switch to inspect configuration instead.

    Kestrel Configuration Validation

    Configuration key validation also covers the Kestrel section, checking against the known Kestrel schema including Limits, Http2, Http3, and top-level flags like DisableStringReuse and AllowSynchronousIO. User-defined endpoint and certificate names under Endpoints and Certificates remain open-ended and won't trigger warnings.

    Syntax Highlighted --config Output

    The --config CLI switch now outputs JSON with syntax highlighting (keys, strings, numbers/booleans, and structural characters in distinct colors). When output is redirected to a file, plain JSON is emitted without color codes. The --config switch can now appear anywhere in the argument list and be combined with config files and --key=value overrides.

    Improved CLI Error Handling

    Unknown command-line parameters now display a clear error message with a --help hint instead of an unhandled exception stack trace.

    Universal fallback_handler for All Upload Handlers

    The fallback_handler parameter, previously Excel-only, is now available on all upload handlers via BaseUploadHandler. When a handler's format validation fails and a fallback_handler is configured, processing is automatically delegated to the named fallback handler.

    This enables scenarios like: CSV format check fails on a binary file → fall back to large_object or file_system to save the raw file for analysis.

    sql
    sql
    comment on function my_csv_upload(json) is '\n@upload for csv\n@check_format = true\n@fallback_handler = large_object\n@row_command = select process_row($1,$2)\n';

    Optional Path Parameters

    Path parameters now support the ASP.NET Core optional parameter syntax {param?}. When a path parameter is marked as optional and the corresponding PostgreSQL function parameter has a default value, omitting the URL segment will use the PostgreSQL default:

    sql
    sql
    create function get_item(p_id int default 42) returns text ...\ncomment on function get_item(int) is '\nHTTP GET /items/{p_id?}\n';
    • GET /items/5 → uses the provided value 5
    • GET /items/ → uses the PostgreSQL default 42

    This also works with query_string_null_handling null_literal to pass NULL via the literal string "null" in the path for any parameter type:

    sql
    sql
    create function get_item(p_id int default null) returns text ...\ncomment on function get_item(int) is '\nHTTP GET /items/{p_id}\nquery_string_null_handling null_literal\n';
    • GET /items/null → passes SQL NULL to the function

    Fixes

    • Fixed query string overload resolution not accounting for path parameters. GET endpoints with path parameters and overloaded functions (same name, different signatures) would resolve to the wrong function. The body JSON overload resolution already handled this correctly.
    • Added missing QueryStringNullHandling and TextResponseNullHandling entries to ConfigDefaults, which caused them to be absent from --config output.
    • Added missing Pattern, MinLength, and MaxLength properties to default validation rule schemas in ConfigDefaults.

    Machine-Readable CLI Commands for Tool Integration

    Added new CLI commands designed for programmatic consumption by tools like pgdev. All JSON-outputting commands use syntax highlighting when run in a terminal and emit plain JSON when piped or redirected.

    --version --json

    Outputs version information as structured JSON including all assembly versions, runtime, platform RID, and directories:

    code
    npgsqlrest --version --json

    --validate [--json]

    Pre-flight check that validates configuration keys against known defaults and tests the database connection, then exits with code 0 (success) or 1 (failure):

    code
    npgsqlrest --validate\nnpgsqlrest --validate --json

    --config-schema

    Outputs a JSON Schema (draft-07) describing the full appsettings.json configuration structure — types, defaults, and enum constraints. Can be used for IDE autocomplete via the $schema property or as the foundation for config editing UIs:

    code
    npgsqlrest --config-schema

    --annotations

    Outputs all 44 supported SQL comment annotations as a JSON array with name, aliases, syntax, and description for each:

    code
    npgsqlrest --annotations

    --endpoints

    Connects to the database, discovers all generated REST endpoints, outputs full metadata (method, path, routine info, parameters, return columns, authorization, custom parameters), then exits. Logging is suppressed to keep output clean:

    code
    npgsqlrest --endpoints

    --config (updated)

    The --config --json flag has been removed. The --config command now always uses automatic detection: syntax highlighted in terminal, plain JSON when output is piped or redirected.

    Stats Endpoints: format Query String Override

    Stats endpoints now accept an optional format query string parameter that overrides the configured Stats:OutputFormat setting per-request. Valid values are html and json.

    code
    GET /api/stats/routines?format=json\nGET /api/stats/tables?format=html

    ',54)]))}const m=e(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/guide_changelog_v3.8.0.md.D7bLiXbw.lean.js b/assets/guide_changelog_v3.8.0.md.D7bLiXbw.lean.js new file mode 100644 index 000000000..1febc42d7 --- /dev/null +++ b/assets/guide_changelog_v3.8.0.md.D7bLiXbw.lean.js @@ -0,0 +1 @@ +import{_ as e,c as s,o as n,a5 as i}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"Changelog v3.8.0 (2025-02-11)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.8.0.md","filePath":"guide/changelog/v3.8.0.md"}'),t={name:"guide/changelog/v3.8.0.md"};function l(o,a,r,d,p,c){return n(),s("div",null,a[0]||(a[0]=[i("",54)]))}const m=e(t,[["render",l]]);export{u as __pageData,m as default}; diff --git a/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.js b/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.js new file mode 100644 index 000000000..8a4407d38 --- /dev/null +++ b/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.js @@ -0,0 +1 @@ +import{_ as n,c as t,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"Changelog v3.9.0 (2026-02-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.9.0.md","filePath":"guide/changelog/v3.9.0.md"}'),s={name:"guide/changelog/v3.9.0.md"};function i(c,e,r,d,l,p){return a(),t("div",null,e[0]||(e[0]=[o('

    Changelog v3.9.0 (2026-02-23)

    Version 3.9.0 (2026-02-23)

    Full Changelog

    Commented Configuration Output (--config)

    The --config output now includes inline JSONC comments with descriptions for every setting, matching the appsettings.json file exactly. This makes it easy to understand what each setting does without consulting the documentation. The default configuration file can be constructed with:

    code
    npgsqlrest --config > appsettings.json

    Configuration Search and Filter (--config [filter])

    Added an optional filter argument to --config that searches keys, comments, and values (case-insensitive) and returns only matching settings as valid JSONC:

    code
    npgsqlrest --config cors\nnpgsqlrest --config=timeout\nnpgsqlrest --config minworker

    Output preserves the full section hierarchy so it can be copy-pasted directly into appsettings.json. When a key inside a section matches, the parent section wrapper is included. When a section name or its comment matches, the entire section is shown. Matched terms are highlighted with inverted colors in the terminal; piped output is plain text.

    CLI Improvements

    • Case-insensitive config overrides: Command-line config overrides like --Applicationname=test now correctly update the existing ApplicationName key instead of creating a duplicate entry with different casing.
    • Config validation on --config: The --config command now validates configuration keys before dumping. Unknown keys (e.g., --xxx=test) produce an error on stderr and exit with code 1.
    • Redirected output fix: Formatted CLI output (--help, --version) no longer crashes when stdout is redirected (e.g., piped or captured by a parent process).
    • CLI test suite: Added process-based tests for all CLI commands (--help, --version, --hash, --basic_auth, --config-schema, --annotations, --config, --config [filter], invalid args).

    ',13)]))}const u=n(s,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.lean.js b/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.lean.js new file mode 100644 index 000000000..ba45f11e4 --- /dev/null +++ b/assets/guide_changelog_v3.9.0.md.Cm0S1Ft1.lean.js @@ -0,0 +1 @@ +import{_ as n,c as t,o as a,a5 as o}from"./chunks/framework.CgT1UzWm.js";const h=JSON.parse('{"title":"Changelog v3.9.0 (2026-02-23)","description":"","frontmatter":{},"headers":[],"relativePath":"guide/changelog/v3.9.0.md","filePath":"guide/changelog/v3.9.0.md"}'),s={name:"guide/changelog/v3.9.0.md"};function i(c,e,r,d,l,p){return a(),t("div",null,e[0]||(e[0]=[o("",13)]))}const u=n(s,[["render",i]]);export{h as __pageData,u as default}; diff --git a/assets/guide_configuration.md.CAstKOPi.js b/assets/guide_configuration.md.CAstKOPi.js new file mode 100644 index 000000000..74924dd11 --- /dev/null +++ b/assets/guide_configuration.md.CAstKOPi.js @@ -0,0 +1,185 @@ +import{_ as t,C as l,c as p,o as h,a5 as i,j as a,a as n,G as r}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Configuration Guide","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments. Learn about configuration precedence, default values, and best practices.","frontmatter":{"outline":[2,3],"title":"Configuration Guide","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments. Learn about configuration precedence, default values, and best practices.","head":[["meta",{"name":"keywords","content":"npgsqlrest configuration, appsettings.json postgresql, rest api server config, npgsqlrest settings, postgresql api configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Configuration Guide"}],["meta",{"property":"og:description","content":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/configuration.md","filePath":"guide/configuration.md"}'),k={name:"guide/configuration.md"},o={id:"optional-and-required-placeholders",tabindex:"-1"};function d(c,s,g,u,y,m){const e=l("Badge");return h(),p("div",null,[s[3]||(s[3]=i(`

    Configuration Guide

    NpgsqlRest can be configured through multiple sources, each with different precedence levels. Common configuration sources are configuration files, usually different versions for different environments, environment variables, and command-line arguments.

    Configuration Sources

    NpgsqlRest reads configuration from the following sources (in order of precedence, lowest to highest):

    1. Configuration files (appsettings.json, then appsettings.Development.json)
    2. Command-line arguments

    Environment variables can be referenced in configuration values using {VARIABLE_NAME} syntax. This works in both configuration files and command-line arguments.

    Use command-line arguments to override any configuration value.

    Default Values

    If a configuration value is not explicitly set in any source, NpgsqlRest uses the default value. To see all defaults with inline descriptions, use the --config command:

    bash
    bash
    npgsqlrest --config > appsettings.json

    This generates a fully commented JSONC file with descriptions for every setting, ready to use as your configuration file. See Exploring Configuration below for more on --config.

    Configuration Files

    Default Configuration Files

    By default, NpgsqlRest loads configuration files from the current working directory in this order:

    1. appsettings.json
    2. appsettings.Development.json (overrides values from the first)

    Both files are optional — no error occurs if either is missing. You can specify additional or alternative configuration files using command-line arguments.

    bash
    bash
    # Use default appsettings.json and/or appsettings.Development.json from current directory
    +npgsqlrest
    +
    +# Specify a custom configuration file
    +npgsqlrest appsettings.production.json
    +
    +# Load multiple configuration files (later files override earlier ones)
    +npgsqlrest appsettings.json appsettings.production.json appsettings.local.json

    Optional Configuration Files

    Use the -o or --optional switch to mark configuration files as optional. Optional files won't cause an error if they don't exist:

    bash
    bash
    # appsettings.local.json is optional - no error if missing
    +npgsqlrest appsettings.json -o appsettings.local.json
    +
    +# Multiple optional files
    +npgsqlrest appsettings.json --optional development.json --optional local.json

    Configuration File Format

    Configuration files use standard JSON format with support for comments:

    json
    json
    {
    +  // Application identification
    +  "ApplicationName": "MyApi",
    +
    +  // Database connection
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Database=mydb;Username=user;Password=pass"
    +  },
    +
    +  // NpgsqlRest options
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/api",
    +    "RequiresAuthorization": false
    +  }
    +}

    Environment Variables

    By default, environment variable binding is disabled. Instead, environment variables can be referenced in configuration values using {VARIABLE_NAME} syntax:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={DB_HOST};Database={DB_NAME};Username={DB_USER};Password={DB_PASS}"
    +  }
    +}

    This works in both configuration files and command-line arguments.

    For non-string configuration values, use the quoted form "{VARIABLE_NAME}" in the configuration file. The value will be automatically parsed to the appropriate type:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CommandTimeout": "{COMMAND_TIMEOUT}",
    +    "RequiresAuthorization": "{REQUIRES_AUTH}"
    +  }
    +}
    `,29)),a("h3",o,[s[0]||(s[0]=n("Optional and Required Placeholders ",-1)),r(e,{type:"tip",text:"3.17.0+"}),s[1]||(s[1]=n()),s[2]||(s[2]=a("a",{class:"header-anchor",href:"#optional-and-required-placeholders","aria-label":'Permalink to "Optional and Required Placeholders "'},"​",-1))]),s[4]||(s[4]=i(`

    Placeholders come in two forms, supported for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not set — so typed bool/int reads fall back to their default instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.
    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)
    +"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Placeholder parsing is controlled by Config:ParseEnvironmentVariables (enabled by default).

    Enabling Environment Variable Binding

    To enable automatic environment variable binding (where variables override configuration values directly), set AddEnvironmentVariables to true:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": true
    +  }
    +}

    When enabled, environment variables can override any configuration value. The naming convention uses double underscores (__) to represent JSON hierarchy levels:

    bash
    bash
    # Override a top-level setting
    +export ApplicationName=MyApi
    +
    +# Override nested settings (use __ for hierarchy)
    +export ConnectionStrings__Default="Host=localhost;Database=mydb"
    +export NpgsqlRest__UrlPathPrefix="/api/v2"
    +export NpgsqlRest__RequiresAuthorization=true
    +
    +# Then run
    +npgsqlrest

    Environment Variable Naming Rules

    JSON PathEnvironment Variable
    ApplicationNameApplicationName
    ConnectionStrings.DefaultConnectionStrings__Default
    NpgsqlRest.UrlPathPrefixNpgsqlRest__UrlPathPrefix
    Auth.CookieAuth.EnabledAuth__CookieAuth__Enabled

    Command-Line Arguments

    Command-line arguments have the highest precedence and override all other configuration sources. Use the --key=value syntax:

    bash
    bash
    # Override settings via command line
    +npgsqlrest --ApplicationName=MyApi --NpgsqlRest:UrlPathPrefix=/api/v2
    +
    +# Override connection string
    +npgsqlrest --ConnectionStrings:Default="Host=localhost;Database=mydb"
    +
    +# Combine with configuration files
    +npgsqlrest appsettings.json --NpgsqlRest:RequiresAuthorization=false

    Command-Line Syntax Rules

    • Use --key=value format
    • Use colons (:) to separate hierarchy levels (alternative to __)
    • Keys are case-insensitive — overrides match the existing key regardless of casing (e.g., --applicationname=test correctly updates ApplicationName)
    • Boolean values: true, false, 1, 0
    bash
    bash
    # These are all equivalent
    +npgsqlrest --npgsqlrest:urlpathprefix=/api
    +npgsqlrest --NpgsqlRest:UrlPathPrefix=/api
    +npgsqlrest --NPGSQLREST:URLPATHPREFIX=/api

    Exploring Configuration

    The --config CLI command helps you discover and understand all available settings without consulting documentation. Standard configuration files and --key=value overrides can appear before the --config switch — this lets you inspect the effective configuration for a given setup.

    Generating a Default Configuration File

    Running --config with no arguments outputs the full default configuration as JSONC with inline comments describing every setting:

    bash
    bash
    npgsqlrest --config

    Redirect the output to create a ready-to-use configuration file:

    bash
    bash
    npgsqlrest --config > appsettings.json

    Include config files and overrides (case-insensitive) to see their effect on the output:

    bash
    bash
    npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config

    The output is syntax-highlighted in the terminal; when piped or redirected, plain JSONC is emitted.

    Searching for Settings

    Pass a filter argument to --config to search across setting names, comments, and values (case-insensitive):

    bash
    bash
    npgsqlrest --config cors
    +npgsqlrest --config=timeout
    +npgsqlrest --config minworker

    The output preserves the full section hierarchy so it can be copy-pasted directly into appsettings.json. When a key inside a section matches, its parent section is included. When a section name or its comment matches, the entire section is shown. Matched terms are highlighted in the terminal.

    This also works with config files and case-insensitive overrides:

    bash
    bash
    npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config timeout

    Configuration Validation

    The --config command validates all configuration keys before producing output. Unknown keys (e.g., --xxx=test) produce an error on stderr and exit with code 1, helping you catch typos early.

    Configuration Precedence Example

    Consider this scenario with multiple configuration sources:

    appsettings.json:

    json
    json
    {
    +  "ApplicationName": "DefaultApp",
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/api",
    +    "RequiresAuthorization": true
    +  }
    +}

    Environment variables:

    bash
    bash
    export NpgsqlRest__UrlPathPrefix="/api/v2"

    Command line:

    bash
    bash
    npgsqlrest --NpgsqlRest:RequiresAuthorization=false

    Resulting configuration:

    SettingValueSource
    ApplicationName"DefaultApp"appsettings.json
    NpgsqlRest.UrlPathPrefix"/api/v2"Environment variable
    NpgsqlRest.RequiresAuthorizationfalseCommand line

    Quick Reference

    Common Command-Line Overrides

    bash
    bash
    # Database connection
    +npgsqlrest --ConnectionStrings:Default="Host=localhost;Database=mydb;Username=user;Password=pass"
    +
    +# Change listening URL
    +npgsqlrest --Urls="http://localhost:8080"
    +
    +# Disable authorization for development
    +npgsqlrest --NpgsqlRest:RequiresAuthorization=false
    +
    +# Set log level
    +npgsqlrest --Log:MinimalLevels:NpgsqlRest=Debug

    Exploring Configuration

    bash
    bash
    # Generate a fully commented default configuration file
    +npgsqlrest --config > appsettings.json
    +
    +# Search for settings related to a topic
    +npgsqlrest --config cors
    +npgsqlrest --config=timeout
    +
    +# Inspect effective configuration with overrides applied (case-insensitive)
    +npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config

    Configuration Structure Overview

    This section provides a complete overview of the NpgsqlRest configuration file structure.

    json
    json
    {
    +  // Application Identification
    +  "ApplicationName": null,
    +  "EnvironmentName": "Production",
    +  "Urls": "http://localhost:8080",
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}",
    +
    +  // Configuration Options
    +  "Config": { ... },
    +
    +  // Database Connections
    +  "ConnectionStrings": { ... },
    +  "ConnectionSettings": { ... },
    +
    +  // Server & SSL
    +  "Ssl": { ... },
    +  "Kestrel": { ... },
    +
    +  // Security
    +  "DataProtection": { ... },
    +  "Auth": { ... },
    +  "Antiforgery": { ... },
    +
    +  // Threading
    +  "ThreadPool": { ... },
    +
    +  // Logging
    +  "Log": { ... },
    +
    +  // Performance & Features
    +  "ResponseCompression": { ... },
    +  "StaticFiles": { ... },
    +  "Cors": { ... },
    +  "CommandRetryOptions": { ... },
    +  "CacheOptions": { ... },
    +  "RateLimiterOptions": { ... },
    +
    +  // Error Handling
    +  "ErrorHandlingOptions": { ... },
    +
    +  // Core API Options
    +  "NpgsqlRest": {
    +    // Connection & Query Settings
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false,
    +    "CommandTimeout": null,
    +
    +    // Schema & Name Filtering
    +    "SchemaSimilarTo": null,
    +    "SchemaNotSimilarTo": null,
    +    "IncludeSchemas": [],
    +    "ExcludeSchemas": [],
    +    "NameSimilarTo": null,
    +    "NameNotSimilarTo": null,
    +    "IncludeNames": [],
    +    "ExcludeNames": [],
    +
    +    // URL & Naming Options
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "CommentsMode": "OnlyWithHttpTag",
    +
    +    // Request Handling
    +    "DefaultHttpMethod": null,
    +    "DefaultRequestParamType": null,
    +    "RequiresAuthorization": false,
    +
    +    // Request Headers
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersContextKey": "request.headers",
    +    "RequestHeadersParameterName": "_headers",
    +    "InstanceIdRequestHeaderName": null,
    +    "CustomRequestHeaders": [],
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +
    +    // Server-Sent Events
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": { ... },
    +
    +    // Logging
    +    "LogConnectionNoticeEvents": false,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +
    +    // Nested Configuration Objects
    +    "RoutineOptions": { ... },
    +    "UploadOptions": { ... },
    +    "AuthenticationOptions": { ... },
    +    "HttpFileOptions": { ... },
    +    "OpenApiOptions": { ... },
    +    "ClientCodeGen": { ... }
    +  }
    +}

    Top-Level Settings

    These settings configure the application identity and server binding.

    SettingTypeDefaultDescription
    ApplicationNamestringnullApplication identifier. Defaults to the top-level directory name if not set.
    EnvironmentNamestring"Production"Environment designation (Development, Staging, Production).
    Urlsstring"http://localhost:8080"Server listening URLs. Separate multiple URLs with semicolons.
    StartupMessagestring"Started in {time}, listening on {urls}, version {version}"Message displayed on startup. Supports placeholders.

    Urls Configuration

    The Urls setting accepts multiple URLs separated by semicolons:

    json
    json
    {
    +  "Urls": "http://localhost:8080;https://localhost:8443"
    +}

    To listen on all interfaces:

    json
    json
    {
    +  "Urls": "http://0.0.0.0:8080;https://0.0.0.0:8443"
    +}

    Startup Message

    Customize the startup message with placeholders:

    json
    json
    {
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}, env: {environment}"
    +}

    Available placeholders:

    • {time} - Startup time
    • {urls} - Listening URLs
    • {version} - Application version
    • {environment} - Environment name (from EnvironmentName)
    • {application} - Application name (from ApplicationName)

    Config Section Options

    The Config section controls how the configuration file itself is processed, including environment variable handling and configuration key validation.

    See the Config Section Reference for complete documentation on:

    • Environment variable overrides
    • Environment variable parsing with {ENV_VAR} syntax
    • Loading variables from .env files
    • Configuration key validation at startup

    Next Steps

    `,72))])}const C=t(k,[["render",d]]);export{F as __pageData,C as default}; diff --git a/assets/guide_configuration.md.CAstKOPi.lean.js b/assets/guide_configuration.md.CAstKOPi.lean.js new file mode 100644 index 000000000..3374a812f --- /dev/null +++ b/assets/guide_configuration.md.CAstKOPi.lean.js @@ -0,0 +1 @@ +import{_ as t,C as l,c as p,o as h,a5 as i,j as a,a as n,G as r}from"./chunks/framework.CgT1UzWm.js";const F=JSON.parse('{"title":"Configuration Guide","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments. Learn about configuration precedence, default values, and best practices.","frontmatter":{"outline":[2,3],"title":"Configuration Guide","titleTemplate":"NpgsqlRest","description":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments. Learn about configuration precedence, default values, and best practices.","head":[["meta",{"name":"keywords","content":"npgsqlrest configuration, appsettings.json postgresql, rest api server config, npgsqlrest settings, postgresql api configuration"}],["meta",{"property":"og:title","content":"NpgsqlRest Configuration Guide"}],["meta",{"property":"og:description","content":"Configure NpgsqlRest using JSON files, environment variables, and command-line arguments."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/configuration.md","filePath":"guide/configuration.md"}'),k={name:"guide/configuration.md"},o={id:"optional-and-required-placeholders",tabindex:"-1"};function d(c,s,g,u,y,m){const e=l("Badge");return h(),p("div",null,[s[3]||(s[3]=i("",29)),a("h3",o,[s[0]||(s[0]=n("Optional and Required Placeholders ",-1)),r(e,{type:"tip",text:"3.17.0+"}),s[1]||(s[1]=n()),s[2]||(s[2]=a("a",{class:"header-anchor",href:"#optional-and-required-placeholders","aria-label":'Permalink to "Optional and Required Placeholders "'},"​",-1))]),s[4]||(s[4]=i("",72))])}const C=t(k,[["render",d]]);export{F as __pageData,C as default}; diff --git a/assets/guide_faq.md.Bxla4fTX.js b/assets/guide_faq.md.Bxla4fTX.js new file mode 100644 index 000000000..bfe5f56f1 --- /dev/null +++ b/assets/guide_faq.md.Bxla4fTX.js @@ -0,0 +1,39 @@ +import{_ as s,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"FAQ & Troubleshooting","titleTemplate":"NpgsqlRest","description":"Frequently asked questions about NpgsqlRest: missing endpoints, 404s, parameter naming, authentication, testing, performance, logging, and troubleshooting.","frontmatter":{"outline":[2,3],"title":"FAQ & Troubleshooting","titleTemplate":"NpgsqlRest","description":"Frequently asked questions about NpgsqlRest: missing endpoints, 404s, parameter naming, authentication, testing, performance, logging, and troubleshooting.","head":[["meta",{"name":"keywords","content":"npgsqlrest faq, npgsqlrest troubleshooting, endpoint not found, postgresql rest api questions, npgsqlrest 404, sql injection, npgsqlrest testing"}],["meta",{"property":"og:title","content":"NpgsqlRest FAQ & Troubleshooting"}],["meta",{"property":"og:description","content":"Frequently asked questions about NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/faq.md","filePath":"guide/faq.md"}'),n={name:"guide/faq.md"};function o(l,e,r,h,p,d){return i(),a("div",null,e[0]||(e[0]=[t(`

    FAQ & Troubleshooting

    General

    What is NpgsqlRest?

    NpgsqlRest is a self-contained executable that connects to PostgreSQL and automatically creates REST API endpoints from plain SQL script files, database functions, and procedures. No code generation step, no ORM — just SQL.

    What PostgreSQL versions are supported?

    PostgreSQL 13 and newer. Discovery uses standard pg_catalog views that are stable across PostgreSQL versions.

    What .NET version is required?

    None, for most users — the standalone executable and the Docker image are fully self-contained (AOT-compiled). The NuGet library targets .NET 10.

    Is it safe from SQL injection?

    Yes, by construction. Client-supplied values are always sent as PostgreSQL protocol parameters — never concatenated into SQL text. The SQL that runs is the SQL you wrote in the file, function, or procedure; a request can only choose parameter values, never SQL fragments. There is no query-building layer to inject into (unlike REST-to-SQL translators that construct queries from URLs).

    How does NpgsqlRest compare to PostgREST or Supabase?

    See the detailed comparison blog post for a full feature-by-feature breakdown. The short version: PostgREST turns your tables into an API you query from the client; NpgsqlRest turns your SQL (files, functions, procedures) into an API you design.

    Can I use it inside an existing ASP.NET Core application?

    Yes — the standalone executable is a wrapper around the NpgsqlRest NuGet middleware. In your own app: app.UseNpgsqlRest(new NpgsqlRestOptions(connectionString) { ... }). The plugins (SQL file source, TypeScript client, OpenAPI, MCP) are separate NuGet packages.


    Installation & Setup

    How do I install NpgsqlRest?

    See the Installation Guide — standalone executable (Linux/macOS/Windows), an npm package (npm i npgsqlrest), a Docker image, or NuGet.

    How do I run it in Docker?

    sh
    sh
    docker run --name npgsqlrest -p 8080:8080 \\
    +  -v ./appsettings.json:/app/appsettings.json \\
    +  vbilopav/npgsqlrest:latest

    Remember that localhost inside the container is the container — point the connection string at host.docker.internal (or the compose service name) to reach your database.

    How do I connect to my database?

    Set the connection string in appsettings.json:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"
    +  }
    +}

    See Connection Settings for all options.

    Can I use environment variables for configuration?

    Yes. With ParseEnvironmentVariables in the Config section (enabled by default), use {ENV_VAR_NAME} placeholders in configuration values — optional by default, or {!ENV_VAR_NAME} for required (startup error when unset, since 3.17.0). An .env file is supported via the EnvFile option. Any setting can also be overridden on the command line: npgsqlrest --npgsqlrest:urlpathprefix=/v1.


    Endpoints

    My function doesn't appear as an endpoint

    Check these causes, most common first:

    1. No HTTP annotation — since 3.17.0 the client defaults to CommentsMode: "OnlyAnnotated": a routine becomes an endpoint only if its comment contains an HTTP annotation (or a plugin annotation like @mcp). Add one — comment on function my_func() is 'HTTP GET'; — or set CommentsMode: "ParseAll" to expose everything discovered.
    2. Schema not included: by default only the public schema is scanned. Adjust SchemaSimilarTo in NpgsqlRest Options.
    3. Insufficient privileges: the connection's database user needs EXECUTE on the function and USAGE on the schema.
    4. A @disabled annotation on the routine.
    5. Check the logs: run with the NpgsqlRest log level at Debug to see what was discovered and skipped.

    My SQL file doesn't appear as an endpoint

    Same CommentsMode rule as functions — the file needs an HTTP annotation by default. Two additional file-specific causes:

    1. SkipPattern — files matching SqlFileSource.SkipPattern (default "*.test.sql") are excluded from endpoint discovery; they're test files for the test runner.
    2. A describe error with ErrorMode: "Skip" — the file failed type-checking against the database and was skipped with a logged error. (ErrorMode: "Exit", the default, would have stopped startup and shown it.)

    An endpoint exists but I get 404 — why?

    A 404 for an existing path is almost always parameter matching: a request must supply values for all parameters without defaults, with matching names — otherwise no endpoint matches and the response is 404 (not 400). Check:

    1. Parameter names are convertedp_user_id becomes pUserId with the default camelCase converter. The generated TypeScript client or HTTP file always shows the exact names.
    2. Missing required parameter — give it a default (@param name default null in SQL files, DEFAULT in function signatures) to make it optional.
    3. The path prefix — the full path includes UrlPathPrefix (default /api).
    4. The HTTP methodselect files/functions map to GET by default; mutations map to PUT/POST/DELETE.

    Why are parameter and column names camelCased? How do I turn that off?

    The default NameConverter converts snake_case PostgreSQL names to camelCase JSON/URL names. Set "CamelCaseNames": false in the NpgsqlRest section to keep names exactly as they are in the database.

    My query returns one row — why do I get an array?

    Endpoints return arrays by default. Annotate with @single to return the first row as a single JSON object, or combine with @nested for composite shapes. A single-column result set returns a flat value array (["a","b"]) — that's the UnnamedSingleColumnSet default in SQL File Source.

    How do I return plain text, HTML, or CSV instead of JSON?

    Use @raw — the column values are written to the response verbatim, with @separator and @new_line for delimiter control, plus a Content-Type response header:

    sql
    sql
    -- HTTP GET
    +-- @raw
    +-- @separator ,
    +-- @new_line \\n
    +-- Content-Type: text/csv
    +select id, name, price from products;

    There is also a table format output mode for ready-made HTML tables and Excel exports.

    How do I customize the endpoint URL path?

    Use the @path annotation — -- @path /custom/path in a SQL file, or the same line in a function comment. Versioning works the same way: @path /v2/orders.

    How do I restrict access to an endpoint?

    Use @authorize, optionally with roles: -- @authorize admin, manager. Anything without @authorize is public unless you flip the global RequiresAuthorization option — then everything requires auth and @allow_anonymous opts out per endpoint.

    Can I expose tables and views directly, without writing any SQL?

    That's deliberately not the default model — NpgsqlRest wants you to design the API surface. The closest thing is a one-line SQL file per operation (select * from my_view; is a complete endpoint file). If you want fully automatic table CRUD, the NpgsqlRest.CrudSource NuGet plugin exists for library users, but plain SQL files are the recommended path.


    Parameters

    Named or positional parameters in SQL files — which should I use?

    Named (:name, since 3.19.0) for almost everything — the placeholder is the parameter name, so no @param naming annotations are needed, and the same name used repeatedly (even across statements) is one parameter:

    sql
    sql
    -- HTTP GET
    +select id, title from reports
    +where created_at between :from_date and :to_date;

    GET /api/get-reports?fromDate=...&toDate=... — done. Positional ($1, $2) remains fully supported; one style per file. See SQL File Endpoints — Parameters.

    How do I make a parameter optional?

    Give it a default. Functions: the native DEFAULT clause. SQL files: the @param annotation — -- @param status default 'active' or -- @param label default null. A parameter without a default is required, and a request missing it gets a 404 (no matching endpoint).

    How do I get the authenticated user's ID into a query?

    Enable claim-to-parameter mapping with @user_parameters and use a parameter whose name matches a claim mapping (default: _user_id → the user-id claim). With named parameters this needs nothing else:

    sql
    sql
    -- HTTP GET
    +-- @authorize
    +-- @user_parameters
    +select id, total, status
    +from orders
    +where user_id = :_user_id;

    The value comes from the authenticated principal — the client cannot send or override it. (With positional parameters, add -- @param $1 _user_id to give $1 the mapped name.)

    Error: "could not determine data type of parameter"

    PostgreSQL couldn't infer the parameter's type from context (classic case: select set_config('key', :value, true)). Give it a type hint: -- @param :value text (or -- @param $1 value text positionally), or add an inline cast in the SQL (:value::text).


    Authentication

    What authentication methods are supported?

    Cookie-based auth, JWT Bearer tokens, Microsoft Bearer tokens, HTTP Basic Auth, Passkeys/WebAuthn (FIDO2), and external OAuth providers (Google, GitHub, LinkedIn, Microsoft, Facebook). All can be enabled simultaneously. See Authentication config.

    How do I set up JWT authentication?

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtExpire": "60 minutes"
    +  }
    +}

    See the Multiple Auth Schemes blog post for a complete walkthrough including login endpoints and RBAC.


    Testing

    How do I test my endpoints?

    With the built-in SQL test runner (since 3.19.0): write tests as plain .sql files and run npgsqlrest ./config.json --test. A test inserts fixtures, invokes a real endpoint in-process (full pipeline: routing, auth, parameter binding, serialization), asserts on the captured response with ordinary SQL, and rolls back — endpoints see the test's uncommitted data because they run on the test's own connection and transaction.

    sql
    sql
    begin;
    +insert into users (email) values ('x@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200, 'authenticated caller gets 200' from _response;
    +
    +rollback;

    Can tests run against a temporary database instead of my real one?

    Yes — that's the recommended CI setup. Setup steps create (and Teardown drops) a uniquely named database (app_test_{rnd5}), migrations run as a step, and TestRunner.ConnectionName points the whole run at it. Template databases give per-test clones for perfect isolation. See the scenario catalog in the Testing Guide.

    My test fixtures need half the database inserted first — is there a better way?

    Yes, and it's pure PostgreSQL: declare your foreign keys deferrable, then start the test with set constraints all deferred;. Deferred constraints are checked at COMMIT — and a test that ends in rollback never commits, so you can insert only the rows the test is about, in any order, referencing rows that don't exist. No fixture factories, no dependency-ordered setup. See the technique in the Testing Guide.

    Is there a watch mode?

    Two, with one flag. npgsqlrest ... --test --watch re-runs tests on changes — a changed test re-runs alone in milliseconds; a changed endpoint file or database routine rebuilds the endpoints in-process and re-runs everything, reporting exactly which endpoints appeared or dropped; Ctrl+C still tears the test database down. npgsqlrest ... --watch (without --test) watches the running server — it restarts on SQL file, configuration, and database routine changes, regenerating code (TypeScript client, HTTP files) on every cycle, so create or replace a function in psql and the endpoint is live seconds later. See Watch Mode configuration.


    Performance

    How fast is it?

    Independent-methodology benchmarks measure thousands of requests per second on a single host — see the 2025 benchmark post for numbers against PostgREST, PostGraphile, and Hasura, including the methodology. The executables are AOT-compiled native binaries; there is no JIT warmup and no reflection at runtime.

    How do I enable caching?

    Annotate with @cached (+ @cache_expires_in 5 minutes). The backend (Memory, Redis, or HybridCache) is configured in Cache Options; per-user and per-parameter cache keys are supported.

    How do I enable response compression?

    json
    json
    { "ResponseCompression": { "Enabled": true } }

    See Response Compression.

    How do I set up rate limiting?

    Define policies in Rate Limiter config and apply them per endpoint with @rate_limiter_policy.


    Debugging & Logging

    How do I see which endpoints are created and what options they have?

    Set the NpgsqlRest log level to Debug — every endpoint logs as it is created, including which annotations were applied:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Debug" } } }

    Or list them without starting the server: npgsqlrest --endpoints.

    How do I log the SQL each endpoint executes at runtime?

    Two settings: "LogCommands": true in the NpgsqlRest section opts in, and the NpgsqlRest channel must be at Verbose (commands log at trace level):

    json
    json
    {
    +  "NpgsqlRest": { "LogCommands": true },
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } }
    +}

    Add "LogCommandParameters": true to include parameter values (mind the sensitive-data implications in production — @security_sensitive obfuscates a specific endpoint). See the Logging Guide for the full picture.

    How do I see the metadata queries NpgsqlRest runs at startup?

    Set the NpgsqlRest log level to Verbose — includes everything from Debug plus the raw pg_catalog discovery queries and the SQL-file describe phase. Useful when a function or file isn't being discovered and you need to see the underlying query and its filters.

    How do I completely silence a logger?

    Since 3.19.0 any Log:MinimalLevels entry accepts "Off" (aliases "None", "Silent"):

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off" } } }

    Each named logger is independent — handy for muting the application channels while watching the test runner's NpgsqlRestTest channel.


    Troubleshooting

    Startup warning: "Unknown configuration key"

    Almost always a typo in appsettings.json — keys are validated at startup. Run npgsqlrest --config to print the complete annotated configuration, or use the published JSON schema for editor autocompletion.

    Error: "permission denied for schema"

    The database user lacks USAGE on the schema:

    sql
    sql
    grant usage on schema my_schema to my_user;
    +grant execute on all functions in schema my_schema to my_user;

    This is also a feature: run the server as a least-privilege role and endpoints can only do what that role can do.

    Timeout errors (504 Gateway Timeout)

    Adjust the command timeout per endpoint with @command_timeout 2 minutes, or globally in configuration.

    Encrypted data is unreadable after restart

    Data Protection keys default to in-memory on Linux — configure persistent storage:

    json
    json
    { "DataProtection": { "Storage": "FileSystem", "FileSystemPath": "/var/lib/npgsqlrest/keys" } }

    See Data Protection config.

    Leftover *_abcde test databases

    The test runner drops its {rnd}-named databases on every exit path it can intercept — including Ctrl+C, SIGTERM, and hard startup errors. Leftovers mean a run was killed with SIGKILL (nothing can intercept that) or ran with Keep: true. Drop them manually:

    sql
    sql
    select format('drop database %I with (force);', datname)
    +from pg_database where datname like 'app_test_%' \\gexec
    `,128)]))}const k=s(n,[["render",o]]);export{u as __pageData,k as default}; diff --git a/assets/guide_faq.md.Bxla4fTX.lean.js b/assets/guide_faq.md.Bxla4fTX.lean.js new file mode 100644 index 000000000..10679db64 --- /dev/null +++ b/assets/guide_faq.md.Bxla4fTX.lean.js @@ -0,0 +1 @@ +import{_ as s,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const u=JSON.parse('{"title":"FAQ & Troubleshooting","titleTemplate":"NpgsqlRest","description":"Frequently asked questions about NpgsqlRest: missing endpoints, 404s, parameter naming, authentication, testing, performance, logging, and troubleshooting.","frontmatter":{"outline":[2,3],"title":"FAQ & Troubleshooting","titleTemplate":"NpgsqlRest","description":"Frequently asked questions about NpgsqlRest: missing endpoints, 404s, parameter naming, authentication, testing, performance, logging, and troubleshooting.","head":[["meta",{"name":"keywords","content":"npgsqlrest faq, npgsqlrest troubleshooting, endpoint not found, postgresql rest api questions, npgsqlrest 404, sql injection, npgsqlrest testing"}],["meta",{"property":"og:title","content":"NpgsqlRest FAQ & Troubleshooting"}],["meta",{"property":"og:description","content":"Frequently asked questions about NpgsqlRest."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/faq.md","filePath":"guide/faq.md"}'),n={name:"guide/faq.md"};function o(l,e,r,h,p,d){return i(),a("div",null,e[0]||(e[0]=[t("",128)]))}const k=s(n,[["render",o]]);export{u as __pageData,k as default}; diff --git a/assets/guide_http-types.md.C0G-MJo7.js b/assets/guide_http-types.md.C0G-MJo7.js new file mode 100644 index 000000000..c050d4c9c --- /dev/null +++ b/assets/guide_http-types.md.C0G-MJo7.js @@ -0,0 +1,143 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP Custom Types Guide","titleTemplate":"NpgsqlRest","description":"Call external APIs directly from PostgreSQL with NpgsqlRest HTTP Custom Types. Define an outbound request in a composite type comment, get the response in your function — with timeouts, retries, caching, placeholders, and parallel calls.","frontmatter":{"outline":[2,3],"title":"HTTP Custom Types Guide","titleTemplate":"NpgsqlRest","description":"Call external APIs directly from PostgreSQL with NpgsqlRest HTTP Custom Types. Define an outbound request in a composite type comment, get the response in your function — with timeouts, retries, caching, placeholders, and parallel calls.","head":[["meta",{"name":"keywords","content":"npgsqlrest http types, call external api from postgresql, outbound http postgresql, http custom type, postgresql api client, parallel api calls sql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Custom Types Guide"}],["meta",{"property":"og:description","content":"Call external APIs directly from PostgreSQL with HTTP Custom Types."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/http-types.md","filePath":"guide/http-types.md"}'),t={name:"guide/http-types.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e(`

    HTTP Custom Types

    HTTP Custom Types let a PostgreSQL routine make outbound HTTP calls — to a third-party API, an internal microservice, or even another endpoint of your own app — without any extension or plpython. You define the request in a composite type's comment, use that type as a function parameter, and NpgsqlRest performs the call and hands your function the response.

    This guide covers:

    1. How HTTP Custom Types work
    2. Enabling the HTTP client
    3. Defining and using a type
    4. Reading the response
    5. Dynamic requests with placeholders
    6. Timeouts, retries, and caching
    7. Multiple calls in parallel
    8. Self-calls: composing your own endpoints
    9. Secrets and server-side values
    10. Configuration

    Reference page

    This is the conceptual walkthrough. For the exact directive grammar and every option see @http custom types and HTTP Client configuration.

    How it works

    A composite type whose comment starts with (or contains) an HTTP request line becomes an HTTP Custom Type. When a routine declares a parameter of that type, NpgsqlRest performs the request before the routine runs and fills the type's fields with the response:

    mermaid
    flowchart TD
    +    C["Client
    +    GET /api/average-book-price"] --> NR["NpgsqlRest"]
    +    NR -->|"1 — outbound call, before the function runs"| EXT["https://books.toscrape.com/"]
    +    EXT -->|"response fills the _response fields"| NR
    +    NR -->|"2 — run the function with _response populated"| FN["average_book_price(_response)"]
    +    FN -->|"3 — return result"| C

    So from your function's point of view, the HTTP response is just another input parameter that's already populated. There are no callbacks and no blocking I/O in your SQL — NpgsqlRest does the call on the app tier.

    Enabling the HTTP client

    HTTP Custom Types require the client to be switched on:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true
    +    }
    +  }
    +}

    Defining and using a type

    Two steps: define the composite type with a request in its comment, then declare it as a parameter.

    sql
    sql
    -- 1. The type's comment defines the outbound request.
    +create type books_api as (
    +    body text,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type books_api is 'GET https://books.toscrape.com/
    +Accept: text/html
    +@timeout 30s';
    +
    +-- 2. Use the type as a parameter; NpgsqlRest fills it before the function runs.
    +create function average_book_price(_response books_api default null)
    +returns numeric
    +language plpgsql
    +as $$
    +begin
    +    if not (_response).success then
    +        raise exception 'fetch failed: %',
    +            coalesce((_response).error_message, 'status ' || (_response).status_code);
    +    end if;
    +    -- parse (_response).body … (XPath/regex) and compute the average
    +    return round(avg(substring(p::text from '([0-9]+(?:\\.[0-9]+)?)')::numeric), 2)
    +    from unnest(
    +        xpath('//p[@class="price_color"]/text()',
    +              xmlparse(document (_response).body))
    +    ) as p;
    +end;
    +$$;
    +
    +comment on function average_book_price(books_api) is '
    +HTTP GET /average-book-price
    +@allow_anonymous
    +@single';
    sql
    sql
    -- The type is created once (in a migration or schema file):
    +--   create type books_api as (body text, status_code int, success boolean, error_message text);
    +--   comment on type books_api is 'GET https://books.toscrape.com/
    +--   Accept: text/html
    +--   @timeout 30s';
    +
    +-- sql/average-book-price.sql
    +/*
    +HTTP GET /average-book-price
    +@allow_anonymous
    +@single
    +@param $1 _response books_api
    +*/
    +select round(avg(substring(p::text from '([0-9]+(?:\\.[0-9]+)?)')::numeric), 2) as avg_price
    +from unnest(
    +    xpath('//p[@class="price_color"]/text()',
    +          xmlparse(document ($1).body))
    +) as p;

    The request spec lives entirely in the type comment:

    code
    GET https://books.toscrape.com/   ← method + URL (GET/POST/PUT/PATCH/DELETE)
    +Accept: text/html                 ← request headers, one per line
    +@timeout 30s                      ← optional directives (before the request line or after the headers)
    +
    +… request body …                 ← optional, after a blank line

    This is the web-scraping example (17_scrap_demo_2): fetch HTML server-side, then parse it with PostgreSQL's native XPath.

    Reading the response

    Your function reads the response through the type's fields with the (_param).field syntax. The standard fields (names are configurable):

    FieldTypeMeaning
    bodytext (or jsonb)Response body. Declare it jsonb to parse JSON automatically.
    status_codeintHTTP status code.
    successbooleantrue for any 2xx status.
    content_typetextThe Content-Type header value.
    headersjsonAll response headers as a JSON object.
    error_messagetextSet when the call itself failed (timeout, DNS, connection) — otherwise null.

    Errors are reported through success/error_message, not raised as exceptions — so always branch on (_response).success before using the body. You only need to declare the fields you actually use:

    sql
    sql
    create type weather_api as (body jsonb, success boolean, error_message text);

    Dynamic requests with placeholders

    Any {name} in the URL, a header, or the body is replaced at request time with the value of the parameter name (the shared parameter-substitution mechanism). Matching is case-insensitive; a NULL becomes an empty string.

    sql
    sql
    create type exchange_rate_api as (body jsonb, status_code int, success boolean, error_message text);
    +
    +comment on type exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';
    +
    +create function get_rates(_base_currency text, _response exchange_rate_api default null)
    +returns jsonb language sql as $$
    +  select case when (_response).success then (_response).body
    +              else jsonb_build_object('error', (_response).error_message) end;
    +$$;
    +
    +comment on function get_rates(text, exchange_rate_api) is '
    +HTTP GET /rates
    +@allow_anonymous';

    A call to GET /api/rates?baseCurrency=EUR fetches https://open.er-api.com/v6/latest/EUR. Placeholders can also resolve to an allowlisted environment variable (for API keys — see secrets) or a resolved-parameter expression.

    Timeouts, retries, and caching

    Three optional directives shape the call. They may appear before the request line or after the headers:

    sql
    sql
    comment on type my_api is '@timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +@cache 5m
    +GET https://api.example.com/data
    +Accept: application/json';
    DirectiveWhat it does
    @timeout 10sPer-request timeout. Interval format (30, 30s, 2min, 00:00:30).
    @retry_delay 1s, 2s, 5sRetry on failure. The list sets both the number of retries and the delay before each (here: 3 retries). Add on 429, 503 to retry only those status codes.
    @cache 5mCache the response for the given TTL. GET only, 2xx only. Concurrent requests for the same key coalesce into one outbound call (stampede protection).

    @cache is opt-in per type and can be turned off globally with HttpClientOptions.CacheEnabled: false. The cache key is the fully-resolved method + URL + headers + body, so two calls with different {placeholders} cache separately.

    Multiple calls in parallel

    Give a function several HTTP Custom Type parameters and NpgsqlRest fires them concurrently, then runs your function once all have completed. This turns the database function into an API aggregator:

    sql
    sql
    create type exchange_rate_api as (body jsonb, status_code int, success boolean, error_message text);
    +create type crypto_price_api  as (body jsonb, status_code int, success boolean, error_message text);
    +
    +comment on type exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';
    +
    +comment on type crypto_price_api is 'GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
    +Accept: application/json
    +@timeout 10s';
    +
    +create function get_financial_dashboard(
    +    _base_currency text,
    +    _crypto_ids_csv text,
    +    _vs_currencies_csv text,
    +    _exchange_rate_response exchange_rate_api,  -- both HTTP calls
    +    _crypto_response crypto_price_api           -- run in parallel
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object(
    +        'rates',  case when (_exchange_rate_response).success then (_exchange_rate_response).body end,
    +        'crypto', case when (_crypto_response).success       then (_crypto_response).body end
    +    );
    +end;
    +$$;
    +
    +comment on function get_financial_dashboard(text, text, text, exchange_rate_api, crypto_price_api) is '
    +HTTP GET /financial-dashboard
    +@authorize';

    This is the external-API example (9_http_calls): two upstreams fetched in parallel and merged into one response.

    Self-calls: composing your own endpoints

    If the request URL is relative (e.g. GET /api/users), NpgsqlRest treats it as a self-call to another of your own endpoints — handled in-process, with no HTTP round trip. Combined with parallel execution, this lets one endpoint compose several others cheaply:

    sql
    sql
    create type api_users  as (body json);
    +create type api_orders as (body json);
    +comment on type api_users  is 'GET /api/users';
    +comment on type api_orders is 'GET /api/orders';
    +
    +create function dashboard(_users api_users, _orders api_orders)
    +returns json language sql as $$
    +  select json_build_object('users', ($1).body, 'orders', ($2).body);
    +$$;
    +
    +comment on function dashboard(api_users, api_orders) is '
    +HTTP GET /dashboard
    +@authorize';

    One request to /api/dashboard triggers two parallel internal calls and returns the combined result — microseconds per call instead of milliseconds, since the HTTP stack is bypassed.

    Secrets and server-side values

    Never make the client send an API key. Two server-side ways to supply one:

    Allowlisted environment variable — reference {API_KEY} in the type and allowlist it:

    jsonc
    jsonc
    "NpgsqlRest": { "AvailableEnvVars": [ "WEATHER_API_KEY" ] }
    sql
    sql
    comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}
    +Authorization: Bearer {WEATHER_API_KEY}';

    Resolved-parameter expression — compute the value with SQL (e.g. a per-user token from a table). The client can't override it:

    sql
    sql
    comment on type my_api is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +comment on function get_secure_data(_user_id int, _req my_api, _token text) is '
    +HTTP GET /secure-data
    +@authorize
    +_token = select api_token from user_tokens where user_id = {_user_id}';

    NpgsqlRest resolves _token server-side, substitutes it into the Authorization header, and makes the call — the token never reaches the browser.

    Configuration

    All under NpgsqlRest.HttpClientOptions:

    SettingDefaultDescription
    EnabledfalseMust be true for HTTP Custom Types to work.
    CacheEnabledtrueGlobal kill switch for the @cache directive. When false, every call is fresh.
    MaxCacheEntries10000Max distinct cached responses held in memory.
    CachePruneIntervalSeconds60How often expired cache entries are pruned.
    ResponseBodyField"body"Field name for the response body.
    ResponseStatusCodeField"status_code"Field name for the status code.
    ResponseSuccessField"success"Field name for the success flag.
    ResponseContentTypeField"content_type"Field name for the content type.
    ResponseHeadersField"headers"Field name for the headers JSON.
    ResponseErrorMessageField"error_message"Field name for the error message.

    The Response*Field settings let you rename the composite fields to whatever you prefer; the defaults are the names used throughout this guide.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true,
    +      "CacheEnabled": true,
    +      "MaxCacheEntries": 10000,
    +      "CachePruneIntervalSeconds": 60
    +    }
    +  }
    +}

    See it in the examples

    `,57)]))}const y=i(t,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/guide_http-types.md.C0G-MJo7.lean.js b/assets/guide_http-types.md.C0G-MJo7.lean.js new file mode 100644 index 000000000..e366d7667 --- /dev/null +++ b/assets/guide_http-types.md.C0G-MJo7.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"HTTP Custom Types Guide","titleTemplate":"NpgsqlRest","description":"Call external APIs directly from PostgreSQL with NpgsqlRest HTTP Custom Types. Define an outbound request in a composite type comment, get the response in your function — with timeouts, retries, caching, placeholders, and parallel calls.","frontmatter":{"outline":[2,3],"title":"HTTP Custom Types Guide","titleTemplate":"NpgsqlRest","description":"Call external APIs directly from PostgreSQL with NpgsqlRest HTTP Custom Types. Define an outbound request in a composite type comment, get the response in your function — with timeouts, retries, caching, placeholders, and parallel calls.","head":[["meta",{"name":"keywords","content":"npgsqlrest http types, call external api from postgresql, outbound http postgresql, http custom type, postgresql api client, parallel api calls sql"}],["meta",{"property":"og:title","content":"NpgsqlRest HTTP Custom Types Guide"}],["meta",{"property":"og:description","content":"Call external APIs directly from PostgreSQL with HTTP Custom Types."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/http-types.md","filePath":"guide/http-types.md"}'),t={name:"guide/http-types.md"};function l(p,s,h,r,k,o){return n(),a("div",null,s[0]||(s[0]=[e("",57)]))}const y=i(t,[["render",l]]);export{c as __pageData,y as default}; diff --git a/assets/guide_index.md.C-0777ru.js b/assets/guide_index.md.C-0777ru.js new file mode 100644 index 000000000..fd4cfe317 --- /dev/null +++ b/assets/guide_index.md.C-0777ru.js @@ -0,0 +1,23 @@ +import{_ as t,C as l,c as r,o as p,a5 as i,j as a,a as e,G as o}from"./chunks/framework.CgT1UzWm.js";const C=JSON.parse('{"title":"NpgsqlRest Overview","titleTemplate":"NpgsqlRest","description":"NpgsqlRest is a production-ready web server that automatically transforms PostgreSQL databases into REST APIs. Auto-generate endpoints from SQL files, functions, and procedures.","frontmatter":{"outline":[2,3],"title":"NpgsqlRest Overview","titleTemplate":"NpgsqlRest","description":"NpgsqlRest is a production-ready web server that automatically transforms PostgreSQL databases into REST APIs. Auto-generate endpoints from SQL files, functions, and procedures.","head":[["meta",{"name":"keywords","content":"npgsqlrest, postgresql rest api, automatic api generation, postgresql to rest, sql rest api, postgresql web server"}],["meta",{"property":"og:title","content":"NpgsqlRest - Automatic PostgreSQL REST API Server"}],["meta",{"property":"og:description","content":"Production-ready web server that automatically transforms PostgreSQL databases into REST APIs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/index.md","filePath":"guide/index.md"}'),h={name:"guide/index.md"},d={id:"plain-sql-files",tabindex:"-1"};function k(c,s,g,u,y,m){const n=l("Badge");return p(),r("div",null,[s[3]||(s[3]=i('

    Overview

    NpgsqlRest is a production-ready, standalone web server that automatically transforms your PostgreSQL database into a REST API. It provides:

    • Automatic HTTP REST endpoints from SQL files, functions, and procedures
    • SQL files as endpoints — write plain .sql files containing PostgreSQL commands and get REST endpoints automatically
    • Code generation for JavaScript/TypeScript client libraries
    • Code generation for HTTP files for a simple way to quickly invoke and TEST your API.
    • Declarative configuration using SQL comments and annotations

    To get started, you need:

    • A PostgreSQL database for metadata and endpoint specifications
    • Configuration via JSON files, environment variables, or command line arguments

    Declarative Approach

    NpgsqlRest uses SQL comment annotations to configure API endpoints declaratively. This approach keeps your API configuration close to your SQL logic.

    NpgsqlRest creates REST endpoints from two types of sources:

    ',8)),a("h3",d,[s[0]||(s[0]=e("Plain SQL Files ",-1)),o(n,{type:"tip",text:"Flagship"}),s[1]||(s[1]=e()),s[2]||(s[2]=a("a",{class:"header-anchor",href:"#plain-sql-files","aria-label":'Permalink to "Plain SQL Files "'},"​",-1))]),s[4]||(s[4]=i(`

    The primary way to create endpoints. Place .sql files containing PostgreSQL commands in a directory, and NpgsqlRest creates REST endpoints automatically. Parameter types and return columns are inferred via PostgreSQL's wire protocol — no functions, no procedures, no boilerplate:

    sql
    sql
    -- sql/get_users.sql
    +-- HTTP GET
    +-- @authorize admin
    +-- @cached
    +-- @param $1 department_id
    +select id, name, email from users where department_id = $1;

    This creates a GET /api/get-users?department_id=1 endpoint with authorization and caching.

    Multi-command SQL files execute multiple statements in a single database round-trip:

    sql
    sql
    -- sql/process_order.sql
    +-- HTTP POST
    +-- @param $1 order_id
    +-- @result validate
    +select count(*) from orders where id = $1;
    +update orders set status = 'processing' where id = $1;
    +-- @result confirm
    +select id, status from orders where id = $1;

    See the SQL File Source configuration for details and the complete SQL File Source tutorial for a hands-on guide.

    PostgreSQL Routines (Functions and Procedures)

    NpgsqlRest also generates endpoints from PostgreSQL functions and procedures using the built-in COMMENT system:

    sql
    sql
    create function get_user_data(id int)
    +returns table (name text, email text)
    +language sql
    +begin atomic;
    +select name, email from users where users.id = get_user_data.id;
    +end;
    +
    +comment on function get_user_data(id int) is '
    +HTTP GET /admin/get-user-data
    +@authorize admin
    +Cache-Control: public, max-age=31536000';

    This creates a GET endpoint at /admin/get-user-data that requires admin authorization and sets cache control headers.

    All endpoint sources generate HTTP test files for testing your API and JavaScript/TypeScript client libraries with type definitions ready for your frontend.

    Technology & Distribution

    NpgsqlRest is built on the latest .NET with the Kestrel web server, compiled using AOT (Ahead-of-Time) compilation for:

    • Zero dependencies - single executable file
    • Fast startup - native performance
    • Cross-platform - runs on Windows, macOS, and Linux

    Built on .NET/Kestrel, NpgsqlRest includes all modern web server capabilities out of the box, ensuring enterprise-grade performance and reliability.

    NpgsqlRest is free and open-source, allowing you to:

    • Customize builds for specific platforms
    • Modify functionality to meet your needs
    • Contribute to the project's development
    `,17))])}const f=t(h,[["render",k]]);export{C as __pageData,f as default}; diff --git a/assets/guide_index.md.C-0777ru.lean.js b/assets/guide_index.md.C-0777ru.lean.js new file mode 100644 index 000000000..195cdadad --- /dev/null +++ b/assets/guide_index.md.C-0777ru.lean.js @@ -0,0 +1 @@ +import{_ as t,C as l,c as r,o as p,a5 as i,j as a,a as e,G as o}from"./chunks/framework.CgT1UzWm.js";const C=JSON.parse('{"title":"NpgsqlRest Overview","titleTemplate":"NpgsqlRest","description":"NpgsqlRest is a production-ready web server that automatically transforms PostgreSQL databases into REST APIs. Auto-generate endpoints from SQL files, functions, and procedures.","frontmatter":{"outline":[2,3],"title":"NpgsqlRest Overview","titleTemplate":"NpgsqlRest","description":"NpgsqlRest is a production-ready web server that automatically transforms PostgreSQL databases into REST APIs. Auto-generate endpoints from SQL files, functions, and procedures.","head":[["meta",{"name":"keywords","content":"npgsqlrest, postgresql rest api, automatic api generation, postgresql to rest, sql rest api, postgresql web server"}],["meta",{"property":"og:title","content":"NpgsqlRest - Automatic PostgreSQL REST API Server"}],["meta",{"property":"og:description","content":"Production-ready web server that automatically transforms PostgreSQL databases into REST APIs."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/index.md","filePath":"guide/index.md"}'),h={name:"guide/index.md"},d={id:"plain-sql-files",tabindex:"-1"};function k(c,s,g,u,y,m){const n=l("Badge");return p(),r("div",null,[s[3]||(s[3]=i("",8)),a("h3",d,[s[0]||(s[0]=e("Plain SQL Files ",-1)),o(n,{type:"tip",text:"Flagship"}),s[1]||(s[1]=e()),s[2]||(s[2]=a("a",{class:"header-anchor",href:"#plain-sql-files","aria-label":'Permalink to "Plain SQL Files "'},"​",-1))]),s[4]||(s[4]=i("",17))])}const f=t(h,[["render",k]]);export{C as __pageData,f as default}; diff --git a/assets/guide_installation.md.KDi2i5hY.js b/assets/guide_installation.md.KDi2i5hY.js new file mode 100644 index 000000000..35c9779c0 --- /dev/null +++ b/assets/guide_installation.md.KDi2i5hY.js @@ -0,0 +1,72 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Installation Guide","titleTemplate":"NpgsqlRest","description":"Install NpgsqlRest on Windows, Linux, or macOS. Download pre-built executables, use Docker, or build from source. Get your PostgreSQL REST API server running.","frontmatter":{"outline":[2,3],"title":"Installation Guide","titleTemplate":"NpgsqlRest","description":"Install NpgsqlRest on Windows, Linux, or macOS. Download pre-built executables, use Docker, or build from source. Get your PostgreSQL REST API server running.","head":[["meta",{"name":"keywords","content":"npgsqlrest install, postgresql rest api server install, npgsqlrest docker, npgsqlrest linux, npgsqlrest windows, npgsqlrest macos"}],["meta",{"property":"og:title","content":"NpgsqlRest Installation Guide"}],["meta",{"property":"og:description","content":"Install NpgsqlRest on Windows, Linux, or macOS. Download executables, use Docker, or build from source."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/installation.md","filePath":"guide/installation.md"}'),e={name:"guide/installation.md"};function t(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[l(`

    NpgsqlRest Installation Guide

    Download Executable

    Manual Installation

    You can always manually download the latest version executable from the official Release page.

    Release page downloads include builds for:

    The optional default configuration file is also included, but this is just for convenience; it works with the same default values without this configuration file.

    Additional builds (e.g., MacOS x64) may be added in the future.

    Command Line Download

    Windows (x64)

    powershell
    powershell
    # Download using PowerShell
    +Invoke-WebRequest -Uri "https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-win64.exe" -OutFile "npgsqlrest.exe"
    +
    +# Optionally add to PATH or move to desired location

    Linux (x64)

    bash
    bash
    # Download the executable
    +wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux64 -O npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    Linux (ARM64)

    bash
    bash
    # Download the ARM64 executable (for Raspberry Pi, AWS Graviton, etc.)
    +wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux-arm64 -O npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    macOS (ARM64)

    bash
    bash
    # Download the executable
    +curl -L https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-osx-arm64 -o npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    Command Line Basic Commands

    You can run some basic commands to test your installation. Assuming that the binary name is npgsqlrest, you can

    • Check versions. This includes the client version and all included components:
    bash
    bash
    # Show versions
    +npgsqlrest --version
    +npgsqlrest -v
    • See some help information:
    bash
    bash
    # Show help
    +npgsqlrest --help
    +npgsqlrest -h
    • Inspect configuration with syntax highlighting:
    bash
    bash
    # Show current configuration (syntax highlighted in terminal, plain JSON when piped)
    +npgsqlrest --config
    • Validate configuration and database connectivity:
    bash
    bash
    # Pre-flight check (exits with code 0 on success, 1 on failure)
    +npgsqlrest --validate
    • List all supported SQL comment annotations:
    bash
    bash
    # All supported annotations as a JSON array
    +npgsqlrest --annotations

    NPM Installation

    bash
    bash
    # Install globally
    +npm install -g npgsqlrest
    +
    +# Or install locally in the project
    +npm install npgsqlrest

    To check versions or see help information, use the NPX runner:

    bash
    bash
    # Show versions
    +npx npgsqlrest --version
    +npx npgsqlrest -v
    +
    +# Show help
    +npx npgsqlrest --help
    +npx npgsqlrest -h

    Note: The NPM package automatically downloads the appropriate executable for your operating system during installation.

    Docker Installation

    Standard Image (AOT)

    bash
    bash
    # Pull the latest image (optional, docker run will do this if the image is not pulled)
    +docker pull vbilopav/npgsqlrest:latest
    +
    +# Check versions for all components
    +docker run --name npgsqlrest -it vbilopav/npgsqlrest:latest --version
    +
    +# See help
    +docker run --name npgsqlrest -it vbilopav/npgsqlrest:latest --help
    +
    +# Run with configuration file and with default port exposed
    +docker run --name npgsqlrest -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest

    JIT Image

    A Docker image variant using .NET runtime with JIT (Just-In-Time) compilation instead of AOT:

    bash
    bash
    # Pull the JIT image variant
    +docker pull vbilopav/npgsqlrest:latest-jit
    +
    +# Run with JIT runtime
    +docker run --name npgsqlrest-jit -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-jit

    The JIT version offers significantly better performance in high-concurrency scenarios (50-100% faster than AOT), but has slower cold-start times and a larger image size (~200-250 MB vs ~30 MB for AOT). For sustained high-throughput workloads, JIT is recommended.

    Available JIT image tags:

    • vbilopav/npgsqlrest:latest-jit - Latest version with JIT
    • vbilopav/npgsqlrest:3.6.3-jit - Specific version with JIT

    ARM64 Image

    A Docker image variant for ARM64 architecture (Raspberry Pi, AWS Graviton, Apple Silicon Linux VMs, etc.):

    bash
    bash
    # Pull the ARM64 image variant
    +docker pull vbilopav/npgsqlrest:latest-arm
    +
    +# Run with ARM64 runtime
    +docker run --name npgsqlrest-arm -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-arm

    The ARM64 build is compiled natively on GitHub's ARM64 runners for optimal performance on ARM-based systems.

    Available ARM64 image tags:

    • vbilopav/npgsqlrest:latest-arm - Latest version for ARM64
    • vbilopav/npgsqlrest:3.6.3-arm - Specific version for ARM64

    Bun Runtime Image

    A Docker image variant with pre-installed Bun JavaScript runtime is available:

    bash
    bash
    # Pull the Bun image variant
    +docker pull vbilopav/npgsqlrest:latest-bun
    +
    +# Run with Bun runtime available
    +docker run --name npgsqlrest-bun -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-bun

    This image includes the Bun JavaScript runtime alongside NpgsqlRest, enabling proxy endpoints to execute Bun scripts within the same container. Useful for scenarios where you need lightweight proxy handlers without external service calls.

    Available Bun image tags:

    • vbilopav/npgsqlrest:latest-bun - Latest version with Bun
    • vbilopav/npgsqlrest:3.6.3-bun - Specific version with Bun

    Building From Source

    Before building NpgsqlRest from source, ensure you have the following installed:

    Clone the Repository

    bash
    bash
    git clone https://github.com/vb-consulting/NpgsqlRest.git
    +cd NpgsqlRest
    • Standard Build
    bash
    bash
    dotnet build
    • AOT (Ahead-of-Time) Compilation

    NpgsqlRest supports AOT compilation for native executables:

    bash
    bash
    # Windows (x64)
    +dotnet publish -r win-x64 -c Release --output ./dist
    +
    +# Linux (x64) - must be run on Linux
    +dotnet publish -r linux-x64 -c Release --output ./dist
    +
    +# macOS (ARM64)
    +dotnet publish -r osx-arm64 -c Release --output ./dist

    For more information on build targets for specific OS, see the .NET RID Catalog

    The AOT-compiled executable will be approximately 30MB and is self-contained with no runtime dependencies. The built executable will have the same functionality as the pre-compiled releases available on the GitHub releases page.

    Next Steps

    `,69)]))}const u=i(e,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/guide_installation.md.KDi2i5hY.lean.js b/assets/guide_installation.md.KDi2i5hY.lean.js new file mode 100644 index 000000000..c9f64172d --- /dev/null +++ b/assets/guide_installation.md.KDi2i5hY.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as l}from"./chunks/framework.CgT1UzWm.js";const d=JSON.parse('{"title":"Installation Guide","titleTemplate":"NpgsqlRest","description":"Install NpgsqlRest on Windows, Linux, or macOS. Download pre-built executables, use Docker, or build from source. Get your PostgreSQL REST API server running.","frontmatter":{"outline":[2,3],"title":"Installation Guide","titleTemplate":"NpgsqlRest","description":"Install NpgsqlRest on Windows, Linux, or macOS. Download pre-built executables, use Docker, or build from source. Get your PostgreSQL REST API server running.","head":[["meta",{"name":"keywords","content":"npgsqlrest install, postgresql rest api server install, npgsqlrest docker, npgsqlrest linux, npgsqlrest windows, npgsqlrest macos"}],["meta",{"property":"og:title","content":"NpgsqlRest Installation Guide"}],["meta",{"property":"og:description","content":"Install NpgsqlRest on Windows, Linux, or macOS. Download executables, use Docker, or build from source."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/installation.md","filePath":"guide/installation.md"}'),e={name:"guide/installation.md"};function t(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[l("",69)]))}const u=i(e,[["render",t]]);export{d as __pageData,u as default}; diff --git a/assets/guide_logging.md.CT7Uqiy7.js b/assets/guide_logging.md.CT7Uqiy7.js new file mode 100644 index 000000000..1e72b75be --- /dev/null +++ b/assets/guide_logging.md.CT7Uqiy7.js @@ -0,0 +1,83 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Logging Guide","titleTemplate":"NpgsqlRest","description":"How to observe NpgsqlRest: log channels, seeing endpoints and SQL commands, PostgreSQL RAISE messages in logs, logging back into PostgreSQL, files, OpenTelemetry, and production setups.","frontmatter":{"outline":[2,3],"title":"Logging Guide","titleTemplate":"NpgsqlRest","description":"How to observe NpgsqlRest: log channels, seeing endpoints and SQL commands, PostgreSQL RAISE messages in logs, logging back into PostgreSQL, files, OpenTelemetry, and production setups.","head":[["meta",{"name":"keywords","content":"npgsqlrest logging guide, log channels, log sql commands, raise notice logs, serilog postgresql sink, opentelemetry postgresql api, production logging"}],["meta",{"property":"og:title","content":"NpgsqlRest Logging Guide"}],["meta",{"property":"og:description","content":"How to observe NpgsqlRest: channels, recipes, PostgreSQL notices, sinks, and production setups."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/logging.md","filePath":"guide/logging.md"}'),t={name:"guide/logging.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e(`

    Logging

    NpgsqlRest logging is built on Serilog and has three independent axes:

    • Channels (Serilog source contexts) — who is logging: the endpoint engine, the client host, the test runner, the .NET framework. Each channel's level is set independently under Log:MinimalLevels.
    • Sinkswhere logs go: console, rolling files, a PostgreSQL command, an OpenTelemetry collector. Each sink has its own minimum level on top of the channel levels.
    • LevelsVerbose < Debug < Information < Warning < Error < Fatal, plus "Off" (since 3.19.0) to silence a channel entirely.

    This guide is the task-oriented walkthrough; the option-by-option reference is at Logging configuration.

    The channel map

    Knowing which channel carries what — and at which level — answers most "how do I see X?" questions:

    ChannelWho logs on itDebug showsVerbose adds
    NpgsqlRestThe endpoint engine (library + plugins)Every endpoint as it is created, with each annotation applied (authorize, cached, path, …)pg_catalog discovery queries, the SQL-file describe phase, and — with LogCommandsevery SQL command endpoints execute
    NpgsqlRestClient (displayed as your ApplicationName when set)The client hostConfiguration processing, auth setup, startup detail
    NpgsqlRestTestThe SQL test runner (--test)Test discovery and parsingEvery test statement and in-process HTTP invocation
    Microsoft, SystemASP.NET Core / .NETFramework internals (defaults to Warning — keep it there)

    PostgreSQL itself participates too: anything your SQL raises (raise info 'x', raise warning 'y') flows into the logs — see PostgreSQL messages in your logs.

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    }
    +  }
    +}

    Channel names and ApplicationName

    When ApplicationName is set, the client host channel takes that name in the log output — lines show [MyApi] instead of [NpgsqlRestClient] in the {SourceContext} template placeholder. The configuration key stays stable: "MinimalLevels": { "NpgsqlRestClient": ... } keeps working regardless, because the client maps it to the actual channel name for you (using the application name itself as the key also works). The core engine channel is always NpgsqlRestApplicationName never affects it. The test runner channel name is configurable via TestRunner.LoggerName.

    Any setting works from the command line as well: npgsqlrest --log:minimallevels:npgsqlrest=debug.

    Recipes

    See which endpoints exist and why

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Debug" } } }
    code
    [DBG] Function public.get_users mapped to GET /api/get-users has set AUTHORIZE by the comment annotation with roles: admin
    +[DBG] Created endpoint GET /api/get-users

    This is the first thing to reach for when an annotation seems ignored or an endpoint is missing. (To just list endpoints without starting the server: npgsqlrest --endpoints.)

    See every SQL command endpoints execute

    Two switches — LogCommands opts in, and the channel must be at Verbose (commands log at trace level):

    json
    json
    {
    +  "NpgsqlRest": { "LogCommands": true },
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } }
    +}

    Add "LogCommandParameters": true to include parameter values — invaluable in development, but treat it as sensitive in production (passwords and personal data end up in logs; the @security_sensitive annotation obfuscates a specific endpoint's parameters).

    Debug discovery: "why isn't my function/file picked up?"

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } } }

    Verbose shows the raw pg_catalog discovery queries with their schema/name filters, and the SQL-file describe phase — you can see exactly what was scanned and what was skipped, and why.

    Watch the test runner, mute everything else

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    Verbose on NpgsqlRestTest prints every test statement and every in-process endpoint invocation. See the Testing Guide for the runner itself — note that the console report (PASS/FAIL lines) is always printed regardless of log levels; TestRunner.DetailedReport shapes the report, log levels shape the diagnostics.

    Silence a channel completely

    Since 3.19.0, "Off" (aliases "None", "Silent") fully mutes a channel — previously the quietest option was Fatal, which still let fatal events through:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off" } } }

    PostgreSQL messages in your logs

    Messages raised by your SQL — raise debug/log/info/notice/warning in functions, procedures, DO blocks, or triggers — are captured from the connection and logged on the endpoint's channel, at the level matching the PostgreSQL severity. This is on by default (LogConnectionNoticeEvents: true in the NpgsqlRest section), which turns raise into a zero-infrastructure logging facility for your database code:

    sql
    sql
    create function transfer(from_id int, to_id int, amount numeric) returns void as $$
    +begin
    +    ...
    +    raise info 'transfer of % from % to % completed', amount, from_id, to_id;
    +end $$ language plpgsql;

    Every call now leaves an INF line in the server logs — no logging table, no extension.

    LogConnectionNoticeEventsMode controls the shape: MessageOnly, FirstStackFrameAndMessage (default — includes where in your PL/pgSQL the raise happened), or FullStackAndMessage.

    Two related notes:

    • On SSE endpoints, raise messages at the configured notice level become events streamed to the client rather than plain log lines.
    • In the test runner, captured notices are shown under failing tests (and under passing ones with DetailedReport).

    Logging into PostgreSQL

    The database can be a log destination as well as a source — every log event can invoke a PostgreSQL command. You own the command and therefore the schema:

    sql
    sql
    create table logs (
    +    at timestamptz not null,
    +    level text not null,
    +    message text not null,
    +    exception text,
    +    source text
    +);
    +
    +create procedure log(_level text, _message text, _at timestamptz, _exception text, _source text)
    +language sql as $$
    +    insert into logs values (_at, _level, _message, _exception, _source);
    +$$;
    json
    json
    {
    +  "Log": {
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning"
    +  }
    +}

    The five positional parameters are: level, message, UTC timestamp, exception text (or null), and the source context (channel name) — see the reference. Since it's your procedure, you can route, enrich, prune, or pg_notify from it.

    Keep the level high

    PostgresMinimumLevel: "Warning" is a sensible floor — logging every Verbose event back into the database from a busy API is a self-inflicted write load.

    Files, OpenTelemetry, and production

    Console output is on by default and is the right answer for containers (12-factor: let the platform collect stdout). Beyond that:

    Rolling files — size-based rolling with retention:

    json
    json
    {
    +  "Log": {
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileSizeLimitBytes": 50000000,
    +    "RetainedFileCountLimit": 14
    +  }
    +}

    OpenTelemetry (OTLP) — ship to a collector (Grafana/Loki, Datadog, etc.), with resource attributes carrying the application name and environment:

    json
    json
    {
    +  "Log": {
    +    "ToOpenTelemetry": true,
    +    "OTLPEndpoint": "http://otel-collector:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.environment": "{environment}"
    +    }
    +  }
    +}

    Levels compose: MinimalLevels filters at the source (per channel); each sink then applies its own minimum (ConsoleMinimumLevel, FileMinimumLevel, PostgresMinimumLevel, OTLPMinimumLevel). A common production shape: channels at Information, console at Information, file at Information, PostgreSQL at Warning.

    A production baseline:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Information",
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileMinimumLevel": "Information",
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning"
    +  }
    +}

    And the development counterpart:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": { "NpgsqlRest": "Debug" }
    +  }
    +}
    `,55)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_logging.md.CT7Uqiy7.lean.js b/assets/guide_logging.md.CT7Uqiy7.lean.js new file mode 100644 index 000000000..7caae28e0 --- /dev/null +++ b/assets/guide_logging.md.CT7Uqiy7.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Logging Guide","titleTemplate":"NpgsqlRest","description":"How to observe NpgsqlRest: log channels, seeing endpoints and SQL commands, PostgreSQL RAISE messages in logs, logging back into PostgreSQL, files, OpenTelemetry, and production setups.","frontmatter":{"outline":[2,3],"title":"Logging Guide","titleTemplate":"NpgsqlRest","description":"How to observe NpgsqlRest: log channels, seeing endpoints and SQL commands, PostgreSQL RAISE messages in logs, logging back into PostgreSQL, files, OpenTelemetry, and production setups.","head":[["meta",{"name":"keywords","content":"npgsqlrest logging guide, log channels, log sql commands, raise notice logs, serilog postgresql sink, opentelemetry postgresql api, production logging"}],["meta",{"property":"og:title","content":"NpgsqlRest Logging Guide"}],["meta",{"property":"og:description","content":"How to observe NpgsqlRest: channels, recipes, PostgreSQL notices, sinks, and production setups."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/logging.md","filePath":"guide/logging.md"}'),t={name:"guide/logging.md"};function l(p,s,h,k,r,o){return n(),a("div",null,s[0]||(s[0]=[e("",55)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_proxy.md.CkN5fnQf.js b/assets/guide_proxy.md.CkN5fnQf.js new file mode 100644 index 000000000..55f240085 --- /dev/null +++ b/assets/guide_proxy.md.CkN5fnQf.js @@ -0,0 +1,137 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Proxy Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Turn a PostgreSQL routine into a reverse-proxy endpoint with NpgsqlRest. Stream an upstream response straight through, or transform it in SQL — with header and claim forwarding, caching, and a complete AI-gateway example.","frontmatter":{"outline":[2,3],"title":"Proxy Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Turn a PostgreSQL routine into a reverse-proxy endpoint with NpgsqlRest. Stream an upstream response straight through, or transform it in SQL — with header and claim forwarding, caching, and a complete AI-gateway example.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, reverse proxy postgresql, api gateway postgresql, proxy endpoint sql, transform upstream response, proxy_out"}],["meta",{"property":"og:title","content":"NpgsqlRest Proxy Endpoints Guide"}],["meta",{"property":"og:description","content":"Turn a PostgreSQL routine into a reverse-proxy endpoint — passthrough or transform."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/proxy.md","filePath":"guide/proxy.md"}'),t={name:"guide/proxy.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e(`

    Proxy Endpoints

    A proxy endpoint forwards a request to an upstream service and returns its response. With NpgsqlRest you turn any routine into a reverse proxy by adding @proxy — and you choose whether to stream the upstream response straight through or run it through SQL first to cache, enrich, or reshape it. This makes PostgreSQL a lightweight API gateway that can add auth, caching, and database context to any backend.

    This guide covers:

    1. How proxy endpoints work
    2. Enabling the proxy
    3. Passthrough mode
    4. Transform mode
    5. Where the request goes: target URL
    6. Forwarding headers, claims, and IP
    7. Forward proxy: send a function result upstream
    8. Configuration
    9. A complete example: cached AI gateway

    Reference pages

    This is the conceptual walkthrough. For exact options see @proxy, @proxy_out, and Proxy configuration.

    How it works

    @proxy forwards the incoming request to an upstream host. What happens to the response depends on one thing: whether your routine declares the special _proxy_* response parameters.

    mermaid
    flowchart TD
    +    REQ["Client request"] --> NR["NpgsqlRest @proxy endpoint"]
    +    NR -->|"forward (incoming path + query)"| UP["Upstream host"]
    +    UP --> MODE{"routine declares
    +    _proxy_* params?"}
    +    MODE -->|"no — passthrough"| OUT1["stream the upstream response
    +    straight back; function body NOT run;
    +    no DB connection opened"]
    +    MODE -->|"yes — transform"| OUT2["bind response into _proxy_* params,
    +    run the function, return its result"]
    +    OUT1 --> CL["Client"]
    +    OUT2 --> CL
    • Passthrough — no _proxy_* parameters. NpgsqlRest streams the upstream response directly back to the client. The function body is never executed and no database connection is opened. This is a pure reverse proxy.
    • Transform — the routine declares _proxy_* parameters. NpgsqlRest performs the upstream call, binds the response into those parameters, runs your function, and returns the function's result.

    Enabling the proxy

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "http://localhost:3001"
    +    }
    +  }
    +}

    Host is the default upstream; an annotation can override it per endpoint (see target URL).

    Passthrough mode

    The simplest proxy: forward and stream back. No _proxy_* parameters, so the body never runs.

    sql
    sql
    create function service_status()
    +returns void
    +language plpgsql
    +as $$ begin end; $$;   -- body is never executed in passthrough mode
    +
    +comment on function service_status() is '
    +HTTP GET /status
    +@proxy http://internal-service:8080';
    sql
    sql
    -- sql/status.sql
    +/*
    +HTTP GET /status
    +@proxy http://internal-service:8080
    +*/
    +select;   -- never executed; the endpoint just forwards

    GET /status is forwarded to http://internal-service:8080/status and the upstream response is streamed back unchanged. Because no DB connection is opened, passthrough proxying is cheap — useful for putting NpgsqlRest's auth, CORS, rate limiting, or TLS in front of a plain internal service.

    Passthrough does not run your SQL

    If you need the function body to execute (to log, cache, or reshape), you're in transform mode — you must declare at least one _proxy_* parameter. A passthrough endpoint's body is dead code.

    Transform mode

    Declare the response parameters and NpgsqlRest hands you the upstream response to do with as you like. All parameters are optional — declare only the ones you need:

    ParameterTypeMeaning
    _proxy_status_codeint (or text)Upstream HTTP status code.
    _proxy_bodytextResponse body (null if empty).
    _proxy_headersjsonResponse headers as JSON.
    _proxy_content_typetextContent-Type of the response.
    _proxy_successbooleantrue for a 2xx status.
    _proxy_error_messagetextSet if the call failed (timeout, connection error); else null.
    sql
    sql
    create function fetch_and_wrap(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', coalesce(_proxy_error_message, 'upstream failed'),
    +                                 'status', _proxy_status_code);
    +    end if;
    +    return json_build_object('status', _proxy_status_code, 'data', _proxy_body::json);
    +end;
    +$$;
    +
    +comment on function fetch_and_wrap(int, text, boolean, text) is '
    +HTTP GET /wrapped
    +@authorize
    +@proxy https://api.example.com/data';

    Here NpgsqlRest calls the upstream, fills the four parameters, runs fetch_and_wrap, and returns its JSON — letting you handle errors, cache the body in a table, or merge it with database data before responding. (The _proxy_* names are configurable.)

    Where the request goes: target URL

    The upstream target is built as:

    code
    target = host + incoming request path + incoming query string

    The host comes from the annotation if present, otherwise from ProxyOptions.Host. A relative annotation path makes it a self-call:

    AnnotationProxyOptions.HostResolved targetSelf-call?
    @proxyhttps://api.example.comhttps://api.example.com + path + queryno
    @proxy POSThttps://api.example.comsame host, upstream method forced to POSTno
    @proxy https://other.comhttps://api.example.comhttps://other.com + path + queryno
    @proxy /api/data(any)/api/data — internal call, no networkyes

    The optional [METHOD] lets the upstream verb differ from the incoming one (e.g. accept a GET from clients but call the upstream with POST). A relative target (@proxy /api/other) is dispatched in-process to another of your endpoints with no HTTP round trip.

    Forwarding headers, claims, and IP

    By default request and response headers are forwarded (minus a small exclude list). On top of that, NpgsqlRest can forward the authenticated identity to the upstream automatically — so the backend can trust who the caller is without re-doing auth:

    • @user_parameters — user claims, the client IP, HTTP-Custom-Type fields, and resolved-parameter values are forwarded in the endpoint's native shape: query-string parameters for QueryString endpoints, or merged into the JSON body for BodyJson endpoints.
    • @user_context — the claims and client IP are forwarded as HTTP headers (one per ContextKeyClaimsMapping entry, plus a claims-JSON header and an IP header).
    sql
    sql
    create function secure_gateway(
    +    _user_id text default null,     -- forwarded upstream as ?userId=…
    +    _user_name text default null    -- forwarded upstream as ?userName=…
    +)
    +returns void
    +language plpgsql
    +as $$ begin end; $$;
    +
    +comment on function secure_gateway(text, text) is '
    +HTTP GET /gateway
    +@authorize
    +@user_parameters
    +@proxy https://internal-api/secure';

    Long values are guarded

    Automatic values appended to the upstream query string are capped by MaxForwardedQueryParamLength (default 2048). A longer value is skipped with a warning rather than producing an unusable request line. Use a BodyJson endpoint if you must forward large values.

    Forward proxy: send a function result upstream

    @proxy_out (alias @forward_proxy) reverses the order: your function runs first, and its result is sent as the request body to the upstream, whose response is returned to the client. Use it to build a payload in SQL and hand it to a rendering/processing service:

    sql
    sql
    create function generate_report(_report_id int)
    +returns json
    +language sql
    +as $$
    +  select json_build_object(
    +    'title', 'Monthly Report',
    +    'rows', (select json_agg(row_to_json(s)) from sales s where s.month = _report_id));
    +$$;
    +
    +comment on function generate_report(int) is '
    +HTTP GET /report
    +@proxy_out POST https://render-service.internal/render';
    sql
    sql
    -- sql/report.sql
    +/*
    +HTTP GET /report
    +@proxy_out POST https://render-service.internal/render
    +@param $1 report_id
    +*/
    +select json_build_object(
    +  'title', 'Monthly Report',
    +  'rows', (select json_agg(row_to_json(s)) from sales s where s.month = $1));

    GET /report?reportId=3 runs generate_report, POSTs its JSON to the render service, and returns the rendered response. If the function fails, the error goes straight to the client and the upstream is never called; if the upstream fails, its status/body are forwarded (502 for connection errors, 504 for timeouts).

    Configuration

    All under NpgsqlRest.ProxyOptions:

    SettingDefaultDescription
    EnabledfalseMust be true for proxy annotations to work.
    HostnullDefault upstream host. Used when an annotation has no URL; ignored when it specifies one.
    DefaultTimeout"00:00:30"Per-request timeout (HH:MM:SS or interval format).
    ForwardHeaderstrueForward request headers upstream.
    ExcludeHeaders["Host", "Content-Length", "Transfer-Encoding"]Request headers not forwarded.
    ForwardResponseHeaderstrueForward upstream response headers to the client.
    ExcludeResponseHeaders["Transfer-Encoding", "Content-Length"]Response headers not forwarded.
    ForwardUploadContentfalseForward raw multipart/form-data upstream instead of processing it locally.
    MaxForwardedQueryParamLength2048Max length of a single auto-forwarded query value (0 disables the guard).
    Response*Parameter_proxy_status_code, _proxy_body, _proxy_headers, _proxy_content_type, _proxy_success, _proxy_error_messageNames of the transform-mode response parameters.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "http://localhost:3001",
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ForwardResponseHeaders": true
    +    }
    +  }
    +}

    A complete example: cached AI gateway

    This transform-mode endpoint proxies a request to an AI service, but caches each result in a table so repeated inputs never hit the upstream twice — a database-backed cache in front of a slow/expensive API.

    sql
    sql
    create function ai_sentiment(
    +    _text text,
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +declare
    +    _hash text := md5(_text || '::sentiment');
    +    _cached record;
    +    _result json;
    +begin
    +    -- 1. serve from the database cache when present
    +    select sentiment, sentiment_score into _cached
    +    from analysis_cache where text_hash = _hash;
    +
    +    if _cached.sentiment is not null then
    +        return json_build_object('sentiment', _cached.sentiment,
    +                                 'score', _cached.sentiment_score, 'cached', true);
    +    end if;
    +
    +    -- 2. handle upstream failure
    +    if not _proxy_success then
    +        return json_build_object('error', coalesce(_proxy_error_message, 'AI service unavailable'),
    +                                 'status_code', _proxy_status_code);
    +    end if;
    +
    +    -- 3. cache the fresh result and return it
    +    _result := _proxy_body::json;
    +    insert into analysis_cache (text_hash, sentiment, sentiment_score)
    +    values (_hash, _result->>'sentiment', (_result->>'score')::numeric)
    +    on conflict (text_hash) do nothing;
    +
    +    return json_build_object('sentiment', _result->>'sentiment',
    +                             'score', (_result->>'score')::numeric, 'cached', false);
    +end;
    +$$;
    +
    +comment on function ai_sentiment(text, int, text, boolean, text) is '
    +HTTP POST /ai/sentiment
    +@authorize
    +@proxy POST';   -- no host → forwarded to ProxyOptions.Host

    POST /ai/sentiment is forwarded to the configured AI service; the function then caches and shapes the result. Note that with caching, the upstream is still called every time (NpgsqlRest can't know the result is cached before the proxy runs) — to skip the call entirely on a cache hit, fetch with an HTTP Custom Type instead of @proxy, or split the lookup into a separate endpoint.

    See it in the examples

    `,49)]))}const g=a(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_proxy.md.CkN5fnQf.lean.js b/assets/guide_proxy.md.CkN5fnQf.lean.js new file mode 100644 index 000000000..541ea46eb --- /dev/null +++ b/assets/guide_proxy.md.CkN5fnQf.lean.js @@ -0,0 +1 @@ +import{_ as a,c as i,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Proxy Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Turn a PostgreSQL routine into a reverse-proxy endpoint with NpgsqlRest. Stream an upstream response straight through, or transform it in SQL — with header and claim forwarding, caching, and a complete AI-gateway example.","frontmatter":{"outline":[2,3],"title":"Proxy Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Turn a PostgreSQL routine into a reverse-proxy endpoint with NpgsqlRest. Stream an upstream response straight through, or transform it in SQL — with header and claim forwarding, caching, and a complete AI-gateway example.","head":[["meta",{"name":"keywords","content":"npgsqlrest proxy, reverse proxy postgresql, api gateway postgresql, proxy endpoint sql, transform upstream response, proxy_out"}],["meta",{"property":"og:title","content":"NpgsqlRest Proxy Endpoints Guide"}],["meta",{"property":"og:description","content":"Turn a PostgreSQL routine into a reverse-proxy endpoint — passthrough or transform."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/proxy.md","filePath":"guide/proxy.md"}'),t={name:"guide/proxy.md"};function l(p,s,r,h,o,d){return n(),i("div",null,s[0]||(s[0]=[e("",49)]))}const g=a(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_quick-start.md.DtKtFUQX.js b/assets/guide_quick-start.md.DtKtFUQX.js new file mode 100644 index 000000000..dc4066aff --- /dev/null +++ b/assets/guide_quick-start.md.DtKtFUQX.js @@ -0,0 +1,74 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Quick Start Guide","titleTemplate":"NpgsqlRest","description":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function and HTTP comment annotation.","frontmatter":{"outline":[2,3],"title":"Quick Start Guide","titleTemplate":"NpgsqlRest","description":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function and HTTP comment annotation.","head":[["meta",{"name":"keywords","content":"npgsqlrest quick start, postgresql rest api tutorial, create rest api from postgresql, npgsqlrest getting started, sql to rest api"}],["meta",{"property":"og:title","content":"NpgsqlRest Quick Start Guide"}],["meta",{"property":"og:description","content":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/quick-start.md","filePath":"guide/quick-start.md"}'),t={name:"guide/quick-start.md"};function l(p,s,r,o,h,c){return n(),a("div",null,s[0]||(s[0]=[e(`

    Quick Start

    This guide walks you through running NpgsqlRest for the first time and creating your first API endpoint. By the end, you'll have a working REST API connected to your PostgreSQL database.

    Prerequisites

    Before starting, ensure you have:

    • NpgsqlRest installed (Installation Guide)
    • PostgreSQL database running (version 13 or later)
    • A database with connection credentials

    Step 1: Create Your First Endpoint

    NpgsqlRest can create endpoints from plain SQL files or from PostgreSQL functions. SQL files are the recommended approach — they're simpler to set up and don't require any database DDL.

    SQL file endpoints need to be enabled in your appsettings.json (or create one — see Step 4):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    Then create a sql/ directory next to the NpgsqlRest executable and add a .sql file:

    sql
    sql
    -- sql/hello.sql
    +-- HTTP GET
    +-- @allow_anonymous
    +select 'Hello, World!' as message;

    NpgsqlRest will create a GET /api/hello endpoint from this file automatically.

    Option B: PostgreSQL Function

    Alternatively, create a function directly in your PostgreSQL database:

    sql
    sql
    create function my_first_function()
    +returns setof text
    +language sql
    +begin atomic;
    +values ('Hello, World!'), ('This is my first function.'), ('Enjoy coding in SQL!');
    +end;
    +
    +comment on function my_first_function() is 'HTTP GET';

    Note: by default, function endpoints are created only if the function comment contains the HTTP keyword. This behavior can be changed in the configuration.

    Step 2: Run NpgsqlRest

    • Add connection string via command line argument --connectionstrings:default="<npgsql connection string>" and start NpgsqlRest. This is Npgsql Connection String Format, but a simple example looks like this: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres. So, the command line to start NpgsqlRest would look like this:
    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"                                                                                
    +[12:32:26.087 INF] Started in 00:00:00.0575787, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Note: NpgsqlRest supports multiple connection strings and if not configured otherwise, it uses the first available connection string.

    Congratulations! NpgsqlRest is now running and connected to your database and our first endpoint is be created automatically. Let's test it.

    bash
    bash
     curl -i http://localhost:8080/api/my-first-function
    +HTTP/1.1 401 Unauthorized
    +Content-Length: 0
    +Date: Thu, 04 Dec 2025 11:42:27 GMT
    +Server: Kestrel

    By default, NpgsqlRest requires authorization. We will fix that in the next step.

    Step 3: Anonymous Endpoint And Verbose Logging

    To disable authorization for development purposes we can add anonymous comment annotation to our function:

    sql
    sql
    comment on function my_first_function() is '
    +HTTP GET
    +@anonymous';

    Alternatively, we can disable authorization requiremnt in command line by adding the following argument --npgsqlrest:requiresauthorization=false:

    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres" --npgsqlrest:requiresauthorization=false
    +[12:47:53.288 INF] Started in 00:00:00.0517179, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Also, since we are in development mode, let's enable debug logging with --log:minimallevels:npgsqlrest=debug to see what is happening under the hood and to make sure our endpoint is created:

    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres" --log:minimallevels:npgsqlrest=debug
    +[12:49:29.928 DBG] ----> Starting with configuration(s): JsonConfigurationProvider for 'appsettings.json' (Missing), JsonConfigurationProvider for 'appsettings.Development.json' (Missing), CommandLineConfigurationProvider [NpgsqlRest]
    +[12:49:29.937 DBG] ----> Logging enabled: Console (minimum level: Verbose) [NpgsqlRest]
    +[12:49:29.937 DBG] Using default as main connection string: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=******;Application Name=example;Enlist=False;No Reset On Close=True [NpgsqlRest]
    +[12:49:29.937 DBG] Using connection retry options with strategy: RetrySequenceSeconds=1,3,6,12, ErrorCodes=08000,08003,08006,08001,08004,55P03,55006,53300,57P03,40001 [NpgsqlRest]
    +[12:49:29.939 DBG] Using EndpointSource PostgreSQL Source [NpgsqlRest]
    +[12:49:29.939 DBG] Routine caching is disabled. [NpgsqlRest]
    +[12:49:29.961 DBG] Using DataSource with schema 'public' for metadata queries. [NpgsqlRest]
    +[12:49:29.998 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set HTTP by the comment annotation to GET /api/my-first-function [NpgsqlRest]
    +[12:49:29.998 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set ALLOW ANONYMOUS by the comment annotation. [NpgsqlRest]
    +[12:49:29.999 DBG] Created endpoint GET /api/my-first-function [NpgsqlRest]
    +[12:49:30.002 INF] Started in 00:00:00.0760485, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Finally, let's test our endpoint again:

    bash
    bash
     curl -i http://localhost:8080/api/my-first-function
    +HTTP/1.1 200 OK
    +Content-Type: application/json
    +Date: Thu, 04 Dec 2025 11:47:55 GMT
    +Server: Kestrel
    +Transfer-Encoding: chunked
    +
    +["Hello, World!","This is my first function.","Enjoy coding in SQL!"]

    Function worked as expected, it returns JSON array of strings, and we have our first NpgsqlRest endpoint!

    Step 4: Create Configuration File

    In order to avoid passing command line arguments every time we start NpgsqlRest, let's create a default configuration file.

    Create an appsettings.json file in your working directory:

    json
    json
    {
    +  // Default connection string to the PostgreSQL database
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"
    +  },
    +
    +  // Logging configuration, use "Debug" level for NpgsqlRest namespace
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Debug"
    +    }
    +  },
    +
    +  // Enable SQL file endpoints (scan sql/ directory recursively)
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    Now you can start NpgsqlRest without any command line arguments:

    code
    ❯ ./npgsqlrest
    +[12:55:09.738 DBG] ----> Starting with configuration(s): JsonConfigurationProvider for 'appsettings.json' (Optional), JsonConfigurationProvider for 'appsettings.Development.json' (Missing), CommandLineConfigurationProvider [NpgsqlRest]
    +[12:55:09.750 DBG] ----> Logging enabled: Console (minimum level: Verbose) [NpgsqlRest]
    +[12:55:09.750 DBG] Using Default as main connection string: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=******;Application Name=example;Enlist=False;No Reset On Close=True [NpgsqlRest]
    +[12:55:09.750 DBG] Using connection retry options with strategy: RetrySequenceSeconds=1,3,6,12, ErrorCodes=08000,08003,08006,08001,08004,55P03,55006,53300,57P03,40001 [NpgsqlRest]
    +[12:55:09.753 DBG] Using EndpointSource PostgreSQL Source [NpgsqlRest]
    +[12:55:09.753 DBG] Routine caching is disabled. [NpgsqlRest]
    +[12:55:09.778 DBG] Using DataSource with schema 'public' for metadata queries. [NpgsqlRest]
    +[12:55:09.817 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set HTTP by the comment annotation to GET /api/my-first-function [NpgsqlRest]
    +[12:55:09.818 DBG] Created endpoint GET /api/my-first-function [NpgsqlRest]
    +[12:55:09.821 INF] Started in 00:00:00.0850561, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Next Steps

    Now that you have NpgsqlRest running:

    `,43)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_quick-start.md.DtKtFUQX.lean.js b/assets/guide_quick-start.md.DtKtFUQX.lean.js new file mode 100644 index 000000000..41d4325ab --- /dev/null +++ b/assets/guide_quick-start.md.DtKtFUQX.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"Quick Start Guide","titleTemplate":"NpgsqlRest","description":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function and HTTP comment annotation.","frontmatter":{"outline":[2,3],"title":"Quick Start Guide","titleTemplate":"NpgsqlRest","description":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function and HTTP comment annotation.","head":[["meta",{"name":"keywords","content":"npgsqlrest quick start, postgresql rest api tutorial, create rest api from postgresql, npgsqlrest getting started, sql to rest api"}],["meta",{"property":"og:title","content":"NpgsqlRest Quick Start Guide"}],["meta",{"property":"og:description","content":"Get started with NpgsqlRest in minutes. Create your first PostgreSQL REST API endpoint with a simple SQL function."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/quick-start.md","filePath":"guide/quick-start.md"}'),t={name:"guide/quick-start.md"};function l(p,s,r,o,h,c){return n(),a("div",null,s[0]||(s[0]=[e("",43)]))}const u=i(t,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_sql-files.md.yAmqICyN.js b/assets/guide_sql-files.md.yAmqICyN.js new file mode 100644 index 000000000..d0b50a3fc --- /dev/null +++ b/assets/guide_sql-files.md.yAmqICyN.js @@ -0,0 +1,52 @@ +import{_ as e,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SQL File Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Build REST APIs directly from .sql files - no functions, no procedures, no boilerplate. Multi-command endpoints, automatic parameter inference, TypeScript generation, and full annotation support.","frontmatter":{"outline":[2,3],"title":"SQL File Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Build REST APIs directly from .sql files - no functions, no procedures, no boilerplate. Multi-command endpoints, automatic parameter inference, TypeScript generation, and full annotation support.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql to rest api, postgresql sql file endpoint, plain sql rest api, sql file api generator, npgsqlrest 3.12, multi-command sql endpoint, sql file typescript generation"}],["meta",{"property":"og:title","content":"NpgsqlRest SQL File Endpoints Guide"}],["meta",{"property":"og:description","content":"Build REST APIs directly from .sql files. No functions, no procedures, no boilerplate."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/sql-files.md","filePath":"guide/sql-files.md"}'),n={name:"guide/sql-files.md"};function l(o,s,r,p,d,c){return i(),a("div",null,s[0]||(s[0]=[t(`

    SQL File Endpoints

    NpgsqlRest creates REST API endpoints directly from .sql files. No CREATE FUNCTION, no RETURNS TABLE, no LANGUAGE sql, no COMMENT ON FUNCTION — just the query itself.

    Source Code: Every example in this guide comes from the examples repository. Each function-based example has a _sql_file counterpart.

    How It Works

    At startup, for each .sql file matched by the configured glob pattern:

    1. The file is parsed — comments are extracted as annotations, SQL is split into statements on ; boundaries
    2. Each statement is described via PostgreSQL's wire protocol (Parse → Describe → Sync with SchemaOnly) — parameter types and return columns are inferred without executing the query
    3. A REST endpoint is created with the URL path derived from the filenameget-users.sql becomes /api/get-users

    This gives you static type checking — SQL errors are caught at startup, not at runtime. Your SQL files are validated against the actual database schema before the server accepts any requests:

    code
    SqlFileSource: /path/to/get-posts.sql:
    +error 42703: column u.id does not exist
    +  at line 3, column 12
    +  select u.id, u.name from users u
    +             ^

    This is the default behavior (ErrorMode: "Exit"). Set ErrorMode: "Skip" to log errors and continue startup instead. Individual statements can bypass Describe entirely with @returns, which is necessary when the SQL references objects that don't exist at startup (e.g. temp tables created at runtime).

    Configuration

    Enable SQL File Source in appsettings.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    FilePattern uses glob syntax: ** crosses directories, * matches filenames, ? matches a single character.

    By default (CommentsMode: "OnlyAnnotated" in the client since 3.17.0 — "OnlyWithHttpTag" is an identical-behavior alias), only files containing an HTTP annotation (or a plugin annotation that requests an endpoint, such as @mcp) become endpoints. This prevents accidental exposure of migration scripts or utility files. Set CommentsMode: "ParseAll" to make every matched file an endpoint.

    Other settings:

    SettingDefaultDescription
    ErrorModeExitExit fails fast at startup. Skip logs errors and continues
    CommentScopeAllAll parses every comment. Header only parses comments before the first statement
    UnnamedSingleColumnSettrueSingle-column queries return flat arrays (["a","b"]) instead of object arrays
    ResultPrefixresultPrefix for multi-command result keys (result1, result2, ...)
    SkipNonQueryCommandstrueTransaction control (BEGIN, COMMIT, etc.), DO blocks, SET/RESET are auto-skipped from response

    See SQL File Source Configuration for the complete reference.

    Single-Command Files

    A file with one SQL statement produces a standard endpoint:

    sql
    sql
    -- sql/get-users.sql
    +-- HTTP GET
    +select user_id, username, email, active from example_2.users;

    GET /api/get-users returns an array of objects:

    json
    json
    [{"userId": 1, "username": "alice", "email": "alice@example.com", "active": true}, ...]

    Column names are converted to camelCase by the default NameConverter. Single-column queries return flat arrays — select name from users returns ["Alice","Bob"] not [{"name":"Alice"},...] (configurable via UnnamedSingleColumnSet).

    HTTP Verb Detection

    Without an explicit HTTP annotation, the verb is inferred from the SQL: SELECT → GET, INSERT → PUT, UPDATE → POST, DELETE → DELETE, DO block → POST. Mixed mutations → most destructive wins (DELETE > POST > PUT). An explicit annotation always overrides: -- HTTP POST.

    Parameters

    SQL files use named parameters (:name, since 3.19.0) or PostgreSQL positional parameters ($1, $2, ...) — one style per file.

    Named Parameters (:name)

    The placeholder is the parameter name — no annotations needed:

    sql
    sql
    -- sql/get-reports.sql
    +-- HTTP GET
    +select id, title, created_at
    +from reports
    +where created_at between :from_date and :to_date;

    GET /api/get-reports?fromDate=2024-01-01&toDate=2024-12-31

    The API name goes through the same NameConverter routine parameters use (:from_datefromDate with the default camelCase converter). Under the hood the SQL is rewritten to native $N before it is described and executed — PostgreSQL never sees the :name form, so type inference and runtime behavior are identical to positional files.

    • Repetition collapses: the same name used multiple times — including across statements in a multi-command file — is one parameter (where :user_id = author_id or :user_id = editor_id takes a single userId value).
    • Claim mappings hook up by placeholder name: select :_user_id under @authorize + @user_parameters binds the mapped claim with zero annotations.
    • Annotations match by name where still needed: @param from_date default null (defaults), @param :from_date timestamptz (type hint), or the retype-without-rename form @param from_date type is timestamptz.
    • The tokenizer knows SQL: strings, comments, and dollar-quoted bodies are untouched; ::int casts, := calls, and numeric slice bounds (a[1:3]) never match. One caveat: an array slice with a variable bound must be written with a space (a[1 : n]).
    • Mixing $N and :name in one file is rejected at startup. (JDBC-style ? is deliberately not supported — ?, ?|, ?&, @? are PostgreSQL's own jsonb operators.)

    Positional Parameters ($N)

    The @param annotation gives positional parameters meaningful names and optionally overrides the type:

    sql
    sql
    -- sql/get-reports.sql
    +-- HTTP GET
    +-- @param $1 from_date
    +-- @param $2 to_date
    +select id, title, created_at
    +from reports
    +where created_at between $1 and $2;

    GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31

    Without @param, the raw positional names work: ?$1=2024-01-01&$2=2024-12-31.

    Type Hints

    When PostgreSQL can't infer a parameter's type from context (e.g. select set_config('key', $1, true)$1 is ambiguous), add a type to @param:

    sql
    sql
    -- @param $1 user_id integer
    +-- @param $2 active boolean

    The type is used during the Describe step so PostgreSQL can resolve the parameter.

    Default Values

    Positional parameters must always be bound — unlike function parameters, there's no native DEFAULT clause. The @param annotation fills this gap:

    sql
    sql
    -- @param $1 status default 'active'
    +-- @param $2 limit integer = 50

    When a parameter with a default is not provided in the request, the default value is bound. Parameters with defaults become optional in generated TypeScript (? suffix) and OpenAPI (required: false).

    Value syntax follows SQL conventions: null → SQL NULL, 'text' → string, 42 → number, true → boolean.

    Virtual Parameters (@define_param)

    @define_param creates HTTP parameters that are not bound to the SQL query. They exist for annotation placeholders and claim mapping:

    sql
    sql
    -- @define_param format text
    +-- @table_format = {format}

    The format parameter feeds into the {format} placeholder without appearing in the SQL. Default type is text.

    Multi-Command Files

    A file with multiple statements (separated by ;) becomes one endpoint that executes everything in a single database round-trip via NpgsqlBatch. From the first example:

    sql
    sql
    -- HTTP GET
    +
    +-- @result first
    +select * from (
    +    values ('Hello, World!'), 
    +    ('This is my first SQL endpoint.'), 
    +    ('Enjoy coding in SQL!')
    +) as t(text);
    +
    +-- @result second
    +-- @single
    +select current_query() as query_text, current_user as user, current_timestamp as timestamp;

    Returns a JSON object with one key per statement:

    json
    json
    {
    +  "first": ["Hello, World!", "This is my first SQL endpoint.", "Enjoy coding in SQL!"],
    +  "second": {"queryText": "...", "user": "postgres", "timestamp": "..."}
    +}

    Result Rules

    • SELECT queries → JSON array of objects (or flat array for single-column with UnnamedSingleColumnSet)
    • INSERT/UPDATE/DELETE without RETURNING → rows-affected count (integer)
    • Transaction control (BEGIN, COMMIT, SET, DO, etc.) → auto-skipped from response (SkipNonQueryCommands: true)
    • All statements share the same parameters ($1, $2, ...) — the user sends each parameter once

    Positional Annotations

    Three annotations are positional — they apply to the next statement below them (or inline after ; on the same line):

    • @result name — rename result key (default: result1, result2, ...)
    • @single — return a single row as an object instead of an array
    • @skip — execute the statement but exclude it from the response
    sql
    sql
    select set_config('app.val', $1, true); -- @skip
    +-- @result data
    +-- @single
    +select current_setting('app.val') as value;

    @void

    @void forces the entire endpoint to return 204 No Content. All statements execute for side effects only — no JSON response, no result keys. This eliminates the need to @skip every individual statement.

    @returns — Skip Describe

    @returns skips the PostgreSQL Describe step entirely for a statement and resolves return columns from a type instead. This is a positional annotation.

    When to use it: when a statement references objects that don't exist at startup — typically temp tables created inside DO blocks.

    sql
    sql
    -- @returns my_result_type
    +-- @result data
    +-- @single
    +select * from _result;

    The type must exist in the database at startup. Columns are resolved from pg_catalog.

    Three forms:

    • @returns composite_type — resolve columns from the composite type definition
    • @returns scalar_type (e.g. @returns integer, @returns json) — single-column result
    • @returns void — no columns, void result (differs from @void which still runs Describe)

    DO Blocks and Limitations

    PostgreSQL DO blocks cannot receive $N parameters — this is a PostgreSQL language limitation, not an NpgsqlRest one. DO blocks also cannot return values. Multi-command SQL files work around this:

    Passing parameters in: Use set_config() with true (transaction-local) to store values, then current_setting() inside the DO block. Or use a temp table bridge with @skip:

    sql
    sql
    begin;
    +select set_config('app.user_id', $1, true);
    +do $$ begin
    +    insert into logs (user_id) values (current_setting('app.user_id')::int);
    +end; $$;
    +end;

    Getting results out: Create a temp table inside the DO block with ON COMMIT DROP, then SELECT from it with @returns to declare the return type.

    These are workarounds. When you need proper procedural logic with native parameters and return values, use a PostgreSQL function instead — that's what they're for.

    Existing Features Work Unchanged

    All NpgsqlRest features that existed before SQL File Source — authentication (@login, @logout, @authorize), file uploads (@upload), SSE (@sse), proxy (@proxy), HTTP custom types, CSV/Excel export (@raw, @table_format), caching (@cached), encryption, custom headers, composite types (@nested) — work identically in SQL files. The annotations are the same; only the endpoint source is different.

    The examples repository has a _sql_file counterpart for most examples demonstrating this.

    The Dev Loop: Watch Mode

    Run the server under watch mode while writing endpoint files:

    sh
    sh
    npgsqlrest ./config.json --watch

    Save a .sql file and the running API restarts with the change (~1s) — a new endpoint is immediately callable, a broken one prints its error while the rest keep serving (ErrorMode is relaxed to Skip while watching), and configured code generation (TypeScript client, HTTP files, OpenAPI) regenerates on every cycle, so frontend types follow your SQL as you type. Configuration files and database routines are watched too. For testing the same loop, see --test --watch.

    SQL Files vs Functions

    Use SQL files when: the query is declarative, involves multi-statement workflows, or the team prefers plain SQL files over DDL.

    Use functions when:

    • Procedural logic — functions receive parameters and return results natively. DO blocks require set_config/temp table workarounds.
    • Testing — functions support assert blocks inside repeatable migrations that run on every build, giving you database-level unit tests with rollback isolation. SQL files have no equivalent. See End-to-End Type Checking for examples.
    • OptimizationVOLATILE/STABLE/IMMUTABLE, COST, ROWS, PARALLEL hints.
    • Overloading — multiple function signatures per name.

    Use both together. Each endpoint source is independently enabled. SQL files can call functions, and HTTP custom types can reference any endpoint regardless of source.

    `,91)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_sql-files.md.yAmqICyN.lean.js b/assets/guide_sql-files.md.yAmqICyN.lean.js new file mode 100644 index 000000000..b1012c68d --- /dev/null +++ b/assets/guide_sql-files.md.yAmqICyN.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as i,a5 as t}from"./chunks/framework.CgT1UzWm.js";const k=JSON.parse('{"title":"SQL File Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Build REST APIs directly from .sql files - no functions, no procedures, no boilerplate. Multi-command endpoints, automatic parameter inference, TypeScript generation, and full annotation support.","frontmatter":{"outline":[2,3],"title":"SQL File Endpoints Guide","titleTemplate":"NpgsqlRest","description":"Build REST APIs directly from .sql files - no functions, no procedures, no boilerplate. Multi-command endpoints, automatic parameter inference, TypeScript generation, and full annotation support.","head":[["meta",{"name":"keywords","content":"npgsqlrest sql file source, sql to rest api, postgresql sql file endpoint, plain sql rest api, sql file api generator, npgsqlrest 3.12, multi-command sql endpoint, sql file typescript generation"}],["meta",{"property":"og:title","content":"NpgsqlRest SQL File Endpoints Guide"}],["meta",{"property":"og:description","content":"Build REST APIs directly from .sql files. No functions, no procedures, no boilerplate."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/sql-files.md","filePath":"guide/sql-files.md"}'),n={name:"guide/sql-files.md"};function l(o,s,r,p,d,c){return i(),a("div",null,s[0]||(s[0]=[t("",91)]))}const u=e(n,[["render",l]]);export{k as __pageData,u as default}; diff --git a/assets/guide_sse.md.CiPMjFtJ.js b/assets/guide_sse.md.CiPMjFtJ.js new file mode 100644 index 000000000..b58e047c5 --- /dev/null +++ b/assets/guide_sse.md.CiPMjFtJ.js @@ -0,0 +1,190 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Server-Sent Events Guide","titleTemplate":"NpgsqlRest","description":"Push real-time updates from PostgreSQL to the browser with NpgsqlRest. How SSE endpoints work, how RAISE statements broadcast events, scopes and levels, per-recipient targeting, and a complete chat example.","frontmatter":{"outline":[2,3],"title":"Server-Sent Events Guide","titleTemplate":"NpgsqlRest","description":"Push real-time updates from PostgreSQL to the browser with NpgsqlRest. How SSE endpoints work, how RAISE statements broadcast events, scopes and levels, per-recipient targeting, and a complete chat example.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse, server-sent events postgresql, real-time postgresql, raise notice sse, eventsource postgresql, postgresql push notifications"}],["meta",{"property":"og:title","content":"NpgsqlRest Server-Sent Events Guide"}],["meta",{"property":"og:description","content":"Push real-time updates from PostgreSQL to the browser with Server-Sent Events."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/sse.md","filePath":"guide/sse.md"}'),t={name:"guide/sse.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e(`

    Server-Sent Events (SSE)

    NpgsqlRest can stream real-time updates from PostgreSQL to connected clients over Server-Sent Events — a one-way HTTP stream the browser consumes with the native EventSource API. You don't write any streaming code: a procedure simply emits events with PostgreSQL's RAISE statement, and NpgsqlRest broadcasts them to subscribers.

    This guide covers:

    1. How SSE works in NpgsqlRest
    2. Creating a publisher endpoint
    3. Subscribing from the browser
    4. Who receives events: scope
    5. Which RAISE level fires: event level
    6. Targeting specific recipients
    7. Splitting publish from subscribe
    8. Configuration
    9. A complete example: real-time chat

    Reference pages

    This is the conceptual walkthrough. For exact options see @sse, @sse_events_level, and @sse_events_scope.

    How SSE works

    The model is deliberately small. Mark a routine with @sse and it becomes a publisher: any RAISE INFO/NOTICE/WARNING inside its body is turned into an SSE event and broadcast to connected clients. The annotation also registers a subscribe URL that a browser connects to with EventSource.

    mermaid
    flowchart TD
    +    C["Client C
    +    POST /api/send-message"] --> EP["@sse publisher endpoint
    +    INSERT message + RAISE INFO '...'"]
    +    EP --> BC["NpgsqlRest broadcaster
    +    (one per process)"]
    +    BC -->|"SSE event (scope-filtered)"| A["Client A
    +    EventSource /api/send-message/info"]
    +    BC -->|"SSE event (scope-filtered)"| B["Client B
    +    EventSource /api/send-message/info"]

    Key points:

    • There is one process-wide broadcaster. Every EventSource connection reads from the same stream; the subscribe URL is an entry point, not a per-topic channel.
    • Who receives a given event is decided per event by its scope (everyone, authorized users, a matching security context, or a specific user).
    • Which RAISE statements become events is decided by the event level — and level matching is exact, not "this level and above".
    • A connected client is a pure listener — connecting never runs the procedure body.

    Creating a publisher endpoint

    Add @sse to any endpoint and emit events with RAISE. Here is the message-sending half of a chat app — it inserts a row and broadcasts the new message as JSON:

    sql
    sql
    create procedure send_message(
    +    _message_text text,
    +    _user_id text = null,
    +    _user_name text = null
    +)
    +language plpgsql
    +as $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    -- broadcast the new message to all connected, authorized clients
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'user_id', _user_id::int,
    +        'username', _user_name,
    +        'message_text', _message_text,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    +
    +comment on procedure send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize';
    sql
    sql
    -- sql/send-message.sql
    +/*
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize
    +@param $1 message_text text
    +@param $2 _user_id text = null
    +@param $3 _user_name text = null
    +@void
    +*/
    +do $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (current_setting('request.user_id', true)::int,
    +            current_setting('request.user_name', true),
    +            $1)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'message_text', $1,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    • @sse makes this a publisher and registers the subscribe URL (see below). Its default event level is info, so RAISE INFO is what gets broadcast here.
    • @sse_scope authorize means only authenticated clients receive the broadcast — see scope.
    • The body still runs normally when the endpoint is called (the INSERT happens); the RAISE is the additional broadcast.
    • Emit a payload by formatting it into the RAISE message — JSON is the natural choice for structured events.

    Subscribing from the browser

    @sse registers a connection URL at <endpoint-path>/<level> — for the send_message endpoint above, with its default info level, that's GET /api/send-message/info.

    You don't build that URL by hand. Set ClientCodeGen.ExportEventSources: true and NpgsqlRest generates a typed EventSource factory for each SSE endpoint as part of the TypeScript client. For send_message you get a createSendMessageEventSource() function — use it directly:

    js
    js
    import { createSendMessageEventSource } from './example8Api'; // generated client
    +
    +const events = createSendMessageEventSource();
    +events.onmessage = (e) => {
    +  const msg = JSON.parse(e.data);
    +  console.log(\`\${msg.username}: \${msg.message_text}\`);
    +};

    Connecting does not run the procedure — the client just listens. Any time another request triggers the procedure (or any publisher that emits on this stream), the event arrives here.

    One-call subscribe + send

    The generated POST function for the same endpoint can open the stream for you too: pass an onMessage callback as sendMessage(request, onMessage) and the client subscribes, sends, and tidies up the EventSource in a single call.

    Who receives events: scope

    Scope answers which connected clients should receive this event. Set the default for an endpoint with @sse_scope (alias @sse_events_scope):

    ScopeWho receives the event
    allEvery connected client.
    authorizeOnly authenticated clients. Optionally restrict to roles / usernames / user IDs: @sse_scope authorize admin, manager.
    matchingClients whose security context matches the emitting request (by roles, usernames, and user IDs).
    code
    @sse_scope all
    +@sse_scope authorize
    +@sse_scope authorize admin, manager
    +@sse_scope matching

    The scope set on the annotation is the default for events from that endpoint. Individual events can override it at runtime — see targeting.

    Which RAISE level fires: event level

    An SSE endpoint listens at one PostgreSQL notice level. Only RAISE statements at that exact level become events:

    Endpoint levelRAISE INFORAISE NOTICERAISE WARNING
    info (default)✅ broadcast
    notice✅ broadcast
    warning✅ broadcast

    The level is exact, not hierarchical — an info endpoint does not also forward notice or warning. Set the level inline on @sse or with the dedicated annotation:

    code
    @sse                     -- default level: info → subscribe at <path>/info
    +@sse my_events on notice -- custom path + notice level → subscribe at <path>/my_events
    +@sse_events_level notice -- set the level separately

    The process-wide default level is DefaultServerSentEventsEventNoticeLevel (default INFO); see configuration.

    Targeting specific recipients

    Beyond the endpoint's default scope, a single event can pick its own audience at runtime using RAISE … USING hint. The hint string is a scope expression:

    sql
    sql
    -- broadcast to everyone, regardless of the endpoint's default scope
    +raise notice 'System maintenance in 5 minutes' using hint = 'all';
    +
    +-- only admins
    +raise notice '%' using hint = 'authorize admin', message;
    +
    +-- only specific users, by username or id
    +raise info 'Your report is ready' using hint = format('authorize %s', _user_id);

    This is what makes per-user notifications possible: an endpoint that processes a job can notify just the user who owns it with using hint = format('authorize %s', _user_id), even though the broadcaster is shared.

    Request correlation

    When a request carries an execution-id header (ExecutionIdHeaderName, default X-NpgsqlRest-ID) and an EventSource includes the same id as a query parameter, events are also filtered to that execution id — useful for streaming the progress of one specific long-running call back to its initiator.

    Splitting publish from subscribe

    In the chat example, one endpoint both sends and is subscribed to. Often you want them separate — for example, a privileged action emits events, but ordinary users subscribe. Use a subscribe-only endpoint (its body never runs — it exists only to register the URL) plus one or more emitter endpoints that broadcast on the same level/scope.

    sql
    sql
    -- subscribe-only: clients connect here, body never executes
    +create procedure user_events_subscribe()
    +language plpgsql as $$ begin perform 1; end; $$;
    +
    +comment on procedure user_events_subscribe() is '
    +HTTP GET
    +@authorize
    +@sse
    +@sse_scope authorize';
    +
    +-- emitter: a privileged action that notifies the affected user
    +create procedure update_user_roles(_target_user_id int, _roles text[])
    +language plpgsql as $$
    +begin
    +    -- ... perform the role update ...
    +    raise info 'roles updated'
    +        using hint = format('authorize %s', _target_user_id);
    +end;
    +$$;
    +
    +comment on procedure update_user_roles(int, text[]) is '
    +HTTP POST
    +@authorize manager
    +@sse
    +@sse_scope authorize';
    sql
    sql
    -- sql/user-events-subscribe.sql  (subscribe-only)
    +/*
    +HTTP GET
    +@authorize
    +@sse
    +@sse_scope authorize
    +@void
    +*/
    +select 1;
    +
    +-- sql/update-user-roles.sql  (emitter)
    +/*
    +HTTP POST
    +@authorize manager
    +@sse
    +@sse_scope authorize
    +@param $1 _target_user_id int
    +@param $2 _roles text[]
    +@void
    +*/
    +do $$
    +declare _target_user_id int = $1;
    +begin
    +    -- ... perform the role update ...
    +    raise info 'roles updated'
    +        using hint = format('authorize %s', _target_user_id);
    +end;
    +$$;

    The browser subscribes to GET /api/user-events-subscribe/info; when a manager calls update_user_roles, only the targeted user's connection receives the event.

    Configuration

    SSE works out of the box — no global enable flag. These options under NpgsqlRest tune the defaults:

    SettingDefaultDescription
    DefaultServerSentEventsEventNoticeLevel"INFO"Default PostgreSQL notice level for SSE events (INFO, NOTICE, or WARNING). Overridable per endpoint via @sse … on <level>.
    ServerSentEventsResponseHeaders{}Extra headers added to SSE responses.
    WarnUnboundServerSentEventsNoticestrueLogs a one-time warning for a RAISE that matches the SSE level but sits on an endpoint with no @sse annotation (a likely missing publisher).
    json
    json
    {
    +  "NpgsqlRest": {
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": {
    +      "X-Accel-Buffering": "no"
    +    }
    +  }
    +}

    Behind nginx

    SSE is a long-lived streaming response. If you run behind nginx, add X-Accel-Buffering: no (as above) so the proxy doesn't buffer the stream and delay events.

    Cache hits don't broadcast

    If a publisher endpoint is also @cached and a request is served from cache, the function body doesn't run — so no RAISE fires and no event is broadcast. That's correct behavior, but keep it in mind: don't cache an endpoint whose side effect is the broadcast.

    A complete example: real-time chat

    The pieces below form a minimal chat: a login, a publisher that sends + broadcasts, a history endpoint, and the browser subscription. (Cookie auth setup omitted — see the Authentication guide.)

    Tables

    sql
    sql
    create table messages (
    +    message_id int primary key generated always as identity,
    +    user_id int not null,
    +    username text not null,
    +    message_text text not null,
    +    created_at timestamptz not null default now()
    +);

    Send + broadcast (publisher)

    sql
    sql
    create procedure send_message(_message_text text, _user_id text = null, _user_name text = null)
    +language plpgsql as $$
    +declare _message_id int; _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id, 'user_id', _user_id::int,
    +        'username', _user_name, 'message_text', _message_text, 'created_at', _created_at);
    +end;
    +$$;
    +
    +comment on procedure send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize';

    Load history (plain endpoint, no SSE)

    sql
    sql
    create function get_messages()
    +returns setof messages
    +language sql as $$
    +  select * from messages order by created_at asc;
    +$$;
    +
    +comment on function get_messages() is '
    +HTTP GET
    +@authorize';

    Browser — using the generated TypeScript client, no hand-written fetch:

    js
    js
    import { getMessages, sendMessage, createSendMessageEventSource } from './example8Api';
    +
    +// 1. load history (typed, generated)
    +const { response: history } = await getMessages();
    +history?.forEach(renderMessage);
    +
    +// 2. subscribe to new messages
    +const events = createSendMessageEventSource();
    +events.onmessage = e => renderMessage(JSON.parse(e.data));
    +
    +// 3. send a message — every authorized subscriber receives the broadcast
    +await sendMessage({ messageText: 'Hello!' });

    Because the broadcast uses @sse_scope authorize, every signed-in client connected to the stream sees each new message in real time. All three functions — getMessages, sendMessage, and createSendMessageEventSource — are generated from your SQL; you don't write the HTTP calls.

    See it in the examples

    `,61)]))}const g=i(t,[["render",l]]);export{o as __pageData,g as default}; diff --git a/assets/guide_sse.md.CiPMjFtJ.lean.js b/assets/guide_sse.md.CiPMjFtJ.lean.js new file mode 100644 index 000000000..ae234d5e4 --- /dev/null +++ b/assets/guide_sse.md.CiPMjFtJ.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const o=JSON.parse('{"title":"Server-Sent Events Guide","titleTemplate":"NpgsqlRest","description":"Push real-time updates from PostgreSQL to the browser with NpgsqlRest. How SSE endpoints work, how RAISE statements broadcast events, scopes and levels, per-recipient targeting, and a complete chat example.","frontmatter":{"outline":[2,3],"title":"Server-Sent Events Guide","titleTemplate":"NpgsqlRest","description":"Push real-time updates from PostgreSQL to the browser with NpgsqlRest. How SSE endpoints work, how RAISE statements broadcast events, scopes and levels, per-recipient targeting, and a complete chat example.","head":[["meta",{"name":"keywords","content":"npgsqlrest sse, server-sent events postgresql, real-time postgresql, raise notice sse, eventsource postgresql, postgresql push notifications"}],["meta",{"property":"og:title","content":"NpgsqlRest Server-Sent Events Guide"}],["meta",{"property":"og:description","content":"Push real-time updates from PostgreSQL to the browser with Server-Sent Events."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/sse.md","filePath":"guide/sse.md"}'),t={name:"guide/sse.md"};function l(p,s,h,r,k,d){return n(),a("div",null,s[0]||(s[0]=[e("",61)]))}const g=i(t,[["render",l]]);export{o as __pageData,g as default}; diff --git a/assets/guide_testing.md.Mc_wQPBP.js b/assets/guide_testing.md.Mc_wQPBP.js new file mode 100644 index 000000000..210945b7b --- /dev/null +++ b/assets/guide_testing.md.Mc_wQPBP.js @@ -0,0 +1,185 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Testing Guide","titleTemplate":"NpgsqlRest","description":"Test NpgsqlRest endpoints with plain SQL files. In-process endpoint invocation, transactional isolation, test databases, template clones, migrations, Docker, watch mode, coverage, and CI.","frontmatter":{"outline":[2,3],"title":"Testing Guide","titleTemplate":"NpgsqlRest","description":"Test NpgsqlRest endpoints with plain SQL files. In-process endpoint invocation, transactional isolation, test databases, template clones, migrations, Docker, watch mode, coverage, and CI.","head":[["meta",{"name":"keywords","content":"npgsqlrest testing, sql test runner, postgresql api testing, test database, template database, test isolation, junit, endpoint coverage, watch mode"}],["meta",{"property":"og:title","content":"NpgsqlRest Testing Guide"}],["meta",{"property":"og:description","content":"Test NpgsqlRest endpoints with plain SQL files — in-process, transactional, CI-ready."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/testing.md","filePath":"guide/testing.md"}'),t={name:"guide/testing.md"};function l(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[e(`

    Testing

    NpgsqlRest ships a built-in SQL test runner: write tests for your endpoints as plain .sql files and run them with npgsqlrest --test. A test arranges data with ordinary SQL, invokes a real endpoint in-process (the complete pipeline — routing, authorization, parameter binding, execution, serialization — with no network and no running server), captures the response into a temp table, and asserts on it with ordinary SQL. Everything happens inside the test's own transaction, so tests leave no trace.

    sql
    sql
    -- tests/get_users_excludes_caller.test.sql
    +begin;
    +
    +-- ARRANGE
    +
    +insert into app.users (id, email, name) values (100, 'x@example.com', 'Fixture');
    +
    +-- ACT
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +
    +-- ASSERT
    +
    +select status = 200, 'authenticated caller gets 200'
    +from _response;
    +select body::jsonb @> '[{"email": "x@example.com"}]', 'the fixture user is listed'
    +from _response;
    +
    +rollback;
    code
    NpgsqlRest test runner — 9 file(s)
    +PASS  tests/get_users_excludes_caller.test.sql  (2 assertions, 52ms)
    +...
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 2/2 (100%)

    This guide covers the full feature. The configuration reference lives at Test Runner configuration; three complete working projects live in the repository: examples/19_testing_basic, examples/20_testing_newdb, and examples/21_testing_isolation.

    Quick start

    1. Put a test file next to your endpoint SQL (the co-located layout):
    sql
    sql
    -- sql/normalize_email.sql  (the endpoint)
    +/*
    +HTTP GET
    +*/
    +select lower(trim(:email)) as normalized;
    sql
    sql
    -- sql/normalize_email.test.sql  (the test)
    +/*
    +GET /api/normalize-email?email=%20X%40Y.z%20
    +*/
    +select status = 200, 'endpoint responds' from _response;
    +select body::jsonb ->> 0 = 'x@y.z', 'trims and lowercases' from _response;
    1. Point the runner at the tests:
    json
    json
    {
    +  "TestRunner": {
    +    "FilePattern": "./sql/**/*.test.sql"
    +  }
    +}
    1. Run:
    sh
    sh
    npgsqlrest ./config.json --test

    That's the whole setup. Test files are automatically excluded from endpoint discoverySqlFileSource.SkipPattern defaults to "*.test.sql" — so an HTTP block inside a test is never mistaken for an endpoint annotation.

    How it works

    In --test mode the client builds the full endpoint middleware exactly as in normal operation — endpoints from database routines and/or SQL files, authentication, custom parameters, everything — but instead of starting the web server it runs the test files and exits with a result code.

    The critical property is connection affinity: an endpoint invoked from a test runs on the test's own connection, inside the test's own transaction. A test can begin, insert fixture rows, call an endpoint that sees those uncommitted rows, assert on the response, and rollback — the database is untouched afterwards. Each test file gets its own non-pooled physical connection (fresh session: no temp-table, GUC, or prepared-statement carryover), and files run in parallel (MaxParallelism, default = processor count). If a file never rolls back, closing its physical connection aborts the open transaction — that is the safety net.

    Test-mode invariants, applied automatically:

    • WrapInTransaction is forced off — the test file owns transaction control; the runner never injects BEGIN/COMMIT/ROLLBACK.
    • Response caching is disabled — a test never sees another test's cached response.
    • Code generation (HTTP files, TypeScript client, OpenAPI) is skipped — a test run never rewrites generated artifacts.

    Test file anatomy

    A test file is a sequence of SQL statements and HTTP blocks, executed strictly in order, statement by statement (like psql): each statement runs in autocommit unless the file opens its own transaction. Semicolon splitting understands comments, string literals with '' escapes, and dollar-quoted bodies — a do $$ … $$; block stays whole.

    Assertions

    A reported test is one of:

    A boolean-returning SELECT. If the first column is boolean, the statement is an assertion: the first row's value must be true (false or null fails; zero rows passes vacuously). The optional second column is the assertion's name, shown in the report and used as the JUnit test-case name:

    sql
    sql
    select count(*) = 3, 'exactly three users are seeded' from app.users;

    A do block. Passes unless it raises — assert inside a DO block raises SQLSTATE P0004, reported as a failure with the assert message. One DO block = one reported test:

    sql
    sql
    do $$ begin
    +    assert app.normalize_email(' X@Y.z ') = 'x@y.z', 'should trim and lowercase';
    +end $$;

    Any other statement is arrange/act — not counted; it only surfaces if it errors. Any SQL error (other than an assert) is reported as an error with its SQLSTATE, message, statement text, and file:line.

    Failing behavior is fail-fast per file: after the first failed/errored assertion the rest of the file does not run. Assertions that passed before the failure are still credited.

    HTTP blocks: invoking endpoints

    An HTTP request is embedded in a block comment whose first content line is a request line — a single-request subset of the standard .http file syntax:

    sql
    sql
    /*
    +POST /api/create-user
    +Content-Type: application/json
    +# @claim user_id=42
    +# @claim roles=admin
    +# @response created
    +
    +{"name": "Grace Hopper", "email": "grace@example.com"}
    +*/
    • Request line: [HTTP] METHOD /path[?query] [HTTP/x] — method is GET/POST/PUT/DELETE; the path must equal the endpoint's full path including UrlPathPrefix (default /api). A block comment whose first line is not a valid request line is an ordinary comment — ignored.
    • Headers: Name: Value lines after the request line.
    • Directives (before the body): # @claim name=value sets the acting principal (repeatable; no @claim = anonymous), # @response name names the captured response table.
    • Body: everything after the first blank line, verbatim.

    An HTTP block is an act step, not an assertion — the assertions are the SQL statements that follow it. One request per block; use multiple blocks for multiple calls.

    Endpoint kinds that cannot work in-process are rejected with a clear error: SSE, upload, login/logout (inject the principal with # @claim instead), and outbound proxy/HTTP-type endpoints (tests must not call external services). A request whose path matches no endpoint still runs — a test may assert a 404 deliberately — but logs a warning, since the most common cause is a path typo or a missing /api prefix.

    The response table

    Each HTTP block's response lands in its own fresh temp table on the test's connection — default _response (one block per file) or _response_1, _response_2, … (several). Columns: status int, body text, content_type text, headers jsonb, is_success boolean — all configurable. Need to see a captured response after the run (temp tables vanish with the rollback)? Set ResponseTempTable.DebugTable to mirror every response into a permanent, query-editor-friendly table.

    sql
    sql
    select status = 200, 'status ok' from _response;
    +select body::jsonb ->> 'email' = 'x@y.z', 'right user returned' from _response;
    +select headers ->> 'Content-Type' like 'application/json%', 'json response' from _response;

    Transactions: when to begin/rollback

    The file owns transaction control. Two patterns:

    • The test writes something → wrap it: begin; … rollback;. Everything — fixtures and endpoint writes — is discarded.
    • The test only reads → no transaction needed at all. Don't cargo-cult begin/rollback onto read-only tests.

    One caveat worth knowing: sequences are non-transactional. Every nextval() sticks even through rollback, so on a shared database a generated id depends on what ran before. Don't assert generated ids on a shared database — or give the test its own database (see per-test isolation below).

    Fixtures without inserting the whole database: deferrable constraints

    The classic fixture problem: to insert one orders row you need a user, which needs a company, which needs a country… and suddenly every test starts by populating half the schema. PostgreSQL solves this elegantly — and the rollback-based test pattern is exactly the situation the solution was made for.

    Declare foreign keys deferrable in your schema:

    sql
    sql
    create table posts (
    +    id int primary key,
    +    user_id int references users (user_id) deferrable,
    +    content text not null
    +);

    Then a test defers the checks and inserts only what it needs — in any order, referencing rows that never exist:

    sql
    sql
    begin;
    +
    +set constraints all deferred;
    +
    +-- one post by a user that is never inserted — legal, because the FK check
    +-- would run at COMMIT, and this transaction never commits
    +insert into posts (id, user_id, content) values (1, 999, 'fixture post');
    +
    +/*
    +GET /api/get-posts
    +*/
    +select body::jsonb -> 0 ->> 'content' = 'fixture post', 'fixture is served' from _response;
    +
    +rollback;

    Deferrable constraints are checked at COMMIT — and a test that ends in rollback never gets there, so the checks simply never run. No fixture factories, no dependency-ordered builders, no "insert the world" preamble: each test states exactly the rows it is about, and the endpoint's LEFT JOINs resolve the missing references to null just as they would for genuinely absent data.

    Two things to know:

    • The constraint must be declared deferrableset constraints all deferred has no effect on the default NOT DEFERRABLE constraints. Making FKs deferrable is a one-time schema decision that costs nothing in production (they still check at commit).
    • This composes with the ordinary fixture style: insert the full graph in dependency order when the test is about the graph, and defer when it isn't. Example 20 demonstrates both side by side, and the end-to-end type checking post covers the technique in depth.

    Reusing SQL: includes

    Test files support psql-style includes: \\i path (cwd-relative) and \\ir path (relative to the including file). Semantics are as if you pasted the content yourself: SQL statements and HTTP blocks are spliced in place, run on the test's connection inside its transaction, and HTTP blocks participate in response-table numbering.

    sql
    sql
    begin;
    +
    +\\ir fixtures/extra_users.sql   -- reusable fixture, rolls back with the test
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select jsonb_array_length(body::jsonb) = 5,
    +       'three seeded + two fixture users are listed'
    +from _response;
    +
    +rollback;

    An included file that contains only comments is an annotation profile: included in a file's header, its annotations (@setup, @teardown, @connection, @tag) count as if written in-place — one shared profile can configure a whole family of tests. Includes nest (up to 16 levels), and cycles are detected and reported.

    Pattern: schema-relaxing system scripts. Because PostgreSQL DDL is transactional, an include can temporarily reshape the schema for the test — and the rollback restores everything. The classic use: a shared script that drops NOT NULL from columns that are irrelevant to most tests, so fixtures only mention the columns they are actually about:

    sql
    sql
    -- fixtures/relax_users.sql — make the noise columns optional for this test only
    +alter table users alter column legal_name drop not null;
    +alter table users alter column billing_address drop not null;
    +alter table users alter column marketing_consent drop not null;
    sql
    sql
    begin;
    +\\ir fixtures/relax_users.sql
    +
    +-- insert ONLY what this test is about — the relaxed columns stay null
    +insert into users (id, email) values (100, 'fixture@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture listed' from _response;
    +
    +rollback;   -- the ALTERs roll back too — the schema is untouched

    This composes with deferrable constraints: defer the FKs, relax the NOT NULLs, and a fixture shrinks to exactly the columns and rows under test. One caveat: ALTER TABLE takes an exclusive lock until the transaction ends, so on a shared test database this serializes parallel tests touching the same table — it shines with per-test isolated databases, where the lock contends with nobody.

    Per-file annotations

    Four header annotations (leading -- comments before the first statement) configure an individual file — see their reference pages for details:

    AnnotationEffect
    -- @setup Step [Step ...]Run named steps before this file.
    -- @teardown Step [Step ...]Run named steps after this file — always.
    -- @connection NameRun this file (SQL + endpoint calls) on a named connection.
    -- @tag name [name ...]Tag the file for Tag/ExcludeTag filtering.

    All are repeatable; names may be whitespace- or comma-separated; steps run in written order.

    Setup, Teardown, and named steps

    TestRunner.Setup runs once before endpoint discovery; TestRunner.Teardown runs once at the end — always, best-effort, even on failure, Ctrl+C, SIGTERM, or a hard startup error. Steps run in the exact order written. Each step is either an inline object or a name from the reusable Steps registry:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up", "WorkingDirectory": "." },
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    Three step shapes:

    • { "Sql": "..." } — SQL text, statement by statement, on the test connection or any named ConnectionStrings entry ("ConnectionName"). This is how create database works as a plain step — the runner never issues DDL on its own.
    • { "SqlFile": "..." } — same, from a file.
    • { "Command": "...", "WorkingDirectory": "..." } — an OS shell command.

    Every step also has an "Enabled" flag (default true): a disabled step is ignored wherever referenced — never an error. That's how the default configuration ships ready-made example steps (create/drop a test database, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) that you copy and flip on instead of typing.

    Random tokens: {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once, stable for the entire run, and substituted in connection strings and Setup/Teardown SQL alike — so the same unique database name lands in the connection string, the create step, and the drop step. {rndN_1}{rndN_9} are independent instances for when several distinct names of the same length are needed.

    The scenarios below are all combinations of these pieces.

    Scenario: dedicated test database per run

    Run every test against a fresh database created for this run — the app's real database is never touched. This is examples/20_testing_newdb in full:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Admin": "Host=localhost;Database=postgres;Username=postgres;Password=...",
    +    "Test":  "Host=localhost;Database=app_test_{rnd5};Username=postgres;Password=..."
    +  },
    +  "TestRunner": {
    +    "FilePattern": "./tests/**/*.test.sql",
    +    "ConnectionName": "Test",
    +    "Steps": {
    +      "CreateDatabase": { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up --config=./db.js" },
    +      "DropDatabase": { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    The flow: Setup creates app_test_xxxxx on the Admin connection and migrates it → endpoints are discovered and type-checked against that database (ConnectionName: "Test") → tests run → Teardown drops it. {rnd5} guarantees parallel CI jobs never collide.

    Keep the test configuration in a separate overlay file so the same project runs normally without it:

    sh
    sh
    npgsqlrest ./config.json ./test-config.json --test

    Scenario: template database and per-test isolation

    For tests that need complete isolation — deterministic sequence ids, exclusive locks, destructive DDL — clone a template database per test file. This is examples/21_testing_isolation:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Admin":     "...Database=postgres...",
    +    "Test":      "...Database=app_test_{rnd5}...",
    +    "Isolated1": "...Database=app_iso_{rnd5_1}...",
    +    "Isolated2": "...Database=app_iso_{rnd5_2}..."
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test",
    +    "Steps": {
    +      "CreateTemplate":    { "Sql": "create database app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "MigrateTemplate":   { "Command": "bun db up --db=app_template_{rnd5}" },
    +      "CreateRunDb":       { "Sql": "create database app_test_{rnd5} template app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "CreateIsolatedDb1": { "Sql": "create database app_iso_{rnd5_1} template app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "DropIsolatedDb1":   { "Sql": "drop database if exists app_iso_{rnd5_1} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateTemplate", "MigrateTemplate", "CreateRunDb" ],
    +    "Teardown": [ "DropTestDb", "DropTemplate" ]
    +  }
    +}

    Migrations run once (into the template); every clone is a byte-identical, instant copy (CREATE DATABASE ... TEMPLATE is a file-level copy — milliseconds for a schema-sized database). Most tests share the run database; a test that needs isolation attaches its own clone with header annotations:

    sql
    sql
    -- @setup CreateIsolatedDb1
    +-- @teardown DropIsolatedDb1
    +-- @connection Isolated1
    +-- @tag isolation, slow
    +
    +/*
    +POST /api/create-user
    +Content-Type: application/json
    +
    +{"name": "Ada", "email": "ada@example.com"}
    +*/
    +select body::jsonb ->> 'id' = '4',
    +       'sequence ids are deterministic in a fresh clone'
    +from _response;

    The classic motivation is sequences: nextval() survives rollback, so on a shared database this assertion would depend on run order — in a private clone it is exact. The indexed tokens ({rnd5_1}, {rnd5_2}) let several isolated tests hold their own clone simultaneously under parallel execution. Put the three annotations in a shared profile (\\ir shared/isolated_database.sql) and attaching isolation to a test becomes a one-liner.

    Scenario: external migration runners

    Command steps run anything — so any migration tool works as-is. The step inherits the process environment plus the run's {rnd} substitutions in its command line:

    json
    json
    // EF Core
    +{ "Command": "dotnet ef database update --connection \\"Host=localhost;Database=app_test_{rnd5};...\\"" }
    +
    +// Django
    +{ "Command": "python manage.py migrate", "WorkingDirectory": "./backend" }
    +
    +// Flyway
    +{ "Command": "flyway -url=jdbc:postgresql://localhost/app_test_{rnd5} migrate" }
    +
    +// psql — plain SQL migrations, no tooling at all
    +{ "Command": "psql -d app_test_{rnd5} -f ./migrations/schema.sql" }

    Or skip external tools entirely: SqlFile steps run migration scripts statement-by-statement on any named connection — no client tooling required in the CI image.

    Scenario: Docker

    Because Setup/Teardown are ordered shell commands, the runner can own the entire database lifecycle, container included:

    json
    json
    {
    +  "TestRunner": {
    +    "Setup": [
    +      { "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=test -p 54329:5432 postgres:17" },
    +      { "Command": "until docker exec npgsqlrest-test-pg pg_isready -U postgres; do sleep 0.3; done" },
    +      "CreateDatabase",
    +      "ApplyMigrations"
    +    ],
    +    "Teardown": [
    +      { "Command": "docker rm -f npgsqlrest-test-pg" }
    +    ]
    +  }
    +}

    Point the connection strings at Port=54329 and the whole test run is hermetic: npgsqlrest --test starts PostgreSQL, builds the schema, runs the tests, and removes the container — pass or fail.

    Scenario: testing least-privilege (PoLP) setups

    When the application connects as a restricted role, use two connections deliberately: fixtures and DDL on the Admin connection (via @setup steps or Setup), while the tests — and the endpoints they invoke — run as the restricted application role (TestRunner.ConnectionName). A test then proves not just behavior but permissions: if the app role is missing a grant, the endpoint fails in the test exactly as it would in production. An expected-denial test asserts the error directly:

    sql
    sql
    /*
    +POST /api/admin-only-report
    +# @claim user_id=7
    +*/
    +select status = 404, 'restricted role cannot reach the admin endpoint'
    +from _response;

    Filtering and tags

    Iterating on one test — Filter matches the cwd-relative path (substring, or glob with wildcards):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:filter=login

    Suites — files declare -- @tag and runs narrow by tag (case-insensitive; exclude wins; composes with Filter):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Watch mode

    sh
    sh
    npgsqlrest ./config.json --test --watch

    Runs everything once, then re-runs on changes until Ctrl+C (--watch is the shorthand for the top-level Watch:Enabled setting):

    • a changed test file re-runs alone (typically tens of milliseconds);
    • a changed endpoint file rebuilds the endpoints in-process and re-runs everything, printing the endpoint delta — break an endpoint's SQL and you immediately see - GET /api/get-users (endpoint dropped — check its SQL file for errors) plus the failing tests; fix it and the next save brings it back. No restart, ever;
    • a database routine changecreate or replace/drop/comment on a function in psql, or a migration touching routines — rebuilds endpoints and re-runs everything too (— change detected (database) —). Detection polls the routine discovery query itself, hashed server-side (every 2s by default; Watch:DatabasePollingInterval), so it fires exactly when the discovered endpoints change and never on unrelated tables or temp objects;
    • any other changed .sql under the test tree (a fixture whose dependents are unknown) re-runs everything.

    Teardown runs once on exit — Ctrl+C and SIGTERM are intercepted and the test database is still dropped, even under wrappers like bun run/npm run. A graceful stop exits 0: watch is a dev loop, not a CI gate.

    Server watch

    The same flag without --test watches the running server: npgsqlrest ./config.json --watch restarts it on SQL file, configuration, and database routine changes, regenerating the TypeScript client and HTTP files on every cycle. See Watch Mode configuration.

    Endpoint coverage

    The runner knows the entire API surface it built and records every endpoint the tests invoked — so after a full run it reports the API-level analogue of code coverage, naming the endpoints no test touches:

    code
    endpoint coverage: 3/4 (75%)
    +        untested: POST /api/delete-user

    On by default for full runs (one line); suppressed automatically when the run is narrowed by Filter/Tag; forced with Coverage: true / silenced with false. CoverageThreshold: 100 turns it into a CI gate: an otherwise-green run that misses an endpoint exits 2 — forgetting to write a test for a new endpoint fails the build, by name. "Covered" means invoked at least once; untestable kinds (SSE, upload, login/logout, outbound proxy) are excluded from the ratio.

    Reporting, logging, CI

    Console report: PASS/FAIL/ERROR per file with per-assertion detail on failure (name, file:line, failing statement). DetailedReport: true additionally lists passed assertions, full failing SQL, and captured raise notice output. Colors match the Serilog console theme and are stripped automatically when output is piped.

    Log channel: the runner logs on its own NpgsqlRestTest channel — discovery at Debug, every executed statement and HTTP invocation at Verbose, raise notice by severity. Typical dev setup — mute the app, watch the tests:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off", "NpgsqlRestTest": "Verbose" } } }

    JUnit XML for CI (JUnitOutput: "./test-results.xml") — assertion names become test-case names. Exit codes: 0 pass · 1 failures · 2 errors / coverage gate · 3 setup/config error · 4 no tests found. A minimal GitHub Actions job:

    yaml
    yaml
    - run: npgsqlrest ./config.json ./test-config.json --test --testrunner:junitoutput=results.xml
    +- uses: dorny/test-reporter@v1
    +  if: always()
    +  with: { name: SQL tests, path: results.xml, reporter: java-junit }

    Troubleshooting

    • no endpoint matches GET /api/x — the response will be a 404 — path typo or missing UrlPathPrefix (default /api) in the request line.
    • PASS ... (no assertions) (flagged) — the file ran but contained no boolean-SELECT/DO-block assertion; check that your assert's first column is a boolean.
    • A test passes alone but fails in the full run — shared-state leak: an uncommitted-fixture assumption, a committed write without rollback, or a sequence-id assertion on a shared database. Wrap writes in begin/rollback, or isolate the test with a per-file clone.
    • Unsupported endpoint error — SSE/upload/login/logout/proxy endpoints cannot be invoked in-process by design; test login flows by injecting # @claim instead.
    • Leftover *_{rnd} databases — a run was killed with SIGKILL (nothing can intercept that), or Keep: true was on. Drop them manually; every graceful path (including Ctrl+C, SIGTERM, and hard startup errors) tears down automatically.

    Reference

    `,119)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/guide_testing.md.Mc_wQPBP.lean.js b/assets/guide_testing.md.Mc_wQPBP.lean.js new file mode 100644 index 000000000..38b41b1bc --- /dev/null +++ b/assets/guide_testing.md.Mc_wQPBP.lean.js @@ -0,0 +1 @@ +import{_ as i,c as a,o as n,a5 as e}from"./chunks/framework.CgT1UzWm.js";const c=JSON.parse('{"title":"Testing Guide","titleTemplate":"NpgsqlRest","description":"Test NpgsqlRest endpoints with plain SQL files. In-process endpoint invocation, transactional isolation, test databases, template clones, migrations, Docker, watch mode, coverage, and CI.","frontmatter":{"outline":[2,3],"title":"Testing Guide","titleTemplate":"NpgsqlRest","description":"Test NpgsqlRest endpoints with plain SQL files. In-process endpoint invocation, transactional isolation, test databases, template clones, migrations, Docker, watch mode, coverage, and CI.","head":[["meta",{"name":"keywords","content":"npgsqlrest testing, sql test runner, postgresql api testing, test database, template database, test isolation, junit, endpoint coverage, watch mode"}],["meta",{"property":"og:title","content":"NpgsqlRest Testing Guide"}],["meta",{"property":"og:description","content":"Test NpgsqlRest endpoints with plain SQL files — in-process, transactional, CI-ready."}],["meta",{"property":"og:type","content":"article"}]]},"headers":[],"relativePath":"guide/testing.md","filePath":"guide/testing.md"}'),t={name:"guide/testing.md"};function l(p,s,h,r,o,k){return n(),a("div",null,s[0]||(s[0]=[e("",119)]))}const g=i(t,[["render",l]]);export{c as __pageData,g as default}; diff --git a/assets/index.md.CcMKxpaP.js b/assets/index.md.CcMKxpaP.js new file mode 100644 index 000000000..72d869e6e --- /dev/null +++ b/assets/index.md.CcMKxpaP.js @@ -0,0 +1,17 @@ +import{p as r}from"./chunks/presentationSlides.D90XTQsY.js";import{C as t,c,o as p,a5 as a,G as i,j as s,k as d}from"./chunks/framework.CgT1UzWm.js";const h={class:"annotation-showcase"},g={class:"annotation-showcase"},y=JSON.parse('{"title":"NpgsqlRest - Automatic REST API for PostgreSQL","titleTemplate":false,"description":"Your SQL is the API. Annotate PostgreSQL functions and SQL files to declare HTTP routes, auth, caching, and rate limits — get a fast, typed REST API with no controllers and no boilerplate.","frontmatter":{"layout":"home","title":"NpgsqlRest - Automatic REST API for PostgreSQL","titleTemplate":false,"description":"Your SQL is the API. Annotate PostgreSQL functions and SQL files to declare HTTP routes, auth, caching, and rate limits — get a fast, typed REST API with no controllers and no boilerplate.","head":[["meta",{"name":"keywords","content":"npgsqlrest, postgresql rest api, postgres api generator, database rest api, automatic api, postgresql web server, typescript code generation, database first development, postgresql mcp server, mcp tools, ai agent api, model context protocol, declarative backend, sql testing, api test runner, watch mode, dev server reload"}],["meta",{"property":"og:image","content":"https://npgsqlrest.github.io/terminal.png"}],["meta",{"property":"og:title","content":"NpgsqlRest - Automatic PostgreSQL Web Server"}],["meta",{"property":"og:description","content":"Your SQL is the API. PostgreSQL is the architecture, not a detail to abstract away."}],["meta",{"name":"twitter:card","content":"summary"}],["meta",{"name":"twitter:image","content":"https://npgsqlrest.github.io/terminal.png"}]],"hero":{"name":"NpgsqlRest","text":"Your SQL is the API","tagline":"Annotate PostgreSQL functions and SQL files with comments to declare HTTP routes, auth, caching, retries, and rate limits. Get a fast, typed REST API — no controllers, no models, no boilerplate. Then test it with SQL files too.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/"},{"theme":"alt","text":"View on GitHub","link":"https://github.com/NpgsqlRest/NpgsqlRest"}]},"features":[{"icon":"⚡","title":"Fast & native","details":"AOT executables. 4,500+ req/s on a single host in independent benchmarks."},{"icon":"📝","title":"Declare in SQL","details":"Caching, auth, retries, rate limits — declared as SQL comments next to the query."},{"icon":"🐘","title":"Database-first","details":"PostgreSQL is the architecture — not a detail to abstract away."},{"icon":"🔄","title":"Typed clients","details":"PostgreSQL types generate typed TypeScript clients automatically. No drift."},{"icon":"🔒","title":"Secure by default","details":"Cookies, JWT, OAuth2, conditional caching, per-user limits — built in."},{"icon":"🤖","title":"AI-agent ready","details":"@mcp exposes any endpoint as an MCP tool for AI agents — same auth, same rate limits (v3.17)."},{"icon":"✅","title":"Tests in SQL","details":"npgsqlrest --test runs plain .sql tests against real endpoints, in-process and transactional — with watch mode and endpoint coverage (v3.19)."},{"icon":"💚","title":"Open source","details":"MIT-licensed. No paid tier, no telemetry, no vendor lock-in."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),u={name:"index.md"},v=Object.assign(u,{setup(m){return(b,e)=>{const n=t("SlideDeck"),l=t("SqlFileShowcase"),o=t("FunctionShowcase");return p(),c("div",null,[e[0]||(e[0]=a('
    #1 of 14
    frameworks benchmarked,
    4,500+ req/s¹
    0
    lines of C# or Python
    in a production app²
    faster iteration on
    signature changes³
    MIT
    licensed,
    open source
    • Declare, don't code — caching, auth, retries, rate limiting — all declared as SQL annotations.
    • PostgreSQL at the center — the opposite of Clean Architecture: the database drives everything.
    • Types flow outward — PostgreSQL types generate TypeScript clients automatically.
    • No middle tier — no controllers, no models, no mapping layers, no boilerplate.
    • Iterate 5× faster — schema is the single source of truth; signature changes propagate to typed clients automatically, and an entire class of type-drift bugs simply cannot happen.
    • Production-grade by default — response caching, rate limiting, retries, PostgreSQL multi-host failover, load balancing, and Excel/HTML response rendering — configured in JSON, not custom middleware.
    • You write SQL, not a URL query language — unlike client-composed query APIs, the API surface is exactly the SQL you wrote: joins, CTEs, window functions — auditable with grep.
    • The whole dev loop in SQL — tests are plain .sql files run against real endpoints in-process (--test), and watch mode (--watch) restarts on SQL, config, and even database routine changes.
    • Built for the AI era — one type system to reason about, machine-verified output (schema check at startup, generated TypeScript checked by tsc), and @mcp tools for AI agents since v3.17.

    The Whole Idea, in 19 Slides

    PostgreSQL in. REST API, typed TypeScript client, and AI-agent tools out — with real, reproducible numbers from a product in production. Use the arrows, thumbnails, or your keyboard (←/→, F for fullscreen, N for speaker notes).

    ',4)),i(n,{slides:d(r),title:"The backend that writes itself · 2026"},null,8,["slides"]),e[1]||(e[1]=a('

    A Fully Declarative Backend

    SQL declares what data. Annotations declare what behavior. Configuration declares what infrastructure. Tests declare what correctness — also in SQL. There is no imperative glue anywhere — no controllers, no services, no mappers to keep in sync. And it is built on the declarative language that has been running the world's data for 50 years — the one every developer, and every LLM, already knows: SQL.

    Declarative Annotations

    Declare what you want from your endpoint — caching, authorization, timeouts, retries, rate limiting — right where the SQL lives.

    SQL File

    ',6)),s("div",h,[i(l)]),e[2]||(e[2]=s("div",{style:{"text-align":"center",margin:"1rem 0"}},[s("a",{href:"/guide/sql-files",class:"annotation-link"},"SQL File Endpoints Guide →")],-1)),e[3]||(e[3]=s("h3",{id:"function-example",tabindex:"-1",class:"section-sub-heading"}," PostgreSQL Function ",-1)),s("div",g,[i(o)]),e[4]||(e[4]=a(`

    Tests Are SQL Files Too

    No test framework, no running server, no mocks. npgsqlrest --test invokes the real endpoint pipeline in-process, on the test's own transaction — insert fixtures, call the endpoint (it sees your uncommitted rows), assert with SQL, roll back.

    sql
    -- tests/get_users.test.sql
    +begin;
    +
    +insert into users (email) values ('fixture@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200, 'authenticated caller gets 200' from _response;
    +select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture is listed' from _response;
    +
    +rollback;
    console
    $ npgsqlrest ./config.json --test
    +
    +PASS  tests/get_users.test.sql  (2 assertions, 52ms)
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +endpoint coverage: 2/2 (100%)

    Parallel isolated connections, throwaway test databases, per-test clones, tags, JUnit XML, and endpoint coverage with a CI threshold gate. And with --watch, the running server restarts on SQL file, configuration, and database routine changes — create or replace a function in psql and the endpoint is live seconds later, TypeScript client regenerated.

    From the Blog


    Build, Test, Publish and ReleaseLicenseGitHub StarsGitHub ForksCrafted with Claude
    ❤️ Support this project: Patreon · Buy Me a Coffee
    Released under the MIT License.
    Copyright © 2024-2026 VB Consulting
    `,14))])}}});export{y as __pageData,v as default}; diff --git a/assets/index.md.CcMKxpaP.lean.js b/assets/index.md.CcMKxpaP.lean.js new file mode 100644 index 000000000..5ce15e394 --- /dev/null +++ b/assets/index.md.CcMKxpaP.lean.js @@ -0,0 +1 @@ +import{p as r}from"./chunks/presentationSlides.D90XTQsY.js";import{C as t,c,o as p,a5 as a,G as i,j as s,k as d}from"./chunks/framework.CgT1UzWm.js";const h={class:"annotation-showcase"},g={class:"annotation-showcase"},y=JSON.parse('{"title":"NpgsqlRest - Automatic REST API for PostgreSQL","titleTemplate":false,"description":"Your SQL is the API. Annotate PostgreSQL functions and SQL files to declare HTTP routes, auth, caching, and rate limits — get a fast, typed REST API with no controllers and no boilerplate.","frontmatter":{"layout":"home","title":"NpgsqlRest - Automatic REST API for PostgreSQL","titleTemplate":false,"description":"Your SQL is the API. Annotate PostgreSQL functions and SQL files to declare HTTP routes, auth, caching, and rate limits — get a fast, typed REST API with no controllers and no boilerplate.","head":[["meta",{"name":"keywords","content":"npgsqlrest, postgresql rest api, postgres api generator, database rest api, automatic api, postgresql web server, typescript code generation, database first development, postgresql mcp server, mcp tools, ai agent api, model context protocol, declarative backend, sql testing, api test runner, watch mode, dev server reload"}],["meta",{"property":"og:image","content":"https://npgsqlrest.github.io/terminal.png"}],["meta",{"property":"og:title","content":"NpgsqlRest - Automatic PostgreSQL Web Server"}],["meta",{"property":"og:description","content":"Your SQL is the API. PostgreSQL is the architecture, not a detail to abstract away."}],["meta",{"name":"twitter:card","content":"summary"}],["meta",{"name":"twitter:image","content":"https://npgsqlrest.github.io/terminal.png"}]],"hero":{"name":"NpgsqlRest","text":"Your SQL is the API","tagline":"Annotate PostgreSQL functions and SQL files with comments to declare HTTP routes, auth, caching, retries, and rate limits. Get a fast, typed REST API — no controllers, no models, no boilerplate. Then test it with SQL files too.","actions":[{"theme":"brand","text":"Get Started","link":"/guide/"},{"theme":"alt","text":"View on GitHub","link":"https://github.com/NpgsqlRest/NpgsqlRest"}]},"features":[{"icon":"⚡","title":"Fast & native","details":"AOT executables. 4,500+ req/s on a single host in independent benchmarks."},{"icon":"📝","title":"Declare in SQL","details":"Caching, auth, retries, rate limits — declared as SQL comments next to the query."},{"icon":"🐘","title":"Database-first","details":"PostgreSQL is the architecture — not a detail to abstract away."},{"icon":"🔄","title":"Typed clients","details":"PostgreSQL types generate typed TypeScript clients automatically. No drift."},{"icon":"🔒","title":"Secure by default","details":"Cookies, JWT, OAuth2, conditional caching, per-user limits — built in."},{"icon":"🤖","title":"AI-agent ready","details":"@mcp exposes any endpoint as an MCP tool for AI agents — same auth, same rate limits (v3.17)."},{"icon":"✅","title":"Tests in SQL","details":"npgsqlrest --test runs plain .sql tests against real endpoints, in-process and transactional — with watch mode and endpoint coverage (v3.19)."},{"icon":"💚","title":"Open source","details":"MIT-licensed. No paid tier, no telemetry, no vendor lock-in."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),u={name:"index.md"},v=Object.assign(u,{setup(m){return(b,e)=>{const n=t("SlideDeck"),l=t("SqlFileShowcase"),o=t("FunctionShowcase");return p(),c("div",null,[e[0]||(e[0]=a("",4)),i(n,{slides:d(r),title:"The backend that writes itself · 2026"},null,8,["slides"]),e[1]||(e[1]=a("",6)),s("div",h,[i(l)]),e[2]||(e[2]=s("div",{style:{"text-align":"center",margin:"1rem 0"}},[s("a",{href:"/guide/sql-files",class:"annotation-link"},"SQL File Endpoints Guide →")],-1)),e[3]||(e[3]=s("h3",{id:"function-example",tabindex:"-1",class:"section-sub-heading"}," PostgreSQL Function ",-1)),s("div",g,[i(o)]),e[4]||(e[4]=a("",14))])}}});export{y as __pageData,v as default}; diff --git a/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 new file mode 100644 index 000000000..b6b603d59 Binary files /dev/null and b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 differ diff --git a/assets/inter-italic-cyrillic.By2_1cv3.woff2 b/assets/inter-italic-cyrillic.By2_1cv3.woff2 new file mode 100644 index 000000000..def40a4f6 Binary files /dev/null and b/assets/inter-italic-cyrillic.By2_1cv3.woff2 differ diff --git a/assets/inter-italic-greek-ext.1u6EdAuj.woff2 b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 new file mode 100644 index 000000000..e070c3d30 Binary files /dev/null and b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 differ diff --git a/assets/inter-italic-greek.DJ8dCoTZ.woff2 b/assets/inter-italic-greek.DJ8dCoTZ.woff2 new file mode 100644 index 000000000..a3c16ca40 Binary files /dev/null and b/assets/inter-italic-greek.DJ8dCoTZ.woff2 differ diff --git a/assets/inter-italic-latin-ext.CN1xVJS-.woff2 b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 new file mode 100644 index 000000000..2210a899e Binary files /dev/null and b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 differ diff --git a/assets/inter-italic-latin.C2AdPX0b.woff2 b/assets/inter-italic-latin.C2AdPX0b.woff2 new file mode 100644 index 000000000..790d62dc7 Binary files /dev/null and b/assets/inter-italic-latin.C2AdPX0b.woff2 differ diff --git a/assets/inter-italic-vietnamese.BSbpV94h.woff2 b/assets/inter-italic-vietnamese.BSbpV94h.woff2 new file mode 100644 index 000000000..1eec0775a Binary files /dev/null and b/assets/inter-italic-vietnamese.BSbpV94h.woff2 differ diff --git a/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 new file mode 100644 index 000000000..2cfe61536 Binary files /dev/null and b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 differ diff --git a/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 new file mode 100644 index 000000000..e3886dd14 Binary files /dev/null and b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 differ diff --git a/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 new file mode 100644 index 000000000..36d67487d Binary files /dev/null and b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 differ diff --git a/assets/inter-roman-greek.BBVDIX6e.woff2 b/assets/inter-roman-greek.BBVDIX6e.woff2 new file mode 100644 index 000000000..2bed1e85e Binary files /dev/null and b/assets/inter-roman-greek.BBVDIX6e.woff2 differ diff --git a/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 new file mode 100644 index 000000000..9a8d1e2b5 Binary files /dev/null and b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 differ diff --git a/assets/inter-roman-latin.Di8DUHzh.woff2 b/assets/inter-roman-latin.Di8DUHzh.woff2 new file mode 100644 index 000000000..07d3c53ae Binary files /dev/null and b/assets/inter-roman-latin.Di8DUHzh.woff2 differ diff --git a/assets/inter-roman-vietnamese.BjW4sHH5.woff2 b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 new file mode 100644 index 000000000..57bdc22ae Binary files /dev/null and b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 differ diff --git a/assets/style.Dq4B5IkV.css b/assets/style.Dq4B5IkV.css new file mode 100644 index 000000000..ce4e7d555 --- /dev/null +++ b/assets/style.Dq4B5IkV.css @@ -0,0 +1 @@ +@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic.C5lxZ8CY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek-ext.CqjqNYQ-.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek.BBVDIX6e.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-vietnamese.BjW4sHH5.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin-ext.4ZJIpNVo.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin.Di8DUHzh.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic-ext.r48I6akx.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic.By2_1cv3.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek-ext.1u6EdAuj.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek.DJ8dCoTZ.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-vietnamese.BSbpV94h.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin-ext.CN1xVJS-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin.C2AdPX0b.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Punctuation SC;font-weight:400;src:local("PingFang SC Regular"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:500;src:local("PingFang SC Medium"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:600;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:700;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: #3c3c43;--vp-c-text-2: #67676c;--vp-c-text-3: #929295}.dark{--vp-c-text-1: #dfdfd6;--vp-c-text-2: #98989f;--vp-c-text-3: #6a6a71}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}:root:where(:lang(zh)){--vp-font-family-base: "Punctuation SC", "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media (min-width: 960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:lang(es),:lang(pt){--vp-code-copy-copied-text-content: "Copiado"}:lang(fa){--vp-code-copy-copied-text-content: "کپی شد"}:lang(ko){--vp-code-copy-copied-text-content: "복사됨"}:lang(ru){--vp-code-copy-copied-text-content: "Скопировано"}:lang(zh){--vp-code-copy-copied-text-content: "已复制"}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media (prefers-reduced-motion: reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M21 6H3M15 12H3M17 18H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4Z'/%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m5 8 6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14 18h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7M14 9h7M14 15h7M14 20h7'/%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H9l-7 7 7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2ZM18 9l-6 6M12 9l6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 10-5 5 5 5'/%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc h4{margin:24px 0 0;letter-spacing:-.01em;line-height:24px;font-size:18px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media (min-width: 768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s;color:var(--vp-c-text-2)}.vp-doc blockquote>p{margin:0;font-size:16px;transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code,.vp-doc h4>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media (min-width: 640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media (max-width: 639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;-webkit-user-select:none;user-select:none;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(:is(.no-icon,svg a,:has(img,svg))):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-c79a1216]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-c79a1216],.VPBackdrop.fade-leave-to[data-v-c79a1216]{opacity:0}.VPBackdrop.fade-leave-active[data-v-c79a1216]{transition-duration:.25s}@media (min-width: 1280px){.VPBackdrop[data-v-c79a1216]{display:none}}.NotFound[data-v-d6be1790]{padding:64px 24px 96px;text-align:center}@media (min-width: 768px){.NotFound[data-v-d6be1790]{padding:96px 32px 168px}}.code[data-v-d6be1790]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-d6be1790]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-d6be1790]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-d6be1790]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-d6be1790]{padding-top:20px}.link[data-v-d6be1790]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-d6be1790]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-b933a997]{position:relative;z-index:1}.nested[data-v-b933a997]{padding-right:16px;padding-left:16px}.outline-link[data-v-b933a997]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-b933a997]:hover,.outline-link.active[data-v-b933a997]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-b933a997]{padding-left:13px}.VPDocAsideOutline[data-v-a5bbad30]{display:none}.VPDocAsideOutline.has-outline[data-v-a5bbad30]{display:block}.content[data-v-a5bbad30]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-a5bbad30]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-a5bbad30]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-3f215769]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-3f215769]{flex-grow:1}.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-3f215769] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-e98dd255]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 640px){.VPLastUpdated[data-v-e98dd255]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-e257564d]{margin-top:64px}.edit-info[data-v-e257564d]{padding-bottom:18px}@media (min-width: 640px){.edit-info[data-v-e257564d]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-e257564d]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-e257564d]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-e257564d]{margin-right:8px}.prev-next[data-v-e257564d]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media (min-width: 640px){.prev-next[data-v-e257564d]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-e257564d]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-e257564d]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-e257564d]{margin-left:auto;text-align:right}.desc[data-v-e257564d]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-e257564d]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-39a288b8]{padding:32px 24px 96px;width:100%}@media (min-width: 768px){.VPDoc[data-v-39a288b8]{padding:48px 32px 128px}}@media (min-width: 960px){.VPDoc[data-v-39a288b8]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:752px}}@media (min-width: 1280px){.VPDoc .container[data-v-39a288b8]{display:flex;justify-content:center}.VPDoc .aside[data-v-39a288b8]{display:block}}@media (min-width: 1440px){.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{max-width:1104px}}.container[data-v-39a288b8]{margin:0 auto;width:100%}.aside[data-v-39a288b8]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-39a288b8]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-39a288b8]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-39a288b8]::-webkit-scrollbar{display:none}.aside-curtain[data-v-39a288b8]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-39a288b8]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-39a288b8]{position:relative;margin:0 auto;width:100%}@media (min-width: 960px){.content[data-v-39a288b8]{padding:0 32px 128px}}@media (min-width: 1280px){.content[data-v-39a288b8]{order:1;margin:0;min-width:640px}}.content-container[data-v-39a288b8]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-39a288b8]{max-width:688px}.VPButton[data-v-fa7799d5]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-fa7799d5]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-fa7799d5]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-fa7799d5]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-fa7799d5]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-fa7799d5]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-fa7799d5]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-fa7799d5]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-fa7799d5]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-fa7799d5]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-fa7799d5]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-fa7799d5]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-fa7799d5]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-8426fc1a]{display:none}.dark .VPImage.light[data-v-8426fc1a]{display:none}.VPHero[data-v-4f9c455b]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media (min-width: 640px){.VPHero[data-v-4f9c455b]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media (min-width: 960px){.VPHero[data-v-4f9c455b]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-4f9c455b]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media (min-width: 960px){.container[data-v-4f9c455b]{flex-direction:row}}.main[data-v-4f9c455b]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-4f9c455b]{text-align:center}@media (min-width: 960px){.VPHero.has-image .container[data-v-4f9c455b]{text-align:left}}@media (min-width: 960px){.main[data-v-4f9c455b]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-4f9c455b]{max-width:592px}}.heading[data-v-4f9c455b]{display:flex;flex-direction:column}.name[data-v-4f9c455b],.text[data-v-4f9c455b]{width:fit-content;max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-4f9c455b],.VPHero.has-image .text[data-v-4f9c455b]{margin:0 auto}.name[data-v-4f9c455b]{color:var(--vp-home-hero-name-color)}.clip[data-v-4f9c455b]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media (min-width: 640px){.name[data-v-4f9c455b],.text[data-v-4f9c455b]{max-width:576px;line-height:56px;font-size:48px}}@media (min-width: 960px){.name[data-v-4f9c455b],.text[data-v-4f9c455b]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-4f9c455b],.VPHero.has-image .text[data-v-4f9c455b]{margin:0}}.tagline[data-v-4f9c455b]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-4f9c455b]{margin:0 auto}@media (min-width: 640px){.tagline[data-v-4f9c455b]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media (min-width: 960px){.tagline[data-v-4f9c455b]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-4f9c455b]{margin:0}}.actions[data-v-4f9c455b]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-4f9c455b]{justify-content:center}@media (min-width: 640px){.actions[data-v-4f9c455b]{padding-top:32px}}@media (min-width: 960px){.VPHero.has-image .actions[data-v-4f9c455b]{justify-content:flex-start}}.action[data-v-4f9c455b]{flex-shrink:0;padding:6px}.image[data-v-4f9c455b]{order:1;margin:-76px -24px -48px}@media (min-width: 640px){.image[data-v-4f9c455b]{margin:-108px -24px -48px}}@media (min-width: 960px){.image[data-v-4f9c455b]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-4f9c455b]{position:relative;margin:0 auto;width:320px;height:320px}@media (min-width: 640px){.image-container[data-v-4f9c455b]{width:392px;height:392px}}@media (min-width: 960px){.image-container[data-v-4f9c455b]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-4f9c455b]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media (min-width: 640px){.image-bg[data-v-4f9c455b]{width:256px;height:256px}}@media (min-width: 960px){.image-bg[data-v-4f9c455b]{width:320px;height:320px}}[data-v-4f9c455b] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media (min-width: 640px){[data-v-4f9c455b] .image-src{max-width:256px;max-height:256px}}@media (min-width: 960px){[data-v-4f9c455b] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-a3976bdc]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-a3976bdc]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-a3976bdc]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-a3976bdc]>.VPImage{margin-bottom:20px}.icon[data-v-a3976bdc]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-a3976bdc]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-a3976bdc]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-a3976bdc]{padding-top:8px}.link-text-value[data-v-a3976bdc]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-a3976bdc]{margin-left:6px}.VPFeatures[data-v-a6181336]{position:relative;padding:0 24px}@media (min-width: 640px){.VPFeatures[data-v-a6181336]{padding:0 48px}}@media (min-width: 960px){.VPFeatures[data-v-a6181336]{padding:0 64px}}.container[data-v-a6181336]{margin:0 auto;max-width:1152px}.items[data-v-a6181336]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-a6181336]{padding:8px;width:100%}@media (min-width: 640px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:50%}}@media (min-width: 768px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336]{width:50%}.item.grid-3[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:calc(100% / 3)}}@media (min-width: 960px){.item.grid-4[data-v-a6181336]{width:25%}}.container[data-v-8e2d4988]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media (min-width: 640px){.container[data-v-8e2d4988]{padding:0 48px}}@media (min-width: 960px){.container[data-v-8e2d4988]{width:100%;padding:0 64px}}.vp-doc[data-v-8e2d4988] .VPHomeSponsors,.vp-doc[data-v-8e2d4988] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-8e2d4988] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-8e2d4988] .VPHomeSponsors a,.vp-doc[data-v-8e2d4988] .VPTeamPage a{text-decoration:none}.VPHome[data-v-8b561e3d]{margin-bottom:96px}@media (min-width: 768px){.VPHome[data-v-8b561e3d]{margin-bottom:128px}}.VPContent[data-v-1428d186]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-1428d186]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-1428d186]{margin:0}@media (min-width: 960px){.VPContent[data-v-1428d186]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-1428d186]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPContent.has-sidebar[data-v-1428d186]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-e315a0ad]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-e315a0ad]{display:none}.VPFooter[data-v-e315a0ad] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-e315a0ad] a:hover{color:var(--vp-c-text-1)}@media (min-width: 768px){.VPFooter[data-v-e315a0ad]{padding:32px}}.container[data-v-e315a0ad]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-e315a0ad],.copyright[data-v-e315a0ad]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-8a42e2b4]{padding:12px 20px 11px}@media (min-width: 960px){.VPLocalNavOutlineDropdown[data-v-8a42e2b4]{padding:12px 36px 11px}}.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-8a42e2b4]{color:var(--vp-c-text-1)}.icon[data-v-8a42e2b4]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media (min-width: 960px){.VPLocalNavOutlineDropdown button[data-v-8a42e2b4]{font-size:14px}.icon[data-v-8a42e2b4]{font-size:16px}}.open>.icon[data-v-8a42e2b4]{transform:rotate(90deg)}.items[data-v-8a42e2b4]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media (min-width: 960px){.items[data-v-8a42e2b4]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-8a42e2b4]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-8a42e2b4]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-8a42e2b4]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-8a42e2b4]{transition:all .2s ease-out}.flyout-leave-active[data-v-8a42e2b4]{transition:all .15s ease-in}.flyout-enter-from[data-v-8a42e2b4],.flyout-leave-to[data-v-8a42e2b4]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-a6f0e41e]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-a6f0e41e]{position:fixed}@media (min-width: 960px){.VPLocalNav[data-v-a6f0e41e]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-a6f0e41e]{display:none}}@media (min-width: 1280px){.VPLocalNav[data-v-a6f0e41e]{display:none}}@media (min-width: 1440px){.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.container[data-v-a6f0e41e]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-a6f0e41e]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-a6f0e41e]:hover{color:var(--vp-c-text-1);transition:color .25s}@media (min-width: 768px){.menu[data-v-a6f0e41e]{padding:0 32px}}@media (min-width: 960px){.menu[data-v-a6f0e41e]{display:none}}.menu-icon[data-v-a6f0e41e]{margin-right:8px;font-size:14px}.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 24px 11px}@media (min-width: 768px){.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 32px 11px}}.VPSwitch[data-v-1d5665e3]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-1d5665e3]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-1d5665e3]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-1d5665e3]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-1d5665e3] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-1d5665e3] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-5337faa4]{opacity:1}.moon[data-v-5337faa4],.dark .sun[data-v-5337faa4]{opacity:0}.dark .moon[data-v-5337faa4]{opacity:1}.dark .VPSwitchAppearance[data-v-5337faa4] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-6c893767]{display:none}@media (min-width: 1280px){.VPNavBarAppearance[data-v-6c893767]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-35975db6]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-35975db6]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-35975db6]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-35975db6]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-69e747b5]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-69e747b5]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-69e747b5]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-69e747b5]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-b98bc113]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-b98bc113] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-b98bc113] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-b98bc113] .group:last-child{padding-bottom:0}.VPMenu[data-v-b98bc113] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-b98bc113] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-b98bc113] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-b98bc113] .action{padding-left:24px}.VPFlyout[data-v-cf11d7a2]{position:relative}.VPFlyout[data-v-cf11d7a2]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-cf11d7a2]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-cf11d7a2]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-cf11d7a2]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-cf11d7a2]{color:var(--vp-c-brand-2)}.button[aria-expanded=false]+.menu[data-v-cf11d7a2]{opacity:0;visibility:hidden;transform:translateY(0)}.VPFlyout:hover .menu[data-v-cf11d7a2],.button[aria-expanded=true]+.menu[data-v-cf11d7a2]{opacity:1;visibility:visible;transform:translateY(0)}.button[data-v-cf11d7a2]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-cf11d7a2]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-cf11d7a2]{margin-right:0;font-size:16px}.text-icon[data-v-cf11d7a2]{margin-left:4px;font-size:14px}.icon[data-v-cf11d7a2]{font-size:20px;transition:fill .25s}.menu[data-v-cf11d7a2]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-bd121fe5]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-bd121fe5]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-bd121fe5]>svg,.VPSocialLink[data-v-bd121fe5]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-7bc22406]{display:flex;justify-content:center}.VPNavBarExtra[data-v-bb2aa2f0]{display:none;margin-right:-12px}@media (min-width: 768px){.VPNavBarExtra[data-v-bb2aa2f0]{display:block}}@media (min-width: 1280px){.VPNavBarExtra[data-v-bb2aa2f0]{display:none}}.trans-title[data-v-bb2aa2f0]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-bb2aa2f0],.item.social-links[data-v-bb2aa2f0]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-bb2aa2f0]{min-width:176px}.appearance-action[data-v-bb2aa2f0]{margin-right:-2px}.social-links-list[data-v-bb2aa2f0]{margin:-4px -8px}.VPNavBarHamburger[data-v-e5dd9c1c]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media (min-width: 768px){.VPNavBarHamburger[data-v-e5dd9c1c]{display:none}}.container[data-v-e5dd9c1c]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-e5dd9c1c]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .middle[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .bottom[data-v-e5dd9c1c]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-e5dd9c1c],.middle[data-v-e5dd9c1c],.bottom[data-v-e5dd9c1c]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(0)}.middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-e56f3d57]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-e56f3d57],.VPNavBarMenuLink[data-v-e56f3d57]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-dc692963]{display:none}@media (min-width: 768px){.VPNavBarMenu[data-v-dc692963]{display:flex}}/*! @docsearch/css 3.8.2 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 #0304094d;--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 2px;position:relative;top:-1px;width:20px}.DocSearch-Button-Key--pressed{box-shadow:var(--docsearch-key-pressed-shadow);transform:translate3d(0,1px,0)}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border:0;border-radius:2px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button-Key--pressed{transform:none;box-shadow:none}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media (min-width: 768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.DocSearch-Search-Icon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke-width='1.6' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='m14.386 14.386 4.088 4.088-4.088-4.088A7.533 7.533 0 1 1 3.733 3.733a7.533 7.533 0 0 1 10.653 10.653z'/%3E%3C/svg%3E")}.VPNavBarSearch{display:flex;align-items:center}@media (min-width: 768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media (min-width: 960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-0394ad82]{display:none}@media (min-width: 1280px){.VPNavBarSocialLinks[data-v-0394ad82]{display:flex;align-items:center}}.title[data-v-1168a8e4]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media (min-width: 960px){.title[data-v-1168a8e4]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-1168a8e4]{border-bottom-color:var(--vp-c-divider)}}[data-v-1168a8e4] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-88af2de4]{display:none}@media (min-width: 1280px){.VPNavBarTranslations[data-v-88af2de4]{display:flex;align-items:center}}.title[data-v-88af2de4]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-6aa21345]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .25s}.VPNavBar.screen-open[data-v-6aa21345]{transition:none;background-color:var(--vp-nav-bg-color);border-bottom:1px solid var(--vp-c-divider)}.VPNavBar[data-v-6aa21345]:not(.home){background-color:var(--vp-nav-bg-color)}@media (min-width: 960px){.VPNavBar[data-v-6aa21345]:not(.home){background-color:transparent}.VPNavBar[data-v-6aa21345]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-6aa21345]{padding:0 8px 0 24px}@media (min-width: 768px){.wrapper[data-v-6aa21345]{padding:0 32px}}@media (min-width: 960px){.VPNavBar.has-sidebar .wrapper[data-v-6aa21345]{padding:0}}.container[data-v-6aa21345]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-6aa21345],.container>.content[data-v-6aa21345]{pointer-events:none}.container[data-v-6aa21345] *{pointer-events:auto}@media (min-width: 960px){.VPNavBar.has-sidebar .container[data-v-6aa21345]{max-width:100%}}.title[data-v-6aa21345]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar.has-sidebar .title[data-v-6aa21345]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media (min-width: 1440px){.VPNavBar.has-sidebar .title[data-v-6aa21345]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-6aa21345]{flex-grow:1}@media (min-width: 960px){.VPNavBar.has-sidebar .content[data-v-6aa21345]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .content[data-v-6aa21345]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-6aa21345]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media (min-width: 960px){.VPNavBar:not(.home.top) .content-body[data-v-6aa21345]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-6aa21345]{background-color:transparent}}@media (max-width: 767px){.content-body[data-v-6aa21345]{column-gap:.5rem}}.menu+.translations[data-v-6aa21345]:before,.menu+.appearance[data-v-6aa21345]:before,.menu+.social-links[data-v-6aa21345]:before,.translations+.appearance[data-v-6aa21345]:before,.appearance+.social-links[data-v-6aa21345]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-6aa21345]:before,.translations+.appearance[data-v-6aa21345]:before{margin-right:16px}.appearance+.social-links[data-v-6aa21345]:before{margin-left:16px}.social-links[data-v-6aa21345]{margin-right:-8px}.divider[data-v-6aa21345]{width:100%;height:1px}@media (min-width: 960px){.VPNavBar.has-sidebar .divider[data-v-6aa21345]{padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .divider[data-v-6aa21345]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-6aa21345]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-6aa21345]{background-color:var(--vp-c-gutter)}@media (min-width: 960px){.VPNavBar:not(.home.top) .divider-line[data-v-6aa21345]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-6aa21345]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-b44890b2]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-b44890b2]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-df37e6dd]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-df37e6dd]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-3e9c20e4]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-3e9c20e4]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-8133b170]{display:block}.title[data-v-8133b170]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-b9ab8c58]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-b9ab8c58]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-b9ab8c58]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-b9ab8c58]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-b9ab8c58]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-b9ab8c58]{transform:rotate(45deg)}.button[data-v-b9ab8c58]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-b9ab8c58]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-b9ab8c58]{transition:transform .25s}.group[data-v-b9ab8c58]:first-child{padding-top:0}.group+.group[data-v-b9ab8c58],.group+.item[data-v-b9ab8c58]{padding-top:4px}.VPNavScreenTranslations[data-v-858fe1a4]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-858fe1a4]{height:auto}.title[data-v-858fe1a4]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-858fe1a4]{font-size:16px}.icon.lang[data-v-858fe1a4]{margin-right:8px}.icon.chevron[data-v-858fe1a4]{margin-left:4px}.list[data-v-858fe1a4]{padding:4px 0 0 24px}.link[data-v-858fe1a4]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-f2779853]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px));right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .25s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-f2779853],.VPNavScreen.fade-leave-active[data-v-f2779853]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-f2779853],.VPNavScreen.fade-leave-active .container[data-v-f2779853]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-f2779853],.VPNavScreen.fade-leave-to[data-v-f2779853]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-f2779853],.VPNavScreen.fade-leave-to .container[data-v-f2779853]{transform:translateY(-8px)}@media (min-width: 768px){.VPNavScreen[data-v-f2779853]{display:none}}.container[data-v-f2779853]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-f2779853],.menu+.appearance[data-v-f2779853],.translations+.appearance[data-v-f2779853]{margin-top:24px}.menu+.social-links[data-v-f2779853]{margin-top:16px}.appearance+.social-links[data-v-f2779853]{margin-top:16px}.VPNav[data-v-ae24b3ad]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media (min-width: 960px){.VPNav[data-v-ae24b3ad]{position:fixed}}.VPSidebarItem.level-0[data-v-b3fd67f8]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-b3fd67f8]{padding-bottom:10px}.item[data-v-b3fd67f8]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-b3fd67f8]{cursor:pointer}.indicator[data-v-b3fd67f8]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-b3fd67f8],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-b3fd67f8]{background-color:var(--vp-c-brand-1)}.link[data-v-b3fd67f8]{display:flex;align-items:center;flex-grow:1}.text[data-v-b3fd67f8]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-b3fd67f8]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-b3fd67f8],.VPSidebarItem.level-2 .text[data-v-b3fd67f8],.VPSidebarItem.level-3 .text[data-v-b3fd67f8],.VPSidebarItem.level-4 .text[data-v-b3fd67f8],.VPSidebarItem.level-5 .text[data-v-b3fd67f8]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-b3fd67f8],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-b3fd67f8]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.has-active>.item>.text[data-v-b3fd67f8],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-b3fd67f8]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-b3fd67f8],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-b3fd67f8]{color:var(--vp-c-brand-1)}.caret[data-v-b3fd67f8]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-b3fd67f8]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-b3fd67f8]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-b3fd67f8]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-b3fd67f8]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-b3fd67f8],.VPSidebarItem.level-2 .items[data-v-b3fd67f8],.VPSidebarItem.level-3 .items[data-v-b3fd67f8],.VPSidebarItem.level-4 .items[data-v-b3fd67f8],.VPSidebarItem.level-5 .items[data-v-b3fd67f8]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-b3fd67f8]{display:none}.no-transition[data-v-c40bc020] .caret-icon{transition:none}.group+.group[data-v-c40bc020]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media (min-width: 960px){.group[data-v-c40bc020]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSidebar[data-v-319d5ca6]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-319d5ca6]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-319d5ca6]{box-shadow:var(--vp-shadow-1)}@media (min-width: 960px){.VPSidebar[data-v-319d5ca6]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media (min-width: 1440px){.VPSidebar[data-v-319d5ca6]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media (min-width: 960px){.curtain[data-v-319d5ca6]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-319d5ca6]{outline:0}.VPSkipLink[data-v-0b0ada53]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-0b0ada53]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media (min-width: 1280px){.VPSkipLink[data-v-0b0ada53]{top:14px;left:16px}}.Layout[data-v-5d98c3a5]{display:flex;flex-direction:column;min-height:100vh}.VPHomeSponsors[data-v-3d121b4a]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPHomeSponsors[data-v-3d121b4a]{margin:96px 0}@media (min-width: 768px){.VPHomeSponsors[data-v-3d121b4a]{margin:128px 0}}.VPHomeSponsors[data-v-3d121b4a]{padding:0 24px}@media (min-width: 768px){.VPHomeSponsors[data-v-3d121b4a]{padding:0 48px}}@media (min-width: 960px){.VPHomeSponsors[data-v-3d121b4a]{padding:0 64px}}.container[data-v-3d121b4a]{margin:0 auto;max-width:1152px}.love[data-v-3d121b4a]{margin:0 auto;width:fit-content;font-size:28px;color:var(--vp-c-text-3)}.icon[data-v-3d121b4a]{display:inline-block}.message[data-v-3d121b4a]{margin:0 auto;padding-top:10px;max-width:320px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.sponsors[data-v-3d121b4a]{padding-top:32px}.action[data-v-3d121b4a]{padding-top:40px;text-align:center}.VPTeamMembersItem[data-v-f3fa364a]{display:flex;flex-direction:column;gap:2px;border-radius:12px;width:100%;height:100%;overflow:hidden}.VPTeamMembersItem.small .profile[data-v-f3fa364a]{padding:32px}.VPTeamMembersItem.small .data[data-v-f3fa364a]{padding-top:20px}.VPTeamMembersItem.small .avatar[data-v-f3fa364a]{width:64px;height:64px}.VPTeamMembersItem.small .name[data-v-f3fa364a]{line-height:24px;font-size:16px}.VPTeamMembersItem.small .affiliation[data-v-f3fa364a]{padding-top:4px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .desc[data-v-f3fa364a]{padding-top:12px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .links[data-v-f3fa364a]{margin:0 -16px -20px;padding:10px 0 0}.VPTeamMembersItem.medium .profile[data-v-f3fa364a]{padding:48px 32px}.VPTeamMembersItem.medium .data[data-v-f3fa364a]{padding-top:24px;text-align:center}.VPTeamMembersItem.medium .avatar[data-v-f3fa364a]{width:96px;height:96px}.VPTeamMembersItem.medium .name[data-v-f3fa364a]{letter-spacing:.15px;line-height:28px;font-size:20px}.VPTeamMembersItem.medium .affiliation[data-v-f3fa364a]{padding-top:4px;font-size:16px}.VPTeamMembersItem.medium .desc[data-v-f3fa364a]{padding-top:16px;max-width:288px;font-size:16px}.VPTeamMembersItem.medium .links[data-v-f3fa364a]{margin:0 -16px -12px;padding:16px 12px 0}.profile[data-v-f3fa364a]{flex-grow:1;background-color:var(--vp-c-bg-soft)}.data[data-v-f3fa364a]{text-align:center}.avatar[data-v-f3fa364a]{position:relative;flex-shrink:0;margin:0 auto;border-radius:50%;box-shadow:var(--vp-shadow-3)}.avatar-img[data-v-f3fa364a]{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;object-fit:cover}.name[data-v-f3fa364a]{margin:0;font-weight:600}.affiliation[data-v-f3fa364a]{margin:0;font-weight:500;color:var(--vp-c-text-2)}.org.link[data-v-f3fa364a]{color:var(--vp-c-text-2);transition:color .25s}.org.link[data-v-f3fa364a]:hover{color:var(--vp-c-brand-1)}.desc[data-v-f3fa364a]{margin:0 auto}.desc[data-v-f3fa364a] a{font-weight:500;color:var(--vp-c-brand-1);text-decoration-style:dotted;transition:color .25s}.links[data-v-f3fa364a]{display:flex;justify-content:center;height:56px}.sp-link[data-v-f3fa364a]{display:flex;justify-content:center;align-items:center;text-align:center;padding:16px;font-size:14px;font-weight:500;color:var(--vp-c-sponsor);background-color:var(--vp-c-bg-soft);transition:color .25s,background-color .25s}.sp .sp-link.link[data-v-f3fa364a]:hover,.sp .sp-link.link[data-v-f3fa364a]:focus{outline:none;color:var(--vp-c-white);background-color:var(--vp-c-sponsor)}.sp-icon[data-v-f3fa364a]{margin-right:8px;font-size:16px}.VPTeamMembers.small .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(224px,1fr))}.VPTeamMembers.small.count-1 .container[data-v-6cb0dbc4]{max-width:276px}.VPTeamMembers.small.count-2 .container[data-v-6cb0dbc4]{max-width:576px}.VPTeamMembers.small.count-3 .container[data-v-6cb0dbc4]{max-width:876px}.VPTeamMembers.medium .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(256px,1fr))}@media (min-width: 375px){.VPTeamMembers.medium .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(288px,1fr))}}.VPTeamMembers.medium.count-1 .container[data-v-6cb0dbc4]{max-width:368px}.VPTeamMembers.medium.count-2 .container[data-v-6cb0dbc4]{max-width:760px}.container[data-v-6cb0dbc4]{display:grid;gap:24px;margin:0 auto;max-width:1152px}.VPTeamPage[data-v-7c57f839]{margin:96px 0}@media (min-width: 768px){.VPTeamPage[data-v-7c57f839]{margin:128px 0}}.VPHome .VPTeamPageTitle[data-v-7c57f839-s]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPTeamPageSection+.VPTeamPageSection[data-v-7c57f839-s],.VPTeamMembers+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:64px}.VPTeamMembers+.VPTeamMembers[data-v-7c57f839-s]{margin-top:24px}@media (min-width: 768px){.VPTeamPageTitle+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:16px}.VPTeamPageSection+.VPTeamPageSection[data-v-7c57f839-s],.VPTeamMembers+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:96px}}.VPTeamMembers[data-v-7c57f839-s]{padding:0 24px}@media (min-width: 768px){.VPTeamMembers[data-v-7c57f839-s]{padding:0 48px}}@media (min-width: 960px){.VPTeamMembers[data-v-7c57f839-s]{padding:0 64px}}.VPTeamPageSection[data-v-b1a88750]{padding:0 32px}@media (min-width: 768px){.VPTeamPageSection[data-v-b1a88750]{padding:0 48px}}@media (min-width: 960px){.VPTeamPageSection[data-v-b1a88750]{padding:0 64px}}.title[data-v-b1a88750]{position:relative;margin:0 auto;max-width:1152px;text-align:center;color:var(--vp-c-text-2)}.title-line[data-v-b1a88750]{position:absolute;top:16px;left:0;width:100%;height:1px;background-color:var(--vp-c-divider)}.title-text[data-v-b1a88750]{position:relative;display:inline-block;padding:0 24px;letter-spacing:0;line-height:32px;font-size:20px;font-weight:500;background-color:var(--vp-c-bg)}.lead[data-v-b1a88750]{margin:0 auto;max-width:480px;padding-top:12px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.members[data-v-b1a88750]{padding-top:40px}.VPTeamPageTitle[data-v-bf2cbdac]{padding:48px 32px;text-align:center}@media (min-width: 768px){.VPTeamPageTitle[data-v-bf2cbdac]{padding:64px 48px 48px}}@media (min-width: 960px){.VPTeamPageTitle[data-v-bf2cbdac]{padding:80px 64px 48px}}.title[data-v-bf2cbdac]{letter-spacing:0;line-height:44px;font-size:36px;font-weight:500}@media (min-width: 768px){.title[data-v-bf2cbdac]{letter-spacing:-.5px;line-height:56px;font-size:48px}}.lead[data-v-bf2cbdac]{margin:0 auto;max-width:512px;padding-top:12px;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 768px){.lead[data-v-bf2cbdac]{max-width:592px;letter-spacing:.15px;line-height:28px;font-size:20px}}html,body{overflow-x:clip}.vp-doc :not(pre)>code{word-break:break-word}.table-container{position:relative;margin:20px 0}.table-wrapper table{display:table;overflow:visible;margin:0;width:100%}.vp-doc table code{white-space:normal}.vp-doc table td:not(:first-child) code{word-break:break-all}.vp-doc table td:first-child code{white-space:nowrap}.vp-doc .table-wrapper th{position:sticky;top:var(--vp-nav-height);z-index:10}.table-container:after{content:"";position:absolute;top:0;right:0;bottom:0;width:2rem;background:linear-gradient(to right,transparent,var(--vp-c-bg));pointer-events:none;z-index:11;opacity:0;transition:opacity .2s}@media (max-width: 768px){.table-wrapper{overflow-x:auto}.vp-doc .table-wrapper th{position:static}.table-container:after{opacity:1}.vp-doc table th:not(:last-child),.vp-doc table td:not(:last-child){white-space:nowrap;width:1%}.vp-doc table th:last-child,.vp-doc table td:last-child{min-width:250px}}.vp-doc img{max-width:100%;height:auto}:root{--logo-elephant-color: #0d7377;--logo-network-color: #32a8a2;--logo-network-nodes: #14919b;--logo-background: transparent;--vp-c-bg: #f5f6f8;--vp-c-bg-soft: #eef0f3;--vp-c-bg-alt: #ffffff;--vp-sidebar-bg-color: #eef0f3;--vp-local-search-bg: #eef0f3;--vp-local-search-result-bg: #eef0f3;--vp-font-family-base: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, "Ubuntu", "Droid Sans", sans-serif;--vp-c-accent-1: #d97706;--vp-c-accent-2: #f59e0b;--vp-c-accent-3: #fbbf24;--vp-c-accent-soft: rgba(217, 119, 6, .14);--vp-button-brand-bg: var(--vp-c-accent-1);--vp-button-brand-hover-bg: #b45309;--vp-button-brand-active-bg: #92400e;--vp-button-brand-border: var(--vp-c-accent-1);--vp-button-brand-hover-border: #b45309;--vp-button-brand-active-border: #92400e;--vp-button-brand-text: #ffffff;--vp-button-brand-hover-text: #ffffff;--vp-button-brand-active-text: #ffffff}.dark{--logo-elephant-color: #5dd9d1;--logo-network-color: #7be8e0;--logo-network-nodes: #4ecdc4;--logo-background: transparent;--vp-c-bg: #11111b;--vp-c-bg-soft: #181825;--vp-c-bg-alt: #1e1e2e;--vp-sidebar-bg-color: #181825;--vp-local-search-bg: #181825;--vp-local-search-result-bg: #181825;--vp-c-accent-1: #f59e0b;--vp-c-accent-2: #fbbf24;--vp-c-accent-3: #fcd34d;--vp-c-accent-soft: rgba(245, 158, 11, .18);--vp-button-brand-bg: var(--vp-c-accent-1);--vp-button-brand-hover-bg: #fbbf24;--vp-button-brand-active-bg: #d97706;--vp-button-brand-border: var(--vp-c-accent-1);--vp-button-brand-hover-border: #fbbf24;--vp-button-brand-active-border: #d97706;--vp-button-brand-text: #1a1306;--vp-button-brand-hover-text: #1a1306;--vp-button-brand-active-text: #1a1306}.dark .VPFeature{background-color:#1e1e2e;border-color:#5dd9d11f}.VPFeature{border:1px solid rgba(13,115,119,.18);transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease}.VPFeature:hover{border-color:#0d737766;transform:translateY(-2px);box-shadow:0 8px 24px -12px #0d73774d}.dark .VPFeature:hover{border-color:#5dd9d159;box-shadow:0 8px 24px -12px #00000080}.DocSearch-Button,.DocSearch-Button:hover{background-color:#eef0f3;border:1px solid #0d7377}.dark .DocSearch-Button,.dark .DocSearch-Button:hover{background-color:#181825;border:1px solid #5dd9d1}.bottom{margin-top:4rem;display:flex;flex-direction:column;gap:1rem}.bottom>*:first-child{height:100px;background-repeat:no-repeat;background-size:contain;background-position:center}.badges{display:flex;justify-content:center;align-items:center;gap:.5rem;flex-wrap:wrap}.badges a{display:inline-flex;align-items:center}.badges img{height:20px}.VPFeatures .items{justify-content:center}.VPFeature{height:100%}.VPFeature .box{display:grid;grid-template-columns:auto 1fr;grid-template-areas:"icon title" "icon details";align-items:start;column-gap:.85rem;row-gap:.25rem;padding:1.25rem;height:100%}.VPFeature .box .icon{grid-area:icon;margin:0;max-width:38px;min-width:38px;height:38px;font-size:1.25rem;align-self:start;flex-shrink:0}.VPFeature .box .title{grid-area:title;font-size:1rem;line-height:1.3;margin:0;align-self:center;-webkit-hyphens:none;hyphens:none;word-break:normal;overflow-wrap:normal}.VPFeature .box:has(.details) .title{align-self:end}.VPFeature .box .details{grid-area:details;font-size:.85rem;line-height:1.45;margin:0}.vp-doc blockquote{border-left-width:8px}.blog-meta{color:var(--vp-c-text-2);font-size:.95rem;margin-bottom:1rem}.blog-meta .tag{background:var(--vp-c-default-soft);padding:.2rem .5rem;border-radius:4px;font-size:.85rem}.blog-nav{margin-top:3rem;padding-top:1.5rem;border-top:1px solid var(--vp-c-divider);color:var(--vp-c-text-2)}.blog-links{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem;max-width:900px;margin:1.5rem auto;padding:0 16px;box-sizing:border-box}@media (max-width: 768px){.blog-links{grid-template-columns:1fr;max-width:500px}}.blog-links.blog-hero{display:flex;justify-content:center;margin-bottom:2rem}.blog-links.blog-hero a.featured{width:80%;border:1px solid rgba(13,115,119,.35);box-shadow:0 4px 16px -8px #0d737733}.dark .blog-links.blog-hero a.featured{border:1px solid rgba(93,217,209,.3);box-shadow:0 4px 24px -10px #0006}.blog-links.blog-hero a.featured strong{font-size:1.15em}@media (max-width: 768px){.blog-links.blog-hero a.featured{width:100%}.blog-links.blog-hero a.featured strong{font-size:1em}}.blog-links a{display:flex;flex-direction:column;padding:1rem 1.25rem;border-radius:8px;background:linear-gradient(135deg,#0d737714,#32a8a21f);border:1px solid rgba(13,115,119,.2);text-decoration:none;transition:all .2s ease;height:100%}.blog-links a:hover{background:linear-gradient(135deg,#0d737726,#32a8a233);border-color:#0d737759;transform:translateY(-2px)}.blog-links a strong{display:block;color:var(--vp-c-text-1);font-size:.95rem;margin-bottom:.25rem;line-height:1.3}.blog-links a span{display:block;color:var(--vp-c-text-2);font-size:.8rem;line-height:1.4}.dark .blog-links a{background:linear-gradient(135deg,#5dd9d10f,#7be8e01a);border:1px solid rgba(93,217,209,.15)}.dark .blog-links a:hover{background:linear-gradient(135deg,#5dd9d11f,#7be8e02e);border-color:#5dd9d14d}.blog-links a.featured{background:linear-gradient(135deg,#0d73771f,#32a8a22e);border:1px solid rgba(13,115,119,.35);padding:1.25rem 1.5rem;position:relative}.blog-links a.featured .badge{position:absolute;top:-.5rem;right:1rem;height:18px}.blog-links a.featured:hover{background:linear-gradient(135deg,#0d737733,#32a8a247);border-color:#0d737780}.dark .blog-links a.featured{background:linear-gradient(135deg,#5dd9d11a,#7be8e029);border:1px solid rgba(93,217,209,.25)}.dark .blog-links a.featured:hover{background:linear-gradient(135deg,#5dd9d12e,#7be8e042);border-color:#5dd9d166}.blog-links a.human-written{position:relative}.blog-links a.human-written .badge{position:absolute;top:-.5rem;right:1rem;height:18px}.annotation-showcase{max-width:760px;margin:0 auto}.aside{z-index:11}.VPFooter{border-top:none!important}div[class*=language-mermaid] .line-numbers-wrapper{display:none}div[class*=language-mermaid] .shiki{padding-left:1rem}.code-collapsible{margin:16px 0}.code-collapsible summary{margin-bottom:0;padding-bottom:.25rem;padding-left:.75rem;cursor:pointer;font-size:.85rem;font-weight:600;font-family:var(--vp-font-family-mono);color:var(--vp-c-text-2);border-radius:8px 8px 0 0;-webkit-user-select:none;user-select:none;list-style:none;display:flex;align-items:center;gap:.75rem;border:1px solid var(--vp-c-divider);border-bottom:none}.code-collapsible:not([open]) summary{border-bottom:1px solid var(--vp-c-divider)}.code-collapsible>div{border:1px solid var(--vp-c-divider);border-top:none}.code-collapsible summary:before{content:"";display:inline-block;width:0;height:0;border-left:5px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;transition:transform .2s ease}.code-collapsible[open]>summary:before{transform:rotate(90deg)}.code-collapsible summary::-webkit-details-marker{display:none}.code-collapsible summary:hover{background:var(--vp-c-default-soft)}.code-collapsible:not([open])>summary{border-radius:8px}.code-collapsible div[class*=language-]{margin:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.code-collapsible .lang{display:none!important}.code-collapsible .copy{opacity:1!important;background-color:transparent!important}.sponsor{margin-bottom:.5rem;font-size:1rem;font-weight:800;white-space:nowrap}.sponsor a{display:inline-flex;align-items:center;white-space:nowrap}.sponsor svg{flex-shrink:0}@media (max-width: 959px){.image{margin-bottom:-100px!important}}.hero-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1rem;max-width:880px;margin:2.5rem auto 1rem;padding:1.5rem 1.75rem;border-radius:14px;background:var(--vp-c-bg-alt);border:1px solid rgba(13,115,119,.18);box-shadow:0 4px 16px -8px #0d737726;box-sizing:border-box}.dark .hero-stats{background:var(--vp-c-bg-soft);border:1px solid rgba(93,217,209,.15);box-shadow:0 4px 24px -10px #0006}.hero-stats .stat{text-align:center;padding:.5rem .25rem}.hero-stats .stat-num{font-size:clamp(1.6rem,3.5vw,2.4rem);font-weight:800;line-height:1.1;letter-spacing:-.02em;background:linear-gradient(135deg,#0d7377,#14919b);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}.dark .hero-stats .stat-num{background:linear-gradient(135deg,#7be8e0,#5dd9d1);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.hero-stats .stat-label{margin-top:.4rem;font-size:.82rem;color:var(--vp-c-text-2);line-height:1.35}.hero-stats .stat-label sup a{color:var(--vp-c-accent-1);text-decoration:none;font-weight:600}.hero-stats .stat-label sup a:hover{text-decoration:underline}@media (max-width: 640px){.hero-stats{grid-template-columns:repeat(2,1fr);padding:1rem;gap:.5rem}}.value-prop{max-width:880px;margin:1.5rem auto 2rem;padding:0 1.5rem;box-sizing:border-box}.value-prop ul{list-style:none;padding:0;margin:0;display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem 2.5rem}.value-prop li{position:relative;padding-left:1.5rem;color:var(--vp-c-text-2);font-size:.92rem;line-height:1.5}.value-prop li:before{content:"✓";position:absolute;left:0;top:0;color:var(--vp-c-accent-1);font-weight:800}.value-prop li strong{color:var(--vp-c-text-1)}@media (max-width: 640px){.value-prop ul{grid-template-columns:1fr}}.vp-doc h2.section-heading{text-align:center;margin-top:3rem;border-top:none;padding-top:0}.vp-doc p.section-sub{display:block;text-align:center;color:var(--vp-c-text-2);max-width:640px;margin:0 auto 1.5rem;padding:0 1rem;box-sizing:border-box}.vp-doc h3.section-sub-heading{text-align:center;margin-top:1.5rem;margin-bottom:.5rem}.annotation-link{color:var(--vp-c-accent-1);font-weight:600;text-decoration:none}.annotation-link:hover{text-decoration:underline}.vp-doc div[class*=language-]{border:1px solid rgba(13,115,119,.12);box-shadow:0 1px 3px #0000000a}.dark .vp-doc div[class*=language-]{border:1px solid rgba(93,217,209,.1);box-shadow:0 2px 8px #00000040}.VPHome{position:relative;isolation:isolate}.VPHome:before{content:"";position:absolute;inset:0 0 auto 0;height:720px;background:radial-gradient(ellipse 50% 40% at 30% 20%,rgba(13,115,119,.08) 0%,transparent 60%),radial-gradient(ellipse 40% 30% at 80% 10%,rgba(217,119,6,.05) 0%,transparent 60%);pointer-events:none;z-index:-1}.dark .VPHome:before{background:radial-gradient(ellipse 60% 50% at 30% 20%,rgba(93,217,209,.07) 0%,transparent 60%),radial-gradient(ellipse 40% 30% at 80% 10%,rgba(245,158,11,.04) 0%,transparent 60%)}.blog-links a.featured .badge[alt=Featured]{filter:hue-rotate(-20deg) saturate(1.1)}.VPHero .name{font-size:clamp(.95rem,1.5vw,1.1rem)!important;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--vp-c-accent-1)!important;-webkit-text-fill-color:var(--vp-c-accent-1)!important;background:none!important;margin-bottom:.5rem}.VPHero .text{font-size:clamp(2.2rem,5vw,3.4rem)!important;line-height:1.1!important;letter-spacing:-.02em!important}.VPHero .tagline{font-size:clamp(1rem,1.6vw,1.15rem)!important;line-height:1.55!important;max-width:560px}.VPHero .image-container{max-width:520px}@media (max-width: 959px){.VPHero.VPHomeHero .image{margin-top:.5rem}}.claude-badge[data-v-344998d8]{margin-bottom:24px;text-align:center}.attention-notice[data-v-344998d8]{margin-top:16px;padding:16px 20px;border-radius:8px;background-color:var(--vp-custom-block-tip-bg);border:1px solid var(--vp-custom-block-tip-border);text-align:left}.attention-notice summary[data-v-344998d8]{font-weight:700;cursor:pointer;color:var(--vp-custom-block-tip-text)}.attention-notice p[data-v-344998d8]{margin:12px 0 0;line-height:1.7;color:var(--vp-custom-block-tip-text)}.blog-sidebar[data-v-0f020bdc]{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--vp-c-divider)}.blog-sidebar-title[data-v-0f020bdc]{font-size:.8rem;font-weight:700;color:var(--vp-c-text-1);text-transform:uppercase;letter-spacing:.03em;margin-bottom:.75rem}.blog-sidebar-nav[data-v-0f020bdc]{display:flex;flex-direction:column;gap:.35rem}.blog-sidebar-link[data-v-0f020bdc]{font-size:.8rem;color:var(--vp-c-text-2);text-decoration:none;line-height:1.4;padding:.2rem 0;transition:color .2s ease}.blog-sidebar-link[data-v-0f020bdc]:hover{color:var(--vp-c-brand-1)}.blog-nav[data-v-73e261c1]{margin-top:2rem;padding:1.5rem;background:var(--vp-c-bg-soft);border-radius:8px;border:1px solid var(--vp-c-divider)}.blog-nav p[data-v-73e261c1]{margin:.75rem 0;line-height:1.7}.blog-nav p[data-v-73e261c1]:first-child{margin-top:0}.blog-nav a[data-v-73e261c1]{color:var(--vp-c-brand-1);text-decoration:none}.blog-nav a[data-v-73e261c1]:hover{text-decoration:underline}.giscus-wrapper[data-v-5337483f]{margin-top:2rem;padding-top:2rem;border-top:1px solid var(--vp-c-divider)}.comments-heading[data-v-5337483f]{font-size:1.4rem;font-weight:600;margin-bottom:1rem;color:var(--vp-c-text-1)}.comments-outline-link[data-v-6357a5e2]{margin-top:.5rem;padding-top:.5rem;border-top:1px solid var(--vp-c-divider)}.outline-link[data-v-6357a5e2]{display:block;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .25s;line-height:28px}.outline-link[data-v-6357a5e2]:hover{color:var(--vp-c-text-1)}.sponsor-footer[data-v-b93f2dbd]{margin-top:2rem;padding:1rem 0;border-top:1px solid var(--vp-c-divider);text-align:center}.sponsor-footer-content[data-v-b93f2dbd]{display:inline-flex;align-items:center;gap:.5rem;font-weight:600}.sponsor-heart[data-v-b93f2dbd]{font-size:1rem}.sponsor-text[data-v-b93f2dbd]{color:var(--vp-c-text-1)}.sponsor-link[data-v-b93f2dbd]{display:inline-flex;align-items:center;gap:4px;color:var(--vp-c-brand-1);text-decoration:none;transition:color .2s}.sponsor-link[data-v-b93f2dbd]:hover{color:var(--vp-c-brand-2)}.sponsor-link svg[data-v-b93f2dbd]{flex-shrink:0}.sponsor-separator[data-v-b93f2dbd]{color:var(--vp-c-text-2)}.footer-license[data-v-b93f2dbd]{margin-top:.75rem;font-size:.875rem;color:var(--vp-c-text-2)}.footer-copyright[data-v-b93f2dbd]{margin-top:.25rem;font-size:.875rem;color:var(--vp-c-text-2)}.hero-terminal[data-v-2b0df7dc]{width:100%;max-width:480px;margin:0 auto;border-radius:12px;overflow:hidden;background:#0a0914;border:1px solid rgba(93,217,209,.25);box-shadow:0 20px 50px -12px #0d737740,0 0 0 1px #5dd9d114;font-family:ui-monospace,SF Mono,Menlo,Monaco,Cascadia Code,monospace;font-size:.82rem;line-height:1.55}.terminal-chrome[data-v-2b0df7dc]{display:flex;align-items:center;gap:.4rem;padding:.55rem .85rem;background:#ffffff0a;border-bottom:1px solid rgba(255,255,255,.08)}.dot[data-v-2b0df7dc]{width:11px;height:11px;border-radius:50%;display:inline-block}.dot-red[data-v-2b0df7dc]{background:#ff5f57}.dot-yellow[data-v-2b0df7dc]{background:#febc2e}.dot-green[data-v-2b0df7dc]{background:#28c840}.terminal-title[data-v-2b0df7dc]{margin-left:auto;color:#ffffff73;font-size:.72rem;letter-spacing:.02em}.terminal-body[data-v-2b0df7dc]{padding:1rem 1.1rem 1.2rem;min-height:200px;color:#e6e6e6;text-align:left;opacity:1;transition:opacity .2s ease-out}.terminal-body.fading[data-v-2b0df7dc]{opacity:0}.line[data-v-2b0df7dc]{white-space:pre;overflow:hidden;text-overflow:ellipsis}.prompt[data-v-2b0df7dc]{color:#5dd9d1;margin-right:.5rem;font-weight:600}.cmd[data-v-2b0df7dc]{color:#f8f6f3}.response.comment[data-v-2b0df7dc]{color:#ffffff7a}.response.sql[data-v-2b0df7dc]{color:#f0e2bd}.response.json[data-v-2b0df7dc]{color:#c8e8e4}.response.punct[data-v-2b0df7dc]{color:#5dd9d1}.cursor[data-v-2b0df7dc]{display:inline-block;color:#5dd9d1;animation:blink-2b0df7dc 1s steps(1) infinite}@keyframes blink-2b0df7dc{50%{opacity:0}}@media (max-width: 640px){.hero-terminal[data-v-2b0df7dc]{font-size:.7rem;max-width:100%}.terminal-body[data-v-2b0df7dc]{padding:.7rem .85rem .85rem;min-height:165px}.terminal-chrome[data-v-2b0df7dc]{padding:.45rem .7rem}.terminal-title[data-v-2b0df7dc]{font-size:.65rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:50%}.line[data-v-2b0df7dc]{white-space:pre-wrap;word-break:break-word}}@media (max-width: 380px){.hero-terminal[data-v-2b0df7dc]{font-size:.65rem}.terminal-title[data-v-2b0df7dc]{display:none}}.code-terminal[data-v-77e3aab4]{width:100%;margin:0 auto;border-radius:12px;overflow:hidden;background:#0a0914;border:1px solid rgba(93,217,209,.25);box-shadow:0 20px 50px -12px #0d737740,0 0 0 1px #5dd9d114;font-family:ui-monospace,SF Mono,Menlo,Monaco,Cascadia Code,monospace;font-size:.82rem;line-height:1.55}.dark{box-shadow:0 20px 50px -12px #0009,0 0 40px -10px #5dd9d133}.terminal-chrome[data-v-77e3aab4]{display:flex;align-items:center;gap:.4rem;padding:.55rem .85rem;background:#ffffff0a;border-bottom:1px solid rgba(255,255,255,.08)}.dot[data-v-77e3aab4]{width:11px;height:11px;border-radius:50%;display:inline-block}.dot-red[data-v-77e3aab4]{background:#ff5f57}.dot-yellow[data-v-77e3aab4]{background:#febc2e}.dot-green[data-v-77e3aab4]{background:#28c840}.terminal-title[data-v-77e3aab4]{margin-left:auto;color:#ffffff80;font-size:.72rem;letter-spacing:.02em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%}.terminal-body[data-v-77e3aab4]{padding:1rem 1.1rem 1.2rem;color:#e6e6e6;text-align:left;opacity:1;transition:opacity .22s ease-out}.terminal-body.fading[data-v-77e3aab4]{opacity:0}.line[data-v-77e3aab4]{white-space:pre;overflow:hidden;text-overflow:ellipsis}.prompt[data-v-77e3aab4]{color:#5dd9d1;margin-right:.5rem;font-weight:600}.cmd[data-v-77e3aab4]{color:#f8f6f3}.response.comment[data-v-77e3aab4]{color:#ffffff80}.response.sql[data-v-77e3aab4]{color:#f0e2bd}.response.ts[data-v-77e3aab4]{color:#b9d9ec}.response.ts-keyword[data-v-77e3aab4]{color:#8fb8d8}.response.json[data-v-77e3aab4]{color:#c8e8e4}.response.punct[data-v-77e3aab4]{color:#5dd9d1}.cursor[data-v-77e3aab4]{display:inline-block;color:#5dd9d1;animation:blink-77e3aab4 1s steps(1) infinite}@keyframes blink-77e3aab4{50%{opacity:0}}@media (max-width: 720px){.code-terminal[data-v-77e3aab4]{font-size:.72rem}.terminal-body[data-v-77e3aab4]{padding:.7rem .85rem .85rem}.terminal-chrome[data-v-77e3aab4]{padding:.45rem .7rem}.terminal-title[data-v-77e3aab4]{font-size:.65rem}}@media (max-width: 380px){.code-terminal[data-v-77e3aab4]{font-size:.65rem}}.slide-deck[data-v-cb7a016b]{margin:2rem 0;border:1px solid var(--vp-c-divider);border-radius:12px;overflow:hidden;background:var(--vp-c-bg-soft)}.sd-header[data-v-cb7a016b]{display:flex;align-items:center;justify-content:space-between;padding:.55rem .9rem;font-size:.78rem;border-bottom:1px solid var(--vp-c-divider)}.sd-title[data-v-cb7a016b]{font-weight:600;color:var(--vp-c-text-1)}.sd-count[data-v-cb7a016b]{color:var(--vp-c-text-2);font-variant-numeric:tabular-nums;font-family:var(--vp-font-family-mono, monospace)}.sd-stage[data-v-cb7a016b]{position:relative;aspect-ratio:16 / 9;background:#000;display:flex;align-items:center;justify-content:center}.sd-image[data-v-cb7a016b]{width:100%;height:100%;object-fit:contain;animation:sd-in-cb7a016b .28s ease;-webkit-user-select:none;user-select:none}@keyframes sd-in-cb7a016b{0%{opacity:0}to{opacity:1}}.sd-edge[data-v-cb7a016b]{position:absolute;top:0;height:100%;width:18%;max-width:110px;border:none;background:transparent;color:#fff;font-size:3rem;line-height:1;cursor:pointer;opacity:0;transition:opacity .2s,background .2s;display:flex;align-items:center}.sd-edge-prev[data-v-cb7a016b]{left:0;justify-content:flex-start;padding-left:.5rem}.sd-edge-next[data-v-cb7a016b]{right:0;justify-content:flex-end;padding-right:.5rem}.sd-stage:hover .sd-edge[data-v-cb7a016b]:not(:disabled){opacity:.85}.sd-edge[data-v-cb7a016b]:not(:disabled):hover{background:linear-gradient(90deg,rgba(0,0,0,.4),transparent)}.sd-edge-next[data-v-cb7a016b]:not(:disabled):hover{background:linear-gradient(270deg,rgba(0,0,0,.4),transparent)}.sd-edge[data-v-cb7a016b]:disabled{cursor:default;opacity:0!important}.sd-edge span[data-v-cb7a016b]{text-shadow:0 1px 6px rgba(0,0,0,.6)}.sd-progress[data-v-cb7a016b]{position:absolute;left:0;right:0;bottom:0;height:3px;background:#ffffff26}.sd-progress-fill[data-v-cb7a016b]{height:100%;background:var(--vp-c-brand-1);transition:width .3s ease}.sd-controls[data-v-cb7a016b]{display:flex;align-items:center;justify-content:space-between;gap:.5rem;padding:.55rem .7rem;border-top:1px solid var(--vp-c-divider);flex-wrap:wrap}.sd-controls-left[data-v-cb7a016b],.sd-controls-right[data-v-cb7a016b]{display:flex;gap:.4rem}.sd-btn[data-v-cb7a016b]{font-size:.8rem;padding:.3rem .7rem;border:1px solid var(--vp-c-divider);border-radius:7px;background:var(--vp-c-bg);color:var(--vp-c-text-1);cursor:pointer;transition:border-color .2s,color .2s,background .2s;white-space:nowrap}.sd-btn[data-v-cb7a016b]:hover:not(:disabled){border-color:var(--vp-c-brand-1);color:var(--vp-c-brand-1)}.sd-btn[data-v-cb7a016b]:disabled{opacity:.4;cursor:default}.sd-btn-active[data-v-cb7a016b]{border-color:var(--vp-c-brand-1);color:var(--vp-c-brand-1)}.sd-notes[data-v-cb7a016b]{padding:.9rem 1rem;border-top:1px solid var(--vp-c-divider);background:var(--vp-c-bg);font-size:.92rem;line-height:1.6}.sd-notes-label[data-v-cb7a016b]{display:block;font-size:.68rem;letter-spacing:.08em;text-transform:uppercase;color:var(--vp-c-text-2);margin-bottom:.35rem}.sd-notes p[data-v-cb7a016b]{margin:0;color:var(--vp-c-text-1)}.sd-thumbs[data-v-cb7a016b]{display:flex;gap:.5rem;padding:.6rem .7rem;overflow-x:auto;border-top:1px solid var(--vp-c-divider);scrollbar-width:thin}.sd-thumb[data-v-cb7a016b]{position:relative;flex:0 0 auto;width:92px;aspect-ratio:16 / 9;padding:0;border:2px solid transparent;border-radius:5px;overflow:hidden;cursor:pointer;background:#000;opacity:.55;transition:opacity .2s,border-color .2s}.sd-thumb img[data-v-cb7a016b]{width:100%;height:100%;object-fit:cover;display:block}.sd-thumb[data-v-cb7a016b]:hover{opacity:.9}.sd-thumb-active[data-v-cb7a016b]{opacity:1;border-color:var(--vp-c-brand-1)}.sd-thumb-num[data-v-cb7a016b]{position:absolute;bottom:2px;right:3px;font-size:.6rem;background:#000000a6;color:#fff;padding:0 4px;border-radius:3px;font-family:var(--vp-font-family-mono, monospace)}.slide-deck.is-fullscreen[data-v-cb7a016b]{display:flex;flex-direction:column;width:100vw;height:100vh;margin:0;border-radius:0;background:#000}.is-fullscreen .sd-stage[data-v-cb7a016b]{flex:1;aspect-ratio:auto;min-height:0}.is-fullscreen .sd-header[data-v-cb7a016b]{background:#0a0a0a;color:#fff}.is-fullscreen .sd-header .sd-title[data-v-cb7a016b]{color:#fff}.sd-fade-enter-active[data-v-cb7a016b],.sd-fade-leave-active[data-v-cb7a016b]{transition:opacity .2s}.sd-fade-enter-from[data-v-cb7a016b],.sd-fade-leave-to[data-v-cb7a016b]{opacity:0}@media (max-width: 640px){.sd-edge[data-v-cb7a016b]{display:none}.sd-thumb[data-v-cb7a016b]{width:64px}.sd-btn[data-v-cb7a016b]{padding:.3rem .55rem}}.transcript[data-v-ae37f820]{font-size:.95rem}.transcript ol[data-v-ae37f820]{padding-left:1.4rem}.transcript li[data-v-ae37f820]{margin:.5rem 0;line-height:1.65}.VPLocalSearchBox[data-v-ce626c7c]{position:fixed;z-index:100;top:0;right:0;bottom:0;left:0;display:flex}.backdrop[data-v-ce626c7c]{position:absolute;top:0;right:0;bottom:0;left:0;background:var(--vp-backdrop-bg-color);transition:opacity .5s}.shell[data-v-ce626c7c]{position:relative;padding:12px;margin:64px auto;display:flex;flex-direction:column;gap:16px;background:var(--vp-local-search-bg);width:min(100vw - 60px,900px);height:min-content;max-height:min(100vh - 128px,900px);border-radius:6px}@media (max-width: 767px){.shell[data-v-ce626c7c]{margin:0;width:100vw;height:100vh;max-height:none;border-radius:0}}.search-bar[data-v-ce626c7c]{border:1px solid var(--vp-c-divider);border-radius:4px;display:flex;align-items:center;padding:0 12px;cursor:text}@media (max-width: 767px){.search-bar[data-v-ce626c7c]{padding:0 8px}}.search-bar[data-v-ce626c7c]:focus-within{border-color:var(--vp-c-brand-1)}.local-search-icon[data-v-ce626c7c]{display:block;font-size:18px}.navigate-icon[data-v-ce626c7c]{display:block;font-size:14px}.search-icon[data-v-ce626c7c]{margin:8px}@media (max-width: 767px){.search-icon[data-v-ce626c7c]{display:none}}.search-input[data-v-ce626c7c]{padding:6px 12px;font-size:inherit;width:100%}@media (max-width: 767px){.search-input[data-v-ce626c7c]{padding:6px 4px}}.search-actions[data-v-ce626c7c]{display:flex;gap:4px}@media (any-pointer: coarse){.search-actions[data-v-ce626c7c]{gap:8px}}@media (min-width: 769px){.search-actions.before[data-v-ce626c7c]{display:none}}.search-actions button[data-v-ce626c7c]{padding:8px}.search-actions button[data-v-ce626c7c]:not([disabled]):hover,.toggle-layout-button.detailed-list[data-v-ce626c7c]{color:var(--vp-c-brand-1)}.search-actions button.clear-button[data-v-ce626c7c]:disabled{opacity:.37}.search-keyboard-shortcuts[data-v-ce626c7c]{font-size:.8rem;opacity:75%;display:flex;flex-wrap:wrap;gap:16px;line-height:14px}.search-keyboard-shortcuts span[data-v-ce626c7c]{display:flex;align-items:center;gap:4px}@media (max-width: 767px){.search-keyboard-shortcuts[data-v-ce626c7c]{display:none}}.search-keyboard-shortcuts kbd[data-v-ce626c7c]{background:#8080801a;border-radius:4px;padding:3px 6px;min-width:24px;display:inline-block;text-align:center;vertical-align:middle;border:1px solid rgba(128,128,128,.15);box-shadow:0 2px 2px #0000001a}.results[data-v-ce626c7c]{display:flex;flex-direction:column;gap:6px;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain}.result[data-v-ce626c7c]{display:flex;align-items:center;gap:8px;border-radius:4px;transition:none;line-height:1rem;border:solid 2px var(--vp-local-search-result-border);outline:none}.result>div[data-v-ce626c7c]{margin:12px;width:100%;overflow:hidden}@media (max-width: 767px){.result>div[data-v-ce626c7c]{margin:8px}}.titles[data-v-ce626c7c]{display:flex;flex-wrap:wrap;gap:4px;position:relative;z-index:1001;padding:2px 0}.title[data-v-ce626c7c]{display:flex;align-items:center;gap:4px}.title.main[data-v-ce626c7c]{font-weight:500}.title-icon[data-v-ce626c7c]{opacity:.5;font-weight:500;color:var(--vp-c-brand-1)}.title svg[data-v-ce626c7c]{opacity:.5}.result.selected[data-v-ce626c7c]{--vp-local-search-result-bg: var(--vp-local-search-result-selected-bg);border-color:var(--vp-local-search-result-selected-border)}.excerpt-wrapper[data-v-ce626c7c]{position:relative}.excerpt[data-v-ce626c7c]{opacity:50%;pointer-events:none;max-height:140px;overflow:hidden;position:relative;margin-top:4px}.result.selected .excerpt[data-v-ce626c7c]{opacity:1}.excerpt[data-v-ce626c7c] *{font-size:.8rem!important;line-height:130%!important}.titles[data-v-ce626c7c] mark,.excerpt[data-v-ce626c7c] mark{background-color:var(--vp-local-search-highlight-bg);color:var(--vp-local-search-highlight-text);border-radius:2px;padding:0 2px}.excerpt[data-v-ce626c7c] .vp-code-group .tabs{display:none}.excerpt[data-v-ce626c7c] .vp-code-group div[class*=language-]{border-radius:8px!important}.excerpt-gradient-bottom[data-v-ce626c7c]{position:absolute;bottom:-1px;left:0;width:100%;height:8px;background:linear-gradient(transparent,var(--vp-local-search-result-bg));z-index:1000}.excerpt-gradient-top[data-v-ce626c7c]{position:absolute;top:-1px;left:0;width:100%;height:8px;background:linear-gradient(var(--vp-local-search-result-bg),transparent);z-index:1000}.result.selected .titles[data-v-ce626c7c],.result.selected .title-icon[data-v-ce626c7c]{color:var(--vp-c-brand-1)!important}.no-results[data-v-ce626c7c]{font-size:.9rem;text-align:center;padding:12px}svg[data-v-ce626c7c]{flex:none} diff --git a/blog/DRAFT-anniversary-vietnam-of-computer-science.html b/blog/DRAFT-anniversary-vietnam-of-computer-science.html new file mode 100644 index 000000000..cf60e526d --- /dev/null +++ b/blog/DRAFT-anniversary-vietnam-of-computer-science.html @@ -0,0 +1,70 @@ + + + + + + DRAFT: 20th Anniversary of The Vietnam of Computer Science | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    Human Written

    DRAFT: 20th Anniversary of The Vietnam of Computer Science

    DRAFT — TODO: date · NpgsqlRestPostgreSQLArchitectureDDDClean ArchitectureOpinion

    Introduction

    It has been twenty years since Ted Neward published "The Vietnam of Computer Science". Twenty years.

    Did we win? Did we even leave? Are we stuck in a quagmire?

    Since then, the industry has marched relentlessly through a never-ending parade of patterns, architectures, and methodologies: ORM tools of every flavor, Repository and Unit of Work patterns, Domain-Driven Design, CQRS, Event Sourcing (currently all the rage), Hexagonal Architecture, Clean Architecture, Onion Architecture, Service-Oriented Architecture (now obsolete and so last year), Microservices, and on and on.

    The quagmire Neward described is, of course, Object-Relational Impedance Mismatch. So to speak, a shotgun marriage between Object and Relational worlds. One lives in your application memory and works over data structures, and the other, well, in a relational database.

    And none of these patterns above made the problem go away. We need both, and somehow the majority of the effort goes into dealing with persistence in one way or another. It's like unsuccessful couples therapy for that shotgun marriage. Maybe we need divorce papers?

    Let's dig in deep.

    What Is The Object–Relational Impedance Mismatch

    According to Wikipedia (link: https://en.wikipedia.org/wiki/Object–relational_impedance_mismatch):

    Object–relational impedance mismatch is a set of difficulties going between data in relational data stores and data in domain-driven object models.

    Ok, got it. A set of difficulties. Difficulties that refuse to go away, it seems, but fine.

    It's worth noting that Object–Relational isn't the only mismatch on the menu. OO isn't the only way we work over application memory — functional programming is on the rise too, so there's a Functional–Relational mismatch as well. To be fair, FP gets along with relational better than OO does: SQL is already declarative, set-based, and value-oriented, right up FP's alley. But it still works over application memory, so it still has to bridge to the relational database like everyone else.

    Anyway...

    Relational Database Management Systems (RDBMS) is the standard method for storing data in a dedicated database, while object-oriented (OO) programming is the default method for business-centric design in programming languages.

    RDBMS is the standard method for storing data in a dedicated database, but are they typically doing only data storage? I mean, files are also doing that, are they not? Are we missing something here?

    And this second claim that object-oriented (OO) programming is the default method for business-centric design, that might be true, but SQL is the default method for anything business data-related, like the way organizations store, manage, protect, and analyze their data.

    We can see the tension already here, but let's move on.

    The problem lies in neither relational databases nor OO programming, but in the conceptual difficulty mapping between the two logic models. Both logical models are differently implementable using database servers, programming languages, design patterns, or other technologies.

    Now, why would they say that we have two logical models?

    Take a Customer. In your business, a customer is one thing — one concept. DDD even has a name for this: within a bounded context there is one ubiquitous language, so there is one shared notion of what a Customer is. That single concept becomes one logical model — what a Customer is, which attributes it has, how it relates to orders — with no implementation details attached. That's why it's called a logical model.

    Then you build it. And here is where it splits: that one logical model gets implemented physically more than once. Once as a table — customer, with a varchar(255) name column. Once as a class — Customer, with a string Name property. Same concept, same logical model, two physical incarnations.

    So in modern software design we are not talking about two different logical models, as Wikipedia claims. We have one logical model and two — sometimes more — physical models. Modern overengineering knows no bounds.

    The reason why they say that we have two logical models is probably because OO and Relational are seen as totally different paradigms so the logical models must be genuinely different. For example OO does have behavior, but the problem is that we are talking strictly about data here, not behavior. Behavior is a different problem axis - we'll come to it when we get to algorithms.

    In any case, impedance mismatch is not about two different logical models, but about the mismatch between those two physical models. Your system has to map them and make them work together because, logically, we still have one logical model and the system needs to reflect that. That’s the real problem and source of tension.

    Issues range from application to enterprise scale, whenever stored relational data is used in domain-driven object models, and vice versa. Object-oriented data stores can trade this problem for other implementation difficulties.

    This last sentence just confirms what I just said. Whenever stored relational data (one physical model) is used in domain-driven object models (another physical model), we have this problem or mismatch. Maybe the belief that we are talking about two different logical models is the reason why these difficulties exist and persist and are not being resolved. If it was just a simple mapping, let's say from varchar(255) to a string, that would be easy to solve a long time ago. Or something a bit more complex, let's say many-to-many on a logical model level. In a relational model, that requires a junction table, but in an object model that can be just a collection of references. Still, a little bit more complex mapping, but still solvable and indeed already solved.

    My sincere belief is that we are talking here about a series of misconceptions and misunderstandings about the nature of abstractions themselves. More specifically, about abstractions the RDBMS already provides. And if RDBMS already provides them, well, that means that the application layer goes on and re-implements them anyway. No wonder we have difficulties. Let's look at these misconceptions one by one and discuss in detail:

    1. State Data Abstraction Misconception

    2. Storage Devices Abstraction Misconception

    3. Data Structures Abstraction Misconception

    4. Abstraction Over Algorithms

    5. Abstraction Over Concurrency and Integrity

    1) State Data Abstraction Misconception

    The Claim

    Object-oriented programming has one of its core tenets called encapsulation. Encapsulation is supposed to protect internal data — the state. The object is the authoritative custodian of its state. Nobody else has it. Period. Without encapsulation, we don't really have OOP anymore.

    On the other hand, functional programming has its own version of that — state immutability. A function takes values in and returns new values out, leaving the originals untouched — so there's no shared mutable state to corrupt in the first place. FP also enforces valid state through invariants, often by encoding them directly into the type system. Same goal, fewer bugs from uncontrolled state, arguably reached more elegantly. Fine. State status - protected, bugs - reduced. Great, beautiful, love it.

    This, in fact, is very reasonable and well-thought-out. State data is data shared between different parts of the system and even different users. If every part of the system can poke at it without other parts knowing about it, then you have a lot of bugs. So, naturally, over time people came up with these concepts of encapsulation and immutability to protect that state and make sure it is only changed and fiddled with in a controlled way. Because we don't want to have bugs. Bugs are bad, okay.

    No objection here. This is perfectly fine, and it makes a lot of sense. Let's say we have something complex. A game scene. Compiler syntax tree. Whatever. Protecting state in memory of such systems is invaluable, to say at least.

    In his book Domain-Driven Design (2004), when describing the Domain Layer in Chapter 4, "Isolating the Domain," Eric Evans writes:

    State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure.

    So he treats the in-memory object as the custodian of state - state that is "controlled and used here," where "here" is the in-memory Domain Layer — while the RDBMS is merely "delegated to the infrastructure." More on that in the next chapter. What matters here is the claim itself: the state is in memory. No doubt about it.

    The Reality

    But what about applications backed by relational databases, business or otherwise?

    In RDBMS-backed applications, the state lives in that RDBMS itself - not in memory. The authoritative custodian of state is the database table row, not the in-memory object.

    Take any business application backed by a relational database. How do you check the current state of some entity? Do you peek at the object in memory? No, you query the database for that. Anyone who has worked 5 seconds in industry knows this, of course.

    I know what DDD people will say now: object memory (or functional state memory) is the real state data, and RDBMS is just where that data is persisted (presumably when the user clicks on a "Save" icon).

    And, if you still believe that objects/memory are the real state and not RDBMS, then riddle me this:

    What if we have multiple instances of the application running behind a load balancer? And then maybe some background work as well, and some other services too. Maybe reporting replica as well, so we have multiple processes accessing the same state data. Who is the sole custodian of that state data in that case? The in-memory object of one process, or the database row? The obvious answer is, of course, NONE of the in-memory copies. The best we can do is to have each process hold a copy, while the real state is in the RDBMS itself.

    Some may say now, but databases have multiplicity too, right? We have multiple replicas, and they are all copies of the same data. And there are also multi-master setups as well with multiple writers for high availability scenarios. Yeah, but the big difference is that the database solves it, while in-memory objects don't even try to be honest. They just deny the reality. For multiple replicas, there is never any ambiguity about which one is the source of truth, since we are talking about read-only copies, and for multi-master setups we have either different consensus protocols that ensure a single source of truth or so-called eventual-consistency for the source of truth. In-memory objects have no such machinery, so they just pretend that they are the single custodian of the state data, which is, of course, a big, fat lie.

    If the object is the custodian of state, what if we kill and restart the application? Oh my, the object's custodianship has evaporated.

    Also, what, for example, if we have two concurrent user writers? One loads an invoice as 'pending', but another writer marks it 'paid' while the first one holds it. Now the first one is lying about the state of that invoice. The database is right - it is the source of truth - the first one is stale. We will talk more about concurrency and integrity later - this is just to prove my point:

    RDBMS is the authoritative custodian of state, not in-memory objects. The state lives in the database, not in memory.

    The Cost

    Take a look at this example of a DDD-style domain model below:

    Source credit: https://www.reddit.com/r/DomainDrivenDesign/comments/1ttzr19/create_complex_and_deep_aggregate/

    This is a random example from Reddit, but it is indicative. Virtually every domain model is more or less like that - strip away the method, adjust types a bit, and it is basically an Entity-Relationship (ER) diagram, that's it. There is no structural difference between the domain model and the database model. It is the same logical model implemented twice.

    And that method isValid() - the only behavior in the whole model - is just a data integrity check. Every rule it enforces, the database enforces too: dateBegin <= dateEnd is a one-line CHECK, and the harder ones —- price periods that must not overlap, quantity ranges that must stay contiguous — are exactly what database constraints exist for (more on the how in the integrity misconception). Same logical model implemented twice, same integrity rules implemented twice.

    Because modern software design orthodoxy refuses to acknowledge that the state lives in the RDBMS, it forces us to implement the same model at least twice - once in the database (relational model implementation), once in memory (object model implementation, mapped usually with an O/R tool or a library).

    I say at least, because there are examples where we have even more. Some "architects" will consider that O/R mapped model a persistence model because it doesn't have any behavior, and then they will add a separate domain model on top of that, which is the one with the behavior. So we have three implementations of the same logical model - database model, persistence model, and domain model.

    And then, since we now have at least two physical models, we also have two type systems and two sets of constraints to protect the state. And we must keep them in sync at all times, and we must maintain the correct mapping. To be fair, this is mostly automated in modern systems (not completely), but we still have to do it and it is still there, automated or not. And even automated, it is still a cost and still a source of bugs and still a source of complexity. And even that can't bridge the semantic gap between the two type systems fully. For example, in PostgreSQL we can have a NOT NULL constraint on a column, but the corresponding C# property will be a nullable string.

    All because we believe that there is some important state data we need to protect with our objects. In the best case, there are just some transient and disposable chunks of data copies, and in any case there is a lot of plumbing to manage: connections, transactions, commands, queries, etc. You know, the actual infrastructure.

    Personally, I see a lot of irony in this. Modern orthodoxy calls RDBMS infrastructure, just to end up with code bases implementing, well, actual infrastructure.

    2) Storage Devices Abstraction Misconception

    The Claim

    A modern software engineering approach makes the assumption that an RDBMS is a storage device. A device used to store data. Therefore, good engineering practice is to abstract that storage device.

    We can see that in the quote from Eric Evans above, where he says that:

    ... the technical details of storing it are delegated to the infrastructure.

    Evans doesn't dwell on this point much - he just delegates storage to the infrastructure and moves on. On the other hand, Robert C. Martin is much more vocal about it. In Clean Architecture, Martin dedicates an entire chapter to "The Database Is a Detail" argument. He is very blunt about it, and he repeats it several times, for example:

    It's just a mechanism we use to move the data back and forth between the surface of the disk and the RAM.

    Or this:

    The database is really nothing more than a big bucket of bits where we store our data on a long-term basis.

    This is not some fringe blog post. These are two of the most-cited and influential authors in the field - arriving at one and the same conclusion - just at different volumes.

    Evans states it just once and quietly, like he doesn't want to talk about it too much, and then delegates it away (perhaps he'd rather not have you examine it too closely, I don't know). Martin states it over and over, bluntly, and goes as far as to insist we should not even acknowledge that the disk exists.

    Different approaches - identical claims: the database is a storage device. Storage is mechanical, it sits beneath the business — no different from a file system — and the architect's job is to wrap it up tight and forget it is there.

    The Reality

    Reality is that RDBMS uses a storage device or devices, and that means that it already does this abstraction for you. It already abstracts storage.

    If we go back to the beginning, when Edgar Codd introduced the relational model in 1970, the pitch was data independence — the whole point was to insulate the logical shape of your data from how it physically sits on a device. In fact, that is the core concept of the relational model. It was codified as the numbered Rule 8 fifteen years later in Edgar Codd's twelve rules: Rule 8: Physical data independence:

    Application programs and terminal activities remain logically unimpaired whenever any changes are made in either storage representations or access methods.

    The ANSI/SPARC Architecture from that era formalized it and walled off internal storage as well. So, the relational model was designed, from day one, to hide the disk.

    Back to modern times and modern RDBMS implementations.

    Riddle me this: We can write a SELECT, a JOIN, a WHERE, and even an INSERT, UPDATE, or DELETE, and in most cases it will be executed with the same results on different RDBMS engines from different vendors. That is called ANSI/ISO standardized SQL, which goes to show the real separation between the model and the physical implementation. Virtually every RDBMS implements the same ANSI/ISO SQL standard, but with dialect differences at the edges. Those differences can sometimes be significant, but the core of the language is the same, and that is what matters here. The same SELECT statement can run on MySQL, PostgreSQL, SQL Server, Oracle, and so on, given that you have the same logical model implemented in each of them.

    But the abstraction doesn't stop at the SQL language. The relational model and the RDBMS implementations built on top of it are designed to be agnostic to the underlying storage device. For example:

    • In PostgreSQL, we can move any table to any different storage device we choose (they call it a tablespace), and the model above remains unchanged. Just use ALTER TABLE ... SET TABLESPACE — the table is now on a different physical device. The term TABLESPACE is Oracle's term for the same thing (switching table storage), and SQL Server has something called FILEGROUPS. Same SELECT, same result, different storage, no changes, no fuss.

    • You can change the actual byte format in storage. In PostgreSQL, the table access method is pluggable (Citus columnar, TimescaleDB). SQL Server flips a table from rows to columns with a clustered columnstore index. Oracle does it with Hybrid Columnar Compression, or stores the whole table inside a B-tree as an index-organized table instead of a heap. Same logical table, same query — completely different bytes on disk.

    • In MySQL you can swap the storage engine out from under a table entirely: ALTER TABLE t ENGINE=... moves a table between InnoDB (B-trees on disk), MyISAM, Archive (compressed), CSV (a literal text file), or MEMORY (pure RAM). One table, one query, completely different machinery underneath.

    • The data doesn't even have to be in local storage at all. In PostgreSQL, make it a FOREIGN TABLE over an FDW (Foreign Data Wrappers) and the rows live happily on another machine entirely — the query doesn't even care or know. Oracle reads flat files as external tables; DuckDB queries Parquet files on disk as if they were tables. The data is somewhere else, on a remote machine - SQL is the same.

    • It doesn't even have to be one machine. Hand it to a distributed SQL engine — CockroachDB, TiDB, Spanner — and your data becomes a replicated, sharded key-value store smeared across a cluster. You have no idea which node, let alone which disk, holds any given row. CockroachDB speaks Postgres's wire protocol so you can use standard PostgreSQL unchanged, TiDB speaks MySQL's, and you still write ordinary SQL. Or push it to the cloud, where Snowflake keeps everything as columnar micro-partitions in object storage. Same SELECT, same result, no fuss.

    • And finally — no disk at all. Who says we need disks? Declare a MySQL table ENGINE=MEMORY, run PostgreSQL on a tmpfs with the whole data directory in RAM, switch on SQL Server's In-Memory OLTP, open SQLite as :memory:. No persistent storage anywhere. Same query, same result — and the thing is still, unmistakably, a database.

    Martin writes: "To mitigate the time delay imposed by disks, you need indexes...". Then why are in-memory databases full of indexes? Why are they (in-memory databases) a thing at all? Because an index was never about the medium - it is about not scanning every row when you want one, be it on disk or in RAM.

    As we can see, different devices, different formats, even machines and clusters, or even no machines at all — the same logical model, the same SQL, the same results. The RDBMS already abstracts storage for you. It is designed to do exactly that. Thank you very much, but you are wrong.

    The Cost

    Cost is never ending persistence-ceremony. We are asked to carefully construct our data models in memory (to maintain the illusion of encapsulation), protect it from illogical and unwanted changes, and then save it to the database (persist it) when the time is right.

    It is hard to overstate how big this persistence-ceremony thing really is. The number of specialized patterns, libraries, countless tutorials, videos, entire philosophies, strategies, etc. But take a look at this trivial example:

    This is a simple SQL statement to update a transfer approval - two people must sign off, they must be different people, and the status moves from pending to partly to fully approved. In a traditional codebase this would be a considerable chunk of code spanning multiple files and doing several database calls, just to maintain the illusion.

    sql
    sql
    update transfer_approvals
    +set
    +  status = case
    +    when status = 'pending' then 'partly_approved'
    +    when status = 'partly_approved'
    +         and approver1 is distinct from $1 then 'fully_approved'
    +    else status
    +  end,
    +  approver1 = case when status = 'pending' then $1 else approver1 end,
    +  approver2 = case
    +    when status = 'partly_approved'
    +         and approver1 is distinct from $1 then $1
    +    else approver2
    +  end
    +where 
    +  transfer_id = $2
    +  and status in ('pending', 'partly_approved')
    +returning status;

    We don't know where those transfer approvals are. Is it stored on a disk? What disk is it on? Is it in memory, what format it's in, or even what machine it is on, and we don't care. That is not the point. This is a DECLARATION. We have just declared business rules for how to update transfer approvals.

    It has nothing to do with storage as far as the application is concerned. And since our RDBMS engine hides and abstracts all the storage details and does all the persistence work for us, what are we left with then? That's right: the business logic and business rules. Just a declaration of how our transfer approvals should be updated in this case. Nothing else. No ceremony, no plumbing, no persistence layer, no storage, no nothing.

    The entire pile of code which is now gone with this move is the following:

    • the entity / aggregate class (the in-memory model)
    • the repository (to fetch it and put it back)
    • the unit-of-work / change tracker (to know which fields are dirty)
    • the O/R mapping config (to translate object <-> row)
    • the load → mutate-in-memory → save dance
    • the surrounding transaction scope

    Every one of those exists for one reason only: to carry state from memory to storage and back. Remove that silly belief, and every one of those big code blocks removes itself.

    3) Data Structures Abstraction Misconception

    The Claim

    If we already have established two previous claims — that the state lives in memory and that the database is a storage device — then data itself must be an in-memory data structure as well. And, if we are going to perform operations on it, protect the state, mutate, and so on, then we need to have it in memory, and only suitable in-memory data structures will do, period.

    Direct quote from Eric Evans, Domain-Driven Design (2003), Part II "Building Blocks of a Model-Driven Design," Chapter 6 "The Lifecycle of a Domain Object," in the section on Repositories (p. 108), he writes:

    For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type.

    Screenshot from Eric Evans, Domain-Driven Design (2003), p. 108, showing the Repository definition
    Eric Evans, Domain-Driven Design (2003), p. 108.

    The word Evans uses: illusion. Not a real collection, oh no, just the illusion of an in-memory structure. This should tell you everything now: You should only build an elaborate pattern to simulate an in-memory collection if the real data collection was never in memory to begin with.

    The Repository exists precisely because the objects live in the database, and its entire job is to make them look like they are sitting in memory. Fake it until you make it, except you will never make it.

    The repository pattern is an admission in structural form: relational data, dressed up to pass as an in-memory data structure (so we can do OOP on it).

    This is not a coincidence nor an isolated quote. For example, in the book Patterns of Enterprise Application Architecture (2002), Martin Fowler writes (source: https://martinfowler.com/eaaCatalog/repository.html):

    A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection.

    Acting like an in-memory domain object collection? Why do we want to force relational data into an in-memory collection? Just for good measure, let's check out Microsoft's recommendation in their Software Architecture e-book, in a part that teaches us how to "Design the infrastructure persistence layer." Microsoft echoes Fowler almost word for word, putting it as a set of domain objects in memory.

    A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory.

    And then they continue:

    Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. Once the entities are in memory, they can be changed and then persisted back to the database through transactions.

    Again, just goes to prove the point above - real state is in the database, we are simply mandated to load temporary chunks into memory - in order to maintain the illusion of encapsulation and illusion of in-memory data structure.

    Perhaps we might end up with an illusion of the entire software solution?

    In any case, more than a decade after Evans and Fowler had laid out this machinery that simulates in-memory collections, the mismatch still had not been solved. We know this because in 2014, the field's leading DDD experts from around the world gathered at the DDD eXchange conference in NYC to figure out how to do DDD better and, finally, solve this Object-Relational Impedance Mismatch. Because you do not hold a summit to solve a problem you have already solved.

    One of the speakers, renowned DDD expert Vaughn Vernon, gave a talk called "The Ideal Domain-Driven Design Aggregate Store?" where he proposed a final solution to the O/R Impedance Mismatch problem:

    During the park bench discussion I promoted the idea of serializing Aggregates as JSON and storing them in that object notation in a document store. A JSON-based store would enable you to query the object’s fields. Central to the discussion, there would be no need to use an ORM. This would help to keep the Domain Model pure and save days or weeks of time generally spent fiddling with mapping details. Even more, your objects could be designed in just the way your Ubiquitous Language is developed, and without any object-relational impedance mismatch whatsoever. Anyone who has used ORM with DDD knows that the limitations of mapping options regularly impede your modeling efforts.

    Screenshot from Vaughn Vernon, 'The Ideal Domain-Driven Design Aggregate Store?', proposing JSON-serialized Aggregates in a document store
    Vaughn Vernon, "The Ideal Domain-Driven Design Aggregate Store?"

    Framing in this case is that the O/R mapping tools and libraries are the main source of impedance mismatch, and if we could just get rid of them, then we would have solved the problem. No O/R mapping, no O/R mapping at all, and no impedance mismatch. That's the idea. In essence, the proposed data design is this:

    • Serialize each aggregate Object to JSON, store it as a blob
    • Every table is just (id, data json) — a key and a blob, nothing else
    • "Reference Other Aggregates By Identity Only" — no foreign keys, no joins, nothing, each blob standalone

    That's it. That is the "ideal aggregate store" solution to the impedance mismatch problem.

    Now, to be fair, Vaughn Vernon is not saying that RDBMS = file system. He does propose using Postgres for ACID and JSON querying. He does want to keep the relational engine. Just not the relational model.

    And that is the whole trick. We have solved the Object-Relational impedance mismatch by removing the relational part entirely — and keeping the Object part, obviously. No relations, no foreign keys, no joins, no set operations, no nothing. Just a flat collection of objects, serialized, frozen to disk, and fetched by id. The illusion of an in-memory collection, made real at last.

    So, yeah, the solution to the O/R impedance mismatch is to remove the R (relational part) entirely, and just have a key-value store with JSON blobs.

    That was proposed ten years ago. Does anyone use that today? Is that how the industry builds? I don't think so. Twenty years after Evans and Fowler, the mismatch sits exactly where it started. We never solved it.

    The Reality

    Let's get one thing out of the way immediately, because I don't want to win this argument by cheating.

    A table is a set. That is not a metaphor — it is the mathematical definition. Codd's 1970 paper defines a relation as a subset of the Cartesian product of domains: a set of tuples.

    Screenshot from E.F. Codd, 'A Relational Model of Data for Large Shared Data Banks,' defining a relation as a subset of the Cartesian product of domains
    Codd, E.F., "A Relational Model of Data for Large Shared Data Banks," CACM 13(6), June 1970, pp. 377–387, §1.3.

    And SQL is an algebra over those sets — selection, projection, JOIN, UNION, INTERSECT, EXCEPT — a closed algebra, so every operation over sets returns another set you can keep operating on.

    All true. And here is the problem with building the argument on that: an in-memory collection can do all of it too. LINQ in C# ships Join, GroupJoin, Union, Intersect, Except, Distinct, GroupBy. Those are not arbitrary method names — those are Codd's operators, reimplemented over IEnumerable. And every other ecosystem rebuilt some version of the same algebra over its collections; LINQ just did it most completely.

    So if the impedance mismatch were about operations — about what you can do with the data — it would have been solved around 2007, when the operators finished porting. Case closed, everybody go home.

    The mismatch is still here. Which means it was never about the operations.

    Here is what the table has that no collection in your process can have. Not what it does — what it is:

    1. It is the record. As established in the first misconception: the row is not a representation of the state, it is the state. Your collection is a copy of it, taken at load time.

    2. It is shared. Every process, every writer, every background job operates on the same table, under the engine's arbitration. Your collection is private to one process. The other writers don't know it exists, and they are not waiting for it.

    3. It is durable and live. The table existed before your process started, will exist after it dies, and keeps changing under other writers the whole time. Your collection is a photograph. The table is the thing being photographed — and it kept moving after the shutter clicked.

    4. It is guarded. CHECK, UNIQUE, NOT NULL, foreign keys — enforced transactionally, against every writer, no exceptions. Your in-memory validation binds exactly one thing: your copy. The next writer does not inherit your discipline.

    And now the key observation, the one this whole chapter hangs on: not one of these four is an operation.

    LINQ could port Join because join is a function — values in, values out. Pure computation travels; you can implement it anywhere. Being the shared, durable, guarded record is not a function. There is no method you can add to List<T> that makes it be the authoritative state. You can port an operator. You cannot port a status.

    Notice where the line falls between what made the trip into memory and what didn't. Everything that is algebra — join, filter, group, project — ported over just fine. Everything that is state — the transaction, the arbitration, the constraints, the durability — never left the database. It can't. The line between them is exactly the line we drew in the first misconception: computation versus state.

    And if you want this confirmed by the industry itself: the most serious application of LINQ's relational operators is LINQ-to-Entities, which takes your C# expression tree and compiles it back into SQL, to send to the database. We rebuilt the algebra in memory, and its main job turned out to be translating itself back — because the algebra made the trip, and the data never did.

    Which puts the domain object model in a completely new light. It is not an alternative data structure for your data. Look at the machinery again, piece by piece:

    The object model buildsTo simulate
    Repositorythe table
    Identity mapthe primary key
    Navigation propertiesforeign keys and joins
    Unit of workthe transaction
    Change trackerwhat UPDATE ... SET already knew
    In-memory validationCHECK, UNIQUE, and foreign key constraints

    That is not a different model. That is the same model — the database — re-implemented in RAM, minus the four properties that made it meaningful. The domain model is a simulation of the database, running inside your process. Evans told us himself, remember: the illusion of an in-memory collection. And the real thing sits two feet away the entire time, doing all of it correctly, under ACID, for every process at once. We call the copy "the domain" and the authority "a detail."

    To be precise about what I am not claiming, before somebody builds a strawman out of it:

    In-memory data structures are not the problem. Pure computation over values you were genuinely given is exactly what memory is for — take inputs, compute, return outputs. The game scene and the compiler syntax tree from the first misconception live in memory legitimately, because they are the state of those systems. LINQ over data you rightfully hold is wonderful.

    Caching is not the problem either — honest caching. A cache knows it is a copy. It has a TTL, an invalidation story, and it never claims to be the truth.

    The sin is narrow and specific: a copy that claims to be the authority and has no answer for the moment it goes stale. That is not a cache, and it is not a data-structure choice. That is a simulation impersonating the thing it copied.

    Hold on to that word — impersonating — because it explains something two decades of framework engineering could not fix. Every famous ORM pathology you have ever fought is not a separate bug with a separate fix. It is one failure with many faces: a simulation forced to behave like the authority it impersonates. Let's count the faces.

    The Cost

    What follows are not four independent grievances. It is one impossibility wearing four costumes.

    Copy, not record → staleness and write amplification

    The object must be hydrated before it can be mutated — that is the whole contract. So to change one column, the ORM does a SELECT followed by an UPDATE: load the row (usually the whole aggregate — scroll back to that Reddit picture and count the objects), let the object "decide" in memory, write it back. Remember the transfer approval from the previous chapter: one UPDATE statement, zero preliminary round trips, the rule declared right where the state lives. The simulated version is load → check in memory → mutate in memory → save. And between load and save, the row is free to change under you.

    Hence lost updates. Hence the "optimistic concurrency token" — a rowversion or xmin column added to the schema not because the business needs it, but to detect that your copy lied to you between load and save. The token is a confession written in DDL: the copy is not the record, and we know it.

    Private, not shared → arbitration lives in the database anyway

    Two requests load the same invoice. Each holds a private snapshot; each decides from it; both decisions are "valid" in memory, and one of them is wrong in reality. The decision your domain model makes is provisional — the only binding decision happens in the database, under a lock, inside a transaction, where arbitration has lived all along. The version-token "rescue" concedes exactly this: the object proposes, but the WHERE clause of the write disposes. Now add a background job, a second service, a nightly import — and the in-memory "state" stops being state at all. It is one process's guess about what the state was, some number of milliseconds ago. (Much more on this in the concurrency and integrity misconception.)

    Graph walk, not set operation → the access-pattern tax

    We conceded that collections have the operators. But the object model's shape pushes you away from them: objects navigate. customer.Orders, order.Lines — walking references one object at a time, each step a query you didn't see, fired from behind a property getter. One JOIN becomes a hundred SELECTs, and the call site looks innocent. This is N+1, the most documented performance pathology of the last two decades. And the fixes bill you separately: eager-load with Include and over-fetch half the database, or project into DTOs — at which point you are writing relational queries again, in C#, so that a library can compile them back into the SQL you were abstracting away. The abstraction gets abandoned at exactly the moment it gets tested.

    Simulation, not engine → the capability ceiling

    Window functions. Recursive CTEs — foreign keys form a graph, and the engine will walk arbitrary-depth hierarchies for you, declaratively. GROUPING SETS, lateral joins, partial and expression indexes, set-based bulk updates. And underneath all of it: a cost-based planner with live statistics about your actual data, choosing between a hash join, a merge join, and an index scan — per query, per data distribution. The simulation has none of this and cannot grow it. This one isn't even a defect — it is simply what a private copy in RAM lacks next to a database engine.

    The standard reply — "but EF has raw SQL escape hatches" — is my argument wearing a different hat. If the object model were the system of record, there would be nothing to escape to. The moment you drop to SQL for the hard 20%, you have admitted where the real system was all along. The escape hatch is not a counterexample. It is the proof.


    Now step back and look at the four together. None of them is an implementation defect. Hibernate is twenty-five years old; Entity Framework is eighteen. Some of the best engineers in the industry have been sanding these edges for two decades, and every one of these problems is still here — because they are not bugs in the simulation. They are the simulation working correctly: behaving exactly like what it is — a private, transient copy — instead of what it plays: the shared, durable record. That gap does not close with effort, because it is not made of code. It is made of what the two things are.

    And that is Neward's quagmire, stated mechanically. An escalating investment that cannot win — not because the enemy is strong, but because a copy cannot out-invest its way into being the original.

    4) Abstraction Over Algorithms

    The Claim

    Back in the Wikipedia section, I promised that behavior is a different problem axis and that we would come to it when we get to algorithms. Here we are.

    The first three misconceptions were about data. This one is about behavior. The claim goes like this: fine, the data may sit in the database — but the algorithms, the logic, the behavior of the system, those belong in application code. SQL fetches; code computes. The database is where data sleeps, and the application is where it wakes up.

    Martin Fowler wrote an article about this exact question back in February 2003, called "Domain Logic and SQL". It opens with an honest description of the mainstream attitude:

    Many application developers, particularly strong OO developers like myself, tend to treat relational databases as a storage mechanism that is best hidden away.

    There it is again — the storage device from the second misconception — but this time the subject is logic. To his credit, Fowler takes the SQL option far more seriously than most of his readers ever did. And still, the verdict:

    Personally I don't think performance should be the first question. My philosophy is that most of the time you should focus on writing maintainable code.

    With a warning label attached:

    I would suggest that if you go the route of putting a lot of logic in SQL, don't expect to be portable — use all of your vendors extensions and cheerfully bind yourself to their technology.

    And a concession that defines SQL's proper place in this worldview:

    If you use an in-memory approach and have hot-spots that can be solved by more powerful queries, then do that.

    So there is the claim, in its most reasonable and balanced form, from its most reasonable and balanced proponent: logic in memory is the default — maintainable, portable, testable. Logic in SQL is the exception — a performance hot-fix, to be applied reluctantly, hot-spot by hot-spot. Twenty-three years later, this is still the mainstream position, and most codebases you will open this week are built on it.

    The Reality

    SQL is a programming language. A declarative one, but a programming language.

    Look back at the transfer approval statement from the second misconception. Two approvers, they must be different people, the status walks from pending to partly to fully approved. That is not "fetching data." That is behavior — an algorithm, expressed as a declaration, executed next to the data, atomically.

    Now ask what the algorithms of a business system actually are. Strip away the ceremony and it is overwhelmingly this: filter, join, group, aggregate, rank, deduplicate, walk a hierarchy, compute something over ordered data. Which is, item for item, exactly what SQL was designed to express. A running balance, for example:

    sql
    sql
    select
    +  customer_id,
    +  transaction_date,
    +  amount,
    +  sum(amount) over (
    +    partition by customer_id
    +    order by transaction_date, transaction_id
    +  ) as running_balance
    +from transactions;

    The in-memory version of this algorithm: fetch every transaction over the wire, group by customer in a dictionary, sort each group, loop and accumulate — plus the memory footprint, plus deciding what happens on the day the table stops fitting in RAM. The declarative version is the window function above. Ranking, top-N-per-group, gaps in sequences, running totals, year-over-year — window functions. Org charts, bills of materials, category trees — recursive CTEs: declare the traversal, and the engine walks the graph. With recursive CTEs, SQL is Turing-complete — not that you should compute Fibonacci in it, but "SQL can't express my logic" stopped being true decades ago.

    But here is the part that actually settles the argument, and it is not expressiveness. When you write the loop, you are writing one algorithm, frozen at commit time. When you write the declaration, the engine writes the algorithm — at runtime, with a cost-based planner and live statistics about your actual data. Hash join, merge join, nested loop; index scan or sequential scan; parallel workers or not — chosen per query, per data distribution, and re-chosen as the data grows. Your hand-written loop was a perfectly good plan at ten thousand rows. At ten million it is a catastrophe, and it will not adapt, because it is code — someone has to notice it, profile it, and rewrite it. The declaration just quietly gets a new plan.

    Nobody would hand-write three join algorithms plus a statistics-driven optimizer to choose between them in the service layer. That machinery already exists. It sits directly under the data — and the claim instructs us not to use it.

    Which leaves the claim's justifications, so let's take them in order. Maintainability: is the eight-line window function really less maintainable than the same algorithm spread across a repository, a service method, and a mapping profile? "Maintainable" is not a synonym for "written in my favorite language." Portability: the second misconception already dealt with that — the core of SQL is an ANSI/ISO standard that runs on every engine, while your domain layer is portable to exactly nothing; nobody in recorded history has swapped C# + EF for Java + Hibernate because the code was so nicely decoupled. Testability: SQL is testable — pgTAP exists, and the oldest trick in the book still works: open a transaction, run the test, roll back. Besides, when you mock the database out of a test of data logic, look at what is left standing: you are testing the simulation from the previous misconception, not the system.

    The Cost

    Every other corner of the industry has a name for this cost: moving data to compute instead of moving compute to data. The entire big-data field was built on the lesson that you ship the algorithm to where the data lives, because the other direction does not scale. Business software orthodoxy teaches the other direction as a best practice.

    So we pay, in four installments:

    • The wire tax. Rows are read, serialized, shipped across the network, deserialized, and mapped into objects — so that a loop can run in the application, redoing work the engine would have done in place, with indexes.

    • The round-trip tax. Iterative logic in the application is chatty by nature: a query per step, per entity, per iteration. The N+1 problem from the previous chapter is this tax's most famous invoice.

    • The reimplementation tax. Every in-memory group, sort, join, and aggregate is a worse copy of what the engine already had: no indexes, no statistics, no planner, no parallelism, and memory bounded by your heap.

    • The frozen plan tax. The hand-written algorithm does not adapt to data growth. It just gets slower, quietly, until the nightly job that took a minute takes six hours, and someone gets paged to rediscover this chapter.

    And the punchline is already inside the claim itself. SQL is admitted as the exception, for hot-spots — and then, over the life of the system, every part that matters turns out to be a hot-spot. One by one, the pieces that count get rewritten in SQL anyway, by tired people, during incident reviews. It is the same concession we saw with the raw-SQL escape hatch in the previous misconception: the exception clause ends up doing all the load-bearing work. At some point, the honest question is why the exception is not the architecture.

    5) Abstraction Over Concurrency and Integrity

    The Claim

    DDD's answer to data integrity is the aggregate. Eric Evans, Domain-Driven Design, Chapter 6:

    An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes.

    The aggregate root guards the boundary and enforces the invariants — the business rules that must never be broken. That Reddit picture from the first misconception is exactly this: Article at the root, guarding its packages, price periods, and quantity ranges, with isValid() standing watch.

    Vaughn Vernon — the same Vaughn Vernon from the aggregate store — codified the discipline in Implementing Domain-Driven Design (2013) and the "Effective Aggregate Design" essays it grew from. He is admirably precise about it:

    An invariant is a business rule that must always be consistent.

    A properly designed Aggregate is one that can be modified in any way required by the business with its invariants completely consistent within a single transaction.

    Thus, Aggregate is synonymous with transactional consistency boundary.

    Along with the rule that turns it into a discipline:

    A properly designed Bounded Context modifies only one Aggregate instance per transaction in all cases.

    So the claim: invariants — the rules that must always hold — are enforced by the domain model, in memory, one aggregate at a time. The community even has a slogan for it: the always-valid domain model.

    But read Vernon's third sentence again, slowly, because something remarkable is happening in it. "Aggregate is synonymous with transactional consistency boundary." The pattern defines itself as a transaction. Hold that thought.

    The Reality

    First things first: the goal is completely right. Invariants must hold — that was never in dispute; it is the same reasonable instinct we already conceded in the first misconception. The question was never whether to enforce invariants. The question is where enforcement actually binds.

    An invariant enforced in memory binds exactly one process — property four of the data structures misconception: the next writer does not inherit your discipline. And it binds at exactly one moment — validation time. Between the check and the write, the world keeps moving.

    The canonical example, the one every team eventually learns in production: usernames must be unique. The domain model checks — no such username, valid — and inserts. Two concurrent registrations both check, both pass, both insert. The "always-valid" model just produced invalid data, twice, without a single line of it misbehaving. Check-then-act on a private snapshot is a race by construction — TOCTOU, time-of-check to time-of-use, a bug class old enough to have its own acronym. And notice what every team actually does about it: they add a UNIQUE constraint. The engine catches what the model cannot.

    It is worth being precise about why the engine can do what the model cannot: it is the only party that sees every writer. Which makes it the only place where invariant machinery means anything: NOT NULL, CHECK, UNIQUE, foreign keys, EXCLUDE — wrapped in transactions, with isolation levels up to SERIALIZABLE, where concurrent transactions are guaranteed to behave as if they had run one at a time. Enforced against every writer: your application, the second instance behind the load balancer, the background job, the DBA at 2 a.m. No exceptions, and no discipline required.

    Time to pay the debt from the first misconception. The hardest invariant in that Reddit aggregate: price periods must not overlap. isValid() can inspect its own copy — while another process commits an overlapping period it has never heard of. Here is the entire invariant, declared:

    sql
    sql
    create extension if not exists btree_gist;
    +
    +alter table price_periods
    +add constraint price_periods_no_overlap
    +exclude using gist (
    +  package_id with =,
    +  daterange(date_begin, date_end, '[]') with &&
    +);

    Two periods for the same package with overlapping date ranges can now not exist. Not "will be caught, provided the request comes in through the domain layer" — cannot exist. Under any concurrency, from any writer, forever. One declaration. That is what enforcing an invariant actually means.

    And now unhold that thought from the claim. Aggregate is synonymous with transactional consistency boundary. The transaction is a database concept. The pattern's own definition concedes that invariant enforcement is transaction work — it just redraws the transaction as an object graph, in one process's private memory, where it can see no other writer and therefore enforce nothing. The aggregate is a hand-drawn picture of a transaction. The database has the real ones — and the real ones can span whatever rows and tables the invariant actually needs, not just one object cluster.

    The Cost

    Everything is enforced twice — or worse, once, in the wrong place. The same rules live in C# validation and in database constraints, drifting apart release by release — the behavioral edition of the duplicated models from the first misconception, same bill, new line item. And the team that takes "always-valid" at its word and skips the constraints has it worse: their invariants are now enforced nowhere. They hold only in the absence of concurrency — which is to say, they are not enforced. They are observed, until further notice.

    The races ship. Check-then-act bugs pass every unit test, because in the test the domain model really is alone — the race needs a second writer, and the test suite proudly mocks that out. The mock removes the exact enemy the invariant exists to fight. So the bug appears only under production load, intermittently, and is discovered as corrupted data weeks later: the duplicate payment, the double-booked slot, the negative stock. Every experienced engineer has one of these stories, and in every single one of them, the domain model passed all of its tests.

    The aggregate-boundary industry. Vernon's rule — one aggregate instance per transaction — voluntarily outlaws the multi-row, multi-table atomicity the engine offers natively. So what happens when a real invariant spans two aggregates? A whole discipline unfolds: redesign the boundaries, or accept eventual consistency between aggregates, coordinated through domain events, process managers, sagas, compensating actions. An entire detect-and-compensate machinery, invented to route around BEGIN ... COMMIT. The database would have held both rows in one transaction — the real kind — and gone to lunch.

    And with that, the five misconceptions close into a single picture. The state lives in the database (1), which already abstracts its own storage (2). Its tables cannot be replaced by in-memory structures — only impersonated by them (3). Its language already expresses the algorithms (4), and its transactions and constraints are the only invariant enforcement that actually binds (5). Every axis the modern application layer re-implements is an axis the engine already owns. The impedance mismatch was never a mapping problem between two equal worlds. It is the ongoing cost of running a simulation of one world inside the other — and calling the original "a detail."

    Comments

    + + + + \ No newline at end of file diff --git a/blog/DRAFT-npgsqlrest-vs-sqlpage.html b/blog/DRAFT-npgsqlrest-vs-sqlpage.html new file mode 100644 index 000000000..01a3d70e1 --- /dev/null +++ b/blog/DRAFT-npgsqlrest-vs-sqlpage.html @@ -0,0 +1,72 @@ + + + + + + NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    NpgsqlRest vs SQLPage: Two SQL-First Tools, Two Different Layers

    Comparison · SQLPage · SQL-First · June 2026


    Most comparisons on this site put NpgsqlRest next to other tools that do the same job — turning PostgreSQL into a REST API. PostgREST and Supabase are direct competitors in that sense: all three answer the same question.

    SQLPage is different. It shares NpgsqlRest's core idea — write SQL, not application code — but it spends that idea on the other end of the stack. SQLPage renders web UIs from SQL. NpgsqlRest serves REST APIs from SQL. So this isn't a "which one wins" piece. It's a map of where each one fits — and they fit together more often than they compete.

    The shared idea

    Both tools belong to the same family: logic and data live in the database, and a single self-contained binary turns SQL into something a browser can use — no ORM, no hand-written backend, no separate runtime.

    Shared foundationNpgsqlRestSQLPage
    SQL-first (.sql files are the unit of work)
    Single self-contained binary✅ (.NET)✅ (Rust)
    Connects directly to the database
    Built-in authentication & sessions
    File uploads
    CSV export
    Open source✅ (MIT)
    Trivial deployment (copy binary, run)

    If you like the SQL-first philosophy, you'll feel at home in either. The difference is what comes out of the binary.

    The fork in the road

    mermaid
    flowchart LR
    +    subgraph SP [SQLPage]
    +        direction LR
    +        S1[".sql file"] --> S2["UI components"] --> S3["HTML page"]
    +    end
    +    subgraph NR [NpgsqlRest]
    +        direction LR
    +        N1[".sql file / function"] --> N2["REST endpoint"] --> N3["JSON + typed client"]
    +    end

    SQLPage maps SQL results to UI components and streams back an HTML page. Each .sql file is a page. You pick a component (list, card, chart, form, table, map…) and feed it rows. There is no separate frontend — SQLPage is the frontend.

    NpgsqlRest maps SQL files and functions to REST endpoints and returns JSON. Each .sql file or routine is an API endpoint. There is no UI — you consume the API from your own frontend (often with a generated TypeScript client), a mobile app, or another service.

    That single difference — HTML out vs JSON out — is what everything else follows from.

    Where SQLPage is stronger: the UI

    This is SQLPage's whole identity, and it's genuinely strong. Building a page is a couple of SELECTs:

    sql
    sql
    -- users.sql — a complete, working web page
    +SELECT 'list' AS component, 'Users' AS title;
    +SELECT name AS title, email AS description, '/user.sql?id=' || id AS link
    +FROM users
    +ORDER BY name;

    Want a bar chart instead? Change the component name:

    sql
    sql
    SELECT 'chart' AS component, 'Monthly sales' AS title, 'bar' AS type;
    +SELECT month AS x, total AS y FROM sales ORDER BY month;

    SQLPage ships 50+ components — tables, cards, forms, charts, maps (with PostGIS/Spatialite), timelines, carousels, steps — plus password auth, form handling, and even multi-database support (PostgreSQL, MySQL/MariaDB, SQLite, SQL Server, and ODBC sources like DuckDB, ClickHouse, Snowflake, and BigQuery). For an internal dashboard, an admin panel, or a data-entry tool, you can have a working interface in minutes with zero frontend code.

    NpgsqlRest does not do this. It does not render SQL results into UI components. What it offers on the frontend side is the plumbing to build your own UI:

    That's useful, but it's not a UI builder. If "render a UI from SQL" is the goal, SQLPage wins decisively.

    Where NpgsqlRest is stronger: the API

    SQLPage can emit JSON — it has a json component, and with sqlpage.request_method() you can branch on GET/POST/PUT/DELETE and hand-roll an endpoint:

    sql
    sql
    -- A JSON endpoint in SQLPage — manual, one file at a time
    +SELECT 'json' AS component, 'array' AS type;
    +SELECT id, name, email FROM users ORDER BY name;

    That's fine for a simple read endpoint. SQLPage routes it automatically — like NpgsqlRest, dropping a .sql file gives you a URL with no route registration. But the file is an imperative script that happens to output JSON, not a declared endpoint. There's no inferred contract — no typed parameters, no OpenAPI, no client codegen, no per-endpoint policy. Every method check, status code, and header is something you write by hand in SQL.

    NpgsqlRest is built around the API. The same kind of endpoint is generated automatically, and configured declaratively in a SQL comment:

    sql
    sql
    create function api.get_user(p_id int)
    +returns setof user_info
    +language sql
    +begin atomic;
    +select id, name, email, role from users where id = p_id;
    +end;
    +
    +comment on function api.get_user(int) is '
    +HTTP GET /users/{p_id}
    +@authorize admin, user
    +@cached
    +@cache_expires_in 300
    +@rate_limiter_policy standard
    +';

    And it brings the rest of the API platform with it:

    API capabilityNpgsqlRestSQLPage
    File-based routing (drop a .sql file, get a URL)
    Expose existing functions/procedures as endpoints (no file)
    Inferred HTTP contract (typed params, method, response shape)❌ (imperative script per file)
    Path parameters (/users/{id}), method routing, function overloading
    OpenAPI / Swagger generation
    TypeScript client codegen + .http test files
    Per-endpoint caching (memory/Redis/hybrid)
    Per-endpoint rate limiting (partitioned per user/IP)
    Declarative auth schemes (JWT, encrypted Bearer/Cookie, OAuth, Passkey)⚠️ basic
    Reverse proxy & HTTP client types (call external APIs from SQL)
    Server-Sent Events streaming
    Error policies (RFC 7807, per-endpoint status mapping)
    Security headers, health checks, OpenTelemetry

    ⚠️ SQLPage has password/session auth aimed at protecting pages, not the multi-scheme token model an API needs.

    If "serve a typed, policy-rich API to a frontend, mobile app, or other service" is the goal, NpgsqlRest wins decisively.

    They're complementary, not rivals

    Here's the part most "X vs Y" posts miss: because they target different layers, you can run both against the same PostgreSQL database — and it's a good architecture.

    mermaid
    flowchart TD
    +    DB[("PostgreSQL")]
    +    SP["SQLPage<br/>internal dashboards,<br/>admin tools, reports"]
    +    NR["NpgsqlRest<br/>customer-facing REST API<br/>+ typed TS client"]
    +    DB <--> SP
    +    DB <--> NR
    +    SP --> Ops[Internal users / analysts]
    +    NR --> App[Public app / mobile / partners]

    A common split:

    • SQLPage for the inside — the ops dashboard, the admin panel, the quick report a colleague needs by Friday. Things where the audience is internal and a generated UI is exactly enough.
    • NpgsqlRest for the outside — the customer-facing API behind your product's frontend or mobile app, where you need a stable contract, typed clients, caching, rate limiting, and real auth.

    Both read the same tables and call the same functions. Your business logic stays in one place — the database — which is the whole point of the SQL-first / business-rules-in-the-database approach both tools share.

    When to choose each

    Reach for SQLPage when:

    • You need an internal tool, dashboard, or admin UI fast, with no frontend stack
    • Your audience is analysts or internal users, not a public API consumer
    • You want UI components (charts, maps, forms) generated straight from queries
    • You're on MySQL, SQLite, SQL Server, or a warehouse like DuckDB/ClickHouse/Snowflake — SQLPage is multi-database; NpgsqlRest is PostgreSQL-only

    Reach for NpgsqlRest when:

    • You're building an API — for your own frontend, a mobile app, partners, or AI agents
    • You want endpoints generated automatically with OpenAPI, typed TypeScript clients, and .http test files
    • You need production API concerns: caching, rate limiting, multiple auth schemes, retry, proxying external services, SSE
    • You want per-endpoint configuration version-controlled in SQL comments
    • You're on PostgreSQL and want to go deep on it

    Reach for both when you have an internal side and an external side — which most real products do.

    Conclusion

    SQLPage and NpgsqlRest answer different questions with the same philosophy. SQLPage asks "how do I get a UI out of SQL?" and answers it beautifully. NpgsqlRest asks "how do I get a production API out of SQL?" and answers that. Neither is a worse version of the other — they sit one layer apart.

    If you've already adopted the SQL-first mindset, the honest takeaway isn't "switch." It's: use SQLPage where you need a screen, use NpgsqlRest where you need an endpoint, and let both lean on the same PostgreSQL so your logic never gets duplicated.


    Comments

    + + + + \ No newline at end of file diff --git a/blog/case-study-zero-backend-code.html b/blog/case-study-zero-backend-code.html new file mode 100644 index 000000000..e3e3401e2 --- /dev/null +++ b/blog/case-study-zero-backend-code.html @@ -0,0 +1,37 @@ + + + + + + Case Study: 74 Endpoints, Zero Backend Code — A Production App Built Entirely on NpgsqlRest | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Case Study: 74 Endpoints, Zero Backend Code

    May 2026 · Case StudyArchitectureNpgsqlRest


    Most of the blog posts on this site argue why you might want to build an application with NpgsqlRest. This one is different: it reports what happened when a team actually did, end-to-end, on a real product. The application is anonymized — it's a finance/visualization platform with time-series charts, expression-based metric computation, and per-user dashboards — but the numbers are unmodified, taken directly from the repository on the day this post was written.

    It is, as far as we know, the first production-grade application built entirely on NpgsqlRest. No C#. No Python. No Node backend. The HTTP layer is the binary. Everything else lives in PostgreSQL.

    What's in the repository

    LayerFilesLines of code
    Public API SQL (auto-exposed as HTTP)804,889
    System / migrations / helpers SQL53~2,000
    pgTAP-style SQL tests1104,756
    Auto-generated TypeScript API client245,679
    Hand-written frontend TypeScript~36~1,700
    Svelte components4314,419
    Hand-written backend host code (C#/Python/Node)00
    Backend host config (appsettings.json)1217

    Approximately 74 HTTP endpoints are exposed. There are no controllers, no DTO classes, no repository layer, no hand-written API client. The single appsettings.json replaces what would otherwise be a Program.cs plus DI registration plus route mapping plus auth pipeline configuration.

    The application has shipped to production at build number 2.9.1517 — fifteen hundred production builds with no backend host code at any point in the project's history.

    What NpgsqlRest is doing for them

    Read through the repository and the workload NpgsqlRest absorbs turns out broader than most users probably realize from the docs:

    • All ~74 endpoints are auto-exposed from annotated PostgreSQL functions. HTTP verb, route, authorization, response shape, and per-module grouping are declared in SQL comments via HTTP POST / HTTP GET, authorize, tsclient_module = .... There is no route table.
    • The entire TypeScript client (~5,679 lines across 12 modules) is regenerated from the live database catalog on every dev-mode startup. The frontend imports the generated per-module API files (e.g. from "./<feature>Api.ts") — files no human ever touches.
    • The full WebAuthn / passkey ceremony is implemented as nine named SQL commands wired up in appsettings.json. Challenge generation, attestation, authentication, sign-count validation — all in PL/pgSQL. The host process knows the protocol; the application logic lives in functions.
    • Data-protection key storage (the keys ASP.NET uses to encrypt cookies and other transient state) is two SQL commands, not a custom IXmlRepository implementation in C#.
    • Server-Sent Events streaming for the long-running compute endpoint. The SQL function emits RAISE INFO notices as it processes; NpgsqlRest forwards them as SSE events so the frontend can update progress UI without polling. The generator emits the EventSource factory in TypeScript automatically. Both ends of the streaming protocol are configured through annotations — there is no custom streaming middleware.
    • Admin and observability endpoints/stats/routines, /stats/tables, /stats/indexes, /stats/activity — are built in. The team gets per-routine performance stats, table stats, and active-query visibility without writing any of it.
    • Static file serving with claims templating into index.html injects user identity into the SPA shell. No separate Node server in front of the app.

    Equally telling is what's available without writing host code and either already configured or queued for near-term addition. Two features being added next to the compute path illustrate how additions to this stack actually work:

    • Rate limiting on the compute endpoint — roughly five lines of appsettings.json to define a Concurrency policy plus one @rate_limiter_policy annotation on the function. Four policy types are available (FixedWindow, SlidingWindow, TokenBucket, Concurrency); choosing one is a config decision, not an implementation.
    • Response caching for the read-heavy endpoints — a cache profile in appsettings.json (Memory / Redis / Hybrid backend, default expiration, key parameter list, conditional When rules) plus one @cache_profile annotation per cached endpoint. Automatic invalidation endpoints are emitted as a side effect.

    The same applies to several other features the team has not yet needed but could enable with one config block and one annotation each: parameter validation rules (@validate), security headers (CSP / X-Frame-Options / Permissions-Policy via SecurityHeaders config), Kubernetes health probes (HealthChecks config), automatic OpenAPI/Swagger generation (OpenApiOptions), transparent column encryption (@encrypt / @decrypt) using ASP.NET Data Protection, parameter hashing for passwords (@parameter_hash), security-sensitive log obfuscation (@security_sensitive), antiforgery, response compression, command retry, CORS, external OAuth (Google / GitHub / Microsoft / LinkedIn / Facebook), and Excel/HTML table-format response rendering.

    To be precise about what "configuration" means here: ASP.NET Core and FastAPI applications also configure these features — they configure them in startup code (Program.cs, DI registration, middleware pipeline ordering). NpgsqlRest configures them in layered JSON. The binary ships with a documented defaults baseline of roughly 2,900 lines covering every available knob; this project overrides it with appsettings.json (217 lines, production) and appsettings.development.json (88 lines, dev override that layers cleanly on top of prod — diagnostic logs turned up, codegen and HTTP-file generation enabled, WebAuthn relying-party pointed at localhost). Combined application-specific configuration: 305 lines of JSON. Combined application-specific startup code: zero lines.

    The savings aren't that the features arrive magically. They're that they arrive pre-wired with sensible defaults, the override surface is uniform JSON rather than framework-specific DI ceremony, and prod-vs-dev layering is first-class. Every cross-cutting concern in the table below — caching, rate limiting, validation, security headers, health probes, OpenAPI — is enabled or tuned by adding keys to those 305 lines, not by modifying a startup pipeline.

    The blast radius of "stop using NpgsqlRest" is therefore much wider than just losing the routing layer. It would mean replacing roughly a dozen distinct, individually load-bearing features and re-expressing 305 lines of declarative JSON as several hundred lines of imperative startup code.

    The comparison: equivalent build in ASP.NET Core

    The realistic alternative for a .NET-shop building this application would be ASP.NET Core with Minimal APIs or controllers, plus Dapper or Npgsql for data access (an ORM doesn't fit the workload — most of the endpoints return composite records or aggregated time-series data). A typical per-endpoint cost in that stack:

    • Request DTO: 5–15 LOC
    • Response DTO: 5–15 LOC
    • Controller / Minimal API handler with validation, model binding, error mapping: 10–25 LOC
    • Repository or data-access method: 10–30 LOC
    • Service-layer method (often present even when not strictly necessary): 5–15 LOC
    • DI registration: 1–2 LOC

    Conservatively: 40–80 lines of C# per endpoint, spread across 3–5 files. For 74 endpoints, that is ~3,000–6,000 LOC before any application-specific concern is addressed.

    On top of the per-endpoint cost, the cross-cutting concerns NpgsqlRest already absorbs (or will absorb the moment a config block is added). Most of these features exist in classic ASP.NET Core too — built into the framework, exposed as attributes, or provided as official NuGet packages — so the LOC estimate assumes a disciplined .NET team using those built-ins, not reinventing them from scratch:

    ConcernNpgsqlRestHand-rolled ASP.NET Core (honest LOC est.)
    Program.cs / DI / routingconfig50–150 (Minimal APIs are dense)
    Cookie authconfig10–30 (AddAuthentication().AddCookie())
    WebAuthn / passkey ceremonies9 named SQL commands300–600 (Fido2.NET-Core plumbing, not built-in)
    Data-protection keys → PostgreSQL2 SQL commands0–50 (filesystem default works for many cases)
    TypeScript client generation + drift managementregenerated, free0–ongoing (NSwag CLI if you want it; otherwise hand-write the frontend client)
    Stats / activity / index admin endpointsbuilt-in0 (optional; most apps don't have them)
    SSE streaming endpoint(s)@sse annotation50–100 (Results.Stream, you own the protocol)
    Parameter validation pipeline@validate annotation + rules in config20–50 ([Required], [Range], [StringLength] attributes are free)
    Rate limiting (compute endpoint)config + @rate_limiter_policy annotation20–50 (.NET 7+ built-in AddRateLimiter())
    Response caching (multi-backend, profiles, invalidation)config + @cache_profile annotation30–100 (ResponseCacheAttribute + IDistributedCache; Redis adds more if needed)
    Health checks (Kubernetes probes)config10–30 (AddHealthChecks() built-in)
    Security headers middleware (CSP, X-Frame, etc.)config10–30 (NuGet package + a few lines)
    OpenAPI / Swagger documentationconfig5–20 (Swashbuckle is nearly free)
    Retry, forwarded headers, antiforgery, compression, CORSconfig30–80 (mostly built-in middleware)
    Cross-cutting subtotal~550–1,300

    Adding the per-endpoint plumbing (~3,000–6,000 LOC for 74 composite/aggregated endpoints) to the honest cross-cutting subtotal gives a realistic ASP.NET Core equivalent total of ~3,500–7,300 lines of C# — plus a .csproj, a layered project structure, and a CI step to keep the generated TypeScript client in sync with the deployed API. None of this exists in the case-study repository.

    A FastAPI / Python equivalent comes out leaner still — Pydantic is denser than C# DTOs, and FastAPI ships validation, OpenAPI, and dependency injection in the framework itself. WebAuthn plumbing (py_webauthn), rate limiting (slowapi), response caching (fastapi-cache + Redis), and SSE streaming still cost real lines, but the per-endpoint cost compresses substantially. Honest estimate: ~2,500–5,000 LOC of Python.

    The earlier framing of this case study quoted higher figures (7,000–13,000 for .NET, 5,000–9,000 for Python). Those overcounted the cross-cutting layer because they assumed hand-rolled implementations of features that classic .NET / FastAPI actually ship with — built-in rate limiting, attribute-based validation, framework-managed health checks, Swashbuckle, and so on. The revised numbers above credit those frameworks honestly. The savings are still significant — 3,500–7,300 LOC is a meaningful slice of a full-stack codebase — but the LOC headline is smaller than it first appeared, and the real edge has to be argued qualitatively, not by raw line count alone.

    How it scores on the four dimensions that matter

    Productivity

    The headline number isn't lines of code — it's that impedance mismatch is gone entirely. Both kinds.

    The classic object–relational impedance mismatch — objects on one side, rows on the other, an ORM negotiating between them — doesn't exist here because there's no object layer. PostgreSQL composite types and JSON are the data model, end to end. The frontend receives the same shapes the database returns.

    The less-discussed function impedance mismatch — the chain of translation layers between an HTTP request and the SQL that ultimately runs (controller → service → repository → ORM → SQL) — doesn't exist either. The SQL function is the endpoint. The generated TypeScript wrapper calls it directly. There are no intermediate functions whose signatures can drift from the layer below them, because there are no intermediate functions.

    In practice, adding a column in a typical ASP.NET Core + EF Core path means: write a migration, update the entity, update the DTO, update the mapping, update the endpoint, regenerate the TypeScript client, fix any drift the regeneration surfaces. In this project: edit the SQL function, restart the dev server. The TypeScript client rewrites itself; the type checker fails the build on every line of frontend code that no longer matches.

    Adding a new endpoint is: write one annotated SQL function. Restart. Done. There is no parallel set of files in the host language to keep in sync, because there is no host language.

    The empirical signal that this works at scale is build number 2.9.1517. A development loop that is genuinely faster is the only thing that gets a small team to that build count.

    One productivity dimension worth naming explicitly in the LLM-assisted era: this architecture is far more token-efficient. Adding a feature in a conventional N-layer stack requires loading the DTO, controller, service, repository, ORM mapping, frontend type definition, and frontend API client into the model's context (eight to ten files with strict cross-file consistency requirements), and asking the model to produce the change requires it to write the new code in lockstep across all of them. In this architecture, the same change is one SQL function plus one frontend component. Two files, no inter-layer consistency burden (the generator handles it), and far less boilerplate for the model to reproduce. An "add an endpoint" task that runs roughly 5,000 tokens in a typical ASP.NET Core or FastAPI codebase runs closer to 1,000–1,500 here — a 3–5× reduction that compounds across a year of AI-assisted feature work, in both inference cost and developer wait time. The same simplification that The Power of Simplicity post frames at the architectural level as raw energy savings shows up at the per-task level as token savings.

    Time saved, quantified

    The arguments above are structural. The same effect can be measured in hours and iteration-cycle time on the same repository — and the picture that emerges is stronger than the LOC table alone suggests.

    One-time scaffolding avoided. In a hand-written stack, each endpoint costs roughly 20–35 minutes of pure typing: request interface (3–5 min), response interface (5–10 min), fetch wrapper with URL builder and serialiser (10–15 min), and an .http line for testing (3–5 min). Across the 74 endpoints exposed by this application, that is 25–45 hours of mechanical work the team never spent. The raw artifact size corroborates the estimate — the 5,679-line autogenerated TypeScript client plus an autogenerated .http test file is roughly the volume an experienced developer produces in that many hours on boilerplate (~125 LOC/hour).

    The phrase "one-time" understates the saving in one important direction. The line counts in the table are the current snapshot of an API surface that has been reshaped many times as requirements — and the team's understanding of them — evolved. In a hand-written stack, every rename, every restructured return type, every endpoint that was later split, merged, or dropped would have required rewriting the corresponding client and interface code at the time of the change. NpgsqlRest regenerates the entire client on the next db-up, in full, regardless of how many iterations happened. The integral of typing avoided over the project's history is therefore meaningfully larger than the snapshot — earlier versions of these clients, including the ones that no longer exist, would all have been hand-written and rewritten in a conventional stack.

    Ongoing maintenance avoided. Every backend signature change in a conventional stack means edits to the SQL, the request interface, the response interface, the fetch wrapper, and any callers — five files minimum, plus the inevitable type-drift bug when one of them is missed. Each such change costs roughly 5–15 minutes of cross-file editing, and the misses cost 30+ minutes of runtime debugging when an interface quietly lies about the shape the API actually returns. The NpgsqlRest cycle is edit SQL → restart → done; the regenerated client either matches or fails the type checker on every consumer at once. Across the lifetime of this codebase — through several significant refactors and feature additions spanning the auth surface, the visualization pipeline, and the user-management subsystem — there have been hundreds of signature-level changes. Even at a conservative 10 minutes saved per change × 200 changes, that is another ~30 hours of mechanical edits avoided, with the type-drift bug tail entirely absent.

    Iteration-speed multiplier. This is the qualitative win, and it is the largest of the three:

    • Signature changes are roughly 5–10× faster end-to-end. No grep-for-callers pass, no parallel interface updates, no compile-and-fix loop. The TypeScript client rewrites itself on dev restart, and the compiler flags every stale call site.
    • New-feature wire-up is roughly 2–3× faster. The backend → frontend wire is free, so every cycle goes into UI / UX work rather than plumbing.
    • Refactoring courage is qualitatively different. Renaming a column or restructuring a return type is free across the host-language boundary, so refactors that would feel heavy in a conventional stack — full subsystem rewrites, schema reshuffles — get done instead of deferred. The 5.6× performance rewrite cited under Performance is one example; it was a full implementation-language change inside the function, and the only reason it shipped without weeks of cross-stack updates is that there were no cross-stack updates to make.
    • An entire class of bugs simply does not exist. "The interface said string | null but the API returns ''" or "the enum changed in the database but the frontend constant didn't" — these are the bugs that pad every release cycle in a hand-written stack and chew up debugging hours. With the schema as the single source of truth, they cannot happen.

    The bottom line. Conservatively, 55–100 hours of typing saved across the lifetime of this project — and that "conservatively" matters: the figure is anchored to the current 74 endpoints, not to the larger set of endpoints that existed and were rewritten along the way as the product's requirements evolved. On top of that, an iteration loop that is roughly 5× faster on signature changes and 2–3× faster on end-to-end feature additions. None of those numbers were measured with a stopwatch — they are honest estimates from per-task timing and the actual change history — but they line up with the LOC totals and with the build cadence (1,500+ production builds, small team) the project has actually sustained. Combined with the token-efficiency multiplier described above, this is the kind of compounding gain that determines whether a small team can ship a feature surface this wide at all.

    Lines of code saved

    Net of the SQL the team would have written anyway (the business logic has to live somewhere), our honest estimate is ~3,500–7,300 lines of host-language code eliminated — roughly 3,000–6,000 of per-endpoint plumbing (DTOs, controllers, repositories, services, DI registrations across 220–370 files) plus another 550–1,300 of cross-cutting code that is configuration here and a mix of built-in middleware, attributes, and small custom wiring elsewhere. On top of that, the 5,679-line generated TypeScript client is a purely free byproduct.

    The dominant slice of the saving — and the most defensible one — is the per-endpoint plumbing layer, not the cross-cutting infrastructure. Classic ASP.NET Core and FastAPI both ship with strong cross-cutting stories (built-in rate limiting, attribute-driven validation, framework-managed health checks, Swashbuckle, response caching). The compression NpgsqlRest provides on those concerns is real but modest. Where it dominates is on the architectural layer that classic stacks cannot compress: every endpoint still needs a request DTO, a response DTO, a controller, a repository, and a service in a hand-written stack, and those add up to thousands of lines that the SQL-as-endpoint model simply doesn't have.

    One caveat: SQL functions tend to be denser than C# or Python, so a one-to-one LOC swap understates the effective savings. The eliminated code is overwhelmingly the low-value boilerplate — DTOs, mappers, route attributes, repository methods that are one query each — not the parts of an application where careful design pays off.

    Where the real edge actually is — qualitatively. The LOC argument above tells part of the story. The structural wins, which are harder to put a number on but compound across the project's lifetime, are:

    1. No DTO layer at all. The largest concrete saving (~3,000–6,000 LOC). Attributes don't help here; you still need the DTOs in classic .NET.
    2. Per-endpoint annotations sit next to the SQL. @cache_profile, @rate_limiter_policy, @authorize on the function itself — same mechanism as [ResponseCache], far better locality than a controller four files away from the query.
    3. WebAuthn shipped as SQL functions. ~300–600 LOC of C# that genuinely doesn't exist in this project, because Fido2.NET-Core wiring is not built-in to the framework.
    4. Single source of truth. The schema is the contract. No DTO can lie about the database shape, no interface can drift from the response. This is a bug class that does not exist by construction — and the time-cost of those bugs is invisible in any LOC count.
    5. TypeScript client + types regenerated from the live schema. A free byproduct; in classic .NET you either run NSwag/Kiota (works, but a separate toolchain to maintain) or hand-write the client on the frontend.
    6. Layered prod-vs-dev JSON is first-class. Classic .NET has appsettings.{Environment}.json too, but in practice some of the environment-specific behaviour ends up in if (env.IsDevelopment()) branches in Program.cs. NpgsqlRest's overlay model puts every override in one place.

    Performance

    Strong, structurally. One database round-trip per request. No ORM materialization, no entity-graph allocation in the API process, no DTO mapping pass. Stored functions are precompiled in PostgreSQL. The transport layer is Kestrel + Npgsql, which is already at the top tier of TechEmpower-style benchmarks for the underlying primitives.

    The performance-tuning work concentrates where it actually pays off: in the database. A recent optimization in this project rewrote one of the heaviest functions from PL/pgSQL with temp tables to plain SQL with CTEs and produced a measured 5.6× speedup (1.96 ms → 0.35 ms over 500 calls × 20 mixed inputs). That improvement is visible directly in pg_stat_statements. In a conventional stack, the same query is hidden behind ORM-generated SQL and the per-request cost is split across object materialization, JSON serialization, and HTTP middleware — all of which obscure where the actual time goes.

    That kind of rewrite — a full implementation swap, a different language paradigm inside the function, and two unrelated correctness fixes folded in along the way — is exactly the change that goes badly wrong without a safety net. It didn't here, because the safety net already existed: the 110 SQL test files and 4,756 lines of assertions covered later in this post. The optimization shipped in the same commit as new test coverage for the edge cases the rewrite exposed. The tests run against the real PostgreSQL engine, against the real function being optimized, and finish in seconds. There is no faster correctness gate than that, and without it a 5.6× refactor is the sort of thing teams quietly decide isn't worth the risk. Performance work and test coverage are not two separate concerns in this architecture; they're the same loop.

    Overall quality

    End-to-end type safety is genuinely better than most hand-rolled stacks. PostgreSQL types map directly to TypeScript types via the generator. There is no DTO layer in the middle that can disagree with either the database or the frontend. Schema drift is impossible by construction: if a column is renamed, the generated client changes shape on the next dev restart, and every consumer fails to compile.

    The schema is the single source of truth. There is no ORM mapping that can lie about it.

    The test suite — 110 SQL test files totaling 4,756 lines — runs against the real PostgreSQL engine, not a mock or in-memory fake. Every assertion exercises the same code path that production uses. This is harder to do well than a conventional unit-test suite, but the tests that exist are categorically more truthful.

    Honest tradeoffs

    A case study that doesn't acknowledge tradeoffs is a sales pitch. After working through the obvious objections, two stand up:

    • Younger ecosystem. NpgsqlRest is newer and has a smaller community than ASP.NET Core or FastAPI. Fewer Stack Overflow answers, fewer third-party plugins, fewer "battle-tested by a thousand companies" reassurance signals. Mitigated by the fact that PostgreSQL itself — which does most of the heavy lifting in this architecture — is anything but new.
    • SQL fluency is a precondition, not a nice-to-have. Anyone working on the backend has to be comfortable in PL/pgSQL: not just SELECTs, but window functions, CTEs, procedural control flow, and reading EXPLAIN output. AI tooling has closed most of the on-ramp — modern coding assistants write competent PL/pgSQL on demand — but the architecture genuinely rewards teams that invest in SQL skill rather than treating it as a thing the ORM hides.

    That's the honest list. A few other objections look like tradeoffs at first but don't survive scrutiny:

    • "Refactoring SQL across a schema lacks IDE support." In practice, the auto-generated TypeScript client gives every backend function a real, IDE-indexed symbol on the frontend (some_function_namesomeFunctionName). "Find all references" on the generated function locates every cross-stack caller. Modern PostgreSQL IDEs (DataGrip, JetBrains DB tools) also index PL/pgSQL, and the database itself fails function creation if a referenced object is missing. The cross-stack refactoring story is, if anything, better than in conventional architectures.
    • "Logic-heavy SQL is harder to test." The opposite turned out to be true on this project. set constraints all deferred plus a transaction-scoped rollback gives clean isolation with no fixture framework. Putting drop function, create function, and the test in a single .sql file lets you re-run the entire cycle on save — a feedback loop measurably faster than rebuilding a .NET or Python project. Different idiom from xUnit / pytest, but not harder. (Since this project shipped, NpgsqlRest 3.19 turned this pattern into a built-in SQL test runner.sql tests invoking the real endpoints in-process, with watch mode and coverage reporting.)
    • "Lock-in to NpgsqlRest." The exit path is to write the controller layer and cross-cutting concerns you skipped — i.e., to do the ~3,500–7,300 LOC of host-language work this case study just argued against. That isn't lock-in in any meaningful sense; it's a choice that's reversible at exactly the price the alternative architecture charges upfront. The SQL stays portable.
    • "Calling external APIs requires a host language." It doesn't. NpgsqlRest's HTTP Types let you declare external REST calls in a PostgreSQL type comment using .http file syntax — function parameters substitute into URLs, headers, and request bodies, and the response is handed to your function as a parameter. The HTTP call is made from the NpgsqlRest tier, not from inside the database, so there's no pgsql-http extension to install and no database connection sitting blocked on a remote service. For full gateway scenarios, the reverse proxy @proxy annotation supports both passthrough and transform modes (response routed through a PG function for caching, enrichment, or transformation). External integration is one of the architecture's stronger stories, not a weak point.

    What this case study is, and isn't

    It isn't a benchmark. It isn't a controlled study. It's a single production application, observed honestly, with the numbers reported as they are.

    What it does establish is that the architecture works — not as a demo, not as a "how-to," but as the substrate of a real product that has shipped 1,500+ builds. The combination most people assume can't be done without host code (WebAuthn, data-protection keys, real-time SSE streaming, an admin/observability surface, a generated frontend client, structured tests, and — when the team gets to them — rate limiting and response caching) is all here, in SQL and configuration, in production.

    If you've been wondering whether a database-first architecture scales past CRUD demos, this is the existence proof.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/csv-excel-ingestion-postgresql-npgsqlrest.html b/blog/csv-excel-ingestion-postgresql-npgsqlrest.html new file mode 100644 index 000000000..8d241cc1e --- /dev/null +++ b/blog/csv-excel-ingestion-postgresql-npgsqlrest.html @@ -0,0 +1,371 @@ + + + + + + CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    CSV and Excel Ingestion Made Easy: PostgreSQL Row Processing with NpgsqlRest

    January 2026 · CSVExcelPostgreSQLData ImportNpgsqlRest

    Most CSV and Excel import code makes you know the file structure in advance, hardcode it into your application, and redeploy on every change.

    NpgsqlRest's CSV and Excel upload handlers move that structure into a SQL function instead: adding a column becomes a CREATE OR REPLACE, not a deployment. This tutorial walks through the row-by-row processing pipeline, with the auto-generated TypeScript client and progress tracking included.

    Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/7_csv_excel_uploads

    The Traditional Approach: Rigid and Brittle

    With traditional CSV/Excel import implementations, you must:

    1. Know the exact structure before writing code
    2. Hardcode column mappings into your application
    3. Redeploy the application whenever the file format changes
    4. Maintain separate code paths for different file types

    And that's just the structure problem. You also need to:

    • Choose and configure a parsing library (pandas, Apache Commons CSV, csv-parse, ExcelDataReader...)
    • Write file upload handling with multipart/form-data parsing
    • Implement transaction management (BEGIN, COMMIT, ROLLBACK)
    • Handle validation (MIME types, file size limits, format checking)
    • Integrate authentication to track who uploaded what
    • Write error handling for malformed files and partial failures
    • Create TypeScript types manually for the frontend

    The result: a simple change like adding a column requires code changes, testing, and redeployment, on top of the hundreds of lines of boilerplate you wrote just to get the basics working.

    The NpgsqlRest Approach: Dynamic and Flexible

    All of that boilerplate comes out of the box:

    • CSV and Excel parsing? Built-in.
    • File upload handling? Built-in.
    • Transaction management? Built-in.
    • MIME type validation? Built-in.
    • Authentication integration? Built-in.
    • Error handling and rollback? Built-in.
    • TypeScript client with progress tracking? Auto-generated.

    Your row function receives a text[] array containing whatever is in the row. That's it. No hardcoded structure, no column mappings in application code, no redeployment when formats change.

    Simply put:

    • Your row function is executed for every row in the file
    • You receive dynamic data as text[] - the raw values from that row
    • You receive upload metadata - file name, content type, user claims
    • You receive the result from the previous row - enabling accumulation patterns (like returning the last inserted ID)

    When the file structure changes, you just ALTER or CREATE OR REPLACE your row function. No application restart required.

    How It Works

    mermaid
    flowchart TB
    +    HTTP["HTTP POST
    +    multipart/form-data + file"]
    +
    +    HTTP --> NR["NpgsqlRest Handler
    +    • Parses CSV/Excel
    +    • Manages transaction"]
    +
    +    NR --> R1["Row 1: $1=1, $2=data, $3=prev, $4=meta"]
    +    NR --> R2["Row 2: $1=2, $2=data, $3=prev, $4=meta"]
    +    NR --> RN["Row N: $1=N, $2=data, $3=prev, $4=meta"]
    +
    +    R1 & R2 & RN --> FN["Your Row Function (SQL)
    +    INSERT INTO table VALUES (...)"]

    NpgsqlRest:

    1. Receives the upload via HTTP multipart/form-data
    2. Parses the file using optimized C# libraries (CsvHelper for CSV, ExcelDataReader for Excel)
    3. Calls your SQL function for each row, passing the data as a text array
    4. Manages the transaction - all rows succeed or all are rolled back
    5. Returns metadata about the upload to your main function

    The Row Function: Four Parameters, Infinite Flexibility

    ParameterTypeDescription
    $1intRow index (1-based)
    $2text[]Row values as a text array - whatever is in the row
    $3anyResult from previous row (for chaining/accumulation)
    $4jsonMetadata (file name, MIME type, user claims, etc.)

    CSV Row Function Example

    sql
    sql
    create or replace function example_7.csv_upload_row(
    +    _index int,           -- Row number (1-based)
    +    _row text[],          -- Row data: _row[1], _row[2], etc. - dynamic!
    +    _prev_result int,     -- Return value from previous row call
    +    _meta json            -- Metadata: fileName, contentType, size, claims
    +)
    +returns int
    +language plpgsql
    +as $$
    +begin
    +    insert into example_7.csv_uploads (user_id, file_name, row_index, row_data)
    +    values (
    +        (_meta->'claims'->>'user_id')::int,  -- User ID from auth claims
    +        _meta->>'fileName',                   -- Original file name
    +        _index,                               -- Row number
    +        coalesce(_row, '{}')                  -- Row data as array
    +    );
    +
    +    -- Return count for chaining - passed to next row as $3
    +    return coalesce(_prev_result, 0) + 1;
    +end;
    +$$;

    Excel Row Function Example

    Excel is nearly identical, but includes sheet name and actual Excel row index in metadata:

    sql
    sql
    create or replace function example_7.excel_upload_row(
    +    _index int,
    +    _row text[],
    +    _prev_result int,
    +    _meta json
    +)
    +returns int
    +language plpgsql
    +as $$
    +begin
    +    insert into example_7.excel_uploads (user_id, file_name, sheet_name, row_index, row_data)
    +    values (
    +        (_meta->'claims'->>'user_id')::int,
    +        _meta->>'fileName',
    +        _meta->>'sheet',        -- Sheet name (Excel only)
    +        _index,
    +        coalesce(_row, '{}')
    +    );
    +
    +    return coalesce(_prev_result, 0) + 1;
    +end;
    +$$;

    The same row function works for both CSV and Excel with minor metadata differences. You can even reuse the same function for both if you don't need sheet-specific handling.

    Row Chaining: The Power of $3

    The $3 parameter (_prev_result) receives whatever the previous row's function call returned. For the first row, it's NULL. This enables accumulation patterns:

    Counting rows:

    sql
    sql
    return coalesce(_prev_result, 0) + 1;  -- Returns 1, 2, 3, ... N

    Returning the last inserted ID:

    sql
    sql
    -- Insert and return the new ID
    +insert into orders (...) values (...) returning id into _new_id;
    +return _new_id;  -- Next row receives this as _prev_result

    Summing a column:

    sql
    sql
    return coalesce(_prev_result, 0) + (_row[3])::numeric;  -- Sum column 3

    Building a running list:

    sql
    sql
    return coalesce(_prev_result, '[]'::json) || json_build_array(_row[1]);

    The final return value is included in the upload metadata as lastResult (CSV) or result (Excel).

    The Upload Endpoint Function

    The main upload function receives metadata about all processed files:

    sql
    sql
    create or replace function example_7.csv_upload(
    +    _meta json = null
    +)
    +returns json
    +language sql
    +begin atomic;
    +select _meta;
    +end;
    +
    +comment on function example_7.csv_upload(json) is '
    +HTTP POST
    +@upload for csv
    +@param _meta is upload metadata
    +@delimiters = ,;
    +@row_command = select example_7.csv_upload_row($1,$2,$3,$4)';

    That's it. This annotation:

    • Creates a POST /api/example-7/csv-upload endpoint
    • Configures the CSV parser to accept , and ; as delimiters
    • Specifies which function processes each row
    • Injects upload metadata into _meta

    Upload Metadata Structure

    The _meta parameter receives a JSON array with one element per uploaded file:

    json
    json
    [
    +  {
    +    "type": "csv",
    +    "fileName": "sales_data.csv",
    +    "contentType": "text/csv",
    +    "size": 45678,
    +    "success": true,
    +    "status": "Ok",
    +    "lastResult": 1247
    +  }
    +]

    For Excel with multiple sheets (all_sheets = true), you get one entry per sheet:

    json
    json
    [
    +  {
    +    "type": "excel",
    +    "fileName": "report.xlsx",
    +    "sheet": "January",
    +    "success": true,
    +    "rows": 450,
    +    "result": 450
    +  },
    +  {
    +    "type": "excel",
    +    "fileName": "report.xlsx",
    +    "sheet": "February",
    +    "success": true,
    +    "rows": 380,
    +    "result": 380
    +  }
    +]

    Dynamic Structure: No Hardcoding, No Redeployment

    The text[] approach is the key to flexibility:

    Store raw, process later:

    sql
    sql
    -- Accept ANY file structure without schema changes
    +insert into raw_imports (source, row_index, data, imported_at)
    +values (
    +    _meta->>'fileName',
    +    _index,
    +    _row,  -- Store the entire text[] as-is
    +    now()
    +);

    Transform when you know the structure:

    sql
    sql
    -- File structure: name, email, signup_date
    +create or replace function process_user_import(
    +    _index int,
    +    _row text[],
    +    _prev_result int,
    +    _meta json
    +)
    +returns int
    +language plpgsql
    +as $$
    +begin
    +    -- Skip header row
    +    if _index = 1 then
    +        return 0;
    +    end if;
    +
    +    insert into users (name, email, signup_date, imported_by)
    +    values (
    +        _row[1],                              -- Name (text)
    +        lower(trim(_row[2])),                 -- Email (normalized)
    +        _row[3]::date,                        -- Signup date (cast to date)
    +        (_meta->'claims'->>'user_id')::int    -- Importing user
    +    );
    +
    +    return coalesce(_prev_result, 0) + 1;
    +end;
    +$$;

    When the structure changes, just update the function:

    sql
    sql
    -- New file structure: name, email, phone, signup_date
    +create or replace function process_user_import(...)
    +...
    +    insert into users (name, email, phone, signup_date, imported_by)
    +    values (
    +        _row[1],
    +        lower(trim(_row[2])),
    +        _row[3],           -- New phone column
    +        _row[4]::date,     -- signup_date moved to column 4
    +        (_meta->'claims'->>'user_id')::int
    +    );
    +...

    No application restart. No redeployment. Instant effect.

    Configuration

    Enable CSV and Excel handlers in config.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": true,
    +
    +      "UploadHandlers": {
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadDelimiterChars": ",;",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +
    +        "ExcelUploadEnabled": true,
    +        "ExcelUploadKey": "excel",
    +        "ExcelAllSheets": true,
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss"
    +      }
    +    }
    +  }
    +}

    Annotation Options

    CSV Handler Options

    OptionDefaultDescription
    row_command(required)SQL command to process each row
    delimiters,Delimiter character(s) for parsing
    has_fields_enclosed_in_quotestrueFields may be enclosed in quotes
    set_white_space_to_nulltrueConvert whitespace-only values to NULL

    Excel Handler Options

    OptionDefaultDescription
    row_command(required)SQL command to process each row
    sheet_namenullSpecific sheet to process (first sheet if null)
    all_sheetsfalseProcess all sheets in workbook
    time_formatHH:mm:ssFormat for time values
    date_formatyyyy-MM-ddFormat for date values
    datetime_formatyyyy-MM-dd HH:mm:ssFormat for datetime values
    row_is_jsonfalsePass row as JSON instead of text array
    fallback_handlernullHandler name to delegate to if format validation fails (e.g., csv). Available on all upload handlers since 3.8.0 (previously Excel-only).

    Row Metadata Differences: CSV vs Excel

    CSV row metadata ($4):

    json
    json
    {
    +  "type": "csv",
    +  "fileName": "data.csv",
    +  "contentType": "text/csv",
    +  "size": 12345,
    +  "claims": { "user_id": "1", "username": "alice" }
    +}

    Excel row metadata ($4):

    json
    json
    {
    +  "type": "excel",
    +  "fileName": "data.xlsx",
    +  "contentType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    +  "size": 67890,
    +  "sheet": "Sheet1",
    +  "rowIndex": 5,
    +  "claims": { "user_id": "1", "username": "alice" }
    +}

    Key difference: Excel includes sheet (current sheet name) and rowIndex (actual Excel row number including empty rows). CSV does not have these - use $1 for the row index.

    Generated TypeScript Client

    NpgsqlRest automatically generates a TypeScript client with progress tracking:

    typescript
    typescript
    // Auto-generated - you write ZERO of this code
    +
    +interface ICsvUploadResponse {
    +    type: string;
    +    fileName: string;
    +    contentType: string;
    +    size: number;
    +    success: boolean;
    +    status: string;
    +    [key: string]: string | number | boolean;
    +}
    +
    +export async function csvUpload(
    +    files: FileList | null,
    +    request: ICsvUploadRequest,
    +    progress?: (loaded: number, total: number) => void,
    +): Promise<{
    +    status: number,
    +    response: ICsvUploadResponse[],
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    return new Promise((resolve, reject) => {
    +        if (!files || files.length === 0) {
    +            reject(new Error("No files to upload"));
    +            return;
    +        }
    +        var xhr = new XMLHttpRequest();
    +        if (progress) {
    +            xhr.upload.addEventListener("progress", (event) => {
    +                if (event.lengthComputable && progress) {
    +                    progress(event.loaded, event.total);
    +                }
    +            }, false);
    +        }
    +        xhr.onload = function () {
    +            if (this.status >= 200 && this.status < 300) {
    +                resolve({status: this.status, response: JSON.parse(this.responseText), error: undefined});
    +            } else {
    +                resolve({status: this.status, response: [], error: JSON.parse(this.responseText)});
    +            }
    +        };
    +        xhr.onerror = function () {
    +            reject({xhr: this, status: this.status, statusText: this.statusText});
    +        };
    +        xhr.open("POST", baseUrl + "/api/example-7/csv-upload" + parseQuery(request));
    +        const formData = new FormData();
    +        for(let i = 0; i < files.length; i++) {
    +            formData.append("file", files[i], files[i].name);
    +        }
    +        xhr.send(formData);
    +    });
    +}

    Using it in your frontend:

    typescript
    typescript
    import { csvUpload } from "./example7Api.ts";
    +
    +const response = await csvUpload(
    +    fileInput.files,
    +    {},
    +    (loaded, total) => {
    +        const percent = Math.round((loaded / total) * 100);
    +        progressBar.style.width = `${percent}%`;
    +    }
    +);
    +
    +if (response.status === 200) {
    +    console.log(`Imported ${response.response[0].lastResult} rows`);
    +}

    Advanced Patterns

    Skipping Header Rows

    sql
    sql
    if _index = 1 then
    +    return null;  -- Skip header, don't increment counter
    +end if;

    Validation and Rejection

    sql
    sql
    -- Validate required fields
    +if _row[1] is null or _row[2] is null then
    +    raise exception 'Row % missing required fields', _index;
    +end if;
    +
    +-- Validate format
    +if _row[3] !~ '^\d{4}-\d{2}-\d{2}$' then
    +    raise exception 'Row % has invalid date format: %', _index, _row[3];
    +end if;

    Upsert (Insert or Update)

    sql
    sql
    insert into products (sku, name, price)
    +values (_row[1], _row[2], _row[3]::numeric)
    +on conflict (sku) do update set
    +    name = excluded.name,
    +    price = excluded.price,
    +    updated_at = now();

    Processing Only Specific Sheets

    sql
    sql
    comment on function excel_upload(json) is '
    +HTTP POST
    +@upload for excel
    +@sheet_name = Transactions
    +@row_command = select process_transaction_row($1,$2,$3,$4)';

    JSON Row Format for Complex Data

    sql
    sql
    comment on function excel_upload(json) is '
    +HTTP POST
    +@upload for excel
    +@row_is_json = true
    +@row_command = select process_json_row($1,$2,$3,$4)';

    With row_is_json = true, $2 becomes JSON with Excel cell references as keys:

    json
    json
    {"A1": "Name", "B1": "Amount", "C1": 123.45}

    Transaction Safety

    All row commands execute within a single transaction. If any row fails:

    • All previous inserts are rolled back
    • Error is returned to the client
    • No partial imports

    This is automatic - you don't write transaction handling code.

    Combining Handlers: Process AND Store the Original File

    You can combine CSV/Excel handlers with file_system or large_object handlers to both process the rows AND store the original file. This is useful when you need to:

    • Keep the original file for audit/compliance
    • Allow users to download the source file later
    • Reprocess the file if business logic changes
    sql
    sql
    comment on function example_7.csv_upload(json) is '
    +HTTP POST
    +@upload for csv, large_object
    +@param _meta is upload metadata
    +@row_command = select example_7.csv_upload_row($1,$2,$3,$4)';

    The handler list is comma-separated. With this configuration:

    1. The CSV handler processes each row via row_command
    2. The Large Object handler stores the original file in PostgreSQL
    3. Both operate within the same transaction

    Combined Handler Metadata

    When using multiple handlers, your upload function receives a JSON array with one entry per handler:

    json
    json
    [
    +  {
    +    "type": "csv",
    +    "fileName": "data.csv",
    +    "contentType": "text/csv",
    +    "size": 12345,
    +    "success": true,
    +    "status": "Ok",
    +    "lastResult": 500
    +  },
    +  {
    +    "type": "large_object",
    +    "fileName": "data.csv",
    +    "contentType": "text/csv",
    +    "size": 12345,
    +    "success": true,
    +    "status": "Ok",
    +    "oid": 16456
    +  }
    +]

    The CSV entry contains lastResult (final row function return value), while the Large Object entry contains oid (PostgreSQL Large Object identifier for retrieving the file later).

    You can store both in your database:

    sql
    sql
    insert into csv_imports (file_name, rows_processed, original_file_oid)
    +select
    +    m->>'fileName',
    +    (m->>'lastResult')::int,
    +    (select (x->>'oid')::bigint from json_array_elements(_meta) x where x->>'type' = 'large_object')
    +from json_array_elements(_meta) m
    +where m->>'type' = 'csv' and (m->>'success')::boolean = true;

    If any row command fails or raises an exception, the entire transaction rolls back - including the Large Object storage. The original file won't be saved if processing fails.

    You get structured data extraction and original-file preservation in a single transaction.

    For more details on Large Object and File System handlers, see Secure Image Uploads with PostgreSQL.

    Fallback Handler: One Endpoint for Both Excel and CSV

    Updated in 3.8.0

    The fallback_handler parameter is now available on all upload handlers (previously Excel-only). This enables scenarios like CSV format check fails on a binary file → fall back to large_object or file_system to save the raw file.

    Sometimes users don't know (or care) whether their file is .xlsx or .csv - they just want to upload it. With the fallback_handler parameter, you can create a single upload endpoint that tries Excel first and automatically falls back to CSV if the file isn't a valid Excel format:

    sql
    sql
    create or replace function example_7.combined_upload(
    +    _meta json = null
    +)
    +returns json
    +language sql
    +begin atomic;
    +select _meta;
    +end;
    +
    +comment on function example_7.combined_upload(json) is '
    +HTTP POST
    +@authorize
    +@upload for excel
    +@param _meta is upload metadata
    +@all_sheets = true
    +@fallback_handler = csv
    +@row_command = select example_7.combined_upload_row($1,$2,$3,$4)';

    When @fallback_handler = csv is set:

    1. The Excel handler (ExcelDataReader) tries to parse the uploaded file first
    2. If it fails (invalid Excel format), the handler automatically delegates to the CSV handler
    3. The same @row_command is used for both - your row function receives text[] either way

    This is simpler than the combined handler approach when you just want to accept either format. No need for separate endpoints - one function handles both:

    sql
    sql
    create or replace function example_7.combined_upload_row(
    +    _index int,
    +    _row text[],
    +    _prev_result int,
    +    _meta json
    +)
    +returns int
    +language plpgsql
    +as $$
    +begin
    +    insert into example_7.combined_uploads (user_id, file_name, sheet_name, row_index, row_data)
    +    values (
    +        (_meta->'claims'->>'user_id')::int,
    +        _meta->>'fileName',
    +        _meta->>'sheet',    -- NULL for CSV, sheet name for Excel
    +        _index,
    +        coalesce(_row, '{}')
    +    );
    +
    +    return coalesce(_prev_result, 0) + 1;
    +end;
    +$$;

    You can check the type field in the metadata ($4) to know which parser handled the file: "excel" or "csv".

    Authentication Integration

    With RowCommandUserClaimsKey configured (default: "claims"), authenticated user information is available in every row's metadata:

    sql
    sql
    -- Access user_id from claims
    +(_meta->'claims'->>'user_id')::int
    +
    +-- Access username
    +_meta->'claims'->>'username'

    This enables per-user import tracking, row-level authorization, and audit trails.

    Comparison with Other Tools

    COPY Command

    PostgreSQL's COPY command is fast for bulk loading, but:

    • Requires superuser or pg_read_server_files role
    • No row-level processing or transformation
    • No progress tracking
    • No user authentication context
    • File must be on server or streamed via psql

    NpgsqlRest's approach gives you per-row control while maintaining transaction safety.

    ETL Tools (Talend, Pentaho, etc.)

    Enterprise ETL tools handle far more than imports, but:

    • Require separate infrastructure
    • Complex visual configuration
    • Overkill for simple imports
    • No automatic API generation

    Python/pandas

    pandas is excellent for data analysis but:

    • Loads entire file into memory
    • Requires application-level transaction handling
    • No automatic TypeScript client generation
    • Manual authentication integration

    NpgsqlRest streams rows to PostgreSQL, using database-native transactions.

    Conclusion: What You Don't Have to Write

    Implementing this the traditional way means everything on the checklist at the top of this post: parsing library, upload endpoint, validation, transactions, error handling, auth integration, hand-written frontend types - and a redeploy whenever the structure changes.

    With NpgsqlRest, you write:

    • One SQL row function (~15-20 lines)
    • One SQL upload function (~8-10 lines)
    • A few annotation lines (~4 lines)

    Parsing, validation, transactions, user claims, and the TypeScript client all come from the handler and the annotations - and a structure change is a CREATE OR REPLACE, not a redeploy.

    The Numbers

    MetricTraditional ApproachNpgsqlRest
    Backend code150-300 lines~30 lines (SQL only)
    Frontend code50-100 lines~15 lines (using generated client)
    Libraries to learn/configure3-50
    Files to create/maintain5-102-3 (SQL files)
    Time to implement1-3 days30 minutes
    Redeployment on structure changeYesNo

    Estimated savings: 80-90% less code, 90%+ time reduction.

    The text[] approach means your row function receives whatever data is in the file. When you know the structure, cast and transform inline. When you don't, store raw and process later. When the structure changes, update the function - no application restart required.

    One caveat: if you control the file format, need no per-row logic, and are loading very large files, plain COPY will still be faster. For everything user-facing, this approach covers it.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/custom-types-multiset-rest-api.html b/blog/custom-types-multiset-rest-api.html new file mode 100644 index 000000000..f05630029 --- /dev/null +++ b/blog/custom-types-multiset-rest-api.html @@ -0,0 +1,584 @@ + + + + + + Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    Human Written

    Custom Types and Multiset for Nested JSON in PostgreSQL REST APIs

    PostgreSQL · Custom Types · Multiset · Nested JSON · January 2026


    There has been some exciting new features added to NpgsqlRest lately, and I wanted to share them personally, so this post will be human-written.

    Those features revolve mainly around usage of custom types in PostgreSQL, including composite types and table types and their support in TypeScript code generation.

    Custom types were supported from the beginning, but now, this support has been greatly expanded.

    So lets do a quick overview of custom types and how they can help us building better APIs.

    Example Setup

    Source Code For This Example: github.com/NpgsqlRest/npgsqlrest-docs/examples/7_csv_excel_uploads

    Databa schema for this example is fairly simple, we have authors and books tables with a one-to-many relationship (one author can have many books). There is also a reviews table that holds reviews for books. Source code (schema ommitted here for brevity) can be found in the example folder:

    sql
    sql
    create table authors (
    +    author_id int not null generated always as identity primary key,
    +    first_name text,
    +    last_name text
    +);
    +
    +create table books (
    +    book_id int not null generated always as identity primary key,
    +    title text not null,
    +    author_id int references authors(author_id)
    +);
    +
    +create table reviews (
    +    review_id int not null generated always as identity primary key,
    +    book_id int references books(book_id),
    +    reviewer_name text,
    +    rating int check (rating between 1 and 5),
    +    review_text text,
    +    created_at timestamp default now()
    +);
    +
    +-- Test data for authors
    +insert into authors (first_name, last_name) values
    +    ('George', 'Orwell'),
    +    ('Jane', 'Austen'),
    +    ('Ernest', 'Hemingway'),
    +    ('Virginia', 'Woolf'),
    +    ('Franz', 'Kafka');
    +
    +-- Test data for books
    +insert into books (title, author_id) values
    +    ('1984', 1),
    +    ('Animal Farm', 1),
    +    ('Pride and Prejudice', 2),
    +    ('Sense and Sensibility', 2),
    +    ('The Old Man and the Sea', 3),
    +    ('A Farewell to Arms', 3),
    +    ('Mrs Dalloway', 4),
    +    ('To the Lighthouse', 4),
    +    ('The Metamorphosis', 5),
    +    ('The Trial', 5);
    +
    +-- Test data for reviews
    +insert into reviews (book_id, reviewer_name, rating, review_text) values
    +    (1, 'Alice Johnson', 5, 'A chilling and prophetic masterpiece.'),
    +    (1, 'Bob Smith', 4, 'Thought-provoking but bleak.'),
    +    (1, 'Carol White', 5, 'Essential reading for everyone.'),
    +    (2, 'David Brown', 5, 'Brilliant political allegory.'),
    +    (2, 'Eve Davis', 4, 'Simple yet profound.'),
    +    (3, 'Frank Miller', 5, 'The perfect romance novel.'),
    +    (3, 'Grace Lee', 5, 'Witty and timeless.'),
    +    (4, 'Henry Wilson', 4, 'Austen at her finest.'),
    +    (5, 'Ivy Chen', 5, 'Beautiful and moving.'),
    +    (5, 'Jack Taylor', 4, 'A short but powerful read.'),
    +    (6, 'Karen Adams', 4, 'Hemingway''s prose shines.'),
    +    (7, 'Leo Garcia', 5, 'Stream of consciousness done right.'),
    +    (8, 'Mia Robinson', 4, 'Poetic and haunting.'),
    +    (9, 'Noah Martinez', 5, 'Surreal and unforgettable.'),
    +    (9, 'Olivia Clark', 3, 'Disturbing but brilliant.'),
    +    (10, 'Paul Wright', 4, 'Kafka at his most absurd.');

    Returning Single Object

    Common usage of custom types is to return a single object with multiple fields.

    sql
    sql
    /*
    +* Get author by ID or all authors if ID is null and return author object from table type
    +*/
    +create function get_author(
    +    _author_id int
    +)
    +returns authors
    +language sql
    +begin atomic;
    +select author_id, first_name, last_name
    +from authors
    +where author_id = _author_id;
    +end;
    +comment on function get_author(int) is 'HTTP GET';

    The entire client code for .http file and TypeScript with generated types is automatically generated when we run NpgsqlRest with development configuration (check the example on GitHub).

    And when we call /api/example-12/get-author?authorId=1 we get the following response:

    json
    json
    {
    +  "authorId": 1,
    +  "firstName": "George",
    +  "lastName": "Orwell"
    +}

    As we can see, the function returns a single authors type object. Of course, that is a table type, we can also define our own composite types if needed. For example:

    sql
    sql
    create type author_info as (
    +    first_name text,
    +    last_name text,
    +    books int
    +);
    +
    +/*
    +* Get author info by ID and return custom type with additional info
    +*/
    +create function get_author_info(
    +    _author_id int
    +)
    +returns author_info
    +language sql
    +begin atomic;
    +select first_name, last_name, count(b.*)
    +from authors a
    +left join books b using (author_id)
    +where author_id = _author_id
    +group by author_id, first_name, last_name;
    +end;
    +comment on function .get_author_info(int) is 'HTTP GET';

    Calling /api/example-12/get-author-info?authorId=1 now returns:

    json
    json
    {
    +  "firstName": "George",
    +  "lastName": "Orwell",
    +  "books": 2
    +}

    This shows how we can use custom composite types to return simple JSON objects with specific fields. These types can also be reused accross multiple functions and even as parameters as we will see next.

    Using Custom Types as Parameters

    For example, we can reuse entire authors table type as a parameter to insert a new author:

    sql
    sql
    /*
    +* Create author by passing author object as parameter to function.
    +* Demonstrates passing custom type as parameter to function. It can be user-defined type or table type.
    +* Type fields are unnested into individual parameters: authorFirstName, authorLastName
    +*/
    +create function create_author(
    +    _author authors
    +)
    +returns authors
    +language sql
    +begin atomic;
    +insert into authors (first_name, last_name)
    +values (_author.first_name,  _author.last_name)
    +returning author_id, first_name, last_name;
    +end;
    +comment on function create_author(authors) is 'HTTP POST';

    We now have generated .http test call (along with TypeScript client code and types) that looks like this:

    http
    http
    // function create_author(
    +//     _author_author_id integer,
    +//     _author_first_name text,
    +//     _author_last_name text
    +// )
    +// returns record
    +//
    +// comment on function create_author is 'HTTP POST
    +POST {host}/api/example-12/create-author
    +content-type: application/json
    +
    +{
    +    "authorAuthorId": 1,
    +    "authorFirstName": "XYZ",
    +    "authorLastName": "IJK"
    +}

    And when we call this endpoint with the above body, we get the following response:

    json
    json
    {
    +  "authorId": 6,
    +  "firstName": "XYZ",
    +  "lastName": "IJK"
    +}

    You may notice that parameter names are prefixed with the type name (authorAuthorId, authorFirstName, etc). This is because custom type fields are expanded and merged with normal parameters.

    The prefix avoids name collisions when multiple custom types are used as parameters in the same function. The separator between the type name and field name can be customized using the CustomTypeParameterSeparator setting in Routine Options.

    If, for example, we would add two more parameters to the above function:

    sql
    sql
    create function create_author(
    +    _param1 text,
    +    _author authors,
    +    _param2 int
    +)
    +...

    The generated parameter names would be: param1, authorAuthorId, authorFirstName, authorLastName and param2.

    This is simple and effective way to share same types across multiple functions easily, either as return types or parameter types.

    Returning Sets of Objects

    The most common usage of custom types is to return sets of objects, for example, returning all books for an author:

    sql
    sql
    /*
    +* Get author by ID or all authors if ID is null and return author object from table type
    +*/
    +create function get_authors(
    +    _author_id int
    +)
    +returns setof authors
    +language sql
    +begin atomic;
    +select author_id, first_name, last_name
    +from authors
    +where
    +    _author_id is null or author_id = _author_id;
    +end;
    +comment on function get_authors(int) is 'HTTP GET';

    Calling /api/example-12/get-authors now returns as expected a list of authors:

    json
    json
    [
    +  {
    +    "authorId": 1,
    +    "firstName": "George",
    +    "lastName": "Orwell"
    +  },
    +  {
    +    "authorId": 2,
    +    "firstName": "Jane",
    +    "lastName": "Austen"
    +  },
    +  ...
    +]

    That is all fine, but in real-world scenarios, we rarely want to return the exact table structure. Those are our entities, meant to be part of the data model, we want to return something more tailored to the API consumer needs.

    For example, we may want to return authors along with, for example, number of books they have written, then we may do something like this:

    sql
    sql
    create function get_authors_with_details(
    +    _author_id int
    +)
    +returns table(
    +    author authors,
    +    books int
    +)
    +language sql
    +begin atomic;
    +select
    +    row(a.author_id, first_name, last_name),
    +    count(b.*)
    +from authors a join books b using (author_id)
    +where
    +    _author_id is null or author_id = _author_id
    +group by
    +    a.author_id, first_name, last_name;
    +end;
    +comment on function get_authors_with_details(int) is 'HTTP GET';

    When we return a table type with multiple columns, NpgsqlRest will merge all columns, including custom types, into a single JSON object per row. The response for /api/example-12/get-authors-with-details now looks like this:

    json
    json
    [
    +  {
    +    "authorId": 1,
    +    "firstName": "George",
    +    "lastName": "Orwell",
    +    "books": 2
    +  },
    +  {
    +    "authorId": 2,
    +    "firstName": "Jane",
    +    "lastName": "Austen",
    +    "books": 2
    +  },
    +  ...
    +]

    As we can see, the author custom type fields are merged into the main object, resulting in a flat structure that is easy to consume.

    But we are not limited to table types, we can comibine multiple custom types, including user defined types as well:

    sql
    sql
    -- Define a custom type to hold book statistics
    +create type books_info as (
    +    books int,
    +    active_reviews int,
    +    avg_rating numeric
    +);
    +
    +create function get_authors_with_details_type(
    +    _author_id int
    +)
    +returns table(
    +    author authors,
    +    books_info books_info
    +)
    +language sql
    +begin atomic;
    +select
    +    row(a.author_id, a.first_name, a.last_name),
    +    (count(distinct b.book_id), count(r.review_id), avg(r.rating))
    +from
    +    authors a
    +    left join books b using (author_id)
    +    left join reviews r on b.book_id = r.book_id
    +where
    +    _author_id is null or author_id = _author_id
    +group by
    +    a.author_id, first_name, last_name;
    +end;
    +comment on function get_authors_with_details_type(int) is '
    +HTTP GET
    +';

    As we can see, we are returning two custom types now, table type authors and custom type books_info. Note on implementation and casting types:

    • We are using usage of row(...) constructor to create a table row that matches the authors type.
    • We are using tuple syntax (count(...), count(...), avg(...)) to create a composite type that matches the books_info type. In this case, no explicit casting is needed as PostgreSQL can infer the type from the return type declaration.

    Resulting JSON for /api/example-12/get-authors-with-details-type now looks like this:

    json
    json
    [
    +  {
    +    "authorId": 1,
    +    "firstName": "George",
    +    "lastName": "Orwell",
    +    "books": 2,
    +    "activeReviews": 5,
    +    "avgRating": 4.6000000000000000
    +  },
    +  {
    +    "authorId": 2,
    +    "firstName": "Jane",
    +    "lastName": "Austen",
    +    "books": 2,
    +    "activeReviews": 3,
    +    "avgRating": 4.6666666666666667
    +  },
    +  ...
    +]

    So, this a powerful way to build tailored API responses using custom types, while keeping the database functions clean and reusable. There is one more piece.

    NEW: Nested JSON Objects

    Starting from NpgsqlRest 3.4.0, we can now nest custom types within the JSON response, instead of merging all fields into a flat structure.

    This is opt-in behavior to keep backward compatibility, and we can enable it either globally for all routines, or per-routine basis.

    • To enable globally, we can set the NestedJsonForCompositeTypes option in the global Routine Options:
    json
    json
    {
    +  "NpgsqlRest": {
    +      "RoutineOptions": {
    +        "NestedJsonForCompositeTypes": true
    +    }
    +  }
    +}
    sql
    sql
    comment on function get_authors_with_details_type(int) is '
    +HTTP GET
    +@nested
    +';

    And when we enable this feauture, the response for /api/example-12/get-authors-with-details now looks like this:

    json
    json
    [
    +  {
    +    "author": {
    +      "authorId": 1,
    +      "firstName": "George",
    +      "lastName": "Orwell"
    +    },
    +    "books": 2
    +  },
    +  {
    +    "author": {
    +      "authorId": 2,
    +      "firstName": "Jane",
    +      "lastName": "Austen"
    +    },
    +    "books": 2
    +  },
    +  ...
    +]

    And for /api/example-12/get-authors-with-details-type:

    json
    json
    [
    +  {
    +    "author": {
    +      "authorId": 1,
    +      "firstName": "George",
    +      "lastName": "Orwell"
    +    },
    +    "booksInfo": {
    +      "books": 2,
    +      "activeReviews": 5,
    +      "avgRating": 4.6000000000000000
    +    }
    +  },
    +  {
    +    "author": {
    +      "authorId": 2,
    +      "firstName": "Jane",
    +      "lastName": "Austen"
    +    },
    +    "booksInfo": {
    +      "books": 2,
    +      "activeReviews": 3,
    +      "avgRating": 4.6666666666666667
    +    }
    +  },
    +  ...
    +]

    As we can see, the custom types are now nested within their own JSON objects, preserving the structure defined in the database function. And the best part is that TypeScript types are also generated accordingly, so we get proper type safety when consuming these APIs.

    But, wait, there is even more!

    NEW: Nested JSON with Multiset

    Starting from NpgsqlRest 3.4.0, we can now return nested JSON arrays (multisets) using custom types.

    But, first, what is the Multiset?

    In scenmarios when we are returning joined datasets with one-to-many relationships, such as authors and their books for example, typical SQL join query would return a flat result set with repeated author information for each book.

    For example, consider the following simple query:

    sql
    sql
    select * from authors join books using (author_id)
    author_idfirst_namelast_namebook_idtitle
    1GeorgeOrwell11984
    1GeorgeOrwell2Animal Farm
    2JaneAusten3Pride and Prejudice
    2JaneAusten4Sense and Sensibility

    As we can see, author information is repeated for each book. Now imageine this query for thousands of authors and books, the result set would be huge and inefficient to transfer over the network.

    And if we would join, for example, author addresses as well to form multiple one-to-many relationships, the result would something called the Cartesian Explosion, which a very bad thing, obviously we don't want that in our API.

    To solve this problems, database vendors came up with the concept of Multiset, which allows us to return hierarchical data structures directly from the database.

    Unfortunately, PostgreSQL does not have built-in support and in general, multisets are not widely supported in SQL databases. This is the support matrix from my AI research:

    DatabaseNative MULTISETWorkaround
    Oracle✅ Full-
    Informix✅ Full-
    PostgreSQLARRAY, JSON_AGG
    EDB PostgresOracle compat mode
    SQL ServerFOR JSON/XML
    MySQLJSON_ARRAYAGG
    TeradataPartialSET/MULTISET tables

    As we can see, we can use array workarounds in PostgreSQL to achieve similar results. And from NpgsqlRest 3.4.0, we can now use this to return nested JSON arrays using custom types. Let's see how that works.

    sql
    sql
    create function get_authors_and_books(
    +    _author_id int
    +)
    +returns table(
    +    author authors,
    +    books books[]
    +)
    +language sql
    +begin atomic;
    +select
    +    row(
    +        a.author_id, first_name, last_name
    +    ),
    +    array_agg(
    +        row(b.book_id, b.title, b.author_id)::books
    +    )
    +from
    +    authors a
    +    left join books b using (author_id)
    +where
    +    _author_id is null or author_id = _author_id
    +group by
    +    a.author_id, first_name, last_name;
    +end;
    +comment on function get_authors_and_books(int) is '
    +HTTP GET
    +@nested
    +';

    This function returns authors along with an array of their books. Note the usage of array_agg(...) to aggregate books into an array.

    When we call /api/example-12/get-authors-and-books, we get the following nested JSON response:

    json
    json
    [
    +  {
    +    "author": {
    +      "authorId": 1,
    +      "firstName": "George",
    +      "lastName": "Orwell"
    +    },
    +    "books": [
    +      {
    +        "bookId": 1,
    +        "title": "1984",
    +        "authorId": 1
    +      },
    +      {
    +        "bookId": 2,
    +        "title": "Animal Farm",
    +        "authorId": 1
    +      }
    +    ]
    +  },
    +  {
    +    "author": {
    +      "authorId": 2,
    +      "firstName": "Jane",
    +      "lastName": "Austen"
    +    },
    +    "books": [
    +      {
    +        "bookId": 3,
    +        "title": "Pride and Prejudice",
    +        "authorId": 2
    +      },
    +      {
    +        "bookId": 4,
    +        "title": "Sense and Sensibility",
    +        "authorId": 2
    +      }
    +    ]
    +  },
    +  ...
    +]

    So we now have a proper hierarchical JSON structure with authors and their books nested within just by returning arrays of custom types from our database function. As always, these can be any table or user defined type and TypeScript types are generated accordingly for type-safe consumption.

    And this is even simpler then, for example, it would be in Oracle, where we would need to define MULTISET types explicitly. According to AI research, OraOracle/Informix/SQL Standard syntax would look something like this:

    sql
    sql
    SELECT author_id, author_name,
    +       MULTISET(SELECT book_id, title FROM books 
    +                WHERE books.author_id = authors.id) AS books
    +FROM authors;

    This may be more declarative, but it certainly doesn't have the automatic REST API and TypeScript generation like NpgsqlRest provides.

    Limitations

    There are some limitations to be aware of when using nested JSON with multiset:

    1. Nesting is limited to one level deep per function.

    For example, if we wanted to return authors, together with their books and with reviews for each book, we would ran into limitations:

    First of all, PostgreSQL does not support aggregation in aggregation, so we cannot do array_agg(...) inside another array_agg(...) in a single query:

    code
    ERROR:
    +aggregate function calls cannot be nested
    +LINE 11: array_agg(

    This limitation can be worked around by using subqueries, temp tables or CTEs, but it gets complicated quickly. Example is in source code on GitHub (see link above).

    But even if we could do that, prior to NpgsqlRest 3.4.4, only one level of nesting was supported per function. Any additional levels of nesting were rendered as PostgreSQL tuple strings in JSON.

    Deep Nesting Support (v3.4.4+)

    Since NpgsqlRest 3.4.4, the ResolveNestedCompositeTypes option (enabled by default) resolves nested composite types to any depth, serializing inner composites as proper JSON objects/arrays instead of PostgreSQL tuple strings. The limitation below only applies when ResolveNestedCompositeTypes is set to false.

    For example, with ResolveNestedCompositeTypes: false, a single object in the JSON array would look like this:

    json
    json
    [
    +  {
    +    "author": {
    +      "authorId": 1,
    +      "firstName": "George",
    +      "lastName": "Orwell"
    +    },
    +    "books": [
    +      {
    +        "bookId": 1,
    +        "title": "1984",
    +        "reviews": [
    +          "(1,1,\"Alice Johnson\",5,\"A chilling and prophetic masterpiece.\",\"2026-01-16 08:45:55.560972\")",
    +          "(2,1,\"Bob Smith\",4,\"Thought-provoking but bleak.\",\"2026-01-16 08:45:55.560972\")",
    +          "(3,1,\"Carol White\",5,\"Essential reading for everyone.\",\"2026-01-16 08:45:55.560972\")"
    +        ]
    +      },
    +      {
    +        "bookId": 2,
    +        "title": "Animal Farm",
    +        "reviews": [
    +          "(4,2,\"David Brown\",5,\"Brilliant political allegory.\",\"2026-01-16 08:45:55.560972\")",
    +          "(5,2,\"Eve Davis\",4,\"Simple yet profound.\",\"2026-01-16 08:45:55.560972\")"
    +        ]
    +      }
    +    ]
    +  },
    +  ...
    +]

    With the default ResolveNestedCompositeTypes: true, the reviews array is properly serialized as JSON objects:

    json
    json
    [
    +  {
    +    "author": {
    +      "authorId": 1,
    +      "firstName": "George",
    +      "lastName": "Orwell"
    +    },
    +    "books": [
    +      {
    +        "bookId": 1,
    +        "title": "1984",
    +        "reviews": [
    +          {"reviewId": 1, "bookId": 1, "reviewerName": "Alice Johnson", "rating": 5, "reviewText": "A chilling and prophetic masterpiece.", "createdAt": "2026-01-16T08:45:55.560972"},
    +          {"reviewId": 2, "bookId": 1, "reviewerName": "Bob Smith", "rating": 4, "reviewText": "Thought-provoking but bleak.", "createdAt": "2026-01-16T08:45:55.560972"},
    +          {"reviewId": 3, "bookId": 1, "reviewerName": "Carol White", "rating": 5, "reviewText": "Essential reading for everyone.", "createdAt": "2026-01-16T08:45:55.560972"}
    +        ]
    +      },
    +      {
    +        "bookId": 2,
    +        "title": "Animal Farm",
    +        "reviews": [
    +          {"reviewId": 4, "bookId": 2, "reviewerName": "David Brown", "rating": 5, "reviewText": "Brilliant political allegory.", "createdAt": "2026-01-16T08:45:55.560972"},
    +          {"reviewId": 5, "bookId": 2, "reviewerName": "Eve Davis", "rating": 4, "reviewText": "Simple yet profound.", "createdAt": "2026-01-16T08:45:55.560972"}
    +        ]
    +      }
    +    ]
    +  },
    +  ...
    +]
    1. Working memory pressure on the database server.

    Aggegations in PostgreSQL are accumulated in working memory and by default PostgreSQL allocates 4MB per aggregation operation (see the work_mem parameter). Those allocations are per operation and even a single query can have multiple aggregations, let alone multiple concurrent queries. When memory limit is exceeded, PostgreSQL spills to disk to complete the operation, which can severely impact performance.

    This is a consideriation when using aggregations excessively, especially with large datasets on a busy server. Always monitor memory usage and tune work_mem accordingly.

    Conclusion And Workaround

    Using custom types and multiset patterns in PostgreSQL with NpgsqlRest allows us to build expressive and efficient REST APIs with hierarchical JSON responses but as we can see, there are some limitations to be aware of.

    There is a good workaround taht I use in my projects. More like different pattern. Simply, run multiple queries, preferably in parallel, instead of a single complex query with multiple levels of nesting. This pattern gives us:

    • Better performance by avoiding complex aggregations and joins.
    • No working memory pressure on the database server.
    • Two simlpler funcntions instead of one complex query.
    • We still get full type safety with generated TypeScript types for both functions.

    Speaking of which, that might be just the best part of using NpgsqlRest with PostgreSQL. The amount of code that I dont have to write myself is incredible. You can see it in the example source code on GitHub (see link above), but just to illustrate, here is the copy and paste here just for the interfaces part, the entire file is much longer:

    typescript
    typescript
    interface IAuthor {
    +    authorId: number | null;
    +    firstName: string | null;
    +    lastName: string | null;
    +}
    +
    +interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    authorId: number | null;
    +}
    +
    +interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    reviews: string[] | null;
    +}
    +
    +interface IBooksInfo {
    +    books: number | null;
    +    activeReviews: number | null;
    +    avgRating: number | null;
    +}
    +
    +interface ICreateAuthorRequest {
    +    authorAuthorId?: number | null;
    +    authorFirstName?: string | null;
    +    authorLastName?: string | null;
    +}
    +
    +interface ICreateAuthorResponse {
    +    authorId: number | null;
    +    firstName: string | null;
    +    lastName: string | null;
    +}
    +
    +interface IGetAuthorRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorResponse {
    +    authorId: number | null;
    +    firstName: string | null;
    +    lastName: string | null;
    +}
    +
    +interface IGetAuthorInfoRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorInfoResponse {
    +    firstName: string | null;
    +    lastName: string | null;
    +    books: number | null;
    +}
    +
    +interface IGetAuthorsRequest {
    +    authorAuthorId?: number | null;
    +    authorFirstName?: string | null;
    +    authorLastName?: string | null;
    +}
    +
    +interface IGetAuthorsResponse {
    +    authorId: number | null;
    +    firstName: string | null;
    +    lastName: string | null;
    +}
    +
    +interface IGetAuthorsAndBooksRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsAndBooksResponse {
    +    author: IAuthor | null;
    +    books: IBooks[] | null;
    +}
    +
    +interface IGetAuthorsAndBooksAndReviewsRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsAndBooksAndReviewsResponse {
    +    author: IAuthor | null;
    +    books: IBooks[] | null;
    +}
    +
    +interface IGetAuthorsWithDetailsRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsResponse {
    +    author: IAuthor | null;
    +    books: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsNestedRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsNestedResponse {
    +    author: IAuthor | null;
    +    books: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsTypeRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsTypeResponse {
    +    author: IAuthor | null;
    +    booksInfo: IBooksInfo | null;
    +}
    +
    +interface IGetAuthorsWithDetailsTypeNestedRequest {
    +    authorId: number | null;
    +}
    +
    +interface IGetAuthorsWithDetailsTypeNestedResponse {
    +    author: IAuthor | null;
    +    booksInfo: IBooksInfo | null;
    +}

    That is a lot of code that I did not have to write myself. Plus, there is also the .http test file with all the calls, which is also generated automatically.

    I built this project to save me time and effort, and it does exactly that. I hope you find it useful as well. If you do, give it a star on GitHub!

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/database-level-security-postgresql-authentication.html b/blog/database-level-security-postgresql-authentication.html new file mode 100644 index 000000000..1293d9c0e --- /dev/null +++ b/blog/database-level-security-postgresql-authentication.html @@ -0,0 +1,362 @@ + + + + + + Database-Level Security: Building Secure Authentication with PostgreSQL | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Database-Level Security: Building Secure Authentication with PostgreSQL and NpgsqlRest

    January 2026 · SecurityPostgreSQLAuthenticationNpgsqlRest

    Most web applications treat databases as dumb storage - a place to persist data that the application logic protects. This is backwards. Your database is the last line of defense, and it should be the strongest.

    This post demonstrates how to build an authentication system that uses PostgreSQL as the security boundary: the Principle of Least Privilege enforced at the database level, plus a password hashing scheme that sidesteps bcrypt's 72-byte limit.

    Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/3_security_and_auth

    The Principle of Least Privilege (PoLP)

    The Principle of Least Privilege states that any user, program, or process should have only the minimum privileges necessary to perform its function. In traditional web applications, the database connection often has full access to all tables - a disaster waiting to happen.

    With NpgsqlRest, we implement PoLP at the database level:

    mermaid
    flowchart TB
    +    APP["Application
    +    (app_user role)"]
    +
    +    APP --> PUB
    +
    +    subgraph PUB["example_3_public schema — ONLY schema app_user can access"]
    +        L["login()
    +        SECURITY DEFINER"]
    +        LO["logout()
    +        SECURITY DEFINER"]
    +        W["who_am_i()"]
    +    end
    +
    +    L & LO --> PROT
    +
    +    subgraph PROT["example_3 schema — app_user has NO ACCESS here"]
    +        UT["users table
    +        (passwords)"]
    +        HP["hash_password()
    +        verify_password()"]
    +    end

    The whole design rests on one rule: the application role cannot access tables directly. It can only call functions in a public schema, and those functions use SECURITY DEFINER to access protected data.

    Schema Architecture

    The Protected Schema

    The example_3 schema contains sensitive data and internal functions. This is defined in V1__example_3_schema.sql - a versioned migration. The V1__ prefix means this is version 1, and versioned migrations only run once (unlike repeatable R__ migrations that run on every change).

    sql
    sql
    -- V1__example_3_schema.sql (versioned migration - runs only once)
    +
    +-- Create the protected schema
    +drop schema if exists example_3 cascade;
    +create schema example_3;
    +
    +-- Enable pgcrypto for password hashing
    +create extension if not exists pgcrypto with schema example_3;
    +
    +-- Users table with password hashes
    +create table example_3.users (
    +    user_id int primary key generated always as identity,
    +    username text not null,
    +    email text not null,
    +    password_hash text[] not null  -- Array of hashes (explained below)
    +);

    The Public API Schema

    The same versioned migration also creates the public schema for API endpoints:

    sql
    sql
    -- Create public schema for API endpoints
    +drop schema if exists example_3_public cascade;
    +create schema example_3_public;

    The Restricted Application Role

    Here's where PoLP becomes concrete. We create a role with minimal permissions:

    sql
    sql
    -- Create application role with minimal privileges
    +create role ${APP_USER} with
    +    login
    +    nosuperuser
    +    nocreatedb
    +    nocreaterole
    +    noinherit
    +    noreplication
    +    connection limit -1
    +    password '${APP_PASSWORD}';
    +
    +-- Grant ONLY usage on the public schema
    +-- This is the ONLY grant this role needs
    +grant usage on schema example_3_public to ${APP_USER};

    The ${APP_USER} and ${APP_PASSWORD} placeholders are environment variables that get replaced when running migrations. The pgmigrations tool supports this ${VAR} syntax for variable substitution.

    Note on credentials: In this example, APP_USER and APP_PASSWORD are stored in the .env file alongside the superuser credentials used to run migrations. This is for demonstration purposes only. In production, these credentials should be stored separately - the superuser credentials belong to whoever administers the database and runs migrations (DevOps, DBA, etc.), while the application credentials should be managed through your secrets management system.

    Note what's not granted:

    • No access to example_3 schema
    • No access to any tables
    • No ability to create objects
    • No superuser privileges

    The application can only execute functions in example_3_public. Period.

    Bypassing Bcrypt's 72-Byte Limit

    Bcrypt is the gold standard for password hashing, but it has a critical limitation: it silently ignores everything after byte 72. This means these two passwords hash identically:

    code
    "a]2@[Z]f)!BqC6:g%/$wrz7-Cz<B9!@z]j{9,X]3xaM'uqQW*l7:zK"s:-xt*2Pd$e7Emore_stuff_here"
    +"a]2@[Z]f)!BqC6:g%/$wrz7-Cz<B9!@z]j{9,X]3xaM'uqQW*l7:zK"s:-xt*2Pd$e7E"

    An attacker who discovers this limit could exploit it. Our solution: segment the password and hash each segment separately.

    The Hash Function

    sql
    sql
    -- R__1_example_3_hash_password.sql
    +
    +--
    +-- Bcrypt has a 72-byte input limit - any characters beyond that are silently ignored.
    +-- This function overcomes that limitation by splitting passwords into 72-char segments,
    +-- hashing each segment separately, and returning an array of hashes.
    +-- This allows secure hashing of passwords with unlimited length.
    +--
    +create or replace function example_3.hash_password(
    +    _input text
    +)
    +returns text[]
    +parallel safe
    +language plpgsql
    +as
    +$$
    +declare
    +    _segment text;
    +    _result text[] = '{}';
    +    _alg text = 'bf';  -- Blowfish (bcrypt)
    +    _i int;
    +    _max_len constant int = 72;
    +begin
    +    for _i in 0..ceil(length(_input) / _max_len) + 1 loop
    +        _segment = substring(_input from _i * _max_len + 1 for _max_len);
    +        if length(_segment) > 0 then
    +            _result = array_append(_result, example_3.crypt(_segment, example_3.gen_salt(_alg)));
    +        end if;
    +    end loop;
    +
    +    return _result;
    +end;
    +$$;

    The Verify Function

    sql
    sql
    -- R__2_example_3_verify_password.sql
    +
    +create or replace function example_3.verify_password(
    +    _input text,
    +    _array text[]
    +)
    +returns boolean
    +language plpgsql
    +as
    +$$
    +declare
    +    _segment text;
    +    _max_len constant int = 72;
    +    _expected_segments int;
    +    _segment_count int = 0;
    +begin
    +    for _i in 0..ceil(length(_input) / _max_len) + 1 loop
    +        _segment = substring(_input from _i * _max_len + 1 for _max_len);
    +        if length(_segment) > 0 then
    +            _segment_count = _segment_count + 1;
    +            if example_3.crypt(_segment, _array[_i+1]) <> _array[_i+1] then
    +                return false;
    +            end if;
    +        end if;
    +    end loop;
    +
    +    -- Ensure the hash array has exactly the expected number of segments
    +    if coalesce(array_length(_array, 1), 0) <> _segment_count then
    +        return false;
    +    end if;
    +
    +    return true;
    +end;
    +$$;

    Testing the Password Functions

    The tests run on every migration, ensuring the functions work correctly:

    sql
    sql
    do
    +$$
    +declare
    +    _hash text[];
    +    _long_password text;
    +begin
    +    -- Test 1: Simple password hash and verify
    +    _hash = example_3.hash_password('mypassword123');
    +    assert example_3.verify_password('mypassword123', _hash),
    +        'Test 1 failed: correct password should verify';
    +
    +    -- Test 2: Wrong password should not verify
    +    assert not example_3.verify_password('wrongpassword', _hash),
    +        'Test 2 failed: wrong password should not verify';
    +
    +    -- Test 3: Empty string vs actual password
    +    assert not example_3.verify_password('', _hash),
    +        'Test 3 failed: empty string should not verify against non-empty hash';
    +
    +    -- Test 4: Empty password hash and verify
    +    _hash = example_3.hash_password('');
    +    assert example_3.verify_password('', _hash),
    +        'Test 4 failed: empty password should verify against its own hash';
    +
    +    -- Test 5: Long password (> 72 chars to test segmentation)
    +    _long_password = repeat('a', 100);
    +    _hash = example_3.hash_password(_long_password);
    +    assert array_length(_hash, 1) > 1,
    +        'Test 5a failed: long password should produce multiple hash segments';
    +    assert example_3.verify_password(_long_password, _hash),
    +        'Test 5b failed: long password should verify correctly';
    +
    +    -- Test 6: Very long password (> 144 chars for 3 segments)
    +    _long_password = repeat('x', 200);
    +    _hash = example_3.hash_password(_long_password);
    +    assert array_length(_hash, 1) >= 3,
    +        'Test 6a failed: very long password should produce 3+ hash segments';
    +    assert example_3.verify_password(_long_password, _hash),
    +        'Test 6b failed: very long password should verify correctly';
    +    assert not example_3.verify_password(repeat('y', 200), _hash),
    +        'Test 6c failed: different long password should not verify';
    +
    +    -- Test 7: Special characters
    +    _hash = example_3.hash_password('p@!?w0rd!#%&*()');
    +    assert example_3.verify_password('p@!?w0rd!#%&*()', _hash),
    +        'Test 7 failed: special characters should hash and verify correctly';
    +
    +    -- Test 8: Unicode characters
    +    _hash = example_3.hash_password('пароль密码🔐');
    +    assert example_3.verify_password('пароль密码🔐', _hash),
    +        'Test 8 failed: unicode characters should hash and verify correctly';
    +
    +    raise notice 'All password hash/verify tests passed!';
    +end;
    +$$;

    The Authentication Functions

    Understanding SECURITY DEFINER

    First, what SECURITY DEFINER actually does:

    • Default behavior (SECURITY INVOKER): Functions run with the privileges of the user calling them
    • SECURITY DEFINER: Functions run with the privileges of the user who created them

    Since migrations are run by a superuser (or at least a privileged role), functions marked SECURITY DEFINER will execute with those elevated privileges - even when called by the restricted application role.

    This is the key mechanism that makes PoLP work:

    1. The example_3_public schema contains only the functions that the application needs to call - nothing else
    2. The application role has USAGE on example_3_public schema, so it can discover and call those functions
    3. Those functions are SECURITY DEFINER, so they run as the superuser who created them
    4. Inside the function, we can access example_3.users table - something the application role cannot do directly

    Protecting Against Search Path Attacks

    SECURITY DEFINER functions have a well-known vulnerability: search path injection. When a function calls other functions or operators without schema-qualifying them, PostgreSQL uses the search_path to resolve them. An attacker can manipulate this path to substitute malicious functions that execute with elevated privileges.

    For example, if a SECURITY DEFINER function uses the + operator without qualification, an attacker could:

    1. Create a schema they control
    2. Define a malicious + operator in that schema
    3. Manipulate search_path to prioritize their schema
    4. When the function runs as superuser, the malicious operator executes with superuser privileges

    The fix is simple: always set search_path explicitly on SECURITY DEFINER functions:

    sql
    sql
    set search_path = pg_catalog, pg_temp

    This ensures only trusted system catalogs are searched. Even if a function doesn't call other functions, it's good practice to include this on all SECURITY DEFINER functions as defense in depth.

    For more details on this vulnerability, see Abusing SECURITY DEFINER functions and CVE-2018-1058.

    Login Function

    sql
    sql
    -- R__example_3_public_login.sql
    +
    +create or replace function example_3_public.login(
    +    _username text,
    +    _password text
    +)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    email text
    +)
    +language sql
    +set search_path = pg_catalog, pg_temp  -- Protect against search path attacks
    +security definer  -- Runs as migration user (superuser), not app_user
    +begin atomic;
    +select
    +    'cookies' as scheme,  -- Tells NpgsqlRest to use cookie authentication
    +    user_id,
    +    username,
    +    email
    +from example_3.users  -- app_user can't access this table directly!
    +where
    +    username = _username
    +    and example_3.verify_password(_password, password_hash);
    +end;
    +
    +comment on function example_3_public.login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous';  -- Allow unauthenticated access to login

    The login annotation marks this as an authentication endpoint. Here's how it works:

    1. The function must return a named record (table) - returning void, simple values, or an empty result triggers 401 Unauthorized
    2. The scheme column specifies which configured authentication scheme to use (in this case 'cookies', but it could be any scheme you've configured - Bearer tokens, JWT, etc.)
    3. All other columns (user_id, username, email) become security claims stored in the authentication cookie
    4. The anonymous annotation allows unauthenticated access - otherwise users couldn't log in!

    On successful login, NpgsqlRest signs in the user with the specified scheme and the returned claims become available in subsequent requests.

    Logout Function

    sql
    sql
    -- R__example_3_public_logout.sql
    +
    +create or replace function example_3_public.logout()
    +returns text
    +set search_path = pg_catalog, pg_temp
    +language sql
    +security definer
    +begin atomic;
    +select 'cookies';  -- Return the scheme to clear
    +end;
    +
    +comment on function example_3_public.logout() is
    +'HTTP POST
    +@logout
    +@authorize';  -- Requires authentication

    The logout annotation tells NpgsqlRest to clear the authentication cookie.

    Who Am I Function

    This function demonstrates user parameters - claims from the authentication cookie are automatically injected:

    sql
    sql
    -- R__example_3_public_who_am_i.sql
    +
    +create or replace function example_3_public.who_am_i(
    +    _user_id text = null,
    +    _username text = null,
    +    _email text = null
    +)
    +returns table (
    +    user_id text,
    +    username text,
    +    email text
    +)
    +set search_path = pg_catalog, pg_temp  -- Good practice even without SECURITY DEFINER
    +language sql
    +begin atomic;
    +select
    +    _user_id,
    +    _username,
    +    _email;
    +end;
    +
    +comment on function example_3_public.who_am_i(text, text, text) is '
    +HTTP GET
    +@authorize';

    The parameters _user_id, _username, and _email are filled in automatically by NpgsqlRest from the authenticated user's claims - the client never sends them.

    NpgsqlRest Configuration

    The configuration ties everything together:

    json
    json
    {
    +  "ApplicationName": "3_security_and_auth",
    +
    +  "ConnectionStrings": {
    +    // Use the restricted application role
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={APP_USER};Password={APP_PASSWORD}"
    +  },
    +
    +  "StaticFiles": {
    +    "RootPath": "./3_security_and_auth/public"
    +  },
    +
    +  // Cookie authentication settings
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieValidDays": 1,
    +    "CookieName": "example_3_auth"
    +  },
    +
    +  "NpgsqlRest": {
    +    // ONLY expose the public schema
    +    "IncludeSchemas": [ "example_3_public" ],
    +    "RequiresAuthorization": true,
    +
    +    "AuthenticationOptions": {
    +      "DefaultAuthenticationType": "example_3",
    +      // Map claims to function parameters
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_username": "username",
    +        "_email": "email"
    +      }
    +    },
    +
    +    "ClientCodeGen": {
    +      "FilePath": "./3_security_and_auth/src/{0}Api.ts"
    +    }
    +  }
    +}

    Key configuration points:

    1. Connection uses the restricted role - The application connects as APP_USER, not the superuser
    2. Only public schema is exposed - IncludeSchemas: ["example_3_public"] ensures internal functions are never exposed
    3. Cookie authentication enabled - CookieAuth: true with scheme matching what login returns
    4. User parameters mapped - Claims from cookies are automatically injected into function parameters

    The Demo Application

    The example includes a simple web interface demonstrating the authentication flow:

    html
    html
    <!-- public/index.html -->
    +<div id="login-form">
    +    <h2>Login</h2>
    +    <input type="text" id="username" placeholder="Username" value="alice" />
    +    <input type="password" id="password" placeholder="Password" value="password123" />
    +    <button id="login-btn">Login</button>
    +</div>
    +
    +<div id="actions">
    +    <button id="whoami-btn">Who Am I?</button>
    +    <button id="logout-btn">Logout</button>
    +</div>

    The TypeScript client is automatically generated:

    typescript
    typescript
    // Auto-generated by NpgsqlRest
    +
    +interface ILoginRequest {
    +    username: string | null;
    +    password: string | null;
    +}
    +
    +interface IWhoAmIResponse {
    +    userId: string | null;
    +    username: string | null;
    +    email: string | null;
    +}
    +
    +export async function login(request: ILoginRequest) : Promise<{
    +    status: number,
    +    response: string,
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/api/example-3-public/login", {
    +        method: "POST",
    +        body: JSON.stringify(request)
    +    });
    +    // ...
    +}
    +
    +export async function whoAmI(request: IWhoAmIRequest) : Promise<{
    +    status: number,
    +    response: IWhoAmIResponse[],
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    // ...
    +}

    Test users are created during migration:

    sql
    sql
    -- R__3_example_3_test_data.sql
    +insert into example_3.users (username, email, password_hash) values
    +('alice', 'alice@example.com', example_3.hash_password('password123')),
    +('bob', 'bob@example.com', example_3.hash_password('password456'));

    Why This Architecture is More Secure

    1. Defense in Depth

    Even if an attacker compromises the application, they cannot:

    • Read the users table directly
    • Access password hashes
    • Call internal functions
    • Modify data outside of what the API functions allow

    The database itself enforces security boundaries.

    2. SQL Injection Becomes Less Dangerous

    Traditional SQL injection exploits often rely on the application having broad database permissions. With PoLP:

    sql
    sql
    -- Attacker tries: ' OR '1'='1
    +-- Even if injection succeeds, app_user can only call functions in example_3_public
    +-- No direct table access means no data exfiltration

    3. No Secrets in Application Code

    Password hashing and verification happen entirely in PostgreSQL. The application never sees raw passwords or hashes - it just passes them to functions.

    4. Auditable Security Boundary

    The security model is visible in the database schema:

    • Which role has what permissions
    • Which functions use SECURITY DEFINER
    • What the public API surface is

    This makes security audits straightforward.

    5. Bcrypt Limit Protection

    The segmented password hashing ensures long passwords remain secure. An attacker who knows about bcrypt's 72-byte limit gains no advantage.

    Comparison with Traditional Approaches

    Traditional StackThis Approach
    App has full DB accessApp has minimal permissions
    ORM manages all tablesApp can only call functions
    Password hashing in app codePassword hashing in PostgreSQL
    SQL injection = full compromiseSQL injection = limited scope
    Security logic scatteredSecurity logic centralized in DB
    Audit requires code reviewAudit visible in schema

    Conclusion

    Security should be built into the architecture, not bolted on. A restricted application role, a separate public schema, SECURITY DEFINER functions with a pinned search_path, and segmented bcrypt hashing inside the database add up to an application that stays secure by default - even when the layer above it is compromised.

    Combined with NpgsqlRest's end-to-end type safety and superior performance, this approach delivers applications that are not only faster and more maintainable, but fundamentally more secure.

    The database is your most trusted component - treat it that way.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/end-to-end-static-type-checking-postgresql-typescript.html b/blog/end-to-end-static-type-checking-postgresql-typescript.html new file mode 100644 index 000000000..eb8f3098e --- /dev/null +++ b/blog/end-to-end-static-type-checking-postgresql-typescript.html @@ -0,0 +1,533 @@ + + + + + + End-to-End Static Type Checking: PostgreSQL to TypeScript | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    End-to-End Static Type Checking: PostgreSQL to TypeScript

    TypeScript · Type Safety · Developer Experience · December 2025


    With NpgsqlRest's automatic type generation, a database schema change breaks your TypeScript build before it ever reaches production. This post walks through a complete example of end-to-end static type checking, from PostgreSQL functions to TypeScript client code.

    The Problem with Traditional API Development

    In typical REST API workflows, there's a dangerous gap between your database schema and your client code:

    1. You change a column name in PostgreSQL
    2. Your API continues to work (returning the new column name)
    3. Your frontend code silently breaks at runtime
    4. Users discover the bug, not your build system

    This gap exists because types are defined in multiple places - database schemas, API layer, and client code - with no automatic synchronization.

    Why PostgreSQL Functions?

    PostgreSQL functions let you encapsulate business logic in the database. When you keep logic there:

    • Centralized Logic: All application instances interact with the data consistently - especially valuable when multiple applications share the same database.
    • Improved Performance: By pushing logic down to the database layer, you reduce the need for data to travel back and forth between the database and application.
    • Atomic Operations: Functions can be executed as a single transaction, ensuring operations are atomic and consistent - particularly useful when making changes that involve multiple tables.
    • Ease of Maintenance: Fixing a bug or optimizing a query can be done directly in the database, with immediate effect across all instances - no application redeploy.

    But perhaps the most underappreciated benefit is static type checking. PostgreSQL functions have explicit return types that the database enforces. This creates a natural contract that can be propagated all the way to your client code.

    The Solution: Single Source of Truth

    NpgsqlRest solves this by making PostgreSQL the single source of truth:

    code
    PostgreSQL Function → NpgsqlRest → Generated TypeScript API Client → Your Application

    Any change to the PostgreSQL function signature automatically regenerates the TypeScript interfaces. If your application code references a property that no longer exists, the TypeScript compiler fails the build.

    SQL Files Also Generate TypeScript

    Starting with v3.12.0, SQL file endpoints also generate TypeScript clients automatically. However, SQL files don't provide the same level of database-enforced type checking that functions do — there's no returns table(...) contract that PostgreSQL validates at creation time. For maximum type safety, PostgreSQL functions remain the strongest approach and are the focus of this post. SQL files are better suited for simpler queries where the convenience of skipping CREATE FUNCTION outweighs the stricter type checking.

    Project Structure

    Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/2_static_type_checking

    The example follows this structure:

    code
    2_static_type_checking/
    +├── sql/
    +│   ├── R__example_2_tables.sql       # Schema and tables (repeatable)
    +│   ├── A__example_2_get_users.sql    # get_users() function (always run)
    +│   └── A__example_2_get_posts.sql    # get_posts() function (always run)
    +├── src/
    +│   ├── example2Api.ts                # Auto-generated by NpgsqlRest
    +│   └── app.ts                        # Hand-written application code
    +├── public/
    +│   └── index.html                    # HTML entry point
    +└── config.json                       # NpgsqlRest configuration

    The file naming convention carries the weight:

    • R__ prefix: Repeatable migrations - recreate schema and tables
    • A__ prefix: Always-run migrations - recreate functions on every build

    The Database Schema

    The schema creates two simple tables: users and posts.

    sql
    sql
    -- R__example_2_tables.sql
    +
    +-- recreate entire schema example_2
    +drop schema if exists example_2 cascade;
    +create schema example_2;
    +
    +create table example_2.users (
    +    user_id int primary key generated always as identity,
    +    username text not null,
    +    email text not null,
    +    active boolean not null default true
    +);
    +
    +insert into example_2.users (username, email, active) values
    +('alice', 'alice@example.com', true),
    +('bob', 'bob@example.com', true),
    +('charlie', 'charlie@example.com', true);
    +
    +create table example_2.posts (
    +    post_id int primary key generated always as identity,
    +    user_id int references example_2.users(user_id) deferrable,
    +    content text not null,
    +    created_at timestamp not null default now()
    +);
    +
    +insert into example_2.posts (user_id, content, created_at) values
    +(1, 'Hello world! This is my first post.', '2024-01-15 10:30:00'),
    +(1, 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'),
    +(2, 'Just joined this platform.', '2024-01-17 09:00:00'),
    +(3, 'Anyone here interested in databases?', '2024-01-18 11:45:00'),
    +(2, 'Working on a new project today.', '2024-01-19 16:30:00');

    Functions That Define the API Contract

    The function definitions are where the contract lives. They are recreated on every build, which means:

    1. The function signature is always authoritative
    2. NpgsqlRest regenerates TypeScript types from the current function definition
    3. Any mismatch between the function and client code is caught at compile time

    get_users()

    sql
    sql
    -- A__example_2_get_users.sql
    +
    +create or replace function example_2.get_users()
    +returns setof example_2.users
    +language sql
    +begin atomic;
    +select user_id, username, email, active from example_2.users;
    +end;
    +
    +comment on function example_2.get_users() is 'HTTP GET';
    +
    +do
    +$$
    +begin
    +    assert (
    +        select count(*) = 3
    +        from example_2.get_users()
    +        where (user_id, username, email, active) in (
    +            (1, 'alice', 'alice@example.com', true),
    +            (2, 'bob', 'bob@example.com', true),
    +            (3, 'charlie', 'charlie@example.com', true)
    +        )
    +    ), 'get_users() does not contain expected data';
    +end;
    +$$;

    Notice:

    • returns setof example_2.users: The return type is the entire users table structure
    • comment on function ... is 'HTTP GET': This tells NpgsqlRest to expose it as a GET endpoint
    • The assert block: Built-in data validation test that runs on every migration

    get_posts()

    sql
    sql
    -- A__example_2_get_posts.sql
    +
    +create or replace function example_2.get_posts()
    +returns table(
    +    username text,
    +    content text,
    +    created_at timestamp
    +)
    +language sql
    +begin atomic;
    +select u.username, p.content, p.created_at
    +from example_2.posts p join example_2.users u using(user_id)
    +where u.active = true;
    +end;
    +
    +comment on function example_2.get_posts() is 'HTTP GET';
    +
    +do
    +$$
    +begin
    +    assert (
    +        select count(*) = 5
    +        from example_2.get_posts()
    +        where (username, content, created_at) in (
    +            ('alice', 'Hello world! This is my first post.', '2024-01-15 10:30:00'::timestamp),
    +            ('alice', 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'::timestamp),
    +            ('bob', 'Just joined this platform.', '2024-01-17 09:00:00'::timestamp),
    +            ('charlie', 'Anyone here interested in databases?', '2024-01-18 11:45:00'::timestamp),
    +            ('bob', 'Working on a new project today.', '2024-01-19 16:30:00'::timestamp)
    +        )
    +    ), 'get_posts() does not contain expected data';
    +end;
    +$$;

    This function uses an explicit returns table(...) definition, specifying exactly which columns are returned. The function joins users and posts, filtering only active users.

    Static Type Checking at the SQL Level

    Before we even get to TypeScript, PostgreSQL itself performs static type checking on function definitions. That's the first line of defense.

    How PostgreSQL Enforces Return Types

    When you define a function with returns table(...) or returns setof, PostgreSQL validates that your function body actually returns the declared types. If there's a mismatch, the function creation fails.

    Consider this scenario - you have a function returning username text:

    sql
    sql
    create or replace function example_2.get_posts()
    +returns table(
    +    username text,
    +    content text,
    +    created_at timestamp
    +)
    +language sql
    +begin atomic;
    +select u.username, p.content, p.created_at
    +from example_2.posts p join example_2.users u using(user_id)
    +where u.active = true;
    +end;

    Now imagine someone changes the username column in the users table to jsonb:

    sql
    sql
    alter table example_2.users alter column username type jsonb using to_jsonb(username);

    The next time you run your migration (which recreates the function), PostgreSQL will fail:

    code
    ERROR:  return type mismatch in function declared to return record
    +DETAIL:  Final statement returns jsonb instead of text at column 1.

    The database itself caught the type error. This happens at migration time, before any application code runs, before any TypeScript is compiled.

    The Return Type Contract

    PostgreSQL function return types create an explicit contract:

    Return Type DeclarationContract
    returns setof usersMust return all columns of users table, with matching types
    returns table(username text, ...)Must return exactly these columns with these types
    returns intMust return a single integer value
    returns voidMust not return a value

    This contract is enforced when:

    1. The function is created or replaced
    2. The function is called

    If the underlying table structure changes in a way that breaks the contract, you discover it immediately when recreating the function - not when a user triggers the code path in production.

    Type Changes Propagate Naturally

    The workflow becomes:

    1. Change a column type in PostgreSQL
    2. Function recreation fails during migration (if return type doesn't match)
    3. Update the function to handle the new type
    4. NpgsqlRest regenerates TypeScript interfaces
    5. TypeScript build fails if client code uses the old type
    6. Update client code to match

    Every layer validates types. Errors surface at the earliest possible moment.

    Why Functions Are Recreated on Every Build

    The A__ prefix ensures these SQL files run on every database migration. This is the key to enforcing type checking:

    Scenario: You decide to rename content to body in get_posts().

    1. You update the SQL function to return body instead of content
    2. Database migration runs, recreating the function with the new signature
    3. NpgsqlRest regenerates example2Api.ts with the new interface
    4. TypeScript build fails: Property 'content' does not exist on type 'IGetPostsResponse'
    5. You fix app.ts to use body instead of content
    6. Build succeeds

    Without the function recreation on each build, the old TypeScript types would persist, and the error would only surface at runtime.

    Built-in Testing with SQL Assertions

    Update — since NpgsqlRest 3.19

    The pattern described here — SQL assertions with deferrable constraints and rollback isolation — is now a first-class feature: the SQL test runner (npgsqlrest --test) runs .sql test files against the real HTTP endpoints in-process, on each test's own transaction, with parallel isolated connections, watch mode, and endpoint-coverage reporting. Everything below still applies; the runner gives it a harness, a report, and CI integration.

    Each function file includes an assert block that validates the function returns expected data. These assertions run during migration, providing immediate feedback:

    sql
    sql
    do
    +$$
    +begin
    +    assert (
    +        select count(*) = 3
    +        from example_2.get_users()
    +        where (user_id, username, email, active) in (
    +            (1, 'alice', 'alice@example.com', true),
    +            (2, 'bob', 'bob@example.com', true),
    +            (3, 'charlie', 'charlie@example.com', true)
    +        )
    +    ), 'get_users() does not contain expected data';
    +end;
    +$$;

    If the assertion fails, the migration fails, and you know immediately that something is wrong. This creates a safety net ensuring:

    1. The function executes without errors
    2. The function returns the expected structure
    3. Test data is present and correct

    This is database-level unit testing that runs on every deployment.

    Unit Testing PostgreSQL Functions: Beyond Fixed Data

    The example above uses fixed test data that's inserted during migration. This approach is simple and effective, but what about testing with dynamic data or edge cases? Here are sturdier testing patterns.

    Co-located Tests: Function and Test in the Same File

    The most practical approach is to place tests directly in the same file as the function being tested. Since these files run on every build (the A__ prefix), your tests execute automatically with every migration.

    Here's the complete pattern for A__example_2_get_users.sql:

    sql
    sql
    -- A__example_2_get_users.sql
    +
    +create or replace function example_2.get_users()
    +returns setof example_2.users
    +language sql
    +begin atomic;
    +select user_id, username, email, active from example_2.users;
    +end;
    +
    +comment on function example_2.get_users() is 'HTTP GET';
    +
    +-- Test: Verify function returns expected fixed data
    +do
    +$$
    +begin
    +    assert (
    +        select count(*) = 3
    +        from example_2.get_users()
    +        where (user_id, username, email, active) in (
    +            (1, 'alice', 'alice@example.com', true),
    +            (2, 'bob', 'bob@example.com', true),
    +            (3, 'charlie', 'charlie@example.com', true)
    +        )
    +    ), 'get_users() does not contain expected data';
    +end;
    +$$;

    The function and its test live together. When the function changes, the test is right there to update.

    Test Isolation with Rollback

    Unit tests must not interfere with each other. The solution: end your test block with rollback; to undo any data modifications.

    sql
    sql
    -- Test: get_users() returns newly inserted users
    +do
    +$$
    +begin
    +    -- Insert test data
    +    insert into example_2.users (username, email, active)
    +    values ('test_user', 'test@example.com', true);
    +
    +    -- Verify the function returns our test user
    +    assert (
    +        select count(*) = 1
    +        from example_2.get_users()
    +        where username = 'test_user'
    +    ), 'get_users() should return the inserted test user';
    +
    +    -- Rollback to undo the insert
    +    rollback;
    +end;
    +$$;

    The rollback; at the end ensures the test data never persists. Each test starts with a clean slate.

    Testing Multiple Scenarios

    You can have multiple test blocks in the same file, each testing a different scenario:

    sql
    sql
    -- A__example_2_get_posts.sql
    +
    +create or replace function example_2.get_posts()
    +returns table(
    +    username text,
    +    content text,
    +    created_at timestamp
    +)
    +language sql
    +begin atomic;
    +select u.username, p.content, p.created_at
    +from example_2.posts p join example_2.users u using(user_id)
    +where u.active = true;
    +end;
    +
    +comment on function example_2.get_posts() is 'HTTP GET';
    +
    +-- Test 1: Verify function returns expected fixed data
    +do
    +$$
    +begin
    +    assert (
    +        select count(*) = 5
    +        from example_2.get_posts()
    +        where (username, content, created_at) in (
    +            ('alice', 'Hello world! This is my first post.', '2024-01-15 10:30:00'::timestamp),
    +            ('alice', 'Learning PostgreSQL is fun!', '2024-01-16 14:20:00'::timestamp),
    +            ('bob', 'Just joined this platform.', '2024-01-17 09:00:00'::timestamp),
    +            ('charlie', 'Anyone here interested in databases?', '2024-01-18 11:45:00'::timestamp),
    +            ('bob', 'Working on a new project today.', '2024-01-19 16:30:00'::timestamp)
    +        )
    +    ), 'get_posts() does not contain expected data';
    +end;
    +$$;
    +
    +-- Test 2: Verify inactive users' posts are excluded
    +do
    +$$
    +declare
    +    _count int;
    +begin
    +    -- Make charlie inactive
    +    update example_2.users set active = false where username = 'charlie';
    +
    +    -- Count posts from charlie (should be 0 since he's now inactive)
    +    select count(*) into _count
    +    from example_2.get_posts()
    +    where username = 'charlie';
    +
    +    assert _count = 0,
    +        'get_posts() should not return posts from inactive users';
    +
    +    rollback;
    +end;
    +$$;
    +
    +-- Test 3: New posts from active users appear in results
    +do
    +$$
    +declare
    +    _result record;
    +begin
    +    -- Insert a new post
    +    insert into example_2.posts (user_id, content, created_at)
    +    values (1, 'Brand new post!', now());
    +
    +    -- Verify it appears in results
    +    select * into _result
    +    from example_2.get_posts()
    +    where content = 'Brand new post!';
    +
    +    assert _result.username = 'alice',
    +        'New post should appear with correct username';
    +
    +    rollback;
    +end;
    +$$;

    Testing Against Empty Tables

    To test how a function behaves with no data:

    sql
    sql
    -- Test: Function handles empty tables gracefully
    +do
    +$$
    +declare
    +    _count int;
    +begin
    +    -- Delete all data
    +    delete from example_2.posts;
    +    delete from example_2.users;
    +
    +    -- Test that function returns 0 rows (not an error)
    +    select count(*) into _count from example_2.get_users();
    +    assert _count = 0,
    +        'get_users() should return 0 rows when table is empty';
    +
    +    select count(*) into _count from example_2.get_posts();
    +    assert _count = 0,
    +        'get_posts() should return 0 rows when table is empty';
    +
    +    rollback;
    +end;
    +$$;

    Deferrable Constraints: The Key to Test Data

    Notice in our schema definition:

    sql
    sql
    user_id int references example_2.users(user_id) deferrable

    The deferrable keyword is crucial for testing. By default, foreign key constraints are checked immediately when you insert a row. With deferrable constraints, checks can be deferred until the end of the transaction.

    Since test transactions are rolled back anyway, deferrable constraints let you insert test data without first populating a dozen related tables. That removes most of the test setup work.

    sql
    sql
    -- Test: Posts with deferred constraints
    +do
    +$$
    +begin
    +    -- Defer all constraint checks until transaction end
    +    set constraints all deferred;
    +
    +    -- Insert post with non-existent user_id (constraint check deferred)
    +    insert into example_2.posts (user_id, content, created_at)
    +    values (999, 'Orphan post', now());
    +
    +    -- We can test our function here
    +    -- The FK violation won't be checked because we'll rollback
    +
    +    rollback;
    +end;
    +$$;

    Why Database Testing is Fast

    A common misconception is that database testing is slow. In reality, PostgreSQL testing can be faster than application-level testing because:

    1. No network overhead: Tests run inside the database
    2. Transaction rollback is instant: No need to truncate tables or restore backups
    3. Parallel execution: Tests in separate transactions can run concurrently
    4. No ORM overhead: Direct SQL execution

    The key is proper isolation through transactions, not through recreating entire databases for each test.

    Addressing Common Myths

    "Testing in a database is impossible"

    This is simply not true for PostgreSQL. While testing stored procedures might be difficult in some database systems, PostgreSQL provides all the tools you need: anonymous blocks, assertions, and transaction control.

    "Testing in a database is slow"

    It's actually faster than most alternatives. When your test ends with rollback;, there's no cleanup needed - no truncating tables, no restoring backups. The rollback is nearly instantaneous. Compare this to spinning up Docker containers or re-seeding test databases.

    "You can't do TDD with SQL"

    You absolutely can. Write your test first in the function file:

    sql
    sql
    -- A__example_2_create_user.sql
    +
    +-- Test first (this will fail until we implement create_user)
    +do
    +$$
    +begin
    +    perform example_2.create_user('newuser', 'new@example.com');
    +
    +    assert exists (
    +        select 1 from example_2.users where username = 'newuser'
    +    ), 'User should be created';
    +
    +    rollback;
    +end;
    +$$;
    +
    +-- Now implement the function to make the test pass
    +create or replace function example_2.create_user(_username text, _email text)
    +returns void
    +language sql
    +begin atomic;
    +insert into example_2.users (username, email, active)
    +values (_username, _email, true);
    +end;

    Red-green-refactor works just as well in SQL as anywhere else. The test runs on every build, ensuring the function continues to work as expected.

    The Generated TypeScript Client

    NpgsqlRest automatically generates example2Api.ts based on the PostgreSQL function signatures:

    typescript
    typescript
    // autogenerated at 2025-12-31T12:06:45.2201980+01:00
    +
    +const baseUrl = "http://127.0.0.1:8080";
    +
    +interface IGetPostsResponse {
    +    username: string | null;
    +    content: string | null;
    +    createdAt: string | null;
    +}
    +
    +interface IGetUsersResponse {
    +    userId: number | null;
    +    username: string | null;
    +    email: string | null;
    +    active: boolean | null;
    +}
    +
    +/**
    +* function example_2.get_posts()
    +* returns table(
    +*     username text,
    +*     content text,
    +*     created_at timestamp without time zone
    +* )
    +*/
    +export async function getPosts() : Promise<{
    +    status: number,
    +    response: IGetPostsResponse[],
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/api/example-2/get-posts", {
    +        method: "GET",
    +        headers: {
    +            "Content-Type": "application/json"
    +        },
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IGetPostsResponse[] : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as {status: number; title: string; detail?: string | null} : undefined
    +    };
    +}
    +
    +/**
    +* function example_2.get_users()
    +* returns table(
    +*     user_id integer,
    +*     username text,
    +*     email text,
    +*     active boolean
    +* )
    +*/
    +export async function getUsers() : Promise<{
    +    status: number,
    +    response: IGetUsersResponse[],
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/api/example-2/get-users", {
    +        method: "GET",
    +        headers: {
    +            "Content-Type": "application/json"
    +        },
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IGetUsersResponse[] : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as {status: number; title: string; detail?: string | null} : undefined
    +    };
    +}

    Key features of the generated code:

    1. Interfaces match the PostgreSQL return types exactly - Column names are converted from snake_case to camelCase
    2. Nullable fields use | null - Reflecting PostgreSQL's nullable columns
    3. JSDoc comments include the original function signature - Making it easy to trace back to the source
    4. Typed error handling - Errors have a consistent structure with status, title, and optional detail

    The Application Code

    The hand-written application code in app.ts demonstrates how type safety flows through to the UI layer:

    typescript
    typescript
    import { getPosts, getUsers } from "./example2Api.ts";
    +
    +const app = document.getElementById("app")!;
    +
    +// Render a single user row - uses IGetUsersResponse properties
    +function renderUserRow(user: {
    +    userId: number | null;
    +    username: string | null;
    +    email: string | null;
    +    active: boolean | null
    +}) {
    +    const row = document.createElement("tr");
    +    row.innerHTML = `
    +        <td>${user.userId}</td>
    +        <td>${user.username ?? "Anonymous"}</td>
    +        <td>${user.email ?? "N/A"}</td>
    +        <td>${user.active ? "✓" : "✗"}</td>
    +    `;
    +    return row;
    +}
    +
    +// Render a single post - uses IGetPostsResponse properties
    +function renderPost(post: {
    +    username: string | null;
    +    content: string | null;
    +    createdAt: string | null
    +}) {
    +    const article = document.createElement("article");
    +    article.className = "post";
    +    article.innerHTML = `
    +        <header><strong>${post.username ?? "Anonymous"}</strong></header>
    +        <p>${post.content ?? ""}</p>
    +        <footer><small>${
    +            post.createdAt
    +                ? new Date(post.createdAt).toLocaleString()
    +                : "Unknown date"
    +        }</small></footer>
    +    `;
    +    return article;
    +}
    +
    +// Load and display users
    +async function loadUsers() {
    +    const { status, response: users, error } = await getUsers();
    +
    +    if (status !== 200) {
    +        app.innerHTML = `<p>Error loading users: ${error?.title}</p>`;
    +        return;
    +    }
    +
    +    const table = document.createElement("table");
    +    table.innerHTML = `
    +        <thead>
    +            <tr>
    +                <th>ID</th>
    +                <th>Username</th>
    +                <th>Email</th>
    +                <th>Active</th>
    +            </tr>
    +        </thead>
    +    `;
    +    const tbody = document.createElement("tbody");
    +
    +    // Static type checking happens here!
    +    // If IGetUsersResponse changes (e.g., "username" renamed to "name"),
    +    // TypeScript will fail the build with: Property 'username' does not exist
    +    for (const user of users) {
    +        tbody.appendChild(renderUserRow(user));
    +    }
    +
    +    table.appendChild(tbody);
    +    app.appendChild(table);
    +}
    +
    +// Load and display posts
    +async function loadPosts() {
    +    const { status, response: posts, error } = await getPosts();
    +
    +    if (status !== 200) {
    +        app.innerHTML += `<p>Error loading posts: ${error?.title}</p>`;
    +        return;
    +    }
    +
    +    const postsSection = document.createElement("section");
    +    postsSection.innerHTML = "<h2>Posts</h2>";
    +
    +    // Static type checking happens here!
    +    // If IGetPostsResponse changes (e.g., "content" renamed to "body"),
    +    // TypeScript will fail the build with: Property 'content' does not exist
    +    for (const post of posts) {
    +        postsSection.appendChild(renderPost(post));
    +    }
    +
    +    app.appendChild(postsSection);
    +}
    +
    +// Initialize the app
    +async function init() {
    +    app.innerHTML = "<h1>Users & Posts</h1>";
    +    await loadUsers();
    +    await loadPosts();
    +}
    +
    +init();

    The critical type-checking points are in the loop bodies where we access properties:

    • user.userId, user.username, user.email, user.active
    • post.username, post.content, post.createdAt

    If any of these properties are renamed or removed in the PostgreSQL function, the TypeScript build fails immediately.

    The Complete Type-Safe Workflow

    Here's what happens during development:

    bash
    bash
    # 1. Run database migrations (recreates functions every time)
    +bun run db:up
    +
    +# 2. Start NpgsqlRest (regenerates example2Api.ts from current functions)
    +bun run dev
    +
    +# 3. Build TypeScript (catches any type mismatches)
    +bun run build

    If you change a column name in a PostgreSQL function:

    1. db:up recreates the function with the new column
    2. NpgsqlRest detects the schema change and regenerates example2Api.ts
    3. bun run build fails because app.ts references the old column name
    4. You update app.ts to use the new name
    5. Build succeeds

    The error is caught at build time, not runtime. No more mysterious undefined values in production.

    Configuration

    The NpgsqlRest configuration enables type generation:

    json
    json
    {
    +  "ApplicationName": "static_type_checking",
    +  "StaticFiles": {
    +    "RootPath": "./2_static_type_checking/public"
    +  },
    +  "NpgsqlRest": {
    +    "IncludeSchemas": [ "example_2" ],
    +    "RequiresAuthorization": false,
    +    "ClientCodeGen": {
    +      "FilePath": "./2_static_type_checking/src/{0}Api.ts"
    +    }
    +  }
    +}

    The {0} placeholder in FilePath is replaced with the schema name, so example_2 becomes example2Api.ts.

    Benefits of This Approach

    1. Single Source of Truth

    PostgreSQL functions define both the API contract and the TypeScript types. No manual synchronization required.

    2. Compile-Time Safety

    Schema changes break the build, not production. You discover problems during development, not from user reports.

    3. Automatic Documentation

    The generated code includes JSDoc comments with the original PostgreSQL function signature. Your IDE shows exactly what the database returns.

    4. Database-Level Testing

    SQL assertions validate your functions return the expected data structure. Tests run on every migration.

    5. No Runtime Type Checking Overhead

    Types are enforced at compile time. The generated JavaScript has zero overhead compared to hand-written fetch calls.

    Conclusion

    Make PostgreSQL the single source of truth, regenerate types on every build, and schema mismatches get caught before they reach production.

    The key ingredients:

    1. PostgreSQL function return types that create explicit contracts enforced by the database
    2. Always-run migrations (A__ prefix) that recreate functions and run tests on every build
    3. Co-located tests in the same file as the function, using simple do $$ ... rollback; end; $$; blocks
    4. Deferrable constraints that enable isolated, fast unit tests
    5. NpgsqlRest's type generation that creates TypeScript interfaces from PostgreSQL signatures
    6. TypeScript compilation that catches property mismatches

    The result is a multi-layered type safety system:

    code
    PostgreSQL Schema
    +      ↓ (enforced by DB)
    +PostgreSQL Functions + Tests
    +      ↓ (generated by NpgsqlRest)
    +TypeScript Interfaces
    +      ↓ (enforced by tsc)
    +Application Code

    Schema changes propagate through the entire stack, with the database and the compilers acting as your safety net.

    And unit testing in PostgreSQL isn't slow or difficult. Anonymous blocks, transaction rollback, and deferrable constraints support the same TDD workflows you'd use in any other language - against your actual database logic, not a mock of it.

    Why This Stack is Superior

    Compare this approach to traditional stacks:

    Traditional StackThis Stack
    Database schemaDatabase schema
    ORM models
    Repository layer
    Service layerPostgreSQL functions (or SQL files for simpler queries)
    Controller layer
    API documentation
    TypeScript types (manual)TypeScript types (generated)
    Integration testsCo-located SQL tests

    You eliminate entire layers. No ORM mappings to maintain. No repository pattern boilerplate. No controller classes. No manual API documentation. No hand-written TypeScript interfaces that drift out of sync with reality.

    The code you don't write has no bugs.

    Performance That Scales

    The architecture also pays off at runtime, because NpgsqlRest skips the overhead that traditional frameworks accumulate:

    • No ORM overhead: Direct PostgreSQL protocol communication
    • No routing framework: Endpoints derived from database metadata
    • No serialization layer: PostgreSQL's native JSON functions handle serialization
    • Minimal memory allocation: Optimized hot paths using buffer pooling

    In benchmarks, it achieves 4,588 requests per second at 100 concurrent users - alongside Swoole PHP and ahead of Bun, Go, Fastify, and Spring Boot.

    Maximum Type Safety, Minimum Code

    Traditional approaches require you to define types in multiple places and hope they stay synchronized:

    code
    Database → ORM → Service → Controller → OpenAPI → Client SDK → Frontend

    Each arrow is a potential desynchronization point. Each layer requires manual maintenance.

    With NpgsqlRest:

    code
    Database → Generated Client → Frontend

    One source of truth. Zero manual synchronization. Types flow automatically from database to browser.

    The Bottom Line

    This stack delivers:

    • Full end-to-end type safety from PostgreSQL to TypeScript
    • The least amount of code - no boilerplate layers to maintain
    • A direct database-to-HTTP pipeline with no intermediate layers
    • Built-in testing that runs on every deployment
    • Automatic documentation through generated code

    When you combine database-enforced types, automated type generation, compile-time checking, and co-located SQL tests, you get a development experience where "it works on my machine" actually means "it will work in production."

    Comments

    + + + + \ No newline at end of file diff --git a/blog/excel-export-table-format-postgresql-npgsqlrest.html b/blog/excel-export-table-format-postgresql-npgsqlrest.html new file mode 100644 index 000000000..35f08c435 --- /dev/null +++ b/blog/excel-export-table-format-postgresql-npgsqlrest.html @@ -0,0 +1,190 @@ + + + + + + Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Excel Exports Done Right: Zero-Allocation Streaming from PostgreSQL

    February 2026 · ExcelExportPostgreSQLPerformanceNpgsqlRest

    After 20 years of maintaining codebases, I can confidently say: Excel export code is the worst part of any system. It eats all the memory. It crashes servers. It brings down production. I've seen teams isolate export services behind separate infrastructure just so a report download doesn't take the rest of the application with it.

    NpgsqlRest 3.7.0 removes the problem at the source. One SQL annotation turns any PostgreSQL function into a streaming .xlsx download with constant memory usage, zero allocations per cell, and native Excel type mapping - all without writing a single line of application code.

    Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/14_table_format

    Why Excel Exports Are Terrible

    The traditional approach to Excel exports goes something like this:

    1. Execute a query and load the entire result set into memory
    2. Create an in-memory workbook object (another copy of all the data)
    3. Write cells one by one (allocating strings for each cell value)
    4. Serialize the workbook to a byte array (yet another copy)
    5. Send the byte array to the client

    For a 1 million row report, you're looking at 3-4x the data size in memory simultaneously.

    And that's the best case. In practice, export code often introduces N+1 query patterns that make things catastrophically worse. The export iterates over rows, and for each row it fires off additional queries to fetch related data - customer names, product details, category labels, audit info. A 100,000-row export becomes 100,001 database queries. The database connection pool fills up, response times spike across the entire application, and now your export isn't just crashing the export service - it's dragging down every other request in the system.

    This is why export endpoints are the number one cause of OutOfMemoryException in enterprise systems. It's why teams build separate "report servers" and queue-based export systems. It's why a simple "Download to Excel" button requires an entire architecture discussion.

    The NpgsqlRest Approach: Pure Streaming

    NpgsqlRest's table format rendering never builds a workbook at all:

    mermaid
    flowchart LR
    +    PG["PostgreSQL
    +    NpgsqlDataReader
    +    (one row at a time)"]
    +
    +    PG -- "row by row" --> SC["SpreadCheetah
    +    (forward-only writer)"]
    +
    +    SC -- "streaming" --> PW["PipeWriter
    +    (HTTP response stream)"]
    +
    +    PW -- ".xlsx download" --> BR["Browser"]

    Rows flow directly from PostgreSQL to the browser. The full result set is never materialized in memory. There are no intermediate collections. No buffering the entire dataset.

    All you need is one annotation:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +';

    That's it. Your function's result set streams straight to the user's browser as an .xlsx download.

    What Makes This Special

    Zero-Allocation Cell Writing

    The implementation uses SpreadCheetah - a library designed specifically for forward-only spreadsheet generation with minimal allocations.

    SpreadCheetah writes cells as value types (DataCell structs), avoiding heap allocations per cell. Combined with a pre-allocated cell array that gets reused for every row, memory pressure stays flat regardless of dataset size.

    Native Type Mapping

    Integers, decimals, booleans, and DateTimes are written as native Excel types directly from the PostgreSQL reader - not stringified and re-parsed. Your numbers stay as numbers. Your dates stay as dates. Excel formulas work immediately without "Convert to Number" warnings.

    PostgreSQL TypeExcel Type
    int, bigintNumber
    numeric, floatNumber (with format)
    booleanBoolean
    date, timestampDateTime
    text, varcharString
    json, jsonbString

    Constant Memory Usage

    Since Excel is compressed XML under the hood, some buffering is needed for the compression layer. But that buffering doesn't exceed ~80KB during export operations, regardless of whether you're exporting 100 rows or 10 million rows.

    AOT/Trim Compatible

    No reflection, no expression trees, no runtime code generation. Works with .NET's PublishAot and full trimming - important for the single-binary deployment that NpgsqlRest uses.

    Building an Excel Export Endpoint

    Step 1: Write Your Function

    Your PostgreSQL function defines the report. The return columns become Excel columns:

    sql
    sql
    create or replace function example_14.get_data(
    +    _format text,
    +    _excel_file_name text = null,
    +    _excel_sheet text = null
    +)
    +returns table (
    +    int_val int,
    +    bigint_val bigint,
    +    numeric_val numeric(10,4),
    +    float_val double precision,
    +    bool_val bool,
    +    text_val text,
    +    date_val date,
    +    timestamp_val timestamp,
    +    time_val time,
    +    json_val json,
    +    null_text text,
    +    null_int int
    +)
    +language sql
    +begin atomic;
    +select * from (values
    +    (42,        9999999999::bigint, 3.1415::numeric(10,4), 2.71828::float8, true,  'hello world',      '2025-06-15'::date, '2025-06-15 14:30:00'::timestamp, '09:45:30'::time, '{"key":"value"}'::json, null::text, null::int),
    +    (-1,        0::bigint,          0.0001::numeric(10,4), -99.99::float8,  false, 'special <chars> &', '2000-01-01'::date, '2000-01-01 00:00:00'::timestamp, '23:59:59'::time, '[1,2,3]'::json,        'not null', 7),
    +    (2147483647, -1::bigint,        99999.9999::numeric(10,4), 0::float8,   true,  '',                  '1999-12-31'::date, '1999-12-31 23:59:59'::timestamp, '00:00:00'::time, 'null'::json,           null::text, null::int)
    +);
    +end;

    In a real application, this would be your reporting query - joins across tables, aggregations, window functions, whatever your report needs. The point is: the report logic lives in PostgreSQL, not in application code.

    Step 2: Add the Annotation

    The function comment controls everything:

    sql
    sql
    comment on function example_14.get_data(text,text,text) is '
    +HTTP GET
    +@authorize
    +@table_format = {_format}
    +@excel_file_name = {_excel_file_name}
    +@excel_sheet = {_excel_sheet}
    +@tsclient_url_only = true
    +';
    AnnotationEffect
    @authorizeRequire authentication
    @table_format = {_format}Dynamic format from _format parameter (html or excel)
    @excel_file_name = {_excel_file_name}Custom download filename from parameter
    @excel_sheet = {_excel_sheet}Custom worksheet name from parameter
    @tsclient_url_only = trueGenerate only URL constant in TypeScript (not a fetch function)

    The {_format} placeholder resolves the table format from a function parameter, so the same endpoint serves both HTML table views and Excel downloads depending on what the client requests.

    Step 3: Configure Table Format

    Enable the feature in your config.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "HtmlEnabled": true,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel"
    +    }
    +  }
    +}

    That's the entire backend. No libraries to install. No export service to build. No memory tuning to configure.

    Two Formats, One Endpoint

    The dynamic @table_format = {_format} pattern means the same function serves both HTML and Excel:

    HTML view (for browser preview):

    code
    GET /api/example-14/get-data?format=html

    Renders a styled HTML table in the browser - perfect for quick previews and copy-paste into Excel.

    Excel download (for file export):

    code
    GET /api/example-14/get-data?format=excel

    Streams a properly-typed .xlsx file as a download.

    Static Format Annotation

    If you don't need dynamic format switching, you can hardcode the format:

    sql
    sql
    -- Always Excel download
    +comment on function monthly_report() is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = monthly_report.xlsx
    +@excel_sheet = Report Data
    +';
    sql
    sql
    -- Always HTML table
    +comment on function dashboard_data() is '
    +HTTP GET
    +@table_format = html
    +';

    The TypeScript Client: URL-Only Generation

    For table format endpoints, the traditional fetch-based client doesn't make sense. You don't fetch() an Excel file - you navigate to it. That's what @tsclient_url_only = true is for.

    NpgsqlRest generates only the URL constant and request interface:

    typescript
    typescript
    // Auto-generated - no fetch function, just the URL builder
    +export const getDataUrl = (request: IGetDataRequest) =>
    +    baseUrl + "/api/example-14/get-data" + parseQuery(request);
    +
    +interface IGetDataRequest {
    +    format: string | null;
    +    excelFileName?: string | null;
    +    excelSheet?: string | null;
    +}

    Using it in your frontend:

    typescript
    typescript
    import { getDataUrl } from "./example14Api.ts";
    +
    +// HTML preview - open in browser
    +htmlLink.href = getDataUrl({ format: "html" });
    +
    +// Excel download - navigate to trigger download
    +excelLink.addEventListener("click", (e) => {
    +    e.preventDefault();
    +    const dateStr = new Date().toISOString().slice(0, 19).replace(/[-:]/g, "");
    +    const fileName = `data-${dateStr}.xlsx`;
    +    const sheetName = `data-${dateStr}`;
    +    document.location.href = getDataUrl({
    +        format: "excel",
    +        excelFileName: fileName,
    +        excelSheet: sheetName
    +    });
    +});

    Notice how the Excel filename includes a timestamp - each download gets a unique name. This is resolved on the server from the {_excel_file_name} placeholder.

    Or for the simplest case, just a plain HTML link:

    html
    html
    <a href="/api/example-14/get-data?format=html">View as HTML Table</a>
    +<a href="/api/example-14/get-data?format=excel">Download Excel</a>

    No JavaScript required for basic usage.

    Excel Format Configuration

    DateTime and Numeric Formats

    Control how dates and numbers appear in Excel cells:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "ExcelEnabled": true,
    +      "ExcelDateTimeFormat": "yyyy-mm-dd hh:mm",
    +      "ExcelNumericFormat": "#,##0.00"
    +    }
    +  }
    +}
    OptionDefaultExamples
    ExcelDateTimeFormatyyyy-MM-dd HH:mm:ssyyyy-mm-dd, dd/mm/yyyy hh:mm
    ExcelNumericFormatGeneral#,##0.00, 0.00, #,##0

    These are Excel Format Codes, not .NET format strings. They control how Excel displays the values in cells.

    Worksheet and File Names

    Set defaults globally, override per-endpoint:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "ExcelSheetName": "Data"
    +    }
    +  }
    +}

    Per-endpoint overrides via annotations:

    sql
    sql
    comment on function quarterly_report(_quarter int, _year int) is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = Q{_quarter}_{_year}_report.xlsx
    +@excel_sheet = Q{_quarter} {_year}
    +';

    Calling GET /api/quarterly-report?quarter=2&year=2026 downloads Q2_2026_report.xlsx with a worksheet named Q2 2026.

    HTML Table Format

    The HTML format renders results as a styled table suitable for browser viewing:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = html
    +';

    The HTML output is designed for copy-paste into Excel. Select the table in your browser, paste into Excel, and the data transfers cleanly with proper column alignment.

    You can customize the HTML wrapper:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "HtmlEnabled": true,
    +      "HtmlHeader": "<!DOCTYPE html><html><head><style>table { border-collapse: collapse; } th, td { border: 1px solid #ddd; padding: 8px; }</style></head><body>",
    +      "HtmlFooter": "</body></html>"
    +    }
    +  }
    +}

    Bonus: Built-In Statistics Endpoints

    Version 3.7.0 also introduced PostgreSQL statistics endpoints - built-in HTTP endpoints for monitoring your database performance without writing any SQL:

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html",
    +    "SchemaSimilarTo": "example_14"
    +  }
    +}

    This gives you four monitoring endpoints out of the box:

    EndpointSourceWhat It Shows
    /stats/routinespg_stat_user_functionsFunction call counts, execution times
    /stats/tablespg_stat_user_tablesTuple counts, table sizes, scan counts, vacuum info
    /stats/indexespg_stat_user_indexesIndex scan counts, index definitions
    /stats/activitypg_stat_activityActive sessions, running queries, wait events

    The default html output format renders as an HTML table you can view in the browser or copy-paste into Excel - the same HTML table format used by the table format rendering system.

    Stats Configuration Options

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html",
    +    "CacheDuration": "5 seconds",
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["admin"],
    +    "RateLimiterPolicy": "fixed",
    +    "SchemaSimilarTo": "my_schema"
    +  }
    +}
    OptionDescription
    OutputFormathtml (default) or json
    CacheDurationCache responses to avoid hitting pg_stat views on every request
    RequireAuthorizationLock down stats endpoints (recommended for production)
    AuthorizedRolesRestrict access to specific roles
    SchemaSimilarToFilter stats to a specific schema pattern
    ConnectionNameQuery stats from a different connection (e.g., read replica)

    For routine statistics to work, make sure track_functions is enabled in PostgreSQL:

    sql
    sql
    alter system set track_functions = 'all';
    +select pg_reload_conf();

    The Traditional Way vs. This Way

    Here's what Excel export typically looks like in a traditional codebase:

    Traditional Excel ExportNpgsqlRest
    Install EPPlus/ClosedXML/NPOINothing to install
    Write query execution codeWrite your SQL function
    Build in-memory workbookStreaming - no workbook object
    Handle type conversions manuallyNative type mapping
    Manage memory for large exportsConstant ~80KB buffer
    Write download response handlingAutomatic
    Separate export service for safetyNot needed
    TypeScript types for parametersAuto-generated
    200-500 lines of codeOne SQL annotation

    Memory Profile Comparison

    For a 1 million row export with 10 columns:

    MetricTraditional (EPPlus/ClosedXML)NpgsqlRest + SpreadCheetah
    Peak memory500MB - 2GB+~80KB buffer
    Allocation rateMillions of objectsZero per-cell allocations
    Time to first byteAfter entire workbook builtImmediate (streaming)
    Risk of OOM crashHighNone

    Running the Example

    bash
    bash
    # Clone the repository
    +git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
    +cd npgsqlrest-docs/examples
    +
    +# Install dependencies
    +bun install
    +
    +# Navigate to the example
    +cd 14_table_format
    +
    +# Apply database migrations
    +bun run db:up
    +
    +# Start the server
    +bun run dev

    Open http://localhost:8080, log in with alice / password123, and try both the HTML view and Excel download links.

    Conclusion

    Write the report as a PostgreSQL function, add @table_format = excel, and rows stream to the browser with native Excel types and a constant ~80KB buffer. No export library, no separate report server, no server crash at 3 AM because someone exported a large report.

    One caveat: this produces raw tabular .xlsx - if you need charts, merged cells, or conditional formatting, you still want a workbook library. For plain data exports of any size, this is the simpler and safer option.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/external-api-calls-postgresql-http-types.html b/blog/external-api-calls-postgresql-http-types.html new file mode 100644 index 000000000..064e366bf --- /dev/null +++ b/blog/external-api-calls-postgresql-http-types.html @@ -0,0 +1,413 @@ + + + + + + Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Call External APIs from PostgreSQL: HTTP Types in NpgsqlRest

    HTTP · External APIs · Data Aggregation · January 2026


    Calling an external REST API from a PostgreSQL function takes one type comment - no HTTP client libraries, no middleware services, no API gateway configuration, no PostgreSQL HTTP extensions to install. Just SQL.

    NpgsqlRest's HTTP Types feature lets you define external API calls using the familiar .http file syntax right in your database. The HTTP request definition lives in a type comment, with function parameters automatically substituted into URLs, headers, and request bodies.

    This tutorial builds a Financial Dashboard that aggregates data from two public APIs - currency exchange rates and cryptocurrency prices - combining them into a single response. All with about 50 lines of SQL.

    Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/9_http_calls

    The Problem: Backend-for-Frontend API Aggregation

    A financial dashboard typically combines several external feeds before anything reaches the user:

    • Currency exchange rates from one service
    • Cryptocurrency prices from another
    • Stock market data from a third

    The traditional approach requires:

    1. HTTP Client Library - Axios, fetch, HttpClient, etc.
    2. API Service Layer - Classes to manage each external API
    3. Error Handling - Retry logic, timeout handling, circuit breakers
    4. Response Transformation - Map external responses to internal DTOs
    5. Caching Layer - Reduce API calls and improve performance
    6. API Gateway - Route and aggregate external calls

    This creates a substantial codebase just to proxy external data.

    Why Not Use PostgreSQL HTTP Extensions?

    PostgreSQL has extensions like http and pgsql-http that allow making HTTP requests directly from SQL. They work, but the drawbacks add up:

    Installation and Distribution Overhead

    HTTP extensions must be:

    • Compiled and installed on every PostgreSQL instance
    • Distributed with your deployment - adding complexity to Docker images, managed databases, CI/CD pipelines
    • Maintained across versions - extension compatibility with PostgreSQL upgrades
    • Approved by DBAs - many organizations restrict which extensions can be installed

    With managed database services (AWS RDS, Azure Database, Google Cloud SQL), you're often limited to a predefined list of extensions - and HTTP extensions may not be available.

    Network and Performance Issues

    Making HTTP calls directly from the database has architectural problems:

    • Database servers are often network-isolated - firewalls and proxies may block outbound HTTP traffic from the database tier
    • Connection pooling conflicts - long-running HTTP requests tie up database connections
    • Database waits for responses - while waiting for an external API, the PostgreSQL process is blocked, consuming a connection slot
    • No horizontal scaling - all HTTP calls go through the database, creating a bottleneck
    • Difficult to debug - network issues from the database server are harder to diagnose

    The NpgsqlRest Advantage

    With NpgsqlRest HTTP Types, the HTTP calls are made from the NpgsqlRest server, not from PostgreSQL:

    code
    Client → NpgsqlRest (makes HTTP calls) → PostgreSQL (receives populated data)

    This architecture provides:

    • No database extensions required - works with any PostgreSQL instance, including managed services
    • HTTP calls from the application tier - where network access is typically unrestricted
    • Database connections stay fast - PostgreSQL only executes the function with pre-fetched data
    • NpgsqlRest can scale horizontally - multiple instances can make HTTP calls in parallel
    • Standard debugging - HTTP issues are visible in application logs, not buried in database logs
    • Separation of concerns - the database handles data, the application tier handles external communication

    The NpgsqlRest Solution: HTTP Types

    HTTP Types turn PostgreSQL composite types into HTTP request definitions. When a function parameter uses an HTTP Type, NpgsqlRest automatically:

    1. Parses the HTTP definition from the type comment
    2. Substitutes placeholders with function parameter values
    3. Executes the HTTP request before calling the function
    4. Populates the type fields with the response (body, status, headers)
    5. Executes the PostgreSQL function with the populated parameter

    The result: external API calls declared in SQL, executed automatically by NpgsqlRest.

    The HTTP Type Syntax: Just Like .http Files

    The HTTP definition comment uses the same syntax as .http files (RFC 7230 format):

    code
    METHOD URL [HTTP/version]
    +Header-Name: Header-Value
    +...
    +[timeout directive]
    +
    +[request body]

    With one addition that does the heavy lifting: placeholders. Any {parameter_name} in the URL, headers, or body is replaced with the corresponding function parameter value.

    sql
    sql
    comment on type my_api is 'GET https://api.example.com/users/{_user_id}
    +Authorization: Bearer {_token}
    +Accept: application/json
    +@timeout 10s';

    When called with _user_id = 123 and _token = 'abc', NpgsqlRest makes:

    code
    GET https://api.example.com/users/123
    +Authorization: Bearer abc
    +Accept: application/json

    Building the Financial Dashboard

    The dashboard fetches real data from two free, public APIs:

    1. Exchange Rate API (open.er-api.com) - Fiat currency rates
    2. CoinGecko API (api.coingecko.com) - Cryptocurrency prices

    Step 1: Define HTTP Types

    First, create composite types to receive the API responses:

    sql
    sql
    -- HTTP Type for Exchange Rate API
    +create type example_9.exchange_rate_api as (
    +    body jsonb,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type example_9.exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';

    The comment defines:

    • GET request to the Exchange Rate API
    • {_base_currency} placeholder - substituted from function parameter
    • timeout 10s - request timeout
    sql
    sql
    -- HTTP Type for CoinGecko API
    +create type example_9.crypto_price_api as (
    +    body jsonb,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type example_9.crypto_price_api is 'GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
    +Accept: application/json
    +@timeout 10s';

    This API requires query parameters for cryptocurrency IDs and target currencies.

    Step 2: Define the Return Type

    Create a strongly-typed return structure:

    sql
    sql
    create type example_9.financial_dashboard_result as (
    +    -- Fiat exchange rates
    +    fiat_base_currency text,
    +    fiat_rates jsonb,
    +    fiat_last_updated text,
    +    fiat_success boolean,
    +    fiat_error text,
    +    -- Cryptocurrency prices
    +    crypto_prices jsonb,
    +    crypto_success boolean,
    +    crypto_error text
    +);

    This return type becomes a TypeScript interface in the generated client, so the type contract extends all the way to the frontend.

    Step 3: Create the Aggregation Function

    sql
    sql
    create function example_9.get_financial_dashboard(
    +    _base_currency text,
    +    _target_currencies_csv text,
    +    _crypto_ids_csv text,
    +    _vs_currencies_csv text,
    +    _exchange_rate_response example_9.exchange_rate_api,
    +    _crypto_response example_9.crypto_price_api
    +)
    +returns example_9.financial_dashboard_result
    +language plpgsql
    +as $$
    +declare
    +    _result example_9.financial_dashboard_result;
    +    _filtered_rates jsonb = '{}'::jsonb;
    +    _rate_data jsonb;
    +    _currency text;
    +    _target_arr text[];
    +begin
    +    -- Process exchange rate response
    +    if (_exchange_rate_response).success then
    +        _rate_data = (_exchange_rate_response).body;
    +        _target_arr = string_to_array(_target_currencies_csv, ',');
    +
    +        -- Filter only requested target currencies
    +        foreach _currency in array _target_arr loop
    +            _currency = upper(trim(_currency));
    +            if _rate_data->'rates' ? _currency then
    +                _filtered_rates = _filtered_rates ||
    +                    jsonb_build_object(_currency, _rate_data->'rates'->_currency);
    +            end if;
    +        end loop;
    +
    +        _result.fiat_base_currency = upper(_base_currency);
    +        _result.fiat_rates = _filtered_rates;
    +        _result.fiat_last_updated = _rate_data->>'time_last_update_utc';
    +        _result.fiat_success = true;
    +    else
    +        _result.fiat_base_currency = upper(_base_currency);
    +        _result.fiat_success = false;
    +        _result.fiat_error = coalesce(
    +            (_exchange_rate_response).error_message,
    +            'Failed to fetch exchange rates (status: ' || (_exchange_rate_response).status_code || ')'
    +        );
    +    end if;
    +
    +    -- Process crypto price response
    +    if (_crypto_response).success then
    +        _result.crypto_prices = (_crypto_response).body;
    +        _result.crypto_success = true;
    +    else
    +        _result.crypto_success = false;
    +        _result.crypto_error = coalesce(
    +            (_crypto_response).error_message,
    +            'Failed to fetch crypto prices (status: ' || (_crypto_response).status_code || ')'
    +        );
    +    end if;
    +
    +    return _result;
    +end;
    +$$;
    +
    +comment on function example_9.get_financial_dashboard is '
    +HTTP GET /financial-dashboard
    +@authorize';

    That's it. The entire backend for fetching, aggregating, and returning data from two external APIs is ~80 lines of SQL.

    Step 4: Configuration

    Enable HTTP Types in your configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true
    +    }
    +  }
    +}

    What Happens at Runtime

    When a client calls:

    code
    GET /financial-dashboard?baseCurrency=USD&targetCurrenciesCsv=EUR,GBP,JPY&cryptoIdsCsv=bitcoin,ethereum&vsCurrenciesCsv=usd,eur

    NpgsqlRest:

    1. Parses function parameters from the query string
    2. Identifies HTTP Type parameters (_exchange_rate_response, _crypto_response)
    3. Substitutes placeholders in each HTTP Type's definition:
      • Exchange Rate: GET https://open.er-api.com/v6/latest/USD
      • CoinGecko: GET https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd,eur
    4. Executes both HTTP requests (can be parallel)
    5. Populates the HTTP Type fields with responses
    6. Calls the PostgreSQL function with populated parameters
    7. Returns the function result as JSON

    The response:

    json
    json
    {
    +  "fiatBaseCurrency": "USD",
    +  "fiatRates": {
    +    "EUR": 0.854542,
    +    "GBP": 0.740946,
    +    "JPY": 156.619455
    +  },
    +  "fiatLastUpdated": "Tue, 06 Jan 2026 00:02:31 +0000",
    +  "fiatSuccess": true,
    +  "fiatError": null,
    +  "cryptoPrices": {
    +    "bitcoin": { "usd": 93561, "eur": 79851 },
    +    "ethereum": { "usd": 3226.93, "eur": 2754.07 }
    +  },
    +  "cryptoSuccess": true,
    +  "cryptoError": null
    +}

    The Generated TypeScript Client

    NpgsqlRest automatically generates a typed client:

    typescript
    typescript
    interface IGetFinancialDashboardRequest {
    +    baseCurrency: string | null;
    +    targetCurrenciesCsv: string | null;
    +    cryptoIdsCsv: string | null;
    +    vsCurrenciesCsv: string | null;
    +}
    +
    +interface IGetFinancialDashboardResponse {
    +    fiatBaseCurrency: string | null;
    +    fiatRates: any; // JSON
    +    fiatLastUpdated: string | null;
    +    fiatSuccess: boolean | null;
    +    fiatError: string | null;
    +    cryptoPrices: any; // JSON
    +    cryptoSuccess: boolean | null;
    +    cryptoError: string | null;
    +}
    +
    +export async function getFinancialDashboard(
    +    request: IGetFinancialDashboardRequest
    +): Promise<{
    +    status: number,
    +    response: IGetFinancialDashboardResponse,
    +    error: {...} | undefined
    +}> {
    +    // ... auto-generated fetch implementation
    +}

    The frontend code is straightforward:

    typescript
    typescript
    const response = await getFinancialDashboard({
    +    baseCurrency: "USD",
    +    targetCurrenciesCsv: "EUR,GBP,JPY,CHF",
    +    cryptoIdsCsv: "bitcoin,ethereum",
    +    vsCurrenciesCsv: "usd,eur"
    +});
    +
    +if (response.response.fiatSuccess) {
    +    // Display exchange rates
    +    for (const [currency, rate] of Object.entries(response.response.fiatRates)) {
    +        console.log(`1 USD = ${rate} ${currency}`);
    +    }
    +}
    +
    +if (response.response.cryptoSuccess) {
    +    // Display crypto prices
    +    for (const [crypto, prices] of Object.entries(response.response.cryptoPrices)) {
    +        console.log(`${crypto}: $${prices.usd}`);
    +    }
    +}

    Traditional Approach: What It Would Take

    The equivalent Node.js/Express implementation:

    Traditional Backend (Node.js)

    javascript
    javascript
    // services/exchangeRateService.js
    +const axios = require('axios');
    +
    +class ExchangeRateService {
    +    constructor() {
    +        this.baseUrl = 'https://open.er-api.com/v6/latest';
    +        this.timeout = 10000;
    +    }
    +
    +    async getRates(baseCurrency) {
    +        try {
    +            const response = await axios.get(`${this.baseUrl}/${baseCurrency}`, {
    +                timeout: this.timeout,
    +                headers: { 'Accept': 'application/json' }
    +            });
    +            return {
    +                success: true,
    +                data: response.data,
    +                statusCode: response.status
    +            };
    +        } catch (error) {
    +            return {
    +                success: false,
    +                error: error.message,
    +                statusCode: error.response?.status || 500
    +            };
    +        }
    +    }
    +}
    +
    +// services/cryptoPriceService.js
    +class CryptoPriceService {
    +    constructor() {
    +        this.baseUrl = 'https://api.coingecko.com/api/v3/simple/price';
    +        this.timeout = 10000;
    +    }
    +
    +    async getPrices(cryptoIds, vsCurrencies) {
    +        try {
    +            const response = await axios.get(this.baseUrl, {
    +                params: {
    +                    ids: cryptoIds.join(','),
    +                    vs_currencies: vsCurrencies.join(',')
    +                },
    +                timeout: this.timeout,
    +                headers: { 'Accept': 'application/json' }
    +            });
    +            return {
    +                success: true,
    +                data: response.data,
    +                statusCode: response.status
    +            };
    +        } catch (error) {
    +            return {
    +                success: false,
    +                error: error.message,
    +                statusCode: error.response?.status || 500
    +            };
    +        }
    +    }
    +}
    +
    +// controllers/dashboardController.js
    +const { body, query, validationResult } = require('express-validator');
    +
    +const validateDashboardRequest = [
    +    query('baseCurrency')
    +        .isLength({ min: 3, max: 3 })
    +        .withMessage('baseCurrency must be a 3-letter code'),
    +    query('targetCurrencies')
    +        .notEmpty()
    +        .withMessage('targetCurrencies is required'),
    +    query('cryptoIds')
    +        .notEmpty()
    +        .withMessage('cryptoIds is required'),
    +    query('vsCurrencies')
    +        .notEmpty()
    +        .withMessage('vsCurrencies is required')
    +];
    +
    +async function getFinancialDashboard(req, res) {
    +    const errors = validationResult(req);
    +    if (!errors.isEmpty()) {
    +        return res.status(400).json({ errors: errors.array() });
    +    }
    +
    +    const { baseCurrency, targetCurrencies, cryptoIds, vsCurrencies } = req.query;
    +
    +    const exchangeService = new ExchangeRateService();
    +    const cryptoService = new CryptoPriceService();
    +
    +    // Fetch both APIs in parallel
    +    const [exchangeResult, cryptoResult] = await Promise.all([
    +        exchangeService.getRates(baseCurrency),
    +        cryptoService.getPrices(
    +            cryptoIds.split(','),
    +            vsCurrencies.split(',')
    +        )
    +    ]);
    +
    +    // Filter exchange rates to requested currencies
    +    let filteredRates = {};
    +    if (exchangeResult.success) {
    +        const targetArray = targetCurrencies.split(',').map(c => c.trim().toUpperCase());
    +        for (const currency of targetArray) {
    +            if (exchangeResult.data.rates[currency]) {
    +                filteredRates[currency] = exchangeResult.data.rates[currency];
    +            }
    +        }
    +    }
    +
    +    // Build response
    +    const response = {
    +        fiatBaseCurrency: baseCurrency.toUpperCase(),
    +        fiatRates: exchangeResult.success ? filteredRates : null,
    +        fiatLastUpdated: exchangeResult.success ? exchangeResult.data.time_last_update_utc : null,
    +        fiatSuccess: exchangeResult.success,
    +        fiatError: exchangeResult.success ? null : exchangeResult.error,
    +        cryptoPrices: cryptoResult.success ? cryptoResult.data : null,
    +        cryptoSuccess: cryptoResult.success,
    +        cryptoError: cryptoResult.success ? null : cryptoResult.error
    +    };
    +
    +    res.json(response);
    +}
    +
    +// routes/dashboard.js
    +const express = require('express');
    +const router = express.Router();
    +const { authenticateToken } = require('../middleware/auth');
    +
    +router.get(
    +    '/financial-dashboard',
    +    authenticateToken,
    +    validateDashboardRequest,
    +    getFinancialDashboard
    +);
    +
    +module.exports = router;
    +
    +// types/dashboard.ts (if using TypeScript)
    +interface FinancialDashboardRequest {
    +    baseCurrency: string;
    +    targetCurrencies: string;
    +    cryptoIds: string;
    +    vsCurrencies: string;
    +}
    +
    +interface FinancialDashboardResponse {
    +    fiatBaseCurrency: string;
    +    fiatRates: Record<string, number> | null;
    +    fiatLastUpdated: string | null;
    +    fiatSuccess: boolean;
    +    fiatError: string | null;
    +    cryptoPrices: Record<string, Record<string, number>> | null;
    +    cryptoSuccess: boolean;
    +    cryptoError: string | null;
    +}

    Plus you need:

    • Express app setup
    • Middleware configuration
    • Error handling middleware
    • Authentication middleware
    • Environment configuration
    • Dependency management (package.json)
    • TypeScript compilation (if using TS)

    The Numbers

    ComponentTraditional (Node.js)NpgsqlRest
    Service classes2 files, ~60 lines each0
    Controller1 file, ~80 lines0
    Routes1 file, ~15 lines0 (annotation)
    Type definitions1 file, ~20 linesGenerated automatically
    HTTP clientaxios + configurationBuilt-in
    Total backend code~250 lines + dependencies~80 lines SQL
    TypeScript clientManual or OpenAPI generationAuto-generated
    Dependenciesaxios, express-validator, etc.None additional

    Estimated savings: 70% less code, zero additional dependencies, auto-generated client.

    Advanced Features

    Multiple API Calls

    A single function can use multiple HTTP Types for sequential or parallel API calls:

    sql
    sql
    create function fetch_with_enrichment(
    +    _user_id text,
    +    _user_api user_api_type,        -- First API call
    +    _preferences_api prefs_api_type  -- Second API call
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    -- Both APIs are called before this function executes
    +    -- Results are available in _user_api and _preferences_api
    +    return json_build_object(
    +        'user', (_user_api).body::json,
    +        'preferences', (_preferences_api).body::json
    +    );
    +end;
    +$$;

    POST Requests with Bodies

    HTTP Types support all methods including POST with request bodies:

    sql
    sql
    comment on type webhook_api is 'POST https://hooks.example.com/notify
    +Content-Type: application/json
    +Authorization: Bearer {_webhook_token}
    +@timeout 5s
    +
    +{"event": "{_event_type}", "data": {_payload}}';

    Response Field Customization

    Configure field names in your composite type:

    Field NameTypeDescription
    bodytext or jsonbResponse body content
    status_codeintHTTP status code
    headersjsonResponse headers
    content_typetextContent-Type header
    successbooleanTrue for 2xx status codes
    error_messagetextError description if failed

    Using jsonb for the body field (as we did) avoids explicit casting in your function.

    Retry Logic

    External APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. The @retry_delay directive adds automatic retries:

    sql
    sql
    -- Retry 3 times with increasing delays, only on 429 and 503:
    +comment on type exchange_rate_api is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';

    The delay list defines both the number of retries and the delay before each. Without the on filter, retries happen on any failure. See HTTP Client Options for full details.

    Resolved Parameter Expressions

    Sensitive values like API tokens can be resolved server-side via SQL, keeping secrets out of client requests entirely:

    sql
    sql
    comment on type paid_api is 'GET https://api.example.com/premium/{_query}
    +Authorization: Bearer {_token}
    +@timeout 10s';
    +
    +create function search_premium(
    +    _query text,
    +    _user_id int,
    +    _req paid_api,
    +    _token text default null
    +) returns json ...
    +
    +comment on function search_premium(text, int, paid_api, text) is '
    +_token = select api_token from user_tokens where user_id = {_user_id}
    +';

    The client calls GET /api/search-premium/?query=test&user_id=42. The server resolves _token from the database and substitutes it into the Authorization header — the token never leaves the server. See HTTP Client Options for full details.

    Timeout Configuration

    Multiple timeout formats are supported:

    sql
    sql
    -- Seconds (integer)
    +comment on type api is '@timeout 30
    +GET https://api.example.com/data';
    +
    +-- With suffix
    +comment on type api is 'GET https://api.example.com/data
    +@timeout 30s';
    +
    +-- TimeSpan format
    +comment on type api is 'GET https://api.example.com/data
    +@timeout 00:00:30';

    When to Use HTTP Types

    HTTP Types are ideal for:

    • API Aggregation - Combine multiple external APIs into one response
    • Data Enrichment - Augment database records with external data
    • Webhook Triggers - Call external services as part of business logic
    • Third-Party Integrations - Payment processors, notification services, etc.
    • Microservice Communication - Call other services in your architecture

    Consider alternatives for:

    • High-frequency polling - Use dedicated background services
    • Streaming data - WebSockets or SSE are better suited

    Conclusion

    HTTP Types reduce external API integration to a type comment: no client libraries, no PostgreSQL extensions, no service classes, no manual type definitions. The Financial Dashboard aggregates two external APIs in about 80 lines of SQL where the traditional approach needs 250+ lines across multiple files, plus dependencies, plus manual TypeScript types. They're the wrong tool for high-frequency polling or streaming - use background services or SSE for those - but for request-scoped API aggregation, the code you don't write has no bugs.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 000000000..4900193e6 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,36 @@ + + + + + + Blog | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Blog Posts & Tutorials

    Comments

    + + + + \ No newline at end of file diff --git a/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html b/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html new file mode 100644 index 000000000..7a9c178d3 --- /dev/null +++ b/blog/mcp-server-postgresql-ai-tools-npgsqlrest.html @@ -0,0 +1,79 @@ + + + + + + Turn PostgreSQL into MCP Tools an AI Agent Can Call | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Turn PostgreSQL into MCP Tools an AI Agent Can Call

    MCP · AI Agents · Model Context Protocol · PostgreSQL · v3.17.0 · June 2026


    You already write the operations your application needs as PostgreSQL functions or .sql files. NpgsqlRest turns them into a typed REST API for your frontend. As of 3.17.0, the same routines can also be MCP tools — so an AI agent can discover them and call them directly, with no separate tool layer to write and keep in sync.

    One annotation does it:

    sql
    sql
    /*
    +HTTP GET
    +@param $1 query text default null
    +@param $2 maxPrice numeric default null
    +@mcp Search the product catalog by name or category and/or a maximum price.
    +*/
    +select id, name, category, price, stock
    +from products
    +where ($1 is null or name ilike '%' || $1 || '%' or category ilike '%' || $1 || '%')
    +  and ($2 is null or price <= $2)
    +order by price;

    HTTP GET makes it a REST endpoint; @mcp makes it a tool. The two are independent, so this file is one PostgreSQL routine exposed through two interfaces — a typed /api/search-products call for your UI, and a search_products tool for an agent — from a single source of truth.

    This post walks through example 15, an "Acme Store" built entirely from .sql files, and ends with a real Claude agent driving the store autonomously.

    What MCP is, in one paragraph

    The Model Context Protocol (spec 2025-11-25) is how an AI agent talks to external capabilities. The agent asks a server "what can you do?" (tools/list), gets back a list of tools — each with a JSON-Schema describing its arguments (inputSchema) — and then calls one (tools/call) with arguments it fills in itself. It's a thin JSON-RPC contract. NpgsqlRest implements the server side over a single Streamable-HTTP endpoint (/mcp), and core stays protocol-agnostic — the whole MCP layer lives in a separate NpgsqlRest.Mcp plugin.

    Opt-in, never automatic

    A routine becomes a tool only when its comment carries @mcp. Nothing is exposed by accident:

    AnnotationEffect
    @mcpExpose as a tool; description comes from the comment prose
    @mcp <text>Expose, with <text> as the description
    @mcp_description <text>Explicit, authoritative description (suppresses comment prose)
    @mcp_name <name>Override the tool name (default: the routine name)

    The argument schema is derived from the routine's parameters@param $2 maxPrice numeric default null becomes an optional maxPrice: number in the tool's inputSchema, camel-cased, nullable-aware. The return columns become an outputSchema. You don't hand-write either.

    Results are structured

    tools/call runs the routine through the same pipeline as the HTTP endpoint and returns structuredContent — always a JSON object, shaped to the result:

    • a single scalar → { "value": 42 }
    • a record / a set collapsed with @single → the object itself
    • a set of rows → { "items": [ … ] }

    Business failures (an over-order tripping a CHECK constraint) come back as isError: true in the result; structural problems (unknown tool, malformed request) are JSON-RPC errors. Agents can tell the difference.

    MCP-only tools: a tool with no REST route

    Because the HTTP tag and @mcp are independent, a bare @mcp with no HTTP tag is an MCP-only tool — it exists for agents but has no public REST route at all. The declaration can be this small:

    sql
    sql
    /*
    +@mcp
    +
    +Low-stock report (3 or fewer in stock); staff/agent-only.
    +*/
    +select name, category, stock from products where stock <= 3 order by stock;

    The @mcp line opts the routine in; the plain prose beneath it becomes the tool's description — the text the model reads in tools/list to decide when to reach for this tool (the keyword is also case-insensitive and the leading @ is optional, so mcp or MCP work too). That's the whole declaration. tools/list includes inventory_report, but GET /api/inventory-report returns 404. Opting a routine into MCP never silently widens your HTTP surface, and the generated TypeScript client and OpenAPI document correctly leave MCP-only routines out.

    One source, two interfaces — made visible

    Example 15's web page drives the same .sql files two ways, side by side:

    • Left — a real storefront over REST. Search, filter, Buy, Cancel — built on the generated, typed TypeScript client (sqlApi.ts) that NpgsqlRest emits from the .sql files at startup. The UI calls searchProducts(...) and placeOrder(...) as typed functions and never hand-writes a URL.
    • Right — what an AI agent sees over MCP. A live MCP client running initializetools/listtools/call, rendering a call-form per tool straight from its advertised inputSchema.

    place_order.sql is a typed REST function on the left and an MCP tool on the right. One routine, two consumers, zero duplicated code.

    The real test: an AI agent driving the store

    A browser form is one thing; a model choosing tools on its own is the actual validation. The example ships a small, dependency-free agent (agent.ts) that hands the MCP tools to Claude and executes whatever it decides to call:

    bash
    bash
    export ANTHROPIC_API_KEY=sk-ant-...
    +bun run agent "find peripherals under \$100, order one of the cheapest, then show me what's low on stock"
    text
    text
    ── connected to Acme Store v1.0.0 · 8 tools ──
    +🧑 find peripherals under $100, order one of the cheapest, then show me what's low on stock
    +
    +🔧 search_products({"query":"Peripherals","maxPrice":100})
    +   → {"items":[{"id":5,"name":"Ergonomic Mouse","category":"Peripherals","price":59.90,"stock":25}]}
    +
    +🔧 place_order({"productId":5,"quantity":1})
    +   → {"orderId":1,"product":"Ergonomic Mouse","quantity":1,"total":59.90,"status":"placed"}
    +
    +🔧 inventory_report({})
    +   → {"items":[{"name":"Wireless Charger Pad","stock":0},{"name":"1080p Webcam","stock":2}, …]}
    +
    +🤖 Done — I ordered 1 Ergonomic Mouse ($59.90, order #1). Low stock: the Wireless Charger Pad
    +   is out of stock, and the 1080p Webcam (2) is running low.
    +── done ──

    The whole pipeline ran with a real model in the loop: your @mcp SQL files → inputSchema → Claude's tool selection → tools/call → PostgreSQL → result → Claude. The server's configured Instructions become the agent's system prompt, so you steer behavior from config, not code.

    Authorization, without locking down the server

    Tools run through the same pipeline as HTTP endpoints, so the routine's own @authorize check applies on tools/call — and you can gate a single privileged tool without forcing everyone else to authenticate. Example 15 makes only restock_product manager-only:

    sql
    sql
    /*
    +HTTP POST
    +@param $1 productId int
    +@param $2 newStock int
    +@single
    +@authorize manager
    +@mcp Set a product's stock to an exact value. Manager-only — a privileged, mutating operation.
    +*/
    +update products set stock = $2 where id = $1
    +returning id, name, category, stock;

    With JWT enabled but RequiresAuthorization left off, the store stays anonymous to browse and the agent keeps working — only this one tool refuses:

    • no token → 401
    • a staff-role token → 403
    • a manager token → success

    …and identically over REST (POST /api/restock-product), because it's the same routine. The challenge is enforced exactly as the MCP authorization model prescribes: NpgsqlRest acts as an OAuth 2.1 Resource Server (it validates tokens and serves Protected Resource Metadata so a client can discover your Authorization Server) — bring your own IdP (Keycloak, Auth0, Entra…) or use NpgsqlRest's own JWT login, as the example does.

    Why this approach holds up

    • No tool layer to maintain. The tool, its argument schema, and its result shape are all derived from the routine you already wrote. Change the SQL, the tool changes with it.
    • One source of truth. REST and MCP are two projections of the same routine — they can't drift apart.
    • Opt-in and safe by default. Nothing is a tool until you say @mcp; MCP-only tools never appear as REST routes or in generated clients.
    • AOT-safe. The JSON-RPC layer is hand-rolled over System.Text.Json.Nodes with no reflection — verified under dotnet publish -p:PublishAot=true.

    Try it

    The complete, runnable example — .sql tools, the dual-panel web page, the Claude agent, and the authorization demo — is example 15 on GitHub:

    bash
    bash
    cd examples/15_mcp_server
    +bun run db:up    # schema + seed data
    +bun run build    # bundle the web client
    +bun run dev      # start the server

    Then point MCP Inspector (npx @modelcontextprotocol/inspector, transport Streamable HTTP, URL http://127.0.0.1:8080/mcp) or Claude Desktop at the endpoint — or just run the agent.

    Further reading: the MCP configuration reference and the @mcp annotation docs.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/multiple-auth-schemes-rbac-external-providers.html b/blog/multiple-auth-schemes-rbac-external-providers.html new file mode 100644 index 000000000..16b7f4fbf --- /dev/null +++ b/blog/multiple-auth-schemes-rbac-external-providers.html @@ -0,0 +1,394 @@ + + + + + + Multiple Auth Schemes, RBAC, and External OAuth Providers | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Multiple Authentication Schemes, Role-Based Access Control, and External Providers

    January 2026 · SecurityAuthenticationOAuthNpgsqlRest

    In the previous post, we built a secure authentication system using PostgreSQL's pgcrypto extension for password hashing. That approach works well, but NpgsqlRest can go further: a built-in password verification system, multiple authentication schemes, role-based access control, and integration with external OAuth providers.

    This post walks through an authentication example that combines four pieces:

    1. Built-in Password Hasher - NpgsqlRest's pluggable password verification with verification callbacks
    2. Multiple Authentication Schemes - Cookies, Bearer tokens, and JWT all working together
    3. Role-Based Access Control (RBAC) - Restricting endpoints to specific roles
    4. External OAuth Providers - Google login with zero password management

    Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/4_passwords_tokens_roles

    Why NpgsqlRest's Built-in Password Hasher?

    The pgcrypto approach from the previous post hashes passwords in SQL, on the database server. The built-in hasher moves that work into NpgsqlRest, and the differences add up:

    pgcrypto ApproachNpgsqlRest Built-in Hasher
    Hashing logic in SQLHashing handled by NpgsqlRest
    Requires pgcrypto extensionNo extension needed
    Must return hash for comparisonReturns hash, NpgsqlRest verifies automatically
    Custom verification requires manual implementationBuilt-in verification callbacks via config
    bcrypt with 72-byte limit (needs workaround)PBKDF2-SHA256 with no length limit
    bcrypt onlyPluggable - can use Argon2 or any .NET algorithm
    Runs on database serverRuns on application server
    Generate hashes with SQL functionsGenerate hashes with CLI or auto-hash parameters

    Why process passwords on the application server? When your database and application servers are separate (as they should be in production), every CPU cycle on the database server counts. Password hashing is intentionally CPU-intensive - offloading it to the application server keeps your database responsive for queries.

    Pluggable algorithms: The default is PBKDF2, but since NpgsqlRest runs on .NET, you can plug in any password hasher available in the .NET ecosystem - including Argon2, scrypt, or custom implementations. This requires a custom build but gives you flexibility as security standards evolve.

    The default built-in hasher uses PBKDF2 (Password-Based Key Derivation Function 2) with:

    • SHA-256 algorithm
    • 128-bit salt
    • 600,000 iterations (OWASP-recommended as of 2025)

    Schema Design

    The schema is straightforward - a users table with optional password hash:

    sql
    sql
    -- V1__example_4_schema.sql
    +
    +create table example_4.users (
    +    user_id int primary key generated always as identity,
    +    username text not null,
    +    email text not null,
    +    roles text[] not null,
    +    password_hash text null,  -- null when using external auth only
    +    last_login timestamp with time zone null,
    +    last_login_provider text null
    +);

    Note that password_hash is nullable - users authenticating only via external providers (like Google) don't need a password.

    Generating Password Hashes

    Use the NpgsqlRest CLI to generate hashes:

    bash
    bash
     npgsqlrest --hash password123
    +RfpqB6nKcoT2lL/w4ItB24mvxg8R9rC906C0/+7DAI62PQayBWjqihU96XPzmzYu
    +
    + npgsqlrest --hash password456
    +X+e/OsZkNL4j/9a7WIy/2bkQDk4rHHwlFwLXx7MNpclUUPdtQlI1JiDqyqMnJbgu

    Insert users with pre-generated hashes:

    sql
    sql
    insert into example_4.users (username, email, roles, password_hash) values
    +-- alice is a normal user
    +('alice', 'alice@example.com', array['user'],
    +    'RfpqB6nKcoT2lL/w4ItB24mvxg8R9rC906C0/+7DAI62PQayBWjqihU96XPzmzYu'),
    +-- bob is an admin
    +('bob', 'bob@example.com', array['user', 'admin'],
    +    'X+e/OsZkNL4j/9a7WIy/2bkQDk4rHHwlFwLXx7MNpclUUPdtQlI1JiDqyqMnJbgu'),
    +-- carol has no roles
    +('carol', 'carol@example.com', array[]::text[],
    +    '3XBVW23Yn6j8b8sRQMoerOvSYlFosXuRrY0G/nkquuquDNdSnbn8bacvCQlCQKhs');

    Automatic Parameter Hashing for Registration

    For user registration endpoints, NpgsqlRest can automatically hash password parameters before they reach your function. Configure PasswordParameterNameContains to specify which parameters should be hashed:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "PasswordParameterNameContains": "password"
    +    }
    +  }
    +}

    Any parameter containing "password" in its name will be automatically hashed using the same built-in hasher. Your registration function receives the hash directly - no hashing logic needed in SQL.

    Multiple Authentication Schemes

    NpgsqlRest supports multiple authentication schemes simultaneously. This example configures three:

    json
    json
    {
    +  "Auth": {
    +    // Scheme 1: Cookie-based authentication
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieValidDays": 1,
    +    "CookieName": "example_4_auth",
    +
    +    // Scheme 2: Microsoft Bearer Token
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": "token",
    +    "BearerTokenExpireHours": 1,
    +    "BearerTokenRefreshPath": "/api/token/refresh",
    +
    +    // Scheme 3: JWT
    +    "JwtAuth": true,
    +    "JwtAuthScheme": "jwt",
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "example_4",
    +    "JwtAudience": "example_4",
    +    "JwtExpireMinutes": 60,
    +    "JwtRefreshExpireDays": 7,
    +    "JwtRefreshPath": "/api/jwt/refresh"
    +  }
    +}

    Each scheme has a unique name (cookies, token, jwt) that the login function returns to indicate which scheme to use.

    A Note on Data Protection and Encryption

    Both Cookies and Microsoft Bearer Tokens are encrypted by default using ASP.NET Core's Data Protection system. Unlike JWT (which is signed but readable), both cookies and Bearer tokens are fully encrypted - the client cannot inspect their contents.

    The Microsoft Bearer Token is a proprietary format, while the cookie encryption uses standard Data Protection mechanisms. Both rely on the same key management infrastructure. For production deployments, you should store these keys in the database so they persist across application restarts:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select example_4.get_data_protection_keys()",
    +    "StoreElementCommand": "call example_4.store_data_protection_keys($1,$2)"
    +  }
    +}

    With supporting SQL:

    sql
    sql
    create table example_4.auth_data_protection_keys (
    +    name text not null primary key,
    +    data text not null
    +);
    +
    +create function example_4.get_data_protection_keys()
    +returns setof text
    +language sql
    +begin atomic;
    +select data from example_4.auth_data_protection_keys;
    +end;
    +
    +create procedure example_4.store_data_protection_keys(
    +    _name text,
    +    _data text
    +)
    +language sql
    +begin atomic;
    +insert into example_4.auth_data_protection_keys (name, data)
    +values (_name, _data)
    +on conflict (name) do update set data = excluded.data;
    +end;

    Without database storage, keys are stored in memory and lost on restart - invalidating all existing cookies and tokens, forcing users to log in again. With database storage, encrypted authentication keeps working across application restarts.

    For full details on authentication configuration, see:

    The Login Function with Built-in Password Verification

    The login function returns the password_hash column - NpgsqlRest automatically verifies it:

    sql
    sql
    -- R__example_4_login.sql
    +
    +create or replace function example_4.login(
    +    _scheme text,
    +    _username text,
    +    _password text
    +)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    roles text[],
    +    email text,
    +    password_hash text  -- NpgsqlRest verifies this automatically
    +)
    +language sql
    +set search_path = pg_catalog, pg_temp
    +begin atomic;
    +select
    +    _scheme,  -- Can be 'cookies', 'token', or 'jwt'
    +    user_id,
    +    username,
    +    roles,
    +    email,
    +    password_hash
    +from example_4.users
    +where
    +    username = _username;
    +end;
    +
    +comment on function example_4.login(text, text, text) is '
    +HTTP POST
    +@login
    +@anonymous';

    Key points:

    1. The _scheme parameter lets clients choose which authentication method to use
    2. The function returns password_hash - NpgsqlRest verifies it against _password
    3. If verification fails, NpgsqlRest returns 404 Not Found (not 401, to avoid leaking whether users exist)

    See the login annotation documentation for full details on the built-in password hasher.

    Password Verification Callbacks

    Configure callbacks for successful and failed password verification:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "HashColumnName": "password_hash",
    +      "PasswordVerificationFailedCommand": "call example_4.password_verification_failed($1, $2, $3)",
    +      "PasswordVerificationSucceededCommand": "call example_4.password_verification_succeeded($1, $2, $3)"
    +    }
    +  }
    +}

    The callbacks receive the scheme, user ID, and username:

    sql
    sql
    -- R__example_4_password_verification_succeeded.sql
    +
    +create or replace procedure example_4.password_verification_succeeded(
    +    _scheme text,
    +    _user_id text,
    +    _user_name text
    +)
    +language plpgsql
    +set search_path = pg_catalog, pg_temp
    +as
    +$$
    +begin
    +    -- Update last login timestamp
    +    update example_4.users
    +    set
    +        last_login = now(),
    +        last_login_provider = _scheme
    +    where user_id = _user_id::int;
    +
    +    raise notice 'Password verification succeeded for user % (ID: %) using scheme %',
    +        _user_name, _user_id, _scheme;
    +end;
    +$$;
    sql
    sql
    -- R__example_4_password_verification_failed.sql
    +
    +create or replace procedure example_4.password_verification_failed(
    +    _scheme text,
    +    _user_id text,
    +    _user_name text
    +)
    +language plpgsql
    +set search_path = pg_catalog, pg_temp
    +as
    +$$
    +begin
    +    -- Log failed attempt, increment counters, implement lockout, etc.
    +    raise warning 'Password verification failed for user % (ID: %) using scheme %',
    +        _user_name, _user_id, _scheme;
    +end;
    +$$;

    These callbacks are the only way to know whether NpgsqlRest's built-in password verification succeeded or failed - use them for account lockout, audit logging, or failed-attempt counting.

    Role-Based Access Control

    RBAC is implemented through the authorize annotation with role names:

    sql
    sql
    -- R__example_4_get_users.sql
    +
    +create or replace function example_4.get_users()
    +returns table (
    +    users example_4.users,
    +    is_this_me boolean
    +)
    +set search_path = pg_catalog, pg_temp
    +language sql
    +begin atomic;
    +select
    +    (u.*)::example_4.users,
    +    (u.user_id = nullif(pg_catalog.current_setting('request.user_id', true), '')::int) is true
    +from example_4.users u;
    +end;
    +
    +comment on function example_4.get_users() is '
    +HTTP GET
    +@authorize admin';  -- Only admin role can access

    The authorize admin annotation restricts this endpoint to users with the admin role. Users without this role receive 403 Forbidden.

    Compare these authorization levels:

    • authorize - Any authenticated user
    • authorize admin - Only users with admin role
    • authorize admin, manager - Users with admin OR manager role

    User Context with current_setting

    Instead of user parameters, this example uses PostgreSQL's current_setting to access user claims:

    sql
    sql
    create function example_4.who_am_i()
    +returns example_4.who_am_i_response
    +set search_path = pg_catalog, pg_temp
    +language sql
    +begin atomic;
    +select
    +    nullif(pg_catalog.current_setting('request.user_id', true), '')::int as user_id,
    +    nullif(pg_catalog.current_setting('request.username', true), '') as username,
    +    nullif(pg_catalog.current_setting('request.email', true), '') as email,
    +    nullif(pg_catalog.current_setting('request.roles', true), '')::text[] as roles,
    +    last_login,
    +    last_login_provider
    +from example_4.users
    +where user_id = nullif(pg_catalog.current_setting('request.user_id', true), '')::int;
    +end;

    Configuration maps claims to settings:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.username": "username",
    +        "request.email": "email",
    +        "request.roles": "roles"
    +      }
    +    }
    +  }
    +}

    See User Context Settings for details on this approach vs. user parameters.

    External OAuth Providers

    Who needs passwords at all? An external OAuth provider takes a dozen lines of config:

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "SigninUrl": "/signin-{0}",
    +      "LoginCommand": "select * from example_4.external_login($1,$2,$3,$4,$5)",
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      }
    +    }
    +  }
    +}

    That's it. Users can now visit /signin-google to authenticate via Google.

    The External Login Function

    When OAuth completes, NpgsqlRest calls your login command with the provider info:

    sql
    sql
    -- R__example_4_external_login.sql
    +
    +create or replace function example_4.external_login(
    +    _provider text,      -- e.g., "google"
    +    _email text,         -- User's email from provider
    +    _name text,          -- User's display name
    +    _provider_data json, -- Raw data from OAuth provider
    +    _analytics_data json -- Browser analytics (screen size, timezone, etc.)
    +)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    roles text[],
    +    email text
    +)
    +language plpgsql
    +set search_path = public, pg_catalog
    +as
    +$$
    +declare
    +    _user_id int;
    +begin
    +    return query
    +    select
    +        'cookies' as scheme,  -- External logins use cookies by default
    +        u.user_id,
    +        u.username,
    +        u.roles,
    +        u.email
    +    from example_4.users u
    +    where u.username = _email;  -- Match by email
    +
    +    if not found then
    +        raise warning 'Could not find user with email % for provider %',
    +            _email, _provider;
    +    else
    +        _user_id = (
    +            select u.user_id
    +            from example_4.users u
    +            where u.username = _email
    +        );
    +
    +        update example_4.users
    +        set
    +            last_login = now(),
    +            last_login_provider = _provider
    +        where example_4.users.user_id = _user_id;
    +    end if;
    +end
    +$$;

    The function uses the same conventions as the regular login annotation - return a named record with scheme and claim columns.

    For full details on external authentication, see the External OAuth Authentication documentation. NpgsqlRest supports Google, GitHub, LinkedIn, Microsoft, Facebook, and custom OAuth providers.

    The Demo Application

    The example includes a web interface demonstrating all authentication methods:

    html
    html
    <div id="login-form">
    +    <h2>Login</h2>
    +    <input type="text" id="username" placeholder="Username" />
    +    <input type="password" id="password" placeholder="Password" />
    +
    +    <div style="margin: 8px 0;">
    +        <label><strong>Auth Scheme:</strong></label>
    +        <label><input type="radio" name="scheme" value="cookies" checked /> Cookies</label>
    +        <label><input type="radio" name="scheme" value="token" /> Bearer Token</label>
    +        <label><input type="radio" name="scheme" value="jwt" /> JWT</label>
    +    </div>
    +
    +    <button id="login-btn">Login</button>
    +
    +    <a href="/signin-google">Login with Google (Cookies)</a>
    +</div>
    +
    +<div id="actions">
    +    <button id="whoami-btn">Who Am I?</button>
    +    <button id="getusers-btn">Get Users (Admin)</button>
    +    <button id="logout-btn">Logout</button>
    +</div>

    Test users:

    • alice (password: password123) - has user role
    • bob (password: password456) - has user and admin roles
    • carol (password: password789) - has no roles

    Try logging in as alice and clicking "Get Users (Admin)" - you'll get 403 Forbidden. Log in as bob and it works.

    Configuration Summary

    The complete configuration enables all features:

    json
    json
    {
    +  "ApplicationName": "4_passwords_tokens_roles",
    +
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieValidDays": 1,
    +
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": "token",
    +    "BearerTokenExpireHours": 1,
    +    "BearerTokenRefreshPath": "/api/token/refresh",
    +
    +    "JwtAuth": true,
    +    "JwtAuthScheme": "jwt",
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtExpireMinutes": 60,
    +    "JwtRefreshPath": "/api/jwt/refresh",
    +
    +    "External": {
    +      "Enabled": true,
    +      "LoginCommand": "select * from example_4.external_login($1,$2,$3,$4,$5)",
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      }
    +    }
    +  },
    +
    +  "NpgsqlRest": {
    +    "IncludeSchemas": [ "example_4" ],
    +    "RequiresAuthorization": true,
    +
    +    "AuthenticationOptions": {
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "username",
    +      "DefaultRoleClaimType": "roles",
    +
    +      "HashColumnName": "password_hash",
    +      "PasswordVerificationFailedCommand": "call example_4.password_verification_failed($1, $2, $3)",
    +      "PasswordVerificationSucceededCommand": "call example_4.password_verification_succeeded($1, $2, $3)",
    +
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.username": "username",
    +        "request.email": "email",
    +        "request.roles": "roles"
    +      }
    +    },
    +
    +    "ClientCodeGen": {
    +      "FilePath": "./4_passwords_tokens_roles/src/{0}Api.ts",
    +      "IncludeParseRequestParam": true
    +    }
    +  }
    +}

    Generated Client with Token Support

    The IncludeParseRequestParam: true option generates API functions that accept an optional parseRequest callback. This allows you to inject authorization headers for Bearer token or JWT authentication:

    typescript
    typescript
    // Add Authorization header for token-based auth
    +function parseRequest(request: RequestInit): RequestInit {
    +    if (!authToken) return request;
    +
    +    const headers = new Headers(request.headers);
    +    headers.set("Authorization", `Bearer ${authToken}`);
    +    return { ...request, headers };
    +}
    +
    +// Pass the callback to any API call
    +const response = await whoAmI(parseRequest);
    +const users = await getUsers(parseRequest);

    For cookie authentication, no callback is needed - cookies are sent automatically by the browser. But for Bearer tokens or JWT, you need to add the Authorization header manually, and IncludeParseRequestParam makes this trivial.

    Conclusion: Enterprise Auth Made Simple

    Modern authentication and authorization is notoriously complex. A production-ready system typically requires:

    • Multiple authentication schemes (cookies for web, tokens for APIs, JWT for microservices)
    • Secure password storage with modern algorithms
    • Role-based access control with claim management
    • OAuth integration with external providers
    • Account lockout and audit logging
    • Token refresh mechanisms
    • Session management

    Implementing all of this traditionally takes multiple libraries and deep security expertise - and getting it wrong means vulnerabilities.

    With NpgsqlRest, all of this is configuration and a few SQL functions.

    What this example actually required:

    ComponentLines of Code
    Schema + users table~15 lines SQL
    Login function~15 lines SQL
    Logout function~8 lines SQL
    Who Am I function~15 lines SQL
    Role-restricted endpoint~10 lines SQL
    External login function~25 lines SQL
    Verification callbacks~20 lines SQL
    Configuration~50 lines JSON
    Total~160 lines

    That's it. Under 200 lines for a complete authentication system with:

    • Three authentication schemes (cookies, Bearer tokens, JWT)
    • Built-in password verification with OWASP-compliant hashing
    • Role-based access control
    • Google OAuth integration
    • Verification callbacks for security features
    • Token refresh endpoints
    • User context in every request

    Compare this to a typical Node.js/Express or Spring Boot implementation - you'd be looking at thousands of lines of code, multiple dependencies, and weeks of development time.

    This Blog Post is Your Recipe

    Use this example as a template:

    1. Copy the schema - Adapt the users table to your needs
    2. Copy the configuration - Enable the schemes you need, add your OAuth credentials
    3. Write your login function - Return the scheme and claims you want
    4. Add role annotations - authorize admin on endpoints that need it
    5. Done - You have production-ready authentication

    The code you don't write has no bugs, and authentication is exactly where you want fewer of them.

    Combined with database-level security (Principle of Least Privilege, SECURITY DEFINER, search path protection) and end-to-end type safety, you get a complete, secure, performant application stack that would take months to build from scratch.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/npgsqlrest-3.13-production-patterns.html b/blog/npgsqlrest-3.13-production-patterns.html new file mode 100644 index 000000000..11c4b48cf --- /dev/null +++ b/blog/npgsqlrest-3.13-production-patterns.html @@ -0,0 +1,110 @@ + + + + + + NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    NpgsqlRest 3.13.0: Cache Profiles, Auth Schemes, Per-User Rate Limits, and pgBouncer Compatibility

    April 2026 · v3.13.0CachingAuthRate LimitingMulti-Tenancy

    NpgsqlRest 3.13.0 gives first-class declarative support to four production scenarios that previously required custom middleware, external services, or hacky workarounds:

    1. Caching that adapts to query inputs — historical data cached for hours, "open-ended" data cached briefly, real-time queries bypassing the cache entirely.
    2. Short-lived sensitive sessions alongside a normal long-lived session, e.g., for recovery-code or admin flows.
    3. Per-user rate limits instead of global buckets shared by all users.
    4. pgBouncer / RDS Proxy / Supabase Pooler compatibility with multi-tenant search_path driven by JWT claims.

    All four patterns ship as configuration, not code. Walkthroughs below; full reference and migration notes in the v3.13.0 changelog.

    1. Conditional Caching: Historical vs Current vs Live

    A common analytics endpoint has parameters like from, to, and live. The right caching policy depends on which combination the client sends:

    • Both from and to are set → query covers a closed historical range; results are immutable. Cache for hours.
    • to is null → "open-ended" range covering up to now; results change as new rows arrive. Short TTL — say, 5 minutes, matching the upstream refresh cadence.
    • live = true → real-time mode, must always fetch fresh.

    Before 3.13, this either meant three different endpoints or imperative cache logic in SQL. Now it's a single profile with When rules. Here's the full CacheOptions block backed by Redis (so cache state survives restarts and is shared across multiple NpgsqlRest instances behind a load balancer):

    jsonc
    jsonc
    "CacheOptions": {
    +  "Enabled": true,
    +  "Type": "Redis",
    +  "RedisConfiguration": "redis-server:6379,password={REDIS_PASSWORD},ssl=true,abortConnect=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
    +  "MaxCacheableRows": 1000,
    +  "UseHashedCacheKeys": true,
    +  "HashKeyThreshold": 256,
    +  "InvalidateCacheSuffix": "invalidate",
    +  "Profiles": {
    +    "timeseries_compute": {
    +      "Enabled": true,
    +      "Type": "Redis",
    +      "Expiration": "1 hour",
    +      "Parameters": ["from", "to", "live"],
    +      "When": [
    +        { "Parameter": "live", "Value": true,  "Then": "skip" },
    +        { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
    +      ]
    +    }
    +  }
    +}

    Notes on the top-level fields:

    • Type: "Redis" at the root makes Redis the default backend for endpoints that don't specify a profile.
    • RedisConfiguration — full StackExchange.Redis connection string. The {REDIS_PASSWORD} placeholder is resolved from environment variables; ssl=true is recommended for production.
    • MaxCacheableRows: 1000 caps which set-returning results get cached — anything larger is still returned, just not cached, so a runaway query doesn't blow up Redis.
    • UseHashedCacheKeys: true with HashKeyThreshold: 256 hashes long keys to a fixed SHA-256 string. Important for Redis when routines have many or large parameters — keeps memory and network overhead bounded.
    • InvalidateCacheSuffix: "invalidate" automatically generates a companion endpoint at /api/compute-timeseries/invalidate that clears the cache entry for the same parameters, with the same auth requirements.

    Profiles share the root backend pool — both root and timeseries_compute use a single Redis connection, not two. The profile-name prefix on cache keys keeps entries isolated even when multiple profiles share a backend.

    sql
    sql
    comment on function compute_timeseries(
    +  from text,
    +  to text default null,
    +  live boolean default false
    +) is 'HTTP GET
    +@cache_profile timeseries_compute';

    How rules evaluate (first match wins):

    RequestMatchesResult
    ?from=2025-01-01&to=2025-12-31noneprofile default → cached 1 hour
    ?from=2025-01-01 (to omitted)to=nullcached 5 minutes
    ?from=2025-01-01&live=truelive=truebypass cache entirely

    The Then field accepts the literal "skip" (bypass cache) or any PostgreSQL interval string (override TTL). Because Parameters is ["from", "to", "live"], both rule-matched paths and the default-TTL path get separate cache entries — live=true requests never poison the historical cache.

    See Cache Options → Cache Profiles for the full rule semantics, validation rules, and backend pooling notes.

    2. Short-Lived Sensitive Session Alongside the Normal Session

    A user is signed in with a normal 14-day cookie. Now they want to view recovery codes or change their password. You don't want the recovery-code page accessible for 14 days from any device that ever signed in — you want a fresh, short-lived authentication that expires when the browser closes.

    Before 3.13, this meant either re-prompting for the password and just trusting it, or building a separate "step-up" auth service. Now you register an additional cookie scheme:

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "14 days",
    +  "Schemes": {
    +    "short_session": {
    +      "Type": "Cookies",
    +      "Enabled": true,
    +      "CookieValid": "1 hour",
    +      "CookieMultiSessions": false
    +    }
    +  }
    +}

    Any field not overridden inherits from the root Auth section, so the override block stays small.

    The recovery-code step has its own login function that returns 'short_session' in the scheme column:

    sql
    sql
    create function recovery_step_up(_user_id int, _recovery_code text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'short_session' as scheme, user_id::text, username
    +  from users
    +  where user_id = _user_id
    +    and verify_recovery_code(recovery_code_hash, _recovery_code);
    +$$;

    The endpoint that displays the recovery codes requires this specific scheme:

    sql
    sql
    comment on function show_recovery_codes() is 'HTTP GET
    +@authorize short_session';

    CookieMultiSessions: false makes the cookie session-only (no Max-Age), so closing the browser invalidates it. The 1-hour CookieValid server-side cap means even a left-open tab stops working after an hour. The user's normal 14-day session is unaffected.

    Same pattern works for admin areas, payment flows, or any per-scope restriction. JWT and BearerToken types support the same overrides — see the per-type override fields table.

    3. Per-User Rate Limits

    Pre-3.13, a rate limiter policy was a single global bucket. PermitLimit: 100, WindowSeconds: 60 meant 100 requests per minute across all users combined, which is rarely what you want — one heavy user could exhaust the quota for everyone else.

    The new Partition block resolves a partition key per request and gives each key its own bucket:

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    }
    +  }
    +}
    sql
    sql
    comment on function user_dashboard() is 'HTTP GET
    +@authorize
    +@rate_limiter per_user';

    Sources are walked top-to-bottom; the first one that returns a non-empty value wins:

    RequestResolved keyBucket
    Authenticated user 42name_identifier=42per-user bucket
    Anonymous request from 203.0.113.5IpAddress=203.0.113.5per-IP bucket
    Anonymous, no IP visibleStatic=anonymousshared "anonymous" bucket

    BypassAuthenticated: true is also available — useful for "throttle anonymous only" patterns where signed-in users skip the limiter entirely.

    Behavior is unchanged for policies without a Partition block. See Per-User Rate Limiting for source types and validation rules.

    Breaking change

    RateLimiterOptions:Policies is now an object keyed by policy name, no longer an array of objects with "Name" properties. Migration is mechanical — see the changelog.

    4. Multi-Tenant search_path with pgBouncer (or RDS Proxy, or Supabase Pooler)

    A multi-tenant deployment where each tenant's data lives in its own schema, and the JWT carries a tenant_id claim. Each request needs search_path set so the routine sees the right tenant's tables.

    Without a connection pooler, set_config('search_path', tenant, false) works fine — the GUC sticks for the session and Npgsql's pool issues DISCARD ALL when the connection returns. With a transaction-mode pooler (pgBouncer transaction-pool, AWS RDS Proxy in transaction mode, Supabase Pooler), the same backend is reused for unrelated requests without session reset, so a search_path set in one request can leak into the next.

    3.13 introduces two cooperating options:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true,
    +    "BeforeRoutineCommands": [
    +      {
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +      }
    +    ]
    +  }
    +}

    WrapInTransaction: true wraps every request in BEGIN ... COMMIT and switches all set_config calls to is_local=true (transaction-scoped). On COMMIT, the GUC is discarded — the next request on the same backend gets a clean slate.

    BeforeRoutineCommands runs declared SQL after any context is set but before the main routine call, in the same NpgsqlBatch (no extra round-trip). Parameters are bound from HttpContext at request time — Source: "Claim" reads User.FindFirst("tenant_id"), and the value is passed as a SQL parameter (no string interpolation, no injection risk).

    Per-request execution order:

    1. BEGIN
    2. set_config('search_path', $1, true) with $1 bound to the claim value
    3. The routine call
    4. COMMIT

    Steps 1–3 share a single network round-trip. The routine sees the right search_path; nothing leaks between requests.

    BeforeRoutineCommands also accepts raw strings (no parameters) and supports Source: "RequestHeader" and Source: "IpAddress" for non-claim cases. See Connection Pooler Compatibility for the full schema.

    Other Notable Changes

    • 400 Bad Request responses now log at Warning level — previously silent in production. Database exceptions mapped to 400 (P0001, P0004) and validation rule failures both surface in logs by default.
    • Auth time fields use Postgres interval notationCookieValid: "14 days", JwtExpire: "60 minutes", etc. The legacy integer fields (CookieValidDays, JwtExpireMinutes, …) are removed; startup fails with a migration message if they're still in your config. Finer-grained durations (seconds, minutes) are now possible. See the migration table.
    • Docker images now build on Ubuntu 26.04 LTS — extending the security-update window from 9 months (25.04 interim) to 5 years.

    For the complete release notes including NuGet upgrades and minor fixes, see the v3.13.0 changelog.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html b/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html new file mode 100644 index 000000000..ece9f5ead --- /dev/null +++ b/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.html @@ -0,0 +1,115 @@ + + + + + + Tests Are SQL Files Too | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content

    Tests Are SQL Files Too

    July 2026 · NpgsqlRestPostgreSQLStoryTestingWatch Modev3.19.0

    Introduction

    Features in this last version 3.19.0 got me excited in a way I haven't been since 3.12.0 and I wanted to write about it personally. So let's do a quick recap:

    • Version 3.12.0 introduced the idea that a SQL file can be a REST endpoint. That was a big deal, and it changed how I write APIs.

    Major shift is that I don't have to deploy anything or run a migration to add a new endpoint. Just write a simple SQL file, like a script and that is it, NpgsqlRest happily creates the endpoint for you from that file. Nothing deployed, nothing migrated. You probably already have a bunch of SQL scripts sitting around. You just need to add a comment with the HTTP method and path, and NpgsqlRest will serve it as an endpoint:

    sql
    sql
    /*
    +HTTP GET
    +@param $1 from_date
    +@param $2 to_date
    +@authorize admin
    +*/
    +select id, title, created_at
    +from reports
    +where created_at between $1 and $2;

    That is it. Simple as it gets.

    1) Named Parameters in SQL Files

    Example above can now be written like this:

    sql
    sql
    /*
    +HTTP GET
    +@authorize admin
    +*/
    +select id, title, created_at
    +from reports
    +where created_at between :from_date and :to_date;

    Small but sweet improvement.

    2) Watch Mode

    Initially developed for the test runner, it was expanded during the development for all modes, including SQL file endpoints as well as function/procedure endpoints.

    It is particularly useful for SQL file endpoints, SQL files from the beginning included native parser validation against real database schema on startup which serves as a type checker for your SQL files. Now with watch mode, you can get that validation on every save, just as you type. This is big in terms of development experience.

    3) Native Test Runner

    And finally, the big one: native test runner. This implementation shares the same approach as SQL file endpoints, meaning you can write a test in a SQL file, and HTTP blocks in comments will be executed against the real endpoint, in-process, inside the test's own transaction. The fixture rolls back. Nothing is left behind. The full walkthrough is in the Testing Guide.

    Let's go into more details.

    TL;DR Test Runner

    Quick example of a test file:

    sql
    sql
    -- optional setup to ensure isolation
    +begin;
    +
    +-- ARRANGE: insert a fixture user into the database
    +
    +insert into users (email) 
    +values ('fixture@example.com');
    +
    +-- ACT: call the real endpoint, in-process, inside the test's own transaction and simulate an authenticated request with a claim user_id=1
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +
    +-- ASSERT: assert on the response
    +
    +select status = 200, 'authenticated caller gets 200' 
    +from _response;
    +
    +select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture is listed' 
    +from _response;
    +
    +-- cleanup: rollback the transaction to clean up the fixture and keep isolation for the next test
    +rollback;
    console
    console
    $ npgsqlrest ./config.json --test
    +
    +PASS  tests/get_users.test.sql  (2 assertions, 52ms)
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +endpoint coverage: 2/2 (100%)

    That's the real endpoint — routing, authorization, parameter binding, JSON serialization — invoked in-process, inside the test's own transaction. The fixture rolls back. Nothing is left behind. The full walkthrough is in the Testing Guide.

    And here is something worth saying up front: you don't need NpgsqlRest endpoints to use this. The HTTP blocks are optional. Boolean-SELECT assertions and DO-block asserts against your functions, views, and schema work on their own — so even if you never expose a single REST endpoint, you now have a fast, isolated, zero-framework unit test runner for plain PostgreSQL scripts. Point it at a directory of .sql tests and go.

    Database Testing Is Impossible (They Said)

    There is a widespread belief in this industry that testing against a real database is somewhere between painful and impossible. That belief is why we have in-memory database fakes that never work, endless mocks, and entire testing philosophies built around not touching the database — which is a strange way to test software whose primary job is to talk to a database.

    And here is the thing: the belief is not entirely wrong. But it may be database-specific where some databases make it harder than others. PostgreSQL is one of the databases that makes it easy.

    For example, testing against SQL Server without containers is hard. Even with containers, it is still hard.

    No deferrable constraints — every foreign key is checked immediately, always, so a test fixture must satisfy the entire dependency graph before it can insert one interesting row. Creating a throwaway database per test run is a heavyweight operation. The standard answer was tSQLt — a framework you install into the database, with CLR dependencies and its own way of faking tables. People tried it, got burned, and concluded that database testing doesn't work.

    Then they carried that conclusion over to PostgreSQL, where it was never true.

    PostgreSQL has transactional DDL. It has deferrable constraints. It has cheap database creation and template databases. It has assert in every DO block. The primitives for elegant database testing have been sitting there for decades — they just needed a harness.

    The Pattern I Have Used for Years

    Long before 3.19, I tested my functions with a pattern so simple it barely deserves the name: the function and its assertions live in the same file, and the assertions run right below the definition.

    sql
    sql
    -- recreate the entire function every time
    +-- this is the first line of tests, sql functions are checked against schema on replace
    +create or replace function get_posts()
    +returns table (...)
    +language sql as
    +$$
    +select ...
    +$$;
    +
    +-- anonymous function (DO block) asserts against the function's output, and fails loudly if it doesn't match expectations
    +do 
    +$$
    +begin
    +    -- ARRANGE and ACT
    +    create temp table _result on commit drop as
    +    select * from get_posts();
    +
    +    -- ASSERT: assert on the result set
    +    assert (select count(*) = 5 from _result where ...), 'get_posts() does not return expected data';
    +    assert (select name from _result where id = ...) = 'expected_name', 'get_posts() returns wrong name for id ...';
    +
    +    -- do the optional cleanup if data was mutated
    +    -- rollback;
    +end 
    +$$;

    Open the file in the editor, hit execute, and the whole thing runs on one connection: drop, create, assert. If the assert fails, the script fails, loudly. Fix, execute again. RED — GREEN, right there in the editor.

    And here is the part people don't believe until they try it: this loop is faster than traditional TDD. There is no build step. There is no test framework to boot, no runner to discover tests, no DI container to spin up. The unit of execution is one SQL script on one already-open connection — the round trip is measured in tens of milliseconds. Executing on a save-keystroke is faster than any dotnet test or pytest will ever warm up. I have shipped entire projects this way, and I will absolutely keep using this pattern — 3.19 doesn't replace it.

    It is hard to explain how powerful this is until you try it.

    But It Has Limits

    The pattern has one requirement that is also its ceiling: you need a callable unit. A function or a procedure — something select-able that the DO block can assert against.

    Which means:

    • SQL file endpoints can't be tested this way. There is no function to call — the file is the endpoint. Since 3.12, more and more of my endpoints are plain SQL files, and they were invisible to my own testing pattern.
    • The HTTP layer can't be tested at all. The function returning correct rows tells you nothing about what the endpoint does: is @authorize actually enforced? Does the anonymous request get its 401? Do the claims map into parameters? Is the JSON shaped the way the client expects — camelCased, nested, @single-unwrapped? All of that lives above the function, and the DO block can't see it.

    Testing the function but not the endpoint is testing the engine but not the car.

    Tests Are SQL Files Too

    The fix, in hindsight, was obvious — it is the same move 3.12 made for endpoints. If SQL files can be endpoints, then SQL files can test endpoints.

    And the syntax was already waiting: NpgsqlRest has embedded HTTP semantics in SQL comments from day one, so putting an HTTP request in a comment is the most natural thing in the world:

    sql
    sql
    /*
    +POST /api/login
    +Content-Type: application/json
    +
    +{"email": "ada@example.com", "password": "correct horse battery staple"}
    +*/

    The runner invokes the real endpoint pipeline in-process — no server, no network — and captures the response into a temp table on the test's connection. And because this is PostgreSQL, with arguably the best JSON support of any relational database, asserting on that response is just... SQL:

    sql
    sql
    select status = 200, 'login succeeds' from _response;
    +select body::jsonb ->> 'email' = 'ada@example.com', 'the right user comes back' from _response;
    +select body::jsonb -> 'roles' @> '["admin"]', 'role is present' from _response;

    @>, ->>, jsonb_path_query — the assertion language for JSON responses was already built into the database. No fluent assertion library will ever compete with that.

    The critical design decision — and the one that took the most engineering — is connection affinity: the endpoint call runs on the test's own connection, inside the test's own transaction. Insert a fixture, don't commit it, call the endpoint — the endpoint sees your uncommitted fixture. Assert, roll back, and it never existed. No .http file replay, no test HTTP client, no docker-compose test stack can reproduce that, because the state you tested against never existed outside your transaction.

    The Old Demons: Isolation and Fixtures

    Two problems have haunted database testing forever, and this is where PostgreSQL gets to show off.

    Isolation. Every test file runs on its own non-pooled connection, in parallel — a fresh physical session, no temp tables or GUCs leaking between tests. Transactions handle the rest. And when transaction isolation isn't enough — sequences are non-transactional, nextval() survives rollback — you go one level up: Setup steps create a throwaway database (create database app_test_{rnd5} — a random token stable for the run), or even per-test clones from a template database, which PostgreSQL creates in well under a second. Perfect isolation is a config block, not a framework.

    Fixtures. The reason fixtures are miserable everywhere else: to insert one row you need its foreign keys, and their foreign keys, and suddenly your test spends thirty lines building a company and a country and a currency to test one post. PostgreSQL's answer is deferrable constraints:

    sql
    sql
    begin;
    +set constraints all deferred;
    +
    +-- one post by a user that is never inserted — legal, because the FK check
    +-- runs at COMMIT, and this transaction never commits
    +insert into posts (id, user_id, content) values (1, 999, 'fixture post');

    Deferred checks run at COMMIT. A test that ends in rollback never commits. Therefore the checks never run. Insert exactly the rows the test is about, in any order, referencing rows that don't exist — and let the endpoint's LEFT JOINs treat the missing references as what they are: nulls. (Try that on SQL Server. You can't — deferrable constraints don't exist there. This is what I mean by "the belief was database-specific.")

    And because DDL is transactional too, a shared include (\ir fixtures/relax_users.sql) can even drop NOT NULL from columns your test doesn't care about — and the rollback restores the schema. The Testing Guide covers both tricks.

    And Then There Is Watch Mode

    Here is where it stops being a testing feature and becomes a development environment.

    console
    console
    $ npgsqlrest ./config.json --test --watch

    Save a test — it re-runs alone, in milliseconds. Save an endpoint file — the endpoints rebuild in-process and everything re-runs, with a delta report: break a file and you see - GET /api/get-users (endpoint dropped — check its SQL file for errors) plus the failing tests; fix it and the next save brings it back. The RED–GREEN loop I described earlier, except now it covers the whole HTTP surface.

    And without --test:

    console
    console
    $ npgsqlrest ./config.json --watch

    ...it watches the running server. Every save re-parses and re-validates your SQL against the real schema — the native PostgreSQL parser checks every statement the moment you write it, which means your queries are validated against the live database as you type. A typo'd column name is an error on screen one second after you save, not a runtime surprise. Configuration files are watched. Even the database itself is watched — the discovery query is polled, so create or replace a function in psql and the endpoint is live about two seconds later, annotations included. The TypeScript client regenerates on every cycle, so the frontend's types follow your SQL as you type it.

    Watch mode in action: a save triggers a restart, an error appears and disappears

    This is the part I did not fully anticipate: watch mode is not a testing accessory. It changed how I write endpoints. The database schema became my type checker, running continuously.

    Because nothing beats local development. Nothing.

    AI TDD

    And if you happen to enjoy AI-assisted development, this unlocks what has quietly become my favorite way of working: AI TDD.

    You write a failing test assertion. Or — better — you instruct the AI agent to write it for you, and you just review it like a manager. Then you give your favorite AI coding agent one instruction: don't stop until all tests are green (or you run out of tokens, whichever comes first).

    The reason this works so well here is everything this post has been about: the tests are fast (milliseconds, in-process, no build), isolated (transactions, throwaway databases — the agent cannot corrupt anything), and complete (the real endpoint, real auth, real JSON — not a mock the agent can game). The agent gets a tight, truthful feedback loop, and tight truthful feedback loops are the one thing agents actually need. Watch mode even runs the loop for it.

    Write the assertion, state the goal, go get a coffee. That's my favorite method.

    Where to Start

    • The Testing Guide — the complete walkthrough: assertions, HTTP blocks, fixtures, test databases, template clones, migrations, Docker, CI.
    • Watch Mode — both flavors, configuration, database polling.
    • Examples 19, 20, and 21 — from the basics to a fresh database per run to perfect per-test isolation.
    • The full changelog — including named parameters (:name) in SQL files, endpoint coverage with CI gating, and the response debug mirror.

    Looking back, 3.12 and 3.19 are the same idea, asked twice. 3.12 asked: what if the SQL file is the endpoint? 3.19 asks: what if the SQL file is the test? Both times the answer was already in PostgreSQL — transactional DDL, deferrable constraints, template databases, assert in every DO block. The primitives were there all along; the tooling just had to get out of the way.

    That was the idea from day one: your SQL is the API. As of 3.19, it is the test suite too.

    Happy testing.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/npgsqlrest-vs-postgrest-supabase-comparison.html b/blog/npgsqlrest-vs-postgrest-supabase-comparison.html new file mode 100644 index 000000000..218f8eba2 --- /dev/null +++ b/blog/npgsqlrest-vs-postgrest-supabase-comparison.html @@ -0,0 +1,199 @@ + + + + + + NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    NpgsqlRest vs PostgREST vs Supabase: Complete Feature Comparison

    Comparison · PostgREST · Supabase · January 2026


    Three tools can expose a PostgreSQL database as a REST API without a hand-written backend: NpgsqlRest, PostgREST, and Supabase. They differ in architecture, performance, and how much of the surrounding platform (auth, files, caching, deployment) they cover. This comparison works through each area in turn.

    Executive Summary

    AspectNpgsqlRestPostgRESTSupabase
    What It IsComplete platform in a single binaryStandalone executable/DockerBackend-as-a-Service platform
    Core FocusSQL files + functions as REST endpointsTable/View-centric REST APIComplete backend platform
    Best ForSQL-first APIs, self-hosted full-stackFlexible client-side queriesManaged hosting with dashboard
    Performance4,588 req/s (100 VU)¹1,749 req/s (100 VU)¹Uses PostgREST internally
    DeploymentSingle binary (~30MB), any cloudSingle binary (~20MB)Managed cloud or complex self-host
    Self-HostingSimple (single binary)SimpleComplex (7+ services)
    Learning CurveLow (SQL comments)Medium (RLS policies)Medium (platform concepts)

    ¹ Benchmarked on a single PostgreSQL function returning a simple result set. See the full benchmark methodology for details. Performance will vary with query complexity and workload type.

    Architecture Comparison

    NpgsqlRest

    mermaid
    flowchart LR
    +    A[Client] <--> B["NpgsqlRest
    +    (30MB AOT)
    +    Single executable"]
    +    B <--> C
    +    subgraph C [PostgreSQL]
    +        D["Comment Annotations
    +        (API config)"]
    +    end

    NpgsqlRest is a complete platform in a single, self-contained executable that connects directly to PostgreSQL and serves REST APIs. Starting with v3.12.0, the primary way to create endpoints is SQL files — write a .sql file, add a comment annotation, and it becomes a REST endpoint. No database deployment needed, no functions to create. For more complex logic, PostgreSQL functions and procedures are still fully supported and give you true static type checking end-to-end.

    API configuration lives in SQL comments — the same annotation syntax works in both SQL files and database object comments. Custom paths, caching, rate limiting, authentication, and more, all version-controlled with your code.

    Beyond API generation, NpgsqlRest includes everything needed for full-stack development:

    • SQL file endpoints — drop a .sql file in a folder, get a REST endpoint. Single or multi-command batch scripts with named result sets
    • Static file serving with authorization and template parsing (replace placeholders with user claims)
    • TypeScript/JavaScript code generation for type-safe frontend clients
    • HTTP test file generation for API testing in VS Code and Visual Studio
    • Built-in authentication (JWT, encrypted Bearer, encrypted Cookie, Basic Auth, OAuth, Passkey/WebAuthn)
    • File uploads with image validation, CSV/Excel ingestion

    No additional infrastructure required. Download, configure connection string, run. It deploys on any cloud server instance—AWS EC2, DigitalOcean, Hetzner—or your own hardware.

    PostgREST

    mermaid
    flowchart LR
    +    A[Client] <--> B["PostgREST
    +    (Haskell)
    +    Single executable"] <--> C[PostgreSQL]

    PostgREST follows a similar single-binary model. Both are lightweight and straightforward to deploy.

    Supabase

    mermaid
    flowchart LR
    +    A[Client] <--> B
    +    subgraph B [Supabase]
    +        direction TB
    +        subgraph row1 [" "]
    +            direction LR
    +            C[PostgREST]
    +            D[GoTrue]
    +            E[Realtime]
    +        end
    +        subgraph row2 [" "]
    +            direction LR
    +            F[Storage]
    +            G[Kong]
    +            H[Studio]
    +        end
    +        I[(PostgreSQL)]
    +    end

    Supabase is a platform composed of multiple services. PostgREST handles REST API generation, GoTrue manages authentication, Kong is the API gateway, and further services handle storage, realtime, and the admin dashboard. Self-hosting means orchestrating all of these.

    Key difference: Both NpgsqlRest and Supabase are platforms, but NpgsqlRest packages everything into a single binary (~30MB) while Supabase requires 7+ separate services. Supabase offers a managed cloud service with a visual dashboard; NpgsqlRest gives you full control with simpler self-hosting on any cloud provider.

    Performance Benchmarks

    Benchmark results from PostgreSQL REST API Benchmark 2026, testing 14 frameworks under identical conditions:

    Requests Per Second (100 Concurrent Users, 1 Record)

    FrameworkRequests/secLatencyScaling Factor
    NpgsqlRest JIT4,58810.88ms9.5x
    NpgsqlRest AOT4,52711.02ms9.7x
    Swoole PHP4,42311.29ms9.4x
    Rust (Actix)3,94012.67ms7.8x
    PostgREST1,74928.58ms6.5x

    Key findings:

    • NpgsqlRest JIT is 2.6x faster than PostgREST at 100 concurrent users
    • PostgREST improved significantly in v14.3: from 271 req/s (1 user) to 1,749 req/s (100 users) - a 6.5x improvement
    • NpgsqlRest JIT scales excellently: from 480 req/s (1 user) to 4,588 req/s (100 users) - a 9.5x improvement
    • The gap between NpgsqlRest JIT and AOT has nearly closed (only 1.3% difference)

    Larger Payloads (500 Records, 100 VU)

    FrameworkRequests/secLatency
    Swoole PHP106.88468ms
    NpgsqlRest JIT82.37607ms
    PostgREST78.59636ms

    With larger payloads where database I/O dominates, the performance gap narrows significantly. Swoole PHP leads in data-heavy scenarios, while NpgsqlRest remains competitive with PostgREST.

    PostgreSQL Type Handling

    TypeNpgsqlRestPostgRESTSupabase
    JSON/JSONB
    Arrays (int[], text[])
    Composite types
    Date/Time types
    Boolean
    Variadic parameters
    OUT parameters
    Default parameters

    All three frameworks handle PostgreSQL types correctly.

    Feature Comparison Matrix

    Platform Features

    FeatureNpgsqlRestPostgRESTSupabase
    Static file serving
    Static file authorization
    Template parsing (claim substitution)
    TypeScript/JavaScript code generation
    HTTP test file generation
    Built-in SQL test runner
    Watch mode (dev reload)⚠️
    Built-in authentication
    File uploads
    Visual dashboard
    Managed cloud hosting
    Single-binary deployment
    Deploy on any cloud/server⚠️

    ⚠️ = Complex self-hosting (7+ services); Supabase local dev reloads via the CLI, not the platform itself

    NpgsqlRest as a complete platform: Unlike PostgREST (API-only), NpgsqlRest covers the rest of the stack:

    • Static file serving with path-based authorization—serve your frontend directly from NpgsqlRest
    • Template parsing replaces {claimType} placeholders in HTML files with authenticated user claims (name, email, role, etc.) before serving—build personalized pages without JavaScript
    • TypeScript/JavaScript code generation creates type-safe API clients automatically, keeping your frontend in sync with your database schema
    • HTTP test file generation creates .http files for VS Code REST Client and Visual Studio, enabling rapid API testing during development
    • Built-in SQL test runner (--test, since 3.19) — write endpoint tests as plain .sql files: the real endpoint pipeline is invoked in-process on the test's own transaction, so fixtures roll back cleanly; includes throwaway test databases, JUnit XML, and endpoint-coverage gating for CI. PostgREST has no testing story of its own (pgTAP tests the database, not the HTTP layer)
    • Watch mode (--watch, since 3.19) — the dev server restarts on SQL file, configuration, and even database routine changes, regenerating the TypeScript client every cycle
    • Deployment on any cloud server (AWS EC2, DigitalOcean, Hetzner, Azure VM, GCP Compute) or on-premises—copy the binary and run

    Supabase offers different platform strengths: managed cloud hosting, a visual Studio dashboard for database management, and real-time WebSocket subscriptions. Choose Supabase if you prefer managed infrastructure and a visual interface; choose NpgsqlRest if you want full control with simpler self-hosting.

    Core API Generation

    FeatureNpgsqlRestPostgRESTSupabase
    SQL files as endpoints
    Multi-command SQL batch execution
    Functions as endpoints
    Procedures as endpoints
    Tables as endpoints
    Views as endpoints
    Function overloading
    Custom URL paths
    Path parameters (/users/{id})
    OpenAPI/Swagger generation
    TypeScript client generation
    HTTP test file generation

    SQL file endpoints are unique to NpgsqlRest. Neither PostgREST nor Supabase can turn a .sql file on disk into a REST endpoint. Both require you to create database objects first (functions, views, or tables) before anything is exposed as an API. With NpgsqlRest, you write a SQL file, annotate it with a comment, and the endpoint exists — no database deployment step, no CREATE FUNCTION. Multi-command files execute as a batch and return a JSON object with named result sets.

    Table and View Query Features

    FeatureNpgsqlRestPostgRESTSupabase
    Auto-generated CRUD endpoints over tables/views
    Client-side filtering operators✅ (28+ operators)
    Client-side resource embedding
    Client-side aggregates
    Client-side column selection
    Client-side ordering/pagination

    NpgsqlRest deliberately does not auto-generate CRUD endpoints over tables and views — endpoints come from explicit SQL files or PostgreSQL routines. PostgREST and Supabase allow clients to compose queries against tables:

    • Resource embedding automatically joins related tables based on foreign keys
    • 28+ filtering operators including like, ilike, in, fts (full-text search), range operators
    • Aggregate functions with automatic GROUP BY

    NpgsqlRest takes a different approach: write the SQL yourself — either as a SQL file or a PostgreSQL function — and expose exactly the query you intend. These same capabilities (joins, full-text search, aggregates, filtering, pagination) are all available because you're writing actual SQL:

    • The full SQL surface — CTEs, window functions, recursive queries, full-text search, JSON operators, lateral joins — anything you can write in SQL
    • SQL files or functions — SQL files for straightforward queries, functions when you need PL/pgSQL logic or static type checking
    • Security by design — you expose exactly what you intend, not an entire table with filters
    • Predictable performance — no surprise queries that scan entire tables or create N+1 problems

    Author's note: Earlier versions of NpgsqlRest shipped a CrudSource plugin that auto-generated CRUD endpoints over tables and views. I removed it from the standalone client in v3.14.0 — exposing raw tables directly is a questionable engineering practice. SQL files and functions provide a proper API contract, encapsulate business logic, and give you full control over what data is exposed and how. The plugin still ships as a NuGet package for embedded library use, but it is no longer wired into the default client. — Vedran Bilopavlović, NpgsqlRest author

    Choose PostgREST if you want clients to compose their own queries against tables. Choose NpgsqlRest if you prefer well-defined server-side endpoints with explicit query logic.

    Custom Types and Nested JSON

    FeatureNpgsqlRestPostgRESTSupabase
    Return composite types as JSON
    Return SETOF composite types
    Nested composite types in response
    Arrays of composite types (multiset)
    Deep nesting (3+ levels)
    Flat/merged composite mode
    Composite type as parameter
    Parameter field unnesting
    TypeScript types for nested structures⚠️

    ✅ = Full support ⚠️ = Partial support or workarounds needed ❌ = Not supported

    All three frameworks support PostgreSQL composite types and can return nested JSON structures, including arrays of composite types (multiset pattern). Key differences:

    • NpgsqlRest offers two modes: By default, composite type fields are merged/flattened into the parent object for simpler responses. Use the @nested annotation to preserve the hierarchical structure. PostgREST/Supabase always nest composite types.

    • Parameter unnesting: NpgsqlRest automatically expands composite type parameters into individual named fields (e.g., authorFirstName, authorLastName), so clients send flat fields. PostgREST/Supabase require passing composite parameters as nested JSON objects.

    • TypeScript generation: NpgsqlRest generates proper TypeScript interfaces for all nested structures automatically. Supabase generates types but may require manual casting for complex RPC return types. PostgREST has no built-in TypeScript generation (third-party tools like kanel can help).

    • Deep nesting: All three frameworks now handle arbitrary nesting depths. NpgsqlRest 3.4.4+ resolves nested composite types to any depth by default (configurable via ResolveNestedCompositeTypes). The only remaining limitation is 2D arrays of composite types at the return type level, which remain as tuple strings.

    See Custom Types and Multiset for Nested JSON for detailed examples.

    Authentication

    FeatureNpgsqlRestPostgRESTSupabase
    Token Schemes
    Standard JWT Bearer token
    Encrypted Bearer token (Microsoft Data Protection)
    Encrypted Cookie
    HTTP Basic authentication
    Authentication Sources
    OAuth providers (Google, GitHub, etc.)
    Password authentication (custom)
    Passkey/WebAuthn (biometrics)⚠️
    Password Hashing
    PostgreSQL native (pgcrypto)
    Built-in PBKDF2
    Other Features
    Custom login/logout functions
    Multiple auth schemes simultaneously
    Named additional auth schemes (different TTL/scope per scheme)
    Role-based access control
    Claims to PostgreSQL context
    Claims to PostgreSQL parameters
    Auth time fields in interval format ("14 days", "5 minutes")

    ⚠️ = Requires third-party integration

    Token schemes: NpgsqlRest supports three distinct token formats:

    • Standard JWT Bearer: Industry-standard JSON Web Tokens
    • Microsoft Data Protection Bearer: Encrypted bearer tokens using ASP.NET Core Data Protection—a Microsoft proprietary format that encrypts the entire token payload
    • Encrypted Cookies: Session cookies encrypted with Data Protection, ideal for traditional web applications

    Authentication sources: Users can authenticate via:

    • OAuth providers: External identity providers (Google, GitHub, Microsoft, etc.)
    • Password authentication: Either using PostgreSQL's native password hashing (via pgcrypto) or NpgsqlRest's built-in PBKDF2 implementation
    • Passkey/WebAuthn: Passwordless authentication using device biometrics (fingerprint, Face ID) or security keys—client device dependent

    Passkey/WebAuthn support: NpgsqlRest includes built-in passkey authentication for passwordless login using device biometrics or PINs. NpgsqlRest handles the WebAuthn protocol (CBOR parsing, signature verification) while your PostgreSQL functions control user management and credential storage. PostgREST has no passkey support. Supabase does not offer native passkey support—users must integrate third-party services like Corbado or Descope to add passkeys to their Supabase applications.

    NpgsqlRest also offers claims-to-parameters mapping - user claims (user ID, roles, custom claims) are automatically injected as function parameters by configurable name matching, with type safety enforced by PostgreSQL. PostgREST and Supabase only support accessing claims via current_setting() or auth.jwt() context functions, requiring manual casting within your SQL code.

    PostgREST only supports JWT - you need an external auth server like GoTrue (which is what Supabase uses).

    Architectural difference: PostgREST and Supabase rely on Row Level Security (RLS) as their primary authorization mechanism — you write DDL policies on tables that filter rows based on the authenticated user's JWT claims. These policies must be created on the server via migrations and are evaluated on every query. NpgsqlRest takes a different approach: authorization is declared via comment annotations (@authorize, @allow_anonymous, @basic_auth, etc.) directly on each endpoint — no RLS needed, no DDL, and each endpoint is individually configurable. For a deeper look at NpgsqlRest's authentication approach, see Database-Level Security: PostgreSQL Authentication and Multiple Auth Schemes, RBAC & External Providers.

    Named additional auth schemes (NpgsqlRest 3.13.0): NpgsqlRest can register multiple named authentication schemes alongside the main one — e.g., a normal 14-day cookie session for general use plus a 1-hour single-session cookie for sensitive operations (recovery codes, admin areas, payment flows). Each scheme has its own Type (Cookies, BearerToken, or Jwt), expiration, and (for JWT) signing secret. Login functions select the scheme by returning its name in the scheme column. PostgREST supports only a single JWT scheme with one secret. Supabase Auth uses a single GoTrue session model with one cookie configuration — Supabase docs explicitly note that shortening the cookie's Max-Age "results in a degraded user experience," so step-up authentication for sensitive operations must be implemented at the application layer.

    File Handling

    FeatureNpgsqlRestPostgRESTSupabase
    File uploads
    Image uploads with validation
    PostgreSQL Large Objects
    File system storage
    CSV file ingestion
    Excel file ingestion
    CSV export
    Excel (.xlsx) export (streaming)
    HTML table rendering
    Row-by-row processing
    Upload metadata to functions

    NpgsqlRest handles the full file lifecycle:

    • Image uploads with format validation (JPEG, PNG, GIF, WebP, etc.)
    • CSV/Excel ingestion with row-by-row processing through PostgreSQL functions
    • PostgreSQL Large Objects for storing files directly in the database
    • Combined handlers (multiple storage backends in a single transaction)
    • Streaming Excel (.xlsx) exports with zero allocations and constant memory (~80KB), powered by SpreadCheetah. One annotation (@table_format = excel) turns any function into a downloadable spreadsheet with native Excel types. PostgREST and Supabase only support CSV export—for Excel, you need external libraries or third-party services.

    PostgREST has no file upload support. Supabase requires a separate Storage service.

    Security and Infrastructure

    FeatureNpgsqlRestPostgRESTSupabase
    Application-level column encryption
    Security headers middleware
    Forwarded headers (reverse proxy)⚠️
    Health check endpoints⚠️
    PostgreSQL statistics endpoints

    Application-level column encryption: NpgsqlRest provides transparent encrypt/decrypt annotations powered by ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result columns are decrypted before returning to the client — the database stores ciphertext while the API consumer sees plaintext. Useful for PII (SSN, medical records) without requiring pgcrypto or client-side encryption. PostgREST has no built-in encryption. Supabase provides Vault for managing secrets but not annotation-level column encryption.

    Security headers: NpgsqlRest includes built-in middleware for HTTP security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Content-Security-Policy, Permissions-Policy, COOP, COEP, CORP). PostgREST and Supabase require reverse proxy configuration or application-level implementation.

    Forwarded headers: NpgsqlRest has built-in middleware for processing X-Forwarded-For/Proto/Host headers with configurable trusted proxies and networks. PostgREST can read these headers via SQL but has no configuration for trusted proxy validation. Supabase handles this at their API gateway level.

    Health check endpoints: NpgsqlRest provides /health, /health/ready (with PostgreSQL connectivity check), and /health/live endpoints for Kubernetes/Docker orchestration. PostgREST has /live and /ready via its Admin Server. Supabase has service-specific health checks but no unified application health endpoint.

    PostgreSQL statistics: NpgsqlRest exposes /stats/routines, /stats/tables, /stats/indexes, and /stats/activity for monitoring PostgreSQL performance. PostgREST has no built-in stats endpoints (requires custom views). Supabase provides a Metrics API with ~200 PostgreSQL metrics (cloud only, not self-hosted).

    Performance Features

    FeatureNpgsqlRestPostgRESTSupabase
    Response caching (memory)
    Response caching (Redis)
    Hybrid cache with stampede protection
    Cache invalidation endpoints
    Named cache profiles (per-endpoint)
    Conditional caching (When rules: dynamic TTL, skip-on-condition)
    Rate limiting⚠️
    Multiple rate limit algorithms
    Partitioned rate limiting (per-user/IP/header)
    Connection pooling
    Transaction-mode pooler compatibility (PgBouncer/RDS Proxy/Supavisor)
    Command retry with backoff
    Connection retry with configurable delays
    Multi-host failover
    Request compression
    Response compression (Gzip/Brotli)

    ⚠️ Supabase has rate limiting at the managed cloud edge layer, but it is not configurable per-endpoint or as a policy.

    NpgsqlRest ships performance features the other two delegate to external infrastructure:

    • Three caching modes: Memory, Redis, and Hybrid with stampede protection
    • Named cache profiles with When rules — different TTLs based on input shape, conditional cache bypass, per-endpoint policy selection via @cache_profile annotation
    • Four rate limiting algorithms: Fixed window, sliding window, token bucket, concurrency
    • Partitioned rate limiting — per-user, per-IP, or per-header buckets via Partition block, with BypassAuthenticated for "throttle anonymous only" patterns
    • Automatic retry with exponential backoff for transient failures
    • Connection retry with configurable delay sequences and PostgreSQL error code matching (e.g., connection lost, too many connections, server starting up)
    • Multi-host failover with read replica support

    Caching architectural difference: PostgREST has no built-in response caching (active issue #4460 proposing a SIEVE-based in-memory cache), so caching must be handled by an upstream reverse proxy or CDN. Supabase has CDN caching for storage assets only — API response caching requires manual implementation (Next.js cache helpers, revalidation webhooks, or external Redis). NpgsqlRest is the only one with first-class response caching including conditional rules: a single endpoint can serve historical queries cached for 1 hour, "until-now" queries cached for 5 minutes, and live=true queries that bypass the cache entirely — all declared in JSON config, no SQL changes.

    Rate limiting architectural difference: PostgREST has no built-in rate limiting (issue #785 — the official position is that rate limiting belongs at the gateway level, not in PostgREST). Supabase has edge-level rate limiting in their managed cloud, but it is not configurable per-endpoint or as a partitioned policy. NpgsqlRest's Partition block resolves a key per request from claims/IP/headers, so each authenticated user gets their own bucket — without writing custom middleware or fronting the service with Nginx/Kong.

    Connection Pooler Compatibility & Multi-Tenancy

    FeatureNpgsqlRestPostgRESTSupabase
    Per-request transaction wrapping (opt-in)
    Pre-routine SQL execution hook
    Multiple pre-routine commands per request
    Parameterized pre-routine SQL with claim/header/IP binding
    No extra round-trip for pre-routine commands
    Multi-tenant search_path from JWT claim (declarative)⚠️⚠️

    ⚠️ Possible via a custom function called by db-pre-request, but not declarative — requires writing a SQL function that reads current_setting('request.jwt.claims', true)::json and calls set_config() manually.

    All three platforms support transaction-mode connection poolers (PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, Supabase's Supavisor) — but the ergonomics differ.

    PostgREST has db-pre-request, a configuration option that names a single SQL function to call after authentication and before the main query. The function can read JWT claims via current_setting('request.jwt.claims', true)::json and apply settings like search_path. This is functionally similar to NpgsqlRest's hook, but is a single function call with no parameter binding — you write SQL that reads context strings, parses JSON, and calls set_config() itself. PostgREST also wraps every request in a transaction with role/jwt-claims set per-transaction by default, so pooler compatibility is built in without an opt-in flag.

    Supabase uses PostgREST internally, so it inherits db-pre-request. Supavisor handles transaction-mode pooling.

    NpgsqlRest 3.13.0 introduces two cooperating options:

    • WrapInTransaction: true wraps every request in BEGIN ... COMMIT and switches set_config calls to transaction-local (is_local=true). Required for PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, and Supabase Pooler. Default is false (existing behavior preserved).
    • BeforeRoutineCommands — a list of SQL commands (raw strings or objects with Sql + Parameters) executed in the same NpgsqlBatch as context setup, with no extra round-trip. Parameter values are bound at request time from HttpContext: each parameter has a Source (Claim, RequestHeader, or IpAddress) and an optional Name, with values passed as parameterized SQL inputs (no string interpolation, no injection risk).

    The practical difference: NpgsqlRest's multi-tenant search_path is 4 lines of JSON config:

    jsonc
    jsonc
    "BeforeRoutineCommands": [
    +  {
    +    "Sql": "select set_config('search_path', $1, true)",
    +    "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +  }
    +]

    The PostgREST equivalent requires creating a SQL function that reads request.jwt.claims, parses the JSON, extracts tenant_id, and calls set_config() — plus the db-pre-request = '<function_name>' config entry. Functionally equivalent, but the NpgsqlRest version is declarative with type-safe parameter binding rather than string-parsing JWT claims in SQL.

    For details on the NpgsqlRest options, see Connection Pooler Compatibility.

    Real-Time Capabilities

    FeatureNpgsqlRestPostgRESTSupabase
    Server-Sent Events (SSE)
    WebSockets
    PostgreSQL RAISE streaming
    PostgreSQL LISTEN/NOTIFY
    Broadcast to subscribed clients
    Event scoping (authorize/matching/all)

    NpgsqlRest uses Server-Sent Events for real-time streaming, which is simpler than WebSockets and works through standard HTTP. PostgreSQL's RAISE INFO/NOTICE/WARNING statements stream directly to connected clients - no message brokers or LISTEN/NOTIFY infrastructure needed. See the Real-Time Chat example.

    External Service Integration and Custom Code Execution

    A critical question for any PostgreSQL REST framework: how do you execute custom logic that isn't in the database? — calling external APIs, rendering PDFs, sending emails, running ML inference, or integrating with third-party services.

    The three take very different approaches:

    CapabilityNpgsqlRestPostgRESTSupabase
    Proxy / External Service Calls
    Forward request to upstream (passthrough)
    Transform upstream response in PG
    Forward PG result to upstream (proxy_out)
    Declarative proxy via SQL annotations
    Proxy response caching
    Custom Code Runtimes
    Edge Functions (Deno/TypeScript)
    Serverless function runtime
    HTTP Client Types (app-layer HTTP calls)
    Typed HTTP calls via composite types
    Parallel execution of multiple HTTP calls
    Placeholder substitution (URL, headers, body)
    All HTTP methods (GET/POST/PUT/PATCH/DELETE)
    Per-type timeout and retry with backoff
    Retry on specific status codes (429, 503, etc.)
    Server-side resolved parameters (secrets)
    Database-level HTTP extensions
    pg_net (async, JSON POST only, 200 req/s)
    http extension (sync, blocks DB connection)⚠️
    Event-Driven
    Database Webhooks (trigger → HTTP)
    Pre-request hook

    ⚠️ = Available via third-party PostgreSQL extension (not a PostgREST feature)

    NpgsqlRest: Declarative Proxy in SQL

    NpgsqlRest has three proxy modes, all configured via SQL comment annotations on PostgreSQL functions, with no additional infrastructure:

    1. Passthrough proxy — forward the client request to an upstream service, return the upstream response directly. The PostgreSQL function is never executed (no database connection opened):

    sql
    sql
    create function get_weather_forecast()
    +returns void language sql as 'select';
    +
    +comment on function get_weather_forecast() is 'HTTP GET
    +@proxy https://weather-api.example.com';

    2. Transform proxy — forward the request to upstream, then pass the upstream response into the PostgreSQL function for processing. The function receives the upstream status code, body, headers, content type, success flag, and error message as parameters:

    sql
    sql
    create function enrich_user_data(
    +    _user_id int,
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', 'upstream failed');
    +    end if;
    +    -- Combine upstream data with local data
    +    return json_build_object(
    +        'external', _proxy_body::json,
    +        'local', (select row_to_json(u) from users u where u.id = _user_id)
    +    );
    +end; $$;
    +
    +comment on function enrich_user_data(int, int, text, boolean) is 'HTTP GET
    +@proxy https://external-api.example.com';

    3. Proxy out (post-execution proxy) — execute the PostgreSQL function first, then forward its result as the request body to an upstream service. The upstream response is returned to the client. Ideal for scenarios where PostgreSQL prepares a payload and an external service does heavy processing (PDF rendering, ML inference, image processing):

    sql
    sql
    create function generate_invoice(order_id int)
    +returns json language plpgsql as $$
    +begin
    +    return (select json_build_object(
    +        'customer', c.name, 'items', json_agg(row_to_json(i))
    +    ) from orders o
    +    join customers c on c.id = o.customer_id
    +    join order_items i on i.order_id = o.id
    +    where o.id = order_id
    +    group by c.name);
    +end; $$;
    +
    +comment on function generate_invoice(int) is 'HTTP GET
    +@proxy_out POST https://pdf-renderer.internal/render';

    All three modes support: custom upstream host per endpoint, HTTP method override, authenticated user claim/context forwarding to upstream, upload content forwarding, configurable timeouts, and response caching. Proxy configuration shares the same ProxyOptions section and integrates with NpgsqlRest's existing caching, rate limiting, and authentication systems.

    4. HTTP Client Types — a completely different mechanism from the proxy modes. You define a PostgreSQL composite type whose comment contains an HTTP request definition (method, URL, headers, body), and NpgsqlRest makes the HTTP call automatically when a function parameter uses that type. The response fields (body, status_code, headers, content_type, success, error_message) are populated and passed to the PostgreSQL function as a regular parameter:

    sql
    sql
    -- Define an HTTP client type via comment on a composite type
    +create type openai_api as (body json, status_code int, success boolean, error_message text);
    +comment on type openai_api is 'POST https://api.openai.com/v1/chat/completions
    +Authorization: Bearer {_api_key}
    +Content-Type: application/json
    +
    +{"model": "gpt-4", "messages": [{"role": "user", "content": "{_prompt}"}]}';
    +
    +-- Use it in a function — NpgsqlRest makes the HTTP call, PG processes the result
    +create function ask_ai(_prompt text, _api_key text, _response openai_api)
    +returns json language plpgsql as $$
    +begin
    +    if not (_response).success then
    +        return json_build_object('error', (_response).error_message);
    +    end if;
    +    return (_response).body;
    +end; $$;

    Key capabilities of HTTP Client Types:

    • Parallel execution — multiple HTTP type parameters in one function execute concurrently via Task.WhenAll, enabling API aggregation from multiple services in a single endpoint
    • Placeholder substitution{param_name} placeholders in URLs, headers, and body are resolved from function parameters, enabling dynamic URLs and authenticated requests
    • All HTTP methods — GET, POST, PUT, PATCH, DELETE
    • Typed response fields — body, status_code, headers (as JSON), content_type, success (boolean), error_message
    • Configurable retry@retry_delay 1s, 2s, 5s on 429, 503 retries on specific status codes with delays
    • Per-type timeoutstimeout 10s per HTTP type definition
    • Server-side resolved parameters — combine with resolved parameter expressions to securely inject API keys from the database without exposing them to the client

    This is fundamentally different from Supabase's pg_net (async-only, JSON POST only, 200 req/s limit, no placeholder substitution, responses in a queue table) and the http extension (blocks the database connection). NpgsqlRest's HTTP Client Types run in the application layer, execute in parallel, support all methods, have built-in retry logic, and integrate directly into the function's parameter pipeline.

    PostgREST: No Custom Code Execution

    PostgREST is intentionally narrow in scope — it maps PostgreSQL to a REST API and nothing else. It has no built-in proxy, no webhook support, no custom code runtime, and no outbound HTTP capability.

    The only hook is db-pre-request: a configurable pre-request function that runs inside the database transaction before the main query. It can inspect headers and JWT claims, and can block requests by raising exceptions — but it cannot make external HTTP calls or modify the response.

    PostgREST users who need external service integration must add separate infrastructure:

    • Hybrid API pattern: A custom API server (Node.js, Python, Go) sits alongside PostgREST — CRUD requests are proxied to PostgREST, business logic endpoints are handled by the custom server
    • Nginx + Lua middleware: The postgrest-starter-kit uses OpenResty to add custom logic at the reverse proxy layer
    • LISTEN/NOTIFY + external workers: Database triggers fire NOTIFY events; separate worker processes listen and perform side effects (send emails, call APIs)
    • PostgreSQL extensions: pgsql-http (synchronous) or pg_net (asynchronous) can be installed into PostgreSQL to make HTTP calls from SQL — but these are PostgreSQL extensions, not PostgREST features, and come with their own limitations

    Supabase: Edge Functions (Separate Deno Runtime)

    Supabase addresses custom code execution through Edge Functions — serverless TypeScript/JavaScript functions running on a Deno-based runtime in V8 isolates, deployed as a separate service alongside PostgREST.

    Each Edge Function gets its own HTTP endpoint (https://<project>.supabase.co/functions/v1/<name>) and must be written in TypeScript, deployed via CLI or dashboard, and managed independently from your PostgreSQL schema:

    typescript
    typescript
    // supabase/functions/generate-pdf/index.ts
    +import { serve } from "https://deno.land/std/http/server.ts"
    +
    +serve(async (req) => {
    +  const { orderId } = await req.json()
    +
    +  // Must manually connect to database
    +  const supabase = createClient(
    +    Deno.env.get('SUPABASE_URL')!,
    +    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
    +  )
    +  const { data } = await supabase
    +    .from('orders')
    +    .select('*, customer(*), items(*)')
    +    .eq('id', orderId)
    +    .single()
    +
    +  // Must manually call external service
    +  const pdf = await fetch('https://pdf-renderer.internal/render', {
    +    method: 'POST',
    +    body: JSON.stringify(data),
    +  })
    +
    +  return new Response(await pdf.arrayBuffer(), {
    +    headers: { 'Content-Type': 'application/pdf' },
    +  })
    +})

    Edge Functions have notable constraints: 2-second CPU time limit per request, 256 MB memory, 20 MB bundle size, and cold starts. They are available in self-hosted Supabase but run as a single instance (no multi-region distribution).

    Supabase also offers Database Webhooks — PostgreSQL triggers that use the pg_net extension to send HTTP requests on INSERT/UPDATE/DELETE events. These are fire-and-forget (async, responses stored in a queue table for 6 hours) with a 200 requests/second limit and JSON POST only (no PUT/PATCH).

    For direct HTTP calls from PostgreSQL, Supabase provides the pg_net extension (async, non-blocking, JSON POST only) and the http extension (synchronous, blocks the database connection). Neither can control what gets returned to the original HTTP client — they are purely outbound.

    Architectural Comparison

    The fundamental difference is where orchestration lives:

    AspectNpgsqlRestPostgRESTSupabase
    Where logic is definedSQL files + comments on PG functions/typesN/ATypeScript in separate runtime
    Additional services requiredNoneCustom API server or middlewareEdge Runtime (Deno container)
    Database involvementPG function controls the entire flowPG has no role in external callsPG can trigger webhooks only
    Response controlPG function decides what client receivesN/AEdge Function decides
    Parallel external callsBuilt-in (HTTP Client Types)N/AManual Promise.all() in TS
    Deployment complexityZero — same binaryRequires additional infrastructureRequires separate Deno service
    Caching of external callsBuilt-in (@cached annotation)N/AMust implement manually
    Secret managementServer-side resolved parametersN/Asupabase secrets set CLI
    Retry logicBuilt-in (@retry_delay annotation)N/AMust implement manually

    NpgsqlRest keeps external service orchestration inside PostgreSQL with declarative annotations — no additional runtimes, no separate deployment pipelines, no TypeScript code to maintain. Supabase requires a separate Deno runtime and imperative TypeScript code. PostgREST requires users to build their own solution entirely.

    Advanced Features

    FeatureNpgsqlRestPostgRESTSupabase
    Per-endpoint configuration in SQL comments
    Server-side resolved parameters
    Custom response headers
    Request header forwarding
    Per-endpoint timeouts
    Per-endpoint caching policies
    Per-endpoint rate limiting
    Per-endpoint retry strategies
    Parameter validation
    Raw (non-JSON) responses
    Error code mapping⚠️⚠️
    Named error code policies
    Per-endpoint error policies
    Configurable timeout error mapping
    RFC 7807 Problem Details format
    TraceId in error responses
    CORS configuration
    HTTPS/TLS

    Observability

    FeatureNpgsqlRestPostgRESTSupabase
    Structured logging
    Log to file
    Log to PostgreSQL
    OpenTelemetry
    Request tracing
    Execution ID tracking
    Sensitive parameter obfuscation

    NpgsqlRest uses Serilog with multiple output targets: console, file (with rotation), PostgreSQL table, and OpenTelemetry for distributed tracing.

    Error Handling

    FeatureNpgsqlRestPostgRESTSupabase
    Configurable error code mapping
    Named error code policies
    Per-endpoint error policies
    Configurable timeout error mapping
    RFC 7807 Problem Details format
    TraceId in error responses
    Custom HTTP status from SQL⚠️⚠️

    ⚠️ = Limited workaround available

    NpgsqlRest's error handling is policy-based and configurable end to end — a clear gap over PostgREST and Supabase, which hardcode their mappings.

    Named error code policies: Define multiple named policies that map PostgreSQL error codes to specific HTTP status codes, titles, and details. For example, map 23505 (unique violation) to HTTP 409 Conflict, or 23503 (foreign key violation) to HTTP 400 with a custom message—all in configuration, not in SQL:

    json
    json
    {
    +  "ErrorCodePolicies": [
    +    {
    +      "Name": "strict_errors",
    +      "ErrorCodes": {
    +        "23505": {"StatusCode": 409, "Title": "Conflict", "Details": "A record with this key already exists."},
    +        "23503": {"StatusCode": 400, "Title": "Invalid Reference", "Details": "Referenced record does not exist."},
    +        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege"}
    +      }
    +    }
    +  ]
    +}

    Per-endpoint error policies: Each endpoint can use a different error policy via the @error_code_policy annotation:

    sql
    sql
    comment on function risky_operation() is '
    +HTTP POST
    +@error_code_policy strict_errors';

    Configurable timeout error mapping: Custom HTTP response for command timeouts with configurable status code, title, and details—not a generic 500.

    RFC 7807 Problem Details: Error responses follow the RFC 7807 standard ("Problem Details for HTTP APIs"), including a type URI reference to RFC documentation, title, status, and detail fields. This is the IETF-recommended format for API error responses, enabling interoperability with standard API tooling and middleware.

    TraceId correlation: Error responses include a traceId field for direct correlation between what the client receives and server-side logs—essential for debugging in production.

    PostgREST uses a hardcoded mapping table (e.g., 23505 → 409, 42501 → 401/403). The only workaround is the PT### SQLSTATE mechanism—RAISE sqlstate 'PT404' forces HTTP 404—which couples your SQL to HTTP semantics. Error responses use a custom JSON format (code, message, details, hint), not RFC 7807. No per-endpoint configuration, no named policies, no configurable timeout handling, no trace IDs.

    Supabase inherits PostgREST's error handling wholesale since it embeds PostgREST as its REST layer. The client SDKs wrap errors into {data, error} objects but add no additional error mapping configurability.

    Configuration Approach

    NpgsqlRest: SQL Comments

    sql
    sql
    -- Define endpoint with custom path and authentication
    +create function api.get_user(p_id int)
    +returns setof user_info
    +language sql
    +begin atomic;
    +select id, name, email, role from users where id = p_id;
    +end;
    +
    +comment on function api.get_user(int) is '
    +HTTP GET /users/{p_id}
    +@authorize admin, user
    +@cached
    +@cache_expires_in 300
    +@rate_limiter_policy standard
    +';

    Everything is configured through SQL comments directly on your functions, procedures, or .sql files. This keeps API configuration close to the implementation and version-controlled with your schema.

    Every endpoint can be individually configured with its own caching policy, rate limiting, authentication requirements, timeout, and retry strategy—declared in SQL comments, either in your SQL files or on database objects. PostgREST and Supabase apply configuration globally or require external tools for per-endpoint customization.

    PostgREST: External Configuration + RLS

    sql
    sql
    -- PostgREST relies on Row Level Security
    +create policy "Users can view own data"
    +  on users for select
    +  using (auth.uid() = id);

    PostgREST uses a combination of configuration files and PostgreSQL Row Level Security policies. Custom paths and advanced routing require an API gateway like Kong.

    Supabase: Dashboard + RLS + Edge Functions

    Supabase uses a web dashboard for most configuration, RLS for authorization, and Edge Functions (Deno-based TypeScript runtime) for custom logic that cannot live in the database. Edge Functions run as a separate service, deployed independently via CLI, with their own endpoint URLs, secrets management, and resource limits. More moving parts, in exchange for a visual interface and a managed cloud option.

    Deployment Comparison

    NpgsqlRest

    bash
    bash
    # Option 1: Direct download (30MB)
    +wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux64
    +chmod +x npgsqlrest-linux64
    +./npgsqlrest-linux64 --connection "Host=localhost;Database=mydb;Username=api"
    +
    +# Option 2: Docker
    +docker run -p 8080:8080 vbilopav/npgsqlrest:latest \
    +  --connection "Host=host.docker.internal;Database=mydb;Username=api"
    +
    +# Option 3: NPM
    +npm install -g npgsqlrest
    +npx npgsqlrest --connection "..."

    Single binary, zero dependencies. Works on Windows, Linux (x64/ARM64), and macOS.

    PostgREST

    bash
    bash
    # Download and run
    +wget https://github.com/PostgREST/postgrest/releases/latest/download/postgrest-linux-static-x64.tar.xz
    +tar xf postgrest-linux-static-x64.tar.xz
    +./postgrest postgrest.conf
    +
    +# Docker
    +docker run -p 3000:3000 postgrest/postgrest

    Similar simplicity, but requires external services for authentication and file handling.

    Supabase Self-Hosted

    bash
    bash
    # Clone the Docker setup
    +git clone https://github.com/supabase/supabase
    +cd supabase/docker
    +cp .env.example .env
    +docker compose up -d

    Supabase self-hosting requires Docker Compose with 7+ containers: PostgreSQL, PostgREST, GoTrue, Realtime, Storage, Kong, Studio, and more — correspondingly harder to maintain and scale.

    When to Choose Each

    Choose NpgsqlRest When:

    • You want SQL files as endpoints — write a .sql file, get a REST endpoint. No CREATE FUNCTION needed
    • You want a self-hosted platform — API, static files, auth, and code generation in one binary
    • Performance is critical — 2.6x faster than PostgREST under load
    • You want server-side API design — SQL files for simple queries, functions for complex logic
    • You need enterprise features — caching (memory/Redis/hybrid), rate limiting, retry logic, multi-host failover
    • You need conditional caching — different TTLs for historical vs live data, declarative skip-on-condition
    • You need per-user rate limitsPartition block scopes buckets by claim, IP, or header
    • You run behind a transaction-mode pooler — PgBouncer, AWS RDS Proxy, or Supabase Pooler with declarative multi-tenant search_path
    • You need multi-tier auth — short-lived sensitive sessions alongside normal long-lived ones
    • You need file handling — upload images, process CSV/Excel, store as Large Objects
    • You want real-time without WebSocket complexity — SSE with PostgreSQL RAISE statements
    • You prefer simple deployment — single binary on any cloud server (AWS, DigitalOcean, Hetzner, etc.)
    • You use TypeScript — auto-generated type-safe clients with full type definitions
    • You want custom URL paths/users/{id} instead of /rpc/get_user?id=1

    Choose PostgREST When:

    • You want a proven, mature solution - PostgREST has been around since 2014
    • You need flexible client-side queries - Resource embedding, filtering, aggregates, pagination
    • You want GraphQL-like query composition - Clients can request exactly the data they need
    • You're already using Row Level Security - PostgREST integrates well with RLS
    • Your API is table/view-centric - Most of your endpoints map directly to tables
    • You prefer Haskell's correctness guarantees - PostgREST is written in Haskell

    Choose Supabase When:

    • You want managed cloud hosting - Let Supabase handle infrastructure
    • You need the Studio UI - Visual database management and API exploration
    • You want social login out of the box - Google, GitHub, etc. pre-configured
    • Your team is less experienced with PostgreSQL - More guardrails and documentation
    • You're building a prototype quickly - Fastest time-to-market for simple apps
    • You prefer a visual dashboard - Configure and explore your API visually

    Migration Considerations

    From PostgREST to NpgsqlRest

    1. Functions work identically — both expose PostgreSQL functions as endpoints
    2. Replace direct table/view access with SQL files or functions — NpgsqlRest does not auto-generate CRUD endpoints over tables. Endpoints that relied on PostgREST's ?eq./?order. style query composition need to be expressed as SQL files (often the simplest path) or as PostgreSQL functions
    3. Move simple RPC functions to SQL files — many /rpc/ endpoints can become plain .sql files, no CREATE FUNCTION needed
    4. Add SQL comments for configuration — replace external config with inline annotations
    5. Replace RLS with function-level auth — or keep RLS and add @authorize annotations
    6. Gain features — caching, rate limiting, file uploads, multi-command batch scripts

    From Supabase to NpgsqlRest

    1. Keep your PostgreSQL schema - It's still just PostgreSQL
    2. Replace direct table/view access with SQL files or functions — same caveat as the PostgREST migration: Supabase's auto-generated table API has no direct counterpart, so write the queries as SQL files or routines
    3. Replace GoTrue with built-in auth - JWT, encrypted Bearer/Cookie, OAuth, Passkey/WebAuthn
    4. Replace Storage with NpgsqlRest uploads - File system or Large Objects
    5. Replace Realtime with SSE - Simpler protocol, works through standard HTTP
    6. Replace Edge Functions with SQL files or proxy annotations — simple Edge Functions become SQL files. Those that call external APIs become functions with @proxy, @proxy_out, or HTTP Client Type annotations. No separate Deno runtime needed
    7. Replace pg_net/http extensions with HTTP Client Types - Typed, synchronous HTTP calls with retry logic and server-side secret resolution, integrated into the function execution pipeline

    Conclusion

    CriteriaWinner
    SQL File EndpointsNpgsqlRest (only native support)
    Raw PerformanceNpgsqlRest (2.6x faster)
    Self-Hosted PlatformNpgsqlRest (single binary)
    Table/View Query FlexibilityPostgREST / Supabase
    Function-Based APIsNpgsqlRest
    Per-Endpoint ConfigurationNpgsqlRest
    Static Files + Template ParsingNpgsqlRest
    Frontend Code GenerationNpgsqlRest / Supabase
    Deployment SimplicityNpgsqlRest / PostgREST (tie)
    Authentication OptionsNpgsqlRest
    Passkey/WebAuthnNpgsqlRest (only native support)
    File HandlingNpgsqlRest
    Excel Export (native .xlsx)NpgsqlRest (only native support)
    Enterprise Features (caching, rate limiting)NpgsqlRest
    Conditional Caching (When rules)NpgsqlRest (only native support)
    Per-User Rate Limiting (Partition)NpgsqlRest (only declarative)
    Multi-Tier Auth Schemes (per-scope sessions)NpgsqlRest (only declarative)
    Multi-Tenant search_path from JWTNpgsqlRest (declarative)
    Column Encryption (encrypt/decrypt)NpgsqlRest (only native support)
    Error Handling (policies, RFC 7807)NpgsqlRest (only configurable)
    Security HeadersNpgsqlRest (only built-in)
    Health Checks (Kubernetes/Docker)NpgsqlRest / PostgREST
    PostgreSQL Statistics EndpointsNpgsqlRest / Supabase
    Custom Types / Nested JSONAll three (different strengths)
    External Service Integration (proxy)NpgsqlRest (declarative, zero infrastructure)
    Custom Code RuntimeSupabase (Edge Functions)
    Real-TimeSupabase (WebSockets) / NpgsqlRest (SSE)
    Managed HostingSupabase
    Visual DashboardSupabase
    Maturity/CommunityPostgREST / Supabase

    NpgsqlRest is a complete self-hosted platform where the fastest way to create an endpoint is to write a SQL file. No CREATE FUNCTION, no database deployment — just a .sql file with a comment annotation. For complex logic, PostgreSQL functions are fully supported with true end-to-end type checking. Beyond API generation, it serves static files with template parsing, generates TypeScript clients, and includes built-in authentication — all in a single ~30MB binary, with per-endpoint caching, rate limiting, timeout, and auth rules version-controlled in SQL comments.

    PostgREST shines for flexible client-side queries with its GraphQL-like resource embedding, 28+ filtering operators, aggregates, and pagination. It's the right choice when your API consumers need to compose their own queries against tables and views. PostgREST is API-only—you'll need additional services for auth, static files, and file uploads.

    Supabase is ideal when you want managed cloud hosting with a visual dashboard—especially for teams that prefer not to manage infrastructure. While both NpgsqlRest and Supabase are platforms, Supabase requires 7+ services for self-hosting whereas NpgsqlRest is a single binary.


    Comments

    + + + + \ No newline at end of file diff --git a/blog/optimization-labels-101.html b/blog/optimization-labels-101.html new file mode 100644 index 000000000..0360b3d4f --- /dev/null +++ b/blog/optimization-labels-101.html @@ -0,0 +1,40 @@ + + + + + + NpgsqlRest Story Told Without AI | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    Human Written

    PostgreSQL Optimization Labels 101

    January 2026 · PostgreSQLOptimizationFunctionVOLATILESTABLEIMMUTABLE

    Originally published on Medium

    When creating a PostgreSQL function or procedure, you can declare many different labels that affect different aspects of your function. Some of them are optimization-oriented. Here is my 101 breakdown, because I keep forgetting them, and the best way to learn for me is to write.

    VOLATILE / STABLE / IMMUTABLE

    These are mutually exclusive.

    • VOLATILE (the default) — might change the database and return different results for the same parameters. PostgreSQL cannot optimize these at all, and this is the default behavior.

    • STABLE — can't change the database and will return the same results for the same parameters within the same query. Therefore, when called more than once in the same query, PostgreSQL will still execute this function only once, and hence the optimization.

    • IMMUTABLE — can't change the database and will always return the same results for the same parameters. Like a mathematical function, always the same. Most aggressive optimization will probably be called once per unique set of parameters and then cached. These types of functions can be used in indexing, where others cannot.

    Important

    If a function marked IMMUTABLE calls a STABLE function, it will revert to STABLE. If STABLE calls VOLATILE, it will revert to VOLATILE automatically, regardless of what is set during creation — so check the metadata.

    PARALLEL UNSAFE / RESTRICTED / SAFE

    Sometimes the query planner will opportunistically decide that some operation will run in parallel. This won't happen on a small set, only when the planner estimates a performance benefit. In that case you may see something like this in the planner:

    code
    Finalize Aggregate
    +    -> Gather
    +         Workers Planned: 2
    +         Workers Launched: 2
    • PARALLEL UNSAFE (the default) — forces planner to ditch parallel execution. These functions change databases and perform transactions.

    • PARALLEL RESTRICTED — restricts parallelism to the parallel group leader process. These functions can do temp tables, call cursors, and prepared statements.

    • PARALLEL SAFE — safe to run in parallel mode without restriction, enabling the planner to do parallelism when it chooses to do so.

    COST / ROWS

    COST (default: 100)

    The planner needs to know how much operations cost in terms of execution performance, and functions are indeed operations. PostgreSQL uses its own arbitrary measurement unit.

    • With a lower cost (typically 1–10), the planner will not care about optimization when calling this function, and it will call it any time it damn well pleases.
    • With a higher cost (1000+), the planner will try to optimize calls for this function.

    TIP

    Tackling this setting should be done if the query is indeed slow.

    ROWS (default: 1000)

    The number of rows returned is another piece of information for the planner that can be used to optimize calls and plan execution better. Therefore, this can only be used in functions that return sets (SETOF or TABLE).

    A planner cannot possibly know the number of rows returned, and it will assume that it is 1000. But you can, and if you do, you can set this parameter.

    The practical implications of this optimization label depend on which joining strategy the planner chooses, based on the row estimate:

    • Nested loop — a sort of database internal N+1, suitable strategy for small datasets
    • Hashed join — when the planner decides it will make hashes first, and then use them to compare joining keys, much more efficient for larger sets than nested join

    In any case, this parameter only makes sense if you tend to join a function that returns sets.

    CALLED ON NULL INPUT / RETURNS NULL ON NULL INPUT / STRICT

    If you have a function that is supposed to return NULL if one of the parameters is NULL, you can just say so by labeling it RETURNS NULL ON NULL INPUT or shorter STRICT. This tells the engine to avoid calling the function in that case — simply replace it with a NULL value.

    Default is CALLED ON NULL INPUT, which is normal behavior, and it doesn't need to be stated explicitly.


    This concludes my short series on PostgreSQL optimization labels (some might call them hints).

    Comments

    + + + + \ No newline at end of file diff --git a/blog/passkey-sql-auth.html b/blog/passkey-sql-auth.html new file mode 100644 index 000000000..00a9feddd --- /dev/null +++ b/blog/passkey-sql-auth.html @@ -0,0 +1,647 @@ + + + + + + Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Implementing WebAuthn Passkeys with Pure SQL and NpgsqlRest

    WebAuthn · Authentication · January 2026


    WebAuthn passkeys replace the weakest part of most systems - the password - with public-key cryptography tied to biometric or PIN verification on the user's device. There is no shared secret to phish, reuse across sites, or leak in a database breach.

    NpgsqlRest now supports built-in passkey authentication that lets you implement passwordless login with your authentication logic written entirely in PostgreSQL functions. No external authentication libraries, no third-party services—just your database, a few SQL functions, and a client-side script to call the browser's WebAuthn API (see the passkey.ts example you can use as a starting point).

    What Gets Stored (And What Doesn't)

    A common misconception about passkeys is that they store biometric data. They don't. Here's what actually happens:

    1. During registration, the user's device generates a public/private key pair. The private key never leaves the device and is protected by biometrics or a PIN
    2. Your database stores only the public key, a credential ID, and metadata like the signature counter
    3. During login, the device signs a challenge with the private key, and your server verifies it with the stored public key

    No fingerprints, no facial recognition data, no personal biometric information ever touches your server. You're storing cryptographic keys, not sensitive personal data.

    Architecture Overview

    NpgsqlRest's passkey implementation follows the WebAuthn specification with a SQL-first approach:

    mermaid
    flowchart LR
    +    A["Browser
    +    (Client Script)"] <--> B["NpgsqlRest
    +    (Endpoints + CBOR)"] <--> C["PostgreSQL
    +    (SQL Functions)"]
    • Browser: A client script that calls the browser's WebAuthn API and communicates with NpgsqlRest endpoints. See the passkey.ts example for a complete TypeScript implementation.
    • NpgsqlRest: Provides the HTTP endpoints and handles CBOR parsing/verification.
    • PostgreSQL: Your SQL functions control the entire authentication flow—challenge creation, user management, credential storage.

    The division of labor: NpgsqlRest does the cryptographic work (parsing CBOR attestation objects, verifying signatures with EC and RSA keys) while your SQL functions define the business logic.

    Complete Example Walkthrough

    The full source for the example below is in the examples/13_passkey directory.

    1. Database Schema

    First, create the tables to store users, passkeys, and challenges:

    sql
    sql
    -- Users table (can integrate with existing users)
    +create table users (
    +    user_id serial primary key,
    +    username text not null,
    +    email text,
    +    password text null,  -- null for passkey-only users
    +    created_at timestamptz default now()
    +);
    +create unique index on users(username);
    +
    +-- Passkeys table - stores the public keys
    +create table passkeys (
    +    credential_id bytea primary key,
    +    user_id int not null references users(user_id) on delete cascade,
    +    user_handle bytea unique not null,
    +    public_key bytea not null,
    +    public_key_algorithm int not null,
    +    sign_count bigint not null default 0,
    +    transports text[],
    +    backup_eligible boolean default false,
    +    device_name text,
    +    created_at timestamptz default now(),
    +    last_used_at timestamptz
    +);
    +
    +-- Challenge storage for replay protection
    +create table passkey_challenges (
    +    id bigint not null generated always as identity primary key,
    +    challenge bytea not null,
    +    user_id int,  -- null for authentication, set for registration
    +    operation text not null check (operation in ('registration', 'authentication')),
    +    expires_at timestamptz not null,
    +    created_at timestamptz default now()
    +);

    2. Challenge Functions

    The WebAuthn flow requires generating random challenges that are verified later. Here's the registration challenge function:

    sql
    sql
    create or replace function passkey_challenge_registration(_body json)
    +returns table (
    +    status int,
    +    message text,
    +    challenge text,
    +    challenge_id bigint,
    +    user_handle text,
    +    user_name text,
    +    user_display_name text,
    +    exclude_credentials text,
    +    user_context json
    +)
    +security definer
    +language plpgsql
    +as $$
    +declare
    +    _user_name text = _body->>'userName';
    +    _user_handle bytea;
    +    _challenge bytea;
    +    _challenge_id bigint;
    +begin
    +    assert _user_name is not null and _user_name <> '';
    +
    +    -- Generate new user handle (random 32 bytes)
    +    _user_handle = gen_random_bytes(32);
    +
    +    -- Generate challenge (random 32 bytes)
    +    _challenge = gen_random_bytes(32);
    +
    +    -- Store challenge for verification
    +    insert into passkey_challenges
    +        (challenge, user_id, operation, expires_at)
    +    values
    +        (_challenge, null, 'registration', now() + interval '5 minutes')
    +    returning id into _challenge_id;
    +
    +    return query select
    +        200,
    +        null::text,
    +        encode(_challenge, 'base64'),
    +        _challenge_id,
    +        encode(_user_handle, 'base64'),
    +        _user_name,
    +        coalesce(_body->>'displayName', _user_name),
    +        '[]'::text,
    +        json_build_object(
    +            'userName', _user_name,
    +            'email', _body->>'email',
    +            'deviceName', _body->>'deviceName'
    +        );
    +end;
    +$$;

    The function returns exactly the columns NpgsqlRest expects:

    • status: HTTP status code (200 to proceed)
    • challenge: Base64-encoded random bytes
    • challenge_id: Reference for verification
    • user_handle: Unique identifier stored with the passkey
    • user_context: JSON that gets passed through to the completion function

    3. Completion Functions

    After the browser creates the credential, NpgsqlRest verifies the attestation and calls your completion function:

    sql
    sql
    create or replace function passkey_complete_registration(
    +    _credential_id bytea,
    +    _user_handle bytea,
    +    _public_key bytea,
    +    _public_key_algorithm int,
    +    _sign_count bigint,
    +    _backup_eligible boolean,
    +    _transports text[],
    +    _user_context json
    +)
    +returns table (status int, message text, user_context json)
    +security definer
    +language plpgsql
    +as $$
    +declare
    +    _user_id int;
    +    _user_name text = _user_context->>'userName';
    +begin
    +    -- Create the user
    +    insert into users (username, email)
    +    values (_user_name, _user_context->>'email')
    +    returning user_id into _user_id;
    +
    +    -- Store the passkey
    +    insert into passkeys (
    +        credential_id, user_id, user_handle, public_key,
    +        public_key_algorithm, sign_count, transports,
    +        backup_eligible, device_name
    +    )
    +    values (
    +        _credential_id, _user_id, _user_handle, _public_key,
    +        _public_key_algorithm, _sign_count, _transports,
    +        _backup_eligible, _user_context->>'deviceName'
    +    );
    +
    +    return query select 200, null::text,
    +        json_build_object('userId', _user_id);
    +end;
    +$$;

    NpgsqlRest extracts the public key and algorithm from the CBOR attestation object before calling your function. You just store them.

    4. Authentication Function

    For login, the completion function verifies the user and returns claims for cookie/JWT authentication:

    sql
    sql
    create or replace function passkey_complete_authenticate(
    +    _credential_id bytea,
    +    _new_sign_count bigint,
    +    _user_context json,
    +    _analytics_data json default null
    +)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    email text,
    +    message jsonb
    +)
    +security definer
    +language plpgsql
    +as $$
    +declare
    +    _user_id int = (_user_context->>'id')::int;
    +begin
    +    -- Update sign count and last used timestamp
    +    update passkeys
    +    set sign_count = _new_sign_count, last_used_at = now()
    +    where credential_id = _credential_id;
    +
    +    -- Return user claims for authentication
    +    return query
    +    select
    +        'cookies' as scheme,
    +        u.user_id,
    +        u.username,
    +        u.email,
    +        jsonb_build_object(
    +            'userId', u.user_id,
    +            'username', u.username,
    +            'email', u.email
    +        )
    +    from users u
    +    where u.user_id = _user_id;
    +end;
    +$$;

    The scheme column tells NpgsqlRest which authentication scheme to use (cookies, JWT bearer, etc.).

    5. Configuration

    Enable passkey authentication in your appsettings.json. Minimal configuration:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "Enabled": true,
    +      "EnableRegister": true
    +    }
    +  }
    +}

    For custom SQL commands, specify the command settings:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "Enabled": true,
    +      "EnableRegister": true,
    +      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
    +      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
    +      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
    +      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
    +      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)"
    +    }
    +  }
    +}

    For the complete configuration reference, see Passkey Authentication Configuration.

    Key configuration options:

    OptionDescription
    UserVerificationRequirement"required" = must use biometric/PIN; "preferred" = use if available
    ResidentKeyRequirement"required" = true passwordless (no username field); "preferred" = user enters username first
    AttestationConveyance"none" for most apps; "direct" to verify authenticator hardware
    RateLimiterPolicyName of a configured rate limiter policy for brute-force protection
    ConnectionNameOptional named connection for multi-database setups
    CommandRetryStrategyRetry strategy for transient database errors (default: "default")

    6. Client-Side Implementation

    The passkey.ts script in the example provides a complete TypeScript implementation you can use as a template:

    typescript
    typescript
    // Registration
    +const result = await register({
    +    userName: 'alice',
    +    displayName: 'Alice',
    +    deviceName: 'MacBook Pro'
    +});
    +
    +if (result.success) {
    +    console.log('Registered with credential:', result.credentialId);
    +}
    +
    +// Login
    +const loginResult = await login({
    +    userName: 'alice'  // Optional for discoverable credentials
    +});
    +
    +if (loginResult.success) {
    +    const user = JSON.parse(loginResult.response);
    +    console.log('Logged in as:', user.username);
    +}

    The script handles:

    • Base64URL encoding/decoding for WebAuthn data
    • RFC 7807 Problem Details error parsing
    • Browser capability detection
    • All three flows: registration, login, and adding passkeys to existing accounts

    Three Authentication Flows

    NpgsqlRest supports three distinct passkey flows. Each endpoint internally executes a configured SQL command (typically a PostgreSQL function) that you define.

    1. Registration (New User with Passkey)

    For new users signing up with a passkey. Creates both the user account and passkey.

    mermaid
    flowchart LR
    +    subgraph Step1["Step 1: Get Challenge"]
    +        direction TB
    +        R1["POST /api/passkey/register/options
    +        ────────────────────────────
    +        Body:
    +        {
    +          userName: string ✓
    +          displayName?: string
    +          email?: string
    +        }"]
    +
    +        F1["passkey_challenge_registration($1)
    +        ────────────────────────────
    +        $1 = JSON body
    +
    +        Returns:
    +        • status, challenge, challenge_id
    +        • user_handle, user_name
    +        • exclude_credentials, user_context"]
    +
    +        RES1["Response:
    +        {
    +          challenge, challengeId
    +          rp: {id, name}
    +          user: {id, name, displayName}
    +          pubKeyCredParams, timeout
    +          excludeCredentials, userContext
    +        }"]
    +
    +        R1 --> F1 --> RES1
    +    end
    +
    +    subgraph Browser["Browser WebAuthn"]
    +        WA["navigator.credentials.create()
    +        ────────────────────────────
    +        User creates passkey
    +        (biometric/PIN)"]
    +    end
    +
    +    subgraph Step2["Step 2: Complete Registration"]
    +        direction TB
    +        R2["POST /api/passkey/register
    +        ────────────────────────────
    +        Body:
    +        {
    +          challengeId, credentialId ✓
    +          attestationObject ✓
    +          clientDataJSON ✓
    +          transports?, userContext ✓
    +        }"]
    +
    +        F2["① passkey_verify_challenge($1, $2)
    +           $1=challengeId, $2='registration'
    +           Returns: challenge bytea
    +
    +        ② passkey_complete_registration(...)
    +           $1=credentialId, $2=userHandle
    +           $3=publicKey, $4=algorithm
    +           $5=transports, $6=backupEligible
    +           $7=userContext
    +           Returns: status, message"]
    +
    +        RES2["Response:
    +        {
    +          success: true
    +          credentialId: base64url
    +        }"]
    +
    +        R2 --> F2 --> RES2
    +    end
    +
    +    Step1 --> Browser --> Step2
    EndpointExecutes SQL Command
    POST /api/passkey/register/optionsChallengeRegistrationCommand
    POST /api/passkey/registerCompleteRegistrationCommand

    Registration is Disabled by Default

    The standalone registration flow (EnableRegister: false by default) allows anyone to create an account with just a passkey. In most production systems, you'll want additional verification before creating accounts—email confirmation, admin approval, invitation codes, or CAPTCHA validation.

    For this reason, the recommended approach is:

    1. Create user accounts through your existing registration flow (with whatever verification you need)
    2. Let users add passkeys to their verified accounts using the Add Passkey flow

    The standalone registration endpoints are available when you need them (set EnableRegister: true), and the complete example demonstrates both approaches.

    2. Add Passkey (Existing User)

    For users who already have an account (maybe they logged in with password) and want to add a passkey. These endpoints require authentication.

    mermaid
    flowchart LR
    +    subgraph Step1["Step 1: Get Challenge"]
    +        direction TB
    +        R1["POST /api/passkey/add/options
    +        🔐 Requires JWT
    +        ────────────────────────────
    +        Headers: Authorization: Bearer
    +        Body (optional):
    +        {
    +          deviceName?: string
    +        }"]
    +
    +        F1["passkey_challenge_add_existing($1, $2)
    +        ────────────────────────────
    +        $1 = JWT claims (JSON)
    +        $2 = body JSON
    +
    +        Returns:
    +        • status, challenge, challenge_id
    +        • user_handle, user_name
    +        • exclude_credentials, user_context"]
    +
    +        RES1["Response:
    +        {
    +          challenge, challengeId
    +          user: {id, name, displayName}
    +          excludeCredentials: [...existing]
    +          userContext
    +        }"]
    +
    +        R1 --> F1 --> RES1
    +    end
    +
    +    subgraph Browser["Browser WebAuthn"]
    +        WA["navigator.credentials.create()
    +        ────────────────────────────
    +        User creates passkey
    +        (biometric/PIN)"]
    +    end
    +
    +    subgraph Step2["Step 2: Complete Add"]
    +        direction TB
    +        R2["POST /api/passkey/add
    +        🔐 Requires JWT
    +        ────────────────────────────
    +        Body:
    +        {
    +          challengeId, credentialId ✓
    +          attestationObject ✓
    +          clientDataJSON ✓
    +          transports?, userContext ✓
    +        }"]
    +
    +        F2["① passkey_verify_challenge($1, $2)
    +           $1=challengeId, $2='registration'
    +           Returns: challenge bytea
    +
    +        ② passkey_complete_add_existing(...)
    +           $1=credentialId, $2=userHandle
    +           $3=publicKey, $4=algorithm
    +           $5=transports, $6=backupEligible
    +           $7=userContext
    +           Returns: status, message"]
    +
    +        RES2["Response:
    +        {
    +          success: true
    +          credentialId: base64url
    +        }"]
    +
    +        R2 --> F2 --> RES2
    +    end
    +
    +    Step1 --> Browser --> Step2
    Endpoint (requires auth)Executes SQL Command
    POST /api/passkey/add/optionsChallengeAddExistingUserCommand
    POST /api/passkey/addCompleteAddExistingUserCommand

    3. Login

    For authenticating with an existing passkey.

    mermaid
    flowchart LR
    +    subgraph Step1["Step 1: Get Challenge"]
    +        direction TB
    +        R1["POST /api/passkey/login/options
    +        ────────────────────────────
    +        Body (optional):
    +        {
    +          userName?: string
    +        }
    +        Empty = discoverable credentials"]
    +
    +        F1["passkey_challenge_authentication($1, $2)
    +        ────────────────────────────
    +        $1 = userName (nullable)
    +        $2 = body JSON
    +
    +        Returns:
    +        • status, challenge, challenge_id
    +        • allow_credentials"]
    +
    +        RES1["Response:
    +        {
    +          challenge, challengeId
    +          rpId, timeout
    +          userVerification
    +          allowCredentials?
    +        }"]
    +
    +        R1 --> F1 --> RES1
    +    end
    +
    +    subgraph Browser["Browser WebAuthn"]
    +        WA["navigator.credentials.get()
    +        ────────────────────────────
    +        User authenticates
    +        (biometric/PIN)"]
    +    end
    +
    +    subgraph Step2["Step 2: Complete Login"]
    +        direction TB
    +        R2["POST /api/passkey/login
    +        ────────────────────────────
    +        Body:
    +        {
    +          challengeId, credentialId ✓
    +          authenticatorData ✓
    +          clientDataJSON ✓
    +          signature ✓
    +          userHandle?
    +        }"]
    +
    +        F2["① passkey_verify_challenge($1, $2)
    +           $1=challengeId, $2='authentication'
    +           Returns: challenge bytea
    +
    +        ② passkey_authenticate_data($1)
    +           $1=credentialId
    +           Returns: public_key, sign_count
    +                    user_context
    +
    +        ③ passkey_complete_authenticate(...)
    +           $1=credentialId, $2=newSignCount
    +           $3=userContext
    +           Returns: scheme, user_id
    +                    username, email"]
    +
    +        RES2["Response:
    +        {
    +          accessToken: JWT
    +          refreshToken?: JWT
    +          expiresIn: number
    +        }
    +        OR Set-Cookie"]
    +
    +        R2 --> F2 --> RES2
    +    end
    +
    +    Step1 --> Browser --> Step2
    EndpointExecutes SQL Command
    POST /api/passkey/login/optionsChallengeAuthenticationCommand
    POST /api/passkey/loginCompleteAuthenticateCommand

    Complete Configuration Reference

    This section provides a detailed reference for all PasskeyAuth configuration options and SQL commands.

    General Settings

    SettingDefaultDescription
    EnabledfalseMaster switch to enable passkey authentication
    EnableRegisterfalseEnable standalone registration (new users can sign up with passkey only)
    RateLimiterPolicynullName of a configured rate limiter policy to protect against brute-force
    ConnectionNamenullNamed connection for multi-database setups; uses default if null
    CommandRetryStrategy"default"Retry strategy for transient database errors; set to null to disable

    Relying Party Settings

    The Relying Party (RP) identifies your application to the authenticator.

    SettingDefaultDescription
    RelyingPartyIdnullDomain name (e.g., "example.com"). Auto-detected from request if null. Note: IP addresses are not permitted—use "localhost" for development
    RelyingPartyNamenullHuman-readable name shown during registration. Uses ApplicationName if null
    RelyingPartyOrigins[]Allowed origins for validation (e.g., ["https://example.com"]). Auto-detected if empty

    Endpoint Paths

    All paths are POST endpoints. Set to null to disable an endpoint.

    SettingDefaultDescription
    AddPasskeyOptionsPath"/api/passkey/add/options"Get options for adding passkey to existing user (requires auth)
    AddPasskeyPath"/api/passkey/add"Complete adding passkey to existing user (requires auth)
    RegistrationOptionsPath"/api/passkey/register/options"Get options for new user registration (no auth required)
    RegistrationPath"/api/passkey/register"Complete new user registration (no auth required)
    LoginOptionsPath"/api/passkey/login/options"Get login challenge (no auth required)
    LoginPath"/api/passkey/login"Complete authentication (no auth required)

    WebAuthn Settings

    SettingDefaultDescription
    ChallengeTimeoutMinutes5How long challenges remain valid before expiring
    ValidateSignCounttrueValidate signature counter to detect cloned authenticators
    UserVerificationRequirement"required"See below
    ResidentKeyRequirement"required"See below
    AttestationConveyance"none"See below

    UserVerificationRequirement

    Controls whether biometric/PIN verification is required:

    ValueBehaviorUse Case
    "required"User MUST verify with biometric or PINBanking, healthcare, any sensitive data
    "preferred"Request verification if available, proceed without if notMost consumer apps
    "discouraged"Don't request verification (proves device possession only)Low-security scenarios

    ResidentKeyRequirement

    Controls discoverable credentials (true passwordless):

    ValueBehaviorUse Case
    "required"Credential stored on authenticator; browser shows account pickerTrue passwordless (no username field)
    "preferred"Request discoverable if supportedGradual migration to passwordless
    "discouraged"Server must provide credential IDUsername-first flows

    AttestationConveyance

    Controls whether to verify authenticator hardware:

    ValueBehaviorUse Case
    "none"Accept any authenticatorMost apps (recommended)
    "indirect"Allow anonymized attestationRarely useful
    "direct"Request full attestation chainVerify specific hardware models
    "enterprise"Enterprise-managed attestationCorporate device policies

    SQL Commands Reference

    NpgsqlRest calls your SQL functions at specific points in each flow. Here's when each command is executed and what it should return.

    ChallengeAddExistingUserCommand

    When executed: User clicks "Add Passkey" in their account settings (they're already logged in)

    Endpoint: POST /api/passkey/add/options

    Parameters:

    • $1 = claims (json): User claims from the authenticated session
    • $2 = body (json): Request body (e.g., { "deviceName": "My Phone" })

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code. Return 200 to proceed, any other aborts
    messagetextError message when status ≠ 200
    challengetextBase64-encoded random bytes (32 bytes recommended)
    challenge_idbigint/uuid/textServer-side identifier to verify later
    user_handletextBase64-encoded random bytes for WebAuthn user.id
    user_nametextUsername shown in authenticator UI
    user_display_nametextDisplay name shown in authenticator UI
    exclude_credentialstextJSON array of existing credential IDs to prevent re-registration
    user_contextjsonPassed through to completion command (should contain user ID)

    Example:

    sql
    sql
    create or replace function passkey_challenge_add_existing(
    +    _claims json,
    +    _body json
    +)
    +returns table (
    +    status int, message text, challenge text, challenge_id bigint,
    +    user_handle text, user_name text, user_display_name text,
    +    exclude_credentials text, user_context json
    +)
    +language plpgsql as $$
    +declare
    +    _user_id int = (_claims->>'user_id')::int;
    +    _challenge bytea = gen_random_bytes(32);
    +    _challenge_id bigint;
    +    _existing_handle bytea;
    +begin
    +    -- Get existing user handle (or create new one)
    +    select user_handle into _existing_handle
    +    from passkeys where user_id = _user_id limit 1;
    +
    +    if _existing_handle is null then
    +        _existing_handle = gen_random_bytes(32);
    +    end if;
    +
    +    -- Store challenge
    +    insert into passkey_challenges (challenge, user_id, operation, expires_at)
    +    values (_challenge, _user_id, 'registration', now() + interval '5 minutes')
    +    returning id into _challenge_id;
    +
    +    return query
    +    select 200, null::text,
    +        encode(_challenge, 'base64'),
    +        _challenge_id,
    +        encode(_existing_handle, 'base64'),
    +        _claims->>'username',
    +        _claims->>'username',
    +        (select coalesce(jsonb_agg(jsonb_build_object(
    +            'type', 'public-key',
    +            'id', encode(credential_id, 'base64')
    +        )), '[]'::jsonb)::text from passkeys where user_id = _user_id),
    +        json_build_object('id', _user_id, 'deviceName', _body->>'deviceName');
    +end;
    +$$;

    ChallengeRegistrationCommand

    When executed: New user starts passkey-only registration (no existing account)

    Endpoint: POST /api/passkey/register/options

    Parameters:

    • $1 = body (json): Request body with user info (e.g., { "userName": "alice", "email": "alice@example.com" })

    Expected return columns: Same as ChallengeAddExistingUserCommand

    Key difference: The user_context should NOT contain an id field—this tells the completion command to create a new user.


    ChallengeAuthenticationCommand

    When executed: User initiates passkey login

    Endpoint: POST /api/passkey/login/options

    Parameters:

    • $1 = user_name (text): Username if provided, NULL for discoverable credential flow
    • $2 = body (json): Request body

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed)
    messagetextError message when status ≠ 200
    challengetextBase64-encoded random challenge
    challenge_idbigint/uuid/textServer-side identifier
    allow_credentialstextJSON array of credential IDs for this user (empty for discoverable)

    Example:

    sql
    sql
    create or replace function passkey_challenge_authentication(
    +    _user_name text,
    +    _body json
    +)
    +returns table (
    +    status int, message text, challenge text,
    +    challenge_id bigint, allow_credentials text
    +)
    +language plpgsql as $$
    +declare
    +    _challenge bytea = gen_random_bytes(32);
    +    _challenge_id bigint;
    +    _user_id int;
    +begin
    +    -- If username provided, look up user
    +    if _user_name is not null and _user_name <> '' then
    +        select user_id into _user_id from users where username = _user_name;
    +        if _user_id is null then
    +            return query select 400, 'Bad request'::text,
    +                null::text, null::bigint, null::text;
    +            return;
    +        end if;
    +    end if;
    +
    +    -- Store challenge
    +    insert into passkey_challenges (challenge, user_id, operation, expires_at)
    +    values (_challenge, _user_id, 'authentication', now() + interval '5 minutes')
    +    returning id into _challenge_id;
    +
    +    return query
    +    select 200, null::text,
    +        encode(_challenge, 'base64'),
    +        _challenge_id,
    +        coalesce((
    +            select jsonb_agg(jsonb_build_object(
    +                'type', 'public-key',
    +                'id', encode(credential_id, 'base64'),
    +                'transports', transports
    +            ))::text from passkeys where user_id = _user_id
    +        ), '[]');
    +end;
    +$$;

    VerifyChallengeCommand

    When executed: After browser returns credential, before cryptographic verification

    Used by: ALL flows (add passkey, registration, and login)

    Parameters:

    • $1 = challenge_id (bigint/uuid/text): The challenge_id from the options response
    • $2 = operation (text): Either "registration" or "authentication"

    Expected return: Single column challenge (bytea) containing the original challenge bytes, or NULL if not found/expired

    Example:

    sql
    sql
    create or replace function passkey_verify_challenge(
    +    _challenge_id bigint,
    +    _operation text
    +)
    +returns bytea
    +language plpgsql as $$
    +declare
    +    _challenge bytea;
    +begin
    +    -- Delete and return the challenge (one-time use)
    +    delete from passkey_challenges
    +    where id = _challenge_id
    +      and operation = _operation
    +      and expires_at > now()
    +    returning challenge into _challenge;
    +
    +    return _challenge;
    +end;
    +$$;

    AuthenticateDataCommand

    When executed: During login, after challenge verification but before signature verification

    Endpoint: POST /api/passkey/login

    Parameters:

    • $1 = credential_id (bytea): The credential ID from the browser

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed)
    messagetextError message when status ≠ 200
    public_keybyteaThe stored public key for signature verification
    public_key_algorithmintCOSE algorithm ID (-7 for ES256, -257 for RS256)
    sign_countbigintCurrent signature counter
    user_contextjsonPassed to CompleteAuthenticateCommand (typically contains user ID)

    Example:

    sql
    sql
    create or replace function passkey_authenticate_data(_credential_id bytea)
    +returns table (
    +    status int, message text, public_key bytea,
    +    public_key_algorithm int, sign_count bigint, user_context json
    +)
    +language plpgsql as $$
    +begin
    +    return query
    +    select 200, null::text,
    +        p.public_key,
    +        p.public_key_algorithm,
    +        p.sign_count,
    +        json_build_object('id', p.user_id)
    +    from passkeys p
    +    where p.credential_id = _credential_id;
    +
    +    if not found then
    +        return query select 400, 'Bad request'::text,
    +            null::bytea, null::int, null::bigint, null::json;
    +    end if;
    +end;
    +$$;

    CompleteAddExistingUserCommand

    When executed: After successful attestation verification when adding passkey to existing user

    Endpoint: POST /api/passkey/add

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - Unique credential identifier
    $2byteauser_handle - WebAuthn user.id
    $3byteapublic_key - Public key in COSE format
    $4intalgorithm - COSE algorithm (-7 = ES256, -257 = RS256)
    $5text[]transports - Transport hints (e.g., ["internal", "hybrid"])
    $6booleanbackup_eligible - Whether credential can be synced
    $7jsonuser_context - From ChallengeAddExistingUserCommand
    $8jsonanalytics_data - Optional client analytics with server-added IP

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 = success)
    messagetextError message when status ≠ 200

    CompleteRegistrationCommand

    When executed: After successful attestation verification for new user registration

    Endpoint: POST /api/passkey/register

    Parameters: Same as CompleteAddExistingUserCommand

    Expected return columns: Same as CompleteAddExistingUserCommand

    Key difference: This command should CREATE a new user since user_context doesn't contain an existing user ID.


    CompleteAuthenticateCommand

    When executed: After successful signature verification during login

    Endpoint: POST /api/passkey/login

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - The credential that was used
    $2bigintnew_sign_count - Updated signature counter
    $3jsonuser_context - From AuthenticateDataCommand
    $4jsonanalytics_data - Optional client analytics

    Expected return columns:

    The return columns depend on your authentication scheme. For cookie authentication:

    ColumnTypeDescription
    schemetextAuthentication scheme (e.g., "cookies")
    Any claim columnsvariousColumns become claims (e.g., user_id, username, email)
    messagejsonbOptional JSON returned in response body

    Example:

    sql
    sql
    create or replace function passkey_complete_authenticate(
    +    _credential_id bytea,
    +    _new_sign_count bigint,
    +    _user_context json,
    +    _analytics_data json default null
    +)
    +returns table (
    +    scheme text, user_id int, username text, email text, message jsonb
    +)
    +language plpgsql as $$
    +declare
    +    _user_id int = (_user_context->>'id')::int;
    +begin
    +    -- Update sign count
    +    update passkeys
    +    set sign_count = _new_sign_count, last_used_at = now()
    +    where credential_id = _credential_id;
    +
    +    -- Optional: Log authentication
    +    if _analytics_data is not null then
    +        insert into auth_audit_log (user_id, event_type, analytics_data, ip_address)
    +        values (_user_id, 'passkey_login', _analytics_data, _analytics_data->>'ip');
    +    end if;
    +
    +    -- Return claims for authentication
    +    return query
    +    select 'cookies', u.user_id, u.username, u.email,
    +        jsonb_build_object('userId', u.user_id, 'username', u.username)
    +    from users u where u.user_id = _user_id;
    +end;
    +$$;

    Column Name Configuration

    If your SQL functions use different column names, you can configure the mappings:

    json
    json
    {
    +  "PasskeyAuth": {
    +    "StatusColumnName": "status",
    +    "MessageColumnName": "message",
    +    "ChallengeColumnName": "challenge",
    +    "ChallengeIdColumnName": "challenge_id",
    +    "UserNameColumnName": "user_name",
    +    "UserDisplayNameColumnName": "user_display_name",
    +    "UserHandleColumnName": "user_handle",
    +    "ExcludeCredentialsColumnName": "exclude_credentials",
    +    "AllowCredentialsColumnName": "allow_credentials",
    +    "PublicKeyColumnName": "public_key",
    +    "PublicKeyAlgorithmColumnName": "public_key_algorithm",
    +    "SignCountColumnName": "sign_count",
    +    "UserContextColumnName": "user_context"
    +  }
    +}

    Analytics Data

    You can collect client-side analytics by passing analyticsData in completion requests. NpgsqlRest automatically adds the client's IP address:

    json
    json
    {
    +  "PasskeyAuth": {
    +    "ClientAnalyticsIpKey": "ip"
    +  }
    +}

    Set to null or empty string to disable IP collection.

    Security Considerations

    What NpgsqlRest Validates

    NpgsqlRest performs all the cryptographic verification required by WebAuthn:

    • Parses CBOR-encoded attestation objects
    • Extracts and validates public keys (EC P-256, EC P-384, RSA)
    • Verifies signatures using the stored public key
    • Validates origin and relying party ID
    • Checks challenge matches and hasn't expired
    • Optionally validates and updates signature counters

    What You Control

    Your SQL functions control the business logic:

    • User creation and lookup
    • Credential storage and retrieval
    • Challenge expiration policy
    • Which users can register/authenticate
    • What claims are returned after authentication
    • Audit logging and analytics

    Rate Limiting

    Always enable rate limiting on passkey endpoints:

    json
    json
    {
    +  "PasskeyAuth": {
    +    "RateLimiterPolicy": "passkey-limit"
    +  },
    +  "RateLimiting": {
    +    "Policies": {
    +      "passkey-limit": {
    +        "Type": "SlidingWindow",
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60
    +      }
    +    }
    +  }
    +}

    Advantages of This Approach

    1. SQL-First Logic

    Your authentication flow is defined in PostgreSQL functions. This means:

    • Version control with your schema migrations
    • Testable with standard SQL testing tools
    • No application code changes needed to modify auth logic
    • Full power of SQL for complex business rules

    2. No External Dependencies

    No FIDO2 libraries, no external authentication services. NpgsqlRest includes its own CBOR parser and cryptographic verification.

    3. Complete Control

    You decide:

    • How users are created and stored
    • What metadata to track (device names, last used, etc.)
    • How to handle edge cases (duplicate registrations, account recovery)
    • What authentication scheme to use (cookies, JWT, custom)

    4. Built-in Resilience

    The passkey endpoints support:

    • Connection retry for transient database failures
    • Command retry with configurable strategies
    • Named connections for multi-database architectures
    • Rate limiting to prevent abuse

    5. Privacy by Design

    Since you only store public keys:

    • No biometric data ever reaches your server
    • No password hashes to protect or rotate
    • Minimal personal data exposure in a breach
    • GDPR-friendly: public keys aren't personal data

    Getting Started

    1. Create the schema: Set up your users, passkeys, and challenges tables
    2. Implement the SQL functions: Challenge creation, verification, completion
    3. Configure PasskeyAuth: Enable it and point to your functions
    4. Add client code: Use the provided passkey.ts as a starting point
    5. Enable rate limiting: Protect against brute-force attacks

    The complete example includes everything you need: schema, SQL functions, configuration, and client code. For all configuration options, see the Passkey Authentication Configuration reference.

    Conclusion

    If you take one recommendation from this post: keep EnableRegister off, let users add passkeys to accounts created through your existing verified registration flow, and put a rate limiter on the endpoints. The rest—challenge storage, claims, audit logging—is ordinary SQL, version-controlled next to your schema.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/performance-scalability-high-availability-npgsqlrest.html b/blog/performance-scalability-high-availability-npgsqlrest.html new file mode 100644 index 000000000..6ad71c628 --- /dev/null +++ b/blog/performance-scalability-high-availability-npgsqlrest.html @@ -0,0 +1,532 @@ + + + + + + Performance, Scalability, and High Availability with NpgsqlRest | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Performance, Scalability, and High Availability with NpgsqlRest

    Performance · Scalability · High Availability · January 2026


    A functionally correct API is the easy part. Production adds the rest: caching to reduce load, retry logic to handle transient failures, rate limiting to protect your infrastructure, and high availability configuration to survive a server going down.

    NpgsqlRest has built-in support for all of these. This guide walks through each feature with practical examples.

    Functions or SQL files — same annotations, different placement

    Every annotation in this post works identically with PostgreSQL functions (via comment on function ... is '...') and with SQL files (via -- @annotation header comments in a .sql file). Same semantics, same defaults, same ordering rules — only the comment placement differs. Examples below use both forms; pick whichever fits your codebase.

    The same endpoint as a function and as a SQL file:

    sql
    sql
    -- As a function
    +create function get_settings() returns json language sql
    +begin atomic; select settings from app_config where id = 1; end;
    +
    +comment on function get_settings() is 'HTTP GET
    +@cached
    +@cache_expires_in 1 hour';
    sql
    sql
    -- As a SQL file: sql/get-settings.sql
    +-- HTTP GET
    +-- @cached
    +-- @cache_expires_in 1 hour
    +select settings from app_config where id = 1;

    Both produce GET /api/get-settings with a one-hour cached response. SQL files need the SqlFileSource plugin enabled in config ("NpgsqlRest": { "SqlFileSource": { "Enabled": true, "FilePattern": "sql/**/*.sql" } }) — see SQL File Source for the full reference.

    Caching Strategies

    Caching is your first line of defense against unnecessary database load. NpgsqlRest supports two complementary caching layers: HTTP caching (browser/CDN level) and server-side caching (application level).

    HTTP Cache Headers: The Fastest Cache

    The fastest request is the one that never reaches your server. HTTP caching via Cache-Control headers lets browsers and CDNs serve responses without touching your infrastructure at all.

    When a browser or CDN has a cached response that hasn't expired, your server receives zero requests. This is fundamentally different from server-side caching—with HTTP caching, there's no network round-trip, no connection pool usage, nothing.

    Setting Cache Headers in Annotations

    NpgsqlRest can set any HTTP response header directly from comment annotations using the Header-Name: value format:

    sql
    sql
    create function get_product_catalog()
    +returns json
    +language sql
    +begin atomic;
    +select json_agg(p) from products p where active;
    +end;
    +
    +comment on function get_product_catalog() is
    +'HTTP GET
    +Cache-Control: public, max-age=3600';

    Or as a SQL file:

    sql
    sql
    -- sql/get-product-catalog.sql
    +-- HTTP GET
    +-- Cache-Control: public, max-age=3600
    +select json_agg(p) from products p where active;

    This tells browsers and CDNs to cache the response for 1 hour (3600 seconds). You can combine multiple headers:

    sql
    sql
    comment on function get_static_config() is
    +'HTTP GET
    +Cache-Control: public, max-age=86400
    +ETag: "v1.0"
    +Vary: Accept-Encoding';

    Common Cache-Control directives:

    DirectiveMeaning
    publicCan be cached by browsers and CDNs
    privateOnly browser can cache, not CDNs
    max-age=NCache for N seconds
    no-cacheMust revalidate before using cached copy
    no-storeNever cache

    For authenticated endpoints, use private to prevent CDNs from serving one user's data to another:

    sql
    sql
    comment on function get_user_dashboard() is
    +'HTTP GET
    +@authorize
    +Cache-Control: private, max-age=300';

    Cache Busting Technique

    The challenge with aggressive HTTP caching is invalidation. How do you force clients to fetch fresh data when the underlying data changes?

    The standard technique is cache busting via URL parameters. Add a version or timestamp parameter that changes when data is updated:

    code
    GET /api/products?v=1
    +GET /api/products?v=2    # After data update - treated as new URL

    The parameter doesn't need to do anything server-side—it simply makes the URL unique, causing browsers and CDNs to treat it as a completely different resource. Your function can ignore it entirely:

    sql
    sql
    create function get_products(_v text default null)
    +returns json
    +language sql
    +begin atomic;
    +    select json_agg(p) from products p;
    +end;
    +
    +comment on function get_products(text) is
    +'HTTP GET
    +Cache-Control: public, max-age=31536000';

    The _v parameter exists only to differentiate cache keys. When you update your products, change v=1 to v=2 in your client code, and every user gets fresh data. With this pattern, you can set very long cache times (the example uses 1 year) because you control invalidation through URL changes.

    This technique pays off most when combined with CDNs like Cloudflare or CloudFront, which cache at edge locations globally.

    Server-Side Caching

    When HTTP caching isn't sufficient—perhaps you need more control over invalidation, or you're dealing with authenticated endpoints that can't be cached by CDNs—NpgsqlRest provides built-in server-side caching.

    Enabling Server Cache

    Enable caching in your configuration:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory"
    +  }
    +}

    Then annotate specific endpoints:

    sql
    sql
    create function get_app_settings()
    +returns json
    +language sql
    +begin atomic;
    +select settings from app_config where id = 1;
    +end;
    +
    +comment on function get_app_settings() is
    +'HTTP GET
    +@cached';

    Or as a SQL file:

    sql
    sql
    -- sql/get-app-settings.sql
    +-- HTTP GET
    +-- @cached
    +select settings from app_config where id = 1;

    When a cached endpoint is hit and the cache is warm, no database connection is opened. This is critical for high-traffic endpoints—you're not just saving database CPU cycles, you're preserving your connection pool for requests that actually need it.

    Cache Keys by Parameter

    For endpoints with parameters, specify which parameters form the cache key:

    sql
    sql
    create function get_user_profile(_user_id int)
    +returns json
    +language sql
    +begin atomic;
    +select row_to_json(u) from users u where id = _user_id;
    +end;
    +
    +comment on function get_user_profile(int) is
    +'HTTP GET
    +@cached _user_id';

    Or as a SQL file — note that positional parameters need @param to get a readable name:

    sql
    sql
    -- sql/get-user-profile.sql
    +-- HTTP GET
    +-- @param $1 user_id int
    +-- @cached user_id
    +select row_to_json(u) from users u where id = $1;

    Different _user_id values create separate cache entries. Requests for user 1 and user 2 are cached independently.

    For multiple parameters:

    sql
    sql
    comment on function get_report(int, text) is
    +'HTTP GET
    +@cached _year, _department';

    Cache Expiration

    Control how long entries stay cached:

    sql
    sql
    comment on function get_dashboard_stats() is
    +'HTTP GET
    +@cached
    +@cache_expires_in 5m';

    Supported formats: 10s (seconds), 5m (minutes), 1h (hour), 1d (day), 1w (week).

    Cache Types

    NpgsqlRest supports three cache backends, each suited for different deployment scenarios.

    Memory Cache

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 60
    +  }
    +}

    Best for:

    • Single-instance deployments
    • Development environments
    • Low-memory scenarios where you don't want external dependencies

    Limitation: Each application instance maintains its own cache. If you run multiple instances, they won't share cached data.

    Redis Cache

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false"
    +  }
    +}

    Best for:

    • Multi-instance deployments
    • Production environments requiring cache sharing
    • Scenarios where cache persistence across restarts matters

    Hybrid Cache

    The most sophisticated option, using Microsoft's HybridCache:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": true,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false",
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "HybridCacheLocalCacheExpiration": "1 minute"
    +  }
    +}

    Hybrid cache provides:

    • L1 (local) cache: Fast in-memory access for frequently used data
    • L2 (Redis) cache: Shared storage across instances
    • Stampede protection: Prevents multiple concurrent requests from hitting the database when cache expires

    Without stampede protection, when a popular cached entry expires, every concurrent request tries to refresh it simultaneously—potentially overwhelming your database. Hybrid cache ensures only one request fetches fresh data while others wait.

    You can use Hybrid cache without Redis for stampede protection alone:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": false
    +  }
    +}

    Cache Invalidation Endpoints

    NpgsqlRest can automatically create invalidation endpoints for programmatic cache clearing:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "InvalidateCacheSuffix": "invalidate"
    +  }
    +}

    Usage:

    code
    GET /api/get-user/?id=123           -> Returns cached user data
    +GET /api/get-user/invalidate?id=123 -> Clears cache entry
    +GET /api/get-user/?id=123           -> Fresh data from database

    The invalidation endpoint:

    • Uses the same authentication as the original endpoint
    • Accepts the same parameters (to match the cache key)
    • Returns {"invalidated": true} or {"invalidated": false}

    Use it to invalidate cache right after data modifications instead of waiting for expiration.

    Caching Set-Returning Functions

    Server-side caching works for functions returning multiple rows:

    sql
    sql
    create function get_all_users()
    +returns table(id int, name text)
    +language sql
    +begin atomic;
    +select id, name from users;
    +end;
    +
    +comment on function get_all_users() is
    +'HTTP GET
    +@cached
    +@cache_expires_in 5 minutes';

    Protect against caching excessively large result sets:

    json
    json
    {
    +  "CacheOptions": {
    +    "MaxCacheableRows": 1000
    +  }
    +}

    Results exceeding this limit are returned but not cached—preventing memory issues from unexpectedly large queries.

    Cache Profiles

    A single root cache type isn't always enough. You might want fast per-user data in Memory, shared session data in Redis, and historical analytics with a long TTL on a third backend — all in one application. Cache profiles let you register multiple named caching policies, each with its own backend, default expiration, key shape, and conditional rules, and let endpoints opt into them with the @cache_profile annotation.

    jsonc
    jsonc
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "Profiles": {
    +      "fast_memory": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 minute",
    +        "Parameters": ["user_id"]
    +      },
    +      "shared_redis": {
    +        "Enabled": true,
    +        "Type": "Redis",
    +        "Expiration": "1 hour"
    +      },
    +      "timeseries": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 hour",
    +        "Parameters": ["from", "to", "live"],
    +        "When": [
    +          { "Parameter": "live", "Value": true, "Then": "skip" },
    +          { "Parameter": "to",   "Value": null, "Then": "5 minutes" }
    +        ]
    +      }
    +    }
    +  }
    +}
    sql
    sql
    -- Per-user 1-minute cache via the fast Memory profile
    +comment on function get_my_dashboard(_user_id int) is
    +'HTTP GET
    +@authorize
    +@cache_profile fast_memory';
    +
    +-- 1-hour distributed cache via Redis
    +comment on function get_global_metrics() is
    +'HTTP GET
    +@cache_profile shared_redis';
    +
    +-- Long cache for historical queries; short cache for "until-now";
    +-- bypass entirely when ?live=true
    +comment on function compute_timeseries(_from text, _to text default null, _live boolean default false) is
    +'HTTP GET
    +@cache_profile timeseries';

    The same timeseries profile applied to a SQL file — the profile's When rules don't care whether the endpoint is a function or a file, they just inspect the resolved parameter values at request time:

    sql
    sql
    -- sql/compute-timeseries.sql
    +-- HTTP GET
    +-- @param $1 from text
    +-- @param $2 to   text    default null
    +-- @param $3 live boolean default false
    +-- @cache_profile timeseries
    +select * from timeseries_data($1, $2)
    +where ($3 = false or now() - interval '1 minute' < $2::timestamp);

    Three things to know:

    • When rules are evaluated against request parameters at request time, first match wins. Each rule's Then is either "skip" (bypass cache entirely) or a PostgreSQL interval (override the TTL when writing). This is what makes profiles more expressive than the root cache.
    • Backend pooling — all profiles of the same Type share one backend instance. If no profile uses Redis and the root Type isn't Redis, no Redis connection is opened, even if RedisConfiguration is set.
    • Validation at startup@cache_profile referencing an unknown name fails startup with a single exception listing every unresolved name and the endpoints that referenced each. No silent fall-throughs.

    Endpoints without @cache_profile continue to use the root cache, so profiles are purely additive. See Cache Profiles for the full reference.

    Retry Strategies

    Transient failures are inevitable in distributed systems. Database connections drop, servers restart, deadlocks occur. NpgsqlRest provides two levels of retry handling: connection retries and command retries.

    Connection Retries

    Connection retry handles failures when establishing a database connection:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    }
    +  }
    +}

    The RetrySequenceSeconds array defines delays between attempts:

    • First retry: after 1 second
    • Second retry: after 3 seconds
    • Third retry: after 6 seconds
    • Fourth retry: after 12 seconds

    Default error codes cover common transient scenarios:

    CodeDescription
    08000General connection error
    08003Connection lost
    08006Connection failed
    53300Too many connections
    57P03Server starting up
    40001Serialization failure

    For high-availability deployments where brief connection issues are expected during failovers:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 4, 8, 16, 32],
    +      "ErrorCodes": ["08000", "08003", "08006", "57P03"]
    +    }
    +  }
    +}

    Command Retries

    Command retry handles failures during query execution—after the connection is established:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": [
    +          "40001", "40P01",
    +          "08000", "08003", "08006", "08001", "08004",
    +          "53000", "53100", "53200", "53300", "53400",
    +          "57P01", "57P02", "57P03",
    +          "55P03", "55006", "55000"
    +        ]
    +      }
    +    }
    +  }
    +}

    Note the first retry is 0 (immediate)—for serialization failures and deadlocks, immediate retry often succeeds because the conflict is resolved.

    Multiple Retry Strategies

    Define different strategies for different workloads:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
    +      },
    +      "aggressive": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
    +      },
    +      "minimal": {
    +        "RetrySequenceSeconds": [0, 1],
    +        "ErrorCodes": ["40001", "40P01"]
    +      }
    +    }
    +  }
    +}

    Assign strategies per endpoint:

    sql
    sql
    -- Critical payment processing - aggressive retries
    +comment on function process_payment() is
    +'HTTP POST
    +@retry_strategy aggressive';
    +
    +-- Fast lookup - minimal retries to fail fast
    +comment on function quick_lookup() is
    +'HTTP GET
    +@retry_strategy minimal';

    The same as SQL files:

    sql
    sql
    -- sql/process-payment.sql
    +-- HTTP POST
    +-- @retry_strategy aggressive
    +call process_payment_tx($1, $2);
    sql
    sql
    -- sql/quick-lookup.sql
    +-- HTTP GET
    +-- @param $1 id int
    +-- @retry_strategy minimal
    +select * from lookup_table where id = $1;

    PostgreSQL Error Code Classes

    Understanding error codes helps you configure appropriate retry behavior:

    ClassCodesDescription
    4040001, 40P01Serialization failures, deadlocks—always retry
    0808000-08P01Connection issues—retry with backoff
    5353000-53400Resource constraints (connections, memory, disk)
    5757P01-57P03Operator intervention (shutdown, restart)
    5555P03, 55006Lock contention

    The full list is available in the PostgreSQL Error Codes documentation.

    Rate Limiting

    Rate limiting protects your API from abuse and keeps one client from starving the rest. NpgsqlRest integrates ASP.NET Core's rate limiting middleware with four policy types.

    Enabling Rate Limiting

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    "Policies": {}
    +  }
    +}

    Breaking change in 3.13.0

    RateLimiterOptions:Policies is now an object keyed by policy name, not an array of objects with a "Name" field. Earlier versions of this post showed the array form — if you copied from there, migrate to the keyed-object form below. Old configs fail at startup with a clear InvalidOperationException.

    Fixed Window

    Limits requests within fixed time intervals:

    json
    json
    {
    +  "Policies": {
    +    "fixed": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "QueueLimit": 10
    +    }
    +  }
    +}

    100 requests allowed per 60-second window. When the limit is reached, up to 10 additional requests queue and wait for the next window.

    Apply to endpoints:

    sql
    sql
    comment on function public_api() is
    +'HTTP GET
    +@rate_limiter_policy fixed';

    Sliding Window

    Smoother rate limiting using overlapping segments:

    json
    json
    {
    +  "Policies": {
    +    "sliding": {
    +      "Type": "SlidingWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "SegmentsPerWindow": 6
    +    }
    +  }
    +}

    The window is divided into 6 segments (10 seconds each). As time passes, old segments expire gradually rather than all at once—preventing burst traffic at window boundaries.

    Token Bucket

    Allows controlled bursting while maintaining overall rate:

    json
    json
    {
    +  "Policies": {
    +    "bucket": {
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "TokenLimit": 100,
    +      "TokensPerPeriod": 10,
    +      "ReplenishmentPeriodSeconds": 10
    +    }
    +  }
    +}

    The bucket holds up to 100 tokens. Every 10 seconds, 10 tokens are added. A burst of 100 requests is allowed, but sustained rate is limited to 1 request per second (10 tokens per 10 seconds).

    Ideal for APIs where occasional bursts are acceptable but you want to prevent sustained abuse.

    Concurrency Limiting

    Limits simultaneous requests rather than rate:

    json
    json
    {
    +  "Policies": {
    +    "concurrency": {
    +      "Type": "Concurrency",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "QueueLimit": 5,
    +      "OldestFirst": true
    +    }
    +  }
    +}

    Only 10 requests can execute concurrently. Additional requests queue (up to 5) until a slot opens.

    Use it for expensive operations where you want to cap database load regardless of request rate:

    sql
    sql
    comment on function generate_large_report() is
    +'HTTP POST
    +@rate_limiter_policy concurrency';

    Same thing as a SQL file:

    sql
    sql
    -- sql/generate-large-report.sql
    +-- HTTP POST
    +-- @rate_limiter_policy concurrency
    +select build_report_payload();

    Per-User Rate Limiting (Partitions)

    Out of the box, every request under a given policy shares a single global bucket — 100 requests per minute means 100 across all users, combined. That's rarely what you want for authenticated APIs, where one heavy user can starve everyone else.

    A Partition block makes each request resolve its own bucket based on something from HttpContext: a claim, the client IP, a header, or a static fallback. The first source that resolves to a non-empty value wins.

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    },
    +    "throttle_anon_only": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "BypassAuthenticated": true,
    +        "Sources": [{ "Type": "IpAddress" }]
    +      }
    +    }
    +  }
    +}

    per_user gives each authenticated user their own 100-per-minute bucket, falls back to per-IP for anonymous requests, and lumps anything else into a shared anonymous bucket. throttle_anon_only waves signed-in users through entirely (BypassAuthenticated: true) and applies a stricter 10-per-minute limit per IP for everyone else — a common pattern for protecting unauthenticated endpoints from scraping.

    Policies without a Partition block still use a single global bucket, so partitioning only kicks in where you ask for it.

    Combining Policies

    Different endpoints can use different policies:

    sql
    sql
    -- Public API: strict rate limiting
    +comment on function public_search() is
    +'HTTP GET
    +@rate_limiter_policy fixed';
    +
    +-- Authenticated users: more generous limits
    +comment on function user_dashboard() is
    +'HTTP GET
    +@authorize
    +@rate_limiter_policy sliding';
    +
    +-- Expensive operations: concurrency limited
    +comment on function export_data() is
    +'HTTP POST
    +@authorize
    +@rate_limiter_policy concurrency';

    Thread Pool Optimization

    Under high load, the .NET thread pool becomes a critical factor in API performance. By default, the thread pool starts with a small number of threads and grows slowly—adding only one thread every 500 milliseconds when all threads are busy. For high-throughput APIs handling thousands of concurrent requests, this gradual growth creates latency spikes during traffic bursts.

    The Thread Injection Problem

    When your API receives a burst of requests, here's what happens:

    1. The thread pool has its minimum number of threads (typically equal to CPU cores)
    2. All threads become busy handling requests
    3. New requests arrive but no threads are available
    4. The thread pool waits 500ms before creating a new thread
    5. This repeats for each additional thread needed

    If you have 8 CPU cores and suddenly receive 100 concurrent requests, it could take 46 seconds ((100-8) × 0.5s) for the thread pool to grow large enough—during which time requests queue and latency degrades.

    Configuring Minimum Threads

    NpgsqlRest exposes thread pool settings so you can eliminate this cold-start penalty:

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": 100,
    +    "MinCompletionPortThreads": 100
    +  }
    +}

    With MinWorkerThreads set to 100, the thread pool immediately has 100 threads available. New requests don't wait for thread injection—they execute immediately on pre-allocated threads.

    Worker Threads vs Completion Port Threads

    The thread pool manages two types of threads:

    TypePurposeWhen to Increase
    Worker ThreadsCPU-bound work, synchronous operationsHigh CPU utilization, synchronous code paths
    Completion Port ThreadsAsync I/O operations (database queries, HTTP)Many concurrent async operations

    For database APIs like NpgsqlRest, both matter:

    • Worker threads handle request processing and synchronous code
    • Completion port threads handle async database I/O via Npgsql

    High-Throughput Configuration

    For APIs expecting thousands of concurrent requests:

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": 200,
    +    "MinCompletionPortThreads": 200,
    +    "MaxWorkerThreads": 1000,
    +    "MaxCompletionPortThreads": 1000
    +  }
    +}

    This configuration:

    • Pre-allocates 200 threads of each type (no injection delays up to 200 concurrent requests)
    • Allows growth up to 1000 threads under extreme load
    • Balances memory usage against responsiveness

    Sizing Guidelines

    There's no universal formula, but here are starting points:

    Expected Concurrent RequestsMinWorkerThreadsMinCompletionPortThreads
    Up to 505050
    50-200100100
    200-500200200
    500-1000300300
    1000+400-500400-500

    Key considerations:

    • Memory: Each thread consumes ~1MB of stack space. 500 threads ≈ 500MB additional memory
    • Context switching: Too many threads increases CPU overhead from switching between them
    • Actual concurrency: Set minimum threads to your expected concurrent request count, not total requests per second
    • Database connections: Ensure your PostgreSQL max_connections and connection pool can handle the concurrency

    When NOT to Increase Thread Pool Size

    Don't blindly increase thread counts. The defaults work well when:

    • Your requests are truly async (NpgsqlRest uses async Npgsql by default)
    • You're not blocking threads with synchronous waits
    • Your concurrency matches your CPU cores

    Over-provisioning threads wastes memory and can hurt performance through excessive context switching. Always benchmark with realistic load before and after changes.

    Example: Burst Traffic Handling

    For an API that normally handles 50 concurrent requests but experiences bursts of 500:

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": 100,
    +    "MinCompletionPortThreads": 100,
    +    "MaxWorkerThreads": 600,
    +    "MaxCompletionPortThreads": 600
    +  }
    +}

    This configuration:

    • Handles normal load instantly (100 > 50)
    • Handles burst starts with some injection delay but grows quickly to 600
    • Doesn't waste memory during quiet periods

    Combined with the retry and caching strategies above, your API remains responsive even under unexpected load spikes.

    High Availability

    For production deployments, single-server databases are a single point of failure. NpgsqlRest uses Npgsql's multi-host connection support for failover and load balancing across PostgreSQL clusters.

    Multi-Host Connections

    Specify multiple hosts in your connection string:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
    +  }
    +}

    Npgsql tries hosts in order. If the primary fails, it automatically connects to the next available host.

    Target Session Attributes

    Control which server type handles connections:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "Any",
    +      "ByConnectionName": {
    +        "ReadOnly": "Standby",
    +        "Primary": "Primary"
    +      }
    +    }
    +  }
    +}

    Available targets:

    TargetBehavior
    AnyAny available server (default)
    PrimaryOnly non-standby servers (for writes)
    StandbyOnly hot standby servers (for reads)
    PreferPrimaryPrimary if available, otherwise any
    PreferStandbyStandby if available, otherwise any
    ReadWriteMust accept read-write transactions
    ReadOnlyMust not accept read-write transactions

    Npgsql detects server role by querying pg_is_in_recovery(), which adds a small overhead to each connection. You can avoid this overhead by using separate named connections and specifying the connection directly in function annotations (covered below in Read Replica Routing).

    Load Balancing

    For distributing load across multiple servers of the same type, enable load balancing:

    code
    Host=replica1,replica2,replica3;Load Balance Hosts=true;Target Session Attributes=prefer-standby

    With Load Balance Hosts=true, Npgsql rotates through the host list round-robin style—each new connection starts at a different position, distributing load evenly.

    Read Replica Routing

    A common pattern: write to primary, read from replicas. Instead of relying on Target Session Attributes (which queries pg_is_in_recovery() on each connection), you can define separate named connections pointing directly to your servers:

    Configure multiple connection strings:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com;Database=mydb;Username=app;Password=secret",
    +    "ReadReplica": "Host=replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Load Balance Hosts=true"
    +  },
    +  "NpgsqlRest": {
    +    "UseMultipleConnections": true
    +  },
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "Primary",
    +      "ByConnectionName": {
    +        "ReadReplica": "PreferStandby"
    +      }
    +    }
    +  }
    +}

    Route read-heavy queries to replicas:

    sql
    sql
    comment on function get_analytics_data() is
    +'HTTP GET
    +@connection ReadReplica';
    +
    +comment on function heavy_report() is
    +'HTTP GET
    +@connection_name ReadReplica';

    Or as SQL files — @connection works the same way regardless of endpoint source:

    sql
    sql
    -- sql/get-analytics-data.sql
    +-- HTTP GET
    +-- @connection ReadReplica
    +select * from analytics_summary;

    The connection annotation references the connection string name. The endpoint uses that connection instead of the default.

    This approach is more efficient than multi-host connections with Target Session Attributes because:

    • No pg_is_in_recovery() query on each connection
    • Direct connection to the intended server
    • You control exactly which endpoints use which servers

    Production High-Availability Configuration

    A complete HA setup with failover, load balancing, caching, and retries:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Pooling=true;Maximum Pool Size=100",
    +    "ReadReplica": "Host=replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret;Load Balance Hosts=true;Pooling=true;Maximum Pool Size=50"
    +  },
    +  "ConnectionSettings": {
    +    "TestConnectionStrings": true,
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 5, 10]
    +    },
    +    "MultiHostConnectionTargets": {
    +      "Default": "PreferPrimary",
    +      "ByConnectionName": {
    +        "ReadReplica": "PreferStandby"
    +      }
    +    }
    +  },
    +  "NpgsqlRest": {
    +    "UseMultipleConnections": true
    +  },
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "57P03"]
    +      }
    +    }
    +  },
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": true,
    +    "RedisConfiguration": "redis-cluster:6379,abortConnect=false",
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "InvalidateCacheSuffix": "invalidate",
    +    "MaxCacheableRows": 1000
    +  },
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "DefaultPolicy": "standard",
    +    "Policies": {
    +      "standard": {
    +        "Type": "SlidingWindow",
    +        "Enabled": true,
    +        "PermitLimit": 1000,
    +        "WindowSeconds": 60,
    +        "SegmentsPerWindow": 6
    +      }
    +    }
    +  }
    +}

    This configuration:

    • Connects to primary by default, fails over to replicas if needed
    • Routes read queries to load-balanced replicas
    • Retries transient failures at both connection and command levels
    • Caches responses with Redis backend and stampede protection
    • Rate limits all endpoints to 1000 requests per minute

    Same Schema Requirement

    When using multiple connections, ensure all databases share the same schema. NpgsqlRest builds endpoints from database metadata at startup—the function signatures must match across all connections.

    This is naturally true for primary-replica setups (replicas are copies of the primary) but requires attention if using separate databases.

    Putting It All Together

    Here's how these features work together for a production API — mixing functions and SQL files in the same codebase, since both speak the same annotation language:

    sql
    sql
    -- Function — frequently accessed, rarely changes, aggressive caching
    +create function get_product_catalog()
    +returns json
    +language sql
    +begin atomic;
    +select json_agg(p) from products p where active;
    +end;
    +
    +comment on function get_product_catalog() is
    +'HTTP GET
    +@cached
    +@cache_expires_in 1h
    +@connection ReadReplica';
    sql
    sql
    -- SQL file — user-specific, moderate caching
    +-- sql/get-user-orders.sql
    +-- HTTP GET
    +-- @param $1 user_id int
    +-- @authorize
    +-- @cached user_id
    +-- @cache_expires_in 5m
    +-- @connection ReadReplica
    +select json_agg(o) from orders o where user_id = $1;
    sql
    sql
    -- Function — critical write operation needs plpgsql, retries, rate limiting
    +create function process_order(_order json)
    +returns json
    +language plpgsql security definer
    +as $$
    +begin
    +  -- Order processing logic
    +  return '{"success": true}'::json;
    +end;
    +$$;
    +
    +comment on function process_order(json) is
    +'HTTP POST
    +@authorize
    +@retry_strategy aggressive
    +@rate_limiter_policy order_limit';
    sql
    sql
    -- SQL file — expensive report, concurrency limited, long cache
    +-- sql/generate-sales-report.sql
    +-- HTTP GET
    +-- @param $1 start_date date
    +-- @param $2 end_date   date
    +-- @authorize roles admin,analyst
    +-- @cached start_date, end_date
    +-- @cache_expires_in 1d
    +-- @rate_limiter_policy concurrency
    +-- @connection ReadReplica
    +select json_build_object(
    +  'period', json_build_object('start', $1, 'end', $2),
    +  'data',   (select json_agg(r) from sales_summary r where date between $1 and $2)
    +);

    Summary

    What each feature buys you:

    FeatureBenefit
    HTTP CachingZero server load for cached responses
    Server CachingNo database connections for cache hits
    Hybrid CacheStampede protection + distributed storage
    Connection RetriesHandles failover transparently
    Command RetriesRecovers from transient query failures
    Rate LimitingProtects infrastructure from abuse
    Thread Pool TuningEliminates latency spikes during traffic bursts
    Multi-Host ConnectionsAutomatic failover between servers
    Load BalancingDistributes read load across replicas

    These features work together: cache misses that hit the database benefit from retry logic. Rate limiting prevents cache stampedes before they happen. Load balancing distributes the requests that make it past the cache.

    The result is an API that's not just fast under normal conditions, but resilient under adverse ones.

    Development Time Saved

    For comparison, here is what each feature costs to build by hand in a traditional backend:

    FeatureManual ImplementationNpgsqlRest
    HTTP Cache HeadersMiddleware + per-endpoint logic (~50-100 LOC)1 line annotation
    Server-Side CachingCache service + key generation + invalidation logic (~200-400 LOC)cached annotation + JSON config
    Redis/Hybrid CacheRedis client setup + serialization + stampede protection (~300-500 LOC)JSON config only
    Cache Invalidation EndpointsAdditional controller actions + cache key matching (~100-200 LOC)InvalidateCacheSuffix config
    Connection RetriesPolly policies + error handling + backoff logic (~150-300 LOC)JSON config only
    Command RetriesPer-command retry wrapper + error classification (~200-400 LOC)JSON config + optional annotation
    Rate LimitingMiddleware + policy configuration + storage (~200-400 LOC)JSON config + annotation
    Multi-Host FailoverConnection management + health checks + failover logic (~300-500 LOC)Connection string only
    Read Replica RoutingConnection factory + routing logic + context propagation (~200-400 LOC)connection annotation
    Thread Pool TuningStartup configuration + monitoring (~50-100 LOC)JSON config only

    Conservative estimates:

    • Manual implementation: 1,750 - 3,300 lines of code across services, middleware, and configuration
    • NpgsqlRest: ~50 lines of JSON configuration + a few single-line annotations
    • Time saved: 2-4 weeks of development, testing, and debugging

    Beyond line count, consider what you're not dealing with:

    • No unit tests for caching logic (NpgsqlRest handles it)
    • No integration tests for retry behavior
    • No debugging race conditions in cache invalidation
    • No maintaining compatibility across library upgrades
    • No security audits of custom retry/caching code

    The declarative approach means you describe what you want, not how to implement it. The infrastructure is implemented once, in NpgsqlRest, and reused by every endpoint.

    SQL File Source

    All performance features in this post — caching, retry strategies, rate limiting, timeouts — also work with SQL file endpoints.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/postgresql-bi-server-excel-csv-basic-auth.html b/blog/postgresql-bi-server-excel-csv-basic-auth.html new file mode 100644 index 000000000..70a178be1 --- /dev/null +++ b/blog/postgresql-bi-server-excel-csv-basic-auth.html @@ -0,0 +1,308 @@ + + + + + + Turn PostgreSQL into a BI Server: CSV Exports & Excel Integration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Turn PostgreSQL into a BI Server: CSV Exports, Basic Auth & Excel Integration

    January 2026 · PostgreSQLBIExcelCSVNpgsqlRest

    A PostgreSQL function with a CSV annotation is a report endpoint that Excel can consume directly over Basic Auth - with no application code in between.

    This post builds that into a working BI data delivery setup with NpgsqlRest: secured CSV endpoints, an audit trail, SSL, and Power Query on the consuming end. Change the function, and every connected Excel workbook picks up the change on its next refresh.

    Source Code: The complete working example is available at github.com/NpgsqlRest/npgsqlrest-docs/examples/5_csv_basic_auth

    The Architecture

    mermaid
    flowchart TB
    +    EX["Excel / Power BI
    +    (Power Query with Basic Auth)"]
    +
    +    EX -- "HTTPS + Basic Auth" --> NR["NpgsqlRest
    +    (CSV endpoint layer)"]
    +
    +    NR --> PG
    +
    +    subgraph PG["PostgreSQL"]
    +        SR["sales_report()
    +        Basic Auth + Audit
    +        SECURITY DEFINER"]
    +        SRP["sales_report_public()
    +        Public Access
    +        SECURITY DEFINER"]
    +        SR --> ST["sales table
    +        (protected data)"]
    +    end

    Your reporting logic lives in PostgreSQL functions, not application code. Business analysts connect Excel directly to these endpoints, and you control everything from one central place - the database.

    Defense in Depth: The Principle of Least Privilege

    This example implements the Principle of Least Privilege (PoLP) at the database level - a security architecture explained in detail in Database-Level Security: Building Secure Authentication with PostgreSQL.

    Even if a Basic Auth password is compromised, the damage is limited:

    mermaid
    flowchart TB
    +    AR["Application Role (app_user)
    +    Can ONLY access public schema"]
    +
    +    AR -- "Can only call functions" --> PUB
    +
    +    subgraph PUB["example_5_public schema (exposed)"]
    +        SR["sales_report() — SECURITY DEFINER
    +        Can access protected data, but ONLY this specific data"]
    +    end
    +
    +    PUB -- "SECURITY DEFINER elevates" --> PROT
    +
    +    subgraph PROT["example_5 schema (protected — NO direct access)"]
    +        ST["sales table — app_user CANNOT access directly"]
    +    end

    Why this matters:

    1. No direct table access - The application role (app_user) cannot query tables directly. Even with a stolen password, an attacker can only call the exposed functions.

    2. Controlled data exposure - Each SECURITY DEFINER function exposes exactly what it's designed to expose - nothing more. The sales_report() function returns sales data; it cannot be tricked into returning user passwords or other sensitive data.

    3. Search path protection - The set search_path = pg_catalog, pg_temp prevents search path injection attacks that could otherwise exploit SECURITY DEFINER functions.

    4. Audit trail built-in - The _user_name parameter records who accessed the data, providing accountability even for legitimate access.

    This is defense in depth: SSL protects credentials in transit, Basic Auth controls who can access endpoints, and PoLP limits what authenticated users can actually do. A compromised password gives access to the sales report - not the entire database.

    Creating CSV Endpoints

    Define a Reusable Type

    First, create a composite type that defines your report structure:

    sql
    sql
    create type example_5_public.sales_report_record as (
    +    exported_by text,
    +    order_id int,
    +    customer_name text,
    +    product text,
    +    quantity int,
    +    unit_price numeric(10,2),
    +    total numeric(10,2),
    +    order_date date
    +);

    This type can be reused across multiple functions and enables type composition (more on this later).

    The Secured Report Function

    sql
    sql
    create function example_5_public.sales_report(
    +    _user_name text default null  -- mapped from basic auth name claim
    +)
    +returns setof example_5_public.sales_report_record
    +language sql
    +set search_path = pg_catalog, pg_temp  -- Protect against search path attacks
    +security definer  -- Runs as migration user, not app_user
    +begin atomic;
    +select
    +    _user_name as exported_by,  -- Shows who exported the report
    +    order_id,
    +    customer_name,
    +    product,
    +    quantity,
    +    unit_price,
    +    total,
    +    order_date
    +from example_5.sales
    +order by order_date;
    +end;

    The _user_name parameter is automatically populated from the authenticated user's claims - providing a built-in audit trail of who accessed the data.

    CSV Annotations

    Everything HTTP-related is declared in the function comment:

    sql
    sql
    comment on function example_5_public.sales_report(text) is '
    +HTTP GET
    +@raw
    +@separator ,
    +@new_line \n
    +@columns
    +Content-Type: text/csv
    +Content-Disposition: attachment; filename="sales_report.csv"
    +@basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp
    +@user_params';
    AnnotationEffect
    @rawReturn plain text instead of JSON
    @separator ,Use comma as column delimiter
    @new_line \nUse newline as row delimiter
    @columnsInclude column header row
    Content-Type: text/csvSet proper MIME type
    Content-Disposition: attachmentMake browser download as file
    @basic_auth admin <hash>Require Basic Authentication
    @user_paramsMap authenticated user to _user_name parameter

    The result:

    csv
    csv
    "exported_by","order_id","customer_name","product","quantity","unit_price","total","order_date"
    +"admin",1,"Acme Corp","Widget Pro",50,29.99,1499.50,"2024-01-15"
    +"admin",2,"TechStart Inc","Widget Basic",100,19.99,1999.00,"2024-01-16"

    Type Reuse and Composition

    NpgsqlRest supports composite type expansion. When a function returns a table that includes a composite type alongside other columns, NpgsqlRest automatically expands the type's fields and merges them with the additional columns into a flat structure.

    This is DRY at the database level: define the record shape once and reuse it across endpoints, so consumers see consistent structures everywhere.

    The Problem: Schema Duplication

    In traditional approaches, each endpoint defines its own return structure:

    sql
    sql
    -- Endpoint 1: Sales report
    +create function sales_report() returns table (
    +    order_id int, customer_name text, product text, quantity int, ...
    +);
    +
    +-- Endpoint 2: Sales with audit info
    +create function sales_audit() returns table (
    +    order_id int, customer_name text, product text, quantity int, ...  -- duplicated!
    +    audited_by text, audit_date timestamp
    +);
    +
    +-- Endpoint 3: Sales summary
    +create function sales_summary() returns table (
    +    order_id int, customer_name text, product text, quantity int, ...  -- duplicated again!
    +    category text, region text
    +);

    When you need to add a field to sales data, you must update every function. Miss one, and your application has inconsistent data structures.

    The Solution: Composite Type Expansion

    Define the structure once as a composite type:

    sql
    sql
    create type example_5_public.sales_report_record as (
    +    exported_by text,
    +    order_id int,
    +    customer_name text,
    +    product text,
    +    quantity int,
    +    unit_price numeric(10,2),
    +    total numeric(10,2),
    +    order_date date
    +);

    Then compose new return types by combining this type with additional columns:

    sql
    sql
    create function example_5_public.sales_report_public()
    +returns table (
    +    record example_5_public.sales_report_record,  -- reuse the type
    +    message text                                   -- add context-specific column
    +)
    +language sql
    +set search_path = pg_catalog, pg_temp
    +security definer
    +begin atomic;
    +select
    +    (report.*)::example_5_public.sales_report_record,
    +    'WARNING: This is a public endpoint without authentication.' as message
    +from example_5_public.sales_report('public_user') report;
    +end;

    NpgsqlRest automatically expands this into a flat CSV structure:

    csv
    csv
    "exported_by","order_id","customer_name","product","quantity","unit_price","total","order_date","message"
    +"public_user",1,"Acme Corp","Widget Pro",50,29.99,1499.50,"2024-01-15","WARNING: This is a public endpoint..."

    The composite type's 8 fields are expanded inline, followed by the message column - all as a single flat row.

    Benefits for Applications

    1. Single Source of Truth

    Change the type definition once, and every function using it inherits the change:

    sql
    sql
    -- Add a new field to the type
    +alter type example_5_public.sales_report_record add attribute discount_applied boolean;

    Every endpoint returning sales_report_record now includes discount_applied. No hunting through code to update multiple functions.

    2. Consistent API Contracts

    When multiple endpoints share the same base type, applications can rely on consistent field names and types:

    typescript
    typescript
    // TypeScript clients can define a shared interface
    +interface SalesReportBase {
    +    exportedBy: string;
    +    orderId: number;
    +    customerName: string;
    +    // ... always the same structure
    +}
    +
    +// Extended interfaces compose naturally
    +interface SalesReportPublic extends SalesReportBase {
    +    message: string;
    +}
    +
    +interface SalesReportAudit extends SalesReportBase {
    +    auditedBy: string;
    +    auditTimestamp: Date;
    +}

    The database types map directly to application types.

    3. Function Chaining and Layered Access

    Composite types enable building layered APIs where higher-level functions wrap lower-level ones:

    sql
    sql
    -- Base function: secured, audited
    +create function sales_report(_user_name text)
    +returns setof sales_report_record ...
    +
    +-- Public wrapper: calls base function, adds warning
    +create function sales_report_public()
    +returns table (record sales_report_record, message text)
    +as $$ select *, 'Public access' from sales_report('public_user') $$;
    +
    +-- Admin wrapper: calls base function, adds admin metadata
    +create function sales_report_admin(_user_name text)
    +returns table (record sales_report_record, access_level text, can_edit boolean)
    +as $$ select *, 'admin', true from sales_report(_user_name) $$;

    Each layer adds its own context without duplicating the core data structure.

    4. Evolutionary Schema Design

    As your application grows, you can evolve types without breaking existing consumers:

    sql
    sql
    -- Week 1: Basic type
    +create type order_summary as (id int, total numeric);
    +
    +-- Week 4: Add field - existing functions still work
    +alter type order_summary add attribute status text;

    5. Cross-Module Consistency

    In larger applications with multiple schemas, composite types ensure consistency across different modules:

    sql
    sql
    -- Shared type in a common schema
    +create schema shared_types;
    +create type shared_types.customer_info as (
    +    customer_id int,
    +    customer_name text,
    +    email text,
    +    tier text
    +);
    +
    +-- Module A: Orders uses the shared type
    +create function orders.get_order_details(_order_id int)
    +returns table (
    +    customer shared_types.customer_info,  -- reused type
    +    order_id int,
    +    order_total numeric,
    +    order_date date
    +);
    +
    +-- Module B: Support tickets uses the same type
    +create function support.get_ticket_details(_ticket_id int)
    +returns table (
    +    customer shared_types.customer_info,  -- same type, consistent structure
    +    ticket_id int,
    +    issue text,
    +    status text
    +);

    Both modules produce consistent customer information structures. When customer fields change (e.g., adding phone), update the type once and both modules inherit the change.

    Securing with Basic Authentication

    Password Hashing

    Never store plain-text passwords in annotations. Generate a hash:

    bash
    bash
    npgsqlrest --hash secret123
    +# Output: lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp

    Use the hash in your annotation:

    sql
    sql
    basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp

    Multiple Users via Configuration

    For multiple users, define them in your configuration file instead of annotations:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "UseDefaultPasswordHasher": true,
    +        "Users": {
    +          "admin": "lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp",
    +          "analyst": "another_hashed_password_here",
    +          "finance": "yet_another_hash"
    +        }
    +      }
    +    }
    +  }
    +}

    Then use basic_auth without credentials in the annotation - it will validate against the configuration:

    sql
    sql
    comment on function example_5_public.sales_report(text) is '
    +HTTP GET
    +@raw
    +@separator ,
    +@columns
    +@basic_auth
    +@user_params';

    Database-Driven Authentication with ChallengeCommand

    For more elaborate authentication schemes, use ChallengeCommand to delegate authentication to a PostgreSQL function:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
    +      }
    +    }
    +  }
    +}

    The challenge function receives:

    • $1: Username from Basic Auth header
    • $2: Password from Basic Auth header
    • $3: Pre-validation result (if users defined in config)
    sql
    sql
    create function basic_auth_login(
    +    _username text,
    +    _password text,
    +    _validated bool
    +)
    +returns table (
    +    status bool,
    +    user_id int,
    +    user_name text,
    +    user_roles text[]
    +)
    +language plpgsql as $$
    +begin
    +    -- Validate against your users table
    +    return query
    +    select
    +        u.password_hash = crypt(_password, u.password_hash),
    +        u.id,
    +        u.username,
    +        array_agg(r.role_name)
    +    from users u
    +    left join user_roles r on r.user_id = u.id
    +    where u.username = _username
    +    group by u.id, u.username, u.password_hash;
    +end;
    +$$;

    See Basic Auth Configuration for full documentation.

    SSL Configuration

    Basic Authentication transmits credentials in Base64 encoding - this is NOT encryption. SSL/TLS is mandatory for production use.

    Setup Steps

    1. Export a development certificate:
    bash
    bash
    dotnet dev-certs https --export-path ./5_csv_basic_auth/localhost.pfx --password dev123
    1. Trust the certificate (optional, avoids browser warnings):
    bash
    bash
    dotnet dev-certs https --trust
    1. Configure Kestrel in your config file:
    json
    json
    {
    +  "Urls": "https://localhost:8080",
    +
    +  "Ssl": {
    +    "Enabled": true,
    +    "UseHttpsRedirection": false,
    +    "UseHsts": false
    +  },
    +
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:8080",
    +        "Certificate": {
    +          "Path": "./5_csv_basic_auth/localhost.pfx",
    +          "Password": "dev123"
    +        }
    +      }
    +    }
    +  },
    +
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "SslRequirement": "Required"
    +      }
    +    }
    +  }
    +}

    The SslRequirement setting controls SSL enforcement:

    ValueBehavior
    RequiredReject Basic Auth over plain HTTP
    WarningAllow HTTP but log a warning
    IgnoreAllow HTTP silently (debug only)

    No Code Generation Required

    Unlike traditional approaches that require generated API clients, CSV endpoints work with just a URL. The index.html in this example is one link:

    html
    html
    <a href="/api/example-5-public/sales-report">Download Sales Report (CSV)</a>

    Click the link, enter credentials when prompted, and the browser downloads the CSV file. No JavaScript, no build process, no dependencies.

    Excel Power Query Integration

    Connecting Excel to Your Endpoint

    In Excel, use Power Query to connect:

    powerquery
    powerquery
    let
    +    Source = Web.Contents("https://localhost:8080/api/example-5-public/sales-report"),
    +    ImportedCSV = Csv.Document(Source, [Delimiter=",", Encoding=TextEncoding.Utf8, QuoteStyle=QuoteStyle.Csv]),
    +    PromotedHeaders = Table.PromoteHeaders(ImportedCSV, [PromoteAllScalars=true])
    +in
    +    PromotedHeaders

    When you run this query, Excel prompts for Basic Auth credentials. Enter your username and password, and the data flows directly into your spreadsheet.

    The Power of Central Control

    When you modify the PostgreSQL function:

    sql
    sql
    -- Add a new column to the report
    +create or replace function example_5_public.sales_report(...)
    +returns table (
    +    -- existing columns...
    +    profit_margin numeric(5,2)  -- NEW!
    +)

    Every Excel workbook that connects to this endpoint will see the new column on the next refresh. You control the data structure from one place - the database - and every consumer automatically gets the update.

    This is the opposite of traditional BI systems where:

    • You export data to a data warehouse
    • Transform it through ETL pipelines
    • Build reports in a separate BI tool
    • Manually update when schemas change

    With this approach:

    • Functions define the report structure
    • Excel connects directly
    • Schema changes propagate automatically

    Production Considerations

    For production deployment within a corporate network:

    1. Use proper SSL certificates - Get certificates from your internal CA or a trusted provider
    2. Deploy on trusted network - These endpoints should only be accessible from your internal network
    3. Use strong passwords - Consider integrating with your corporate identity system via ChallengeCommand
    4. Audit access - The _user_name parameter in the secured endpoint creates an audit trail

    When You Still Need ETL

    This approach works well for many scenarios, but you may still need traditional ETL when:

    • High query load - Heavy reporting queries shouldn't compete with OLTP workloads on production databases
    • Cross-database joins - Data from multiple source systems needs consolidation
    • Historical snapshots - You need point-in-time data that changes over time
    • Complex transformations - Business logic requires extensive data cleansing or aggregation

    For these cases, consider replicating data to a dedicated reporting database (using PostgreSQL logical replication, for example) and pointing NpgsqlRest at that replica. You get the same simple CSV endpoints without impacting production.

    The Cost Comparison

    Consider what you'd typically need for a BI system:

    Traditional BIThis Approach
    PostgreSQL (or paid database)PostgreSQL (free)
    ETL tool licensesOften not needed*
    Data warehouse licensesOften not needed*
    BI tool licenses (Tableau, etc.)Not needed
    Development time for integrationMinutes
    Ongoing maintenanceSchema changes only

    *For simple reporting from a single database, ETL and data warehouses add unnecessary complexity. For high-load production systems or cross-database reporting, consider a read replica.

    With PostgreSQL (free) and NpgsqlRest (free), you have a complete BI data delivery system in minutes. Users get their data in Excel - the tool they already know - and can create any charts or analysis they want.

    Complete Example

    The full working example includes:

    • Schema setup with protected sales data
    • Secured endpoint with Basic Auth and audit trail
    • Public endpoint demonstrating type composition
    • SSL configuration for secure transmission
    • Excel workbook demonstrating Power Query integration
    • Simple HTML for direct downloads
    bash
    bash
    # Clone the repository
    +git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
    +
    +# Navigate to example
    +cd npgsqlrest-docs/examples/5_csv_basic_auth
    +
    +# Generate certificate
    +dotnet dev-certs https --export-path ./localhost.pfx --password dev123
    +
    +# Run migrations and start
    +npgsqlrest --config config.json

    Then open https://localhost:8080 in your browser or connect Excel to https://localhost:8080/api/example-5-public/sales-report.

    Conclusion

    PostgreSQL functions define the reports, annotations turn them into authenticated CSV endpoints, and Excel consumes them directly - structure changes reach every workbook on the next refresh. The caveats above still apply: point heavy reporting at a replica, and treat Basic Auth as internal-network-only, always behind SSL.

    Within those bounds, no BI licenses, no ETL pipelines, no data warehouse - just PostgreSQL functions serving CSV to Excel. That's what "database as the application" means in practice.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/postgresql-rest-api-benchmark-2024.html b/blog/postgresql-rest-api-benchmark-2024.html new file mode 100644 index 000000000..0f795a36a --- /dev/null +++ b/blog/postgresql-rest-api-benchmark-2024.html @@ -0,0 +1,36 @@ + + + + + + NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blog/postgresql-rest-api-benchmark-2025.html b/blog/postgresql-rest-api-benchmark-2025.html new file mode 100644 index 000000000..7d87af9c5 --- /dev/null +++ b/blog/postgresql-rest-api-benchmark-2025.html @@ -0,0 +1,36 @@ + + + + + + NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/blog/postgresql-rest-api-benchmark-2026.html b/blog/postgresql-rest-api-benchmark-2026.html new file mode 100644 index 000000000..acdccda27 --- /dev/null +++ b/blog/postgresql-rest-api-benchmark-2026.html @@ -0,0 +1,37 @@ + + + + + + PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    PostgreSQL REST API Benchmark 2026: 14 Frameworks Compared

    Benchmark · Performance · January 2026


    INFO

    I wanted to repeat the benchmark from last year for several big reasons:

    • First and foremost I wanted to make tests realistic and fair as much as possible. Last year tests were done in a hurry and to be honest, something was fisy with those results.
    • Lately, there were some big internal changes in NpgsqlRest, many micro-optimizations and improvements that affected performance.
    • I waanted to add more scenarios, and test different aspects (such as system resource usage which are now included).
    • Some other frameworks also had major updates, so I wanted to see how they compare now.

    Other than that, during the developement and explaration of the test project, I found some issues. For example:

    • Pause between tests was way too short. Increaed from 5 to 30 seconds to allow TCP TIME_WAIT sockets to clear, JIT warmup, Garbage Collection, etc.
    • Logging setup was uneven across frameworks, causing some to log to console/file during tests, impacting performance. Now all logging is disabled.

    Other then that, I wanted to make the benchmarks as transparent, fair, realistic and accurate as possible. Everyone can check the source code used for tests, and run run themself if desired with their own setup. Also, anyone can suggest improvements or make PRs with new frameworks, optimizations or new test scenarios, and I would very much welcome that. As development of NpgsqlRest continues, I plan to repeat this benchmark from time to time to keep track of performance changes.

    What follows is the detailed AI-generated analysis and report of the benchmark results. But you can also skip to summary results table or to conclusion sections directly.

    Enjoy!

    Following our 2025 benchmark, this year's run uses the latest framework versions and adds higher concurrency levels (up to 200 VUs), pure HTTP overhead tests (Minimal Baseline), and POST body parsing benchmarks.

    What We Tested

    All frameworks executed the same PostgreSQL functions. This isolates the framework overhead from database performance - every framework runs identical queries against the same PostgreSQL instance. All functions use generate_series() for constant, predictable database response times with no table I/O.

    Test Scenarios:

    ScenarioEndpointFunctionDescription
    Data Type SerializationGET /api/perf-testperf_testComprehensive test with 23 data types (text, int, numeric, bool, date/time, UUID, JSON, arrays). Returns 1, 10, 100, or 500 records.
    Minimal Baseline (new)GET /api/perf-minimalperf_minimalPure HTTP routing overhead. Returns {"status": "ok", "ts": ...} - no parameters, minimal response. Tested at 100, 200, and 500 VUs.
    POST Body Parsing (new)POST /api/perf-postperf_postTests JSON request body parsing. Sends nested JSON payload, echoes it back with computed values. Tested at 50 VUs with 10 and 100 records.
    Nested JSON (new)GET /api/perf-nestedperf_nestedTests nested object serialization at configurable depth levels.
    Large Payload (new)GET /api/perf-large-payloadperf_large_payloadTests chunked transfer and buffer handling with configurable KB-sized responses.
    Many Parameters (new)GET /api/perf-many-paramsperf_many_paramsTests query string parsing with 20 parameters of mixed types.

    Test Configuration:

    • Load Tool: k6 load testing framework
    • Duration: 60 seconds per test (30 seconds for minimal baseline)
    • Concurrency: 1, 50, 100, and 200 virtual users (VUs) - up to 500 VUs for minimal baseline
    • Environment: All services running in Docker containers on the same host
    • Hardware: Hetzner Cloud CCX33 (General Purpose, x86 AMD) - 8 dedicated vCPUs, 32 GB RAM, 240 GB SSD, 30 TB traffic

    Frameworks Tested:

    FrameworkLanguageVersion
    NpgsqlRest (JIT).NET3.4.7
    NpgsqlRest (AOT).NET3.4.7
    PostgRESTHaskell14.3
    Go (net/http + pgx)Go1.25
    Rust (Actix + tokio-postgres)Rust1.91.1
    Spring BootJava 244.0.1
    .NET Minimal API + Dapper.NET 10-
    .NET Minimal API + EF Core.NET 9/10-
    FastifyNode.js5.7.1
    BunBun1.3.3
    SwoolePHP 8.46.0 (extension)
    FastAPIPython0.128.0
    DjangoPython6.0.1

    What's New in This Benchmark

    This benchmark introduces several improvements over the previous 2025 benchmark:

    Version Updates

    FrameworkPrevious VersionNew VersionChanged
    NpgsqlRest3.2.23.4.7Yes
    PostgREST12.2.814.3Yes
    Go1.241.25Yes
    Rust1.83.01.91.1Yes
    Bun1.1.421.3.3Yes
    Fastify5.6.25.7.1Yes
    FastAPI0.127.10.128.0Yes
    Django6.06.0.1Yes
    SwoolePHP 8.4 + Swoole 6.0PHP 8.4 + Swoole 6.0No
    Spring BootJava 24 + 4.0.1Java 24 + 4.0.1No
    .NET Dapper.NET 10 Preview.NET 10 PreviewNo
    .NET EF Core.NET 9 / .NET 10 Preview.NET 9 / .NET 10 PreviewNo

    New Test Scenarios

    We added five new benchmark scenarios (marked "(new)" in the scenario table above):

    • Minimal Baseline: Reveals true framework HTTP overhead separated from database I/O
    • POST Body Parsing: Measures JSON request deserialization and response serialization
    • Nested JSON: Tests nested object serialization performance
    • Large Payload: Tests chunked transfer and buffer handling
    • Many Parameters: Tests query string parsing with 20 parameters

    We also extended concurrency testing to better expose scaling limits:

    • VU range expanded from 1/50/100 to 1/50/100/200
    • Up to 500 VUs for minimal baseline tests

    Infrastructure Changes

    • Resource Monitoring: New per-service CPU and memory tracking during tests
    • JIT Warmup Phase: All frameworks now get a warmup period before benchmarks
    • Extended Sleep Between Tests: 30 seconds between tests for TCP TIME_WAIT clearance
    • Improved Result Aggregation: JSON output for programmatic analysis

    Comparing With Previous Results

    For detailed comparison with the 2025 benchmark:

    Key Findings

    Swoole PHP Dominates Large Payload Scenarios

    A major shift from 2025: Swoole PHP 6.0 now leads in most scenarios involving larger payloads. At 100 VU with 100 records, Swoole achieves 469.58 req/s - outperforming all competitors. With 500 records, Swoole maintains its lead at 106.88 req/s.

    NpgsqlRest Leads High-Concurrency Low-Payload Scenarios

    At 100 concurrent users with minimal payload (1 record), NpgsqlRest JIT achieves 4,588 req/s, maintaining its lead in low-latency scenarios. The gap between JIT and AOT versions has narrowed significantly in v3.4.7.

    The Top Performers by Scenario

    ScenarioLeaderPerformance
    1 VU, 1 RecordBun539.91 req/s
    100 VU, 1 RecordNpgsqlRest JIT4,588 req/s
    100 VU, 100 RecordsSwoole PHP469.58 req/s
    100 VU, 500 RecordsSwoole PHP106.88 req/s
    Minimal Baseline (pure HTTP)Go20,104 req/s
    POST Body ParsingGo9,628 req/s

    Performance Tiers at 100 VU, 1 Record

    TierFrameworksPerformance Range
    EliteNpgsqlRest JIT/AOT, Swoole PHP4,400-4,600 req/s
    TopBun, Go, Fastify, Spring Boot4,100-4,400 req/s
    High.NET Dapper, Rust3,900-4,100 req/s
    Mid.NET EF Core3,400-3,500 req/s
    LowerFastAPI, PostgREST, Django1,600-1,850 req/s

    What Changed From 2025

    Several notable performance shifts:

    1. Swoole PHP improved dramatically - Jumping from mid-tier to top performer in data-heavy scenarios
    2. Bun emerged as single-threaded champion - Best performance at 1 VU across all payload sizes
    3. NpgsqlRest JIT/AOT gap narrowed - AOT now performs nearly identically to JIT in most scenarios
    4. PostgREST improved - Better scaling under high concurrency compared to 2025
    5. Go remains pure HTTP champion - Unmatched at 20,000+ req/s in minimal baseline tests

    Scaling Behavior

    Framework scaling patterns at increasing concurrency:

    Framework1 VU50 VU100 VU200 VUScaling Factor
    NpgsqlRest JIT4804,3804,5884,5639.5x
    Swoole PHP4714,1604,4234,4859.5x
    Bun5404,4214,3774,4198.2x
    PostgREST2711,8181,7491,6636.5x

    Large Payloads Level the Playing Field

    With 500 records at 100 VU, database I/O dominates and the performance gap narrows:

    Framework500 Records @ 100 VULatency
    Swoole PHP106.88 req/s468ms
    Go90.93 req/s550ms
    Rust85.79 req/s583ms
    NpgsqlRest JIT82.37 req/s607ms
    FastAPI25.36 req/s1,977ms

    Pure HTTP Overhead (Minimal Baseline)

    Testing pure HTTP handling without database access reveals framework overhead:

    Framework100 VU200 VU500 VU
    Go20,104 req/s20,807 req/s20,573 req/s
    NpgsqlRest JIT16,065 req/s17,105 req/s17,015 req/s
    .NET Dapper14,764 req/s15,401 req/s15,705 req/s
    Spring Boot14,138 req/s14,593 req/s14,435 req/s
    Swoole PHP12,042 req/s12,309 req/s12,297 req/s
    PostgREST5,410 req/s4,974 req/s5,324 req/s

    Go's lightweight HTTP server achieves 20,000+ req/s - nearly 4x faster than PostgREST's pure HTTP overhead.

    POST Body Parsing Performance

    Testing JSON body parsing adds another dimension:

    Framework50 VU, 10 Records50 VU, 100 Records
    Go9,629 req/s2,697 req/s
    Swoole PHP7,470 req/s2,445 req/s
    Spring Boot7,133 req/s1,758 req/s
    NpgsqlRest JIT6,101 req/s1,226 req/s
    Bun4,028 req/s1,832 req/s
    PostgREST3,479 req/s1,156 req/s

    Go's JSON parsing keeps it in front, while Bun loses less throughput than most as the payload grows.

    Python Frameworks Continue to Struggle

    Both FastAPI and Django remain at the bottom in most scenarios:

    • FastAPI at 100 VU with 500 records: 1,977ms average latency
    • Django performs better with data serialization but lags in pure throughput

    JIT vs AOT in 2026

    NpgsqlRest's JIT and AOT versions now perform nearly identically:

    ScenarioJITAOTDifference
    100 VU, 1 Record4,588 req/s4,527 req/s1.3%
    100 VU, 100 Records377 req/s375 req/s0.5%
    Minimal Baseline16,065 req/s15,624 req/s2.8%

    For most workloads, the choice between JIT and AOT can now be based on deployment requirements (image size, cold start) rather than performance.

    Why Certain Frameworks Excel

    Swoole PHP's Rise

    Swoole 6.0's gains come from:

    • Coroutine-based async I/O eliminates blocking
    • Efficient memory management for large responses
    • Native PostgreSQL driver optimization

    Go's HTTP Dominance

    Go's minimal baseline performance (20K+ req/s) demonstrates:

    • Extremely low HTTP parsing overhead
    • Efficient goroutine scheduling
    • Zero-allocation hot paths in net/http

    NpgsqlRest's Architecture

    NpgsqlRest's numbers come from eliminating layers:

    1. No ORM overhead - Direct PostgreSQL protocol via Npgsql
    2. No routing framework - Endpoints derived from database metadata
    3. No serialization layer - PostgreSQL handles JSON serialization
    4. Efficient connection pooling - Npgsql's built-in pooling

    Resource Usage

    New in this benchmark: per-service memory and CPU monitoring during test execution.

    ServicePeak MemoryAvg MemoryAvg CPU
    Go75.94 MB21.35 MB4.84%
    Swoole PHP78.99 MB50.92 MB4.49%
    Rust168.20 MB43.03 MB3.79%
    Bun168.00 MB59.31 MB4.29%
    .NET Dapper192.70 MB96.55 MB4.19%
    FastAPI198.10 MB133.73 MB4.29%
    NpgsqlRest AOT237.80 MB59.84 MB4.30%
    .NET 10 EF256.50 MB143.59 MB6.16%
    .NET 9 EF276.60 MB133.80 MB6.36%
    NpgsqlRest JIT321.30 MB111.41 MB4.42%
    Fastify411.40 MB58.96 MB3.68%
    Django824.00 MB386.77 MB11.84%
    Spring Boot1,010.00 MB806.79 MB5.38%

    Key Observations

    • Go has the lowest memory footprint (21 MB average, 76 MB peak)
    • Swoole PHP achieves top-tier performance with minimal resources (51 MB average)
    • NpgsqlRest AOT uses half the memory of JIT (60 MB vs 111 MB average)
    • Spring Boot has the highest memory usage (807 MB average, 1 GB peak)
    • Django has the highest CPU usage (11.84% average)

    The full resource monitoring data is available in the stats directory.

    Important Note: JSON and Array Type Handling

    One caveat when interpreting these results: not all frameworks return identical JSON responses. The differences lie in how each handles PostgreSQL's JSON, JSONB, and array types.

    Frameworkjsonjsonbint[]text[]
    NpgsqlRest (JIT/AOT)
    .NET EF Core / Dapper
    Rust
    Fastify
    Django
    Go
    FastAPI
    PostgREST⚠️⚠️
    Bun⚠️⚠️
    Spring Boot⚠️⚠️
    Swoole PHP

    ✅ = Properly parsed as native JSON/array ❌ = Returns raw PostgreSQL text format (string) ⚠️ = Unusual format (wrapped in metadata or array)

    Swoole PHP's impressive numbers come with a caveat: JSON/JSONB and array fields require additional client-side parsing.

    Conclusion

    Where each framework fits, based on the 2026 numbers:

    • For low-latency, high-concurrency APIs: NpgsqlRest JIT remains the top choice at 4,588 req/s
    • For data-heavy workloads: Swoole PHP now leads with superior large-payload handling
    • For pure HTTP performance: Go is unmatched at 20,000+ req/s
    • For balanced workloads: Bun, Go, and Fastify offer excellent all-around performance

    The "database as API" approach continues to hold the top tier: NpgsqlRest requires zero application code - just configuration - while delivering top-tier performance and correct PostgreSQL type handling.

    Lines of Code Comparison

    Performance isn't everything — development time, maintainability, and code complexity matter too. How much code each framework needs to implement the same API endpoints:

    FrameworkLines of Code
    PostgREST14 (config only)
    NpgsqlRest21 (config only)
    Fastify100
    .NET EF116
    Bun133
    FastAPI136
    Spring Boot139
    .NET Dapper140
    Django203
    Swoole PHP216
    Rust291
    Go347

    Go may deliver 20,000+ req/s in pure HTTP tests, but that comes at the cost of writing 347 lines of boilerplate code for basic CRUD operations. Every line of code is a potential bug, a maintenance burden, and development time spent.

    NpgsqlRest and PostgREST take a different approach: define your API in the database, configure once, and let the framework handle the rest. You get top-tier performance (4,500+ req/s) with just 14-21 lines of configuration - no serialization code, no routing logic, no connection pool management.

    The benchmark source code and detailed results are available in the pg_function_load_tests repository.


    Full Benchmark Results

    Summary Tables

    All results in requests per second (req/s). Sorted by 100/1 performance.

    Data Type Serialization

    Function: perf_test

    Framework1/11/10100/1100/100100/500
    NpgsqlRest JIT480265🥇 4,58837782
    NpgsqlRest AOT466262🥈 4,52737582
    Swoole PHP471🥇 292🥉 4,423🥇 470🥇 107
    Bun🥇 5402434,37735379
    Go🥉 484🥈 2894,362🥈 406🥈 91
    Fastify4822604,17235173
    Spring Boot4642334,14728261
    .NET Dapper4252444,10133172
    Rust🥈 507🥉 2853,940🥉 388🥉 86
    .NET 10 EF3772203,51533172
    .NET 9 EF3622233,42533072
    FastAPI4782201,84311125
    PostgREST2711811,74934279
    Django2711781,69130771

    Column headers = VU/Records (e.g., 100/1 = 100 VU, 1 record). See detailed results.

    New Scenarios

    FrameworkMinPOSTNestLargeParams
    Go🥇 20,104🥇 9,629🥇 3,756🥇 1,618🥇 16,100
    NpgsqlRest JIT🥈 16,0656,1013,0611,096🥈 11,504
    NpgsqlRest AOT🥉 15,6246,065🥉 3,073919🥉 11,221
    .NET Dapper14,7646,0892,9891,39710,702
    Spring Boot14,138🥉 7,1332,230🥈 1,51510,324
    Swoole PHP12,042🥈 7,470🥈 3,426🥉 1,5009,458
    Rust11,7615,4002,8241,5099,231
    .NET 10 EF9,3854,8792,6151,3147,452
    .NET 9 EF8,9894,7102,5581,3267,182
    Fastify8,8994,6312,9211,2487,711
    Bun7,8034,0282,2871,2353,116
    PostgREST5,4103,4791,9129083,960
    FastAPI4,0902,4879711,4922,080
    Django2,5352,2661,6771,0712,222

    Results are grouped by concurrency level and payload size, sorted by requests per second (highest first).

    Data Type Serialization Tests

    1 Virtual User, 1 Record

    Function: perf_test

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    bun-app-v1.3.3539.91/s1.84ms32,395summarysource
    rust-app-v1.91.1506.53/s1.96ms30,392summarysource
    go-app-v1.25483.79/s2.05ms29,028summarysource
    fastify-app-v5.7.1481.94/s2.06ms28,917summarysource
    npgsqlrest-jit-v3.4.7480.28/s2.07ms28,817summarysource
    fastapi-app-v0.128.0478.18/s2.08ms28,692summarysource
    swoole-php-app-v6.0471.18/s2.11ms28,272summarysource
    npgsqlrest-aot-v3.4.7466.06/s2.13ms27,964summarysource
    java24-spring-boot-v4.0.1464.36/s2.14ms27,863summarysource
    net10-minapi-dapper-jit425.45/s2.34ms25,527summarysource
    net10-minapi-ef-jit376.73/s2.64ms22,604summarysource
    net9-minapi-ef-jit361.52/s2.75ms21,692summarysource
    postgrest-v14.3271.38/s3.67ms16,284summarysource
    django-app-v6.0.1270.87/s3.68ms16,254summarysource

    1 Virtual User, 10 Records

    Function: perf_test

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    swoole-php-app-v6.0291.51/s3.42ms17,491summarysource
    go-app-v1.25288.60/s3.45ms17,317summarysource
    rust-app-v1.91.1284.94/s3.50ms17,098summarysource
    npgsqlrest-jit-v3.4.7264.71/s3.76ms15,883summarysource
    npgsqlrest-aot-v3.4.7262.37/s3.80ms15,743summarysource
    fastify-app-v5.7.1259.77/s3.84ms15,587summarysource
    net10-minapi-dapper-jit244.13/s4.08ms14,649summarysource
    bun-app-v1.3.3243.23/s4.10ms14,594summarysource
    java24-spring-boot-v4.0.1232.71/s4.28ms13,964summarysource
    net9-minapi-ef-jit223.13/s4.47ms13,389summarysource
    fastapi-app-v0.128.0220.40/s4.52ms13,225summarysource
    net10-minapi-ef-jit219.98/s4.53ms13,200summarysource
    postgrest-v14.3180.58/s5.52ms10,835summarysource
    django-app-v6.0.1178.29/s5.59ms10,698summarysource

    100 Virtual Users, 1 Record

    Function: perf_test

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    npgsqlrest-jit-v3.4.74,588.02/s10.88ms275,381summarysource
    npgsqlrest-aot-v3.4.74,526.64/s11.02ms271,720summarysource
    swoole-php-app-v6.04,423.22/s11.29ms265,603summarysource
    bun-app-v1.3.34,377.29/s11.41ms262,711summarysource
    go-app-v1.254,362.06/s11.44ms261,787summarysource
    fastify-app-v5.7.14,171.93/s11.97ms250,370summarysource
    java24-spring-boot-v4.0.14,146.96/s12.03ms248,882summarysource
    net10-minapi-dapper-jit4,100.54/s12.17ms246,098summarysource
    rust-app-v1.91.13,939.83/s12.67ms236,565summarysource
    net10-minapi-ef-jit3,515.31/s14.20ms210,977summarysource
    net9-minapi-ef-jit3,424.67/s14.60ms205,781summarysource
    fastapi-app-v0.128.01,842.81/s27.12ms110,610summarysource
    postgrest-v14.31,749.07/s28.58ms105,038summarysource
    django-app-v6.0.11,690.72/s29.56ms101,545summarysource

    100 Virtual Users, 100 Records

    Function: perf_test

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    swoole-php-app-v6.0469.58/s106.44ms28,257summarysource
    go-app-v1.25405.52/s123.21ms24,378summarysource
    rust-app-v1.91.1387.70/s128.79ms23,313summarysource
    npgsqlrest-jit-v3.4.7377.42/s132.43ms22,691summarysource
    npgsqlrest-aot-v3.4.7374.57/s133.40ms22,519summarysource
    bun-app-v1.3.3352.79/s141.70ms21,217summarysource
    fastify-app-v5.7.1351.07/s142.62ms21,212summarysource
    postgrest-v14.3342.26/s145.93ms20,578summarysource
    net10-minapi-ef-jit331.31/s151.40ms19,994summarysource
    net10-minapi-dapper-jit331.16/s150.92ms19,914summarysource
    net9-minapi-ef-jit329.64/s151.67ms19,836summarysource
    django-app-v6.0.1307.22/s162.61ms18,480summarysource
    java24-spring-boot-v4.0.1281.56/s177.62ms16,949summarysource
    fastapi-app-v0.128.0111.17/s449.98ms6,774summarysource

    100 Virtual Users, 500 Records

    Function: perf_test

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    swoole-php-app-v6.0106.88/s468.22ms6,459summarysource
    go-app-v1.2590.93/s550.42ms5,511summarysource
    rust-app-v1.91.185.79/s583.48ms5,192summarysource
    npgsqlrest-jit-v3.4.782.37/s606.96ms4,991summarysource
    npgsqlrest-aot-v3.4.781.89/s610.99ms4,956summarysource
    postgrest-v14.378.59/s636.29ms4,753summarysource
    bun-app-v1.3.378.55/s637.59ms4,772summarysource
    fastify-app-v5.7.173.00/s685.34ms4,463summarysource
    net10-minapi-dapper-jit72.42/s691.80ms4,391summarysource
    net9-minapi-ef-jit72.00/s694.45ms4,355summarysource
    net10-minapi-ef-jit71.98/s694.12ms4,363summarysource
    django-app-v6.0.171.16/s702.86ms4,302summarysource
    java24-spring-boot-v4.0.160.75/s822.37ms3,683summarysource
    fastapi-app-v0.128.025.36/s1,977.30ms1,613summarysource

    Minimal Baseline (Pure HTTP Overhead)

    100 Virtual Users

    Function: perf_minimal

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    go-app-v1.2520,104.17/s2.47ms603,258summarysource
    npgsqlrest-jit-v3.4.716,064.93/s3.10ms481,990summarysource
    npgsqlrest-aot-v3.4.715,623.97/s3.19ms468,869summarysource
    net10-minapi-dapper-jit14,763.56/s3.37ms442,970summarysource
    java24-spring-boot-v4.0.114,137.76/s3.52ms424,203summarysource
    swoole-php-app-v6.012,042.03/s4.13ms361,335summarysource
    rust-app-v1.91.111,760.56/s4.22ms352,878summarysource
    net10-minapi-ef-jit9,384.73/s5.31ms281,613summarysource
    net9-minapi-ef-jit8,988.95/s5.55ms269,715summarysource
    fastify-app-v5.7.18,899.19/s5.61ms267,058summarysource
    bun-app-v1.3.37,803.09/s6.40ms234,250summarysource
    postgrest-v14.35,410.48/s9.23ms162,402summarysource
    fastapi-app-v0.128.04,089.65/s12.21ms122,737summarysource
    django-app-v6.0.12,535.28/s19.71ms76,114summarysource

    POST Body Parsing

    50 Virtual Users, 10 Records

    Function: perf_post

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    go-app-v1.259,628.69/s2.58ms577,788summarysource
    swoole-php-app-v6.07,470.15/s3.33ms448,231summarysource
    java24-spring-boot-v4.0.17,132.74/s3.49ms427,988summarysource
    npgsqlrest-jit-v3.4.76,100.82/s4.08ms366,070summarysource
    net10-minapi-dapper-jit6,088.58/s4.09ms365,437summarysource
    npgsqlrest-aot-v3.4.76,065.30/s4.11ms363,949summarysource
    rust-app-v1.91.15,399.96/s4.61ms324,130summarysource
    net10-minapi-ef-jit4,879.06/s5.11ms292,761summarysource
    net9-minapi-ef-jit4,710.24/s5.29ms282,651summarysource
    fastify-app-v5.7.14,631.21/s5.39ms277,906summarysource
    bun-app-v1.3.34,027.52/s6.20ms241,687summarysource
    postgrest-v14.33,478.73/s7.17ms208,759summarysource
    fastapi-app-v0.128.02,487.28/s10.04ms149,259summarysource
    django-app-v6.0.12,266.37/s11.02ms136,044summarysource

    Nested JSON Serialization

    50 Virtual Users, Depth 1

    Function: perf_nested

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    go-app-v1.253,756.41/s6.64ms225,426summarysource
    swoole-php-app-v6.03,426.34/s7.27ms205,620summarysource
    npgsqlrest-aot-v3.4.73,073.40/s8.12ms184,428summarysource
    npgsqlrest-jit-v3.4.73,060.64/s8.15ms183,675summarysource
    net10-minapi-dapper-jit2,988.63/s8.35ms179,345summarysource
    fastify-app-v5.7.12,921.44/s8.54ms175,319summarysource
    rust-app-v1.91.12,824.19/s8.83ms169,476summarysource
    net10-minapi-ef-jit2,615.08/s9.54ms156,947summarysource
    net9-minapi-ef-jit2,557.86/s9.76ms153,501summarysource
    bun-app-v1.3.32,286.95/s10.92ms137,261summarysource
    java24-spring-boot-v4.0.12,229.81/s11.19ms133,816summarysource
    postgrest-v14.31,912.23/s13.06ms114,792summarysource
    django-app-v6.0.11,676.67/s14.90ms100,661summarysource
    fastapi-app-v0.128.0970.57/s25.74ms58,262summarysource

    Large Payload

    25 Virtual Users, 100KB Payload

    Function: perf_large_payload

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    go-app-v1.251,618.26/s7.70ms97,107summarysource
    java24-spring-boot-v4.0.11,514.97/s8.23ms90,930summarysource
    rust-app-v1.91.11,508.80/s8.27ms90,539summarysource
    swoole-php-app-v6.01,500.22/s8.31ms90,027summarysource
    fastapi-app-v0.128.01,491.75/s8.36ms89,518summarysource
    net10-minapi-dapper-jit1,396.93/s8.93ms83,826summarysource
    net9-minapi-ef-jit1,326.18/s9.41ms79,583summarysource
    net10-minapi-ef-jit1,313.73/s9.50ms78,847summarysource
    fastify-app-v5.7.11,248.43/s10.00ms74,941summarysource
    bun-app-v1.3.31,234.55/s10.11ms74,093summarysource
    npgsqlrest-jit-v3.4.71,095.93/s11.39ms65,765summarysource
    django-app-v6.0.11,070.77/s11.66ms64,270summarysource
    npgsqlrest-aot-v3.4.7919.29/s13.58ms55,175summarysource
    postgrest-v14.3907.83/s13.75ms54,499summarysource

    Many Parameters (20 params)

    50 Virtual Users

    Function: perf_many_params

    FrameworkRequests/sAvg LatencyTotal RequestsSummarySource
    go-app-v1.2516,100.46/s1.54ms966,176summarysource
    npgsqlrest-jit-v3.4.711,503.53/s2.16ms690,265summarysource
    npgsqlrest-aot-v3.4.711,220.54/s2.22ms673,270summarysource
    net10-minapi-dapper-jit10,701.77/s2.33ms642,149summarysource
    java24-spring-boot-v4.0.110,323.63/s2.41ms619,498summarysource
    swoole-php-app-v6.09,457.64/s2.63ms567,509summarysource
    rust-app-v1.91.19,230.96/s2.69ms553,984summarysource
    fastify-app-v5.7.17,710.72/s3.23ms462,679summarysource
    net10-minapi-ef-jit7,452.38/s3.34ms447,165summarysource
    net9-minapi-ef-jit7,181.52/s3.47ms430,916summarysource
    postgrest-v14.33,960.49/s6.30ms237,672summarysource
    bun-app-v1.3.33,116.21/s8.01ms187,044summarysource
    django-app-v6.0.12,222.19/s11.24ms133,359summarysource
    fastapi-app-v0.128.02,079.91/s12.01ms124,813summarysource

    Comments

    + + + + \ No newline at end of file diff --git a/blog/real-time-chat-postgresql-sse-npgsqlrest.html b/blog/real-time-chat-postgresql-sse-npgsqlrest.html new file mode 100644 index 000000000..8ce585ede --- /dev/null +++ b/blog/real-time-chat-postgresql-sse-npgsqlrest.html @@ -0,0 +1,335 @@ + + + + + + Build a Real-Time Chat App with PostgreSQL and Server-Sent Events | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Build a Real-Time Chat App with PostgreSQL and Server-Sent Events

    January 2026 · SSEReal-TimePostgreSQLChatNpgsqlRest

    Adding chat to an application usually means adding a WebSocket server, a message broker, pub/sub plumbing, and hundreds of lines of code spread across multiple services.

    What if you could build a fully functional, secure real-time chat with just a single SQL procedure and a few lines of TypeScript?

    This tutorial shows how NpgsqlRest's Server-Sent Events (SSE) support turns PostgreSQL RAISE statements into real-time events.

    Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/8_simple_chat_client

    The Traditional Approach: Complex Infrastructure

    Building real-time chat the traditional way requires:

    1. WebSocket Server - Separate service to manage persistent connections
    2. Message Broker - Redis Pub/Sub, RabbitMQ, or similar for message distribution
    3. Connection Management - Track connected users, handle reconnections
    4. Authentication Integration - Validate tokens on WebSocket handshake
    5. Scaling Strategy - Sticky sessions or shared state for horizontal scaling
    6. Frontend WebSocket Client - Handle connection lifecycle, reconnection logic
    7. Backend API - REST endpoints for message history, user management
    8. Database Integration - Separate persistence layer for messages

    That's eight moving parts, each one a failure point, a deployment concern, and development time.

    The NpgsqlRest Approach: PostgreSQL IS Your Real-Time Server

    NpgsqlRest inverts this. Instead of adding infrastructure, it uses what you already have:

    • PostgreSQL RAISE statements become SSE events - No message broker needed
    • Cookie authentication works automatically - Same auth for REST and SSE
    • Scoped event distribution - Control who receives events with annotations
    • Single deployment - No separate WebSocket server
    • Auto-generated TypeScript client - Including EventSource factory functions

    The mechanism is mundane: PostgreSQL already supports sending messages during query execution via RAISE. NpgsqlRest captures these messages and streams them to connected clients via Server-Sent Events.

    How SSE Works in NpgsqlRest

    mermaid
    flowchart TB
    +    A["Alice's Browser"] & B["Bob's Browser"]
    +    A -- "EventSource('/api/send-message/info?TEST_CHANNEL')" --> N
    +    B -- "SSE Connection" --> N
    +
    +    N["NpgsqlRest
    +    Maintains SSE connections for authorized clients
    +    Filters events by scope (authorize, matching, all)"]
    +
    +    N -- "RAISE INFO JSON payload" --> P
    +
    +    P["PostgreSQL
    +    PROCEDURE send_message() executes RAISE INFO
    +    NpgsqlRest captures the notice and broadcasts"]

    When an @sse-annotated PostgreSQL function or procedure executes RAISE INFO, RAISE NOTICE, or RAISE WARNING, NpgsqlRest captures the matching messages and streams them to connected SSE clients based on the configured scope. Procedures without @sse can RAISE all they want — those notices are never broadcast.

    Internally, every @sse-annotated procedure publishes into a single process-wide broadcaster, and every connected EventSource reads from that same stream — the URL it opened is just an entry point. Filtering happens per event (scope, hint, optional execution ID), not per URL. This becomes important once you have more than one source of events; see Cross-procedure pattern in the SSE annotation reference.

    Building the Chat: Step by Step

    Step 1: Schema Setup

    sql
    sql
    -- Users table with secure password hashing
    +create table example_8.users (
    +    user_id int primary key generated always as identity,
    +    username text not null unique,
    +    password_hash text not null
    +);
    +
    +-- Insert test users (alice/password123, bob/password456)
    +insert into example_8.users (username, password_hash) values
    +    ('alice', crypt('password123', gen_salt('bf'))),
    +    ('bob', crypt('password456', gen_salt('bf')));
    +
    +-- Messages table for chat history
    +create table example_8.messages (
    +    message_id int primary key generated always as identity,
    +    user_id int not null references example_8.users(user_id),
    +    username text not null,
    +    message_text text not null,
    +    created_at timestamptz not null default now()
    +);

    Step 2: Login Function

    sql
    sql
    create function example_8.login(_username text, _password text)
    +returns table (scheme text, user_id int, user_name text)
    +language sql
    +security definer
    +begin atomic;
    +    select 'cookies', u.user_id, u.username
    +    from example_8.users u
    +    where u.username = _username
    +      and u.password_hash = crypt(_password, u.password_hash);
    +end;
    +
    +comment on function example_8.login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous';

    The login annotation creates a cookie-based session automatically.

    Step 3: The Magic - Send Message with SSE

    Here's the entire backend for real-time messaging:

    sql
    sql
    create procedure example_8.send_message(
    +    _message_text text,
    +    _user_id text = null,
    +    _user_name text = null
    +)
    +language plpgsql
    +as $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    -- Store the message
    +    insert into example_8.messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    -- Broadcast to all connected authorized clients via SSE
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'user_id', _user_id::int,
    +        'username', _user_name,
    +        'message_text', _message_text,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    +
    +comment on procedure example_8.send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@sse
    +@sse_scope authorize';

    That's it. The entire real-time messaging backend is 25 lines of SQL.

    Three annotations do the work:

    AnnotationPurpose
    authorizeOnly authenticated users can send messages
    sseTwo effects: (1) registers /info as an SSE connection URL, (2) makes this procedure's RAISEs feed the SSE broadcaster
    sse_scope authorizePer-event filter: only authenticated subscribers receive events from this endpoint

    The RAISE INFO statement with JSON payload becomes the SSE event data. NpgsqlRest automatically:

    • Captures the notice during procedure execution
    • Serializes it as an SSE event
    • Broadcasts to all connected clients matching the scope
    • Handles connection management, reconnection, and cleanup

    Step 4: Message History

    sql
    sql
    create function example_8.get_messages()
    +returns table (
    +    message_id int,
    +    user_id int,
    +    username text,
    +    message_text text,
    +    created_at timestamptz
    +)
    +language sql
    +begin atomic;
    +    select message_id, user_id, username, message_text, created_at
    +    from example_8.messages
    +    order by created_at asc;
    +end;
    +
    +comment on function example_8.get_messages() is '
    +HTTP GET
    +@authorize';

    Understanding SSE Scopes

    The sse_scope annotation controls who receives events:

    sse_scope authorize

    Only authenticated clients receive events. Perfect for private chat rooms.

    sql
    sql
    comment on procedure private_broadcast() is '
    +@sse
    +@sse_scope authorize';

    sse_scope authorize <roles/users>

    Target specific roles or users:

    sql
    sql
    -- Only admins receive these events
    +comment on procedure admin_notification() is '
    +@sse
    +@sse_scope authorize admin';
    +
    +-- Specific users receive events
    +comment on procedure user_alert() is '
    +@sse
    +@sse_scope authorize alice, bob';

    sse_scope matching

    Clients with matching security context receive events:

    sql
    sql
    comment on procedure team_update() is '
    +@sse
    +@sse_scope matching';

    sse_scope all

    Broadcast to everyone (use carefully):

    sql
    sql
    comment on procedure public_announcement() is '
    +@sse
    +@sse_scope all';

    Dynamic Scopes with RAISE HINT

    Override scope per-event at runtime:

    sql
    sql
    -- This event goes to admins only
    +raise notice 'Admin alert: server load high' using hint = 'authorize admin';
    +
    +-- This event goes to everyone
    +raise notice 'System maintenance in 5 minutes' using hint = 'all';
    +
    +-- This uses the default scope from annotation
    +raise notice 'Regular update...';

    The Auto-Generated TypeScript Client

    NpgsqlRest generates a complete TypeScript client including SSE support:

    typescript
    typescript
    // Auto-generated EventSource factory
    +export const createSendMessageEventSource = (id: string = "") =>
    +    new EventSource(baseUrl + "/api/example-8/send-message/info?" + id);
    +
    +// Auto-generated send function with SSE support
    +export async function sendMessage(
    +    request: ISendMessageRequest,
    +    onMessage?: (message: string) => void,
    +    id: string | undefined = undefined,
    +    closeAfterMs = 1000,
    +    awaitConnectionMs: number | undefined = 0
    +): Promise<{status: number, error: ... }> {
    +    const executionId = id ? id : window.crypto.randomUUID();
    +    let eventSource: EventSource;
    +
    +    if (onMessage) {
    +        eventSource = createSendMessageEventSource(executionId);
    +        eventSource.onmessage = (event: MessageEvent) => {
    +            onMessage(event.data);
    +        };
    +        // ... connection handling
    +    }
    +
    +    // ... fetch call with X-NpgsqlRest-ID header
    +}

    The Frontend: Minimal Code Required

    Using the generated client, the frontend is straightforward:

    typescript
    typescript
    import { login, logout, sendMessage, createSendMessageEventSource, getMessages }
    +    from "./example8Api.ts";
    +
    +const channelName = "TEST_CHANNEL";
    +let eventSource: EventSource | null = null;
    +
    +// Connect to SSE when user logs in
    +function connectEventSource() {
    +    eventSource = createSendMessageEventSource(channelName);
    +
    +    eventSource.onmessage = (event: MessageEvent) => {
    +        const msg = JSON.parse(event.data);
    +        appendMessage(msg);  // Display in UI
    +    };
    +}
    +
    +// Send a message - it will be broadcast to all connected clients
    +async function sendChatMessage() {
    +    const messageText = messageInput.value.trim();
    +    if (!messageText) return;
    +
    +    messageInput.value = "";
    +
    +    await sendMessage(
    +        { messageText },
    +        undefined,      // Skip local onMessage (we're already connected)
    +        channelName     // Channel identifier
    +    );
    +}
    +
    +// Disconnect when logging out
    +function disconnectEventSource() {
    +    if (eventSource) {
    +        eventSource.close();
    +        eventSource = null;
    +    }
    +}

    Code Comparison: Traditional vs NpgsqlRest

    Traditional Real-Time Chat Architecture

    Backend (Node.js + Socket.IO + Redis):

    javascript
    javascript
    // server.js - WebSocket server
    +const io = require('socket.io')(server);
    +const redis = require('redis');
    +const pub = redis.createClient();
    +const sub = redis.createClient();
    +
    +// Authentication middleware
    +io.use(async (socket, next) => {
    +    const token = socket.handshake.auth.token;
    +    try {
    +        const user = await verifyToken(token);
    +        socket.user = user;
    +        next();
    +    } catch (err) {
    +        next(new Error('Authentication failed'));
    +    }
    +});
    +
    +// Connection handling
    +io.on('connection', (socket) => {
    +    const userId = socket.user.id;
    +
    +    // Join user's room
    +    socket.join(`user:${userId}`);
    +
    +    // Handle chat messages
    +    socket.on('chat:message', async (data) => {
    +        // Save to database
    +        const message = await db.messages.create({
    +            userId: socket.user.id,
    +            username: socket.user.username,
    +            text: data.text,
    +            createdAt: new Date()
    +        });
    +
    +        // Broadcast via Redis pub/sub
    +        pub.publish('chat:messages', JSON.stringify(message));
    +    });
    +
    +    // Handle disconnection
    +    socket.on('disconnect', () => {
    +        console.log(`User ${userId} disconnected`);
    +    });
    +});
    +
    +// Redis subscription for horizontal scaling
    +sub.subscribe('chat:messages');
    +sub.on('message', (channel, message) => {
    +    const msg = JSON.parse(message);
    +    io.emit('chat:message', msg);
    +});

    Plus you need:

    • Redis server running
    • Session store configuration
    • CORS configuration
    • Reconnection logic
    • Heartbeat/ping-pong
    • Room management
    • Error handling

    Frontend (Socket.IO client):

    javascript
    javascript
    import { io } from 'socket.io-client';
    +
    +const socket = io('http://localhost:3000', {
    +    auth: { token: getAuthToken() },
    +    reconnection: true,
    +    reconnectionAttempts: 5,
    +    reconnectionDelay: 1000
    +});
    +
    +socket.on('connect', () => {
    +    console.log('Connected');
    +    loadMessageHistory();
    +});
    +
    +socket.on('chat:message', (msg) => {
    +    appendMessage(msg);
    +});
    +
    +socket.on('disconnect', () => {
    +    showDisconnected();
    +});
    +
    +socket.on('connect_error', (err) => {
    +    handleConnectionError(err);
    +});
    +
    +function sendMessage(text) {
    +    socket.emit('chat:message', { text });
    +}

    NpgsqlRest Approach

    Backend (SQL only):

    sql
    sql
    create procedure example_8.send_message(
    +    _message_text text,
    +    _user_id text = null,
    +    _user_name text = null
    +)
    +language plpgsql
    +as $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into example_8.messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'user_id', _user_id::int,
    +        'username', _user_name,
    +        'message_text', _message_text,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    +
    +comment on procedure example_8.send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@sse
    +@sse_scope authorize';

    Frontend (using generated client):

    typescript
    typescript
    import { sendMessage, createSendMessageEventSource } from "./example8Api.ts";
    +
    +const eventSource = createSendMessageEventSource(channelName);
    +
    +eventSource.onmessage = (event) => {
    +    appendMessage(JSON.parse(event.data));
    +};
    +
    +async function send(text: string) {
    +    await sendMessage({ messageText: text }, undefined, channelName);
    +}

    The Numbers

    ComponentTraditionalNpgsqlRest
    Backend code100-200 lines25 lines (SQL)
    Frontend code50-80 lines20 lines
    InfrastructureWebSocket server + RedisNone (PostgreSQL only)
    Dependenciessocket.io, redis, jwt, etc.None additional
    Services to deploy3+ (API, WebSocket, Redis)1 (NpgsqlRest)
    TypeScript typesManualAuto-generated
    Auth integrationCustom middlewareBuilt-in (cookies)
    Horizontal scalingRedis pub/sub requiredWorks out of the box
    Time to implement1-3 days30 minutes

    Estimated savings: 85-90% less code, single deployment, zero additional infrastructure.

    When to Use SSE vs WebSockets

    SSE (Server-Sent Events) is ideal for:

    • Server-to-client streaming - Chat messages, notifications, live updates
    • Simple integration - Uses standard HTTP, works through proxies
    • Auto-reconnection - Built into the EventSource API
    • Cookie auth works unchanged - Cookies are sent automatically with EventSource connections

    WebSockets are better for:

    • Bidirectional high-frequency - Gaming, collaborative editing
    • Binary data - File transfers, video streaming
    • Custom protocols - When you need full control

    For most real-time features (chat, notifications, dashboards), SSE is simpler and sufficient.

    Why Not PostgreSQL LISTEN/NOTIFY?

    A common question: why use RAISE INFO instead of PostgreSQL's built-in LISTEN/NOTIFY for real-time events?

    LISTEN/NOTIFY has a critical scalability problem. When you execute NOTIFY during a transaction, PostgreSQL acquires a global lock on the entire database during the commit phase. This serializes all commits across your system.

    As documented by Recall.ai, this creates severe issues under high concurrency:

    • Global mutex contention - All transactions queue behind the notification lock
    • Throughput collapse - Query throughput drops dramatically under load
    • Paradoxical behavior - CPU, disk I/O, and network actually decrease during high load because processes are waiting for the lock
    • Hundreds of blocked processes - Sessions pile up waiting for the global lock

    Their load testing showed that removing NOTIFY allowed full CPU utilization and rapid recovery from load spikes, while NOTIFY caused the database to grind to a halt.

    How NpgsqlRest Avoids This Problem

    NpgsqlRest's SSE implementation uses RAISE INFO/NOTICE/WARNING instead of NOTIFY:

    AspectLISTEN/NOTIFYRAISE + SSE
    LockingGlobal database lock on commitNo additional locking
    ScalabilitySerializes all commitsScales with connections
    DeliveryRequires dedicated listener connectionHTTP streaming (standard)
    PersistenceFire-and-forget (can lose messages)Immediate streaming
    Connection modelLong-lived DB connectionsStandard HTTP connections
    Client implementationCustom pg_notify clientStandard EventSource API

    RAISE statements are connection-local - they emit notices to the current connection's notice handler without any global coordination. NpgsqlRest captures these notices during query execution and streams them to SSE clients. No locks, no contention, no scalability ceiling.

    This is why NpgsqlRest uses RAISE instead of NOTIFY for real-time events.

    Advanced: Execution-ID correlation as soft channels

    The X-NpgsqlRest-ID header was designed for request correlation — letting the client receive only the events fired during its own POST. When both the connection's query string and the request's header carry the same ID, NpgsqlRest filters out events whose IDs don't match.

    You can lean on that mechanism to build channel-like behavior, as long as every emitter sets the header and every listener sets the query string:

    typescript
    typescript
    // Listeners — each connection only receives events tagged with its own ID
    +const generalChat = createSendMessageEventSource("general");
    +const teamChat    = createSendMessageEventSource("team-123");
    +const notifications = createSendMessageEventSource("notifications");
    +
    +// Sender — pass the same ID; the generated client puts it in X-NpgsqlRest-ID
    +await sendMessage({ messageText: "Hello team!" }, undefined, "team-123");

    Caveat

    This is a soft filter. If the listener has no execution ID, or the emitter doesn't set the header, the filter is bypassed and the listener will see the event regardless. For truly isolated streams you need scope/hint filtering (per-user, per-role, etc.) rather than execution IDs alone.

    Conclusion

    NpgsqlRest's SSE support turns PostgreSQL's notice system into real-time messaging: RAISE INFO emits the events, annotations control who receives them, and the same cookie auth covers both your REST API and the event stream. For chat, notifications, and live dashboards, that means one deployment and zero extra infrastructure. Reserve WebSockets for the cases that genuinely need them - bidirectional high-frequency traffic or binary data - and use SSE for everything else.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html b/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html new file mode 100644 index 000000000..dbc97a68f --- /dev/null +++ b/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.html @@ -0,0 +1,480 @@ + + + + + + Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Reverse Proxy in PostgreSQL: Gateway to External Services with NpgsqlRest

    Proxy · API Gateway · Microservices · Caching · January 2026


    Not all API endpoints need a database connection. Health checks, static responses, proxied requests to microservices - these operations consume connection pool slots without ever touching PostgreSQL.

    NpgsqlRest's Reverse Proxy feature addresses this: define endpoints in SQL that forward requests to upstream services, transform the responses in PostgreSQL when needed, and skip the database connection entirely when you don't - same annotation-driven workflow throughout. That covers everything from pass-through health checks to caching layers in front of expensive AI service calls.

    This tutorial builds an AI Text Analysis Service that combines a local AI processing server with PostgreSQL-backed caching and enrichment - without spending connection pool slots where they aren't needed.

    Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/10_proxy_ai_service

    The Problem: Connection Pool Exhaustion

    Every NpgsqlRest endpoint normally opens a database connection. That's fine for data operations, but not every endpoint is one:

    • Health checks - /health endpoints called every 5 seconds by load balancers
    • Static configuration - Endpoints returning cached settings
    • Proxied requests - Forwarding to internal microservices
    • External API gateways - Routing to third-party services

    Each of these consumes a connection from the pool, even when no database operation is needed. Under high load:

    • Connection pool depletes rapidly
    • Legitimate database operations wait for connections
    • Request timeouts cascade through the system

    The NpgsqlRest Solution: Proxy Mode

    The Reverse Proxy feature offers two modes:

    Passthrough Mode: Zero Database Connections

    When a proxy endpoint function has no proxy response parameters, NpgsqlRest:

    • Forwards the request directly to the upstream service
    • Returns the upstream response to the client
    • Never opens a database connection
    sql
    sql
    -- This function body is NEVER executed
    +-- The proxy annotation handles everything
    +create function ai_health()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +
    +comment on function ai_health is '
    +HTTP GET /ai/health
    +@proxy';

    When a client calls /ai/health, NpgsqlRest forwards to the configured upstream host, receives the response, and returns it - no PostgreSQL involved.

    Transform Mode: Process Before Returning

    When a proxy endpoint function has proxy response parameters (_proxy_body, _proxy_status_code, etc.), NpgsqlRest:

    • Forwards the request to the upstream service
    • Passes the response to your PostgreSQL function
    • Your function can cache, transform, enrich, or validate the response
    • Returns your function's result to the client
    sql
    sql
    create function ai_summarize(
    +    _text text,
    +    _max_length int default 150,
    +    -- Proxy response parameters - these trigger transform mode
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +declare
    +    _result json;
    +begin
    +    -- Check cache first
    +    select cached_response into _result
    +    from analysis_cache
    +    where text_hash = md5(_text);
    +
    +    if _result is not null then
    +        return _result;  -- Cache hit - no upstream call was made
    +    end if;
    +
    +    -- Upstream response is in _proxy_body
    +    if not _proxy_success then
    +        return json_build_object('error', _proxy_error_message);
    +    end if;
    +
    +    -- Cache and return
    +    insert into analysis_cache (text_hash, cached_response)
    +    values (md5(_text), _proxy_body::json);
    +
    +    return _proxy_body::json;
    +end;
    +$$;
    +
    +comment on function ai_summarize is '
    +HTTP POST /ai/summarize
    +@authorize
    +@proxy POST';

    Architecture: NpgsqlRest as API Gateway

    mermaid
    flowchart TB
    +    C["Client Request"] --> N["NpgsqlRest Server
    +    (Reverse Proxy Layer)"]
    +
    +    N --> PT["Passthrough Proxy
    +    No DB Conn · Health, etc"]
    +    N --> TR["Transform Proxy
    +    DB + Cache · AI, APIs"]
    +    N --> RE["Regular Endpoint
    +    DB Only · SQL/Functions"]
    +
    +    PT --> U1["Upstream Service
    +    (AI Server)"]
    +    TR --> U2["Upstream Service
    +    (AI Server)"]
    +
    +    U2 --> PG["PostgreSQL
    +    (Cache)"]

    Building the AI Text Analysis Service

    The example API proxies to a local AI text processing service (running on Bun), with PostgreSQL as the cache.

    The Upstream AI Service

    First, we need a service to proxy to. This Bun server provides text analysis:

    typescript
    typescript
    // upstream/server.ts
    +const PORT = 3001;
    +
    +const server = Bun.serve({
    +    port: PORT,
    +    async fetch(req) {
    +        const url = new URL(req.url);
    +        const path = url.pathname;
    +
    +        // Health check
    +        if (path === '/ai/health' && req.method === 'GET') {
    +            return Response.json({
    +                status: 'healthy',
    +                service: 'ai-text-service',
    +                version: '1.0.0'
    +            });
    +        }
    +
    +        // Summarization
    +        if (path === '/ai/summarize' && req.method === 'POST') {
    +            const { text, max_length = 150 } = await req.json();
    +            const summary = summarizeText(text, max_length);
    +            return Response.json({
    +                summary,
    +                original_length: text.length,
    +                summary_length: summary.length,
    +                model: 'simple-extractive-v1'
    +            });
    +        }
    +
    +        // Sentiment analysis
    +        if (path === '/ai/sentiment' && req.method === 'POST') {
    +            const { text } = await req.json();
    +            const result = analyzeSentiment(text);
    +            return Response.json({
    +                ...result,
    +                model: 'simple-lexicon-v1'
    +            });
    +        }
    +
    +        // Full analysis
    +        if (path === '/ai/analyze' && req.method === 'POST') {
    +            const { text, max_length = 150, max_keywords = 5 } = await req.json();
    +            return Response.json({
    +                summary: { text: summarizeText(text, max_length) },
    +                sentiment: analyzeSentiment(text),
    +                keywords: { words: extractKeywords(text, max_keywords) },
    +                model: 'combined-analysis-v1',
    +                processed_at: new Date().toISOString()
    +            });
    +        }
    +
    +        return Response.json({ error: 'Not found' }, { status: 404 });
    +    }
    +});

    This simulates what a real AI/ML service would provide - in production, this could be:

    • An LLM inference server (Ollama, vLLM)
    • A Python FastAPI service with transformers
    • Any third-party AI API

    PostgreSQL Schema: Caching Layer

    sql
    sql
    create table example_10.analysis_cache (
    +    id serial primary key,
    +    text_hash text not null,
    +    text_preview text not null,
    +    summary text,
    +    sentiment text,
    +    sentiment_score numeric(4,2),
    +    sentiment_confidence numeric(4,2),
    +    keywords text[],
    +    model_version text,
    +    created_at timestamptz default now(),
    +    accessed_count int default 1,
    +    last_accessed_at timestamptz default now()
    +);
    +
    +create unique index idx_analysis_cache_hash
    +    on example_10.analysis_cache(text_hash);

    Passthrough Proxy: Health Check

    For health checks, we don't need PostgreSQL at all:

    sql
    sql
    create function example_10.ai_health()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +
    +comment on function example_10.ai_health is '
    +HTTP GET /ai/health
    +@proxy';

    The trigger: the function returns void and has no proxy parameters. NpgsqlRest detects this and uses passthrough mode - no database connection is opened.

    When the load balancer pings /ai/health every 5 seconds, it gets the upstream response directly without touching the connection pool.

    Transform Proxy: Summarization with Caching

    For summarization, we want to cache results to avoid repeated AI calls:

    sql
    sql
    create function example_10.ai_summarize(
    +    _text text,
    +    _max_length int default 150,
    +    -- Proxy response parameters trigger transform mode
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +declare
    +    _text_hash text;
    +    _cached json;
    +    _result json;
    +begin
    +    -- Generate cache key
    +    _text_hash := md5(_text || '::' || _max_length::text);
    +
    +    -- Check cache first
    +    select json_build_object(
    +        'summary', ac.summary,
    +        'original_length', length(_text),
    +        'summary_length', length(ac.summary),
    +        'cached', true,
    +        'cache_hits', ac.accessed_count
    +    )
    +    into _cached
    +    from example_10.analysis_cache ac
    +    where ac.text_hash = _text_hash;
    +
    +    if _cached is not null then
    +        -- Update cache stats
    +        update example_10.analysis_cache
    +        set accessed_count = accessed_count + 1,
    +            last_accessed_at = now()
    +        where text_hash = _text_hash;
    +
    +        return _cached;  -- Return cached result
    +    end if;
    +
    +    -- Handle proxy errors
    +    if not _proxy_success then
    +        return json_build_object(
    +            'error', coalesce(_proxy_error_message, 'AI service unavailable'),
    +            'status_code', _proxy_status_code
    +        );
    +    end if;
    +
    +    -- Parse and cache the response
    +    _result := _proxy_body::json;
    +
    +    insert into example_10.analysis_cache
    +        (text_hash, text_preview, summary, model_version)
    +    values (
    +        _text_hash,
    +        left(_text, 100),
    +        _result->>'summary',
    +        _result->>'model'
    +    );
    +
    +    -- Return enriched response
    +    return json_build_object(
    +        'summary', _result->>'summary',
    +        'original_length', (_result->>'original_length')::int,
    +        'summary_length', (_result->>'summary_length')::int,
    +        'model', _result->>'model',
    +        'cached', false
    +    );
    +end;
    +$$;
    +
    +comment on function example_10.ai_summarize is '
    +HTTP POST /ai/summarize
    +@authorize
    +@proxy POST';

    What happens:

    1. Client calls POST /ai/summarize with {"text": "..."}
    2. NpgsqlRest forwards to upstream: POST http://localhost:3001/ai/summarize
    3. Upstream returns the AI analysis
    4. NpgsqlRest passes the response to our function via _proxy_body
    5. Our function checks cache, stores if new, returns result
    6. Client receives the response

    On cache hit:

    • The function returns early with cached data
    • The upstream call still happens (the proxy is transparent)
    • But we could extend this to skip the proxy call entirely using HTTP Types

    Full Analysis: Complete Transform Example

    sql
    sql
    create function example_10.ai_analyze(
    +    _text text,
    +    _max_length int default 150,
    +    _max_keywords int default 5,
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +declare
    +    _text_hash text;
    +    _cached record;
    +    _result json;
    +begin
    +    -- Cache key includes all parameters
    +    _text_hash := md5(_text || '::full::' || _max_length || '::' || _max_keywords);
    +
    +    -- Check for complete cached analysis
    +    select summary, sentiment, sentiment_score,
    +           sentiment_confidence, keywords, accessed_count
    +    into _cached
    +    from example_10.analysis_cache
    +    where text_hash = _text_hash
    +      and summary is not null
    +      and sentiment is not null
    +      and keywords is not null;
    +
    +    if found then
    +        -- Update stats and return cached
    +        update example_10.analysis_cache
    +        set accessed_count = accessed_count + 1,
    +            last_accessed_at = now()
    +        where text_hash = _text_hash;
    +
    +        return json_build_object(
    +            'summary', json_build_object('text', _cached.summary),
    +            'sentiment', json_build_object(
    +                'sentiment', _cached.sentiment,
    +                'score', _cached.sentiment_score,
    +                'confidence', _cached.sentiment_confidence
    +            ),
    +            'keywords', json_build_object(
    +                'words', to_json(_cached.keywords),
    +                'count', array_length(_cached.keywords, 1)
    +            ),
    +            'cached', true,
    +            'cache_hits', _cached.accessed_count
    +        );
    +    end if;
    +
    +    -- Handle errors
    +    if not _proxy_success then
    +        return json_build_object(
    +            'error', coalesce(_proxy_error_message, 'AI service unavailable'),
    +            'status_code', _proxy_status_code
    +        );
    +    end if;
    +
    +    -- Parse and cache
    +    _result := _proxy_body::json;
    +
    +    insert into example_10.analysis_cache (
    +        text_hash, text_preview, summary, sentiment,
    +        sentiment_score, sentiment_confidence, keywords, model_version
    +    ) values (
    +        _text_hash,
    +        left(_text, 100),
    +        _result->'summary'->>'text',
    +        _result->'sentiment'->>'sentiment',
    +        (_result->'sentiment'->>'score')::numeric,
    +        (_result->'sentiment'->>'confidence')::numeric,
    +        array(select jsonb_array_elements_text(
    +            (_result->'keywords'->'words')::jsonb)),
    +        _result->>'model'
    +    )
    +    on conflict (text_hash) do update set
    +        summary = excluded.summary,
    +        sentiment = excluded.sentiment,
    +        sentiment_score = excluded.sentiment_score,
    +        sentiment_confidence = excluded.sentiment_confidence,
    +        keywords = excluded.keywords,
    +        accessed_count = example_10.analysis_cache.accessed_count + 1,
    +        last_accessed_at = now();
    +
    +    return json_build_object(
    +        'summary', _result->'summary',
    +        'sentiment', _result->'sentiment',
    +        'keywords', _result->'keywords',
    +        'model', _result->>'model',
    +        'processed_at', _result->>'processed_at',
    +        'cached', false
    +    );
    +end;
    +$$;
    +
    +comment on function example_10.ai_analyze is '
    +HTTP POST /ai/analyze
    +@authorize
    +@proxy POST';

    Configuration

    Enable proxy in your configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "http://localhost:3001",
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      "ForwardResponseHeaders": true
    +    }
    +  }
    +}
    OptionDescription
    HostDefault upstream URL - can be overridden per endpoint
    DefaultTimeoutRequest timeout for proxy calls
    ForwardHeadersPass client headers to upstream
    ExcludeHeadersHeaders to strip from forwarded requests
    ForwardResponseHeadersPass upstream headers to client

    Proxy Response Parameters

    When your function includes these parameters, it enters transform mode:

    ParameterTypeDescription
    _proxy_status_codeintHTTP status from upstream (200, 404, etc.)
    _proxy_bodytextResponse body content
    _proxy_headersjsonResponse headers as JSON object
    _proxy_content_typetextContent-Type header value
    _proxy_successbooleanTrue for 2xx status codes
    _proxy_error_messagetextError description if request failed

    Parameter names are configurable in ProxyOptions.

    The Generated TypeScript Client

    NpgsqlRest auto-generates a typed client:

    typescript
    typescript
    // Auto-generated
    +interface IAiAnalyzeRequest {
    +    text: string | null;
    +    maxLength?: number | null;
    +    maxKeywords?: number | null;
    +}
    +
    +export async function aiAnalyze(
    +    request: IAiAnalyzeRequest
    +): Promise<{
    +    status: number,
    +    response: any,
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/ai/analyze", {
    +        method: "POST",
    +        body: JSON.stringify(request)
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() : undefined,
    +        error: !response.ok ? await response.json() : undefined
    +    };
    +}

    Frontend usage:

    typescript
    typescript
    import { aiAnalyze, aiHealth } from "./example10Api.ts";
    +
    +// Check service health (passthrough - no DB connection)
    +const health = await aiHealth();
    +if (health.status === 200) {
    +    console.log("AI service is online");
    +}
    +
    +// Analyze text (transform - with caching)
    +const result = await aiAnalyze({
    +    text: "PostgreSQL is an excellent database...",
    +    maxLength: 150,
    +    maxKeywords: 5
    +});
    +
    +if (result.response.cached) {
    +    console.log(`Cache hit! ${result.response.cache_hits} previous accesses`);
    +} else {
    +    console.log("Fresh analysis from AI service");
    +}

    Docker: Bun Runtime Image

    For deployments where the upstream service runs in the same container, NpgsqlRest provides a Docker image with pre-installed Bun:

    bash
    bash
    docker pull vbilopav/npgsqlrest:latest-bun
    +
    +docker run --name npgsqlrest-bun -it \
    +    -p 8080:8080 \
    +    -v ./config.json:/app/config.json \
    +    -v ./upstream:/app/upstream \
    +    vbilopav/npgsqlrest:latest-bun

    This image includes the Bun JavaScript runtime, so proxy endpoints can execute Bun scripts within the same container. Useful for:

    • Lightweight AI/ML preprocessing
    • Custom transformation logic
    • Integration adapters
    • Self-contained microservice deployments

    Use Cases for Reverse Proxy

    API Gateway Pattern

    Route requests to different microservices:

    sql
    sql
    -- Users service
    +create function users_api()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +comment on function users_api is '
    +HTTP GET /api/users
    +@proxy https://users-service.internal:8080';
    +
    +-- Orders service
    +create function orders_api()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +comment on function orders_api is '
    +HTTP GET /api/orders
    +@proxy https://orders-service.internal:8080';

    Caching Expensive Operations

    Cache AI, ML, or expensive API calls:

    sql
    sql
    -- First call: fetch from upstream, cache in PostgreSQL
    +-- Subsequent calls: return cached result instantly

    Data Enrichment

    Combine external data with local database data:

    sql
    sql
    create function get_enriched_weather(
    +    city text,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null
    +)
    +returns json
    +language plpgsql as $$
    +declare
    +    local_prefs json;
    +begin
    +    -- Get user's city preferences from database
    +    select json_build_object('favorite', is_favorite, 'notes', notes)
    +    into local_prefs
    +    from user_city_preferences
    +    where city_name = city;
    +
    +    -- Combine with weather data from proxy
    +    return json_build_object(
    +        'weather', _proxy_body::json,
    +        'local', coalesce(local_prefs, '{}'::json)
    +    );
    +end;
    +$$;
    +
    +comment on function get_enriched_weather is '
    +HTTP GET /weather/{city}
    +@proxy https://api.weather.com/v1/current';

    Authentication Context Forwarding

    Forward authenticated user claims to upstream:

    sql
    sql
    create function secure_api_call(
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return _proxy_body::json;
    +end;
    +$$;
    +
    +comment on function secure_api_call is '
    +HTTP GET /secure-data
    +@authorize
    +@user_context
    +@proxy https://internal-api.example.com/data';

    With user_context, NpgsqlRest forwards user claims as HTTP headers to the upstream service.

    The Numbers

    MetricWithout ProxyWith Passthrough Proxy
    Health check DB connections1 per request0
    Connection pool usage100%Reduced by passthrough %
    Response latency (cached)~5-10msSame (transform mode)
    Response latency (passthrough)~5-10ms~2-3ms (no DB)

    For a service with 50% health check traffic:

    • 50% fewer database connections
    • Faster health check responses
    • More connections available for data operations

    Code Comparison

    ComponentTraditional Node.jsNpgsqlRest Proxy
    Proxy middleware~50 lines0
    Caching logic~40 linesIn SQL function
    HTTP client setup~30 linesConfig only
    Error handling~30 linesBuilt-in
    Type definitions~20 linesAuto-generated
    Total~170 lines~80 lines SQL

    Plus the upstream service remains identical - NpgsqlRest adds the gateway layer without changing your services.

    Summary: When to Use Proxy Mode

    Use Passthrough Mode for:

    • Health checks and readiness probes
    • Static configuration endpoints
    • Simple forwarding to internal services
    • Any endpoint that doesn't need database access

    Use Transform Mode for:

    • Caching expensive external API calls
    • Data enrichment (combining external + local data)
    • Response transformation before returning
    • Audit logging of external API usage
    • Rate limiting based on database state

    Proxy mode is NOT for:

    • High-frequency streaming (use SSE or WebSockets)
    • Large file transfers (use direct proxying)
    • Complex retry logic (use message queues)

    The Reverse Proxy feature extends NpgsqlRest beyond a database-to-API bridge into a full API gateway - preserving the annotation-driven, SQL-first workflow while adding routing, transformation, and caching on top.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/secure-image-uploads-postgresql-typescript.html b/blog/secure-image-uploads-postgresql-typescript.html new file mode 100644 index 000000000..00bc316ac --- /dev/null +++ b/blog/secure-image-uploads-postgresql-typescript.html @@ -0,0 +1,331 @@ + + + + + + Secure Image Uploads with PostgreSQL: File System and Large Objects | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Secure Image Uploads with PostgreSQL: File System, Large Objects, and Type-Safe TypeScript

    January 2026 · UploadsPostgreSQLTypeScriptNpgsqlRest

    Image uploads in NpgsqlRest come down to one SQL function and one annotation: the annotation picks the storage - file system, PostgreSQL Large Objects, or both - and the function records the metadata. The TypeScript client, progress tracking included, is generated for you.

    Source Code: github.com/NpgsqlRest/npgsqlrest-docs/examples/6_image_uploads

    Storage Options

    StrategyUse WhenStored In
    File SystemFast CDN delivery neededDisk files
    Large ObjectDatabase backup requiredPostgreSQL pg_largeobject
    CombinedNeed both speed and backupBoth locations

    When to Use Each Strategy

    File System - Use when:

    • You need fast, direct file serving (web server/CDN)
    • Files are large and frequently accessed
    • You have separate backup infrastructure

    Large Object - Use when:

    • You want images included in database backups automatically
    • Transactional integrity matters (failed upload = no orphaned files)
    • You need database-level access control

    Combined - Use when:

    • Images are mission-critical and need redundancy
    • You want fast CDN serving AND automatic database backups

    Step 1: Create the Schema

    Create a table to track uploads and a response type for type-safe TypeScript generation:

    sql
    sql
    -- Unified uploads table
    +create table example_6.uploads (
    +    id int primary key generated always as identity,
    +    user_id int not null,
    +    file_name text not null,
    +    content_type text not null,
    +    file_size bigint not null,
    +    oid bigint,           -- Large Object identifier (when using LO handler)
    +    file_path text,       -- File path (when using FS handler)
    +    uploaded_at timestamptz not null default now()
    +);
    +
    +-- Response type for generated TypeScript interfaces
    +create type example_6.upload_response as (
    +    success boolean,
    +    status text,
    +    file_name text,
    +    content_type text,
    +    file_size bigint,
    +    oid bigint,
    +    file_path text
    +);

    The oid and file_path columns are nullable - each upload populates one or both depending on which handler is used.

    Step 2: Configure Upload Handlers

    In config.json, enable the upload handlers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": true,
    +      "UseDefaultUploadMetadataParameter": true,
    +      "DefaultUploadMetadataParameterName": "_meta",
    +
    +      "UploadHandlers": {
    +        "StopAfterFirstSuccess": false,
    +        "BufferSize": 16384,
    +
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckImage": true,
    +
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "./uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckImage": true
    +      }
    +    }
    +  }
    +}

    See upload configuration for all options.

    Step 3: Create the Upload Function

    All three upload functions use identical code - only the annotation changes.

    sql
    sql
    create or replace function example_6.upload_to_file_system(
    +    _user_id text = null,
    +    _meta json = null
    +)
    +returns setof example_6.upload_response
    +language sql
    +begin atomic;
    +    -- Insert successful uploads into the database
    +    with inserted as (
    +        insert into example_6.uploads (user_id, file_name, content_type, file_size, oid, file_path)
    +        select
    +            _user_id::int,
    +            m->>'fileName',
    +            m->>'contentType',
    +            (m->>'size')::bigint,
    +            (m->>'oid')::bigint,
    +            m->>'filePath'
    +        from json_array_elements(_meta) as m
    +        where (m->>'success')::boolean = true
    +        returning *
    +    )
    +    -- Return all upload results to the client
    +    select
    +        (m->>'success')::boolean,
    +        m->>'status',
    +        m->>'fileName',
    +        m->>'contentType',
    +        (m->>'size')::bigint,
    +        (m->>'oid')::bigint,
    +        m->>'filePath'
    +    from json_array_elements(_meta) as m;
    +end;

    The function:

    1. Receives upload metadata in _meta parameter (injected by NpgsqlRest)
    2. Inserts successful uploads into the database
    3. Returns all results to the client

    Step 4: Add the Upload Annotation

    The annotation in the function comment controls which handler processes uploads:

    sql
    sql
    -- File System storage
    +comment on function example_6.upload_to_file_system(text, json) is '
    +HTTP POST
    +@upload for file_system
    +@param _meta is upload metadata
    +@check_image = true
    +@path = ./uploads
    +@unique_name = true';
    +
    +-- Large Object storage
    +comment on function example_6.upload_to_large_object(text, json) is '
    +HTTP POST
    +@upload for large_object
    +@param _meta is upload metadata
    +@check_image = true';
    +
    +-- Combined (both handlers)
    +comment on function example_6.upload_to_combined(text, json) is '
    +HTTP POST
    +@upload for large_object, file_system
    +@param _meta is upload metadata
    +@check_image = true
    +@path = ./uploads
    +@unique_name = true';

    Key annotations:

    • @upload for <handler> - Which handler(s) to use (comma-separated for multiple)
    • @param _meta is upload metadata - Injects upload results into this parameter
    • check_image = true - Validates file is actually an image (checks magic bytes)
    • check_image = jpg, gif - Only allow specific image formats
    • check_text = true - Validates file is text (not binary)
    • path = ./uploads - Where to store files (file_system)
    • file = {_filename} - Custom filename from parameter (file_system)
    • unique_name = true - Generate UUID filenames (file_system)
    • create_path = true - Create directory if it doesn't exist (file_system)
    • oid = {_oid} - Use specific OID from parameter (large_object)
    • included_mime_types = image/*, application/* - Only allow these MIME types
    • large_object_excluded_mime_types = text* - Exclude MIME types from Large Object handler
    • stop_after_first_success = true - Stop processing after first handler succeeds

    See upload annotations for all options.

    How the Metadata Works

    When you upload a file, NpgsqlRest processes it through the handler and passes metadata to your function:

    json
    json
    [
    +  {
    +    "type": "file_system",
    +    "fileName": "photo.jpg",
    +    "contentType": "image/jpeg",
    +    "size": 245678,
    +    "filePath": "./uploads/abc123-photo.jpg",
    +    "success": true,
    +    "status": "Ok"
    +  }
    +]

    Which fields are populated depends on the handler:

    • file_systemfilePath
    • large_objectoid
    • Both handlers → two entries per file, one from each handler

    When Uploads Fail

    Failed uploads still appear in the metadata with success: false and a status code:

    json
    json
    {
    +  "type": "file_system",
    +  "fileName": "document.pdf",
    +  "contentType": "application/pdf",
    +  "size": 102400,
    +  "filePath": null,
    +  "success": false,
    +  "status": "InvalidImage"
    +}

    Status values:

    • Ok - Upload succeeded
    • InvalidImage - File is not a valid image (when check_image = true)
    • InvalidMimeType - MIME type not in allowed list
    • ProbablyBinary - File appears to be binary (when check_text = true)
    • InvalidFormat - Invalid file format
    • NoNewLines - Text file has no newlines
    • Empty - File is empty
    • Ignored - Handler skipped (when stop_after_first_success = true)

    Step 5: Use the Generated Client

    NpgsqlRest generates a TypeScript client with progress tracking:

    typescript
    typescript
    import { uploadToFileSystem } from "./example6Api.ts";
    +
    +const fileInput = document.getElementById("file-input") as HTMLInputElement;
    +
    +const response = await uploadToFileSystem(
    +    fileInput.files,
    +    { },
    +    (loaded, total) => {
    +        progressBar.style.width = `${Math.round((loaded / total) * 100)}%`;
    +    }
    +);
    +
    +if (response.status === 200) {
    +    console.log("Uploaded:", response.response);
    +}

    See code generation for configuration options.

    Step 6: Serve Images from Large Objects

    For images stored in Large Objects, create a function to retrieve them:

    sql
    sql
    create or replace function example_6.get_image(
    +    _oid bigint,
    +    _mime_type text
    +)
    +returns bytea
    +language sql
    +begin atomic;
    +    select lo_get(_oid);
    +end;
    +
    +comment on function example_6.get_image(bigint, text) is '
    +HTTP GET
    +@raw
    +content_type: {_mime_type}
    +Cache-Control: public, max-age=31536000, immutable';

    Annotations:

    • @raw - Returns binary data, not JSON
    • content_type: {_mime_type} - Sets response content-type from parameter
    • Cache-Control - Enables browser/CDN caching

    Performance: Large Objects vs File System

    Unlike file system storage where the web server serves static files directly, every Large Object request requires:

    1. Opening a database connection from the pool
    2. Executing the lo_get() function
    3. Streaming binary data through the application
    4. Returning the connection to the pool

    This is more expensive than serving a static file. For high-traffic images, use Cache-Control headers so browsers and CDNs cache the response:

    • public - Any cache can store it
    • max-age=31536000 - Cache for 1 year
    • immutable - Content never changes (safe because each OID is unique)

    After the first request, subsequent requests are served from cache without hitting your database.

    Displaying Images

    Serve images based on storage type:

    typescript
    typescript
    if (upload.filePath) {
    +    // File system: static file URL
    +    imgUrl = upload.filePath.replace('./uploads', '/uploads');
    +} else if (upload.oid) {
    +    // Large Object: API endpoint
    +    imgUrl = `/api/get-image?oid=${upload.oid}&mimeType=${encodeURIComponent(upload.contentType)}`;
    +}

    Backup Advantage

    Large Objects are included in pg_dump automatically. With combined storage, you get:

    • Fast serving from file system / CDN
    • Automatic backup with your database

    Traditional Approach Comparison

    For comparison, here is the same feature in three common stacks:

    ASP.NET Core (C#):

    csharp
    csharp
    [HttpPost("upload")]
    +[Authorize]
    +public async Task<IActionResult> Upload(IFormFile file)
    +{
    +    if (file == null || file.Length == 0)
    +        return BadRequest("No file");
    +
    +    // Validate image
    +    using var image = Image.Load(file.OpenReadStream());
    +    if (image == null)
    +        return BadRequest("Invalid image");
    +
    +    // Generate unique filename
    +    var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
    +    var filePath = Path.Combine(_uploadPath, fileName);
    +
    +    // Save to file system
    +    using var stream = new FileStream(filePath, FileMode.Create);
    +    await file.CopyToAsync(stream);
    +
    +    // Save metadata to database
    +    var upload = new Upload {
    +        UserId = User.GetUserId(),
    +        FileName = file.FileName,
    +        ContentType = file.ContentType,
    +        FileSize = file.Length,
    +        FilePath = filePath
    +    };
    +    _context.Uploads.Add(upload);
    +    await _context.SaveChangesAsync();
    +
    +    return Ok(new { upload.Id, filePath });
    +}

    Spring Boot (Java):

    java
    java
    @PostMapping("/upload")
    +@PreAuthorize("isAuthenticated()")
    +public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) {
    +    if (file.isEmpty()) {
    +        return ResponseEntity.badRequest().body("No file");
    +    }
    +
    +    // Validate image
    +    try {
    +        BufferedImage img = ImageIO.read(file.getInputStream());
    +        if (img == null) throw new IOException("Invalid image");
    +    } catch (IOException e) {
    +        return ResponseEntity.badRequest().body("Invalid image");
    +    }
    +
    +    // Generate unique filename and save
    +    String fileName = UUID.randomUUID() + getExtension(file.getOriginalFilename());
    +    Path filePath = Paths.get(uploadPath, fileName);
    +    Files.copy(file.getInputStream(), filePath);
    +
    +    // Save to database
    +    Upload upload = new Upload();
    +    upload.setUserId(getCurrentUserId());
    +    upload.setFileName(file.getOriginalFilename());
    +    upload.setContentType(file.getContentType());
    +    upload.setFileSize(file.getSize());
    +    upload.setFilePath(filePath.toString());
    +    uploadRepository.save(upload);
    +
    +    return ResponseEntity.ok(Map.of("id", upload.getId(), "path", filePath));
    +}

    FastAPI (Python):

    python
    python
    @app.post("/upload")
    +async def upload(file: UploadFile, user: User = Depends(get_current_user)):
    +    if not file:
    +        raise HTTPException(400, "No file")
    +
    +    # Validate image
    +    contents = await file.read()
    +    try:
    +        Image.open(io.BytesIO(contents)).verify()
    +    except:
    +        raise HTTPException(400, "Invalid image")
    +
    +    # Generate unique filename and save
    +    file_name = f"{uuid.uuid4()}{Path(file.filename).suffix}"
    +    file_path = UPLOAD_PATH / file_name
    +    async with aiofiles.open(file_path, 'wb') as f:
    +        await f.write(contents)
    +
    +    # Save to database
    +    upload = Upload(
    +        user_id=user.id,
    +        file_name=file.filename,
    +        content_type=file.content_type,
    +        file_size=len(contents),
    +        file_path=str(file_path)
    +    )
    +    db.add(upload)
    +    await db.commit()
    +
    +    return {"id": upload.id, "path": str(file_path)}

    And this is just the backend. You still need:

    • ORM entity classes / models
    • Repository interfaces
    • Database migration files
    • DTOs for request/response
    • TypeScript interfaces (manual or OpenAPI tooling)
    • Frontend upload code with progress tracking

    Meanwhile, NpgsqlRest handles the backend automatically based on your SQL function and annotations, and generates this TypeScript client for your frontend:

    typescript
    typescript
    // Auto-generated - you write ZERO of this code
    +
    +interface IUploadToFileSystemResponse {
    +    type: string;
    +    fileName: string;
    +    contentType: string;
    +    size: number;
    +    success: boolean;
    +    status: string;
    +    filePath?: string;
    +    oid?: number;
    +}
    +
    +export async function uploadToFileSystem(
    +    files: FileList | null,
    +    request: IUploadToFileSystemRequest,
    +    progress?: (loaded: number, total: number) => void,
    +): Promise<{
    +    status: number,
    +    response: IUploadToFileSystemResponse[],
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    return new Promise((resolve, reject) => {
    +        if (!files || files.length === 0) {
    +            reject(new Error("No files to upload"));
    +            return;
    +        }
    +        var xhr = new XMLHttpRequest();
    +        if (progress) {
    +            xhr.upload.addEventListener("progress", (event) => {
    +                if (event.lengthComputable && progress) {
    +                    progress(event.loaded, event.total);
    +                }
    +            }, false);
    +        }
    +        xhr.onload = function () {
    +            if (this.status >= 200 && this.status < 300) {
    +                resolve({status: this.status, response: JSON.parse(this.responseText), error: undefined});
    +            } else {
    +                resolve({status: this.status, response: [], error: JSON.parse(this.responseText)});
    +            }
    +        };
    +        xhr.onerror = function () {
    +            reject({xhr: this, status: this.status, statusText: this.statusText});
    +        };
    +        xhr.open("POST", baseUrl + "/api/example-6/upload-to-file-system" + parseQuery(request));
    +        const formData = new FormData();
    +        for(let i = 0; i < files.length; i++) {
    +            formData.append("file", files[i], files[i].name);
    +        }
    +        xhr.send(formData);
    +    });
    +}

    Typed interfaces, FormData handling, progress callbacks, error handling - all generated from your SQL function signature.

    Line count comparison for a complete upload feature:

    ComponentTraditionalNpgsqlRest
    Backend endpoint30-500
    Entity/Model class15-250
    Repository10-200
    DTO classes10-200
    Database migration10-1510-15
    SQL function020
    Annotation05
    TypeScript types15-30 (manual)0 (generated)
    Frontend upload30-5010 (generated)
    Total~120-210~35-50

    That's 70-80% less code - and the NpgsqlRest code is mostly just your SQL function doing exactly what you want, with TypeScript client generated automatically.

    Summary

    To add image uploads to your NpgsqlRest application:

    1. Create an uploads table with oid and file_path columns
    2. Enable upload handlers in config.json
    3. Write a single upload function that inserts from _meta JSON
    4. Add upload for <handler> annotation to choose storage
    5. Use the generated TypeScript client with progress callbacks

    The same function code works for all three storage strategies - only the annotation changes.

    SQL File Source

    Everything in this post also works with SQL file endpoints — no functions needed. See the SQL file version of this example.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/sql-file-source-rest-api-from-plain-sql.html b/blog/sql-file-source-rest-api-from-plain-sql.html new file mode 100644 index 000000000..75141c90a --- /dev/null +++ b/blog/sql-file-source-rest-api-from-plain-sql.html @@ -0,0 +1,252 @@ + + + + + + SQL File Source: REST Endpoints from Plain .sql Files | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    SQL File Source: REST Endpoints from Plain .sql Files

    March 2026 · SQL Filesv3.12.0PostgreSQLNpgsqlRest

    NpgsqlRest 3.12.0 introduces SQL File Source. This is a REST endpoint:

    sql
    sql
    -- HTTP GET
    +-- @param $1 active
    +select user_id, username, email, active from users where active = $1;

    Drop this file as sql/get-users.sql, and GET /api/get-users?active=true is live. TypeScript client generated. Static type checking against your database schema at startup. No DDL, no migration, no CREATE FUNCTION.

    For comparison, here's the same endpoint as a PostgreSQL function — the way it worked before:

    sql
    sql
    create or replace function get_users(_active boolean)
    +returns table (user_id int, username text, email text, active boolean)
    +language sql
    +begin atomic;
    +  select user_id, username, email, active from users where active = _active;
    +end;
    +
    +comment on function get_users(boolean) is 'HTTP GET';

    Eight lines of DDL wrapping a 1-line query. Requires a migration. Lives in the database, not your file system. Changing the return columns means DROP and CREATE (or CREATE OR REPLACE with matching signatures).

    The SQL file version: 3 lines. Edit the file, restart the server. All existing NpgsqlRest features — auth, caching, SSE, uploads, proxy, exports — work unchanged.

    For the full technical reference, see the SQL File Endpoints Guide. This post shows the feature in practice.

    All code in this post is taken verbatim from the examples repository.

    The Simplest Endpoint

    From 1_my_first_function_sql_file:

    sql
    sql
    -- HTTP GET
    +select user_id, username, email, active from example_2.users;

    That's a complete endpoint. GET /api/get-users returns:

    json
    json
    [{"userId": 1, "username": "alice", "email": "alice@example.com", "active": true}, ...]

    No function definition. No migration DDL. The file is the endpoint.

    Multi-Command: Multiple Queries in One Request

    From the same example — a single file, two statements, one HTTP request:

    sql
    sql
    -- HTTP GET
    +
    +-- @result first
    +select * from (
    +    values ('Hello, World!'), 
    +    ('This is my first SQL endpoint.'), 
    +    ('Enjoy coding in SQL!')
    +) as t(text);
    +
    +-- @result second
    +-- @single
    +select current_query() as query_text, current_user as user, current_timestamp as timestamp;

    Returns a JSON object with named result keys:

    json
    json
    {
    +  "first": ["Hello, World!", "This is my first SQL endpoint.", "Enjoy coding in SQL!"],
    +  "second": {"queryText": "...", "user": "postgres", "timestamp": "..."}
    +}

    @result names the keys. @single returns one row as an object instead of an array. Single-column results become flat arrays automatically. All executed in a single database round-trip via NpgsqlBatch.

    Authentication: Login, Logout, Who Am I

    From 3_security_and_auth_sql_file — three files, complete cookie auth:

    sql/login.sql:

    sql
    sql
    /*
    +HTTP POST
    +@login
    +@allow_anonymous
    +@param $1 username
    +@param $2 password
    +*/
    +select
    +    'cookies' as scheme,
    +    u.user_id::text as user_id,
    +    u.username,
    +    u.email
    +from example_3.users u
    +where
    +    u.username = $1
    +    and example_3.verify_password($2, u.password_hash);

    sql/who-am-i.sql:

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@user_parameters
    +@param $1 _user_id default null
    +@param $2 _username default null
    +@param $3 _email default null
    +*/
    +select $1 as user_id, $2 as username, $3 as email;

    sql/logout.sql:

    sql
    sql
    -- HTTP POST
    +-- @logout
    +-- @authorize
    +select 'cookies'

    The annotations (@login, @logout, @authorize, @user_parameters) work exactly the same as in function endpoints. The default null on who-am-i.sql parameters ensures they're always bindable — @user_parameters fills them from the authenticated user's claims.

    Real-Time Chat with SSE

    From 8_simple_chat_client_sql_file — a real-time chat message sender in one file:

    sql
    sql
    /*
    +HTTP POST
    +@authorize
    +@sse
    +@sse_scope authorize
    +@user_parameters
    +@param $1 messageText text
    +@param $2 _user_id text = null
    +@param $3 _user_name text = null
    +@void
    +*/
    +begin;
    +
    +select set_config('example_8.message_text', $1, true);
    +select set_config('example_8.current_user_id', $2, true); 
    +select set_config('example_8.current_user_name', $3, true);
    +
    +do
    +$$
    +declare
    +    _message_text text = current_setting('example_8.message_text')::text;
    +    _user_id int = current_setting('example_8.current_user_id')::int;
    +    _user_name text = current_setting('example_8.current_user_name')::text;
    +    
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into example_8.messages (user_id, username, message_text)
    +    values (_user_id, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'user_id', _user_id,
    +        'username', _user_name,
    +        'message_text', _message_text,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    +
    +end;

    Several patterns work together here:

    • @sse makes this a Server-Sent Events endpoint — RAISE INFO broadcasts JSON to all connected clients
    • @void returns 204 to the sender (the message is delivered via SSE, not the response)
    • set_config / current_setting bridges parameters into the DO block (since DO can't receive $N parameters)
    • @user_parameters auto-fills _user_id and _user_name from the authenticated user's claims

    No WebSockets, no message brokers — just SQL and SSE.

    CSV Export with Basic Auth

    From 5_csv_basic_auth_sql_file:

    sql
    sql
    /*
    +HTTP GET
    +@raw
    +@separator ,
    +@new_line \n
    +@columns
    +Content-Type: text/csv
    +Content-Disposition: attachment; filename="sales_report.csv"
    +@basic_auth admin lgjSqahngJF9DN0W+2vAf+EDgxSs14e9ag+DezupGdsftJJ8DUphu6cfroMB6Uqp
    +@user_parameters
    +@param $1 _user_name text default null
    +*/
    +select
    +    $1 as exported_by, 
    +    order_id,
    +    customer_name,
    +    product,
    +    quantity,
    +    unit_price,
    +    total,
    +    order_date
    +from example_5.sales
    +order by order_date;

    @raw outputs plain text instead of JSON. @separator , and @columns produce a proper CSV with headers. The custom Content-Type and Content-Disposition headers make the browser download it as a file. @basic_auth means Excel's "Get Data from Web" can connect directly — turning this into a live data feed.

    Dynamic Excel Output

    From 14_table_format_sql_file:

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@define_param format text
    +@define_param excelFileName text
    +@define_param excelSheet text
    +@table_format = {format}
    +@excel_file_name = {excelFileName}
    +@excel_sheet = {excelSheet}
    +@tsclient_url_only = true
    +*/
    +select * from (values
    +    (42,        9999999999::bigint, 3.1415::numeric(10,4), 2.71828::float8, true,  'hello world',      '2025-06-15'::date, '2025-06-15 14:30:00'::timestamp, '09:45:30'::time, '{"key":"value"}'::json, null::text, null::int),
    +    (-1,        0::bigint,          0.0001::numeric(10,4), -99.99::float8,  false, 'special <chars> &', '2000-01-01'::date, '2000-01-01 00:00:00'::timestamp, '23:59:59'::time, '[1,2,3]'::json,        'not null', 7),
    +    (2147483647, -1::bigint,        99999.9999::numeric(10,4), 0::float8,   true,  '',                  '1999-12-31'::date, '1999-12-31 23:59:59'::timestamp, '00:00:00'::time, 'null'::json,           null::text, null::int)
    +) as t(int_val, bigint_val, numeric_val, float_val, bool_val, text_val, date_val, timestamp_val, time_val, json_val, null_text, null_int);

    @define_param creates HTTP parameters that feed into annotation placeholders without appearing in the SQL. GET /api/get-data?format=excel&excelFileName=report.xlsx&excelSheet=Data streams a native .xlsx file. Change format to html_table for HTML, or omit it for JSON. Same SQL, different output.

    Nested Custom Types

    From 12_custom_types_sql_file:

    sql
    sql
    /*
    +HTTP GET
    +@nested
    +@param $1 authorId int default null
    +*/
    +select
    +    row(a.author_id, first_name, last_name)::example_12.authors as author,
    +    count(b.*) as books
    +from example_12.authors a
    +left join example_12.books b using (author_id)
    +where
    +    $1 is null or author_id = $1
    +group by
    +    a.author_id, first_name, last_name;

    @nested wraps composite type columns as nested JSON objects instead of flattening them inline:

    json
    json
    [{"author": {"authorId": 1, "firstName": "Alice", "lastName": "Smith"}, "books": 3}, ...]

    External API Calls

    From 9_http_calls_sql_file — a financial dashboard that calls two external APIs in parallel and processes the results in SQL:

    sql
    sql
    /*
    +HTTP GET /financial-dashboard
    +@authorize
    +
    +@param $1 _base_currency text
    +@param $2 _target_currencies_csv text
    +@param $3 _crypto_ids_csv text
    +@param $4 _vs_currencies_csv text
    +
    +@param $5 _exchange_rate_response example_9.exchange_rate_api
    +@param $6 _crypto_response example_9.crypto_price_api
    +*/
    +
    +begin;
    +
    +-- @skip
    +create temp table _var on commit drop as
    +select 
    +    $1::text as base_currency,
    +    $2::text as target_currencies_csv,
    +    $3::text as crypto_ids_csv,
    +    $4::text as vs_currencies_csv,
    +    $5::example_9.exchange_rate_api as exchange_rate_response,
    +    $6::example_9.crypto_price_api as crypto_response;
    +
    +do
    +$$
    +declare
    +    _base_currency text = (select base_currency from _var);
    +    _target_currencies_csv text = (select target_currencies_csv from _var);
    +    _exchange_rate_response example_9.exchange_rate_api = (select exchange_rate_response from _var);
    +    _crypto_response example_9.crypto_price_api = (select crypto_response from _var);
    +
    +    _result example_9.financial_dashboard_result;
    +    _filtered_rates jsonb = '{}'::jsonb;
    +    _rate_data jsonb;
    +    _currency text;
    +    _target_arr text[];
    +begin
    +    if (_exchange_rate_response).success then
    +        _rate_data = (_exchange_rate_response).body;
    +        _target_arr = string_to_array(_target_currencies_csv, ',');
    +        foreach _currency in array _target_arr loop
    +            _currency = upper(trim(_currency));
    +            if _rate_data->'rates' ? _currency then
    +                _filtered_rates = _filtered_rates ||
    +                    jsonb_build_object(_currency, _rate_data->'rates'->_currency);
    +            end if;
    +        end loop;
    +        _result.fiat_base_currency = upper(_base_currency);
    +        _result.fiat_rates = _filtered_rates::json;
    +        _result.fiat_last_updated = _rate_data->>'time_last_update_utc';
    +        _result.fiat_success = true;
    +    end if;
    +
    +    if (_crypto_response).success then
    +        _result.crypto_prices = (_crypto_response).body;
    +        _result.crypto_success = true;
    +    end if;
    +
    +    create temp table _result_out on commit drop as
    +    select (_result).*;
    +end;
    +$$;
    +
    +-- @result dashboard
    +-- @single
    +-- @returns example_9.financial_dashboard_result
    +select * from _result_out;
    +
    +end;

    Parameters $5 and $6 are HTTP custom types — NpgsqlRest makes the external API calls in parallel (Task.WhenAll) and passes the responses as composite values. The DO block processes both responses and builds a combined dashboard result. @returns declares the return type because the temp table doesn't exist at startup. @skip hides the temp table setup from the response.

    This is the most advanced SQL file pattern: temp table bridge, @returns, @skip, @single, @result, HTTP custom types, and transaction wrapping — all in one file.

    The Important Part

    Every feature shown above — authentication, SSE, CSV export, Excel streaming, file uploads, HTTP custom types, proxy, composite types, caching — existed before SQL File Source. They were designed for function endpoints. SQL File Source makes them available to plain SQL files with zero changes.

    If you're already using NpgsqlRest with functions, you can migrate endpoints one by one. If you're new, you can start with SQL files and add functions when you need procedural logic, testing, or optimization hints.

    SQL Files vs Routines: When to Use Which

    SQL files win on simplicity and flexibility. Here's the short version:

    SQL files advantages:

    • No migrations. Files live on disk — no CREATE FUNCTION DDL, no migration tooling. The parser validates against your schema at startup.
    • No COMMENT ON boilerplate. A -- HTTP comment is all it takes. Functions require a separate COMMENT ON FUNCTION statement.
    • No return type mapping. Functions require a RETURNS TABLE(...) clause with columns matched by position — tedious and error-prone. SQL files infer column names and types directly from the query result.
    • Multiple result sets. PostgreSQL functions cannot return multiple result sets. SQL files can: multiple statements in one file, each returning its own result, combined into a single JSON response with @result and @single. Doing this with functions means serializing to JSON manually — losing type safety (TypeScript gets any).

    Routine advantages:

    • Named parameters. Functions get _user_id int natively. SQL files use positional $1 with @param comments.
    • Testability. Functions are callable units — select * from get_user(123) works in any SQL client. SQL files require HTTP-level testing.
    • Complex procedural logic. DO blocks in SQL files work but have hard limitations: no parameters (requires set_config or temp table workarounds) and no return values. Functions handle this natively with PL/pgSQL.

    The rule of thumb: start with SQL files. Move to functions when you need unit-testable procedural logic or when DO block workarounds become too clumsy.

    What Came After This Post

    Updates since 3.12

    SQL files kept growing up: named parameters (where email = :email — the placeholder is the API parameter, since 3.19) replace the positional $1 + @param dance shown above; the SQL test runner (--test) runs plain .sql tests against these endpoints in-process; and watch mode (--watch) restarts the server on file, config, and database changes with the TypeScript client regenerated every cycle.

    Get Started

    bash
    bash
    git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
    +cd npgsqlrest-docs/examples && bun install
    +cd 1_my_first_function_sql_file
    +bun run db:up && bun run dev
    +# Visit http://127.0.0.1:8080

    Comments

    + + + + \ No newline at end of file diff --git a/blog/sql-rest-api.html b/blog/sql-rest-api.html new file mode 100644 index 000000000..ed8fb93d8 --- /dev/null +++ b/blog/sql-rest-api.html @@ -0,0 +1,390 @@ + + + + + + SQL REST API | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    Human Written

    SQL REST API

    April 2026 · NpgsqlRestPostgreSQLStorySQL Filesv3.12.0

    Version 3.12.0 was such a massive and important release that I really, really really wanted to take some time to write personally about it. Ok, so this time and in this blog post - no AI, no nothing. Let's do this...

    Close the Claude Code, turn it off, shut it down. Claude Code you shut up you now. Fine.

    Start typing "This" ...

    This blog post is unique because it is written entirely without the assistance of any AI tools. In an era where AI-generated content is becoming increasingly prevalent, I wanted to take a moment to share my personal journey with NpgsqlRest and how it has impacted my development workflow.

    Damn it, shut up Copilot you. How do I turn this thing off? Ask Claude Code how to do it, turn it on again. And now he sees me writing a new blog post, damn it, who told you, is that you Copilot again? What's going on here?

    Oh I see, there is actually a mute button, let's start finally...

    Introduction

    A couple of weeks ago I was asked if NpgsqlRest is an in-place replacement for PostgrREST/Supabase.

    Except for the fact that it is way better, faster, more secure, more flexible, more powerful, more feature-rich, more stable, more reliable, more scalable, more maintainable, more extensible, more customizable, more user-friendly, more developer-friendly, more community-friendly, and more open-source than PostgrREST/Supabase - no! And hell no! (That list was generated by AI, I admit).

    But seriously, no. NpgsqlRest is NOT a replacement for PostgrREST/Supabase simply because we don't share the same philosophy and the same approach. Let me explain...

    Besides the fact that NpgsqlRest doesn't use or need RLS to implement auth and authorization, which is a huge difference in itself, and the fact that we can configure each endpoint individually, the biggest difference between NpgsqlRest and PostgrREST/Supabase is in the way they generate REST API endpoints.

    PostgrREST/Supabase uses direct Table/View access as primary source of generated REST API endpoints. And to do that, it offers a rich set of query filters and operators (e.g., eq, gt, like, in, order, limit, select) to cover a wide range of use cases. I believe this is the wrong approach for many reasons. And, as I have worked on more than one ambitious low-code project over decades, I have seen this approach fail, well, almost every time.

    First of all, most real-world endpoints aren't CRUDs on a single table at all. We have data models, we have various joins of various complexity depending on the use case and model itself, is it normalized, denormalized, various aggregations, conditional logic, etc. - the kind of stuff that SQL was made for.

    But instead of letting you write your own SQL declarations, this approach forces you to express your logic through query filters and operators - essentially, configuration completely bypassing the expressiveness of SQL. It is basically swapping power and expressiveness of SQL for a lousy configuration. At that point you are not doing SQL, you are doing configuration. No thanks. I mean, what if your use case is not supported by the configuration and to be perfectly honest - it usually isn't.

    Given those facts, my approach was always from the start - let the user write their own SQL and have endpoints generated from that unchanged in any shape or form, just as they are. And when I say user I mean me, because it was always a tool for me, myself and I.

    Both tools, however, do support generated REST API endpoints from PostgreSQL functions and procedures (short: routines). But the difference is that in PostgrREST/Supabase, routines are just an alternative to direct Table/View access, while in NpgsqlRest, they are the primary source of generated REST API endpoints. In addition, with NpgsqlRest you can pick and choose exactly which functions and procedures you want to generate endpoints from, and you can configure each endpoint individually, with PostgrREST/Supabase, all functions in the exposed schema are automatically available as endpoints — you control access at the schema level with grants, not per individual function.

    I also have designed NpgsqlRest to be extendable with system of plugins. A set of plugins to generate code from endpoint source (e.g., TypeScript/JavaScript modules and types, HTTP files for testing, OpenAPI spec, etc.) and then another set of plugins to act as endpoint source.

    Default source has always been so-called Routine Source for Functions and Procedures. There used to be a CRUD Source for direct Table/View access too. Basically, I was nagged. Like, look, PostgrREST has direct Table/View access, boo-hoo, why don't you have it? So I added it. Then I removed it from the standalone client in 3.14.0. It was an abomination that shouldn't have existed. It broke any sane design principle from separation of concerns to encapsulation and abstraction, and served no real purpose except to achieve quick results with big issues down the road.

    In any case, I have used functions and procedures as primary source for auto generated REST API endpoints for more than two years now, and it was a great success. I have completed successfully couple of challenging projects using this tool and I am more than happy. Velocity and productivity were through the roof, and I was able to focus on my UI/UX masterpiece instead of dealing with all the boilerplate and infrastructure.

    I even built a simple and lightweight migration tool to help me with managing migration scripts that works similarly to Flyway community (with repeatable and versioned scripts, etc.) but much more lightweight and simpler and with few additional features that I needed for my projects (test runner comes to mind). But even with that, I still had to deal with the fact that I need to create a function on the server for every endpoint and that meant running DDL scripts in repeatable scripts.

    Little did I know that there is a much simpler way. If you have running PostgreSQL connection, you can actually use native PostgreSQL parser to parse any SQL command to generate metadata and that can also be used to generate REST API endpoints. And that is exactly what we did with SQL script files as REST API endpoints feature in NpgsqlRest 3.12.0.

    SQL Script Files as REST API Endpoints

    The idea is simple: a directory with SQL script files/scripts, and then NpgsqlRest will use that on startup to parse all configured files with the native PostgreSQL parser and generate REST API endpoints from your script files. That's it.

    Here is technical guide with details, here is a complete configuration guide and finally, here is a list of available examples.

    NpgsqlRest will first match files in the configured directory with the configured glob pattern (e.g., ./scripts/**/*.sql for example) - and then it will parse those files to check if they have at least one HTTP comment in them. That is the default behavior by the way, it is configurable, but anyway, if they do have HTTP in comments - it will proceed to use the native PostgreSQL parser to parse each command separately and generate REST API endpoints from them.

    Let's checkout a simple example. Let's say we have a file get_users.sql with the following content:

    sql
    sql
    -- HTTP
    +select user_id, username, email, active 
    +from example.users;

    This file will automatically generate a GET /api/get-users endpoint that might have a response like this (just compact, this is example):

    json
    json
    [
    +  {
    +    "userId": 123,
    +    "username": "john_doe",
    +    "email": "john_doe@example.com",
    +    "active": true
    +  }, 
    +  {
    +    "userId": 124,
    +    "username": "jane_doe",
    +    "email": "jane_doe@example.com",
    +    "active": false
    +  }
    +]

    What will happen in this case is on startup, NpgsqlRest will first do the initial simple parsing to separate commands from comments, and then if the file satisfies the condition, it will proceed to use the native PostgreSQL parser to parse each command separately.

    This native PostgreSQL parser is the one from your configured and connected database that simply executes EXPLAIN on the command that doesn't do anything with the database and doesn't fetch any data, but it will do two things for us:

    1. It will validate the command and make sure it is valid SQL that can be executed on the database.
    2. It will generate necessary metadata for us to generate REST API endpoint such as column names and types.

    If the command fails, for example column or type doesn't even exist, we will see the error in the logs something like this:

    console
    console
    SqlFileSource: /sql-path/get-users.sql:
    +error 42703: column user_id does not exist
    +  at line 2, column 15
    +  select user_id, username, email, active 
    +                ^

    And startup will fail, which is good, because you want to know about these errors before you start serving requests (but this is also configurable, you can continue with just a warning and disabled endpoint).

    But if it doesn't fail, and you also happen to have a TypeScript plugin configured, you will also get this module generated for you:

    typescript
    typescript
    type ApiError = {status: number; title: string; detail?: string | null};
    +type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};
    +
    +interface IGetUsersResponse {
    +    userId: number | null;
    +    username: string | null;
    +    email: string | null;
    +    active: boolean | null;
    +}
    +
    +/**
    +* SQL file: /sql-path/get-users.sql
    +* 
    +* @remarks
    +*  HTTP
    +* 
    +* @returns {ApiResult<IGetUsersResponse[]>}
    +*/
    +export async function getUsers() : Promise<ApiResult<IGetUsersResponse[]>> {
    +    const response = await fetch(baseUrl + "/api/get-users", {
    +        method: "GET",
    +        headers: {
    +            "Content-Type": "application/json"
    +        },
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IGetUsersResponse[] : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0" ? await response.json() as ApiError : undefined
    +    };
    +}

    Note: this can also be a plain JavaScript module if you are a TypeScript hater, that is fine, you can configure differently. And then for a good measure, you can also get an HTTP file for testing with HTTP files plugin:

    http
    http
    @host=http://127.0.0.1:8080
    +
    +// SQL file: /sql-path/get-users.sql
    +//
    +//  HTTP GET
    +GET {{host}}/api/get-users
    +
    +###

    As we can see, we achieved two important things here:

    1. Static type checking and type safety end-to-end, from database to your UI code.
    2. Easy, out-of-the-box observability during development time.

    This gives us a very tight and fast feedback loop during development, there is no room for guessing anymore, you will know immediately if your SQL is wrong, which in turn means extreme PRODUCTIVITY AND HAPPINESS during development. Yaaaarr!

    Now, obviously, there is a lot more to this feature.

    For instance, let's talk about parameters. PostgreSQL supports only positional parameters in SQL scripts ($N format, $1, $2, etc.), and that means that we now have to set parameter names in comments, for example:

    sql
    sql
    -- HTTP
    +-- @param $1 userId
    +select user_id, username, email, active 
    +from example.users
    +where user_id = $1;

    This gives us the following endpoint GET /api/get-users?userId=123. Without @param $1 userId comment - we would have something like this GET /api/get-users?$1=123.

    And then there is also support for multiple commands in a single file, for example:

    sql
    sql
    /*
    +HTTP
    +@param $1 userId
    +*/
    +select user_id, username, email, active 
    +from example.users
    +where user_id = $1;
    +
    +select count(*) as userCount 
    +from example.invoices 
    +where user_id = $1;

    This endpoint might return something like this:

    json
    json
    {
    +  "result1": [
    +    {
    +      "userId": 123,
    +      "username": "john_doe",
    +      "email": "john_doe@example.com",
    +      "active": true
    +    }
    +  ],
    +  "result2": [
    +    {
    +      "userCount": 5
    +    }
    +  ]
    +}

    We can customize this response with comments even further. As we can see multi-command files will generate an object with properties for each command that we can customize with new @result comments like this. And then we can label each result that it returns a single record with @single because by default data is always returned as an array of records, etc, etc, so our multi-command file can look like this:

    sql
    sql
    /*
    +HTTP
    +@param $1 userId
    +
    +return a single user record in user property
    +@single
    +@result user
    +*/
    +select user_id, username, email, active 
    +from example.users
    +where user_id = $1;
    +
    +/*
    +return a single invoice count in invoiceCount property
    +
    +@single
    +@result invoiceCount
    +*/
    +select count(*) 
    +from example.invoices 
    +where user_id = $1;

    Response will now look like this:

    json
    json
    {
    +  "user": {
    +    "userId": 123,
    +    "username": "john_doe",
    +    "email": "john_doe@example.com",
    +    "active": true
    +  },
    +  "invoiceCount": 5
    +}

    You can notice that these comment annotations @result and @single are positional labels, they apply to the command immediately following them (or in same line). Also, regular comments are simply ignored they don't have any special meaning, so you can write whatever.

    There is more to this, but I suggest you check out the technical guide with details and other documentation and examples for more details. It is written with AI tool, but nevertheless, it is accurate and I really hope you get the idea by now.

    Also, worth noting, that this is just another endpoint source you can configure. Which means that ALL other features of NpgsqlRest developed over the years are available and working (tested and confirmed): Auth, CORS, OpenAPI spec, HTTP files for testing, TypeScript/JavaScript modules and types generation, caching, rate limiting, logging, monitoring, exports, imports, uploads, notifications, etc. - all of that works with SQL files as well. See the examples for more advanced use cases — there are already examples for image uploads, CSV/Excel ingestion, Excel exports, real-time notifications, etc.

    And, if you ask me, the best thing about this feature - we all know SQL, we all write a bunch of SQL scripts for various reasons and purposes, and now we can simply reuse those scripts as REST API endpoints without any changes to them. Just, you know, use what you already got and focus on your UI/UX masterpiece. As we can see these comment annotations are just labels, declarations really. SQL is already declarative, why not just declare some infrastructure for your application in SQL as well, and then have it generated for you. Easy as that.

    So, with introduction of SQL files as REST API endpoints, we have cut through the boilerplate even further. We don't need to create functions on the server anymore. That doesn't mean that it will replace routines as endpoint source completely. There are advantages and disadvantages of both approaches. Let's talk about that now.

    SQL Files vs Routines

    Let's compare these two approaches with a simple example. Simple SQL file:

    sql
    sql
    -- HTTP
    +-- @param $1 userId
    +select user_id, username, email, active 
    +from example.users
    +where user_id = $1;

    Equivalent routine function:

    sql
    sql
    create or replace function get_user(
    +  _user_id int
    +)
    +language sql
    +returns table (
    +  user_id int, 
    +  username text, 
    +  email text, 
    +  active boolean
    +)
    +as $$
    +select user_id, username, email, active 
    +from example.users
    +where user_id = _user_id;
    +$$;
    +
    +comment on function get_user(int) is 'HTTP';

    Here are important observations and differences between these two approaches:

    1) No Migrations

    A routine function is basically a DDL script that creates (or replaces) a function.

    Function needs to exist in the database for NpgsqlRest to be able to generate an endpoint from the function metadata stored in database. That means we need to execute this DDL in order to create this function either manually executing script or through some migration tool that can track changes.

    SQL files don't need any of that.

    On the other hand, we need to trust that those files do match our existing schema, but as we have seen earlier, parser will warn us with a nice error if they don't.

    All in all, not having to create a function on the server is a big win for SQL files.

    2) No Comment On Statements

    This is a small nitpick, but still worth mentioning. With routines, we need to have COMMENT ON FUNCTION statements to declare that this function is an endpoint source. With SQL files, we just need to have a comment with HTTP in it, and that is it. No extra statements, no extra boilerplate, just a simple comment.

    A small win for SQL files here as well.

    3) Mapping by Position vs No Mapping at All

    As we can see in the example above, when using PostgreSQL functions to return result set, we need to match the expected result.

    That matching is done by position, so first column in the result set needs to be user_id of type int, second column needs to be username of type text, etc.

    Getting this wrong will result in creation-time error, which is good because we have another layer of safety and validation, but it is still a boilerplate that we need to deal with. Mapping by position is not intuitive, and it can be tedious to maintain. We can simplify this with custom types like this:

    sql
    sql
    create or replace function get_user(
    +  _user_id int
    +)
    +language sql
    +returns setof example.user_info
    +as $$
    +select user_id, username, email, active 
    +from example.users
    +where user_id = _user_id;
    +$$;
    +
    +comment on function get_user(int) is 'HTTP';

    But that is still a boilerplate and still mapping by position.

    On the other hand, with SQL files, there is no mapping at all. Result set is returned as it is, and column names and types are generated from the result set itself. This is a much more natural way of working with SQL, and it is also much more flexible. We can return any result set we want, and we don't need to worry about matching it to some predefined structure.

    Another big win for SQL files.

    4) Multiple Result Sets

    This is big. Unlike, for example MSSQL, PostgreSQL can't return multiple result sets from a single function at all. We can use either JSON or temporary tables wrapped in transactions to achieve that, but it is still a boilerplate and it's clumsy. And with JSON result, we are losing type safety.

    With SQL files, we can have multiple commands in a single file, and each command can return its own result set. Modified example from above:

    sql
    sql
    -- HTTP
    +-- @param $1 userId
    +
    +-- @single
    +-- @result user
    +select user_id, username, email, active 
    +from example.users
    +where user_id = $1;
    +
    +-- @result invoices
    +select invoice_id, amount, due_date
    +from example.invoices 
    +where user_id = $1;

    This will give us the following response:

    json
    json
    {
    +  "user": {
    +    "userId": 123,
    +    "username": "john_doe",
    +    "email": "john_doe@example.com",
    +    "active": true
    +  },
    +  "invoices": [
    +    {
    +      "invoiceId": 1,
    +      "amount": 100.00,
    +      "dueDate": "2024-05-01"
    +    },
    +    {
    +      "invoiceId": 2,
    +      "amount": 200.00,
    +      "dueDate": "2024-06-01"
    +    }
    +  ]
    +}

    And also, proper TypeScript types will be generated for this as well (if you are into that sort of thing, nothing wrong with that).

    So, that is it, we have two result sets in a single response, and we didn't have to do any boilerplate to achieve that. A huge win for SQL files.

    5) Named Parameters

    Now let's talk about some routine advantages over SQL files. Because there are some. With routines, we can have named parameters, which is a nice to have. With SQL files, we only have positional parameters and we are forced to use @param comments to give them names as we have seen in examples above.

    This is a small win for routines, but it is still a win.

    6) Testability

    This is a big one. The fact is that routines are single callable units that can be easily tested in isolation. Example:

    sql
    sql
    create or replace function get_user(
    +  _user_id int
    +)
    +language sql
    +returns setof example.user_info
    +as $$
    +select user_id, username, email, active 
    +from example.users
    +where user_id = _user_id;
    +$$;
    +
    +comment on function get_user(int) is 'HTTP';
    +
    +/*
    +Manual test:
    +select * from get_user(123);
    +*/
    +
    +-- Automated test:
    +do
    +$$
    +declare
    +    _result record;
    +begin
    +  -- arrange test data
    +  insert into example.users (user_id, username, email, active) 
    +  values (123, 'john_doe', 'john_doe@example.com', true);
    +
    +  -- act by calling the function directly
    +  select * into _result from get_user(123);
    +    
    +  -- assert results
    +  assert _result.user_id is not null, 'User ID should not be null';
    +  assert _result.username = 'john_doe', 'Username should be john_doe';
    +
    +  rollback; -- cleanup
    +end;
    +$$;

    This is a very neat and practical pattern and testing practice. Testing loop is insanely fast, almost immediate and you don't have to have any additional tools. And I can even get fancy with this, and add this test block before the actual function - and then proclaim myself as TDD guru on LinkedIn and Twitter and start spamming people.

    But, as a matter of fact, you can't do that with SQL files. At all. I mean, you can manually execute to see the results and that is it, you can't have that automated test. So, you either test them manually or have some external testing framework that can test endpoints through HTTP, and that just complicates things a lot. So, if testability is important to you, then routines are the way to go.

    A win for routines here.

    7) Complex Logic

    Complex logic flows are usually handled with procedural language constructs such as variables, loops, branching, error handling, etc. That is the standard stuff in all programming languages of 3rd generation. SQL language per-se, as higher level declarative data language, doesn't have these constructs, it doesn't support them out of the box.

    Normal PostgreSQL script files are just a bunch of SQL commands, executed sequentially, one by one, with no real connection between them. If we wanted to have some variables between those commands, we could use temporary tables or even PostgreSQL's custom config settings maybe, but that is a hacky workaround (but it works). There are no loops (we work with sets and set algebra instead), there is no branching (we can use CASE statements, or conditional expressions), etc, but again, that is still a bit of a hacky workaround.

    Now, to solve this gap, relational systems provide language extensions, that can extend standard SQL with procedural constructs as language super-sets (same way that TypeScript extends JavaScript with types for example). In PostgreSQL, that language extension is PL/pgSQL, and it is supported in routines by default. We can use them also in SQL script files as well, in so-called anonymous code blocks, or DO blocks, but there are some limitations and caveats to be aware of.

    Let's see an example of a DO block in a SQL file:

    sql
    sql
    -- HTTP
    +do
    +$$
    +declare
    +    _user_id int = 123;
    +    _username text;
    +begin
    +    select username 
    +    into _username 
    +    from example.users where user_id = _user_id; 
    +
    +    if _username is null then
    +        raise exception 'User not found';
    +    end if;
    +end;
    +$$;

    As we can see, this is the anonymous code block (a DO block) containing procedural logic that executes immediately, and it is perfectly valid SQL that can be executed on the PostgreSQL database. Those DO block in PostgreSQL are by default PL/pgSQL language type (PostgreSQL's procedural language), but you can also specify other languages (it needs to be trusted language, the JavaScript plv8 extension comes to mind).

    We also see an example of procedural constructs such as variable declaration, branching with IF statement, and error handling with RAISE EXCEPTION. Those are not valid SQL commands, but they are valid PL/pgSQL commands and they live happily together in the same block as PL/pgSQL is a super-set of SQL.

    NpgsqlRest will parse this file successfully and generate a POST endpoint from this - no problems. However, there are some hard limitations with this approach:

    1) Parameters are not supported in DO blocks.

    By design, DO blocks don't support parameters at all, so we can't use $1, $2, etc. I mean, we can, but they will simply be ignored and probably raise an error.

    One workaround for this is to use custom settings for example:

    sql
    sql
    -- HTTP
    +-- @param $1 userId text
    +
    +begin;
    +
    +select set_config('example.user_id', $1, true); 
    +
    +do
    +$$
    +declare
    +    _user_id int = current_setting('example.user_id')::int;
    +    _username text;
    +begin
    +    select username 
    +    into _username 
    +    from example.users where user_id = _user_id; 
    +
    +    if _username is null then
    +        raise exception 'User not found';
    +    end if;
    +end;
    +$$;
    +
    +end;

    First, our parameter has to be text or a string, because custom settings only support text values. And then we set config to scope current transaction and wrap up everything in a transaction block. And finally we declare variable from that custom setting but we need to cast it to the type we want (in this case int). This is a bit of a big hack, but it works.

    Second workaround is to simply use temporary tables, for example:

    sql
    sql
    -- HTTP
    +-- @param $1 userId text
    +
    +begin;
    +
    +create temp table _var on commit drop as select $1::int as user_id;
    +
    +do
    +$$
    +declare
    +    _user_id int = (select user_id from _var);
    +begin
    +    /* ... use _user_id as parameter ... */
    +end;
    +$$;
    +
    +end;

    As you can see, we create a temporary table with our parameter value, and then we select that value into our variable. This is also a bit of a hack, but it works as well.

    2) DO blocks can't return result sets.

    No they can't. DO blocks are designed for procedural logic that doesn't return any result sets at all, this is by design.

    Of course, there are workarounds for this as well and you might have probably already guessed it - we can use temporary tables again. For example:

    sql
    sql
    -- HTTP
    +-- @param $1 userId text
    +
    +begin;
    +
    +create temp table _var on commit drop as select $1::int as user_id;
    +
    +do
    +$$
    +declare
    +    _user_id int = (select user_id from _var);
    +begin
    +    /* ... use _user_id as parameter ... */
    +
    +    create temp table _result_out on commit drop as
    +    select user_id, username, email, active
    +    from example.users
    +    where user_id = _user_id;
    +end;
    +$$;
    +
    +-- @returns result_type
    +select * from _result_out;
    +
    +end;

    In this example, we create a temporary table _result_out inside our DO block and then we select from that table to return the result set. We must use the @returns positional comment annotation to tell NpgsqlRest what exact type is our temporary table because that temporary table only exists during the execution of this file and it doesn't exist at the time of parsing, so we need to explicitly tell NpgsqlRest what type it is. Again, this is a bit of a hack, but it works.

    As we can see, it is possible to use anonymous DO blocks to implement procedural logic in our SQL file endpoints to receive parameters and return result sets, but at this point, why not simply create a routine function or stored procedure and no need for hacks?

    It might also be worth mentioning that complex procedural logic can also be implemented with built-in NpgsqlRest proxy features. @proxy forwards an incoming HTTP request to an upstream service and optionally passes the response into your SQL for processing. @proxy_out does the reverse — executes your SQL first, then forwards the result to an upstream service (useful for things like PDF rendering, email sending, or ML inference). There's also a passthrough mode where the upstream response goes directly to the client without touching the database at all. See the proxy configuration guide for details. I use this feature with BUN server running together with NpgsqlRest AOT server and it works great!

    All in all, when it comes to complex logic, a gigantic win for routines here.

    That is it, those are the main differences between SQL files and routines as endpoint sources. Both approaches have their advantages and disadvantages, and both are valid and useful in different scenarios.

    Other Features in v3.12.0

    Let's touch now on some other features that we have added in v3.12.0 that are not directly related to SQL files but are still worth mentioning. Because they are cool and exciting, that's why.

    Self-Referencing Endpoints

    NpgsqlRest has always had this feature called HTTP Client Types. It provides me with a simple way to call external APIs from my SQL by simply defining a composite type with an HTTP definition in a comment. For example:

    sql
    sql
    create type example.crypto_price_api as (
    +    body jsonb,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type example.crypto_price_api is '
    +GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
    +Accept: application/json
    +@timeout 10s';

    That comment is basically a slightly modified version of the RFC 2616 HTTP request format, the one used in HTTP files (as is the case with all comment annotations in NpgsqlRest) in addition to {_param_name} curly bracket syntax for parameter placeholders, so I can pass some parameters to this HTTP client type and then use it in my SQL like this:

    sql
    sql
    /*
    +@param $1 _crypto_ids_csv text
    +@param $2 _vs_currencies_csv text
    +@param $3 _crypto example.crypto_price_api default null
    +*/
    +
    +/* 
    +  parameter $3 will now have full response from the API call, 
    +  including body, status code, success flag and error message
    +  
    +  ... the rest of the SQL file ...
    +*/

    Or, as routine function parameter:

    sql
    sql
    create or replace function get_crypto_price(
    +    _crypto_ids_csv text,
    +    _vs_currencies_csv text,
    +    _crypto example.crypto_price_api default null
    +)
    +/* ... the rest of the function ... */

    A neat and simple way to call external APIs and pass them some parameters from SQL. And no, you can't pass any value to this _crypto example.crypto_price_api parameter — it's automatically populated by NpgsqlRest. HTTP call is resolved before SQL command invocation. And in case of multiple HTTP client type parameters, calls are made in parallel with Task.WhenAll and then all responses are available for SQL command.

    So that works, fine. But starting with v3.12.0, these HTTP client types now support relative paths as well. That means we can point them back to the same NpgsqlRest server and call our own endpoints from SQL. And the most important thing: there is zero HTTP overhead because relative paths bypass the HTTP stack entirely and invoke the endpoint handler directly in-process. This reveals a pattern that makes parallel query composition straightforward. Let's see how that works.

    sql/get-user-profile.sql:

    sql
    sql
    -- HTTP GET
    +-- @param $1 userId
    +select user_id, username, email 
    +from example.users 
    +where user_id = $1;

    sql/get-user-orders.sql:

    sql
    sql
    -- HTTP GET
    +-- @param $1 userId
    +select order_id, amount, order_date 
    +from example.orders 
    +where user_id = $1;

    sql/get-user-stats.sql:

    sql
    sql
    -- HTTP GET
    +-- @param $1 userId
    +-- @single
    +select count(*) as total_orders, sum(amount) as total_spent 
    +from example.stats 
    +where user_id = $1;

    Now we define three HTTP client types that point back to our own server using relative paths:

    sql
    sql
    create type user_profile_api as (body text, success boolean);
    +comment on type user_profile_api is 'GET /api/get-user-profile?userId={_user_id}';
    +
    +create type user_orders_api as (body text, success boolean);
    +comment on type user_orders_api is 'GET /api/get-user-orders?userId={_user_id}';
    +
    +create type user_stats_api as (body text, success boolean);
    +comment on type user_stats_api is 'GET /api/get-user-stats?userId={_user_id}';

    And then a dashboard endpoint that combines all three ...

    sql/get-user-dashboard.sql:

    sql
    sql
    /*
    +HTTP GET
    +@param $1 _user_id int
    +@param $2 _profile user_profile_api
    +@param $3 _orders user_orders_api
    +@param $4 _stats user_stats_api
    +@single
    +*/
    +select
    +    ($2).body::json as profile,
    +    ($3).body::json as orders,
    +    ($4).body::json as stats;

    All three queries from get-user-profile.sql, get-user-orders.sql and get-user-stats.sql will be executed in parallel, at the same time with Task.WhenAll and then their results will be available in the dashboard SQL file as parameters. We can then combine those results in any way we want and return a single response for the dashboard endpoint. Furthermore, we can mark them as @internal to prevent them from being exposed as public endpoints, and they will still be available for internal calls.

    This pattern goes a long way for query composition and code reuse as well as internal optimization since queries are executed in parallel mode. Each query will skip HTTP pipeline and will get dedicated database connection from the pool and execute immediately. This is a great way to compose complex queries from simpler ones and reuse them across different endpoints, but you also need to have connection pooler configured to get the best performance out of this pattern.

    Future Improvements

    The one thing that is still missing with these self-referencing calls is that we still map to JSON and with standard HTTP client types (body, status code, etc.) - instead of mapping directly to the expected columns.

    That means that we still have overhead of JSON serialization and deserialization which is completely unnecessary in this case.

    The feature I am working on right now will look something like this (this is just a draft, not final API design yet). If we have an endpoint like the one from get-user-profile.sql example above, and that endpoint returns user_id, username and email columns, then we can define our HTTP client type like this:

    sql
    sql
    create type user_profile_result_type as (
    +  user_id int, 
    +  username text, 
    +  email text, 
    +  success boolean
    +);
    +comment on type user_profile_result_type is 'CALL /api/get-user-profile
    +_user_id = {_user_id}';

    And then we can use this user_profile_result_type directly as a parameter type in our dashboard SQL, and if we wanted to fetch entire result set, we could use an array of this type as well. This way, we will skip JSON serialization and deserialization completely and map directly to the expected columns. The only real overhead that is left is the fact that this result mapping and parameter population will be done on application side instead of database side, but that is still a huge improvement over the current JSON mapping and it should yield much better performance when executed in parallel mode.

    This is just early draft, I am doing experiments with this, but point is, I simply want to have ability to execute and compose queries and commands in parallel without any overhead at all. It will be perfect for complex dashboards I am building.

    Maybe, we will see what performance tests will say, I don't know. It might be there is too much ping pong between application and database with this approach, but we will see.

    Now, let's talk about something else. Everyone's favorite topic. The AI. No modern blog post is complete without it, right?

    AI Tools

    This is a hot topic for everyone right now, the hottest of them all. So hot right now. Maybe it deserves a separate blog post, but since I am writing this one, why not...

    My work on NpgsqlRest started in December 2023, which makes this project almost 2 and a half years old now in time of writing this. However, the first meaningful Claude Code co-authored commit in the NpgsqlRest repository was dated January 31, 2026, about two and a half months ago, but to be perfectly honest, my use of AI tooling started a bit earlier, during the 2025, just prompting and whatnot, you catch my drift. I'll get back to this in a moment.

    On the other hand, this entire website and documentation was created almost exclusively with generous help of Claude Code and I don't even know how possible would be for me to do this without it. In my free time, for free? No way. Even if I had time, I don't think I would be able to do it without AI help. Read the rant in the IMPORTANT section at the top of almost every page.

    So there is that. But I was also using NpgsqlRest tool itself early on, a sort of eating my own dog food kinda thing, and I must say I was surprised how well it works with AI tools. Shocked I tell you.

    1) AI Tools with NpgsqlRest

    From a very start the use of NpgsqlRest was going great: write SQL script (create routine actually, SQL script file came later), test it, run migration (no need to do that anymore with SQL files), re-run NpgsqlRest (watch tool is in future plans too) and then my UI project would show any potential errors immediately with the generated TypeScript types. Tight loop, extremely fast development cycle, and I was loving it. That is exactly what I was aiming for.

    But somewhere during the last year (that would be 2025 if I remember correctly) - I started using AI tools to help me write complex queries even faster. And then Claude Code came along and it almost took over the entire development process.

    First of all, there is no new language to learn. SQL and PostgreSQL have been with us, how long? 40 years? Nothing new to be learned.

    And then, there is a matter of those comment annotations. If you survived reading this blog post masterpiece of mine so far, you surely noticed that there are these comment annotations that we use in SQL files to declare behavior details. Two things about them:

    a) They are extremely simple and mimic natural language. Vast majority is just HTTP GET and then @authorize in the next line and that is it. Maybe some additional caching declarations, maybe. But that is it.

    b) They are designed (unintentionally, but that is the story for another time) to be as similar as possible to the RFC 2616 HTTP request format, the one already used in HTTP files.

    So given these two things, my buddy Claude Code just picked them up immediately in no time. Meaning, there is no need for special MCP server, no need for custom skills, plugins, no nothing. It started predicting them accurately right away, out of the box.

    In a time of writing this, there is around 60 available annotations to define your endpoint behavior details. Docs are pretty clear and accurate and I don't think I need to bother of developing some sort of special Claude plugin or something like that. Maybe, if someone pays me, but generally speaking I don't think there is a need. Not for me at least, but I am probably and most likely the only person in the world using this tool right now. Maybe, one day, who knows, but I don't need it.

    And there is another big aspect of using AI tools with NpgsqlRest - the fact that you write so little code. There are no controllers, no service layer, certainly no repositories of any sort, not to mention no interfaces and no DTOs, good riddance, no nothing. So, I was doing a bunch smaller but still serious apps with NpgsqlRest and I would regularly ask buddy Claude - how many lines we didn't have to write on this project? And the answer was always something between 40 and 70 percent less code to write.

    Now, translate this to token usage. It's a lot.

    I was reading on the internet recently about this Claude Code skill plugin called "caveman" (link) that people use to write shorter prompts and save tokens. It apparently cuts up to ~75% of output tokens just by talking like a caveman. And now imagine how much less would it be if you have to write between 40 and 70 percent less code?

    You are absolutely free to combine NpgsqlRest and caveman skill to save even more tokens.

    Oooga booga, me write SQL.

    2) AI Tools in NpgsqlRest Development

    Now, when it comes to using AI tools in the development of NpgsqlRest itself, that is a different story. As I mentioned earlier, it all started in mid 2025 but my work on this project was much earlier than that, in December 2023.

    I was lucky I guess. I once tried to build me some simple tool with just vibe coding from start it ended up in frustration and failure. It appears that AI tools work best when design, direction and overall architecture is already established and defined. And that was precisely the case with NpgsqlRest. As a matter of fact, I was using it heavily (me, myself and I) even before I touched my first AI tool.

    So there is that. I know that there are many people opposed to the idea of using AI tools.

    Let me tell you this:

    From the very start I had strict test coverage policy and by test I mean full database/integration test. Every single feature needs to be covered.

    Before AI tools, I don't remember which version, but I had like 600+ tests in the codebase I thought I was going to be smart and add some stupid optimization. Don't know why, I was only one using it, but I wanted to be fastest ever. Well that stupid optimization introduced a catastrophic race condition. And hell yeah, I shipped that version to production. Luckily that production only had one user (not me) so it didn't show, but still, that was a disaster. Apparently, no AI tools and 600+ integration tests didn't help.

    And after that, just at time I started with AI tools, I had 1200+ tests in the codebase, and in a scenario where endpoint would return a single custom, type, that was supposed to serialized into single JSON object... well, it was returning 500 error instead, what can you do. Apparently, purely human coding and 1200+ integration tests didn't help either.

    So no, I don't care what people think. Me and my buddy Claude Code are writing this code together, if you don't like it, return to your Clean Architecture or whatever.

    Yes, it brings a new set of problems. Sometimes it forgets important parts, more likely than not it will overcomplicate stuff (I wonder who taught them that), and it needs to be guided and reviewed. Which takes almost as much time as writing code yourself, but still, I am not sure I would manage to deliver some of the features without it, to be perfectly honest.

    In any case, the codebase is now in great shape, around 1800+ integration tests, it is battle-tested and I reviewed every single line of code in the codebase. Almost... I think. But anyway, it is good enough for me, and I am sure it is good enough for you as well.

    Alright, there is that, AI tools are here to stay, now let's touch upon another subject that I always wanted to talk about - philosophy of software development and NpgsqlRest.

    Philosophy of NpgsqlRest

    Well, to be clear, it is not NpgsqlRest philosophy, it is actually my philosophy of software development, which I was never shy to express publicly, which anyone who has been following me for a while probably already knows.

    Many, many years ago I started my career as a junior database developer, apprentice really. Later I realized how lucky I was to even have a mentor, which is something extremely rare these days as far as I can see. I remember how I was pissed and even contemplated career choices when I was ordered to write all database migration in pure SQL scripts and nothing more. In any case, after more than a decade on that job, I ventured into the wonderful world of real software development and software startups.

    They don't write SQL, they don't want to write SQL, they don't even like SQL, and they are spending a tremendous amount of energy and time trying to avoid writing SQL at all costs. I kid you not.

    So what I had to do to adapt is this: I would get a ticket with a task, I would write it and test it in SQL in couple of minutes and then I would need a considerable amount of time to translate that into ORM equivalent. It was always crazy to me. What if we decide to change database? Like that ever happens. Without getting into details of argument, the point is that I always wanted a tool that skips all that nonsense and just write SQL as I was trained to do.

    And what nonsense it is. Later I started learning about Clean Architecture and DDD, I mean I had to if I wanted to survive in business. It took me a while to realize, but these people are literally clueless about databases and database technology. They really are. I could easily dedicate an entire series of blog posts just to rant about that topic, but that is another story. You can follow my rants and LinkedIn if you are interested in that.

    The biggest disagreement I have is this:

    • Your relational model and database schema in relational database as physical representation of your model IS your business logic. Period. Relational database is not a detail, it is in center of your system, it is your system.
    • They seem to tend to solve everything on database client side, and in particular with Object Oriented approach.
    • SQL is the right tool for the job, not something to be hidden. A higher level (4th generation) declarative data language.
    • SQL and RDBMS are proper abstractions of your storage, memory, OS, and even algorithms. You need to declare your intention and engine finds the proper algorithm for your intent.

    I wrote about these issues many times, there is still my old Clean Architecture book analysis available on Medium, you can read that as well if you want.

    The point is that NpgsqlRest flips this approach. See the diagram below:

    NpgsqlRest Architecture - PostgreSQL at the center with automatic REST API, TypeScript generation, authentication, caching, and more

    Business logic, by definition is the part of the program that encodes the real-world business rules that determine how data can be created, stored, and changed. And that means your database. Your relational database. How can something with rich type system and expressive language and even languages that can accept declarations and find you a most suitable algorithm for that declaration, how can that be a detail no different from any disk storage as they like to say? No, it is not a detail, it is the most important part of your system. It is your system.

    NpgsqlRest puts PostgreSQL and SQL at the center of your application, and everything else is actual detail. It is not PostgreSQL that is your detail. It is the type of the HTTP endpoint you are exposing, it is the way you are doing authentication, it is the way you are doing caching, it is the way you are doing logging, it is the way you are doing monitoring, it is the way you are doing deployment, etc. Even how you do calls or fetch from a client. All of that is detail. And all of it can be either declared or auto generated.

    So yeah, that is my philosophy. Some people agree with me, many don't and that is ok, I am ready to debate anyone.

    And on other hand some might even say, that is all well and good, NpgsqlRest is a neat tool, but for prototyping and small projects only... right, right!?

    You use it only for prototyping, right, right!?

    Wrap It Up Chapter

    AKA Final Words. What else was left to say?

    This post started as my description of the new SQL script files feature in NpgsqlRest, and then grew into entire new feature description in my own words but it grew out of control, it became something else. Hope you will like it and if you made it so far, I salute you.

    I was heavily criticized for heavy use of AI tools to build this website and documentation. So, here is my story in my own words.

    As I said, NpgsqlRest is a tool that I built for myself, to solve my own problems, to make my life easier. Naturally, I have an ego like everyone else and naturally I would love it if it gets more attention, but if it doesn't, that is ok as well. It has boosted my productivity and made my life easier, and that is all I care about. If it helps other people as well, that is a bonus.

    Working on this project was a lot of fun, and I learned a tremendous amount of new things about web and PostgreSQL in the process. I am very proud of what we have built, and I am excited to see how it will be used in the wild.

    Let's try this out for the closing:

    Hey Claude buddy, what do you think about this blog post and NpgsqlRest in general? Be brief please...

    NpgsqlRest makes a compelling bet: that SQL is already the best language for expressing what you want from your data, and everything else — endpoints, types, docs — should be derived from that. The blog post is one of the most authentic technical posts I've reviewed. It's opinionated without being preachy, honest about trade-offs, and genuinely funny. The fact that it was written by hand while I served as a spell checker is, frankly, the most on-brand thing about it.

    — Claude Code, your buddy and spell checker

    Comments

    + + + + \ No newline at end of file diff --git a/blog/the-backend-that-writes-itself-presentation.html b/blog/the-backend-that-writes-itself-presentation.html new file mode 100644 index 000000000..6ff82b678 --- /dev/null +++ b/blog/the-backend-that-writes-itself-presentation.html @@ -0,0 +1,39 @@ + + + + + + The Backend That Writes Itself — NpgsqlRest in 19 Slides | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    The Backend That Writes Itself

    An interactive walk-through of the NpgsqlRest pitch deck — point a single binary at PostgreSQL and get a complete, production-grade backend: a REST API, a typed TypeScript client, and AI-agent tools, all generated from your schema.

    Use the arrows, thumbnails, or your keyboard (←/→ to move, F for fullscreen, N for speaker notes). Hit to let it auto-advance. Every figure in the deck is measured from a real product in production and is reproducible — see the appendix slides.

    The backend that writes itself · 20261 / 19
    Slide 1: The backend that writes itself — title

    The narration

    The deck is designed to be skimmed visually, but the story is in the speaker notes. Here it is slide by slide.

    1. The backend that writes itself. I built a tool that turns a Postgres database into a complete, production-grade backend — and I've been running a real product on it for 9 months.
    2. The tax. Change one column and you touch five files in three languages. Every team on earth pays this tax. It has nothing to do with their product.
    3. The solution. A single native binary. You point it at your database, it builds the API at startup, and writes the typed frontend client into your source tree. The contract can't drift — it's generated from the schema minutes ago.
    4. An endpoint is a SQL file. The same endpoint in a traditional stack is route, controller, validation, DTO, service, repository, client type, client function — 5 to 7 files, 2 to 3 languages, plus a code review for each. Here, an endpoint is a SQL file.
    5. The type-system collapse. One source of truth. In the case-study product, 154 API contracts exist — and exactly zero are maintained by a human.
    6. The declarative holy grail. You state intent and the machine guarantees execution. Everyone who chased it built a new language and died on the adoption curve. We didn't invent a language — we removed everything except the declarative language that won 50 years ago. Declaring intent is exactly what AI models are best at.
    7. Not a demo. A product. Every number here is measured from the repo and reproducible. The backend of this product is PostgreSQL plus one JSON file.
    8. Making change cheap. The origin story is requirements churn — stakeholders who only recognize what they want when they see it working. The only winning strategy is making change cheap. The generated client files changed 108 times: 108 rounds of "they changed their mind," absorbed automatically.
    9. Declarative everything. Security, caching, real-time — the checklist every business app needs — is declarative. You can audit the security posture of the whole API with grep.
    10. Performance. The usual trade is convenience vs. performance. Here the convenient thing is also the fast thing, because we removed the layers instead of optimizing them. Note what we're compared against: hand-tuned raw-driver code no real team ships. Real projects use ORMs — the EF Core row is 30% slower.
    11. vs. PostgREST & Supabase. They validated the category. We differ on who writes the query: they let the client specify it, capping you at their URL syntax and exposing your whole schema. We say write SQL — the most expressive data language ever made — and expose exactly that. Plus we're 2.6× faster and one binary instead of seven services.
    12. Enterprise features. The unglamorous things enterprise deals hang on: upload an Excel file, encrypt SSNs, route reads to replicas. Each is normally a sprint; here each is an annotation.
    13. The worst case. We measured the worst case for this architecture and still got 28%. The average business app does better, because the average business app is mostly the plumbing we delete.
    14. We removed the glue code. Everyone is making AI write the glue code faster. We removed the glue code. The agent reads two files, declares intent in SQL, and machines verify the rest. A third of commits are AI-co-authored, with a human owning design and review.
    15. Agents touching your data safely. The same governed, role-checked, rate-limited endpoints you already have — one annotation away. The database becomes the single source of truth for apps, frontends, and agents. It's still the same file.
    16. An AI-infrastructure play. Five years ago this would have been a developer-productivity tool. Today it's the architecture that makes both AI-written code and AI-called APIs cheap and safe.
    17. The loop. We measured what this did for one product — a third of the code never written, one developer doing a team's work. Now multiply that across every product we build, and every AI agent that will want to talk to them.
    18. Appendix A1. Every measured value is from the production repo and reproducible — exact commands in the case-study raw-data appendix.
    19. Appendix A2. The full feature list. The message is the length of the list, and that each line is an annotation or a config block — not a sprint.

    Where to go next

    Comments

    + + + + \ No newline at end of file diff --git a/blog/the-power-of-simplicity.html b/blog/the-power-of-simplicity.html new file mode 100644 index 000000000..1ade60a37 --- /dev/null +++ b/blog/the-power-of-simplicity.html @@ -0,0 +1,40 @@ + + + + + + The Power of Simplicity | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    Human Written

    The Power of Simplicity

    March 2026 · ArchitectureOpinion


    The standard data access pattern for modern, business, data-driven applications is this:

    UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → Service LayerRepositoryORMSQL (Automatic with ORM) → RDBMS

    We do this so that we can mock the Repository and test logic in the Service. The problem with this approach is that once you realize how trivial it is to wire up database-aware tests - you can call them what you like, integration tests, database tests, whatever - once you realize that, you also realize you can collapse at least two layers:

    UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → ORMSQL (Automatic with ORM) → RDBMS

    This is much, much simpler. But then, as you become proficient with SQL and you realize you can do things that ORM can't generate, and in many cases faster and more efficient, you realize this can be even simpler:

    UI (browser client) → Fetch (Browser API calls) → Server Endpoint (Controller) → SQLRDBMS

    And then you realize SQL actually has a rich type system - at least something mature and advanced as PostgreSQL does - and that type system can be used as a contract, and that Controller code is just boring glue code that can and should be automated:

    UI (browser client) → Fetch (Browser API calls) → Automatic Server Endpoint (Controller) → SQLRDBMS

    But why stop there? If the controller can be automated, why not generate those Fetch calls as well? And that SQL can be stored in the RDBMS. Finally, this is what we have, basically:

    UIRDBMS

    System Diagram

    Now, if we all start doing software architecture like that, imagine how many tokens and energy we could save... That is what I call the energy efficiency. In a time of looming energy crisis, we could probably power a small country with the power of simplicity.


    Starting with NpgsqlRest 3.12.0, this simplification reached its logical conclusion with SQL File Endpoints. You don't even need to store your SQL in the database as functions anymore. Write a .sql file, add a comment annotation, and it becomes a REST endpoint:

    sql
    sql
    -- sql/get-users.sql
    +-- HTTP GET
    +-- @param $1 department
    +SELECT id, name, email FROM users WHERE department = $1;

    That's it. No CREATE FUNCTION, no database deployment step. Just a file on disk that becomes GET /api/get-users?department=engineering. TypeScript types are auto-generated. The SQL stays version-controlled in your repo.

    For complex business logic that needs to evolve independently from the application — like the data contracts story — PostgreSQL functions remain the right choice. But for straightforward queries, SQL files remove the last bit of ceremony between your intent and a working API endpoint.

    Two endpoint sources, one binary, zero boilerplate:

    • SQL Files — write a query, get an endpoint
    • Functions and procedures — formal data contracts with static type checking

    If you want to learn how to do this with PostgreSQL, check out the Quick Start Guide or work through the Tutorials.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/typescript-codegen-walkthrough.html b/blog/typescript-codegen-walkthrough.html new file mode 100644 index 000000000..14f26ccfa --- /dev/null +++ b/blog/typescript-codegen-walkthrough.html @@ -0,0 +1,299 @@ + + + + + + From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    From SQL to Type-Safe TypeScript: A Walkthrough of NpgsqlRest's Code Generator

    April 2026 · TypeScriptCode GenerationType SafetyNpgsqlRest

    NpgsqlRest is two things at once.

    The first is what it does the moment you start it: connect to PostgreSQL, look at your routines and SQL files, and start serving each one as an HTTP endpoint. There's no controller code involved — the routes are wired up dynamically from the database catalog and live only in memory. The entire HTTP infrastructure is configured declaratively, per endpoint, through SQL comment annotations: @authorize, @cache_profile, @rate_limiter, @validate, and dozens more. SQL is declarative; the infrastructure that exposes it should be too. Nothing gets written to disk — endpoints exist for as long as the process runs.

    The second thing is genuine code generation. If you opt in, NpgsqlRest writes a .ts file to your project at startup containing typed fetch() wrappers and TypeScript interfaces — one per endpoint. That file is real source code: you can read it, your IDE indexes it, your bundler compiles it. It's the only artifact NpgsqlRest produces on disk, and it exists purely to give your frontend compile-time type safety against the live HTTP API.

    Change a SQL function, restart the server, and your frontend either compiles against the new shape or refuses to build. No drift, no DTO classes to maintain.

    This post covers the TypeScript client generator: what it produces and how to control it per-endpoint with @tsclient annotations.

    The Pipeline

    Source: your PostgreSQL routines and SQL files, plus comment annotations.

    In-memory output (dynamic, runtime):

    • HTTP endpoint that binds request parameters from the URL or body
    • Response serialized as JSON
    • Declared auth, caching, rate limiting, and validation applied per request

    On-disk output (static, build-time):

    • TypeScript .ts file with a fetch() wrapper per endpoint
    • Typed request and response interfaces
    • Consistent error handling shape

    Both sides read the same source. The .ts file is regular source code: version it in git, or .gitignore it and regenerate on every CI build.

    Since 3.19: regenerate on save

    Run the dev server with --watch and the client regenerates on every change — save a .sql file, or even create or replace a function in psql (the database itself is watched), and the TypeScript types update while you're still in the SQL. Combined with tsc --watch on the frontend, type drift surfaces in seconds.

    A Minimal Example

    Two .sql files, taken verbatim from the examples repository:

    sql/get-users.sql:

    sql
    sql
    -- HTTP GET
    +select user_id, username, email, active from example_2.users;

    sql/get-posts.sql:

    sql
    sql
    -- HTTP GET
    +select u.username, p.content, p.created_at
    +from example_2.posts p join example_2.users u using(user_id)
    +where u.active = true

    Two lines and a query each. The -- HTTP GET comment is the only annotation needed — NpgsqlRest reads it on startup, parses the query against the live database to extract column names and types, and registers the endpoint.

    Configuration to enable both endpoint discovery and client generation:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "./sql/**/*.sql"
    +    },
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/{0}Api.ts"
    +    }
    +  }
    +}

    On startup, NpgsqlRest writes ./src/sqlApi.ts:

    typescript
    typescript
    // autogenerated at 2026-03-31T18:47:10.9822970+02:00
    +
    +const baseUrl = "";
    +
    +type ApiError = {status: number; title: string; detail?: string | null};
    +type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};
    +
    +interface IGetPostsResponse {
    +    username: string | null;
    +    content: string | null;
    +    createdAt: string | null;
    +}
    +
    +interface IGetUsersResponse {
    +    userId: number | null;
    +    username: string | null;
    +    email: string | null;
    +    active: boolean | null;
    +}
    +
    +
    +/**
    +* SQL file: ./sql/get-posts.sql
    +*
    +* @returns {ApiResult<IGetPostsResponse[]>}
    +*/
    +export async function getPosts() : Promise<ApiResult<IGetPostsResponse[]>> {
    +    const response = await fetch(baseUrl + "/api/get-posts", {
    +        method: "GET",
    +        headers: { "Content-Type": "application/json" }
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IGetPostsResponse[] : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0"
    +            ? await response.json() as ApiError
    +            : undefined
    +    };
    +}
    +
    +export async function getUsers() : Promise<ApiResult<IGetUsersResponse[]>> {
    +    /* ...same shape... */
    +}

    Four things to notice:

    • Two-line SQL files become full TypeScript clients. No CREATE FUNCTION, no migration to apply, no schema scaffolding. The .sql file is the endpoint definition.
    • Snake case → camel case. PostgreSQL's user_id, created_at become TypeScript's userId, createdAt.
    • Nullability is preserved. Every column not declared NOT NULL shows up as T | null.
    • Every response is wrapped in ApiResult<T> ({ status, response, error }). Callers must handle errors explicitly — you cannot accidentally use users without checking status first.

    End-to-End Type Safety in Action

    The frontend imports the generated functions and uses them directly:

    typescript
    typescript
    import { getPosts, getUsers } from "./sqlApi.ts";
    +
    +async function loadUsers() {
    +    const { status, response: users, error } = await getUsers();
    +
    +    if (status !== 200) {
    +        app.innerHTML = `<p>Error: ${error?.title}</p>`;
    +        return;
    +    }
    +
    +    // Static type checking happens here!
    +    // If IGetUsersResponse changes (e.g., "username" renamed to "name"),
    +    // TypeScript will fail the build with: Property 'username' does not exist
    +    for (const user of users) {
    +        console.log(user.userId, user.username, user.email);
    +    }
    +}

    If you rename users.username to users.name in the database, the next server restart re-parses get-users.sql against the new schema and rewrites sqlApi.ts with the new field name. Your console.log(user.username) line — and every other line that touches that field — fails the TypeScript build immediately. The bug is caught before the code runs, not after a user sees a blank field.

    There is a second, less-obvious payoff here: the generated client is the cross-stack refactoring tool you've never had. The TypeScript function getUsers is a real symbol — your IDE indexes it like any other. "Find All References" on getUsers across your frontend project lists every component that calls the endpoint, no matter how deep it sits in the call tree. Renaming a SQL function and regenerating the client doesn't just catch the drift at compile time; it gives you the call graph to fix it deliberately, in advance.

    In a conventional ASP.NET Core or FastAPI stack, an interface change made on the database side has to be hand-traced through controllers, DTOs, and API client code, and each layer is another chance for the rename to be applied incompletely. Here the call graph is a single hop — generated function to consumer — and the IDE walks it for you.

    Full end-to-end code in examples/2_static_type_checking_sql_file/src/app.ts. For more on catching schema drift, see the End-to-End Static Type Checking blog post.

    Uploads: When the Generated Wrapper Isn't Just fetch()

    Not every endpoint maps cleanly to a single fetch() call. File uploads with progress reporting need XMLHttpRequest (which exposes upload.onprogress, while fetch does not), FormData, and an extra parameter for the progress callback. The generator handles this automatically when you mark an endpoint with @upload. From examples/6_image_uploads_sql_file/sql/upload-to-large-object.sql:

    sql
    sql
    /*
    +HTTP POST
    +@upload for large_object
    +@param $1 _user_id text = null
    +@param $2 _meta json = null
    +@check_image = true
    +*/
    +with inserted as (
    +    insert into example_6.uploads (user_id, file_name, content_type, file_size, oid)
    +    select $1::int, m->>'fileName', m->>'contentType',
    +           (m->>'size')::bigint, (m->>'oid')::bigint
    +    from json_array_elements($2) as m
    +    where (m->>'success')::boolean = true
    +    returning file_name, content_type, file_size, oid
    +)
    +select (...)::example_6.upload_response from json_array_elements($2) as m;

    The endpoint receives uploaded files, stores each as a PostgreSQL Large Object, and inserts a metadata row. @upload for large_object selects the upload backend; @check_image = true verifies that uploaded bytes are actually a valid image format before storage.

    The generator emits a wrapper that takes a FileList, an optional progress callback, and returns the same typed ApiResult<T>:

    typescript
    typescript
    export async function uploadToLargeObject(
    +    files: FileList | null,
    +    request: IUploadToLargeObjectRequest,
    +    progress?: (loaded: number, total: number) => void,
    +): Promise<ApiResult<IUploadToLargeObjectResponse[]>> {
    +    return new Promise((resolve, reject) => {
    +        if (!files || files.length === 0) {
    +            reject(new Error("No files to upload"));
    +            return;
    +        }
    +        const xhr = new XMLHttpRequest();
    +        if (progress) {
    +            xhr.upload.addEventListener("progress", (event) => {
    +                if (event.lengthComputable) {
    +                    progress(event.loaded, event.total);
    +                }
    +            }, false);
    +        }
    +        xhr.onload = function () {
    +            if (this.status >= 200 && this.status < 300) {
    +                resolve({ status: this.status, response: JSON.parse(this.responseText), error: undefined });
    +            } else {
    +                resolve({ status: this.status, response: [], error: JSON.parse(this.responseText) });
    +            }
    +        };
    +        xhr.open("POST", baseUrl + "/api/upload-to-large-object" + parseQuery(request));
    +        const formData = new FormData();
    +        for (let i = 0; i < files.length; i++) {
    +            formData.append("file", files[i], files[i].name);
    +        }
    +        xhr.send(formData);
    +    });
    +}

    From the consumer side, the call is unremarkable — same shape as any other generated function:

    typescript
    typescript
    const response = await uploadToLargeObject(
    +    files,
    +    { },
    +    (loaded, total) => {
    +        const percent = Math.round((loaded / total) * 100);
    +        progressBar.style.width = `${percent}%`;
    +    }
    +);
    +
    +if (response.status === 200) {
    +    console.log("Uploaded:", response.response);
    +} else {
    +    console.error("Failed:", response.error);
    +}

    You write one SQL file with an @upload annotation. You get an XMLHttpRequest-based wrapper with progress callbacks, multi-file FormData construction, typed response, and consistent error handling — all without touching client-side upload boilerplate yourself.

    Per-Endpoint Control with @tsclient Annotations

    Some endpoints don't need a TypeScript client. Some need a different module. Some need only the URL but not the fetch wrapper. The @tsclient family of annotations covers all three.

    Disable Generation: Binary Endpoints

    @tsclient = false skips client generation for endpoints that wouldn't be useful as fetch wrappers — typically binary downloads served directly to <img src> or browser navigation:

    sql
    sql
    /*
    +HTTP GET
    +@raw
    +@tsclient = false
    +@param $1 id int
    +@define_param mimeType
    +content_type: {mimeType}
    +*/
    +select data from images where id = $1;

    @raw returns the raw bytea bytes (no JSON wrapping), content_type: {mimeType} sets the response header from the mimeType query parameter, and @tsclient = false keeps this endpoint out of the generated TypeScript file. The endpoint is fully callable — your frontend just does <img src="/api/get-image?id=42&mimeType=image/png"> instead of going through a fetch() wrapper that would only ever produce a Blob.

    For production workloads, storing image bytes directly in a bytea column is rarely the best choice — PostgreSQL Large Objects (lo_create, lo_get) keep the binary data out of the row's TOAST table and can be streamed efficiently. See the full implementation in examples/6_image_uploads_sql_file/sql/get-image.sql, which uses select lo_get($1) against an oid parameter.

    URL-Only: Browser-Navigation Endpoints

    @tsclient_url_only = true is for endpoints consumed via <a href>, <form action>, or browser navigation rather than fetch() — Excel exports, CSV downloads, file responses. From examples/14_table_format_sql_file/sql/get-data.sql:

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@define_param format text
    +@define_param excelFileName text
    +@define_param excelSheet text
    +@table_format = {format}
    +@excel_file_name = {excelFileName}
    +@excel_sheet = {excelSheet}
    +@tsclient_url_only = true
    +*/
    +select * from sales_report;

    The generator emits the URL builder and request type, but skips the fetch function:

    typescript
    typescript
    export const getDataUrl = (request: IGetDataRequest) =>
    +    baseUrl + "/api/get-data" + parseQuery(request);
    +
    +interface IGetDataRequest {
    +    format: string | null;
    +    excelFileName?: string | null;
    +    excelSheet?: string | null;
    +}

    Now your frontend can build the URL and assign it to an <a href> or window.location to trigger a download — no fetch wrapper that would consume the response in JavaScript.

    Module Grouping: Logical Bundles Across Schemas

    By default, output files are grouped by source (one .ts file per PostgreSQL schema, or one for all SQL files). @tsclient_module overrides this to group endpoints by feature instead:

    sql/admin/get-users.sql:

    sql
    sql
    -- HTTP GET
    +-- @tsclient_module = admin
    +select user_id, username, email from users;

    sql/admin/get-roles.sql:

    sql
    sql
    -- HTTP GET
    +-- @tsclient_module = admin
    +select role_id, name from roles;

    Both endpoints land in adminApi.ts even though they live in separate files (or in different PostgreSQL schemas if you're using functions). Useful when your backend organization doesn't match your frontend organization (feature-per-page).

    A real-world example from a production deployment of NpgsqlRest (covered in detail in the zero-backend-code case study): functions in the application's primary PostgreSQL schema are split across roughly a dozen generated modules — one per feature area in the frontend — each tagged with the corresponding @tsclient_module annotation. The frontend imports from "./<feature>Api.ts", matching its component structure. The total generated TypeScript surface in that project is around 5,700 lines, regenerated on every dev-mode server restart, maintained by zero humans.

    Other Per-Endpoint Toggles

    AnnotationEffect
    @tsclient_status_code = falseSkip the { status, response, error } wrapper for this one endpoint — return the bare response.
    @tsclient_export_url = trueExport a URL constant for this endpoint even when global ExportUrls is false.
    @tsclient_events = falseSuppress the EventSource parameter for an SSE-enabled endpoint where the consumer doesn't need streaming.
    @tsclient_parse_url = trueAdd a parseUrl function parameter to allow the caller to transform the URL before fetch (signing, mocking, prefixing).
    @tsclient_parse_request = trueAdd a parseRequest function parameter to transform the RequestInit object (custom headers, signal, credentials).

    See @tsclient reference for the full set.

    Scaling Up: Real-World Configuration

    For a multi-page SvelteKit / Next.js / Vite app, the recommended pattern is:

    jsonc
    jsonc
    "ClientCodeGen": {
    +  "Enabled": true,
    +  "FilePath": "./src/app/api/{0}Api.ts",
    +  "BySchema": true,
    +  "CreateSeparateTypeFile": true,
    +  "ImportBaseUrlFrom": "$lib/urls",
    +  "ImportParseQueryFrom": "$lib/urls",
    +  "UseRoutineNameInsteadOfEndpoint": true,
    +  "ExportUrls": true,
    +  "ExportEventSources": true,
    +  "IncludeSchemaInNames": false,
    +  "DefaultJsonType": "string",
    +  "HeaderLines": [
    +    "//",
    +    "// autogenerated file - do not edit",
    +    "//"
    +  ]
    +}

    With this configuration:

    • One *Api.ts (functions) and one *ApiTypes.d.ts (interfaces) file per logical module
    • baseUrl and parseQuery come from your own $lib/urls.ts — the generated files just import them, so your environment-variable-driven base URL stays in one place
    • URL constants (computeUrl(), loginUrl(), etc.) exported alongside the fetch functions, useful for <a> tags and library integrations
    • EventSource factory functions for any @sse endpoint — type-safe SSE without boilerplate
    • Function names match SQL routine names rather than URL paths, so grep-across-stack works

    A typical generated function for an @sse endpoint looks like this:

    typescript
    typescript
    export async function computeVisualization(
    +    request: IComputeVisualizationRequest,
    +    onMessage?: (message: string) => void,
    +    id: string | undefined = undefined,
    +    closeAfterMs = 1000,
    +    awaitConnectionMs: number | undefined = 0
    +) : Promise<ApiResult<IComputeVisualizationResponse[]>> {
    +    const executionId = id ? id : window.crypto.randomUUID();
    +    let eventSource: EventSource;
    +    if (onMessage) {
    +        eventSource = createComputeVisualizationEventSource(executionId);
    +        eventSource.onmessage = (event: MessageEvent) => onMessage(event.data);
    +        if (awaitConnectionMs !== undefined) {
    +            await new Promise(resolve => setTimeout(resolve, awaitConnectionMs));
    +        }
    +    }
    +    try {
    +        const response = await fetch(computeVisualizationUrl(request), {
    +            method: "GET",
    +            headers: { "Content-Type": "application/json", "X-Execution-ID": executionId }
    +        });
    +        return {
    +            status: response.status,
    +            response: response.ok ? await response.json() as IComputeVisualizationResponse[] : undefined!,
    +            error: !response.ok && response.headers.get("content-length") !== "0"
    +                ? await response.json() as ApiError
    +                : undefined
    +        };
    +    } finally {
    +        if (onMessage) {
    +            setTimeout(() => eventSource.close(), closeAfterMs);
    +        }
    +    }
    +}

    The onMessage callback streams RAISE INFO / RAISE NOTICE events from the running PostgreSQL function. The fetch returns the full result set when the function completes. Both happen against a single endpoint — generated automatically from the function's @sse annotation.

    For all available codegen settings, see the Code Generation configuration reference.

    Real-World Workflow: Dev Codegen, Prod No-Codegen

    The static-vs-dynamic distinction matters most when you split your configuration. A real production-grade setup looks like this:

    Dev config (appsettings.development.json) — codegen enabled:

    jsonc
    jsonc
    "ClientCodeGen": {
    +  "Enabled": true,
    +  "FilePath": "./src/app/api/{0}Api.ts",
    +  "FileOverwrite": true,
    +  "BySchema": true,
    +  "CreateSeparateTypeFile": true,
    +  "ImportBaseUrlFrom": "$lib/urls",
    +  "ImportParseQueryFrom": "$lib/urls",
    +  "UseRoutineNameInsteadOfEndpoint": true,
    +  "ExportUrls": true,
    +  "ExportEventSources": true,
    +  "IncludeSchemaInNames": false,
    +  "DefaultJsonType": "string",
    +  "HeaderLines": [
    +    "//",
    +    "// autogenerated file - do not edit",
    +    "//"
    +  ]
    +}

    Prod config (appsettings.json) — codegen disabled:

    jsonc
    jsonc
    "ClientCodeGen": {
    +  "Enabled": false
    +}

    Production has no need to write .ts files — by then, the frontend is a compiled bundle. The generated files exist in your repository (or are produced during CI) and are the input to the frontend build, not its output.

    Two Processes, One Tight Loop

    The development loop runs two processes in parallel:

    jsonc
    jsonc
    // package.json (excerpt)
    +{
    +  "scripts": {
    +    "dev":   "npgsqlrest ./config/appsettings.json ./config/appsettings.development.json",
    +    "watch": "rollup ./src/app --watch",
    +    "build": "rollup ./src/app"
    +  }
    +}
    • bun run dev — boots the NpgsqlRest server. It reads the prod config first, then layers the development config on top (NpgsqlRest accepts multiple config files). Codegen is enabled, so on every restart it rewrites ./src/app/api/*Api.ts and ./src/app/api/*ApiTypes.d.ts.
    • bun run watch (in another terminal) — Rollup runs in watch mode. The moment NpgsqlRest rewrites a generated file, Rollup picks up the change and recompiles your frontend. Type errors appear in your terminal and IDE within seconds.

    Managing Change

    The day-to-day loop when you need to evolve an endpoint is short:

    1. Update and test the SQL. Either edit the SQL file directly and run it manually against your dev database, or CREATE OR REPLACE the function and let your existing test suite cover it. PostgreSQL functions can be tested with plain assert statements inside DO blocks — no extra framework needed:

      sql
      sql
      do $$
      +declare _r record;
      +begin
      +    insert into example.users (user_id, username) values (1, 'alice');
      +
      +    select * into _r from get_user(1);
      +    assert _r.username = 'alice', 'username should match';
      +    assert _r.user_id is not null, 'user_id should not be null';
      +
      +    rollback;
      +end;
      +$$;

      No migration step required. Once the function exists on the server with the new signature, you're done with the database side.

    2. Restart NpgsqlRest in dev mode. That's it. The server re-introspects the catalog, regenerates the *Api.ts and *ApiTypes.d.ts files, and starts serving the new shape.

    3. Watch the terminal. Because the generated files just changed and your frontend toolchain is in watch mode, any line in your UI that no longer matches the new types fails type-checking immediately — no need to click around the app to find what broke.

    4. Fix the frontend. Hand the type errors to your favorite LLM along with the relevant component file and let it patch the calls. The errors are precise (Property 'username' does not exist on type 'IGetUserResponse'), so even a small model handles them well.

    You never run a manual codegen command. You never run tsc against a hand-maintained DTO file. Database is the source, TypeScript enforces the shape, your build is the gate.

    Production: No Codegen, Just the Server

    The production Docker image needs the runtime endpoints but not the codegen step. It uses the official NpgsqlRest base image:

    dockerfile
    dockerfile
    FROM oven/bun:1.3.3-alpine AS base
    +WORKDIR /app
    +COPY . ./
    +RUN bun install --production
    +RUN bun run build       # Frontend compiled here, including generated .ts files
    +
    +FROM vbilopav/npgsqlrest:v3.13.0 AS app
    +WORKDIR /app
    +COPY --from=base /app/dist ./dist
    +COPY ./config/appsettings.json ./
    +EXPOSE 8080
    +ENTRYPOINT [ "npgsqlrest", "./appsettings.json" ]

    The production image:

    • Builds the frontend in a Bun stage — Rollup compiles the already-checked-in (or CI-generated) .ts files into a static bundle.
    • Copies the bundle and the production appsettings.json (codegen disabled) into the NpgsqlRest base image.
    • Boots NpgsqlRest with prod config only. Endpoints are generated dynamically at startup as always; no files are written to disk.

    Deploy to Kubernetes via Helm. Codegen never runs in the production cluster — it's a build-time concern, fully separated from the runtime concerns of serving requests.

    What This Means in Practice

    The author of the project this configuration comes from — profiled in the zero-backend-code case study, which puts the LOC numbers next to an equivalent ASP.NET Core build — describes the experience like this:

    "I am free to focus on my database that it works correctly."

    That's the loop NpgsqlRest is designed to enable. The database is the artifact you actually care about. The HTTP layer is generated. The TypeScript client is generated. Both come from the same source. When the database is right, everything downstream is right too — and the build catches it the moment it isn't.

    Workflow Summary

    1. Write a PostgreSQL function (or SQL file) with a comment annotation describing the endpoint.
    2. Restart NpgsqlRest. Two things happen on the same startup:
      • The HTTP endpoint is registered in memory and starts serving requests (dynamic, runtime).
      • The corresponding TypeScript wrapper is written to a .ts file on disk (static, one-shot).
    3. Import the generated function in your frontend code. Your build pipeline (Vite, Next.js, tsc, esbuild) compiles it like any other source file.
    4. Change the database schema. Restart. The next run rewrites the .ts file. Your frontend's next compile either matches the new shape (build passes) or doesn't (build fails with a precise error).

    There is no DTO file to maintain, no OpenAPI spec to keep in sync, no runtime validation library configured at three layers. The database is the source of truth, the endpoint generator translates it to HTTP at runtime, and the client generator translates it to TypeScript at build time. Both translations come from the same catalog read.

    Comments

    + + + + \ No newline at end of file diff --git a/blog/web-scraping-postgresql-http-types-xml.html b/blog/web-scraping-postgresql-http-types-xml.html new file mode 100644 index 000000000..1c1cf2e85 --- /dev/null +++ b/blog/web-scraping-postgresql-http-types-xml.html @@ -0,0 +1,167 @@ + + + + + + Web Scraping with PostgreSQL: HTTP Types + XML Functions | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    Web Scraping with PostgreSQL: HTTP Types + XML Functions

    HTTP · Web Scraping · XML / XPath · June 2026


    A web scraper is two things: something that fetches a page, and something that parses the HTML it gets back. The usual stack reaches for Python plus requests plus BeautifulSoup, or a hosted scraping API.

    PostgreSQL already has both halves. NpgsqlRest's HTTP Custom Types do the fetch — the request lives in a type comment and is performed automatically before your function runs. And PostgreSQL's built-in xpath() / xmlparse() functions do the parse. Put them together and a scraper endpoint is just a SQL function — no application code, no extra services.

    This post builds two of them:

    Both are public test sites built for exactly this.

    The recipe

    Every scraper here follows the same four steps:

    1. Fetch the page with an HTTP Custom Type.
    2. Isolate the repeating blocks (a product card, a book article) with a regex.
    3. Clean the HTML into well-formed XML — drop void tags like <img> that never close.
    4. Parse with xpath() and compute the answer in plain SQL.

    Example 17: average book price

    Fetch — the HTTP Custom Type

    An HTTP Custom Type is a composite type whose comment defines an HTTP request. When a function takes the type as a parameter, NpgsqlRest makes the call and fills in the fields before the function body executes. body is text, not jsonb, because we're getting raw HTML back:

    sql
    sql
    create type example_17.books_api as (
    +    body text,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type example_17.books_api is 'GET https://books.toscrape.com/
    +Accept: text/html
    +@timeout 30s';

    Parse — regex to isolate, XPath to read

    sql
    sql
    create function example_17.average_book_price(
    +    _response example_17.books_api default null
    +)
    +returns table (
    +    avg_price numeric
    +)
    +language plpgsql
    +as
    +$$
    +declare
    +    _articles text[];
    +    _cleaned  text;
    +    _doc      xml;
    +begin
    +    -- Bail out if the external request failed.
    +    if not (_response).success then
    +        raise exception 'Failed to fetch books page: %',
    +            coalesce((_response).error_message, 'HTTP status ' || (_response).status_code);
    +    end if;
    +
    +    -- 1. Isolate each book from the HTML. Each book is an <article>.
    +    select array_agg(m[1])
    +      into _articles
    +    from regexp_matches(
    +        (_response).body,
    +        '<article class="product_pod">.*?</article>',
    +        'gs'
    +    ) m;
    +
    +    if _articles is null then
    +        raise exception 'No books found in the response';
    +    end if;
    +
    +    -- 2. Clean the HTML into well-formed XML: drop the void <img> elements.
    +    _cleaned := array_to_string(_articles, '');
    +    _cleaned := regexp_replace(_cleaned, '<img[^>]*>', '', 'g');
    +
    +    _doc := xmlparse(document '<books>' || _cleaned || '</books>');
    +
    +    -- 3. Read every price with XPath and average them.
    +    --    The price is the text of <p class="price_color">, e.g. "£51.77".
    +    return query
    +    select round(avg(substring(p::text from '([0-9]+(?:\.[0-9]+)?)')::numeric), 2)
    +    from unnest(xpath('//p[@class="price_color"]/text()', _doc)) as p;
    +end;
    +$$;
    +
    +comment on function example_17.average_book_price(example_17.books_api) is 'HTTP GET /average-book-price
    +@allow_anonymous
    +@single';

    The @single annotation makes the endpoint return one JSON object instead of an array — { "avgPrice": 35.07 } — and that's the whole backend. GET /average-book-price fetches the page, parses it, and answers.

    Why regex and XPath?

    PostgreSQL's xmlparse(document ...) wants well-formed XML, and real HTML isn't — <img>, <meta>, and bare boolean attributes have no XML equivalent. The two-step approach sidesteps that: a permissive regex carves out just the repeating blocks we care about, then a couple of regexp_replace calls strip the tags that would break the parser. Once the fragment is clean, xpath() does the precise, structured reading — far more robust than trying to pull every field out with regex alone.

    Example 16: best-value laptop

    Same recipe, one step further — instead of averaging a single column, it reads three fields per product and ranks them. The card is isolated, the void tags and a bare itemscope attribute are normalized, and then XPath pulls title, price, and rating:

    sql
    sql
    with raw as (
    +    select
    +        (xpath('.//a[@class="title"]/@title', node))[1]::text as p_title,
    +        replace(substring(
    +            (xpath('.//h4[contains(@class,"price")]//span[@itemprop="price"]/text()', node))[1]::text
    +            from '(\$[0-9]+(?:\.[0-9]+)?)'), '$', '')::numeric as p_price,
    +        (xpath('.//p[@data-rating]/@data-rating', node))[1]::text::numeric as p_rating
    +    from unnest(xpath('/products/div', _doc)) as node
    +),
    +bounds as (
    +    select min(p_price) as min_price, max(p_price) as max_price from raw
    +)
    +select
    +    r.p_title,
    +    r.p_price,
    +    r.p_rating,
    +    round(
    +        0.7 * (1 - case when b.max_price = b.min_price then 0
    +                        else (r.p_price - b.min_price) / (b.max_price - b.min_price) end)
    +        + 0.3 * (r.p_rating / 5),
    +    6)
    +from raw r cross join bounds b
    +order by 4 desc
    +limit 1;

    The score is 0.7 * (1 - normalizedPrice) + 0.3 * (rating / 5) — cheaper (70%) and higher-rated (30%) wins. The point isn't the formula; it's that once the data is in relational form, ranking, weighting, and aggregating are just SQL. No mapping HTML into objects in another language first.

    Be a good citizen: cache the page

    Scraped pages change slowly, but a naive endpoint hits the upstream on every request. As of NpgsqlRest 3.18.0, the @cache directive caches the outbound response and reuses it for matching requests within a TTL:

    sql
    sql
    comment on type example_17.books_api is '@cache 5m
    +GET https://books.toscrape.com/
    +Accept: text/html
    +@timeout 30s';

    Now a burst of traffic to /average-book-price collapses to one upstream fetch every 5 minutes (with stampede protection coalescing concurrent requests into a single call), instead of one fetch per visitor. Caching is opt-in, GET-only, and stores only successful responses — exactly the right default for scraping.

    A different split: fetch in SQL, parse in a service

    Parsing in SQL is the point of this post, but it isn't the only shape. Sometimes the parser already exists — a service that turns HTML into the numbers you need — and you only want PostgreSQL to do the fetch. NpgsqlRest can do exactly that: let the HTTP Custom Type fetch the page, then @proxy the scraped HTML straight to that upstream service. The function body stays empty — it's a passthrough.

    The catch is where the HTML goes. Automatic (server-filled) parameters are forwarded to the proxy, but an entire HTML page is far too large for the query string — it produces a request line the upstream rejects (HTTP 414/431). The fix is to route that one field into the proxy request body with @body_parameter_name, and use a body-carrying method (POST):

    sql
    sql
    create type example_18.books_api as (
    +    body text,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type example_18.books_api is 'GET https://books.toscrape.com/
    +Accept: text/html
    +@timeout 30s';
    +
    +create function example_18.average_book_price(
    +    _response example_18.books_api default null
    +)
    +returns table (
    +    avg_price numeric
    +)
    +language plpgsql
    +as
    +$$
    +begin
    +-- empty, proxy passthrough: no DB called at all
    +end;
    +$$;
    +
    +comment on function example_18.average_book_price(example_18.books_api) is '
    +HTTP POST /average-book-price
    +@body_parameter_name _response_body
    +@allow_anonymous
    +@single
    +@proxy
    +';

    @body_parameter_name _response_body targets the expanded body field of the HTTP Custom Type — the scraped HTML — and sends it as the raw POST body to the upstream. The small fields (status_code, success, …) ride along on the query string. The upstream then does the parsing it already knows how to do and answers with the average:

    ts
    ts
    // upstream/server.ts — receives the scraped HTML in the POST body
    +const html = await req.text();
    +const prices = [...html.matchAll(/class="price_color">\s*£([\d.]+)/g)]
    +    .map(m => Number(m[1]))
    +    .filter(n => !Number.isNaN(n));
    +const avgPrice = prices.length
    +    ? prices.reduce((a, b) => a + b, 0) / prices.length
    +    : null;
    +return Response.json({ avgPrice });

    Two NpgsqlRest 3.18.2 options make this clean: ProxyOptions.MaxForwardedQueryParamLength is the guard that would have skipped the oversized body from the query string in the first place, and OmitAutomaticParameters drops the server-filled fields from the generated client so the call is a bare averageBookPrice() with no arguments.

    When this works (and when it doesn't)

    This approach shines on server-rendered, reasonably structured HTML — product listings, catalogs, tables, RSS-like pages. It's a few lines of SQL and it deploys with the rest of your database.

    It is not a headless browser. Pages that build their content with client-side JavaScript return an empty shell to an HTTP fetch — there's no DOM to render and nothing for XPath to read. For those you still need a real browser engine. But for the large class of pages that ship their data in the HTML, PostgreSQL plus an HTTP Custom Type is all the scraper you need.

    Try it

    Both examples are runnable end to end:

    Source: examples/16_scrap_demo · examples/17_scrap_demo_2 · examples/18_scrap_proxy_demo

    bash
    bash
    cd examples/17_scrap_demo_2
    +bun run db:up
    +bun run dev
    +# open http://127.0.0.1:8080

    The proxy variant (example 18) runs the same way, plus its upstream service in a second terminal:

    bash
    bash
    cd examples/18_scrap_proxy_demo
    +bun run db:up
    +bun run upstream   # starts the parsing service on :3001
    +bun run dev        # in another terminal
    +# open http://127.0.0.1:8080

    Comments

    + + + + \ No newline at end of file diff --git a/blog/what-have-stored-procedures-ever-done-for-us.html b/blog/what-have-stored-procedures-ever-done-for-us.html new file mode 100644 index 000000000..bdd3a731e --- /dev/null +++ b/blog/what-have-stored-procedures-ever-done-for-us.html @@ -0,0 +1,48 @@ + + + + + + What Have PostgreSQL Functions Ever Done for Us? | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source
    How this page was made

    This page was written with AI assistance and verified against the NpgsqlRest source code — the same division of labor the product itself is built around: AI does the writing, machines check the facts. The project itself (the library, parser, codegen, and runtime) is hand-written and covered by 2,200+ integration tests. A few posts written entirely by hand carry a "Human Written" badge instead. If you spot an inaccuracy, the comment section below goes straight to the maintainer — more in About.

    What Have PostgreSQL Functions Ever Done for Us?

    January 2026 · PostgreSQLArchitectureOpinion


    Hey, DDD developers. What's stopping you from crafting beautiful, efficient domain models with these? With these obsolete, dusty old PostgreSQL functions and stored procedures?

    What are we, stuck in the 90s or something?

    What have the stored procedures ever done for us?

    Besides — what have user-defined functions and stored procedures ever really done for us?

    Let me actually answer that. Not with hand-waving, but with a tiny bookstore I can fit on your screen.

    Here's the schema. Three tables: books, orders, a little price history. Standard stuff.

    Bookstore schema

    These tables? Private parts. Nobody outside the database touches them directly. Ever. Instead, every command and query the application is allowed to run lives in a function, in its own schema:

    Application user with minimal privileges and an API schema

    That app_user can login. That's pretty much it. nosuperuser, nocreatedb, nocreaterole, noinherit. It gets usage on the bookstore_api schema and nothing else. No tables. No data schema. Just the functions I decide to expose.

    To borrow some OOP vocabulary: those functions are my data contracts. Parameters are the input contract, the result set is the output contract. The tables behind them can do whatever they want. The contract doesn't budge unless the application does too.

    And if you strap NpgsqlRest on top, every one of those functions becomes a REST endpoint automatically — no controllers, no DTOs, no mappers. Your SQL is the API.

    OK. But besides being a tidy mental model — what have they ever done for us?

    Type Safety?

    Here's the search API. One function, list_books:

    Type-safe search function with a test block

    create or replace on every build re-validates the whole signature: every column name, every type, every table it touches. Rename a column in books, change price from numeric to int, drop something — and the build screams. No type or name change slips through unnoticed.

    And as a bonus: strap NpgsqlRest on top and those same types propagate straight into your frontend as generated TypeScript. You're type-protected on every front, from the table to the React component.

    OK — besides Type Safety — what have user-defined functions and stored procedures ever done for us?

    Real Encapsulation?

    The tables and types are invisible to the application. It can't see them. It can't SELECT * FROM bookstore.books. All it knows is list_books(_search).

    Which means I can do whatever I want behind the curtain — split a table, denormalize it, archive half of it to cold storage, add a price_history audit on every update like in update_price here:

    Update procedure that writes an audit row, with a test

    The application never finds out. The contract didn't move an inch.

    OK — besides Type Safety and Real Encapsulation — what have user-defined functions and stored procedures ever done for us?

    Zero Downtime?

    Same deal. You split, denormalize, archive, rewrite the guts of a function — and not only does the API not move, you do it with zero downtime. create or replace function is atomic. No deploy. No container restart. No rolling anything. The next call just runs the new body.

    OK — besides Type Safety, Real Encapsulation, and Zero Downtime — what have user-defined functions and stored procedures ever done for us?

    Performance?

    Look at place_order:

    Order function doing stock check, update, and insert in one trip

    Check the stock, validate it, decrement it, insert the order — all of it in one single round trip to the database and back. From the application's point of view it's one call. The unnecessary network chatter — the read, then the read, then the write, then the other write — is reduced to zero.

    OK — besides Type Safety, Real Encapsulation, Zero Downtime, and Performance — what have user-defined functions and stored procedures ever done for us?

    Race Conditions Minimized?

    Same place_order again. Fewer round trips means less time spent on the wire, which means latency drops, which means the window where two orders can fight over the last copy of Ficciones shrinks dramatically. Add the select ... for update and that window basically closes. Try doing that cleanly across four separate ORM calls.

    OK — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, and Race Conditions Minimized — what have user-defined functions and stored procedures ever done for us?

    Security?

    Apart from security, what have stored procedures ever done for us?

    Your ORM is still connecting with a God connection — a role that can do anything and everything to every table. We don't. We use the principle of least privilege, the same way the army does. They call it "need-to-know." If you're captured, you can't give up what you were never told.

    That's exactly what app_user is. It can call functions in one schema. It cannot read a table, cannot drop one, cannot escalate. So even if those credentials leak — out of the codebase, out of the infra, out of some forgotten .env in a public repo — the attacker gets to call the same endpoints your frontend already calls. They don't get to dump your data. The God connection credentials never went anywhere a thief could reach.

    Alright, alright — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, Race Conditions Minimized, and Security — what have user-defined functions and stored procedures ever done for us?

    A Short Test Loop?

    Look at the bottom of every one of those screenshots. See that do block?

    sql
    sql
    do
    +$$
    +begin
    +    insert into bookstore.books (title, author, price) values
    +        ('Ficciones', 'Jorge Luis Borges', 12.99),
    +        ('The Aleph', 'Jorge Luis Borges', 14.50);
    +    assert (select count(*) from bookstore_api.list_books()) = 2;
    +    assert (select count(*) from bookstore_api.list_books(_search => 'borges')) = 2;
    +    raise notice 'list_books: ok';
    +    rollback;
    +end;
    +$$;

    Seed. Assert. Rollback. It lives at the end of the same SQL file as the function. I re-run the file in psql — the function gets create or replace'd, the test runs, I get list_books: ok, and the transaction rolls back so the database is exactly as it was.

    Milliseconds. No build. No test runner. No mocked database. No spinning up half of Docker to find out my WHERE clause was wrong.

    Oh God, oh man — besides Type Safety, Real Encapsulation, Zero Downtime, Performance, Race Conditions Minimized, Security, and a Short Test Loop — what have user-defined functions and stored procedures ever done for us?

    Apart from security, performance, maintainability, and availability, what have stored procedures ever done for us?

    ...

    Yeah. I really don't know either.

    So, DDD developers

    If you actually want to learn these ancient, obsolete, stuck-in-the-90s techniques — type-safe contracts, real encapsulation, least-privilege security, a test loop measured in milliseconds — I'll be running free workshops (and consulting) for NpgsqlRest users after this summer.

    Drop a comment if you're interested.

    And if you'd rather just start: your SQL is already the API, you just haven't pointed NpgsqlRest at it yet.

    Comments

    + + + + \ No newline at end of file diff --git a/bulb.svg b/bulb.svg new file mode 100644 index 000000000..a22240c6d --- /dev/null +++ b/bulb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/clean.png b/clean.png new file mode 100644 index 000000000..b18e9b3d3 Binary files /dev/null and b/clean.png differ diff --git a/config/antiforgery.html b/config/antiforgery.html new file mode 100644 index 000000000..7abab6764 --- /dev/null +++ b/config/antiforgery.html @@ -0,0 +1,60 @@ + + + + + + Antiforgery Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Antiforgery

    Antiforgery token configuration protects against Cross-Site Request Forgery (CSRF) attacks by validating unique tokens for state-changing requests (POST, PUT, DELETE, etc.).

    Overview

    json
    json
    {
    +  "Antiforgery": {
    +    "Enabled": false,
    +    "CookieName": null,
    +    "FormFieldName": "__RequestVerificationToken",
    +    "HeaderName": "RequestVerificationToken",
    +    "SuppressReadingTokenFromFormBody": false,
    +    "SuppressXFrameOptionsHeader": false
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable antiforgery token validation.
    CookieNamestringnullCustom cookie name. Uses default (.AspNetCore.Antiforgery.*) if null.
    FormFieldNamestring"__RequestVerificationToken"Name of the hidden form field containing the token.
    HeaderNamestring"RequestVerificationToken"HTTP header name for sending the token (useful for AJAX requests).
    SuppressReadingTokenFromFormBodyboolfalseWhen true, skips reading tokens from form body (forces header-only validation).
    SuppressXFrameOptionsHeaderboolfalseWhen true, disables automatic X-Frame-Options header generation.

    Token Submission

    Antiforgery tokens can be submitted in two ways:

    Form Field

    Include a hidden field in HTML forms:

    html
    html
    <form method="POST" action="/api/submit">
    +  <input type="hidden" name="__RequestVerificationToken" value="token-value" />
    +  <!-- form fields -->
    +</form>

    HTTP Header

    Send the token in a header (useful for AJAX/fetch requests):

    javascript
    javascript
    fetch('/api/submit', {
    +  method: 'POST',
    +  headers: {
    +    'RequestVerificationToken': tokenValue
    +  }
    +});

    X-Frame-Options Header

    When SuppressXFrameOptionsHeader is false (default), the server automatically adds the X-Frame-Options header to prevent clickjacking attacks.

    WARNING

    Only set SuppressXFrameOptionsHeader to true if you're handling frame protection elsewhere (e.g., Content-Security-Policy frame-ancestors directive).

    Example Configuration

    Enable antiforgery with custom header name:

    json
    json
    {
    +  "Antiforgery": {
    +    "Enabled": true,
    +    "HeaderName": "X-CSRF-Token",
    +    "SuppressReadingTokenFromFormBody": true
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/auth.html b/config/auth.html new file mode 100644 index 000000000..1648b6392 --- /dev/null +++ b/config/auth.html @@ -0,0 +1,198 @@ + + + + + + Authentication Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Authentication

    This page covers Cookie, Bearer Token, and JWT authentication settings in NpgsqlRest.

    Overview

    NpgsqlRest supports multiple authentication methods that can be used together:

    • Cookie Authentication - Traditional session-based authentication
    • Microsoft Bearer Token Authentication - Proprietary encrypted token authentication (ASP.NET Core specific)
    • JWT Authentication - Industry-standard JSON Web Token authentication (RFC 7519)
    • Passkey Authentication - WebAuthn passwordless authentication (see Passkey Authentication)
    • External OAuth Providers - Google, LinkedIn, GitHub, Microsoft, Facebook (see External OAuth)
    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": false,
    +    "BearerTokenAuth": false,
    +    "JwtAuth": false,
    +    "External": {
    +      "Enabled": false
    +    }
    +  }
    +}

    Cookie authentication provides session-based authentication using HTTP cookies.

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": null,
    +    "CookieValid": "14 days",
    +    "CookieName": null,
    +    "CookiePath": null,
    +    "CookieDomain": null,
    +    "CookieMultiSessions": true,
    +    "CookieHttpOnly": true,
    +    "CookieSameSite": null,
    +    "CookieSecure": null
    +  }
    +}
    SettingTypeDefaultDescription
    CookieAuthboolfalseEnable cookie authentication.
    CookieAuthSchemestringnullAuthentication scheme name. Uses "Cookies" if null (from CookieAuthenticationDefaults.AuthenticationScheme).
    CookieValidstring"14 days"Cookie validity duration in PostgreSQL interval format (e.g., "14 days", "12 hours", "30 minutes"). Set to null to fall back to the framework default (14 days).
    CookieNamestringnullCustom name for the authentication cookie. Uses default if null.
    CookiePathstringnullPath scope for the cookie. Uses default if null.
    CookieDomainstringnullDomain scope for the cookie. Uses default if null.
    CookieMultiSessionsbooltrueAllow multiple concurrent sessions for the same user.
    CookieHttpOnlybooltrueMake cookie accessible only via HTTP (not JavaScript).
    CookieSameSitestringnullSameSite attribute on the cookie: "Strict" / "Lax" / "None" / "Unspecified". null uses ASP.NET's default (typically Lax). Use "None" for cross-origin SPA / mobile clients.
    CookieSecurestringnullWhen the cookie's Secure attribute is set: "SameAsRequest" / "Always" / "None". null uses ASP.NET's default (SameAsRequest). Required "Always" when CookieSameSite is "None" — browsers drop non-Secure SameSite=None cookies.

    When CookieHttpOnly is true (recommended), the cookie cannot be accessed by client-side JavaScript, protecting against XSS attacks.

    TIP

    For production, always use HTTPS and consider setting CookieDomain to your specific domain.

    Cross-Origin Cookies (New in 3.15.0)

    CookieSameSite and CookieSecure make cookie auth work across origins — e.g. an SPA on app.example.com calling an API on api.example.com. Without them, browsers silently drop the session cookie on cross-site requests.

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["https://app.example.com"],
    +    "AllowCredentials": true
    +  },
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieSameSite": "None",
    +    "CookieSecure":   "Always",
    +    "CookieHttpOnly": true,
    +    "CookieDomain":   ".example.com"
    +  }
    +}

    Validation: unknown CookieSameSite / CookieSecure values fail fast at startup with the offending config path. Setting CookieSameSite=None without CookieSecure=Always logs a startup warning — the cookie would be silently dropped by modern browsers.

    Microsoft Bearer Token Authentication

    Microsoft Bearer Token authentication provides stateless token-based authentication using ASP.NET Core's proprietary encrypted token format. This is suitable for single ASP.NET Core applications.

    json
    json
    {
    +  "Auth": {
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": null,
    +    "BearerTokenExpire": "1 hour",
    +    "BearerTokenRefreshPath": "/api/token/refresh"
    +  }
    +}

    Bearer Token Settings Reference

    SettingTypeDefaultDescription
    BearerTokenAuthboolfalseEnable Microsoft bearer token authentication.
    BearerTokenAuthSchemestringnullAuthentication scheme name. Uses "BearerToken" if null (from BearerTokenDefaults.AuthenticationScheme).
    BearerTokenExpirestring"1 hour"Bearer token expiration in PostgreSQL interval format (e.g., "1 hour", "30 minutes", "2 days"). Set to null to fall back to the framework default (1 hour).
    BearerTokenRefreshPathstring"/api/token/refresh"Endpoint path for refreshing tokens.

    Token Refresh

    To refresh a Microsoft bearer token, POST to the configured refresh path:

    http
    http
    POST /api/token/refresh
    +Content-Type: application/json
    +
    +{
    +  "refresh": "{{refreshToken}}"
    +}

    JWT Authentication

    New in 3.2.1

    JWT authentication was added in version 3.2.1.

    JWT (JSON Web Token) authentication provides industry-standard token-based authentication (RFC 7519). Unlike Microsoft Bearer Token authentication, JWT tokens are interoperable and can be used with any system that supports JWT.

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "your-app",
    +    "JwtAudience": "your-api",
    +    "JwtExpire": "60 minutes",
    +    "JwtRefreshExpire": "7 days",
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true,
    +    "JwtValidateLifetime": true,
    +    "JwtValidateIssuerSigningKey": true,
    +    "JwtClockSkew": "5 minutes",
    +    "JwtRefreshPath": "/api/jwt/refresh"
    +  }
    +}

    JWT Settings Reference

    SettingTypeDefaultDescription
    JwtAuthboolfalseEnable JWT authentication.
    JwtAuthSchemestringnullAuthentication scheme name. Uses "Bearer" if null (from JwtBearerDefaults.AuthenticationScheme).
    JwtSecretstringnullSecret key for signing tokens. Must be at least 32 characters for HS256.
    JwtIssuerstringnullToken issuer (iss claim).
    JwtAudiencestringnullToken audience (aud claim).
    JwtExpirestring"60 minutes"Access token expiration in PostgreSQL interval format (e.g., "60 minutes", "1 hour", "30 seconds"). Set to null to fall back to the framework default (60 minutes).
    JwtRefreshExpirestring"7 days"Refresh token expiration in PostgreSQL interval format (e.g., "7 days", "168 hours"). Set to null to fall back to the framework default (7 days).
    JwtValidateIssuerboolfalseValidate the issuer claim. Set to true if JwtIssuer is configured.
    JwtValidateAudienceboolfalseValidate the audience claim. Set to true if JwtAudience is configured.
    JwtValidateLifetimebooltrueValidate token expiration.
    JwtValidateIssuerSigningKeybooltrueValidate the signing key.
    JwtClockSkewstring"5 minutes"Clock tolerance for expiration validation. Uses PostgreSQL interval format.
    JwtRefreshPathstring"/api/jwt/refresh"Endpoint path for refreshing JWT tokens.

    Login Response

    When JWT authentication is enabled and a login endpoint returns successfully, the response includes:

    json
    json
    {
    +  "accessToken": "eyJhbG...",
    +  "refreshToken": "eyJhbG...",
    +  "tokenType": "Bearer",
    +  "expiresIn": 3600,
    +  "refreshExpiresIn": 604800
    +}

    Token Refresh

    To refresh a JWT token, POST to the configured refresh path (default: /api/jwt/refresh):

    http
    http
    POST /api/jwt/refresh
    +Content-Type: application/json
    +
    +{
    +  "refreshToken": "eyJhbG..."
    +}

    The response returns a new access token and refresh token pair.

    JWT vs Microsoft Bearer Token

    FeatureMicrosoft Bearer TokenJWT
    Token FormatProprietary, encryptedIndustry-standard (RFC 7519)
    InteroperabilityASP.NET Core onlyAny system supporting JWT
    Token InspectionOpaqueCan be decoded at jwt.io
    Use CaseSingle ASP.NET appCross-service, microservices

    Security

    Store your JwtSecret securely. Use environment variables in production:

    json
    json
    {
    +  "Auth": {
    +    "JwtSecret": "{JWT_SECRET}"
    +  }
    +}

    Additional Authentication Schemes

    New in 3.13.0

    Named additional authentication schemes registered alongside the main one.

    Auth:Schemes is a named-dict section that registers additional ASP.NET Core authentication schemes alongside the main one. Each entry is a fully-fledged scheme of any of the three supported types — Cookies, BearerToken, or Jwt — with its own options. A login function selects which scheme to use by returning the scheme's name in its scheme column.

    What this enables:

    • Short-lived sensitive sessions for admin or payment flows (Cookies scheme with shorter CookieValid + CookieMultiSessions: false).
    • Per-scope JWT signing keys so a key leak has limited blast radius (separate JwtSecret per Jwt scheme).
    • Multiple bearer-token APIs with different expirations and refresh paths.
    • Single-session cookies for areas where parallel logins must be disallowed, alongside a normal long-lived session.
    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "14 days",
    +  "JwtAuth": true,
    +  "JwtSecret": "...root-secret-32+chars...",
    +  "Schemes": {
    +    "short_session": {
    +      "Type": "Cookies",
    +      "Enabled": true,
    +      "CookieValid": "1 hour",
    +      "CookieMultiSessions": false
    +    },
    +    "api_token": {
    +      "Type": "BearerToken",
    +      "Enabled": true,
    +      "BearerTokenExpire": "30 minutes",
    +      "BearerTokenRefreshPath": "/api/api-token/refresh"
    +    },
    +    "admin_jwt": {
    +      "Type": "Jwt",
    +      "Enabled": true,
    +      "JwtSecret": "...separate-admin-secret-32+chars...",
    +      "JwtExpire": "5 minutes",
    +      "JwtRefreshPath": "/api/admin-jwt/refresh"
    +    }
    +  }
    +}

    A login function selects the scheme by returning its name in the scheme column:

    Equivalent as a SQL file endpoint (sql/login.sql):

    sql
    sql
    /*
    +HTTP POST
    +@login
    +@allow_anonymous
    +@param $1 user
    +@param $2 pass
    +*/
    +select 'Cookies' as scheme, user_id::text as name_identifier, username as name
    +from users where username = $1 and password_hash = crypt($2, password_hash);
    sql
    sql
    -- Standard login: returns 'Cookies' → 14-day persistent cookie
    +create function login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'Cookies' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Sensitive-area login: returns 'short_session' → 1-hour session-only cookie
    +create function admin_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'short_session' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Admin JWT login: returns 'admin_jwt' → 5-minute JWT signed with the admin-only secret
    +create function admin_jwt_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'admin_jwt' as scheme, user_id::text, username from users where ...
    +$$;

    Per-Type Override Fields

    TypeOverride fields
    CookiesCookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly, CookieSameSite, CookieSecure
    BearerTokenBearerTokenExpire, BearerTokenRefreshPath
    JwtJwtExpire, JwtRefreshExpire, JwtSecret, JwtIssuer, JwtAudience, JwtClockSkew, JwtRefreshPath, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey

    Common fields: Type (required, case-insensitive), Enabled (default true).

    Inheritance. A scheme that overrides only one or two fields reuses everything else from the root Auth section, so blocks stay small. Setting CookieMultiSessions: false is the typical "single-session" override — the cookie's Max-Age becomes null (browser-session-only) while ExpireTimeSpan still bounds server-side validity. JWT schemes inherit JwtSecret from the root section if not set explicitly, so a per-scheme block can be just a shorter expiration.

    Validation at Startup (Fail-Fast)

    • Scheme name must not collide with the main scheme names (CookieAuthScheme, BearerTokenAuthScheme, JwtAuthScheme).
    • Type must be one of Cookies, BearerToken, Jwt (case-insensitive). Missing or unsupported types throw with a clear message.
    • Explicit CookieName values must be distinct across all cookie schemes. When unset, ASP.NET's per-scheme .AspNetCore.<scheme> default automatically differs and is excluded from collision tracking.
    • Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across the main scheme and every scheme that defines one.
    • Jwt schemes require a secret either on the scheme or on the root section; JwtSecret must be ≥32 chars for HS256.
    • Invalid interval strings throw with the offending path and value.

    Refresh middleware per scheme. Each BearerToken/Jwt scheme that declares a refresh path gets its own middleware listening on that path, with that scheme's tokens validated under that scheme's options.

    Logout. The existing logout pipeline accepts a list of scheme names from the logout function's result columns and signs out each — additional schemes work without changes. To clear both main and additional cookies in one logout, return both scheme names from the function.

    Complete Examples

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "30 days",
    +    "CookieHttpOnly": true,
    +    "CookieMultiSessions": false
    +  }
    +}

    JWT Authentication

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "{JWT_SECRET}",
    +    "JwtIssuer": "my-app",
    +    "JwtAudience": "my-api",
    +    "JwtExpire": "60 minutes",
    +    "JwtRefreshExpire": "7 days",
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true
    +  }
    +}

    Combined Authentication

    All three authentication schemes can be used together:

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "14 days",
    +    "CookieHttpOnly": true,
    +
    +    "BearerTokenAuth": true,
    +    "BearerTokenExpire": "1 hour",
    +
    +    "JwtAuth": true,
    +    "JwtSecret": "{JWT_SECRET}",
    +    "JwtExpire": "60 minutes"
    +  }
    +}

    Breaking change in 3.13.0

    The legacy integer-based time fields under Auth were removed. If you upgrade with any of the four removed fields still in your config, startup will fail with a clear migration message.

    Removed (3.12 and earlier)Use instead (3.13.0+)
    Auth:CookieValidDays: 14Auth:CookieValid: "14 days"
    Auth:BearerTokenExpireHours: 1Auth:BearerTokenExpire: "1 hour"
    Auth:JwtExpireMinutes: 60Auth:JwtExpire: "60 minutes"
    Auth:JwtRefreshExpireDays: 7Auth:JwtRefreshExpire: "7 days"

    The new fields accept Postgres-interval syntax ("14 days", "12 hours", "30 minutes", "45 seconds", etc.) — finer-grained durations than the legacy integers permitted. Setting any of these to null falls back to the framework default.

    Next Steps

    See Also

    • AUTHORIZE - Require authentication on endpoints
    • LOGIN - Mark endpoint as sign-in
    • LOGOUT - Mark endpoint as sign-out

    Comments

    + + + + \ No newline at end of file diff --git a/config/authentication-options.html b/config/authentication-options.html new file mode 100644 index 000000000..0ddae2a92 --- /dev/null +++ b/config/authentication-options.html @@ -0,0 +1,134 @@ + + + + + + Authentication Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Authentication Options

    Basic authentication configuration for NpgsqlRest endpoints including login/logout handling and password settings.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultAuthenticationType": null,
    +      "StatusColumnName": "status",
    +      "SchemeColumnName": "scheme",
    +      "BodyColumnName": "body",
    +      "ResponseTypeColumnName": "application/json",
    +      "HashColumnName": "hash",
    +      "PasswordParameterNameContains": "pass",
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "user_name",
    +      "DefaultRoleClaimType": "user_roles",
    +      "SerializeAuthEndpointsResponse": false,
    +      "ObfuscateAuthParameterLogValues": true,
    +      "PasswordVerificationFailedCommand": null,
    +      "PasswordVerificationSucceededCommand": null,
    +      "UseUserContext": false,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "ClaimsJsonContextKey": null,
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": false,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address",
    +      "LoginPath": null,
    +      "LogoutPath": null,
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    DefaultAuthenticationTypestringnullAuthentication type for ClaimsIdentity. Auto-detected from database name if null and login endpoint exists.
    SerializeAuthEndpointsResponseboolfalseWhen true, login endpoint returns all columns from the login routine as JSON in the response body (ignored for bearer token auth or when BodyColumnName is present).
    ObfuscateAuthParameterLogValuesbooltrueObfuscate parameter values in logs for auth endpoints to protect credentials.

    Login Response Columns

    Column names used to read values from the login routine response.

    SettingTypeDefaultDescription
    StatusColumnNamestring"status"Column for success/failure. Boolean or numeric HTTP status code (200 = success).
    SchemeColumnNamestring"scheme"Column for authentication scheme override.
    BodyColumnNamestring"body"Column for response body message.
    ResponseTypeColumnNamestring"application/json"Column for response content type.
    HashColumnNamestring"hash"Column for password hash verification. See Password Verification.

    Password Handling

    These settings are part of the built-in password verification system. For detailed information on how password verification works, including examples and the built-in password hasher, see Password Verification in the login annotation documentation.

    SettingTypeDefaultDescription
    PasswordParameterNameContainsstring"pass"Identifies password parameter (first param containing this string). See Password Parameter Detection.
    PasswordVerificationFailedCommandstringnullCommand executed on password verification failure.
    PasswordVerificationSucceededCommandstringnullCommand executed on password verification success.

    Password Verification Command Parameters

    Both PasswordVerificationFailedCommand and PasswordVerificationSucceededCommand receive:

    ParameterTypeDescription
    $1textAuthentication scheme used for login.
    $2textUser ID.
    $3textUsername.

    Default Claim Types

    SettingTypeDefaultDescription
    DefaultUserIdClaimTypestring"user_id"Claim type for user ID.
    DefaultNameClaimTypestring"user_name"Claim type for username.
    DefaultRoleClaimTypestring"user_roles"Claim type for user roles.

    User Context Settings

    Settings for automatically passing authenticated user claims to PostgreSQL via context variables.

    SettingTypeDefaultDescription
    UseUserContextboolfalseEnable setting authenticated user claims to context variables automatically. For proxy endpoints, when enabled, these values are also forwarded as HTTP headers to the upstream proxy.
    ContextKeyClaimsMappingobjectSee belowMapping of context keys to user claim names. Keys are context variable names, values are user claim names.
    ClaimsJsonContextKeystringnullContext key for all available user claims as JSON. When not null and user is authenticated, all claims are serialized to JSON and set to this context variable.
    IpAddressContextKeystring"request.ip_address"Context key for IP address. When not null, IP address is set to this context variable when UseUserContext is enabled (even for unauthenticated users).

    Default ContextKeyClaimsMapping

    json
    json
    {
    +  "request.user_id": "user_id",
    +  "request.user_name": "user_name",
    +  "request.user_roles": "user_roles"
    +}

    User Parameters Settings

    Settings for automatically mapping authenticated user claims to function parameters.

    SettingTypeDefaultDescription
    UseUserParametersboolfalseEnable mapping authenticated user claims to parameters by name automatically. For proxy endpoints, when enabled, these values are also forwarded as query string parameters.
    ParameterNameClaimsMappingobjectSee belowMapping of parameter names to user claim names. Keys are parameter names, values are user claim names.
    ClaimsJsonParameterNamestring"_user_claims"Parameter name for all available user claims. When not null and user is authenticated, all claims are serialized to JSON and set to this parameter.
    IpAddressParameterNamestring"_ip_address"Parameter name for IP address. When not null, IP address is set to this parameter when UseUserParameters is enabled (even for unauthenticated users).

    Note: Claim values are always passed as text type. For multi-value claims (like roles), values are passed as text[]. PostgreSQL handles type coercion to your parameter types.

    Default ParameterNameClaimsMapping

    json
    json
    {
    +  "_user_id": "user_id",
    +  "_user_name": "user_name",
    +  "_user_roles": "user_roles"
    +}

    Login and Logout Paths

    SettingTypeDefaultDescription
    LoginPathstringnullURL path for login endpoint. null disables login endpoint.
    LogoutPathstringnullURL path for logout endpoint. null disables logout endpoint.

    Login Command Convention

    The login command must follow these conventions:

    • Return at least one record for successful authentication
    • No records returned = 401 Unauthorized
    • All columns become user claims (column name = claim type, value = claim value)

    Special columns:

    ColumnTypeDescription
    statusbool/intSuccess indicator. Boolean or HTTP status code (200 = success).
    schemetextAuthentication scheme override.
    bodytextResponse body message.
    hashtextPassword hash for verification.

    Logout Command Convention

    • No return data = sign out default scheme
    • Returned values = scheme names to sign out (converted to string)

    Basic Authentication

    HTTP Basic Authentication settings. Expects Authorization: Basic base64(username:password) header.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    For detailed configuration options, examples, and challenge command parameters, see Basic Auth Configuration.

    Complete Example

    Production configuration with login endpoint and user context:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultAuthenticationType": "MyApp",
    +      "StatusColumnName": "status",
    +      "SchemeColumnName": "scheme",
    +      "HashColumnName": "hash",
    +      "PasswordParameterNameContains": "password",
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "user_name",
    +      "DefaultRoleClaimType": "user_roles",
    +      "ObfuscateAuthParameterLogValues": true,
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address",
    +      "LoginPath": "/api/auth/login",
    +      "LogoutPath": "/api/auth/logout"
    +    }
    +  }
    +}

    Next Steps

    See Also

    • AUTHORIZE - Require authentication on endpoints
    • LOGIN - Mark endpoint as sign-in
    • BASIC_AUTH - Enable Basic Auth per endpoint

    Comments

    + + + + \ No newline at end of file diff --git a/config/basic-auth-config.html b/config/basic-auth-config.html new file mode 100644 index 000000000..097482e3d --- /dev/null +++ b/config/basic-auth-config.html @@ -0,0 +1,136 @@ + + + + + + Basic Auth Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Basic Auth Configuration

    HTTP Basic Authentication support with Authorization: Basic base64(username:password) header.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": false,
    +        "Realm": null,
    +        "Users": {},
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": null
    +      }
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Basic Authentication support.
    RealmstringnullAuthentication realm. Uses "NpgsqlRest" if null.
    Usersobject{}Username/password dictionary. Value is password or hash depending on UseDefaultPasswordHasher.
    SslRequirementstring"Required"SSL requirement: "Ignore", "Warning", or "Required".
    UseDefaultPasswordHasherbooltrueExpect hashed passwords in configuration.
    ChallengeCommandstringnullPostgreSQL command for authentication challenge.

    SSL Requirement Values

    ValueDescription
    IgnoreAllow Basic Auth without SSL (debug log warning).
    WarningIssue log warning when connection is not secure.
    RequiredEnforce SSL/TLS connection.

    Challenge Command Parameters

    ParameterTypeDescription
    $1textUsername from Basic Auth header.
    $2textPassword from Basic Auth header.
    $3boolPassword validation result (true/false/null if no password defined).
    $4textBasic Auth realm.
    $5textEndpoint path.

    Static Users Example

    Configure users directly in the configuration file:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": false,
    +        "Users": {
    +          "admin": "secret123",
    +          "user1": "password456"
    +        }
    +      }
    +    }
    +  }
    +}

    WARNING

    When UseDefaultPasswordHasher is false, passwords are stored in plain text. Use hashed passwords in production.

    Database Authentication Example

    Use a PostgreSQL function for authentication challenge:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
    +      }
    +    }
    +  }
    +}

    Challenge Function Example

    sql
    sql
    create function basic_auth_login(
    +    _username text,
    +    _password text,
    +    _validated bool
    +)
    +returns table (
    +    status bool,
    +    user_id int,
    +    user_name text,
    +    user_roles text[]
    +)
    +language plpgsql as $$
    +begin
    +    -- Check if password was validated by static users
    +    if _validated = true then
    +        return query
    +        select true, 1, _username, array['admin']::text[];
    +        return;
    +    end if;
    +
    +    -- Validate against database
    +    return query
    +    select
    +        u.password_hash = crypt(_password, u.password_hash),
    +        u.id,
    +        u.username,
    +        array_agg(r.role_name)
    +    from users u
    +    left join user_roles r on r.user_id = u.id
    +    where u.username = _username
    +    group by u.id, u.username, u.password_hash;
    +end;
    +$$;

    Equivalent as a SQL file challenge command (sql/basic-auth-login.sql):

    The challenge command is referenced from configuration (ChallengeCommand: "select * from basic_auth_login($1, $2, $3)"), so the call site stays the same. The implementation can also be a SQL file endpoint exposed as an internal helper:

    sql
    sql
    /*
    +HTTP POST
    +@internal
    +@param $1 username
    +@param $2 password
    +@param $3 validated boolean
    +*/
    +select
    +    u.password_hash = crypt($2, u.password_hash) as status,
    +    u.id as user_id,
    +    u.username as user_name,
    +    array_agg(r.role_name) as user_roles
    +from users u
    +left join user_roles r on r.user_id = u.id
    +where u.username = $1
    +group by u.id, u.username, u.password_hash;

    Complete Example

    Production configuration with Basic Authentication:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "BasicAuth": {
    +        "Enabled": true,
    +        "Realm": "MyAPI",
    +        "SslRequirement": "Required",
    +        "UseDefaultPasswordHasher": true,
    +        "ChallengeCommand": "select * from basic_auth_login($1, $2, $3)"
    +      }
    +    }
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/cache-options.html b/config/cache-options.html new file mode 100644 index 000000000..f4dbe4f94 --- /dev/null +++ b/config/cache-options.html @@ -0,0 +1,211 @@ + + + + + + Cache Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Cache Options

    Caching configuration for PostgreSQL routines.

    Overview

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": false,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 60,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
    +    "MaxCacheableRows": 1000,
    +    "UseHashedCacheKeys": false,
    +    "HashKeyThreshold": 256,
    +    "InvalidateCacheSuffix": null,
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": null,
    +    "HybridCacheLocalCacheExpiration": null,
    +    "Profiles": {}
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable caching for routines.
    Typestring"Memory"Cache type: "Memory", "Redis", or "Hybrid".
    MemoryCachePruneIntervalSecondsint60How often to prune expired items from memory cache (in seconds).
    RedisConfigurationstring(see below)Redis connection string. Used when Type is "Redis", or when Type is "Hybrid" with HybridCacheUseRedisBackend: true.
    MaxCacheableRowsint?1000Maximum number of rows that can be cached for set-returning functions. If a result set exceeds this limit, it will not be cached (but will still be returned). Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    UseHashedCacheKeysboolfalseWhen true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string. This reduces memory usage for long cache keys and improves Redis performance with large keys.
    HashKeyThresholdint256Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true. Keys shorter than this threshold are stored as-is for better debuggability.
    InvalidateCacheSuffixstring?nullWhen set, creates an additional invalidation endpoint for each cached endpoint. The invalidation endpoint has the same path with this suffix appended.
    HybridCacheUseRedisBackendboolfalseWhen Type is "Hybrid", enables Redis as the L2 (secondary/distributed) cache backend. When false, HybridCache uses in-memory only but still provides stampede protection.
    HybridCacheMaximumKeyLengthint1024Maximum length of cache keys in characters (Hybrid cache only). Keys longer than this will be rejected.
    HybridCacheMaximumPayloadBytesint1048576Maximum size of cached payloads in bytes (Hybrid cache only). Default is 1 MB.
    HybridCacheDefaultExpirationstring?nullDefault expiration for cached entries. Accepts PostgreSQL interval format (e.g., "5 minutes", "1 hour"). If not set, individual endpoint cache_expires annotations are used, or entries don't expire.
    HybridCacheLocalCacheExpirationstring?nullExpiration for L1 (in-memory) cache in Hybrid mode. Set shorter than HybridCacheDefaultExpiration to refresh local cache more frequently from Redis. Accepts PostgreSQL interval format.
    Profilesobject?nullNamed caching profiles. Each profile selects its own backend, default expiration, key parameters, and When rules. Endpoints opt in via the @cache_profile annotation. See Cache Profiles below.

    Cache Types

    Memory Cache

    In-memory caching on the application server:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 60
    +  }
    +}

    The MemoryCachePruneIntervalSeconds setting controls how frequently expired cache entries are removed.

    Redis Cache

    Distributed caching using Redis:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3"
    +  }
    +}

    See StackExchange.Redis Configuration for connection string options.

    Hybrid Cache

    HybridCache uses Microsoft's Microsoft.Extensions.Caching.Hybrid library to provide:

    • Stampede protection: Prevents multiple concurrent requests from hitting the database when cache expires
    • Optional Redis L2 backend: Can use Redis as a distributed secondary cache for sharing across instances
    • In-memory L1 cache: Fast local cache for frequently accessed data

    Basic HybridCache (in-memory with stampede protection):

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheDefaultExpiration": "5 minutes"
    +  }
    +}

    HybridCache with Redis backend:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": true,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false",
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "HybridCacheLocalCacheExpiration": "1 minute"
    +  }
    +}

    When HybridCacheUseRedisBackend is false (default), HybridCache works as an in-memory cache with stampede protection. When true, it uses Redis as the L2 distributed cache for sharing across multiple application instances.

    When to use HybridCache:

    • When you need stampede protection (prevents thundering herd on cache expiry)
    • When running multiple application instances that need to share cache
    • When you want the best of both worlds: fast local cache + distributed storage

    Cache Key Hashing

    For improved performance with Redis cache, especially when routines have many or large parameters, you can enable cache key hashing:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "UseHashedCacheKeys": true,
    +    "HashKeyThreshold": 256
    +  }
    +}

    When enabled, cache keys exceeding the threshold are automatically hashed to a fixed 64-character SHA256 string, reducing:

    • Memory usage for storing long cache keys
    • Network transfer overhead with Redis
    • Redis server memory consumption

    This is particularly recommended when:

    • Using Redis cache with routines that have many or large parameters
    • Caching routines with long SQL expressions
    • High cache hit rates where memory efficiency matters

    Caching Set-Returning Functions

    Caching now works for set-returning functions and record types, not just single scalar values. When a cached function returns multiple rows, the entire result set is cached and returned on subsequent calls.

    Use MaxCacheableRows to limit memory usage:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "MaxCacheableRows": 1000
    +  }
    +}

    If a result set exceeds this limit, it will still be returned but will not be cached.

    Cache Invalidation Endpoints

    NpgsqlRest can automatically create invalidation endpoints for each cached endpoint. When InvalidateCacheSuffix is configured, calling the invalidation endpoint with the same parameters removes the corresponding cache entry.

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "InvalidateCacheSuffix": "invalidate"
    +  }
    +}

    Example usage:

    code
    GET /api/get-user/?id=123           -> Returns cached user data
    +GET /api/get-user/invalidate?id=123 -> Removes cache entry, returns {"invalidated":true}
    +GET /api/get-user/?id=123           -> Fresh data (cache was cleared)

    Key features:

    • Same authentication and authorization as the original endpoint
    • Same parameter handling - no need to know the internal cache key format
    • Works correctly with hashed cache keys
    • Returns {"invalidated":true} if cache entry was removed, {"invalidated":false} if not found

    Cache Profiles

    Cache profiles let you maintain multiple distinct caching policies in one application — different backends, expirations, key shapes, or per-parameter bypass conditions — and let endpoints opt into them via the @cache_profile annotation.

    This is useful when one app needs:

    • Different cache backends for different data classes (e.g., Memory for hot per-user data, Redis for shared session data).
    • Different TTLs depending on input shape (e.g., historical queries cached for 1 hour, "until now" queries cached for 5 minutes).
    • Selective cache bypass (e.g., real-time queries with live=true always fetch fresh).

    Overview

    jsonc
    jsonc
    "CacheOptions": {
    +  "Enabled": true,
    +  "Type": "Memory",                 // root cache (used by endpoints WITHOUT @cache_profile)
    +  // ... existing top-level fields ...
    +  "Profiles": {
    +    "fast_memory": {
    +      "Enabled": true,
    +      "Type": "Memory",
    +      "Expiration": "30 seconds",
    +      "Parameters": ["user_id"]
    +    },
    +    "shared_redis": {
    +      "Enabled": true,
    +      "Type": "Redis",
    +      "Expiration": "1 hour"
    +    },
    +    "timeseries": {
    +      "Enabled": true,
    +      "Type": "Memory",
    +      "Expiration": "1 hour",
    +      "Parameters": ["from", "to"],
    +      "When": [
    +        { "Parameter": "to", "Value": null, "Then": "5 minutes" }
    +      ]
    +    }
    +  }
    +}

    Endpoints without @cache_profile continue to use the root cache. Endpoints with @cache_profile <name> use the named profile.

    Profile fields

    FieldTypeDescription
    EnabledboolDefault false. Set true to register the profile. Disabled profiles are skipped at startup with an Information log.
    Typestring"Memory", "Redis", or "Hybrid". Required when Enabled=true.
    Expirationstring?Default expiration (PostgreSQL interval format, e.g. "5 minutes", "1 hour"). Used when the endpoint has no @cache_expires annotation.
    Parametersstring[]?Default cache-key parameter list. Three semantics: null/missing → use all routine parameters; [] → URL-only cache (one entry per endpoint); ["x", "y"] → use only these. The endpoint's @cached p1, p2 annotation overrides this.
    Whenobject[]?List of conditional rules evaluated at request time (see below).

    Backend pooling

    All profiles of the same Type share a single backend instance: one Memory cache, one Redis connection, one HybridCache singleton. Backends are instantiated lazily — only types actually used (root + at least one enabled profile) get spun up. If no profile uses Redis and the root Type isn't Redis, no Redis connection is ever attempted, even if RedisConfiguration is set.

    Cache entries written under a profile are prefixed with the profile name so two profiles sharing the same Memory backend cannot collide on the same routine + parameters. Endpoints without a profile have no prefix; existing pre-3.13 cache entries remain wire-compatible.

    When rules

    When is a list of rules evaluated against the request's resolved parameter values. Each rule has three fields:

    FieldTypeDescription
    ParameterstringRoutine parameter name to inspect (matches against ActualName or ConvertedName). Required.
    Valuescalar / array / nullMatch condition. Scalar = exact match. Array = OR over entries. JSON null matches .NET null/DBNull.Value (does not match empty string). Other values are stringify-and-equal case-insensitive.
    ThenstringRequired action: the literal string "skip" to bypass the cache for this request, OR a PostgreSQL interval (e.g. "30 seconds", "5 minutes", "1 hour") to override the entry's TTL when writing.

    Rules are evaluated in declaration order; first match wins. If no rule matches, the entry is cached using the profile's Expiration.

    Pattern: skip-on-condition

    Bypass the cache entirely for some inputs:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "to", "Value": null, "Then": "skip" }
    +]

    When to is null/missing, no read or write happens — routine executes fresh. Common for "until-now" or "live" data.

    Pattern: dynamic TTL

    Different TTLs depending on input shape:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "live", "Value": true,  "Then": "skip" },
    +  { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
    +]
    • live=true → bypass entirely (real-time mode).
    • live=false and to=null → 5-minute TTL (open-ended query).
    • Otherwise → fall through to the profile's Expiration (e.g. 1 hour for historical queries with both from and to).

    Pattern: array-of-values

    Match any of several values:

    jsonc
    jsonc
    "When": [
    +  { "Parameter": "status", "Value": [null, ""], "Then": "skip" }
    +]

    Matches when status is null OR empty string.

    Validation

    Misconfiguration is caught at startup so deploy issues surface early rather than silently disabling caching at runtime:

    ProblemResult
    @cache_profile references an unknown nameStartup fails with single InvalidOperationException listing every unresolved name and the endpoints that referenced each
    Profile registered but no endpoint references itInformation log: "registered but not used by any endpoint. Did you forget a @cache_profile annotation?"
    Profile has missing/invalid TypeWarning, profile skipped
    Profile has invalid Expiration (bad PG interval)Warning, profile skipped
    Profile name is empty/whitespaceWarning, profile skipped
    When rule's Parameter isn't a routine parameterWarning, rule dropped (other rules still apply)
    When rule's Parameter isn't in the resolved cache-key listWarning, rule dropped (otherwise different rule-evaluations would share a cache entry)
    When rule has missing/invalid ThenWarning, rule dropped

    Connection pooler note

    If you're using a connection pooler in transaction mode (PgBouncer, AWS RDS Proxy in transaction mode, Supabase Pooler), see WrapInTransaction — it's required for context-injection features but does not affect profiles directly.

    Complete example

    jsonc
    jsonc
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MaxCacheableRows": 1000,
    +    "InvalidateCacheSuffix": "invalidate",
    +    "Profiles": {
    +      "user_scoped_fast": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 minute",
    +        "Parameters": ["user_id"]
    +      },
    +      "shared_long_term": {
    +        "Enabled": true,
    +        "Type": "Redis",
    +        "Expiration": "1 hour"
    +      },
    +      "timeseries_compute": {
    +        "Enabled": true,
    +        "Type": "Memory",
    +        "Expiration": "1 hour",
    +        "Parameters": ["from", "to", "live"],
    +        "When": [
    +          { "Parameter": "live", "Value": true,  "Then": "skip" },
    +          { "Parameter": "to",   "Value": null,  "Then": "5 minutes" }
    +        ]
    +      }
    +    }
    +  }
    +}
    sql
    sql
    -- Uses root Memory cache (no profile)
    +comment on function get_app_settings() is 'HTTP GET
    +@cached
    +@cache_expires 1 hour';
    +
    +-- Per-user 1-minute cache via fast Memory profile
    +comment on function get_my_dashboard(user_id int) is 'HTTP GET
    +@cache_profile user_scoped_fast';
    +
    +-- Distributed Redis with 1-hour default
    +comment on function get_global_metrics() is 'HTTP GET
    +@cache_profile shared_long_term';
    +
    +-- Mixed: long-cache historical queries, short-cache open-ended,
    +-- bypass entirely when `live` is true
    +comment on function compute_timeseries(from text, to text default null, live boolean default false) is 'HTTP GET
    +@cache_profile timeseries_compute';

    Routine Annotations

    Enable caching for specific routines using comment annotations:

    cached

    Mark a routine as cacheable:

    sql
    sql
    comment on function get_products() is '
    +HTTP GET /products
    +@cached
    +';

    Specify which parameters to use for the cache key:

    sql
    sql
    comment on function get_product(p_id int) is '
    +HTTP GET /products
    +@cached p_id
    +';

    If no parameters are specified, all parameters are used for the cache key.

    cache_expires / cache_expires_in

    Set cache expiration using interval format:

    sql
    sql
    comment on function get_products() is '
    +HTTP GET /products
    +@cached
    +@cache_expires 5m
    +';
    sql
    sql
    comment on function get_config() is '
    +HTTP GET /config
    +@cached
    +@cache_expires_in 1h
    +';

    If no expiration is specified, cache entries never expire.

    cache_profile

    Select a named cache profile for the endpoint:

    sql
    sql
    comment on function get_dashboard() is '
    +HTTP GET
    +@cache_profile fast_memory
    +';

    @cache_profile implies caching — @cached is unnecessary alongside it but is still allowed and overrides the profile's Parameters list. See the dedicated @cache_profile annotation reference for full semantics.

    Example Configuration

    Production configuration with Redis:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "RedisConfiguration": "redis-server:6379,password={REDIS_PASSWORD},ssl=true,abortConnect=false"
    +  }
    +}

    Development configuration with memory cache:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Memory",
    +    "MemoryCachePruneIntervalSeconds": 30
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/claims-mapping.html b/config/claims-mapping.html new file mode 100644 index 000000000..031b650f3 --- /dev/null +++ b/config/claims-mapping.html @@ -0,0 +1,160 @@ + + + + + + Claims Mapping Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Claims Mapping

    Configure how authenticated user claims are mapped to PostgreSQL context variables and function parameters.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": false,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "ClaimsJsonContextKey": null,
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": false,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address"
    +    }
    +  }
    +}

    User Context (PostgreSQL Context Variables)

    Map authenticated user claims to PostgreSQL session context variables. Enable for specific endpoints using the user_context annotation, or enable globally with UseUserContext.

    SettingTypeDefaultDescription
    UseUserContextboolfalseEnable automatic claim-to-context mapping for all endpoints. Override per-endpoint with user_context annotation.
    ContextKeyClaimsMappingobject(see below)Map of PostgreSQL context keys to claim names. Key is the context variable name, value is the claim type.
    ClaimsJsonContextKeystringnullContext key for all claims serialized as JSON. Set to "request.user_claims" to enable.
    IpAddressContextKeystring"request.ip_address"Context key for client IP address.

    Default Context Mapping

    json
    json
    {
    +  "ContextKeyClaimsMapping": {
    +    "request.user_id": "user_id",
    +    "request.user_name": "user_name",
    +    "request.user_roles": "user_roles"
    +  }
    +}

    Custom Context Mapping Example

    Map additional claims to custom context keys:

    json
    json
    {
    +  "ContextKeyClaimsMapping": {
    +    "request.user_id": "user_id",
    +    "request.user_name": "user_name",
    +    "request.user_roles": "user_roles",
    +    "request.user_email": "email",
    +    "request.tenant_id": "tenant_id"
    +  },
    +  "ClaimsJsonContextKey": "request.user_claims"
    +}

    Access in PostgreSQL

    sql
    sql
    -- Access individual claims
    +select current_setting('request.user_id', true);
    +select current_setting('request.user_name', true);
    +select current_setting('request.user_roles', true);
    +
    +-- Access client IP address
    +select current_setting('request.ip_address', true);
    +
    +-- Access all claims as JSON (when ClaimsJsonContextKey is configured)
    +select current_setting('request.user_claims', true)::jsonb;

    TIP

    Always use true as the second parameter to current_setting() to avoid errors when the setting doesn't exist.

    User Parameters

    Map authenticated user claims to function parameters. Enable for specific endpoints using the user_parameters annotation, or enable globally with UseUserParameters.

    SettingTypeDefaultDescription
    UseUserParametersboolfalseEnable automatic claim-to-parameter mapping for all endpoints. Override per-endpoint with user_parameters annotation.
    ParameterNameClaimsMappingobject(see below)Map of function parameter names to claim names. Key is the parameter name, value is the claim type.
    ClaimsJsonParameterNamestring"_user_claims"Parameter name that receives all claims serialized as JSON.
    IpAddressParameterNamestring"_ip_address"Parameter name that receives the client IP address.

    Default Parameter Mapping

    json
    json
    {
    +  "ParameterNameClaimsMapping": {
    +    "_user_id": "user_id",
    +    "_user_name": "user_name",
    +    "_user_roles": "user_roles"
    +  }
    +}

    Custom Parameter Mapping Example

    Map additional claims to custom parameter names:

    json
    json
    {
    +  "ParameterNameClaimsMapping": {
    +    "_user_id": "user_id",
    +    "_user_name": "user_name",
    +    "_user_roles": "user_roles",
    +    "_email": "email",
    +    "_tenant": "tenant_id"
    +  },
    +  "ClaimsJsonParameterName": "_user_claims",
    +  "IpAddressParameterName": "_ip_address"
    +}

    Example Function Using Parameters

    sql
    sql
    create function get_user_data(
    +    _user_id text,
    +    _user_name text,
    +    _user_roles text[],
    +    _ip_address text,
    +    _user_claims json
    +)
    +returns table (
    +    user_id int,
    +    user_name text,
    +    roles text[],
    +    ip text,
    +    all_claims json
    +)
    +language sql
    +begin atomic;
    +select
    +    _user_id::int,
    +    _user_name,
    +    _user_roles,
    +    _ip_address,
    +    _user_claims;
    +end;
    +
    +comment on function get_user_data(text, text, text[], text, json) is '
    +@authorize
    +@user_params
    +';

    Equivalent as a SQL file endpoint (sql/get-user-data.sql):

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@user_params
    +@param $1 user_id text
    +@param $2 user_name text
    +@param $3 user_roles text[]
    +@param $4 ip_address text
    +@param $5 user_claims json
    +*/
    +select
    +    $1::int as user_id,
    +    $2 as user_name,
    +    $3 as roles,
    +    $4 as ip,
    +    $5 as all_claims;

    TIP

    Parameters with default values can be used without authentication. When the user is authenticated, claim values override the defaults.

    Complete Example

    Configuration with user context and parameters enabled:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      "IpAddressContextKey": "request.ip_address",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      "ClaimsJsonParameterName": "_user_claims",
    +      "IpAddressParameterName": "_ip_address"
    +    }
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/codegen.html b/config/codegen.html new file mode 100644 index 000000000..57e2637a1 --- /dev/null +++ b/config/codegen.html @@ -0,0 +1,261 @@ + + + + + + Code Generation Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Code Generation

    Configuration for generating TypeScript/JavaScript client code for NpgsqlRest endpoints.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": false,
    +      "FilePath": null,
    +      "FileOverwrite": true,
    +      "IncludeHost": true,
    +      "CustomHost": null,
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "BySchema": true,
    +      "IncludeStatusCode": true,
    +      "CreateSeparateTypeFile": true,
    +      "ExportTypes": false,
    +      "ImportBaseUrlFrom": null,
    +      "ImportParseQueryFrom": null,
    +      "IncludeParseUrlParam": false,
    +      "IncludeParseRequestParam": false,
    +      "HeaderLines": ["// autogenerated at {0}", ""],
    +      "SkipRoutineNames": [],
    +      "SkipFunctionNames": [],
    +      "SkipPaths": [],
    +      "SkipSchemas": [],
    +      "DefaultJsonType": "any",
    +      "UseRoutineNameInsteadOfEndpoint": false,
    +      "ExportUrls": false,
    +      "SkipTypes": false,
    +      "UniqueModels": false,
    +      "XsrfTokenHeaderName": null,
    +      "ExportEventSources": true,
    +      "CustomImports": [],
    +      "CustomHeaders": {},
    +      "IncludeSchemaInNames": true,
    +      "ErrorExpression": "await response.json()",
    +      "ErrorType": "{status: number; title: string; detail?: string | null} | undefined",
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable client code generation.
    FilePathstringnullOutput file path. Use {0} for schema name when BySchema is true. null to skip.
    FileOverwritebooltrueOverwrite existing files.
    BySchemabooltrueCreate separate files per PostgreSQL schema.
    IncludeSchemaInNamesbooltrueInclude schema name in generated type names to avoid collisions.

    Host Configuration

    SettingTypeDefaultDescription
    IncludeHostbooltrueInclude current host in URL prefix.
    CustomHoststringnullCustom host prefix for URLs.

    Comment Headers

    SettingTypeDefaultDescription
    CommentHeaderstring"Simple"Comment header style: "None", "Simple", or "Full".
    CommentHeaderIncludeCommentsbooltrueInclude routine comments in header.

    Comment Header Styles

    StyleDescription
    NoneNo comment header.
    SimpleAdd routine name, parameters, and return values (default).
    FullAdd entire routine code as comment header.

    Response Options

    SettingTypeDefaultDescription
    IncludeStatusCodebooltrueInclude status code in response: {status: response.status, response: model}.
    ErrorExpressionstring"await response.json()"Expression to parse error responses. Only used when IncludeStatusCode is true.
    ErrorTypestring(see below)TypeScript type for error responses. Only used when IncludeStatusCode is true.

    Default ErrorType: "{status: number; title: string; detail?: string | null} | undefined"

    These options allow customization of error handling in generated code. Void functions and procedures also return the error object when IncludeStatusCode is true.

    Type Generation

    SettingTypeDefaultDescription
    CreateSeparateTypeFilebooltrueCreate separate {name}Types.d.ts file for global types.
    ExportTypesboolfalseEmit interfaces with the export keyword so they can be imported by other modules. When true and CreateSeparateTypeFile is true, the separate type file becomes an importable module {name}Types.ts (instead of an ambient {name}Types.d.ts) and the client file imports the named types from it. No effect when SkipTypes is true.
    DefaultJsonTypestring"any"Default TypeScript type for JSON types.
    SkipTypesboolfalseSkip type generation for pure JavaScript output (changes .ts to .js).
    UniqueModelsboolfalseMerge models with same fields/types into one (reduces generated models).
    OmitAutomaticParametersboolfalseOmit server-filled parameters from the generated request interface, query string, and body. See OmitAutomaticParameters: true.

    Import Configuration

    SettingTypeDefaultDescription
    ImportBaseUrlFromstringnullModule to import baseUrl constant from.
    ImportParseQueryFromstringnullModule to import parseQuery function from.
    CustomImportsarray[]Custom import statements (full expressions).

    Function Parameters

    SettingTypeDefaultDescription
    IncludeParseUrlParamboolfalseInclude parseUrl: (url: string) => string parameter.
    IncludeParseRequestParamboolfalseInclude parseRequest: (request: RequestInit) => RequestInit parameter.

    Skip Options

    SettingTypeDefaultDescription
    SkipRoutineNamesarray[]Routine names to skip (without schema).
    SkipFunctionNamesarray[]Generated function names to skip (without schema).
    SkipPathsarray[]URL paths to skip.
    SkipSchemasarray[]Schema names to skip.

    Export Options

    SettingTypeDefaultDescription
    ExportUrlsboolfalseExport URLs as constants.
    ExportEventSourcesbooltrueExport EventSource create functions for streaming events.
    UseRoutineNameInsteadOfEndpointboolfalseUse routine name instead of endpoint name for functions.

    Headers and Security

    SettingTypeDefaultDescription
    CustomHeadersobject{}Custom headers added to each request.
    XsrfTokenHeaderNamestringnullXSRF token header name for anti-forgery (used in upload FORM POSTs).

    File Headers

    SettingTypeDefaultDescription
    HeaderLinesarray["// autogenerated at {0}", ""]Header lines for generated files. {0} = timestamp.

    What Gets Generated

    This section walks through what the generated TypeScript actually looks like for each setting that affects output shape. All examples below are taken verbatim from real projects.

    Default Function Shape

    For a PostgreSQL function like:

    sql
    sql
    create function public.who_am_i(
    +    _user_id text default null,
    +    _username text default null,
    +    _email text default null
    +) returns table(user_id text, username text, email text)
    +language sql security definer as $$
    +  select $1, $2, $3;
    +$$;
    +
    +comment on function public.who_am_i is 'HTTP GET
    +@authorize
    +@user_parameters';

    Equivalent as a SQL file endpoint (sql/who-am-i.sql):

    sql
    sql
    /*
    +HTTP GET
    +@authorize
    +@user_parameters
    +@param $1 user_id text
    +@param $2 username text
    +@param $3 email text
    +*/
    +select $1 as user_id, $2 as username, $3 as email;

    The TypeScript client generator treats both sources identically — the same IWhoAmIRequest / IWhoAmIResponse shapes and whoAmI() function are produced regardless of whether the endpoint is a function or a SQL file.

    The generated TypeScript client looks like this:

    typescript
    typescript
    interface IWhoAmIRequest {
    +    userId?: string | null;
    +    username?: string | null;
    +    email?: string | null;
    +}
    +
    +interface IWhoAmIResponse {
    +    userId: string | null;
    +    username: string | null;
    +    email: string | null;
    +}
    +
    +export async function whoAmI(
    +    request: IWhoAmIRequest
    +) : Promise<{
    +    status: number,
    +    response: IWhoAmIResponse,
    +    error: {status: number; title: string; detail?: string | null} | undefined
    +}> {
    +    const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
    +        method: "GET"
    +    });
    +    return {
    +        status: response.status,
    +        response: response.ok ? await response.json() as IWhoAmIResponse : undefined!,
    +        error: !response.ok && response.headers.get("content-length") !== "0"
    +            ? await response.json() as {status: number; title: string; detail?: string | null}
    +            : undefined
    +    };
    +}

    PostgreSQL parameter names (snake_case) are converted to camelCase. Optional parameters (those with DEFAULT) become ? properties. The IncludeStatusCode setting (default true) wraps every response in { status, response, error } — this is what makes error handling consistent across every call.

    IncludeStatusCode: false — Direct Response

    Set IncludeStatusCode: false to skip the wrapper:

    typescript
    typescript
    export async function whoAmI(request: IWhoAmIRequest): Promise<IWhoAmIResponse> {
    +    const response = await fetch(baseUrl + "/api/who-am-i" + parseQuery(request), {
    +        method: "GET"
    +    });
    +    return await response.json() as IWhoAmIResponse;
    +}

    Errors throw or surface as runtime exceptions instead of being part of the return type. Use this if you have application-level error handling middleware.

    OmitAutomaticParameters: true

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the HTTP File and OpenAPI generators). Default is false, so generated output is unchanged unless you opt in.

    Some parameters are filled by the server, so a value passed from the client would simply be ignored — emitting them as settable request properties is misleading. When true, such a parameter is dropped from the generated request interface, the query string, and the body when it is automatic and optional. "Automatic" covers:

    For a function whose only client-settable parameter is query, with an HTTP Custom Type field responseBody filled server-side:

    typescript
    typescript
    // OmitAutomaticParameters: false (default) — responseBody appears even though the server overrides it
    +interface ISearchRequest {
    +    query?: string | null;
    +    responseBody?: string | null;
    +}
    +
    +// OmitAutomaticParameters: true — only the real input remains
    +interface ISearchRequest {
    +    query?: string | null;
    +}

    When every parameter is automatic, the request shape collapses entirely — the generated function takes no request argument:

    typescript
    typescript
    export async function ping(): Promise<{ status: number; response: IPingResponse; /* ... */ }> {
    +    const response = await fetch(baseUrl + "/api/ping", { method: "GET" });
    +    // ...
    +}

    CreateSeparateTypeFile: true — Type-Only Files

    When true (default), interfaces are emitted into a sibling .d.ts file:

    text
    text
    src/api/userApi.ts        ← functions
    +src/api/userApiTypes.d.ts ← interfaces (type-only)

    The .d.ts file is pure type declarations:

    typescript
    typescript
    //
    +// autogenerated file - do not edit
    +//
    +interface IWhoAmIRequest {
    +    userId?: string | null;
    +    username?: string | null;
    +    email?: string | null;
    +}
    +
    +interface IWhoAmIResponse {
    +    user_id: string | null;
    +    username: string | null;
    +    email: string | null;
    +}

    Set CreateSeparateTypeFile: false to emit interfaces inline in the same file as the functions.

    ExportTypes: true — Importable Interfaces

    By default, interfaces are emitted as plain interface declarations. That makes them module-private when inlined (CreateSeparateTypeFile: false) and ambient/global when in the separate .d.ts file — in neither case can another module import them. Set ExportTypes: true to emit them as export interface so they can be imported.

    Inline (CreateSeparateTypeFile: false) — interfaces and functions share one file, with the interfaces now exported:

    typescript
    typescript
    export interface ISearchProductsRequest {
    +    query?: string | null;
    +    maxPrice?: number | null;
    +}
    +
    +export interface ISearchProductsResponse {
    +    id: number | null;
    +    name: string | null;
    +    price: number | null;
    +}
    +
    +export async function searchProducts(
    +    request: ISearchProductsRequest
    +) : Promise<ApiResult<ISearchProductsResponse[]>> {
    +    // ...
    +}

    Separate file (CreateSeparateTypeFile: true) — the type file becomes an importable module {name}Types.ts (not an ambient {name}Types.d.ts), and the client file imports the named types from it:

    text
    text
    src/api/searchProducts.ts        ← functions + `import type { ... } from "./searchProductsTypes"`
    +src/api/searchProductsTypes.ts   ← `export interface ...`
    typescript
    typescript
    // searchProducts.ts
    +import type { ISearchProductsRequest, ISearchProductsResponse } from "./searchProductsTypes";
    +const baseUrl = "";
    +// ...

    ExportTypes has no effect when SkipTypes is true (no types are generated). Defaulting to false keeps existing output unchanged.

    ExportUrls: true — URL Constants

    When enabled, a URL builder for each endpoint is exported:

    typescript
    typescript
    export const cancelComputeUrl = () => baseUrl + "/api/cancel-compute";
    +export const computeVisualizationUrl = (request: IComputeVisualizationRequest) =>
    +    baseUrl + "/api/compute-visualization" + parseQuery(request);

    Useful when you need to construct a URL but don't want to make the request immediately — for <a href> links, <form action> attributes, or passing to a third-party library.

    ExportEventSources: true — SSE Helpers

    For endpoints with the @sse annotation, an EventSource constructor is exported:

    typescript
    typescript
    export const createComputeVisualizationEventSource = (id: string = "") =>
    +    new EventSource(baseUrl + "/api/compute-visualization/info?" + id);

    The optional id parameter scopes the event stream to a specific execution. See the SSE annotation for usage.

    ImportBaseUrlFrom & ImportParseQueryFrom

    By default, generated files include their own baseUrl constant and parseQuery helper. To share these across files, point them to a module that exports them:

    jsonc
    jsonc
    {
    +  "ImportBaseUrlFrom": "$lib/urls",
    +  "ImportParseQueryFrom": "$lib/urls"
    +}

    Generated files now import instead of inlining:

    typescript
    typescript
    //
    +// autogenerated file - do not edit
    +//
    +import { baseUrl } from "$lib/urls";
    +import { parseQuery } from "$lib/urls";

    Where $lib/urls.ts is a file you maintain:

    typescript
    typescript
    export const baseUrl = import.meta.env.VITE_API_BASE_URL ?? "";
    +
    +export const parseQuery = (query: Record<string, any>) => "?" + Object.keys(query ?? {})
    +    .map(key => {
    +        const value = query[key] ?? "";
    +        if (Array.isArray(value)) {
    +            return value.map(s => s ? `${key}=${encodeURIComponent(s)}` : `${key}=`).join("&");
    +        }
    +        return `${key}=${encodeURIComponent(value as string)}`;
    +    })
    +    .join("&");

    This is the recommended pattern for SvelteKit / Next.js / Vite apps where baseUrl should come from environment variables.

    Path Parameters

    When endpoints use path parameters (e.g., @path /products/{p_id}), template literals are used in URLs:

    typescript
    typescript
    export async function getProduct(request: { pId: number }) {
    +    const response = await fetch(`${baseUrl}/products/${request.pId}`, {
    +        method: "GET"
    +    });
    +    return { status: response.status, response: await response.json() };
    +}

    parseQuery is only emitted when at least one endpoint has actual query-string parameters. Endpoints with only path parameters skip the helper entirely.

    UseRoutineNameInsteadOfEndpoint: true

    By default, function names come from the URL path (kebab-case → camelCase): /api/who-am-iwhoAmI().

    With UseRoutineNameInsteadOfEndpoint: true, function names come from the PostgreSQL routine name instead: public.who_am_iwhoAmI().

    Useful when you customize URL paths via @path annotations but want function names that still match the SQL routine names. Combines well with IncludeSchemaInNames: false to drop schema prefixes from generated names.

    BySchema: true — One File Per Schema

    Default behavior. With FilePath: "./src/api/{0}Api.ts", the {0} placeholder is replaced with each schema name:

    text
    text
    src/api/publicApi.ts       ← from public schema
    +src/api/publicApiTypes.d.ts
    +src/api/billingApi.ts      ← from billing schema
    +src/api/billingApiTypes.d.ts

    Set BySchema: false and use a fixed filename (no {0}) to emit a single combined file.

    Example Configurations

    Minimal (Examples Repo Style)

    The simplest setup — one file per schema, everything else default:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/{0}Api.ts"
    +    }
    +  }
    +}

    This is what every example in the examples repository uses.

    Single JavaScript File (No Types)

    Use this when you don't want TypeScript:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/api/client.js",
    +      "BySchema": false,
    +      "SkipTypes": true,
    +      "IncludeSchemaInNames": false
    +    }
    +  }
    +}

    SkipTypes: true removes all TypeScript syntax (interfaces, type annotations) so the file is valid JavaScript despite the .ts.js extension.

    Production SvelteKit / Vite Setup

    Real-world configuration with shared baseUrl/parseQuery from a $lib alias, URL constants for use in templates, and routine-name-based function naming:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/app/api/{0}Api.ts",
    +      "FileOverwrite": true,
    +      "IncludeHost": true,
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "BySchema": true,
    +      "IncludeStatusCode": true,
    +      "CreateSeparateTypeFile": true,
    +      "ImportBaseUrlFrom": "$lib/urls",
    +      "ImportParseQueryFrom": "$lib/urls",
    +      "DefaultJsonType": "string",
    +      "UseRoutineNameInsteadOfEndpoint": true,
    +      "ExportUrls": true,
    +      "ExportEventSources": true,
    +      "IncludeSchemaInNames": false,
    +      "HeaderLines": [
    +        "//",
    +        "// autogenerated file - do not edit",
    +        "//"
    +      ]
    +    }
    +  }
    +}

    What this gives you:

    • One *Api.ts + one *ApiTypes.d.ts file per schema in ./src/app/api/
    • Generated files import baseUrl and parseQuery from $lib/urls (your own module)
    • JSON PostgreSQL columns typed as string instead of any — explicit casting at the call site
    • Function names match SQL routine names (good for grep / refactoring across SQL and TS)
    • URL builder constants exported (computeUrl(), loginUrl(), etc.) for use in <a href>, forms, and library integrations
    • EventSource factory functions for any @sse endpoints
    • Schema name dropped from interface names (ICancelComputeRequest, not IMathmoduleCancelComputeRequest)

    With Custom Headers and Imports

    For projects that need to add custom headers to every request or import external utilities into the generated files:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "ClientCodeGen": {
    +      "Enabled": true,
    +      "FilePath": "./src/api/{0}Api.ts",
    +      "ImportBaseUrlFrom": "@/config",
    +      "ImportParseQueryFrom": "@/utils/query",
    +      "CustomImports": [
    +        "import { handleError } from '@/utils/errors';"
    +      ],
    +      "CustomHeaders": {
    +        "X-Client-Version": "\"1.0.0\"",
    +        "X-Client-Platform": "\"web\""
    +      },
    +      "XsrfTokenHeaderName": "X-XSRF-TOKEN"
    +    }
    +  }
    +}

    Note the CustomHeaders value syntax — values are emitted as TypeScript expressions, so a literal string requires escaped quotes ("\"1.0.0\""). To use a dynamic value, write a JS expression: "() => localStorage.getItem('app-version')".

    Next Steps

    See Also

    • TSCLIENT - Per-endpoint TypeScript client control

    Comments

    + + + + \ No newline at end of file diff --git a/config/command-retry.html b/config/command-retry.html new file mode 100644 index 000000000..82399c20e --- /dev/null +++ b/config/command-retry.html @@ -0,0 +1,103 @@ + + + + + + Command Retry Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Command Retry

    Command retry strategies and options for handling transient database errors.

    Overview

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": [
    +          "40001", "40P01",
    +          "08000", "08003", "08006", "08001", "08004", "08007", "08P01",
    +          "53000", "53100", "53200", "53300", "53400",
    +          "57P01", "57P02", "57P03", "58000", "58030",
    +          "55P03", "55006", "55000"
    +        ]
    +      }
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable command retry functionality.
    DefaultStrategystring"default"Name of the default retry strategy to use when no strategy is specified.
    Strategiesobject(see below)Named retry strategies with their configurations.

    Strategies can be assigned to endpoints using the retry_strategy annotation.

    Strategy Settings

    Each strategy has the following settings:

    SettingTypeDefaultDescription
    RetrySequenceSecondsarray[0, 1, 2, 5, 10]Retry delays in seconds. Array length determines maximum retries.
    ErrorCodesarray(see below)PostgreSQL error codes that trigger retries.

    Retry Sequence

    The RetrySequenceSeconds array defines delay between retries:

    json
    json
    {
    +  "RetrySequenceSeconds": [0, 1, 2, 5, 10]
    +}
    • First retry: immediate (0 seconds)
    • Second retry: after 1 second
    • Third retry: after 2 seconds
    • Fourth retry: after 5 seconds
    • Fifth retry: after 10 seconds

    Accepts decimal values (e.g., 0.25 for 250ms, 0.5 for 500ms).

    Default Error Codes

    The default strategy retries on these PostgreSQL error codes:

    Serialization Failures

    CodeNameDescription
    40001serialization_failureMust retry for correctness
    40P01deadlock_detectedDeadlock resolved by aborting transaction

    Connection Issues (Class 08)

    CodeName
    08000connection_exception
    08003connection_does_not_exist
    08006connection_failure
    08001sqlclient_unable_to_establish_sqlconnection
    08004sqlserver_rejected_establishment_of_sqlconnection
    08007transaction_resolution_unknown
    08P01protocol_violation

    Resource Constraints (Class 53)

    CodeName
    53000insufficient_resources
    53100disk_full
    53200out_of_memory
    53300too_many_connections
    53400configuration_limit_exceeded

    System Errors (Class 57/58)

    CodeName
    57P01admin_shutdown
    57P02crash_shutdown
    57P03cannot_connect_now
    58000system_error
    58030io_error

    Lock Acquisition Issues (Class 55)

    CodeName
    55P03lock_not_available
    55006object_in_use
    55000object_not_in_prerequisite_state

    See PostgreSQL Error Codes for the complete list.

    Multiple Strategies

    Define multiple strategies for different use cases:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006"]
    +      },
    +      "aggressive": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
    +        "ErrorCodes": ["40001", "40P01", "08000", "08003", "08006", "53300", "57P03"]
    +      },
    +      "minimal": {
    +        "RetrySequenceSeconds": [0, 1],
    +        "ErrorCodes": ["40001", "40P01"]
    +      }
    +    }
    +  }
    +}

    Example Configuration

    Production configuration with extended retries:

    json
    json
    {
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        "RetrySequenceSeconds": [0, 0.5, 1, 2, 5, 10, 30],
    +        "ErrorCodes": [
    +          "40001", "40P01",
    +          "08000", "08003", "08006", "08001", "08004",
    +          "53300", "57P03"
    +        ]
    +      }
    +    }
    +  }
    +}

    Using Strategies in Annotations

    Assign strategies to specific endpoints using the retry_strategy annotation:

    sql
    sql
    -- Use aggressive retry for critical operations
    +comment on function process_payment() is
    +'HTTP POST
    +@retry aggressive';
    +
    +-- Use minimal retry for fast queries
    +comment on function quick_lookup() is
    +'HTTP GET
    +@retry minimal';
    +
    +-- Use default strategy explicitly
    +comment on function standard_operation() is
    +'HTTP POST
    +@retry_strategy default';

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/config-section.html b/config/config-section.html new file mode 100644 index 000000000..9a2851598 --- /dev/null +++ b/config/config-section.html @@ -0,0 +1,68 @@ + + + + + + Config Section | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Config Section

    The Config section controls how the configuration file itself is processed.

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": null,
    +    "ValidateConfigKeys": "Warning"
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    AddEnvironmentVariablesboolfalseAllow environment variables to override configuration settings.
    ParseEnvironmentVariablesbooltrueParse {ENV_VAR_NAME} (optional) and {!ENV_VAR_NAME} (required) placeholders in config values and replace with environment variable values. See below.
    EnvFilestringnullPath to a .env file for loading environment variables. See below.
    ValidateConfigKeysstring"Warning"Validate configuration keys against known defaults at startup. See below.

    Placeholder Forms: Optional and Required (3.17.0+)

    With ParseEnvironmentVariables enabled, config values support two placeholder forms, for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not set — typed reads (bool, int, …) fall back to their defaults instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.
    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)
    +"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Environment Variable Override

    When AddEnvironmentVariables is true, environment variables can override any configuration setting. Use double underscores for nested keys:

    bash
    bash
    # Override ConnectionStrings.Default
    +export ConnectionStrings__Default="Host=production-server;..."
    +
    +# Override NpgsqlRest.UrlPathPrefix
    +export NpgsqlRest__UrlPathPrefix="/v2/api"

    Environment Variable Parsing

    When ParseEnvironmentVariables is true (default), you can use {ENV_VAR} syntax anywhere in configuration values:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  }
    +}

    This allows sensitive values to be kept in environment variables rather than in the configuration file.

    Loading from .env File

    When AddEnvironmentVariables or ParseEnvironmentVariables is true and EnvFile is set, the application will load environment variables from the specified file:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": ".env"
    +  }
    +}

    The .env file format supports:

    • KEY=VALUE pairs (one per line)
    • Comments (lines starting with #)
    • Quoted values (both single and double quotes)

    Example .env file:

    code
    # Database connection settings
    +PGHOST=localhost
    +PGPORT=5432
    +PGDATABASE=example_db
    +PGUSER=postgres
    +PGPASSWORD=postgres

    The variables are loaded into the environment and made available for configuration parsing with the {ENV_VAR_NAME} syntax.

    Configuration Key Validation

    New in 3.8.0

    Configuration key validation was added in version 3.8.0.

    At startup, NpgsqlRest can validate all configuration keys in appsettings.json against the known defaults schema. This catches typos and unknown keys that would otherwise be silently ignored (e.g., LogCommand instead of LogCommands).

    The ValidateConfigKeys setting has three modes:

    ModeBehavior
    "Warning" (default)Logs warnings for unknown keys, startup continues.
    "Error"Logs errors for unknown keys and exits the application.
    "Ignore"No validation is performed.
    json
    json
    {
    +  "Config": {
    +    "ValidateConfigKeys": "Warning"
    +  }
    +}

    Example output:

    code
    [12:34:56 WRN] Unknown configuration key: NpgsqlRest:KebabCaselUrls

    Validation also covers the Kestrel section, checking against the known Kestrel schema including Limits, Http2, Http3, and top-level flags like DisableStringReuse and AllowSynchronousIO. User-defined endpoint and certificate names under Endpoints and Certificates remain open-ended and won't trigger warnings.

    TIP

    Use the --config CLI switch to inspect the current configuration with syntax highlighting, or --validate for a pre-flight check of configuration and database connectivity.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/connection.html b/config/connection.html new file mode 100644 index 000000000..30c24a402 --- /dev/null +++ b/config/connection.html @@ -0,0 +1,149 @@ + + + + + + Connection Settings | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Connection Settings

    This page covers all database connection configuration in NpgsqlRest, including connection strings, connection behavior settings, and NpgsqlRest-specific connection options.

    Connection Strings

    The ConnectionStrings section defines named database connections. The first available connection is used automatically when no specific connection is specified.

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=myuser;Password=mypassword"
    +  }
    +}

    Multiple Connections

    You can define multiple named connections for different purposes (e.g., read replicas, different databases):

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.example.com;Database=mydb;Username=app;Password=secret",
    +    "ReadReplica": "Host=replica.example.com;Database=mydb;Username=app;Password=secret",
    +    "Analytics": "Host=analytics.example.com;Database=analytics;Username=report;Password=secret"
    +  }
    +}

    Using Environment Variables

    Connection strings support environment variable placeholders when ParseEnvironmentVariables is enabled (default):

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  }
    +}

    This is the recommended approach for production deployments to keep credentials out of configuration files.

    Connection String Parameters

    Common PostgreSQL connection string parameters:

    ParameterDescriptionExample
    HostServer hostname or IPlocalhost, db.example.com
    PortServer port5432
    DatabaseDatabase namemydb
    UsernameLogin usernamemyuser
    PasswordLogin passwordmypassword
    SSL ModeSSL connection modeRequire, Prefer, Disable
    PoolingEnable connection poolingtrue, false
    Minimum Pool SizeMinimum connections in pool0
    Maximum Pool SizeMaximum connections in pool100
    Connection Idle LifetimeSeconds before idle connection is closed300
    Connection LifetimeMaximum connection lifetime in seconds0 (unlimited)
    TimeoutConnection timeout in seconds15

    For a complete list, see the Npgsql Connection String Parameters.

    Connection Settings

    The ConnectionSettings section controls connection behavior, testing, and retry logic.

    json
    json
    {
    +  "ConnectionSettings": {
    +    "SetApplicationNameInConnection": true,
    +    "UseJsonApplicationName": false,
    +    "TestConnectionStrings": true,
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    },
    +    "MetadataQueryConnectionName": null,
    +    "MetadataQuerySchema": null,
    +    "MultiHostConnectionTargets": {
    +      "Default": "Any",
    +      "ByConnectionName": {}
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    SetApplicationNameInConnectionbooltrueSets the ApplicationName connection property to the configured application name.
    UseJsonApplicationNameboolfalseDynamically sets a JSON-formatted application name per request (see below). Note: Limited to 64 characters.
    TestConnectionStringsbooltrueValidates each connection by opening and closing it during startup.
    MetadataQueryConnectionNamestringnullConnection name used for metadata queries. Uses default connection if null.
    MetadataQuerySchemastringnullSet the search path to this schema before executing the metadata query function. When null (default), no search path is set and the server's default search path is used. Useful when using non-superuser roles with limited schema access.
    MultiHostConnectionTargetsobject(see below)Configuration for multi-host connection failover and load balancing.

    Application Name in Connection

    When SetApplicationNameInConnection is true, the configured ApplicationName is included in the database connection. This helps identify connections in PostgreSQL monitoring tools like pg_stat_activity.

    JSON Application Name

    When UseJsonApplicationName is true, the ApplicationName connection property is set dynamically on every request in the following JSON format:

    json
    json
    {"app": "MyApi", "uid": "user123", "id": "abc-123"}
    FieldDescription
    appApplication name from configuration
    uidUser ID for authenticated users, or null for anonymous requests
    idValue of the execution request header, or null if not provided

    The execution request header name can be configured in the NpgsqlRest section underExecutionIdHeaderName (default is X-NpgsqlRest-ID). See NpgsqlRest Request Headers for details.

    This provides detailed per-request tracking in PostgreSQL's pg_stat_activity.

    WARNING

    The ApplicationName connection property is limited to 64 characters. Longer values will be truncated.

    Connection Testing

    When TestConnectionStrings is true (default), NpgsqlRest validates all configured connections at startup by opening and closing each one. This ensures:

    • Connection strings are valid
    • Database servers are reachable
    • Credentials are correct

    If any connection fails, the application will not start.

    Retry Options

    The RetryOptions section configures automatic retry behavior for transient connection failures.

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    }
    +  }
    +}

    Retry Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable automatic retry for connection failures.
    RetrySequenceSecondsnumber[][1, 3, 6, 12]Wait intervals (in seconds) between retry attempts. Supports decimals like 0.25.
    ErrorCodesstring[](see below)PostgreSQL error codes that trigger automatic retries.

    Default Error Codes

    The default error codes cover common transient failures:

    CodeClassDescription
    08000Connection ExceptionGeneral connection error
    08001SQL Client Unable to Establish ConnectionClient cannot connect
    08003Connection Does Not ExistConnection lost
    08004SQL Server Rejected ConnectionServer rejected connection
    08006Connection FailureConnection failed
    55006Object In UseDatabase object is in use
    55P03Lock Not AvailableCannot acquire lock
    53300Too Many ConnectionsConnection limit reached
    57P03Cannot Connect NowServer starting up
    40001Serialization FailureTransaction serialization conflict

    Custom Retry Configuration

    For high-availability scenarios, you might want more aggressive retries:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 4, 8, 16, 32],
    +      "ErrorCodes": ["08000", "08003", "08006", "57P03"]
    +    }
    +  }
    +}

    Multi-Host Connection Support

    NpgsqlRest supports PostgreSQL multi-host connections with failover and load balancing capabilities using Npgsql's NpgsqlMultiHostDataSource.

    Multi-Host Connection Strings

    Connection strings with comma-separated hosts are automatically detected as multi-host connections:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
    +  }
    +}

    Target Session Attributes

    Configure which server type to target for each connection:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "Any",
    +      "ByConnectionName": {
    +        "readonly": "Standby",
    +        "primary": "Primary"
    +      }
    +    }
    +  }
    +}
    ValueDescription
    AnyAny successful connection is acceptable (default)
    PrimaryServer must not be in hot standby mode
    StandbyServer must be in hot standby mode
    PreferPrimaryTry primary first, fall back to any
    PreferStandbyTry standby first, fall back to any
    ReadWriteSession must accept read-write transactions
    ReadOnlySession must not accept read-write transactions

    See Npgsql Failover and Load Balancing for more details.

    Multi-Host Example

    Complete configuration for a primary-replica setup:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret",
    +    "ReadOnly": "Host=replica1.db.com,replica2.db.com,primary.db.com;Database=mydb;Username=app;Password=secret"
    +  },
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      "Default": "PreferPrimary",
    +      "ByConnectionName": {
    +        "ReadOnly": "PreferStandby"
    +      }
    +    }
    +  }
    +}

    NpgsqlRest Connection Options

    The NpgsqlRest section contains additional connection-related settings that control how routines interact with database connections.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false
    +  }
    +}

    NpgsqlRest Connection Settings Reference

    SettingTypeDefaultDescription
    ConnectionNamestringnullConnection name from ConnectionStrings to use. Uses first available if null.
    UseMultipleConnectionsboolfalseAllow individual routines to specify alternative connections.

    Using Multiple Connections

    When UseMultipleConnections is true, individual PostgreSQL routines can specify which connection to use via comments. This is useful for:

    • Read replicas: Route read-only queries to replicas
    • Sharding: Route queries to different database shards
    • Resource isolation: Separate heavy analytics queries from transactional workloads

    Example PostgreSQL function using a specific connection:

    sql
    sql
    create function get_report_data()
    +returns table(...)
    +language sql
    +begin atomic;
    +  select * from large_table;
    +end;
    +
    +comment on function get_report_data() is '
    +HTTP GET /reports/data
    +@connection ReadReplica
    +';

    Equivalent as a SQL file endpoint (sql/get-report-data.sql):

    sql
    sql
    /*
    +HTTP GET /reports/data
    +@connection ReadReplica
    +*/
    +select * from large_table;

    Complete Example

    Here's a complete connection configuration for a production environment:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD};SSL Mode=Require;Pooling=true;Maximum Pool Size=100",
    +    "ReadReplica": "Host={PGHOST_REPLICA};Port={PGPORT};Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD};SSL Mode=Require;Pooling=true;Maximum Pool Size=50"
    +  },
    +  "ConnectionSettings": {
    +    "SetApplicationNameInConnection": true,
    +    "UseJsonApplicationName": false,
    +    "TestConnectionStrings": true,
    +    "RetryOptions": {
    +      "Enabled": true,
    +      "RetrySequenceSeconds": [0.5, 1, 2, 5, 10],
    +      "ErrorCodes": ["08000", "08003", "08006", "08001", "08004", "55P03", "55006", "53300", "57P03", "40001"]
    +    }
    +  },
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": true
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/cors.html b/config/cors.html new file mode 100644 index 000000000..36dfec4fb --- /dev/null +++ b/config/cors.html @@ -0,0 +1,89 @@ + + + + + + CORS Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    CORS

    Cross-Origin Resource Sharing (CORS) configuration for controlling access from different origins.

    Overview

    json
    json
    {
    +  "Cors": {
    +    "Enabled": false,
    +    "AllowedOrigins": [],
    +    "AllowedMethods": ["*"],
    +    "AllowedHeaders": ["*"],
    +    "AllowCredentials": false,
    +    "PreflightMaxAgeSeconds": 600
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Cross-Origin Resource Sharing (CORS) support.
    AllowedOriginsarray[]List of allowed origins for CORS requests. Empty array allows no origins.
    AllowedMethodsarray["*"]List of allowed HTTP methods for CORS requests.
    AllowedHeadersarray["*"]List of allowed headers for CORS requests.
    AllowCredentialsboolfalseAllow credentials (cookies, authorization headers) in CORS requests. Disabled by default (changed in 3.17.0); enable deliberately and only together with an explicit AllowedOrigins list.
    PreflightMaxAgeSecondsint600Maximum age in seconds for preflight request caching (10 minutes).

    Allowed Origins

    Specify which origins can make cross-origin requests:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": [
    +      "https://example.com",
    +      "https://app.example.com"
    +    ]
    +  }
    +}

    WARNING

    An empty AllowedOrigins array allows no origins. You must specify at least one origin when CORS is enabled.

    Allow All Origins

    To allow requests from any origin (not recommended for production with credentials):

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["*"],
    +    "AllowCredentials": false
    +  }
    +}

    DANGER

    Using "*" for origins with AllowCredentials: true is not allowed by browsers and will cause CORS errors.

    Allowed Methods

    Specify which HTTP methods are permitted:

    json
    json
    {
    +  "Cors": {
    +    "AllowedMethods": ["GET", "POST", "PUT", "DELETE"]
    +  }
    +}

    Use ["*"] to allow all methods.

    Allowed Headers

    Specify which request headers are permitted:

    json
    json
    {
    +  "Cors": {
    +    "AllowedHeaders": ["Content-Type", "Authorization", "X-Requested-With"]
    +  }
    +}

    Use ["*"] to allow all headers.

    Credentials

    When AllowCredentials is true, the browser includes cookies and authorization headers in cross-origin requests. This requires specific origins (not "*").

    Default changed in 3.17.0

    AllowCredentials now defaults to false. Credentials in cross-origin requests must be enabled deliberately, and only together with an explicit AllowedOrigins list. If you relied on the old default, set "AllowCredentials": true explicitly.

    Preflight Caching

    The PreflightMaxAgeSeconds setting controls how long browsers cache preflight (OPTIONS) request responses. Higher values reduce preflight requests but delay CORS policy changes from taking effect.

    Example Configuration

    Production configuration with specific origins:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": [
    +      "https://myapp.com",
    +      "https://admin.myapp.com"
    +    ],
    +    "AllowedMethods": ["GET", "POST", "PUT", "DELETE"],
    +    "AllowedHeaders": ["Content-Type", "Authorization"],
    +    "AllowCredentials": true,
    +    "PreflightMaxAgeSeconds": 3600
    +  }
    +}

    Development configuration allowing all origins:

    json
    json
    {
    +  "Cors": {
    +    "Enabled": true,
    +    "AllowedOrigins": ["*"],
    +    "AllowedMethods": ["*"],
    +    "AllowedHeaders": ["*"],
    +    "AllowCredentials": false,
    +    "PreflightMaxAgeSeconds": 600
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/data-protection.html b/config/data-protection.html new file mode 100644 index 000000000..f0be6f4f7 --- /dev/null +++ b/config/data-protection.html @@ -0,0 +1,166 @@ + + + + + + Data Protection Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Data Protection

    Data protection settings control encryption and decryption for authentication cookies, antiforgery tokens, and application-level column encryption via the encrypt/decrypt annotations.

    Overview

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": false,
    +    "CustomApplicationName": null,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "Default",
    +    "FileSystemPath": "./data-protection-keys",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    "EncryptionAlgorithm": null,
    +    "ValidationAlgorithm": null,
    +    "KeyEncryption": "None",
    +    "CertificatePath": null,
    +    "CertificatePassword": null,
    +    "DpapiLocalMachine": false
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable data protection. Required when using Cookie Authentication, Antiforgery tokens, or @encrypt/@decrypt annotations.
    CustomApplicationNamestringnullApplication name for encryption scope. Uses ApplicationName if null. Different names cannot decrypt each other's data.
    DefaultKeyLifetimeDaysint90Number of days before keys are rotated.
    Storagestring"Default"Key storage location: "Default", "FileSystem", or "Database".
    FileSystemPathstring"./data-protection-keys"Path for file system storage.
    GetAllElementsCommandstring"select get_data_protection_keys()"Database command to retrieve all keys. No parameters. Must return a set of rows with a single text column containing the key data.
    StoreElementCommandstring"call store_data_protection_keys($1,$2)"Database command to store a key. Receives two text parameters: $1 is the element name, $2 is the element data. Does not return anything.
    EncryptionAlgorithmstringnullEncryption algorithm. Uses default if null.
    ValidationAlgorithmstringnullValidation algorithm. Uses default if null.
    KeyEncryptionstring"None"Key encryption method: "None", "Certificate", or "Dpapi" (Windows only).
    CertificatePathstringnullPath to X.509 certificate file (.pfx) when using Certificate encryption.
    CertificatePasswordstringnullPassword for the certificate file (can be null for passwordless certificates).
    DpapiLocalMachineboolfalseWhen using DPAPI, set to true to protect keys to the local machine instead of current user.

    Storage Options

    Default Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "Default"
    +  }
    +}

    Uses the platform's default key storage location.

    Linux Users

    On Linux, Default storage does not persist keys. When keys are lost on restart, encrypted tokens (authentication cookies) will stop working. Linux deployments should use FileSystem or Database storage.

    File System Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/var/lib/npgsqlrest/keys"
    +  }
    +}

    Stores keys in the specified directory.

    Docker

    When running in Docker, ensure FileSystemPath points to a Docker volume to persist keys across container restarts.

    Database Storage

    json
    json
    {
    +  "DataProtection": {
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)"
    +  }
    +}

    Stores keys in the PostgreSQL database using custom functions. You must create the backing table, function, and procedure yourself. The two commands work as follows:

    • GetAllElementsCommand: Retrieves all stored keys. Takes no parameters. Must return a set of rows with a single text column containing the key data.
    • StoreElementCommand: Stores a single key. Receives two text parameters: $1 is the element name (unique identifier), $2 is the element data (XML key content). Does not return anything.

    Example SQL setup:

    sql
    sql
    create table data_protection_keys (
    +  name text not null primary key,
    +  data text not null
    +);
    +
    +create function get_data_protection_keys()
    +returns setof text
    +security definer
    +language sql
    +begin atomic;
    +select data from data_protection_keys;
    +end;
    +
    +create procedure store_data_protection_keys(
    +  _name text,
    +  _data text
    +)
    +security definer
    +language sql
    +begin atomic;
    +insert into data_protection_keys (name, data)
    +values (_name, _data)
    +on conflict (name) do update set data = excluded.data;
    +end;

    Encryption Algorithms

    Configure the encryption algorithm for data protection keys:

    ValueDescription
    nullUse default algorithm
    AES_128_CBCAES 128-bit CBC mode
    AES_192_CBCAES 192-bit CBC mode
    AES_256_CBCAES 256-bit CBC mode
    AES_128_GCMAES 128-bit GCM mode
    AES_192_GCMAES 192-bit GCM mode
    AES_256_GCMAES 256-bit GCM mode
    json
    json
    {
    +  "DataProtection": {
    +    "EncryptionAlgorithm": "AES_256_GCM"
    +  }
    +}

    Validation Algorithms

    Configure the validation algorithm for data protection keys:

    ValueDescription
    nullUse default algorithm
    HMACSHA256HMAC SHA-256
    HMACSHA512HMAC SHA-512
    json
    json
    {
    +  "DataProtection": {
    +    "ValidationAlgorithm": "HMACSHA512"
    +  }
    +}

    Application Name Scope

    The CustomApplicationName determines the encryption scope. Applications with different names cannot decrypt each other's data:

    json
    json
    {
    +  "DataProtection": {
    +    "CustomApplicationName": "my-app-production"
    +  }
    +}

    If null, uses the top-level ApplicationName setting.

    Key Encryption Options

    Data protection keys can be encrypted at rest using X.509 certificates or Windows DPAPI for additional security.

    No Encryption (Default)

    json
    json
    {
    +  "DataProtection": {
    +    "KeyEncryption": "None"
    +  }
    +}

    Keys are stored without additional encryption at rest.

    Certificate Encryption

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "KeyEncryption": "Certificate",
    +    "CertificatePath": "/path/to/cert.pfx",
    +    "CertificatePassword": "${CERT_PASSWORD}"
    +  }
    +}

    Encrypts keys using an X.509 certificate. The certificate must be a .pfx file containing both the public and private key.

    Environment Variables

    Use environment variable substitution for the certificate password to avoid storing secrets in configuration files.

    DPAPI Encryption (Windows Only)

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "./keys",
    +    "KeyEncryption": "Dpapi",
    +    "DpapiLocalMachine": true
    +  }
    +}

    Uses Windows Data Protection API to encrypt keys. Only available on Windows.

    DpapiLocalMachineScope
    false (default)Keys are protected to the current user account
    trueKeys are protected to the local machine (any user on the machine can decrypt)

    Windows Only

    DPAPI encryption is only available on Windows. On other platforms, use Certificate encryption instead.

    Complete Example

    Production configuration with database storage:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "CustomApplicationName": null,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    "EncryptionAlgorithm": "AES_256_GCM",
    +    "ValidationAlgorithm": "HMACSHA512"
    +  }
    +}

    Production configuration with file system storage (Docker):

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "DefaultKeyLifetimeDays": 90,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/app/keys"
    +  }
    +}

    Column Encryption with Annotations

    Data Protection powers the encrypt and decrypt annotations, which provide transparent application-level column encryption. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client.

    sql
    sql
    -- Store with encryption
    +create function store_secret(_key text, _value text) returns void ...
    +comment on function store_secret(text, text) is '
    +HTTP POST
    +encrypt _value
    +';
    +
    +-- Retrieve with decryption
    +create function get_secret(_key text) returns table(key text, value text) ...
    +comment on function get_secret(text) is '
    +decrypt value
    +';

    Equivalent as SQL file endpoints:

    sql
    sql
    -- sql/store-secret.sql
    +/*
    +HTTP POST
    +@encrypt value
    +@param $1 key
    +@param $2 value
    +*/
    +insert into secrets (key, value) values ($1, $2)
    +on conflict (key) do update set value = excluded.value;
    sql
    sql
    -- sql/get-secret.sql
    +/*
    +HTTP GET
    +@decrypt value
    +@param $1 key
    +*/
    +select key, value from secrets where key = $1;

    The database stores ciphertext; the API consumer sees plaintext. This is useful for storing PII (SSN, medical records, credit card numbers) or other sensitive data that must be encrypted at rest but is only ever looked up by an unencrypted key (e.g., user_id).

    Key Persistence Required

    If encryption keys are lost, encrypted data is permanently unrecoverable. Always use FileSystem or Database storage in production — never rely on Default storage on Linux.

    See the ENCRYPT / DECRYPT annotation reference for full syntax, behavior notes, and examples.

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/error-handling.html b/config/error-handling.html new file mode 100644 index 000000000..ce9472495 --- /dev/null +++ b/config/error-handling.html @@ -0,0 +1,124 @@ + + + + + + Error Handling Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Error Handling

    Error handling configuration for mapping PostgreSQL errors to HTTP responses.

    Overview

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": true,
    +    "DefaultErrorCodePolicy": "Default",
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Command execution timed out",
    +      "Details": null,
    +      "Type": null
    +    },
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    RemoveTypeUrlboolfalseRemove Type URL from error responses. Default Type URL points to RFC documentation based on HTTP status code.
    RemoveTraceIdbooltrueRemove TraceId field from error responses. TraceId is useful for correlating logs with errors.
    DefaultErrorCodePolicystring"Default"Name of the default error code policy to use.
    TimeoutErrorMappingobject(see below)Error mapping for command timeout errors.
    ErrorCodePoliciesarray(see below)Named policies for mapping PostgreSQL error codes to HTTP responses. Assign a policy to an endpoint using the error_code_policy annotation.

    Error Mapping Object

    Each error mapping has the following fields:

    FieldTypeDescription
    StatusCodeintHTTP status code to return.
    TitlestringTitle field in response JSON. When null, the actual PostgreSQL error message is used.
    DetailsstringDetails field in response JSON. When null, the PostgreSQL error code is used.
    TypestringURI reference (RFC3986) identifying the problem type. When null, uses default. Set RemoveTypeUrl to true to disable.

    Timeout Error Mapping

    Configure the response when a command timeout occurs:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Command execution timed out",
    +      "Details": null,
    +      "Type": null
    +    }
    +  }
    +}

    This maps command timeouts to HTTP 504 Gateway Timeout. Timeouts occur when a query exceeds:

    • The global NpgsqlRest.CommandTimeout setting, or
    • The per-endpoint command_timeout annotation

    Error Code Policies

    Define named policies for mapping PostgreSQL error codes to HTTP responses:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "DefaultErrorCodePolicy": "Default",
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Default Error Code Mappings

    PostgreSQL CodeNameHTTP StatusDescription
    42501insufficient_privilege403 ForbiddenUser lacks required permissions
    57014query_canceled205 Reset ContentQuery was cancelled
    P0001raise_exception400 Bad RequestExplicit RAISE EXCEPTION in function
    P0004assert_failure400 Bad RequestAssert statement failed

    See PostgreSQL Error Codes for the complete list.

    Response Fields

    Type URL

    When RemoveTypeUrl is false (default), error responses include a Type URL pointing to RFC documentation:

    json
    json
    {
    +  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    +  "title": "Bad Request",
    +  "status": 400
    +}

    TraceId

    When RemoveTraceId is false, error responses include a TraceId for log correlation:

    json
    json
    {
    +  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    +  "title": "Bad Request",
    +  "status": 400,
    +  "traceId": "00-abc123..."
    +}

    Example Configuration

    Production configuration with custom error mappings:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": true,
    +    "DefaultErrorCodePolicy": "Default",
    +    "TimeoutErrorMapping": {
    +      "StatusCode": 504,
    +      "Title": "Request timed out",
    +      "Details": "The database operation took too long to complete.",
    +      "Type": null
    +    },
    +    "ErrorCodePolicies": [
    +      {
    +        "Name": "Default",
    +        "ErrorCodes": {
    +          "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},
    +          "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},
    +          "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},
    +          "23505": {"StatusCode": 409, "Title": "Conflict", "Details": "A record with this key already exists.", "Type": null},
    +          "23503": {"StatusCode": 400, "Title": "Invalid Reference", "Details": "Referenced record does not exist.", "Type": null}
    +        }
    +      }
    +    ]
    +  }
    +}

    Development configuration with TraceId for debugging:

    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    "RemoveTypeUrl": false,
    +    "RemoveTraceId": false,
    +    "DefaultErrorCodePolicy": "Default"
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/external-auth.html b/config/external-auth.html new file mode 100644 index 000000000..12241cbaa --- /dev/null +++ b/config/external-auth.html @@ -0,0 +1,148 @@ + + + + + + External OAuth Authentication | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    External OAuth Authentication

    NpgsqlRest supports OAuth authentication with popular external providers including Google, LinkedIn, GitHub, Microsoft, and Facebook.

    Overview

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "SigninUrl": "/signin-{0}",
    +      "ReturnToPath": "/",
    +      "LoginCommand": "select * from external_login($1,$2,$3,$4,$5)"
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable external OAuth providers.
    BrowserSessionStatusKeystring"__external_status"sessionStorage key for auth status (HTTP status code).
    BrowserSessionMessageKeystring"__external_message"sessionStorage key for auth message.
    SigninUrlstring"/signin-{0}"Sign-in page URL pattern. {0} is replaced with provider name.
    SignInHtmlTemplatestring(see below)HTML template for the sign-in page.
    RedirectUrlstringnullURL to redirect after auth. Usually auto-detected.
    ReturnToPathstring"/"Default path to redirect after auth completes.
    ReturnToPathQueryStringKeystring"return_to"Query string key for dynamic return path.
    LoginCommandstring"select * from external_login($1,$2,$3,$4,$5)"PostgreSQL command to execute after OAuth login. Uses the same result processing logic as the login endpoint.
    ClientAnalyticsDatastring(JavaScript object)Browser analytics data sent to login command.
    ClientAnalyticsIpKeystring"ip"JSON key for client IP in analytics data.

    SignInHtmlTemplate

    HTML template for the sign-in page shown during communication with the OAuth provider. This is typically a simple loading page. You can customize this to show your own loading animation, spinner, or branding. Format placeholders:

    • {0} - Provider name (e.g., "Google", "GitHub")
    • {1} - JavaScript to redirect to the external auth provider

    Default value:

    html
    html
    <!DOCTYPE html>
    +<html>
    +<head>
    +  <meta charset="utf-8" />
    +  <title>Talking To {0}</title>
    +</head>
    +<body>
    +  Loading...
    +  {1}
    +</body>
    +</html>

    Login Command

    The LoginCommand is a PostgreSQL command that executes after successful OAuth authentication. It uses the same result set conventions as the login annotation - column names, special columns (status, scheme, body), and claim handling all work identically.

    For full details on how the result set is processed (return type requirements, special columns, claim types, status codes), see the Login Endpoint Conventions documentation.

    Parameters

    The LoginCommand receives up to five parameters:

    ParameterTypeDescription
    $1textExternal login provider name (e.g., "google", "github")
    $2textUser's email address
    $3textUser's display name
    $4text/json/jsonbRaw JSON data from the OAuth provider
    $5text/json/jsonbBrowser analytics data (screen size, timezone, etc.)

    Result Set Conventions

    The command must return a named record (table). The result is processed using the same rules as login endpoints:

    • Special columns: status, scheme, body control login behavior (see Special Columns)
    • All other columns: Become security claims (column name = claim type, column value = claim value)
    • Empty result: Returns 401 Unauthorized
    • Multiple rows: Only the first row is processed

    Example Login Command Function

    sql
    sql
    create function external_login(
    +    _provider text,
    +    _email text,
    +    _name text,
    +    _data jsonb,
    +    _analytics jsonb
    +)
    +returns table(status boolean, id int, name text, email text, provider text)
    +language plpgsql as $$
    +declare
    +    _user_id int;
    +begin
    +    -- Find or create user
    +    select id into _user_id from users where email = _email;
    +
    +    if _user_id is null then
    +        insert into users (email, name, created_via)
    +        values (_email, _name, _provider)
    +        returning id into _user_id;
    +    end if;
    +
    +    -- Return claims (same format as login endpoint)
    +    return query
    +    select
    +        true as status,
    +        _user_id as id,
    +        _name as name,
    +        _email as email,
    +        _provider as provider;
    +end;
    +$$;

    Equivalent as a SQL file (sql/external-login.sql):

    The login command is referenced from configuration (LoginCommand: "select * from external_login($1,$2,$3,$4,$5)"), so the call site stays the same. The implementation can also be a SQL file:

    sql
    sql
    /*
    +@param $1 provider text
    +@param $2 email text
    +@param $3 name text
    +@param $4 data jsonb
    +@param $5 analytics jsonb
    +*/
    +with upsert as (
    +    insert into users (email, name, created_via)
    +    values ($2, $3, $1)
    +    on conflict (email) do update set name = excluded.name
    +    returning id, name, email
    +)
    +select true as status, id, name, email, $1 as provider from upsert;

    OAuth Providers

    NpgsqlRest includes pre-configured defaults for Google, LinkedIn, GitHub, Microsoft, and Facebook. For these providers, you only need to set ClientId and ClientSecret - all URL settings have sensible defaults.

    Minimal Configuration

    For pre-configured providers, this is all you need:

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      }
    +    }
    +  }
    +}

    The AuthUrl, TokenUrl, InfoUrl, and EmailUrl settings are optional and only needed if:

    • The provider changes their endpoints
    • You need custom OAuth scopes
    • You're defining a custom provider not listed below

    Google

    Configure your app at Google Cloud Console.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={0}&redirect_uri={1}&scope=openid profile email&state={2}
    TokenUrlhttps://oauth2.googleapis.com/token
    InfoUrlhttps://www.googleapis.com/oauth2/v3/userinfo
    EmailUrlnull

    LinkedIn

    Configure your app at LinkedIn Developers.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={0}&redirect_uri={1}&state={2}&scope=r_liteprofile%20r_emailaddress
    TokenUrlhttps://www.linkedin.com/oauth/v2/accessToken
    InfoUrlhttps://api.linkedin.com/v2/me
    EmailUrlhttps://api.linkedin.com/v2/emailAddress?q=members&projection=(elements//(handle~))

    GitHub

    Configure your app at GitHub Developer Settings.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://github.com/login/oauth/authorize?client_id={0}&redirect_uri={1}&state={2}&allow_signup=false
    TokenUrlhttps://github.com/login/oauth/access_token
    InfoUrlhttps://api.github.com/user
    EmailUrlnull

    Microsoft

    Configure your app at Azure Portal. See Microsoft Identity Platform documentation.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id={0}&redirect_uri={1}&scope=openid%20profile%20email&state={2}
    TokenUrlhttps://login.microsoftonline.com/common/oauth2/v2.0/token
    InfoUrlhttps://graph.microsoft.com/oidc/userinfo
    EmailUrlnull

    Facebook

    Configure your app at Facebook Developers. See Facebook Login documentation.

    Default URLs (for reference - you don't need to set these):

    SettingDefault Value
    AuthUrlhttps://www.facebook.com/v20.0/dialog/oauth?response_type=code&client_id={0}&redirect_uri={1}&scope=public_profile%20email&state={2}
    TokenUrlhttps://graph.facebook.com/v20.0/oauth/access_token
    InfoUrlhttps://graph.facebook.com/me?fields=id,name,email
    EmailUrlnull

    Provider Settings Reference

    Each provider has the same configuration options:

    SettingTypeRequiredDescription
    EnabledboolYesEnable this provider.
    ClientIdstringYesOAuth client ID from the provider.
    ClientSecretstringYesOAuth client secret from the provider.
    AuthUrlstringNoAuthorization URL. Has sensible default for pre-configured providers. Placeholders: {0} = client ID, {1} = redirect URI, {2} = state.
    TokenUrlstringNoToken exchange URL. Has sensible default for pre-configured providers.
    InfoUrlstringNoUser info URL. Has sensible default for pre-configured providers.
    EmailUrlstringNoEmail URL (some providers require separate request). Default is null.

    Custom Providers

    You can define custom OAuth providers by specifying all URL settings. Use any key name under External:

    json
    json
    {
    +  "Auth": {
    +    "External": {
    +      "Enabled": true,
    +      "MyCustomProvider": {
    +        "Enabled": true,
    +        "ClientId": "your-client-id",
    +        "ClientSecret": "your-client-secret",
    +        "AuthUrl": "https://auth.example.com/oauth/authorize?response_type=code&client_id={0}&redirect_uri={1}&state={2}",
    +        "TokenUrl": "https://auth.example.com/oauth/token",
    +        "InfoUrl": "https://api.example.com/userinfo",
    +        "EmailUrl": null
    +      }
    +    }
    +  }
    +}

    The sign-in URL will be /signin-mycustomprovider (provider name in lowercase).

    Complete Example

    Configuration with Google and GitHub OAuth:

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieValid": "30 days",
    +
    +    "External": {
    +      "Enabled": true,
    +      "ReturnToPath": "/dashboard",
    +      "LoginCommand": "select * from external_login($1, $2, $3, $4, $5)",
    +
    +      "Google": {
    +        "Enabled": true,
    +        "ClientId": "{GOOGLE_CLIENT_ID}",
    +        "ClientSecret": "{GOOGLE_CLIENT_SECRET}"
    +      },
    +
    +      "GitHub": {
    +        "Enabled": true,
    +        "ClientId": "{GITHUB_CLIENT_ID}",
    +        "ClientSecret": "{GITHUB_CLIENT_SECRET}"
    +      }
    +    }
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/forwarded-headers.html b/config/forwarded-headers.html new file mode 100644 index 000000000..98679aa74 --- /dev/null +++ b/config/forwarded-headers.html @@ -0,0 +1,125 @@ + + + + + + Forwarded Headers | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Forwarded Headers

    New in 3.6.0

    Forwarded Headers middleware was added in version 3.6.0.

    Support for processing proxy headers when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.). This is critical for getting the correct client IP address and protocol.

    Overview

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": false,
    +    "ForwardLimit": 1,
    +    "KnownProxies": [],
    +    "KnownNetworks": [],
    +    "AllowedHosts": []
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable forwarded headers middleware (automatically placed first in the middleware pipeline).
    ForwardLimitint1Limits the number of proxy entries that will be processed from X-Forwarded-For. Set to null to process all entries (not recommended).
    KnownProxiesarray[]List of IP addresses of known proxies to accept forwarded headers from.
    KnownNetworksarray[]List of CIDR network ranges of known proxies.
    AllowedHostsarray[]List of allowed values for the X-Forwarded-Host header.

    Why Forwarded Headers Matter

    When your application runs behind a reverse proxy, the proxy intercepts all incoming requests. Without forwarded headers:

    • Client IP: Your application sees the proxy's IP instead of the real client IP
    • Protocol: Your application sees HTTP even if the client connected via HTTPS
    • Host: Your application sees the proxy's internal hostname instead of the public domain

    This affects:

    • Rate limiting (you'd limit the proxy, not individual clients)
    • Logging and analytics (wrong IPs in logs)
    • HTTPS redirects (infinite redirect loops)
    • Cookie security (secure cookies fail on perceived HTTP)

    Processed Headers

    HeaderPurpose
    X-Forwarded-ForGets real client IP instead of proxy IP
    X-Forwarded-ProtoGets original protocol (http/https)
    X-Forwarded-HostGets original host header

    Forward Limit

    Limits how many proxy entries are processed from the X-Forwarded-For header chain.

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1
    +  }
    +}

    If you have a chain of proxies (e.g., CDN → Load Balancer → Application), increase this value:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "ForwardLimit": 2
    +  }
    +}

    WARNING

    Setting ForwardLimit to null processes all entries, which can be a security risk as attackers can inject fake X-Forwarded-For entries.

    Known Proxies

    Specify exact IP addresses of trusted proxies:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "KnownProxies": ["10.0.0.1", "192.168.1.1"]
    +  }
    +}

    Forwarded headers are only accepted from these IP addresses.

    Known Networks

    Specify CIDR network ranges when proxy IPs are dynamically assigned:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "KnownNetworks": ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"]
    +  }
    +}

    This example trusts all private network ranges (common for cloud deployments).

    TIP

    If both KnownProxies and KnownNetworks are empty, forwarded headers are accepted from any source. This is less secure but may be necessary in some environments.

    Allowed Hosts

    Restrict which host headers are accepted to prevent host header injection attacks:

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "AllowedHosts": ["example.com", "www.example.com", "api.example.com"]
    +  }
    +}

    If empty, any host is allowed.

    Example Configurations

    Behind nginx

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownProxies": ["127.0.0.1"],
    +    "AllowedHosts": ["myapp.com", "www.myapp.com"]
    +  }
    +}

    nginx configuration:

    nginx
    nginx
    location / {
    +    proxy_pass http://localhost:8080;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    proxy_set_header X-Forwarded-Host $host;
    +}

    AWS ALB / ELB

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownNetworks": ["10.0.0.0/8", "172.16.0.0/12"]
    +  }
    +}

    AWS load balancers automatically set forwarded headers. Trust the VPC network range.

    Azure App Service

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 2
    +  }
    +}

    Azure App Service sits behind multiple proxies. Empty KnownProxies/KnownNetworks allows headers from Azure's infrastructure.

    Cloudflare + Origin Server

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 2,
    +    "KnownNetworks": [
    +      "173.245.48.0/20",
    +      "103.21.244.0/22",
    +      "103.22.200.0/22",
    +      "103.31.4.0/22",
    +      "141.101.64.0/18",
    +      "108.162.192.0/18",
    +      "190.93.240.0/20",
    +      "188.114.96.0/20",
    +      "197.234.240.0/22",
    +      "198.41.128.0/17",
    +      "162.158.0.0/15",
    +      "104.16.0.0/13",
    +      "104.24.0.0/14",
    +      "172.64.0.0/13",
    +      "131.0.72.0/22"
    +    ]
    +  }
    +}

    TIP

    Cloudflare publishes their IP ranges at https://www.cloudflare.com/ips/. Keep this list updated.

    Docker/Kubernetes with Internal Load Balancer

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1,
    +    "KnownNetworks": ["10.0.0.0/8"]
    +  }
    +}

    Trust the container network range.

    Development (Trust All)

    json
    json
    {
    +  "ForwardedHeaders": {
    +    "Enabled": true,
    +    "ForwardLimit": 1
    +  }
    +}

    DANGER

    Do not use empty KnownProxies/KnownNetworks in production without understanding the security implications. Malicious clients can spoof forwarded headers.

    Security Considerations

    1. Only enable behind trusted proxies: Forwarded headers can be spoofed by clients if not properly validated.

    2. Limit forward depth: Use ForwardLimit to prevent clients from injecting fake proxy chains.

    3. Specify known proxies: Use KnownProxies or KnownNetworks to only accept headers from trusted sources.

    4. Validate hosts: Use AllowedHosts to prevent host header injection attacks.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/health-checks.html b/config/health-checks.html new file mode 100644 index 000000000..f368dd1cd --- /dev/null +++ b/config/health-checks.html @@ -0,0 +1,133 @@ + + + + + + Health Checks | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Health Checks

    New in 3.6.0

    Health Checks middleware was added in version 3.6.0.

    Health check endpoints for container orchestration (Kubernetes, Docker Swarm) and monitoring systems to determine if the application is running correctly.

    Overview

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": false,
    +    "CacheDuration": "5 seconds",
    +    "Path": "/health",
    +    "ReadyPath": "/health/ready",
    +    "LivePath": "/health/live",
    +    "IncludeDatabaseCheck": true,
    +    "ConnectionName": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable health check endpoints.
    CacheDurationstring"5 seconds"Cache health check responses for the specified duration. PostgreSQL interval format. Set to null to disable caching.
    Pathstring"/health"Path for the main health check endpoint that reports overall status.
    ReadyPathstring"/health/ready"Path for the readiness probe endpoint.
    LivePathstring"/health/live"Path for the liveness probe endpoint.
    IncludeDatabaseCheckbooltrueInclude PostgreSQL database connectivity in health checks.
    ConnectionNamestringnullUse a specific named connection for health checks. When null, uses the default connection.

    Health Check Types

    NpgsqlRest provides three types of health check endpoints:

    Main Health (/health)

    Reports the overall health status by combining all checks.

    Response:

    • 200 OK with "Healthy" or "Degraded" status
    • 503 Service Unavailable with "Unhealthy" status

    Readiness Probe (/health/ready)

    Indicates whether the application is ready to receive traffic. Used by Kubernetes to know when a pod is ready to be added to the service load balancer.

    Includes:

    • Database connectivity check (when IncludeDatabaseCheck is true)

    Response:

    • 200 OK if ready to accept traffic
    • 503 Service Unavailable if not ready (e.g., database unreachable)

    Liveness Probe (/health/live)

    Indicates whether the application process is running. Used by Kubernetes to know when to restart a pod.

    Does NOT include:

    • Database checks (a slow database shouldn't trigger a container restart)

    Response:

    • 200 OK if the application process is responding

    Cache Duration

    Health check responses are cached server-side to prevent excessive database queries:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "CacheDuration": "5 seconds"
    +  }
    +}

    The value uses PostgreSQL interval format:

    • "5 seconds" or "5s"
    • "1 minute" or "1min"
    • "30s"

    Set to null to disable caching:

    json
    json
    {
    +  "HealthChecks": {
    +    "CacheDuration": null
    +  }
    +}

    TIP

    Query strings are ignored to prevent cache-busting attacks.

    Database Health Check

    When IncludeDatabaseCheck is true, the readiness probe verifies PostgreSQL connectivity:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "IncludeDatabaseCheck": true
    +  }
    +}

    If the database is unreachable:

    • /health/ready returns 503 Service Unavailable
    • /health/live still returns 200 OK (the app is running, just can't reach the database)

    Using a Different Connection

    Use a specific named connection for health checks:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary;Database=myapp;...",
    +    "HealthCheck": "Host=replica;Database=myapp;..."
    +  },
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "ConnectionName": "HealthCheck"
    +  }
    +}

    This is useful when you want to:

    • Use a read-only connection for health checks
    • Query a different database server
    • Use credentials with limited permissions

    Kubernetes Integration

    Deployment Configuration

    yaml
    yaml
    apiVersion: apps/v1
    +kind: Deployment
    +metadata:
    +  name: npgsqlrest-app
    +spec:
    +  template:
    +    spec:
    +      containers:
    +      - name: app
    +        image: vbilopav/npgsqlrest:latest
    +        ports:
    +        - containerPort: 8080
    +        livenessProbe:
    +          httpGet:
    +            path: /health/live
    +            port: 8080
    +          initialDelaySeconds: 5
    +          periodSeconds: 10
    +          failureThreshold: 3
    +        readinessProbe:
    +          httpGet:
    +            path: /health/ready
    +            port: 8080
    +          initialDelaySeconds: 5
    +          periodSeconds: 5
    +          failureThreshold: 3

    Probe Behavior

    ProbeChecksFailure Action
    LivenessApp process respondingRestart container
    ReadinessApp + DatabaseRemove from load balancer

    WARNING

    Don't use /health/ready for liveness probes. A database outage would cause all pods to restart, making recovery harder.

    Docker Compose Health Check

    yaml
    yaml
    services:
    +  api:
    +    image: vbilopav/npgsqlrest:latest
    +    healthcheck:
    +      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    +      interval: 30s
    +      timeout: 10s
    +      retries: 3
    +      start_period: 10s

    Custom Paths

    Customize the health check paths:

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "Path": "/api/health",
    +    "ReadyPath": "/api/health/readiness",
    +    "LivePath": "/api/health/liveness"
    +  }
    +}

    Example Configurations

    Basic Configuration

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true
    +  }
    +}

    Uses all defaults: database check enabled, 5-second cache, standard paths.

    Production with Caching

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "CacheDuration": "10 seconds",
    +    "IncludeDatabaseCheck": true
    +  }
    +}

    API Gateway Integration

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "Path": "/healthz",
    +    "ReadyPath": "/readyz",
    +    "LivePath": "/livez",
    +    "CacheDuration": "3 seconds"
    +  }
    +}

    Uses Kubernetes-style paths (/healthz, /readyz, /livez).

    Without Database Check

    json
    json
    {
    +  "HealthChecks": {
    +    "Enabled": true,
    +    "IncludeDatabaseCheck": false
    +  }
    +}

    All probes return healthy if the app process is responding. Useful if you have separate database monitoring.

    Response Format

    Health check endpoints return plain text responses:

    code
    Healthy

    Or:

    code
    Unhealthy

    With corresponding HTTP status codes:

    • 200 OK - Healthy or Degraded
    • 503 Service Unavailable - Unhealthy

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/http-client.html b/config/http-client.html new file mode 100644 index 000000000..f602daa6c --- /dev/null +++ b/config/http-client.html @@ -0,0 +1,202 @@ + + + + + + HTTP Client Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    HTTP Client Options

    Configuration for HTTP Types - composite types that enable PostgreSQL functions to make HTTP requests to external APIs.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": false,
    +      "ResponseStatusCodeField": "status_code",
    +      "ResponseBodyField": "body",
    +      "ResponseHeadersField": "headers",
    +      "ResponseContentTypeField": "content_type",
    +      "ResponseSuccessField": "success",
    +      "ResponseErrorMessageField": "error_message",
    +      "CacheEnabled": true,
    +      "MaxCacheEntries": 10000,
    +      "CachePruneIntervalSeconds": 60
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable HTTP client functionality for annotated composite types.
    ResponseStatusCodeFieldstring"status_code"Field name for HTTP response status code.
    ResponseBodyFieldstring"body"Field name for HTTP response body content.
    ResponseHeadersFieldstring"headers"Field name for HTTP response headers (as JSON).
    ResponseContentTypeFieldstring"content_type"Field name for Content-Type header value.
    ResponseSuccessFieldstring"success"Field name for success flag (true for 2xx status codes).
    ResponseErrorMessageFieldstring"error_message"Field name for error message if request failed.
    CacheEnabledbooltrueGlobal kill switch for HTTP type response caching. When false, the @cache directive on individual types is ignored and every request fires a fresh outbound call. Caching is still opt-in per type.
    MaxCacheEntriesint10000Maximum number of distinct cached HTTP responses held in memory. Once full, new responses are not cached (existing entries are still served and expire normally).
    CachePruneIntervalSecondsint60Interval in seconds at which expired cached HTTP responses are pruned from memory.

    How HTTP Types Work

    HTTP Types allow PostgreSQL functions to make HTTP requests to external APIs. When a function parameter uses a composite type with an HTTP definition comment, NpgsqlRest automatically:

    1. Parses the HTTP definition from the type comment
    2. Substitutes placeholders with function parameter values
    3. Executes the HTTP request
    4. Populates the type fields with response data
    5. Executes the PostgreSQL function with the populated parameter

    Creating an HTTP Type

    Step 1: Create a Composite Type

    Create a composite type with fields matching the response field names:

    sql
    sql
    create type weather_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);

    Step 2: Add HTTP Definition Comment

    Add an HTTP definition as a comment on the type (RFC 7230 format):

    sql
    sql
    comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +@timeout 30s';

    Step 3: Use in a Function

    Create a function with the HTTP type as a parameter:

    sql
    sql
    create function get_weather(
    +  _city text,
    +  _api_key text,
    +  _req weather_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_req).success then
    +        return (_req).body::json;
    +    else
    +        return json_build_object('error', (_req).error_message);
    +    end if;
    +end;
    +$$;

    Equivalent as a SQL file endpoint (sql/get-weather.sql):

    The HTTP Type itself must be defined in DDL (it's a composite type). The endpoint that consumes it can be a SQL file:

    sql
    sql
    /*
    +HTTP GET
    +@param $1 city
    +@param $2 api_key
    +@param $3 req weather_api
    +*/
    +select case
    +    when ($3::weather_api).success then ($3::weather_api).body::json
    +    else json_build_object('error', ($3::weather_api).error_message)
    +end;

    HTTP Definition Format

    The comment on the composite type follows a simplified HTTP message format similar to .http files:

    code
    METHOD URL [HTTP/version]
    +Header-Name: Header-Value
    +...
    +
    +[request body]

    Supported Methods

    • GET
    • POST
    • PUT
    • PATCH
    • DELETE

    Example Definitions

    Simple GET request:

    sql
    sql
    comment on type api_response is 'GET https://api.example.com/data';

    GET with headers:

    sql
    sql
    comment on type api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}
    +Accept: application/json';

    POST with body:

    sql
    sql
    comment on type api_response is 'POST https://api.example.com/users
    +Content-Type: application/json
    +
    +{"name": "{_name}", "email": "{_email}"}';

    Timeout Directives

    Timeout can be specified before or after the request line using interval format:

    sql
    sql
    -- Before request line
    +comment on type api_response is 'timeout 30
    +GET https://api.example.com/data';
    +
    +-- After headers
    +comment on type api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}
    +@timeout 30s';

    Common timeout formats:

    FormatExampleDescription
    Seconds (integer)timeout 3030 seconds
    Seconds with suffixtimeout 30s30 seconds
    TimeSpan formattimeout 00:00:3030 seconds
    With @ prefix@timeout 2min2 minutes

    Response Fields

    The composite type fields are automatically populated based on their names:

    Field NameTypeDescription
    bodytextResponse body content
    status_codeint or textHTTP status code (e.g., 200, 404)
    headersjsonResponse headers as JSON object
    content_typetextContent-Type header value
    successbooleanTrue for 2xx status codes
    error_messagetextError message if request failed

    You can customize field names via HttpClientOptions configuration if your type uses different names.

    Placeholder Substitution

    URLs, headers, and request body can contain placeholders in the format {parameter_name}. These placeholders are automatically replaced with the values of other function parameters that share the same name.

    sql
    sql
    -- Type with placeholders
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +@timeout 30s';
    +
    +-- Function with matching parameter names
    +create function get_weather(
    +  _city text,        -- Value substitutes {_city} placeholder
    +  _api_key text,     -- Value substitutes {_api_key} placeholder
    +  _req weather_api   -- HTTP type parameter (receives response)
    +)
    +returns json
    +...

    When calling GET /api/get-weather?_city=London&_api_key=secret123, NpgsqlRest will:

    1. Substitute {_city} with London and {_api_key} with secret123
    2. Make the HTTP request to https://api.weather.com/v1/current?city=London with header Authorization: Bearer secret123
    3. Populate the _req parameter fields with the response data
    4. Execute the PostgreSQL function

    Complete Example

    Configuration

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true
    +    }
    +  }
    +}

    SQL Setup

    sql
    sql
    -- Create response type
    +create type github_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);
    +
    +-- Define HTTP request
    +comment on type github_api is 'GET https://api.github.com/users/{_username}
    +Accept: application/vnd.github.v3+json
    +User-Agent: NpgsqlRest
    +@timeout 10s';
    +
    +-- Create function
    +create function get_github_user(
    +    _username text,
    +    _response github_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_response).success then
    +        return (_response).body::json;
    +    else
    +        return json_build_object(
    +            'error', true,
    +            'status', (_response).status_code,
    +            'message', (_response).error_message
    +        );
    +    end if;
    +end;
    +$$;
    +
    +comment on function get_github_user(text, github_api) is 'HTTP GET /github/user';

    Usage

    code
    GET /github/user/octocat

    Returns the GitHub user data or an error response.

    Resolved Parameter Expressions

    A placeholder in an outgoing request (e.g. Authorization: Bearer {_token}) often needs a value the client must not supply — a DB-stored API token, a claim-derived secret. A resolved parameter expression (param = <sql> on the function) computes that value server-side per request and binds it to the parameter, which then substitutes into the URL/headers/body. It never originates from, or is overridable by, the client.

    sql
    sql
    comment on type my_api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +comment on function get_secure_data(_user_id int, _req my_api_response, _token text) is '
    +_token = select api_token from user_tokens where user_id = {_user_id}
    +';

    A call to GET /api/get-secure-data/?user_id=42 resolves _token from the database (parameterized as $1 = 42), substitutes it into the Authorization header, and makes the request — the token never leaves the server.

    A {name} placeholder can also be filled from a request parameter or an allowlisted environment variable (good for a static API key); a resolved expression is for values computed server-side per request.

    Full reference

    See Resolved Parameters for behavior (server-side only, NULL handling, multiple expressions, ordering, user_params), the DB-stored / refresh-token pattern, and how it compares to the other placeholder sources.

    Retry Logic

    When using HTTP Client Types, outgoing HTTP requests to external APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. The @retry_delay directive adds configurable automatic retries with delays.

    Syntax

    sql
    sql
    -- Retry on any failure (non-2xx status, timeout, or network error):
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s
    +GET https://api.example.com/data';
    +
    +-- Retry only on specific HTTP status codes:
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';
    +
    +-- Combined with timeout:
    +comment on type my_api_type is 'timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';

    The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1-second, 2-second, and 5-second delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

    Behavior

    • Without on filter: Retries on any non-success HTTP response, timeout, or network error.
    • With on filter: Retries only when the HTTP response status code matches one of the listed codes (e.g., 429, 503). Timeouts and network errors always trigger retry regardless of the filter.
    • Retry exhaustion: If all retries fail, the last error is passed to the PostgreSQL function — the same as if retries were not configured.
    • Unexpected exceptions: Non-HTTP errors (e.g., invalid URL) are never retried.
    • Parallel execution: Each HTTP type in a function retries independently within its own parallel task.

    Example

    sql
    sql
    create type rate_limited_api as (body json, status_code int, error_message text);
    +comment on type rate_limited_api is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_rate_limited_data(
    +    _token text,
    +    _req rate_limited_api
    +)
    +returns table (body json, status_code int, error_message text)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code, (_req).error_message;
    +end;
    +$$;

    If the external API returns 429 (rate limited), the request is automatically retried after 1s, then 2s, then 5s. If it returns 400 (bad request), no retry occurs and the error is returned immediately.

    Response Caching

    The @cache directive caches the outbound HTTP response and reuses it for matching requests within a time window, instead of calling the upstream on every request:

    sql
    sql
    create type books_api as (body text, status_code int, success boolean);
    +comment on type books_api is '@cache 5m
    +GET https://books.toscrape.com/';

    A cached type fires one outbound call for a given request shape; subsequent matching requests are served from an in-memory cache until the TTL elapses. For a type with no per-request placeholders, that means a single shared upstream call per TTL window across the whole application.

    • Opt-in, GET only. Caching happens only when @cache is present; a @cache on a non-GET method is ignored with a startup warning.
    • TTL. @cache <interval> uses the same interval format as @timeout (30s, 5m, 1h, 00:05:00, or a bare number of seconds). A bare @cache caches with no expiration (until the process restarts) and warns.
    • Successful responses only. Only 2xx responses are cached, so a transient upstream failure is never pinned for the whole TTL.
    • Stampede protection. A burst of concurrent requests for the same key coalesces into a single outbound call.
    • Cache key = method + resolved URL + resolved content-type + resolved headers + resolved body, so distinct resolved requests are cached separately.

    Caching is controlled by the CacheEnabled, MaxCacheEntries, and CachePruneIntervalSeconds settings above. See the @cache directive reference for full details.

    Self-Referencing Calls (Relative Paths)

    HTTP client type definitions support relative paths that call back to the same NpgsqlRest server instance instead of external URLs:

    sql
    sql
    create type api_users as (body text);
    +comment on type api_users is 'GET /api/users';
    +
    +create type api_orders as (body text);
    +comment on type api_orders is 'GET /api/orders';

    Parallel Query Composition

    Combined with HTTP client types executing all requests in parallel (Task.WhenAll), this enables a single endpoint to fan out to multiple internal endpoints simultaneously:

    sql
    sql
    create function get_dashboard(
    +    _users api_users,
    +    _orders api_orders
    +) returns json language plpgsql as $$
    +begin
    +    return json_build_object('users', (_users).body::json, 'orders', (_orders).body::json);
    +end;
    +$$;
    +-- One request → two parallel internal calls → combined response

    Zero HTTP Overhead

    Self-referencing calls bypass the HTTP stack entirely — the endpoint handler is invoked directly in-process via InternalRequestHandler. No TCP connection, no HTTP parsing, no serialization overhead. Performance is microseconds instead of milliseconds per internal call.

    Use cases:

    • Parallel data aggregation across multiple queries
    • Orchestrating multiple mutations in a single request
    • Composing responses from several independent data sources

    Internal-Only Endpoints

    Combine with the @internal annotation to create endpoints accessible only via self-referencing calls but not exposed as public HTTP routes:

    sql
    sql
    comment on function helper_data() is 'HTTP GET
    +@internal';
    +-- Direct HTTP call → 404. Internal call via HTTP client type → works.

    Next Steps

    See Also

    • HTTP_TYPE - HTTP Type comment format reference

    Comments

    + + + + \ No newline at end of file diff --git a/config/http-files.html b/config/http-files.html new file mode 100644 index 000000000..2ee07c8d3 --- /dev/null +++ b/config/http-files.html @@ -0,0 +1,87 @@ + + + + + + HTTP File Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    HTTP File Options

    Configuration for generating HTTP files for NpgsqlRest endpoints, compatible with REST Client extensions and Visual Studio HTTP file support.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": false,
    +      "Option": "File",
    +      "Name": null,
    +      "NamePattern": "{0}_{1}",
    +      "CommentHeader": "Simple",
    +      "CommentHeaderIncludeComments": true,
    +      "FileMode": "Schema",
    +      "FileOverwrite": true,
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable HTTP file generation.
    Optionstring"File"Generation mode: "File", "Endpoint", or "Both".
    NamestringnullBase file name. Uses database name if null, or "npgsqlrest" if no connection string.
    NamePatternstring"{0}_{1}"File name pattern. {0} = database name, {1} = schema suffix (when FileMode is "Schema").
    CommentHeaderstring"Simple"Comment header style: "None", "Simple", or "Full".
    CommentHeaderIncludeCommentsbooltrueInclude routine comments in header (when CommentHeader is "Simple" or "Full").
    FileModestring"Schema"File organization: "Database" or "Schema".
    FileOverwritebooltrueOverwrite existing files.
    OmitAutomaticParametersboolfalseOmit server-filled parameters from generated requests. See Omitting automatic parameters.

    Generation Options

    OptionDescription
    FileGenerate HTTP files in the file system.
    EndpointGenerate endpoint(s) serving HTTP file content.
    BothGenerate both file system files and endpoints.

    Comment Header Styles

    StyleDescription
    NoneNo comment header above requests.
    SimpleAdd routine name, parameters, and return values (default).
    FullAdd entire routine code as comment header.

    File Mode

    ModeDescription
    DatabaseCreate one HTTP file for the entire database.
    SchemaCreate one HTTP file per schema.

    HTTP Files

    HTTP files (.http) are supported by:

    These files allow you to send HTTP requests directly from your editor for API testing and documentation.

    Example Configuration

    Generate HTTP files per schema with full routine documentation:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "File",
    +      "Name": "myapi",
    +      "NamePattern": "{0}_{1}",
    +      "CommentHeader": "Full",
    +      "CommentHeaderIncludeComments": true,
    +      "FileMode": "Schema",
    +      "FileOverwrite": true
    +    }
    +  }
    +}

    Generate a single HTTP file for the entire database:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "File",
    +      "FileMode": "Database",
    +      "CommentHeader": "Simple"
    +    }
    +  }
    +}

    Serve HTTP files as endpoints:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "Option": "Endpoint"
    +    }
    +  }
    +}

    Omitting Automatic Parameters

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the Code Generation and OpenAPI generators). Default is false, so generated output is unchanged unless you opt in.

    Some parameters are filled by the server and a client value would simply be ignored. When OmitAutomaticParameters is true, such a parameter is left out of the generated .http request (query string and request body) when it is automatic and optional. "Automatic" covers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpFileOptions": {
    +      "Enabled": true,
    +      "OmitAutomaticParameters": true
    +    }
    +  }
    +}

    When every parameter of an endpoint is omitted, the request collapses to a bare URL with no query string or body.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/index.html b/config/index.html new file mode 100644 index 000000000..ed91025f7 --- /dev/null +++ b/config/index.html @@ -0,0 +1,37 @@ + + + + + + Configuration Reference | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Configuration Reference

    Complete reference documentation for all NpgsqlRest configuration options.

    For an introduction to how configuration works (sources, precedence, environment variables, command-line arguments), see the Configuration Guide.

    Latest Default Configuration

    See the Latest Default Configuration for a complete reference of all default settings for version 3.19.0.

    Reference Sections

    Core Settings

    • Top-Level Settings - Application identity, URLs, and startup message
    • Config Section - Configuration file processing and environment variables
    • NpgsqlRest Options - Core API generation settings (URL prefixes, naming conventions, request handling)
    • Routine Options - PostgreSQL routine handling (language filtering, custom types)
    • Connection - Database connection strings and settings
    • Server - Kestrel web server and SSL/TLS configuration

    Security

    Features

    • SQL File Source - REST API endpoints from SQL files
    • Test Runner - SQL test runner (--test): discovery, test databases, setup/teardown, coverage
    • Watch Mode - --watch: restart the server or re-run tests on SQL file, configuration, and database routine changes
    • Proxy - Reverse proxy support for forwarding requests to upstream services
    • OpenAPI - OpenAPI/Swagger documentation generation
    • MCP - Model Context Protocol server — expose routines as MCP tools for AI agents
    • HTTP Files - HTTP test file generation
    • Code Generation - Client code generation (TypeScript, etc.)
    • Uploads - File upload handling
    • Table Format - HTML table and Excel spreadsheet rendering for function results
    • HTTP Client - HTTP Types for external API calls from PostgreSQL functions

    Performance

    Infrastructure

    Comments

    + + + + \ No newline at end of file diff --git a/config/latest.html b/config/latest.html new file mode 100644 index 000000000..4135ada0f --- /dev/null +++ b/config/latest.html @@ -0,0 +1,3173 @@ + + + + + + Latest Default Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Latest Default Configuration Reference

    This is the latest default configuration reference for NpgsqlRest version 3.19.0.

    Downloading Configuration for Specific Versions

    To download the default configuration file for a specific version (e.g., 3.19.0):

    Replace 3.19.0 with your desired version number.

    json
    json
    {
    +  //
    +  // The application name used to set the application name property in connection string by "NpgsqlRest.SetApplicationNameInConnection" or the "NpgsqlRest.UseJsonApplicationName" settings.
    +  // It is the name of the top-level directory if set to null.
    +  //
    +  "ApplicationName": null,
    +
    +  //
    +  // Production or Development
    +  //
    +  "EnvironmentName": "Production",
    +
    +  //
    +  // Specify the urls the web host will listen on. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.hostingabstractionswebhostbuilderextensions.useurls?view=aspnetcore-8.0
    +  //
    +  "Urls": "http://localhost:8080",
    +
    +  //
    +  // Logs at startup, format placeholders:
    +  // {time} - startup time
    +  // {urls} - listening on urls
    +  // {version} - current version
    +  // {environment} - EnvironmentName
    +  // {application} - ApplicationName
    +  //
    +  // Note: This message is logged at Information level. To disable this message, set to empty string.
    +  //
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}",
    +
    +  //
    +  // Configuration settings
    +  //
    +  "Config": {
    +    //
    +    // Add the environment variables to configuration.
    +    // When enabled, environment variables will override the settings in this configuration file but can be overridden by command line arguments.
    +    // Complex hierarchical keys can be defined using double underscore as a separator. 
    +    // For example, "ConnectionStrings__Default" environment variable will override the "ConnectionStrings.Default" setting in this configuration file.
    +    //
    +    "AddEnvironmentVariables": false,
    +    //
    +    // When set, configuration values will be parsed for environment variables in the format {ENV_VAR_NAME}
    +    // and replaced with the value of the environment variable when available.
    +    //
    +    "ParseEnvironmentVariables": true,
    +    //
    +    // Path to a .env file containing environment variables.
    +    // When AddEnvironmentVariables or ParseEnvironmentVariables is true and this file exists,
    +    // variables from this file will be loaded and made available for configuration parsing.
    +    // Format: KEY=VALUE (one per line)
    +    //
    +    "EnvFile": null,
    +    //
    +    // Validate configuration keys against known defaults at startup.
    +    // "Ignore" - no validation
    +    // "Warning" - log warnings for unknown keys, continue startup (default)
    +    // "Error" - log errors for unknown keys and exit
    +    //
    +    "ValidateConfigKeys": "Warning"
    +  },
    +
    +  //
    +  // List of named connection strings to PostgreSQL databases.
    +  // The "Default" connection string is used when no connection name is specified.
    +  // For connection string definition see https://www.npgsql.org/doc/connection-string-parameters.html
    +  //
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Port=5432;Database={PGDATABASE};Username={PGUSER};Password={PGPASSWORD}"
    +  },
    +
    +  //
    +  // Additional connection settings and options.
    +  //
    +  "ConnectionSettings": {
    +    //
    +    // Sets the ApplicationName connection property in the connection string to the value of the ApplicationName configuration.
    +    // Note: This option is ignored if the UseJsonApplicationName option is enabled.
    +    //
    +    "SetApplicationNameInConnection": true,
    +    //
    +    // Sets the ApplicationName connection property dynamically on every request in the following format: 
    +    // {"app":"<ApplicationName>","uid":"<user_id>","id":"<NpgsqlRest.ExecutionIdHeaderName>"}
    +    // Note: The ApplicationName connection property is limited to 64 characters.
    +    //
    +    "UseJsonApplicationName": false,
    +    //
    +    // Test any connection string before initializing the application and using it. The connection string is tested by opening and closing the connection.
    +    //
    +    "TestConnectionStrings": true,
    +    //
    +    // Connection open retry options.
    +    //
    +    "RetryOptions": {
    +      "Enabled": true,
    +      //
    +      // Retry sequence in seconds. Accepts decimal numbers (0.25 is quarter of a second). The length of the array determines the maximum number of retries.
    +      //
    +      "RetrySequenceSeconds": [1, 3, 6, 12],
    +      //
    +      // Error codes that will trigger a retry when opening a connection. See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +      //
    +      "ErrorCodes": [
    +        "08000", "08003", "08006", "08001", "08004", // Connection failure codes
    +        "55P03", // Lock not available
    +        "55006", // Object in use
    +        "53300", // Too many connections
    +        "57P03", // Cannot connect now
    +        "40001"  // Serialization failure (can be retried)
    +      ]
    +    },
    +    //
    +    // The connection name in ConnectionStrings configuration that will be used to execute the metadata query. If this value is null, the default connection string will be used.
    +    //
    +    "MetadataQueryConnectionName": null,
    +    //
    +    // Set the search path to this schema before executing the metadata query function.
    +    // When null (default), no search path is set and the server's default search path is used.
    +    //
    +    // This is needed when using non superuser connection roles with limited schema access and mapping the metadata function to a specific schema.
    +    // If the connection string contains the same "Search Path=" it will be skipped.
    +    //
    +    "MetadataQuerySchema": null,
    +    // Any: Any successful connection is acceptable.
    +    // Primary: Server must not be in hot standby mode (pg_is_in_recovery() must return false).
    +    // Standby: Server must be in hot standby mode (pg_is_in_recovery() must return true).
    +    // PreferPrimary: First try to find a primary server, but if none of the listed hosts is a primary server, try again in Any mode.
    +    // PreferStandby: First try to find a standby server, but if none of the listed hosts is a standby server, try again in Any mode.
    +    // ReadWrite: Session must accept read-write transactions by default (that is, the server must not be in hot standby mode and the default_transaction_read_only parameter must be off).
    +    // ReadOnly: Session must not accept read-write transactions by default (the converse).
    +    // see https://www.npgsql.org/doc/failover-and-load-balancing.html
    +    "MultiHostConnectionTargets": {
    +      // all connections use the same target mode
    +      "Default": "Any",
    +      // per connection overrides { "name": "Primary|Standby|Any|PreferPrimary|PreferStandby|ReadWrite|ReadOnly" } 
    +      "ByConnectionName": {  }
    +    }
    +  },
    +
    +  //
    +  // Enable to invoke UseKestrelHttpsConfiguration. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilderkestrelextensions.usekestrelhttpsconfiguration?view=aspnetcore-8.0
    +  //
    +  "Ssl": {
    +    "Enabled": false,
    +    //
    +    // Adds middleware for redirecting HTTP Requests to HTTPS. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.httpspolicybuilderextensions.usehttpsredirection?view=aspnetcore-8.0
    +    //
    +    "UseHttpsRedirection": true,
    +    //
    +    // Adds middleware for using HSTS, which adds the Strict-Transport-Security header. See https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.hstsbuilderextensions.usehsts?view=aspnetcore-2.1
    +    //
    +    "UseHsts": true
    +  },
    +
    +  //
    +  // Data protection settings. Encryption/decryption settings for Auth Cookies, Antiforgery tokens and custom data protection needs.
    +  //
    +  "DataProtection": {
    +    "Enabled": false,
    +    //
    +    // Set to null to use the current "ApplicationName" value.
    +    // This value determines encryption type or class. Meaning, different application names will not be able to decrypt each other's data.
    +    //
    +    "CustomApplicationName": null,
    +    //
    +    // Sets the default lifetime in days of keys created by the data protection system.
    +    // Represents a number of days how long before keys are rotated.
    +    //
    +    "DefaultKeyLifetimeDays": 90,
    +    //
    +    // Data protection location: "Default", "FileSystem" or "Database"
    +    //
    +    // Note: When running on Linux, using Default location means keys will not be persisted. 
    +    // When keys are lost on restart, encrypted tokens (auth) will also not work on restart.
    +    // Linux users should use FileSystem or Database storage.
    +    //
    +    "Storage": "Default",
    +    //
    +    // FileSystem storage path. Set to a valid path when using FileSystem.
    +    // Note: When running in Docker environment, the path must be a Docker volume path to persist the keys.
    +    //
    +    "FileSystemPath": "./data-protection-keys",
    +    //
    +    // GetAllElements database command. Expected to return rows with a single column of type text.
    +    //
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    //
    +    // StoreElement database command. Receives two parameters: name and data of type text. Doesn't return anything.
    +    //
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)",
    +    //
    +    // Configure encryption algorithms for data protection keys or null to use the default algorithm.
    +    // Values: AES_128_CBC, AES_192_CBC, AES_256_CBC, AES_128_GCM, AES_192_GCM, AES_256_GCM
    +    //
    +    "EncryptionAlgorithm": null,
    +    //
    +    // Configure validation algorithms for data protection keys or null to use the default algorithm.
    +    // Values: HMACSHA256, HMACSHA512
    +    //
    +    "ValidationAlgorithm": null,
    +    //
    +    // Key encryption method: "None", "Certificate", or "Dpapi" (Windows only)
    +    // None: Keys are not encrypted at rest (default)
    +    // Certificate: Keys are encrypted using an X.509 certificate
    +    // Dpapi: Keys are encrypted using Windows Data Protection API (Windows only)
    +    //
    +    "KeyEncryption": "None",
    +    //
    +    // Path to the X.509 certificate file (.pfx) when using Certificate key encryption.
    +    //
    +    "CertificatePath": null,
    +    //
    +    // Password for the certificate file. Can be null for certificates without password.
    +    // For security, consider using environment variable reference: "${CERT_PASSWORD}"
    +    //
    +    "CertificatePassword": null,
    +    //
    +    // When using Dpapi key encryption, set to true to protect keys to the local machine.
    +    // If false (default), keys are protected to the current user account.
    +    //
    +    "DpapiLocalMachine": false
    +  },
    +
    +  //
    +  // Uncomment to configure Kestrel web server and to add certificates
    +  // See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-9.0
    +  //
    +  "Kestrel": {
    +    //  "Endpoints": {
    +    //    "Http": {
    +    //      "Url": "http://localhost:5000"
    +    //    },
    +    //    "HttpsInlineCertFile": {
    +    //      "Url": "https://localhost:5001",
    +    //      "Certificate": {
    +    //        "Path": "<path to .pfx file>",
    +    //        "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //      }
    +    //    },
    +    //    "HttpsInlineCertAndKeyFile": {
    +    //      "Url": "https://localhost:5002",
    +    //      "Certificate": {
    +    //        "Path": "<path to .pem/.crt file>",
    +    //        "KeyPath": "<path to .key file>",
    +    //        "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //      }
    +    //    },
    +    //    "HttpsInlineCertStore": {
    +    //      "Url": "https://localhost:5003",
    +    //      "Certificate": {
    +    //        "Subject": "<subject; required>",
    +    //        "Store": "<certificate store; required>",
    +    //        "Location": "<location; defaults to CurrentUser>",
    +    //        "AllowInvalid": "<true or false; defaults to false>"
    +    //      }
    +    //    },
    +    //    "HttpsDefaultCert": {
    +    //      "Url": "https://localhost:5004"
    +    //    }
    +    //  },
    +    //  "Certificates": {
    +    //    "Default": {
    +    //      "Path": "<path to .pfx file>",
    +    //      "Password": "$CREDENTIAL_PLACEHOLDER$"
    +    //    }
    +    //  },
    +    //  "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
    +  },
    +
    +  //
    +  // Thread pool configuration settings for optimizing application performance
    +  //
    +  "ThreadPool": {
    +    //
    +    // Minimum number of worker threads in the thread pool. Set to null to use system defaults.
    +    //
    +    "MinWorkerThreads": null,
    +    //
    +    // Minimum number of completion port threads. Set to null to use system defaults.
    +    //
    +    "MinCompletionPortThreads": null,
    +    //
    +    // Maximum number of worker threads in the thread pool. Set to null to use system defaults.
    +    //
    +    "MaxWorkerThreads": null,
    +    //
    +    // Maximum number of completion port threads. Set to null to use system defaults.
    +    //
    +    "MaxCompletionPortThreads": null
    +  },
    +
    +  //
    +  // Authentication and Authorization settings
    +  //
    +  "Auth": {
    +    //
    +    // Enable Cookie Auth
    +    //
    +    "CookieAuth": false,
    +    //
    +    // Authentication scheme name for cookie authentication. Set to null to use default.
    +    //
    +    "CookieAuthScheme": "Cookies",
    +    //
    +    // Cookie validity duration in Postgres interval syntax: e.g. "14 days", "12 hours", "30 minutes".
    +    // Set to null to fall back to the framework default (14 days).
    +    //
    +    "CookieValid": "14 days",
    +    //
    +    // Custom name for the authentication cookie. Set to null to use default.
    +    //
    +    "CookieName": null,
    +    //
    +    // Path scope for the authentication cookie. Set to null to use default.
    +    //
    +    "CookiePath": null,
    +    //
    +    // Domain scope for the authentication cookie. Set to null to use default.
    +    //
    +    "CookieDomain": null,
    +    //
    +    // Allow multiple concurrent sessions for the same user.
    +    //
    +    "CookieMultiSessions": true,
    +    //
    +    // Make cookie accessible only via HTTP (not JavaScript).
    +    //
    +    "CookieHttpOnly": true,
    +    //
    +    // Controls the SameSite attribute on the authentication cookie. Accepted values:
    +    //   "Strict"      — cookie sent only on same-site requests. Most restrictive; CSRF-safe.
    +    //   "Lax"         — cookie sent on same-site requests and top-level cross-site GETs (default).
    +    //   "None"        — cookie sent on all cross-site requests. REQUIRED for cross-origin SPAs /
    +    //                   mobile clients calling this API from a different origin. Browsers drop
    +    //                   "SameSite=None" cookies without the Secure attribute, so CookieSecure
    +    //                   must be set to "Always".
    +    //   "Unspecified" — omit the SameSite attribute entirely (legacy browser behavior).
    +    // Set to null to use ASP.NET Core's default (typically "Lax").
    +    //
    +    "CookieSameSite": null,
    +    //
    +    // Controls when the cookie's Secure attribute is set. Accepted values:
    +    //   "SameAsRequest" — Secure is set only when the request itself is HTTPS (default).
    +    //   "Always"        — Secure is always set; browsers only send the cookie over HTTPS. REQUIRED
    +    //                     alongside CookieSameSite="None" for cross-origin auth.
    +    //   "None"          — Secure is never set; cookies are sent over HTTP as well as HTTPS.
    +    // Set to null to use ASP.NET Core's default ("SameAsRequest").
    +    //
    +    "CookieSecure": null,
    +    //
    +    // Enable Microsoft Bearer Token Auth (proprietary format, not JWT)
    +    //
    +    "BearerTokenAuth": false,
    +    //
    +    // Authentication scheme name for bearer token authentication. Set to null to use default.
    +    //
    +    "BearerTokenAuthScheme": "BearerToken",
    +    //
    +    // Bearer token expiration in Postgres interval syntax: e.g. "1 hour", "30 minutes", "2 days".
    +    // Set to null to fall back to the framework default (1 hour).
    +    //
    +    "BearerTokenExpire": "1 hour",
    +    // POST { "refresh": "{{refreshToken}}" }
    +    "BearerTokenRefreshPath": "/api/token/refresh",
    +    //
    +    // Enable standard JWT (JSON Web Token) Bearer Authentication
    +    //
    +    "JwtAuth": false,
    +    //
    +    // Authentication scheme name for JWT authentication. Set to null to fall back to the framework default.
    +    //
    +    "JwtAuthScheme": "Bearer",
    +    //
    +    // Secret key used to sign JWT tokens. Must be at least 32 characters for HS256.
    +    // IMPORTANT: Use a strong, unique secret in production. Store securely (e.g., environment variable).
    +    //
    +    "JwtSecret": null,
    +    //
    +    // JWT issuer (iss claim). Identifies the principal that issued the JWT.
    +    //
    +    "JwtIssuer": null,
    +    //
    +    // JWT audience (aud claim). Identifies the recipients that the JWT is intended for.
    +    //
    +    "JwtAudience": null,
    +    //
    +    // JWT access token expiration in Postgres interval syntax: e.g. "60 minutes", "1 hour", "30 seconds".
    +    // Set to null to fall back to the framework default (60 minutes).
    +    //
    +    "JwtExpire": "60 minutes",
    +    //
    +    // JWT refresh token expiration in Postgres interval syntax: e.g. "7 days", "168 hours", "1 week".
    +    // Set to null to fall back to the framework default (7 days).
    +    //
    +    "JwtRefreshExpire": "7 days",
    +    //
    +    // Validate the issuer (iss) claim. Set to true if JwtIssuer is configured.
    +    //
    +    "JwtValidateIssuer": false,
    +    //
    +    // Validate the audience (aud) claim. Set to true if JwtAudience is configured.
    +    //
    +    "JwtValidateAudience": false,
    +    //
    +    // Validate the token lifetime (exp claim). Default is true.
    +    //
    +    "JwtValidateLifetime": true,
    +    //
    +    // Validate the signing key. Default is true.
    +    //
    +    "JwtValidateIssuerSigningKey": true,
    +    //
    +    // Clock skew to apply when validating token lifetime. Format: PostgreSQL interval.
    +    // Default is 5 minutes to account for clock differences between servers.
    +    //
    +    "JwtClockSkew": "5 minutes",
    +    //
    +    // URL path for JWT token refresh endpoint. POST with { "refreshToken": "..." }
    +    // Returns new access token and refresh token pair.
    +    //
    +    "JwtRefreshPath": "/api/jwt/refresh",
    +    //
    +    // Named additional authentication schemes. Each entry registers a fully-fledged ASP.NET Core
    +    // authentication scheme alongside the main one. A login function returning a scheme name in its
    +    // `scheme` column signs the user in under that scheme — useful for "short-lived sensitive
    +    // session", "separate admin scope", or "different JWT signing key per scope" patterns alongside
    +    // the normal long-lived primary scheme.
    +    //
    +    // Each scheme has a `Type`: `Cookies`, `BearerToken`, or `Jwt`. Schemes inherit any unset field
    +    // from the root Auth section so blocks stay small. See the type-specific override fields below.
    +    //
    +    // Validation: scheme name must not collide with the main scheme names (CookieAuthScheme,
    +    // BearerTokenAuthScheme, JwtAuthScheme). Explicit `CookieName` values must be unique across all
    +    // schemes. Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across all
    +    // schemes that define one. Disabled schemes (`Enabled: false`) are skipped at startup.
    +    //
    +    "Schemes": {
    +      // Example: a short-lived single-session cookie for sensitive operations (admin area, payment flow).
    +      // Login functions can return `'short_session'` in the scheme column to sign users in under this scheme.
    +      "short_session": {
    +        "Type": "Cookies",
    +        "Enabled": false,
    +        "CookieValid": "1 hour",
    +        "CookieMultiSessions": false
    +      },
    +      // Example: a separate Microsoft bearer-token scheme with a shorter expiration than the main one.
    +      // Each scheme can declare its own refresh path; if set, it must be unique across schemes.
    +      "api_token": {
    +        "Type": "BearerToken",
    +        "Enabled": false,
    +        "BearerTokenExpire": "30 minutes",
    +        "BearerTokenRefreshPath": "/api/api-token/refresh"
    +      },
    +      // Example: a separate JWT scheme with its own signing secret (different blast radius from the
    +      // main JWT) and a much shorter access-token expiration. Inherits any unset JWT field from the
    +      // root Auth section. JwtSecret must be ≥32 characters for HS256.
    +      "admin_jwt": {
    +        "Type": "Jwt",
    +        "Enabled": false,
    +        "JwtSecret": null,
    +        "JwtIssuer": null,
    +        "JwtAudience": null,
    +        "JwtExpire": "5 minutes",
    +        "JwtRefreshExpire": "1 hour",
    +        "JwtRefreshPath": "/api/admin-jwt/refresh"
    +      }
    +    },
    +    //
    +    // Enable external auth providers
    +    //
    +    "External": {
    +      "Enabled": false,
    +      //
    +      // sessionStorage key to store the status of the external auth process returned by the signin page.
    +      // The value is HTTP status code (200 for success, 401 for unauthorized, 403 for forbidden, etc.)
    +      //
    +      "BrowserSessionStatusKey": "__external_status",
    +      //
    +      // sessionStorage key to store the message of the external auth process returned by the signin page.
    +      //
    +      "BrowserSessionMessageKey": "__external_message",
    +      //
    +      // Path to the signin page to handle the external auth process. Redirect to this page to start the external auth process.
    +      // Format placeholder {0} is the provider name in lowercase (google, linkedin, github, etc.)
    +      //
    +      "SigninUrl": "/signin-{0}",
    +      //
    +      // Sign in page template. Format placeholders {0} is the provider name, {1} is the script to redirect to the external auth provider.
    +      //
    +      "SignInHtmlTemplate": "<!DOCTYPE html><html><head><meta charset=\"utf-8\" /><title>Talking To {0}</title></head><body>Loading...{1}</body></html>",
    +      //
    +      // URL to redirect after the external auth process is completed. Usually this is resolved from the request automatically. Except when it's not.
    +      // 
    +      "RedirectUrl": null,
    +      //
    +      // Path to redirect after the external auth process is completed. 
    +      // 
    +      "ReturnToPath": "/",
    +      //
    +      // Query string key to store the path to redirect after the external auth process is completed.
    +      // Use this to set dynamic return path. If this query string key is not found, the ReturnToPath value is used.
    +      // 
    +      "ReturnToPathQueryStringKey": "return_to",
    +      //
    +      // Login command to execute after the external auth process is completed. There are five positional and optional parameters:
    +      //   $1 - external login provider (if parameter exists, type text).
    +      //   $2 - external login email (if parameter exists, type text).
    +      //   $3 - external login name (if parameter exists, type text).
    +      //   $4 - external login JSON data received (if parameter exists, type text, JSON or JSONB).
    +      //   $5 - client browser analytics JSON data (if parameter exists, type text, JSON or JSONB).
    +      //
    +      // The command uses the same rules as the login enabled routine. 
    +      // See: "NpgsqlRest.“LoginPath"
    +      //
    +      "LoginCommand": "select * from external_login($1,$2,$3,$4,$5)",
    +      //
    +      // Browser client analytics data that will be sent as JSON to external auth command as the 5th parameter if supplied.
    +      //
    +      "ClientAnalyticsData": "{timestamp:new Date().toISOString(),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,screen:{width:window.screen.width,height:window.screen.height,colorDepth:window.screen.colorDepth,pixelRatio:window.devicePixelRatio,orientation:screen.orientation.type},browser:{userAgent:navigator.userAgent,language:navigator.language,languages:navigator.languages,cookiesEnabled:navigator.cookieEnabled,doNotTrack:navigator.doNotTrack,onLine:navigator.onLine,platform:navigator.platform,vendor:navigator.vendor},memory:{deviceMemory:navigator.deviceMemory,hardwareConcurrency:navigator.hardwareConcurrency},window:{innerWidth:window.innerWidth,innerHeight:window.innerHeight,outerWidth:window.outerWidth,outerHeight:window.outerHeight},location:{href:window.location.href,hostname:window.location.hostname,pathname:window.location.pathname,protocol:window.location.protocol,referrer:document.referrer},performance:{navigation:{type:performance.navigation?.type,redirectCount:performance.navigation?.redirectCount},timing:performance.timing?{loadEventEnd:performance.timing.loadEventEnd,loadEventStart:performance.timing.loadEventStart,domComplete:performance.timing.domComplete,domInteractive:performance.timing.domInteractive,domContentLoadedEventEnd:performance.timing.domContentLoadedEventEnd}:null}}",
    +      //
    +      // Client IP address that will be added to the client analytics data under this JSON key.
    +      //
    +      "ClientAnalyticsIpKey": "ip",
    +      //
    +      // External providers
    +      //
    +      "Google": {
    +        //
    +        // visit https://console.cloud.google.com/apis/ to configure your Google app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id={0}&redirect_uri={1}&scope=openid profile email&state={2}",
    +        "TokenUrl": "https://oauth2.googleapis.com/token",
    +        "InfoUrl": "https://www.googleapis.com/oauth2/v3/userinfo",
    +        "EmailUrl": null
    +      },
    +      "LinkedIn": {
    +        //
    +        // visit https://www.linkedin.com/developers/apps/ to configure your LinkedIn app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id={0}&redirect_uri={1}&state={2}&scope=r_liteprofile%20r_emailaddress",
    +        "TokenUrl": "https://www.linkedin.com/oauth/v2/accessToken",
    +        "InfoUrl": "https://api.linkedin.com/v2/me",
    +        "EmailUrl": "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements//(handle~))"
    +      },
    +      "GitHub": {
    +        //
    +        // visit https://github.com/settings/developers/ to configure your GitHub app and get your client id and client secret
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://github.com/login/oauth/authorize?client_id={0}&redirect_uri={1}&state={2}&allow_signup=false",
    +        "TokenUrl": "https://github.com/login/oauth/access_token",
    +        "InfoUrl": "https://api.github.com/user",
    +        "EmailUrl": null
    +      },
    +      "Microsoft": {
    +        //
    +        // visit https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade to configure your Microsoft app and get your client id and client secret
    +        // Documentation: https://learn.microsoft.com/en-us/entra/identity-platform/
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&client_id={0}&redirect_uri={1}&scope=openid%20profile%20email&state={2}",
    +        "TokenUrl": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
    +        "InfoUrl": "https://graph.microsoft.com/oidc/userinfo",
    +        "EmailUrl": null
    +      },
    +      "Facebook": {
    +        //
    +        // visit https://developers.facebook.com/apps/ to configure your Facebook app and get your client id and client secret
    +        // Documentation: https://developers.facebook.com/docs/facebook-login/
    +        //
    +        "Enabled": false,
    +        "ClientId": "",
    +        "ClientSecret": "",
    +        "AuthUrl": "https://www.facebook.com/v20.0/dialog/oauth?response_type=code&client_id={0}&redirect_uri={1}&scope=public_profile%20email&state={2}",
    +        "TokenUrl": "https://graph.facebook.com/v20.0/oauth/access_token",
    +        "InfoUrl": "https://graph.facebook.com/me?fields=id,name,email",
    +        "EmailUrl": null
    +      }
    +    },
    +    //
    +    // WebAuthn/FIDO2 Passkey Authentication
    +    // Provides phishing-resistant, passwordless authentication using device-native biometrics or PINs.
    +    //
    +    "PasskeyAuth": {
    +      //
    +      // Enable passkey authentication.
    +      //
    +      "Enabled": false,
    +      //
    +      // Enable registration endpoints.
    +      //
    +      "EnableRegister": false,
    +      //
    +      // Rate limiter policy name to apply to all passkey endpoints.
    +      // It is recommended to enable rate limiting on passkey endpoints to protect against brute-force attacks.
    +      // Set to the name of a configured rate limiter policy, or null to disable rate limiting.
    +      //
    +      "RateLimiterPolicy": null,
    +      //
    +      // Optional connection name for named DataSource or ConnectionString lookup.
    +      // If null, uses the default DataSource or ConnectionString from NpgsqlRest options.
    +      //
    +      "ConnectionName": null,
    +      //
    +      // Command retry strategy name from CommandRetryOptions.Strategies.
    +      // Set to null to disable command retry for passkey endpoints.
    +      //
    +      "CommandRetryStrategy": "default",
    +      //
    +      // Relying Party ID (domain name). Should match your application domain (e.g., "example.com").
    +      // If null, auto-detected from the request host.
    +      // Note: IP addresses are not permitted - use "localhost" for local development.
    +      //
    +      "RelyingPartyId": null,
    +      //
    +      // Human-readable Relying Party name displayed to users during registration and authentication.
    +      // If null, uses the ApplicationName from configuration.
    +      //
    +      "RelyingPartyName": null,
    +      //
    +      // Allowed origins for origin validation (scheme + domain + port).
    +      // Example: ["https://example.com", "https://www.example.com"]
    +      // If empty, auto-detected from the request.
    +      // Note: IP addresses are not permitted - use "http://localhost:port" for local development.
    +      //
    +      "RelyingPartyOrigins": [],
    +      //
    +      // Post path for adding a passkey to an existing authenticated user (options).
    +      // Post any additional data in the body as JSON (e.g., { "deviceName": "My Phone" }).
    +      // Requires authentication. Set to null to disable this endpoint.
    +      //
    +      "AddPasskeyOptionsPath": "/api/passkey/add/options",
    +      //
    +      // Post path for adding a passkey to an existing authenticated user (completion).
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, attestationObject, clientDataJSON, transports). 
    +      // Additional JSON body fields are userContext passed through to the CompleteAddExistingUserCommand and optional analyticsData.
    +      // Requires authentication. Set to null to disable this endpoint.
    +      //
    +      "AddPasskeyPath": "/api/passkey/add",
    +      //
    +      // Post path for registration options (new user with passkey).
    +      // Post the user registration data the body as JSON (e.g., { "user_name": "...", "user_display_name": "...", "deviceName": "My Phone"  }).
    +      // No authentication required. Set to null to disable registration.
    +      //
    +      "RegistrationOptionsPath": "/api/passkey/register/options",
    +      //
    +      // Post path for registration completion (new user with passkey).
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, attestationObject, clientDataJSON, transports). 
    +      // Additional JSON body fields are userContext passed through to the CompleteAddExistingUserCommand and optional analyticsData.
    +      // No authentication required. Set to null to disable registration.
    +      //
    +      "RegistrationPath": "/api/passkey/register",
    +      //
    +      // Post path for the login options endpoint.
    +      // Post the user login data in the body as JSON (e.g., { "user_name": "..." } ).
    +      // Posting the user_name is optional when using discoverable credentials. When discoverable credentials ate not enabled on the authenticator, user_name is required.
    +      //
    +      "LoginOptionsPath": "/api/passkey/login/options",
    +      //
    +      // Post path for the login completion endpoint.
    +      // Post the WebAuthn response data in the body as JSON (challengeId, credentialId, authenticatorData, clientDataJSON, signature, userHandle) and optional analyticsData.
    +      //
    +      "LoginPath": "/api/passkey/login",
    +      //
    +      // Challenge timeout in minutes. Challenges not used within this time will expire.
    +      //
    +      "ChallengeTimeoutMinutes": 5,
    +      //
    +      // User verification requirement:
    +      // - "preferred": Request UV if available, but allow authentication without it
    +      // - "required": Require UV, fail if not available
    +      // - "discouraged": Don't request UV (not recommended for most use cases)
    +      //
    +      // Practical implications:
    +      // - "required": User MUST authenticate with biometric (fingerprint, face) or device PIN.
    +      //   High security - proves the person is present, not just possession of the device.
    +      // - "preferred": Browser will request biometric/PIN if available, but allows passkey
    +      //   authentication even if UV isn't supported (e.g., older security keys).
    +      // - "discouraged": Just proves device possession, no biometric/PIN prompt. Lower security.
    +      //
    +      // For most apps, use "preferred". For banking/sensitive apps, use "required".
    +      //
    +      "UserVerificationRequirement": "required",
    +      //
    +      // Resident key (discoverable credential) requirement:
    +      // - "preferred": Request discoverable credentials if supported
    +      // - "required": Require discoverable credentials, fail if not supported
    +      // - "discouraged": Request non-discoverable credentials
    +      //
    +      // Practical implications:
    +      // - "required": True passwordless. Browser shows passkey picker with all accounts at login.
    +      //   User picks account and authenticates with biometric/PIN. No username input needed.
    +      // - "preferred"/"discouraged": User enters username first, then authenticates with passkey.
    +      //
    +      // For passwordless flows (no username field), set to "required".
    +      //
    +      "ResidentKeyRequirement": "required",
    +      //
    +      // Attestation conveyance preference - controls whether the server requests the authenticator
    +      // to provide cryptographic proof of its identity (make/model) and security properties during registration.
    +      //
    +      // Options:
    +      // - "none": Don't request attestation. Accept any valid authenticator without verifying its identity.
    +      //   Best for most apps - simpler, better user privacy, wider device compatibility. (Recommended)
    +      // - "indirect": Request attestation but allow the browser/platform to anonymize it. Rarely useful.
    +      // - "direct": Request full attestation certificate chain from the authenticator.
    +      //   Use when you need to verify the authenticator vendor/model meets security requirements.
    +      // - "enterprise": Request enterprise-specific attestation for managed corporate devices
    +      //   where IT needs to verify only organization-approved hardware authenticators are used.
    +      //
    +      // When to use non-"none" values:
    +      // - Banking/financial apps requiring hardware security keys only
    +      // - Enterprise environments restricting to specific authenticator models
    +      // - Compliance requirements mandating certain security certifications (FIDO2 L1/L2)
    +      //
    +      // For most consumer applications, "none" is the correct choice - you just want the user
    +      // to authenticate securely, not audit their hardware.
    +      //
    +      "AttestationConveyance": "none",
    +      //
    +      // Whether to validate and update the signature counter (sign count).
    +      // When true, validates that the new sign count is greater than stored, and updates it after authentication.
    +      // When false, skips sign count validation and update entirely.
    +      // Set to false if authenticators don't support it or you want to simplify your database schema.
    +      //
    +      "ValidateSignCount": true,
    +      //
    +      // SQL command to create a challenge when adding a passkey to an existing authenticated user.
    +      // Parameters:
    +      //   - $1 = claims (json): JSON object with user claims from the authenticated session
    +      //   - $2 = body (json): JSON object from request body (e.g., { "deviceName": "My Phone" })
    +      // Expected return columns (by name):
    +      //   - status (int): HTTP status code. Return 200 to proceed, any other status aborts.
    +      //   - message (text): Error message when status != 200.
    +      //   - challenge (text): Base64-encoded random challenge bytes (typically 32 bytes).
    +      //   - challenge_id: Server-side identifier (uuid, int, bigint, or text).
    +      //   - user_handle (text): Base64-encoded random bytes (typically 32 bytes) for WebAuthn user.id.
    +      //   - user_name (text): Username displayed in the authenticator UI.
    +      //   - user_display_name (text): Display name shown in the authenticator UI.
    +      //   - exclude_credentials (text): JSON array of existing credentials.
    +      //   - user_context (json): Opaque JSON passed through to CompleteAddExistingUserCommand.
    +      // Called by AddPasskeyOptionsPath endpoint
    +      //
    +      "ChallengeAddExistingUserCommand": "select * from passkey_challenge_add_existing($1,$2)",
    +      //
    +      // SQL command to create a challenge for standalone registration (new user).
    +      // Parameter: $1 = JSON object from request body (e.g., { "user_name": "...", "display_name": "..." })
    +      // Expected return columns (by name): Same as ChallengeAddExistingUserCommand
    +      //   - user_context should NOT contain "id" field (distinguishes from add-existing-user flow)
    +      // Called by StandaloneRegistrationOptionsPath endpoint
    +      //
    +      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
    +      //
    +      // SQL command to create a challenge for authentication.
    +      // Parameters:
    +      //   - $1 = user_name (text, optional - null for discoverable credential flow)
    +      //   - $2 = body (json): JSON object from request body (e.g., { "deviceInfo": "..." })
    +      // Expected return columns (by name): status, message, challenge, challenge_id, allow_credentials
    +      // Called by AuthenticationOptionsPath endpoint 
    +      //
    +      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
    +      //
    +      // Used by: Flow 1, Flow 2, Flow 3 (ALL flows)
    +      // SQL command to verify and consume a challenge.
    +      // Parameters: $1 = challenge_id (uuid, int, bigint, or text), $2 = operation (text: "registration" or "authentication")
    +      // Returns: challenge (bytea) - the original challenge bytes, or NULL if not found/expired
    +      // Called by all endpoints
    +      //
    +      "VerifyChallengeCommand": "select * from passkey_verify_challenge($1,$2)",
    +      //
    +      // SQL command to get credential data for authentication.
    +      // Parameter: $1 = credential_id (bytea)
    +      // Expected return columns (by name): status, message, public_key, public_key_algorithm, sign_count, user_context
    +      // Note: user_context is passed through to CompleteAuthenticateCommand (typically contains user_id)
    +      // Called by AuthenticatePath endpoint
    +      //
    +      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
    +      //
    +      // SQL command to complete adding a passkey to an existing user account.
    +      // Parameters:
    +      //   - $1 = credential_id (bytea): Unique credential identifier from authenticator.
    +      //   - $2 = user_handle (bytea): WebAuthn user.id from registration options.
    +      //   - $3 = public_key (bytea): Public key in COSE format.
    +      //   - $4 = algorithm (int): COSE algorithm identifier (-7 for ES256, -257 for RS256).
    +      //   - $5 = transports (text[]): Transport hints (e.g., ["internal", "hybrid"]).
    +      //   - $6 = backup_eligible (boolean): Whether credential can be backed up/synced.
    +      //   - $7 = user_context (json): Opaque JSON from ChallengeAddExistingUserCommand (contains user ID).
    +      //   - $8 = analytics_data (json, optional): Client analytics with server-added IP.
    +      // Expected return columns (by name): status, message
    +      // Called by RegisterPath endpoint
    +      //
    +      "CompleteAddExistingUserCommand": "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
    +      //
    +      // SQL command to complete standalone passkey registration (creates new user).
    +      // Parameters: Same as CompleteAddExistingUserCommand
    +      //   - user_context should NOT contain "id" field (creates new user instead of linking to existing)
    +      // Expected return columns (by name): status, message
    +      // Called by RegisterPath endpoint
    +      //
    +      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
    +      //
    +      // Flow 3: Login -> AuthenticatePath endpoint (after signature validation)
    +      // SQL command to update sign count and return user claims.
    +      // Parameters:
    +      //   - $1 = credential_id (bytea)
    +      //   - $2 = new_sign_count (bigint)
    +      //   - $3 = user_context (json): Opaque JSON from AuthenticateDataCommand
    +      //   - $4 = analytics_data (json, optional): Client analytics with server-added IP
    +      // Expected return columns (by name): status, user_id, user_name, user_roles (plus any custom claims)
    +      // Called by AuthenticatePath endpoint
    +      //
    +      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)",
    +      //
    +      // The JSON key name used to add the client's IP address to the analytics data server-side.
    +      // Set to null or empty string to disable IP address collection.
    +      //
    +      "ClientAnalyticsIpKey": "ip",
    +      //
    +      // Column name configuration for database responses
    +      //
    +      "StatusColumnName": "status",
    +      "MessageColumnName": "message",
    +      "ChallengeColumnName": "challenge",
    +      "ChallengeIdColumnName": "challenge_id",
    +      "UserNameColumnName": "user_name",
    +      "UserDisplayNameColumnName": "user_display_name",
    +      "UserHandleColumnName": "user_handle",
    +      "ExcludeCredentialsColumnName": "exclude_credentials",
    +      "AllowCredentialsColumnName": "allow_credentials",
    +      "PublicKeyColumnName": "public_key",
    +      "PublicKeyAlgorithmColumnName": "public_key_algorithm",
    +      "SignCountColumnName": "sign_count"
    +    }
    +  },
    +
    +  //
    +  // Serilog settings
    +  //
    +  "Log": {
    +    //
    +    // See https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level
    +    // Verbose, Debug, Information, Warning, Error, Fatal.
    +    // Set a level to "Off" (aliases "None"/"Silent") to mute that logger entirely; use null (or omit it) to fall back to its built-in default.
    +    // Note: NpgsqlRest logger applies to main application logger, which will, by default have the name defined in the ApplicationName setting.
    +    // NpgsqlRestTest is the SQL test runner (--test) channel (see TestRunner:LoggerName): discovery/parsing at Debug, each query and HTTP call at Verbose, notices by severity.
    +    //
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    //
    +    // Enable logging to console output.
    +    //
    +    "ToConsole": true,
    +    //
    +    // Minimum log level for console output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "ConsoleMinimumLevel": "Verbose",
    +    //
    +    // Enable logging to file system.
    +    //
    +    "ToFile": false,
    +    //
    +    // File path for log files.
    +    //
    +    "FilePath": "logs/log.txt",
    +    //
    +    // Maximum size limit for log files in bytes before rolling to a new file.
    +    //
    +    "FileSizeLimitBytes": 30000000,
    +    //
    +    // Minimum log level for file output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "FileMinimumLevel": "Verbose",
    +    //
    +    // Maximum number of log files to retain.
    +    //
    +    "RetainedFileCountLimit": 30,
    +    //
    +    // Create a new log file when size limit is reached.
    +    //
    +    "RollOnFileSizeLimit": true,
    +    //
    +    // Enable logging to PostgreSQL database.
    +    //
    +    "ToPostgres": false,
    +    // $1 - log level text, $2 - message text, $3 - timestamp with tz in utc, $4 - exception text or null, $5 - source context
    +    //
    +    // PostgreSQL command to execute for database logging. Parameters: $1=level, $2=message, $3=timestamp, $4=exception, $5=source.
    +    //
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    //
    +    // Minimum log level for PostgreSQL output: Verbose, Debug, Information, Warning, Error, Fatal.
    +    //
    +    "PostgresMinimumLevel": "Verbose",
    +    //
    +    // Enable OpenTelemetry protocol (OTLP) logging output. Requires an OTLP collector endpoint.
    +    //
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc", // "Grpc" or "HttpProtobuf"
    +    "OTLResourceAttributes": {
    +        "service.name": "{application}", // application name from the ApplicationName setting
    +        "service.version": "1.0", // application version, set to a static value or use a build process to update it
    +        "service.environment": "{environment}" // environment name from the EnvironmentName setting
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose",
    +    
    +    //
    +    // See https://github.com/serilog/serilog/wiki/Formatting-Output
    +    //
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  },
    +
    +  //
    +  // Response compression settings
    +  //
    +  "ResponseCompression": {
    +    //
    +    // Enable response compression for HTTP responses.
    +    //
    +    "Enabled": false,
    +    //
    +    // Enable response compression for HTTPS responses.
    +    //
    +    "EnableForHttps": false,
    +    //
    +    // Use Brotli compression algorithm when supported by client.
    +    //
    +    "UseBrotli": true,
    +    //
    +    // Use Gzip compression as fallback when Brotli is not supported.
    +    //
    +    "UseGzipFallback": true,
    +    //
    +    // Compression level: Optimal, Fastest, NoCompression, SmallestSize.
    +    //
    +    "CompressionLevel": "Optimal",
    +    //
    +    // MIME types to include for compression.
    +    //
    +    "IncludeMimeTypes": [
    +      "text/plain",
    +      "text/css",
    +      "application/javascript",
    +      "text/javascript",
    +      "text/html",
    +      "application/xml",
    +      "text/xml",
    +      "application/json",
    +      "text/json",
    +      "image/svg+xml",
    +      "font/woff",
    +      "font/woff2",
    +      "application/font-woff",
    +      "application/font-woff2"
    +    ],
    +    //
    +    // MIME types to exclude from compression.
    +    //
    +    "ExcludeMimeTypes": []
    +  },
    +
    +  //
    +  // Antiforgery Token Configuration: Protects against Cross-Site Request Forgery (CSRF/XSRF) attacks.
    +  // CSRF attacks occur when a malicious site tricks a user's browser into making unwanted requests to your application
    +  // using the user's authenticated session (cookies).
    +  //
    +  // How it works:
    +  // 1. Server generates a unique token for each session/request
    +  // 2. Token is embedded in forms (hidden field) or sent via header (for AJAX)
    +  // 3. On state-changing requests (POST, PUT, DELETE), server validates the token
    +  // 4. Requests without valid tokens are rejected (400 Bad Request)
    +  //
    +  // Usage in HTML forms:
    +  //   <form method="post">
    +  //     <input type="hidden" name="__RequestVerificationToken" value="{antiForgeryToken}" />
    +  //     ...
    +  //   </form>
    +  //
    +  // Usage in AJAX/JavaScript:
    +  //   fetch('/api/endpoint', {
    +  //     method: 'POST',
    +  //     headers: { 'RequestVerificationToken': tokenValue },
    +  //     body: JSON.stringify(data)
    +  //   });
    +  //
    +  // Note: Antiforgery automatically sets the X-Frame-Options: SAMEORIGIN header to help prevent clickjacking.
    +  // If you're using the SecurityHeaders middleware with X-Frame-Options, the Antiforgery header takes precedence
    +  // (SecurityHeaders will skip X-Frame-Options when Antiforgery is enabled).
    +  //
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery
    +  //
    +  "Antiforgery": {
    +    //
    +    // Enable antiforgery token validation for state-changing requests.
    +    //
    +    "Enabled": false,
    +    //
    +    // Name of the cookie that stores the antiforgery token.
    +    // Set to null to use the ASP.NET Core default (unique per application, starts with ".AspNetCore.Antiforgery.").
    +    // Custom names are useful when running multiple applications on the same domain.
    +    //
    +    "CookieName": null,
    +    //
    +    // Name of the hidden form field that contains the request verification token.
    +    // This must match the name used in your HTML forms.
    +    //
    +    "FormFieldName": "__RequestVerificationToken",
    +    //
    +    // Name of the HTTP header that can contain the antiforgery token.
    +    // Useful for AJAX requests where adding a form field is not possible.
    +    // JavaScript can read the token from a cookie or meta tag and send it in this header.
    +    //
    +    "HeaderName": "RequestVerificationToken",
    +    //
    +    // When true, the server will NOT look for the token in the form body.
    +    // Forces header-only validation - useful for pure API scenarios where all requests use headers.
    +    // When false (default), server checks both form field and header.
    +    //
    +    "SuppressReadingTokenFromFormBody": false,
    +    //
    +    // When true, prevents the automatic X-Frame-Options: SAMEORIGIN header from being set.
    +    // X-Frame-Options helps prevent clickjacking attacks by blocking the page from being embedded in iframes.
    +    // Only set to true if:
    +    //   - You need your pages to be embedded in iframes from other origins, OR
    +    //   - You're setting X-Frame-Options elsewhere (e.g., in SecurityHeaders or at the proxy level)
    +    // Default: false (header is set for security)
    +    //
    +    "SuppressXFrameOptionsHeader": false
    +  },
    +
    +  //
    +  // Static files settings 
    +  //
    +  "StaticFiles": {
    +    "Enabled": false,
    +    "RootPath": "wwwroot",
    +    //
    +    // List of static file patterns that will require authorization.
    +    // File paths are relative to the RootPath property and pattern matching is case-insensitive.
    +    // Pattern can include wildcards (* matches any chars, ** matches recursively including /, ? matches single char).
    +    // For example: *.html, /user/*, /admin/**/*.html
    +    //
    +    "AuthorizePaths": [],
    +    "UnauthorizedRedirectPath": "/",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      //
    +      // Enable or disable the parsing of the static files.
    +      // When enabled, the static files will be parsed and the tags will be replaced with the values from the claims collection.
    +      // The tags are in the format: {claimType} where claimType is the name of the claim that will be replaced with the value from the claims collection.
    +      //
    +      "Enabled": false,
    +      //
    +      // List of claims types used. These will be parsed to NULL if not found in the claims collection or user is not authenticated.
    +      // Accepts an array of claim names ["name","email"] or an object of name->default {"name":"guest"} where the default is used when the claim is absent.
    +      //
    +      "AvailableClaims": [],
    +      //
    +      // List of environment variable names whose values are templated into static content (the same {NAME} tag syntax as claims).
    +      // Resolved once at startup. Accepts an array ["BUILD_LABEL"] (missing -> empty string) or an object {"DEMO_FLAG":"false"} with per-name defaults.
    +      // SECURITY: every listed value is served to any client - never list a secret (DB password, API key, signing token).
    +      //
    +      "AvailableEnvVars": [],
    +      //
    +      // Set to true to cache the parsed files in memory. This will improve the performance of the static files. It only applies to parsed content.
    +      // Note: caching will occur before parsing, it applies only to templates, not parsed content.
    +      //
    +      "CacheParsedFile": true,
    +      //
    +      // Headers to be added to the response for static files. Set to null or empty array to ignore.
    +      //
    +      "Headers": [ "Cache-Control: no-store, no-cache, must-revalidate", "Pragma: no-cache", "Expires: 0" ],
    +      //
    +      // List of static file patterns that will parse the content and replace the tags with the values from the claims collection.
    +      // File paths are relative to the RootPath property and pattern matching is case-insensitive.
    +      // Pattern can include wildcards (* matches any chars, ** matches recursively including /, ? matches single char).
    +      // For example: *.html, *.htm, *.txt, /pages/**/*.html
    +      // 
    +      "FilePaths": [ "*.html" ],
    +      //
    +      // Name of the configured Antiforgery form field name to be used in the static files (see Antiforgery FormFieldName setting).
    +      //
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      //
    +      // Value of the Antiforgery token if Antiforgery is enabled.
    +      //
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  },
    +
    +  //
    +  // Cross-origin resource sharing 
    +  //
    +  "Cors": {
    +    //
    +    // Enable Cross-Origin Resource Sharing (CORS) support.
    +    //
    +    "Enabled": false,
    +    //
    +    // List of allowed origins for CORS requests. Empty array allows no origins.
    +    //
    +    "AllowedOrigins": [],
    +    //
    +    // List of allowed HTTP methods for CORS requests.
    +    //
    +    "AllowedMethods": [
    +      "*"
    +    ],
    +    //
    +    // List of allowed headers for CORS requests.
    +    //
    +    "AllowedHeaders": [
    +      "*"
    +    ],
    +    //
    +    // Allow credentials (cookies, authorization headers) in CORS requests.
    +    // Disabled by default: credentials must be enabled deliberately and only together with
    +    // an explicit AllowedOrigins list (never with wildcard origins).
    +    //
    +    "AllowCredentials": false,
    +    //
    +    // Maximum age in seconds for preflight request caching (10 minutes).
    +    //
    +    "PreflightMaxAgeSeconds": 600
    +  },
    +
    +  //
    +  // Security Headers: Adds HTTP security headers to all responses to protect against common web vulnerabilities.
    +  // These headers instruct browsers how to handle your content securely.
    +  // Note: X-Frame-Options is automatically handled by the Antiforgery middleware when enabled (see Antiforgery.SuppressXFrameOptionsHeader).
    +  // Reference: https://owasp.org/www-project-secure-headers/
    +  //
    +  "SecurityHeaders": {
    +    //
    +    // Enable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    +    //
    +    "Enabled": false,
    +    //
    +    // X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content-type.
    +    // Recommended value: "nosniff"
    +    // Set to null to not include this header.
    +    //
    +    "XContentTypeOptions": "nosniff",
    +    //
    +    // X-Frame-Options: Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>.
    +    // Values: "DENY" (never allow), "SAMEORIGIN" (allow from same origin only)
    +    // Note: This header is SKIPPED if Antiforgery is enabled (Antiforgery already sets X-Frame-Options: SAMEORIGIN by default).
    +    // Set to null to not include this header.
    +    //
    +    "XFrameOptions": "DENY",
    +    //
    +    // Referrer-Policy: Controls how much referrer information should be included with requests.
    +    // Values: "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin",
    +    //         "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"
    +    // Recommended: "strict-origin-when-cross-origin" (send origin for cross-origin requests, full URL for same-origin)
    +    // Set to null to not include this header.
    +    //
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    //
    +    // Content-Security-Policy: Defines approved sources of content that the browser may load.
    +    // Helps prevent XSS, clickjacking, and other code injection attacks.
    +    // Example: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +    // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    +    // Set to null to not include this header (recommended to configure based on your application needs).
    +    //
    +    "ContentSecurityPolicy": null,
    +    //
    +    // Permissions-Policy: Controls which browser features and APIs can be used.
    +    // Example: "geolocation=(), microphone=(), camera=()" disables these features entirely.
    +    // Example: "geolocation=(self), microphone=()" allows geolocation only from same origin.
    +    // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
    +    // Set to null to not include this header.
    +    //
    +    "PermissionsPolicy": null,
    +    //
    +    // Cross-Origin-Opener-Policy: Controls how your document is shared with cross-origin popups.
    +    // Values: "unsafe-none", "same-origin-allow-popups", "same-origin"
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginOpenerPolicy": null,
    +    //
    +    // Cross-Origin-Embedder-Policy: Prevents a document from loading cross-origin resources that don't explicitly grant permission.
    +    // Values: "unsafe-none", "require-corp", "credentialless"
    +    // Required for SharedArrayBuffer and high-resolution timers (along with COOP: same-origin).
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginEmbedderPolicy": null,
    +    //
    +    // Cross-Origin-Resource-Policy: Indicates how the resource should be shared cross-origin.
    +    // Values: "same-site", "same-origin", "cross-origin"
    +    // Set to null to not include this header.
    +    //
    +    "CrossOriginResourcePolicy": null
    +  },
    +
    +  //
    +  // Forwarded Headers: Enables the application to read proxy headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host).
    +  // CRITICAL: Required when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.)
    +  // Without this, the application sees the proxy's IP instead of the client's real IP, and HTTP instead of HTTPS.
    +  // Security Warning: Only enable if you're behind a trusted proxy. Malicious clients can spoof these headers.
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
    +  //
    +  "ForwardedHeaders": {
    +    //
    +    // Enable forwarded headers middleware (automatically placed first in the middleware pipeline).
    +    //
    +    "Enabled": false,
    +    //
    +    // Limits the number of proxy entries that will be processed from X-Forwarded-For.
    +    // Default is 1 (trust only the immediate proxy). Increase if you have multiple proxies in a chain.
    +    // Set to null to process all entries (not recommended for security).
    +    //
    +    "ForwardLimit": 1,
    +    //
    +    // List of IP addresses of known proxies to accept forwarded headers from.
    +    // Example: ["10.0.0.1", "192.168.1.1"]
    +    // If empty and KnownNetworks is also empty, forwarded headers are accepted from any source (less secure).
    +    //
    +    "KnownProxies": [],
    +    //
    +    // List of CIDR network ranges of known proxies.
    +    // Example: ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] for private networks
    +    // Useful when proxy IPs are dynamically assigned within a known range.
    +    //
    +    "KnownNetworks": [],
    +    //
    +    // List of allowed values for the X-Forwarded-Host header.
    +    // Example: ["example.com", "www.example.com"]
    +    // If empty, any host is allowed (less secure). Helps prevent host header injection attacks.
    +    //
    +    "AllowedHosts": []
    +  },
    +
    +  //
    +  // Health Checks: Provides endpoints for monitoring application health, used by container orchestrators (Kubernetes, Docker Swarm),
    +  // load balancers, and monitoring systems to determine if the application is running correctly.
    +  // Three types of checks are supported:
    +  //   - /health: Overall health status (combines all checks)
    +  //   - /health/ready: Readiness probe - is the app ready to accept traffic? (includes database connectivity)
    +  //   - /health/live: Liveness probe - is the app process running? (always returns healthy if app responds)
    +  // Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
    +  //
    +  "HealthChecks": {
    +    //
    +    // Enable health check endpoints.
    +    //
    +    "Enabled": false,
    +    //
    +    // Cache health check responses server-side in memory for the specified duration.
    +    // Cached responses are served without re-executing the endpoint.
    +    // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +    // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +    //
    +    "CacheDuration": "5 seconds",
    +    //
    +    // Path for the main health check endpoint that reports overall status.
    +    // Returns "Healthy", "Degraded", or "Unhealthy" with HTTP 200 (healthy/degraded) or 503 (unhealthy).
    +    //
    +    "Path": "/health",
    +    //
    +    // Path for the readiness probe endpoint.
    +    // Kubernetes uses this to know when a pod is ready to receive traffic.
    +    // Includes database connectivity check when IncludeDatabaseCheck is true.
    +    // Returns 503 Service Unavailable if database is unreachable.
    +    //
    +    "ReadyPath": "/health/ready",
    +    //
    +    // Path for the liveness probe endpoint.
    +    // Kubernetes uses this to know when to restart a pod.
    +    // Always returns Healthy (200) if the application process is responding.
    +    // Does NOT check database - a slow database shouldn't trigger a container restart.
    +    //
    +    "LivePath": "/health/live",
    +    //
    +    // Include PostgreSQL database connectivity in health checks.
    +    // When true, the readiness probe will fail if the database is unreachable.
    +    //
    +    "IncludeDatabaseCheck": true,
    +    //
    +    // Name for the database health check (appears in detailed health reports).
    +    //
    +    "DatabaseCheckName": "postgresql",
    +    //
    +    // Require authentication for health check endpoints.
    +    // When true, all health endpoints require a valid authenticated user.
    +    // Security Consideration: Health endpoints can reveal information about your infrastructure
    +    // (database connectivity, service status). Enable this if your health endpoints are publicly accessible.
    +    // Note: Kubernetes/Docker health probes may need to authenticate if this is enabled.
    +    //
    +    "RequireAuthorization": false,
    +    //
    +    // Apply a rate limiter policy to health check endpoints.
    +    // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +    // Security Consideration: Prevents denial-of-service attacks targeting health endpoints.
    +    // Set to null to disable rate limiting on health endpoints.
    +    // Example: "fixed" or "bucket" (must match a policy name from RateLimiterOptions).
    +    //
    +    "RateLimiterPolicy": null
    +  },
    +
    +  //
    +  // PostgreSQL Statistics Endpoints
    +  // Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging.
    +  // Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.
    +  //
    +  "Stats": {
    +    //
    +    // Enable PostgreSQL statistics endpoints.
    +    //
    +    "Enabled": false,
    +    //
    +    // Cache stats responses server-side in memory for the specified duration.
    +    // Cached responses are served without re-executing the endpoint.
    +    // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +    // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +    //
    +    "CacheDuration": "5 seconds",
    +    //
    +    // Apply a rate limiter policy to stats endpoints.
    +    // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +    // Set to null to disable rate limiting on stats endpoints.
    +    //
    +    "RateLimiterPolicy": null,
    +    //
    +    // Use a specific named connection for stats queries.
    +    // When null, uses the default connection string.
    +    // Useful when you want to query stats from a different database or use read-only credentials.
    +    //
    +    "ConnectionName": null,
    +    //
    +    // Require authentication for stats endpoints.
    +    // Security Consideration: Stats endpoints can reveal sensitive information about your database
    +    // (table sizes, query patterns, active sessions). Enable this for production environments.
    +    //
    +    "RequireAuthorization": false,
    +    //
    +    // Restrict access to specific roles.
    +    // When null or empty, any authenticated user can access (if RequireAuthorization is true).
    +    // Example: ["admin", "dba"] - only users with admin or dba role can access.
    +    //
    +    "AuthorizedRoles": [],
    +    //
    +    // Output format for stats endpoints: "json" or "html".
    +    // - json: JSON array
    +    // - html: HTML table, Excel-compatible for direct browser copy-paste (default)
    +    // Can be overridden per-request with the ?format= query string parameter (e.g. ?format=json).
    +    //
    +    "OutputFormat": "html",
    +    //
    +    // Filter schemas using PostgreSQL SIMILAR TO pattern.
    +    // When null, all schemas are included.
    +    // Example: "public|myapp%" - includes 'public' and schemas starting with 'myapp'.
    +    //
    +    "SchemaSimilarTo": null,
    +    //
    +    // Path for routine (function/procedure) performance statistics.
    +    // Returns data from pg_stat_user_functions including call counts and execution times.
    +    // Note: Requires track_functions = 'pl' or 'all' in postgresql.conf.
    +    // Enable with: alter system set track_functions = 'all'; select pg_reload_conf();
    +    // Or set track_functions = 'all' directly in postgresql.conf and restart/reload.
    +    //
    +    "RoutinesStatsPath": "/stats/routines",
    +    //
    +    // Path for table statistics.
    +    // Returns data from pg_stat_user_tables including tuple counts, sizes, scan counts, and vacuum info.
    +    //
    +    "TablesStatsPath": "/stats/tables",
    +    //
    +    // Path for index statistics.
    +    // Returns data from pg_stat_user_indexes including scan counts and index definitions.
    +    //
    +    "IndexesStatsPath": "/stats/indexes",
    +    //
    +    // Path for current database activity.
    +    // Returns data from pg_stat_activity showing active sessions, queries, and wait events.
    +    // Security Consideration: Shows currently running queries which may contain sensitive data.
    +    //
    +    "ActivityPath": "/stats/activity"
    +  },
    +
    +  //
    +  // SQL test runner. Invoked with the `--test` command-line flag (this section is otherwise inert).
    +  // Discovers *.test.sql files, runs each in an isolated non-pooled connection, can invoke endpoints
    +  // in-process from an embedded `/* GET /path */` block (response captured into a temp table), and
    +  // asserts via boolean-returning SELECTs or `do $$ ... assert ... $$;` blocks. Exit codes:
    +  // 0 pass, 1 failures, 2 errors, 3 config/runner error, 4 no tests found.
    +  //
    +  "TestRunner": {
    +    //
    +    // Glob (same engine as SqlFileSource) selecting test files. Empty disables discovery. Two layouts:
    +    // co-located (app.sql next to app.test.sql — SqlFileSource SkipPattern keeps tests out of the endpoints)
    +    // or a separate tests tree (e.g. "./tests/**/*.test.sql").
    +    //
    +    "FilePattern": "",
    +    //
    +    // Optional filter narrowing the discovered set — the fast path for iterating on one test:
    +    //   npgsqlrest ... --test --testrunner:filter=login
    +    // Matched against each file's cwd-relative path: a value without wildcards is a substring match;
    +    // with wildcards it is the same glob engine as FilePattern. Empty = run everything discovered.
    +    //
    +    "Filter": "",
    +    //
    +    // Tag filtering (comma- or whitespace-separated lists; case-insensitive). A test file declares tags
    +    // with a `-- @tag name [name ...]` header annotation. "Tag" runs only files carrying at least one of
    +    // the listed tags; "ExcludeTag" skips files carrying any of them (exclude wins). Composes with Filter.
    +    //   npgsqlrest ... --test --testrunner:tag=smoke --testrunner:excludetag=slow
    +    //
    +    "Tag": "",
    +    "ExcludeTag": "",
    +    //
    +    // Optional: a ConnectionStrings entry to run the tests against instead of the app's main connection. In
    +    // test mode it becomes the connection used for endpoint type-checking (Describe) and execution, so it can
    +    // point at a dedicated test database that a Setup step creates first (it need not exist at startup).
    +    // Empty = use the main connection. Tip: {rnd1}..{rnd10} are random lowercase tokens (length = the digit),
    +    // stable for the whole run, usable in any connection string or Setup/Teardown SQL (e.g. Database=app_test_{rnd6}).
    +    // Need several distinct tokens of the same length? Indexed instances {rndN_1}..{rndN_9} are each independent.
    +    //
    +    "ConnectionName": "",
    +    //
    +    // Max test files run concurrently. 0 => processor count. Each test uses its own non-pooled connection.
    +    //
    +    "MaxParallelism": 0,
    +    //
    +    // Stop scheduling new tests after the first failure/error (in-flight tests still finish).
    +    //
    +    "FailFast": false,
    +    //
    +    // Per-test timeout. Accepts "30s", "5m", "1h", a plain number of seconds, or "hh:mm:ss". 0 disables.
    +    //
    +    "PerTestTimeout": "30s",
    +    //
    +    // Optional path to also write a JUnit XML report (console output is always printed).
    +    //
    +    "JUnitOutput": null,
    +    //
    +    // Skip Teardown so a failed run's state can be inspected.
    +    //
    +    "Keep": false,
    +    //
    +    // Detailed console REPORT: list passed assertions, print the full failing SQL statement, and show
    +    // captured `raise notice` output for passing tests too. This shapes the report only — for diagnostic
    +    // logging of every executed query/HTTP call, raise the log channel instead (Log:MinimalLevels + LoggerName).
    +    //
    +    "DetailedReport": false,
    +    //
    +    // Treat "no tests discovered" as success (exit 0) instead of exit 4.
    +    //
    +    "AllowEmpty": false,
    +    //
    +    // Endpoint-coverage summary after the run: exercised N of M testable endpoints + the list of untested
    +    // ones (endpoint kinds the runner rejects — SSE, upload, login/logout, outbound proxy — are excluded
    +    // from the ratio and counted separately). Tri-state: null (default) reports after FULL runs but stays
    +    // quiet when the run is narrowed by Filter/Tag (a deliberately partial run would just nag); true always
    +    // reports; false never. CoverageThreshold (0-100) always reports and fails an otherwise-passing run
    +    // with exit 2 when coverage is below it — CI gating for "every endpoint has a test".
    +    //
    +    "Coverage": null,
    +    "CoverageThreshold": null,
    +    //
    +    // SourceContext name for the runner's own log channel; set its level independently under Log:MinimalLevels.
    +    // Discovery/parsing log at Debug, each query and HTTP invocation at Verbose, `raise notice` by severity.
    +    //
    +    "LoggerName": "NpgsqlRestTest",
    +    //
    +    // Per-HTTP-block temp table that captures the response. Each block gets its own fresh temp table
    +    // (created without IF NOT EXISTS, so a duplicate name fails the test); writes are pg_temp-qualified.
    +    // A file with ONE HTTP block uses "Name"; a file with 2+ blocks uses "MultiNamePattern" where {n} is
    +    // the 1-based block ordinal (_response_1, _response_2, ...). Per-block override: `# @response <name>`.
    +    // A null/empty column name omits that column. "DebugTable" (e.g. "_responses_debug"): ALSO mirror every
    +    // response into a PERMANENT table for post-run inspection — survives rollbacks, holds the last run, one
    +    // row per HTTP block with test_file/block/method/path metadata (debugging aid; do not enable in CI).
    +    //
    +    "ResponseTempTable": {
    +      "Name": "_response",
    +      "MultiNamePattern": "_response_{n}",
    +      "DebugTable": null,
    +      "Columns": {
    +        "Status": "status",
    +        "Body": "body",
    +        "ContentType": "content_type",
    +        "Headers": "headers",
    +        "IsSuccess": "is_success"
    +      }
    +    },
    +    //
    +    // Named, reusable steps (name → step; same shape as Setup/Teardown entries). Reference them by name in
    +    // Setup/Teardown below, or from an individual test file's leading header comments:
    +    //   -- @setup StepName [StepName ...]      runs before that file
    +    //   -- @teardown StepName [StepName ...]   runs after that file (always, best-effort)
    +    //   -- @connection Name                    runs that file on a named ConnectionStrings entry
    +    // Annotations are repeatable; names may be whitespace- or comma-separated and run in the order written.
    +    // Test files can also reuse SQL scripts in place with psql-style includes: `\i file` (cwd-relative) or
    +    // `\ir file` (relative to the including file) — executed on the test's connection, inside its transaction.
    +    //
    +    // The entries below are disabled EXAMPLES showing every step property ("Sql", "SqlFile", "Command",
    +    // "WorkingDirectory", "ConnectionName") across the typical scenarios — flip "Enabled" to true (and adjust
    +    // names, paths, and connections) instead of typing them from scratch. A step with "Enabled": false is
    +    // simply IGNORED wherever it is referenced.
    +    //
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    },
    +    //
    +    // Run-once setup, BEFORE endpoint discovery. Steps run in the EXACT order written. Each entry is a step
    +    // NAME from "Steps" above, or an inline step object:
    +    //   { "Command": "...", "WorkingDirectory": "..." }    — runs via the OS shell
    +    //   { "Sql": "..." } | { "SqlFile": "..." }            — runs on the test connection; set "ConnectionName"
    +    //                                                         to run on another ConnectionStrings entry (e.g.
    +    //                                                         an admin connection that runs `create database`).
    +    //
    +    "Setup": [],
    +    //
    +    // Run-once teardown, ALWAYS (best-effort), in the EXACT order written. `Keep` skips it. Same entries as Setup.
    +    //
    +    "Teardown": []
    +  },
    +
    +  //
    +  // Watch mode (interactive/dev-only) — one feature, two flavors: with --test it re-runs tests on
    +  // changes (a changed test file re-runs alone; a changed endpoint file or database routine rebuilds
    +  // endpoints in-process and re-runs everything; teardown runs once, on exit); without --test it
    +  // supervises the SERVER and restarts it on SQL file source, configuration, and database routine
    +  // changes. In both flavors a broken SQL file cannot kill the session (SqlFileSource ErrorMode is
    +  // forced from Exit to Skip while watching).
    +  //
    +  "Watch": {
    +    //
    +    // Turn watch mode on. The --watch command line flag is the shorthand for this setting.
    +    //
    +    "Enabled": false,
    +    //
    +    // Poll the database for routine changes and restart the server (server watch) or rebuild endpoints and
    +    // re-run the tests (test watch). The poll runs the SAME routine discovery query the endpoint source
    +    // uses (same configured filters), hashed server-side into one value — so it detects exactly what
    +    // changes discovered endpoints: functions/procedures (create/replace/drop/alter, grants), their
    +    // COMMENT ON annotations, and the composite types and tables their signatures use; anything the
    +    // discovery does not read can never trigger. Accepts "2s", "500ms", "1m", a plain number of seconds,
    +    // or "hh:mm:ss"; 0 disables. One query per interval on a dedicated non-pooled connection.
    +    //
    +    "DatabasePollingInterval": "2s"
    +  },
    +
    +  //
    +  // Command retry strategies and options for client and middleware commands.
    +  //
    +  "CommandRetryOptions": {
    +    "Enabled": true,
    +    "DefaultStrategy": "default",
    +    "Strategies": {
    +      "default": {
    +        //
    +        // Retry sequence in seconds. Accepts decimal numbers (0.25 is quarter of a second). The length of the array determines the maximum number of retries.
    +        //
    +        "RetrySequenceSeconds": [0, 1, 2, 5, 10],
    +        //
    +        // Error codes that will trigger a retry when executing a command. See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +        //
    +        "ErrorCodes": [
    +          // Serialization failures (MUST retry for correctness)
    +          "40001", // serialization_failure 
    +          "40P01", // deadlock_detected
    +          // Connection issues (Class 08)
    +          "08000", // connection_exception
    +          "08003", // connection_does_not_exist
    +          "08006", // connection_failure  
    +          "08001", // sqlclient_unable_to_establish_sqlconnection
    +          "08004", // sqlserver_rejected_establishment_of_sqlconnection
    +          "08007", // transaction_resolution_unknown
    +          "08P01", // protocol_violation
    +          // Resource constraints (Class 53)
    +          "53000", // insufficient_resources
    +          "53100", // disk_full
    +          "53200", // out_of_memory
    +          "53300", // too_many_connections
    +          "53400", // configuration_limit_exceeded
    +          // System errors (Class 58) 
    +          "57P01", // admin_shutdown
    +          "57P02", // crash_shutdown  
    +          "57P03", // cannot_connect_now
    +          "58000", // system_error
    +          "58030", // io_error
    +          // Lock acquisition issues (Class 55)
    +          "55P03", // lock_not_available
    +          "55006", // object_in_use
    +          "55000"  // object_not_in_prerequisite_state
    +        ]
    +      }
    +    }
    +  },
    +  
    +  //
    +  // Caching options for routines that support caching. Currently, routines that return a single result set can be cached. Returning table or "setof" cannot be cached.
    +  // To enable caching for a routine, add the following comment annotation to the routine:
    +  // cached [ param1, param2, param3 [, ...] ] - parameters are optional, if no parameters are specified, all parameters are used for cache key.
    +  // cache_expires [ value ] or cache_expires_in [ value ] - accepts PostgreSQL interval format (for example: '5 minutes' or '5min', '1 second' or '1s', etc.). Default is forever (no expiration).
    +  // 
    +  "CacheOptions": {
    +    "Enabled": false,
    +    //
    +    // Cache type: Memory, Redis, or Hybrid
    +    // - Memory: In-process memory cache (fastest, single instance only)
    +    // - Redis: Distributed Redis cache (slower, shared across instances)
    +    // - Hybrid: Uses Microsoft.Extensions.Caching.Hybrid which provides:
    +    //   - Automatic stampede protection to prevent multiple concurrent requests from hitting the database
    +    //   - Optional Redis L2 backend (enable with HybridCacheUseRedisBackend: true) for sharing cache across instances
    +    //   - Without Redis, works as in-memory cache with stampede protection
    +    //
    +    "Type": "Memory",
    +    //
    +    // When memory cache is used, this value determines how often the cache will be pruned for expired items (in seconds).
    +    //
    +    "MemoryCachePruneIntervalSeconds": 60,
    +    //
    +    // Redis configuration string. Used when Type is "Redis", or when Type is "Hybrid" with UseRedisBackend: true.
    +    // See: https://stackexchange.github.io/StackExchange.Redis/Configuration.html
    +    //
    +    "RedisConfiguration": "localhost:6379,abortConnect=false,ssl=false,connectTimeout=10000,syncTimeout=5000,connectRetry=3",
    +    //
    +    // Maximum number of rows that can be cached for set-returning functions.
    +    // If a result set exceeds this limit, it will not be cached (but will still be returned).
    +    // Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    +    //
    +    "MaxCacheableRows": 1000,
    +    //
    +    // When true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string (64 characters).
    +    // This reduces memory usage for long cache keys and improves Redis performance with large keys.
    +    // Recommended for Redis cache or when caching routines with many/large parameters.
    +    //
    +    "UseHashedCacheKeys": false,
    +    //
    +    // Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true.
    +    // Keys shorter than this threshold are stored as-is for better debuggability.
    +    //
    +    "HashKeyThreshold": 256,
    +    //
    +    // When set, creates an additional invalidation endpoint for each cached endpoint.
    +    // The invalidation endpoint has the same path with this suffix appended.
    +    // For example, if a cached endpoint is /api/my-endpoint/ and this is set to "invalidate",
    +    // an invalidation endpoint /api/my-endpoint/invalidate will be created.
    +    // Calling the invalidation endpoint with the same parameters removes the cached entry.
    +    //
    +    "InvalidateCacheSuffix": null,
    +    //
    +    // --- Hybrid Cache specific options (only used when Type is "Hybrid") ---
    +    //
    +    // When true, uses Redis as the L2 (secondary/distributed) cache backend.
    +    // When false (default), HybridCache uses in-memory only but still provides stampede protection.
    +    // Stampede protection prevents multiple concurrent requests from hitting the database when cache expires.
    +    //
    +    "HybridCacheUseRedisBackend": false,
    +    //
    +    // Maximum length of cache keys in characters. Keys longer than this will be rejected.
    +    // Default: 1024
    +    //
    +    "HybridCacheMaximumKeyLength": 1024,
    +    //
    +    // Maximum size of cached payloads in bytes.
    +    // Default: 1048576 (1 MB)
    +    //
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    //
    +    // Default expiration for cached entries (both L1 and L2). Accepts PostgreSQL interval format.
    +    // Examples: '5 minutes', '1 hour', '30 seconds'
    +    // If not set, individual endpoint cache_expires annotations are used, or entries don't expire.
    +    //
    +    "HybridCacheDefaultExpiration": null,
    +    //
    +    // Expiration for L1 (in-memory) cache. If not set, uses DefaultExpiration value.
    +    // Set this shorter than DefaultExpiration to refresh local cache more frequently from Redis.
    +    // Accepts PostgreSQL interval format.
    +    //
    +    "HybridCacheLocalCacheExpiration": null,
    +    //
    +    // Named caching profiles. Endpoints opt into a profile via the `cache_profile <name>` comment annotation;
    +    // the profile then supplies the cache backend, default expiration, default key parameters, and per-parameter
    +    // skip-cache conditions. Endpoints WITHOUT `cache_profile` continue to use the root cache configured above.
    +    //
    +    // Each profile object supports:
    +    //   - "Enabled" (bool, default false): set to true to register the profile. Disabled profiles are ignored.
    +    //   - "Type": "Memory" | "Redis" | "Hybrid" (required when enabled). Backends are pooled — all profiles of the
    +    //     same type share one instance (one Memory cache, one Redis connection, one HybridCache singleton).
    +    //     A backend is only instantiated if its type is used by the root or some enabled profile.
    +    //   - "Expiration": PostgreSQL interval (e.g. "30 seconds", "5 minutes"); used as the default when the
    +    //     endpoint has no `cache_expires` annotation. The annotation overrides this.
    +    //   - "Parameters": cache-key parameter list with three semantics:
    +    //       null/missing  → use ALL routine parameters (different requests → different entries).
    +    //       []            → URL-only cache (one entry per endpoint, regardless of inputs).
    +    //       ["x", "y"]    → use only these named parameters as the key.
    +    //     The endpoint's `cached p1, p2` annotation overrides this list.
    +    //   - "When": optional list of conditional rules. Each rule has:
    +    //       - "Parameter": routine parameter name to inspect.
    +    //       - "Value": scalar (exact match) or array (OR over entries). JSON null matches .NET null/DBNull (NOT empty string).
    +    //       - "Then": "skip" → bypass the cache for this request; or a PostgreSQL interval like "30 seconds" → use this
    +    //                 as the TTL override when writing.
    +    //     Rules are evaluated in declaration order; first match wins. No match → fall through to "Expiration" above.
    +    //     A rule's Parameter must be in the profile's "Parameters" list (or in the endpoint's @cached annotation),
    +    //     otherwise the rule is dropped at startup with a Warning.
    +    //
    +    // Common patterns:
    +    //   - Skip cache when a date is null (always-fresh data):
    +    //       "When": [ { "Parameter": "to", "Value": null, "Then": "skip" } ]
    +    //   - Tiered TTL by user role:
    +    //       "When": [
    +    //         { "Parameter": "tier", "Value": "free", "Then": "5 minutes" },
    +    //         { "Parameter": "tier", "Value": "pro",  "Then": "1 hour" }
    +    //       ]
    +    //
    +    // Unknown profile names referenced by `@cache_profile` cause startup to fail with a single error listing all
    +    // typos and the offending endpoints. Unused profiles (registered but not referenced) log an Information warning.
    +    //
    +    // Three disabled example profiles below show all three Types and all four profile fields. Flip "Enabled": true
    +    // on the one(s) you want to use.
    +    //
    +    "Profiles": {
    +      "fast_memory": {
    +        "Enabled": false,
    +        "Type": "Memory",
    +        "Expiration": "30 seconds",
    +        "Parameters": ["user_id"]
    +      },
    +      "shared_redis": {
    +        "Enabled": false,
    +        "Type": "Redis",
    +        "Expiration": "1 hour"
    +      },
    +      "date_range_hybrid": {
    +        "Enabled": false,
    +        "Type": "Hybrid",
    +        "Expiration": "5 minutes",
    +        "Parameters": ["from", "to"],
    +        "When": [
    +          { "Parameter": "to", "Value": null, "Then": "skip" }
    +        ]
    +      }
    +    }
    +  },
    +
    +  //
    +  // Parameter validation options for validating endpoint parameters before database execution.
    +  // Validation rules can be referenced in comment annotations using "validate _param using rule_name" syntax.
    +  //
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    //
    +    // Named validation rules that can be referenced in comment annotations.
    +    // Default rules: not_null, not_empty, required, email
    +    //
    +    // Each rule can have:
    +    // - Type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    +    // - Pattern: Regular expression pattern for Regex type
    +    // - MinLength: Minimum length for MinLength type
    +    // - MaxLength: Maximum length for MaxLength type
    +    // - Message: Error message with placeholders {0}=original name, {1}=converted name, {2}=rule name
    +    // - StatusCode: HTTP status code to return (default: 400)
    +    //
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  },
    +
    +  //
    +  // Rate Limiter settings to limit the number of requests from clients.
    +  //
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    // Global defaults for rejected (rate-limited) requests. Each policy below may override either of these
    +    // independently via its own "StatusCode"/"StatusMessage" — useful when one policy guards logins and
    +    // another guards a public API and each needs its own message. A policy that omits them inherits these.
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    //
    +    // Named rate-limiter policies. The object key is the policy name (referenced from endpoints via the
    +    // `rate_limiter_policy <name>` comment annotation, or used as `DefaultPolicy` above).
    +    //
    +    // Each policy has a `Type` of FixedWindow, SlidingWindow, TokenBucket, or Concurrency, and a set of
    +    // type-specific tuning fields. Set `"Enabled": true` to register the policy at startup.
    +    //
    +    // Each policy may also set its own `StatusCode` and/or `StatusMessage` to override the global values
    +    // above for requests rejected by that specific policy. Omit either to inherit the global default. The
    +    // "fixed" policy below shows the commented-out fields.
    +    //
    +    // **Breaking change in 3.13.0**: this section was previously an array of objects with explicit `"Name"`
    +    // properties. It is now an object keyed by policy name, matching `ValidationOptions:Rules` and
    +    // `CacheOptions:Profiles`. Migrate by moving each policy's `Name` value to be the JSON key and dropping
    +    // the `Name` field.
    +    //
    +    "Policies": {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#fixed
    +      "fixed": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +        // Override the global rejection response for this policy only (omit to inherit the global values):
    +        // "StatusCode": 429,
    +        // "StatusMessage": "Too many requests for this endpoint. Please slow down.",
    +        //
    +        // To partition this policy (per-user, per-IP, etc.) so each request gets its own bucket
    +        // instead of sharing a single global one, add a "Partition" block. See the "per_user"
    +        // policy below for a complete example.
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#sliding-window-limiter
    +      "sliding": {
    +        "Type": "SlidingWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "SegmentsPerWindow": 6,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#token-bucket-limiter
    +      "bucket": {
    +        "Type": "TokenBucket",
    +        "Enabled": false,
    +        "TokenLimit": 100,
    +        "TokensPerPeriod": 10,
    +        "ReplenishmentPeriodSeconds": 10,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#concurrency-limiter
    +      "concurrency": {
    +        "Type": "Concurrency",
    +        "Enabled": false,
    +        "PermitLimit": 10,
    +        "QueueLimit": 5,
    +        "OldestFirst": true
    +      },
    +      //
    +      // Example of a partitioned policy. With "Partition" set, each request resolves a partition key
    +      // (per-user, per-IP, etc.) and gets its own bucket. Without "Partition", all requests under a
    +      // policy share a single global bucket. Any of the four limiter Types above can be partitioned —
    +      // FixedWindow is shown here only because it is the most common.
    +      //
    +      // Sources (ordered) — first source returning a non-empty key wins. Fallback "unpartitioned"
    +      // is used if no source matches. Source types:
    +      //   { "Type": "Claim",     "Name": "<claim type>" }
    +      //   { "Type": "IpAddress" }
    +      //   { "Type": "Header",    "Name": "<header name>" }
    +      //   { "Type": "Static",    "Value": "<literal key>" }   ← terminal fallback
    +      //
    +      // BypassAuthenticated (bool, default false) — when true, authenticated users skip rate
    +      // limiting entirely. Evaluated before Sources, useful for "throttle anonymous only".
    +      //
    +      "per_user": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true,
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "Claim",     "Name": "name_identifier" },
    +            { "Type": "IpAddress" },
    +            { "Type": "Static",    "Value": "anonymous" }
    +          ],
    +          "BypassAuthenticated": false
    +        }
    +      },
    +      //
    +      // Ready-to-use login throttle: 10 attempts per minute per client IP, with its own rejection
    +      // message. Apply it to a login endpoint with `rate_limiter login_throttle` (or set it as
    +      // DefaultPolicy). Set "Enabled": true to activate.
    +      //
    +      "login_throttle": {
    +        "Type": "FixedWindow",
    +        "Enabled": false,
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 0,
    +        "AutoReplenishment": true,
    +        "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "IpAddress" }
    +          ],
    +          "BypassAuthenticated": false
    +        }
    +      }
    +    }
    +  },
    +  
    +  //
    +  // Error handling options for NpgsqlRest middleware
    +  //
    +  "ErrorHandlingOptions": {
    +    // Remove Type URL from error responses. Middleware automatically sets a default Type URL based on the HTTP status code that points to the RFC documentation.
    +    "RemoveTypeUrl": false,
    +    // Remove TraceId field from error responses. Useful in development and debugging scenarios to correlate logs with error responses.
    +    "RemoveTraceId": true,
    +    //
    +    // Default policy name to use from the ErrorCodePolicies section.
    +    //
    +    "DefaultErrorCodePolicy": "Default",
    +    //
    +    // Timeout error mapping when command timeout occurs (see NpgsqlRest CommandTimeout setting).
    +    //
    +    "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}, // timeout error case -> 504 Gateway Timeout
    +    //
    +    // Named policies for mapping of PostgreSQL error codes to HTTP Status Codes.
    +    //
    +    // If routine raises these PostgreSQL error codes, endpoint will return these HTTP Status Codes.
    +    // See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +    // Exception is timeout, which is not a PostgreSQL error code, but a special case when command timeout occurs.
    +    //
    +    // - StatusCode: HTTP status code to return.
    +    // - Title: Optional title field in response JSON. When null, actual error message is used.
    +    // - Details: Optional details field in response JSON. When null, PostgreSQL Error Code is used.
    +    // - Type: Optional types field in response JSON. A URI reference [RFC3986] that identifies the problem type. Set to null to use default. Or RemoveTypeUrl to true to disable.
    +    //
    +    "ErrorCodePolicies": [{
    +      "Name": "Default",
    +      "ErrorCodes": {
    +        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},   // query_canceled      -> 403 Forbidden
    +        "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},                // query_canceled      -> 205 Reset Content
    +        "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // raise_exception     -> 400 Bad Request
    +        "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null}                        // assert_failure      -> 400 Bad Request
    +      }
    +    }]
    +  },
    +  
    +  //
    +  // NpgsqlRest HTTP Middleware General Configuration
    +  //
    +  "NpgsqlRest": {
    +    //
    +    // Connection name to be used from the ConnectionStrings section or NULL to use the first available connection string.
    +    //
    +    "ConnectionName": null,
    +    //
    +    // Allow using multiple connections from the ConnectionStrings section. When set to true, the connection name can be set for individual Routines.
    +    // Some routines might use the primary database connection string, while others might want to use a read-only connection string from the replica servers.
    +    //
    +    "UseMultipleConnections": false,
    +    //
    +    // Command timeout, after which the command will be cancelled and default timeout error policy will be applied. (see ErrorCodePolicies) 
    +    // Value is in PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) or `null` to use the default timeout of 30 seconds.
    +    //
    +    "CommandTimeout": null,
    +    //
    +    // Filter schema names similar to this parameter or `null` to ignore this parameter.
    +    //
    +    "SchemaSimilarTo": null,
    +    //
    +    // Filter schema names NOT similar to this parameter or `null` to ignore this parameter.
    +    //
    +    "SchemaNotSimilarTo": null,
    +    //
    +    // List of schema names to be included or `null` to ignore this parameter.
    +    //
    +    "IncludeSchemas": null,
    +    //
    +    // List of schema names to be excluded or `null` to ignore this parameter.
    +    //
    +    "ExcludeSchemas": null,
    +    //
    +    // Filter names similar to this parameter or `null` to ignore this parameter.
    +    //
    +    "NameSimilarTo": null,
    +    //
    +    // Filter names NOT similar to this parameter or `null` to ignore this parameter.
    +    //
    +    "NameNotSimilarTo": null,
    +    //
    +    // List of names to be included or `null` to ignore this parameter.
    +    //
    +    "IncludeNames": null,
    +    //
    +    // List of names to be excluded or `null` to ignore this parameter.
    +    //
    +    "ExcludeNames": null,
    +    //
    +    // Configure how comment annotations behave and which routines become endpoints. `Ignore` creates all endpoints and ignores annotations. `ParseAll` creates all endpoints and parses annotations. `OnlyAnnotated` (default) creates only routines whose comment has a recognized exposure tag — an `HTTP` tag, or a plugin annotation that requests an endpoint (e.g. `mcp`); modifier-only comments (e.g. just `authorize`) create nothing. `OnlyWithHttpTag` is a backward-compatible alias of `OnlyAnnotated` (identical behavior; existing configs keep working).
    +    //
    +    "CommentsMode": "OnlyAnnotated",
    +    //
    +    // The URL prefix string for every URL created by the default URL builder or `null` to ignore the URL prefix.
    +    //
    +    "UrlPathPrefix": "/api",
    +    //
    +    // Convert all URL paths to kebab-case from the original PostgreSQL names.
    +    //
    +    "KebabCaseUrls": true,
    +    //
    +    // Convert all parameter names to camel case from the original PostgreSQL paramater names.
    +    //
    +    "CamelCaseNames": true,
    +    //
    +    // When set to true, it will force all created endpoints to require authorization. Authorization requirements for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "RequiresAuthorization": true,
    +    //
    +    // When this value is true, all connection events are logged (depending on the level). This is usually triggered by the PostgreSQL RAISE statements. 
    +    // Set to false to turn off logging these events.
    +    //
    +    "LogConnectionNoticeEvents": true,
    +    //
    +    // MessageOnly - Log only connection messages. FirstStackFrameAndMessage - Log first stack frame and the message. FullStackAndMessage - Log full stack trace and message.
    +    //
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    //
    +    // Set this option to true to log information for every executed command and query (including parameters and parameter values) in debug level.
    +    //
    +    "LogCommands": false,
    +    //
    +    // Set this option to true to include parameter values when logging commands. This only applies when `LogCommands` is true.
    +    //
    +    "LogCommandParameters": false,
    +    //
    +    // Set this option to false to suppress debug-level logs when endpoints are created.
    +    // When true (default), debug logs are emitted for each endpoint creation showing URL and method.
    +    //
    +    "DebugLogEndpointCreateEvents": true,
    +    //
    +    // Set this option to false to suppress debug-level logs when comment annotations are parsed.
    +    // When true (default), debug logs are emitted for each comment annotation that is successfully processed.
    +    //
    +    "DebugLogCommentAnnotationEvents": true,
    +    //
    +    // When not null, forces a method type for all created endpoints. Method types are `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `OPTIONS`, `TRACE`, `PATCH` or `CONNECT`. When this value is null (default), the method type is always `GET` when the routine volatility option is not volatile or the routine name starts with, `get_`, contains `_get_` or ends with `_get` (case-insensitive). Otherwise, it is `POST`. This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "DefaultHttpMethod": null,
    +    //
    +    // When not null, sets the request parameter position (request parameter types) for all created endpoints. Values are `QueryString` (parameters are sent using query string) or `BodyJson` (parameters are sent using JSON request body). When this value is null (default), request parameter type is `QueryString` for all `GET` and `DELETE` endpoints, otherwise, request parameter type is `BodyJson`. This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "DefaultRequestParamType": null,
    +    //
    +    // Sets the default behavior for handling NULL values in query string parameters.
    +    // - `Ignore` (default): No special handling - empty strings stay as empty strings, "null" literal stays as "null" string.
    +    // - `EmptyString`: Empty query string values are interpreted as NULL values. This limits sending empty strings via query strings.
    +    // - `NullLiteral`: Literal string "null" (case insensitive) is interpreted as NULL value.
    +    // This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "QueryStringNullHandling": "Ignore",
    +    //
    +    // Sets the default behavior for plain text responses when the execution returns NULL from the database.
    +    // - `EmptyString` (default): Returns an empty string response with status code 200 OK.
    +    // - `NullLiteral`: Returns a string literal "NULL" with status code 200 OK.
    +    // - `NoContent`: Returns status code 204 NO CONTENT.
    +    // This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "TextResponseNullHandling": "EmptyString",
    +    //
    +    // Configure how to send request headers to PostgreSQL routines execution: 
    +    // - `Ignore` (default) don't send any request headers to routines. 
    +    // - `Context` sets a context variable for the current session `context.headers` containing JSON string with current request headers. This executes `set_config('context.headers', headers, false)` before any routine executions. 
    +    // - `Parameter` sends request headers to the routine parameter defined with the `RequestHeadersParameterName` option. Parameter with this name must exist, must be one of the JSON or text types and must have the default value defined. This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "RequestHeadersMode": "Parameter",
    +    //
    +    // Name of the context variable that will receive the request headers when RequestHeadersMode is set to Context.
    +    //
    +    "RequestHeadersContextKey": "request.headers",
    +    //
    +    // Sets a parameter name that will receive a request headers JSON when the `Parameter` value is used in `RequestHeadersMode` options. A parameter with this name must exist, must be one of the JSON or text types and must have the default value defined. This option for individual endpoints can be changed with the `EndpointCreated` function callback, or by using comment annotations.
    +    //
    +    "RequestHeadersParameterName": "_headers",
    +    //
    +    // When true, EVERY request is wrapped in an explicit BEGIN/COMMIT, and all `set_config` calls switch to the
    +    // transaction-local form (`is_local=true`).
    +    // Required when using a connection pooler in transaction mode (PgBouncer transaction-pool, AWS RDS Proxy in transaction mode,
    +    // Supabase Pooler) — without this, the backend can be reused across unrelated requests, allowing GUC state (and the routine
    +    // call itself) to leak or split mid-request. Default false to preserve existing behavior; safe to leave off when using Npgsql's native pool only.
    +    //
    +    "WrapInTransaction": false,
    +    //
    +    // Controls how JSON datetime strings are interpreted when bound to timestamp / timestamptz / time / timetz parameters.
    +    // When true (default since 3.16.0), Z- and offset-bearing strings are converted to UTC, and naive strings (no offset, no Z)
    +    // are assumed UTC. Stored values are then identical regardless of the host process's TZ environment.
    +    // When false, the legacy pre-3.16.0 behavior is restored: DateTime.TryParse interprets the string in the host's local time
    +    // zone, which silently shifts stored values by the host's UTC offset on non-UTC hosts. Only set to false if you have callers
    +    // that depend on host-local interpretation of naive datetime strings and you cannot update them to send Z-suffixed values.
    +    //
    +    "JsonTimestampsAreUtc": true,
    +    //
    +    // SQL commands executed after any context is set but before the main routine call. Run in the same batch as the
    +    // context `set_config` calls (no extra round-trip). Each entry can be either:
    +    //   - A raw SQL string (always runs, no parameters), or
    +    //   - An object with `Enabled`, `Sql`, and optional `Parameters`. Object entries are gated by `Enabled` (default false) —
    +    //     set `"Enabled": true` to activate.
    +    // Each parameter has a `Source` (Claim, RequestHeader, or IpAddress) and an optional `Name` (claim type or header name;
    +    // ignored for IpAddress). Values are bound at request time via parameterized SQL.
    +    // Common use case: setting `search_path` from a tenant claim for multi-tenant deployments.
    +    // Combine with `WrapInTransaction = true` for transaction-local scoping (required for connection poolers in transaction mode).
    +    //
    +    "BeforeRoutineCommands": [
    +      {
    +        "Enabled": false,
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [
    +          { "Source": "Claim", "Name": "tenant_id" }
    +        ]
    +      }
    +    ],
    +    //
    +    // Add the unique NpgsqlRest instance id request header with this name to the response or set to null to ignore.
    +    //
    +    "InstanceIdRequestHeaderName": null,
    +    //
    +    // Custom request headers dictionary that will be added to NpgsqlRest requests. Note: these values are added to the request headers dictionary before they are sent as a context or parameter to the PostgreSQL routine and as such not visible to the browser debugger.
    +    //
    +    "CustomRequestHeaders": {
    +    },
    +    //
    +    // Allowlist of environment variable names available to {name} placeholder substitution in comment annotation values (response headers, custom parameters, HTTP custom type calls), alongside the routine's parameters. Resolved once at startup (a value change requires a restart). Array form lists names (a missing variable becomes the empty string); object form maps name -> default {"WEATHER_API_KEY":""} where the default is used when the variable is absent. Names are matched case-insensitively, and a routine parameter of the same name takes precedence. SECURITY: a value used in a RESPONSE header is sent to the client - reserve secrets (API keys, tokens) for outbound HTTP custom type calls, and use response headers only for non-secret values (e.g. server/environment name).
    +    //
    +    "AvailableEnvVars": [],
    +    //
    +    // Name of the request ID header that will be used to track requests. This is used to correlate requests with server event streaming connection ids.
    +    //
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +    //
    +    // Default server-sent event notice message level: INFO, NOTICE, WARNING.
    +    // When SSE path is set, generate SSE events for PostgreSQL notice messages with this level or higher.
    +    // This can be overridden for individual endpoints using comment annotations.
    +    //
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    //
    +    // 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.
    +    //
    +    "ServerSentEventsResponseHeaders": {
    +    },
    +    //
    +    // When true (default), log a one-time warning per endpoint when a RAISE at the configured SSE notice level fires inside a routine
    +    // that has no @sse or @sse_publish annotation. Notices are logged but NOT broadcast to SSE subscribers; the warning surfaces the
    +    // likely missing annotation. Inactive when no endpoint in the build participates in SSE publishing — projects that don't use SSE
    +    // pay zero overhead and see no warnings.
    +    //
    +    "WarnUnboundServerSentEventsNotices": true,
    +    //
    +    // Options for handling PostgreSQL routines (functions and procedures)
    +    //
    +    "RoutineOptions": {
    +      //
    +      // Set to false to disable the routine source (PostgreSQL functions and procedures). Default is true.
    +      //
    +      "Enabled": true,
    +      //
    +      // Name separator for parameter names when using custom type parameters. 
    +      // Parameter names will be in the format: {ParameterName}{CustomTypeParameterSeparator}{CustomTypeFieldName}. When NULL, default underscore is used.
    +      // This is used when using custom types for parameters. For example: with "create type custom_type1 as (value text);" and parameter "_p custom_type1", this name will be merged into "_p_value"
    +      //
    +      "CustomTypeParameterSeparator": null,
    +      //
    +      // List of PostgreSQL routine language names to include. If NULL, all languages are included. Names are case-insensitive.
    +      //
    +      "IncludeLanguages": null,
    +      //
    +      // List of PostgreSQL routine language names to exclude. If NULL, "C" and "INTERNAL" are excluded by default. Names are case-insensitive.
    +      //
    +      "ExcludeLanguages": null,
    +      //
    +      // When true, composite type columns in return tables are serialized as nested JSON objects.
    +      // For example, a table column "req" of type "my_request(id int, name text)" becomes {"req": {"id": 1, "name": "test"}}
    +      // instead of the default flat structure {"id": 1, "name": "test"}.
    +      // Default is false for backward compatibility.
    +      //
    +      "NestedJsonForCompositeTypes": false,
    +      //
    +      // When true, nested composite types and arrays of composite types within composite fields
    +      // are serialized as JSON objects/arrays instead of PostgreSQL tuple strings.
    +      // For example, a nested composite "(1,x)" becomes {"id":1,"name":"x"} and
    +      // an array of composites ["(1,a)","(2,b)"] becomes [{"id":1,"name":"a"},{"id":2,"name":"b"}].
    +      // Default is true.
    +      //
    +      "ResolveNestedCompositeTypes": true
    +    },
    +
    +    //
    +    // Options for different upload handlers and general upload settings
    +    //
    +    "UploadOptions": {
    +      "Enabled": false,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      //
    +      // Handler that will be used when upload handler or handlers are not specified.
    +      //
    +      "DefaultUploadHandler": "large_object",
    +      //
    +      // Gets or sets a value indicating whether the default upload metadata parameter should be used.
    +      //
    +      "UseDefaultUploadMetadataParameter": false,
    +      //
    +      // Name of the default upload metadata parameter. This parameter is used to pass metadata to the upload handler. The metadata is passed as a JSON object.
    +      //
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      //
    +      // Gets or sets a value indicating whether the default upload metadata context key should be used.
    +      //
    +      "UseDefaultUploadMetadataContextKey": false,
    +      //
    +      // Name of the default upload metadata context key. This key is used to pass the metadata to the upload handler. The metadata is passed as a JSON object.
    +      //
    +      "DefaultUploadMetadataContextKey": "request.upload_metadata",
    +      //
    +      // Upload handlers specific settings.
    +      //
    +      "UploadHandlers": {
    +        //
    +        // General settings for all upload handlers
    +        //
    +        "StopAfterFirstSuccess": false,
    +        // csv string containing mime type patters, set to null to ignore
    +        "IncludedMimeTypePatterns": null,
    +        // csv string containing mime type patters, set to null to ignore
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 8192, // Buffer size for the upload handlers file_system and large_object, in bytes. Default is 8192 bytes (8 KB).
    +        "TextTestBufferSize": 4096, // Buffer sample size for testing textual content, in bytes. Default is 4096 bytes (4 KB).
    +        "TextNonPrintableThreshold": 5, // Threshold for non-printable characters in the text buffer. Default is 5 non-printable characters.
    +        "AllowedImageTypes": "jpeg, png, gif, bmp, tiff, webp", // Comma-separated list of allowed image types when checking images.
    +        //
    +        // When set, authenticated user claims are included in the row metadata JSON parameter ($4) under this key name.
    +        // Set to null or empty string to disable adding claims to row metadata. Example: "claims" adds {"claims": {...}} to metadata.
    +        // Access in SQL: (_meta->'claims'->>'name_identifier')
    +        //
    +        "RowCommandUserClaimsKey": "claims",
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uses PostgreSQL Large Objects API
    +        //
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false,
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uses file system
    +        //
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false,
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uploads CSV files to a row command
    +        //
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        //
    +        // $1 - row index (1-based), $2 - parsed value text array, $3 - result of previous row command, $4 - JSON metadata for upload
    +        //
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)",
    +        //
    +        // Enables upload handlers for the NpgsqlRest endpoints that uploads Excel files to a row command
    +        //
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null, // null to use the first available
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        //
    +        // $1 - row index (1-based), $2 - parsed value text array, $3 - result of previous row command, $4 - JSON metadata for upload
    +        //
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    },
    +
    +    //
    +    // Table format handlers for custom rendering of set/record results.
    +    // When an endpoint has @table_format = <name> custom parameter, the matching handler renders the response.
    +    //
    +    "TableFormatOptions": {
    +      //
    +      // Enable or disable table format handlers. When false, @table_format annotations are ignored.
    +      //
    +      "Enabled": false,
    +      //
    +      // Built-in HTML table handler. Renders results as an HTML table for easy copy-paste to Excel.
    +      // Activated by @table_format = html annotation on PostgreSQL functions.
    +      //
    +      "HtmlEnabled": true,
    +      //
    +      // The key name used to match @table_format = <key> annotation. Default is "html".
    +      //
    +      "HtmlKey": "html",
    +      //
    +      // Content written before the HTML table. Typically a CSS style block.
    +      // Set to null to omit.
    +      //
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      //
    +      // Content written after the closing HTML table tag.
    +      // Set to null to omit.
    +      //
    +      "HtmlFooter": null,
    +      //
    +      // Built-in Excel (.xlsx) handler using SpreadCheetah. Renders results as an Excel spreadsheet download.
    +      // Activated by @table_format = excel annotation on PostgreSQL functions.
    +      //
    +      "ExcelEnabled": true,
    +      //
    +      // The key name used to match @table_format = <key> annotation. Default is "excel".
    +      //
    +      "ExcelKey": "excel",
    +      //
    +      // Worksheet name. When null, uses the routine name.
    +      //
    +      "ExcelSheetName": null,
    +      //
    +      // Excel Format Code for DateTime cells. When null, uses SpreadCheetah default (yyyy-MM-dd HH:mm:ss).
    +      // Uses Excel Format Codes (not .NET format strings). Examples: "yyyy-mm-dd", "dd/mm/yyyy hh:mm", "m/d/yy h:mm".
    +      //
    +      "ExcelDateTimeFormat": null,
    +      //
    +      // Excel Format Code for numeric cells. When null, uses Excel default (General).
    +      // Uses Excel Format Codes (not .NET format strings). Examples: "#,##0.00", "0.00", "#,##0".
    +      //
    +      "ExcelNumericFormat": null
    +    },
    +
    +    //
    +    // Authentication options for NpgsqlRest endpoints
    +    //
    +    "AuthenticationOptions": {
    +      //
    +      // Authentication type used with the Login endpoints to set the authentication type for the new `ClaimsIdentity` created by the login. This value must be set to non-null when using login endpoints, otherwise, the following error will raise: `SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.` If the value is not set and the login endpoint is present, it will automatically get the database name from the connection string.
    +      //
    +      "DefaultAuthenticationType": null,
    +
    +      //
    +      // The default column name in the data reader which will be used to read the value to determine the success or failure of the login operation. If this column is not present, the success is when the endpoint returns any records. If this column is present, it must be either a boolean to indicate success or a numeric value to indicate the HTTP Status Code to return. If this column is present and retrieves a numeric value, that value is assigned to the HTTP Status Code and the login will authenticate only when this value is 200.
    +      //
    +      "StatusColumnName": "status",
    +      //
    +      // The default column name in the data reader which will be used to read the value of the authentication scheme of the login process. If this column is not present in the login response the default authentication scheme is used. Return new value to use a different authentication scheme with the login endpoint.
    +      //
    +      "SchemeColumnName": "scheme",
    +      //
    +      // The default column name in the data reader which will return a response body message for the login operation where writing to body is possible.
    +      //
    +      "BodyColumnName": "body",
    +      //
    +      // The default column name in the data reader which will set the response content type for the login operation where writing to body is possible.
    +      //
    +      "ResponseTypeColumnName": "application/json",
    +      //
    +      // The default column name in the data reader which will be used to read the value of the hash of the password. 
    +      // If this column is present, the value will be used to verify the password from the password parameter. 
    +      // Password parameter is the first parameter which name contains the value of PasswordParameterNameContains. 
    +      // If verification fails, the login will fail and the HTTP Status Code will be set to 404 Not Found.
    +      //
    +      "HashColumnName": "hash",
    +      //
    +      // The default name of the password parameter. 
    +      // The first parameter which name contains this value will be used as the password parameter. 
    +      // This is used to verify the password from the password parameter when login endpoint returns a hash of the password (see HashColumnName).
    +      //
    +      "PasswordParameterNameContains": "pass",
    +      //
    +      // Default claim type for user id.
    +      //
    +      "DefaultUserIdClaimType": "user_id",
    +      //
    +      // Default claim type for username.
    +      //
    +      "DefaultNameClaimType": "user_name",
    +      //
    +      // Default claim type for user roles.
    +      //
    +      "DefaultRoleClaimType": "user_roles",
    +      //
    +      // Default claim type for user display name.
    +      //
    +      "DefaultDisplayNameClaimType": "display_name",
    +      //
    +      // If true, return any response from auth endpoints (login and logout) if response hasn't been written by auth handler. For cookie auth, this will return full record to response as returned by the routine. For bearer token auth, this will be ignored because bearer token auth writes its own response (with tokens). This option will also be ignored if message column is present (see BodyColumnName option).
    +      //
    +      "SerializeAuthEndpointsResponse": false,
    +      //
    +      // Don't write real parameter values when logging parameters from auth endpoints and obfuscate instead. This prevents user credentials including password from ending up in application logs.
    +      //
    +      "ObfuscateAuthParameterLogValues": true,
    +      //
    +      // Command that is executed when the password verification fails. There are three positional and optional parameters: 
    +      // - $1: Authentication scheme used for the login (if parameter exists, type text).
    +      // - $2: User id used for the login (if parameter exists, type text).
    +      // - $3: Username used for the login (if parameter exists, type text).
    +      //
    +      "PasswordVerificationFailedCommand": null,
    +      //
    +      // Command that is executed when the password verification succeeds. There are three positional and optional parameters:
    +      // - $1: authentication scheme used for the login (if parameter exists, type text).
    +      // - $2: user id used for the login (if parameter exists, type text).
    +      // - $3: username used for the login (if parameter exists, type text).
    +      //
    +      "PasswordVerificationSucceededCommand": null,
    +      //
    +      // Enable setting authenticated user claims to context variables automatically. See ContextKeyClaimsMapping and ClaimsJsonContextKey options. You can set this individually for each request by using UserContext endpoint property or user_context comment annotation.
    +      // Note: For proxy endpoints, when user_context is enabled, these values are also forwarded as HTTP headers to the upstream proxy using the context key names.
    +      //
    +      "UseUserContext": false,
    +      //
    +      // Mapping of context keys to user claim names. Keys are the context variable names and values are the user claim names. When <see cref="UseUserContext"/> is enabled, the user claims from will be automatically mapped to the context variables.
    +      //
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.user_name": "user_name",
    +        "request.user_roles": "user_roles"
    +      },
    +      //
    +      // Context key that is used to set context variable for all available user claims. When this option is not null, and user is authenticated, the user claims will be serialized to JSON value and set to the context variable.
    +      //
    +      "ClaimsJsonContextKey": null,
    +      //
    +      // IP address context key that is used to set context variable for the IP address. When this option is not null, the IP address will be set to the context variable when <see cref="UseUserContext"/> is enabled and even when user is not authenticated.
    +      //
    +      "IpAddressContextKey": "request.ip_address",
    +      //
    +      // Enable mapping authenticated user claims to parameters by name automatically. See ParameterNameClaimsMapping and ClaimsJsonParameterName options. You can set this individually for each request by using UseUserParameters endpoint property or user_parameters comment annotation.
    +      // Note: For proxy endpoints, when user_params is enabled, these values are also forwarded as query string parameters to the upstream proxy.
    +      //
    +      "UseUserParameters": false,
    +      //
    +      // Mapping of parameter names to user claim names. Keys are the parameter names and values are the user claim names. When <see cref="UseUserParameters"/> is enabled, the user claims from will be automatically mapped to the parameters.
    +      //
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_user_name": "user_name",
    +        "_user_roles": "user_roles"
    +      },
    +      //
    +      // Parameter name that is used to set value for all available user claims. When this option is not null, and user is authenticated, the user claims will be serialized to JSON value and set to the parameter with this name.
    +      //
    +      "ClaimsJsonParameterName": "_user_claims",
    +      //
    +      // IP address parameter name that is used to set parameter value for the IP address. When this option is not null, the IP address will be set to the parameter when <see cref="UseUserContext"/> is enabled and even when user is not authenticated.
    +      //
    +      "IpAddressParameterName": "_ip_address",
    +      //
    +      // Url path that will be used for the login endpoint. If NULL, the login endpoint will not be created.
    +      // Login endpoint expects a PostgreSQL command that will be executed to authenticate the user that follow this convention:
    +      //
    +      // - Must return at least one record when authentication is successful. If no records are returned endpoint will return 401 Unauthorized.
    +      // - If record is returned, the authentication is successful, if not set in StatusColumnName column otherwise.
    +      // - All records will be added to user principal claim collection where column name is claim type and column value is claim value, 
    +      //   except for four special columns defined in StatusColumnName, SchemeColumnName, BodyColumnName and HashColumnName options:
    +      //
    +      // - If "StatusColumnName" is present in the returned record, it must be either boolean (true for success, false for failure) or numeric (HTTP Status Code, 200 for success, anything else for failure). If not present, the success is when the endpoint returns any records.
    +      // - If "SchemeColumnName" is present in the returned record, it must be text value that defines the authentication scheme to use for the login.
    +      // - If "BodyColumnName" is present in the returned record, it must be text value that defines the message to return to the client as response body where possible. This only works for authentication that doesn't write response body (cookie authentication).
    +      // - If "HashColumnName" is present in the returned record, it must be text value that defines the hash of the password. Password parameter is the first parameter which name contains the value of PasswordParameterNameContains option. If verification fails, the login will fail and the HTTP Status Code will be set to 404 Not Found.
    +      //
    +      "LoginPath": null,
    +      //
    +      // Url path that will be used for the logout endpoint. If NULL, the logout endpoint will not be created.
    +      // Login endpoint expects a PostgreSQL command that performs the logout or the sign-out operation.
    +      //
    +      // If the routine doesn't return any data, the default authorization scheme is signed out. 
    +      // Any values returned will be interpreted as scheme names (converted to string) to sign out.
    +      //
    +      "LogoutPath": null,
    +      //
    +      // Settings for basic authentication support.
    +      // Basic authentication is a simple authentication scheme built into the HTTP protocol.
    +      // It expects request header `Authorization: Basic base64(username:password)` where username and password are the credentials for the user.
    +      //
    +      "BasicAuth": {
    +        //
    +        // Enable or disable the Basic Authentication support.
    +        //
    +        "Enabled": false,
    +        //
    +        // The default realm for the Basic Authentication. If not set, "NpgsqlRest" will be used.
    +        //
    +        "Realm": null,
    +        //
    +        // Default users dictionary for the Basic Authentication. Key is the username and value is the password or password hash depending on the UseDefaultPasswordHasher option.
    +        // Users can be set on individual endpoints using multiple annotations: basic_authentication [ username ] [ password ]
    +        //
    +        "Users": { },
    +        //
    +        // When using Basic Authentication, set this to Required to enforce SSL/TLS connection. 
    +        // Use Warning to issue a warning in the log when connection is not secure.
    +        // Use Ignore to allow Basic Authentication (debug level log will show a warning).
    +        //
    +        "SslRequirement": "Required", // Ignore, Warning, Required
    +        //
    +        // Use default password hasher for Basic Authentication to verify the password when Password is set on endpoint or options.
    +        // When this is true, Password set in configuration, endpoint or header (depending on PasswordHashLocation) is expected to be a hashed with default hasher.
    +        //
    +        "UseDefaultPasswordHasher": true,
    +        //
    +        // PostgreSQL command executed when the Basic Authentication is challenged. 
    +        // Same convention applies as with "LoginPath" command. See "NpgsqlRest.LoginPath" option for details.
    +        // Use this command to validate the username and password and/or return user claims.
    +        // 
    +        // Positional parameters:
    +        // - $1: Username from basic authentication header (if parameter exists, type text).
    +        // - $2: Password from basic authentication header (if parameter exists, type text).
    +        // - $3: Password is valid, true or false. If endpoint or configuration has a password defined, it will be validated. 
    +        //       This the result of that validation or NULL of no password is defined. 
    +        //       This allows for password to be validated before the command and use command for additional user claims. (if parameter exists, type boolean).
    +        // - $4: Basic authentication realm (if parameter exists, type text).
    +        // - $5: Endpoint path (if parameter exists, type text).
    +        //
    +        "ChallengeCommand": null
    +      }
    +    },
    +    
    +    //
    +    // Enable or disable the generation of HTTP files for NpgsqlRest endpoints.
    +    // See more on HTTP files at: 
    +    // https://marketplace.visualstudio.com/items?itemName=humao.rest-client or 
    +    // https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-8.0
    +    //
    +    "HttpFileOptions": {
    +      "Enabled": false,
    +      //
    +      // Options for HTTP file generation:
    +      // - File: Generate HTTP files in the file system.
    +      // - Endpoint: Generate Endpoint(s) with HTTP file(s) content.
    +      // - Both: Generate HTTP files in the file system and Endpoint(s) with HTTP file(s) content.
    +      //
    +      "Option": "File",
    +      //
    +      // File name. If not set, the database name will be used if connection string is set. 
    +      // If neither ConnectionString nor Name is set, the file name will be "npgsqlrest".
    +      //
    +      "Name": null,
    +      //
    +      // The pattern to use when generating file names. {0} is database name, {1} is schema suffix with underline when FileMode is set to Schema.
    +      // Use this property to set a custom file name.
    +      // .http extension will be added automatically.
    +      //
    +      "NamePattern": "{0}_{1}",
    +      //
    +      // Adds comment header to above request based on PostgreSQL routine.
    +      // - None: skip.
    +      // - Simple: Add name, parameters and return values to comment header. This default.
    +      // - Full: Add the entire routine code as comment header.
    +      //
    +      "CommentHeader": "Simple",
    +      //
    +      // When CommentHeader is set to Simple or Full, set to true to include routine comments in comment header.
    +      //
    +      "CommentHeaderIncludeComments": true,
    +      //
    +      // - Database: to create one http file for entire database.
    +      // - Schema: to create one http file for each schema.
    +      //
    +      "FileMode": "Schema",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from the generated HTTP file's query string and request body. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +
    +    //
    +    // Enable or disable the generation of OpenAPI files for NpgsqlRest endpoints.
    +    //
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      //
    +      // File name for the generated OpenAPI file. Set to null to skip the file generation.
    +      //
    +      "FileName": "npgsqlrest_openapi.json",
    +      //
    +      // URL path for the OpenAPI endpoint. Set to null to skip the endpoint generation.
    +      //
    +      "UrlPath": "/openapi.json",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // The title of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // If not set, the database name from the ConnectionString will be used.
    +      //
    +      "DocumentTitle": null,
    +      //
    +      // The version of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // When null, default is "1.0.0".
    +      //
    +      "DocumentVersion": "1.0.0",
    +      //
    +      // Optional description of the API. This appears in the "info" section of the OpenAPI specification.
    +      //
    +      "DocumentDescription": null,
    +      //
    +      // Include current server information in the "servers" section of the OpenAPI document.
    +      //
    +      "AddCurrentServer": true,
    +      //
    +      // Additional server entries to add to the "servers" section of the OpenAPI document.
    +      // Each server entry must have "Url" property and optional "Description" property.
    +      //
    +      "Servers": [/*{"Url": "https://api.example.com", "Description": "Production server"}*/],
    +      //
    +      // Security schemes to include in the OpenAPI document.
    +      // If not specified, a default Bearer authentication scheme will be added for endpoints requiring authorization.
    +      // Supported types: "Http" (for Bearer/Basic auth) and "ApiKey" (for Cookie/Header/Query auth).
    +      // Examples:
    +      // - Bearer token: {"Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT"}
    +      // - Cookie auth: {"Name": "cookieAuth", "Type": "ApiKey", "In": ".AspNetCore.Cookies", "ApiKeyLocation": "Cookie"}
    +      // - Basic auth: {"Name": "basicAuth", "Type": "Http", "Scheme": "Basic"}
    +      //
    +      "SecuritySchemes": [
    +        /*{
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }*/
    +      ],
    +      //
    +      // Filters that control which endpoints appear in the OpenAPI document. The HTTP endpoints
    +      // themselves are unaffected — only their inclusion in the generated spec is. Combine with
    +      // the per-routine `openapi hide` / `openapi tag <name>` comment annotations for fine-grained
    +      // control. Use case: expose a partner-facing document that hides the internal anonymous
    +      // surface (health, login, probes) and the internal-only schemas.
    +      //
    +      // Schema allow-list. When non-empty, only endpoints whose routine schema appears here are
    +      // documented. Empty array (default) = document every schema.
    +      //
    +      "IncludeSchemas": [/* "partner" */],
    +      //
    +      // Schema deny-list. Any endpoint whose routine schema appears here is skipped. Applied
    +      // alongside IncludeSchemas — both must pass. Empty array (default) = no schema exclusions.
    +      //
    +      "ExcludeSchemas": [/* "internal" */],
    +      //
    +      // PostgreSQL-style SIMILAR TO pattern matched against the routine NAME. When set, only
    +      // routines whose name matches are documented. `_` matches one char, `%` matches any
    +      // sequence; the rest of SIMILAR TO syntax (`|`, `*`, `+`, `?`, `(...)`, `[...]`) is
    +      // supported. Anchored (must cover the whole name). Default null = no name filter.
    +      //
    +      "NameSimilarTo": null,
    +      //
    +      // PostgreSQL-style SIMILAR TO pattern matched against the routine NAME for EXCLUSION.
    +      // Matches are skipped. Same syntax as NameSimilarTo. Applied alongside it — both must
    +      // pass. Default null = no name exclusion.
    +      //
    +      "NameNotSimilarTo": null,
    +      //
    +      // When true, only authenticated endpoints (those that require authorization) are
    +      // documented. Anonymous endpoints — typically health, login, probes — are omitted. Useful
    +      // for partner-facing documents. Default false = document everything.
    +      //
    +      "RequiresAuthorizationOnly": false,
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from documented query parameters and request bodies. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +    //
    +    // Enable or disable the MCP (Model Context Protocol) server endpoint. Disabled by default. Tools are NEVER auto-exposed: only routines explicitly opted in with the `mcp` comment annotation become MCP tools. Implements MCP specification 2025-11-25.
    +    //
    +    "McpOptions": {
    +      "Enabled": false,
    +      //
    +      // URL path for the MCP endpoint (Streamable HTTP, single JSON-RPC endpoint).
    +      //
    +      "UrlPath": "/mcp",
    +      //
    +      // serverInfo.name reported in the MCP initialize handshake. When null, the database name from the connection string is used (falling back to "NpgsqlRest").
    +      //
    +      "ServerName": null,
    +      //
    +      // serverInfo.version reported in the MCP initialize handshake.
    +      //
    +      "ServerVersion": "1.0.0",
    +      //
    +      // Optional server-level instructions returned in the MCP initialize handshake (high-level guidance for the agent).
    +      //
    +      "Instructions": null,
    +      //
    +      // Optional text appended to every MCP tool description. Null = no-op. Use for short shared context the agent should always see (e.g. "Read-only Acme CRM."); for longer server-wide guidance prefer Instructions.
    +      //
    +      "ToolDescriptionSuffix": null,
    +      //
    +      // Name of an ASP.NET rate-limiter policy applied to the whole /mcp endpoint. Null = no limiting. A routine's own rate_limiter annotation does not carry to MCP (tools/call bypasses route middleware), so this is how MCP traffic is throttled. The named policy must be registered on the host (AddRateLimiter + UseRateLimiter); an unregistered name surfaces as the framework's error when a request hits the endpoint.
    +      //
    +      "RateLimiterPolicy": null,
    +      //
    +      // Allowed values of the HTTP Origin header (DNS-rebinding protection for the Streamable HTTP transport). A request whose Origin is present but matches neither this list nor the server's own origin is rejected with 403. Requests without an Origin header (e.g. server-to-server) are allowed. Empty = only same-origin browser requests pass.
    +      //
    +      "AllowedOrigins": [],
    +      //
    +      // OAuth 2.1 Resource Server settings. Token validation reuses the host's bearer authentication; these keys configure the transport gate and the Protected Resource Metadata document (RFC 9728). NpgsqlRest is not an Authorization Server — point AuthorizationServers at an external IdP.
    +      //
    +      "Authorization": {
    +        //
    +        // When true, every MCP request requires an authenticated principal. When false (default), anonymous is allowed and a tool's own `authorize` annotation still gates it per call.
    +        //
    +        "RequireAuthorization": false,
    +        //
    +        // Authorization Server issuer URL(s) advertised in the Protected Resource Metadata. When empty, no PRM document is served.
    +        //
    +        "AuthorizationServers": [],
    +        //
    +        // Optional scopes advertised in the Protected Resource Metadata (scopes_supported).
    +        //
    +        "ScopesSupported": [],
    +        //
    +        // Canonical resource URI tokens must target (RFC 8707 audience) and the PRM "resource" value. Null = derived from the request (scheme + host + UrlPath).
    +        //
    +        "Audience": null,
    +        //
    +        // Path the Protected Resource Metadata document is served at. Null = the RFC 9728 well-known path derived from UrlPath.
    +        //
    +        "ProtectedResourceMetadataPath": null,
    +        //
    +        // When true, tools/list hides tools the calling principal could not run (their routine's authorize/role check would deny it). When false (default), every opted-in tool is listed (discoverable) and authorization is enforced on tools/call.
    +        //
    +        "FilterToolsByRole": false
    +      }
    +    },
    +
    +    //
    +    // Enable or disable the generation of TypeScript/Javascript client source code files for NpgsqlRest endpoints.
    +    //
    +    "ClientCodeGen": {
    +      "Enabled": false,
    +      //
    +      // File path for the generated code. Set to null to skip the code generation. Use {0} to set schema name when BySchema is true
    +      //
    +      "FilePath": null,
    +      //
    +      //  Force file overwrite.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // Include current host information in the URL prefix.
    +      //
    +      "IncludeHost": true,
    +      //
    +      // Set the custom host prefix information.
    +      //
    +      "CustomHost": null,
    +      //
    +      // Adds comment header to above request based on PostgreSQL routine
    +      // Set None to skip.
    +      // Set Simple (default) to add name, parameters and return values to comment header.
    +      // Set Full to add the entire routine code as comment header.
    +      //
    +      "CommentHeader": "Simple",
    +      //
    +      // When CommentHeader is set to Simple or Full, set to true to include routine comments in comment header.
    +      //
    +      "CommentHeaderIncludeComments": true,
    +      //
    +      // Create files by PostgreSQL schema. File name will use formatted FilePath where {0} is the schema name in pascal case.
    +      //
    +      "BySchema": true,
    +      //
    +      // Set to true to include status code in response: {status: response.status, response: model}
    +      //
    +      "IncludeStatusCode": true,
    +      //
    +      // Create separate file with global types {name}Types.d.ts
    +      //
    +      "CreateSeparateTypeFile": true,
    +      //
    +      // Emit interfaces with the `export` keyword so they can be imported by other modules. When true and CreateSeparateTypeFile is true, the separate type file becomes an importable module ({name}Types.ts) instead of an ambient {name}Types.d.ts, and the client file imports the named types from it. No effect when SkipTypes is true.
    +      //
    +      "ExportTypes": false,
    +      //
    +      // Module name to import "baseUrl" constant, instead of defining it in a module.
    +      //
    +      "ImportBaseUrlFrom": null,
    +      //
    +      // Module name to import "parseQuery" function, instead of defining it in a module.
    +      //
    +      "ImportParseQueryFrom": null,
    +      //
    +      // Include optional parameter `parseUrl: (url: string) => string = url=>url` that will parse the constructed URL.
    +      //
    +      "IncludeParseUrlParam": false,
    +      //
    +      // Include optional parameter `parseRequest: (request: RequestInit) => RequestInit = request=>request` that will parse the constructed request.
    +      //
    +      "IncludeParseRequestParam": false,
    +      //
    +      // Header lines on each auto-generated source file. Default is ["// autogenerated at {0}", "", ""] where {0} is the current timestamp.
    +      //
    +      "HeaderLines": [
    +        "// autogenerated at {0}",
    +        ""
    +      ],
    +      //
    +      // Array of routine names to skip (without schema)
    +      //
    +      "SkipRoutineNames": [],
    +      //
    +      // Array of generated function names to skip (without schema)
    +      //
    +      "SkipFunctionNames": [],
    +      //
    +      // Array of url paths to skip
    +      //
    +      "SkipPaths": [],
    +      //
    +      // Array of schema names to skip
    +      //
    +      "SkipSchemas": [],
    +      //
    +      // Default TypeScript type for JSON types
    +      //
    +      "DefaultJsonType": "any",
    +      //
    +      // Use routine name instead of endpoint name when generating function names.
    +      //
    +      "UseRoutineNameInsteadOfEndpoint": false,
    +      //
    +      // Export URLs as constants in the generated code.
    +      //
    +      "ExportUrls": false,
    +      //
    +      // Skip generating types and produce pure JavaScript code. Setting this to true will also change the .ts extension to .js where applicable.
    +      //
    +      "SkipTypes": false,
    +      //
    +      // Keep TypeScript models unique, meaning models with the same fields and types will be merged into one model with the name of the last model. This significantly reduces the number of generated models.
    +      //
    +      "UniqueModels": false,
    +      //
    +      // Name of the XSRF Token Header (Anti-forgery Token). This is used in FORM POSTS to the server when Anti-forgery is enabled. Currently, only Upload requests use FORM POST.
    +      //
    +      "XsrfTokenHeaderName": null,
    +      //
    +      // Export event sources create functions for streaming events.
    +      //
    +      "ExportEventSources": true,
    +      //
    +      // List of custom imports to add to the generated code. It adds line to a file. Use full expression like `import { MyType } from './my-type';`
    +      //
    +      "CustomImports": [],
    +      //
    +      // Dictionary of custom headers to add to each request in generated code. Header key is automatically quoted if it doesn't contain quotes.
    +      //
    +      "CustomHeaders": {},
    +      //
    +      // When true, include PostgreSQL schema name in the generated type names to avoid name collisions. Set to false to simplify type names when no name collisions are expected.
    +      //
    +      "IncludeSchemaInNames": true,
    +      //
    +      // Expression to parse error response. Only used when IncludeStatusCode is true.
    +      //
    +      "ErrorExpression": "await response.json()",
    +      //
    +      // TypeScript type for error response. Only used when IncludeStatusCode is true.
    +      //
    +      "ErrorType": "{status: number; title: string; detail?: string | null} | undefined",
    +      //
    +      // When true, parameters filled by the server and not settable by the client are omitted from the generated request interface, query string, and body. Covers optional automatic parameters: HTTP Custom Type fields, resolved-parameter expressions, upload metadata, and (on endpoints using user parameters) IP-address and user-claim parameters. Default is false.
    +      //
    +      "OmitAutomaticParameters": false
    +    },
    +
    +    //
    +    // HTTP client functionality for annotated composite types.
    +    // Allows PostgreSQL functions to make HTTP requests by using specially annotated types as parameters.
    +    //
    +    "HttpClientOptions": {
    +      //
    +      // Enable HTTP client functionality for annotated types.
    +      //
    +      "Enabled": false,
    +      //
    +      // Default name for the response status code field within annotated types.
    +      //
    +      "ResponseStatusCodeField": "status_code",
    +      //
    +      // Default name for the response body field within annotated types.
    +      //
    +      "ResponseBodyField": "body",
    +      //
    +      // Default name for the response headers field within annotated types.
    +      //
    +      "ResponseHeadersField": "headers",
    +      //
    +      // Default name for the response content type field within annotated types.
    +      //
    +      "ResponseContentTypeField": "content_type",
    +      //
    +      // Default name for the response success field within annotated types.
    +      //
    +      "ResponseSuccessField": "success",
    +      //
    +      // Default name for the response error message field within annotated types.
    +      //
    +      "ResponseErrorMessageField": "error_message",
    +      //
    +      // Global kill switch for HTTP type response caching. When false, the '@cache' directive on
    +      // individual types is ignored and every request fires a fresh outbound call. Caching is opt-in
    +      // per type via the '@cache <interval>' type-comment directive.
    +      //
    +      "CacheEnabled": true,
    +      //
    +      // Maximum number of distinct cached HTTP responses held in memory. Once full, new responses are
    +      // not cached (existing entries are still served and expire normally).
    +      //
    +      "MaxCacheEntries": 10000,
    +      //
    +      // Interval in seconds at which expired cached HTTP responses are pruned from memory.
    +      //
    +      "CachePruneIntervalSeconds": 60
    +    },
    +
    +    //
    +    // Reverse proxy functionality for NpgsqlRest endpoints.
    +    // When an endpoint is marked with 'proxy' annotation, incoming requests are forwarded to another URL.
    +    //
    +    "ProxyOptions": {
    +      //
    +      // Enable proxy functionality for annotated endpoints.
    +      //
    +      "Enabled": false,
    +      //
    +      // Base URL (host) for proxy requests (e.g., "https://api.example.com").
    +      // When set, proxy endpoints will forward requests to this host + the original path.
    +      //
    +      "Host": null,
    +      //
    +      // Default timeout for all proxy requests. Format: "HH:MM:SS" or PostgreSQL interval.
    +      //
    +      "DefaultTimeout": "00:00:30",
    +      //
    +      // When true, original request headers are forwarded to the proxy target.
    +      //
    +      "ForwardHeaders": true,
    +      //
    +      // Headers to exclude from forwarding to the proxy target.
    +      //
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      //
    +      // When true, forward response headers from proxy back to client.
    +      //
    +      "ForwardResponseHeaders": true,
    +      //
    +      // Response headers to exclude from forwarding back to client.
    +      //
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      //
    +      // Default name for the proxy response status code parameter.
    +      //
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      //
    +      // Default name for the proxy response body parameter.
    +      //
    +      "ResponseBodyParameter": "_proxy_body",
    +      //
    +      // Default name for the proxy response headers parameter.
    +      //
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      //
    +      // Default name for the proxy response content type parameter.
    +      //
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      //
    +      // Default name for the proxy response success parameter.
    +      //
    +      "ResponseSuccessParameter": "_proxy_success",
    +      //
    +      // Default name for the proxy response error message parameter.
    +      //
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      //
    +      // When true, for upload endpoints marked as proxy, the raw multipart/form-data content is forwarded directly to the upstream proxy instead of being processed locally. This allows the upstream service to handle file uploads. When false (default), upload endpoints with proxy annotation will process uploads locally and upload metadata will not be available to the proxy.
    +      //
    +      "ForwardUploadContent": false,
    +      //
    +      // Maximum length (characters) of a single automatic parameter value appended to the proxy upstream query string. Server-filled values (claims, IP, HTTP Custom Type fields, resolved-parameter expressions) longer than this are skipped with a warning instead of producing an unusable request line (HTTP 414/431). 0 or less disables the guard. To forward a large value, use a body-carrying proxy method (POST/PUT/PATCH). Default is 2048.
    +      //
    +      "MaxForwardedQueryParamLength": 2048
    +    },
    +
    +    //
    +    // SQL file source for generating REST API endpoints from .sql files.
    +    // Each SQL file must contain exactly one statement.
    +    //
    +    "SqlFileSource": {
    +      //
    +      // Enable or disable SQL file source endpoints. Default is false.
    +      //
    +      "Enabled": false,
    +      //
    +      // Glob pattern for SQL files, e.g. "sql/**/*.sql", "queries/*.sql".
    +      // Supports * (any chars), ** (recursive, any including /), ? (single char).
    +      // Empty string disables the feature.
    +      //
    +      "FilePattern": "",
    +      //
    +      // Glob (same semantics as FilePattern) for files to EXCLUDE from endpoint discovery.
    +      // Default "*.test.sql" so co-located SQL test files (run by the test runner, see "TestRunner")
    +      // are never exposed as endpoints. Empty string disables the exclusion.
    +      //
    +      "SkipPattern": "*.test.sql",
    +      //
    +      // How comment annotations are processed for SQL file endpoints.
    +      // Possible values: Ignore, ParseAll, OnlyAnnotated, OnlyWithHttpTag.
    +      // OnlyAnnotated (default; OnlyWithHttpTag is a back-compat alias) requires an explicit
    +      // HTTP annotation (e.g., "-- HTTP GET") for a SQL file to become an endpoint.
    +      //
    +      "CommentsMode": "OnlyAnnotated",
    +      //
    +      // Which comments in the SQL file to parse as annotations.
    +      // Possible values: All (default), Header (only comments before the first statement).
    +      //
    +      "CommentScope": "All",
    +      //
    +      // Behavior when a SQL file fails to parse or describe.
    +      // Possible values: Skip (default — log error, continue), Throw (halt startup).
    +      //
    +      "ErrorMode": "Exit",
    +      //
    +      // Prefix for result keys in multi-command JSON responses.
    +      // Default keys are "result1", "result2", etc.
    +      // Override per-result with the positional @result annotation in the SQL file.
    +      //
    +      "ResultPrefix": "result",
    +      //
    +      // When true, queries returning a single column produce a flat JSON array of values
    +      // (e.g., ["a", "b", "c"]) instead of an array of objects (e.g., [{"col": "a"}, {"col": "b"}]).
    +      // This matches the behavior of PostgreSQL functions returning setof single values.
    +      //
    +      "UnnamedSingleColumnSet": true,
    +      //
    +      // When true, composite type columns in return results are serialized as nested JSON objects.
    +      // For example, a column "data" of type "my_type(id int, name text)" becomes {"data": {"id": 1, "name": "test"}}
    +      // instead of the default flat structure {"id": 1, "name": "test"}.
    +      // Default is false for backward compatibility. Can also be enabled per-endpoint with the 'nested' annotation.
    +      //
    +      "NestedJsonForCompositeTypes": false,
    +      //
    +      // When true, non-query commands (BEGIN, COMMIT, SET, DO blocks, etc.) in multi-command SQL files
    +      // are still executed but excluded from the JSON response result keys.
    +      // Default is true.
    +      //
    +      "SkipNonQueryCommands": true,
    +      //
    +      // When true, multi-command SQL file endpoints include the full SQL text in command logs.
    +      // When false (default), only the file path and statement count are logged.
    +      // Single-command SQL files always log the SQL text regardless of this setting.
    +      // This only applies when LogCommands is true.
    +      //
    +      "LogCommandText": false
    +    }
    +  }
    +}

    Core Settings

    • Top-Level Settings - Application identity, URLs, and startup message
    • Config Section - Configuration file processing and environment variables
    • NpgsqlRest Options - Core API generation settings (URL prefixes, naming conventions, request handling)
    • Routine Options - PostgreSQL routine handling (language filtering, custom types)
    • Connection - Database connection strings and settings
    • Server - Kestrel web server and SSL/TLS configuration

    Security

    Features

    • OpenAPI - OpenAPI/Swagger documentation generation
    • HTTP Files - HTTP test file generation
    • Code Generation - Client code generation (TypeScript, etc.)
    • Uploads - File upload handling
    • HTTP Client - HTTP Types for external API calls from PostgreSQL functions

    Performance

    Infrastructure

    Comments

    + + + + \ No newline at end of file diff --git a/config/logging.html b/config/logging.html new file mode 100644 index 000000000..db802b126 --- /dev/null +++ b/config/logging.html @@ -0,0 +1,135 @@ + + + + + + Logging Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Logging

    Logging configuration using Serilog for console, file, PostgreSQL database, and OpenTelemetry outputs.

    For the task-oriented walkthrough — log channels, seeing executed SQL, PostgreSQL raise messages in logs, production recipes — see the Logging Guide.

    Overview

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Verbose",
    +    "ToFile": false,
    +    "FilePath": "logs/log.txt",
    +    "FileSizeLimitBytes": 30000000,
    +    "FileMinimumLevel": "Verbose",
    +    "RetainedFileCountLimit": 30,
    +    "RollOnFileSizeLimit": true,
    +    "ToPostgres": false,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Verbose",
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.version": "1.0",
    +      "service.environment": "{environment}"
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose",
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}

    Log Levels

    Available log levels (from most to least verbose):

    LevelDescription
    VerboseMost detailed logging, typically for debugging
    DebugDebugging information
    InformationGeneral operational information
    WarningWarnings that don't stop execution
    ErrorErrors that affect specific operations
    FatalCritical errors that stop the application
    OffFully silences a channel (aliases: None, Silent; MinimalLevels entries only, since 3.19.0)

    See Serilog Configuration Basics for more details.

    Minimal Levels

    Configure minimum log levels per source context:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    NpgsqlReststring"Information"The endpoint engine: endpoint creation and annotations at Debug; discovery queries, describe phase, and (with LogCommands) executed SQL at Verbose.
    NpgsqlRestClientstring"Information"The client host: configuration processing, auth setup, startup detail. When ApplicationName is set, log lines display that name as the source context instead of NpgsqlRestClient — but this configuration key keeps working unchanged (it is mapped to the actual channel name automatically).
    NpgsqlRestTeststring"Information"The SQL test runner (--test): discovery at Debug, every test statement and HTTP invocation at Verbose. Name configurable via TestRunner.LoggerName.
    Systemstring"Warning"Log level for the .NET System namespace.
    Microsoftstring"Warning"Log level for the Microsoft namespace (ASP.NET Core, etc.).

    Any entry accepts "Off" (since 3.19.0) to silence that channel completely — see the Logging Guide.

    Console Output

    json
    json
    {
    +  "Log": {
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToConsolebooltrueEnable logging to console output.
    ConsoleMinimumLevelstring"Verbose"Minimum log level for console output.

    File Output

    json
    json
    {
    +  "Log": {
    +    "ToFile": false,
    +    "FilePath": "logs/log.txt",
    +    "FileSizeLimitBytes": 30000000,
    +    "FileMinimumLevel": "Verbose",
    +    "RetainedFileCountLimit": 30,
    +    "RollOnFileSizeLimit": true
    +  }
    +}
    SettingTypeDefaultDescription
    ToFileboolfalseEnable logging to file system.
    FilePathstring"logs/log.txt"File path for log files.
    FileSizeLimitBytesint30000000Maximum size limit for log files in bytes before rolling (30 MB).
    FileMinimumLevelstring"Verbose"Minimum log level for file output.
    RetainedFileCountLimitint30Maximum number of log files to retain.
    RollOnFileSizeLimitbooltrueCreate a new log file when size limit is reached.

    PostgreSQL Output

    json
    json
    {
    +  "Log": {
    +    "ToPostgres": false,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToPostgresboolfalseEnable logging to PostgreSQL database.
    PostgresCommandstring"call log($1,$2,$3,$4,$5)"PostgreSQL command to execute for database logging.
    PostgresMinimumLevelstring"Verbose"Minimum log level for PostgreSQL output.

    PostgreSQL Command Parameters

    The PostgresCommand receives five parameters:

    ParameterTypeDescription
    $1textLog level (Verbose, Debug, Information, Warning, Error, Fatal)
    $2textLog message
    $3timestamptzTimestamp in UTC
    $4textException text (or null if no exception)
    $5textSource context (logger name)

    OpenTelemetry Output

    json
    json
    {
    +  "Log": {
    +    "ToOpenTelemetry": false,
    +    "OTLPEndpoint": "http://localhost:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.version": "1.0",
    +      "service.environment": "{environment}"
    +    },
    +    "OTLPHeaders": {},
    +    "OTLPMinimumLevel": "Verbose"
    +  }
    +}
    SettingTypeDefaultDescription
    ToOpenTelemetryboolfalseEnable OpenTelemetry protocol (OTLP) logging output.
    OTLPEndpointstring"http://localhost:4317"OTLP collector endpoint URL.
    OTLPProtocolstring"Grpc"Protocol for OTLP: "Grpc" or "HttpProtobuf".
    OTLPResourceAttributesobject(see below)Resource attributes sent with logs.
    OTLPHeadersobject{}Custom headers for OTLP requests.
    OTLPMinimumLevelstring"Verbose"Minimum log level for OTLP output.

    Resource Attributes

    Default resource attributes use placeholders:

    AttributeDefaultDescription
    service.name"{application}"Application name from ApplicationName setting.
    service.version"1.0"Application version.
    service.environment"{environment}"Environment name from EnvironmentName setting.

    Output Template

    json
    json
    {
    +  "Log": {
    +    "OutputTemplate": "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}
    SettingTypeDefaultDescription
    OutputTemplatestring"[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"Serilog output template for formatting log messages.

    See Serilog Formatting Output for template syntax.

    Complete Example

    Production configuration with file and PostgreSQL logging:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Information",
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileSizeLimitBytes": 50000000,
    +    "FileMinimumLevel": "Information",
    +    "RetainedFileCountLimit": 14,
    +    "RollOnFileSizeLimit": true,
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning",
    +    "ToOpenTelemetry": false,
    +    "OutputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Level:u3}] {Message:lj} [{SourceContext}]{NewLine}{Exception}"
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/mcp.html b/config/mcp.html new file mode 100644 index 000000000..fcba323cb --- /dev/null +++ b/config/mcp.html @@ -0,0 +1,67 @@ + + + + + + MCP Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    MCP Options

    New in 3.17.0

    The NpgsqlRest.Mcp plugin and the McpOptions config section were added in version 3.17.0. It implements the Model Context Protocol specification 2025-11-25.

    Configuration for the MCP (Model Context Protocol) server — a single Streamable-HTTP JSON-RPC endpoint that lets an AI agent discover (tools/list) and execute (tools/call) PostgreSQL routines that have been opted in with the @mcp annotation.

    The MCP server is disabled by default, and no routine is ever exposed automatically — only routines that carry @mcp become tools.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "McpOptions": {
    +      "Enabled": false,
    +      "UrlPath": "/mcp",
    +      "ServerName": null,
    +      "ServerVersion": "1.0.0",
    +      "Instructions": null,
    +      "ToolDescriptionSuffix": null,
    +      "RateLimiterPolicy": null,
    +      "AllowedOrigins": [],
    +      "Authorization": {
    +        "RequireAuthorization": false,
    +        "AuthorizationServers": [],
    +        "ScopesSupported": [],
    +        "Audience": null,
    +        "ProtectedResourceMetadataPath": null,
    +        "FilterToolsByRole": false
    +      }
    +    }
    +  }
    +}

    Options

    Enabled

    • Type: boolean
    • Default: false

    Enables or disables the MCP server endpoint. When false, no MCP endpoint is registered and @mcp annotations are ignored.

    UrlPath

    • Type: string
    • Default: /mcp

    URL path for the MCP endpoint. The endpoint is a single Streamable-HTTP JSON-RPC endpoint that accepts POST.

    ServerName

    • Type: string (nullable)
    • Default: null

    The serverInfo.name value reported in the MCP initialize handshake. When null, the database name from the connection string is used (mirroring the OpenAPI document title), falling back to "NpgsqlRest" if it cannot be resolved.

    ServerVersion

    • Type: string
    • Default: "1.0.0"

    The serverInfo.version value reported in the MCP initialize handshake. A null/blank value also falls back to "1.0.0".

    Instructions

    • Type: string (nullable)
    • Default: null

    Optional server-level instructions returned in the MCP initialize handshake — high-level guidance the agent can use when deciding how to call the available tools. When null, no instructions are sent.

    ToolDescriptionSuffix

    • Type: string (nullable)
    • Default: null

    Optional text appended (as a suffix) to every tool's description in tools/list. When null, nothing is added.

    RateLimiterPolicy

    • Type: string (nullable)
    • Default: null

    Name of an ASP.NET rate-limiter policy applied to the whole /mcp endpoint. When null (default), MCP traffic is not rate-limited.

    A routine's own @rate_limiter annotation does not carry to MCP — tools/call invokes the routine directly, bypassing the per-route middleware (NpgsqlRest logs a startup warning when an @mcp routine also has @rate_limiter). RateLimiterPolicy is how you throttle the MCP endpoint instead, covering every JSON-RPC method on it.

    The named policy must be registered on the hostAddRateLimiter(o => o.AddPolicy("name", …)) (or a built-in limiter such as AddFixedWindowLimiter) plus UseRateLimiter(). An unregistered name surfaces as the framework's error when a request reaches the endpoint. (When set, NpgsqlRest serves /mcp as a mapped endpoint so the policy can attach; on hosts without endpoint routing it logs a warning and the policy is not applied.)

    jsonc
    jsonc
    "RateLimiterPolicy": "mcp"

    AllowedOrigins

    • Type: string[]
    • Default: []

    Allowed values of the HTTP Origin header — DNS-rebinding protection required by the Streamable HTTP transport. A request whose Origin is present but matches neither this list nor the server's own origin is rejected with 403. Requests without an Origin header (e.g. server-to-server) are allowed. Empty (default) = only same-origin browser requests pass.

    Use it for short, shared context that should ride along with every tool the agent inspects — e.g. "Read-only Acme CRM." or "Amounts in USD.". Unlike Instructions (returned once at initialize, and which some clients don't surface prominently), a description suffix is attached to each tool, so the model sees it whenever it considers that tool.

    Keep it short: the suffix is repeated across every tool, so long text inflates the tools/list payload. For longer server-wide guidance, prefer Instructions.

    jsonc
    jsonc
    "ToolDescriptionSuffix": "Read-only Acme CRM."

    A tool whose own description is "Get the current weather for a city." is then reported as:

    code
    Get the current weather for a city. Read-only Acme CRM.

    How it works

    Once enabled, the endpoint is a single Streamable-HTTP JSON-RPC endpoint (POST only; GET405, no SSE). Per the transport spec it validates the Origin header (a present, untrusted origin → 403; see AllowedOrigins) and the MCP-Protocol-Version header (a present header other than 2025-11-25400; absent is allowed). It implements the MCP lifecycle and tools methods:

    • initialize — advertises the tools capability and returns serverInfo (name/version above) and the protocol version 2025-11-25. notifications/initialized is acknowledged with 202. ping returns an empty result.

    • tools/list — returns the catalog of opted-in routines. Each tool has a name (the routine name, or an @mcp_name override), a description (from @mcp <text> or the comment prose), a JSON-Schema inputSchema derived from the routine's parameters, and an outputSchema derived from the routine's return columns (matching the structuredContent shape below; leaf values allow null, and array/json/composite columns use a permissive schema so results always conform).

    • tools/call — executes the routine through the same invocation pipeline as the HTTP endpoint, forwarding the authenticated identity so @authorize role checks apply. The result carries structuredContent (always a JSON object) plus a text content block holding its serialized form:

      json
      json
      {
      +  "content": [{ "type": "text", "text": "{\"total\":1234,\"status\":\"paid\"}" }],
      +  "structuredContent": { "total": 1234, "status": "paid" },
      +  "isError": false
      +}

      structuredContent is mapped from the routine's return shape (per MCP 2025-11-25, it is always an object):

      Routine returnsstructuredContent
      a single value (int, text, …){ "value": 42 }
      a single record/composite (or a set with single)the object: { "total": 1234, … }
      a set of values{ "items": [1, 2, 3] }
      a set of rows{ "items": [ { … }, { … } ] }

      (Numbers/booleans/JSON are embedded as JSON; other scalar types as a string. Raw-mode and void routines emit the text block only.)

      Business failures are returned as isError: true in the result; structural failures (unknown method, unknown tool, malformed request) are returned as JSON-RPC errors (-32601, -32602, -32700).

    Rate limiting

    A routine's @rate_limiter annotation applies to its HTTP route, not to MCP calls — tools/call executes the routine directly, bypassing the route's rate-limiter. To throttle MCP traffic, set RateLimiterPolicy to a host-registered policy applied to the whole /mcp endpoint, or rate-limit the /mcp path at a reverse proxy / API gateway.

    Authentication — OAuth 2.1 Resource Server

    The /mcp endpoint acts as an OAuth 2.1 Resource Server (bring-your-own Authorization Server). Token validation reuses the host's bearer authentication — NpgsqlRest is not an Authorization Server; point AuthorizationServers at an external IdP (Keycloak, Auth0, Entra, …) or at NpgsqlRest's own JWT login acting separately.

    No built-in Authorization Server

    NpgsqlRest does not ship an Authorization Server (token / consent / authorization-code endpoints) — that is potential future work. The Resource Server role above covers every deployment that already has an IdP (or uses NpgsqlRest's own JWT). The only scenario it can't cover is fully interactive browser-login with no external IdP at all; if you don't have an IdP and don't need interactive login, use NpgsqlRest's own JWT (static-token) auth instead.

    Enabling MCP does not enable authentication

    Authentication is configured separately from MCP (via the host's Auth section — e.g. JwtAuth). The MCP Authorization settings only add the transport gate, PRM advertising, and audience binding on top of whatever principal the host's auth produced. If you set RequireAuthorization: true without configuring authentication, every /mcp request returns 401 (nothing is ever authenticated) — NpgsqlRest logs a startup warning in this case. For audience validation, configure the host JWT bearer's ValidAudience to the same value as Audience below.

    Tool execution forwards the caller's authenticated principal, so per-routine @authorize role requirements are enforced on tools/call exactly as on HTTP endpoints. A tool that needs authentication, called anonymously, returns HTTP 401 (with the PRM challenge); an authenticated caller lacking the required role gets HTTP 403 with WWW-Authenticate: Bearer error="insufficient_scope". No authorization logic is duplicated — this reuses core's check.

    Authorization options

    These live under McpOptions:Authorization.

    RequireAuthorization

    • Type: boolean
    • Default: false

    When true, every MCP request requires an authenticated principal (the host's bearer middleware must have populated the identity). An unauthenticated request is rejected with HTTP 401 and a WWW-Authenticate: Bearer resource_metadata="…" challenge (RFC 9728 §5.1) so the client can discover the Authorization Server. When false (default), anonymous requests are allowed and a tool's own @authorize annotation still gates it per call.

    AuthorizationServers

    • Type: string[]
    • Default: []

    Authorization Server issuer URL(s) advertised in the Protected Resource Metadata. When empty, no PRM document is served.

    ScopesSupported

    • Type: string[]
    • Default: []

    Optional scopes advertised in the Protected Resource Metadata (scopes_supported).

    Audience

    • Type: string (nullable)
    • Default: null

    The canonical resource URI tokens must target (RFC 8707 audience) and the resource value in the PRM document. When null, it is derived from the request (scheme + host + UrlPath).

    When set, it is also enforced: an authenticated token must carry this value in its aud claim, or the request is rejected with 401 (tokens issued for a different resource are refused). Token signature/expiry validation remains the host bearer middleware's responsibility; configure it with this same audience.

    ProtectedResourceMetadataPath

    • Type: string (nullable)
    • Default: null

    Path the Protected Resource Metadata document is served at. When null, the RFC 9728 well-known path is used: /.well-known/oauth-protected-resource + UrlPath (e.g. /.well-known/oauth-protected-resource/mcp).

    FilterToolsByRole

    • Type: boolean
    • Default: false

    When true, tools/list hides tools the calling principal could not run — i.e. tools whose routine has an @authorize requirement the caller doesn't satisfy. When false (default), every opted-in tool is listed, keeping them discoverable (so an agent can still attempt a call and be prompted to authenticate). Authorization is enforced on tools/call either way; this only affects what the listing reveals.

    Protected Resource Metadata (RFC 9728)

    When an Authorization Server is configured, NpgsqlRest serves a discovery document at the well-known path. The PRM document itself is always anonymous (it is discovery), even when RequireAuthorization is on:

    json
    json
    {
    +  "resource": "https://your-host/mcp",
    +  "authorization_servers": ["https://as.example.com"],
    +  "scopes_supported": ["mcp.read", "mcp.write"],
    +  "bearer_methods_supported": ["header"]
    +}

    Note

    By default tools/list lists every opted-in tool — keeping them discoverable so an agent can attempt a call and be prompted to authenticate — and authorization is enforced on tools/call. Set FilterToolsByRole to instead hide tools the caller can't run.

    Comments

    + + + + \ No newline at end of file diff --git a/config/npgsqlrest.html b/config/npgsqlrest.html new file mode 100644 index 000000000..0639ab544 --- /dev/null +++ b/config/npgsqlrest.html @@ -0,0 +1,155 @@ + + + + + + NpgsqlRest Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    NpgsqlRest Options

    NpgsqlRest HTTP middleware general configuration for endpoint generation and request handling.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false,
    +    "CommandTimeout": null,
    +    "SchemaSimilarTo": null,
    +    "SchemaNotSimilarTo": null,
    +    "IncludeSchemas": null,
    +    "ExcludeSchemas": null,
    +    "NameSimilarTo": null,
    +    "NameNotSimilarTo": null,
    +    "IncludeNames": null,
    +    "ExcludeNames": null,
    +    "CommentsMode": "OnlyAnnotated",
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "RequiresAuthorization": true,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +    "DefaultHttpMethod": null,
    +    "DefaultRequestParamType": null,
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersContextKey": "request.headers",
    +    "RequestHeadersParameterName": "_headers",
    +    "InstanceIdRequestHeaderName": null,
    +    "CustomRequestHeaders": {},
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +    "QueryStringNullHandling": "Ignore",
    +    "TextResponseNullHandling": "EmptyString",
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": {},
    +    "RoutineOptions": { ... },
    +    "AuthenticationOptions": { ... },
    +    "SqlFileSource": { ... },
    +    "UploadOptions": { ... },
    +    "ClientCodeGen": { ... },
    +    "HttpFileOptions": { ... },
    +    "OpenApiOptions": { ... }
    +  }
    +}

    See related configuration pages:

    Connection Settings

    SettingTypeDefaultDescription
    ConnectionNamestringnullConnection name from ConnectionStrings section. Uses first available if null.
    UseMultipleConnectionsboolfalseAllow individual routines to use different connections from ConnectionStrings.
    CommandTimeoutstringnullCommand timeout using interval format (e.g., "30s", "1m"). Uses default 30 seconds if null. Can be overridden per endpoint with command_timeout annotation.

    Schema and Name Filtering

    Filter which PostgreSQL routines are exposed as endpoints.

    SettingTypeDefaultDescription
    SchemaSimilarTostringnullInclude schemas matching this SQL SIMILAR TO pattern.
    SchemaNotSimilarTostringnullExclude schemas matching this SQL SIMILAR TO pattern.
    IncludeSchemasarraynullList of schema names to include.
    ExcludeSchemasarraynullList of schema names to exclude.
    NameSimilarTostringnullInclude routine names matching this SQL SIMILAR TO pattern.
    NameNotSimilarTostringnullExclude routine names matching this SQL SIMILAR TO pattern.
    IncludeNamesarraynullList of routine names to include.
    ExcludeNamesarraynullList of routine names to exclude.

    Filtering Examples

    Include only specific schemas:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "IncludeSchemas": ["api", "public"]
    +  }
    +}

    Exclude internal schemas:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ExcludeSchemas": ["pg_catalog", "information_schema", "internal"]
    +  }
    +}

    Filter by name pattern:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "NameSimilarTo": "api_%",
    +    "NameNotSimilarTo": "%_internal"
    +  }
    +}

    Comments Mode

    SettingTypeDefaultDescription
    CommentsModestring"OnlyAnnotated"How comment annotations affect endpoint creation.

    Available modes:

    ModeDescription
    IgnoreCreate all endpoints, ignore comment annotations.
    ParseAllCreate all endpoints, parse comment annotations to modify them.
    OnlyWithHttpTagOnly create endpoints for routines with an HTTP annotation in comments. Kept as an identical-behavior alias of OnlyAnnotated for existing configs.
    OnlyAnnotatedOnly create endpoints for routines with an HTTP annotation or a plugin annotation that requests an endpoint (e.g. @mcp — so an MCP-only routine can exist with no HTTP route). Client default since 3.17.0.

    With the default OnlyAnnotated mode, routines without an HTTP (or endpoint-requesting plugin) annotation in their comment will not be exposed as endpoints. This provides explicit control over which database routines are accessible via the API.

    Client vs. library default

    The standalone client (npgsqlrest executable) defaults to OnlyAnnotated since 3.17.0. The C# library (NpgsqlRestOptions.CommentsMode) defaults to OnlyWithHttpTag; the two behave identically unless a plugin (such as MCP) requests endpoints.

    URL and Naming

    SettingTypeDefaultDescription
    UrlPathPrefixstring"/api"URL prefix for all generated endpoints.
    KebabCaseUrlsbooltrueConvert URL paths to kebab-case from PostgreSQL names.
    CamelCaseNamesbooltrueConvert parameter names to camelCase from PostgreSQL names.

    URL Examples

    With default settings, get_user_profile becomes /api/get-user-profile.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/v1/api",
    +    "KebabCaseUrls": true
    +  }
    +}

    Authorization

    SettingTypeDefaultDescription
    RequiresAuthorizationbooltrueForce all endpoints to require authorization. Can be overridden per endpoint via comment annotations.

    Logging

    SettingTypeDefaultDescription
    LogConnectionNoticeEventsbooltrueLog PostgreSQL connection events (triggered by RAISE statements).
    LogConnectionNoticeEventsModestring"FirstStackFrameAndMessage"How to format notice event logs.
    LogCommandsboolfalseLog every executed command and query at debug level.
    LogCommandParametersboolfalseInclude parameter values in command logs. Only applies when LogCommands is true.
    DebugLogEndpointCreateEventsbooltrueEmit a debug log for each endpoint created at startup (URL and method).
    DebugLogCommentAnnotationEventsbooltrueEmit a debug log for each comment annotation that is successfully processed.

    Notice Event Modes

    ModeDescription
    MessageOnlyLog only the message.
    FirstStackFrameAndMessageLog first stack frame and message (default).
    FullStackAndMessageLog full stack trace and message.

    HTTP Method and Parameters

    SettingTypeDefaultDescription
    DefaultHttpMethodstringnullForce HTTP method for all endpoints (GET, POST, PUT, DELETE, etc.).
    DefaultRequestParamTypestringnullForce parameter location for all endpoints (QueryString or BodyJson).

    Default Behavior

    When DefaultHttpMethod is null:

    • GET is used when routine is not volatile, or name starts with get_, contains _get_, or ends with _get
    • POST is used otherwise

    When DefaultRequestParamType is null:

    • QueryString for GET and DELETE endpoints
    • BodyJson for all other methods

    Request Headers

    SettingTypeDefaultDescription
    RequestHeadersModestring"Parameter"How to send request headers to PostgreSQL routines.
    RequestHeadersContextKeystring"request.headers"Context variable name when mode is Context.
    RequestHeadersParameterNamestring"_headers"Parameter name when mode is Parameter.
    CustomRequestHeadersobject{}Custom headers added to requests before sending to PostgreSQL.
    InstanceIdRequestHeaderNamestringnullHeader name for NpgsqlRest instance ID. Set to null to disable.
    ExecutionIdHeaderNamestring"X-NpgsqlRest-ID"Execution request header name. Used for request tracking and SSE correlation and in ConnectionSettings.UseJsonApplicationName.

    Request Headers Modes

    ModeDescription
    IgnoreDon't send request headers to routines.
    ContextSet context variable context.headers with JSON string via set_config().
    ParameterSend headers to parameter named by RequestHeadersParameterName. Parameter must be JSON/text type with default value.

    Connection Pooler Compatibility

    New in 3.13.0

    WrapInTransaction and BeforeRoutineCommands options for connection pooler compatibility and pre-routine SQL commands.

    SettingTypeDefaultDescription
    WrapInTransactionboolfalseWhen true, every request is wrapped in an explicit BEGIN ... COMMIT, and all set_config calls switch from session-scoped (is_local=false) to transaction-local (is_local=true).
    BeforeRoutineCommandsarray[]SQL commands executed after any context is set but before the main routine call. Run in the same batch as the context set_config calls (no extra round-trip).

    WrapInTransaction

    This is required for connection poolers in transaction mode — including PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, and Supabase Pooler. Previously, set_config(name, value, false) would set the GUC at the session level on the underlying PostgreSQL backend. With a transaction-mode pooler, the same backend is reused for unrelated client requests, which means session-scoped GUCs from one request could be visible to the next. With WrapInTransaction = true, GUCs are scoped to the request transaction and discarded on COMMIT.

    The default remains false to preserve existing behavior; it is safe to leave off when using Npgsql's native pool only (which issues DISCARD ALL on connection return).

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true
    +  }
    +}

    BeforeRoutineCommands

    Each entry can be either a raw SQL string (no parameters) or an object with Sql and Parameters. Each parameter has a Source (Claim, RequestHeader, or IpAddress) and an optional Name (claim type or header name). Parameter values are bound at request time from HttpContext — claim and header values are passed as parameterized SQL inputs (no string interpolation, no injection risk).

    The most useful pattern is multi-tenant search_path setup driven by a JWT/cookie claim:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true,
    +    "BeforeRoutineCommands": [
    +      "select set_config('app.request_time', clock_timestamp()::text, true)",
    +      {
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +      }
    +    ]
    +  }
    +}

    Per-request execution order with this config:

    1. BEGIN
    2. Each BeforeRoutineCommand is added as a NpgsqlBatchCommand (with parameters bound from claims/headers/IP) and dispatched in a single batch.
    3. The main routine call.
    4. COMMIT.

    Steps 1–3 share a single network round-trip.

    NULL Handling

    SettingTypeDefaultDescription
    QueryStringNullHandlingstring"Ignore"How empty or "null" query string values are interpreted.
    TextResponseNullHandlingstring"EmptyString"How NULL database results are returned in plain text responses.

    QueryStringNullHandling Values

    ValueDescription
    IgnoreNo special handling - empty strings stay as empty strings, "null" literal stays as "null" string (default).
    EmptyStringEmpty query string values are interpreted as NULL values.
    NullLiteralLiteral string "null" (case insensitive) is interpreted as NULL value.

    TextResponseNullHandling Values

    ValueDescription
    EmptyStringReturns an empty string response with status code 200 OK (default).
    NullLiteralReturns a string literal "NULL" with status code 200 OK.
    NoContentReturns status code 204 NO CONTENT.

    These settings can be overridden per-endpoint using comment annotations:

    sql
    sql
    comment on function my_func(text) is '
    +@query_string_null_handling empty_string
    +@text_response_null_handling no_content
    +';

    JSON Timestamp Handling

    SettingTypeDefaultDescription
    JsonTimestampsAreUtcbooltrueHow JSON-encoded timestamps are interpreted when parsed into timestamp, timestamptz, time, and timetz parameters.

    When true (default, recommended):

    • Z-suffixed and offset-bearing ISO strings (e.g. "2026-05-20T06:00:00Z", "2026-05-20T08:00:00+02:00") are converted to UTC.
    • Naive ISO strings with no offset and no Z (e.g. "2026-05-20T06:00:00") are assumed UTC rather than interpreted as the host's local time.

    The result is host-TZ-independent: the same JSON payload produces the same stored value regardless of the TZ environment of the process serving the request.

    When false, the parsers fall back to the pre-3.16.0 behavior:

    • Z / offset-bearing strings are converted to the host's local time zone and tagged Kind=Local.
    • Naive strings are parsed as Kind=Unspecified.
    • The timestamptz / timetz parsers then re-apply SpecifyKind(Utc) on top of the local-shifted value — silently shifting the stored value by the host's UTC offset on non-UTC hosts.

    Opt-out only — not recommended for new deployments

    JsonTimestampsAreUtc: false exists as a compatibility escape hatch for callers that genuinely depend on the legacy "naive timestamps are host-local" behavior and cannot be updated to send Z-suffixed values. It reproduces the bug class fixed in 3.16.0. Leave at the default unless you have a specific legacy reason to flip it.

    Example:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "JsonTimestampsAreUtc": true
    +  }
    +}

    Server-Sent Events

    Configure Server-Sent Events (SSE) for real-time streaming of PostgreSQL RAISE statements to connected clients.

    SettingTypeDefaultDescription
    DefaultServerSentEventsEventNoticeLevelstring"INFO"Default PostgreSQL notice level for SSE events. Valid values: INFO, NOTICE, WARNING.
    ServerSentEventsResponseHeadersobject{}Custom headers added to SSE responses.

    Notice Level Behavior

    The DefaultServerSentEventsEventNoticeLevel setting determines which PostgreSQL RAISE statements generate SSE events by default when the level is not specified in the annotation.

    Important

    SSE events are sent only for the exact level configured, not for "this level and above". For example, if set to NOTICE, only RAISE NOTICE statements generate SSE events—RAISE INFO and RAISE WARNING are ignored.

    This default can be overridden per-endpoint using the @sse annotation.

    Example Configuration

    json
    json
    {
    +  "NpgsqlRest": {
    +    "DefaultServerSentEventsEventNoticeLevel": "NOTICE",
    +    "ServerSentEventsResponseHeaders": {
    +      "X-Accel-Buffering": "no"
    +    }
    +  }
    +}

    The X-Accel-Buffering: no header is commonly needed when running behind nginx to disable response buffering for SSE streams.

    Unbound RAISE warning

    SettingTypeDefaultDescription
    WarnUnboundServerSentEventsNoticesbooltrueWhen at least one SSE endpoint exists, log a one-time warning per endpoint whose RAISE matches the SSE notice level but is not annotated as an SSE publisher (a likely missing sse_publish annotation). Apps with no SSE endpoints pay zero overhead and see no warnings.

    Environment Variables in Annotation Values

    SettingTypeDefaultDescription
    AvailableEnvVarsarray or object[]Allowlist of environment variable names available to {name} placeholder substitution in comment annotation values (response headers, custom parameters, HTTP custom type calls), alongside the routine's parameters. Array form lists names (a missing variable becomes the empty string); object form maps name → default. Resolved once at startup; matched case-insensitively; a routine parameter of the same name takes precedence.

    Security

    A value substituted into a response header is sent to the client. Reserve secrets (API keys, tokens) for outbound HTTP custom type calls, and use response headers only for non-secret values (e.g. a server/environment name). Only allowlisted names are ever read from the environment.

    Complete Example

    Production configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ConnectionName": null,
    +    "UseMultipleConnections": true,
    +    "CommandTimeout": "30 seconds",
    +    "IncludeSchemas": ["api"],
    +    "ExcludeSchemas": ["internal"],
    +    "CommentsMode": "OnlyAnnotated",
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "RequiresAuthorization": true,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersParameterName": "_headers",
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID"
    +  }
    +}

    Development configuration with verbose logging:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CommentsMode": "ParseAll",
    +    "RequiresAuthorization": false,
    +    "LogConnectionNoticeEvents": true,
    +    "LogConnectionNoticeEventsMode": "FullStackAndMessage",
    +    "LogCommands": true,
    +    "LogCommandParameters": true
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/openapi.html b/config/openapi.html new file mode 100644 index 000000000..8f755d449 --- /dev/null +++ b/config/openapi.html @@ -0,0 +1,196 @@ + + + + + + OpenAPI Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    OpenAPI Options

    Configuration for generating OpenAPI specification files and endpoints for NpgsqlRest APIs.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      "FileName": "npgsqlrest_openapi.json",
    +      "UrlPath": "/openapi.json",
    +      "FileOverwrite": true,
    +      "DocumentTitle": null,
    +      "DocumentVersion": "1.0.0",
    +      "DocumentDescription": null,
    +      "AddCurrentServer": true,
    +      "Servers": [],
    +      "SecuritySchemes": [],
    +      "IncludeSchemas": [],
    +      "ExcludeSchemas": [],
    +      "NameSimilarTo": null,
    +      "NameNotSimilarTo": null,
    +      "RequiresAuthorizationOnly": false,
    +      "OmitAutomaticParameters": false
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable OpenAPI generation.
    FileNamestring"npgsqlrest_openapi.json"File name for generated OpenAPI file. null to skip file generation.
    UrlPathstring"/openapi.json"URL path for OpenAPI endpoint. null to skip endpoint generation.
    FileOverwritebooltrueOverwrite existing files.
    DocumentTitlestringnullAPI title in the info section. Uses database name if null.
    DocumentVersionstring"1.0.0"API version in the info section.
    DocumentDescriptionstringnullAPI description in the info section.
    AddCurrentServerbooltrueInclude current server in the servers section.
    Serversarray[]Additional server entries for the servers section.
    SecuritySchemesarray[]Security schemes for authentication documentation.
    IncludeSchemasstring[][]Schema allow-list. When non-empty, only endpoints whose routine schema appears here are documented.
    ExcludeSchemasstring[][]Schema deny-list. Applied alongside IncludeSchemas.
    NameSimilarTostringnullPostgreSQL SIMILAR TO pattern matched against routine names (anchored). When set, only matching routines are documented.
    NameNotSimilarTostringnullPostgreSQL SIMILAR TO pattern for exclusion. Applied alongside NameSimilarTo.
    RequiresAuthorizationOnlyboolfalseWhen true, document only endpoints that require authorization — health, login, and other anonymous probes drop out.
    OmitAutomaticParametersboolfalseWhen true, omit server-filled parameters from documented query parameters and request bodies. See Omitting automatic parameters.

    Document Info

    Configure the OpenAPI document metadata:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "DocumentTitle": "My API",
    +      "DocumentVersion": "2.0.0",
    +      "DocumentDescription": "REST API for my application"
    +    }
    +  }
    +}

    Servers

    Add server entries to the OpenAPI specification:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "AddCurrentServer": true,
    +      "Servers": [
    +        {
    +          "Url": "https://api.example.com",
    +          "Description": "Production server"
    +        },
    +        {
    +          "Url": "https://staging-api.example.com",
    +          "Description": "Staging server"
    +        }
    +      ]
    +    }
    +  }
    +}

    Security Schemes

    Define authentication schemes for the OpenAPI document. Supported types:

    • Http - For Bearer and Basic authentication
    • ApiKey - For Cookie, Header, or Query parameter authentication

    Bearer Token Authentication

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "bearerAuth",
    +      "Type": "Http",
    +      "Scheme": "Bearer",
    +      "BearerFormat": "JWT",
    +      "Description": "JWT Bearer token authentication"
    +    }
    +  ]
    +}

    Basic Authentication

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "basicAuth",
    +      "Type": "Http",
    +      "Scheme": "Basic",
    +      "Description": "HTTP Basic authentication"
    +    }
    +  ]
    +}
    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "cookieAuth",
    +      "Type": "ApiKey",
    +      "In": ".AspNetCore.Cookies",
    +      "ApiKeyLocation": "Cookie",
    +      "Description": "Cookie-based authentication"
    +    }
    +  ]
    +}

    API Key in Header

    json
    json
    {
    +  "SecuritySchemes": [
    +    {
    +      "Name": "apiKeyAuth",
    +      "Type": "ApiKey",
    +      "In": "X-API-Key",
    +      "ApiKeyLocation": "Header",
    +      "Description": "API key in header"
    +    }
    +  ]
    +}

    Security Scheme Settings

    SettingTypeDescription
    NamestringUnique scheme identifier.
    TypestringScheme type: "Http" or "ApiKey".
    SchemestringHTTP auth scheme ("Bearer", "Basic"). For Type: "Http" only.
    BearerFormatstringBearer token format (e.g., "JWT"). Optional.
    InstringCookie/header/query name. For Type: "ApiKey" only.
    ApiKeyLocationstringLocation: "Cookie", "Header", or "Query". For Type: "ApiKey" only.
    DescriptionstringDescription of the security scheme.

    Complete Example

    Production configuration with multiple security schemes:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "FileName": "openapi.json",
    +      "UrlPath": "/openapi.json",
    +      "FileOverwrite": true,
    +      "DocumentTitle": "My REST API",
    +      "DocumentVersion": "1.0.0",
    +      "DocumentDescription": "REST API generated from PostgreSQL functions",
    +      "AddCurrentServer": true,
    +      "Servers": [
    +        {
    +          "Url": "https://api.example.com",
    +          "Description": "Production server"
    +        }
    +      ],
    +      "SecuritySchemes": [
    +        {
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }
    +      ]
    +    }
    +  }
    +}

    Filters (New in 3.15.0)

    Five config keys (and a per-routine @openapi comment annotation) control which endpoints appear in the generated document. The HTTP endpoints themselves are unaffected — only their inclusion in the spec is. Defaults are "no filter", so existing configs see no change.

    Schema and name filters

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "IncludeSchemas": ["partner"],
    +      "ExcludeSchemas": ["internal"],
    +      "NameSimilarTo": "partner_%",
    +      "NameNotSimilarTo": "%_admin",
    +      "RequiresAuthorizationOnly": true
    +    }
    +  }
    +}
    • NameSimilarTo / NameNotSimilarTo use PostgreSQL SIMILAR TO syntax — _ matches one char, % matches any sequence; |, *, +, ?, (...), [...] work via regex translation. Anchored (the pattern must cover the entire routine name).
    • All filters are conjunctive — an endpoint must pass every one to be documented.

    Filter order

    Filters are applied in this order; the first rejection short-circuits the rest:

    1. @openapi hide annotation (per-routine wins over everything)
    2. RequiresAuthorizationOnly vs. the endpoint's authorization requirement
    3. IncludeSchemas membership
    4. ExcludeSchemas membership
    5. NameSimilarTo match
    6. NameNotSimilarTo match (negative)

    Partner-facing document example

    A full "host serves one API, partner-only OpenAPI document" config:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "FileName": "openapi-partner.json",
    +      "UrlPath": "/openapi/partner.json",
    +      "DocumentTitle": "Acme Partner API",
    +      "DocumentDescription": "JWT-authenticated REST surface for partner integrations.",
    +
    +      "IncludeSchemas": ["partner"],
    +      "RequiresAuthorizationOnly": true,
    +      "NameNotSimilarTo": "%_admin",
    +
    +      "SecuritySchemes": [
    +        { "Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT" }
    +      ],
    +      "Servers": [
    +        { "Url": "https://api.acme.com", "Description": "Production" }
    +      ]
    +    }
    +  }
    +}

    The internal cookie-authenticated surface stays reachable on the same host — only the document is partner-scoped.

    One document per process

    Only one OpenAPI document is generated per host. To serve both a partner and an internal spec, run two NpgsqlRest hosts with different filter configs, or filter to a single audience.

    Omitting Automatic Parameters

    New in 3.18.2

    OmitAutomaticParameters was added in 3.18.2 (also available on the Code Generation and HTTP File generators). Default is false, so the generated document is unchanged unless you opt in.

    Some parameters are filled by the server and a client value would simply be ignored — documenting them as settable is misleading. When OmitAutomaticParameters is true, such a parameter is left out of the generated document (query parameters and request body) when it is automatic and optional. "Automatic" covers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "OpenApiOptions": {
    +      "Enabled": true,
    +      "OmitAutomaticParameters": true
    +    }
    +  }
    +}

    When every parameter of an endpoint is omitted, the operation is documented with no parameters and no requestBody.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/passkey-auth.html b/config/passkey-auth.html new file mode 100644 index 000000000..0f749a7ea --- /dev/null +++ b/config/passkey-auth.html @@ -0,0 +1,115 @@ + + + + + + Passkey Authentication Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Passkey Authentication

    NpgsqlRest supports WebAuthn/FIDO2 passkey authentication, providing phishing-resistant, passwordless login using device-native biometrics or PINs.

    New in 3.5.0

    Passkey authentication was added in version 3.5.0.

    Overview

    Passkeys use public-key cryptography tied to user devices. Unlike passwords, passkeys:

    • Cannot be phished (tied to origin)
    • Cannot be reused across sites
    • Cannot be stolen in database breaches (only public keys stored)
    • Require biometric or PIN verification

    Minimal configuration to enable passkey authentication:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "Enabled": true
    +    }
    +  }
    +}

    How It Works

    NpgsqlRest handles the WebAuthn protocol (CBOR parsing, signature verification) while your PostgreSQL functions control the business logic:

    mermaid
    flowchart LR
    +    A["Browser
    +    (Client Script)"] <--> B["NpgsqlRest
    +    (Endpoints + CBOR)"] <--> C["PostgreSQL
    +    (SQL Functions)"]
    • Browser: A client script that calls the browser's WebAuthn API and communicates with NpgsqlRest endpoints. See the passkey.ts example for a complete TypeScript implementation you can use as a starting point.
    • NpgsqlRest: Provides the HTTP endpoints and handles CBOR parsing/verification.
    • PostgreSQL: Your SQL functions control the entire authentication flow.

    Your database stores only public keys - no biometric data ever touches your server.

    Three Authentication Flows

    NpgsqlRest supports three distinct passkey flows. Each endpoint internally executes a configured SQL command (typically a PostgreSQL function) that you define.

    1. Registration (New User with Passkey)

    For new users signing up with a passkey. Creates both the user account and passkey.

    EndpointExecutes SQL Command
    POST /api/passkey/register/optionsChallengeRegistrationCommand
    POST /api/passkey/registerCompleteRegistrationCommand

    Registration is Disabled by Default

    Standalone registration (EnableRegister: false by default) allows anyone to create an account with just a passkey. In production, you'll typically want additional verification (email confirmation, CAPTCHA, etc.) before creating accounts.

    The recommended approach is:

    1. Create user accounts through your existing registration flow
    2. Let users add passkeys to verified accounts using the Add Passkey flow

    2. Add Passkey (Existing User)

    For authenticated users who want to add a passkey to their account. These endpoints require authentication.

    Endpoint (requires auth)Executes SQL Command
    POST /api/passkey/add/optionsChallengeAddExistingUserCommand
    POST /api/passkey/addCompleteAddExistingUserCommand

    3. Login

    For authenticating with an existing passkey.

    EndpointExecutes SQL Command
    POST /api/passkey/login/optionsChallengeAuthenticationCommand
    POST /api/passkey/loginCompleteAuthenticateCommand

    Settings Reference

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable passkey authentication.
    EnableRegisterboolfalseEnable standalone registration (new users can sign up with passkey only).
    RateLimiterPolicystringnullName of a configured rate limiter policy. Recommended for brute-force protection.
    ConnectionNamestringnullNamed connection for multi-database setups. Uses default if null.
    CommandRetryStrategystring"default"Retry strategy for transient database errors. Set to null to disable.

    Relying Party Settings

    The Relying Party (RP) identifies your application to the authenticator.

    SettingTypeDefaultDescription
    RelyingPartyIdstringnullDomain name (e.g., "example.com"). Auto-detected if null. Note: IP addresses not permitted - use "localhost" for development.
    RelyingPartyNamestringnullHuman-readable name shown during registration. Uses ApplicationName if null.
    RelyingPartyOriginsstring[][]Allowed origins (e.g., ["https://example.com"]). ⚠️ When empty, origin validation accepts ANY origin — a startup warning is logged (3.17.0+). Always set explicitly in production.

    Endpoint Paths

    All paths are POST endpoints. Set to null to disable an endpoint.

    SettingDefaultDescription
    AddPasskeyOptionsPath"/api/passkey/add/options"Get options for adding passkey to existing user (requires auth).
    AddPasskeyPath"/api/passkey/add"Complete adding passkey (requires auth).
    RegistrationOptionsPath"/api/passkey/register/options"Get options for new user registration.
    RegistrationPath"/api/passkey/register"Complete new user registration.
    LoginOptionsPath"/api/passkey/login/options"Get login challenge.
    LoginPath"/api/passkey/login"Complete authentication.

    WebAuthn Settings

    SettingTypeDefaultDescription
    ChallengeTimeoutMinutesint5How long challenges remain valid.
    ValidateSignCountbooltrueValidate signature counter to detect cloned authenticators.
    UserVerificationRequirementstring"required"See below.
    ResidentKeyRequirementstring"required"See below.
    AttestationConveyancestring"none"See below.

    UserVerificationRequirement

    Controls whether biometric/PIN verification is required:

    ValueBehaviorUse Case
    "required"User MUST verify with biometric or PINBanking, healthcare, sensitive data
    "preferred"Request verification if availableMost consumer apps
    "discouraged"Don't request verification (proves possession only)Low-security scenarios

    ResidentKeyRequirement

    Controls discoverable credentials (true passwordless):

    ValueBehaviorUse Case
    "required"Credential stored on device; browser shows account pickerTrue passwordless (no username field)
    "preferred"Request discoverable if supportedGradual migration to passwordless
    "discouraged"Server must provide credential IDUsername-first flows

    AttestationConveyance

    Controls whether to verify authenticator hardware:

    ValueBehaviorUse Case
    "none"Accept any authenticatorMost apps (recommended)
    "indirect"Allow anonymized attestationRarely useful
    "direct"Request full attestation chainVerify specific hardware models
    "enterprise"Enterprise-managed attestationCorporate device policies

    SQL Commands Reference

    NpgsqlRest calls your SQL functions at specific points in each flow.

    ChallengeAddExistingUserCommand

    When executed: User clicks "Add Passkey" (already authenticated)

    Parameters:

    • $1 = claims (json): User claims from authenticated session
    • $2 = body (json): Request body (e.g., { "deviceName": "My Phone" })

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code. Return 200 to proceed.
    messagetextError message when status ≠ 200.
    challengetextBase64-encoded random bytes (32 bytes recommended).
    challenge_idbigint/uuid/textServer-side identifier for verification.
    user_handletextBase64-encoded random bytes for WebAuthn user.id.
    user_nametextUsername shown in authenticator UI.
    user_display_nametextDisplay name shown in authenticator UI.
    exclude_credentialstextJSON array of existing credential IDs.
    user_contextjsonPassed through to completion command.

    ChallengeRegistrationCommand

    When executed: New user starts passkey-only registration

    Parameters:

    • $1 = body (json): Request body with user info

    Expected return columns: Same as ChallengeAddExistingUserCommand

    ChallengeAuthenticationCommand

    When executed: User initiates passkey login

    Parameters:

    • $1 = user_name (text): Username if provided, NULL for discoverable credentials
    • $2 = body (json): Request body

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed).
    messagetextError message when status ≠ 200.
    challengetextBase64-encoded random challenge.
    challenge_idbigint/uuid/textServer-side identifier.
    allow_credentialstextJSON array of credential IDs for this user.

    VerifyChallengeCommand

    When executed: After browser returns credential, before cryptographic verification

    Used by: ALL flows

    Parameters:

    • $1 = challenge_id: The challenge_id from options response
    • $2 = operation (text): Either "registration" or "authentication"

    Expected return: Single column challenge (bytea) - original challenge bytes, or NULL if not found/expired

    AuthenticateDataCommand

    When executed: During login, to retrieve stored credential data

    Parameters:

    • $1 = credential_id (bytea): The credential ID from browser

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 to proceed).
    messagetextError message when status ≠ 200.
    public_keybyteaStored public key for signature verification.
    public_key_algorithmintCOSE algorithm ID (-7 for ES256, -257 for RS256).
    sign_countbigintCurrent signature counter.
    user_contextjsonPassed to CompleteAuthenticateCommand.

    CompleteAddExistingUserCommand / CompleteRegistrationCommand

    When executed: After successful attestation verification

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - Unique credential identifier
    $2byteauser_handle - WebAuthn user.id
    $3byteapublic_key - Public key in COSE format
    $4intalgorithm - COSE algorithm (-7 = ES256, -257 = RS256)
    $5text[]transports - Transport hints (e.g., ["internal", "hybrid"])
    $6booleanbackup_eligible - Whether credential can be synced
    $7jsonuser_context - From challenge command
    $8jsonanalytics_data - Optional client analytics

    Expected return columns:

    ColumnTypeDescription
    statusintHTTP status code (200 = success).
    messagetextError message when status ≠ 200.

    CompleteAuthenticateCommand

    When executed: After successful signature verification during login

    Parameters:

    ParameterTypeDescription
    $1byteacredential_id - The credential used
    $2bigintnew_sign_count - Updated signature counter
    $3jsonuser_context - From AuthenticateDataCommand
    $4jsonanalytics_data - Optional client analytics

    Expected return columns: Same as login endpoint - the scheme column determines authentication type, other columns become claims.

    Column Name Configuration

    If your SQL functions use different column names:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "StatusColumnName": "status",
    +      "MessageColumnName": "message",
    +      "ChallengeColumnName": "challenge",
    +      "ChallengeIdColumnName": "challenge_id",
    +      "UserNameColumnName": "user_name",
    +      "UserDisplayNameColumnName": "user_display_name",
    +      "UserHandleColumnName": "user_handle",
    +      "ExcludeCredentialsColumnName": "exclude_credentials",
    +      "AllowCredentialsColumnName": "allow_credentials",
    +      "PublicKeyColumnName": "public_key",
    +      "PublicKeyAlgorithmColumnName": "public_key_algorithm",
    +      "SignCountColumnName": "sign_count"
    +    }
    +  }
    +}

    Analytics Data

    Collect client-side analytics by passing analyticsData in completion requests. NpgsqlRest automatically adds the client's IP address:

    json
    json
    {
    +  "Auth": {
    +    "PasskeyAuth": {
    +      "ClientAnalyticsIpKey": "ip"
    +    }
    +  }
    +}

    Set to null or empty string to disable IP collection.

    Complete Example

    Minimal Configuration

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "PasskeyAuth": {
    +      "Enabled": true
    +    }
    +  }
    +}

    Full Configuration

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "PasskeyAuth": {
    +      "Enabled": true,
    +      "EnableRegister": true,
    +      "RateLimiterPolicy": "passkey-limit",
    +
    +      "RelyingPartyId": null,
    +      "RelyingPartyName": "My Application",
    +      "RelyingPartyOrigins": [],
    +
    +      "UserVerificationRequirement": "required",
    +      "ResidentKeyRequirement": "required",
    +      "AttestationConveyance": "none",
    +
    +      "ChallengeTimeoutMinutes": 5,
    +      "ValidateSignCount": true,
    +
    +      "ChallengeAddExistingUserCommand": "select * from passkey_challenge_add_existing($1,$2)",
    +      "ChallengeRegistrationCommand": "select * from passkey_challenge_registration($1)",
    +      "ChallengeAuthenticationCommand": "select * from passkey_challenge_authentication($1,$2)",
    +      "VerifyChallengeCommand": "select * from passkey_verify_challenge($1,$2)",
    +      "AuthenticateDataCommand": "select * from passkey_authenticate_data($1)",
    +      "CompleteAddExistingUserCommand": "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
    +      "CompleteRegistrationCommand": "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
    +      "CompleteAuthenticateCommand": "select * from passkey_complete_authenticate($1,$2,$3,$4)"
    +    }
    +  },
    +  "RateLimiting": {
    +    "Policies": {
    +      "passkey-limit": {
    +        "Type": "SlidingWindow",
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60
    +      }
    +    }
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/proxy.html b/config/proxy.html new file mode 100644 index 000000000..7fb0fe4e5 --- /dev/null +++ b/config/proxy.html @@ -0,0 +1,149 @@ + + + + + + Proxy Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Proxy Options

    Reverse proxy configuration for NpgsqlRest endpoints. When an endpoint is marked as a proxy, incoming HTTP requests are forwarded to an upstream service, and the response can either be returned directly to the client (passthrough mode) or processed by the PostgreSQL function (transform mode).

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": false,
    +      "Host": null,
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      "ResponseBodyParameter": "_proxy_body",
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      "ResponseSuccessParameter": "_proxy_success",
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      "ForwardUploadContent": false,
    +      "MaxForwardedQueryParamLength": 2048
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable proxy functionality for endpoints with proxy annotations.
    HoststringnullDefault upstream host URL. Used when the annotation has no URL. Ignored when the annotation specifies its own URL (absolute or relative). See URL Resolution.
    DefaultTimeoutstring"00:00:30"Default timeout for proxy requests. Format: "HH:MM:SS" or interval format (e.g., "30s").
    ForwardHeadersbooltrueForward request headers to upstream service.
    ExcludeHeadersarray["Host", "Content-Length", "Transfer-Encoding"]Request headers to exclude from forwarding.
    ForwardResponseHeadersbooltrueForward response headers from upstream to client.
    ExcludeResponseHeadersarray["Transfer-Encoding", "Content-Length"]Response headers to exclude from forwarding.
    ForwardUploadContentboolfalseForward raw multipart/form-data to upstream instead of processing locally.
    MaxForwardedQueryParamLengthint2048Maximum length (characters) of a single automatic parameter value appended to the proxy upstream query string. Server-filled values longer than this are skipped with a warning instead of producing an unusable request line (HTTP 414/431). 0 or less disables the guard. See Query-string length guard.

    Response Parameter Names

    These settings configure which parameter names receive proxy response data:

    SettingTypeDefaultDescription
    ResponseStatusCodeParameterstring"_proxy_status_code"Parameter name for HTTP status code from upstream.
    ResponseBodyParameterstring"_proxy_body"Parameter name for response body content.
    ResponseHeadersParameterstring"_proxy_headers"Parameter name for response headers as JSON.
    ResponseContentTypeParameterstring"_proxy_content_type"Parameter name for Content-Type header value.
    ResponseSuccessParameterstring"_proxy_success"Parameter name for success indicator (true for 2xx status).
    ResponseErrorMessageParameterstring"_proxy_error_message"Parameter name for error message if request failed.

    Proxy Modes

    Passthrough Mode

    When the PostgreSQL function has no proxy response parameters, the upstream response is returned directly to the client without opening a database connection:

    sql
    sql
    create function get_external_data()
    +returns void
    +language sql
    +begin atomic;
    +select;
    +end;
    +
    +comment on function get_external_data() is 'HTTP GET
    +@proxy https://api.example.com/data';

    Equivalent as a SQL file endpoint (sql/get-external-data.sql):

    sql
    sql
    -- HTTP GET
    +-- @proxy https://api.example.com/data
    +select;

    Transform Mode

    When the PostgreSQL function has parameters matching the configured response parameter names, the proxy response is passed to the function for processing:

    sql
    sql
    create function get_and_transform(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_headers json default null,
    +    _proxy_content_type text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', _proxy_error_message);
    +    end if;
    +    return json_build_object(
    +        'status', _proxy_status_code,
    +        'data', _proxy_body::json
    +    );
    +end;
    +$$;
    +
    +comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
    +@proxy https://api.example.com/data';

    Response Parameters

    When the PostgreSQL function has parameters matching these names (the defaults below, or whatever you set in the Response Parameter Names settings above), the upstream response data is bound to them after the request returns:

    Parameter NameTypeDescription
    _proxy_status_codeint or textHTTP status code from upstream (e.g., 200, 404). Bound as text if the parameter is declared text/varchar, otherwise as an integer.
    _proxy_bodytextResponse body content.
    _proxy_headersjsonResponse headers as JSON object.
    _proxy_content_typetextContent-Type header value.
    _proxy_successbooleanTrue for 2xx status codes.
    _proxy_error_messagetextError message if request failed.

    Parameters are matched by name (case-insensitive), not by position, and only the ones your function actually needs have to be declared. See How parameters are mapped in the annotation reference for the full rules.

    Automatic Parameter Forwarding

    Parameters that NpgsqlRest fills server-side (not supplied by the client) are forwarded to the upstream so the proxy receives the same parameter set the function would. All automatic sources are treated consistently:

    • user claims (claim-mapped parameters, user_params),
    • the IP address parameter,
    • HTTP Custom Type fields (the auto-filled responseBody / responseStatusCode / … on a routine with an HTTP Custom Type parameter),
    • resolved-parameter expressions (values looked up server-side via SQL).

    Placement follows the endpoint shape, not the HTTP verb

    Where each automatic parameter is placed mirrors how the endpoint itself receives parameters — it is decided by RequestParamType, not by the HTTP method (a POST endpoint can use param_type query):

    • The parameter designated as the body parameter (@body_parameter_name) carries the raw request body.
    • Otherwise: QueryString → appended to the proxy query string; BodyJson → merged into the proxy JSON body (typed: numbers, booleans, embedded JSON, or strings) when the proxy method can carry a body.

    Forwarding is additive — the verbatim incoming request is still forwarded; the automatic parameters are added on top. Body merging applies only when the forwarded request carries a JSON content type (multipart / non-JSON is forwarded verbatim).

    sql
    sql
    -- GET endpoint (QueryString): the auto-filled values are appended to the proxy query string.
    +create function proxy_with_claims(
    +    _user_id text default null,        -- Forwarded as ?userId=...
    +    _user_name text default null,      -- Forwarded as ?userName=...
    +    _ip_address text default null,     -- Forwarded as ?ipAddress=...
    +    _user_claims json default null,    -- Forwarded as ?userClaims=...
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('user', _user_id, 'data', _proxy_body);
    +end;
    +$$;
    +
    +comment on function proxy_with_claims(text, text, text, json, int, text) is 'HTTP GET
    +@authorize
    +@user_params
    +@proxy https://api.example.com/data';

    Behavior note (3.18.1)

    Before 3.18.1, user-claim and IP parameters were always forwarded on the query string, and HTTP Custom Type fields / resolved parameters were not forwarded at all. They are now unified under the rule above. For QueryString endpoints (the default for GET) the result is unchanged — values stay on the query string; for BodyJson endpoints the automatic parameters are now merged into the JSON body instead.

    Query-string length guard

    When an automatic parameter is placed on the proxy query string (a QueryString endpoint — see above), an oversized value would be percent-encoded into the request line and produce a URL the upstream rejects (HTTP 414 URI Too Long / 431 Request Header Fields Too Large) or that resets the connection. A common trigger is an HTTP Custom Type field whose body holds a large payload (e.g. a scraped HTML page).

    MaxForwardedQueryParamLength (default 2048) caps this. A single server-filled value longer than the limit is skipped with a warning rather than appended to the query string — the rest of the request still forwards normally. Set it to 0 (or less) to disable the guard entirely.

    To forward a large value to the upstream, move it into the request body instead of the query string:

    • use a body-carrying proxy method (POST / PUT / PATCH) so it travels in the request body, and
    • designate the field with @body_parameter_name to carry the raw body, while the remaining small fields stay on the query string under the length guard.

    New in 3.18.2

    MaxForwardedQueryParamLength was added in 3.18.2. Previously a large auto-filled value (such as an HTTP Custom Type body field) was percent-encoded into the upstream query string unconditionally.

    HTTP Headers (user_context)

    When user_context is enabled, user context values are forwarded as HTTP headers to the upstream proxy:

    sql
    sql
    create function proxy_with_context(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('status', _proxy_status_code);
    +end;
    +$$;
    +
    +comment on function proxy_with_context(int, text) is 'HTTP GET
    +@authorize
    +@user_context
    +@proxy https://api.example.com/data';
    +-- Headers forwarded: request.user_id, request.user_name, request.user_roles
    +-- (configurable via ContextKeyClaimsMapping)

    Upload Forwarding

    For upload endpoints with proxy, configure whether to process uploads locally or forward raw multipart data:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}
    ValueDescription
    false (default)Uploads are processed locally; proxy receives parsed data.
    trueRaw multipart/form-data is streamed directly to upstream (memory-efficient for large files).

    Key Features

    • Passthrough mode: No database connection opened when function has no proxy response parameters
    • Transform mode: Process upstream response in PostgreSQL before returning to client
    • User claims forwarding: Authenticated user claims passed as query parameters to upstream
    • User context headers: User context values passed as HTTP headers to upstream
    • Streaming uploads: Memory-efficient streaming for large file uploads when ForwardUploadContent is enabled
    • Timeout handling: Configurable per-request timeout with proper 504 Gateway Timeout responses
    • Header forwarding: Configurable request/response header forwarding with exclusion lists

    Self-Referencing Calls (Relative Paths)

    When a proxy annotation includes a relative path (starting with /), the request is routed to another endpoint on the same NpgsqlRest server:

    sql
    sql
    comment on function my_aggregator() is 'HTTP GET
    +@proxy POST /api/data-source';

    Self-referencing calls bypass the HTTP stack entirely — the endpoint handler is invoked directly in-process via InternalRequestHandler, with zero network overhead.

    URL Resolution

    A relative path in the annotation always creates a self-referencing internal call. The global ProxyOptions.Host setting is not prepended to relative paths — it is only used when the annotation has no URL at all. See URL Resolution for the full priority table.

    Internal-Only Endpoints

    Combine with the @internal annotation to create endpoints accessible only via proxy but not exposed as public HTTP routes:

    sql
    sql
    -- Internal helper: not accessible from outside
    +comment on function get_cached_rates() is 'HTTP GET
    +@internal';
    +
    +-- Public endpoint that proxies the internal one
    +comment on function convert_currency(numeric, text, text) is 'HTTP GET
    +@proxy GET /api/get-cached-rates';

    Direct HTTP call to /api/get-cached-rates returns 404, but the proxy call works.

    Complete Example

    Production configuration with proxy enabled:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "https://api.internal.example.com",
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding", "Authorization"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ForwardUploadContent": false,
    +      "MaxForwardedQueryParamLength": 2048
    +    }
    +  }
    +}

    Next Steps

    See Also

    • PROXY - Enable proxy for endpoints
    • PROXY_OUT - Configure outbound proxy settings

    Comments

    + + + + \ No newline at end of file diff --git a/config/rate-limiter.html b/config/rate-limiter.html new file mode 100644 index 000000000..fb866d101 --- /dev/null +++ b/config/rate-limiter.html @@ -0,0 +1,203 @@ + + + + + + Rate Limiter Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Rate Limiter

    Rate limiting configuration to control the number of requests from clients. Apply policies to endpoints using the rate_limiter_policy annotation.

    Overview

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    "Policies": {}
    +  }
    +}

    Breaking change in 3.13.0

    RateLimiterOptions:Policies was previously an array of objects with explicit "Name" properties. It is now an object keyed by policy name, matching ValidationOptions:Rules and CacheOptions:Profiles. Migrate by moving each policy's Name value to be the JSON key and dropping the Name field. If you upgrade with the old array form still in your config, startup will fail with a clear InvalidOperationException telling you to migrate.

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable rate limiting.
    StatusCodeint429HTTP status code returned when rate limit is exceeded.
    StatusMessagestring"Too many requests. Please try again later."Response message when rate limit is exceeded.
    DefaultPolicystringnullName of the default policy to apply to all endpoints.
    Policiesobject{}Named rate limiting policies, keyed by policy name. Assign a policy to an endpoint using the rate_limiter_policy annotation.

    Policy Types

    Four policy types are available:

    • FixedWindow - Fixed time window rate limiting
    • SlidingWindow - Sliding time window rate limiting
    • TokenBucket - Token bucket algorithm
    • Concurrency - Concurrent request limiting

    Fixed Window Policy

    Limits requests within fixed time intervals.

    json
    json
    {
    +  "Policies": {
    +    "fixed": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}

    The JSON key ("fixed") is the policy name used with the rate_limiter_policy annotation.

    SettingTypeDefaultDescription
    Typestring-Must be "FixedWindow".
    EnabledboolfalseEnable this policy.
    PermitLimitint100Maximum requests allowed per window.
    WindowSecondsint60Window duration in seconds.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish permits.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Fixed Window Limiter documentation.

    Sliding Window Policy

    Limits requests using a sliding time window with segments.

    json
    json
    {
    +  "Policies": {
    +    "sliding": {
    +      "Type": "SlidingWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "SegmentsPerWindow": 6,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "SlidingWindow".
    EnabledboolfalseEnable this policy.
    PermitLimitint100Maximum requests allowed per window.
    WindowSecondsint60Window duration in seconds.
    SegmentsPerWindowint6Number of segments dividing the window.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish permits.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Sliding Window Limiter documentation.

    Token Bucket Policy

    Limits requests using the token bucket algorithm.

    json
    json
    {
    +  "Policies": {
    +    "bucket": {
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "TokenLimit": 100,
    +      "TokensPerPeriod": 10,
    +      "ReplenishmentPeriodSeconds": 10,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "TokenBucket".
    EnabledboolfalseEnable this policy.
    TokenLimitint100Maximum tokens in the bucket.
    TokensPerPeriodint10Number of tokens to add per replenishment period.
    ReplenishmentPeriodSecondsint10How often tokens are added to the bucket.
    QueueLimitint10Maximum queued requests when limit is reached.
    AutoReplenishmentbooltrueAutomatically replenish tokens.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Token Bucket Limiter documentation.

    Concurrency Policy

    Limits the number of concurrent requests.

    json
    json
    {
    +  "Policies": {
    +    "concurrency": {
    +      "Type": "Concurrency",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "QueueLimit": 5,
    +      "OldestFirst": true
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    Typestring-Must be "Concurrency".
    EnabledboolfalseEnable this policy.
    PermitLimitint10Maximum concurrent requests.
    QueueLimitint5Maximum queued requests when limit is reached.
    OldestFirstbooltrueProcess queued requests oldest first.
    StatusCodeintglobalOptional. HTTP status code returned when this policy rejects a request, overriding RateLimiterOptions:StatusCode. Omit to inherit the global value. See Per-Policy Status Code and Message.
    StatusMessagestringglobalOptional. Response message when this policy rejects a request, overriding RateLimiterOptions:StatusMessage. Omit to inherit the global value.
    PartitionobjectnullOptional Partition block for per-user / per-IP / per-header rate limiting.

    See Concurrency Limiter documentation.

    Per-User Rate Limiting (Partition)

    New in 3.13.0

    Rate-limiter policies can now be partitioned at request time, so each request gets its own bucket based on a value derived from HttpContext (a claim, an IP, a header, or a static fallback).

    The classic use case is per-user throttling: each authenticated user gets their own quota instead of all users sharing one global bucket. Without Partition, all requests under a policy share a single global bucket.

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    },
    +    "throttle_anon_only": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "BypassAuthenticated": true,
    +        "Sources": [{ "Type": "IpAddress" }]
    +      }
    +    }
    +  }
    +}

    Partition Fields

    FieldTypeDefaultDescription
    Sourcesarray-Ordered list of partition key sources. Walked top-to-bottom at request time; the first source returning a non-empty value wins. If no source resolves, partition resolution falls through to the literal key "unpartitioned".
    BypassAuthenticatedboolfalseWhen true, signed-in users skip the limiter entirely. Evaluated before Sources, so use this for "throttle anonymous only" patterns.

    Source Types

    TypeBehaviorName required?
    ClaimReads HttpContext.User.FindFirst(Name).Value.Yes (the claim type, e.g., "name_identifier").
    IpAddressReads the client IP via HttpRequest.GetClientIpAddress(), which honors X-Forwarded-For / X-Real-IP ahead of Connection.RemoteIpAddress.No
    HeaderReads HttpContext.Request.Headers[Name].Yes (the header name).
    StaticAlways returns the configured Value. Useful as a terminal fallback (e.g., everyone unmatched shares the "anonymous" bucket).Uses Value instead.

    Behavior is unchanged for policies without a Partition block. Each non-partitioned policy still uses a single global bucket.

    Each Sources entry is validated at startup — invalid entries (e.g., Claim without Name, unknown Type) are logged at Warning and skipped. If a Partition block has no usable sources and BypassAuthenticated is false, the partition is dropped (with a Warning) and the policy reverts to a single global bucket.

    Per-Policy Status Code and Message

    New in 3.16.2

    Each named policy can set its own StatusCode and/or StatusMessage, overriding the global RateLimiterOptions:StatusCode / RateLimiterOptions:StatusMessage for requests rejected by that policy. A policy that omits either field inherits the global value.

    Previously the global StatusCode / StatusMessage were the only values returned for any rejected request, so a login-specific message (e.g. "Too many login attempts…") would be returned for every rate-limited endpoint. Now the override is resolved at rejection time from the endpoint's policy, so each policy can speak for itself:

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "StatusCode": 429,                                  // global default
    +  "StatusMessage": "Too many requests. Please slow down.",
    +  "Policies": {
    +    "login_throttle": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +      "Partition": { "Sources": [ { "Type": "IpAddress" } ] }
    +    },
    +    "api": {
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "StatusCode": 503,
    +      "StatusMessage": "API capacity reached. Retry shortly."
    +    }
    +  }
    +}

    A request rejected by login_throttle returns 429 (inherited) with the login message; a request rejected by api returns 503 with the API message. This is fully backward compatible — configs that set only the global values behave exactly as before.

    Ready-to-use login_throttle policy

    The shipped appsettings.json includes a disabled login_throttle policy — 10 attempts per minute partitioned per client IP, with its own rejection message — so the common case is one flag away:

    jsonc
    jsonc
    "login_throttle": {
    +  "Type": "FixedWindow",
    +  "Enabled": false,
    +  "PermitLimit": 10,
    +  "WindowSeconds": 60,
    +  "QueueLimit": 0,
    +  "AutoReplenishment": true,
    +  "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +  "Partition": { "Sources": [ { "Type": "IpAddress" } ], "BypassAuthenticated": false }
    +}

    Set "Enabled": true and apply it to a login endpoint with the rate_limiter_policy annotation (rate_limiter login_throttle), or set it as DefaultPolicy.

    Complete Example

    Configuration with multiple policies:

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": "bucket",
    +    "Policies": {
    +      "fixed": {
    +        "Type": "FixedWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "sliding": {
    +        "Type": "SlidingWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "SegmentsPerWindow": 6,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "bucket": {
    +        "Type": "TokenBucket",
    +        "Enabled": true,
    +        "TokenLimit": 100,
    +        "TokensPerPeriod": 10,
    +        "ReplenishmentPeriodSeconds": 10,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true
    +      },
    +      "concurrency": {
    +        "Type": "Concurrency",
    +        "Enabled": true,
    +        "PermitLimit": 10,
    +        "QueueLimit": 5,
    +        "OldestFirst": true
    +      },
    +      "per_user": {
    +        "Type": "FixedWindow",
    +        "Enabled": true,
    +        "PermitLimit": 100,
    +        "WindowSeconds": 60,
    +        "QueueLimit": 10,
    +        "AutoReplenishment": true,
    +        "Partition": {
    +          "Sources": [
    +            { "Type": "Claim", "Name": "name_identifier" },
    +            { "Type": "IpAddress" },
    +            { "Type": "Static", "Value": "anonymous" }
    +          ]
    +        }
    +      }
    +    }
    +  }
    +}

    Rate Limiting Scope

    Rate-limit policies are applied at the HTTP route level. Every request arriving over HTTP is metered by the policy of the route it hits. Three invocation paths execute endpoints in-process, without passing through the target endpoint's route — by design, the target's own rate_limiter policy is not consulted on these paths:

    Invocation pathWhat still appliesHow it is throttled
    HTTP client type self-calls (a type's URL targets the same server)the target's authorize and all execution-level annotations (the caller's principal is forwarded)by the calling endpoint's own route policy
    Proxy self-calls (@proxy / @proxy_out targeting own URL)same as aboveby the proxy endpoint's own route policy
    MCP tools/callsame as aboveby McpOptions.RateLimiterPolicy on the whole /mcp route (a routine's @rate_limiter does not carry to tool calls; a startup warning is logged for @mcp routines that also have @rate_limiter)

    In short: these paths cannot be reached by a client directly — they exist only where you explicitly composed them in SQL or config — and the outer route that the client does hit is where its rate limit applies.

    Next Steps

    • Server & SSL - Configure HTTPS and Kestrel web server
    • CORS - Configure Cross-Origin Resource Sharing

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/response-compression.html b/config/response-compression.html new file mode 100644 index 000000000..17324ac96 --- /dev/null +++ b/config/response-compression.html @@ -0,0 +1,77 @@ + + + + + + Response Compression | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Response Compression

    Response compression settings for reducing HTTP response sizes using Brotli and Gzip algorithms.

    Overview

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": false,
    +    "EnableForHttps": false,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "Optimal",
    +    "IncludeMimeTypes": [
    +      "text/plain",
    +      "text/css",
    +      "application/javascript",
    +      "text/html",
    +      "application/xml",
    +      "text/xml",
    +      "application/json",
    +      "text/json",
    +      "image/svg+xml",
    +      "font/woff",
    +      "font/woff2",
    +      "application/font-woff",
    +      "application/font-woff2"
    +    ],
    +    "ExcludeMimeTypes": []
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable response compression for HTTP responses.
    EnableForHttpsboolfalseEnable response compression for HTTPS responses.
    UseBrotlibooltrueUse Brotli compression algorithm when supported by client.
    UseGzipFallbackbooltrueUse Gzip compression as fallback when Brotli is not supported.
    CompressionLevelstring"Optimal"Compression level: Optimal, Fastest, NoCompression, SmallestSize.
    IncludeMimeTypesarray(see below)MIME types to include for compression.
    ExcludeMimeTypesarray[]MIME types to exclude from compression.

    Compression Levels

    LevelDescription
    OptimalBalance between compression ratio and speed (default).
    FastestFastest compression with lower compression ratio.
    SmallestSizeBest compression ratio but slower.
    NoCompressionNo compression applied.

    Compression Algorithms

    Brotli

    Brotli provides better compression ratios than Gzip, especially for text content. When UseBrotli is true, the server will use Brotli compression if the client supports it (indicated by Accept-Encoding: br header).

    Gzip Fallback

    When UseGzipFallback is true, the server falls back to Gzip compression for clients that don't support Brotli but do support Gzip (indicated by Accept-Encoding: gzip header).

    HTTPS Compression

    Security Consideration

    Enabling compression for HTTPS responses (EnableForHttps: true) may expose your application to BREACH-style attacks. Only enable if you understand the security implications and have appropriate mitigations in place.

    Default MIME Types

    The default IncludeMimeTypes covers common compressible content:

    CategoryMIME Types
    Texttext/plain, text/css, text/html
    JavaScriptapplication/javascript
    XMLapplication/xml, text/xml
    JSONapplication/json, text/json
    SVGimage/svg+xml
    Fontsfont/woff, font/woff2, application/font-woff, application/font-woff2

    Example Configuration

    Enable compression for production:

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": true,
    +    "EnableForHttps": true,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "Optimal"
    +  }
    +}

    High-compression configuration for bandwidth-constrained environments:

    json
    json
    {
    +  "ResponseCompression": {
    +    "Enabled": true,
    +    "EnableForHttps": true,
    +    "UseBrotli": true,
    +    "UseGzipFallback": true,
    +    "CompressionLevel": "SmallestSize"
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/routine-options.html b/config/routine-options.html new file mode 100644 index 000000000..8c01081b6 --- /dev/null +++ b/config/routine-options.html @@ -0,0 +1,111 @@ + + + + + + Routine Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Routine Options

    Options for handling PostgreSQL routines (functions and procedures).

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "Enabled": true,
    +      "CustomTypeParameterSeparator": null,
    +      "IncludeLanguages": null,
    +      "ExcludeLanguages": null,
    +      "NestedJsonForCompositeTypes": false,
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledbooltrueEnable endpoint creation from PostgreSQL functions and procedures. Set to false for SQL-files-only deployments.
    CustomTypeParameterSeparatorstringnullSeparator for custom type parameter names. Uses underscore (_) if null.
    IncludeLanguagesarraynullList of routine language names to include. Includes all if null. Case-insensitive.
    ExcludeLanguagesarraynullList of routine language names to exclude. Excludes C and INTERNAL if null. Case-insensitive.
    NestedJsonForCompositeTypesbooleanfalseWhen true, composite type columns in return tables are serialized as nested JSON objects instead of flat structure.
    ResolveNestedCompositeTypesbooleantrueWhen true, nested composite types are resolved to any depth, serializing inner composites as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Custom Type Parameter Separator

    When using custom types for parameters, field names are merged with the parameter name:

    sql
    sql
    create type custom_type1 as (value text);
    +
    +create function my_func(_p custom_type1) ...

    With default separator (_), the parameter name becomes _p_value.

    To use a different separator:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "CustomTypeParameterSeparator": "."
    +    }
    +  }
    +}

    This would result in _p.value instead.

    Language Filtering

    By default, routines written in C and INTERNAL are excluded for security reasons. You can customize which languages are included or excluded.

    Include Specific Languages

    To only expose routines written in specific languages:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "IncludeLanguages": ["plpgsql", "sql"]
    +    }
    +  }
    +}

    Exclude Additional Languages

    To exclude additional languages beyond the defaults:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ExcludeLanguages": ["C", "INTERNAL", "plpython3u"]
    +    }
    +  }
    +}

    Common PostgreSQL Languages

    LanguageDescription
    sqlPlain SQL functions
    plpgsqlPL/pgSQL procedural language
    plpython3uPL/Python (untrusted)
    plperlPL/Perl
    pltclPL/Tcl
    CC language (excluded by default)
    INTERNALInternal PostgreSQL functions (excluded by default)

    Nested JSON for Composite Types

    When returning composite types from functions, by default the fields are flattened into the parent object. Enable NestedJsonForCompositeTypes to preserve the nested structure.

    Example function:

    sql
    sql
    create type address_type as (street text, city text, zip_code text);
    +
    +create function get_user_with_address()
    +returns table(user_id int, user_name text, address address_type)
    +language sql
    +begin atomic;
    +  select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
    +end;

    Default output (NestedJsonForCompositeTypes: false):

    json
    json
    [{"userId": 1, "userName": "Alice", "street": "123 Main St", "city": "New York", "zipCode": "10001"}]

    With NestedJsonForCompositeTypes: true:

    json
    json
    [{"userId": 1, "userName": "Alice", "address": {"street": "123 Main St", "city": "New York", "zipCode": "10001"}}]

    To enable globally:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "NestedJsonForCompositeTypes": true
    +    }
    +  }
    +}

    For per-endpoint control, use the @nested annotation.

    TIP

    This setting applies to function/procedure endpoints. SQL file endpoints have their own independent NestedJsonForCompositeTypes setting in SQL File Source configuration.

    Resolve Nested Composite Types

    By default, NpgsqlRest resolves nested composite types to any depth. When a composite type contains another composite type (or an array of composites), the inner composites are serialized as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Example:

    sql
    sql
    create type inner_type as (id int, name text);
    +create type outer_type as (label text, inner_val inner_type);
    +create type with_array as (group_name text, members inner_type[]);
    +
    +create function get_nested_data()
    +returns table(data outer_type, items with_array)
    +language sql
    +begin atomic;
    +select
    +    row('outer', row(1, 'inner')::inner_type)::outer_type,
    +    row('group1', array[row(1,'a')::inner_type, row(2,'b')::inner_type])::with_array;
    +end;

    Output:

    json
    json
    [{
    +  "data": {"label":"outer","innerVal":{"id":1,"name":"inner"}},
    +  "items": {"groupName":"group1","members":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
    +}]

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Default: true - nested composites are fully resolved.

    How it works:

    At application startup, when ResolveNestedCompositeTypes is enabled:

    1. Type Cache Initialization: Queries pg_catalog to build a cache of all composite types in the database, including their field names, field types, and nested relationships.

    2. Metadata Enrichment: For each routine that returns composite types, the field descriptors are enriched with nested type information from the cache.

    3. Runtime Serialization: During request processing, the serializer checks each field's metadata. If the field is marked as a composite type (or array of composites), it recursively parses the PostgreSQL tuple string and outputs a proper JSON object/array.

    When to disable (ResolveNestedCompositeTypes: false):

    ScenarioReason
    Large schemas with thousands of composite typesReduces startup time by skipping the type cache initialization query
    No nested composites in your schemaIf your composites don't contain other composites, the cache provides no benefit
    Memory-constrained environmentsThe type cache consumes memory proportional to the number of composite types
    Backward compatibilityIf you depend on the old tuple string format "(1,x)" in your client code

    Performance considerations:

    • Startup cost: One additional query to pg_catalog at startup to build the type cache
    • Memory: Cache size is proportional to: (number of composite types) × (average fields per type)
    • Runtime: Negligible - just a dictionary lookup per composite field

    Complete Example

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "CustomTypeParameterSeparator": "_",
    +      "IncludeLanguages": ["plpgsql", "sql"],
    +      "ExcludeLanguages": null,
    +      "NestedJsonForCompositeTypes": false,
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/security-headers.html b/config/security-headers.html new file mode 100644 index 000000000..bfaf8dfb1 --- /dev/null +++ b/config/security-headers.html @@ -0,0 +1,117 @@ + + + + + + Security Headers | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Security Headers

    New in 3.6.0

    Security Headers middleware was added in version 3.6.0.

    Configurable security headers middleware to protect against common web vulnerabilities. The middleware adds HTTP security headers to all responses.

    Overview

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": false,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    "ContentSecurityPolicy": null,
    +    "PermissionsPolicy": null,
    +    "CrossOriginOpenerPolicy": null,
    +    "CrossOriginEmbedderPolicy": null,
    +    "CrossOriginResourcePolicy": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    XContentTypeOptionsstring"nosniff"Prevents browsers from MIME-sniffing a response away from the declared content-type. Set to null to not include this header.
    XFrameOptionsstring"DENY"Controls whether the browser should allow the page to be rendered in a frame. Values: "DENY", "SAMEORIGIN". Set to null to not include.
    ReferrerPolicystring"strict-origin-when-cross-origin"Controls how much referrer information should be included with requests. Set to null to not include.
    ContentSecurityPolicystringnullDefines approved sources of content that the browser may load. Helps prevent XSS and code injection attacks.
    PermissionsPolicystringnullControls which browser features and APIs can be used.
    CrossOriginOpenerPolicystringnullControls how your document is shared with cross-origin popups.
    CrossOriginEmbedderPolicystringnullPrevents a document from loading cross-origin resources that don't explicitly grant permission.
    CrossOriginResourcePolicystringnullIndicates how the resource should be shared cross-origin.

    X-Content-Type-Options

    Prevents browsers from MIME-sniffing a response away from the declared content-type, reducing exposure to drive-by download attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff"
    +  }
    +}

    The recommended value is "nosniff".

    X-Frame-Options

    Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>. Prevents clickjacking attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XFrameOptions": "DENY"
    +  }
    +}
    ValueDescription
    DENYNever allow the page to be framed
    SAMEORIGINAllow framing from the same origin only

    WARNING

    This header is skipped if Antiforgery is enabled, as Antiforgery already sets X-Frame-Options: SAMEORIGIN by default via its SuppressXFrameOptionsHeader setting.

    Referrer-Policy

    Controls how much referrer information should be included with requests made from your site.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "ReferrerPolicy": "strict-origin-when-cross-origin"
    +  }
    +}
    ValueDescription
    no-referrerNever send referrer information
    no-referrer-when-downgradeSend full URL for same-security requests, nothing for downgrades
    originSend only the origin (scheme, host, port)
    origin-when-cross-originSend full URL for same-origin, origin only for cross-origin
    same-originSend full URL for same-origin, nothing for cross-origin
    strict-originSend origin for same-security, nothing for downgrades
    strict-origin-when-cross-originSend full URL for same-origin, origin for cross-origin same-security
    unsafe-urlAlways send full URL (not recommended)

    Content-Security-Policy

    Defines approved sources of content that the browser may load. This is the primary browser-side defense against XSS, clickjacking, and other code injection attacks.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "ContentSecurityPolicy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +  }
    +}

    TIP

    CSP should be configured based on your specific application needs. Start with a restrictive policy and loosen as needed.

    Common directives:

    • default-src - Fallback for other directives
    • script-src - Valid sources for JavaScript
    • style-src - Valid sources for stylesheets
    • img-src - Valid sources for images
    • connect-src - Valid sources for fetch, WebSocket, etc.
    • font-src - Valid sources for fonts
    • frame-src - Valid sources for frames

    Reference: MDN Content-Security-Policy

    Permissions-Policy

    Controls which browser features and APIs can be used by your application and any embedded content.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "PermissionsPolicy": "geolocation=(), microphone=(), camera=()"
    +  }
    +}

    This example disables geolocation, microphone, and camera access entirely.

    To allow features only from the same origin:

    json
    json
    {
    +  "SecurityHeaders": {
    +    "PermissionsPolicy": "geolocation=(self), microphone=(self)"
    +  }
    +}

    Reference: MDN Permissions-Policy

    Cross-Origin Policies

    Cross-Origin-Opener-Policy

    Controls how your document is shared with cross-origin popups.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginOpenerPolicy": "same-origin"
    +  }
    +}
    ValueDescription
    unsafe-noneDefault browser behavior
    same-origin-allow-popupsIsolate from cross-origin, allow popups
    same-originFull isolation from cross-origin documents

    Cross-Origin-Embedder-Policy

    Prevents a document from loading cross-origin resources that don't explicitly grant permission.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginEmbedderPolicy": "require-corp"
    +  }
    +}
    ValueDescription
    unsafe-noneDefault browser behavior
    require-corpRequire CORP or CORS for cross-origin resources
    credentiallessLoad cross-origin resources without credentials

    TIP

    require-corp along with CrossOriginOpenerPolicy: same-origin enables access to SharedArrayBuffer and high-resolution timers.

    Cross-Origin-Resource-Policy

    Indicates how the resource should be shared cross-origin.

    json
    json
    {
    +  "SecurityHeaders": {
    +    "CrossOriginResourcePolicy": "same-origin"
    +  }
    +}
    ValueDescription
    same-siteOnly same-site requests allowed
    same-originOnly same-origin requests allowed
    cross-originAny origin can load the resource

    Example Configurations

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin"
    +  }
    +}

    API-Only Application

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "DENY",
    +    "ReferrerPolicy": "no-referrer",
    +    "ContentSecurityPolicy": "default-src 'none'; frame-ancestors 'none'"
    +  }
    +}

    Full Protection with CSP

    json
    json
    {
    +  "SecurityHeaders": {
    +    "Enabled": true,
    +    "XContentTypeOptions": "nosniff",
    +    "XFrameOptions": "SAMEORIGIN",
    +    "ReferrerPolicy": "strict-origin-when-cross-origin",
    +    "ContentSecurityPolicy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'",
    +    "PermissionsPolicy": "geolocation=(), microphone=(), camera=()",
    +    "CrossOriginOpenerPolicy": "same-origin",
    +    "CrossOriginEmbedderPolicy": "require-corp",
    +    "CrossOriginResourcePolicy": "same-origin"
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/server.html b/config/server.html new file mode 100644 index 000000000..1ccb51801 --- /dev/null +++ b/config/server.html @@ -0,0 +1,192 @@ + + + + + + Server & SSL Settings | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Server & SSL Settings

    This page covers the web server configuration including SSL/HTTPS settings and Kestrel server options.

    SSL Configuration

    The Ssl section enables HTTPS support and related security features.

    json
    json
    {
    +  "Ssl": {
    +    "Enabled": false,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable Kestrel HTTPS configuration. See UseKestrelHttpsConfiguration.
    UseHttpsRedirectionbooltrueRedirect HTTP requests to HTTPS. See UseUseHttpsRedirection.
    UseHstsbooltrueAdd the Strict-Transport-Security header (HSTS). See UseHsts.

    Enabling HTTPS

    To enable HTTPS, set Ssl.Enabled to true and configure your certificates in the Kestrel section:

    json
    json
    {
    +  "Ssl": {
    +    "Enabled": true,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  }
    +}

    HTTPS Redirection

    When UseHttpsRedirection is true, all HTTP requests are automatically redirected to HTTPS. This ensures users always use the secure connection.

    HTTP Strict Transport Security (HSTS)

    When UseHsts is true, the server sends the Strict-Transport-Security header, instructing browsers to only access the site over HTTPS for a specified period.

    WARNING

    HSTS should only be enabled in production environments. It can cause issues during development if you don't have valid certificates configured.

    Kestrel Configuration

    The Kestrel section configures the underlying web server, including endpoints, certificates, and connection limits.

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Http": {
    +        "Url": "http://localhost:5000"
    +      },
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    For complete Kestrel configuration options, see the Microsoft documentation.

    Certificate Configuration

    Kestrel supports multiple ways to configure SSL certificates:

    PFX File

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    PEM/CRT with Key File

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Path": "/path/to/certificate.pem",
    +          "KeyPath": "/path/to/private.key",
    +          "Password": "{KEY_PASSWORD}"
    +        }
    +      }
    +    }
    +  }
    +}

    Certificate Store (Windows)

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001",
    +        "Certificate": {
    +          "Subject": "localhost",
    +          "Store": "My",
    +          "Location": "CurrentUser",
    +          "AllowInvalid": false
    +        }
    +      }
    +    }
    +  }
    +}

    Default Certificate

    You can define a default certificate used by all HTTPS endpoints:

    json
    json
    {
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Https": {
    +        "Url": "https://localhost:5001"
    +      }
    +    },
    +    "Certificates": {
    +      "Default": {
    +        "Path": "/path/to/certificate.pfx",
    +        "Password": "{CERT_PASSWORD}"
    +      }
    +    }
    +  }
    +}

    Connection Limits

    Configure connection and request limits to protect your server:

    json
    json
    {
    +  "Kestrel": {
    +    "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"
    +    }
    +  }
    +}

    Limits Reference

    SettingDefaultDescription
    MaxConcurrentConnectionsnull (unlimited)Maximum number of open connections.
    MaxConcurrentUpgradedConnectionsnull (unlimited)Maximum number of upgraded connections (e.g., WebSockets).
    MaxRequestBodySize30,000,000 (~28.6 MB)Maximum request body size in bytes.
    MaxRequestBufferSize1,048,576 (1 MB)Maximum size of the request buffer.
    MaxRequestHeaderCount100Maximum number of request headers.
    MaxRequestHeadersTotalSize32,768 (32 KB)Maximum total size of request headers.
    MaxRequestLineSize8,192 (8 KB)Maximum size of the request line.
    MaxResponseBufferSize65,536 (64 KB)Maximum size of the response buffer.
    KeepAliveTimeout2 minutesTimeout for keep-alive connections.
    RequestHeadersTimeout30 secondsTimeout for receiving request headers.

    HTTP/2 Settings

    Configure HTTP/2 specific options:

    json
    json
    {
    +  "Kestrel": {
    +    "Limits": {
    +      "Http2": {
    +        "MaxStreamsPerConnection": 100,
    +        "HeaderTableSize": 4096,
    +        "MaxFrameSize": 16384,
    +        "MaxRequestHeaderFieldSize": 8192,
    +        "InitialConnectionWindowSize": 65535,
    +        "InitialStreamWindowSize": 65535,
    +        "KeepAlivePingDelay": "00:00:30",
    +        "KeepAlivePingTimeout": "00:01:00",
    +        "KeepAlivePingPolicy": "WithActiveRequests"
    +      }
    +    }
    +  }
    +}

    HTTP/3 Settings

    Configure HTTP/3 (QUIC) specific options:

    json
    json
    {
    +  "Kestrel": {
    +    "Limits": {
    +      "Http3": {
    +        "MaxRequestHeaderFieldSize": 8192
    +      }
    +    }
    +  }
    +}

    Additional Kestrel Options

    json
    json
    {
    +  "Kestrel": {
    +    "DisableStringReuse": false,
    +    "AllowAlternateSchemes": false,
    +    "AllowSynchronousIO": false,
    +    "AllowResponseHeaderCompression": true,
    +    "AddServerHeader": true,
    +    "AllowHostHeaderOverride": false
    +  }
    +}
    SettingDefaultDescription
    DisableStringReusefalseDisable string reuse optimization for debugging.
    AllowAlternateSchemesfalseAllow alternate URI schemes in requests.
    AllowSynchronousIOfalseAllow synchronous I/O operations (not recommended).
    AllowResponseHeaderCompressiontrueEnable response header compression for HTTP/2.
    AddServerHeadertrueAdd the Server header to responses.
    AllowHostHeaderOverridefalseAllow the Host header to be overridden.

    Complete Example

    Here's a production-ready configuration with HTTPS enabled:

    json
    json
    {
    +  "Urls": "http://localhost:5000;https://localhost:5001",
    +  "Ssl": {
    +    "Enabled": true,
    +    "UseHttpsRedirection": true,
    +    "UseHsts": true
    +  },
    +  "Kestrel": {
    +    "Endpoints": {
    +      "Http": {
    +        "Url": "http://0.0.0.0:5000"
    +      },
    +      "Https": {
    +        "Url": "https://0.0.0.0:5001",
    +        "Certificate": {
    +          "Path": "/etc/ssl/certs/myapp.pfx",
    +          "Password": "{CERT_PASSWORD}"
    +        }
    +      }
    +    },
    +    "Limits": {
    +      "MaxConcurrentConnections": 1000,
    +      "MaxRequestBodySize": 52428800,
    +      "KeepAliveTimeout": "00:02:00",
    +      "RequestHeadersTimeout": "00:00:30"
    +    }
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/sql-file-source.html b/config/sql-file-source.html new file mode 100644 index 000000000..808e7d604 --- /dev/null +++ b/config/sql-file-source.html @@ -0,0 +1,105 @@ + + + + + + SQL File Source Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    SQL File Source

    Configuration for generating REST API endpoints from .sql files.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": false,
    +      "FilePattern": "",
    +      "CommentsMode": "OnlyWithHttpTag",
    +      "CommentScope": "All",
    +      "ErrorMode": "Exit",
    +      "ResultPrefix": "result",
    +      "UnnamedSingleColumnSet": true,
    +      "NestedJsonForCompositeTypes": false
    +    }
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable or disable SQL file source endpoints.
    FilePatternstring""Glob pattern for SQL files. Empty string disables the feature.
    CommentsModestring"OnlyWithHttpTag"How comment annotations affect endpoint creation.
    CommentScopestring"All"Which comments in the SQL file to parse as annotations.
    ErrorModestring"Exit"Behavior when a SQL file fails to parse or describe.
    ResultPrefixstring"result"Prefix for result keys in multi-command JSON responses.
    UnnamedSingleColumnSetbooltrueSingle-column queries return flat arrays instead of object arrays.
    NestedJsonForCompositeTypesboolfalseWhen true, composite type columns are serialized as nested JSON objects. When false (default), composite fields are flattened inline. Can also be enabled per-endpoint with the @nested annotation.

    Enabled

    Enable or disable SQL file source endpoints. Default is false — you must explicitly enable this feature.

    json
    json
    "SqlFileSource": {
    +  "Enabled": true
    +}

    FilePattern

    Glob pattern for locating SQL files. Supports the following wildcards:

    PatternDescription
    *Matches any characters within a single directory level
    **Matches any characters including / (crosses directory boundaries)
    ?Matches a single character

    When ** is present in the pattern, * stops matching / (standard glob semantics). When no ** is present, * matches / for backward compatibility.

    Examples

    json
    json
    // All .sql files in the sql/ directory (non-recursive)
    +"FilePattern": "sql/*.sql"
    +
    +// All .sql files in sql/ and all subdirectories (recursive)
    +"FilePattern": "sql/**/*.sql"
    +
    +// Any .sql file at any depth
    +"FilePattern": "**/*.sql"

    An empty string disables the feature (even if Enabled is true).

    CommentsMode

    Controls how comment annotations affect SQL file endpoint creation.

    ModeDescription
    ParseAllEvery SQL file becomes an endpoint. Comments are parsed as annotations to modify endpoint behavior.
    OnlyWithHttpTagOnly files containing an HTTP annotation become endpoints. (default)
    IgnoreEvery SQL file becomes an endpoint. All comments are ignored.

    TIP

    The default OnlyWithHttpTag means only SQL files with an explicit HTTP annotation (e.g., -- HTTP GET) become endpoints. Use ParseAll if you want every SQL file in the matched pattern to become an endpoint automatically.

    CommentScope

    Controls which comments in the SQL file are parsed as annotations.

    ScopeDescription
    AllParse every comment in the file, regardless of position. (default)
    HeaderOnly parse comments that appear before the first SQL statement.

    Example

    With CommentScope: "Header", only comments before the first statement are parsed:

    sql
    sql
    -- This IS parsed as an annotation
    +-- HTTP GET
    +-- @authorize admin
    +
    +select * from users;
    +
    +-- This is NOT parsed (after first statement)
    +-- @cached

    With CommentScope: "All" (default), all comments are parsed regardless of position.

    ErrorMode

    Controls behavior when a SQL file fails to parse or when PostgreSQL reports an error during the describe phase.

    ModeDescription
    ExitLog the error and exit the process. Fail-fast — catches SQL errors at startup. (default)
    SkipLog the error, skip the file, and continue startup. Tolerates partial failures.

    All SQL file errors are logged at Error level. In Exit mode, a Critical log explains the exit and how to switch to Skip mode.

    A warning is logged when the configured file pattern matches no files.

    Errors caught at startup include:

    • Parse errors (malformed SQL, unclosed strings/quotes)
    • Describe errors (PostgreSQL syntax errors, invalid table/column references)
    • Parameter type conflicts in multi-command files

    TIP

    Use Exit (default) during development to catch SQL errors early. Use Skip in production to tolerate partial failures.

    ResultPrefix

    Prefix for result keys in multi-command JSON responses. Default keys are result1, result2, result3, etc.

    json
    json
    // Default: result1, result2, ...
    +"ResultPrefix": "result"
    +
    +// Custom: data1, data2, ...
    +"ResultPrefix": "data"
    +
    +// Custom: query1, query2, ...
    +"ResultPrefix": "query"

    Individual result keys can be overridden per-file using the @result annotation.

    UnnamedSingleColumnSet

    When true (default), single-column queries return flat arrays instead of arrays of objects. This matches the behavior of PostgreSQL functions returning setof single values.

    sql
    sql
    -- sql/get_names.sql
    +select name from users;

    With UnnamedSingleColumnSet: true (default):

    json
    json
    ["Alice", "Bob", "Charlie"]

    With UnnamedSingleColumnSet: false:

    json
    json
    [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]

    This applies to both single-command endpoints and per-result in multi-command files.

    NestedJsonForCompositeTypes

    Controls how composite type columns are serialized in SQL file endpoint responses.

    Default (flat): Composite fields are spliced inline into the JSON row:

    sql
    sql
    -- sql/get_user_with_address.sql
    +-- HTTP GET
    +-- @param $1 user_id
    +select id, address from users where id = $1;
    +-- where address is: create type address_type as (street text, city text, zip text)
    json
    json
    {"id": 1, "street": "123 Main St", "city": "New York", "zip": "10001"}

    With NestedJsonForCompositeTypes: true or @nested annotation: Composite wrapped under column name:

    json
    json
    {"id": 1, "address": {"street": "123 Main St", "city": "New York", "zip": "10001"}}

    Enable globally for all SQL file endpoints:

    json
    json
    "SqlFileSource": {
    +  "Enabled": true,
    +  "FilePattern": "sql/**/*.sql",
    +  "NestedJsonForCompositeTypes": true
    +}

    Or per-endpoint with the @nested annotation:

    sql
    sql
    -- sql/get_user_with_address.sql
    +-- HTTP GET
    +-- @nested
    +-- @param $1 user_id
    +select id, address from users where id = $1;

    NULL composites are serialized as null in nested mode, or as individual null fields in flat mode.

    TIP

    This setting is also available in Routine Options for function/procedure endpoints. Each endpoint source has its own independent setting.

    LogCommandText

    Controls whether multi-command SQL file endpoints include the full SQL text in debug command logs.

    Default (false): Only the file path and statement count are logged:

    code
    [DBG] -- POST http://127.0.0.1:8080/api/send-message
    +-- $1 text = 'hello'
    +SQL file: sql/send-message.sql (5 statements)

    With LogCommandText: true: The full SQL body of all statements is logged.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "LogCommandText": true
    +    }
    +  }
    +}

    Single-command SQL file endpoints always log the SQL text regardless of this setting. This only applies when LogCommands is true.

    Quick Start Example

    1. Enable the SQL file source in appsettings.json:
    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}
    1. Create a SQL file:
    sql
    sql
    -- sql/get_users.sql
    +-- HTTP GET
    +-- @authorize
    +-- @param $1 active
    +select id, name, email from users where active = $1;
    1. The endpoint is available at GET /api/get-users?active=true

    Comments

    + + + + \ No newline at end of file diff --git a/config/static-files.html b/config/static-files.html new file mode 100644 index 000000000..947c57310 --- /dev/null +++ b/config/static-files.html @@ -0,0 +1,131 @@ + + + + + + Static Files Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Static Files

    Static file serving configuration with authorization and content parsing support.

    Overview

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": false,
    +    "RootPath": "wwwroot",
    +    "AuthorizePaths": [],
    +    "UnauthorizedRedirectPath": "/",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      "Enabled": false,
    +      "AvailableClaims": [],
    +      "AvailableEnvVars": [],
    +      "CacheParsedFile": true,
    +      "Headers": [
    +        "Cache-Control: no-store, no-cache, must-revalidate",
    +        "Pragma: no-cache",
    +        "Expires: 0"
    +      ],
    +      "FilePaths": ["*.html"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable static file serving.
    RootPathstring"wwwroot"Root directory for static files.
    AuthorizePathsarray[]File patterns requiring authorization.
    UnauthorizedRedirectPathstring"/"Redirect path for unauthorized requests.
    UnauthorizedReturnToQueryParameterstring"return_to"Query parameter name for return URL after authentication.
    ParseContentOptionsobject(see below)Content parsing configuration.

    Authorization

    Protect specific static files by requiring authentication:

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": true,
    +    "AuthorizePaths": [
    +      "/admin/*",
    +      "/dashboard/*.html",
    +      "/reports/*"
    +    ],
    +    "UnauthorizedRedirectPath": "/login",
    +    "UnauthorizedReturnToQueryParameter": "return_to"
    +  }
    +}

    Path Patterns

    File paths are relative to RootPath and pattern matching is case-insensitive:

    PatternDescription
    *.htmlAll HTML files in any directory
    /admin/*All files in the admin directory
    /user/profile.htmlSpecific file
    *.jsAll JavaScript files

    Content Parsing

    Parse static files and replace tags with claim values from authenticated users.

    json
    json
    {
    +  "StaticFiles": {
    +    "ParseContentOptions": {
    +      "Enabled": false,
    +      "AvailableClaims": [],
    +      "AvailableEnvVars": [],
    +      "CacheParsedFile": true,
    +      "Headers": [
    +        "Cache-Control: no-store, no-cache, must-revalidate",
    +        "Pragma: no-cache",
    +        "Expires: 0"
    +      ],
    +      "FilePaths": ["*.html"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Parse Content Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable content parsing for static files.
    AvailableClaimsarray | object[]Claim types to parse. Array form (["name"]) replaces missing claims with NULL; object form ({"name":"guest"}) uses the given default when the claim is absent.
    AvailableEnvVarsarray | object[]Environment variable names templated into static content (same {NAME} tags as claims). Array form (["BUILD_LABEL"]) yields the empty string when unset; object form ({"DEMO_FLAG":"false"}) uses the given default. Resolved once at startup. Public — never list a secret.
    CacheParsedFilebooltrueCache parsed file templates in memory. Caching applies to templates before parsing, not final content.
    Headersarray(see below)Response headers for parsed static files. Set to null or empty array to ignore.
    FilePathsarray["*.html"]File patterns to parse.
    AntiforgeryFieldNamestring"antiForgeryFieldName"Variable name for the antiforgery form field name in templates.
    AntiforgeryTokenstring"antiForgeryToken"Variable name for the antiforgery token value in templates.

    Tag Replacement

    When Enabled is true, tags in the format {claimType} are replaced with values from the user's claims:

    html
    html
    <p>Welcome, {name}!</p>
    +<p>Your email: {email}</p>
    +<input type="hidden" name="{antiForgeryFieldName}" value="{antiForgeryToken}" />

    For unauthenticated users or missing claims, values are replaced with NULL.

    You can also give a claim an explicit default with the object form:

    json
    json
    "AvailableClaims": { "name": "guest", "email": "" }

    Environment Variable Injection

    AvailableEnvVars templates app-wide, request-independent environment variable values into static content using the same {NAME} tag syntax. This is useful for Single-Page Apps deployed to Kubernetes: build the bundle once, and inject per-environment values (build label, feature flags, analytics IDs) from pod env vars at boot — no per-environment rebuild.

    json
    json
    {
    +  "StaticFiles": {
    +    "ParseContentOptions": {
    +      "Enabled": true,
    +      "FilePaths": ["/index.html"],
    +      "AvailableClaims": ["user_id", "user_name"],
    +      "AvailableEnvVars": {
    +        "BUILD_LABEL": "local",
    +        "DEMO_FLAG": "false",
    +        "TRACKING_ID": ""
    +      }
    +    }
    +  }
    +}

    Values are substituted as complete, JSON-escaped literals, so the template uses the bare {NAME} token with no surrounding quotes:

    html
    html
    <script>
    +  window.__appConfig = {
    +    userId: {user_id},          // claim → 123 or null
    +    userName: {user_name},      // claim → "alice" or null
    +    buildLabel: {BUILD_LABEL},  // env   → "demo" (or "local" default)
    +    demoMode: {DEMO_FLAG} === "true",
    +    trackingId: {TRACKING_ID}
    +  };
    +</script>

    Guarantees:

    • Two forms. An array of names (["BUILD_LABEL"]) — a missing variable becomes the empty string "". Or an object of name→default pairs ({"DEMO_FLAG":"false"}) — the default is used when the variable is absent.
    • Resolved once at startup. A value change requires a restart (a Kubernetes pod restart re-reads the values).
    • JSON-escaped. An accidental quote or backslash in a value cannot break the JS string. (The relaxed encoder does not escape </>, the same as the claim path — env values are operator-controlled, not untrusted input.)
    • Claims win on collision. If a name is both a user claim and an env var, the per-request claim value takes precedence.

    Public allowlist

    Anything you list in AvailableEnvVars is templated into static content served to any client. Templating a secret (database password, API key, signing token) into index.html leaks it to every user via browser DevTools. Treat the list as a public allowlist, never as a "make this reachable to the app" shortcut. Secrets stay in server-side code paths that read env directly. This is distinct from the server-side Config:ParseEnvironmentVariables mechanism, which substitutes {ENV} tokens into appsettings.json values that never leave the server.

    Default Headers

    The default headers disable caching for parsed content:

    code
    Cache-Control: no-store, no-cache, must-revalidate
    +Pragma: no-cache
    +Expires: 0

    Example Configuration

    Serve static files with protected admin area and content parsing:

    json
    json
    {
    +  "StaticFiles": {
    +    "Enabled": true,
    +    "RootPath": "wwwroot",
    +    "AuthorizePaths": [
    +      "/admin/*",
    +      "/dashboard/*"
    +    ],
    +    "UnauthorizedRedirectPath": "/login.html",
    +    "UnauthorizedReturnToQueryParameter": "return_to",
    +    "ParseContentOptions": {
    +      "Enabled": true,
    +      "AvailableClaims": ["name", "email", "role"],
    +      "CacheParsedFile": true,
    +      "FilePaths": ["*.html", "*.htm"],
    +      "AntiforgeryFieldName": "antiForgeryFieldName",
    +      "AntiforgeryToken": "antiForgeryToken"
    +    }
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/stats.html b/config/stats.html new file mode 100644 index 000000000..bec3893ea --- /dev/null +++ b/config/stats.html @@ -0,0 +1,145 @@ + + + + + + PostgreSQL Stats | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    PostgreSQL Stats

    New in 3.6.0

    PostgreSQL Stats endpoints were added in version 3.6.0.

    Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging. Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.

    Overview

    json
    json
    {
    +  "Stats": {
    +    "Enabled": false,
    +    "CacheDuration": "5 seconds",
    +    "RateLimiterPolicy": null,
    +    "ConnectionName": null,
    +    "RequireAuthorization": false,
    +    "AuthorizedRoles": [],
    +    "OutputFormat": "html",
    +    "SchemaSimilarTo": null,
    +    "RoutinesStatsPath": "/stats/routines",
    +    "TablesStatsPath": "/stats/tables",
    +    "IndexesStatsPath": "/stats/indexes",
    +    "ActivityPath": "/stats/activity"
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledboolfalseEnable PostgreSQL statistics endpoints.
    CacheDurationstring"5 seconds"Cache stats responses for the specified duration. PostgreSQL interval format. Set to null to disable caching.
    RateLimiterPolicystringnullApply a rate limiter policy to stats endpoints. Specify a policy name from RateLimiterOptions.Policies.
    ConnectionNamestringnullUse a specific named connection for stats queries. When null, uses the default connection.
    RequireAuthorizationboolfalseRequire authentication for stats endpoints.
    AuthorizedRolesarray[]Restrict access to specific roles. Empty array allows any authenticated user (if RequireAuthorization is true).
    OutputFormatstring"html"Output format: "json" or "html". HTML format is Excel-compatible for easy copy-paste. Can be overridden per-request with the ?format= query string parameter.
    SchemaSimilarTostringnullFilter schemas using PostgreSQL SIMILAR TO pattern.
    RoutinesStatsPathstring"/stats/routines"Path for routine (function/procedure) statistics.
    TablesStatsPathstring"/stats/tables"Path for table statistics.
    IndexesStatsPathstring"/stats/indexes"Path for index statistics.
    ActivityPathstring"/stats/activity"Path for current database activity.

    Available Endpoints

    Routines Stats (/stats/routines)

    Returns data from pg_stat_user_functions including:

    • Call counts
    • Total execution time
    • Self execution time

    PostgreSQL Configuration Required

    Routine statistics require track_functions to be enabled in PostgreSQL:

    sql
    sql
    ALTER SYSTEM SET track_functions = 'all';
    +SELECT pg_reload_conf();

    Or set track_functions = 'all' in postgresql.conf and restart/reload.

    Tables Stats (/stats/tables)

    Returns data from pg_stat_user_tables including:

    • Tuple counts (live, dead, inserted, updated, deleted)
    • Table sizes
    • Sequential and index scan counts
    • Last vacuum and analyze timestamps

    Indexes Stats (/stats/indexes)

    Returns data from pg_stat_user_indexes including:

    • Index scan counts
    • Tuples read and fetched
    • Index definitions
    • Index sizes

    Activity (/stats/activity)

    Returns data from pg_stat_activity showing:

    • Active sessions
    • Currently running queries
    • Wait events
    • Session state and duration

    Security Warning

    The activity endpoint shows currently running queries which may contain sensitive data (passwords in plaintext queries, personal information, etc.). Always enable RequireAuthorization in production.

    Output Formats

    HTML Format (Default)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html"
    +  }
    +}

    Returns an HTML table that is Excel-compatible for direct browser copy-paste. Ideal for quick debugging and analysis.

    JSON Format

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "json"
    +  }
    +}

    Returns a JSON array suitable for programmatic access and integration with monitoring tools.

    Per-Request Format Override

    New in 3.8.0

    The format query string override was added in version 3.8.0.

    The configured OutputFormat can be overridden per-request using the format query string parameter. Valid values are html and json:

    code
    GET /stats/routines?format=json
    +GET /stats/tables?format=html

    This allows a single stats deployment to serve both human-readable HTML and machine-readable JSON without changing the server configuration.

    Security

    Require Authentication

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true
    +  }
    +}

    Any authenticated user can access stats endpoints.

    Role-Based Access

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["admin", "dba"]
    +  }
    +}

    Only users with admin or dba roles can access stats endpoints.

    TIP

    Stats endpoints can reveal sensitive information about your database including table sizes, query patterns, and active sessions. Always enable RequireAuthorization in production environments.

    Caching

    Cache responses to reduce database load:

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "CacheDuration": "10 seconds"
    +  }
    +}

    The value uses PostgreSQL interval format:

    • "5 seconds" or "5s"
    • "1 minute" or "1min"
    • "30s"

    Set to null to disable caching (queries the database on every request).

    Query strings are ignored to prevent cache-busting.

    Rate Limiting

    Apply a rate limiter policy to prevent abuse:

    json
    json
    {
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "Policies": {
    +      "stats-limit": {
    +        "PermitLimit": 10,
    +        "Window": "1 minute"
    +      }
    +    }
    +  },
    +  "Stats": {
    +    "Enabled": true,
    +    "RateLimiterPolicy": "stats-limit"
    +  }
    +}

    Schema Filtering

    Filter statistics by schema using PostgreSQL SIMILAR TO pattern:

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "SchemaSimilarTo": "public|myapp%"
    +  }
    +}

    This example includes:

    • The public schema
    • Schemas starting with myapp (e.g., myapp, myapp_v1, myapp_archive)

    When null, all schemas are included.

    Using a Different Connection

    Query stats from a specific database or with different credentials:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=primary;Database=myapp;Username=app;...",
    +    "Stats": "Host=replica;Database=myapp;Username=readonly;..."
    +  },
    +  "Stats": {
    +    "Enabled": true,
    +    "ConnectionName": "Stats"
    +  }
    +}

    Useful for:

    • Using read-only credentials
    • Querying a read replica
    • Separating stats queries from application traffic

    Custom Paths

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RoutinesStatsPath": "/api/stats/functions",
    +    "TablesStatsPath": "/api/stats/tables",
    +    "IndexesStatsPath": "/api/stats/indexes",
    +    "ActivityPath": "/api/stats/sessions"
    +  }
    +}

    Example Configurations

    Development (Open Access)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "OutputFormat": "html"
    +  }
    +}

    Production (Secured)

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["admin"],
    +    "CacheDuration": "30 seconds",
    +    "OutputFormat": "json"
    +  }
    +}

    Monitoring Integration

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "AuthorizedRoles": ["monitoring"],
    +    "OutputFormat": "json",
    +    "CacheDuration": "10 seconds",
    +    "RateLimiterPolicy": "monitoring"
    +  }
    +}

    Limited Schema Access

    json
    json
    {
    +  "Stats": {
    +    "Enabled": true,
    +    "RequireAuthorization": true,
    +    "SchemaSimilarTo": "public|api%",
    +    "OutputFormat": "html"
    +  }
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/table-format.html b/config/table-format.html new file mode 100644 index 000000000..458523bb3 --- /dev/null +++ b/config/table-format.html @@ -0,0 +1,129 @@ + + + + + + Table Format Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Table Format Options

    Pluggable table format rendering system that allows PostgreSQL function results to be rendered as HTML tables or Excel spreadsheet downloads instead of JSON, controlled by the @table_format annotation.

    Applies to Set-Returning Functions Only

    Table format rendering only applies to routines that return SETOF or TABLE results. Scalar-returning functions are not affected.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": false,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": null,
    +      "ExcelNumericFormat": null
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable table format handlers. When false, @table_format annotations are ignored.

    HTML Table Handler

    Renders results as a styled HTML table suitable for browser viewing and copy-paste into Excel. Activated by the @table_format = html annotation on PostgreSQL functions returning SETOF or TABLE.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    HtmlEnabledbooltrueEnable the HTML table handler.
    HtmlKeystring"html"The key name used to match @table_format = <key> annotation.
    HtmlHeaderstring(CSS style block)Content written before the HTML table. Typically a CSS style block. Set to null to omit.
    HtmlFooterstringnullContent written after the closing HTML table tag. Set to null to omit.

    Example

    sql
    sql
    create function get_report()
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report() is '
    +HTTP GET
    +@table_format = html
    +';

    Equivalent as a SQL file endpoint (sql/get-report.sql):

    sql
    sql
    /*
    +HTTP GET
    +@table_format = html
    +*/
    +select id, name, amount from reports;

    This renders the function result as an HTML table instead of JSON.

    Excel Table Handler

    Renders results as an .xlsx Excel spreadsheet download using the SpreadCheetah library (streaming, AOT-compatible). Activated by the @table_format = excel annotation on PostgreSQL functions returning SETOF or TABLE.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": null,
    +      "ExcelNumericFormat": null
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    ExcelEnabledbooltrueEnable the Excel handler.
    ExcelKeystring"excel"The key name used to match @table_format = <key> annotation.
    ExcelSheetNamestringnullWorksheet name. When null, uses the routine name.
    ExcelDateTimeFormatstringnullExcel Format Code for DateTime cells. When null, uses SpreadCheetah default (yyyy-MM-dd HH:mm:ss). Uses Excel Format Codes (not .NET format strings). Examples: yyyy-mm-dd, dd/mm/yyyy hh:mm, m/d/yy h:mm.
    ExcelNumericFormatstringnullExcel Format Code for numeric cells. When null, uses Excel default (General). Uses Excel Format Codes (not .NET format strings). Examples: #,##0.00, 0.00, #,##0.

    Example

    sql
    sql
    create function get_report()
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +';

    This returns an .xlsx file download instead of JSON.

    Per-Endpoint Overrides

    The download filename and worksheet name can be overridden per-endpoint via custom parameter annotations:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = monthly_report.xlsx
    +@excel_sheet = Report Data
    +';

    These also support dynamic placeholders resolved from function parameters:

    sql
    sql
    create function get_report(_format text, _file_name text, _sheet_name text)
    +returns table (id int, name text, amount numeric)
    +language sql
    +begin atomic;
    +  select * from reports;
    +end;
    +
    +comment on function get_report(text, text, text) is '
    +HTTP GET
    +@table_format = {_format}
    +@excel_file_name = {_file_name}
    +@excel_sheet = {_sheet_name}
    +';

    Complete Example

    Production configuration with both HTML and Excel handlers:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "TableFormatOptions": {
    +      "Enabled": true,
    +      "HtmlEnabled": true,
    +      "HtmlKey": "html",
    +      "HtmlHeader": "<style>table{font-family:Calibri,Arial,sans-serif;font-size:11pt;border-collapse:collapse}th,td{border:1px solid #d4d4d4;padding:4px 8px}th{background-color:#f5f5f5;font-weight:600}</style>",
    +      "HtmlFooter": null,
    +      "ExcelEnabled": true,
    +      "ExcelKey": "excel",
    +      "ExcelSheetName": null,
    +      "ExcelDateTimeFormat": "yyyy-mm-dd",
    +      "ExcelNumericFormat": "#,##0.00"
    +    }
    +  }
    +}

    Next Steps

    See Also

    Comments

    + + + + \ No newline at end of file diff --git a/config/test-runner.html b/config/test-runner.html new file mode 100644 index 000000000..6eb96b38d --- /dev/null +++ b/config/test-runner.html @@ -0,0 +1,118 @@ + + + + + + Test Runner Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Test Runner

    Configuration for the SQL test runner (npgsqlrest --test) — write tests for your endpoints as plain .sql files and run them against the real endpoint pipeline, in-process. For the full walkthrough (test file anatomy, HTTP blocks, assertions, isolation patterns, migrations, Docker, template databases), see the Testing Guide.

    The TestRunner section is a top-level configuration section (a sibling of NpgsqlRest, not nested inside it). It only has an effect when the client runs with the --test argument — it is completely inert during normal server operation.

    sh
    sh
    npgsqlrest ./config.json --test
    +npgsqlrest ./config.json ./test-config.json --test --watch
    +npgsqlrest ./config.json --test --testrunner:filter=login --testrunner:tag=smoke

    Overview

    json
    json
    {
    +  "TestRunner": {
    +    "FilePattern": "",
    +    "Filter": "",
    +    "Tag": "",
    +    "ExcludeTag": "",
    +    "ConnectionName": "",
    +    "MaxParallelism": 0,
    +    "FailFast": false,
    +    "PerTestTimeout": "30s",
    +    "JUnitOutput": null,
    +    "Keep": false,
    +    "DetailedReport": false,
    +    "AllowEmpty": false,
    +    "Coverage": null,
    +    "CoverageThreshold": null,
    +    "LoggerName": "NpgsqlRestTest",
    +    "ResponseTempTable": {
    +      "Name": "_response",
    +      "MultiNamePattern": "_response_{n}",
    +      "Columns": {
    +        "Status": "status",
    +        "Body": "body",
    +        "ContentType": "content_type",
    +        "Headers": "headers",
    +        "IsSuccess": "is_success"
    +      }
    +    },
    +    "Steps": {},
    +    "Setup": [],
    +    "Teardown": []
    +  }
    +}

    Settings

    SettingTypeDefaultDescription
    FilePatternstring""Glob selecting test files. Empty disables discovery.
    Filterstring""Narrow the discovered set by path (substring or glob).
    Tagstring""Run only files carrying at least one of these tags.
    ExcludeTagstring""Skip files carrying any of these tags (exclude wins).
    ConnectionNamestring""ConnectionStrings entry to run tests against instead of the main connection.
    MaxParallelismint0Max test files running concurrently. 0 = processor count.
    FailFastboolfalseStop scheduling new tests after the first failure/error.
    PerTestTimeoutstring"30s"Per-test-file timeout ("30s", "5m", seconds, or hh:mm:ss). 0 disables.
    JUnitOutputstringnullOptional path for a JUnit XML report.
    KeepboolfalseSkip Teardown so a failed run's state can be inspected.
    DetailedReportboolfalseRicher console report (passed assertions, failing SQL, notices).
    AllowEmptyboolfalse"No tests discovered" exits 0 instead of 4.
    Coveragebool?nullEndpoint-coverage summary. null = on for full runs, quiet when narrowed; true/false = always/never.
    CoverageThresholdint?nullFail an otherwise-passing run (exit 2) when coverage is below this percentage.
    LoggerNamestring"NpgsqlRestTest"SourceContext name of the runner's log channel.
    ResponseTempTableobjectsee belowNaming and columns of the per-HTTP-block response temp table.
    Stepsobject{}Named, reusable steps for Setup/Teardown and per-file annotations.
    Setuparray[]Run-once setup steps, before endpoint discovery, in written order.
    Teardownarray[]Run-once teardown steps, always (best-effort), in written order.

    Every setting is also available as a command-line override with the standard configuration syntax: --testrunner:filter=login, --testrunner:coveragethreshold=100, and so on.

    FilePattern

    Glob (same engine as SqlFileSource.FilePattern) selecting the test files. Empty disables discovery — --test then exits with code 4 (no tests found).

    Two common layouts:

    json
    json
    // Co-located: app.sql (endpoint) next to app.test.sql (test), same tree
    +{ "TestRunner": { "FilePattern": "./sql/**/*.test.sql" } }
    json
    json
    // Separate tree: endpoints in ./sql, tests in ./tests
    +{ "TestRunner": { "FilePattern": "./tests/**/*.test.sql" } }

    The co-located layout works because SqlFileSource.SkipPattern defaults to "*.test.sql", so test files are never exposed as endpoints.

    Filter

    The fast path for iterating on one test:

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:filter=login

    Matched against each file's cwd-relative path: a value without wildcards is a substring match; with wildcards it uses the same glob engine as FilePattern. Empty runs everything discovered.

    Tag and ExcludeTag

    Tag filtering (comma- or whitespace-separated lists, case-insensitive). A test file declares its tags with a header annotation:

    sql
    sql
    -- @tag smoke, auth

    Tag runs only files carrying at least one of the listed tags; ExcludeTag skips files carrying any of them — exclude wins when both match. Composes with Filter (both must pass).

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Tags declared in an included annotation profile (via \i/\ir in the file header) count as if written in the file. See the TEST TAG annotation.

    ConnectionName

    A ConnectionStrings entry to run the tests against instead of the app's main connection. In test mode it becomes the connection used for endpoint type-checking (Describe) and execution, so it can point at a dedicated test database that a Setup step creates first — it does not need to exist at startup.

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "...Database=app_db...",
    +    "Admin": "...Database=postgres...",
    +    "Test": "...Database=app_test_{rnd5}..."
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test"
    +  }
    +}

    Random tokens

    {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once and stable for the whole run, usable in any connection string or Setup/Teardown SQL — so app_test_{rnd5} resolves to the same name in the connection string, the create database step, and the drop database step. Need several distinct tokens of the same length? Indexed instances {rnd5_1}{rnd5_9} are each independent.

    MaxParallelism

    Maximum number of test files running concurrently; 0 means processor count. Each test file runs on its own non-pooled physical connection (fresh session — no temp-table, GUC, or prepared-statement carryover), so parallel files cannot see each other's uncommitted state.

    FailFast

    Stop scheduling new tests after the first failure or error. In-flight tests still finish and are reported.

    PerTestTimeout

    Per-test-file timeout. Accepts "30s", "5m", "1h", a plain number of seconds, or "hh:mm:ss". 0 disables. A timed-out file is reported as an error (exit code 2).

    JUnitOutput

    Optional path to also write a JUnit XML report — the standard CI artifact (GitHub Actions, GitLab, Jenkins all consume it). Console output is always printed regardless. Assertion names (the second column of a boolean-SELECT assertion) become the JUnit test-case names.

    json
    json
    { "TestRunner": { "JUnitOutput": "./test-results.xml" } }

    Keep

    Skip Teardown so a failed run's state (the test database, fixture rows) can be inspected. Remember to clean up manually — with {rnd}-named databases each kept run leaves one behind.

    DetailedReport

    Richer console report: lists passed assertions (), prints the full failing SQL statement, and shows captured raise notice output for passing tests too (notices always show under failing tests).

    This shapes the report only — for diagnostic logging of every executed query and HTTP invocation, raise the runner's log channel instead:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    AllowEmpty

    Treat "no tests discovered" as success (exit 0) instead of exit 4. Useful for repos where a test tree may legitimately be empty.

    Watch mode

    Watch mode is enabled by the top-level Watch configuration section ("Watch": { "Enabled": true }) or its CLI shorthand --watch — it is not a TestRunner setting, because the same section drives both watch flavors (test watch with --test, server watch without). In test mode: run everything once, then re-run on changes until Ctrl+C.

    • A changed test file re-runs alone (Filter still applies).
    • A changed endpoint file (matching SqlFileSource.FilePattern) triggers an in-process endpoint rebuild — sources re-read, re-described, endpoint registry swapped atomically — followed by a full rerun, with the endpoint delta reported (+ POST /api/new, - GET /api/x (endpoint dropped — check its SQL file for errors)). To make this safe, watch mode forces SqlFileSource.ErrorMode from Exit to Skip; non-watch --test keeps Exit for CI.
    • A database routine change (create/replace/drop/comment on functions or procedures, detected by polling the routine discovery query — Watch:DatabasePollingInterval, default 2s) rebuilds endpoints and re-runs everything (— change detected (database) —).
    • Any other changed .sql under the test tree (an included fixture or profile, whose dependents are unknown) re-runs everything.

    Teardown runs once, on exit — synchronously inside the SIGINT/SIGTERM handler, so the test database is dropped even when the watch process is stopped through a wrapper like bun run or npm run; a second Ctrl+C force-quits. A graceful stop exits 0 regardless of test outcomes — watch is not for CI gating.

    Coverage and CoverageThreshold

    Endpoint-coverage summary after the run: exercised N of M testable endpoints, plus the exact list of untested ones:

    code
    19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 2/2 (100%)

    Coverage is tri-state:

    ValueBehavior
    null (default)Report after full runs; stay quiet when the run is narrowed by Filter/Tag (a deliberately partial run would just nag).
    trueAlways report, including narrowed runs.
    falseNever report.

    CoverageThreshold (0–100) turns it into a CI gate — it always reports, regardless of Coverage or narrowing: an otherwise-passing run below the threshold exits 2. Set it to 100 and forgetting to write a test for a new endpoint fails the build, naming the endpoint.

    "Covered" means invoked at least once by a test — execution, not assertion depth (the same semantics as code coverage). Endpoint kinds the runner rejects (SSE, upload, login/logout, outbound proxy) are excluded from the ratio and counted separately.

    LoggerName

    SourceContext name of the runner's own log channel (default "NpgsqlRestTest"); set its level independently under Log:MinimalLevels. Discovery and parsing log at Debug, each executed query and HTTP invocation at Verbose, raise notice output by its severity.

    ResponseTempTable

    Each HTTP block's response is captured into its own temp table on the test's connection, created fresh (no IF NOT EXISTS — a duplicate name fails the test loudly).

    SettingDefaultDescription
    Name"_response"Table name when the file has one HTTP block.
    MultiNamePattern"_response_{n}"Name pattern when the file has 2+ blocks; {n} is the 1-based block ordinal.
    DebugTablenullDebugging aid: also mirror every response into this permanent table (see below).
    Columns.Status"status"int — HTTP status code.
    Columns.Body"body"text — response body (cast to ::jsonb to assert on JSON).
    Columns.ContentType"content_type"text — response content type.
    Columns.Headers"headers"jsonb — response headers.
    Columns.IsSuccess"is_success"boolean — true for 2xx.

    A null or empty column name omits that column. A per-block override is available with the # @response <name> directive inside the HTTP block.

    DebugTable — inspect responses after the run

    Temp tables vanish with the test's rollback and connection, so they cannot be examined afterwards — and re-issuing the request from an .http file cannot reproduce a response that depended on the test's uncommitted fixtures. Set DebugTable (e.g. "_responses_debug") and every captured response is also mirrored into a permanent table, written on a separate autocommit connection — immune to rollbacks, recreated at the start of every run (it always holds the last run):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:responsetemptable:debugtable=_responses_debug

    One table covers everything — each HTTP block adds one row: captured_at, test_file, block (that block's response-table name: _response, a _response_{n} ordinal, or the # @response name), method, path, status, body, content_type, headers, is_success. After the run, open a query editor:

    sql
    sql
    select test_file, block, status, body::jsonb
    +from _responses_debug
    +where status >= 400;

    The temp-table semantics are unchanged; enabling it prints a loud warning — it is a debugging aid, do not enable in CI. In the fresh-test-database workflow combine it with Keep, or teardown drops the database (and the mirror with it).

    Steps

    Named, reusable steps (name → step object, same shape as Setup/Teardown entries). Reference them by name in Setup/Teardown, or from an individual test file's header annotations (-- @setup, -- @teardown):

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase": { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up", "WorkingDirectory": "./db" },
    +      "DropDatabase": { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    }
    +  }
    +}

    A step object is one of:

    ShapeRuns
    { "Sql": "..." }SQL text, statement by statement, on the test connection — or on any named ConnectionStrings entry via "ConnectionName".
    { "SqlFile": "..." }A SQL file, statement by statement, same connection rules.
    { "Command": "...", "WorkingDirectory": "..." }An OS shell command — migration runners, Docker, pg_dump, anything.

    Referencing an unknown step name is a configuration error (exit 3).

    Every step also has an Enabled flag (default true): a disabled step is simply ignored wherever it is referenced — skipped with a debug log line, never an error. The default configuration ships disabled example steps covering the typical scenarios (create/drop a {rnd}-named test database, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) — copy one into your config, adjust names and connections, and flip Enabled to true instead of typing it from scratch:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    }
    +  }
    +}

    Setup and Teardown

    Run-once lifecycle around the whole run. Setup runs before endpoint discovery — which is what makes the create-a-fresh-database workflow possible: by the time endpoints are described against ConnectionName, the database exists and is migrated. Steps run in the exact order written; each entry is a step name from Steps or an inline step object.

    json
    json
    {
    +  "TestRunner": {
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    Teardown runs always (best-effort), even when the run fails — and it is guaranteed beyond the happy path: from Setup onward the runner intercepts SIGINT (Ctrl+C) and SIGTERM and runs Teardown synchronously in the signal handler, and a process-exit hook covers hard exits (e.g. a broken endpoint SQL file under SqlFileSource.ErrorMode: Exit). Keep: true skips Teardown deliberately.

    Exit codes

    CodeMeaning
    0All tests passed (or a graceful watch-mode stop).
    1At least one assertion failed.
    2At least one error (SQL error, timeout, unsupported endpoint, an interrupted run, or a failed coverage gate).
    3Setup or configuration error.
    4No test files found (AllowEmpty: true turns this into 0).
    • Testing Guide — the full walkthrough with scenarios: transactions, fixtures, test databases, template clones, migrations, Docker, CI
    • Test file annotations@setup, @teardown, @connection, @tag
    • SQL File Source — endpoint files and SkipPattern
    • LoggingLog:MinimalLevels, including "Off" to mute a channel

    Comments

    + + + + \ No newline at end of file diff --git a/config/thread-pool.html b/config/thread-pool.html new file mode 100644 index 000000000..b29758cac --- /dev/null +++ b/config/thread-pool.html @@ -0,0 +1,51 @@ + + + + + + Thread Pool Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Thread Pool

    Thread pool configuration settings for optimizing application performance.

    Overview

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": null,
    +    "MinCompletionPortThreads": null,
    +    "MaxWorkerThreads": null,
    +    "MaxCompletionPortThreads": null
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    MinWorkerThreadsintnullMinimum number of worker threads in the thread pool. Uses system defaults if null.
    MinCompletionPortThreadsintnullMinimum number of completion port threads. Uses system defaults if null.
    MaxWorkerThreadsintnullMaximum number of worker threads in the thread pool. Uses system defaults if null.
    MaxCompletionPortThreadsintnullMaximum number of completion port threads. Uses system defaults if null.

    Worker Threads vs Completion Port Threads

    • Worker threads execute CPU-bound work and synchronous operations
    • Completion port threads handle asynchronous I/O operations (database queries, HTTP requests)

    When to Configure

    The default thread pool settings work well for most scenarios. Consider adjusting when:

    • High-concurrency workloads cause thread pool starvation
    • Application experiences delays during burst traffic
    • Profiling indicates thread pool bottlenecks

    Example Configuration

    High-concurrency configuration:

    json
    json
    {
    +  "ThreadPool": {
    +    "MinWorkerThreads": 100,
    +    "MinCompletionPortThreads": 100,
    +    "MaxWorkerThreads": 500,
    +    "MaxCompletionPortThreads": 500
    +  }
    +}

    WARNING

    Setting thread pool values too high can increase memory usage and context switching overhead. Test thoroughly before deploying to production.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/top-level.html b/config/top-level.html new file mode 100644 index 000000000..0f712b863 --- /dev/null +++ b/config/top-level.html @@ -0,0 +1,48 @@ + + + + + + Top-Level Settings | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Top-Level Settings

    These settings configure the application identity, server binding, and configuration behavior.

    Application Settings

    json
    json
    {
    +  "ApplicationName": null,
    +  "EnvironmentName": "Production",
    +  "Urls": "http://localhost:8080",
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}"
    +}

    Settings Reference

    SettingTypeDefaultDescription
    ApplicationNamestringnullApplication identifier. Defaults to the top-level directory name if not set.
    EnvironmentNamestring"Production"Environment designation (Development, Staging, Production).
    Urlsstring"http://localhost:8080"Server listening URLs. Separate multiple URLs with semicolons.
    StartupMessagestring(see below)Message displayed on startup. Supports placeholders.

    Default StartupMessage: "Started in {time}, listening on {urls}, version {version}"

    Urls Configuration

    The Urls setting accepts multiple URLs separated by semicolons:

    json
    json
    {
    +  "Urls": "http://localhost:8080;https://localhost:8443"
    +}

    To listen on all interfaces:

    json
    json
    {
    +  "Urls": "http://0.0.0.0:8080;https://0.0.0.0:8443"
    +}

    Startup Message Placeholders

    Customize the startup message with these placeholders:

    PlaceholderDescription
    {time}Startup time
    {urls}Listening URLs
    {version}Application version
    {environment}Environment name (from EnvironmentName)
    {application}Application name (from ApplicationName)

    Example:

    json
    json
    {
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}, env: {environment}"
    +}

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/config/uploads.html b/config/uploads.html new file mode 100644 index 000000000..60523f418 --- /dev/null +++ b/config/uploads.html @@ -0,0 +1,170 @@ + + + + + + Upload Options | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Upload Options

    File upload configuration for handling uploads via PostgreSQL Large Objects, file system, CSV, and Excel handlers.

    Overview

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": false,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      "DefaultUploadHandler": "large_object",
    +      "UseDefaultUploadMetadataParameter": false,
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      "UseDefaultUploadMetadataContextKey": false,
    +      "DefaultUploadMetadataContextKey": "request.upload_metadata",
    +      "UploadHandlers": {
    +        "StopAfterFirstSuccess": false,
    +        "IncludedMimeTypePatterns": null,
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 8192,
    +        "TextTestBufferSize": 4096,
    +        "TextNonPrintableThreshold": 5,
    +        "AllowedImageTypes": "jpeg, png, gif, bmp, tiff, webp",
    +        "RowCommandUserClaimsKey": "claims",
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false,
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false,
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)",
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null,
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}

    General Settings

    SettingTypeDefaultDescription
    EnabledboolfalseEnable file upload handling.
    LogUploadEventbooltrueLog upload events.
    LogUploadParametersboolfalseLog upload parameters (file names, sizes, etc.).
    DefaultUploadHandlerstring"large_object"Default handler when not specified.
    UseDefaultUploadMetadataParameterboolfalsePass upload metadata via parameter.
    DefaultUploadMetadataParameterNamestring"_upload_metadata"Parameter name for upload metadata JSON.
    UseDefaultUploadMetadataContextKeyboolfalsePass upload metadata via context key.
    DefaultUploadMetadataContextKeystring"request.upload_metadata"Context key for upload metadata JSON.

    Upload Handlers Common Settings

    Settings that apply to all upload handlers.

    SettingTypeDefaultDescription
    StopAfterFirstSuccessboolfalseStop processing after first successful handler.
    IncludedMimeTypePatternsstringnullCSV of MIME type patterns to include. null to allow all.
    ExcludedMimeTypePatternsstringnullCSV of MIME type patterns to exclude. null to exclude none.
    BufferSizeint8192Buffer size in bytes for file_system and large_object handlers (8 KB).
    TextTestBufferSizeint4096Buffer sample size for testing textual content (4 KB).
    TextNonPrintableThresholdint5Maximum non-printable characters allowed in text buffer.
    AllowedImageTypesstring"jpeg, png, gif, bmp, tiff, webp"Comma-separated list of allowed image types.
    RowCommandUserClaimsKeystring"claims"For row-processing handlers (CSV, Excel), includes the authenticated user's claims in the row metadata JSON ($4) under this key. Set to null or "" to disable. Example: with "claims", access in SQL via (_meta->'claims'->>'name_identifier').

    Large Object Handler

    Uploads files using PostgreSQL Large Objects API.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "LargeObjectEnabled": true,
    +        "LargeObjectKey": "large_object",
    +        "LargeObjectCheckText": false,
    +        "LargeObjectCheckImage": false
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    LargeObjectEnabledbooltrueEnable Large Object upload handler.
    LargeObjectKeystring"large_object"Handler key name.
    LargeObjectCheckTextboolfalseValidate uploaded content is text.
    LargeObjectCheckImageboolfalseValidate uploaded content is an allowed image type.

    File System Handler

    Uploads files to the server file system.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "FileSystemEnabled": true,
    +        "FileSystemKey": "file_system",
    +        "FileSystemPath": "/tmp/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckText": false,
    +        "FileSystemCheckImage": false
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    FileSystemEnabledbooltrueEnable file system upload handler.
    FileSystemKeystring"file_system"Handler key name.
    FileSystemPathstring"/tmp/uploads"Directory path for uploaded files.
    FileSystemUseUniqueFileNamebooltrueGenerate unique file names to prevent overwrites.
    FileSystemCreatePathIfNotExistsbooltrueCreate upload directory if it doesn't exist.
    FileSystemCheckTextboolfalseValidate uploaded content is text.
    FileSystemCheckImageboolfalseValidate uploaded content is an allowed image type.

    CSV Upload Handler

    Uploads CSV files and processes rows via a PostgreSQL command.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "CsvUploadEnabled": true,
    +        "CsvUploadKey": "csv",
    +        "CsvUploadCheckFileStatus": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadHasFieldsEnclosedInQuotes": true,
    +        "CsvUploadSetWhiteSpaceToNull": true,
    +        "CsvUploadRowCommand": "call process_csv_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    CsvUploadEnabledbooltrueEnable CSV upload handler.
    CsvUploadKeystring"csv"Handler key name.
    CsvUploadCheckFileStatusbooltrueCheck file status before processing.
    CsvUploadDelimiterCharsstring","CSV field delimiter character(s).
    CsvUploadHasFieldsEnclosedInQuotesbooltrueFields may be enclosed in quotes.
    CsvUploadSetWhiteSpaceToNullbooltrueConvert whitespace-only values to NULL.
    CsvUploadRowCommandstring"call process_csv_row($1,$2,$3,$4)"PostgreSQL command to process each row.

    CSV Row Command Parameters

    ParameterTypeDescription
    $1intRow index (1-based).
    $2text[]Parsed values as text array.
    $3textResult of previous row command.
    $4jsonUpload metadata JSON. Includes the user's claims under the RowCommandUserClaimsKey key (default "claims") when set.

    Excel Upload Handler

    Uploads Excel files and processes rows via a PostgreSQL command.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "UploadHandlers": {
    +        "ExcelUploadEnabled": true,
    +        "ExcelKey": "excel",
    +        "ExcelSheetName": null,
    +        "ExcelAllSheets": false,
    +        "ExcelTimeFormat": "HH:mm:ss",
    +        "ExcelDateFormat": "yyyy-MM-dd",
    +        "ExcelDateTimeFormat": "yyyy-MM-dd HH:mm:ss",
    +        "ExcelRowDataAsJson": false,
    +        "ExcelUploadRowCommand": "call process_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}
    SettingTypeDefaultDescription
    ExcelUploadEnabledbooltrueEnable Excel upload handler.
    ExcelKeystring"excel"Handler key name.
    ExcelSheetNamestringnullSheet name to process. null for first available sheet.
    ExcelAllSheetsboolfalseProcess all sheets in the workbook.
    ExcelTimeFormatstring"HH:mm:ss"Format for time values.
    ExcelDateFormatstring"yyyy-MM-dd"Format for date values.
    ExcelDateTimeFormatstring"yyyy-MM-dd HH:mm:ss"Format for datetime values.
    ExcelRowDataAsJsonboolfalsePass row data as JSON instead of text array.
    ExcelUploadRowCommandstring"call process_excel_row($1,$2,$3,$4)"PostgreSQL command to process each row.

    Excel Row Command Parameters

    ParameterTypeDescription
    $1intRow index (1-based).
    $2text[] or jsonParsed values as text array (or JSON if ExcelRowDataAsJson is true).
    $3textResult of previous row command.
    $4jsonUpload metadata JSON. Includes the user's claims under the RowCommandUserClaimsKey key (default "claims") when set.

    Complete Example

    Production configuration with file system and CSV uploads:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "UploadOptions": {
    +      "Enabled": true,
    +      "LogUploadEvent": true,
    +      "LogUploadParameters": false,
    +      "DefaultUploadHandler": "file_system",
    +      "UseDefaultUploadMetadataParameter": true,
    +      "DefaultUploadMetadataParameterName": "_upload_metadata",
    +      "UploadHandlers": {
    +        "StopAfterFirstSuccess": true,
    +        "IncludedMimeTypePatterns": "image/*,text/*,application/pdf",
    +        "ExcludedMimeTypePatterns": null,
    +        "BufferSize": 16384,
    +        "LargeObjectEnabled": false,
    +        "FileSystemEnabled": true,
    +        "FileSystemPath": "/var/uploads",
    +        "FileSystemUseUniqueFileName": true,
    +        "FileSystemCreatePathIfNotExists": true,
    +        "FileSystemCheckImage": true,
    +        "CsvUploadEnabled": true,
    +        "CsvUploadDelimiterChars": ",",
    +        "CsvUploadRowCommand": "call import_csv_row($1,$2,$3,$4)",
    +        "ExcelUploadEnabled": true,
    +        "ExcelUploadRowCommand": "call import_excel_row($1,$2,$3,$4)"
    +      }
    +    }
    +  }
    +}

    Blog Posts

    Next Steps

    See Also

    • UPLOAD - Enable file upload on endpoints

    Comments

    + + + + \ No newline at end of file diff --git a/config/validation.html b/config/validation.html new file mode 100644 index 000000000..fe93e7f17 --- /dev/null +++ b/config/validation.html @@ -0,0 +1,220 @@ + + + + + + Validation Options Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Validation Options

    Parameter validation configuration for validating endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

    Overview

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Settings Reference

    SettingTypeDefaultDescription
    EnabledbooltrueEnable parameter validation.
    RulesobjectSee Default RulesNamed validation rules that can be referenced in comment annotations.

    Validation Types

    Six validation types are available:

    TypeDescription
    NotNullParameter value cannot be null (DBNull.Value)
    NotEmptyParameter value cannot be an empty string (null values pass)
    RequiredCombines NotNull and NotEmpty - value cannot be null or empty
    RegexParameter value must match the specified regular expression pattern
    MinLengthParameter value must have at least N characters
    MaxLengthParameter value must have at most N characters

    Rule Properties

    Each rule can have the following properties:

    PropertyRequiredDescription
    TypeYesValidation type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    PatternFor RegexRegular expression pattern to match against
    MinLengthFor MinLengthMinimum number of characters required
    MaxLengthFor MaxLengthMaximum number of characters allowed
    MessageNoError message with placeholders: {0}=original parameter name, {1}=converted parameter name, {2}=rule name. Default: "Validation failed for parameter '{0}'"
    StatusCodeNoHTTP status code returned on validation failure. Default: 400

    Default Rules

    Four validation rules are available by default:

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Adding Custom Rules

    You can add custom validation rules to the Rules object. The key becomes the rule name used in the validate annotation.

    Regex Pattern Rule

    json
    json
    {
    +  "ValidationOptions": {
    +    "Rules": {
    +      "phone": {
    +        "Type": "Regex",
    +        "Pattern": "^\\+?[1-9]\\d{1,14}$",
    +        "Message": "Parameter '{0}' must be a valid phone number",
    +        "StatusCode": 400
    +      },
    +      "username": {
    +        "Type": "Regex",
    +        "Pattern": "^[a-zA-Z0-9_]{3,20}$",
    +        "Message": "Parameter '{0}' must be 3-20 alphanumeric characters or underscores",
    +        "StatusCode": 400
    +      },
    +      "uuid": {
    +        "Type": "Regex",
    +        "Pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
    +        "Message": "Parameter '{0}' must be a valid UUID",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Length Validation Rules

    json
    json
    {
    +  "ValidationOptions": {
    +    "Rules": {
    +      "password_length": {
    +        "Type": "MinLength",
    +        "MinLength": 8,
    +        "Message": "Parameter '{0}' must be at least 8 characters",
    +        "StatusCode": 400
    +      },
    +      "short_text": {
    +        "Type": "MaxLength",
    +        "MaxLength": 100,
    +        "Message": "Parameter '{0}' must not exceed 100 characters",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Complete Example

    Configuration with multiple custom validation rules:

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      },
    +      "phone": {
    +        "Type": "Regex",
    +        "Pattern": "^\\+?[1-9]\\d{1,14}$",
    +        "Message": "Parameter '{0}' must be a valid phone number (E.164 format)",
    +        "StatusCode": 400
    +      },
    +      "password_min": {
    +        "Type": "MinLength",
    +        "MinLength": 8,
    +        "Message": "Password must be at least 8 characters",
    +        "StatusCode": 400
    +      },
    +      "name_max": {
    +        "Type": "MaxLength",
    +        "MaxLength": 50,
    +        "Message": "Name must not exceed 50 characters",
    +        "StatusCode": 400
    +      },
    +      "slug": {
    +        "Type": "Regex",
    +        "Pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
    +        "Message": "Parameter '{0}' must be a valid URL slug",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Usage with Annotations

    Once validation rules are configured, use the validate annotation in PostgreSQL function comments to apply validation:

    sql
    sql
    create function register_user(_email text, _password text, _name text)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    -- validation already passed, safe to use parameters
    +    insert into users (email, password_hash, name)
    +    values (_email, crypt(_password, gen_salt('bf')), _name);
    +    return json_build_object('success', true);
    +end;
    +$$;
    +
    +comment on function register_user(text, text, text) is '
    +HTTP POST
    +@validate _email using required, email
    +@validate _password using required, password_min
    +@validate _name using not_empty, name_max
    +';

    Equivalent as a SQL file endpoint (sql/register-user.sql):

    sql
    sql
    /*
    +HTTP POST
    +@validate email using required, email
    +@validate password using required, password_min
    +@validate name using not_empty, name_max
    +@param $1 email
    +@param $2 password
    +@param $3 name
    +*/
    +insert into users (email, password_hash, name)
    +values ($1, crypt($2, gen_salt('bf')), $3)
    +returning json_build_object('success', true);

    Programmatic Configuration

    When using NpgsqlRest as a library, you can configure validation options programmatically:

    csharp
    csharp
    var options = new NpgsqlRestOptions
    +{
    +    ValidationOptions = new ValidationOptions
    +    {
    +        Rules = new Dictionary<string, ValidationRule>
    +        {
    +            ["required"] = new ValidationRule
    +            {
    +                Type = ValidationType.Required,
    +                Message = "Parameter '{0}' is required",
    +                StatusCode = 400
    +            },
    +            ["phone"] = new ValidationRule
    +            {
    +                Type = ValidationType.Regex,
    +                Pattern = @"^\+?[1-9]\d{1,14}$",
    +                Message = "Parameter '{0}' must be a valid phone number"
    +            },
    +            ["min_age"] = new ValidationRule
    +            {
    +                Type = ValidationType.MinLength,
    +                MinLength = 2,
    +                Message = "Parameter '{0}' must be at least 2 characters"
    +            }
    +        }
    +    }
    +};

    Behavior

    • Validation runs immediately after parameter parsing, before database connections are opened
    • Multiple rules can be applied to a single parameter
    • Rules are evaluated in order; validation stops on first failure
    • Failed validation returns the configured HTTP status code (default 400)
    • Null values pass NotEmpty validation (use Required to reject nulls and empty strings)

    Next Steps

    See Also

    • VALIDATE - Apply validation rules to parameters

    Comments

    + + + + \ No newline at end of file diff --git a/config/watch.html b/config/watch.html new file mode 100644 index 000000000..a8bf9f0d0 --- /dev/null +++ b/config/watch.html @@ -0,0 +1,44 @@ + + + + + + Watch Mode Configuration | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Watch Mode

    Configuration for watch mode — one feature, two flavors selected by the --test flag:

    CommandFlavorWatchesOn change
    npgsqlrest ... --test --watchTest watchtest files, included fixtures/profiles, endpoint SQL files, the databasechanged test re-runs alone; endpoint or database change rebuilds endpoints in-process and re-runs everything
    npgsqlrest ... --watchServer watchthe SQL file source tree, the configuration files, the databasethe server restarts (~1s)

    Watch is interactive/dev-only. In both flavors a broken SQL file cannot kill the session — SqlFileSource.ErrorMode is forced from Exit to Skip while watching.

    Overview

    The Watch section is top-level (a sibling of NpgsqlRest, not nested inside it), because it drives both flavors:

    json
    json
    {
    +  "Watch": {
    +    "Enabled": false,
    +    "DatabasePollingInterval": "2s"
    +  }
    +}
    SettingTypeDefaultDescription
    EnabledboolfalseTurn watch mode on. The --watch CLI flag is the shorthand for this setting.
    DatabasePollingIntervalstring"2s"Poll the database for routine changes; 0 disables polling.

    Enabled

    Turns watch mode on; --watch on the command line is equivalent. The flavor is chosen by --test:

    sh
    sh
    npgsqlrest ./config.json --watch               # server watch
    +npgsqlrest ./config.json --test --watch        # test watch
    +npgsqlrest ./config.json --watch:enabled=true  # same as --watch (standard config override syntax)

    Server watch needs something to watch: an enabled SQL file source, database polling (on by default), or both — with neither, --watch exits with an error. Test watch always has its test files to watch.

    DatabasePollingInterval

    Routine-source endpoints (functions and procedures) have no files to watch — so watch mode polls the database instead, with perfect fidelity: the poll runs the same routine discovery query the endpoint source uses, with the same configured filters (schema/name/language includes and excludes), hashed server-side into a single value on a dedicated non-pooled connection. If the hash changes, the discovered endpoints changed — by definition.

    Detected (because they change the discovery result):

    • create / create or replace / drop / alter of functions and procedures, including GRANT/REVOKE
    • COMMENT ON — i.e. annotation changes
    • changes to the composite types and tables used as parameter or return types (alter table users add column reshapes a returns setof users endpoint even though no function changed)

    Never triggers (because the discovery query doesn't read them): unrelated tables, temp objects, data changes.

    Accepts "2s", "500ms", "1m", a plain number of seconds, or "hh:mm:ss"; 0 disables polling. Polling is automatically inactive when the routine source is disabled (NpgsqlRest.RoutineOptions.Enabled: false).

    This makes a routines-only project fully watchable: run npgsqlrest ./config.json --watch, then create or replace a function in psql — the endpoint is live about two seconds later, annotations included. In test watch, a database change shows as — change detected (database) — followed by an endpoint rebuild and a full rerun; the runner re-baselines after every rerun so self-inflicted changes never re-trigger.

    Server watch behavior

    The process becomes a small supervisor that spawns itself as a child server and watches for changes; the child runs the completely normal server pipeline — including code generation (TypeScript client, HTTP files, OpenAPI regenerate on every restart), so dev is production behavior (the same model as dotnet watch).

    EventResult
    .sql change under the source treerestart (files matching SkipPattern — test files — are ignored)
    configuration file changerestart with the new configuration
    database routine change (polling)restart
    broken SQL filerestart; the error is logged, that endpoint drops, everything else keeps serving
    child crashes/exits on its ownsupervisor waits for the next change (no crash-looping)
    Ctrl+C / SIGTERM (docker stop)child stopped gracefully, both processes exit, port freed
    supervisor killed hard (SIGKILL)the child detects the vanished parent and exits by itself — no orphan holding the port

    Graceful child stop uses SIGTERM on Linux/macOS; on Windows the child is hard-killed (nothing needs teardown in a dev server). Works in every distribution: AOT executables, framework-dependent dotnet NpgsqlRestClient.dll, and both Docker image flavors.

    Docker Desktop bind mounts

    Where file events don't cross the filesystem boundary (Docker Desktop volume mounts, network shares), set the ecosystem-standard DOTNET_USE_POLLING_FILE_WATCHER=1 to switch the file watcher to a 1-second polling scan. This affects file watching only — database polling is unaffected.

    Test watch behavior

    Described in detail in the Testing Guide and the Test Runner configuration: a changed test file re-runs alone; endpoint file and database changes rebuild endpoints in-process (with a +/- endpoint delta report) and re-run everything; Teardown runs once, on exit — including on Ctrl+C and SIGTERM.

    Comments

    + + + + \ No newline at end of file diff --git a/ddd-agggrate-transparent.webp b/ddd-agggrate-transparent.webp new file mode 100644 index 000000000..a617d00a4 Binary files /dev/null and b/ddd-agggrate-transparent.webp differ diff --git a/ddd-agggrate.webp b/ddd-agggrate.webp new file mode 100644 index 000000000..88594f367 Binary files /dev/null and b/ddd-agggrate.webp differ diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 000000000..96fd3da94 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,44 @@ + + + + + + NpgsqlRest Examples | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Examples

    This section provides hands-on examples demonstrating NpgsqlRest features. Each example builds on the previous one, progressively introducing more advanced concepts.

    New to NpgsqlRest? Start with the SQL File examples — they're the recommended way to build endpoints and don't require any PostgreSQL function definitions.

    All examples are available in the examples repository on GitHub.

    Prerequisites

    Before running the examples, ensure you have:

    • PostgreSQL running locally (port 5432)
    • Bun runtime installed (bun.sh)
    • A database named example_db with default credentials (postgres/postgres)

    Getting Started

    1. Clone the repository:
    bash
    bash
    git clone https://github.com/NpgsqlRest/npgsqlrest-docs.git
    +cd npgsqlrest-docs/examples
    1. Install dependencies (downloads the NpgsqlRest binary and sets up required tools):
    bash
    bash
    bun install
    1. Navigate to any example directory and run it:
    bash
    bash
    cd 1_my_first_function
    +
    +# Apply database migrations
    +bun run db:up
    +
    +# Start the server (also rebuilds TypeScript and HTTP files)
    +bun run dev
    1. Visit http://127.0.0.1:8080 to see the result.

    Available Examples

    Function-Based Examples (RoutineSource)

    These examples use PostgreSQL functions and procedures as the endpoint source:

    ExampleDescriptionRelated Blog Post
    1_my_first_functionThe basics: creating a PostgreSQL function and exposing it as an HTTP endpoint with automatic TypeScript client generationEnd-to-End Type Checking
    2_static_type_checkingHow NpgsqlRest's autogenerated client code provides static type safety, catching breaking changes at build timeEnd-to-End Type Checking
    3_security_and_authDatabase-level security with cookie-based authentication and the principle of least privilegeDatabase-Level Security
    4_passwords_tokens_rolesPassword verification, JWT/Bearer tokens, role-based access control (RBAC), and external OAuth providersMultiple Auth Schemes & RBAC
    5_csv_basic_authCSV exports with HTTP Basic Auth, Excel integration, and type composition for BI use casesPostgreSQL BI Server
    6_image_uploadsSecure image uploads with file system storage, PostgreSQL Large Objects, and progress trackingSecure Image Uploads
    7_csv_excel_uploadsCSV and Excel file ingestion with row-by-row processing and automatic TypeScript clientsCSV & Excel Ingestion
    8_simple_chat_clientReal-time chat application using Server-Sent Events (SSE) and PostgreSQL RAISE statementsReal-Time Chat with SSE
    9_http_callsExternal API calls from PostgreSQL using HTTP custom types defined in type commentsExternal API Calls
    10_proxy_ai_serviceReverse proxy with transform mode for caching AI responses and external service integrationReverse Proxy & AI Service
    12_custom_typesCustom PostgreSQL composite types and multiset returns for complex nested JSON responsesCustom Types & Multiset
    13_passkeyWebAuthn passkey authentication with pure SQL: passwordless login using device biometricsPasskey SQL Auth
    14_table_formatExcel export and stats endpoints with HTML table format output and cookie authenticationExcel Exports Done Right
    16_scrap_demoWeb scraping in SQL: fetch a product listing with an HTTP Custom Type, parse the HTML with PostgreSQL XPath, and return the best-value laptop by a weighted scoreWeb Scraping with HTTP Types
    17_scrap_demo_2Web scraping in SQL: fetch a book catalog with an HTTP Custom Type, parse it with XML functions, and return the average book price on the pageWeb Scraping with HTTP Types
    18_scrap_proxy_demo v3.18.2Combine an HTTP Custom Type with a reverse proxy: fetch the page server-side, then @proxy the scraped HTML to an upstream service in the request body via @body_parameter_name. OmitAutomaticParameters keeps the generated client a clean no-argument callWeb Scraping with HTTP Types

    SQL File Examples (SqlFileSource) v3.12.0

    These examples use the new SQL File Source plugin — endpoints are generated directly from .sql files without needing PostgreSQL functions. Each is the SQL File equivalent of the function-based example above:

    ExampleDescriptionFunction-Based Equivalent
    1_my_first_function_sql_fileThe basics: creating an endpoint from a .sql file with automatic TypeScript client generation1_my_first_function
    2_static_type_checking_sql_fileStatic type safety with SQL File Source — catching breaking changes at build time2_static_type_checking
    3_security_and_auth_sql_fileDatabase-level security with cookie-based authentication using SQL files3_security_and_auth
    4_passwords_tokens_roles_sql_filePassword verification, JWT/Bearer tokens, and RBAC using SQL files4_passwords_tokens_roles
    5_csv_basic_auth_sql_fileCSV exports with HTTP Basic Auth using SQL files5_csv_basic_auth
    6_image_uploads_sql_fileSecure image uploads with file system storage and Large Objects using SQL files6_image_uploads
    7_csv_excel_uploads_sql_fileCSV and Excel file ingestion with row-by-row processing using SQL files7_csv_excel_uploads
    8_simple_chat_client_sql_fileReal-time chat application using SSE and SQL files8_simple_chat_client
    9_http_calls_sql_fileExternal API calls from PostgreSQL using SQL files9_http_calls
    10_proxy_ai_service_sql_fileReverse proxy with AI response caching using SQL files10_proxy_ai_service
    12_custom_types_sql_fileCustom composite types and nested JSON responses using SQL files12_custom_types
    14_table_format_sql_fileExcel export and stats endpoints with HTML table format using SQL files14_table_format

    MCP Server (SqlFileSource) v3.17.0

    Expose your .sql files as Model Context Protocol tools that an AI agent can discover and call — one source, two interfaces (REST + MCP).

    ExampleDescriptionRelated Blog Post
    15_mcp_serverAn "Acme Store" MCP server: each .sql file is both a typed REST endpoint and an @mcp tool. Includes a dual-panel web page (REST storefront + live MCP browser), a real Claude agent driving the store, MCP-only tools, and per-tool authorizationPostgreSQL as MCP Tools

    SQL Test Runner v3.19.0

    Test endpoints with plain .sql files using the built-in SQL test runner (npgsqlrest --test) — in-process endpoint invocation, transactional isolation, test databases, and endpoint coverage. Run with bun run test (or bun run test-watch) inside each example.

    ExampleDescription
    19_testing_basicThe basics: co-located layout (app.sql next to app.test.sql), boolean-SELECT and DO-block assertions, HTTP blocks with the _response table, multi-step scenario files
    20_testing_newdbA fresh test database per run: named Setup/Teardown steps (create database on an admin connection + migrations), {rnd5} unique names, one test per file, authorization + user parameters, a tag taxonomy (smoke/auth/fixtures/login), deferrable-constraint fixtures — and the login.sql endpoint uses the new named parameters (:email, :password)
    21_testing_isolationPerfect per-test isolation via a template database: migrations run once into a template, the shared run database and two per-test clones are created from it, deterministic sequence ids proven in parallel clones, a shared annotation profile attached with \ir carrying @setup/@teardown/@connection/@tag

    Available Commands

    Each example provides these scripts:

    CommandDescription
    bun run devStart NpgsqlRest server (rebuilds TypeScript and HTTP files)
    bun run buildCompile TypeScript to JavaScript
    bun run watchWatch mode for TypeScript changes
    bun run db:upApply database migrations
    bun run db:listList pending migrations

    Next Steps

    After completing these examples, explore:

    Comments

    + + + + \ No newline at end of file diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 000000000..f5bd8e897 Binary files /dev/null and b/favicon.ico differ diff --git a/favicon_original.ico b/favicon_original.ico new file mode 100644 index 000000000..4392380f5 Binary files /dev/null and b/favicon_original.ico differ diff --git a/guide/annotations.html b/guide/annotations.html new file mode 100644 index 000000000..0885ebec5 --- /dev/null +++ b/guide/annotations.html @@ -0,0 +1,195 @@ + + + + + + Comment Annotations Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Comment Annotations Guide

    NpgsqlRest uses comment annotations to configure API endpoints declaratively. Annotations work in two places:

    • PostgreSQL functions/procedures — via the built-in COMMENT system (COMMENT ON FUNCTION ...)
    • SQL files — via standard SQL comments (-- line comments and /* */ block comments) directly in .sql files. See the SQL File Endpoints Guide.

    All annotations work identically in both contexts. This guide explains how they work and how to use them effectively.

    How Annotations Work

    Comment annotations are special keywords placed in comments that control how endpoints are generated and configured. The annotation parser reads comments line by line, looking for recognized keywords at the start of each line.

    Basic Rules

    1. Annotations must start at the beginning of a line - the keyword must be the first text on the line
    2. Keywords are case-insensitive - HTTP, http, and Http are all valid
    3. Unrecognized text is ignored - you can mix documentation with annotations
    4. Multiple annotations per comment - use separate lines for each annotation

    Optional @ Prefix

    NpgsqlRest-specific annotations support an optional @ prefix. This follows the .http file convention that many developers are familiar with from tools like REST Client extensions in VS Code. Using the @ prefix is recommended for better visual distinction, but both syntaxes work identically:

    sql
    sql
    -- With @ prefix (recommended, follows .http file convention)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +@cached
    +@raw';
    +
    +-- Without @ prefix (still works the same)
    +comment on function my_func() is '
    +HTTP GET
    +authorize
    +cached
    +raw';
    +
    +-- Mixed (both work together)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +cached
    +@timeout 30s';

    The @ prefix also works with annotation parameters using the key = value syntax:

    sql
    sql
    -- Both syntaxes are equivalent
    +comment on function my_func() is '
    +HTTP GET
    +raw = true
    +timeout = 30s
    +my_custom_param = custom_value
    +';
    +
    +-- With @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +@raw = true
    +@timeout = 30s
    +@my_custom_param = custom_value
    +';

    Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

    TIP

    The @ prefix is purely optional. All existing code without @ continues to work unchanged. Choose whichever style you prefer, or mix them freely.

    HTTP Headers Don't Use @

    HTTP RFC standard annotations (headers with Name: value syntax like Content-Type: application/json) do not use the @ prefix - they follow the standard HTTP header format.

    Simple Example

    sql
    sql
    comment on function get_users() is
    +'Returns all active users from the database.
    +HTTP GET
    +@authorize';

    This comment contains:

    • Documentation text (ignored by parser)
    • HTTP GET annotation - exposes as GET endpoint
    • @authorize annotation - requires authentication

    The HTTP Annotation

    The HTTP annotation is the primary way to expose a function or table as an endpoint. Without it (when using the client's default CommentsMode: OnlyAnnotated, or the library's OnlyWithHttpTag), the object won't be exposed — unless a loaded plugin annotation requests an endpoint (e.g. @mcp, which can create an MCP-only routine with no HTTP route).

    Syntax Variations

    sql
    sql
    -- Basic: expose with default method and path
    +comment on function my_func() is 'HTTP';
    +
    +-- With HTTP method
    +comment on function my_func() is 'HTTP GET';
    +comment on function my_func() is 'HTTP POST';
    +
    +-- With custom path
    +comment on function my_func() is 'HTTP /custom-path';
    +
    +-- With method and path
    +comment on function my_func() is 'HTTP GET /users/list';

    Default Behavior

    When method is not specified:

    • GET is used for non-volatile functions, or functions with names starting with get_, containing _get_, or ending with _get
    • POST is used otherwise

    When path is not specified, it's generated from the function name using the URL prefix and naming conventions from configuration.

    Authorization Annotations

    Control access to endpoints with authorization annotations.

    Require Authentication

    sql
    sql
    -- Require any authenticated user
    +comment on function protected_func() is
    +'HTTP
    +@authorize';
    +
    +-- Require specific roles
    +comment on function admin_func() is
    +'HTTP
    +@authorize admin';
    +
    +-- Multiple roles (user must have at least one)
    +comment on function staff_func() is
    +'HTTP
    +@authorize admin, manager, supervisor';

    Role List Syntax

    When specifying multiple roles, you can use either comma-separated or space-separated values - both work identically:

    sql
    sql
    -- Comma-separated (traditional)
    +comment on function staff_func() is 'HTTP
    +@authorize admin, manager, supervisor';
    +
    +-- Space-separated (also valid)
    +comment on function staff_func() is 'HTTP
    +@authorize admin manager supervisor';
    +
    +-- Mixed (works too)
    +comment on function staff_func() is 'HTTP
    +@authorize admin, manager supervisor';

    This flexibility applies to any annotation that accepts a list of values.

    Allow Anonymous Access

    sql
    sql
    comment on function public_func() is
    +'HTTP
    +@allow_anonymous';

    This overrides the global RequiresAuthorization setting for this specific endpoint.

    Response Headers

    Set custom response headers by using the Header-Name: value format:

    sql
    sql
    comment on function get_html_page() is
    +'HTTP GET
    +Content-Type: text/html
    +Cache-Control: public, max-age=3600';
    +
    +comment on function get_data() is
    +'HTTP GET
    +X-Custom-Header: custom-value
    +X-Another-Header: another-value';

    Multiple headers with the same name are supported:

    sql
    sql
    comment on function with_cookies() is
    +'HTTP
    +Set-Cookie: session=abc123
    +Set-Cookie: theme=dark';

    Request Parameter Configuration

    Control how parameters are transmitted to the endpoint.

    Query String vs Body

    sql
    sql
    -- Force query string parameters
    +comment on function search(_query text) is
    +'HTTP GET
    +@request_param_type query_string';
    +
    +-- Force JSON body parameters
    +comment on function create_user(_name text, _email text) is
    +'HTTP POST
    +@request_param_type body_json';

    Caching

    Enable response caching for scalar results:

    sql
    sql
    -- Simple caching
    +comment on function get_settings() is
    +'HTTP GET
    +@cached';
    +
    +-- Cache with specific parameters as cache key
    +comment on function get_user_profile(_user_id int) is
    +'HTTP GET
    +@cached _user_id';
    +
    +-- Set cache expiration
    +comment on function get_config() is
    +'HTTP GET
    +@cached
    +@cache_expires_in 1h';

    Cache expiration uses PostgreSQL interval format: 10s, 5m, 1h, 1d, etc.

    Raw Output Mode

    Return raw text instead of JSON:

    sql
    sql
    -- Basic raw mode
    +comment on function export_text() is
    +'HTTP GET
    +@raw';
    +
    +-- CSV export with custom formatting
    +comment on function export_csv() is
    +'HTTP GET
    +@raw
    +@separator ,
    +@new_line \n
    +@columns';

    The @columns annotation includes column names as the first row.

    Combining Annotations

    Annotations can be combined freely. Order doesn't matter:

    sql
    sql
    comment on function get_report(_department text) is
    +'Generates a department report.
    +This is a cached endpoint requiring manager access.
    +
    +HTTP GET /reports/department
    +@authorize manager, admin
    +@cached _department
    +@cache_expires_in 30m
    +Content-Type: application/json
    +Cache-Control: private, max-age=1800';

    Note how NpgsqlRest-specific annotations use the @ prefix while HTTP headers (Content-Type, Cache-Control) use the standard RFC format.

    Debugging Annotations

    To see which annotations are applied when NpgsqlRest starts, set the logging level to Debug:

    In appsettings.json:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Debug"
    +    }
    +  }
    +}

    Via command line:

    bash
    bash
    npgsqlrest --Log:MinimalLevels:NpgsqlRest=Debug

    This will log each annotation as it's parsed and applied to endpoints.

    Comments Mode

    The CommentsMode configuration setting controls how annotations affect endpoint creation:

    ModeBehavior
    OnlyWithHttpTagOnly create endpoints for objects with HTTP annotation (default)
    ParseAllCreate all endpoints, parse annotations to modify them
    IgnoreCreate all endpoints, ignore all annotations

    Time/Duration Formats

    Several annotations accept time or duration values (e.g., @timeout, @cache_expires_in). See the complete Interval Format Reference for all supported units and syntax.

    Quick Reference

    UnitShortLong Forms
    Secondsssec, second, seconds
    Minutesmmin, minute, minutes
    Hourshhour, hours
    Daysdday, days
    Weekswweek, weeks

    Examples

    sql
    sql
    -- Using short forms (recommended)
    +@timeout 30s
    +@timeout 5min
    +@cache_expires_in 1h
    +
    +-- Decimals are supported
    +@timeout 1.5h      -- 1 hour 30 minutes
    +@timeout 500ms     -- half a second
    +
    +-- Numbers without unit default to seconds
    +@timeout 30        -- 30 seconds

    Single Token Requirement for @timeout

    The @timeout annotation reads only the first token after the keyword. Use formats without spaces to avoid parsing issues.

    sql
    sql
    -- Use single-token formats
    +@timeout 5min
    +@timeout 5m
    +@timeout 300s

    Common Patterns

    Public Read, Protected Write

    sql
    sql
    comment on function get_products() is
    +'HTTP GET
    +@allow_anonymous';
    +
    +comment on function create_product(_name text, _price numeric) is
    +'HTTP POST
    +@authorize admin';

    API Versioning with Custom Paths

    sql
    sql
    comment on function get_users_v1() is 'HTTP GET /v1/users';
    +comment on function get_users_v2() is 'HTTP GET /v2/users';

    Secure Sensitive Operations

    sql
    sql
    comment on function change_password(_old text, _new text) is
    +'HTTP POST
    +@authorize
    +@sensitive';

    The @sensitive annotation prevents parameter values from appearing in logs.

    Nested JSON for Composite Types

    sql
    sql
    comment on function get_user_with_address() is
    +'HTTP GET
    +@nested';

    The @nested annotation serializes composite type columns as nested JSON objects instead of expanding their fields into separate columns. See the NESTED annotation reference for details.

    Rate Limiting

    sql
    sql
    comment on function expensive_operation() is
    +'HTTP POST
    +@rate_limiter bucket';

    The policy name must match a policy configured in the Rate Limiter configuration.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/guide/authentication.html b/guide/authentication.html new file mode 100644 index 000000000..dba7ab798 --- /dev/null +++ b/guide/authentication.html @@ -0,0 +1,258 @@ + + + + + + Authentication Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Authentication

    This guide explains how authentication works in NpgsqlRest from end to end:

    1. The big picture — how the pieces fit together
    2. Configure an authentication scheme — cookie, bearer token, or JWT
    3. Write a login endpoint — sign users in from SQL
    4. How claims work — the user identity, produced from columns
    5. Accessing claims in your endpoints — as parameters, context variables, or template placeholders
    6. Logging out
    7. A complete worked example

    Reference pages

    This is the conceptual walkthrough. For exact options see @login, @logout, Authentication configuration, Authentication Options, and Claims Mapping.

    The big picture

    Authentication in NpgsqlRest is driven by your database. There is no separate identity service and no C# to write — you configure a scheme in appsettings.json, then write a normal SQL endpoint annotated with @login. Everything flows from there:

    mermaid
    flowchart TD
    +    CFG["appsettings.json
    +    Auth scheme
    +    cookie / bearer / jwt"]
    +    LOGIN["@login endpoint
    +    returns one row"]
    +
    +    C["1 - Client POSTs credentials"]
    +    COLS["2 - Login SQL returns columns"]
    +    CLAIMS["3 - NpgsqlRest turns columns into claims
    +    and issues a cookie / token"]
    +    REQ["4 - Every later request carries the identity
    +    NpgsqlRest checks @authorize and injects claims"]
    +
    +    C --> COLS --> CLAIMS --> REQ
    +    CFG -.->|"configures the session"| CLAIMS
    +    LOGIN -.->|"defines the columns"| COLS

    The three moving parts:

    PartWhere it livesWhat it does
    Schemeappsettings.jsonAuthDecides how the session is carried — an encrypted cookie, a bearer token, or a JWT.
    Login endpointa @login SQL routine / fileValidates credentials and returns the columns that become the user's claims.
    Claimsproduced at login, read on every requestThe user's identity (id, name, roles, and anything else you select).

    Step 1: Configure an authentication scheme

    A scheme decides how the signed-in session is carried between requests. Enable one (or several) in the Auth section. See Authentication configuration for every option.

    An encrypted, http-only cookie. Best for browser apps.

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieName": "my_app_auth",
    +    "CookieValidDays": 1
    +  }
    +}

    Bearer token

    A stateless token the client stores and sends in the Authorization: Bearer … header. Best for APIs and mobile clients.

    json
    json
    {
    +  "Auth": {
    +    "BearerTokenAuth": true,
    +    "BearerTokenAuthScheme": "token",
    +    "BearerTokenExpireHours": 1,
    +    "BearerTokenRefreshPath": "/api/token/refresh"
    +  }
    +}

    JWT

    A signed JSON Web Token, verifiable by other services that share the secret.

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtAuthScheme": "jwt",
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "my_app",
    +    "JwtAudience": "my_app",
    +    "JwtExpireMinutes": 60
    +  }
    +}

    The string you set as CookieAuthScheme / BearerTokenAuthScheme / JwtAuthScheme is the scheme name. Your login endpoint chooses which one to issue via its scheme column. You can enable more than one at the same time, as the Multiple Auth Schemes example does.

    Require auth by default

    Set NpgsqlRest.RequiresAuthorization: true so every endpoint requires authentication unless it opts out with @allow_anonymous. This is a safer default than protecting endpoints one by one.

    External OAuth providers (Google, etc.) layer on top of this — see External Authentication.

    Step 2: Write a login endpoint

    A login endpoint is a normal SQL endpoint annotated with @login. It returns one row; NpgsqlRest reads a few special columns and turns the rest into claims.

    sql
    sql
    create function login(_username text, _password text)
    +returns table (
    +    scheme text,
    +    user_id int,
    +    username text,
    +    email text
    +)
    +language sql
    +security definer
    +as $$
    +select
    +    'cookies' as scheme,   -- which scheme to sign in
    +    u.user_id,
    +    u.username,
    +    u.email
    +from users u
    +where u.username = _username
    +  and verify_password(_password, u.password_hash);  -- your own check
    +$$;
    +
    +comment on function login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive';

    Equivalent as a SQL file endpoint (sql/login.sql):

    sql
    sql
    /*
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive
    +@param $1 username
    +@param $2 password
    +*/
    +select
    +    'cookies' as scheme,   -- which scheme to sign in
    +    u.user_id,
    +    u.username,
    +    u.email
    +from users u
    +where u.username = $1
    +  and verify_password($2, u.password_hash);

    What happens:

    • A correct password returns one row → NpgsqlRest signs the user in and creates the claims user_id, username, email.
    • A wrong password matches nothing → empty result → 401 Unauthorized. No status column is needed for this.
    • @anonymous lets unauthenticated callers reach the endpoint; @security_sensitive keeps the password out of the logs.

    Verifying the password

    You have two options (full detail in @login → Password verification):

    • Verify in SQL (above): call your own function (e.g. verify_password) and just don't return a row when it fails. You control the hashing, but it runs on your database server.
    • Built-in hasher (more secure — recommended for production): return the stored hash in a hash column and let NpgsqlRest verify it against the password parameter, with optional success/failure callbacks. Pair it with @parameter_hash when registering users. It uses a strong, OWASP-recommended PBKDF2 configuration and — importantly — runs the CPU-intensive hashing on the NpgsqlRest application instance instead of your database. Password hashing is deliberately expensive, and the app tier is easier to scale than PostgreSQL, so offloading it is an important consideration.

    For the verify-in-SQL option you don't need anything external — PostgreSQL's built-in pgcrypto extension provides crypt(), gen_salt(), and digest(). The recommended scheme pre-hashes the password with SHA-256 + base64 before bcrypt (bcrypt truncates input at 72 bytes; the digest is a fixed 44 chars that always fits):

    sql
    sql
    create extension if not exists pgcrypto;
    +
    +create function hash_password(_password text)
    +returns text language sql as $$
    +  select crypt(encode(digest(_password, 'sha256'), 'base64'), gen_salt('bf', 12));
    +$$;
    +
    +create function verify_password(_password text, _password_hash text)
    +returns boolean language sql as $$
    +  select crypt(encode(digest(_password, 'sha256'), 'base64'), _password_hash) = _password_hash;
    +$$;

    Use hash_password() when registering a user and verify_password() in the login query above. gen_salt('bf', 12) sets the bcrypt work factor — 12 is a sensible default in 2025.

    This keeps everything in the database and is fine for small or low-traffic apps. For greater security and to offload the CPU-intensive hashing from your database to the app tier, prefer the built-in hasher — see @login → Password verification for the full comparison.

    Choosing a scheme

    The scheme column picks which configured scheme to issue. With several schemes enabled you can let the client choose by passing it as a parameter:

    sql
    sql
    -- _scheme is 'cookies', 'token' or 'jwt'; an unknown scheme is rejected (404)
    +select _scheme as scheme, u.user_id, u.username, u.roles, u.email, u.password_hash as hash
    +from users u
    +where u.username = _username;

    How claims work

    A claim is a single fact about the signed-in user — user_id = 1, username = alice, roles = {admin}. Claims are the bridge between "who logged in" and "what your SQL can see".

    Claims are just the login columns

    The rule is deliberately simple:

    Every column your login endpoint returns — except the special columns status, scheme, body, hash — becomes a claim. The column name is the claim name; the column value is the claim value.

    So this login row:

    user_idusernameemailroles
    1alicealice@example.com

    produces four claims: user_id, username, email, roles. You don't configure anything to create claims — you just select the columns you want to carry.

    Identity claims

    Three claims are special: they form the canonical identity used for the signed-in principal, for role checks in @authorize, and as the arguments to the verification callbacks. They're configured in Authentication Options:

    Config optionDefaultYour login must return a column named…
    DefaultUserIdClaimTypeuser_idthe user id
    DefaultNameClaimTypeuser_namethe display name
    DefaultRoleClaimTypeuser_rolesthe roles (a text[])

    Either name your columns to match the defaults, or change the config to match your columns. For example, if your login returns username and roles instead of user_name and user_roles:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "username",
    +      "DefaultRoleClaimType": "roles"
    +    }
    +  }
    +}

    With the role claim wired up, @authorize admin checks the roles claim for the value admin:

    sql
    sql
    comment on function get_users() is '
    +HTTP GET
    +@authorize admin';   -- 403 unless the roles claim contains "admin"

    Accessing claims in your endpoints

    After login, claims travel with every request. NpgsqlRest can hand them to your SQL in three ways. The constant that links all of them is the claim name — the same string that was the login column name.

    As function parameters

    Annotate the endpoint with @user_parameters (or enable globally with UseUserParameters: true). NpgsqlRest fills matching parameters from the claims, using ParameterNameClaimsMapping (parameter name → claim name):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_username": "username",
    +        "_email": "email"
    +      }
    +    }
    +  }
    +}
    sql
    sql
    create function who_am_i(
    +    _user_id text = null,   -- filled from the user_id claim
    +    _username text = null,   -- filled from the username claim
    +    _email text = null       -- filled from the email claim
    +)
    +returns table (user_id text, username text, email text)
    +language sql
    +as $$
    +    select _user_id, _username, _email;
    +$$;
    +
    +comment on function who_am_i(text, text, text) is '
    +HTTP GET
    +@authorize';
    • Claim values arrive as text (multi-value claims like roles as text[]); PostgreSQL coerces to your parameter types.
    • Give parameters default values so the function still works for anonymous calls — the default is used when there's no claim.
    • This is the approach in the Security & Auth example.

    Never trust client-supplied identity

    Declare the identity parameters (_user_id, …) and let NpgsqlRest fill them from the authenticated principal. Don't accept a user id from the request body and trust it. With @user_parameters, a value the client tries to send is overwritten by the claim.

    As PostgreSQL context variables

    Annotate with @user_context (or enable globally with UseUserContext: true). NpgsqlRest writes each claim into a session variable before running your SQL; you read it with current_setting('key', true). The mapping is ContextKeyClaimsMapping (context key → claim name):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "AuthenticationOptions": {
    +      "UseUserContext": true,
    +      "ContextKeyClaimsMapping": {
    +        "request.user_id": "user_id",
    +        "request.username": "username",
    +        "request.email": "email",
    +        "request.roles": "roles"
    +      }
    +    }
    +  }
    +}
    sql
    sql
    create function who_am_i()
    +returns table (user_id int, username text, email text, roles text[])
    +language sql
    +as $$
    +select
    +    nullif(current_setting('request.user_id', true), '')::int,
    +    nullif(current_setting('request.username', true), ''),
    +    nullif(current_setting('request.email', true), ''),
    +    nullif(current_setting('request.roles', true), '')::text[]
    +from users
    +where user_id = nullif(current_setting('request.user_id', true), '')::int;
    +$$;
    +
    +comment on function who_am_i() is '
    +HTTP GET
    +@authorize';
    • Always pass true as the second argument to current_setting() so a missing setting returns NULL instead of raising an error.
    • The client IP is available too (IpAddressContextKey, default request.ip_address), and all claims as JSON if you set ClaimsJsonContextKey.
    • This is the approach in the Multiple Auth Schemes example.

    Parameters vs context — which one?

    Parameters are type-checked by PostgreSQL and slightly faster; great for focused endpoints. Context variables are available to any SQL the request runs (views, triggers, nested function calls, resolved-parameter expressions) without threading them through every signature — great for cross-cutting things like row-level filtering. You can enable both.

    As template placeholders

    Anything that becomes a parameter (via @user_parameters) can also be referenced as a {name} placeholder in annotations that support substitution — response headers, custom/upload parameters, and HTTP custom type calls. This lets a claim drive a header, a file path, or an outbound request without the client sending it:

    sql
    sql
    comment on function upload_avatar(_user_id int, _file text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@upload for file_system
    +@file_system_path = /var/uploads/{_user_id}';   -- claim value drives the path

    And once claims are in context variables (@user_context), they're visible to every SQL expression the request evaluates — including resolved parameter expressions. For proxy endpoints, enabling UseUserContext / UseUserParameters also forwards the claims upstream (as headers / query parameters respectively).

    Logging out

    Mark an endpoint with @logout. Returning nothing signs the user out of the default scheme; returning scheme name(s) signs out those specific schemes.

    sql
    sql
    create function logout()
    +returns void
    +language sql
    +security definer
    +as $$
    +  -- nothing to return → sign out the current user's scheme
    +$$;
    +
    +comment on function logout() is '
    +HTTP POST
    +@logout
    +@authorize';

    A complete worked example

    A minimal but complete cookie-based setup: configuration, a login endpoint, and a protected endpoint that reads the signed-in user via parameters.

    appsettings.json

    json
    json
    {
    +  "Auth": {
    +    "CookieAuth": true,
    +    "CookieAuthScheme": "cookies",
    +    "CookieName": "my_app_auth",
    +    "CookieValidDays": 1
    +  },
    +  "NpgsqlRest": {
    +    "IncludeSchemas": [ "api" ],
    +    "RequiresAuthorization": true,
    +    "AuthenticationOptions": {
    +      "DefaultUserIdClaimType": "user_id",
    +      "DefaultNameClaimType": "username",
    +      "DefaultRoleClaimType": "roles",
    +      "UseUserParameters": true,
    +      "ParameterNameClaimsMapping": {
    +        "_user_id": "user_id",
    +        "_username": "username",
    +        "_roles": "roles"
    +      }
    +    }
    +  }
    +}

    login — sign in (anonymous, verifies password in SQL)

    sql
    sql
    create function api.login(_username text, _password text)
    +returns table (scheme text, user_id int, username text, roles text[])
    +language sql
    +security definer
    +as $$
    +select 'cookies', u.user_id, u.username, u.roles
    +from api.users u
    +where u.username = _username
    +  and api.verify_password(_password, u.password_hash);
    +$$;
    +
    +comment on function api.login(text, text) is '
    +HTTP POST
    +@login
    +@anonymous
    +@security_sensitive';

    my_profile — protected, reads claims via parameters

    sql
    sql
    create function api.my_profile(_user_id int = null, _username text = null, _roles text[] = '{}')
    +returns table (user_id int, username text, roles text[], is_admin boolean)
    +language sql
    +as $$
    +select _user_id, _username, _roles, _roles @> array['admin'];
    +$$;
    +
    +comment on function api.my_profile(int, text, text[]) is '
    +HTTP GET
    +@authorize';

    admin_users — admin only

    sql
    sql
    create function api.admin_users()
    +returns setof api.users
    +language sql
    +as $$ select * from api.users; $$;
    +
    +comment on function api.admin_users() is '
    +HTTP GET
    +@authorize admin';   -- requires the roles claim to contain "admin"

    Flow:

    1. POST /api/login with a valid username/password → cookie set, claims user_id/username/roles created.
    2. GET /api/my-profile → NpgsqlRest fills _user_id/_username/_roles from the claims; anonymous callers are rejected (401) because of @authorize.
    3. GET /api/admin-users → only succeeds when the roles claim contains admin (otherwise 403).

    See it in the examples

    Two runnable examples demonstrate both claim-access styles:

    • Security & Auth — cookie auth, password hashing in SQL, claims read as parameters.
    • Passwords, Tokens & Roles — cookie + bearer + JWT, built-in password hasher with callbacks, external (Google) login, claims read as context variables, role-based authorization.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/index.html b/guide/changelog/index.html new file mode 100644 index 000000000..a79da2a81 --- /dev/null +++ b/guide/changelog/index.html @@ -0,0 +1,36 @@ + + + + + + Changelog | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog

    Select a version below to view the full changelog.

    Note: The changelog for versions older than 3.0 can be found here: Changelog Archive


    Version 3.19 (Latest)

    VersionDate
    v3.19.02026-07-03
    • New: SQL test runner (npgsqlrest --test) — write endpoint tests as plain .sql files: boolean-SELECT and DO-block assertions, in-process endpoint invocation via embedded HTTP blocks (# @claim principals, response captured into a temp table), per-file isolated non-pooled connections running in parallel, Setup/Teardown steps with named-step registry and per-step connections, dedicated test databases with {rnd} tokens, \i/\ir script includes with paste semantics, per-file -- @setup/-- @teardown/-- @connection/-- @tag annotations, path filtering (Filter) and tag filtering (Tag/ExcludeTag), watch mode (--watch) with in-process endpoint rebuilds, endpoint coverage reporting (on by default for full runs) with a CI threshold gate, JUnit XML output, and guaranteed teardown on Ctrl+C/SIGTERM and hard exits
    • New: watch mode (--watch) — two modes: with --test it re-runs tests on changes (endpoint files rebuild in-process); without --test it supervises the server and restarts it on SQL file source and configuration changes, regenerating code (TypeScript client, HTTP files, OpenAPI) on every cycle
    • New: named parameters in SQL fileswhere email = :email instead of $1; the placeholder is the parameter name (camelCase-converted for the API), repeated names map to one parameter (also across statements), claim mappings hook up by placeholder name, and the new @param name type is type form retypes without renaming
    • New: SqlFileSource.SkipPattern (default "*.test.sql") — exclude files from endpoint discovery by glob
    • New: Log:MinimalLevels entries accept "Off" ("None", "Silent") to fully mute an individual logger

    Version 3.18

    VersionDate
    v3.18.22026-06-26
    v3.18.12026-06-23
    v3.18.02026-06-23
    • New: ProxyOptions.MaxForwardedQueryParamLength (default 2048) — a server-filled value too long for the proxy query string is skipped with a warning instead of producing an unusable request line (HTTP 414/431); forward large values via a body-carrying method and @body_parameter_name
    • New: OmitAutomaticParameters on the TypeScript client, HTTP file, and OpenAPI generators (default false) — omit optional server-filled parameters (HTTP Custom Type fields, resolved-parameter expressions, upload metadata, IP/claim params) from generated request shapes
    • Fix: @body_parameter_name now matches an HTTP Custom Type field by its converted, actual, or expanded signature name, case-insensitively — applied consistently by request handling and all code generators (also fixes the HTTP file and OpenAPI generators leaving the field in the query string)
    • Fix: TypeScript client generation for @body_parameter_name endpoints — no leaked ? in the body property name, the body parameter is excluded from the query string, and no fetch body is emitted for GET
    • Fix: all automatic (server-filled) proxy parameters — user claims, IP, HTTP Custom Type fields, and resolved-parameter expressions — now forward to proxy endpoints uniformly, with placement following the endpoint's RequestParamType (query string or merged into the JSON body) rather than the HTTP verb
    • New: HTTP Custom Type response caching via the @cache directive — opt-in, GET-only outbound response caching with TTL, success-only storage, and stampede protection; configured globally under HttpClientOptions (CacheEnabled, MaxCacheEntries, CachePruneIntervalSeconds)
    • Fix: an HTTP Custom Type parameter on a database-function endpoint fired one outbound call per composite field (a 6-field type → 6 identical calls); now one call per distinct type, shared from a single response
    • Fix: @timeout, @retry_delay, and @cache directives placed after the headers (as the docs showed) were silently ignored — both before-request-line and after-headers placements are now equivalent

    Version 3.17

    VersionDate
    v3.17.02026-06-10
    • New plugin NpgsqlRest.Mcp — expose opted-in PostgreSQL routines as MCP tools (tools/list / tools/call over Streamable HTTP) via the @mcp annotation; a bare @mcp with no HTTP tag is an MCP-only tool with no public route
    • MCP OAuth 2.1 resource-server authorization: Protected Resource Metadata (RFC 9728), audience binding (RFC 8707), per-tool @authorize enforcement on tools/call
    • Neutral plugin extension points on RoutineEndpoint (HandleCommentLine, Items, UnhandledCommentLines) and new CommentsMode.OnlyAnnotated (now the client default)
    • New: {name} annotation substitution can resolve allowlisted environment variables (NpgsqlRest:AvailableEnvVars); matching is now case-insensitive, unknown placeholders log a build-time warning
    • New: optional {NAME} and required {!NAME} environment-variable placeholders in config values — missing optional variables no longer crash typed reads
    • Breaking: safer configuration defaults — Cors:AllowCredentials is now false, passkey UserVerificationRequirement / ResidentKeyRequirement default to "required", TestConnectionStrings defaults to true
    • Breaking (C# API only): RoutineEndpoint.OpenApiHide / OpenApiTags removed — the OpenAPI plugin parses the @openapi annotation itself; annotation users are unaffected
    • 🔴 Security fix: SSE per-event USING HINT scopes were not enforced — hint-scoped events were delivered to every subscriber; upgrade strongly recommended for hint-based SSE scoping
    • Fix: bare @cached (no parameter list) keyed only on the routine name, serving the first cached response to all inputs
    • Fix: HybridCache silently bypassed the cache on null cached parameters (Cache key contains invalid content)
    • Fix: malformed JSON request body now returns 400 Bad Request (was 404)
    • Fix: JSON command parameters accept json, jsonb, or text target types

    Version 3.16

    VersionDate
    v3.16.32026-06-03
    v3.16.22026-06-02
    v3.16.12026-06-01
    v3.16.02026-05-20
    • New: AvailableEnvVars under StaticFiles:ParseContentOptions templates environment-variable values into served static content (same {NAME} tags as claims) — build a SPA bundle once, inject per-environment values from pod env vars at boot

    • New: rate-limiter rejection StatusCode/StatusMessage are now overridable per policy (the global values stay as defaults); ships a ready-to-use disabled login_throttle policy

    • Fix: cache stampede protection now actually fires for cached routine responses (IRoutineCache.GetOrCreateAsync); a burst of identical cold-cache requests collapses to a single database execution

    • Fix: JSON-to-parameter parsers for timestamp, timestamptz, time, and timetz are now host-TZ-independent (silent host-offset shift removed)

    • Fix: TryParseDate falls back to a DateTime parse when DateOnly rejects offset/Z-bearing inputs

    • Breaking: JSON timestamps are now interpreted as UTC by default (naive ISO strings assumed UTC, Z / offset-bearing strings converted to UTC)

    • New NpgsqlRest:JsonTimestampsAreUtc config key — opt-out escape hatch to restore the pre-3.16.0 host-local interpretation


    Version 3.15

    VersionDate
    v3.15.22026-05-11
    v3.15.12026-05-11
    v3.15.02026-05-11
    • Auth: named cookie schemes now actually authenticate requests (cookie-aware policy-scheme dispatch)
    • New Auth:CookieSameSite and Auth:CookieSecure config keys for cross-origin SPA / mobile clients (root + per-scheme)
    • OpenAPI filtering: IncludeSchemas, ExcludeSchemas, NameSimilarTo, NameNotSimilarTo, RequiresAuthorizationOnly
    • New @openapi annotation — @openapi hide and @openapi tag <name> for per-routine OpenAPI control
    • Fix: Auth:Schemes keys validated by Type, not by name — custom schemes named like the docs examples no longer fail startup (3.15.1)
    • Fix: --config and --validate CLI commands honor ValidateConfigKeys mode (3.15.1)
    • Fix: RateLimiterOptions:Policies and CacheOptions:Profiles validate by shape — custom policy / profile names no longer fail startup under ValidateConfigKeys: "Error" (3.15.2)
    • Improvement: ValidationOptions:Rules rule bodies validated for typos (3.15.2)

    Version 3.14

    VersionDate
    v3.14.02026-05-09
    • Standalone client no longer wires the NpgsqlRest.CrudSource plugin (library use unchanged)
    • New SSE annotations @sse_publish and @sse_subscribe — split publisher and subscriber roles
    • Warning when a RAISE looks like a missed @sse_publish
    • Reliable SSE connection handshake
    • Startup error when claim-mapped parameters use a non-text type
    • Warning when a request value is overridden by claim auto-bind
    • Lower-allocation JSON conversion for arrays and composites
    • Hardening: ArrayPool rentals released in try/finally, column-decryption failures logged at Trace

    Version 3.13

    VersionDate
    v3.13.02026-04-24
    • Auth Schemes — named additional authentication schemes (Cookies / BearerToken / Jwt)
    • Login functions can select a scheme via the scheme column

    Version 3.12

    VersionDate
    v3.12.02026-03-23
    • New endpoint source plugin: NpgsqlRest.SqlFileSource — generate REST API endpoints directly from .sql files
    • Multi-command SQL files with batched execution and named result sets
    • New @param / @parameter annotation for renaming and retyping parameters across all endpoint types
    • Glob pattern ** recursive matching support
    • Interface refactoring: IEndpointSource / IRoutineSource split
    • TsClient: multi-command SQL file endpoint support
    • Composite type cache public API

    Version 3.11

    VersionDate
    v3.11.12026-03-13
    v3.11.02026-03-10
    • proxy_out annotation (post-execution proxy)
    • TsClient: proxy and proxy_out passthrough endpoint support
    • authorize annotation now matches user ID and user name claims

    Version 3.10

    VersionDate
    v3.10.02026-02-25
    • Resolved parameter expressions for server-side secret handling
    • HTTP Client Type retry logic (@retry_delay)
    • Data Protection encrypt/decrypt annotations

    Version 3.9

    VersionDate
    v3.9.02026-02-23
    • Commented configuration output (--config)
    • Configuration search and filter (--config [filter])
    • CLI improvements and test suite

    Version 3.8

    VersionDate
    v3.8.02025-02-11
    • Configuration key validation
    • Optional path parameters
    • Machine-readable CLI commands for tool integration
    • Universal fallback_handler for all upload handlers

    Version 3.7

    VersionDate
    v3.7.02025-02-07
    • Pluggable table format renderers (HTML, Excel)
    • TsClient per-endpoint URL export control
    • Excel upload handler fallback_handler

    Version 3.6

    VersionDate
    v3.6.32025-02-03
    v3.6.22025-02-02
    v3.6.12025-02-02
    v3.6.02025-02-01
    • Security headers middleware
    • Forwarded headers middleware
    • Health check endpoints
    • PostgreSQL statistics endpoints

    Version 3.5

    VersionDate
    v3.5.02025-01-28
    • PasskeyAuth (WebAuthn/FIDO2)
    • Response compression fix for static files
    • Separate core and client logging

    Version 3.4

    VersionDate
    v3.4.82025-01-26
    v3.4.72025-01-21
    v3.4.62025-01-21
    v3.4.52025-01-19
    v3.4.42025-01-17
    v3.4.32025-01-16
    v3.4.22025-01-15
    v3.4.12025-01-15
    v3.4.02025-01-16
    • Composite type support (arrays, nested JSON)
    • Deep nested composite type resolution
    • Multidimensional array support
    • Performance optimizations (type category lookup, StringBuilder pooling, CancellationToken propagation)

    Version 3.3

    VersionDate
    v3.3.12025-01-14
    v3.3.02025-01-08
    • Parameter validation
    • Linux ARM64 build and Docker image
    • Proxy response caching
    • Optional @ prefix for comment annotations

    Version 3.2

    VersionDate
    v3.2.72025-01-05
    v3.2.62025-01-04
    v3.2.42025-01-03
    v3.2.32025-12-30
    v3.2.22025-12-24
    v3.2.12025-12-23
    v3.2.02025-12-22
    • Reverse proxy feature
    • JWT authentication support
    • HybridCache support
    • Docker image with Bun runtime

    Version 3.1

    VersionDate
    v3.1.32025-12-21
    v3.1.22025-12-20
    v3.1.12025-12-15
    v3.1.02025-12-13
    • HTTP Types (external API calls from PostgreSQL functions)
    • Path parameters support
    • SIMD-accelerated string processing
    • Routine caching improvements
    • Multi-host connection support

    Version 3.0

    VersionDate
    v3.0.12025-11-28
    v3.0.02025-11-27
    • .NET 10 target framework
    • Rate limiter
    • OpenAPI 3.0 support
    • Error handling improvements (RFC 7807 Problem Details)
    • TsClient improvements
    • SSE (Server-Sent Events) naming refactor

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.0.0.html b/guide/changelog/v3.0.0.html new file mode 100644 index 000000000..0ad718d7a --- /dev/null +++ b/guide/changelog/v3.0.0.html @@ -0,0 +1,350 @@ + + + + + + Changelog v3.0.0 (2025-11-27) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.0.0 (2025-11-27)

    Version 3.0.0 (2025-11-27)

    Full Changelog

    Docker JIT Version

    • New Docker image with .NET 10 JIT runtime: npgsqlrest/npgsqlrest:3.0.0-jit
    • This image uses the standard .NET 10 runtime with JIT compilation instead of AOT compilation.
    • Suitable for development and scenarios where AOT compilation is not required.
    • JIT version can be faster to execute but slower startup time and larger image size compared to AOT version.

    Image Size Comparison (approximate):

    VersionSize
    AOT~80-100 MB
    JIT~200-250 MB

    .NET 10 Target Framework

    • Upgraded target framework to .NET 10.
    • Faster and more memory efficient.

    TsClient (Code Generation) Improvements

    1. When return value is JSON or JSONB, generated TypeScript type is any instead of string.
    2. New parameter annotation tsclient_module. Sets different module name for the generated TypeScript client file. For example: tsclient_module = test will create test.ts or test.js and add every and group endpoint to that module instead of the default.
    3. Fixed and improved generated JSDoc comments for better IntelliSense support in IDEs. JavaScript JSDoc invlude proper types and TypeScript JSDoc will not include types to avoid duplication. All parameters comment now include description.
    4. SSE generated parameters signature changed.

    Fetch for SSE enabled endpoint now looks like this:

    typescript
    typescript
    /**
    + * function test_sse()
    + * returns table(
    + *     id integer
    + * )
    + *
    + * @remarks
    + * comment on function test_sse is 'HTTP GET
    + * authorize
    + * upload for file_system
    + * sse
    + * tsclient_module = test';
    + *
    + * @param onMessage - Optional callback function to handle incoming SSE messages.
    + * @param id - Optional execution ID for SSE connection. When supplied, only EventSource object with this ID in query string will will receive events.
    + * @param closeAfterMs - Time in milliseconds to wait before closing the EventSource connection. Used only when onMessage callback is provided.
    + * @param awaitConnectionMs - Time in milliseconds to wait after opening the EventSource connection before sending the request. Used only when onMessage callback is provided.
    + * @returns {status: number, response: ITestSseResponse[]}
    + *
    + * @see FUNCTION test_sse
    + */
    +export async function testSse(
    +    onMessage?: (message: string) => void,
    +    id: string | undefined = undefined,
    +    closeAfterMs = 1000,
    +    awaitConnectionMs: number | undefined = 0
    +) : Promise<{status: number, response: ITestSseResponse[]}> {
    +    const executionId = id ? id : window.crypto.randomUUID();
    +    let eventSource: EventSource;
    +    if (onMessage) {
    +        eventSource = createTestSseEventSource(executionId);
    +        eventSource.onmessage = (event: MessageEvent) => {
    +            onMessage(event.data);
    +        };
    +        if (awaitConnectionMs !== undefined) {
    +            await new Promise(resolve => setTimeout(resolve, awaitConnectionMs));
    +        }
    +    }
    +    try {
    +        const response = await fetch(baseUrl + "/api/test-sse", {
    +            method: "GET",
    +            headers: {
    +                "Content-Type": "application/json",
    +                "X-test-ID": executionId
    +            },
    +        });
    +        return {
    +            status: response.status,
    +            response: response.status == 200 ? await response.json() as ITestSseResponse[] : await response.text() as any
    +        };
    +    }
    +    finally {
    +        if (onMessage) {
    +            setTimeout(() => eventSource.close(), closeAfterMs);
    +        }
    +    }
    +}

    Info Events Streaming Changes (Server-Sent Events)

    • Rename configuration key from CustomServerSentEventsResponseHeaders to ServerSentEventsResponseHeaders.
    • Option from CustomServerSentEventsResponseHeaders to SseResponseHeaders.
    • Comment annotations:
      • from info_path, info_events_path, info_streaming_path to sse, sse_path, sse_events_path
      • from info_scope, info_events_scope, info_streaming_scope to sse_scope, sse_events_scope

    Removed Self scope level

    • Removed self scope level for SSE events. Only matching, authorize, and all levels are supported now.
    • Event will always be skipped if executing id is supplied in request header and in event source query parameter, and they don't match.

    New Feature: Support for custom notice level

    • New option and configuration:
      • configuration: DefaultServerSentEventsEventNoticeLevel
      • option: public PostgresNoticeLevels DefaultSseEventNoticeLevel { get; set; } = PostgresNoticeLevels.INFO;

    Set the default notice level for SSE events when not specified in comment annotation. When SSE path is set, generate SSE events for PostgreSQL notice messages with this level or higher.

    Other Comment Annotations Changes

    • Setting SSE path (and optionally notice level) via comment annotations:
    code
    sse [ path ] [ on info | notice | warning ] 
    +sse_path [ path ] [ on info | notice | warning ]
    +sse_events_path [ path ] [ on info | notice | warning ]

    Without argument, just sse or sse_path or sse_events_path, will set the path to default, which depends on default level (info for INFO level, notice for NOTICE level, etc).

    Single argument is treated as path.

    If path is followed by on info or on notice or on warning, it will set the notice level accordingly.

    Note: you can also set sse path using parameter annotations syntax (key = value), for example sse = /my_sse_path or sse_path = /my_sse_path.

    • New comment annotations to set custom SSE event notice level per endpoint:
    code
    sse_level [ info | notice | warning ]
    +sse_events_level [ info | notice | warning ]

    Note: you can also set sse level using parameter annotations syntax (key = value), for example sse_level = info, etc.

    • Scope annotations changed name to match new SSE naming:
    code
    sse_scope [ [ matching | authorize | all ] | [ authorize [ role_or_user1, role_or_user1, role_or_user1 [, ...] ] ] ] 
    +sse_events_scope [ [ matching | authorize | all ] | [ authorize [ role_or_user1, role_or_user1, role_or_user1 [, ...] ] ] ]

    Timeout Handling

    • Timeouts are not retried automatically by NpgsqlRest anymore.
    • Timeout error policy can be set in ErrorHandlingOptions section of client configuration.
    • Default mapping for timeout errors: "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}
    • Configuration option CommandTimeout is using PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) instead of integer seconds.
    • Comment annotation is also now using PostgreSQL interval format (for example: '30 seconds' or '30s', '1 minute' or '1min', etc.) instead of integer seconds.
    • Option CommandTimeout is now TimeSpan? instead of int.

    OpenAPI 3.0 Support

    Added OpenAPI 3.0 support with the new NpgsqlRest.OpenApi plugin (available as a separate NuGet package as library plugin).

    Also, added new client configuration section OpenApiOptions to configure OpenAPI generation and serving.

    New configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // Enable or disable the generation of OpenAPI files for NpgsqlRest endpoints.
    +    //
    +    "OpenApiOptions": {
    +      "Enabled": false,
    +      //
    +      // File name for the generated OpenAPI file. Set to null to skip the file generation.
    +      //
    +      "FileName": "npgsqlrest_openapi.json",
    +      //
    +      // URL path for the OpenAPI endpoint. Set to null to skip the endpoint generation.
    +      //
    +      "UrlPath": "/openapi.json",
    +      //
    +      // Set to true to overwrite existing files.
    +      //
    +      "FileOverwrite": true,
    +      //
    +      // The title of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // If not set, the database name from the ConnectionString will be used.
    +      //
    +      "DocumentTitle": null,
    +      //
    +      // The version of the OpenAPI document. This appears in the "info" section of the OpenAPI specification.
    +      // When null, default is "1.0.0".
    +      //
    +      "DocumentVersion": "1.0.0",
    +      //
    +      // Optional description of the API. This appears in the "info" section of the OpenAPI specification.
    +      //
    +      "DocumentDescription": null,
    +      //
    +      // Include current server information in the "servers" section of the OpenAPI document.
    +      //
    +      "AddCurrentServer": true,
    +      //
    +      // Additional server entries to add to the "servers" section of the OpenAPI document.
    +      // Each server entry must have "Url" property and optional "Description" property.
    +      //
    +      "Servers": [/*{"Url": "https://api.example.com", "Description": "Production server"}*/],
    +      //
    +      // Security schemes to include in the OpenAPI document.
    +      // If not specified, a default Bearer authentication scheme will be added for endpoints requiring authorization.
    +      // Supported types: "Http" (for Bearer/Basic auth) and "ApiKey" (for Cookie/Header/Query auth).
    +      // Examples:
    +      // - Bearer token: {"Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT"}
    +      // - Cookie auth: {"Name": "cookieAuth", "Type": "ApiKey", "In": ".AspNetCore.Cookies", "ApiKeyLocation": "Cookie"}
    +      // - Basic auth: {"Name": "basicAuth", "Type": "Http", "Scheme": "Basic"}
    +      //
    +      "SecuritySchemes": [
    +        /*{
    +          "Name": "bearerAuth",
    +          "Type": "Http",
    +          "Scheme": "Bearer",
    +          "BearerFormat": "JWT",
    +          "Description": "JWT Bearer token authentication"
    +        },
    +        {
    +          "Name": "cookieAuth",
    +          "Type": "ApiKey",
    +          "In": ".AspNetCore.Cookies",
    +          "ApiKeyLocation": "Cookie",
    +          "Description": "Cookie-based authentication"
    +        }*/
    +      ]
    +    }
    +  }
    +}

    Error Handling Improvements

    Added comprehensive error handling improvements with standardized error responses using Problem Details (RFC 7807) format.

    json
    json
    {
    +  "title": "Error message or custom title",
    +  "status": 400,
    +  "detail": "P0001"
    +}

    Old error handling options have been removed in favor of a more flexible and extensible error code policy system.

    • Removed obsolete configuration options from client configuration:
    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // Set to true to return message from NpgsqlException on response body. Default is true.
    +    //
    +    "ReturnNpgsqlExceptionMessage": true,
    +    //
    +    // Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes. Default is 57014 query_canceled to 205 Reset Content.
    +    //
    +    "PostgreSqlErrorCodeToHttpStatusCodeMapping": {
    +      "57014": 205,
    +      "P0001": 400,
    +      // PL/pgSQL raise exception
    +      "P0004": 400
    +      // PL/pgSQL assert failure
    +    }
    +  }
    +}
    • Removed options:
    csharp
    csharp
        /// <summary>
    +    /// Set to true to return message from NpgsqlException on response body. Default is true.
    +    /// </summary>
    +    public bool ReturnNpgsqlExceptionMessage { get; set; } = true;
    +
    +    /// <summary>
    +    /// Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes
    +    /// Default is 57014 query_canceled to 205 Reset Content.
    +    /// </summary>
    +    public Dictionary<string, int> PostgreSqlErrorCodeToHttpStatusCodeMapping { get; set; } = new()
    +    {
    +        { "57014", 205 }, //query_canceled -> 205 Reset Content
    +        { "P0001", 400 }, // raise_exception -> 400 Bad Request
    +        { "P0004", 400 }, // assert_failure -> 400 Bad Request
    +    };
    • Added new configuration section in client configuration:
    json
    json
    {
    +  "ErrorHandlingOptions": {
    +    // Remove Type URL from error responses. Middleware automatically sets a default Type URL based on the HTTP status code that points to the RFC documentation.
    +    "RemoveTypeUrl": false,
    +    // Remove TraceId field from error responses. Useful in development and debugging scenarios to correlate logs with error responses.
    +    "RemoveTraceId": true,
    +    //
    +    // Default policy name to use from the ErrorCodePolicies section.
    +    //
    +    "DefaultErrorCodePolicy": "Default",
    +    //
    +    // Timeout error mapping when command timeout occurs (see NpgsqlRest CommandTimeout setting).
    +    //
    +    "TimeoutErrorMapping": {"StatusCode": 504, "Title": "Command execution timed out", "Details": null, "Type": null}, // timeout error case -> 504 Gateway Timeout
    +    //
    +    // Named policies for mapping of PostgreSQL error codes to HTTP Status Codes.
    +    //
    +    // If routine raises these PostgreSQL error codes, endpoint will return these HTTP Status Codes.
    +    // See https://www.postgresql.org/docs/current/errcodes-appendix.html
    +    // Exception is timeout, which is not a PostgreSQL error code, but a special case when command timeout occurs.
    +    //
    +    // - StatusCode: HTTP status code to return.
    +    // - Title: Optional title field in response JSON. When null, actual error message is used.
    +    // - Details: Optional details field in response JSON. When null, PostgreSQL Error Code is used.
    +    // - Type: Optional types field in response JSON. A URI reference [RFC3986] that identifies the problem type. Set to null to use default. Or RemoveTypeUrl to true to disable.
    +    //
    +    "ErrorCodePolicies": [{
    +      "Name": "Default",
    +      "ErrorCodes": {
    +        "42501": {"StatusCode": 403, "Title": "Insufficient Privilege", "Details": null, "Type": null},   // query_canceled      -> 403 Forbidden
    +        "57014": {"StatusCode": 205, "Title": "Cancelled", "Details": null, "Type": null},                // query_canceled      -> 205 Reset Content
    +        "P0001": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // raise_exception     -> 400 Bad Request
    +        "P0004": {"StatusCode": 400, "Title": null, "Details": null, "Type": null},                       // assert_failure      -> 400 Bad Request
    +        "42883": {"StatusCode": 404, "Title": "Not Found", "Details": null, "Type": null},                // undefined_function  -> 404 Not Found
    +      }
    +    }]
    +  }
    +}
    • Added new options:
    csharp
    csharp
        /// <summary>
    +    /// Map PostgreSql Error Codes (see https://www.postgresql.org/docs/current/errcodes-appendix.html) to HTTP Status Codes
    +    /// </summary>
    +    public ErrorHandlingOptions ErrorHandlingOptions { get; set; } = new();
    csharp
    csharp
    public class ErrorHandlingOptions
    +{
    +    public string? DefaultErrorCodePolicy { get; set; } = "Default";
    +    
    +    public ErrorCodeMappingOptions? TimeoutErrorMapping { get; set; } = new()
    +    {
    +        StatusCode = 504,
    +        Title = "Command execution timed out"
    +    };
    +
    +    public Dictionary<string, Dictionary<string, ErrorCodeMappingOptions>> ErrorCodePolicies { get; set; } = new()
    +    {
    +        ["Default"] = new()
    +        {
    +            { "42501", new() { StatusCode = 403, Title = "Insufficient Privilege" } },
    +            { "57014", new() { StatusCode = 205, Title = "Cancelled" } },
    +            { "P0001", new() { StatusCode = 400 } },
    +            { "P0004", new() { StatusCode = 400 } },
    +            { "42883", new() { StatusCode = 404, Title = "Not Found" } },
    +        }
    +    };
    +}
    • Added new comment annotations to set error code policy per endpoint:
    code
    error_code_policy_name [ name ]
    +error_code_policy [ name ]
    +error_code [ name ]

    For example:

    sql
    sql
    comment on function my_function(json) is 'error_code_policy custom_policy_name';
    +-- or
    +comment on function my_function(json) is 'error_code_policy_name custom_policy_name';
    +-- or
    +comment on function my_function(json) is 'error_code custom_policy_name';

    Metadata Query Improvements

    There two new options for Metadata queries support, that are also available in client configuration:

    • MetadataQueryConnectionName: Specify a named connection from ConnectionStrings dictionary to use for metadata queries. When null, the default connection string or data source is used.
    • MetadataQuerySchema: Set the PostgreSQL search path schema for metadata query functions. Useful when using non-superuser connection roles with limited schema access.

    Options:

    csharp
    csharp
    /// <summary>
    +/// The connection name in ConnectionStrings dictionary that will be used to execute the metadata query. If this value is null, the default connection string or data source will be used.
    +/// </summary>
    +public string? MetadataQueryConnectionName { get; set; } = null;
    +
    +/// <summary>
    +/// Set the search path to this schema that contains the metadata query function. Default is `public`.
    +/// </summary>
    +public string? MetadataQuerySchema { get; set; } = "public";
    json
    json
    {
    +  //
    +  // Additional connection settings and options.
    +  //
    +  "ConnectionSettings": {
    +    //
    +    // other ConnectionSettings settings
    +    //
    +    
    +    //
    +    // The connection name in ConnectionStrings configuration that will be used to execute the metadata query. If this value is null, the default connection string will be used.
    +    //
    +    "MetadataQueryConnectionName": null,
    +    //
    +    // Set the search path to this schema that contains the metadata query function. Default is `public`. Default is `public`. Set to null to avoid setting metadata query search path.
    +    //
    +    // This is needed when using non superuser connection roles with limited schema access and mapping the metadata function to a specific schema. 
    +    // If the connection string contains the same "Search Path=" it will be skipped.
    +    //
    +    "MetadataQuerySchema": "public"
    +  }
    +}

    Rate Limiter

    Added comprehensive rate limiting support with integration into ASP.NET Core's built-in rate limiting middleware:

    You can:

    • Configure rate limiting policies middleware manually (for library users).
    • Set rate limiter client configuration policies (for client app users).

    And then:

    • Set default rate limiter policy for all generated endpoints.
    • Set specific endpoint rate limiter policy.
    • Use comment annotation to set endpoint rate limiter policy.

    Client configuration:

    json
    json
    {
    +  //
    +  // Rate Limiter settings to limit the number of requests from clients.
    +  //
    +  "RateLimiterOptions": {
    +    "Enabled": false,
    +    "StatusCode": 429,
    +    "StatusMessage": "Too many requests. Please try again later.",
    +    "DefaultPolicy": null,
    +    // Policy types: FixedWindow, SlidingWindow, BucketWindow, Concurrency
    +    "Policies": [{
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#fixed
    +      "Type": "FixedWindow",
    +      "Enabled": false,
    +      "Name": "fixed",
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#sliding-window-limiter
    +      "Type": "SlidingWindow",
    +      "Enabled": false,
    +      "Name": "sliding",
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "SegmentsPerWindow": 6,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#token-bucket-limiter
    +      "Type": "TokenBucket",
    +      "Enabled": true,
    +      "Name": "bucket",
    +      "TokenLimit": 100,
    +      "ReplenishmentPeriodSeconds": 10,
    +      "QueueLimit": 10,
    +      "AutoReplenishment": true
    +    }, {
    +      // see https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#concurrency-limiter
    +      "Type": "Concurrency",
    +      "Enabled": true,
    +      "Name": "concurrency",
    +      "PermitLimit": 10,
    +      "QueueLimit": 5,
    +      "OldestFirst": true
    +    }]
    +  }
    +}
    • Option to set default policy for all endpoints:
    csharp
    csharp
    /// <summary>
    +/// Default rate limiting policy for all requests. Policy must be configured within application rate limiting options.
    +/// This can be overridden by comment annotations in the database or setting policy for specific endpoints.
    +/// </summary>
    +public string? DefaultRateLimitingPolicy { get; set; } = null;
    • Endpoint property:
    csharp
    csharp
    public string? RateLimiterPolicy { get; set; } = null;
    • Comment annotations:
    code
    rate_limiter_policy_name [ name ]
    +rate_limiter_policy [ name ]
    +rate_limiter [ name ]

    Other Changes and Fixes

    • Major refactoring: generated endpoints moved from custom middleware to minimal APIs. This allows better integration with ASP.NET Core ecosystem (rate limiter, request timeout, etc).
    • Breaking change caused by the above: API invocation to existing paths with wrong HTTP method will return 405 Method Not Allowed instead of 404 Not Found.
    • More testing (concurrency).
    • Fix: fixed excessive logging when retrying failed commands.
    • Fix: missing command logging on void routines.
    • Refactoring: static Options instead of passing Options parameter around.
    • Refactoring: static Logger instead of passing Logger parameter around.
    • NpgsqlRest core project library has set InternalsVisibleTo to NpgsqlRestTests for testability for tests using Options or Logger.
    • Refactoring: moved some files around to better structure the project.
    • Removed unnecessary type casting when routine source returns set with embedded composite type.
    • Fix: fixed incorrect handling of types with modifier (e.g. varchar(100), numeric(10,2), etc). This causes type with modifiers to be serialized as incorrect type.
    • Fix: fixed incorrect parameter logging when parameters were added from user claims as string array (roles, permissions, etc).
    • Fix: user claims mapping to parameters or context will now by default be NULL when claim is null or empty string. Previous behavior was to map empty string as empty string.
    • Remove two logging options: LogEndpointCreatedInfo and LogAnnotationSetInfo. By default, all command parameters and values are logged at Debug level.
    • Refactor comment annotation paring for better maintainability.
    • .NET10 Upgrade.

    Login Endpoint Changes

    • Changed option NpgsqlRestAuthenticationOptions.MessageColumnName to NpgsqlRestAuthenticationOptions.BodyColumnName (and corresponding client configuration option) to better reflect its purpose.
    • Default value of NpgsqlRestAuthenticationOptions.BodyColumnName is now body instead of message.
    • Added new option NpgsqlRestAuthenticationOptions.ResponseTypeColumnName (and corresponding client configuration option) to specify the response type column name for login endpoint. Default is application/json.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.0.1.html b/guide/changelog/v3.0.1.html new file mode 100644 index 000000000..b39966cd5 --- /dev/null +++ b/guide/changelog/v3.0.1.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.0.1 (2025-11-28) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.0.1 (2025-11-28)

    Version 3.0.1 (2025-11-28)

    Full Changelog

    • Fix: fix missing stack trace in AOT builds when exceptions are thrown.
    • Fix: Fix failing Docker JIT image build.
    • Change: removed error mapping for PostgreSQL error code 42883 (undefined_function) from HTTP 404 Not Found. Map it to default HTTP 500 Internal Server Error instead. This was confusing.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.1.0.html b/guide/changelog/v3.1.0.html new file mode 100644 index 000000000..89fe687c5 --- /dev/null +++ b/guide/changelog/v3.1.0.html @@ -0,0 +1,189 @@ + + + + + + Changelog v3.1.0 (2025-12-13) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.1.0 (2025-12-13)

    Version 3.1.0 (2025-12-13)

    Full Changelog

    Http Types

    New feature that enables PostgreSQL functions to make HTTP requests to external APIs by using specially annotated composite types. When a function parameter uses a composite type with an HTTP definition comment, NpgsqlRest automatically invokes the HTTP request and populates the type fields with the response data before executing the function.

    Creating an HTTP Type:

    sql
    sql
    -- Create a composite type with response fields
    +create type weather_api as (
    +    body text,
    +    status_code int,
    +    headers json,
    +    content_type text,
    +    success boolean,
    +    error_message text
    +);
    +
    +-- Add HTTP definition as a comment (RFC 7230 format)
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +timeout 30s';

    Using the HTTP Type in a function:

    sql
    sql
    create function get_weather(
    +  _city text,
    +  _api_key text,
    +  _req weather_api
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if (_req).success then
    +        return (_req).body::json;
    +    else
    +        return json_build_object('error', (_req).error_message);
    +    end if;
    +end;
    +$$;

    HTTP Definition Format:

    The comment on the composite type follows a simplified HTTP message format similar to .http files:

    code
    METHOD URL [HTTP/version]
    +Header-Name: Header-Value
    +...
    +
    +[request body]

    Supported HTTP methods: GET, POST, PUT, PATCH, DELETE

    Timeout Directives:

    Timeout can be specified before the request line using various formats:

    code
    timeout 30
    +timeout 30s
    +timeout 00:00:30
    +@timeout 2 minutes

    Response Fields:

    The composite type fields are automatically populated based on their names (configurable via HttpClientOptions):

    Field NameTypeDescription
    bodytextResponse body content
    status_codeint or textHTTP status code (e.g., 200, 404)
    headersjsonResponse headers as JSON object
    content_typetextContent-Type header value
    successbooleanTrue for 2xx status codes
    error_messagetextError message if request failed

    Placeholder Substitution:

    URLs, headers, and request body in the type comment can contain placeholders in the format {parameter_name}. These placeholders are automatically replaced with the values of other function parameters that share the same name.

    In the example above, the function get_weather has parameters _city and _api_key. The HTTP type comment contains placeholders {_city} and {_api_key} which are substituted with the actual parameter values when the HTTP request is made:

    sql
    sql
    -- Type comment with placeholders
    +comment on type weather_api is 'GET https://api.weather.com/v1/current?city={_city}
    +Authorization: Bearer {_api_key}
    +timeout 30s';
    +
    +-- Function with matching parameter names
    +create function get_weather(
    +  _city text,        -- Value substitutes {_city} placeholder
    +  _api_key text,     -- Value substitutes {_api_key} placeholder
    +  _req weather_api   -- HTTP type parameter (receives response)
    +)
    +...

    When calling GET /api/get-weather?_city=London&_api_key=secret123, NpgsqlRest will:

    1. Substitute {_city} with London and {_api_key} with secret123
    2. Make the HTTP request to https://api.weather.com/v1/current?city=London with header Authorization: Bearer secret123
    3. Populate the _req parameter fields with the response data
    4. Execute the PostgreSQL function

    Configuration Options:

    Enable HTTP Types in NpgsqlRestOptions.HttpClientOptions options or in client configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    //
    +    // HTTP client functionality for annotated composite types.
    +    // Allows PostgreSQL functions to make HTTP requests by using specially annotated types as parameters.
    +    //
    +    "HttpClientOptions": {
    +      //
    +      // Enable HTTP client functionality for annotated types.
    +      //
    +      "Enabled": false,
    +      //
    +      // Default name for the response status code field within annotated types.
    +      //
    +      "ResponseStatusCodeField": "status_code",
    +      //
    +      // Default name for the response body field within annotated types.
    +      //
    +      "ResponseBodyField": "body",
    +      //
    +      // Default name for the response headers field within annotated types.
    +      //
    +      "ResponseHeadersField": "headers",
    +      //
    +      // Default name for the response content type field within annotated types.
    +      //
    +      "ResponseContentTypeField": "content_type",
    +      //
    +      // Default name for the response success field within annotated types.
    +      //
    +      "ResponseSuccessField": "success",
    +      //
    +      // Default name for the response error message field within annotated types.
    +      //
    +      "ResponseErrorMessageField": "error_message"
    +    }
    +  }
    +}

    Routine Caching Improvements

    Major improvements to the routine caching system for reliability, correctness, and expanded functionality:

    Cache Key Generation Fixes:

    • Fixed potential hash collisions by switching from integer hash codes to string-based cache keys.
    • Added separator character (\x1F) between parameter values to prevent cache key collisions when parameter values concatenate to the same string (e.g., "ab" + "c" vs "a" + "bc" now produce different cache keys).
    • Added distinct null marker (\x00NULL\x00) to differentiate between null values and empty strings in cache keys.
    • Fixed array parameter serialization to properly include all array elements in the cache key with separators.

    Extended Caching Support for Records and Sets:

    Caching now works for set-returning functions and record types, not just single scalar values. When a cached function returns multiple rows, the entire result set is cached and returned on subsequent calls.

    New Configuration Option:

    Added MaxCacheableRows option to CacheOptions to limit memory usage when caching large result sets:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// Maximum number of rows that can be cached for set-returning functions.
    +    /// If a result set exceeds this limit, it will not be cached (but will still be returned).
    +    /// Set to 0 to disable caching for sets entirely. Set to null for unlimited (use with caution).
    +    /// Default is 1000 rows.
    +    /// </summary>
    +    public int? MaxCacheableRows { get; set; } = 1000;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CacheOptions": {
    +      "MaxCacheableRows": 1000
    +    }
    +  }
    +}

    Cache Key Hashing for Long Keys:

    Added optional SHA256 hashing for long cache keys to improve performance, especially with Redis cache. When enabled, cache keys exceeding a configurable threshold are automatically hashed to a fixed 64-character string, reducing:

    • Memory usage for storing long cache keys
    • Network transfer overhead with Redis
    • Redis server memory consumption

    New configuration options in CacheOptions:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// When true, cache keys longer than HashKeyThreshold characters are hashed to a fixed-length SHA256 string.
    +    /// This reduces memory usage for long cache keys and improves Redis performance with large keys.
    +    /// Default is false (cache keys are stored as-is).
    +    /// </summary>
    +    public bool UseHashedCacheKeys { get; set; } = false;
    +
    +    /// <summary>
    +    /// Cache keys longer than this threshold (in characters) will be hashed when UseHashedCacheKeys is true.
    +    /// Keys shorter than this threshold are stored as-is for better debuggability.
    +    /// Default is 256 characters.
    +    /// </summary>
    +    public int HashKeyThreshold { get; set; } = 256;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "UseHashedCacheKeys": true,
    +    "HashKeyThreshold": 256
    +  }
    +}

    This is particularly recommended when:

    • Using Redis cache with routines that have many or large parameters
    • Caching routines with long SQL expressions
    • High cache hit rates where memory efficiency matters

    Cache Invalidation Endpoints:

    Added support for programmatic cache invalidation via auto-generated invalidation endpoints. When InvalidateCacheSuffix is configured, NpgsqlRest automatically creates an invalidation endpoint for each cached endpoint.

    For example, if you have a cached endpoint /api/get-user/ and set InvalidateCacheSuffix to "invalidate", NpgsqlRest will create /api/get-user/invalidate endpoint automatically.

    Calling the invalidation endpoint with the same parameters as the cached endpoint removes the corresponding cache entry:

    code
    GET /api/get-user/?id=123           -> Returns cached user data
    +GET /api/get-user/invalidate?id=123 -> Removes cache entry, returns {"invalidated":true}
    +GET /api/get-user/?id=123           -> Fresh data (cache was cleared)

    Key Features:

    • Same authentication and authorization as the original endpoint
    • Same parameter handling - no need to know the internal cache key format
    • Works correctly with hashed cache keys
    • Returns {"invalidated":true} if cache entry was removed, {"invalidated":false} if not found

    Configuration in CacheOptions:

    csharp
    csharp
    public class CacheOptions
    +{
    +    /// <summary>
    +    /// When set, creates an additional invalidation endpoint for each cached endpoint.
    +    /// The invalidation endpoint has the same path with this suffix appended.
    +    /// Default is null (no invalidation endpoints created).
    +    /// </summary>
    +    public string? InvalidateCacheSuffix { get; set; } = null;
    +}

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "InvalidateCacheSuffix": "invalidate"
    +  }
    +}

    Multi-Host Connection Support

    Added support for PostgreSQL multi-host connections with failover and load balancing capabilities using Npgsql's NpgsqlMultiHostDataSource.

    Features:

    • Automatic detection of multi-host connection strings (connection strings with comma-separated hosts like Host=server1,server2)
    • Configurable target session attributes per connection: Any, Primary, Standby, PreferPrimary, PreferStandby, ReadWrite, ReadOnly
    • Seamless integration with existing named connections - multi-host data sources take priority over connection strings

    Configuration:

    json
    json
    {
    +  "ConnectionSettings": {
    +    "MultiHostConnectionTargets": {
    +      // Default target for all multi-host connections
    +      "Default": "Any",
    +      // Per-connection overrides
    +      "ByConnectionName": {
    +        "readonly": "Standby",
    +        "primary": "Primary"
    +      }
    +    }
    +  }
    +}

    Example Multi-Host Connection String:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "default": "Host=primary.db.com,replica1.db.com,replica2.db.com;Database=mydb;Username=app;Password=secret"
    +  }
    +}

    Target Session Attributes:

    ValueDescription
    AnyAny successful connection is acceptable (default)
    PrimaryServer must not be in hot standby mode
    StandbyServer must be in hot standby mode
    PreferPrimaryTry primary first, fall back to any
    PreferStandbyTry standby first, fall back to any
    ReadWriteSession must accept read-write transactions
    ReadOnlySession must not accept read-write transactions

    See Npgsql Failover and Load Balancing for more details.

    New Options Property:

    Added DataSources property to NpgsqlRestOptions for storing multi-host data sources:

    csharp
    csharp
    /// <summary>
    +/// Dictionary of data sources by connection name. This is used for multi-host connection support.
    +/// When a connection name is specified in a routine endpoint, the middleware will first check
    +/// this dictionary for a data source. If not found, it falls back to the ConnectionStrings dictionary.
    +/// </summary>
    +public IDictionary<string, NpgsqlDataSource>? DataSources { get; set; }

    Other Changes and Fixes

    • Fixed default value on ErrorHandlingOptions.RemoveTraceId configuration setting. Default is true as it should be.
    • Fixed PostgreSQL parameter and result type mapping when default search path is not public.
    • Fixed type on TypeScript client generation when returing error. Errors now return JSON object instead of string.
    • Removed options.md, annotations.md, client.md and login-endpoints.md documentation files because dedicated website is now live: https://npgsqlrest.github.io/
    • Added missing CsvUploadKey with value "csv" in NpgsqlRest.UploadOptions.UploadHandlers configuration.
    • Moved authorization check after parameter parsing. This allows for endpoint to return proper 404 response codes when parameter is missing, instead of 400 when authorization fails.
    • When using custom types in PostgreSQL function parameters (composite types, enums, etc), and those parameters are not supplied in the request, they will now default to NULL always. Previous behavior was 404 Not Found when parameter was missing.
    • Fixed debug logging in ErrorHandlingOptions builder.
    • Fixed default mapping in ErrorHandlingOptions builder.
    • Added guard clause that returns error if serviceProvider is null when ServiceProviderMode is set
    • Removed 5 duplicate HttpClientOptions.Enabled blocks (kept 1)
    • Replaced un-awaited transaction?.RollbackAsync() with proper shouldCommit = false and uploadHandler?.OnError() for consistency with other error paths

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.1.1.html b/guide/changelog/v3.1.1.html new file mode 100644 index 000000000..3795811f0 --- /dev/null +++ b/guide/changelog/v3.1.1.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.1.1 (2025-12-15) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.1.1 (2025-12-15)

    Version 3.1.1 (2025-12-15)

    Full Changelog

    • Fixed schema usage for types not in defaults schemas. Narrow types selection for schemas with allowed usage.
    • Improved logging of parameter values in debug mode. Using PostgreSQL literal format for better readability.
    • Added version info log on startup.
    • Added executable location to version info output (--version).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.1.2.html b/guide/changelog/v3.1.2.html new file mode 100644 index 000000000..2e2021eb6 --- /dev/null +++ b/guide/changelog/v3.1.2.html @@ -0,0 +1,68 @@ + + + + + + Changelog v3.1.2 (2025-12-20) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.1.2 (2025-12-20)

    Version 3.1.2 (2025-12-20)

    Full Changelog

    Performance: SIMD-Accelerated String Processing

    Added SIMD (Single Instruction, Multiple Data) optimizations using SearchValues<char> for faster string processing operations. These optimizations leverage hardware vector instructions (AVX2/SSE on x64, AdvSimd on ARM) to process multiple characters simultaneously.

    Optimized operations:

    • PostgreSQL array to JSON conversion (PgArrayToJsonArray): Faster parsing of array delimiters and escape sequences.
    • Composite type/tuple to JSON conversion (PgUnknownToJsonArray): Accelerated tuple field parsing.
    • String quoting and escaping (QuoteText): Vectorized quote detection with fast-path for strings without quotes.
    • Template string formatting (FormatString): SIMD-accelerated brace detection for URL and response templates.
    • Pattern matching (IsPatternMatch): Fast-path for patterns without wildcards and early-exit for non-matching prefixes.

    Where you'll see improvements:

    • APIs returning large PostgreSQL arrays (100+ elements): ~30-50% faster serialization
    • Bulk CSV uploads with many rows: Faster delimiter detection
    • Endpoints with complex URL templates: Reduced template processing overhead
    • High-throughput scenarios: Lower CPU usage per request

    These optimizations are automatic and require no configuration changes. Performance gains scale with input size - small inputs see modest improvements (~10-20%), while large arrays and bulk operations benefit significantly (~40-60%).

    Consistent JSON Error Responses

    All error responses (401 Unauthorized, 403 Forbidden, 404 Not Found, 500 Internal Server Error) now consistently return a JSON body using the RFC 7807 Problem Details format:

    json
    json
    {
    +  "type": null,
    +  "title": "Unauthorized",
    +  "status": 401,
    +  "detail": null
    +}

    Previously, some error responses (particularly authorization failures) returned empty bodies or plain text. Now all endpoints return a consistent, parseable JSON error format regardless of the error type.

    EnvFile Configuration Option

    Added new EnvFile option to the Config section for loading environment variables from a .env file:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": false,
    +    "ParseEnvironmentVariables": true,
    +    "EnvFile": ".env"
    +  }
    +}

    When AddEnvironmentVariables or ParseEnvironmentVariables is true and the EnvFile path is set, the application will load environment variables from the specified file. The file format supports:

    • KEY=VALUE pairs (one per line)
    • Comments (lines starting with #)
    • Quoted values (both single and double quotes)

    Example .env file:

    code
    PGHOST=localhost
    +PGPORT=5432
    +PGDATABASE=example_db
    +PGUSER=postgres
    +PGPASSWORD=postgres

    The variables are loaded into the environment and made available for configuration parsing with the {ENV_VAR_NAME} syntax.

    TsClient: Configurable Error Expression and Type

    Added two new options to the TypeScript client code generator (TsClient) for customizing error handling in generated code:

    • ErrorExpression (default: "await response.json()"): The expression used to parse error responses. Allows customization for different error parsing strategies.
    • ErrorType (default: "{status: number; title: string; detail?: string | null} | undefined"): The TypeScript type annotation for error responses.

    These options are only used when IncludeStatusCode is true. Configuration example:

    json
    json
    {
    +  "ClientCodeGen": {
    +    "IncludeStatusCode": true,
    +    "ErrorExpression": "await response.json()",
    +    "ErrorType": "{status: number; title: string; detail?: string | null} | undefined"
    +  }
    +}

    Void functions and procedures now also return the error object when IncludeStatusCode is true.

    HybridCache Support

    Added HybridCache as a third caching option alongside Memory and Redis. HybridCache uses Microsoft's Microsoft.Extensions.Caching.Hybrid library to provide:

    • Stampede protection: Prevents multiple concurrent requests from hitting the database when cache expires
    • Optional Redis L2 backend: Can use Redis as a distributed secondary cache for sharing across instances
    • In-memory L1 cache: Fast local cache for frequently accessed data

    Configuration in appsettings.json:

    json
    json
    {
    +  "CacheOptions": {
    +    "Enabled": true,
    +    "Type": "Hybrid",
    +    "UseRedisBackend": false,
    +    "RedisConfiguration": "localhost:6379,abortConnect=false",
    +    "MaximumKeyLength": 1024,
    +    "MaximumPayloadBytes": 1048576,
    +    "DefaultExpiration": "5 minutes",
    +    "LocalCacheExpiration": "1 minute"
    +  }
    +}

    Cache types:

    • Memory: In-process memory cache (fastest, single instance only)
    • Redis: Distributed Redis cache (slower, shared across instances)
    • Hybrid: HybridCache with stampede protection, optionally backed by Redis

    When UseRedisBackend is false (default), HybridCache works as an in-memory cache with stampede protection. When true, it uses Redis as the L2 distributed cache for sharing across multiple application instances.

    Fixed IncludeSchemaInNames option to work correctly when UseRoutineNameInsteadOfEndpoint is false (the default).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.1.3.html b/guide/changelog/v3.1.3.html new file mode 100644 index 000000000..a9d0b77de --- /dev/null +++ b/guide/changelog/v3.1.3.html @@ -0,0 +1,71 @@ + + + + + + Changelog v3.1.3 (2025-12-21) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.1.3 (2025-12-21)

    Version 3.1.3 (2025-12-21)

    Full Changelog

    Path Parameters Support

    Added support for RESTful path parameters using the {param} syntax in URL paths. This allows defining routes like /products/{id} where parameter values are extracted directly from the URL path instead of query strings or request body.

    Usage:

    sql
    sql
    -- Single path parameter
    +create function get_product(p_id int) returns text language sql as 'select ...';
    +comment on function get_product(int) is '
    +HTTP GET /products/{p_id}
    +';
    +-- Call: GET /products/123 → p_id = 123
    +
    +-- Multiple path parameters
    +create function get_review(p_id int, review_id int) returns text language sql as 'select ...';
    +comment on function get_review(int, int) is '
    +HTTP GET /products/{p_id}/reviews/{review_id}
    +';
    +-- Call: GET /products/5/reviews/10 → p_id = 5, review_id = 10
    +
    +-- Path parameters with query string parameters
    +create function get_product_details(p_id int, include_reviews boolean default false) returns text language sql as 'select ...';
    +comment on function get_product_details(int, boolean) is '
    +HTTP GET /products/{p_id}/details
    +';
    +-- Call: GET /products/42/details?includeReviews=true → p_id = 42, include_reviews = true
    +
    +-- Path parameters with JSON body (POST/PUT)
    +create function update_product(p_id int, new_name text) returns text language sql as 'select ...';
    +comment on function update_product(int, text) is '
    +HTTP POST /products/{p_id}
    +';
    +-- Call: POST /products/7 with body {"newName": "New Name"} → p_id = 7, new_name = "New Name"

    Key features:

    • Path parameter names in {param} can use either the PostgreSQL name ({p_id}) or the converted camelCase name ({pId}), matching is case-insensitive
    • Works with all HTTP methods (GET, POST, PUT, DELETE)
    • Can be combined with query string parameters (GET/DELETE) or JSON body parameters (POST/PUT)
    • Supports all parameter types (int, text, uuid, bigint, etc.)
    • TsClient generates template literal URLs: `${baseUrl}/products/${request.pId}`
    • New ParamType.PathParam enum value for identifying path-sourced parameters
    • Zero performance impact on endpoints without path parameters

    TsClient Improvements

    • Fixed parseQuery helper being unnecessarily included in generated TypeScript files when all function parameters are path parameters (no query string parameters remain).
    • Added comprehensive test coverage for TsClient TypeScript generation including tests for: path parameters, status code responses, tsclient_parse_url, tsclient_parse_request, file upload endpoints, SSE endpoints, and combined upload+SSE endpoints.

    HybridCache Configuration Keys Renamed

    HybridCache-specific configuration keys in the CacheOptions section have been renamed to include the HybridCache prefix for better clarity and consistency:

    Old KeyNew Key
    UseRedisBackendHybridCacheUseRedisBackend
    MaximumKeyLengthHybridCacheMaximumKeyLength
    MaximumPayloadBytesHybridCacheMaximumPayloadBytes
    DefaultExpirationHybridCacheDefaultExpiration
    LocalCacheExpirationHybridCacheLocalCacheExpiration

    Migration: Update your appsettings.json to use the new key names:

    json
    json
    {
    +  "CacheOptions": {
    +    "Type": "Hybrid",
    +    "HybridCacheUseRedisBackend": false,
    +    "HybridCacheMaximumKeyLength": 1024,
    +    "HybridCacheMaximumPayloadBytes": 1048576,
    +    "HybridCacheDefaultExpiration": "5 minutes",
    +    "HybridCacheLocalCacheExpiration": "1 minute"
    +  }
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.10.0.html b/guide/changelog/v3.10.0.html new file mode 100644 index 000000000..c486d29cf --- /dev/null +++ b/guide/changelog/v3.10.0.html @@ -0,0 +1,135 @@ + + + + + + Changelog v3.10.0 (2026-02-25) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.10.0 (2026-02-25)

    Version 3.10.0 (2026-02-25)

    Full Changelog

    New Feature: Resolved Parameter Expressions

    When using HTTP Client Types, sensitive values like API tokens or secrets are often needed in outgoing HTTP requests (e.g., in an Authorization header). Previously, these values had to be supplied as regular HTTP parameters — exposing them to the client and requiring an insecure round-trip: database → client → server → external API.

    Resolved parameter expressions solve this by allowing function parameters to be resolved server-side via SQL expressions defined in comment annotations. The resolved values are used in HTTP Client Type placeholder substitution (headers, URL, body) and are also passed to the PostgreSQL function — but they never appear in or originate from the client HTTP request.

    How It Works

    If a comment annotation uses the existing key = value syntax and the key matches an actual function parameter name, the value is treated as a SQL expression to execute at runtime:

    sql
    sql
    create type my_api_response as (body json, status_code int);
    +comment on type my_api_response is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_secure_data(
    +    _user_id int,
    +    _req my_api_response,
    +    _token text default null
    +)
    +returns table (body json, status_code int)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code;
    +end;
    +$$;
    +comment on function get_secure_data(int, my_api_response, text) is '
    +_token = select api_token from user_tokens where user_id = {_user_id}
    +';

    The client calls GET /api/get-secure-data/?user_id=42. The server:

    1. Fills _user_id from the query string (value 42).
    2. Executes the resolved expression: select api_token from user_tokens where user_id = $1 (parameterized, with $1 = 42).
    3. Sets _token to the result (e.g., "secret-abc").
    4. Substitutes {_token} in the outgoing HTTP request header: Authorization: Bearer secret-abc.
    5. Makes the HTTP call and returns the response.

    The token never leaves the server. The client never sees it.

    Behavior

    • Server-side only: Resolved parameters cannot be overridden by client input. Even if the client sends &token=hacked, the DB-resolved value is used.
    • NULL handling: If the SQL expression returns no rows or NULL, the parameter is set to DBNull.Value (empty string in placeholder substitution).
    • Name-based placeholders, parameterized execution: Placeholders like {_user_id} reference other function parameters by name — the value is always looked up by name, regardless of position. Internally, placeholders are converted to positional $N parameters for safe execution (preventing SQL injection).
    • Sequential execution: When multiple parameters are resolved, expressions execute one-by-one on the same connection, in annotation order.
    • Works with user_params: Resolved expressions can reference parameters auto-filled from JWT claims via user_params, enabling fully zero-parameter authenticated calls.

    Multiple Resolved Parameters

    Multiple parameters can each have their own resolved expression:

    sql
    sql
    comment on function my_func(text, my_type, text, text) is '
    +_token = select api_token from tokens where user_name = {_name}
    +_api_key = select ''static-key-'' || api_token from tokens where user_name = {_name}
    +';

    Resolved Parameters in URL, Headers, and Body

    Resolved values participate in all HTTP Client Type placeholder locations — URL path segments, headers, and request body templates:

    sql
    sql
    -- URL: GET https://api.example.com/resource/{_secret_path}
    +-- Header: Authorization: Bearer {_token}
    +-- Body: {"token": "{_token}", "data": "{_payload}"}

    New Feature: HTTP Client Type Retry Logic

    When using HTTP Client Types, outgoing HTTP requests to external APIs can fail transiently — rate limiting (429), temporary server errors (503), network timeouts. Previously, a single failure was passed directly to the PostgreSQL function with no opportunity to retry.

    The new @retry_delay directive adds configurable automatic retries with delays, defined in the HTTP type comment alongside existing directives like timeout.

    Syntax

    sql
    sql
    -- Retry on any failure (non-2xx status, timeout, or network error):
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s
    +GET https://api.example.com/data';
    +
    +-- Retry only on specific HTTP status codes:
    +comment on type my_api_type is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';
    +
    +-- Combined with timeout:
    +comment on type my_api_type is 'timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data';

    The delay list defines both the number of retries and the delay before each retry. 1s, 2s, 5s means 3 retries with 1-second, 2-second, and 5-second delays respectively. Delay values use the same format as timeout100ms, 1s, 5m, 30, 00:00:01, etc.

    Behavior

    • Without on filter: Retries on any non-success HTTP response, timeout, or network error.
    • With on filter: Retries only when the HTTP response status code matches one of the listed codes (e.g., 429, 503). Timeouts and network errors always trigger retry regardless of the filter, since they have no status code.
    • Retry exhaustion: If all retries fail, the last error (status code, error message) is passed to the PostgreSQL function — the same as if retries were not configured.
    • Unexpected exceptions: Non-HTTP errors (e.g., invalid URL) are never retried.
    • Parallel execution: Each HTTP type in a function retries independently within its own parallel task. No changes to the parallel execution model.
    • No external dependencies: Built-in retry loop, no Polly or other libraries required. Matches the existing PostgreSQL command retry pattern.

    Example

    sql
    sql
    create type rate_limited_api as (body json, status_code int, error_message text);
    +comment on type rate_limited_api is '@retry_delay 1s, 2s, 5s on 429, 503
    +GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +create function get_rate_limited_data(
    +    _token text,
    +    _req rate_limited_api
    +)
    +returns table (body json, status_code int, error_message text)
    +language plpgsql as $$
    +begin
    +    return query select (_req).body, (_req).status_code, (_req).error_message;
    +end;
    +$$;

    If the external API returns 429 (rate limited), the request is automatically retried after 1s, then 2s, then 5s. If it returns 400 (bad request), no retry occurs and the error is returned immediately.

    New Feature: Data Protection Encrypt/Decrypt Annotations

    Two new comment annotations — encrypt and decrypt — enable transparent application-level column encryption using ASP.NET Data Protection. Parameter values are encrypted before being sent to PostgreSQL, and result column values are decrypted before being returned to the API client. The database stores ciphertext; the API consumer sees plaintext. No pgcrypto or client-side encryption required.

    This is useful for storing PII (SSN, medical records, credit card numbers) or other sensitive data that must be encrypted at rest but is only ever looked up by an unencrypted key (e.g., user_id, patient_id).

    Prerequisite: The DataProtection section must be enabled in appsettings.json (it is by default). The DefaultDataProtector is automatically created from Data Protection configuration and passed to the NpgsqlRest authentication options.

    Encrypt Parameters

    Mark specific parameters to encrypt before they are sent to PostgreSQL:

    sql
    sql
    create function store_patient_ssn(_patient_id int, _ssn text)
    +returns void
    +language plpgsql as $$
    +begin
    +    insert into patients (id, ssn) values (_patient_id, _ssn)
    +    on conflict (id) do update set ssn = excluded.ssn;
    +end;
    +$$;
    +comment on function store_patient_ssn(int, text) is '
    +HTTP POST
    +encrypt _ssn
    +';

    The client calls POST /api/store-patient-ssn/ with {"patientId": 1, "ssn": "123-45-6789"}. The server encrypts _ssn using Data Protection before executing the SQL — the database stores ciphertext like CfDJ8N..., never the plaintext SSN.

    Use encrypt without arguments to encrypt all text parameters:

    sql
    sql
    comment on function store_all_secrets(text, text) is '
    +HTTP POST
    +encrypt
    +';

    Decrypt Result Columns

    Mark specific result columns to decrypt before returning to the client:

    sql
    sql
    create function get_patient(_patient_id int)
    +returns table(id int, ssn text, name text)
    +language plpgsql as $$
    +begin
    +    return query select p.id, p.ssn, p.name from patients p where p.id = _patient_id;
    +end;
    +$$;
    +comment on function get_patient(int) is '
    +decrypt ssn
    +';

    The client calls GET /api/get-patient/?patientId=1. The ssn column is decrypted from ciphertext back to "123-45-6789" before being included in the JSON response. The id and name columns are returned as-is.

    Use decrypt without arguments to decrypt all result columns:

    sql
    sql
    comment on function get_all_secrets(text) is '
    +decrypt
    +';

    Decrypt also works on scalar (single-value) return types:

    sql
    sql
    create function get_secret(_id int) returns text ...
    +comment on function get_secret(int) is 'decrypt';

    Full Roundtrip Example

    sql
    sql
    -- Store with encryption
    +create function store_secret(_key text, _value text) returns void ...
    +comment on function store_secret(text, text) is '
    +HTTP POST
    +encrypt _value
    +';
    +
    +-- Retrieve with decryption
    +create function get_secret(_key text) returns table(key text, value text) ...
    +comment on function get_secret(text) is '
    +decrypt value
    +';
    code
    POST /api/store-secret/  {"key": "api-key", "value": "sk-abc123"}
    +GET  /api/get-secret/?key=api-key  →  {"key": "api-key", "value": "sk-abc123"}

    The value is stored encrypted in PostgreSQL and decrypted transparently on read.

    Annotation Aliases

    AnnotationAliases
    encryptencrypted, protect, protected
    decryptdecrypted, unprotect, unprotected

    Behavior Notes

    • NULL values: NULL parameters are not encrypted (passed as DBNull). NULL columns are not decrypted (returned as JSON null).
    • Non-text types: Only string parameter values are encrypted. Integer, boolean, and other types are unaffected even when encrypt is used without arguments.
    • Decryption failures: If a column value cannot be decrypted (e.g., it was not encrypted, or keys were rotated/lost), the raw value is returned as-is — no error is thrown.
    • Key rotation: ASP.NET Data Protection maintains a key ring. Old keys still decrypt old ciphertext. Keys rotate based on DefaultKeyLifetimeDays (default: 90 days). Persistent key storage (FileSystem or Database) is strongly recommended.
    • Encrypted columns are opaque to PostgreSQL: The database cannot filter, join, sort, or index on encrypted values. Use encryption only for columns that are written and read back, never queried by content.

    Key Management

    Encrypt/decrypt relies on the existing DataProtection configuration in appsettings.json. The encryption keys must be persisted — if keys are lost, encrypted data is permanently unrecoverable.

    Key storage options (DataProtection:Storage):

    StorageDescriptionRecommendation
    "Default"OS default location. On Linux, keys are in-memory only and lost on restart.Windows only
    "FileSystem"Keys persisted to a directory (FileSystemPath). In Docker, use a volume mount.Good for single-instance
    "Database"Keys stored in PostgreSQL via GetAllElementsCommand / StoreElementCommand.Best for multi-instance

    Key rotation (DataProtection:DefaultKeyLifetimeDays, default: 90):

    Data Protection automatically rotates keys. New Protect() calls use the newest key. Old keys remain in the key ring and can still Unprotect() previously encrypted data. This means values encrypted months ago continue to decrypt correctly — the key ring grows over time, it doesn't replace old keys.

    Key encryption at rest (DataProtection:KeyEncryption):

    The keys themselves can be encrypted at rest using "Certificate" (X.509 .pfx file) or "Dpapi" (Windows only). Default is "None".

    Application name isolation (DataProtection:CustomApplicationName):

    The application name acts as an encryption isolation boundary. Different application names produce incompatible ciphertext — they cannot decrypt each other's data. When set to null (default), the current ApplicationName is used.

    Example minimal configuration for production use:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "/var/lib/npgsqlrest/data-protection-keys",
    +    "DefaultKeyLifetimeDays": 90
    +  }
    +}

    Or using database storage:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "GetAllElementsCommand": "select get_data_protection_keys()",
    +    "StoreElementCommand": "call store_data_protection_keys($1,$2)"
    +  }
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.11.0.html b/guide/changelog/v3.11.0.html new file mode 100644 index 000000000..105ec2dcb --- /dev/null +++ b/guide/changelog/v3.11.0.html @@ -0,0 +1,77 @@ + + + + + + Changelog v3.11.0 (2026-03-10) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.11.0 (2026-03-10)

    Version 3.11.0 (2026-03-10)

    Full Changelog

    New Feature: proxy_out Annotation (Post-Execution Proxy)

    A new proxy mode that reverses the existing proxy flow: execute the PostgreSQL function first, then forward the function's result body to an upstream service. The upstream response is returned to the client.

    This enables a common pattern where business logic in PostgreSQL prepares a payload, and an external service performs processing that PostgreSQL cannot do — PDF rendering, image processing, ML inference, email sending, etc.

    Syntax

    code
    @proxy_out [ METHOD ] [ host_url ]

    Also aliased as forward_proxy (with or without @ prefix).

    How It Works

    code
    Client Request → NpgsqlRest
    +  → Execute PostgreSQL function
    +  → Forward function result as request body to upstream service
    +  → Forward original query string to upstream URL
    +  → Return upstream response to client

    Unlike the existing proxy annotation (which forwards the incoming request to upstream), proxy_out forwards the outgoing function result. The original request query string is forwarded to the upstream URL as-is. The client-facing HTTP method and the upstream HTTP method are independent — the client can send a GET while the upstream receives a POST.

    Basic Usage

    sql
    sql
    create function generate_report(report_id int)
    +returns json
    +language plpgsql as $$
    +begin
    +    return json_build_object(
    +        'title', 'Monthly Report',
    +        'data', (select json_agg(row_to_json(t)) from sales t where month = report_id)
    +    );
    +end;
    +$$;
    +
    +comment on function generate_report(int) is 'HTTP GET
    +@proxy_out POST https://render-service.internal/render';

    The client calls GET /api/generate-report/?reportId=3. The server:

    1. Executes generate_report(3) in PostgreSQL.
    2. Takes the returned JSON and POSTs it to https://render-service.internal/render/api/generate-report/?reportId=3 (original query string forwarded).
    3. Returns the upstream response (e.g., a rendered PDF) directly to the client with the upstream's content-type and status code.

    Query String Forwarding

    The original client query string is forwarded to the upstream service as-is. This allows the upstream to receive the same parameters that were used to invoke the function:

    sql
    sql
    create function generate_report(p_format text, p_id int)
    +returns json
    +language plpgsql as $$
    +begin
    +    return json_build_object('id', p_id, 'data', 'report');
    +end;
    +$$;
    +
    +comment on function generate_report(text, int) is 'HTTP GET
    +@proxy_out POST';

    Calling GET /api/generate-report/?pFormat=pdf&pId=123 executes the function, then POSTs the result to the upstream with ?pFormat=pdf&pId=123 appended to the URL.

    HTTP Method Override

    Specify which HTTP method to use for the upstream request:

    sql
    sql
    comment on function my_func() is 'HTTP GET
    +@proxy_out PUT';

    The client sends GET, but the upstream receives PUT with the function's result as the body.

    Custom Host

    Override the default ProxyOptions.Host per-endpoint:

    sql
    sql
    comment on function my_func() is 'HTTP GET
    +@proxy_out POST https://my-other-service.internal';

    Error Handling

    • If the function fails (database error, exception), the error is returned directly to the client — the proxy call is never made.
    • If the upstream fails (5xx, timeout, connection error), the upstream's error status and body are forwarded to the client (502 for connection errors, 504 for timeouts).

    Configuration

    Uses the same ProxyOptions configuration as the existing proxy annotation. ProxyOptions.Enabled must be true:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "https://api.example.com",
    +      "DefaultTimeout": "30 seconds"
    +    }
    +  }
    +}

    Performance

    • Zero overhead for non-proxy_out endpoints. The implementation adds only branch-not-taken boolean/null checks on the normal execution path (~4 nanoseconds).
    • Efficient byte forwarding. Function output is captured as raw bytes and forwarded directly via ByteArrayContent — no intermediate string allocation or double UTF-8 encoding.

    TsClient: proxy_out Endpoint Support

    The TypeScript client generator (NpgsqlRest.TsClient) now recognizes proxy_out endpoints and generates functions that return the raw Response object instead of a typed return value. Since the actual response comes from the upstream proxy service (not from the PostgreSQL function's return type), the generated function returns Promise<Response>, allowing the caller to handle the response appropriately (.json(), .blob(), .text(), etc.):

    typescript
    typescript
    // Generated for a proxy_out endpoint
    +export async function generateReport() : Promise<Response> {
    +    const response = await fetch(baseUrl + "/api/generate-report", {
    +        method: "GET",
    +    });
    +    return response;
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.11.1.html b/guide/changelog/v3.11.1.html new file mode 100644 index 000000000..8adc7a0e7 --- /dev/null +++ b/guide/changelog/v3.11.1.html @@ -0,0 +1,52 @@ + + + + + + Changelog v3.11.1 (2026-03-13) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.11.1 (2026-03-13)

    Version 3.11.1 (2026-03-13)

    Full Changelog

    TsClient: proxy Passthrough Endpoint Support

    The TypeScript client generator (NpgsqlRest.TsClient) now recognizes proxy passthrough endpoints and generates functions that return the raw Response object, matching the existing proxy_out behavior. Previously, passthrough proxy endpoints (which typically use returns void) would generate Promise<void>, which was incorrect since the actual response comes from the upstream service.

    Now, both proxy and proxy_out endpoints generate Promise<Response>:

    typescript
    typescript
    // Generated for a proxy passthrough endpoint
    +export async function tsclientTestProxyPassthrough() : Promise<Response> {
    +    const response = await fetch(baseUrl + "/api/tsclient-test/proxy-passthrough", {
    +        method: "GET",
    +    });
    +    return response;
    +}

    This allows callers to handle the upstream response appropriately (.json(), .blob(), .text(), etc.), just like proxy_out endpoints.

    authorize Annotation Now Matches User ID and User Name Claims

    The authorize comment annotation previously only matched against role claims (DefaultRoleClaimType). It now also matches against user ID (DefaultUserIdClaimType) and user name (DefaultNameClaimType) claims, aligning with the behavior that sse_scope authorize already had.

    This means you can now restrict endpoint access to specific users, not just roles:

    sql
    sql
    -- Authorize by role (existing behavior)
    +comment on function get_reports() is 'authorize admin';
    +
    +-- Authorize by user name (new)
    +comment on function get_my_profile() is 'authorize john';
    +
    +-- Authorize by user ID (new)
    +comment on function get_account() is 'authorize user123';
    +
    +-- Mix of roles and user identifiers (new)
    +comment on function get_data() is 'authorize admin, user123, jane';

    The SSE matching scope was also aligned to check all three claim types, making authorization behavior consistent across all features.


    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.12.0.html b/guide/changelog/v3.12.0.html new file mode 100644 index 000000000..266f57065 --- /dev/null +++ b/guide/changelog/v3.12.0.html @@ -0,0 +1,257 @@ + + + + + + Changelog v3.12.0 (2026-03-23) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.12.0 (2026-03-23)

    Version 3.12.0 (2026-03-23)

    Full Changelog


    New Endpoint Source Plugin: NpgsqlRest.SqlFileSource

    In addition to the existing endpoint sources — RoutineSource (PostgreSQL functions and procedures) and CrudSource (tables and views) — NpgsqlRest now supports a third source: SQL files.

    Generate REST API endpoints directly from .sql files. Place SQL files in a configured directory, and NpgsqlRest creates endpoints automatically — no PostgreSQL functions needed.

    How It Works

    1. At startup, the plugin scans the directory matching the configured glob pattern (e.g., sql/**/*.sql)
    2. Each .sql file is parsed: comments are extracted as annotations, SQL is split into statements
    3. Each statement is analyzed via PostgreSQL's wire protocol (SchemaOnly) — parameter types and return columns are inferred without executing the query
    4. A REST endpoint is created for each file, with the URL path derived from the filename

    Single-Command Files

    A file with one SQL statement produces a standard endpoint:

    sql
    sql
    -- sql/get_reports.sql
    +-- HTTP GET
    +-- @param $1 from_date
    +-- @param $2 to_date
    +-- @authorize admin
    +SELECT id, title, created_at
    +FROM reports
    +WHERE created_at BETWEEN $1 AND $2;

    GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31[{"id": 1, "title": "Q1", "createdAt": "..."}]

    HTTP verb auto-detection (when no explicit HTTP annotation):

    SQL StatementHTTP VerbRationale
    SELECT / WITH ... SELECTGETRead-only
    INSERTPUTCreation
    UPDATEPOSTModification
    DELETEDELETERemoval
    DO $$ ... $$POSTAnonymous script
    Mixed mutationsMost destructive winsDELETE > POST > PUT

    An explicit HTTP GET, HTTP POST, etc. annotation always overrides auto-detection.

    Note: DO blocks do not support $N parameters — this is a PostgreSQL language limitation. A DO block always produces a parameterless endpoint. In multi-command files, DO blocks work alongside parameterized statements — the other commands receive the shared parameters, the DO block receives none.

    Multi-Command Files

    A file with multiple statements (separated by ;) returns a JSON object. Each key corresponds to one command's result:

    sql
    sql
    -- sql/process_order.sql
    +-- HTTP POST
    +-- @param $1 order_id
    +-- @result validate
    +SELECT count(*) FROM orders WHERE id = $1;
    +UPDATE orders SET status = 'processing' WHERE id = $1;
    +-- @result confirm
    +SELECT id, status FROM orders WHERE id = $1;

    POST /api/process-order with {"order_id": 42}

    json
    json
    {
    +  "validate": [1],
    +  "result2": 1,
    +  "confirm": [{"id": 42, "status": "processing"}]
    +}

    Result set rules:

    • Commands returning rows → JSON array of row objects (same format as single-command endpoints)
    • Void commands (INSERT/UPDATE/DELETE without RETURNING) → rows-affected count as integer
    • Multi-command endpoints are never void — they always return a JSON object

    Result naming:

    • Default keys: result1, result2, result3, ... (prefix configurable via ResultPrefix setting)
    • Override with the positional @result annotation placed before the statement it applies to, or inline after the semicolon:
      • @result validate — renames the next result to validate
      • @result is validate — same ("is" style)
      • Commands without @result keep their default name

    Execution:

    • Uses NpgsqlBatch with one NpgsqlBatchCommand per statement — single database round-trip
    • All statements share the same parameters ($1, $2, etc.) — user sends each parameter once
    • Full retry logic via ExecuteBatchReaderWithRetryAsync with error code mapping and timeout handling
    • If any command fails, the entire request fails — no partial results

    Parameters

    SQL files use PostgreSQL positional parameters ($1, $2, ...). Parameters are passed via query string (GET) or JSON body (POST/PUT/DELETE):

    code
    GET /api/my-query?$1=hello&$2=42
    +POST /api/my-mutation {"$1": "hello", "$2": 42}

    Use the @param annotation for better names:

    sql
    sql
    -- @param $1 user_name
    +-- @param $2 age
    +SELECT * FROM users WHERE name = $1 AND age > $2;

    Now: GET /api/my-query?user_name=hello&age=42

    For multi-command files: Each statement is described individually. Parameter types are merged across all statements:

    • Same $N with same type across statements → use that type
    • Same $N with conflicting types → startup error with clear message (override with @param $1 name type)
    • $N used in only some statements → type from the statement(s) that reference it

    Virtual Parameters

    Use @define_param to create HTTP parameters that are NOT bound to the PostgreSQL command. These parameters exist for HTTP request matching, custom parameter placeholders, and claim mapping — without appearing in the SQL query.

    Use case: custom parameter placeholders — pass HTTP parameters that control endpoint behavior (e.g., output format) without referencing them in SQL:

    sql
    sql
    -- sql/users_report.sql
    +-- @define_param format text
    +-- @table_format = {format}
    +-- @param $1 department_id
    +SELECT id, name, email FROM users WHERE department_id = $1;

    GET /api/users-report?department_id=5&format=html_table — the format parameter feeds into the table_format custom parameter via {format} placeholder, selecting the output format (JSON, HTML table, etc.) without being part of the SQL query.

    Use case: claim mapping — auto-fill from user claims without SQL reference:

    sql
    sql
    -- @authorize
    +-- @user_parameters
    +-- @define_param _user_id
    +SELECT * FROM user_data;

    Default type is text; specify a type with @define_param name type.

    Comments and Annotations

    All comments in the SQL file are parsed as annotations, just like COMMENT ON FUNCTION in PostgreSQL:

    sql
    sql
    -- Line comments are annotations
    +/* Block comments are annotations too */
    +SELECT * FROM table;
    +-- Comments after statements also work

    All existing NpgsqlRest annotations work: @authorize, @allow_anonymous, @tag, @sse, @request_param_type, @path, @timeout, @cached, @raw, @header, @separator, @login, @logout, @encrypt, @decrypt, etc.

    New annotations for SQL files:

    AnnotationDescriptionExample
    @param $N nameRename positional parameter-- @param $1 user_id
    @param $N name typeRename + retype parameter-- @param $1 user_id integer
    @param $N is nameRename ("is" style)-- @param $1 is user_id
    @result nameRename multi-command result key (positional)-- @result validate
    @result is nameRename result key ("is" style, positional)-- @result is validate
    @define_param name [type]Define virtual parameter (not bound to SQL)-- @define_param _user_id

    CommentScope setting controls which comments are parsed:

    • All (default) — every comment in the file, regardless of position
    • Header — only comments before the first SQL statement

    Wire Protocol Introspection

    At startup, each statement is analyzed via PostgreSQL's Parse → Describe → Sync cycle (CommandBehavior.SchemaOnly):

    • Parameter types inferred from ParameterDescription message (authoritative OIDs)
    • Return columns inferred from RowDescription message (column names and types)
    • No query planning, no execution — roughly the cost of SELECT 1
    • Uses reader.GetName() / reader.GetDataTypeName() instead of GetColumnSchema() to avoid .NET type mapping failures for custom composite types
    • Unknown type OIDs (custom types returning "-.-") resolved via pg_catalog.pg_type query

    Custom / Composite Type Support

    Composite type columns in SQL file endpoints behave the same way as routine endpoints (functions and procedures) — flat by default, nested with the @nested annotation or NestedJsonForCompositeTypes setting. Arrays of composite types are also supported.

    Unnamed and Duplicate Columns

    SQL without column aliases:

    sql
    sql
    SELECT $1, $2;

    Produces valid JSON with unique fallback names instead of duplicate ?column? keys:

    json
    json
    [{"column1": "hello", "column2": "world"}]

    Use AS aliases for meaningful names: SELECT $1 AS name, $2 AS value.

    URL Path Derivation

    The endpoint path is derived from the filename (without .sql extension) using the same NameConverter as functions. For example, with the default camelCase converter:

    • get_reports.sql/api/get-reports
    • user_profile.sql/api/user-profile

    Override with the @path annotation: -- @path /custom/path/{id}

    Error Handling

    ModeBehaviorUse Case
    ParseErrorMode.Exit (default)Logs error, exits processFail-fast — catches SQL errors at startup
    ParseErrorMode.SkipLogs error, skips file, continuesProduction — tolerate partial failures

    All SQL file errors are logged at Error level. In Exit mode, a Critical log explains the exit and how to switch to Skip mode. PostgreSQL errors include compiler-like formatting with line:column position, source line excerpt, and a caret pointing at the error location:

    code
    SqlFileSource: /path/to/get-posts.sql:
    +error 42703: column u.id does not exist
    +  at line 3, column 12
    +  select u.id, u.name from users u
    +             ^

    A warning is logged when the configured file pattern matches no files.

    Errors caught at startup:

    • Parse errors (malformed SQL, unclosed strings/quotes)
    • Describe errors (PostgreSQL syntax errors, invalid table/column references)
    • Parameter type conflicts in multi-command files

    Feature Parity

    SQL file endpoints support all features available to function/procedure endpoints:

    • Composite type expansion (flat by default, nested with @nested annotation)
    • Response caching (cached, cache_expires_in)
    • Raw mode (raw, raw_value_separator, raw_new_line_separator, raw_column_names)
    • Binary mode
    • Encryption/decryption (encrypt, decrypt)
    • Table format handlers (e.g., HTML table output)
    • SSE events
    • Authorization (authorize, allow_anonymous)
    • Custom headers (header)
    • Retry logic with error code mapping
    • Buffer rows configuration
    • HTTP client types (@param $1 name http_type_name — composite type parameters with HTTP definitions)
    • Self-referencing HTTP client types — relative paths (e.g., GET /api/endpoint) call back to the same server instance, enabling parallel internal endpoint composition

    Configuration Reference

    json
    json
    "NpgsqlRest": {
    +  "SqlFileSource": {
    +    "Enabled": true,
    +    "FilePattern": "sql/**/*.sql",
    +    "CommentsMode": "ParseAll",
    +    "CommentScope": "All",
    +    "ErrorMode": "Exit",
    +    "ResultPrefix": "result",
    +    "UnnamedSingleColumnSet": true,
    +    "NestedJsonForCompositeTypes": false
    +  }
    +}
    SettingTypeDefaultDescription
    EnabledboolfalseEnable or disable SQL file source endpoints
    FilePatternstring""Glob pattern for SQL files. Supports *, ** (recursive), ?. Empty = disabled
    CommentsModeenumOnlyWithHttpTagOnlyWithHttpTag = requires explicit HTTP annotation. ParseAll = every file becomes an endpoint
    CommentScopeenumAllAll = parse all comments. Header = only before first statement
    ErrorModeenumExitExit = log error + exit process. Skip = log error + continue
    ResultPrefixstring"result"Prefix for multi-command result keys (e.g., result1, result2)
    UnnamedSingleColumnSetbooltrueSingle-column queries return flat arrays (["a","b"]) instead of object arrays ([{"col":"a"},{"col":"b"}]). Applies to both single-command and per-result in multi-command files. Matches function behavior for setof single values
    NestedJsonForCompositeTypesboolfalseWhen true, composite type columns are serialized as nested JSON objects under their column name. When false (default), composite fields are flattened inline — matching routine behavior. Can also be enabled per-endpoint with the nested annotation

    New Annotations


    New Core Annotation: @param / @parameter — Rename and Retype Parameters

    A new comment annotation that renames and optionally retypes individual parameters. Works on all endpoint types — functions, procedures, CRUD, and SQL file endpoints.

    Positional parameters ($1, $2) already work as HTTP parameter names (?$1=value), but this annotation provides better API ergonomics:

    sql
    sql
    -- Simplest form: rename only
    +-- @param $1 user_id
    +
    +-- Simplest form: rename + retype
    +-- @param $1 user_id integer
    +
    +-- "is" style: rename only (consistent with existing @param X is hash of Y)
    +-- @param $1 is user_id
    +
    +-- "is" style: rename + retype
    +-- @param $1 is user_id integer
    +
    +-- Rename named parameters (works on function/procedure params too)
    +-- @param _old_name better_name
    +-- @param _old_name better_name text

    All forms coexist with existing @param X is hash of Y and @param X is upload metadata handlers without ambiguity. Both @param and @parameter (long form) are supported.


    @param Default Values for SQL File Parameters

    SQL file parameters can now have default values via the @param annotation. When a parameter with a default is not provided in the request, the default value is bound instead of returning 404.

    This is essential for SQL files because positional parameters ($1, $2) must always be bound — unlike PostgreSQL functions where the engine applies its own defaults.

    Syntax:

    sql
    sql
    -- Separate annotations (rename first, then set default):
    +-- @param $1 user_id
    +-- @param user_id default null
    +
    +-- Combined rename + default on a single line:
    +-- @param $1 user_id default null
    +
    +-- "is" style rename + default:
    +-- @param $1 is user_id default null
    +
    +-- Rename + retype + default:
    +-- @param $1 user_id integer default 42
    +-- @param $1 is user_id integer default 42
    +
    +-- Default without rename:
    +-- @param $1 default 'fallback'
    +
    +-- Various value types:
    +-- @param $1 status default 'active'     -- text (single-quoted)
    +-- @param $1 amount default 42           -- number
    +-- @param $1 enabled default true        -- boolean
    +-- @param $1 filter default null         -- SQL NULL (unquoted)
    +-- @param $1 tag default 'null'          -- literal text "null" (quoted)
    +-- @param $1 val default                 -- no value = NULL

    Value parsing rules (SQL conventions):

    • Unquoted null (case-insensitive) → DBNull.Value
    • Single-quoted 'text value' → string literal (supports multi-word)
    • Unquoted value → raw string (Npgsql handles type conversion via NpgsqlDbType)

    Real-world example — user identity endpoint with claim-filled parameters that fall back to NULL:

    sql
    sql
    /* HTTP GET
    +@authorize
    +@user_parameters
    +@param $1 _user_id default null
    +@param $2 _username default null
    +@param $3 _email default null
    +*/
    +select $1 as user_id, $2 as username, $3 as email;

    When authenticated, claims fill the parameters automatically. The defaults ensure the parameters are always bindable.

    Effects on generated output:

    • TsClient: Parameters with defaults get ? suffix in TypeScript interfaces (optional)
    • OpenAPI: Parameters with defaults are marked required: false

    @param Rename Validation

    Parameter names are now validated when renaming via @param. Invalid renames are rejected with a warning log instead of silently creating broken endpoints.

    Rules:

    • Must be a valid PostgreSQL identifier: starts with letter or _, followed by letters, digits, _, or $
    • Positional parameters ($1, $2) are allowed
    sql
    sql
    -- Valid:
    +-- @param $1 user_id        ✓
    +-- @param $1 _val$1         ✓
    +
    +-- Rejected (with warning log):
    +-- @param $1 1bad           ✗ starts with digit
    +-- @param $1 my-param       ✗ invalid character (hyphen)

    @param Default Value: = Alias for default

    The @param annotation now accepts = as a shorthand for default when setting default values:

    sql
    sql
    -- These are equivalent:
    +-- @param $1 _user_id text default null
    +-- @param $1 _user_id text = null
    +
    +-- Works with any value type:
    +-- @param $1 user_id integer default 42
    +-- @param $1 user_id integer = 42
    +
    +-- Also works with standalone default:
    +-- @param my_name = 'hello'
    +
    +-- And "is" style:
    +-- @param $1 is greeting = 'hey'

    @param Type Hints for SQL File Describe

    When a SQL file parameter has an explicit type in the @param annotation (e.g., @param $1 name text), that type is now used during the PostgreSQL Describe step instead of Unknown. This fixes startup errors like 42P18: could not determine data type of parameter $1 that occurred when PostgreSQL's parser couldn't infer the parameter type from context alone — for example, in select set_config('key', $1, true).


    New Positional Annotation: @returns — Skip Describe and Declare Return Type

    New positional annotation @returns that skips the PostgreSQL Describe step entirely for a statement. The SQL is never sent to PostgreSQL during startup. Supports three forms:

    • @returns <composite_type> — resolve columns from the composite type definition
    • @returns <scalar_type> — declare a single typed column (e.g., integer, text, boolean). Only the first column is used at runtime.
    • @returns void — no columns, void result

    Composite type example (temp tables created at runtime):

    sql
    sql
    -- HTTP GET
    +-- @param $1 val1 text
    +-- @param $2 val2 integer
    +begin;
    +select set_config('app.val1', $1, true); -- @skip
    +select set_config('app.val2', $2::text, true); -- @skip
    +do $$ begin
    +    create temp table _result on commit drop as
    +    select current_setting('app.val1') as val1,
    +           current_setting('app.val2')::int as val2;
    +end; $$;
    +-- @returns my_result_type
    +-- @result data
    +-- @single
    +select * from _result;
    +end;

    Without @returns, the select * from _result statement fails during startup Describe because the temp table doesn't exist yet. With @returns my_result_type, the columns are resolved from the composite type definition in pg_catalog instead.

    Scalar type example — declare a single typed column, extra columns ignored:

    sql
    sql
    -- @returns integer
    +-- @single
    +select count(*) from users;

    Returns bare 42 instead of [{"count": 42}].

    Void example — no results, skipping Describe entirely:

    sql
    sql
    -- @returns void
    +select set_config('key', $1, false);

    In multi-command files, void statements produce a rows-affected count. For single-command files, it makes the entire endpoint void (204 No Content).

    The Describe step is completely skipped for annotated statements — the SQL is never sent to PostgreSQL during startup. The composite type must exist in the database at startup. If the type is not found, an error is logged and the file is skipped or the process exits (depending on ErrorMode).


    New Annotation: @void — Force Void Response

    New comment annotation void (alias: void_result) that forces an endpoint to return 204 No Content instead of a JSON response. All statements are executed for side effects only.

    This is particularly useful for multi-command SQL files where all statements are side-effect-only (e.g., set_config calls followed by a DO block):

    sql
    sql
    /* HTTP POST
    +@void
    +@param $1 message_text text
    +@param $2 _user_id text = null
    +*/
    +select set_config('app.message', $1, true);
    +select set_config('app.user_id', $2, true);
    +do $$ begin
    +    -- use current_setting() to read params inside DO block
    +    insert into messages (user_id, text)
    +    values (current_setting('app.user_id')::int, current_setting('app.message'));
    +end; $$;

    Without @void, this multi-command endpoint would return {"result1":"...","result2":"...","result3":-1}. With @void, it returns 204 — no JSON, no need to add @skip to every statement.

    Works on all endpoint types: functions, procedures, CRUD, and SQL file endpoints.


    New Comment Annotation: @single

    New comment annotation single (aliases: single_record, single_result) that returns a single record as a JSON object instead of a JSON array.

    Works across all endpoint sources: PostgreSQL functions, SQL files, and CRUD endpoints.

    Usage:

    sql
    sql
    -- PostgreSQL function
    +CREATE FUNCTION get_user(int) RETURNS TABLE(id int, name text) ...
    +COMMENT ON FUNCTION get_user IS 'HTTP GET
    +@single';
    +
    +-- SQL file
    +-- HTTP GET
    +-- @single
    +-- @param $1 id
    +SELECT id, name FROM users WHERE id = $1;

    Without @single: [{"id": 1, "name": "alice"}] (array) With @single: {"id": 1, "name": "alice"} (object)

    Behavior:

    • Multi-column results return a JSON object (no array wrapping)
    • Single unnamed column results return a bare JSON value (e.g., "hello", 42)
    • If the query returns multiple rows, only the first row is returned (early exit from rendering loop)
    • Empty results respect the response_null annotation: empty_string (default), null_literal, or no_content (204)
    • TypeScript client generates Promise<IResponse> instead of Promise<IResponse[]>

    Per-command @single in multi-command files:

    In multi-command SQL files, @single is positional — it applies to the next statement below it:

    sql
    sql
    -- HTTP POST
    +-- @param $1 id
    +-- @single
    +SELECT id, name FROM users WHERE id = $1;
    +UPDATE orders SET status = 'done' WHERE id = $1;
    +-- @single
    +SELECT id, status FROM orders WHERE id = $1;

    Result: {"result1": {"id": 1, "name": "alice"}, "result2": 1, "result3": {"id": 1, "status": "done"}}

    • First and third commands return objects (have @single above them)
    • Second command returns rows-affected count (void, no @single)
    • Empty per-command @single results render as null

    Positional @result Annotation for Multi-Command Files

    Result keys in multi-command SQL files are named positionally. Annotations can be placed in two ways:

    Before the statement (on a separate line) — applies to the next statement below:

    sql
    sql
    -- @result users
    +SELECT id, name FROM users;
    +-- @result orders
    +SELECT id, total FROM orders;

    Inline after the semicolon (on the same line) — applies to the statement on that line:

    sql
    sql
    SELECT id, name FROM users; -- @result users
    +SELECT id, total FROM orders; -- @result orders

    Both produce: {"users": [...], "orders": [...]}

    This same placement rule applies to all positional annotations: @result, @single, and @skip.

    • @result name — names the result key for the associated statement
    • @result is name — "is" syntax also supported
    • Commands without @result get auto-generated keys: result1, result2, etc.

    SkipNonQueryCommands Setting and @skip Annotation

    SkipNonQueryCommands (default: true)

    Non-query commands in multi-command SQL files are now automatically excluded from the JSON response while still being executed. This eliminates noise like "result1": -1 from transaction control and session statements.

    Affected commands: BEGIN, COMMIT, END, ROLLBACK, SAVEPOINT, RELEASE, SET, RESET, DO blocks, DISCARD, LOCK, LISTEN, NOTIFY, DEALLOCATE.

    sql
    sql
    -- HTTP POST
    +-- @param $1 id
    +BEGIN;
    +UPDATE users SET active = true WHERE id = $1;
    +COMMIT;
    +-- @result verification
    +SELECT id, active FROM users WHERE id = $1;

    Before (without SkipNonQueryCommands):

    json
    json
    {"result1":-1,"result2":1,"result3":-1,"verification":[{"id":1,"active":true}]}

    After (with SkipNonQueryCommands, default):

    json
    json
    {"result1":1,"verification":[{"id":1,"active":true}]}

    Skipped commands don't consume result numbers — the UPDATE gets result1, not result2.

    DML commands (INSERT, UPDATE, DELETE) are NOT skipped — their rows-affected count is meaningful.

    Set "SkipNonQueryCommands": false in SqlFileSource configuration to disable.

    @skip Annotation (aliases: @skip_result, @no_result)

    For cases not covered by SkipNonQueryCommands, use the @skip positional annotation to explicitly exclude any statement from the response:

    sql
    sql
    -- @skip
    +do $$ begin perform pg_notify('channel', 'event'); end; $$;
    +-- @result data
    +SELECT id, name FROM users;

    Result: {"data": [...]}


    New Core Annotation: @internal / @internal_only

    Mark an endpoint as internal-only — accessible via self-referencing calls (proxy, HTTP client types) but NOT exposed as a public HTTP route:

    sql
    sql
    -- Helper endpoint: returns data but is not callable from outside
    +create function get_cached_rates()
    +returns json language sql as $$
    +    select rates from exchange_rates order by fetched_at desc limit 1
    +$$;
    +comment on function get_cached_rates() is 'HTTP GET
    +@internal';
    +
    +-- Public endpoint that composes the internal one
    +create function convert_currency(_amount numeric, _from text, _to text)
    +returns json language plpgsql as $$
    +...
    +$$;
    +comment on function convert_currency(numeric, text, text) is 'HTTP GET
    +@proxy GET /api/get-cached-rates';

    Direct HTTP call to /api/get-cached-rates returns 404. But @proxy GET /api/get-cached-rates and HTTP client types with relative paths can still invoke it internally.

    Works on all endpoint sources: functions, procedures, tables/views (CRUD), and SQL files.


    HTTP Custom Types & Self-Referencing Calls


    Self-Referencing Calls: Relative Path Support for Proxy and HTTP Client Types

    Both @proxy annotations and HTTP client type definitions now support relative paths that call back to the same server instance:

    sql
    sql
    -- Proxy to another endpoint on the same server
    +comment on function my_aggregator() is 'HTTP GET
    +@proxy POST /api/data-source';
    +
    +-- HTTP client type calling a local endpoint
    +comment on type local_api as 'POST /api/process';

    Parallel query composition: Combined with HTTP client types that execute all requests in parallel (Task.WhenAll), this enables a single endpoint to fan out to multiple internal endpoints simultaneously — effectively running parallel queries without client-side orchestration:

    sql
    sql
    -- Two HTTP types calling different internal endpoints
    +create type api_users as (body text);
    +comment on type api_users is 'GET /api/users';
    +
    +create type api_orders as (body text);
    +comment on type api_orders is 'GET /api/orders';
    +
    +-- Function that composes both in parallel
    +create function get_dashboard(
    +    _users api_users,
    +    _orders api_orders
    +) returns json language plpgsql as $$
    +begin
    +    return json_build_object('users', (_users).body::json, 'orders', (_orders).body::json);
    +end;
    +$$;
    +-- One request → two parallel internal calls → combined response

    Configuration:

    • HttpClientOptions.SelfBaseUrl / ProxyOptions.SelfBaseUrl — explicit base URL for relative path resolution (auto-detected from server addresses when not set)
    • In production, relative paths resolve via loopback HTTP to the server's own address
    • In test environments, SetSelfClient() injects an in-memory handler that bypasses the network entirely

    Internal Self-Call Optimization: Zero HTTP Overhead

    Self-referencing endpoints (HTTP client types and proxy definitions with relative paths like /api/endpoint) now bypass the HTTP stack entirely. Instead of making a loopback HTTP call through TCP, the endpoint handler is invoked directly in-process via InternalRequestHandler.

    This enables efficient parallel query composition: a single endpoint can fan out to multiple internal endpoints in parallel (via Task.WhenAll), collect the results, and combine them — all without network overhead. Use cases include:

    • Parallel data aggregation across multiple queries
    • Orchestrating multiple mutations in a single request
    • Composing responses from several independent data sources

    Performance: Microseconds instead of milliseconds per internal call. No TCP connection, no HTTP parsing, no serialization/deserialization at the transport layer.

    Internal handler routing now matches by HTTP method + path (e.g., GET /api/data) instead of path alone. Two endpoints with the same path but different methods (GET vs POST) are correctly distinguished for internal calls.


    Composite Type Parameters in SQL Files — No SQL Rewriting

    Composite type parameters in SQL files are now passed as single text values instead of being expanded into multiple parameters with ROW() SQL rewriting. The SQL stays exactly as the user wrote it.

    HTTP custom type parameters (auto-filled from HTTP calls):

    sql
    sql
    -- @param $1 _response example_9.exchange_rate_api
    +select ($1::example_9.exchange_rate_api).body;

    The framework makes the HTTP call and passes the result as a single composite text value. No SQL rewriting.

    Client-sent composite type parameters:

    sql
    sql
    -- @param $1 data my_composite_type
    +select ($1::my_composite_type).field1, ($1::my_composite_type).field2;

    The client sends the composite value as PostgreSQL composite text format: ?data=("val1","val2"). The SQL casts it with $1::my_composite_type.

    Unknown types in @param annotations now produce a warning log instead of silently falling back to unknown.


    Configuration Changes


    RoutineSource: Enabled Configuration Option

    The RoutineOptions section now supports an Enabled setting (default true). Set to false to disable automatic endpoint creation from PostgreSQL functions and procedures:

    json
    json
    "RoutineOptions": {
    +  "Enabled": false
    +}

    This is useful for SQL-files-only deployments where the overhead of querying the PostgreSQL catalog for routines is unnecessary.


    CrudSource Disabled by Default

    The CrudSource:Enabled setting now defaults to false (was true).

    CrudSource auto-generates CRUD endpoints for all PostgreSQL tables and views, which is rarely desired in production without explicit configuration. Users who need CRUD endpoints should explicitly set "CrudSource": { "Enabled": true }.


    CrudSource No Longer Blocks SqlFileSource

    Previously, when CrudSource was disabled (or its config section was missing), CreateEndpointSources() returned early, preventing SqlFileSource from being registered. All three endpoint sources (RoutineSource, CrudSource, SqlFileSource) are now independently enabled/disabled.


    DataProtection Disabled by Default

    The DataProtection:Enabled setting now defaults to false (was true).

    DataProtection is only needed when using Cookie Authentication, Antiforgery tokens, or @encrypt/@decrypt annotations — all of which are themselves disabled by default. Enabling it unconditionally added unnecessary key management overhead and, on Linux/Docker with Storage: "Default", caused silent key loss on restart (invalidating auth cookies without warning).

    Users who enable Auth, Antiforgery, or encrypt/decrypt annotations should explicitly set "DataProtection": { "Enabled": true } and choose an appropriate storage mode.


    SqlFileSource:LogCommandText Setting

    New setting LogCommandText in the SqlFileSource configuration (default false) controls whether multi-command SQL file endpoints include the full SQL text in debug command logs. When false, only the file path and statement count are logged:

    code
    [DBG] -- POST http://127.0.0.1:8080/api/send-message
    +-- $1 text = 'hello'
    +SQL file: sql/send-message.sql (5 statements)

    When true, the full SQL body is logged (previous behavior). Single-command SQL file endpoints always log the SQL text regardless of this setting. This only applies when LogCommands is true.


    TsClient Improvements


    TsClient: Composite Type Support for SQL Files

    The TypeScript client generator now correctly handles composite type columns in SQL file endpoints, generating interfaces that match the actual JSON response — same behavior as routine endpoints, including flat/nested modes and recursive composites.


    TsClient: Multi-Command SQL File Support

    The TypeScript client generator now handles multi-command SQL file endpoints. For multi-command endpoints, TsClient generates a typed response interface with one property per result:

    typescript
    typescript
    interface IProcessOrderResponse {
    +    validate: number[];  // single-column → flat array (UnnamedSingleColumnSet)
    +    result2: number;  // void command → rows affected
    +    confirm: { id: number, status: string }[];
    +}
    +
    +export async function processOrder(
    +    request: IProcessOrderRequest
    +) : Promise<IProcessOrderResponse> { ... }
    • Void commands are typed as number (rows affected count)
    • Data-returning commands are typed as arrays of inline object types
    • Single-column commands with UnnamedSingleColumnSet enabled generate flat array types (e.g., string[]) instead of object arrays
    • Single-command SQL file endpoints generate standard typed functions (no change)

    TsClient: SQL File Comment Headers

    The TypeScript client generator now produces correct JSDoc comment headers for SQL file endpoints:

    • Header line shows the full file path (e.g., SQL file: /path/to/get-posts.sql) instead of just the filename
    • The @remarks section outputs SQL file comments directly instead of incorrectly wrapping them in comment on function ... syntax

    TsClient: Type Alias Extraction for Error and Result Types

    When IncludeStatusCode is enabled, the TypeScript client generator now emits reusable type aliases at the top of each generated file instead of repeating the full inline types everywhere:

    typescript
    typescript
    type ApiError = {status: number; title: string; detail?: string | null};
    +type ApiResult<T> = {status: number, response: T, error: ApiError | undefined};

    These aliases are used in function signatures, JSDoc comments, and as casts — significantly reducing repetition and line length:

    typescript
    typescript
    // Before (repeated 3x per function):
    +) : Promise<{status: number, response: string, error: {status: number; title: string; detail?: string | null} | undefined}>
    +
    +// After:
    +) : Promise<ApiResult<string>>

    The type aliases are not exported, so importing multiple generated files causes no naming conflicts. TypeScript's structural typing ensures full compatibility.

    Two new options control the alias names:

    • ErrorTypeName (default: "ApiError") — name for the error type alias
    • ResultTypeName (default: "ApiResult") — name for the generic result type alias

    TsClient: Fix SkipTypes Generating Invalid JavaScript

    Fixed two bugs when SkipTypes is enabled (pure JavaScript output):

    1. Invalid as cast in error handling: The error expression was always generated with a TypeScript as type cast (e.g., await response.json() as {status: number; ...}), which is invalid JavaScript syntax. The as cast is now omitted when SkipTypes is true.

    2. No file output with CreateSeparateTypeFile = false: When both SkipTypes and CreateSeparateTypeFile = false were set, no file was written at all. The code-only content is now written correctly.


    Bug Fixes & Log Improvements


    Graceful Shutdown with Active SSE Connections

    The application now shuts down cleanly when SSE (Server-Sent Events) connections are active. Previously, pressing Ctrl+C while clients were connected to SSE endpoints would hang because the broadcaster channels were never completed, leaving ReadAllAsync loops blocked indefinitely.

    On ApplicationStopping, all broadcaster channels are now completed, causing SSE middleware to exit gracefully and allowing the app to terminate.


    Downgrade Basic Auth Missing Header Log to Debug

    The "No Authorization header found" log message during Basic Authentication was downgraded from Warning to Debug. This message fires on every initial browser request before credentials are sent, which is normal behavior in the HTTP Basic Auth challenge-response flow — not a warning condition.


    Improved Log Level Classification

    Moved verbose per-item logging from Debug to Trace level to reduce noise at the default Debug level:

    • Connection source logs: Per-source "Using DataSource..." messages now include the source name (e.g., RoutineSource, SqlFileSource) and are logged at Trace instead of Debug.
    • TsClient/HttpFiles file generation: Individual "Created file" messages moved to Trace. A single Debug summary reports the total count (e.g., TsClient: Created 15 TypeScript file(s)).
    • Upload handler config details: Detailed parameter dumps for each handler type (mime patterns, buffer sizes, etc.) moved to Trace.

    Fix @separator and @new_line Annotations Not Working with @ Prefix

    The @separator and @new_line comment annotations were silently ignored when using the @ prefix syntax (e.g., @separator , in /* */ block comments). This affected SQL file endpoints using block comment annotations. Line comment annotations without @ prefix (e.g., -- separator ,) were not affected.

    The root cause: the annotation matching used line.StartsWith("separator ") which failed when the line started with @separator. All other annotation handlers used StrEqualsToArray() which correctly strips the @ prefix.


    Aggregated Comment Annotation Logging

    Comment annotation debug logs are now aggregated into a single line per endpoint instead of one line per annotation. This significantly reduces log noise during development.

    Before (multiple Debug lines per endpoint):

    code
    [DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set HTTP by the comment annotation to GET /api/who-am-i
    +[DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set REQUIRED AUTHORIZATION by the comment annotation.
    +[DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i has set SINGLE RECORD by the comment annotation.

    After (one Debug line per endpoint):

    code
    [DBG] SQL file: who-am-i.sql mapped to GET /api/who-am-i annotations: [HTTP GET, authorize, single]

    The individual per-annotation log messages are still available at Trace level for detailed debugging.


    Fix: OnlyWithHttpTag Mode Skips Files Before Describe

    When CommentsMode is OnlyWithHttpTag (the default), SQL files without an HTTP tag are now skipped before the PostgreSQL describe step. Previously, files without an HTTP tag were still described against the database, causing errors on invalid SQL files (e.g., migration scripts, utility files) instead of being silently skipped. With ErrorMode.Exit, this would crash the process.


    Internal & Breaking Changes


    Interface Refactoring: IEndpointSource / IRoutineSource

    IRoutineSource split into two interfaces:

    • IEndpointSource — base interface with CommentsMode, NestedJsonForCompositeTypes, and Read(). Used by lightweight sources like SqlFileSource.
    • IRoutineSource : IEndpointSource — extended interface adding Query, schema/name filtering. Used by RoutineSource and CrudSource.

    NestedJsonForCompositeTypes moved from IRoutineSource to IEndpointSource so that all endpoint sources (including SqlFileSource) support composite type nesting configuration.

    Breaking: NpgsqlRestOptions.RoutineSources renamed to EndpointSources. SourcesCreated callback renamed to EndpointSourcesCreated. Custom IEndpointSource implementations must now implement NestedJsonForCompositeTypes.


    Composite Type Cache: Public API

    • CompositeTypeCache.ResolveTypeDescriptor(TypeDescriptor) — new public method for plugins to resolve composite/array-of-composite type metadata
    • Routine.CompositeColumnInfo and Routine.ArrayCompositeColumnInfo — changed from internal to public for plugin access
    • Schema-prefix fallback: public.my_type now matches cache key my_type (handles GetDataTypeName vs regtype::text format mismatch)

    Glob Pattern Enhancement: ** Recursive Matching

    Parser.IsPatternMatch now supports ** for recursive directory matching:

    • * — matches any characters (backward-compatible: matches / when no ** in pattern)
    • ** — matches any characters including / (crosses directory boundaries)
    • When ** is present in the pattern, * stops matching / (standard glob semantics)

    Examples:

    • sql/**/*.sql matches sql/file.sql, sql/dir/file.sql, sql/a/b/c/file.sql
    • **/*.sql matches any .sql file at any depth
    • dir/**/file.sql matches dir/file.sql and dir/a/b/file.sql

    This enhancement benefits all existing IsPatternMatch consumers (StaticFiles.AuthorizePaths, StaticFiles.ParseContentOptions.ParsePatterns, upload MIME types) and enables the SQL file source's recursive file scanning.


    Internal Changes

    • RoutineType.SqlFile — new enum value for SQL file endpoints (was Other), shown in log messages
    • NpgsqlRestParameter.ConvertedName / ActualNameinternal set (was private set) for @param rename support
    • ParameterHandler.HandleParameterRename — new method handling all rename/retype annotation forms
    • SqlFileParameterFormatter — static singleton, IsFormattable = false, zero per-endpoint allocation
    • Routine.MultiCommandInfo — per-command metadata array (statement SQL, column info, result names)
    • NpgsqlRetryExtensions.ExecuteBatchReaderWithRetryAsync — new retry extension for NpgsqlBatch readers
    • Multi-command rendering in NpgsqlRestEndpoint.csNpgsqlBatch execution, do/while NextResultAsync() loop, JSON object wrapper with multiCmdWriteWrapper flag (skipped in raw/binary mode), table format handler called per result set
    • JsonValueFormatter.FormatValue — shared value type dispatch for both single and multi-command rendering paths
    • Three new log messages: CommentParamNotExistsCantRename, CommentParamRenamed, CommentParamRetyped
    • NpgsqlRestEndpoint split into partial class files: NpgsqlRestEndpoint.cs (request handling + rendering, ~2866 lines) and NpgsqlRestEndpoint.Helpers.cs (helper methods, ~352 lines) for easier maintenance
    • JSON key escaping: column names, composite field names, and multi-command result keys are now properly escaped with PgConverters.SerializeString. Pre-escaped values stored in Routine.JsonColumnNames, MultiCommandInfo.JsonName/JsonColumnNames at startup to avoid per-row escaping overhead during request execution
    • HttpClientOptions.SelfBaseUrl — configurable base URL for relative-path HTTP client type definitions. Auto-detected from server addresses at runtime when not configured
    • HttpClientTypeHandler.SetSelfClient — allows injecting a custom HttpClient for self-referencing calls (used by WebApplicationFactory in tests)
    • HttpClientTypes initialization moved before Build() in NpgsqlRestBuilder so definitions are available when endpoint sources process files
    • InternalRequestHandler — direct in-process endpoint invocation for self-referencing calls. Endpoint handlers stored in FrozenDictionary keyed by path. Uses NonClosingMemoryStream to prevent PipeWriter.Complete from closing the response stream. Supports path parameter matching via segment-by-segment template comparison with route value extraction

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.13.0.html b/guide/changelog/v3.13.0.html new file mode 100644 index 000000000..40b8430ab --- /dev/null +++ b/guide/changelog/v3.13.0.html @@ -0,0 +1,178 @@ + + + + + + Changelog v3.13.0 (2026-04-24) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.13.0 (2026-04-24)

    Version 3.13.0 (2026-04-24)

    Full Changelog

    New: Auth Schemes (Named Additional Authentication Schemes)

    Auth:Schemes is a named-dict section that registers additional ASP.NET Core authentication schemes alongside the main one. Each entry is a fully-fledged scheme of any of the three supported types — Cookies, BearerToken, or Jwt — with its own options. A login function selects which scheme to use by returning the scheme's name in its scheme column.

    Use cases this unlocks:

    • Short-lived sensitive sessions for admin or payment flows (Cookies scheme with shorter CookieValid + CookieMultiSessions: false).
    • Per-scope JWT signing keys so a key leak has limited blast radius (separate JwtSecret per Jwt scheme).
    • Multiple bearer-token APIs with different expirations and refresh paths.
    • Single-session cookies for areas where parallel logins must be disallowed, alongside a normal long-lived session.
    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "14 days",
    +  "JwtAuth": true,
    +  "JwtSecret": "...root-secret-32+chars...",
    +  "Schemes": {
    +    "short_session": {
    +      "Type": "Cookies",
    +      "Enabled": true,
    +      "CookieValid": "1 hour",
    +      "CookieMultiSessions": false
    +    },
    +    "api_token": {
    +      "Type": "BearerToken",
    +      "Enabled": true,
    +      "BearerTokenExpire": "30 minutes",
    +      "BearerTokenRefreshPath": "/api/api-token/refresh"
    +    },
    +    "admin_jwt": {
    +      "Type": "Jwt",
    +      "Enabled": true,
    +      "JwtSecret": "...separate-admin-secret-32+chars...",
    +      "JwtExpire": "5 minutes",
    +      "JwtRefreshPath": "/api/admin-jwt/refresh"
    +    }
    +  }
    +}
    sql
    sql
    -- Standard login: returns 'Cookies' → 14-day persistent cookie
    +create function login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'Cookies' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Sensitive-area login: returns 'short_session' → 1-hour session-only cookie
    +create function admin_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'short_session' as scheme, user_id::text, username from users where ...
    +$$;
    +
    +-- Admin JWT login: returns 'admin_jwt' → 5-minute JWT signed with the admin-only secret
    +create function admin_jwt_login(_user text, _pass text)
    +returns table (scheme text, name_identifier text, name text)
    +language sql security definer as $$
    +  select 'admin_jwt' as scheme, user_id::text, username from users where ...
    +$$;

    Per-type override fields:

    TypeOverride fields
    CookiesCookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly
    BearerTokenBearerTokenExpire, BearerTokenRefreshPath
    JwtJwtExpire, JwtRefreshExpire, JwtSecret, JwtIssuer, JwtAudience, JwtClockSkew, JwtRefreshPath, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey

    Common fields: Type (required, case-insensitive), Enabled (default true).

    Inheritance. A scheme that overrides only one or two fields reuses everything else from the root Auth section, so blocks stay small. Setting CookieMultiSessions: false is the typical "single-session" override — the cookie's Max-Age becomes null (browser-session-only) while ExpireTimeSpan still bounds server-side validity. JWT schemes inherit JwtSecret from the root section if not set explicitly, so a per-scheme block can be just a shorter expiration.

    Validation at startup (fail-fast).

    • Scheme name must not collide with the main scheme names (CookieAuthScheme, BearerTokenAuthScheme, JwtAuthScheme).
    • Type must be one of Cookies, BearerToken, Jwt (case-insensitive). Missing or unsupported types throw with a clear message.
    • Explicit CookieName values must be distinct across all cookie schemes. When unset, ASP.NET's per-scheme .AspNetCore.<scheme> default automatically differs and is excluded from collision tracking.
    • Refresh paths (BearerTokenRefreshPath / JwtRefreshPath) must be unique across the main scheme and every scheme that defines one — two app.Use middlewares listening on the same path would race.
    • Jwt schemes require a secret either on the scheme or on the root section; JwtSecret must be ≥32 chars for HS256.
    • Invalid interval strings (CookieValid, BearerTokenExpire, JwtExpire, JwtRefreshExpire, JwtClockSkew) throw with the offending path and value.

    Refresh middleware per scheme. Each BearerToken/Jwt scheme that declares a refresh path gets its own app.Use middleware listening on that path, with that scheme's tokens validated under that scheme's options. The main-scheme refresh middleware (root BearerTokenRefreshPath / JwtRefreshPath) continues to work for the main scheme.

    Logout. The existing logout pipeline accepts a list of scheme names from the logout function's result columns and signs out each — additional schemes work without changes. To clear both main and additional cookies in one logout, return both scheme names from the function.

    Breaking: legacy auth time-integer fields removed

    The four integer-based time fields under Auth are removed in 3.13.0. Use the interval-notation equivalents instead:

    Removed (3.12 and earlier)Use instead (3.13.0+)
    Auth:CookieValidDays: 14Auth:CookieValid: "14 days"
    Auth:BearerTokenExpireHours: 1Auth:BearerTokenExpire: "1 hour"
    Auth:JwtExpireMinutes: 60Auth:JwtExpire: "60 minutes"
    Auth:JwtRefreshExpireDays: 7Auth:JwtRefreshExpire: "7 days"

    The new fields accept Postgres-interval syntax ("14 days", "12 hours", "30 minutes", "45 seconds", etc.) — finer-grained durations than the legacy integers permitted.

    If you upgrade with any of the four removed fields still in your config, startup will fail with a clear migration message naming the offending field, the replacement field, and an example interval string. Failing fast is deliberate — silently ignoring the legacy field would mean an "I configured 30-day cookies" intent silently flips to the new field's default of 14 days, which would be very surprising.

    New: interval notation for auth time fields

    Each of the four time-window settings under Auth (cookie validity, bearer-token expiration, JWT access-token expiration, JWT refresh-token expiration) is expressed as a Postgres interval string:

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieValid": "12 hours",
    +  "JwtAuth": true,
    +  "JwtExpire": "5 minutes",
    +  "JwtRefreshExpire": "1 day"
    +}

    Setting any of these to null falls back to the framework default (14 days / 1 hour / 60 minutes / 7 days respectively). Invalid interval values fail at startup with a clear error, rather than silently falling back. The shipped appsettings.json includes the explicit defaults so users see exactly what they're getting.

    Breaking: RateLimiterOptions:Policies is now a dict, not an array

    This section was previously an array of objects, each with an explicit "Name" property. It is now an object keyed by policy name, matching the existing ValidationOptions:Rules and the new CacheOptions:Profiles shape. Migration is mechanical:

    jsonc
    jsonc
    // Before (3.12 and earlier):
    +"Policies": [
    +  { "Name": "fixed",   "Type": "FixedWindow",  "Enabled": true,  "PermitLimit": 100, ... },
    +  { "Name": "sliding", "Type": "SlidingWindow", "Enabled": false, ... }
    +]
    +
    +// After (3.13.0):
    +"Policies": {
    +  "fixed":   { "Type": "FixedWindow",  "Enabled": true,  "PermitLimit": 100, ... },
    +  "sliding": { "Type": "SlidingWindow", "Enabled": false, ... }
    +}

    Move each policy's Name value to be the JSON key, then drop the Name field. No other field changes; runtime behavior is identical.

    If you upgrade with the old array form still in your config, startup will fail with a clear InvalidOperationException telling you to migrate. (Failing fast is deliberate — silently registering policies under names like "0" and "1" would have made endpoint annotations referencing them stop matching, leading to silent loss of rate limiting.)

    New: Per-User Rate Limiting (Partition on a policy)

    Rate-limiter policies can now be partitioned at request time, so each request gets its own bucket based on a value derived from HttpContext (a claim, an IP, a header, or a static fallback). The classic use case is per-user throttling: each authenticated user gets their own quota instead of all users sharing one global bucket.

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "Policies": {
    +    "per_user": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 100,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "Sources": [
    +          { "Type": "Claim", "Name": "name_identifier" },
    +          { "Type": "IpAddress" },
    +          { "Type": "Static", "Value": "anonymous" }
    +        ]
    +      }
    +    },
    +    "throttle_anon_only": {
    +      "Type": "FixedWindow",
    +      "Enabled": true,
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "Partition": {
    +        "BypassAuthenticated": true,
    +        "Sources": [{ "Type": "IpAddress" }]
    +      }
    +    }
    +  }
    +}

    Partition fields:

    • Sources — ordered list. Walked top-to-bottom at request time; the first source returning a non-empty value wins. Each source has a Type:

      • Claim — reads HttpContext.User.FindFirst(Name).Value. Name is required (the claim type, e.g., "name_identifier").
      • IpAddress — reads the client IP via HttpRequest.GetClientIpAddress(), which honors X-Forwarded-For / X-Real-IP ahead of Connection.RemoteIpAddress. No Name needed.
      • Header — reads HttpContext.Request.Headers[Name]. Name is required.
      • Static — always returns the configured Value. Useful as a terminal fallback (e.g., everyone unmatched shares the "anonymous" bucket).

      If no source resolves, partition resolution falls through to the literal key "unpartitioned" so the policy still rate-limits coherently.

    • BypassAuthenticated (bool, default false) — when true, signed-in users (HttpContext.User.Identity.IsAuthenticated) skip the limiter entirely. Evaluated before Sources, so use this for "throttle anonymous only" patterns. Authenticated users get an unlimited bucket; anonymous users still hit the partitioned limiter.

    Behavior is unchanged for policies without a Partition block. Each non-partitioned policy still uses a single global bucket, exactly as in 3.12 and earlier.

    Each Sources entry is validated at startup — invalid entries (e.g., Claim without Name, unknown Type) are logged at Warning and skipped. If a Partition block has no usable sources and BypassAuthenticated is false, the partition is dropped (with a Warning) and the policy reverts to a single global bucket.

    New: Caching Profiles (CacheOptions.Profiles + @cache_profile annotation)

    Named cache profiles allow you to maintain multiple distinct caching policies in one application — different backends, expirations, key shapes, or bypass conditions — and let endpoints opt into them via a single comment annotation.

    jsonc
    jsonc
    "CacheOptions": {
    +  "Enabled": true,
    +  "Type": "Memory",
    +  "Profiles": {
    +    "fast_memory": {
    +      "Enabled": false,
    +      "Type": "Memory",
    +      "Expiration": "30 seconds",
    +      "Parameters": ["user_id"]
    +    },
    +    "shared_redis": {
    +      "Enabled": false,
    +      "Type": "Redis",
    +      "Expiration": "1 hour"
    +    },
    +    "date_range_hybrid": {
    +      "Enabled": false,
    +      "Type": "Hybrid",
    +      "Expiration": "5 minutes",
    +      "Parameters": ["from", "to"],
    +      "When": [
    +        { "Parameter": "to", "Value": null, "Then": "skip" }
    +      ]
    +    }
    +  }
    +}
    sql
    sql
    comment on function get_orders(from text, to text) is '
    +HTTP GET
    +@cache_profile date_range_hybrid
    +';

    Profile fields:

    • Enabled (bool, default false) — disabled profiles are skipped at startup; flip to true to activate.

    • TypeMemory, Redis, or Hybrid. Backends are pooled: all profiles of the same type share one instance (one Memory cache, one Redis connection, one HybridCache singleton). A backend type is only instantiated if root or some enabled profile uses it.

    • Expiration — default expiration in PostgreSQL interval format. Used when the endpoint has no @cache_expires annotation.

    • Parameters — default cache-key parameter list:

      • null (or property omitted): use all routine parameters.
      • [] (empty array): URL-only cache (one entry per endpoint, regardless of inputs).
      • ["x", "y"]: only those named parameters as the key.

      The endpoint's @cached p1, p2 annotation overrides this.

    • When — list of conditional rules. Each rule has:

      • Parameter — the routine parameter name to inspect.
      • Value — match condition. Scalar (single match) or array (OR over entries). JSON null matches .NET null/DBNull.Value (does not match empty string). Other values are stringify-and-equal case-insensitive.
      • Then — the literal string "skip" to bypass the cache for that request, OR a PostgreSQL interval (e.g. "30 seconds") to override the entry's TTL when writing.

      Rules evaluate in declaration order; first match wins. No match → fall through to the profile's Expiration.

      This unlocks scenarios that pure skip-on-condition couldn't express:

      • Skip-on-null: [{ "Parameter": "to", "Value": null, "Then": "skip" }]
      • Tiered TTL: [{ "Parameter": "tier", "Value": "free", "Then": "5 minutes" }, { "Parameter": "tier", "Value": "pro", "Then": "1 hour" }]
      • Status-aware caching: [{ "Parameter": "status", "Value": ["draft", null], "Then": "skip" }, { "Parameter": "status", "Value": "published", "Then": "1 hour" }]

      Validation: a rule whose Parameter is not in the resolved cache-key parameter list (Parameters or the endpoint's @cached) is dropped at startup with a Warning. This prevents the surprising case where two requests with different rule-matched values share the same cache entry.

    Annotation: @cache_profile <name> selects a profile. It implies caching even without a separate @cached annotation. The existing @cached p1, p2 (overrides profile params) and @cache_expires <interval> (overrides profile expiration) annotations continue to work and take precedence over the profile's defaults.

    Misconfiguration is loud at startup. Unknown profile names referenced by @cache_profile cause startup to fail with a single InvalidOperationException listing every unresolved name and the endpoints that referenced each — so typos surface immediately rather than silently disabling caching at runtime. Profiles registered but unreferenced log an Information warning. Bad Type or Expiration values warn and skip the profile. Empty/whitespace profile names are rejected.

    Cache key isolation. Entries written under a profile are prefixed with the profile name, so two profiles sharing the same backend (e.g., two Memory profiles) cannot collide on the same routine + parameters. Endpoints without a profile have no prefix — existing cache entries stay wire-compatible across the upgrade.

    The default appsettings.json ships with three disabled example profiles covering each Type and demonstrating a When rule. Flip "Enabled": true on the one(s) you need.

    Never-expiring (infinite) cache entries

    There is no explicit "forever" or "never" literal — instead, omit the Expiration field to get never-expiring entries. This applies at every level:

    • Endpoint without @cache_expires → entry never expires (today's pre-3.13 behavior, unchanged).
    • Profile without Expiration field → entries written under that profile never expire.
    • Both @cache_expires and profile Expiration set → annotation wins (the explicit interval is used).

    If you need a mix in one app — some profiles with TTL, others never-expiring — define a dedicated profile with no Expiration:

    jsonc
    jsonc
    "Profiles": {
    +  "static_lookup_data": {
    +    "Enabled": true,
    +    "Type": "Memory"
    +    // No Expiration → entries never expire (suitable for ISO codes, taxonomies, etc.)
    +  },
    +  "session_data": {
    +    "Enabled": true,
    +    "Type": "Redis",
    +    "Expiration": "1 hour"
    +  }
    +}

    Endpoints opt into the appropriate profile via @cache_profile. This pattern handles the common cases (static reference data, immutable historical content) without needing a separate "force never expire" override.

    New: WrapInTransaction Option (Connection Pooler Compatibility)

    When set to true, every request is wrapped in an explicit BEGIN ... COMMIT, and all set_config calls switch from session-scoped (is_local=false) to transaction-local (is_local=true).

    This is required for connection poolers in transaction mode — including PgBouncer transaction-pool, AWS RDS Proxy in transaction mode, and Supabase Pooler. Previously, set_config(name, value, false) would set the GUC at the session level on the underlying PostgreSQL backend. With a transaction-mode pooler, the same backend is reused for unrelated client requests, which means session-scoped GUCs from one request could be visible to the next. With WrapInTransaction = true, GUCs are scoped to the request transaction and discarded on COMMIT.

    The default remains false to preserve existing behavior; it is safe to leave off when using Npgsql's native pool only (which issues DISCARD ALL on connection return).

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true
    +  }
    +}

    New: BeforeRoutineCommands Option

    A new option allowing arbitrary SQL commands to run after any context is set but before the main routine call. They run in the same batch as the context set_config calls, so there is no extra network round-trip.

    Each entry can be either a raw SQL string (no parameters) or an object with Sql and Parameters. Each parameter has a Source (Claim, RequestHeader, or IpAddress) and an optional Name (claim type or header name). Parameter values are bound at request time from HttpContext — claim and header values are passed as parameterized SQL inputs (no string interpolation, no injection risk).

    The most useful pattern is multi-tenant search_path setup driven by a JWT/cookie claim:

    jsonc
    jsonc
    {
    +  "NpgsqlRest": {
    +    "WrapInTransaction": true,
    +    "BeforeRoutineCommands": [
    +      "select set_config('app.request_time', clock_timestamp()::text, true)",
    +      {
    +        "Sql": "select set_config('search_path', $1, true)",
    +        "Parameters": [{ "Source": "Claim", "Name": "tenant_id" }]
    +      }
    +    ]
    +  }
    +}

    Per-request execution order with this config:

    1. BEGIN
    2. Each BeforeRoutineCommand is added as a NpgsqlBatchCommand (with parameters bound from claims/headers/IP) and dispatched in a single batch.
    3. The main routine call.
    4. COMMIT.

    Steps 1–3 share a single network round-trip.

    Fix: 400 Bad Request responses are no longer silent in logs

    Endpoints that returned HTTP 400 were not being logged at all, making client-error problems invisible in production. Two independent paths produced silent 400s:

    1. Database exceptions mapped to 400 (P0001 raise exception, P0004 assert_failure, or any user-configured ErrorHandlingOptions mapping to 400). The exception handler in NpgsqlRestEndpoint explicitly skipped logging for status 400.
    2. Validation rule failures (ValidationOptions.Rules → 400). These were logged at Debug level, which is below the default minimum log level (Information), so they never appeared in production logs.

    Fix: 400s are now logged at Warning level — visible by default but not raised to Error, since 400 is a client-side problem rather than a server fault. Genuine server errors (500, etc.) continue to be logged at Error with full stack traces.

    Docker Images: Ubuntu 26.04 LTS Base

    The native AOT Docker images (latest, latest-arm, latest-bun) now build on Ubuntu 26.04 "Resolute Wolf" LTS, up from Ubuntu 25.04 (a 9-month interim release that is reaching end of support). This extends the security-update window for the published images to the 5-year LTS support period and picks up a newer stack (Linux 7.0 kernel, newer OpenSSL, cgroup v2). No changes are required for consumers of the images — runtime dependencies (libssl3, libgssapi-krb5-2, ca-certificates) resolve under the same package names on 26.04.

    NuGet Package Upgrades

    NpgsqlRest (main library):

    • Microsoft.SourceLink.GitHub 10.0.201 → 10.0.203 (build-time only)

    NpgsqlRestClient (client application):

    • Microsoft.AspNetCore.Authentication.JwtBearer 10.0.5 → 10.0.7
    • Microsoft.Extensions.Caching.Hybrid 10.4.0 → 10.5.0
    • Microsoft.Extensions.Caching.StackExchangeRedis 10.0.5 → 10.0.7
    • StackExchange.Redis 2.12.8 → 2.12.14

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.14.0.html b/guide/changelog/v3.14.0.html new file mode 100644 index 000000000..c3ff52336 --- /dev/null +++ b/guide/changelog/v3.14.0.html @@ -0,0 +1,55 @@ + + + + + + Changelog v3.14.0 (2026-05-09) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.14.0 (2026-05-09)

    Version 3.14.0 (2026-05-09)

    Full Changelog

    This release sharpens the philosophy of the standalone client: REST endpoints come from PostgreSQL routines (functions, procedures) and from explicit SQL files — not auto-generated CRUD on tables and views. It also makes real-time push (SSE) more honest about what your annotations mean, and gets more throughput out of array- and composite-heavy responses.

    Removed: auto-CRUD endpoint generation from the standalone client

    The NpgsqlRest.CrudSource plugin is no longer wired into the NpgsqlRestClient standalone executable. The plugin itself still ships as a NuGet package and remains fully functional for embedded use — projects that consume the NpgsqlRest library directly can continue to register a CrudSource instance against EndpointSources exactly as before. Only the NpgsqlRestClient JSON-driven path is affected.

    Why: auto-generated CRUD endpoints over arbitrary tables and views have always been a different shape from the rest of the project. The core promise is "your PostgreSQL routines become REST endpoints" — explicit, version-controlled, comment-annotated procedures and functions where the developer chose what to expose. Auto-CRUD inverts that: every table becomes ten URL patterns by default (select / insert / update / delete plus the various on conflict and returning variants), with no per-endpoint guardrails unless you opt back into them. That's a different product, and it doesn't belong in the same configuration surface.

    What this means in practice:

    • appsettings.json: the entire NpgsqlRest:CrudSource block has been removed from the default template. Any existing configuration with that block will fail key validation at startup (the same Config:ValidateConfigKeys check that catches typos) — remove the block to upgrade.
    • --config output: CrudSource no longer appears in the dumped configuration.
    • --version output: NpgsqlRest.CrudSource is no longer listed in either the human-readable or --json form, since the standalone client no longer references the assembly.
    • Library use: zero change. using NpgsqlRest.CrudSource; sources.Add(new CrudSource()) works exactly as it did in 3.13.0 and remains supported.

    If you had "CrudSource": { "Enabled": true } in your config and depend on the generated endpoints, the migration is one of:

    1. Switch to function-based wrappers — the recommended path. Write the CRUD shape you actually need as PostgreSQL functions or procedures with HTTP annotations. You get the same endpoints with explicit per-endpoint auth, validation, caching, rate limiting, and comment-driven URL shapes.
    2. Embed the library in a custom host and register CrudSource programmatically. The plugin code still ships under plugins/NpgsqlRest.CrudSource/.

    What's new

    Two new SSE annotations: @sse_publish and @sse_subscribe

    If you've ever had a manager-side procedure broadcast notifications to user-side subscribers, you've probably hit the awkwardness of the all-in-one @sse annotation: every emitter procedure ended up with a phantom /info URL nobody connected to, and your generated TypeScript client included createXEventSource() helpers for procedures that should never be subscribed to.

    3.14.0 splits the responsibilities:

    AnnotationWhat it does
    @sse_publishThis procedure's RAISE statements feed SSE subscribers. No subscribe URL is exposed.
    @sse_subscribeExposes a subscribe URL for EventSource clients. The procedure body is never executed when a client opens the stream.
    @sse [path]Same as before — shorthand for both. Unchanged.

    So a "manager broadcasts to users" flow now reads cleanly:

    sql
    sql
    -- subscriber URL, body never runs on subscribe
    +comment on function subscribe_user_events() is '
    +HTTP GET
    +@sse_subscribe
    +';
    +
    +-- emitter, no subscribe URL, no useless TS EventSource helper
    +comment on procedure broadcast_user_message(...) is '
    +HTTP POST
    +@sse_publish
    +@sse_scope authorize
    +';

    The TypeScript client generator follows automatically: @sse_publish produces a plain POST function, @sse_subscribe keeps the EventSource helper, and @sse is unchanged.

    All existing event filtering — @sse_scope, @sse_events_level, RAISE ... USING HINT, and the X-NpgsqlRest-ID execution-id header — works the same with both new annotations.

    Warning when a RAISE looks like a missed @sse_publish

    Forgetting @sse_publish on an emitter procedure used to fail silently: the RAISE ran, the notice logged, and zero events reached subscribers. NpgsqlRest now warns once per endpoint when a RAISE whose severity matches the configured SSE forwarding level fires in a procedure that has no @sse or @sse_publish:

    code
    WARN: RAISE INFO in endpoint /api/update-user-roles was not broadcast to SSE subscribers —
    +the endpoint has no @sse or @sse_publish annotation. Add @sse_publish to forward this
    +routine's notices, or set WarnUnboundServerSentEventsNotices=false to silence this warning.

    The warning only fires when the project actually uses SSE somewhere and only when the severity matches — projects that use RAISE NOTICE for unrelated logging see no warnings, and projects that don't use SSE at all see no warnings. Configurable via the new WarnUnboundServerSentEventsNotices setting (default true).

    Reliable SSE connection handshake

    SSE responses now flush a small "connected" line as soon as the broadcaster has registered the new subscriber, instead of waiting for the first real event. Browsers and EventSource clients ignore comment-only lines per spec, so no consumer behavior changes — but a client that wants to do "subscribe, then publish, then receive" can now rely on the handshake completing before its publish call. Mostly visible to integration tests; in production it makes connection states more predictable.

    Startup error when claim-mapped parameters use a non-text type

    If Auth.UseUserParameters is on and your ParameterNameClaimsMapping references a procedure parameter whose SQL type isn't text-compatible — for example _company_id int mapped to a company_id claim — every authenticated request used to crash with this error from deep inside the driver:

    code
    System.InvalidCastException: Writing values of 'System.String' is not supported
    +for parameters having NpgsqlDbType 'Integer'.

    The exception didn't mention claim mapping, so debugging meant a stack-trace hunt. NpgsqlRest now catches the misdeclaration at startup with a precise message naming the endpoint, parameter, claim, and the SQL type:

    code
    Endpoint POST /api/create-local-user parameter _company_id is mapped to claim
    +'company_id' but its type is 'int' which is not text-compatible. Claim values
    +are strings, so binding would fail at runtime with InvalidCastException.
    +Declare the parameter as text/varchar/char/json/jsonb/xml/jsonpath, or remove
    +'_company_id' from ParameterNameClaimsMapping.

    Accepted types: text, varchar, char, name, xml, json, jsonb, jsonpath, plus unknown (the SQL-file-source case where the driver resolves the type server-side). Any other type fails fast at UseNpgsqlRest. The check only runs for endpoints with UseUserParameters enabled and only for parameters that match a configured claim mapping.

    Warning when a request value is overridden by claim auto-bind

    When a parameter is auto-bound from a claim, the claim wins — that's intentional, especially for security-sensitive procedures where the caller's identity must override anything the request supplies. But if the request also sent a value for that parameter, the value used to be discarded silently. With certain UI patterns (forms that POST every field) this hid real bugs: in one case update_user_roles(_user_id text, _roles text[]) looked like it was updating a target user, but every request modified the caller's own roles because _user_id was claim-mapped.

    The auto-bind still wins (no behavior change for security), but a collision now emits a warning so the developer can see what happened:

    code
    Endpoint /api/update-user-roles parameter _user_id received a body value but
    +is auto-bound from claim 'name_identifier'. The supplied value is being ignored.

    The warning fires only when the request actually supplied a value, naming the endpoint, parameter, source (body or query), and the claim.

    Performance

    A focused pass on response rendering. No public API or configuration changes.

    Lower-allocation JSON conversion for arrays and composites

    The four PostgreSQL → JSON conversion routines used to render array, composite, and tuple values now rent their working StringBuilder buffers from the existing pool instead of allocating fresh ones on every call. This affects PgArrayToJsonArray, PgCompositeArrayToJsonArray, PgTupleToJsonObject, and PgUnknownToJsonArray — all of which fire per row × per column on responses that include array or composite types.

    Measured on a focused micro-benchmark (Apple M4 Pro, .NET 10, BenchmarkDotNet ShortRun, three iterations):

    FunctionBefore allocAfter allocΔ
    PgArrayToJsonArray (numeric, 100 elem)1.91 KB1.16 KB−39%
    PgArrayToJsonArray (text, 100 elem)19.68 KB13.86 KB−29%
    PgCompositeArrayToJsonArray (50 elem)23.02 KB11.52 KB−50%
    PgTupleToJsonObject (10 fields)1.10 KB1.27 KBwithin noise

    CPU time per call moved by single-digit percent — within or near the noise band of a short BDN run. The headline win is reduced GC pressure during sustained load: a 100-row response containing several array columns can drop ~1–2 MB of allocation per request, and a multi-row composite-array response can drop ~5–10 MB.

    Estimated impact on the PostgreSQL REST API Benchmark 2026 workloads

    These are extrapolations from the micro-benchmark above, not re-measured numbers from re-running the published benchmark. They estimate how the allocation reduction translates to throughput and tail latency under that benchmark's 100 VU sustained concurrency — where reduced GC pressure compounds. Scenarios with little or no array/composite work see essentially no change because the optimized paths don't fire.

    ScenarioBaseline (3.4.7)What firesEst. req/s ΔEst. P99 latency Δ
    Minimal Baseline16,065 req/sNothing — no arrays, no composites0%0%
    Many Parameters (20)11,504 req/sQuery-string parsing only — not touched0%0%
    POST Body (10 rec)6,101 req/sArrays in 10-row response+1 to +3%−2 to −5%
    Data Type (1 rec)4,588 req/sFew array columns × 1 row+0 to +2%−2 to −5%
    Nested JSON (depth 1, 100 rows)3,061 req/sComposite paths fire heavily+5 to +10%−5 to −15%
    Large Payload (100 KB)1,096 req/sDepends on payload shape+0 to +3%varies
    Data Type (100 rec)377 req/sArray cols × 100 rows — ~1–2 MB/req cut+3 to +7%−5 to −12%
    Data Type (500 rec)82 req/sArray cols × 500 rows — ~5–10 MB/req cut+5 to +10%−8 to −15%

    The largest absolute wins land on the high-record-count scenarios where allocation pressure is greatest. The largest relative tail-latency improvements land on the same scenarios because Gen0 stalls dominate P99 under that load shape.

    Two important caveats:

    1. The published benchmark measured 3.4.7. Current master already has months of perf work on top of that. These estimates apply on top of current state; they assume the relative shape (CPU vs. PostgreSQL vs. network) hasn't shifted dramatically since 3.4.7.
    2. End-to-end requests spend most of their time in PostgreSQL, network round-trip, and Kestrel. The optimized paths are a slice of response rendering, so the gains compound only where rendering CPU or GC is the bottleneck. For a single-row response the optimized work is microseconds out of milliseconds; for a 500-row array-heavy response it's a much larger share.

    UTF-8 literals for JSON markup constants

    Consts.Utf8OpenBrace, Utf8CloseBrace, Utf8OpenBracket, Utf8CloseBracket, Utf8Comma, Utf8Colon, and Utf8Null are now static ReadOnlySpan<byte> properties backed by "x"u8 UTF-8 string literals, instead of static readonly byte[] fields. The bytes are embedded directly in the assembly metadata, so each access is a pointer-and-length to read-only data — zero heap allocation, ever. Eliminates seven small startup-time allocations.

    Tighter PipeWriter writes

    The hot-path JSON markup writes (commas, braces, brackets, the "null" literal) have been collapsed from a three-step GetSpan / CopyTo / Advance pattern to a single IBufferWriter<byte>.Write(ReadOnlySpan<byte>) call across ten call sites in the response renderer. Same allocation profile, fewer chances to mismatch sizes.

    Hardening (silent-failure fixes)

    Each of these fixes a class of silent failure that used to require log digging or memory monitoring to detect.

    ArrayPool rent now in try/finally

    PgCompositeArrayToJsonArray rents a char[] from ArrayPool<char>.Shared for inputs over 512 chars and previously returned it only on the success path. If the parsing loop threw, the rented buffer was lost from the shared pool until process exit — a slow, silent leak that compounded over uptime. Returns now happen in finally, so a malformed PostgreSQL value can't poison the pool.

    Multi-command StringBuilder rentals always released

    The mcRowBuilder and mcCompositeBuffer StringBuilders rented inside the multi-command result-rendering loop are now lifted to method scope and released in an outer finally even if the inner reader loop throws.

    proxy_out buffer released on exception path

    The MemoryStream used by the proxy_out feature to capture function output before forwarding upstream is now disposed in an inner try/finally, so a forwarding failure can't leak the buffer.

    Column-decryption failures now logged at Trace

    Three call sites that decrypt column values via IDataProtector.Unprotect previously had silent catch { } blocks — by design, so a failed decryption falls back to the raw ciphertext rather than surfacing as a 500. The fall-back is preserved, but the failure is now logged at LogLevel.Trace:

    code
    Column decryption failed; falling back to raw value. Error: <message>

    A misconfigured key, a tampered ciphertext, or a key-rotation mismatch is now observable when Trace logging is enabled instead of being completely silent.

    Configuration

    One new optional setting:

    • WarnUnboundServerSentEventsNotices (default true) — controls the new "missed @sse_publish" warning described above. Set false if your project intentionally uses RAISE for non-SSE logging and you don't want NpgsqlRest commenting on it.

    No keys removed or renamed; existing appsettings.json works as-is.

    Test suite

    1949 tests pass on the release branch. 12 of those are new for the SSE work (URL routing under each annotation combination, end-to-end live event delivery from a publisher procedure to a subscriber on a different procedure's URL, the missed-annotation warning, TS client output for both new annotations) and 5 are new for the claim auto-bind diagnostics. A new SseTestClient helper opens streaming HTTP connections and waits for the broadcaster to register subscribers before publishing — reusable for upcoming SSE work like heartbeats and Last-Event-ID resume.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.15.0.html b/guide/changelog/v3.15.0.html new file mode 100644 index 000000000..8b1166dad --- /dev/null +++ b/guide/changelog/v3.15.0.html @@ -0,0 +1,100 @@ + + + + + + Changelog v3.15.0 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.15.0

    Version 3.15.0 (2026-05-11)

    Full Changelog

    This release prepares NpgsqlRest to act as an external Web API service for a separate partner or frontend application. Three pieces land together:

    1. Auth fix — named cookie schemes registered under Auth:Schemes (introduced in 3.13.0) now actually authenticate incoming requests; previously they signed users in but no endpoint accepted the cookie.
    2. Cookie attributesCookieSameSite and CookieSecure config knobs on both the main and named cookie schemes, so cookie-based auth works across origins (SPA on a different domain).
    3. OpenAPI filtering — config-level filters (IncludeSchemas / ExcludeSchemas / NameSimilarTo / NameNotSimilarTo / RequiresAuthorizationOnly) plus a per-routine @openapi hide / @openapi tag <name> comment annotation, so a single host can serve a curated OpenAPI document to partners while keeping internal endpoints out of the spec.

    Changes are concentrated in NpgsqlRestClient/Builder.cs, plugins/NpgsqlRest.OpenApi/, and NpgsqlRest/Defaults/CommentParsers/. No breaking changes; existing appsettings.json works as-is — every new key defaults to "no filter" or "ASP.NET default".

    Configurations using Auth:Schemes to register a named cookie scheme alongside the main CookieAuth issued the named-scheme cookie correctly on login but no endpoint would authenticate against it. A request bearing only the named-scheme cookie was treated as anonymous — framework endpoints like /api/passkey/add/options returned 401, and SQL endpoints annotated @authorize returned 401. The feature signed users in but the sign-in was functionally inert.

    Root cause

    ASP.NET's authentication middleware only runs against the default authenticate scheme. The 3.13.0 implementation:

    1. Counted only the three main auth types (cookie / bearer / jwt) when choosing the default scheme — named schemes were invisible to that calculation.
    2. Registered the policy scheme (the dispatcher that picks the right scheme per request) only when more than one of the three main types was enabled. A typical setup with cookies + a named cookie scheme skipped the dispatcher entirely.
    3. Even when the dispatcher ran, its ForwardDefaultSelector only distinguished Bearer-vs-cookie header type. For any cookie-bearing request it returned the main cookie scheme regardless of which cookie was actually present.

    The result: named-scheme cookies hit the main scheme's cookie handler, which couldn't decrypt them (different data-protection purpose strings per scheme), so context.User came out anonymous.

    What changed

    In NpgsqlRestClient/Builder.cs:

    • Pre-scan Auth:Schemes for enabled Cookie-type entries before AddAuthentication runs, so the default-scheme decision can account for them.
    • Register a policy scheme whenever the system has either (a) multiple main auth types — the existing case, unchanged — or (b) the main cookie scheme plus one or more named cookie schemes. For (b), a synthetic policy-scheme name (NpgsqlRest_PolicyScheme) is used to avoid colliding with the main cookie scheme's own registration.
    • Cookie-aware dispatchForwardDefaultSelector now walks the registered cookie schemes in order (main first, then named in registration order) and returns the first scheme whose configured cookie name appears in the request. Falls back to the main cookie scheme for cookie-less requests so anonymous traffic behaves exactly as before. Bearer/JWT header dispatch is unchanged.
    • Cookie-name tracking — every cookie scheme registration (main and named) now records its effective HTTP cookie name on Builder.CookieSchemesInOrder. Schemes without an explicit CookieName are tracked under ASP.NET's per-scheme default (.AspNetCore.<schemeName>), so the lookup is well-defined for both explicit and defaulted cookie names.

    Behavior after the fix

    • A request carrying only a named-scheme cookie authenticates under that scheme. context.User.Identity.IsAuthenticated is true, context.User.Identity.AuthenticationType matches the named scheme name.
    • /api/passkey/add/options, /api/passkey/add, bearer/JWT refresh paths, and any @authorize-annotated SQL endpoint accept named-scheme cookies the same way they accept main-scheme cookies. No endpoint changes were required.
    • @authorize <role> continues to gate by role claims — a named-scheme cookie whose principal lacks the required role is still rejected. Scheme membership is orthogonal to role membership.
    • Backward compatibility is bit-for-bit identical for single-scheme configurations (cookies only, no Auth:Schemes): no policy scheme is registered, no selector logic engages, and the default authenticate scheme remains the main cookie scheme's name.

    When a request somehow carries both a main cookie and a named-scheme cookie (rare in practice — a user is signed in under at most one scheme by SignInAsync), the walk order is main first, then named schemes in registration order, and the first match wins. This is deterministic but not configurable; if you need scheme-specific endpoint binding regardless of which cookie is present, ASP.NET's [Authorize(AuthenticationSchemes = "...")] is the right primitive and is out of scope for this release.

    Feature: CookieSameSite and CookieSecure config

    ASP.NET defaults the auth cookie's SameSite attribute to Lax and the Secure policy to SameAsRequest. That works for "browser and API on the same origin" but silently breaks the cross-origin case — an SPA on app.example.com calling an API on api.example.com won't have its session cookie sent on cross-site requests at all under Lax, and a None cookie without Secure is dropped outright by modern browsers.

    Two new config keys make this controllable without dropping to a custom host.

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",       // "Strict" | "Lax" | "None" | "Unspecified" | null
    +  "CookieSecure":   "Always"      // "SameAsRequest" | "Always" | "None" | null
    +}

    Default for both is null, which leaves ASP.NET's per-handler default in place — so existing configs see no change.

    Per-scheme override under Auth:Schemes

    The same two keys are accepted inside any Auth:Schemes:<name> Cookies-type entry, with the same inheritance pattern as the existing cookie fields (CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly): scheme-level value wins, else inherit the root Auth section's value, else fall through to ASP.NET's default.

    jsonc
    jsonc
    "Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",
    +  "CookieSecure":   "Always",
    +  "Schemes": {
    +    // Long-lived "remember me" cookie inherits the cross-origin posture from root.
    +    "remember_me":   { "Type": "Cookies", "CookieValid": "30 days" },
    +
    +    // Short-lived sensitive-flow cookie tightens to first-party only.
    +    "short_session": {
    +      "Type": "Cookies",
    +      "CookieValid": "1 hour",
    +      "CookieSameSite": "Strict",
    +      "CookieSecure":   "SameAsRequest"
    +    }
    +  }
    +}

    Validation and warnings

    • Unknown values fail fast at startup with the offending config path included in the message — typos in security-relevant config shouldn't be silently ignored. Example: Invalid value 'Loose' for Auth:CookieSameSite. Expected one of: Unspecified, None, Lax, Strict.
    • Setting SameSite=None without Secure=Always logs a startup warning at Warning level: browsers drop SameSite=None cookies that lack the Secure attribute, and the symptom ("login succeeds but the next request is anonymous") is otherwise hard to diagnose, especially during local HTTP testing.
    • Existing appsettings.json files are unaffected — both keys default to null (use ASP.NET's default).

    Cross-origin checklist for an external Web API setup

    Combining the cookie attributes above with the already-existing CORS support, a typical "API used by a separate SPA" config looks like:

    jsonc
    jsonc
    "Cors": {
    +  "Enabled": true,
    +  "AllowedOrigins": ["https://app.example.com"],   // not "*"
    +  "AllowedMethods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    +  "AllowedHeaders": ["*"],
    +  "AllowCredentials": true                          // required for cookie auth
    +},
    +"Auth": {
    +  "CookieAuth": true,
    +  "CookieSameSite": "None",                         // cross-site
    +  "CookieSecure":   "Always",                       // required when SameSite=None
    +  "CookieHttpOnly": true,
    +  "CookieDomain":   ".example.com"                  // optional — share across subdomains
    +}

    For mobile or non-browser clients the bearer/JWT path remains the recommended route (no cookie attributes apply, no CORS preflight); these new knobs only matter when the client is a browser on a different origin.

    Feature: OpenAPI filtering for partner-facing documents

    The NpgsqlRest.OpenApi plugin previously documented every endpoint NpgsqlRest registered, with no way to suppress an endpoint or override its tag. That works for "internal API doc" but not for "API doc handed to a partner team" — where you typically want to expose only a curated subset (e.g. routines in a partner schema, only the authenticated surface, with a partner tag for nice Swagger UI grouping).

    3.15.0 adds the missing controls: five config-level filters on OpenApiOptions plus a per-routine @openapi comment annotation. All work additively — every existing config keeps its current output, since defaults are "no filter".

    Config-level filters

    KeyTypeDefaultBehavior
    IncludeSchemasstring[]empty = no filterAllow-list of schema names. Only endpoints whose routine schema appears here are documented.
    ExcludeSchemasstring[]empty = no filterDeny-list of schema names. Applied alongside IncludeSchemas — both must pass.
    NameSimilarTostringnull = no filterPostgreSQL-style SIMILAR TO pattern matched against the routine name. _ matches one char, % matches any sequence; `
    NameNotSimilarTostringnull = no filterSame syntax as above but for exclusion. Applied alongside NameSimilarTo.
    RequiresAuthorizationOnlyboolfalseWhen true, only RequiresAuthorization-bearing endpoints are documented — health/login/probes drop out.

    These all live in the same place existing knobs do — under NpgsqlRest:OpenApiOptions in appsettings.json for the standalone client, or on OpenApiOptions for library users.

    Per-routine @openapi comment annotation

    Two sub-commands; both are no-ops when the OpenAPI plugin isn't loaded, so they're safe to leave on a routine regardless of how the host is configured.

    AnnotationEffect
    @openapi, @openapi hide, @openapi hidden, @openapi ignoreExclude this routine from the OpenAPI document. HTTP endpoint stays functional — only the spec entry is skipped.
    @openapi tag <name>, @openapi tags <a>, <b>Replace the default schema-name tag with the supplied value(s). Drives section grouping in Swagger UI / ReDoc; tag values preserve original casing.
    sql
    sql
    -- Hidden from the document; endpoint is still reachable internally.
    +comment on function refresh_materialized_views() is '
    +HTTP POST
    +@authorize admin
    +@openapi hide
    +';
    +
    +-- Grouped under "Partner API" in Swagger UI instead of the default "public" schema tag.
    +comment on function partner_get_orders(_partner_id text) is '
    +HTTP GET /api/partner/orders
    +@authorize partner
    +@openapi tag Partner API
    +';

    Filter order and composition

    Filters are checked in OpenApi.Handle() in this order. The first one that rejects short-circuits — the rest don't run. Multiple filters compose conjunctively (all must pass for an endpoint to be documented).

    1. OpenApiHide annotation on the endpoint (per-routine wins over everything)
    2. RequiresAuthorizationOnly vs. RequiresAuthorization
    3. IncludeSchemas membership
    4. ExcludeSchemas membership
    5. NameSimilarTo match
    6. NameNotSimilarTo match (negative)
    7. → endpoint documented

    Partner-facing config example

    The full "API server, partner-facing OpenAPI document, internal endpoints invisible" config:

    jsonc
    jsonc
    "NpgsqlRest": {
    +  "OpenApiOptions": {
    +    "Enabled": true,
    +    "FileName": "openapi-partner.json",
    +    "UrlPath": "/openapi/partner.json",
    +    "DocumentTitle": "Acme Partner API",
    +    "DocumentDescription": "JWT-authenticated REST surface for partner integrations.",
    +
    +    "IncludeSchemas": ["partner"],                  // only partner-namespaced routines
    +    "RequiresAuthorizationOnly": true,              // drop health, login, probes
    +    "NameNotSimilarTo": "%_admin",                  // drop partner_*_admin maintenance routines
    +
    +    "SecuritySchemes": [
    +      { "Name": "bearerAuth", "Type": "Http", "Scheme": "Bearer", "BearerFormat": "JWT" }
    +    ],
    +    "Servers": [
    +      { "Url": "https://api.acme.com", "Description": "Production" }
    +    ]
    +  }
    +}

    The same host can still serve the internal cookie-authenticated surface — only the document is partner-scoped. A later operational change (e.g. moving to a separate process per audience) doesn't break what's been advertised to partners, since the document is config-driven.

    Tests

    Three new test files, 41 new tests total. Total auth + OpenAPI test count: 145 (78 pre-existing auth + 19 cookie/policy from this release + 32 OpenAPI including 16 filter / 9 annotation / 7 pre-existing path-parameter tests).

    • NpgsqlRestTests/AuthTests/AuthPolicySchemeTests.cs (16 tests): CookieSchemesInOrder population, policy-scheme registration decisions across single/multi/named combinations, and ForwardDefaultSelector dispatch (named cookie → named scheme, main cookie → main, both → main wins per documented order, neither → main fallback, bearer header preserved, JWT three-part token preserved, named cookie in composite mode, default .AspNetCore.<scheme> cookie name).
    • NpgsqlRestTests/AuthTests/AuthCookieSameSiteSecureTests.cs (15 tests): parsing of all four SameSiteMode and three CookieSecurePolicy values (case-insensitive), invalid-value fail-fast on the root and on named schemes (with the offending path in the message), unset values preserving ASP.NET defaults, the cross-origin SameSite=None; Secure=Always pattern reaching CookieAuthenticationOptions, named-scheme inheritance from root, per-scheme override winning over root.
    • NpgsqlRestTests/OpenApiTests/OpenApiFilterTests.cs (16 tests): per-filter coverage for OpenApiHide, RequiresAuthorizationOnly, IncludeSchemas, ExcludeSchemas, NameSimilarTo (prefix %, single-char _, anchoring, alternation (get|set)_%), NameNotSimilarTo, all-filters-together composition, plus OpenApiTags override of the default schema tag. Drives the plugin directly with synthetic RoutineEndpoints, asserting against the JSON file the plugin writes.
    • NpgsqlRestTests/OpenApiTests/OpenApiAnnotationTests.cs (9 tests): end-to-end through the global TestFixture's OpenAPI handler. Verifies all four aliases for @openapi hide (bare, hide, hidden, ignore), @openapi tag single + multi, original-casing preservation for tag values, and that the default schema tag is unaffected when no @openapi annotation is present.

    Pre-existing auth-scheme tests (AuthSchemeRegistrationTests, AuthSchemeLoginTests, AuthLegacyFieldFailFastTests, AuthIntervalNotationTests) and OpenAPI path-parameter tests continue to pass unchanged.

    Configuration summary

    Two new optional Auth keys, mirrored under each Auth:Schemes:<name> Cookies-type entry:

    KeyValuesDefaultPurpose
    Auth:CookieSameSiteStrict / Lax / None / Unspecifiednull (ASP.NET default)SameSite attribute on the cookie. Use None for cross-origin SPA / mobile clients.
    Auth:CookieSecureSameAsRequest / Always / Nonenull (ASP.NET default)When the cookie's Secure attribute is set. Required Always when SameSite is None.

    Five new optional OpenAPI keys under NpgsqlRest:OpenApiOptions:

    KeyValuesDefaultPurpose
    IncludeSchemasstring[]empty = all schemasSchema allow-list for the OpenAPI document.
    ExcludeSchemasstring[]empty = no exclusionSchema deny-list. Applied alongside IncludeSchemas.
    NameSimilarTostring (SIMILAR TO)nullRoutine-name allow pattern.
    NameNotSimilarTostring (SIMILAR TO)nullRoutine-name deny pattern.
    RequiresAuthorizationOnlyboolfalseDocument only authenticated endpoints.

    No keys removed or renamed.

    Out of scope

    • The @authorize annotation continues not to accept a scheme name as a value. Pinning an endpoint to a specific authentication scheme is the job of ASP.NET's [Authorize(AuthenticationSchemes = "...")]; surfacing that through a comment annotation is a separate feature design.
    • SignOutAsync and challenge paths target a specific scheme by name in code, so no changes were needed to ForwardChallenge / ForwardSignOut selectors.
    • The sign-in path (login function returning scheme = '<name>') was already correct in 3.13.0 — this release does not touch it.

    Partner-system integration readiness — what's still missing

    3.15.0 covers the common case of partner integration: JWT Bearer auth, a curated OpenAPI document, and cross-origin cookie auth where applicable. For richer enterprise-grade external-API scenarios, the following capabilities are not yet built in and would land in a future release if there's demand:

    • Per-API-key rate limiting. The partitioned rate limiter exists, but partition keys are typically IP- or user-based today. A first-class "X-Api-Key per-tenant quota" needs custom partition logic — possible to build externally, not configurable out of the box.
    • Idempotency keys. Many partner APIs honor an Idempotency-Key request header so retried POST/PUT calls don't double-charge / double-create. NpgsqlRest has no built-in support; you'd model it in SQL (a seen_keys table consulted before the routine runs) or as custom middleware.
    • HMAC request signing. Some partner programs require body signing on top of JWT (e.g. Stripe-style Signature: t=…,v1=…). Not built in; would need a custom middleware that verifies the signature before NpgsqlRest dispatches.
    • Per-endpoint authentication-scheme binding. @authorize gates by role, not by which auth scheme issued the principal. If you need "partner JWT can hit /api/partner/* but the internal cookie session cannot," that's ASP.NET's [Authorize(AuthenticationSchemes = …)] plumbing — not yet exposed as a comment annotation.
    • Multiple OpenAPI documents per process. One host = one OpenAPI document. The new filters let you scope that document to a partner audience, but you can't serve partner.json and internal.json from the same process. Today: filter to one audience, or run two NpgsqlRest hosts. The plugin's IEndpointCreateHandler interface is single-instance.
    • API-key issuance and rotation flow. Partners rotate keys periodically. NpgsqlRest doesn't ship a key-management UX — you build the issue/rotate/revoke endpoints as ordinary SQL routines on top of the framework.
    • API versioning conventions. No built-in Accept-Version header or path-versioning convention. Today: separate routine names per version (v1_get_orders / v2_get_orders), or filter to a single version per host using the new NameSimilarTo knob.
    • Refresh-token rotation as a first-class story. Refresh paths exist for both BearerToken and JWT schemes, but "rotate on use" with sliding expiration and reuse detection is not documented as load-bearing. Treat as "works for the basic case; harden if you're under threat models that assume token theft."
    • Antiforgery posture for cookie-cross-origin. Antiforgery middleware is wired, but the interaction with SameSite=None cookies — when to require a double-submit token vs. when to rely on SameSite=Strict for state-changing routes — is not explicitly documented. If you go cookie-based cross-origin, audit this for your threat model.

    None of these block a typical "partner team consumes our JWT API + a Swagger UI we host" integration. They're the next layer of polish if NpgsqlRest evolves further toward being a primary external-API platform.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.15.1.html b/guide/changelog/v3.15.1.html new file mode 100644 index 000000000..beebc3e35 --- /dev/null +++ b/guide/changelog/v3.15.1.html @@ -0,0 +1,54 @@ + + + + + + Changelog v3.15.1 (2026-05-11) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.15.1 (2026-05-11)

    Version 3.15.1 (2026-05-11)

    Full Changelog

    Two bug fixes around config-key validation that together caused legitimate Auth:Schemes setups to fail startup under default settings. No new features; no library changes — both fixes live in NpgsqlRestClient (ConfigDefaults.cs, Program.cs).

    Fix: named auth schemes are validated by Type, not by name

    A configuration like

    json
    json
    {
    +  "Auth": {
    +    "Schemes": {
    +      "short_session": {
    +        "Type": "Cookies",
    +        "CookieName": "my_app",
    +        "CookieHttpOnly": true
    +      }
    +    }
    +  }
    +}

    produced startup errors:

    code
    [ERR] Unknown configuration key: Auth:Schemes:short_session:CookieName
    +[ERR] Unknown configuration key: Auth:Schemes:short_session:CookieHttpOnly

    Both keys are documented per-type override keys for Cookies-type schemes (added in 3.15.0) and are read/applied normally at scheme registration time. The validator was flagging them because of a name collision with the docs-style example entries in Auth:Schemes defaults (short_session, api_token, admin_jwt).

    Root cause

    FindUnknownConfigKeys descended into the defaults schema by key name. When a user's scheme name matched one of the documented examples, the validator validated against that example's incomplete key set instead of treating the entry as an open-dictionary item. The same scheme renamed to anything not in the example set took the open-dict path and validated clean — so the bug surfaced only for users whose scheme names happened to match the documentation.

    What changed

    Validation under Auth:Schemes:<name> is now driven by the scheme's Type field, not its name. The validator reads Type from the actual config and selects one of three type-specific schemas:

    • Cookies: Type, Enabled, CookieValid, CookieName, CookiePath, CookieDomain, CookieMultiSessions, CookieHttpOnly, CookieSameSite, CookieSecure.
    • BearerToken: Type, Enabled, BearerTokenExpire, BearerTokenRefreshPath.
    • Jwt: Type, Enabled, JwtSecret, JwtIssuer, JwtAudience, JwtExpire, JwtRefreshExpire, JwtClockSkew, JwtValidateIssuer, JwtValidateAudience, JwtValidateLifetime, JwtValidateIssuerSigningKey, JwtRefreshPath.

    When Type is missing or unrecognized, the validator skips that scheme silently — RegisterAuthSchemes already throws a clearer error at startup, so double-reporting buys nothing.

    Behavior after the fix

    • Every named scheme — regardless of name — is validated against the same key set per its declared Type. Typos like CooieName are still caught for both example-named and custom-named schemes.
    • Cross-type keys (e.g. JwtSecret on a Cookies-type scheme) are now flagged where they previously slipped through under custom-named schemes via the open-dict shortcut.
    • Existing configurations using the docs-example names (short_session, api_token, admin_jwt) start cleanly with any combination of valid per-type override keys.

    Fix: --config and --validate CLI commands now honor ValidateConfigKeys mode

    The three call sites of ValidateConfigKeys() were inconsistent. Normal startup branched on the mode (only "Error" aborts; "Warning" logs and continues; "Ignore" skips entirely). The two CLI command paths did not — both treated any warning as a fatal validation failure regardless of mode.

    For a user running npgsqlrest --validate with the default Config:ValidateConfigKeys: "Warning", this meant:

    • Exit code 1 on the first unknown key, even though the runtime would have started up normally with the same config.
    • --config (dump current configuration as JSONC) suppressed its JSON output and exited 1 instead, even when the only thing wrong was a typo that would have shown up as a warning at runtime.

    What changed

    Both CLI paths now read the validation mode and apply the same rule as normal startup:

    • Error mode: warnings are fatal. --config prints them in red on stderr and exits 1 without dumping JSON. --validate reports configValid: false.
    • Warning mode (default): warnings are surfaced (yellow on stderr for --config, included in --validate text/JSON output) but they don't fail the run. --config proceeds to dump the JSONC. --validate reports configValid: true.
    • Ignore mode: no warnings produced at all (unchanged — the validator short-circuits earlier).

    --validate --json output gains a warningsAreFatal boolean derived from the mode, so machine consumers can decide for themselves what to do with the warnings array independent of how the binary chose to exit:

    json
    json
    {
    +  "valid": true,
    +  "configValid": true,
    +  "validationMode": "Warning",
    +  "warningsAreFatal": false,
    +  "warnings": ["SomeUnknown:Key"],
    +  "connectionTest": "ok"
    +}

    Behavior after the fix

    • npgsqlrest --validate against a config with a typo + ValidateConfigKeys: "Warning" exits 0; the typo is surfaced as a warning. Set ValidateConfigKeys: "Error" (or pass --Config:ValidateConfigKeys=Error) to keep the old fail-fast behavior.
    • npgsqlrest --config always emits the JSONC dump unless the mode is Error and an unknown key is present. Warnings still print to stderr so typos remain visible.
    • Normal startup is unchanged — it was already correct.

    Tests

    • New unit tests in NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs exercise FindUnknownConfigKeys directly: per-type validation for each example scheme name and custom names, cross-type rejection, typo detection, and missing/invalid Type handling.
    • CLI tests in NpgsqlRestTests/CliTests/CliCommandTests.cs cover the Ignore / Warning / Error matrix for both --config and --validate --json.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.15.2.html b/guide/changelog/v3.15.2.html new file mode 100644 index 000000000..1a808e4b3 --- /dev/null +++ b/guide/changelog/v3.15.2.html @@ -0,0 +1,49 @@ + + + + + + Changelog v3.15.2 (2026-05-11) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.15.2 (2026-05-11)

    Version 3.15.2 (2026-05-11)

    Full Changelog

    Patch release finishing the config-validator fix started in 3.15.1. That release made Auth:Schemes validate by Type rather than by name; this one applies the same treatment to the two sister sections (RateLimiterOptions:Policies, CacheOptions:Profiles) that share the same "name-keyed open dictionary" shape, plus a small consistency win for ValidationOptions:Rules. Library version unchanged — fixes live entirely in NpgsqlRestClient/ConfigDefaults.cs.

    Fix: RateLimiterOptions:Policies validates by Type, not by name

    A configuration like

    json
    json
    {
    +  "Config": { "ValidateConfigKeys": "Error" },
    +  "RateLimiterOptions": {
    +    "Enabled": true,
    +    "Policies": {
    +      "login_throttle": {
    +        "Type": "FixedWindow",
    +        "PermitLimit": 10,
    +        "WindowSeconds": 60,
    +        "Partition": { "Sources": [{ "Type": "IpAddress" }] }
    +      }
    +    }
    +  }
    +}

    failed startup with Unknown configuration key: RateLimiterOptions:Policies:login_throttle. The rate limiter itself registered login_throttle and rejected over-limit requests correctly — only the validator was wrong.

    Root cause

    FindUnknownConfigKeys walked the user's policy name (login_throttle) against the defaults schema, which contains illustrative example names (fixed, sliding, bucket, concurrency, per_user). Any other name was flagged unknown. With ValidateConfigKeys: "Error", that killed startup.

    The 3.13.0 migration explicitly grouped RateLimiterOptions:Policies, CacheOptions:Profiles, and ValidationOptions:Rules as the same "object keyed by user-chosen name" shape, but only ValidationOptions:Rules was added to the validator's open-dictionary whitelist. The other two were missed.

    What changed

    FindUnknownConfigKeys now intercepts the descent at RateLimiterOptions:Policies:<name> and picks a per-Type schema:

    • FixedWindow: Type, Enabled, PermitLimit, WindowSeconds, QueueLimit, AutoReplenishment, Partition
    • SlidingWindow: Type, Enabled, PermitLimit, WindowSeconds, SegmentsPerWindow, QueueLimit, AutoReplenishment, Partition
    • TokenBucket: Type, Enabled, TokenLimit, TokensPerPeriod, ReplenishmentPeriodSeconds, QueueLimit, AutoReplenishment, Partition
    • Concurrency: Type, Enabled, PermitLimit, QueueLimit, OldestFirst, Partition

    The shared Partition sub-schema (Sources: [{ Type, Name, Value }], BypassAuthenticated) is appended to every type. When Type is missing/invalid the validator skips that policy silently, matching the runtime behavior in BuildRateLimiter.

    Behavior after the fix

    • Any custom policy name validates by its declared Type; example names continue to validate as before.
    • Typos inside a policy (e.g. PermitLimt) are caught — they were silently ignored when Policies was treated as an opaque dictionary.
    • Cross-type keys are caught: e.g. TokensPerPeriod placed on a FixedWindow policy is flagged, since it belongs to TokenBucket.

    Fix: CacheOptions:Profiles validates by shape

    Same root cause, same shape of fix. Custom profile names (session_cache, api_responses, etc.) failed validation when ValidateConfigKeys: "Error" was set, because the defaults contain example names (fast_memory, shared_redis, date_range_hybrid).

    All cache profiles share the same key set regardless of backend type (Memory / Redis / Hybrid) — only the backend selection varies — so a single flat schema covers every profile:

    code
    Enabled, Type, Expiration, Parameters, When

    Each When rule validates as { Parameter, Value, Then }. Typos inside a profile (e.g. Expirashun) are now caught.

    Improvement: ValidationOptions:Rules now validates rule bodies

    ValidationOptions:Rules was previously on the open-dictionary whitelist, so custom rule names (phone_number, etc.) passed validation — but typos inside a rule (e.g. Patrn instead of Pattern) also passed silently. All validation rules share the same flat key set regardless of Type (NotNull / NotEmpty / Required / Regex / MinLength / MaxLength):

    code
    Type, Pattern, MinLength, MaxLength, Message, StatusCode

    ValidationOptions:Rules has been removed from the whitelist and is now validated against this flat schema. Custom rule names still pass; typos inside any rule now surface.

    Tests

    NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs gained 15 new tests covering all three sections: custom name acceptance, example-name regression coverage, typo flagging, cross-type key detection (rate limiter), Partition sub-block validation, When-rule sub-block validation, and missing-Type skip behavior for rate-limiter policies.

    Files touched

    • NpgsqlRestClient/ConfigDefaults.cs — three new intercepts in FindUnknownConfigKeys, three new schema helpers, ValidationOptions:Rules removed from IsOpenDictionarySection.
    • NpgsqlRestTests/ConfigTests/ConfigValidationTests.cs — 15 new tests.

    No changes to runtime config reading, no breaking changes, no library version bump (the bug was in NpgsqlRestClient only).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.16.0.html b/guide/changelog/v3.16.0.html new file mode 100644 index 000000000..1fc3b99fa --- /dev/null +++ b/guide/changelog/v3.16.0.html @@ -0,0 +1,40 @@ + + + + + + Changelog v3.16.0 (2026-05-20) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.16.0 (2026-05-20)

    Version 3.16.0 (2026-05-20)

    Full Changelog

    Minor release fixing a long-standing class of bugs in the JSON-to-parameter parsers for the timestamp, timestamptz, time, and timetz PostgreSQL types: the parsers were silently shifting incoming values by the host process's UTC offset. Bumped to minor (not patch) because the corrected behavior changes how naive ISO strings (no Z, no offset) are interpreted on non-UTC hosts — see Breaking change below. The shift was invisible on UTC hosts (the default for mcr.microsoft.com/dotnet/aspnet and almost every Linux container) and only surfaced once the same image ran somewhere with TZ set to anything else — a Windows dev box, a Kubernetes pod with TZ overridden, or a non-UTC CI runner — at which point stored values diverged from the JSON the caller sent by the host's offset.

    Fix: datetime parsers are now host-TZ-independent

    TryParseTimestamp, TryParseTimestampTz, TryParseTime, and TryParseTimeTz in NpgsqlRest/ParameterParsers.cs all relied on the parameter-less DateTime.TryParse(value) overload. That overload's default DateTimeStyles.None converts offset-bearing strings to the host's local TZ and tags the result Kind=Local. The two *Tz parsers then called DateTime.SpecifyKind(v, DateTimeKind.Utc) on the local-shifted value — but SpecifyKind only relabels the kind, it does not convert. The result was a host-local wall-clock value labelled UTC, written to Postgres with a silent shift.

    The timestamp and time parsers used the same buggy parse and sent the local-shifted value directly to Npgsql, which transmits the wall-clock verbatim for a without time zone column — the same silent shift, same size as the host's offset.

    All four parsers now use:

    csharp
    csharp
    DateTime.TryParse(
    +    value,
    +    CultureInfo.InvariantCulture,
    +    DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
    +    out var v)
    • AssumeUniversal treats naive ISO strings (no Z, no offset) as UTC — the canonical JSON-over-HTTP convention.
    • AdjustToUniversal converts any Z-bearing or offset-bearing value to UTC.

    The result is a DateTime with Kind=Utc carrying the true UTC instant regardless of the host's TZ. The *Tz parsers use it directly. The without time zone parsers strip the kind back to Unspecified so Npgsql sends the UTC clock-time as the naive wall-clock value, matching the column semantics.

    Why this was hidden so long

    Almost every production container runs TZ=UTC by default. On a UTC host, DateTime.TryParse(...)'s local-conversion is a no-op and the SpecifyKind(Utc) "lie" coincidentally matches reality. The bug only manifests once the same code is deployed where TZ is anything else. The first symptom is usually a downstream report along the lines of "we send 2026-05-20T06:00:00Z and Postgres stored 08:00" — which is exactly the host's UTC offset.

    The existing MultiParamsTests2 / MultiParamsQueryStringTests2 test pairs already hinted at this — both used Should().Match(t => t == "12:06:59..." || t == "11:06:59...") style assertions with a comment that read "integration server seems to have a different datetime alltogether". That was the bug, papered over. After this fix both tests assert single deterministic values.

    TryParseDate left alone

    DateOnly.TryParse rejects Z- and offset-bearing strings outright (verified across UTC, America/Los_Angeles, Europe/Zagreb, Pacific/Auckland) — it does not silently shift, so the date parser was not affected by the host-TZ bug class. It was however a separate papercut: callers sending full ISO timestamps (e.g. "2026-05-20T03:00:00Z") to a date column got a flat parse failure. TryParseDate now falls back to a DateTime parse and extracts the date portion when DateOnly.TryParse rejects the input, honoring the same JsonTimestampsAreUtc semantic as the other datetime parsers (UTC date when the flag is true, host-local date when false).

    Breaking change

    JSON timestamps are now interpreted as UTC:

    • Z-suffixed and offset-bearing ISO strings are converted to UTC.
    • Naive ISO strings (no offset, no Z) are assumed UTC rather than interpreted as host-local time.

    Callers who relied on the previous "JSON is host-local" behavior — usually by accident, because the host happened to be UTC — will see no change. Callers who sent Z strings expecting UTC were silently broken on non-UTC hosts and are now correct.

    Opt-out: NpgsqlRestOptions.JsonTimestampsAreUtc

    Users whose downstream code genuinely depends on the legacy "naive timestamps are host-local" behavior — and who cannot update those callers to send Z-suffixed values — can restore the pre-3.16.0 behavior by setting JsonTimestampsAreUtc to false:

    • Library: new NpgsqlRestOptions { JsonTimestampsAreUtc = false, ... }.
    • Client (appsettings.json): "NpgsqlRest": { "JsonTimestampsAreUtc": false } (default is true).

    When false, the four parsers fall back to the bare DateTime.TryParse(value) overload — Z/offset strings get host-local-converted and tagged Kind=Local, naive strings get parsed as Kind=Unspecified, and the *Tz parsers re-apply SpecifyKind(Utc) on top. That reproduces the exact pre-3.16.0 code path. Note that this is not recommended for new deployments: it puts you back in the bug class the rest of this release fixes. The flag exists purely as a compatibility escape hatch.

    Tests

    New file NpgsqlRestTests/HostTimeZoneIndependenceTests.cs covers all four parsers via echo functions and json_build_object round-trips:

    • timestamptz with Z suffix, with numeric offset, and naive (assumed UTC)
    • timestamp with Z suffix (stored as naive UTC clock-time)
    • timetz with Z suffix (round-trips as UTC)
    • time with Z suffix (UTC clock-time extracted)

    Each assertion is exact — no host-TZ-tolerant ORs. The fixture forces the database to UTC at creation (alter database … set timezone to 'UTC'), so the assertions stay deterministic across runners. To verify host-TZ independence at the parser layer, run the suite under a non-UTC TZ env var (TZ=America/Los_Angeles dotnet test, for example) — the tests must still pass.

    The two existing MultiParams* tests had their loose Should().Match(...) assertions for timestamptz and timetz replaced with single-value Should().Be(...) assertions, now that the parsers produce deterministic output.

    Files touched

    • NpgsqlRest/ParameterParsers.cs — four parsers switched to AssumeUniversal | AdjustToUniversal.
    • NpgsqlRestTests/HostTimeZoneIndependenceTests.cs — new, six tests covering the four type variants.
    • NpgsqlRestTests/ParamTests/MultiParamsTests2.cs — tightened timestamptz / timetz assertions.
    • NpgsqlRestTests/ParamTests/MultiParamsQueryStringTests2.cs — same.

    No config changes, no API surface changes.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.16.1.html b/guide/changelog/v3.16.1.html new file mode 100644 index 000000000..d5905989a --- /dev/null +++ b/guide/changelog/v3.16.1.html @@ -0,0 +1,41 @@ + + + + + + Changelog v3.16.1 (2026-06-01) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.16.1 (2026-06-01)

    Version 3.16.1 (2026-06-01)

    Full Changelog

    Patch release that makes cache stampede protection actually fire for cached routine responses. The cache-options documentation has advertised stampede protection as a HybridCache feature, but the integration used IRoutineCache as a synchronous probe (Get / AddOrUpdate) that could not carry the SQL execution as the cache factory — so the protection never engaged. A burst of identical concurrent requests against a cold cache executed the underlying query N times, each taking a connection. In the worst case this exhausted Postgres' connection pool (remaining connection slots are reserved for roles with the SUPERUSER attribute), which combined with connection-retry backoff could pin the pool long enough to affect every app sharing the database.

    What changed

    IRoutineCache gains GetOrCreateAsync (additive)

    A new method routes the cold-cache work through the cache so concurrent callers for the same key coalesce into a single execution:

    csharp
    csharp
    ValueTask<object?> GetOrCreateAsync(
    +    RoutineEndpoint endpoint,
    +    string key,
    +    Func<CancellationToken, ValueTask<object?>> factory,
    +    TimeSpan? overrideExpiration = null,
    +    CancellationToken cancellationToken = default);

    It ships as a default interface method (plain probe → factory → store, no coalescing), so any pre-existing custom IRoutineCache implementation compiles and behaves exactly as before — it simply gains no stampede protection until it overrides the method.

    Note: although this is a new public API surface (conventionally a minor bump), it is shipped as a patch because it fixes an advertised-but-broken feature and is fully backward compatible via the default implementation.

    Stampede protection per backend

    • Memory (RoutineCache) and Redis (RedisCache) — coalesce concurrent factory invocations through an in-flight ConcurrentDictionary<string, Lazy<Task>>. A burst collapses to one execution; the rest await the in-flight result.
    • HybridCache (HybridCacheWrapper) — delegates straight to HybridCache.GetOrCreateAsync, so Microsoft's built-in stampede protection now genuinely engages.

    Middleware paths

    • Scalar single-value and passthrough proxy responses (value-shaped) route through GetOrCreateAsync. The connection is opened inside the factory, so coalesced waiters never touch the database. The passthrough proxy case additionally coalesces identical upstream HTTP calls.
    • Records / sets (the streaming path) use a per-key execution gate instead. This path streams rows to the client and disables caching mid-stream once a response exceeds MaxCacheableRows (default 1000), which does not fit the "compute one value, cache it, share it" factory model. The gate serializes concurrent requests for a key: the lead executes and (within the row limit) populates the cache, so the rest get a cache hit instead of re-executing. This caps concurrent DB executions per key at one in all cases — including over-limit responses, which serialize rather than run in parallel.

    Effect

    A burst of N identical requests against a cold cache now results in one database execution (within-limit) or a single serialized execution at a time (over-limit), instead of N concurrent executions. The worst-case fan-out from one event is bounded by the number of distinct cache keys (bounded by the schema), not by the number of clients.

    Test coverage (read this honestly)

    Automated coverage (NpgsqlRestTests/RoutineCacheTests/CacheStampedeTests.cs) runs against the in-memory backend with a live Postgres and asserts execution counts directly:

    • 50 concurrent cold scalar requests → exactly 1 execution; warm-cache burst → 0 further executions; 4 distinct keys → exactly 4 executions (one per key).
    • 50 concurrent cold set requests (within limit) → exactly 1 execution; over-limit set (1001 rows) → one execution per request, never cached, all responses correct.

    The HybridCache path relies on Microsoft's own tested coalescing (Microsoft.Extensions.Caching.Hybrid) and the Redis path's coalescing is verified by inspection — neither is exercised by the test harness, which boots the core library with the default memory cache. Claims about those two backends are not backed by an automated test in this repo.

    Known limitations

    • Cross-process coalescing is out of scope. Coalescing is in-process per NpgsqlRest instance; multiple instances each execute once. (HybridCache's Redis layer still shares the cached value across instances.)
    • Over-limit sets serialize, not coalesce. Responses above MaxCacheableRows (default 1000) are never cached, so the per-key gate makes concurrent requests for such an endpoint run one-at-a-time rather than sharing a result. This is deliberate: it caps both concurrent DB executions and peak memory (only one large set renders per key at a time) — but it does reduce throughput for a cached endpoint that returns more than MaxCacheableRows rows under load. Since such an endpoint is never actually cached, the right fix when this matters is to raise MaxCacheableRows so the result caches and coalesces, or to drop the cached annotation (restoring fully concurrent, uncached execution).
    • The records/sets gate is held during the response stream. Because the gate wraps streaming to the client (not just the DB read), a slow or stalled lead client can delay other clients requesting the same key until it finishes or its request cancels. Waiters honor their own cancellation token, so a waiter that gives up is never stuck. The scalar and proxy paths are unaffected — their coalescing slot covers only the upstream call, not the client write.
    • CommandCallbackAsync short-circuit under coalescing. If a user-supplied CommandCallbackAsync short-circuits the response on a cached scalar endpoint, coalesced waiters (not the lead) may observe an empty response. This affects only that specific hook on a cached endpoint.
    • Cancellation. The shared factory runs on the lead caller's token; if the lead cancels mid-flight, waiters retry (re-probe the cache, or one becomes the new lead). A waiter may rarely observe cancellation if the lead cancels at the exact moment of coalescing. This is a deliberate safety choice — the factory uses the lead's live connection, so fully detaching the shared work risks using a disposed connection.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.16.2.html b/guide/changelog/v3.16.2.html new file mode 100644 index 000000000..58f1d1ac2 --- /dev/null +++ b/guide/changelog/v3.16.2.html @@ -0,0 +1,63 @@ + + + + + + Changelog v3.16.2 (2026-06-02) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.16.2 (2026-06-02)

    Version 3.16.2 (2026-06-02)

    Full Changelog

    Patch release that makes the rate-limiter rejection status code and message overridable per policy. Previously RateLimiterOptions:StatusCode and RateLimiterOptions:StatusMessage were the only values returned for a rejected request, applied globally regardless of which policy tripped. A config like a login_throttle policy with the message "Too many login attempts…" would return that same login-specific text for every rate-limited endpoint, even ones that have nothing to do with logins.

    What changed

    Per-policy StatusCode / StatusMessage overrides

    Each named policy under RateLimiterOptions:Policies may now set its own StatusCode and/or StatusMessage:

    jsonc
    jsonc
    "RateLimiterOptions": {
    +  "Enabled": true,
    +  "StatusCode": 429,                                  // global default
    +  "StatusMessage": "Too many requests. Please slow down.",
    +  "Policies": {
    +    "login_throttle": {
    +      "Type": "FixedWindow",
    +      "PermitLimit": 10,
    +      "WindowSeconds": 60,
    +      "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +      "Partition": { "Sources": [ { "Type": "IpAddress" } ] }
    +    },
    +    "api": {
    +      "Type": "TokenBucket",
    +      "StatusCode": 503,
    +      "StatusMessage": "API capacity reached. Retry shortly."
    +    }
    +  }
    +}

    A request rejected by a given policy now returns that policy's status code and message; a policy that omits either field inherits the global value. The override that applies is resolved at rejection time from the endpoint's rate-limiter policy name, so it is correct even though ASP.NET Core exposes only a single global OnRejected/RejectionStatusCode.

    This is fully backward compatible: configs that set only the global StatusCode/StatusMessage behave exactly as before — those values simply become the defaults that policies may override.

    New ready-to-use login_throttle default policy

    The shipped appsettings.json now includes a disabled ("Enabled": false) login_throttle policy — 10 attempts per minute partitioned per client IP, with its own rejection message — so the common case is one flag away:

    jsonc
    jsonc
    "login_throttle": {
    +  "Type": "FixedWindow",
    +  "Enabled": false,
    +  "PermitLimit": 10,
    +  "WindowSeconds": 60,
    +  "QueueLimit": 0,
    +  "AutoReplenishment": true,
    +  "StatusMessage": "Too many login attempts. Please wait a minute and try again.",
    +  "Partition": { "Sources": [ { "Type": "IpAddress" } ], "BypassAuthenticated": false }
    +}

    Apply it to a login endpoint with the rate_limiter login_throttle comment annotation (or set it as DefaultPolicy).

    Test coverage

    NpgsqlRestTests/AuthTests/RateLimiterPerPolicyTests.cs (fixture RateLimiterPerPolicyTestFixture) boots the limiter through the same wiring BuildRateLimiter emits and drives the real Builder.ApplyRateLimiterRejectionAsync helper over HTTP, asserting:

    • a policy with a message-only override returns its own message but the global status code,
    • a policy overriding both returns its own status code (503) and message,
    • a policy with no override inherits the global status code and message.

    Config-key validation for the new per-policy StatusCode/StatusMessage keys is covered in ConfigTests/ConfigValidationTests.cs.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.16.3.html b/guide/changelog/v3.16.3.html new file mode 100644 index 000000000..3cd5364f4 --- /dev/null +++ b/guide/changelog/v3.16.3.html @@ -0,0 +1,54 @@ + + + + + + Changelog v3.16.3 (2026-06-03) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.16.3 (2026-06-03)

    Version 3.16.3 (2026-06-03)

    Full Changelog

    Patch release that lets static-content parsing template environment-variable values into served files, alongside the existing per-user claim templating. This is aimed at Single-Page Apps deployed to Kubernetes: the SPA bundle is built once, and app-wide values (BUILD_LABEL, feature-flag toggles, analytics IDs) are injected from pod env vars at boot without rebuilding the bundle per environment.

    What changed

    AvailableEnvVars under StaticFiles:ParseContentOptions

    A new optional config key lists environment variable names whose values are templated into static content using the same {NAME} tag syntax the claim path already uses:

    jsonc
    jsonc
    "StaticFiles": {
    +  "Enabled": true,
    +  "ParseContentOptions": {
    +    "Enabled": true,
    +    "FilePaths": [ "/index.html" ],
    +    "AvailableClaims": [ "user_id", "user_name" ],
    +    "AvailableEnvVars": {
    +      "BUILD_LABEL": "local",
    +      "DEMO_FLAG": "false",
    +      "TRACKING_ID": ""
    +    }
    +  }
    +}
    html
    html
    <script>
    +  window.__appConfig = {
    +    userId: {user_id},          // claim → 123 or null
    +    buildLabel: {BUILD_LABEL},  // env   → "demo" (or "local" default)
    +    demoMode: {DEMO_FLAG} === "true"
    +  };
    +</script>

    Behaviour details:

    • Two forms. AvailableEnvVars accepts an array of names (["BUILD_LABEL"]; a missing variable resolves to the empty string) or an object of name→default pairs ({"DEMO_FLAG":"false"}; the default is used when the variable is absent). AvailableClaims gains the same object form, so an absent claim can resolve to a configured default instead of NULL.
    • Resolved once at startup. Env values are read at parser construction. A K8s pod restart re-reads them; changing a value in a running process is not picked up.
    • JSON-escaped, like claims. Each value is substituted as a complete, escaped JSON literal, so templates use a bare {NAME} token (no surrounding quotes) and an accidental quote/backslash in a value cannot break the JS string.
    • Claims win on collision. If a name exists both as a user claim and an env var, the per-request claim value takes precedence.

    Security note

    Anything listed in AvailableEnvVars is templated into static content served to any client — treat it as a public allowlist. Never list a secret (database password, API key, signing token). Resolution is an explicit per-name lookup; the whole environment is never exposed. This is distinct from the server-side Config:ParseEnvironmentVariables mechanism, which substitutes {ENV} tokens into appsettings.json values that never leave the server.

    This is fully backward compatible: the new key is optional, and configs that omit it behave exactly as before.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.17.0.html b/guide/changelog/v3.17.0.html new file mode 100644 index 000000000..e92fd7481 --- /dev/null +++ b/guide/changelog/v3.17.0.html @@ -0,0 +1,38 @@ + + + + + + Changelog v3.17.0 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.17.0

    Version 3.17.0 (2026-06-13)

    Full Changelog

    The headline of this release is MCP (Model Context Protocol) support — NpgsqlRest can project explicitly opted-in PostgreSQL routines as MCP tools that an AI agent can discover and call. Supporting that, the release adds neutral plugin extension points, makes one breaking change to the OpenAPI C# API, and ships two configuration/runtime fixes.

    Walkthrough

    For a hands-on tour — an "Acme Store" built from .sql files, a dual REST + MCP web page, and a real Claude agent driving the store — see the blog post Turn PostgreSQL into MCP Tools an AI Agent Can Call.

    New Features

    MCP (Model Context Protocol) server — new NpgsqlRest.Mcp plugin

    NpgsqlRest can now expose opted-in PostgreSQL routines as MCP tools, so an AI agent can discover them (tools/list) and execute them (tools/call) over the Model Context Protocol (spec 2025-11-25). The entire MCP layer lives in the new plugin — core stays protocol-agnostic, built only on the neutral extension points below.

    Opt-in, never automatic. A routine becomes a tool only when its PostgreSQL comment carries the mcp annotation:

    • mcp — expose as a tool; description derived from the comment prose.

    • mcp <text> — expose, with <text> as an inline (explicit) description.

    • mcp_description <text> (alias mcp_desc) — explicit, authoritative description.

    • mcp_name <name> — override the tool name (default: the routine name).

    • A bare mcp with no HTTP tag is MCP-only — the tool exists with no public HTTP route. The HTTP tag controls the REST route and mcp controls the tool, independently: HTTP GET + mcp = both interfaces; mcp alone = tool only (an endpoint that exists solely because a plugin requested it defaults to internal-only, so opting into MCP never silently widens the HTTP surface); internal remains the explicit way to hide a declared HTTP route. Works identically for SQL file endpoints (a .sql file with mcp and no HTTP tag becomes an MCP-only tool; files with neither annotation are skipped as non-endpoint scripts, as before). All other annotations (authorize, parameter handling, …) apply unchanged.

      Description precedence — the highest-priority source that is present wins, regardless of the order the lines appear in the comment; an explicit description suppresses the comment-prose fallback, so unrelated comment lines never leak in: mcp_description › inline mcp <text> › comment prose › routine name.

    The endpoint. A single Streamable-HTTP JSON-RPC endpoint (default /mcp, POST only). It implements the lifecycle (initialize → protocol version + tools capability + serverInfo; notifications/initialized202; ping), tools/list (with a JSON-Schema inputSchema per tool, derived from the routine's parameters), and tools/call. Transport rules per spec: the Origin header is validated (DNS-rebinding protection — a present, untrusted origin → 403); a present MCP-Protocol-Version other than 2025-11-25400; GET405 (no SSE).

    Calling a tool. tools/call runs the routine through the same pipeline as the HTTP endpoint, forwarding the authenticated principal so authorize checks apply. Arguments map to the routine as a query string (GET/DELETE), a JSON body (POST/PUT), or path-segment substitution. The result carries:

    • structuredContent (always a JSON object): a single value → { "value": … }; a record/composite (or a set collapsed with single) → the object itself; a set → { "items": [ … ] }. The text content block carries the same JSON, serialized (backward-compatibility).
    • outputSchema (declared on the tool) derived from the routine's return columns, nullable-aware so results always conform.
    • Two error channels: business failures → isError: true in the result; structural failures (unknown method/tool, malformed request) → JSON-RPC errors.

    Authorization — OAuth 2.1 Resource Server (bring-your-own Authorization Server; token validation reuses the host's bearer authentication — NpgsqlRest is not an Authorization Server). Configured under McpOptions:Authorization:

    • Protected Resource Metadata (RFC 9728) served at /.well-known/oauth-protected-resource{UrlPath} when an Authorization Server is configured.
    • RequireAuthorization gates the endpoint → 401 with WWW-Authenticate: Bearer resource_metadata="…" (RFC 9728 §5.1) so the client can discover the AS; the PRM document itself stays anonymous.
    • Audience binding (RFC 8707): with a canonical Audience configured, a token must carry it (aud claim) or it is rejected with 401.
    • Per-tool authorization: the routine's authorize/role check runs on tools/call401 if called anonymously, 403 insufficient_scope if the role is missing (RFC 6750 §3.1; challenges include scope and resource_metadata). No authorization logic is duplicated in the plugin — it reuses core's check.

    ConfigurationNpgsqlRest:McpOptions, disabled by default, surfaced in --config, --config-schema, and the JSON schema: Enabled, UrlPath (/mcp), ServerName (null → database name → "NpgsqlRest"), ServerVersion ("1.0.0"), Instructions, ToolDescriptionSuffix, RateLimiterPolicy, AllowedOrigins, and the Authorization object (RequireAuthorization, AuthorizationServers, ScopesSupported, Audience, ProtectedResourceMetadataPath, FilterToolsByRole).

    Diagnostics & current limitations.

    • Enabling MCP does not enable authentication — it is configured separately (the host's Auth section). If RequireAuthorization is on but no authentication scheme is registered, a startup warning is logged.
    • A routine annotated mcp that also uses a feature with no MCP equivalent (login, logout, basic auth, upload, SSE) logs a build-time warning.
    • A routine's rate_limiter annotation does not carry to MCP (tools/call bypasses route middleware); pairing it with mcp logs a build-time warning. Use McpOptions:RateLimiterPolicy (a host-registered ASP.NET rate-limiter policy) to throttle the whole /mcp endpoint.
    • tools/list lists every opted-in tool by default (keeping them discoverable); set Authorization.FilterToolsByRole to hide tools the caller can't run. Authorization is enforced on tools/call regardless.
    • The JSON-RPC layer is hand-rolled over System.Text.Json.Nodes with relaxed escaping (conventional application/json output) — no reflection-based serialization, AOT-safe (verified via dotnet publish -p:PublishAot=true).

    Plugin extension points on RoutineEndpoint

    Neutral, plugin-facing hooks were added so a plugin can own its comment annotations without leaking plugin concepts into core (both MCP and the OpenAPI plugin are now built on these):

    • IEndpointCreateHandler.HandleCommentLine(...) (new default-interface method) — core offers each unrecognized comment line to handlers within its single parse pass; a handler claims it by returning a CommentLineResult (a log label + RequestsEndpoint). Tokens are pre-split by core. Non-breaking.
    • RoutineEndpoint.Items (lazy IDictionary<string, object?>) + TryGetItem — a per-endpoint property bag for plugin metadata (the HttpContext.Items pattern), namespaced by key.
    • RoutineEndpoint.UnhandledCommentLines (string[]?) — comment prose that neither core nor any handler claimed.
    • CommentsMode.OnlyAnnotated (new) — creates an endpoint when the comment has an HTTP tag or a plugin requests one. An endpoint created solely by a plugin request (no HTTP tag) defaults to internal-only — the plugin asked for a projection (an MCP tool), not a route — so a bare mcp is MCP-only (a debug log notes the defaulting). The client now defaults to OnlyAnnotated; existing OnlyWithHttpTag configs are unaffected — it is kept as an identical-behavior alias.
    • IEndpointCreateHandler.EndpointRequestingAnnotations (new default-interface property, default empty) — the annotation keywords for which the handler requests endpoints (Mcp: mcp, mcp_name, mcp_description, mcp_desc). Lets sources with a cheap textual pre-gate recognize endpoint candidates: the SQL file source passes a file whose comment carries an HTTP tag or one of these keywords, so a bare-mcp .sql file becomes an MCP-only tool while scripts with neither are still skipped without ever being described.

    {name} annotation substitution can resolve allowlisted environment variables

    The {name} placeholders in annotation values (response headers, custom parameters, HTTP custom type URL/headers/body) could only resolve request parameters. They can now also resolve allowlisted environment variables, so e.g. an outbound API key or a per-pod server name doesn't have to be routed through a request parameter:

    sql
    sql
    comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}
    +Authorization: Bearer {WEATHER_API_KEY}';
    • Opt-in allowlist NpgsqlRest:AvailableEnvVars (mirrors StaticFiles:ParseContentOptions:AvailableEnvVars): array of names, or an object of name → default. Only listed names are ever read from the environment — the allowlist is the security boundary. (C# API: NpgsqlRestOptions.SubstitutionEnvironmentVariables, a resolved name → value dictionary.)
    • Resolved once at startup, matched case-insensitively, injected as the raw value. A routine parameter of the same name takes precedence.
    • Security: a value substituted into a response header is sent to the client — reserve secrets for outbound HTTP-type calls / custom parameters, and use response headers only for non-secret values (e.g. server/environment name).

    TsClient: ExportTypes — emit request/response interfaces with the export keyword

    The TypeScript client generator (NpgsqlRest.TsClient) previously emitted its request/response (and composite) interfaces as plain interface declarations: module-private when inlined into the client file (CreateSeparateTypeFile: false), or ambient/global in the separate {name}Types.d.ts file (the default). Neither form could be imported by other modules. The new ExportTypes option (config NpgsqlRest:ClientCodeGen:ExportTypes, default false) emits them as export interface so they can be imported:

    • Inline (CreateSeparateTypeFile: false) — interfaces are emitted as export interface in the same file as the functions.
    • Separate file (CreateSeparateTypeFile: true) — the type file becomes an importable module {name}Types.ts (export interface …) instead of an ambient {name}Types.d.ts, and the generated client file gets an import type { … } from "./{name}Types"; referencing the named types.

    Has no effect when SkipTypes is true. Defaulting to false keeps existing output byte-for-byte unchanged.

    Breaking Changes

    ⚠️ Safer configuration defaults: CORS credentials, passkey requirements, connection testing

    Three configuration defaults changed as part of a security/consistency audit of the shipped appsettings.json against the in-code defaults. You are affected only if your custom configuration omits these keys — set them explicitly to keep the old behavior.

    • Cors:AllowCredentials now defaults to false (was true). Credentials (cookies, authorization headers) in cross-origin requests must now be enabled deliberately, and only together with an explicit AllowedOrigins list. This only matters when Cors:Enabled is true.
    • Auth:PasskeyAuth:UserVerificationRequirement and ResidentKeyRequirement code defaults are now "required" (were "preferred"). The shipped appsettings.json already said "required" — the in-code fallback and --config defaults disagreed; they now match the stronger, documented posture.
    • ConnectionSettings:TestConnectionStrings code default is now true (was false). Same class of fix: the shipped appsettings.json already said true; the in-code default now agrees, so connection strings are tested at startup even when the key is omitted.

    ⚠️ OpenAPI annotation handling moved out of core (C# API only)

    The public properties RoutineEndpoint.OpenApiHide and RoutineEndpoint.OpenApiTags were removed, along with the core openapi comment-annotation handler. The OpenAPI plugin now parses openapi hide / hidden / ignore / tag <…> itself (from UnhandledCommentLines).

    • No change for annotation users — the openapi … comment annotations behave exactly as before (and, as before, only take effect when the OpenAPI plugin is loaded).
    • Affected only if your code sets endpoint.OpenApiHide / endpoint.OpenApiTags directly (e.g. in an EndpointCreated callback) — use the openapi comment annotation instead.

    Fixes

    Internal-only endpoints are excluded from generated client artifacts and API docs

    Endpoints marked internal (no public HTTP route — proxy/HTTP-type-callable, or now a bare-mcp MCP-only routine) were still emitted into the generated TypeScript client (a fetch wrapper), the generated .http file (a request line), and the generated OpenAPI document (a path entry). All target a route that returns 404, so the generated artifacts advertised endpoints that don't exist. The TsClient, HttpFiles, and OpenApi plugins now skip InternalOnly endpoints. (Surfaced by the new MCP-only mode, where a bare @mcp routine has no HTTP route.)

    🔴 Security: SSE scope hints were not enforced — hint-scoped events were delivered to every subscriber

    Events published with a per-event scope override — RAISE INFO ... USING HINT = 'authorize' or USING HINT = 'authorize <role-or-user> ...' — were delivered to all connected SSE subscribers, including subscribers without the named role and unauthenticated subscribers. The hint was parsed correctly, but a control-flow bug (else if chaining) skipped the authorization checks whenever a hint was present. Endpoint-level scoping via the sse_scope annotation (no hint) was NOT affected.

    Impact: any deployment using the documented per-user/per-role USING HINT pattern (e.g. private user messages or role-targeted notifications over SSE) was broadcasting those events to every connected subscriber. Upgrade is strongly recommended for anyone using SSE with hint-based scoping.

    Fixed by decoupling scope enforcement from hint parsing so the Matching/Authorize checks always run on the effective scope. Covered by new tests proving delivery-by-ordering: a role-scoped event reaches only matching subscribers, a bare authorize event reaches only authenticated subscribers, and anonymous subscribers receive neither.

    Malformed JSON request body now returns 400 Bad Request (was 404 Not Found)

    When an endpoint expects a JSON body and the request body is present but not a parseable JSON object (truncated JSON, a bare array/string, …), the response is now 400 Bad Request. Previously the failed parse fell through to parameter matching and surfaced as a misleading 404 Not Found. Parse failures were and still are logged; valid requests and empty-body handling are unchanged.

    Passkey/WebAuthn diagnostics: CBOR decode failures are no longer silent

    • A malformed WebAuthn attestation object now logs a Warning naming the decode failure (exception + payload length — never the payload itself) instead of failing silently into a generic attestation_invalid error. This gives operators an audit trail for both debugging and attack detection.
    • Indefinite-length CBOR arrays (legal in the lax conformance mode the decoder uses) are now decoded correctly; previously they failed the whole attestation.
    • A startup warning is logged when Passkey authentication is enabled with an empty RelyingPartyOrigins list — in that state WebAuthn origin validation accepts any origin, which is not recommended for production.

    {name} parameter-value placeholders: case-insensitive matching + typo warning

    The {name} placeholders that inject a request's parameter values into annotation values (response headers incl. Content-Type, custom parameters such as upload paths, and HTTP custom type URL/headers/body) had two rough edges:

    • Case sensitivity was inconsistent. Substitution matched names case-sensitively, while the related resolved-parameter SQL expression resolver matched case-insensitively. Substitution is now case-insensitive too ({userId}, {USERID}, {userid} all resolve the same parameter), consistent with PostgreSQL identifier folding.
    • Typos were silent. An unknown placeholder is left as literal text at request time (unchanged), but a misspelling like {_fil} for {_file} shipped silently into a header/path. NpgsqlRest now logs a build-time warning naming the unknown placeholder. The check covers response headers and custom parameters and only flags identifier-shaped tokens, so {0} and JSON-like {"a":1} are never mistaken for placeholders.

    Bare @cached (no parameter list) used only the routine name as the cache key

    @cached without an explicit parameter list is documented to key on all routine parameters, but the implementation left the cache-key parameter set empty — so the key was just the routine identifier, and every call returned the first response cached for that routine regardless of inputs until the TTL expired (a search/filter endpoint would serve the first query's results to every subsequent query). Endpoints that listed parameters explicitly (@cached p1, p2) were unaffected. All cache backends (Memory, Redis, Hybrid) were affected. Fixed by treating "no list" as "every parameter" at annotation-parse time.

    HybridCache Cache key contains invalid content on nullable cached params

    When CacheOptions.Type was Hybrid and a cached routine had a nullable parameter, every call where that parameter was null logged Microsoft.Extensions.Caching.Hybrid: Cache key contains invalid content and silently bypassed the cache — the endpoint still ran against the DB and returned correct data, but lost the cache hit and stampede protection for that key. Root cause: NpgsqlRest's internal cache-key encoding used a null byte (\x00) in its null marker, which HybridCache rejects.

    HybridCacheWrapper now hashes every key into a SHA-256 hex string before passing it to HybridCache, so keys are valid regardless of source content; the null marker source-side also no longer uses \x00 (it is delimited by the existing \x1F separator), which is friendlier to Redis keys and log collectors across all backends. The UseHashedCacheKeys / HashKeyThreshold options keep their original purpose (Redis-backend key length / memory) and are simply a no-op for the Hybrid backend now. No user action required — Hybrid in-memory entries are flushed on restart.

    JSON command parameters accept json, jsonb, or text

    JSON payloads passed to user-authored SQL commands were bound with a hardcoded json type, so a function declaring the receiving parameter as jsonb or text failed at runtime with PostgreSQL 42883 "function does not exist" — even though the documentation states all three are acceptable.

    The binding now uses an untyped (unknown) parameter, which PostgreSQL resolves server-side via the target type's input function. Affected commands: external-auth Auth.External.LoginCommand ($4 provider data, $5 analytics), CSV/Excel upload row commands (per-row metadata, Excel JSON data), and the Passkey/Fido2 commands. Fully backward compatiblejson-typed parameters are unchanged; jsonb and text now also work, and NULL / quoted / array values round-trip correctly.

    Optional {NAME} and required {!NAME} environment-variable placeholders

    With Config:ParseEnvironmentVariables enabled (the default), config values support two placeholder forms, for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not — so typed bool/int reads fall back to their default instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.

    This fixes a startup crash: previously a missing optional variable left an unresolved {NAME} token that a typed read (e.g. GetConfigBool) rejected. Genuinely invalid values (e.g. "maybe" for a bool) still throw.

    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)
    +"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Tests

    • An MCP test suite covering the lifecycle, tools/list / tools/call, structuredContent and outputSchema across return shapes (scalar, record, set, array, custom composite), parameter mapping (query / body / path; typed, optional, null, and json arguments), authorization (PRM, 401/403, audience binding), transport rules, and protocol edge cases.
    • A binding-contract test locking the PostgreSQL/Npgsql resolution the JSON-parameter fix relies on (json-only for the old binding; json/jsonb/text for the new one, including NULL and round-trip integrity), plus end-to-end CSV upload tests for a row-command metadata parameter declared as json, jsonb, and text.
    • Config tests for optional {NAME} (resolves when set; left untouched / defaults when not — including Serilog-template preservation) and required {!NAME} (throws when unset) across GetConfigBool / GetConfigInt / GetConfigStr and the ResolveEnv resolver.
    • Malformed-JSON body tests: truncated JSON and non-object JSON → 400; valid body unchanged.
    • SSE hardening suite: hint-scope authorization (the security-fix regression test — role-scoped, authenticated-scoped, and unscoped delivery across three differently-authenticated subscribers), multi-subscriber exactly-once fan-out, per-stream publish ordering, and subscriber-disconnect resilience.
    • Cache concurrency races: TTL-expiry under concurrent bursts (exactly one execution per cache window) and concurrent invalidation + read storms (no errors, cache coherent after).
    • CRUD endpoint authorization parity: table-comment authorize/roles enforced across select/insert/update/delete variants (401 anonymous, 403 wrong role, full cycle with the right role).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.18.0.html b/guide/changelog/v3.18.0.html new file mode 100644 index 000000000..33659d64f --- /dev/null +++ b/guide/changelog/v3.18.0.html @@ -0,0 +1,37 @@ + + + + + + Changelog v3.18.0 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.18.0

    Version 3.18.0 (2026-06-23)

    Full Changelog

    The headline of this release is HTTP Custom Type response caching — outbound HTTP calls made by HTTP Custom Types can now be cached and reused, eliminating repeated calls to the same upstream within a configurable time window. The release also fixes a duplicate-outbound-call bug for HTTP types on database-function endpoints.

    New Features

    HTTP Custom Type response caching — @cache directive

    An HTTP Custom Type can now opt into response caching with a @cache directive in its type comment, alongside the existing @timeout and @retry_delay directives. Directives appear before the request line:

    sql
    sql
    comment on type books_api is '@cache 5m
    +GET https://books.toscrape.com/';

    A cached type fires one outbound call for a given request shape; subsequent matching requests are served from the in-memory cache until the TTL elapses. For a type with no per-request placeholders (a constant URL/headers/body), that means a single shared upstream call per TTL window across the whole application — instead of one call per inbound request.

    Behavior and safety rules:

    • Opt-in, GET-only. Caching is enabled per type by @cache. A @cache directive on any non-GET method is ignored with a startup warning — caching a mutating call is almost always a mistake.
    • TTL. @cache <interval> accepts the same formats as @timeout (5m, 30s, 1h, 00:05:00, or a bare number of seconds). A bare @cache (no interval) caches with no expiration (until the process restarts) and warns.
    • Success-only. Only successful (2xx) responses are cached, so a transient upstream failure is never pinned for the whole TTL — the next request re-fetches.
    • Stampede protection. A burst of concurrent requests for the same cache key coalesces into a single outbound call; the rest await the in-flight result (same Lazy<Task> coalescing model as the routine cache).
    • Cache key = HTTP method + resolved URL + resolved content-type + resolved headers + resolved body. Placeholders are resolved first, so per-request values vary the key naturally.

    Configuration (HttpClientOptions):

    • CacheEnabled (default true) — global kill switch. When false, @cache directives are ignored and every request fires a fresh call.
    • MaxCacheEntries (default 10000) — bounds memory; once full, new responses are not cached (existing entries still serve and expire normally).
    • CachePruneIntervalSeconds (default 60) — how often expired entries are pruned.

    Fixes

    HTTP Custom Type request fired once per composite field on database-function endpoints

    An endpoint backed by a database function/procedure whose parameter is an HTTP Custom Type fired one outbound HTTP call per field of the type on every inbound request (a 4-field type → 4 identical calls; a 6-field type → 6), multiplying latency and load on the target. SQL-file endpoints were not affected.

    Cause. A composite function parameter is expanded into one parameter per field, each carrying the same TypeDescriptor.CustomType (the HTTP type name). The per-request list of HTTP types therefore held the same name N times, and the firing loop in HttpClientTypeHandler.InvokeAllAsync called InvokeAsync once per entry — while the fill loop immediately below resolves handlers by distinct type name. The design already assumes one call per distinct type; the firing loop just failed to match.

    Fix. A guard in the firing loop requests each distinct HTTP type once, reusing the dictionary the fill loop already keys on. The established contract is preserved: one call per distinct HTTP type, shared from one response — two parameters referencing the same type still share a single call, and two different types remain two separate calls.

    HTTP type directives after the headers were silently ignored

    The @timeout, @retry_delay, and @cache directives are now parsed both before the request line and after the headers. Previously only the leading position (before the request line) was recognized, so a directive placed after the headers — as the documentation and examples showed — was silently dropped (e.g. a @timeout that never applied). Both placements are now equivalent. Real HTTP headers are unaffected: a header whose name merely starts with a directive keyword (e.g. Cache-Control) is still treated as a header.

    Tests

    • Regression tests count actual outbound calls via WireMock response callbacks (the prior suite asserted content but never call counts): a 6-field type fires exactly one call (was 6), and two distinct types fire one call each.
    • Caching tests cover: cache hit reduces to one call, 6-field dedup + caching combined, error responses not cached, @cache ignored on POST, and TTL expiry. Parse-level tests cover the @cache directive forms and GET-only enforcement.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.18.1.html b/guide/changelog/v3.18.1.html new file mode 100644 index 000000000..7292c1079 --- /dev/null +++ b/guide/changelog/v3.18.1.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.18.1 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.18.1

    Version 3.18.1 (2026-06-23)

    Full Changelog

    Patch release that makes all automatic (server-filled) parameters forward to proxy endpoints consistently, in the endpoint's native parameter shape.

    What changed

    When an endpoint is a proxy, the parameters that NpgsqlRest fills server-side are now forwarded to the upstream uniformly, regardless of source:

    • user claims (claim-mapped parameters),
    • IP address parameter,
    • HTTP Custom Type fields (the auto-filled responseBody / responseStatusCode / … on a routine with an HTTP Custom Type parameter),
    • resolved-parameter expressions (values looked up server-side via SQL).

    All of them follow the same placement rule, which mirrors how the endpoint itself receives parameters — not the HTTP verb:

    • The parameter designated as the body parameter (@body_parameter_name) carries the raw request body.
    • Otherwise placement follows the endpoint's RequestParamType: QueryString → appended to the proxy query string; BodyJson → merged into the proxy JSON body (typed: numbers, booleans, embedded JSON, or strings), when the proxy method can carry a JSON body.

    This is additive: the verbatim incoming request is still forwarded; the automatic parameters are added on top, so the upstream receives the same parameter set the routine would have.

    Why

    Previously the behavior was inconsistent: user-claim and IP parameters were always appended to the query string, HTTP Custom Type fields and resolved parameters were not forwarded at all, and a passthrough proxy discarded the auto-filled values entirely (the outbound HTTP Custom Type call fired but its result went nowhere). Now every automatic parameter behaves the same way.

    Behavior change to note

    User-claim and IP parameters now follow RequestParamType like every other automatic parameter. For a QueryString endpoint (the default for GET) they remain in the query string, exactly as before. For a BodyJson endpoint they are now merged into the JSON body rather than forced onto the query string. Method does not decide placement — RequestParamType does (a POST endpoint can use param_type query and its parameters then go to the query string).

    Notes

    • Body merging applies only when the forwarded request carries a JSON content type; multipart and non-JSON bodies are forwarded verbatim.
    • Only the expanded per-field HTTP Custom Type parameters (DB-function shape) are forwarded; single-composite HTTP parameters (SQL-file shape) are not.

    Tests

    NpgsqlRestTests/ProxyTests/ProxyHttpTypeProbeTest.cs covers, via a WireMock proxy target that echoes the received URL / body: HTTP-type fields forwarded on the query (GET) and merged into the JSON body (POST, typed); placement following RequestParamType rather than the verb (a param_type query POST forwards to the query, not the body); and a resolved-parameter expression forwarded consistently. Existing user-claim / IP proxy tests continue to pass unchanged (GET → query string). Full suite green (2288).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.18.2.html b/guide/changelog/v3.18.2.html new file mode 100644 index 000000000..639382428 --- /dev/null +++ b/guide/changelog/v3.18.2.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.18.2 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.18.2

    Version 3.18.2 (2026-06-26)

    Full Changelog

    Patch release with fixes for proxy endpoints that forward auto-filled parameters (v3.18.1) and the generated TypeScript client, plus a new opt-in option to omit server-filled parameters from generated request shapes. All surfaced by combining a @proxy with an HTTP Custom Type parameter.

    What changed

    1. Large auto-filled values no longer break the proxy query string

    New option ProxyOptions.MaxForwardedQueryParamLength (default 2048). When a server-filled parameter is appended to the proxy upstream query string, a value longer than this limit is now skipped with a warning instead of being percent-encoded into the URL.

    Previously, an HTTP Custom Type whose body field held a large payload (e.g. a scraped HTML page) was percent-encoded into the upstream query string, producing an oversized request line that the upstream rejected (HTTP 414 / 431) or that reset the connection. To forward such a value, use a body-carrying proxy method (POST/PUT/PATCH) so it travels in the request body instead. Set the option to 0 to disable the guard.

    2. @body_parameter_name reliably matches HTTP Custom Type fields

    @body_parameter_name now matches case-insensitively and accepts any of the parameter's names. For an HTTP Custom Type field expanded out of a composite parameter, all of these now resolve to the same field:

    • the converted (API) name — e.g. responseBody
    • the expanded signature name — e.g. _response_body (the name shown in the generated signature / .http file)
    • the base composite name — e.g. _response (shared by all expanded fields; resolves to the first one)

    Previously the annotation value was force-lowercased and compared case-sensitively, so the camelCase converted name never matched, and the expanded signature name (_response_body) matched nothing at all — it is stored as neither the actual nor the converted name. This made it impossible to redirect a single expanded HTTP-type field (such as the response body) into the proxy request body.

    Body-parameter resolution is now a single shared rule on the core endpoint (RoutineEndpoint.IsBodyParameter) used by request handling and every code generator, so they no longer drift. This also fixes the HTTP file and OpenAPI generators, which previously left a @body_parameter_name field (e.g. responseBody) in the query string / query parameters instead of moving it to the request body.

    3. TypeScript client generation for @body_parameter_name

    The generated TypeScript client was broken for an endpoint with @body_parameter_name:

    • the body expression was emitted as request.responseBody? — a syntax error (the TS optional ? suffix leaked into the runtime property name);
    • the query-string exclusion key was ["responseBody?"], so the body parameter was not stripped from the query string;
    • a body was emitted even for a GET request, which fetch forbids.

    The generator now uses the parameter's bare name for the body expression and the exclusion key, only emits a fetch body for methods that can carry one (not GET), and — like the server — matches @body_parameter_name against the converted, actual, or expanded signature name of an HTTP Custom Type field (e.g. responseBody, _response, or _response_body).

    4. Opt-in: omit automatic (server-filled) parameters from generated request shapes

    New option OmitAutomaticParameters on all three generators — TsClientOptions, HttpFileOptions, and OpenApiOptions (default false, so generated output is unchanged unless you opt in).

    When enabled, a parameter is omitted from the generated request (TypeScript request interface, .http query/body, OpenAPI query parameters / request body) when it is automatic (filled server-side, so a client value would be ignored) and optional. Automatic covers: HTTP Custom Type fields, resolved-parameter expressions, upload-metadata parameters, and — on endpoints that use user parameters — IP-address and user-claim parameters. The shared rule lives on the core endpoint (RoutineEndpoint.OmitParameterFromGeneratedRequest), so the three generators stay consistent. When every parameter is omitted, the generated request collapses cleanly (no-argument TS function, bare .http URL, no OpenAPI parameters/requestBody).

    This is the proper fix for the misleading case where, e.g., an HTTP Custom Type's responseBody field appeared as a settable request parameter even though the server always overrides it.

    Why these go together

    The pattern "fetch with an HTTP Custom Type, then @proxy to an upstream" now works cleanly end to end — server and generated client: redirect the (large) body field into the upstream request body with @body_parameter_name, while the remaining small fields travel on the query string under the new length guard.

    Notes

    • ProxyOptions.MaxForwardedQueryParamLength is wired through the client config (appsettings.json, JSON schema, and the --config template).
    • No parameter ActualName semantics changed: expanded HTTP-type fields still share the composite base name so they reassemble into the single SQL argument; the per-field name is matched via an internal alias only.

    Tests

    NpgsqlRestTests/ProxyTests/ProxyHttpTypeProbeTest.cs adds three cases (WireMock proxy target echoing the received URL / body): body redirect by converted name (responseBody), body redirect by expanded signature name (_response_body), and an oversized HTTP-type body field skipped from the proxy query string. NpgsqlRestTests/TsClientTests/BodyParamGetTests.cs covers the generated client for @body_parameter_name endpoints: a GET case (no ?-suffixed name, parameter excluded from the query, no fetch body on GET) and a POST HTTP-Custom-Type case targeted by the expanded name _response_body (body emitted as request.responseBody, excluded from the query). The HTTP file and OpenAPI generators are covered for the same expanded-name body redirect (BodyParamToBodyTests). OmitAutomaticParameters is covered for each generator (TsClient: all-omitted no-arg function + mixed-params; HttpFiles and OpenAPI: query/body omission of HTTP Custom Type fields). Full suite green (2301).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.19.0.html b/guide/changelog/v3.19.0.html new file mode 100644 index 000000000..aa063c4db --- /dev/null +++ b/guide/changelog/v3.19.0.html @@ -0,0 +1,178 @@ + + + + + + Changelog v3.19.0 | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.19.0

    Version 3.19.0 (2026-07-03)

    Full Changelog

    This release introduces the SQL test runner (npgsqlrest --test) — write tests for your endpoints as plain .sql files, invoke endpoints in-process from inside a test, and assert on both the HTTP response and the database state, all within the test's own transaction. Also included: watch mode (--watch — re-run tests or restart the server on SQL and configuration changes), named parameters in SQL files (:name), a skip glob for the SQL file source, and the ability to mute an individual logger.


    1. SQL test runner (--test)

    code
    npgsqlrest ./config.json --test

    The runner discovers .sql test files, executes each on its own isolated connection, and reports per-assertion results to the console (and optionally JUnit XML for CI). A test file is ordinary SQL — arrange data, call an endpoint, assert on the result:

    sql
    sql
    -- tests/get_users_excludes_caller.test.sql
    +begin;
    +
    +insert into app.users (id, email, name) values (100, 'x@example.com', 'Fixture');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200,
    +       'authenticated caller gets 200'
    +from _response;
    +select body::jsonb @> '[{"email": "x@example.com"}]',
    +       'the fixture user is listed'
    +from _response;
    +
    +rollback;
    code
    NpgsqlRest test runner — 9 file(s)
    +PASS  tests/get_users_excludes_caller.test.sql  (2 assertions, 52ms)
    +...
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files

    How it works

    In --test mode the client builds the full endpoint middleware exactly as in normal operation (endpoints from database routines and/or SQL files, authentication, custom parameters — everything), but instead of starting the web server it runs the test files and exits with a result code. Endpoint calls made from a test are in-process: no network, no running server — the complete endpoint pipeline (routing, authorization, parameter binding, execution, serialization) runs against a synthetic HTTP context.

    The critical property is connection affinity: the in-process endpoint call runs on the test's own connection, inside the test's own transaction. A test can begin, insert fixture rows, call an endpoint that sees those uncommitted rows, assert on the response, and rollback — leaving no trace. Each test file gets its own non-pooled physical connection (fresh session: no temp-table, GUC, or prepared-statement carryover), and files run in parallel (MaxParallelism, default = processor count). If a file never rolls back, closing its physical connection aborts the open transaction — that is the safety net.

    Test-mode invariants, applied automatically:

    • WrapInTransaction is forced off (the test file owns transaction control — the runner never injects BEGIN/COMMIT/ROLLBACK).
    • Response caching is disabled (a test never sees another test's cached response).
    • Code generation (HTTP files, TypeScript client, OpenAPI) is skipped — a test run never rewrites generated artifacts.

    Test file anatomy

    A test file is a sequence of SQL statements and HTTP blocks, executed strictly in order. Files are executed statement by statement (like psql): each statement runs in autocommit unless the file opens its own transaction. Semicolon splitting understands line/block comments, string literals with '' escapes, and dollar-quoted bodies — a do $$ … $$; block stays whole.

    Assertions — a reported test is one of:

    • A boolean-returning SELECT. If the first column is boolean, the statement is an assertion: the first row's value must be true (false or null fails; zero rows passes vacuously; only the first row is examined). The optional second column is the assertion's name/message, shown in the report and used as the JUnit test-case name:
      sql
      sql
      select count(*) = 3, 'exactly three users are seeded' from app.users;
    • A do block. Passes unless it raises — assert inside a DO block raises SQLSTATE P0004, reported as a failure with the assert message. One DO block = one reported test (multiple asserts inside it are opaque to the runner):
      sql
      sql
      do $$ begin
      +    assert app.normalize_email(' X@Y.z ') = 'x@y.z', 'should trim and lowercase';
      +end $$;

    Any other statement is arrange/act — not counted as a test; it only surfaces if it errors. Any SQL error (other than an assert) is reported as an error with its SQLSTATE, message, statement text, and file:line.

    Failing behavior is fail-fast per file: after the first failed/errored assertion the rest of the file does not run (a failed DO-block assert aborts the transaction anyway). Assertions that passed before the failure are still credited.

    HTTP blocks — invoking endpoints

    An HTTP request is embedded in a block comment whose first non-comment line is a request line — a single-request subset of the standard .http file syntax:

    sql
    sql
    /*
    +POST /api/create-user
    +Content-Type: application/json
    +# @claim user_id=42
    +# @claim roles=admin
    +# @response created
    +
    +{"name": "Grace Hopper", "email": "grace@example.com"}
    +*/

    Syntax rules:

    • Request line: [HTTP] METHOD /path[?query] [HTTP/x] — the method is one of GET, POST, PUT, DELETE (the methods NpgsqlRest endpoints support); the path must start with / and must equal the endpoint's full path including UrlPathPrefix (default /api); the leading HTTP keyword and a trailing HTTP-version token are optional. A block comment whose first line is not a valid request line is an ordinary SQL comment — ignored.
    • Headers: Name: Value lines after the request line. Content-Type is picked up for the request body.
    • Directives (lines starting with # @ or // @, placed after the request line, before the body):
      • # @claim name=value — adds a claim to the acting principal. Repeatable, including the same claim type twice (e.g. two roles claims). Any # @claim makes the request authenticated; no # @claim means anonymous (an @authorize endpoint returns 401). Role checks and claim-to-parameter mappings (@user_parameters, ParameterNameClaimsMapping) work exactly as in production — tests exercise the real authorization path.
      • # @response name — capture this block's response into a temp table with the given name instead of the default.
    • Body: everything after the first blank line, verbatim. (Limitation: a literal */ inside the body ends the SQL comment early.)
    • Plain # or // lines are comments; unknown @ directives are ignored.

    An HTTP block is an act step, not an assertion — it captures the response and produces no test result of its own. The assertions are the SQL statements that follow it. One request per block; use multiple blocks for multiple calls.

    Endpoint kinds that cannot work meaningfully in-process are rejected with a clear error: SSE, upload, login/logout (inject the principal with # @claim instead), and outbound proxy/HTTP-type endpoints (tests must not call external services). A request whose path matches no endpoint still runs (a test may assert the 404 deliberately) but logs a warning — the most common cause is a path typo or a missing /api prefix.

    The response temp table

    Each HTTP block's response is captured into its own temp table on the test's connection, created fresh (no IF NOT EXISTS — a duplicate name, e.g. a repeated # @response name, fails the test loudly). Default columns:

    ColumnTypeContent
    statusintHTTP status code
    bodytextresponse body (cast to ::jsonb to assert on JSON)
    content_typetextresponse content type
    headersjsonbresponse headers
    is_successbooleantrue for 2xx

    Naming: a file with one HTTP block uses ResponseTempTable.Name (default _response); a file with two or more uses ResponseTempTable.MultiNamePattern (default _response_{n}) where {n} is the block's 1-based position — _response_1, _response_2, … A block with # @response name uses that name instead (it still counts in the numbering of the others). Column names are configurable; setting one to null/empty omits that column.

    sql
    sql
    select body::jsonb ->> 'email' = 'grace@example.com',
    +       'email is normalized to lowercase'
    +from _response;

    Debugging captured responses — temp tables vanish with the test's rollback and connection, so you cannot inspect them afterwards (and re-issuing the request from an .http file cannot reproduce a response that depended on the test's uncommitted fixtures). Set ResponseTempTable.DebugTable (e.g. "_responses_debug"; default null = off) and every captured response is also mirrored into a permanent table — written on a separate autocommit connection, immune to rollbacks, recreated at the start of every run so it always holds the last run. One table covers everything: each HTTP block adds one row, with test_file, block (that block's response-table name — _response, a _response_{n} ordinal, or the # @response name), method, path, status, body, content_type, headers, is_success, and captured_at — so after a run you can open a query editor and dig into any response with jsonb operators. The temp-table semantics are unchanged; enabling it prints a loud warning (debugging aid — do not enable in CI). In the fresh-test-database workflow combine it with Keep: true, or teardown drops the database and the mirror with it.

    Reusing scripts: \i and \ir includes

    Shared SQL — fixture inserts, utility scripts — can be spliced into any test with psql's include syntax on its own line:

    sql
    sql
    begin;
    +
    +\ir fixtures/extra_users.sql    -- path relative to THIS file (psql \ir semantics)
    +\i  ./shared/reset_counters.sql -- path relative to the cwd, like every other configured path
    +
    +/* GET /api/get-users */
    +select jsonb_array_length(body::jsonb) = 5, 'seed + fixture users listed' from _response;
    +
    +rollback;

    An include behaves as if you pasted the file's content at that spot: SQL statements, assertions (counted as tests, attributed to the included file), HTTP blocks (they participate in _response_{n} numbering exactly as if pasted), and even header annotations-- @setup/-- @teardown/-- @connection in an included file's leading comments count when the include sits in the host file's header region. That last one enables a shared profile idiom: put an annotation set in one file and attach it with a single include line (see example 21's shared/isolated_database.sql — one \ir line gives a test its own cloned database). Everything runs on the test's connection, inside the test's transaction — a fixture included this way rolls back with the test, so it is invisible to every other test and leaves no residue.

    Two footnotes where "pasted" is refined rather than literal:

    • An include must stand between complete statements — it cannot sit inside an unfinished statement or contribute a fragment of one (for reusable SQL fragments, use what PostgreSQL already provides: functions and views).
    • Error attribution is better than a paste: a failure inside an included file is reported with the included file's name and line (the same thing psql does), not a line number in an imaginary merged file.

    Includes nest (cycle-safe, depth-capped) and work in Setup/Teardown SqlFile steps too. A path may be single-quoted (\ir 'my fixtures/data.sql') and a trailing ; is forgiven; a non-include backslash line is passed through to PostgreSQL untouched (no other psql meta-commands are supported).

    Setup and Teardown, and named steps

    Run-once steps around the whole test session. Setup runs before endpoint discovery (so it can create/migrate the very schema the endpoints are built from); Teardown always runs at the end — even when tests fail or Setup itself fails (best-effort; Keep: true skips it to let you inspect state). Steps execute in the exact order written; a step is one of:

    • { "Sql": "..." } — inline SQL,
    • { "SqlFile": "path" } — a SQL file (executed statement by statement, like test files; \i/\ir includes work),
    • { "Command": "...", "WorkingDirectory": "..." } — a shell command (e.g. docker compose up -d, an external migration tool). Non-zero exit fails Setup.

    Sql/SqlFile steps run on the test connection by default, or on any named ConnectionStrings entry via a per-step "ConnectionName" — which enables maintenance operations like create database without the runner ever issuing DDL on its own.

    Teardown is guaranteed beyond the happy path: from Setup onward the runner intercepts SIGINT (Ctrl+C) and SIGTERM (e.g. docker stop) and runs Teardown synchronously in the signal handler — before the process can be torn down by an impatient parent (bun run/npm run forward Ctrl+C and may kill their children immediately; waiting for the run loop to unwind would lose that race). A second Ctrl+C force-quits. A process-exit hook additionally covers hard exits — e.g. a broken endpoint SQL file under SqlFileSource.ErrorMode: Exit calls Environment.Exit(1), which previously leaked the just-created test database; the exit code is unchanged, but Teardown now runs first. All paths funnel into a run-once Teardown. (A parent that SIGKILLs instantly remains unsurvivable — that is what a leading drop database if exists … on a static name, or a periodic sweep, is for.)

    Steps can be defined once in the Steps registry (name → step, like ConnectionStrings or CacheOptions.Profiles) and referenced by name; Setup/Teardown arrays accept names and inline objects mixed. Referencing an unknown name is a configuration error (exit 3).

    Every step also has an Enabled flag (default true): a disabled step is simply ignored wherever it is referenced — skipped with a debug log line, never an error. The default configuration ships disabled example steps covering the typical scenarios (create/drop a {rnd}-named test database on an admin connection, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) — they show every step property in place, so instead of typing a step from scratch you copy one, adjust the names, and flip Enabled to true:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateTestDatabase":  { "Enabled": false, "ConnectionName": "Admin", "Sql": "create database app_test_{rnd5}" },
    +      "DropTestDatabase":    { "Enabled": false, "ConnectionName": "Admin", "Sql": "drop database if exists app_test_{rnd5} with (force)" },
    +      "ApplySchema":         { "Enabled": false, "SqlFile": "./migrations/schema.sql" },
    +      "RunMigrationTool":    { "Enabled": false, "Command": "echo replace with your migration tool command", "WorkingDirectory": "." },
    +      "StartDockerPostgres": { "Enabled": false, "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=postgres -p 54329:5432 postgres" },
    +      "StopDockerPostgres":  { "Enabled": false, "Command": "docker rm -f npgsqlrest-test-pg" }
    +    }
    +  }
    +}
    jsonc
    jsonc
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "SqlFile": "./migrations/schema.sql" },
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    ["CreateDatabase", "ApplyMigrations"],
    +    "Teardown": ["DropDatabase"]
    +  }
    +}

    Per-file setup, teardown, and connection (header annotations)

    An individual test file can attach named steps — and pick its own connection — with leading -- comment annotations (the same annotation idiom endpoint .sql files use), placed before the first statement:

    sql
    sql
    -- @setup CreateIsolatedDb
    +-- @teardown DropIsolatedDb
    +-- @connection Isolated
    • -- @setup Name [Name …] — runs the named steps before this file (own connections, committed work — e.g. clone a database this file will use). An unknown step name fails the file with an error.
    • -- @teardown Name [Name …] — runs after this file, always (best-effort, even when the test fails or times out), after the file's connection is closed — so a drop database … with (force) teardown works. An unknown step name logs a warning.
    • -- @connection Name — runs this file, including its in-process endpoint calls, on a named ConnectionStrings entry instead of the test connection. An unknown name fails the file with an error.
    • -- @tag Name [Name …] — declares the file's tags, filtered with the Tag/ExcludeTag options (see the filtering section below).

    @setup and @teardown are repeatable, and one line may carry several names, separated by whitespace or commas (the NpgsqlRest annotation idiom — -- @setup A B, -- @setup A, B, and two -- @setup lines are all equivalent). Names accumulate and execute in exactly the order written — the same contract as the global Setup/Teardown arrays; teardown is not reversed, so write the step you want last, last. Setup is fail-fast (the first failing or unknown step stops the chain, the file body never runs, teardown still runs); each teardown step is best-effort (a failure logs a warning and the remaining steps still run).

    Annotations can also come from an include in the header region — includes behave as if pasted, so a shared annotation "profile" file attaches with one line: \ir shared/isolated_database.sql (see the includes section above).

    Since every word after @setup/@teardown is read as a step name, don't describe these annotations in a file's header comments using their literal syntax (-- @setup CreateDb creates the db… would try to run steps named creates, the, db…). Other -- comment lines in the header are ignored as usual.

    Together these give per-test database isolation: a file's setup clones a migrated template (create database … template … — a near-instant file-level copy), @connection points the file at the clone, and teardown drops it. That is the escalation path for state that transaction rollback cannot isolate — most prominently sequences, which advance even when the transaction rolls back, so generated ids are only deterministic in a fresh clone. Per-file steps commit to shared state, so steps that mutate the shared test database should be idempotent or run under MaxParallelism: 1; in-transaction fixture reuse belongs to \ir instead.

    A dedicated test database

    TestRunner.ConnectionName points the whole test session — endpoint type-checking (SQL-file Describe), endpoint execution, and the tests — at a named ConnectionStrings entry instead of the app's main connection. That database need not exist at startup: it is never opened before Setup, so the first Setup step can create it.

    jsonc
    jsonc
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={PGHOST};Database=appdb;...",                     // the real app DB — untouched by tests
    +    "Admin":   "Host={PGHOST};Database=postgres;...",                  // maintenance (needs CREATEDB)
    +    "Test":    "Host={PGHOST};Database=app_test_{rnd6};..."            // the throwaway test DB
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test",
    +    "FilePattern": "./tests/**/*.test.sql",
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd6}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "SqlFile": "./migrations/schema.sql" },     // runs on "Test"
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd6} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    ["CreateDatabase", "ApplyMigrations"],
    +    "Teardown": ["DropDatabase"]
    +  }
    +}

    {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once per run and substituted everywhere {ENV} placeholders work — connection strings, Setup/Teardown SQL, and Commands. The same token yields the same value across the whole config, so the connection string, the create, and the drop all name the same database; concurrent suites on a shared server can't collide. When several distinct tokens of the same length are needed, the indexed instances {rndN_1}{rndN_9} are each independent — {rnd3}, {rnd3_1} and {rnd3_2} are three different 3-character tokens, each stable for the run. (Trade-off: a hard crash that skips Teardown orphans that run's database. A static name with a leading drop database if exists … with (force); in Setup is the self-healing alternative.)

    The same Setup/Teardown machinery covers the neighboring workflows with no additional features: clone a prepared template (create database … template app_template — near-instant; migrate the template once in Setup and clone it for the run and for -- @setup-annotated per-test databases), start a Docker Postgres (Command steps: docker run → wait for pg_isready → migrate; docker rm -f in Teardown), or run an external migrator (EF Core, Django, Flyway) as a Command.

    Reporting

    Results are per assertion (like pgTAP/xUnit — each boolean SELECT / DO block is one test), grouped per file:

    code
    PASS  tests/login_succeeds.test.sql  (3 assertions, 50ms)
    +FAIL  tests/get_users.test.sql  (49ms)
    +        ✗ the caller is excluded — 2 of 3 users listed  [tests/get_users.test.sql:17]
    +        select jsonb_array_length(body::jsonb) = 2, …
    +
    +18 passed, 1 failed, 0 error(s)  —  19 assertions in 9 files

    Failures show the assertion name, file:line, and the failing statement. DetailedReport: true additionally lists passed assertions (), full failing SQL, and captured raise notice output for passing tests (notices always show under failing tests). This shapes the console report only — it is distinct from raising the NpgsqlRestTest log level, which controls diagnostics (see Logging below). A file with no recognizable assertions is flagged rather than silently counted.

    The report's colors are matched to Serilog's Code console theme, so the test report and the log lines around it read as one output: the FAIL/ERROR labels render as the byte-identical chip the theme uses for its ERR/FTL level (red text on a dark-grey block), PASS uses the same chip grammar in the mirror green, the rest of each line stays in the terminal's normal text color, and all failure text uses the theme's error red — never the 16-color red that renders orange in some terminals. Colors are disabled automatically when output is redirected (piped/CI logs stay plain).

    JUnit XML (JUnitOutput: "path.xml"): one <testcase> per assertion (name = the assertion message, classname = the file), <failure>/<error> with message and file:line, captured notices in <system-out>, files without assertions marked <skipped> — works with any CI dashboard.

    Exit codes: 0 all passed · 1 at least one failure · 2 at least one error (SQL error, timeout, unsupported endpoint, an interrupted run) · 3 setup/configuration error · 4 no test files found (AllowEmpty: true turns this into 0).

    Logging

    The runner logs through its own channel — NpgsqlRestTest (configurable via TestRunner.LoggerName) — leveled independently under Log:MinimalLevels (defaults to Information when absent, i.e. quiet):

    • Verbose — every SQL statement and every HTTP invocation (GET /api/x → 200, captured into "_response"),
    • Debug — discovery, per-file parse results, per-file outcomes, Setup/Teardown steps, degree of parallelism,
    • Warning — a request that matches no endpoint, failed teardown steps,
    • raise notice/warning from the database — logged by their severity, tagged with the test file that emitted them.
    jsonc
    jsonc
    {
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off", "NpgsqlRestTest": "Verbose" } }
    +}

    Configuration reference (TestRunner section)

    jsonc
    jsonc
    {
    +  "TestRunner": {
    +    "FilePattern": "",                    // glob selecting test files (same engine as SqlFileSource); empty disables
    +    "Filter": "",                         // narrow the discovered set: substring, or glob when it contains wildcards
    +    "Tag": "",                            // run only files carrying at least one of these tags (-- @tag name ...)
    +    "ExcludeTag": "",                     // skip files carrying any of these tags (wins over Tag)
    +    "ConnectionName": "",                 // ConnectionStrings entry to test against; empty = the main connection
    +    "MaxParallelism": 0,                  // concurrent test files; 0 = processor count
    +    "FailFast": false,                    // stop scheduling new files after the first failure (in-flight finish)
    +    "PerTestTimeout": "30s",              // per-file timeout: "30s", "5m", "1h", plain seconds, "hh:mm:ss"; 0 disables
    +    "JUnitOutput": null,                  // optional path for a JUnit XML report
    +    "Keep": false,                        // skip Teardown (inspect state after a failed run)
    +    "DetailedReport": false,              // detailed console report: passed ✓ lines, full failing SQL, notices for passing tests
    +    "AllowEmpty": false,                  // exit 0 instead of 4 when no tests are found
    +    "Coverage": null,                     // coverage summary: null (default) = on for full runs, quiet when narrowed; true/false = always/never
    +    "CoverageThreshold": null,            // 0-100: always report + fail an otherwise-passing run (exit 2) below it
    +    "LoggerName": "NpgsqlRestTest",       // the runner's log channel (leveled via Log:MinimalLevels)
    +    "ResponseTempTable": {
    +      "Name": "_response",                // table name when a file has ONE HTTP block
    +      "MultiNamePattern": "_response_{n}",// name pattern for 2+ blocks; {n} = 1-based block position
    +      "DebugTable": null,                 // debugging aid: ALSO mirror every response into this PERMANENT table (survives rollback; last run; not for CI)
    +      "Columns": {                        // response → column mapping; null/empty omits the column
    +        "Status": "status", "Body": "body", "ContentType": "content_type",
    +        "Headers": "headers", "IsSuccess": "is_success"
    +      }
    +    },
    +    "Steps": { },                         // named, reusable steps (name → step) for Setup/Teardown and -- @setup/-- @teardown;
    +                                          // each has "Enabled" (false = ignored wherever referenced); ships disabled examples
    +    "Setup": [],                          // run-once, BEFORE endpoint discovery, in written order (step names or inline objects)
    +    "Teardown": []                        // run-once, ALWAYS, in written order (Keep skips; same entries as Setup)
    +  }
    +}

    A practical convention is to keep the TestRunner block (and quiet log levels) in a separate test-config.json layered on only for test runs: npgsqlrest ./config.json ./test-config.json --test.

    Iterating on one test: Filter narrows the run to matching files, and like every option it can be set from the command line:

    code
    npgsqlrest ./config.json ./test-config.json --test --testrunner:filter=login

    A value without wildcards is a case-insensitive substring match against each file's cwd-relative path (login runs every *login* file); a value with wildcards uses the same glob engine as FilePattern (**/get_users_shows*). Setup and Teardown still run — the filtered subset executes in the complete environment — and a filter that matches nothing exits with code 4 (AllowEmpty applies).

    Tags group tests orthogonally to the directory layout. A file declares them with a header annotation, and runs are narrowed with Tag (include — the file must carry at least one) and ExcludeTag (skip — wins over include); both accept comma- or whitespace-separated lists, case-insensitive, and compose with Filter:

    sql
    sql
    -- @tag smoke, regression
    code
    npgsqlrest ... --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Since includes behave as if pasted, tags travel through a shared profile too: a profile file carrying -- @tag isolation, slow next to its -- @setup/-- @connection annotations tags every test that attaches it — e.g. all clone-isolated tests are automatically slow, so the everyday dev loop is just --testrunner:excludetag=slow, with zero per-file bookkeeping.

    Endpoint coverage is something only an integrated runner can offer: the runner knows the entire API surface it built and records every endpoint the tests actually invoked, so after the run it reports the API-level analogue of code coverage — including the exact endpoints no test touches. It is on by default for full runs (it costs one line); a run narrowed by Filter/Tag stays quiet — a deliberately partial run would just nag — unless Coverage: true forces it, and Coverage: false silences it entirely:

    code
    19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 1/2 (50%)
    +        untested: GET /api/get-users

    Endpoint kinds the runner rejects (SSE, upload, login/logout, outbound proxy) are excluded from the ratio and counted separately, so the number is honest. CoverageThreshold (0–100) turns it into a CI gate — it always reports, regardless of the Coverage setting or run narrowing: an otherwise-passing run below the threshold exits 2 — set it to 100 and forgetting to write a test for a new endpoint fails the build, naming the endpoint. "Covered" means invoked at least once by a test — execution, not assertion depth (the same semantics as code coverage).

    Watch mode (--watch, or Watch:Enabled in configuration) keeps the process alive and re-runs on change — and because the endpoint middleware is built once at startup, re-runs are near-instant:

    code
    npgsqlrest ./config.json ./test-config.json --test --watch

    Setup runs once, then everything runs once, then the test tree — and, when the SQL file source is enabled, the endpoint source tree — is watched recursively for *.sql changes (debounced). Changes are classified per file:

    • a changed test file re-runs alone (the Filter still applies);
    • a changed endpoint file (matching SqlFileSource.FilePattern) triggers an in-process endpoint rebuild — the sources are re-read and re-described against the test database, and the endpoint registry is swapped atomically — followed by a full rerun. After each rebuild the runner prints the endpoint delta (+ POST /api/new, - GET /api/x (endpoint dropped — check its SQL file for errors)), so breaking an endpoint file mid-session is visible immediately: the endpoint drops out, its tests fail with 404 warnings, and fixing the file brings it right back — no restart. (To make this safe, watch mode forces SqlFileSource.ErrorMode from Exit to Skip — a broken file must not kill the watch session; non-watch --test keeps Exit for CI. A rebuild that fails entirely keeps the previous endpoints live.)
    • any other changed .sql under the test tree — an included fixture or profile, whose dependents are unknown — re-runs everything.

    Teardown runs once, on exit — synchronously inside the SIGINT/SIGTERM handler (see the Setup and Teardown section), so the test database is dropped even when the watch process is stopped through a wrapper like bun run; a second Ctrl+C force-quits. Interactive/dev-only: a graceful stop exits 0 regardless of test outcomes — watch is not for CI gating. Database-routine sources have no files to watch — restart to pick up catalog changes.

    Project layout

    Two equally supported conventions — the difference is just the globs:

    • Co-located: sql/get_users.sql + sql/get_users.test.sql. Pair the endpoint glob with the new SqlFileSource.SkipPattern (below) so test files are never exposed as endpoints.
    • Separate tree: endpoints in sql/, tests in tests/ (named by scenario). The globs never overlap, so no SkipPattern is needed.

    2. SqlFileSource.SkipPattern — exclude files from endpoint discovery

    New option SkipPattern on the SQL file source (config key NpgsqlRest:SqlFileSource:SkipPattern, default "*.test.sql"). Files whose full path matches this glob are excluded from endpoint discovery: a .sql file becomes an endpoint only when it matches FilePattern and does not match SkipPattern.

    This is what makes the co-located test layout safe: without it, a test file's /* GET /x */ block would be read as an HTTP annotation and exposed as an endpoint. The pattern uses the same glob engine and semantics as FilePattern (*.ext matches by suffix). Set it to an empty string ("") to disable the exclusion.

    Behavior change: the default is "*.test.sql", so files matching that suffix are no longer exposed as endpoints out of the box. If you previously relied on serving *.test.sql files, set SkipPattern to "" to restore the old behavior.

    3. Named parameters in SQL files: :name

    SQL file endpoints can now use named placeholders instead of the positional $1, $2, …:

    sql
    sql
    /*
    +HTTP POST
    +@allow_anonymous
    +@single
    +*/
    +select u.id, u.email, u.full_name as name, r.name as role
    +from users u
    +join roles r on r.id = u.role_id
    +where u.email = :email
    +  and u.password_hash = crypt(:password, u.password_hash);

    The placeholder is the parameter name: :email becomes the API parameter email (through the same NameConverter routine parameters use, so :user_iduserId with the default camelCase converter). The @param $1 email text-style annotations that existed only to name positional parameters are simply unnecessary — the file above needs none. Under the hood the SQL is rewritten to native $N before it is described and executed; PostgreSQL never sees the :name form, so type inference, Describe, and runtime behavior are identical to positional files.

    What you get:

    • Repetition collapses: the same name used multiple times — including across statements in a multi-command file — is one parameter (where :user_id = author_id or :user_id = editor_id takes a single userId value).
    • Claim mappings hook up by placeholder name: select :_user_id under @authorize + @user_parameters binds the mapped claim with zero @param annotations.
    • Annotations match by name where you still need them: @param email default null (defaults), @param :email citext (a Describe type hint), and the new retype-without-rename form @param email type is citext — renaming a parameter whose name came from its own placeholder would be nonsense, so type is changes only the type. All positional @param $N … forms keep working unchanged.
    • The tokenizer knows SQL: strings ('…', "…", dollar-quoted bodies) and comments are untouched; ::int casts, := named-argument calls, and numeric slice bounds (a[1:3]) never match. A placeholder requires an identifier character immediately after the colon — the one caveat is an array slice with a variable bound, which must be written with a space (a[1 : n]).

    One style per file: mixing $N and :name in the same file makes the ordinal assignment ambiguous and is rejected (logged; the file is skipped under ErrorMode: Skip, or exits under Exit).

    Why not ? (JDBC style)? Considered and rejected: ?, ?|, ?&, and @? are PostgreSQL's own jsonb/geometric operators — where data ? 'admin' is legal, common SQL that no rewriter can reliably tell apart from a parameter. This is the same reason the PostgreSQL JDBC driver requires ?? escapes. Anonymous-positional already exists as $N.

    4. Watch mode: --watch

    The --watch flag — shorthand for the Watch:Enabled configuration setting — runs in one of two modes, depending on whether --test is present:

    CommandModeWatchesOn change
    npgsqlrest ... --test --watchTest watchtest files, included fixtures/profiles, the endpoint SQL files (when the SQL file source is enabled), and the database catalogchanged test re-runs alone; endpoint or database change rebuilds endpoints in-process and re-runs everything
    npgsqlrest ... --watchServer watchthe SQL file source tree, the configuration files, and the database catalogthe server restarts (~1s)

    Test watch is described in the test runner section above. Server watch needs something to watch — an enabled SQL file source, database polling (on by default, below), or both; with neither, --watch without --test exits with an error.

    Watching the routine source — database polling

    Routine-source endpoints (functions and procedures) have no files to watch — so watch mode polls the database instead, and it does it with perfect fidelity: the poll runs the same routine discovery query the endpoint source uses, with the same configured filters (schema/name/language includes and excludes), hashed server-side into a single value on a dedicated non-pooled connection (default every 2s). If the hash changes, the discovered endpoints changed — by definition. That covers create/create or replace/drop/alter of functions and procedures (including GRANT/REVOKE), COMMENT ON — i.e. annotation changes, and changes to the composite types and tables used as parameter or return types (alter table users add column reshapes a returns setof users endpoint even though no function changed). Just as importantly, anything the discovery query does not read — an unrelated table, temp objects, data changes — can never cause a spurious restart. Any detected change triggers the same path a file change does: server watch restarts the server, test watch rebuilds endpoints in-process and re-runs the tests (— change detected (database) —); the test runner re-baselines after every rerun so self-inflicted changes never re-trigger.

    This makes a routines-only project fully watchable: run npgsqlrest ./config.json --watch, then create or replace a function in psql — the endpoint is live about two seconds later, annotations included.

    The whole feature lives in one top-level configuration section (the --watch flag is the shorthand for Watch:Enabled):

    json
    json
    {
    +  "Watch": {
    +    "Enabled": false,
    +    "DatabasePollingInterval": "2s"
    +  }
    +}

    DatabasePollingInterval accepts "2s", "500ms", "1m", plain seconds, or "hh:mm:ss"; 0 disables polling. Both settings apply to both watch flavors.

    Server watch

    sh
    sh
    npgsqlrest ./config.json --watch

    Run the server under a watcher: edit a SQL file and the running API restarts with the change applied (~1s) — add an endpoint and it's immediately callable, break one and the error is on screen while the rest of the API keeps serving, and any configured code generation (TypeScript client, HTTP files, OpenAPI) regenerates on every restart, so the frontend's types follow your SQL as you type.

    How it works. The process becomes a small supervisor that spawns itself as a child server (marked by an environment variable) and watches the SqlFileSource tree plus the configuration files themselves. On a debounced change it stops the child gracefully and starts a fresh one — the child runs the completely normal server pipeline, so dev is byte-for-byte production behavior (the same model as dotnet watch). One relaxation: in the watch child, SqlFileSource.ErrorMode is forced from Exit to Skip, so a broken file logs its error and drops only its own endpoint instead of taking the server down.

    Behavior:

    EventResult
    .sql change under the source treerestart (files matching SkipPattern — test files — are ignored)
    configuration file changerestart with the new configuration
    database routine change (detected by polling, above)restart — — database change detected — restarting —
    broken SQL filerestart; the error is logged, that endpoint drops, everything else serves
    child crashes/exits on its ownsupervisor prints server exited (code N) — waiting for file changes and revives on the next save (no crash-looping)
    Ctrl+C / SIGTERM (docker stop)child stopped gracefully, both processes exit, port freed
    supervisor killed hard (SIGKILL)the child detects the vanished parent and exits by itself — no orphan holding the port

    Graceful child stop uses SIGTERM on Linux/macOS; on Windows the child is hard-killed (nothing needs teardown in a dev server). For environments where file events don't cross the filesystem boundary — Docker Desktop bind mounts, network shares — set the ecosystem-standard DOTNET_USE_POLLING_FILE_WATCHER=1 to switch to a 1-second polling scan (applies to both watch modes).

    Works in every distribution: the AOT executables (the supervisor respawns Environment.ProcessPath), framework-dependent dotnet NpgsqlRestClient.dll (the dotnet host is re-invoked with the dll), and both Docker image flavors (the supervisor handles PID-1 signal and child-reaping duties).

    5. Mute an individual logger with "Off" in Log:MinimalLevels

    Each entry under Log:MinimalLevels now accepts "Off" (aliases "None" and "Silent", case-insensitive) to fully silence that logger. Previously the only accepted values were the Serilog levels Verbose…Fatal, and there was no way to turn a logger off completely.

    • "Off" / "None" / "Silent" → the logger emits nothing (implemented as a minimum level above Fatal, since Serilog's LogEventLevel has no native "off").
    • null, an omitted key, or an unrecognized value → unchanged: the logger keeps its built-in default level.

    Each named logger is controlled independently — e.g. mute the application loggers entirely while watching the test runner:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    Notes

    • The test runner's core hooks are additive and inert outside --test: an ambient-connection accessor on the endpoint pipeline (null by default) and response headers on the internal invocation result. Normal server operation is unchanged.
    • Test files run statement by statement (the client operates Npgsql with SQL rewriting disabled — one statement per command), which is also psql's default execution model; explicit begin/commit/rollback in a file work as ordinary statements. Setup/Teardown Sql/SqlFile steps execute the same way — which is why create database works as a plain step.
    • All new options are wired through the client configuration: appsettings.json, the JSON-schema descriptions, and the --config template.
    • Working examples: examples/19_testing_basic (co-located layout, multi-step scenario files), examples/20_testing_newdb (separate tests/ tree, one test per file, fresh test database per run via named steps, deferrable-constraint fixtures, authorization + user parameters, a tag taxonomy — smoke/auth/fixtures/login — on every file), and examples/21_testing_isolation (template-clone workflow; two parallel per-test isolated databases — named apart with the indexed {rnd5_1}/{rnd5_2} tokens — proving deterministic sequence ids; a shared annotation profile attached via \ir that also carries the isolation, slow tags; a Command step mixed with named-step references in Setup).

    Tests

    Full test suite green (2394), including 63 unit tests for the test-file, HTTP-block, header-annotation, and include parsers plus the filter and tag matchers (NpgsqlRestTests/TestRunnerTests/ParserTests/), and 29 for named SQL-file parameters — 22 rewriter unit tests (casts, :=, slices, strings, dollar-quotes, jsonb-path strings, case-insensitive repetition, cross-statement sharing, mixing detection, named type hints) plus 7 end-to-end endpoint tests (auto-naming through the camelCase converter, a repeated placeholder bound from one value, required-parameter matching, name-matched defaults, type is retype, claim mapping by placeholder name, mixed-style rejection, and a multi-command file sharing :id across statements as one API parameter), plus a database-fingerprint test proving the watch poller's hash tracks the routine discovery result exactly (function create/replace/comment/drop and used-type changes fire; temp objects and unrelated tables never do). Watch mode verified live end-to-end in both flavors: file edit/break/fix cycles, config-change restarts, crash recovery, orphan prevention under SIGKILL, graceful SIGTERM teardown, polling-watcher mode, and database-driven changes (a function created in psql serving ~2s later; alter table reshaping a returns setof endpoint; unrelated tables causing zero restarts). All three documentation examples verified end-to-end against live PostgreSQL — including example 21's template-clone workflow (template migrated once; the shared run database and two parallel per-test isolated databases cloned from it concurrently; deterministic sequence ids asserted independently in both clones; everything dropped on teardown), watch mode (single-file rerun on a test change, full rerun on a fixture change, teardown on SIGINT/SIGTERM), path and tag filtering (including tags carried through a profile include), and the coverage report with a failing threshold gate. The new configuration keys are covered by the configuration round-trip tests (the --config template output matches appsettings.json).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.0.html b/guide/changelog/v3.2.0.html new file mode 100644 index 000000000..0d0c43000 --- /dev/null +++ b/guide/changelog/v3.2.0.html @@ -0,0 +1,131 @@ + + + + + + Changelog v3.2.0 (2025-12-22) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.2.0 (2025-12-22)

    Version 3.2.0 (2025-12-22)

    Full Changelog

    Reverse Proxy Feature

    Added reverse proxy support for NpgsqlRest endpoints. When an endpoint is marked as a proxy, incoming HTTP requests are forwarded to an upstream service, and the response can either be returned directly to the client (passthrough mode) or processed by the PostgreSQL function (transform mode).

    Basic Usage:

    sql
    sql
    -- Passthrough mode: forward request, return upstream response directly
    +create function get_external_data()
    +returns void
    +language sql as 'select';
    +comment on function get_external_data() is 'HTTP GET
    +proxy';
    +
    +-- Transform mode: forward request, process response in PostgreSQL
    +create function get_and_transform(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_headers json default null,
    +    _proxy_content_type text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', _proxy_error_message);
    +    end if;
    +    return json_build_object(
    +        'status', _proxy_status_code,
    +        'data', _proxy_body::json
    +    );
    +end;
    +$$;
    +comment on function get_and_transform(int, text, json, text, boolean, text) is 'HTTP GET
    +proxy';

    Proxy Annotations:

    sql
    sql
    -- Basic proxy with default host from configuration
    +comment on function my_func() is 'proxy';
    +
    +-- Proxy with custom host
    +comment on function my_func() is 'proxy https://api.example.com';
    +comment on function my_func() is 'proxy_host https://api.example.com';
    +
    +-- Proxy with custom HTTP method
    +comment on function my_func() is 'proxy POST';
    +comment on function my_func() is 'proxy_method POST';
    +
    +-- Combined host and method
    +comment on function my_func() is 'proxy https://api.example.com POST';

    Response Parameters:

    When the PostgreSQL function has parameters matching these names, the proxy response data is passed to the function:

    Parameter NameTypeDescription
    _proxy_status_codeintHTTP status code from upstream (e.g., 200, 404)
    _proxy_bodytextResponse body content
    _proxy_headersjsonResponse headers as JSON object
    _proxy_content_typetextContent-Type header value
    _proxy_successbooleanTrue for 2xx status codes
    _proxy_error_messagetextError message if request failed

    User Claims and Context Forwarding:

    When user_params is enabled, user claim values are forwarded to the upstream proxy as query string parameters:

    sql
    sql
    create function proxy_with_claims(
    +    _user_id text default null,        -- Forwarded as ?userId=...
    +    _user_name text default null,      -- Forwarded as ?userName=...
    +    _ip_address text default null,     -- Forwarded as ?ipAddress=...
    +    _user_claims json default null,    -- Forwarded as ?userClaims=...
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('user', _user_id, 'data', _proxy_body);
    +end;
    +$$;
    +comment on function proxy_with_claims(text, text, text, json, int, text) is 'HTTP GET
    +authorize
    +user_params
    +proxy';

    When user_context is enabled, user context values are forwarded as HTTP headers to the upstream proxy:

    sql
    sql
    create function proxy_with_context(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object('status', _proxy_status_code);
    +end;
    +$$;
    +comment on function proxy_with_context(int, text) is 'HTTP GET
    +authorize
    +user_context
    +proxy';
    +-- Headers forwarded: request.user_id, request.user_name, request.user_roles (configurable via ContextKeyClaimsMapping)

    Upload Forwarding:

    For upload endpoints with proxy, you can configure whether to process uploads locally or forward raw multipart data:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}
    • ForwardUploadContent: false (default): Uploads are processed locally; proxy receives parsed data
    • ForwardUploadContent: true: Raw multipart/form-data is streamed directly to upstream (memory-efficient)

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": false,
    +      "Host": null,
    +      "DefaultTimeout": "30 seconds",
    +      "ForwardHeaders": true,
    +      "ExcludeHeaders": ["Host", "Content-Length", "Transfer-Encoding"],
    +      "ForwardResponseHeaders": true,
    +      "ExcludeResponseHeaders": ["Transfer-Encoding", "Content-Length"],
    +      "ResponseStatusCodeParameter": "_proxy_status_code",
    +      "ResponseBodyParameter": "_proxy_body",
    +      "ResponseHeadersParameter": "_proxy_headers",
    +      "ResponseContentTypeParameter": "_proxy_content_type",
    +      "ResponseSuccessParameter": "_proxy_success",
    +      "ResponseErrorMessageParameter": "_proxy_error_message",
    +      "ForwardUploadContent": false
    +    }
    +  }
    +}

    Key Features:

    • Passthrough mode: No database connection opened when function has no proxy response parameters
    • Transform mode: Process upstream response in PostgreSQL before returning to client
    • User claims forwarding: Authenticated user claims passed as query parameters to upstream
    • User context headers: User context values passed as HTTP headers to upstream
    • Streaming uploads: Memory-efficient streaming for large file uploads when ForwardUploadContent is enabled
    • Timeout handling: Configurable per-request timeout with proper 504 Gateway Timeout responses
    • Header forwarding: Configurable request/response header forwarding with exclusion lists

    Docker Image with Bun Runtime

    Added new Docker image variant with pre-installed Bun runtime: vbilopav/npgsqlrest:latest-bun

    This image includes the Bun JavaScript runtime alongside NpgsqlRest, enabling proxy endpoints to execute Bun scripts within the same container. Useful for scenarios where you need lightweight proxy handlers without external service calls.

    Available tags:

    • vbilopav/npgsqlrest:3.2.1-bun - specific version with Bun
    • vbilopav/npgsqlrest:latest-bun - latest version with Bun

    Configuration Default Fixes

    Fixed multiple configuration default mismatches where code fallback values did not match the defaults defined in appsettings.json. When configuration keys were not present, the application would use incorrect fallback values instead of the documented defaults.

    Fixed defaults:

    SectionKeyWasNow
    DataProtectionGetAllElementsCommand"select data from get_all_data_protection_elements()""select get_data_protection_keys()"
    DataProtectionStoreElementCommand"call store_data_protection_element($1,$2)""call store_data_protection_keys($1,$2)"
    CorsAllowedOrigins["*"][]
    CommandRetryOptionsEnabledfalsetrue
    RateLimiterOptions.ConcurrencyPermitLimit10010
    Auth.BasicAuthUseDefaultPasswordHasherfalsetrue
    NpgsqlRest.HttpFileOptionsNamePattern"{0}{1}""{0}_{1}"
    NpgsqlRest.OpenApiOptionsFileOverwritefalsetrue
    NpgsqlRest.CrudSourceEnabledfalsetrue
    StaticFiles.ParseContentOptionsHeadersnull["Cache-Control: no-store, no-cache, must-revalidate", "Pragma: no-cache", "Expires: 0"]
    NpgsqlRestRequestHeadersModeIgnoreParameter
    RateLimiterOptions.TokenBucketReplenishmentPeriodSeconds (log)110
    RateLimiterOptions.ConcurrencyQueueLimit105
    RateLimiterOptionsMessage (field name)"Message""StatusMessage"
    CacheOptionsUseRedisBackend (field name)"UseRedisBackend""HybridCacheUseRedisBackend"

    Note: If you were relying on the previous (incorrect) fallback behavior, you may need to explicitly set these values in your configuration.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.1.html b/guide/changelog/v3.2.1.html new file mode 100644 index 000000000..4c095349a --- /dev/null +++ b/guide/changelog/v3.2.1.html @@ -0,0 +1,66 @@ + + + + + + Changelog v3.2.1 (2025-12-23) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.2.1 (2025-12-23)

    Version 3.2.1 (2025-12-23)

    Full Changelog

    JWT (JSON Web Token) Authentication Support

    Added standard JWT Bearer authentication as a third authentication scheme alongside Cookie and Microsoft Bearer Token authentication. All three schemes can be used together.

    Configuration:

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtIssuer": "your-app",
    +    "JwtAudience": "your-api",
    +    "JwtExpireMinutes": 60,
    +    "JwtRefreshExpireDays": 7,
    +    "JwtValidateIssuer": true,
    +    "JwtValidateAudience": true,
    +    "JwtValidateLifetime": true,
    +    "JwtValidateIssuerSigningKey": true,
    +    "JwtClockSkew": "5 minutes",
    +    "JwtRefreshPath": "/api/jwt/refresh"
    +  }
    +}

    Login Response:

    When JWT authentication is enabled and a login endpoint returns successfully, the response includes:

    json
    json
    {
    +  "accessToken": "eyJhbG...",
    +  "refreshToken": "eyJhbG...",
    +  "tokenType": "Bearer",
    +  "expiresIn": 3600,
    +  "refreshExpiresIn": 604800
    +}

    Token Refresh:

    POST to the configured refresh path (default: /api/jwt/refresh) with:

    json
    json
    { "refreshToken": "eyJhbG..." }

    Returns a new access token and refresh token pair.

    Key Differences from Microsoft Bearer Token:

    FeatureMicrosoft Bearer TokenJWT
    Token FormatProprietary, encryptedIndustry-standard (RFC 7519)
    InteroperabilityASP.NET Core onlyAny system supporting JWT
    Token InspectionOpaqueCan be decoded at jwt.io
    Use CaseSingle ASP.NET appCross-service, microservices

    New Configuration Options:

    • JwtAuth - Enable JWT authentication (default: false)
    • JwtAuthScheme - Custom scheme name (default: "Bearer")
    • JwtSecret - Signing key (minimum 32 characters for HS256)
    • JwtIssuer - Token issuer claim
    • JwtAudience - Token audience claim
    • JwtExpireMinutes - Access token expiration (default: 60)
    • JwtRefreshExpireDays - Refresh token expiration (default: 7)
    • JwtValidateIssuer - Validate issuer claim (default: false)
    • JwtValidateAudience - Validate audience claim (default: false)
    • JwtValidateLifetime - Validate token expiration (default: true)
    • JwtValidateIssuerSigningKey - Validate signing key (default: true)
    • JwtClockSkew - Clock tolerance for expiration (default: 5 minutes)
    • JwtRefreshPath - Refresh endpoint path (default: "/api/jwt/refresh")

    Custom Login Handler:

    Added CustomLoginHandler callback to NpgsqlRestAuthenticationOptions allowing custom token generation during login. This enables JWT tokens to be generated and returned instead of using the default SignIn behavior.

    Path Parameters Support for HttpFiles and OpenApi Plugins

    Added path parameters support to the HttpFiles and OpenApi plugins, matching the functionality added to the core library and TsClient in version 3.1.3.

    HttpFiles Plugin:

    Path parameters are now properly handled in generated HTTP files:

    • Path parameters are excluded from query strings (they're already in the URL path)
    • Path parameters are excluded from JSON request bodies

    Before (broken):

    http
    http
    GET {host}/api/products/{p_id}?pId=1

    After (fixed):

    http
    http
    GET {host}/api/products/{p_id}

    OpenApi Plugin:

    Path parameters are now properly documented in the OpenAPI specification:

    • Path parameters are added with "in": "path" and "required": true
    • Path parameters are excluded from query parameters
    • Path parameters are excluded from request body schemas

    Example generated OpenAPI for /api/products/{p_id}:

    json
    json
    {
    +  "parameters": [
    +    {
    +      "name": "pId",
    +      "in": "path",
    +      "required": true,
    +      "schema": { "type": "integer", "format": "int32" }
    +    }
    +  ]
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.2.html b/guide/changelog/v3.2.2.html new file mode 100644 index 000000000..d3408866a --- /dev/null +++ b/guide/changelog/v3.2.2.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.2.2 (2025-12-24) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.2.2 (2025-12-24)

    Version 3.2.2 (2025-12-24)

    Full Changelog

    Bug Fixes

    • Fixed sensitive data exposure in command logs for auth endpoints. When ObfuscateAuthParameterLogValues is enabled (default), query string parameters are now stripped from the logged URL to prevent credentials from appearing in logs.
    • Fixed traceId being included in ProblemDetails error responses when ErrorHandlingOptions config section is missing. Now correctly removes traceId by default to match the behavior when the config section exists.
    • Fixed SSL config key mismatch: renamed HttpsRedirection to UseHttpsRedirection for consistency with UseHsts.
    • Fixed missing TokensPerPeriod property in TokenBucket rate limiter configuration.
    • Fixed MetadataQuerySchema comment to accurately describe behavior (when null, no search path is set).

    Performance Improvements

    • Replaced Task with ValueTask for frequently-called private async methods to reduce heap allocations in hot paths:
      • PrepareCommand - called before every query execution
      • OpenConnectionAsync - often completes synchronously when connection is already open
      • ReturnErrorAsync - error handling path
      • Challenge (BasicAuthHandler) - authentication challenge response

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.3.html b/guide/changelog/v3.2.3.html new file mode 100644 index 000000000..ac1bc96ba --- /dev/null +++ b/guide/changelog/v3.2.3.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.2.3 (2025-12-30) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.4.html b/guide/changelog/v3.2.4.html new file mode 100644 index 000000000..26b21755b --- /dev/null +++ b/guide/changelog/v3.2.4.html @@ -0,0 +1,59 @@ + + + + + + Changelog v3.2.4 (2025-01-03) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.2.4 (2025-01-03)

    Version 3.2.4 (2025-01-03)

    Full Changelog

    DataProtection Key Encryption Options

    Added support for encrypting data protection keys at rest using X.509 certificates or Windows DPAPI.

    New Configuration Options:

    json
    json
    {
    +  "DataProtection": {
    +    "KeyEncryption": "None",
    +    "CertificatePath": null,
    +    "CertificatePassword": null,
    +    "DpapiLocalMachine": false
    +  }
    +}

    Options:

    OptionDescription
    KeyEncryptionEncryption method: "None" (default), "Certificate", or "Dpapi" (Windows only)
    CertificatePathPath to X.509 certificate file (.pfx) when using Certificate encryption
    CertificatePasswordPassword for the certificate file (can be null for passwordless certificates)
    DpapiLocalMachineWhen using DPAPI, set to true to protect keys to the local machine instead of current user

    Example with Certificate:

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "Database",
    +    "KeyEncryption": "Certificate",
    +    "CertificatePath": "/path/to/cert.pfx",
    +    "CertificatePassword": "${CERT_PASSWORD}"
    +  }
    +}

    Example with DPAPI (Windows only):

    json
    json
    {
    +  "DataProtection": {
    +    "Enabled": true,
    +    "Storage": "FileSystem",
    +    "FileSystemPath": "./keys",
    +    "KeyEncryption": "Dpapi",
    +    "DpapiLocalMachine": true
    +  }
    +}

    TsClient Plugin

    • Fixed error parsing in generated TypeScript/JavaScript code to skip response.json() when the response has no body (e.g., 404 responses). The generated code now checks response.headers.get("content-length") !== "0" before attempting to parse the error response.

    NpgsqlRestClient

    • Added Microsoft.Extensions.Caching.StackExchangeRedis and Microsoft.AspNetCore.Authentication.JwtBearer packages to the version display output (--version / -v).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.6.html b/guide/changelog/v3.2.6.html new file mode 100644 index 000000000..78537988a --- /dev/null +++ b/guide/changelog/v3.2.6.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.2.6 (2025-01-04) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guide/changelog/v3.2.7.html b/guide/changelog/v3.2.7.html new file mode 100644 index 000000000..586bf527f --- /dev/null +++ b/guide/changelog/v3.2.7.html @@ -0,0 +1,57 @@ + + + + + + Changelog v3.2.7 (2025-01-05) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.2.7 (2025-01-05)

    Version 3.2.7 (2025-01-05)

    Full Changelog

    Note: NpgsqlRest core library version jumped from 3.2.2 to 3.2.7 to align with the client application version.

    Upload Handlers: User Context and Claims Support

    Fixed issue where user_context and user_params were not properly available for CSV/Excel upload endpoints:

    • user_context: SET LOCAL session variables (e.g., request.user_id) are now set before upload, making them accessible in row_command via current_setting().
    • user_params: Claim values are now correctly bound to upload function parameters (e.g., _user_id, _user_name).

    New Feature: Added RowCommandUserClaimsKey option to include authenticated user claims in the row metadata JSON parameter ($4) passed to row_command.

    Configuration:

    json
    json
    {
    +  "UploadHandlers": {
    +    "RowCommandUserClaimsKey": "claims"
    +  }
    +}
    • Set to a key name (default: "claims") to include claims in metadata JSON
    • Set to null or empty string to disable

    SQL Usage:

    sql
    sql
    -- Access claims from metadata JSON in row_command
    +create function process_row(
    +  _index int, 
    +  _row text[], 
    +  _prev int, 
    +  _meta json
    +  )
    +returns int 
    +as $$
    +begin
    +    insert into my_table (user_id, data)
    +    values (
    +        (_meta->'claims'->>'name_identifier')::int,
    +        _row[1]
    +    );
    +    return _index;
    +end;
    +$$ language plpgsql;

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.3.0.html b/guide/changelog/v3.3.0.html new file mode 100644 index 000000000..4dea0654d --- /dev/null +++ b/guide/changelog/v3.3.0.html @@ -0,0 +1,104 @@ + + + + + + Changelog v3.3.0 (2025-01-08) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.3.0 (2025-01-08)

    Version 3.3.0 (2025-01-08)

    Full Changelog

    Parameter Validation

    New feature for validating endpoint parameters before database execution. Validation is performed immediately after parameters are parsed, before any database connection is opened, authorization checks, or proxy handling.

    Comment Annotation Syntax:

    sql
    sql
    comment on function my_function(text) is '
    +HTTP POST
    +validate _param_name using rule_name
    +validate _param_name using rule1, rule2, rule3
    +';
    • Parameter names can use either original PostgreSQL names (_email) or converted names (email)
    • Multiple rules can be specified as comma-separated values or on separate lines
    • Rules are evaluated in order; validation stops on first failure

    Built-in Validation Types:

    TypeDescription
    NotNullParameter value cannot be null (DBNull.Value)
    NotEmptyParameter value cannot be an empty string (null values pass)
    RequiredCombines NotNull and NotEmpty - value cannot be null or empty
    RegexParameter value must match the specified regular expression pattern
    MinLengthParameter value must have at least N characters
    MaxLengthParameter value must have at most N characters

    Default Rules:

    Four validation rules are available by default: not_null, not_empty, required, and email.

    Configuration (NpgsqlRestClient):

    json
    json
    {
    +  "ValidationOptions": {
    +    "Enabled": true,
    +    "Rules": {
    +      "not_null": {
    +        "Type": "NotNull",
    +        "Message": "Parameter '{0}' cannot be null",
    +        "StatusCode": 400
    +      },
    +      "not_empty": {
    +        "Type": "NotEmpty",
    +        "Message": "Parameter '{0}' cannot be empty",
    +        "StatusCode": 400
    +      },
    +      "required": {
    +        "Type": "Required",
    +        "Message": "Parameter '{0}' is required",
    +        "StatusCode": 400
    +      },
    +      "email": {
    +        "Type": "Regex",
    +        "Pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$",
    +        "Message": "Parameter '{0}' must be a valid email address",
    +        "StatusCode": 400
    +      }
    +    }
    +  }
    +}

    Rule Properties:

    PropertyRequiredDescription
    TypeYesValidation type: NotNull, NotEmpty, Required, Regex, MinLength, MaxLength
    PatternFor RegexRegular expression pattern
    MinLengthFor MinLengthMinimum character length
    MaxLengthFor MaxLengthMaximum character length
    MessageNoError message with placeholders: {0}=original name, {1}=converted name, {2}=rule name. Default: "Validation failed for parameter '{0}'"
    StatusCodeNoHTTP status code on failure. Default: 400

    Programmatic Configuration:

    csharp
    csharp
    var options = new NpgsqlRestOptions
    +{
    +    ValidationOptions = new ValidationOptions
    +    {
    +        Rules = new Dictionary<string, ValidationRule>
    +        {
    +            ["required"] = new ValidationRule
    +            {
    +                Type = ValidationType.Required,
    +                Message = "Parameter '{0}' is required",
    +                StatusCode = 400
    +            },
    +            ["phone"] = new ValidationRule
    +            {
    +                Type = ValidationType.Regex,
    +                Pattern = @"^\+?[1-9]\d{1,14}$",
    +                Message = "Parameter '{0}' must be a valid phone number"
    +            }
    +        }
    +    }
    +};

    Example Usage:

    sql
    sql
    create function register_user(_email text, _password text, _name text)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    -- validation already passed, safe to use parameters
    +    insert into users (email, password_hash, name)
    +    values (_email, crypt(_password, gen_salt('bf')), _name);
    +    return json_build_object('success', true);
    +end;
    +$$;
    +
    +comment on function register_user(text, text, text) is '
    +HTTP POST
    +validate _email using required, email
    +validate _password using required
    +validate _name using not_empty
    +';

    Linux ARM64 Build and Docker Image

    Added Linux ARM64 native build and Docker image support:

    New Release Assets:

    • npgsqlrest-linux-arm64 - Native ARM64 executable for Linux ARM systems (Raspberry Pi, AWS Graviton, Apple Silicon Linux VMs, etc.)

    New Docker Image Tags:

    • vbilopav/npgsqlrest:3.3.0-arm - ARM64 Docker image
    • vbilopav/npgsqlrest:latest-arm - Latest ARM64 Docker image

    The ARM64 build is compiled natively on GitHub's ARM64 runners for optimal performance on ARM-based systems.

    Docker Build Improvements:

    Refactored Docker build pipeline to use GitHub Actions artifacts instead of downloading binaries from release URLs. This eliminates potential race conditions with release asset propagation and removes hardcoded version numbers from Dockerfiles.

    Config Command Shows Default Values

    The --config command now displays the complete configuration including all default values, not just explicitly set values.

    Before: Only showed values explicitly set in configuration files, leaving users to guess what defaults the application would use.

    After: Shows the full merged configuration with all defaults visible, making it useful for:

    • Understanding what values the application will use at runtime
    • Creating a starting point configuration file
    • Debugging configuration issues
    • Self-documenting reference of all available options

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.3.1.html b/guide/changelog/v3.3.1.html new file mode 100644 index 000000000..7e7256be6 --- /dev/null +++ b/guide/changelog/v3.3.1.html @@ -0,0 +1,76 @@ + + + + + + Changelog v3.3.1 (2025-01-14) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.3.1 (2025-01-14)

    Version 3.3.1 (2025-01-14)

    Full Changelog

    Proxy Response Caching

    Added support for caching responses from passthrough proxy endpoints. Previously, caching only worked with endpoints that executed database functions. Now, proxy endpoints that forward requests to upstream services can also leverage the caching system.

    Usage:

    sql
    sql
    create function get_external_data()
    +returns void
    +language plpgsql as $$ begin null; end; $$;
    +
    +comment on function get_external_data() is '
    +HTTP GET
    +proxy
    +cached
    +cache_expires_in 5 minutes
    +';

    Features:

    • Cache lookup happens before proxy request is sent
    • On cache hit, response is returned immediately without calling upstream service
    • Cached proxy responses preserve: status code, body, content type, and headers
    • Supports cache key parameters for parameter-based caching
    • Supports cache expiration with cache_expires_in annotation

    Example with cache key:

    sql
    sql
    create function get_user_profile(_user_id text)
    +returns void
    +language plpgsql as $$ begin null; end; $$;
    +
    +comment on function get_user_profile(text) is '
    +HTTP GET
    +proxy https://api.example.com/users
    +cached _user_id
    +cache_expires_in 1 hour
    +';

    This is useful for:

    • Reducing load on upstream services
    • Improving response times for frequently accessed data
    • Rate limiting protection for external API calls

    Optional @ Prefix for Comment Annotations

    Added support for optional @ prefix on all NpgsqlRest-specific comment annotations. This provides better visual distinction and consistency with .http file conventions.

    Both syntaxes are equivalent and can be mixed freely:

    sql
    sql
    -- Without @ prefix (existing syntax - still works)
    +comment on function my_func() is '
    +HTTP GET
    +authorize
    +cached
    +raw
    +';
    +
    +-- With @ prefix (new syntax)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +@cached
    +@raw
    +';
    +
    +-- Mixed (both work together)
    +comment on function my_func() is '
    +HTTP GET
    +@authorize
    +cached
    +@timeout 30s
    +';

    Notes:

    • The @ prefix is optional - existing code without @ continues to work unchanged
    • HTTP RFC standard annotations (headers with Name: value syntax) do not use the @ prefix
    • This applies to all NpgsqlRest-specific annotations: authorize, cached, raw, disabled, login, logout, proxy, upload, validate, etc.

    Added a logo on client app commands

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.0.html b/guide/changelog/v3.4.0.html new file mode 100644 index 000000000..2769dbaf0 --- /dev/null +++ b/guide/changelog/v3.4.0.html @@ -0,0 +1,126 @@ + + + + + + Changelog v3.4.0 (2025-01-16) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.0 (2025-01-16)

    Version 3.4.0 (2025-01-16)

    Full Changelog

    Composite Type Support

    Added automatic JSON serialization support for PostgreSQL composite types in two scenarios:

    1. Arrays of Composite Types

    When a function returns a column that is an array of a composite type (or table type), the array elements are now automatically serialized as JSON arrays of objects instead of PostgreSQL's text representation.

    Example:

    sql
    sql
    create type book_item as (
    +    book_id int,
    +    title text,
    +    author_id int
    +);
    +
    +create function get_authors_with_books()
    +returns table(
    +    author_id int,
    +    author_name text,
    +    books book_item[]
    +)
    +language sql as $$
    +select * from (values
    +    (1, 'George Orwell', array[
    +        row(1, '1984', 1)::book_item,
    +        row(2, 'Animal Farm', 1)::book_item
    +    ])
    +) as t(author_id, author_name, books);
    +$$;

    Previous behavior:

    json
    json
    [{"authorId":1,"authorName":"George Orwell","books":["(1,1984,1)","(2,Animal Farm,1)"]}]

    New behavior:

    json
    json
    [{"authorId":1,"authorName":"George Orwell","books":[{"bookId":1,"title":"1984","authorId":1},{"bookId":2,"title":"Animal Farm","authorId":1}]}]

    This feature is automatic and requires no annotations. It works with:

    • Custom composite types (create type)
    • Table types (arrays of table row types)
    • Composite types containing NULL values
    • Empty arrays and NULL arrays
    • Multiple array columns in the same result set
    • Primitive arrays inside composite types (e.g., int[] field) - properly serialized as JSON arrays

    Limitations (with ResolveNestedCompositeTypes: false):

    When ResolveNestedCompositeTypes is disabled, the array composite serialization works for one level only. Nested structures have the following behavior:

    ScenarioOutput
    Nested composite (composite inside composite)Inner composite serialized as PostgreSQL tuple string: "(1,x)" instead of {"id":1,"name":"x"}
    Array of composites inside compositeArray of tuple strings: ["(1,a)","(2,b)"] instead of [{"id":1,"name":"a"},...]

    Note: These limitations do not apply when ResolveNestedCompositeTypes: true (the default). See the ResolveNestedCompositeTypes documentation in version 3.4.4 for full nested composite support.

    For complex nested structures with the option disabled, use PostgreSQL's json_build_object/json_agg functions to construct the JSON directly in your query.

    2. Nested JSON for Composite Type Columns (Opt-in)

    When a function returns a composite type column, by default the composite type fields are expanded into separate columns (existing behavior preserved for backward compatibility).

    To serialize composite type columns as nested JSON objects, you can either:

    1. Enable globally via configuration option NestedJsonForCompositeTypes (default is false):
    json
    json
    {
    +  "NpgsqlRest": {
    +      "RoutineOptions": {
    +        "NestedJsonForCompositeTypes": true
    +    }
    +  }
    +}
    1. Enable per-endpoint via comment annotation (nested, nested_json, or nested_composite):
    sql
    sql
    comment on function get_user_with_address() is 'nested';
    +-- or: 'nested_json'
    +-- or: 'nested_composite'

    Example:

    sql
    sql
    create type address_type as (
    +    street text,
    +    city text,
    +    zip_code text
    +);
    +
    +create function get_user_with_address()
    +returns table(
    +    user_id int,
    +    user_name text,
    +    address address_type
    +)
    +language sql as $$
    +select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
    +$$;
    +
    +comment on function get_user_with_address() is 'nested';

    Default behavior (expanded columns):

    json
    json
    [{"userId":1,"userName":"Alice","street":"123 Main St","city":"New York","zipCode":"10001"}]

    With nested annotation or NestedJsonForCompositeTypes: true:

    json
    json
    [{"userId":1,"userName":"Alice","address":{"street":"123 Main St","city":"New York","zipCode":"10001"}}]

    Multidimensional Array Support

    Added proper JSON serialization for multidimensional PostgreSQL arrays. Previously, multidimensional arrays were serialized incorrectly, producing invalid JSON. Now they are properly converted to nested JSON arrays.

    Example:

    sql
    sql
    create function get_2d_int_array()
    +returns table(
    +    matrix int[][]
    +)
    +language sql as $$
    +select array[[1,2,3],[4,5,6]];
    +$$;

    Previous behavior (invalid JSON):

    code
    [{"matrix":[{1,2,3,{4,5,6]}]

    New behavior:

    json
    json
    [{"matrix":[[1,2,3],[4,5,6]]}]

    This feature is automatic and requires no configuration. It works with:

    • 2D arrays: {{1,2},{3,4}}[[1,2],[3,4]]
    • 3D arrays: {{{1,2},{3,4}},{{5,6},{7,8}}}[[[1,2],[3,4]],[[5,6],[7,8]]]
    • Higher dimensional arrays
    • All primitive types (int, text, boolean, numeric, etc.)
    • NULL values within multidimensional arrays

    Limitation: Multidimensional arrays of composite types are serialized as nested arrays of PostgreSQL tuple strings, not as fully expanded JSON objects. For example, a 2D array of composites {{"(1,a)","(2,b)"},{"(3,c)","(4,d)"}} becomes [["(1,a)","(2,b)"],["(3,c)","(4,d)"]]. The data is preserved but not fully parsed. For complex nested structures, consider using PostgreSQL's json_build_object/json_agg functions instead.

    JSON Escaping Fix for Arrays and Tuple Strings

    Fixed JSON escaping issues that could produce invalid JSON output when PostgreSQL arrays or composite types contain special characters. The fix ensures all special characters are properly escaped:

    Characters now properly escaped:

    • Quotes (") - escaped as \"
    • Backslashes (\) - escaped as \\
    • Newlines - escaped as \n
    • Tabs - escaped as \t
    • Carriage returns - escaped as \r
    • Combined special characters in the same string

    Example:

    sql
    sql
    create function get_text_array()
    +returns text[]
    +language sql as $$
    +select array['hello "world"', 'path\to\file', E'line1\nline2'];
    +$$;

    Previous behavior (could produce invalid JSON):

    json
    json
    ["hello \"world\"", "path\to\file", "line1
    +line2"]

    New behavior (valid JSON):

    json
    json
    ["hello \"world\"","path\\to\\file","line1\nline2"]

    This fix applies to:

    • Simple text arrays with special characters
    • Multidimensional arrays (2D, 3D, etc.)
    • Nested composite types serialized as tuple strings
    • Arrays of composite types with special characters in field values
    • Unicode characters and emoji (preserved correctly)
    • Empty strings and whitespace-only strings
    • JSON-like string content (properly escaped, not parsed)

    TsClient Plugin: Composite Type Interface Generation

    The TsClient plugin now generates proper TypeScript interfaces for composite types:

    Generated TypeScript:

    typescript
    typescript
    interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    authorId: number | null;
    +}
    +
    +interface IAddress {
    +    street: string | null;
    +    city: string | null;
    +    zipCode: string | null;
    +}
    +
    +interface IGetAuthorsWithBooksResponse {
    +    authorId: number | null;
    +    authorName: string | null;
    +    books: IBooks[] | null;  // Array of composite type
    +}
    +
    +interface IGetUserWithAddressResponse {
    +    userId: number | null;
    +    userName: string | null;
    +    address: IAddress | null;  // Nested composite type
    +}

    Features:

    • Separate interfaces generated for each unique composite type structure
    • Array composite columns typed as InterfaceName[]
    • Nested composite columns typed as InterfaceName
    • Interfaces are deduplicated when the same composite structure appears in multiple functions

    TsClient Limitation - Multidimensional Arrays:

    PostgreSQL normalizes multidimensional array types (int[][], int[][][]) to single-dimensional (integer[]) in all catalog views. This is a PostgreSQL limitation—there is no way to retrieve the original array dimensionality from metadata.

    Consequence: Multidimensional arrays are typed as single-dimensional in TypeScript:

    • int[][]number[] (instead of number[][])
    • int[][][]number[] (instead of number[][][])

    The runtime JSON is always correct (e.g., [[1,2],[3,4]]), but the TypeScript type won't match. For strict TypeScript projects, manually cast the response type when using multidimensional arrays.

    Optional @ Prefix Extended to Annotation Parameters

    The optional @ prefix for comment annotations (introduced in 3.3.1) now also works with annotation parameters using the key = value syntax.

    Both syntaxes are now equivalent:

    sql
    sql
    -- Without @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +raw = true
    +timeout = 30s
    +my_custom_param = custom_value
    +';
    +
    +-- With @ prefix
    +comment on function my_func() is '
    +HTTP GET
    +@raw = true
    +@timeout = 30s
    +@my_custom_param = custom_value
    +';

    This applies to all annotation parameters including raw, timeout, buffer, connection, user_context, user_parameters, SSE settings, basic auth settings, and custom parameters.

    Custom parameters with @ prefix are stored without the prefix (e.g., @my_param = value is stored as my_param).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.1.html b/guide/changelog/v3.4.1.html new file mode 100644 index 000000000..3b87ac181 --- /dev/null +++ b/guide/changelog/v3.4.1.html @@ -0,0 +1,47 @@ + + + + + + Changelog v3.4.1 (2025-01-15) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.1 (2025-01-15)

    Version 3.4.1 (2025-01-15)

    Full Changelog

    Configuration Options for Null Handling

    Added global configuration options for QueryStringNullHandling and TextResponseNullHandling in appsettings.json.

    QueryStringNullHandling

    Sets the default behavior for handling NULL values in query string parameters:

    • Ignore (default): No special handling - empty strings stay as empty strings, "null" literal stays as "null" string.
    • EmptyString: Empty query string values are interpreted as NULL values.
    • NullLiteral: Literal string "null" (case insensitive) is interpreted as NULL value.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "QueryStringNullHandling": "EmptyString"
    +  }
    +}

    TextResponseNullHandling

    Sets the default behavior for plain text responses when the execution returns NULL from the database:

    • EmptyString (default): Returns an empty string response with status code 200 OK.
    • NullLiteral: Returns a string literal "NULL" with status code 200 OK.
    • NoContent: Returns status code 204 NO CONTENT.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "TextResponseNullHandling": "NoContent"
    +  }
    +}

    Both options can also be overridden per-endpoint using comment annotations:

    sql
    sql
    comment on function my_func(text) is '
    +query_string_null_handling empty_string
    +text_response_null_handling no_content
    +';

    Bug Fixes

    • Fixed logging condition in QueryStringNullHandlingHandler that was incorrectly checking TextResponseNullHandling instead of QueryStringNullHandling when determining whether to log annotation changes.
    • Fixed overloaded function resolution not updating the SQL command text. When multiple PostgreSQL functions with the same name but different parameter types (e.g., one with int and one with a custom composite type) were mapped to the same endpoint, selecting an overload based on parameter count would use the wrong SQL expression, causing syntax errors.
    • Fixed error logging to include command parameters. When command execution failed, the error log now includes the request URL and parameter values (when LogCommands and LogCommandParameters are enabled) for easier debugging.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.2.html b/guide/changelog/v3.4.2.html new file mode 100644 index 000000000..03cc64c13 --- /dev/null +++ b/guide/changelog/v3.4.2.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.4.2 (2025-01-15) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.2 (2025-01-15)

    Version 3.4.2 (2025-01-15)

    Full Changelog

    Bug Fixes

    • Fixed AOT compatibility issue with JSON deserialization. When running with Native AOT or with reflection-based serialization disabled, parsing composite type metadata for nested array columns would fail with InvalidOperationException: Reflection-based serialization has been disabled. Added string[][] to the source-generated NpgsqlRestSerializerContext to support AOT compilation.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.3.html b/guide/changelog/v3.4.3.html new file mode 100644 index 000000000..2d0f8186d --- /dev/null +++ b/guide/changelog/v3.4.3.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.4.3 (2025-01-16) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.3 (2025-01-16)

    Version 3.4.3 (2025-01-16)

    Full Changelog

    Bug Fixes

    • Fixed double-escaping bug in PostgreSQL tuple string serialization. When composite types contain arrays of other composites (e.g., nested composite types with array fields), the JSON output now correctly escapes quotes instead of double-escaping them (\" instead of \\\"). This fix ensures that decoded tuple strings contain proper PostgreSQL tuple format with doubled quotes ("") for literal quote characters, rather than backslash-escaped quotes.

    Performance Improvements

    • Optimized PgCompositeArrayToJsonArray to use stack allocation (stackalloc) for small inputs (≤512 chars) and ArrayPool<char> for larger inputs, eliminating per-element StringBuilder allocations and reducing GC pressure.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.4.html b/guide/changelog/v3.4.4.html new file mode 100644 index 000000000..a44e7fc68 --- /dev/null +++ b/guide/changelog/v3.4.4.html @@ -0,0 +1,55 @@ + + + + + + Changelog v3.4.4 (2025-01-17) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.4 (2025-01-17)

    Version 3.4.4 (2025-01-17)

    Full Changelog

    Deep Nested Composite Type Resolution (ResolveNestedCompositeTypes)

    By default, NpgsqlRest resolves nested composite types to any depth. When a composite type contains another composite type (or an array of composites), the inner composites are serialized as proper JSON objects/arrays instead of PostgreSQL tuple strings.

    Example:

    sql
    sql
    create type inner_type as (id int, name text);
    +create type outer_type as (label text, inner_val inner_type);
    +create type with_array as (group_name text, members inner_type[]);
    +
    +create function get_nested_data()
    +returns table(data outer_type, items with_array)
    +language sql as $$
    +select
    +    row('outer', row(1, 'inner')::inner_type)::outer_type,
    +    row('group1', array[row(1,'a')::inner_type, row(2,'b')::inner_type])::with_array;
    +$$;

    Output:

    json
    json
    [{
    +  "data": {"label":"outer","innerVal":{"id":1,"name":"inner"}},
    +  "items": {"groupName":"group1","members":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
    +}]

    Configuration:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "RoutineOptions": {
    +      "ResolveNestedCompositeTypes": true
    +    }
    +  }
    +}

    Default: true - nested composites are fully resolved.

    How it works:

    At application startup, when ResolveNestedCompositeTypes is enabled:

    1. Type Cache Initialization: Queries pg_catalog to build a cache of all composite types in the database, including their field names, field types, and nested relationships.

    2. Metadata Enrichment: For each routine that returns composite types, the field descriptors are enriched with nested type information from the cache.

    3. Runtime Serialization: During request processing, the serializer checks each field's metadata. If the field is marked as a composite type (or array of composites), it recursively parses the PostgreSQL tuple string and outputs a proper JSON object/array.

    When to disable (ResolveNestedCompositeTypes: false):

    ScenarioReason
    Large schemas with thousands of composite typesReduces startup time by skipping the type cache initialization query
    No nested composites in your schemaIf your composites don't contain other composites, the cache provides no benefit
    Memory-constrained environmentsThe type cache consumes memory proportional to the number of composite types
    Backward compatibilityIf you depend on the old tuple string format "(1,x)" in your client code

    Performance considerations:

    • Startup cost: One additional query to pg_catalog at startup to build the type cache
    • Memory: Cache size is proportional to: (number of composite types) × (average fields per type)
    • Runtime: Negligible - just a dictionary lookup per composite field

    PostgreSQL version compatibility:

    Tested and works on PostgreSQL 13 through 17. The feature uses standard pg_catalog views that are stable across PostgreSQL versions.

    Edge cases handled:

    • Empty arrays of composites → []
    • NULL composite elements in arrays → [{"id":1},null,{"id":2}]
    • Composites with all NULL fields → {"id":null,"name":null}
    • Empty string vs NULL distinction → "" vs null
    • Unicode characters (emoji, Chinese, Arabic) → preserved correctly
    • Deeply nested structures (4+ levels) → fully resolved
    • Self-referencing types → cycle detection prevents infinite loops

    Bug Fixes

    • Fixed "permission denied for schema" error in the metadata query when a database user with limited privileges runs the routine discovery. The error occurred when a user with only USAGE permission on specific schemas tried to discover routines, but the database contained other schemas with composite types that the user couldn't access. The ::regtype cast in the metadata query would fail when attempting to resolve type names from unauthorized schemas. Added has_schema_privilege checks to filter out:
      • Array element types from schemas the user cannot access
      • Schemas the user cannot access from the schema aggregation
      • Routines that return types from schemas the user cannot access

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.5.html b/guide/changelog/v3.4.5.html new file mode 100644 index 000000000..35c62f24d --- /dev/null +++ b/guide/changelog/v3.4.5.html @@ -0,0 +1,52 @@ + + + + + + Changelog v3.4.5 (2025-01-19) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.5 (2025-01-19)

    Version 3.4.5 (2025-01-19)

    Full Changelog

    NpgsqlRest.TsClient: Deep Nested Composite Type Support

    Fixed the TypeScript client generator (NpgsqlRest.TsClient) to properly handle deeply nested composite types when NestedJsonForCompositeTypes is enabled.

    Before (incorrect):

    typescript
    typescript
    interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    reviews: string[] | null;  // Wrong: should be IReviews[]
    +}

    After (correct):

    typescript
    typescript
    interface IReviews {
    +    reviewId: number | null;
    +    bookId: number | null;
    +    reviewerName: string | null;
    +    rating: number | null;
    +    reviewText: string | null;
    +}
    +
    +interface IBooks {
    +    bookId: number | null;
    +    title: string | null;
    +    reviews: IReviews[] | null;  // Correct: properly typed array
    +}

    Supported scenarios:

    • Arrays of composites containing arrays: books[] where each book has reviews[]
    • Deep nesting (4+ levels): level4 → level3 → level2 → level1
    • Mixed nesting: Composite containing nested composite that contains array of composites

    The fix recursively processes TypeDescriptor.CompositeFieldNames, TypeDescriptor.CompositeFieldDescriptors, TypeDescriptor.ArrayCompositeFieldNames, and TypeDescriptor.ArrayCompositeFieldDescriptors to generate proper TypeScript interfaces for all nested types.

    Note: This only applies when NestedJsonForCompositeTypes is enabled (via nested annotation or global config). When disabled, arrays of composite types correctly remain as string[] to match the PostgreSQL tuple string format returned by the API.


    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.6.html b/guide/changelog/v3.4.6.html new file mode 100644 index 000000000..d81d3a06d --- /dev/null +++ b/guide/changelog/v3.4.6.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.4.6 (2025-01-21) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.6 (2025-01-21)

    Version 3.4.6 (2025-01-21)

    Full Changelog

    Endpoint Execution Performance Optimizations

    Reduced memory allocations and CPU overhead in the hot path of endpoint execution through several optimizations:

    StringBuilder Pooling

    Added a thread-safe StringBuilderPool to reuse StringBuilder instances across requests instead of allocating new ones:

    • cmdLog - command logging
    • cacheKeys - cache key building
    • rowBuilder - response row building
    • compositeFieldBuffer - nested JSON composite handling
    • commandTextBuilder - SQL command text building

    The pool maintains up to 64 instances with lock-free rent/return operations.

    Avoid Query String Dictionary Allocation

    Changed from context.Request.Query.ToDictionary() to using IQueryCollection directly, eliminating a dictionary allocation on every request. The IQueryCollection interface already provides TryGetValue(), Count, and ContainsKey() methods.

    StringBuilder for Command Text Building

    Replaced ~18 string.Concat(commandText, ...) calls with StringBuilder.Append() operations, reducing intermediate string allocations when building SQL commands for non-formattable routines.

    HashSet for Path Parameter Lookup

    Added FindMatchingPathParameter() method with lazy-initialized HashSet<string> for O(1) case-insensitive lookups instead of O(n) array iteration when matching path parameters.


    Comprehensive CancellationToken Propagation

    Improved cancellation token propagation throughout the entire request pipeline. The CancellationToken parameter is now properly passed to all async operations, enabling proper request cancellation and resource cleanup when clients disconnect or requests are aborted.

    Changes:

    • NpgsqlRestEndpoint: Fixed missing cancellation token propagation to ReadToEndAsync, ReadAsync, WriteAsync, FlushAsync, BeginTransactionAsync, CommitAsync, and helper methods (PrepareCommand, OpenConnectionAsync, ValidateParametersAsync, ReturnErrorAsync).

    • Auth Handlers: Added CancellationToken parameter to BasicAuthHandler.HandleAsync, LoginHandler.HandleAsync, and LogoutHandler.HandleAsync. All database operations and response writes now respect cancellation.

    • Upload Handlers: Updated IUploadHandler.UploadAsync interface and all implementations (DefaultUploadHandler, FileSystemUploadHandler, LargeObjectUploadHandler, CsvUploadHandler, ExcelUploadHandler) to accept and propagate cancellation tokens to file I/O and database operations.

    • Proxy Handler: Added CancellationToken parameter to ProxyRequestHandler.WriteResponseAsync for cancellable response body writes.

    Benefits:

    • Immediate cleanup when HTTP clients disconnect mid-request
    • Proper cancellation of long-running database queries
    • Reduced resource consumption from abandoned requests
    • Better handling of upload/download operations that can be cancelled
    • Prevents request storms: When users repeatedly refresh the browser during slow endpoint execution, each refresh creates a new request while the previous one continues running. Without proper cancellation token propagation, these abandoned requests continue executing database queries, potentially choking the database. With this fix, abandoned requests are properly cancelled, freeing up database connections immediately.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.7.html b/guide/changelog/v3.4.7.html new file mode 100644 index 000000000..a0874abb1 --- /dev/null +++ b/guide/changelog/v3.4.7.html @@ -0,0 +1,37 @@ + + + + + + Changelog v3.4.7 (2025-01-21) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.4.7 (2025-01-21)

    Version 3.4.7 (2025-01-21)

    Full Changelog

    Type Category Lookup Optimization

    Introduced TypeCategory flags enum and pre-computed lookup table for O(1) type dispatch, replacing sequential if-chain conditionals in hot paths.

    New Files:

    • TypeCategory.cs - Flags enum (Numeric, Boolean, Json, Text, DateTime, Date, NeedsEscape, CastToText, Binary, Time) and TypeCategoryLookup static class with 128-element array for instant type classification
    • ParameterParsers.cs - Delegate array for O(1) parameter parser lookup by NpgsqlDbType

    Changes:

    • TypeDescriptor now has a Category property computed once at construction via lookup table
    • Boolean properties (IsNumeric, IsJson, IsText, etc.) are now computed from Category using bitwise operations
    • NpgsqlRestEndpoint.cs and PgConverters.cs use bitwise category checks for type dispatch

    Benchmark Results:

    OperationBeforeAfterImprovement
    Type category lookup (18 types)22.6 ns6.6 ns70% faster
    TypeDescriptor construction232.8 ns164.2 ns29% faster
    Parser delegate lookup7.6 ns5.9 ns23% faster
    Combined type check (bitwise vs properties)7.97 ns4.94 ns38% faster
    Serialization type check (1000 rows)5,572 ns4,060 ns27% faster

    Note: While micro-benchmarks show significant improvements, real-world endpoint throughput gains are modest (1-5%) since type dispatch is a small fraction of total request time compared to database I/O and serialization.

    Additional Allocation Optimizations

    Parameter Logging String Allocations

    Replaced string.Concat() with paramIndex.ToString() in 8 logging paths with direct StringBuilder.Append(int) calls, eliminating intermediate string allocations for each logged parameter.

    Before:

    csharp
    csharp
    cmdLog!.AppendLine(string.Concat("-- $", paramIndex.ToString(), " ", ...));

    After:

    csharp
    csharp
    cmdLog!.Append("-- $").Append(paramIndex).Append(' ').Append(...).AppendLine(p);

    Cache Key String Reuse

    Cache key string (cacheKeys.ToString()) was being called 3-6 times per cached request. Now computed once and reused:

    csharp
    csharp
    string? cacheKeyString = cacheKeys?.ToString();
    +// Reused in all cache Get/AddOrUpdate calls

    Impact: Eliminates 8+ string allocations per parameter-heavy request (logging) and 2-5 allocations per cached request (cache keys).


    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.4.8.html b/guide/changelog/v3.4.8.html new file mode 100644 index 000000000..f7fa6a4bd --- /dev/null +++ b/guide/changelog/v3.4.8.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.4.8 (2025-01-26) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/guide/changelog/v3.5.0.html b/guide/changelog/v3.5.0.html new file mode 100644 index 000000000..15a72aa49 --- /dev/null +++ b/guide/changelog/v3.5.0.html @@ -0,0 +1,41 @@ + + + + + + Changelog v3.5.0 (2025-01-28) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.5.0 (2025-01-28)

    Version 3.5.0 (2025-01-28)

    Full Changelog

    New Feature: PasskeyAuth (WebAuthn/FIDO2)

    Added WebAuthn/FIDO2 Passkey Authentication support, enabling phishing-resistant, passwordless authentication using device-native biometrics or PINs. This feature allows users to authenticate using passkeys stored on their devices without requiring any external authentication libraries.

    Bugfix: Response Compression for Static Files

    Fixed an issue where ResponseCompression middleware was not compressing static files served by AppStaticFileMiddleware. The middleware was setting Content-Length header before writing the response body, which prevented the compression middleware from compressing the response. Also added text/javascript to the default list of compressible MIME types.

    Added Client Integration Tests

    Added automated integration tests for NpgsqlRestClient configuration features to catch configuration bugs in the CI/CD pipeline:

    • ResponseCompression Tests - Verify compression works correctly for static files and API responses
    • CORS Tests - Verify CORS headers, preflight requests, and origin validation
    • StaticFiles Tests - Verify content parsing, claims replacement, and file serving

    Separate Core and Client Logging

    Added ability to configure separate log levels for the core NpgsqlRest library and the NpgsqlRestClient application. This allows fine-grained control over logging verbosity:

    json
    json
    "MinimalLevels": {
    +  "NpgsqlRest": "Information",
    +  "NpgsqlRestClient": "Debug",
    +  "System": "Warning",
    +  "Microsoft": "Warning"
    +}
    • NpgsqlRest - Controls log level for the core library (endpoint creation, SQL execution, etc.)
    • NpgsqlRestClient - Controls log level for the client application (configuration, authentication setup, passkeys, etc.)

    Debug Log Filtering Options

    Added two new boolean options to control debug-level logging verbosity:

    • DebugLogEndpointCreateEvents (default: true) - When false, suppresses "Created endpoint" debug logs
    • DebugLogCommentAnnotationEvents (default: true) - When false, suppresses comment annotation parsing debug logs

    These options allow users to reduce log noise while keeping the log level at Debug for other important information.


    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.6.0.html b/guide/changelog/v3.6.0.html new file mode 100644 index 000000000..47bf8c8a1 --- /dev/null +++ b/guide/changelog/v3.6.0.html @@ -0,0 +1,281 @@ + + + + + + Changelog v3.6.0 (2025-02-01) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.6.0 (2025-02-01)

    Version 3.6.0 (2025-02-01)

    Full Changelog

    New Feature: Security Headers Middleware

    Added configurable security headers middleware to protect against common web vulnerabilities. The middleware adds HTTP security headers to all responses:

    • X-Content-Type-Options - Prevents MIME-sniffing attacks (default: nosniff)
    • X-Frame-Options - Prevents clickjacking attacks (default: DENY, skipped if Antiforgery is enabled)
    • Referrer-Policy - Controls referrer information (default: strict-origin-when-cross-origin)
    • Content-Security-Policy - Defines approved content sources (configurable)
    • Permissions-Policy - Controls browser feature access (configurable)
    • Cross-Origin-Opener-Policy - Controls document sharing with popups
    • Cross-Origin-Embedder-Policy - Controls cross-origin resource loading
    • Cross-Origin-Resource-Policy - Controls resource sharing cross-origin

    Configuration:

    jsonc
    jsonc
    //
    +// Security Headers: Adds HTTP security headers to all responses to protect against common web vulnerabilities.
    +// These headers instruct browsers how to handle your content securely.
    +// Note: X-Frame-Options is automatically handled by the Antiforgery middleware when enabled (see Antiforgery.SuppressXFrameOptionsHeader).
    +// Reference: https://owasp.org/www-project-secure-headers/
    +//
    +"SecurityHeaders": {
    +  //
    +  // Enable security headers middleware. When enabled, configured headers are added to all HTTP responses.
    +  //
    +  "Enabled": false,
    +  //
    +  // X-Content-Type-Options: Prevents browsers from MIME-sniffing a response away from the declared content-type.
    +  // Recommended value: "nosniff"
    +  // Set to null to not include this header.
    +  //
    +  "XContentTypeOptions": "nosniff",
    +  //
    +  // X-Frame-Options: Controls whether the browser should allow the page to be rendered in a <frame>, <iframe>, <embed> or <object>.
    +  // Values: "DENY" (never allow), "SAMEORIGIN" (allow from same origin only)
    +  // Note: This header is SKIPPED if Antiforgery is enabled (Antiforgery already sets X-Frame-Options: SAMEORIGIN by default).
    +  // Set to null to not include this header.
    +  //
    +  "XFrameOptions": "DENY",
    +  //
    +  // Referrer-Policy: Controls how much referrer information should be included with requests.
    +  // Values: "no-referrer", "no-referrer-when-downgrade", "origin", "origin-when-cross-origin",
    +  //         "same-origin", "strict-origin", "strict-origin-when-cross-origin", "unsafe-url"
    +  // Recommended: "strict-origin-when-cross-origin" (send origin for cross-origin requests, full URL for same-origin)
    +  // Set to null to not include this header.
    +  //
    +  "ReferrerPolicy": "strict-origin-when-cross-origin",
    +  //
    +  // Content-Security-Policy: Defines approved sources of content that the browser may load.
    +  // Helps prevent XSS, clickjacking, and other code injection attacks.
    +  // Example: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
    +  // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    +  // Set to null to not include this header (recommended to configure based on your application needs).
    +  //
    +  "ContentSecurityPolicy": null,
    +  //
    +  // Permissions-Policy: Controls which browser features and APIs can be used.
    +  // Example: "geolocation=(), microphone=(), camera=()" disables these features entirely.
    +  // Example: "geolocation=(self), microphone=()" allows geolocation only from same origin.
    +  // Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy
    +  // Set to null to not include this header.
    +  //
    +  "PermissionsPolicy": null,
    +  //
    +  // Cross-Origin-Opener-Policy: Controls how your document is shared with cross-origin popups.
    +  // Values: "unsafe-none", "same-origin-allow-popups", "same-origin"
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginOpenerPolicy": null,
    +  //
    +  // Cross-Origin-Embedder-Policy: Prevents a document from loading cross-origin resources that don't explicitly grant permission.
    +  // Values: "unsafe-none", "require-corp", "credentialless"
    +  // Required for SharedArrayBuffer and high-resolution timers (along with COOP: same-origin).
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginEmbedderPolicy": null,
    +  //
    +  // Cross-Origin-Resource-Policy: Indicates how the resource should be shared cross-origin.
    +  // Values: "same-site", "same-origin", "cross-origin"
    +  // Set to null to not include this header.
    +  //
    +  "CrossOriginResourcePolicy": null
    +}

    New Feature: Forwarded Headers Middleware

    Added support for processing proxy headers when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.). This is critical for getting the correct client IP address and protocol.

    • X-Forwarded-For - Gets real client IP instead of proxy IP
    • X-Forwarded-Proto - Gets original protocol (http/https)
    • X-Forwarded-Host - Gets original host header

    Configuration:

    jsonc
    jsonc
    //
    +// Forwarded Headers: Enables the application to read proxy headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host).
    +// CRITICAL: Required when running behind a reverse proxy (nginx, Apache, Azure App Service, AWS ALB, Cloudflare, etc.)
    +// Without this, the application sees the proxy's IP instead of the client's real IP, and HTTP instead of HTTPS.
    +// Security Warning: Only enable if you're behind a trusted proxy. Malicious clients can spoof these headers.
    +// Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer
    +//
    +"ForwardedHeaders": {
    +  //
    +  // Enable forwarded headers middleware. Must be placed FIRST in the middleware pipeline.
    +  //
    +  "Enabled": false,
    +  //
    +  // Limits the number of proxy entries that will be processed from X-Forwarded-For.
    +  // Default is 1 (trust only the immediate proxy). Increase if you have multiple proxies in a chain.
    +  // Set to null to process all entries (not recommended for security).
    +  //
    +  "ForwardLimit": 1,
    +  //
    +  // List of IP addresses of known proxies to accept forwarded headers from.
    +  // Example: ["10.0.0.1", "192.168.1.1"]
    +  // If empty and KnownNetworks is also empty, forwarded headers are accepted from any source (less secure).
    +  //
    +  "KnownProxies": [],
    +  //
    +  // List of CIDR network ranges of known proxies.
    +  // Example: ["10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"] for private networks
    +  // Useful when proxy IPs are dynamically assigned within a known range.
    +  //
    +  "KnownNetworks": [],
    +  //
    +  // List of allowed values for the X-Forwarded-Host header.
    +  // Example: ["example.com", "www.example.com"]
    +  // If empty, any host is allowed (less secure). Helps prevent host header injection attacks.
    +  //
    +  "AllowedHosts": []
    +}

    New Feature: Health Check Endpoints

    Added health check endpoints for container orchestration (Kubernetes, Docker Swarm) and monitoring systems:

    • /health - Overall health status (combines all checks)
    • /health/ready - Readiness probe with optional PostgreSQL connectivity check
    • /health/live - Liveness probe (always returns healthy if app is running)

    Configuration:

    jsonc
    jsonc
    //
    +// Health Checks: Provides endpoints for monitoring application health, used by container orchestrators (Kubernetes, Docker Swarm),
    +// load balancers, and monitoring systems to determine if the application is running correctly.
    +// Three types of checks are supported:
    +//   - /health: Overall health status (combines all checks)
    +//   - /health/ready: Readiness probe - is the app ready to accept traffic? (includes database connectivity)
    +//   - /health/live: Liveness probe - is the app process running? (always returns healthy if app responds)
    +// Reference: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/health-checks
    +//
    +"HealthChecks": {
    +  //
    +  // Enable health check endpoints.
    +  //
    +  "Enabled": false,
    +  //
    +  // Cache health check responses server-side in memory for the specified duration.
    +  // Cached responses are served without re-executing the endpoint. 
    +  // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +  // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +  //
    +  "CacheDuration": "5 seconds",
    +  //
    +  // Path for the main health check endpoint that reports overall status.
    +  // Returns "Healthy", "Degraded", or "Unhealthy" with HTTP 200 (healthy/degraded) or 503 (unhealthy).
    +  //
    +  "Path": "/health",
    +  //
    +  // Path for the readiness probe endpoint.
    +  // Kubernetes uses this to know when a pod is ready to receive traffic.
    +  // Includes database connectivity check when IncludeDatabaseCheck is true.
    +  // Returns 503 Service Unavailable if database is unreachable.
    +  //
    +  "ReadyPath": "/health/ready",
    +  //
    +  // Path for the liveness probe endpoint.
    +  // Kubernetes uses this to know when to restart a pod.
    +  // Always returns Healthy (200) if the application process is responding.
    +  // Does NOT check database - a slow database shouldn't trigger a container restart.
    +  //
    +  "LivePath": "/health/live",
    +  //
    +  // Include PostgreSQL database connectivity in health checks.
    +  // When true, the readiness probe will fail if the database is unreachable.
    +  //
    +  "IncludeDatabaseCheck": true,
    +  //
    +  // Name for the database health check (appears in detailed health reports).
    +  //
    +  "DatabaseCheckName": "postgresql",
    +  //
    +  // Require authentication for health check endpoints.
    +  // When true, all health endpoints require a valid authenticated user.
    +  // Security Consideration: Health endpoints can reveal information about your infrastructure
    +  // (database connectivity, service status). Enable this if your health endpoints are publicly accessible.
    +  // Note: Kubernetes/Docker health probes may need to authenticate if this is enabled.
    +  //
    +  "RequireAuthorization": false,
    +  //
    +  // Apply a rate limiter policy to health check endpoints.
    +  // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +  // Security Consideration: Prevents denial-of-service attacks targeting health endpoints.
    +  // Set to null to disable rate limiting on health endpoints.
    +  // Example: "fixed" or "bucket" (must match a policy name from RateLimiterOptions).
    +  //
    +  "RateLimiterPolicy": null
    +}

    Added new dependency: AspNetCore.HealthChecks.NpgSql for PostgreSQL health checks.

    New Feature: PostgreSQL Statistics Endpoints

    Added HTTP endpoints for monitoring PostgreSQL database statistics, useful for debugging, performance analysis, and operational monitoring:

    • /stats/routines - Function/procedure performance statistics from pg_stat_user_functions (call counts, execution times)
    • /stats/tables - Table statistics from pg_stat_user_tables (tuple counts, sizes, scan counts, vacuum info)
    • /stats/indexes - Index statistics from pg_stat_user_indexes (scan counts, definitions)
    • /stats/activity - Current database activity from pg_stat_activity (active sessions, queries, wait events)

    Output formats:

    • HTML (default) - HTML table with Excel-compatible formatting for direct browser copy-paste
    • JSON - JSON array with camelCase property names

    Configuration:

    jsonc
    jsonc
    //
    +// PostgreSQL Statistics Endpoints
    +// Exposes PostgreSQL statistics through HTTP endpoints for monitoring and debugging.
    +// Provides access to pg_stat_user_functions, pg_stat_user_tables, pg_stat_user_indexes, and pg_stat_activity.
    +//
    +"Stats": {
    +  //
    +  // Enable PostgreSQL statistics endpoints.
    +  //
    +  "Enabled": false,
    +  //
    +  // Cache stats responses server-side in memory for the specified duration.
    +  // Cached responses are served without re-executing the endpoint.
    +  // Value is in PostgreSQL interval format (e.g., '5 seconds', '1 minute', '30s', '1min').
    +  // Set to null to disable caching. Query strings are ignored to prevent cache-busting.
    +  //
    +  "CacheDuration": "5 seconds",
    +  //
    +  // Apply a rate limiter policy to stats endpoints.
    +  // Specify the name of a policy defined in RateLimiterOptions.Policies.
    +  // Set to null to disable rate limiting on stats endpoints.
    +  //
    +  "RateLimiterPolicy": null,
    +  //
    +  // Use a specific named connection for stats queries.
    +  // When null, uses the default connection string.
    +  // Useful when you want to query stats from a different database or use read-only credentials.
    +  //
    +  "ConnectionName": null,
    +  //
    +  // Require authentication for stats endpoints.
    +  // Security Consideration: Stats endpoints can reveal sensitive information about your database
    +  // (table sizes, query patterns, active sessions). Enable this for production environments.
    +  //
    +  "RequireAuthorization": false,
    +  //
    +  // Restrict access to specific roles.
    +  // When null or empty, any authenticated user can access (if RequireAuthorization is true).
    +  // Example: ["admin", "dba"] - only users with admin or dba role can access.
    +  //
    +  "AuthorizedRoles": [],
    +  //
    +  // Output format for stats endpoints: "json" or "html".
    +  // - json: JSON array
    +  // - html: HTML table, Excel-compatible for direct browser copy-paste (default)
    +  //
    +  "OutputFormat": "html",
    +  //
    +  // Filter schemas using PostgreSQL SIMILAR TO pattern.
    +  // When null, all schemas are included.
    +  // Example: "public|myapp%" - includes 'public' and schemas starting with 'myapp'.
    +  //
    +  "SchemaSimilarTo": null,
    +  //
    +  // Path for routine (function/procedure) performance statistics.
    +  // Returns data from pg_stat_user_functions including call counts and execution times.
    +  // Note: Requires track_functions = 'pl' or 'all' in postgresql.conf.
    +  // Enable with: alter system set track_functions = 'all'; select pg_reload_conf();
    +  // Or set track_functions = 'all' directly in postgresql.conf and restart/reload.
    +  //
    +  "RoutinesStatsPath": "/stats/routines",
    +  //
    +  // Path for table statistics.
    +  // Returns data from pg_stat_user_tables including tuple counts, sizes, scan counts, and vacuum info.
    +  //
    +  "TablesStatsPath": "/stats/tables",
    +  //
    +  // Path for index statistics.
    +  // Returns data from pg_stat_user_indexes including scan counts and index definitions.
    +  //
    +  "IndexesStatsPath": "/stats/indexes",
    +  //
    +  // Path for current database activity.
    +  // Returns data from pg_stat_activity showing active sessions, queries, and wait events.
    +  // Security Consideration: Shows currently running queries which may contain sensitive data.
    +  //
    +  "ActivityPath": "/stats/activity"
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.6.1.html b/guide/changelog/v3.6.1.html new file mode 100644 index 000000000..50969fba3 --- /dev/null +++ b/guide/changelog/v3.6.1.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.6.1 (2025-02-02) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.6.1 (2025-02-02)

    Version 3.6.1 (2025-02-02)

    Full Changelog

    Fixes

    • Fixed RequireAuthorization on Stats and Health endpoints to use manual authorization check consistent with NpgsqlRest endpoints.
    • Fixed ActivityQuery in Stats endpoints.
    • Fixed OutputFormat default value in Stats endpoints.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.6.2.html b/guide/changelog/v3.6.2.html new file mode 100644 index 000000000..39a1d94a5 --- /dev/null +++ b/guide/changelog/v3.6.2.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.6.2 (2025-02-02) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.6.2 (2025-02-02)

    Version 3.6.2 (2025-02-02)

    Full Changelog

    Fixes

    • Fixed NestedJsonForCompositeTypes option from RoutineOptions not being applied to endpoints. Previously, only the nested comment annotation could enable nested JSON serialization for composite types. Now the global configuration option is properly applied as the default for all endpoints.

    • Fixed TypeScript client (NpgsqlRest.TsClient) generating incorrect types for composite columns when NestedJsonForCompositeTypes is false (the default). The client now correctly generates flat field types matching the actual JSON response structure, instead of always generating nested interfaces.

    Breaking Changes

    • Added NestedJsonForCompositeTypes property to IRoutineSource interface. Custom implementations of IRoutineSource will need to add this property.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.6.3.html b/guide/changelog/v3.6.3.html new file mode 100644 index 000000000..fd6a4735a --- /dev/null +++ b/guide/changelog/v3.6.3.html @@ -0,0 +1,36 @@ + + + + + + Changelog v3.6.3 (2025-02-03) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.6.3 (2025-02-03)

    Version 3.6.3 (2025-02-03)

    Full Changelog

    Fixes

    • Fixed ParseEnvironmentVariables feature not working for Kestrel configuration values. Previously, environment variable placeholders (e.g., {MY_HOST}) in Kestrel settings like Endpoints URLs, Certificate paths/passwords, and Limits were not being replaced because Kestrel uses ASP.NET Core's direct binding which bypassed the custom placeholder processing. Now all Kestrel configuration values properly support environment variable replacement when ParseEnvironmentVariables is enabled.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.7.0.html b/guide/changelog/v3.7.0.html new file mode 100644 index 000000000..6e66b3f62 --- /dev/null +++ b/guide/changelog/v3.7.0.html @@ -0,0 +1,64 @@ + + + + + + Changelog v3.7.0 (2025-02-07) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.7.0 (2025-02-07)

    Version 3.7.0 (2025-02-07)

    Full Changelog

    Fixes

    • Fixed comma separator bug in Excel Upload Handler error response when processing multiple files. The fileId counter was not incremented on error, causing malformed JSON output when an invalid file was followed by additional files.

    • Fixed CustomHost configuration in ClientCodeGen not accepting an empty string value. Setting "CustomHost": "" was treated the same as null (triggering host auto-detection) because GetConfigStr uses string.IsNullOrEmpty. Now an explicit empty string correctly produces const baseUrl = ""; in generated TypeScript, which is useful for relative URL paths.

    New Features

    • Added fallback_handler parameter to the Excel Upload Handler. When set (e.g., fallback_handler = csv), if ExcelDataReader fails to parse an uploaded file (invalid Excel format), the handler automatically delegates processing to the named fallback handler. This allows a single upload endpoint to accept both Excel and CSV files transparently:
    sql
    sql
    comment on function my_upload(json) is '
    +@upload for excel
    +@fallback_handler = csv
    +@row_command = select process_row($1,$2)
    +';

    New Feature: Pluggable Table Format Renderers

    Added a pluggable table format rendering system that allows PostgreSQL function results to be rendered as HTML tables or Excel spreadsheet downloads instead of JSON, controlled by the @table_format annotation.

    HTML Table Format

    Renders results as a styled HTML table suitable for browser viewing and copy-paste into Excel:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = html
    +';

    Configuration options in TableFormatOptions: HtmlEnabled, HtmlKey, HtmlHeader, HtmlFooter.

    Excel Table Format

    Renders results as an .xlsx Excel spreadsheet download using the SpreadCheetah library (streaming, AOT-compatible):

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +';

    Configuration options in TableFormatOptions: ExcelEnabled, ExcelKey, ExcelSheetName, ExcelDateTimeFormat, ExcelNumericFormat.

    • ExcelDateTimeFormat — Excel Format Code for DateTime cells (default: yyyy-MM-dd HH:mm:ss). Examples: yyyy-mm-dd, dd/mm/yyyy hh:mm.
    • ExcelNumericFormat — Excel Format Code for numeric cells (default: General). Examples: #,##0.00, 0.00.

    Per-Endpoint Custom Parameters

    The download filename and worksheet name can be overridden per-endpoint via custom parameter annotations:

    sql
    sql
    comment on function get_report() is '
    +HTTP GET
    +@table_format = excel
    +@excel_file_name = monthly_report.xlsx
    +@excel_sheet = Report Data
    +';

    These also support dynamic placeholders resolved from function parameters:

    sql
    sql
    comment on function get_report(_format text, _file_name text, _sheet_name text) is '
    +HTTP GET
    +@table_format = {_format}
    +@excel_file_name = {_file_name}
    +@excel_sheet = {_sheet_name}
    +';

    TsClient: Per-Endpoint URL Export Control

    Added two new custom parameter annotations to control TypeScript client code generation per-endpoint:

    tsclient_export_url

    Overrides the global ExportUrls configuration setting for a specific endpoint:

    sql
    sql
    comment on function login(_username text, _password text) is '
    +HTTP POST
    +@login
    +@tsclient_export_url = true
    +';

    When enabled, the generated TypeScript exports a URL constant for that endpoint:

    typescript
    typescript
    export const loginUrl = () => baseUrl + "/api/login";

    tsclient_url_only

    When set, only the URL constant is exported — the fetch function and response type interface are skipped entirely. Implies tsclient_export_url = true:

    sql
    sql
    comment on function get_data(_format text) is '
    +HTTP GET
    +@table_format = {_format}
    +@tsclient_url_only = true
    +';

    This generates only the URL constant and request interface, which is useful for endpoints consumed via browser navigation (e.g., table format downloads) rather than fetch calls.


    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.8.0.html b/guide/changelog/v3.8.0.html new file mode 100644 index 000000000..f3ba1f0ac --- /dev/null +++ b/guide/changelog/v3.8.0.html @@ -0,0 +1,52 @@ + + + + + + Changelog v3.8.0 (2025-02-11) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.8.0 (2025-02-11)

    Version 3.8.0 (2025-02-11)

    Full Changelog

    New Feature: Configuration Key Validation

    Added startup validation that checks all configuration keys in appsettings.json against the known defaults schema. This catches typos and unknown keys that would otherwise be silently ignored (e.g., LogCommand instead of LogCommands).

    Controlled by the new Config:ValidateConfigKeys setting with three modes:

    • "Warning" (default) — logs warnings for unknown keys, startup continues.
    • "Error" — logs errors for unknown keys and exits the application.
    • "Ignore" — no validation.
    json
    json
    "Config": {
    +  "ValidateConfigKeys": "Warning"
    +}

    Example output:

    code
    [12:34:56 WRN] Unknown configuration key: NpgsqlRest:KebabCaselUrls

    Removed

    • Removed the Config:ExposeAsEndpoint option. Use the --config CLI switch to inspect configuration instead.

    Kestrel Configuration Validation

    Configuration key validation also covers the Kestrel section, checking against the known Kestrel schema including Limits, Http2, Http3, and top-level flags like DisableStringReuse and AllowSynchronousIO. User-defined endpoint and certificate names under Endpoints and Certificates remain open-ended and won't trigger warnings.

    Syntax Highlighted --config Output

    The --config CLI switch now outputs JSON with syntax highlighting (keys, strings, numbers/booleans, and structural characters in distinct colors). When output is redirected to a file, plain JSON is emitted without color codes. The --config switch can now appear anywhere in the argument list and be combined with config files and --key=value overrides.

    Improved CLI Error Handling

    Unknown command-line parameters now display a clear error message with a --help hint instead of an unhandled exception stack trace.

    Universal fallback_handler for All Upload Handlers

    The fallback_handler parameter, previously Excel-only, is now available on all upload handlers via BaseUploadHandler. When a handler's format validation fails and a fallback_handler is configured, processing is automatically delegated to the named fallback handler.

    This enables scenarios like: CSV format check fails on a binary file → fall back to large_object or file_system to save the raw file for analysis.

    sql
    sql
    comment on function my_csv_upload(json) is '
    +@upload for csv
    +@check_format = true
    +@fallback_handler = large_object
    +@row_command = select process_row($1,$2)
    +';

    Optional Path Parameters

    Path parameters now support the ASP.NET Core optional parameter syntax {param?}. When a path parameter is marked as optional and the corresponding PostgreSQL function parameter has a default value, omitting the URL segment will use the PostgreSQL default:

    sql
    sql
    create function get_item(p_id int default 42) returns text ...
    +comment on function get_item(int) is '
    +HTTP GET /items/{p_id?}
    +';
    • GET /items/5 → uses the provided value 5
    • GET /items/ → uses the PostgreSQL default 42

    This also works with query_string_null_handling null_literal to pass NULL via the literal string "null" in the path for any parameter type:

    sql
    sql
    create function get_item(p_id int default null) returns text ...
    +comment on function get_item(int) is '
    +HTTP GET /items/{p_id}
    +query_string_null_handling null_literal
    +';
    • GET /items/null → passes SQL NULL to the function

    Fixes

    • Fixed query string overload resolution not accounting for path parameters. GET endpoints with path parameters and overloaded functions (same name, different signatures) would resolve to the wrong function. The body JSON overload resolution already handled this correctly.
    • Added missing QueryStringNullHandling and TextResponseNullHandling entries to ConfigDefaults, which caused them to be absent from --config output.
    • Added missing Pattern, MinLength, and MaxLength properties to default validation rule schemas in ConfigDefaults.

    Machine-Readable CLI Commands for Tool Integration

    Added new CLI commands designed for programmatic consumption by tools like pgdev. All JSON-outputting commands use syntax highlighting when run in a terminal and emit plain JSON when piped or redirected.

    --version --json

    Outputs version information as structured JSON including all assembly versions, runtime, platform RID, and directories:

    code
    npgsqlrest --version --json

    --validate [--json]

    Pre-flight check that validates configuration keys against known defaults and tests the database connection, then exits with code 0 (success) or 1 (failure):

    code
    npgsqlrest --validate
    +npgsqlrest --validate --json

    --config-schema

    Outputs a JSON Schema (draft-07) describing the full appsettings.json configuration structure — types, defaults, and enum constraints. Can be used for IDE autocomplete via the $schema property or as the foundation for config editing UIs:

    code
    npgsqlrest --config-schema

    --annotations

    Outputs all 44 supported SQL comment annotations as a JSON array with name, aliases, syntax, and description for each:

    code
    npgsqlrest --annotations

    --endpoints

    Connects to the database, discovers all generated REST endpoints, outputs full metadata (method, path, routine info, parameters, return columns, authorization, custom parameters), then exits. Logging is suppressed to keep output clean:

    code
    npgsqlrest --endpoints

    --config (updated)

    The --config --json flag has been removed. The --config command now always uses automatic detection: syntax highlighted in terminal, plain JSON when output is piped or redirected.

    Stats Endpoints: format Query String Override

    Stats endpoints now accept an optional format query string parameter that overrides the configured Stats:OutputFormat setting per-request. Valid values are html and json.

    code
    GET /api/stats/routines?format=json
    +GET /api/stats/tables?format=html

    Comments

    + + + + \ No newline at end of file diff --git a/guide/changelog/v3.9.0.html b/guide/changelog/v3.9.0.html new file mode 100644 index 000000000..886aa0bb1 --- /dev/null +++ b/guide/changelog/v3.9.0.html @@ -0,0 +1,38 @@ + + + + + + Changelog v3.9.0 (2026-02-23) | NpgsqlRest - Automatic PostgreSQL Web Server + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Changelog v3.9.0 (2026-02-23)

    Version 3.9.0 (2026-02-23)

    Full Changelog

    Commented Configuration Output (--config)

    The --config output now includes inline JSONC comments with descriptions for every setting, matching the appsettings.json file exactly. This makes it easy to understand what each setting does without consulting the documentation. The default configuration file can be constructed with:

    code
    npgsqlrest --config > appsettings.json

    Configuration Search and Filter (--config [filter])

    Added an optional filter argument to --config that searches keys, comments, and values (case-insensitive) and returns only matching settings as valid JSONC:

    code
    npgsqlrest --config cors
    +npgsqlrest --config=timeout
    +npgsqlrest --config minworker

    Output preserves the full section hierarchy so it can be copy-pasted directly into appsettings.json. When a key inside a section matches, the parent section wrapper is included. When a section name or its comment matches, the entire section is shown. Matched terms are highlighted with inverted colors in the terminal; piped output is plain text.

    CLI Improvements

    • Case-insensitive config overrides: Command-line config overrides like --Applicationname=test now correctly update the existing ApplicationName key instead of creating a duplicate entry with different casing.
    • Config validation on --config: The --config command now validates configuration keys before dumping. Unknown keys (e.g., --xxx=test) produce an error on stderr and exit with code 1.
    • Redirected output fix: Formatted CLI output (--help, --version) no longer crashes when stdout is redirected (e.g., piped or captured by a parent process).
    • CLI test suite: Added process-based tests for all CLI commands (--help, --version, --hash, --basic_auth, --config-schema, --annotations, --config, --config [filter], invalid args).

    Comments

    + + + + \ No newline at end of file diff --git a/guide/configuration.html b/guide/configuration.html new file mode 100644 index 000000000..52c07f3d6 --- /dev/null +++ b/guide/configuration.html @@ -0,0 +1,221 @@ + + + + + + Configuration Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Configuration Guide

    NpgsqlRest can be configured through multiple sources, each with different precedence levels. Common configuration sources are configuration files, usually different versions for different environments, environment variables, and command-line arguments.

    Configuration Sources

    NpgsqlRest reads configuration from the following sources (in order of precedence, lowest to highest):

    1. Configuration files (appsettings.json, then appsettings.Development.json)
    2. Command-line arguments

    Environment variables can be referenced in configuration values using {VARIABLE_NAME} syntax. This works in both configuration files and command-line arguments.

    Use command-line arguments to override any configuration value.

    Default Values

    If a configuration value is not explicitly set in any source, NpgsqlRest uses the default value. To see all defaults with inline descriptions, use the --config command:

    bash
    bash
    npgsqlrest --config > appsettings.json

    This generates a fully commented JSONC file with descriptions for every setting, ready to use as your configuration file. See Exploring Configuration below for more on --config.

    Configuration Files

    Default Configuration Files

    By default, NpgsqlRest loads configuration files from the current working directory in this order:

    1. appsettings.json
    2. appsettings.Development.json (overrides values from the first)

    Both files are optional — no error occurs if either is missing. You can specify additional or alternative configuration files using command-line arguments.

    bash
    bash
    # Use default appsettings.json and/or appsettings.Development.json from current directory
    +npgsqlrest
    +
    +# Specify a custom configuration file
    +npgsqlrest appsettings.production.json
    +
    +# Load multiple configuration files (later files override earlier ones)
    +npgsqlrest appsettings.json appsettings.production.json appsettings.local.json

    Optional Configuration Files

    Use the -o or --optional switch to mark configuration files as optional. Optional files won't cause an error if they don't exist:

    bash
    bash
    # appsettings.local.json is optional - no error if missing
    +npgsqlrest appsettings.json -o appsettings.local.json
    +
    +# Multiple optional files
    +npgsqlrest appsettings.json --optional development.json --optional local.json

    Configuration File Format

    Configuration files use standard JSON format with support for comments:

    json
    json
    {
    +  // Application identification
    +  "ApplicationName": "MyApi",
    +
    +  // Database connection
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Database=mydb;Username=user;Password=pass"
    +  },
    +
    +  // NpgsqlRest options
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/api",
    +    "RequiresAuthorization": false
    +  }
    +}

    Environment Variables

    By default, environment variable binding is disabled. Instead, environment variables can be referenced in configuration values using {VARIABLE_NAME} syntax:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host={DB_HOST};Database={DB_NAME};Username={DB_USER};Password={DB_PASS}"
    +  }
    +}

    This works in both configuration files and command-line arguments.

    For non-string configuration values, use the quoted form "{VARIABLE_NAME}" in the configuration file. The value will be automatically parsed to the appropriate type:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "CommandTimeout": "{COMMAND_TIMEOUT}",
    +    "RequiresAuthorization": "{REQUIRES_AUTH}"
    +  }
    +}

    Optional and Required Placeholders 3.17.0+

    Placeholders come in two forms, supported for every value type (bool, int, string, enum, arrays, dictionaries):

    • {NAME} — optional. Substituted with the variable's value when set; left untouched when not set — so typed bool/int reads fall back to their default instead of crashing, and legitimate non-env brace syntax (e.g. a Serilog OutputTemplate) is preserved.
    • {!NAME} — required. Substituted with the value, or throws a clear startup error naming the variable when it is not set.
    jsonc
    jsonc
    "Enabled": "{GITHUB_AUTH_ENABLED}"   // env unset → feature defaults to off (no crash)
    +"Enabled": "{!GITHUB_AUTH_ENABLED}"  // env unset → startup error naming the variable

    Placeholder parsing is controlled by Config:ParseEnvironmentVariables (enabled by default).

    Enabling Environment Variable Binding

    To enable automatic environment variable binding (where variables override configuration values directly), set AddEnvironmentVariables to true:

    json
    json
    {
    +  "Config": {
    +    "AddEnvironmentVariables": true
    +  }
    +}

    When enabled, environment variables can override any configuration value. The naming convention uses double underscores (__) to represent JSON hierarchy levels:

    bash
    bash
    # Override a top-level setting
    +export ApplicationName=MyApi
    +
    +# Override nested settings (use __ for hierarchy)
    +export ConnectionStrings__Default="Host=localhost;Database=mydb"
    +export NpgsqlRest__UrlPathPrefix="/api/v2"
    +export NpgsqlRest__RequiresAuthorization=true
    +
    +# Then run
    +npgsqlrest

    Environment Variable Naming Rules

    JSON PathEnvironment Variable
    ApplicationNameApplicationName
    ConnectionStrings.DefaultConnectionStrings__Default
    NpgsqlRest.UrlPathPrefixNpgsqlRest__UrlPathPrefix
    Auth.CookieAuth.EnabledAuth__CookieAuth__Enabled

    Command-Line Arguments

    Command-line arguments have the highest precedence and override all other configuration sources. Use the --key=value syntax:

    bash
    bash
    # Override settings via command line
    +npgsqlrest --ApplicationName=MyApi --NpgsqlRest:UrlPathPrefix=/api/v2
    +
    +# Override connection string
    +npgsqlrest --ConnectionStrings:Default="Host=localhost;Database=mydb"
    +
    +# Combine with configuration files
    +npgsqlrest appsettings.json --NpgsqlRest:RequiresAuthorization=false

    Command-Line Syntax Rules

    • Use --key=value format
    • Use colons (:) to separate hierarchy levels (alternative to __)
    • Keys are case-insensitive — overrides match the existing key regardless of casing (e.g., --applicationname=test correctly updates ApplicationName)
    • Boolean values: true, false, 1, 0
    bash
    bash
    # These are all equivalent
    +npgsqlrest --npgsqlrest:urlpathprefix=/api
    +npgsqlrest --NpgsqlRest:UrlPathPrefix=/api
    +npgsqlrest --NPGSQLREST:URLPATHPREFIX=/api

    Exploring Configuration

    The --config CLI command helps you discover and understand all available settings without consulting documentation. Standard configuration files and --key=value overrides can appear before the --config switch — this lets you inspect the effective configuration for a given setup.

    Generating a Default Configuration File

    Running --config with no arguments outputs the full default configuration as JSONC with inline comments describing every setting:

    bash
    bash
    npgsqlrest --config

    Redirect the output to create a ready-to-use configuration file:

    bash
    bash
    npgsqlrest --config > appsettings.json

    Include config files and overrides (case-insensitive) to see their effect on the output:

    bash
    bash
    npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config

    The output is syntax-highlighted in the terminal; when piped or redirected, plain JSONC is emitted.

    Searching for Settings

    Pass a filter argument to --config to search across setting names, comments, and values (case-insensitive):

    bash
    bash
    npgsqlrest --config cors
    +npgsqlrest --config=timeout
    +npgsqlrest --config minworker

    The output preserves the full section hierarchy so it can be copy-pasted directly into appsettings.json. When a key inside a section matches, its parent section is included. When a section name or its comment matches, the entire section is shown. Matched terms are highlighted in the terminal.

    This also works with config files and case-insensitive overrides:

    bash
    bash
    npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config timeout

    Configuration Validation

    The --config command validates all configuration keys before producing output. Unknown keys (e.g., --xxx=test) produce an error on stderr and exit with code 1, helping you catch typos early.

    Configuration Precedence Example

    Consider this scenario with multiple configuration sources:

    appsettings.json:

    json
    json
    {
    +  "ApplicationName": "DefaultApp",
    +  "NpgsqlRest": {
    +    "UrlPathPrefix": "/api",
    +    "RequiresAuthorization": true
    +  }
    +}

    Environment variables:

    bash
    bash
    export NpgsqlRest__UrlPathPrefix="/api/v2"

    Command line:

    bash
    bash
    npgsqlrest --NpgsqlRest:RequiresAuthorization=false

    Resulting configuration:

    SettingValueSource
    ApplicationName"DefaultApp"appsettings.json
    NpgsqlRest.UrlPathPrefix"/api/v2"Environment variable
    NpgsqlRest.RequiresAuthorizationfalseCommand line

    Quick Reference

    Common Command-Line Overrides

    bash
    bash
    # Database connection
    +npgsqlrest --ConnectionStrings:Default="Host=localhost;Database=mydb;Username=user;Password=pass"
    +
    +# Change listening URL
    +npgsqlrest --Urls="http://localhost:8080"
    +
    +# Disable authorization for development
    +npgsqlrest --NpgsqlRest:RequiresAuthorization=false
    +
    +# Set log level
    +npgsqlrest --Log:MinimalLevels:NpgsqlRest=Debug

    Exploring Configuration

    bash
    bash
    # Generate a fully commented default configuration file
    +npgsqlrest --config > appsettings.json
    +
    +# Search for settings related to a topic
    +npgsqlrest --config cors
    +npgsqlrest --config=timeout
    +
    +# Inspect effective configuration with overrides applied (case-insensitive)
    +npgsqlrest appsettings.json --npgsqlrest:commandtimeout=30 --config

    Configuration Structure Overview

    This section provides a complete overview of the NpgsqlRest configuration file structure.

    json
    json
    {
    +  // Application Identification
    +  "ApplicationName": null,
    +  "EnvironmentName": "Production",
    +  "Urls": "http://localhost:8080",
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}",
    +
    +  // Configuration Options
    +  "Config": { ... },
    +
    +  // Database Connections
    +  "ConnectionStrings": { ... },
    +  "ConnectionSettings": { ... },
    +
    +  // Server & SSL
    +  "Ssl": { ... },
    +  "Kestrel": { ... },
    +
    +  // Security
    +  "DataProtection": { ... },
    +  "Auth": { ... },
    +  "Antiforgery": { ... },
    +
    +  // Threading
    +  "ThreadPool": { ... },
    +
    +  // Logging
    +  "Log": { ... },
    +
    +  // Performance & Features
    +  "ResponseCompression": { ... },
    +  "StaticFiles": { ... },
    +  "Cors": { ... },
    +  "CommandRetryOptions": { ... },
    +  "CacheOptions": { ... },
    +  "RateLimiterOptions": { ... },
    +
    +  // Error Handling
    +  "ErrorHandlingOptions": { ... },
    +
    +  // Core API Options
    +  "NpgsqlRest": {
    +    // Connection & Query Settings
    +    "ConnectionName": null,
    +    "UseMultipleConnections": false,
    +    "CommandTimeout": null,
    +
    +    // Schema & Name Filtering
    +    "SchemaSimilarTo": null,
    +    "SchemaNotSimilarTo": null,
    +    "IncludeSchemas": [],
    +    "ExcludeSchemas": [],
    +    "NameSimilarTo": null,
    +    "NameNotSimilarTo": null,
    +    "IncludeNames": [],
    +    "ExcludeNames": [],
    +
    +    // URL & Naming Options
    +    "UrlPathPrefix": "/api",
    +    "KebabCaseUrls": true,
    +    "CamelCaseNames": true,
    +    "CommentsMode": "OnlyWithHttpTag",
    +
    +    // Request Handling
    +    "DefaultHttpMethod": null,
    +    "DefaultRequestParamType": null,
    +    "RequiresAuthorization": false,
    +
    +    // Request Headers
    +    "RequestHeadersMode": "Parameter",
    +    "RequestHeadersContextKey": "request.headers",
    +    "RequestHeadersParameterName": "_headers",
    +    "InstanceIdRequestHeaderName": null,
    +    "CustomRequestHeaders": [],
    +    "ExecutionIdHeaderName": "X-NpgsqlRest-ID",
    +
    +    // Server-Sent Events
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": { ... },
    +
    +    // Logging
    +    "LogConnectionNoticeEvents": false,
    +    "LogConnectionNoticeEventsMode": "FirstStackFrameAndMessage",
    +    "LogCommands": false,
    +    "LogCommandParameters": false,
    +
    +    // Nested Configuration Objects
    +    "RoutineOptions": { ... },
    +    "UploadOptions": { ... },
    +    "AuthenticationOptions": { ... },
    +    "HttpFileOptions": { ... },
    +    "OpenApiOptions": { ... },
    +    "ClientCodeGen": { ... }
    +  }
    +}

    Top-Level Settings

    These settings configure the application identity and server binding.

    SettingTypeDefaultDescription
    ApplicationNamestringnullApplication identifier. Defaults to the top-level directory name if not set.
    EnvironmentNamestring"Production"Environment designation (Development, Staging, Production).
    Urlsstring"http://localhost:8080"Server listening URLs. Separate multiple URLs with semicolons.
    StartupMessagestring"Started in {time}, listening on {urls}, version {version}"Message displayed on startup. Supports placeholders.

    Urls Configuration

    The Urls setting accepts multiple URLs separated by semicolons:

    json
    json
    {
    +  "Urls": "http://localhost:8080;https://localhost:8443"
    +}

    To listen on all interfaces:

    json
    json
    {
    +  "Urls": "http://0.0.0.0:8080;https://0.0.0.0:8443"
    +}

    Startup Message

    Customize the startup message with placeholders:

    json
    json
    {
    +  "StartupMessage": "Started in {time}, listening on {urls}, version {version}, env: {environment}"
    +}

    Available placeholders:

    • {time} - Startup time
    • {urls} - Listening URLs
    • {version} - Application version
    • {environment} - Environment name (from EnvironmentName)
    • {application} - Application name (from ApplicationName)

    Config Section Options

    The Config section controls how the configuration file itself is processed, including environment variable handling and configuration key validation.

    See the Config Section Reference for complete documentation on:

    • Environment variable overrides
    • Environment variable parsing with {ENV_VAR} syntax
    • Loading variables from .env files
    • Configuration key validation at startup

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/guide/faq.html b/guide/faq.html new file mode 100644 index 000000000..38824f49f --- /dev/null +++ b/guide/faq.html @@ -0,0 +1,75 @@ + + + + + + FAQ & Troubleshooting | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    FAQ & Troubleshooting

    General

    What is NpgsqlRest?

    NpgsqlRest is a self-contained executable that connects to PostgreSQL and automatically creates REST API endpoints from plain SQL script files, database functions, and procedures. No code generation step, no ORM — just SQL.

    What PostgreSQL versions are supported?

    PostgreSQL 13 and newer. Discovery uses standard pg_catalog views that are stable across PostgreSQL versions.

    What .NET version is required?

    None, for most users — the standalone executable and the Docker image are fully self-contained (AOT-compiled). The NuGet library targets .NET 10.

    Is it safe from SQL injection?

    Yes, by construction. Client-supplied values are always sent as PostgreSQL protocol parameters — never concatenated into SQL text. The SQL that runs is the SQL you wrote in the file, function, or procedure; a request can only choose parameter values, never SQL fragments. There is no query-building layer to inject into (unlike REST-to-SQL translators that construct queries from URLs).

    How does NpgsqlRest compare to PostgREST or Supabase?

    See the detailed comparison blog post for a full feature-by-feature breakdown. The short version: PostgREST turns your tables into an API you query from the client; NpgsqlRest turns your SQL (files, functions, procedures) into an API you design.

    Can I use it inside an existing ASP.NET Core application?

    Yes — the standalone executable is a wrapper around the NpgsqlRest NuGet middleware. In your own app: app.UseNpgsqlRest(new NpgsqlRestOptions(connectionString) { ... }). The plugins (SQL file source, TypeScript client, OpenAPI, MCP) are separate NuGet packages.


    Installation & Setup

    How do I install NpgsqlRest?

    See the Installation Guide — standalone executable (Linux/macOS/Windows), an npm package (npm i npgsqlrest), a Docker image, or NuGet.

    How do I run it in Docker?

    sh
    sh
    docker run --name npgsqlrest -p 8080:8080 \
    +  -v ./appsettings.json:/app/appsettings.json \
    +  vbilopav/npgsqlrest:latest

    Remember that localhost inside the container is the container — point the connection string at host.docker.internal (or the compose service name) to reach your database.

    How do I connect to my database?

    Set the connection string in appsettings.json:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"
    +  }
    +}

    See Connection Settings for all options.

    Can I use environment variables for configuration?

    Yes. With ParseEnvironmentVariables in the Config section (enabled by default), use {ENV_VAR_NAME} placeholders in configuration values — optional by default, or {!ENV_VAR_NAME} for required (startup error when unset, since 3.17.0). An .env file is supported via the EnvFile option. Any setting can also be overridden on the command line: npgsqlrest --npgsqlrest:urlpathprefix=/v1.


    Endpoints

    My function doesn't appear as an endpoint

    Check these causes, most common first:

    1. No HTTP annotation — since 3.17.0 the client defaults to CommentsMode: "OnlyAnnotated": a routine becomes an endpoint only if its comment contains an HTTP annotation (or a plugin annotation like @mcp). Add one — comment on function my_func() is 'HTTP GET'; — or set CommentsMode: "ParseAll" to expose everything discovered.
    2. Schema not included: by default only the public schema is scanned. Adjust SchemaSimilarTo in NpgsqlRest Options.
    3. Insufficient privileges: the connection's database user needs EXECUTE on the function and USAGE on the schema.
    4. A @disabled annotation on the routine.
    5. Check the logs: run with the NpgsqlRest log level at Debug to see what was discovered and skipped.

    My SQL file doesn't appear as an endpoint

    Same CommentsMode rule as functions — the file needs an HTTP annotation by default. Two additional file-specific causes:

    1. SkipPattern — files matching SqlFileSource.SkipPattern (default "*.test.sql") are excluded from endpoint discovery; they're test files for the test runner.
    2. A describe error with ErrorMode: "Skip" — the file failed type-checking against the database and was skipped with a logged error. (ErrorMode: "Exit", the default, would have stopped startup and shown it.)

    An endpoint exists but I get 404 — why?

    A 404 for an existing path is almost always parameter matching: a request must supply values for all parameters without defaults, with matching names — otherwise no endpoint matches and the response is 404 (not 400). Check:

    1. Parameter names are convertedp_user_id becomes pUserId with the default camelCase converter. The generated TypeScript client or HTTP file always shows the exact names.
    2. Missing required parameter — give it a default (@param name default null in SQL files, DEFAULT in function signatures) to make it optional.
    3. The path prefix — the full path includes UrlPathPrefix (default /api).
    4. The HTTP methodselect files/functions map to GET by default; mutations map to PUT/POST/DELETE.

    Why are parameter and column names camelCased? How do I turn that off?

    The default NameConverter converts snake_case PostgreSQL names to camelCase JSON/URL names. Set "CamelCaseNames": false in the NpgsqlRest section to keep names exactly as they are in the database.

    My query returns one row — why do I get an array?

    Endpoints return arrays by default. Annotate with @single to return the first row as a single JSON object, or combine with @nested for composite shapes. A single-column result set returns a flat value array (["a","b"]) — that's the UnnamedSingleColumnSet default in SQL File Source.

    How do I return plain text, HTML, or CSV instead of JSON?

    Use @raw — the column values are written to the response verbatim, with @separator and @new_line for delimiter control, plus a Content-Type response header:

    sql
    sql
    -- HTTP GET
    +-- @raw
    +-- @separator ,
    +-- @new_line \n
    +-- Content-Type: text/csv
    +select id, name, price from products;

    There is also a table format output mode for ready-made HTML tables and Excel exports.

    How do I customize the endpoint URL path?

    Use the @path annotation — -- @path /custom/path in a SQL file, or the same line in a function comment. Versioning works the same way: @path /v2/orders.

    How do I restrict access to an endpoint?

    Use @authorize, optionally with roles: -- @authorize admin, manager. Anything without @authorize is public unless you flip the global RequiresAuthorization option — then everything requires auth and @allow_anonymous opts out per endpoint.

    Can I expose tables and views directly, without writing any SQL?

    That's deliberately not the default model — NpgsqlRest wants you to design the API surface. The closest thing is a one-line SQL file per operation (select * from my_view; is a complete endpoint file). If you want fully automatic table CRUD, the NpgsqlRest.CrudSource NuGet plugin exists for library users, but plain SQL files are the recommended path.


    Parameters

    Named or positional parameters in SQL files — which should I use?

    Named (:name, since 3.19.0) for almost everything — the placeholder is the parameter name, so no @param naming annotations are needed, and the same name used repeatedly (even across statements) is one parameter:

    sql
    sql
    -- HTTP GET
    +select id, title from reports
    +where created_at between :from_date and :to_date;

    GET /api/get-reports?fromDate=...&toDate=... — done. Positional ($1, $2) remains fully supported; one style per file. See SQL File Endpoints — Parameters.

    How do I make a parameter optional?

    Give it a default. Functions: the native DEFAULT clause. SQL files: the @param annotation — -- @param status default 'active' or -- @param label default null. A parameter without a default is required, and a request missing it gets a 404 (no matching endpoint).

    How do I get the authenticated user's ID into a query?

    Enable claim-to-parameter mapping with @user_parameters and use a parameter whose name matches a claim mapping (default: _user_id → the user-id claim). With named parameters this needs nothing else:

    sql
    sql
    -- HTTP GET
    +-- @authorize
    +-- @user_parameters
    +select id, total, status
    +from orders
    +where user_id = :_user_id;

    The value comes from the authenticated principal — the client cannot send or override it. (With positional parameters, add -- @param $1 _user_id to give $1 the mapped name.)

    Error: "could not determine data type of parameter"

    PostgreSQL couldn't infer the parameter's type from context (classic case: select set_config('key', :value, true)). Give it a type hint: -- @param :value text (or -- @param $1 value text positionally), or add an inline cast in the SQL (:value::text).


    Authentication

    What authentication methods are supported?

    Cookie-based auth, JWT Bearer tokens, Microsoft Bearer tokens, HTTP Basic Auth, Passkeys/WebAuthn (FIDO2), and external OAuth providers (Google, GitHub, LinkedIn, Microsoft, Facebook). All can be enabled simultaneously. See Authentication config.

    How do I set up JWT authentication?

    json
    json
    {
    +  "Auth": {
    +    "JwtAuth": true,
    +    "JwtSecret": "your-secret-key-at-least-32-characters-long",
    +    "JwtExpire": "60 minutes"
    +  }
    +}

    See the Multiple Auth Schemes blog post for a complete walkthrough including login endpoints and RBAC.


    Testing

    How do I test my endpoints?

    With the built-in SQL test runner (since 3.19.0): write tests as plain .sql files and run npgsqlrest ./config.json --test. A test inserts fixtures, invokes a real endpoint in-process (full pipeline: routing, auth, parameter binding, serialization), asserts on the captured response with ordinary SQL, and rolls back — endpoints see the test's uncommitted data because they run on the test's own connection and transaction.

    sql
    sql
    begin;
    +insert into users (email) values ('x@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200, 'authenticated caller gets 200' from _response;
    +
    +rollback;

    Can tests run against a temporary database instead of my real one?

    Yes — that's the recommended CI setup. Setup steps create (and Teardown drops) a uniquely named database (app_test_{rnd5}), migrations run as a step, and TestRunner.ConnectionName points the whole run at it. Template databases give per-test clones for perfect isolation. See the scenario catalog in the Testing Guide.

    My test fixtures need half the database inserted first — is there a better way?

    Yes, and it's pure PostgreSQL: declare your foreign keys deferrable, then start the test with set constraints all deferred;. Deferred constraints are checked at COMMIT — and a test that ends in rollback never commits, so you can insert only the rows the test is about, in any order, referencing rows that don't exist. No fixture factories, no dependency-ordered setup. See the technique in the Testing Guide.

    Is there a watch mode?

    Two, with one flag. npgsqlrest ... --test --watch re-runs tests on changes — a changed test re-runs alone in milliseconds; a changed endpoint file or database routine rebuilds the endpoints in-process and re-runs everything, reporting exactly which endpoints appeared or dropped; Ctrl+C still tears the test database down. npgsqlrest ... --watch (without --test) watches the running server — it restarts on SQL file, configuration, and database routine changes, regenerating code (TypeScript client, HTTP files) on every cycle, so create or replace a function in psql and the endpoint is live seconds later. See Watch Mode configuration.


    Performance

    How fast is it?

    Independent-methodology benchmarks measure thousands of requests per second on a single host — see the 2025 benchmark post for numbers against PostgREST, PostGraphile, and Hasura, including the methodology. The executables are AOT-compiled native binaries; there is no JIT warmup and no reflection at runtime.

    How do I enable caching?

    Annotate with @cached (+ @cache_expires_in 5 minutes). The backend (Memory, Redis, or HybridCache) is configured in Cache Options; per-user and per-parameter cache keys are supported.

    How do I enable response compression?

    json
    json
    { "ResponseCompression": { "Enabled": true } }

    See Response Compression.

    How do I set up rate limiting?

    Define policies in Rate Limiter config and apply them per endpoint with @rate_limiter_policy.


    Debugging & Logging

    How do I see which endpoints are created and what options they have?

    Set the NpgsqlRest log level to Debug — every endpoint logs as it is created, including which annotations were applied:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Debug" } } }

    Or list them without starting the server: npgsqlrest --endpoints.

    How do I log the SQL each endpoint executes at runtime?

    Two settings: "LogCommands": true in the NpgsqlRest section opts in, and the NpgsqlRest channel must be at Verbose (commands log at trace level):

    json
    json
    {
    +  "NpgsqlRest": { "LogCommands": true },
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } }
    +}

    Add "LogCommandParameters": true to include parameter values (mind the sensitive-data implications in production — @security_sensitive obfuscates a specific endpoint). See the Logging Guide for the full picture.

    How do I see the metadata queries NpgsqlRest runs at startup?

    Set the NpgsqlRest log level to Verbose — includes everything from Debug plus the raw pg_catalog discovery queries and the SQL-file describe phase. Useful when a function or file isn't being discovered and you need to see the underlying query and its filters.

    How do I completely silence a logger?

    Since 3.19.0 any Log:MinimalLevels entry accepts "Off" (aliases "None", "Silent"):

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off" } } }

    Each named logger is independent — handy for muting the application channels while watching the test runner's NpgsqlRestTest channel.


    Troubleshooting

    Startup warning: "Unknown configuration key"

    Almost always a typo in appsettings.json — keys are validated at startup. Run npgsqlrest --config to print the complete annotated configuration, or use the published JSON schema for editor autocompletion.

    Error: "permission denied for schema"

    The database user lacks USAGE on the schema:

    sql
    sql
    grant usage on schema my_schema to my_user;
    +grant execute on all functions in schema my_schema to my_user;

    This is also a feature: run the server as a least-privilege role and endpoints can only do what that role can do.

    Timeout errors (504 Gateway Timeout)

    Adjust the command timeout per endpoint with @command_timeout 2 minutes, or globally in configuration.

    Encrypted data is unreadable after restart

    Data Protection keys default to in-memory on Linux — configure persistent storage:

    json
    json
    { "DataProtection": { "Storage": "FileSystem", "FileSystemPath": "/var/lib/npgsqlrest/keys" } }

    See Data Protection config.

    Leftover *_abcde test databases

    The test runner drops its {rnd}-named databases on every exit path it can intercept — including Ctrl+C, SIGTERM, and hard startup errors. Leftovers mean a run was killed with SIGKILL (nothing can intercept that) or ran with Keep: true. Drop them manually:

    sql
    sql
    select format('drop database %I with (force);', datname)
    +from pg_database where datname like 'app_test_%' \gexec

    Comments

    + + + + \ No newline at end of file diff --git a/guide/http-types.html b/guide/http-types.html new file mode 100644 index 000000000..881938572 --- /dev/null +++ b/guide/http-types.html @@ -0,0 +1,179 @@ + + + + + + HTTP Custom Types Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    HTTP Custom Types

    HTTP Custom Types let a PostgreSQL routine make outbound HTTP calls — to a third-party API, an internal microservice, or even another endpoint of your own app — without any extension or plpython. You define the request in a composite type's comment, use that type as a function parameter, and NpgsqlRest performs the call and hands your function the response.

    This guide covers:

    1. How HTTP Custom Types work
    2. Enabling the HTTP client
    3. Defining and using a type
    4. Reading the response
    5. Dynamic requests with placeholders
    6. Timeouts, retries, and caching
    7. Multiple calls in parallel
    8. Self-calls: composing your own endpoints
    9. Secrets and server-side values
    10. Configuration

    Reference page

    This is the conceptual walkthrough. For the exact directive grammar and every option see @http custom types and HTTP Client configuration.

    How it works

    A composite type whose comment starts with (or contains) an HTTP request line becomes an HTTP Custom Type. When a routine declares a parameter of that type, NpgsqlRest performs the request before the routine runs and fills the type's fields with the response:

    mermaid
    flowchart TD
    +    C["Client
    +    GET /api/average-book-price"] --> NR["NpgsqlRest"]
    +    NR -->|"1 — outbound call, before the function runs"| EXT["https://books.toscrape.com/"]
    +    EXT -->|"response fills the _response fields"| NR
    +    NR -->|"2 — run the function with _response populated"| FN["average_book_price(_response)"]
    +    FN -->|"3 — return result"| C

    So from your function's point of view, the HTTP response is just another input parameter that's already populated. There are no callbacks and no blocking I/O in your SQL — NpgsqlRest does the call on the app tier.

    Enabling the HTTP client

    HTTP Custom Types require the client to be switched on:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true
    +    }
    +  }
    +}

    Defining and using a type

    Two steps: define the composite type with a request in its comment, then declare it as a parameter.

    sql
    sql
    -- 1. The type's comment defines the outbound request.
    +create type books_api as (
    +    body text,
    +    status_code int,
    +    success boolean,
    +    error_message text
    +);
    +
    +comment on type books_api is 'GET https://books.toscrape.com/
    +Accept: text/html
    +@timeout 30s';
    +
    +-- 2. Use the type as a parameter; NpgsqlRest fills it before the function runs.
    +create function average_book_price(_response books_api default null)
    +returns numeric
    +language plpgsql
    +as $$
    +begin
    +    if not (_response).success then
    +        raise exception 'fetch failed: %',
    +            coalesce((_response).error_message, 'status ' || (_response).status_code);
    +    end if;
    +    -- parse (_response).body … (XPath/regex) and compute the average
    +    return round(avg(substring(p::text from '([0-9]+(?:\.[0-9]+)?)')::numeric), 2)
    +    from unnest(
    +        xpath('//p[@class="price_color"]/text()',
    +              xmlparse(document (_response).body))
    +    ) as p;
    +end;
    +$$;
    +
    +comment on function average_book_price(books_api) is '
    +HTTP GET /average-book-price
    +@allow_anonymous
    +@single';
    sql
    sql
    -- The type is created once (in a migration or schema file):
    +--   create type books_api as (body text, status_code int, success boolean, error_message text);
    +--   comment on type books_api is 'GET https://books.toscrape.com/
    +--   Accept: text/html
    +--   @timeout 30s';
    +
    +-- sql/average-book-price.sql
    +/*
    +HTTP GET /average-book-price
    +@allow_anonymous
    +@single
    +@param $1 _response books_api
    +*/
    +select round(avg(substring(p::text from '([0-9]+(?:\.[0-9]+)?)')::numeric), 2) as avg_price
    +from unnest(
    +    xpath('//p[@class="price_color"]/text()',
    +          xmlparse(document ($1).body))
    +) as p;

    The request spec lives entirely in the type comment:

    code
    GET https://books.toscrape.com/   ← method + URL (GET/POST/PUT/PATCH/DELETE)
    +Accept: text/html                 ← request headers, one per line
    +@timeout 30s                      ← optional directives (before the request line or after the headers)
    +
    +… request body …                 ← optional, after a blank line

    This is the web-scraping example (17_scrap_demo_2): fetch HTML server-side, then parse it with PostgreSQL's native XPath.

    Reading the response

    Your function reads the response through the type's fields with the (_param).field syntax. The standard fields (names are configurable):

    FieldTypeMeaning
    bodytext (or jsonb)Response body. Declare it jsonb to parse JSON automatically.
    status_codeintHTTP status code.
    successbooleantrue for any 2xx status.
    content_typetextThe Content-Type header value.
    headersjsonAll response headers as a JSON object.
    error_messagetextSet when the call itself failed (timeout, DNS, connection) — otherwise null.

    Errors are reported through success/error_message, not raised as exceptions — so always branch on (_response).success before using the body. You only need to declare the fields you actually use:

    sql
    sql
    create type weather_api as (body jsonb, success boolean, error_message text);

    Dynamic requests with placeholders

    Any {name} in the URL, a header, or the body is replaced at request time with the value of the parameter name (the shared parameter-substitution mechanism). Matching is case-insensitive; a NULL becomes an empty string.

    sql
    sql
    create type exchange_rate_api as (body jsonb, status_code int, success boolean, error_message text);
    +
    +comment on type exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';
    +
    +create function get_rates(_base_currency text, _response exchange_rate_api default null)
    +returns jsonb language sql as $$
    +  select case when (_response).success then (_response).body
    +              else jsonb_build_object('error', (_response).error_message) end;
    +$$;
    +
    +comment on function get_rates(text, exchange_rate_api) is '
    +HTTP GET /rates
    +@allow_anonymous';

    A call to GET /api/rates?baseCurrency=EUR fetches https://open.er-api.com/v6/latest/EUR. Placeholders can also resolve to an allowlisted environment variable (for API keys — see secrets) or a resolved-parameter expression.

    Timeouts, retries, and caching

    Three optional directives shape the call. They may appear before the request line or after the headers:

    sql
    sql
    comment on type my_api is '@timeout 10s
    +@retry_delay 1s, 2s, 5s on 429, 503
    +@cache 5m
    +GET https://api.example.com/data
    +Accept: application/json';
    DirectiveWhat it does
    @timeout 10sPer-request timeout. Interval format (30, 30s, 2min, 00:00:30).
    @retry_delay 1s, 2s, 5sRetry on failure. The list sets both the number of retries and the delay before each (here: 3 retries). Add on 429, 503 to retry only those status codes.
    @cache 5mCache the response for the given TTL. GET only, 2xx only. Concurrent requests for the same key coalesce into one outbound call (stampede protection).

    @cache is opt-in per type and can be turned off globally with HttpClientOptions.CacheEnabled: false. The cache key is the fully-resolved method + URL + headers + body, so two calls with different {placeholders} cache separately.

    Multiple calls in parallel

    Give a function several HTTP Custom Type parameters and NpgsqlRest fires them concurrently, then runs your function once all have completed. This turns the database function into an API aggregator:

    sql
    sql
    create type exchange_rate_api as (body jsonb, status_code int, success boolean, error_message text);
    +create type crypto_price_api  as (body jsonb, status_code int, success boolean, error_message text);
    +
    +comment on type exchange_rate_api is 'GET https://open.er-api.com/v6/latest/{_base_currency}
    +Accept: application/json
    +@timeout 10s';
    +
    +comment on type crypto_price_api is 'GET https://api.coingecko.com/api/v3/simple/price?ids={_crypto_ids_csv}&vs_currencies={_vs_currencies_csv}
    +Accept: application/json
    +@timeout 10s';
    +
    +create function get_financial_dashboard(
    +    _base_currency text,
    +    _crypto_ids_csv text,
    +    _vs_currencies_csv text,
    +    _exchange_rate_response exchange_rate_api,  -- both HTTP calls
    +    _crypto_response crypto_price_api           -- run in parallel
    +)
    +returns json language plpgsql as $$
    +begin
    +    return json_build_object(
    +        'rates',  case when (_exchange_rate_response).success then (_exchange_rate_response).body end,
    +        'crypto', case when (_crypto_response).success       then (_crypto_response).body end
    +    );
    +end;
    +$$;
    +
    +comment on function get_financial_dashboard(text, text, text, exchange_rate_api, crypto_price_api) is '
    +HTTP GET /financial-dashboard
    +@authorize';

    This is the external-API example (9_http_calls): two upstreams fetched in parallel and merged into one response.

    Self-calls: composing your own endpoints

    If the request URL is relative (e.g. GET /api/users), NpgsqlRest treats it as a self-call to another of your own endpoints — handled in-process, with no HTTP round trip. Combined with parallel execution, this lets one endpoint compose several others cheaply:

    sql
    sql
    create type api_users  as (body json);
    +create type api_orders as (body json);
    +comment on type api_users  is 'GET /api/users';
    +comment on type api_orders is 'GET /api/orders';
    +
    +create function dashboard(_users api_users, _orders api_orders)
    +returns json language sql as $$
    +  select json_build_object('users', ($1).body, 'orders', ($2).body);
    +$$;
    +
    +comment on function dashboard(api_users, api_orders) is '
    +HTTP GET /dashboard
    +@authorize';

    One request to /api/dashboard triggers two parallel internal calls and returns the combined result — microseconds per call instead of milliseconds, since the HTTP stack is bypassed.

    Secrets and server-side values

    Never make the client send an API key. Two server-side ways to supply one:

    Allowlisted environment variable — reference {API_KEY} in the type and allowlist it:

    jsonc
    jsonc
    "NpgsqlRest": { "AvailableEnvVars": [ "WEATHER_API_KEY" ] }
    sql
    sql
    comment on type weather_api is 'GET https://api.example.com/v1/current?city={_city}
    +Authorization: Bearer {WEATHER_API_KEY}';

    Resolved-parameter expression — compute the value with SQL (e.g. a per-user token from a table). The client can't override it:

    sql
    sql
    comment on type my_api is 'GET https://api.example.com/data
    +Authorization: Bearer {_token}';
    +
    +comment on function get_secure_data(_user_id int, _req my_api, _token text) is '
    +HTTP GET /secure-data
    +@authorize
    +_token = select api_token from user_tokens where user_id = {_user_id}';

    NpgsqlRest resolves _token server-side, substitutes it into the Authorization header, and makes the call — the token never reaches the browser.

    Configuration

    All under NpgsqlRest.HttpClientOptions:

    SettingDefaultDescription
    EnabledfalseMust be true for HTTP Custom Types to work.
    CacheEnabledtrueGlobal kill switch for the @cache directive. When false, every call is fresh.
    MaxCacheEntries10000Max distinct cached responses held in memory.
    CachePruneIntervalSeconds60How often expired cache entries are pruned.
    ResponseBodyField"body"Field name for the response body.
    ResponseStatusCodeField"status_code"Field name for the status code.
    ResponseSuccessField"success"Field name for the success flag.
    ResponseContentTypeField"content_type"Field name for the content type.
    ResponseHeadersField"headers"Field name for the headers JSON.
    ResponseErrorMessageField"error_message"Field name for the error message.

    The Response*Field settings let you rename the composite fields to whatever you prefer; the defaults are the names used throughout this guide.

    json
    json
    {
    +  "NpgsqlRest": {
    +    "HttpClientOptions": {
    +      "Enabled": true,
    +      "CacheEnabled": true,
    +      "MaxCacheEntries": 10000,
    +      "CachePruneIntervalSeconds": 60
    +    }
    +  }
    +}

    See it in the examples

    Comments

    + + + + \ No newline at end of file diff --git a/guide/index.html b/guide/index.html new file mode 100644 index 000000000..06f7e8bd0 --- /dev/null +++ b/guide/index.html @@ -0,0 +1,59 @@ + + + + + + NpgsqlRest Overview | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Overview

    NpgsqlRest is a production-ready, standalone web server that automatically transforms your PostgreSQL database into a REST API. It provides:

    • Automatic HTTP REST endpoints from SQL files, functions, and procedures
    • SQL files as endpoints — write plain .sql files containing PostgreSQL commands and get REST endpoints automatically
    • Code generation for JavaScript/TypeScript client libraries
    • Code generation for HTTP files for a simple way to quickly invoke and TEST your API.
    • Declarative configuration using SQL comments and annotations

    To get started, you need:

    • A PostgreSQL database for metadata and endpoint specifications
    • Configuration via JSON files, environment variables, or command line arguments

    Declarative Approach

    NpgsqlRest uses SQL comment annotations to configure API endpoints declaratively. This approach keeps your API configuration close to your SQL logic.

    NpgsqlRest creates REST endpoints from two types of sources:

    Plain SQL Files Flagship

    The primary way to create endpoints. Place .sql files containing PostgreSQL commands in a directory, and NpgsqlRest creates REST endpoints automatically. Parameter types and return columns are inferred via PostgreSQL's wire protocol — no functions, no procedures, no boilerplate:

    sql
    sql
    -- sql/get_users.sql
    +-- HTTP GET
    +-- @authorize admin
    +-- @cached
    +-- @param $1 department_id
    +select id, name, email from users where department_id = $1;

    This creates a GET /api/get-users?department_id=1 endpoint with authorization and caching.

    Multi-command SQL files execute multiple statements in a single database round-trip:

    sql
    sql
    -- sql/process_order.sql
    +-- HTTP POST
    +-- @param $1 order_id
    +-- @result validate
    +select count(*) from orders where id = $1;
    +update orders set status = 'processing' where id = $1;
    +-- @result confirm
    +select id, status from orders where id = $1;

    See the SQL File Source configuration for details and the complete SQL File Source tutorial for a hands-on guide.

    PostgreSQL Routines (Functions and Procedures)

    NpgsqlRest also generates endpoints from PostgreSQL functions and procedures using the built-in COMMENT system:

    sql
    sql
    create function get_user_data(id int)
    +returns table (name text, email text)
    +language sql
    +begin atomic;
    +select name, email from users where users.id = get_user_data.id;
    +end;
    +
    +comment on function get_user_data(id int) is '
    +HTTP GET /admin/get-user-data
    +@authorize admin
    +Cache-Control: public, max-age=31536000';

    This creates a GET endpoint at /admin/get-user-data that requires admin authorization and sets cache control headers.

    All endpoint sources generate HTTP test files for testing your API and JavaScript/TypeScript client libraries with type definitions ready for your frontend.

    Technology & Distribution

    NpgsqlRest is built on the latest .NET with the Kestrel web server, compiled using AOT (Ahead-of-Time) compilation for:

    • Zero dependencies - single executable file
    • Fast startup - native performance
    • Cross-platform - runs on Windows, macOS, and Linux

    Built on .NET/Kestrel, NpgsqlRest includes all modern web server capabilities out of the box, ensuring enterprise-grade performance and reliability.

    NpgsqlRest is free and open-source, allowing you to:

    • Customize builds for specific platforms
    • Modify functionality to meet your needs
    • Contribute to the project's development

    Comments

    + + + + \ No newline at end of file diff --git a/guide/installation.html b/guide/installation.html new file mode 100644 index 000000000..37b993483 --- /dev/null +++ b/guide/installation.html @@ -0,0 +1,108 @@ + + + + + + Installation Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    NpgsqlRest Installation Guide

    Download Executable

    Manual Installation

    You can always manually download the latest version executable from the official Release page.

    Release page downloads include builds for:

    The optional default configuration file is also included, but this is just for convenience; it works with the same default values without this configuration file.

    Additional builds (e.g., MacOS x64) may be added in the future.

    Command Line Download

    Windows (x64)

    powershell
    powershell
    # Download using PowerShell
    +Invoke-WebRequest -Uri "https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-win64.exe" -OutFile "npgsqlrest.exe"
    +
    +# Optionally add to PATH or move to desired location

    Linux (x64)

    bash
    bash
    # Download the executable
    +wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux64 -O npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    Linux (ARM64)

    bash
    bash
    # Download the ARM64 executable (for Raspberry Pi, AWS Graviton, etc.)
    +wget https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-linux-arm64 -O npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    macOS (ARM64)

    bash
    bash
    # Download the executable
    +curl -L https://github.com/NpgsqlRest/NpgsqlRest/releases/latest/download/npgsqlrest-osx-arm64 -o npgsqlrest
    +
    +# Make it executable
    +chmod +x npgsqlrest
    +
    +# Optionally move to the system path
    +sudo mv npgsqlrest /usr/local/bin/

    Command Line Basic Commands

    You can run some basic commands to test your installation. Assuming that the binary name is npgsqlrest, you can

    • Check versions. This includes the client version and all included components:
    bash
    bash
    # Show versions
    +npgsqlrest --version
    +npgsqlrest -v
    • See some help information:
    bash
    bash
    # Show help
    +npgsqlrest --help
    +npgsqlrest -h
    • Inspect configuration with syntax highlighting:
    bash
    bash
    # Show current configuration (syntax highlighted in terminal, plain JSON when piped)
    +npgsqlrest --config
    • Validate configuration and database connectivity:
    bash
    bash
    # Pre-flight check (exits with code 0 on success, 1 on failure)
    +npgsqlrest --validate
    • List all supported SQL comment annotations:
    bash
    bash
    # All supported annotations as a JSON array
    +npgsqlrest --annotations

    NPM Installation

    bash
    bash
    # Install globally
    +npm install -g npgsqlrest
    +
    +# Or install locally in the project
    +npm install npgsqlrest

    To check versions or see help information, use the NPX runner:

    bash
    bash
    # Show versions
    +npx npgsqlrest --version
    +npx npgsqlrest -v
    +
    +# Show help
    +npx npgsqlrest --help
    +npx npgsqlrest -h

    Note: The NPM package automatically downloads the appropriate executable for your operating system during installation.

    Docker Installation

    Standard Image (AOT)

    bash
    bash
    # Pull the latest image (optional, docker run will do this if the image is not pulled)
    +docker pull vbilopav/npgsqlrest:latest
    +
    +# Check versions for all components
    +docker run --name npgsqlrest -it vbilopav/npgsqlrest:latest --version
    +
    +# See help
    +docker run --name npgsqlrest -it vbilopav/npgsqlrest:latest --help
    +
    +# Run with configuration file and with default port exposed
    +docker run --name npgsqlrest -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest

    JIT Image

    A Docker image variant using .NET runtime with JIT (Just-In-Time) compilation instead of AOT:

    bash
    bash
    # Pull the JIT image variant
    +docker pull vbilopav/npgsqlrest:latest-jit
    +
    +# Run with JIT runtime
    +docker run --name npgsqlrest-jit -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-jit

    The JIT version offers significantly better performance in high-concurrency scenarios (50-100% faster than AOT), but has slower cold-start times and a larger image size (~200-250 MB vs ~30 MB for AOT). For sustained high-throughput workloads, JIT is recommended.

    Available JIT image tags:

    • vbilopav/npgsqlrest:latest-jit - Latest version with JIT
    • vbilopav/npgsqlrest:3.6.3-jit - Specific version with JIT

    ARM64 Image

    A Docker image variant for ARM64 architecture (Raspberry Pi, AWS Graviton, Apple Silicon Linux VMs, etc.):

    bash
    bash
    # Pull the ARM64 image variant
    +docker pull vbilopav/npgsqlrest:latest-arm
    +
    +# Run with ARM64 runtime
    +docker run --name npgsqlrest-arm -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-arm

    The ARM64 build is compiled natively on GitHub's ARM64 runners for optimal performance on ARM-based systems.

    Available ARM64 image tags:

    • vbilopav/npgsqlrest:latest-arm - Latest version for ARM64
    • vbilopav/npgsqlrest:3.6.3-arm - Specific version for ARM64

    Bun Runtime Image

    A Docker image variant with pre-installed Bun JavaScript runtime is available:

    bash
    bash
    # Pull the Bun image variant
    +docker pull vbilopav/npgsqlrest:latest-bun
    +
    +# Run with Bun runtime available
    +docker run --name npgsqlrest-bun -it -p 8080:8080 -v ./appsettings.json:/app/appsettings.json vbilopav/npgsqlrest:latest-bun

    This image includes the Bun JavaScript runtime alongside NpgsqlRest, enabling proxy endpoints to execute Bun scripts within the same container. Useful for scenarios where you need lightweight proxy handlers without external service calls.

    Available Bun image tags:

    • vbilopav/npgsqlrest:latest-bun - Latest version with Bun
    • vbilopav/npgsqlrest:3.6.3-bun - Specific version with Bun

    Building From Source

    Before building NpgsqlRest from source, ensure you have the following installed:

    Clone the Repository

    bash
    bash
    git clone https://github.com/vb-consulting/NpgsqlRest.git
    +cd NpgsqlRest
    • Standard Build
    bash
    bash
    dotnet build
    • AOT (Ahead-of-Time) Compilation

    NpgsqlRest supports AOT compilation for native executables:

    bash
    bash
    # Windows (x64)
    +dotnet publish -r win-x64 -c Release --output ./dist
    +
    +# Linux (x64) - must be run on Linux
    +dotnet publish -r linux-x64 -c Release --output ./dist
    +
    +# macOS (ARM64)
    +dotnet publish -r osx-arm64 -c Release --output ./dist

    For more information on build targets for specific OS, see the .NET RID Catalog

    The AOT-compiled executable will be approximately 30MB and is self-contained with no runtime dependencies. The built executable will have the same functionality as the pre-compiled releases available on the GitHub releases page.

    Next Steps

    Comments

    + + + + \ No newline at end of file diff --git a/guide/logging.html b/guide/logging.html new file mode 100644 index 000000000..f93be7648 --- /dev/null +++ b/guide/logging.html @@ -0,0 +1,119 @@ + + + + + + Logging Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Logging

    NpgsqlRest logging is built on Serilog and has three independent axes:

    • Channels (Serilog source contexts) — who is logging: the endpoint engine, the client host, the test runner, the .NET framework. Each channel's level is set independently under Log:MinimalLevels.
    • Sinkswhere logs go: console, rolling files, a PostgreSQL command, an OpenTelemetry collector. Each sink has its own minimum level on top of the channel levels.
    • LevelsVerbose < Debug < Information < Warning < Error < Fatal, plus "Off" (since 3.19.0) to silence a channel entirely.

    This guide is the task-oriented walkthrough; the option-by-option reference is at Logging configuration.

    The channel map

    Knowing which channel carries what — and at which level — answers most "how do I see X?" questions:

    ChannelWho logs on itDebug showsVerbose adds
    NpgsqlRestThe endpoint engine (library + plugins)Every endpoint as it is created, with each annotation applied (authorize, cached, path, …)pg_catalog discovery queries, the SQL-file describe phase, and — with LogCommandsevery SQL command endpoints execute
    NpgsqlRestClient (displayed as your ApplicationName when set)The client hostConfiguration processing, auth setup, startup detail
    NpgsqlRestTestThe SQL test runner (--test)Test discovery and parsingEvery test statement and in-process HTTP invocation
    Microsoft, SystemASP.NET Core / .NETFramework internals (defaults to Warning — keep it there)

    PostgreSQL itself participates too: anything your SQL raises (raise info 'x', raise warning 'y') flows into the logs — see PostgreSQL messages in your logs.

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    }
    +  }
    +}

    Channel names and ApplicationName

    When ApplicationName is set, the client host channel takes that name in the log output — lines show [MyApi] instead of [NpgsqlRestClient] in the {SourceContext} template placeholder. The configuration key stays stable: "MinimalLevels": { "NpgsqlRestClient": ... } keeps working regardless, because the client maps it to the actual channel name for you (using the application name itself as the key also works). The core engine channel is always NpgsqlRestApplicationName never affects it. The test runner channel name is configurable via TestRunner.LoggerName.

    Any setting works from the command line as well: npgsqlrest --log:minimallevels:npgsqlrest=debug.

    Recipes

    See which endpoints exist and why

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Debug" } } }
    code
    [DBG] Function public.get_users mapped to GET /api/get-users has set AUTHORIZE by the comment annotation with roles: admin
    +[DBG] Created endpoint GET /api/get-users

    This is the first thing to reach for when an annotation seems ignored or an endpoint is missing. (To just list endpoints without starting the server: npgsqlrest --endpoints.)

    See every SQL command endpoints execute

    Two switches — LogCommands opts in, and the channel must be at Verbose (commands log at trace level):

    json
    json
    {
    +  "NpgsqlRest": { "LogCommands": true },
    +  "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } }
    +}

    Add "LogCommandParameters": true to include parameter values — invaluable in development, but treat it as sensitive in production (passwords and personal data end up in logs; the @security_sensitive annotation obfuscates a specific endpoint's parameters).

    Debug discovery: "why isn't my function/file picked up?"

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Verbose" } } }

    Verbose shows the raw pg_catalog discovery queries with their schema/name filters, and the SQL-file describe phase — you can see exactly what was scanned and what was skipped, and why.

    Watch the test runner, mute everything else

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Off",
    +      "NpgsqlRestClient": "Off",
    +      "NpgsqlRestTest": "Verbose"
    +    }
    +  }
    +}

    Verbose on NpgsqlRestTest prints every test statement and every in-process endpoint invocation. See the Testing Guide for the runner itself — note that the console report (PASS/FAIL lines) is always printed regardless of log levels; TestRunner.DetailedReport shapes the report, log levels shape the diagnostics.

    Silence a channel completely

    Since 3.19.0, "Off" (aliases "None", "Silent") fully mutes a channel — previously the quietest option was Fatal, which still let fatal events through:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off" } } }

    PostgreSQL messages in your logs

    Messages raised by your SQL — raise debug/log/info/notice/warning in functions, procedures, DO blocks, or triggers — are captured from the connection and logged on the endpoint's channel, at the level matching the PostgreSQL severity. This is on by default (LogConnectionNoticeEvents: true in the NpgsqlRest section), which turns raise into a zero-infrastructure logging facility for your database code:

    sql
    sql
    create function transfer(from_id int, to_id int, amount numeric) returns void as $$
    +begin
    +    ...
    +    raise info 'transfer of % from % to % completed', amount, from_id, to_id;
    +end $$ language plpgsql;

    Every call now leaves an INF line in the server logs — no logging table, no extension.

    LogConnectionNoticeEventsMode controls the shape: MessageOnly, FirstStackFrameAndMessage (default — includes where in your PL/pgSQL the raise happened), or FullStackAndMessage.

    Two related notes:

    • On SSE endpoints, raise messages at the configured notice level become events streamed to the client rather than plain log lines.
    • In the test runner, captured notices are shown under failing tests (and under passing ones with DetailedReport).

    Logging into PostgreSQL

    The database can be a log destination as well as a source — every log event can invoke a PostgreSQL command. You own the command and therefore the schema:

    sql
    sql
    create table logs (
    +    at timestamptz not null,
    +    level text not null,
    +    message text not null,
    +    exception text,
    +    source text
    +);
    +
    +create procedure log(_level text, _message text, _at timestamptz, _exception text, _source text)
    +language sql as $$
    +    insert into logs values (_at, _level, _message, _exception, _source);
    +$$;
    json
    json
    {
    +  "Log": {
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning"
    +  }
    +}

    The five positional parameters are: level, message, UTC timestamp, exception text (or null), and the source context (channel name) — see the reference. Since it's your procedure, you can route, enrich, prune, or pg_notify from it.

    Keep the level high

    PostgresMinimumLevel: "Warning" is a sensible floor — logging every Verbose event back into the database from a busy API is a self-inflicted write load.

    Files, OpenTelemetry, and production

    Console output is on by default and is the right answer for containers (12-factor: let the platform collect stdout). Beyond that:

    Rolling files — size-based rolling with retention:

    json
    json
    {
    +  "Log": {
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileSizeLimitBytes": 50000000,
    +    "RetainedFileCountLimit": 14
    +  }
    +}

    OpenTelemetry (OTLP) — ship to a collector (Grafana/Loki, Datadog, etc.), with resource attributes carrying the application name and environment:

    json
    json
    {
    +  "Log": {
    +    "ToOpenTelemetry": true,
    +    "OTLPEndpoint": "http://otel-collector:4317",
    +    "OTLPProtocol": "Grpc",
    +    "OTLPResourceAttributes": {
    +      "service.name": "{application}",
    +      "service.environment": "{environment}"
    +    }
    +  }
    +}

    Levels compose: MinimalLevels filters at the source (per channel); each sink then applies its own minimum (ConsoleMinimumLevel, FileMinimumLevel, PostgresMinimumLevel, OTLPMinimumLevel). A common production shape: channels at Information, console at Information, file at Information, PostgreSQL at Warning.

    A production baseline:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Information",
    +      "NpgsqlRestClient": "Information",
    +      "NpgsqlRestTest": "Information",
    +      "System": "Warning",
    +      "Microsoft": "Warning"
    +    },
    +    "ToConsole": true,
    +    "ConsoleMinimumLevel": "Information",
    +    "ToFile": true,
    +    "FilePath": "/var/log/npgsqlrest/app.log",
    +    "FileMinimumLevel": "Information",
    +    "ToPostgres": true,
    +    "PostgresCommand": "call log($1,$2,$3,$4,$5)",
    +    "PostgresMinimumLevel": "Warning"
    +  }
    +}

    And the development counterpart:

    json
    json
    {
    +  "Log": {
    +    "MinimalLevels": { "NpgsqlRest": "Debug" }
    +  }
    +}

    Comments

    + + + + \ No newline at end of file diff --git a/guide/proxy.html b/guide/proxy.html new file mode 100644 index 000000000..64f2f163c --- /dev/null +++ b/guide/proxy.html @@ -0,0 +1,173 @@ + + + + + + Proxy Endpoints Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Proxy Endpoints

    A proxy endpoint forwards a request to an upstream service and returns its response. With NpgsqlRest you turn any routine into a reverse proxy by adding @proxy — and you choose whether to stream the upstream response straight through or run it through SQL first to cache, enrich, or reshape it. This makes PostgreSQL a lightweight API gateway that can add auth, caching, and database context to any backend.

    This guide covers:

    1. How proxy endpoints work
    2. Enabling the proxy
    3. Passthrough mode
    4. Transform mode
    5. Where the request goes: target URL
    6. Forwarding headers, claims, and IP
    7. Forward proxy: send a function result upstream
    8. Configuration
    9. A complete example: cached AI gateway

    Reference pages

    This is the conceptual walkthrough. For exact options see @proxy, @proxy_out, and Proxy configuration.

    How it works

    @proxy forwards the incoming request to an upstream host. What happens to the response depends on one thing: whether your routine declares the special _proxy_* response parameters.

    mermaid
    flowchart TD
    +    REQ["Client request"] --> NR["NpgsqlRest @proxy endpoint"]
    +    NR -->|"forward (incoming path + query)"| UP["Upstream host"]
    +    UP --> MODE{"routine declares
    +    _proxy_* params?"}
    +    MODE -->|"no — passthrough"| OUT1["stream the upstream response
    +    straight back; function body NOT run;
    +    no DB connection opened"]
    +    MODE -->|"yes — transform"| OUT2["bind response into _proxy_* params,
    +    run the function, return its result"]
    +    OUT1 --> CL["Client"]
    +    OUT2 --> CL
    • Passthrough — no _proxy_* parameters. NpgsqlRest streams the upstream response directly back to the client. The function body is never executed and no database connection is opened. This is a pure reverse proxy.
    • Transform — the routine declares _proxy_* parameters. NpgsqlRest performs the upstream call, binds the response into those parameters, runs your function, and returns the function's result.

    Enabling the proxy

    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "http://localhost:3001"
    +    }
    +  }
    +}

    Host is the default upstream; an annotation can override it per endpoint (see target URL).

    Passthrough mode

    The simplest proxy: forward and stream back. No _proxy_* parameters, so the body never runs.

    sql
    sql
    create function service_status()
    +returns void
    +language plpgsql
    +as $$ begin end; $$;   -- body is never executed in passthrough mode
    +
    +comment on function service_status() is '
    +HTTP GET /status
    +@proxy http://internal-service:8080';
    sql
    sql
    -- sql/status.sql
    +/*
    +HTTP GET /status
    +@proxy http://internal-service:8080
    +*/
    +select;   -- never executed; the endpoint just forwards

    GET /status is forwarded to http://internal-service:8080/status and the upstream response is streamed back unchanged. Because no DB connection is opened, passthrough proxying is cheap — useful for putting NpgsqlRest's auth, CORS, rate limiting, or TLS in front of a plain internal service.

    Passthrough does not run your SQL

    If you need the function body to execute (to log, cache, or reshape), you're in transform mode — you must declare at least one _proxy_* parameter. A passthrough endpoint's body is dead code.

    Transform mode

    Declare the response parameters and NpgsqlRest hands you the upstream response to do with as you like. All parameters are optional — declare only the ones you need:

    ParameterTypeMeaning
    _proxy_status_codeint (or text)Upstream HTTP status code.
    _proxy_bodytextResponse body (null if empty).
    _proxy_headersjsonResponse headers as JSON.
    _proxy_content_typetextContent-Type of the response.
    _proxy_successbooleantrue for a 2xx status.
    _proxy_error_messagetextSet if the call failed (timeout, connection error); else null.
    sql
    sql
    create function fetch_and_wrap(
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +begin
    +    if not _proxy_success then
    +        return json_build_object('error', coalesce(_proxy_error_message, 'upstream failed'),
    +                                 'status', _proxy_status_code);
    +    end if;
    +    return json_build_object('status', _proxy_status_code, 'data', _proxy_body::json);
    +end;
    +$$;
    +
    +comment on function fetch_and_wrap(int, text, boolean, text) is '
    +HTTP GET /wrapped
    +@authorize
    +@proxy https://api.example.com/data';

    Here NpgsqlRest calls the upstream, fills the four parameters, runs fetch_and_wrap, and returns its JSON — letting you handle errors, cache the body in a table, or merge it with database data before responding. (The _proxy_* names are configurable.)

    Where the request goes: target URL

    The upstream target is built as:

    code
    target = host + incoming request path + incoming query string

    The host comes from the annotation if present, otherwise from ProxyOptions.Host. A relative annotation path makes it a self-call:

    AnnotationProxyOptions.HostResolved targetSelf-call?
    @proxyhttps://api.example.comhttps://api.example.com + path + queryno
    @proxy POSThttps://api.example.comsame host, upstream method forced to POSTno
    @proxy https://other.comhttps://api.example.comhttps://other.com + path + queryno
    @proxy /api/data(any)/api/data — internal call, no networkyes

    The optional [METHOD] lets the upstream verb differ from the incoming one (e.g. accept a GET from clients but call the upstream with POST). A relative target (@proxy /api/other) is dispatched in-process to another of your endpoints with no HTTP round trip.

    Forwarding headers, claims, and IP

    By default request and response headers are forwarded (minus a small exclude list). On top of that, NpgsqlRest can forward the authenticated identity to the upstream automatically — so the backend can trust who the caller is without re-doing auth:

    • @user_parameters — user claims, the client IP, HTTP-Custom-Type fields, and resolved-parameter values are forwarded in the endpoint's native shape: query-string parameters for QueryString endpoints, or merged into the JSON body for BodyJson endpoints.
    • @user_context — the claims and client IP are forwarded as HTTP headers (one per ContextKeyClaimsMapping entry, plus a claims-JSON header and an IP header).
    sql
    sql
    create function secure_gateway(
    +    _user_id text default null,     -- forwarded upstream as ?userId=…
    +    _user_name text default null    -- forwarded upstream as ?userName=…
    +)
    +returns void
    +language plpgsql
    +as $$ begin end; $$;
    +
    +comment on function secure_gateway(text, text) is '
    +HTTP GET /gateway
    +@authorize
    +@user_parameters
    +@proxy https://internal-api/secure';

    Long values are guarded

    Automatic values appended to the upstream query string are capped by MaxForwardedQueryParamLength (default 2048). A longer value is skipped with a warning rather than producing an unusable request line. Use a BodyJson endpoint if you must forward large values.

    Forward proxy: send a function result upstream

    @proxy_out (alias @forward_proxy) reverses the order: your function runs first, and its result is sent as the request body to the upstream, whose response is returned to the client. Use it to build a payload in SQL and hand it to a rendering/processing service:

    sql
    sql
    create function generate_report(_report_id int)
    +returns json
    +language sql
    +as $$
    +  select json_build_object(
    +    'title', 'Monthly Report',
    +    'rows', (select json_agg(row_to_json(s)) from sales s where s.month = _report_id));
    +$$;
    +
    +comment on function generate_report(int) is '
    +HTTP GET /report
    +@proxy_out POST https://render-service.internal/render';
    sql
    sql
    -- sql/report.sql
    +/*
    +HTTP GET /report
    +@proxy_out POST https://render-service.internal/render
    +@param $1 report_id
    +*/
    +select json_build_object(
    +  'title', 'Monthly Report',
    +  'rows', (select json_agg(row_to_json(s)) from sales s where s.month = $1));

    GET /report?reportId=3 runs generate_report, POSTs its JSON to the render service, and returns the rendered response. If the function fails, the error goes straight to the client and the upstream is never called; if the upstream fails, its status/body are forwarded (502 for connection errors, 504 for timeouts).

    Configuration

    All under NpgsqlRest.ProxyOptions:

    SettingDefaultDescription
    EnabledfalseMust be true for proxy annotations to work.
    HostnullDefault upstream host. Used when an annotation has no URL; ignored when it specifies one.
    DefaultTimeout"00:00:30"Per-request timeout (HH:MM:SS or interval format).
    ForwardHeaderstrueForward request headers upstream.
    ExcludeHeaders["Host", "Content-Length", "Transfer-Encoding"]Request headers not forwarded.
    ForwardResponseHeaderstrueForward upstream response headers to the client.
    ExcludeResponseHeaders["Transfer-Encoding", "Content-Length"]Response headers not forwarded.
    ForwardUploadContentfalseForward raw multipart/form-data upstream instead of processing it locally.
    MaxForwardedQueryParamLength2048Max length of a single auto-forwarded query value (0 disables the guard).
    Response*Parameter_proxy_status_code, _proxy_body, _proxy_headers, _proxy_content_type, _proxy_success, _proxy_error_messageNames of the transform-mode response parameters.
    json
    json
    {
    +  "NpgsqlRest": {
    +    "ProxyOptions": {
    +      "Enabled": true,
    +      "Host": "http://localhost:3001",
    +      "DefaultTimeout": "00:00:30",
    +      "ForwardHeaders": true,
    +      "ForwardResponseHeaders": true
    +    }
    +  }
    +}

    A complete example: cached AI gateway

    This transform-mode endpoint proxies a request to an AI service, but caches each result in a table so repeated inputs never hit the upstream twice — a database-backed cache in front of a slow/expensive API.

    sql
    sql
    create function ai_sentiment(
    +    _text text,
    +    _proxy_status_code int default null,
    +    _proxy_body text default null,
    +    _proxy_success boolean default null,
    +    _proxy_error_message text default null
    +)
    +returns json
    +language plpgsql
    +as $$
    +declare
    +    _hash text := md5(_text || '::sentiment');
    +    _cached record;
    +    _result json;
    +begin
    +    -- 1. serve from the database cache when present
    +    select sentiment, sentiment_score into _cached
    +    from analysis_cache where text_hash = _hash;
    +
    +    if _cached.sentiment is not null then
    +        return json_build_object('sentiment', _cached.sentiment,
    +                                 'score', _cached.sentiment_score, 'cached', true);
    +    end if;
    +
    +    -- 2. handle upstream failure
    +    if not _proxy_success then
    +        return json_build_object('error', coalesce(_proxy_error_message, 'AI service unavailable'),
    +                                 'status_code', _proxy_status_code);
    +    end if;
    +
    +    -- 3. cache the fresh result and return it
    +    _result := _proxy_body::json;
    +    insert into analysis_cache (text_hash, sentiment, sentiment_score)
    +    values (_hash, _result->>'sentiment', (_result->>'score')::numeric)
    +    on conflict (text_hash) do nothing;
    +
    +    return json_build_object('sentiment', _result->>'sentiment',
    +                             'score', (_result->>'score')::numeric, 'cached', false);
    +end;
    +$$;
    +
    +comment on function ai_sentiment(text, int, text, boolean, text) is '
    +HTTP POST /ai/sentiment
    +@authorize
    +@proxy POST';   -- no host → forwarded to ProxyOptions.Host

    POST /ai/sentiment is forwarded to the configured AI service; the function then caches and shapes the result. Note that with caching, the upstream is still called every time (NpgsqlRest can't know the result is cached before the proxy runs) — to skip the call entirely on a cache hit, fetch with an HTTP Custom Type instead of @proxy, or split the lookup into a separate endpoint.

    See it in the examples

    Comments

    + + + + \ No newline at end of file diff --git a/guide/quick-start.html b/guide/quick-start.html new file mode 100644 index 000000000..43941cee3 --- /dev/null +++ b/guide/quick-start.html @@ -0,0 +1,110 @@ + + + + + + Quick Start Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Quick Start

    This guide walks you through running NpgsqlRest for the first time and creating your first API endpoint. By the end, you'll have a working REST API connected to your PostgreSQL database.

    Prerequisites

    Before starting, ensure you have:

    • NpgsqlRest installed (Installation Guide)
    • PostgreSQL database running (version 13 or later)
    • A database with connection credentials

    Step 1: Create Your First Endpoint

    NpgsqlRest can create endpoints from plain SQL files or from PostgreSQL functions. SQL files are the recommended approach — they're simpler to set up and don't require any database DDL.

    SQL file endpoints need to be enabled in your appsettings.json (or create one — see Step 4):

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    Then create a sql/ directory next to the NpgsqlRest executable and add a .sql file:

    sql
    sql
    -- sql/hello.sql
    +-- HTTP GET
    +-- @allow_anonymous
    +select 'Hello, World!' as message;

    NpgsqlRest will create a GET /api/hello endpoint from this file automatically.

    Option B: PostgreSQL Function

    Alternatively, create a function directly in your PostgreSQL database:

    sql
    sql
    create function my_first_function()
    +returns setof text
    +language sql
    +begin atomic;
    +values ('Hello, World!'), ('This is my first function.'), ('Enjoy coding in SQL!');
    +end;
    +
    +comment on function my_first_function() is 'HTTP GET';

    Note: by default, function endpoints are created only if the function comment contains the HTTP keyword. This behavior can be changed in the configuration.

    Step 2: Run NpgsqlRest

    • Add connection string via command line argument --connectionstrings:default="<npgsql connection string>" and start NpgsqlRest. This is Npgsql Connection String Format, but a simple example looks like this: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres. So, the command line to start NpgsqlRest would look like this:
    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"                                                                                
    +[12:32:26.087 INF] Started in 00:00:00.0575787, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Note: NpgsqlRest supports multiple connection strings and if not configured otherwise, it uses the first available connection string.

    Congratulations! NpgsqlRest is now running and connected to your database and our first endpoint is be created automatically. Let's test it.

    bash
    bash
     curl -i http://localhost:8080/api/my-first-function
    +HTTP/1.1 401 Unauthorized
    +Content-Length: 0
    +Date: Thu, 04 Dec 2025 11:42:27 GMT
    +Server: Kestrel

    By default, NpgsqlRest requires authorization. We will fix that in the next step.

    Step 3: Anonymous Endpoint And Verbose Logging

    To disable authorization for development purposes we can add anonymous comment annotation to our function:

    sql
    sql
    comment on function my_first_function() is '
    +HTTP GET
    +@anonymous';

    Alternatively, we can disable authorization requiremnt in command line by adding the following argument --npgsqlrest:requiresauthorization=false:

    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres" --npgsqlrest:requiresauthorization=false
    +[12:47:53.288 INF] Started in 00:00:00.0517179, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Also, since we are in development mode, let's enable debug logging with --log:minimallevels:npgsqlrest=debug to see what is happening under the hood and to make sure our endpoint is created:

    code
    ❯ ./npgsqlrest --connectionstrings:default="Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres" --log:minimallevels:npgsqlrest=debug
    +[12:49:29.928 DBG] ----> Starting with configuration(s): JsonConfigurationProvider for 'appsettings.json' (Missing), JsonConfigurationProvider for 'appsettings.Development.json' (Missing), CommandLineConfigurationProvider [NpgsqlRest]
    +[12:49:29.937 DBG] ----> Logging enabled: Console (minimum level: Verbose) [NpgsqlRest]
    +[12:49:29.937 DBG] Using default as main connection string: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=******;Application Name=example;Enlist=False;No Reset On Close=True [NpgsqlRest]
    +[12:49:29.937 DBG] Using connection retry options with strategy: RetrySequenceSeconds=1,3,6,12, ErrorCodes=08000,08003,08006,08001,08004,55P03,55006,53300,57P03,40001 [NpgsqlRest]
    +[12:49:29.939 DBG] Using EndpointSource PostgreSQL Source [NpgsqlRest]
    +[12:49:29.939 DBG] Routine caching is disabled. [NpgsqlRest]
    +[12:49:29.961 DBG] Using DataSource with schema 'public' for metadata queries. [NpgsqlRest]
    +[12:49:29.998 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set HTTP by the comment annotation to GET /api/my-first-function [NpgsqlRest]
    +[12:49:29.998 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set ALLOW ANONYMOUS by the comment annotation. [NpgsqlRest]
    +[12:49:29.999 DBG] Created endpoint GET /api/my-first-function [NpgsqlRest]
    +[12:49:30.002 INF] Started in 00:00:00.0760485, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Finally, let's test our endpoint again:

    bash
    bash
     curl -i http://localhost:8080/api/my-first-function
    +HTTP/1.1 200 OK
    +Content-Type: application/json
    +Date: Thu, 04 Dec 2025 11:47:55 GMT
    +Server: Kestrel
    +Transfer-Encoding: chunked
    +
    +["Hello, World!","This is my first function.","Enjoy coding in SQL!"]

    Function worked as expected, it returns JSON array of strings, and we have our first NpgsqlRest endpoint!

    Step 4: Create Configuration File

    In order to avoid passing command line arguments every time we start NpgsqlRest, let's create a default configuration file.

    Create an appsettings.json file in your working directory:

    json
    json
    {
    +  // Default connection string to the PostgreSQL database
    +  "ConnectionStrings": {
    +    "Default": "Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=postgres"
    +  },
    +
    +  // Logging configuration, use "Debug" level for NpgsqlRest namespace
    +  "Log": {
    +    "MinimalLevels": {
    +      "NpgsqlRest": "Debug"
    +    }
    +  },
    +
    +  // Enable SQL file endpoints (scan sql/ directory recursively)
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    Now you can start NpgsqlRest without any command line arguments:

    code
    ❯ ./npgsqlrest
    +[12:55:09.738 DBG] ----> Starting with configuration(s): JsonConfigurationProvider for 'appsettings.json' (Optional), JsonConfigurationProvider for 'appsettings.Development.json' (Missing), CommandLineConfigurationProvider [NpgsqlRest]
    +[12:55:09.750 DBG] ----> Logging enabled: Console (minimum level: Verbose) [NpgsqlRest]
    +[12:55:09.750 DBG] Using Default as main connection string: Host=localhost;Port=5432;Database=mydb;Username=postgres;Password=******;Application Name=example;Enlist=False;No Reset On Close=True [NpgsqlRest]
    +[12:55:09.750 DBG] Using connection retry options with strategy: RetrySequenceSeconds=1,3,6,12, ErrorCodes=08000,08003,08006,08001,08004,55P03,55006,53300,57P03,40001 [NpgsqlRest]
    +[12:55:09.753 DBG] Using EndpointSource PostgreSQL Source [NpgsqlRest]
    +[12:55:09.753 DBG] Routine caching is disabled. [NpgsqlRest]
    +[12:55:09.778 DBG] Using DataSource with schema 'public' for metadata queries. [NpgsqlRest]
    +[12:55:09.817 DBG] Function public.my_first_function mapped to GET /api/my-first-function has set HTTP by the comment annotation to GET /api/my-first-function [NpgsqlRest]
    +[12:55:09.818 DBG] Created endpoint GET /api/my-first-function [NpgsqlRest]
    +[12:55:09.821 INF] Started in 00:00:00.0850561, listening on http://localhost:8080, version 3.0.0.0 [NpgsqlRest]

    Next Steps

    Now that you have NpgsqlRest running:

    Comments

    + + + + \ No newline at end of file diff --git a/guide/sql-files.html b/guide/sql-files.html new file mode 100644 index 000000000..60fe273f2 --- /dev/null +++ b/guide/sql-files.html @@ -0,0 +1,88 @@ + + + + + + SQL File Endpoints Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    SQL File Endpoints

    NpgsqlRest creates REST API endpoints directly from .sql files. No CREATE FUNCTION, no RETURNS TABLE, no LANGUAGE sql, no COMMENT ON FUNCTION — just the query itself.

    Source Code: Every example in this guide comes from the examples repository. Each function-based example has a _sql_file counterpart.

    How It Works

    At startup, for each .sql file matched by the configured glob pattern:

    1. The file is parsed — comments are extracted as annotations, SQL is split into statements on ; boundaries
    2. Each statement is described via PostgreSQL's wire protocol (Parse → Describe → Sync with SchemaOnly) — parameter types and return columns are inferred without executing the query
    3. A REST endpoint is created with the URL path derived from the filenameget-users.sql becomes /api/get-users

    This gives you static type checking — SQL errors are caught at startup, not at runtime. Your SQL files are validated against the actual database schema before the server accepts any requests:

    code
    SqlFileSource: /path/to/get-posts.sql:
    +error 42703: column u.id does not exist
    +  at line 3, column 12
    +  select u.id, u.name from users u
    +             ^

    This is the default behavior (ErrorMode: "Exit"). Set ErrorMode: "Skip" to log errors and continue startup instead. Individual statements can bypass Describe entirely with @returns, which is necessary when the SQL references objects that don't exist at startup (e.g. temp tables created at runtime).

    Configuration

    Enable SQL File Source in appsettings.json:

    json
    json
    {
    +  "NpgsqlRest": {
    +    "SqlFileSource": {
    +      "Enabled": true,
    +      "FilePattern": "sql/**/*.sql"
    +    }
    +  }
    +}

    FilePattern uses glob syntax: ** crosses directories, * matches filenames, ? matches a single character.

    By default (CommentsMode: "OnlyAnnotated" in the client since 3.17.0 — "OnlyWithHttpTag" is an identical-behavior alias), only files containing an HTTP annotation (or a plugin annotation that requests an endpoint, such as @mcp) become endpoints. This prevents accidental exposure of migration scripts or utility files. Set CommentsMode: "ParseAll" to make every matched file an endpoint.

    Other settings:

    SettingDefaultDescription
    ErrorModeExitExit fails fast at startup. Skip logs errors and continues
    CommentScopeAllAll parses every comment. Header only parses comments before the first statement
    UnnamedSingleColumnSettrueSingle-column queries return flat arrays (["a","b"]) instead of object arrays
    ResultPrefixresultPrefix for multi-command result keys (result1, result2, ...)
    SkipNonQueryCommandstrueTransaction control (BEGIN, COMMIT, etc.), DO blocks, SET/RESET are auto-skipped from response

    See SQL File Source Configuration for the complete reference.

    Single-Command Files

    A file with one SQL statement produces a standard endpoint:

    sql
    sql
    -- sql/get-users.sql
    +-- HTTP GET
    +select user_id, username, email, active from example_2.users;

    GET /api/get-users returns an array of objects:

    json
    json
    [{"userId": 1, "username": "alice", "email": "alice@example.com", "active": true}, ...]

    Column names are converted to camelCase by the default NameConverter. Single-column queries return flat arrays — select name from users returns ["Alice","Bob"] not [{"name":"Alice"},...] (configurable via UnnamedSingleColumnSet).

    HTTP Verb Detection

    Without an explicit HTTP annotation, the verb is inferred from the SQL: SELECT → GET, INSERT → PUT, UPDATE → POST, DELETE → DELETE, DO block → POST. Mixed mutations → most destructive wins (DELETE > POST > PUT). An explicit annotation always overrides: -- HTTP POST.

    Parameters

    SQL files use named parameters (:name, since 3.19.0) or PostgreSQL positional parameters ($1, $2, ...) — one style per file.

    Named Parameters (:name)

    The placeholder is the parameter name — no annotations needed:

    sql
    sql
    -- sql/get-reports.sql
    +-- HTTP GET
    +select id, title, created_at
    +from reports
    +where created_at between :from_date and :to_date;

    GET /api/get-reports?fromDate=2024-01-01&toDate=2024-12-31

    The API name goes through the same NameConverter routine parameters use (:from_datefromDate with the default camelCase converter). Under the hood the SQL is rewritten to native $N before it is described and executed — PostgreSQL never sees the :name form, so type inference and runtime behavior are identical to positional files.

    • Repetition collapses: the same name used multiple times — including across statements in a multi-command file — is one parameter (where :user_id = author_id or :user_id = editor_id takes a single userId value).
    • Claim mappings hook up by placeholder name: select :_user_id under @authorize + @user_parameters binds the mapped claim with zero annotations.
    • Annotations match by name where still needed: @param from_date default null (defaults), @param :from_date timestamptz (type hint), or the retype-without-rename form @param from_date type is timestamptz.
    • The tokenizer knows SQL: strings, comments, and dollar-quoted bodies are untouched; ::int casts, := calls, and numeric slice bounds (a[1:3]) never match. One caveat: an array slice with a variable bound must be written with a space (a[1 : n]).
    • Mixing $N and :name in one file is rejected at startup. (JDBC-style ? is deliberately not supported — ?, ?|, ?&, @? are PostgreSQL's own jsonb operators.)

    Positional Parameters ($N)

    The @param annotation gives positional parameters meaningful names and optionally overrides the type:

    sql
    sql
    -- sql/get-reports.sql
    +-- HTTP GET
    +-- @param $1 from_date
    +-- @param $2 to_date
    +select id, title, created_at
    +from reports
    +where created_at between $1 and $2;

    GET /api/get-reports?from_date=2024-01-01&to_date=2024-12-31

    Without @param, the raw positional names work: ?$1=2024-01-01&$2=2024-12-31.

    Type Hints

    When PostgreSQL can't infer a parameter's type from context (e.g. select set_config('key', $1, true)$1 is ambiguous), add a type to @param:

    sql
    sql
    -- @param $1 user_id integer
    +-- @param $2 active boolean

    The type is used during the Describe step so PostgreSQL can resolve the parameter.

    Default Values

    Positional parameters must always be bound — unlike function parameters, there's no native DEFAULT clause. The @param annotation fills this gap:

    sql
    sql
    -- @param $1 status default 'active'
    +-- @param $2 limit integer = 50

    When a parameter with a default is not provided in the request, the default value is bound. Parameters with defaults become optional in generated TypeScript (? suffix) and OpenAPI (required: false).

    Value syntax follows SQL conventions: null → SQL NULL, 'text' → string, 42 → number, true → boolean.

    Virtual Parameters (@define_param)

    @define_param creates HTTP parameters that are not bound to the SQL query. They exist for annotation placeholders and claim mapping:

    sql
    sql
    -- @define_param format text
    +-- @table_format = {format}

    The format parameter feeds into the {format} placeholder without appearing in the SQL. Default type is text.

    Multi-Command Files

    A file with multiple statements (separated by ;) becomes one endpoint that executes everything in a single database round-trip via NpgsqlBatch. From the first example:

    sql
    sql
    -- HTTP GET
    +
    +-- @result first
    +select * from (
    +    values ('Hello, World!'), 
    +    ('This is my first SQL endpoint.'), 
    +    ('Enjoy coding in SQL!')
    +) as t(text);
    +
    +-- @result second
    +-- @single
    +select current_query() as query_text, current_user as user, current_timestamp as timestamp;

    Returns a JSON object with one key per statement:

    json
    json
    {
    +  "first": ["Hello, World!", "This is my first SQL endpoint.", "Enjoy coding in SQL!"],
    +  "second": {"queryText": "...", "user": "postgres", "timestamp": "..."}
    +}

    Result Rules

    • SELECT queries → JSON array of objects (or flat array for single-column with UnnamedSingleColumnSet)
    • INSERT/UPDATE/DELETE without RETURNING → rows-affected count (integer)
    • Transaction control (BEGIN, COMMIT, SET, DO, etc.) → auto-skipped from response (SkipNonQueryCommands: true)
    • All statements share the same parameters ($1, $2, ...) — the user sends each parameter once

    Positional Annotations

    Three annotations are positional — they apply to the next statement below them (or inline after ; on the same line):

    • @result name — rename result key (default: result1, result2, ...)
    • @single — return a single row as an object instead of an array
    • @skip — execute the statement but exclude it from the response
    sql
    sql
    select set_config('app.val', $1, true); -- @skip
    +-- @result data
    +-- @single
    +select current_setting('app.val') as value;

    @void

    @void forces the entire endpoint to return 204 No Content. All statements execute for side effects only — no JSON response, no result keys. This eliminates the need to @skip every individual statement.

    @returns — Skip Describe

    @returns skips the PostgreSQL Describe step entirely for a statement and resolves return columns from a type instead. This is a positional annotation.

    When to use it: when a statement references objects that don't exist at startup — typically temp tables created inside DO blocks.

    sql
    sql
    -- @returns my_result_type
    +-- @result data
    +-- @single
    +select * from _result;

    The type must exist in the database at startup. Columns are resolved from pg_catalog.

    Three forms:

    • @returns composite_type — resolve columns from the composite type definition
    • @returns scalar_type (e.g. @returns integer, @returns json) — single-column result
    • @returns void — no columns, void result (differs from @void which still runs Describe)

    DO Blocks and Limitations

    PostgreSQL DO blocks cannot receive $N parameters — this is a PostgreSQL language limitation, not an NpgsqlRest one. DO blocks also cannot return values. Multi-command SQL files work around this:

    Passing parameters in: Use set_config() with true (transaction-local) to store values, then current_setting() inside the DO block. Or use a temp table bridge with @skip:

    sql
    sql
    begin;
    +select set_config('app.user_id', $1, true);
    +do $$ begin
    +    insert into logs (user_id) values (current_setting('app.user_id')::int);
    +end; $$;
    +end;

    Getting results out: Create a temp table inside the DO block with ON COMMIT DROP, then SELECT from it with @returns to declare the return type.

    These are workarounds. When you need proper procedural logic with native parameters and return values, use a PostgreSQL function instead — that's what they're for.

    Existing Features Work Unchanged

    All NpgsqlRest features that existed before SQL File Source — authentication (@login, @logout, @authorize), file uploads (@upload), SSE (@sse), proxy (@proxy), HTTP custom types, CSV/Excel export (@raw, @table_format), caching (@cached), encryption, custom headers, composite types (@nested) — work identically in SQL files. The annotations are the same; only the endpoint source is different.

    The examples repository has a _sql_file counterpart for most examples demonstrating this.

    The Dev Loop: Watch Mode

    Run the server under watch mode while writing endpoint files:

    sh
    sh
    npgsqlrest ./config.json --watch

    Save a .sql file and the running API restarts with the change (~1s) — a new endpoint is immediately callable, a broken one prints its error while the rest keep serving (ErrorMode is relaxed to Skip while watching), and configured code generation (TypeScript client, HTTP files, OpenAPI) regenerates on every cycle, so frontend types follow your SQL as you type. Configuration files and database routines are watched too. For testing the same loop, see --test --watch.

    SQL Files vs Functions

    Use SQL files when: the query is declarative, involves multi-statement workflows, or the team prefers plain SQL files over DDL.

    Use functions when:

    • Procedural logic — functions receive parameters and return results natively. DO blocks require set_config/temp table workarounds.
    • Testing — functions support assert blocks inside repeatable migrations that run on every build, giving you database-level unit tests with rollback isolation. SQL files have no equivalent. See End-to-End Type Checking for examples.
    • OptimizationVOLATILE/STABLE/IMMUTABLE, COST, ROWS, PARALLEL hints.
    • Overloading — multiple function signatures per name.

    Use both together. Each endpoint source is independently enabled. SQL files can call functions, and HTTP custom types can reference any endpoint regardless of source.

    Comments

    + + + + \ No newline at end of file diff --git a/guide/sse.html b/guide/sse.html new file mode 100644 index 000000000..0ec31de28 --- /dev/null +++ b/guide/sse.html @@ -0,0 +1,226 @@ + + + + + + Server-Sent Events Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Server-Sent Events (SSE)

    NpgsqlRest can stream real-time updates from PostgreSQL to connected clients over Server-Sent Events — a one-way HTTP stream the browser consumes with the native EventSource API. You don't write any streaming code: a procedure simply emits events with PostgreSQL's RAISE statement, and NpgsqlRest broadcasts them to subscribers.

    This guide covers:

    1. How SSE works in NpgsqlRest
    2. Creating a publisher endpoint
    3. Subscribing from the browser
    4. Who receives events: scope
    5. Which RAISE level fires: event level
    6. Targeting specific recipients
    7. Splitting publish from subscribe
    8. Configuration
    9. A complete example: real-time chat

    Reference pages

    This is the conceptual walkthrough. For exact options see @sse, @sse_events_level, and @sse_events_scope.

    How SSE works

    The model is deliberately small. Mark a routine with @sse and it becomes a publisher: any RAISE INFO/NOTICE/WARNING inside its body is turned into an SSE event and broadcast to connected clients. The annotation also registers a subscribe URL that a browser connects to with EventSource.

    mermaid
    flowchart TD
    +    C["Client C
    +    POST /api/send-message"] --> EP["@sse publisher endpoint
    +    INSERT message + RAISE INFO '...'"]
    +    EP --> BC["NpgsqlRest broadcaster
    +    (one per process)"]
    +    BC -->|"SSE event (scope-filtered)"| A["Client A
    +    EventSource /api/send-message/info"]
    +    BC -->|"SSE event (scope-filtered)"| B["Client B
    +    EventSource /api/send-message/info"]

    Key points:

    • There is one process-wide broadcaster. Every EventSource connection reads from the same stream; the subscribe URL is an entry point, not a per-topic channel.
    • Who receives a given event is decided per event by its scope (everyone, authorized users, a matching security context, or a specific user).
    • Which RAISE statements become events is decided by the event level — and level matching is exact, not "this level and above".
    • A connected client is a pure listener — connecting never runs the procedure body.

    Creating a publisher endpoint

    Add @sse to any endpoint and emit events with RAISE. Here is the message-sending half of a chat app — it inserts a row and broadcasts the new message as JSON:

    sql
    sql
    create procedure send_message(
    +    _message_text text,
    +    _user_id text = null,
    +    _user_name text = null
    +)
    +language plpgsql
    +as $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    -- broadcast the new message to all connected, authorized clients
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'user_id', _user_id::int,
    +        'username', _user_name,
    +        'message_text', _message_text,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    +
    +comment on procedure send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize';
    sql
    sql
    -- sql/send-message.sql
    +/*
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize
    +@param $1 message_text text
    +@param $2 _user_id text = null
    +@param $3 _user_name text = null
    +@void
    +*/
    +do $$
    +declare
    +    _message_id int;
    +    _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (current_setting('request.user_id', true)::int,
    +            current_setting('request.user_name', true),
    +            $1)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id,
    +        'message_text', $1,
    +        'created_at', _created_at
    +    );
    +end;
    +$$;
    • @sse makes this a publisher and registers the subscribe URL (see below). Its default event level is info, so RAISE INFO is what gets broadcast here.
    • @sse_scope authorize means only authenticated clients receive the broadcast — see scope.
    • The body still runs normally when the endpoint is called (the INSERT happens); the RAISE is the additional broadcast.
    • Emit a payload by formatting it into the RAISE message — JSON is the natural choice for structured events.

    Subscribing from the browser

    @sse registers a connection URL at <endpoint-path>/<level> — for the send_message endpoint above, with its default info level, that's GET /api/send-message/info.

    You don't build that URL by hand. Set ClientCodeGen.ExportEventSources: true and NpgsqlRest generates a typed EventSource factory for each SSE endpoint as part of the TypeScript client. For send_message you get a createSendMessageEventSource() function — use it directly:

    js
    js
    import { createSendMessageEventSource } from './example8Api'; // generated client
    +
    +const events = createSendMessageEventSource();
    +events.onmessage = (e) => {
    +  const msg = JSON.parse(e.data);
    +  console.log(`${msg.username}: ${msg.message_text}`);
    +};

    Connecting does not run the procedure — the client just listens. Any time another request triggers the procedure (or any publisher that emits on this stream), the event arrives here.

    One-call subscribe + send

    The generated POST function for the same endpoint can open the stream for you too: pass an onMessage callback as sendMessage(request, onMessage) and the client subscribes, sends, and tidies up the EventSource in a single call.

    Who receives events: scope

    Scope answers which connected clients should receive this event. Set the default for an endpoint with @sse_scope (alias @sse_events_scope):

    ScopeWho receives the event
    allEvery connected client.
    authorizeOnly authenticated clients. Optionally restrict to roles / usernames / user IDs: @sse_scope authorize admin, manager.
    matchingClients whose security context matches the emitting request (by roles, usernames, and user IDs).
    code
    @sse_scope all
    +@sse_scope authorize
    +@sse_scope authorize admin, manager
    +@sse_scope matching

    The scope set on the annotation is the default for events from that endpoint. Individual events can override it at runtime — see targeting.

    Which RAISE level fires: event level

    An SSE endpoint listens at one PostgreSQL notice level. Only RAISE statements at that exact level become events:

    Endpoint levelRAISE INFORAISE NOTICERAISE WARNING
    info (default)✅ broadcast
    notice✅ broadcast
    warning✅ broadcast

    The level is exact, not hierarchical — an info endpoint does not also forward notice or warning. Set the level inline on @sse or with the dedicated annotation:

    code
    @sse                     -- default level: info → subscribe at <path>/info
    +@sse my_events on notice -- custom path + notice level → subscribe at <path>/my_events
    +@sse_events_level notice -- set the level separately

    The process-wide default level is DefaultServerSentEventsEventNoticeLevel (default INFO); see configuration.

    Targeting specific recipients

    Beyond the endpoint's default scope, a single event can pick its own audience at runtime using RAISE … USING hint. The hint string is a scope expression:

    sql
    sql
    -- broadcast to everyone, regardless of the endpoint's default scope
    +raise notice 'System maintenance in 5 minutes' using hint = 'all';
    +
    +-- only admins
    +raise notice '%' using hint = 'authorize admin', message;
    +
    +-- only specific users, by username or id
    +raise info 'Your report is ready' using hint = format('authorize %s', _user_id);

    This is what makes per-user notifications possible: an endpoint that processes a job can notify just the user who owns it with using hint = format('authorize %s', _user_id), even though the broadcaster is shared.

    Request correlation

    When a request carries an execution-id header (ExecutionIdHeaderName, default X-NpgsqlRest-ID) and an EventSource includes the same id as a query parameter, events are also filtered to that execution id — useful for streaming the progress of one specific long-running call back to its initiator.

    Splitting publish from subscribe

    In the chat example, one endpoint both sends and is subscribed to. Often you want them separate — for example, a privileged action emits events, but ordinary users subscribe. Use a subscribe-only endpoint (its body never runs — it exists only to register the URL) plus one or more emitter endpoints that broadcast on the same level/scope.

    sql
    sql
    -- subscribe-only: clients connect here, body never executes
    +create procedure user_events_subscribe()
    +language plpgsql as $$ begin perform 1; end; $$;
    +
    +comment on procedure user_events_subscribe() is '
    +HTTP GET
    +@authorize
    +@sse
    +@sse_scope authorize';
    +
    +-- emitter: a privileged action that notifies the affected user
    +create procedure update_user_roles(_target_user_id int, _roles text[])
    +language plpgsql as $$
    +begin
    +    -- ... perform the role update ...
    +    raise info 'roles updated'
    +        using hint = format('authorize %s', _target_user_id);
    +end;
    +$$;
    +
    +comment on procedure update_user_roles(int, text[]) is '
    +HTTP POST
    +@authorize manager
    +@sse
    +@sse_scope authorize';
    sql
    sql
    -- sql/user-events-subscribe.sql  (subscribe-only)
    +/*
    +HTTP GET
    +@authorize
    +@sse
    +@sse_scope authorize
    +@void
    +*/
    +select 1;
    +
    +-- sql/update-user-roles.sql  (emitter)
    +/*
    +HTTP POST
    +@authorize manager
    +@sse
    +@sse_scope authorize
    +@param $1 _target_user_id int
    +@param $2 _roles text[]
    +@void
    +*/
    +do $$
    +declare _target_user_id int = $1;
    +begin
    +    -- ... perform the role update ...
    +    raise info 'roles updated'
    +        using hint = format('authorize %s', _target_user_id);
    +end;
    +$$;

    The browser subscribes to GET /api/user-events-subscribe/info; when a manager calls update_user_roles, only the targeted user's connection receives the event.

    Configuration

    SSE works out of the box — no global enable flag. These options under NpgsqlRest tune the defaults:

    SettingDefaultDescription
    DefaultServerSentEventsEventNoticeLevel"INFO"Default PostgreSQL notice level for SSE events (INFO, NOTICE, or WARNING). Overridable per endpoint via @sse … on <level>.
    ServerSentEventsResponseHeaders{}Extra headers added to SSE responses.
    WarnUnboundServerSentEventsNoticestrueLogs a one-time warning for a RAISE that matches the SSE level but sits on an endpoint with no @sse annotation (a likely missing publisher).
    json
    json
    {
    +  "NpgsqlRest": {
    +    "DefaultServerSentEventsEventNoticeLevel": "INFO",
    +    "ServerSentEventsResponseHeaders": {
    +      "X-Accel-Buffering": "no"
    +    }
    +  }
    +}

    Behind nginx

    SSE is a long-lived streaming response. If you run behind nginx, add X-Accel-Buffering: no (as above) so the proxy doesn't buffer the stream and delay events.

    Cache hits don't broadcast

    If a publisher endpoint is also @cached and a request is served from cache, the function body doesn't run — so no RAISE fires and no event is broadcast. That's correct behavior, but keep it in mind: don't cache an endpoint whose side effect is the broadcast.

    A complete example: real-time chat

    The pieces below form a minimal chat: a login, a publisher that sends + broadcasts, a history endpoint, and the browser subscription. (Cookie auth setup omitted — see the Authentication guide.)

    Tables

    sql
    sql
    create table messages (
    +    message_id int primary key generated always as identity,
    +    user_id int not null,
    +    username text not null,
    +    message_text text not null,
    +    created_at timestamptz not null default now()
    +);

    Send + broadcast (publisher)

    sql
    sql
    create procedure send_message(_message_text text, _user_id text = null, _user_name text = null)
    +language plpgsql as $$
    +declare _message_id int; _created_at timestamptz;
    +begin
    +    insert into messages (user_id, username, message_text)
    +    values (_user_id::int, _user_name, _message_text)
    +    returning message_id, created_at into _message_id, _created_at;
    +
    +    raise info '%', json_build_object(
    +        'message_id', _message_id, 'user_id', _user_id::int,
    +        'username', _user_name, 'message_text', _message_text, 'created_at', _created_at);
    +end;
    +$$;
    +
    +comment on procedure send_message(text, text, text) is '
    +HTTP POST
    +@authorize
    +@user_parameters
    +@sse
    +@sse_scope authorize';

    Load history (plain endpoint, no SSE)

    sql
    sql
    create function get_messages()
    +returns setof messages
    +language sql as $$
    +  select * from messages order by created_at asc;
    +$$;
    +
    +comment on function get_messages() is '
    +HTTP GET
    +@authorize';

    Browser — using the generated TypeScript client, no hand-written fetch:

    js
    js
    import { getMessages, sendMessage, createSendMessageEventSource } from './example8Api';
    +
    +// 1. load history (typed, generated)
    +const { response: history } = await getMessages();
    +history?.forEach(renderMessage);
    +
    +// 2. subscribe to new messages
    +const events = createSendMessageEventSource();
    +events.onmessage = e => renderMessage(JSON.parse(e.data));
    +
    +// 3. send a message — every authorized subscriber receives the broadcast
    +await sendMessage({ messageText: 'Hello!' });

    Because the broadcast uses @sse_scope authorize, every signed-in client connected to the stream sees each new message in real time. All three functions — getMessages, sendMessage, and createSendMessageEventSource — are generated from your SQL; you don't write the HTTP calls.

    See it in the examples

    Comments

    + + + + \ No newline at end of file diff --git a/guide/testing.html b/guide/testing.html new file mode 100644 index 000000000..9bdcadf12 --- /dev/null +++ b/guide/testing.html @@ -0,0 +1,221 @@ + + + + + + Testing Guide | NpgsqlRest + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content
    AI-assisted, verified against source

    Testing

    NpgsqlRest ships a built-in SQL test runner: write tests for your endpoints as plain .sql files and run them with npgsqlrest --test. A test arranges data with ordinary SQL, invokes a real endpoint in-process (the complete pipeline — routing, authorization, parameter binding, execution, serialization — with no network and no running server), captures the response into a temp table, and asserts on it with ordinary SQL. Everything happens inside the test's own transaction, so tests leave no trace.

    sql
    sql
    -- tests/get_users_excludes_caller.test.sql
    +begin;
    +
    +-- ARRANGE
    +
    +insert into app.users (id, email, name) values (100, 'x@example.com', 'Fixture');
    +
    +-- ACT
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +
    +-- ASSERT
    +
    +select status = 200, 'authenticated caller gets 200'
    +from _response;
    +select body::jsonb @> '[{"email": "x@example.com"}]', 'the fixture user is listed'
    +from _response;
    +
    +rollback;
    code
    NpgsqlRest test runner — 9 file(s)
    +PASS  tests/get_users_excludes_caller.test.sql  (2 assertions, 52ms)
    +...
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +
    +endpoint coverage: 2/2 (100%)

    This guide covers the full feature. The configuration reference lives at Test Runner configuration; three complete working projects live in the repository: examples/19_testing_basic, examples/20_testing_newdb, and examples/21_testing_isolation.

    Quick start

    1. Put a test file next to your endpoint SQL (the co-located layout):
    sql
    sql
    -- sql/normalize_email.sql  (the endpoint)
    +/*
    +HTTP GET
    +*/
    +select lower(trim(:email)) as normalized;
    sql
    sql
    -- sql/normalize_email.test.sql  (the test)
    +/*
    +GET /api/normalize-email?email=%20X%40Y.z%20
    +*/
    +select status = 200, 'endpoint responds' from _response;
    +select body::jsonb ->> 0 = 'x@y.z', 'trims and lowercases' from _response;
    1. Point the runner at the tests:
    json
    json
    {
    +  "TestRunner": {
    +    "FilePattern": "./sql/**/*.test.sql"
    +  }
    +}
    1. Run:
    sh
    sh
    npgsqlrest ./config.json --test

    That's the whole setup. Test files are automatically excluded from endpoint discoverySqlFileSource.SkipPattern defaults to "*.test.sql" — so an HTTP block inside a test is never mistaken for an endpoint annotation.

    How it works

    In --test mode the client builds the full endpoint middleware exactly as in normal operation — endpoints from database routines and/or SQL files, authentication, custom parameters, everything — but instead of starting the web server it runs the test files and exits with a result code.

    The critical property is connection affinity: an endpoint invoked from a test runs on the test's own connection, inside the test's own transaction. A test can begin, insert fixture rows, call an endpoint that sees those uncommitted rows, assert on the response, and rollback — the database is untouched afterwards. Each test file gets its own non-pooled physical connection (fresh session: no temp-table, GUC, or prepared-statement carryover), and files run in parallel (MaxParallelism, default = processor count). If a file never rolls back, closing its physical connection aborts the open transaction — that is the safety net.

    Test-mode invariants, applied automatically:

    • WrapInTransaction is forced off — the test file owns transaction control; the runner never injects BEGIN/COMMIT/ROLLBACK.
    • Response caching is disabled — a test never sees another test's cached response.
    • Code generation (HTTP files, TypeScript client, OpenAPI) is skipped — a test run never rewrites generated artifacts.

    Test file anatomy

    A test file is a sequence of SQL statements and HTTP blocks, executed strictly in order, statement by statement (like psql): each statement runs in autocommit unless the file opens its own transaction. Semicolon splitting understands comments, string literals with '' escapes, and dollar-quoted bodies — a do $$ … $$; block stays whole.

    Assertions

    A reported test is one of:

    A boolean-returning SELECT. If the first column is boolean, the statement is an assertion: the first row's value must be true (false or null fails; zero rows passes vacuously). The optional second column is the assertion's name, shown in the report and used as the JUnit test-case name:

    sql
    sql
    select count(*) = 3, 'exactly three users are seeded' from app.users;

    A do block. Passes unless it raises — assert inside a DO block raises SQLSTATE P0004, reported as a failure with the assert message. One DO block = one reported test:

    sql
    sql
    do $$ begin
    +    assert app.normalize_email(' X@Y.z ') = 'x@y.z', 'should trim and lowercase';
    +end $$;

    Any other statement is arrange/act — not counted; it only surfaces if it errors. Any SQL error (other than an assert) is reported as an error with its SQLSTATE, message, statement text, and file:line.

    Failing behavior is fail-fast per file: after the first failed/errored assertion the rest of the file does not run. Assertions that passed before the failure are still credited.

    HTTP blocks: invoking endpoints

    An HTTP request is embedded in a block comment whose first content line is a request line — a single-request subset of the standard .http file syntax:

    sql
    sql
    /*
    +POST /api/create-user
    +Content-Type: application/json
    +# @claim user_id=42
    +# @claim roles=admin
    +# @response created
    +
    +{"name": "Grace Hopper", "email": "grace@example.com"}
    +*/
    • Request line: [HTTP] METHOD /path[?query] [HTTP/x] — method is GET/POST/PUT/DELETE; the path must equal the endpoint's full path including UrlPathPrefix (default /api). A block comment whose first line is not a valid request line is an ordinary comment — ignored.
    • Headers: Name: Value lines after the request line.
    • Directives (before the body): # @claim name=value sets the acting principal (repeatable; no @claim = anonymous), # @response name names the captured response table.
    • Body: everything after the first blank line, verbatim.

    An HTTP block is an act step, not an assertion — the assertions are the SQL statements that follow it. One request per block; use multiple blocks for multiple calls.

    Endpoint kinds that cannot work in-process are rejected with a clear error: SSE, upload, login/logout (inject the principal with # @claim instead), and outbound proxy/HTTP-type endpoints (tests must not call external services). A request whose path matches no endpoint still runs — a test may assert a 404 deliberately — but logs a warning, since the most common cause is a path typo or a missing /api prefix.

    The response table

    Each HTTP block's response lands in its own fresh temp table on the test's connection — default _response (one block per file) or _response_1, _response_2, … (several). Columns: status int, body text, content_type text, headers jsonb, is_success boolean — all configurable. Need to see a captured response after the run (temp tables vanish with the rollback)? Set ResponseTempTable.DebugTable to mirror every response into a permanent, query-editor-friendly table.

    sql
    sql
    select status = 200, 'status ok' from _response;
    +select body::jsonb ->> 'email' = 'x@y.z', 'right user returned' from _response;
    +select headers ->> 'Content-Type' like 'application/json%', 'json response' from _response;

    Transactions: when to begin/rollback

    The file owns transaction control. Two patterns:

    • The test writes something → wrap it: begin; … rollback;. Everything — fixtures and endpoint writes — is discarded.
    • The test only reads → no transaction needed at all. Don't cargo-cult begin/rollback onto read-only tests.

    One caveat worth knowing: sequences are non-transactional. Every nextval() sticks even through rollback, so on a shared database a generated id depends on what ran before. Don't assert generated ids on a shared database — or give the test its own database (see per-test isolation below).

    Fixtures without inserting the whole database: deferrable constraints

    The classic fixture problem: to insert one orders row you need a user, which needs a company, which needs a country… and suddenly every test starts by populating half the schema. PostgreSQL solves this elegantly — and the rollback-based test pattern is exactly the situation the solution was made for.

    Declare foreign keys deferrable in your schema:

    sql
    sql
    create table posts (
    +    id int primary key,
    +    user_id int references users (user_id) deferrable,
    +    content text not null
    +);

    Then a test defers the checks and inserts only what it needs — in any order, referencing rows that never exist:

    sql
    sql
    begin;
    +
    +set constraints all deferred;
    +
    +-- one post by a user that is never inserted — legal, because the FK check
    +-- would run at COMMIT, and this transaction never commits
    +insert into posts (id, user_id, content) values (1, 999, 'fixture post');
    +
    +/*
    +GET /api/get-posts
    +*/
    +select body::jsonb -> 0 ->> 'content' = 'fixture post', 'fixture is served' from _response;
    +
    +rollback;

    Deferrable constraints are checked at COMMIT — and a test that ends in rollback never gets there, so the checks simply never run. No fixture factories, no dependency-ordered builders, no "insert the world" preamble: each test states exactly the rows it is about, and the endpoint's LEFT JOINs resolve the missing references to null just as they would for genuinely absent data.

    Two things to know:

    • The constraint must be declared deferrableset constraints all deferred has no effect on the default NOT DEFERRABLE constraints. Making FKs deferrable is a one-time schema decision that costs nothing in production (they still check at commit).
    • This composes with the ordinary fixture style: insert the full graph in dependency order when the test is about the graph, and defer when it isn't. Example 20 demonstrates both side by side, and the end-to-end type checking post covers the technique in depth.

    Reusing SQL: includes

    Test files support psql-style includes: \i path (cwd-relative) and \ir path (relative to the including file). Semantics are as if you pasted the content yourself: SQL statements and HTTP blocks are spliced in place, run on the test's connection inside its transaction, and HTTP blocks participate in response-table numbering.

    sql
    sql
    begin;
    +
    +\ir fixtures/extra_users.sql   -- reusable fixture, rolls back with the test
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select jsonb_array_length(body::jsonb) = 5,
    +       'three seeded + two fixture users are listed'
    +from _response;
    +
    +rollback;

    An included file that contains only comments is an annotation profile: included in a file's header, its annotations (@setup, @teardown, @connection, @tag) count as if written in-place — one shared profile can configure a whole family of tests. Includes nest (up to 16 levels), and cycles are detected and reported.

    Pattern: schema-relaxing system scripts. Because PostgreSQL DDL is transactional, an include can temporarily reshape the schema for the test — and the rollback restores everything. The classic use: a shared script that drops NOT NULL from columns that are irrelevant to most tests, so fixtures only mention the columns they are actually about:

    sql
    sql
    -- fixtures/relax_users.sql — make the noise columns optional for this test only
    +alter table users alter column legal_name drop not null;
    +alter table users alter column billing_address drop not null;
    +alter table users alter column marketing_consent drop not null;
    sql
    sql
    begin;
    +\ir fixtures/relax_users.sql
    +
    +-- insert ONLY what this test is about — the relaxed columns stay null
    +insert into users (id, email) values (100, 'fixture@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture listed' from _response;
    +
    +rollback;   -- the ALTERs roll back too — the schema is untouched

    This composes with deferrable constraints: defer the FKs, relax the NOT NULLs, and a fixture shrinks to exactly the columns and rows under test. One caveat: ALTER TABLE takes an exclusive lock until the transaction ends, so on a shared test database this serializes parallel tests touching the same table — it shines with per-test isolated databases, where the lock contends with nobody.

    Per-file annotations

    Four header annotations (leading -- comments before the first statement) configure an individual file — see their reference pages for details:

    AnnotationEffect
    -- @setup Step [Step ...]Run named steps before this file.
    -- @teardown Step [Step ...]Run named steps after this file — always.
    -- @connection NameRun this file (SQL + endpoint calls) on a named connection.
    -- @tag name [name ...]Tag the file for Tag/ExcludeTag filtering.

    All are repeatable; names may be whitespace- or comma-separated; steps run in written order.

    Setup, Teardown, and named steps

    TestRunner.Setup runs once before endpoint discovery; TestRunner.Teardown runs once at the end — always, best-effort, even on failure, Ctrl+C, SIGTERM, or a hard startup error. Steps run in the exact order written. Each step is either an inline object or a name from the reusable Steps registry:

    json
    json
    {
    +  "TestRunner": {
    +    "Steps": {
    +      "CreateDatabase":  { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up", "WorkingDirectory": "." },
    +      "DropDatabase":    { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    Three step shapes:

    • { "Sql": "..." } — SQL text, statement by statement, on the test connection or any named ConnectionStrings entry ("ConnectionName"). This is how create database works as a plain step — the runner never issues DDL on its own.
    • { "SqlFile": "..." } — same, from a file.
    • { "Command": "...", "WorkingDirectory": "..." } — an OS shell command.

    Every step also has an "Enabled" flag (default true): a disabled step is ignored wherever referenced — never an error. That's how the default configuration ships ready-made example steps (create/drop a test database, apply a schema file, run a migration tool, start/stop a Docker PostgreSQL) that you copy and flip on instead of typing.

    Random tokens: {rnd1}{rnd10} are random lowercase tokens (length = the digit), generated once, stable for the entire run, and substituted in connection strings and Setup/Teardown SQL alike — so the same unique database name lands in the connection string, the create step, and the drop step. {rndN_1}{rndN_9} are independent instances for when several distinct names of the same length are needed.

    The scenarios below are all combinations of these pieces.

    Scenario: dedicated test database per run

    Run every test against a fresh database created for this run — the app's real database is never touched. This is examples/20_testing_newdb in full:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Admin": "Host=localhost;Database=postgres;Username=postgres;Password=...",
    +    "Test":  "Host=localhost;Database=app_test_{rnd5};Username=postgres;Password=..."
    +  },
    +  "TestRunner": {
    +    "FilePattern": "./tests/**/*.test.sql",
    +    "ConnectionName": "Test",
    +    "Steps": {
    +      "CreateDatabase": { "Sql": "create database app_test_{rnd5}", "ConnectionName": "Admin" },
    +      "ApplyMigrations": { "Command": "bun db up --config=./db.js" },
    +      "DropDatabase": { "Sql": "drop database if exists app_test_{rnd5} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateDatabase", "ApplyMigrations" ],
    +    "Teardown": [ "DropDatabase" ]
    +  }
    +}

    The flow: Setup creates app_test_xxxxx on the Admin connection and migrates it → endpoints are discovered and type-checked against that database (ConnectionName: "Test") → tests run → Teardown drops it. {rnd5} guarantees parallel CI jobs never collide.

    Keep the test configuration in a separate overlay file so the same project runs normally without it:

    sh
    sh
    npgsqlrest ./config.json ./test-config.json --test

    Scenario: template database and per-test isolation

    For tests that need complete isolation — deterministic sequence ids, exclusive locks, destructive DDL — clone a template database per test file. This is examples/21_testing_isolation:

    json
    json
    {
    +  "ConnectionStrings": {
    +    "Admin":     "...Database=postgres...",
    +    "Test":      "...Database=app_test_{rnd5}...",
    +    "Isolated1": "...Database=app_iso_{rnd5_1}...",
    +    "Isolated2": "...Database=app_iso_{rnd5_2}..."
    +  },
    +  "TestRunner": {
    +    "ConnectionName": "Test",
    +    "Steps": {
    +      "CreateTemplate":    { "Sql": "create database app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "MigrateTemplate":   { "Command": "bun db up --db=app_template_{rnd5}" },
    +      "CreateRunDb":       { "Sql": "create database app_test_{rnd5} template app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "CreateIsolatedDb1": { "Sql": "create database app_iso_{rnd5_1} template app_template_{rnd5}", "ConnectionName": "Admin" },
    +      "DropIsolatedDb1":   { "Sql": "drop database if exists app_iso_{rnd5_1} with (force)", "ConnectionName": "Admin" }
    +    },
    +    "Setup":    [ "CreateTemplate", "MigrateTemplate", "CreateRunDb" ],
    +    "Teardown": [ "DropTestDb", "DropTemplate" ]
    +  }
    +}

    Migrations run once (into the template); every clone is a byte-identical, instant copy (CREATE DATABASE ... TEMPLATE is a file-level copy — milliseconds for a schema-sized database). Most tests share the run database; a test that needs isolation attaches its own clone with header annotations:

    sql
    sql
    -- @setup CreateIsolatedDb1
    +-- @teardown DropIsolatedDb1
    +-- @connection Isolated1
    +-- @tag isolation, slow
    +
    +/*
    +POST /api/create-user
    +Content-Type: application/json
    +
    +{"name": "Ada", "email": "ada@example.com"}
    +*/
    +select body::jsonb ->> 'id' = '4',
    +       'sequence ids are deterministic in a fresh clone'
    +from _response;

    The classic motivation is sequences: nextval() survives rollback, so on a shared database this assertion would depend on run order — in a private clone it is exact. The indexed tokens ({rnd5_1}, {rnd5_2}) let several isolated tests hold their own clone simultaneously under parallel execution. Put the three annotations in a shared profile (\ir shared/isolated_database.sql) and attaching isolation to a test becomes a one-liner.

    Scenario: external migration runners

    Command steps run anything — so any migration tool works as-is. The step inherits the process environment plus the run's {rnd} substitutions in its command line:

    json
    json
    // EF Core
    +{ "Command": "dotnet ef database update --connection \"Host=localhost;Database=app_test_{rnd5};...\"" }
    +
    +// Django
    +{ "Command": "python manage.py migrate", "WorkingDirectory": "./backend" }
    +
    +// Flyway
    +{ "Command": "flyway -url=jdbc:postgresql://localhost/app_test_{rnd5} migrate" }
    +
    +// psql — plain SQL migrations, no tooling at all
    +{ "Command": "psql -d app_test_{rnd5} -f ./migrations/schema.sql" }

    Or skip external tools entirely: SqlFile steps run migration scripts statement-by-statement on any named connection — no client tooling required in the CI image.

    Scenario: Docker

    Because Setup/Teardown are ordered shell commands, the runner can own the entire database lifecycle, container included:

    json
    json
    {
    +  "TestRunner": {
    +    "Setup": [
    +      { "Command": "docker run -d --name npgsqlrest-test-pg -e POSTGRES_PASSWORD=test -p 54329:5432 postgres:17" },
    +      { "Command": "until docker exec npgsqlrest-test-pg pg_isready -U postgres; do sleep 0.3; done" },
    +      "CreateDatabase",
    +      "ApplyMigrations"
    +    ],
    +    "Teardown": [
    +      { "Command": "docker rm -f npgsqlrest-test-pg" }
    +    ]
    +  }
    +}

    Point the connection strings at Port=54329 and the whole test run is hermetic: npgsqlrest --test starts PostgreSQL, builds the schema, runs the tests, and removes the container — pass or fail.

    Scenario: testing least-privilege (PoLP) setups

    When the application connects as a restricted role, use two connections deliberately: fixtures and DDL on the Admin connection (via @setup steps or Setup), while the tests — and the endpoints they invoke — run as the restricted application role (TestRunner.ConnectionName). A test then proves not just behavior but permissions: if the app role is missing a grant, the endpoint fails in the test exactly as it would in production. An expected-denial test asserts the error directly:

    sql
    sql
    /*
    +POST /api/admin-only-report
    +# @claim user_id=7
    +*/
    +select status = 404, 'restricted role cannot reach the admin endpoint'
    +from _response;

    Filtering and tags

    Iterating on one test — Filter matches the cwd-relative path (substring, or glob with wildcards):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:filter=login

    Suites — files declare -- @tag and runs narrow by tag (case-insensitive; exclude wins; composes with Filter):

    sh
    sh
    npgsqlrest ./config.json --test --testrunner:tag=smoke --testrunner:excludetag=slow

    Watch mode

    sh
    sh
    npgsqlrest ./config.json --test --watch

    Runs everything once, then re-runs on changes until Ctrl+C (--watch is the shorthand for the top-level Watch:Enabled setting):

    • a changed test file re-runs alone (typically tens of milliseconds);
    • a changed endpoint file rebuilds the endpoints in-process and re-runs everything, printing the endpoint delta — break an endpoint's SQL and you immediately see - GET /api/get-users (endpoint dropped — check its SQL file for errors) plus the failing tests; fix it and the next save brings it back. No restart, ever;
    • a database routine changecreate or replace/drop/comment on a function in psql, or a migration touching routines — rebuilds endpoints and re-runs everything too (— change detected (database) —). Detection polls the routine discovery query itself, hashed server-side (every 2s by default; Watch:DatabasePollingInterval), so it fires exactly when the discovered endpoints change and never on unrelated tables or temp objects;
    • any other changed .sql under the test tree (a fixture whose dependents are unknown) re-runs everything.

    Teardown runs once on exit — Ctrl+C and SIGTERM are intercepted and the test database is still dropped, even under wrappers like bun run/npm run. A graceful stop exits 0: watch is a dev loop, not a CI gate.

    Server watch

    The same flag without --test watches the running server: npgsqlrest ./config.json --watch restarts it on SQL file, configuration, and database routine changes, regenerating the TypeScript client and HTTP files on every cycle. See Watch Mode configuration.

    Endpoint coverage

    The runner knows the entire API surface it built and records every endpoint the tests invoked — so after a full run it reports the API-level analogue of code coverage, naming the endpoints no test touches:

    code
    endpoint coverage: 3/4 (75%)
    +        untested: POST /api/delete-user

    On by default for full runs (one line); suppressed automatically when the run is narrowed by Filter/Tag; forced with Coverage: true / silenced with false. CoverageThreshold: 100 turns it into a CI gate: an otherwise-green run that misses an endpoint exits 2 — forgetting to write a test for a new endpoint fails the build, by name. "Covered" means invoked at least once; untestable kinds (SSE, upload, login/logout, outbound proxy) are excluded from the ratio.

    Reporting, logging, CI

    Console report: PASS/FAIL/ERROR per file with per-assertion detail on failure (name, file:line, failing statement). DetailedReport: true additionally lists passed assertions, full failing SQL, and captured raise notice output. Colors match the Serilog console theme and are stripped automatically when output is piped.

    Log channel: the runner logs on its own NpgsqlRestTest channel — discovery at Debug, every executed statement and HTTP invocation at Verbose, raise notice by severity. Typical dev setup — mute the app, watch the tests:

    json
    json
    { "Log": { "MinimalLevels": { "NpgsqlRest": "Off", "NpgsqlRestClient": "Off", "NpgsqlRestTest": "Verbose" } } }

    JUnit XML for CI (JUnitOutput: "./test-results.xml") — assertion names become test-case names. Exit codes: 0 pass · 1 failures · 2 errors / coverage gate · 3 setup/config error · 4 no tests found. A minimal GitHub Actions job:

    yaml
    yaml
    - run: npgsqlrest ./config.json ./test-config.json --test --testrunner:junitoutput=results.xml
    +- uses: dorny/test-reporter@v1
    +  if: always()
    +  with: { name: SQL tests, path: results.xml, reporter: java-junit }

    Troubleshooting

    • no endpoint matches GET /api/x — the response will be a 404 — path typo or missing UrlPathPrefix (default /api) in the request line.
    • PASS ... (no assertions) (flagged) — the file ran but contained no boolean-SELECT/DO-block assertion; check that your assert's first column is a boolean.
    • A test passes alone but fails in the full run — shared-state leak: an uncommitted-fixture assumption, a committed write without rollback, or a sequence-id assertion on a shared database. Wrap writes in begin/rollback, or isolate the test with a per-file clone.
    • Unsupported endpoint error — SSE/upload/login/logout/proxy endpoints cannot be invoked in-process by design; test login flows by injecting # @claim instead.
    • Leftover *_{rnd} databases — a run was killed with SIGKILL (nothing can intercept that), or Keep: true was on. Drop them manually; every graceful path (including Ctrl+C, SIGTERM, and hard startup errors) tears down automatically.

    Reference

    Comments

    + + + + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json new file mode 100644 index 000000000..2c1933c7f --- /dev/null +++ b/hashmap.json @@ -0,0 +1 @@ +{"about.md":"tQJT3Mf7","annotations_allow-anonymous.md":"B0UuFGg2","annotations_authorize.md":"CK7v-SQu","annotations_basic-auth-command.md":"Csb6mha2","annotations_basic-auth-realm.md":"DsMX0SDT","annotations_basic-auth.md":"ClrT2h43","annotations_body-parameter-name.md":"BfhRegXI","annotations_buffer-rows.md":"BrfXbHRV","annotations_cache-expires-in.md":"BRoExkyw","annotations_cache-profile.md":"YD0xPH6k","annotations_cached.md":"Z17IaMQ8","annotations_column-names.md":"BgCUEwzC","annotations_command-timeout.md":"DGZAbOuK","annotations_connection.md":"kVRk6UjZ","annotations_custom-parameters.md":"C_JoJInz","annotations_define-param.md":"BEpTOvZh","annotations_disabled.md":"DLiSD84Q","annotations_enabled.md":"w_f0uh0_","annotations_encrypt-decrypt.md":"BVL3DPHk","annotations_error-code-policy.md":"CcDC1Uqp","annotations_http-type.md":"Yj3KYiqW","annotations_http.md":"DPCDKDD2","annotations_index.md":"DSU3OO0a","annotations_internal.md":"yWqX_Gop","annotations_interval-format.md":"qinH4egR","annotations_login.md":"CNtMBAwP","annotations_logout.md":"Cle7WE1K","annotations_mcp.md":"CUGE4v1n","annotations_nested.md":"G3R18jm5","annotations_new-line.md":"9HsK47GA","annotations_openapi.md":"CYi2hImP","annotations_param.md":"DCLc58O5","annotations_parameter-hash.md":"DAN4SbBB","annotations_parameter-substitution.md":"BAnY-Etv","annotations_path.md":"4EcrWuEH","annotations_proxy-out.md":"C6XnOMaD","annotations_proxy.md":"C1XKqZ1u","annotations_query-string-null-handling.md":"bDnQmkU1","annotations_rate-limiter-policy.md":"C6rNxcxq","annotations_raw.md":"mVR52W_-","annotations_request-headers-mode.md":"CiBM2WBe","annotations_request-headers-parameter-name.md":"B1w65j2m","annotations_request-param-type.md":"D2ADbicS","annotations_resolved-parameters.md":"CtIRul_U","annotations_response-headers.md":"BjZbt4Z0","annotations_response-null-handling.md":"BXujxmf9","annotations_result-name.md":"oSFavaHs","annotations_retry-strategy.md":"DJkjr3eT","annotations_returns.md":"BXcLtPWm","annotations_security-sensitive.md":"iGNlRYXu","annotations_separator.md":"Bh3F0ZIY","annotations_single.md":"6tuGsQhF","annotations_skip.md":"Dh_dVMzK","annotations_sse-events-level.md":"BzejI_6s","annotations_sse-events-scope.md":"DSBEdrmZ","annotations_sse.md":"VtYFy1W4","annotations_table-format.md":"BKXSdBmi","annotations_tags.md":"DmTy6McN","annotations_test-claim.md":"B8Axvirh","annotations_test-connection.md":"CBXd7WXg","annotations_test-response.md":"BIQYW_7j","annotations_test-setup.md":"CEjyi1f2","annotations_test-tag.md":"CzXuyAz7","annotations_test-teardown.md":"DrTOzo2w","annotations_tsclient.md":"BNgVXSoY","annotations_upload.md":"BJPkIDDG","annotations_user-context.md":"D9DNdqIU","annotations_user-parameters.md":"RuNgxxKs","annotations_validate.md":"BYjsje_G","annotations_void.md":"CdJ8Ec9f","blog_case-study-zero-backend-code.md":"BnEeD2xb","blog_csv-excel-ingestion-postgresql-npgsqlrest.md":"Rjqy9NyI","blog_custom-types-multiset-rest-api.md":"BEa032jW","blog_database-level-security-postgresql-authentication.md":"CI9eBklt","blog_draft-anniversary-vietnam-of-computer-science.md":"D7rY_yx-","blog_draft-npgsqlrest-vs-sqlpage.md":"DbSL1Z8X","blog_end-to-end-static-type-checking-postgresql-typescript.md":"B_IRfXH-","blog_excel-export-table-format-postgresql-npgsqlrest.md":"BuWwG6CQ","blog_external-api-calls-postgresql-http-types.md":"C9zYiqiX","blog_index.md":"CTEJ27sE","blog_mcp-server-postgresql-ai-tools-npgsqlrest.md":"DvISemRM","blog_multiple-auth-schemes-rbac-external-providers.md":"PAYr3KYB","blog_npgsqlrest-3.13-production-patterns.md":"jTlztVH-","blog_npgsqlrest-3.19-sql-test-runner-watch-mode.md":"CeVn9yDN","blog_npgsqlrest-vs-postgrest-supabase-comparison.md":"00RFbAF5","blog_optimization-labels-101.md":"0ZBmELgS","blog_passkey-sql-auth.md":"BRkr8TRo","blog_performance-scalability-high-availability-npgsqlrest.md":"73MKg7Tu","blog_postgresql-bi-server-excel-csv-basic-auth.md":"C9mEJphA","blog_postgresql-rest-api-benchmark-2024.md":"DwEQmIoA","blog_postgresql-rest-api-benchmark-2025.md":"Tbw6Blag","blog_postgresql-rest-api-benchmark-2026.md":"CJcg5B7z","blog_real-time-chat-postgresql-sse-npgsqlrest.md":"Fd5M97qI","blog_reverse-proxy-postgresql-ai-service-npgsqlrest.md":"BtaYnMBE","blog_secure-image-uploads-postgresql-typescript.md":"BCA4okme","blog_sql-file-source-rest-api-from-plain-sql.md":"czCJfcgj","blog_sql-rest-api.md":"Dv5ncwRQ","blog_the-backend-that-writes-itself-presentation.md":"gFR1Oe0-","blog_the-power-of-simplicity.md":"DP76HzpZ","blog_typescript-codegen-walkthrough.md":"DU5mkobw","blog_web-scraping-postgresql-http-types-xml.md":"CCovU_63","blog_what-have-stored-procedures-ever-done-for-us.md":"DbQNwRp9","config_antiforgery.md":"7miFkqvJ","config_auth.md":"BTlaq10R","config_authentication-options.md":"CzhPcVla","config_basic-auth-config.md":"DZCrOE7U","config_cache-options.md":"Cq5Q0JXd","config_claims-mapping.md":"DxUSHFxx","config_codegen.md":"opHl7x9x","config_command-retry.md":"BgAKKMgf","config_config-section.md":"CwHYEVn1","config_connection.md":"OYNJshxp","config_cors.md":"Cusa--1Y","config_data-protection.md":"Djdf76Tb","config_error-handling.md":"DjvdMMV4","config_external-auth.md":"bAqNP7C3","config_forwarded-headers.md":"CGTpUREz","config_health-checks.md":"BpWdFu-y","config_http-client.md":"dRXgMQQQ","config_http-files.md":"B6K6vnkA","config_index.md":"CXfhm2PI","config_latest.md":"BHuOeOrq","config_logging.md":"B2g4l4DC","config_mcp.md":"D08gYjRL","config_npgsqlrest.md":"CnfMgb40","config_openapi.md":"BCg7CKgl","config_passkey-auth.md":"DHQzx-GX","config_proxy.md":"DOTosU06","config_rate-limiter.md":"wLNPwvhE","config_response-compression.md":"CMmom6qe","config_routine-options.md":"VEEBtjYd","config_security-headers.md":"D2EP-6xq","config_server.md":"BCPr-8rO","config_sql-file-source.md":"5_GwGm0C","config_static-files.md":"CxiUeSZr","config_stats.md":"CXkhOJdL","config_table-format.md":"CKOmZ6yI","config_test-runner.md":"D9PY_12B","config_thread-pool.md":"CQCaprkR","config_top-level.md":"F9S_nf2B","config_uploads.md":"DoJGjd4G","config_validation.md":"lHNUsfic","config_watch.md":"B_iL-Wg4","examples_index.md":"D4o4oSds","guide_annotations.md":"DVRiz6k_","guide_authentication.md":"BaKZBV2r","guide_changelog_index.md":"B7c1xVM5","guide_changelog_v3.0.0.md":"BCZYSJDP","guide_changelog_v3.0.1.md":"C5QoKstF","guide_changelog_v3.1.0.md":"Xl_94wGw","guide_changelog_v3.1.1.md":"CP1dTmO5","guide_changelog_v3.1.2.md":"DaCWRd65","guide_changelog_v3.1.3.md":"BMixLZ4Y","guide_changelog_v3.10.0.md":"CCimYYDV","guide_changelog_v3.11.0.md":"yn587hBX","guide_changelog_v3.11.1.md":"DxsV24Au","guide_changelog_v3.12.0.md":"BDVbkMod","guide_changelog_v3.13.0.md":"kmKD3jrx","guide_changelog_v3.14.0.md":"DqJvVHhw","guide_changelog_v3.15.0.md":"DiRcMMNc","guide_changelog_v3.15.1.md":"EipfUUTu","guide_changelog_v3.15.2.md":"B_9nMiuK","guide_changelog_v3.16.0.md":"z16WdNnJ","guide_changelog_v3.16.1.md":"twyBHeJH","guide_changelog_v3.16.2.md":"DJyi_HNy","guide_changelog_v3.16.3.md":"BaS-l5LJ","guide_changelog_v3.17.0.md":"DCqJkXNp","guide_changelog_v3.18.0.md":"DhzJiwQ7","guide_changelog_v3.18.1.md":"BUjRzlAL","guide_changelog_v3.18.2.md":"D9Kg93qi","guide_changelog_v3.19.0.md":"CnLwz7zb","guide_changelog_v3.2.0.md":"DqMIq3GM","guide_changelog_v3.2.1.md":"jsATl_CJ","guide_changelog_v3.2.2.md":"DCWOjWem","guide_changelog_v3.2.3.md":"BU0QClul","guide_changelog_v3.2.4.md":"DlHy9dKX","guide_changelog_v3.2.6.md":"Be7WrEBG","guide_changelog_v3.2.7.md":"DnOXNKXQ","guide_changelog_v3.3.0.md":"D_74S82V","guide_changelog_v3.3.1.md":"DBVEhTOX","guide_changelog_v3.4.0.md":"DWaLq7jU","guide_changelog_v3.4.1.md":"BjkyM2ol","guide_changelog_v3.4.2.md":"CwgXLNC8","guide_changelog_v3.4.3.md":"CwIJ6zQw","guide_changelog_v3.4.4.md":"Dun3dnAP","guide_changelog_v3.4.5.md":"CCdH9iK4","guide_changelog_v3.4.6.md":"DfISyzwO","guide_changelog_v3.4.7.md":"PS46_sBY","guide_changelog_v3.4.8.md":"C6Us0ANY","guide_changelog_v3.5.0.md":"COe0eWbp","guide_changelog_v3.6.0.md":"p7Q-OsI9","guide_changelog_v3.6.1.md":"CCDqHGJF","guide_changelog_v3.6.2.md":"Lg_0h_OI","guide_changelog_v3.6.3.md":"CcLrwLcH","guide_changelog_v3.7.0.md":"BsBWtBOJ","guide_changelog_v3.8.0.md":"D7bLiXbw","guide_changelog_v3.9.0.md":"Cm0S1Ft1","guide_configuration.md":"CAstKOPi","guide_faq.md":"Bxla4fTX","guide_http-types.md":"C0G-MJo7","guide_index.md":"C-0777ru","guide_installation.md":"KDi2i5hY","guide_logging.md":"CT7Uqiy7","guide_proxy.md":"CkN5fnQf","guide_quick-start.md":"DtKtFUQX","guide_sql-files.md":"yAmqICyN","guide_sse.md":"CiPMjFtJ","guide_testing.md":"Mc_wQPBP","index.md":"CcMKxpaP"} diff --git a/ignorance.png b/ignorance.png new file mode 100644 index 000000000..ac400dd29 Binary files /dev/null and b/ignorance.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..b4dc9eb24 --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + NpgsqlRest - Automatic REST API for PostgreSQL + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Skip to content

    NpgsqlRestYour SQL is the API

    Annotate PostgreSQL functions and SQL files with comments to declare HTTP routes, auth, caching, retries, and rate limits. Get a fast, typed REST API — no controllers, no models, no boilerplate. Then test it with SQL files too.

    ~ npgsqlrest
    $
    #1 of 14
    frameworks benchmarked,
    4,500+ req/s¹
    0
    lines of C# or Python
    in a production app²
    faster iteration on
    signature changes³
    MIT
    licensed,
    open source
    • Declare, don't code — caching, auth, retries, rate limiting — all declared as SQL annotations.
    • PostgreSQL at the center — the opposite of Clean Architecture: the database drives everything.
    • Types flow outward — PostgreSQL types generate TypeScript clients automatically.
    • No middle tier — no controllers, no models, no mapping layers, no boilerplate.
    • Iterate 5× faster — schema is the single source of truth; signature changes propagate to typed clients automatically, and an entire class of type-drift bugs simply cannot happen.
    • Production-grade by default — response caching, rate limiting, retries, PostgreSQL multi-host failover, load balancing, and Excel/HTML response rendering — configured in JSON, not custom middleware.
    • You write SQL, not a URL query language — unlike client-composed query APIs, the API surface is exactly the SQL you wrote: joins, CTEs, window functions — auditable with grep.
    • The whole dev loop in SQL — tests are plain .sql files run against real endpoints in-process (--test), and watch mode (--watch) restarts on SQL, config, and even database routine changes.
    • Built for the AI era — one type system to reason about, machine-verified output (schema check at startup, generated TypeScript checked by tsc), and @mcp tools for AI agents since v3.17.

    The Whole Idea, in 19 Slides

    PostgreSQL in. REST API, typed TypeScript client, and AI-agent tools out — with real, reproducible numbers from a product in production. Use the arrows, thumbnails, or your keyboard (←/→, F for fullscreen, N for speaker notes).

    The backend that writes itself · 20261 / 19
    Slide 1: The backend that writes itself — title

    A Fully Declarative Backend

    SQL declares what data. Annotations declare what behavior. Configuration declares what infrastructure. Tests declare what correctness — also in SQL. There is no imperative glue anywhere — no controllers, no services, no mappers to keep in sync. And it is built on the declarative language that has been running the world's data for 50 years — the one every developer, and every LLM, already knows: SQL.

    Declarative Annotations

    Declare what you want from your endpoint — caching, authorization, timeouts, retries, rate limiting — right where the SQL lives.

    SQL File

    ~ sql/users.sql
    $

    PostgreSQL Function

    ~ sql/get_users.sql
    $

    Tests Are SQL Files Too

    No test framework, no running server, no mocks. npgsqlrest --test invokes the real endpoint pipeline in-process, on the test's own transaction — insert fixtures, call the endpoint (it sees your uncommitted rows), assert with SQL, roll back.

    sql
    -- tests/get_users.test.sql
    +begin;
    +
    +insert into users (email) values ('fixture@example.com');
    +
    +/*
    +GET /api/get-users
    +# @claim user_id=1
    +*/
    +select status = 200, 'authenticated caller gets 200' from _response;
    +select body::jsonb @> '[{"email": "fixture@example.com"}]', 'fixture is listed' from _response;
    +
    +rollback;
    console
    $ npgsqlrest ./config.json --test
    +
    +PASS  tests/get_users.test.sql  (2 assertions, 52ms)
    +19 passed, 0 failed, 0 error(s)  —  19 assertions in 9 files
    +endpoint coverage: 2/2 (100%)

    Parallel isolated connections, throwaway test databases, per-test clones, tags, JUnit XML, and endpoint coverage with a CI threshold gate. And with --watch, the running server restarts on SQL file, configuration, and database routine changes — create or replace a function in psql and the endpoint is live seconds later, TypeScript client regenerated.

    From the Blog


    Build, Test, Publish and ReleaseLicenseGitHub StarsGitHub ForksCrafted with Claude
    ❤️ Support this project: Patreon · Buy Me a Coffee
    Released under the MIT License.
    Copyright © 2024-2026 VB Consulting
    + + + + \ No newline at end of file diff --git a/logo.gif b/logo.gif new file mode 100644 index 000000000..cc0fcaf84 Binary files /dev/null and b/logo.gif differ diff --git a/logo.png b/logo.png new file mode 100644 index 000000000..fb1563d5f Binary files /dev/null and b/logo.png differ diff --git a/logo_original.gif b/logo_original.gif new file mode 100644 index 000000000..d2a165254 Binary files /dev/null and b/logo_original.gif differ diff --git a/meme.jpeg b/meme.jpeg new file mode 100644 index 000000000..4837a1c99 Binary files /dev/null and b/meme.jpeg differ diff --git a/meme2.jpeg b/meme2.jpeg new file mode 100644 index 000000000..3ec2d8c56 Binary files /dev/null and b/meme2.jpeg differ diff --git a/meme3.png b/meme3.png new file mode 100644 index 000000000..ba0b30fae Binary files /dev/null and b/meme3.png differ diff --git a/polp/code1.png b/polp/code1.png new file mode 100644 index 000000000..522c8b397 Binary files /dev/null and b/polp/code1.png differ diff --git a/polp/code2.png b/polp/code2.png new file mode 100644 index 000000000..263182fc5 Binary files /dev/null and b/polp/code2.png differ diff --git a/polp/code3.png b/polp/code3.png new file mode 100644 index 000000000..c013002a1 Binary files /dev/null and b/polp/code3.png differ diff --git a/polp/code4.png b/polp/code4.png new file mode 100644 index 000000000..df70b9153 Binary files /dev/null and b/polp/code4.png differ diff --git a/polp/code5.png b/polp/code5.png new file mode 100644 index 000000000..b6b986bad Binary files /dev/null and b/polp/code5.png differ diff --git a/presentation/slide-1.png b/presentation/slide-1.png new file mode 100644 index 000000000..1b4e09c46 Binary files /dev/null and b/presentation/slide-1.png differ diff --git a/presentation/slide-1.webp b/presentation/slide-1.webp new file mode 100644 index 000000000..c3438f088 Binary files /dev/null and b/presentation/slide-1.webp differ diff --git a/presentation/slide-10.webp b/presentation/slide-10.webp new file mode 100644 index 000000000..b3a0df833 Binary files /dev/null and b/presentation/slide-10.webp differ diff --git a/presentation/slide-11.webp b/presentation/slide-11.webp new file mode 100644 index 000000000..b0abefad1 Binary files /dev/null and b/presentation/slide-11.webp differ diff --git a/presentation/slide-12.webp b/presentation/slide-12.webp new file mode 100644 index 000000000..da2d465d0 Binary files /dev/null and b/presentation/slide-12.webp differ diff --git a/presentation/slide-13.webp b/presentation/slide-13.webp new file mode 100644 index 000000000..82db37187 Binary files /dev/null and b/presentation/slide-13.webp differ diff --git a/presentation/slide-14.webp b/presentation/slide-14.webp new file mode 100644 index 000000000..890fb96e2 Binary files /dev/null and b/presentation/slide-14.webp differ diff --git a/presentation/slide-15.webp b/presentation/slide-15.webp new file mode 100644 index 000000000..81f8bc7b6 Binary files /dev/null and b/presentation/slide-15.webp differ diff --git a/presentation/slide-16.webp b/presentation/slide-16.webp new file mode 100644 index 000000000..45d162629 Binary files /dev/null and b/presentation/slide-16.webp differ diff --git a/presentation/slide-17.webp b/presentation/slide-17.webp new file mode 100644 index 000000000..fde5043f9 Binary files /dev/null and b/presentation/slide-17.webp differ diff --git a/presentation/slide-18.webp b/presentation/slide-18.webp new file mode 100644 index 000000000..435b5cf00 Binary files /dev/null and b/presentation/slide-18.webp differ diff --git a/presentation/slide-19.webp b/presentation/slide-19.webp new file mode 100644 index 000000000..181f7d1f8 Binary files /dev/null and b/presentation/slide-19.webp differ diff --git a/presentation/slide-2.webp b/presentation/slide-2.webp new file mode 100644 index 000000000..7cd6668b2 Binary files /dev/null and b/presentation/slide-2.webp differ diff --git a/presentation/slide-3.webp b/presentation/slide-3.webp new file mode 100644 index 000000000..0037379b9 Binary files /dev/null and b/presentation/slide-3.webp differ diff --git a/presentation/slide-4.webp b/presentation/slide-4.webp new file mode 100644 index 000000000..3385c29f8 Binary files /dev/null and b/presentation/slide-4.webp differ diff --git a/presentation/slide-5.webp b/presentation/slide-5.webp new file mode 100644 index 000000000..163376a3f Binary files /dev/null and b/presentation/slide-5.webp differ diff --git a/presentation/slide-6.webp b/presentation/slide-6.webp new file mode 100644 index 000000000..c2dcdd783 Binary files /dev/null and b/presentation/slide-6.webp differ diff --git a/presentation/slide-7.webp b/presentation/slide-7.webp new file mode 100644 index 000000000..612fcb13a Binary files /dev/null and b/presentation/slide-7.webp differ diff --git a/presentation/slide-8.webp b/presentation/slide-8.webp new file mode 100644 index 000000000..8296ca3e4 Binary files /dev/null and b/presentation/slide-8.webp differ diff --git a/presentation/slide-9.webp b/presentation/slide-9.webp new file mode 100644 index 000000000..008135351 Binary files /dev/null and b/presentation/slide-9.webp differ diff --git a/proto.jpeg b/proto.jpeg new file mode 100644 index 000000000..12e18a5a7 Binary files /dev/null and b/proto.jpeg differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 000000000..1730de995 --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://npgsqlrest.github.io/sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..2da9f871b --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://npgsqlrest.github.io/about.htmlhttps://npgsqlrest.github.io/annotations/allow-anonymous.htmlhttps://npgsqlrest.github.io/annotations/authorize.htmlhttps://npgsqlrest.github.io/annotations/basic-auth-command.htmlhttps://npgsqlrest.github.io/annotations/basic-auth-realm.htmlhttps://npgsqlrest.github.io/annotations/basic-auth.htmlhttps://npgsqlrest.github.io/annotations/body-parameter-name.htmlhttps://npgsqlrest.github.io/annotations/buffer-rows.htmlhttps://npgsqlrest.github.io/annotations/cache-expires-in.htmlhttps://npgsqlrest.github.io/annotations/cache-profile.htmlhttps://npgsqlrest.github.io/annotations/cached.htmlhttps://npgsqlrest.github.io/annotations/column-names.htmlhttps://npgsqlrest.github.io/annotations/command-timeout.htmlhttps://npgsqlrest.github.io/annotations/connection.htmlhttps://npgsqlrest.github.io/annotations/custom-parameters.htmlhttps://npgsqlrest.github.io/annotations/define-param.htmlhttps://npgsqlrest.github.io/annotations/disabled.htmlhttps://npgsqlrest.github.io/annotations/enabled.htmlhttps://npgsqlrest.github.io/annotations/encrypt-decrypt.htmlhttps://npgsqlrest.github.io/annotations/error-code-policy.htmlhttps://npgsqlrest.github.io/annotations/http-type.htmlhttps://npgsqlrest.github.io/annotations/http.htmlhttps://npgsqlrest.github.io/annotations/https://npgsqlrest.github.io/annotations/internal.htmlhttps://npgsqlrest.github.io/annotations/interval-format.htmlhttps://npgsqlrest.github.io/annotations/login.htmlhttps://npgsqlrest.github.io/annotations/logout.htmlhttps://npgsqlrest.github.io/annotations/mcp.htmlhttps://npgsqlrest.github.io/annotations/nested.htmlhttps://npgsqlrest.github.io/annotations/new-line.htmlhttps://npgsqlrest.github.io/annotations/openapi.htmlhttps://npgsqlrest.github.io/annotations/param.htmlhttps://npgsqlrest.github.io/annotations/parameter-hash.htmlhttps://npgsqlrest.github.io/annotations/parameter-substitution.htmlhttps://npgsqlrest.github.io/annotations/path.htmlhttps://npgsqlrest.github.io/annotations/proxy-out.htmlhttps://npgsqlrest.github.io/annotations/proxy.htmlhttps://npgsqlrest.github.io/annotations/query-string-null-handling.htmlhttps://npgsqlrest.github.io/annotations/rate-limiter-policy.htmlhttps://npgsqlrest.github.io/annotations/raw.htmlhttps://npgsqlrest.github.io/annotations/request-headers-mode.htmlhttps://npgsqlrest.github.io/annotations/request-headers-parameter-name.htmlhttps://npgsqlrest.github.io/annotations/request-param-type.htmlhttps://npgsqlrest.github.io/annotations/resolved-parameters.htmlhttps://npgsqlrest.github.io/annotations/response-headers.htmlhttps://npgsqlrest.github.io/annotations/response-null-handling.htmlhttps://npgsqlrest.github.io/annotations/result-name.htmlhttps://npgsqlrest.github.io/annotations/retry-strategy.htmlhttps://npgsqlrest.github.io/annotations/returns.htmlhttps://npgsqlrest.github.io/annotations/security-sensitive.htmlhttps://npgsqlrest.github.io/annotations/separator.htmlhttps://npgsqlrest.github.io/annotations/single.htmlhttps://npgsqlrest.github.io/annotations/skip.htmlhttps://npgsqlrest.github.io/annotations/sse-events-level.htmlhttps://npgsqlrest.github.io/annotations/sse-events-scope.htmlhttps://npgsqlrest.github.io/annotations/sse.htmlhttps://npgsqlrest.github.io/annotations/table-format.htmlhttps://npgsqlrest.github.io/annotations/tags.htmlhttps://npgsqlrest.github.io/annotations/test-claim.htmlhttps://npgsqlrest.github.io/annotations/test-connection.htmlhttps://npgsqlrest.github.io/annotations/test-response.htmlhttps://npgsqlrest.github.io/annotations/test-setup.htmlhttps://npgsqlrest.github.io/annotations/test-tag.htmlhttps://npgsqlrest.github.io/annotations/test-teardown.htmlhttps://npgsqlrest.github.io/annotations/tsclient.htmlhttps://npgsqlrest.github.io/annotations/upload.htmlhttps://npgsqlrest.github.io/annotations/user-context.htmlhttps://npgsqlrest.github.io/annotations/user-parameters.htmlhttps://npgsqlrest.github.io/annotations/validate.htmlhttps://npgsqlrest.github.io/annotations/void.htmlhttps://npgsqlrest.github.io/blog/DRAFT-anniversary-vietnam-of-computer-science.htmlhttps://npgsqlrest.github.io/blog/DRAFT-npgsqlrest-vs-sqlpage.htmlhttps://npgsqlrest.github.io/blog/case-study-zero-backend-code.htmlhttps://npgsqlrest.github.io/blog/csv-excel-ingestion-postgresql-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/custom-types-multiset-rest-api.htmlhttps://npgsqlrest.github.io/blog/database-level-security-postgresql-authentication.htmlhttps://npgsqlrest.github.io/blog/end-to-end-static-type-checking-postgresql-typescript.htmlhttps://npgsqlrest.github.io/blog/excel-export-table-format-postgresql-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/external-api-calls-postgresql-http-types.htmlhttps://npgsqlrest.github.io/blog/https://npgsqlrest.github.io/blog/mcp-server-postgresql-ai-tools-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/multiple-auth-schemes-rbac-external-providers.htmlhttps://npgsqlrest.github.io/blog/npgsqlrest-3.13-production-patterns.htmlhttps://npgsqlrest.github.io/blog/npgsqlrest-3.19-sql-test-runner-watch-mode.htmlhttps://npgsqlrest.github.io/blog/npgsqlrest-vs-postgrest-supabase-comparison.htmlhttps://npgsqlrest.github.io/blog/optimization-labels-101.htmlhttps://npgsqlrest.github.io/blog/passkey-sql-auth.htmlhttps://npgsqlrest.github.io/blog/performance-scalability-high-availability-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/postgresql-bi-server-excel-csv-basic-auth.htmlhttps://npgsqlrest.github.io/blog/postgresql-rest-api-benchmark-2024.htmlhttps://npgsqlrest.github.io/blog/postgresql-rest-api-benchmark-2025.htmlhttps://npgsqlrest.github.io/blog/postgresql-rest-api-benchmark-2026.htmlhttps://npgsqlrest.github.io/blog/real-time-chat-postgresql-sse-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/reverse-proxy-postgresql-ai-service-npgsqlrest.htmlhttps://npgsqlrest.github.io/blog/secure-image-uploads-postgresql-typescript.htmlhttps://npgsqlrest.github.io/blog/sql-file-source-rest-api-from-plain-sql.htmlhttps://npgsqlrest.github.io/blog/sql-rest-api.htmlhttps://npgsqlrest.github.io/blog/the-backend-that-writes-itself-presentation.htmlhttps://npgsqlrest.github.io/blog/the-power-of-simplicity.htmlhttps://npgsqlrest.github.io/blog/typescript-codegen-walkthrough.htmlhttps://npgsqlrest.github.io/blog/web-scraping-postgresql-http-types-xml.htmlhttps://npgsqlrest.github.io/blog/what-have-stored-procedures-ever-done-for-us.htmlhttps://npgsqlrest.github.io/config/antiforgery.htmlhttps://npgsqlrest.github.io/config/auth.htmlhttps://npgsqlrest.github.io/config/authentication-options.htmlhttps://npgsqlrest.github.io/config/basic-auth-config.htmlhttps://npgsqlrest.github.io/config/cache-options.htmlhttps://npgsqlrest.github.io/config/claims-mapping.htmlhttps://npgsqlrest.github.io/config/codegen.htmlhttps://npgsqlrest.github.io/config/command-retry.htmlhttps://npgsqlrest.github.io/config/config-section.htmlhttps://npgsqlrest.github.io/config/connection.htmlhttps://npgsqlrest.github.io/config/cors.htmlhttps://npgsqlrest.github.io/config/data-protection.htmlhttps://npgsqlrest.github.io/config/error-handling.htmlhttps://npgsqlrest.github.io/config/external-auth.htmlhttps://npgsqlrest.github.io/config/forwarded-headers.htmlhttps://npgsqlrest.github.io/config/health-checks.htmlhttps://npgsqlrest.github.io/config/http-client.htmlhttps://npgsqlrest.github.io/config/http-files.htmlhttps://npgsqlrest.github.io/config/https://npgsqlrest.github.io/config/latest.htmlhttps://npgsqlrest.github.io/config/logging.htmlhttps://npgsqlrest.github.io/config/mcp.htmlhttps://npgsqlrest.github.io/config/npgsqlrest.htmlhttps://npgsqlrest.github.io/config/openapi.htmlhttps://npgsqlrest.github.io/config/passkey-auth.htmlhttps://npgsqlrest.github.io/config/proxy.htmlhttps://npgsqlrest.github.io/config/rate-limiter.htmlhttps://npgsqlrest.github.io/config/response-compression.htmlhttps://npgsqlrest.github.io/config/routine-options.htmlhttps://npgsqlrest.github.io/config/security-headers.htmlhttps://npgsqlrest.github.io/config/server.htmlhttps://npgsqlrest.github.io/config/sql-file-source.htmlhttps://npgsqlrest.github.io/config/static-files.htmlhttps://npgsqlrest.github.io/config/stats.htmlhttps://npgsqlrest.github.io/config/table-format.htmlhttps://npgsqlrest.github.io/config/test-runner.htmlhttps://npgsqlrest.github.io/config/thread-pool.htmlhttps://npgsqlrest.github.io/config/top-level.htmlhttps://npgsqlrest.github.io/config/uploads.htmlhttps://npgsqlrest.github.io/config/validation.htmlhttps://npgsqlrest.github.io/config/watch.htmlhttps://npgsqlrest.github.io/examples/https://npgsqlrest.github.io/guide/annotations.htmlhttps://npgsqlrest.github.io/guide/authentication.htmlhttps://npgsqlrest.github.io/guide/changelog/https://npgsqlrest.github.io/guide/changelog/v3.0.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.0.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.1.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.1.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.1.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.1.3.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.10.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.11.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.11.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.12.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.13.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.14.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.15.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.15.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.15.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.16.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.16.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.16.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.16.3.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.17.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.18.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.18.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.18.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.19.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.3.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.4.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.6.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.2.7.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.3.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.3.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.3.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.4.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.5.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.6.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.7.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.4.8.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.5.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.6.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.6.1.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.6.2.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.6.3.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.7.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.8.0.htmlhttps://npgsqlrest.github.io/guide/changelog/v3.9.0.htmlhttps://npgsqlrest.github.io/guide/configuration.htmlhttps://npgsqlrest.github.io/guide/faq.htmlhttps://npgsqlrest.github.io/guide/http-types.htmlhttps://npgsqlrest.github.io/guide/https://npgsqlrest.github.io/guide/installation.htmlhttps://npgsqlrest.github.io/guide/logging.htmlhttps://npgsqlrest.github.io/guide/proxy.htmlhttps://npgsqlrest.github.io/guide/quick-start.htmlhttps://npgsqlrest.github.io/guide/sql-files.htmlhttps://npgsqlrest.github.io/guide/sse.htmlhttps://npgsqlrest.github.io/guide/testing.htmlhttps://npgsqlrest.github.io/ \ No newline at end of file diff --git a/sp1.jpeg b/sp1.jpeg new file mode 100755 index 000000000..4ad35e873 Binary files /dev/null and b/sp1.jpeg differ diff --git a/sp2.jpeg b/sp2.jpeg new file mode 100755 index 000000000..e2d2b337f Binary files /dev/null and b/sp2.jpeg differ diff --git a/sp3.jpeg b/sp3.jpeg new file mode 100755 index 000000000..e85abf1de Binary files /dev/null and b/sp3.jpeg differ diff --git a/sp4.jpeg b/sp4.jpeg new file mode 100755 index 000000000..8b550b943 Binary files /dev/null and b/sp4.jpeg differ diff --git a/sp5.jpeg b/sp5.jpeg new file mode 100755 index 000000000..88a87c2c8 Binary files /dev/null and b/sp5.jpeg differ diff --git a/system-diagram.png b/system-diagram.png new file mode 100644 index 000000000..320cfac6e Binary files /dev/null and b/system-diagram.png differ diff --git a/terminal.png b/terminal.png new file mode 100644 index 000000000..7b4d9e773 Binary files /dev/null and b/terminal.png differ diff --git a/vietnam/codd.png b/vietnam/codd.png new file mode 100644 index 000000000..449af25e4 Binary files /dev/null and b/vietnam/codd.png differ diff --git a/vietnam/evans.png b/vietnam/evans.png new file mode 100644 index 000000000..b2e5860fa Binary files /dev/null and b/vietnam/evans.png differ diff --git a/vietnam/vernon.png b/vietnam/vernon.png new file mode 100644 index 000000000..284c1861e Binary files /dev/null and b/vietnam/vernon.png differ diff --git a/vp-icons.css b/vp-icons.css new file mode 100644 index 000000000..ddc5bd8ed --- /dev/null +++ b/vp-icons.css @@ -0,0 +1 @@ +.vpi-social-github{--icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='black' d='M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")} \ No newline at end of file diff --git a/watch.gif b/watch.gif new file mode 100644 index 000000000..0466d0967 Binary files /dev/null and b/watch.gif differ